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 }