/*
* UserPreferences.java 15 mai 2006
*
* Sweet Home 3D, Copyright (c) 2006 Emmanuel PUYBARET / eTeks <info@eteks.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package com.eteks.sweethome3d.model;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.io.InputStream;
import java.security.AccessControlException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.Properties;
import java.util.PropertyPermission;
import java.util.PropertyResourceBundle;
import java.util.ResourceBundle;
/**
* User preferences.
* @author Emmanuel Puybaret
*/
public abstract class UserPreferences {
/**
* The properties of user preferences that may change. <code>PropertyChangeListener</code>s added
* to user preferences will be notified under a property name equal to the string value of one these properties.
*/
public enum Property {LANGUAGE, SUPPORTED_LANGUAGES, UNIT, MAGNETISM_ENABLED, RULERS_VISIBLE, GRID_VISIBLE,
FURNITURE_VIEWED_FROM_TOP, ROOM_FLOOR_COLORED_OR_TEXTURED, WALL_PATTERN, NEW_WALL_PATTERN,
NEW_WALL_HEIGHT, NEW_WALL_THICKNESS, NEW_FLOOR_THICKNESS, RECENT_HOMES, IGNORED_ACTION_TIP,
FURNITURE_CATALOG_VIEWED_IN_TREE, NAVIGATION_PANEL_VISIBLE, AERIAL_VIEW_CENTERED_ON_SELECTION_ENABLED,
CHECK_UPDATES_ENABLED, UPDATES_MINIMUM_DATE, AUTO_SAVE_DELAY_FOR_RECOVERY, AUTO_COMPLETION_STRINGS,
RECENT_COLORS}
public static final String FURNITURE_LIBRARY_TYPE = "Furniture library";
public static final String TEXTURES_LIBRARY_TYPE = "Textures library";
public static final String LANGUAGE_LIBRARY_TYPE = "Language library";
private static final String [] DEFAULT_SUPPORTED_LANGUAGES;
private static final List<ClassLoader> DEFAULT_CLASS_LOADER =
Arrays.asList(new ClassLoader [] {UserPreferences.class.getClassLoader()});
private static final TextStyle DEFAULT_TEXT_STYLE = new TextStyle(18f);
private static final TextStyle DEFAULT_ROOM_TEXT_STYLE = new TextStyle(24f);
static {
Properties supportedLanguagesProperties = new Properties();
String [] defaultSupportedLanguages;
try {
// As of version 4.1 where Trusted-Library manifest attribute was added to applet jars,
// UserPreferences.properties was renamed as SupportedLanguages.properties
// because strangely UserPreferences.properties resource couldn't be found in applet environment
InputStream in = UserPreferences.class.getResourceAsStream("SupportedLanguages.properties");
supportedLanguagesProperties.load(in);
in.close();
// Get property value of supportedLanguages
defaultSupportedLanguages = supportedLanguagesProperties.getProperty("supportedLanguages", "en").split("\\s");
} catch (IOException ex) {
defaultSupportedLanguages = new String [] {"en"};
}
DEFAULT_SUPPORTED_LANGUAGES = defaultSupportedLanguages;
}
private final PropertyChangeSupport propertyChangeSupport;
private final Map<Class<?>, ResourceBundle> classResourceBundles;
private final Map<String, ResourceBundle> resourceBundles;
private FurnitureCatalog furnitureCatalog;
private TexturesCatalog texturesCatalog;
private PatternsCatalog patternsCatalog;
private final String defaultCountry;
private String [] supportedLanguages;
private String language;
private String currency;
private LengthUnit unit;
private boolean furnitureCatalogViewedInTree = true;
private boolean aerialViewCenteredOnSelectionEnabled;
private boolean navigationPanelVisible = true;
private boolean magnetismEnabled = true;
private boolean rulersVisible = true;
private boolean gridVisible = true;
private boolean furnitureViewedFromTop;
private boolean roomFloorColoredOrTextured;
private TextureImage wallPattern;
private TextureImage newWallPattern;
private float newWallThickness;
private float newWallHeight;
private float newFloorThickness;
private List<String> recentHomes;
private boolean checkUpdatesEnabled;
private Long updatesMinimumDate;
private int autoSaveDelayForRecovery;
private Map<String, List<String>> autoCompletionStrings;
private List<Integer> recentColors;
/**
* Creates user preferences.</br>
* Caution: during creation, the default locale will be updated if it doesn't belong to the supported ones.
*/
public UserPreferences() {
this.propertyChangeSupport = new PropertyChangeSupport(this);
this.classResourceBundles = new HashMap<Class<?>, ResourceBundle>();
this.resourceBundles = new HashMap<String, ResourceBundle>();
this.autoCompletionStrings = new LinkedHashMap<String, List<String>>();
this.supportedLanguages = DEFAULT_SUPPORTED_LANGUAGES;
this.defaultCountry = Locale.getDefault().getCountry();
String defaultLanguage = Locale.getDefault().getLanguage();
// Find closest language among supported languages in Sweet Home 3D
// For example, use simplified Chinese even for Chinese users (zh_?) not from China (zh_CN)
// unless their exact locale is supported as in Taiwan (zh_TW)
for (String supportedLanguage : this.supportedLanguages) {
if (supportedLanguage.equals(defaultLanguage + "_" + this.defaultCountry)) {
this.language = supportedLanguage;
break; // Found the exact supported language
} else if (this.language == null
&& supportedLanguage.startsWith(defaultLanguage)) {
this.language = supportedLanguage; // Found a supported language
}
}
// If no language was found, let's use English by default
if (this.language == null) {
this.language = Locale.ENGLISH.getLanguage();
}
updateDefaultLocale();
}
/**
* Updates default locale from preferences language.
*/
private void updateDefaultLocale() {
try {
int underscoreIndex = this.language.indexOf("_");
if (underscoreIndex != -1) {
Locale.setDefault(new Locale(this.language.substring(0, underscoreIndex),
this.language.substring(underscoreIndex + 1)));
} else {
Locale.setDefault(new Locale(this.language, this.defaultCountry));
}
} catch (AccessControlException ex) {
// Let's keep default language even if it's not supported
this.language = Locale.getDefault().getLanguage();
}
}
/**
* Writes user preferences.
* @throws RecorderException if user preferences couldn'y be saved.
*/
public abstract void write() throws RecorderException;
/**
* Adds the <code>listener</code> in parameter to these preferences.
* <br>Caution: a user preferences instance generally exists during all the application ;
* therefore you should take care of not bounding permanently listeners to this
* object (for example, do not create anonymous listeners on user preferences
* in classes depending on an edited home).
*/
public void addPropertyChangeListener(Property property,
PropertyChangeListener listener) {
this.propertyChangeSupport.addPropertyChangeListener(property.name(), listener);
}
/**
* Removes the <code>listener</code> in parameter from these preferences.
*/
public void removePropertyChangeListener(Property property,
PropertyChangeListener listener) {
this.propertyChangeSupport.removePropertyChangeListener(property.name(), listener);
}
/**
* Returns the furniture catalog.
*/
public FurnitureCatalog getFurnitureCatalog() {
return this.furnitureCatalog;
}
/**
* Sets furniture catalog.
*/
protected void setFurnitureCatalog(FurnitureCatalog catalog) {
this.furnitureCatalog = catalog;
}
/**
* Returns the textures catalog.
*/
public TexturesCatalog getTexturesCatalog() {
return this.texturesCatalog;
}
/**
* Sets textures catalog.
*/
protected void setTexturesCatalog(TexturesCatalog catalog) {
this.texturesCatalog = catalog;
}
/**
* Returns the patterns catalog available to fill plan areas.
*/
public PatternsCatalog getPatternsCatalog() {
return this.patternsCatalog;
}
/**
* Sets the patterns available to fill plan areas.
*/
protected void setPatternsCatalog(PatternsCatalog catalog) {
this.patternsCatalog = catalog;
}
/**
* Returns the length unit currently in use.
*/
public LengthUnit getLengthUnit() {
return this.unit;
}
/**
* Changes the unit currently in use, and notifies listeners of this change.
* @param unit one of the values of Unit.
*/
public void setUnit(LengthUnit unit) {
if (this.unit != unit) {
LengthUnit oldUnit = this.unit;
this.unit = unit;
this.propertyChangeSupport.firePropertyChange(Property.UNIT.name(), oldUnit, unit);
}
}
/**
* Returns the preferred language to display information, noted with an ISO 639 code
* that may be followed by an underscore and an ISO 3166 code.
*/
public String getLanguage() {
return this.language;
}
/**
* If {@linkplain #isLanguageEditable() language can be changed}, sets the preferred language to display information,
* changes current default locale accordingly and notifies listeners of this change.
* @param language an ISO 639 code that may be followed by an underscore and an ISO 3166 code
* (for example fr, de, it, en_US, zh_CN).
*/
public void setLanguage(String language) {
if (!language.equals(this.language)
&& isLanguageEditable()) {
String oldLanguage = this.language;
this.language = language;
updateDefaultLocale();
this.classResourceBundles.clear();
this.resourceBundles.clear();
this.propertyChangeSupport.firePropertyChange(Property.LANGUAGE.name(),
oldLanguage, language);
}
}
/**
* Returns <code>true</code> if the language in preferences can be set.
* @return <code>true</code> except if <code>user.language</code> System property isn't writable.
* @since 3.4
*/
public boolean isLanguageEditable() {
try {
SecurityManager securityManager = System.getSecurityManager();
if (securityManager != null) {
securityManager.checkPermission(new PropertyPermission("user.language", "write"));
}
return true;
} catch (AccessControlException ex) {
return false;
}
}
/**
* Returns the array of default available languages in Sweet Home 3D.
*/
public String [] getDefaultSupportedLanguages() {
return DEFAULT_SUPPORTED_LANGUAGES.clone();
}
/**
* Returns the array of available languages in Sweet Home 3D including languages in libraries.
* @since 3.4
*/
public String [] getSupportedLanguages() {
return this.supportedLanguages.clone();
}
/**
* Returns the array of available languages in Sweet Home 3D.
* @since 3.4
*/
protected void setSupportedLanguages(String [] supportedLanguages) {
if (!Arrays.deepEquals(this.supportedLanguages, supportedLanguages)) {
String [] oldSupportedLanguages = this.supportedLanguages;
this.supportedLanguages = supportedLanguages.clone();
this.propertyChangeSupport.firePropertyChange(Property.SUPPORTED_LANGUAGES.name(),
oldSupportedLanguages, supportedLanguages);
}
}
/**
* Returns the string matching <code>resourceKey</code> in current language in the
* context of <code>resourceClass</code>.
* If <code>resourceParameters</code> isn't empty the string is considered
* as a format string, and the returned string will be formatted with these parameters.
* This implementation searches first the key in a properties file named as
* <code>resourceClass</code>, then if this file doesn't exist, it searches
* the key prefixed by <code>resourceClass</code> name and a dot in a package.properties file
* in the folder matching the package of <code>resourceClass</code>.
* @exception IllegalArgumentException if no string for the given key can be found
*/
public String getLocalizedString(Class<?> resourceClass,
String resourceKey,
Object ... resourceParameters) {
ResourceBundle classResourceBundle = this.classResourceBundles.get(resourceClass);
if (classResourceBundle == null) {
try {
classResourceBundle = getResourceBundle(resourceClass.getName());
this.classResourceBundles.put(resourceClass, classResourceBundle);
} catch (IOException ex) {
try {
String className = resourceClass.getName();
int lastIndex = className.lastIndexOf(".");
String resourceFamily;
if (lastIndex != -1) {
resourceFamily = className.substring(0, lastIndex) + ".package";
} else {
resourceFamily = "package";
}
classResourceBundle = new PrefixedResourceBundle(getResourceBundle(resourceFamily),
resourceClass.getSimpleName() + ".");
this.classResourceBundles.put(resourceClass, classResourceBundle);
} catch (IOException ex2) {
throw new IllegalArgumentException(
"Can't find resource bundle for " + resourceClass, ex);
}
}
}
return getLocalizedString(classResourceBundle, resourceKey, resourceParameters);
}
/**
* Returns the string matching <code>resourceKey</code> in current language
* for the given resource family.
* <code>resourceFamily</code> should match the absolute path of a .properties resource family,
* shouldn't start by a slash and may contain dots '.' or slash '/' as folder separators.
* If <code>resourceParameters</code> isn't empty the string is considered
* as a format string, and the returned string will be formatted with these parameters.
* This implementation searches the key in a properties file named as
* <code>resourceFamily</code>.
* @exception IllegalArgumentException if no string for the given key can be found
* @since 2.3
*/
public String getLocalizedString(String resourceFamily,
String resourceKey,
Object ... resourceParameters) {
try {
ResourceBundle resourceBundle = getResourceBundle(resourceFamily);
return getLocalizedString(resourceBundle, resourceKey, resourceParameters);
} catch (IOException ex) {
throw new IllegalArgumentException(
"Can't find resource bundle for " + resourceFamily, ex);
}
}
/**
* Returns a new resource bundle for the given <code>familyName</code>
* that matches current default locale. The search will be done
* only among .properties files.
* @throws IOException if no .properties file was found
*/
private ResourceBundle getResourceBundle(String resourceFamily) throws IOException {
resourceFamily = resourceFamily.replace('.', '/');
ResourceBundle resourceBundle = this.resourceBundles.get(resourceFamily);
if (resourceBundle != null) {
return resourceBundle;
}
Locale defaultLocale = Locale.getDefault();
String language = defaultLocale.getLanguage();
String country = defaultLocale.getCountry();
String [] suffixes = {".properties",
"_" + language + ".properties",
"_" + language + "_" + country + ".properties"};
for (String suffix : suffixes) {
for (ClassLoader classLoader : getResourceClassLoaders()) {
InputStream in = classLoader.getResourceAsStream(resourceFamily + suffix);
if (in != null) {
final ResourceBundle parentResourceBundle = resourceBundle;
try {
resourceBundle = new PropertyResourceBundle(in) {
{
setParent(parentResourceBundle);
}
};
break;
} catch (IllegalArgumentException ex) {
// May happen if the file contains some wrongly encoded characters
ex.printStackTrace();
} finally {
in.close();
}
}
}
}
if (resourceBundle == null) {
throw new IOException("No available resource bundle for " + resourceFamily);
}
this.resourceBundles.put(resourceFamily, resourceBundle);
return resourceBundle;
}
/**
* Returns the string matching <code>resourceKey</code> for the given resource bundle.
*/
private String getLocalizedString(ResourceBundle resourceBundle,
String resourceKey,
Object... resourceParameters) {
try {
String localizedString = resourceBundle.getString(resourceKey);
if (resourceParameters.length > 0) {
localizedString = String.format(localizedString, resourceParameters);
}
return localizedString;
} catch (MissingResourceException ex) {
throw new IllegalArgumentException("Unknown key " + resourceKey);
}
}
/**
* Returns the class loaders through which localized strings returned by
* {@link #getLocalizedString(Class, String, Object...) getLocalizedString} might be loaded.
* @since 2.3
*/
public List<ClassLoader> getResourceClassLoaders() {
return DEFAULT_CLASS_LOADER;
}
/**
* Returns the default currency in use, noted with ISO 4217 code, or <code>null</code>
* if prices aren't used in application.
*/
public String getCurrency() {
return this.currency;
}
/**
* Sets the default currency in use.
*/
protected void setCurrency(String currency) {
this.currency = currency;
}
/**
* Returns <code>true</code> if the furniture catalog should be viewed in a tree.
* @since 2.3
*/
public boolean isFurnitureCatalogViewedInTree() {
return this.furnitureCatalogViewedInTree;
}
/**
* Sets whether the furniture catalog should be viewed in a tree or a different way.
* @since 2.3
*/
public void setFurnitureCatalogViewedInTree(boolean furnitureCatalogViewedInTree) {
if (this.furnitureCatalogViewedInTree != furnitureCatalogViewedInTree) {
this.furnitureCatalogViewedInTree = furnitureCatalogViewedInTree;
this.propertyChangeSupport.firePropertyChange(Property.FURNITURE_CATALOG_VIEWED_IN_TREE.name(),
!furnitureCatalogViewedInTree, furnitureCatalogViewedInTree);
}
}
/**
* Returns <code>true</code> if the navigation panel should be displayed.
* @since 2.3
*/
public boolean isNavigationPanelVisible() {
return this.navigationPanelVisible;
}
/**
* Sets whether the navigation panel should be displayed or not.
* @since 2.3
*/
public void setNavigationPanelVisible(boolean navigationPanelVisible) {
if (this.navigationPanelVisible != navigationPanelVisible) {
this.navigationPanelVisible = navigationPanelVisible;
this.propertyChangeSupport.firePropertyChange(Property.NAVIGATION_PANEL_VISIBLE.name(),
!navigationPanelVisible, navigationPanelVisible);
}
}
/**
* Sets whether aerial view should be centered on selection or not.
* @since 4.0
*/
public void setAerialViewCenteredOnSelectionEnabled(boolean aerialViewCenteredOnSelectionEnabled) {
if (aerialViewCenteredOnSelectionEnabled != this.aerialViewCenteredOnSelectionEnabled) {
this.aerialViewCenteredOnSelectionEnabled = aerialViewCenteredOnSelectionEnabled;
this.propertyChangeSupport.firePropertyChange(Property.AERIAL_VIEW_CENTERED_ON_SELECTION_ENABLED.name(),
!aerialViewCenteredOnSelectionEnabled, aerialViewCenteredOnSelectionEnabled);
}
}
/**
* Returns whether aerial view should be centered on selection or not.
* @since 4.0
*/
public boolean isAerialViewCenteredOnSelectionEnabled() {
return this.aerialViewCenteredOnSelectionEnabled;
}
/**
* Returns <code>true</code> if magnetism is enabled.
* @return <code>true</code> by default.
*/
public boolean isMagnetismEnabled() {
return this.magnetismEnabled;
}
/**
* Sets whether magnetism is enabled or not, and notifies
* listeners of this change.
* @param magnetismEnabled <code>true</code> if magnetism is enabled,
* <code>false</code> otherwise.
*/
public void setMagnetismEnabled(boolean magnetismEnabled) {
if (this.magnetismEnabled != magnetismEnabled) {
this.magnetismEnabled = magnetismEnabled;
this.propertyChangeSupport.firePropertyChange(Property.MAGNETISM_ENABLED.name(),
!magnetismEnabled, magnetismEnabled);
}
}
/**
* Returns <code>true</code> if rulers are visible.
* @return <code>true</code> by default.
*/
public boolean isRulersVisible() {
return this.rulersVisible;
}
/**
* Sets whether rulers are visible or not, and notifies
* listeners of this change.
* @param rulersVisible <code>true</code> if rulers are visible,
* <code>false</code> otherwise.
*/
public void setRulersVisible(boolean rulersVisible) {
if (this.rulersVisible != rulersVisible) {
this.rulersVisible = rulersVisible;
this.propertyChangeSupport.firePropertyChange(Property.RULERS_VISIBLE.name(),
!rulersVisible, rulersVisible);
}
}
/**
* Returns <code>true</code> if plan grid visible.
* @return <code>true</code> by default.
*/
public boolean isGridVisible() {
return this.gridVisible;
}
/**
* Sets whether plan grid is visible or not, and notifies
* listeners of this change.
* @param gridVisible <code>true</code> if grid is visible,
* <code>false</code> otherwise.
*/
public void setGridVisible(boolean gridVisible) {
if (this.gridVisible != gridVisible) {
this.gridVisible = gridVisible;
this.propertyChangeSupport.firePropertyChange(Property.GRID_VISIBLE.name(),
!gridVisible, gridVisible);
}
}
/**
* Returns <code>true</code> if furniture should be viewed from its top in plan.
* @since 2.0
*/
public boolean isFurnitureViewedFromTop() {
return this.furnitureViewedFromTop;
}
/**
* Sets how furniture icon should be displayed in plan, and notifies
* listeners of this change.
* @param furnitureViewedFromTop if <code>true</code> the furniture
* should be viewed from its top.
* @since 2.0
*/
public void setFurnitureViewedFromTop(boolean furnitureViewedFromTop) {
if (this.furnitureViewedFromTop != furnitureViewedFromTop) {
this.furnitureViewedFromTop = furnitureViewedFromTop;
this.propertyChangeSupport.firePropertyChange(Property.FURNITURE_VIEWED_FROM_TOP.name(),
!furnitureViewedFromTop, furnitureViewedFromTop);
}
}
/**
* Returns <code>true</code> if room floors should be rendered with color or texture
* in plan.
* @return <code>false</code> by default.
* @since 2.0
*/
public boolean isRoomFloorColoredOrTextured() {
return this.roomFloorColoredOrTextured;
}
/**
* Sets whether room floors should be rendered with color or texture,
* and notifies listeners of this change.
* @param roomFloorColoredOrTextured <code>true</code> if floor color
* or texture is used, <code>false</code> otherwise.
* @since 2.0
*/
public void setFloorColoredOrTextured(boolean roomFloorColoredOrTextured) {
if (this.roomFloorColoredOrTextured != roomFloorColoredOrTextured) {
this.roomFloorColoredOrTextured = roomFloorColoredOrTextured;
this.propertyChangeSupport.firePropertyChange(Property.ROOM_FLOOR_COLORED_OR_TEXTURED.name(),
!roomFloorColoredOrTextured, roomFloorColoredOrTextured);
}
}
/**
* Returns the wall pattern in plan used by default.
* @since 2.0
*/
public TextureImage getWallPattern() {
return this.wallPattern;
}
/**
* Sets how walls should be displayed in plan by default, and notifies
* listeners of this change.
* @since 2.0
*/
public void setWallPattern(TextureImage wallPattern) {
if (this.wallPattern != wallPattern) {
TextureImage oldWallPattern = this.wallPattern;
this.wallPattern = wallPattern;
this.propertyChangeSupport.firePropertyChange(Property.WALL_PATTERN.name(),
oldWallPattern, wallPattern);
}
}
/**
* Returns the pattern used for new walls in plan or <code>null</code> if it's not set.
* @since 4.0
*/
public TextureImage getNewWallPattern() {
return this.newWallPattern;
}
/**
* Sets how new walls should be displayed in plan, and notifies
* listeners of this change.
* @since 4.0
*/
public void setNewWallPattern(TextureImage newWallPattern) {
if (this.newWallPattern != newWallPattern) {
TextureImage oldWallPattern = this.newWallPattern;
this.newWallPattern = newWallPattern;
this.propertyChangeSupport.firePropertyChange(Property.NEW_WALL_PATTERN.name(),
oldWallPattern, newWallPattern);
}
}
/**
* Returns default thickness of new walls in home.
*/
public float getNewWallThickness() {
return this.newWallThickness;
}
/**
* Sets default thickness of new walls in home, and notifies
* listeners of this change.
*/
public void setNewWallThickness(float newWallThickness) {
if (this.newWallThickness != newWallThickness) {
float oldDefaultThickness = this.newWallThickness;
this.newWallThickness = newWallThickness;
this.propertyChangeSupport.firePropertyChange(Property.NEW_WALL_THICKNESS.name(),
oldDefaultThickness, newWallThickness);
}
}
/**
* Returns default wall height of new home walls.
*/
public float getNewWallHeight() {
return this.newWallHeight;
}
/**
* Sets default wall height of new walls, and notifies
* listeners of this change.
*/
public void setNewWallHeight(float newWallHeight) {
if (this.newWallHeight != newWallHeight) {
float oldWallHeight = this.newWallHeight;
this.newWallHeight = newWallHeight;
this.propertyChangeSupport.firePropertyChange(Property.NEW_WALL_HEIGHT.name(),
oldWallHeight, newWallHeight);
}
}
/**
* Returns default thickness of the floor of new levels in home.
* @since 3.4
*/
public float getNewFloorThickness() {
return this.newFloorThickness;
}
/**
* Sets default thickness of the floor of new levels in home, and notifies
* listeners of this change.
* @since 3.4
*/
public void setNewFloorThickness(float newFloorThickness) {
if (this.newFloorThickness != newFloorThickness) {
float oldFloorThickness = this.newFloorThickness;
this.newFloorThickness = newFloorThickness;
this.propertyChangeSupport.firePropertyChange(Property.NEW_FLOOR_THICKNESS.name(),
oldFloorThickness, newFloorThickness);
}
}
/**
* Returns <code>true</code> if updates should be checked.
* @since 4.0
*/
public boolean isCheckUpdatesEnabled() {
return this.checkUpdatesEnabled;
}
/**
* Sets whether updates should be checked or not.
* @since 4.0
*/
public void setCheckUpdatesEnabled(boolean updatesChecked) {
if (updatesChecked != this.checkUpdatesEnabled) {
this.checkUpdatesEnabled = updatesChecked;
this.propertyChangeSupport.firePropertyChange(Property.CHECK_UPDATES_ENABLED.name(),
!updatesChecked, updatesChecked);
}
}
/**
* Returns the minimum date of updates that may interest user.
* @return the date expressed in millis second since the epoch or <code>null</code> if not defined.
* @since 4.0
*/
public Long getUpdatesMinimumDate() {
return this.updatesMinimumDate;
}
/**
* Sets the minimum date of updates that may interest user, and notifies
* listeners of this change.
* @since 4.0
*/
public void setUpdatesMinimumDate(Long updatesMinimumDate) {
if (this.updatesMinimumDate != updatesMinimumDate
&& (updatesMinimumDate == null || !updatesMinimumDate.equals(this.updatesMinimumDate))) {
Long oldUpdatesMinimumDate = this.updatesMinimumDate;
this.updatesMinimumDate = updatesMinimumDate;
this.propertyChangeSupport.firePropertyChange(Property.UPDATES_MINIMUM_DATE.name(),
oldUpdatesMinimumDate, updatesMinimumDate);
}
}
/**
* Returns the delay between two automatic save operations of homes for recovery purpose.
* @return a delay in milliseconds or 0 to disable auto save.
* @since 3.0
*/
public int getAutoSaveDelayForRecovery() {
return this.autoSaveDelayForRecovery;
}
/**
* Sets the delay between two automatic save operations of homes for recovery purpose.
* @since 3.0
*/
public void setAutoSaveDelayForRecovery(int autoSaveDelayForRecovery) {
if (this.autoSaveDelayForRecovery != autoSaveDelayForRecovery) {
float oldAutoSaveDelayForRecovery = this.autoSaveDelayForRecovery;
this.autoSaveDelayForRecovery = autoSaveDelayForRecovery;
this.propertyChangeSupport.firePropertyChange(Property.AUTO_SAVE_DELAY_FOR_RECOVERY.name(),
oldAutoSaveDelayForRecovery, autoSaveDelayForRecovery);
}
}
/**
* Returns an unmodifiable list of the recent homes.
*/
public List<String> getRecentHomes() {
return Collections.unmodifiableList(this.recentHomes);
}
/**
* Sets the recent homes list and notifies listeners of this change.
*/
public void setRecentHomes(List<String> recentHomes) {
if (!recentHomes.equals(this.recentHomes)) {
List<String> oldRecentHomes = this.recentHomes;
this.recentHomes = new ArrayList<String>(recentHomes);
this.propertyChangeSupport.firePropertyChange(Property.RECENT_HOMES.name(),
oldRecentHomes, getRecentHomes());
}
}
/**
* Returns the maximum count of homes that should be proposed to the user.
*/
public int getRecentHomesMaxCount() {
return 10;
}
/**
* Sets which action tip should be ignored.
* <br>This method should be overridden to store the ignore information.
* By default it just notifies listeners of this change.
*/
public void setActionTipIgnored(String actionKey) {
this.propertyChangeSupport.firePropertyChange(Property.IGNORED_ACTION_TIP.name(), null, actionKey);
}
/**
* Returns whether an action tip should be ignored or not.
* <br>This method should be overridden to return the the display information
* stored in {@link #setActionTipIgnored(String) setActionTipDisplayed}.
* By default it returns <code>true</code>.
*/
public boolean isActionTipIgnored(String actionKey) {
return true;
}
/**
* Resets the ignore flag of action tips.
* <br>This method should be overridden to clear all the display flags.
* By default it just notifies listeners of this change.
*/
public void resetIgnoredActionTips() {
this.propertyChangeSupport.firePropertyChange(Property.IGNORED_ACTION_TIP.name(), null, null);
}
/**
* Returns the default text style of a class of selectable item.
*/
public TextStyle getDefaultTextStyle(Class<? extends Selectable> selectableClass) {
if (Room.class.isAssignableFrom(selectableClass)) {
return DEFAULT_ROOM_TEXT_STYLE;
} else {
return DEFAULT_TEXT_STYLE;
}
}
/**
* Returns the strings that may be used for the auto completion of the given <code>property</code>.
* @since 3.4
*/
public List<String> getAutoCompletionStrings(String property) {
List<String> propertyAutoCompletionStrings = this.autoCompletionStrings.get(property);
if (propertyAutoCompletionStrings != null) {
return Collections.unmodifiableList(propertyAutoCompletionStrings);
} else {
return Collections.emptyList();
}
}
/**
* Adds the given string to the list of the strings used in auto completion of a <code>property</code>
* and notifies listeners of this change.
* @since 3.4
*/
public void addAutoCompletionString(String property, String autoCompletionString) {
if (autoCompletionString != null
&& autoCompletionString.length() > 0) {
List<String> propertyAutoCompletionStrings = this.autoCompletionStrings.get(property);
if (propertyAutoCompletionStrings == null) {
propertyAutoCompletionStrings = new ArrayList<String>();
} else if (!propertyAutoCompletionStrings.contains(autoCompletionString)) {
propertyAutoCompletionStrings = new ArrayList<String>(propertyAutoCompletionStrings);
} else {
return;
}
propertyAutoCompletionStrings.add(0, autoCompletionString);
setAutoCompletionStrings(property, propertyAutoCompletionStrings);
}
}
/**
* Sets the auto completion strings list of the given <code>property</code> and notifies listeners of this change.
* @since 3.4
*/
public void setAutoCompletionStrings(String property, List<String> autoCompletionStrings) {
List<String> propertyAutoCompletionStrings = this.autoCompletionStrings.get(property);
if (!autoCompletionStrings.equals(propertyAutoCompletionStrings)) {
this.autoCompletionStrings.put(property, new ArrayList<String>(autoCompletionStrings));
this.propertyChangeSupport.firePropertyChange(Property.AUTO_COMPLETION_STRINGS.name(),
null, property);
}
}
/**
* Returns the list of properties with auto completion strings.
* @since 3.4
*/
public List<String> getAutoCompletedProperties() {
if (this.autoCompletionStrings != null) {
return Arrays.asList(this.autoCompletionStrings.keySet().toArray(new String [this.autoCompletionStrings.size()]));
} else {
return Collections.emptyList();
}
}
/**
* Returns an unmodifiable list of the recent colors.
* @since 4.0
*/
public List<Integer> getRecentColors() {
return Collections.unmodifiableList(this.recentColors);
}
/**
* Sets the recent colors list and notifies listeners of this change.
* @since 4.0
*/
public void setRecentColors(List<Integer> recentColors) {
if (!recentColors.equals(this.recentColors)) {
List<Integer> oldRecentColors = this.recentColors;
this.recentColors = new ArrayList<Integer>(recentColors);
this.propertyChangeSupport.firePropertyChange(Property.RECENT_COLORS.name(),
oldRecentColors, getRecentColors());
}
}
/**
* Adds the language library to make the languages it contains available to supported languages.
* @param languageLibraryLocation the location where the library can be found.
* @since 2.3
*/
public abstract void addLanguageLibrary(String languageLibraryLocation) throws RecorderException;
/**
* Returns <code>true</code> if the language library at the given location exists.
* @param languageLibraryLocation the name of the resource to check
* @since 2.3
*/
public abstract boolean languageLibraryExists(String languageLibraryLocation) throws RecorderException;
/**
* Adds <code>furnitureLibraryName</code> to furniture catalog
* to make the furniture it contains available.
* @param furnitureLibraryLocation the location where the library can be found.
*/
public abstract void addFurnitureLibrary(String furnitureLibraryLocation) throws RecorderException;
/**
* Returns <code>true</code> if the furniture library at the given location exists.
* @param furnitureLibraryLocation the name of the resource to check
*/
public abstract boolean furnitureLibraryExists(String furnitureLibraryLocation) throws RecorderException;
/**
* Adds the textures library at the given location to textures catalog
* to make the textures it contains available.
* @param texturesLibraryLocation the location where the library can be found.
* @since 2.3
*/
public abstract void addTexturesLibrary(String texturesLibraryLocation) throws RecorderException;
/**
* Returns <code>true</code> if the textures library at the given location exists.
* @param texturesLibraryLocation the name of the resource to check
* @since 2.3
*/
public abstract boolean texturesLibraryExists(String texturesLibraryLocation) throws RecorderException;
/**
* Returns the libraries available in user preferences.
* @since 4.0
*/
public abstract List<Library> getLibraries();
/**
* A resource bundle with a prefix added to resource key.
*/
private static class PrefixedResourceBundle extends ResourceBundle {
private ResourceBundle resourceBundle;
private String keyPrefix;
public PrefixedResourceBundle(ResourceBundle resourceBundle,
String keyPrefix) {
this.resourceBundle = resourceBundle;
this.keyPrefix = keyPrefix;
}
@Override
public Locale getLocale() {
return this.resourceBundle.getLocale();
}
@Override
protected Object handleGetObject(String key) {
key = this.keyPrefix + key;
return this.resourceBundle.getObject(key);
}
@Override
public Enumeration<String> getKeys() {
return this.resourceBundle.getKeys();
}
}
}