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