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 setDesignMode("on"); 310 executeCommand(INSERT_TAB.getCommand(), null); 311 setDesignMode("off"); 312 } 313 } 314 else { 315 /* 316 ** if we are in either Bullet or Numbers mode then the 317 ** Shift-TAB key tells us to outdent. 318 */ 319 if (getCommandState(BULLETS.getCommand()) || getCommandState(NUMBERS.getCommand())) { 320 executeCommand(OUTDENT.getCommand(), null); 321 } 322 } 323 return; 324 } 325 // Work around for bug that sends events from ColorPicker to this Scene 326 if ((fgColorButton != null && fgColorButton.isShowing()) || 327 (bgColorButton != null && bgColorButton.isShowing())) { 328 return; 329 } 330 Platform.runLater(() -> { 331 if (webPage.getClientSelectedText().isEmpty()) { 332 if (event.getCode() == KeyCode.UP || event.getCode() == KeyCode.DOWN || 333 event.getCode() == KeyCode.LEFT || event.getCode() == KeyCode.RIGHT || 334 event.getCode() == KeyCode.HOME || event.getCode() == KeyCode.END) { 335 updateToolbarState(true); 336 } else if (event.isControlDown() || event.isMetaDown()) { 337 if (event.getCode() == KeyCode.B) { 338 performCommand(BOLD); 339 } else if (event.getCode() == KeyCode.I) { 340 performCommand(ITALIC); 341 } else if (event.getCode() == KeyCode.U) { 342 performCommand(UNDERLINE); 343 } 344 updateToolbarState(true); 345 } else { 346 resetToolbarState = event.getCode() == KeyCode.ENTER; 347 if (resetToolbarState) { 348 if (getCommandState(BOLD.getCommand()) != boldButton.selectedProperty().getValue()) { 349 executeCommand(BOLD.getCommand(), boldButton.selectedProperty().getValue().toString()); 350 } 351 } 352 updateToolbarState(false); 353 } 354 resetToolbarState = false; 355 } else if (event.isShiftDown() && 356 (event.getCode() == KeyCode.UP || event.getCode() == KeyCode.DOWN || 357 event.getCode() == KeyCode.LEFT || event.getCode() == KeyCode.RIGHT || 358 event.getCode() == KeyCode.HOME || event.getCode() == KeyCode.END)) { 359 updateToolbarState(true); 360 } else if ((event.isControlDown() || event.isMetaDown()) && 361 event.getCode() == KeyCode.A) { 362 updateToolbarState(true); 363 } 364 }); 365 }); 366 367 webView.addEventHandler(KeyEvent.KEY_RELEASED, event -> { 368 if (event.getCode() == KeyCode.CONTROL || event.getCode() == KeyCode.META) { 369 return; 370 } 371 // Work around for bug that sends events from ColorPicker to this Scene 372 if ((fgColorButton != null && fgColorButton.isShowing()) || 373 (bgColorButton != null && bgColorButton.isShowing())) { 374 return; 375 } 376 Platform.runLater(() -> { 377 if (webPage.getClientSelectedText().isEmpty()) { 378 if (event.getCode() == KeyCode.UP || event.getCode() == KeyCode.DOWN || 379 event.getCode() == KeyCode.LEFT || event.getCode() == KeyCode.RIGHT || 380 event.getCode() == KeyCode.HOME || event.getCode() == KeyCode.END) { 381 updateToolbarState(true); 382 } else if (event.isControlDown() || event.isMetaDown()) { 383 if (event.getCode() == KeyCode.B) { 384 performCommand(BOLD); 385 } else if (event.getCode() == KeyCode.I) { 386 performCommand(ITALIC); 387 } else if (event.getCode() == KeyCode.U) { 388 performCommand(UNDERLINE); 389 } 390 updateToolbarState(true); 391 } else { 392 resetToolbarState = event.getCode() == KeyCode.ENTER; 393 if (!resetToolbarState) { 394 updateToolbarState(false); 395 } 396 } 397 resetToolbarState = false; 398 } 399 }); 400 }); 401 402 getSkinnable().focusedProperty().addListener((observable, oldValue, newValue) -> { 403 Platform.runLater(new Runnable() { 404 @Override public void run() { 405 if (newValue) { 406 webView.requestFocus(); 407 } 408 } 409 }); 410 }); 411 412 webView.focusedProperty().addListener((observable, oldValue, newValue) -> { 413 // disabling as a fix for RT-30081 414 // if (newValue) { 415 // webPage.dispatchFocusEvent(new WCFocusEvent(WCFocusEvent.FOCUS_GAINED, WCFocusEvent.FORWARD)); 416 // enableToolbar(true); 417 // } else { 418 // webPage.dispatchFocusEvent(new WCFocusEvent(WCFocusEvent.FOCUS_LOST, WCFocusEvent.FORWARD)); 419 // enableToolbar(false); 420 // } 421 422 pseudoClassStateChanged(CONTAINS_FOCUS_PSEUDOCLASS_STATE, newValue); 423 424 Platform.runLater(new Runnable() { 425 @Override public void run() { 426 updateToolbarState(true); 427 428 if (PlatformImpl.isSupported(ConditionalFeature.VIRTUAL_KEYBOARD)) { 429 Scene scene = getSkinnable().getScene(); 430 if (newValue) { 431 FXVK.attach(webView); 432 } else if (scene == null || 433 scene.getWindow() == null || 434 !scene.getWindow().isFocused() || 435 !(scene.getFocusOwner() instanceof TextInputControl /*|| 436 getScene().getFocusOwner() instanceof WebView*/)) { 437 FXVK.detach(); 438 } 439 } 440 } 441 }); 442 }); 443 444 webView.getEngine().getLoadWorker().workDoneProperty().addListener((observable, oldValue, newValue) -> { 445 Platform.runLater(() -> { 446 webView.requestLayout(); 447 }); 448 449 double totalWork = webView.getEngine().getLoadWorker().getTotalWork(); 450 if (newValue.doubleValue() == totalWork) { 451 cachedHTMLText = null; 452 Platform.runLater(() -> { 453 setContentEditable(true); 454 updateToolbarState(true); 455 updateNodeOrientation(); 456 executeCommand(STYLEWITHCSS.getCommand(), "true"); 457 }); 458 } 459 }); 460 461 enableToolbar(true); 462 setHTMLText(cachedHTMLText); 463 464 engine = new ParentTraversalEngine(getSkinnable(), new Algorithm() { 465 @Override 466 public Node select(Node owner, Direction dir, TraversalContext context) { 467 return cutButton; 468 } 469 470 @Override 471 public Node selectFirst(TraversalContext context) { 472 return cutButton; 473 } 474 475 @Override 476 public Node selectLast(TraversalContext context) { 477 return cutButton; 478 } 479 }); 480 ParentHelper.setTraversalEngine(getSkinnable(), engine); 481 webView.setFocusTraversable(true); 482 gridPane.getChildren().addListener(itemsListener); 483 } 484 485 486 487 /*************************************************************************** 488 * * 489 * Public API * 490 * * 491 **************************************************************************/ 492 493 /** 494 * Special-case handling for certain commands. Over time this may be extended 495 * to handle additional commands. The current list of supported commands is: 496 * 497 * <ul> 498 * <li>BOLD</li> 499 * <li>ITALIC</li> 500 * <li>UNDERLINE</li> 501 * </ul> 502 * @param command the command 503 */ 504 public void performCommand(final Command command) { 505 switch (command) { 506 case BOLD: boldButton.fire(); break; 507 case ITALIC: italicButton.setSelected(!italicButton.isSelected()); break; 508 case UNDERLINE: underlineButton.setSelected(!underlineButton.isSelected()); break; 509 } 510 } 511 512 /** {@inheritDoc} */ 513 @Override protected void layoutChildren(final double x, final double y, 514 final double w, final double h) { 515 516 if (isFirstRun) { 517 populateToolbars(); 518 isFirstRun = false; 519 } 520 super.layoutChildren(x,y,w,h); 521 double toolbarWidth = Math.max(toolbar1.prefWidth(-1), toolbar2.prefWidth(-1)); 522 toolbar1.setMinWidth(toolbarWidth); 523 toolbar1.setPrefWidth(toolbarWidth); 524 toolbar2.setMinWidth(toolbarWidth); 525 toolbar2.setPrefWidth(toolbarWidth); 526 } 527 528 529 530 /*************************************************************************** 531 * * 532 * Private Implementation * 533 * * 534 **************************************************************************/ 535 536 final String getHTMLText() { 537 // RT17203 setHTMLText is asynchronous. We use the cached version of 538 // the html text until the page finishes loading. 539 return cachedHTMLText != null ? cachedHTMLText : webPage.getHtml(webPage.getMainFrame()); 540 } 541 542 final void setHTMLText(String htmlText) { 543 cachedHTMLText = htmlText; 544 webPage.load(webPage.getMainFrame(), htmlText, "text/html"); 545 546 Platform.runLater(() -> { 547 updateToolbarState(true); 548 }); 549 } 550 551 private void populateToolbars() { 552 resources = ResourceBundle.getBundle(HTMLEditorSkin.class.getName()); 553 554 // Toolbar 1 555 cutButton = addButton(toolbar1, resources.getString("cutIcon"), resources.getString("cut"), CUT.getCommand(), "html-editor-cut"); 556 copyButton = addButton(toolbar1, resources.getString("copyIcon"), resources.getString("copy"), COPY.getCommand(), "html-editor-copy"); 557 pasteButton = addButton(toolbar1, resources.getString("pasteIcon"), resources.getString("paste"), PASTE.getCommand(), "html-editor-paste"); 558 559 toolbar1.getItems().add(new Separator(Orientation.VERTICAL)); 560 561 // undoButton = addButton(toolbar1, "undoIcon", resources.getString("undo"), UNDO.getCommand()); 562 // redoButton = addButton(toolbar1, "redoIcon", resources.getString("redo"), REDO.getCommand());// 563 // toolbar1.getItems().add(new Separator()); 564 565 alignmentToggleGroup = new ToggleGroup(); 566 alignLeftButton = addToggleButton(toolbar1, alignmentToggleGroup, 567 resources.getString("alignLeftIcon"), resources.getString("alignLeft"), ALIGN_LEFT.getCommand(), "html-editor-align-left"); 568 alignCenterButton = addToggleButton(toolbar1, alignmentToggleGroup, 569 resources.getString("alignCenterIcon"), resources.getString("alignCenter"), ALIGN_CENTER.getCommand(), "html-editor-align-center"); 570 alignRightButton = addToggleButton(toolbar1, alignmentToggleGroup, 571 resources.getString("alignRightIcon"), resources.getString("alignRight"), ALIGN_RIGHT.getCommand(), "html-editor-align-right"); 572 alignJustifyButton = addToggleButton(toolbar1, alignmentToggleGroup, 573 resources.getString("alignJustifyIcon"), resources.getString("alignJustify"), ALIGN_JUSTIFY.getCommand(), "html-editor-align-justify"); 574 575 toolbar1.getItems().add(new Separator(Orientation.VERTICAL)); 576 577 outdentButton = addButton(toolbar1, resources.getString("outdentIcon"), resources.getString("outdent"), OUTDENT.getCommand(), "html-editor-outdent"); 578 if (outdentButton.getGraphic() != null) outdentButton.getGraphic().setNodeOrientation(NodeOrientation.INHERIT); 579 indentButton = addButton(toolbar1, resources.getString("indentIcon"), resources.getString("indent"), INDENT.getCommand(), "html-editor-indent"); 580 if (indentButton.getGraphic() != null) indentButton.getGraphic().setNodeOrientation(NodeOrientation.INHERIT); 581 582 toolbar1.getItems().add(new Separator(Orientation.VERTICAL)); 583 584 ToggleGroup listStyleToggleGroup = new ToggleGroup(); 585 bulletsButton = addToggleButton(toolbar1, listStyleToggleGroup, 586 resources.getString("bulletsIcon"), resources.getString("bullets"), BULLETS.getCommand(), "html-editor-bullets"); 587 if (bulletsButton.getGraphic() != null) bulletsButton.getGraphic().setNodeOrientation(NodeOrientation.INHERIT); 588 numbersButton = addToggleButton(toolbar1, listStyleToggleGroup, 589 resources.getString("numbersIcon"), resources.getString("numbers"), NUMBERS.getCommand(), "html-editor-numbers"); 590 591 toolbar1.getItems().add(new Separator(Orientation.VERTICAL)); 592 593 //toolbar1.getItems().add(new Separator()); 594 595 // Toolbar 2 596 formatComboBox = new ComboBox<String>(); 597 formatComboBox.getStyleClass().add("font-menu-button"); 598 formatComboBox.setFocusTraversable(false); 599 formatComboBox.setMinWidth(Region.USE_PREF_SIZE); 600 toolbar2.getItems().add(formatComboBox); 601 602 formatStyleMap = new HashMap<String, String>(); 603 styleFormatMap = new HashMap<String, String>(); 604 605 createFormatMenuItem(FORMAT_PARAGRAPH, resources.getString("paragraph")); 606 Platform.runLater(() -> { 607 formatComboBox.setValue(resources.getString("paragraph")); 608 }); 609 createFormatMenuItem(FORMAT_HEADING_1, resources.getString("heading1")); 610 createFormatMenuItem(FORMAT_HEADING_2, resources.getString("heading2")); 611 createFormatMenuItem(FORMAT_HEADING_3, resources.getString("heading3")); 612 createFormatMenuItem(FORMAT_HEADING_4, resources.getString("heading4")); 613 createFormatMenuItem(FORMAT_HEADING_5, resources.getString("heading5")); 614 createFormatMenuItem(FORMAT_HEADING_6, resources.getString("heading6")); 615 616 // formatComboBox.setCellFactory(new Callback<ListView<String>, ListCell<String>>() { 617 // @Override public ListCell<String> call(ListView<String> param) { 618 // final ListCell<String> cell = new ListCell<String>() { 619 // @Override public void updateItem(String item, boolean empty) { 620 // super.updateItem(item, empty); 621 // if (item != null) { 622 // setText(item); 623 // } 624 // } 625 // }; 626 // return cell; 627 // } 628 // }); 629 630 formatComboBox.setTooltip(new Tooltip(resources.getString("format"))); 631 632 formatComboBox.valueProperty().addListener((observable, oldValue, newValue) -> { 633 if (newValue == null) { 634 formatComboBox.setValue(null); 635 } else { 636 String formatValue = formatStyleMap.get(newValue); 637 executeCommand(FORMAT.getCommand(), formatValue); 638 updateToolbarState(false); 639 640 // RT-16330 match the new font format with the required weight and size 641 for (int i = 0; i < DEFAULT_FORMAT_MAPPINGS.length; i++) { 642 String[] mapping = DEFAULT_FORMAT_MAPPINGS[i]; 643 if (mapping[0].equalsIgnoreCase(formatValue)) { 644 executeCommand(FONT_SIZE.getCommand(), mapping[2]); 645 updateToolbarState(false); 646 break; 647 } 648 } 649 } 650 }); 651 652 fontFamilyComboBox = new ComboBox<String>(); 653 fontFamilyComboBox.getStyleClass().add("font-menu-button"); 654 fontFamilyComboBox.setMinWidth(FONT_FAMILY_MENUBUTTON_WIDTH); 655 fontFamilyComboBox.setPrefWidth(FONT_FAMILY_MENUBUTTON_WIDTH); 656 fontFamilyComboBox.setMaxWidth(FONT_FAMILY_MENUBUTTON_WIDTH); 657 fontFamilyComboBox.setFocusTraversable(false); 658 fontFamilyComboBox.setTooltip(new Tooltip(resources.getString("fontFamily"))); 659 toolbar2.getItems().add(fontFamilyComboBox); 660 661 // Fix for RT-32906, where all rows were being put through the cell factory 662 // so that they could be measured. Because we have a fixed width for the 663 // button this is unnecessary and so we tell the ComboBox to not measure 664 // any rows. 665 fontFamilyComboBox.getProperties().put("comboBoxRowsToMeasureWidth", 0); 666 667 fontFamilyComboBox.setCellFactory(new Callback<ListView<String>, ListCell<String>>() { 668 @Override public ListCell<String> call(ListView<String> param) { 669 final ListCell<String> cell = new ListCell<String>() { 670 @Override public void updateItem(String item, boolean empty) { 671 super.updateItem(item, empty); 672 if (item != null) { 673 setText(item); 674 setFont(new Font(item, 12)); 675 } 676 } 677 }; 678 cell.setMinWidth(FONT_FAMILY_MENU_WIDTH); 679 cell.setPrefWidth(FONT_FAMILY_MENU_WIDTH); 680 cell.setMaxWidth(FONT_FAMILY_MENU_WIDTH); 681 return cell; 682 } 683 }); 684 685 Platform.runLater(() -> { 686 final ObservableList<String> fonts = FXCollections.observableArrayList(Font.getFamilies()); 687 fonts.add(0, ""); 688 for (String fontFamily : fonts) { 689 fontFamilyComboBox.setValue(""); 690 fontFamilyComboBox.setItems(fonts); 691 } 692 }); 693 694 fontFamilyComboBox.valueProperty().addListener((observable, oldValue, newValue) -> { 695 executeCommand(FONT_FAMILY.getCommand(), ("".equals(newValue)) ? "''" : newValue); 696 }); 697 698 fontSizeComboBox = new ComboBox<String>(); 699 fontSizeComboBox.getStyleClass().add("font-menu-button"); 700 fontSizeComboBox.setFocusTraversable(false); 701 toolbar2.getItems().add(fontSizeComboBox); 702 703 fontSizeMap = new HashMap<String, String>(); 704 sizeFontMap = new HashMap<String, String>(); 705 706 createFontSizeMenuItem(SIZE_XX_SMALL, resources.getString("extraExtraSmall")); 707 createFontSizeMenuItem(SIZE_X_SMALL, resources.getString("extraSmall")); 708 createFontSizeMenuItem(SIZE_SMALL, resources.getString("small")); 709 Platform.runLater(() -> { 710 fontSizeComboBox.setValue(resources.getString("small")); 711 }); 712 createFontSizeMenuItem(SIZE_MEDIUM, resources.getString("medium")); 713 createFontSizeMenuItem(SIZE_LARGE, resources.getString("large")); 714 createFontSizeMenuItem(SIZE_X_LARGE, resources.getString("extraLarge")); 715 createFontSizeMenuItem(SIZE_XX_LARGE, resources.getString("extraExtraLarge")); 716 fontSizeComboBox.setTooltip(new Tooltip(resources.getString("fontSize"))); 717 718 fontSizeComboBox.setCellFactory(new Callback<ListView<String>, ListCell<String>>() { 719 @Override public ListCell<String> call(ListView<String> param) { 720 final ListCell<String> cell = new ListCell<String>() { 721 @Override public void updateItem(String item, boolean empty) { 722 super.updateItem(item, empty); 723 if (item != null) { 724 setText(item); 725 // Remove trailing non-digits to get the size (don't assume there's a space). 726 String size = item.replaceFirst("[^0-9.].*$", ""); 727 setFont(new Font((String)fontFamilyComboBox.getValue(), Double.valueOf(size))); 728 } 729 } 730 }; 731 return cell; 732 } 733 }); 734 735 736 fontSizeComboBox.valueProperty().addListener((observable, oldValue, newValue) -> { 737 Object fontSizeValue = getCommandValue(FONT_SIZE.getCommand()); 738 if (!newValue.equals(fontSizeValue)) { 739 executeCommand(FONT_SIZE.getCommand(), fontSizeMap.get(newValue)); 740 } 741 }); 742 743 toolbar2.getItems().add(new Separator(Orientation.VERTICAL)); 744 745 boldButton = addToggleButton(toolbar2, null, 746 resources.getString("boldIcon"), resources.getString("bold"), BOLD.getCommand(), "html-editor-bold"); 747 boldButton.setOnAction(event1 -> { 748 // Only use the bold button for paragraphs. We don't 749 // want to turn bold off for headings. 750 751 if ("<p>".equals(formatStyleMap.get(formatComboBox.getValue()))) { 752 executeCommand(BOLD.getCommand(), boldButton.selectedProperty().getValue().toString()); 753 } 754 }); 755 italicButton = addToggleButton(toolbar2, null, 756 resources.getString("italicIcon"), resources.getString("italic"), ITALIC.getCommand(), "html-editor-italic"); 757 underlineButton = addToggleButton(toolbar2, null, 758 resources.getString("underlineIcon"), resources.getString("underline"), UNDERLINE.getCommand(), "html-editor-underline"); 759 strikethroughButton = addToggleButton(toolbar2, null, 760 resources.getString("strikethroughIcon"), resources.getString("strikethrough"), STRIKETHROUGH.getCommand(), "html-editor-strike"); 761 762 toolbar2.getItems().add(new Separator(Orientation.VERTICAL)); 763 764 insertHorizontalRuleButton = addButton(toolbar2, resources.getString("insertHorizontalRuleIcon"), 765 resources.getString("insertHorizontalRule"), INSERT_HORIZONTAL_RULE.getCommand(), "html-editor-hr"); 766 // We override setOnAction to insert a new line. This fixes RT-16453 767 insertHorizontalRuleButton.setOnAction(event -> { 768 executeCommand(INSERT_NEW_LINE.getCommand(), null); 769 executeCommand(INSERT_HORIZONTAL_RULE.getCommand(), null); 770 updateToolbarState(false); 771 }); 772 773 fgColorButton = new ColorPicker(); 774 fgColorButton.getStyleClass().add("html-editor-foreground"); 775 fgColorButton.setFocusTraversable(false); 776 toolbar1.getItems().add(fgColorButton); 777 778 // JDK-8115747: Icon URLs are now specified in CSS. 779 // fgColorButton.applyCss(); 780 // ColorPickerSkin fgColorPickerSkin = (ColorPickerSkin) fgColorButton.getSkin(); 781 // String fgIcon = AccessController.doPrivileged((PrivilegedAction<String>) () -> HTMLEditorSkin.class.getResource(resources.getString("foregroundColorIcon")).toString()); 782 // ((StyleableProperty)fgColorPickerSkin.imageUrlProperty()).applyStyle(null,fgIcon); 783 784 fgColorButton.setValue(DEFAULT_FG_COLOR); 785 fgColorButton.setTooltip(new Tooltip(resources.getString("foregroundColor"))); 786 fgColorButton.setOnAction(ev1 -> { 787 Color newValue = fgColorButton.getValue(); 788 if (newValue != null) { 789 executeCommand(FOREGROUND_COLOR.getCommand(), colorValueToRGBA(newValue)); 790 fgColorButton.hide(); 791 } 792 }); 793 794 bgColorButton = new ColorPicker(); 795 bgColorButton.getStyleClass().add("html-editor-background"); 796 bgColorButton.setFocusTraversable(false); 797 toolbar1.getItems().add(bgColorButton); 798 799 // JDK-8115747: Icon URLs are now specified in CSS. 800 // bgColorButton.applyCss(); 801 // ColorPickerSkin bgColorPickerSkin = (ColorPickerSkin) bgColorButton.getSkin(); 802 // String bgIcon = AccessController.doPrivileged((PrivilegedAction<String>) () -> HTMLEditorSkin.class.getResource(resources.getString("backgroundColorIcon")).toString()); 803 // ((StyleableProperty)bgColorPickerSkin.imageUrlProperty()).applyStyle(null,bgIcon); 804 805 bgColorButton.setValue(DEFAULT_BG_COLOR); 806 bgColorButton.setTooltip(new Tooltip(resources.getString("backgroundColor"))); 807 808 bgColorButton.setOnAction(ev -> { 809 Color newValue = bgColorButton.getValue(); 810 if (newValue != null) { 811 executeCommand(BACKGROUND_COLOR.getCommand(), colorValueToRGBA(newValue)); 812 bgColorButton.hide(); 813 } 814 }); 815 } 816 817 private String colorValueToRGBA(Color c) { 818 return String.format((Locale)null, "rgba(%d, %d, %d, %.5f)", 819 Math.round(c.getRed() * 255), 820 Math.round(c.getGreen() * 255), 821 Math.round(c.getBlue() * 255), 822 c.getOpacity()); 823 } 824 825 private Button addButton(ToolBar toolbar, final String iconName, String tooltipText, 826 final String command, final String styleClass) { 827 Button button = new Button(); 828 button.setFocusTraversable(false); 829 button.getStyleClass().add(styleClass); 830 toolbar.getItems().add(button); 831 832 Image icon = AccessController.doPrivileged((PrivilegedAction<Image>) () -> new Image(HTMLEditorSkin.class.getResource(iconName).toString())); 833 // button.setGraphic(new ImageView(icon)); 834 ((StyleableProperty)button.graphicProperty()).applyStyle(null, new ImageView(icon)); 835 button.setTooltip(new Tooltip(tooltipText)); 836 837 button.setOnAction(event -> { 838 executeCommand(command, null); 839 updateToolbarState(false); 840 }); 841 842 return button; 843 } 844 845 private ToggleButton addToggleButton(ToolBar toolbar, ToggleGroup toggleGroup, 846 final String iconName, String tooltipText, final String command, final String styleClass) { 847 ToggleButton toggleButton = new ToggleButton(); 848 toggleButton.setUserData(command); 849 toggleButton.setFocusTraversable(false); 850 toggleButton.getStyleClass().add(styleClass); 851 toolbar.getItems().add(toggleButton); 852 if (toggleGroup != null) { 853 toggleButton.setToggleGroup(toggleGroup); 854 } 855 856 Image icon = AccessController.doPrivileged((PrivilegedAction<Image>) () -> new Image(HTMLEditorSkin.class.getResource(iconName).toString())); 857 ((StyleableProperty)toggleButton.graphicProperty()).applyStyle(null, new ImageView(icon)); 858 // toggleButton.setGraphic(new ImageView(icon)); 859 860 toggleButton.setTooltip(new Tooltip(tooltipText)); 861 862 if (!BOLD.getCommand().equals(command)) { 863 toggleButton.selectedProperty().addListener((observable, oldValue, newValue) -> { 864 if (getCommandState(command) != newValue.booleanValue()) { 865 executeCommand(command, null); 866 } 867 }); 868 } 869 return toggleButton; 870 } 871 872 private void createFormatMenuItem(String formatValue, String label) { 873 formatComboBox.getItems().add(label); 874 formatStyleMap.put(label, formatValue); 875 styleFormatMap.put(formatValue, label); 876 } 877 878 private void createFontSizeMenuItem(String fontSizeValue, String label) { 879 fontSizeComboBox.getItems().add(label); 880 fontSizeMap.put(label, fontSizeValue); 881 sizeFontMap.put(fontSizeValue, label); 882 } 883 884 private void updateNodeOrientation() { 885 NodeOrientation orientation = getSkinnable().getEffectiveNodeOrientation(); 886 887 HTMLDocument htmlDocument = (HTMLDocument)webPage.getDocument(webPage.getMainFrame()); 888 HTMLElement htmlDocumentElement = (HTMLElement)htmlDocument.getDocumentElement(); 889 if (htmlDocumentElement.getAttribute("dir") == null) { 890 htmlDocumentElement.setAttribute("dir", (orientation == RIGHT_TO_LEFT) ? "rtl" : "ltr"); 891 } 892 893 } 894 895 private void updateToolbarState(final boolean updateAlignment) { 896 if (!webView.isFocused()) { 897 return; 898 } 899 900 atomicityCount++; 901 902 // These command aways return true. 903 copyButton.setDisable(!isCommandEnabled(CUT.getCommand())); 904 cutButton.setDisable(!isCommandEnabled(COPY.getCommand())); 905 pasteButton.setDisable(!isCommandEnabled(PASTE.getCommand())); 906 907 // undoButton.setDisable(!isCommandEnabled(UNDO.getCommand())); 908 // redoButton.setDisable(!isCommandEnabled(REDO.getCommand())); 909 910 // undoButton.setDisable(!isCommandEnabled(FORMAT.getCommand())); 911 // redoButton.setDisable(!isCommandEnabled(FORMAT.getCommand())); 912 913 insertHorizontalRuleButton.setDisable(!isCommandEnabled(INSERT_HORIZONTAL_RULE.getCommand())); 914 915 if (updateAlignment) { 916 alignLeftButton.setDisable(!isCommandEnabled(ALIGN_LEFT.getCommand())); 917 alignLeftButton.setSelected(getCommandState(ALIGN_LEFT.getCommand())); 918 alignCenterButton.setDisable(!isCommandEnabled(ALIGN_CENTER.getCommand())); 919 alignCenterButton.setSelected(getCommandState(ALIGN_CENTER.getCommand())); 920 alignRightButton.setDisable(!isCommandEnabled(ALIGN_RIGHT.getCommand())); 921 alignRightButton.setSelected(getCommandState(ALIGN_RIGHT.getCommand())); 922 alignJustifyButton.setDisable(!isCommandEnabled(ALIGN_JUSTIFY.getCommand())); 923 alignJustifyButton.setSelected(getCommandState(ALIGN_JUSTIFY.getCommand())); 924 } else { 925 if (alignmentToggleGroup.getSelectedToggle() != null) { 926 String command = alignmentToggleGroup.getSelectedToggle().getUserData().toString(); 927 if (isCommandEnabled(command) && !getCommandState(command) ) { 928 executeCommand(command, null); 929 } 930 } 931 } 932 933 if (alignmentToggleGroup.getSelectedToggle() == null 934 && webPage.getClientSelectedText().isEmpty()) { 935 alignmentToggleGroup.selectToggle(alignLeftButton); 936 } 937 938 bulletsButton.setDisable(!isCommandEnabled(BULLETS.getCommand())); 939 bulletsButton.setSelected(getCommandState(BULLETS.getCommand())); 940 numbersButton.setDisable(!isCommandEnabled(NUMBERS.getCommand())); 941 numbersButton.setSelected(getCommandState(NUMBERS.getCommand())); 942 943 indentButton.setDisable(!isCommandEnabled(INDENT.getCommand())); 944 outdentButton.setDisable(!isCommandEnabled(OUTDENT.getCommand())); 945 946 formatComboBox.setDisable(!isCommandEnabled(FORMAT.getCommand())); 947 948 949 String formatValue = getCommandValue(FORMAT.getCommand()); 950 if (formatValue != null) { 951 String htmlTag = "<" + formatValue + ">"; 952 String comboFormatValue = styleFormatMap.get(htmlTag); 953 String format = formatComboBox.getValue(); 954 955 // if the format value is then we assume that we're dealing with a paragraph, 956 // which seems to correspond with the HTML output we receive. 957 if ((resetToolbarState || htmlTag.equals("<>") || htmlTag.equalsIgnoreCase("<div>") || htmlTag.equalsIgnoreCase("<blockquote>"))) { 958 formatComboBox.setValue(resources.getString("paragraph")); 959 } else if (format != null && ! format.equalsIgnoreCase(comboFormatValue)) { 960 formatComboBox.setValue(comboFormatValue); 961 } 962 } 963 964 fontFamilyComboBox.setDisable(!isCommandEnabled(FONT_FAMILY.getCommand())); 965 final String fontFamilyValue = getCommandValue(FONT_FAMILY.getCommand()); 966 if (fontFamilyValue != null) { 967 String fontFamilyStr = fontFamilyValue; 968 969 // stripping out apostrophe characters, which are appended to either 970 // end of the font face name when the font face has one or more spaces. 971 if (fontFamilyStr.startsWith("'")) { 972 fontFamilyStr = fontFamilyStr.substring(1); 973 } 974 if (fontFamilyStr.endsWith("'")) { 975 fontFamilyStr = fontFamilyStr.substring(0,fontFamilyStr.length() - 1); 976 } 977 978 Object selectedFont = fontFamilyComboBox.getValue(); 979 if (selectedFont instanceof String) { 980 if (!selectedFont.equals(fontFamilyStr)) { 981 982 ObservableList<String> fontFamilyItems = fontFamilyComboBox.getItems(); 983 String selectedComboFont = null; 984 for (String comboFontFamilyValue : fontFamilyItems) { 985 986 if (comboFontFamilyValue.equals(fontFamilyStr)) { 987 selectedComboFont = comboFontFamilyValue; 988 break; 989 } 990 // Note: By default, 'Dialog' is the font returned from webview. 991 // For presidio, we're just mapping to the default font. 992 if (comboFontFamilyValue.equals("") && fontFamilyStr.equals("Dialog")) { 993 selectedComboFont = comboFontFamilyValue; 994 break; 995 } 996 } 997 998 if (selectedComboFont != null) { 999 fontFamilyComboBox.setValue(selectedComboFont); 1000 } 1001 } 1002 } 1003 } 1004 1005 fontSizeComboBox.setDisable(!isCommandEnabled(FONT_SIZE.getCommand())); 1006 String fontSizeValue = getCommandValue(FONT_SIZE.getCommand()); 1007 1008 // added test for fontSizeValue == null to combat RT-28847 1009 if (resetToolbarState && fontSizeValue == null) { 1010 fontSizeComboBox.setValue(sizeFontMap.get(SIZE_SMALL)); 1011 } else { 1012 if (fontSizeValue != null) { 1013 if (!fontSizeComboBox.getValue().equals(sizeFontMap.get(fontSizeValue))) { 1014 fontSizeComboBox.setValue(sizeFontMap.get(fontSizeValue)); 1015 } 1016 } 1017 else { 1018 /* 1019 ** these is no font size set in webview, 1020 ** let's just use the default.... 1021 */ 1022 if ((fontSizeComboBox.getValue() == null) || !fontSizeComboBox.getValue().equals(sizeFontMap.get(SIZE_SMALL))) { 1023 fontSizeComboBox.setValue(sizeFontMap.get(SIZE_SMALL)); 1024 } 1025 } 1026 } 1027 1028 boldButton.setDisable(!isCommandEnabled(BOLD.getCommand())); 1029 boldButton.setSelected(getCommandState(BOLD.getCommand())); 1030 italicButton.setDisable(!isCommandEnabled(ITALIC.getCommand())); 1031 italicButton.setSelected(getCommandState(ITALIC.getCommand())); 1032 underlineButton.setDisable(!isCommandEnabled(UNDERLINE.getCommand())); 1033 underlineButton.setSelected(getCommandState(UNDERLINE.getCommand())); 1034 strikethroughButton.setDisable(!isCommandEnabled(STRIKETHROUGH.getCommand())); 1035 strikethroughButton.setSelected(getCommandState(STRIKETHROUGH.getCommand())); 1036 1037 fgColorButton.setDisable(!isCommandEnabled(FOREGROUND_COLOR.getCommand())); 1038 String foregroundColorValue = getCommandValue(FOREGROUND_COLOR.getCommand()); 1039 if (foregroundColorValue != null) { 1040 fgColorButton.setValue(getColor(foregroundColorValue)); 1041 } 1042 1043 bgColorButton.setDisable(!isCommandEnabled(BACKGROUND_COLOR.getCommand())); 1044 String backgroundColorValue = getCommandValue(BACKGROUND_COLOR.getCommand()); 1045 if (backgroundColorValue != null) { 1046 bgColorButton.setValue(getColor(backgroundColorValue)); 1047 } 1048 1049 atomicityCount = atomicityCount == 0 ? 0 : --atomicityCount; 1050 } 1051 1052 private void enableToolbar(final boolean enable) { 1053 Platform.runLater(() -> { 1054 1055 // Make sure buttons have been created to avoid NPE 1056 if (copyButton == null) return; 1057 1058 /* 1059 ** if we're to enable, we still only enable 1060 ** the cut/copy/paste buttons that make sense 1061 */ 1062 if (enable) { 1063 copyButton.setDisable(!isCommandEnabled(COPY.getCommand())); 1064 cutButton.setDisable(!isCommandEnabled(CUT.getCommand())); 1065 pasteButton.setDisable(!isCommandEnabled(PASTE.getCommand())); 1066 } else { 1067 copyButton.setDisable(true); 1068 cutButton.setDisable(true); 1069 pasteButton.setDisable(true); 1070 } 1071 1072 // undoButton.setDisable(!enable); 1073 // redoButton.setDisable(!enable); 1074 insertHorizontalRuleButton.setDisable(!enable); 1075 alignLeftButton.setDisable(!enable); 1076 alignCenterButton.setDisable(!enable); 1077 alignRightButton.setDisable(!enable); 1078 alignJustifyButton.setDisable(!enable); 1079 bulletsButton.setDisable(!enable); 1080 numbersButton.setDisable(!enable); 1081 indentButton.setDisable(!enable); 1082 outdentButton.setDisable(!enable); 1083 formatComboBox.setDisable(!enable); 1084 fontFamilyComboBox.setDisable(!enable); 1085 fontSizeComboBox.setDisable(!enable); 1086 boldButton.setDisable(!enable); 1087 italicButton.setDisable(!enable); 1088 underlineButton.setDisable(!enable); 1089 strikethroughButton.setDisable(!enable); 1090 fgColorButton.setDisable(!enable); 1091 bgColorButton.setDisable(!enable); 1092 }); 1093 } 1094 1095 private boolean executeCommand(String command, String value) { 1096 // The mentions of atomicity throughout this class relate back to RT-39941, 1097 // refer to that jira issue for more context. 1098 if (!enableAtomicityCheck || (enableAtomicityCheck && atomicityCount == 0)) { 1099 return webPage.executeCommand(command, value); 1100 } 1101 return false; 1102 } 1103 1104 private boolean isCommandEnabled(String command) { 1105 return webPage.queryCommandEnabled(command); 1106 } 1107 1108 private void setContentEditable(boolean b) { 1109 HTMLDocument htmlDocument = (HTMLDocument)webPage.getDocument(webPage.getMainFrame()); 1110 HTMLElement htmlDocumentElement = (HTMLElement)htmlDocument.getDocumentElement(); 1111 HTMLElement htmlBodyElement = (HTMLElement)htmlDocumentElement.getElementsByTagName("body").item(0); 1112 htmlBodyElement.setAttribute("contenteditable", Boolean.toString(b)); 1113 } 1114 1115 private void setDesignMode(String mode) { 1116 HTMLDocumentImpl htmlDocumentImpl = (HTMLDocumentImpl)webPage.getDocument(webPage.getMainFrame()); 1117 htmlDocumentImpl.setDesignMode(mode); 1118 } 1119 1120 private boolean getCommandState(String command) { 1121 return webPage.queryCommandState(command); 1122 } 1123 1124 private String getCommandValue(String command) { 1125 return webPage.queryCommandValue(command); 1126 } 1127 1128 private Color getColor(String value) { 1129 Color color = Color.web(value); 1130 /* The default background color for WebView, according to the HTML 1131 * standard is rgba=#00000000 (transparent). The canvas background is expected 1132 * to be white. 1133 */ 1134 if (color.equals(Color.TRANSPARENT)) { 1135 color = Color.WHITE; 1136 } 1137 return color; 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 STYLEWITHCSS("styleWithCSS"), 1202 1203 INSERT_NEW_LINE("insertnewline"), 1204 INSERT_TAB("inserttab"); 1205 1206 private final String command; 1207 1208 Command(String command) { 1209 this.command = command; 1210 } 1211 1212 public String getCommand() { 1213 return command; 1214 } 1215 } 1216 }