net.ontopia.topicmaps.viz.VizTopicMapConfigurationManager.java Source code

Java tutorial

Introduction

Here is the source code for net.ontopia.topicmaps.viz.VizTopicMapConfigurationManager.java

Source

/*
 * #!
 * 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 com.touchgraph.graphlayout.Node;
import java.awt.Color;
import java.awt.Font;
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.net.URL;
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.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 org.apache.commons.codec.binary.Base64;
import org.apache.commons.io.IOUtils;

/**
 * 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(URL tmurl) throws IOException {
        super(tmurl);
        setupPriorityManager();
    }

    /**
     * Creates an empty configuration manager where everything is set to default.
     */
    public VizTopicMapConfigurationManager() {
        super();
        setupPriorityManager();
    }

    @Override
    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 ("true".equals(visibility))
            return VizTopicMapConfigurationManager.FILTER_IN;
        if ("false".equals(visibility))
            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;

            icon = new ImageIcon(Base64.decodeBase64(base64));
            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);
            IOUtils.copy(file, output);
            file.close();
            byte[] bytes = output.toByteArray();
            ImageIcon icon = new ImageIcon(bytes);
            iconCache.put(string, icon);
            output.reset();
            output.write(Base64.encodeBase64(bytes));
        } 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;
    }
}