1 /*
  2  * UserPreferences.js
  3  *
  4  * Sweet Home 3D, Copyright (c) 2015 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 
 21 // Requires core.js
 22 //          LengthUnit.js
 23 
 24 /**
 25  * User preferences.
 26  * @constructor
 27  * @author Emmanuel Puybaret
 28  */
 29 function UserPreferences() {
 30   this.propertyChangeSupport = new PropertyChangeSupport(this);
 31   // TODO
 32   // this.classResourceBundles = new HashMap<Class<?>, ResourceBundle>();
 33   // this.resourceBundles = new HashMap<String, ResourceBundle>();
 34 
 35   this.supportedLanguages = UserPreferences.DEFAULT_SUPPORTED_LANGUAGES;
 36   this.defaultCountry = navigator.language.substring(navigator.language.indexOf("_") + 1, navigator.language.length);    
 37   var defaultLanguage = navigator.language.substring(0, navigator.language.indexOf("_"));
 38   // Find closest language among supported languages in Sweet Home 3D
 39   // For example, use simplified Chinese even for Chinese users (zh_?) not from China (zh_CN)
 40   // unless their exact locale is supported as in Taiwan (zh_TW)
 41   for (var i = 0; i < this.supportedLanguages; i++) {
 42     var supportedLanguage = this.supportedLanguages [i];
 43     if (supportedLanguage == defaultLanguage + "_" + this.defaultCountry) {
 44       this.language = supportedLanguage;
 45       break; // Found the exact supported language
 46     } else if (this.language === null 
 47                && supportedLanguage.indexOf(defaultLanguage) === 0) {
 48       this.language = supportedLanguage; // Found a supported language
 49     }
 50   }
 51   // If no language was found, let's use English by default
 52   if (this.language === null) {
 53     this.language = "en";
 54   }
 55   
 56   this.resourceBundles = {};
 57   this.furnitureCatalog = null;
 58   this.texturesCatalog = null;
 59   this.patternsCatalog = null;
 60   this.currency = null;
 61   this.unit = null;
 62   this.furnitureCatalogViewedInTree = true;
 63   this.aerialViewCenteredOnSelectionEnabled = false;
 64   this.observerCameraSelectedAtChange = true;
 65   this.navigationPanelVisible = true;
 66   this.magnetismEnabled = true;
 67   this.rulersVisible    = true;
 68   this.gridVisible      = true;
 69   this.defaultFontName  = null;
 70   this.furnitureViewedFromTop = true;
 71   this.furnitureModelIconSize = 128;
 72   this.roomFloorColoredOrTextured = true;
 73   this.wallPattern = null;
 74   this.newWallPattern = null;
 75   this.newWallThickness = 7.5;
 76   this.newWallHeight = 250;
 77   this.newWallBaseboardThickness = 1;
 78   this.newWallBaseboardHeight = 7;
 79   this.newFloorThickness = 12;
 80   this.recentHomes = [];
 81   this.autoSaveDelayForRecovery;
 82   this.autoCompletionStrings = {};
 83   this.recentColors = [];
 84   this.recentTextures = [];
 85   this.homeExamples = [];
 86 }
 87 
 88 UserPreferences.DEFAULT_SUPPORTED_LANGUAGES = ["en"]; 
 89 
 90 UserPreferences.DEFAULT_TEXT_STYLE = new TextStyle(18);
 91 UserPreferences.DEFAULT_ROOM_TEXT_STYLE = new TextStyle(24);
 92 
 93 
 94 /**
 95  * Adds the <code>listener</code> in parameter to these preferences. 
 96  * The listener is a function that will receive in parameter an event of {@link PropertyChangeEvent} class.
 97  */
 98 UserPreferences.prototype.addPropertyChangeListener = function(property,  listener) {
 99   this.propertyChangeSupport.addPropertyChangeListener(property, listener);
100 }
101 
102 /**
103  * Removes the <code>listener</code> in parameter from these preferences.
104  */
105 UserPreferences.prototype.removePropertyChangeListener = function(property, listener) {
106   this.propertyChangeSupport.removePropertyChangeListener(property, listener);
107 }
108 
109 /**
110  * Returns the furniture catalog.
111  * @ignore
112  */
113 UserPreferences.prototype.getFurnitureCatalog = function() {
114   return this.furnitureCatalog;
115 }
116 
117 /**
118  * Sets furniture catalog.
119  * @ignore
120  */
121 UserPreferences.prototype.setFurnitureCatalog = function(catalog) {
122   this.furnitureCatalog = catalog;
123 }
124 
125 /**
126  * Returns the textures catalog.
127  * @ignore
128  */
129 UserPreferences.prototype.getTexturesCatalog = function() {
130   return this.texturesCatalog;
131 }
132 
133 /**
134  * Sets textures catalog.
135  * @ignore
136  */
137 UserPreferences.prototype.setTexturesCatalog = function(catalog) {
138   this.texturesCatalog = catalog;
139 }
140 
141 /**
142  * Returns the patterns catalog available to fill plan areas. 
143  * @ignore
144  */
145 UserPreferences.prototype.getPatternsCatalog = function() {
146   return this.patternsCatalog;
147 }
148 
149 /**
150  * Sets the patterns available to fill plan areas.
151  * @ignore
152  */
153 UserPreferences.prototype.setPatternsCatalog = function(catalog) {
154   this.patternsCatalog = catalog;
155 }
156 
157 /**
158  * Returns the length unit currently in use.
159  */
160 UserPreferences.prototype.getLengthUnit = function() {
161   return this.unit;
162 }
163 
164 /**
165  * Changes the unit currently in use, and notifies listeners of this change. 
166  * @param unit one of the values of Unit.
167  */
168 UserPreferences.prototype.setUnit = function(unit) {
169   if (this.unit !== unit) {
170     var oldUnit = this.unit;
171     this.unit = unit;
172     this.propertyChangeSupport.firePropertyChange("UNIT", oldUnit, unit);
173   }
174 }
175 
176 /**
177  * Returns the preferred language to display information, noted with an ISO 639 code
178  * that may be followed by an underscore and an ISO 3166 code.
179  * @ignore 
180  */
181 UserPreferences.prototype.getLanguage = function() {
182   return this.language;
183 }
184 
185 /**
186  * If language can be changed, sets the preferred language to display information, 
187  * changes current default locale accordingly and notifies listeners of this change.
188  * @param language an ISO 639 code that may be followed by an underscore and an ISO 3166 code
189  *            (for example fr, de, it, en_US, zh_CN). 
190  * @ignore 
191  */
192 UserPreferences.prototype.setLanguage = function(language) {
193   if (language != this.language
194       && this.isLanguageEditable()) {
195     var oldLanguage = this.language;
196     this.language = language;
197     // TODO
198     // this.classResourceBundles.clear();
199     // this.resourceBundles.clear();
200     this.propertyChangeSupport.firePropertyChange("LANGUAGE", oldLanguage, language);
201   }
202 }
203 
204 /**
205  * Returns <code>true</code> if the language in preferences can be set.
206  * @return <code>true</code> except if <code>user.language</code> System property isn't writable.
207  * @ignore 
208  */
209 UserPreferences.prototype.isLanguageEditable = function() {
210   return true;
211 }
212 
213 /**
214  * Returns the array of default available languages in Sweet Home 3D.
215  * @ignore 
216  */
217 UserPreferences.prototype.getDefaultSupportedLanguages = function() {
218   return UserPreferences.DEFAULT_SUPPORTED_LANGUAGES.slice(0);
219 }
220 
221 /**
222  * Returns the array of available languages in Sweet Home 3D including languages in libraries.
223  * @ignore 
224  */
225 UserPreferences.prototype.getSupportedLanguages = function() {
226   return this.supportedLanguages.slice(0);
227 }
228 
229 /**
230  * Returns the array of available languages in Sweet Home 3D.
231  * @ignore 
232  */
233 UserPreferences.prototype.setSupportedLanguages = function(supportedLanguages) {
234   if (this.supportedLanguages != supportedLanguages) {
235     var oldSupportedLanguages = this.supportedLanguages;
236     this.supportedLanguages = supportedLanguages.slice(0);
237     this.propertyChangeSupport.firePropertyChange("SUPPORTED_LANGUAGES", 
238         oldSupportedLanguages, supportedLanguages);
239   }
240 }
241 
242 /**
243  * Returns the string matching <code>resourceKey</code> in current language in the 
244  * context of <code>resourceClass</code> or for a resource family if <code>resourceClass</code>
245  * is a string.
246  * If <code>resourceParameters</code> isn't empty the string is considered
247  * as a format string, and the returned string will be formatted with these parameters. 
248  * This implementation searches first the key in a properties file named as 
249  * <code>resourceClass</code>, then if this file doesn't exist, it searches 
250  * the key prefixed by <code>resourceClass</code> name and a dot in a package.properties file 
251  * in the folder matching the package of <code>resourceClass</code>. 
252  * @throws IllegalArgumentException if no string for the given key can be found
253  * @throws UnsupportedOperationException Not implemented yet
254  * @ignore
255  */
256 UserPreferences.prototype.getLocalizedString = function(resourceClass, resourceKey, resourceParameters) {
257   // TODO Implement
258   throw new UnsupportedOperationException("Not implemented yet");
259   
260   if (typeof resourceClass !== "string") {
261     var classResourceBundle = this.classResourceBundles [resourceClass];
262     if (classResourceBundle === null) {
263       try {      
264         classResourceBundle = this.getResourceBundle(resourceClass.constructor.name);
265         this.classResourceBundles [resourceClass.constructor.name] = classResourceBundle;
266       } catch (ex) {
267         try {
268           var className = resourceClass.constructor.name;
269           var lastIndex = className.lastIndexOf(".");
270           var resourceFamily;
271           if (lastIndex !== -1) {
272             resourceFamily = className.substring(0, lastIndex) + ".package";
273           } else {
274             resourceFamily = "package";
275           }
276           classResourceBundle = new PrefixedResourceBundle(getResourceBundle(resourceFamily), 
277               resourceClass.constructor.name + ".");
278           this.classResourceBundles [resourceClass.constructor.name] = classResourceBundle;
279         } catch (ex2) {
280           throw new IllegalArgumentException(
281               "Can't find resource bundle for " + resourceClass, ex);
282         }
283       }
284     } 
285     
286     return this.getBundleLocalizedString(classResourceBundle, resourceKey, resourceParameters);
287   } else {
288     var resourceFamily = resourceClass;
289     
290     try {      
291       var resourceBundle = this.getResourceBundle(resourceFamily);
292       return this.getBundleLocalizedString(resourceBundle, resourceKey, resourceParameters);
293     } catch (ex) {
294       throw new IllegalArgumentException(
295           "Can't find resource bundle for " + resourceFamily, ex);
296     }
297   }
298 }
299 
300 /**
301  * Returns a new resource bundle for the given <code>familyName</code> 
302  * that matches current default locale. The search will be done
303  * only among .properties files.
304  * @throws IOException if no .properties file was found
305  * @private
306  */
307 UserPreferences.prototype.getResourceBundle = function(resourceFamily) {
308   resourceFamily = resourceFamily.replace('.', '/');
309   var resourceBundle = this.resourceBundles.get(resourceFamily);
310   if (resourceBundle !== null) {
311     return resourceBundle;
312   }
313   var defaultLocale = this.language;
314   var language = defaultLocale.getLanguage();
315   var country = defaultLocale.getCountry();
316   var suffixes = [".properties",
317                   "_" + language + ".properties",
318                   "_" + language + "_" + country + ".properties"];
319   for (var i = 0; i < suffixes.length; i++) {
320     var suffix = suffixes [i];
321     var classLoaders = this.getResourceClassLoaders();
322     for (var j = 0; j < classLoaders.length; j++) {
323       var classLoader = classLoaders [j];
324       var input = classLoader.getResourceAsStream(resourceFamily + suffix);
325       if (input !== null) {
326         var parentResourceBundle = resourceBundle;
327         try {
328           resourceBundle = new PropertyResourceBundle(input);
329           resourceBundle.setParent(parentResourceBundle);
330           break;
331         } catch (ex) {
332           // May happen if the file contains some wrongly encoded characters
333           ex.printStackTrace();
334         } finally {
335           input.close();
336         }
337       }
338     }
339   }
340   if (resourceBundle === null) {
341     throw new IOException("No available resource bundle for " + resourceFamily);
342   }
343   this.resourceBundles.put(resourceFamily, resourceBundle);
344   return resourceBundle;
345 }
346 
347 /**
348  * Returns the string matching <code>resourceKey</code> for the given resource bundle.
349  * @private
350  */
351 UserPreferences.prototype.getBundleLocalizedString = function(resourceBundle, resourceKey, resourceParameters) {
352   try {
353     var localizedString = resourceBundle.getString(resourceKey);
354     if (resourceParameters.length > 0) {
355       localizedString = String.format(localizedString, resourceParameters);
356     }      
357     return localizedString;
358   } catch (ex) {
359     throw new IllegalArgumentException("Unknown key " + resourceKey);
360   }
361 }
362 
363 /**
364  * Returns the class loaders through which localized strings returned by 
365  * <code>getLocalizedString</code> might be loaded.
366  * @throws UnsupportedOperationException Not implemented yet
367  * @ignore
368  */
369 UserPreferences.prototype.getResourceClassLoaders = function() {
370   // TODO Implement ?
371   throw new UnsupportedOperationException("Not implemented yet");
372   
373   return DEFAULT_CLASS_LOADER;
374 }
375 
376 /**
377  * Returns the default currency in use, noted with ISO 4217 code, or <code>null</code> 
378  * if prices aren't used in application.
379  * @ignore
380  */
381 UserPreferences.prototype.getCurrency = function() {
382   return this.currency;
383 }
384 
385 /**
386  * Sets the default currency in use.
387  * @package
388  * @ignore
389  */
390 UserPreferences.prototype.setCurrency = function(currency) {
391   this.currency = currency;
392 }
393   
394 /**
395  * Returns <code>true</code> if the furniture catalog should be viewed in a tree.
396  * @ignore
397  */
398 UserPreferences.prototype.isFurnitureCatalogViewedInTree = function() {
399   return this.furnitureCatalogViewedInTree;
400 }
401 
402 /**
403  * Sets whether the furniture catalog should be viewed in a tree or a different way.
404  * @ignore
405  */
406 UserPreferences.prototype.setFurnitureCatalogViewedInTree = function(furnitureCatalogViewedInTree) {
407   if (this.furnitureCatalogViewedInTree !== furnitureCatalogViewedInTree) {
408     this.furnitureCatalogViewedInTree = furnitureCatalogViewedInTree;
409     this.propertyChangeSupport.firePropertyChange("FURNITURE_CATALOG_VIEWED_IN_TREE", 
410         !furnitureCatalogViewedInTree, furnitureCatalogViewedInTree);
411   }
412 }
413 
414 /**
415  * Returns <code>true</code> if the navigation panel should be displayed.
416  */
417 UserPreferences.prototype.isNavigationPanelVisible = function() {
418   return this.navigationPanelVisible;
419 }
420 
421 /**
422  * Sets whether the navigation panel should be displayed or not.
423  */
424 UserPreferences.prototype.setNavigationPanelVisible = function(navigationPanelVisible) {
425   if (this.navigationPanelVisible !== navigationPanelVisible) {
426     this.navigationPanelVisible = navigationPanelVisible;
427     this.propertyChangeSupport.firePropertyChange("NAVIGATION_PANEL_VISIBLE", 
428         !navigationPanelVisible, navigationPanelVisible);
429   }
430 }
431 
432 /**
433  * Sets whether aerial view should be centered on selection or not.
434  */
435 UserPreferences.prototype.setAerialViewCenteredOnSelectionEnabled = function(aerialViewCenteredOnSelectionEnabled) {
436   if (aerialViewCenteredOnSelectionEnabled !== this.aerialViewCenteredOnSelectionEnabled) {
437     this.aerialViewCenteredOnSelectionEnabled = aerialViewCenteredOnSelectionEnabled;
438     this.propertyChangeSupport.firePropertyChange("AERIAL_VIEW_CENTERED_ON_SELECTION_ENABLED", 
439         !aerialViewCenteredOnSelectionEnabled, aerialViewCenteredOnSelectionEnabled);
440   }
441 }
442 
443 /**
444  * Returns whether aerial view should be centered on selection or not.
445  */
446 UserPreferences.prototype.isAerialViewCenteredOnSelectionEnabled = function() {
447   return this.aerialViewCenteredOnSelectionEnabled;
448 }
449 
450 /**
451  * Sets whether the observer camera should be selected at each change.
452  * @since 5.5
453  */
454 UserPreferences.prototype.setObserverCameraSelectedAtChange = function(observerCameraSelectedAtChange) {
455   if (observerCameraSelectedAtChange !== this.observerCameraSelectedAtChange) {
456     this.observerCameraSelectedAtChange = observerCameraSelectedAtChange;
457     this.propertyChangeSupport.firePropertyChange("OBSERVER_CAMERA_SELECTED_AT_CHANGE", 
458         !observerCameraSelectedAtChange, observerCameraSelectedAtChange);
459   }
460 }
461 
462 /**
463  * Returns whether the observer camera should be selected at each change.
464  * @since 5.5
465  */
466 UserPreferences.prototype.isObserverCameraSelectedAtChange = function() {
467   return this.observerCameraSelectedAtChange;
468 }
469 
470 /**
471  * Returns <code>true</code> if magnetism is enabled.
472  * @return <code>true</code> by default.
473  * @ignore
474  */
475 UserPreferences.prototype.isMagnetismEnabled = function() {
476   return this.magnetismEnabled;
477 }
478 
479 /**
480  * Sets whether magnetism is enabled or not, and notifies
481  * listeners of this change. 
482  * @param magnetismEnabled <code>true</code> if magnetism is enabled,
483  *          <code>false</code> otherwise.
484  * @ignore
485  */
486 UserPreferences.prototype.setMagnetismEnabled = function(magnetismEnabled) {
487   if (this.magnetismEnabled !== magnetismEnabled) {
488     this.magnetismEnabled = magnetismEnabled;
489     this.propertyChangeSupport.firePropertyChange("MAGNETISM_ENABLED", 
490         !magnetismEnabled, magnetismEnabled);
491   }
492 }
493 
494 /**
495  * Returns <code>true</code> if rulers are visible.
496  * @return <code>true</code> by default.
497  * @ignore
498  */
499 UserPreferences.prototype.isRulersVisible = function() {
500   return this.rulersVisible;
501 }
502 
503 /**
504  * Sets whether rulers are visible or not, and notifies
505  * listeners of this change. 
506  * @param rulersVisible <code>true</code> if rulers are visible,
507  *          <code>false</code> otherwise.
508  * @ignore
509  */
510 UserPreferences.prototype.setRulersVisible = function(rulersVisible) {
511   if (this.rulersVisible !== rulersVisible) {
512     this.rulersVisible = rulersVisible;
513     this.propertyChangeSupport.firePropertyChange("RULERS_VISIBLE", 
514         !rulersVisible, rulersVisible);
515   }
516 }
517 
518 /**
519  * Returns <code>true</code> if plan grid visible.
520  * @return <code>true</code> by default.
521  * @ignore
522  */
523 UserPreferences.prototype.isGridVisible = function() {
524   return this.gridVisible;
525 }
526 
527 /**
528  * Sets whether plan grid is visible or not, and notifies
529  * listeners of this change. 
530  * @param gridVisible <code>true</code> if grid is visible,
531  *          <code>false</code> otherwise.
532  * @ignore
533  */
534 UserPreferences.prototype.setGridVisible = function(gridVisible) {
535   if (this.gridVisible !== gridVisible) {
536     this.gridVisible = gridVisible;
537     this.propertyChangeSupport.firePropertyChange("GRID_VISIBLE", 
538         !gridVisible, gridVisible);
539   }
540 }
541 
542 /**
543  * Returns the name of the font that should be used by default or <code>null</code> 
544  * if the default font should be the default one in the application.
545  * @ignore
546  */
547 UserPreferences.prototype.getDefaultFontName = function() {
548   return this.defaultFontName;
549 }
550 
551 /**
552  * Sets the name of the font that should be used by default.
553  * @ignore
554  */
555 UserPreferences.prototype.setDefaultFontName = function(defaultFontName) {
556   if (defaultFontName != this.defaultFontName) {
557     var oldName = this.defaultFontName;
558     this.defaultFontName = defaultFontName;
559     this.propertyChangeSupport.firePropertyChange("DEFAULT_FONT_NAME", oldName, defaultFontName);
560   }
561 }
562 
563 /**
564  * Returns <code>true</code> if furniture should be viewed from its top in plan.
565  * @ignore
566  */
567 UserPreferences.prototype.isFurnitureViewedFromTop = function() {
568   return this.furnitureViewedFromTop;
569 }
570 
571 /**
572  * Sets how furniture icon should be displayed in plan, and notifies
573  * listeners of this change. 
574  * @param furnitureViewedFromTop if <code>true</code> the furniture 
575  *    should be viewed from its top.
576  * @ignore
577  */
578 UserPreferences.prototype.setFurnitureViewedFromTop = function(furnitureViewedFromTop) {
579   if (this.furnitureViewedFromTop !== furnitureViewedFromTop) {
580     this.furnitureViewedFromTop = furnitureViewedFromTop;
581     this.propertyChangeSupport.firePropertyChange("FURNITURE_VIEWED_FROM_TOP", 
582         !furnitureViewedFromTop, furnitureViewedFromTop);
583   }
584 }
585 
586 /**
587  * Returns the size used to generate icons of furniture viewed from top.
588  * @since 5.5
589  * @ignore
590  */
591 UserPreferences.prototype.getFurnitureModelIconSize = function() {
592   return this.furnitureModelIconSize;
593 }
594 
595 /**
596  * Sets the name of the font that should be used by default.
597  * @since 5.5
598  * @ignore
599  */
600 UserPreferences.prototype.setFurnitureModelIconSize = function(furnitureModelIconSize) {
601   if (furnitureModelIconSize !== this.furnitureModelIconSize) {
602     var oldSize = this.furnitureModelIconSize;
603     this.furnitureModelIconSize = furnitureModelIconSize;
604     this.propertyChangeSupport.firePropertyChange("FURNITURE_MODEL_ICON_SIZE", oldSize, furnitureModelIconSize);
605   }
606 }
607 
608 /**
609  * Returns <code>true</code> if room floors should be rendered with color or texture 
610  * in plan.
611  * @return <code>false</code> by default.
612  * @ignore
613  */
614 UserPreferences.prototype.isRoomFloorColoredOrTextured = function() {
615   return this.roomFloorColoredOrTextured;
616 }
617 
618 /**
619  * Sets whether room floors should be rendered with color or texture, 
620  * and notifies listeners of this change. 
621  * @param roomFloorColoredOrTextured <code>true</code> if floor color 
622  *          or texture is used, <code>false</code> otherwise.
623  * @ignore
624  */
625 UserPreferences.prototype.setFloorColoredOrTextured = function(roomFloorColoredOrTextured) {
626   if (this.roomFloorColoredOrTextured !== roomFloorColoredOrTextured) {
627     this.roomFloorColoredOrTextured = roomFloorColoredOrTextured;
628     this.propertyChangeSupport.firePropertyChange("ROOM_FLOOR_COLORED_OR_TEXTURED", 
629         !roomFloorColoredOrTextured, roomFloorColoredOrTextured);
630   }
631 }
632 
633 /**
634  * Returns the wall pattern in plan used by default.
635  * @ignore
636  */
637 UserPreferences.prototype.getWallPattern = function() {
638   return this.wallPattern;
639 }
640 
641 /**
642  * Sets how walls should be displayed in plan by default, and notifies
643  * listeners of this change.
644  * @ignore
645  */
646 UserPreferences.prototype.setWallPattern = function(wallPattern) {
647   if (this.wallPattern !== wallPattern) {
648     var oldWallPattern = this.wallPattern;
649     this.wallPattern = wallPattern;
650     this.propertyChangeSupport.firePropertyChange("WALL_PATTERN", 
651         oldWallPattern, wallPattern);
652   }
653 }
654 
655 /**
656  * Returns the pattern used for new walls in plan or <code>null</code> if it's not set.
657  * @ignore
658  */
659 UserPreferences.prototype.getNewWallPattern = function() {
660   return this.newWallPattern;
661 }
662 
663 /**
664  * Sets how new walls should be displayed in plan, and notifies
665  * listeners of this change.
666  * @ignore
667  */
668 UserPreferences.prototype.setNewWallPattern = function(newWallPattern) {
669   if (this.newWallPattern !== newWallPattern) {
670     var oldWallPattern = this.newWallPattern;
671     this.newWallPattern = newWallPattern;
672     this.propertyChangeSupport.firePropertyChange("NEW_WALL_PATTERN", 
673         oldWallPattern, newWallPattern);
674   }
675 }
676 
677 /**
678  * Returns default thickness of new walls in home. 
679  * @ignore
680  */
681 UserPreferences.prototype.getNewWallThickness = function() {
682   return this.newWallThickness;
683 }
684 
685 /**
686  * Sets default thickness of new walls in home, and notifies
687  * listeners of this change.  
688  * @ignore
689  */
690 UserPreferences.prototype.setNewWallThickness = function(newWallThickness) {
691   if (this.newWallThickness !== newWallThickness) {
692     var oldDefaultThickness = this.newWallThickness;
693     this.newWallThickness = newWallThickness;
694     this.propertyChangeSupport.firePropertyChange("NEW_WALL_THICKNESS", 
695         oldDefaultThickness, newWallThickness);
696   }
697 }
698 
699 /**
700  * Returns default wall height of new home walls. 
701  * @ignore
702  */
703 UserPreferences.prototype.getNewWallHeight = function() {
704   return this.newWallHeight;
705 }
706 
707 /**
708  * Sets default wall height of new walls, and notifies
709  * listeners of this change. 
710  * @ignore
711  */
712 UserPreferences.prototype.setNewWallHeight = function(newWallHeight) {
713   if (this.newWallHeight !== newWallHeight) {
714     var oldWallHeight = this.newWallHeight;
715     this.newWallHeight = newWallHeight;
716     this.propertyChangeSupport.firePropertyChange("NEW_WALL_HEIGHT", 
717         oldWallHeight, newWallHeight);
718   }
719 }
720 
721 /**
722  * Returns default baseboard thickness of new walls in home. 
723  * @ignore
724  */
725 UserPreferences.prototype.getNewWallBaseboardThickness = function() {
726   return this.newWallBaseboardThickness;
727 }
728 
729 /**
730  * Sets default baseboard thickness of new walls in home, and notifies
731  * listeners of this change.  
732  * @ignore
733  */
734 UserPreferences.prototype.setNewWallBaseboardThickness = function(newWallBaseboardThickness) {
735   if (this.newWallBaseboardThickness !== newWallBaseboardThickness) {
736     var oldThickness = this.newWallBaseboardThickness;
737     this.newWallBaseboardThickness = newWallBaseboardThickness;
738     this.propertyChangeSupport.firePropertyChange("NEW_WALL_SIDEBOARD_THICKNESS", 
739         oldThickness, newWallBaseboardThickness);
740   }
741 }
742 
743 /**
744  * Returns default baseboard height of new home walls. 
745  * @ignore
746  */
747 UserPreferences.prototype.getNewWallBaseboardHeight = function() {
748   return this.newWallBaseboardHeight;
749 }
750 
751 /**
752  * Sets default baseboard height of new walls, and notifies
753  * listeners of this change. 
754  * @ignore
755  */
756 UserPreferences.prototype.setNewWallBaseboardHeight = function(newWallBaseboardHeight) {
757   if (this.newWallBaseboardHeight !== newWallBaseboardHeight) {
758     var oldHeight = this.newWallBaseboardHeight;
759     this.newWallBaseboardHeight = newWallBaseboardHeight;
760     this.propertyChangeSupport.firePropertyChange("NEW_WALL_SIDEBOARD_HEIGHT", 
761         oldHeight, newWallBaseboardHeight);
762   }
763 }
764 
765 /**
766  * Returns default thickness of the floor of new levels in home. 
767  * @ignore
768  */
769 UserPreferences.prototype.getNewFloorThickness = function() {
770   return this.newFloorThickness;
771 }
772 
773 /**
774  * Sets default thickness of the floor of new levels in home, and notifies
775  * listeners of this change.  
776  * @ignore
777  */
778 UserPreferences.prototype.setNewFloorThickness = function(newFloorThickness) {
779   if (this.newFloorThickness !== newFloorThickness) {
780     var oldFloorThickness = this.newFloorThickness;
781     this.newFloorThickness = newFloorThickness;
782     this.propertyChangeSupport.firePropertyChange("NEW_FLOOR_THICKNESS", 
783         oldFloorThickness, newFloorThickness);
784   }
785 }
786 
787 /**
788  * Returns the delay between two automatic save operations of homes for recovery purpose.
789  * @return a delay in milliseconds or 0 to disable auto save.
790  * @ignore
791  */
792 UserPreferences.prototype.getAutoSaveDelayForRecovery = function() {
793   return this.autoSaveDelayForRecovery;
794 }
795 
796 /**
797  * Sets the delay between two automatic save operations of homes for recovery purpose.
798  * @ignore
799  */
800 UserPreferences.prototype.setAutoSaveDelayForRecovery = function(autoSaveDelayForRecovery) {
801   if (this.autoSaveDelayForRecovery !== autoSaveDelayForRecovery) {
802     var oldAutoSaveDelayForRecovery = this.autoSaveDelayForRecovery;
803     this.autoSaveDelayForRecovery = autoSaveDelayForRecovery;
804     this.propertyChangeSupport.firePropertyChange("AUTO_SAVE_DELAY_FOR_RECOVERY", 
805         oldAutoSaveDelayForRecovery, autoSaveDelayForRecovery);
806   }
807 }
808 
809 /**
810  * Returns an unmodifiable list of the recent homes.
811  * @ignore
812  */
813 UserPreferences.prototype.getRecentHomes = function() {
814   return Collections.unmodifiableList(this.recentHomes);
815 }
816 
817 /**
818  * Sets the recent homes list and notifies listeners of this change.
819  * @ignore
820  */
821 UserPreferences.prototype.setRecentHomes = function(recentHomes) {
822   if (recentHomes != this.recentHomes) {
823     var oldRecentHomes = this.recentHomes;
824     this.recentHomes = new ArrayList<String>(recentHomes);
825     this.propertyChangeSupport.firePropertyChange("RECENT_HOMES", 
826         oldRecentHomes, this.getRecentHomes());
827   }
828 }
829 
830 /**
831  * Returns the maximum count of homes that should be proposed to the user.
832  * @ignore
833  */
834 UserPreferences.prototype.getRecentHomesMaxCount = function() {
835   return 10;
836 }
837 
838 /**
839  * Returns the maximum count of stored cameras in homes that should be proposed to the user.
840  * @ignore
841  */
842 UserPreferences.prototype.getStoredCamerasMaxCount = function() {
843   return 50;
844 }
845 
846 /**
847  * Sets which action tip should be ignored.
848  * <br>This method should be overridden to store the ignore information.
849  * By default it just notifies listeners of this change. 
850  * @ignore
851  */
852 UserPreferences.prototype.setActionTipIgnored = function(actionKey) {    
853   this.propertyChangeSupport.firePropertyChange("IGNORED_ACTION_TIP", null, actionKey);
854 }
855 
856 /**
857  * Returns whether an action tip should be ignored or not. 
858  * <br>This method should be overridden to return the display information
859  * stored in setActionTipIgnored.
860  * By default it returns <code>true</code>. 
861  * @ignore
862  */
863 UserPreferences.prototype.isActionTipIgnored = function(actionKey) {
864   return true;
865 }
866 
867 /**
868  * Resets the ignore flag of action tips.
869  * <br>This method should be overridden to clear all the display flags.
870  * By default it just notifies listeners of this change. 
871  * @ignore
872  */
873 UserPreferences.prototype.resetIgnoredActionTips = function() {    
874   this.propertyChangeSupport.firePropertyChange("IGNORED_ACTION_TIP", null, null);
875 }
876 
877 /**
878  * Returns the default text style of a class of selectable item. 
879  * @ignore
880  */
881 UserPreferences.prototype.getDefaultTextStyle = function(selectableClass) {
882   if (selectableClass.name == "Room") {
883     return UserPreferences.DEFAULT_ROOM_TEXT_STYLE;
884   } else {
885     return UserPreferences.DEFAULT_TEXT_STYLE;
886   }
887 }
888 
889 /**
890  * Returns the strings that may be used for the auto completion of the given <code>property</code>.
891  * @ignore
892  */
893 UserPreferences.prototype.getAutoCompletionStrings = function(property) {
894   var propertyAutoCompletionStrings = this.autoCompletionStrings.get(property);
895   if (propertyAutoCompletionStrings !== null) {
896     return Collections.unmodifiableList(propertyAutoCompletionStrings);
897   } else {
898     return [];
899   }
900 }
901 
902 /**
903  * Adds the given string to the list of the strings used in auto completion of a <code>property</code>
904  * and notifies listeners of this change.
905  * @ignore
906  */
907 UserPreferences.prototype.addAutoCompletionString = function(property, autoCompletionString) {
908   if (autoCompletionString !== null 
909       && autoCompletionString.length() > 0) {
910     var propertyAutoCompletionStrings = this.autoCompletionStrings [property];
911     if (propertyAutoCompletionStrings === undefined) {
912       propertyAutoCompletionStrings = [];
913     } else if (!propertyAutoCompletionStrings.contains(autoCompletionString)) {
914       propertyAutoCompletionStrings = new ArrayList<String>(propertyAutoCompletionStrings);
915     } else {
916       return;
917     }
918     propertyAutoCompletionStrings.splice(0, 0, autoCompletionString);
919     this.setAutoCompletionStrings(property, propertyAutoCompletionStrings);
920   }
921 }
922 
923 /**
924  * Sets the auto completion strings list of the given <code>property</code> and notifies listeners of this change.
925  * @ignore
926  */
927 UserPreferences.prototype.setAutoCompletionStrings = function(property, autoCompletionStrings) {
928   var propertyAutoCompletionStrings = this.autoCompletionStrings [property];
929   if (autoCompletionStrings != propertyAutoCompletionStrings) {
930     this.autoCompletionStrings.put(property, autoCompletionStrings.slice(0));
931     this.propertyChangeSupport.firePropertyChange("AUTO_COMPLETION_STRINGS", 
932         null, property);
933   }
934 }
935 
936 /**
937  * Returns the list of properties with auto completion strings. 
938  * @ignore
939  */
940 UserPreferences.prototype.getAutoCompletedProperties = function() {
941   if (this.autoCompletionStrings !== null) {
942     return Object.keys(this.autoCompletionStrings);
943   } else {
944     return Collections.emptyList();
945   }
946 }
947 
948 /**
949  * Returns the list of the recent colors.
950  * @ignore
951  */
952 UserPreferences.prototype.getRecentColors = function() {
953   return this.recentColors;
954 }
955 
956 /**
957  * Sets the recent colors list and notifies listeners of this change.
958  * @ignore
959  */
960 UserPreferences.prototype.setRecentColors = function(recentColors) {
961   if (recentColors != this.recentColors) {
962     var oldRecentColors = this.recentColors;
963     this.recentColors = recentColors.slice(0);
964     this.propertyChangeSupport.firePropertyChange("RECENT_COLORS", 
965         oldRecentColors, this.getRecentColors());
966   }
967 }
968 
969 /**
970  * Returns the list of the recent textures.
971  * @ignore
972  */
973 UserPreferences.prototype.getRecentTextures = function() {
974   return this.recentTextures;
975 }
976 
977 /**
978  * Sets the recent colors list and notifies listeners of this change.
979  * @ignore
980  */
981 UserPreferences.prototype.setRecentTextures = function(recentTextures) {
982   if (recentTextures != this.recentTextures) {
983     var oldRecentTextures = this.recentTextures;
984     this.recentTextures = recentTextures.slice(0);
985     this.propertyChangeSupport.firePropertyChange("RECENT_TEXTURES", 
986         oldRecentTextures, this.getRecentTextures());
987   }
988 }
989 
990 /**
991  * Sets the home examples available for the user.
992  * @param {HomeDescriptor[]} homeExamples an array of examples
993  * @since 5.5
994  * @ignore
995  */
996 UserPreferences.prototype.setHomeExamples = function(homeExamples) {
997   if (homeExamples != this.homeExamples) {
998     var oldExamples = this.homeExamples;
999     this.homeExamples = homeExamples.slice(0);
1000     this.propertyChangeSupport.firePropertyChange("HOME_EXAMPLES", 
1001         oldExamples, this.getHomeExamples());
1002   }
1003 }
1004 
1005 /**
1006  * Returns the home examples available for the user.
1007  * @return {HomeDescriptor[]} an array of examples.
1008  * @since 5.5
1009  * @ignore
1010  */
1011 UserPreferences.prototype.getHomeExamples = function() {
1012   return this.homeExamples;
1013 }
1014 
1015 /**
1016  * A resource bundle with a prefix added to resource key.
1017  */
1018 // TODO
1019 //private static class PrefixedResourceBundle extends ResourceBundle {
1020 //  private ResourceBundle resourceBundle;
1021 //  private var         keyPrefix;
1022 //
1023 //  public PrefixedResourceBundle(ResourceBundle resourceBundle, 
1024 //                                var keyPrefix) {
1025 //    this.resourceBundle = resourceBundle;
1026 //    this.keyPrefix = keyPrefix;
1027 //  }
1028 //  
1029 ////  public Locale getLocale() {
1030 //    return this.resourceBundle.getLocale();
1031 //  }
1032 //  
1033 ////  protected Object handleGetObject(String key) {
1034 //    key = this.keyPrefix + key;
1035 //    return this.resourceBundle.getObject(key);
1036 //  }    
1037 //
1038 ////  public Enumeration<String> getKeys() {
1039 //    return this.resourceBundle.getKeys();
1040 //  }    
1041 //}
1042 
1043 
1044 /**
1045  * Default user preferences.
1046  * @constructor
1047  * @extends UserPreferences
1048  * @author Emmanuel Puybaret
1049  */
1050 function DefaultUserPreferences() {
1051   UserPreferences.call(this);
1052   this.setUnit(LengthUnit.CENTIMETER);
1053   this.setNavigationPanelVisible(false);
1054 }
1055 DefaultUserPreferences.prototype = Object.create(UserPreferences.prototype);
1056 DefaultUserPreferences.prototype.constructor = DefaultUserPreferences;
1057