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.gesture.mouse; 33 34 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.ContentPanelController; 35 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.driver.AbstractDriver; 36 import com.oracle.javafx.scenebuilder.kit.editor.panel.content.driver.pring.AbstractPring; 37 import com.oracle.javafx.scenebuilder.kit.editor.selection.Selection; 38 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMDocument; 39 import com.oracle.javafx.scenebuilder.kit.fxom.FXOMObject; 40 import com.oracle.javafx.scenebuilder.kit.metadata.util.DesignHierarchyMask; 41 import com.oracle.javafx.scenebuilder.kit.metadata.util.DesignHierarchyMask.Accessory; 42 import java.util.Arrays; 43 import java.util.HashSet; 44 import java.util.List; 45 import java.util.Set; 46 import javafx.geometry.BoundingBox; 47 import javafx.geometry.Point2D; 48 import javafx.scene.Group; 49 import javafx.scene.input.KeyEvent; 50 import javafx.scene.shape.Rectangle; 51 52 /** 53 * 54 * 55 */ 56 public class SelectWithMarqueeGesture extends AbstractMouseGesture { 57 58 private FXOMObject hitObject; 59 private FXOMObject scopeObject; 60 private AbstractPring<?> scopeHilit; 61 private final Set<FXOMObject> candidates = new HashSet<>(); 62 private final Rectangle marqueeRect = new Rectangle(); 63 64 public SelectWithMarqueeGesture(ContentPanelController contentPanelController) { 65 super(contentPanelController); 66 } 67 68 public void setup(FXOMObject hitObject, FXOMObject scopeObject) { 69 assert (hitObject == null) || (hitObject.isDescendantOf(scopeObject) == false); 70 this.hitObject = hitObject; 71 this.scopeObject = scopeObject; 72 marqueeRect.getStyleClass().add("marquee"); 73 } 74 75 public FXOMObject getHitObject() { 76 return hitObject; 77 } 78 79 /* 80 * AbstractMouseGesture 81 */ 82 83 @Override 84 protected void mousePressed() { 85 } 86 87 @Override 88 protected void mouseDragStarted() { 89 contentPanelController.getEditorController().getSelection().clear(); 90 collectCandidates(); 91 showScopeHilit(); 92 showMarqueeRect(); 93 } 94 95 @Override 96 protected void mouseDragged() { 97 updateMarqueeRect(); 98 updateSelection(); 99 } 100 101 @Override 102 protected void mouseDragEnded() { 103 candidates.clear(); 104 hideScopeHilit(); 105 hideMarqueeRect(); 106 } 107 108 @Override 109 protected void mouseReleased() { 110 // Mouse has not been dragged 111 // If an object is below the mouse, then we select it. 112 // Else we unselect all. 113 if (isMouseDidDrag() == false) { 114 final Selection selection 115 = contentPanelController.getEditorController().getSelection(); 116 if (hitObject != null) { 117 selection.select(hitObject); 118 } else { 119 selection.clear(); 120 } 121 } 122 } 123 124 @Override 125 protected void keyEvent(KeyEvent e) { 126 } 127 128 @Override 129 protected void userDidCancel() { 130 } 131 132 133 /* 134 * Private 135 */ 136 137 private void showScopeHilit() { 138 if (scopeObject != null) { 139 final AbstractDriver driver 140 = contentPanelController.lookupDriver(scopeObject); 141 final Group rudderLayer 142 = contentPanelController.getRudderLayer(); 143 assert driver != null; 144 scopeHilit = driver.makePring(scopeObject); 145 scopeHilit.changeStroke(contentPanelController.getPringColor()); 146 rudderLayer.getChildren().add(scopeHilit.getRootNode()); 147 } 148 } 149 150 151 private void hideScopeHilit() { 152 if (scopeHilit != null) { 153 final Group rudderLayer = contentPanelController.getRudderLayer(); 154 assert rudderLayer.getChildren().contains(scopeHilit.getRootNode()); 155 rudderLayer.getChildren().remove(scopeHilit.getRootNode()); 156 scopeHilit = null; 157 } 158 } 159 160 private void showMarqueeRect() { 161 final Group rudderLayer = contentPanelController.getRudderLayer(); 162 rudderLayer.getChildren().add(marqueeRect); 163 updateMarqueeRect(); 164 } 165 166 private void updateMarqueeRect() { 167 final double xPressed = getMousePressedEvent().getSceneX(); 168 final double yPressed = getMousePressedEvent().getSceneY(); 169 final double xCurrent = getLastMouseEvent().getSceneX(); 170 final double yCurrent = getLastMouseEvent().getSceneY(); 171 172 final double xMin = Math.min(xPressed, xCurrent); 173 final double yMin = Math.min(yPressed, yCurrent); 174 final double xMax = Math.max(xPressed, xCurrent); 175 final double yMax = Math.max(yPressed, yCurrent); 176 177 final Group rudderLayer = contentPanelController.getRudderLayer(); 178 final Point2D p0 = rudderLayer.sceneToLocal(xMin, yMin, true /* rootScene */); 179 final Point2D p1 = rudderLayer.sceneToLocal(xMax, yMax, true /* rootScene */); 180 181 marqueeRect.setX(p0.getX()); 182 marqueeRect.setY(p0.getY()); 183 marqueeRect.setWidth(p1.getX() - p0.getX()); 184 marqueeRect.setHeight(p1.getY() - p0.getY()); 185 } 186 187 private void hideMarqueeRect() { 188 final Group rudderLayer = contentPanelController.getRudderLayer(); 189 rudderLayer.getChildren().remove(marqueeRect); 190 } 191 192 193 private void updateSelection() { 194 final double xPressed = getMousePressedEvent().getSceneX(); 195 final double yPressed = getMousePressedEvent().getSceneY(); 196 final double xCurrent = getLastMouseEvent().getSceneX(); 197 final double yCurrent = getLastMouseEvent().getSceneY(); 198 199 final double xMin = Math.min(xPressed, xCurrent); 200 final double yMin = Math.min(yPressed, yCurrent); 201 final double xMax = Math.max(xPressed, xCurrent); 202 final double yMax = Math.max(yPressed, yCurrent); 203 final BoundingBox marqueeBounds 204 = new BoundingBox(xMin, yMin, xMax - xMin, yMax - yMin); 205 206 final Set<FXOMObject> winners = new HashSet<>(); 207 for (FXOMObject candidate : candidates) { 208 final AbstractDriver driver 209 = contentPanelController.lookupDriver(candidate); 210 if ((driver != null) && driver.intersectsBounds(candidate, marqueeBounds)) { 211 winners.add(candidate); 212 } 213 } 214 215 final Selection selection 216 = contentPanelController.getEditorController().getSelection(); 217 selection.select(winners); 218 } 219 220 221 private void collectCandidates() { 222 if (scopeObject == null) { 223 // Only one candidate : the root object 224 final FXOMDocument fxomDocument 225 = contentPanelController.getEditorController().getFxomDocument(); 226 if ((fxomDocument != null) && (fxomDocument.getFxomRoot() != null)) { 227 candidates.add(fxomDocument.getFxomRoot()); 228 } 229 } else { 230 final DesignHierarchyMask m 231 = new DesignHierarchyMask(scopeObject); 232 if (m.isAcceptingSubComponent()) { 233 final int count = m.getSubComponentCount(); 234 for (int i = 0; i < count; i++) { 235 candidates.add(m.getSubComponentAtIndex(i)); 236 } 237 } else { 238 final List<Accessory> accessories = Arrays.asList( 239 Accessory.CONTENT, 240 Accessory.CENTER, 241 Accessory.BOTTOM, Accessory.TOP, 242 Accessory.LEFT, Accessory.RIGHT, 243 Accessory.XAXIS, Accessory.YAXIS); 244 for (Accessory accessory : accessories) { 245 if (m.isAcceptingAccessory(accessory)) { 246 final FXOMObject fxomObject = m.getAccessory(accessory); 247 if (fxomObject != null) { 248 candidates.add(fxomObject); 249 } 250 } 251 } 252 } 253 } 254 } 255 }