/*
* Freeplane - mind map editor
* Copyright (C) 2008 Dimitry Polivaev
*
* This file author is Dimitry Polivaev
*
* 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, see <http://www.gnu.org/licenses/>.
*/
package org.freeplane.core.resources.components;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.net.URI;
import java.net.URL;
import java.text.Collator;
import java.util.Arrays;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.Vector;
import javax.swing.tree.DefaultMutableTreeNode;
import org.freeplane.core.io.IElementDOMHandler;
import org.freeplane.core.io.ReadManager;
import org.freeplane.core.io.xml.TreeXmlReader;
import org.freeplane.core.resources.ResourceBundles;
import org.freeplane.core.resources.ResourceController;
import org.freeplane.core.ui.IndexedTree;
import org.freeplane.core.util.FileUtils;
import org.freeplane.core.util.LogUtils;
import org.freeplane.core.util.TextUtils;
import org.freeplane.n3.nanoxml.XMLElement;
import org.freeplane.n3.nanoxml.XMLException;
/**
* @author Dimitry Polivaev 26.12.2008
* <p>
* Note that the OptionPanelBuilder allows to set a custom validator for
* options, see {@link #addValidator(IValidator)}.
*/
public class OptionPanelBuilder {
private class BooleanOptionCreator extends PropertyCreator {
@Override
public IPropertyControlCreator getCreator(final String name,
final XMLElement data) {
return createBooleanOptionCreator(name);
}
}
private class ColorOptionCreator extends PropertyCreator {
@Override
public IPropertyControlCreator getCreator(final String name,
final XMLElement data) {
return createColorOptionCreator(name);
}
}
private class ComboOptionCreator extends PropertyCreator {
@Override
public IPropertyControlCreator getCreator(final String name,
final XMLElement data) {
final int childrenCount = data.getChildrenCount();
final Vector<String> choices = new Vector<String>(childrenCount);
final Vector<String> translations = new Vector<String>(
childrenCount);
for (int i = 0; i < childrenCount; i++) {
final XMLElement element = data.getChildAtIndex(i);
final String choice = element.getAttribute("value", null);
choices.add(choice);
final String translationKey = element.getAttribute("text",
"OptionPanel." + choice);
final String translation = TextUtils
.getOptionalText(translationKey);
translations.add(translation);
}
return createComboProperty(name, choices, translations);
}
}
private class LanguagesComboCreator extends PropertyCreator {
@Override
public IPropertyControlCreator getCreator(final String name,
final XMLElement data) {
final Set<String> locales = findAvailableLocales();
locales.add(ResourceBundles.LANGUAGE_AUTOMATIC);
final Vector<String> choices = new Vector<String>(locales.size());
final Vector<String> translations = new Vector<String>(
locales.size());
// sort according to current locale
final TreeMap<String, String> inverseMap = new TreeMap<String, String>(
Collator.getInstance());
for (String locale : locales) {
final String translation = TextUtils
.getOptionalText("OptionPanel." + locale);
choices.add(locale);
translations.add(translation);
if (inverseMap.containsKey(translation)) {
LogUtils.severe("translation " + translation
+ " is used for more that one locale, for "
+ inverseMap.get(translation) + " and for "
+ locale + ".");
}
inverseMap.put(translation, locale);
}
if (inverseMap.size() == choices.size()) {
// fix #630: Language not sorted alphabetically
choices.clear();
translations.clear();
for (Entry<String, String> entry : inverseMap.entrySet()) {
choices.add(entry.getValue());
translations.add(entry.getKey());
}
}
return createComboProperty(name, choices, translations);
}
private Set<String> findAvailableLocales() {
final TreeSet<String> locales = new TreeSet<String>();
final String name = "/translations/locales.txt";
final InputStream stream = ResourceController.class
.getResourceAsStream(name);
if (stream == null) {
LogUtils.info("available locales not found");
// as this happens when Freeplane is started from Eclipse add
// some locales for developer's sake
locales.addAll(Arrays
.asList(("ar,ca,cs,da,de,el,es,et,fr,gl,hr,hu,id,it,ja,ko,lt,nb,nl,nn,pl,pt_BR,"
+ "pt_PT,ru,sk,sl,sr,sv,tr,uk_UA,zh_CN,zh_TW,en")
.split(",")));
return locales;
}
ByteArrayOutputStream out = new ByteArrayOutputStream();
try {
FileUtils.copyStream(stream, out);
locales.addAll(Arrays.asList(out.toString().split("\\s+")));
} catch (IOException e) {
// OK - return locales
} finally {
FileUtils.silentlyClose(stream);
}
return locales;
}
}
private class EmptyCreator extends PropertyCreator {
@Override
public IPropertyControlCreator getCreator(final String name,
final XMLElement data) {
return null;
}
}
private class FontOptionCreator extends PropertyCreator {
@Override
public IPropertyControlCreator getCreator(final String name,
final XMLElement data) {
return createFontOptionCreator(name);
}
}
private class KeyOptionCreator extends PropertyCreator {
@Override
public IPropertyControlCreator getCreator(final String name,
final XMLElement data) {
return createKeyOptionCreator(name);
}
}
private class PasswordOptionCreator extends PropertyCreator {
@Override
public IPropertyControlCreator getCreator(final String name, final XMLElement data) {
//TODO: define XML structure
// final int min = Integer.parseInt(data.getAttribute("min", "1"));
// final int step = Integer.parseInt(data.getAttribute("step", "1"));
// final int max = Integer.parseInt(data.getAttribute("max", MAX_INT));
return createPasswordOptionCreator(name);
}
}
private class UriLinkCreator extends PropertyCreator {
@Override
public IPropertyControlCreator getCreator(final String name, final XMLElement data) {
URI uri = URI.create(data.getAttribute("uri", null));
String label = data.getAttribute("label", null);
return createUriLinkCreator(name, label, uri);
}
}
private class RadioButtonCreator extends PropertyCreator {
@Override
public IPropertyControlCreator getCreator(final String name, final XMLElement data) {
String enabled = data.getAttribute("enabled", name);
return createRadioButtonOptionCreator(name, enabled);
}
}
private class ActionCreator extends PropertyCreator{
@Override
public IPropertyControlCreator getCreator(final String name, final XMLElement data) {
String actionCommand = data.getAttribute("actionCommand", name);
return createActionOptionCreator(name, actionCommand);
}
}
private class NumberOptionCreator extends PropertyCreator {
private IPropertyControlCreator createNumberPropertyCreator(
final String name, final int min, final int step, final int max) {
return new IPropertyControlCreator() {
public IPropertyControl createControl() {
return new NumberProperty(name, min, max, step);
}
};
}
private IPropertyControlCreator createNumberPropertyCreator(
final String name, final double min, final double step,
final double max) {
return new IPropertyControlCreator() {
public IPropertyControl createControl() {
return new NumberProperty(name, min, max, step);
}
};
}
@Override
public IPropertyControlCreator getCreator(final String name,
final XMLElement data) {
final String minString = data.getAttribute("min", "1");
final String maxString = data.getAttribute("max", MAX_INT);
final String stepString = data.getAttribute("step", "1");
if (minString.contains(".") || maxString.contains(".")
|| stepString.contains(".")) {
return createNumberPropertyCreator(name,
Double.parseDouble(minString),
Double.parseDouble(stepString),
Double.parseDouble(maxString));
} else {
return createNumberPropertyCreator(name,
Integer.parseInt(minString),
Integer.parseInt(stepString),
Integer.parseInt(maxString));
}
}
}
private class PathOptionCreator extends PropertyCreator {
private IPropertyControlCreator createPathPropertyCreator(final String name, final boolean isDir,
final boolean showHidden, final String[] suffixes) {
return new IPropertyControlCreator() {
public IPropertyControl createControl() {
return new PathProperty(name, isDir, showHidden, suffixes);
}
};
}
@Override
public IPropertyControlCreator getCreator(final String name, final XMLElement data) {
final boolean isDir = Boolean.parseBoolean(data.getAttribute("dir", "false"));
final boolean showHidden = Boolean.parseBoolean(data.getAttribute("showhidden", "false"));
final String[] suffixes = parseCSV(data.getAttribute("suffixes", ""));
return createPathPropertyCreator(name, isDir, showHidden, suffixes);
}
// Parses CSV, strips whitespace, returns null if empty
private String[] parseCSV(String csv) {
if (csv == null)
return null;
final String[] result = csv.trim().split("\\s*,\\s*");
return result.length > 0 ? result : null;
}
}
private static class Path {
static Path emptyPath() {
final Path Path = new Path(null);
Path.path = null;
return Path;
}
String parentPath;
String path;
Path(final String path) {
parentPath = path;
}
void setName(final String name) {
path = parentPath == null ? name : parentPath + '/' + name;
}
@Override
public String toString() {
return path;
}
}
protected abstract class PropertyCreator implements IElementDOMHandler {
public Object createElement(final Object parent, final String tag,
final XMLElement attributes) {
if (attributes == null) {
return null;
}
final String name = attributes.getAttribute("name", null);
if (name == null) {
return parent == null ? Path.emptyPath() : parent;
}
final Path path = new Path(parent == null ? null
: parent.toString());
path.setName(name);
if (!tree.contains(path.path)) {
tree.addElement(path.parentPath == null ? tree
: path.parentPath, this, path.path,
IndexedTree.AS_CHILD);
}
return path;
}
public void endElement(final Object parent, final String tag,
final Object userObject, final XMLElement lastBuiltElement) {
final String name = lastBuiltElement.getAttribute("name", null);
final Path path = (Path) userObject;
if (path.path == null) {
return;
}
final DefaultMutableTreeNode treeNode = tree.get(path.path);
if (treeNode.getUserObject() == this) {
final IPropertyControlCreator creator = getCreator(name,
lastBuiltElement);
final String text = lastBuiltElement.getAttribute("text", null);
if (text == null) {
treeNode.setUserObject(creator);
} else {
treeNode.setUserObject(new IPropertyControlCreator() {
public IPropertyControl createControl() {
final IPropertyControl control = creator
.createControl();
if (control instanceof PropertyAdapter) {
final PropertyAdapter control2 = (PropertyAdapter) control;
control2.setLabel(text);
}
return control;
}
});
}
}
}
abstract public IPropertyControlCreator getCreator(String name,
XMLElement data);
}
private class RemindValueCreator extends PropertyCreator {
@Override
public IPropertyControlCreator getCreator(final String name,
final XMLElement data) {
return createRemindValueProperty(name);
}
}
private class SeparatorCreator extends PropertyCreator {
@Override
public void endElement(final Object parent, final String tag,
final Object userObject, final XMLElement lastBuiltElement) {
final Path path = (Path) userObject;
final DefaultMutableTreeNode treeNode = tree.get(path.path);
if (treeNode.getUserObject() != this) {
return;
}
super.endElement(parent, tag, userObject, lastBuiltElement);
tree.addElement(path.parentPath == null ? tree : path.parentPath,
nextLineCreator, IndexedTree.AS_CHILD);
return;
}
@Override
public IPropertyControlCreator getCreator(final String name,
final XMLElement data) {
final String label = "OptionPanel.separator." + name;
return createSeparatorCreator(label);
}
}
private class StringOptionCreator extends PropertyCreator {
@Override
public IPropertyControlCreator getCreator(final String name,
final XMLElement data) {
return createStringOptionCreator(name);
}
}
private class TabCreator extends PropertyCreator {
@Override
public IPropertyControlCreator getCreator(final String name,
final XMLElement data) {
final String label = "OptionPanel." + name;
final String layout = data.getAttribute("layout", null);
return createTabCreator(label, layout);
}
}
private class TextCreator extends PropertyCreator {
@Override
public IPropertyControlCreator getCreator(final String name,
final XMLElement data) {
final String label = "OptionPanel.text." + name;
return createTextCreator(label);
}
}
private static final String MAX_INT = Integer.toString(Integer.MAX_VALUE);
final private IPropertyControlCreator nextLineCreator;
final private ReadManager readManager;
final private IndexedTree tree;
public OptionPanelBuilder() {
readManager = new ReadManager();
tree = new IndexedTree(null);
nextLineCreator = new IPropertyControlCreator() {
public IPropertyControl createControl() {
return new NextLineProperty();
}
};
initReadManager();
}
public void addBooleanProperty(final String path, final String name,
final int position) {
tree.addElement(path, createBooleanOptionCreator(name), path + "/"
+ name, position);
}
public void addColorProperty(final String path, final String name,
final int position) {
tree.addElement(path, createColorOptionCreator(name),
path + "/" + name, position);
}
public void addComboProperty(final String path, final String name,
final Vector<String> choices, final Vector<String> translations,
final int position) {
tree.addElement(path, createComboProperty(name, choices, translations),
path + "/" + name, position);
}
public void addCreator(final String path,
final IPropertyControlCreator creator, final int position) {
tree.addElement(path, creator, position);
}
public void addCreator(final String path,
final IPropertyControlCreator creator, final String name,
final int position) {
tree.addElement(path, creator, path + "/" + name, position);
}
public void addFontProperty(final String path, final String name, final int position) {
tree.addElement(path, createFontOptionCreator(name), path + "/" + name, position);
}
public void addKeyProperty(final String path, final String name, final int position) {
tree.addElement(path, createKeyOptionCreator(name), path + "/" + name, position);
}
public void addActionProperty(final String path, final String name, final int position) {
tree.addElement(path, createActionOptionCreator(name), path + "/" + name, position);
}
public void addActionProperty(final String path, final String name, String actionCommand, final int position) {
tree.addElement(path, createActionOptionCreator(name, actionCommand), path + "/" + name, position);
}
public void addPasswordProperty(final String path, final String name, final int position) {
tree.addElement(path, createPasswordOptionCreator(name), path + "/" + name, position);
}
public void addNumberProperty(final String path, final String name, final int min, final int max, final int step,
final int position) {
tree.addElement(path, createNumberOptionCreator(name, min, max, step), path + "/" + name, position);
}
public void addRemindValueProperty(final String path, final String name, final int position) {
tree.addElement(path, createRemindValueProperty(name), path + "/" + name, position);
}
public void addSeparator(final String path, final String name, final int position) {
tree.addElement(path, createSeparatorCreator(name), path + "/" + name, position);
}
public void addSpace(final String path, final int position) {
tree.addElement(path, nextLineCreator, position);
}
public void addStringProperty(final String path, final String name,
final int position) {
tree.addElement(path, createStringOptionCreator(name), path + "/"
+ name, position);
}
public void addTab(final String name) {
addTab(name, null, IndexedTree.AS_CHILD);
}
public void addTab(final String name, final String layout,
final int position) {
tree.addElement(tree, createTabCreator(name, layout), name, position);
}
public void addText(final String path, final String name, final int position) {
tree.addElement(path, createTextCreator(name), path + "/" + name,
position);
}
private IPropertyControlCreator createBooleanOptionCreator(final String name) {
return new IPropertyControlCreator() {
public IPropertyControl createControl() {
return new BooleanProperty(name);
}
};
}
private IPropertyControlCreator createColorOptionCreator(final String name) {
return new IPropertyControlCreator() {
public IPropertyControl createControl() {
return new ColorProperty(name, ResourceController
.getResourceController().getDefaultProperty(name));
}
};
}
private IPropertyControlCreator createComboProperty(final String name,
final Vector<String> choices, final Vector<String> translations) {
return new IPropertyControlCreator() {
public IPropertyControl createControl() {
return new ComboProperty(name, choices, translations);
}
};
}
private IPropertyControlCreator createFontOptionCreator(final String name) {
return new IPropertyControlCreator() {
public IPropertyControl createControl() {
return new FontProperty(name);
}
};
}
private IPropertyControlCreator createKeyOptionCreator(final String name) {
return new IPropertyControlCreator() {
public IPropertyControl createControl() {
return new KeyProperty(name);
}
};
}
private IPropertyControlCreator createActionOptionCreator(final String name) {
return createActionOptionCreator(name, name);
}
private IPropertyControlCreator createActionOptionCreator(final String name, final String actionCommand) {
return new IPropertyControlCreator() {
public IPropertyControl createControl() {
return new ActionProperty(name, actionCommand);
}
};
}
private IPropertyControlCreator createUriLinkCreator(final String name, final String label, final URI uri) {
return new IPropertyControlCreator() {
public IPropertyControl createControl() {
return new UriLink(name, label, uri);
}
};
}
private IPropertyControlCreator createPasswordOptionCreator(final String name) {
return new IPropertyControlCreator() {
public IPropertyControl createControl() {
return new PasswordProperty(name);
}
};
}
private IPropertyControlCreator createRadioButtonOptionCreator(final String name,final String enabled) {
return new IPropertyControlCreator() {
public IPropertyControl createControl() {
return new RadioButtonProperty(name, enabled);
}
};
}
private Object createNumberOptionCreator(final String name, final int min, final int max, final int step) {
return new IPropertyControlCreator() {
public IPropertyControl createControl() {
return new NumberProperty(name, min, max, step);
}
};
}
private IPropertyControlCreator createRemindValueProperty(final String name) {
return new IPropertyControlCreator() {
public IPropertyControl createControl() {
return new RemindValueProperty(name);
}
};
}
private IPropertyControlCreator createSeparatorCreator(final String label) {
return new IPropertyControlCreator() {
public IPropertyControl createControl() {
return new SeparatorProperty(label);
}
};
}
private IPropertyControlCreator createStringOptionCreator(final String name) {
return new IPropertyControlCreator() {
public IPropertyControl createControl() {
return new StringProperty(name);
}
};
}
private IPropertyControlCreator createTabCreator(final String label,
final String layout) {
return new IPropertyControlCreator() {
public IPropertyControl createControl() {
if (layout != null) {
return new TabProperty(label, layout);
}
return new TabProperty(label);
}
};
}
private IPropertyControlCreator createTextCreator(final String label) {
return new IPropertyControlCreator() {
public IPropertyControl createControl() {
return new Text(label);
}
};
}
ReadManager getReadManager() {
return readManager;
}
public DefaultMutableTreeNode getRoot() {
return getTree().getRoot();
}
IndexedTree getTree() {
return tree;
}
private void initReadManager() {
readManager.addElementHandler("preferences_structure",
new EmptyCreator());
readManager.addElementHandler("tabbed_pane", new EmptyCreator());
readManager.addElementHandler("group", new EmptyCreator());
readManager.addElementHandler("tab", new TabCreator());
readManager.addElementHandler("separator", new SeparatorCreator());
readManager.addElementHandler("text", new TextCreator());
readManager.addElementHandler("string", new StringOptionCreator());
readManager.addElementHandler("font", new FontOptionCreator());
readManager.addElementHandler("boolean", new BooleanOptionCreator());
readManager.addElementHandler("number", new NumberOptionCreator());
readManager.addElementHandler("path", new PathOptionCreator());
readManager.addElementHandler("color", new ColorOptionCreator());
readManager.addElementHandler("combo", new ComboOptionCreator());
readManager.addElementHandler("languages", new LanguagesComboCreator());
readManager.addElementHandler("key", new KeyOptionCreator());
readManager.addElementHandler("remind_value", new RemindValueCreator());
readManager.addElementHandler("action", new ActionCreator());
readManager.addElementHandler("password", new PasswordOptionCreator());
readManager.addElementHandler("radiobutton", new RadioButtonCreator());
readManager.addElementHandler("uri", new UriLinkCreator());
}
public void load(final URL menu) {
InputStreamReader reader = null;
try {
reader = new InputStreamReader(new BufferedInputStream(
menu.openStream()));
load(reader);
} catch (final IOException e) {
throw new RuntimeException(e);
} finally {
FileUtils.silentlyClose(reader);
}
}
public void load(final Reader inputStreamReader) {
final TreeXmlReader treeXmlReader = new TreeXmlReader(readManager);
try {
treeXmlReader.load(inputStreamReader);
} catch (final XMLException e) {
throw new RuntimeException(e);
}
}
}