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