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.panel.content.mode;
  33 
  34 import com.oracle.javafx.scenebuilder.kit.editor.EditorController;
  35 import com.oracle.javafx.scenebuilder.kit.editor.drag.DragController;
  36 import com.oracle.javafx.scenebuilder.kit.editor.drag.target.AbstractDropTarget;
  37 import com.oracle.javafx.scenebuilder.kit.editor.drag.target.GridPaneDropTarget;
  38 import com.oracle.javafx.scenebuilder.kit.editor.drag.target.RootDropTarget;
  39 import com.oracle.javafx.scenebuilder.kit.editor.job.atomic.ModifyObjectJob;
  40 import com.oracle.javafx.scenebuilder.kit.editor.job.RelocateSelectionJob;
  41 import com.oracle.javafx.scenebuilder.kit.editor.messagelog.MessageLog;
  42 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.AbstractDecoration;
  43 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.ContentPanelController;
  44 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.driver.AbstractDriver;
  45 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.driver.gridpane.GridPaneHandles;
  46 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.driver.gridpane.GridPaneTring;
  47 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.driver.handles.AbstractHandles;
  48 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.driver.pring.AbstractPring;
  49 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.driver.tring.AbstractTring;
  50 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.gesture.AbstractGesture;
  51 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.gesture.DragGesture;
  52 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.gesture.mouse.SelectAndMoveGesture;
  53 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.gesture.mouse.SelectWithMarqueeGesture;
  54 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.gesture.ZoomGesture;
  55 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.gesture.key.MoveWithKeyGesture;
  56 import com.oracle.javafx.scenebuilder.kit.editor.util.InlineEditController;
  57 import com.oracle.javafx.scenebuilder.kit.editor.selection.GridSelectionGroup;
  58 import com.oracle.javafx.scenebuilder.kit.editor.selection.ObjectSelectionGroup;
  59 import com.oracle.javafx.scenebuilder.kit.editor.selection.Selection;
  60 import com.oracle.javafx.scenebuilder.kit.editor.util.ContextMenuController;
  61 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMDocument;
  62 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMInstance;
  63 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMObject;
  64 import com.oracle.javafx.scenebuilder.kit.metadata.Metadata;
  65 import com.oracle.javafx.scenebuilder.kit.metadata.property.ValuePropertyMetadata;
  66 import com.oracle.javafx.scenebuilder.kit.metadata.util.DesignHierarchyMask;
  67 import com.oracle.javafx.scenebuilder.kit.metadata.util.PropertyName;
  68 import java.util.ArrayList;
  69 import java.util.HashSet;
  70 import java.util.List;
  71 import java.util.Set;
  72 import javafx.collections.ObservableList;
  73 import javafx.event.EventHandler;
  74 import javafx.scene.Group;
  75 import javafx.scene.Node;
  76 import javafx.scene.control.TextArea;
  77 import javafx.scene.control.TextInputControl;
  78 import javafx.scene.input.DragEvent;
  79 import javafx.scene.input.InputEvent;
  80 import javafx.scene.input.KeyEvent;
  81 import javafx.scene.input.MouseButton;
  82 import javafx.scene.input.MouseEvent;
  83 import javafx.scene.input.ZoomEvent;
  84 import javafx.scene.layout.GridPane;
  85 import javafx.util.Callback;
  86 
  87 /**
  88  *
  89  *
  90  */
  91 
  92 /**
  93  *
  94  *
  95  */
  96 public class EditModeController extends AbstractModeController
  97 implements AbstractGesture.Observer {
  98 
  99     private final List<AbstractHandles<?>> handles = new ArrayList<>();
 100     private final Set<FXOMObject> excludes = new HashSet<>();
 101     private final SelectWithMarqueeGesture selectWithMarqueeGesture;
 102     private final SelectAndMoveGesture selectAndMoveGesture;
 103     private final ZoomGesture zoomGesture;
 104 
 105     private AbstractPring<?> pring;
 106     private AbstractTring<?> tring;
 107     private AbstractGesture activeGesture;
 108     private AbstractGesture glassGesture;
 109     private FXOMInstance inlineEditedObject;
 110 
 111 
 112     public EditModeController(ContentPanelController contentPanelController) {
 113         super(contentPanelController);
 114         selectWithMarqueeGesture = new SelectWithMarqueeGesture(contentPanelController);
 115         selectAndMoveGesture = new SelectAndMoveGesture(contentPanelController);
 116         zoomGesture = new ZoomGesture(contentPanelController);
 117     }
 118 
 119 
 120     /**
 121      * Returns null or the handles associated to the specified fxom object.
 122      *
 123      * @param fxomObject an fxom object (never null)
 124      * @return null or the handles associated to the specified fxom object.
 125      */
 126 
 127     public AbstractHandles<?> lookupHandles(FXOMObject fxomObject) {
 128         assert fxomObject != null;
 129 
 130         AbstractHandles<?> result = null;
 131         for (AbstractHandles<?> h : handles) {
 132             if (h.getFxomObject() == fxomObject) {
 133                 result = h;
 134                 break;
 135             }
 136         }
 137 
 138         return result;
 139     }
 140 
 141     /*
 142      * AbstractGesture.Observer
 143      */
 144 
 145     @Override
 146     public void gestureDidTerminate(AbstractGesture gesture) {
 147         assert activeGesture == gesture;
 148         activeGesture = null;
 149         startListeningToInputEvents();
 150         contentPanelController.endInteraction();
 151 
 152         // Object below the mouse may have changed : current glass gesture
 153         // must be searched again.
 154         this.glassGesture = null;
 155     }
 156 
 157     /*
 158      * AbstractModeController
 159      */
 160 
 161     @Override
 162     public void willResignActive(AbstractModeController nextModeController) {
 163         stopListeningToInputEvents();
 164 
 165         removeAllHandles();
 166         removePring();
 167         removeTring();
 168 
 169         assert contentPanelController.getHandleLayer().getChildren().isEmpty();
 170         assert contentPanelController.getPringLayer().getChildren().isEmpty();
 171         assert contentPanelController.getRudderLayer().getChildren().isEmpty();
 172     }
 173 
 174     @Override
 175     public void didBecomeActive(AbstractModeController previousModeController) {
 176         assert contentPanelController.getGlassLayer() != null;
 177         assert contentPanelController.getHandleLayer() != null;
 178         assert contentPanelController.getPringLayer() != null;
 179         assert contentPanelController.getRudderLayer() != null;
 180 
 181         editorSelectionDidChange();
 182         startListeningToInputEvents();
 183     }
 184 
 185     @Override
 186     public void editorSelectionDidChange() {
 187         updateParentRing();
 188         updateHandles();
 189         makeSelectionVisible();
 190     }
 191 
 192     @Override
 193     public void fxomDocumentDidChange(FXOMDocument oldDocument) {
 194         // Same logic as when the scene graph is changed
 195         fxomDocumentDidRefreshSceneGraph();
 196     }
 197 
 198     @Override
 199     public void fxomDocumentDidRefreshSceneGraph() {
 200         updateParentRing();
 201         updateHandles();
 202 
 203         // Object below the mouse may have changed : current glass gesture
 204         // must searched again.
 205         this.glassGesture = null;
 206     }
 207 
 208     @Override
 209     public void dropTargetDidChange() {
 210         updateTring();
 211     }
 212 
 213     /*
 214      * Private
 215      */
 216 
 217     private void makeSelectionVisible() {
 218 
 219         // Scrolls the content panel so that selected objects are visible.
 220         contentPanelController.scrollToSelection();
 221 
 222         // Walks trough the ancestor nodes of the first selected object and
 223         // makes sure that TabPane and Accordion are setup for displaying
 224         // this selected object.
 225         if (handles.isEmpty() == false) {
 226             contentPanelController.reveal(handles.get(0).getFxomObject());
 227         }
 228     }
 229 
 230     /*
 231      * Private (pring)
 232      */
 233     private void updateParentRing() {
 234         final AbstractPring<?> newPring;
 235 
 236         if (contentPanelController.isContentDisplayable()) {
 237             final Selection selection
 238                     = contentPanelController.getEditorController().getSelection();
 239             if ((pring == null) || (pring.getFxomObject() != selection.getAncestor())) {
 240                 if (selection.getAncestor() != null) {
 241                     newPring = makePring(selection.getAncestor());
 242                 } else {
 243                     newPring = null;
 244                 }
 245             } else {
 246                 switch(pring.getState()) {
 247                     default:
 248                     case CLEAN:
 249                         newPring = pring;
 250                         break;
 251                     case NEEDS_RECONCILE:
 252                         newPring = pring;
 253                         pring.reconcile();;
 254                         break;
 255                     case NEEDS_REPLACE:
 256                         newPring = makePring(pring.getFxomObject());
 257                         break;
 258                 }
 259             }
 260         } else {
 261             // Document content cannot be displayed in content panel
 262             newPring = null;
 263         }
 264 
 265         if (newPring != pring) {
 266             final Group pringLayer = contentPanelController.getPringLayer();
 267             if (pring != null) {
 268                 pringLayer.getChildren().remove(pring.getRootNode());
 269             }
 270             pring = newPring;
 271             if (pring != null) {
 272                 pringLayer.getChildren().add(pring.getRootNode());
 273             }
 274         } else {
 275             assert (pring == null) || pring.getState() == AbstractPring.State.CLEAN;
 276         }
 277     }
 278 
 279     private AbstractPring<?> makePring(FXOMObject fxomObject) {
 280         final AbstractDriver driver = contentPanelController.lookupDriver(fxomObject);
 281         final AbstractPring<?> result;
 282 
 283         if (driver != null) {
 284             result = driver.makePring(fxomObject);
 285             if (result != null) {
 286                 result.changeStroke(contentPanelController.getPringColor());
 287             }
 288         } else {
 289             result = null;
 290         }
 291 
 292         return result;
 293     }
 294 
 295 
 296     private void removePring() {
 297         if (pring != null) {
 298             final Group pringLayer = contentPanelController.getPringLayer();
 299             pringLayer.getChildren().remove(pring.getRootNode());
 300             pring = null;
 301         }
 302     }
 303 
 304     /*
 305      * Private (tring)
 306      */
 307     private void updateTring() {
 308         final DragController dragController
 309                 = contentPanelController.getEditorController().getDragController();
 310         final AbstractTring<?> newTring;
 311 
 312         if (dragController.isDropAccepted()
 313                 && contentPanelController.isContentDisplayable()) {
 314             final AbstractDropTarget dropTarget = dragController.getDropTarget();
 315             if ((tring instanceof GridPaneTring) && (dropTarget instanceof GridPaneDropTarget)) {
 316                 // Let's reuse the GridPaneTring (because it's costly)
 317                 newTring = tring;
 318                 updateTring((GridPaneTring) tring, (GridPaneDropTarget) dropTarget);
 319             } else {
 320                 newTring = makeTring(dragController.getDropTarget());
 321             }
 322         } else {
 323             newTring = null;
 324         }
 325 
 326         if (newTring != tring) {
 327             final Group rudderLayer = contentPanelController.getRudderLayer();
 328             if (tring != null) {
 329                 rudderLayer.getChildren().remove(tring.getRootNode());
 330             }
 331             tring = newTring;
 332             if (tring != null) {
 333                 rudderLayer.getChildren().add(tring.getRootNode());
 334             }
 335         } else {
 336             assert (tring == null) || tring.getState() == AbstractPring.State.CLEAN;
 337         }
 338     }
 339 
 340     private void updateTring(GridPaneTring tring, GridPaneDropTarget dropTarget) {
 341         assert tring != null;
 342         assert dropTarget != null;
 343 
 344         tring.setupWithDropTarget(dropTarget);
 345     }
 346 
 347     private AbstractTring<?> makeTring(AbstractDropTarget dropTarget) {
 348         final AbstractTring<?> result;
 349 
 350         if (dropTarget.getTargetObject() == null) {
 351             assert dropTarget instanceof RootDropTarget;
 352             result = null;
 353         } else {
 354             final AbstractDriver driver
 355                     = contentPanelController.lookupDriver(dropTarget.getTargetObject());
 356             if (driver != null) {
 357                 result = driver.makeTring(dropTarget);
 358                 if (result != null) {
 359                     result.changeStroke(contentPanelController.getPringColor());
 360                 }
 361             } else {
 362                 result = null;
 363             }
 364         }
 365 
 366         return result;
 367     }
 368 
 369     private void removeTring() {
 370         if (tring != null) {
 371             final Group rudderLayer = contentPanelController.getRudderLayer();
 372             rudderLayer.getChildren().remove(tring.getRootNode());
 373             tring = null;
 374         }
 375     }
 376 
 377     /*
 378      * Private (handles)
 379      */
 380 
 381     private void updateHandles() {
 382         final Selection selection = contentPanelController.getEditorController().getSelection();
 383         if (selection.getGroup() instanceof ObjectSelectionGroup) {
 384             updateHandles((ObjectSelectionGroup) selection.getGroup());
 385         } else if (selection.getGroup() instanceof GridSelectionGroup) {
 386             updateHandles((GridSelectionGroup) selection.getGroup());
 387         } else {
 388             assert selection.getGroup() == null
 389                     : "Implement updateHandles() for " + selection.getGroup();
 390             // Selection is empty : removes all handles
 391             removeAllHandles();
 392         }
 393 
 394         final boolean enabled = handles.size() == 1;
 395         for (AbstractHandles<?> h : handles) {
 396             h.setEnabled(enabled);
 397         }
 398     }
 399 
 400     private void updateHandles(ObjectSelectionGroup osg) {
 401         final List<AbstractHandles<?>> obsoleteHandles = new ArrayList<>();
 402         final List<FXOMObject> incomingObjects = new ArrayList<>();
 403 
 404         // Collects fxom objects from selection
 405         if (contentPanelController.isContentDisplayable()) {
 406             incomingObjects.addAll(osg.getItems());
 407         }
 408 
 409         // Collects obsolete handles
 410         for (AbstractHandles<?> h : handles) {
 411             if (incomingObjects.contains(h.getFxomObject())) {
 412                 // FXOM object associated to these handles is still selected
 413                 switch(h.getState()) {
 414                     case CLEAN:
 415                         incomingObjects.remove(h.getFxomObject());
 416                         break;
 417                     case NEEDS_RECONCILE:
 418                         // scene graph associated to h has changed but h is still compatible
 419                         h.reconcile();
 420                         incomingObjects.remove(h.getFxomObject());
 421                         break;
 422                     case NEEDS_REPLACE:
 423                         // h is no longer compatible with the new scene graph object
 424                         obsoleteHandles.add(h);
 425                         break;
 426                 }
 427                 // If h is grid pane handles reset the selected columns/rows
 428                 if (h instanceof GridPaneHandles) {
 429                     final GridPaneHandles gph = (GridPaneHandles) h;
 430                     gph.updateColumnRowSelection(null);
 431                 }
 432             } else {
 433                 // FXOM object associated to these handles is no longer selected
 434                 // => handles become obsolete
 435                 obsoleteHandles.add(h);
 436             }
 437         }
 438 
 439         // Let's create new handles for the incoming objects
 440         excludes.clear();
 441         final Group handleLayer = contentPanelController.getHandleLayer();
 442         for (FXOMObject incomingObject : incomingObjects) {
 443             final AbstractDriver driver = contentPanelController.lookupDriver(incomingObject);
 444             if (driver == null) {
 445                 // incomingObject cannot be managed by content panel (eg MenuItem)
 446                 excludes.add(incomingObject);
 447             } else {
 448                 final AbstractHandles<?> newHandles = driver.makeHandles(incomingObject);
 449                 handleLayer.getChildren().add(newHandles.getRootNode());
 450                 handles.add(newHandles);
 451             }
 452         }
 453 
 454         // Let's disconnect the obsolete handles
 455         for (AbstractHandles<?> h : obsoleteHandles) {
 456             handleLayer.getChildren().remove(h.getRootNode());
 457             handles.remove(h);
 458         }
 459     }
 460 
 461 
 462     private void updateHandles(GridSelectionGroup gsg) {
 463         final List<AbstractHandles<?>> obsoleteHandles = new ArrayList<>();
 464 
 465         // Collects obsolete handles
 466         if (contentPanelController.isContentDisplayable()) {
 467             for (AbstractHandles<?> h : handles) {
 468                 if (h.getFxomObject() == gsg.getParentObject()) {
 469                     assert h instanceof GridPaneHandles;
 470 
 471                     if (h.getState() == AbstractDecoration.State.NEEDS_RECONCILE) {
 472                         // scene graph associated to h has changed but h is still compatible
 473                         h.reconcile();
 474                     } else {
 475                         assert h.getState() == AbstractDecoration.State.CLEAN;
 476                     }
 477 
 478                     final GridPaneHandles gph = (GridPaneHandles) h;
 479                     gph.updateColumnRowSelection(gsg);
 480                 } else {
 481                     // FXOM object associated to these handles is no longer selected
 482                     // => handles become obsolete
 483                     obsoleteHandles.add(h);
 484                 }
 485             }
 486         } else {
 487             // Document content is not displayed (because its root is not a node)
 488             // => all handles are obsoletes
 489             obsoleteHandles.addAll(handles);
 490         }
 491 
 492         // Let's create new handles for the incoming objects
 493         excludes.clear();
 494         final Group handleLayer = contentPanelController.getHandleLayer();
 495         if (handles.size() == obsoleteHandles.size()) {
 496             // No handles for grid pane row/column selection : creates one.
 497             assert gsg.getParentObject().getSceneGraphObject() instanceof GridPane;
 498             final AbstractDriver driver = contentPanelController.lookupDriver(gsg.getParentObject());
 499             assert driver != null;
 500             final AbstractHandles<?> newHandles = driver.makeHandles(gsg.getParentObject());
 501             handleLayer.getChildren().add(newHandles.getRootNode());
 502             handles.add(newHandles);
 503             assert newHandles instanceof GridPaneHandles;
 504             final GridPaneHandles gridPaneHandles = (GridPaneHandles) newHandles;
 505             gridPaneHandles.updateColumnRowSelection(gsg);
 506         }
 507 
 508         // Let's disconnect the obsolete handles
 509         for (AbstractHandles<?> h : obsoleteHandles) {
 510             handleLayer.getChildren().remove(h.getRootNode());
 511             handles.remove(h);
 512         }
 513     }
 514 
 515     private void removeAllHandles() {
 516         final Group handleLayer = contentPanelController.getHandleLayer();
 517         for (AbstractHandles<?> h : new ArrayList<>(handles)) {
 518             handleLayer.getChildren().remove(h.getRootNode());
 519             handles.remove(h);
 520         }
 521     }
 522 
 523     /*
 524      * Private (event listeners)
 525      */
 526 
 527     private final EventHandler<MouseEvent> mouseEnteredGlassLayerListener
 528             = e -> mouseEnteredGlassLayer(e);
 529 
 530     private final EventHandler<MouseEvent> mouseExitedGlassLayerListener
 531             = e -> mouseExitedGlassLayer(e);
 532 
 533     private final EventHandler<MouseEvent> mouseMovedOnGlassLayerListener
 534             = e -> mouseMovedOnGlassLayer(e);
 535 
 536     private final EventHandler<MouseEvent> mousePressedOnGlassLayerListener
 537             = e -> mousePressedOnGlassLayer(e);
 538 
 539     private final EventHandler<KeyEvent> keyPressedOnGlassLayerListener
 540             = e -> keyPressedOnGlassLayer(e);
 541 
 542     private final EventHandler<ZoomEvent> zoomStartedOnGlassLayer
 543             = e -> zoomStartedOnGlassLayer(e);
 544 
 545     private final EventHandler<DragEvent> dragEnteredGlassLayerListener
 546             = e -> dragEnteredGlassLayer(e);
 547 
 548     private final EventHandler<MouseEvent> mousePressedOnHandleLayerListener
 549             = e -> mousePressedOnHandleLayer(e);
 550 
 551     private final EventHandler<MouseEvent> mousePressedOnPringLayerListener
 552             = e -> mousePressedOnPringLayer(e);
 553 
 554     private void startListeningToInputEvents() {
 555         final Node glassLayer = contentPanelController.getGlassLayer();
 556         assert glassLayer.getOnMouseEntered() == null;
 557         assert glassLayer.getOnMouseExited() == null;
 558         assert glassLayer.getOnMouseMoved() == null;
 559         assert glassLayer.getOnMousePressed() == null;
 560         assert glassLayer.getOnKeyPressed() == null;
 561         assert glassLayer.getOnZoomStarted() == null;
 562         assert glassLayer.getOnDragEntered() == null;
 563 
 564         glassLayer.setOnMouseEntered(mouseEnteredGlassLayerListener);
 565         glassLayer.setOnMouseExited(mouseExitedGlassLayerListener);
 566         glassLayer.setOnMouseMoved(mouseMovedOnGlassLayerListener);
 567         glassLayer.setOnMousePressed(mousePressedOnGlassLayerListener);
 568         glassLayer.setOnKeyPressed(keyPressedOnGlassLayerListener);
 569         glassLayer.setOnZoomStarted(zoomStartedOnGlassLayer);
 570         glassLayer.setOnDragEntered(dragEnteredGlassLayerListener);
 571 
 572         final Node handleLayer = contentPanelController.getHandleLayer();
 573         assert handleLayer.getOnMousePressed() == null;
 574         handleLayer.setOnMousePressed(mousePressedOnHandleLayerListener);
 575 
 576         final Node pringLayer = contentPanelController.getPringLayer();
 577         assert pringLayer.getOnMousePressed() == null;
 578         pringLayer.setOnMousePressed(mousePressedOnPringLayerListener);
 579     }
 580 
 581     private void stopListeningToInputEvents() {
 582 
 583         final Node glassLayer = contentPanelController.getGlassLayer();
 584         glassLayer.setOnMouseEntered(null);
 585         glassLayer.setOnMouseExited(null);
 586         glassLayer.setOnMouseMoved(null);
 587         glassLayer.setOnMousePressed(null);
 588         glassLayer.setOnKeyPressed(null);
 589         glassLayer.setOnZoomStarted(null);
 590         glassLayer.setOnDragEntered(null);
 591 
 592         final Node handleLayer = contentPanelController.getHandleLayer();
 593         handleLayer.setOnMousePressed(null);
 594 
 595         final Node pringLayer = contentPanelController.getPringLayer();
 596         pringLayer.setOnMousePressed(null);
 597     }
 598 
 599 
 600     /*
 601      * Private (event handlers)
 602      */
 603 
 604     private void mouseEnteredGlassLayer(MouseEvent e) {
 605         mouseMovedOnGlassLayer(e);
 606     }
 607 
 608     private void mouseExitedGlassLayer(MouseEvent e) {
 609         assert activeGesture == null : "activeGesture=" + activeGesture;
 610         glassGesture = null;
 611     }
 612 
 613     private void mouseMovedOnGlassLayer(MouseEvent e) {
 614         assert activeGesture == null : "activeGesture=" + activeGesture;
 615 
 616         /*
 617          *   1) hitObject == null
 618          *                  => mouse is over the workspace/background
 619          *                  => mouse press+drag should "select with marquee"
 620          *
 621          *   2) hitObject != null
 622          *
 623          *      2.1) hitObject == root object
 624          *
 625          *          2.1) hitObject is the selectionAncestor
 626          *                  => mouse is over the "parent ring object"
 627          *                  => mouse press+drag should "select with marquee"
 628          *
 629          *          2.2) hitObject is not the selectionAncestor
 630          *                  => mouse is over an object
 631          *                  => this object is inside or outside of the parent ring
 632          *                  => mouse press+drag should "select and move"
 633          *
 634          */
 635 
 636         final FXOMObject hitObject
 637                 = contentPanelController.pick(e.getSceneX(), e.getSceneY());
 638         final FXOMObject selectionAncestor
 639                 = contentPanelController.getEditorController().getSelection().getAncestor();
 640         if (hitObject == null) {
 641             // Case #1
 642             selectWithMarqueeGesture.setup(null, selectionAncestor);
 643             glassGesture = selectWithMarqueeGesture;
 644         } else if (hitObject == selectionAncestor) {
 645             // Case #2.1
 646             selectWithMarqueeGesture.setup(selectionAncestor, selectionAncestor);
 647             glassGesture = selectWithMarqueeGesture;
 648         } else {
 649             // Case #2.2
 650             selectAndMoveGesture.setHitObject(hitObject);
 651             selectAndMoveGesture.setHitSceneX(e.getSceneX());
 652             selectAndMoveGesture.setHitSceneY(e.getSceneY());
 653             glassGesture = selectAndMoveGesture;
 654         }
 655     }
 656 
 657     private void mousePressedOnGlassLayer(MouseEvent e) {
 658 
 659         // Make sure that glass layer has keyboard focus
 660         contentPanelController.getGlassLayer().requestFocus();
 661 
 662         /*
 663          * At that point, is expected that a "mouse entered" or "mouse moved"
 664          * event was received before and that this.glassGesture is setup.
 665          *
 666          * However this is no always the case. It may be null in two cases:
 667          * 1) on Linux, mouse entered/moved events are not always delivered
 668          *    before mouse pressed event (see DTL-5956).
 669          * 2) while the mouse is immobile, fxomDocumentDidRefreshSceneGraph()
 670          *    method may have been invoked and reset this.glassGesture.
 671          *
 672          * That is why we test this.glassGesture and manually invoke
 673          * mouseMovedOnGlassLayer() here.
 674          */
 675         if (glassGesture == null) {
 676             mouseMovedOnGlassLayer(e);
 677         }
 678 
 679         assert glassGesture != null;
 680         switch(e.getClickCount()) {
 681             case 1:
 682                 if (e.getButton() == MouseButton.SECONDARY) {
 683                     // Update the selection (see spec detailed in DTL-5640)
 684                     final FXOMObject hitObject;
 685                     if (glassGesture == selectAndMoveGesture) {
 686                         hitObject = selectAndMoveGesture.getHitObject();
 687                     } else {
 688                         assert glassGesture == selectWithMarqueeGesture;
 689                         hitObject = selectWithMarqueeGesture.getHitObject();
 690                     }
 691                     final Selection selection
 692                             = contentPanelController.getEditorController().getSelection();
 693                     if (hitObject != null && selection.isSelected(hitObject) == false) {
 694                         selection.select(hitObject);
 695                     }
 696                     final ContextMenuController contextMenuController
 697                             = contentPanelController.getEditorController().getContextMenuController();
 698                     // The context menu items depend on the selection so
 699                     // we need to rebuild it each time it is invoked.
 700                     contextMenuController.updateContextMenuItems();
 701                 } else {
 702                     activateGesture(glassGesture, e);
 703                 }
 704                 break;
 705             case 2:
 706                 mouseDoubleClickedOnGlassLayer(e);
 707                 break;
 708             default:
 709                 // We ignore triple clicks and upper...
 710                 break;
 711         }
 712         e.consume();
 713     }
 714 
 715     private void mouseDoubleClickedOnGlassLayer(MouseEvent e) {
 716         assert activeGesture == null;
 717         assert (glassGesture == selectAndMoveGesture)
 718                 || (glassGesture == selectWithMarqueeGesture);
 719 
 720         if (glassGesture == selectAndMoveGesture) {
 721             assert selectAndMoveGesture.getHitObject() instanceof FXOMInstance;
 722             final FXOMInstance hitObject
 723                     = (FXOMInstance) selectAndMoveGesture.getHitObject();
 724             final DesignHierarchyMask m
 725                     = new DesignHierarchyMask(hitObject);
 726             // Do not allow inline editing of the I18N value
 727             if (m.isResourceKey() == false) {
 728                 handleInlineEditing((FXOMInstance) selectAndMoveGesture.getHitObject());
 729             } else {
 730                 final MessageLog ml = contentPanelController.getEditorController().getMessageLog();
 731                 ml.logWarningMessage("log.warning.inline.edit.internationalized.strings");
 732             }
 733         }
 734     }
 735 
 736     private void handleInlineEditing(FXOMInstance hitObject) {
 737 
 738         assert hitObject != null;
 739         assert inlineEditedObject == null;
 740 
 741         final AbstractDriver driver
 742                 = contentPanelController.lookupDriver(hitObject);
 743         final Node inlineEditingBounds
 744                 = driver.getInlineEditorBounds(hitObject);
 745 
 746         if (inlineEditingBounds != null) {
 747             inlineEditedObject = hitObject;
 748 
 749             final InlineEditController inlineEditController =
 750                     contentPanelController.getEditorController().getInlineEditController();
 751             final DesignHierarchyMask m
 752                     = new DesignHierarchyMask(inlineEditedObject);
 753             final String text = m.getDescription();
 754             final InlineEditController.Type type;
 755             if (inlineEditingBounds instanceof TextArea
 756                     || DesignHierarchyMask.containsLineFeed(text)) {
 757                 type = InlineEditController.Type.TEXT_AREA;
 758             } else {
 759                 type = InlineEditController.Type.TEXT_FIELD;
 760             }
 761             final TextInputControl inlineEditor
 762                     = inlineEditController.createTextInputControl(
 763                             type, inlineEditingBounds, text);
 764             // CSS
 765             final ObservableList<String> styleSheets
 766                     = getContentPanelController().getPanelRoot().getStylesheets();
 767             inlineEditor.getStylesheets().addAll(styleSheets);
 768             inlineEditor.getStyleClass().add("theme-presets"); //NOI18N
 769             inlineEditor.getStyleClass().add(InlineEditController.INLINE_EDITOR);
 770             final Callback<String, Boolean> requestCommit
 771                     = value -> inlineEditingDidRequestCommit(value);
 772             final Callback<Void, Boolean> requestRevert
 773                     = value -> {
 774                 inlineEditingDidRequestRevert();
 775                 return true;
 776             };
 777             inlineEditController.startEditingSession(inlineEditor,
 778                     inlineEditingBounds, requestCommit, requestRevert);
 779         } else {
 780             System.out.println("Beep");
 781         }
 782 
 783         assert contentPanelController.getEditorController().isTextEditingSessionOnGoing()
 784                 || (inlineEditedObject == null);
 785     }
 786 
 787 
 788     private boolean inlineEditingDidRequestCommit(String newValue) {
 789         assert inlineEditedObject != null;
 790 
 791         final DesignHierarchyMask m
 792                 = new DesignHierarchyMask(inlineEditedObject);
 793         final PropertyName propertyName
 794                 = m.getPropertyNameForDescription();
 795         assert propertyName != null;
 796         final ValuePropertyMetadata vpm
 797                 = Metadata.getMetadata().queryValueProperty(inlineEditedObject, propertyName);
 798         final EditorController editorController
 799                 = contentPanelController.getEditorController();
 800         final ModifyObjectJob job
 801                 = new ModifyObjectJob(inlineEditedObject, vpm, newValue, editorController);
 802 
 803         if (job.isExecutable()) {
 804             editorController.getJobManager().push(job);
 805         }
 806 
 807         inlineEditedObject = null;
 808 
 809         return true;
 810     }
 811 
 812 
 813     private void inlineEditingDidRequestRevert() {
 814         assert inlineEditedObject != null;
 815         inlineEditedObject = null;
 816     }
 817 
 818     private void keyPressedOnGlassLayer(KeyEvent e) {
 819         assert activeGesture == null : "activeGesture=" + activeGesture;
 820         switch(e.getCode()) {
 821             case UP:
 822             case DOWN:
 823             case LEFT:
 824             case RIGHT:
 825                 if (RelocateSelectionJob.isSelectionMovable(contentPanelController.getEditorController())) {
 826                     activateGesture(new MoveWithKeyGesture(contentPanelController), e);
 827                 } else {
 828                     System.out.println("Selection is not movable");
 829                 }
 830                 e.consume();
 831                 break;
 832             case ENTER:
 833                 final Selection selection = contentPanelController.getEditorController().getSelection();
 834                 if (selection.getGroup() instanceof ObjectSelectionGroup) {
 835                     final ObjectSelectionGroup osg = (ObjectSelectionGroup) selection.getGroup();
 836                     if (osg.getItems().size() == 1) {
 837                         final DesignHierarchyMask mask = new DesignHierarchyMask(osg.getSortedItems().get(0));
 838                         final FXOMObject nodeFxomObject = mask.getClosestFxNode();
 839                         if (nodeFxomObject instanceof FXOMInstance) {
 840                             handleInlineEditing((FXOMInstance)nodeFxomObject);
 841                         }
 842                     }
 843                 }
 844                 break;
 845             default:
 846                 // We let other key events flow up in the scene graph
 847                 break;
 848         }
 849     }
 850 
 851     private void zoomStartedOnGlassLayer(ZoomEvent e) {
 852         activateGesture(zoomGesture, e);
 853         e.consume();
 854     }
 855 
 856     private void dragEnteredGlassLayer(DragEvent e) {
 857         activateGesture(new DragGesture(contentPanelController), e);
 858     }
 859 
 860 
 861     private void mousePressedOnHandleLayer(MouseEvent e) {
 862         assert e.getTarget() instanceof Node;
 863 
 864         if (e.getButton() == MouseButton.SECONDARY) {
 865             final ContextMenuController contextMenuController
 866                     = contentPanelController.getEditorController().getContextMenuController();
 867             // The context menu items depend on the selection so
 868             // we need to rebuild it each time it is invoked.
 869             contextMenuController.updateContextMenuItems();
 870         } else {
 871             final Node target = (Node) e.getTarget();
 872             Node hitNode = target;
 873             AbstractHandles<?> hitHandles = AbstractHandles.lookupHandles(hitNode);
 874             while ((hitHandles == null) && (hitNode.getParent() != null)) {
 875                 hitNode = hitNode.getParent();
 876                 hitHandles = AbstractHandles.lookupHandles(hitNode);
 877             }
 878 
 879             if (hitHandles != null) {
 880                 activateGesture(hitHandles.findEnabledGesture(hitNode), e);
 881             } else {
 882                 // Emergency code
 883                 assert false : "event target has no HANDLES property :" + target;
 884             }
 885         }
 886         e.consume();
 887     }
 888 
 889     private void mousePressedOnPringLayer(MouseEvent e) {
 890         assert e.getTarget() instanceof Node;
 891 
 892         final Node target = (Node) e.getTarget();
 893         Node hitNode = target;
 894         AbstractPring<?> hitPring = AbstractPring.lookupPring(target);
 895         while ((hitPring == null) && (hitNode.getParent() != null)) {
 896             hitNode = hitNode.getParent();
 897             hitPring = AbstractPring.lookupPring(hitNode);
 898         }
 899 
 900         if (hitPring != null) {
 901             activateGesture(hitPring.findGesture(hitNode), e);
 902         } else {
 903             // Emergency code
 904             assert false : "event target has no PRING property :" + target;
 905         }
 906         e.consume();
 907     }
 908 
 909     private void activateGesture(AbstractGesture gesture, InputEvent e) {
 910         assert activeGesture == null : "activeGesture=" + activeGesture;
 911         assert gesture != null;
 912 
 913         /*
 914          * Before activating the gesture, we check:
 915          *   - that there is a document attached to the editor controller
 916          *   - if a text session is on-going and can be completed cleanly.
 917          * If not, we do not activate the gesture.
 918          */
 919         final EditorController editorController
 920                 = contentPanelController.getEditorController();
 921         if (contentPanelController.isContentDisplayable() && editorController.canGetFxmlText()) {
 922 
 923             contentPanelController.beginInteraction();
 924 
 925             stopListeningToInputEvents();
 926             activeGesture = gesture;
 927             gesture.start(e, this);
 928 
 929             // Note that some gestures may terminates immediately.
 930             // So activeGesture may have switch back to null.
 931             assert (activeGesture == gesture) || (activeGesture == null);
 932         }
 933     }
 934 
 935 }