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