1 /* 2 * Home.java 15 mai 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.model; 21 22 import java.beans.PropertyChangeEvent; 23 import java.beans.PropertyChangeListener; 24 import java.beans.PropertyChangeSupport; 25 import java.io.IOException; 26 import java.io.ObjectInputStream; 27 import java.io.Serializable; 28 import java.util.ArrayList; 29 import java.util.Arrays; 30 import java.util.Collection; 31 import java.util.Collections; 32 import java.util.Comparator; 33 import java.util.HashMap; 34 import java.util.List; 35 import java.util.Map; 36 37 /** 38 * The home managed by the application with its furniture and walls. 39 * @author Emmanuel Puybaret 40 */ 41 public class Home implements Serializable, Cloneable { 42 private static final long serialVersionUID = 1L; 43 44 /** 45 * The current version of this home. Each time the field list is changed 46 * in <code>Home</code> class or in one of the classes that it uses, 47 * this number is increased. 48 */ 49 public static final long CURRENT_VERSION = 6500; 50 51 private static final String HOME_TOP_CAMERA_ID = "camera-homeTopCamera"; 52 private static final String HOME_OBSERVER_CAMERA_ID = "observerCamera-homeObserverCamera"; 53 private static final String HOME_ENVIRONMENT_ID = "environment-homeEnvironment"; 54 private static final String HOME_COMPASS_ID = "compass-homeCompass"; 55 56 private static final boolean KEEP_BACKWARD_COMPATIBLITY = true; 57 58 private static final Comparator<Level> LEVEL_ELEVATION_COMPARATOR = new Comparator<Level>() { 59 public int compare(Level level1, Level level2) { 60 int elevationComparison = Float.compare(level1.getElevation(), level2.getElevation()); 61 if (elevationComparison != 0) { 62 return elevationComparison; 63 } else { 64 return level1.getElevationIndex() - level2.getElevationIndex(); 65 } 66 } 67 }; 68 69 /** 70 * The properties of a home that may change. <code>PropertyChangeListener</code>s added 71 * to a home will be notified under a property name equal to the name value of one these properties. 72 */ 73 public enum Property {NAME, MODIFIED, 74 FURNITURE_SORTED_PROPERTY, FURNITURE_DESCENDING_SORTED, FURNITURE_VISIBLE_PROPERTIES, 75 BACKGROUND_IMAGE, CAMERA, PRINT, BASE_PLAN_LOCKED, STORED_CAMERAS, RECOVERED, REPAIRED, 76 SELECTED_LEVEL, ALL_LEVELS_SELECTION}; 77 78 private List<HomePieceOfFurniture> furniture; 79 private transient CollectionChangeSupport<HomePieceOfFurniture> furnitureChangeSupport; 80 private transient List<Selectable> selectedItems; 81 private transient List<SelectionListener> selectionListeners; 82 private transient boolean allLevelsSelection; 83 private List<Level> levels; 84 private Level selectedLevel; 85 private transient CollectionChangeSupport<Level> levelsChangeSupport; 86 private List<Wall> walls; 87 private transient CollectionChangeSupport<Wall> wallsChangeSupport; 88 private List<Room> rooms; 89 private transient CollectionChangeSupport<Room> roomsChangeSupport; 90 private List<Polyline> polylines; 91 private transient CollectionChangeSupport<Polyline> polylinesChangeSupport; 92 private List<DimensionLine> dimensionLines; 93 private transient CollectionChangeSupport<DimensionLine> dimensionLinesChangeSupport; 94 private List<Label> labels; 95 private transient CollectionChangeSupport<Label> labelsChangeSupport; 96 private Camera camera; 97 private String name; 98 private final float wallHeight; 99 private transient boolean modified; 100 private transient boolean recovered; 101 private transient boolean repaired; 102 private BackgroundImage backgroundImage; 103 private ObserverCamera observerCamera; 104 private Camera topCamera; 105 private List<Camera> storedCameras; 106 private HomeEnvironment environment; 107 private HomePrint print; 108 private String furnitureSortedPropertyName; 109 private List<String> furnitureVisiblePropertyNames; 110 private boolean furnitureDescendingSorted; 111 private Map<String, Object> visualProperties; 112 private Map<String, String> properties; 113 private transient PropertyChangeSupport propertyChangeSupport; 114 private long version; 115 private boolean basePlanLocked; 116 private Compass compass; 117 // The 5 following environment fields are still declared for compatibility reasons 118 private int skyColor; 119 private int groundColor; 120 private HomeTexture groundTexture; 121 private int lightColor; 122 private float wallsAlpha; 123 // The two following fields aren't transient for backward compatibility reasons 124 private HomePieceOfFurniture.SortableProperty furnitureSortedProperty; 125 private List<HomePieceOfFurniture.SortableProperty> furnitureVisibleProperties; 126 // The following field is a temporary copy of furniture containing HomeDoorOrWindow instances 127 // created at serialization time for backward compatibility reasons 128 private List<HomePieceOfFurniture> furnitureWithDoorsAndWindows; 129 // The following field is a temporary copy of furniture containing HomeFurnitureGroup instances 130 // created at serialization time for backward compatibility reasons 131 private List<HomePieceOfFurniture> furnitureWithGroups; 132 133 /** 134 * Creates a home with no furniture, no walls, 135 * and a height equal to 250 cm. 136 */ Home()137 public Home() { 138 this(250); 139 } 140 141 /** 142 * Creates a home with no furniture and no walls. 143 * @param wallHeight default height for home walls 144 */ Home(float wallHeight)145 public Home(float wallHeight) { 146 this(new ArrayList<HomePieceOfFurniture>(), wallHeight); 147 } 148 149 /** 150 * Creates a home with the given <code>furniture</code>, 151 * no walls and a height equal to 250 cm. 152 */ Home(List<HomePieceOfFurniture> furniture)153 public Home(List<HomePieceOfFurniture> furniture) { 154 this(furniture, 250); 155 } 156 Home(List<HomePieceOfFurniture> furniture, float wallHeight)157 private Home(List<HomePieceOfFurniture> furniture, float wallHeight) { 158 this.furniture = new ArrayList<HomePieceOfFurniture>(furniture); 159 this.walls = new ArrayList<Wall>(); 160 this.wallHeight = wallHeight; 161 this.furnitureVisibleProperties = Arrays.asList(new HomePieceOfFurniture.SortableProperty [] { 162 HomePieceOfFurniture.SortableProperty.NAME, 163 HomePieceOfFurniture.SortableProperty.WIDTH, 164 HomePieceOfFurniture.SortableProperty.DEPTH, 165 HomePieceOfFurniture.SortableProperty.HEIGHT, 166 HomePieceOfFurniture.SortableProperty.VISIBLE}); 167 // Init transient lists and other fields 168 init(true); 169 addModelListeners(); 170 } 171 172 /** 173 * Creates a home from an other one. All mutable data of the source <code>home</code> 174 * is cloned to this home and listeners support is reset. 175 * @since 5.0 176 */ Home(Home home)177 protected Home(Home home) { 178 this.wallHeight = home.getWallHeight(); 179 copyHomeData(home, this); 180 initListenersSupport(this); 181 addModelListeners(); 182 } 183 184 /** 185 * Initializes new and transient home fields to their default values 186 * and reads home from <code>in</code> stream with default reading method. 187 */ readObject(ObjectInputStream in)188 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { 189 init(false); 190 in.defaultReadObject(); 191 192 if (KEEP_BACKWARD_COMPATIBLITY) { 193 // Restore furnitureSortedProperty from furnitureSortedPropertyName 194 if (this.furnitureSortedPropertyName != null) { 195 try { 196 this.furnitureSortedProperty = 197 HomePieceOfFurniture.SortableProperty.valueOf(this.furnitureSortedPropertyName); 198 } catch (IllegalArgumentException ex) { 199 // Ignore malformed enum constant 200 } 201 this.furnitureSortedPropertyName = null; 202 } 203 // Restore furnitureVisibleProperties from furnitureVisiblePropertyNames 204 if (this.furnitureVisiblePropertyNames != null) { 205 this.furnitureVisibleProperties = new ArrayList<HomePieceOfFurniture.SortableProperty>(); 206 for (String furnitureVisiblePropertyName : this.furnitureVisiblePropertyNames) { 207 try { 208 this.furnitureVisibleProperties.add( 209 HomePieceOfFurniture.SortableProperty.valueOf(furnitureVisiblePropertyName)); 210 } catch (IllegalArgumentException ex) { 211 // Ignore malformed enum constants 212 } 213 } 214 this.furnitureVisiblePropertyNames = null; 215 } 216 217 // Ensure all wall have an height 218 for (Wall wall : this.walls) { 219 if (wall.getHeight() == null) { 220 wall.setHeight(this.wallHeight); 221 } 222 } 223 224 // Restore referenced HomeDoorOrWindow instances stored in a separate field 225 // for backward compatibility reasons 226 if (this.furnitureWithDoorsAndWindows != null) { 227 this.furniture = this.furnitureWithDoorsAndWindows; 228 this.furnitureWithDoorsAndWindows = null; 229 } 230 231 // Restore referenced HomeFurnitureGroup instances stored in a separate field 232 // for backward compatibility reasons 233 if (this.furnitureWithGroups != null) { 234 this.furniture = this.furnitureWithGroups; 235 this.furnitureWithGroups = null; 236 } 237 238 // Restore environment fields from home fields for compatibility reasons 239 this.environment.setGroundColor(this.groundColor); 240 this.environment.setGroundTexture(this.groundTexture); 241 this.environment.setSkyColor(this.skyColor); 242 this.environment.setLightColor(this.lightColor); 243 this.environment.setWallsAlpha(this.wallsAlpha); 244 245 if (this.version <= 3400) { 246 // Automatically adjust ground color to a darker color 247 int groundColor = this.environment.getGroundColor(); 248 this.environment.setGroundColor( 249 ((((groundColor >> 16) & 0xFF) * 3 / 4) << 16) 250 | ((((groundColor >> 8) & 0xFF) * 3 / 4) << 8) 251 | ((groundColor & 0xFF) * 3 / 4)); 252 } 253 254 // Assign level elevation index from current order if index is equal to -1 255 if (this.levels.size() > 0) { 256 Level previousLevel = this.levels.get(0); 257 if (previousLevel.getElevationIndex() == -1) { 258 previousLevel.setElevationIndex(0); 259 } 260 for (int i = 1; i < this.levels.size(); i++) { 261 Level level = this.levels.get(i); 262 if (level.getElevationIndex() == -1) { 263 if (previousLevel.getElevation() == level.getElevation()) { 264 level.setElevationIndex(previousLevel.getElevationIndex() + 1); 265 } else { 266 level.setElevationIndex(0); 267 } 268 } 269 previousLevel = level; 270 } 271 } 272 273 // Move known visual properties to string properties 274 moveVisualProperty("com.eteks.sweethome3d.swing.PhotoPanel.PhotoDialogX"); 275 moveVisualProperty("com.eteks.sweethome3d.swing.PhotoPanel.PhotoDialogY"); 276 moveVisualProperty("com.eteks.sweethome3d.swing.PhotosPanel.PhotoDialogX"); 277 moveVisualProperty("com.eteks.sweethome3d.swing.PhotosPanel.PhotoDialogY"); 278 moveVisualProperty("com.eteks.sweethome3d.swing.VideoPanel.VideoDialogX"); 279 moveVisualProperty("com.eteks.sweethome3d.swing.VideoPanel.VideoDialogY"); 280 moveVisualProperty("com.eteks.sweethome3d.swing.HomeComponent3D.detachedViewX"); 281 moveVisualProperty("com.eteks.sweethome3d.swing.HomeComponent3D.detachedViewY"); 282 moveVisualProperty("com.eteks.sweethome3d.swing.HomeComponent3D.detachedViewWidth"); 283 moveVisualProperty("com.eteks.sweethome3d.swing.HomeComponent3D.detachedViewHeight"); 284 moveVisualProperty("com.eteks.sweethome3d.swing.HomeComponent3D.detachedView"); 285 moveVisualProperty("com.eteks.sweethome3d.swing.HomeComponent3D.detachedViewDividerLocation"); 286 moveVisualProperty("com.eteks.sweethome3d.SweetHome3D.MainPaneDividerLocation"); 287 moveVisualProperty("com.eteks.sweethome3d.SweetHome3D.CatalogPaneDividerLocation"); 288 moveVisualProperty("com.eteks.sweethome3d.SweetHome3D.PlanPaneDividerLocation"); 289 moveVisualProperty("com.eteks.sweethome3d.SweetHome3D.PlanViewportX"); 290 moveVisualProperty("com.eteks.sweethome3d.SweetHome3D.PlanViewportY"); 291 moveVisualProperty("com.eteks.sweethome3d.SweetHome3D.FurnitureViewportY"); 292 moveVisualProperty("com.eteks.sweethome3d.SweetHome3D.PlanScale"); 293 moveVisualProperty("com.eteks.sweethome3d.SweetHome3D.ExpandedGroups"); 294 moveVisualProperty("com.eteks.sweethome3d.SweetHome3D.FrameX"); 295 moveVisualProperty("com.eteks.sweethome3d.SweetHome3D.FrameY"); 296 moveVisualProperty("com.eteks.sweethome3d.SweetHome3D.FrameWidth"); 297 moveVisualProperty("com.eteks.sweethome3d.SweetHome3D.FrameHeight"); 298 moveVisualProperty("com.eteks.sweethome3d.SweetHome3D.FrameMaximized"); 299 moveVisualProperty("com.eteks.sweethome3d.SweetHome3D.ScreenWidth"); 300 moveVisualProperty("com.eteks.sweethome3d.SweetHome3D.ScreenHeight"); 301 } 302 303 if (this.version < 6400) { 304 // Ensure environment, compass, topCamera and observerCamera have their ID set to the default IDs 305 // Create a copy of environment with the expected ID 306 HomeEnvironment environment = new HomeEnvironment(HOME_ENVIRONMENT_ID, 307 this.environment.getGroundColor(), this.environment.getGroundTexture(), 308 this.environment.getSkyColor(), this.environment.getSkyTexture(), 309 this.environment.getLightColor(), this.environment.getWallsAlpha()); 310 for (String name : this.environment.getPropertyNames()) { 311 environment.setProperty(name, this.environment.getProperty(name)); 312 } 313 environment.setBackgroundImageVisibleOnGround3D(this.environment.isBackgroundImageVisibleOnGround3D()); 314 environment.setAllLevelsVisible(this.environment.isAllLevelsVisible()); 315 environment.setObserverCameraElevationAdjusted(this.environment.isObserverCameraElevationAdjusted()); 316 environment.setCeillingLightColor(this.environment.getCeillingLightColor()); 317 environment.setDrawingMode(this.environment.getDrawingMode()); 318 environment.setSubpartSizeUnderLight(this.environment.getSubpartSizeUnderLight()); 319 environment.setPhotoWidth(this.environment.getPhotoWidth()); 320 environment.setPhotoHeight(this.environment.getPhotoHeight()); 321 environment.setPhotoAspectRatio(this.environment.getPhotoAspectRatio()); 322 environment.setPhotoQuality(this.environment.getPhotoQuality()); 323 environment.setVideoWidth(this.environment.getVideoWidth()); 324 environment.setVideoAspectRatio(this.environment.getVideoAspectRatio()); 325 environment.setVideoQuality(this.environment.getVideoQuality()); 326 environment.setVideoSpeed(this.environment.getVideoSpeed()); 327 environment.setVideoFrameRate(this.environment.getVideoFrameRate()); 328 environment.setVideoCameraPath(this.environment.getVideoCameraPath()); 329 this.environment = environment; 330 331 // Create a copy of compass with the expected ID 332 Compass compass = new Compass(HOME_COMPASS_ID, this.compass.getX(), this.compass.getY(), this.compass.getDiameter()); 333 for (String name : this.compass.getPropertyNames()) { 334 compass.setProperty(name, this.compass.getProperty(name)); 335 } 336 compass.setNorthDirection(this.compass.getNorthDirection()); 337 compass.setLongitude(this.compass.getLongitude()); 338 compass.setLatitude(this.compass.getLatitude()); 339 compass.setTimeZone(this.compass.getTimeZone()); 340 compass.setVisible(this.compass.isVisible()); 341 this.compass = compass; 342 343 // Create copies of cameras with expected IDs 344 Camera topCamera = new Camera(HOME_TOP_CAMERA_ID, this.topCamera.getX(), this.topCamera.getY(), this.topCamera.getZ(), 345 this.topCamera.getYaw(), this.topCamera.getPitch(), this.topCamera.getFieldOfView()); 346 for (String name : this.topCamera.getPropertyNames()) { 347 topCamera.setProperty(name, this.topCamera.getProperty(name)); 348 } 349 topCamera.setLens(this.topCamera.getLens()); 350 topCamera.setTime(this.topCamera.getTime()); 351 if (this.camera == this.topCamera) { 352 this.camera = topCamera; 353 } 354 this.topCamera = topCamera; 355 356 ObserverCamera observerCamera = new ObserverCamera(HOME_OBSERVER_CAMERA_ID, 357 this.observerCamera.getX(), this.observerCamera.getY(), this.observerCamera.getZ(), 358 this.observerCamera.getYaw(), this.observerCamera.getPitch(), this.observerCamera.getFieldOfView()); 359 for (String name : this.observerCamera.getPropertyNames()) { 360 observerCamera.setProperty(name, this.observerCamera.getProperty(name)); 361 } 362 observerCamera.setFixedSize(this.observerCamera.isFixedSize()); 363 observerCamera.setLens(this.observerCamera.getLens()); 364 observerCamera.setTime(this.observerCamera.getTime()); 365 if (this.camera == this.observerCamera) { 366 this.camera = observerCamera; 367 } 368 this.observerCamera = observerCamera; 369 } 370 371 addModelListeners(); 372 } 373 moveVisualProperty(String visualPropertyName)374 private void moveVisualProperty(String visualPropertyName) { 375 if (this.visualProperties.containsKey(visualPropertyName)) { 376 Object value = this.visualProperties.get(visualPropertyName); 377 this.properties.put(visualPropertyName, value != null ? String.valueOf(value) : null); 378 this.visualProperties.remove(visualPropertyName); 379 } 380 } 381 init(boolean newHome)382 private void init(boolean newHome) { 383 // Initialize transient lists 384 this.selectedItems = new ArrayList<Selectable>(); 385 initListenersSupport(this); 386 387 if (this.furnitureVisibleProperties == null) { 388 // Set the furniture properties that were visible before version 0.19 389 this.furnitureVisibleProperties = Arrays.asList(new HomePieceOfFurniture.SortableProperty [] { 390 HomePieceOfFurniture.SortableProperty.NAME, 391 HomePieceOfFurniture.SortableProperty.WIDTH, 392 HomePieceOfFurniture.SortableProperty.DEPTH, 393 HomePieceOfFurniture.SortableProperty.HEIGHT, 394 HomePieceOfFurniture.SortableProperty.COLOR, 395 HomePieceOfFurniture.SortableProperty.MOVABLE, 396 HomePieceOfFurniture.SortableProperty.DOOR_OR_WINDOW, 397 HomePieceOfFurniture.SortableProperty.VISIBLE}); 398 } 399 // Create a default top camera that matches default point of view 400 this.topCamera = new Camera(HOME_TOP_CAMERA_ID, 50, 1050, 1010, 401 (float)Math.PI, (float)Math.PI / 4, (float)Math.PI * 63 / 180); 402 // Create a default observer camera (use a 63� field of view equivalent to a 35mm lens for a 24x36 film) 403 this.observerCamera = new ObserverCamera(HOME_OBSERVER_CAMERA_ID, 50, 50, 170, 404 7 * (float)Math.PI / 4, (float)Math.PI / 16, (float)Math.PI * 63 / 180); 405 this.storedCameras = Collections.emptyList(); 406 // Initialize new fields 407 this.environment = new HomeEnvironment(HOME_ENVIRONMENT_ID); 408 this.rooms = new ArrayList<Room>(); 409 this.polylines = new ArrayList<Polyline>(); 410 this.dimensionLines = new ArrayList<DimensionLine>(); 411 this.labels = new ArrayList<Label>(); 412 this.compass = new Compass(HOME_COMPASS_ID, -100, 50, 100); 413 this.levels = new ArrayList<Level>(); 414 // Let compass be visible only on new homes 415 this.compass.setVisible(newHome); 416 this.visualProperties = new HashMap<String, Object>(); 417 this.properties = new HashMap<String, String>(); 418 419 this.version = CURRENT_VERSION; 420 } 421 initListenersSupport(Home home)422 private static void initListenersSupport(Home home) { 423 home.furnitureChangeSupport = new CollectionChangeSupport<HomePieceOfFurniture>(home); 424 home.selectionListeners = new ArrayList<SelectionListener>(); 425 home.levelsChangeSupport = new CollectionChangeSupport<Level>(home); 426 home.wallsChangeSupport = new CollectionChangeSupport<Wall>(home); 427 home.roomsChangeSupport = new CollectionChangeSupport<Room>(home); 428 home.polylinesChangeSupport = new CollectionChangeSupport<Polyline>(home); 429 home.dimensionLinesChangeSupport = new CollectionChangeSupport<DimensionLine>(home); 430 home.labelsChangeSupport = new CollectionChangeSupport<Label>(home); 431 home.propertyChangeSupport = new PropertyChangeSupport(home); 432 } 433 434 /** 435 * Adds listeners to model. 436 */ addModelListeners()437 private void addModelListeners() { 438 // Add listeners to levels to maintain its elevation order 439 final PropertyChangeListener levelElevationChangeListener = new PropertyChangeListener() { 440 public void propertyChange(PropertyChangeEvent ev) { 441 if (Level.Property.ELEVATION.name().equals(ev.getPropertyName()) 442 || Level.Property.ELEVATION_INDEX.name().equals(ev.getPropertyName())) { 443 Home.this.levels = new ArrayList<Level>(Home.this.levels); 444 Collections.sort(Home.this.levels, LEVEL_ELEVATION_COMPARATOR); 445 } 446 } 447 }; 448 for (Level level : this.levels) { 449 level.addPropertyChangeListener(levelElevationChangeListener); 450 } 451 addLevelsListener(new CollectionListener<Level>() { 452 public void collectionChanged(CollectionEvent<Level> ev) { 453 switch (ev.getType()) { 454 case ADD : 455 ev.getItem().addPropertyChangeListener(levelElevationChangeListener); 456 break; 457 case DELETE : 458 ev.getItem().removePropertyChangeListener(levelElevationChangeListener); 459 break; 460 } 461 } 462 }); 463 } 464 465 /** 466 * Sets the version of this home and writes it to <code>out</code> stream 467 * with default writing method. 468 */ writeObject(java.io.ObjectOutputStream out)469 private void writeObject(java.io.ObjectOutputStream out) throws IOException { 470 this.version = CURRENT_VERSION; 471 472 if (KEEP_BACKWARD_COMPATIBLITY) { 473 HomePieceOfFurniture.SortableProperty homeFurnitureSortedProperty = this.furnitureSortedProperty; 474 List<HomePieceOfFurniture.SortableProperty> homeFurnitureVisibleProperties = this.furnitureVisibleProperties; 475 List<HomePieceOfFurniture> homeFurniture = this.furniture; 476 try { 477 if (this.furnitureSortedProperty != null) { 478 this.furnitureSortedPropertyName = this.furnitureSortedProperty.name(); 479 // Store in furnitureSortedProperty only backward compatible property 480 if (!isFurnitureSortedPropertyBackwardCompatible(this.furnitureSortedProperty)) { 481 this.furnitureSortedProperty = null; 482 } 483 } 484 485 this.furnitureVisiblePropertyNames = new ArrayList<String>(); 486 // Store in furnitureVisibleProperties only backward compatible properties 487 this.furnitureVisibleProperties = new ArrayList<HomePieceOfFurniture.SortableProperty>(); 488 for (HomePieceOfFurniture.SortableProperty visibleProperty : homeFurnitureVisibleProperties) { 489 this.furnitureVisiblePropertyNames.add(visibleProperty.name()); 490 if (isFurnitureSortedPropertyBackwardCompatible(visibleProperty)) { 491 this.furnitureVisibleProperties.add(visibleProperty); 492 } 493 } 494 495 // Store referenced HomeFurnitureGroup instances in a separate field 496 // for backward compatibility reasons (version < 2.3) 497 this.furnitureWithGroups = this.furniture; 498 // Serialize a furnitureWithDoorsAndWindows field that contains only 499 // HomePieceOfFurniture, HomeDoorOrWindow and HomeLight instances 500 // for backward compatibility reasons (version < 1.7) 501 this.furnitureWithDoorsAndWindows = new ArrayList<HomePieceOfFurniture>(this.furniture.size()); 502 // Serialize a furniture field that contains only HomePieceOfFurniture instances 503 this.furniture = new ArrayList<HomePieceOfFurniture>(this.furniture.size()); 504 for (HomePieceOfFurniture piece : this.furnitureWithGroups) { 505 if (piece.getClass() == HomePieceOfFurniture.class) { 506 this.furnitureWithDoorsAndWindows.add(piece); 507 this.furniture.add(piece); 508 } else { 509 if (piece.getClass() == HomeFurnitureGroup.class) { 510 // Add the ungrouped pieces to furniture and furnitureWithDoorsAndWindows list 511 for (HomePieceOfFurniture groupPiece : getGroupFurniture((HomeFurnitureGroup)piece)) { 512 this.furnitureWithDoorsAndWindows.add(groupPiece); 513 if (groupPiece.getClass() == HomePieceOfFurniture.class) { 514 this.furniture.add(groupPiece); 515 } else { 516 // Create backward compatible instances 517 this.furniture.add(new HomePieceOfFurniture(groupPiece)); 518 } 519 } 520 } else { 521 this.furnitureWithDoorsAndWindows.add(piece); 522 // Create backward compatible instances 523 this.furniture.add(new HomePieceOfFurniture(piece)); 524 } 525 } 526 } 527 528 // Store environment fields in home fields for compatibility reasons 529 this.groundColor = this.environment.getGroundColor(); 530 this.groundTexture = this.environment.getGroundTexture(); 531 this.skyColor = this.environment.getSkyColor(); 532 this.lightColor = this.environment.getLightColor(); 533 this.wallsAlpha = this.environment.getWallsAlpha(); 534 535 out.defaultWriteObject(); 536 } finally { 537 // Restore home values 538 this.furniture = homeFurniture; 539 this.furnitureWithDoorsAndWindows = null; 540 this.furnitureWithGroups = null; 541 542 this.furnitureSortedProperty = homeFurnitureSortedProperty; 543 this.furnitureVisibleProperties = homeFurnitureVisibleProperties; 544 // Set furnitureSortedPropertyName and furnitureVisiblePropertyNames to null 545 // (they are used only for serialization) 546 this.furnitureSortedPropertyName = null; 547 this.furnitureVisiblePropertyNames = null; 548 } 549 } else { 550 out.defaultWriteObject(); 551 } 552 } 553 554 /** 555 * Returns <code>true</code> if the given <code>property</code> is compatible 556 * with the first set of sortable properties that existed in <code>HomePieceOfFurniture</code> class. 557 */ isFurnitureSortedPropertyBackwardCompatible(HomePieceOfFurniture.SortableProperty property)558 private boolean isFurnitureSortedPropertyBackwardCompatible(HomePieceOfFurniture.SortableProperty property) { 559 switch (property) { 560 case NAME : 561 case WIDTH : 562 case DEPTH : 563 case HEIGHT : 564 case MOVABLE : 565 case DOOR_OR_WINDOW : 566 case COLOR : 567 case VISIBLE : 568 case X : 569 case Y : 570 case ELEVATION : 571 case ANGLE : 572 return true; 573 default : 574 return false; 575 } 576 } 577 578 /** 579 * Returns all the pieces of the given <code>furnitureGroup</code>. 580 */ getGroupFurniture(HomeFurnitureGroup furnitureGroup)581 private List<HomePieceOfFurniture> getGroupFurniture(HomeFurnitureGroup furnitureGroup) { 582 List<HomePieceOfFurniture> groupFurniture = new ArrayList<HomePieceOfFurniture>(); 583 for (HomePieceOfFurniture piece : furnitureGroup.getFurniture()) { 584 if (piece instanceof HomeFurnitureGroup) { 585 groupFurniture.addAll(getGroupFurniture((HomeFurnitureGroup)piece)); 586 } else { 587 groupFurniture.add(piece); 588 } 589 } 590 return groupFurniture; 591 } 592 593 /** 594 * Adds the level <code>listener</code> in parameter to this home. 595 * @param listener the listener to add 596 * @since 3.4 597 */ addLevelsListener(CollectionListener<Level> listener)598 public void addLevelsListener(CollectionListener<Level> listener) { 599 this.levelsChangeSupport.addCollectionListener(listener); 600 } 601 602 /** 603 * Removes the level <code>listener</code> in parameter from this home. 604 * @param listener the listener to remove 605 * @since 3.4 606 */ removeLevelsListener(CollectionListener<Level> listener)607 public void removeLevelsListener(CollectionListener<Level> listener) { 608 this.levelsChangeSupport.removeCollectionListener(listener); 609 } 610 611 /** 612 * Returns an unmodifiable collection of the levels of this home. 613 * @since 3.4 614 */ getLevels()615 public List<Level> getLevels() { 616 return Collections.unmodifiableList(this.levels); 617 } 618 619 /** 620 * Adds the given <code>level</code> to the list of levels of this home. 621 * Once the <code>level</code> is added, level listeners added to this home will receive a 622 * {@link CollectionListener#collectionChanged(CollectionEvent) collectionChanged} 623 * notification, with an {@link CollectionEvent#getType() event type} 624 * equal to {@link CollectionEvent.Type#ADD ADD}. 625 * @param level the level to add 626 * @since 3.4 627 */ addLevel(Level level)628 public void addLevel(Level level) { 629 if (level.getElevationIndex() < 0) { 630 // Search elevation index of the added level 631 int elevationIndex = 0; 632 for (Level homeLevel : this.levels) { 633 if (homeLevel.getElevation() == level.getElevation()) { 634 elevationIndex = homeLevel.getElevationIndex() + 1; 635 } else if (homeLevel.getElevation() > level.getElevation()) { 636 break; 637 } 638 } 639 level.setElevationIndex(elevationIndex); 640 } 641 // Make a copy of the list to avoid conflicts in the list returned by getLevels 642 this.levels = new ArrayList<Level>(this.levels); 643 // Search at which index should be inserted the new level 644 int index = Collections.binarySearch(this.levels, level, LEVEL_ELEVATION_COMPARATOR); 645 int levelIndex; 646 if (index >= 0) { 647 levelIndex = index; 648 } else { 649 levelIndex = -(index + 1); 650 } 651 this.levels.add(levelIndex, level); 652 this.levelsChangeSupport.fireCollectionChanged(level, levelIndex, CollectionEvent.Type.ADD); 653 } 654 655 /** 656 * Removes the given <code>level</code> from the set of levels of this home 657 * and all the furniture, walls, rooms, dimension lines and labels that belong to this level. 658 * Once the <code>level</code> is removed, level listeners added to this home will receive a 659 * {@link CollectionListener#collectionChanged(CollectionEvent) collectionChanged} 660 * notification, with an {@link CollectionEvent#getType() event type} 661 * equal to {@link CollectionEvent.Type#DELETE DELETE}. 662 * @param level the level to remove 663 * @since 3.4 664 */ deleteLevel(Level level)665 public void deleteLevel(Level level) { 666 int index = this.levels.indexOf(level); 667 if (index != -1) { 668 for (HomePieceOfFurniture piece : this.furniture) { 669 if (piece.getLevel() == level) { 670 deletePieceOfFurniture(piece); 671 } 672 } 673 for (Room room : this.rooms) { 674 if (room.getLevel() == level) { 675 deleteRoom(room); 676 } 677 } 678 for (Wall wall : this.walls) { 679 if (wall.getLevel() == level) { 680 deleteWall(wall); 681 } 682 } 683 for (Polyline polyline : this.polylines) { 684 if (polyline.getLevel() == level) { 685 deletePolyline(polyline); 686 } 687 } 688 for (DimensionLine dimensionLine : this.dimensionLines) { 689 if (dimensionLine.getLevel() == level) { 690 deleteDimensionLine(dimensionLine); 691 } 692 } 693 for (Label label : this.labels) { 694 if (label.getLevel() == level) { 695 deleteLabel(label); 696 } 697 } 698 if (this.selectedLevel == level) { 699 if (this.levels.size() == 1) { 700 setSelectedLevel(null); 701 setAllLevelsSelection(false); 702 } else { 703 setSelectedLevel(this.levels.get(index >= 1 ? index - 1 : index + 1)); 704 } 705 } 706 // Make a copy of the list to avoid conflicts in the list returned by getLevels 707 this.levels = new ArrayList<Level>(this.levels); 708 this.levels.remove(index); 709 this.levelsChangeSupport.fireCollectionChanged(level, index, CollectionEvent.Type.DELETE); 710 } 711 } 712 713 /** 714 * Returns the selected level in home or <code>null</code> if home has no level. 715 * @since 3.4 716 */ getSelectedLevel()717 public Level getSelectedLevel() { 718 return this.selectedLevel; 719 } 720 721 /** 722 * Sets the selected level in home and notifies listeners of the change. 723 * @param selectedLevel the level to select 724 * @since 3.4 725 */ setSelectedLevel(Level selectedLevel)726 public void setSelectedLevel(Level selectedLevel) { 727 if (selectedLevel != this.selectedLevel) { 728 Level oldSelectedLevel = this.selectedLevel; 729 this.selectedLevel = selectedLevel; 730 this.propertyChangeSupport.firePropertyChange(Property.SELECTED_LEVEL.name(), oldSelectedLevel, selectedLevel); 731 } 732 } 733 734 /** 735 * Returns <code>true</code> if the selected items in this home are from all levels. 736 * @since 4.4 737 */ isAllLevelsSelection()738 public boolean isAllLevelsSelection() { 739 return this.allLevelsSelection; 740 } 741 742 /** 743 * Sets whether the selected items in this home are from all levels, and notifies listeners of the change. 744 * @since 4.4 745 */ setAllLevelsSelection(boolean selectionAtAllLevels)746 public void setAllLevelsSelection(boolean selectionAtAllLevels) { 747 if (selectionAtAllLevels != this.allLevelsSelection) { 748 this.allLevelsSelection = selectionAtAllLevels; 749 this.propertyChangeSupport.firePropertyChange(Property.ALL_LEVELS_SELECTION.name(), !selectionAtAllLevels, selectionAtAllLevels); 750 } 751 } 752 753 /** 754 * Adds the furniture <code>listener</code> in parameter to this home. 755 * @param listener the listener to add 756 */ addFurnitureListener(CollectionListener<HomePieceOfFurniture> listener)757 public void addFurnitureListener(CollectionListener<HomePieceOfFurniture> listener) { 758 this.furnitureChangeSupport.addCollectionListener(listener); 759 } 760 761 /** 762 * Removes the furniture <code>listener</code> in parameter from this home. 763 * @param listener the listener to remove 764 */ removeFurnitureListener(CollectionListener<HomePieceOfFurniture> listener)765 public void removeFurnitureListener(CollectionListener<HomePieceOfFurniture> listener) { 766 this.furnitureChangeSupport.removeCollectionListener(listener); 767 } 768 769 /** 770 * Returns an unmodifiable list of the furniture managed by this home. 771 * This furniture in this list is always sorted in the index order they were added to home. 772 */ getFurniture()773 public List<HomePieceOfFurniture> getFurniture() { 774 return Collections.unmodifiableList(this.furniture); 775 } 776 777 /** 778 * Adds the <code>piece</code> in parameter to this home at the end of the furniture list. 779 * Once the <code>piece</code> is added, furniture listeners added to this home will receive a 780 * {@link CollectionListener#collectionChanged(CollectionEvent) collectionChanged} 781 * notification. 782 * @param piece the piece to add 783 */ addPieceOfFurniture(HomePieceOfFurniture piece)784 public void addPieceOfFurniture(HomePieceOfFurniture piece) { 785 addPieceOfFurniture(piece, this.furniture.size()); 786 } 787 788 /** 789 * Adds the <code>piece</code> in parameter at a given <code>index</code>. 790 * Once the <code>piece</code> is added, furniture listeners added to this home will receive a 791 * {@link CollectionListener#collectionChanged(CollectionEvent) collectionChanged} 792 * notification. 793 * @param piece the piece to add 794 * @param index the index at which the piece will be added 795 */ addPieceOfFurniture(HomePieceOfFurniture piece, int index)796 public void addPieceOfFurniture(HomePieceOfFurniture piece, int index) { 797 // Make a copy of the list to avoid conflicts in the list returned by getFurniture 798 this.furniture = new ArrayList<HomePieceOfFurniture>(this.furniture); 799 piece.setLevel(this.selectedLevel); 800 this.furniture.add(index, piece); 801 this.furnitureChangeSupport.fireCollectionChanged(piece, index, CollectionEvent.Type.ADD); 802 } 803 804 /** 805 * Adds the <code>piece</code> in parameter at the <code>index</code> in the given <code>group</code>. 806 * Once the <code>piece</code> is added, furniture listeners added to this home will receive a 807 * {@link CollectionListener#collectionChanged(CollectionEvent) collectionChanged} 808 * notification with an event {@link CollectionEvent#getIndex() index} equal to -1. 809 * @param piece the piece to add 810 * @param group the group to which the piece will be added 811 * @param index the index at which the piece will be added 812 */ addPieceOfFurnitureToGroup(HomePieceOfFurniture piece, HomeFurnitureGroup group, int index)813 public void addPieceOfFurnitureToGroup(HomePieceOfFurniture piece, HomeFurnitureGroup group, int index) { 814 piece.setLevel(this.selectedLevel); 815 group.addPieceOfFurniture(piece, index); 816 this.furnitureChangeSupport.fireCollectionChanged(piece, CollectionEvent.Type.ADD); 817 } 818 819 /** 820 * Deletes the <code>piece</code> in parameter from this home. 821 * Once the <code>piece</code> is deleted, furniture listeners added to this home will receive a 822 * {@link CollectionListener#collectionChanged(CollectionEvent) collectionChanged} 823 * notification. If the removed <code>piece</code> belongs to a group, the 824 * {@link CollectionEvent#getIndex() index} of the event will be -1. 825 * @param piece the piece to remove 826 */ deletePieceOfFurniture(HomePieceOfFurniture piece)827 public void deletePieceOfFurniture(HomePieceOfFurniture piece) { 828 // Ensure selectedItems don't keep a reference to piece 829 deselectItem(piece); 830 int index = this.furniture.indexOf(piece); 831 HomeFurnitureGroup group = index == -1 832 ? getPieceOfFurnitureGroup(piece, null, this.furniture) 833 : null; 834 if (index != -1 835 || group != null) { 836 piece.setLevel(null); 837 // Make a copy of the list to avoid conflicts in the list returned by getFurniture 838 this.furniture = new ArrayList<HomePieceOfFurniture>(this.furniture); 839 if (group != null) { 840 group.deletePieceOfFurniture(piece); 841 this.furnitureChangeSupport.fireCollectionChanged(piece, CollectionEvent.Type.DELETE); 842 } else { 843 this.furniture.remove(index); 844 this.furnitureChangeSupport.fireCollectionChanged(piece, index, CollectionEvent.Type.DELETE); 845 } 846 } 847 } 848 849 /** 850 * Returns the furniture group that contains the given <code>piece</code> or <code>null</code> 851 * if it can't be found. 852 */ getPieceOfFurnitureGroup(HomePieceOfFurniture piece, HomeFurnitureGroup furnitureGroup, List<HomePieceOfFurniture> furniture)853 private HomeFurnitureGroup getPieceOfFurnitureGroup(HomePieceOfFurniture piece, 854 HomeFurnitureGroup furnitureGroup, 855 List<HomePieceOfFurniture> furniture) { 856 for (HomePieceOfFurniture homePiece : furniture) { 857 if (homePiece.equals(piece)) { 858 return furnitureGroup; 859 } else if (homePiece instanceof HomeFurnitureGroup) { 860 HomeFurnitureGroup group = getPieceOfFurnitureGroup(piece, 861 (HomeFurnitureGroup)homePiece, ((HomeFurnitureGroup)homePiece).getFurniture()); 862 if (group != null) { 863 return group; 864 } 865 } 866 } 867 return null; 868 } 869 870 /** 871 * Adds the selection <code>listener</code> in parameter to this home. 872 * @param listener the listener to add 873 */ addSelectionListener(SelectionListener listener)874 public void addSelectionListener(SelectionListener listener) { 875 this.selectionListeners.add(listener); 876 } 877 878 /** 879 * Removes the selection <code>listener</code> in parameter from this home. 880 * @param listener the listener to remove 881 */ removeSelectionListener(SelectionListener listener)882 public void removeSelectionListener(SelectionListener listener) { 883 this.selectionListeners.remove(listener); 884 } 885 886 /** 887 * Returns an unmodifiable list of the selected items in home. 888 */ getSelectedItems()889 public List<Selectable> getSelectedItems() { 890 return Collections.unmodifiableList(this.selectedItems); 891 } 892 893 /** 894 * Sets the selected items in home and notifies listeners selection change. 895 * @param selectedItems the list of selected items 896 */ setSelectedItems(List<? extends Selectable> selectedItems)897 public void setSelectedItems(List<? extends Selectable> selectedItems) { 898 // Make a copy of the list to avoid conflicts in the list returned by getSelectedItems 899 this.selectedItems = new ArrayList<Selectable>(selectedItems); 900 if (!this.selectionListeners.isEmpty()) { 901 SelectionEvent selectionEvent = new SelectionEvent(this, getSelectedItems()); 902 // Work on a copy of selectionListeners to ensure a listener 903 // can modify safely listeners list 904 SelectionListener [] listeners = this.selectionListeners. 905 toArray(new SelectionListener [this.selectionListeners.size()]); 906 for (SelectionListener listener : listeners) { 907 listener.selectionChanged(selectionEvent); 908 } 909 } 910 } 911 912 /** 913 * Deselects <code>item</code> if it's selected and notifies listeners selection change. 914 * @param item the item to remove from selected items 915 * @since 2.2 916 */ deselectItem(Selectable item)917 public void deselectItem(Selectable item) { 918 int pieceSelectionIndex = this.selectedItems.indexOf(item); 919 if (pieceSelectionIndex != -1) { 920 List<Selectable> selectedItems = new ArrayList<Selectable>(getSelectedItems()); 921 selectedItems.remove(pieceSelectionIndex); 922 setSelectedItems(selectedItems); 923 } 924 } 925 926 /** 927 * Adds the room <code>listener</code> in parameter to this home. 928 * @param listener the listener to add 929 */ addRoomsListener(CollectionListener<Room> listener)930 public void addRoomsListener(CollectionListener<Room> listener) { 931 this.roomsChangeSupport.addCollectionListener(listener); 932 } 933 934 /** 935 * Removes the room <code>listener</code> in parameter from this home. 936 * @param listener the listener to remove 937 */ removeRoomsListener(CollectionListener<Room> listener)938 public void removeRoomsListener(CollectionListener<Room> listener) { 939 this.roomsChangeSupport.removeCollectionListener(listener); 940 } 941 942 /** 943 * Returns an unmodifiable collection of the rooms of this home. 944 */ getRooms()945 public List<Room> getRooms() { 946 return Collections.unmodifiableList(this.rooms); 947 } 948 949 /** 950 * Adds the given <code>room</code> at the end of the rooms list of this home. 951 * Once the <code>room</code> is added, room listeners added to this home will receive a 952 * {@link CollectionListener#collectionChanged(CollectionEvent) collectionChanged} 953 * notification, with an {@link CollectionEvent#getType() event type} 954 * equal to {@link CollectionEvent.Type#ADD ADD}. 955 * @param room the room to add 956 */ addRoom(Room room)957 public void addRoom(Room room) { 958 addRoom(room, this.rooms.size()); 959 } 960 961 /** 962 * Adds the <code>room</code> in parameter at a given <code>index</code>. 963 * Once the <code>room</code> is added, room listeners added to this home will receive a 964 * {@link CollectionListener#collectionChanged(CollectionEvent) collectionChanged} 965 * notification, with an {@link CollectionEvent#getType() event type} 966 * equal to {@link CollectionEvent.Type#ADD ADD}. 967 * @param room the room to add 968 * @param index the index at which the room will be added 969 */ addRoom(Room room, int index)970 public void addRoom(Room room, int index) { 971 // Make a copy of the list to avoid conflicts in the list returned by getRooms 972 this.rooms = new ArrayList<Room>(this.rooms); 973 this.rooms.add(index, room); 974 room.setLevel(this.selectedLevel); 975 this.roomsChangeSupport.fireCollectionChanged(room, index, CollectionEvent.Type.ADD); 976 } 977 978 /** 979 * Removes the given <code>room</code> from the set of rooms of this home. 980 * Once the <code>room</code> is removed, room listeners added to this home will receive a 981 * {@link CollectionListener#collectionChanged(CollectionEvent) collectionChanged} 982 * notification, with an {@link CollectionEvent#getType() event type} 983 * equal to {@link CollectionEvent.Type#DELETE DELETE}. 984 * @param room the room to remove 985 */ deleteRoom(Room room)986 public void deleteRoom(Room room) { 987 // Ensure selectedItems don't keep a reference to room 988 deselectItem(room); 989 int index = this.rooms.indexOf(room); 990 if (index != -1) { 991 room.setLevel(null); 992 // Make a copy of the list to avoid conflicts in the list returned by getRooms 993 this.rooms = new ArrayList<Room>(this.rooms); 994 this.rooms.remove(index); 995 this.roomsChangeSupport.fireCollectionChanged(room, index, CollectionEvent.Type.DELETE); 996 } 997 } 998 999 /** 1000 * Adds the wall <code>listener</code> in parameter to this home. 1001 * @param listener the listener to add 1002 */ addWallsListener(CollectionListener<Wall> listener)1003 public void addWallsListener(CollectionListener<Wall> listener) { 1004 this.wallsChangeSupport.addCollectionListener(listener); 1005 } 1006 1007 /** 1008 * Removes the wall <code>listener</code> in parameter from this home. 1009 * @param listener the listener to remove 1010 */ removeWallsListener(CollectionListener<Wall> listener)1011 public void removeWallsListener(CollectionListener<Wall> listener) { 1012 this.wallsChangeSupport.removeCollectionListener(listener); 1013 } 1014 1015 /** 1016 * Returns an unmodifiable collection of the walls of this home. 1017 */ getWalls()1018 public Collection<Wall> getWalls() { 1019 return Collections.unmodifiableCollection(this.walls); 1020 } 1021 1022 /** 1023 * Adds the given <code>wall</code> to the set of walls of this home. 1024 * Once the <code>wall</code> is added, wall listeners added to this home will receive a 1025 * {@link CollectionListener#collectionChanged(CollectionEvent) collectionChanged} 1026 * notification, with an {@link CollectionEvent#getType() event type} 1027 * equal to {@link CollectionEvent.Type#ADD ADD}. 1028 * @param wall the wall to add 1029 */ addWall(Wall wall)1030 public void addWall(Wall wall) { 1031 // Make a copy of the list to avoid conflicts in the list returned by getWalls 1032 this.walls = new ArrayList<Wall>(this.walls); 1033 this.walls.add(wall); 1034 wall.setLevel(this.selectedLevel); 1035 this.wallsChangeSupport.fireCollectionChanged(wall, CollectionEvent.Type.ADD); 1036 } 1037 1038 /** 1039 * Removes the given <code>wall</code> from the set of walls of this home. 1040 * Once the <code>wall</code> is removed, wall listeners added to this home will receive a 1041 * {@link CollectionListener#collectionChanged(CollectionEvent) collectionChanged} 1042 * notification, with an {@link CollectionEvent#getType() event type} 1043 * equal to {@link CollectionEvent.Type#DELETE DELETE}. 1044 * If any wall is attached to <code>wall</code> they will be detached from it. 1045 * @param wall the wall to remove 1046 */ deleteWall(Wall wall)1047 public void deleteWall(Wall wall) { 1048 // Ensure selectedItems don't keep a reference to wall 1049 deselectItem(wall); 1050 // Detach any other wall attached to wall 1051 for (Wall otherWall : getWalls()) { 1052 if (wall.equals(otherWall.getWallAtStart())) { 1053 otherWall.setWallAtStart(null); 1054 } else if (wall.equals(otherWall.getWallAtEnd())) { 1055 otherWall.setWallAtEnd(null); 1056 } 1057 } 1058 int index = this.walls.indexOf(wall); 1059 if (index != -1) { 1060 wall.setLevel(null); 1061 // Make a copy of the list to avoid conflicts in the list returned by getWalls 1062 this.walls = new ArrayList<Wall>(this.walls); 1063 this.walls.remove(index); 1064 this.wallsChangeSupport.fireCollectionChanged(wall, CollectionEvent.Type.DELETE); 1065 } 1066 } 1067 1068 /** 1069 * Adds the polyline <code>listener</code> in parameter to this home. 1070 * @param listener the listener to add 1071 * @since 5.0 1072 */ addPolylinesListener(CollectionListener<Polyline> listener)1073 public void addPolylinesListener(CollectionListener<Polyline> listener) { 1074 this.polylinesChangeSupport.addCollectionListener(listener); 1075 } 1076 1077 /** 1078 * Removes the polyline <code>listener</code> in parameter from this home. 1079 * @param listener the listener to remove 1080 * @since 5.0 1081 */ removePolylinesListener(CollectionListener<Polyline> listener)1082 public void removePolylinesListener(CollectionListener<Polyline> listener) { 1083 this.polylinesChangeSupport.removeCollectionListener(listener); 1084 } 1085 1086 /** 1087 * Returns an unmodifiable collection of the polylines of this home. 1088 * @since 5.0 1089 */ getPolylines()1090 public List<Polyline> getPolylines() { 1091 return Collections.unmodifiableList(this.polylines); 1092 } 1093 1094 /** 1095 * Adds a given <code>polyline</code> at the end of the polylines list of this home. 1096 * Once the <code>polyline</code> is added, polyline listeners added to this home will receive a 1097 * {@link CollectionListener#collectionChanged(CollectionEvent) collectionChanged} 1098 * notification, with an {@link CollectionEvent#getType() event type} 1099 * equal to {@link CollectionEvent.Type#ADD ADD}. 1100 * @param polyline the polyline to add 1101 * @since 5.0 1102 */ addPolyline(Polyline polyline)1103 public void addPolyline(Polyline polyline) { 1104 addPolyline(polyline, this.polylines.size()); 1105 } 1106 1107 /** 1108 * Adds a <code>polyline</code> at a given <code>index</code> of the set of polylines of this home. 1109 * Once the <code>polyline</code> is added, polyline listeners added to this home will receive a 1110 * {@link CollectionListener#collectionChanged(CollectionEvent) collectionChanged} 1111 * notification, with an {@link CollectionEvent#getType() event type} 1112 * equal to {@link CollectionEvent.Type#ADD ADD}. 1113 * @param polyline the polyline to add 1114 * @param index the index at which the polyline will be added 1115 * @since 5.0 1116 */ addPolyline(Polyline polyline, int index)1117 public void addPolyline(Polyline polyline, int index) { 1118 // Make a copy of the list to avoid conflicts in the list returned by getPolylines 1119 this.polylines = new ArrayList<Polyline>(this.polylines); 1120 this.polylines.add(index, polyline); 1121 polyline.setLevel(this.selectedLevel); 1122 this.polylinesChangeSupport.fireCollectionChanged(polyline, CollectionEvent.Type.ADD); 1123 } 1124 1125 /** 1126 * Removes a given <code>polyline</code> from the set of polylines of this home. 1127 * Once the <code>polyline</code> is removed, polyline listeners added to this home will receive a 1128 * {@link CollectionListener#collectionChanged(CollectionEvent) collectionChanged} 1129 * notification, with an {@link CollectionEvent#getType() event type} 1130 * equal to {@link CollectionEvent.Type#DELETE DELETE}. 1131 * @param polyline the polyline to remove 1132 * @since 5.0 1133 */ deletePolyline(Polyline polyline)1134 public void deletePolyline(Polyline polyline) { 1135 // Ensure selectedItems don't keep a reference to polyline 1136 deselectItem(polyline); 1137 int index = this.polylines.indexOf(polyline); 1138 if (index != -1) { 1139 polyline.setLevel(null); 1140 // Make a copy of the list to avoid conflicts in the list returned by getPolylines 1141 this.polylines = new ArrayList<Polyline>(this.polylines); 1142 this.polylines.remove(index); 1143 this.polylinesChangeSupport.fireCollectionChanged(polyline, CollectionEvent.Type.DELETE); 1144 } 1145 } 1146 1147 /** 1148 * Adds the dimension line <code>listener</code> in parameter to this home. 1149 * @param listener the listener to add 1150 */ addDimensionLinesListener(CollectionListener<DimensionLine> listener)1151 public void addDimensionLinesListener(CollectionListener<DimensionLine> listener) { 1152 this.dimensionLinesChangeSupport.addCollectionListener(listener); 1153 } 1154 1155 /** 1156 * Removes the dimension line <code>listener</code> in parameter from this home. 1157 * @param listener the listener to remove 1158 */ removeDimensionLinesListener(CollectionListener<DimensionLine> listener)1159 public void removeDimensionLinesListener(CollectionListener<DimensionLine> listener) { 1160 this.dimensionLinesChangeSupport.removeCollectionListener(listener); 1161 } 1162 1163 /** 1164 * Returns an unmodifiable collection of the dimension lines of this home. 1165 */ getDimensionLines()1166 public Collection<DimensionLine> getDimensionLines() { 1167 return Collections.unmodifiableCollection(this.dimensionLines); 1168 } 1169 1170 /** 1171 * Adds the given dimension line to the set of dimension lines of this home. 1172 * Once <code>dimensionLine</code> is added, dimension line listeners added 1173 * to this home will receive a 1174 * {@link CollectionListener#collectionChanged(CollectionEvent) collectionChanged} 1175 * notification, with an {@link CollectionEvent#getType() event type} 1176 * equal to {@link CollectionEvent.Type#ADD ADD}. 1177 * @param dimensionLine the dimension line to add 1178 */ addDimensionLine(DimensionLine dimensionLine)1179 public void addDimensionLine(DimensionLine dimensionLine) { 1180 // Make a copy of the list to avoid conflicts in the list returned by getDimensionLines 1181 this.dimensionLines = new ArrayList<DimensionLine>(this.dimensionLines); 1182 this.dimensionLines.add(dimensionLine); 1183 dimensionLine.setLevel(this.selectedLevel); 1184 this.dimensionLinesChangeSupport.fireCollectionChanged(dimensionLine, CollectionEvent.Type.ADD); 1185 } 1186 1187 /** 1188 * Removes the given dimension line from the set of dimension lines of this home. 1189 * Once <code>dimensionLine</code> is removed, dimension line listeners added 1190 * to this home will receive a 1191 * {@link CollectionListener#collectionChanged(CollectionEvent) collectionChanged} 1192 * notification, with an {@link CollectionEvent#getType() event type} 1193 * equal to {@link CollectionEvent.Type#DELETE DELETE}. 1194 * @param dimensionLine the dimension line to remove 1195 */ deleteDimensionLine(DimensionLine dimensionLine)1196 public void deleteDimensionLine(DimensionLine dimensionLine) { 1197 // Ensure selectedItems don't keep a reference to dimension line 1198 deselectItem(dimensionLine); 1199 int index = this.dimensionLines.indexOf(dimensionLine); 1200 if (index != -1) { 1201 dimensionLine.setLevel(null); 1202 // Make a copy of the list to avoid conflicts in the list returned by getDimensionLines 1203 this.dimensionLines = new ArrayList<DimensionLine>(this.dimensionLines); 1204 this.dimensionLines.remove(index); 1205 this.dimensionLinesChangeSupport.fireCollectionChanged(dimensionLine, CollectionEvent.Type.DELETE); 1206 } 1207 } 1208 1209 /** 1210 * Adds the label <code>listener</code> in parameter to this home. 1211 * @param listener the listener to add 1212 */ addLabelsListener(CollectionListener<Label> listener)1213 public void addLabelsListener(CollectionListener<Label> listener) { 1214 this.labelsChangeSupport.addCollectionListener(listener); 1215 } 1216 1217 /** 1218 * Removes the label <code>listener</code> in parameter from this home. 1219 * @param listener the listener to remove 1220 */ removeLabelsListener(CollectionListener<Label> listener)1221 public void removeLabelsListener(CollectionListener<Label> listener) { 1222 this.labelsChangeSupport.removeCollectionListener(listener); 1223 } 1224 1225 /** 1226 * Returns an unmodifiable collection of the labels of this home. 1227 */ getLabels()1228 public Collection<Label> getLabels() { 1229 return Collections.unmodifiableCollection(this.labels); 1230 } 1231 1232 /** 1233 * Adds the given label to the set of labels of this home. 1234 * Once <code>label</code> is added, label listeners added 1235 * to this home will receive a 1236 * {@link CollectionListener#collectionChanged(CollectionEvent) collectionChanged} 1237 * notification, with an {@link CollectionEvent#getType() event type} 1238 * equal to {@link CollectionEvent.Type#ADD ADD}. 1239 * @param label the label to add 1240 */ addLabel(Label label)1241 public void addLabel(Label label) { 1242 // Make a copy of the list to avoid conflicts in the list returned by getLabels 1243 this.labels = new ArrayList<Label>(this.labels); 1244 this.labels.add(label); 1245 label.setLevel(this.selectedLevel); 1246 this.labelsChangeSupport.fireCollectionChanged(label, CollectionEvent.Type.ADD); 1247 } 1248 1249 /** 1250 * Removes the given label from the set of labels of this home. 1251 * Once <code>label</code> is removed, label listeners added to this home will receive a 1252 * {@link CollectionListener#collectionChanged(CollectionEvent) collectionChanged} 1253 * notification, with an {@link CollectionEvent#getType() event type} 1254 * equal to {@link CollectionEvent.Type#DELETE DELETE}. 1255 * @param label the label to remove 1256 */ deleteLabel(Label label)1257 public void deleteLabel(Label label) { 1258 // Ensure selectedItems don't keep a reference to label 1259 deselectItem(label); 1260 int index = this.labels.indexOf(label); 1261 if (index != -1) { 1262 label.setLevel(null); 1263 // Make a copy of the list to avoid conflicts in the list returned by getLabels 1264 this.labels = new ArrayList<Label>(this.labels); 1265 this.labels.remove(index); 1266 this.labelsChangeSupport.fireCollectionChanged(label, CollectionEvent.Type.DELETE); 1267 } 1268 } 1269 1270 /** 1271 * Returns all the selectable and viewable items in this home, except the observer camera. 1272 * @return a list containing viewable walls, rooms, furniture, dimension lines, polylines, labels and compass. 1273 * @since 5.0 1274 */ getSelectableViewableItems()1275 public List<Selectable> getSelectableViewableItems() { 1276 List<Selectable> items = new ArrayList<Selectable>(); 1277 addViewableItems(this.walls, items); 1278 addViewableItems(this.rooms, items); 1279 addViewableItems(this.dimensionLines, items); 1280 addViewableItems(this.polylines, items); 1281 addViewableItems(this.labels, items); 1282 for (HomePieceOfFurniture piece : getFurniture()) { 1283 if (piece.isVisible() 1284 && (piece.getLevel() == null 1285 || piece.getLevel().isViewable())) { 1286 items.add(piece); 1287 } 1288 } 1289 if (this.compass.isVisible()) { 1290 items.add(this.compass); 1291 } 1292 return items; 1293 } 1294 1295 /** 1296 * Adds the viewable items to the set of selectable viewable items. 1297 */ addViewableItems(Collection<T> items, List<Selectable> selectableViewableItems)1298 private <T extends Selectable> void addViewableItems(Collection<T> items, 1299 List<Selectable> selectableViewableItems) { 1300 for (T item : items) { 1301 if (item instanceof Elevatable) { 1302 Elevatable elevatableItem = (Elevatable)item; 1303 if (elevatableItem.getLevel() == null 1304 || elevatableItem.getLevel().isViewable()) { 1305 selectableViewableItems.add(item); 1306 } 1307 } 1308 } 1309 } 1310 1311 /** 1312 * Returns all the mutable objects handled by this home. 1313 * @return a list containing environment, compass, levels, walls, rooms, furniture and their possible children, 1314 * polylines, dimension lines, labels and cameras. 1315 * @since 6.4 1316 */ getHomeObjects()1317 public List<HomeObject> getHomeObjects() { 1318 List<HomeObject> homeItems = new ArrayList<HomeObject>(); 1319 homeItems.add(this.environment); 1320 homeItems.add(this.compass); 1321 homeItems.addAll(this.levels); 1322 homeItems.addAll(this.walls); 1323 homeItems.addAll(this.rooms); 1324 homeItems.addAll(this.dimensionLines); 1325 homeItems.addAll(this.polylines); 1326 homeItems.addAll(this.labels); 1327 for (HomePieceOfFurniture piece : getFurniture()) { 1328 homeItems.add(piece); 1329 if (piece instanceof HomeFurnitureGroup) { 1330 homeItems.addAll(((HomeFurnitureGroup)piece).getAllFurniture()); 1331 } 1332 } 1333 homeItems.add(this.topCamera); 1334 homeItems.add(this.observerCamera); 1335 homeItems.addAll(this.storedCameras); 1336 homeItems.addAll(this.environment.getVideoCameraPath()); 1337 return homeItems; 1338 } 1339 1340 /** 1341 * Returns <code>true</code> if this home doesn't contain any item i.e. 1342 * no piece of furniture, no wall, no room, no dimension line and no label. 1343 * @since 2.2 1344 */ isEmpty()1345 public boolean isEmpty() { 1346 return this.furniture.isEmpty() 1347 && this.walls.isEmpty() 1348 && this.rooms.isEmpty() 1349 && this.dimensionLines.isEmpty() 1350 && this.polylines.isEmpty() 1351 && this.labels.isEmpty(); 1352 } 1353 1354 /** 1355 * Adds the property change <code>listener</code> in parameter to this home. 1356 * Properties change will be notified with an event of {@link PropertyChangeEvent} class which property name 1357 * will be equal to the value returned by {@link Property#name()} call. 1358 * @param property the property to follow 1359 * @param listener the listener to add 1360 */ addPropertyChangeListener(Property property, PropertyChangeListener listener)1361 public void addPropertyChangeListener(Property property, PropertyChangeListener listener) { 1362 this.propertyChangeSupport.addPropertyChangeListener(property.name(), listener); 1363 } 1364 1365 /** 1366 * Removes the property change <code>listener</code> in parameter from this home. 1367 * @param property the followed property 1368 * @param listener the listener to remove 1369 */ removePropertyChangeListener(Property property, PropertyChangeListener listener)1370 public void removePropertyChangeListener(Property property, PropertyChangeListener listener) { 1371 this.propertyChangeSupport.removePropertyChangeListener(property.name(), listener); 1372 } 1373 1374 /** 1375 * Returns the wall height of this home. 1376 */ getWallHeight()1377 public float getWallHeight() { 1378 return this.wallHeight; 1379 } 1380 1381 /** 1382 * Returns the name of this home. 1383 */ getName()1384 public String getName() { 1385 return this.name; 1386 } 1387 1388 /** 1389 * Sets the name of this home and fires a <code>PropertyChangeEvent</code>. 1390 * @param name the new name of this home 1391 */ setName(String name)1392 public void setName(String name) { 1393 if (name != this.name 1394 && (name == null || !name.equals(this.name))) { 1395 String oldName = this.name; 1396 this.name = name; 1397 this.propertyChangeSupport.firePropertyChange(Property.NAME.name(), oldName, name); 1398 } 1399 } 1400 1401 /** 1402 * Returns whether the state of this home is modified or not. 1403 */ isModified()1404 public boolean isModified() { 1405 return this.modified; 1406 } 1407 1408 /** 1409 * Sets the modified state of this home and fires a <code>PropertyChangeEvent</code>. 1410 */ setModified(boolean modified)1411 public void setModified(boolean modified) { 1412 if (modified != this.modified) { 1413 this.modified = modified; 1414 this.propertyChangeSupport.firePropertyChange( 1415 Property.MODIFIED.name(), !modified, modified); 1416 } 1417 } 1418 1419 /** 1420 * Returns whether this home was recovered or not. 1421 * @since 3.0 1422 */ isRecovered()1423 public boolean isRecovered() { 1424 return this.recovered; 1425 } 1426 1427 /** 1428 * Sets whether this home was recovered or not and fires a <code>PropertyChangeEvent</code>. 1429 * @since 3.0 1430 */ setRecovered(boolean recovered)1431 public void setRecovered(boolean recovered) { 1432 if (recovered != this.recovered) { 1433 this.recovered = recovered; 1434 this.propertyChangeSupport.firePropertyChange( 1435 Property.RECOVERED.name(), !recovered, recovered); 1436 } 1437 } 1438 1439 /** 1440 * Returns whether this home was repaired or not. 1441 * @since 4.4 1442 */ isRepaired()1443 public boolean isRepaired() { 1444 return this.repaired; 1445 } 1446 1447 /** 1448 * Sets whether this home is repaired or not and fires a <code>PropertyChangeEvent</code>. 1449 * @since 4.4 1450 */ setRepaired(boolean repaired)1451 public void setRepaired(boolean repaired) { 1452 if (repaired != this.repaired) { 1453 this.repaired = repaired; 1454 this.propertyChangeSupport.firePropertyChange( 1455 Property.REPAIRED.name(), !repaired, repaired); 1456 } 1457 } 1458 1459 /** 1460 * Returns the furniture property on which home is sorted or <code>null</code> if 1461 * home furniture isn't sorted. 1462 */ getFurnitureSortedProperty()1463 public HomePieceOfFurniture.SortableProperty getFurnitureSortedProperty() { 1464 return this.furnitureSortedProperty; 1465 } 1466 1467 /** 1468 * Sets the furniture property on which this home should be sorted 1469 * and fires a <code>PropertyChangeEvent</code>. 1470 * @param furnitureSortedProperty the new property 1471 */ setFurnitureSortedProperty(HomePieceOfFurniture.SortableProperty furnitureSortedProperty)1472 public void setFurnitureSortedProperty(HomePieceOfFurniture.SortableProperty furnitureSortedProperty) { 1473 if (furnitureSortedProperty != this.furnitureSortedProperty 1474 && (furnitureSortedProperty == null || !furnitureSortedProperty.equals(this.furnitureSortedProperty))) { 1475 HomePieceOfFurniture.SortableProperty oldFurnitureSortedProperty = this.furnitureSortedProperty; 1476 this.furnitureSortedProperty = furnitureSortedProperty; 1477 this.propertyChangeSupport.firePropertyChange( 1478 Property.FURNITURE_SORTED_PROPERTY.name(), 1479 oldFurnitureSortedProperty, furnitureSortedProperty); 1480 } 1481 } 1482 1483 /** 1484 * Returns whether furniture is sorted in ascending or descending order. 1485 */ isFurnitureDescendingSorted()1486 public boolean isFurnitureDescendingSorted() { 1487 return this.furnitureDescendingSorted; 1488 } 1489 1490 /** 1491 * Sets the furniture sort order on which home should be sorted 1492 * and fires a <code>PropertyChangeEvent</code>. 1493 */ setFurnitureDescendingSorted(boolean furnitureDescendingSorted)1494 public void setFurnitureDescendingSorted(boolean furnitureDescendingSorted) { 1495 if (furnitureDescendingSorted != this.furnitureDescendingSorted) { 1496 this.furnitureDescendingSorted = furnitureDescendingSorted; 1497 this.propertyChangeSupport.firePropertyChange( 1498 Property.FURNITURE_DESCENDING_SORTED.name(), 1499 !furnitureDescendingSorted, furnitureDescendingSorted); 1500 } 1501 } 1502 1503 /** 1504 * Returns an unmodifiable list of the furniture properties that are visible. 1505 */ getFurnitureVisibleProperties()1506 public List<HomePieceOfFurniture.SortableProperty> getFurnitureVisibleProperties() { 1507 if (this.furnitureVisibleProperties == null) { 1508 return Collections.emptyList(); 1509 } else { 1510 return Collections.unmodifiableList(this.furnitureVisibleProperties); 1511 } 1512 } 1513 1514 /** 1515 * Sets the furniture properties that are visible and the order in which they are visible, 1516 * then fires a <code>PropertyChangeEvent</code>. 1517 * @param furnitureVisibleProperties the properties to display 1518 */ setFurnitureVisibleProperties(List<HomePieceOfFurniture.SortableProperty> furnitureVisibleProperties)1519 public void setFurnitureVisibleProperties(List<HomePieceOfFurniture.SortableProperty> furnitureVisibleProperties) { 1520 if (furnitureVisibleProperties != this.furnitureVisibleProperties 1521 && (furnitureVisibleProperties == null || !furnitureVisibleProperties.equals(this.furnitureVisibleProperties))) { 1522 List<HomePieceOfFurniture.SortableProperty> oldFurnitureVisibleProperties = this.furnitureVisibleProperties; 1523 this.furnitureVisibleProperties = new ArrayList<HomePieceOfFurniture.SortableProperty>(furnitureVisibleProperties); 1524 this.propertyChangeSupport.firePropertyChange( 1525 Property.FURNITURE_VISIBLE_PROPERTIES.name(), 1526 Collections.unmodifiableList(oldFurnitureVisibleProperties), 1527 Collections.unmodifiableList(furnitureVisibleProperties)); 1528 } 1529 } 1530 1531 /** 1532 * Returns the plan background image of this home. 1533 */ getBackgroundImage()1534 public BackgroundImage getBackgroundImage() { 1535 return this.backgroundImage; 1536 } 1537 1538 /** 1539 * Sets the plan background image of this home and fires a <code>PropertyChangeEvent</code>. 1540 * @param backgroundImage the new background image 1541 */ setBackgroundImage(BackgroundImage backgroundImage)1542 public void setBackgroundImage(BackgroundImage backgroundImage) { 1543 if (backgroundImage != this.backgroundImage) { 1544 BackgroundImage oldBackgroundImage = this.backgroundImage; 1545 this.backgroundImage = backgroundImage; 1546 this.propertyChangeSupport.firePropertyChange( 1547 Property.BACKGROUND_IMAGE.name(), oldBackgroundImage, backgroundImage); 1548 } 1549 } 1550 1551 /** 1552 * Returns the camera used to display this home from a top point of view. 1553 */ getTopCamera()1554 public Camera getTopCamera() { 1555 return this.topCamera; 1556 } 1557 1558 /** 1559 * Returns the camera used to display this home from an observer point of view. 1560 */ getObserverCamera()1561 public ObserverCamera getObserverCamera() { 1562 return this.observerCamera; 1563 } 1564 1565 /** 1566 * Sets the camera used to display this home and fires a <code>PropertyChangeEvent</code>. 1567 * @param camera the camera to use 1568 */ setCamera(Camera camera)1569 public void setCamera(Camera camera) { 1570 if (camera != this.camera) { 1571 Camera oldCamera = this.camera; 1572 this.camera = camera; 1573 this.propertyChangeSupport.firePropertyChange( 1574 Property.CAMERA.name(), oldCamera, camera); 1575 } 1576 } 1577 1578 /** 1579 * Returns the camera used to display this home. 1580 */ getCamera()1581 public Camera getCamera() { 1582 if (this.camera == null) { 1583 // Use by default top camera 1584 this.camera = getTopCamera(); 1585 } 1586 return this.camera; 1587 } 1588 1589 /** 1590 * Sets the cameras stored by this home and fires a <code>PropertyChangeEvent</code>. 1591 * The list given as parameter is cloned but not the camera instances it contains. 1592 * @param storedCameras the new list of cameras 1593 * @since 3.0 1594 */ setStoredCameras(List<Camera> storedCameras)1595 public void setStoredCameras(List<Camera> storedCameras) { 1596 if (!this.storedCameras.equals(storedCameras)) { 1597 List<Camera> oldStoredCameras = this.storedCameras; 1598 if (storedCameras == null) { 1599 this.storedCameras = Collections.emptyList(); 1600 } else { 1601 this.storedCameras = new ArrayList<Camera>(storedCameras); 1602 } 1603 this.propertyChangeSupport.firePropertyChange( 1604 Property.STORED_CAMERAS.name(), Collections.unmodifiableList(oldStoredCameras), Collections.unmodifiableList(storedCameras)); 1605 } 1606 } 1607 1608 /** 1609 * Returns an unmodifiable list of the cameras stored by this home. 1610 * @since 3.0 1611 */ getStoredCameras()1612 public List<Camera> getStoredCameras() { 1613 return Collections.unmodifiableList(this.storedCameras); 1614 } 1615 1616 /** 1617 * Returns the environment attributes of this home. 1618 */ getEnvironment()1619 public HomeEnvironment getEnvironment() { 1620 return this.environment; 1621 } 1622 1623 /** 1624 * Returns the compass associated to this home. 1625 * @since 3.0 1626 */ getCompass()1627 public Compass getCompass() { 1628 return this.compass; 1629 } 1630 1631 /** 1632 * Returns the print attributes of this home. 1633 */ getPrint()1634 public HomePrint getPrint() { 1635 return this.print; 1636 } 1637 1638 /** 1639 * Sets the print attributes of this home and fires a <code>PropertyChangeEvent</code>. 1640 * @param print the new print attributes 1641 */ setPrint(HomePrint print)1642 public void setPrint(HomePrint print) { 1643 if (print != this.print) { 1644 HomePrint oldPrint = this.print; 1645 this.print = print; 1646 this.propertyChangeSupport.firePropertyChange(Property.PRINT.name(), oldPrint, print); 1647 } 1648 this.print = print; 1649 } 1650 1651 /** 1652 * Returns the value of the visual property <code>name</code> associated with this home. 1653 * @deprecated {@link #getVisualProperty(String)} and {@link #setVisualProperty(String, Object)} 1654 * should be replaced by calls to {@link #getProperty(String)} and {@link #setProperty(String, String)} 1655 * to ensure they can be easily saved and read. Future file format might not save visual properties anymore. 1656 */ getVisualProperty(String name)1657 public Object getVisualProperty(String name) { 1658 return this.visualProperties.get(name); 1659 } 1660 1661 /** 1662 * Sets a visual property associated with this home. 1663 * @deprecated {@link #getVisualProperty(String)} and {@link #setVisualProperty(String, Object)} 1664 * should be replaced by calls to {@link #getProperty(String)} and {@link #setProperty(String, String)} 1665 * to ensure they can be easily saved and read. Future file format might not save visual properties anymore. 1666 */ setVisualProperty(String name, Object value)1667 public void setVisualProperty(String name, Object value) { 1668 this.visualProperties.put(name, value); 1669 } 1670 1671 /** 1672 * Returns the value of the property <code>name</code> associated with this home. 1673 * @return the value of the property or <code>null</code> if it doesn't exist. 1674 * @since 5.2 1675 */ getProperty(String name)1676 public String getProperty(String name) { 1677 return this.properties.get(name); 1678 } 1679 1680 /** 1681 * Returns the numeric value of the property <code>name</code> associated with this home. 1682 * @return an instance of {@link Long}, {@link Double} or <code>null</code> if the property 1683 * doesn't exist or can't be parsed. 1684 * @since 5.2 1685 */ getNumericProperty(String name)1686 public Number getNumericProperty(String name) { 1687 String value = this.properties.get(name); 1688 if (value != null) { 1689 try { 1690 return new Long (value); 1691 } catch (NumberFormatException ex) { 1692 try { 1693 return new Double (value); 1694 } catch (NumberFormatException ex1) { 1695 } 1696 } 1697 } 1698 return null; 1699 } 1700 1701 /** 1702 * Sets a property associated with this home. Once the property is updated, 1703 * listeners added to this home will receive a change event of 1704 * {@link PropertyChangeEvent} class.<br> 1705 * To avoid any issue with existing or future properties of Sweet Home 3D classes, 1706 * do not use property names written with only upper case letters. 1707 * @param name the name of the property to set 1708 * @param value the new value of the property 1709 * @since 5.2 1710 */ setProperty(String name, String value)1711 public void setProperty(String name, String value) { 1712 String oldValue = this.properties.get(name); 1713 if (value == null) { 1714 if (oldValue != null) { 1715 this.properties.remove(name); 1716 this.propertyChangeSupport.firePropertyChange(name, oldValue, null); 1717 } 1718 } else { 1719 this.properties.put(name, value); 1720 // Event fired only if not null value changed 1721 this.propertyChangeSupport.firePropertyChange(name, oldValue, value); 1722 } 1723 } 1724 1725 /** 1726 * Returns the property names. 1727 * @return a collection of all the names of the properties set with {@link #setProperty(String, String) setProperty} 1728 * @since 5.2 1729 */ getPropertyNames()1730 public Collection<String> getPropertyNames() { 1731 return this.properties.keySet(); 1732 } 1733 1734 /** 1735 * Adds the property change <code>listener</code> in parameter to this home for a specific property name. 1736 * Properties set with {@link #setProperty(String, String) setProperty} will be notified with 1737 * an event of {@link PropertyChangeEvent} class which property name will be equal to the property, 1738 * whereas changes on properties of {@link Property} enum will be notified with an event where 1739 * the property name will be equal to the value returned by {@link Property#name()} call. 1740 * @since 6.4 1741 */ addPropertyChangeListener(String propertyName, PropertyChangeListener listener)1742 public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) { 1743 this.propertyChangeSupport.addPropertyChangeListener(propertyName, listener); 1744 } 1745 1746 /** 1747 * Removes the property change <code>listener</code> in parameter from this object. 1748 * @since 6.4 1749 */ removePropertyChangeListener(String propertyName, PropertyChangeListener listener)1750 public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) { 1751 this.propertyChangeSupport.removePropertyChangeListener(propertyName, listener); 1752 } 1753 1754 /** 1755 * Returns <code>true</code> if the home objects belonging to the base plan 1756 * (generally walls, rooms, dimension lines and texts) are locked. 1757 * @since 1.8 1758 */ isBasePlanLocked()1759 public boolean isBasePlanLocked() { 1760 return this.basePlanLocked; 1761 } 1762 1763 /** 1764 * Sets whether home objects belonging to the base plan (generally walls, rooms, 1765 * dimension lines and texts) are locked and fires a <code>PropertyChangeEvent</code>. 1766 * @since 1.8 1767 */ setBasePlanLocked(boolean basePlanLocked)1768 public void setBasePlanLocked(boolean basePlanLocked) { 1769 if (basePlanLocked != this.basePlanLocked) { 1770 this.basePlanLocked = basePlanLocked; 1771 this.propertyChangeSupport.firePropertyChange( 1772 Property.BASE_PLAN_LOCKED.name(), !basePlanLocked, basePlanLocked); 1773 } 1774 } 1775 1776 /** 1777 * Returns the version of this home, the last time it was serialized or 1778 * or {@link #CURRENT_VERSION} if it is not serialized yet or 1779 * was serialized with Sweet Home 3D 0.x. 1780 * Version is useful to know with which Sweet Home 3D version this home was saved 1781 * and warn user that he may lose information if he saves with 1782 * current application a home created by a more recent version. 1783 */ getVersion()1784 public long getVersion() { 1785 return this.version; 1786 } 1787 1788 /** 1789 * Sets the version of this home. 1790 * @return version the new version 1791 * @since 5.2 1792 */ setVersion(long version)1793 public void setVersion(long version) { 1794 this.version = version; 1795 } 1796 1797 /** 1798 * Returns a clone of this home and the objects it contains. 1799 * Listeners bound to this home aren't added to the returned home. 1800 * @since 2.3 1801 */ 1802 @Override clone()1803 public Home clone() { 1804 try { 1805 Home clone = (Home)super.clone(); 1806 copyHomeData(this, clone); 1807 initListenersSupport(clone); 1808 clone.addModelListeners(); 1809 return clone; 1810 } catch (CloneNotSupportedException ex) { 1811 throw new IllegalStateException("Super class isn't cloneable"); 1812 } 1813 } 1814 1815 /** 1816 * Copies all data of a <code>source</code> home to a <code>destination</code> home. 1817 */ copyHomeData(Home source, Home destination)1818 private static void copyHomeData(Home source, Home destination) { 1819 // Copy non mutable data 1820 destination.allLevelsSelection = source.allLevelsSelection; 1821 destination.name = source.name; 1822 destination.modified = source.modified; 1823 destination.recovered = source.recovered; 1824 destination.repaired = source.repaired; 1825 destination.backgroundImage = source.backgroundImage; 1826 destination.print = source.print; 1827 destination.furnitureDescendingSorted = source.furnitureDescendingSorted; 1828 destination.version = source.version; 1829 destination.basePlanLocked = source.basePlanLocked; 1830 destination.skyColor = source.skyColor; 1831 destination.groundColor = source.groundColor; 1832 destination.lightColor = source.lightColor; 1833 destination.wallsAlpha = source.wallsAlpha; 1834 destination.furnitureSortedProperty = source.furnitureSortedProperty; 1835 1836 // Deep copy selectable items 1837 destination.selectedItems = new ArrayList<Selectable>(source.selectedItems.size()); 1838 destination.furniture = cloneSelectableItems( 1839 source.furniture, source.selectedItems, destination.selectedItems); 1840 destination.rooms = cloneSelectableItems(source.rooms, source.selectedItems, destination.selectedItems); 1841 destination.dimensionLines = cloneSelectableItems( 1842 source.dimensionLines, source.selectedItems, destination.selectedItems); 1843 destination.polylines = cloneSelectableItems( 1844 source.polylines, source.selectedItems, destination.selectedItems); 1845 destination.labels = cloneSelectableItems(source.labels, source.selectedItems, destination.selectedItems); 1846 // Deep copy walls 1847 destination.walls = Wall.clone(source.walls); 1848 for (int i = 0; i < source.walls.size(); i++) { 1849 Wall wall = source.walls.get(i); 1850 if (source.selectedItems.contains(wall)) { 1851 destination.selectedItems.add(destination.walls.get(i)); 1852 } 1853 } 1854 // Clone levels and set the level of cloned objects 1855 destination.levels = new ArrayList<Level>(); 1856 if (source.levels.size() > 0) { 1857 for (Level level : source.levels) { 1858 destination.levels.add(level.clone()); 1859 } 1860 for (int i = 0; i < source.furniture.size(); i++) { 1861 Level pieceLevel = source.furniture.get(i).getLevel(); 1862 if (pieceLevel != null) { 1863 // As soon as there's more than one level, every object is supposed to have its level set 1864 // but as level can still be null for a undetermined reason, prefer to keep level 1865 // to null in the cloned object and having errors further than throwing exception here 1866 destination.furniture.get(i).setLevel(destination.levels.get(source.levels.indexOf(pieceLevel))); 1867 } 1868 } 1869 for (int i = 0; i < source.rooms.size(); i++) { 1870 Level roomLevel = source.rooms.get(i).getLevel(); 1871 if (roomLevel != null) { 1872 destination.rooms.get(i).setLevel(destination.levels.get(source.levels.indexOf(roomLevel))); 1873 } 1874 } 1875 for (int i = 0; i < source.dimensionLines.size(); i++) { 1876 Level dimensionLineLevel = source.dimensionLines.get(i).getLevel(); 1877 if (dimensionLineLevel != null) { 1878 destination.dimensionLines.get(i).setLevel(destination.levels.get(source.levels.indexOf(dimensionLineLevel))); 1879 } 1880 } 1881 for (int i = 0; i < source.polylines.size(); i++) { 1882 Level polylineLevel = source.polylines.get(i).getLevel(); 1883 if (polylineLevel != null) { 1884 destination.polylines.get(i).setLevel(destination.levels.get(source.levels.indexOf(polylineLevel))); 1885 } 1886 } 1887 for (int i = 0; i < source.labels.size(); i++) { 1888 Level labelLevel = source.labels.get(i).getLevel(); 1889 if (labelLevel != null) { 1890 destination.labels.get(i).setLevel(destination.levels.get(source.levels.indexOf(labelLevel))); 1891 } 1892 } 1893 for (int i = 0; i < source.walls.size(); i++) { 1894 Level wallLevel = source.walls.get(i).getLevel(); 1895 if (wallLevel != null) { 1896 destination.walls.get(i).setLevel(destination.levels.get(source.levels.indexOf(wallLevel))); 1897 } 1898 } 1899 if (source.selectedLevel != null) { 1900 destination.selectedLevel = destination.levels.get(source.levels.indexOf(source.selectedLevel)); 1901 } 1902 } 1903 // Copy cameras 1904 destination.observerCamera = source.observerCamera.clone(); 1905 destination.topCamera = source.topCamera.clone(); 1906 if (source.camera == source.observerCamera) { 1907 destination.camera = destination.observerCamera; 1908 if (source.selectedItems.contains(source.observerCamera)) { 1909 destination.selectedItems.add(destination.observerCamera); 1910 } 1911 } else { 1912 destination.camera = destination.topCamera; 1913 } 1914 destination.storedCameras = new ArrayList<Camera>(source.storedCameras.size()); 1915 for (Camera camera : source.storedCameras) { 1916 destination.storedCameras.add(camera.clone()); 1917 } 1918 // Copy other mutable objects 1919 destination.environment = source.environment.clone(); 1920 destination.compass = source.compass.clone(); 1921 destination.furnitureVisibleProperties = new ArrayList<HomePieceOfFurniture.SortableProperty>( 1922 source.furnitureVisibleProperties); 1923 destination.visualProperties = new HashMap<String, Object>(source.visualProperties); 1924 destination.properties = new HashMap<String, String>(source.properties); 1925 } 1926 1927 /** 1928 * Returns the list of cloned items in <code>source</code>. 1929 * If a cloned item is selected its clone will be selected too (ie added to 1930 * <code>destinationSelectedItems</code>). 1931 */ 1932 @SuppressWarnings("unchecked") cloneSelectableItems(List<T> source, List<Selectable> sourceSelectedItems, List<Selectable> destinationSelectedItems)1933 private static <T extends Selectable> List<T> cloneSelectableItems(List<T> source, 1934 List<Selectable> sourceSelectedItems, 1935 List<Selectable> destinationSelectedItems) { 1936 List<T> destination = new ArrayList<T>(source.size()); 1937 for (T item : source) { 1938 T clone = (T)item.clone(); 1939 destination.add(clone); 1940 if (sourceSelectedItems.contains(item)) { 1941 destinationSelectedItems.add(clone); 1942 } else if (item instanceof HomeFurnitureGroup) { 1943 // Check if furniture in group is selected 1944 List<HomePieceOfFurniture> sourceFurnitureGroup = ((HomeFurnitureGroup)item).getAllFurniture(); 1945 List<HomePieceOfFurniture> destinationFurnitureGroup = null; 1946 for (int i = 0, n = sourceFurnitureGroup.size(); i < n; i++) { 1947 HomePieceOfFurniture piece = sourceFurnitureGroup.get(i); 1948 if (sourceSelectedItems.contains(piece)) { 1949 if (destinationFurnitureGroup == null) { 1950 destinationFurnitureGroup = ((HomeFurnitureGroup)clone).getAllFurniture(); 1951 } 1952 destinationSelectedItems.add(destinationFurnitureGroup.get(i)); 1953 } 1954 } 1955 } 1956 } 1957 return destination; 1958 } 1959 1960 /** 1961 * Returns a deep copy of home selectable <code>items</code>. 1962 * Duplicated items are at the same index as their original and use different ids. 1963 * @param items the items to duplicate 1964 */ duplicate(List<? extends Selectable> items)1965 public static List<Selectable> duplicate(List<? extends Selectable> items) { 1966 List<Selectable> list = new ArrayList<Selectable>(); 1967 // Clone first walls list with their walls at start and end point set 1968 List<Wall> duplicatedWalls = Wall.duplicate(getWallsSubList(items)); 1969 int wallIndex = 0; 1970 for (Selectable item : items) { 1971 if (item instanceof Wall) { 1972 list.add(duplicatedWalls.get(wallIndex++)); 1973 } else if (item instanceof HomeObject) { 1974 list.add((Selectable)((HomeObject)item).duplicate()); 1975 } else { 1976 list.add(item.clone()); 1977 } 1978 } 1979 return list; 1980 } 1981 1982 /** 1983 * Returns a sub list of <code>items</code> that contains only home furniture. 1984 * @param items the items among which the search is done 1985 */ getFurnitureSubList(List<? extends Selectable> items)1986 public static List<HomePieceOfFurniture> getFurnitureSubList(List<? extends Selectable> items) { 1987 return getSubList(items, HomePieceOfFurniture.class); 1988 } 1989 1990 /** 1991 * Returns a sub list of <code>items</code> that contains only walls. 1992 * @param items the items among which the search is done 1993 */ getWallsSubList(List<? extends Selectable> items)1994 public static List<Wall> getWallsSubList(List<? extends Selectable> items) { 1995 return getSubList(items, Wall.class); 1996 } 1997 1998 /** 1999 * Returns a sub list of <code>items</code> that contains only rooms. 2000 * @param items the items among which the search is done 2001 */ getRoomsSubList(List<? extends Selectable> items)2002 public static List<Room> getRoomsSubList(List<? extends Selectable> items) { 2003 return getSubList(items, Room.class); 2004 } 2005 2006 /** 2007 * Returns a sub list of <code>items</code> that contains only labels. 2008 * @param items the items among which the search is done 2009 * @since 5.0 2010 */ getPolylinesSubList(List<? extends Selectable> items)2011 public static List<Polyline> getPolylinesSubList(List<? extends Selectable> items) { 2012 return getSubList(items, Polyline.class); 2013 } 2014 2015 /** 2016 * Returns a sub list of <code>items</code> that contains only dimension lines. 2017 * @param items the items among which the search is done 2018 */ getDimensionLinesSubList(List<? extends Selectable> items)2019 public static List<DimensionLine> getDimensionLinesSubList(List<? extends Selectable> items) { 2020 return getSubList(items, DimensionLine.class); 2021 } 2022 2023 /** 2024 * Returns a sub list of <code>items</code> that contains only labels. 2025 * @param items the items among which the search is done 2026 */ getLabelsSubList(List<? extends Selectable> items)2027 public static List<Label> getLabelsSubList(List<? extends Selectable> items) { 2028 return getSubList(items, Label.class); 2029 } 2030 2031 /** 2032 * Returns a sub list of <code>items</code> that contains only instances of <code>subListClass</code>. 2033 * @param items the items among which the search is done 2034 * @param subListClass the class of the searched items 2035 * @since 2.2 2036 */ 2037 @SuppressWarnings("unchecked") getSubList(List<? extends Selectable> items, Class<T> subListClass)2038 public static <T> List<T> getSubList(List<? extends Selectable> items, 2039 Class<T> subListClass) { 2040 List<T> subList = new ArrayList<T>(); 2041 for (Selectable item : items) { 2042 if (subListClass.isInstance(item)) { 2043 subList.add((T)item); 2044 } 2045 } 2046 return subList; 2047 } 2048 }