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