/* * #! * Ontopia Vizigator * #- * Copyright (C) 2001 - 2013 The Ontopia Project * #- * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * !# */ package net.ontopia.topicmaps.viz; import java.awt.Color; import java.awt.Font; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.StringTokenizer; import javax.swing.Icon; import javax.swing.ImageIcon; import javax.swing.UIManager; import net.ontopia.infoset.core.LocatorIF; import net.ontopia.infoset.impl.basic.URILocator; import net.ontopia.net.Base64Decoder; import net.ontopia.net.Base64Encoder; import net.ontopia.topicmaps.core.AssociationIF; import net.ontopia.topicmaps.core.AssociationRoleIF; import net.ontopia.topicmaps.core.OccurrenceIF; import net.ontopia.topicmaps.core.TopicIF; import net.ontopia.topicmaps.core.TopicMapIF; import net.ontopia.utils.OntopiaRuntimeException; import net.ontopia.utils.StreamUtils; import com.touchgraph.graphlayout.Node; /** * INTERNAL: Stores and manages configuration. The configuration information is * stored as a topic map. */ public class VizTopicMapConfigurationManager extends VizConfigurationManager { private VizTopicTypePriorityConfigManager priorityManager; private static final String TYPE_COLOR = BASE + "type-color"; private static final String TOPIC_TYPE_SHAPE = BASE + "topic-type-shape"; private static final String OVERRIDE_RANDOM_COLORS = BASE + "override-random-colors"; private static final String ASSOC_TYPE_SHAPE = BASE + "assoc-type-shape"; private static final String ASSOCIATION_TYPE_COLOR_AUTOGENERATED = BASE + "association-type-color-autogenerated"; private static final String TOPIC_TYPE_COLOR_AUTOGENERATED = BASE + "topic-type-color-autogenerated"; private static final String TYPE_VISIBLE = BASE + "type-visible"; private static final String SHOW_ROLE_HOVER_HELP = BASE + "show-role-hover-help"; private static final String ENABLE_MOTION_KILLER = BASE + "enable-motion-killer"; private static final String TYPE_ICON_FILENAME = BASE + "type-icon-filename"; private static final String TYPE_LINE_WEIGHT = BASE + "type-line-weight"; private static final String TYPE_SHAPE_PADDING = BASE + "type-shape-padding"; private static final String TYPE_ICON = BASE + "type-icon"; private static final String TYPE_FONT = BASE + "type-font"; private static final String SINGLE_MOUSE_CLICK = BASE + "single-mouse-click"; private static final String LOCALITY_ALGORITHM = BASE + "locality-algorithm"; private static final String MOTION_KILLER_DELAY = BASE + "motion-killer-delay"; private static final String MAX_TOPIC_NAME_LENGTH = BASE + "max-topic-name-length"; private static final String DOUBLE_MOUSE_CLICK = BASE + "double-mouse-click"; private static final String START_TOPIC = BASE + "start-topic"; private static final String SUBJECT_INDICATOR = BASE + "subject-indicator"; private static final String SUBJECT = BASE + "subject"; private static final String SOURCE_LOCATOR = BASE + "source-locator"; private static final String TYPE_INSTANCE_TYPE = BASE + "type-instance"; private boolean visibleByDefault = true; private final Font defaultFont = UIManager.getFont("TextArea.font"); private final Font defaultAssociationFont = new Font(defaultFont.getFamily(), defaultFont.getStyle(), defaultFont.getSize() - 2); private TopicIF typeIcon; // Topics representing configuration properties of topic and association types private TopicIF typeColor; // Represents the color. private TopicIF typeVisible; // Represents whether it should be visible. private TopicIF typeFont; // Represents the font. private TopicIF typeExcluded; // Represents whether it should be excluded. // Topics representing configuration properties of topic types. private TopicIF typeIconFilename; // Represents the icon. private TopicIF topicTypeShape; // Represents the shape. private TopicIF typeShapePadding; // Represents the shape padding. private TopicIF topicTypeColorAutogeneratedTopic; // Represents the shape padding. // Topics representing configuration properties of association types. private TopicIF assocTypeShape; // Represents the shape. private TopicIF typeLineWeight; // Represents the line weight. private TopicIF associationTypeColorAutogeneratedTopic; // Represents the shape padding. private TopicIF showRoleHoverHelp; private TopicIF enableMotionKiller; private TopicIF startTopic; private TopicIF typeInstanceType; private Map iconCache; private Map fontCache; private Map colourCache; private ColorAssigner associationTypeColorAssigner; private ColorAssigner topicTypeColorAssigner; public static final Color DEFAULT_PANEL_BACKGROUND_COLOUR = new Color(240, 240, 240); private TopicIF doubleMouseClick; private TopicIF singleMouseClick; private TopicIF localityAlgorithm; private TopicIF motionKillerDelay; private TopicIF maxTopicNameLength; public static final int NODE_ORIENTED = 0; public static final int EDGE_ORIENTED = 1; public static final int EXPAND_NODE = 0; public static final int SET_FOCUS_NODE = 1; public static final int GO_TO_TOPIC = 2; private static final int DEFAULT_SINGLE_CLICK = EXPAND_NODE; private static final int DEFAULT_LOCALITY_ALGORITHM = NODE_ORIENTED; private static final int DEFAULT_KILLER_DELAY = 3; public static final int DEFAULT_MAX_TOPIC_NAME_LENGTH = 15; private static final int DEFAULT_DOUBLE_CLICK = SET_FOCUS_NODE; private TopicIF displayScopedAssociationNames; private static final String DISPLAY_SCOPED_ASSOC_NAMES = BASE + "display-scoped-assoc-names"; private TopicIF scopeFilter; private TopicIF filterStrictness; public static final int SHOW_ALL_ASSOCIATION_SCOPES = 0; public static final int LOOSE_ASSOCIATION_SCOPES = 1; public static final int STRICT_ASSOCIATION_SCOPES = 2; private static final String TYPE_EXCLUDED = BASE + "type-excluded"; private static final String SCOPE_FILTER = BASE + "scope-filter"; private static final String FILTER_STRICTNESS = BASE + "filter-strictness"; private static final int DEFAULT_FILTER_SELECTION = VizTopicMapConfigurationManager.FILTER_IN; private TopicIF subjectIndicator; private TopicIF subject; private TopicIF sourceLocator; protected static final String NULL_TOPIC = BASE + "null"; protected TopicIF nullTopic; private TopicIF scopingTopic; private TopicIF overrideRandomColorsTopic; public static final int FILTER_DEFAULT = 2; public static final int FILTER_OUT = 1; public static final int FILTER_IN = 0; private static final String SCOPING_TOPIC = BASE + "scoping-topic"; /** * Constructor initializes the configuration by loading a topic map from the * URL given in the parameter. */ public VizTopicMapConfigurationManager(File tmfile) throws IOException { super(tmfile); setupPriorityManager(); } /** * Constructor initializes the configuration by loading a topic map from the * URL given in the parameter. */ public VizTopicMapConfigurationManager(String tmurl) throws IOException { super(tmurl); setupPriorityManager(); } /** * Creates an empty configuration manager where everything is set to default. */ public VizTopicMapConfigurationManager() { super(); setupPriorityManager(); } protected void init() { super.init(); startTopic = getTopic(START_TOPIC); scopingTopic = getTopic(SCOPING_TOPIC); subjectIndicator = getTopic(SUBJECT_INDICATOR); subject = getTopic(SUBJECT); sourceLocator = getTopic(SOURCE_LOCATOR); typeVisible = getTopic(TYPE_VISIBLE); typeColor = getTopic(TYPE_COLOR); topicTypeShape = getTopic(TOPIC_TYPE_SHAPE); overrideRandomColorsTopic = getTopic(OVERRIDE_RANDOM_COLORS); assocTypeShape = getTopic(ASSOC_TYPE_SHAPE); typeIconFilename = getTopic(TYPE_ICON_FILENAME); typeIcon = getTopic(TYPE_ICON); typeLineWeight = getTopic(TYPE_LINE_WEIGHT); typeFont = getTopic(TYPE_FONT); typeShapePadding = getTopic(TYPE_SHAPE_PADDING); associationTypeColorAutogeneratedTopic = getTopic(ASSOCIATION_TYPE_COLOR_AUTOGENERATED); topicTypeColorAutogeneratedTopic = getTopic(TOPIC_TYPE_COLOR_AUTOGENERATED); typeExcluded = getTopic(TYPE_EXCLUDED); scopeFilter = getTopic(SCOPE_FILTER); filterStrictness = getTopic(FILTER_STRICTNESS); singleMouseClick = getTopic(SINGLE_MOUSE_CLICK); doubleMouseClick = getTopic(DOUBLE_MOUSE_CLICK); localityAlgorithm = getTopic(LOCALITY_ALGORITHM); motionKillerDelay = getTopic(MOTION_KILLER_DELAY); maxTopicNameLength = getTopic(MAX_TOPIC_NAME_LENGTH); typeInstanceType = getTopic(TYPE_INSTANCE_TYPE, Messages .getString("Viz.InstanceOf")); showRoleHoverHelp = getTopic(SHOW_ROLE_HOVER_HELP); enableMotionKiller = getTopic(ENABLE_MOTION_KILLER); displayScopedAssociationNames = getTopic(DISPLAY_SCOPED_ASSOC_NAMES); Node.DEFAULT_TYPE = Node.TYPE_ROUNDRECT; associationTypeColorAssigner = new ColorAssigner(); topicTypeColorAssigner = new ColorAssigner(); colourCache = new HashMap(); iconCache = new HashMap(); fontCache = new HashMap(); } public TopicIF getOverrideColorsTopic() { return overrideRandomColorsTopic; } public TopicIF getTopicTypeShapeTopic() { return topicTypeShape; } public TopicIF getAssociationTypeColorAutogeneratedTopic() { return associationTypeColorAutogeneratedTopic; } public TopicIF getTopicTypeColorAutogeneratedTopic() { return topicTypeColorAutogeneratedTopic; } public TopicIF getAssociationTypeShapeTopic() { return assocTypeShape; } public TopicIF getTopicTypeShapePaddingTopic() { return typeShapePadding; } public TopicIF getTopicTypeIconTopic() { return typeIcon; } public TopicIF getTopicTypeFontTopic() { return typeFont; } public TopicIF getTopicTypeColorTopic() { return typeColor; } public TopicIF getAssociationTypeColorTopic() { return typeColor; } // FIXME: Using the same type for topic types and association types is risky, // as a topic may be both topic type and association type at the same time. public TopicIF getTypeVisibleTopic() { return typeVisible; } public TopicIF getTopicTypeExcludedTopic() { return typeExcluded; } public TopicIF getAssociationTypeLineWeightTopic() { return typeLineWeight; } public TopicIF getAssociationTypeFontTopic() { return typeFont; } private void setupPriorityManager() { priorityManager = new VizTopicTypePriorityConfigManager(this); } public VizTopicTypePriorityConfigManager getTTPriorityManager() { return priorityManager; } // --- External interface public Color getAssociationTypeColor(TopicIF type) { Color c = (Color) colourCache.get(type); if (getUsesDefault(type, false) && defaultOverrides(false)) c = lookupColor(defaultAssociationType, typeColor); if (c == null) c = lookupColor(type, typeColor); if (c == null && defaultOverrides(false)) c = lookupColor(defaultAssociationType, typeColor); if (c == null) { c = associationTypeColorAssigner.getNextColor(); setTypeColor(type, c); setOccurenceValue(type, getAssociationTypeColorAutogeneratedTopic(), true); } return c; } /** * returns true iff the default type is set to verride autogenerated values. * @param isTopicType Set to true to check for the association default type, * and false for the topic default type. */ public boolean defaultOverrides(boolean isTopicType) { return getOccurrenceValue(isTopicType ? defaultType : defaultAssociationType, getOverrideColorsTopic(), false); } /** * returns true iff the given topic type is set to use default when it doesn't * its own colour. * @param type The type to check. * @param isTopicType */ public boolean getUsesDefault(TopicIF type, boolean isTopicType) { // Types that don't specify whether they use the dafault state do not by // default. return getOccurrenceValue(type, isTopicType ? getTopicTypeColorAutogeneratedTopic() : getAssociationTypeColorAutogeneratedTopic(), false); } public Color getTopicTypeColor(TopicIF type) { Color c = (Color) colourCache.get(type); if (getUsesDefault(type, true) && defaultOverrides(true)) c = lookupColor(defaultType, typeColor); if (c == null) c = lookupColor(type, typeColor); if (c == null && defaultOverrides(true)) c = lookupColor(defaultType, typeColor); if (c == null) { c = topicTypeColorAssigner.getNextColor(); setTypeColor(type, c); setOccurenceValue(type, getTopicTypeColorAutogeneratedTopic(), true); } return c; } public int getTopicTypeShape(TopicIF topictype) { return getOccurrenceValue(topictype, topicTypeShape, getOccurrenceValue(defaultType, topicTypeShape, Node.DEFAULT_TYPE)); } public boolean hasOccurrence(TopicIF topictype, TopicIF type) { TopicIF cfgtopic = getConfigTopic(topictype); return cfgtopic != null && getOccurrence(cfgtopic, type) != null; } public Font getDefaultFont() { return defaultFont; } public void setTypeColor(TopicIF type, Color c) { setColor(type, typeColor, c); colourCache.put(type, c); } //----------------------------------------------- /** * Sets the colour of a given 'type' and updates 'view' accordingly. * If the type is defaultType/defaultTopicType, all topic/association types * that have no explicit colour setting are updated with this colour. */ public void setTypeColor(TopicIF type, Color c, TopicMapView view) { setTypeColor(type, c); setOccurenceValue(type, getTopicTypeColorAutogeneratedTopic(), false); setOccurenceValue(type, getAssociationTypeColorAutogeneratedTopic(), false); if (type == defaultType) { // For each topic type Iterator typesIt = view.getAllTopicTypesWithNull().iterator(); while (typesIt.hasNext()) { TopicIF currentType = (TopicIF)typesIt.next(); boolean hasColor = hasOccurrence(currentType, getTopicTypeColorTopic()); boolean isAutogenerated = getOccurrenceValue(currentType, getTopicTypeColorAutogeneratedTopic(), false); boolean defaultOverrules = getOccurrenceValue(type, getOverrideColorsTopic(), false); // If the type doesn't have an explicicly defined color yet: if (!hasColor || isAutogenerated && defaultOverrules) { // Update it in view with the new default color. if (currentType == null) // Untyped topic removeOccurrence(untypedTopic, getTopicTypeColorTopic()); else removeOccurrence(currentType, getTopicTypeColorTopic()); view.setTypeColor(currentType, c); } } } else if (type == defaultAssociationType) { // For each association type Iterator typesIt = view.getAssociationTypes().iterator(); while (typesIt.hasNext()) { TopicIF currentType = (TopicIF)typesIt.next(); boolean hasColor = hasOccurrence(currentType, getAssociationTypeColorTopic()); boolean isAutogenerated = getOccurrenceValue(currentType, getAssociationTypeColorAutogeneratedTopic(), false); boolean defaultOverrules = getOccurrenceValue(type, getOverrideColorsTopic(), false); // If the type doesn't have a defined color yet: if (!hasColor || isAutogenerated && defaultOverrules) { // Update it in view with the new default color. removeOccurrence(currentType, getAssociationTypeColorTopic()); view.setTypeColor(currentType, c); } } } else view.setTypeColor(type, c); } /** * Sets the color of a given topic/association 'type' to the default value * and updates 'view' accordingly. */ public void setColorToDefault(TopicIF type, boolean topicType, TopicMapView view) { removeOccurence(type, topicType ? getTopicTypeColorTopic() : getAssociationTypeColorTopic()); if (topicType) view.setTypeColor(type, getTopicTypeColor(defaultType)); else view.setTypeColor(type, getAssociationTypeColor(defaultAssociationType)); } /** * Sets the given topic 'type' be either filtered in, filtered out or to * use the filter setting of the default type. * For defaultType, updates all types with no explicit setting. * Updates view accordingly. */ public void setTypeVisibility(TopicIF type, int visibility, TopicMapView view) { // Only set configuration for default type itself. setTypeVisible(type, visibility); boolean visible = visibility == VizTopicMapConfigurationManager.FILTER_IN || (visibility == VizTopicMapConfigurationManager .FILTER_DEFAULT && isTopicTypeVisible(defaultType)); if (type == defaultType) { // For each topic type Iterator typesIt = view.getAllTopicTypesWithNull().iterator(); while (typesIt.hasNext()) { TopicIF currentType = (TopicIF)typesIt.next(); // FIXME: Added this code to avoid a NullPointerException, which should, // never occur, but did occur after filtering in the topic type Opera // in the Opera topic map and then filtering out Default type. // Would be good to find out why it occurred, as this is a possible // source of wrong filtering. if (currentType == null) continue; // If the type doesn't have a defined shape yet: if (!hasOccurrence(currentType, getTypeVisibleTopic())) { // Update it in view with the new default filter setting. view.setTopicTypeVisible(currentType, visibility == VizTopicMapConfigurationManager .FILTER_IN); } } } else { view.setTopicTypeVisible(type, visible); } // Having made the appropriate changes to the filtering configuration, now // add any nodes and edges within locality's reach that are filtered in. if (visible) view.loadNodesInLocality(view.getFocusNode(), true, false); } /** * Sets the given association 'type' be either filtered in, filtered out or to * use the filter setting of the default type. * For defaultAssociationType, updates all types with no explicit setting. * Updates view accordingly. */ public void setAssociationTypeVisible(TopicIF type, int visibility, TopicMapView view) { setTypeVisible(type, visibility); boolean visible = visibility == VizTopicMapConfigurationManager.FILTER_IN || (visibility == VizTopicMapConfigurationManager.FILTER_DEFAULT && isTopicTypeVisible(defaultAssociationType)); if (type == defaultAssociationType) { // For each association type Iterator typesIt = view.getAssociationTypes().iterator(); while (typesIt.hasNext()) { TopicIF currentType = (TopicIF)typesIt.next(); // If visibility hasn't been set for this type yet: if (!hasOccurrence(currentType, getTypeVisibleTopic())) { // Update it in view with the new default filter setting. view.setAssociationTypeVisible(currentType, visibility == VizTopicMapConfigurationManager.FILTER_IN); } } } else { view.setAssociationTypeVisible(type, visible); } // Having made the appropriate changes to the filtering configuration, now // add any nodes and edges within locality's reach that are filtered in. if (visible) view.loadNodesInLocality(view.getFocusNode(), true, false); } // FIXME: SHOULD BECOME REDUNDANT. THEN REMOVE. /** * Sets the given topic 'type' to be visible and updates 'view' accordingly. * For defaultType, updates all types with no explicit setting. */ public void setTypeVisible(TopicIF type, boolean visible, TopicMapView view) { // Only set configuration for default type itself. setTypeVisible(type, visible); if (type == defaultType) { // For each topic type Iterator typesIt = view.getAllTopicTypesWithNull().iterator(); while (typesIt.hasNext()) { TopicIF currentType = (TopicIF)typesIt.next(); // If the type doesn't have a defined shape yet: if (!hasOccurrence(currentType, getTypeVisibleTopic())) { // Update it in view with the new default filter setting. view.setTopicTypeVisible(currentType, visible); } } } else view.setTopicTypeVisible(type, visible); if (visible) view.loadNodesInLocality(view.getFocusNode(), true, false); } // FIXME: TO BECOME REDUNDANT. THEN REMOVE. /** * Sets the given association 'type' to be visible and updates 'view' * accordingly. * For defaultAssociationType, updates all types with no explicit setting. */ public void setAssociationTypeVisible(TopicIF type, boolean visible, TopicMapView view) { setTypeVisible(type, visible); if (type == defaultAssociationType) { // For each association type Iterator typesIt = view.getAssociationTypes().iterator(); while (typesIt.hasNext()) { TopicIF currentType = (TopicIF)typesIt.next(); // If visibility hasn't been set for this type yet: if (!hasOccurrence(currentType, getTypeVisibleTopic())) { // Update it in view with the new default filter setting. view.setAssociationTypeVisible(currentType, visible); } } } else view.setAssociationTypeVisible(type, visible); } /** * Sets the shape of the given topic 'type' and updates 'view' accordingly. * For defaultType, updates all types with no explicit setting. */ public void setTopicTypeShape(TopicIF type, int i, TopicMapView view) { setTopicTypeShape(type, i); if (type == defaultType) { // For each topic type Iterator typesIt = view.getAllTopicTypesWithNull().iterator(); while (typesIt.hasNext()) { TopicIF currentType = (TopicIF)typesIt.next(); // If the type doesn't have a defined shape yet: if (!hasOccurrence(currentType, getTopicTypeShapeTopic())) // Update it in view with the new default shape. view.setTopicTypeShape(currentType, i); } } else if (i == TypesConfigFrame.UNDEFINED_NODE_SHAPE) { removeOccurence(type, getTopicTypeShapeTopic()); view.setTopicTypeShape(type, getTopicTypeShape(defaultType)); } else view.setTopicTypeShape(type, i); } /** * Sets the shape of the given association 'type' and updates 'view' * accordingly. * For defaultType, updates all types with no explicit setting. */ public void setAssociationTypeShape(TopicIF type, int i, TopicMapView view) { setAssociationTypeShape(type, i); if (type == defaultAssociationType) { // For each topic type Iterator typesIt = view.getAssociationTypes().iterator(); while (typesIt.hasNext()) { TopicIF currentType = (TopicIF)typesIt.next(); // If the type doesn't have a defined shape yet: if (!hasOccurrence(currentType, getAssociationTypeShapeTopic())) // Update it in view with the new default shape. view.setAssociationTypeShape(currentType, i); } } else if (i == TypesConfigFrame.UNDEFINED_EDGE_SHAPE) { removeOccurence(type, getAssociationTypeShapeTopic()); view.setAssociationTypeShape(type, getAssociationTypeShape(defaultAssociationType)); } else view.setAssociationTypeShape(type, i); } /** * Sets the font of a given topic/association 'type' to the default value * and updates 'view' accordingly. */ public void setFontToDefault(TopicIF type, boolean topicType, TopicMapView view) { removeOccurence(type, topicType ? getTopicTypeFontTopic() : getAssociationTypeFontTopic()); if (topicType) view.setTypeFont(type, getTypeFont(defaultType)); else view.setTypeFont(type, getTypeFont(defaultAssociationType)); } /** * Sets the font of the given topic/association 'type' and updates 'view' * accordingly. For defaultType/defaultAssociationType, updates all types * with no explicit setting. */ public void setTypeFont(TopicIF type, Font font, TopicMapView view) { setTypeFont(type, font); if (type == defaultType) { // For each topic type Iterator typesIt = view.getAllTopicTypesWithNull().iterator(); while (typesIt.hasNext()) { TopicIF currentType = (TopicIF)typesIt.next(); // If the type doesn't have a defined font yet: if (!hasOccurrence(currentType, getTopicTypeFontTopic())) // Update it in view with the new default font. view.setTypeFont(currentType, font); } } else if (type == defaultAssociationType) { // For each association type Iterator typesIt = view.getAssociationTypes().iterator(); while (typesIt.hasNext()) { TopicIF currentType = (TopicIF)typesIt.next(); // If the type doesn't have a defined font yet: if (!hasOccurrence(currentType, getAssociationTypeFontTopic())) // Update it in view with the new default font. view.setTypeFont(currentType, font); } } else view.setTypeFont(type, font); } /** * Sets the line weight of the given association 'type' and updates 'view' * accordingly. * For defaultAssociationType, updates all types with no explicit setting. */ public void setAssociationTypeLineWeight(TopicIF type, int i, TopicMapView view) { setTypeLineWeight(type, i); if (type == defaultAssociationType) { // For each topic type Iterator typesIt = view.getAssociationTypes().iterator(); while (typesIt.hasNext()) { TopicIF currentType = (TopicIF)typesIt.next(); // If the type doesn't have a defined shape yet: if (!hasOccurrence(currentType, getAssociationTypeLineWeightTopic())) // Update it in view with the new default shape. view.setAssociationTypeLineWeight(currentType, i); } } else if (i == TypesConfigFrame.UNDEFINED_EDGE_SHAPE_WEIGHT) { removeOccurence(type, getAssociationTypeLineWeightTopic()); view.setAssociationTypeLineWeight(type, getAssociationTypeLineWeight(defaultType)); } else view.setAssociationTypeLineWeight(type, i); } /** * Sets the shape padding of the given topic 'type' and updates 'view' * accordingly. * For defaultType, updates all types with no explicit setting. */ public void setTopicTypeShapePadding(TopicIF type, int i, TopicMapView view) { setTopicTypeShapePadding(type, i); if (type == defaultType) { // For each topic type Iterator typesIt = view.getAllTopicTypesWithNull().iterator(); while (typesIt.hasNext()) { TopicIF currentType = (TopicIF)typesIt.next(); // If the type doesn't have a defined shape yet: if (!hasOccurrence(currentType, getTopicTypeShapePaddingTopic())) // Update it in view with the new default shape. view.setTopicTypeShapePadding(currentType, i); } } else if (i == TypesConfigFrame.UNDEFINED_NODE_SHAPE_PADDING) { removeOccurence(type, getTopicTypeShapePaddingTopic()); view.setTopicTypeShapePadding(type, getTopicTypeShape(defaultType)); } else view.setTopicTypeShapePadding(type, i); } /** * Sets the name of the icon file (optional) of the given topic 'type' and * updates 'view' accordingly. * For defaultType, updates all types with no explicit setting. */ public void setTypeIconFilename(TopicIF type, String string, TopicMapView view) { setTypeIconFilename(type, string); Icon icon = getTypeIcon(type); if (type == defaultType) { // For each topic type Iterator typesIt = view.getAllTopicTypesWithNull().iterator(); while (typesIt.hasNext()) { TopicIF currentType = (TopicIF)typesIt.next(); // If the type doesn't have a defined shape yet: if (!hasOccurrence(currentType, getTopicTypeIconTopic())) // Update it in view with the new default shape. view.setTypeIcon(currentType, icon); } } else if (type == defaultAssociationType) { // For each topic type Iterator typesIt = view.getAssociationTypes().iterator(); while (typesIt.hasNext()) { TopicIF currentType = (TopicIF)typesIt.next(); // If the type doesn't have a defined shape yet: if (!hasOccurrence(currentType, getTopicTypeIconTopic())) // Update it in view with the new default shape. view.setTypeIcon(currentType, icon); } } else if (string == null) { removeOccurence(type, getTopicTypeIconTopic()); view.setTypeIcon(type, getTypeIcon(defaultType)); } else view.setTypeIcon(type, icon); } /** * Sets the given topic 'type' to be included and updates 'view' accordingly. * For defaultType, updates all types with no explicit setting. */ public void setTypeIncluded(TopicIF type, TopicMapView view) { setTypeIncluded(type); if (type == defaultType) { // For each topic type Iterator typesIt = view.getAllTopicTypesWithNull().iterator(); while (typesIt.hasNext()) { TopicIF currentType = (TopicIF)typesIt.next(); // If the type doesn't have a defined shape yet: if (!hasOccurrence(currentType, getTopicTypeExcludedTopic())) // Update it in view with the new default shape. view.setTopicTypeExcluded(currentType, false); } } else view.setTopicTypeExcluded(type, false); } /** * Sets the given topic 'type' to be excluded and updates 'view' accordingly. * For defaultType, updates all types with no explicit setting. */ public void setTypeExcluded(TopicIF type, TopicMapView view) { setTypeExcluded(type); if (type == defaultType) { // For each topic type Iterator typesIt = view.getAllTopicTypesWithNull().iterator(); while (typesIt.hasNext()) { TopicIF currentType = (TopicIF)typesIt.next(); // If the type doesn't have a defined shape yet: if (!hasOccurrence(currentType, getTopicTypeExcludedTopic())) // Update it in view with the new default shape. view.setTopicTypeExcluded(currentType, true); } } else view.setTopicTypeExcluded(type, true); } // ---------------------- public void setTopicTypeShape(TopicIF type, int i) { setOccurrenceValue(type, topicTypeShape, i); } public void setAssociationTypeShape(TopicIF type, int i) { setOccurrenceValue(type, assocTypeShape, i); } public void setTypeLineWeight(TopicIF type, int i) { setOccurrenceValue(type, typeLineWeight, i); } public void setTopicTypeShapePadding(TopicIF type, int i) { setOccurrenceValue(type, typeShapePadding, i); } public void setTypeVisible(TopicIF type, int visibility) { if (visibility == VizTopicMapConfigurationManager.FILTER_DEFAULT) { if (typeVisible == defaultAssociationType || typeVisible == defaultType) return; removeOccurence(type, typeVisible); } else setOccurenceValue(type, typeVisible, visibility == VizTopicMapConfigurationManager.FILTER_IN); } // FIXME: THIS METHOD SHOULD BECOME REDUNDANT. REMOVE IT THEN. public void setTypeVisible(TopicIF type, boolean visible) { setOccurenceValue(type, typeVisible, visible); } public boolean isAssociationTypeVisible(TopicIF assoctype) { return lookupVisible(assoctype, typeVisible, defaultAssociationType); } public boolean isTopicTypeVisible(TopicIF assoctype) { return lookupVisible(assoctype, typeVisible, defaultType); } public boolean lookupVisible(TopicIF type, TopicIF occtype, TopicIF defType) { TopicIF cfgtopic = getConfigTopic(type); OccurrenceIF occ = getOccurrence(cfgtopic, occtype); if (occ == null || occ.getValue() == null) { cfgtopic = getConfigTopic(defType); occ = getOccurrence(cfgtopic, occtype); } if (occ == null || occ.getValue() == null) return visibleByDefault; return occ.getValue().equalsIgnoreCase("true"); } public boolean isVisible(TopicIF topic) { Iterator it = topic.getTypes().iterator(); if (!it.hasNext()) // no types return isTopicTypeVisible(null); while (it.hasNext()) { TopicIF type = (TopicIF) it.next(); if (!isTopicTypeVisible(type)) return false; } return true; } public boolean isVisible(AssociationIF assoc) { // Only check the visibility of the association // The visibility of the roles is taken care of in #assertNode() return isAssociationTypeVisible(assoc.getType()) && matchesFilter(assoc.getScope()); } /** * Checks if a given scope matches the association scope filter. * See matchScope(scope, filter) for details. * @param scope The scope to check for matching. * @return true iff scope matches the association scope filter. */ public boolean matchesFilter(Collection scope) { Collection filter = getAssociationScopeFilter(); return matchesFilter(scope, filter); } /** * Checks if a given scope (collection of topics) matches a given filter * (another collection of topics). * @param scope The scope to match with the filter. * @param filter The filter for the scope to be matched with. * @return true iff scope matches filter. The scope either has to contain one, * all or none of the topics in filter, * depending on getAssociationFilterTightness(). */ public boolean matchesFilter(Collection scope, Collection filter) { // An empty scope matches all filters.. if (scope.isEmpty()) return true; // An emtpty filter is matched by all scopes. if (filter.isEmpty()) return true; int associationFilterStrictness = getAssociationScopeFilterStrictness(); Iterator scopeIt = scope.iterator(); switch (associationFilterStrictness) { case SHOW_ALL_ASSOCIATION_SCOPES: // No filter: Show all associations (regardless of scope). return true; case LOOSE_ASSOCIATION_SCOPES: // Show associations with scope matching one+ topic in the filter. while (scopeIt.hasNext()) if (filter.contains(getConfigTopic((TopicIF)scopeIt.next()))) return true; return false; case STRICT_ASSOCIATION_SCOPES: // Show associaitons with scope matching all topics in the filter. int filterMatches = 0; while (scopeIt.hasNext()) if (filter.contains(getConfigTopic((TopicIF)scopeIt.next()))) filterMatches++; return filterMatches == filter.size(); default: // SHOW_ALL_ASSOCIATION_SCOPES // No filter: Show all associations (regardless of scope). return true; } } public boolean isVisible(AssociationRoleIF role) { // A role is only visible if it is not part of an optimized // association, and the association object it is part of // is itself visible. The visibility of the roles is taken // care of in #assertNode() if (role.getAssociation().getRoles().size() == 2) return false; return isVisible(role.getAssociation()); } /** * Finds the color for this association or topic type that is stored in the * topic map. Returns null if no color is stored in the topic map. */ public Color lookupColor(TopicIF type, TopicIF occtype) { String value = getOccurrenceValue(type, occtype); if (value == null) return null; return parseColor(value); } // --- Internal /** * Sets the color for this association or topic type in the topic map. */ private void setColor(TopicIF type, TopicIF occtype, Color c) { setOccurenceValue(type, occtype, c.getRed() + " " + c.getGreen() + " " + c.getBlue()); } /** * Constructs a Color instance from RGB values that are delimited within a * string. */ private Color parseColor(String value) { try { StringTokenizer strtok = new StringTokenizer(value); int red = Integer.parseInt(strtok.nextToken()); int green = Integer.parseInt(strtok.nextToken()); int blue = Integer.parseInt(strtok.nextToken()); return new Color(red, green, blue); } catch (Exception e) { throw new OntopiaRuntimeException(Messages.getString( "Viz.ColourParseError", value)); } } // --- Color assigner /** * Helper class which can semi-randomly provide colors. */ private class ColorAssigner { private int previous; private Color[] colors; private ColorAssigner() { previous = -1; colors = new Color[22]; colors[0] = new Color(116, 10, 57); colors[1] = new Color(219, 1, 99); colors[2] = new Color(245, 157, 205); colors[3] = new Color(121, 9, 125); colors[4] = new Color(247, 21, 255); colors[5] = new Color(245, 179, 241); colors[6] = new Color(9, 20, 121); colors[7] = new Color(1, 34, 233); colors[8] = new Color(92, 114, 250); colors[9] = new Color(9, 125, 129); colors[10] = new Color(17, 235, 255); colors[11] = new Color(153, 245, 242); colors[12] = new Color(161, 245, 165); colors[12] = new Color(0, 238, 22); colors[13] = new Color(11, 85, 14); colors[14] = new Color(229, 255, 5); colors[15] = new Color(250, 248, 150); colors[16] = new Color(134, 55, 8); colors[17] = new Color(255, 71, 21); colors[18] = new Color(246, 164, 142); colors[19] = new Color(10, 106, 66); colors[20] = new Color(11, 22, 89); colors[21] = Color.darkGray; } public Color getNextColor() { if (previous < colors.length - 1) previous++; return colors[previous]; } } /** * INTERNAL: Finds the start topic in the given topic map, and returns it. */ public TopicIF getStartTopic(TopicMapIF graphtm) { return getTopicFromReference(graphtm, startTopic); } /** * INTERNAL: Finds the scoping topic in the given topic map, and returns it. */ public TopicIF getScopingTopic(TopicMapIF graphtm) { return getTopicFromReference(graphtm, scopingTopic); } /** * INTERNAL: Finds the scoping topic in the given topic map, and returns it. */ public TopicIF getScopingTopicHolder() { return scopingTopic; } private TopicIF getTopicFromReference(TopicMapIF graphtm, TopicIF reference) { // See #setTopicReference() // for full details how the start topic is referenced. String base = graphtm.getStore().getBaseAddress().getAddress(); Iterator occurs = reference.getOccurrences().iterator(); if (occurs.hasNext()) { OccurrenceIF occurrence = (OccurrenceIF) occurs.next(); TopicIF occType = occurrence.getType(); if (occType == null) { // This is just here for backwards compatability LocatorIF loc = occurrence.getLocator(); TopicIF topic = (TopicIF) graphtm.getObjectByItemIdentifier(loc); // convert to new form if (topic != null) setStartTopic(topic); return topic; } else if (subjectIndicator.equals(occType)) { LocatorIF locator = occurrence.getLocator(); if (locator == null) { String data = occurrence.getValue(); try { locator = new URILocator(base + data); } catch (MalformedURLException e) { // Ignore any errors } } return graphtm.getTopicBySubjectIdentifier(locator); } else if (subject.equals(occType)) { LocatorIF locator = occurrence.getLocator(); return graphtm.getTopicBySubjectLocator(locator); } else if (sourceLocator.equals(occType)) { LocatorIF locator = occurrence.getLocator(); if (locator == null) { String data = occurrence.getValue(); try { locator = new URILocator(base + data); } catch (MalformedURLException e) { // Ignore any errors } } return (TopicIF) graphtm.getObjectByItemIdentifier(locator); } } return null; } /** * Sets the start topic of the vizualization. We use the srclocator stored in * an occurrence as not all topics have a subjind. */ public void setStartTopic(TopicIF extstart) { setTopicReference(startTopic, extstart, extstart.getTopicMap().getStore() .getBaseAddress().getAddress()); } protected void setTopicReference(TopicIF topic, TopicIF extstart, String base) { clearTopic(topic); // removes the occurrences recording the srclocs for (Iterator iter = extstart.getSubjectIdentifiers().iterator(); iter .hasNext();) { LocatorIF locator = (LocatorIF) iter.next(); if (locator.getAddress().startsWith(base)) builder.makeOccurrence(topic, subjectIndicator, locator.getAddress() .substring(base.length())); else builder.makeOccurrence(topic, subjectIndicator, locator); return; } for (Iterator iter = extstart.getSubjectLocators().iterator(); iter .hasNext();) { LocatorIF locator = (LocatorIF) iter.next(); if (locator.getAddress().startsWith(base)) builder.makeOccurrence(topic, subject, locator.getAddress() .substring(base.length())); else builder.makeOccurrence(topic, subject, locator); return; } for (Iterator iter = extstart.getItemIdentifiers().iterator(); iter .hasNext();) { LocatorIF locator = (LocatorIF) iter.next(); if (locator.getAddress().startsWith(base)) builder.makeOccurrence(topic, sourceLocator, locator.getAddress() .substring(base.length())); else builder.makeOccurrence(topic, sourceLocator, locator); return; } } /** Clear the start topic if it was set. */ public void clearTopic(TopicIF aTopic) { if (!aTopic.getOccurrences().isEmpty()) { Iterator it = new ArrayList(aTopic.getOccurrences()).iterator(); while (it.hasNext()) { OccurrenceIF occur = (OccurrenceIF) it.next(); occur.remove(); } } } public TopicIF getTypeInstanceType() { return typeInstanceType; } public int getTypeVisibility(TopicIF selectedType) { String visibility = getOccurrenceValue(selectedType, typeVisible); if (visibility == null) { return selectedType == defaultAssociationType || selectedType == defaultType ? DEFAULT_FILTER_SELECTION : VizTopicMapConfigurationManager.FILTER_DEFAULT; } if (visibility.equals("true")) return VizTopicMapConfigurationManager.FILTER_IN; if (visibility.equals("false")) return VizTopicMapConfigurationManager.FILTER_OUT; // The following should never happen, and is an error in the configuration. throw new OntopiaRuntimeException("Error in Vizigator configuration. " + "Filter setting should always be 'true' or 'false', but was'" + visibility + "'"); } public int getAssociationTypeShape(TopicIF selectedType) { return getOccurrenceValue(selectedType, assocTypeShape, getOccurrenceValue(defaultType, assocTypeShape, TMRoleEdge.DEFAULT_SHAPE)); } public int getAssociationTypeLineWeight(TopicIF selectedType) { return getOccurrenceValue(selectedType, typeLineWeight, getOccurrenceValue(defaultAssociationType, typeLineWeight, TMRoleEdge.DEFAULT_LINE_WEIGHT)); } public int getTopicTypeShapePadding(TopicIF selectedType) { return getOccurrenceValue(selectedType, typeShapePadding, getOccurrenceValue(defaultType, typeShapePadding, TMTopicNode.DEFAULT_SHAPE_PADDING)); } public String getTypeIconFilename(TopicIF type) { return getOccurrenceValue(type, typeIconFilename); } public Icon getTypeIcon(TopicIF type) { return lookupIcon(type, typeIconFilename, typeIcon); } public Font getTypeFont(TopicIF type) { return getTypeFont(type, getTypeFont(defaultType, getDefaultFont())); } public Font getAssociationTypeFont(TopicIF type) { return getTypeFont(type, getDefaultAssociationFont()); } private Font getDefaultAssociationFont() { return defaultAssociationFont; } private Font getTypeFont(TopicIF type, Font df) { String result = getOccurrenceValue(type, typeFont); if (result == null) { if (type == defaultType) setTypeFont(defaultType, df); if (type == defaultAssociationType) setTypeFont(defaultAssociationType, df); return df; } return parseFont(result); } public void setTypeFont(TopicIF type, Font font) { setOccurenceValue(type, typeFont, serializeFont(font)); } private String serializeFont(Font font) { // Fonts are serialized to the form: // "<family name>-<style>-<size>" StringBuilder buffer = new StringBuilder(); buffer.append(font.getFamily()); buffer.append("-"); buffer.append(font.getStyle()); buffer.append("-"); buffer.append(font.getSize()); String result = buffer.toString(); fontCache.put(result, font); return result; } public Font parseFont(String fontString) { // Fonts are serialized to the form: // "<family name>-<style>-<size>" Font font = (Font) fontCache.get(fontString); if (font != null) { return font; } StringTokenizer tokenizer = new StringTokenizer(fontString, "-"); return new Font(tokenizer.nextToken(), Integer.parseInt(tokenizer .nextToken()), Integer.parseInt(tokenizer.nextToken())); } private Icon lookupIcon(TopicIF type, TopicIF filenameTopic, TopicIF iconTopic) { String filename = getOccurrenceValue(type, filenameTopic); if (filename == null) return null; ImageIcon icon = (ImageIcon) iconCache.get(filename); if (icon == null) { String base64 = getOccurrenceValue(type, iconTopic); if (base64 == null) return null; ByteArrayOutputStream output = new ByteArrayOutputStream(); try { Base64Decoder.decode(base64, output); } catch (IOException e) { throw new OntopiaRuntimeException(e); } icon = new ImageIcon(output.toByteArray()); iconCache.put(filename, icon); } return icon; } public void setTypeIconFilename(TopicIF type, String string) { if (string == null) { removeOccurence(type, typeIconFilename); removeOccurence(type, typeIcon); } else setIcon(type, string, typeIconFilename, typeIcon); } private void setIcon(TopicIF topictype, String string, TopicIF filenameTopic, TopicIF iconTopic) { setOccurenceValue(topictype, filenameTopic, string); ByteArrayOutputStream output = new ByteArrayOutputStream(); try { FileInputStream file = new FileInputStream(string); StreamUtils.transfer(file, output); file.close(); byte[] bytes = output.toByteArray(); ImageIcon icon = new ImageIcon(bytes); iconCache.put(string, icon); ByteArrayInputStream input = new ByteArrayInputStream(bytes); output.reset(); Base64Encoder.encode(input, output); } catch (IOException e) { // should never occur throw new OntopiaRuntimeException("INTERNAL ERROR", e); } try { setOccurenceValue(topictype, iconTopic, output.toString("ISO-8859-1")); } catch (UnsupportedEncodingException e1) { throw new OntopiaRuntimeException(e1); } } public boolean shouldDisplayRoleHoverHelp() { return getOccurrenceValue(generalTopic, showRoleHoverHelp, true); } public boolean isMotionKillerEnabled() { return getOccurrenceValue(generalTopic, enableMotionKiller, true); } public boolean shouldDisplayScopedAssociationNames() { return getOccurrenceValue(generalTopic, displayScopedAssociationNames, true); } public void shouldDisplayRoleHoverHelp(boolean value) { setOccurenceValue(generalTopic, showRoleHoverHelp, value); } public void setMotionKillerEnabled(boolean value) { setOccurenceValue(generalTopic, enableMotionKiller, value); } public void shouldDisplayScopedAssociationNames(boolean value) { setOccurenceValue(generalTopic, displayScopedAssociationNames, value); } public void setPanelBackgroundColour(Color aColor) { setColor(generalTopic, typeColor, aColor); } public Color getPanelBackgroundColour() { Color colour = lookupColor(generalTopic, typeColor); if (colour == null) colour = DEFAULT_PANEL_BACKGROUND_COLOUR; return colour; } public void setGeneralSingleClick(int anAction) { setOccurrenceValue(generalTopic, singleMouseClick, anAction); } public void setGeneralLocalityAlgorithm(int anAction) { setOccurrenceValue(generalTopic, localityAlgorithm, anAction); } public void setMotionKillerDelay(int seconds) { setOccurrenceValue(generalTopic, motionKillerDelay, seconds); } public void setMaxTopicNameLength(int length) { setOccurrenceValue(generalTopic, maxTopicNameLength, length); } public void setGeneralDoubleClick(int anAction) { setOccurrenceValue(generalTopic, doubleMouseClick, anAction); } public int getGeneralDoubleClick() { return getOccurrenceValue(generalTopic, doubleMouseClick, DEFAULT_DOUBLE_CLICK); } public int getGeneralSingleClick() { return getOccurrenceValue(generalTopic, singleMouseClick, DEFAULT_SINGLE_CLICK); } public int getGeneralLocalityAlgorithm() { return getOccurrenceValue(generalTopic, localityAlgorithm, DEFAULT_LOCALITY_ALGORITHM); } public int getGeneralMotionKillerDelay() { return getOccurrenceValue(generalTopic, motionKillerDelay, DEFAULT_KILLER_DELAY); } public int getMaxTopicNameLength() { return getOccurrenceValue(generalTopic, maxTopicNameLength, DEFAULT_MAX_TOPIC_NAME_LENGTH); } public void setTypeIncluded(TopicIF type) { setOccurenceValue(type, typeExcluded, false); } public void setTypeExcluded(TopicIF type) { setOccurenceValue(type, typeExcluded, true); } public boolean isTypeExcluded(TopicIF aType) { return getOccurrenceValue(aType, typeExcluded, false); } public void setScopingTopic(TopicIF scope) { if (scope == null) clearTopic(scopingTopic); else { setTopicReference(scopingTopic, scope, scope.getTopicMap().getStore() .getBaseAddress().getAddress()); } } public void clearStartTopic() { clearTopic(startTopic); } protected TopicIF getSourceLocator() { return sourceLocator; } protected TopicIF getSubject() { return subject; } protected TopicIF getSubjectIndicator() { return subjectIndicator; } public void setInAssociationScopeFilter(TopicIF scope, boolean included) { setOccurenceValue(scope, scopeFilter, included); } public boolean isInAssociationScopeFilter(TopicIF scope) { return getOccurrenceValue(scope, scopeFilter, false); } public void setAssociationScopeFilterStrictness(int strictness) { setOccurrenceValue(scopeFilter, filterStrictness, strictness); } public int getAssociationScopeFilterStrictness() { int retVal = SHOW_ALL_ASSOCIATION_SCOPES; String occurrenceValue = getOccurrenceValue(scopeFilter, filterStrictness); try { if (occurrenceValue != null) retVal = Integer.parseInt(occurrenceValue); } catch (NumberFormatException e) { } return retVal; } /** * @return All topics that are used as scopes of associations and are used to * filter associations in Vizigator. */ public Collection getAssociationScopeFilter() { // Get all topics that have an occurrence of type scopeFilter of value true. Collection retVal = new ArrayList(); // Note: making new ArrayList() to avoid ConcurrentModificationException. Collection topics = new ArrayList(topicmap.getTopics()); for (Iterator topicsIt = topics.iterator(); topicsIt.hasNext();) { TopicIF currentTopic = (TopicIF)topicsIt.next(); if (isInAssociationScopeFilter(currentTopic)) retVal.add(currentTopic); } return retVal; } }