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