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