1 /*
   2  * Copyright (c) 2012, 2014, Oracle and/or its affiliates.
   3  * All rights reserved. Use is subject to license terms.
   4  *
   5  * This file is available and licensed under the following license:
   6  *
   7  * Redistribution and use in source and binary forms, with or without
   8  * modification, are permitted provided that the following conditions
   9  * are met:
  10  *
  11  *  - Redistributions of source code must retain the above copyright
  12  *    notice, this list of conditions and the following disclaimer.
  13  *  - Redistributions in binary form must reproduce the above copyright
  14  *    notice, this list of conditions and the following disclaimer in
  15  *    the documentation and/or other materials provided with the distribution.
  16  *  - Neither the name of Oracle Corporation nor the names of its
  17  *    contributors may be used to endorse or promote products derived
  18  *    from this software without specific prior written permission.
  19  *
  20  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  21  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  22  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  23  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  24  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  25  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  26  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  30  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  31  */
  32 package com.oracle.javafx.scenebuilder.kit.editor.util;
  33 
  34 import com.oracle.javafx.scenebuilder.kit.editor.EditorController;
  35 import com.oracle.javafx.scenebuilder.kit.editor.EditorController.ControlAction;
  36 import com.oracle.javafx.scenebuilder.kit.editor.EditorController.EditAction;
  37 import com.oracle.javafx.scenebuilder.kit.editor.i18n.I18N;
  38 import com.oracle.javafx.scenebuilder.kit.editor.selection.AbstractSelectionGroup;
  39 import com.oracle.javafx.scenebuilder.kit.editor.selection.GridSelectionGroup;
  40 import com.oracle.javafx.scenebuilder.kit.editor.selection.ObjectSelectionGroup;
  41 import com.oracle.javafx.scenebuilder.kit.editor.selection.Selection;
  42 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMObject;
  43 
  44 import javafx.beans.value.ChangeListener;
  45 import javafx.collections.ObservableList;
  46 import javafx.event.ActionEvent;
  47 import javafx.event.Event;
  48 import javafx.event.EventHandler;
  49 import javafx.scene.control.ContextMenu;
  50 import javafx.scene.control.Menu;
  51 import javafx.scene.control.MenuItem;
  52 import javafx.scene.control.RadioMenuItem;
  53 import javafx.scene.control.SeparatorMenuItem;
  54 import javafx.scene.layout.GridPane;
  55 import javafx.stage.WindowEvent;
  56 
  57 /**
  58  * Controller used to handle contextual menu in hierarchy and content view.
  59  */
  60 public class ContextMenuController {
  61 
  62     private final EditorController editorController;
  63     private ContextMenu contextMenu; // Initialized lazily
  64 
  65     private MenuItem cutMenuItem;
  66     private MenuItem copyMenuItem;
  67     private MenuItem pasteMenuItem;
  68     private MenuItem pasteIntoMenuItem;
  69     private MenuItem duplicateMenuItem;
  70     private MenuItem deleteMenuItem;
  71     private MenuItem selectParentMenuItem;
  72     private MenuItem fitToParentMenuItem;
  73     private MenuItem useComputedSizesMenuItem;
  74     private MenuItem editIncludedFileMenuItem;
  75     private MenuItem revealIncludedFileMenuItem;
  76     private MenuItem bringToFrontMenuItem;
  77     private MenuItem sendToBackMenuItem;
  78     private MenuItem bringForwardMenuItem;
  79     private MenuItem sendBackwardMenuItem;
  80     private Menu wrapInMenu;
  81     private MenuItem wrapInAnchorPaneMenuItem;
  82     private MenuItem wrapInBorderPaneMenuItem;
  83     private MenuItem wrapInButtonBarMenuItem;
  84     private MenuItem wrapInDialogPaneMenuItem;
  85     private MenuItem wrapInFlowPaneMenuItem;
  86     private MenuItem wrapInGridPaneMenuItem;
  87     private MenuItem wrapInGroupMenuItem;
  88     private MenuItem wrapInHBoxMenuItem;
  89     private MenuItem wrapInPaneMenuItem;
  90     private MenuItem wrapInScrollPaneMenuItem;
  91     private MenuItem wrapInSplitPaneMenuItem;
  92     private MenuItem wrapInStackPaneMenuItem;
  93     private MenuItem wrapInTabPaneMenuItem;
  94     private MenuItem wrapInTextFlowMenuItem;
  95     private MenuItem wrapInTilePaneMenuItem;
  96     private MenuItem wrapInTitledPaneMenuItem;
  97     private MenuItem wrapInToolBarMenuItem;
  98     private MenuItem wrapInVBoxMenuItem;
  99     private MenuItem unwrapMenuItem;
 100     private Menu gridPaneMenu;
 101     private MenuItem moveRowAboveMenuItem;
 102     private MenuItem moveRowBelowMenuItem;
 103     private MenuItem moveColumnBeforeMenuItem;
 104     private MenuItem moveColumnAfterMenuItem;
 105     private MenuItem addRowAboveMenuItem;
 106     private MenuItem addRowBelowMenuItem;
 107     private MenuItem addColumnBeforeMenuItem;
 108     private MenuItem addColumnAfterMenuItem;
 109     private MenuItem increaseRowSpan;
 110     private MenuItem decreaseRowSpan;
 111     private MenuItem increaseColumnSpan;
 112     private MenuItem decreaseColumnSpan;
 113 
 114     private final EventHandler<Event> onShowingMenuEventHandler
 115             = t -> {
 116         assert t.getSource() instanceof Menu;
 117         final Menu menu = (Menu) t.getSource();
 118         handleOnShowing(menu.getItems());
 119     };
 120 
 121     private final EventHandler<WindowEvent> onShowingContextMenuEventHandler
 122             = t -> {
 123         assert t.getSource() instanceof ContextMenu;
 124         final ContextMenu contextMenu = (ContextMenu) t.getSource();
 125         handleOnShowing(contextMenu.getItems());
 126     };
 127 
 128     private final EventHandler<ActionEvent> onActionEventHandler
 129             = t -> {
 130         assert t.getSource() instanceof MenuItem;
 131         handleOnActionMenu((MenuItem) t.getSource());
 132     };
 133 
 134     private final ChangeListener<Number> jobManagerRevisionListener
 135             = (observable, oldValue, newValue) -> jobManagerRevisionDidChange();
 136 
 137     public ContextMenuController(final EditorController editorController) {
 138         this.editorController = editorController;
 139         this.editorController.getJobManager().revisionProperty().addListener(jobManagerRevisionListener);
 140     }
 141 
 142     public ContextMenu getContextMenu() {
 143         if (contextMenu == null) {
 144             // Initialization of context menu
 145             contextMenu = new ContextMenu();
 146             contextMenu.setConsumeAutoHidingEvents(false);
 147             contextMenu.setOnShowing(onShowingContextMenuEventHandler);
 148         }
 149         return contextMenu;
 150     }
 151 
 152     /**
 153      * Updates the context menu items depending on the selection.
 154      */
 155     public void updateContextMenuItems() {
 156 
 157         // Lazely initialization of context menu items
 158         if (copyMenuItem == null) {
 159             initializeMenuItems();
 160         }
 161         getContextMenu().getItems().clear();
 162 
 163         final Selection selection = editorController.getSelection();
 164         if (selection.isEmpty() == false) {
 165             final AbstractSelectionGroup asg = selection.getGroup();
 166             if (asg instanceof ObjectSelectionGroup) {
 167 
 168                 // Common editing actions
 169                 getContextMenu().getItems().addAll(
 170                         cutMenuItem,
 171                         copyMenuItem,
 172                         pasteMenuItem,
 173                         pasteIntoMenuItem,
 174                         duplicateMenuItem,
 175                         deleteMenuItem,
 176                         new SeparatorMenuItem(),
 177                         selectParentMenuItem,
 178                         new SeparatorMenuItem(),
 179                         fitToParentMenuItem,
 180                         useComputedSizesMenuItem);
 181                 // GridPane specific actions
 182                 if (canPerformGridPaneActions() || canPerformGridPaneChildActions()) {
 183                     updateGridPaneMenuItems();
 184                     getContextMenu().getItems().add(gridPaneMenu);
 185                 }
 186                 // Common editing actions on going
 187                 getContextMenu().getItems().addAll(
 188                         editIncludedFileMenuItem,
 189                         revealIncludedFileMenuItem,
 190                         new SeparatorMenuItem(),
 191                         bringToFrontMenuItem,
 192                         sendToBackMenuItem,
 193                         bringForwardMenuItem,
 194                         sendBackwardMenuItem,
 195                         new SeparatorMenuItem(),
 196                         wrapInMenu,
 197                         unwrapMenuItem);
 198 
 199             } else {
 200                 assert asg instanceof GridSelectionGroup;
 201                 getContextMenu().getItems().addAll(
 202                         deleteMenuItem,
 203                         new SeparatorMenuItem(),
 204                         moveRowAboveMenuItem,
 205                         moveRowBelowMenuItem,
 206                         moveColumnBeforeMenuItem,
 207                         moveColumnAfterMenuItem,
 208                         new SeparatorMenuItem(),
 209                         addRowAboveMenuItem,
 210                         addRowBelowMenuItem,
 211                         addColumnBeforeMenuItem,
 212                         addColumnAfterMenuItem);
 213             }
 214         }
 215     }
 216 
 217     private void handleOnShowing(final ObservableList<MenuItem> menuItems) {
 218         for (MenuItem menuItem : menuItems) {
 219             final boolean disable, selected;
 220             final String title;
 221             if (menuItem.getUserData() instanceof MenuItemController) {
 222                 final MenuItemController c = (MenuItemController) menuItem.getUserData();
 223                 disable = !c.canPerform();
 224                 title = c.getTitle();
 225                 selected = c.isSelected();
 226             } else {
 227                 if (menuItem instanceof Menu) {
 228                     disable = false;
 229                     selected = false;
 230                     title = null;
 231                 } else {
 232                     disable = true;
 233                     selected = false;
 234                     title = null;
 235                 }
 236             }
 237             menuItem.setDisable(disable);
 238             if (title != null) {
 239                 menuItem.setText(title);
 240             }
 241             if (menuItem instanceof RadioMenuItem) {
 242                 final RadioMenuItem ri = (RadioMenuItem) menuItem;
 243                 ri.setSelected(selected);
 244             }
 245         }
 246     }
 247 
 248     private void handleOnActionMenu(MenuItem i) {
 249         assert i.getUserData() instanceof MenuItemController;
 250         final MenuItemController c = (MenuItemController) i.getUserData();
 251         c.perform();
 252     }
 253 
 254     private void jobManagerRevisionDidChange() {
 255         // FXOMDocument has been modified by a job.
 256         if (contextMenu != null && contextMenu.isShowing()) {
 257             contextMenu.hide();
 258         }
 259     }
 260 
 261 
 262     /**
 263      * Returns true if all the selected items are GridPanes.
 264      *
 265      * @return
 266      */
 267     private boolean canPerformGridPaneActions() {
 268         boolean result = false;
 269         final Selection selection = editorController.getSelection();
 270         final AbstractSelectionGroup asg = selection.getGroup();
 271 
 272         if (asg instanceof ObjectSelectionGroup) {
 273             final ObjectSelectionGroup osg = (ObjectSelectionGroup) asg;
 274             result = true;
 275             for (FXOMObject obj : osg.getItems()) {
 276                 if ((obj.getSceneGraphObject() instanceof GridPane) == false) {
 277                     result = false;
 278                     break;
 279                 }
 280             }
 281         }
 282         return result;
 283     }
 284 
 285     /**
 286      * Returns true if all the selected items are children of 1 or more GridPanes.
 287      *
 288      * @return
 289      */
 290     private boolean canPerformGridPaneChildActions() {
 291         boolean result = false;
 292         final Selection selection = editorController.getSelection();
 293         final AbstractSelectionGroup asg = selection.getGroup();
 294 
 295         if (asg instanceof ObjectSelectionGroup) {
 296             final ObjectSelectionGroup osg = (ObjectSelectionGroup) asg;
 297             result = true;
 298             for (FXOMObject obj : osg.getItems()) {
 299                 final FXOMObject parent = obj.getParentObject();
 300                 if (parent == null
 301                         || (parent.getSceneGraphObject() instanceof GridPane) == false) {
 302                     result = false;
 303                     break;
 304                 }
 305             }
 306         }
 307         return result;
 308     }
 309 
 310     private void updateGridPaneMenuItems() {
 311         assert canPerformGridPaneActions() || canPerformGridPaneChildActions();
 312         gridPaneMenu.getItems().clear();
 313         // Add actions on the GridPane rows/columns
 314         if (canPerformGridPaneActions()) {
 315             gridPaneMenu.getItems().addAll(
 316                     moveRowAboveMenuItem,
 317                     moveRowBelowMenuItem,
 318                     moveColumnBeforeMenuItem,
 319                     moveColumnAfterMenuItem,
 320                     new SeparatorMenuItem(),
 321                     addRowAboveMenuItem,
 322                     addRowBelowMenuItem,
 323                     addColumnBeforeMenuItem,
 324                     addColumnAfterMenuItem);
 325         }
 326         // Add actions on the GridPane row/column span
 327         if (canPerformGridPaneChildActions()) {
 328             // The selection is a GridPane child of another GridPane
 329             if (gridPaneMenu.getItems().isEmpty() == false) {
 330                 gridPaneMenu.getItems().add(new SeparatorMenuItem());
 331             }
 332             gridPaneMenu.getItems().addAll(
 333                     increaseRowSpan,
 334                     decreaseRowSpan,
 335                     increaseColumnSpan,
 336                     decreaseColumnSpan);
 337         }
 338     }
 339 
 340     private void initializeMenuItems() {
 341 
 342         copyMenuItem = new MenuItem(I18N.getString("menu.title.copy"));
 343         copyMenuItem.setOnAction(onActionEventHandler);
 344         copyMenuItem.setUserData(new ControlActionController(ControlAction.COPY));
 345         selectParentMenuItem = new MenuItem(I18N.getString("menu.title.select.parent"));
 346         selectParentMenuItem.setOnAction(onActionEventHandler);
 347         selectParentMenuItem.setUserData(new ControlActionController(ControlAction.SELECT_PARENT));
 348         editIncludedFileMenuItem = new MenuItem(I18N.getString("menu.title.edit.included.default"));
 349         editIncludedFileMenuItem.setOnAction(onActionEventHandler);
 350         editIncludedFileMenuItem.setUserData(new ControlActionController(ControlAction.EDIT_INCLUDED_FILE));
 351         revealIncludedFileMenuItem = new MenuItem(I18N.getString("menu.title.reveal.included.default"));
 352         revealIncludedFileMenuItem.setOnAction(onActionEventHandler);
 353         revealIncludedFileMenuItem.setUserData(new ControlActionController(ControlAction.REVEAL_INCLUDED_FILE));
 354         cutMenuItem = new MenuItem(I18N.getString("menu.title.cut"));
 355         cutMenuItem.setOnAction(onActionEventHandler);
 356         cutMenuItem.setUserData(new EditActionController(EditAction.CUT));
 357         pasteMenuItem = new MenuItem(I18N.getString("menu.title.paste"));
 358         pasteMenuItem.setOnAction(onActionEventHandler);
 359         pasteMenuItem.setUserData(new EditActionController(EditAction.PASTE));
 360         pasteIntoMenuItem = new MenuItem(I18N.getString("menu.title.paste.into"));
 361         pasteIntoMenuItem.setOnAction(onActionEventHandler);
 362         pasteIntoMenuItem.setUserData(new EditActionController(EditAction.PASTE_INTO));
 363         duplicateMenuItem = new MenuItem(I18N.getString("menu.title.duplicate"));
 364         duplicateMenuItem.setOnAction(onActionEventHandler);
 365         duplicateMenuItem.setUserData(new EditActionController(EditAction.DUPLICATE));
 366         deleteMenuItem = new MenuItem(I18N.getString("menu.title.delete"));
 367         deleteMenuItem.setOnAction(onActionEventHandler);
 368         deleteMenuItem.setUserData(new EditActionController(EditAction.DELETE));
 369         fitToParentMenuItem = new MenuItem(I18N.getString("menu.title.fit"));
 370         fitToParentMenuItem.setOnAction(onActionEventHandler);
 371         fitToParentMenuItem.setUserData(new EditActionController(EditAction.FIT_TO_PARENT));
 372         useComputedSizesMenuItem = new MenuItem(I18N.getString("menu.title.use.computed.sizes"));
 373         useComputedSizesMenuItem.setOnAction(onActionEventHandler);
 374         useComputedSizesMenuItem.setUserData(new EditActionController(EditAction.USE_COMPUTED_SIZES));
 375         bringToFrontMenuItem = new MenuItem(I18N.getString("menu.title.front"));
 376         bringToFrontMenuItem.setOnAction(onActionEventHandler);
 377         bringToFrontMenuItem.setUserData(new EditActionController(EditAction.BRING_TO_FRONT));
 378         sendToBackMenuItem = new MenuItem(I18N.getString("menu.title.back"));
 379         sendToBackMenuItem.setOnAction(onActionEventHandler);
 380         sendToBackMenuItem.setUserData(new EditActionController(EditAction.SEND_TO_BACK));
 381         bringForwardMenuItem = new MenuItem(I18N.getString("menu.title.forward"));
 382         bringForwardMenuItem.setOnAction(onActionEventHandler);
 383         bringForwardMenuItem.setUserData(new EditActionController(EditAction.BRING_FORWARD));
 384         sendBackwardMenuItem = new MenuItem(I18N.getString("menu.title.backward"));
 385         sendBackwardMenuItem.setOnAction(onActionEventHandler);
 386         sendBackwardMenuItem.setUserData(new EditActionController(EditAction.SEND_BACKWARD));
 387         wrapInMenu = new Menu(I18N.getString("menu.title.wrap"));
 388         wrapInAnchorPaneMenuItem = new MenuItem("AnchorPane");
 389         wrapInAnchorPaneMenuItem.setOnAction(onActionEventHandler);
 390         wrapInAnchorPaneMenuItem.setUserData(new EditActionController(EditAction.WRAP_IN_ANCHOR_PANE));
 391         wrapInBorderPaneMenuItem = new MenuItem("BorderPane");
 392         wrapInBorderPaneMenuItem.setOnAction(onActionEventHandler);
 393         wrapInBorderPaneMenuItem.setUserData(new EditActionController(EditAction.WRAP_IN_BORDER_PANE));
 394         wrapInButtonBarMenuItem = new MenuItem("ButtonBar");
 395         wrapInButtonBarMenuItem.setOnAction(onActionEventHandler);
 396         wrapInButtonBarMenuItem.setUserData(new EditActionController(EditAction.WRAP_IN_BUTTON_BAR));
 397         wrapInDialogPaneMenuItem = new MenuItem("DialogPane");
 398         wrapInDialogPaneMenuItem.setOnAction(onActionEventHandler);
 399         wrapInDialogPaneMenuItem.setUserData(new EditActionController(EditAction.WRAP_IN_DIALOG_PANE));
 400         wrapInFlowPaneMenuItem = new MenuItem("FlowPane");
 401         wrapInFlowPaneMenuItem.setOnAction(onActionEventHandler);
 402         wrapInFlowPaneMenuItem.setUserData(new EditActionController(EditAction.WRAP_IN_FLOW_PANE));
 403         wrapInGridPaneMenuItem = new MenuItem("GridPane");
 404         wrapInGridPaneMenuItem.setOnAction(onActionEventHandler);
 405         wrapInGridPaneMenuItem.setUserData(new EditActionController(EditAction.WRAP_IN_GRID_PANE));
 406         wrapInHBoxMenuItem = new MenuItem("HBox");
 407         wrapInHBoxMenuItem.setOnAction(onActionEventHandler);
 408         wrapInHBoxMenuItem.setUserData(new EditActionController(EditAction.WRAP_IN_HBOX));
 409         wrapInPaneMenuItem = new MenuItem("Pane");
 410         wrapInPaneMenuItem.setOnAction(onActionEventHandler);
 411         wrapInPaneMenuItem.setUserData(new EditActionController(EditAction.WRAP_IN_PANE));
 412         wrapInScrollPaneMenuItem = new MenuItem("ScrollPane");
 413         wrapInScrollPaneMenuItem.setOnAction(onActionEventHandler);
 414         wrapInScrollPaneMenuItem.setUserData(new EditActionController(EditAction.WRAP_IN_SCROLL_PANE));
 415         wrapInSplitPaneMenuItem = new MenuItem("SplitPane");
 416         wrapInSplitPaneMenuItem.setOnAction(onActionEventHandler);
 417         wrapInSplitPaneMenuItem.setUserData(new EditActionController(EditAction.WRAP_IN_SPLIT_PANE));
 418         wrapInStackPaneMenuItem = new MenuItem("StackPane");
 419         wrapInStackPaneMenuItem.setOnAction(onActionEventHandler);
 420         wrapInStackPaneMenuItem.setUserData(new EditActionController(EditAction.WRAP_IN_STACK_PANE));
 421         wrapInTabPaneMenuItem = new MenuItem("TabPane");
 422         wrapInTabPaneMenuItem.setOnAction(onActionEventHandler);
 423         wrapInTabPaneMenuItem.setUserData(new EditActionController(EditAction.WRAP_IN_TAB_PANE));
 424         wrapInTilePaneMenuItem = new MenuItem("TilePane");
 425         wrapInTilePaneMenuItem.setOnAction(onActionEventHandler);
 426         wrapInTilePaneMenuItem.setUserData(new EditActionController(EditAction.WRAP_IN_TILE_PANE));
 427         wrapInTextFlowMenuItem = new MenuItem("TextFlow");
 428         wrapInTextFlowMenuItem.setOnAction(onActionEventHandler);
 429         wrapInTextFlowMenuItem.setUserData(new EditActionController(EditAction.WRAP_IN_TEXT_FLOW));
 430         wrapInTitledPaneMenuItem = new MenuItem("TitledPane");
 431         wrapInTitledPaneMenuItem.setOnAction(onActionEventHandler);
 432         wrapInTitledPaneMenuItem.setUserData(new EditActionController(EditAction.WRAP_IN_TITLED_PANE));
 433         wrapInToolBarMenuItem = new MenuItem("ToolBar");
 434         wrapInToolBarMenuItem.setOnAction(onActionEventHandler);
 435         wrapInToolBarMenuItem.setUserData(new EditActionController(EditAction.WRAP_IN_TOOL_BAR));
 436         wrapInVBoxMenuItem = new MenuItem("VBox");
 437         wrapInVBoxMenuItem.setOnAction(onActionEventHandler);
 438         wrapInVBoxMenuItem.setUserData(new EditActionController(EditAction.WRAP_IN_VBOX));
 439         wrapInGroupMenuItem = new MenuItem("Group");
 440         wrapInGroupMenuItem.setOnAction(onActionEventHandler);
 441         wrapInGroupMenuItem.setUserData(new EditActionController(EditAction.WRAP_IN_GROUP));
 442         wrapInMenu.getItems().setAll(
 443                 wrapInAnchorPaneMenuItem,
 444                 wrapInBorderPaneMenuItem,
 445                 wrapInButtonBarMenuItem,
 446                 wrapInDialogPaneMenuItem,
 447                 wrapInFlowPaneMenuItem,
 448                 wrapInGridPaneMenuItem,
 449                 wrapInGroupMenuItem,
 450                 wrapInHBoxMenuItem,
 451                 wrapInPaneMenuItem,
 452                 wrapInScrollPaneMenuItem,
 453                 wrapInSplitPaneMenuItem,
 454                 wrapInStackPaneMenuItem,
 455                 wrapInTabPaneMenuItem,
 456                 wrapInTextFlowMenuItem,
 457                 wrapInTilePaneMenuItem,
 458                 wrapInTitledPaneMenuItem,
 459                 wrapInToolBarMenuItem,
 460                 wrapInVBoxMenuItem);
 461         unwrapMenuItem = new MenuItem(I18N.getString("menu.title.unwrap"));
 462         unwrapMenuItem.setOnAction(onActionEventHandler);
 463         unwrapMenuItem.setUserData(new EditActionController(EditAction.UNWRAP));
 464         // GridPane specifics
 465         gridPaneMenu = new Menu(I18N.getString("menu.title.grid"));
 466         moveRowAboveMenuItem = new MenuItem(I18N.getString("menu.title.grid.move.row.above"));
 467         moveRowAboveMenuItem.setOnAction(onActionEventHandler);
 468         moveRowAboveMenuItem.setUserData(new EditActionController(EditAction.MOVE_ROW_ABOVE));
 469         moveRowBelowMenuItem = new MenuItem(I18N.getString("menu.title.grid.move.row.below"));
 470         moveRowBelowMenuItem.setOnAction(onActionEventHandler);
 471         moveRowBelowMenuItem.setUserData(new EditActionController(EditAction.MOVE_ROW_BELOW));
 472         moveColumnBeforeMenuItem = new MenuItem(I18N.getString("menu.title.grid.move.column.before"));
 473         moveColumnBeforeMenuItem.setOnAction(onActionEventHandler);
 474         moveColumnBeforeMenuItem.setUserData(new EditActionController(EditAction.MOVE_COLUMN_BEFORE));
 475         moveColumnAfterMenuItem = new MenuItem(I18N.getString("menu.title.grid.move.column.after"));
 476         moveColumnAfterMenuItem.setOnAction(onActionEventHandler);
 477         moveColumnAfterMenuItem.setUserData(new EditActionController(EditAction.MOVE_COLUMN_AFTER));
 478         addRowAboveMenuItem = new MenuItem(I18N.getString("menu.title.grid.add.row.above"));
 479         addRowAboveMenuItem.setOnAction(onActionEventHandler);
 480         addRowAboveMenuItem.setUserData(new EditActionController(EditAction.ADD_ROW_ABOVE));
 481         addRowBelowMenuItem = new MenuItem(I18N.getString("menu.title.grid.add.row.below"));
 482         addRowBelowMenuItem.setOnAction(onActionEventHandler);
 483         addRowBelowMenuItem.setUserData(new EditActionController(EditAction.ADD_ROW_BELOW));
 484         addColumnBeforeMenuItem = new MenuItem(I18N.getString("menu.title.grid.add.column.before"));
 485         addColumnBeforeMenuItem.setOnAction(onActionEventHandler);
 486         addColumnBeforeMenuItem.setUserData(new EditActionController(EditAction.ADD_COLUMN_BEFORE));
 487         addColumnAfterMenuItem = new MenuItem(I18N.getString("menu.title.grid.add.column.after"));
 488         addColumnAfterMenuItem.setOnAction(onActionEventHandler);
 489         addColumnAfterMenuItem.setUserData(new EditActionController(EditAction.ADD_COLUMN_AFTER));
 490         increaseRowSpan = new MenuItem(I18N.getString("menu.title.grid.increase.row.span"));
 491         increaseRowSpan.setOnAction(onActionEventHandler);
 492         increaseRowSpan.setUserData(new EditActionController(EditAction.INCREASE_ROW_SPAN));
 493         decreaseRowSpan = new MenuItem(I18N.getString("menu.title.grid.decrease.row.span"));
 494         decreaseRowSpan.setOnAction(onActionEventHandler);
 495         decreaseRowSpan.setUserData(new EditActionController(EditAction.DECREASE_ROW_SPAN));
 496         increaseColumnSpan = new MenuItem(I18N.getString("menu.title.grid.increase.column.span"));
 497         increaseColumnSpan.setOnAction(onActionEventHandler);
 498         increaseColumnSpan.setUserData(new EditActionController(EditAction.INCREASE_COLUMN_SPAN));
 499         decreaseColumnSpan = new MenuItem(I18N.getString("menu.title.grid.decrease.column.span"));
 500         decreaseColumnSpan.setOnAction(onActionEventHandler);
 501         decreaseColumnSpan.setUserData(new EditActionController(EditAction.DECREASE_COLUMN_SPAN));
 502 
 503         wrapInMenu.setOnShowing(onShowingMenuEventHandler);
 504         gridPaneMenu.setOnShowing(onShowingMenuEventHandler);
 505     }
 506 
 507     class EditActionController extends MenuItemController {
 508 
 509         private final EditAction editAction;
 510 
 511         public EditActionController(EditAction editAction) {
 512             this.editAction = editAction;
 513         }
 514 
 515         @Override
 516         public boolean canPerform() {
 517             boolean result;
 518             if (editorController.getFxomDocument() == null) {
 519                 result = false;
 520             } else {
 521                 result = editorController.canPerformEditAction(editAction);
 522             }
 523             return result;
 524         }
 525 
 526         @Override
 527         public void perform() {
 528             assert canPerform() : "editAction=" + editAction;
 529             editorController.performEditAction(editAction);
 530         }
 531     }
 532 
 533     class ControlActionController extends MenuItemController {
 534 
 535         private final ControlAction controlAction;
 536 
 537         public ControlActionController(ControlAction controlAction) {
 538             this.controlAction = controlAction;
 539         }
 540 
 541         @Override
 542         public boolean canPerform() {
 543             boolean result;
 544             if (editorController.getFxomDocument() == null) {
 545                 result = false;
 546             } else {
 547                 result = editorController.canPerformControlAction(controlAction);
 548             }
 549             return result;
 550         }
 551 
 552         @Override
 553         public void perform() {
 554             assert canPerform() : "controlAction=" + controlAction;
 555             editorController.performControlAction(controlAction);
 556         }
 557     }
 558 
 559     abstract class MenuItemController {
 560 
 561         public abstract boolean canPerform();
 562 
 563         public abstract void perform();
 564 
 565         public String getTitle() {
 566             return null;
 567         }
 568 
 569         public boolean isSelected() {
 570             return false;
 571         }
 572     }
 573 }