1 /* 2 * PlanController.java 2 juin 2006 3 * 4 * Sweet Home 3D, Copyright (c) 2006 Emmanuel PUYBARET / eTeks <info@eteks.com> 5 * 6 * This program is free software; you can redistribute it and/or modify 7 * it under the terms of the GNU General Public License as published by 8 * the Free Software Foundation; either version 2 of the License, or 9 * (at your option) any later version. 10 * 11 * This program is distributed in the hope that it will be useful, 12 * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 * GNU General Public License for more details. 15 * 16 * You should have received a copy of the GNU General Public License 17 * along with this program; if not, write to the Free Software 18 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 19 */ 20 package com.eteks.sweethome3d.viewcontroller; 21 22 import java.awt.Shape; 23 import java.awt.geom.AffineTransform; 24 import java.awt.geom.Area; 25 import java.awt.geom.GeneralPath; 26 import java.awt.geom.Line2D; 27 import java.awt.geom.PathIterator; 28 import java.awt.geom.Point2D; 29 import java.awt.geom.Rectangle2D; 30 import java.beans.PropertyChangeEvent; 31 import java.beans.PropertyChangeListener; 32 import java.beans.PropertyChangeSupport; 33 import java.util.ArrayList; 34 import java.util.Arrays; 35 import java.util.Collection; 36 import java.util.Collections; 37 import java.util.Comparator; 38 import java.util.Hashtable; 39 import java.util.List; 40 import java.util.Map; 41 import java.util.TreeMap; 42 43 import javax.swing.undo.AbstractUndoableEdit; 44 import javax.swing.undo.CannotRedoException; 45 import javax.swing.undo.CannotUndoException; 46 import javax.swing.undo.UndoableEdit; 47 import javax.swing.undo.UndoableEditSupport; 48 49 import com.eteks.sweethome3d.model.BackgroundImage; 50 import com.eteks.sweethome3d.model.Baseboard; 51 import com.eteks.sweethome3d.model.Camera; 52 import com.eteks.sweethome3d.model.CollectionEvent; 53 import com.eteks.sweethome3d.model.CollectionListener; 54 import com.eteks.sweethome3d.model.Compass; 55 import com.eteks.sweethome3d.model.DimensionLine; 56 import com.eteks.sweethome3d.model.Elevatable; 57 import com.eteks.sweethome3d.model.Home; 58 import com.eteks.sweethome3d.model.HomeDoorOrWindow; 59 import com.eteks.sweethome3d.model.HomeFurnitureGroup; 60 import com.eteks.sweethome3d.model.HomeLight; 61 import com.eteks.sweethome3d.model.HomePieceOfFurniture; 62 import com.eteks.sweethome3d.model.HomeTexture; 63 import com.eteks.sweethome3d.model.Label; 64 import com.eteks.sweethome3d.model.LengthUnit; 65 import com.eteks.sweethome3d.model.Level; 66 import com.eteks.sweethome3d.model.ObserverCamera; 67 import com.eteks.sweethome3d.model.Polyline; 68 import com.eteks.sweethome3d.model.Room; 69 import com.eteks.sweethome3d.model.Selectable; 70 import com.eteks.sweethome3d.model.SelectionEvent; 71 import com.eteks.sweethome3d.model.SelectionListener; 72 import com.eteks.sweethome3d.model.TextStyle; 73 import com.eteks.sweethome3d.model.UserPreferences; 74 import com.eteks.sweethome3d.model.Wall; 75 76 /** 77 * A MVC controller for the plan view. 78 * @author Emmanuel Puybaret 79 */ 80 public class PlanController extends FurnitureController implements Controller { 81 public enum Property {MODE, MODIFICATION_STATE, BASE_PLAN_MODIFICATION_STATE, SCALE} 82 83 /** 84 * Selectable modes in controller. 85 */ 86 public static class Mode { 87 // Don't qualify Mode as an enumeration to be able to extend Mode class 88 public static final Mode SELECTION = new Mode("SELECTION"); 89 public static final Mode PANNING = new Mode("PANNING"); 90 public static final Mode WALL_CREATION = new Mode("WALL_CREATION"); 91 public static final Mode ROOM_CREATION = new Mode("ROOM_CREATION"); 92 public static final Mode POLYLINE_CREATION = new Mode("POLYLINE_CREATION"); 93 public static final Mode DIMENSION_LINE_CREATION = new Mode("DIMENSION_LINE_CREATION"); 94 public static final Mode LABEL_CREATION = new Mode("LABEL_CREATION"); 95 96 private final String name; 97 Mode(String name)98 protected Mode(String name) { 99 this.name = name; 100 } 101 name()102 public final String name() { 103 return this.name; 104 } 105 106 @Override toString()107 public String toString() { 108 return this.name; 109 } 110 }; 111 112 /** 113 * Fields that can be edited in plan view. 114 */ 115 public static enum EditableProperty {X, Y, LENGTH, ANGLE, THICKNESS, OFFSET, ARC_EXTENT} 116 117 private static final String SCALE_VISUAL_PROPERTY = "com.eteks.sweethome3d.SweetHome3D.PlanScale"; 118 119 private static final int PIXEL_MARGIN = 4; 120 private static final int INDICATOR_PIXEL_MARGIN = 5; 121 private static final int WALL_ENDS_PIXEL_MARGIN = 2; 122 123 private final Home home; 124 private final UserPreferences preferences; 125 private final ViewFactory viewFactory; 126 private final ContentManager contentManager; 127 private final UndoableEditSupport undoSupport; 128 private final PropertyChangeSupport propertyChangeSupport; 129 private PlanView planView; 130 private SelectionListener selectionListener; 131 private PropertyChangeListener wallChangeListener; 132 private PropertyChangeListener furnitureSizeChangeListener; 133 // Possibles states 134 private final ControllerState selectionState; 135 private final ControllerState rectangleSelectionState; 136 private final ControllerState selectionMoveState; 137 private final ControllerState panningState; 138 private final ControllerState dragAndDropState; 139 private final ControllerState wallCreationState; 140 private final ControllerState wallDrawingState; 141 private final ControllerState wallResizeState; 142 private final ControllerState wallArcExtentState; 143 private final ControllerState pieceOfFurnitureRotationState; 144 private final ControllerState pieceOfFurniturePitchRotationState; 145 private final ControllerState pieceOfFurnitureRollRotationState; 146 private final ControllerState pieceOfFurnitureElevationState; 147 private final ControllerState pieceOfFurnitureHeightState; 148 private final ControllerState pieceOfFurnitureResizeState; 149 private final ControllerState lightPowerModificationState; 150 private final ControllerState pieceOfFurnitureNameOffsetState; 151 private final ControllerState pieceOfFurnitureNameRotationState; 152 private final ControllerState cameraYawRotationState; 153 private final ControllerState cameraPitchRotationState; 154 private final ControllerState cameraElevationState; 155 private final ControllerState dimensionLineCreationState; 156 private final ControllerState dimensionLineDrawingState; 157 private final ControllerState dimensionLineResizeState; 158 private final ControllerState dimensionLineOffsetState; 159 private final ControllerState roomCreationState; 160 private final ControllerState roomDrawingState; 161 private final ControllerState roomResizeState; 162 private final ControllerState roomAreaOffsetState; 163 private final ControllerState roomAreaRotationState; 164 private final ControllerState roomNameOffsetState; 165 private final ControllerState roomNameRotationState; 166 private final ControllerState polylineCreationState; 167 private final ControllerState polylineDrawingState; 168 private final ControllerState polylineResizeState; 169 private final ControllerState labelCreationState; 170 private final ControllerState labelRotationState; 171 private final ControllerState labelElevationState; 172 private final ControllerState compassRotationState; 173 private final ControllerState compassResizeState; 174 // Current state 175 private ControllerState state; 176 private ControllerState previousState; 177 // Mouse cursor position at last mouse press 178 private float xLastMousePress; 179 private float yLastMousePress; 180 private boolean shiftDownLastMousePress; 181 private boolean alignmentActivatedLastMousePress; 182 private boolean duplicationActivatedLastMousePress; 183 private boolean magnetismToggledLastMousePress; 184 private View.PointerType pointerTypeLastMousePress; 185 private float xLastMouseMove; 186 private float yLastMouseMove; 187 private Area wallsAreaCache; 188 private Area wallsIncludingBaseboardsAreaCache; 189 private Area insideWallsAreaCache; 190 private List<GeneralPath> roomPathsCache; 191 private Map<HomePieceOfFurniture, Area> furnitureSidesCache; 192 private List<Selectable> draggedItems; 193 194 195 196 /** 197 * Creates the controller of plan view. 198 * @param home the home plan edited by this controller and its view 199 * @param preferences the preferences of the application 200 * @param viewFactory a factory able to create the plan view managed by this controller 201 * @param contentManager a content manager used to import furniture 202 * @param undoSupport undo support to post changes on plan by this controller 203 */ PlanController(Home home, UserPreferences preferences, ViewFactory viewFactory, ContentManager contentManager, UndoableEditSupport undoSupport)204 public PlanController(Home home, 205 UserPreferences preferences, 206 ViewFactory viewFactory, 207 ContentManager contentManager, 208 UndoableEditSupport undoSupport) { 209 super(home, preferences, viewFactory, contentManager, undoSupport); 210 this.home = home; 211 this.preferences = preferences; 212 this.viewFactory = viewFactory; 213 this.contentManager = contentManager; 214 this.undoSupport = undoSupport; 215 this.propertyChangeSupport = new PropertyChangeSupport(this); 216 this.furnitureSidesCache = new Hashtable<HomePieceOfFurniture, Area>(); 217 // Initialize states 218 this.selectionState = new SelectionState(); 219 this.selectionMoveState = new SelectionMoveState(); 220 this.rectangleSelectionState = new RectangleSelectionState(); 221 this.panningState = new PanningState(); 222 this.dragAndDropState = new DragAndDropState(); 223 this.wallCreationState = new WallCreationState(); 224 this.wallDrawingState = new WallDrawingState(); 225 this.wallResizeState = new WallResizeState(); 226 this.wallArcExtentState = new WallArcExtentState(); 227 this.pieceOfFurnitureRotationState = new PieceOfFurnitureRotationState(); 228 this.pieceOfFurniturePitchRotationState = new PieceOfFurniturePitchRotationState(); 229 this.pieceOfFurnitureRollRotationState = new PieceOfFurnitureRollRotationState(); 230 this.pieceOfFurnitureElevationState = new PieceOfFurnitureElevationState(); 231 this.pieceOfFurnitureHeightState = new PieceOfFurnitureHeightState(); 232 this.pieceOfFurnitureResizeState = new PieceOfFurnitureResizeState(); 233 this.lightPowerModificationState = new LightPowerModificationState(); 234 this.pieceOfFurnitureNameOffsetState = new PieceOfFurnitureNameOffsetState(); 235 this.pieceOfFurnitureNameRotationState = new PieceOfFurnitureNameRotationState(); 236 this.cameraYawRotationState = new CameraYawRotationState(); 237 this.cameraPitchRotationState = new CameraPitchRotationState(); 238 this.cameraElevationState = new CameraElevationState(); 239 this.dimensionLineCreationState = new DimensionLineCreationState(); 240 this.dimensionLineDrawingState = new DimensionLineDrawingState(); 241 this.dimensionLineResizeState = new DimensionLineResizeState(); 242 this.dimensionLineOffsetState = new DimensionLineOffsetState(); 243 this.roomCreationState = new RoomCreationState(); 244 this.roomDrawingState = new RoomDrawingState(); 245 this.roomResizeState = new RoomResizeState(); 246 this.roomAreaOffsetState = new RoomAreaOffsetState(); 247 this.roomAreaRotationState = new RoomAreaRotationState(); 248 this.roomNameOffsetState = new RoomNameOffsetState(); 249 this.roomNameRotationState = new RoomNameRotationState(); 250 this.polylineCreationState = new PolylineCreationState(); 251 this.polylineDrawingState = new PolylineDrawingState(); 252 this.polylineResizeState = new PolylineResizeState(); 253 this.labelCreationState = new LabelCreationState(); 254 this.labelRotationState = new LabelRotationState(); 255 this.labelElevationState = new LabelElevationState(); 256 this.compassRotationState = new CompassRotationState(); 257 this.compassResizeState = new CompassResizeState(); 258 // Set default state to selectionState 259 setState(this.selectionState); 260 261 addModelListeners(); 262 263 // Restore previous scale if it exists 264 Number scale = home.getNumericProperty(SCALE_VISUAL_PROPERTY); 265 if (scale != null) { 266 setScale(scale.floatValue()); 267 } 268 } 269 270 /** 271 * Returns the view associated with this controller. 272 */ getView()273 public PlanView getView() { 274 // Create view lazily only once it's needed 275 if (this.planView == null) { 276 this.planView = this.viewFactory.createPlanView(this.home, this.preferences, this); 277 } 278 return this.planView; 279 } 280 281 /** 282 * Changes current state of controller. 283 */ setState(ControllerState state)284 protected void setState(ControllerState state) { 285 Mode oldMode = null; 286 boolean oldModificationState = false; 287 boolean oldBasePlanModificationState = false; 288 if (this.state != null) { 289 oldMode = this.state.getMode(); 290 oldModificationState = this.state.isModificationState(); 291 oldBasePlanModificationState = this.state.isBasePlanModificationState(); 292 this.state.exit(); 293 } 294 295 this.previousState = this.state; 296 this.state = state; 297 this.state.enter(); 298 299 if (oldMode != state.getMode()) { 300 this.propertyChangeSupport.firePropertyChange(Property.MODE.name(), 301 oldMode, state.getMode()); 302 } 303 if (oldModificationState != state.isModificationState()) { 304 this.propertyChangeSupport.firePropertyChange(Property.MODIFICATION_STATE.name(), 305 oldModificationState, !oldModificationState); 306 } 307 if (oldBasePlanModificationState != state.isBasePlanModificationState()) { 308 this.propertyChangeSupport.firePropertyChange(Property.BASE_PLAN_MODIFICATION_STATE.name(), 309 oldBasePlanModificationState, !oldBasePlanModificationState); 310 } 311 } 312 313 /** 314 * Adds the property change <code>listener</code> in parameter to this controller. 315 */ addPropertyChangeListener(Property property, PropertyChangeListener listener)316 public void addPropertyChangeListener(Property property, PropertyChangeListener listener) { 317 this.propertyChangeSupport.addPropertyChangeListener(property.name(), listener); 318 } 319 320 /** 321 * Removes the property change <code>listener</code> in parameter from this controller. 322 */ removePropertyChangeListener(Property property, PropertyChangeListener listener)323 public void removePropertyChangeListener(Property property, PropertyChangeListener listener) { 324 this.propertyChangeSupport.removePropertyChangeListener(property.name(), listener); 325 } 326 327 /** 328 * Returns the active mode of this controller. 329 */ getMode()330 public Mode getMode() { 331 return this.state.getMode(); 332 } 333 334 /** 335 * Sets the active mode of this controller and fires a <code>PropertyChangeEvent</code>. 336 */ setMode(Mode mode)337 public void setMode(Mode mode) { 338 Mode oldMode = this.state.getMode(); 339 if (mode != oldMode) { 340 this.state.setMode(mode); 341 this.propertyChangeSupport.firePropertyChange(Property.MODE.name(), oldMode, mode); 342 } 343 } 344 345 /** 346 * Returns <code>true</code> if the interactions in the current mode may modify 347 * the state of a home. 348 */ isModificationState()349 public boolean isModificationState() { 350 return this.state.isModificationState(); 351 } 352 353 /** 354 * Returns <code>true</code> if the interactions in the current mode may modify 355 * the base plan of a home. 356 */ isBasePlanModificationState()357 public boolean isBasePlanModificationState() { 358 return this.state.isBasePlanModificationState(); 359 } 360 361 /** 362 * Deletes the selection in home. 363 */ 364 @Override deleteSelection()365 public void deleteSelection() { 366 this.state.deleteSelection(); 367 } 368 369 /** 370 * Escapes of current action. 371 */ escape()372 public void escape() { 373 this.state.escape(); 374 } 375 376 /** 377 * Moves the selection of (<code>dx</code>,<code>dy</code>) in home. 378 */ moveSelection(float dx, float dy)379 public void moveSelection(float dx, float dy) { 380 this.state.moveSelection(dx, dy); 381 } 382 383 /** 384 * Toggles temporary magnetism feature of user preferences. 385 * @param magnetismToggled if <code>true</code> then magnetism feature is toggled. 386 */ toggleMagnetism(boolean magnetismToggled)387 public void toggleMagnetism(boolean magnetismToggled) { 388 this.state.toggleMagnetism(magnetismToggled); 389 } 390 391 /** 392 * Activates or deactivates alignment feature. 393 * @param alignmentActivated if <code>true</code> then alignment is active. 394 * @since 4.0 395 */ setAlignmentActivated(boolean alignmentActivated)396 public void setAlignmentActivated(boolean alignmentActivated) { 397 this.state.setAlignmentActivated(alignmentActivated); 398 } 399 400 /** 401 * Activates or deactivates duplication feature. 402 * @param duplicationActivated if <code>true</code> then duplication is active. 403 */ setDuplicationActivated(boolean duplicationActivated)404 public void setDuplicationActivated(boolean duplicationActivated) { 405 this.state.setDuplicationActivated(duplicationActivated); 406 } 407 408 /** 409 * Activates or deactivates edition. 410 * @param editionActivated if <code>true</code> then edition is active 411 */ setEditionActivated(boolean editionActivated)412 public void setEditionActivated(boolean editionActivated) { 413 this.state.setEditionActivated(editionActivated); 414 } 415 416 /** 417 * Updates an editable property with the entered <code>value</code>. 418 */ updateEditableProperty(EditableProperty editableProperty, Object value)419 public void updateEditableProperty(EditableProperty editableProperty, Object value) { 420 this.state.updateEditableProperty(editableProperty, value); 421 } 422 423 /** 424 * Processes a mouse button pressed event. 425 */ pressMouse(float x, float y, int clickCount, boolean shiftDown, boolean duplicationActivated)426 public void pressMouse(float x, float y, int clickCount, 427 boolean shiftDown, boolean duplicationActivated) { 428 pressMouse(x, y, clickCount, shiftDown, shiftDown, duplicationActivated, shiftDown); 429 } 430 431 /** 432 * Processes a mouse button pressed event. 433 * @since 4.0 434 */ pressMouse(float x, float y, int clickCount, boolean shiftDown, boolean alignmentActivated, boolean duplicationActivated, boolean magnetismToggled)435 public void pressMouse(float x, float y, int clickCount, boolean shiftDown, 436 boolean alignmentActivated, boolean duplicationActivated, boolean magnetismToggled) { 437 pressMouse(x, y, clickCount, shiftDown, alignmentActivated, duplicationActivated, magnetismToggled, null); 438 } 439 440 /** 441 * Processes a mouse button pressed event. 442 * @since 6.4 443 */ pressMouse(float x, float y, int clickCount, boolean shiftDown, boolean alignmentActivated, boolean duplicationActivated, boolean magnetismToggled, View.PointerType pointerType)444 public void pressMouse(float x, float y, int clickCount, boolean shiftDown, 445 boolean alignmentActivated, boolean duplicationActivated, boolean magnetismToggled, 446 View.PointerType pointerType) { 447 // Store the last coordinates of a mouse press 448 this.xLastMousePress = x; 449 this.yLastMousePress = y; 450 this.xLastMouseMove = x; 451 this.yLastMouseMove = y; 452 this.shiftDownLastMousePress = shiftDown; 453 this.alignmentActivatedLastMousePress = alignmentActivated; 454 this.duplicationActivatedLastMousePress = duplicationActivated; 455 this.pointerTypeLastMousePress = pointerType; 456 this.magnetismToggledLastMousePress = magnetismToggled; 457 this.state.pressMouse(x, y, clickCount, shiftDown, duplicationActivated); 458 } 459 460 /** 461 * Processes a mouse button released event. 462 */ releaseMouse(float x, float y)463 public void releaseMouse(float x, float y) { 464 this.state.releaseMouse(x, y); 465 } 466 467 /** 468 * Processes a mouse button moved event. 469 */ moveMouse(float x, float y)470 public void moveMouse(float x, float y) { 471 // Store the last coordinates of a mouse move 472 this.xLastMouseMove = x; 473 this.yLastMouseMove = y; 474 this.state.moveMouse(x, y); 475 } 476 477 /** 478 * Processes a zoom event. 479 */ zoom(float factor)480 public void zoom(float factor) { 481 this.state.zoom(factor); 482 } 483 484 /** 485 * Returns the selection state. 486 */ getSelectionState()487 protected ControllerState getSelectionState() { 488 return this.selectionState; 489 } 490 491 /** 492 * Returns the selection move state. 493 */ getSelectionMoveState()494 protected ControllerState getSelectionMoveState() { 495 return this.selectionMoveState; 496 } 497 498 /** 499 * Returns the rectangle selection state. 500 */ getRectangleSelectionState()501 protected ControllerState getRectangleSelectionState() { 502 return this.rectangleSelectionState; 503 } 504 505 /** 506 * Returns the panning state. 507 */ getPanningState()508 protected ControllerState getPanningState() { 509 return this.panningState; 510 } 511 512 /** 513 * Returns the drag and drop state. 514 */ getDragAndDropState()515 protected ControllerState getDragAndDropState() { 516 return this.dragAndDropState; 517 } 518 519 /** 520 * Returns the wall creation state. 521 */ getWallCreationState()522 protected ControllerState getWallCreationState() { 523 return this.wallCreationState; 524 } 525 526 /** 527 * Returns the wall drawing state. 528 */ getWallDrawingState()529 protected ControllerState getWallDrawingState() { 530 return this.wallDrawingState; 531 } 532 533 /** 534 * Returns the wall resize state. 535 */ getWallResizeState()536 protected ControllerState getWallResizeState() { 537 return this.wallResizeState; 538 } 539 540 /** 541 * Returns the wall arc extent state. 542 * @since 6.0 543 */ getWallArcExtentState()544 protected ControllerState getWallArcExtentState() { 545 return this.wallArcExtentState; 546 } 547 548 /** 549 * Returns the piece rotation state. 550 */ getPieceOfFurnitureRotationState()551 protected ControllerState getPieceOfFurnitureRotationState() { 552 return this.pieceOfFurnitureRotationState; 553 } 554 555 /** 556 * Returns the piece pitch rotation state. 557 */ getPieceOfFurniturePitchRotationState()558 protected ControllerState getPieceOfFurniturePitchRotationState() { 559 return this.pieceOfFurniturePitchRotationState; 560 } 561 562 /** 563 * Returns the piece roll rotation state. 564 */ getPieceOfFurnitureRollRotationState()565 protected ControllerState getPieceOfFurnitureRollRotationState() { 566 return this.pieceOfFurnitureRollRotationState; 567 } 568 569 /** 570 * Returns the piece elevation state. 571 */ getPieceOfFurnitureElevationState()572 protected ControllerState getPieceOfFurnitureElevationState() { 573 return this.pieceOfFurnitureElevationState; 574 } 575 576 /** 577 * Returns the piece height state. 578 */ getPieceOfFurnitureHeightState()579 protected ControllerState getPieceOfFurnitureHeightState() { 580 return this.pieceOfFurnitureHeightState; 581 } 582 583 /** 584 * Returns the piece resize state. 585 */ getPieceOfFurnitureResizeState()586 protected ControllerState getPieceOfFurnitureResizeState() { 587 return this.pieceOfFurnitureResizeState; 588 } 589 590 /** 591 * Returns the light power modification state. 592 */ getLightPowerModificationState()593 protected ControllerState getLightPowerModificationState() { 594 return this.lightPowerModificationState; 595 } 596 597 /** 598 * Returns the piece name offset state. 599 */ getPieceOfFurnitureNameOffsetState()600 protected ControllerState getPieceOfFurnitureNameOffsetState() { 601 return this.pieceOfFurnitureNameOffsetState; 602 } 603 604 /** 605 * Returns the piece name rotation state. 606 * @since 3.6 607 */ getPieceOfFurnitureNameRotationState()608 protected ControllerState getPieceOfFurnitureNameRotationState() { 609 return this.pieceOfFurnitureNameRotationState; 610 } 611 612 /** 613 * Returns the camera yaw rotation state. 614 */ getCameraYawRotationState()615 protected ControllerState getCameraYawRotationState() { 616 return this.cameraYawRotationState; 617 } 618 619 /** 620 * Returns the camera pitch rotation state. 621 */ getCameraPitchRotationState()622 protected ControllerState getCameraPitchRotationState() { 623 return this.cameraPitchRotationState; 624 } 625 626 /** 627 * Returns the camera elevation state. 628 */ getCameraElevationState()629 protected ControllerState getCameraElevationState() { 630 return this.cameraElevationState; 631 } 632 633 /** 634 * Returns the dimension line creation state. 635 */ getDimensionLineCreationState()636 protected ControllerState getDimensionLineCreationState() { 637 return this.dimensionLineCreationState; 638 } 639 640 /** 641 * Returns the dimension line drawing state. 642 */ getDimensionLineDrawingState()643 protected ControllerState getDimensionLineDrawingState() { 644 return this.dimensionLineDrawingState; 645 } 646 647 /** 648 * Returns the dimension line resize state. 649 */ getDimensionLineResizeState()650 protected ControllerState getDimensionLineResizeState() { 651 return this.dimensionLineResizeState; 652 } 653 654 /** 655 * Returns the dimension line offset state. 656 */ getDimensionLineOffsetState()657 protected ControllerState getDimensionLineOffsetState() { 658 return this.dimensionLineOffsetState; 659 } 660 661 /** 662 * Returns the room creation state. 663 */ getRoomCreationState()664 protected ControllerState getRoomCreationState() { 665 return this.roomCreationState; 666 } 667 668 /** 669 * Returns the room drawing state. 670 */ getRoomDrawingState()671 protected ControllerState getRoomDrawingState() { 672 return this.roomDrawingState; 673 } 674 675 /** 676 * Returns the room resize state. 677 */ getRoomResizeState()678 protected ControllerState getRoomResizeState() { 679 return this.roomResizeState; 680 } 681 682 /** 683 * Returns the room area offset state. 684 */ getRoomAreaOffsetState()685 protected ControllerState getRoomAreaOffsetState() { 686 return this.roomAreaOffsetState; 687 } 688 689 /** 690 * Returns the room area rotation state. 691 * @since 3.6 692 */ getRoomAreaRotationState()693 protected ControllerState getRoomAreaRotationState() { 694 return this.roomAreaRotationState; 695 } 696 697 /** 698 * Returns the room name offset state. 699 */ getRoomNameOffsetState()700 protected ControllerState getRoomNameOffsetState() { 701 return this.roomNameOffsetState; 702 } 703 704 /** 705 * Returns the room name rotation state. 706 * @since 3.6 707 */ getRoomNameRotationState()708 protected ControllerState getRoomNameRotationState() { 709 return this.roomNameRotationState; 710 } 711 712 /** 713 * Returns the polyline creation state. 714 * @since 5.0 715 */ getPolylineCreationState()716 protected ControllerState getPolylineCreationState() { 717 return this.polylineCreationState; 718 } 719 720 /** 721 * Returns the polyline drawing state. 722 * @since 5.0 723 */ getPolylineDrawingState()724 protected ControllerState getPolylineDrawingState() { 725 return this.polylineDrawingState; 726 } 727 728 /** 729 * Returns the polyline resize state. 730 * @since 5.0 731 */ getPolylineResizeState()732 protected ControllerState getPolylineResizeState() { 733 return this.polylineResizeState; 734 } 735 736 /** 737 * Returns the label creation state. 738 */ getLabelCreationState()739 protected ControllerState getLabelCreationState() { 740 return this.labelCreationState; 741 } 742 743 /** 744 * Returns the label rotation state. 745 * @since 3.6 746 */ getLabelRotationState()747 protected ControllerState getLabelRotationState() { 748 return this.labelRotationState; 749 } 750 751 /** 752 * Returns the label elevation state. 753 * @since 5.0 754 */ getLabelElevationState()755 protected ControllerState getLabelElevationState() { 756 return this.labelElevationState; 757 } 758 759 /** 760 * Returns the compass rotation state. 761 */ getCompassRotationState()762 protected ControllerState getCompassRotationState() { 763 return this.compassRotationState; 764 } 765 766 /** 767 * Returns the compass resize state. 768 */ getCompassResizeState()769 protected ControllerState getCompassResizeState() { 770 return this.compassResizeState; 771 } 772 773 /** 774 * Returns the abscissa of mouse position at last mouse press. 775 */ getXLastMousePress()776 protected float getXLastMousePress() { 777 return this.xLastMousePress; 778 } 779 780 /** 781 * Returns the ordinate of mouse position at last mouse press. 782 */ getYLastMousePress()783 protected float getYLastMousePress() { 784 return this.yLastMousePress; 785 } 786 787 /** 788 * Returns <code>true</code> if shift key was down at last mouse press. 789 */ wasShiftDownLastMousePress()790 protected boolean wasShiftDownLastMousePress() { 791 return this.shiftDownLastMousePress; 792 } 793 794 /** 795 * Returns <code>true</code> if magnetism was toggled at last mouse press. 796 * @since 4.0 797 */ wasMagnetismToggledLastMousePress()798 protected boolean wasMagnetismToggledLastMousePress() { 799 return this.magnetismToggledLastMousePress; 800 } 801 802 /** 803 * Returns <code>true</code> if alignment was activated at last mouse press. 804 * @since 4.0 805 */ wasAlignmentActivatedLastMousePress()806 protected boolean wasAlignmentActivatedLastMousePress() { 807 return this.alignmentActivatedLastMousePress; 808 } 809 810 /** 811 * Returns <code>true</code> if duplication was activated at last mouse press. 812 */ wasDuplicationActivatedLastMousePress()813 protected boolean wasDuplicationActivatedLastMousePress() { 814 return this.duplicationActivatedLastMousePress; 815 } 816 817 /** 818 * Returns the pointer type used at the last mouse press. 819 * @since 6.4 820 */ getPointerTypeLastMousePress()821 protected View.PointerType getPointerTypeLastMousePress() { 822 return this.pointerTypeLastMousePress; 823 } 824 825 /** 826 * Returns the abscissa of mouse position at last mouse move. 827 */ getXLastMouseMove()828 protected float getXLastMouseMove() { 829 return this.xLastMouseMove; 830 } 831 832 /** 833 * Returns the ordinate of mouse position at last mouse move. 834 */ getYLastMouseMove()835 protected float getYLastMouseMove() { 836 return this.yLastMouseMove; 837 } 838 839 /** 840 * Controls the modification of selected walls. 841 */ modifySelectedWalls()842 public void modifySelectedWalls() { 843 if (!Home.getWallsSubList(this.home.getSelectedItems()).isEmpty()) { 844 new WallController(this.home, this.preferences, this.viewFactory, 845 this.contentManager, this.undoSupport).displayView(getView()); 846 } 847 } 848 849 /** 850 * Locks home base plan. 851 */ lockBasePlan()852 public void lockBasePlan() { 853 if (!this.home.isBasePlanLocked()) { 854 final boolean allLevelsSelection = this.home.isAllLevelsSelection(); 855 List<Selectable> selection = this.home.getSelectedItems(); 856 final Selectable [] oldSelection = 857 selection.toArray(new Selectable [selection.size()]); 858 859 List<Selectable> newSelection = getItemsNotPartOfBasePlan(selection); 860 final Selectable [] newSelectedItems = 861 newSelection.toArray(new Selectable [newSelection.size()]); 862 863 this.home.setBasePlanLocked(true); 864 selectItems(newSelection, allLevelsSelection); 865 this.undoSupport.postEdit(new LockingUndoableEdit(this, this.home, this.preferences, 866 oldSelection, allLevelsSelection, newSelectedItems)); 867 } 868 } 869 870 /** 871 * Undoable edit for plan locking. 872 */ 873 private static class LockingUndoableEdit extends LocalizedUndoableEdit { 874 private final PlanController controller; 875 private final Home home; 876 private final Selectable [] oldSelection; 877 private final boolean allLevelsSelection; 878 private final Selectable [] newSelectedItems; 879 LockingUndoableEdit(PlanController controller, Home home, UserPreferences preferences, Selectable [] oldSelection, boolean allLevelsSelection, Selectable [] newSelectedItems)880 public LockingUndoableEdit(PlanController controller, Home home, UserPreferences preferences, 881 Selectable [] oldSelection, boolean allLevelsSelection, 882 Selectable [] newSelectedItems) { 883 super(preferences, PlanController.class, "undoLockBasePlan"); 884 this.controller = controller; 885 this.home = home; 886 this.oldSelection = oldSelection; 887 this.allLevelsSelection = allLevelsSelection; 888 this.newSelectedItems = newSelectedItems; 889 } 890 891 @Override undo()892 public void undo() throws CannotUndoException { 893 super.undo(); 894 this.home.setBasePlanLocked(false); 895 this.controller.selectAndShowItems(Arrays.asList(this.oldSelection), this.allLevelsSelection); 896 } 897 898 @Override redo()899 public void redo() throws CannotRedoException { 900 super.redo(); 901 this.home.setBasePlanLocked(true); 902 this.controller.selectAndShowItems(Arrays.asList(this.newSelectedItems), this.allLevelsSelection); 903 } 904 } 905 906 /** 907 * Returns <code>true</code> it the given <code>item</code> belongs 908 * to the base plan. 909 */ isItemPartOfBasePlan(Selectable item)910 protected boolean isItemPartOfBasePlan(Selectable item) { 911 if (item instanceof HomePieceOfFurniture) { 912 return isPieceOfFurniturePartOfBasePlan((HomePieceOfFurniture)item); 913 } else { 914 return !(item instanceof ObserverCamera); 915 } 916 } 917 918 /** 919 * Returns the items among the given list that are not part of the base plan. 920 */ getItemsNotPartOfBasePlan(List<? extends Selectable> items)921 private List<Selectable> getItemsNotPartOfBasePlan(List<? extends Selectable> items) { 922 List<Selectable> itemsNotPartOfBasePlan = new ArrayList<Selectable>(); 923 for (Selectable item : items) { 924 if (!isItemPartOfBasePlan(item)) { 925 itemsNotPartOfBasePlan.add(item); 926 } 927 } 928 return itemsNotPartOfBasePlan; 929 } 930 931 /** 932 * Unlocks home base plan. 933 */ unlockBasePlan()934 public void unlockBasePlan() { 935 if (this.home.isBasePlanLocked()) { 936 final boolean allLevelsSelection = this.home.isAllLevelsSelection(); 937 List<Selectable> selection = this.home.getSelectedItems(); 938 final Selectable [] selectedItems = 939 selection.toArray(new Selectable [selection.size()]); 940 941 this.home.setBasePlanLocked(false); 942 this.home.setAllLevelsSelection(false); 943 this.undoSupport.postEdit(new UnlockingUndoableEdit(this, this.home, this.preferences, 944 selectedItems, allLevelsSelection)); 945 } 946 } 947 948 /** 949 * Undoable edit for plan unlocking. 950 */ 951 private static class UnlockingUndoableEdit extends LocalizedUndoableEdit { 952 private final PlanController controller; 953 private final Home home; 954 private final Selectable [] selectedItems; 955 private final boolean allLevelsSelection; 956 UnlockingUndoableEdit(PlanController controller, Home home, UserPreferences preferences, Selectable [] selectedItems, boolean allLevelsSelection)957 public UnlockingUndoableEdit(PlanController controller, Home home, UserPreferences preferences, 958 Selectable [] selectedItems, boolean allLevelsSelection) { 959 super(preferences, PlanController.class, "undoUnlockBasePlan"); 960 this.controller = controller; 961 this.home = home; 962 this.selectedItems = selectedItems; 963 this.allLevelsSelection = allLevelsSelection; 964 } 965 966 @Override undo()967 public void undo() throws CannotUndoException { 968 super.undo(); 969 this.home.setBasePlanLocked(true); 970 this.controller.selectAndShowItems(Arrays.asList(this.selectedItems), this.allLevelsSelection); 971 } 972 973 @Override redo()974 public void redo() throws CannotRedoException { 975 super.redo(); 976 this.home.setBasePlanLocked(false); 977 this.controller.selectAndShowItems(Arrays.asList(this.selectedItems), false); 978 } 979 } 980 981 /** 982 * Returns <code>true</code> if the given <code>item</code> may be moved 983 * in the plan. Default implementation returns <code>true</code>. 984 */ isItemMovable(Selectable item)985 protected boolean isItemMovable(Selectable item) { 986 if (item instanceof HomePieceOfFurniture) { 987 return isPieceOfFurnitureMovable((HomePieceOfFurniture)item); 988 } else { 989 return true; 990 } 991 } 992 993 /** 994 * Returns <code>true</code> if the given <code>item</code> may be resized. 995 * Default implementation returns <code>false</code> if the given <code>item</code> 996 * is a non resizable piece of furniture. 997 */ isItemResizable(Selectable item)998 protected boolean isItemResizable(Selectable item) { 999 if (item instanceof HomePieceOfFurniture) { 1000 return ((HomePieceOfFurniture)item).isResizable(); 1001 } else { 1002 return true; 1003 } 1004 } 1005 1006 /** 1007 * Returns <code>true</code> if the given <code>item</code> may be deleted. 1008 * Default implementation returns <code>true</code> except if the given <code>item</code> 1009 * is a camera or a compass or if the given <code>item</code> isn't a 1010 * {@linkplain #isPieceOfFurnitureDeletable(HomePieceOfFurniture) deletable piece of furniture}. 1011 */ isItemDeletable(Selectable item)1012 protected boolean isItemDeletable(Selectable item) { 1013 if (item instanceof HomePieceOfFurniture) { 1014 return isPieceOfFurnitureDeletable((HomePieceOfFurniture)item); 1015 } else { 1016 return !(item instanceof Compass || item instanceof Camera); 1017 } 1018 } 1019 1020 /** 1021 * Flips horizontally selected objects. 1022 * @since 6.0 1023 */ flipHorizontally()1024 public void flipHorizontally() { 1025 flipSelectedItems(true); 1026 } 1027 1028 /** 1029 * Flips vertically selected objects. 1030 * @since 6.0 1031 */ flipVertically()1032 public void flipVertically() { 1033 flipSelectedItems(false); 1034 } 1035 flipSelectedItems(boolean horizontally)1036 private void flipSelectedItems(boolean horizontally) { 1037 List<Selectable> selectedItems = this.home.getSelectedItems(); 1038 if (!selectedItems.isEmpty()) { 1039 Selectable [] flippedItems = selectedItems.toArray(new Selectable [selectedItems.size()]); 1040 // Compute text base offsets to avoid using plan view during undo / redo 1041 float [][] itemTextBaseOffsets = new float [flippedItems.length][]; 1042 for (int i = 0; i < itemTextBaseOffsets.length; i++) { 1043 if (flippedItems [i] instanceof HomeFurnitureGroup) { 1044 HomeFurnitureGroup group = (HomeFurnitureGroup)flippedItems [i]; 1045 List<HomePieceOfFurniture> furniture = group.getAllFurniture(); 1046 itemTextBaseOffsets [i] = new float [furniture.size() + 1]; 1047 itemTextBaseOffsets [i][0] = getTextBaseOffset(group.getName(), group.getNameStyle(), group.getClass()); 1048 for (int j = 0; j < furniture.size(); j++) { 1049 HomePieceOfFurniture piece = furniture.get(j); 1050 itemTextBaseOffsets [i][j + 1] = getTextBaseOffset(piece.getName(), piece.getNameStyle(), piece.getClass()); 1051 } 1052 } else if (flippedItems [i] instanceof HomePieceOfFurniture) { 1053 HomePieceOfFurniture piece = (HomePieceOfFurniture)flippedItems [i]; 1054 itemTextBaseOffsets [i] = new float [] {getTextBaseOffset(piece.getName(), piece.getNameStyle(), piece.getClass())}; 1055 } else if (flippedItems [i] instanceof Room) { 1056 Room room = (Room)flippedItems [i]; 1057 itemTextBaseOffsets [i] = new float [] { 1058 getTextBaseOffset(room.getName(), room.getNameStyle(), room.getClass()), 1059 getTextBaseOffset(this.preferences.getLengthUnit().getAreaFormatWithUnit().format(room.getArea()), room.getAreaStyle(), room.getClass())}; 1060 } 1061 } 1062 doFlipItems(flippedItems, itemTextBaseOffsets, horizontally); 1063 selectAndShowItems(selectedItems, this.home.isAllLevelsSelection()); 1064 this.undoSupport.postEdit(new FlippingUndoableEdit(this, this.preferences, this.home.isAllLevelsSelection(), 1065 selectedItems.toArray(new Selectable [selectedItems.size()]), itemTextBaseOffsets, horizontally)); 1066 } 1067 } 1068 1069 /** 1070 * Undoable edit for flipped items. 1071 */ 1072 private static class FlippingUndoableEdit extends LocalizedUndoableEdit { 1073 private final PlanController controller; 1074 private final boolean allLevelsSelection; 1075 private final Selectable [] items; 1076 private final float [][] itemTextBaseOffsets; 1077 private final boolean horizontalFlip; 1078 FlippingUndoableEdit(PlanController controller, UserPreferences preferences, boolean allLevelsSelection, Selectable [] items, float [][] itemTextBaseOffsets, boolean horizontalFlip)1079 public FlippingUndoableEdit(PlanController controller, UserPreferences preferences, 1080 boolean allLevelsSelection, Selectable [] items, 1081 float [][] itemTextBaseOffsets, boolean horizontalFlip) { 1082 super(preferences, PlanController.class, "undoFlipName"); 1083 this.controller = controller; 1084 this.allLevelsSelection = allLevelsSelection; 1085 this.items = items; 1086 this.itemTextBaseOffsets = itemTextBaseOffsets; 1087 this.horizontalFlip = horizontalFlip; 1088 } 1089 1090 @Override undo()1091 public void undo() throws CannotUndoException { 1092 super.undo(); 1093 this.controller.doFlipItems(this.items, this.itemTextBaseOffsets, this.horizontalFlip); 1094 this.controller.selectAndShowItems(Arrays.asList(this.items), this.allLevelsSelection); 1095 } 1096 1097 @Override redo()1098 public void redo() throws CannotRedoException { 1099 super.redo(); 1100 this.controller.doFlipItems(this.items, this.itemTextBaseOffsets, this.horizontalFlip); 1101 this.controller.selectAndShowItems(Arrays.asList(this.items), this.allLevelsSelection); 1102 } 1103 } 1104 1105 /** 1106 * Flips the <code>items</code>. 1107 */ doFlipItems(Selectable [] items, float [][] itemTextBaseOffsets, boolean horizontalFlip)1108 private void doFlipItems(Selectable [] items, float [][] itemTextBaseOffsets, boolean horizontalFlip) { 1109 float minX = Float.MAX_VALUE; 1110 float minY = Float.MAX_VALUE; 1111 float maxX = -Float.MAX_VALUE; 1112 float maxY = -Float.MAX_VALUE; 1113 for (Selectable item : items) { 1114 if (!(item instanceof ObserverCamera)) { 1115 for (float [] point : item.getPoints()) { 1116 minX = Math.min(minX, point [0]); 1117 minY = Math.min(minY, point [1]); 1118 maxX = Math.max(maxX, point [0]); 1119 maxY = Math.max(maxY, point [1]); 1120 } 1121 } 1122 } 1123 float symmetryX = (minX + maxX) / 2; 1124 float symmetryY = (minY + maxY) / 2; 1125 List<Selectable> flippedItems = Arrays.asList(items); 1126 for (int i = 0; i < items.length; i++) { 1127 flipItem(items [i], itemTextBaseOffsets [i], 0, 1128 horizontalFlip ? symmetryX : symmetryY, horizontalFlip, flippedItems); 1129 } 1130 } 1131 1132 /** 1133 * Flips the given <code>item</code> with the given axis coordinate. 1134 * @param item the item to flip 1135 * @param itemTextBaseOffsets base offset for the texts of the item 1136 * @param offsetIndex index to get the first text base offset of item 1137 * @param axisCoordinate the coordinate of the symmetry axis 1138 * @param horizontalFlip if <code>true</code> the item should be flipped horizontally otherwise vertically 1139 * @param flippedItems list of all the items that must be flipped 1140 * @since 6.0 1141 */ flipItem(Selectable item, float [] itemTextBaseOffsets, int offsetIndex, float axisCoordinate, boolean horizontalFlip, List<Selectable> flippedItems)1142 protected void flipItem(Selectable item, float [] itemTextBaseOffsets, int offsetIndex, 1143 float axisCoordinate, boolean horizontalFlip, List<Selectable> flippedItems) { 1144 if (item instanceof HomePieceOfFurniture) { 1145 HomePieceOfFurniture piece = (HomePieceOfFurniture)item; 1146 if (horizontalFlip) { 1147 piece.setX(axisCoordinate * 2 - piece.getX()); 1148 piece.setAngle(-piece.getAngle()); 1149 flipPieceOfFurnitureName(piece, itemTextBaseOffsets [0], horizontalFlip); 1150 } else { 1151 piece.setY(axisCoordinate * 2 - piece.getY()); 1152 piece.setAngle((float)Math.PI - piece.getAngle()); 1153 flipPieceOfFurnitureName(piece, itemTextBaseOffsets [0], horizontalFlip); 1154 } 1155 if (piece.isHorizontallyRotatable()) { 1156 piece.setRoll(-piece.getRoll()); 1157 } 1158 if (piece.isResizable()) { 1159 piece.setModelMirrored(!piece.isModelMirrored()); 1160 } 1161 1162 if (item instanceof HomeFurnitureGroup) { 1163 List<HomePieceOfFurniture> furniture = ((HomeFurnitureGroup)item).getAllFurniture(); 1164 for (int i = 0; i < furniture.size(); i++) { 1165 flipPieceOfFurnitureName(furniture.get(i), itemTextBaseOffsets [i + 1], horizontalFlip); 1166 } 1167 } 1168 } else if (item instanceof Wall) { 1169 Wall wall = (Wall)item; 1170 if (horizontalFlip) { 1171 wall.setXStart(axisCoordinate * 2 - wall.getXStart()); 1172 Wall wallAtStart = wall.getWallAtStart(); 1173 if (wallAtStart != null && !flippedItems.contains(wallAtStart)) { 1174 if (wallAtStart.getWallAtStart() == wall) { 1175 wallAtStart.setXStart(axisCoordinate * 2 - wallAtStart.getXStart()); 1176 } else { 1177 wallAtStart.setXEnd(axisCoordinate * 2 - wallAtStart.getXEnd()); 1178 } 1179 } 1180 wall.setXEnd(axisCoordinate * 2 - wall.getXEnd()); 1181 Wall wallAtEnd = wall.getWallAtEnd(); 1182 if (wallAtEnd != null && !flippedItems.contains(wallAtEnd)) { 1183 if (wallAtEnd.getWallAtStart() == wall) { 1184 wallAtEnd.setXStart(axisCoordinate * 2 - wallAtEnd.getXStart()); 1185 } else { 1186 wallAtEnd.setXEnd(axisCoordinate * 2 - wallAtEnd.getXEnd()); 1187 } 1188 } 1189 } else { 1190 wall.setYStart(axisCoordinate * 2 - wall.getYStart()); 1191 Wall wallAtStart = wall.getWallAtStart(); 1192 if (wallAtStart != null && !flippedItems.contains(wallAtStart)) { 1193 if (wallAtStart.getWallAtStart() == wall) { 1194 wallAtStart.setYStart(axisCoordinate * 2 - wallAtStart.getYStart()); 1195 } else { 1196 wallAtStart.setYEnd(axisCoordinate * 2 - wallAtStart.getYEnd()); 1197 } 1198 } 1199 wall.setYEnd(axisCoordinate * 2 - wall.getYEnd()); 1200 Wall wallAtEnd = wall.getWallAtEnd(); 1201 if (wallAtEnd != null && !flippedItems.contains(wallAtEnd)) { 1202 if (wallAtEnd.getWallAtStart() == wall) { 1203 wallAtEnd.setYStart(axisCoordinate * 2 - wallAtEnd.getYStart()); 1204 } else { 1205 wallAtEnd.setYEnd(axisCoordinate * 2 - wallAtEnd.getYEnd()); 1206 } 1207 } 1208 } 1209 Float arcExtent = wall.getArcExtent(); 1210 if (arcExtent != null) { 1211 wall.setArcExtent(-arcExtent); 1212 } 1213 reverseWallSidesStyle(wall); 1214 } else if (item instanceof Room) { 1215 Room room = (Room)item; 1216 float [][] points = room.getPoints(); 1217 for (float [] point : points) { 1218 if (horizontalFlip) { 1219 point [0] = axisCoordinate * 2 - point [0]; 1220 } else { 1221 point [1] = axisCoordinate * 2 - point [1]; 1222 } 1223 } 1224 room.setPoints(points); 1225 TextStyle nameStyle = room.getNameStyle(); 1226 TextStyle areaStyle = room.getAreaStyle(); 1227 if (horizontalFlip) { 1228 room.setNameXOffset(-room.getNameXOffset()); 1229 room.setAreaXOffset(-room.getAreaXOffset()); 1230 if (nameStyle != null) { 1231 if (nameStyle.getAlignment() == TextStyle.Alignment.LEFT) { 1232 room.setNameStyle(nameStyle.deriveStyle(TextStyle.Alignment.RIGHT)); 1233 } else if (nameStyle.getAlignment() == TextStyle.Alignment.RIGHT) { 1234 room.setNameStyle(nameStyle.deriveStyle(TextStyle.Alignment.LEFT)); 1235 } 1236 } 1237 if (areaStyle != null) { 1238 if (areaStyle.getAlignment() == TextStyle.Alignment.LEFT) { 1239 room.setAreaStyle(areaStyle.deriveStyle(TextStyle.Alignment.RIGHT)); 1240 } else if (areaStyle.getAlignment() == TextStyle.Alignment.RIGHT) { 1241 room.setAreaStyle(areaStyle.deriveStyle(TextStyle.Alignment.LEFT)); 1242 } 1243 } 1244 } else { 1245 room.setNameYOffset(-room.getNameYOffset()); 1246 // Take into account font size 1247 float baseOffset = itemTextBaseOffsets [0]; 1248 room.setNameXOffset(room.getNameXOffset() - baseOffset * (float)Math.sin(room.getNameAngle())); 1249 room.setNameYOffset(room.getNameYOffset() - baseOffset * (float)Math.cos(room.getNameAngle())); 1250 1251 room.setAreaYOffset(-room.getAreaYOffset()); 1252 baseOffset = itemTextBaseOffsets [1]; 1253 room.setAreaXOffset(room.getAreaXOffset() - baseOffset * (float)Math.sin(room.getAreaAngle())); 1254 room.setAreaYOffset(room.getAreaYOffset() - baseOffset * (float)Math.cos(room.getAreaAngle())); 1255 } 1256 room.setNameAngle(-room.getNameAngle()); 1257 room.setAreaAngle(-room.getAreaAngle()); 1258 } else if (item instanceof Polyline) { 1259 Polyline polyline = (Polyline)item; 1260 float [][] points = polyline.getPoints(); 1261 for (float [] point : points) { 1262 if (horizontalFlip) { 1263 point [0] = axisCoordinate * 2 - point [0]; 1264 } else { 1265 point [1] = axisCoordinate * 2 - point [1]; 1266 } 1267 } 1268 polyline.setPoints(points); 1269 } else if (item instanceof DimensionLine) { 1270 DimensionLine dimensionLine = (DimensionLine)item; 1271 if (horizontalFlip) { 1272 // Reverse also ends to keep same text orientation 1273 float xStart = dimensionLine.getXStart(); 1274 dimensionLine.setXStart(axisCoordinate * 2 - dimensionLine.getXEnd()); 1275 dimensionLine.setXEnd(axisCoordinate * 2 - xStart); 1276 float yStart = dimensionLine.getYStart(); 1277 dimensionLine.setYStart(dimensionLine.getYEnd()); 1278 dimensionLine.setYEnd(yStart); 1279 } else { 1280 dimensionLine.setYStart(axisCoordinate * 2 - dimensionLine.getYStart()); 1281 dimensionLine.setYEnd(axisCoordinate * 2 - dimensionLine.getYEnd()); 1282 dimensionLine.setOffset(-dimensionLine.getOffset()); 1283 } 1284 } else if (item instanceof Label) { 1285 Label label = (Label)item; 1286 if (horizontalFlip) { 1287 label.setX(axisCoordinate * 2 - label.getX()); 1288 label.setAngle(-label.getAngle()); 1289 } else { 1290 label.setY(axisCoordinate * 2 - label.getY()); 1291 if (label.getPitch() != null) { 1292 label.setAngle((float)Math.PI - label.getAngle()); 1293 } else { 1294 label.setAngle(-label.getAngle()); 1295 } 1296 } 1297 TextStyle style = label.getStyle(); 1298 if (style != null) { 1299 if (style.getAlignment() == TextStyle.Alignment.LEFT) { 1300 label.setStyle(style.deriveStyle(TextStyle.Alignment.RIGHT)); 1301 } else if (style.getAlignment() == TextStyle.Alignment.RIGHT) { 1302 label.setStyle(style.deriveStyle(TextStyle.Alignment.LEFT)); 1303 } 1304 } 1305 } else if (item instanceof Compass) { 1306 Compass compass = (Compass)item; 1307 if (horizontalFlip) { 1308 compass.setX(axisCoordinate * 2 - compass.getX()); 1309 compass.setNorthDirection(-compass.getNorthDirection()); 1310 } else { 1311 compass.setY(axisCoordinate * 2 - compass.getY()); 1312 compass.setNorthDirection((float)Math.PI - compass.getNorthDirection()); 1313 } 1314 } 1315 } 1316 1317 /** 1318 * Flips the name of the given <code>piece</code>. 1319 */ flipPieceOfFurnitureName(HomePieceOfFurniture piece, float nameBaseOffset, boolean horizontalFlip)1320 private static void flipPieceOfFurnitureName(HomePieceOfFurniture piece, float nameBaseOffset, boolean horizontalFlip) { 1321 if (horizontalFlip) { 1322 piece.setNameXOffset(-piece.getNameXOffset()); 1323 TextStyle nameStyle = piece.getNameStyle(); 1324 if (nameStyle != null) { 1325 if (nameStyle.getAlignment() == TextStyle.Alignment.LEFT) { 1326 piece.setNameStyle(nameStyle.deriveStyle(TextStyle.Alignment.RIGHT)); 1327 } else if (nameStyle.getAlignment() == TextStyle.Alignment.RIGHT) { 1328 piece.setNameStyle(nameStyle.deriveStyle(TextStyle.Alignment.LEFT)); 1329 } 1330 } 1331 } else { 1332 piece.setNameYOffset(-piece.getNameYOffset()); 1333 if (piece.getNameXOffset() != 0 || piece.getNameYOffset() != 0) { 1334 // Take into account font size 1335 piece.setNameXOffset(piece.getNameXOffset() - nameBaseOffset * (float)Math.sin(piece.getNameAngle())); 1336 piece.setNameYOffset(piece.getNameYOffset() - nameBaseOffset * (float)Math.cos(piece.getNameAngle())); 1337 } 1338 piece.setNameAngle(-piece.getNameAngle()); 1339 } 1340 } 1341 1342 /** 1343 * Returns the offset between the vertical middle of the text and its base. 1344 */ getTextBaseOffset(String text, TextStyle textStyle, Class<? extends Selectable> itemClass)1345 private float getTextBaseOffset(String text, TextStyle textStyle, Class<? extends Selectable> itemClass) { 1346 if (textStyle == null) { 1347 textStyle = this.preferences.getDefaultTextStyle(itemClass); 1348 } 1349 float [][] textBounds = getView().getTextBounds(text != null ? text : "Ag", textStyle, 0, 0, 0); 1350 return (textBounds [textBounds.length - 1][1] + textBounds [0][1]) / 2; 1351 } 1352 1353 /** 1354 * Controls how selected walls are joined. 1355 * @since 5.5 1356 */ joinSelectedWalls()1357 public void joinSelectedWalls() { 1358 List<Selectable> selectedItems = this.home.getSelectedItems(); 1359 List<Wall> selectedWalls = Home.getWallsSubList(selectedItems); 1360 final Wall [] walls = {null, null}; 1361 for (Wall wall : selectedWalls) { 1362 if ((wall.getArcExtent() == null 1363 || wall.getArcExtent() == 0f) 1364 && (wall.getWallAtStart() == null 1365 || wall.getWallAtEnd() == null)) { 1366 if (walls [0] == null) { 1367 walls [0] = wall; 1368 } else { 1369 walls [1] = wall; 1370 break; 1371 } 1372 } 1373 } 1374 if (walls [1] == null) { 1375 Collections.sort(selectedWalls, new Comparator<Wall>() { 1376 public int compare(Wall wall1, Wall wall2) { 1377 float[] intersection1 = computeIntersection(wall1.getXStart(), wall1.getYStart(), wall1.getXEnd(), wall1.getYEnd(), 1378 walls [0].getXStart(), walls [0].getYStart(), walls [0].getXEnd(), walls [0].getYEnd()); 1379 float[] intersection2 = computeIntersection(wall2.getXStart(), wall2.getYStart(), wall2.getXEnd(), wall2.getYEnd(), 1380 walls [0].getXStart(), walls [0].getYStart(), walls [0].getXEnd(), walls [0].getYEnd()); 1381 double closestPoint1 = Math.min(Point2D.distanceSq(walls [0].getXStart(), walls [0].getYStart(), intersection1 [0], intersection1 [1]), 1382 Point2D.distanceSq(walls [0].getXEnd(), walls [0].getYEnd(), intersection1 [0], intersection1 [1])); 1383 double closestPoint2 = Math.min(Point2D.distanceSq(walls [0].getXStart(), walls [0].getYStart(), intersection2 [0], intersection2 [1]), 1384 Point2D.distanceSq(walls [0].getXEnd(), walls [0].getYEnd(), intersection2 [0], intersection2 [1])); 1385 return Double.compare(closestPoint1, closestPoint2); 1386 } 1387 }); 1388 if (walls [0] != selectedWalls.get(1)) { 1389 walls [1] = selectedWalls.get(1); 1390 } 1391 } 1392 if (walls [1] != null) { 1393 // Check parallelism 1 deg close 1394 double firstWallAngle = Math.atan2(walls [0].getYEnd() - walls [0].getYStart(), 1395 walls [0].getXEnd() - walls [0].getXStart()); 1396 double secondWallAngle = Math.atan2(walls [1].getYEnd() - walls [1].getYStart(), 1397 walls [1].getXEnd() - walls [1].getXStart()); 1398 double wallsAngle = Math.abs(firstWallAngle - secondWallAngle) % Math.PI; 1399 boolean parallel = wallsAngle <= Math.PI / 360 || (Math.PI - wallsAngle) <= Math.PI / 360; 1400 float[] joinPoint = null; 1401 if (!parallel) { 1402 joinPoint = computeIntersection(walls [0].getXStart(), walls [0].getYStart(), walls [0].getXEnd(), walls [0].getYEnd(), 1403 walls [1].getXStart(), walls [1].getYStart(), walls [1].getXEnd(), walls [1].getYEnd()); 1404 } else if (Line2D.ptLineDistSq(walls [1].getXStart(), walls [1].getYStart(), walls [1].getXEnd(), walls [1].getYEnd(), walls [0].getXStart(), walls [0].getYStart()) < 1E-2 1405 && Line2D.ptLineDistSq(walls [1].getXStart(), walls [1].getYStart(), walls [1].getXEnd(), walls [1].getYEnd(), walls [0].getXEnd(), walls [0].getYEnd()) < 1E-2) { 1406 // Search join point for walls in the same row 1407 if (walls [1].getWallAtStart() == null 1408 ^ walls [1].getWallAtEnd() == null) { 1409 // If second wall has only one free end, join the first wall to this free end 1410 if (walls [1].getWallAtStart() == null) { 1411 joinPoint = new float [] {walls [1].getXStart(), walls [1].getYStart()}; 1412 } else { 1413 joinPoint = new float [] {walls [1].getXEnd(), walls [1].getYEnd()}; 1414 } 1415 } else if (walls [1].getWallAtStart() == null 1416 && walls [1].getWallAtEnd() == null) { 1417 double wallStartDistanceToSegment = Line2D.ptSegDistSq(walls [1].getXStart(), walls [1].getYStart(), walls [1].getXEnd(), walls [1].getYEnd(), walls [0].getXStart(), walls [0].getYStart()); 1418 double wallEndDistanceToSegment = Line2D.ptSegDistSq(walls [1].getXStart(), walls [1].getYStart(), walls [1].getXEnd(), walls [1].getYEnd(), walls [0].getXEnd(), walls [0].getYEnd()); 1419 if (wallStartDistanceToSegment > 1E-2 1420 && wallEndDistanceToSegment > 1E-2) { 1421 // If walls don't overlap, connect first wall to the closest point 1422 if (walls [0].getWallAtEnd() != null 1423 || walls [0].getWallAtStart() == null 1424 && wallStartDistanceToSegment <= wallEndDistanceToSegment) { 1425 if (Point2D.distanceSq(walls [1].getXStart(), walls [1].getYStart(), walls [0].getXStart(), walls [0].getYStart()) 1426 < Point2D.distanceSq(walls [1].getXEnd(), walls [1].getYEnd(), walls [0].getXStart(), walls [0].getYStart())) { 1427 joinPoint = new float [] {walls [1].getXStart(), walls [1].getYStart()}; 1428 } else { 1429 joinPoint = new float [] {walls [1].getXEnd(), walls [1].getYEnd()}; 1430 } 1431 } else { 1432 if (Point2D.distanceSq(walls [1].getXStart(), walls [1].getYStart(), walls [0].getXEnd(), walls [0].getYEnd()) 1433 < Point2D.distanceSq(walls [1].getXEnd(), walls [1].getYEnd(), walls [0].getXEnd(), walls [0].getYEnd())) { 1434 joinPoint = new float [] {walls [1].getXStart(), walls [1].getYStart()}; 1435 } else { 1436 joinPoint = new float [] {walls [1].getXEnd(), walls [1].getYEnd()}; 1437 } 1438 } 1439 } 1440 } 1441 } 1442 if (joinPoint != null) { 1443 JoinedWall [] joinedWalls = JoinedWall.getJoinedWalls(Arrays.asList(walls [0], walls [1])); 1444 doJoinWalls(joinedWalls, joinPoint); 1445 this.undoSupport.postEdit(new WallsJoiningUndoableEdit(this, this.preferences, 1446 selectedItems.toArray(new Selectable [selectedItems.size()]), 1447 home.isAllLevelsSelection(), joinedWalls, joinPoint)); 1448 } 1449 } 1450 } 1451 1452 /** 1453 * Undoable edit for joining walls. 1454 */ 1455 private static class WallsJoiningUndoableEdit extends LocalizedUndoableEdit { 1456 private PlanController controller; 1457 private final Selectable [] oldSelection; 1458 private final boolean allLevelsSelection; 1459 private final JoinedWall [] joinedWalls; 1460 private final float [] joinPoint; 1461 WallsJoiningUndoableEdit(PlanController controller, UserPreferences preferences, Selectable [] oldSelection, boolean allLevelsSelection, JoinedWall [] joinedWalls, float [] joinPoint)1462 public WallsJoiningUndoableEdit(PlanController controller, UserPreferences preferences, 1463 Selectable [] oldSelection, boolean allLevelsSelection, 1464 JoinedWall [] joinedWalls, float [] joinPoint) { 1465 super(preferences, PlanController.class, "undoJoinWallsName"); 1466 this.controller = controller; 1467 this.oldSelection = oldSelection; 1468 this.allLevelsSelection = allLevelsSelection; 1469 this.joinedWalls = joinedWalls; 1470 this.joinPoint = joinPoint; 1471 } 1472 1473 @Override undo()1474 public void undo() throws CannotUndoException { 1475 super.undo(); 1476 for (JoinedWall joinedWall : this.joinedWalls) { 1477 Wall wall = joinedWall.getWall(); 1478 wall.setWallAtStart(joinedWall.getWallAtStart()); 1479 if (joinedWall.getWallAtStart() != null) { 1480 if (joinedWall.isJoinedAtEndOfWallAtStart()) { 1481 joinedWall.getWallAtStart().setWallAtEnd(wall); 1482 } else { 1483 joinedWall.getWallAtStart().setWallAtStart(wall); 1484 } 1485 } 1486 wall.setWallAtEnd(joinedWall.getWallAtEnd()); 1487 if (joinedWall.getWallAtEnd() != null) { 1488 if (joinedWall.isJoinedAtStartOfWallAtEnd()) { 1489 joinedWall.getWallAtEnd().setWallAtStart(wall); 1490 } else { 1491 joinedWall.getWallAtEnd().setWallAtEnd(wall); 1492 } 1493 } 1494 wall.setXStart(joinedWall.getXStart()); 1495 wall.setYStart(joinedWall.getYStart()); 1496 wall.setXEnd(joinedWall.getXEnd()); 1497 wall.setYEnd(joinedWall.getYEnd()); 1498 } 1499 this.controller.selectAndShowItems(Arrays.asList(this.oldSelection), this.allLevelsSelection); 1500 } 1501 1502 @Override redo()1503 public void redo() throws CannotRedoException { 1504 super.redo(); 1505 this.controller.doJoinWalls(this.joinedWalls, this.joinPoint); 1506 } 1507 } 1508 1509 /** 1510 * Joins two walls at the given point. 1511 */ doJoinWalls(final JoinedWall [] joinedWalls, float [] joinPoint)1512 private void doJoinWalls(final JoinedWall [] joinedWalls, 1513 float [] joinPoint) { 1514 Wall [] walls = {joinedWalls [0].getWall(), joinedWalls [1].getWall()}; 1515 // Ignore parallel walls 1516 boolean connected = false; 1517 for (int i = 0; i < 2; i++) { 1518 boolean joinAtEnd = walls [i].getWallAtEnd() == null; 1519 boolean joinAtStart = walls [i].getWallAtStart() == null; 1520 if (joinAtStart && joinAtEnd) { 1521 // Join at the point closest to intersection 1522 if (Point2D.distanceSq(walls [i].getXStart(), walls [i].getYStart(), joinPoint [0], joinPoint [1]) 1523 < Point2D.distanceSq(walls [i].getXEnd(), walls [i].getYEnd(), joinPoint [0], joinPoint [1])) { 1524 joinAtEnd = false; 1525 } else { 1526 joinAtStart = false; 1527 } 1528 } 1529 if (joinAtEnd) { 1530 walls [i].setXEnd(joinPoint [0]); 1531 walls [i].setYEnd(joinPoint [1]); 1532 } else if (joinAtStart) { 1533 walls [i].setXStart(joinPoint [0]); 1534 walls [i].setYStart(joinPoint [1]); 1535 } 1536 if (connected 1537 || walls [(i + 1) % 2].getWallAtStart() == null 1538 || walls [(i + 1) % 2].getWallAtEnd() == null) { 1539 if (joinAtEnd) { 1540 walls [i].setWallAtEnd(walls [(i + 1) % 2]); 1541 connected = true; 1542 } else if (joinAtStart) { 1543 walls [i].setWallAtStart(walls [(i + 1) % 2]); 1544 connected = true; 1545 } 1546 } 1547 } 1548 if (connected) { 1549 this.home.setSelectedItems(Arrays.asList(walls [0], walls [1])); 1550 } else { 1551 this.home.setSelectedItems(Arrays.asList(walls [0])); 1552 } 1553 } 1554 1555 /** 1556 * Controls the direction reverse of selected walls. 1557 */ reverseSelectedWallsDirection()1558 public void reverseSelectedWallsDirection() { 1559 List<Selectable> selectedItems = this.home.getSelectedItems(); 1560 List<Wall> selectedWalls = Home.getWallsSubList(selectedItems); 1561 if (!selectedWalls.isEmpty()) { 1562 Wall [] reversedWalls = selectedWalls.toArray(new Wall [selectedWalls.size()]); 1563 doReverseWallsDirection(reversedWalls); 1564 selectAndShowItems(Arrays.asList(reversedWalls), false); 1565 this.undoSupport.postEdit(new WallsDirectionReversingUndoableEdit(this, this.preferences, 1566 selectedItems.toArray(new Selectable [selectedItems.size()]), this.home.isAllLevelsSelection(), reversedWalls)); 1567 } 1568 } 1569 1570 /** 1571 * Undoable edit for reversing walls direction. 1572 */ 1573 private static class WallsDirectionReversingUndoableEdit extends LocalizedUndoableEdit { 1574 private final PlanController controller; 1575 private final Selectable [] oldSelection; 1576 private final boolean allLevelsSelection; 1577 private final Wall [] walls; 1578 WallsDirectionReversingUndoableEdit(PlanController controller, UserPreferences preferences, Selectable [] oldSelection, boolean allLevelsSelection, Wall [] walls)1579 public WallsDirectionReversingUndoableEdit(PlanController controller, UserPreferences preferences, 1580 Selectable [] oldSelection, boolean allLevelsSelection, Wall [] walls) { 1581 super(preferences, PlanController.class, "undoReverseWallsDirectionName"); 1582 this.controller = controller; 1583 this.oldSelection = oldSelection; 1584 this.allLevelsSelection = allLevelsSelection; 1585 this.walls = walls; 1586 } 1587 1588 @Override undo()1589 public void undo() throws CannotUndoException { 1590 super.undo(); 1591 controller.doReverseWallsDirection(this.walls); 1592 controller.selectAndShowItems(Arrays.asList(this.oldSelection), this.allLevelsSelection); 1593 } 1594 1595 @Override redo()1596 public void redo() throws CannotRedoException { 1597 super.redo(); 1598 controller.doReverseWallsDirection(this.walls); 1599 controller.selectAndShowItems(Arrays.asList(this.walls), false); 1600 } 1601 } 1602 1603 /** 1604 * Reverses the <code>walls</code> direction. 1605 */ doReverseWallsDirection(Wall [] walls)1606 private void doReverseWallsDirection(Wall [] walls) { 1607 for (Wall wall : walls) { 1608 float xStart = wall.getXStart(); 1609 float yStart = wall.getYStart(); 1610 float xEnd = wall.getXEnd(); 1611 float yEnd = wall.getYEnd(); 1612 wall.setXStart(xEnd); 1613 wall.setYStart(yEnd); 1614 wall.setXEnd(xStart); 1615 wall.setYEnd(yStart); 1616 if (wall.getArcExtent() != null) { 1617 wall.setArcExtent(-wall.getArcExtent()); 1618 } 1619 1620 Wall wallAtStart = wall.getWallAtStart(); 1621 boolean joinedAtEndOfWallAtStart = 1622 wallAtStart != null 1623 && wallAtStart.getWallAtEnd() == wall; 1624 boolean joinedAtStartOfWallAtStart = 1625 wallAtStart != null 1626 && wallAtStart.getWallAtStart() == wall; 1627 Wall wallAtEnd = wall.getWallAtEnd(); 1628 boolean joinedAtEndOfWallAtEnd = 1629 wallAtEnd != null 1630 && wallAtEnd.getWallAtEnd() == wall; 1631 boolean joinedAtStartOfWallAtEnd = 1632 wallAtEnd != null 1633 && wallAtEnd.getWallAtStart() == wall; 1634 1635 wall.setWallAtStart(wallAtEnd); 1636 wall.setWallAtEnd(wallAtStart); 1637 1638 if (joinedAtEndOfWallAtStart) { 1639 wallAtStart.setWallAtEnd(wall); 1640 } else if (joinedAtStartOfWallAtStart) { 1641 wallAtStart.setWallAtStart(wall); 1642 } 1643 1644 if (joinedAtEndOfWallAtEnd) { 1645 wallAtEnd.setWallAtEnd(wall); 1646 } else if (joinedAtStartOfWallAtEnd) { 1647 wallAtEnd.setWallAtStart(wall); 1648 } 1649 1650 Float heightAtEnd = wall.getHeightAtEnd(); 1651 if (heightAtEnd != null) { 1652 Float height = wall.getHeight(); 1653 wall.setHeight(heightAtEnd); 1654 wall.setHeightAtEnd(height); 1655 } 1656 1657 reverseWallSidesStyle(wall); 1658 } 1659 } 1660 1661 /** 1662 * Exchanges the style of wall sides. 1663 */ reverseWallSidesStyle(Wall wall)1664 private static void reverseWallSidesStyle(Wall wall) { 1665 Integer rightSideColor = wall.getRightSideColor(); 1666 HomeTexture rightSideTexture = wall.getRightSideTexture(); 1667 float leftSideShininess = wall.getLeftSideShininess(); 1668 Baseboard leftSideBaseboard = wall.getLeftSideBaseboard(); 1669 Integer leftSideColor = wall.getLeftSideColor(); 1670 HomeTexture leftSideTexture = wall.getLeftSideTexture(); 1671 float rightSideShininess = wall.getRightSideShininess(); 1672 Baseboard rightSideBaseboard = wall.getRightSideBaseboard(); 1673 wall.setLeftSideColor(rightSideColor); 1674 wall.setLeftSideTexture(rightSideTexture); 1675 wall.setLeftSideShininess(rightSideShininess); 1676 wall.setLeftSideBaseboard(rightSideBaseboard); 1677 wall.setRightSideColor(leftSideColor); 1678 wall.setRightSideTexture(leftSideTexture); 1679 wall.setRightSideShininess(leftSideShininess); 1680 wall.setRightSideBaseboard(leftSideBaseboard); 1681 } 1682 1683 /** 1684 * Controls the split of the selected wall in two joined walls of equal length. 1685 */ splitSelectedWall()1686 public void splitSelectedWall() { 1687 List<Selectable> selectedItems = this.home.getSelectedItems(); 1688 List<Wall> selectedWalls = Home.getWallsSubList(selectedItems); 1689 if (selectedWalls.size() == 1) { 1690 boolean allLevelsSelection = this.home.isAllLevelsSelection(); 1691 boolean basePlanLocked = this.home.isBasePlanLocked(); 1692 Wall splitWall = selectedWalls.get(0); 1693 JoinedWall splitJoinedWall = new JoinedWall(splitWall); 1694 float xStart = splitWall.getXStart(); 1695 float yStart = splitWall.getYStart(); 1696 float xEnd = splitWall.getXEnd(); 1697 float yEnd = splitWall.getYEnd(); 1698 float xMiddle = (xStart + xEnd) / 2; 1699 float yMiddle = (yStart + yEnd) / 2; 1700 1701 Wall wallAtStart = splitWall.getWallAtStart(); 1702 boolean joinedAtEndOfWallAtStart = 1703 wallAtStart != null 1704 && wallAtStart.getWallAtEnd() == splitWall; 1705 boolean joinedAtStartOfWallAtStart = 1706 wallAtStart != null 1707 && wallAtStart.getWallAtStart() == splitWall; 1708 Wall wallAtEnd = splitWall.getWallAtEnd(); 1709 boolean joinedAtEndOfWallAtEnd = 1710 wallAtEnd != null 1711 && wallAtEnd.getWallAtEnd() == splitWall; 1712 boolean joinedAtStartOfWallAtEnd = 1713 wallAtEnd != null 1714 && wallAtEnd.getWallAtStart() == splitWall; 1715 1716 // Clone new walls to copy their characteristics 1717 Wall firstWall = (Wall)splitWall.duplicate(); 1718 this.home.addWall(firstWall); 1719 firstWall.setLevel(splitWall.getLevel()); 1720 Wall secondWall = (Wall)splitWall.duplicate(); 1721 this.home.addWall(secondWall); 1722 secondWall.setLevel(splitWall.getLevel()); 1723 1724 // Change split walls end and start point 1725 firstWall.setXEnd(xMiddle); 1726 firstWall.setYEnd(yMiddle); 1727 secondWall.setXStart(xMiddle); 1728 secondWall.setYStart(yMiddle); 1729 if (splitWall.getHeightAtEnd() != null) { 1730 Float heightAtMiddle = (splitWall.getHeight() + splitWall.getHeightAtEnd()) / 2; 1731 firstWall.setHeightAtEnd(heightAtMiddle); 1732 secondWall.setHeight(heightAtMiddle); 1733 } 1734 1735 firstWall.setWallAtEnd(secondWall); 1736 secondWall.setWallAtStart(firstWall); 1737 1738 firstWall.setWallAtStart(wallAtStart); 1739 if (joinedAtEndOfWallAtStart) { 1740 wallAtStart.setWallAtEnd(firstWall); 1741 } else if (joinedAtStartOfWallAtStart) { 1742 wallAtStart.setWallAtStart(firstWall); 1743 } 1744 1745 secondWall.setWallAtEnd(wallAtEnd); 1746 if (joinedAtEndOfWallAtEnd) { 1747 wallAtEnd.setWallAtEnd(secondWall); 1748 } else if (joinedAtStartOfWallAtEnd) { 1749 wallAtEnd.setWallAtStart(secondWall); 1750 } 1751 1752 // Delete split wall 1753 this.home.deleteWall(splitWall); 1754 selectAndShowItems(Arrays.asList(new Wall [] {firstWall}), false); 1755 1756 this.undoSupport.postEdit(new WallSplittingUndoableEdit(this, this.preferences, 1757 selectedItems.toArray(new Selectable [selectedItems.size()]), basePlanLocked, allLevelsSelection, 1758 splitJoinedWall, new JoinedWall(firstWall), new JoinedWall(secondWall), this.home.isBasePlanLocked())); 1759 } 1760 } 1761 1762 /** 1763 * Undoable edit for splitting wall. 1764 */ 1765 private static class WallSplittingUndoableEdit extends LocalizedUndoableEdit { 1766 private final PlanController controller; 1767 private final Selectable [] oldSelection; 1768 private final boolean oldBasePlanLocked; 1769 private final boolean oldAllLevelsSelection; 1770 private final JoinedWall splitJoinedWall; 1771 private final JoinedWall firstJoinedWall; 1772 private final JoinedWall secondJoinedWall; 1773 private final boolean newBasePlanLocked; 1774 WallSplittingUndoableEdit(PlanController controller, UserPreferences preferences, Selectable [] oldSelection, boolean oldBasePlanLocked, boolean oldAllLevelsSelection, JoinedWall splitJoinedWall, JoinedWall firstJoinedWall, JoinedWall secondJoinedWall, boolean newBasePlanLocked)1775 public WallSplittingUndoableEdit(PlanController controller, UserPreferences preferences, Selectable [] oldSelection, boolean oldBasePlanLocked, 1776 boolean oldAllLevelsSelection, JoinedWall splitJoinedWall, 1777 JoinedWall firstJoinedWall, JoinedWall secondJoinedWall, 1778 boolean newBasePlanLocked) { 1779 super(preferences, PlanController.class, "undoSplitWallName"); 1780 this.controller = controller; 1781 this.oldSelection = oldSelection; 1782 this.oldBasePlanLocked = oldBasePlanLocked; 1783 this.oldAllLevelsSelection = oldAllLevelsSelection; 1784 this.splitJoinedWall = splitJoinedWall; 1785 this.firstJoinedWall = firstJoinedWall; 1786 this.secondJoinedWall = secondJoinedWall; 1787 this.newBasePlanLocked = newBasePlanLocked; 1788 } 1789 1790 @Override undo()1791 public void undo() throws CannotUndoException { 1792 super.undo(); 1793 this.controller.doDeleteWalls(new JoinedWall [] {this.firstJoinedWall, this.secondJoinedWall}, this.oldBasePlanLocked); 1794 this.controller.doAddWalls(new JoinedWall [] {this.splitJoinedWall}, this.oldBasePlanLocked); 1795 this.controller.selectAndShowItems(Arrays.asList(this.oldSelection), this.oldAllLevelsSelection); 1796 } 1797 1798 @Override redo()1799 public void redo() throws CannotRedoException { 1800 super.redo(); 1801 this.controller.doDeleteWalls(new JoinedWall [] {this.splitJoinedWall}, this.newBasePlanLocked); 1802 this.controller.doAddWalls(new JoinedWall [] {this.firstJoinedWall, this.secondJoinedWall}, this.newBasePlanLocked); 1803 this.controller.selectAndShowItems(Arrays.asList(new Wall [] {this.firstJoinedWall.getWall()}), false); 1804 } 1805 } 1806 1807 /** 1808 * Controls the modification of the selected rooms. 1809 */ modifySelectedRooms()1810 public void modifySelectedRooms() { 1811 if (!Home.getRoomsSubList(this.home.getSelectedItems()).isEmpty()) { 1812 new RoomController(this.home, this.preferences, this.viewFactory, 1813 this.contentManager, this.undoSupport).displayView(getView()); 1814 } 1815 } 1816 1817 /** 1818 * Returns a new label. The new label isn't added to home. 1819 */ createLabel(float x, float y)1820 private void createLabel(float x, float y) { 1821 new LabelController(this.home, x, y, this.preferences, this.viewFactory, 1822 this.undoSupport).displayView(getView()); 1823 } 1824 1825 /** 1826 * Controls the modification of the selected labels. 1827 */ modifySelectedLabels()1828 public void modifySelectedLabels() { 1829 if (!Home.getLabelsSubList(this.home.getSelectedItems()).isEmpty()) { 1830 new LabelController(this.home, this.preferences, this.viewFactory, 1831 this.undoSupport).displayView(getView()); 1832 } 1833 } 1834 1835 /** 1836 * Controls the modification of the selected polylines. 1837 * @since 5.0 1838 */ modifySelectedPolylines()1839 public void modifySelectedPolylines() { 1840 if (!Home.getPolylinesSubList(this.home.getSelectedItems()).isEmpty()) { 1841 new PolylineController(this.home, this.preferences, this.viewFactory, 1842 this.contentManager, this.undoSupport).displayView(getView()); 1843 } 1844 } 1845 1846 /** 1847 * Controls the modification of the compass. 1848 */ modifyCompass()1849 public void modifyCompass() { 1850 new CompassController(this.home, this.preferences, this.viewFactory, 1851 this.undoSupport).displayView(getView()); 1852 } 1853 1854 /** 1855 * Controls the modification of the observer camera. 1856 */ modifyObserverCamera()1857 public void modifyObserverCamera() { 1858 new ObserverCameraController(this.home, this.preferences, this.viewFactory).displayView(getView()); 1859 } 1860 1861 /** 1862 * Toggles bold style of texts in selected items. 1863 */ toggleBoldStyle()1864 public void toggleBoldStyle() { 1865 // Find if selected items are all bold or not 1866 Boolean selectionBoldStyle = null; 1867 for (Selectable item : this.home.getSelectedItems()) { 1868 Boolean bold; 1869 if (item instanceof Label) { 1870 bold = getItemTextStyle(item, ((Label)item).getStyle()).isBold(); 1871 } else if (item instanceof HomePieceOfFurniture 1872 && ((HomePieceOfFurniture)item).isVisible()) { 1873 bold = getItemTextStyle(item, ((HomePieceOfFurniture)item).getNameStyle()).isBold(); 1874 } else if (item instanceof Room) { 1875 Room room = (Room)item; 1876 bold = getItemTextStyle(room, room.getNameStyle()).isBold(); 1877 if (bold != getItemTextStyle(room, room.getAreaStyle()).isBold()) { 1878 bold = null; 1879 } 1880 } else if (item instanceof DimensionLine) { 1881 bold = getItemTextStyle(item, ((DimensionLine)item).getLengthStyle()).isBold(); 1882 } else { 1883 continue; 1884 } 1885 if (selectionBoldStyle == null) { 1886 selectionBoldStyle = bold; 1887 } else if (bold == null || !selectionBoldStyle.equals(bold)) { 1888 selectionBoldStyle = null; 1889 break; 1890 } 1891 } 1892 1893 // Apply new bold style to all selected items 1894 if (selectionBoldStyle == null) { 1895 selectionBoldStyle = Boolean.TRUE; 1896 } else { 1897 selectionBoldStyle = !selectionBoldStyle; 1898 } 1899 1900 List<Selectable> itemsWithText = new ArrayList<Selectable>(); 1901 List<TextStyle> oldTextStyles = new ArrayList<TextStyle>(); 1902 List<TextStyle> textStyles = new ArrayList<TextStyle>(); 1903 for (Selectable item : this.home.getSelectedItems()) { 1904 if (item instanceof Label) { 1905 Label label = (Label)item; 1906 itemsWithText.add(label); 1907 TextStyle oldTextStyle = getItemTextStyle(label, label.getStyle()); 1908 oldTextStyles.add(oldTextStyle); 1909 textStyles.add(oldTextStyle.deriveBoldStyle(selectionBoldStyle)); 1910 } else if (item instanceof HomePieceOfFurniture) { 1911 HomePieceOfFurniture piece = (HomePieceOfFurniture)item; 1912 if (piece.isVisible()) { 1913 itemsWithText.add(piece); 1914 TextStyle oldNameStyle = getItemTextStyle(piece, piece.getNameStyle()); 1915 oldTextStyles.add(oldNameStyle); 1916 textStyles.add(oldNameStyle.deriveBoldStyle(selectionBoldStyle)); 1917 } 1918 } else if (item instanceof Room) { 1919 final Room room = (Room)item; 1920 itemsWithText.add(room); 1921 TextStyle oldNameStyle = getItemTextStyle(room, room.getNameStyle()); 1922 oldTextStyles.add(oldNameStyle); 1923 textStyles.add(oldNameStyle.deriveBoldStyle(selectionBoldStyle)); 1924 TextStyle oldAreaStyle = getItemTextStyle(room, room.getAreaStyle()); 1925 oldTextStyles.add(oldAreaStyle); 1926 textStyles.add(oldAreaStyle.deriveBoldStyle(selectionBoldStyle)); 1927 } else if (item instanceof DimensionLine) { 1928 DimensionLine dimensionLine = (DimensionLine)item; 1929 itemsWithText.add(dimensionLine); 1930 TextStyle oldLengthStyle = getItemTextStyle(dimensionLine, dimensionLine.getLengthStyle()); 1931 oldTextStyles.add(oldLengthStyle); 1932 textStyles.add(oldLengthStyle.deriveBoldStyle(selectionBoldStyle)); 1933 } 1934 } 1935 modifyTextStyle(itemsWithText.toArray(new Selectable [itemsWithText.size()]), 1936 oldTextStyles.toArray(new TextStyle [oldTextStyles.size()]), 1937 textStyles.toArray(new TextStyle [textStyles.size()])); 1938 } 1939 1940 /** 1941 * Returns <code>textStyle</code> if not null or the default text style. 1942 */ getItemTextStyle(Selectable item, TextStyle textStyle)1943 private TextStyle getItemTextStyle(Selectable item, TextStyle textStyle) { 1944 if (textStyle == null) { 1945 textStyle = this.preferences.getDefaultTextStyle(item.getClass()); 1946 } 1947 return textStyle; 1948 } 1949 1950 /** 1951 * Toggles italic style of texts in selected items. 1952 */ toggleItalicStyle()1953 public void toggleItalicStyle() { 1954 // Find if selected items are all italic or not 1955 Boolean selectionItalicStyle = null; 1956 for (Selectable item : this.home.getSelectedItems()) { 1957 Boolean italic; 1958 if (item instanceof Label) { 1959 italic = getItemTextStyle(item, ((Label)item).getStyle()).isItalic(); 1960 } else if (item instanceof HomePieceOfFurniture 1961 && ((HomePieceOfFurniture)item).isVisible()) { 1962 italic = getItemTextStyle(item, ((HomePieceOfFurniture)item).getNameStyle()).isItalic(); 1963 } else if (item instanceof Room) { 1964 Room room = (Room)item; 1965 italic = getItemTextStyle(room, room.getNameStyle()).isItalic(); 1966 if (italic != getItemTextStyle(room, room.getAreaStyle()).isItalic()) { 1967 italic = null; 1968 } 1969 } else if (item instanceof DimensionLine) { 1970 italic = getItemTextStyle(item, ((DimensionLine)item).getLengthStyle()).isItalic(); 1971 } else { 1972 continue; 1973 } 1974 if (selectionItalicStyle == null) { 1975 selectionItalicStyle = italic; 1976 } else if (italic == null || !selectionItalicStyle.equals(italic)) { 1977 selectionItalicStyle = null; 1978 break; 1979 } 1980 } 1981 1982 // Apply new italic style to all selected items 1983 if (selectionItalicStyle == null) { 1984 selectionItalicStyle = Boolean.TRUE; 1985 } else { 1986 selectionItalicStyle = !selectionItalicStyle; 1987 } 1988 1989 List<Selectable> itemsWithText = new ArrayList<Selectable>(); 1990 List<TextStyle> oldTextStyles = new ArrayList<TextStyle>(); 1991 List<TextStyle> textStyles = new ArrayList<TextStyle>(); 1992 for (Selectable item : this.home.getSelectedItems()) { 1993 if (item instanceof Label) { 1994 Label label = (Label)item; 1995 itemsWithText.add(label); 1996 TextStyle oldTextStyle = getItemTextStyle(label, label.getStyle()); 1997 oldTextStyles.add(oldTextStyle); 1998 textStyles.add(oldTextStyle.deriveItalicStyle(selectionItalicStyle)); 1999 } else if (item instanceof HomePieceOfFurniture) { 2000 HomePieceOfFurniture piece = (HomePieceOfFurniture)item; 2001 if (piece.isVisible()) { 2002 itemsWithText.add(piece); 2003 TextStyle oldNameStyle = getItemTextStyle(piece, piece.getNameStyle()); 2004 oldTextStyles.add(oldNameStyle); 2005 textStyles.add(oldNameStyle.deriveItalicStyle(selectionItalicStyle)); 2006 } 2007 } else if (item instanceof Room) { 2008 final Room room = (Room)item; 2009 itemsWithText.add(room); 2010 TextStyle oldNameStyle = getItemTextStyle(room, room.getNameStyle()); 2011 oldTextStyles.add(oldNameStyle); 2012 textStyles.add(oldNameStyle.deriveItalicStyle(selectionItalicStyle)); 2013 TextStyle oldAreaStyle = getItemTextStyle(room, room.getAreaStyle()); 2014 oldTextStyles.add(oldAreaStyle); 2015 textStyles.add(oldAreaStyle.deriveItalicStyle(selectionItalicStyle)); 2016 } else if (item instanceof DimensionLine) { 2017 DimensionLine dimensionLine = (DimensionLine)item; 2018 itemsWithText.add(dimensionLine); 2019 TextStyle oldLengthStyle = getItemTextStyle(dimensionLine, dimensionLine.getLengthStyle()); 2020 oldTextStyles.add(oldLengthStyle); 2021 textStyles.add(oldLengthStyle.deriveItalicStyle(selectionItalicStyle)); 2022 } 2023 } 2024 modifyTextStyle(itemsWithText.toArray(new Selectable [itemsWithText.size()]), 2025 oldTextStyles.toArray(new TextStyle [oldTextStyles.size()]), 2026 textStyles.toArray(new TextStyle [textStyles.size()])); 2027 } 2028 2029 /** 2030 * Increase the size of texts in selected items. 2031 */ increaseTextSize()2032 public void increaseTextSize() { 2033 applyFactorToTextSize(1.1f); 2034 } 2035 2036 /** 2037 * Decrease the size of texts in selected items. 2038 */ decreaseTextSize()2039 public void decreaseTextSize() { 2040 applyFactorToTextSize(1 / 1.1f); 2041 } 2042 2043 /** 2044 * Applies a factor to the font size of the texts of the selected items in home. 2045 */ applyFactorToTextSize(float factor)2046 private void applyFactorToTextSize(float factor) { 2047 List<Selectable> itemsWithText = new ArrayList<Selectable>(); 2048 List<TextStyle> oldTextStyles = new ArrayList<TextStyle>(); 2049 List<TextStyle> textStyles = new ArrayList<TextStyle>(); 2050 for (Selectable item : this.home.getSelectedItems()) { 2051 if (item instanceof Label) { 2052 Label label = (Label)item; 2053 itemsWithText.add(label); 2054 TextStyle oldLabelStyle = getItemTextStyle(item, label.getStyle()); 2055 oldTextStyles.add(oldLabelStyle); 2056 textStyles.add(oldLabelStyle.deriveStyle(Math.round(oldLabelStyle.getFontSize() * factor))); 2057 } else if (item instanceof HomePieceOfFurniture) { 2058 HomePieceOfFurniture piece = (HomePieceOfFurniture)item; 2059 if (piece.isVisible()) { 2060 itemsWithText.add(piece); 2061 TextStyle oldNameStyle = getItemTextStyle(piece, piece.getNameStyle()); 2062 oldTextStyles.add(oldNameStyle); 2063 textStyles.add(oldNameStyle.deriveStyle(Math.round(oldNameStyle.getFontSize() * factor))); 2064 } 2065 } else if (item instanceof Room) { 2066 final Room room = (Room)item; 2067 itemsWithText.add(room); 2068 TextStyle oldNameStyle = getItemTextStyle(room, room.getNameStyle()); 2069 oldTextStyles.add(oldNameStyle); 2070 textStyles.add(oldNameStyle.deriveStyle(Math.round(oldNameStyle.getFontSize() * factor))); 2071 TextStyle oldAreaStyle = getItemTextStyle(room, room.getAreaStyle()); 2072 oldTextStyles.add(oldAreaStyle); 2073 textStyles.add(oldAreaStyle.deriveStyle(Math.round(oldAreaStyle.getFontSize() * factor))); 2074 } else if (item instanceof DimensionLine) { 2075 DimensionLine dimensionLine = (DimensionLine)item; 2076 itemsWithText.add(dimensionLine); 2077 TextStyle oldLengthStyle = getItemTextStyle(dimensionLine, dimensionLine.getLengthStyle()); 2078 oldTextStyles.add(oldLengthStyle); 2079 textStyles.add(oldLengthStyle.deriveStyle(Math.round(oldLengthStyle.getFontSize() * factor))); 2080 } 2081 } 2082 modifyTextStyle(itemsWithText.toArray(new Selectable [itemsWithText.size()]), 2083 oldTextStyles.toArray(new TextStyle [oldTextStyles.size()]), 2084 textStyles.toArray(new TextStyle [textStyles.size()])); 2085 } 2086 2087 /** 2088 * Changes the style of items and posts an undoable change style operation. 2089 */ modifyTextStyle(final Selectable [] items, final TextStyle [] oldStyles, final TextStyle [] styles)2090 private void modifyTextStyle(final Selectable [] items, 2091 final TextStyle [] oldStyles, 2092 final TextStyle [] styles) { 2093 final boolean allLevelsSelection = home.isAllLevelsSelection(); 2094 List<Selectable> oldSelectedItems = this.home.getSelectedItems(); 2095 final Selectable [] oldSelection = 2096 oldSelectedItems.toArray(new Selectable [oldSelectedItems.size()]); 2097 2098 doModifyTextStyle(items, styles); 2099 this.undoSupport.postEdit(new TextStyleModificationUndoableEdit(this, this.preferences, 2100 oldSelection, allLevelsSelection, oldStyles, items, styles)); 2101 } 2102 2103 /** 2104 * Undoable edit for text style modification. 2105 */ 2106 private static class TextStyleModificationUndoableEdit extends LocalizedUndoableEdit { 2107 private final PlanController controller; 2108 private final Selectable [] oldSelection; 2109 private final boolean allLevelsSelection; 2110 private final TextStyle [] oldStyles; 2111 private final Selectable [] items; 2112 private final TextStyle [] styles; 2113 TextStyleModificationUndoableEdit(PlanController controller, UserPreferences preferences, Selectable [] oldSelection, boolean allLevelsSelection, TextStyle [] oldStyles, Selectable [] items, TextStyle [] styles)2114 public TextStyleModificationUndoableEdit(PlanController controller, UserPreferences preferences, 2115 Selectable [] oldSelection, boolean allLevelsSelection, TextStyle [] oldStyles, 2116 Selectable [] items, TextStyle [] styles) { 2117 super(preferences, PlanController.class, "undoModifyTextStyleName"); 2118 this.controller = controller; 2119 this.oldSelection = oldSelection; 2120 this.allLevelsSelection = allLevelsSelection; 2121 this.oldStyles = oldStyles; 2122 this.items = items; 2123 this.styles = styles; 2124 } 2125 2126 @Override undo()2127 public void undo() throws CannotUndoException { 2128 super.undo(); 2129 doModifyTextStyle(this.items, this.oldStyles); 2130 this.controller.selectAndShowItems(Arrays.asList(this.oldSelection), this.allLevelsSelection); 2131 } 2132 2133 @Override redo()2134 public void redo() throws CannotRedoException { 2135 super.redo(); 2136 doModifyTextStyle(this.items, this.styles); 2137 this.controller.selectAndShowItems(Arrays.asList(this.oldSelection), this.allLevelsSelection); 2138 } 2139 } 2140 2141 /** 2142 * Changes the style of items. 2143 */ doModifyTextStyle(Selectable [] items, TextStyle [] styles)2144 private static void doModifyTextStyle(Selectable [] items, TextStyle [] styles) { 2145 int styleIndex = 0; 2146 for (Selectable item : items) { 2147 if (item instanceof Label) { 2148 ((Label)item).setStyle(styles [styleIndex++]); 2149 } else if (item instanceof HomePieceOfFurniture) { 2150 HomePieceOfFurniture piece = (HomePieceOfFurniture)item; 2151 if (piece.isVisible()) { 2152 piece.setNameStyle(styles [styleIndex++]); 2153 } 2154 } else if (item instanceof Room) { 2155 final Room room = (Room)item; 2156 room.setNameStyle(styles [styleIndex++]); 2157 room.setAreaStyle(styles [styleIndex++]); 2158 } else if (item instanceof DimensionLine) { 2159 ((DimensionLine)item).setLengthStyle(styles [styleIndex++]); 2160 } 2161 } 2162 } 2163 2164 /** 2165 * Returns the minimum scale of the plan view. 2166 */ getMinimumScale()2167 public float getMinimumScale() { 2168 return 0.01f; 2169 } 2170 2171 /** 2172 * Returns the maximum scale of the plan view. 2173 */ getMaximumScale()2174 public float getMaximumScale() { 2175 return 10f; 2176 } 2177 2178 /** 2179 * Returns the scale in plan view. 2180 */ getScale()2181 public float getScale() { 2182 return getView().getScale(); 2183 } 2184 2185 /** 2186 * Controls the scale in plan view and and fires a <code>PropertyChangeEvent</code>. 2187 */ setScale(float scale)2188 public void setScale(float scale) { 2189 scale = Math.max(getMinimumScale(), Math.min(scale, getMaximumScale())); 2190 if (scale != getView().getScale()) { 2191 float oldScale = getView().getScale(); 2192 this.furnitureSidesCache.clear(); 2193 if (getView() != null) { 2194 int x = getView().convertXModelToScreen(getXLastMouseMove()); 2195 int y = getView().convertXModelToScreen(getYLastMouseMove()); 2196 getView().setScale(scale); 2197 // Update mouse location 2198 moveMouse(getView().convertXPixelToModel(x), getView().convertYPixelToModel(y)); 2199 } 2200 this.propertyChangeSupport.firePropertyChange(Property.SCALE.name(), oldScale, scale); 2201 this.home.setProperty(SCALE_VISUAL_PROPERTY, String.valueOf(scale)); 2202 } 2203 } 2204 2205 /** 2206 * Sets the selected level in home. 2207 */ setSelectedLevel(Level level)2208 public final void setSelectedLevel(Level level) { 2209 this.home.setSelectedLevel(level); 2210 } 2211 2212 /** 2213 * Selects all visible items in the selected level of home. 2214 */ 2215 @Override selectAll()2216 public void selectAll() { 2217 List<Selectable> all = getVisibleItemsAtSelectedLevel(); 2218 if (this.home.isBasePlanLocked()) { 2219 this.home.setSelectedItems(getItemsNotPartOfBasePlan(all)); 2220 } else { 2221 this.home.setSelectedItems(all); 2222 } 2223 this.home.setAllLevelsSelection(false); 2224 } 2225 2226 /** 2227 * Returns the viewable and selectable home items at the selected level, except camera. 2228 */ getVisibleItemsAtSelectedLevel()2229 private List<Selectable> getVisibleItemsAtSelectedLevel() { 2230 List<Selectable> selectableItems = new ArrayList<Selectable>(); 2231 Level selectedLevel = this.home.getSelectedLevel(); 2232 for (Selectable item : this.home.getSelectableViewableItems()) { 2233 if (item instanceof HomePieceOfFurniture) { 2234 if (isPieceOfFurnitureVisibleAtSelectedLevel((HomePieceOfFurniture)item)) { 2235 selectableItems.add(item); 2236 } 2237 } else if (!(item instanceof Elevatable) 2238 || ((Elevatable)item).isAtLevel(selectedLevel)) { 2239 selectableItems.add(item); 2240 } 2241 } 2242 return selectableItems; 2243 } 2244 2245 /** 2246 * Selects all visible items in all levels of home. 2247 * @since 4.4 2248 */ selectAllAtAllLevels()2249 public void selectAllAtAllLevels() { 2250 List<Selectable> allItems = new ArrayList<Selectable>(this.home.getSelectableViewableItems()); 2251 if (this.home.isBasePlanLocked()) { 2252 allItems = getItemsNotPartOfBasePlan(allItems); 2253 } 2254 this.home.setSelectedItems(allItems); 2255 this.home.setAllLevelsSelection(true); 2256 } 2257 2258 /** 2259 * Returns the visible (fully or partially) rooms at the selected level in home. 2260 */ getDetectableRoomsAtSelectedLevel()2261 private List<Room> getDetectableRoomsAtSelectedLevel() { 2262 List<Room> rooms = this.home.getRooms(); 2263 Level selectedLevel = this.home.getSelectedLevel(); 2264 List<Level> levels = this.home.getLevels(); 2265 if (selectedLevel == null || levels.size() <= 1) { 2266 return rooms; 2267 } else { 2268 List<Room> visibleRooms = new ArrayList<Room>(rooms.size()); 2269 int selectedLevelIndex = levels.indexOf(selectedLevel); 2270 boolean level0 = levels.get(0) == selectedLevel 2271 || levels.get(selectedLevelIndex - 1).getElevation() == selectedLevel.getElevation(); 2272 Level otherLevel = levels.get(level0 && selectedLevelIndex < levels.size() - 1 2273 ? selectedLevelIndex + 1 2274 : selectedLevelIndex - 1); 2275 for (Room room : rooms) { 2276 if (room.isAtLevel(selectedLevel) 2277 || otherLevel != null 2278 && room.isAtLevel(otherLevel) 2279 && (level0 && room.isFloorVisible() 2280 || !level0 && room.isCeilingVisible())) { 2281 visibleRooms.add(room); 2282 } 2283 } 2284 return visibleRooms; 2285 } 2286 } 2287 2288 /** 2289 * Returns the visible (fully or partially) walls at the selected level in home. 2290 */ getDetectableWallsAtSelectedLevel()2291 private Collection<Wall> getDetectableWallsAtSelectedLevel() { 2292 Collection<Wall> walls = this.home.getWalls(); 2293 Level selectedLevel = this.home.getSelectedLevel(); 2294 List<Level> levels = this.home.getLevels(); 2295 if (selectedLevel == null || levels.size() <= 1) { 2296 return walls; 2297 } else { 2298 Collection<Wall> visibleWalls = new ArrayList<Wall>(walls.size()); 2299 int selectedLevelIndex = levels.indexOf(selectedLevel); 2300 boolean level0 = levels.get(0) == selectedLevel 2301 || levels.get(selectedLevelIndex - 1).getElevation() == selectedLevel.getElevation(); 2302 Level otherLevel = levels.get(level0 && selectedLevelIndex < levels.size() - 1 2303 ? selectedLevelIndex + 1 2304 : selectedLevelIndex - 1); 2305 for (Wall wall : walls) { 2306 if (wall.isAtLevel(selectedLevel) 2307 || otherLevel != null 2308 && wall.isAtLevel(otherLevel)) { 2309 visibleWalls.add(wall); 2310 } 2311 } 2312 return visibleWalls; 2313 } 2314 } 2315 2316 /** 2317 * Returns the horizontal ruler of the plan view. 2318 */ getHorizontalRulerView()2319 public View getHorizontalRulerView() { 2320 return getView().getHorizontalRuler(); 2321 } 2322 2323 /** 2324 * Returns the vertical ruler of the plan view. 2325 */ getVerticalRulerView()2326 public View getVerticalRulerView() { 2327 return getView().getVerticalRuler(); 2328 } 2329 addModelListeners()2330 private void addModelListeners() { 2331 this.selectionListener = new SelectionListener() { 2332 public void selectionChanged(SelectionEvent ev) { 2333 selectLevelFromSelectedItems(); 2334 if (getView() != null) { 2335 getView().makeSelectionVisible(); 2336 } 2337 } 2338 }; 2339 this.home.addSelectionListener(this.selectionListener); 2340 // Ensure observer camera is visible when its size, location or angles change 2341 this.home.getObserverCamera().addPropertyChangeListener(new PropertyChangeListener() { 2342 public void propertyChange(PropertyChangeEvent ev) { 2343 if (home.getSelectedItems().contains(ev.getSource())) { 2344 if (getView() != null) { 2345 getView().makeSelectionVisible(); 2346 } 2347 } 2348 } 2349 }); 2350 this.wallChangeListener = new PropertyChangeListener() { 2351 public void propertyChange(PropertyChangeEvent ev) { 2352 String propertyName = ev.getPropertyName(); 2353 if (Wall.Property.X_START.name().equals(propertyName) 2354 || Wall.Property.X_END.name().equals(propertyName) 2355 || Wall.Property.Y_START.name().equals(propertyName) 2356 || Wall.Property.Y_END.name().equals(propertyName) 2357 || Wall.Property.WALL_AT_START.name().equals(propertyName) 2358 || Wall.Property.WALL_AT_END.name().equals(propertyName) 2359 || Wall.Property.THICKNESS.name().equals(propertyName) 2360 || Wall.Property.ARC_EXTENT.name().equals(propertyName) 2361 || Wall.Property.LEVEL.name().equals(propertyName) 2362 || Wall.Property.HEIGHT.name().equals(propertyName) 2363 || Wall.Property.HEIGHT_AT_END.name().equals(propertyName) 2364 || Wall.Property.LEFT_SIDE_BASEBOARD.name().equals(propertyName) 2365 || Wall.Property.RIGHT_SIDE_BASEBOARD.name().equals(propertyName)) { 2366 resetAreaCache(); 2367 // Unselect unreachable wall 2368 Wall wall = (Wall)ev.getSource(); 2369 if (!wall.isAtLevel(home.getSelectedLevel())) { 2370 List<Selectable> selectedItems = new ArrayList<Selectable>(home.getSelectedItems()); 2371 if (selectedItems.remove(wall)) { 2372 selectItems(selectedItems, home.isAllLevelsSelection()); 2373 } 2374 } 2375 } 2376 } 2377 }; 2378 for (Wall wall : this.home.getWalls()) { 2379 wall.addPropertyChangeListener(this.wallChangeListener); 2380 } 2381 this.home.addWallsListener(new CollectionListener<Wall> () { 2382 public void collectionChanged(CollectionEvent<Wall> ev) { 2383 if (ev.getType() == CollectionEvent.Type.ADD) { 2384 ev.getItem().addPropertyChangeListener(wallChangeListener); 2385 } else if (ev.getType() == CollectionEvent.Type.DELETE) { 2386 ev.getItem().removePropertyChangeListener(wallChangeListener); 2387 } 2388 resetAreaCache(); 2389 } 2390 }); 2391 // Add listener to update furnitureBordersCache when walls change 2392 final PropertyChangeListener furnitureChangeListener = new PropertyChangeListener() { 2393 public void propertyChange(PropertyChangeEvent ev) { 2394 String propertyName = ev.getPropertyName(); 2395 if (HomePieceOfFurniture.Property.X.name().equals(propertyName) 2396 || HomePieceOfFurniture.Property.Y.name().equals(propertyName) 2397 || HomePieceOfFurniture.Property.WIDTH_IN_PLAN.name().equals(propertyName) 2398 || HomePieceOfFurniture.Property.DEPTH_IN_PLAN.name().equals(propertyName)) { 2399 furnitureSidesCache.remove((HomePieceOfFurniture)ev.getSource()); 2400 } 2401 } 2402 }; 2403 this.furnitureSizeChangeListener = new PropertyChangeListener() { 2404 public void propertyChange(PropertyChangeEvent ev) { 2405 HomePieceOfFurniture piece = (HomePieceOfFurniture)ev.getSource(); 2406 String propertyName = ev.getPropertyName(); 2407 if (HomePieceOfFurniture.Property.MODEL.name().equals(propertyName) 2408 || HomePieceOfFurniture.Property.MODEL_MIRRORED.name().equals(propertyName) 2409 || HomePieceOfFurniture.Property.MODEL_ROTATION.name().equals(propertyName) 2410 || HomePieceOfFurniture.Property.WIDTH.name().equals(propertyName) 2411 || HomePieceOfFurniture.Property.DEPTH.name().equals(propertyName) 2412 || HomePieceOfFurniture.Property.HEIGHT.name().equals(propertyName) 2413 || HomePieceOfFurniture.Property.ROLL.name().equals(propertyName) 2414 || HomePieceOfFurniture.Property.PITCH.name().equals(propertyName)) { 2415 // Update piece size in plan 2416 float [] size = getView().getPieceOfFurnitureSizeInPlan(piece); 2417 if (size != null) { 2418 piece.setWidthInPlan(size [0]); 2419 piece.setDepthInPlan(size [1]); 2420 piece.setHeightInPlan(size [2]); 2421 } else if (HomePieceOfFurniture.Property.WIDTH.name().equals(propertyName)) { 2422 // If the 2D view is unable to send the new piece size in the plan 2423 // the size change is considered to be applied proportionally 2424 float scale = piece.getWidth() / ((Number)ev.getOldValue()).floatValue(); 2425 piece.setWidthInPlan(scale * piece.getWidthInPlan()); 2426 piece.setDepthInPlan(scale * piece.getDepthInPlan()); 2427 piece.setHeightInPlan(scale * piece.getHeightInPlan()); 2428 } 2429 } 2430 } 2431 }; 2432 for (HomePieceOfFurniture piece : this.home.getFurniture()) { 2433 piece.addPropertyChangeListener(furnitureChangeListener); 2434 piece.addPropertyChangeListener(furnitureSizeChangeListener); 2435 if (piece instanceof HomeFurnitureGroup) { 2436 for (HomePieceOfFurniture childPiece : ((HomeFurnitureGroup)piece).getAllFurniture()) { 2437 childPiece.addPropertyChangeListener(furnitureSizeChangeListener); 2438 } 2439 } 2440 } 2441 this.home.addFurnitureListener(new CollectionListener<HomePieceOfFurniture> () { 2442 public void collectionChanged(CollectionEvent<HomePieceOfFurniture> ev) { 2443 HomePieceOfFurniture piece = ev.getItem(); 2444 if (ev.getType() == CollectionEvent.Type.ADD) { 2445 piece.addPropertyChangeListener(furnitureChangeListener); 2446 piece.addPropertyChangeListener(furnitureSizeChangeListener); 2447 if (piece instanceof HomeFurnitureGroup) { 2448 for (HomePieceOfFurniture childPiece : ((HomeFurnitureGroup)piece).getAllFurniture()) { 2449 childPiece.addPropertyChangeListener(furnitureSizeChangeListener); 2450 } 2451 } 2452 } else if (ev.getType() == CollectionEvent.Type.DELETE) { 2453 piece.removePropertyChangeListener(furnitureChangeListener); 2454 furnitureSidesCache.remove(piece); 2455 piece.removePropertyChangeListener(furnitureSizeChangeListener); 2456 if (piece instanceof HomeFurnitureGroup) { 2457 for (HomePieceOfFurniture childPiece : ((HomeFurnitureGroup)piece).getAllFurniture()) { 2458 childPiece.removePropertyChangeListener(furnitureSizeChangeListener); 2459 } 2460 } 2461 } 2462 } 2463 }); 2464 2465 this.home.addPropertyChangeListener(Home.Property.SELECTED_LEVEL, new PropertyChangeListener() { 2466 public void propertyChange(PropertyChangeEvent ev) { 2467 resetAreaCache(); 2468 } 2469 }); 2470 this.home.getObserverCamera().setFixedSize(home.getLevels().size() >= 2); 2471 this.home.addLevelsListener(new CollectionListener<Level>() { 2472 public void collectionChanged(CollectionEvent<Level> ev) { 2473 home.getObserverCamera().setFixedSize(home.getLevels().size() >= 2); 2474 } 2475 }); 2476 } 2477 2478 /** 2479 * Returns the selection listener add to the controlled home. 2480 */ getSelectionListener()2481 private SelectionListener getSelectionListener() { 2482 return this.selectionListener; 2483 } 2484 resetAreaCache()2485 private void resetAreaCache() { 2486 wallsAreaCache = null; 2487 wallsIncludingBaseboardsAreaCache = null; 2488 insideWallsAreaCache = null; 2489 roomPathsCache = null; 2490 } 2491 2492 /** 2493 * Displays in plan view the feedback of <code>draggedItems</code>, 2494 * during a drag and drop operation initiated from outside of plan view. 2495 */ startDraggedItems(List<Selectable> draggedItems, float x, float y)2496 public void startDraggedItems(List<Selectable> draggedItems, float x, float y) { 2497 this.draggedItems = draggedItems; 2498 // If magnetism is enabled, adjust furniture size and elevation 2499 if (this.preferences.isMagnetismEnabled()) { 2500 for (HomePieceOfFurniture piece : Home.getFurnitureSubList(draggedItems)) { 2501 if (piece.isResizable()) { 2502 // Roll and pitch angles of dragged items is always 0 2503 piece.setWidth(this.preferences.getLengthUnit().getMagnetizedLength(piece.getWidth(), 0.1f)); 2504 piece.setWidthInPlan(piece.getWidth()); 2505 piece.setDepth(this.preferences.getLengthUnit().getMagnetizedLength(piece.getDepth(), 0.1f)); 2506 piece.setDepthInPlan(piece.getDepth()); 2507 piece.setHeight(this.preferences.getLengthUnit().getMagnetizedLength(piece.getHeight(), 0.1f)); 2508 piece.setHeightInPlan(piece.getHeight()); 2509 } 2510 piece.setElevation(this.preferences.getLengthUnit().getMagnetizedLength(piece.getElevation(), 0.1f)); 2511 } 2512 } 2513 if (isModificationState()) { 2514 escape(); 2515 } 2516 setState(getDragAndDropState()); 2517 moveMouse(x, y); 2518 } 2519 2520 /** 2521 * Deletes in plan view the feedback of the dragged items. 2522 */ stopDraggedItems()2523 public void stopDraggedItems() { 2524 if (this.state != getDragAndDropState()) { 2525 throw new IllegalStateException("Controller isn't in a drag and drop state"); 2526 } 2527 this.draggedItems = null; 2528 setState(this.previousState); 2529 } 2530 2531 /** 2532 * Attempts to modify <code>piece</code> location depending of its context. 2533 * If the <code>piece</code> is a door or a window and the point (<code>x</code>, <code>y</code>) 2534 * belongs to a wall, the piece will be resized, rotated and moved so 2535 * its opening depth is equal to wall thickness and its angle matches wall direction. 2536 * If the <code>piece</code> isn't a door or a window and the point (<code>x</code>, <code>y</code>) 2537 * belongs to a wall, the piece will be rotated and moved so 2538 * its back face lies along the closest wall side and its angle matches wall direction. 2539 * If the <code>piece</code> isn't a door or a window, its bounding box is included in 2540 * the one of an other object and its elevation is equal to zero, it will be elevated 2541 * to appear on the top of the latter. 2542 */ adjustMagnetizedPieceOfFurniture(HomePieceOfFurniture piece, float x, float y)2543 protected void adjustMagnetizedPieceOfFurniture(HomePieceOfFurniture piece, float x, float y) { 2544 boolean pieceElevationAdjusted = adjustPieceOfFurnitureElevation(piece) != null; 2545 Wall magnetWall = adjustPieceOfFurnitureOnWallAt(piece, x, y, true); 2546 if (!pieceElevationAdjusted) { 2547 adjustPieceOfFurnitureSideBySideAt(piece, magnetWall == null, magnetWall); 2548 } 2549 } 2550 2551 /** 2552 * Attempts to move and resize <code>piece</code> depending on the wall under the 2553 * point (<code>x</code>, <code>y</code>) and returns that wall it it exists. 2554 * @see #adjustMagnetizedPieceOfFurniture(HomePieceOfFurniture, float, float) 2555 */ adjustPieceOfFurnitureOnWallAt(HomePieceOfFurniture piece, float x, float y, boolean forceOrientation)2556 private Wall adjustPieceOfFurnitureOnWallAt(HomePieceOfFurniture piece, 2557 float x, float y, boolean forceOrientation) { 2558 float margin = PIXEL_MARGIN / getScale(); 2559 Level selectedLevel = this.home.getSelectedLevel(); 2560 float [][] piecePoints = piece.getPoints(); 2561 2562 final boolean includeBaseboards = !piece.isDoorOrWindow() 2563 && piece.getElevation() == 0; 2564 Area wallsArea = getWallsArea(includeBaseboards); 2565 Collection<Wall> walls = this.home.getWalls(); 2566 2567 Wall referenceWall = null; 2568 Float referenceWallArcExtent = null; 2569 if (forceOrientation 2570 || !piece.isDoorOrWindow()) { 2571 // Search if point (x, y) is contained in home walls with no margin 2572 for (Wall wall : walls) { 2573 if (wall.isAtLevel(selectedLevel) 2574 && isLevelNullOrViewable(wall.getLevel()) 2575 && wall.containsPoint(x, y, includeBaseboards, 0) 2576 && wall.getStartPointToEndPointDistance() > 0) { 2577 referenceWall = getReferenceWall(wall, x, y); 2578 referenceWallArcExtent = wall.getArcExtent(); 2579 break; 2580 } 2581 } 2582 if (referenceWall == null) { 2583 // If not found search if point (x, y) is contained in home walls with a margin 2584 for (Wall wall : walls) { 2585 if (wall.isAtLevel(selectedLevel) 2586 && isLevelNullOrViewable(wall.getLevel()) 2587 && wall.containsPoint(x, y, includeBaseboards, 0) 2588 && wall.getStartPointToEndPointDistance() > 0) { 2589 referenceWall = getReferenceWall(wall, x, y); 2590 referenceWallArcExtent = wall.getArcExtent(); 2591 break; 2592 } 2593 } 2594 } 2595 } 2596 2597 if (referenceWall == null) { 2598 // Search if the border of a wall at floor level intersects with the given piece 2599 Area pieceAreaWithMargin = new Area(getRotatedRectangle( 2600 piece.getX() - piece.getWidthInPlan() / 2 - margin, piece.getY() - piece.getDepthInPlan() / 2 - margin, 2601 piece.getWidthInPlan() + 2 * margin, piece.getDepthInPlan() + 2 * margin, piece.getAngle())); 2602 float intersectionWithReferenceWallSurface = 0; 2603 for (Wall wall : walls) { 2604 if (wall.isAtLevel(selectedLevel) 2605 && isLevelNullOrViewable(wall.getLevel()) 2606 && wall.getStartPointToEndPointDistance() > 0) { 2607 float [][] wallPoints = wall.getPoints(includeBaseboards); 2608 Area wallAreaIntersection = new Area(getPath(wallPoints)); 2609 wallAreaIntersection.intersect(pieceAreaWithMargin); 2610 if (!wallAreaIntersection.isEmpty()) { 2611 float surface = getArea(wallAreaIntersection); 2612 if (surface > intersectionWithReferenceWallSurface) { 2613 intersectionWithReferenceWallSurface = surface; 2614 if (forceOrientation) { 2615 referenceWall = getReferenceWall(wall, x, y); 2616 referenceWallArcExtent = wall.getArcExtent(); 2617 } else { 2618 Rectangle2D intersectionBounds = wallAreaIntersection.getBounds2D(); 2619 referenceWall = getReferenceWall(wall, (float)intersectionBounds.getCenterX(), (float)intersectionBounds.getCenterY()); 2620 referenceWallArcExtent = wall.getArcExtent(); 2621 } 2622 } 2623 } 2624 } 2625 } 2626 } 2627 2628 if (referenceWall != null) { 2629 float xPiece = x; 2630 float yPiece = y; 2631 float pieceAngle = piece.getAngle(); 2632 float halfWidth = piece.getWidthInPlan() / 2; 2633 float halfDepth = piece.getDepthInPlan() / 2; 2634 double wallAngle = Math.atan2(referenceWall.getYEnd() - referenceWall.getYStart(), 2635 referenceWall.getXEnd() - referenceWall.getXStart()); 2636 float [][] wallPoints = referenceWall.getPoints(includeBaseboards); 2637 boolean magnetizedAtRight = wallAngle > -Math.PI / 2 && wallAngle <= Math.PI / 2; 2638 double cosWallAngle = Math.cos(wallAngle); 2639 double sinWallAngle = Math.sin(wallAngle); 2640 double distanceToLeftSide = wallPoints [0][0] != wallPoints [0][1] || wallPoints [1][0] != wallPoints [1][1] 2641 ? Line2D.ptLineDist(wallPoints [0][0], wallPoints [0][1], wallPoints [1][0], wallPoints [1][1], x, y) 2642 : Point2D.distance(wallPoints [0][0], wallPoints [0][1], x, y); 2643 double distanceToRightSide = wallPoints [2][0] != wallPoints [2][1] || wallPoints [3][0] != wallPoints [3][1] 2644 ? Line2D.ptLineDist(wallPoints [2][0], wallPoints [2][1], wallPoints [3][0], wallPoints [3][1], x, y) 2645 : Point2D.distance(wallPoints [2][0], wallPoints [2][1], x, y); 2646 boolean adjustOrientation = forceOrientation 2647 || piece.isDoorOrWindow() 2648 || referenceWall.containsPoint(x, y, includeBaseboards, margin); 2649 if (adjustOrientation) { 2650 double distanceToPieceLeftSide = Line2D.ptLineDist( 2651 piecePoints [0][0], piecePoints [0][1], piecePoints [3][0], piecePoints [3][1], x, y); 2652 double distanceToPieceRightSide = Line2D.ptLineDist( 2653 piecePoints [1][0], piecePoints [1][1], piecePoints [2][0], piecePoints [2][1], x, y); 2654 double distanceToPieceSide = pieceAngle > (3 * Math.PI / 2 + 1E-6) || pieceAngle < (Math.PI / 2 + 1E-6) 2655 ? distanceToPieceLeftSide 2656 : distanceToPieceRightSide; 2657 pieceAngle = (float)(distanceToRightSide < distanceToLeftSide 2658 ? wallAngle 2659 : wallAngle + Math.PI); 2660 2661 if (piece.isDoorOrWindow()) { 2662 final float thicknessEpsilon = 0.00075f; 2663 float wallDistance; 2664 if (referenceWallArcExtent == null 2665 || referenceWallArcExtent.floatValue() == 0) { 2666 wallDistance = thicknessEpsilon / 2; 2667 if (piece instanceof HomeDoorOrWindow) { 2668 HomeDoorOrWindow doorOrWindow = (HomeDoorOrWindow)piece; 2669 if (piece.isResizable() 2670 && isItemResizable(piece) 2671 && doorOrWindow.isWidthDepthDeformable() 2672 && doorOrWindow.getModelTransformations() == null) { 2673 // Doors and windows can't be rotated around horizontal axes 2674 piece.setDepth(thicknessEpsilon 2675 + referenceWall.getThickness() / doorOrWindow.getWallThickness()); 2676 // Need to set depth in plan because piece isn't added to home during initial drop 2677 piece.setDepthInPlan(piece.getDepth()); 2678 halfDepth = piece.getDepth() / 2; 2679 wallDistance += piece.getDepth() * doorOrWindow.getWallDistance(); 2680 } else { 2681 wallDistance += piece.getDepth() * (doorOrWindow.getWallDistance() + doorOrWindow.getWallThickness()) 2682 - referenceWall.getThickness(); 2683 } 2684 } 2685 } else { 2686 // Place the window in the middle of the round wall 2687 wallDistance = -referenceWall.getThickness() / 2; 2688 if (piece instanceof HomeDoorOrWindow) { 2689 // Place the window part in the middle of the round wall 2690 HomeDoorOrWindow doorOrWindow = (HomeDoorOrWindow)piece; 2691 wallDistance += piece.getDepth() * (doorOrWindow.getWallDistance() + doorOrWindow.getWallThickness() / 2); 2692 } 2693 } 2694 if (distanceToRightSide < distanceToLeftSide) { 2695 xPiece += sinWallAngle * ( (distanceToLeftSide + wallDistance) - halfDepth); 2696 yPiece += cosWallAngle * (-(distanceToLeftSide + wallDistance) + halfDepth); 2697 } else { 2698 xPiece += sinWallAngle * (-(distanceToRightSide + wallDistance) + halfDepth); 2699 yPiece += cosWallAngle * ( (distanceToRightSide + wallDistance) - halfDepth); 2700 } 2701 if (magnetizedAtRight) { 2702 xPiece += cosWallAngle * (halfWidth - distanceToPieceSide); 2703 yPiece += sinWallAngle * (halfWidth - distanceToPieceSide); 2704 } else { 2705 // Ensure adjusted window is at the right of the cursor 2706 xPiece += -cosWallAngle * (halfWidth - distanceToPieceSide); 2707 yPiece += -sinWallAngle * (halfWidth - distanceToPieceSide); 2708 } 2709 } else { 2710 if (distanceToRightSide < distanceToLeftSide) { 2711 int pointIndicator = Line2D.relativeCCW( 2712 wallPoints [2][0], wallPoints [2][1], wallPoints [3][0], wallPoints [3][1], x, y); 2713 xPiece += pointIndicator * sinWallAngle * distanceToRightSide - sinWallAngle * halfDepth; 2714 yPiece += -pointIndicator * cosWallAngle * distanceToRightSide + cosWallAngle * halfDepth; 2715 } else { 2716 int pointIndicator = Line2D.relativeCCW( 2717 wallPoints [0][0], wallPoints [0][1], wallPoints [1][0], wallPoints [1][1], x, y); 2718 xPiece += -pointIndicator * sinWallAngle * distanceToLeftSide + sinWallAngle * halfDepth; 2719 yPiece += pointIndicator * cosWallAngle * distanceToLeftSide - cosWallAngle * halfDepth; 2720 } 2721 if (magnetizedAtRight) { 2722 xPiece += cosWallAngle * (halfWidth - distanceToPieceSide); 2723 yPiece += sinWallAngle * (halfWidth - distanceToPieceSide); 2724 } else { 2725 // Ensure adjusted piece is at the right of the cursor 2726 xPiece += -cosWallAngle * (halfWidth - distanceToPieceSide); 2727 yPiece += -sinWallAngle * (halfWidth - distanceToPieceSide); 2728 } 2729 } 2730 } else { 2731 // Search the distance required to align piece on the left or right side of the reference wall 2732 Line2D centerLine = new Line2D.Float(referenceWall.getXStart(), referenceWall.getYStart(), 2733 referenceWall.getXEnd(), referenceWall.getYEnd()); 2734 Shape pieceBoundingBox = getRotatedRectangle(0, 0, piece.getWidthInPlan(), piece.getDepthInPlan(), (float)(pieceAngle - wallAngle)); 2735 double rotatedBoundingBoxDepth = pieceBoundingBox.getBounds2D().getHeight(); 2736 float relativeCCWToPieceCenterSignum = Math.signum(centerLine.relativeCCW(piece.getX(), piece.getY())); 2737 float relativeCCWToPointSignum = Math.signum(centerLine.relativeCCW(x, y)); 2738 double distance = relativeCCWToPieceCenterSignum 2739 * (-referenceWall.getThickness() / 2 + centerLine.ptLineDist(piece.getX(), piece.getY()) - rotatedBoundingBoxDepth / 2); 2740 if (includeBaseboards) { 2741 if (relativeCCWToPieceCenterSignum > 0 2742 && referenceWall.getLeftSideBaseboard() != null) { 2743 distance -= relativeCCWToPieceCenterSignum * referenceWall.getLeftSideBaseboard().getThickness(); 2744 } else if (relativeCCWToPieceCenterSignum < 0 2745 && referenceWall.getRightSideBaseboard() != null) { 2746 distance -= relativeCCWToPieceCenterSignum * referenceWall.getRightSideBaseboard().getThickness(); 2747 } 2748 } 2749 if (relativeCCWToPointSignum != relativeCCWToPieceCenterSignum) { 2750 distance -= relativeCCWToPointSignum * (rotatedBoundingBoxDepth + referenceWall.getThickness()); 2751 if (referenceWall.getLeftSideBaseboard() != null) { 2752 distance -= relativeCCWToPointSignum * referenceWall.getLeftSideBaseboard().getThickness(); 2753 } 2754 if (referenceWall.getRightSideBaseboard() != null) { 2755 distance -= relativeCCWToPointSignum * referenceWall.getRightSideBaseboard().getThickness(); 2756 } 2757 } 2758 xPiece = piece.getX() + (float)(-distance * sinWallAngle); 2759 yPiece = piece.getY() + (float)(distance * cosWallAngle); 2760 } 2761 2762 if (!piece.isDoorOrWindow() 2763 && (referenceWall.getArcExtent() == null // Ignore reoriented piece when (x, y) is inside a round wall 2764 || !adjustOrientation 2765 || Line2D.relativeCCW(referenceWall.getXStart(), referenceWall.getYStart(), 2766 referenceWall.getXEnd(), referenceWall.getYEnd(), x, y) > 0)) { 2767 // Search if piece intersects some other walls and avoid it intersects the closest one 2768 Area wallsAreaIntersection = new Area(wallsArea); 2769 Area adjustedPieceArea = new Area(getRotatedRectangle(xPiece - halfWidth, 2770 yPiece - halfDepth, piece.getWidthInPlan(), piece.getDepthInPlan(), pieceAngle)); 2771 wallsAreaIntersection.subtract(new Area(getPath(wallPoints))); 2772 wallsAreaIntersection.intersect(adjustedPieceArea); 2773 if (!wallsAreaIntersection.isEmpty()) { 2774 // Search the wall intersection path the closest to (x, y) 2775 GeneralPath closestWallIntersectionPath = getClosestPath(getAreaPaths(wallsAreaIntersection), x, y); 2776 if (closestWallIntersectionPath != null) { 2777 // In case the adjusted piece crosses a wall, search the area intersecting that wall 2778 // + other parts which crossed the wall (the farthest ones from (x,y)) 2779 adjustedPieceArea.subtract(wallsArea); 2780 if (adjustedPieceArea.isEmpty()) { 2781 return null; 2782 } else { 2783 List<GeneralPath> adjustedPieceAreaPaths = getAreaPaths(adjustedPieceArea); 2784 // Ignore too complex cases when the piece intersect many walls and is not parallel to a wall 2785 double angleDifference = (wallAngle - pieceAngle + 2 * Math.PI) % Math.PI; 2786 if (angleDifference < 1E-5 2787 || Math.PI - angleDifference < 1E-5 2788 || adjustedPieceAreaPaths.size() < 2) { 2789 GeneralPath adjustedPiecePathInArea = getClosestPath(adjustedPieceAreaPaths, x, y); 2790 Area adjustingArea = new Area(closestWallIntersectionPath); 2791 for (GeneralPath path : adjustedPieceAreaPaths) { 2792 if (path != adjustedPiecePathInArea) { 2793 adjustingArea.add(new Area(path)); 2794 } 2795 } 2796 AffineTransform rotation = AffineTransform.getRotateInstance(-wallAngle); 2797 Rectangle2D adjustingAreaBounds = adjustingArea.createTransformedArea(rotation).getBounds2D(); 2798 Rectangle2D adjustedPiecePathInAreaBounds = adjustedPiecePathInArea.createTransformedShape(rotation).getBounds2D(); 2799 if (!adjustingAreaBounds.contains(adjustedPiecePathInAreaBounds)) { 2800 double adjustLeftBorder = Math.signum(adjustedPiecePathInAreaBounds.getCenterX() - adjustingAreaBounds.getCenterX()); 2801 xPiece += adjustingAreaBounds.getWidth() * cosWallAngle * adjustLeftBorder; 2802 yPiece += adjustingAreaBounds.getWidth() * sinWallAngle * adjustLeftBorder; 2803 } 2804 } 2805 } 2806 } 2807 } 2808 } 2809 2810 piece.setAngle(pieceAngle); 2811 piece.setX(xPiece); 2812 piece.setY(yPiece); 2813 if (piece instanceof HomeDoorOrWindow) { 2814 ((HomeDoorOrWindow)piece).setBoundToWall(referenceWallArcExtent == null 2815 || referenceWallArcExtent.floatValue() == 0); 2816 } 2817 return referenceWall; 2818 } 2819 2820 return null; 2821 } 2822 2823 /** 2824 * Returns <code>true</code> is the given <code>level</code> is viewable. 2825 */ 2826 private boolean isLevelNullOrViewable(Level level) { 2827 return level == null || level.isViewable(); 2828 } 2829 2830 /** 2831 * Returns <code>wall</code> or a small wall part at the angle formed by the line joining wall center to 2832 * (<code>x</code>, <code>y</code>) point if the given <code>wall</code> is round. 2833 */ 2834 private Wall getReferenceWall(Wall wall, float x, float y) { 2835 Float arcExtent = wall.getArcExtent(); 2836 if (arcExtent == null || arcExtent.floatValue() == 0) { 2837 return wall; 2838 } else { 2839 double angle = Math.atan2(wall.getYArcCircleCenter() - y, x - wall.getXArcCircleCenter()); 2840 double radius = Point2D.distance(wall.getXArcCircleCenter(), wall.getYArcCircleCenter(), wall.getXStart(), wall.getYStart()); 2841 float epsilonAngle = 0.001f; 2842 Wall wallPart = new Wall((float)(wall.getXArcCircleCenter() + Math.cos(angle + epsilonAngle) * radius), 2843 (float)(wall.getYArcCircleCenter() - Math.sin(angle + epsilonAngle) * radius), 2844 (float)(wall.getXArcCircleCenter() + Math.cos(angle - epsilonAngle) * radius), 2845 (float)(wall.getYArcCircleCenter() - Math.sin(angle - epsilonAngle) * radius), wall.getThickness(), 0); 2846 wallPart.setLeftSideBaseboard(wall.getLeftSideBaseboard()); 2847 wallPart.setRightSideBaseboard(wall.getRightSideBaseboard()); 2848 return wallPart; 2849 } 2850 } 2851 2852 /** 2853 * Returns the closest path among <code>paths</code> ones to the given point. 2854 */ 2855 private GeneralPath getClosestPath(List<GeneralPath> paths, float x, float y) { 2856 GeneralPath closestPath = null; 2857 double closestPathDistance = Double.MAX_VALUE; 2858 for (GeneralPath path : paths) { 2859 float [][] pathPoints = getPathPoints(path, true); 2860 for (int i = 0; i < pathPoints.length; i++) { 2861 double distanceToPath = Line2D.ptSegDistSq(pathPoints [i][0], pathPoints [i][1], 2862 pathPoints [(i + 1) % pathPoints.length][0], pathPoints [(i + 1) % pathPoints.length][1], x, y); 2863 if (distanceToPath < closestPathDistance) { 2864 closestPathDistance = distanceToPath; 2865 closestPath = path; 2866 } 2867 } 2868 } 2869 return closestPath; 2870 } 2871 2872 /** 2873 * Returns the dimension lines that indicates how is placed a given <code>piece</code> 2874 * along a <code>wall</code>. 2875 */ 2876 private List<DimensionLine> getDimensionLinesAlongWall(HomePieceOfFurniture piece, Wall wall) { 2877 // Search the points on the wall side closest to piece 2878 float [][] piecePoints = piece.getPoints(); 2879 float angle = piece.getAngle(); 2880 float [][] wallPoints = wall.getPoints(); 2881 float [] pieceLeftPoint; 2882 float [] pieceRightPoint; 2883 float [] piecePoint = piece.isDoorOrWindow() 2884 ? piecePoints [3] // Front side point 2885 : piecePoints [0]; // Back side point 2886 if (Line2D.ptLineDistSq(wallPoints [0][0], wallPoints [0][1], 2887 wallPoints [1][0], wallPoints [1][1], 2888 piecePoint [0], piecePoint [1]) 2889 <= Line2D.ptLineDistSq(wallPoints [2][0], wallPoints [2][1], 2890 wallPoints [3][0], wallPoints [3][1], 2891 piecePoint [0], piecePoint [1])) { 2892 pieceLeftPoint = computeIntersection(wallPoints [0], wallPoints [1], piecePoints [0], piecePoints [3]); 2893 pieceRightPoint = computeIntersection(wallPoints [0], wallPoints [1], piecePoints [1], piecePoints [2]); 2894 } else { 2895 pieceLeftPoint = computeIntersection(wallPoints [2], wallPoints [3], piecePoints [0], piecePoints [3]); 2896 pieceRightPoint = computeIntersection(wallPoints [2], wallPoints [3], piecePoints [1], piecePoints [2]); 2897 } 2898 2899 List<DimensionLine> dimensionLines = new ArrayList<DimensionLine>(); 2900 float [] wallEndPointJoinedToPieceLeftPoint = null; 2901 float [] wallEndPointJoinedToPieceRightPoint = null; 2902 // Search among room paths which segment includes pieceLeftPoint and pieceRightPoint 2903 List<GeneralPath> roomPaths = getRoomPathsFromWalls(); 2904 for (int i = 0; 2905 i < roomPaths.size() 2906 && wallEndPointJoinedToPieceLeftPoint == null 2907 && wallEndPointJoinedToPieceRightPoint == null; i++) { 2908 float [][] roomPoints = getPathPoints(roomPaths.get(i), true); 2909 for (int j = 0; j < roomPoints.length; j++) { 2910 float [] startPoint = roomPoints [j]; 2911 float [] endPoint = roomPoints [(j + 1) % roomPoints.length]; 2912 float deltaX = endPoint [0] - startPoint [0]; 2913 float deltaY = endPoint [1] - startPoint [1]; 2914 double segmentAngle = Math.abs(deltaX) < 1E-5 2915 ? Math.PI / 2 2916 : (Math.abs(deltaY) < 1E-5 2917 ? 0 2918 : Math.atan2(deltaY, deltaX)); 2919 // If segment and piece are parallel 2920 double angleDifference = (segmentAngle - angle + 2 * Math.PI) % Math.PI; 2921 if (angleDifference < 1E-5 || Math.PI - angleDifference < 1E-5) { 2922 boolean segmentContainsLeftPoint = Line2D.ptSegDistSq(startPoint [0], startPoint [1], 2923 endPoint [0], endPoint [1], pieceLeftPoint [0], pieceLeftPoint [1]) < 0.0001; 2924 boolean segmentContainsRightPoint = Line2D.ptSegDistSq(startPoint [0], startPoint [1], 2925 endPoint [0], endPoint [1], pieceRightPoint [0], pieceRightPoint [1]) < 0.0001; 2926 if (segmentContainsLeftPoint || segmentContainsRightPoint) { 2927 if (segmentContainsLeftPoint) { 2928 // Compute distances to segment start point 2929 double startPointToLeftPointDistance = Point2D.distanceSq(startPoint [0], startPoint [1], 2930 pieceLeftPoint [0], pieceLeftPoint [1]); 2931 double startPointToRightPointDistance = Point2D.distanceSq(startPoint [0], startPoint [1], 2932 pieceRightPoint [0], pieceRightPoint [1]); 2933 if (startPointToLeftPointDistance < startPointToRightPointDistance 2934 || !segmentContainsRightPoint) { 2935 wallEndPointJoinedToPieceLeftPoint = startPoint.clone(); 2936 } else { 2937 wallEndPointJoinedToPieceLeftPoint = endPoint.clone(); 2938 } 2939 } 2940 if (segmentContainsRightPoint) { 2941 // Compute distances to segment start point 2942 double endPointToLeftPointDistance = Point2D.distanceSq(endPoint [0], endPoint [1], 2943 pieceLeftPoint [0], pieceLeftPoint [1]); 2944 double endPointToRightPointDistance = Point2D.distanceSq(endPoint [0], endPoint [1], 2945 pieceRightPoint [0], pieceRightPoint [1]); 2946 if (endPointToLeftPointDistance < endPointToRightPointDistance 2947 && segmentContainsLeftPoint) { 2948 wallEndPointJoinedToPieceRightPoint = startPoint.clone(); 2949 } else { 2950 wallEndPointJoinedToPieceRightPoint = endPoint.clone(); 2951 } 2952 } 2953 break; 2954 } 2955 } 2956 } 2957 } 2958 2959 boolean pieceFrontSideAlongWallSide = !piece.isDoorOrWindow() 2960 && Line2D.ptLineDistSq(wall.getXStart(), wall.getYStart(), wall.getXEnd(), wall.getYEnd(), piecePoint [0], piecePoint [1]) 2961 > Line2D.ptLineDistSq(wall.getXStart(), wall.getYStart(), wall.getXEnd(), wall.getYEnd(), piecePoints [3][0], piecePoints [3][1]); 2962 if (wallEndPointJoinedToPieceLeftPoint != null) { 2963 float offset; 2964 if (pieceFrontSideAlongWallSide) { 2965 offset = -(float)Point2D.distance(pieceLeftPoint [0], pieceLeftPoint [1], 2966 piecePoints [0][0], piecePoints [0][1]) - 10 / getView().getScale(); 2967 } else { 2968 offset = (float)Point2D.distance(pieceLeftPoint [0], pieceLeftPoint [1], 2969 piecePoints [3][0], piecePoints [3][1]) + 10 / getView().getScale(); 2970 } 2971 dimensionLines.add(getDimensionLineBetweenPoints(wallEndPointJoinedToPieceLeftPoint, pieceLeftPoint, offset, 0, false)); 2972 } 2973 if (wallEndPointJoinedToPieceRightPoint != null) { 2974 float offset; 2975 if (pieceFrontSideAlongWallSide) { 2976 offset = -(float)Point2D.distance(pieceRightPoint [0], pieceRightPoint [1], 2977 piecePoints [1][0], piecePoints [1][1]) - 10 / getView().getScale(); 2978 } else { 2979 offset = (float)Point2D.distance(pieceRightPoint [0], pieceRightPoint [1], 2980 piecePoints [2][0], piecePoints [2][1]) + 10 / getView().getScale(); 2981 } 2982 dimensionLines.add(getDimensionLineBetweenPoints(pieceRightPoint, wallEndPointJoinedToPieceRightPoint, offset, 0, false)); 2983 } 2984 for (int i = dimensionLines.size() - 1; i >= 0; i--) { 2985 if (dimensionLines.get(i).getLength() < 0.01f) { 2986 dimensionLines.remove(i); 2987 } 2988 } 2989 return dimensionLines; 2990 } 2991 2992 /** 2993 * Returns the intersection point between the lines defined by the points 2994 * (<code>point1</code>, <code>point2</code>) and (<code>point3</code>, <code>pont4</code>). 2995 */ 2996 private static float [] computeIntersection(float [] point1, float [] point2, float [] point3, float [] point4) { 2997 return computeIntersection(point1 [0], point1 [1], point2 [0], point2 [1], 2998 point3 [0], point3 [1], point4 [0], point4 [1]); 2999 } 3000 3001 /** 3002 * Returns the intersection point between the line joining the first two points and 3003 * the line joining the two last points. 3004 */ 3005 static float [] computeIntersection(float xPoint1, float yPoint1, float xPoint2, float yPoint2, 3006 float xPoint3, float yPoint3, float xPoint4, float yPoint4) { 3007 float x = xPoint2; 3008 float y = yPoint2; 3009 float alpha1 = (yPoint2 - yPoint1) / (xPoint2 - xPoint1); 3010 float alpha2 = (yPoint4 - yPoint3) / (xPoint4 - xPoint3); 3011 // If the two lines are not parallel 3012 if (alpha1 != alpha2) { 3013 // If first line is vertical 3014 if (Math.abs(alpha1) > 4000) { 3015 if (Math.abs(alpha2) < 4000) { 3016 x = xPoint1; 3017 float beta2 = yPoint4 - alpha2 * xPoint4; 3018 y = alpha2 * x + beta2; 3019 } 3020 // If second line is vertical 3021 } else if (Math.abs(alpha2) > 4000) { 3022 if (Math.abs(alpha1) < 4000) { 3023 x = xPoint3; 3024 float beta1 = yPoint2 - alpha1 * xPoint2; 3025 y = alpha1 * x + beta1; 3026 } 3027 } else { 3028 boolean sameSignum = Math.signum(alpha1) == Math.signum(alpha2); 3029 if (Math.abs(alpha1 - alpha2) > 1E-5 3030 && (!sameSignum || (Math.abs(alpha1) > Math.abs(alpha2) ? alpha1 / alpha2 : alpha2 / alpha1) > 1.004)) { 3031 float beta1 = yPoint2 - alpha1 * xPoint2; 3032 float beta2 = yPoint4 - alpha2 * xPoint4; 3033 x = (beta2 - beta1) / (alpha1 - alpha2); 3034 y = alpha1 * x + beta1; 3035 } 3036 } 3037 } 3038 return new float [] {x, y}; 3039 } 3040 3041 /** 3042 * Attempts to elevate <code>piece</code> depending on the highest piece that includes 3043 * its bounding box and returns that piece. 3044 * @see #adjustMagnetizedPieceOfFurniture(HomePieceOfFurniture, float, float) 3045 */ 3046 private HomePieceOfFurniture adjustPieceOfFurnitureElevation(HomePieceOfFurniture piece) { 3047 if (!piece.isDoorOrWindow() 3048 && piece.getElevation() == 0) { 3049 // Search if another piece at floor level contains the given piece to elevate it at its height 3050 HomePieceOfFurniture highestSurroundingPiece = getHighestSurroundingPieceOfFurniture(piece); 3051 if (highestSurroundingPiece != null) { 3052 float elevation = highestSurroundingPiece.getElevation(); 3053 if (highestSurroundingPiece.isHorizontallyRotated()) { 3054 elevation += highestSurroundingPiece.getHeightInPlan(); 3055 } else { 3056 elevation += highestSurroundingPiece.getHeight() * highestSurroundingPiece.getDropOnTopElevation(); 3057 } 3058 if (highestSurroundingPiece.getLevel() != null) { 3059 elevation += highestSurroundingPiece.getLevel().getElevation() 3060 - (piece.getLevel() != null 3061 ? piece.getLevel().getElevation() 3062 : this.home.getSelectedLevel().getElevation()); 3063 } 3064 piece.setElevation(Math.max(0, elevation)); 3065 return highestSurroundingPiece; 3066 } 3067 } 3068 return null; 3069 } 3070 3071 /** 3072 * Attempts to align <code>piece</code> on the borders of home furniture at the the same elevation 3073 * that intersect with it and returns that piece. 3074 * @see #adjustMagnetizedPieceOfFurniture(HomePieceOfFurniture, float, float) 3075 */ 3076 private HomePieceOfFurniture adjustPieceOfFurnitureSideBySideAt(HomePieceOfFurniture piece, 3077 boolean forceOrientation, 3078 Wall magnetWall) { 3079 float [][] piecePoints = piece.getPoints(); 3080 Area pieceArea = new Area(getPath(piecePoints)); 3081 boolean doorOrWindowBoundToWall = piece instanceof HomeDoorOrWindow 3082 && ((HomeDoorOrWindow)piece).isBoundToWall(); 3083 3084 // Search if the border of another piece at floor level intersects with the given piece 3085 float pieceElevation = piece.getGroundElevation(); 3086 float margin = 2 * PIXEL_MARGIN / getScale(); 3087 HomePieceOfFurniture referencePiece = null; 3088 Area intersectionWithReferencePieceArea = null; 3089 float intersectionWithReferencePieceSurface = 0; 3090 float [][] referencePiecePoints = null; 3091 for (HomePieceOfFurniture homePiece : this.home.getFurniture()) { 3092 float homePieceElevation = homePiece.getGroundElevation(); 3093 if (homePiece != piece 3094 && isPieceOfFurnitureVisibleAtSelectedLevel(homePiece) 3095 && pieceElevation < homePieceElevation + homePiece.getHeightInPlan() 3096 && pieceElevation + piece.getHeightInPlan() > homePieceElevation 3097 && (!doorOrWindowBoundToWall // Ignore other furniture for doors and windows bound to a wall 3098 || homePiece.isDoorOrWindow())) { 3099 float [][] points = homePiece.getPoints(); 3100 Area marginArea; 3101 if (doorOrWindowBoundToWall && homePiece.isDoorOrWindow()) { 3102 marginArea = new Area(getPath(new Wall( 3103 points [1][0], points [1][1], points [2][0], points [2][1], margin, 0).getPoints())); 3104 marginArea.add(new Area(getPath(new Wall( 3105 points [3][0], points [3][1], points [0][0], points [0][1], margin, 0).getPoints()))); 3106 } else { 3107 // Build an area matching piece contour with a thickness equal to margin using walls instances 3108 marginArea = this.furnitureSidesCache.get(homePiece); 3109 if (marginArea == null) { 3110 Wall [] pieceSideWalls = new Wall [points.length]; 3111 for (int i = 0; i < pieceSideWalls.length; i++) { 3112 pieceSideWalls [i] = new Wall(points [i][0], points [i][1], 3113 points [(i + 1) % pieceSideWalls.length][0], points [(i + 1) % pieceSideWalls.length][1], margin, 0); 3114 } 3115 for (int i = 0; i < pieceSideWalls.length; i++) { 3116 pieceSideWalls [(i + 1) % pieceSideWalls.length].setWallAtStart(pieceSideWalls [i]); 3117 pieceSideWalls [i].setWallAtEnd(pieceSideWalls [(i + 1) % pieceSideWalls.length]); 3118 } 3119 float [][] pieceSidePoints = new float [pieceSideWalls.length * 2 + 2][]; 3120 float [][] pieceSideWallPoints = null; 3121 for (int i = 0; i < pieceSideWalls.length; i++) { 3122 pieceSideWallPoints = pieceSideWalls [i].getPoints(); 3123 pieceSidePoints [i] = pieceSideWallPoints [0]; 3124 pieceSidePoints [pieceSidePoints.length - i - 1] = pieceSideWallPoints [3]; 3125 } 3126 pieceSidePoints [pieceSidePoints.length / 2 - 1] = pieceSideWallPoints [1]; 3127 pieceSidePoints [pieceSidePoints.length / 2] = pieceSideWallPoints [2]; 3128 marginArea = new Area(getPath(pieceSidePoints)); 3129 this.furnitureSidesCache.put(homePiece, marginArea); 3130 } 3131 } 3132 Area intersection = new Area(marginArea); 3133 intersection.intersect(pieceArea); 3134 if (!intersection.isEmpty()) { 3135 Area exclusiveOr = new Area(pieceArea); 3136 exclusiveOr.exclusiveOr(intersection); 3137 if (exclusiveOr.isSingular()) { 3138 Area insideArea = new Area(getPath(points)); 3139 insideArea.subtract(marginArea); 3140 insideArea.intersect(pieceArea); 3141 if (insideArea.isEmpty()) { 3142 float surface = getArea(intersection); 3143 if (surface > intersectionWithReferencePieceSurface) { 3144 intersectionWithReferencePieceSurface = surface; 3145 referencePiece = homePiece; 3146 referencePiecePoints = points; 3147 intersectionWithReferencePieceArea = intersection; 3148 } 3149 } 3150 } 3151 } 3152 } 3153 } 3154 3155 if (referencePiece != null) { 3156 boolean alignedOnReferencePieceFrontOrBackSide; 3157 if (doorOrWindowBoundToWall && referencePiece.isDoorOrWindow()) { 3158 alignedOnReferencePieceFrontOrBackSide = false; 3159 } else { 3160 GeneralPath referencePieceLargerBoundingBox = getRotatedRectangle(referencePiece.getX() - referencePiece.getWidthInPlan(), 3161 referencePiece.getY() - referencePiece.getDepthInPlan(), referencePiece.getWidthInPlan() * 2, referencePiece.getDepthInPlan() * 2, 3162 referencePiece.getAngle()); 3163 float [][] pathPoints = getPathPoints(referencePieceLargerBoundingBox, false); 3164 alignedOnReferencePieceFrontOrBackSide = isAreaLargerOnFrontOrBackSide(intersectionWithReferencePieceArea, pathPoints); 3165 } 3166 if (forceOrientation) { 3167 piece.setAngle(referencePiece.getAngle()); 3168 } 3169 Shape pieceBoundingBox = getRotatedRectangle(0, 0, piece.getWidthInPlan(), piece.getDepthInPlan(), piece.getAngle() - referencePiece.getAngle()); 3170 float deltaX = 0; 3171 float deltaY = 0; 3172 if (!alignedOnReferencePieceFrontOrBackSide) { 3173 // Search the distance required to align piece on the left or right side of the reference piece 3174 Line2D centerLine = new Line2D.Float(referencePiece.getX(), referencePiece.getY(), 3175 (referencePiecePoints [0][0] + referencePiecePoints [1][0]) / 2, (referencePiecePoints [0][1] + referencePiecePoints [1][1]) / 2); 3176 double rotatedBoundingBoxWidth = pieceBoundingBox.getBounds2D().getWidth(); 3177 double distance = centerLine.relativeCCW(piece.getX(), piece.getY()) 3178 * (-referencePiece.getWidthInPlan() / 2 + centerLine.ptLineDist(piece.getX(), piece.getY()) - rotatedBoundingBoxWidth / 2); 3179 deltaX = (float)(distance * Math.cos(referencePiece.getAngle())); 3180 deltaY = (float)(distance * Math.sin(referencePiece.getAngle())); 3181 } else { 3182 // Search the distance required to align piece on the front or back side of the reference piece 3183 Line2D centerLine = new Line2D.Float(referencePiece.getX(), referencePiece.getY(), 3184 (referencePiecePoints [2][0] + referencePiecePoints [1][0]) / 2, (referencePiecePoints [2][1] + referencePiecePoints [1][1]) / 2); 3185 double rotatedBoundingBoxDepth = pieceBoundingBox.getBounds2D().getHeight(); 3186 double distance = centerLine.relativeCCW(piece.getX(), piece.getY()) 3187 * (-referencePiece.getDepthInPlan() / 2 + centerLine.ptLineDist(piece.getX(), piece.getY()) - rotatedBoundingBoxDepth / 2); 3188 deltaX = (float)(-distance * Math.sin(referencePiece.getAngle())); 3189 deltaY = (float)(distance * Math.cos(referencePiece.getAngle())); 3190 if (!isIntersectionEmpty(piece, magnetWall, deltaX, deltaY)) { 3191 deltaX = deltaY = 0; 3192 } 3193 } 3194 3195 // Accept move only if reference piece and moved piece share some points 3196 if (!isIntersectionEmpty(piece, referencePiece, deltaX, deltaY)) { 3197 piece.move(deltaX, deltaY); 3198 return referencePiece; 3199 } else { 3200 if (forceOrientation) { 3201 // Update points array 3202 piecePoints = piece.getPoints(); 3203 } 3204 boolean alignedOnPieceFrontOrBackSide = isAreaLargerOnFrontOrBackSide(intersectionWithReferencePieceArea, piecePoints); 3205 Shape referencePieceBoundingBox = getRotatedRectangle(0, 0, referencePiece.getWidthInPlan(), referencePiece.getDepthInPlan(), 3206 referencePiece.getAngle() - piece.getAngle()); 3207 if (!alignedOnPieceFrontOrBackSide) { 3208 // Search the distance required to align piece on its left or right side 3209 Line2D centerLine = new Line2D.Float(piece.getX(), piece.getY(), 3210 (piecePoints [0][0] + piecePoints [1][0]) / 2, (piecePoints [0][1] + piecePoints [1][1]) / 2); 3211 double rotatedBoundingBoxWidth = referencePieceBoundingBox.getBounds2D().getWidth(); 3212 double distance = centerLine.relativeCCW(referencePiece.getX(), referencePiece.getY()) 3213 * (-piece.getWidthInPlan() / 2 + centerLine.ptLineDist(referencePiece.getX(), referencePiece.getY()) - rotatedBoundingBoxWidth / 2); 3214 deltaX = -(float)(distance * Math.cos(piece.getAngle())); 3215 deltaY = -(float)(distance * Math.sin(piece.getAngle())); 3216 } else { 3217 // Search the distance required to align piece on its front or back side 3218 Line2D centerLine = new Line2D.Float(piece.getX(), piece.getY(), 3219 (piecePoints [2][0] + piecePoints [1][0]) / 2, (piecePoints [2][1] + piecePoints [1][1]) / 2); 3220 double rotatedBoundingBoxDepth = referencePieceBoundingBox.getBounds2D().getHeight(); 3221 double distance = centerLine.relativeCCW(referencePiece.getX(), referencePiece.getY()) 3222 * (-piece.getDepthInPlan() / 2 + centerLine.ptLineDist(referencePiece.getX(), referencePiece.getY()) - rotatedBoundingBoxDepth / 2); 3223 deltaX = -(float)(-distance * Math.sin(piece.getAngle())); 3224 deltaY = -(float)(distance * Math.cos(piece.getAngle())); 3225 if (!isIntersectionEmpty(piece, magnetWall, deltaX, deltaY)) { 3226 deltaX = deltaY = 0; 3227 } 3228 } 3229 3230 // Accept move only if reference piece and moved piece share some points 3231 if (!isIntersectionEmpty(piece, referencePiece, deltaX, deltaY)) { 3232 piece.move(deltaX, deltaY); 3233 return referencePiece; 3234 } 3235 } 3236 return referencePiece; 3237 } 3238 return null; 3239 } 3240 3241 /** 3242 * Returns <code>true</code> if the intersection between the given <code>area</code> and 3243 * the front or back sides of the rectangle defined by <code>piecePoints</code> is larger 3244 * than with the left and right sides of this rectangle. 3245 */ 3246 private boolean isAreaLargerOnFrontOrBackSide(Area area, float [][] piecePoints) { 3247 GeneralPath pieceFrontAndBackQuarters = new GeneralPath(); 3248 pieceFrontAndBackQuarters.moveTo(piecePoints [0][0], piecePoints [0][1]); 3249 pieceFrontAndBackQuarters.lineTo(piecePoints [2][0], piecePoints [2][1]); 3250 pieceFrontAndBackQuarters.lineTo(piecePoints [3][0], piecePoints [3][1]); 3251 pieceFrontAndBackQuarters.lineTo(piecePoints [1][0], piecePoints [1][1]); 3252 pieceFrontAndBackQuarters.closePath(); 3253 Area intersectionWithFrontOrBack = new Area(area); 3254 intersectionWithFrontOrBack.intersect(new Area(pieceFrontAndBackQuarters)); 3255 if (intersectionWithFrontOrBack.isEmpty()) { 3256 return false; 3257 } else { 3258 GeneralPath pieceLeftAndRightQuarters = new GeneralPath(); 3259 pieceLeftAndRightQuarters.moveTo(piecePoints [0][0], piecePoints [0][1]); 3260 pieceLeftAndRightQuarters.lineTo(piecePoints [2][0], piecePoints [2][1]); 3261 pieceLeftAndRightQuarters.lineTo(piecePoints [1][0], piecePoints [1][1]); 3262 pieceLeftAndRightQuarters.lineTo(piecePoints [3][0], piecePoints [3][1]); 3263 pieceLeftAndRightQuarters.closePath(); 3264 Area intersectionWithLeftAndRight = new Area(area); 3265 intersectionWithLeftAndRight.intersect(new Area(pieceLeftAndRightQuarters)); 3266 return getArea(intersectionWithFrontOrBack) > getArea(intersectionWithLeftAndRight); 3267 } 3268 } 3269 3270 /** 3271 * Returns the area of the given shape. 3272 */ 3273 private float getArea(Area area) { 3274 float [][] pathPoints = getPathPoints(getPath(area), false); 3275 if (pathPoints.length > 1) { 3276 return new Room(pathPoints).getArea(); 3277 } else { 3278 return 0; 3279 } 3280 } 3281 3282 /** 3283 * Returns <code>true</code> if the given pieces don't intersect once the first is moved from 3284 * (<code>deltaX</code>, <code>deltaY</code>) vector. 3285 */ 3286 private boolean isIntersectionEmpty(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2, 3287 float deltaX, float deltaY) { 3288 Area intersection = new Area(getRotatedRectangle(piece1.getX() - piece1.getWidthInPlan() / 2 + deltaX, 3289 piece1.getY() - piece1.getDepthInPlan() / 2 + deltaY, piece1.getWidthInPlan(), piece1.getDepth(), piece1.getAngle())); 3290 float epsilon = 0.01f; 3291 intersection.intersect(new Area(getRotatedRectangle(piece2.getX() - piece2.getWidthInPlan() / 2 - epsilon, 3292 piece2.getY() - piece2.getDepthInPlan() / 2 - epsilon, 3293 piece2.getWidthInPlan() + 2 * epsilon, piece2.getDepthInPlan() + 2 * epsilon, piece2.getAngle()))); 3294 return intersection.isEmpty(); 3295 } 3296 3297 /** 3298 * Returns <code>true</code> if the given area and wall don't intersect once the area is moved from 3299 * (<code>deltaX</code>, <code>deltaY</code>) vector. 3300 */ 3301 private boolean isIntersectionEmpty(HomePieceOfFurniture piece, Wall wall, 3302 float deltaX, float deltaY) { 3303 if (wall != null) { 3304 Area wallAreaIntersection = new Area(getPath(wall.getPoints())); 3305 wallAreaIntersection.intersect(new Area(getRotatedRectangle(piece.getX() - piece.getWidthInPlan() / 2 + deltaX, 3306 piece.getY() - piece.getDepthInPlan() / 2 + deltaY, piece.getWidthInPlan(), piece.getDepthInPlan(), piece.getAngle()))); 3307 return getArea(wallAreaIntersection) < 1E-4f; 3308 } 3309 return true; 3310 } 3311 3312 /** 3313 * Returns the shape of the given rectangle rotated of a given <code>angle</code>. 3314 */ 3315 private GeneralPath getRotatedRectangle(float x, float y, float width, float height, float angle) { 3316 Rectangle2D referencePieceLargerBoundingBox = new Rectangle2D.Float(x, y, width, height); 3317 AffineTransform rotation = AffineTransform.getRotateInstance(angle, x + width / 2, y + height / 2); 3318 GeneralPath rotatedBoundingBox = new GeneralPath(); 3319 rotatedBoundingBox.append(referencePieceLargerBoundingBox.getPathIterator(rotation), false); 3320 return rotatedBoundingBox; 3321 } 3322 3323 /** 3324 * Returns the dimension line that measures the side of a piece, the length of a room side 3325 * or the length of a wall side at (<code>x</code>, <code>y</code>) point, 3326 * or <code>null</code> if it doesn't exist. 3327 */ 3328 private DimensionLine getMeasuringDimensionLineAt(float x, float y, 3329 boolean magnetismEnabled) { 3330 float margin = getSelectionMargin(); 3331 for (HomePieceOfFurniture piece : this.home.getFurniture()) { 3332 if (isPieceOfFurnitureVisibleAtSelectedLevel(piece)) { 3333 DimensionLine dimensionLine = getDimensionLineBetweenPointsAt(piece.getPoints(), x, y, margin, magnetismEnabled); 3334 if (dimensionLine != null) { 3335 return dimensionLine; 3336 } 3337 } 3338 } 3339 for (GeneralPath roomPath : getRoomPathsFromWalls()) { 3340 if (roomPath.intersects(x - margin, y - margin, 2 * margin, 2 * margin)) { 3341 DimensionLine dimensionLine = getDimensionLineBetweenPointsAt( 3342 getPathPoints(roomPath, true), x, y, margin, magnetismEnabled); 3343 if (dimensionLine != null) { 3344 return dimensionLine; 3345 } 3346 } 3347 } 3348 for (Room room : this.home.getRooms()) { 3349 if (isLevelNullOrViewable(room.getLevel()) 3350 && room.isAtLevel(this.home.getSelectedLevel())) { 3351 DimensionLine dimensionLine = getDimensionLineBetweenPointsAt(room.getPoints(), x, y, margin, magnetismEnabled); 3352 if (dimensionLine != null) { 3353 return dimensionLine; 3354 } 3355 } 3356 } 3357 return null; 3358 } 3359 3360 /** 3361 * Returns the dimension line that measures the side of the given polygon at (<code>x</code>, <code>y</code>) point, 3362 * or <code>null</code> if it doesn't exist. 3363 */ 3364 private DimensionLine getDimensionLineBetweenPointsAt(float [][] points, float x, float y, 3365 float margin, boolean magnetismEnabled) { 3366 for (int i = 0; i < points.length; i++) { 3367 int nextPointIndex = (i + 1) % points.length; 3368 // Ignore sides with a length smaller than 0.1 cm 3369 double distanceBetweenPointsSq = Point2D.distanceSq(points [i][0], points [i][1], 3370 points [nextPointIndex][0], points [nextPointIndex][1]); 3371 if (distanceBetweenPointsSq > 0.01 3372 && Line2D.ptSegDistSq(points [i][0], points [i][1], 3373 points [nextPointIndex][0], points [nextPointIndex][1], 3374 x, y) <= margin * margin) { 3375 return getDimensionLineBetweenPoints(points [i], points [nextPointIndex], 0, distanceBetweenPointsSq, magnetismEnabled); 3376 } 3377 } 3378 return null; 3379 } 3380 3381 /** 3382 * Returns the dimension line between the given points. 3383 */ 3384 private DimensionLine getDimensionLineBetweenPoints(float [] point1, float [] point2, float offset, 3385 boolean magnetismEnabled) { 3386 return getDimensionLineBetweenPoints(point1, point2, offset, 3387 Point2D.distanceSq(point1 [0], point1 [1], point2 [0], point2 [1]), magnetismEnabled); 3388 } 3389 3390 /** 3391 * Returns the dimension line between the given points. 3392 */ 3393 private DimensionLine getDimensionLineBetweenPoints(float [] point1, float [] point2, float offset, 3394 double distanceBetweenPointsSq, 3395 boolean magnetismEnabled) { 3396 double angle = Math.atan2(point1 [1] - point2 [1], point2 [0] - point1 [0]); 3397 boolean reverse = angle <= -Math.PI / 2 || angle > Math.PI / 2; 3398 float xStart; 3399 float yStart; 3400 float xEnd; 3401 float yEnd; 3402 if (reverse) { 3403 // Avoid reversed text on the dimension line 3404 xStart = point2 [0]; 3405 yStart = point2 [1]; 3406 xEnd = point1 [0]; 3407 yEnd = point1 [1]; 3408 offset = -offset; 3409 } else { 3410 xStart = point1 [0]; 3411 yStart = point1 [1]; 3412 xEnd = point2 [0]; 3413 yEnd = point2 [1]; 3414 } 3415 3416 if (magnetismEnabled) { 3417 float magnetizedLength = this.preferences.getLengthUnit().getMagnetizedLength( 3418 (float)Math.sqrt(distanceBetweenPointsSq), getView().getPixelLength()); 3419 if (reverse) { 3420 xEnd = point2 [0] - (float)(magnetizedLength * Math.cos(angle)); 3421 yEnd = point2 [1] + (float)(magnetizedLength * Math.sin(angle)); 3422 } else { 3423 xEnd = point1 [0] + (float)(magnetizedLength * Math.cos(angle)); 3424 yEnd = point1 [1] - (float)(magnetizedLength * Math.sin(angle)); 3425 } 3426 } 3427 return new DimensionLine(xStart, yStart, xEnd, yEnd, offset); 3428 } 3429 3430 /** 3431 * Controls the creation of a new level. 3432 */ 3433 public void addLevel() { 3434 addLevel(false); 3435 } 3436 3437 /** 3438 * Controls the creation of a new level at same elevation. 3439 */ 3440 public void addLevelAtSameElevation() { 3441 addLevel(true); 3442 } 3443 3444 /** 3445 * Controls the creation of a level. 3446 */ 3447 private void addLevel(boolean sameElevation) { 3448 final boolean allLevelsSelection = this.home.isAllLevelsSelection(); 3449 List<Selectable> oldSelectedItems = this.home.getSelectedItems(); 3450 final Selectable [] oldSelection = 3451 oldSelectedItems.toArray(new Selectable [oldSelectedItems.size()]); 3452 final Level oldSelectedLevel = this.home.getSelectedLevel(); 3453 final BackgroundImage homeBackgroundImage = this.home.getBackgroundImage(); 3454 List<Level> levels = this.home.getLevels(); 3455 float newWallHeight = this.preferences.getNewWallHeight(); 3456 float newFloorThickness = this.preferences.getNewFloorThickness(); 3457 final Level level0; 3458 if (levels.isEmpty()) { 3459 // Create level 0 3460 String level0Name = this.preferences.getLocalizedString(PlanController.class, "levelName", 0); 3461 level0 = createLevel(level0Name, 0, newFloorThickness, newWallHeight); 3462 moveHomeItemsToLevel(level0); 3463 level0.setBackgroundImage(homeBackgroundImage); 3464 this.home.setBackgroundImage(null); 3465 levels = this.home.getLevels(); 3466 } else { 3467 level0 = null; 3468 } 3469 String newLevelName = this.preferences.getLocalizedString(PlanController.class, "levelName", levels.size()); 3470 final Level newLevel; 3471 if (sameElevation) { 3472 Level referencedLevel = level0 != null 3473 ? level0 3474 : this.home.getSelectedLevel(); 3475 newLevel = createLevel(newLevelName, referencedLevel.getElevation(), 3476 referencedLevel.getFloorThickness(), referencedLevel.getHeight()); 3477 } else { 3478 float newLevelElevation = levels.get(levels.size() - 1).getElevation() 3479 + newWallHeight + newFloorThickness; 3480 newLevel = createLevel(newLevelName, newLevelElevation, newFloorThickness, newWallHeight); 3481 } 3482 setSelectedLevel(newLevel); 3483 this.undoSupport.postEdit(new LevelAdditionUndoableEdit(this, this.home, this.preferences, 3484 oldSelection, allLevelsSelection, oldSelectedLevel, level0, homeBackgroundImage, newLevel)); 3485 } 3486 3487 /** 3488 * Undoable edit for level addition. 3489 */ 3490 private static class LevelAdditionUndoableEdit extends LocalizedUndoableEdit { 3491 private final PlanController controller; 3492 private final Home home; 3493 private final Selectable [] oldSelection; 3494 private final boolean allLevelsSelection; 3495 private final Level oldSelectedLevel; 3496 private final Level level0; 3497 private final BackgroundImage homeBackgroundImage; 3498 private final Level newLevel; 3499 3500 public LevelAdditionUndoableEdit(PlanController controller, Home home, UserPreferences preferences, 3501 Selectable [] oldSelection, boolean allLevelsSelection, Level oldSelectedLevel, 3502 Level level0, BackgroundImage homeBackgroundImage, Level newLevel) { 3503 super(preferences, PlanController.class, "undoAddLevel"); 3504 this.controller = controller; 3505 this.home = home; 3506 this.oldSelection = oldSelection; 3507 this.allLevelsSelection = allLevelsSelection; 3508 this.oldSelectedLevel = oldSelectedLevel; 3509 this.level0 = level0; 3510 this.homeBackgroundImage = homeBackgroundImage; 3511 this.newLevel = newLevel; 3512 } 3513 3514 @Override 3515 public void undo() throws CannotUndoException { 3516 super.undo(); 3517 controller.setSelectedLevel(this.oldSelectedLevel); 3518 this.home.deleteLevel(this.newLevel); 3519 if (this.level0 != null) { 3520 this.home.setBackgroundImage(this.homeBackgroundImage); 3521 controller.moveHomeItemsToLevel(this.oldSelectedLevel); 3522 this.home.deleteLevel(this.level0); 3523 } 3524 controller.selectAndShowItems(Arrays.asList(this.oldSelection), this.allLevelsSelection); 3525 } 3526 3527 @Override 3528 public void redo() throws CannotRedoException { 3529 super.redo(); 3530 if (this.level0 != null) { 3531 this.home.addLevel(this.level0); 3532 controller.moveHomeItemsToLevel(this.level0); 3533 this.level0.setBackgroundImage(this.homeBackgroundImage); 3534 this.home.setBackgroundImage(null); 3535 } 3536 this.home.addLevel(this.newLevel); 3537 controller.setSelectedLevel(this.newLevel); 3538 } 3539 } 3540 3541 /** 3542 * Returns a new level added to home. 3543 */ 3544 protected Level createLevel(String name, float elevation, float floorThickness, float height) { 3545 Level newLevel = new Level(name, elevation, floorThickness, height); 3546 this.home.addLevel(newLevel); 3547 return newLevel; 3548 } 3549 3550 /** 3551 * Moves to the given <code>level</code> all existing furniture, walls, rooms, dimension lines 3552 * and labels. 3553 */ 3554 private void moveHomeItemsToLevel(Level level) { 3555 for (HomePieceOfFurniture piece : this.home.getFurniture()) { 3556 piece.setLevel(level); 3557 } 3558 for (Wall wall : this.home.getWalls()) { 3559 wall.setLevel(level); 3560 } 3561 for (Room room : this.home.getRooms()) { 3562 room.setLevel(level); 3563 } 3564 for (Polyline polyline : this.home.getPolylines()) { 3565 polyline.setLevel(level); 3566 } 3567 for (DimensionLine dimensionLine : this.home.getDimensionLines()) { 3568 dimensionLine.setLevel(level); 3569 } 3570 for (Label label : this.home.getLabels()) { 3571 label.setLevel(level); 3572 } 3573 } 3574 3575 /** 3576 * Toggles the viewability of the selected level. 3577 * @since 5.0 3578 */ 3579 public void toggleSelectedLevelViewability() { 3580 final Level selectedLevel = this.home.getSelectedLevel(); 3581 selectedLevel.setViewable(!selectedLevel.isViewable()); 3582 this.undoSupport.postEdit(new LevelViewabilityModificationUndoableEdit(this, this.preferences, selectedLevel)); 3583 } 3584 3585 /** 3586 * Undoable edit for level viewability modification. 3587 */ 3588 private static class LevelViewabilityModificationUndoableEdit extends LocalizedUndoableEdit { 3589 private final PlanController controller; 3590 private final Level selectedLevel; 3591 3592 public LevelViewabilityModificationUndoableEdit(PlanController controller, UserPreferences preferences, 3593 Level selectedLevel) { 3594 super(preferences, PlanController.class, "undoModifyLevelViewabilityName"); 3595 this.controller = controller; 3596 this.selectedLevel = selectedLevel; 3597 } 3598 3599 @Override 3600 public void undo() throws CannotUndoException { 3601 super.undo(); 3602 this.controller.setSelectedLevel(this.selectedLevel); 3603 this.selectedLevel.setViewable(!this.selectedLevel.isViewable()); 3604 } 3605 3606 @Override 3607 public void redo() throws CannotRedoException { 3608 super.redo(); 3609 this.controller.setSelectedLevel(this.selectedLevel); 3610 this.selectedLevel.setViewable(!this.selectedLevel.isViewable()); 3611 } 3612 } 3613 3614 /** 3615 * Makes the selected level the only viewable one. 3616 * @since 6.0 3617 */ 3618 public void setSelectedLevelOnlyViewable() { 3619 final Level [] viewableLevels = getLevels(true); 3620 final Level selectedLevel = this.home.getSelectedLevel(); 3621 final boolean selectedLevelViewable = selectedLevel.isViewable(); 3622 if (viewableLevels.length != 1 3623 || !selectedLevelViewable) { 3624 setLevelsViewability(viewableLevels, false); 3625 selectedLevel.setViewable(true); 3626 this.undoSupport.postEdit(new LevelsViewabilityModificationUndoableEdit(this, this.preferences, 3627 selectedLevel, selectedLevelViewable, viewableLevels)); 3628 } 3629 } 3630 3631 /** 3632 * Undoable edit for the viewability modification of multiple levels. 3633 */ 3634 private static class LevelsViewabilityModificationUndoableEdit extends LocalizedUndoableEdit { 3635 private final PlanController controller; 3636 private final Level selectedLevel; 3637 private final boolean selectedLevelViewable; 3638 private final Level [] viewableLevels; 3639 3640 public LevelsViewabilityModificationUndoableEdit(PlanController controller, UserPreferences preferences, 3641 Level selectedLevel, boolean selectedLevelViewable, Level [] viewableLevels) { 3642 super(preferences, PlanController.class, "undoModifyLevelViewabilityName"); 3643 this.controller = controller; 3644 this.selectedLevel = selectedLevel; 3645 this.selectedLevelViewable = selectedLevelViewable; 3646 this.viewableLevels = viewableLevels; 3647 } 3648 3649 @Override 3650 public void undo() throws CannotUndoException { 3651 super.undo(); 3652 this.controller.setSelectedLevel(this.selectedLevel); 3653 setLevelsViewability(this.viewableLevels, true); 3654 this.selectedLevel.setViewable(this.selectedLevelViewable); 3655 } 3656 3657 @Override 3658 public void redo() throws CannotRedoException { 3659 super.redo(); 3660 this.controller.setSelectedLevel(this.selectedLevel); 3661 setLevelsViewability(this.viewableLevels, false); 3662 this.selectedLevel.setViewable(true); 3663 } 3664 } 3665 3666 /** 3667 * Makes all levels viewable. 3668 * @since 6.0 3669 */ 3670 public void setAllLevelsViewable() { 3671 final Level [] unviewableLevels = getLevels(false); 3672 if (unviewableLevels.length > 0) { 3673 final Level selectedLevel = this.home.getSelectedLevel(); 3674 setLevelsViewability(unviewableLevels, true); 3675 undoSupport.postEdit(new AllLevelsViewabilityModificationUndoableEdit(this, this.preferences, selectedLevel, unviewableLevels)); 3676 } 3677 } 3678 3679 /** 3680 * Undoable edit for the viewability modification of all levels. 3681 */ 3682 private static class AllLevelsViewabilityModificationUndoableEdit extends LocalizedUndoableEdit { 3683 private final PlanController controller; 3684 private final Level selectedLevel; 3685 private final Level [] unviewableLevels; 3686 3687 public AllLevelsViewabilityModificationUndoableEdit(PlanController controller, UserPreferences preferences, 3688 Level selectedLevel, Level [] unviewableLevels) { 3689 super(preferences, PlanController.class, "undoModifyLevelViewabilityName"); 3690 this.controller = controller; 3691 this.selectedLevel = selectedLevel; 3692 this.unviewableLevels = unviewableLevels; 3693 } 3694 3695 @Override 3696 public void undo() throws CannotUndoException { 3697 super.undo(); 3698 this.controller.setSelectedLevel(this.selectedLevel); 3699 setLevelsViewability(this.unviewableLevels, false); 3700 } 3701 3702 @Override 3703 public void redo() throws CannotRedoException { 3704 super.redo(); 3705 this.controller.setSelectedLevel(this.selectedLevel); 3706 setLevelsViewability(this.unviewableLevels, true); 3707 } 3708 } 3709 3710 /** 3711 * Returns levels which are viewable or not according to parameter. 3712 */ 3713 private Level [] getLevels(boolean viewable) { 3714 List<Level> levels = new ArrayList<Level>(); 3715 for (Level level : this.home.getLevels()) { 3716 if (level.isViewable() == viewable) { 3717 levels.add(level); 3718 } 3719 } 3720 return levels.toArray(new Level [levels.size()]); 3721 } 3722 3723 private static void setLevelsViewability(Level [] levels, boolean viewable) { 3724 for (Level level : levels) { 3725 level.setViewable(viewable); 3726 } 3727 } 3728 3729 public void modifySelectedLevel() { 3730 if (this.home.getSelectedLevel() != null) { 3731 new LevelController(this.home, this.preferences, this.viewFactory, 3732 this.undoSupport).displayView(getView()); 3733 } 3734 } 3735 3736 /** 3737 * Deletes the selected level and the items that belongs to it. 3738 */ 3739 public void deleteSelectedLevel() { 3740 // Start a compound edit that delete walls, furniture, rooms, dimension lines and labels from home 3741 undoSupport.beginUpdate(); 3742 List<HomePieceOfFurniture> levelFurniture = new ArrayList<HomePieceOfFurniture>(); 3743 final Level oldSelectedLevel = this.home.getSelectedLevel(); 3744 for (HomePieceOfFurniture piece : this.home.getFurniture()) { 3745 if (piece.getLevel() == oldSelectedLevel) { 3746 levelFurniture.add(piece); 3747 } 3748 } 3749 // Delete furniture with inherited method 3750 deleteFurniture(levelFurniture); 3751 3752 List<Selectable> levelOtherItems = new ArrayList<Selectable>(); 3753 addLevelItemsAtSelectedLevel(this.home.getWalls(), levelOtherItems); 3754 addLevelItemsAtSelectedLevel(this.home.getRooms(), levelOtherItems); 3755 addLevelItemsAtSelectedLevel(this.home.getDimensionLines(), levelOtherItems); 3756 addLevelItemsAtSelectedLevel(this.home.getLabels(), levelOtherItems); 3757 // First post to undo support that walls, rooms and dimension lines are deleted, 3758 // otherwise data about joined walls and rooms index can't be stored 3759 postDeleteItems(levelOtherItems, this.home.isBasePlanLocked(), this.home.isAllLevelsSelection()); 3760 // Then delete items from plan 3761 doDeleteItems(levelOtherItems); 3762 3763 this.home.deleteLevel(oldSelectedLevel); 3764 List<Level> levels = this.home.getLevels(); 3765 final Level remainingLevel; 3766 final Float remainingLevelElevation; 3767 final boolean remainingLevelViewable; 3768 if (levels.size() == 1) { 3769 remainingLevel = levels.get(0); 3770 remainingLevelElevation = remainingLevel.getElevation(); 3771 remainingLevelViewable = remainingLevel.isViewable(); 3772 remainingLevel.setElevation(0); 3773 remainingLevel.setViewable(true); 3774 } else { 3775 remainingLevel = null; 3776 remainingLevelElevation = null; 3777 remainingLevelViewable = false; 3778 } 3779 undoSupport.postEdit(new LevelDeletionUndoableEdit(this, this.home, this.preferences, oldSelectedLevel, 3780 remainingLevel, remainingLevelElevation, remainingLevelViewable)); 3781 3782 // End compound edit 3783 undoSupport.endUpdate(); 3784 } 3785 3786 /** 3787 * Undoable edit for the level deletion. 3788 */ 3789 private static class LevelDeletionUndoableEdit extends LocalizedUndoableEdit { 3790 private final PlanController controller; 3791 private final Home home; 3792 private final Level oldSelectedLevel; 3793 private final Level remainingLevel; 3794 private final Float remainingLevelElevation; 3795 private final boolean remainingLevelViewable; 3796 3797 public LevelDeletionUndoableEdit(PlanController controller, Home home, UserPreferences preferences, 3798 Level oldSelectedLevel, Level remainingLevel, 3799 Float remainingLevelElevation, boolean remainingLevelViewable) { 3800 super(preferences, PlanController.class, "undoDeleteSelectedLevel"); 3801 this.controller = controller; 3802 this.home = home; 3803 this.oldSelectedLevel = oldSelectedLevel; 3804 this.remainingLevel = remainingLevel; 3805 this.remainingLevelElevation = remainingLevelElevation; 3806 this.remainingLevelViewable = remainingLevelViewable; 3807 } 3808 3809 @Override 3810 public void undo() throws CannotUndoException { 3811 super.undo(); 3812 if (this.remainingLevel != null) { 3813 this.remainingLevel.setElevation(this.remainingLevelElevation); 3814 this.remainingLevel.setViewable(this.remainingLevelViewable); 3815 } 3816 this.home.addLevel(this.oldSelectedLevel); 3817 this.controller.setSelectedLevel(this.oldSelectedLevel); 3818 } 3819 3820 @Override 3821 public void redo() throws CannotRedoException { 3822 super.redo(); 3823 this.home.deleteLevel(this.oldSelectedLevel); 3824 if (this.remainingLevel != null) { 3825 this.remainingLevel.setElevation(0); 3826 this.remainingLevel.setViewable(true); 3827 } 3828 } 3829 } 3830 3831 private void addLevelItemsAtSelectedLevel(Collection<? extends Selectable> items, 3832 List<Selectable> levelItems) { 3833 Level selectedLevel = this.home.getSelectedLevel(); 3834 for (Selectable item : items) { 3835 if (item instanceof Elevatable 3836 && ((Elevatable)item).getLevel() == selectedLevel) { 3837 levelItems.add(item); 3838 } 3839 } 3840 } 3841 3842 /** 3843 * Returns a new wall instance between (<code>xStart</code>, 3844 * <code>yStart</code>) and (<code>xEnd</code>, <code>yEnd</code>) 3845 * end points. The new wall is added to home and its start point is joined 3846 * to the start of <code>wallStartAtStart</code> or 3847 * the end of <code>wallEndAtStart</code>. 3848 */ 3849 protected Wall createWall(float xStart, float yStart, 3850 float xEnd, float yEnd, 3851 Wall wallStartAtStart, 3852 Wall wallEndAtStart) { 3853 // Create a new wall 3854 Wall newWall = new Wall(xStart, yStart, xEnd, yEnd, 3855 this.preferences.getNewWallThickness(), 3856 this.preferences.getNewWallHeight(), 3857 this.preferences.getNewWallPattern()); 3858 this.home.addWall(newWall); 3859 if (wallStartAtStart != null) { 3860 newWall.setWallAtStart(wallStartAtStart); 3861 wallStartAtStart.setWallAtStart(newWall); 3862 } else if (wallEndAtStart != null) { 3863 newWall.setWallAtStart(wallEndAtStart); 3864 wallEndAtStart.setWallAtEnd(newWall); 3865 } 3866 return newWall; 3867 } 3868 3869 /** 3870 * Joins the end point of <code>wall</code> to the start of 3871 * <code>wallStartAtEnd</code> or the end of <code>wallEndAtEnd</code>. 3872 */ 3873 private void joinNewWallEndToWall(Wall wall, 3874 Wall wallStartAtEnd, Wall wallEndAtEnd) { 3875 if (wallStartAtEnd != null) { 3876 wall.setWallAtEnd(wallStartAtEnd); 3877 wallStartAtEnd.setWallAtStart(wall); 3878 // Make wall end at the exact same position as wallAtEnd start point 3879 wall.setXEnd(wallStartAtEnd.getXStart()); 3880 wall.setYEnd(wallStartAtEnd.getYStart()); 3881 } else if (wallEndAtEnd != null) { 3882 wall.setWallAtEnd(wallEndAtEnd); 3883 wallEndAtEnd.setWallAtEnd(wall); 3884 // Make wall end at the exact same position as wallAtEnd end point 3885 wall.setXEnd(wallEndAtEnd.getXEnd()); 3886 wall.setYEnd(wallEndAtEnd.getYEnd()); 3887 } 3888 } 3889 3890 /** 3891 * Returns the wall at (<code>x</code>, <code>y</code>) point, 3892 * which has a start point not joined to any wall. 3893 */ 3894 private Wall getWallStartAt(float x, float y, Wall ignoredWall) { 3895 float margin = WALL_ENDS_PIXEL_MARGIN / getScale(); 3896 for (Wall wall : this.home.getWalls()) { 3897 if (wall != ignoredWall 3898 && isLevelNullOrViewable(wall.getLevel()) 3899 && wall.isAtLevel(this.home.getSelectedLevel()) 3900 && wall.getWallAtStart() == null 3901 && wall.containsWallStartAt(x, y, margin)) { 3902 return wall; 3903 } 3904 } 3905 return null; 3906 } 3907 3908 /** 3909 * Returns the wall at (<code>x</code>, <code>y</code>) point, 3910 * which has a end point not joined to any wall. 3911 */ 3912 private Wall getWallEndAt(float x, float y, Wall ignoredWall) { 3913 float margin = WALL_ENDS_PIXEL_MARGIN / getScale(); 3914 for (Wall wall : this.home.getWalls()) { 3915 if (wall != ignoredWall 3916 && isLevelNullOrViewable(wall.getLevel()) 3917 && wall.isAtLevel(this.home.getSelectedLevel()) 3918 && wall.getWallAtEnd() == null 3919 && wall.containsWallEndAt(x, y, margin)) { 3920 return wall; 3921 } 3922 } 3923 return null; 3924 } 3925 3926 /** 3927 * Returns the tolerance margin to handle an indicator. 3928 */ 3929 private float getIndicatorMargin() { 3930 float indicatorPixelMagin = INDICATOR_PIXEL_MARGIN; 3931 if (getPointerTypeLastMousePress() == View.PointerType.TOUCH) { 3932 indicatorPixelMagin *= 3; 3933 } 3934 return indicatorPixelMagin / getScale(); 3935 } 3936 3937 /** 3938 * Returns the selected wall with a start point 3939 * at (<code>x</code>, <code>y</code>). 3940 */ 3941 private Wall getResizedWallStartAt(float x, float y) { 3942 List<Selectable> selectedItems = this.home.getSelectedItems(); 3943 if (selectedItems.size() == 1 3944 && selectedItems.get(0) instanceof Wall 3945 && isItemResizable(selectedItems.get(0))) { 3946 Wall wall = (Wall)selectedItems.get(0); 3947 float margin = getIndicatorMargin(); 3948 if (wall.isAtLevel(this.home.getSelectedLevel()) 3949 && wall.containsWallStartAt(x, y, margin)) { 3950 return wall; 3951 } 3952 } 3953 return null; 3954 } 3955 3956 /** 3957 * Returns the selected wall with an end point at (<code>x</code>, <code>y</code>). 3958 */ 3959 private Wall getResizedWallEndAt(float x, float y) { 3960 List<Selectable> selectedItems = this.home.getSelectedItems(); 3961 if (selectedItems.size() == 1 3962 && selectedItems.get(0) instanceof Wall 3963 && isItemResizable(selectedItems.get(0))) { 3964 Wall wall = (Wall)selectedItems.get(0); 3965 float margin = getIndicatorMargin(); 3966 if (wall.isAtLevel(this.home.getSelectedLevel()) 3967 && wall.containsWallEndAt(x, y, margin)) { 3968 return wall; 3969 } 3970 } 3971 return null; 3972 } 3973 3974 /** 3975 * Returns the selected wall with a middle point at (<code>x</code>, <code>y</code>). 3976 */ 3977 private Wall getArcExtentWallAt(float x, float y) { 3978 List<Selectable> selectedItems = this.home.getSelectedItems(); 3979 if (selectedItems.size() == 1 3980 && selectedItems.get(0) instanceof Wall 3981 && isItemResizable(selectedItems.get(0))) { 3982 Wall wall = (Wall)selectedItems.get(0); 3983 float margin = getIndicatorMargin(); 3984 if (wall.isAtLevel(this.home.getSelectedLevel()) 3985 && wall.isMiddlePointAt(x, y, margin)) { 3986 return wall; 3987 } 3988 } 3989 return null; 3990 } 3991 3992 /** 3993 * Returns a new room instance with the given points. 3994 * The new room is added to home. 3995 */ 3996 protected Room createRoom(float [][] roomPoints) { 3997 Room newRoom = new Room(roomPoints); 3998 newRoom.setFloorColor(this.preferences.getNewRoomFloorColor()); 3999 this.home.addRoom(newRoom); 4000 return newRoom; 4001 } 4002 4003 /** 4004 * Returns the selected room with a point at (<code>x</code>, <code>y</code>). 4005 */ 4006 private Room getResizedRoomAt(float x, float y) { 4007 List<Selectable> selectedItems = this.home.getSelectedItems(); 4008 if (selectedItems.size() == 1 4009 && selectedItems.get(0) instanceof Room 4010 && isItemResizable(selectedItems.get(0))) { 4011 Room room = (Room)selectedItems.get(0); 4012 float margin = getIndicatorMargin(); 4013 if (room.isAtLevel(this.home.getSelectedLevel()) 4014 && room.getPointIndexAt(x, y, margin) != -1) { 4015 return room; 4016 } 4017 } 4018 return null; 4019 } 4020 4021 /** 4022 * Returns the selected room with its name center point at (<code>x</code>, <code>y</code>). 4023 */ 4024 private Room getRoomNameAt(float x, float y) { 4025 List<Selectable> selectedItems = this.home.getSelectedItems(); 4026 if (selectedItems.size() == 1 4027 && selectedItems.get(0) instanceof Room 4028 && isItemMovable(selectedItems.get(0))) { 4029 Room room = (Room)selectedItems.get(0); 4030 float margin = getIndicatorMargin(); 4031 if (room.isAtLevel(this.home.getSelectedLevel()) 4032 && room.getName() != null 4033 && room.getName().trim().length() > 0 4034 && room.isNameCenterPointAt(x, y, margin)) { 4035 return room; 4036 } 4037 } 4038 return null; 4039 } 4040 4041 /** 4042 * Returns the selected room with its 4043 * name angle point at (<code>x</code>, <code>y</code>). 4044 */ 4045 private Room getRoomRotatedNameAt(float x, float y) { 4046 List<Selectable> selectedItems = this.home.getSelectedItems(); 4047 if (selectedItems.size() == 1 4048 && selectedItems.get(0) instanceof Room 4049 && isItemMovable(selectedItems.get(0))) { 4050 Room room = (Room)selectedItems.get(0); 4051 float margin = getIndicatorMargin(); 4052 if (room.isAtLevel(this.home.getSelectedLevel()) 4053 && room.getName() != null 4054 && room.getName().trim().length() > 0 4055 && isTextAnglePointAt(room, room.getName(), room.getNameStyle(), 4056 room.getXCenter() + room.getNameXOffset(), room.getYCenter() + room.getNameYOffset(), 4057 room.getNameAngle(), x, y, margin)) { 4058 return room; 4059 } 4060 } 4061 return null; 4062 } 4063 4064 /** 4065 * Returns the selected room with its area center point at (<code>x</code>, <code>y</code>). 4066 */ 4067 private Room getRoomAreaAt(float x, float y) { 4068 List<Selectable> selectedItems = this.home.getSelectedItems(); 4069 if (selectedItems.size() == 1 4070 && selectedItems.get(0) instanceof Room 4071 && isItemMovable(selectedItems.get(0))) { 4072 Room room = (Room)selectedItems.get(0); 4073 float margin = getIndicatorMargin(); 4074 if (room.isAtLevel(this.home.getSelectedLevel()) 4075 && room.isAreaVisible() 4076 && room.isAreaCenterPointAt(x, y, margin)) { 4077 return room; 4078 } 4079 } 4080 return null; 4081 } 4082 4083 /** 4084 * Returns the selected room with its 4085 * area angle point at (<code>x</code>, <code>y</code>). 4086 */ 4087 private Room getRoomRotatedAreaAt(float x, float y) { 4088 List<Selectable> selectedItems = this.home.getSelectedItems(); 4089 if (selectedItems.size() == 1 4090 && selectedItems.get(0) instanceof Room 4091 && isItemMovable(selectedItems.get(0))) { 4092 Room room = (Room)selectedItems.get(0); 4093 float margin = getIndicatorMargin(); 4094 if (room.isAtLevel(this.home.getSelectedLevel()) 4095 && room.isAreaVisible()) { 4096 float area = room.getArea(); 4097 if (area > 0.01f) { 4098 String areaText = this.preferences.getLengthUnit().getAreaFormatWithUnit().format(area); 4099 if (isTextAnglePointAt(room, areaText, room.getAreaStyle(), 4100 room.getXCenter() + room.getAreaXOffset(), room.getYCenter() + room.getAreaYOffset(), 4101 room.getAreaAngle(), x, y, margin)) { 4102 return room; 4103 } 4104 } 4105 } 4106 } 4107 return null; 4108 } 4109 4110 /** 4111 * Adds a point to the selected room at the given coordinates and posts an undoable operation. 4112 * @since 5.0 4113 */ 4114 public void addPointToSelectedRoom(final float x, final float y) { 4115 final List<Selectable> selectedItems = this.home.getSelectedItems(); 4116 if (selectedItems.size() == 1 4117 && selectedItems.get(0) instanceof Room 4118 && isItemResizable(selectedItems.get(0))) { 4119 final Room room = (Room)selectedItems.get(0); 4120 final float [][] points = room.getPoints(); 4121 // Search the segment closest to (x, y) 4122 int closestSegmentIndex = -1; 4123 double smallestDistance = Double.MAX_VALUE; 4124 for (int i = 0; i < points.length; i++) { 4125 float [] point = points [i]; 4126 float [] nextPoint = points [(i + 1) % points.length]; 4127 double distanceToSegment = Line2D.ptSegDistSq(point [0], point [1], nextPoint [0], nextPoint [1], x, y); 4128 if (smallestDistance > distanceToSegment) { 4129 smallestDistance = distanceToSegment; 4130 closestSegmentIndex = i; 4131 } 4132 } 4133 final int index = closestSegmentIndex + 1; 4134 room.addPoint(x, y, index); 4135 this.home.setSelectedItems(Arrays.asList(new Room [] {room})); 4136 // Upright an undoable edit 4137 this.undoSupport.postEdit(new RoomPointAdditionUndoableEdit(this, this.preferences, 4138 selectedItems.toArray(new Selectable [selectedItems.size()]), room, index, x, y)); 4139 } 4140 } 4141 4142 /** 4143 * Undoable edit for room point addition. 4144 */ 4145 private static class RoomPointAdditionUndoableEdit extends LocalizedUndoableEdit { 4146 private final PlanController controller; 4147 private final Selectable [] oldSelection; 4148 private final Room room; 4149 private final int index; 4150 private final float x; 4151 private final float y; 4152 4153 public RoomPointAdditionUndoableEdit(PlanController controller, UserPreferences preferences, 4154 Selectable [] oldSelection, Room room, int index, float x, float y) { 4155 super(preferences, PlanController.class, "undoAddRoomPointName"); 4156 this.controller = controller; 4157 this.oldSelection = oldSelection; 4158 this.room = room; 4159 this.index = index; 4160 this.x = x; 4161 this.y = y; 4162 } 4163 4164 @Override 4165 public void undo() throws CannotUndoException { 4166 super.undo(); 4167 this.room.removePoint(this.index); 4168 this.controller.selectAndShowItems(Arrays.asList(this.oldSelection)); 4169 } 4170 4171 @Override 4172 public void redo() throws CannotRedoException { 4173 super.redo(); 4174 this.room.addPoint(this.x, this.y, this.index); 4175 this.controller.selectAndShowItems(Arrays.asList(new Room [] {this.room})); 4176 } 4177 } 4178 4179 /** 4180 * Returns <code>true</code> if the given point can be removed from the <code>room</code>. 4181 */ 4182 public boolean isRoomPointDeletableAt(Room room, float x, float y) { 4183 return isItemResizable(room) 4184 && room.getPointIndexAt(x, y, getIndicatorMargin()) >= 0; 4185 } 4186 4187 /** 4188 * Deletes the point of the selected room at the given coordinates and posts an undoable operation. 4189 * @since 5.0 4190 */ 4191 public void deletePointFromSelectedRoom(float x, float y) { 4192 final List<Selectable> selectedItems = this.home.getSelectedItems(); 4193 if (selectedItems.size() == 1 4194 && selectedItems.get(0) instanceof Room 4195 && isItemResizable(selectedItems.get(0))) { 4196 final Room room = (Room)selectedItems.get(0); 4197 final int index = room.getPointIndexAt(x, y, getIndicatorMargin()); 4198 if (index >= 0) { 4199 float [][] points = room.getPoints(); 4200 float [] point = points [index]; 4201 final float xPoint = point [0]; 4202 final float yPoint = point [1]; 4203 4204 room.removePoint(index); 4205 this.home.setSelectedItems(Arrays.asList(new Room [] {room})); 4206 // Upright an undoable edit 4207 this.undoSupport.postEdit(new RoomPointDeletionUndoableEdit(this, this.preferences, 4208 selectedItems.toArray(new Selectable [selectedItems.size()]), room, index, xPoint, yPoint)); 4209 } 4210 } 4211 } 4212 4213 /** 4214 * Undoable edit for room point deletion. 4215 */ 4216 private static class RoomPointDeletionUndoableEdit extends LocalizedUndoableEdit { 4217 private final PlanController controller; 4218 private final Selectable [] oldSelection; 4219 private final Room room; 4220 private final int index; 4221 private final float xPoint; 4222 private final float yPoint; 4223 4224 public RoomPointDeletionUndoableEdit(PlanController controller, UserPreferences preferences, 4225 Selectable [] oldSelection, Room room, int index, 4226 float xPoint, float yPoint) { 4227 super(preferences, PlanController.class, "undoDeleteRoomPointName"); 4228 this.controller = controller; 4229 this.oldSelection = oldSelection; 4230 this.room = room; 4231 this.index = index; 4232 this.xPoint = xPoint; 4233 this.yPoint = yPoint; 4234 } 4235 4236 @Override 4237 public void undo() throws CannotUndoException { 4238 super.undo(); 4239 this.room.addPoint(this.xPoint, this.yPoint, this.index); 4240 this.controller.selectAndShowItems(Arrays.asList(this.oldSelection)); 4241 } 4242 4243 @Override 4244 public void redo() throws CannotRedoException { 4245 super.redo(); 4246 this.room.removePoint(this.index); 4247 this.controller.selectAndShowItems(Arrays.asList(new Room [] {this.room})); 4248 } 4249 } 4250 4251 /** 4252 * Returns a new dimension instance joining (<code>xStart</code>, 4253 * <code>yStart</code>) and (<code>xEnd</code>, <code>yEnd</code>) points. 4254 * The new dimension line is added to home. 4255 */ 4256 protected DimensionLine createDimensionLine(float xStart, float yStart, 4257 float xEnd, float yEnd, 4258 float offset) { 4259 DimensionLine newDimensionLine = new DimensionLine(xStart, yStart, xEnd, yEnd, offset); 4260 this.home.addDimensionLine(newDimensionLine); 4261 return newDimensionLine; 4262 } 4263 4264 /** 4265 * Returns the selected dimension line with an end extension line 4266 * at (<code>x</code>, <code>y</code>). 4267 */ 4268 private DimensionLine getResizedDimensionLineStartAt(float x, float y) { 4269 List<Selectable> selectedItems = this.home.getSelectedItems(); 4270 if (selectedItems.size() == 1 4271 && selectedItems.get(0) instanceof DimensionLine 4272 && isItemResizable(selectedItems.get(0))) { 4273 DimensionLine dimensionLine = (DimensionLine)selectedItems.get(0); 4274 float margin = getIndicatorMargin(); 4275 if (dimensionLine.isAtLevel(this.home.getSelectedLevel()) 4276 && dimensionLine.containsStartExtensionLinetAt(x, y, margin)) { 4277 return dimensionLine; 4278 } 4279 } 4280 return null; 4281 } 4282 4283 /** 4284 * Returns the selected dimension line with an end extension line 4285 * at (<code>x</code>, <code>y</code>). 4286 */ 4287 private DimensionLine getResizedDimensionLineEndAt(float x, float y) { 4288 List<Selectable> selectedItems = this.home.getSelectedItems(); 4289 if (selectedItems.size() == 1 4290 && selectedItems.get(0) instanceof DimensionLine 4291 && isItemResizable(selectedItems.get(0))) { 4292 DimensionLine dimensionLine = (DimensionLine)selectedItems.get(0); 4293 float margin = getIndicatorMargin(); 4294 if (dimensionLine.isAtLevel(this.home.getSelectedLevel()) 4295 && dimensionLine.containsEndExtensionLineAt(x, y, margin)) { 4296 return dimensionLine; 4297 } 4298 } 4299 return null; 4300 } 4301 4302 /** 4303 * Returns the selected dimension line with a point 4304 * at (<code>x</code>, <code>y</code>) at its middle. 4305 */ 4306 private DimensionLine getOffsetDimensionLineAt(float x, float y) { 4307 List<Selectable> selectedItems = this.home.getSelectedItems(); 4308 if (selectedItems.size() == 1 4309 && selectedItems.get(0) instanceof DimensionLine 4310 && isItemResizable(selectedItems.get(0))) { 4311 DimensionLine dimensionLine = (DimensionLine)selectedItems.get(0); 4312 float margin = getIndicatorMargin(); 4313 if (dimensionLine.isAtLevel(this.home.getSelectedLevel()) 4314 && dimensionLine.isMiddlePointAt(x, y, margin)) { 4315 return dimensionLine; 4316 } 4317 } 4318 return null; 4319 } 4320 4321 /** 4322 * Returns a new polyline instance with the given points. 4323 * The new polyline is added to home. 4324 */ 4325 private Polyline createPolyline(float [][] polylinePoints) { 4326 Polyline newPolyline = new Polyline(polylinePoints); 4327 LengthUnit lengthUnit = preferences.getLengthUnit(); 4328 newPolyline.setThickness(lengthUnit == LengthUnit.INCH || lengthUnit == LengthUnit.INCH_DECIMALS 4329 ? LengthUnit.inchToCentimeter(1) 4330 : 2); 4331 this.home.addPolyline(newPolyline); 4332 return newPolyline; 4333 } 4334 4335 /** 4336 * Returns the selected polyline with a point at (<code>x</code>, <code>y</code>). 4337 */ 4338 private Polyline getResizedPolylineAt(float x, float y) { 4339 List<Selectable> selectedItems = this.home.getSelectedItems(); 4340 if (selectedItems.size() == 1 4341 && selectedItems.get(0) instanceof Polyline 4342 && isItemResizable(selectedItems.get(0))) { 4343 Polyline polyline = (Polyline)selectedItems.get(0); 4344 float margin = getIndicatorMargin(); 4345 if (polyline.isAtLevel(this.home.getSelectedLevel()) 4346 && polyline.getPointIndexAt(x, y, margin) != -1) { 4347 return polyline; 4348 } 4349 } 4350 return null; 4351 } 4352 4353 /** 4354 * Returns the tolerance margin to select an item. 4355 */ 4356 private float getSelectionMargin() { 4357 float indicatorPixelMagin = PIXEL_MARGIN; 4358 if (getPointerTypeLastMousePress() == View.PointerType.TOUCH) { 4359 indicatorPixelMagin *= 2; 4360 } 4361 return indicatorPixelMagin / getScale(); 4362 } 4363 4364 /** 4365 * Returns the selected item at (<code>x</code>, <code>y</code>) point. 4366 */ 4367 private boolean isItemSelectedAt(float x, float y) { 4368 float margin = getSelectionMargin(); 4369 for (Selectable item : this.home.getSelectedItems()) { 4370 if (item.containsPoint(x, y, margin)) { 4371 return true; 4372 } 4373 } 4374 return false; 4375 } 4376 4377 /** 4378 * Returns the selectable item at (<code>x</code>, <code>y</code>) point. 4379 */ 4380 public Selectable getSelectableItemAt(float x, float y) { 4381 return getSelectableItemAt(x, y, true); 4382 } 4383 4384 /** 4385 * Returns the selectable item at (<code>x</code>, <code>y</code>) point. 4386 */ 4387 private Selectable getSelectableItemAt(float x, float y, boolean ignoreGroupsFurniture) { 4388 List<Selectable> selectableItems = getSelectableItemsAt(x, y, true, ignoreGroupsFurniture); 4389 if (selectableItems.size() != 0) { 4390 return selectableItems.get(0); 4391 } else { 4392 return null; 4393 } 4394 } 4395 4396 /** 4397 * Returns the selectable items at (<code>x</code>, <code>y</code>) point. 4398 */ 4399 public List<Selectable> getSelectableItemsAt(float x, float y) { 4400 return getSelectableItemsAt(x, y, false, true); 4401 } 4402 4403 /** 4404 * Returns the selectable items at (<code>x</code>, <code>y</code>) point. 4405 */ 4406 private List<Selectable> getSelectableItemsAt(float x, float y, 4407 boolean stopAtFirstItem, 4408 boolean ignoreGroupsFurniture) { 4409 List<Selectable> items = new ArrayList<Selectable>(); 4410 float margin = getSelectionMargin(); 4411 float textMargin = margin / 2; 4412 ObserverCamera camera = this.home.getObserverCamera(); 4413 if (camera != null 4414 && camera == this.home.getCamera() 4415 && camera.containsPoint(x, y, margin)) { 4416 items.add(camera); 4417 if (stopAtFirstItem) { 4418 return items; 4419 } 4420 } 4421 4422 boolean basePlanLocked = this.home.isBasePlanLocked(); 4423 Level selectedLevel = this.home.getSelectedLevel(); 4424 for (Label label : this.home.getLabels()) { 4425 if ((!basePlanLocked 4426 || !isItemPartOfBasePlan(label)) 4427 && isLevelNullOrViewable(label.getLevel()) 4428 && label.isAtLevel(selectedLevel) 4429 && (label.containsPoint(x, y, margin) 4430 || isItemTextAt(label, label.getText(), label.getStyle(), 4431 label.getX(), label.getY(), label.getAngle(), x, y, textMargin))) { 4432 items.add(label); 4433 if (stopAtFirstItem) { 4434 return items; 4435 } 4436 } 4437 } 4438 4439 for (DimensionLine dimensionLine : this.home.getDimensionLines()) { 4440 if ((!basePlanLocked 4441 || !isItemPartOfBasePlan(dimensionLine)) 4442 && isLevelNullOrViewable(dimensionLine.getLevel()) 4443 && dimensionLine.isAtLevel(selectedLevel) 4444 && dimensionLine.containsPoint(x, y, margin)) { 4445 items.add(dimensionLine); 4446 if (stopAtFirstItem) { 4447 return items; 4448 } 4449 } 4450 } 4451 4452 List<Polyline> polylines = this.home.getPolylines(); 4453 // Search in home polylines in reverse order to give priority to last drawn polyline 4454 for (int i = polylines.size() - 1; i >= 0; i--) { 4455 Polyline polyline = polylines.get(i); 4456 if ((!basePlanLocked 4457 || !isItemPartOfBasePlan(polyline)) 4458 && isLevelNullOrViewable(polyline.getLevel()) 4459 && polyline.isAtLevel(selectedLevel) 4460 && polyline.containsPoint(x, y, margin)) { 4461 items.add(polyline); 4462 if (stopAtFirstItem) { 4463 return items; 4464 } 4465 } 4466 } 4467 4468 List<HomePieceOfFurniture> furniture = this.home.getFurniture(); 4469 // Search in home furniture in reverse order to give priority to last drawn piece 4470 // at highest elevation in case it covers an other piece 4471 List<HomePieceOfFurniture> foundFurniture = new ArrayList<HomePieceOfFurniture>(); 4472 HomePieceOfFurniture foundPiece = null; 4473 for (int i = furniture.size() - 1; i >= 0; i--) { 4474 HomePieceOfFurniture piece = furniture.get(i); 4475 if ((!basePlanLocked 4476 || !isItemPartOfBasePlan(piece)) 4477 && isPieceOfFurnitureVisibleAtSelectedLevel(piece)) { 4478 if (piece.containsPoint(x, y, margin)) { 4479 foundFurniture.add(piece); 4480 if (foundPiece == null 4481 || piece.getGroundElevation() > foundPiece.getGroundElevation()) { 4482 foundPiece = piece; 4483 } 4484 } else if (foundPiece == null) { 4485 // Search if piece name contains point in case it is drawn outside of the piece 4486 String pieceName = piece.getName(); 4487 if (pieceName != null 4488 && piece.isNameVisible() 4489 && isItemTextAt(piece, pieceName, piece.getNameStyle(), 4490 piece.getX() + piece.getNameXOffset(), 4491 piece.getY() + piece.getNameYOffset(), piece.getNameAngle(), x, y, textMargin)) { 4492 foundFurniture.add(piece); 4493 foundPiece = piece; 4494 } 4495 } 4496 } 4497 } 4498 if (foundPiece == null 4499 && basePlanLocked) { 4500 // Check among the furniture that is already selected if there's a movable piece at the given location 4501 for (Selectable item : home.getSelectedItems()) { 4502 if (item instanceof HomePieceOfFurniture) { 4503 HomePieceOfFurniture piece = (HomePieceOfFurniture)item; 4504 if (!isItemPartOfBasePlan(piece) 4505 && isPieceOfFurnitureVisibleAtSelectedLevel(piece) 4506 && (piece.containsPoint(x, y, margin) 4507 || piece.getName() != null 4508 && piece.isNameVisible() 4509 && isItemTextAt(piece, piece.getName(), piece.getNameStyle(), 4510 piece.getX() + piece.getNameXOffset(), 4511 piece.getY() + piece.getNameYOffset(), piece.getNameAngle(), x, y, textMargin))) { 4512 foundFurniture.add(piece); 4513 foundPiece = piece; 4514 if (stopAtFirstItem) { 4515 break; 4516 } 4517 } 4518 } 4519 } 4520 } 4521 if (foundPiece != null 4522 && stopAtFirstItem) { 4523 if (!ignoreGroupsFurniture 4524 && (foundPiece instanceof HomeFurnitureGroup)) { 4525 List<Selectable> selectedItems = this.home.getSelectedItems(); 4526 if (selectedItems.size() >= 1) { 4527 // If selected items are in the same group 4528 if ((selectedItems.size() == 1 && selectedItems.get(0) == foundPiece) 4529 || ((HomeFurnitureGroup)foundPiece).getAllFurniture().containsAll(selectedItems)) { 4530 for (Selectable selectedItem : selectedItems) { 4531 if (selectedItem instanceof HomeFurnitureGroup) { 4532 // Search the piece at point among the furniture of the selected group 4533 List<HomePieceOfFurniture> groupFurniture = ((HomeFurnitureGroup)selectedItem).getFurniture(); 4534 for (int i = groupFurniture.size() - 1; i >= 0; i--) { 4535 HomePieceOfFurniture piece = groupFurniture.get(i); 4536 if ((!basePlanLocked 4537 || !isItemPartOfBasePlan(piece)) 4538 && !selectedItems.contains(piece) 4539 && piece.containsPoint(x, y, margin)) { 4540 return Arrays.asList(new Selectable [] {piece}); 4541 } 4542 } 4543 } 4544 } 4545 // Search the piece at point among the groups of selected furniture 4546 for (Selectable selectedItem : selectedItems) { 4547 if (selectedItem instanceof HomePieceOfFurniture) { 4548 List<HomePieceOfFurniture> groupFurniture = getFurnitureInSameGroup((HomePieceOfFurniture)selectedItem); 4549 for (int i = groupFurniture.size() - 1; i >= 0; i--) { 4550 HomePieceOfFurniture piece = groupFurniture.get(i); 4551 if ((!basePlanLocked 4552 || !isItemPartOfBasePlan(piece)) 4553 && piece.containsPoint(x, y, margin)) { 4554 return Arrays.asList(new Selectable [] {piece}); 4555 } 4556 } 4557 } 4558 } 4559 } 4560 } 4561 } 4562 return Arrays.asList(new Selectable [] {foundPiece}); 4563 } else { 4564 Collections.sort(foundFurniture, new Comparator<HomePieceOfFurniture>() { 4565 public int compare(HomePieceOfFurniture p1, HomePieceOfFurniture p2) { 4566 return -Float.compare(p1.getGroundElevation(), p2.getGroundElevation()); 4567 } 4568 }); 4569 items.addAll(foundFurniture); 4570 for (Wall wall : this.home.getWalls()) { 4571 if ((!basePlanLocked 4572 || !isItemPartOfBasePlan(wall)) 4573 && isLevelNullOrViewable(wall.getLevel()) 4574 && wall.isAtLevel(selectedLevel) 4575 && wall.containsPoint(x, y, margin)) { 4576 items.add(wall); 4577 if (stopAtFirstItem) { 4578 return items; 4579 } 4580 } 4581 } 4582 4583 List<Room> rooms = this.home.getRooms(); 4584 // Search in home rooms in reverse order to give priority to last drawn room 4585 // at highest elevation in case it covers an other piece 4586 Room foundRoom = null; 4587 for (int i = rooms.size() - 1; i >= 0; i--) { 4588 Room room = rooms.get(i); 4589 if ((!basePlanLocked 4590 || !isItemPartOfBasePlan(room)) 4591 && isLevelNullOrViewable(room.getLevel()) 4592 && room.isAtLevel(selectedLevel)) { 4593 if (room.containsPoint(x, y, margin)) { 4594 items.add(room); 4595 if (foundRoom == null 4596 || room.isCeilingVisible() && !foundRoom.isCeilingVisible()) { 4597 foundRoom = room; 4598 } 4599 } else { 4600 // Search if room name contains point in case it is drawn outside of the room 4601 String roomName = room.getName(); 4602 if (roomName != null 4603 && isItemTextAt(room, roomName, room.getNameStyle(), 4604 room.getXCenter() + room.getNameXOffset(), 4605 room.getYCenter() + room.getNameYOffset(), room.getNameAngle(), x, y, textMargin)) { 4606 items.add(room); 4607 foundRoom = room; 4608 } 4609 // Search if room area contains point in case its text is drawn outside of the room 4610 if (room.isAreaVisible()) { 4611 String areaText = this.preferences.getLengthUnit().getAreaFormatWithUnit().format(room.getArea()); 4612 if (isItemTextAt(room, areaText, room.getAreaStyle(), 4613 room.getXCenter() + room.getAreaXOffset(), 4614 room.getYCenter() + room.getAreaYOffset(), room.getAreaAngle(), x, y, textMargin)) { 4615 items.add(room); 4616 foundRoom = room; 4617 } 4618 } 4619 } 4620 } 4621 } 4622 if (foundRoom != null 4623 && stopAtFirstItem) { 4624 return Arrays.asList(new Selectable [] {foundRoom}); 4625 } else { 4626 Compass compass = this.home.getCompass(); 4627 if ((!basePlanLocked 4628 || !isItemPartOfBasePlan(compass)) 4629 && compass.containsPoint(x, y, textMargin)) { 4630 items.add(compass); 4631 } 4632 return items; 4633 } 4634 } 4635 } 4636 4637 /** 4638 * Returns <code>true</code> if the <code>text</code> of an <code>item</code> displayed 4639 * at the point (<code>xText</code>, <code>yText</code>) contains the point (<code>x</code>, <code>y</code>). 4640 */ 4641 private boolean isItemTextAt(Selectable item, String text, TextStyle textStyle, float xText, float yText, float textAngle, 4642 float x, float y, float textMargin) { 4643 if (textStyle == null) { 4644 textStyle = this.preferences.getDefaultTextStyle(item.getClass()); 4645 } 4646 float [][] textBounds = getView().getTextBounds(text, textStyle, xText, yText, textAngle); 4647 return getPath(textBounds).intersects(x - textMargin, y - textMargin, 2 * textMargin, 2 * textMargin); 4648 } 4649 4650 /** 4651 * Returns the items that intersects with the rectangle of (<code>x0</code>, 4652 * <code>y0</code>), (<code>x1</code>, <code>y1</code>) opposite corners. 4653 */ 4654 protected List<Selectable> getSelectableItemsIntersectingRectangle(float x0, float y0, float x1, float y1) { 4655 List<Selectable> items = new ArrayList<Selectable>(); 4656 boolean basePlanLocked = this.home.isBasePlanLocked(); 4657 for (Selectable item : getVisibleItemsAtSelectedLevel()) { 4658 if ((!basePlanLocked 4659 || !isItemPartOfBasePlan(item)) 4660 && item.intersectsRectangle(x0, y0, x1, y1)) { 4661 items.add(item); 4662 } 4663 } 4664 ObserverCamera camera = this.home.getObserverCamera(); 4665 if (camera != null && camera.intersectsRectangle(x0, y0, x1, y1)) { 4666 items.add(camera); 4667 } 4668 return items; 4669 } 4670 4671 /** 4672 * Returns the selected piece of furniture with a point 4673 * at (<code>x</code>, <code>y</code>) that can be used to rotate the piece. 4674 */ 4675 private HomePieceOfFurniture getRotatedPieceOfFurnitureAt(float x, float y) { 4676 HomePieceOfFurniture selectedPiece = getSelectedMovablePieceOfFurniture(); 4677 if (selectedPiece != null) { 4678 float margin = getIndicatorMargin(); 4679 if (selectedPiece.isTopLeftPointAt(x, y, margin) 4680 // Ignore piece shape to ensure there's always enough space to drag it 4681 && !selectedPiece.containsPoint(x, y, 0)) { 4682 return selectedPiece; 4683 } 4684 } 4685 return null; 4686 } 4687 4688 /** 4689 * Returns the selected piece of furniture with a point 4690 * at (<code>x</code>, <code>y</code>) that can be used to elevate the piece. 4691 */ 4692 private HomePieceOfFurniture getElevatedPieceOfFurnitureAt(float x, float y) { 4693 HomePieceOfFurniture selectedPiece = getSelectedMovablePieceOfFurniture(); 4694 if (selectedPiece != null) { 4695 float margin = getIndicatorMargin(); 4696 if (selectedPiece.isTopRightPointAt(x, y, margin) 4697 // Ignore piece shape to ensure there's always enough space to drag it 4698 && !selectedPiece.containsPoint(x, y, 0)) { 4699 return selectedPiece; 4700 } 4701 } 4702 return null; 4703 } 4704 4705 /** 4706 * Returns the selected piece of furniture with a point 4707 * at (<code>x</code>, <code>y</code>) that can be used to resize the height 4708 * of the piece. 4709 */ 4710 private HomePieceOfFurniture getHeightResizedPieceOfFurnitureAt(float x, float y) { 4711 HomePieceOfFurniture selectedPiece = getSelectedResizablePieceOfFurniture(); 4712 if (selectedPiece != null) { 4713 float margin = getIndicatorMargin(); 4714 if (!selectedPiece.isHorizontallyRotated() 4715 && selectedPiece.isBottomLeftPointAt(x, y, margin) 4716 // Ignore piece shape to ensure there's always enough space to drag it 4717 && !selectedPiece.containsPoint(x, y, 0)) { 4718 return selectedPiece; 4719 } 4720 } 4721 return null; 4722 } 4723 4724 /** 4725 * Returns the selected piece of furniture with a point 4726 * at (<code>x</code>, <code>y</code>) that can be used to rotate the piece 4727 * around the pitch axis. 4728 */ 4729 private HomePieceOfFurniture getPitchRotatedPieceOfFurnitureAt(float x, float y) { 4730 HomePieceOfFurniture selectedPiece = getSelectedMovablePieceOfFurniture(); 4731 if (selectedPiece != null 4732 && this.getView().isFurnitureSizeInPlanSupported()) { 4733 float margin = getIndicatorMargin(); 4734 if (selectedPiece.getPitch() != 0 4735 && selectedPiece.isBottomLeftPointAt(x, y, margin) 4736 // Ignore piece shape to ensure there's always enough space to drag it 4737 && !selectedPiece.containsPoint(x, y, 0)) { 4738 return selectedPiece; 4739 } 4740 } 4741 return null; 4742 } 4743 4744 /** 4745 * Returns the selected piece of furniture with a point 4746 * at (<code>x</code>, <code>y</code>) that can be used to rotate the piece 4747 * around the roll axis. 4748 */ 4749 private HomePieceOfFurniture getRollRotatedPieceOfFurnitureAt(float x, float y) { 4750 HomePieceOfFurniture selectedPiece = getSelectedMovablePieceOfFurniture(); 4751 if (selectedPiece != null 4752 && this.getView().isFurnitureSizeInPlanSupported()) { 4753 float margin = getIndicatorMargin(); 4754 if (selectedPiece.getRoll() != 0 4755 && selectedPiece.isBottomLeftPointAt(x, y, margin) 4756 // Ignore piece shape to ensure there's always enough space to drag it 4757 && !selectedPiece.containsPoint(x, y, 0)) { 4758 return selectedPiece; 4759 } 4760 } 4761 return null; 4762 } 4763 4764 /** 4765 * Returns the selected piece of furniture with a point 4766 * at (<code>x</code>, <code>y</code>) that can be used to resize 4767 * the width and the depth of the piece. 4768 */ 4769 private HomePieceOfFurniture getWidthAndDepthResizedPieceOfFurnitureAt(float x, float y) { 4770 HomePieceOfFurniture selectedPiece = getSelectedResizablePieceOfFurniture(); 4771 if (selectedPiece != null) { 4772 float margin = getIndicatorMargin(); 4773 if (selectedPiece.isBottomRightPointAt(x, y, margin) 4774 // Ignore piece shape to ensure there's always enough space to drag it 4775 && !selectedPiece.containsPoint(x, y, 0)) { 4776 return selectedPiece; 4777 } 4778 } 4779 return null; 4780 } 4781 4782 /** 4783 * Returns the selected item if selection contains one selected movable piece of furniture. 4784 */ 4785 private HomePieceOfFurniture getSelectedMovablePieceOfFurniture() { 4786 HomePieceOfFurniture selectedPiece = getSelectedPieceOfFurniture(); 4787 if (selectedPiece != null 4788 && isItemMovable(selectedPiece)) { 4789 return selectedPiece; 4790 } 4791 return null; 4792 } 4793 4794 /** 4795 * Returns the selected item if selection contains one selected resizable piece of furniture. 4796 */ 4797 private HomePieceOfFurniture getSelectedResizablePieceOfFurniture() { 4798 HomePieceOfFurniture selectedPiece = getSelectedPieceOfFurniture(); 4799 if (selectedPiece != null 4800 && selectedPiece.isResizable() 4801 && isItemResizable(selectedPiece)) { 4802 return selectedPiece; 4803 } 4804 return null; 4805 } 4806 4807 /** 4808 * Returns the selected item if selection contains one selected piece of furniture. 4809 */ 4810 private HomePieceOfFurniture getSelectedPieceOfFurniture() { 4811 List<Selectable> selectedItems = this.home.getSelectedItems(); 4812 if (selectedItems.size() == 1 4813 && selectedItems.get(0) instanceof HomePieceOfFurniture) { 4814 HomePieceOfFurniture piece = (HomePieceOfFurniture)selectedItems.get(0); 4815 if (isPieceOfFurnitureVisibleAtSelectedLevel(piece)) { 4816 return piece; 4817 } 4818 } 4819 return null; 4820 } 4821 4822 /** 4823 * Returns the selected light with a point at (<code>x</code>, <code>y</code>) 4824 * that can be used to resize the power of the light. 4825 */ 4826 private HomeLight getModifiedLightPowerAt(float x, float y) { 4827 HomePieceOfFurniture selectedPiece = getSelectedPieceOfFurniture(); 4828 if (selectedPiece instanceof HomeLight) { 4829 float margin = getIndicatorMargin(); 4830 if (selectedPiece.isBottomLeftPointAt(x, y, margin) 4831 // Ignore piece shape to ensure there's always enough space to drag it 4832 && !selectedPiece.containsPoint(x, y, 0)) { 4833 return (HomeLight)selectedPiece; 4834 } 4835 } 4836 return null; 4837 } 4838 4839 /** 4840 * Returns the selected piece of furniture with its 4841 * name center point at (<code>x</code>, <code>y</code>). 4842 */ 4843 private HomePieceOfFurniture getPieceOfFurnitureNameAt(float x, float y) { 4844 HomePieceOfFurniture selectedPiece = getSelectedMovablePieceOfFurniture(); 4845 if (selectedPiece != null) { 4846 float margin = getIndicatorMargin(); 4847 if (selectedPiece.isNameVisible() 4848 && selectedPiece.getName().trim().length() > 0 4849 && selectedPiece.isNameCenterPointAt(x, y, margin)) { 4850 return selectedPiece; 4851 } 4852 } 4853 return null; 4854 } 4855 4856 /** 4857 * Returns the selected piece of furniture with its 4858 * name angle point at (<code>x</code>, <code>y</code>). 4859 */ 4860 private HomePieceOfFurniture getPieceOfFurnitureRotatedNameAt(float x, float y) { 4861 HomePieceOfFurniture selectedPiece = getSelectedMovablePieceOfFurniture(); 4862 if (selectedPiece != null) { 4863 float margin = getIndicatorMargin(); 4864 if (selectedPiece.isNameVisible() 4865 && selectedPiece.getName().trim().length() > 0 4866 && isTextAnglePointAt(selectedPiece, selectedPiece.getName(), selectedPiece.getNameStyle(), 4867 selectedPiece.getX() + selectedPiece.getNameXOffset(), selectedPiece.getY() + selectedPiece.getNameYOffset(), 4868 selectedPiece.getNameAngle(), x, y, margin)) { 4869 return selectedPiece; 4870 } 4871 } 4872 return null; 4873 } 4874 4875 /** 4876 * Returns <code>true</code> if the angle indicator of the <code>text</code> of an <code>item</code> displayed 4877 * at the point (<code>xText</code>, <code>yText</code>) is equal to the point (<code>x</code>, <code>y</code>). 4878 */ 4879 private boolean isTextAnglePointAt(Selectable item, String text, TextStyle textStyle, float xText, float yText, float textAngle, 4880 float x, float y, float margin) { 4881 if (textStyle == null) { 4882 textStyle = this.preferences.getDefaultTextStyle(item.getClass()); 4883 } 4884 float [][] textBounds = getView().getTextBounds(text, textStyle, xText, yText, textAngle); 4885 float anglePointX; 4886 float anglePointY; 4887 if (textStyle.getAlignment() == TextStyle.Alignment.LEFT) { 4888 anglePointX = textBounds [0][0]; 4889 anglePointY = textBounds [0][1]; 4890 } else if (textStyle.getAlignment() == TextStyle.Alignment.RIGHT) { 4891 anglePointX = textBounds [1][0]; 4892 anglePointY = textBounds [1][1]; 4893 } else { // CENTER 4894 anglePointX = (textBounds [0][0] + textBounds [1][0]) / 2; 4895 anglePointY = (textBounds [0][1] + textBounds [1][1]) / 2; 4896 } 4897 return Math.abs(x - anglePointX) <= margin 4898 && Math.abs(y - anglePointY) <= margin; 4899 } 4900 4901 /** 4902 * Returns the selected label with its angle point at (<code>x</code>, <code>y</code>). 4903 */ 4904 private Label getRotatedLabelAt(float x, float y) { 4905 List<Selectable> selectedItems = this.home.getSelectedItems(); 4906 if (selectedItems.size() == 1 4907 && selectedItems.get(0) instanceof Label 4908 && isItemMovable(selectedItems.get(0))) { 4909 Label label = (Label)selectedItems.get(0); 4910 float margin = getIndicatorMargin(); 4911 if (label.isAtLevel(this.home.getSelectedLevel()) 4912 && isTextAnglePointAt(label, label.getText(), label.getStyle(), 4913 label.getX(), label.getY(), label.getAngle(), x, y, margin)) { 4914 return label; 4915 } 4916 } 4917 return null; 4918 } 4919 4920 /** 4921 * Returns the selected label with its elevation point at (<code>x</code>, <code>y</code>). 4922 */ 4923 private Label getElevatedLabelAt(float x, float y) { 4924 List<Selectable> selectedItems = this.home.getSelectedItems(); 4925 if (selectedItems.size() == 1 4926 && selectedItems.get(0) instanceof Label) { 4927 Label label = (Label)selectedItems.get(0); 4928 if (label.getPitch() != null 4929 && isItemMovable(label)) { 4930 float margin = getIndicatorMargin(); 4931 if (label.isAtLevel(this.home.getSelectedLevel())) { 4932 TextStyle style = label.getStyle(); 4933 if (style == null) { 4934 style = this.preferences.getDefaultTextStyle(label.getClass()); 4935 } 4936 float [][] textBounds = getView().getTextBounds(label.getText(), getItemTextStyle(label, label.getStyle()), 4937 label.getX(), label.getY(), label.getAngle()); 4938 float pointX; 4939 float pointY; 4940 if (style.getAlignment() == TextStyle.Alignment.LEFT) { 4941 pointX = textBounds [3][0]; 4942 pointY = textBounds [3][1]; 4943 } else if (style.getAlignment() == TextStyle.Alignment.RIGHT) { 4944 pointX = textBounds [2][0]; 4945 pointY = textBounds [2][1]; 4946 } else { // CENTER 4947 pointX = (textBounds [2][0] + textBounds [3][0]) / 2; 4948 pointY = (textBounds [2][1] + textBounds [3][1]) / 2; 4949 } 4950 if (Math.abs(x - pointX) <= margin 4951 && Math.abs(y - pointY) <= margin) { 4952 return label; 4953 } 4954 } 4955 } 4956 } 4957 return null; 4958 } 4959 4960 /** 4961 * Returns the selected camera with a point at (<code>x</code>, <code>y</code>) 4962 * that can be used to change the camera yaw angle. 4963 */ 4964 private Camera getYawRotatedCameraAt(float x, float y) { 4965 List<Selectable> selectedItems = this.home.getSelectedItems(); 4966 if (selectedItems.size() == 1 4967 && selectedItems.get(0) instanceof Camera 4968 && isItemResizable(selectedItems.get(0))) { 4969 ObserverCamera camera = (ObserverCamera)selectedItems.get(0); 4970 float margin = getIndicatorMargin(); 4971 float [][] cameraPoints = camera.getPoints(); 4972 // Check if (x,y) matches the point between the first and the last points 4973 // of the rectangle surrounding camera 4974 float xMiddleFirstAndLastPoint = (cameraPoints [0][0] + cameraPoints [3][0]) / 2; 4975 float yMiddleFirstAndLastPoint = (cameraPoints [0][1] + cameraPoints [3][1]) / 2; 4976 if (Math.abs(x - xMiddleFirstAndLastPoint) <= margin 4977 && Math.abs(y - yMiddleFirstAndLastPoint) <= margin 4978 // Ignore camera shape to ensure there's always enough space to drag it 4979 && !camera.containsPoint(x, y, 0)) { 4980 return camera; 4981 } 4982 } 4983 return null; 4984 } 4985 4986 /** 4987 * Returns the selected camera with a point at (<code>x</code>, <code>y</code>) 4988 * that can be used to change the camera pitch angle. 4989 */ 4990 private Camera getPitchRotatedCameraAt(float x, float y) { 4991 List<Selectable> selectedItems = this.home.getSelectedItems(); 4992 if (selectedItems.size() == 1 4993 && selectedItems.get(0) instanceof Camera 4994 && isItemResizable(selectedItems.get(0))) { 4995 ObserverCamera camera = (ObserverCamera)selectedItems.get(0); 4996 float margin = getIndicatorMargin(); 4997 float [][] cameraPoints = camera.getPoints(); 4998 // Check if (x,y) matches the point between the second and the third points 4999 // of the rectangle surrounding camera 5000 float xMiddleFirstAndLastPoint = (cameraPoints [1][0] + cameraPoints [2][0]) / 2; 5001 float yMiddleFirstAndLastPoint = (cameraPoints [1][1] + cameraPoints [2][1]) / 2; 5002 if (Math.abs(x - xMiddleFirstAndLastPoint) <= margin 5003 && Math.abs(y - yMiddleFirstAndLastPoint) <= margin 5004 // Ignore camera shape to ensure there's always enough space to drag it 5005 && !camera.containsPoint(x, y, 0)) { 5006 return camera; 5007 } 5008 } 5009 return null; 5010 } 5011 5012 /** 5013 * Returns the selected camera with a point at (<code>x</code>, <code>y</code>) 5014 * that can be used to change the camera elevation. 5015 */ 5016 private Camera getElevatedCameraAt(float x, float y) { 5017 List<Selectable> selectedItems = this.home.getSelectedItems(); 5018 if (selectedItems.size() == 1 5019 && selectedItems.get(0) instanceof Camera 5020 && isItemResizable(selectedItems.get(0))) { 5021 ObserverCamera camera = (ObserverCamera)selectedItems.get(0); 5022 float margin = getIndicatorMargin(); 5023 float [][] cameraPoints = camera.getPoints(); 5024 // Check if (x,y) matches the point between the first and the second points 5025 // of the rectangle surrounding camera 5026 float xMiddleFirstAndSecondPoint = (cameraPoints [0][0] + cameraPoints [1][0]) / 2; 5027 float yMiddleFirstAndSecondPoint = (cameraPoints [0][1] + cameraPoints [1][1]) / 2; 5028 if (Math.abs(x - xMiddleFirstAndSecondPoint) <= margin 5029 && Math.abs(y - yMiddleFirstAndSecondPoint) <= margin 5030 // Ignore camera shape to ensure there's always enough space to drag it 5031 && !camera.containsPoint(x, y, 0)) { 5032 return camera; 5033 } 5034 } 5035 return null; 5036 } 5037 5038 /** 5039 * Returns the selected compass with a point 5040 * at (<code>x</code>, <code>y</code>) that can be used to rotate it. 5041 */ 5042 private Compass getRotatedCompassAt(float x, float y) { 5043 List<Selectable> selectedItems = this.home.getSelectedItems(); 5044 if (selectedItems.size() == 1 5045 && selectedItems.get(0) instanceof Compass 5046 && isItemMovable(selectedItems.get(0))) { 5047 Compass compass = (Compass)selectedItems.get(0); 5048 float margin = getIndicatorMargin(); 5049 float [][] compassPoints = compass.getPoints(); 5050 // Check if (x,y) matches the point between the third and the fourth points (South point) 5051 // of the rectangle surrounding compass 5052 float xMiddleThirdAndFourthPoint = (compassPoints [2][0] + compassPoints [3][0]) / 2; 5053 float yMiddleThirdAndFourthPoint = (compassPoints [2][1] + compassPoints [3][1]) / 2; 5054 if (Math.abs(x - xMiddleThirdAndFourthPoint) <= margin 5055 && Math.abs(y - yMiddleThirdAndFourthPoint) <= margin 5056 // Ignore camera shape to ensure there's always enough space to drag it 5057 && !compass.containsPoint(x, y, 0)) { 5058 return compass; 5059 } 5060 } 5061 return null; 5062 } 5063 5064 /** 5065 * Returns the selected compass with a point 5066 * at (<code>x</code>, <code>y</code>) that can be used to resize it. 5067 */ 5068 private Compass getResizedCompassAt(float x, float y) { 5069 List<Selectable> selectedItems = this.home.getSelectedItems(); 5070 if (selectedItems.size() == 1 5071 && selectedItems.get(0) instanceof Compass 5072 && isItemMovable(selectedItems.get(0))) { 5073 Compass compass = (Compass)selectedItems.get(0); 5074 float margin = getIndicatorMargin(); 5075 float [][] compassPoints = compass.getPoints(); 5076 // Check if (x,y) matches the point between the second and the third points (East point) 5077 // of the rectangle surrounding compass 5078 float xMiddleSecondAndThirdPoint = (compassPoints [1][0] + compassPoints [2][0]) / 2; 5079 float yMiddleSecondAndThirdPoint = (compassPoints [1][1] + compassPoints [2][1]) / 2; 5080 if (Math.abs(x - xMiddleSecondAndThirdPoint) <= margin 5081 && Math.abs(y - yMiddleSecondAndThirdPoint) <= margin 5082 // Ignore camera shape to ensure there's always enough space to drag it 5083 && !compass.containsPoint(x, y, 0)) { 5084 return compass; 5085 } 5086 } 5087 return null; 5088 } 5089 5090 /** 5091 * Deletes <code>items</code> in plan and record it as an undoable operation. 5092 */ 5093 public void deleteItems(List<? extends Selectable> items) { 5094 List<Selectable> deletedItems = new ArrayList<Selectable>(items.size()); 5095 for (Selectable item : items) { 5096 if (isItemDeletable(item)) { 5097 deletedItems.add(item); 5098 } 5099 } 5100 5101 if (!deletedItems.isEmpty()) { 5102 this.undoSupport.beginUpdate(); 5103 // Remove selectionListener to avoid level selection change 5104 // when selected items are deleted 5105 this.home.removeSelectionListener(this.selectionListener); 5106 // Start a compound edit that deletes walls, furniture and dimension lines from home 5107 5108 final boolean allLevelsSelection = home.isAllLevelsSelection(); 5109 final List<Selectable> selectedItems = new ArrayList<Selectable>(items); 5110 // Add a undoable edit that will select the undeleted items at undo 5111 this.undoSupport.postEdit(new ItemsDeletionStartUndoableEdit(this, this.home, 5112 allLevelsSelection, selectedItems.toArray(new Selectable [selectedItems.size()]))); 5113 5114 // Delete furniture with inherited method 5115 deleteFurniture(Home.getFurnitureSubList(deletedItems)); 5116 5117 List<Selectable> deletedOtherItems = 5118 new ArrayList<Selectable>(Home.getWallsSubList(deletedItems)); 5119 deletedOtherItems.addAll(Home.getRoomsSubList(deletedItems)); 5120 deletedOtherItems.addAll(Home.getDimensionLinesSubList(deletedItems)); 5121 deletedOtherItems.addAll(Home.getPolylinesSubList(deletedItems)); 5122 deletedOtherItems.addAll(Home.getLabelsSubList(deletedItems)); 5123 // First post to undo support that walls, rooms and dimension lines are deleted, 5124 // otherwise data about joined walls and rooms index can't be stored 5125 postDeleteItems(deletedOtherItems, this.home.isBasePlanLocked(), this.home.isAllLevelsSelection()); 5126 // Then delete items from plan 5127 doDeleteItems(deletedOtherItems); 5128 this.home.addSelectionListener(this.selectionListener); 5129 5130 this.undoSupport.postEdit(new ItemsDeletionEndUndoableEdit(this, this.home)); 5131 5132 // End compound edit 5133 this.undoSupport.endUpdate(); 5134 } 5135 } 5136 5137 /** 5138 * Undoable edit for items deletion start. 5139 */ 5140 private static class ItemsDeletionStartUndoableEdit extends AbstractUndoableEdit { 5141 private final PlanController controller; 5142 private final Home home; 5143 private final boolean allLevelsSelection; 5144 private final Selectable [] selectedItems; 5145 5146 public ItemsDeletionStartUndoableEdit(PlanController controller, Home home, 5147 boolean allLevelsSelection, Selectable [] selectedItems) { 5148 this.controller = controller; 5149 this.home = home; 5150 this.allLevelsSelection = allLevelsSelection; 5151 this.selectedItems = selectedItems; 5152 } 5153 5154 @Override 5155 public void undo() throws CannotRedoException { 5156 super.undo(); 5157 this.controller.selectAndShowItems(Arrays.asList(this.selectedItems), this.allLevelsSelection); 5158 } 5159 5160 @Override 5161 public void redo() throws CannotRedoException { 5162 super.redo(); 5163 this.home.removeSelectionListener(this.controller.getSelectionListener()); 5164 } 5165 } 5166 5167 /** 5168 * Undoable edit for items deletion end. 5169 */ 5170 private static class ItemsDeletionEndUndoableEdit extends AbstractUndoableEdit { 5171 private PlanController controller; 5172 private Home home; 5173 5174 public ItemsDeletionEndUndoableEdit(PlanController controller, Home home) { 5175 this.controller = controller; 5176 this.home = home; 5177 5178 } 5179 5180 @Override 5181 public void redo() throws CannotRedoException { 5182 super.redo(); 5183 this.home.addSelectionListener(this.controller.getSelectionListener()); 5184 } 5185 } 5186 5187 /** 5188 * Posts an undoable delete items operation about <code>deletedItems</code>. 5189 */ 5190 private void postDeleteItems(final List<? extends Selectable> deletedItems, 5191 final boolean basePlanLocked, 5192 final boolean allLevelsSelection) { 5193 // Manage walls 5194 List<Wall> deletedWalls = Home.getWallsSubList(deletedItems); 5195 // Get joined walls data for undo operation 5196 final JoinedWall [] joinedDeletedWalls = JoinedWall.getJoinedWalls(deletedWalls); 5197 5198 // Manage rooms and their index 5199 List<Room> deletedRooms = Home.getRoomsSubList(deletedItems); 5200 List<Room> homeRooms = this.home.getRooms(); 5201 // Sort the deleted rooms in the ascending order of their index in home 5202 TreeMap<Integer, Room> sortedMap = new TreeMap<Integer, Room>(); 5203 for (Room room : deletedRooms) { 5204 sortedMap.put(homeRooms.indexOf(room), room); 5205 } 5206 final Room [] rooms = sortedMap.values().toArray(new Room [sortedMap.size()]); 5207 final int [] roomsIndices = new int [rooms.length]; 5208 final Level [] roomsLevels = new Level [rooms.length]; 5209 int i = 0; 5210 for (int index : sortedMap.keySet()) { 5211 roomsIndices [i] = index; 5212 roomsLevels [i] = rooms [i].getLevel(); 5213 i++; 5214 } 5215 5216 // Manage dimension lines 5217 List<DimensionLine> deletedDimensionLines = Home.getDimensionLinesSubList(deletedItems); 5218 final DimensionLine [] dimensionLines = deletedDimensionLines.toArray( 5219 new DimensionLine [deletedDimensionLines.size()]); 5220 final Level [] dimensionLinesLevels = new Level [dimensionLines.length]; 5221 for (i = 0; i < dimensionLines.length; i++) { 5222 dimensionLinesLevels [i] = dimensionLines [i].getLevel(); 5223 } 5224 5225 // Manage polylines and their index 5226 List<Polyline> deletedPolylines = Home.getPolylinesSubList(deletedItems); 5227 List<Polyline> homePolylines = this.home.getPolylines(); 5228 // Sort the deleted polylines in the ascending order of their index in home 5229 TreeMap<Integer, Polyline> sortedPolylinesMap = new TreeMap<Integer, Polyline>(); 5230 for (Polyline polyline : deletedPolylines) { 5231 sortedPolylinesMap.put(homePolylines.indexOf(polyline), polyline); 5232 } 5233 final Polyline [] polylines = sortedPolylinesMap.values().toArray(new Polyline [sortedPolylinesMap.size()]); 5234 final int [] polylinesIndices = new int [polylines.length]; 5235 final Level [] polylinesLevels = new Level [polylines.length]; 5236 i = 0; 5237 for (int index : sortedPolylinesMap.keySet()) { 5238 polylinesIndices [i] = index; 5239 polylinesLevels [i] = polylines [i].getLevel(); 5240 i++; 5241 } 5242 5243 // Manage labels 5244 List<Label> deletedLabels = Home.getLabelsSubList(deletedItems); 5245 final Label [] labels = deletedLabels.toArray(new Label [deletedLabels.size()]); 5246 final Level [] labelsLevels = new Level [labels.length]; 5247 for (i = 0; i < labels.length; i++) { 5248 labelsLevels [i] = labels [i].getLevel(); 5249 } 5250 5251 this.undoSupport.postEdit(new ItemsDeletionUndoableEdit(this, this.preferences, basePlanLocked, allLevelsSelection, 5252 deletedItems.toArray(new Selectable [deletedItems.size()]), joinedDeletedWalls, 5253 rooms, roomsIndices, roomsLevels, 5254 dimensionLines, dimensionLinesLevels, 5255 polylines, polylinesIndices, polylinesLevels, 5256 labels, labelsLevels)); 5257 } 5258 5259 /** 5260 * Undoable edit for items deletion. 5261 */ 5262 private static class ItemsDeletionUndoableEdit extends LocalizedUndoableEdit { 5263 private final PlanController controller; 5264 private final boolean basePlanLocked; 5265 private final boolean allLevelsSelection; 5266 private final Selectable [] deletedItems; 5267 private final JoinedWall [] joinedDeletedWalls; 5268 private final Room [] rooms; 5269 private final int [] roomsIndices; 5270 private final Level [] roomsLevels; 5271 private final DimensionLine [] dimensionLines; 5272 private final Level [] dimensionLinesLevels; 5273 private final Polyline [] polylines; 5274 private final int [] polylinesIndices; 5275 private final Level [] polylinesLevels; 5276 private final Label [] labels; 5277 private final Level [] labelsLevels; 5278 5279 public ItemsDeletionUndoableEdit(PlanController controller, UserPreferences preferences, 5280 boolean basePlanLocked, boolean allLevelsSelection, 5281 Selectable [] deletedItems, 5282 JoinedWall [] joinedDeletedWalls, 5283 Room [] rooms, int [] roomsIndices, Level [] roomsLevels, 5284 DimensionLine [] dimensionLines, Level [] dimensionLinesLevels, 5285 Polyline [] polylines, int [] polylinesIndices, Level [] polylinesLevels, 5286 Label [] labels, Level [] labelsLevels) { 5287 super(preferences, PlanController.class, "undoDeleteSelectionName"); 5288 this.controller = controller; 5289 this.basePlanLocked = basePlanLocked; 5290 this.allLevelsSelection = allLevelsSelection; 5291 this.deletedItems = deletedItems; 5292 this.joinedDeletedWalls = joinedDeletedWalls; 5293 this.rooms = rooms; 5294 this.roomsIndices = roomsIndices; 5295 this.roomsLevels = roomsLevels; 5296 this.dimensionLines = dimensionLines; 5297 this.dimensionLinesLevels = dimensionLinesLevels; 5298 this.polylines = polylines; 5299 this.polylinesIndices = polylinesIndices; 5300 this.polylinesLevels = polylinesLevels; 5301 this.labels = labels; 5302 this.labelsLevels = labelsLevels; 5303 } 5304 5305 @Override 5306 public void undo() throws CannotUndoException { 5307 super.undo(); 5308 this.controller.doAddWalls(this.joinedDeletedWalls, this.basePlanLocked); 5309 this.controller.doAddRooms(this.rooms, this.roomsIndices, this.roomsLevels, null, this.basePlanLocked); 5310 this.controller.doAddDimensionLines(this.dimensionLines, this.dimensionLinesLevels, null, this.basePlanLocked); 5311 this.controller.doAddPolylines(this.polylines, this.polylinesIndices, this.polylinesLevels, null, this.basePlanLocked); 5312 this.controller.doAddLabels(this.labels, this.labelsLevels, null, this.basePlanLocked); 5313 this.controller.selectAndShowItems(Arrays.asList(this.deletedItems), this.allLevelsSelection); 5314 } 5315 5316 @Override 5317 public void redo() throws CannotRedoException { 5318 super.redo(); 5319 this.controller.selectItems(Arrays.asList(this.deletedItems)); 5320 this.controller.doDeleteWalls(this.joinedDeletedWalls, this.basePlanLocked); 5321 this.controller.doDeleteRooms(this.rooms, this.basePlanLocked); 5322 this.controller.doDeleteDimensionLines(this.dimensionLines, this.basePlanLocked); 5323 this.controller.doDeletePolylines(this.polylines, this.basePlanLocked); 5324 this.controller.doDeleteLabels(this.labels, this.basePlanLocked); 5325 } 5326 } 5327 5328 /** 5329 * Deletes <code>items</code> from home. 5330 */ 5331 private void doDeleteItems(List<Selectable> items) { 5332 boolean basePlanLocked = this.home.isBasePlanLocked(); 5333 for (Selectable item : items) { 5334 if (item instanceof Wall) { 5335 home.deleteWall((Wall)item); 5336 } else if (item instanceof DimensionLine) { 5337 home.deleteDimensionLine((DimensionLine)item); 5338 } else if (item instanceof Room) { 5339 home.deleteRoom((Room)item); 5340 } else if (item instanceof Polyline) { 5341 home.deletePolyline((Polyline)item); 5342 } else if (item instanceof Label) { 5343 home.deleteLabel((Label)item); 5344 } else if (item instanceof HomePieceOfFurniture) { 5345 home.deletePieceOfFurniture((HomePieceOfFurniture)item); 5346 } 5347 // Unlock base plan if item is a part of it 5348 basePlanLocked &= !isItemPartOfBasePlan(item); 5349 } 5350 this.home.setBasePlanLocked(basePlanLocked); 5351 this.home.setAllLevelsSelection(false); 5352 } 5353 5354 /** 5355 * Moves and shows selected items in plan component of (<code>dx</code>, 5356 * <code>dy</code>) units and record it as undoable operation. 5357 */ 5358 private void moveAndShowSelectedItems(float dx, float dy) { 5359 List<Selectable> selectedItems = this.home.getSelectedItems(); 5360 List<Selectable> movedItems = new ArrayList<Selectable>(selectedItems.size()); 5361 for (Selectable item : selectedItems) { 5362 if (isItemMovable(item)) { 5363 movedItems.add(item); 5364 } 5365 } 5366 5367 if (!movedItems.isEmpty()) { 5368 moveItems(movedItems, dx, dy); 5369 selectAndShowItems(movedItems, this.home.isAllLevelsSelection()); 5370 if (movedItems.size() != 1 5371 || !(movedItems.get(0) instanceof Camera)) { 5372 // Post move undo only for items different from the camera 5373 postItemsMove(movedItems, selectedItems, dx, dy); 5374 } 5375 } 5376 } 5377 5378 /** 5379 * Moves <code>items</code> of (<code>dx</code>, <code>dy</code>) units. 5380 */ 5381 public void moveItems(List<? extends Selectable> items, float dx, float dy) { 5382 for (Selectable item : items) { 5383 if (item instanceof Wall) { 5384 Wall wall = (Wall)item; 5385 // Remove temporarily listener to avoid side effect 5386 wall.removePropertyChangeListener(this.wallChangeListener); 5387 moveWallStartPoint(wall, 5388 wall.getXStart() + dx, wall.getYStart() + dy, 5389 !items.contains(wall.getWallAtStart())); 5390 moveWallEndPoint(wall, 5391 wall.getXEnd() + dx, wall.getYEnd() + dy, 5392 !items.contains(wall.getWallAtEnd())); 5393 resetAreaCache(); 5394 wall.addPropertyChangeListener(this.wallChangeListener); 5395 } else { 5396 boolean boundToWall = false; 5397 if (item instanceof HomeDoorOrWindow) { 5398 boundToWall = ((HomeDoorOrWindow)item).isBoundToWall(); 5399 } 5400 item.move(dx, dy); 5401 if (boundToWall) { 5402 Area itemArea = new Area(getPath(item.getPoints())); 5403 itemArea.intersect(getWallsArea(true)); 5404 ((HomeDoorOrWindow)item).setBoundToWall(!itemArea.isEmpty()); 5405 } 5406 } 5407 } 5408 } 5409 5410 /** 5411 * Moves <code>wall</code> start point to (<code>xStart</code>, <code>yStart</code>) 5412 * and the wall point joined to its start point if <code>moveWallAtStart</code> is true. 5413 */ 5414 private static void moveWallStartPoint(Wall wall, float xStart, float yStart, 5415 boolean moveWallAtStart) { 5416 float oldXStart = wall.getXStart(); 5417 float oldYStart = wall.getYStart(); 5418 wall.setXStart(xStart); 5419 wall.setYStart(yStart); 5420 Wall wallAtStart = wall.getWallAtStart(); 5421 // If wall is joined to a wall at its start 5422 // and this wall doesn't belong to the list of moved walls 5423 if (wallAtStart != null && moveWallAtStart) { 5424 // Move the wall start point or end point 5425 if (wallAtStart.getWallAtStart() == wall 5426 && (wallAtStart.getWallAtEnd() != wall 5427 || (wallAtStart.getXStart() == oldXStart 5428 && wallAtStart.getYStart() == oldYStart))) { 5429 wallAtStart.setXStart(xStart); 5430 wallAtStart.setYStart(yStart); 5431 } else if (wallAtStart.getWallAtEnd() == wall 5432 && (wallAtStart.getWallAtStart() != wall 5433 || (wallAtStart.getXEnd() == oldXStart 5434 && wallAtStart.getYEnd() == oldYStart))) { 5435 wallAtStart.setXEnd(xStart); 5436 wallAtStart.setYEnd(yStart); 5437 } 5438 } 5439 } 5440 5441 /** 5442 * Moves <code>wall</code> end point to (<code>xEnd</code>, <code>yEnd</code>) 5443 * and the wall point joined to its end if <code>moveWallAtEnd</code> is true. 5444 */ 5445 private static void moveWallEndPoint(Wall wall, float xEnd, float yEnd, 5446 boolean moveWallAtEnd) { 5447 float oldXEnd = wall.getXEnd(); 5448 float oldYEnd = wall.getYEnd(); 5449 wall.setXEnd(xEnd); 5450 wall.setYEnd(yEnd); 5451 Wall wallAtEnd = wall.getWallAtEnd(); 5452 // If wall is joined to a wall at its end 5453 // and this wall doesn't belong to the list of moved walls 5454 if (wallAtEnd != null && moveWallAtEnd) { 5455 // Move the wall start point or end point 5456 if (wallAtEnd.getWallAtStart() == wall 5457 && (wallAtEnd.getWallAtEnd() != wall 5458 || (wallAtEnd.getXStart() == oldXEnd 5459 && wallAtEnd.getYStart() == oldYEnd))) { 5460 wallAtEnd.setXStart(xEnd); 5461 wallAtEnd.setYStart(yEnd); 5462 } else if (wallAtEnd.getWallAtEnd() == wall 5463 && (wallAtEnd.getWallAtStart() != wall 5464 || (wallAtEnd.getXEnd() == oldXEnd 5465 && wallAtEnd.getYEnd() == oldYEnd))) { 5466 wallAtEnd.setXEnd(xEnd); 5467 wallAtEnd.setYEnd(yEnd); 5468 } 5469 } 5470 } 5471 5472 /** 5473 * Moves <code>wall</code> start point to (<code>x</code>, <code>y</code>) 5474 * if <code>editingStartPoint</code> is true or <code>wall</code> end point 5475 * to (<code>x</code>, <code>y</code>) if <code>editingStartPoint</code> is false. 5476 */ 5477 private static void moveWallPoint(Wall wall, float x, float y, boolean startPoint) { 5478 if (startPoint) { 5479 moveWallStartPoint(wall, x, y, true); 5480 } else { 5481 moveWallEndPoint(wall, x, y, true); 5482 } 5483 } 5484 5485 /** 5486 * Moves <code>room</code> point at the given index to (<code>x</code>, <code>y</code>). 5487 */ 5488 private static void moveRoomPoint(Room room, float x, float y, int pointIndex) { 5489 room.setPoint(x, y, pointIndex); 5490 } 5491 5492 /** 5493 * Moves <code>dimensionLine</code> start point to (<code>x</code>, <code>y</code>) 5494 * if <code>editingStartPoint</code> is true or <code>dimensionLine</code> end point 5495 * to (<code>x</code>, <code>y</code>) if <code>editingStartPoint</code> is false. 5496 */ 5497 private static void moveDimensionLinePoint(DimensionLine dimensionLine, float x, float y, boolean startPoint) { 5498 if (startPoint) { 5499 dimensionLine.setXStart(x); 5500 dimensionLine.setYStart(y); 5501 } else { 5502 dimensionLine.setXEnd(x); 5503 dimensionLine.setYEnd(y); 5504 } 5505 } 5506 5507 /** 5508 * Swaps start and end points of the given dimension line. 5509 */ 5510 private static void reverseDimensionLine(DimensionLine dimensionLine) { 5511 float swappedX = dimensionLine.getXStart(); 5512 float swappedY = dimensionLine.getYStart(); 5513 dimensionLine.setXStart(dimensionLine.getXEnd()); 5514 dimensionLine.setYStart(dimensionLine.getYEnd()); 5515 dimensionLine.setXEnd(swappedX); 5516 dimensionLine.setYEnd(swappedY); 5517 dimensionLine.setOffset(-dimensionLine.getOffset()); 5518 } 5519 5520 /** 5521 * Selects <code>items</code> and make them visible at screen. 5522 */ 5523 protected void selectAndShowItems(List<? extends Selectable> items) { 5524 selectAndShowItems(items, false); 5525 } 5526 5527 /** 5528 * Selects <code>items</code> and make them visible at screen. 5529 */ 5530 private void selectAndShowItems(List<? extends Selectable> items, boolean allLevelsSelection) { 5531 selectItems(items, allLevelsSelection); 5532 selectLevelFromSelectedItems(); 5533 getView().makeSelectionVisible(); 5534 } 5535 5536 /** 5537 * Selects <code>items</code>. 5538 */ 5539 protected void selectItems(List<? extends Selectable> items) { 5540 this.selectItems(items, false); 5541 } 5542 5543 /** 5544 * Selects <code>items</code>. 5545 */ 5546 private void selectItems(List<? extends Selectable> items, boolean allLevelsSelection) { 5547 // Remove selectionListener when selection is done from this controller 5548 // to control when selection should be made visible 5549 this.home.removeSelectionListener(this.selectionListener); 5550 this.home.setSelectedItems(items); 5551 this.home.addSelectionListener(this.selectionListener); 5552 this.home.setAllLevelsSelection(allLevelsSelection); 5553 } 5554 5555 /** 5556 * Selects the given <code>item</code>. 5557 */ 5558 public void selectItem(Selectable item) { 5559 selectItems(Arrays.asList(new Selectable [] {item})); 5560 } 5561 5562 /** 5563 * Toggles the selection of the given <code>item</code>. 5564 * @since 4.4 5565 */ 5566 public void toggleItemSelection(Selectable item) { 5567 List<Selectable> selectedItems = new ArrayList<Selectable>(this.home.getSelectedItems()); 5568 if (selectedItems.contains(item)) { 5569 selectedItems.remove(item); 5570 } else { 5571 selectedItems.add(item); 5572 } 5573 selectItems(selectedItems, this.home.isAllLevelsSelection()); 5574 } 5575 5576 /** 5577 * Deselects all walls in plan. 5578 */ 5579 private void deselectAll() { 5580 List<Selectable> emptyList = Collections.emptyList(); 5581 selectItems(emptyList); 5582 } 5583 5584 /** 5585 * Adds <code>items</code> to home and post an undoable operation. 5586 */ 5587 public void addItems(final List<? extends Selectable> items) { 5588 // Start a compound edit that adds walls, furniture, rooms, dimension lines and labels to home 5589 this.undoSupport.beginUpdate(); 5590 addFurniture(Home.getFurnitureSubList(items)); 5591 addWalls(Home.getWallsSubList(items)); 5592 addRooms(Home.getRoomsSubList(items)); 5593 addPolylines(Home.getPolylinesSubList(items)); 5594 addDimensionLines(Home.getDimensionLinesSubList(items)); 5595 addLabels(Home.getLabelsSubList(items)); 5596 this.home.setSelectedItems(items); 5597 5598 // Add a undoable edit that will select all the items at redo 5599 undoSupport.postEdit(new ItemsAdditionEndUndoableEdit(this.home, this.preferences, 5600 items.toArray(new Selectable [items.size()]))); 5601 // End compound edit 5602 undoSupport.endUpdate(); 5603 } 5604 5605 /** 5606 * Undoable edit for items addition end. 5607 */ 5608 private static class ItemsAdditionEndUndoableEdit extends LocalizedUndoableEdit { 5609 private final Home home; 5610 private final Selectable[] items; 5611 5612 private ItemsAdditionEndUndoableEdit(Home home, UserPreferences preferences, Selectable[] items) { 5613 super(preferences, PlanController.class, "undoAddItemsName"); 5614 this.home = home; 5615 this.items = items; 5616 } 5617 5618 @Override 5619 public void redo() throws CannotRedoException { 5620 super.redo(); 5621 this.home.setSelectedItems(Arrays.asList(this.items)); 5622 } 5623 } 5624 5625 /** 5626 * Adds furniture to home and updates door and window flags if they intersect with walls and magnetism is enabled. 5627 */ 5628 @Override 5629 public void addFurniture(List<HomePieceOfFurniture> furniture) { 5630 super.addFurniture(furniture); 5631 if (this.preferences.isMagnetismEnabled()) { 5632 Area wallsArea = getWallsArea(false); 5633 for (HomePieceOfFurniture piece : furniture) { 5634 if (piece instanceof HomeDoorOrWindow) { 5635 float [][] piecePoints = piece.getPoints(); 5636 Area pieceAreaIntersection = new Area(getPath(piecePoints)); 5637 pieceAreaIntersection.intersect(wallsArea); 5638 if (!pieceAreaIntersection.isEmpty() 5639 && new Room(piecePoints).getArea() / getArea(pieceAreaIntersection) > 0.999) { 5640 ((HomeDoorOrWindow) piece).setBoundToWall(true); 5641 } 5642 } 5643 } 5644 } 5645 } 5646 5647 /** 5648 * Adds <code>walls</code> to home and post an undoable new wall operation. 5649 */ 5650 public void addWalls(List<Wall> walls) { 5651 for (Wall wall : walls) { 5652 this.home.addWall(wall); 5653 } 5654 postCreateWalls(walls, this.home.getSelectedItems(), 5655 home.isBasePlanLocked(), home.isAllLevelsSelection()); 5656 } 5657 5658 /** 5659 * Posts an undoable new wall operation, about <code>newWalls</code>. 5660 */ 5661 private void postCreateWalls(List<Wall> newWalls, 5662 List<Selectable> oldSelectedItems, 5663 final boolean oldBasePlanLocked, 5664 final boolean oldAllLevelsSelection) { 5665 if (newWalls.size() > 0) { 5666 boolean basePlanLocked = this.home.isBasePlanLocked(); 5667 if (basePlanLocked) { 5668 for (Wall wall : newWalls) { 5669 // Unlock base plan if wall is a part of it 5670 basePlanLocked &= !isItemPartOfBasePlan(wall); 5671 } 5672 this.home.setBasePlanLocked(basePlanLocked); 5673 } 5674 final boolean newBasePlanLocked = basePlanLocked; 5675 5676 // Retrieve data about joined walls to newWalls 5677 final JoinedWall [] joinedNewWalls = JoinedWall.getJoinedWalls(newWalls); 5678 final Selectable [] oldSelection = 5679 oldSelectedItems.toArray(new Selectable [oldSelectedItems.size()]); 5680 this.undoSupport.postEdit(new WallsCreationUndoableEdit(this, this.preferences, 5681 oldSelection, oldBasePlanLocked, oldAllLevelsSelection, 5682 joinedNewWalls, newBasePlanLocked)); 5683 } 5684 } 5685 5686 /** 5687 * Undoable edit for walls creation. 5688 */ 5689 private static class WallsCreationUndoableEdit extends LocalizedUndoableEdit { 5690 private final PlanController controller; 5691 private final Selectable [] oldSelection; 5692 private final boolean oldBasePlanLocked; 5693 private final boolean oldAllLevelsSelection; 5694 private final JoinedWall [] joinedNewWalls; 5695 private final boolean newBasePlanLocked; 5696 5697 public WallsCreationUndoableEdit(PlanController controller, UserPreferences preferences, 5698 Selectable [] oldSelection, boolean oldBasePlanLocked, 5699 boolean oldAllLevelsSelection, JoinedWall [] joinedNewWalls, 5700 boolean newBasePlanLocked) { 5701 super(preferences, PlanController.class, "undoCreateWallsName"); 5702 this.controller = controller; 5703 this.oldSelection = oldSelection; 5704 this.oldBasePlanLocked = oldBasePlanLocked; 5705 this.oldAllLevelsSelection = oldAllLevelsSelection; 5706 this.joinedNewWalls = joinedNewWalls; 5707 this.newBasePlanLocked = newBasePlanLocked; 5708 } 5709 5710 @Override 5711 public void undo() throws CannotUndoException { 5712 super.undo(); 5713 this.controller.doDeleteWalls(this.joinedNewWalls, this.oldBasePlanLocked); 5714 this.controller.selectAndShowItems(Arrays.asList(this.oldSelection), this.oldAllLevelsSelection); 5715 } 5716 5717 @Override 5718 public void redo() throws CannotRedoException { 5719 super.redo(); 5720 this.controller.doAddWalls(this.joinedNewWalls, this.newBasePlanLocked); 5721 this.controller.selectAndShowItems(JoinedWall.getWalls(this.joinedNewWalls), false); 5722 } 5723 } 5724 5725 /** 5726 * Adds the walls in <code>joinedWalls</code> to plan component, joins 5727 * them to other walls if necessary. 5728 */ 5729 private void doAddWalls(JoinedWall [] joinedWalls, boolean basePlanLocked) { 5730 // First add all walls to home 5731 for (JoinedWall joinedNewWall : joinedWalls) { 5732 Wall wall = joinedNewWall.getWall(); 5733 this.home.addWall(wall); 5734 wall.setLevel(joinedNewWall.getLevel()); 5735 } 5736 this.home.setBasePlanLocked(basePlanLocked); 5737 5738 // Then join them to each other if necessary 5739 for (JoinedWall joinedNewWall : joinedWalls) { 5740 Wall wall = joinedNewWall.getWall(); 5741 Wall wallAtStart = joinedNewWall.getWallAtStart(); 5742 if (wallAtStart != null) { 5743 wall.setWallAtStart(wallAtStart); 5744 if (joinedNewWall.isJoinedAtEndOfWallAtStart()) { 5745 wallAtStart.setWallAtEnd(wall); 5746 } else { 5747 wallAtStart.setWallAtStart(wall); 5748 } 5749 } 5750 Wall wallAtEnd = joinedNewWall.getWallAtEnd(); 5751 if (wallAtEnd != null) { 5752 wall.setWallAtEnd(wallAtEnd); 5753 if (joinedNewWall.isJoinedAtStartOfWallAtEnd()) { 5754 wallAtEnd.setWallAtStart(wall); 5755 } else { 5756 wallAtEnd.setWallAtEnd(wall); 5757 } 5758 } 5759 } 5760 } 5761 5762 /** 5763 * Deletes walls referenced in <code>joinedDeletedWalls</code>. 5764 */ 5765 private void doDeleteWalls(JoinedWall [] joinedDeletedWalls, 5766 boolean basePlanLocked) { 5767 for (JoinedWall joinedWall : joinedDeletedWalls) { 5768 this.home.deleteWall(joinedWall.getWall()); 5769 } 5770 this.home.setBasePlanLocked(basePlanLocked); 5771 } 5772 5773 /** 5774 * Add <code>newRooms</code> to home and post an undoable new room line operation. 5775 */ 5776 public void addRooms(List<Room> rooms) { 5777 final Room [] newRooms = rooms.toArray(new Room [rooms.size()]); 5778 // Get indices of rooms added to home 5779 final int [] roomsIndex = new int [rooms.size()]; 5780 int endIndex = home.getRooms().size(); 5781 for (int i = 0; i < roomsIndex.length; i++) { 5782 roomsIndex [i] = endIndex++; 5783 this.home.addRoom(newRooms [i], roomsIndex [i]); 5784 } 5785 postCreateRooms(newRooms, roomsIndex, this.home.getSelectedItems(), 5786 this.home.isBasePlanLocked(), this.home.isAllLevelsSelection()); 5787 } 5788 5789 /** 5790 * Posts an undoable new room operation, about <code>newRooms</code>. 5791 */ 5792 private void postCreateRooms(final Room [] newRooms, 5793 final int [] roomsIndex, 5794 List<Selectable> oldSelectedItems, 5795 final boolean oldBasePlanLocked, 5796 final boolean oldAllLevelsSelection) { 5797 if (newRooms.length > 0) { 5798 boolean basePlanLocked = this.home.isBasePlanLocked(); 5799 if (basePlanLocked) { 5800 for (Room room : newRooms) { 5801 // Unlock base plan if room is a part of it 5802 basePlanLocked &= !isItemPartOfBasePlan(room); 5803 } 5804 this.home.setBasePlanLocked(basePlanLocked); 5805 } 5806 final boolean newBasePlanLocked = basePlanLocked; 5807 5808 final Selectable [] oldSelection = 5809 oldSelectedItems.toArray(new Selectable [oldSelectedItems.size()]); 5810 final Level roomsLevel = this.home.getSelectedLevel(); 5811 this.undoSupport.postEdit(new RoomsCreationUndoableEdit(this, this.preferences, 5812 oldSelection, oldBasePlanLocked, oldAllLevelsSelection, 5813 newRooms, roomsIndex, roomsLevel, newBasePlanLocked)); 5814 } 5815 } 5816 5817 /** 5818 * Undoable edit for rooms creation. 5819 */ 5820 private static class RoomsCreationUndoableEdit extends LocalizedUndoableEdit { 5821 private final PlanController controller; 5822 private final Selectable [] oldSelection; 5823 private final boolean oldBasePlanLocked; 5824 private final boolean oldAllLevelsSelection; 5825 private final Room [] newRooms; 5826 private final int [] roomsIndex; 5827 private final Level roomsLevel; 5828 private final boolean newBasePlanLocked; 5829 5830 public RoomsCreationUndoableEdit(PlanController controller, UserPreferences preferences, 5831 Selectable [] oldSelection, boolean oldBasePlanLocked, boolean oldAllLevelsSelection, 5832 Room [] newRooms, int [] roomsIndex, Level roomsLevel, 5833 boolean newBasePlanLocked) { 5834 super(preferences, PlanController.class, "undoCreateRoomsName"); 5835 this.controller = controller; 5836 this.oldSelection = oldSelection; 5837 this.oldBasePlanLocked = oldBasePlanLocked; 5838 this.oldAllLevelsSelection = oldAllLevelsSelection; 5839 this.newRooms = newRooms; 5840 this.roomsIndex = roomsIndex; 5841 this.roomsLevel = roomsLevel; 5842 this.newBasePlanLocked = newBasePlanLocked; 5843 } 5844 5845 @Override 5846 public void undo() throws CannotUndoException { 5847 super.undo(); 5848 this.controller.doDeleteRooms(this.newRooms, this.oldBasePlanLocked); 5849 this.controller.selectAndShowItems(Arrays.asList(this.oldSelection), this.oldAllLevelsSelection); 5850 } 5851 5852 @Override 5853 public void redo() throws CannotRedoException { 5854 super.redo(); 5855 this.controller.doAddRooms(this.newRooms, this.roomsIndex, null, this.roomsLevel, this.newBasePlanLocked); 5856 this.controller.selectAndShowItems(Arrays.asList(this.newRooms), false); 5857 } 5858 } 5859 5860 /** 5861 * Posts an undoable new room operation, about <code>newRooms</code>. 5862 */ 5863 private void postCreateRooms(List<Room> rooms, 5864 List<Selectable> oldSelection, 5865 boolean basePlanLocked, 5866 boolean allLevelsSelection) { 5867 // Search the index of rooms in home list of rooms 5868 Room [] newRooms = rooms.toArray(new Room [rooms.size()]); 5869 int [] roomsIndex = new int [rooms.size()]; 5870 List<Room> homeRooms = this.home.getRooms(); 5871 for (int i = 0; i < roomsIndex.length; i++) { 5872 roomsIndex [i] = homeRooms.lastIndexOf(newRooms [i]); 5873 } 5874 postCreateRooms(newRooms, roomsIndex, oldSelection, basePlanLocked, allLevelsSelection); 5875 } 5876 5877 /** 5878 * Adds the <code>rooms</code> to plan component. 5879 */ 5880 private void doAddRooms(Room [] rooms, 5881 int [] roomsIndices, 5882 Level [] roomsLevels, 5883 Level uniqueRoomsLevel, 5884 boolean basePlanLocked) { 5885 for (int i = 0; i < roomsIndices.length; i++) { 5886 this.home.addRoom (rooms [i], roomsIndices [i]); 5887 rooms [i].setLevel(roomsLevels != null 5888 ? roomsLevels [i] 5889 : uniqueRoomsLevel); 5890 } 5891 this.home.setBasePlanLocked(basePlanLocked); 5892 } 5893 5894 /** 5895 * Deletes <code>rooms</code>. 5896 */ 5897 private void doDeleteRooms(Room [] rooms, 5898 boolean basePlanLocked) { 5899 for (Room room : rooms) { 5900 this.home.deleteRoom(room); 5901 } 5902 this.home.setBasePlanLocked(basePlanLocked); 5903 } 5904 5905 /** 5906 * Add <code>dimensionLines</code> to home and post an undoable new dimension line operation. 5907 */ 5908 public void addDimensionLines(List<DimensionLine> dimensionLines) { 5909 for (DimensionLine dimensionLine : dimensionLines) { 5910 this.home.addDimensionLine(dimensionLine); 5911 } 5912 postCreateDimensionLines(dimensionLines, this.home.getSelectedItems(), 5913 this.home.isBasePlanLocked(), this.home.isAllLevelsSelection()); 5914 } 5915 5916 /** 5917 * Posts an undoable new dimension line operation, about <code>newDimensionLines</code>. 5918 */ 5919 private void postCreateDimensionLines(List<DimensionLine> newDimensionLines, 5920 List<Selectable> oldSelectedItems, 5921 final boolean oldBasePlanLocked, 5922 final boolean oldAllLevelsSelection) { 5923 if (newDimensionLines.size() > 0) { 5924 boolean basePlanLocked = this.home.isBasePlanLocked(); 5925 if (basePlanLocked) { 5926 for (DimensionLine dimensionLine : newDimensionLines) { 5927 // Unlock base plan if dimension line is a part of it 5928 basePlanLocked &= !isItemPartOfBasePlan(dimensionLine); 5929 } 5930 this.home.setBasePlanLocked(basePlanLocked); 5931 } 5932 final boolean newBasePlanLocked = basePlanLocked; 5933 5934 final DimensionLine [] dimensionLines = newDimensionLines.toArray( 5935 new DimensionLine [newDimensionLines.size()]); 5936 final Selectable [] oldSelection = 5937 oldSelectedItems.toArray(new Selectable [oldSelectedItems.size()]); 5938 final Level dimensionLinesLevel = this.home.getSelectedLevel(); 5939 this.undoSupport.postEdit(new DimensionLinesCreationUndoableEdit(this, this.preferences, 5940 oldSelection, oldBasePlanLocked, oldAllLevelsSelection, dimensionLines, 5941 dimensionLinesLevel, newBasePlanLocked)); 5942 } 5943 } 5944 5945 /** 5946 * Undoable edit for dimension lines creation. 5947 */ 5948 private static class DimensionLinesCreationUndoableEdit extends LocalizedUndoableEdit { 5949 private final PlanController controller; 5950 private final Selectable [] oldSelection; 5951 private final boolean oldBasePlanLocked; 5952 private final boolean oldAllLevelsSelection; 5953 private final DimensionLine [] dimensionLines; 5954 private final Level dimensionLinesLevel; 5955 private final boolean newBasePlanLocked; 5956 5957 public DimensionLinesCreationUndoableEdit(PlanController controller, UserPreferences preferences, Selectable [] oldSelection, boolean oldBasePlanLocked, 5958 boolean oldAllLevelsSelection, DimensionLine [] dimensionLines, 5959 Level dimensionLinesLevel, boolean newBasePlanLocked) { 5960 super(preferences, PlanController.class, "undoCreateDimensionLinesName"); 5961 this.controller = controller; 5962 this.oldSelection = oldSelection; 5963 this.oldBasePlanLocked = oldBasePlanLocked; 5964 this.oldAllLevelsSelection = oldAllLevelsSelection; 5965 this.dimensionLines = dimensionLines; 5966 this.dimensionLinesLevel = dimensionLinesLevel; 5967 this.newBasePlanLocked = newBasePlanLocked; 5968 } 5969 5970 @Override 5971 public void undo() throws CannotUndoException { 5972 super.undo(); 5973 this.controller.doDeleteDimensionLines(this.dimensionLines, this.oldBasePlanLocked); 5974 this.controller.selectAndShowItems(Arrays.asList(this.oldSelection), this.oldAllLevelsSelection); 5975 } 5976 5977 @Override 5978 public void redo() throws CannotRedoException { 5979 super.redo(); 5980 this.controller.doAddDimensionLines(this.dimensionLines, null, this.dimensionLinesLevel, this.newBasePlanLocked); 5981 this.controller.selectAndShowItems(Arrays.asList(this.dimensionLines), false); 5982 } 5983 } 5984 5985 /** 5986 * Adds the dimension lines in <code>dimensionLines</code> to plan component. 5987 */ 5988 private void doAddDimensionLines(DimensionLine [] dimensionLines, 5989 Level [] dimensionLinesLevels, 5990 Level uniqueDimensionLinesLevel, boolean basePlanLocked) { 5991 for (int i = 0; i < dimensionLines.length; i++) { 5992 DimensionLine dimensionLine = dimensionLines [i]; 5993 this.home.addDimensionLine(dimensionLine); 5994 dimensionLine.setLevel(dimensionLinesLevels != null 5995 ? dimensionLinesLevels [i] 5996 : uniqueDimensionLinesLevel); 5997 } 5998 this.home.setBasePlanLocked(basePlanLocked); 5999 } 6000 6001 /** 6002 * Deletes dimension lines in <code>dimensionLines</code>. 6003 */ 6004 private void doDeleteDimensionLines(DimensionLine [] dimensionLines, 6005 boolean basePlanLocked) { 6006 for (DimensionLine dimensionLine : dimensionLines) { 6007 this.home.deleteDimensionLine(dimensionLine); 6008 } 6009 this.home.setBasePlanLocked(basePlanLocked); 6010 } 6011 6012 /** 6013 * Add <code>newPolylines</code> to home and post an undoable new polyline line operation. 6014 */ 6015 public void addPolylines(List<Polyline> polylines) { 6016 final Polyline [] newPolylines = polylines.toArray(new Polyline [polylines.size()]); 6017 // Get indices of polylines added to home 6018 final int [] polylinesIndex = new int [polylines.size()]; 6019 int endIndex = home.getPolylines().size(); 6020 for (int i = 0; i < polylinesIndex.length; i++) { 6021 polylinesIndex [i] = endIndex++; 6022 this.home.addPolyline(newPolylines [i], polylinesIndex [i]); 6023 } 6024 postCreatePolylines(newPolylines, polylinesIndex, this.home.getSelectedItems(), 6025 this.home.isBasePlanLocked(), this.home.isAllLevelsSelection()); 6026 } 6027 6028 /** 6029 * Posts an undoable new polyline operation about <code>newPolylines</code>. 6030 */ 6031 private void postCreatePolylines(final Polyline [] newPolylines, 6032 final int [] polylinesIndex, 6033 List<Selectable> oldSelectedItems, 6034 final boolean oldBasePlanLocked, 6035 final boolean oldAllLevelsSelection) { 6036 if (newPolylines.length > 0) { 6037 boolean basePlanLocked = this.home.isBasePlanLocked(); 6038 if (basePlanLocked) { 6039 for (Polyline polyline : newPolylines) { 6040 // Unlock base plan if polyline is a part of it 6041 basePlanLocked &= !isItemPartOfBasePlan(polyline); 6042 } 6043 this.home.setBasePlanLocked(basePlanLocked); 6044 } 6045 final boolean newBasePlanLocked = basePlanLocked; 6046 6047 final Selectable [] oldSelection = 6048 oldSelectedItems.toArray(new Selectable [oldSelectedItems.size()]); 6049 final Level polylinesLevel = this.home.getSelectedLevel(); 6050 this.undoSupport.postEdit(new PolylinesCreationUndoableEdit(this, this.preferences, 6051 oldSelection, oldBasePlanLocked, oldAllLevelsSelection, 6052 newPolylines, polylinesIndex, polylinesLevel, newBasePlanLocked)); 6053 } 6054 } 6055 6056 /** 6057 * Undoable edit for polylines creation. 6058 */ 6059 private static class PolylinesCreationUndoableEdit extends LocalizedUndoableEdit { 6060 private final PlanController controller; 6061 private final Selectable [] oldSelection; 6062 private final boolean oldBasePlanLocked; 6063 private final boolean oldAllLevelsSelection; 6064 private final Polyline [] newPolylines; 6065 private final int [] polylinesIndex; 6066 private final Level polylinesLevel; 6067 private final boolean newBasePlanLocked; 6068 6069 public PolylinesCreationUndoableEdit(PlanController controller, UserPreferences preferences, 6070 Selectable [] oldSelection, boolean oldBasePlanLocked, boolean oldAllLevelsSelection, 6071 Polyline [] newPolylines, int [] polylinesIndex, 6072 Level polylinesLevel, boolean newBasePlanLocked) { 6073 super(preferences, PlanController.class, "undoCreatePolylinesName"); 6074 this.controller = controller; 6075 this.oldSelection = oldSelection; 6076 this.oldBasePlanLocked = oldBasePlanLocked; 6077 this.oldAllLevelsSelection = oldAllLevelsSelection; 6078 this.newPolylines = newPolylines; 6079 this.polylinesIndex = polylinesIndex; 6080 this.polylinesLevel = polylinesLevel; 6081 this.newBasePlanLocked = newBasePlanLocked; 6082 } 6083 6084 @Override 6085 public void undo() throws CannotUndoException { 6086 super.undo(); 6087 this.controller.doDeletePolylines(this.newPolylines, this.oldBasePlanLocked); 6088 this.controller.selectAndShowItems(Arrays.asList(this.oldSelection), this.oldAllLevelsSelection); 6089 } 6090 6091 @Override 6092 public void redo() throws CannotRedoException { 6093 super.redo(); 6094 this.controller.doAddPolylines(this.newPolylines, this.polylinesIndex, null, this.polylinesLevel, this.newBasePlanLocked); 6095 this.controller.selectAndShowItems(Arrays.asList(this.newPolylines)); 6096 } 6097 } 6098 6099 /** 6100 * Posts an undoable new polyline operation about <code>newPolylines</code>. 6101 */ 6102 private void postCreatePolylines(List<Polyline> polylines, 6103 List<Selectable> oldSelection, 6104 boolean basePlanLocked, 6105 boolean allLevelsSelection) { 6106 // Search the index of polylines in home list of polylines 6107 Polyline [] newPolylines = polylines.toArray(new Polyline [polylines.size()]); 6108 int [] polylinesIndex = new int [polylines.size()]; 6109 List<Polyline> homePolylines = home.getPolylines(); 6110 for (int i = 0; i < polylinesIndex.length; i++) { 6111 polylinesIndex [i] = homePolylines.lastIndexOf(newPolylines [i]); 6112 } 6113 postCreatePolylines(newPolylines, polylinesIndex, oldSelection, basePlanLocked, allLevelsSelection); 6114 } 6115 6116 /** 6117 * Adds the <code>polylines</code> to plan component. 6118 */ 6119 private void doAddPolylines(Polyline [] polylines, 6120 int [] polylinesIndex, 6121 Level [] polylinesLevels, 6122 Level uniqueDimensionLinesLevel, 6123 boolean basePlanLocked) { 6124 for (int i = 0; i < polylinesIndex.length; i++) { 6125 this.home.addPolyline(polylines [i], polylinesIndex [i]); 6126 polylines [i].setLevel(polylinesLevels != null 6127 ? polylinesLevels [i] 6128 : uniqueDimensionLinesLevel); 6129 } 6130 this.home.setBasePlanLocked(basePlanLocked); 6131 } 6132 6133 /** 6134 * Deletes <code>polylines</code>. 6135 */ 6136 private void doDeletePolylines(Polyline [] polylines, 6137 boolean basePlanLocked) { 6138 for (Polyline polyline : polylines) { 6139 this.home.deletePolyline(polyline); 6140 } 6141 this.home.setBasePlanLocked(basePlanLocked); 6142 } 6143 6144 /** 6145 * Add <code>labels</code> to home and post an undoable new label operation. 6146 */ 6147 public void addLabels(List<Label> labels) { 6148 for (Label label : labels) { 6149 this.home.addLabel(label); 6150 } 6151 postCreateLabels(labels, this.home.getSelectedItems(), 6152 this.home.isBasePlanLocked(), this.home.isAllLevelsSelection()); 6153 } 6154 6155 /** 6156 * Posts an undoable new label operation, about <code>newLabels</code>. 6157 */ 6158 private void postCreateLabels(List<Label> newLabels, 6159 List<Selectable> oldSelectedItems, 6160 final boolean oldBasePlanLocked, 6161 final boolean oldAllLevelsSelection) { 6162 if (newLabels.size() > 0) { 6163 boolean basePlanLocked = this.home.isBasePlanLocked(); 6164 if (basePlanLocked) { 6165 for (Label label : newLabels) { 6166 // Unlock base plan if label is a part of it 6167 basePlanLocked &= !isItemPartOfBasePlan(label); 6168 } 6169 this.home.setBasePlanLocked(basePlanLocked); 6170 } 6171 final boolean newBasePlanLocked = basePlanLocked; 6172 6173 final Label [] labels = newLabels.toArray(new Label [newLabels.size()]); 6174 final Selectable [] oldSelection = 6175 oldSelectedItems.toArray(new Selectable [oldSelectedItems.size()]); 6176 final Level labelsLevel = this.home.getSelectedLevel(); 6177 this.undoSupport.postEdit(new LabelsCreationUndoableEdit(this, this.preferences, 6178 oldSelection, oldBasePlanLocked, oldAllLevelsSelection, 6179 labels, labelsLevel, newBasePlanLocked)); 6180 } 6181 } 6182 6183 /** 6184 * Undoable edit for label creation. 6185 */ 6186 private static class LabelsCreationUndoableEdit extends LocalizedUndoableEdit { 6187 private final PlanController controller; 6188 private final Selectable [] oldSelection; 6189 private final boolean oldBasePlanLocked; 6190 private final boolean oldAllLevelsSelection; 6191 private final Label [] labels; 6192 private final Level labelsLevel; 6193 private final boolean newBasePlanLocked; 6194 6195 public LabelsCreationUndoableEdit(PlanController controller, UserPreferences preferences, 6196 Selectable [] oldSelection, boolean oldBasePlanLocked, boolean oldAllLevelsSelection, 6197 Label [] labels, Level labelsLevel, boolean newBasePlanLocked) { 6198 super(preferences, PlanController.class, "undoCreateLabelsName"); 6199 this.controller = controller; 6200 this.oldSelection = oldSelection; 6201 this.oldBasePlanLocked = oldBasePlanLocked; 6202 this.oldAllLevelsSelection = oldAllLevelsSelection; 6203 this.labels = labels; 6204 this.labelsLevel = labelsLevel; 6205 this.newBasePlanLocked = newBasePlanLocked; 6206 } 6207 6208 @Override 6209 public void undo() throws CannotUndoException { 6210 super.undo(); 6211 this.controller.doDeleteLabels(this.labels, this.oldBasePlanLocked); 6212 this.controller.selectAndShowItems(Arrays.asList(this.oldSelection), this.oldAllLevelsSelection); 6213 } 6214 6215 @Override 6216 public void redo() throws CannotRedoException { 6217 super.redo(); 6218 this.controller.doAddLabels(this.labels, null, this.labelsLevel, this.newBasePlanLocked); 6219 this.controller.selectAndShowItems(Arrays.asList(this.labels), false); 6220 } 6221 } 6222 6223 /** 6224 * Adds the labels in <code>labels</code> to plan component. 6225 */ 6226 private void doAddLabels(Label [] labels, Level [] labelsLevels, Level uniqueLabelLevel, boolean basePlanLocked) { 6227 for (int i = 0; i < labels.length; i++) { 6228 Label label = labels [i]; 6229 this.home.addLabel(label); 6230 label.setLevel(labelsLevels != null 6231 ? labelsLevels [i] 6232 : uniqueLabelLevel); 6233 } 6234 this.home.setBasePlanLocked(basePlanLocked); 6235 } 6236 6237 /** 6238 * Deletes labels in <code>labels</code>. 6239 */ 6240 private void doDeleteLabels(Label [] labels, boolean basePlanLocked) { 6241 for (Label label : labels) { 6242 this.home.deleteLabel(label); 6243 } 6244 this.home.setBasePlanLocked(basePlanLocked); 6245 } 6246 6247 /** 6248 * Posts an undoable operation about <code>label</code> angle change. 6249 */ 6250 private void postLabelRotation(final Label label, final float oldAngle) { 6251 final float newAngle = label.getAngle(); 6252 if (newAngle != oldAngle) { 6253 this.undoSupport.postEdit(new LabelRotationUndoableEdit(this, this.preferences, 6254 oldAngle, label, newAngle)); 6255 } 6256 } 6257 6258 /** 6259 * Undoable edit for label rotation. 6260 */ 6261 private static class LabelRotationUndoableEdit extends LocalizedUndoableEdit { 6262 private final PlanController controller; 6263 private final float oldAngle; 6264 private final Label label; 6265 private final float newAngle; 6266 6267 public LabelRotationUndoableEdit(PlanController controller, UserPreferences preferences, 6268 float oldAngle, Label label, float newAngle) { 6269 super(preferences, PlanController.class, "undoLabelRotationName"); 6270 this.controller = controller; 6271 this.oldAngle = oldAngle; 6272 this.label = label; 6273 this.newAngle = newAngle; 6274 } 6275 6276 @Override 6277 public void undo() throws CannotUndoException { 6278 super.undo(); 6279 this.label.setAngle(this.oldAngle); 6280 this.controller.selectAndShowItems(Arrays.asList(new Label [] {this.label})); 6281 } 6282 6283 @Override 6284 public void redo() throws CannotRedoException { 6285 super.redo(); 6286 this.label.setAngle(this.newAngle); 6287 this.controller.selectAndShowItems(Arrays.asList(new Label [] {this.label})); 6288 } 6289 } 6290 6291 /** 6292 * Post to undo support an elevation change on <code>label</code>. 6293 */ 6294 private void postLabelElevation(final Label label, final float oldElevation) { 6295 final float newElevation = label.getElevation(); 6296 if (newElevation != oldElevation) { 6297 this.undoSupport.postEdit(new LabelElevationModificationUndoableEdit(this, this.preferences, 6298 oldElevation, label, newElevation)); 6299 } 6300 } 6301 6302 /** 6303 * Undoable edit for label elevation modification. 6304 */ 6305 private static class LabelElevationModificationUndoableEdit extends LocalizedUndoableEdit { 6306 private final PlanController controller; 6307 private final float oldElevation; 6308 private final Label label; 6309 private final float newElevation; 6310 6311 public LabelElevationModificationUndoableEdit(PlanController controller, UserPreferences preferences, 6312 float oldElevation, Label label, float newElevation) { 6313 super(preferences, PlanController.class, 6314 oldElevation < newElevation 6315 ? "undoLabelRaiseName" 6316 : "undoLabelLowerName"); 6317 this.controller = controller; 6318 this.oldElevation = oldElevation; 6319 this.label = label; 6320 this.newElevation = newElevation; 6321 } 6322 6323 @Override 6324 public void undo() throws CannotUndoException { 6325 super.undo(); 6326 this.label.setElevation(this.oldElevation); 6327 this.controller.selectAndShowItems(Arrays.asList(new Label [] {this.label})); 6328 } 6329 6330 @Override 6331 public void redo() throws CannotRedoException { 6332 super.redo(); 6333 this.label.setElevation(this.newElevation); 6334 this.controller.selectAndShowItems(Arrays.asList(new Label [] {this.label})); 6335 } 6336 } 6337 6338 /** 6339 * Posts an undoable operation of a (<code>dx</code>, <code>dy</code>) move 6340 * of <code>movedItems</code>. 6341 */ 6342 private void postItemsMove(List<? extends Selectable> movedItems, 6343 List<? extends Selectable> oldSelectedItems, 6344 final float dx, final float dy) { 6345 if (dx != 0 || dy != 0) { 6346 // Store the moved items in an array 6347 final Selectable [] itemsArray = 6348 movedItems.toArray(new Selectable [movedItems.size()]); 6349 final boolean allLevelsSelection = home.isAllLevelsSelection(); 6350 final Selectable [] oldSelection = 6351 oldSelectedItems.toArray(new Selectable [oldSelectedItems.size()]); 6352 this.undoSupport.postEdit(new ItemsMovingUndoableEdit(this, this.preferences, 6353 oldSelection, allLevelsSelection, itemsArray, dx, dy)); 6354 } 6355 } 6356 6357 /** 6358 * Undoable edit for moving items. 6359 */ 6360 private static class ItemsMovingUndoableEdit extends LocalizedUndoableEdit { 6361 private final PlanController controller; 6362 private final Selectable [] oldSelection; 6363 private final boolean allLevelsSelection; 6364 private final Selectable [] itemsArray; 6365 private final float dx; 6366 private final float dy; 6367 6368 public ItemsMovingUndoableEdit(PlanController controller, UserPreferences preferences, 6369 Selectable [] oldSelection, boolean allLevelsSelection, 6370 Selectable [] items, float dx, float dy) { 6371 super(preferences, PlanController.class, "undoMoveSelectionName"); 6372 this.controller = controller; 6373 this.oldSelection = oldSelection; 6374 this.allLevelsSelection = allLevelsSelection; 6375 this.itemsArray = items; 6376 this.dx = dx; 6377 this.dy = dy; 6378 } 6379 6380 @Override 6381 public void undo() throws CannotUndoException { 6382 super.undo(); 6383 this.controller.doMoveAndShowItems(this.itemsArray, this.oldSelection, -this.dx, -this.dy, this.allLevelsSelection); 6384 } 6385 6386 @Override 6387 public void redo() throws CannotRedoException { 6388 super.redo(); 6389 this.controller.doMoveAndShowItems(this.itemsArray, this.itemsArray, this.dx, this.dy, this.allLevelsSelection); 6390 } 6391 } 6392 6393 /** 6394 * Moves <code>movedItems</code> of (<code>dx</code>, <code>dy</code>) pixels, 6395 * selects them and make them visible. 6396 */ 6397 private void doMoveAndShowItems(Selectable [] movedItems, 6398 Selectable [] selectedItems, 6399 float dx, float dy, 6400 boolean allLevelsSelection) { 6401 this.home.setAllLevelsSelection(allLevelsSelection); 6402 moveItems(Arrays.asList(movedItems), dx, dy); 6403 selectAndShowItems(Arrays.asList(selectedItems), allLevelsSelection); 6404 } 6405 6406 /** 6407 * Posts an undoable operation of a (<code>dx</code>, <code>dy</code>) move 6408 * of the given <code>piece</code>. 6409 */ 6410 private void postPieceOfFurnitureMove(final HomePieceOfFurniture piece, 6411 final float dx, final float dy, 6412 final float oldAngle, 6413 final float oldDepth, 6414 final float oldElevation, 6415 final boolean oldDoorOrWindowBoundToWall) { 6416 final float newAngle = piece.getAngle(); 6417 final float newDepth = piece.getDepth(); 6418 final float newElevation = piece.getElevation(); 6419 if (dx != 0 || dy != 0 6420 || newAngle != oldAngle 6421 || newDepth != oldDepth 6422 || newElevation != oldElevation) { 6423 this.undoSupport.postEdit(new PieceOfFurnitureMovingUndoableEdit(this, this.preferences, 6424 oldAngle, oldDepth, oldElevation, oldDoorOrWindowBoundToWall, 6425 piece, dx, dy, newAngle, newDepth, newElevation)); 6426 } 6427 } 6428 6429 /** 6430 * Undoable edit for moving a piece of furniture. 6431 */ 6432 private static class PieceOfFurnitureMovingUndoableEdit extends LocalizedUndoableEdit { 6433 private final PlanController controller; 6434 private final float oldAngle; 6435 private final float oldDepth; 6436 private final float oldElevation; 6437 private final boolean oldDoorOrWindowBoundToWall; 6438 private final HomePieceOfFurniture piece; 6439 private final float dx; 6440 private final float dy; 6441 private final float newAngle; 6442 private final float newDepth; 6443 private final float newElevation; 6444 6445 public PieceOfFurnitureMovingUndoableEdit(PlanController controller, UserPreferences preferences, 6446 float oldAngle, float oldDepth, float oldElevation, boolean oldDoorOrWindowBoundToWall, 6447 HomePieceOfFurniture piece, float dx, float dy, 6448 float newAngle, float newDepth, float newElevation) { 6449 super(preferences, PlanController.class, "undoMoveSelectionName"); 6450 this.controller = controller; 6451 this.oldAngle = oldAngle; 6452 this.oldDepth = oldDepth; 6453 this.oldElevation = oldElevation; 6454 this.oldDoorOrWindowBoundToWall = oldDoorOrWindowBoundToWall; 6455 this.piece = piece; 6456 this.dx = dx; 6457 this.dy = dy; 6458 this.newAngle = newAngle; 6459 this.newDepth = newDepth; 6460 this.newElevation = newElevation; 6461 } 6462 6463 @Override 6464 public void undo() throws CannotUndoException { 6465 super.undo(); 6466 this.piece.move(-this.dx, -this.dy); 6467 this.piece.setAngle(this.oldAngle); 6468 if (this.piece instanceof HomeDoorOrWindow 6469 && this.piece.isResizable() 6470 && this.controller.isItemResizable(this.piece)) { 6471 // Update of depth may happen only for doors and windows which can't be rotated around horizontal axes 6472 this.piece.setDepth(this.oldDepth); 6473 } 6474 this.piece.setElevation(this.oldElevation); 6475 if (this.piece instanceof HomeDoorOrWindow) { 6476 ((HomeDoorOrWindow)this.piece).setBoundToWall(this.oldDoorOrWindowBoundToWall); 6477 } 6478 this.controller.selectAndShowItems(Arrays.asList(new HomePieceOfFurniture [] {this.piece})); 6479 } 6480 6481 @Override 6482 public void redo() throws CannotRedoException { 6483 super.redo(); 6484 this.piece.move(this.dx, this.dy); 6485 this.piece.setAngle(this.newAngle); 6486 if (this.piece instanceof HomeDoorOrWindow 6487 && this.piece.isResizable() 6488 && this.controller.isItemResizable(this.piece)) { 6489 // Update of depth may happen only for doors and windows which can't be rotated around horizontal axes 6490 this.piece.setDepth(this.newDepth); 6491 } 6492 this.piece.setElevation(this.newElevation); 6493 this.controller.selectAndShowItems(Arrays.asList(new HomePieceOfFurniture [] {this.piece})); 6494 } 6495 } 6496 6497 /** 6498 * Posts an undoable operation about duplication <code>items</code>. 6499 */ 6500 private void postItemsDuplication(final List<Selectable> items, 6501 final List<Selectable> oldSelection) { 6502 boolean basePlanLocked = this.home.isBasePlanLocked(); 6503 final boolean allLevelsSelection = this.home.isAllLevelsSelection(); 6504 // Delete furniture and add it again in a compound edit 6505 List<HomePieceOfFurniture> furniture = Home.getFurnitureSubList(items); 6506 for (HomePieceOfFurniture piece : furniture) { 6507 this.home.deletePieceOfFurniture(piece); 6508 } 6509 6510 // Post duplicated items in a compound edit 6511 this.undoSupport.beginUpdate(); 6512 // Add a undoable edit that will select previous items at undo 6513 this.undoSupport.postEdit(new DuplicationStartUndoableEdit(this, 6514 oldSelection.toArray(new Selectable [oldSelection.size()]), allLevelsSelection)); 6515 6516 addFurniture(furniture); 6517 List<Selectable> emptyList = Collections.emptyList(); 6518 postCreateWalls(Home.getWallsSubList(items), emptyList, basePlanLocked, allLevelsSelection); 6519 postCreateRooms(Home.getRoomsSubList(items), emptyList, basePlanLocked, allLevelsSelection); 6520 postCreatePolylines(Home.getPolylinesSubList(items), emptyList, basePlanLocked, allLevelsSelection); 6521 postCreateDimensionLines(Home.getDimensionLinesSubList(items), emptyList, basePlanLocked, allLevelsSelection); 6522 postCreateLabels(Home.getLabelsSubList(items), emptyList, basePlanLocked, allLevelsSelection); 6523 6524 // Add a undoable edit that will select all the items at redo 6525 this.undoSupport.postEdit(new DuplicationEndUndoableEdit(this, this.preferences, items.toArray(new Selectable [items.size()]))); 6526 6527 // End compound edit 6528 this.undoSupport.endUpdate(); 6529 6530 selectItems(items); 6531 } 6532 6533 /** 6534 * Undoable edit for duplication start. 6535 */ 6536 private static class DuplicationStartUndoableEdit extends AbstractUndoableEdit { 6537 private final PlanController controller; 6538 private final Selectable [] oldSelection; 6539 private final boolean allLevelsSelection; 6540 6541 public DuplicationStartUndoableEdit(PlanController controller, Selectable [] oldSelection, boolean allLevelsSelection) { 6542 this.controller = controller; 6543 this.oldSelection = oldSelection; 6544 this.allLevelsSelection = allLevelsSelection; 6545 } 6546 6547 @Override 6548 public void undo() throws CannotRedoException { 6549 super.undo(); 6550 this.controller.selectAndShowItems(Arrays.asList(this.oldSelection), this.allLevelsSelection); 6551 } 6552 } 6553 6554 /** 6555 * Undoable edit for duplication end. 6556 */ 6557 private static class DuplicationEndUndoableEdit extends LocalizedUndoableEdit { 6558 private final PlanController controller; 6559 private final Selectable [] items; 6560 6561 public DuplicationEndUndoableEdit(PlanController controller, UserPreferences preferences, Selectable [] items) { 6562 super(preferences, PlanController.class, "undoDuplicateSelectionName"); 6563 this.controller = controller; 6564 this.items = items; 6565 } 6566 6567 @Override 6568 public void redo() throws CannotRedoException { 6569 super.redo(); 6570 this.controller.selectAndShowItems(Arrays.asList(this.items)); 6571 } 6572 } 6573 6574 /** 6575 * Posts an undoable operation about <code>wall</code> resizing. 6576 */ 6577 private void postWallResize(final Wall wall, final float oldX, final float oldY, 6578 final boolean startPoint) { 6579 final float newX; 6580 final float newY; 6581 if (startPoint) { 6582 newX = wall.getXStart(); 6583 newY = wall.getYStart(); 6584 } else { 6585 newX = wall.getXEnd(); 6586 newY = wall.getYEnd(); 6587 } 6588 if (newX != oldX || newY != oldY) { 6589 this.undoSupport.postEdit(new WallResizingUndoableEdit(this, this.preferences, 6590 oldX, oldY, wall, startPoint, newX, newY)); 6591 } 6592 } 6593 6594 /** 6595 * Undoable edit for wall resizing. 6596 */ 6597 private static class WallResizingUndoableEdit extends LocalizedUndoableEdit { 6598 private final PlanController controller; 6599 private final float oldX; 6600 private final float oldY; 6601 private final Wall wall; 6602 private final boolean startPoint; 6603 private final float newX; 6604 private final float newY; 6605 6606 public WallResizingUndoableEdit(PlanController controller, UserPreferences preferences, 6607 float oldX, float oldY, Wall wall, boolean startPoint, float newX, float newY) { 6608 super(preferences, PlanController.class, "undoWallResizeName"); 6609 this.controller = controller; 6610 this.oldX = oldX; 6611 this.oldY = oldY; 6612 this.wall = wall; 6613 this.startPoint = startPoint; 6614 this.newX = newX; 6615 this.newY = newY; 6616 } 6617 6618 @Override 6619 public void undo() throws CannotUndoException { 6620 super.undo(); 6621 moveWallPoint(this.wall, this.oldX, this.oldY, this.startPoint); 6622 this.controller.selectAndShowItems(Arrays.asList(new Wall [] {this.wall})); 6623 } 6624 6625 @Override 6626 public void redo() throws CannotRedoException { 6627 super.redo(); 6628 moveWallPoint(this.wall, this.newX, this.newY, this.startPoint); 6629 this.controller.selectAndShowItems(Arrays.asList(new Wall [] {this.wall})); 6630 } 6631 } 6632 6633 /** 6634 * Posts an undoable operation about <code>wall</code> arc extent change. 6635 */ 6636 private void postWallArcExtent(final Wall wall, final Float oldArcExtent) { 6637 final Float newArcExtent = wall.getArcExtent(); 6638 if (newArcExtent != oldArcExtent 6639 && (newArcExtent == null || !newArcExtent.equals(oldArcExtent))) { 6640 this.undoSupport.postEdit(new WallArcExtentModificationUndoableEdit( 6641 this, this.preferences, oldArcExtent, wall, newArcExtent)); 6642 } 6643 } 6644 6645 /** 6646 * Undoable edit for wall arc extent modification. 6647 */ 6648 private static class WallArcExtentModificationUndoableEdit extends LocalizedUndoableEdit { 6649 private final PlanController controller; 6650 private final Float oldArcExtent; 6651 private final Wall wall; 6652 private final Float newArcExtent; 6653 6654 public WallArcExtentModificationUndoableEdit(PlanController controller, UserPreferences preferences, 6655 Float oldArcExtent, Wall wall, Float newArcExtent) { 6656 super(preferences, PlanController.class, "undoWallArcExtentName"); 6657 this.controller = controller; 6658 this.oldArcExtent = oldArcExtent; 6659 this.wall = wall; 6660 this.newArcExtent = newArcExtent; 6661 } 6662 6663 @Override 6664 public void undo() throws CannotUndoException { 6665 super.undo(); 6666 this.wall.setArcExtent(this.oldArcExtent); 6667 this.controller.selectAndShowItems(Arrays.asList(new Wall [] {this.wall})); 6668 } 6669 6670 @Override 6671 public void redo() throws CannotRedoException { 6672 super.redo(); 6673 this.wall.setArcExtent(this.newArcExtent); 6674 this.controller.selectAndShowItems(Arrays.asList(new Wall [] {this.wall})); 6675 } 6676 } 6677 6678 /** 6679 * Posts an undoable operation about <code>room</code> resizing. 6680 */ 6681 private void postRoomResize(final Room room, final float oldX, final float oldY, 6682 final int pointIndex) { 6683 float [] roomPoint = room.getPoints() [pointIndex]; 6684 final float newX = roomPoint [0]; 6685 final float newY = roomPoint [1]; 6686 if (newX != oldX || newY != oldY) { 6687 UndoableEdit undoableEdit = new RoomResizingUndoableEdit(this, this.preferences, 6688 oldX, oldY, room, pointIndex, newX, newY); 6689 this.undoSupport.postEdit(undoableEdit); 6690 } 6691 } 6692 6693 /** 6694 * Undoable edit for room resizing. 6695 */ 6696 private static class RoomResizingUndoableEdit extends LocalizedUndoableEdit { 6697 private final PlanController controller; 6698 private final float oldX; 6699 private final float oldY; 6700 private final Room room; 6701 private final int pointIndex; 6702 private final float newX; 6703 private final float newY; 6704 6705 public RoomResizingUndoableEdit(PlanController controller, UserPreferences preferences, 6706 float oldX, float oldY, Room room, int pointIndex, float newX, float newY) { 6707 super(preferences, PlanController.class, "undoRoomResizeName"); 6708 this.controller = controller; 6709 this.oldX = oldX; 6710 this.oldY = oldY; 6711 this.room = room; 6712 this.pointIndex = pointIndex; 6713 this.newX = newX; 6714 this.newY = newY; 6715 } 6716 6717 @Override 6718 public void undo() throws CannotUndoException { 6719 super.undo(); 6720 moveRoomPoint(this.room, this.oldX, this.oldY, this.pointIndex); 6721 this.controller.selectAndShowItems(Arrays.asList(new Room [] {this.room})); 6722 } 6723 6724 @Override 6725 public void redo() throws CannotRedoException { 6726 super.redo(); 6727 moveRoomPoint(this.room, this.newX, this.newY, this.pointIndex); 6728 this.controller.selectAndShowItems(Arrays.asList(new Room [] {this.room})); 6729 } 6730 } 6731 6732 /** 6733 * Posts an undoable operation about <code>room</code> name offset change. 6734 */ 6735 private void postRoomNameOffset(final Room room, final float oldNameXOffset, 6736 final float oldNameYOffset) { 6737 final float newNameXOffset = room.getNameXOffset(); 6738 final float newNameYOffset = room.getNameYOffset(); 6739 if (newNameXOffset != oldNameXOffset 6740 || newNameYOffset != oldNameYOffset) { 6741 this.undoSupport.postEdit(new RoomNameOffsetModificationUndoableEdit(this, this.preferences, 6742 oldNameXOffset, oldNameYOffset, room, newNameXOffset, newNameYOffset)); 6743 } 6744 } 6745 6746 /** 6747 * Undoable edit for room name offset modification. 6748 */ 6749 private static class RoomNameOffsetModificationUndoableEdit extends LocalizedUndoableEdit { 6750 private final PlanController controller; 6751 private final float oldNameXOffset; 6752 private final float oldNameYOffset; 6753 private final Room room; 6754 private final float newNameXOffset; 6755 private final float newNameYOffset; 6756 6757 public RoomNameOffsetModificationUndoableEdit(PlanController controller, UserPreferences preferences, 6758 float oldNameXOffset, float oldNameYOffset, 6759 Room room, float newNameXOffset, float newNameYOffset) { 6760 super(preferences, PlanController.class, "undoRoomNameOffsetName"); 6761 this.controller = controller; 6762 this.oldNameXOffset = oldNameXOffset; 6763 this.oldNameYOffset = oldNameYOffset; 6764 this.room = room; 6765 this.newNameXOffset = newNameXOffset; 6766 this.newNameYOffset = newNameYOffset; 6767 } 6768 6769 @Override 6770 public void undo() throws CannotUndoException { 6771 super.undo(); 6772 this.room.setNameXOffset(this.oldNameXOffset); 6773 this.room.setNameYOffset(this.oldNameYOffset); 6774 this.controller.selectAndShowItems(Arrays.asList(new Room [] {this.room})); 6775 } 6776 6777 @Override 6778 public void redo() throws CannotRedoException { 6779 super.redo(); 6780 this.room.setNameXOffset(this.newNameXOffset); 6781 this.room.setNameYOffset(this.newNameYOffset); 6782 this.controller.selectAndShowItems(Arrays.asList(new Room [] {this.room})); 6783 } 6784 } 6785 6786 /** 6787 * Posts an undoable operation about <code>room</code> name angle change. 6788 */ 6789 private void postRoomNameRotation(final Room room, final float oldNameAngle) { 6790 final float newNameAngle = room.getNameAngle(); 6791 if (newNameAngle != oldNameAngle) { 6792 this.undoSupport.postEdit(new RoomNameRotationUndoableEdit(this, this.preferences, 6793 oldNameAngle, room, newNameAngle)); 6794 } 6795 } 6796 6797 /** 6798 * Undoable edit for room name rotation. 6799 */ 6800 private static class RoomNameRotationUndoableEdit extends LocalizedUndoableEdit { 6801 private final PlanController controller; 6802 private final float oldNameAngle; 6803 private final Room room; 6804 private final float newNameAngle; 6805 6806 public RoomNameRotationUndoableEdit(PlanController controller, UserPreferences preferences, 6807 float oldNameAngle, Room room, float newNameAngle) { 6808 super(preferences, PlanController.class, "undoRoomNameRotationName"); 6809 this.controller = controller; 6810 this.oldNameAngle = oldNameAngle; 6811 this.room = room; 6812 this.newNameAngle = newNameAngle; 6813 } 6814 6815 @Override 6816 public void undo() throws CannotUndoException { 6817 super.undo(); 6818 this.room.setNameAngle(this.oldNameAngle); 6819 this.controller.selectAndShowItems(Arrays.asList(new Room [] {this.room})); 6820 } 6821 6822 @Override 6823 public void redo() throws CannotRedoException { 6824 super.redo(); 6825 this.room.setNameAngle(this.newNameAngle); 6826 this.controller.selectAndShowItems(Arrays.asList(new Room [] {this.room})); 6827 } 6828 } 6829 6830 /** 6831 * Posts an undoable operation about <code>room</code> area offset change. 6832 */ 6833 private void postRoomAreaOffset(final Room room, final float oldAreaXOffset, 6834 final float oldAreaYOffset) { 6835 final float newAreaXOffset = room.getAreaXOffset(); 6836 final float newAreaYOffset = room.getAreaYOffset(); 6837 if (newAreaXOffset != oldAreaXOffset 6838 || newAreaYOffset != oldAreaYOffset) { 6839 this.undoSupport.postEdit(new RoomAreaOffsetModificationUndoableEdit(this, this.preferences, 6840 oldAreaXOffset, oldAreaYOffset, room, newAreaXOffset, newAreaYOffset)); 6841 } 6842 } 6843 6844 /** 6845 * Undoable edit for room area offset modification. 6846 */ 6847 private static class RoomAreaOffsetModificationUndoableEdit extends LocalizedUndoableEdit { 6848 private final PlanController controller; 6849 private final float oldAreaXOffset; 6850 private final float oldAreaYOffset; 6851 private final Room room; 6852 private final float newAreaXOffset; 6853 private final float newAreaYOffset; 6854 6855 public RoomAreaOffsetModificationUndoableEdit(PlanController controller, UserPreferences preferences, 6856 float oldAreaXOffset, float oldAreaYOffset, 6857 Room room, float newAreaXOffset, float newAreaYOffset) { 6858 super(preferences, PlanController.class, "undoRoomAreaOffsetName"); 6859 this.controller = controller; 6860 this.oldAreaXOffset = oldAreaXOffset; 6861 this.oldAreaYOffset = oldAreaYOffset; 6862 this.room = room; 6863 this.newAreaXOffset = newAreaXOffset; 6864 this.newAreaYOffset = newAreaYOffset; 6865 } 6866 6867 @Override 6868 public void undo() throws CannotUndoException { 6869 super.undo(); 6870 this.room.setAreaXOffset(this.oldAreaXOffset); 6871 this.room.setAreaYOffset(this.oldAreaYOffset); 6872 this.controller.selectAndShowItems(Arrays.asList(new Room [] {this.room})); 6873 } 6874 6875 @Override 6876 public void redo() throws CannotRedoException { 6877 super.redo(); 6878 this.room.setAreaXOffset(this.newAreaXOffset); 6879 this.room.setAreaYOffset(this.newAreaYOffset); 6880 this.controller.selectAndShowItems(Arrays.asList(new Room [] {this.room})); 6881 } 6882 } 6883 6884 /** 6885 * Posts an undoable operation about <code>room</code> area angle change. 6886 */ 6887 private void postRoomAreaRotation(final Room room, final float oldAreaAngle) { 6888 final float newAreaAngle = room.getAreaAngle(); 6889 if (newAreaAngle != oldAreaAngle) { 6890 this.undoSupport.postEdit(new RoomAreaRotationUndoableEdit(this, this.preferences, 6891 oldAreaAngle, room, newAreaAngle)); 6892 } 6893 } 6894 6895 /** 6896 * Undoable edit for room area rotation. 6897 */ 6898 private static class RoomAreaRotationUndoableEdit extends LocalizedUndoableEdit { 6899 private final PlanController controller; 6900 private final float oldAreaAngle; 6901 private final Room room; 6902 private final float newAreaAngle; 6903 6904 public RoomAreaRotationUndoableEdit(PlanController controller, UserPreferences preferences, 6905 float oldAreaAngle, Room room, float newAreaAngle) { 6906 super(preferences, PlanController.class, "undoRoomAreaRotationName"); 6907 this.controller = controller; 6908 this.oldAreaAngle = oldAreaAngle; 6909 this.room = room; 6910 this.newAreaAngle = newAreaAngle; 6911 } 6912 6913 @Override 6914 public void undo() throws CannotUndoException { 6915 super.undo(); 6916 this.room.setAreaAngle(this.oldAreaAngle); 6917 controller.selectAndShowItems(Arrays.asList(new Room [] {this.room})); 6918 } 6919 6920 @Override 6921 public void redo() throws CannotRedoException { 6922 super.redo(); 6923 this.room.setAreaAngle(this.newAreaAngle); 6924 controller.selectAndShowItems(Arrays.asList(new Room [] {this.room})); 6925 } 6926 } 6927 6928 /** 6929 * Post to undo support an angle change on <code>piece</code>. 6930 */ 6931 private void postPieceOfFurnitureRotation(final HomePieceOfFurniture piece, 6932 final float oldAngle, 6933 final boolean oldDoorOrWindowBoundToWall) { 6934 final float newAngle = piece.getAngle(); 6935 if (newAngle != oldAngle) { 6936 this.undoSupport.postEdit(new PieceOfFurnitureRotationUndoableEdit(this, this.preferences, 6937 oldAngle, oldDoorOrWindowBoundToWall, piece, newAngle)); 6938 } 6939 } 6940 6941 /** 6942 * Undoable edit for the rotation of a piece of furniture. 6943 */ 6944 private static class PieceOfFurnitureRotationUndoableEdit extends LocalizedUndoableEdit { 6945 private final PlanController controller; 6946 private final float oldAngle; 6947 private final boolean oldDoorOrWindowBoundToWall; 6948 private final HomePieceOfFurniture piece; 6949 private final float newAngle; 6950 6951 public PieceOfFurnitureRotationUndoableEdit(PlanController controller, UserPreferences preferences, 6952 float oldAngle, boolean oldDoorOrWindowBoundToWall, 6953 HomePieceOfFurniture piece, float newAngle) { 6954 super(preferences, PlanController.class, "undoPieceOfFurnitureRotationName"); 6955 this.controller = controller; 6956 this.oldAngle = oldAngle; 6957 this.oldDoorOrWindowBoundToWall = oldDoorOrWindowBoundToWall; 6958 this.piece = piece; 6959 this.newAngle = newAngle; 6960 } 6961 6962 @Override 6963 public void undo() throws CannotUndoException { 6964 super.undo(); 6965 this.piece.setAngle(this.oldAngle); 6966 if (this.piece instanceof HomeDoorOrWindow) { 6967 ((HomeDoorOrWindow)this.piece).setBoundToWall(this.oldDoorOrWindowBoundToWall); 6968 } 6969 this.controller.selectAndShowItems(Arrays.asList(new HomePieceOfFurniture [] {this.piece})); 6970 } 6971 6972 @Override 6973 public void redo() throws CannotRedoException { 6974 super.redo(); 6975 this.piece.setAngle(this.newAngle); 6976 this.controller.selectAndShowItems(Arrays.asList(new HomePieceOfFurniture [] {this.piece})); 6977 } 6978 } 6979 6980 /** 6981 * Post to undo support a pitch change on <code>piece</code>. 6982 */ 6983 private void postPieceOfFurniturePitchRotation(final HomePieceOfFurniture piece, float oldPitch, 6984 float oldWidthInPlan, float oldDepthInPlan, float oldHeightInPlan) { 6985 final float newPitch = piece.getPitch(); 6986 if (newPitch != oldPitch) { 6987 this.undoSupport.postEdit(new PieceOfFurniturePitchRotationUndoableEdit(this, this.preferences, 6988 oldPitch, oldWidthInPlan, oldDepthInPlan, oldHeightInPlan, piece, 6989 newPitch, piece.getWidthInPlan(), piece.getDepthInPlan(), piece.getHeightInPlan())); 6990 } 6991 } 6992 6993 /** 6994 * Undoable edit for the pitch rotation of a piece of furniture. 6995 */ 6996 private static class PieceOfFurniturePitchRotationUndoableEdit extends LocalizedUndoableEdit { 6997 private final PlanController controller; 6998 private final float oldPitch; 6999 private final float oldWidthInPlan; 7000 private final float oldDepthInPlan; 7001 private final float oldHeightInPlan; 7002 private final HomePieceOfFurniture piece; 7003 private final float newPitch; 7004 private final float newWidthInPlan; 7005 private final float newDepthInPlan; 7006 private final float newHeightInPlan; 7007 7008 public PieceOfFurniturePitchRotationUndoableEdit(PlanController controller, UserPreferences preferences, 7009 float oldPitch, float oldWidthInPlan, float oldDepthInPlan, float oldHeightInPlan, 7010 HomePieceOfFurniture piece, float newPitch, float newWidthInPlan, float newDepthInPlan, float newHeightInPlan) { 7011 super(preferences, PlanController.class, "undoPieceOfFurnitureRotationName"); 7012 this.controller = controller; 7013 this.oldPitch = oldPitch; 7014 this.oldWidthInPlan = oldWidthInPlan; 7015 this.oldDepthInPlan = oldDepthInPlan; 7016 this.oldHeightInPlan = oldHeightInPlan; 7017 this.piece = piece; 7018 this.newPitch = newPitch; 7019 this.newWidthInPlan = newWidthInPlan; 7020 this.newDepthInPlan = newDepthInPlan; 7021 this.newHeightInPlan = newHeightInPlan; 7022 } 7023 7024 @Override 7025 public void undo() throws CannotUndoException { 7026 super.undo(); 7027 this.controller.setPieceOfFurniturePitch(this.piece, this.oldPitch, this.oldWidthInPlan, this.oldDepthInPlan, this.oldHeightInPlan); 7028 this.controller.selectAndShowItems(Arrays.asList(new HomePieceOfFurniture [] {this.piece})); 7029 } 7030 7031 @Override 7032 public void redo() throws CannotRedoException { 7033 super.redo(); 7034 this.controller.setPieceOfFurniturePitch(this.piece, this.newPitch, this.newWidthInPlan, this.newDepthInPlan, this.newHeightInPlan); 7035 this.controller.selectAndShowItems(Arrays.asList(new HomePieceOfFurniture [] {this.piece})); 7036 } 7037 } 7038 7039 /** 7040 * Sets the pitch angle on the given piece without computing new size in plan. 7041 */ 7042 private void setPieceOfFurniturePitch(HomePieceOfFurniture piece, float pitch, 7043 float widthInPlan, float depthInPlan, float heightInPlan) { 7044 piece.removePropertyChangeListener(this.furnitureSizeChangeListener); 7045 piece.setPitch(pitch); 7046 piece.setWidthInPlan(widthInPlan); 7047 piece.setDepthInPlan(depthInPlan); 7048 piece.setHeightInPlan(heightInPlan); 7049 piece.addPropertyChangeListener(this.furnitureSizeChangeListener); 7050 } 7051 7052 /** 7053 * Post to undo support a roll change on <code>piece</code>. 7054 */ 7055 private void postPieceOfFurnitureRollRotation(final HomePieceOfFurniture piece, float oldRoll, 7056 float oldWidthInPlan, float oldDepthInPlan, float oldHeightInPlan) { 7057 final float newRoll = piece.getRoll(); 7058 if (newRoll != oldRoll) { 7059 this.undoSupport.postEdit(new PieceOfFurnitureRollRotationUndoableEdit(this, this.preferences, 7060 oldRoll, oldWidthInPlan, oldDepthInPlan, oldHeightInPlan, piece, 7061 newRoll, piece.getWidthInPlan(), piece.getDepthInPlan(), piece.getHeightInPlan())); 7062 } 7063 } 7064 7065 /** 7066 * Undoable edit for the roll rotation of a piece of furniture. 7067 */ 7068 private static class PieceOfFurnitureRollRotationUndoableEdit extends LocalizedUndoableEdit { 7069 private final PlanController controller; 7070 private final float oldRoll; 7071 private final float oldWidthInPlan; 7072 private final float oldDepthInPlan; 7073 private final float oldHeightInPlan; 7074 private final HomePieceOfFurniture piece; 7075 private final float newRoll; 7076 private final float newWidthInPlan; 7077 private final float newDepthInPlan; 7078 private final float newHeightInPlan; 7079 7080 public PieceOfFurnitureRollRotationUndoableEdit(PlanController controller, UserPreferences preferences, 7081 float oldRoll, float oldWidthInPlan, float oldDepthInPlan, float oldHeightInPlan, 7082 HomePieceOfFurniture piece, float newRoll, float newWidthInPlan, float newDepthInPlan, float newHeightInPlan) { 7083 super(preferences, PlanController.class, "undoPieceOfFurnitureRotationName"); 7084 this.controller = controller; 7085 this.oldRoll = oldRoll; 7086 this.oldWidthInPlan = oldWidthInPlan; 7087 this.oldDepthInPlan = oldDepthInPlan; 7088 this.oldHeightInPlan = oldHeightInPlan; 7089 this.piece = piece; 7090 this.newRoll = newRoll; 7091 this.newWidthInPlan = newWidthInPlan; 7092 this.newDepthInPlan = newDepthInPlan; 7093 this.newHeightInPlan = newHeightInPlan; 7094 } 7095 7096 @Override 7097 public void undo() throws CannotUndoException { 7098 super.undo(); 7099 this.controller.setPieceOfFurnitureRoll(this.piece, this.oldRoll, this.oldWidthInPlan, this.oldDepthInPlan, this.oldHeightInPlan); 7100 this.controller.selectAndShowItems(Arrays.asList(new HomePieceOfFurniture [] {this.piece})); 7101 } 7102 7103 @Override 7104 public void redo() throws CannotRedoException { 7105 super.redo(); 7106 this.controller.setPieceOfFurnitureRoll(this.piece, this.newRoll, this.newWidthInPlan, this.newDepthInPlan, this.newHeightInPlan); 7107 this.controller.selectAndShowItems(Arrays.asList(new HomePieceOfFurniture [] {this.piece})); 7108 } 7109 } 7110 7111 /** 7112 * Sets the roll angle on the given piece without computing new size in plan. 7113 */ 7114 private void setPieceOfFurnitureRoll(HomePieceOfFurniture piece, float roll, 7115 float widthInPlan, float depthInPlan, float heightInPlan) { 7116 piece.removePropertyChangeListener(this.furnitureSizeChangeListener); 7117 piece.setRoll(roll); 7118 piece.setWidthInPlan(widthInPlan); 7119 piece.setDepthInPlan(depthInPlan); 7120 piece.setHeightInPlan(heightInPlan); 7121 piece.addPropertyChangeListener(this.furnitureSizeChangeListener); 7122 } 7123 7124 /** 7125 * Post to undo support an elevation change on <code>piece</code>. 7126 */ 7127 private void postPieceOfFurnitureElevation(final HomePieceOfFurniture piece, final float oldElevation) { 7128 final float newElevation = piece.getElevation(); 7129 if (newElevation != oldElevation) { 7130 this.undoSupport.postEdit(new PieceOfFurnitureElevationModificationUndoableEdit(this, this.preferences, 7131 oldElevation, piece, newElevation)); 7132 } 7133 } 7134 7135 /** 7136 * Undoable edit for the elevation modification of a piece of furniture. 7137 */ 7138 private static class PieceOfFurnitureElevationModificationUndoableEdit extends LocalizedUndoableEdit { 7139 private final PlanController controller; 7140 private final float oldElevation; 7141 private final HomePieceOfFurniture piece; 7142 private final float newElevation; 7143 7144 public PieceOfFurnitureElevationModificationUndoableEdit(PlanController controller, UserPreferences preferences, 7145 float oldElevation, HomePieceOfFurniture piece, float newElevation) { 7146 super(preferences, PlanController.class, 7147 oldElevation < newElevation 7148 ? "undoPieceOfFurnitureRaiseName" 7149 : "undoPieceOfFurnitureLowerName"); 7150 this.controller = controller; 7151 this.oldElevation = oldElevation; 7152 this.piece = piece; 7153 this.newElevation = newElevation; 7154 } 7155 7156 @Override 7157 public void undo() throws CannotUndoException { 7158 super.undo(); 7159 this.piece.setElevation(this.oldElevation); 7160 this.controller.selectAndShowItems(Arrays.asList(new HomePieceOfFurniture [] {this.piece})); 7161 } 7162 7163 @Override 7164 public void redo() throws CannotRedoException { 7165 super.redo(); 7166 this.piece.setElevation(this.newElevation); 7167 this.controller.selectAndShowItems(Arrays.asList(new HomePieceOfFurniture [] {this.piece})); 7168 } 7169 } 7170 7171 /** 7172 * Post to undo support a height change on <code>piece</code>. 7173 */ 7174 private void postPieceOfFurnitureHeightResize(final ResizedPieceOfFurniture resizedPiece) { 7175 if (resizedPiece.getPieceOfFurniture().getHeight() != resizedPiece.getHeight()) { 7176 postPieceOfFurnitureResize(resizedPiece, "undoPieceOfFurnitureHeightResizeName"); 7177 } 7178 } 7179 7180 /** 7181 * Post to undo support a width and depth change on <code>piece</code>. 7182 */ 7183 private void postPieceOfFurnitureWidthAndDepthResize(final ResizedPieceOfFurniture resizedPiece) { 7184 HomePieceOfFurniture piece = resizedPiece.getPieceOfFurniture(); 7185 if (piece.getWidth() != resizedPiece.getWidth() 7186 || piece.getDepth() != resizedPiece.getDepth()) { 7187 postPieceOfFurnitureResize(resizedPiece, "undoPieceOfFurnitureWidthAndDepthResizeName"); 7188 } 7189 } 7190 7191 /** 7192 * Post to undo support a size change on <code>piece</code>. 7193 */ 7194 private void postPieceOfFurnitureResize(final ResizedPieceOfFurniture resizedPiece, 7195 final String presentationNameKey) { 7196 HomePieceOfFurniture piece = resizedPiece.getPieceOfFurniture(); 7197 final boolean doorOrWindowBoundToWall = piece instanceof HomeDoorOrWindow 7198 && ((HomeDoorOrWindow)piece).isBoundToWall(); 7199 this.undoSupport.postEdit(new PieceOfFurnitureResizingUndoableEdit(this, this.preferences, 7200 presentationNameKey, doorOrWindowBoundToWall, resizedPiece, 7201 piece.getX(), piece.getY(), piece.getWidth(), piece.getDepth(), piece.getHeight())); 7202 } 7203 7204 /** 7205 * Undoable edit for the resizing of a piece of furniture. 7206 */ 7207 private static class PieceOfFurnitureResizingUndoableEdit extends LocalizedUndoableEdit { 7208 private final PlanController controller; 7209 private final boolean doorOrWindowBoundToWall; 7210 private final ResizedPieceOfFurniture resizedPiece; 7211 private final float newX; 7212 private final float newY; 7213 private final float newWidth; 7214 private final float newDepth; 7215 private final float newHeight; 7216 7217 public PieceOfFurnitureResizingUndoableEdit(PlanController controller, UserPreferences preferences, String presentationNameKey, 7218 boolean doorOrWindowBoundToWall, ResizedPieceOfFurniture resizedPiece, 7219 float newX, float newY, float newWidth, float newDepth, float newHeight) { 7220 super(preferences, PlanController.class, presentationNameKey); 7221 this.controller = controller; 7222 this.doorOrWindowBoundToWall = doorOrWindowBoundToWall; 7223 this.resizedPiece = resizedPiece; 7224 this.newX = newX; 7225 this.newY = newY; 7226 this.newWidth = newWidth; 7227 this.newDepth = newDepth; 7228 this.newHeight = newHeight; 7229 } 7230 7231 @Override 7232 public void undo() throws CannotUndoException { 7233 super.undo(); 7234 this.controller.resetPieceOfFurnitureSize(this.resizedPiece); 7235 this.controller.selectAndShowItems(Arrays.asList(new HomePieceOfFurniture [] {this.resizedPiece.getPieceOfFurniture()})); 7236 } 7237 7238 @Override 7239 public void redo() throws CannotRedoException { 7240 super.redo(); 7241 HomePieceOfFurniture piece = this.resizedPiece.getPieceOfFurniture(); 7242 piece.setX(this.newX); 7243 piece.setY(this.newY); 7244 this.controller.setPieceOfFurnitureSize(this.resizedPiece, this.newWidth, this.newDepth, this.newHeight); 7245 if (piece instanceof HomeDoorOrWindow) { 7246 ((HomeDoorOrWindow)piece).setBoundToWall(this.doorOrWindowBoundToWall); 7247 } 7248 this.controller.selectAndShowItems(Arrays.asList(new HomePieceOfFurniture [] {piece})); 7249 } 7250 } 7251 7252 /** 7253 * Sets the size of the given piece of furniture. 7254 */ 7255 private void setPieceOfFurnitureSize(ResizedPieceOfFurniture resizedPiece, 7256 float width, float depth, float height) { 7257 HomePieceOfFurniture piece = resizedPiece.getPieceOfFurniture(); 7258 piece.removePropertyChangeListener(this.furnitureSizeChangeListener); 7259 if (piece instanceof HomeFurnitureGroup) { 7260 for (HomePieceOfFurniture childPiece : ((HomeFurnitureGroup)piece).getAllFurniture()) { 7261 childPiece.removePropertyChangeListener(this.furnitureSizeChangeListener); 7262 } 7263 } 7264 ResizedPieceOfFurniture.setPieceOfFurnitureSize(piece, width, depth, height); 7265 piece.addPropertyChangeListener(this.furnitureSizeChangeListener); 7266 if (piece instanceof HomeFurnitureGroup) { 7267 for (HomePieceOfFurniture childPiece : ((HomeFurnitureGroup)piece).getAllFurniture()) { 7268 childPiece.addPropertyChangeListener(this.furnitureSizeChangeListener); 7269 } 7270 } 7271 } 7272 7273 /** 7274 * Resets the size of the given piece of furniture. 7275 */ 7276 private void resetPieceOfFurnitureSize(ResizedPieceOfFurniture resizedPiece) { 7277 HomePieceOfFurniture piece = resizedPiece.getPieceOfFurniture(); 7278 piece.removePropertyChangeListener(this.furnitureSizeChangeListener); 7279 if (piece instanceof HomeFurnitureGroup) { 7280 for (HomePieceOfFurniture childPiece : ((HomeFurnitureGroup)piece).getAllFurniture()) { 7281 childPiece.removePropertyChangeListener(this.furnitureSizeChangeListener); 7282 } 7283 } 7284 resizedPiece.reset(); 7285 piece.addPropertyChangeListener(this.furnitureSizeChangeListener); 7286 if (piece instanceof HomeFurnitureGroup) { 7287 for (HomePieceOfFurniture childPiece : ((HomeFurnitureGroup)piece).getAllFurniture()) { 7288 childPiece.addPropertyChangeListener(this.furnitureSizeChangeListener); 7289 } 7290 } 7291 } 7292 7293 /** 7294 * Post to undo support a power modification on <code>light</code>. 7295 */ 7296 private void postLightPowerModification(final HomeLight light, final float oldPower) { 7297 final float newPower = light.getPower(); 7298 if (newPower != oldPower) { 7299 this.undoSupport.postEdit(new LightPowerModificationUndoableEdit(this, this.preferences, 7300 oldPower, light, newPower)); 7301 } 7302 } 7303 7304 /** 7305 * Undoable edit for the light power modification. 7306 */ 7307 private static class LightPowerModificationUndoableEdit extends LocalizedUndoableEdit { 7308 private final PlanController controller; 7309 private final float oldPower; 7310 private final HomeLight light; 7311 private final float newPower; 7312 7313 public LightPowerModificationUndoableEdit(PlanController controller, UserPreferences preferences, 7314 float oldPower, HomeLight light, float newPower) { 7315 super(preferences, PlanController.class, "undoLightPowerModificationName"); 7316 this.controller = controller; 7317 this.oldPower = oldPower; 7318 this.light = light; 7319 this.newPower = newPower; 7320 } 7321 7322 @Override 7323 public void undo() throws CannotUndoException { 7324 super.undo(); 7325 this.light.setPower(this.oldPower); 7326 this.controller.selectAndShowItems(Arrays.asList(new HomePieceOfFurniture [] {this.light})); 7327 } 7328 7329 @Override 7330 public void redo() throws CannotRedoException { 7331 super.redo(); 7332 this.light.setPower(this.newPower); 7333 this.controller.selectAndShowItems(Arrays.asList(new HomePieceOfFurniture [] {this.light})); 7334 } 7335 } 7336 7337 /** 7338 * Posts an undoable operation about <code>piece</code> name offset change. 7339 */ 7340 private void postPieceOfFurnitureNameOffset(final HomePieceOfFurniture piece, 7341 final float oldNameXOffset, 7342 final float oldNameYOffset) { 7343 final float newNameXOffset = piece.getNameXOffset(); 7344 final float newNameYOffset = piece.getNameYOffset(); 7345 if (newNameXOffset != oldNameXOffset 7346 || newNameYOffset != oldNameYOffset) { 7347 this.undoSupport.postEdit(new PieceOfFurnitureNameOffsetModificationUndoableEdit(this, this.preferences, 7348 oldNameXOffset, oldNameYOffset, piece, newNameXOffset, newNameYOffset)); 7349 } 7350 } 7351 7352 /** 7353 * Undoable edit for the name offset modification of a piece of furniture. 7354 */ 7355 private static class PieceOfFurnitureNameOffsetModificationUndoableEdit extends LocalizedUndoableEdit { 7356 private final PlanController controller; 7357 private final float oldNameXOffset; 7358 private final float oldNameYOffset; 7359 private final HomePieceOfFurniture piece; 7360 private final float newNameXOffset; 7361 private final float newNameYOffset; 7362 7363 public PieceOfFurnitureNameOffsetModificationUndoableEdit(PlanController controller, UserPreferences preferences, 7364 float oldNameXOffset, float oldNameYOffset, HomePieceOfFurniture piece, 7365 float newNameXOffset, float newNameYOffset) { 7366 super(preferences, PlanController.class, "undoPieceOfFurnitureNameOffsetName"); 7367 this.controller = controller; 7368 this.oldNameXOffset = oldNameXOffset; 7369 this.oldNameYOffset = oldNameYOffset; 7370 this.piece = piece; 7371 this.newNameXOffset = newNameXOffset; 7372 this.newNameYOffset = newNameYOffset; 7373 } 7374 7375 @Override 7376 public void undo() throws CannotUndoException { 7377 super.undo(); 7378 this.piece.setNameXOffset(this.oldNameXOffset); 7379 this.piece.setNameYOffset(this.oldNameYOffset); 7380 this.controller.selectAndShowItems(Arrays.asList(new HomePieceOfFurniture [] {this.piece})); 7381 } 7382 7383 @Override 7384 public void redo() throws CannotRedoException { 7385 super.redo(); 7386 this.piece.setNameXOffset(this.newNameXOffset); 7387 this.piece.setNameYOffset(this.newNameYOffset); 7388 this.controller.selectAndShowItems(Arrays.asList(new HomePieceOfFurniture [] {this.piece})); 7389 } 7390 } 7391 7392 /** 7393 * Posts an undoable operation about <code>piece</code> name angle change. 7394 */ 7395 private void postPieceOfFurnitureNameRotation(final HomePieceOfFurniture piece, 7396 final float oldNameAngle) { 7397 final float newNameAngle = piece.getNameAngle(); 7398 if (newNameAngle != oldNameAngle) { 7399 this.undoSupport.postEdit(new PieceOfFurnitureNameRotationUndoableEdit(this, this.preferences, 7400 oldNameAngle, piece, newNameAngle)); 7401 } 7402 } 7403 7404 /** 7405 * Undoable edit for the name rotation of a piece of furniture. 7406 */ 7407 private static class PieceOfFurnitureNameRotationUndoableEdit extends LocalizedUndoableEdit { 7408 private final PlanController controller; 7409 private final float oldNameAngle; 7410 private final HomePieceOfFurniture piece; 7411 private final float newNameAngle; 7412 7413 public PieceOfFurnitureNameRotationUndoableEdit(PlanController controller, UserPreferences preferences, 7414 float oldNameAngle, HomePieceOfFurniture piece, float newNameAngle) { 7415 super(preferences, PlanController.class, "undoPieceOfFurnitureNameRotationName"); 7416 this.controller = controller; 7417 this.oldNameAngle = oldNameAngle; 7418 this.piece = piece; 7419 this.newNameAngle = newNameAngle; 7420 } 7421 7422 @Override 7423 public void undo() throws CannotUndoException { 7424 super.undo(); 7425 this.piece.setNameAngle(this.oldNameAngle); 7426 this.controller.selectAndShowItems(Arrays.asList(new HomePieceOfFurniture [] {this.piece})); 7427 } 7428 7429 @Override 7430 public void redo() throws CannotRedoException { 7431 super.redo(); 7432 this.piece.setNameAngle(this.newNameAngle); 7433 this.controller.selectAndShowItems(Arrays.asList(new HomePieceOfFurniture [] {this.piece})); 7434 } 7435 } 7436 7437 /** 7438 * Posts an undoable operation about <code>dimensionLine</code> resizing. 7439 */ 7440 private void postDimensionLineResize(final DimensionLine dimensionLine, final float oldX, final float oldY, 7441 final boolean startPoint, final boolean reversed) { 7442 final float newX; 7443 final float newY; 7444 if (startPoint) { 7445 newX = dimensionLine.getXStart(); 7446 newY = dimensionLine.getYStart(); 7447 } else { 7448 newX = dimensionLine.getXEnd(); 7449 newY = dimensionLine.getYEnd(); 7450 } 7451 if (newX != oldX || newY != oldY || reversed) { 7452 this.undoSupport.postEdit(new DimensionLineResizingUndoableEdit(this, this.preferences, 7453 oldX, oldY, dimensionLine, newX, newY, startPoint, reversed)); 7454 } 7455 } 7456 7457 /** 7458 * Undoable edit for dimension line resizing. 7459 */ 7460 private static class DimensionLineResizingUndoableEdit extends LocalizedUndoableEdit { 7461 private final PlanController controller; 7462 private final float oldX; 7463 private final float oldY; 7464 private final DimensionLine dimensionLine; 7465 private final float newX; 7466 private final float newY; 7467 private final boolean startPoint; 7468 private final boolean reversed; 7469 7470 public DimensionLineResizingUndoableEdit(PlanController controller, UserPreferences preferences, 7471 float oldX, float oldY, DimensionLine dimensionLine, 7472 float newX, float newY, boolean startPoint, boolean reversed) { 7473 super(preferences, PlanController.class, "undoDimensionLineResizeName"); 7474 this.controller = controller; 7475 this.oldX = oldX; 7476 this.oldY = oldY; 7477 this.dimensionLine = dimensionLine; 7478 this.newX = newX; 7479 this.newY = newY; 7480 this.startPoint = startPoint; 7481 this.reversed = reversed; 7482 } 7483 7484 @Override 7485 public void undo() throws CannotUndoException { 7486 super.undo(); 7487 if (this.reversed) { 7488 reverseDimensionLine(this.dimensionLine); 7489 moveDimensionLinePoint(this.dimensionLine, this.oldX, this.oldY, !this.startPoint); 7490 } else { 7491 moveDimensionLinePoint(this.dimensionLine, this.oldX, this.oldY, this.startPoint); 7492 } 7493 this.controller.selectAndShowItems(Arrays.asList(new DimensionLine [] {this.dimensionLine})); 7494 } 7495 7496 @Override 7497 public void redo() throws CannotRedoException { 7498 super.redo(); 7499 if (this.reversed) { 7500 moveDimensionLinePoint(this.dimensionLine, this.newX, this.newY, !this.startPoint); 7501 reverseDimensionLine(this.dimensionLine); 7502 } else { 7503 moveDimensionLinePoint(this.dimensionLine, this.newX, this.newY, this.startPoint); 7504 } 7505 this.controller.selectAndShowItems(Arrays.asList(new DimensionLine [] {this.dimensionLine})); 7506 } 7507 } 7508 7509 /** 7510 * Posts an undoable operation about <code>dimensionLine</code> offset change. 7511 */ 7512 private void postDimensionLineOffset(final DimensionLine dimensionLine, final float oldOffset) { 7513 final float newOffset = dimensionLine.getOffset(); 7514 if (newOffset != oldOffset) { 7515 this.undoSupport.postEdit(new DimensionLineOffsetModificationUndoableEdit(this, this.preferences, 7516 oldOffset, dimensionLine, newOffset)); 7517 } 7518 } 7519 7520 /** 7521 * Undoable edit for dimension line offset modification. 7522 */ 7523 private static class DimensionLineOffsetModificationUndoableEdit extends LocalizedUndoableEdit { 7524 private final PlanController controller; 7525 private final float oldOffset; 7526 private final DimensionLine dimensionLine; 7527 private final float newOffset; 7528 7529 public DimensionLineOffsetModificationUndoableEdit(PlanController controller, UserPreferences preferences, 7530 float oldOffset, DimensionLine dimensionLine, float newOffset) { 7531 super(preferences, PlanController.class, "undoDimensionLineOffsetName"); 7532 this.controller = controller; 7533 this.oldOffset = oldOffset; 7534 this.dimensionLine = dimensionLine; 7535 this.newOffset = newOffset; 7536 } 7537 7538 @Override 7539 public void undo() throws CannotUndoException { 7540 super.undo(); 7541 this.dimensionLine.setOffset(this.oldOffset); 7542 this.controller.selectAndShowItems(Arrays.asList(new DimensionLine [] {this.dimensionLine})); 7543 } 7544 7545 @Override 7546 public void redo() throws CannotRedoException { 7547 super.redo(); 7548 this.dimensionLine.setOffset(this.newOffset); 7549 this.controller.selectAndShowItems(Arrays.asList(new DimensionLine [] {this.dimensionLine})); 7550 } 7551 } 7552 7553 /** 7554 * Posts an undoable operation about <code>polyline</code> resizing. 7555 */ 7556 private void postPolylineResize(final Polyline polyline, final float oldX, final float oldY, 7557 final int pointIndex) { 7558 float [] polylinePoint = polyline.getPoints() [pointIndex]; 7559 final float newX = polylinePoint [0]; 7560 final float newY = polylinePoint [1]; 7561 if (newX != oldX || newY != oldY) { 7562 this.undoSupport.postEdit(new PolylineResizingUndoableEdit(this, this.preferences, 7563 oldX, oldY, polyline, pointIndex, newX, newY)); 7564 } 7565 } 7566 7567 /** 7568 * Undoable edit for polyline resizing. 7569 */ 7570 private static class PolylineResizingUndoableEdit extends LocalizedUndoableEdit { 7571 private final PlanController controller; 7572 private final float oldX; 7573 private final float oldY; 7574 private final Polyline polyline; 7575 private final int pointIndex; 7576 private final float newX; 7577 private final float newY; 7578 7579 public PolylineResizingUndoableEdit(PlanController controller, UserPreferences preferences, 7580 float oldX, float oldY, Polyline polyline, int pointIndex, float newX, float newY) { 7581 super(preferences, PlanController.class, "undoPolylineResizeName"); 7582 this.controller = controller; 7583 this.oldX = oldX; 7584 this.oldY = oldY; 7585 this.polyline = polyline; 7586 this.pointIndex = pointIndex; 7587 this.newX = newX; 7588 this.newY = newY; 7589 } 7590 7591 @Override 7592 public void undo() throws CannotUndoException { 7593 super.undo(); 7594 this.polyline.setPoint(this.oldX, this.oldY, this.pointIndex); 7595 this.controller.selectAndShowItems(Arrays.asList(new Polyline [] {this.polyline})); 7596 } 7597 7598 @Override 7599 public void redo() throws CannotRedoException { 7600 super.redo(); 7601 this.polyline.setPoint(this.newX, this.newY, this.pointIndex); 7602 this.controller.selectAndShowItems(Arrays.asList(new Polyline [] {this.polyline})); 7603 } 7604 } 7605 7606 /** 7607 * Post to undo support a north direction change on <code>compass</code>. 7608 */ 7609 private void postCompassRotation(final Compass compass, 7610 final float oldNorthDirection) { 7611 final float newNorthDirection = compass.getNorthDirection(); 7612 if (newNorthDirection != oldNorthDirection) { 7613 this.undoSupport.postEdit(new CompassRotationUndoableEdit(this, this.preferences, 7614 oldNorthDirection, compass, newNorthDirection)); 7615 } 7616 } 7617 7618 /** 7619 * Undoable edit for compass rotation. 7620 */ 7621 private static class CompassRotationUndoableEdit extends LocalizedUndoableEdit { 7622 private final PlanController controller; 7623 private final float oldNorthDirection; 7624 private final Compass compass; 7625 private final float newNorthDirection; 7626 7627 public CompassRotationUndoableEdit(PlanController controller, UserPreferences preferences, 7628 float oldNorthDirection, Compass compass, float newNorthDirection) { 7629 super(preferences, PlanController.class, "undoCompassRotationName"); 7630 this.controller = controller; 7631 this.compass = compass; 7632 this.newNorthDirection = newNorthDirection; 7633 this.oldNorthDirection = oldNorthDirection; 7634 } 7635 7636 @Override 7637 public void undo() throws CannotUndoException { 7638 super.undo(); 7639 this.compass.setNorthDirection(this.oldNorthDirection); 7640 this.controller.selectAndShowItems(Arrays.asList(new Compass [] {this.compass})); 7641 } 7642 7643 @Override 7644 public void redo() throws CannotRedoException { 7645 super.redo(); 7646 this.compass.setNorthDirection(this.newNorthDirection); 7647 this.controller.selectAndShowItems(Arrays.asList(new Compass [] {this.compass})); 7648 } 7649 } 7650 7651 /** 7652 * Post to undo support a size change on <code>compass</code>. 7653 */ 7654 private void postCompassResize(final Compass compass, 7655 final float oldDiameter) { 7656 final float newDiameter = compass.getDiameter(); 7657 if (newDiameter != oldDiameter) { 7658 this.undoSupport.postEdit(new CompassResizingUndoableEdit(this, this.preferences, 7659 oldDiameter, compass, newDiameter)); 7660 } 7661 } 7662 7663 /** 7664 * Undoable edit for compass resizing. 7665 */ 7666 private static class CompassResizingUndoableEdit extends LocalizedUndoableEdit { 7667 private final PlanController controller; 7668 private final float oldDiameter; 7669 private final Compass compass; 7670 private final float newDiameter; 7671 7672 public CompassResizingUndoableEdit(PlanController controller, UserPreferences preferences, 7673 float oldDiameter, Compass compass, float newDiameter) { 7674 super(preferences, PlanController.class, "undoCompassResizeName"); 7675 this.controller = controller; 7676 this.oldDiameter = oldDiameter; 7677 this.compass = compass; 7678 this.newDiameter = newDiameter; 7679 } 7680 7681 @Override 7682 public void undo() throws CannotUndoException { 7683 super.undo(); 7684 this.compass.setDiameter(this.oldDiameter); 7685 this.controller.selectAndShowItems(Arrays.asList(new Compass [] {this.compass})); 7686 } 7687 7688 @Override 7689 public void redo() throws CannotRedoException { 7690 super.redo(); 7691 this.compass.setDiameter(this.newDiameter); 7692 this.controller.selectAndShowItems(Arrays.asList(new Compass [] {this.compass})); 7693 } 7694 } 7695 7696 /** 7697 * Returns the points of a general path which contains only one path. 7698 */ 7699 private float [][] getPathPoints(GeneralPath path, 7700 boolean removeAlignedPoints) { 7701 List<float []> pathPoints = new ArrayList<float[]>(); 7702 float [] previousPathPoint = null; 7703 for (PathIterator it = path.getPathIterator(null); !it.isDone(); it.next()) { 7704 float [] pathPoint = new float[2]; 7705 if (it.currentSegment(pathPoint) != PathIterator.SEG_CLOSE 7706 && (previousPathPoint == null 7707 || !Arrays.equals(pathPoint, previousPathPoint))) { 7708 boolean replacePoint = false; 7709 if (removeAlignedPoints 7710 && pathPoints.size() > 1) { 7711 // Check if pathPoint is aligned with the last line added to pathPoints 7712 float [] lastLineStartPoint = pathPoints.get(pathPoints.size() - 2); 7713 float [] lastLineEndPoint = previousPathPoint; 7714 replacePoint = Line2D.ptLineDistSq(lastLineStartPoint [0], lastLineStartPoint [1], 7715 lastLineEndPoint [0], lastLineEndPoint [1], 7716 pathPoint [0], pathPoint [1]) < 0.0001; 7717 } 7718 if (replacePoint) { 7719 pathPoints.set(pathPoints.size() - 1, pathPoint); 7720 } else { 7721 pathPoints.add(pathPoint); 7722 } 7723 previousPathPoint = pathPoint; 7724 } 7725 } 7726 7727 // Remove last point if it's equal to first point 7728 if (pathPoints.size() > 1 7729 && Arrays.equals(pathPoints.get(0), pathPoints.get(pathPoints.size() - 1))) { 7730 pathPoints.remove(pathPoints.size() - 1); 7731 } 7732 7733 return pathPoints.toArray(new float [pathPoints.size()][]); 7734 } 7735 7736 /** 7737 * Returns the list of closed paths that may define rooms from 7738 * the current set of home walls. 7739 */ 7740 private List<GeneralPath> getRoomPathsFromWalls() { 7741 if (this.roomPathsCache == null) { 7742 // Iterate over all the paths the walls area contains 7743 Area wallsArea = getWallsArea(false); 7744 List<GeneralPath> roomPaths = getAreaPaths(wallsArea); 7745 Area insideWallsArea = new Area(wallsArea); 7746 for (GeneralPath roomPath : roomPaths) { 7747 insideWallsArea.add(new Area(roomPath)); 7748 } 7749 7750 this.roomPathsCache = roomPaths; 7751 this.insideWallsAreaCache = insideWallsArea; 7752 } 7753 return this.roomPathsCache; 7754 } 7755 7756 /** 7757 * Returns the paths described by the given <code>area</code>. 7758 */ 7759 private List<GeneralPath> getAreaPaths(Area area) { 7760 List<GeneralPath> roomPaths = new ArrayList<GeneralPath>(); 7761 GeneralPath roomPath = null; 7762 float [] previousRoomPoint = null; 7763 for (PathIterator it = area.getPathIterator(null, 0.5f); !it.isDone(); it.next()) { 7764 float [] roomPoint = new float[2]; 7765 switch (it.currentSegment(roomPoint)) { 7766 case PathIterator.SEG_MOVETO : 7767 roomPath = new GeneralPath(); 7768 roomPath.moveTo(roomPoint [0], roomPoint [1]); 7769 previousRoomPoint = roomPoint; 7770 break; 7771 case PathIterator.SEG_LINETO : 7772 if ((roomPoint [0] != previousRoomPoint [0] 7773 || roomPoint [1] != previousRoomPoint [1]) 7774 && Point2D.distanceSq(roomPoint [0], roomPoint [1], previousRoomPoint [0], previousRoomPoint [1]) > 1E-10) { 7775 roomPath.lineTo(roomPoint [0], roomPoint [1]); 7776 previousRoomPoint = roomPoint; 7777 } 7778 break; 7779 case PathIterator.SEG_CLOSE : 7780 roomPath.closePath(); 7781 roomPaths.add(roomPath); 7782 break; 7783 } 7784 } 7785 return roomPaths; 7786 } 7787 7788 /** 7789 * Returns the area that includes walls and inside walls area. 7790 */ 7791 private Area getInsideWallsArea() { 7792 if (this.insideWallsAreaCache == null) { 7793 getRoomPathsFromWalls(); 7794 } 7795 return this.insideWallsAreaCache; 7796 } 7797 7798 /** 7799 * Returns the area covered by walls. 7800 */ 7801 private Area getWallsArea(boolean includeBaseboards) { 7802 if (!includeBaseboards && this.wallsAreaCache == null 7803 || includeBaseboards && this.wallsIncludingBaseboardsAreaCache == null) { 7804 // Compute walls area 7805 Area wallsArea = new Area(); 7806 Level selectedLevel = this.home.getSelectedLevel(); 7807 for (Wall wall : this.home.getWalls()) { 7808 if (wall.isAtLevel(selectedLevel)) { 7809 wallsArea.add(new Area(getPath(wall.getPoints(includeBaseboards)))); 7810 } 7811 } 7812 if (includeBaseboards) { 7813 this.wallsIncludingBaseboardsAreaCache = wallsArea; 7814 } else { 7815 this.wallsAreaCache = wallsArea; 7816 } 7817 } 7818 return includeBaseboards 7819 ? this.wallsIncludingBaseboardsAreaCache 7820 : this.wallsAreaCache; 7821 } 7822 7823 /** 7824 * Returns the shape matching the coordinates in <code>points</code> array. 7825 */ 7826 private GeneralPath getPath(float [][] points) { 7827 GeneralPath path = new GeneralPath(); 7828 path.moveTo(points [0][0], points [0][1]); 7829 for (int i = 1; i < points.length; i++) { 7830 path.lineTo(points [i][0], points [i][1]); 7831 } 7832 path.closePath(); 7833 return path; 7834 } 7835 7836 /** 7837 * Returns the path matching a given area. 7838 */ 7839 private GeneralPath getPath(Area area) { 7840 GeneralPath path = new GeneralPath(); 7841 float [] point = new float [2]; 7842 for (PathIterator it = area.getPathIterator(null, 0.5f); !it.isDone(); it.next()) { 7843 switch (it.currentSegment(point)) { 7844 case PathIterator.SEG_MOVETO : 7845 path.moveTo(point [0], point [1]); 7846 break; 7847 case PathIterator.SEG_LINETO : 7848 path.lineTo(point [0], point [1]); 7849 break; 7850 } 7851 } 7852 return path; 7853 } 7854 7855 /** 7856 * Selects the level of the first elevatable item in the current selection 7857 * if no selected item is visible at the selected level. 7858 */ 7859 private void selectLevelFromSelectedItems() { 7860 Level selectedLevel = this.home.getSelectedLevel(); 7861 List<Selectable> selectedItems = this.home.getSelectedItems(); 7862 for (Object item : selectedItems) { 7863 if (item instanceof Elevatable 7864 && ((Elevatable)item).isAtLevel(selectedLevel)) { 7865 return; 7866 } 7867 } 7868 7869 for (Object item : selectedItems) { 7870 if (item instanceof Elevatable) { 7871 setSelectedLevel(((Elevatable)item).getLevel()); 7872 break; 7873 } 7874 } 7875 } 7876 7877 /** 7878 * Stores the size of a resized piece of furniture. 7879 */ 7880 private static class ResizedPieceOfFurniture { 7881 private final HomePieceOfFurniture piece; 7882 private final float x; 7883 private final float y; 7884 private final float width; 7885 private final float depth; 7886 private final float height; 7887 private final boolean doorOrWindowBoundToWall; 7888 private final float [] groupFurnitureX; 7889 private final float [] groupFurnitureY; 7890 private final float [] groupFurnitureWidth; 7891 private final float [] groupFurnitureDepth; 7892 private final float [] groupFurnitureHeight; 7893 7894 public ResizedPieceOfFurniture(HomePieceOfFurniture piece) { 7895 this.piece = piece; 7896 this.x = piece.getX(); 7897 this.y = piece.getY(); 7898 this.width = piece.getWidth(); 7899 this.depth = piece.getDepth(); 7900 this.height = piece.getHeight(); 7901 this.doorOrWindowBoundToWall = piece instanceof HomeDoorOrWindow 7902 && ((HomeDoorOrWindow)piece).isBoundToWall(); 7903 if (piece instanceof HomeFurnitureGroup) { 7904 List<HomePieceOfFurniture> groupFurniture = ((HomeFurnitureGroup)piece).getAllFurniture(); 7905 this.groupFurnitureX = new float [groupFurniture.size()]; 7906 this.groupFurnitureY = new float [groupFurniture.size()]; 7907 this.groupFurnitureWidth = new float [groupFurniture.size()]; 7908 this.groupFurnitureDepth = new float [groupFurniture.size()]; 7909 this.groupFurnitureHeight = new float [groupFurniture.size()]; 7910 for (int i = 0; i < groupFurniture.size(); i++) { 7911 HomePieceOfFurniture groupPiece = groupFurniture.get(i); 7912 this.groupFurnitureX [i] = groupPiece.getX(); 7913 this.groupFurnitureY [i] = groupPiece.getY(); 7914 this.groupFurnitureWidth [i] = groupPiece.getWidth(); 7915 this.groupFurnitureDepth [i] = groupPiece.getDepth(); 7916 this.groupFurnitureHeight [i] = groupPiece.getHeight(); 7917 } 7918 } else { 7919 this.groupFurnitureX = null; 7920 this.groupFurnitureY = null; 7921 this.groupFurnitureWidth = null; 7922 this.groupFurnitureDepth = null; 7923 this.groupFurnitureHeight = null; 7924 } 7925 } 7926 7927 public HomePieceOfFurniture getPieceOfFurniture() { 7928 return this.piece; 7929 } 7930 7931 public float getWidth() { 7932 return this.width; 7933 } 7934 7935 public float getDepth() { 7936 return this.depth; 7937 } 7938 7939 public float getHeight() { 7940 return this.height; 7941 } 7942 7943 public boolean isDoorOrWindowBoundToWall() { 7944 return this.doorOrWindowBoundToWall; 7945 } 7946 7947 public void reset() { 7948 this.piece.setX(this.x); 7949 this.piece.setY(this.y); 7950 setPieceOfFurnitureSize(this.piece, this.width, this.depth, this.height); 7951 if (this.piece instanceof HomeDoorOrWindow) { 7952 ((HomeDoorOrWindow)this.piece).setBoundToWall(this.doorOrWindowBoundToWall); 7953 } 7954 if (this.piece instanceof HomeFurnitureGroup) { 7955 List<HomePieceOfFurniture> groupFurniture = ((HomeFurnitureGroup)this.piece).getAllFurniture(); 7956 for (int i = 0; i < groupFurniture.size(); i++) { 7957 HomePieceOfFurniture groupPiece = groupFurniture.get(i); 7958 if (this.piece.isResizable()) { 7959 // Restore group furniture location and size because resizing a group isn't reversible 7960 groupPiece.setX(this.groupFurnitureX [i]); 7961 groupPiece.setY(this.groupFurnitureY [i]); 7962 setPieceOfFurnitureSize(groupPiece, 7963 this.groupFurnitureWidth [i], this.groupFurnitureDepth [i], this.groupFurnitureHeight [i]); 7964 } 7965 } 7966 } 7967 } 7968 7969 public static void setPieceOfFurnitureSize(HomePieceOfFurniture piece, 7970 float width, float depth, float height) { 7971 if (piece.isHorizontallyRotated()) { 7972 // Furniture rotated around horizontal axes are always changed proportionally 7973 float scale = width / piece.getWidth(); 7974 piece.scale(scale); 7975 piece.setWidthInPlan(scale * piece.getWidthInPlan()); 7976 piece.setDepthInPlan(scale * piece.getDepthInPlan()); 7977 piece.setHeightInPlan(scale * piece.getHeightInPlan()); 7978 if (piece instanceof HomeFurnitureGroup) { 7979 for (HomePieceOfFurniture childPiece : ((HomeFurnitureGroup)piece).getAllFurniture()) { 7980 childPiece.setWidthInPlan(scale * childPiece.getWidthInPlan()); 7981 childPiece.setDepthInPlan(scale * childPiece.getDepthInPlan()); 7982 childPiece.setHeightInPlan(scale * childPiece.getHeightInPlan()); 7983 } 7984 } 7985 } else { 7986 float widthInPlan = piece.getWidthInPlan() * width / piece.getWidth(); 7987 piece.setWidth(width); 7988 piece.setWidthInPlan(widthInPlan); 7989 float depthInPlan = piece.getDepthInPlan() * depth / piece.getDepth(); 7990 piece.setDepth(depth); 7991 piece.setDepthInPlan(depthInPlan); 7992 float heightInPlan = piece.getHeightInPlan() * height / piece.getHeight(); 7993 piece.setHeight(height); 7994 piece.setHeightInPlan(heightInPlan); 7995 if (piece instanceof HomeFurnitureGroup) { 7996 for (HomePieceOfFurniture childPiece : ((HomeFurnitureGroup)piece).getAllFurniture()) { 7997 childPiece.setWidthInPlan(childPiece.getWidth()); 7998 childPiece.setDepthInPlan(childPiece.getDepth()); 7999 childPiece.setHeightInPlan(childPiece.getHeight()); 8000 } 8001 } 8002 } 8003 } 8004 } 8005 8006 /** 8007 * Stores the walls at start and at end of a given wall. This data are useful 8008 * to add a collection of walls after an undo/redo delete operation. 8009 */ 8010 private static final class JoinedWall { 8011 private final Wall wall; 8012 private final Level level; 8013 private final float xStart; 8014 private final float yStart; 8015 private final float xEnd; 8016 private final float yEnd; 8017 private final Wall wallAtStart; 8018 private final Wall wallAtEnd; 8019 private final boolean joinedAtEndOfWallAtStart; 8020 private final boolean joinedAtStartOfWallAtEnd; 8021 8022 public JoinedWall(Wall wall) { 8023 this.wall = wall; 8024 this.level = wall.getLevel(); 8025 this.xStart = wall.getXStart(); 8026 this.xEnd = wall.getXEnd(); 8027 this.yStart = wall.getYStart(); 8028 this.yEnd = wall.getYEnd(); 8029 this.wallAtStart = wall.getWallAtStart(); 8030 this.joinedAtEndOfWallAtStart = 8031 this.wallAtStart != null 8032 && this.wallAtStart.getWallAtEnd() == wall; 8033 this.wallAtEnd = wall.getWallAtEnd(); 8034 this.joinedAtStartOfWallAtEnd = 8035 this.wallAtEnd != null 8036 && wallAtEnd.getWallAtStart() == wall; 8037 } 8038 8039 public Wall getWall() { 8040 return this.wall; 8041 } 8042 8043 public Level getLevel() { 8044 return this.level; 8045 } 8046 8047 public float getXStart() { 8048 return this.xStart; 8049 } 8050 8051 public float getYStart() { 8052 return this.yStart; 8053 } 8054 8055 public float getXEnd() { 8056 return this.xEnd; 8057 } 8058 8059 public float getYEnd() { 8060 return this.yEnd; 8061 } 8062 8063 public Wall getWallAtEnd() { 8064 return this.wallAtEnd; 8065 } 8066 8067 public Wall getWallAtStart() { 8068 return this.wallAtStart; 8069 } 8070 8071 public boolean isJoinedAtEndOfWallAtStart() { 8072 return this.joinedAtEndOfWallAtStart; 8073 } 8074 8075 public boolean isJoinedAtStartOfWallAtEnd() { 8076 return this.joinedAtStartOfWallAtEnd; 8077 } 8078 8079 /** 8080 * A helper method that builds an array of <code>JoinedWall</code> objects 8081 * for a given list of walls. 8082 */ 8083 public static JoinedWall [] getJoinedWalls(List<Wall> walls) { 8084 JoinedWall [] joinedWalls = new JoinedWall [walls.size()]; 8085 for (int i = 0; i < joinedWalls.length; i++) { 8086 joinedWalls [i] = new JoinedWall(walls.get(i)); 8087 } 8088 return joinedWalls; 8089 } 8090 8091 /** 8092 * A helper method that builds a list of <code>Wall</code> objects 8093 * for a given array of <code>JoinedWall</code> objects. 8094 */ 8095 public static List<Wall> getWalls(JoinedWall [] joinedWalls) { 8096 Wall [] walls = new Wall [joinedWalls.length]; 8097 for (int i = 0; i < joinedWalls.length; i++) { 8098 walls [i] = joinedWalls [i].getWall(); 8099 } 8100 return Arrays.asList(walls); 8101 } 8102 } 8103 8104 /** 8105 * A point which coordinates are computed with an angle magnetism algorithm. 8106 */ 8107 private static class PointWithAngleMagnetism { 8108 private static final int CIRCLE_STEPS_15_DEG = 24; 8109 8110 private float x; 8111 private float y; 8112 private float angle; 8113 8114 /** 8115 * Create a point that applies angle magnetism to point (<code>x</code>, <code>y</code>). 8116 * Point end coordinates stored at object initialization may be different from x or y, 8117 * to match the closest point belonging to one of the radius of a circle centered at 8118 * (<code>xStart</code>, <code>yStart</code>), each radius being a multiple of 15 degrees. 8119 * The length of the line joining (<code>xStart</code>, <code>yStart</code>) to the computed 8120 * point is approximated depending on the current <code>unit</code> and scale. 8121 */ 8122 public PointWithAngleMagnetism(float xStart, float yStart, float x, float y, 8123 LengthUnit unit, float maxLengthDelta) { 8124 this(xStart, yStart, x, y, unit, maxLengthDelta, CIRCLE_STEPS_15_DEG); 8125 } 8126 8127 public PointWithAngleMagnetism(float xStart, float yStart, float x, float y, 8128 LengthUnit unit, float maxLengthDelta, int circleSteps) { 8129 this.x = x; 8130 this.y = y; 8131 if (xStart == x) { 8132 // Apply magnetism to the length of the line joining start point to magnetized point 8133 float magnetizedLength = unit.getMagnetizedLength(Math.abs(yStart - y), maxLengthDelta); 8134 this.y = yStart + (float)(magnetizedLength * Math.signum(y - yStart)); 8135 } else if (yStart == y) { 8136 // Apply magnetism to the length of the line joining start point to magnetized point 8137 float magnetizedLength = unit.getMagnetizedLength(Math.abs(xStart - x), maxLengthDelta); 8138 this.x = xStart + (float)(magnetizedLength * Math.signum(x - xStart)); 8139 } else { // xStart != x && yStart != y 8140 double angleStep = 2 * Math.PI / circleSteps; 8141 // Caution : pixel coordinate space is indirect ! 8142 double angle = Math.atan2(yStart - y, x - xStart); 8143 // Compute previous angle closest to a step angle (multiple of angleStep) 8144 double previousStepAngle = Math.floor(angle / angleStep) * angleStep; 8145 double angle1; 8146 double tanAngle1; 8147 double angle2; 8148 double tanAngle2; 8149 // Compute the tan of previousStepAngle and the next step angle 8150 if (Math.tan(angle) > 0) { 8151 angle1 = previousStepAngle; 8152 tanAngle1 = Math.tan(previousStepAngle); 8153 angle2 = previousStepAngle + angleStep; 8154 tanAngle2 = Math.tan(previousStepAngle + angleStep); 8155 } else { 8156 // If slope is negative inverse the order of the two angles 8157 angle1 = previousStepAngle + angleStep; 8158 tanAngle1 = Math.tan(previousStepAngle + angleStep); 8159 angle2 = previousStepAngle; 8160 tanAngle2 = Math.tan(previousStepAngle); 8161 } 8162 // Search in the first quarter of the trigonometric circle, 8163 // the point (xEnd1,yEnd1) or (xEnd2,yEnd2) closest to point 8164 // (xEnd,yEnd) that belongs to angle 1 or angle 2 radius 8165 double firstQuarterTanAngle1 = Math.abs(tanAngle1); 8166 double firstQuarterTanAngle2 = Math.abs(tanAngle2); 8167 float xEnd1 = Math.abs(xStart - x); 8168 float yEnd2 = Math.abs(yStart - y); 8169 float xEnd2 = 0; 8170 // If angle 2 is greater than 0 rad 8171 if (firstQuarterTanAngle2 > 1E-10) { 8172 // Compute the abscissa of point 2 that belongs to angle 1 radius at 8173 // y2 ordinate 8174 xEnd2 = (float)(yEnd2 / firstQuarterTanAngle2); 8175 } 8176 float yEnd1 = 0; 8177 // If angle 1 is smaller than PI / 2 rad 8178 if (firstQuarterTanAngle1 < 1E10) { 8179 // Compute the ordinate of point 1 that belongs to angle 1 radius at 8180 // x1 abscissa 8181 yEnd1 = (float)(xEnd1 * firstQuarterTanAngle1); 8182 } 8183 8184 // Apply magnetism to the smallest distance 8185 double magnetismAngle; 8186 if (Math.abs(xEnd2 - xEnd1) < Math.abs(yEnd1 - yEnd2)) { 8187 magnetismAngle = angle2; 8188 this.x = xStart + (float)((yStart - y) / tanAngle2); 8189 } else { 8190 magnetismAngle = angle1; 8191 this.y = yStart - (float)((x - xStart) * tanAngle1); 8192 } 8193 8194 // Apply magnetism to the length of the line joining start point 8195 // to magnetized point 8196 float magnetizedLength = unit.getMagnetizedLength((float)Point2D.distance(xStart, yStart, 8197 this.x, this.y), maxLengthDelta); 8198 this.x = xStart + (float)(magnetizedLength * Math.cos(magnetismAngle)); 8199 this.y = yStart - (float)(magnetizedLength * Math.sin(magnetismAngle)); 8200 this.angle = (float)magnetismAngle; 8201 } 8202 } 8203 8204 /** 8205 * Returns the abscissa of end point. 8206 */ 8207 public float getX() { 8208 return this.x; 8209 } 8210 8211 /** 8212 * Sets the abscissa of end point. 8213 */ 8214 protected void setX(float x) { 8215 this.x = x; 8216 } 8217 8218 /** 8219 * Returns the ordinate of end point. 8220 */ 8221 public float getY() { 8222 return this.y; 8223 } 8224 8225 /** 8226 * Sets the ordinate of end point. 8227 */ 8228 protected void setY(float y) { 8229 this.y = y; 8230 } 8231 8232 protected float getAngle() { 8233 return this.angle; 8234 } 8235 } 8236 8237 /** 8238 * A point with coordinates computed with angle and wall points magnetism. 8239 */ 8240 private class WallPointWithAngleMagnetism extends PointWithAngleMagnetism { 8241 public WallPointWithAngleMagnetism(Wall editedWall, float xWall, float yWall, float x, float y) { 8242 super(xWall, yWall, x, y, preferences.getLengthUnit(), getView().getPixelLength()); 8243 float margin = PIXEL_MARGIN / getScale(); 8244 // Search which wall start or end point is close to (x, y) 8245 // ignoring the start and end point of editedWall 8246 float deltaXToClosestWall = Float.POSITIVE_INFINITY; 8247 float deltaYToClosestWall = Float.POSITIVE_INFINITY; 8248 float xClosestWall = 0; 8249 float yClosestWall = 0; 8250 for (Wall wall : getDetectableWallsAtSelectedLevel()) { 8251 if (wall != editedWall) { 8252 if (Math.abs(getX() - wall.getXStart()) < margin 8253 && (editedWall == null 8254 || !equalsWallPoint(wall.getXStart(), wall.getYStart(), editedWall))) { 8255 if (Math.abs(deltaYToClosestWall) > Math.abs(getY() - wall.getYStart())) { 8256 xClosestWall = wall.getXStart(); 8257 deltaYToClosestWall = getY() - yClosestWall; 8258 } 8259 } else if (Math.abs(getX() - wall.getXEnd()) < margin 8260 && (editedWall == null 8261 || !equalsWallPoint(wall.getXEnd(), wall.getYEnd(), editedWall))) { 8262 if (Math.abs(deltaYToClosestWall) > Math.abs(getY() - wall.getYEnd())) { 8263 xClosestWall = wall.getXEnd(); 8264 deltaYToClosestWall = getY() - yClosestWall; 8265 } 8266 } 8267 8268 if (Math.abs(getY() - wall.getYStart()) < margin 8269 && (editedWall == null 8270 || !equalsWallPoint(wall.getXStart(), wall.getYStart(), editedWall))) { 8271 if (Math.abs(deltaXToClosestWall) > Math.abs(getX() - wall.getXStart())) { 8272 yClosestWall = wall.getYStart(); 8273 deltaXToClosestWall = getX() - xClosestWall; 8274 } 8275 } else if (Math.abs(getY() - wall.getYEnd()) < margin 8276 && (editedWall == null 8277 || !equalsWallPoint(wall.getXEnd(), wall.getYEnd(), editedWall))) { 8278 if (Math.abs(deltaXToClosestWall) > Math.abs(getX() - wall.getXEnd())) { 8279 yClosestWall = wall.getYEnd(); 8280 deltaXToClosestWall = getX() - xClosestWall; 8281 } 8282 } 8283 } 8284 } 8285 8286 if (editedWall != null) { 8287 double alpha = -Math.tan(getAngle()); 8288 double beta = Math.abs(alpha) < 1E10 8289 ? yWall - alpha * xWall 8290 : Double.POSITIVE_INFINITY; 8291 if (deltaXToClosestWall != Float.POSITIVE_INFINITY && Math.abs(alpha) > 1E-10) { 8292 float newX = (float)((yClosestWall - beta) / alpha); 8293 if (Point2D.distanceSq(getX(), getY(), newX, yClosestWall) <= margin * margin) { 8294 setX(newX); 8295 setY(yClosestWall); 8296 return; 8297 } 8298 } 8299 if (deltaYToClosestWall != Float.POSITIVE_INFINITY 8300 && beta != Double.POSITIVE_INFINITY) { 8301 float newY = (float)(alpha * xClosestWall + beta); 8302 if (Point2D.distanceSq(getX(), getY(), xClosestWall, newY) <= margin * margin) { 8303 setX(xClosestWall); 8304 setY(newY); 8305 } 8306 } 8307 } else { 8308 if (deltaXToClosestWall != Float.POSITIVE_INFINITY) { 8309 setY(yClosestWall); 8310 } 8311 if (deltaYToClosestWall != Float.POSITIVE_INFINITY) { 8312 setX(xClosestWall); 8313 } 8314 } 8315 } 8316 8317 /** 8318 * Returns <code>true</code> if <code>wall</code> start or end point 8319 * equals the point (<code>x</code>, <code>y</code>). 8320 */ 8321 private boolean equalsWallPoint(float x, float y, Wall wall) { 8322 return x == wall.getXStart() && y == wall.getYStart() 8323 || x == wall.getXEnd() && y == wall.getYEnd(); 8324 } 8325 } 8326 8327 /** 8328 * A point with coordinates computed with angle and room points magnetism. 8329 */ 8330 private class RoomPointWithAngleMagnetism extends PointWithAngleMagnetism { 8331 public RoomPointWithAngleMagnetism(Room editedRoom, int editedPointIndex, float xRoom, float yRoom, float x, float y) { 8332 super(xRoom, yRoom, x, y, preferences.getLengthUnit(), getView().getPixelLength()); 8333 float planScale = getScale(); 8334 float margin = PIXEL_MARGIN / planScale; 8335 // Search which room points are close to (x, y) 8336 // ignoring the start and end point of alignedRoom 8337 float deltaXToClosestObject = Float.POSITIVE_INFINITY; 8338 float deltaYToClosestObject = Float.POSITIVE_INFINITY; 8339 float xClosestObject = 0; 8340 float yClosestObject = 0; 8341 for (Room room : getDetectableRoomsAtSelectedLevel()) { 8342 float [][] roomPoints = room.getPoints(); 8343 for (int i = 0; i < roomPoints.length; i++) { 8344 if (editedPointIndex == -1 || (i != editedPointIndex && roomPoints.length > 2)) { 8345 if (Math.abs(getX() - roomPoints [i][0]) < margin 8346 && Math.abs(deltaYToClosestObject) > Math.abs(getY() - roomPoints [i][1])) { 8347 xClosestObject = roomPoints [i][0]; 8348 deltaYToClosestObject = getY() - roomPoints [i][1]; 8349 } 8350 if (Math.abs(getY() - roomPoints [i][1]) < margin 8351 && Math.abs(deltaXToClosestObject) > Math.abs(getX() - roomPoints [i][0])) { 8352 yClosestObject = roomPoints [i][1]; 8353 deltaXToClosestObject = getX() - roomPoints [i][0]; 8354 } 8355 } 8356 } 8357 } 8358 // Search which wall points are close to (x, y) 8359 for (Wall wall : getDetectableWallsAtSelectedLevel()) { 8360 float [][] wallPoints = wall.getPoints(); 8361 // Take into account only points at start and end of the wall 8362 wallPoints = new float [][] {wallPoints [0], wallPoints [wallPoints.length / 2 - 1], 8363 wallPoints [wallPoints.length / 2], wallPoints [wallPoints.length - 1]}; 8364 for (int i = 0; i < wallPoints.length; i++) { 8365 if (Math.abs(getX() - wallPoints [i][0]) < margin 8366 && Math.abs(deltaYToClosestObject) > Math.abs(getY() - wallPoints [i][1])) { 8367 xClosestObject = wallPoints [i][0]; 8368 deltaYToClosestObject = getY() - wallPoints [i][1]; 8369 } 8370 if (Math.abs(getY() - wallPoints [i][1]) < margin 8371 && Math.abs(deltaXToClosestObject) > Math.abs(getX() - wallPoints [i][0])) { 8372 yClosestObject = wallPoints [i][1]; 8373 deltaXToClosestObject = getX() - wallPoints [i][0]; 8374 } 8375 } 8376 } 8377 8378 if (editedRoom != null) { 8379 double alpha = -Math.tan(getAngle()); 8380 double beta = Math.abs(alpha) < 1E10 8381 ? yRoom - alpha * xRoom 8382 : Double.POSITIVE_INFINITY; 8383 if (deltaXToClosestObject != Float.POSITIVE_INFINITY && Math.abs(alpha) > 1E-10) { 8384 float newX = (float)((yClosestObject - beta) / alpha); 8385 if (Point2D.distanceSq(getX(), getY(), newX, yClosestObject) <= margin * margin) { 8386 setX(newX); 8387 setY(yClosestObject); 8388 return; 8389 } 8390 } 8391 if (deltaYToClosestObject != Float.POSITIVE_INFINITY 8392 && beta != Double.POSITIVE_INFINITY) { 8393 float newY = (float)(alpha * xClosestObject + beta); 8394 if (Point2D.distanceSq(getX(), getY(), xClosestObject, newY) <= margin * margin) { 8395 setX(xClosestObject); 8396 setY(newY); 8397 } 8398 } 8399 } else { 8400 if (deltaXToClosestObject != Float.POSITIVE_INFINITY) { 8401 setY(yClosestObject); 8402 } 8403 if (deltaYToClosestObject != Float.POSITIVE_INFINITY) { 8404 setX(xClosestObject); 8405 } 8406 } 8407 } 8408 } 8409 8410 /** 8411 * A point which coordinates are equal to the closest point of a wall or a room. 8412 */ 8413 private class PointMagnetizedToClosestWallOrRoomPoint { 8414 private float x; 8415 private float y; 8416 private boolean magnetized; 8417 8418 /** 8419 * Creates a point that applies magnetism to point (<code>x</code>, <code>y</code>). 8420 * If this point is close to a point of a wall corner or of a room, it will be initialiazed to its coordinates. 8421 */ 8422 public PointMagnetizedToClosestWallOrRoomPoint(float x, float y) { 8423 this(null, -1, x, y); 8424 } 8425 8426 public PointMagnetizedToClosestWallOrRoomPoint(Room editedRoom, int editedPointIndex, float x, float y) { 8427 float margin = getSelectionMargin(); 8428 // Find the closest wall point to (x,y) 8429 double smallestDistance = Double.MAX_VALUE; 8430 for (GeneralPath roomPath : getRoomPathsFromWalls()) { 8431 smallestDistance = updateMagnetizedPoint(-1, x, y, 8432 smallestDistance, getPathPoints(roomPath, false)); 8433 } 8434 for (Room room : getDetectableRoomsAtSelectedLevel()) { 8435 smallestDistance = updateMagnetizedPoint(room == editedRoom ? editedPointIndex : - 1, 8436 x, y, smallestDistance, room.getPoints()); 8437 } 8438 this.magnetized = smallestDistance <= margin * margin; 8439 if (!this.magnetized) { 8440 // Don't magnetism if closest wall point is too far 8441 this.x = x; 8442 this.y = y; 8443 } 8444 } 8445 8446 private double updateMagnetizedPoint(int editedPointIndex, 8447 float x, float y, 8448 double smallestDistance, 8449 float [][] points) { 8450 for (int i = 0; i < points.length; i++) { 8451 if (i != editedPointIndex) { 8452 double distance = Point2D.distanceSq(points [i][0], points [i][1], x, y); 8453 if (distance < smallestDistance) { 8454 this.x = points [i][0]; 8455 this.y = points [i][1]; 8456 smallestDistance = distance; 8457 } 8458 } 8459 } 8460 return smallestDistance; 8461 } 8462 8463 /** 8464 * Returns the abscissa of end point computed with magnetism. 8465 */ 8466 public float getX() { 8467 return this.x; 8468 } 8469 8470 /** 8471 * Returns the ordinate of end point computed with magnetism. 8472 */ 8473 public float getY() { 8474 return this.y; 8475 } 8476 8477 public boolean isMagnetized() { 8478 return this.magnetized; 8479 } 8480 } 8481 8482 /** 8483 * Controller state classes super class. 8484 */ 8485 protected static abstract class ControllerState { 8486 public void enter() { 8487 } 8488 8489 public void exit() { 8490 } 8491 8492 public abstract Mode getMode(); 8493 8494 public void setMode(Mode mode) { 8495 } 8496 8497 public boolean isModificationState() { 8498 return false; 8499 } 8500 8501 public boolean isBasePlanModificationState() { 8502 return false; 8503 } 8504 8505 public void deleteSelection() { 8506 } 8507 8508 public void escape() { 8509 } 8510 8511 public void moveSelection(float dx, float dy) { 8512 } 8513 8514 public void toggleMagnetism(boolean magnetismToggled) { 8515 } 8516 8517 public void setAlignmentActivated(boolean alignmentActivated) { 8518 } 8519 8520 public void setDuplicationActivated(boolean duplicationActivated) { 8521 } 8522 8523 public void setEditionActivated(boolean editionActivated) { 8524 } 8525 8526 public void updateEditableProperty(EditableProperty editableField, Object value) { 8527 } 8528 8529 public void pressMouse(float x, float y, int clickCount, 8530 boolean shiftDown, boolean duplicationActivated) { 8531 } 8532 8533 public void releaseMouse(float x, float y) { 8534 } 8535 8536 public void moveMouse(float x, float y) { 8537 } 8538 8539 public void zoom(float factor) { 8540 } 8541 } 8542 8543 /** 8544 * A decorator on controller state, useful to change the behavior of an existing state. 8545 * @since 6.4 8546 */ 8547 protected static abstract class ControllerStateDecorator extends ControllerState { 8548 private final ControllerState state; 8549 8550 public ControllerStateDecorator(ControllerState state) { 8551 this.state = state; 8552 } 8553 8554 @Override 8555 public void enter() { 8556 this.state.enter(); 8557 } 8558 8559 @Override 8560 public void exit() { 8561 this.state.exit(); 8562 } 8563 8564 @Override 8565 public Mode getMode() { 8566 return this.state.getMode(); 8567 } 8568 8569 @Override 8570 public void setMode(Mode mode) { 8571 this.state.setMode(mode); 8572 } 8573 8574 @Override 8575 public boolean isModificationState() { 8576 return this.state.isModificationState(); 8577 } 8578 8579 @Override 8580 public boolean isBasePlanModificationState() { 8581 return this.state.isBasePlanModificationState(); 8582 } 8583 8584 @Override 8585 public void deleteSelection() { 8586 this.state.deleteSelection(); 8587 } 8588 8589 @Override 8590 public void escape() { 8591 this.state.escape(); 8592 } 8593 8594 @Override 8595 public void moveSelection(float dx, float dy) { 8596 this.state.moveSelection(dx, dy); 8597 } 8598 8599 @Override 8600 public void toggleMagnetism(boolean magnetismToggled) { 8601 this.state.toggleMagnetism(magnetismToggled); 8602 } 8603 8604 @Override 8605 public void setAlignmentActivated(boolean alignmentActivated) { 8606 this.state.setAlignmentActivated(alignmentActivated); 8607 } 8608 8609 @Override 8610 public void setDuplicationActivated(boolean duplicationActivated) { 8611 this.state.setDuplicationActivated(duplicationActivated); 8612 } 8613 8614 @Override 8615 public void setEditionActivated(boolean editionActivated) { 8616 this.state.setEditionActivated(editionActivated); 8617 } 8618 8619 @Override 8620 public void updateEditableProperty(EditableProperty editableField, 8621 Object value) { 8622 this.state.updateEditableProperty(editableField, value); 8623 } 8624 8625 @Override 8626 public void pressMouse(float x, float y, int clickCount, 8627 boolean shiftDown, boolean duplicationActivated) { 8628 this.state.pressMouse(x, y, clickCount, shiftDown, duplicationActivated); 8629 } 8630 8631 @Override 8632 public void releaseMouse(float x, float y) { 8633 this.state.releaseMouse(x, y); 8634 } 8635 8636 @Override 8637 public void moveMouse(float x, float y) { 8638 this.state.moveMouse(x, y); 8639 } 8640 8641 @Override 8642 public void zoom(float factor) { 8643 this.state.zoom(factor); 8644 } 8645 } 8646 8647 // ControllerState subclasses 8648 8649 /** 8650 * Abstract state able to manage the transition to other modes. 8651 */ 8652 private abstract class AbstractModeChangeState extends ControllerState { 8653 @Override 8654 public void setMode(Mode mode) { 8655 if (mode == Mode.SELECTION) { 8656 setState(getSelectionState()); 8657 } else if (mode == Mode.PANNING) { 8658 setState(getPanningState()); 8659 } else if (mode == Mode.WALL_CREATION) { 8660 setState(getWallCreationState()); 8661 } else if (mode == Mode.ROOM_CREATION) { 8662 setState(getRoomCreationState()); 8663 } else if (mode == Mode.POLYLINE_CREATION) { 8664 setState(getPolylineCreationState()); 8665 } else if (mode == Mode.DIMENSION_LINE_CREATION) { 8666 setState(getDimensionLineCreationState()); 8667 } else if (mode == Mode.LABEL_CREATION) { 8668 setState(getLabelCreationState()); 8669 } 8670 } 8671 8672 @Override 8673 public void deleteSelection() { 8674 deleteItems(home.getSelectedItems()); 8675 // Compute again feedback 8676 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 8677 } 8678 8679 @Override 8680 public void moveSelection(float dx, float dy) { 8681 moveAndShowSelectedItems(dx, dy); 8682 // Compute again feedback 8683 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 8684 } 8685 8686 @Override 8687 public void zoom(float factor) { 8688 setScale(getScale() * factor); 8689 } 8690 } 8691 8692 /** 8693 * Default selection state. This state manages transition to other modes, 8694 * the deletion of selected items, and the move of selected items with arrow keys. 8695 */ 8696 private class SelectionState extends AbstractModeChangeState { 8697 private final SelectionListener selectionListener = new SelectionListener() { 8698 public void selectionChanged(SelectionEvent selectionEvent) { 8699 List<Selectable> selectedItems = home.getSelectedItems(); 8700 getView().setResizeIndicatorVisible(selectedItems.size() == 1 8701 && (isItemResizable(selectedItems.get(0)) 8702 || isItemMovable(selectedItems.get(0)))); 8703 } 8704 }; 8705 8706 @Override 8707 public Mode getMode() { 8708 return Mode.SELECTION; 8709 } 8710 8711 @Override 8712 public void enter() { 8713 if (getView() != null) { 8714 if (getPointerTypeLastMousePress() != View.PointerType.TOUCH) { 8715 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 8716 } 8717 home.addSelectionListener(this.selectionListener); 8718 this.selectionListener.selectionChanged(null); 8719 } 8720 } 8721 8722 @Override 8723 public void moveMouse(float x, float y) { 8724 if (getRotatedLabelAt(x, y) != null 8725 || getYawRotatedCameraAt(x, y) != null 8726 || getPitchRotatedCameraAt(x, y) != null) { 8727 getView().setCursor(PlanView.CursorType.ROTATION); 8728 } else if (getElevatedLabelAt(x, y) != null 8729 || getElevatedCameraAt(x, y) != null) { 8730 getView().setCursor(PlanView.CursorType.ELEVATION); 8731 } else if (getRoomNameAt(x, y) != null 8732 || getRoomAreaAt(x, y) != null) { 8733 getView().setCursor(PlanView.CursorType.RESIZE); 8734 } else if (getRoomRotatedNameAt(x, y) != null 8735 || getRoomRotatedAreaAt(x, y) != null) { 8736 getView().setCursor(PlanView.CursorType.ROTATION); 8737 } else if (getResizedDimensionLineStartAt(x, y) != null 8738 || getResizedDimensionLineEndAt(x, y) != null 8739 || getWidthAndDepthResizedPieceOfFurnitureAt(x, y) != null 8740 || getResizedWallStartAt(x, y) != null 8741 || getResizedWallEndAt(x, y) != null 8742 || getResizedPolylineAt(x, y) != null 8743 || getResizedRoomAt(x, y) != null) { 8744 getView().setCursor(PlanView.CursorType.RESIZE); 8745 } else if (getPitchRotatedPieceOfFurnitureAt(x, y) != null 8746 || getRollRotatedPieceOfFurnitureAt(x, y) != null) { 8747 getView().setCursor(PlanView.CursorType.ROTATION); 8748 } else if (getModifiedLightPowerAt(x, y) != null) { 8749 getView().setCursor(PlanView.CursorType.POWER); 8750 } else if (getOffsetDimensionLineAt(x, y) != null 8751 || getHeightResizedPieceOfFurnitureAt(x, y) != null 8752 || getArcExtentWallAt(x, y) != null) { 8753 getView().setCursor(PlanView.CursorType.HEIGHT); 8754 } else if (getRotatedPieceOfFurnitureAt(x, y) != null) { 8755 getView().setCursor(PlanView.CursorType.ROTATION); 8756 } else if (getElevatedPieceOfFurnitureAt(x, y) != null) { 8757 getView().setCursor(PlanView.CursorType.ELEVATION); 8758 } else if (getPieceOfFurnitureNameAt(x, y) != null) { 8759 getView().setCursor(PlanView.CursorType.RESIZE); 8760 } else if (getPieceOfFurnitureRotatedNameAt(x, y) != null) { 8761 getView().setCursor(PlanView.CursorType.ROTATION); 8762 } else if (getRotatedCompassAt(x, y) != null) { 8763 getView().setCursor(PlanView.CursorType.ROTATION); 8764 } else if (getResizedCompassAt(x, y) != null) { 8765 getView().setCursor(PlanView.CursorType.RESIZE); 8766 } else { 8767 // If a selected item is under cursor position 8768 if (isItemSelectedAt(x, y)) { 8769 getView().setCursor(PlanView.CursorType.MOVE); 8770 } else { 8771 getView().setCursor(PlanView.CursorType.SELECTION); 8772 } 8773 } 8774 } 8775 8776 @Override 8777 public void pressMouse(float x, float y, int clickCount, 8778 boolean shiftDown, boolean duplicationActivated) { 8779 if (clickCount == 1) { 8780 if (getPointerTypeLastMousePress() == View.PointerType.TOUCH) { 8781 moveMouse(x, y); // Update cursor 8782 } 8783 if (getRotatedLabelAt(x, y) != null) { 8784 setState(getLabelRotationState()); 8785 } else if (getYawRotatedCameraAt(x, y) != null) { 8786 setState(getCameraYawRotationState()); 8787 } else if (getPitchRotatedCameraAt(x, y) != null) { 8788 setState(getCameraPitchRotationState()); 8789 } else if (getElevatedLabelAt(x, y) != null) { 8790 setState(getLabelElevationState()); 8791 } else if (getElevatedCameraAt(x, y) != null) { 8792 setState(getCameraElevationState()); 8793 } else if (getRoomNameAt(x, y) != null) { 8794 setState(getRoomNameOffsetState()); 8795 } else if (getRoomRotatedNameAt(x, y) != null) { 8796 setState(getRoomNameRotationState()); 8797 } else if (getRoomAreaAt(x, y) != null) { 8798 setState(getRoomAreaOffsetState()); 8799 } else if (getRoomRotatedAreaAt(x, y) != null) { 8800 setState(getRoomAreaRotationState()); 8801 } else if (getResizedDimensionLineStartAt(x, y) != null 8802 || getResizedDimensionLineEndAt(x, y) != null) { 8803 setState(getDimensionLineResizeState()); 8804 } else if (getWidthAndDepthResizedPieceOfFurnitureAt(x, y) != null) { 8805 setState(getPieceOfFurnitureResizeState()); 8806 } else if (getResizedWallStartAt(x, y) != null 8807 || getResizedWallEndAt(x, y) != null) { 8808 setState(getWallResizeState()); 8809 } else if (getResizedRoomAt(x, y) != null) { 8810 setState(getRoomResizeState()); 8811 } else if (getOffsetDimensionLineAt(x, y) != null) { 8812 setState(getDimensionLineOffsetState()); 8813 } else if (getResizedPolylineAt(x, y) != null) { 8814 setState(getPolylineResizeState()); 8815 } else if (getPitchRotatedPieceOfFurnitureAt(x, y) != null) { 8816 setState(getPieceOfFurniturePitchRotationState()); 8817 } else if (getRollRotatedPieceOfFurnitureAt(x, y) != null) { 8818 setState(getPieceOfFurnitureRollRotationState()); 8819 } else if (getModifiedLightPowerAt(x, y) != null) { 8820 setState(getLightPowerModificationState()); 8821 } else if (getHeightResizedPieceOfFurnitureAt(x, y) != null) { 8822 setState(getPieceOfFurnitureHeightState()); 8823 } else if (getArcExtentWallAt(x, y) != null) { 8824 setState(getWallArcExtentState()); 8825 } else if (getRotatedPieceOfFurnitureAt(x, y) != null) { 8826 setState(getPieceOfFurnitureRotationState()); 8827 } else if (getElevatedPieceOfFurnitureAt(x, y) != null) { 8828 setState(getPieceOfFurnitureElevationState()); 8829 } else if (getPieceOfFurnitureNameAt(x, y) != null) { 8830 setState(getPieceOfFurnitureNameOffsetState()); 8831 } else if (getPieceOfFurnitureRotatedNameAt(x, y) != null) { 8832 setState(getPieceOfFurnitureNameRotationState()); 8833 } else if (getRotatedCompassAt(x, y) != null) { 8834 setState(getCompassRotationState()); 8835 } else if (getResizedCompassAt(x, y) != null) { 8836 setState(getCompassResizeState()); 8837 } else { 8838 // If shift isn't pressed, and an item is under cursor position 8839 if (!shiftDown 8840 && (getPointerTypeLastMousePress() == View.PointerType.TOUCH 8841 || getSelectableItemAt(x, y) != null)) { 8842 // Change state to SelectionMoveState 8843 setState(getSelectionMoveState()); 8844 } else { 8845 // Otherwise change state to RectangleSelectionState 8846 setState(getRectangleSelectionState()); 8847 } 8848 } 8849 } else if (clickCount == 2) { 8850 Selectable item = getSelectableItemAt(x, y); 8851 // If shift isn't pressed, and an item is under cursor position 8852 if (!shiftDown && item != null) { 8853 // Modify selected item on a double click 8854 if (item instanceof Wall) { 8855 modifySelectedWalls(); 8856 } else if (item instanceof HomePieceOfFurniture) { 8857 modifySelectedFurniture(); 8858 } else if (item instanceof Room) { 8859 modifySelectedRooms(); 8860 } else if (item instanceof Polyline) { 8861 modifySelectedPolylines(); 8862 } else if (item instanceof Label) { 8863 modifySelectedLabels(); 8864 } else if (item instanceof Compass) { 8865 modifyCompass(); 8866 } else if (item instanceof ObserverCamera) { 8867 modifyObserverCamera(); 8868 } 8869 } 8870 } 8871 } 8872 8873 @Override 8874 public void exit() { 8875 if (getView() != null) { 8876 home.removeSelectionListener(this.selectionListener); 8877 getView().setResizeIndicatorVisible(false); 8878 } 8879 } 8880 } 8881 8882 /** 8883 * Move selection state. This state manages the move of current selected items 8884 * with mouse and the selection of one item, if mouse isn't moved while button 8885 * is depressed. If duplication is activated during the move of the mouse, 8886 * moved items are duplicated first. 8887 */ 8888 private class SelectionMoveState extends ControllerState { 8889 private float xLastMouseMove; 8890 private float yLastMouseMove; 8891 private boolean mouseMoved; 8892 private List<Selectable> oldSelection; 8893 private boolean selectionUpdateNeeded; 8894 private List<Selectable> movedItems; 8895 private List<Selectable> duplicatedItems; 8896 private HomePieceOfFurniture movedPieceOfFurniture; 8897 private float angleMovedPieceOfFurniture; 8898 private float depthMovedPieceOfFurniture; 8899 private float elevationMovedPieceOfFurniture; 8900 private float xMovedPieceOfFurniture; 8901 private float yMovedPieceOfFurniture; 8902 private boolean movedDoorOrWindowBoundToWall; 8903 private boolean magnetismEnabled; 8904 private boolean duplicationActivated; 8905 private boolean alignmentActivated; 8906 private boolean basePlanModification; 8907 8908 @Override 8909 public Mode getMode() { 8910 return Mode.SELECTION; 8911 } 8912 8913 @Override 8914 public boolean isModificationState() { 8915 return true; 8916 } 8917 8918 @Override 8919 public boolean isBasePlanModificationState() { 8920 return this.basePlanModification; 8921 } 8922 8923 @Override 8924 public void enter() { 8925 this.xLastMouseMove = getXLastMousePress(); 8926 this.yLastMouseMove = getYLastMousePress(); 8927 this.mouseMoved = false; 8928 List<Selectable> selectableItemsUnderCursor = getSelectableItemsAt(getXLastMousePress(), getYLastMousePress()); 8929 List<Selectable> selectableItemsAndGroupsFurnitureUnderCursor = new ArrayList<Selectable>(selectableItemsUnderCursor); 8930 float selectionMargin = getSelectionMargin(); 8931 for (Selectable item : selectableItemsUnderCursor) { 8932 if (item instanceof HomeFurnitureGroup) { 8933 for (HomePieceOfFurniture piece : ((HomeFurnitureGroup)item).getAllFurniture()) { 8934 if (piece.containsPoint(getXLastMousePress(), getYLastMousePress(), selectionMargin)) { 8935 selectableItemsAndGroupsFurnitureUnderCursor.add(piece); 8936 } 8937 } 8938 } 8939 } 8940 this.oldSelection = home.getSelectedItems(); 8941 toggleMagnetism(wasMagnetismToggledLastMousePress()); 8942 this.selectionUpdateNeeded = Collections.disjoint(selectableItemsAndGroupsFurnitureUnderCursor, this.oldSelection); 8943 if (this.selectionUpdateNeeded 8944 && getPointerTypeLastMousePress() != View.PointerType.TOUCH) { 8945 // Select only the item with highest priority under cursor position 8946 selectItem(getSelectableItemAt(getXLastMousePress(), getYLastMousePress(), false)); 8947 // In touch environment, wait either for mouse move to switch to panning state 8948 // or mouse release to update the selection 8949 } 8950 List<Selectable> selectedItems = home.getSelectedItems(); 8951 this.movedItems = new ArrayList<Selectable>(selectedItems.size()); 8952 this.basePlanModification = false; 8953 for (Selectable item : selectedItems) { 8954 if (isItemMovable(item)) { 8955 this.movedItems.add(item); 8956 if (!this.basePlanModification 8957 && isItemPartOfBasePlan(item)) { 8958 this.basePlanModification = true; 8959 } 8960 } 8961 } 8962 if (this.movedItems.size() == 1 8963 && this.movedItems.get(0) instanceof HomePieceOfFurniture) { 8964 this.movedPieceOfFurniture = (HomePieceOfFurniture)this.movedItems.get(0); 8965 this.xMovedPieceOfFurniture = this.movedPieceOfFurniture.getX(); 8966 this.yMovedPieceOfFurniture = this.movedPieceOfFurniture.getY(); 8967 this.angleMovedPieceOfFurniture = this.movedPieceOfFurniture.getAngle(); 8968 this.depthMovedPieceOfFurniture = this.movedPieceOfFurniture.getDepth(); 8969 this.elevationMovedPieceOfFurniture = this.movedPieceOfFurniture.getElevation(); 8970 this.movedDoorOrWindowBoundToWall = this.movedPieceOfFurniture instanceof HomeDoorOrWindow 8971 && ((HomeDoorOrWindow)this.movedPieceOfFurniture).isBoundToWall(); 8972 } 8973 this.duplicatedItems = null; 8974 this.duplicationActivated = wasDuplicationActivatedLastMousePress() && !home.isAllLevelsSelection(); 8975 this.alignmentActivated = wasAlignmentActivatedLastMousePress(); 8976 if (!(getPointerTypeLastMousePress() == View.PointerType.TOUCH 8977 && this.selectionUpdateNeeded)) { 8978 getView().setCursor(PlanView.CursorType.MOVE); 8979 } 8980 } 8981 8982 @Override 8983 public void moveMouse(float x, float y) { 8984 if (getPointerTypeLastMousePress() == View.PointerType.TOUCH 8985 && this.selectionUpdateNeeded) { 8986 // When the user touched an unselected item 8987 // go to panning state for one shot, returning to selection state once panning is done 8988 setState(new ControllerStateDecorator(getPanningState()) { 8989 @Override 8990 public void enter() { 8991 super.enter(); 8992 pressMouse(getXLastMousePress(), getYLastMousePress(), 1, wasShiftDownLastMousePress(), wasDuplicationActivatedLastMousePress()); 8993 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 8994 } 8995 8996 @Override 8997 public void releaseMouse(float x, float y) { 8998 escape(); 8999 } 9000 9001 @Override 9002 public void escape() { 9003 super.escape(); 9004 setState(getSelectionState()); 9005 } 9006 }); 9007 } else { 9008 if (!this.mouseMoved) { 9009 toggleDuplication(this.duplicationActivated); 9010 } 9011 if (this.alignmentActivated) { 9012 PointWithAngleMagnetism alignedPoint = new PointWithAngleMagnetism(getXLastMousePress(), getYLastMousePress(), 9013 x, y, preferences.getLengthUnit(), getView().getPixelLength(), 4); 9014 x = alignedPoint.getX(); 9015 y = alignedPoint.getY(); 9016 } 9017 if (this.movedPieceOfFurniture != null) { 9018 // Reset to default piece values and adjust piece of furniture location, angle and depth 9019 this.movedPieceOfFurniture.setX(this.xMovedPieceOfFurniture); 9020 this.movedPieceOfFurniture.setY(this.yMovedPieceOfFurniture); 9021 this.movedPieceOfFurniture.setAngle(this.angleMovedPieceOfFurniture); 9022 if (this.movedPieceOfFurniture instanceof HomeDoorOrWindow 9023 && this.movedPieceOfFurniture.isResizable() 9024 && isItemResizable(this.movedPieceOfFurniture)) { 9025 this.movedPieceOfFurniture.setDepth(this.depthMovedPieceOfFurniture); 9026 } 9027 this.movedPieceOfFurniture.setElevation(this.elevationMovedPieceOfFurniture); 9028 this.movedPieceOfFurniture.move(x - getXLastMousePress(), y - getYLastMousePress()); 9029 if (this.magnetismEnabled && !this.alignmentActivated) { 9030 boolean elevationAdjusted = adjustPieceOfFurnitureElevation(this.movedPieceOfFurniture) != null; 9031 Wall magnetWall = adjustPieceOfFurnitureOnWallAt(this.movedPieceOfFurniture, x, y, false); 9032 if (!elevationAdjusted) { 9033 adjustPieceOfFurnitureSideBySideAt(this.movedPieceOfFurniture, false, magnetWall); 9034 } 9035 if (magnetWall != null) { 9036 getView().setDimensionLinesFeedback(getDimensionLinesAlongWall(this.movedPieceOfFurniture, magnetWall)); 9037 } else { 9038 getView().setDimensionLinesFeedback(null); 9039 } 9040 } 9041 } else { 9042 moveItems(this.movedItems, x - this.xLastMouseMove, y - this.yLastMouseMove); 9043 } 9044 9045 if (!this.mouseMoved) { 9046 selectItems(this.movedItems, home.isAllLevelsSelection()); 9047 } 9048 getView().makePointVisible(x, y); 9049 this.xLastMouseMove = x; 9050 this.yLastMouseMove = y; 9051 this.mouseMoved = true; 9052 } 9053 } 9054 9055 @Override 9056 public void releaseMouse(float x, float y) { 9057 if (this.mouseMoved) { 9058 // Post in undo support a move or duplicate operation if selection isn't a camera 9059 if (this.movedItems.size() > 0 9060 && !(this.movedItems.get(0) instanceof Camera)) { 9061 if (this.duplicatedItems != null) { 9062 postItemsDuplication(this.movedItems, this.duplicatedItems); 9063 } else if (this.movedPieceOfFurniture != null) { 9064 postPieceOfFurnitureMove(this.movedPieceOfFurniture, 9065 this.movedPieceOfFurniture.getX() - this.xMovedPieceOfFurniture, 9066 this.movedPieceOfFurniture.getY() - this.yMovedPieceOfFurniture, 9067 this.angleMovedPieceOfFurniture, 9068 this.depthMovedPieceOfFurniture, 9069 this.elevationMovedPieceOfFurniture, 9070 this.movedDoorOrWindowBoundToWall); 9071 } else { 9072 postItemsMove(this.movedItems, this.oldSelection, 9073 this.xLastMouseMove - getXLastMousePress(), 9074 this.yLastMouseMove - getYLastMousePress()); 9075 } 9076 } 9077 } else { 9078 // If mouse didn't move, select only the item at (x,y) 9079 if (getPointerTypeLastMousePress() == View.PointerType.TOUCH 9080 || !Collections.disjoint(home.getSelectedItems(), this.oldSelection)) { 9081 Selectable itemUnderCursor = getSelectableItemAt(x, y, false); 9082 if (itemUnderCursor != null) { 9083 // Select only the item under cursor position 9084 selectItem(itemUnderCursor); 9085 } else { 9086 // May happen only in touch environment 9087 deselectAll(); 9088 } 9089 } 9090 } 9091 // Change the state to SelectionState 9092 setState(getSelectionState()); 9093 } 9094 9095 @Override 9096 public void toggleMagnetism(boolean magnetismToggled) { 9097 // Compute active magnetism 9098 this.magnetismEnabled = preferences.isMagnetismEnabled() 9099 ^ magnetismToggled; 9100 // Compute again piece move as if mouse moved 9101 if (this.movedPieceOfFurniture != null) { 9102 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 9103 if (!this.magnetismEnabled) { 9104 getView().deleteFeedback(); 9105 } 9106 } 9107 } 9108 9109 @Override 9110 public void escape() { 9111 if (this.mouseMoved) { 9112 if (this.duplicatedItems != null) { 9113 // Delete moved items and select original items 9114 doDeleteItems(this.movedItems); 9115 selectItems(this.duplicatedItems); 9116 } else { 9117 // Put items back to their initial location 9118 if (this.movedPieceOfFurniture != null) { 9119 this.movedPieceOfFurniture.setX(this.xMovedPieceOfFurniture); 9120 this.movedPieceOfFurniture.setY(this.yMovedPieceOfFurniture); 9121 this.movedPieceOfFurniture.setAngle(this.angleMovedPieceOfFurniture); 9122 if (this.movedPieceOfFurniture instanceof HomeDoorOrWindow 9123 && this.movedPieceOfFurniture.isResizable() 9124 && isItemResizable(this.movedPieceOfFurniture)) { 9125 this.movedPieceOfFurniture.setDepth(this.depthMovedPieceOfFurniture); 9126 } 9127 this.movedPieceOfFurniture.setElevation(this.elevationMovedPieceOfFurniture); 9128 if (this.movedPieceOfFurniture instanceof HomeDoorOrWindow) { 9129 ((HomeDoorOrWindow)this.movedPieceOfFurniture).setBoundToWall( 9130 this.movedDoorOrWindowBoundToWall); 9131 } 9132 } else { 9133 moveItems(this.movedItems, 9134 getXLastMousePress() - this.xLastMouseMove, 9135 getYLastMousePress() - this.yLastMouseMove); 9136 } 9137 } 9138 } 9139 // Change the state to SelectionState 9140 setState(getSelectionState()); 9141 } 9142 9143 @Override 9144 public void setDuplicationActivated(boolean duplicationActivated) { 9145 duplicationActivated &= !home.isAllLevelsSelection(); 9146 if (this.mouseMoved) { 9147 toggleDuplication(duplicationActivated); 9148 } 9149 this.duplicationActivated = duplicationActivated; 9150 } 9151 9152 @Override 9153 public void setAlignmentActivated(boolean alignmentActivated) { 9154 if (getPointerTypeLastMousePress() == View.PointerType.TOUCH 9155 && this.selectionUpdateNeeded 9156 && !this.duplicationActivated) { 9157 // When the user touched an unselected item and simulated a shift press 9158 // go back to selection state, simulating a click with shift key pressed 9159 setState(getSelectionState()); 9160 PlanController.this.pressMouse(getXLastMousePress(), getYLastMousePress(), 1, true, false, false, false, getPointerTypeLastMousePress()); 9161 } else { 9162 this.alignmentActivated = alignmentActivated; 9163 if (this.mouseMoved) { 9164 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 9165 } 9166 if (this.alignmentActivated) { 9167 getView().deleteFeedback(); 9168 } 9169 } 9170 } 9171 9172 private void toggleDuplication(boolean duplicationActivated) { 9173 if (this.movedItems.size() > 1 9174 || (this.movedItems.size() == 1 9175 && !(this.movedItems.get(0) instanceof Camera) 9176 && !(this.movedItems.get(0) instanceof Compass))) { 9177 if (duplicationActivated 9178 && this.duplicatedItems == null) { 9179 // Duplicate original items and add them to home 9180 this.duplicatedItems = this.movedItems; 9181 this.movedItems = new ArrayList<Selectable>(); 9182 for (Selectable item : Home.duplicate(this.duplicatedItems)) { 9183 if (item instanceof Wall) { 9184 home.addWall((Wall)item); 9185 } else if (item instanceof Room) { 9186 home.addRoom((Room)item); 9187 } else if (item instanceof Polyline) { 9188 home.addPolyline((Polyline)item); 9189 } else if (item instanceof DimensionLine) { 9190 home.addDimensionLine((DimensionLine)item); 9191 } else if (item instanceof HomePieceOfFurniture) { 9192 home.addPieceOfFurniture((HomePieceOfFurniture)item); 9193 } else if (item instanceof Label) { 9194 home.addLabel((Label)item); 9195 } else { 9196 continue; 9197 } 9198 this.movedItems.add(item); 9199 } 9200 9201 // Put original items back to their initial location 9202 if (this.movedPieceOfFurniture != null) { 9203 this.movedPieceOfFurniture.setX(this.xMovedPieceOfFurniture); 9204 this.movedPieceOfFurniture.setY(this.yMovedPieceOfFurniture); 9205 this.movedPieceOfFurniture.setAngle(this.angleMovedPieceOfFurniture); 9206 if (this.movedPieceOfFurniture instanceof HomeDoorOrWindow 9207 && this.movedPieceOfFurniture.isResizable() 9208 && isItemResizable(this.movedPieceOfFurniture)) { 9209 this.movedPieceOfFurniture.setDepth(this.depthMovedPieceOfFurniture); 9210 } 9211 this.movedPieceOfFurniture.setElevation(this.elevationMovedPieceOfFurniture); 9212 this.movedPieceOfFurniture = (HomePieceOfFurniture)this.movedItems.get(0); 9213 } else { 9214 moveItems(this.duplicatedItems, 9215 getXLastMousePress() - this.xLastMouseMove, 9216 getYLastMousePress() - this.yLastMouseMove); 9217 } 9218 9219 getView().setCursor(PlanView.CursorType.DUPLICATION); 9220 } else if (!duplicationActivated 9221 && this.duplicatedItems != null) { 9222 // Delete moved items 9223 doDeleteItems(this.movedItems); 9224 9225 // Move original items to the current location 9226 moveItems(this.duplicatedItems, 9227 this.xLastMouseMove - getXLastMousePress(), 9228 this.yLastMouseMove - getYLastMousePress()); 9229 this.movedItems = this.duplicatedItems; 9230 this.duplicatedItems = null; 9231 if (this.movedPieceOfFurniture != null) { 9232 this.movedPieceOfFurniture = (HomePieceOfFurniture)this.movedItems.get(0); 9233 } 9234 getView().setCursor(PlanView.CursorType.MOVE); 9235 } 9236 9237 selectItems(this.movedItems, home.isAllLevelsSelection()); 9238 } 9239 } 9240 9241 @Override 9242 public void exit() { 9243 getView().deleteFeedback(); 9244 this.movedItems = null; 9245 this.duplicatedItems = null; 9246 this.movedPieceOfFurniture = null; 9247 } 9248 } 9249 9250 /** 9251 * Selection with rectangle state. This state manages selection when mouse 9252 * press is done outside of an item or when mouse press is done with shift key 9253 * down. 9254 */ 9255 private class RectangleSelectionState extends ControllerState { 9256 private List<Selectable> selectedItemsMousePressed; 9257 private boolean ignoreRectangleSelection; 9258 private boolean mouseMoved; 9259 9260 @Override 9261 public Mode getMode() { 9262 return Mode.SELECTION; 9263 } 9264 9265 @Override 9266 public boolean isModificationState() { 9267 return true; 9268 } 9269 9270 @Override 9271 public void enter() { 9272 Selectable itemUnderCursor = getSelectableItemAt(getXLastMousePress(), getYLastMousePress()); 9273 // If no item under cursor and shift wasn't down, deselect all 9274 if (itemUnderCursor == null && !wasShiftDownLastMousePress()) { 9275 deselectAll(); 9276 } 9277 // Store current selection 9278 this.selectedItemsMousePressed = new ArrayList<Selectable>(home.getSelectedItems()); 9279 List<HomePieceOfFurniture> furniture = home.getFurniture(); 9280 this.ignoreRectangleSelection = false; 9281 for (Selectable item : this.selectedItemsMousePressed) { 9282 if ((item instanceof HomePieceOfFurniture) 9283 && !furniture.contains(item)) { 9284 this.ignoreRectangleSelection = true; 9285 break; 9286 } 9287 } 9288 this.mouseMoved = false; 9289 } 9290 9291 @Override 9292 public void moveMouse(float x, float y) { 9293 this.mouseMoved = true; 9294 if (!this.ignoreRectangleSelection) { 9295 updateSelectedItems(getXLastMousePress(), getYLastMousePress(), 9296 x, y, this.selectedItemsMousePressed); 9297 // Update rectangle feedback 9298 PlanView planView = getView(); 9299 planView.setRectangleFeedback( 9300 getXLastMousePress(), getYLastMousePress(), x, y); 9301 planView.makePointVisible(x, y); 9302 } 9303 } 9304 9305 @Override 9306 public void releaseMouse(float x, float y) { 9307 // If cursor didn't move 9308 if (!this.mouseMoved) { 9309 Selectable itemUnderCursor = getSelectableItemAt(x, y, false); 9310 // Toggle selection of the item under cursor 9311 if (itemUnderCursor != null) { 9312 if (this.selectedItemsMousePressed.contains(itemUnderCursor)) { 9313 this.selectedItemsMousePressed.remove(itemUnderCursor); 9314 } else { 9315 for (int i = this.selectedItemsMousePressed.size() - 1; i >= 0; i--) { 9316 // Remove any camera or group of a selected piece from current selection 9317 Selectable item = this.selectedItemsMousePressed.get(i); 9318 if (item instanceof Camera 9319 || (itemUnderCursor instanceof HomePieceOfFurniture 9320 && item instanceof HomeFurnitureGroup 9321 && ((HomeFurnitureGroup)item).getAllFurniture().contains(itemUnderCursor)) 9322 || (itemUnderCursor instanceof HomeFurnitureGroup 9323 && item instanceof HomePieceOfFurniture 9324 && ((HomeFurnitureGroup)itemUnderCursor).getAllFurniture().contains(item))) { 9325 this.selectedItemsMousePressed.remove(i); 9326 } 9327 } 9328 // Let the camera belong to selection only if no item are selected 9329 if (!(itemUnderCursor instanceof Camera) 9330 || this.selectedItemsMousePressed.size() == 0) { 9331 this.selectedItemsMousePressed.add(itemUnderCursor); 9332 } 9333 } 9334 selectItems(this.selectedItemsMousePressed, 9335 home.isAllLevelsSelection() && wasShiftDownLastMousePress()); 9336 } 9337 } 9338 // Change state to SelectionState 9339 setState(getSelectionState()); 9340 } 9341 9342 @Override 9343 public void escape() { 9344 setState(getSelectionState()); 9345 } 9346 9347 @Override 9348 public void exit() { 9349 getView().deleteFeedback(); 9350 this.selectedItemsMousePressed = null; 9351 } 9352 9353 /** 9354 * Updates selection from <code>selectedItemsMousePressed</code> and the 9355 * items that intersects the rectangle at coordinates (<code>x0</code>, 9356 * <code>y0</code>) and (<code>x1</code>, <code>y1</code>). 9357 */ 9358 private void updateSelectedItems(float x0, float y0, 9359 float x1, float y1, 9360 List<Selectable> selectedItemsMousePressed) { 9361 List<Selectable> selectedItems; 9362 boolean shiftDown = wasShiftDownLastMousePress(); 9363 if (shiftDown) { 9364 selectedItems = new ArrayList<Selectable>(selectedItemsMousePressed); 9365 } else { 9366 selectedItems = new ArrayList<Selectable>(); 9367 } 9368 9369 // For all the items that intersect with rectangle 9370 for (Selectable item : getSelectableItemsIntersectingRectangle(x0, y0, x1, y1)) { 9371 // Don't let the camera be able to be selected with a rectangle 9372 if (!(item instanceof Camera)) { 9373 // If shift was down at mouse press 9374 if (shiftDown) { 9375 // Toggle selection of item 9376 if (selectedItemsMousePressed.contains(item)) { 9377 selectedItems.remove(item); 9378 } else { 9379 selectedItems.add(item); 9380 } 9381 } else if (!selectedItemsMousePressed.contains(item)) { 9382 // Else select the wall 9383 selectedItems.add(item); 9384 } 9385 } 9386 } 9387 // Update selection 9388 selectItems(selectedItems, home.isAllLevelsSelection() && shiftDown); 9389 } 9390 } 9391 9392 /** 9393 * Panning state. 9394 */ 9395 private class PanningState extends ControllerState { 9396 private Integer xLastMouseMove; 9397 private Integer yLastMouseMove; 9398 9399 @Override 9400 public Mode getMode() { 9401 return Mode.PANNING; 9402 } 9403 9404 @Override 9405 public void setMode(Mode mode) { 9406 if (mode == Mode.SELECTION) { 9407 setState(getSelectionState()); 9408 } else if (mode == Mode.WALL_CREATION) { 9409 setState(getWallCreationState()); 9410 } else if (mode == Mode.ROOM_CREATION) { 9411 setState(getRoomCreationState()); 9412 } else if (mode == Mode.POLYLINE_CREATION) { 9413 setState(getPolylineCreationState()); 9414 } else if (mode == Mode.DIMENSION_LINE_CREATION) { 9415 setState(getDimensionLineCreationState()); 9416 } else if (mode == Mode.LABEL_CREATION) { 9417 setState(getLabelCreationState()); 9418 } 9419 } 9420 9421 @Override 9422 public void enter() { 9423 getView().setCursor(PlanView.CursorType.PANNING); 9424 } 9425 9426 @Override 9427 public void moveSelection(float dx, float dy) { 9428 getView().moveView(dx * 10, dy * 10); 9429 } 9430 9431 @Override 9432 public void pressMouse(float x, float y, int clickCount, boolean shiftDown, boolean duplicationActivated) { 9433 if (clickCount == 1) { 9434 this.xLastMouseMove = getView().convertXModelToScreen(x); 9435 this.yLastMouseMove = getView().convertYModelToScreen(y); 9436 } else { 9437 this.xLastMouseMove = null; 9438 this.yLastMouseMove = null; 9439 } 9440 } 9441 9442 @Override 9443 public void moveMouse(float x, float y) { 9444 if (this.xLastMouseMove != null) { 9445 int newX = getView().convertXModelToScreen(x); 9446 int newY = getView().convertYModelToScreen(y); 9447 getView().moveView((this.xLastMouseMove - newX) / getScale(), (this.yLastMouseMove - newY) / getScale()); 9448 this.xLastMouseMove = newX; 9449 this.yLastMouseMove = newY; 9450 } 9451 } 9452 9453 @Override 9454 public void releaseMouse(float x, float y) { 9455 this.xLastMouseMove = null; 9456 } 9457 9458 @Override 9459 public void escape() { 9460 this.xLastMouseMove = null; 9461 } 9462 9463 @Override 9464 public void zoom(float factor) { 9465 setScale(getScale() * factor); 9466 } 9467 } 9468 9469 /** 9470 * Drag and drop state. This state manages the dragging of items 9471 * transfered from outside of plan view with the mouse. 9472 */ 9473 private class DragAndDropState extends ControllerState { 9474 private float xLastMouseMove; 9475 private float yLastMouseMove; 9476 private HomePieceOfFurniture draggedPieceOfFurniture; 9477 private float xDraggedPieceOfFurniture; 9478 private float yDraggedPieceOfFurniture; 9479 private float angleDraggedPieceOfFurniture; 9480 private float depthDraggedPieceOfFurniture; 9481 private float elevationDraggedPieceOfFurniture; 9482 9483 @Override 9484 public Mode getMode() { 9485 return Mode.SELECTION; 9486 } 9487 9488 @Override 9489 public boolean isModificationState() { 9490 // This state is used before a modification is performed 9491 return false; 9492 } 9493 9494 @Override 9495 public boolean isBasePlanModificationState() { 9496 return this.draggedPieceOfFurniture != null 9497 && isPieceOfFurniturePartOfBasePlan(this.draggedPieceOfFurniture); 9498 } 9499 9500 @Override 9501 public void enter() { 9502 this.xLastMouseMove = 0; 9503 this.yLastMouseMove = 0; 9504 getView().setDraggedItemsFeedback(draggedItems); 9505 if (draggedItems.size() == 1 9506 && draggedItems.get(0) instanceof HomePieceOfFurniture) { 9507 this.draggedPieceOfFurniture = (HomePieceOfFurniture)draggedItems.get(0); 9508 this.xDraggedPieceOfFurniture = this.draggedPieceOfFurniture.getX(); 9509 this.yDraggedPieceOfFurniture = this.draggedPieceOfFurniture.getY(); 9510 this.angleDraggedPieceOfFurniture = this.draggedPieceOfFurniture.getAngle(); 9511 this.depthDraggedPieceOfFurniture = this.draggedPieceOfFurniture.getDepth(); 9512 this.elevationDraggedPieceOfFurniture = this.draggedPieceOfFurniture.getElevation(); 9513 } 9514 } 9515 9516 @Override 9517 public void moveMouse(float x, float y) { 9518 List<Selectable> draggedItemsFeedback = new ArrayList<Selectable>(draggedItems); 9519 // Update in plan view the location of the feedback of the dragged items 9520 moveItems(draggedItems, x - this.xLastMouseMove, y - this.yLastMouseMove); 9521 if (this.draggedPieceOfFurniture != null 9522 && preferences.isMagnetismEnabled()) { 9523 // Reset to default piece values and adjust piece of furniture location, angle and depth 9524 this.draggedPieceOfFurniture.setX(this.xDraggedPieceOfFurniture); 9525 this.draggedPieceOfFurniture.setY(this.yDraggedPieceOfFurniture); 9526 this.draggedPieceOfFurniture.setAngle(this.angleDraggedPieceOfFurniture); 9527 if (this.draggedPieceOfFurniture.isResizable()) { 9528 // Update of depth may happen only for doors and windows which can't be rotated around horizontal axes 9529 this.draggedPieceOfFurniture.setDepth(this.depthDraggedPieceOfFurniture); 9530 this.draggedPieceOfFurniture.setDepthInPlan(this.depthDraggedPieceOfFurniture); 9531 } 9532 this.draggedPieceOfFurniture.setElevation(this.elevationDraggedPieceOfFurniture); 9533 this.draggedPieceOfFurniture.move(x, y); 9534 9535 boolean elevationAdjusted = adjustPieceOfFurnitureElevation(this.draggedPieceOfFurniture) != null; 9536 Wall magnetWall = adjustPieceOfFurnitureOnWallAt(this.draggedPieceOfFurniture, x, y, true); 9537 if (!elevationAdjusted) { 9538 adjustPieceOfFurnitureSideBySideAt(this.draggedPieceOfFurniture, magnetWall == null, magnetWall); 9539 } 9540 if (magnetWall != null) { 9541 getView().setDimensionLinesFeedback(getDimensionLinesAlongWall(this.draggedPieceOfFurniture, magnetWall)); 9542 } else { 9543 getView().setDimensionLinesFeedback(null); 9544 } 9545 } 9546 getView().setDraggedItemsFeedback(draggedItemsFeedback); 9547 this.xLastMouseMove = x; 9548 this.yLastMouseMove = y; 9549 } 9550 9551 @Override 9552 public void exit() { 9553 this.draggedPieceOfFurniture = null; 9554 getView().deleteFeedback(); 9555 } 9556 } 9557 9558 /** 9559 * Wall creation state. This state manages transition to other modes, 9560 * and initial wall creation. 9561 */ 9562 private class WallCreationState extends AbstractModeChangeState { 9563 private boolean magnetismEnabled; 9564 9565 @Override 9566 public Mode getMode() { 9567 return Mode.WALL_CREATION; 9568 } 9569 9570 @Override 9571 public void enter() { 9572 getView().setCursor(PlanView.CursorType.DRAW); 9573 toggleMagnetism(wasMagnetismToggledLastMousePress()); 9574 } 9575 9576 @Override 9577 public void moveMouse(float x, float y) { 9578 if (this.magnetismEnabled) { 9579 WallPointWithAngleMagnetism point = new WallPointWithAngleMagnetism(null, x, y, x, y); 9580 x = point.getX(); 9581 y = point.getY(); 9582 } 9583 getView().setAlignmentFeedback(Wall.class, null, x, y, false); 9584 } 9585 9586 @Override 9587 public void pressMouse(float x, float y, int clickCount, 9588 boolean shiftDown, boolean duplicationActivated) { 9589 // Change state to WallDrawingState 9590 setState(getWallDrawingState()); 9591 } 9592 9593 @Override 9594 public void setEditionActivated(boolean editionActivated) { 9595 if (editionActivated) { 9596 setState(getWallDrawingState()); 9597 PlanController.this.setEditionActivated(editionActivated); 9598 } 9599 } 9600 9601 @Override 9602 public void toggleMagnetism(boolean magnetismToggled) { 9603 // Compute active magnetism 9604 this.magnetismEnabled = preferences.isMagnetismEnabled() 9605 ^ magnetismToggled; 9606 if (getPointerTypeLastMousePress() != View.PointerType.TOUCH) { 9607 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 9608 } 9609 } 9610 9611 @Override 9612 public void exit() { 9613 getView().deleteFeedback(); 9614 } 9615 } 9616 9617 /** 9618 * Wall modification state. 9619 */ 9620 private abstract class AbstractWallState extends ControllerState { 9621 private String wallLengthToolTipFeedback; 9622 private String wallAngleToolTipFeedback; 9623 private String wallArcExtentToolTipFeedback; 9624 private String wallThicknessToolTipFeedback; 9625 9626 @Override 9627 public void enter() { 9628 this.wallLengthToolTipFeedback = preferences.getLocalizedString( 9629 PlanController.class, "wallLengthToolTipFeedback"); 9630 try { 9631 this.wallAngleToolTipFeedback = preferences.getLocalizedString( 9632 PlanController.class, "wallAngleToolTipFeedback"); 9633 } catch (IllegalArgumentException ex) { 9634 // This tool tip part is optional 9635 } 9636 this.wallArcExtentToolTipFeedback = preferences.getLocalizedString( 9637 PlanController.class, "wallArcExtentToolTipFeedback"); 9638 try { 9639 this.wallThicknessToolTipFeedback = preferences.getLocalizedString( 9640 PlanController.class, "wallThicknessToolTipFeedback"); 9641 } catch (IllegalArgumentException ex) { 9642 // This tool tip part is optional 9643 } 9644 } 9645 9646 protected String getToolTipFeedbackText(Wall wall, boolean ignoreArcExtent) { 9647 Float arcExtent = wall.getArcExtent(); 9648 if (!ignoreArcExtent && arcExtent != null) { 9649 return "<html>" + String.format(this.wallArcExtentToolTipFeedback, Math.round(Math.toDegrees(arcExtent))); 9650 } else { 9651 float startPointToEndPointDistance = wall.getStartPointToEndPointDistance(); 9652 String toolTipFeedbackText = "<html>" + String.format(this.wallLengthToolTipFeedback, preferences.getLengthUnit().getFormatWithUnit().format(startPointToEndPointDistance)); 9653 if (this.wallAngleToolTipFeedback != null 9654 && this.wallAngleToolTipFeedback.length() > 0) { 9655 toolTipFeedbackText += "<br>" + String.format(this.wallAngleToolTipFeedback, getWallAngleInDegrees(wall, startPointToEndPointDistance)); 9656 } 9657 if (this.wallThicknessToolTipFeedback != null 9658 && this.wallThicknessToolTipFeedback.length() > 0) { 9659 toolTipFeedbackText += "<br>" + String.format(this.wallThicknessToolTipFeedback, preferences.getLengthUnit().getFormatWithUnit().format(wall.getThickness())); 9660 } 9661 return toolTipFeedbackText; 9662 } 9663 } 9664 9665 /** 9666 * Returns wall angle in degrees. 9667 */ 9668 protected Integer getWallAngleInDegrees(Wall wall) { 9669 return getWallAngleInDegrees(wall, wall.getStartPointToEndPointDistance()); 9670 } 9671 9672 private Integer getWallAngleInDegrees(Wall wall, float startPointToEndPointDistance) { 9673 Wall wallAtStart = wall.getWallAtStart(); 9674 if (wallAtStart != null) { 9675 float wallAtStartSegmentDistance = wallAtStart.getStartPointToEndPointDistance(); 9676 if (startPointToEndPointDistance != 0 && wallAtStartSegmentDistance != 0) { 9677 // Compute the angle between the wall and its wall at start 9678 float xWallVector = (wall.getXEnd() - wall.getXStart()) / startPointToEndPointDistance; 9679 float yWallVector = (wall.getYEnd() - wall.getYStart()) / startPointToEndPointDistance; 9680 float xWallAtStartVector = (wallAtStart.getXEnd() - wallAtStart.getXStart()) / wallAtStartSegmentDistance; 9681 float yWallAtStartVector = (wallAtStart.getYEnd() - wallAtStart.getYStart()) / wallAtStartSegmentDistance; 9682 if (wallAtStart.getWallAtStart() == wall) { 9683 // Reverse wall at start direction 9684 xWallAtStartVector = -xWallAtStartVector; 9685 yWallAtStartVector = -yWallAtStartVector; 9686 } 9687 int wallAngle = (int)Math.round(180 - Math.toDegrees(Math.atan2( 9688 yWallVector * xWallAtStartVector - xWallVector * yWallAtStartVector, 9689 xWallVector * xWallAtStartVector + yWallVector * yWallAtStartVector))); 9690 if (wallAngle > 180) { 9691 wallAngle -= 360; 9692 } 9693 return wallAngle; 9694 } 9695 } 9696 if (startPointToEndPointDistance == 0) { 9697 return 0; 9698 } else { 9699 return (int)Math.round(Math.toDegrees(Math.atan2( 9700 wall.getYStart() - wall.getYEnd(), wall.getXEnd() - wall.getXStart()))); 9701 } 9702 } 9703 9704 /** 9705 * Returns arc extent from the circumscribed circle of the triangle 9706 * with vertices (x1, y1) (x2, y2) (x, y). 9707 */ 9708 protected float getArcExtent(float x1, float y1, float x2, float y2, float x, float y) { 9709 float [] arcCenter = getCircumscribedCircleCenter(x1, y1, x2, y2, x, y); 9710 double startPointToBissectorLine1Distance = Point2D.distance(x1, y1, x2, y2) / 2; 9711 double arcCenterToWallDistance = Float.isInfinite(arcCenter [0]) || Float.isInfinite(arcCenter [1]) 9712 ? Float.POSITIVE_INFINITY 9713 : Line2D.ptLineDist(x1, y1, x2, y2, arcCenter [0], arcCenter [1]); 9714 int mousePosition = Line2D.relativeCCW(x1, y1, x2, y2, x, y); 9715 int centerPosition = Line2D.relativeCCW(x1, y1, x2, y2, arcCenter [0], arcCenter [1]); 9716 float arcExtent; 9717 if (centerPosition == mousePosition) { 9718 arcExtent = (float)(Math.PI + 2 * Math.atan2(arcCenterToWallDistance, startPointToBissectorLine1Distance)); 9719 } else { 9720 arcExtent = (float)(2 * Math.atan2(startPointToBissectorLine1Distance, arcCenterToWallDistance)); 9721 } 9722 arcExtent = Math.min(arcExtent, 3 * (float)Math.PI / 2); 9723 arcExtent *= mousePosition; 9724 return arcExtent; 9725 } 9726 9727 /** 9728 * Returns the circumscribed circle of the triangle with vertices (x1, y1) (x2, y2) (x, y). 9729 */ 9730 private float [] getCircumscribedCircleCenter(float x1, float y1, float x2, float y2, float x, float y) { 9731 float [][] bissectorLine1 = getBissectorLine(x1, y1, x2, y2); 9732 float [][] bissectorLine2 = getBissectorLine(x1, y1, x, y); 9733 float [] arcCenter = computeIntersection(bissectorLine1 [0], bissectorLine1 [1], 9734 bissectorLine2 [0], bissectorLine2 [1]); 9735 return arcCenter; 9736 } 9737 9738 private float [][] getBissectorLine(float x1, float y1, float x2, float y2) { 9739 float xMiddlePoint = (x1 + x2) / 2; 9740 float yMiddlePoint = (y1 + y2) / 2; 9741 float bissectorLineAlpha = (x1 - x2) / (y2 - y1); 9742 if (bissectorLineAlpha > 1E10) { 9743 // Vertical line 9744 return new float [][] {{xMiddlePoint, yMiddlePoint}, {xMiddlePoint, yMiddlePoint + 1}}; 9745 } else { 9746 return new float [][] {{xMiddlePoint, yMiddlePoint}, {xMiddlePoint + 1, bissectorLineAlpha + yMiddlePoint}}; 9747 } 9748 } 9749 9750 protected void showWallAngleFeedback(Wall wall, boolean ignoreArcExtent) { 9751 Float arcExtent = wall.getArcExtent(); 9752 if (!ignoreArcExtent && arcExtent != null) { 9753 if (arcExtent < 0) { 9754 getView().setAngleFeedback(wall.getXArcCircleCenter(), wall.getYArcCircleCenter(), 9755 wall.getXStart(), wall.getYStart(), wall.getXEnd(), wall.getYEnd()); 9756 } else { 9757 getView().setAngleFeedback(wall.getXArcCircleCenter(), wall.getYArcCircleCenter(), 9758 wall.getXEnd(), wall.getYEnd(), wall.getXStart(), wall.getYStart()); 9759 } 9760 } else if (this.wallAngleToolTipFeedback != null 9761 && this.wallAngleToolTipFeedback.length() > 0) { 9762 Wall wallAtStart = wall.getWallAtStart(); 9763 if (wallAtStart != null) { 9764 if (wallAtStart.getWallAtStart() == wall) { 9765 if (getWallAngleInDegrees(wall) > 0) { 9766 getView().setAngleFeedback(wall.getXStart(), wall.getYStart(), 9767 wallAtStart.getXEnd(), wallAtStart.getYEnd(), wall.getXEnd(), wall.getYEnd()); 9768 } else { 9769 getView().setAngleFeedback(wall.getXStart(), wall.getYStart(), 9770 wall.getXEnd(), wall.getYEnd(), wallAtStart.getXEnd(), wallAtStart.getYEnd()); 9771 } 9772 } else { 9773 if (getWallAngleInDegrees(wall) > 0) { 9774 getView().setAngleFeedback(wall.getXStart(), wall.getYStart(), 9775 wallAtStart.getXStart(), wallAtStart.getYStart(), 9776 wall.getXEnd(), wall.getYEnd()); 9777 } else { 9778 getView().setAngleFeedback(wall.getXStart(), wall.getYStart(), 9779 wall.getXEnd(), wall.getYEnd(), 9780 wallAtStart.getXStart(), wallAtStart.getYStart()); 9781 } 9782 } 9783 } 9784 } 9785 } 9786 } 9787 9788 /** 9789 * Wall drawing state. This state manages wall creation at each mouse press. 9790 */ 9791 private class WallDrawingState extends AbstractWallState { 9792 private float xStart; 9793 private float yStart; 9794 private float xLastEnd; 9795 private float yLastEnd; 9796 private Wall wallStartAtStart; 9797 private Wall wallEndAtStart; 9798 private Wall newWall; 9799 private Wall wallStartAtEnd; 9800 private Wall wallEndAtEnd; 9801 private Wall lastWall; 9802 private List<Selectable> oldSelection; 9803 private boolean oldBasePlanLocked; 9804 private boolean oldAllLevelsSelection; 9805 private List<Wall> newWalls; 9806 private boolean magnetismEnabled; 9807 private boolean alignmentActivated; 9808 private boolean roundWall; 9809 private long lastWallCreationTime; 9810 private Float wallArcExtent; 9811 9812 @Override 9813 public Mode getMode() { 9814 return Mode.WALL_CREATION; 9815 } 9816 9817 @Override 9818 public boolean isModificationState() { 9819 return true; 9820 } 9821 9822 @Override 9823 public boolean isBasePlanModificationState() { 9824 return true; 9825 } 9826 9827 @Override 9828 public void setMode(Mode mode) { 9829 // Escape current creation and change state to matching mode 9830 escape(); 9831 if (mode == Mode.SELECTION) { 9832 setState(getSelectionState()); 9833 } else if (mode == Mode.PANNING) { 9834 setState(getPanningState()); 9835 } else if (mode == Mode.ROOM_CREATION) { 9836 setState(getRoomCreationState()); 9837 } else if (mode == Mode.POLYLINE_CREATION) { 9838 setState(getPolylineCreationState()); 9839 } else if (mode == Mode.DIMENSION_LINE_CREATION) { 9840 setState(getDimensionLineCreationState()); 9841 } else if (mode == Mode.LABEL_CREATION) { 9842 setState(getLabelCreationState()); 9843 } 9844 } 9845 9846 @Override 9847 public void enter() { 9848 super.enter(); 9849 this.oldSelection = home.getSelectedItems(); 9850 this.oldBasePlanLocked = home.isBasePlanLocked(); 9851 this.oldAllLevelsSelection = home.isAllLevelsSelection(); 9852 this.alignmentActivated = wasAlignmentActivatedLastMousePress(); 9853 toggleMagnetism(wasMagnetismToggledLastMousePress()); 9854 this.xStart = getXLastMouseMove(); 9855 this.yStart = getYLastMouseMove(); 9856 // If the start or end line of a wall close to (xStart, yStart) is 9857 // free, it will the wall at start of the new wall 9858 this.wallEndAtStart = getWallEndAt(this.xStart, this.yStart, null); 9859 if (this.wallEndAtStart != null) { 9860 this.wallStartAtStart = null; 9861 this.xStart = this.wallEndAtStart.getXEnd(); 9862 this.yStart = this.wallEndAtStart.getYEnd(); 9863 } else { 9864 this.wallStartAtStart = getWallStartAt( 9865 this.xStart, this.yStart, null); 9866 if (this.wallStartAtStart != null) { 9867 this.xStart = this.wallStartAtStart.getXStart(); 9868 this.yStart = this.wallStartAtStart.getYStart(); 9869 } else if (this.magnetismEnabled) { 9870 WallPointWithAngleMagnetism point = new WallPointWithAngleMagnetism(null, this.xStart, this.yStart, this.xStart, this.yStart); 9871 this.xStart = point.getX(); 9872 this.yStart = point.getY(); 9873 } 9874 } 9875 this.newWall = null; 9876 this.wallStartAtEnd = null; 9877 this.wallEndAtEnd = null; 9878 this.lastWall = null; 9879 this.newWalls = new ArrayList<Wall>(); 9880 this.lastWallCreationTime = -1; 9881 deselectAll(); 9882 setDuplicationActivated(wasDuplicationActivatedLastMousePress()); 9883 PlanView planView = getView(); 9884 planView.setAlignmentFeedback(Wall.class, null, this.xStart, this.yStart, false); 9885 } 9886 9887 @Override 9888 public void moveMouse(float x, float y) { 9889 PlanView planView = getView(); 9890 // Compute the coordinates where wall end point should be moved 9891 float xEnd; 9892 float yEnd; 9893 if (this.alignmentActivated) { 9894 PointWithAngleMagnetism point = new PointWithAngleMagnetism(this.xStart, this.yStart, x, y, 9895 preferences.getLengthUnit(), planView.getPixelLength()); 9896 xEnd = point.getX(); 9897 yEnd = point.getY(); 9898 } else if (this.magnetismEnabled) { 9899 WallPointWithAngleMagnetism point = new WallPointWithAngleMagnetism(this.newWall, this.xStart, this.yStart, x, y); 9900 xEnd = point.getX(); 9901 yEnd = point.getY(); 9902 } else { 9903 xEnd = x; 9904 yEnd = y; 9905 } 9906 9907 // If current wall doesn't exist 9908 if (this.newWall == null) { 9909 // Create a new one 9910 this.newWall = createWall(this.xStart, this.yStart, 9911 xEnd, yEnd, this.wallStartAtStart, this.wallEndAtStart); 9912 this.newWalls.add(this.newWall); 9913 } else if (this.wallArcExtent != null) { 9914 // Compute current wall arc extent from the circumscribed circle of the triangle 9915 // with vertices (xStart, yStart) (xEnd, yEnd) (x, y) 9916 this.wallArcExtent = getArcExtent(this.newWall.getXStart(), this.newWall.getXEnd(), 9917 this.newWall.getYStart(), this.newWall.getYEnd(), x, y); 9918 if (this.alignmentActivated 9919 || this.magnetismEnabled) { 9920 this.wallArcExtent = (float)Math.toRadians(Math.round(Math.toDegrees(this.wallArcExtent))); 9921 } 9922 this.newWall.setArcExtent(this.wallArcExtent); 9923 } else { 9924 // Otherwise update its end point 9925 this.newWall.setXEnd(xEnd); 9926 this.newWall.setYEnd(yEnd); 9927 } 9928 planView.setToolTipFeedback(getToolTipFeedbackText(this.newWall, false), x, y); 9929 planView.setAlignmentFeedback(Wall.class, this.newWall, xEnd, yEnd, false); 9930 showWallAngleFeedback(this.newWall, false); 9931 9932 // If the start or end line of a wall close to (xEnd, yEnd) is 9933 // free, it will the wall at end of the new wall. 9934 this.wallStartAtEnd = getWallStartAt(xEnd, yEnd, this.newWall); 9935 if (this.wallStartAtEnd != null) { 9936 this.wallEndAtEnd = null; 9937 // Select the wall with a free start to display a feedback to user 9938 selectItem(this.wallStartAtEnd); 9939 } else { 9940 this.wallEndAtEnd = getWallEndAt(xEnd, yEnd, this.newWall); 9941 if (this.wallEndAtEnd != null) { 9942 // Select the wall with a free end to display a feedback to user 9943 selectItem(this.wallEndAtEnd); 9944 } else { 9945 deselectAll(); 9946 } 9947 } 9948 9949 // Ensure point at (x,y) is visible 9950 planView.makePointVisible(x, y); 9951 // Update move coordinates 9952 this.xLastEnd = xEnd; 9953 this.yLastEnd = yEnd; 9954 } 9955 9956 @Override 9957 public void pressMouse(float x, float y, int clickCount, 9958 boolean shiftDown, boolean duplicationActivated) { 9959 if (clickCount == 2) { 9960 Selectable selectableItem = getSelectableItemAt(x, y); 9961 if (this.newWalls.size() == 0 9962 && selectableItem instanceof Room) { 9963 createWallsAroundRoom((Room)selectableItem); 9964 } else { 9965 if (this.roundWall && this.newWall != null) { 9966 // Let's end wall creation of round walls after a double click 9967 endWallCreation(); 9968 } 9969 if (this.lastWall != null) { 9970 // Join last wall to the selected wall at its end 9971 joinNewWallEndToWall(this.lastWall, 9972 this.wallStartAtEnd, this.wallEndAtEnd); 9973 } 9974 } 9975 validateDrawnWalls(); 9976 } else { 9977 // Create a new wall only when it will have a distance between start and end points > 0 9978 if (this.newWall != null 9979 && this.newWall.getStartPointToEndPointDistance() > 0) { 9980 if (this.roundWall && this.wallArcExtent == null) { 9981 this.wallArcExtent = (float)Math.PI; 9982 this.newWall.setArcExtent(this.wallArcExtent); 9983 getView().setToolTipFeedback(getToolTipFeedbackText(this.newWall, false), x, y); 9984 } else { 9985 getView().deleteToolTipFeedback(); 9986 selectItem(this.newWall); 9987 endWallCreation(); 9988 } 9989 } 9990 } 9991 } 9992 9993 /** 9994 * Creates walls around the given <code>room</code>. 9995 */ 9996 private void createWallsAroundRoom(Room room) { 9997 if (room.isSingular()) { 9998 float [][] roomPoints = room.getPoints(); 9999 List<float []> pointsList = new ArrayList<float[]>(Arrays.asList(roomPoints)); 10000 // It points are not clockwise reverse their order 10001 if (!room.isClockwise()) { 10002 Collections.reverse(pointsList); 10003 } 10004 // Remove equal points 10005 for (int i = 0; i < pointsList.size(); ) { 10006 float [] point = pointsList.get(i); 10007 float [] nextPoint = pointsList.get((i + 1) % pointsList.size()); 10008 if (point [0] == nextPoint [0] 10009 && point [1] == nextPoint [1]) { 10010 pointsList.remove(i); 10011 } else { 10012 i++; 10013 } 10014 } 10015 roomPoints = pointsList.toArray(new float [pointsList.size()][]); 10016 10017 float halfWallThickness = preferences.getNewWallThickness() / 2; 10018 float [][] largerRoomPoints = new float [roomPoints.length][]; 10019 for (int i = 0; i < roomPoints.length; i++) { 10020 float [] point = roomPoints [i]; 10021 float [] previousPoint = roomPoints [(i + roomPoints.length - 1) % roomPoints.length]; 10022 float [] nextPoint = roomPoints [(i + 1) % roomPoints.length]; 10023 10024 // Compute the angle of the line with a direction orthogonal to line (previousPoint, point) 10025 double previousAngle = Math.atan2(point [0] - previousPoint [0], previousPoint [1] - point [1]); 10026 // Compute the points of the line joining previous and current point 10027 // at a distance equal to the half wall thickness 10028 float deltaX = (float)(Math.cos(previousAngle) * halfWallThickness); 10029 float deltaY = (float)(Math.sin(previousAngle) * halfWallThickness); 10030 float [] point1 = {previousPoint [0] - deltaX, previousPoint [1] - deltaY}; 10031 float [] point2 = {point [0] - deltaX, point [1] - deltaY}; 10032 10033 // Compute the angle of the line with a direction orthogonal to line (point, nextPoint) 10034 double nextAngle = Math.atan2(nextPoint [0] - point [0], point [1] - nextPoint [1]); 10035 // Compute the points of the line joining current and next point 10036 // at a distance equal to the half wall thickness 10037 deltaX = (float)(Math.cos(nextAngle) * halfWallThickness); 10038 deltaY = (float)(Math.sin(nextAngle) * halfWallThickness); 10039 float [] point3 = {point [0] - deltaX, point [1] - deltaY}; 10040 float [] point4 = {nextPoint [0] - deltaX, nextPoint [1] - deltaY}; 10041 10042 largerRoomPoints [i] = computeIntersection(point1, point2, point3, point4); 10043 } 10044 10045 // Create walls joining points of largerRoomPoints 10046 Wall lastWall = null; 10047 Area wallsArea = getWallsArea(false); 10048 float thinThickness = 0.05f; 10049 for (int i = 0; i < largerRoomPoints.length; i++) { 10050 final float [] sidePoint = largerRoomPoints [i]; 10051 float [] nextSidePoint = largerRoomPoints [(i + 1) % roomPoints.length]; 10052 // Remove from wall line the intersection with existing walls 10053 Area lineArea = new Area(getPath(new Wall( 10054 sidePoint [0], sidePoint [1], nextSidePoint [0], nextSidePoint [1], thinThickness, 0).getPoints())); 10055 lineArea.subtract(wallsArea); 10056 List<GeneralPath> newWallPaths = getAreaPaths(lineArea); 10057 List<Wall> roomSideWalls = new ArrayList<Wall>(); 10058 int ignoredWall = 0; 10059 for (int j = 0; j < newWallPaths.size(); j++) { 10060 float [][] newWallPoints = getPathPoints(newWallPaths.get(j), false); 10061 if (newWallPoints.length > 4) { 10062 // Try to remove aligned points 10063 newWallPoints = getPathPoints(newWallPaths.get(j), true); 10064 } 10065 if (newWallPoints.length == 4) { 10066 // Search the two ends of the line 10067 float [] point1; 10068 float [] point2; 10069 if (Point2D.distanceSq(newWallPoints [0][0], newWallPoints [0][1], newWallPoints [1][0], newWallPoints [1][1]) 10070 < Point2D.distanceSq(newWallPoints [0][0], newWallPoints [0][1], newWallPoints [3][0], newWallPoints [3][1])) { 10071 point1 = new float [] {(newWallPoints [0][0] + newWallPoints [1][0]) / 2, (newWallPoints [0][1] + newWallPoints [1][1]) / 2}; 10072 point2 = new float [] {(newWallPoints [2][0] + newWallPoints [3][0]) / 2, (newWallPoints [2][1] + newWallPoints [3][1]) / 2}; 10073 } else { 10074 point1 = new float [] {(newWallPoints [0][0] + newWallPoints [3][0]) / 2, (newWallPoints [0][1] + newWallPoints [3][1]) / 2}; 10075 point2 = new float [] {(newWallPoints [1][0] + newWallPoints [2][0]) / 2, (newWallPoints [1][1] + newWallPoints [2][1]) / 2}; 10076 } 10077 float [] startPoint; 10078 float [] endPoint; 10079 if (Point2D.distanceSq(point1 [0], point1 [1], sidePoint [0], sidePoint [1]) 10080 < Point2D.distanceSq(point2 [0], point2 [1], sidePoint [0], sidePoint [1])) { 10081 startPoint = point1; 10082 endPoint = point2; 10083 } else { 10084 startPoint = point2; 10085 endPoint = point1; 10086 } 10087 if (Point2D.distanceSq(startPoint [0], startPoint [1], endPoint [0], endPoint [1]) > 0.01) { 10088 roomSideWalls.add(createWall(startPoint [0], startPoint [1], endPoint [0], endPoint [1], null, 10089 lastWall != null && Point2D.distanceSq(lastWall.getXEnd(), lastWall.getYEnd(), startPoint [0], startPoint [1]) < 1E-2 ? lastWall : null)); 10090 } else { 10091 ignoredWall++; 10092 } 10093 } 10094 } 10095 if (newWallPaths.size() > ignoredWall && roomSideWalls.isEmpty()) { 10096 Wall existingWall = null; 10097 for (Wall wall : home.getWalls()) { 10098 if (wall.isAtLevel(home.getSelectedLevel()) 10099 && Math.abs(wall.getXStart() - sidePoint [0]) < 0.05 10100 && Math.abs(wall.getYStart() - sidePoint [1]) < 0.05 10101 && Math.abs(wall.getXEnd() - nextSidePoint [0]) < 0.05 10102 && Math.abs(wall.getYEnd() - nextSidePoint [1]) < 0.05 10103 && (wall.getArcExtent() == null 10104 || wall.getArcExtent() == 0)) { 10105 existingWall = wall; 10106 break; 10107 } 10108 } 10109 if (existingWall == null) { 10110 // Ensure at least a wall is created for each room side that doesn't exist 10111 roomSideWalls.add(createWall(sidePoint [0], sidePoint [1], nextSidePoint [0], nextSidePoint [1], null, lastWall)); 10112 } 10113 } 10114 if (roomSideWalls.size() > 0) { 10115 // Order walls from the closest to first point to the farthest one 10116 Collections.sort(roomSideWalls, new Comparator<Wall>() { 10117 public int compare(Wall wall1, Wall wall2) { 10118 return Double.compare(Point2D.distanceSq(wall1.getXStart(), wall1.getYStart(), sidePoint [0], sidePoint [1]), 10119 Point2D.distanceSq(wall2.getXStart(), wall2.getYStart(), sidePoint [0], sidePoint [1])); 10120 } 10121 }); 10122 this.newWalls.addAll(roomSideWalls); 10123 lastWall = roomSideWalls.get(roomSideWalls.size() - 1); 10124 } else { 10125 lastWall = null; 10126 } 10127 } 10128 if (lastWall != null && Point2D.distanceSq(lastWall.getXEnd(), lastWall.getYEnd(), this.newWalls.get(0).getXStart(), this.newWalls.get(0).getYStart()) < 1E-2) { 10129 joinNewWallEndToWall(lastWall, this.newWalls.get(0), null); 10130 } 10131 } 10132 } 10133 10134 private void validateDrawnWalls() { 10135 if (this.newWalls.size() > 0) { 10136 // Post walls creation to undo support 10137 postCreateWalls(this.newWalls, this.oldSelection, this.oldBasePlanLocked, this.oldAllLevelsSelection); 10138 selectItems(this.newWalls); 10139 } 10140 if (getPointerTypeLastMousePress() == View.PointerType.TOUCH) { 10141 setState(getSelectionState()); 10142 } else { 10143 // Change state to WallCreationState 10144 setState(getWallCreationState()); 10145 } 10146 } 10147 10148 private void endWallCreation() { 10149 this.lastWall = 10150 this.wallEndAtStart = this.newWall; 10151 this.wallStartAtStart = null; 10152 this.xStart = this.newWall.getXEnd(); 10153 this.yStart = this.newWall.getYEnd(); 10154 this.newWall = null; 10155 this.wallArcExtent = null; 10156 } 10157 10158 @Override 10159 public void setEditionActivated(boolean editionActivated) { 10160 PlanView planView = getView(); 10161 if (editionActivated) { 10162 planView.deleteFeedback(); 10163 if (this.newWalls.size() == 0 10164 && this.wallEndAtStart == null 10165 && this.wallStartAtStart == null) { 10166 // Edit xStart and yStart 10167 planView.setToolTipEditedProperties(new EditableProperty [] {EditableProperty.X, 10168 EditableProperty.Y}, 10169 new Object [] {this.xStart, this.yStart}, 10170 this.xStart, this.yStart); 10171 } else { 10172 if (this.newWall == null) { 10173 // May happen if edition is activated after the user clicked to finish one wall 10174 createNextWall(); 10175 } 10176 if (this.wallArcExtent == null) { 10177 // Edit length, angle and thickness 10178 planView.setToolTipEditedProperties(new EditableProperty [] {EditableProperty.LENGTH, 10179 EditableProperty.ANGLE, 10180 EditableProperty.THICKNESS}, 10181 new Object [] {this.newWall.getLength(), 10182 getWallAngleInDegrees(this.newWall), 10183 this.newWall.getThickness()}, 10184 this.newWall.getXEnd(), this.newWall.getYEnd()); 10185 } else { 10186 // Edit arc extent 10187 planView.setToolTipEditedProperties(new EditableProperty [] {EditableProperty.ARC_EXTENT}, 10188 new Object [] {new Integer((int)Math.round(Math.toDegrees(this.wallArcExtent)))}, 10189 this.newWall.getXEnd(), this.newWall.getYEnd()); 10190 } 10191 } 10192 } else { 10193 if (this.newWall == null) { 10194 // Create a new wall once user entered the start point of the first wall 10195 LengthUnit lengthUnit = preferences.getLengthUnit(); 10196 float defaultLength = lengthUnit == LengthUnit.INCH || lengthUnit == LengthUnit.INCH_DECIMALS 10197 ? LengthUnit.footToCentimeter(10) : 300; 10198 this.xLastEnd = this.xStart + defaultLength; 10199 this.yLastEnd = this.yStart; 10200 this.newWall = createWall(this.xStart, this.yStart, 10201 this.xLastEnd, this.yLastEnd, this.wallStartAtStart, this.wallEndAtStart); 10202 this.newWalls.add(this.newWall); 10203 // Activate automatically second step to let user enter the 10204 // length, angle and thickness of the new wall 10205 planView.deleteFeedback(); 10206 setEditionActivated(true); 10207 } else if (this.roundWall && this.wallArcExtent == null) { 10208 this.wallArcExtent = (float)Math.PI; 10209 this.newWall.setArcExtent(this.wallArcExtent); 10210 setEditionActivated(true); 10211 } else if (System.currentTimeMillis() - this.lastWallCreationTime < 300) { 10212 // If the user deactivated edition less than 300 ms after activation, 10213 // validate drawn walls after removing the last added wall 10214 if (this.newWalls.size() > 1) { 10215 this.newWalls.remove(this.newWall); 10216 home.deleteWall(this.newWall); 10217 } 10218 validateDrawnWalls(); 10219 } else { 10220 endWallCreation(); 10221 if (this.newWalls.size() > 2 && this.wallStartAtEnd != null) { 10222 // Join last wall to the first wall at its end and validate drawn walls 10223 joinNewWallEndToWall(this.lastWall, this.wallStartAtEnd, null); 10224 validateDrawnWalls(); 10225 return; 10226 } 10227 createNextWall(); 10228 // Reactivate automatically second step 10229 planView.deleteToolTipFeedback(); 10230 setEditionActivated(true); 10231 } 10232 } 10233 } 10234 10235 private void createNextWall() { 10236 Wall previousWall = this.wallEndAtStart != null 10237 ? this.wallEndAtStart 10238 : this.wallStartAtStart; 10239 // Create a new wall with an angle equal to previous wall angle - 90� 10240 double previousWallAngle = Math.PI - Math.atan2(previousWall.getYStart() - previousWall.getYEnd(), 10241 previousWall.getXStart() - previousWall.getXEnd()); 10242 previousWallAngle -= Math.PI / 2; 10243 float previousWallSegmentDistance = previousWall.getStartPointToEndPointDistance(); 10244 this.xLastEnd = (float)(this.xStart + previousWallSegmentDistance * Math.cos(previousWallAngle)); 10245 this.yLastEnd = (float)(this.yStart - previousWallSegmentDistance * Math.sin(previousWallAngle)); 10246 this.newWall = createWall(this.xStart, this.yStart, 10247 this.xLastEnd, this.yLastEnd, this.wallStartAtStart, previousWall); 10248 this.newWall.setThickness(previousWall.getThickness()); 10249 this.newWalls.add(this.newWall); 10250 this.lastWallCreationTime = System.currentTimeMillis(); 10251 deselectAll(); 10252 } 10253 10254 @Override 10255 public void updateEditableProperty(EditableProperty editableProperty, Object value) { 10256 PlanView planView = getView(); 10257 if (this.newWall == null) { 10258 float maximumLength = preferences.getLengthUnit().getMaximumLength(); 10259 // Update start point of the first wall 10260 switch (editableProperty) { 10261 case X : 10262 this.xStart = value != null ? ((Number)value).floatValue() : 0; 10263 this.xStart = Math.max(-maximumLength, Math.min(this.xStart, maximumLength)); 10264 break; 10265 case Y : 10266 this.yStart = value != null ? ((Number)value).floatValue() : 0; 10267 this.yStart = Math.max(-maximumLength, Math.min(this.yStart, maximumLength)); 10268 break; 10269 } 10270 planView.setAlignmentFeedback(Wall.class, null, this.xStart, this.yStart, true); 10271 planView.makePointVisible(this.xStart, this.yStart); 10272 } else { 10273 if (editableProperty == EditableProperty.THICKNESS) { 10274 float thickness = value != null ? Math.abs(((Number)value).floatValue()) : 0; 10275 thickness = Math.max(0.01f, Math.min(thickness, 1000)); 10276 this.newWall.setThickness(thickness); 10277 } else if (editableProperty == EditableProperty.ARC_EXTENT) { 10278 double arcExtent = Math.toRadians(value != null ? ((Number)value).doubleValue() : 0); 10279 this.wallArcExtent = (float)(Math.signum(arcExtent) * Math.min(Math.abs(arcExtent), 3 * Math.PI / 2)); 10280 this.newWall.setArcExtent(this.wallArcExtent); 10281 showWallAngleFeedback(this.newWall, false); 10282 } else { 10283 // Update end point of the current wall 10284 switch (editableProperty) { 10285 case LENGTH : 10286 float length = value != null ? ((Number)value).floatValue() : 0; 10287 length = Math.max(0.001f, Math.min(length, preferences.getLengthUnit().getMaximumLength())); 10288 double wallAngle = Math.PI - Math.atan2(this.yStart - this.yLastEnd, this.xStart - this.xLastEnd); 10289 this.xLastEnd = (float)(this.xStart + length * Math.cos(wallAngle)); 10290 this.yLastEnd = (float)(this.yStart - length * Math.sin(wallAngle)); 10291 break; 10292 case ANGLE : 10293 wallAngle = Math.toRadians(value != null ? ((Number)value).doubleValue() : 0); 10294 Wall previousWall = this.newWall.getWallAtStart(); 10295 if (previousWall != null 10296 && previousWall.getStartPointToEndPointDistance() > 0) { 10297 wallAngle -= Math.atan2(previousWall.getYStart() - previousWall.getYEnd(), 10298 previousWall.getXStart() - previousWall.getXEnd()); 10299 } 10300 float startPointToEndPointDistance = this.newWall.getStartPointToEndPointDistance(); 10301 this.xLastEnd = (float)(this.xStart + startPointToEndPointDistance * Math.cos(wallAngle)); 10302 this.yLastEnd = (float)(this.yStart - startPointToEndPointDistance * Math.sin(wallAngle)); 10303 break; 10304 default : 10305 return; 10306 } 10307 10308 // Update new wall 10309 this.newWall.setXEnd(this.xLastEnd); 10310 this.newWall.setYEnd(this.yLastEnd); 10311 planView.setAlignmentFeedback(Wall.class, this.newWall, this.xLastEnd, this.yLastEnd, false); 10312 showWallAngleFeedback(this.newWall, false); 10313 // Ensure wall points are visible 10314 planView.makePointVisible(this.xStart, this.yStart); 10315 planView.makePointVisible(this.xLastEnd, this.yLastEnd); 10316 // Search if the free start point of the first wall matches the end point of the current wall 10317 if (this.newWalls.size() > 2 10318 && this.newWalls.get(0).getWallAtStart() == null 10319 && this.newWalls.get(0).containsWallStartAt(this.xLastEnd, this.yLastEnd, 1E-3f)) { 10320 this.wallStartAtEnd = this.newWalls.get(0); 10321 selectItem(this.wallStartAtEnd); 10322 } else { 10323 this.wallStartAtEnd = null; 10324 deselectAll(); 10325 } 10326 } 10327 } 10328 } 10329 10330 @Override 10331 public void toggleMagnetism(boolean magnetismToggled) { 10332 // Compute active magnetism 10333 this.magnetismEnabled = preferences.isMagnetismEnabled() 10334 ^ magnetismToggled; 10335 // If the new wall already exists, 10336 // compute again its end as if mouse moved 10337 if (this.newWall != null) { 10338 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 10339 } 10340 } 10341 10342 @Override 10343 public void setAlignmentActivated(boolean alignmentActivated) { 10344 this.alignmentActivated = alignmentActivated; 10345 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 10346 } 10347 10348 @Override 10349 public void setDuplicationActivated(boolean duplicationActivated) { 10350 // Reuse duplication activation for round circle creation 10351 this.roundWall = duplicationActivated; 10352 } 10353 10354 @Override 10355 public void escape() { 10356 if (this.newWall != null) { 10357 // Delete current created wall 10358 home.deleteWall(this.newWall); 10359 this.newWalls.remove(this.newWall); 10360 } 10361 validateDrawnWalls(); 10362 } 10363 10364 @Override 10365 public void exit() { 10366 PlanView planView = getView(); 10367 planView.deleteFeedback(); 10368 this.wallStartAtStart = null; 10369 this.wallEndAtStart = null; 10370 this.newWall = null; 10371 this.wallArcExtent = null; 10372 this.wallStartAtEnd = null; 10373 this.wallEndAtEnd = null; 10374 this.lastWall = null; 10375 this.oldSelection = null; 10376 this.newWalls = null; 10377 } 10378 } 10379 10380 /** 10381 * Wall resize state. This state manages wall resizing. 10382 */ 10383 private class WallResizeState extends AbstractWallState { 10384 private Wall selectedWall; 10385 private boolean startPoint; 10386 private float oldX; 10387 private float oldY; 10388 private float deltaXToResizePoint; 10389 private float deltaYToResizePoint; 10390 private boolean magnetismEnabled; 10391 private boolean alignmentActivated; 10392 10393 @Override 10394 public Mode getMode() { 10395 return Mode.SELECTION; 10396 } 10397 10398 @Override 10399 public boolean isModificationState() { 10400 return true; 10401 } 10402 10403 @Override 10404 public boolean isBasePlanModificationState() { 10405 return true; 10406 } 10407 10408 @Override 10409 public void enter() { 10410 super.enter(); 10411 this.selectedWall = (Wall)home.getSelectedItems().get(0); 10412 this.startPoint = this.selectedWall 10413 == getResizedWallStartAt(getXLastMousePress(), getYLastMousePress()); 10414 if (this.startPoint) { 10415 this.oldX = this.selectedWall.getXStart(); 10416 this.oldY = this.selectedWall.getYStart(); 10417 } else { 10418 this.oldX = this.selectedWall.getXEnd(); 10419 this.oldY = this.selectedWall.getYEnd(); 10420 } 10421 this.deltaXToResizePoint = getXLastMousePress() - this.oldX; 10422 this.deltaYToResizePoint = getYLastMousePress() - this.oldY; 10423 this.alignmentActivated = wasAlignmentActivatedLastMousePress(); 10424 toggleMagnetism(wasMagnetismToggledLastMousePress()); 10425 PlanView planView = getView(); 10426 planView.setResizeIndicatorVisible(true); 10427 planView.setToolTipFeedback(getToolTipFeedbackText(this.selectedWall, true), 10428 getXLastMousePress(), getYLastMousePress()); 10429 planView.setAlignmentFeedback(Wall.class, this.selectedWall, this.oldX, this.oldY, false); 10430 showWallAngleFeedback(this.selectedWall, true); 10431 planView.setDimensionLinesFeedback(getDimensionLinesAlongWall(this.selectedWall)); 10432 } 10433 10434 @Override 10435 public void moveMouse(float x, float y) { 10436 PlanView planView = getView(); 10437 float newX = x - this.deltaXToResizePoint; 10438 float newY = y - this.deltaYToResizePoint; 10439 float opositeEndX = this.startPoint 10440 ? this.selectedWall.getXEnd() 10441 : this.selectedWall.getXStart(); 10442 float opositeEndY = this.startPoint 10443 ? this.selectedWall.getYEnd() 10444 : this.selectedWall.getYStart(); 10445 if (this.alignmentActivated) { 10446 PointWithAngleMagnetism point = new PointWithAngleMagnetism(opositeEndX, opositeEndY, newX, newY, 10447 preferences.getLengthUnit(), planView.getPixelLength()); 10448 newX = point.getX(); 10449 newY = point.getY(); 10450 } else if (this.magnetismEnabled) { 10451 WallPointWithAngleMagnetism point = new WallPointWithAngleMagnetism(this.selectedWall, 10452 opositeEndX, opositeEndY, newX, newY); 10453 newX = point.getX(); 10454 newY = point.getY(); 10455 } 10456 moveWallPoint(this.selectedWall, newX, newY, this.startPoint); 10457 10458 planView.setToolTipFeedback(getToolTipFeedbackText(this.selectedWall, true), x, y); 10459 planView.setAlignmentFeedback(Wall.class, this.selectedWall, newX, newY, false); 10460 showWallAngleFeedback(this.selectedWall, true); 10461 planView.setDimensionLinesFeedback(getDimensionLinesAlongWall(this.selectedWall)); 10462 // Ensure point at (x,y) is visible 10463 planView.makePointVisible(x, y); 10464 } 10465 10466 private List<DimensionLine> getDimensionLinesAlongWall(Wall wall) { 10467 List<DimensionLine> dimensionLines = new ArrayList<DimensionLine>(); 10468 if (wall.getArcExtent() == null || wall.getArcExtent() == 0) { 10469 float offset = 20 / getView().getScale(); 10470 float [][] wallPoints = wall.getPoints(); 10471 // Search among room paths which segment are included in wall sides 10472 List<GeneralPath> roomPaths = getRoomPathsFromWalls(); 10473 for (int i = 0; i < roomPaths.size(); i++) { 10474 float [][] roomPoints = getPathPoints(roomPaths.get(i), true); 10475 for (int j = 0; j < roomPoints.length; j++) { 10476 float [] startPoint = roomPoints [j]; 10477 float [] endPoint = roomPoints [(j + 1) % roomPoints.length]; 10478 boolean segmentPartOfLeftSide = Line2D.ptLineDistSq(wallPoints [0][0], wallPoints [0][1], wallPoints [1][0], wallPoints [1][1], startPoint [0], startPoint [1]) < 0.0001 10479 && Line2D.ptLineDistSq(wallPoints [0][0], wallPoints [0][1], wallPoints [1][0], wallPoints [1][1], endPoint [0], endPoint [1]) < 0.0001; 10480 boolean segmentAccepted; 10481 if (segmentPartOfLeftSide) { 10482 // Check that either end of [startPoint, endPoint] or the wall side belongs to one of the segment 10483 segmentAccepted = Line2D.ptSegDistSq(wallPoints [0][0], wallPoints [0][1], wallPoints [1][0], wallPoints [1][1], startPoint [0], startPoint [1]) < 0.0001 10484 || Line2D.ptSegDistSq(wallPoints [0][0], wallPoints [0][1], wallPoints [1][0], wallPoints [1][1], endPoint [0], endPoint [1]) < 0.0001 10485 || Line2D.ptSegDistSq(startPoint [0], startPoint [1], endPoint [0], endPoint [1], wallPoints [0][0], wallPoints [0][1]) < 0.0001 10486 || Line2D.ptSegDistSq(startPoint [0], startPoint [1], endPoint [0], endPoint [1], wallPoints [1][0], wallPoints [1][1]) < 0.0001; 10487 } else { 10488 segmentAccepted = Line2D.ptLineDistSq(wallPoints [2][0], wallPoints [2][1], wallPoints [3][0], wallPoints [3][1], startPoint [0], startPoint [1]) < 0.0001 10489 && Line2D.ptLineDistSq(wallPoints [2][0], wallPoints [2][1], wallPoints [3][0], wallPoints [3][1], endPoint [0], endPoint [1]) < 0.0001 10490 && (Line2D.ptSegDistSq(wallPoints [2][0], wallPoints [2][1], wallPoints [3][0], wallPoints [3][1], startPoint [0], startPoint [1]) < 0.0001 10491 || Line2D.ptSegDistSq(wallPoints [2][0], wallPoints [2][1], wallPoints [3][0], wallPoints [3][1], endPoint [0], endPoint [1]) < 0.0001 10492 || Line2D.ptSegDistSq(startPoint [0], startPoint [1], endPoint [0], endPoint [1], wallPoints [2][0], wallPoints [2][1]) < 0.0001 10493 || Line2D.ptSegDistSq(startPoint [0], startPoint [1], endPoint [0], endPoint [1], wallPoints [3][0], wallPoints [3][1]) < 0.0001); 10494 } 10495 if (segmentAccepted) { 10496 dimensionLines.add(getDimensionLineBetweenPoints(startPoint, endPoint, 10497 isDimensionInsideWall(wall, startPoint, endPoint) ? -offset : offset, false)); 10498 } 10499 } 10500 } 10501 for (int i = dimensionLines.size() - 1; i >= 0; i--) { 10502 if (dimensionLines.get(i).getLength() < 0.01f) { 10503 dimensionLines.remove(i); 10504 } 10505 } 10506 if (dimensionLines.size() == 2 10507 && Math.abs(dimensionLines.get(0).getLength() - dimensionLines.get(1).getLength()) < 0.01f) { 10508 dimensionLines.remove(1); 10509 } 10510 } 10511 return dimensionLines; 10512 } 10513 10514 private boolean isDimensionInsideWall(Wall wall, float [] point1, float [] point2) { 10515 // Search the coordinates of the point where the dimension will be displayed 10516 AffineTransform rotation = AffineTransform.getRotateInstance( 10517 Math.atan2(point2 [1] - point1 [1], point2 [0] - point1 [0]), point1 [0], point1 [1]); 10518 float [] dimensionPoint = {(float)(point1 [0] + Point2D.distance(point1 [0], point1 [1], point2 [0], point2 [1]) / 2), point1 [1] + 0.01f}; 10519 rotation.transform(dimensionPoint, 0, dimensionPoint, 0, 1); 10520 return wall.containsPoint(dimensionPoint [0], dimensionPoint [1], 0); 10521 } 10522 10523 @Override 10524 public void releaseMouse(float x, float y) { 10525 postWallResize(this.selectedWall, this.oldX, this.oldY, this.startPoint); 10526 setState(getSelectionState()); 10527 } 10528 10529 @Override 10530 public void toggleMagnetism(boolean magnetismToggled) { 10531 // Compute active magnetism 10532 this.magnetismEnabled = preferences.isMagnetismEnabled() 10533 ^ magnetismToggled; 10534 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 10535 } 10536 10537 @Override 10538 public void setAlignmentActivated(boolean alignmentActivated) { 10539 this.alignmentActivated = alignmentActivated; 10540 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 10541 } 10542 10543 @Override 10544 public void escape() { 10545 moveWallPoint(this.selectedWall, this.oldX, this.oldY, this.startPoint); 10546 setState(getSelectionState()); 10547 } 10548 10549 @Override 10550 public void exit() { 10551 PlanView planView = getView(); 10552 planView.setResizeIndicatorVisible(false); 10553 planView.deleteFeedback(); 10554 this.selectedWall = null; 10555 } 10556 } 10557 10558 /** 10559 * Wall arc extent state. This state manages wall arc extent change. 10560 */ 10561 private class WallArcExtentState extends AbstractWallState { 10562 private Wall selectedWall; 10563 private Float oldArcExtent; 10564 private float deltaXToMiddlePoint; 10565 private float deltaYToMiddlePoint; 10566 private boolean magnetismEnabled; 10567 private boolean alignmentActivated; 10568 10569 @Override 10570 public Mode getMode() { 10571 return Mode.SELECTION; 10572 } 10573 10574 @Override 10575 public boolean isModificationState() { 10576 return true; 10577 } 10578 10579 @Override 10580 public boolean isBasePlanModificationState() { 10581 return true; 10582 } 10583 10584 @Override 10585 public void enter() { 10586 super.enter(); 10587 this.selectedWall = (Wall)home.getSelectedItems().get(0); 10588 this.oldArcExtent = this.selectedWall.getArcExtent(); 10589 float [][] wallPoints = this.selectedWall.getPoints(); 10590 int leftSideMiddlePointIndex = wallPoints.length / 4; 10591 int rightSideMiddlePointIndex = wallPoints.length - 1 - leftSideMiddlePointIndex; 10592 if (wallPoints.length % 4 == 0) { 10593 leftSideMiddlePointIndex--; 10594 } 10595 float middleX = (wallPoints [leftSideMiddlePointIndex][0] + wallPoints [rightSideMiddlePointIndex][0]) / 2; 10596 float middleY = (wallPoints [leftSideMiddlePointIndex][1] + wallPoints [rightSideMiddlePointIndex][1]) / 2; 10597 this.deltaXToMiddlePoint = getXLastMousePress() - middleX; 10598 this.deltaYToMiddlePoint = getYLastMousePress() - middleY; 10599 this.alignmentActivated = wasAlignmentActivatedLastMousePress(); 10600 this.magnetismEnabled = preferences.isMagnetismEnabled() 10601 ^ wasMagnetismToggledLastMousePress(); 10602 PlanView planView = getView(); 10603 planView.setResizeIndicatorVisible(true); 10604 planView.setToolTipFeedback(getToolTipFeedbackText(this.selectedWall, false), 10605 getXLastMousePress(), getYLastMousePress()); 10606 showWallAngleFeedback(this.selectedWall, false); 10607 } 10608 10609 @Override 10610 public void moveMouse(float x, float y) { 10611 PlanView planView = getView(); 10612 float newX = x - this.deltaXToMiddlePoint; 10613 float newY = y - this.deltaYToMiddlePoint; 10614 float arcExtent = getArcExtent(this.selectedWall.getXStart(), this.selectedWall.getYStart(), 10615 this.selectedWall.getXEnd(), this.selectedWall.getYEnd(), newX, newY); 10616 if (this.alignmentActivated 10617 || this.magnetismEnabled) { 10618 arcExtent = (float)Math.toRadians(Math.round(Math.toDegrees(arcExtent))); 10619 } 10620 this.selectedWall.setArcExtent(arcExtent); 10621 10622 planView.setToolTipFeedback(getToolTipFeedbackText(this.selectedWall, false), x, y); 10623 showWallAngleFeedback(this.selectedWall, false); 10624 // Ensure point at (x,y) is visible 10625 planView.makePointVisible(x, y); 10626 } 10627 10628 @Override 10629 public void releaseMouse(float x, float y) { 10630 postWallArcExtent(this.selectedWall, this.oldArcExtent); 10631 setState(getSelectionState()); 10632 } 10633 10634 @Override 10635 public void toggleMagnetism(boolean magnetismToggled) { 10636 this.magnetismEnabled = preferences.isMagnetismEnabled() 10637 ^ magnetismToggled; 10638 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 10639 } 10640 10641 @Override 10642 public void setAlignmentActivated(boolean alignmentActivated) { 10643 this.alignmentActivated = alignmentActivated; 10644 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 10645 } 10646 10647 @Override 10648 public void escape() { 10649 this.selectedWall.setArcExtent(this.oldArcExtent); 10650 setState(getSelectionState()); 10651 } 10652 10653 @Override 10654 public void exit() { 10655 PlanView planView = getView(); 10656 planView.setResizeIndicatorVisible(false); 10657 planView.deleteFeedback(); 10658 this.selectedWall = null; 10659 } 10660 } 10661 10662 /** 10663 * Furniture rotation state. This states manages the rotation of a piece of furniture around the vertical axis. 10664 */ 10665 private class PieceOfFurnitureRotationState extends ControllerState { 10666 private static final int STEP_COUNT = 24; 10667 private boolean magnetismEnabled; 10668 private boolean alignmentActivated; 10669 private HomePieceOfFurniture selectedPiece; 10670 private float angleMousePress; 10671 private float oldAngle; 10672 private boolean doorOrWindowBoundToWall; 10673 private String rotationToolTipFeedback; 10674 10675 @Override 10676 public Mode getMode() { 10677 return Mode.SELECTION; 10678 } 10679 10680 @Override 10681 public boolean isModificationState() { 10682 return true; 10683 } 10684 10685 @Override 10686 public boolean isBasePlanModificationState() { 10687 return this.selectedPiece != null 10688 && isPieceOfFurniturePartOfBasePlan(this.selectedPiece); 10689 } 10690 10691 @Override 10692 public void enter() { 10693 this.rotationToolTipFeedback = preferences.getLocalizedString( 10694 PlanController.class, "rotationToolTipFeedback"); 10695 this.selectedPiece = (HomePieceOfFurniture)home.getSelectedItems().get(0); 10696 this.angleMousePress = (float)Math.atan2(this.selectedPiece.getY() - getYLastMousePress(), 10697 getXLastMousePress() - this.selectedPiece.getX()); 10698 this.oldAngle = this.selectedPiece.getAngle(); 10699 this.doorOrWindowBoundToWall = this.selectedPiece instanceof HomeDoorOrWindow 10700 && ((HomeDoorOrWindow)this.selectedPiece).isBoundToWall(); 10701 this.alignmentActivated = wasAlignmentActivatedLastMousePress(); 10702 this.magnetismEnabled = preferences.isMagnetismEnabled() 10703 ^ wasMagnetismToggledLastMousePress(); 10704 PlanView planView = getView(); 10705 planView.setResizeIndicatorVisible(true); 10706 planView.setToolTipFeedback(getToolTipFeedbackText(this.oldAngle), 10707 getXLastMousePress(), getYLastMousePress()); 10708 } 10709 10710 @Override 10711 public void moveMouse(float x, float y) { 10712 if (x != this.selectedPiece.getX() || y != this.selectedPiece.getY()) { 10713 // Compute the new angle of the piece 10714 float angleMouseMove = (float)Math.atan2(this.selectedPiece.getY() - y, 10715 x - this.selectedPiece.getX()); 10716 float newAngle = this.oldAngle - angleMouseMove + this.angleMousePress; 10717 10718 if (this.alignmentActivated 10719 || this.magnetismEnabled) { 10720 float angleStep = 2 * (float)Math.PI / STEP_COUNT; 10721 // Compute angles closest to a step angle (multiple of angleStep) 10722 newAngle = Math.round(newAngle / angleStep) * angleStep; 10723 } 10724 10725 // Update piece new angle 10726 this.selectedPiece.setAngle(newAngle); 10727 10728 // Ensure point at (x,y) is visible 10729 PlanView planView = getView(); 10730 planView.makePointVisible(x, y); 10731 planView.setToolTipFeedback(getToolTipFeedbackText(newAngle), x, y); 10732 } 10733 } 10734 10735 @Override 10736 public void releaseMouse(float x, float y) { 10737 postPieceOfFurnitureRotation(this.selectedPiece, this.oldAngle, this.doorOrWindowBoundToWall); 10738 setState(getSelectionState()); 10739 } 10740 10741 @Override 10742 public void toggleMagnetism(boolean magnetismToggled) { 10743 // Compute active magnetism 10744 this.magnetismEnabled = preferences.isMagnetismEnabled() 10745 ^ magnetismToggled; 10746 // Compute again angle as if mouse moved 10747 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 10748 } 10749 10750 @Override 10751 public void setAlignmentActivated(boolean alignmentActivated) { 10752 this.alignmentActivated = alignmentActivated; 10753 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 10754 } 10755 10756 @Override 10757 public void escape() { 10758 this.selectedPiece.setAngle(this.oldAngle); 10759 if (this.selectedPiece instanceof HomeDoorOrWindow) { 10760 ((HomeDoorOrWindow)this.selectedPiece).setBoundToWall(this.doorOrWindowBoundToWall); 10761 } 10762 setState(getSelectionState()); 10763 } 10764 10765 @Override 10766 public void exit() { 10767 PlanView planView = getView(); 10768 planView.setResizeIndicatorVisible(false); 10769 planView.deleteFeedback(); 10770 this.selectedPiece = null; 10771 } 10772 10773 private String getToolTipFeedbackText(float angle) { 10774 return String.format(this.rotationToolTipFeedback, (Math.round(Math.toDegrees(angle)) + 360) % 360); 10775 } 10776 } 10777 10778 /** 10779 * Furniture pitch rotation state. This states manages the rotation of a piece of furniture 10780 * around the horizontal pitch (transversal) axis. 10781 */ 10782 private class PieceOfFurniturePitchRotationState extends ControllerState { 10783 private HomePieceOfFurniture selectedPiece; 10784 private float oldPitch; 10785 private float oldWidthInPlan; 10786 private float oldDepthInPlan; 10787 private float oldHeightInPlan; 10788 private String pitchRotationToolTipFeedback; 10789 10790 @Override 10791 public Mode getMode() { 10792 return Mode.SELECTION; 10793 } 10794 10795 @Override 10796 public boolean isModificationState() { 10797 return true; 10798 } 10799 10800 @Override 10801 public boolean isBasePlanModificationState() { 10802 return this.selectedPiece != null 10803 && isPieceOfFurniturePartOfBasePlan(this.selectedPiece); 10804 } 10805 10806 @Override 10807 public void enter() { 10808 this.pitchRotationToolTipFeedback = preferences.getLocalizedString( 10809 PlanController.class, "pitchRotationToolTipFeedback"); 10810 this.selectedPiece = (HomePieceOfFurniture)home.getSelectedItems().get(0); 10811 this.oldPitch = this.selectedPiece.getPitch(); 10812 this.oldWidthInPlan = this.selectedPiece.getWidthInPlan(); 10813 this.oldDepthInPlan = this.selectedPiece.getDepthInPlan(); 10814 this.oldHeightInPlan = this.selectedPiece.getHeightInPlan(); 10815 PlanView planView = getView(); 10816 planView.setResizeIndicatorVisible(true); 10817 planView.setToolTipFeedback(getToolTipFeedbackText(this.oldPitch), 10818 getXLastMousePress(), getYLastMousePress()); 10819 } 10820 10821 @Override 10822 public void moveMouse(float x, float y) { 10823 // Compute the new pitch angle of the piece 10824 float newPitch = (float)(this.oldPitch 10825 - (y - getYLastMousePress()) * Math.cos(this.selectedPiece.getAngle()) * Math.PI / 360 10826 + (x - getXLastMousePress()) * Math.sin(this.selectedPiece.getAngle()) * Math.PI / 360); 10827 if (Math.abs(newPitch) < 1E-8) { 10828 newPitch = 0; 10829 } 10830 // Update pitch angle 10831 this.selectedPiece.setPitch(newPitch); 10832 getView().setToolTipFeedback(getToolTipFeedbackText(newPitch), x, y); 10833 } 10834 10835 @Override 10836 public void releaseMouse(float x, float y) { 10837 postPieceOfFurniturePitchRotation(this.selectedPiece, this.oldPitch, this.oldWidthInPlan, this.oldDepthInPlan, this.oldHeightInPlan); 10838 setState(getSelectionState()); 10839 } 10840 10841 @Override 10842 public void escape() { 10843 this.selectedPiece.setPitch(this.oldPitch); 10844 setState(getSelectionState()); 10845 } 10846 10847 @Override 10848 public void exit() { 10849 PlanView planView = getView(); 10850 planView.setResizeIndicatorVisible(false); 10851 planView.deleteFeedback(); 10852 this.selectedPiece = null; 10853 } 10854 10855 private String getToolTipFeedbackText(float pitch) { 10856 return String.format(this.pitchRotationToolTipFeedback, (Math.round(Math.toDegrees(pitch)) + 360) % 360); 10857 } 10858 } 10859 10860 /** 10861 * Furniture roll rotation state. This states manages the rotation of a piece of furniture 10862 * around the horizontal roll axis. 10863 */ 10864 private class PieceOfFurnitureRollRotationState extends ControllerState { 10865 private HomePieceOfFurniture selectedPiece; 10866 private float oldRoll; 10867 private float oldWidthInPlan; 10868 private float oldDepthInPlan; 10869 private float oldHeightInPlan; 10870 private String rollRotationToolTipFeedback; 10871 10872 @Override 10873 public Mode getMode() { 10874 return Mode.SELECTION; 10875 } 10876 10877 @Override 10878 public boolean isModificationState() { 10879 return true; 10880 } 10881 10882 @Override 10883 public boolean isBasePlanModificationState() { 10884 return this.selectedPiece != null 10885 && isPieceOfFurniturePartOfBasePlan(this.selectedPiece); 10886 } 10887 10888 @Override 10889 public void enter() { 10890 this.rollRotationToolTipFeedback = preferences.getLocalizedString( 10891 PlanController.class, "rollRotationToolTipFeedback"); 10892 this.selectedPiece = (HomePieceOfFurniture)home.getSelectedItems().get(0); 10893 this.oldRoll = this.selectedPiece.getRoll(); 10894 this.oldWidthInPlan = this.selectedPiece.getWidthInPlan(); 10895 this.oldDepthInPlan = this.selectedPiece.getDepthInPlan(); 10896 this.oldHeightInPlan = this.selectedPiece.getHeightInPlan(); 10897 PlanView planView = getView(); 10898 planView.setResizeIndicatorVisible(true); 10899 planView.setToolTipFeedback(getToolTipFeedbackText(this.oldRoll), 10900 getXLastMousePress(), getYLastMousePress()); 10901 } 10902 10903 @Override 10904 public void moveMouse(float x, float y) { 10905 // Compute the new roll angle of the piece 10906 float newRoll = (float)(this.oldRoll 10907 + (y - getYLastMousePress()) * Math.sin(this.selectedPiece.getAngle()) * Math.PI / 360 10908 + (x - getXLastMousePress()) * Math.cos(this.selectedPiece.getAngle()) * Math.PI / 360); 10909 if (Math.abs(newRoll) < 1E-8) { 10910 newRoll = 0; 10911 } 10912 // Update roll angle 10913 this.selectedPiece.setRoll(newRoll); 10914 getView().setToolTipFeedback(getToolTipFeedbackText(newRoll), x, y); 10915 } 10916 10917 @Override 10918 public void releaseMouse(float x, float y) { 10919 postPieceOfFurnitureRollRotation(this.selectedPiece, this.oldRoll, this.oldWidthInPlan, this.oldDepthInPlan, this.oldHeightInPlan); 10920 setState(getSelectionState()); 10921 } 10922 10923 @Override 10924 public void escape() { 10925 this.selectedPiece.setRoll(this.oldRoll); 10926 setState(getSelectionState()); 10927 } 10928 10929 @Override 10930 public void exit() { 10931 PlanView planView = getView(); 10932 planView.setResizeIndicatorVisible(false); 10933 planView.deleteFeedback(); 10934 this.selectedPiece = null; 10935 } 10936 10937 private String getToolTipFeedbackText(float roll) { 10938 return String.format(this.rollRotationToolTipFeedback, (Math.round(Math.toDegrees(roll)) + 360) % 360); 10939 } 10940 } 10941 10942 /** 10943 * Furniture elevation state. This states manages the elevation change of a piece of furniture. 10944 */ 10945 private class PieceOfFurnitureElevationState extends ControllerState { 10946 private boolean magnetismEnabled; 10947 private float deltaYToElevationPoint; 10948 private HomePieceOfFurniture selectedPiece; 10949 private float oldElevation; 10950 private String elevationToolTipFeedback; 10951 10952 @Override 10953 public Mode getMode() { 10954 return Mode.SELECTION; 10955 } 10956 10957 @Override 10958 public boolean isModificationState() { 10959 return true; 10960 } 10961 10962 @Override 10963 public boolean isBasePlanModificationState() { 10964 return this.selectedPiece != null 10965 && isPieceOfFurniturePartOfBasePlan(this.selectedPiece); 10966 } 10967 10968 @Override 10969 public void enter() { 10970 this.elevationToolTipFeedback = preferences.getLocalizedString( 10971 PlanController.class, "elevationToolTipFeedback"); 10972 this.selectedPiece = (HomePieceOfFurniture)home.getSelectedItems().get(0); 10973 float [] elevationPoint = this.selectedPiece.getPoints() [1]; 10974 this.deltaYToElevationPoint = getYLastMousePress() - elevationPoint [1]; 10975 this.oldElevation = this.selectedPiece.getElevation(); 10976 this.magnetismEnabled = preferences.isMagnetismEnabled() 10977 ^ wasMagnetismToggledLastMousePress(); 10978 PlanView planView = getView(); 10979 planView.setResizeIndicatorVisible(true); 10980 planView.setToolTipFeedback(getToolTipFeedbackText(this.oldElevation), 10981 getXLastMousePress(), getYLastMousePress()); 10982 } 10983 10984 @Override 10985 public void moveMouse(float x, float y) { 10986 // Compute the new elevation of the piece 10987 PlanView planView = getView(); 10988 float [] topRightPoint = this.selectedPiece.getPoints() [1]; 10989 float deltaY = y - this.deltaYToElevationPoint - topRightPoint[1]; 10990 float newElevation = this.oldElevation - deltaY; 10991 newElevation = Math.min(Math.max(newElevation, 0f), preferences.getLengthUnit().getMaximumElevation()); 10992 if (this.magnetismEnabled) { 10993 newElevation = preferences.getLengthUnit().getMagnetizedLength(newElevation, planView.getPixelLength()); 10994 } 10995 10996 // Update piece new dimension 10997 this.selectedPiece.setElevation(newElevation); 10998 10999 // Ensure point at (x,y) is visible 11000 planView.makePointVisible(x, y); 11001 planView.setToolTipFeedback(getToolTipFeedbackText(newElevation), x, y); 11002 } 11003 11004 @Override 11005 public void releaseMouse(float x, float y) { 11006 postPieceOfFurnitureElevation(this.selectedPiece, this.oldElevation); 11007 setState(getSelectionState()); 11008 } 11009 11010 @Override 11011 public void toggleMagnetism(boolean magnetismToggled) { 11012 // Compute active magnetism 11013 this.magnetismEnabled = preferences.isMagnetismEnabled() 11014 ^ magnetismToggled; 11015 // Compute again angle as if mouse moved 11016 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 11017 } 11018 11019 @Override 11020 public void escape() { 11021 this.selectedPiece.setElevation(this.oldElevation); 11022 setState(getSelectionState()); 11023 } 11024 11025 @Override 11026 public void exit() { 11027 PlanView planView = getView(); 11028 planView.setResizeIndicatorVisible(false); 11029 planView.deleteFeedback(); 11030 this.selectedPiece = null; 11031 } 11032 11033 private String getToolTipFeedbackText(float height) { 11034 return String.format(this.elevationToolTipFeedback, 11035 preferences.getLengthUnit().getFormatWithUnit().format(height)); 11036 } 11037 } 11038 11039 /** 11040 * Furniture height state. This states manages the height resizing of a piece of furniture. 11041 * Caution: Do not use for furniture with a roll or pitch angle different from 0 11042 */ 11043 private class PieceOfFurnitureHeightState extends ControllerState { 11044 private boolean magnetismEnabled; 11045 private float deltaYToResizePoint; 11046 private ResizedPieceOfFurniture resizedPiece; 11047 private float [] topLeftPoint; 11048 private float [] resizePoint; 11049 private String resizeToolTipFeedback; 11050 11051 @Override 11052 public Mode getMode() { 11053 return Mode.SELECTION; 11054 } 11055 11056 @Override 11057 public boolean isModificationState() { 11058 return true; 11059 } 11060 11061 @Override 11062 public boolean isBasePlanModificationState() { 11063 return this.resizedPiece != null 11064 && isPieceOfFurniturePartOfBasePlan(this.resizedPiece.getPieceOfFurniture()); 11065 } 11066 11067 @Override 11068 public void enter() { 11069 this.resizeToolTipFeedback = preferences.getLocalizedString( 11070 PlanController.class, "heightResizeToolTipFeedback"); 11071 HomePieceOfFurniture selectedPiece = (HomePieceOfFurniture)home.getSelectedItems().get(0); 11072 this.resizedPiece = new ResizedPieceOfFurniture(selectedPiece); 11073 float [][] resizedPiecePoints = selectedPiece.getPoints(); 11074 this.resizePoint = resizedPiecePoints [3]; 11075 this.deltaYToResizePoint = getYLastMousePress() - this.resizePoint [1]; 11076 this.topLeftPoint = resizedPiecePoints [0]; 11077 this.magnetismEnabled = preferences.isMagnetismEnabled() 11078 ^ wasMagnetismToggledLastMousePress(); 11079 PlanView planView = getView(); 11080 planView.setResizeIndicatorVisible(true); 11081 planView.setToolTipFeedback(getToolTipFeedbackText(selectedPiece.getHeight()), 11082 getXLastMousePress(), getYLastMousePress()); 11083 } 11084 11085 @Override 11086 public void moveMouse(float x, float y) { 11087 // Compute the new height of the piece 11088 PlanView planView = getView(); 11089 HomePieceOfFurniture selectedPiece = this.resizedPiece.getPieceOfFurniture(); 11090 float deltaY = y - this.deltaYToResizePoint - this.resizePoint [1]; 11091 float newHeight = this.resizedPiece.getHeight() - deltaY; 11092 newHeight = Math.max(newHeight, 0f); 11093 if (this.magnetismEnabled) { 11094 newHeight = preferences.getLengthUnit().getMagnetizedLength(newHeight, planView.getPixelLength()); 11095 } 11096 newHeight = Math.min(Math.max(newHeight, preferences.getLengthUnit().getMinimumLength()), 11097 preferences.getLengthUnit().getMaximumLength()); 11098 11099 if (selectedPiece.isDeformable() 11100 && !selectedPiece.isHorizontallyRotated() 11101 && selectedPiece.getModelTransformations() == null) { 11102 // Update piece new dimension 11103 setPieceOfFurnitureSize(this.resizedPiece, 11104 this.resizedPiece.getWidth(), this.resizedPiece.getDepth(), newHeight); 11105 } else { 11106 // Manage proportional constraint 11107 float scale = newHeight / this.resizedPiece.getHeight(); 11108 float newWidth = this.resizedPiece.getWidth() * scale; 11109 float newDepth = this.resizedPiece.getDepth() * scale; 11110 // Update piece new location 11111 float angle = selectedPiece.getAngle(); 11112 double cos = Math.cos(angle); 11113 double sin = Math.sin(angle); 11114 float newX = (float)(this.topLeftPoint [0] + (newWidth * cos - newDepth * sin) / 2f); 11115 float newY = (float)(this.topLeftPoint [1] + (newWidth * sin + newDepth * cos) / 2f); 11116 selectedPiece.setX(newX); 11117 selectedPiece.setY(newY); 11118 setPieceOfFurnitureSize(this.resizedPiece, newWidth, newDepth, newHeight); 11119 } 11120 11121 // Ensure point at (x,y) is visible 11122 planView.makePointVisible(x, y); 11123 planView.setToolTipFeedback(getToolTipFeedbackText(newHeight), x, y); 11124 } 11125 11126 @Override 11127 public void releaseMouse(float x, float y) { 11128 postPieceOfFurnitureHeightResize(this.resizedPiece); 11129 setState(getSelectionState()); 11130 } 11131 11132 @Override 11133 public void toggleMagnetism(boolean magnetismToggled) { 11134 // Compute active magnetism 11135 this.magnetismEnabled = preferences.isMagnetismEnabled() 11136 ^ magnetismToggled; 11137 // Compute again angle as if mouse moved 11138 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 11139 } 11140 11141 @Override 11142 public void escape() { 11143 resetPieceOfFurnitureSize(this.resizedPiece); 11144 setState(getSelectionState()); 11145 } 11146 11147 @Override 11148 public void exit() { 11149 PlanView planView = getView(); 11150 planView.setResizeIndicatorVisible(false); 11151 planView.deleteFeedback(); 11152 this.resizedPiece = null; 11153 } 11154 11155 private String getToolTipFeedbackText(float height) { 11156 return String.format(this.resizeToolTipFeedback, 11157 preferences.getLengthUnit().getFormatWithUnit().format(height)); 11158 } 11159 } 11160 11161 /** 11162 * Furniture resize state. This states manages the resizing of a piece of furniture. 11163 */ 11164 private class PieceOfFurnitureResizeState extends ControllerState { 11165 private boolean magnetismEnabled; 11166 private boolean alignmentActivated; 11167 private boolean widthOrDepthResizingActivated; 11168 private float deltaXToResizePoint; 11169 private float deltaYToResizePoint; 11170 private ResizedPieceOfFurniture resizedPiece; 11171 private float resizedPieceWidthInPlan; 11172 private float [] resizePoint; 11173 private float [] topLeftPoint; 11174 private String widthResizeToolTipFeedback; 11175 private String depthResizeToolTipFeedback; 11176 private String heightResizeToolTipFeedback; 11177 11178 @Override 11179 public Mode getMode() { 11180 return Mode.SELECTION; 11181 } 11182 11183 @Override 11184 public boolean isModificationState() { 11185 return true; 11186 } 11187 11188 @Override 11189 public boolean isBasePlanModificationState() { 11190 return this.resizedPiece != null 11191 && isPieceOfFurniturePartOfBasePlan(this.resizedPiece.getPieceOfFurniture()); 11192 } 11193 11194 @Override 11195 public void enter() { 11196 this.widthResizeToolTipFeedback = preferences.getLocalizedString( 11197 PlanController.class, "widthResizeToolTipFeedback"); 11198 this.depthResizeToolTipFeedback = preferences.getLocalizedString( 11199 PlanController.class, "depthResizeToolTipFeedback"); 11200 this.heightResizeToolTipFeedback = preferences.getLocalizedString( 11201 PlanController.class, "heightResizeToolTipFeedback"); 11202 HomePieceOfFurniture selectedPiece = (HomePieceOfFurniture)home.getSelectedItems().get(0); 11203 this.resizedPiece = new ResizedPieceOfFurniture(selectedPiece); 11204 this.resizedPieceWidthInPlan = selectedPiece.getWidthInPlan(); 11205 float [][] resizedPiecePoints = selectedPiece.getPoints(); 11206 this.resizePoint = resizedPiecePoints [2]; 11207 this.deltaXToResizePoint = getXLastMousePress() - this.resizePoint [0]; 11208 this.deltaYToResizePoint = getYLastMousePress() - this.resizePoint [1]; 11209 this.topLeftPoint = resizedPiecePoints [0]; 11210 this.magnetismEnabled = preferences.isMagnetismEnabled() 11211 ^ wasMagnetismToggledLastMousePress(); 11212 this.alignmentActivated = wasAlignmentActivatedLastMousePress(); 11213 this.widthOrDepthResizingActivated = wasDuplicationActivatedLastMousePress(); 11214 PlanView planView = getView(); 11215 planView.setResizeIndicatorVisible(true); 11216 planView.setToolTipFeedback(getToolTipFeedbackText(selectedPiece.getWidth(), selectedPiece.getDepth(), selectedPiece.getHeight()), 11217 getXLastMousePress(), getYLastMousePress()); 11218 } 11219 11220 @Override 11221 public void moveMouse(float x, float y) { 11222 // Compute the new location and dimension of the piece to let 11223 // its bottom right point be at mouse location 11224 PlanView planView = getView(); 11225 HomePieceOfFurniture selectedPiece = this.resizedPiece.getPieceOfFurniture(); 11226 float angle = selectedPiece.getAngle(); 11227 double cos = Math.cos(angle); 11228 double sin = Math.sin(angle); 11229 float deltaX = x - this.deltaXToResizePoint - this.topLeftPoint [0]; 11230 float deltaY = y - this.deltaYToResizePoint - this.topLeftPoint [1]; 11231 float newWidth = (float)(deltaY * sin + deltaX * cos); 11232 if (this.magnetismEnabled) { 11233 newWidth = preferences.getLengthUnit().getMagnetizedLength(newWidth, planView.getPixelLength()); 11234 } 11235 newWidth = Math.min(Math.max(newWidth, preferences.getLengthUnit().getMinimumLength()), 11236 preferences.getLengthUnit().getMaximumLength()); 11237 11238 float newDepth = this.resizedPiece.getDepth(); 11239 float newHeight = this.resizedPiece.getHeight(); 11240 boolean doorOrWindowBoundToWall = this.resizedPiece.isDoorOrWindowBoundToWall(); 11241 // Manage constraints 11242 if (isProprortionallyResized(selectedPiece)) { 11243 // Use resizing scale based on width in plan 11244 float scale = newWidth / this.resizedPieceWidthInPlan; 11245 newWidth = this.resizedPiece.getWidth() * scale; 11246 newDepth = this.resizedPiece.getDepth() * scale; 11247 newHeight = this.resizedPiece.getHeight() * scale; 11248 doorOrWindowBoundToWall = newDepth == this.resizedPiece.getDepth(); 11249 } else if (!selectedPiece.isWidthDepthDeformable()) { 11250 newDepth = this.resizedPiece.getDepth() * newWidth / this.resizedPiece.getWidth(); 11251 } else if (!this.resizedPiece.isDoorOrWindowBoundToWall() 11252 || !this.magnetismEnabled 11253 || this.widthOrDepthResizingActivated) { 11254 // Update piece depth if it's not a door a window 11255 // or if it's a a door a window unbound to a wall when magnetism is enabled 11256 newDepth = (float)(deltaY * cos - deltaX * sin); 11257 if (this.magnetismEnabled) { 11258 newDepth = preferences.getLengthUnit().getMagnetizedLength(newDepth, planView.getPixelLength()); 11259 } 11260 newDepth = Math.min(Math.max(newDepth, preferences.getLengthUnit().getMinimumLength()), 11261 preferences.getLengthUnit().getMaximumLength()); 11262 doorOrWindowBoundToWall = newDepth == this.resizedPiece.getDepth(); 11263 11264 if (this.widthOrDepthResizingActivated) { 11265 // Allow resizing of only width or depth depending on the location of the cursor 11266 // above or below a line joining the two opposite corners of the resized piece 11267 if (Math.signum(Line2D.relativeCCW(this.topLeftPoint [0], this.topLeftPoint [1], 11268 this.resizePoint [0], this.resizePoint [1], 11269 x - this.deltaXToResizePoint, y - this.deltaYToResizePoint)) >= 0) { 11270 newDepth = this.resizedPiece.getDepth(); 11271 } else { 11272 newWidth = this.resizedPiece.getWidth(); 11273 } 11274 } 11275 } 11276 11277 // Update piece size 11278 setPieceOfFurnitureSize(this.resizedPiece, newWidth, newDepth, newHeight); 11279 if (this.resizedPiece.isDoorOrWindowBoundToWall()) { 11280 // Maintain boundToWall flag 11281 ((HomeDoorOrWindow)selectedPiece).setBoundToWall(this.magnetismEnabled && doorOrWindowBoundToWall); 11282 } 11283 11284 // Update piece new location 11285 float newX = (float)(this.topLeftPoint [0] + (selectedPiece.getWidthInPlan() * cos - selectedPiece.getDepthInPlan() * sin) / 2f); 11286 float newY = (float)(this.topLeftPoint [1] + (selectedPiece.getWidthInPlan() * sin + selectedPiece.getDepthInPlan() * cos) / 2f); 11287 selectedPiece.setX(newX); 11288 selectedPiece.setY(newY); 11289 11290 // Ensure point at (x,y) is visible 11291 planView.makePointVisible(x, y); 11292 planView.setToolTipFeedback(getToolTipFeedbackText(newWidth, newDepth, newHeight), x, y); 11293 } 11294 11295 /** 11296 * Returns <code>true</code> if the <code>piece</code> should be proportionally resized. 11297 */ 11298 private boolean isProprortionallyResized(HomePieceOfFurniture piece) { 11299 return !piece.isDeformable() 11300 || piece.isHorizontallyRotated() 11301 || piece.getModelTransformations() != null 11302 || this.alignmentActivated; 11303 } 11304 11305 @Override 11306 public void releaseMouse(float x, float y) { 11307 postPieceOfFurnitureWidthAndDepthResize(this.resizedPiece); 11308 setState(getSelectionState()); 11309 } 11310 11311 @Override 11312 public void toggleMagnetism(boolean magnetismToggled) { 11313 // Compute active magnetism 11314 this.magnetismEnabled = preferences.isMagnetismEnabled() 11315 ^ magnetismToggled; 11316 // Compute again angle as if mouse moved 11317 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 11318 } 11319 11320 @Override 11321 public void setAlignmentActivated(boolean alignmentActivated) { 11322 this.alignmentActivated = alignmentActivated; 11323 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 11324 } 11325 11326 @Override 11327 public void setDuplicationActivated(boolean duplicationActivated) { 11328 this.widthOrDepthResizingActivated = duplicationActivated; 11329 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 11330 } 11331 11332 @Override 11333 public void escape() { 11334 resetPieceOfFurnitureSize(this.resizedPiece); 11335 setState(getSelectionState()); 11336 } 11337 11338 @Override 11339 public void exit() { 11340 PlanView planView = getView(); 11341 planView.setResizeIndicatorVisible(false); 11342 planView.deleteFeedback(); 11343 this.resizedPiece = null; 11344 } 11345 11346 private String getToolTipFeedbackText(float width, float depth, float height) { 11347 String toolTipFeedbackText = "<html>" + String.format(this.widthResizeToolTipFeedback, 11348 preferences.getLengthUnit().getFormatWithUnit().format(width)); 11349 if (!(this.resizedPiece.getPieceOfFurniture() instanceof HomeDoorOrWindow) 11350 || !((HomeDoorOrWindow)this.resizedPiece.getPieceOfFurniture()).isBoundToWall() 11351 || isProprortionallyResized(this.resizedPiece.getPieceOfFurniture())) { 11352 toolTipFeedbackText += "<br>" + String.format(this.depthResizeToolTipFeedback, 11353 preferences.getLengthUnit().getFormatWithUnit().format(depth)); 11354 } 11355 if (isProprortionallyResized(this.resizedPiece.getPieceOfFurniture())) { 11356 toolTipFeedbackText += "<br>" + String.format(this.heightResizeToolTipFeedback, 11357 preferences.getLengthUnit().getFormatWithUnit().format(height)); 11358 } 11359 return toolTipFeedbackText; 11360 } 11361 } 11362 11363 /** 11364 * Light power state. This states manages the power modification of a light. 11365 */ 11366 private class LightPowerModificationState extends ControllerState { 11367 private float deltaXToModificationPoint; 11368 private HomeLight selectedLight; 11369 private float oldPower; 11370 private String lightPowerToolTipFeedback; 11371 11372 @Override 11373 public Mode getMode() { 11374 return Mode.SELECTION; 11375 } 11376 11377 @Override 11378 public boolean isModificationState() { 11379 return true; 11380 } 11381 11382 @Override 11383 public void enter() { 11384 this.lightPowerToolTipFeedback = preferences.getLocalizedString( 11385 PlanController.class, "lightPowerToolTipFeedback"); 11386 this.selectedLight = (HomeLight)home.getSelectedItems().get(0); 11387 float [] resizePoint = this.selectedLight.getPoints() [3]; 11388 this.deltaXToModificationPoint = getXLastMousePress() - resizePoint [0]; 11389 this.oldPower = this.selectedLight.getPower(); 11390 PlanView planView = getView(); 11391 planView.setResizeIndicatorVisible(true); 11392 planView.setToolTipFeedback(getToolTipFeedbackText(this.oldPower), 11393 getXLastMousePress(), getYLastMousePress()); 11394 } 11395 11396 @Override 11397 public void moveMouse(float x, float y) { 11398 // Compute the new power of the light 11399 PlanView planView = getView(); 11400 float [] bottomLeftPoint = this.selectedLight.getPoints() [3]; 11401 float deltaX = x - this.deltaXToModificationPoint - bottomLeftPoint [0]; 11402 float newPower = this.oldPower + deltaX / 100f * getScale(); 11403 newPower = Math.min(Math.max(newPower, 0f), 1f); 11404 // Update light power 11405 this.selectedLight.setPower(newPower); 11406 11407 // Ensure point at (x,y) is visible 11408 planView.makePointVisible(x, y); 11409 planView.setToolTipFeedback(getToolTipFeedbackText(newPower), x, y); 11410 } 11411 11412 @Override 11413 public void releaseMouse(float x, float y) { 11414 postLightPowerModification(this.selectedLight, this.oldPower); 11415 setState(getSelectionState()); 11416 } 11417 11418 @Override 11419 public void escape() { 11420 this.selectedLight.setPower(this.oldPower); 11421 setState(getSelectionState()); 11422 } 11423 11424 @Override 11425 public void exit() { 11426 PlanView planView = getView(); 11427 planView.setResizeIndicatorVisible(false); 11428 planView.deleteFeedback(); 11429 this.selectedLight = null; 11430 } 11431 11432 private String getToolTipFeedbackText(float power) { 11433 return String.format(this.lightPowerToolTipFeedback, Math.round(power * 100)); 11434 } 11435 } 11436 11437 /** 11438 * Furniture name offset state. This state manages the name offset of a piece of furniture. 11439 */ 11440 private class PieceOfFurnitureNameOffsetState extends ControllerState { 11441 private HomePieceOfFurniture selectedPiece; 11442 private float oldNameXOffset; 11443 private float oldNameYOffset; 11444 private float xLastMouseMove; 11445 private float yLastMouseMove; 11446 private boolean alignmentActivated; 11447 11448 @Override 11449 public Mode getMode() { 11450 return Mode.SELECTION; 11451 } 11452 11453 @Override 11454 public boolean isModificationState() { 11455 return true; 11456 } 11457 11458 @Override 11459 public void enter() { 11460 this.selectedPiece = (HomePieceOfFurniture)home.getSelectedItems().get(0); 11461 this.oldNameXOffset = this.selectedPiece.getNameXOffset(); 11462 this.oldNameYOffset = this.selectedPiece.getNameYOffset(); 11463 this.xLastMouseMove = getXLastMousePress(); 11464 this.yLastMouseMove = getYLastMousePress(); 11465 this.alignmentActivated = wasAlignmentActivatedLastMousePress(); 11466 PlanView planView = getView(); 11467 planView.setResizeIndicatorVisible(true); 11468 } 11469 11470 @Override 11471 public void moveMouse(float x, float y) { 11472 if (this.alignmentActivated) { 11473 PointWithAngleMagnetism alignedPoint = new PointWithAngleMagnetism(getXLastMousePress(), getYLastMousePress(), 11474 x, y, preferences.getLengthUnit(), getView().getPixelLength(), 4); 11475 x = alignedPoint.getX(); 11476 y = alignedPoint.getY(); 11477 } 11478 this.selectedPiece.setNameXOffset(this.selectedPiece.getNameXOffset() + x - this.xLastMouseMove); 11479 this.selectedPiece.setNameYOffset(this.selectedPiece.getNameYOffset() + y - this.yLastMouseMove); 11480 this.xLastMouseMove = x; 11481 this.yLastMouseMove = y; 11482 11483 // Ensure point at (x,y) is visible 11484 getView().makePointVisible(x, y); 11485 } 11486 11487 @Override 11488 public void releaseMouse(float x, float y) { 11489 postPieceOfFurnitureNameOffset(this.selectedPiece, this.oldNameXOffset, this.oldNameYOffset); 11490 setState(getSelectionState()); 11491 } 11492 11493 @Override 11494 public void escape() { 11495 this.selectedPiece.setNameXOffset(this.oldNameXOffset); 11496 this.selectedPiece.setNameYOffset(this.oldNameYOffset); 11497 setState(getSelectionState()); 11498 } 11499 11500 @Override 11501 public void setAlignmentActivated(boolean alignmentActivated) { 11502 this.alignmentActivated = alignmentActivated; 11503 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 11504 } 11505 11506 @Override 11507 public void exit() { 11508 getView().setResizeIndicatorVisible(false); 11509 this.selectedPiece = null; 11510 } 11511 } 11512 11513 /** 11514 * Furniture name rotation state. This state manages the name rotation of a piece of furniture. 11515 */ 11516 private class PieceOfFurnitureNameRotationState extends ControllerState { 11517 private static final int STEP_COUNT = 24; 11518 11519 private HomePieceOfFurniture selectedPiece; 11520 private float oldNameAngle; 11521 private float angleMousePress; 11522 private boolean magnetismEnabled; 11523 private boolean alignmentActivated; 11524 11525 @Override 11526 public Mode getMode() { 11527 return Mode.SELECTION; 11528 } 11529 11530 @Override 11531 public boolean isModificationState() { 11532 return true; 11533 } 11534 11535 @Override 11536 public void enter() { 11537 this.selectedPiece = (HomePieceOfFurniture)home.getSelectedItems().get(0); 11538 this.angleMousePress = (float)Math.atan2(this.selectedPiece.getY() + this.selectedPiece.getNameYOffset() - getYLastMousePress(), 11539 getXLastMousePress() - this.selectedPiece.getX() - this.selectedPiece.getNameXOffset()); 11540 this.oldNameAngle = this.selectedPiece.getNameAngle(); 11541 this.alignmentActivated = wasAlignmentActivatedLastMousePress(); 11542 this.magnetismEnabled = preferences.isMagnetismEnabled() 11543 ^ wasMagnetismToggledLastMousePress(); 11544 PlanView planView = getView(); 11545 planView.setResizeIndicatorVisible(true); 11546 } 11547 11548 @Override 11549 public void moveMouse(float x, float y) { 11550 if (x != this.selectedPiece.getX() + this.selectedPiece.getNameXOffset() 11551 || y != this.selectedPiece.getY() + this.selectedPiece.getNameYOffset()) { 11552 // Compute the new angle of the piece name 11553 float angleMouseMove = (float)Math.atan2(this.selectedPiece.getY() + this.selectedPiece.getNameYOffset() - y, 11554 x - this.selectedPiece.getX() - this.selectedPiece.getNameXOffset()); 11555 float newAngle = this.oldNameAngle - angleMouseMove + this.angleMousePress; 11556 11557 if (this.alignmentActivated 11558 || this.magnetismEnabled) { 11559 float angleStep = 2 * (float)Math.PI / STEP_COUNT; 11560 // Compute angles closest to a step angle (multiple of angleStep) 11561 newAngle = Math.round(newAngle / angleStep) * angleStep; 11562 } 11563 11564 // Update piece name new angle 11565 this.selectedPiece.setNameAngle(newAngle); 11566 getView().makePointVisible(x, y); 11567 } 11568 } 11569 11570 @Override 11571 public void releaseMouse(float x, float y) { 11572 postPieceOfFurnitureNameRotation(this.selectedPiece, this.oldNameAngle); 11573 setState(getSelectionState()); 11574 } 11575 11576 @Override 11577 public void toggleMagnetism(boolean magnetismToggled) { 11578 // Compute active magnetism 11579 this.magnetismEnabled = preferences.isMagnetismEnabled() 11580 ^ magnetismToggled; 11581 // Compute again angle as if mouse moved 11582 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 11583 } 11584 11585 @Override 11586 public void setAlignmentActivated(boolean alignmentActivated) { 11587 this.alignmentActivated = alignmentActivated; 11588 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 11589 } 11590 11591 @Override 11592 public void escape() { 11593 this.selectedPiece.setNameAngle(this.oldNameAngle); 11594 setState(getSelectionState()); 11595 } 11596 11597 @Override 11598 public void exit() { 11599 getView().setResizeIndicatorVisible(false); 11600 this.selectedPiece = null; 11601 } 11602 } 11603 11604 /** 11605 * Camera yaw change state. This states manages the change of the observer camera yaw angle. 11606 */ 11607 private class CameraYawRotationState extends ControllerState { 11608 private ObserverCamera selectedCamera; 11609 private float oldYaw; 11610 private float xLastMouseMove; 11611 private float yLastMouseMove; 11612 private float angleLastMouseMove; 11613 private String rotationToolTipFeedback; 11614 11615 @Override 11616 public Mode getMode() { 11617 return Mode.SELECTION; 11618 } 11619 11620 @Override 11621 public boolean isModificationState() { 11622 return true; 11623 } 11624 11625 @Override 11626 public void enter() { 11627 this.rotationToolTipFeedback = preferences.getLocalizedString( 11628 PlanController.class, "cameraYawRotationToolTipFeedback"); 11629 this.selectedCamera = (ObserverCamera)home.getSelectedItems().get(0); 11630 this.oldYaw = this.selectedCamera.getYaw(); 11631 this.xLastMouseMove = getXLastMousePress(); 11632 this.yLastMouseMove = getYLastMousePress(); 11633 this.angleLastMouseMove = (float)Math.atan2(this.selectedCamera.getY() - this.yLastMouseMove, 11634 this.xLastMouseMove - this.selectedCamera.getX()); 11635 PlanView planView = getView(); 11636 planView.setResizeIndicatorVisible(true); 11637 planView.setToolTipFeedback(getToolTipFeedbackText(this.oldYaw), 11638 getXLastMousePress(), getYLastMousePress()); 11639 } 11640 11641 @Override 11642 public void moveMouse(float x, float y) { 11643 if (x != this.selectedCamera.getX() || y != this.selectedCamera.getY()) { 11644 // Compute the new angle of the camera 11645 float angleMouseMove = (float)Math.atan2(this.selectedCamera.getY() - y, 11646 x - this.selectedCamera.getX()); 11647 11648 // Compute yaw angle with a delta that takes into account the direction 11649 // of the rotation (clock wise or counter clock wise) 11650 float deltaYaw = angleLastMouseMove - angleMouseMove; 11651 float orientation = Math.signum((y - this.selectedCamera.getY()) * (this.xLastMouseMove - this.selectedCamera.getX()) 11652 - (this.yLastMouseMove - this.selectedCamera.getY()) * (x- this.selectedCamera.getX())); 11653 if (orientation < 0 && deltaYaw > 0) { 11654 deltaYaw -= (float)(Math.PI * 2f); 11655 } else if (orientation > 0 && deltaYaw < 0) { 11656 deltaYaw += (float)(Math.PI * 2f); 11657 } 11658 11659 // Update camera new yaw angle 11660 float newYaw = this.selectedCamera.getYaw() + deltaYaw; 11661 this.selectedCamera.setYaw(newYaw); 11662 11663 getView().setToolTipFeedback(getToolTipFeedbackText(newYaw), x, y); 11664 11665 this.xLastMouseMove = x; 11666 this.yLastMouseMove = y; 11667 this.angleLastMouseMove = angleMouseMove; 11668 } 11669 } 11670 11671 @Override 11672 public void releaseMouse(float x, float y) { 11673 setState(getSelectionState()); 11674 } 11675 11676 @Override 11677 public void escape() { 11678 this.selectedCamera.setYaw(this.oldYaw); 11679 setState(getSelectionState()); 11680 } 11681 11682 @Override 11683 public void exit() { 11684 PlanView planView = getView(); 11685 planView.setResizeIndicatorVisible(false); 11686 planView.deleteFeedback(); 11687 this.selectedCamera = null; 11688 } 11689 11690 private String getToolTipFeedbackText(float angle) { 11691 return String.format(this.rotationToolTipFeedback, 11692 (Math.round(Math.toDegrees(angle)) + 360) % 360); 11693 } 11694 } 11695 11696 /** 11697 * Camera pitch rotation state. This states manages the change of the observer camera pitch angle. 11698 */ 11699 private class CameraPitchRotationState extends ControllerState { 11700 private ObserverCamera selectedCamera; 11701 private float oldPitch; 11702 private String rotationToolTipFeedback; 11703 11704 @Override 11705 public Mode getMode() { 11706 return Mode.SELECTION; 11707 } 11708 11709 @Override 11710 public boolean isModificationState() { 11711 return true; 11712 } 11713 11714 @Override 11715 public void enter() { 11716 this.rotationToolTipFeedback = preferences.getLocalizedString( 11717 PlanController.class, "cameraPitchRotationToolTipFeedback"); 11718 this.selectedCamera = (ObserverCamera)home.getSelectedItems().get(0); 11719 this.oldPitch = this.selectedCamera.getPitch(); 11720 PlanView planView = getView(); 11721 planView.setResizeIndicatorVisible(true); 11722 planView.setToolTipFeedback(getToolTipFeedbackText(this.oldPitch), 11723 getXLastMousePress(), getYLastMousePress()); 11724 } 11725 11726 @Override 11727 public void moveMouse(float x, float y) { 11728 // Compute the new angle of the camera 11729 float newPitch = (float)(this.oldPitch 11730 + (y - getYLastMousePress()) * Math.cos(this.selectedCamera.getYaw()) * Math.PI / 360 11731 - (x - getXLastMousePress()) * Math.sin(this.selectedCamera.getYaw()) * Math.PI / 360); 11732 // Check new angle is between -90� and 90� 11733 newPitch = Math.max(newPitch, -(float)Math.PI / 2); 11734 newPitch = Math.min(newPitch, (float)Math.PI / 2); 11735 11736 // Update camera pitch angle 11737 this.selectedCamera.setPitch(newPitch); 11738 11739 getView().setToolTipFeedback(getToolTipFeedbackText(newPitch), x, y); 11740 } 11741 11742 @Override 11743 public void releaseMouse(float x, float y) { 11744 setState(getSelectionState()); 11745 } 11746 11747 @Override 11748 public void escape() { 11749 this.selectedCamera.setPitch(this.oldPitch); 11750 setState(getSelectionState()); 11751 } 11752 11753 @Override 11754 public void exit() { 11755 PlanView planView = getView(); 11756 planView.setResizeIndicatorVisible(false); 11757 planView.deleteFeedback(); 11758 this.selectedCamera = null; 11759 } 11760 11761 private String getToolTipFeedbackText(float angle) { 11762 return String.format(this.rotationToolTipFeedback, 11763 Math.round(Math.toDegrees(angle)) % 360); 11764 } 11765 } 11766 11767 /** 11768 * Camera elevation state. This states manages the change of the observer camera elevation. 11769 */ 11770 private class CameraElevationState extends ControllerState { 11771 private ObserverCamera selectedCamera; 11772 private float oldElevation; 11773 private String cameraElevationToolTipFeedback; 11774 private String observerHeightToolTipFeedback; 11775 11776 @Override 11777 public Mode getMode() { 11778 return Mode.SELECTION; 11779 } 11780 11781 @Override 11782 public boolean isModificationState() { 11783 return true; 11784 } 11785 11786 @Override 11787 public void enter() { 11788 this.cameraElevationToolTipFeedback = preferences.getLocalizedString( 11789 PlanController.class, "cameraElevationToolTipFeedback"); 11790 this.observerHeightToolTipFeedback = preferences.getLocalizedString( 11791 PlanController.class, "observerHeightToolTipFeedback"); 11792 this.selectedCamera = (ObserverCamera)home.getSelectedItems().get(0); 11793 this.oldElevation = this.selectedCamera.getZ(); 11794 PlanView planView = getView(); 11795 planView.setResizeIndicatorVisible(true); 11796 planView.setToolTipFeedback(getToolTipFeedbackText(this.oldElevation), 11797 getXLastMousePress(), getYLastMousePress()); 11798 } 11799 11800 @Override 11801 public void moveMouse(float x, float y) { 11802 // Compute the new elevation of the camera 11803 float newElevation = (float)(this.oldElevation - (y - getYLastMousePress())); 11804 List<Level> levels = home.getLevels(); 11805 float minimumElevation = levels.size() == 0 ? 10 : 10 + levels.get(0).getElevation(); 11806 // Check new elevation is between min and max 11807 newElevation = Math.min(Math.max(newElevation, minimumElevation), preferences.getLengthUnit().getMaximumElevation()); 11808 11809 // Update camera elevation 11810 this.selectedCamera.setZ(newElevation); 11811 getView().setToolTipFeedback(getToolTipFeedbackText(newElevation), x, y); 11812 } 11813 11814 @Override 11815 public void releaseMouse(float x, float y) { 11816 setState(getSelectionState()); 11817 } 11818 11819 @Override 11820 public void escape() { 11821 this.selectedCamera.setZ(this.oldElevation); 11822 setState(getSelectionState()); 11823 } 11824 11825 @Override 11826 public void exit() { 11827 PlanView planView = getView(); 11828 planView.setResizeIndicatorVisible(false); 11829 planView.deleteFeedback(); 11830 this.selectedCamera = null; 11831 } 11832 11833 private String getToolTipFeedbackText(float elevation) { 11834 String toolTipFeedbackText = "<html>" + String.format(this.cameraElevationToolTipFeedback, 11835 preferences.getLengthUnit().getFormatWithUnit().format(elevation)); 11836 if (!this.selectedCamera.isFixedSize() && elevation >= 70 && elevation <= 218.75f) { 11837 toolTipFeedbackText += "<br>" + String.format(this.observerHeightToolTipFeedback, 11838 preferences.getLengthUnit().getFormatWithUnit().format(elevation * 15 / 14)); 11839 } 11840 return toolTipFeedbackText; 11841 } 11842 } 11843 11844 /** 11845 * Dimension line creation state. This state manages transition to 11846 * other modes, and initial dimension line creation. 11847 */ 11848 private class DimensionLineCreationState extends AbstractModeChangeState { 11849 private boolean magnetismEnabled; 11850 11851 @Override 11852 public Mode getMode() { 11853 return Mode.DIMENSION_LINE_CREATION; 11854 } 11855 11856 @Override 11857 public void enter() { 11858 getView().setCursor(PlanView.CursorType.DRAW); 11859 toggleMagnetism(wasMagnetismToggledLastMousePress()); 11860 } 11861 11862 @Override 11863 public void moveMouse(float x, float y) { 11864 getView().setAlignmentFeedback(DimensionLine.class, null, x, y, false); 11865 DimensionLine dimensionLine = getMeasuringDimensionLineAt(x, y, this.magnetismEnabled); 11866 if (dimensionLine != null) { 11867 getView().setDimensionLinesFeedback(Arrays.asList(new DimensionLine [] {dimensionLine})); 11868 } else { 11869 getView().setDimensionLinesFeedback(null); 11870 } 11871 } 11872 11873 @Override 11874 public void pressMouse(float x, float y, int clickCount, 11875 boolean shiftDown, boolean duplicationActivated) { 11876 // Ignore double clicks (may happen when state is activated returning from DimensionLineDrawingState) 11877 if (clickCount == 1) { 11878 // Change state to DimensionLineDrawingState 11879 setState(getDimensionLineDrawingState()); 11880 } 11881 } 11882 11883 @Override 11884 public void setEditionActivated(boolean editionActivated) { 11885 if (editionActivated) { 11886 setState(getDimensionLineDrawingState()); 11887 PlanController.this.setEditionActivated(editionActivated); 11888 } 11889 } 11890 11891 @Override 11892 public void toggleMagnetism(boolean magnetismToggled) { 11893 // Compute active magnetism 11894 this.magnetismEnabled = preferences.isMagnetismEnabled() 11895 ^ magnetismToggled; 11896 if (getPointerTypeLastMousePress() != View.PointerType.TOUCH) { 11897 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 11898 } 11899 } 11900 11901 @Override 11902 public void exit() { 11903 getView().deleteFeedback(); 11904 } 11905 } 11906 11907 /** 11908 * Dimension line drawing state. This state manages dimension line creation at mouse press. 11909 */ 11910 private class DimensionLineDrawingState extends ControllerState { 11911 private float xStart; 11912 private float yStart; 11913 private boolean editingStartPoint; 11914 private DimensionLine newDimensionLine; 11915 private List<Selectable> oldSelection; 11916 private boolean oldBasePlanLocked; 11917 private boolean oldAllLevelsSelection; 11918 private boolean magnetismEnabled; 11919 private boolean alignmentActivated; 11920 private boolean offsetChoice; 11921 11922 @Override 11923 public Mode getMode() { 11924 return Mode.DIMENSION_LINE_CREATION; 11925 } 11926 11927 @Override 11928 public boolean isModificationState() { 11929 return true; 11930 } 11931 11932 @Override 11933 public boolean isBasePlanModificationState() { 11934 return true; 11935 } 11936 11937 @Override 11938 public void setMode(Mode mode) { 11939 // Escape current creation and change state to matching mode 11940 escape(); 11941 if (mode == Mode.SELECTION) { 11942 setState(getSelectionState()); 11943 } else if (mode == Mode.PANNING) { 11944 setState(getPanningState()); 11945 } else if (mode == Mode.WALL_CREATION) { 11946 setState(getWallCreationState()); 11947 } else if (mode == Mode.ROOM_CREATION) { 11948 setState(getRoomCreationState()); 11949 } else if (mode == Mode.POLYLINE_CREATION) { 11950 setState(getPolylineCreationState()); 11951 } else if (mode == Mode.LABEL_CREATION) { 11952 setState(getLabelCreationState()); 11953 } 11954 } 11955 11956 @Override 11957 public void enter() { 11958 this.oldSelection = home.getSelectedItems(); 11959 this.oldBasePlanLocked = home.isBasePlanLocked(); 11960 this.oldAllLevelsSelection = home.isAllLevelsSelection(); 11961 this.xStart = getXLastMouseMove(); 11962 this.yStart = getYLastMouseMove(); 11963 this.editingStartPoint = false; 11964 this.offsetChoice = false; 11965 this.newDimensionLine = null; 11966 deselectAll(); 11967 this.alignmentActivated = wasAlignmentActivatedLastMousePress(); 11968 toggleMagnetism(wasMagnetismToggledLastMousePress()); 11969 DimensionLine dimensionLine = getMeasuringDimensionLineAt( 11970 getXLastMousePress(), getYLastMousePress(), this.magnetismEnabled); 11971 if (dimensionLine != null) { 11972 getView().setDimensionLinesFeedback(Arrays.asList(new DimensionLine [] {dimensionLine})); 11973 } 11974 getView().setAlignmentFeedback(DimensionLine.class, 11975 null, getXLastMousePress(), getYLastMousePress(), false); 11976 } 11977 11978 @Override 11979 public void moveMouse(float x, float y) { 11980 PlanView planView = getView(); 11981 planView.deleteFeedback(); 11982 if (this.offsetChoice) { 11983 float distanceToDimensionLine = (float)Line2D.ptLineDist( 11984 this.newDimensionLine.getXStart(), this.newDimensionLine.getYStart(), 11985 this.newDimensionLine.getXEnd(), this.newDimensionLine.getYEnd(), x, y); 11986 if (this.newDimensionLine.getLength() > 0) { 11987 int relativeCCW = Line2D.relativeCCW( 11988 this.newDimensionLine.getXStart(), this.newDimensionLine.getYStart(), 11989 this.newDimensionLine.getXEnd(), this.newDimensionLine.getYEnd(), x, y); 11990 this.newDimensionLine.setOffset( 11991 -Math.signum(relativeCCW) * distanceToDimensionLine); 11992 } 11993 } else { 11994 // Compute the coordinates where dimension line end point should be moved 11995 float newX; 11996 float newY; 11997 if (this.magnetismEnabled 11998 || this.alignmentActivated) { 11999 PointWithAngleMagnetism point = new PointWithAngleMagnetism( 12000 this.xStart, this.yStart, x, y, 12001 preferences.getLengthUnit(), planView.getPixelLength()); 12002 newX = point.getX(); 12003 newY = point.getY(); 12004 } else { 12005 newX = x; 12006 newY = y; 12007 } 12008 12009 // If current dimension line doesn't exist 12010 if (this.newDimensionLine == null) { 12011 // Create a new one 12012 this.newDimensionLine = createDimensionLine(this.xStart, this.yStart, newX, newY, 0); 12013 getView().setDimensionLinesFeedback(null); 12014 } else { 12015 // Otherwise update its end points 12016 if (this.editingStartPoint) { 12017 this.newDimensionLine.setXStart(newX); 12018 this.newDimensionLine.setYStart(newY); 12019 } else { 12020 this.newDimensionLine.setXEnd(newX); 12021 this.newDimensionLine.setYEnd(newY); 12022 } 12023 } 12024 updateReversedDimensionLine(); 12025 12026 planView.setAlignmentFeedback(DimensionLine.class, 12027 this.newDimensionLine, newX, newY, false); 12028 } 12029 // Ensure point at (x,y) is visible 12030 planView.makePointVisible(x, y); 12031 } 12032 12033 /** 12034 * Swaps start and end point of the created dimension line if needed 12035 * to ensure its text is never upside down. 12036 */ 12037 private void updateReversedDimensionLine() { 12038 double angle = getDimensionLineAngle(); 12039 boolean reverse = angle < -Math.PI / 2 || angle > Math.PI / 2; 12040 if (reverse ^ this.editingStartPoint) { 12041 reverseDimensionLine(this.newDimensionLine); 12042 this.editingStartPoint = !this.editingStartPoint; 12043 } 12044 } 12045 12046 private double getDimensionLineAngle() { 12047 if (this.newDimensionLine.getLength() == 0) { 12048 return 0; 12049 } else { 12050 if (this.editingStartPoint) { 12051 return Math.atan2(this.yStart - this.newDimensionLine.getYStart(), 12052 this.newDimensionLine.getXStart() - this.xStart); 12053 } else { 12054 return Math.atan2(this.yStart - this.newDimensionLine.getYEnd(), 12055 this.newDimensionLine.getXEnd() - this.xStart); 12056 } 12057 } 12058 } 12059 12060 @Override 12061 public void pressMouse(float x, float y, int clickCount, 12062 boolean shiftDown, boolean duplicationActivated) { 12063 if (this.newDimensionLine == null 12064 && clickCount == 2) { 12065 // Try to guess the item to measure 12066 DimensionLine dimensionLine = getMeasuringDimensionLineAt(x, y, this.magnetismEnabled); 12067 if (dimensionLine != null) { 12068 this.newDimensionLine = createDimensionLine( 12069 dimensionLine.getXStart(), dimensionLine.getYStart(), 12070 dimensionLine.getXEnd(), dimensionLine.getYEnd(), 12071 dimensionLine.getOffset()); 12072 if (getPointerTypeLastMousePress() == View.PointerType.TOUCH) { 12073 validateDrawnDimensionLine(); 12074 } 12075 } else { 12076 setState(getDimensionLineCreationState()); 12077 return; 12078 } 12079 } 12080 // Create a new dimension line only when it will have a length > 0 12081 // meaning after the first mouse move 12082 if (this.newDimensionLine != null) { 12083 if (this.offsetChoice) { 12084 validateDrawnDimensionLine(); 12085 } else { 12086 // Switch to offset choice 12087 this.offsetChoice = true; 12088 PlanView planView = getView(); 12089 planView.setCursor(PlanView.CursorType.HEIGHT); 12090 planView.deleteFeedback(); 12091 } 12092 } 12093 } 12094 12095 @Override 12096 public void releaseMouse(float x, float y) { 12097 if (getPointerTypeLastMousePress() == View.PointerType.TOUCH 12098 && this.newDimensionLine != null) { 12099 validateDrawnDimensionLine(); 12100 } 12101 } 12102 12103 private void validateDrawnDimensionLine() { 12104 selectItem(this.newDimensionLine); 12105 // Post dimension line creation to undo support 12106 postCreateDimensionLines(Arrays.asList(new DimensionLine [] {this.newDimensionLine}), 12107 this.oldSelection, this.oldBasePlanLocked, this.oldAllLevelsSelection); 12108 this.newDimensionLine = null; 12109 if (getPointerTypeLastMousePress() == View.PointerType.TOUCH) { 12110 setState(getSelectionState()); 12111 } else { 12112 // Change state to DimensionLineCreationState 12113 setState(getDimensionLineCreationState()); 12114 } 12115 } 12116 12117 @Override 12118 public void setEditionActivated(boolean editionActivated) { 12119 PlanView planView = getView(); 12120 if (editionActivated) { 12121 planView.deleteFeedback(); 12122 if (this.newDimensionLine == null) { 12123 // Edit xStart and yStart 12124 planView.setToolTipEditedProperties(new EditableProperty [] {EditableProperty.X, 12125 EditableProperty.Y}, 12126 new Object [] {this.xStart, this.yStart}, 12127 this.xStart, this.yStart); 12128 } else if (this.offsetChoice) { 12129 // Edit offset 12130 planView.setToolTipEditedProperties(new EditableProperty [] {EditableProperty.OFFSET}, 12131 new Object [] {this.newDimensionLine.getOffset()}, 12132 this.newDimensionLine.getXEnd(), this.newDimensionLine.getYEnd()); 12133 } else { 12134 // Edit length and angle 12135 planView.setToolTipEditedProperties(new EditableProperty [] {EditableProperty.LENGTH, 12136 EditableProperty.ANGLE}, 12137 new Object [] {this.newDimensionLine.getLength(), 12138 (int)Math.round(Math.toDegrees(getDimensionLineAngle()))}, 12139 this.newDimensionLine.getXEnd(), this.newDimensionLine.getYEnd()); 12140 } 12141 } else { 12142 if (this.newDimensionLine == null) { 12143 // Create a new dimension line once user entered its start point 12144 LengthUnit lengthUnit = preferences.getLengthUnit(); 12145 float defaultLength = lengthUnit == LengthUnit.INCH || lengthUnit == LengthUnit.INCH_DECIMALS 12146 ? LengthUnit.footToCentimeter(3) : 100; 12147 this.newDimensionLine = createDimensionLine(this.xStart, this.yStart, 12148 this.xStart + defaultLength, this.yStart, 0); 12149 // Activate automatically second step to let user enter the 12150 // length and angle of the new dimension line 12151 planView.deleteFeedback(); 12152 setEditionActivated(true); 12153 } else if (this.offsetChoice) { 12154 validateDrawnDimensionLine(); 12155 } else { 12156 this.offsetChoice = true; 12157 setEditionActivated(true); 12158 } 12159 } 12160 } 12161 12162 @Override 12163 public void updateEditableProperty(EditableProperty editableProperty, Object value) { 12164 PlanView planView = getView(); 12165 float maximumLength = preferences.getLengthUnit().getMaximumLength(); 12166 if (this.newDimensionLine == null) { 12167 // Update start point of the dimension line 12168 switch (editableProperty) { 12169 case X : 12170 this.xStart = value != null ? ((Number)value).floatValue() : 0; 12171 this.xStart = Math.max(-maximumLength, Math.min(this.xStart, maximumLength)); 12172 break; 12173 case Y : 12174 this.yStart = value != null ? ((Number)value).floatValue() : 0; 12175 this.yStart = Math.max(-maximumLength, Math.min(this.yStart, maximumLength)); 12176 break; 12177 } 12178 planView.setAlignmentFeedback(DimensionLine.class, null, this.xStart, this.yStart, true); 12179 planView.makePointVisible(this.xStart, this.yStart); 12180 } else if (this.offsetChoice) { 12181 if (editableProperty == EditableProperty.OFFSET) { 12182 // Update new dimension line offset 12183 float offset = value != null ? ((Number)value).floatValue() : 0; 12184 offset = Math.max(-maximumLength, Math.min(offset, maximumLength)); 12185 this.newDimensionLine.setOffset(offset); 12186 } 12187 } else { 12188 float newX; 12189 float newY; 12190 // Update end point of the dimension line 12191 switch (editableProperty) { 12192 case LENGTH : 12193 float length = value != null ? ((Number)value).floatValue() : 0; 12194 length = Math.max(0.001f, Math.min(length, maximumLength)); 12195 double dimensionLineAngle = getDimensionLineAngle(); 12196 newX = (float)(this.xStart + length * Math.cos(dimensionLineAngle)); 12197 newY = (float)(this.yStart - length * Math.sin(dimensionLineAngle)); 12198 break; 12199 case ANGLE : 12200 dimensionLineAngle = Math.toRadians(value != null ? ((Number)value).floatValue() : 0); 12201 float dimensionLineLength = this.newDimensionLine.getLength(); 12202 newX = (float)(this.xStart + dimensionLineLength * Math.cos(dimensionLineAngle)); 12203 newY = (float)(this.yStart - dimensionLineLength * Math.sin(dimensionLineAngle)); 12204 break; 12205 default : 12206 return; 12207 } 12208 12209 // Update new dimension line 12210 if (this.editingStartPoint) { 12211 this.newDimensionLine.setXStart(newX); 12212 this.newDimensionLine.setYStart(newY); 12213 } else { 12214 this.newDimensionLine.setXEnd(newX); 12215 this.newDimensionLine.setYEnd(newY); 12216 } 12217 updateReversedDimensionLine(); 12218 planView.setAlignmentFeedback(DimensionLine.class, this.newDimensionLine, newX, newY, false); 12219 // Ensure dimension line end points are visible 12220 planView.makePointVisible(this.xStart, this.yStart); 12221 planView.makePointVisible(newX, newY); 12222 } 12223 } 12224 12225 @Override 12226 public void toggleMagnetism(boolean magnetismToggled) { 12227 // Compute active magnetism 12228 this.magnetismEnabled = preferences.isMagnetismEnabled() 12229 ^ magnetismToggled; 12230 // If the new dimension line already exists, 12231 // compute again its end as if mouse moved 12232 if (this.newDimensionLine != null && !this.offsetChoice) { 12233 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 12234 } 12235 } 12236 12237 @Override 12238 public void setAlignmentActivated(boolean alignmentActivated) { 12239 this.alignmentActivated = alignmentActivated; 12240 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 12241 } 12242 12243 @Override 12244 public void escape() { 12245 if (this.newDimensionLine != null) { 12246 // Delete current created dimension line 12247 home.deleteDimensionLine(this.newDimensionLine); 12248 } 12249 if (getPointerTypeLastMousePress() == View.PointerType.TOUCH) { 12250 setState(getSelectionState()); 12251 } else { 12252 // Change state to DimensionLineCreationState 12253 setState(getDimensionLineCreationState()); 12254 } 12255 } 12256 12257 @Override 12258 public void exit() { 12259 getView().deleteFeedback(); 12260 this.newDimensionLine = null; 12261 this.oldSelection = null; 12262 } 12263 } 12264 12265 /** 12266 * Dimension line resize state. This state manages dimension line resizing. 12267 */ 12268 private class DimensionLineResizeState extends ControllerState { 12269 private DimensionLine selectedDimensionLine; 12270 private boolean editingStartPoint; 12271 private float oldX; 12272 private float oldY; 12273 private boolean reversedDimensionLine; 12274 private float deltaXToResizePoint; 12275 private float deltaYToResizePoint; 12276 private float distanceFromResizePointToDimensionBaseLine; 12277 private boolean magnetismEnabled; 12278 private boolean alignmentActivated; 12279 12280 12281 @Override 12282 public Mode getMode() { 12283 return Mode.SELECTION; 12284 } 12285 12286 @Override 12287 public boolean isModificationState() { 12288 return true; 12289 } 12290 12291 @Override 12292 public boolean isBasePlanModificationState() { 12293 return true; 12294 } 12295 12296 @Override 12297 public void enter() { 12298 PlanView planView = getView(); 12299 planView.setResizeIndicatorVisible(true); 12300 12301 this.selectedDimensionLine = (DimensionLine)home.getSelectedItems().get(0); 12302 this.editingStartPoint = this.selectedDimensionLine 12303 == getResizedDimensionLineStartAt(getXLastMousePress(), getYLastMousePress()); 12304 if (this.editingStartPoint) { 12305 this.oldX = this.selectedDimensionLine.getXStart(); 12306 this.oldY = this.selectedDimensionLine.getYStart(); 12307 } else { 12308 this.oldX = this.selectedDimensionLine.getXEnd(); 12309 this.oldY = this.selectedDimensionLine.getYEnd(); 12310 } 12311 this.reversedDimensionLine = false; 12312 12313 float xResizePoint; 12314 float yResizePoint; 12315 // Compute the closest resize point placed on the extension line and the distance 12316 // between that point and the dimension line base 12317 float alpha1 = (float)(this.selectedDimensionLine.getYEnd() - this.selectedDimensionLine.getYStart()) 12318 / (this.selectedDimensionLine.getXEnd() - this.selectedDimensionLine.getXStart()); 12319 // If line is vertical 12320 if (Math.abs(alpha1) > 1E5) { 12321 xResizePoint = getXLastMousePress(); 12322 if (this.editingStartPoint) { 12323 yResizePoint = this.selectedDimensionLine.getYStart(); 12324 } else { 12325 yResizePoint = this.selectedDimensionLine.getYEnd(); 12326 } 12327 } else if (this.selectedDimensionLine.getYStart() == this.selectedDimensionLine.getYEnd()) { 12328 if (this.editingStartPoint) { 12329 xResizePoint = this.selectedDimensionLine.getXStart(); 12330 } else { 12331 xResizePoint = this.selectedDimensionLine.getXEnd(); 12332 } 12333 yResizePoint = getYLastMousePress(); 12334 } else { 12335 float beta1 = getYLastMousePress() - alpha1 * getXLastMousePress(); 12336 float alpha2 = -1 / alpha1; 12337 float beta2; 12338 12339 if (this.editingStartPoint) { 12340 beta2 = this.selectedDimensionLine.getYStart() - alpha2 * this.selectedDimensionLine.getXStart(); 12341 } else { 12342 beta2 = this.selectedDimensionLine.getYEnd() - alpha2 * this.selectedDimensionLine.getXEnd(); 12343 } 12344 xResizePoint = (beta2 - beta1) / (alpha1 - alpha2); 12345 yResizePoint = alpha1 * xResizePoint + beta1; 12346 } 12347 12348 this.deltaXToResizePoint = getXLastMousePress() - xResizePoint; 12349 this.deltaYToResizePoint = getYLastMousePress() - yResizePoint; 12350 if (this.editingStartPoint) { 12351 this.distanceFromResizePointToDimensionBaseLine = (float)Point2D.distance(xResizePoint, yResizePoint, 12352 this.selectedDimensionLine.getXStart(), this.selectedDimensionLine.getYStart()); 12353 planView.setAlignmentFeedback(DimensionLine.class, this.selectedDimensionLine, 12354 this.selectedDimensionLine.getXStart(), this.selectedDimensionLine.getYStart(), false); 12355 } else { 12356 this.distanceFromResizePointToDimensionBaseLine = (float)Point2D.distance(xResizePoint, yResizePoint, 12357 this.selectedDimensionLine.getXEnd(), this.selectedDimensionLine.getYEnd()); 12358 planView.setAlignmentFeedback(DimensionLine.class, this.selectedDimensionLine, 12359 this.selectedDimensionLine.getXEnd(), this.selectedDimensionLine.getYEnd(), false); 12360 } 12361 this.alignmentActivated = wasAlignmentActivatedLastMousePress(); 12362 toggleMagnetism(wasMagnetismToggledLastMousePress()); 12363 } 12364 12365 @Override 12366 public void moveMouse(float x, float y) { 12367 PlanView planView = getView(); 12368 float xResizePoint = x - this.deltaXToResizePoint; 12369 float yResizePoint = y - this.deltaYToResizePoint; 12370 if (this.editingStartPoint) { 12371 // Compute the new start point of the dimension line knowing that the distance 12372 // from resize point to dimension line base is constant, 12373 // and that the end point of the dimension line doesn't move 12374 double distanceFromResizePointToDimensionLineEnd = Point2D.distance(xResizePoint, yResizePoint, 12375 this.selectedDimensionLine.getXEnd(), this.selectedDimensionLine.getYEnd()); 12376 double distanceFromDimensionLineStartToDimensionLineEnd = Math.sqrt( 12377 distanceFromResizePointToDimensionLineEnd * distanceFromResizePointToDimensionLineEnd 12378 - this.distanceFromResizePointToDimensionBaseLine * this.distanceFromResizePointToDimensionBaseLine); 12379 if (distanceFromDimensionLineStartToDimensionLineEnd > 0) { 12380 double dimensionLineRelativeAngle = -Math.atan2(this.distanceFromResizePointToDimensionBaseLine, 12381 distanceFromDimensionLineStartToDimensionLineEnd); 12382 if (this.selectedDimensionLine.getOffset() >= 0) { 12383 dimensionLineRelativeAngle = -dimensionLineRelativeAngle; 12384 } 12385 double resizePointToDimensionLineEndAngle = Math.atan2(yResizePoint - this.selectedDimensionLine.getYEnd(), 12386 xResizePoint - this.selectedDimensionLine.getXEnd()); 12387 double dimensionLineStartToDimensionLineEndAngle = dimensionLineRelativeAngle + resizePointToDimensionLineEndAngle; 12388 float xNewStartPoint = this.selectedDimensionLine.getXEnd() + (float)(distanceFromDimensionLineStartToDimensionLineEnd 12389 * Math.cos(dimensionLineStartToDimensionLineEndAngle)); 12390 float yNewStartPoint = this.selectedDimensionLine.getYEnd() + (float)(distanceFromDimensionLineStartToDimensionLineEnd 12391 * Math.sin(dimensionLineStartToDimensionLineEndAngle)); 12392 12393 if (this.alignmentActivated 12394 || this.magnetismEnabled) { 12395 PointWithAngleMagnetism point = new PointWithAngleMagnetism( 12396 this.selectedDimensionLine.getXEnd(), 12397 this.selectedDimensionLine.getYEnd(), xNewStartPoint, yNewStartPoint, 12398 preferences.getLengthUnit(), planView.getPixelLength()); 12399 xNewStartPoint = point.getX(); 12400 yNewStartPoint = point.getY(); 12401 } 12402 12403 moveDimensionLinePoint(this.selectedDimensionLine, xNewStartPoint, yNewStartPoint, this.editingStartPoint); 12404 updateReversedDimensionLine(); 12405 planView.setAlignmentFeedback(DimensionLine.class, this.selectedDimensionLine, 12406 xNewStartPoint, yNewStartPoint, false); 12407 } else { 12408 planView.deleteFeedback(); 12409 } 12410 } else { 12411 // Compute the new end point of the dimension line knowing that the distance 12412 // from resize point to dimension line base is constant, 12413 // and that the start point of the dimension line doesn't move 12414 double distanceFromResizePointToDimensionLineStart = Point2D.distance(xResizePoint, yResizePoint, 12415 this.selectedDimensionLine.getXStart(), this.selectedDimensionLine.getYStart()); 12416 double distanceFromDimensionLineStartToDimensionLineEnd = Math.sqrt( 12417 distanceFromResizePointToDimensionLineStart * distanceFromResizePointToDimensionLineStart 12418 - this.distanceFromResizePointToDimensionBaseLine * this.distanceFromResizePointToDimensionBaseLine); 12419 if (distanceFromDimensionLineStartToDimensionLineEnd > 0) { 12420 double dimensionLineRelativeAngle = Math.atan2(this.distanceFromResizePointToDimensionBaseLine, 12421 distanceFromDimensionLineStartToDimensionLineEnd); 12422 if (this.selectedDimensionLine.getOffset() >= 0) { 12423 dimensionLineRelativeAngle = -dimensionLineRelativeAngle; 12424 } 12425 double resizePointToDimensionLineStartAngle = Math.atan2(yResizePoint - this.selectedDimensionLine.getYStart(), 12426 xResizePoint - this.selectedDimensionLine.getXStart()); 12427 double dimensionLineStartToDimensionLineEndAngle = dimensionLineRelativeAngle + resizePointToDimensionLineStartAngle; 12428 float xNewEndPoint = this.selectedDimensionLine.getXStart() + (float)(distanceFromDimensionLineStartToDimensionLineEnd 12429 * Math.cos(dimensionLineStartToDimensionLineEndAngle)); 12430 float yNewEndPoint = this.selectedDimensionLine.getYStart() + (float)(distanceFromDimensionLineStartToDimensionLineEnd 12431 * Math.sin(dimensionLineStartToDimensionLineEndAngle)); 12432 12433 if (this.alignmentActivated 12434 || this.magnetismEnabled) { 12435 PointWithAngleMagnetism point = new PointWithAngleMagnetism( 12436 this.selectedDimensionLine.getXStart(), 12437 this.selectedDimensionLine.getYStart(), xNewEndPoint, yNewEndPoint, 12438 preferences.getLengthUnit(), planView.getPixelLength()); 12439 xNewEndPoint = point.getX(); 12440 yNewEndPoint = point.getY(); 12441 } 12442 12443 moveDimensionLinePoint(this.selectedDimensionLine, xNewEndPoint, yNewEndPoint, this.editingStartPoint); 12444 updateReversedDimensionLine(); 12445 planView.setAlignmentFeedback(DimensionLine.class, this.selectedDimensionLine, 12446 xNewEndPoint, yNewEndPoint, false); 12447 } else { 12448 planView.deleteFeedback(); 12449 } 12450 } 12451 12452 // Ensure point at (x,y) is visible 12453 getView().makePointVisible(x, y); 12454 } 12455 12456 /** 12457 * Swaps start and end point of the dimension line if needed 12458 * to ensure its text is never upside down. 12459 */ 12460 private void updateReversedDimensionLine() { 12461 double angle = getDimensionLineAngle(); 12462 if (angle < -Math.PI / 2 || angle > Math.PI / 2) { 12463 reverseDimensionLine(this.selectedDimensionLine); 12464 this.editingStartPoint = !this.editingStartPoint; 12465 this.reversedDimensionLine = !this.reversedDimensionLine; 12466 } 12467 } 12468 12469 private double getDimensionLineAngle() { 12470 if (this.selectedDimensionLine.getLength() == 0) { 12471 return 0; 12472 } else { 12473 return Math.atan2(this.selectedDimensionLine.getYStart() - this.selectedDimensionLine.getYEnd(), 12474 this.selectedDimensionLine.getXEnd() - this.selectedDimensionLine.getXStart()); 12475 } 12476 } 12477 12478 @Override 12479 public void releaseMouse(float x, float y) { 12480 postDimensionLineResize(this.selectedDimensionLine, this.oldX, this.oldY, 12481 this.editingStartPoint, this.reversedDimensionLine); 12482 setState(getSelectionState()); 12483 } 12484 12485 @Override 12486 public void toggleMagnetism(boolean magnetismToggled) { 12487 // Compute active magnetism 12488 this.magnetismEnabled = preferences.isMagnetismEnabled() 12489 ^ magnetismToggled; 12490 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 12491 } 12492 12493 @Override 12494 public void setAlignmentActivated(boolean alignmentActivated) { 12495 this.alignmentActivated = alignmentActivated; 12496 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 12497 } 12498 12499 @Override 12500 public void escape() { 12501 if (this.reversedDimensionLine) { 12502 reverseDimensionLine(this.selectedDimensionLine); 12503 this.editingStartPoint = !this.editingStartPoint; 12504 } 12505 moveDimensionLinePoint(this.selectedDimensionLine, this.oldX, this.oldY, this.editingStartPoint); 12506 setState(getSelectionState()); 12507 } 12508 12509 @Override 12510 public void exit() { 12511 PlanView planView = getView(); 12512 planView.deleteFeedback(); 12513 planView.setResizeIndicatorVisible(false); 12514 this.selectedDimensionLine = null; 12515 } 12516 } 12517 12518 /** 12519 * Dimension line offset state. This state manages dimension line offset. 12520 */ 12521 private class DimensionLineOffsetState extends ControllerState { 12522 private DimensionLine selectedDimensionLine; 12523 private float oldOffset; 12524 private float deltaXToOffsetPoint; 12525 private float deltaYToOffsetPoint; 12526 12527 @Override 12528 public Mode getMode() { 12529 return Mode.SELECTION; 12530 } 12531 12532 @Override 12533 public boolean isModificationState() { 12534 return true; 12535 } 12536 12537 @Override 12538 public boolean isBasePlanModificationState() { 12539 return true; 12540 } 12541 12542 @Override 12543 public void enter() { 12544 this.selectedDimensionLine = (DimensionLine)home.getSelectedItems().get(0); 12545 this.oldOffset = this.selectedDimensionLine.getOffset(); 12546 double angle = Math.atan2(this.selectedDimensionLine.getYEnd() - this.selectedDimensionLine.getYStart(), 12547 this.selectedDimensionLine.getXEnd() - this.selectedDimensionLine.getXStart()); 12548 float dx = (float)-Math.sin(angle) * this.oldOffset; 12549 float dy = (float)Math.cos(angle) * this.oldOffset; 12550 float xMiddle = (this.selectedDimensionLine.getXStart() + this.selectedDimensionLine.getXEnd()) / 2 + dx; 12551 float yMiddle = (this.selectedDimensionLine.getYStart() + this.selectedDimensionLine.getYEnd()) / 2 + dy; 12552 this.deltaXToOffsetPoint = getXLastMousePress() - xMiddle; 12553 this.deltaYToOffsetPoint = getYLastMousePress() - yMiddle; 12554 PlanView planView = getView(); 12555 planView.setResizeIndicatorVisible(true); 12556 } 12557 12558 @Override 12559 public void moveMouse(float x, float y) { 12560 float newX = x - this.deltaXToOffsetPoint; 12561 float newY = y - this.deltaYToOffsetPoint; 12562 12563 float distanceToDimensionLine = 12564 (float)Line2D.ptLineDist(this.selectedDimensionLine.getXStart(), this.selectedDimensionLine.getYStart(), 12565 this.selectedDimensionLine.getXEnd(), this.selectedDimensionLine.getYEnd(), newX, newY); 12566 int relativeCCW = Line2D.relativeCCW(this.selectedDimensionLine.getXStart(), this.selectedDimensionLine.getYStart(), 12567 this.selectedDimensionLine.getXEnd(), this.selectedDimensionLine.getYEnd(), newX, newY); 12568 this.selectedDimensionLine.setOffset( 12569 -Math.signum(relativeCCW) * distanceToDimensionLine); 12570 12571 // Ensure point at (x,y) is visible 12572 getView().makePointVisible(x, y); 12573 } 12574 12575 @Override 12576 public void releaseMouse(float x, float y) { 12577 postDimensionLineOffset(this.selectedDimensionLine, this.oldOffset); 12578 setState(getSelectionState()); 12579 } 12580 12581 @Override 12582 public void escape() { 12583 this.selectedDimensionLine.setOffset(this.oldOffset); 12584 setState(getSelectionState()); 12585 } 12586 12587 @Override 12588 public void exit() { 12589 getView().setResizeIndicatorVisible(false); 12590 this.selectedDimensionLine = null; 12591 } 12592 } 12593 12594 /** 12595 * Room creation state. This state manages transition to 12596 * other modes, and initial room creation. 12597 */ 12598 private class RoomCreationState extends AbstractModeChangeState { 12599 private boolean magnetismEnabled; 12600 12601 @Override 12602 public Mode getMode() { 12603 return Mode.ROOM_CREATION; 12604 } 12605 12606 @Override 12607 public void enter() { 12608 getView().setCursor(PlanView.CursorType.DRAW); 12609 toggleMagnetism(wasMagnetismToggledLastMousePress()); 12610 } 12611 12612 @Override 12613 public void moveMouse(float x, float y) { 12614 if (this.magnetismEnabled) { 12615 // Find the closest wall or room point to current mouse location 12616 PointMagnetizedToClosestWallOrRoomPoint point = new PointMagnetizedToClosestWallOrRoomPoint(x, y); 12617 if (point.isMagnetized()) { 12618 getView().setAlignmentFeedback(Room.class, null, point.getX(), 12619 point.getY(), point.isMagnetized()); 12620 } else { 12621 RoomPointWithAngleMagnetism pointWithAngleMagnetism = new RoomPointWithAngleMagnetism(null, -1, 12622 getXLastMouseMove(), getYLastMouseMove(), getXLastMouseMove(), getYLastMouseMove()); 12623 getView().setAlignmentFeedback(Room.class, null, pointWithAngleMagnetism.getX(), 12624 pointWithAngleMagnetism.getY(), point.isMagnetized()); 12625 } 12626 } else { 12627 getView().setAlignmentFeedback(Room.class, null, x, y, false); 12628 } 12629 } 12630 12631 @Override 12632 public void pressMouse(float x, float y, int clickCount, 12633 boolean shiftDown, boolean duplicationActivated) { 12634 // Change state to RoomDrawingState 12635 setState(getRoomDrawingState()); 12636 } 12637 12638 @Override 12639 public void setEditionActivated(boolean editionActivated) { 12640 if (editionActivated) { 12641 setState(getRoomDrawingState()); 12642 PlanController.this.setEditionActivated(editionActivated); 12643 } 12644 } 12645 12646 @Override 12647 public void toggleMagnetism(boolean magnetismToggled) { 12648 // Compute active magnetism 12649 this.magnetismEnabled = preferences.isMagnetismEnabled() 12650 ^ magnetismToggled; 12651 if (getPointerTypeLastMousePress() != View.PointerType.TOUCH) { 12652 // Compute again feedback point as if mouse moved 12653 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 12654 } 12655 } 12656 12657 @Override 12658 public void exit() { 12659 getView().deleteFeedback(); 12660 } 12661 } 12662 12663 /** 12664 * Room modification state. 12665 */ 12666 private abstract class AbstractRoomState extends ControllerState { 12667 private String roomSideLengthToolTipFeedback; 12668 private String roomSideAngleToolTipFeedback; 12669 12670 @Override 12671 public void enter() { 12672 this.roomSideLengthToolTipFeedback = preferences.getLocalizedString( 12673 PlanController.class, "roomSideLengthToolTipFeedback"); 12674 try { 12675 this.roomSideAngleToolTipFeedback = preferences.getLocalizedString( 12676 PlanController.class, "roomSideAngleToolTipFeedback"); 12677 } catch (IllegalArgumentException ex) { 12678 // This tool tip part is optional 12679 } 12680 } 12681 12682 protected String getToolTipFeedbackText(Room room, int pointIndex) { 12683 float length = getRoomSideLength(room, pointIndex); 12684 int angle = getRoomSideAngle(room, pointIndex); 12685 String toolTipFeedbackText = "<html>" + String.format(this.roomSideLengthToolTipFeedback, preferences.getLengthUnit().getFormatWithUnit().format(length)); 12686 if (this.roomSideAngleToolTipFeedback != null 12687 && this.roomSideAngleToolTipFeedback.length() > 0) { 12688 toolTipFeedbackText += "<br>" + String.format(this.roomSideAngleToolTipFeedback, angle); 12689 } 12690 return toolTipFeedbackText; 12691 } 12692 12693 protected float getRoomSideLength(Room room, int pointIndex) { 12694 float [][] points = room.getPoints(); 12695 float [] previousPoint = points [(pointIndex + points.length - 1) % points.length]; 12696 return (float)Point2D.distance(previousPoint [0], previousPoint [1], 12697 points [pointIndex][0], points [pointIndex][1]); 12698 } 12699 12700 protected List<DimensionLine> getTriangulationDimensionLines(Room room, int pointIndex) { 12701 float [][] points = room.getPoints(); 12702 float [] previousPoint = points [(pointIndex + points.length - 1) % points.length]; 12703 List<DimensionLine> dimensionLines = new ArrayList<DimensionLine>(3); 12704 float offset = 20 / getView().getScale(); 12705 if (!Arrays.equals(points [pointIndex], previousPoint)) { 12706 dimensionLines.add(getDimensionLineBetweenPoints(previousPoint, points [pointIndex], 12707 isDimensionInsideRoom(room, previousPoint, points [pointIndex]) ? -offset : offset, false)); 12708 } 12709 if (points.length > 2) { 12710 float [] nextPoint = points [pointIndex + 1 < points.length ? pointIndex + 1 : 0]; 12711 if (!Arrays.equals(points [pointIndex], nextPoint)) { 12712 dimensionLines.add(getDimensionLineBetweenPoints(points [pointIndex], nextPoint, 12713 isDimensionInsideRoom(room, points [pointIndex], nextPoint) ? -offset : offset, false)); 12714 } 12715 float [] previousPreviousPoint = points [(pointIndex + points.length - 2) % points.length]; 12716 if (points.length == 3) { 12717 dimensionLines.add(getDimensionLineBetweenPoints(previousPoint, previousPreviousPoint, 12718 isDimensionInsideRoom(room, previousPoint, previousPreviousPoint) ? -offset : offset, false)); 12719 } else if (!Arrays.equals(points [pointIndex], previousPreviousPoint)) { 12720 dimensionLines.add(getDimensionLineBetweenPoints(points [pointIndex], previousPreviousPoint, 0, false)); 12721 } 12722 } 12723 return dimensionLines; 12724 } 12725 12726 private boolean isDimensionInsideRoom(Room room, float [] point1, float [] point2) { 12727 // Search the coordinates of the point where the dimension will be displayed 12728 AffineTransform rotation = AffineTransform.getRotateInstance( 12729 Math.atan2(point2 [1] - point1 [1], point2 [0] - point1 [0]), point1 [0], point1 [1]); 12730 float [] dimensionPoint = {(float)(point1 [0] + Point2D.distance(point1 [0], point1 [1], point2 [0], point2 [1]) / 2), point1 [1] + 1f}; 12731 rotation.transform(dimensionPoint, 0, dimensionPoint, 0, 1); 12732 return room.containsPoint(dimensionPoint [0], dimensionPoint [1], 0); 12733 } 12734 12735 /** 12736 * Returns room side angle at the given point index in degrees. 12737 */ 12738 protected Integer getRoomSideAngle(Room room, int pointIndex) { 12739 float [][] points = room.getPoints(); 12740 float [] point = points [pointIndex]; 12741 float [] previousPoint = points [(pointIndex + points.length - 1) % points.length]; 12742 float [] previousPreviousPoint = points [(pointIndex + points.length - 2) % points.length]; 12743 float sideLength = (float)Point2D.distance( 12744 previousPoint [0], previousPoint [1], 12745 points [pointIndex][0], points [pointIndex][1]); 12746 float previousSideLength = (float)Point2D.distance( 12747 previousPreviousPoint [0], previousPreviousPoint [1], 12748 previousPoint [0], previousPoint [1]); 12749 if (previousPreviousPoint != point 12750 && sideLength != 0 && previousSideLength != 0) { 12751 // Compute the angle between the side finishing at pointIndex 12752 // and the previous side 12753 float xSideVector = (point [0] - previousPoint [0]) / sideLength; 12754 float ySideVector = (point [1] - previousPoint [1]) / sideLength; 12755 float xPreviousSideVector = (previousPoint [0] - previousPreviousPoint [0]) / previousSideLength; 12756 float yPreviousSideVector = (previousPoint [1] - previousPreviousPoint [1]) / previousSideLength; 12757 int sideAngle = (int)Math.round(180 - Math.toDegrees(Math.atan2( 12758 ySideVector * xPreviousSideVector - xSideVector * yPreviousSideVector, 12759 xSideVector * xPreviousSideVector + ySideVector * yPreviousSideVector))); 12760 if (sideAngle > 180) { 12761 sideAngle -= 360; 12762 } 12763 return sideAngle; 12764 } 12765 if (sideLength == 0) { 12766 return 0; 12767 } else { 12768 return (int)Math.round(Math.toDegrees(Math.atan2( 12769 previousPoint [1] - point [1], 12770 point [0] - previousPoint [0]))); 12771 } 12772 } 12773 12774 protected void showRoomAngleFeedback(Room room, int pointIndex) { 12775 float [][] points = room.getPoints(); 12776 if (this.roomSideAngleToolTipFeedback != null 12777 && this.roomSideAngleToolTipFeedback.length() > 0 12778 && points.length > 2) { 12779 float [] previousPoint = points [(pointIndex + points.length - 1) % points.length]; 12780 float [] previousPreviousPoint = points [(pointIndex + points.length - 2) % points.length]; 12781 if (getRoomSideAngle(room, pointIndex) > 0) { 12782 getView().setAngleFeedback(previousPoint [0], previousPoint [1], 12783 previousPreviousPoint [0], previousPreviousPoint [1], 12784 points [pointIndex][0], points [pointIndex][1]); 12785 } else { 12786 getView().setAngleFeedback(previousPoint [0], previousPoint [1], 12787 points [pointIndex][0], points [pointIndex][1], 12788 previousPreviousPoint [0], previousPreviousPoint [1]); 12789 } 12790 } 12791 } 12792 } 12793 12794 /** 12795 * Room drawing state. This state manages room creation at mouse press. 12796 */ 12797 private class RoomDrawingState extends AbstractRoomState { 12798 private float xPreviousPoint; 12799 private float yPreviousPoint; 12800 private Room newRoom; 12801 private float [] newPoint; 12802 private List<Selectable> oldSelection; 12803 private boolean oldBasePlanLocked; 12804 private boolean oldAllLevelsSelection; 12805 private boolean magnetismEnabled; 12806 private boolean alignmentActivated; 12807 private long lastPointCreationTime; 12808 12809 @Override 12810 public Mode getMode() { 12811 return Mode.ROOM_CREATION; 12812 } 12813 12814 @Override 12815 public boolean isModificationState() { 12816 return true; 12817 } 12818 12819 @Override 12820 public boolean isBasePlanModificationState() { 12821 return true; 12822 } 12823 12824 @Override 12825 public void setMode(Mode mode) { 12826 // Escape current creation and change state to matching mode 12827 escape(); 12828 if (mode == Mode.SELECTION) { 12829 setState(getSelectionState()); 12830 } else if (mode == Mode.PANNING) { 12831 setState(getPanningState()); 12832 } else if (mode == Mode.WALL_CREATION) { 12833 setState(getWallCreationState()); 12834 } else if (mode == Mode.POLYLINE_CREATION) { 12835 setState(getPolylineCreationState()); 12836 } else if (mode == Mode.DIMENSION_LINE_CREATION) { 12837 setState(getDimensionLineCreationState()); 12838 } else if (mode == Mode.LABEL_CREATION) { 12839 setState(getLabelCreationState()); 12840 } 12841 } 12842 12843 @Override 12844 public void enter() { 12845 super.enter(); 12846 this.oldSelection = home.getSelectedItems(); 12847 this.oldBasePlanLocked = home.isBasePlanLocked(); 12848 this.oldAllLevelsSelection = home.isAllLevelsSelection(); 12849 this.newRoom = null; 12850 this.alignmentActivated = wasAlignmentActivatedLastMousePress(); 12851 toggleMagnetism(wasMagnetismToggledLastMousePress()); 12852 if (this.magnetismEnabled) { 12853 // Find the closest wall or room point to current mouse location 12854 PointMagnetizedToClosestWallOrRoomPoint point = new PointMagnetizedToClosestWallOrRoomPoint( 12855 getXLastMouseMove(), getYLastMouseMove()); 12856 if (point.isMagnetized()) { 12857 this.xPreviousPoint = point.getX(); 12858 this.yPreviousPoint = point.getY(); 12859 } else { 12860 RoomPointWithAngleMagnetism pointWithAngleMagnetism = new RoomPointWithAngleMagnetism(null, -1, 12861 getXLastMouseMove(), getYLastMouseMove(), getXLastMouseMove(), getYLastMouseMove()); 12862 this.xPreviousPoint = pointWithAngleMagnetism.getX(); 12863 this.yPreviousPoint = pointWithAngleMagnetism.getY(); 12864 } 12865 getView().setAlignmentFeedback(Room.class, null, 12866 this.xPreviousPoint, this.yPreviousPoint, point.isMagnetized()); 12867 } else { 12868 this.xPreviousPoint = getXLastMousePress(); 12869 this.yPreviousPoint = getYLastMousePress(); 12870 getView().setAlignmentFeedback(Room.class, null, 12871 this.xPreviousPoint, this.yPreviousPoint, false); 12872 } 12873 deselectAll(); 12874 } 12875 12876 @Override 12877 public void moveMouse(float x, float y) { 12878 PlanView planView = getView(); 12879 // Compute the coordinates where current edit room point should be moved 12880 float xEnd = x; 12881 float yEnd = y; 12882 boolean magnetizedPoint = false; 12883 if (this.alignmentActivated) { 12884 PointWithAngleMagnetism pointWithAngleMagnetism = new PointWithAngleMagnetism( 12885 this.xPreviousPoint, this.yPreviousPoint, x, y, preferences.getLengthUnit(), planView.getPixelLength()); 12886 xEnd = pointWithAngleMagnetism.getX(); 12887 yEnd = pointWithAngleMagnetism.getY(); 12888 } else if (this.magnetismEnabled) { 12889 // Find the closest wall or room point to current mouse location 12890 PointMagnetizedToClosestWallOrRoomPoint point = this.newRoom != null 12891 ? new PointMagnetizedToClosestWallOrRoomPoint(this.newRoom, this.newRoom.getPointCount() - 1, x, y) 12892 : new PointMagnetizedToClosestWallOrRoomPoint(x, y); 12893 magnetizedPoint = point.isMagnetized(); 12894 if (magnetizedPoint) { 12895 xEnd = point.getX(); 12896 yEnd = point.getY(); 12897 } else { 12898 // Use magnetism if closest wall point is too far 12899 int editedPointIndex = this.newRoom != null 12900 ? this.newRoom.getPointCount() - 1 12901 : -1; 12902 RoomPointWithAngleMagnetism pointWithAngleMagnetism = new RoomPointWithAngleMagnetism( 12903 this.newRoom, editedPointIndex, this.xPreviousPoint, this.yPreviousPoint, x, y); 12904 xEnd = pointWithAngleMagnetism.getX(); 12905 yEnd = pointWithAngleMagnetism.getY(); 12906 } 12907 } 12908 12909 // If current room doesn't exist 12910 if (this.newRoom == null) { 12911 // Create a new one 12912 this.newRoom = createAndSelectRoom(this.xPreviousPoint, this.yPreviousPoint, xEnd, yEnd); 12913 } else if (this.newPoint != null) { 12914 // Add a point to current room 12915 float [][] points = this.newRoom.getPoints(); 12916 this.xPreviousPoint = points [points.length - 1][0]; 12917 this.yPreviousPoint = points [points.length - 1][1]; 12918 this.newRoom.addPoint(xEnd, yEnd); 12919 this.newPoint = null; 12920 } else { 12921 // Otherwise update its last point 12922 this.newRoom.setPoint(xEnd, yEnd, this.newRoom.getPointCount() - 1); 12923 } 12924 planView.setToolTipFeedback( 12925 getToolTipFeedbackText(this.newRoom, this.newRoom.getPointCount() - 1), x, y); 12926 planView.setAlignmentFeedback(Room.class, this.newRoom, 12927 xEnd, yEnd, magnetizedPoint); 12928 showRoomAngleFeedback(this.newRoom, this.newRoom.getPointCount() - 1); 12929 planView.setDimensionLinesFeedback(getTriangulationDimensionLines(this.newRoom, this.newRoom.getPointCount() - 1)); 12930 12931 // Ensure point at (x,y) is visible 12932 planView.makePointVisible(x, y); 12933 } 12934 12935 /** 12936 * Returns a new room instance with one side between (<code>xStart</code>, 12937 * <code>yStart</code>) and (<code>xEnd</code>, <code>yEnd</code>) points. 12938 * The new room is added to home and selected 12939 */ 12940 private Room createAndSelectRoom(float xStart, float yStart, 12941 float xEnd, float yEnd) { 12942 Room newRoom = createRoom(new float [][] {{xStart, yStart}, {xEnd, yEnd}}); 12943 // Let's consider that points outside of home will create by default a room with no ceiling 12944 Area insideWallsArea = getInsideWallsArea(); 12945 newRoom.setCeilingVisible(insideWallsArea.contains(xStart, yStart)); 12946 selectItem(newRoom); 12947 return newRoom; 12948 } 12949 12950 @Override 12951 public void pressMouse(float x, float y, int clickCount, 12952 boolean shiftDown, boolean duplicationActivated) { 12953 if (clickCount == 2) { 12954 if (this.newRoom == null) { 12955 // Try to guess the room that contains the point (x,y) 12956 this.newRoom = createRoomAt(x, y); 12957 if (this.newRoom != null) { 12958 selectItem(this.newRoom); 12959 } 12960 } 12961 validateDrawnRoom(); 12962 } else { 12963 endRoomSide(); 12964 } 12965 } 12966 12967 private void validateDrawnRoom() { 12968 if (this.newRoom != null) { 12969 float [][] points = this.newRoom.getPoints(); 12970 if (points.length < 3) { 12971 // Delete current created room if it doesn't have more than 2 clicked points 12972 home.deleteRoom(this.newRoom); 12973 } else { 12974 // Post room creation to undo support 12975 postCreateRooms(Arrays.asList(new Room [] {this.newRoom}), 12976 this.oldSelection, this.oldBasePlanLocked, this.oldAllLevelsSelection); 12977 } 12978 } 12979 if (getPointerTypeLastMousePress() == View.PointerType.TOUCH) { 12980 setState(getSelectionState()); 12981 } else { 12982 // Change state to RoomCreationState 12983 setState(getRoomCreationState()); 12984 } 12985 } 12986 12987 private void endRoomSide() { 12988 // Create a new room side only when its length is greater than zero 12989 if (this.newRoom != null 12990 && getRoomSideLength(this.newRoom, this.newRoom.getPointCount() - 1) > 0) { 12991 this.newPoint = new float [2]; 12992 // Let's consider that any point outside of home will create 12993 // by default a room with no ceiling 12994 if (this.newRoom.isCeilingVisible()) { 12995 float [][] roomPoints = this.newRoom.getPoints(); 12996 float [] lastPoint = roomPoints [roomPoints.length - 1]; 12997 if (!getInsideWallsArea().contains(lastPoint [0], lastPoint [1])) { 12998 this.newRoom.setCeilingVisible(false); 12999 } 13000 } 13001 } 13002 } 13003 13004 /** 13005 * Returns the room matching the closed path that contains the point at the given 13006 * coordinates or <code>null</code> if there's no closed path at this point. 13007 */ 13008 private Room createRoomAt(float x, float y) { 13009 for (GeneralPath roomPath : getRoomPathsFromWalls()) { 13010 if (roomPath.contains(x, y)) { 13011 // Add to roomPath the doorstep between the room border and the middle of the doors and windows 13012 // with an elevation equal to zero that intersects with roomPath 13013 for (HomePieceOfFurniture piece : getVisibleDoorsAndWindowsAtGround(home.getFurniture())) { 13014 float [][] doorPoints = piece.getPoints(); 13015 int intersectionCount = 0; 13016 for (int i = 0; i < doorPoints.length; i++) { 13017 if (roomPath.contains(doorPoints [i][0], doorPoints [i][1])) { 13018 intersectionCount++; 13019 } 13020 } 13021 if (doorPoints.length == 4) { 13022 float epsilon = 0.05f; 13023 float [][] doorStepPoints = null; 13024 if (piece instanceof HomeDoorOrWindow 13025 && ((HomeDoorOrWindow)piece).isWallCutOutOnBothSides()) { 13026 HomeDoorOrWindow door = (HomeDoorOrWindow)piece; 13027 Level selectedLevel = home.getSelectedLevel(); 13028 Area doorArea = new Area(getPath(doorPoints)); 13029 Area wallsDoorIntersection = new Area(); 13030 for (Wall wall : home.getWalls()) { 13031 if (wall.isAtLevel(selectedLevel) 13032 && door.isParallelToWall(wall)) { 13033 GeneralPath wallPath = getPath(wall.getPoints()); 13034 Area intersectionArea = new Area(wallPath); 13035 intersectionArea.intersect(doorArea); 13036 if (!intersectionArea.isEmpty()) { 13037 HomePieceOfFurniture deeperDoor = door.clone(); 13038 // Increase door depth to ensure the wall will be cut on both sides 13039 // (doors and windows can't be rotated around horizontal axes) 13040 deeperDoor.setDepthInPlan(deeperDoor.getDepth() + 4 * wall.getThickness()); 13041 intersectionArea = new Area(wallPath); 13042 intersectionArea.intersect(new Area(getPath(deeperDoor.getPoints()))); 13043 wallsDoorIntersection.add(intersectionArea); 13044 } 13045 } 13046 } 13047 if (!wallsDoorIntersection.isEmpty() 13048 && wallsDoorIntersection.isSingular()) { 13049 float [][] intersectionPoints = getPathPoints(getPath(wallsDoorIntersection), true); 13050 if (intersectionPoints.length == 4) { 13051 // Compute the location of door points at the middle of its wall part 13052 float doorMiddleY = door.getY() 13053 + door.getDepth() * (-0.5f + door.getWallDistance() + door.getWallThickness() / 2); 13054 float halfWidth = door.getWidth() / 2; 13055 float [] doorMiddlePoints = {door.getX() - halfWidth, doorMiddleY, 13056 door.getX() + halfWidth, doorMiddleY}; 13057 AffineTransform rotation = AffineTransform.getRotateInstance( 13058 door.getAngle(), door.getX(), door.getY()); 13059 rotation.transform(doorMiddlePoints, 0, doorMiddlePoints, 0, 2); 13060 13061 for (int i = 0; i < intersectionPoints.length - 1; i++) { 13062 // Check point in room with rectangle intersection test otherwise we miss some points 13063 if (roomPath.intersects(intersectionPoints [i][0] - epsilon / 2, 13064 intersectionPoints [i][1] - epsilon / 2, epsilon, epsilon)) { 13065 int inPoint1 = i; 13066 int outPoint1; 13067 int outPoint2; 13068 if (roomPath.intersects(intersectionPoints [i + 1][0] - epsilon / 2, 13069 intersectionPoints [i + 1][1] - epsilon / 2, epsilon, epsilon)) { 13070 outPoint2 = (i + 2) % 4; 13071 outPoint1 = (i + 3) % 4; 13072 } else if (roomPath.intersects(intersectionPoints [(i + 3) % 4][0] - epsilon / 2, 13073 intersectionPoints [(i + 3) % 4][1] - epsilon / 2, epsilon, epsilon)) { 13074 outPoint1 = (i + 1) % 4; 13075 outPoint2 = (i + 2) % 4; 13076 } else { 13077 // May happen if door intersects room path at only one point when door is larger that room side 13078 break; 13079 } 13080 if (Point2D.distanceSq(intersectionPoints [inPoint1][0], intersectionPoints [inPoint1][1], 13081 doorMiddlePoints [0], doorMiddlePoints [1]) 13082 < Point2D.distanceSq(intersectionPoints [inPoint1][0], intersectionPoints [inPoint1][1], 13083 doorMiddlePoints [2], doorMiddlePoints [3])) { 13084 intersectionPoints [outPoint1][0] = doorMiddlePoints [0]; 13085 intersectionPoints [outPoint1][1] = doorMiddlePoints [1]; 13086 intersectionPoints [outPoint2][0] = doorMiddlePoints [2]; 13087 intersectionPoints [outPoint2][1] = doorMiddlePoints [3]; 13088 } else { 13089 intersectionPoints [outPoint1][0] = doorMiddlePoints [2]; 13090 intersectionPoints [outPoint1][1] = doorMiddlePoints [3]; 13091 intersectionPoints [outPoint2][0] = doorMiddlePoints [0]; 13092 intersectionPoints [outPoint2][1] = doorMiddlePoints [1]; 13093 } 13094 13095 doorStepPoints = intersectionPoints; 13096 break; 13097 } 13098 } 13099 } 13100 } 13101 } 13102 if (doorStepPoints == null 13103 && intersectionCount == 2) { 13104 // Find the intersection of the door with home walls 13105 Area wallsDoorIntersection = new Area(getWallsArea(false)); 13106 wallsDoorIntersection.intersect(new Area(getPath(doorPoints))); 13107 // Reduce the size of intersection to its half 13108 float [][] intersectionPoints = getPathPoints(getPath(wallsDoorIntersection), false); 13109 if (intersectionPoints.length == 4) { 13110 for (int i = 0; i < intersectionPoints.length; i++) { 13111 // Check point in room with rectangle intersection test otherwise we miss some points 13112 if (roomPath.intersects(intersectionPoints [i][0] - epsilon / 2, 13113 intersectionPoints [i][1] - epsilon / 2, epsilon, epsilon)) { 13114 int inPoint1 = i; 13115 int inPoint2; 13116 int outPoint1; 13117 int outPoint2; 13118 if (roomPath.intersects(intersectionPoints [i + 1][0] - epsilon / 2, 13119 intersectionPoints [i + 1][1] - epsilon / 2, epsilon, epsilon)) { 13120 inPoint2 = i + 1; 13121 outPoint2 = (i + 2) % 4; 13122 outPoint1 = (i + 3) % 4; 13123 } else { 13124 outPoint1 = (i + 1) % 4; 13125 outPoint2 = (i + 2) % 4; 13126 inPoint2 = (i + 3) % 4; 13127 } 13128 intersectionPoints [outPoint1][0] = (intersectionPoints [outPoint1][0] 13129 + intersectionPoints [inPoint1][0]) / 2; 13130 intersectionPoints [outPoint1][1] = (intersectionPoints [outPoint1][1] 13131 + intersectionPoints [inPoint1][1]) / 2; 13132 intersectionPoints [outPoint2][0] = (intersectionPoints [outPoint2][0] 13133 + intersectionPoints [inPoint2][0]) / 2; 13134 intersectionPoints [outPoint2][1] = (intersectionPoints [outPoint2][1] 13135 + intersectionPoints [inPoint2][1]) / 2; 13136 13137 doorStepPoints = intersectionPoints; 13138 break; 13139 } 13140 } 13141 } 13142 } 13143 13144 if (doorStepPoints != null) { 13145 GeneralPath path = getPath(doorStepPoints); 13146 // Enlarge the intersection path to ensure its union with room builds only one path 13147 Rectangle2D bounds2D = path.getBounds2D(); 13148 AffineTransform transform = AffineTransform.getTranslateInstance(bounds2D.getCenterX(), bounds2D.getCenterY()); 13149 double min = Math.min(bounds2D.getWidth(), bounds2D.getHeight()); 13150 double scale = (min + epsilon) / min; 13151 transform.scale(scale, scale); 13152 transform.translate(-bounds2D.getCenterX(), -bounds2D.getCenterY()); 13153 Shape doorStepPath = path.createTransformedShape(transform); 13154 Area halfDoorRoomUnion = new Area(doorStepPath); 13155 halfDoorRoomUnion.add(new Area(roomPath)); 13156 roomPath = getPath(halfDoorRoomUnion); 13157 } 13158 } 13159 } 13160 13161 return createRoom(getPathPoints(roomPath, false)); 13162 } 13163 } 13164 return null; 13165 } 13166 13167 /** 13168 * Returns all the visible doors and windows with a null elevation in the given <code>furniture</code>. 13169 */ 13170 private List<HomePieceOfFurniture> getVisibleDoorsAndWindowsAtGround(List<HomePieceOfFurniture> furniture) { 13171 List<HomePieceOfFurniture> doorsAndWindows = new ArrayList<HomePieceOfFurniture>(furniture.size()); 13172 for (HomePieceOfFurniture piece : furniture) { 13173 if (isPieceOfFurnitureVisibleAtSelectedLevel(piece) 13174 && piece.getElevation() == 0) { 13175 if (piece instanceof HomeFurnitureGroup) { 13176 doorsAndWindows.addAll(getVisibleDoorsAndWindowsAtGround(((HomeFurnitureGroup)piece).getFurniture())); 13177 } else if (piece.isDoorOrWindow()) { 13178 doorsAndWindows.add(piece); 13179 } 13180 } 13181 } 13182 return doorsAndWindows; 13183 } 13184 13185 @Override 13186 public void setEditionActivated(boolean editionActivated) { 13187 PlanView planView = getView(); 13188 if (editionActivated) { 13189 planView.deleteFeedback(); 13190 if (this.newRoom == null) { 13191 // Edit previous point 13192 planView.setToolTipEditedProperties(new EditableProperty [] {EditableProperty.X, 13193 EditableProperty.Y}, 13194 new Object [] {this.xPreviousPoint, this.yPreviousPoint}, 13195 this.xPreviousPoint, this.yPreviousPoint); 13196 } else { 13197 if (this.newPoint != null) { 13198 // May happen if edition is activated after the user clicked to add a new point 13199 createNextSide(); 13200 } 13201 // Edit length and angle 13202 float [][] points = this.newRoom.getPoints(); 13203 planView.setToolTipEditedProperties(new EditableProperty [] {EditableProperty.LENGTH, 13204 EditableProperty.ANGLE}, 13205 new Object [] {getRoomSideLength(this.newRoom, points.length - 1), 13206 getRoomSideAngle(this.newRoom, points.length - 1)}, 13207 points [points.length - 1][0], points [points.length - 1][1]); 13208 } 13209 } else { 13210 if (this.newRoom == null) { 13211 // Create a new side once user entered the start point of the room 13212 LengthUnit lengthUnit = preferences.getLengthUnit(); 13213 float defaultLength = lengthUnit == LengthUnit.INCH || lengthUnit == LengthUnit.INCH_DECIMALS 13214 ? LengthUnit.footToCentimeter(10) : 300; 13215 this.newRoom = createAndSelectRoom(this.xPreviousPoint, this.yPreviousPoint, 13216 this.xPreviousPoint + defaultLength, this.yPreviousPoint); 13217 // Activate automatically second step to let user enter the 13218 // length and angle of the new side 13219 planView.deleteFeedback(); 13220 setEditionActivated(true); 13221 } else if (System.currentTimeMillis() - this.lastPointCreationTime < 300) { 13222 // If the user deactivated edition less than 300 ms after activation, 13223 // escape current side creation 13224 escape(); 13225 } else { 13226 endRoomSide(); 13227 float [][] points = this.newRoom.getPoints(); 13228 // If last edited point matches first point validate drawn room 13229 if (points.length > 2 13230 && this.newRoom.getPointIndexAt(points [points.length - 1][0], points [points.length - 1][1], 0.001f) == 0) { 13231 // Remove last currently edited point. 13232 this.newRoom.removePoint(this.newRoom.getPointCount() - 1); 13233 validateDrawnRoom(); 13234 return; 13235 } 13236 createNextSide(); 13237 // Reactivate automatically second step 13238 planView.deleteToolTipFeedback(); 13239 setEditionActivated(true); 13240 } 13241 } 13242 } 13243 13244 private void createNextSide() { 13245 // Add a point to current room 13246 float [][] points = this.newRoom.getPoints(); 13247 this.xPreviousPoint = points [points.length - 1][0]; 13248 this.yPreviousPoint = points [points.length - 1][1]; 13249 // Create a new side with an angle equal to previous side angle - 90� 13250 double previousSideAngle = Math.PI - Math.atan2(points [points.length - 2][1] - points [points.length - 1][1], 13251 points [points.length - 2][0] - points [points.length - 1][0]); 13252 previousSideAngle -= Math.PI / 2; 13253 float previousSideLength = getRoomSideLength(this.newRoom, points.length - 1); 13254 this.newRoom.addPoint( 13255 (float)(this.xPreviousPoint + previousSideLength * Math.cos(previousSideAngle)), 13256 (float)(this.yPreviousPoint - previousSideLength * Math.sin(previousSideAngle))); 13257 this.newPoint = null; 13258 this.lastPointCreationTime = System.currentTimeMillis(); 13259 } 13260 13261 @Override 13262 public void updateEditableProperty(EditableProperty editableProperty, Object value) { 13263 PlanView planView = getView(); 13264 if (this.newRoom == null) { 13265 float maximumLength = preferences.getLengthUnit().getMaximumLength(); 13266 // Update start point of the first wall 13267 switch (editableProperty) { 13268 case X : 13269 this.xPreviousPoint = value != null ? ((Number)value).floatValue() : 0; 13270 this.xPreviousPoint = Math.max(-maximumLength, Math.min(this.xPreviousPoint, maximumLength)); 13271 break; 13272 case Y : 13273 this.yPreviousPoint = value != null ? ((Number)value).floatValue() : 0; 13274 this.yPreviousPoint = Math.max(-maximumLength, Math.min(this.yPreviousPoint, maximumLength)); 13275 break; 13276 } 13277 planView.setAlignmentFeedback(Room.class, null, this.xPreviousPoint, this.yPreviousPoint, true); 13278 planView.makePointVisible(this.xPreviousPoint, this.yPreviousPoint); 13279 } else { 13280 float [][] roomPoints = this.newRoom.getPoints(); 13281 float [] previousPoint = roomPoints [roomPoints.length - 2]; 13282 float [] point = roomPoints [roomPoints.length - 1]; 13283 float newX; 13284 float newY; 13285 // Update end point of the current room 13286 switch (editableProperty) { 13287 case LENGTH : 13288 float length = value != null ? ((Number)value).floatValue() : 0; 13289 length = Math.max(0.001f, Math.min(length, preferences.getLengthUnit().getMaximumLength())); 13290 double sideAngle = Math.PI - Math.atan2(previousPoint [1] - point [1], 13291 previousPoint [0] - point [0]); 13292 newX = (float)(previousPoint [0] + length * Math.cos(sideAngle)); 13293 newY = (float)(previousPoint [1] - length * Math.sin(sideAngle)); 13294 break; 13295 case ANGLE : 13296 sideAngle = Math.toRadians(value != null ? ((Number)value).floatValue() : 0); 13297 if (roomPoints.length > 2) { 13298 sideAngle -= Math.atan2(roomPoints [roomPoints.length - 3][1] - previousPoint [1], 13299 roomPoints [roomPoints.length - 3][0] - previousPoint [0]); 13300 } 13301 float sideLength = getRoomSideLength(this.newRoom, roomPoints.length - 1); 13302 newX = (float)(previousPoint [0] + sideLength * Math.cos(sideAngle)); 13303 newY = (float)(previousPoint [1] - sideLength * Math.sin(sideAngle)); 13304 break; 13305 default : 13306 return; 13307 } 13308 this.newRoom.setPoint(newX, newY, roomPoints.length - 1); 13309 13310 // Update new room 13311 planView.setAlignmentFeedback(Room.class, this.newRoom, newX, newY, false); 13312 showRoomAngleFeedback(this.newRoom, roomPoints.length - 1); 13313 planView.setDimensionLinesFeedback(getTriangulationDimensionLines(this.newRoom, roomPoints.length - 1)); 13314 // Ensure room side points are visible 13315 planView.makePointVisible(previousPoint [0], previousPoint [1]); 13316 planView.makePointVisible(newX, newY); 13317 } 13318 } 13319 13320 @Override 13321 public void toggleMagnetism(boolean magnetismToggled) { 13322 // Compute active magnetism 13323 this.magnetismEnabled = preferences.isMagnetismEnabled() 13324 ^ magnetismToggled; 13325 // If the new room already exists, 13326 // compute again its last point as if mouse moved 13327 if (this.newRoom != null) { 13328 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 13329 } 13330 } 13331 13332 @Override 13333 public void setAlignmentActivated(boolean alignmentActivated) { 13334 this.alignmentActivated = alignmentActivated; 13335 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 13336 } 13337 13338 @Override 13339 public void escape() { 13340 if (this.newRoom != null 13341 && this.newPoint == null) { 13342 // Remove last currently edited point. 13343 this.newRoom.removePoint(this.newRoom.getPointCount() - 1); 13344 } 13345 validateDrawnRoom(); 13346 } 13347 13348 @Override 13349 public void exit() { 13350 getView().deleteFeedback(); 13351 this.newRoom = null; 13352 this.newPoint = null; 13353 this.oldSelection = null; 13354 } 13355 } 13356 13357 /** 13358 * Room resize state. This state manages room resizing. 13359 */ 13360 private class RoomResizeState extends AbstractRoomState { 13361 private Room selectedRoom; 13362 private int roomPointIndex; 13363 private float oldX; 13364 private float oldY; 13365 private float deltaXToResizePoint; 13366 private float deltaYToResizePoint; 13367 private boolean magnetismEnabled; 13368 private boolean alignmentActivated; 13369 13370 @Override 13371 public Mode getMode() { 13372 return Mode.SELECTION; 13373 } 13374 13375 @Override 13376 public boolean isModificationState() { 13377 return true; 13378 } 13379 13380 @Override 13381 public boolean isBasePlanModificationState() { 13382 return true; 13383 } 13384 13385 @Override 13386 public void enter() { 13387 super.enter(); 13388 this.selectedRoom = (Room)home.getSelectedItems().get(0); 13389 float margin = getIndicatorMargin(); 13390 this.roomPointIndex = this.selectedRoom.getPointIndexAt( 13391 getXLastMousePress(), getYLastMousePress(), margin); 13392 float [][] roomPoints = this.selectedRoom.getPoints(); 13393 this.oldX = roomPoints [this.roomPointIndex][0]; 13394 this.oldY = roomPoints [this.roomPointIndex][1]; 13395 this.deltaXToResizePoint = getXLastMousePress() - this.oldX; 13396 this.deltaYToResizePoint = getYLastMousePress() - this.oldY; 13397 this.alignmentActivated = wasAlignmentActivatedLastMousePress(); 13398 toggleMagnetism(wasMagnetismToggledLastMousePress()); 13399 PlanView planView = getView(); 13400 planView.setResizeIndicatorVisible(true); 13401 planView.setToolTipFeedback(getToolTipFeedbackText(this.selectedRoom, this.roomPointIndex), 13402 getXLastMousePress(), getYLastMousePress()); 13403 showRoomAngleFeedback(this.selectedRoom, this.roomPointIndex); 13404 planView.setDimensionLinesFeedback(getTriangulationDimensionLines(this.selectedRoom, this.roomPointIndex)); 13405 } 13406 13407 @Override 13408 public void moveMouse(float x, float y) { 13409 PlanView planView = getView(); 13410 float newX = x - this.deltaXToResizePoint; 13411 float newY = y - this.deltaYToResizePoint; 13412 float [][] roomPoints = this.selectedRoom.getPoints(); 13413 int previousPointIndex = this.roomPointIndex == 0 13414 ? roomPoints.length - 1 13415 : this.roomPointIndex - 1; 13416 float xPreviousPoint = roomPoints [previousPointIndex][0]; 13417 float yPreviousPoint = roomPoints [previousPointIndex][1]; 13418 boolean magnetizedPoint = false; 13419 if (this.alignmentActivated) { 13420 PointWithAngleMagnetism pointWithAngleMagnetism = new PointWithAngleMagnetism( 13421 xPreviousPoint, yPreviousPoint, newX, newY, preferences.getLengthUnit(), planView.getPixelLength()); 13422 newX = pointWithAngleMagnetism.getX(); 13423 newY = pointWithAngleMagnetism.getY(); 13424 } else if (this.magnetismEnabled) { 13425 // Find the closest wall or room point to current mouse location 13426 PointMagnetizedToClosestWallOrRoomPoint point = new PointMagnetizedToClosestWallOrRoomPoint( 13427 this.selectedRoom, this.roomPointIndex, newX, newY); 13428 magnetizedPoint = point.isMagnetized(); 13429 if (magnetizedPoint) { 13430 newX = point.getX(); 13431 newY = point.getY(); 13432 } else { 13433 // Use magnetism if closest wall point is too far 13434 RoomPointWithAngleMagnetism pointWithAngleMagnetism = new RoomPointWithAngleMagnetism( 13435 this.selectedRoom, this.roomPointIndex, xPreviousPoint, yPreviousPoint, newX, newY); 13436 newX = pointWithAngleMagnetism.getX(); 13437 newY = pointWithAngleMagnetism.getY(); 13438 } 13439 } 13440 moveRoomPoint(this.selectedRoom, newX, newY, this.roomPointIndex); 13441 13442 planView.setToolTipFeedback(getToolTipFeedbackText(this.selectedRoom, this.roomPointIndex), x, y); 13443 planView.setAlignmentFeedback(Room.class, this.selectedRoom, newX, newY, magnetizedPoint); 13444 showRoomAngleFeedback(this.selectedRoom, this.roomPointIndex); 13445 planView.setDimensionLinesFeedback(getTriangulationDimensionLines(this.selectedRoom, this.roomPointIndex)); 13446 // Ensure point at (x,y) is visible 13447 planView.makePointVisible(x, y); 13448 } 13449 13450 @Override 13451 public void releaseMouse(float x, float y) { 13452 postRoomResize(this.selectedRoom, this.oldX, this.oldY, this.roomPointIndex); 13453 setState(getSelectionState()); 13454 } 13455 13456 @Override 13457 public void toggleMagnetism(boolean magnetismToggled) { 13458 // Compute active magnetism 13459 this.magnetismEnabled = preferences.isMagnetismEnabled() 13460 ^ magnetismToggled; 13461 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 13462 } 13463 13464 @Override 13465 public void setAlignmentActivated(boolean alignmentActivated) { 13466 this.alignmentActivated = alignmentActivated; 13467 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 13468 } 13469 13470 @Override 13471 public void escape() { 13472 moveRoomPoint(this.selectedRoom, this.oldX, this.oldY, this.roomPointIndex); 13473 setState(getSelectionState()); 13474 } 13475 13476 @Override 13477 public void exit() { 13478 PlanView planView = getView(); 13479 planView.setResizeIndicatorVisible(false); 13480 planView.deleteFeedback(); 13481 this.selectedRoom = null; 13482 } 13483 } 13484 13485 /** 13486 * Room name offset state. This state manages room name offset. 13487 */ 13488 private class RoomNameOffsetState extends ControllerState { 13489 private Room selectedRoom; 13490 private float oldNameXOffset; 13491 private float oldNameYOffset; 13492 private float xLastMouseMove; 13493 private float yLastMouseMove; 13494 private boolean alignmentActivated; 13495 13496 @Override 13497 public Mode getMode() { 13498 return Mode.SELECTION; 13499 } 13500 13501 @Override 13502 public boolean isModificationState() { 13503 return true; 13504 } 13505 13506 @Override 13507 public void enter() { 13508 this.selectedRoom = (Room)home.getSelectedItems().get(0); 13509 this.oldNameXOffset = this.selectedRoom.getNameXOffset(); 13510 this.oldNameYOffset = this.selectedRoom.getNameYOffset(); 13511 this.xLastMouseMove = getXLastMousePress(); 13512 this.yLastMouseMove = getYLastMousePress(); 13513 this.alignmentActivated = wasAlignmentActivatedLastMousePress(); 13514 PlanView planView = getView(); 13515 planView.setResizeIndicatorVisible(true); 13516 } 13517 13518 @Override 13519 public void moveMouse(float x, float y) { 13520 if (this.alignmentActivated) { 13521 PointWithAngleMagnetism alignedPoint = new PointWithAngleMagnetism(getXLastMousePress(), getYLastMousePress(), 13522 x, y, preferences.getLengthUnit(), getView().getPixelLength(), 4); 13523 x = alignedPoint.getX(); 13524 y = alignedPoint.getY(); 13525 } 13526 this.selectedRoom.setNameXOffset(this.selectedRoom.getNameXOffset() + x - this.xLastMouseMove); 13527 this.selectedRoom.setNameYOffset(this.selectedRoom.getNameYOffset() + y - this.yLastMouseMove); 13528 this.xLastMouseMove = x; 13529 this.yLastMouseMove = y; 13530 13531 // Ensure point at (x,y) is visible 13532 getView().makePointVisible(x, y); 13533 } 13534 13535 @Override 13536 public void releaseMouse(float x, float y) { 13537 postRoomNameOffset(this.selectedRoom, this.oldNameXOffset, this.oldNameYOffset); 13538 setState(getSelectionState()); 13539 } 13540 13541 @Override 13542 public void setAlignmentActivated(boolean alignmentActivated) { 13543 this.alignmentActivated = alignmentActivated; 13544 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 13545 } 13546 13547 @Override 13548 public void escape() { 13549 this.selectedRoom.setNameXOffset(this.oldNameXOffset); 13550 this.selectedRoom.setNameYOffset(this.oldNameYOffset); 13551 setState(getSelectionState()); 13552 } 13553 13554 @Override 13555 public void exit() { 13556 getView().setResizeIndicatorVisible(false); 13557 this.selectedRoom = null; 13558 } 13559 } 13560 13561 /** 13562 * Room name rotation state. This state manages the name rotation of a room. 13563 */ 13564 private class RoomNameRotationState extends ControllerState { 13565 private static final int STEP_COUNT = 24; 13566 13567 private Room selectedRoom; 13568 private float oldNameAngle; 13569 private float angleMousePress; 13570 private boolean magnetismEnabled; 13571 private boolean alignmentActivated; 13572 13573 @Override 13574 public Mode getMode() { 13575 return Mode.SELECTION; 13576 } 13577 13578 @Override 13579 public boolean isModificationState() { 13580 return true; 13581 } 13582 13583 @Override 13584 public void enter() { 13585 this.selectedRoom = (Room)home.getSelectedItems().get(0); 13586 this.angleMousePress = (float)Math.atan2(this.selectedRoom.getYCenter() + this.selectedRoom.getNameYOffset() - getYLastMousePress(), 13587 getXLastMousePress() - this.selectedRoom.getXCenter() - this.selectedRoom.getNameXOffset()); 13588 this.oldNameAngle = this.selectedRoom.getNameAngle(); 13589 this.alignmentActivated = wasAlignmentActivatedLastMousePress(); 13590 this.magnetismEnabled = preferences.isMagnetismEnabled() 13591 ^ wasMagnetismToggledLastMousePress(); 13592 PlanView planView = getView(); 13593 planView.setResizeIndicatorVisible(true); 13594 } 13595 13596 @Override 13597 public void moveMouse(float x, float y) { 13598 if (x != this.selectedRoom.getXCenter() + this.selectedRoom.getNameXOffset() 13599 || y != this.selectedRoom.getYCenter() + this.selectedRoom.getNameYOffset()) { 13600 // Compute the new angle of the room name 13601 float angleMouseMove = (float)Math.atan2(this.selectedRoom.getYCenter() + this.selectedRoom.getNameYOffset() - y, 13602 x - this.selectedRoom.getXCenter() - this.selectedRoom.getNameXOffset()); 13603 float newAngle = this.oldNameAngle - angleMouseMove + this.angleMousePress; 13604 13605 if (this.alignmentActivated 13606 || this.magnetismEnabled) { 13607 float angleStep = 2 * (float)Math.PI / STEP_COUNT; 13608 // Compute angles closest to a step angle (multiple of angleStep) 13609 newAngle = Math.round(newAngle / angleStep) * angleStep; 13610 } 13611 13612 // Update room name new angle 13613 this.selectedRoom.setNameAngle(newAngle); 13614 getView().makePointVisible(x, y); 13615 } 13616 } 13617 13618 @Override 13619 public void releaseMouse(float x, float y) { 13620 postRoomNameRotation(this.selectedRoom, this.oldNameAngle); 13621 setState(getSelectionState()); 13622 } 13623 13624 @Override 13625 public void toggleMagnetism(boolean magnetismToggled) { 13626 // Compute active magnetism 13627 this.magnetismEnabled = preferences.isMagnetismEnabled() 13628 ^ magnetismToggled; 13629 // Compute again angle as if mouse moved 13630 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 13631 } 13632 13633 @Override 13634 public void setAlignmentActivated(boolean alignmentActivated) { 13635 this.alignmentActivated = alignmentActivated; 13636 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 13637 } 13638 13639 @Override 13640 public void escape() { 13641 this.selectedRoom.setNameAngle(this.oldNameAngle); 13642 setState(getSelectionState()); 13643 } 13644 13645 @Override 13646 public void exit() { 13647 getView().setResizeIndicatorVisible(false); 13648 this.selectedRoom = null; 13649 } 13650 } 13651 13652 /** 13653 * Room area offset state. This state manages room area offset. 13654 */ 13655 private class RoomAreaOffsetState extends ControllerState { 13656 private Room selectedRoom; 13657 private float oldAreaXOffset; 13658 private float oldAreaYOffset; 13659 private float xLastMouseMove; 13660 private float yLastMouseMove; 13661 private boolean alignmentActivated; 13662 13663 @Override 13664 public Mode getMode() { 13665 return Mode.SELECTION; 13666 } 13667 13668 @Override 13669 public boolean isModificationState() { 13670 return true; 13671 } 13672 13673 @Override 13674 public void enter() { 13675 this.selectedRoom = (Room)home.getSelectedItems().get(0); 13676 this.oldAreaXOffset = this.selectedRoom.getAreaXOffset(); 13677 this.oldAreaYOffset = this.selectedRoom.getAreaYOffset(); 13678 this.xLastMouseMove = getXLastMousePress(); 13679 this.yLastMouseMove = getYLastMousePress(); 13680 this.alignmentActivated = wasAlignmentActivatedLastMousePress(); 13681 PlanView planView = getView(); 13682 planView.setResizeIndicatorVisible(true); 13683 } 13684 13685 @Override 13686 public void moveMouse(float x, float y) { 13687 if (this.alignmentActivated) { 13688 PointWithAngleMagnetism alignedPoint = new PointWithAngleMagnetism(getXLastMousePress(), getYLastMousePress(), 13689 x, y, preferences.getLengthUnit(), getView().getPixelLength(), 4); 13690 x = alignedPoint.getX(); 13691 y = alignedPoint.getY(); 13692 } 13693 this.selectedRoom.setAreaXOffset(this.selectedRoom.getAreaXOffset() + x - this.xLastMouseMove); 13694 this.selectedRoom.setAreaYOffset(this.selectedRoom.getAreaYOffset() + y - this.yLastMouseMove); 13695 this.xLastMouseMove = x; 13696 this.yLastMouseMove = y; 13697 13698 // Ensure point at (x,y) is visible 13699 getView().makePointVisible(x, y); 13700 } 13701 13702 @Override 13703 public void releaseMouse(float x, float y) { 13704 postRoomAreaOffset(this.selectedRoom, this.oldAreaXOffset, this.oldAreaYOffset); 13705 setState(getSelectionState()); 13706 } 13707 13708 @Override 13709 public void setAlignmentActivated(boolean alignmentActivated) { 13710 this.alignmentActivated = alignmentActivated; 13711 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 13712 } 13713 13714 @Override 13715 public void escape() { 13716 this.selectedRoom.setAreaXOffset(this.oldAreaXOffset); 13717 this.selectedRoom.setAreaYOffset(this.oldAreaYOffset); 13718 setState(getSelectionState()); 13719 } 13720 13721 @Override 13722 public void exit() { 13723 getView().setResizeIndicatorVisible(false); 13724 this.selectedRoom = null; 13725 } 13726 } 13727 13728 /** 13729 * Room area rotation state. This state manages the area rotation of a room. 13730 */ 13731 private class RoomAreaRotationState extends ControllerState { 13732 private static final int STEP_COUNT = 24; 13733 13734 private Room selectedRoom; 13735 private float oldAreaAngle; 13736 private float angleMousePress; 13737 private boolean magnetismEnabled; 13738 private boolean alignmentActivated; 13739 13740 @Override 13741 public Mode getMode() { 13742 return Mode.SELECTION; 13743 } 13744 13745 @Override 13746 public boolean isModificationState() { 13747 return true; 13748 } 13749 13750 @Override 13751 public void enter() { 13752 this.selectedRoom = (Room)home.getSelectedItems().get(0); 13753 this.angleMousePress = (float)Math.atan2(this.selectedRoom.getYCenter() + this.selectedRoom.getAreaYOffset() - getYLastMousePress(), 13754 getXLastMousePress() - this.selectedRoom.getXCenter() - this.selectedRoom.getAreaXOffset()); 13755 this.oldAreaAngle = this.selectedRoom.getAreaAngle(); 13756 this.alignmentActivated = wasAlignmentActivatedLastMousePress(); 13757 this.magnetismEnabled = preferences.isMagnetismEnabled() 13758 ^ wasMagnetismToggledLastMousePress(); 13759 PlanView planView = getView(); 13760 planView.setResizeIndicatorVisible(true); 13761 } 13762 13763 @Override 13764 public void moveMouse(float x, float y) { 13765 if (x != this.selectedRoom.getXCenter() + this.selectedRoom.getAreaXOffset() 13766 || y != this.selectedRoom.getYCenter() + this.selectedRoom.getAreaYOffset()) { 13767 // Compute the new angle of the room area 13768 float angleMouseMove = (float)Math.atan2(this.selectedRoom.getYCenter() + this.selectedRoom.getAreaYOffset() - y, 13769 x - this.selectedRoom.getXCenter() - this.selectedRoom.getAreaXOffset()); 13770 float newAngle = this.oldAreaAngle - angleMouseMove + this.angleMousePress; 13771 13772 if (this.alignmentActivated 13773 || this.magnetismEnabled) { 13774 float angleStep = 2 * (float)Math.PI / STEP_COUNT; 13775 // Compute angles closest to a step angle (multiple of angleStep) 13776 newAngle = Math.round(newAngle / angleStep) * angleStep; 13777 } 13778 13779 // Update room area new angle 13780 this.selectedRoom.setAreaAngle(newAngle); 13781 getView().makePointVisible(x, y); 13782 } 13783 } 13784 13785 @Override 13786 public void releaseMouse(float x, float y) { 13787 postRoomAreaRotation(this.selectedRoom, this.oldAreaAngle); 13788 setState(getSelectionState()); 13789 } 13790 13791 @Override 13792 public void toggleMagnetism(boolean magnetismToggled) { 13793 // Compute active magnetism 13794 this.magnetismEnabled = preferences.isMagnetismEnabled() 13795 ^ magnetismToggled; 13796 // Compute again angle as if mouse moved 13797 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 13798 } 13799 13800 @Override 13801 public void setAlignmentActivated(boolean alignmentActivated) { 13802 this.alignmentActivated = alignmentActivated; 13803 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 13804 } 13805 13806 @Override 13807 public void escape() { 13808 this.selectedRoom.setAreaAngle(this.oldAreaAngle); 13809 setState(getSelectionState()); 13810 } 13811 13812 @Override 13813 public void exit() { 13814 getView().setResizeIndicatorVisible(false); 13815 this.selectedRoom = null; 13816 } 13817 } 13818 13819 /** 13820 * Polyline creation state. This state manages transition to 13821 * other modes, and initial polyline creation. 13822 */ 13823 private class PolylineCreationState extends AbstractModeChangeState { 13824 @Override 13825 public Mode getMode() { 13826 return Mode.POLYLINE_CREATION; 13827 } 13828 13829 @Override 13830 public void enter() { 13831 getView().setCursor(PlanView.CursorType.DRAW); 13832 } 13833 13834 @Override 13835 public void pressMouse(float x, float y, int clickCount, 13836 boolean shiftDown, boolean duplicationActivated) { 13837 // Change state to PolylineDrawingState 13838 setState(getPolylineDrawingState()); 13839 } 13840 13841 @Override 13842 public void setEditionActivated(boolean editionActivated) { 13843 if (editionActivated) { 13844 setState(getPolylineDrawingState()); 13845 PlanController.this.setEditionActivated(editionActivated); 13846 } 13847 } 13848 } 13849 13850 /** 13851 * Polyline modification state. 13852 */ 13853 private abstract class AbstractPolylineState extends ControllerState { 13854 private String polylineSegmentLengthToolTipFeedback; 13855 private String polylineSegmentAngleToolTipFeedback; 13856 13857 @Override 13858 public void enter() { 13859 this.polylineSegmentLengthToolTipFeedback = preferences.getLocalizedString( 13860 PlanController.class, "polylineSegmentLengthToolTipFeedback"); 13861 try { 13862 this.polylineSegmentAngleToolTipFeedback = preferences.getLocalizedString( 13863 PlanController.class, "polylineSegmentAngleToolTipFeedback"); 13864 } catch (IllegalArgumentException ex) { 13865 // This tool tip part is optional 13866 } 13867 } 13868 13869 protected String getToolTipFeedbackText(Polyline polyline, int pointIndex) { 13870 float length = getPolylineSegmentLength(polyline, pointIndex); 13871 int angle = getPolylineSegmentAngle(polyline, pointIndex); 13872 String toolTipFeedbackText = "<html>" + String.format(this.polylineSegmentLengthToolTipFeedback, preferences.getLengthUnit().getFormatWithUnit().format(length)); 13873 if (this.polylineSegmentAngleToolTipFeedback != null 13874 && this.polylineSegmentAngleToolTipFeedback.length() > 0) { 13875 toolTipFeedbackText += "<br>" + String.format(this.polylineSegmentAngleToolTipFeedback, angle); 13876 } 13877 return toolTipFeedbackText; 13878 } 13879 13880 protected float getPolylineSegmentLength(Polyline polyline, int pointIndex) { 13881 if (pointIndex == 0 && !polyline.isClosedPath()) { 13882 // Return the length of the first segment for index 0 of a not closed path 13883 pointIndex++; 13884 } 13885 float [][] points = polyline.getPoints(); 13886 float [] previousPoint = points [(pointIndex + points.length - 1) % points.length]; 13887 return (float)Point2D.distance(previousPoint [0], previousPoint [1], 13888 points [pointIndex][0], points [pointIndex][1]); 13889 } 13890 13891 /** 13892 * Returns polyline segment angle at the given point index in degrees. 13893 */ 13894 protected Integer getPolylineSegmentAngle(Polyline polyline, int pointIndex) { 13895 if (pointIndex == 0 && !polyline.isClosedPath()) { 13896 // Return the angle of the first segment for index 0 of a not closed path 13897 pointIndex++; 13898 } 13899 float [][] points = polyline.getPoints(); 13900 float [] point = points [pointIndex]; 13901 float [] previousPoint = points [(pointIndex + points.length - 1) % points.length]; 13902 float [] previousPreviousPoint = points [(pointIndex + points.length - 2) % points.length]; 13903 float segmentLength = (float)Point2D.distance( 13904 previousPoint [0], previousPoint [1], 13905 points [pointIndex][0], points [pointIndex][1]); 13906 float previousSegmentLength = (float)Point2D.distance( 13907 previousPreviousPoint [0], previousPreviousPoint [1], 13908 previousPoint [0], previousPoint [1]); 13909 if (previousPreviousPoint != point 13910 && segmentLength != 0 && previousSegmentLength != 0) { 13911 // Compute the angle between the segment finishing at pointIndex 13912 // and the previous segment 13913 float xSegmentVector = (point [0] - previousPoint [0]) / segmentLength; 13914 float ySegmentVector = (point [1] - previousPoint [1]) / segmentLength; 13915 float xPreviousSegmentVector = (previousPoint [0] - previousPreviousPoint [0]) / previousSegmentLength; 13916 float yPreviousSegmentVector = (previousPoint [1] - previousPreviousPoint [1]) / previousSegmentLength; 13917 int segmentAngle = (int)Math.round(180 - Math.toDegrees(Math.atan2( 13918 ySegmentVector * xPreviousSegmentVector - xSegmentVector * yPreviousSegmentVector, 13919 xSegmentVector * xPreviousSegmentVector + ySegmentVector * yPreviousSegmentVector))); 13920 if (segmentAngle > 180) { 13921 segmentAngle -= 360; 13922 } 13923 return segmentAngle; 13924 } 13925 if (segmentLength == 0) { 13926 return 0; 13927 } else { 13928 return (int)Math.round(Math.toDegrees(Math.atan2( 13929 previousPoint [1] - point [1], 13930 point [0] - previousPoint [0]))); 13931 } 13932 } 13933 13934 protected void showPolylineAngleFeedback(Polyline polyline, int pointIndex) { 13935 float [][] points = polyline.getPoints(); 13936 if (this.polylineSegmentAngleToolTipFeedback != null 13937 && this.polylineSegmentAngleToolTipFeedback.length() > 0 13938 && (pointIndex >= 2 13939 || points.length > 2 && polyline.isClosedPath())) { 13940 float [] previousPoint = points [(pointIndex + points.length - 1) % points.length]; 13941 float [] previousPreviousPoint = points [(pointIndex + points.length - 2) % points.length]; 13942 getView().setAngleFeedback(previousPoint [0], previousPoint [1], 13943 previousPreviousPoint [0], previousPreviousPoint [1], 13944 points [pointIndex][0], points [pointIndex][1]); 13945 } 13946 } 13947 } 13948 13949 /** 13950 * Polyline drawing state. This state manages polyline creation at mouse press. 13951 */ 13952 private class PolylineDrawingState extends AbstractPolylineState { 13953 private float xPreviousPoint; 13954 private float yPreviousPoint; 13955 private Polyline newPolyline; 13956 private float [] newPoint; 13957 private List<Selectable> oldSelection; 13958 private boolean oldBasePlanLocked; 13959 private boolean oldAllLevelsSelection; 13960 private boolean magnetismEnabled; 13961 private boolean alignmentActivated; 13962 private boolean curvedPolyline; 13963 private long lastPointCreationTime; 13964 13965 @Override 13966 public Mode getMode() { 13967 return Mode.POLYLINE_CREATION; 13968 } 13969 13970 @Override 13971 public boolean isModificationState() { 13972 return true; 13973 } 13974 13975 @Override 13976 public boolean isBasePlanModificationState() { 13977 return true; 13978 } 13979 13980 @Override 13981 public void setMode(Mode mode) { 13982 // Escape current creation and change state to matching mode 13983 escape(); 13984 if (mode == Mode.SELECTION) { 13985 setState(getSelectionState()); 13986 } else if (mode == Mode.PANNING) { 13987 setState(getPanningState()); 13988 } else if (mode == Mode.WALL_CREATION) { 13989 setState(getWallCreationState()); 13990 } else if (mode == Mode.ROOM_CREATION) { 13991 setState(getRoomCreationState()); 13992 } else if (mode == Mode.DIMENSION_LINE_CREATION) { 13993 setState(getDimensionLineCreationState()); 13994 } else if (mode == Mode.LABEL_CREATION) { 13995 setState(getLabelCreationState()); 13996 } 13997 } 13998 13999 @Override 14000 public void enter() { 14001 super.enter(); 14002 this.oldSelection = home.getSelectedItems(); 14003 this.oldBasePlanLocked = home.isBasePlanLocked(); 14004 this.oldAllLevelsSelection = home.isAllLevelsSelection(); 14005 this.newPolyline = null; 14006 this.alignmentActivated = wasAlignmentActivatedLastMousePress(); 14007 toggleMagnetism(wasMagnetismToggledLastMousePress()); 14008 this.xPreviousPoint = getXLastMousePress(); 14009 this.yPreviousPoint = getYLastMousePress(); 14010 setDuplicationActivated(wasDuplicationActivatedLastMousePress()); 14011 deselectAll(); 14012 } 14013 14014 @Override 14015 public void moveMouse(float x, float y) { 14016 PlanView planView = getView(); 14017 // Compute the coordinates where current edit polyline point should be moved 14018 float xEnd = x; 14019 float yEnd = y; 14020 if (this.alignmentActivated 14021 || this.magnetismEnabled) { 14022 PointWithAngleMagnetism pointWithAngleMagnetism = new PointWithAngleMagnetism( 14023 this.xPreviousPoint, this.yPreviousPoint, x, y, preferences.getLengthUnit(), planView.getPixelLength()); 14024 xEnd = pointWithAngleMagnetism.getX(); 14025 yEnd = pointWithAngleMagnetism.getY(); 14026 } 14027 14028 // If current polyline doesn't exist 14029 if (this.newPolyline == null) { 14030 // Create a new one 14031 this.newPolyline = createAndSelectPolyline(this.xPreviousPoint, this.yPreviousPoint, xEnd, yEnd); 14032 } else if (this.newPoint != null) { 14033 // Add a point to current polyline 14034 float [][] points = this.newPolyline.getPoints(); 14035 this.xPreviousPoint = points [points.length - 1][0]; 14036 this.yPreviousPoint = points [points.length - 1][1]; 14037 this.newPolyline.addPoint(xEnd, yEnd); 14038 this.newPoint [0] = xEnd; 14039 this.newPoint [1] = yEnd; 14040 this.newPoint = null; 14041 } else { 14042 // Otherwise update its last point 14043 this.newPolyline.setPoint(xEnd, yEnd, this.newPolyline.getPointCount() - 1); 14044 } 14045 planView.setAlignmentFeedback(Polyline.class, null, x, y, false); 14046 planView.setToolTipFeedback( 14047 getToolTipFeedbackText(this.newPolyline, this.newPolyline.getPointCount() - 1), x, y); 14048 if (this.newPolyline.getJoinStyle() != Polyline.JoinStyle.CURVED) { 14049 showPolylineAngleFeedback(this.newPolyline, this.newPolyline.getPointCount() - 1); 14050 } 14051 14052 // Ensure point at (x,y) is visible 14053 planView.makePointVisible(x, y); 14054 } 14055 14056 /** 14057 * Returns a new polyline instance with one segment between (<code>xStart</code>, 14058 * <code>yStart</code>) and (<code>xEnd</code>, <code>yEnd</code>) points. 14059 * The new polyline is added to home and selected 14060 */ 14061 private Polyline createAndSelectPolyline(float xStart, float yStart, 14062 float xEnd, float yEnd) { 14063 Polyline newPolyline = createPolyline(new float [][] {{xStart, yStart}, {xEnd, yEnd}}); 14064 if (this.curvedPolyline) { 14065 newPolyline.setJoinStyle(Polyline.JoinStyle.CURVED); 14066 } 14067 selectItems(Arrays.asList(new Selectable [] {newPolyline})); 14068 return newPolyline; 14069 } 14070 14071 @Override 14072 public void pressMouse(float x, float y, int clickCount, 14073 boolean shiftDown, boolean duplicationActivated) { 14074 if (clickCount == 2) { 14075 if (this.newPolyline != null) { 14076 int pointIndex = this.newPolyline.getPointIndexAt(x, y, getSelectionMargin()); 14077 if (pointIndex == 0) { 14078 this.newPolyline.removePoint(this.newPolyline.getPointCount() - 1); 14079 this.newPolyline.setClosedPath(true); 14080 } 14081 validateDrawnPolyline(); 14082 } else { 14083 if (getPointerTypeLastMousePress() == View.PointerType.TOUCH) { 14084 setState(getSelectionState()); 14085 } else { 14086 setState(getPolylineCreationState()); 14087 } 14088 } 14089 } else { 14090 endPolylineSegment(); 14091 } 14092 } 14093 14094 private void validateDrawnPolyline() { 14095 if (this.newPolyline != null) { 14096 float [][] points = this.newPolyline.getPoints(); 14097 if (points.length < 2) { 14098 // Delete current created polyline if it doesn't have more than 2 clicked points 14099 home.deletePolyline(this.newPolyline); 14100 } else { 14101 // Post polyline creation to undo support 14102 postCreatePolylines(Arrays.asList(new Polyline [] {this.newPolyline}), 14103 this.oldSelection, this.oldBasePlanLocked, this.oldAllLevelsSelection); 14104 } 14105 } 14106 if (getPointerTypeLastMousePress() == View.PointerType.TOUCH) { 14107 setState(getSelectionState()); 14108 } else { 14109 // Change state to PolylineCreationState 14110 setState(getPolylineCreationState()); 14111 } 14112 } 14113 14114 private void endPolylineSegment() { 14115 // Create a new polyline segment only when its length is greater than zero 14116 if (this.newPolyline != null 14117 && getPolylineSegmentLength(this.newPolyline, this.newPolyline.getPointCount() - 1) > 0) { 14118 this.newPoint = new float [2]; 14119 if (this.newPolyline.getPointCount() <= 2 14120 && this.curvedPolyline 14121 && newPolyline.getJoinStyle() != Polyline.JoinStyle.CURVED) { 14122 // Give a second chance to create a curved polyline 14123 newPolyline.setJoinStyle(Polyline.JoinStyle.CURVED); 14124 } 14125 } 14126 } 14127 14128 @Override 14129 public void setEditionActivated(boolean editionActivated) { 14130 PlanView planView = getView(); 14131 if (editionActivated) { 14132 planView.deleteFeedback(); 14133 if (this.newPolyline == null) { 14134 // Edit xStart and yStart 14135 planView.setToolTipEditedProperties(new EditableProperty [] {EditableProperty.X, 14136 EditableProperty.Y}, 14137 new Object [] {this.xPreviousPoint, this.yPreviousPoint}, 14138 this.xPreviousPoint, this.yPreviousPoint); 14139 } else { 14140 if (this.newPoint != null) { 14141 // May happen if edition is activated after the user clicked to add a new point 14142 createNextSegment(); 14143 } 14144 // Edit length and angle 14145 float [][] points = this.newPolyline.getPoints(); 14146 planView.setToolTipEditedProperties(new EditableProperty [] {EditableProperty.LENGTH, 14147 EditableProperty.ANGLE}, 14148 new Object [] {getPolylineSegmentLength(this.newPolyline, points.length - 1), 14149 getPolylineSegmentAngle(this.newPolyline, points.length - 1)}, 14150 points [points.length - 1][0], points [points.length - 1][1]); 14151 } 14152 } else { 14153 if (this.newPolyline == null) { 14154 // Create a new segment once user entered the start point of the polyline 14155 LengthUnit lengthUnit = preferences.getLengthUnit(); 14156 float defaultLength = lengthUnit == LengthUnit.INCH || lengthUnit == LengthUnit.INCH_DECIMALS 14157 ? LengthUnit.footToCentimeter(10) : 300; 14158 this.newPolyline = createAndSelectPolyline(this.xPreviousPoint, this.yPreviousPoint, 14159 this.xPreviousPoint + defaultLength, this.yPreviousPoint); 14160 // Activate automatically second step to let user enter the 14161 // length and angle of the new segment 14162 planView.deleteFeedback(); 14163 setEditionActivated(true); 14164 } else if (System.currentTimeMillis() - this.lastPointCreationTime < 300) { 14165 // If the user deactivated edition less than 300 ms after activation, 14166 // escape current segment creation 14167 escape(); 14168 } else { 14169 endPolylineSegment(); 14170 float [][] points = this.newPolyline.getPoints(); 14171 // If last edited point matches first point validate drawn polyline 14172 if (points.length > 2 14173 && this.newPolyline.getPointIndexAt(points [points.length - 1][0], points [points.length - 1][1], 0.001f) == 0) { 14174 // Remove last currently edited point and close path 14175 this.newPolyline.removePoint(this.newPolyline.getPointCount() - 1); 14176 this.newPolyline.setClosedPath(true); 14177 validateDrawnPolyline(); 14178 return; 14179 } 14180 createNextSegment(); 14181 // Reactivate automatically second step 14182 planView.deleteToolTipFeedback(); 14183 setEditionActivated(true); 14184 } 14185 } 14186 } 14187 14188 private void createNextSegment() { 14189 // Add a point to current polyline 14190 float [][] points = this.newPolyline.getPoints(); 14191 this.xPreviousPoint = points [points.length - 1][0]; 14192 this.yPreviousPoint = points [points.length - 1][1]; 14193 // Create a new segment with an angle equal to previous segment angle - 90� 14194 double previousSegmentAngle = Math.PI - Math.atan2(points [points.length - 2][1] - points [points.length - 1][1], 14195 points [points.length - 2][0] - points [points.length - 1][0]); 14196 previousSegmentAngle -= Math.PI / 2; 14197 float previousSegmentLength = getPolylineSegmentLength(this.newPolyline, points.length - 1); 14198 this.newPolyline.addPoint( 14199 (float)(this.xPreviousPoint + previousSegmentLength * Math.cos(previousSegmentAngle)), 14200 (float)(this.yPreviousPoint - previousSegmentLength * Math.sin(previousSegmentAngle))); 14201 this.newPoint = null; 14202 this.lastPointCreationTime = System.currentTimeMillis(); 14203 } 14204 14205 @Override 14206 public void updateEditableProperty(EditableProperty editableProperty, Object value) { 14207 PlanView planView = getView(); 14208 if (this.newPolyline == null) { 14209 // Update start point of the first wall 14210 switch (editableProperty) { 14211 case X : 14212 this.xPreviousPoint = value != null ? ((Number)value).floatValue() : 0; 14213 this.xPreviousPoint = Math.max(-100000f, Math.min(this.xPreviousPoint, 100000f)); 14214 break; 14215 case Y : 14216 this.yPreviousPoint = value != null ? ((Number)value).floatValue() : 0; 14217 this.yPreviousPoint = Math.max(-100000f, Math.min(this.yPreviousPoint, 100000f)); 14218 break; 14219 } 14220 planView.setAlignmentFeedback(Polyline.class, null, this.xPreviousPoint, this.yPreviousPoint, true); 14221 planView.makePointVisible(this.xPreviousPoint, this.yPreviousPoint); 14222 } else { 14223 float [][] points = this.newPolyline.getPoints(); 14224 float [] previousPoint = points [points.length - 2]; 14225 float [] point = points [points.length - 1]; 14226 float newX; 14227 float newY; 14228 // Update end point of the current polyline 14229 switch (editableProperty) { 14230 case LENGTH : 14231 float length = value != null ? ((Number)value).floatValue() : 0; 14232 length = Math.max(0.001f, Math.min(length, preferences.getLengthUnit().getMaximumLength())); 14233 double segmentAngle = Math.PI - Math.atan2(previousPoint [1] - point [1], 14234 previousPoint [0] - point [0]); 14235 newX = (float)(previousPoint [0] + length * Math.cos(segmentAngle)); 14236 newY = (float)(previousPoint [1] - length * Math.sin(segmentAngle)); 14237 break; 14238 case ANGLE : 14239 segmentAngle = Math.toRadians(value != null ? ((Number)value).floatValue() : 0); 14240 if (points.length > 2) { 14241 segmentAngle -= Math.atan2(points [points.length - 3][1] - previousPoint [1], 14242 points [points.length - 3][0] - previousPoint [0]); 14243 } 14244 float segmentLength = getPolylineSegmentLength(this.newPolyline, points.length - 1); 14245 newX = (float)(previousPoint [0] + segmentLength * Math.cos(segmentAngle)); 14246 newY = (float)(previousPoint [1] - segmentLength * Math.sin(segmentAngle)); 14247 break; 14248 default : 14249 return; 14250 } 14251 this.newPolyline.setPoint(newX, newY, points.length - 1); 14252 14253 if (this.newPolyline.getJoinStyle() != Polyline.JoinStyle.CURVED) { 14254 showPolylineAngleFeedback(this.newPolyline, points.length - 1); 14255 } 14256 planView.setAlignmentFeedback(Polyline.class, null, newX, newY, false); 14257 // Ensure polyline segment points are visible 14258 planView.makePointVisible(points [points.length - 2][0], points [points.length - 2][1]); 14259 planView.makePointVisible(points [points.length - 1][0], points [points.length - 1][1]); 14260 } 14261 } 14262 14263 @Override 14264 public void toggleMagnetism(boolean magnetismToggled) { 14265 // Compute active magnetism 14266 this.magnetismEnabled = preferences.isMagnetismEnabled() 14267 ^ magnetismToggled; 14268 if (this.newPolyline != null) { 14269 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 14270 } 14271 } 14272 14273 @Override 14274 public void setAlignmentActivated(boolean alignmentActivated) { 14275 this.alignmentActivated = alignmentActivated; 14276 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 14277 } 14278 14279 @Override 14280 public void setDuplicationActivated(boolean duplicationActivated) { 14281 // Reuse duplication activation for curved polyline creation 14282 this.curvedPolyline = duplicationActivated; 14283 } 14284 14285 @Override 14286 public void escape() { 14287 if (this.newPolyline != null 14288 && this.newPoint == null) { 14289 // Remove last currently edited point. 14290 this.newPolyline.removePoint(this.newPolyline.getPointCount() - 1); 14291 } 14292 validateDrawnPolyline(); 14293 } 14294 14295 @Override 14296 public void exit() { 14297 getView().deleteFeedback(); 14298 this.newPolyline = null; 14299 this.newPoint = null; 14300 this.oldSelection = null; 14301 } 14302 } 14303 14304 /** 14305 * Polyline resize state. This state manages polyline resizing. 14306 */ 14307 private class PolylineResizeState extends AbstractPolylineState { 14308 private Collection<Polyline> polylines; 14309 private Polyline selectedPolyline; 14310 private int polylinePointIndex; 14311 private float oldX; 14312 private float oldY; 14313 private float deltaXToResizePoint; 14314 private float deltaYToResizePoint; 14315 private boolean magnetismEnabled; 14316 private boolean alignmentActivated; 14317 14318 @Override 14319 public Mode getMode() { 14320 return Mode.SELECTION; 14321 } 14322 14323 @Override 14324 public boolean isModificationState() { 14325 return true; 14326 } 14327 14328 @Override 14329 public boolean isBasePlanModificationState() { 14330 return true; 14331 } 14332 14333 @Override 14334 public void enter() { 14335 super.enter(); 14336 this.selectedPolyline = (Polyline)home.getSelectedItems().get(0); 14337 this.polylines = new ArrayList<Polyline>(home.getPolylines()); 14338 this.polylines.remove(this.selectedPolyline); 14339 float margin = getIndicatorMargin(); 14340 this.polylinePointIndex = this.selectedPolyline.getPointIndexAt( 14341 getXLastMousePress(), getYLastMousePress(), margin); 14342 float [][] polylinePoints = this.selectedPolyline.getPoints(); 14343 this.oldX = polylinePoints [this.polylinePointIndex][0]; 14344 this.oldY = polylinePoints [this.polylinePointIndex][1]; 14345 this.deltaXToResizePoint = getXLastMousePress() - this.oldX; 14346 this.deltaYToResizePoint = getYLastMousePress() - this.oldY; 14347 this.alignmentActivated = wasAlignmentActivatedLastMousePress(); 14348 toggleMagnetism(wasMagnetismToggledLastMousePress()); 14349 PlanView planView = getView(); 14350 planView.setResizeIndicatorVisible(true); 14351 String toolTipFeedbackText = getToolTipFeedbackText(this.selectedPolyline, this.polylinePointIndex); 14352 if (toolTipFeedbackText != null) { 14353 planView.setToolTipFeedback(toolTipFeedbackText, getXLastMousePress(), getYLastMousePress()); 14354 if (this.selectedPolyline.getJoinStyle() != Polyline.JoinStyle.CURVED) { 14355 showPolylineAngleFeedback(this.selectedPolyline, this.polylinePointIndex); 14356 } 14357 } 14358 } 14359 14360 @Override 14361 public void moveMouse(float x, float y) { 14362 PlanView planView = getView(); 14363 float newX = x - this.deltaXToResizePoint; 14364 float newY = y - this.deltaYToResizePoint; 14365 if (this.alignmentActivated 14366 || this.magnetismEnabled) { 14367 float [][] polylinePoints = this.selectedPolyline.getPoints(); 14368 int previousPointIndex = this.polylinePointIndex == 0 14369 ? (this.selectedPolyline.isClosedPath() 14370 ? polylinePoints.length - 1 14371 : 1) 14372 : this.polylinePointIndex - 1; 14373 float xPreviousPoint = polylinePoints [previousPointIndex][0]; 14374 float yPreviousPoint = polylinePoints [previousPointIndex][1]; 14375 PointWithAngleMagnetism pointWithAngleMagnetism = new PointWithAngleMagnetism( 14376 xPreviousPoint, yPreviousPoint, newX, newY, preferences.getLengthUnit(), planView.getPixelLength()); 14377 newX = pointWithAngleMagnetism.getX(); 14378 newY = pointWithAngleMagnetism.getY(); 14379 } 14380 this.selectedPolyline.setPoint(newX, newY, this.polylinePointIndex); 14381 14382 planView.setToolTipFeedback(getToolTipFeedbackText(this.selectedPolyline, this.polylinePointIndex), x, y); 14383 if (this.selectedPolyline.getJoinStyle() != Polyline.JoinStyle.CURVED) { 14384 showPolylineAngleFeedback(this.selectedPolyline, this.polylinePointIndex); 14385 } 14386 // Ensure point at (x,y) is visible 14387 planView.makePointVisible(x, y); 14388 } 14389 14390 @Override 14391 public void releaseMouse(float x, float y) { 14392 postPolylineResize(this.selectedPolyline, this.oldX, this.oldY, this.polylinePointIndex); 14393 setState(getSelectionState()); 14394 } 14395 14396 @Override 14397 public void toggleMagnetism(boolean magnetismToggled) { 14398 // Compute active magnetism 14399 this.magnetismEnabled = preferences.isMagnetismEnabled() 14400 ^ magnetismToggled; 14401 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 14402 } 14403 14404 @Override 14405 public void setAlignmentActivated(boolean alignmentActivated) { 14406 this.alignmentActivated = alignmentActivated; 14407 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 14408 } 14409 14410 @Override 14411 public void escape() { 14412 this.selectedPolyline.setPoint(this.oldX, this.oldY, this.polylinePointIndex); 14413 setState(getSelectionState()); 14414 } 14415 14416 @Override 14417 public void exit() { 14418 PlanView planView = getView(); 14419 planView.setResizeIndicatorVisible(false); 14420 planView.deleteFeedback(); 14421 this.selectedPolyline = null; 14422 } 14423 } 14424 14425 /** 14426 * Label creation state. This state manages transition to 14427 * other modes, and initial label creation. 14428 */ 14429 private class LabelCreationState extends AbstractModeChangeState { 14430 @Override 14431 public Mode getMode() { 14432 return Mode.LABEL_CREATION; 14433 } 14434 14435 @Override 14436 public void enter() { 14437 getView().setCursor(PlanView.CursorType.DRAW); 14438 } 14439 14440 @Override 14441 public void pressMouse(float x, float y, int clickCount, 14442 boolean shiftDown, boolean duplicationActivated) { 14443 createLabel(x, y); 14444 if (getPointerTypeLastMousePress() == View.PointerType.TOUCH) { 14445 setState(getSelectionState()); 14446 } 14447 } 14448 } 14449 14450 /** 14451 * Label rotation state. This state manages the rotation of a label. 14452 */ 14453 private class LabelRotationState extends ControllerState { 14454 private static final int STEP_COUNT = 24; 14455 14456 private Label selectedLabel; 14457 private float oldAngle; 14458 private float angleMousePress; 14459 private boolean magnetismEnabled; 14460 private boolean alignmentActivated; 14461 14462 @Override 14463 public Mode getMode() { 14464 return Mode.SELECTION; 14465 } 14466 14467 @Override 14468 public boolean isModificationState() { 14469 return true; 14470 } 14471 14472 @Override 14473 public boolean isBasePlanModificationState() { 14474 return true; 14475 } 14476 14477 @Override 14478 public void enter() { 14479 this.selectedLabel = (Label)home.getSelectedItems().get(0); 14480 this.angleMousePress = (float)Math.atan2(this.selectedLabel.getY() - getYLastMousePress(), 14481 getXLastMousePress() - this.selectedLabel.getX()); 14482 this.oldAngle = this.selectedLabel.getAngle(); 14483 this.alignmentActivated = wasAlignmentActivatedLastMousePress(); 14484 this.magnetismEnabled = preferences.isMagnetismEnabled() 14485 ^ wasMagnetismToggledLastMousePress(); 14486 PlanView planView = getView(); 14487 planView.setResizeIndicatorVisible(true); 14488 } 14489 14490 @Override 14491 public void moveMouse(float x, float y) { 14492 if (x != this.selectedLabel.getX() || y != this.selectedLabel.getY()) { 14493 // Compute the new angle of the label text 14494 float angleMouseMove = (float)Math.atan2(this.selectedLabel.getY() - y, 14495 x - this.selectedLabel.getX()); 14496 float newAngle = this.oldAngle - angleMouseMove + this.angleMousePress; 14497 14498 if (this.alignmentActivated 14499 ||this.magnetismEnabled) { 14500 float angleStep = 2 * (float)Math.PI / STEP_COUNT; 14501 // Compute angles closest to a step angle (multiple of angleStep) 14502 newAngle = Math.round(newAngle / angleStep) * angleStep; 14503 } 14504 14505 // Update label text new angle 14506 this.selectedLabel.setAngle(newAngle); 14507 getView().makePointVisible(x, y); 14508 } 14509 } 14510 14511 @Override 14512 public void releaseMouse(float x, float y) { 14513 postLabelRotation(this.selectedLabel, this.oldAngle); 14514 setState(getSelectionState()); 14515 } 14516 14517 @Override 14518 public void toggleMagnetism(boolean magnetismToggled) { 14519 // Compute active magnetism 14520 this.magnetismEnabled = preferences.isMagnetismEnabled() 14521 ^ magnetismToggled; 14522 // Compute again angle as if mouse moved 14523 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 14524 } 14525 14526 @Override 14527 public void setAlignmentActivated(boolean alignmentActivated) { 14528 this.alignmentActivated = alignmentActivated; 14529 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 14530 } 14531 14532 @Override 14533 public void escape() { 14534 this.selectedLabel.setAngle(this.oldAngle); 14535 setState(getSelectionState()); 14536 } 14537 14538 @Override 14539 public void exit() { 14540 getView().setResizeIndicatorVisible(false); 14541 this.selectedLabel = null; 14542 } 14543 } 14544 14545 /** 14546 * Label elevation state. This states manages the elevation change of a label. 14547 */ 14548 private class LabelElevationState extends ControllerState { 14549 private boolean magnetismEnabled; 14550 private float deltaYToElevationPoint; 14551 private Label selectedLabel; 14552 private float oldElevation; 14553 private String elevationToolTipFeedback; 14554 14555 @Override 14556 public Mode getMode() { 14557 return Mode.SELECTION; 14558 } 14559 14560 @Override 14561 public boolean isModificationState() { 14562 return true; 14563 } 14564 14565 @Override 14566 public boolean isBasePlanModificationState() { 14567 return true; 14568 } 14569 14570 @Override 14571 public void enter() { 14572 this.elevationToolTipFeedback = preferences.getLocalizedString( 14573 PlanController.class, "elevationToolTipFeedback"); 14574 this.selectedLabel = (Label)home.getSelectedItems().get(0); 14575 TextStyle textStyle = getItemTextStyle(this.selectedLabel, this.selectedLabel.getStyle()); 14576 float [][] textBounds = getView().getTextBounds(this.selectedLabel.getText(), textStyle, 14577 this.selectedLabel.getX(), this.selectedLabel.getY(), this.selectedLabel.getAngle()); 14578 this.deltaYToElevationPoint = getYLastMousePress() - (textBounds [2][1] + textBounds [3][1]) / 2; 14579 this.oldElevation = this.selectedLabel.getElevation(); 14580 this.magnetismEnabled = preferences.isMagnetismEnabled() 14581 ^ wasMagnetismToggledLastMousePress(); 14582 PlanView planView = getView(); 14583 planView.setResizeIndicatorVisible(true); 14584 planView.setToolTipFeedback(getToolTipFeedbackText(this.oldElevation), 14585 getXLastMousePress(), getYLastMousePress()); 14586 } 14587 14588 @Override 14589 public void moveMouse(float x, float y) { 14590 // Compute the new elevation of the piece 14591 PlanView planView = getView(); 14592 TextStyle textStyle = getItemTextStyle(this.selectedLabel, this.selectedLabel.getStyle()); 14593 float [][] textBounds = getView().getTextBounds(this.selectedLabel.getText(), textStyle, 14594 this.selectedLabel.getX(), this.selectedLabel.getY(), this.selectedLabel.getAngle()); 14595 float deltaY = y - this.deltaYToElevationPoint - (textBounds [2][1] + textBounds [3][1]) / 2; 14596 float newElevation = this.oldElevation - deltaY; 14597 newElevation = Math.min(Math.max(newElevation, 0f), preferences.getLengthUnit().getMaximumElevation()); 14598 if (this.magnetismEnabled) { 14599 newElevation = preferences.getLengthUnit().getMagnetizedLength(newElevation, planView.getPixelLength()); 14600 } 14601 14602 // Update piece new dimension 14603 this.selectedLabel.setElevation(newElevation); 14604 14605 // Ensure point at (x,y) is visible 14606 planView.makePointVisible(x, y); 14607 planView.setToolTipFeedback(getToolTipFeedbackText(newElevation), x, y); 14608 } 14609 14610 @Override 14611 public void releaseMouse(float x, float y) { 14612 postLabelElevation(this.selectedLabel, this.oldElevation); 14613 setState(getSelectionState()); 14614 } 14615 14616 @Override 14617 public void toggleMagnetism(boolean magnetismToggled) { 14618 // Compute active magnetism 14619 this.magnetismEnabled = preferences.isMagnetismEnabled() 14620 ^ magnetismToggled; 14621 // Compute again angle as if mouse moved 14622 moveMouse(getXLastMouseMove(), getYLastMouseMove()); 14623 } 14624 14625 @Override 14626 public void escape() { 14627 this.selectedLabel.setElevation(this.oldElevation); 14628 setState(getSelectionState()); 14629 } 14630 14631 @Override 14632 public void exit() { 14633 PlanView planView = getView(); 14634 planView.setResizeIndicatorVisible(false); 14635 planView.deleteFeedback(); 14636 this.selectedLabel = null; 14637 } 14638 14639 private String getToolTipFeedbackText(float height) { 14640 return String.format(this.elevationToolTipFeedback, 14641 preferences.getLengthUnit().getFormatWithUnit().format(height)); 14642 } 14643 } 14644 14645 /** 14646 * Compass rotation state. This states manages the rotation of the compass. 14647 */ 14648 private class CompassRotationState extends ControllerState { 14649 private Compass selectedCompass; 14650 private float angleMousePress; 14651 private float oldNorthDirection; 14652 private String rotationToolTipFeedback; 14653 14654 @Override 14655 public Mode getMode() { 14656 return Mode.SELECTION; 14657 } 14658 14659 @Override 14660 public boolean isModificationState() { 14661 return true; 14662 } 14663 14664 @Override 14665 public boolean isBasePlanModificationState() { 14666 return true; 14667 } 14668 14669 @Override 14670 public void enter() { 14671 this.rotationToolTipFeedback = preferences.getLocalizedString( 14672 PlanController.class, "rotationToolTipFeedback"); 14673 this.selectedCompass = (Compass)home.getSelectedItems().get(0); 14674 this.angleMousePress = (float)Math.atan2(this.selectedCompass.getY() - getYLastMousePress(), 14675 getXLastMousePress() - this.selectedCompass.getX()); 14676 this.oldNorthDirection = this.selectedCompass.getNorthDirection(); 14677 PlanView planView = getView(); 14678 planView.setResizeIndicatorVisible(true); 14679 planView.setToolTipFeedback(getToolTipFeedbackText(this.oldNorthDirection), 14680 getXLastMousePress(), getYLastMousePress()); 14681 } 14682 14683 @Override 14684 public void moveMouse(float x, float y) { 14685 if (x != this.selectedCompass.getX() || y != this.selectedCompass.getY()) { 14686 // Compute the new north direction of the compass 14687 float angleMouseMove = (float)Math.atan2(this.selectedCompass.getY() - y, 14688 x - this.selectedCompass.getX()); 14689 float newNorthDirection = this.oldNorthDirection - angleMouseMove + this.angleMousePress; 14690 float angleStep = (float)Math.PI / 180; 14691 // Compute angles closest to a degree with a value between 0 and 2 PI 14692 newNorthDirection = Math.round(newNorthDirection / angleStep) * angleStep; 14693 newNorthDirection = (float)((newNorthDirection + 2 * Math.PI) % (2 * Math.PI)); 14694 // Update compass new north direction 14695 this.selectedCompass.setNorthDirection(newNorthDirection); 14696 // Ensure point at (x,y) is visible 14697 PlanView planView = getView(); 14698 planView.makePointVisible(x, y); 14699 planView.setToolTipFeedback(getToolTipFeedbackText(newNorthDirection), x, y); 14700 } 14701 } 14702 14703 @Override 14704 public void releaseMouse(float x, float y) { 14705 postCompassRotation(this.selectedCompass, this.oldNorthDirection); 14706 setState(getSelectionState()); 14707 } 14708 14709 @Override 14710 public void escape() { 14711 this.selectedCompass.setNorthDirection(this.oldNorthDirection); 14712 setState(getSelectionState()); 14713 } 14714 14715 @Override 14716 public void exit() { 14717 PlanView planView = getView(); 14718 planView.setResizeIndicatorVisible(false); 14719 planView.deleteFeedback(); 14720 this.selectedCompass = null; 14721 } 14722 14723 private String getToolTipFeedbackText(float angle) { 14724 return String.format(this.rotationToolTipFeedback, Math.round(Math.toDegrees(angle))); 14725 } 14726 } 14727 14728 /** 14729 * Compass resize state. This states manages the resizing of the compass. 14730 */ 14731 private class CompassResizeState extends ControllerState { 14732 private Compass selectedCompass; 14733 private float oldDiameter; 14734 private float deltaXToResizePoint; 14735 private float deltaYToResizePoint; 14736 private String resizeToolTipFeedback; 14737 14738 @Override 14739 public Mode getMode() { 14740 return Mode.SELECTION; 14741 } 14742 14743 @Override 14744 public boolean isModificationState() { 14745 return true; 14746 } 14747 14748 @Override 14749 public boolean isBasePlanModificationState() { 14750 return true; 14751 } 14752 14753 @Override 14754 public void enter() { 14755 this.resizeToolTipFeedback = preferences.getLocalizedString( 14756 PlanController.class, "diameterToolTipFeedback"); 14757 this.selectedCompass = (Compass)home.getSelectedItems().get(0); 14758 float [][] compassPoints = this.selectedCompass.getPoints(); 14759 float xMiddleSecondAndThirdPoint = (compassPoints [1][0] + compassPoints [2][0]) / 2; 14760 float yMiddleSecondAndThirdPoint = (compassPoints [1][1] + compassPoints [2][1]) / 2; 14761 this.deltaXToResizePoint = getXLastMousePress() - xMiddleSecondAndThirdPoint; 14762 this.deltaYToResizePoint = getYLastMousePress() - yMiddleSecondAndThirdPoint; 14763 this.oldDiameter = this.selectedCompass.getDiameter(); 14764 PlanView planView = getView(); 14765 planView.setResizeIndicatorVisible(true); 14766 planView.setToolTipFeedback(getToolTipFeedbackText(this.oldDiameter), 14767 getXLastMousePress(), getYLastMousePress()); 14768 } 14769 14770 @Override 14771 public void moveMouse(float x, float y) { 14772 // Compute the new diameter of the compass 14773 PlanView planView = getView(); 14774 float newDiameter = (float)Point2D.distance(this.selectedCompass.getX(), this.selectedCompass.getY(), 14775 x - this.deltaXToResizePoint, y - this.deltaYToResizePoint) * 2; 14776 newDiameter = preferences.getLengthUnit().getMagnetizedLength(newDiameter, planView.getPixelLength()); 14777 newDiameter = Math.min(Math.max(newDiameter, preferences.getLengthUnit().getMinimumLength()), 14778 preferences.getLengthUnit().getMaximumLength() / 10); 14779 // Update piece size 14780 this.selectedCompass.setDiameter(newDiameter); 14781 // Ensure point at (x,y) is visible 14782 planView.makePointVisible(x, y); 14783 planView.setToolTipFeedback(getToolTipFeedbackText(newDiameter), x, y); 14784 } 14785 14786 @Override 14787 public void releaseMouse(float x, float y) { 14788 postCompassResize(this.selectedCompass, this.oldDiameter); 14789 setState(getSelectionState()); 14790 } 14791 14792 @Override 14793 public void escape() { 14794 this.selectedCompass.setDiameter(this.oldDiameter); 14795 setState(getSelectionState()); 14796 } 14797 14798 @Override 14799 public void exit() { 14800 PlanView planView = getView(); 14801 planView.setResizeIndicatorVisible(false); 14802 planView.deleteFeedback(); 14803 this.selectedCompass = null; 14804 } 14805 14806 private String getToolTipFeedbackText(float diameter) { 14807 return String.format(this.resizeToolTipFeedback, 14808 preferences.getLengthUnit().getFormatWithUnit().format(diameter)); 14809 } 14810 } 14811 } 14812