/* * AppletUserPreferences.java 11 Oct 2008 * * Sweet Home 3D, Copyright (c) 2008 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.applet; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.net.URLEncoder; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import com.eteks.sweethome3d.io.DefaultFurnitureCatalog; import com.eteks.sweethome3d.io.DefaultTexturesCatalog; import com.eteks.sweethome3d.io.DefaultUserPreferences; import com.eteks.sweethome3d.model.CatalogPieceOfFurniture; import com.eteks.sweethome3d.model.CatalogTexture; import com.eteks.sweethome3d.model.FurnitureCatalog; import com.eteks.sweethome3d.model.FurnitureCategory; import com.eteks.sweethome3d.model.LengthUnit; import com.eteks.sweethome3d.model.Library; import com.eteks.sweethome3d.model.PatternsCatalog; import com.eteks.sweethome3d.model.RecorderException; import com.eteks.sweethome3d.model.TextureImage; import com.eteks.sweethome3d.model.TexturesCatalog; import com.eteks.sweethome3d.model.TexturesCategory; import com.eteks.sweethome3d.model.UserPreferences; /** * Applet user preferences. * @author Emmanuel Puybaret */ public class AppletUserPreferences extends UserPreferences { private static final String LANGUAGE = "language"; private static final String UNIT = "unit"; private static final String FURNITURE_CATALOG_VIEWED_IN_TREE = "furnitureCatalogViewedInTree"; private static final String NAVIGATION_PANEL_VISIBLE = "navigationPanelVisible"; private static final String AERIAL_VIEW_CENTERED_ON_SELECTION_ENABLED = "aerialViewCenteredOnSelectionEnabled"; private static final String MAGNETISM_ENABLED = "magnetismEnabled"; private static final String RULERS_VISIBLE = "rulersVisible"; private static final String GRID_VISIBLE = "gridVisible"; private static final String FURNITURE_VIEWED_FROM_TOP = "furnitureViewedFromTop"; private static final String ROOM_FLOOR_COLORED_OR_TEXTURED = "roomFloorColoredOrTextured"; private static final String WALL_PATTERN = "wallPattern"; private static final String NEW_WALL_PATTERN = "newWallPattern"; private static final String NEW_WALL_HEIGHT = "newHomeWallHeight"; private static final String NEW_WALL_THICKNESS = "newWallThickness"; private static final String NEW_FLOOR_THICKNESS = "newFloorThickness"; private static final String RECENT_HOMES = "recentHomes#"; private static final String IGNORED_ACTION_TIP = "ignoredActionTip#"; private final URL [] pluginFurnitureCatalogURLs; private final URL furnitureResourcesUrlBase; private final URL [] pluginTexturesCatalogURLs; private final URL texturesResourcesUrlBase; private Properties properties; private final URL writePreferencesURL; private final URL readPreferencesURL; private Executor catalogsLoader; private Executor updater; private final Map<String, Boolean> ignoredActionTips = new HashMap<String, Boolean>(); /** * Creates default user preferences read from resource files and catalogs urls given in parameter. */ public AppletUserPreferences(URL [] pluginFurnitureCatalogURLs, URL [] pluginTexturesCatalogURLs) { this(pluginFurnitureCatalogURLs, pluginTexturesCatalogURLs, null, null); } /** * Creates default user preferences read from resource files and catalogs urls given in parameter, * then reads saved user preferences from the XML content returned by <code>readPreferencesURL</code>, * if URL isn't <code>null</code> or empty. * Preferences modifications will be notified to <code>writePreferencesURL</code> with * an XML content describing preferences in a parameter named preferences, * if URL isn't <code>null</code> or empty. * The DTD of XML content is specified at * <a href="http://java.sun.com/dtd/properties.dtd">http://java.sun.com/dtd/properties.dtd</a>. * Preferences written with this class don't include imported furniture and textures. */ public AppletUserPreferences(URL [] pluginFurnitureCatalogURLs, URL [] pluginTexturesCatalogURLs, URL writePreferencesURL, URL readPreferencesURL) { this(pluginFurnitureCatalogURLs, pluginTexturesCatalogURLs, writePreferencesURL, readPreferencesURL, null); } /** * Creates default user preferences read from resource files and catalogs urls given in parameter, * then reads saved user preferences from the XML content returned by <code>readPreferencesURL</code>, * if URL isn't <code>null</code> or empty. * Preferences modifications will be notified to <code>writePreferencesURL</code> with * an XML content describing preferences in a parameter named preferences, * if URL isn't <code>null</code> or empty. * The DTD of XML content is specified at * <a href="http://java.sun.com/dtd/properties.dtd">http://java.sun.com/dtd/properties.dtd</a>. * Preferences written with this class don't include imported furniture and textures. */ public AppletUserPreferences(URL [] pluginFurnitureCatalogURLs, URL [] pluginTexturesCatalogURLs, URL writePreferencesURL, URL readPreferencesURL, String userLanguage) { this(pluginFurnitureCatalogURLs, null, pluginTexturesCatalogURLs, null, writePreferencesURL, readPreferencesURL, userLanguage); } /** * Creates default user preferences read from resource files and catalogs urls given in parameter, * then reads saved user preferences from the XML content returned by <code>readPreferencesURL</code>, * if URL isn't <code>null</code> or empty. * Preferences modifications will be notified to <code>writePreferencesURL</code> with * an XML content describing preferences in a parameter named preferences, * if URL isn't <code>null</code> or empty. * The DTD of XML content is specified at * <a href="http://java.sun.com/dtd/properties.dtd">http://java.sun.com/dtd/properties.dtd</a>. * Preferences written with this class don't include imported furniture and textures. */ public AppletUserPreferences(URL [] pluginFurnitureCatalogURLs, URL furnitureResourcesUrlBase, URL [] pluginTexturesCatalogURLs, URL texturesResourcesUrlBase, URL writePreferencesURL, URL readPreferencesURL, String userLanguage) { this(pluginFurnitureCatalogURLs, furnitureResourcesUrlBase, pluginTexturesCatalogURLs, texturesResourcesUrlBase, writePreferencesURL, readPreferencesURL, null, userLanguage); } /** * Creates default user preferences read from resource files and catalogs urls given in parameter, * then reads saved user preferences from the XML content returned by <code>readPreferencesURL</code>, * if URL isn't <code>null</code> or empty. * Preferences modifications will be notified to <code>writePreferencesURL</code> with * an XML content describing preferences in a parameter named preferences, * if URL isn't <code>null</code> or empty. * The DTD of XML content is specified at * <a href="http://java.sun.com/dtd/properties.dtd">http://java.sun.com/dtd/properties.dtd</a>. * Preferences written with this class don't include imported furniture and textures. */ public AppletUserPreferences(URL [] pluginFurnitureCatalogURLs, URL furnitureResourcesUrlBase, URL [] pluginTexturesCatalogURLs, URL texturesResourcesUrlBase, URL writePreferencesURL, URL readPreferencesURL, Executor updater, String userLanguage) { this.pluginFurnitureCatalogURLs = pluginFurnitureCatalogURLs; this.furnitureResourcesUrlBase = furnitureResourcesUrlBase; this.pluginTexturesCatalogURLs = pluginTexturesCatalogURLs; this.texturesResourcesUrlBase = texturesResourcesUrlBase; this.writePreferencesURL = writePreferencesURL; this.readPreferencesURL = readPreferencesURL; if (updater == null) { this.catalogsLoader = this.updater = new Executor() { public void execute(Runnable command) { command.run(); } }; } else { this.catalogsLoader = Executors.newSingleThreadExecutor(); this.updater = updater; } final Properties properties = getProperties(); if (userLanguage == null) { userLanguage = getLanguage(); } if (!Arrays.asList(getSupportedLanguages()).contains(userLanguage)) { userLanguage = Locale.ENGLISH.getLanguage(); } setLanguage(properties.getProperty(LANGUAGE, userLanguage)); // Read default furniture and textures catalog setFurnitureCatalog(new FurnitureCatalog()); setTexturesCatalog(new TexturesCatalog()); updateDefaultCatalogs(); DefaultUserPreferences defaultPreferences = new DefaultUserPreferences(); defaultPreferences.setLanguage(getLanguage()); // Fill default patterns catalog PatternsCatalog patternsCatalog = defaultPreferences.getPatternsCatalog(); setPatternsCatalog(patternsCatalog); // Read other preferences setUnit(LengthUnit.valueOf(properties.getProperty(UNIT, defaultPreferences.getLengthUnit().name()))); setFurnitureCatalogViewedInTree(Boolean.parseBoolean(properties.getProperty(FURNITURE_CATALOG_VIEWED_IN_TREE, String.valueOf(defaultPreferences.isFurnitureCatalogViewedInTree())))); setNavigationPanelVisible(Boolean.parseBoolean(properties.getProperty(NAVIGATION_PANEL_VISIBLE, String.valueOf(defaultPreferences.isNavigationPanelVisible())))); setAerialViewCenteredOnSelectionEnabled(Boolean.parseBoolean(properties.getProperty(AERIAL_VIEW_CENTERED_ON_SELECTION_ENABLED, String.valueOf(defaultPreferences.isAerialViewCenteredOnSelectionEnabled())))); setMagnetismEnabled(Boolean.parseBoolean(properties.getProperty(MAGNETISM_ENABLED, "true"))); setRulersVisible(Boolean.parseBoolean(properties.getProperty(RULERS_VISIBLE, String.valueOf(defaultPreferences.isMagnetismEnabled())))); setGridVisible(Boolean.parseBoolean(properties.getProperty(GRID_VISIBLE, String.valueOf(defaultPreferences.isGridVisible())))); setFurnitureViewedFromTop(Boolean.parseBoolean(properties.getProperty(FURNITURE_VIEWED_FROM_TOP, String.valueOf(defaultPreferences.isFurnitureViewedFromTop())))); setFloorColoredOrTextured(Boolean.parseBoolean(properties.getProperty(ROOM_FLOOR_COLORED_OR_TEXTURED, String.valueOf(defaultPreferences.isRoomFloorColoredOrTextured())))); try { setWallPattern(patternsCatalog.getPattern(properties.getProperty(WALL_PATTERN, defaultPreferences.getWallPattern().getName()))); } catch (IllegalArgumentException ex) { // Ensure wall pattern always exists even if new patterns are added in future versions setWallPattern(defaultPreferences.getWallPattern()); } try { if (defaultPreferences.getNewWallPattern() != null) { setNewWallPattern(patternsCatalog.getPattern(properties.getProperty(NEW_WALL_PATTERN, defaultPreferences.getNewWallPattern().getName()))); } } catch (IllegalArgumentException ex) { // Keep new wall pattern unchanged } setNewWallThickness(Float.parseFloat(properties.getProperty(NEW_WALL_THICKNESS, String.valueOf(defaultPreferences.getNewWallThickness())))); setNewWallHeight(Float.parseFloat(properties.getProperty(NEW_WALL_HEIGHT, String.valueOf(defaultPreferences.getNewWallHeight())))); setNewFloorThickness(Float.parseFloat(properties.getProperty(NEW_FLOOR_THICKNESS, String.valueOf(defaultPreferences.getNewFloorThickness())))); setCurrency(defaultPreferences.getCurrency()); setRecentColors(new ArrayList<Integer>()); // Read recent homes list List<String> recentHomes = new ArrayList<String>(); for (int i = 1; i <= getRecentHomesMaxCount(); i++) { String recentHome = properties.getProperty(RECENT_HOMES + i, null); if (recentHome != null) { recentHomes.add(recentHome); } } setRecentHomes(recentHomes); // Read ignored action tips for (int i = 1; ; i++) { String ignoredActionTip = properties.getProperty(IGNORED_ACTION_TIP + i, ""); if (ignoredActionTip.length() == 0) { break; } else { this.ignoredActionTips.put(ignoredActionTip, true); } } addPropertyChangeListener(Property.LANGUAGE, new PropertyChangeListener() { public void propertyChange(PropertyChangeEvent evt) { updateDefaultCatalogs(); } }); } /** * Reloads furniture and textures default catalogs. */ private void updateDefaultCatalogs() { // Delete default pieces of current furniture catalog final FurnitureCatalog furnitureCatalog = getFurnitureCatalog(); for (FurnitureCategory category : furnitureCatalog.getCategories()) { for (CatalogPieceOfFurniture piece : category.getFurniture()) { if (!piece.isModifiable()) { furnitureCatalog.delete(piece); } } } // Add default pieces this.catalogsLoader.execute(new Runnable() { public void run() { final DefaultFurnitureCatalog defaultFurnitureCatalog = new DefaultFurnitureCatalog(pluginFurnitureCatalogURLs, furnitureResourcesUrlBase); for (final FurnitureCategory category : defaultFurnitureCatalog.getCategories()) { for (final CatalogPieceOfFurniture piece : category.getFurniture()) { updater.execute(new Runnable() { public void run() { furnitureCatalog.add(category, piece); } }); } } } }); // Delete default textures of current textures catalog final TexturesCatalog texturesCatalog = getTexturesCatalog(); for (TexturesCategory category : texturesCatalog.getCategories()) { for (CatalogTexture texture : category.getTextures()) { if (!texture.isModifiable()) { texturesCatalog.delete(texture); } } } // Add default textures this.catalogsLoader.execute(new Runnable() { public void run() { final DefaultTexturesCatalog defaultTexturesCatalog = new DefaultTexturesCatalog(pluginTexturesCatalogURLs, texturesResourcesUrlBase); for (final TexturesCategory category : defaultTexturesCatalog.getCategories()) { for (final CatalogTexture texture : category.getTextures()) { updater.execute(new Runnable() { public void run() { texturesCatalog.add(category, texture); } }); } } } }); } /** * Writes user preferences. This method sends to the <code>writePreferencesURL</code> * given at the creation a XML content describing preferences in a parameter named preferences. */ @Override public void write() throws RecorderException { Properties properties = getProperties(); // Write other preferences properties.setProperty(LANGUAGE, getLanguage()); properties.setProperty(UNIT, getLengthUnit().name()); properties.setProperty(FURNITURE_CATALOG_VIEWED_IN_TREE, String.valueOf(isFurnitureCatalogViewedInTree())); properties.setProperty(NAVIGATION_PANEL_VISIBLE, String.valueOf(isNavigationPanelVisible())); properties.setProperty(AERIAL_VIEW_CENTERED_ON_SELECTION_ENABLED, String.valueOf(isAerialViewCenteredOnSelectionEnabled())); properties.setProperty(MAGNETISM_ENABLED, String.valueOf(isMagnetismEnabled())); properties.setProperty(RULERS_VISIBLE, String.valueOf(isRulersVisible())); properties.setProperty(GRID_VISIBLE, String.valueOf(isGridVisible())); properties.setProperty(FURNITURE_VIEWED_FROM_TOP, String.valueOf(isFurnitureViewedFromTop())); properties.setProperty(ROOM_FLOOR_COLORED_OR_TEXTURED, String.valueOf(isRoomFloorColoredOrTextured())); properties.setProperty(WALL_PATTERN, getWallPattern().getName()); TextureImage newWallPattern = getNewWallPattern(); if (newWallPattern != null) { properties.setProperty(NEW_WALL_PATTERN, newWallPattern.getName()); } properties.setProperty(NEW_WALL_THICKNESS, String.valueOf(getNewWallThickness())); properties.setProperty(NEW_WALL_HEIGHT, String.valueOf(getNewWallHeight())); properties.setProperty(NEW_FLOOR_THICKNESS, String.valueOf(getNewFloorThickness())); // Write recent homes list int i = 1; for (Iterator<String> it = getRecentHomes().iterator(); it.hasNext() && i <= getRecentHomesMaxCount(); i ++) { properties.setProperty(RECENT_HOMES + i, it.next()); } // Write ignored action tips i = 1; for (Iterator<Map.Entry<String, Boolean>> it = this.ignoredActionTips.entrySet().iterator(); it.hasNext(); ) { Entry<String, Boolean> ignoredActionTipEntry = it.next(); if (ignoredActionTipEntry.getValue()) { properties.setProperty(IGNORED_ACTION_TIP + i++, ignoredActionTipEntry.getKey()); } } try { // Write preferences if (this.writePreferencesURL != null) { writePreferences(getProperties()); } } catch (IOException ex) { throw new RecorderException("Couldn't write preferences", ex); } } /** * Returns Java preferences for current system user. */ private Properties getProperties() { if (this.properties == null) { this.properties = new Properties(); if (this.readPreferencesURL != null) { readPreferences(this.properties); } } return this.properties; } /** * Reads user preferences. */ private void readPreferences(Properties properties) { URLConnection connection = null; InputStream in = null; try { // Open an input stream to server connection = this.readPreferencesURL.openConnection(); connection.setRequestProperty("Content-Type", "charset=UTF-8"); connection.setUseCaches(false); in = connection.getInputStream(); properties.loadFromXML(in); } catch (IOException ex) { // Let default preferences unchanged } finally { try { if (in != null) { in.close(); } } catch (IOException ex) { // Let default preferences unchanged } } } /** * Writes user preferences. */ private void writePreferences(Properties properties) throws IOException { HttpURLConnection connection = null; try { ByteArrayOutputStream bytes = new ByteArrayOutputStream(); properties.storeToXML(bytes, "Applet user preferences 1.0"); bytes.close(); connection = (HttpURLConnection)this.writePreferencesURL.openConnection(); connection.setRequestMethod("POST"); connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8"); connection.setDoOutput(true); connection.setDoInput(true); connection.setUseCaches(false); OutputStream out = connection.getOutputStream(); out.write("preferences=".getBytes("UTF-8")); out.write(URLEncoder.encode(new String(bytes.toByteArray(), "UTF-8"), "UTF-8").getBytes("UTF-8")); out.close(); // Read response InputStream in = connection.getInputStream(); int read = in.read(); in.close(); if (read != '1') { throw new IOException("Saving preferences failed"); } } finally { if (connection != null) { connection.disconnect(); } } } /** * Sets which action tip should be ignored. */ @Override public void setActionTipIgnored(String actionKey) { this.ignoredActionTips.put(actionKey, true); super.setActionTipIgnored(actionKey); } /** * Returns whether an action tip should be ignored or not. */ @Override public boolean isActionTipIgnored(String actionKey) { Boolean ignoredActionTip = this.ignoredActionTips.get(actionKey); return ignoredActionTip != null && ignoredActionTip.booleanValue(); } /** * Resets the display flag of action tips. */ @Override public void resetIgnoredActionTips() { for (Iterator<Map.Entry<String, Boolean>> it = this.ignoredActionTips.entrySet().iterator(); it.hasNext(); ) { Entry<String, Boolean> ignoredActionTipEntry = it.next(); ignoredActionTipEntry.setValue(false); } super.resetIgnoredActionTips(); } /** * Throws an exception because applet user preferences can't manage language libraries. */ @Override public void addLanguageLibrary(String location) throws RecorderException { throw new UnsupportedOperationException(); } /** * Throws an exception because applet user preferences can't manage additional language libraries. */ @Override public boolean languageLibraryExists(String location) throws RecorderException { throw new UnsupportedOperationException(); } /** * Returns <code>true</code> if the furniture library at the given <code>location</code> exists. */ @Override public boolean furnitureLibraryExists(String location) throws RecorderException { throw new UnsupportedOperationException(); } /** * Throws an exception because applet user preferences can't manage additional furniture libraries. */ @Override public void addFurnitureLibrary(String location) throws RecorderException { throw new UnsupportedOperationException(); } /** * Returns <code>true</code> if the textures library at the given <code>location</code> exists. */ @Override public boolean texturesLibraryExists(String location) throws RecorderException { throw new UnsupportedOperationException(); } /** * Throws an exception because applet user preferences can't manage additional textures libraries. */ @Override public void addTexturesLibrary(String location) throws RecorderException { throw new UnsupportedOperationException(); } /** * Throws an exception because applet user preferences don't manage additional libraries. */ @Override public List<Library> getLibraries() { throw new UnsupportedOperationException(); } }