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