1 /* 2 * Copyright (c) 2010, 2017, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package javafx.scene.web; 27 28 import java.util.ResourceBundle; 29 30 import com.sun.javafx.application.PlatformImpl; 31 import com.sun.javafx.scene.ParentHelper; 32 import com.sun.javafx.scene.traversal.Algorithm; 33 import com.sun.javafx.scene.traversal.Direction; 34 import com.sun.javafx.scene.traversal.ParentTraversalEngine; 35 import com.sun.javafx.scene.traversal.TraversalContext; 36 import javafx.css.PseudoClass; 37 import javafx.geometry.Orientation; 38 import org.w3c.dom.html.HTMLDocument; 39 import org.w3c.dom.html.HTMLElement; 40 41 import javafx.application.ConditionalFeature; 42 import javafx.application.Platform; 43 import javafx.collections.FXCollections; 44 import javafx.collections.ObservableList; 45 import javafx.css.StyleableProperty; 46 import javafx.geometry.NodeOrientation; 47 import javafx.scene.Node; 48 import javafx.scene.control.Button; 49 import javafx.scene.control.ComboBox; 50 import javafx.scene.control.ListCell; 51 import javafx.scene.control.ListView; 52 import javafx.scene.control.Separator; 53 import javafx.scene.control.TextInputControl; 54 import javafx.scene.control.ToggleButton; 55 import javafx.scene.control.ToggleGroup; 56 import javafx.scene.control.ToolBar; 57 import javafx.scene.control.Tooltip; 58 import javafx.scene.image.Image; 59 import javafx.scene.image.ImageView; 60 import javafx.scene.input.KeyCode; 61 import javafx.scene.input.KeyEvent; 62 import javafx.scene.input.MouseEvent; 63 import javafx.scene.layout.ColumnConstraints; 64 import javafx.scene.layout.GridPane; 65 import javafx.scene.layout.Priority; 66 import javafx.scene.paint.Color; 67 import javafx.scene.text.Font; 68 import javafx.util.Callback; 69 70 import com.sun.javafx.scene.control.skin.FXVK; 71 import com.sun.javafx.scene.web.behavior.HTMLEditorBehavior; 72 import com.sun.webkit.WebPage; 73 import com.sun.javafx.webkit.Accessor; 74 75 import java.security.AccessController; 76 import java.security.PrivilegedAction; 77 78 import java.util.HashMap; 79 import java.util.Locale; 80 import java.util.Map; 81 import javafx.scene.Scene; 82 import javafx.scene.control.*; 83 import javafx.scene.layout.*; 84 import javafx.collections.ListChangeListener; 85 86 import static javafx.geometry.NodeOrientation.*; 87 import javafx.print.PrinterJob; 88 89 import static javafx.scene.web.HTMLEditorSkin.Command.*; 90 91 /** 92 * HTML editor skin. 93 * 94 * @see HTMLEditor 95 * @since 9 96 */ 97 public class HTMLEditorSkin extends SkinBase<HTMLEditor> { 98 99 /*************************************************************************** 100 * * 101 * Private fields * 102 * * 103 **************************************************************************/ 104 105 private GridPane gridPane; 106 107 private ToolBar toolbar1; 108 private ToolBar toolbar2; 109 110 private Button cutButton; 111 private Button copyButton; 112 private Button pasteButton; 113 114 // private Button undoButton; 115 // private Button redoButton; 116 117 private Button insertHorizontalRuleButton; 118 119 private ToggleGroup alignmentToggleGroup; 120 private ToggleButton alignLeftButton; 121 private ToggleButton alignCenterButton; 122 private ToggleButton alignRightButton; 123 private ToggleButton alignJustifyButton; 124 125 private ToggleButton bulletsButton; 126 private ToggleButton numbersButton; 127 128 private Button indentButton; 129 private Button outdentButton; 130 131 private ComboBox<String> formatComboBox; 132 private Map<String, String> formatStyleMap; 133 private Map<String, String> styleFormatMap; 134 135 private ComboBox<String> fontFamilyComboBox; 136 137 private ComboBox<String> fontSizeComboBox; 138 private Map<String, String> fontSizeMap; 139 private Map<String, String> sizeFontMap; 140 141 private ToggleButton boldButton; 142 private ToggleButton italicButton; 143 private ToggleButton underlineButton; 144 private ToggleButton strikethroughButton; 145 146 private ColorPicker fgColorButton; 147 private ColorPicker bgColorButton; 148 149 private WebView webView; 150 private WebPage webPage; 151 152 private ParentTraversalEngine engine; 153 154 private boolean resetToolbarState = false; 155 private String cachedHTMLText = "<html><head></head><body contenteditable=\"true\"></body></html>"; 156 private ResourceBundle resources; 157 158 private boolean enableAtomicityCheck = false; 159 private int atomicityCount = 0; 160 private boolean isFirstRun = true; 161 162 private static final int FONT_FAMILY_MENUBUTTON_WIDTH = 150; 163 private static final int FONT_FAMILY_MENU_WIDTH = 100; 164 private static final int FONT_SIZE_MENUBUTTON_WIDTH = 80; 165 166 167 168 /*************************************************************************** 169 * * 170 * Static fields * 171 * * 172 **************************************************************************/ 173 174 private static final Color DEFAULT_BG_COLOR = Color.WHITE; 175 private static final Color DEFAULT_FG_COLOR = Color.BLACK; 176 177 private static final String FORMAT_PARAGRAPH = "<p>"; 178 private static final String FORMAT_HEADING_1 = "<h1>"; 179 private static final String FORMAT_HEADING_2 = "<h2>"; 180 private static final String FORMAT_HEADING_3 = "<h3>"; 181 private static final String FORMAT_HEADING_4 = "<h4>"; 182 private static final String FORMAT_HEADING_5 = "<h5>"; 183 private static final String FORMAT_HEADING_6 = "<h6>"; 184 185 private static final String SIZE_XX_SMALL = "1"; 186 private static final String SIZE_X_SMALL = "2"; 187 private static final String SIZE_SMALL = "3"; 188 private static final String SIZE_MEDIUM = "4"; 189 private static final String SIZE_LARGE = "5"; 190 private static final String SIZE_X_LARGE = "6"; 191 private static final String SIZE_XX_LARGE = "7"; 192 193 // As per RT-16330: default format -> bold/size mappings are as follows: 194 private static final String[][] DEFAULT_FORMAT_MAPPINGS = { 195 { FORMAT_PARAGRAPH, "", SIZE_SMALL }, 196 { FORMAT_HEADING_1, BOLD.getCommand(), SIZE_X_LARGE }, 197 { FORMAT_HEADING_2, BOLD.getCommand(), SIZE_LARGE }, 198 { FORMAT_HEADING_3, BOLD.getCommand(), SIZE_MEDIUM }, 199 { FORMAT_HEADING_4, BOLD.getCommand(), SIZE_SMALL }, 200 { FORMAT_HEADING_5, BOLD.getCommand(), SIZE_X_SMALL }, 201 { FORMAT_HEADING_6, BOLD.getCommand(), SIZE_XX_SMALL }, 202 }; 203 204 private static PseudoClass CONTAINS_FOCUS_PSEUDOCLASS_STATE = PseudoClass.getPseudoClass("contains-focus"); 205 206 207 208 /*************************************************************************** 209 * * 210 * Static Methods * 211 * * 212 **************************************************************************/ 213 214 215 216 /*************************************************************************** 217 * * 218 * Listeners * 219 * * 220 **************************************************************************/ 221 222 private ListChangeListener<Node> itemsListener = c -> { 223 while (c.next()) { 224 if (c.getRemovedSize() > 0) { 225 for (Node n : c.getList()) { 226 if (n instanceof WebView) { 227 // RT-28611 webView removed - set associated webPage to null 228 webPage.dispose(); 229 } 230 } 231 } 232 } 233 }; 234 235 236 237 /*************************************************************************** 238 * * 239 * Constructors * 240 * * 241 **************************************************************************/ 242 243 /** 244 * Creates a new HTMLEditorSkin instance, installing the necessary child 245 * nodes into the Control {@link Control#getChildren() children} list, as 246 * well as the necessary input mappings for handling key, mouse, etc events. 247 * 248 * @param control The control that this skin should be installed onto. 249 */ 250 public HTMLEditorSkin(HTMLEditor control) { 251 super(control); 252 253 // install default input map for the HTMLEditor control 254 HTMLEditorBehavior behavior = new HTMLEditorBehavior(control); 255 // htmlEditor.setInputMap(behavior.getInputMap()); 256 257 getChildren().clear(); 258 259 gridPane = new GridPane(); 260 gridPane.getStyleClass().add("grid"); 261 getChildren().addAll(gridPane); 262 263 toolbar1 = new ToolBar(); 264 toolbar1.getStyleClass().add("top-toolbar"); 265 gridPane.add(toolbar1, 0, 0); 266 267 toolbar2 = new ToolBar(); 268 toolbar2.getStyleClass().add("bottom-toolbar"); 269 gridPane.add(toolbar2, 0, 1); 270 271 // populateToolbars(); 272 273 webView = new WebView(); 274 gridPane.add(webView, 0, 2); 275 276 ColumnConstraints column = new ColumnConstraints(); 277 column.setHgrow(Priority.ALWAYS); 278 gridPane.getColumnConstraints().add(column); 279 280 webPage = Accessor.getPageFor(webView.getEngine()); 281 282 webView.addEventHandler(MouseEvent.MOUSE_RELEASED, event2 -> { 283 Platform.runLater(new Runnable() { 284 @Override public void run() { 285 enableAtomicityCheck = true; 286 updateToolbarState(true); 287 enableAtomicityCheck = false; 288 } 289 }); 290 }); 291 292 293 webView.addEventHandler(KeyEvent.KEY_PRESSED, event -> { 294 applyTextFormatting(); 295 if (event.getCode() == KeyCode.CONTROL || event.getCode() == KeyCode.META) { 296 return; 297 } 298 if (event.getCode() == KeyCode.TAB && !event.isControlDown()) { 299 if (!event.isShiftDown()) { 300 /* 301 ** if we are in either Bullet or Numbers mode then the 302 ** TAB key tells us to indent again. 303 */ 304 if (getCommandState(BULLETS.getCommand()) || getCommandState(NUMBERS.getCommand())) { 305 executeCommand(INDENT.getCommand(), null); 306 } 307 else { 308 executeCommand(INSERT_TAB.getCommand(), null); 309 } 310 } 311 else { 312 /* 313 ** if we are in either Bullet or Numbers mode then the 314 ** Shift-TAB key tells us to outdent. 315 */ 316 if (getCommandState(BULLETS.getCommand()) || getCommandState(NUMBERS.getCommand())) { 317 executeCommand(OUTDENT.getCommand(), null); 318 } 319 } 320 return; 321 } 322 // Work around for bug that sends events from ColorPicker to this Scene 323 if ((fgColorButton != null && fgColorButton.isShowing()) || 324 (bgColorButton != null && bgColorButton.isShowing())) { 325 return; 326 } 327 Platform.runLater(() -> { 328 if (webPage.getClientSelectedText().isEmpty()) { 329 if (event.getCode() == KeyCode.UP || event.getCode() == KeyCode.DOWN || 330 event.getCode() == KeyCode.LEFT || event.getCode() == KeyCode.RIGHT || 331 event.getCode() == KeyCode.HOME || event.getCode() == KeyCode.END) { 332 updateToolbarState(true); 333 } else if (event.isControlDown() || event.isMetaDown()) { 334 if (event.getCode() == KeyCode.B) { 335 performCommand(BOLD); 336 } else if (event.getCode() == KeyCode.I) { 337 performCommand(ITALIC); 338 } else if (event.getCode() == KeyCode.U) { 339 performCommand(UNDERLINE); 340 } 341 updateToolbarState(true); 342 } else { 343 resetToolbarState = event.getCode() == KeyCode.ENTER; 344 if (resetToolbarState) { 345 if (getCommandState(BOLD.getCommand()) != boldButton.selectedProperty().getValue()) { 346 executeCommand(BOLD.getCommand(), boldButton.selectedProperty().getValue().toString()); 347 } 348 } 349 updateToolbarState(false); 350 } 351 resetToolbarState = false; 352 } else if (event.isShiftDown() && 353 (event.getCode() == KeyCode.UP || event.getCode() == KeyCode.DOWN || 354 event.getCode() == KeyCode.LEFT || event.getCode() == KeyCode.RIGHT)) { 355 updateToolbarState(true); 356 } 357 }); 358 }); 359 360 webView.addEventHandler(KeyEvent.KEY_RELEASED, event -> { 361 if (event.getCode() == KeyCode.CONTROL || event.getCode() == KeyCode.META) { 362 return; 363 } 364 // Work around for bug that sends events from ColorPicker to this Scene 365 if ((fgColorButton != null && fgColorButton.isShowing()) || 366 (bgColorButton != null && bgColorButton.isShowing())) { 367 return; 368 } 369 Platform.runLater(() -> { 370 if (webPage.getClientSelectedText().isEmpty()) { 371 if (event.getCode() == KeyCode.UP || event.getCode() == KeyCode.DOWN || 372 event.getCode() == KeyCode.LEFT || event.getCode() == KeyCode.RIGHT || 373 event.getCode() == KeyCode.HOME || event.getCode() == KeyCode.END) { 374 updateToolbarState(true); 375 } else if (event.isControlDown() || event.isMetaDown()) { 376 if (event.getCode() == KeyCode.B) { 377 performCommand(BOLD); 378 } else if (event.getCode() == KeyCode.I) { 379 performCommand(ITALIC); 380 } else if (event.getCode() == KeyCode.U) { 381 performCommand(UNDERLINE); 382 } 383 updateToolbarState(true); 384 } else { 385 resetToolbarState = event.getCode() == KeyCode.ENTER; 386 if (!resetToolbarState) { 387 updateToolbarState(false); 388 } 389 } 390 resetToolbarState = false; 391 } 392 }); 393 }); 394 395 getSkinnable().focusedProperty().addListener((observable, oldValue, newValue) -> { 396 Platform.runLater(new Runnable() { 397 @Override public void run() { 398 if (newValue) { 399 webView.requestFocus(); 400 } 401 } 402 }); 403 }); 404 405 webView.focusedProperty().addListener((observable, oldValue, newValue) -> { 406 // disabling as a fix for RT-30081 407 // if (newValue) { 408 // webPage.dispatchFocusEvent(new WCFocusEvent(WCFocusEvent.FOCUS_GAINED, WCFocusEvent.FORWARD)); 409 // enableToolbar(true); 410 // } else { 411 // webPage.dispatchFocusEvent(new WCFocusEvent(WCFocusEvent.FOCUS_LOST, WCFocusEvent.FORWARD)); 412 // enableToolbar(false); 413 // } 414 415 pseudoClassStateChanged(CONTAINS_FOCUS_PSEUDOCLASS_STATE, newValue); 416 417 Platform.runLater(new Runnable() { 418 @Override public void run() { 419 updateToolbarState(true); 420 421 if (PlatformImpl.isSupported(ConditionalFeature.VIRTUAL_KEYBOARD)) { 422 Scene scene = getSkinnable().getScene(); 423 if (newValue) { 424 FXVK.attach(webView); 425 } else if (scene == null || 426 scene.getWindow() == null || 427 !scene.getWindow().isFocused() || 428 !(scene.getFocusOwner() instanceof TextInputControl /*|| 429 getScene().getFocusOwner() instanceof WebView*/)) { 430 FXVK.detach(); 431 } 432 } 433 } 434 }); 435 }); 436 437 webView.getEngine().getLoadWorker().workDoneProperty().addListener((observable, oldValue, newValue) -> { 438 Platform.runLater(() -> { 439 webView.requestLayout(); 440 }); 441 442 double totalWork = webView.getEngine().getLoadWorker().getTotalWork(); 443 if (newValue.doubleValue() == totalWork) { 444 cachedHTMLText = null; 445 Platform.runLater(() -> { 446 setContentEditable(true); 447 updateToolbarState(true); 448 updateNodeOrientation(); 449 }); 450 } 451 }); 452 453 enableToolbar(true); 454 setHTMLText(cachedHTMLText); 455 456 engine = new ParentTraversalEngine(getSkinnable(), new Algorithm() { 457 @Override 458 public Node select(Node owner, Direction dir, TraversalContext context) { 459 return cutButton; 460 } 461 462 @Override 463 public Node selectFirst(TraversalContext context) { 464 return cutButton; 465 } 466 467 @Override 468 public Node selectLast(TraversalContext context) { 469 return cutButton; 470 } 471 }); 472 ParentHelper.setTraversalEngine(getSkinnable(), engine); 473 webView.setFocusTraversable(true); 474 gridPane.getChildren().addListener(itemsListener); 475 } 476 477 478 479 /*************************************************************************** 480 * * 481 * Public API * 482 * * 483 **************************************************************************/ 484 485 /** 486 * Special-case handling for certain commands. Over time this may be extended 487 * to handle additional commands. The current list of supported commands is: 488 * 489 * <ul> 490 * <li>BOLD</li> 491 * <li>ITALIC</li> 492 * <li>UNDERLINE</li> 493 * </ul> 494 * @param command the command 495 */ 496 public void performCommand(final Command command) { 497 switch (command) { 498 case BOLD: boldButton.fire(); break; 499 case ITALIC: italicButton.setSelected(!italicButton.isSelected()); break; 500 case UNDERLINE: underlineButton.setSelected(!underlineButton.isSelected()); break; 501 } 502 } 503 504 /** {@inheritDoc} */ 505 @Override protected void layoutChildren(final double x, final double y, 506 final double w, final double h) { 507 508 if (isFirstRun) { 509 populateToolbars(); 510 isFirstRun = false; 511 } 512 super.layoutChildren(x,y,w,h); 513 double toolbarWidth = Math.max(toolbar1.prefWidth(-1), toolbar2.prefWidth(-1)); 514 toolbar1.setMinWidth(toolbarWidth); 515 toolbar1.setPrefWidth(toolbarWidth); 516 toolbar2.setMinWidth(toolbarWidth); 517 toolbar2.setPrefWidth(toolbarWidth); 518 } 519 520 521 522 /*************************************************************************** 523 * * 524 * Private Implementation * 525 * * 526 **************************************************************************/ 527 528 final String getHTMLText() { 529 // RT17203 setHTMLText is asynchronous. We use the cached version of 530 // the html text until the page finishes loading. 531 return cachedHTMLText != null ? cachedHTMLText : webPage.getHtml(webPage.getMainFrame()); 532 } 533 534 final void setHTMLText(String htmlText) { 535 cachedHTMLText = htmlText; 536 webPage.load(webPage.getMainFrame(), htmlText, "text/html"); 537 538 Platform.runLater(() -> { 539 updateToolbarState(true); 540 }); 541 } 542 543 private void populateToolbars() { 544 resources = ResourceBundle.getBundle(HTMLEditorSkin.class.getName()); 545 546 // Toolbar 1 547 cutButton = addButton(toolbar1, resources.getString("cutIcon"), resources.getString("cut"), CUT.getCommand(), "html-editor-cut"); 548 copyButton = addButton(toolbar1, resources.getString("copyIcon"), resources.getString("copy"), COPY.getCommand(), "html-editor-copy"); 549 pasteButton = addButton(toolbar1, resources.getString("pasteIcon"), resources.getString("paste"), PASTE.getCommand(), "html-editor-paste"); 550 551 toolbar1.getItems().add(new Separator(Orientation.VERTICAL)); 552 553 // undoButton = addButton(toolbar1, "undoIcon", resources.getString("undo"), UNDO.getCommand()); 554 // redoButton = addButton(toolbar1, "redoIcon", resources.getString("redo"), REDO.getCommand());// 555 // toolbar1.getItems().add(new Separator()); 556 557 alignmentToggleGroup = new ToggleGroup(); 558 alignLeftButton = addToggleButton(toolbar1, alignmentToggleGroup, 559 resources.getString("alignLeftIcon"), resources.getString("alignLeft"), ALIGN_LEFT.getCommand(), "html-editor-align-left"); 560 alignCenterButton = addToggleButton(toolbar1, alignmentToggleGroup, 561 resources.getString("alignCenterIcon"), resources.getString("alignCenter"), ALIGN_CENTER.getCommand(), "html-editor-align-center"); 562 alignRightButton = addToggleButton(toolbar1, alignmentToggleGroup, 563 resources.getString("alignRightIcon"), resources.getString("alignRight"), ALIGN_RIGHT.getCommand(), "html-editor-align-right"); 564 alignJustifyButton = addToggleButton(toolbar1, alignmentToggleGroup, 565 resources.getString("alignJustifyIcon"), resources.getString("alignJustify"), ALIGN_JUSTIFY.getCommand(), "html-editor-align-justify"); 566 567 toolbar1.getItems().add(new Separator(Orientation.VERTICAL)); 568 569 outdentButton = addButton(toolbar1, resources.getString("outdentIcon"), resources.getString("outdent"), OUTDENT.getCommand(), "html-editor-outdent"); 570 if (outdentButton.getGraphic() != null) outdentButton.getGraphic().setNodeOrientation(NodeOrientation.INHERIT); 571 indentButton = addButton(toolbar1, resources.getString("indentIcon"), resources.getString("indent"), INDENT.getCommand(), "html-editor-indent"); 572 if (indentButton.getGraphic() != null) indentButton.getGraphic().setNodeOrientation(NodeOrientation.INHERIT); 573 574 toolbar1.getItems().add(new Separator(Orientation.VERTICAL)); 575 576 ToggleGroup listStyleToggleGroup = new ToggleGroup(); 577 bulletsButton = addToggleButton(toolbar1, listStyleToggleGroup, 578 resources.getString("bulletsIcon"), resources.getString("bullets"), BULLETS.getCommand(), "html-editor-bullets"); 579 if (bulletsButton.getGraphic() != null) bulletsButton.getGraphic().setNodeOrientation(NodeOrientation.INHERIT); 580 numbersButton = addToggleButton(toolbar1, listStyleToggleGroup, 581 resources.getString("numbersIcon"), resources.getString("numbers"), NUMBERS.getCommand(), "html-editor-numbers"); 582 583 toolbar1.getItems().add(new Separator(Orientation.VERTICAL)); 584 585 //toolbar1.getItems().add(new Separator()); 586 587 // Toolbar 2 588 formatComboBox = new ComboBox<String>(); 589 formatComboBox.getStyleClass().add("font-menu-button"); 590 formatComboBox.setFocusTraversable(false); 591 formatComboBox.setMinWidth(Region.USE_PREF_SIZE); 592 toolbar2.getItems().add(formatComboBox); 593 594 formatStyleMap = new HashMap<String, String>(); 595 styleFormatMap = new HashMap<String, String>(); 596 597 createFormatMenuItem(FORMAT_PARAGRAPH, resources.getString("paragraph")); 598 Platform.runLater(() -> { 599 formatComboBox.setValue(resources.getString("paragraph")); 600 }); 601 createFormatMenuItem(FORMAT_HEADING_1, resources.getString("heading1")); 602 createFormatMenuItem(FORMAT_HEADING_2, resources.getString("heading2")); 603 createFormatMenuItem(FORMAT_HEADING_3, resources.getString("heading3")); 604 createFormatMenuItem(FORMAT_HEADING_4, resources.getString("heading4")); 605 createFormatMenuItem(FORMAT_HEADING_5, resources.getString("heading5")); 606 createFormatMenuItem(FORMAT_HEADING_6, resources.getString("heading6")); 607 608 // formatComboBox.setCellFactory(new Callback<ListView<String>, ListCell<String>>() { 609 // @Override public ListCell<String> call(ListView<String> param) { 610 // final ListCell<String> cell = new ListCell<String>() { 611 // @Override public void updateItem(String item, boolean empty) { 612 // super.updateItem(item, empty); 613 // if (item != null) { 614 // setText(item); 615 // } 616 // } 617 // }; 618 // return cell; 619 // } 620 // }); 621 622 formatComboBox.setTooltip(new Tooltip(resources.getString("format"))); 623 624 formatComboBox.valueProperty().addListener((observable, oldValue, newValue) -> { 625 if (newValue == null) { 626 formatComboBox.setValue(null); 627 } else { 628 String formatValue = formatStyleMap.get(newValue); 629 executeCommand(FORMAT.getCommand(), formatValue); 630 updateToolbarState(false); 631 632 // RT-16330 match the new font format with the required weight and size 633 for (int i = 0; i < DEFAULT_FORMAT_MAPPINGS.length; i++) { 634 String[] mapping = DEFAULT_FORMAT_MAPPINGS[i]; 635 if (mapping[0].equalsIgnoreCase(formatValue)) { 636 executeCommand(FONT_SIZE.getCommand(), mapping[2]); 637 updateToolbarState(false); 638 break; 639 } 640 } 641 } 642 }); 643 644 fontFamilyComboBox = new ComboBox<String>(); 645 fontFamilyComboBox.getStyleClass().add("font-menu-button"); 646 fontFamilyComboBox.setMinWidth(FONT_FAMILY_MENUBUTTON_WIDTH); 647 fontFamilyComboBox.setPrefWidth(FONT_FAMILY_MENUBUTTON_WIDTH); 648 fontFamilyComboBox.setMaxWidth(FONT_FAMILY_MENUBUTTON_WIDTH); 649 fontFamilyComboBox.setFocusTraversable(false); 650 fontFamilyComboBox.setTooltip(new Tooltip(resources.getString("fontFamily"))); 651 toolbar2.getItems().add(fontFamilyComboBox); 652 653 // Fix for RT-32906, where all rows were being put through the cell factory 654 // so that they could be measured. Because we have a fixed width for the 655 // button this is unnecessary and so we tell the ComboBox to not measure 656 // any rows. 657 fontFamilyComboBox.getProperties().put("comboBoxRowsToMeasureWidth", 0); 658 659 fontFamilyComboBox.setCellFactory(new Callback<ListView<String>, ListCell<String>>() { 660 @Override public ListCell<String> call(ListView<String> param) { 661 final ListCell<String> cell = new ListCell<String>() { 662 @Override public void updateItem(String item, boolean empty) { 663 super.updateItem(item, empty); 664 if (item != null) { 665 setText(item); 666 setFont(new Font(item, 12)); 667 } 668 } 669 }; 670 cell.setMinWidth(FONT_FAMILY_MENU_WIDTH); 671 cell.setPrefWidth(FONT_FAMILY_MENU_WIDTH); 672 cell.setMaxWidth(FONT_FAMILY_MENU_WIDTH); 673 return cell; 674 } 675 }); 676 677 Platform.runLater(() -> { 678 final ObservableList<String> fonts = FXCollections.observableArrayList(Font.getFamilies()); 679 fonts.add(0, ""); 680 for (String fontFamily : fonts) { 681 fontFamilyComboBox.setValue(""); 682 fontFamilyComboBox.setItems(fonts); 683 } 684 }); 685 686 fontFamilyComboBox.valueProperty().addListener((observable, oldValue, newValue) -> { 687 executeCommand(FONT_FAMILY.getCommand(), ("".equals(newValue)) ? "''" : newValue); 688 }); 689 690 fontSizeComboBox = new ComboBox<String>(); 691 fontSizeComboBox.getStyleClass().add("font-menu-button"); 692 fontSizeComboBox.setFocusTraversable(false); 693 toolbar2.getItems().add(fontSizeComboBox); 694 695 fontSizeMap = new HashMap<String, String>(); 696 sizeFontMap = new HashMap<String, String>(); 697 698 createFontSizeMenuItem(SIZE_XX_SMALL, resources.getString("extraExtraSmall")); 699 createFontSizeMenuItem(SIZE_X_SMALL, resources.getString("extraSmall")); 700 createFontSizeMenuItem(SIZE_SMALL, resources.getString("small")); 701 Platform.runLater(() -> { 702 fontSizeComboBox.setValue(resources.getString("small")); 703 }); 704 createFontSizeMenuItem(SIZE_MEDIUM, resources.getString("medium")); 705 createFontSizeMenuItem(SIZE_LARGE, resources.getString("large")); 706 createFontSizeMenuItem(SIZE_X_LARGE, resources.getString("extraLarge")); 707 createFontSizeMenuItem(SIZE_XX_LARGE, resources.getString("extraExtraLarge")); 708 fontSizeComboBox.setTooltip(new Tooltip(resources.getString("fontSize"))); 709 710 fontSizeComboBox.setCellFactory(new Callback<ListView<String>, ListCell<String>>() { 711 @Override public ListCell<String> call(ListView<String> param) { 712 final ListCell<String> cell = new ListCell<String>() { 713 @Override public void updateItem(String item, boolean empty) { 714 super.updateItem(item, empty); 715 if (item != null) { 716 setText(item); 717 // Remove trailing non-digits to get the size (don't assume there's a space). 718 String size = item.replaceFirst("[^0-9.].*$", ""); 719 setFont(new Font((String)fontFamilyComboBox.getValue(), Double.valueOf(size))); 720 } 721 } 722 }; 723 return cell; 724 } 725 }); 726 727 728 fontSizeComboBox.valueProperty().addListener((observable, oldValue, newValue) -> { 729 Object fontSizeValue = getCommandValue(FONT_SIZE.getCommand()); 730 if (!newValue.equals(fontSizeValue)) { 731 executeCommand(FONT_SIZE.getCommand(), fontSizeMap.get(newValue)); 732 } 733 }); 734 735 toolbar2.getItems().add(new Separator(Orientation.VERTICAL)); 736 737 boldButton = addToggleButton(toolbar2, null, 738 resources.getString("boldIcon"), resources.getString("bold"), BOLD.getCommand(), "html-editor-bold"); 739 boldButton.setOnAction(event1 -> { 740 // Only use the bold button for paragraphs. We don't 741 // want to turn bold off for headings. 742 743 if ("<p>".equals(formatStyleMap.get(formatComboBox.getValue()))) { 744 executeCommand(BOLD.getCommand(), boldButton.selectedProperty().getValue().toString()); 745 } 746 }); 747 italicButton = addToggleButton(toolbar2, null, 748 resources.getString("italicIcon"), resources.getString("italic"), ITALIC.getCommand(), "html-editor-italic"); 749 underlineButton = addToggleButton(toolbar2, null, 750 resources.getString("underlineIcon"), resources.getString("underline"), UNDERLINE.getCommand(), "html-editor-underline"); 751 strikethroughButton = addToggleButton(toolbar2, null, 752 resources.getString("strikethroughIcon"), resources.getString("strikethrough"), STRIKETHROUGH.getCommand(), "html-editor-strike"); 753 754 toolbar2.getItems().add(new Separator(Orientation.VERTICAL)); 755 756 insertHorizontalRuleButton = addButton(toolbar2, resources.getString("insertHorizontalRuleIcon"), 757 resources.getString("insertHorizontalRule"), INSERT_HORIZONTAL_RULE.getCommand(), "html-editor-hr"); 758 // We override setOnAction to insert a new line. This fixes RT-16453 759 insertHorizontalRuleButton.setOnAction(event -> { 760 executeCommand(INSERT_NEW_LINE.getCommand(), null); 761 executeCommand(INSERT_HORIZONTAL_RULE.getCommand(), null); 762 updateToolbarState(false); 763 }); 764 765 fgColorButton = new ColorPicker(); 766 fgColorButton.getStyleClass().add("html-editor-foreground"); 767 fgColorButton.setFocusTraversable(false); 768 toolbar1.getItems().add(fgColorButton); 769 770 // JDK-8115747: Icon URLs are now specified in CSS. 771 // fgColorButton.applyCss(); 772 // ColorPickerSkin fgColorPickerSkin = (ColorPickerSkin) fgColorButton.getSkin(); 773 // String fgIcon = AccessController.doPrivileged((PrivilegedAction<String>) () -> HTMLEditorSkin.class.getResource(resources.getString("foregroundColorIcon")).toString()); 774 // ((StyleableProperty)fgColorPickerSkin.imageUrlProperty()).applyStyle(null,fgIcon); 775 776 fgColorButton.setValue(DEFAULT_FG_COLOR); 777 fgColorButton.setTooltip(new Tooltip(resources.getString("foregroundColor"))); 778 fgColorButton.setOnAction(ev1 -> { 779 Color newValue = fgColorButton.getValue(); 780 if (newValue != null) { 781 executeCommand(FOREGROUND_COLOR.getCommand(), colorValueToHex(newValue)); 782 fgColorButton.hide(); 783 } 784 }); 785 786 bgColorButton = new ColorPicker(); 787 bgColorButton.getStyleClass().add("html-editor-background"); 788 bgColorButton.setFocusTraversable(false); 789 toolbar1.getItems().add(bgColorButton); 790 791 // JDK-8115747: Icon URLs are now specified in CSS. 792 // bgColorButton.applyCss(); 793 // ColorPickerSkin bgColorPickerSkin = (ColorPickerSkin) bgColorButton.getSkin(); 794 // String bgIcon = AccessController.doPrivileged((PrivilegedAction<String>) () -> HTMLEditorSkin.class.getResource(resources.getString("backgroundColorIcon")).toString()); 795 // ((StyleableProperty)bgColorPickerSkin.imageUrlProperty()).applyStyle(null,bgIcon); 796 797 bgColorButton.setValue(DEFAULT_BG_COLOR); 798 bgColorButton.setTooltip(new Tooltip(resources.getString("backgroundColor"))); 799 800 bgColorButton.setOnAction(ev -> { 801 Color newValue = bgColorButton.getValue(); 802 if (newValue != null) { 803 executeCommand(BACKGROUND_COLOR.getCommand(), colorValueToHex(newValue)); 804 bgColorButton.hide(); 805 } 806 }); 807 } 808 809 private String colorValueToHex(Color c) { 810 return String.format((Locale)null, "#%02x%02x%02x", 811 Math.round(c.getRed() * 255), 812 Math.round(c.getGreen() * 255), 813 Math.round(c.getBlue() * 255)); 814 } 815 816 private Button addButton(ToolBar toolbar, final String iconName, String tooltipText, 817 final String command, final String styleClass) { 818 Button button = new Button(); 819 button.setFocusTraversable(false); 820 button.getStyleClass().add(styleClass); 821 toolbar.getItems().add(button); 822 823 Image icon = AccessController.doPrivileged((PrivilegedAction<Image>) () -> new Image(HTMLEditorSkin.class.getResource(iconName).toString())); 824 // button.setGraphic(new ImageView(icon)); 825 ((StyleableProperty)button.graphicProperty()).applyStyle(null, new ImageView(icon)); 826 button.setTooltip(new Tooltip(tooltipText)); 827 828 button.setOnAction(event -> { 829 executeCommand(command, null); 830 updateToolbarState(false); 831 }); 832 833 return button; 834 } 835 836 private ToggleButton addToggleButton(ToolBar toolbar, ToggleGroup toggleGroup, 837 final String iconName, String tooltipText, final String command, final String styleClass) { 838 ToggleButton toggleButton = new ToggleButton(); 839 toggleButton.setUserData(command); 840 toggleButton.setFocusTraversable(false); 841 toggleButton.getStyleClass().add(styleClass); 842 toolbar.getItems().add(toggleButton); 843 if (toggleGroup != null) { 844 toggleButton.setToggleGroup(toggleGroup); 845 } 846 847 Image icon = AccessController.doPrivileged((PrivilegedAction<Image>) () -> new Image(HTMLEditorSkin.class.getResource(iconName).toString())); 848 ((StyleableProperty)toggleButton.graphicProperty()).applyStyle(null, new ImageView(icon)); 849 // toggleButton.setGraphic(new ImageView(icon)); 850 851 toggleButton.setTooltip(new Tooltip(tooltipText)); 852 853 if (!BOLD.getCommand().equals(command)) { 854 toggleButton.selectedProperty().addListener((observable, oldValue, newValue) -> { 855 if (getCommandState(command) != newValue.booleanValue()) { 856 executeCommand(command, null); 857 } 858 }); 859 } 860 return toggleButton; 861 } 862 863 private void createFormatMenuItem(String formatValue, String label) { 864 formatComboBox.getItems().add(label); 865 formatStyleMap.put(label, formatValue); 866 styleFormatMap.put(formatValue, label); 867 } 868 869 private void createFontSizeMenuItem(String fontSizeValue, String label) { 870 fontSizeComboBox.getItems().add(label); 871 fontSizeMap.put(label, fontSizeValue); 872 sizeFontMap.put(fontSizeValue, label); 873 } 874 875 private void updateNodeOrientation() { 876 NodeOrientation orientation = getSkinnable().getEffectiveNodeOrientation(); 877 878 HTMLDocument htmlDocument = (HTMLDocument)webPage.getDocument(webPage.getMainFrame()); 879 HTMLElement htmlDocumentElement = (HTMLElement)htmlDocument.getDocumentElement(); 880 if (htmlDocumentElement.getAttribute("dir") == null) { 881 htmlDocumentElement.setAttribute("dir", (orientation == RIGHT_TO_LEFT) ? "rtl" : "ltr"); 882 } 883 884 } 885 886 private void updateToolbarState(final boolean updateAlignment) { 887 if (!webView.isFocused()) { 888 return; 889 } 890 891 atomicityCount++; 892 893 // These command aways return true. 894 copyButton.setDisable(!isCommandEnabled(CUT.getCommand())); 895 cutButton.setDisable(!isCommandEnabled(COPY.getCommand())); 896 pasteButton.setDisable(!isCommandEnabled(PASTE.getCommand())); 897 898 // undoButton.setDisable(!isCommandEnabled(UNDO.getCommand())); 899 // redoButton.setDisable(!isCommandEnabled(REDO.getCommand())); 900 901 // undoButton.setDisable(!isCommandEnabled(FORMAT.getCommand())); 902 // redoButton.setDisable(!isCommandEnabled(FORMAT.getCommand())); 903 904 insertHorizontalRuleButton.setDisable(!isCommandEnabled(INSERT_HORIZONTAL_RULE.getCommand())); 905 906 if (updateAlignment) { 907 alignLeftButton.setDisable(!isCommandEnabled(ALIGN_LEFT.getCommand())); 908 alignLeftButton.setSelected(getCommandState(ALIGN_LEFT.getCommand())); 909 alignCenterButton.setDisable(!isCommandEnabled(ALIGN_CENTER.getCommand())); 910 alignCenterButton.setSelected(getCommandState(ALIGN_CENTER.getCommand())); 911 alignRightButton.setDisable(!isCommandEnabled(ALIGN_RIGHT.getCommand())); 912 alignRightButton.setSelected(getCommandState(ALIGN_RIGHT.getCommand())); 913 alignJustifyButton.setDisable(!isCommandEnabled(ALIGN_JUSTIFY.getCommand())); 914 alignJustifyButton.setSelected(getCommandState(ALIGN_JUSTIFY.getCommand())); 915 } else { 916 if (alignmentToggleGroup.getSelectedToggle() != null) { 917 String command = alignmentToggleGroup.getSelectedToggle().getUserData().toString(); 918 if (isCommandEnabled(command) && !getCommandState(command) ) { 919 executeCommand(command, null); 920 } 921 } 922 } 923 924 if (alignmentToggleGroup.getSelectedToggle() == null) { 925 alignmentToggleGroup.selectToggle(alignLeftButton); 926 } 927 928 bulletsButton.setDisable(!isCommandEnabled(BULLETS.getCommand())); 929 bulletsButton.setSelected(getCommandState(BULLETS.getCommand())); 930 numbersButton.setDisable(!isCommandEnabled(NUMBERS.getCommand())); 931 numbersButton.setSelected(getCommandState(NUMBERS.getCommand())); 932 933 indentButton.setDisable(!isCommandEnabled(INDENT.getCommand())); 934 outdentButton.setDisable(!isCommandEnabled(OUTDENT.getCommand())); 935 936 formatComboBox.setDisable(!isCommandEnabled(FORMAT.getCommand())); 937 938 939 String formatValue = getCommandValue(FORMAT.getCommand()); 940 if (formatValue != null) { 941 String htmlTag = "<" + formatValue + ">"; 942 String comboFormatValue = styleFormatMap.get(htmlTag); 943 String format = formatComboBox.getValue(); 944 945 // if the format value is then we assume that we're dealing with a paragraph, 946 // which seems to correspond with the HTML output we receive. 947 if ((resetToolbarState || htmlTag.equals("<>") || htmlTag.equalsIgnoreCase("<div>") || htmlTag.equalsIgnoreCase("<blockquote>"))) { 948 formatComboBox.setValue(resources.getString("paragraph")); 949 } else if (format != null && ! format.equalsIgnoreCase(comboFormatValue)) { 950 formatComboBox.setValue(comboFormatValue); 951 } 952 } 953 954 fontFamilyComboBox.setDisable(!isCommandEnabled(FONT_FAMILY.getCommand())); 955 final String fontFamilyValue = getCommandValue(FONT_FAMILY.getCommand()); 956 if (fontFamilyValue != null) { 957 String fontFamilyStr = fontFamilyValue; 958 959 // stripping out apostrophe characters, which are appended to either 960 // end of the font face name when the font face has one or more spaces. 961 if (fontFamilyStr.startsWith("'")) { 962 fontFamilyStr = fontFamilyStr.substring(1); 963 } 964 if (fontFamilyStr.endsWith("'")) { 965 fontFamilyStr = fontFamilyStr.substring(0,fontFamilyStr.length() - 1); 966 } 967 968 Object selectedFont = fontFamilyComboBox.getValue(); 969 if (selectedFont instanceof String) { 970 if (!selectedFont.equals(fontFamilyStr)) { 971 972 ObservableList<String> fontFamilyItems = fontFamilyComboBox.getItems(); 973 String selectedComboFont = null; 974 for (String comboFontFamilyValue : fontFamilyItems) { 975 976 if (comboFontFamilyValue.equals(fontFamilyStr)) { 977 selectedComboFont = comboFontFamilyValue; 978 break; 979 } 980 // Note: By default, 'Dialog' is the font returned from webview. 981 // For presidio, we're just mapping to the default font. 982 if (comboFontFamilyValue.equals("") && fontFamilyStr.equals("Dialog")) { 983 selectedComboFont = comboFontFamilyValue; 984 break; 985 } 986 } 987 988 if (selectedComboFont != null) { 989 fontFamilyComboBox.setValue(selectedComboFont); 990 } 991 } 992 } 993 } 994 995 fontSizeComboBox.setDisable(!isCommandEnabled(FONT_SIZE.getCommand())); 996 String fontSizeValue = getCommandValue(FONT_SIZE.getCommand()); 997 998 // added test for fontSizeValue == null to combat RT-28847 999 if (resetToolbarState && fontSizeValue == null) { 1000 fontSizeComboBox.setValue(sizeFontMap.get(SIZE_SMALL)); 1001 } else { 1002 if (fontSizeValue != null) { 1003 if (!fontSizeComboBox.getValue().equals(sizeFontMap.get(fontSizeValue))) { 1004 fontSizeComboBox.setValue(sizeFontMap.get(fontSizeValue)); 1005 } 1006 } 1007 else { 1008 /* 1009 ** these is no font size set in webview, 1010 ** let's just use the default.... 1011 */ 1012 if ((fontSizeComboBox.getValue() != null) && !fontSizeComboBox.getValue().equals(sizeFontMap.get(SIZE_SMALL))) { 1013 fontSizeComboBox.setValue(sizeFontMap.get(SIZE_SMALL)); 1014 } 1015 } 1016 } 1017 1018 boldButton.setDisable(!isCommandEnabled(BOLD.getCommand())); 1019 boldButton.setSelected(getCommandState(BOLD.getCommand())); 1020 italicButton.setDisable(!isCommandEnabled(ITALIC.getCommand())); 1021 italicButton.setSelected(getCommandState(ITALIC.getCommand())); 1022 underlineButton.setDisable(!isCommandEnabled(UNDERLINE.getCommand())); 1023 underlineButton.setSelected(getCommandState(UNDERLINE.getCommand())); 1024 strikethroughButton.setDisable(!isCommandEnabled(STRIKETHROUGH.getCommand())); 1025 strikethroughButton.setSelected(getCommandState(STRIKETHROUGH.getCommand())); 1026 1027 fgColorButton.setDisable(!isCommandEnabled(FOREGROUND_COLOR.getCommand())); 1028 String foregroundColorValue = getCommandValue(FOREGROUND_COLOR.getCommand()); 1029 if (foregroundColorValue != null) { 1030 Color c = Color.web(rgbToHex((String)foregroundColorValue)); 1031 fgColorButton.setValue(c); 1032 } 1033 1034 bgColorButton.setDisable(!isCommandEnabled(BACKGROUND_COLOR.getCommand())); 1035 String backgroundColorValue = getCommandValue(BACKGROUND_COLOR.getCommand()); 1036 if (backgroundColorValue != null) { 1037 Color c = Color.web(rgbToHex((String)backgroundColorValue)); 1038 bgColorButton.setValue(c); 1039 } 1040 1041 atomicityCount = atomicityCount == 0 ? 0 : --atomicityCount; 1042 } 1043 1044 private void enableToolbar(final boolean enable) { 1045 Platform.runLater(() -> { 1046 1047 // Make sure buttons have been created to avoid NPE 1048 if (copyButton == null) return; 1049 1050 /* 1051 ** if we're to enable, we still only enable 1052 ** the cut/copy/paste buttons that make sense 1053 */ 1054 if (enable) { 1055 copyButton.setDisable(!isCommandEnabled(COPY.getCommand())); 1056 cutButton.setDisable(!isCommandEnabled(CUT.getCommand())); 1057 pasteButton.setDisable(!isCommandEnabled(PASTE.getCommand())); 1058 } else { 1059 copyButton.setDisable(true); 1060 cutButton.setDisable(true); 1061 pasteButton.setDisable(true); 1062 } 1063 1064 // undoButton.setDisable(!enable); 1065 // redoButton.setDisable(!enable); 1066 insertHorizontalRuleButton.setDisable(!enable); 1067 alignLeftButton.setDisable(!enable); 1068 alignCenterButton.setDisable(!enable); 1069 alignRightButton.setDisable(!enable); 1070 alignJustifyButton.setDisable(!enable); 1071 bulletsButton.setDisable(!enable); 1072 numbersButton.setDisable(!enable); 1073 indentButton.setDisable(!enable); 1074 outdentButton.setDisable(!enable); 1075 formatComboBox.setDisable(!enable); 1076 fontFamilyComboBox.setDisable(!enable); 1077 fontSizeComboBox.setDisable(!enable); 1078 boldButton.setDisable(!enable); 1079 italicButton.setDisable(!enable); 1080 underlineButton.setDisable(!enable); 1081 strikethroughButton.setDisable(!enable); 1082 fgColorButton.setDisable(!enable); 1083 bgColorButton.setDisable(!enable); 1084 }); 1085 } 1086 1087 private boolean executeCommand(String command, String value) { 1088 // The mentions of atomicity throughout this class relate back to RT-39941, 1089 // refer to that jira issue for more context. 1090 if (!enableAtomicityCheck || (enableAtomicityCheck && atomicityCount == 0)) { 1091 return webPage.executeCommand(command, value); 1092 } 1093 return false; 1094 } 1095 1096 private boolean isCommandEnabled(String command) { 1097 return webPage.queryCommandEnabled(command); 1098 } 1099 1100 private void setContentEditable(boolean b) { 1101 HTMLDocument htmlDocument = (HTMLDocument)webPage.getDocument(webPage.getMainFrame()); 1102 HTMLElement htmlDocumentElement = (HTMLElement)htmlDocument.getDocumentElement(); 1103 HTMLElement htmlBodyElement = (HTMLElement)htmlDocumentElement.getElementsByTagName("body").item(0); 1104 htmlBodyElement.setAttribute("contenteditable", Boolean.toString(b)); 1105 } 1106 1107 private boolean getCommandState(String command) { 1108 return webPage.queryCommandState(command); 1109 } 1110 1111 private String getCommandValue(String command) { 1112 return webPage.queryCommandValue(command); 1113 } 1114 1115 private static String rgbToHex(String value) { 1116 if (value.startsWith("rgba")) { 1117 String[] components = value.substring(value.indexOf('(') + 1, value.lastIndexOf(')')).split(","); 1118 value = String.format("#%02X%02X%02X%02X", 1119 Integer.parseInt(components[0].trim()), 1120 Integer.parseInt(components[1].trim()), 1121 Integer.parseInt(components[2].trim()), 1122 Integer.parseInt(components[3].trim())); 1123 // The default background color for WebView, according to the HTML 1124 // standard is rgba=#00000000 (black). The canvas background is expected 1125 // to be white. 1126 if ("#00000000".equals(value)) { 1127 return "#FFFFFFFF"; 1128 } 1129 } else if (value.startsWith("rgb")) { 1130 String[] components = value.substring(value.indexOf('(') + 1, value.lastIndexOf(')')).split(","); 1131 value = String.format("#%02X%02X%02X", 1132 Integer.parseInt(components[0].trim()), 1133 Integer.parseInt(components[1].trim()), 1134 Integer.parseInt(components[2].trim())); 1135 } 1136 1137 return value; 1138 } 1139 1140 private void applyTextFormatting() { 1141 if (getCommandState(BULLETS.getCommand()) || getCommandState(NUMBERS.getCommand())) { 1142 return; 1143 } 1144 1145 if (webPage.getClientCommittedTextLength() == 0) { 1146 String format = formatStyleMap.get(formatComboBox.getValue()); 1147 String font = fontFamilyComboBox.getValue().toString(); 1148 1149 executeCommand(FORMAT.getCommand(), format); 1150 executeCommand(FONT_FAMILY.getCommand(), font); 1151 } 1152 } 1153 1154 void print(PrinterJob job) { 1155 webView.getEngine().print(job); 1156 } 1157 1158 1159 1160 /*************************************************************************** 1161 * * 1162 * Support Classes * 1163 * * 1164 **************************************************************************/ 1165 1166 /** 1167 * Represents commands that can be passed into the HTMLEditor web engine. 1168 */ 1169 public enum Command { 1170 CUT("cut"), 1171 COPY("copy"), 1172 PASTE("paste"), 1173 1174 UNDO("undo"), 1175 REDO("redo"), 1176 1177 INSERT_HORIZONTAL_RULE("inserthorizontalrule"), 1178 1179 ALIGN_LEFT("justifyleft"), 1180 ALIGN_CENTER("justifycenter"), 1181 ALIGN_RIGHT("justifyright"), 1182 ALIGN_JUSTIFY("justifyfull"), 1183 1184 BULLETS("insertUnorderedList"), 1185 NUMBERS("insertOrderedList"), 1186 1187 INDENT("indent"), 1188 OUTDENT("outdent"), 1189 1190 FORMAT("formatblock"), 1191 FONT_FAMILY("fontname"), 1192 FONT_SIZE("fontsize"), 1193 1194 BOLD("bold"), 1195 ITALIC("italic"), 1196 UNDERLINE("underline"), 1197 STRIKETHROUGH("strikethrough"), 1198 1199 FOREGROUND_COLOR("forecolor"), 1200 BACKGROUND_COLOR("backcolor"), 1201 1202 INSERT_NEW_LINE("insertnewline"), 1203 INSERT_TAB("inserttab"); 1204 1205 private final String command; 1206 1207 Command(String command) { 1208 this.command = command; 1209 } 1210 1211 public String getCommand() { 1212 return command; 1213 } 1214 } 1215 }