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