1 /*
2  * HomeEnvironment.java 6 nov. 2008
3  *
4  * Sweet Home 3D, Copyright (c) 2008 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.io.IOException;
25 import java.io.ObjectInputStream;
26 import java.io.Serializable;
27 import java.util.ArrayList;
28 import java.util.Collections;
29 import java.util.List;
30 
31 /**
32  * The environment attributes of a home.
33  * @author Emmanuel Puybaret
34  */
35 public class HomeEnvironment extends HomeObject implements Serializable, Cloneable {
36   private static final long serialVersionUID = 1L;
37 
38   /**
39    * The environment properties that may change.
40    */
41   public enum Property {OBSERVER_CAMERA_ELEVATION_ADJUSTED, GROUND_COLOR, GROUND_TEXTURE, BACKGROUND_IMAGE_VISIBLE_ON_GROUND_3D,
42                         SKY_COLOR, SKY_TEXTURE, LIGHT_COLOR, CEILING_LIGHT_COLOR,
43                         WALLS_ALPHA, DRAWING_MODE, SUBPART_SIZE_UNDER_LIGHT, ALL_LEVELS_VISIBLE,
44                         PHOTO_WIDTH, PHOTO_HEIGHT, PHOTO_ASPECT_RATIO, PHOTO_QUALITY,
45                         VIDEO_WIDTH, VIDEO_ASPECT_RATIO, VIDEO_QUALITY, VIDEO_SPEED, VIDEO_FRAME_RATE, VIDEO_CAMERA_PATH};
46   /**
47    * The various modes used to draw home in 3D.
48    */
49   public enum DrawingMode {
50     FILL, OUTLINE, FILL_AND_OUTLINE
51   }
52 
53   private boolean                         observerCameraElevationAdjusted;
54   private int                             groundColor;
55   private HomeTexture                     groundTexture;
56   private boolean                         backgroundImageVisibleOnGround3D;
57   private int                             skyColor;
58   private HomeTexture                     skyTexture;
59   private int                             lightColor;
60   private int                             ceilingLightColor;
61   private float                           wallsAlpha;
62   private DrawingMode                     drawingMode;
63   private float                           subpartSizeUnderLight;
64   private boolean                         allLevelsVisible;
65   private int                             photoWidth;
66   private int                             photoHeight;
67   private transient AspectRatio           photoAspectRatio;
68   // Aspect ratios are saved as a string to be able to keep backward compatibility
69   // if new constants are added to AspectRatio enum in future versions
70   private String                          photoAspectRatioName;
71   private int                             photoQuality;
72   private int                             videoWidth;
73   private transient AspectRatio           videoAspectRatio;
74   // Aspect ratios are saved as a string to be able to keep backward compatibility
75   // if new constants are added to AspectRatio enum in future versions
76   private String                          videoAspectRatioName;
77   private int                             videoQuality;
78   private float                           videoSpeed;
79   private int                             videoFrameRate;
80   private List<Camera>                    cameraPath;
81 
82   /**
83    * Creates default environment.
84    */
HomeEnvironment()85   public HomeEnvironment() {
86     this(HomeObject.createId("environment"));
87   }
88 
89   /**
90    * Creates default environment.
91    * @since 6.4
92    */
HomeEnvironment(String id)93   public HomeEnvironment(String id) {
94     this(id,
95          0xA8A8A8, // Ground color
96          null,     // Ground texture
97          0xCCE4FC, // Sky color
98          null,     // Sky texture
99          0xD0D0D0, // Light color
100          0);       // Walls alpha
101   }
102 
103   /**
104    * Creates home environment from parameters.
105    */
HomeEnvironment(int groundColor, HomeTexture groundTexture, int skyColor, int lightColor, float wallsAlpha)106   public HomeEnvironment(int groundColor,
107                          HomeTexture groundTexture, int skyColor,
108                          int lightColor, float wallsAlpha) {
109     this(groundColor, groundTexture, skyColor, null,
110         lightColor, wallsAlpha);
111   }
112 
113   /**
114    * Creates home environment from parameters.
115    * @since 2.2
116    */
HomeEnvironment(int groundColor, HomeTexture groundTexture, int skyColor, HomeTexture skyTexture, int lightColor, float wallsAlpha)117   public HomeEnvironment(int groundColor, HomeTexture groundTexture,
118                          int skyColor, HomeTexture skyTexture,
119                          int lightColor, float wallsAlpha) {
120     this(HomeObject.createId("environment"), groundColor, groundTexture,
121         skyColor, skyTexture, lightColor, wallsAlpha);
122   }
123 
124   /**
125    * Creates home environment from parameters.
126    * @since 6.4
127    */
HomeEnvironment(String id, int groundColor, HomeTexture groundTexture, int skyColor, HomeTexture skyTexture, int lightColor, float wallsAlpha)128   public HomeEnvironment(String id,
129                          int groundColor, HomeTexture groundTexture,
130                          int skyColor, HomeTexture skyTexture,
131                          int lightColor, float wallsAlpha) {
132     super(id);
133     this.observerCameraElevationAdjusted = true;
134     this.groundColor = groundColor;
135     this.groundTexture = groundTexture;
136     this.skyColor = skyColor;
137     this.skyTexture = skyTexture;
138     this.lightColor = lightColor;
139     this.ceilingLightColor = 0xD0D0D0;
140     this.wallsAlpha = wallsAlpha;
141     this.drawingMode = DrawingMode.FILL;
142     this.photoWidth = 400;
143     this.photoHeight = 300;
144     this.photoAspectRatio = AspectRatio.VIEW_3D_RATIO;
145     this.videoWidth = 320;
146     this.videoAspectRatio = AspectRatio.RATIO_4_3;
147     this.videoSpeed = 2400f / 3600; // 2.4 km/h
148     this.videoFrameRate = 25;
149     this.cameraPath = Collections.emptyList();
150   }
151 
152   /**
153    * Initializes environment transient fields
154    * and reads attributes from <code>in</code> stream with default reading method.
155    */
readObject(ObjectInputStream in)156   private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
157     this.ceilingLightColor = 0xD0D0D0;
158     this.photoWidth = 400;
159     this.photoHeight = 300;
160     this.photoAspectRatio = AspectRatio.VIEW_3D_RATIO;
161     this.videoWidth = 320;
162     this.videoAspectRatio = AspectRatio.RATIO_4_3;
163     this.videoSpeed = 2400f / 3600;
164     this.videoFrameRate = 25;
165     this.cameraPath = Collections.emptyList();
166     in.defaultReadObject();
167     try {
168       // Read aspect from a string
169       if (this.photoAspectRatioName != null) {
170         this.photoAspectRatio = AspectRatio.valueOf(this.photoAspectRatioName);
171       }
172     } catch (IllegalArgumentException ex) {
173       // Ignore malformed enum constant
174     }
175     try {
176       // Read aspect from a string
177       if (this.videoAspectRatioName != null) {
178         this.videoAspectRatio = AspectRatio.valueOf(this.videoAspectRatioName);
179       }
180     } catch (IllegalArgumentException ex) {
181       // Ignore malformed enum constant
182     }
183   }
184 
185   /**
186    * Adds the property change <code>listener</code> in parameter to this environment.
187    * Properties change will be notified with an event of {@link PropertyChangeEvent} class which property name
188    * will be equal to the value returned by {@link Property#name()} call.
189    */
addPropertyChangeListener(Property property, PropertyChangeListener listener)190   public void addPropertyChangeListener(Property property, PropertyChangeListener listener) {
191     addPropertyChangeListener(property.name(), listener);
192   }
193 
194   /**
195    * Removes the property change <code>listener</code> in parameter from this environment.
196    */
removePropertyChangeListener(Property property, PropertyChangeListener listener)197   public void removePropertyChangeListener(Property property, PropertyChangeListener listener) {
198     removePropertyChangeListener(property.name(), listener);
199   }
200 
201   /**
202    * Returns <code>true</code> if the observer elevation should be adjusted according
203    * to the elevation of the selected level.
204    * @since 3.5
205    */
isObserverCameraElevationAdjusted()206   public boolean isObserverCameraElevationAdjusted() {
207     return this.observerCameraElevationAdjusted;
208   }
209 
210   /**
211    * Sets whether the observer elevation should be adjusted according
212    * to the elevation of the selected level and fires a <code>PropertyChangeEvent</code>.
213    * @since 3.5
214    */
setObserverCameraElevationAdjusted(boolean observerCameraElevationAdjusted)215   public void setObserverCameraElevationAdjusted(boolean observerCameraElevationAdjusted) {
216     if (this.observerCameraElevationAdjusted != observerCameraElevationAdjusted) {
217       this.observerCameraElevationAdjusted = observerCameraElevationAdjusted;
218       firePropertyChange(Property.OBSERVER_CAMERA_ELEVATION_ADJUSTED.name(),
219           !observerCameraElevationAdjusted, observerCameraElevationAdjusted);
220     }
221   }
222 
223   /**
224    * Returns the ground color of this environment.
225    */
getGroundColor()226   public int getGroundColor() {
227     return this.groundColor;
228   }
229 
230   /**
231    * Sets the ground color of this environment and fires a <code>PropertyChangeEvent</code>.
232    */
setGroundColor(int groundColor)233   public void setGroundColor(int groundColor) {
234     if (groundColor != this.groundColor) {
235       int oldGroundColor = this.groundColor;
236       this.groundColor = groundColor;
237       firePropertyChange(Property.GROUND_COLOR.name(), oldGroundColor, groundColor);
238     }
239   }
240 
241   /**
242    * Returns the ground texture of this environment.
243    */
getGroundTexture()244   public HomeTexture getGroundTexture() {
245     return this.groundTexture;
246   }
247 
248   /**
249    * Sets the ground texture of this environment and fires a <code>PropertyChangeEvent</code>.
250    */
setGroundTexture(HomeTexture groundTexture)251   public void setGroundTexture(HomeTexture groundTexture) {
252     if (groundTexture != this.groundTexture) {
253       HomeTexture oldGroundTexture = this.groundTexture;
254       this.groundTexture = groundTexture;
255       firePropertyChange(Property.GROUND_TEXTURE.name(), oldGroundTexture, groundTexture);
256     }
257   }
258 
259   /**
260    * Returns <code>true</code> if the background image should be displayed on the ground in 3D.
261    * @since 6.0
262    */
isBackgroundImageVisibleOnGround3D()263   public boolean isBackgroundImageVisibleOnGround3D() {
264     return this.backgroundImageVisibleOnGround3D;
265   }
266 
267   /**
268    * Sets whether the background image should be displayed on the ground in 3D and
269    * fires a <code>PropertyChangeEvent</code>.
270    * @since 6.0
271    */
setBackgroundImageVisibleOnGround3D(boolean backgroundImageVisibleOnGround3D)272   public void setBackgroundImageVisibleOnGround3D(boolean backgroundImageVisibleOnGround3D) {
273     if (this.backgroundImageVisibleOnGround3D != backgroundImageVisibleOnGround3D) {
274       this.backgroundImageVisibleOnGround3D = backgroundImageVisibleOnGround3D;
275       firePropertyChange(Property.BACKGROUND_IMAGE_VISIBLE_ON_GROUND_3D.name(),
276           !backgroundImageVisibleOnGround3D, backgroundImageVisibleOnGround3D);
277     }
278   }
279 
280   /**
281    * Returns the sky color of this environment.
282    */
getSkyColor()283   public int getSkyColor() {
284     return this.skyColor;
285   }
286 
287   /**
288    * Sets the sky color of this environment and fires a <code>PropertyChangeEvent</code>.
289    */
setSkyColor(int skyColor)290   public void setSkyColor(int skyColor) {
291     if (skyColor != this.skyColor) {
292       int oldSkyColor = this.skyColor;
293       this.skyColor = skyColor;
294       firePropertyChange(Property.SKY_COLOR.name(), oldSkyColor, skyColor);
295     }
296   }
297 
298   /**
299    * Returns the sky texture of this environment.
300    */
getSkyTexture()301   public HomeTexture getSkyTexture() {
302     return this.skyTexture;
303   }
304 
305   /**
306    * Sets the sky texture of this environment and fires a <code>PropertyChangeEvent</code>.
307    */
setSkyTexture(HomeTexture skyTexture)308   public void setSkyTexture(HomeTexture skyTexture) {
309     if (skyTexture != this.skyTexture) {
310       HomeTexture oldSkyTexture = this.skyTexture;
311       this.skyTexture = skyTexture;
312       firePropertyChange(Property.SKY_TEXTURE.name(), oldSkyTexture, skyTexture);
313     }
314   }
315 
316   /**
317    * Returns the light color of this environment.
318    */
getLightColor()319   public int getLightColor() {
320     return this.lightColor;
321   }
322 
323   /**
324    * Sets the color that lights this environment and fires a <code>PropertyChangeEvent</code>.
325    */
setLightColor(int lightColor)326   public void setLightColor(int lightColor) {
327     if (lightColor != this.lightColor) {
328       int oldLightColor = this.lightColor;
329       this.lightColor = lightColor;
330       firePropertyChange(Property.LIGHT_COLOR.name(), oldLightColor, lightColor);
331     }
332   }
333 
334   /**
335    * Returns the color of ceiling lights.
336    */
getCeillingLightColor()337   public int getCeillingLightColor() {
338     return this.ceilingLightColor;
339   }
340 
341   /**
342    * Sets the color of ceiling lights and fires a <code>PropertyChangeEvent</code>.
343    */
setCeillingLightColor(int ceilingLightColor)344   public void setCeillingLightColor(int ceilingLightColor) {
345     if (ceilingLightColor != this.ceilingLightColor) {
346       int oldCeilingLightColor = this.ceilingLightColor;
347       this.ceilingLightColor = ceilingLightColor;
348       firePropertyChange(Property.CEILING_LIGHT_COLOR.name(), oldCeilingLightColor, ceilingLightColor);
349     }
350   }
351 
352   /**
353    * Returns the walls transparency alpha factor of this environment.
354    */
getWallsAlpha()355   public float getWallsAlpha() {
356     return this.wallsAlpha;
357   }
358 
359   /**
360    * Sets the walls transparency alpha of this environment and fires a <code>PropertyChangeEvent</code>.
361    * @param wallsAlpha a value between 0 and 1, 0 meaning opaque and 1 invisible.
362    */
setWallsAlpha(float wallsAlpha)363   public void setWallsAlpha(float wallsAlpha) {
364     if (wallsAlpha != this.wallsAlpha) {
365       float oldWallsAlpha = this.wallsAlpha;
366       this.wallsAlpha = wallsAlpha;
367       firePropertyChange(Property.WALLS_ALPHA.name(), oldWallsAlpha, wallsAlpha);
368     }
369   }
370 
371   /**
372    * Returns the drawing mode of this environment.
373    */
getDrawingMode()374   public DrawingMode getDrawingMode() {
375     return this.drawingMode;
376   }
377 
378   /**
379    * Sets the drawing mode of this environment and fires a <code>PropertyChangeEvent</code>.
380    */
setDrawingMode(DrawingMode drawingMode)381   public void setDrawingMode(DrawingMode drawingMode) {
382     if (drawingMode != this.drawingMode) {
383       DrawingMode oldDrawingMode = this.drawingMode;
384       this.drawingMode = drawingMode;
385       firePropertyChange(Property.DRAWING_MODE.name(), oldDrawingMode, drawingMode);
386     }
387   }
388 
389   /**
390    * Returns the size of subparts under home lights in this environment.
391    * @return a size in centimeters or 0 if home lights don't illuminate home.
392    * @since 3.7
393    */
getSubpartSizeUnderLight()394   public float getSubpartSizeUnderLight() {
395     return this.subpartSizeUnderLight;
396   }
397 
398   /**
399    * Sets the size of subparts under home lights of this environment and fires a <code>PropertyChangeEvent</code>.
400    * @since 3.7
401    */
setSubpartSizeUnderLight(float subpartSizeUnderLight)402   public void setSubpartSizeUnderLight(float subpartSizeUnderLight) {
403     if (subpartSizeUnderLight != this.subpartSizeUnderLight) {
404       float oldSubpartWidthUnderLight = this.subpartSizeUnderLight;
405       this.subpartSizeUnderLight = subpartSizeUnderLight;
406       firePropertyChange(Property.SUBPART_SIZE_UNDER_LIGHT.name(), oldSubpartWidthUnderLight, subpartSizeUnderLight);
407     }
408   }
409 
410   /**
411    * Returns whether all levels should be visible or not.
412    */
isAllLevelsVisible()413   public boolean isAllLevelsVisible() {
414     return this.allLevelsVisible;
415   }
416 
417   /**
418    * Sets whether all levels should be visible or not and fires a <code>PropertyChangeEvent</code>.
419    */
setAllLevelsVisible(boolean allLevelsVisible)420   public void setAllLevelsVisible(boolean allLevelsVisible) {
421     if (allLevelsVisible != this.allLevelsVisible) {
422       this.allLevelsVisible = allLevelsVisible;
423       firePropertyChange(Property.ALL_LEVELS_VISIBLE.name(), !allLevelsVisible, allLevelsVisible);
424     }
425   }
426 
427   /**
428    * Returns the preferred photo width.
429    * @since 2.0
430    */
getPhotoWidth()431   public int getPhotoWidth() {
432     return this.photoWidth;
433   }
434 
435   /**
436    * Sets the preferred photo width, and notifies
437    * listeners of this change.
438    * @since 2.0
439    */
setPhotoWidth(int photoWidth)440   public void setPhotoWidth(int photoWidth) {
441     if (this.photoWidth != photoWidth) {
442       int oldPhotoWidth = this.photoWidth;
443       this.photoWidth = photoWidth;
444       firePropertyChange(Property.PHOTO_WIDTH.name(), oldPhotoWidth, photoWidth);
445     }
446   }
447 
448   /**
449    * Returns the preferred photo height.
450    * @since 2.0
451    */
getPhotoHeight()452   public int getPhotoHeight() {
453     return this.photoHeight;
454   }
455 
456   /**
457    * Sets the preferred photo height, and notifies
458    * listeners of this change.
459    * @since 2.0
460    */
setPhotoHeight(int photoHeight)461   public void setPhotoHeight(int photoHeight) {
462     if (this.photoHeight != photoHeight) {
463       int oldPhotoHeight = this.photoHeight;
464       this.photoHeight = photoHeight;
465       firePropertyChange(Property.PHOTO_HEIGHT.name(), oldPhotoHeight, photoHeight);
466     }
467   }
468 
469   /**
470    * Returns the preferred photo aspect ratio.
471    * @since 2.0
472    */
getPhotoAspectRatio()473   public AspectRatio getPhotoAspectRatio() {
474     return this.photoAspectRatio;
475   }
476 
477   /**
478    * Sets the preferred photo aspect ratio, and notifies
479    * listeners of this change.
480    * @since 2.0
481    */
setPhotoAspectRatio(AspectRatio photoAspectRatio)482   public void setPhotoAspectRatio(AspectRatio photoAspectRatio) {
483     if (this.photoAspectRatio != photoAspectRatio) {
484       AspectRatio oldPhotoAspectRatio = this.photoAspectRatio;
485       this.photoAspectRatio = photoAspectRatio;
486       this.photoAspectRatioName = this.photoAspectRatio.name();
487       firePropertyChange(Property.PHOTO_ASPECT_RATIO.name(), oldPhotoAspectRatio, photoAspectRatio);
488     }
489   }
490 
491   /**
492    * Returns the preferred photo quality.
493    * @since 2.0
494    */
getPhotoQuality()495   public int getPhotoQuality() {
496     return this.photoQuality;
497   }
498 
499   /**
500    * Sets preferred photo quality, and notifies
501    * listeners of this change.
502    * @since 2.0
503    */
setPhotoQuality(int photoQuality)504   public void setPhotoQuality(int photoQuality) {
505     if (this.photoQuality != photoQuality) {
506       int oldPhotoQuality = this.photoQuality;
507       this.photoQuality = photoQuality;
508       firePropertyChange(Property.PHOTO_QUALITY.name(), oldPhotoQuality, photoQuality);
509     }
510   }
511 
512   /**
513    * Returns the preferred video width.
514    * @since 2.3
515    */
getVideoWidth()516   public int getVideoWidth() {
517     return this.videoWidth;
518   }
519 
520   /**
521    * Sets the preferred video width, and notifies
522    * listeners of this change.
523    * @since 2.3
524    */
setVideoWidth(int videoWidth)525   public void setVideoWidth(int videoWidth) {
526     if (this.videoWidth != videoWidth) {
527       int oldVideoWidth = this.videoWidth;
528       this.videoWidth = videoWidth;
529       firePropertyChange(Property.VIDEO_WIDTH.name(), oldVideoWidth, videoWidth);
530     }
531   }
532 
533   /**
534    * Returns the preferred video height.
535    * @since 2.3
536    */
getVideoHeight()537   public int getVideoHeight() {
538     return Math.round(getVideoWidth() / getVideoAspectRatio().getValue());
539   }
540 
541   /**
542    * Returns the preferred video aspect ratio.
543    * @since 2.3
544    */
getVideoAspectRatio()545   public AspectRatio getVideoAspectRatio() {
546     return this.videoAspectRatio;
547   }
548 
549   /**
550    * Sets the preferred video aspect ratio, and notifies
551    * listeners of this change.
552    * @since 2.3
553    */
setVideoAspectRatio(AspectRatio videoAspectRatio)554   public void setVideoAspectRatio(AspectRatio videoAspectRatio) {
555     if (this.videoAspectRatio != videoAspectRatio) {
556       if (videoAspectRatio.getValue() == null) {
557         throw new IllegalArgumentException("Unsupported aspect ratio " + videoAspectRatio);
558       }
559       AspectRatio oldVideoAspectRatio = this.videoAspectRatio;
560       this.videoAspectRatio = videoAspectRatio;
561       this.videoAspectRatioName = this.videoAspectRatio.name();
562       firePropertyChange(Property.VIDEO_ASPECT_RATIO.name(), oldVideoAspectRatio, videoAspectRatio);
563     }
564   }
565 
566   /**
567    * Returns preferred video quality.
568    * @since 2.3
569    */
getVideoQuality()570   public int getVideoQuality() {
571     return this.videoQuality;
572   }
573 
574   /**
575    * Sets the preferred video quality, and notifies
576    * listeners of this change.
577    * @since 2.3
578    */
setVideoQuality(int videoQuality)579   public void setVideoQuality(int videoQuality) {
580     if (this.videoQuality != videoQuality) {
581       int oldVideoQuality = this.videoQuality;
582       this.videoQuality = videoQuality;
583       firePropertyChange(Property.VIDEO_QUALITY.name(), oldVideoQuality, videoQuality);
584     }
585   }
586 
587   /**
588    * Returns the preferred speed of movements in videos in m/s.
589    * @since 6.0
590    */
getVideoSpeed()591   public float getVideoSpeed() {
592     return this.videoSpeed;
593   }
594 
595   /**
596    * Sets the preferred speed of movements in videos in m/s.
597    * @since 6.0
598    */
setVideoSpeed(float videoSpeed)599   public void setVideoSpeed(float videoSpeed) {
600     if (this.videoSpeed != videoSpeed) {
601       float oldVideoSpeed = this.videoSpeed;
602       this.videoSpeed = videoSpeed;
603       firePropertyChange(Property.VIDEO_SPEED.name(), oldVideoSpeed, videoSpeed);
604     }
605   }
606 
607   /**
608    * Returns the preferred video frame rate.
609    * @since 2.3
610    */
getVideoFrameRate()611   public int getVideoFrameRate() {
612     return this.videoFrameRate;
613   }
614 
615   /**
616    * Sets the preferred video frame rate, and notifies
617    * listeners of this change.
618    * @since 2.3
619    */
setVideoFrameRate(int videoFrameRate)620   public void setVideoFrameRate(int videoFrameRate) {
621     if (this.videoFrameRate != videoFrameRate) {
622       int oldVideoFrameRate = this.videoFrameRate;
623       this.videoFrameRate = videoFrameRate;
624       firePropertyChange(Property.VIDEO_FRAME_RATE.name(), oldVideoFrameRate, videoFrameRate);
625     }
626   }
627 
628   /**
629    * Returns the preferred video camera path.
630    * @since 2.3
631    */
getVideoCameraPath()632   public List<Camera> getVideoCameraPath() {
633     return Collections.unmodifiableList(this.cameraPath);
634   }
635 
636   /**
637    * Sets the preferred video camera path, and notifies
638    * listeners of this change.
639    * @since 2.3
640    */
setVideoCameraPath(List<Camera> cameraPath)641   public void setVideoCameraPath(List<Camera> cameraPath) {
642     if (this.cameraPath != cameraPath) {
643       List<Camera> oldCameraPath = this.cameraPath;
644       if (cameraPath != null) {
645         this.cameraPath = new ArrayList<Camera>(cameraPath);
646       } else {
647         this.cameraPath = Collections.emptyList();
648       }
649       firePropertyChange(Property.VIDEO_CAMERA_PATH.name(), oldCameraPath, cameraPath);
650     }
651   }
652 
653   /**
654    * Returns a clone of this environment.
655    * @since 2.3
656    */
657   @Override
clone()658   public HomeEnvironment clone() {
659     HomeEnvironment clone = (HomeEnvironment)super.clone();
660     clone.cameraPath = new ArrayList<Camera>(this.cameraPath.size());
661     for (Camera camera : this.cameraPath) {
662       clone.cameraPath.add(camera.clone());
663     }
664     return clone;
665   }
666 }