/* * Freeplane - mind map editor * Copyright (C) 2009 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.features.styles; import java.awt.Color; import java.io.File; import java.io.IOException; import java.io.StringWriter; import java.net.URL; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.Vector; import org.freeplane.core.extension.IExtension; import org.freeplane.core.io.IElementContentHandler; import org.freeplane.core.io.IElementDOMHandler; import org.freeplane.core.io.IElementHandler; import org.freeplane.core.io.IExtensionElementWriter; import org.freeplane.core.io.ITreeWriter; import org.freeplane.core.resources.NamedObject; import org.freeplane.core.resources.ResourceController; import org.freeplane.core.ui.components.UITools; import org.freeplane.core.undo.IActor; import org.freeplane.core.undo.IUndoHandler; import org.freeplane.core.util.ColorUtils; import org.freeplane.core.util.Compat; import org.freeplane.core.util.LogUtils; import org.freeplane.core.util.TextUtils; import org.freeplane.features.filter.FilterController; import org.freeplane.features.filter.condition.ASelectableCondition; import org.freeplane.features.filter.condition.ConditionFactory; import org.freeplane.features.map.IMapLifeCycleListener; import org.freeplane.features.map.MapChangeEvent; import org.freeplane.features.map.MapController; import org.freeplane.features.map.MapModel; import org.freeplane.features.map.MapWriter; import org.freeplane.features.map.NodeModel; import org.freeplane.features.map.MapWriter.Mode; import org.freeplane.features.mode.Controller; import org.freeplane.features.mode.ModeController; import org.freeplane.features.mode.NodeHookDescriptor; import org.freeplane.features.mode.PersistentNodeHook; import org.freeplane.features.styles.ConditionalStyleModel.Item; import org.freeplane.features.url.UrlManager; import org.freeplane.n3.nanoxml.XMLElement; /** * @author Dimitry Polivaev * Mar 9, 2009 */ @NodeHookDescriptor(hookName = "MapStyle") public class MapStyle extends PersistentNodeHook implements IExtension, IMapLifeCycleListener { private static final String NODE_CONDITIONAL_STYLES = "NodeConditionalStyles"; public static final String RESOURCES_BACKGROUND_COLOR = "standardbackgroundcolor"; public static final String MAP_STYLES = "MAP_STYLES"; public static void install(boolean persistent){ new MapStyle(persistent); } protected MapStyle( final boolean persistent) { super(); ModeController modeController = Controller.getCurrentModeController(); if (persistent) { final MapController mapController = modeController.getMapController(); mapController.getWriteManager().addExtensionElementWriter(getExtensionClass(), new XmlWriter()); mapController.getReadManager().addElementHandler("conditional_styles", new IElementDOMHandler() { public Object createElement(Object parent, String tag, XMLElement attributes) { return parent; } public void endElement(Object parent, String tag, Object element, XMLElement dom) { final NodeModel node = (NodeModel) parent; final MapStyleModel mapStyleModel = MapStyleModel.getExtension(node); if(mapStyleModel != null) loadConditionalStyles(mapStyleModel.getConditionalStyleModel(), dom); } }); mapController.getWriteManager().addExtensionElementWriter(ConditionalStyleModel.class, new IExtensionElementWriter() { public void writeContent(ITreeWriter writer, Object element, IExtension extension) throws IOException { final ConditionalStyleModel conditionalStyleModel = (ConditionalStyleModel) extension; if (conditionalStyleModel.getStyleCount() == 0) return; final XMLElement hook = new XMLElement("hook"); hook.setAttribute("NAME", NODE_CONDITIONAL_STYLES); saveConditionalStyles(conditionalStyleModel, hook, false); writer.addElement(null, hook); } }); mapController.getReadManager().addElementHandler("hook", new IElementDOMHandler() { public Object createElement(Object parent, String tag, XMLElement attributes) { if (attributes == null || !NODE_CONDITIONAL_STYLES.equals(attributes.getAttribute("NAME", null))) { return null; } final ConditionalStyleModel conditionalStyleModel = new ConditionalStyleModel(); ((NodeModel)parent).addExtension(conditionalStyleModel); return conditionalStyleModel; } public void endElement(Object parent, String tag, Object element, XMLElement dom) { loadConditionalStyles((ConditionalStyleModel) element, dom); } }); mapController.getReadManager().addElementHandler("map_styles", new IElementContentHandler() { public Object createElement(Object parent, String tag, XMLElement attributes) { return parent; } public void endElement(final Object parent, final String tag, final Object userObject, final XMLElement attributes, final String content) { // bugfix if(isContentEmpty(content)) return; final NodeModel node = (NodeModel) userObject; final MapStyleModel mapStyleModel = MapStyleModel.getExtension(node); if (mapStyleModel == null) { return; } final MapModel map = node.getMap(); mapStyleModel.createStyleMap(map, mapStyleModel, content); map.getIconRegistry().addIcons(mapStyleModel.getStyleMap()); } private boolean isContentEmpty(final String content) { return content.indexOf('<') == -1; } } ); } modeController.getMapController().addMapLifeCycleListener(this); final MapController mapController = modeController.getMapController(); mapController.addMapLifeCycleListener(this); } protected class XmlWriter implements IExtensionElementWriter { public void writeContent(final ITreeWriter writer, final Object object, final IExtension extension) throws IOException { final MapStyleModel mapStyleModel = (MapStyleModel) extension; final MapModel styleMap = mapStyleModel.getStyleMap(); final String el = System.getProperty("line.separator"); if (styleMap == null) { return; } final MapWriter mapWriter = Controller.getCurrentModeController().getMapController().getMapWriter(); final StringWriter sw = new StringWriter(); sw.append(el); sw.append("<map_styles>"); sw.append(el); final NodeModel rootNode = styleMap.getRootNode(); final boolean forceFormatting = Boolean.TRUE.equals(writer.getHint(MapWriter.WriterHint.FORCE_FORMATTING)); try { mapWriter.writeNodeAsXml(sw, rootNode, Mode.STYLE, true, true, forceFormatting); } catch (final IOException e) { e.printStackTrace(); } sw.append("</map_styles>"); sw.append(el); final XMLElement element = new XMLElement("hook"); saveExtension(extension, element); writer.addElement(sw.toString(), element); } } @Override protected XmlWriter createXmlWriter() { return null; } protected class MyXmlReader extends XmlReader{ public Object createElement(final Object parent, final String tag, final XMLElement attributes) { if (null == super.createElement(parent, tag, attributes)){ return null; } super.endElement(parent, tag, parent, attributes); return parent; } @Override public void endElement(Object parent, String tag, Object userObject, XMLElement xml) { // do nothing for not root nodes final XMLElement parentNodeElement = xml.getParent().getParent(); if (parentNodeElement == null || !parentNodeElement.getName().equals("map")) { return; } NodeModel node = (NodeModel) userObject; loadMapStyleProperties(MapStyleModel.getExtension(node), xml); } private void loadMapStyleProperties(MapStyleModel model, XMLElement xml) { final Vector<XMLElement> propertyXml = xml.getChildrenNamed("properties"); if(propertyXml != null && propertyXml.size() >= 1){ final Map<String, String> properties = model.getProperties(); final Properties attributes = propertyXml.get(0).getAttributes(); for(Entry<Object, Object> attribute:attributes.entrySet()){ properties.put(attribute.getKey().toString(), attribute.getValue().toString()); } } } } @Override protected IElementHandler createXmlReader() { return new MyXmlReader(); } @Override protected IExtension createExtension(final NodeModel node, final XMLElement element) { final MapStyleModel model = new MapStyleModel(); final String colorString = element.getAttribute("background", null); final Color bgColor; if (colorString != null) { bgColor = ColorUtils.stringToColor(colorString); } else { bgColor = null; } model.setBackgroundColor(bgColor); final String zoomString = element.getAttribute("zoom", null); if (zoomString != null) { final float zoom = Float.valueOf(zoomString); model.setZoom(zoom); } final String layoutString = element.getAttribute("layout", null); try { if (layoutString != null) { final MapViewLayout layout = MapViewLayout.valueOf(layoutString); model.setMapViewLayout(layout); } } catch (final Exception e) { } final String maxNodeWidthString = element.getAttribute("max_node_width", null); final String minNodeWidthString = element.getAttribute("min_node_width",null); try { if (maxNodeWidthString != null) { final int maxNodeWidth = Integer.valueOf(maxNodeWidthString); model.setMaxNodeWidth(maxNodeWidth); } if (minNodeWidthString != null) { final int minNodeWidth = Integer.valueOf(minNodeWidthString); model.setMinNodeWidth(minNodeWidth); } } catch (final Exception e) { } return model; } private void loadConditionalStyles(ConditionalStyleModel conditionalStyleModel, XMLElement conditionalStylesRoot) { final ConditionFactory conditionFactory = FilterController.getCurrentFilterController().getConditionFactory(); final Vector<XMLElement> styleElements = conditionalStylesRoot.getChildrenNamed("conditional_style"); for(XMLElement styleElement : styleElements){ final boolean isActive = Boolean.valueOf(styleElement.getAttribute("ACTIVE", "false")); final boolean isLast = Boolean.valueOf(styleElement.getAttribute("LAST", "false")); String styleText = styleElement.getAttribute("LOCALIZED_STYLE_REF", null); final IStyle style; if(styleText != null){ style = StyleFactory.create(NamedObject.format((String) styleText)); } else { style = StyleFactory.create(styleElement.getAttribute("STYLE_REF", null)); } final ASelectableCondition condition; if(styleElement.getChildrenCount() == 1){ final XMLElement conditionElement = styleElement.getChildAtIndex(0); condition = conditionFactory.loadCondition(conditionElement); } else{ condition = null; } conditionalStyleModel.addCondition(isActive, condition, style, isLast); } } private void saveConditionalStyles(ConditionalStyleModel conditionalStyleModel, XMLElement parent, boolean createRoot) { final int styleCount = conditionalStyleModel.getStyleCount(); if(styleCount == 0){ return; } final XMLElement conditionalStylesRoot; if(createRoot){ conditionalStylesRoot = parent.createElement("conditional_styles"); parent.addChild(conditionalStylesRoot); } else conditionalStylesRoot = parent; for(final Item item : conditionalStyleModel){ item.toXml(conditionalStylesRoot); } } public Color getBackground(final MapModel map) { final IExtension extension = map.getRootNode().getExtension(MapStyleModel.class); final Color backgroundColor = extension != null ? ((MapStyleModel) extension).getBackgroundColor() : null; if (backgroundColor != null) { return backgroundColor; } final String stdcolor = ResourceController.getResourceController().getProperty( MapStyle.RESOURCES_BACKGROUND_COLOR); final Color standardMapBackgroundColor = ColorUtils.stringToColor(stdcolor); return standardMapBackgroundColor; } @Override protected Class<MapStyleModel> getExtensionClass() { return MapStyleModel.class; } public void onCreate(final MapModel map) { final NodeModel rootNode = map.getRootNode(); final MapStyleModel mapStyleModel = MapStyleModel.getExtension(rootNode); if (mapStyleModel != null && mapStyleModel.getStyleMap() != null) { return; } createDefaultStyleMap(map); } private void createDefaultStyleMap(final MapModel map) { UrlManager loader = UrlManager.getController(); final File file = loader.defaultTemplateFile(); if (file != null) { try { MapModel styleMapContainer = new MapModel(); loader.load(Compat.fileToUrl(file), styleMapContainer); if (null != MapStyleModel.getExtension(styleMapContainer)){ moveStyle(styleMapContainer, map, false); return; } } catch (Exception e) { LogUtils.warn(e); UITools.errorMessage(TextUtils.format("error_in_template", file)); } }; MapModel styleMapContainer = new MapModel(); try { loader.load(ResourceController.getResourceController().getResource("/styles/viewer_standard.mm"), styleMapContainer); moveStyle(styleMapContainer, map, false); } catch (Exception e) { throw new RuntimeException(e); } } private void moveStyle(final MapModel sourceMap, final MapModel targetMap, boolean overwrite) { final MapStyleModel source = (MapStyleModel) sourceMap.getRootNode().removeExtension(MapStyleModel.class); if(source == null) return; final IExtension undoHandler = targetMap.getExtension(IUndoHandler.class); source.getStyleMap().putExtension(IUndoHandler.class, undoHandler); final NodeModel targetRoot = targetMap.getRootNode(); final MapStyleModel target = MapStyleModel.getExtension(targetRoot); if(target == null){ targetRoot.addExtension(source); } else{ target.copyFrom(source, overwrite); } } @Override protected HookAction createHookAction() { return null; } public void copyStyle(final URL source, final MapModel targetMap, boolean undoable) { final MapModel styleMapContainer = new MapModel(); final IExtension oldStyleModel = targetMap.getRootNode().removeExtension(MapStyleModel.class); UrlManager.getController().loadCatchExceptions(source, styleMapContainer); onCreate(styleMapContainer); moveStyle(styleMapContainer, targetMap, true); LogicalStyleController.getController().refreshMap(targetMap); if(! undoable){ return; } final IExtension newStyleModel = targetMap.getRootNode().getExtension(MapStyleModel.class); IActor actor = new IActor() { public void undo() { targetMap.getRootNode().putExtension(oldStyleModel); } public String getDescription() { return "moveStyle"; } public void act() { targetMap.getRootNode().putExtension(newStyleModel); } }; Controller.getCurrentModeController().execute(actor, targetMap); } public void onRemove(final MapModel map) { } @Override protected void saveExtension(final IExtension extension, final XMLElement element) { final MapStyleModel mapStyleModel = (MapStyleModel) extension; super.saveExtension(extension, element); final Color backgroundColor = mapStyleModel.getBackgroundColor(); if (backgroundColor != null) { element.setAttribute("background", ColorUtils.colorToString(backgroundColor)); } final float zoom = mapStyleModel.getZoom(); if (zoom != 1f) { element.setAttribute("zoom", Float.toString(zoom)); } final MapViewLayout layout = mapStyleModel.getMapViewLayout(); if (!layout.equals(MapViewLayout.MAP)) { element.setAttribute("layout", layout.toString()); } saveConditionalStyles(mapStyleModel.getConditionalStyleModel(), element, true); saveProperties(mapStyleModel.getProperties(), element); } private void saveProperties(Map<String, String> properties, XMLElement element) { if(properties.isEmpty()){ return; } final XMLElement xmlElement = new XMLElement("properties"); for (Entry<String, String> entry: properties.entrySet()){ xmlElement.setAttribute(entry.getKey(), entry.getValue()); } element.addChild(xmlElement); } public void setZoom(final MapModel map, final float zoom) { final MapStyleModel mapStyleModel = MapStyleModel.getExtension(map); if (zoom == mapStyleModel.getZoom()) { return; } mapStyleModel.setZoom(zoom); Controller.getCurrentModeController().getMapController().setSaved(map, false); } public void setMapViewLayout(final MapModel map, final MapViewLayout layout) { final MapStyleModel mapStyleModel = MapStyleModel.getExtension(map); if (layout.equals(mapStyleModel.getMapViewLayout())) { return; } mapStyleModel.setMapViewLayout(layout); Controller.getCurrentModeController().getMapController().setSaved(map, false); } public void setBackgroundColor(final MapStyleModel model, final Color actionColor) { final Color oldColor = model.getBackgroundColor(); if (actionColor == oldColor || actionColor != null && actionColor.equals(oldColor)) { return; } final IActor actor = new IActor() { public void act() { model.setBackgroundColor(actionColor); Controller.getCurrentModeController().getMapController().fireMapChanged( new MapChangeEvent(MapStyle.this, Controller.getCurrentController().getMap(), MapStyle.RESOURCES_BACKGROUND_COLOR, oldColor, actionColor)); } public String getDescription() { return "MapStyle.setBackgroundColor"; } public void undo() { model.setBackgroundColor(oldColor); Controller.getCurrentModeController().getMapController().fireMapChanged( new MapChangeEvent(MapStyle.this, Controller.getCurrentController().getMap(), MapStyle.RESOURCES_BACKGROUND_COLOR, actionColor, oldColor)); } }; Controller.getCurrentModeController().execute(actor, Controller.getCurrentController().getMap()); } public static MapStyle getController(final ModeController modeController) { return (MapStyle) modeController.getExtension(MapStyle.class); } public static MapStyle getController() { return (MapStyle) getController(Controller.getCurrentModeController()); } public void setProperty(final MapModel model, final String key, final String newValue) { final MapStyleModel styleModel = MapStyleModel.getExtension(model); final String oldValue = styleModel.getProperty(key); if(oldValue == newValue || oldValue != null && oldValue.equals(newValue)) return; IActor actor = new IActor() { public void undo() { setPropertyWithoutUndo(model, key, oldValue); } public String getDescription() { return "set map style property"; } public void act() { setPropertyWithoutUndo(model, key, newValue); } private void setPropertyWithoutUndo(final MapModel model, final String key, final String newValue) { styleModel.setProperty(key, newValue); Controller.getCurrentModeController().getMapController().fireMapChanged( new MapChangeEvent(MapStyle.this, model, key, oldValue, newValue)); } }; Controller.getCurrentModeController().execute(actor, model); } public String getPropertySetDefault(final MapModel model, final String key) { final MapStyleModel styleModel = MapStyleModel.getExtension(model); final String oldValue = styleModel.getProperty(key); if(oldValue != null){ return oldValue; } final String value = ResourceController.getResourceController().getProperty(key); styleModel.setProperty(key, value); return value; } public void onSavedAs(MapModel map) { } public void onSaved(MapModel map) { } }