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 }