/*
* funCKit - functional Circuit Kit
* Copyright (C) 2013 Lukas Elsner <open@mindrunner.de>
* Copyright (C) 2013 Peter Dahlberg <catdog2@tuxzone.org>
* Copyright (C) 2013 Julian Stier <mail@julian-stier.de>
* Copyright (C) 2013 Sebastian Vetter <mail@b4sti.eu>
* Copyright (C) 2013 Thomas Poxrucker <poxrucker_t@web.de>
* Copyright (C) 2013 Alexander Treml <alex.treml@directbox.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 3 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, see <http://www.gnu.org/licenses/>.
*/
package de.sep2011.funckit.model.sessionmodel;
import de.sep2011.funckit.model.graphmodel.AccessPoint;
import de.sep2011.funckit.model.graphmodel.Wire;
import de.sep2011.funckit.observer.AbstractObservable;
import de.sep2011.funckit.observer.SettingsInfo;
import de.sep2011.funckit.observer.SettingsObserver;
import de.sep2011.funckit.util.Log;
import net.iharder.Base64;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.io.Serializable;
import java.util.InvalidPropertiesFormatException;
import java.util.Properties;
/**
* Settings are application properties (or configuration) that should - in
* contrast to data from {@link SessionModel} - be persisted.
*/
public class Settings extends AbstractObservable<SettingsObserver, SettingsInfo> {
/**
* Must not be null.
*/
public static final String DEFAULT_BRICK_ORIENTATION = "brick.defaultOrientation";
/**
* Must not be null.
*/
public static final String DEFAULT_BRICK_WIDTH = "brick.defaultWidth";
/**
* Must not be null.
*/
public static final String DEFAULT_BRICK_HEIGHT = "brick.defaultHeight";
/**
* Must not be null. Specifies maximum length of command dispatcher.
*/
public static final String MAXIMUM_COMMAND_QUEUE_SIZE = "command.maximumQueueSize";
/**
* Must not be null.
*/
public static final String GRID_SIZE = "ui.gridSize";
/**
* Must not be null.
*/
public static final String GRID_COLOR = "ui.gridColor";
/**
* Must not be null. Specifies if docking to grid should be enabled.
*/
public static final String GRID_LOCK = "ui.gridLock";
/**
* Must not be null. Specifies if grid should be displayed.
*/
public static final String SHOW_GRID = "ui.gridToggle";
/**
* Must not be null.
*/
public static final String ELEMENT_FILL_COLOR = "ui.element.fillColor";
/**
* Must not be null.
*/
public static final String ELEMENT_BORDER_COLOR = "ui.element.borderColor";
/**
* Must not be null.
*/
public static final String ELEMENT_ERROR_BORDER_COLOR = "ui.element.error.borderColor";
/**
* Must not be null. Border color for active elements.
*/
public static final String ELEMENT_ACTIVE_BORDER_COLOR = "ui.element.active.borderColor";
/**
* Must not be null. Fill color for active elements.
*/
public static final String ELEMENT_ACTIVE_FILL_COLOR = "ui.element.active.fillColor";
/**
* Must not be null.
*/
public static final String ELEMENT_SELECTED_BORDER_COLOR = "ui.element.selected.borderColor";
/**
* Must not be null.
*/
public static final String ELEMENT_SELECTED_FILL_COLOR = "ui.element.selected.fillColor";
/**
* May be null.
*/
public static final String ELEMENT_INFO_BORDER_COLOR = "ui.element.info.borderColor";
/**
* May be null.
*/
public static final String ELEMENT_INFO_FILL_COLOR = "ui.element.info.fillColor";
/**
* Must not be null.
*/
public static final String ELEMENT_SIMULATED_BORDER_COLOR = "ui.element.simulated.borderColor";
/**
* Must not be null.
*/
public static final String ELEMENT_SIMULATED_FILL_COLOR = "ui.element.simulated.fillColor";
/**
* May be null. Extra color for wires if it should differ from element
* border color.
*/
public static final String WIRE_COLOR = "ui.wire.color";
/**
* May be null. Extra color for gates if it should differ from element
* border color.
*/
public static final String GATE_BORDER_COLOR = "ui.brick.borderColor";
/**
* May be null. Extra fill color for gates if it should differ from.
*/
public static final String GATE_FILL_COLOR = "ui.brick.fillColor";
/**
* Must not be null.
*/
public static final String GHOST_BORDER_COLOR = "ui.element.ghost.borderColor";
public static final String GHOST_TEXT_COLOR = "ui.element.ghost.textColor";
/**
* Must not be null.
*/
public static final String GHOST_FILL_COLOR = "ui.element.ghost.fillColor";
public static final String LIGHT_BORDER_COLOR = "ui.light.borderColor";
public static final String LIGHT_FILL_COLOR = "ui.light.fillColor";
public static final String SWITCH_BORDER_COLOR = "ui.switch.borderColor";
public static final String SWITCH_FILL_COLOR = "ui.switch.fillColor";
/**
* Must not be null.
*/
public static final String SELECTION_FILL_COLOR = "ui.selection.fillColor";
/**
* Must not be null.
*/
public static final String SELECTION_BORDER_COLOR = "ui.selection.borderColor";
/**
* Must not be null.
*/
public static final String SHOW_TOOLTIPS = "ui.drawToolTips";
/**
* Must not be null.
*/
public static final String REALTIME_VALIDATION = "RealTimeValidation";
/**
* Must not be null.
*/
public static final String OPENED_PROJECTS = "openedProjects";
/**
* Must not be null.
*/
public static final String LOOK_AND_FEEL = "swingLookAndFeel";
/**
* Must not be null.
*/
public static final String SIMULATION_UNDO_ENABLED = "simulation.undoEnabled";
/**
* Must not be null.
*/
public static final String SCROLL_SPEED = "ui.scrollSpeed";
/**
* Must not be null.
*/
public static final String Language = "language";
/**
* Must not be null.
*/
public static final String MMMode = "mmmode";
/**
* Render in low quality.
*/
public static final String LOW_RENDERING_QUALITY_MODE = "rendering.lowQuality";
/**
* Default delay for the simulation timer.
*/
public static final String DEFAULT_TIMER_DELAY = "timer.defaultDelay";
/**
* Scatter factor for clicking onto {@link AccessPoint}s.
*/
public static final String ACCESS_POINT_SCATTER_FACTOR = "tool.accessPointScatterFactor";
/**
* Scatter factor for clicking onto {@link Wire}s.
*/
public static final String WIRE_SCATTER_FACTOR = "tool.wireScatterFactor";
public static final String ELEMENT_TYPE_COLOR = "ui.element.type.color";
public static final String ELEMENT_INPUT_LABEL_COLOR = "ui.element.input.label.color";
public static final String ELEMENT_OUTPUT_LABEL_COLOR = "ui.element.output.label.color";
public static final String EXPERT_MODE = "funckit.expertMode";
public static final String SIMULATION_SLIDER_FACTOR = "toolbar.simulationSliderFactor";
public static final String WINDOW_BOUNDS = "window_bounds";
public static final String QUEUE_BORDER_COLOR = "ui.element.simulated.queue.borderColor";
public static final String QUEUE_FILL_COLOR = "ui.element.simulated.queue.fillColor";
public static final String QUEUE_TEXT_FONT = "ui.element.simulated.queue.font";
public static final String QUEUE_TEXT_COLOR = "ui.element.simulated.queue.textColor";
public static final String QUEUE_MARGIN = "ui.element.simulated.queue.margin";
/**
* Must not be null.
*/
public static final String INPUT_RADIUS = "ui.element.input.radius";
/**
* Must not be null.
*/
public static final String OUTPUT_RADIUS = "ui.element.output.radius";
/**
* May be null.
*/
public static final String OUTPUT_FONT = "ui.element.output.font";
/**
* May be null.
*/
public static final String ELEMENT_DELAY_FONT = "ui.element.delay.font";
/**
* May be null.
*/
public static final String ELEMENT_DELAY_COLOR = "ui.element.delay.color";
/**
* May be null.
*/
public static final String ELEMENT_NAME_FONT = "ui.element.name.font";
/**
* May be null.
*/
public static final String ELEMENT_NAME_COLOR = "ui.element.name.color";
/**
* Path to associated persistence file.
*/
private String settingsFile;
/**
* Object, settings are stored in (to decouple persistence file and direct
* access).
*/
private final Properties properties;
/**
* Flag to determine if changed settings should be saved automatically.
*/
private boolean autosave;
/**
* Create a new {@link Settings} object.
*/
public Settings() {
properties = new Properties();
initInfo(SettingsInfo.getInfo());
setAutosave(false);
}
/**
* Create a new {@link Settings} object using a File.
*
* @param settingsFile the path to the settings File.
*/
public Settings(String settingsFile) {
this.settingsFile = settingsFile;
properties = new Properties();
initInfo(SettingsInfo.getInfo());
reload();
}
/**
* Remove a setting.
*
* @param key setting to remove
*/
public void remove(String key) {
properties.remove(key);
this.getInfo().setChangedSetting(key);
this.setChanged();
this.notifyObserversIfAuto();
}
/**
* (Re-)loads settings from file stream.
*
* @return true if successful, else false
*/
public boolean reload() {
if (settingsFile == null) {
Log.gl().warn("Can not load settings of uninitialized settings object.");
return false;
}
Log.gl().info("Loading settings ..");
try {
InputStream is = new FileInputStream(settingsFile);
properties.loadFromXML(is);
Log.gl().info("Settings loaded!");
return true;
} catch (FileNotFoundException e) {
Log.gl().info("Settings file '" + settingsFile + "' does not exist, yet.");
} catch (InvalidPropertiesFormatException e) {
Log.gl().error("Settings has '" + settingsFile + "' invalid format.");
} catch (IOException e) {
Log.gl().error("Could not save settings.");
}
return false;
}
/**
* Returns flag if automatic saving is enabled.
*
* @return true if automatic saving is enabled.
*/
public boolean isAutosave() {
return autosave;
}
/**
* Specifies if automatic saving should be enabled or disabled.
*
* @param autosave True iff automatic save should be enabled.
*/
public void setAutosave(boolean autosave) {
this.autosave = autosave;
if (autosave && hasChanged()) {
save();
}
}
/**
* Wrapper for save-method to save only if autosave is enabled.
*/
private void autosave() {
if (autosave) {
save();
}
}
/**
* Initializes saving settings from property object to settings file.
*
* @return true if save was successful
*/
public boolean save() {
if (settingsFile == null) {
Log.gl().warn("Can not save settings for uninitialized settings object.");
return false;
}
Log.gl().info("Saving settings ..");
try {
OutputStream outputStream = new FileOutputStream(settingsFile);
properties.storeToXML(outputStream, "Last updated " + new java.util.Date(), "UTF-8");
outputStream.close();
Log.gl().info("Settings saved!");
return true;
} catch (FileNotFoundException e) {
Log.gl().error("Settings file '" + settingsFile + "' not found for saving.");
} catch (IOException e) {
Log.gl().error("Could not save settings.");
}
return false;
}
/**
* Set the Setting of key to the specified value.
*
* @param key a unique string identifying the setting.
* @param value a {@link Serializable} object
*/
public <T> void set(String key, T value) {
assert value != null;
assert value instanceof Serializable;
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
try {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);
objectOutputStream.writeObject(value);
outputStream.close();
properties.setProperty(key, Base64.encodeBytes(outputStream.toByteArray()));
autosave();
} catch (IOException e) {
Log.gl().warn("Catched IOException in setSetting(" + key + ", " + value + ")");
}
this.getInfo().setChangedSetting(key);
this.setChanged();
this.notifyObserversIfAuto();
}
/**
* Set the Setting of key to the specified value.
*
* @param key a unique string identifying the setting.
* @param value a String
*/
public void set(String key, String value) {
assert value != null;
properties.setProperty(key, value);
autosave();
this.getInfo().setChangedSetting(key);
this.setChanged();
this.notifyObserversIfAuto();
}
/**
* Set the Setting of key to the specified value.
*
* @param key a unique string identifying the setting.
* @param value a boolean
*/
public void set(String key, boolean value) {
properties.setProperty(key, String.valueOf(value));
autosave();
this.getInfo().setChangedSetting(key);
this.setChanged();
this.notifyObserversIfAuto();
}
/**
* Set the Setting of key to the specified value.
*
* @param key a unique string identifying the setting.
* @param value an int
*/
public void set(String key, int value) {
properties.setProperty(key, String.valueOf(value));
autosave();
this.getInfo().setChangedSetting(key);
this.setChanged();
this.notifyObserversIfAuto();
}
/**
* Set the Setting of key to the specified value.
*
* @param key a unique string identifying the setting.
* @param value a double
*/
public void set(String key, double value) {
properties.setProperty(key, String.valueOf(value));
autosave();
this.getInfo().setChangedSetting(key);
this.setChanged();
this.notifyObserversIfAuto();
}
/**
* Set the Setting of key to the specified value.
*
* @param key a unique string identifying the setting.
* @param value a long
*/
public void set(String key, long value) {
properties.setProperty(key, String.valueOf(value));
autosave();
this.getInfo().setChangedSetting(key);
this.setChanged();
this.notifyObserversIfAuto();
}
/**
* Set the Setting of key to the specified value.
*
* @param key a unique string identifying the setting
* @param value a float
*/
public void set(String key, float value) {
properties.setProperty(key, String.valueOf(value));
autosave();
this.getInfo().setChangedSetting(key);
this.setChanged();
this.notifyObserversIfAuto();
}
/**
* Returns the setting corresponding to the given key.
*
* @param key a unique string identifying the setting
* @param type because of generics you need to specify a class object of the
* type you want get
* @return the value of the setting corresponding to the given key, null if
* no setting exists
*/
public <T> T get(String key, Class<T> type) {
String property = properties.getProperty(key);
if (property == null) {
// Log.gl().warn("Unknown setting " + key);
return null;
}
byte[] value = null;
try {
value = Base64.decode(property);
} catch (IOException e) {
Log.gl().warn("Could not decode " + key);
e.printStackTrace();
}
// TODO julian cache serialization
if (value != null) {
InputStream inputStream = new ByteArrayInputStream(value);
try {
ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
Object object = objectInputStream.readObject();
objectInputStream.close();
return type.cast(object);
} catch (ClassNotFoundException e) {
Log.gl().warn("Tried to load unknown serialized object");
e.printStackTrace();
} catch (IOException e) {
Log.gl().warn("Catched IOException in get(" + key + ", " + type + ")");
e.printStackTrace();
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return null;
}
/**
* Returns the corresponding int value to the given key.
*
* @param key a unique string identifying the setting
* @return the corresponding int value to the given key
*/
public int getInt(String key) {
String value = properties.getProperty(key);
if (value == null) {
return 0;
}
try {
return Integer.valueOf(value);
} catch (NumberFormatException e) {
return 0;
}
}
/**
* Returns the corresponding long value to the given key.
*
* @param key a unique string identifying the setting
* @return the corresponding long value to the given key
*/
public long getLong(String key) {
String value = properties.getProperty(key);
if (value == null) {
return 0L;
}
try {
return Long.valueOf(value);
} catch (NumberFormatException e) {
return 0L;
}
}
/**
* Returns the corresponding String value to the given key.
*
* @param key a unique string identifying the setting
* @return the corresponding String value to the given key
*/
public String getString(String key) {
return properties.getProperty(key);
}
/**
* Returns the corresponding float value to the given key.
*
* @param key a unique string identifying the setting
* @return the corresponding float value to the given key
*/
public float getFloat(String key) {
String value = properties.getProperty(key);
if (value == null) {
return 0f;
}
try {
return Float.valueOf(value);
} catch (NumberFormatException e) {
return 0f;
}
}
/**
* Returns the corresponding double value to the given key.
*
* @param key a unique string identifying the setting
* @return the corresponding double value to the given key
*/
public double getDouble(String key) {
String value = properties.getProperty(key);
if (value == null) {
return 0d;
}
try {
return Double.valueOf(value);
} catch (NumberFormatException e) {
return 0d;
}
}
/**
* Returns the corresponding boolean value to the given key.
*
* @param key a unique string identifying the setting
* @return the corresponding boolean value to the given key
*/
public boolean getBoolean(String key) {
String value = properties.getProperty(key);
return value != null ? Boolean.valueOf(value) : false;
}
@Override
public void notifyObserver(SettingsInfo i, SettingsObserver obs) {
obs.settingsChanged(this, i);
}
/**
* Applys the content of the other settings object.
*
* @param other the other settings object to apply
* @param overwrite if true overwrite existing settings
*/
public void apply(Settings other, boolean overwrite) {
for (String key : other.properties.stringPropertyNames()) {
if (overwrite || getString(key) == null) {
properties.setProperty(key, other.properties.getProperty(key));
this.setChanged();
getInfo().setChangedSetting(key);
this.notifyObservers();
}
}
}
}