/* * #! * 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.Cursor; import java.awt.Font; import java.awt.Frame; import java.awt.Graphics; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.File; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Set; import java.util.Vector; import javax.swing.Icon; import javax.swing.JDialog; import javax.swing.JOptionPane; import net.ontopia.infoset.core.LocatorIF; import net.ontopia.topicmaps.core.OccurrenceIF; import net.ontopia.topicmaps.core.TopicIF; import net.ontopia.topicmaps.core.TopicMapIF; import net.ontopia.topicmaps.core.TopicMapReaderIF; import net.ontopia.topicmaps.impl.remote.RemoteTopic; import net.ontopia.topicmaps.impl.remote.RemoteTopicMapStore; import net.ontopia.topicmaps.utils.ImportExportUtils; import net.ontopia.topicmaps.utils.TopicStringifiers; import net.ontopia.topicmaps.utils.tmrap.RemoteTopicIndex; import net.ontopia.topicmaps.utils.tmrap.TopicPage; import net.ontopia.utils.OntopiaRuntimeException; import net.ontopia.utils.CollectionUtils; import net.ontopia.utils.StringifierIF; import net.ontopia.utils.URIUtils; import com.touchgraph.graphlayout.Node; import com.touchgraph.graphlayout.TGPaintListener; import com.touchgraph.graphlayout.TGPanel; import com.touchgraph.graphlayout.graphelements.Locality; import java.util.HashMap; import java.util.Map; /** * INTERNAL: The VizController manages the interaction between the * gui, model and configuration manager. */ public class VizController { private TopicMapView view; // null if no TM loaded private ApplicationContextIF appContext; private VizTopicMapConfigurationManager tmConfig; private VizGeneralConfigurationManager generalConfig; private VizPanel vpanel; private PropertiesFrame propertiesFrame; private boolean ignoreStateChangedEvent = false; private boolean assocTypesLoaded = false; private boolean topicTypesLoaded = false; private static final String SHORT_NAME = "http://psi.ontopia.net/basename/" + "#short-name"; private StringifierIF stringifier; private VizHoverHelpManager hoverHelpManager; private HighlightNode highlightNode; // This KeyInputManager is never referenced, but it handles keyboard inputs. private KeyInputManager keyMan; protected UndoManager undoManager; protected boolean showNeighbouringCircle = false; protected boolean showNeighboursOnMouseover = false; public VizController(VizPanel vpanel, VizFrontEndIF vizFrontEnd, TGPanel aTgPanel) { undoManager = new UndoManager(this); this.vpanel = vpanel; hoverHelpManager = new VizHoverHelpManager(aTgPanel); appContext = vizFrontEnd.getContext(); appContext.setVizPanel(vpanel); if (vizFrontEnd.useGeneralConfig()) { File configFile = getGeneralConfigurationFile(); if (!configFile.exists()) { generalConfig = new VizGeneralConfigurationManager(); } else { try { generalConfig = new VizGeneralConfigurationManager(configFile); } catch (IOException e) { ErrorDialog.showError(vpanel, Messages.getString("Viz.ErrorLoadingConfig"), e); generalConfig = new VizGeneralConfigurationManager(); } } } String configurl = null; try { configurl = vizFrontEnd.getConfigURL(); if (configurl != null) { tmConfig = new VizTopicMapConfigurationManager(configurl); } } catch (MalformedURLException mue) { ErrorDialog.showError(vpanel, Messages.getString("Viz.ErrorLoadingConfig"), mue); } catch (IOException ioe) { ErrorDialog.showError(vpanel, Messages.getString("Viz.ErrorLoadingConfig"), ioe); } finally { // if an error occurred, or there is no config parameter, then create a default one if(tmConfig == null) { tmConfig = new VizTopicMapConfigurationManager(); } } appContext.setTmConfig(tmConfig); if (vizFrontEnd.mapPreLoaded()) { view = new TopicMapView(this, vizFrontEnd.getTopicMap(), aTgPanel, tmConfig); appContext.setView(view); } highlightNode = new HighlightNode(this); keyMan = new KeyInputManager(this); if (vizFrontEnd.mapPreLoaded()) focusStartTopicInternal(); } public VizPanel getVizPanel() { return vpanel; } public TopicMapView getView() { return view; } private File getGeneralConfigurationFile() { String dir = System.getProperty( "net.ontopia.topicmaps.viz.home", System.getProperty("user.home", ".")); return new File(dir + File.separator + "vizconf.xtm"); } public TopicIF getDefaultScopingTopic(TopicMapIF topicmap) { TopicIF scope = appContext.getDefaultScopingTopic(topicmap); if (scope == null) scope = appContext.getTopicForLocator(VizUtils.makeLocator(SHORT_NAME), topicmap); return scope; } public boolean hasTopicMap() { return view != null; } public boolean isApplet() { return appContext.isApplet(); } // --- model introspection ----------------------------------------------- public Collection getAssociationTypes() { return view.getAssociationTypes(); } public Collection getAllTopicTypesWithNull() { return view.getAllTopicTypesWithNull(); } public Collection getAllTopicTypes() { return view.getAllTopicTypes(); } // --- view introspection ------------------------------------------------ public boolean isAssocTypeVisible(TopicIF type) { return tmConfig.isAssociationTypeVisible(type); } public boolean isTopicTypeVisible(TopicIF type) { return tmConfig.isTopicTypeVisible(type); } public Color getTopicTypeColor(TopicIF type) { return tmConfig.getTopicTypeColor(type); } public TopicMapIF getTopicMap() { return view.getTopicMap(); } public int getTopicTypeShape(TopicIF type) { return tmConfig.getTopicTypeShape(type); } public Color getAssociationTypeColor(TopicIF type) { return tmConfig.getAssociationTypeColor(type); } public TopicIF getStartTopic() { return getStartTopic(view.getTopicMap()); } public TopicIF getStartTopic(TopicMapIF aTopicmap) { return appContext.getStartTopic(aTopicmap); } // --- view modifications ------------------------------------------------ public void setLocality(int locality) { headedDebug("setLocality ------------- before ------- locality: " + locality, null); undoManager.startOperation(new DoSetLocality(this, locality)); // IDM 13-09-04 // At startup, when there is no TopicMap loaded, the variable // "view" is null causing a NullPointerException error in the // original the code below. I have inserted a null check just // to get around this. However maybe this should be resolved in // another way, such as disabling the spinner when there is no // TopicMap loaded ? if (view == null) return; view.setLocality(locality); updateDisplayLazily(); undoManager.completeOperation(); headedDebug("setLocality ------------- after ------- locality: " + locality, null); } /** * Required because the colour chooser will trigger the setTypeColor method * when the selected is colour is changed by Vizigator (no user input). * In this case the colour view and configuration should not be changed. */ public boolean getIgnoreStateChangedEvent() { return ignoreStateChangedEvent; } public void setIgnoreStateChangedEvent(boolean ignoreStateChangedEvent) { this.ignoreStateChangedEvent = ignoreStateChangedEvent; } public void updateViewTypeColor(TopicIF type, Color color) { view.setTypeColor(type, color); repaint(); } public void updateViewType(TopicIF type) { view.updateType(type); } public void setColorToDefault(TopicIF type, boolean topicType) { tmConfig.setColorToDefault(type, topicType, view); } public void setTypeColor(TopicIF type, Color c) { if (!getIgnoreStateChangedEvent()) { tmConfig.setTypeColor(type, c, view); repaint(); } } /** * Set the given node to be the focus node. This method should only be called * from the user interface, and should not be used to implement other * operations. * To implement other operations, use focusNodeInternal(TMAbstractNode). * @param node The new focus node. */ public void focusNode(TMAbstractNode node) { headedDebug("focusNode - before", node); view.stat.init(); view.stat1.init(); undoManager.startOperation(new DoFocusNode(this, (NodeRecoveryObjectIF)node.getRecreator())); focusNodeInternal(node); updateDisplay(); view.stat.report(); view.stat1.report(); undoManager.completeOperation(); headedDebug("focusNode - after", node); } /** * Set the given node to be the focus node. This method should only be used * for the internal working of other operations, like * focusNode(TMAbstractNode), and should not be called directly from the user * interface. * @param node The new focus node. */ public void focusNodeInternal(TMAbstractNode node) { TMAbstractNode focusNode = getFocusNode(); if (focusNode != null) { SetFocusNode setFocusNode = new SetFocusNode((NodeRecoveryObjectIF) focusNode.getRecreator()); undoManager.addRecovery(setFocusNode); } // It is possible to get here before the view has been completely // generated and assigned to its instance variable. if (view != null) { vpanel.clearSearchResults(); appContext.focusNode(node); } updateDisplay(); } /** * Sets all nodes to fixed (sticky) or not fixed. * @param fixed true(/false) if all nodes should get a (un)fixed position. */ public void setAllNodesFixed(boolean fixed) { view.setAllNodesFixed(fixed); // Note: resetDamper is sufficient. No call to updateDisplay is needed since // no Nodes or edges were added, filtered or removed. view.getTGPanel().resetDamper(); } public TMAbstractNode getFocusNode() { return view.getFocusNode(); } public void setStartTopic(TopicIF topic) { if (view == null) return; appContext.setStartTopic(topic); } public void clearStartTopic() { if (view != null) tmConfig.clearStartTopic(); } // --- environment actions ----------------------------------------------- /** * Opens the supplied url string in a browser window. Which window is used is * defined by the 'propTarget' applet parameter * * @param url String representing the target url */ public void openPropertiesURL(String url) { appContext.openPropertiesURL(url); } public void goToTopic(TopicIF topic) { appContext.goToTopic(topic); } public void saveTopicMapConfiguration(File file) throws IOException { tmConfig.save(file); } // --- old code ----------------------------------------------------- public void loadConfig(File f) throws IOException { try { vpanel.setCursor(new Cursor(Cursor.WAIT_CURSOR)); tmConfig = new VizTopicMapConfigurationManager(f); view.setConfigManager(tmConfig); } finally { vpanel.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } } public TopicMapIF loadTopicMap(File f) throws IOException { return loadTopicMap(f, null); } // this method exists because it allows us to build a correctly // configured view directly public TopicMapIF loadTopicMap(File tmfile, File cfgfile) throws IOException { TopicMapIF topicmap; try { vpanel.setCursor(new Cursor(Cursor.WAIT_CURSOR)); vpanel.clearSearchResults(); TopicMapReaderIF reader = ImportExportUtils.getReader(tmfile); Map<String, Object> properties = new HashMap<String, Object>(3); properties.put("lenient", true); properties.put("generateNames", true); String mappingFile = generalConfig.getRDFMappingFile(); if (mappingFile != null) { properties.put("mappingFile", new File(mappingFile)); } reader.setAdditionalProperties(properties); topicmap = importTopicMap(reader, tmfile.getName()); if (topicmap != null) { if (cfgfile == null) tmConfig = new VizTopicMapConfigurationManager(); else tmConfig = new VizTopicMapConfigurationManager(cfgfile); // Remove all paintListeners hoverHelpManager.resetPainters(); view = new TopicMapView(this, topicmap, vpanel.getTGPanel(), tmConfig); appContext.setView(view); setScopingTopic(getDefaultScopingTopic(topicmap)); int locality = getDefaultLocality(); VizDebugUtils.debug("loadTopicMap(tmfile, cfgfile) - " + "setting locality: " + locality); vpanel.setLocality(locality); VizDebugUtils.debug("loadTopicMap(tmfile, cfgfile) - " + "have set locality: " + locality); view.build(); initializeMotionKillerEnabled(); } } finally { vpanel.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } headedDebug("loadTopicMap - after (only)", null); return topicmap; } public TopicMapIF loadTopicMap(TopicMapIF topicMap) throws IOException { return loadTopicMap(topicMap, null); } // this method exists because it allows us to build a correctly // configured view directly public TopicMapIF loadTopicMap(TopicMapIF topicmap, File cfgfile) throws IOException { try { vpanel.setCursor(new Cursor(Cursor.WAIT_CURSOR)); vpanel.clearSearchResults(); if (topicmap != null) { if (cfgfile == null) tmConfig = new VizTopicMapConfigurationManager(); else tmConfig = new VizTopicMapConfigurationManager(cfgfile); // Remove all paintListeners hoverHelpManager.resetPainters(); view = new TopicMapView(this, topicmap, vpanel.getTGPanel(), tmConfig); appContext.setView(view); setScopingTopic(getDefaultScopingTopic(topicmap)); int locality = getDefaultLocality(); VizDebugUtils.debug("loadTopicMap(tm, cfgfile) - setting locality: " + locality); vpanel.setLocality(locality); view.build(); initializeMotionKillerEnabled(); } } finally { vpanel.setCursor(new Cursor(Cursor.DEFAULT_CURSOR)); } return topicmap; } public int getDefaultLocality() { return appContext.getDefaultLocality(); } public int getMaxLocality() { return appContext.getMaxLocality(); } protected void setHighlightNode(TMAbstractNode node, Graphics g) { if (highlightNode != null) highlightNode.setNode(node, g); } private TopicMapIF importTopicMap(TopicMapReaderIF reader, String name) { final JOptionPane pane = new JOptionPane(new Object[] { Messages .getString("Viz.LoadingTopicMap") + name }, JOptionPane.INFORMATION_MESSAGE, JOptionPane.DEFAULT_OPTION, null, new String[] {}, null); Frame frame = JOptionPane.getFrameForComponent(vpanel); final JDialog dialog = new JDialog(frame, Messages .getString("Viz.Information"), true); dialog.setContentPane(pane); dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); dialog.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent we) { JOptionPane .showMessageDialog(pane, Messages.getString("Viz.CannotCancelOperation"), Messages.getString("Viz.Information"), JOptionPane.INFORMATION_MESSAGE); } }); dialog.pack(); dialog.setLocationRelativeTo(frame); TopicMapIF tm; final TopicMapReaderIF r = reader; final SwingWorker worker = new SwingWorker() { public Object construct() { TopicMapIF result = null; try { result = r.read(); } catch (IOException e) { dialog.setVisible(false); ErrorDialog.showError(vpanel, e.getMessage()); } return result; } public void finished() { dialog.setVisible(false); } }; worker.start(); dialog.setVisible(true); tm = (TopicMapIF) worker.getValue(); return tm; } public VizTopicMapConfigurationManager getConfigurationManager() { return tmConfig; } public void setTopicTypeVisibility(TopicIF type, int visibility) { headedDebug("setTopicTypeVisible - before, type: " + type + ", visible: " + visibility, type); undoManager.startOperation(new DoSetTTVisibility(type, visibility)); DoSetTTVisibilityState recovery = new DoSetTTVisibilityState(type, tmConfig.getTypeVisibility(type)); undoManager.addRecovery(recovery); tmConfig.setTypeVisibility(type, visibility, view); updateDisplay(); repaint(); undoManager.completeOperation(); headedDebug("setTopicTypeVisible - after", type); } public void setAssociationTypeVisibility(TopicIF type, int visibility) { headedDebug("setAssociationTypeVisible - before, visible: " + visibility, type); undoManager.startOperation(new DoSetATVisibility(type, visibility)); DoSetATVisibilityState recovery = new DoSetATVisibilityState(type, tmConfig.getTypeVisibility(type)); undoManager.addRecovery(recovery); tmConfig.setAssociationTypeVisible(type, visibility, view); updateDisplay(); repaint(); undoManager.completeOperation(); headedDebug("setAssociationTypeVisible - after, visible: " + visibility, type); } // FIXME: SHOULD BECOME REDUNDANT. THEN REMOVE. public void setTopicTypeVisible(TopicIF type, boolean visible) { headedDebug("setTopicTypeVisible - before, type: " + type + ", visible: " + visible, type); tmConfig.setTypeVisible(type, visible, view); updateDisplay(); repaint(); headedDebug("setTopicTypeVisible - after", type); } // FIXME: SHOULD BECOME REDUNDANT. THEN REMOVE. public void setAssociationTypeVisible(TopicIF type, boolean visible) { headedDebug("setAssociationTypeVisible - before, visible: " + visible, type); tmConfig.setAssociationTypeVisible(type, visible, view); if (visible) view.loadNodesInLocality(view.getFocusNode(), true, false); repaint(); updateDisplay(); headedDebug("setAssociationTypeVisible - after, visible: " + visible, type); } public void goToMapView() { headedDebug("goToMapView - before", null); view.clearFocusNode(); // This will unmark all nodes that were scheduled to have their hidden // association recounted. That's o.k. since all these numbers reset to 0 // in 'view.clearFocusNode()'. view.updateDisplayNoWork(); headedDebug("goToMapView - after", null); } // --- Internals private void repaint() { vpanel.repaint(); } public void setTopicTypeShape(TopicIF type, int i) { tmConfig.setTopicTypeShape(type, i, view); repaint(); } public void setAssociationTypeShape(TopicIF type, int i) { tmConfig.setAssociationTypeShape(type, i, view); repaint(); } public void setFontToDefault(TopicIF type, boolean topicType) { tmConfig.setFontToDefault(type, topicType, view); } public void setTypeFont(TopicIF type, Font font) { tmConfig.setTypeFont(type, font, view); repaint(); } public void setAssociationTypeLineWeight(TopicIF type, int i) { tmConfig.setAssociationTypeLineWeight(type, i, view); repaint(); } public void setTopicTypeShapePadding(TopicIF type, int i) { tmConfig.setTopicTypeShapePadding(type, i, view); repaint(); } public int getTypeVisibility(TopicIF selectedType) { return tmConfig.getTypeVisibility(selectedType); } public int getAssoicationTypeShape(TopicIF selectedType) { return tmConfig.getAssociationTypeShape(selectedType); } public int getAssoicationTypeLineWeight(TopicIF selectedType) { return tmConfig.getAssociationTypeLineWeight(selectedType); } public int getTopicTypeShapePadding(TopicIF selectedType) { return tmConfig.getTopicTypeShapePadding(selectedType); } public String getTypeIconFilename(TopicIF selectedType) { return tmConfig.getTypeIconFilename(selectedType); } public Icon getTypeIcon(TopicIF selectedType) { return tmConfig.getTypeIcon(selectedType); } public Font getTypeFont(TopicIF selectedType) { return tmConfig.getTypeFont(selectedType); } public void setTypeIconFilename(TopicIF type, String string) { tmConfig.setTypeIconFilename(type, string, view); repaint(); } public void openProperties(TMTopicNode node) { if (propertiesFrame == null) { propertiesFrame = new PropertiesFrame(this); propertiesFrame.setVisible(true); } propertiesFrame.setTarget(node.getTopic()); propertiesFrame.setVisible(true); propertiesFrame.toFront(); } public void shouldDisplayRoleHoverHelp(boolean newValue) { tmConfig.shouldDisplayRoleHoverHelp(newValue); view.shouldDisplayRoleHoverHelp(newValue); } public void initializeMotionKillerEnabled() { setMotionKillerEnabled(getConfigurationManager().isMotionKillerEnabled()); vpanel.enableDisableMotionKillerMenuItem(getConfigurationManager() .isMotionKillerEnabled()); } public void setMotionKillerEnabled(boolean newValue) { tmConfig.setMotionKillerEnabled(newValue); view.setMotionKillerEnabled(newValue); vpanel.updateEnableMotionKillerMenuItem(); } public void shouldDisplayScopedAssociationNames(boolean newValue) { tmConfig.shouldDisplayScopedAssociationNames(newValue); view.shouldDisplayScopedAssociationNames(newValue); } public void setPanelBackgroundColour(Color aColor) { tmConfig.setPanelBackgroundColour(aColor); view.setPanelBackgroundColour(aColor); } public void setGeneralSingleClick(int anAction) { tmConfig.setGeneralSingleClick(anAction); } public void setGeneralLocalityAlgorithm(int anAction) { tmConfig.setGeneralLocalityAlgorithm(anAction); focusNode(getFocusNode()); } public void setMotionKillerDelay(int seconds) { tmConfig.setMotionKillerDelay(seconds); view.motionKiller.setMaxCycle(seconds); } public void setGeneralDoubleClick(int anAction) { tmConfig.setGeneralDoubleClick(anAction); } public void setMaxTopicNameLength(int length) { tmConfig.setMaxTopicNameLength(length); view.setMaxTopicNameLength(length); } public void setTypeIncluded(TopicIF type) { tmConfig.setTypeIncluded(type, view); repaint(); } public void setTypeExcluded(TopicIF type) { headedDebug("setTypeExcluded - before", type); tmConfig.setTypeExcluded(type, view); repaint(); headedDebug("setTypeExcluded - after", type); } public List performSearch(String searchString) { if (view != null) return view.performSearch(searchString); return Collections.EMPTY_LIST; } public void loadNode(TMAbstractNode node) { view.stat.startOp(); view.createAssociations(node); view.stat.stopOp(); } public void outputDebugInfo(String operation) { if (view == null) return; view.outputDebugInfo(operation); } public void expandNode(TMAbstractNode node) { headedDebug("expandNode - before", node); undoManager.startOperation(new DoExpandNode(this, (NodeRecoveryObjectIF)node.getRecreator())); // HACK: make sure that we load expanded nodes and related. This code should // possibly go somewhere else. if (node instanceof TMTopicNode) { TopicIF topic = ((TMTopicNode)node).getTopic(); if (topic instanceof net.ontopia.topicmaps.impl.remote.RemoteTopic) { RemoteTopicMapStore store = (RemoteTopicMapStore)topic.getTopicMap().getStore(); RemoteTopicIndex tindex = store.getTopicIndex(); tindex.loadRelatedTopics(topic.getSubjectIdentifiers(), topic.getItemIdentifiers(), topic.getSubjectLocators(), false); // only 1 step out, not 2 } } loadNode(node); view.getTGPanel().expandNode(node); updateDisplay(); undoManager.completeOperation(); headedDebug("expandNode - after", node); } public StringifierIF getStringifier() { return (stringifier == null ? TopicStringifiers.getDefaultStringifier() : stringifier); } public void saveGeneralConfiguration() throws IOException { generalConfig.save(getGeneralConfigurationFile()); } public void updateRecentFiles(File f) { generalConfig.updateRecentFiles(f); } public List getRecentFiles() { return generalConfig.getRecentFiles(); } public String getRdfMappingFile() { return generalConfig.getRDFMappingFile(); } public void setRdfMappingFile(File file) throws IOException { generalConfig.setRdfMappingFile(file); saveGeneralConfiguration(); } public String getCurrentTMDir() { return generalConfig.getCurrentTMDir(); } public void setCurrentTMDir(String currentTMDir) throws IOException { generalConfig.setCurrentTMDir(currentTMDir); saveGeneralConfiguration(); } public String getCurrentRDBMSDir() { return generalConfig.getCurrentRDBMSDir(); } public void setCurrentRDBMSDir(String dir) throws IOException { generalConfig.setCurrentRDBMSDir(dir); saveGeneralConfiguration(); } public String getCurrentConfigDir() { return generalConfig.getCurrentConfigDir(); } public void setCurrentConfigDir(String dir) throws IOException { generalConfig.setCurrentConfigDir(dir); saveGeneralConfiguration(); } public void loadTopic(TopicIF aTopic) { appContext.loadTopic(aTopic); } public void collapseNode(TMAbstractNode node) { TGPanel panel = view.getTGPanel(); undoManager.startOperation(new DoCollapseNode(this, (NodeRecoveryObjectIF)node.getRecreator())); TMAbstractNode target = (TMAbstractNode) panel.getSelect(); if (node == target) view.hideNode(node); else if (target == null) // If we are in Map view, use default behaviour panel.collapseNode(node); else { // If there is a focus node, collapse all links that are not // connected to the focus node. Vector hidden = getOrphanedNodes(node, target); ((Locality) panel.getGES()).removeNodes(hidden); updateDisplay(); } undoManager.completeOperation(); } private Vector getOrphanedNodes(TMAbstractNode node, TMAbstractNode target) { Vector hidden = new Vector(); for (Iterator edges = node.getVisibleEdges(); edges.hasNext();) { TMAbstractEdge edge = (TMAbstractEdge) edges.next(); TMAbstractNode neighbour = (TMAbstractNode) edge.getOtherEndpt(node); HashSet visited = new HashSet(); visited.add(node); if (!neighbour.hasPathTo(target, visited)) addToHidden(neighbour, node, hidden); } return hidden; } protected void addToHidden(TMAbstractNode target, Node source, Vector hidden) { if (hidden.contains(target)) return; hidden.add(target); for (Iterator edges = target.getVisibleEdges(); edges.hasNext();) { TMAbstractEdge edge = (TMAbstractEdge) edges.next(); TMAbstractNode neighbour = (TMAbstractNode) edge.getOtherEndpt(target); if (!neighbour.equals(source)) addToHidden(neighbour, source, hidden); } } public void focusStartTopic() { if (view == null) return; focusNode(view.getStartNode()); } public void focusStartTopicInternal() { if (view == null) return; focusNodeInternal(view.getStartNode()); } protected VizHoverHelpManager getHoverHelpManager() { return hoverHelpManager; } public void setScopingTopic(TopicIF aScope) { stringifier = VizUtils.stringifierFor(aScope); tmConfig.setScopingTopic(aScope); view.setScopingTopic(aScope); appContext.setScopingTopic(aScope); } /** * Configure the given AssociationScopeFilterMenu * @param menu The menu to configure. * @param topicmap The topicmap filtered by the filter of the menu. * @param parentListener Listens for actions on the items in the menu. */ public void configure(AssociationScopeFilterMenu menu, TopicMapIF topicmap, ActionListener parentListener) { menu.configure(topicmap, parentListener, this); } public void setInAssociationScopeFilter(TopicIF scope, boolean useInFilter) { headedDebug("setInAssociationScopeFilter - before - useInFilter: " + useInFilter, scope); undoManager.startOperation(new DoSetInASFilter(scope, useInFilter)); DoSetInASFilterState recovery = new DoSetInASFilterState(scope, tmConfig.isInAssociationScopeFilter(scope)); undoManager.addRecovery(recovery); tmConfig.setInAssociationScopeFilter(scope, useInFilter); if (useInFilter) { view.addAssociationScopeFilterTopic(scope); } else view.removeAssociationScopeFilterTopic(scope); updateDisplay(); repaint(); undoManager.completeOperation(); headedDebug("setInAssociationScopeFilter - after - useInFilter: " + useInFilter, scope); } /** * Output debug info with a header for the method, provided debug is enabled. */ private void headedDebug(String header, Object o) { if (view == null) return; view.headedDebug(header, o); view.debug.integrityCheck(); } public void setAssociationScopeFilterStrictness(int strictness) { headedDebug("setAssociationScopeFilterStrictness - before - strictness: " + strictness, null); undoManager.startOperation(new DoSetASStrictness(strictness)); DoSetASStrictnessState recovery = new DoSetASStrictnessState(tmConfig .getAssociationScopeFilterStrictness()); undoManager.addRecovery(recovery); tmConfig.setAssociationScopeFilterStrictness(strictness); view.setAssociationScopeFilterStrictness(strictness); updateDisplay(); undoManager.completeOperation(); headedDebug("setAssociationScopeFilterStrictness - after - strictness: " + strictness, null); } public int getAssociationScopeFilterStrictness() { return tmConfig.getAssociationScopeFilterStrictness(); } public boolean isInAssociationScopeFilter(TopicIF scope) { return tmConfig.isInAssociationScopeFilter(scope); } public TypesConfigFrame getTopicFrame() { return appContext.getTopicFrame(); } public TypesConfigFrame getAssocFrame() { return appContext.getAssocFrame(); } public void hideEdge(TMAbstractEdge edge) { headedDebug("hideEdge - before", edge); undoManager.startOperation(new DoHideEdge(this, (EdgeRecoveryObjectIF)edge.getRecreator())); view.deleteSingleEdge(edge); updateDisplayLazily(); undoManager.completeOperation(); headedDebug("hideEdge - after", edge); } public void undo() { headedDebug("undo - before", null); undoManager.undo(); // FIXME: Consider marking nodes, so display can be updated lazily. updateDisplay(); headedDebug("undo - after", null); } public boolean canUndo() { return undoManager.canUndo(); } public boolean canRedo() { return undoManager.canRedo(); } public void redo() { headedDebug("redo - before", null); undoManager.redo(); // FIXME: Consider marking nodes, so display can be updated lazily. updateDisplay(); headedDebug("redo - after", null); } private void updateDisplay() { view.updateDisplay(); } private void updateDisplayLazily() { view.updateDisplayLazily(); } /** * Delete a node, all incident edges and all nodes and edges that no longer * have a path to the focus node as a consequence of this. * @param node The base node to delete. */ public void hideNode(TMAbstractNode node) { headedDebug("hideNode - before", node); undoManager.startOperation(new DoHideNode(this, (NodeRecoveryObjectIF)node.getRecreator())); view.hideNode(node); // Lazy update is o.k. in both map view and topic view. // For details, see view.hideNode(node). updateDisplayLazily(); undoManager.completeOperation(); headedDebug("hideNode - after", node); } /** * Stops the motion of all nodes completely. */ public void stopMovingNodes() { TGPanel panel = view.getTGPanel(); panel.stopMotion(); panel.stopMotion(); } /** * Enables/disables the motion killer. * Note: VizPanel uses the value of enabled to build menus, so this method * should only be changed (indirectly) from there. */ public void enableMotionKiller(boolean enable) { view.motionKiller.setEnabled(enable); } public boolean isMotionKillerEnabled() { return view.motionKiller.getEnabled(); } /** * Enables/disables animation. * Note: VizPanel uses the value of enabled to build menus, so this method * should only be changed (indirectly) from there. */ public void enableAnimation(boolean enable) { view.vizigatorUser.setEnabled(enable); } public boolean isAnimationEnabled() { return view.vizigatorUser.getEnabled(); } public ParsedMenuFile getEnabledItemIds() { ParsedMenuFile enabledItemIds = appContext.getEnabledItemIds(); VizDebugUtils.debug("VizController.getEnabledItemIds() - enabledItemIds" + enabledItemIds); return enabledItemIds; } public void loadAssociationTypes() { if (!appContext.isApplet()) return; // no loading to do on desktop if (assocTypesLoaded) return; try { TopicMapIF topicmap = view.getTopicMap(); RemoteTopicMapStore store = (RemoteTopicMapStore) topicmap.getStore(); RemoteTopicIndex tindex = store.getTopicIndex(); tindex.loadAssociationTypes(topicmap); } catch (IOException e) { throw new OntopiaRuntimeException(e); } assocTypesLoaded = true; } public void loadTopicTypes() { if (!appContext.isApplet()) return; // no loading to do on desktop if (topicTypesLoaded) return; try { TopicMapIF topicmap = view.getTopicMap(); RemoteTopicMapStore store = (RemoteTopicMapStore) topicmap.getStore(); RemoteTopicIndex tindex = store.getTopicIndex(); tindex.loadTopicTypes(topicmap); } catch (IOException e) { throw new OntopiaRuntimeException(e); } topicTypesLoaded = true; } // ----------- Nested classes and interfaces ------------- /** * INTERNAL: Hover Help Manager */ protected class VizHoverHelpManager extends Object implements TGPaintListener { protected ArrayList painters; protected VizHoverHelpManager(TGPanel panel) { panel.addPaintListener(this); resetPainters(); } protected void resetPainters() { painters = new ArrayList(); } public void paintFirst(Graphics g) { // Do it this way so that we do not get concurrent modification // errors when loading large topic maps for (int i = 0; i < painters.size(); i++) ((TGPaintListener) painters.get(i)).paintFirst(g); } public void addPaintListener(TGPaintListener aListener) { painters.add(aListener); } public void paintAfterEdges(Graphics g) { // Do it this way so that we do not get concurrent modification // errors when loading large topic maps for (int i = 0; i < painters.size(); i++) ((TGPaintListener) painters.get(i)).paintAfterEdges(g); } public void paintLast(Graphics g) { // Do it this way so that we do not get concurrent modification // errors when loading large topic maps for (int i = 0; i < painters.size(); i++) ((TGPaintListener) painters.get(i)).paintLast(g); } public void removePaintListener(TGPaintListener aListener) { painters.remove(aListener); } } }