/* * Freeplane - mind map editor * Copyright (C) 2009 Dimitry * * This file author is Dimitry * * 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.Component; import java.awt.EventQueue; import java.lang.ref.WeakReference; import java.util.Collection; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.Map; import org.freeplane.core.extension.IExtension; import org.freeplane.core.io.IAttributeHandler; import org.freeplane.core.io.IAttributeWriter; import org.freeplane.core.io.ITreeWriter; import org.freeplane.core.io.ReadManager; import org.freeplane.core.io.WriteManager; import org.freeplane.core.resources.NamedObject; import org.freeplane.core.resources.ResourceController; import org.freeplane.core.undo.IActor; import org.freeplane.core.util.HtmlUtils; import org.freeplane.core.util.TextUtils; import org.freeplane.features.filter.condition.ASelectableCondition; import org.freeplane.features.map.IMapChangeListener; import org.freeplane.features.map.INodeChangeListener; import org.freeplane.features.map.ITooltipProvider; import org.freeplane.features.map.MapChangeEvent; import org.freeplane.features.map.MapController; import org.freeplane.features.map.MapModel; import org.freeplane.features.map.NodeBuilder; import org.freeplane.features.map.NodeChangeEvent; import org.freeplane.features.map.NodeModel; import org.freeplane.features.mode.CombinedPropertyChain; import org.freeplane.features.mode.Controller; import org.freeplane.features.mode.IPropertyHandler; import org.freeplane.features.mode.ModeController; import org.freeplane.features.styles.ConditionalStyleModel.Item; /** * @author Dimitry Polivaev * 28.09.2009 */ public class LogicalStyleController implements IExtension { // final private ModeController modeController; private static final int STYLE_TOOLTIP = 0; private WeakReference<NodeModel> cachedNode; private Collection<IStyle> cachedStyle; final private CombinedPropertyChain<Collection<IStyle>, NodeModel> styleHandlers; public LogicalStyleController(ModeController modeController) { // this.modeController = modeController; styleHandlers = new CombinedPropertyChain<Collection<IStyle>, NodeModel>(false); createBuilder(); registerChangeListener(); addStyleGetter(IPropertyHandler.NODE, new IPropertyHandler<Collection<IStyle>, NodeModel>() { public Collection<IStyle> getProperty(NodeModel node, Collection<IStyle> currentValue) { final MapStyleModel styleModel = MapStyleModel.getExtension(node.getMap()); add(node, styleModel, currentValue, new StyleNode(node)); return currentValue; } }); addStyleGetter(IPropertyHandler.STYLE, new IPropertyHandler<Collection<IStyle>, NodeModel>() { public Collection<IStyle> getProperty(NodeModel node, Collection<IStyle> currentValue) { IStyle style = LogicalStyleModel.getStyle(node); if(style != null){ add(node, currentValue, style); } final MapStyleModel styleModel = MapStyleModel.getExtension(node.getMap()); Collection<IStyle> condStyles = styleModel.getConditionalStyleModel().getStyles(node); addAll(node, styleModel, currentValue, condStyles); return currentValue; } }); addStyleGetter(IPropertyHandler.DEFAULT, new IPropertyHandler<Collection<IStyle>, NodeModel>() { public Collection<IStyle> getProperty(NodeModel node, Collection<IStyle> currentValue) { add(node, currentValue, MapStyleModel.DEFAULT_STYLE); return currentValue; } }); modeController.addToolTipProvider(STYLE_TOOLTIP, new ITooltipProvider() { public String getTooltip(ModeController modeController, NodeModel node, Component view) { if(!ResourceController.getResourceController().getBooleanProperty("show_styles_in_tooltip")) return null; final Collection<IStyle> styles = getStyles(node); if(styles.size() > 0) styles.remove(styles.iterator().next()); final String label = TextUtils.getText("node_styles"); return HtmlUtils.plainToHTML(label + ": " + getStyleNames(styles, ", ")); } }); } protected Collection<IStyle> getResursively(NodeModel node, Collection<IStyle> collection) { final MapStyleModel styleModel = MapStyleModel.getExtension(node.getMap()); Collection<IStyle> set = new LinkedHashSet<IStyle>(); addAll(node, styleModel, set, collection); return set; } protected void addAll(NodeModel node, MapStyleModel styleModel, Collection<IStyle> currentValue, Collection<IStyle> collection) { for(IStyle styleKey : collection){ add(node, styleModel, currentValue, styleKey); } } public void add(NodeModel node, Collection<IStyle> currentValue, IStyle style) { final MapStyleModel styleModel = MapStyleModel.getExtension(node.getMap()); add(node, styleModel, currentValue, style); } protected void add(NodeModel node, MapStyleModel styleModel, Collection<IStyle> currentValue, IStyle styleKey) { if(!currentValue.add(styleKey)){ return; } final NodeModel styleNode = styleModel.getStyleNode(styleKey); if (styleNode == null) { return; } if(styleKey instanceof StyleNode){ IStyle style = LogicalStyleModel.getStyle(styleNode); if(style != null){ add(node, styleModel, currentValue, style); } } final ConditionalStyleModel conditionalStyleModel = (ConditionalStyleModel) styleNode.getExtension(ConditionalStyleModel.class); if(conditionalStyleModel == null) return; addAll(node, styleModel, currentValue, conditionalStyleModel.getStyles(node)); } private void registerChangeListener() { ModeController modeController = Controller.getCurrentModeController(); final MapController mapController = modeController.getMapController(); mapController.addMapChangeListener(new IMapChangeListener() { public void onPreNodeMoved(NodeModel oldParent, int oldIndex, NodeModel newParent, NodeModel child, int newIndex) { clearCache(); } public void onPreNodeDelete(NodeModel oldParent, NodeModel selectedNode, int index) { clearCache(); } public void onNodeMoved(NodeModel oldParent, int oldIndex, NodeModel newParent, NodeModel child, int newIndex) { clearCache(); } public void onNodeInserted(NodeModel parent, NodeModel child, int newIndex) { clearCache(); } public void onNodeDeleted(NodeModel parent, NodeModel child, int index) { clearCache(); } public void mapChanged(MapChangeEvent event) { clearCache(); } }); mapController.addNodeChangeListener(new INodeChangeListener() { public void nodeChanged(NodeChangeEvent event) { clearCache(); } }); } private void createBuilder() { ModeController modeController = Controller.getCurrentModeController(); final MapController mapController = modeController.getMapController(); final ReadManager readManager = mapController.getReadManager(); readManager.addAttributeHandler(NodeBuilder.XML_NODE, "STYLE_REF", new IAttributeHandler() { public void setAttribute(final Object node, final String value) { final LogicalStyleModel extension = LogicalStyleModel.createExtension((NodeModel) node); extension.setStyle(StyleFactory.create(value)); } }); readManager.addAttributeHandler(NodeBuilder.XML_NODE, "LOCALIZED_STYLE_REF", new IAttributeHandler() { public void setAttribute(final Object node, final String value) { final LogicalStyleModel extension = LogicalStyleModel.createExtension((NodeModel) node); extension.setStyle(StyleFactory.create(NamedObject.format(value))); } }); final WriteManager writeManager = mapController.getWriteManager(); writeManager.addAttributeWriter(NodeBuilder.XML_NODE, new IAttributeWriter() { public void writeAttributes(final ITreeWriter writer, final Object node, final String tag) { final LogicalStyleModel extension = LogicalStyleModel.getExtension((NodeModel) node); if (extension == null) { return; } final IStyle style = extension.getStyle(); if (style == null) { return; } final String value = StyleNamedObject.toKeyString(style); if (style instanceof StyleNamedObject) { writer.addAttribute("LOCALIZED_STYLE_REF", value); } else { writer.addAttribute("STYLE_REF", value); } } }); } public static void install( final LogicalStyleController logicalStyleController) { final ModeController modeController = Controller.getCurrentModeController(); modeController.addExtension(LogicalStyleController.class, logicalStyleController); } public static LogicalStyleController getController() { final ModeController modeController = Controller.getCurrentModeController(); return getController(modeController); } public static LogicalStyleController getController(ModeController modeController) { return (LogicalStyleController) modeController.getExtension(LogicalStyleController.class); } public void refreshMap(final MapModel map) { final IActor actor = new IActor() { public void undo() { refreshMapLater(map); } public String getDescription() { return "refreshMap"; } public void act() { refreshMapLater(map); } }; Controller.getCurrentModeController().execute(actor, map); } private static Map<MapModel, Integer> mapsToRefresh = new HashMap<MapModel, Integer>(); private void refreshMapLater(final MapModel map) { final Integer count = mapsToRefresh.get(map); if (count == null) { mapsToRefresh.put(map, 0); } else { mapsToRefresh.put(map, count + 1); } EventQueue.invokeLater(new Runnable() { public void run() { final Integer count = mapsToRefresh.get(map); if (count > 0) { mapsToRefresh.put(map, count - 1); return; } mapsToRefresh.remove(map); final MapStyleModel extension = MapStyleModel.getExtension(map); extension.refreshStyles(); final MapController mapController = Controller.getCurrentModeController().getMapController(); mapController.fireMapChanged( new MapChangeEvent(this, map, MapStyle.MAP_STYLES, null, null)); } }); } public IStyle getFirstStyle(final NodeModel node){ final Collection<IStyle> styles = getStyles(node); boolean found = false; for(IStyle style:styles){ if(found){ return style; } if((style instanceof StyleNode)){ found = true; } } return MapStyleModel.DEFAULT_STYLE; } public Collection<IStyle> getStyles(final NodeModel node) { if(cachedNode != null && node.equals(cachedNode.get())){ return cachedStyle; } cachedStyle = styleHandlers.getProperty(node, new LinkedHashSet<IStyle>()); cachedNode = new WeakReference<NodeModel>(node); return cachedStyle; } public void moveConditionalStyleDown(final ConditionalStyleModel conditionalStyleModel, int index) { conditionalStyleModel.moveDown(index); } public void moveConditionalStyleUp(final ConditionalStyleModel conditionalStyleModel, int index) { conditionalStyleModel.moveUp(index); } public void addConditionalStyle(final ConditionalStyleModel conditionalStyleModel, boolean isActive, ASelectableCondition condition, IStyle style, boolean isLast) { conditionalStyleModel.addCondition(isActive, condition, style, isLast); } public void insertConditionalStyle(final ConditionalStyleModel conditionalStyleModel, int index, boolean isActive, ASelectableCondition condition, IStyle style, boolean isLast) { conditionalStyleModel.insertCondition(index, isActive, condition, style, isLast); } public Item removeConditionalStyle(final ConditionalStyleModel conditionalStyleModel, int index) { return conditionalStyleModel.removeCondition(index); } private void clearCache() { cachedStyle = null; cachedNode = null; } public IPropertyHandler<Collection<IStyle>, NodeModel> addStyleGetter( final Integer key, final IPropertyHandler<Collection<IStyle>, NodeModel> getter) { return styleHandlers.addGetter(key, getter); } public IPropertyHandler<Collection<IStyle>, NodeModel> removeStyleGetter( final Integer key, final IPropertyHandler<Collection<IStyle>, NodeModel> getter) { return styleHandlers.addGetter(key, getter); } public String getStyleNames(final Collection<IStyle> styles, String separator) { StringBuilder sb = new StringBuilder(); int i = 0; for(IStyle style :styles){ if(i > 0) sb.append(separator); sb.append(style.toString()); i++; } return sb.toString(); } public Collection<IStyle> getConditionalMapStyles(final NodeModel node) { final MapStyleModel styleModel = MapStyleModel.getExtension(node.getMap()); Collection<IStyle> condStyles = styleModel.getConditionalStyleModel().getStyles(node); return getResursively(node, condStyles); } public Collection<IStyle> getConditionalNodeStyles(final NodeModel node) { final Collection<IStyle> condStyles = new LinkedHashSet<IStyle>(); IStyle style = LogicalStyleModel.getStyle(node); if(style != null){ condStyles.add(style); } final ConditionalStyleModel conditionalStyleModel = (ConditionalStyleModel) node.getExtension(ConditionalStyleModel.class); if(conditionalStyleModel != null) condStyles.addAll(conditionalStyleModel.getStyles(node)); final Collection<IStyle> all = getResursively(node, condStyles); if(style != null){ all.remove(style); } return all; } public String getNodeStyleNames(NodeModel node, String separator) { return getStyleNames(getConditionalNodeStyles(node), separator); } public String getMapStyleNames(NodeModel node, String separator) { return getStyleNames(getConditionalMapStyles(node), separator); } }