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