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