es.ucm.fdi.clover.view.ClusterView.java Source code

Java tutorial

Introduction

Here is the source code for es.ucm.fdi.clover.view.ClusterView.java

Source

/**
 * AC - A source-code copy detector
 *
 *     For more information please visit:  http://github.com/manuel-freire/ac
 *
 * ****************************************************************************
 *
 * This file is part of AC, version 2.0
 *
 * AC is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the
 * Free Software Foundation, either version 3 of the License,
 * or (at your option) any later version.
 *
 * AC is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with AC.  If not, see <http://www.gnu.org/licenses/>.
 */

/*
 * BaseView.java
 *
 * Created on May 6, 2006, 8:55 PM
 * Original Author: Manuel Freire (manuel.freire@uam.es)
 */

package es.ucm.fdi.clover.view;

import es.ucm.fdi.clover.event.ClusteringChangeEvent;
import es.ucm.fdi.clover.event.ClusteringChangeListener;
import es.ucm.fdi.clover.model.Cluster;
import es.ucm.fdi.clover.model.ClusterHierarchy;
import es.ucm.fdi.clover.model.ClusteredGraph;
import es.ucm.fdi.clover.model.Slice;
import es.ucm.fdi.clover.model.ViewGraph;
import java.awt.Event;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseMotionAdapter;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.StringTokenizer;
import org.apache.log4j.Logger;

import es.ucm.fdi.clover.model.BaseGraph;
import java.awt.event.ActionEvent;

import java.util.ArrayList;
import javax.swing.AbstractAction;
import javax.swing.KeyStroke;
import org.jdom2.Element;
import org.jgraph.graph.AttributeMap;
import org.jgraph.graph.DefaultGraphCell;
import org.jgraph.graph.DefaultGraphModel;

/**
 * A visual component that represents a ClusterGraph. It includes some simple
 * interactions: cluster expansion/collapse, aura, layout history, and more.
 *
 * @author mfreire
 */
public class ClusterView extends BaseView implements ClusteringChangeListener {

    private Logger log = Logger.getLogger(ClusterView.class);

    /** vertices that are currently frozen (ie: wont be hidden or expanded by poi changes) */
    private HashSet frozen;

    /** focus size - area around the focus that will be expanded to same detail as focus */
    private int focusSize;

    /** max vertices in simultaneous view */
    private int maxClusters;

    /** if set, clustering will not change when mouse selects node */
    private boolean clusterLock;

    /** used to catch clicks and do things with them (such as change the POI) */
    private VertexCellClickListener vertexClickListener;

    /** used to highlight would-be-expanded nodes */
    private AuraMouseListener auraMouseListener;

    /** can be used to bypass the AuraMouseListener and highlight stuff directly */
    private Highlighter highlighter;

    /** a stack with the history for expansions&collapses followed until this point */
    private ArrayList<ClusteringChangeEvent> clusterNavHistory;

    /** the current point in the stack (= the index of the last event that was carried out) */
    private int currentNavIndex = 0;

    /**
     * Restore from saved JDom element
     */
    public ClusterView(ViewGraph viewGraph) {
        super(viewGraph);
        highlighter = new Highlighter(this);
        auraMouseListener = new AuraMouseListener();
        vertexClickListener = new VertexCellClickListener();
        addMouseListener(vertexClickListener);
        addMouseMotionListener(auraMouseListener);
        focusSize = 1;
        frozen = new HashSet();
        maxClusters = 16;
        clusterLock = false;
        clusterNavHistory = new ArrayList<ClusteringChangeEvent>();
        currentNavIndex = -1;

        // DEBUG ACTION TO DUMP HIERARCHY 
        this.getInputMap().put(KeyStroke.getKeyStroke('c'), "save-clustering");
        this.getActionMap().put("save-clustering", new AbstractAction() {
            public void actionPerformed(ActionEvent e) {
                System.err.println("Dumping out: " + ((ClusteredGraph) getBase()).getHierarchy().getRoot().dump());
            }
        });
    }

    public ClusterHierarchy geHierarchy() {
        return ((ClusteredGraph) getBase()).getHierarchy();
    }

    public void setBase(BaseGraph base) {

        if (!(base instanceof ClusteredGraph)) {
            throw new IllegalArgumentException("Base must be a ClusteredGraph");
        }

        // register for clustering nav events notification (to be used for undo/redo)
        if (getBase() != null) {
            ((ClusteredGraph) getBase()).removeClusteringChangeListener(this);
        }

        // perform first expansion
        ClusteredGraph cg = (ClusteredGraph) base;
        if (frozen == null) {
            frozen = new HashSet();
        }
        setCurrentPoI(cg.getHierarchy().getRoot().getFirstLeafVertex());
        cg.addClusteringChangeListener(this);

        setModel(new DefaultGraphModel());
        // this is where the base is actually set
        viewGraph.setBase(base);
        setModel(getViewGraph().getModelAdapter());

        if (animator == null) {
            animator = new ClusterAnimator(this);
        } else {
            animator.setView(this);
        }
    }

    public Slice getSlice() {
        return ((ClusteredGraph) getBase()).getSlice();
    }

    public ClusteringChangeEvent getPoIChangeEventFor(Object vertex) {
        if (vertex == null)
            return null;
        ClusteredGraph cg = (ClusteredGraph) getBase();
        ClusteringChangeEvent cce = cg.createPoIChangeEvent(vertex, frozen, getFocusSize(), getMaxClusters());
        return cce;
    }

    /**
     * Locks/Unlocks clustering changes from happening. Default is unlocked.
     */
    public void setClusterLock(boolean clusterLock) {
        if (this.clusterLock != clusterLock) {
            if (clusterLock) {
                removeMouseMotionListener(auraMouseListener);
            } else {
                addMouseMotionListener(auraMouseListener);
            }
        }
        this.clusterLock = clusterLock;
    }

    public boolean isClusterLock() {
        return clusterLock;
    }

    public boolean isFrozen(Object v) {
        return frozen.contains(v);
    }

    public void setFrozen(Object v, boolean b) {
        DefaultGraphCell cell = ViewHelper.getVertexCell(this, v);
        String label = (String) cell.getAttributes().get(ViewGraph.LABEL);
        if (b) {
            frozen.add(v);
            label = "[" + label + "]";
        } else {
            frozen.remove(v);
            label = label.replaceAll("[\\]\\[]", "");
        }
        Map map = new AttributeMap();
        map.put(ViewGraph.LABEL, label);
        getGraphLayoutCache().editCell(cell, map);
    }

    public Object getCurrentPoI() {
        return ((ClusteredGraph) getBase()).getPointOfInterest();
    }

    /**
     * Changes the current focus; will trigger a visibility recalculation
     * unless the clustering is 'locked'.
     */
    public void setCurrentPoI(Object anotherPoI) {
        setCurrentPoI(anotherPoI, !clusterLock);
    }

    /**
     * This one lets you decide whether to trigger Degree Of Interest 
     * recalculation or not.
     */
    public void setCurrentPoI(Object anotherPoI, boolean recalculateDoI) {

        if (anotherPoI == null) {
            return;
        }
        ClusteredGraph cg = (ClusteredGraph) getBase();
        cg.setPointOfInterest(anotherPoI);

        if (recalculateDoI) {
            recalculateDoI();
        }
    }

    /**
     * Make a vertex visible
     */
    public void makeVertexVisible(Object v) {
        ClusteredGraph cg = (ClusteredGraph) getBase();
        ClusteringChangeEvent cce = cg.createMakeVisibleEvent(v);
        if (cce != null) {
            cg.clusteringChangePerformed(cce);
        }
    }

    /**
     * Get the focus size
     */
    public int getFocusSize() {
        return focusSize;
    }

    /**
     * Set the focus size
     */
    public void setFocusSize(int focusSize) {
        this.focusSize = focusSize;
        if (!clusterLock) {
            recalculateDoI();
        }
    }

    /**
     * Get the current max cluster number
     */
    public int getMaxClusters() {
        return maxClusters;
    }

    /**
     * Set the max number of clusters
     */
    public void setMaxClusters(int maxClusters) {
        this.maxClusters = maxClusters;
        if (!clusterLock) {
            recalculateDoI();
        }
    }

    /**
     * Force a recalculation of "degree of interest" (visibility).
     * Recalculates the "degree of interest" of all vertices; only those with 
     * the highest interest will remain visible; the rest will be collapsed.
     */
    public void recalculateDoI() {
        ClusteredGraph cg = (ClusteredGraph) getBase();
        ClusteringChangeEvent cce = getPoIChangeEventFor(getCurrentPoI());
        if (cce == null) {
            return;
        }
        if (cce.getCollapsed().size() + cce.getExpanded().size() > 0) {
            cg.clusteringChangePerformed(cce);
        }
    }

    /**
     * Navigation history "redo" operation
     */
    public void nextNavAction() {
        if (currentNavIndex + 1 >= clusterNavHistory.size()) {
            log.debug("Already at end of history, aborted");
            return;
        }
        ClusteringChangeEvent cce = clusterNavHistory.get(currentNavIndex + 1);
        ClusteredGraph cg = (ClusteredGraph) getBase();
        cg.clusteringChangePerformed(cce);
        log.debug("Done. Now at " + currentNavIndex);
    }

    /**
     * Navigation history "undo" operation
     */
    public void prevNavAction() {
        if (currentNavIndex < 0) {
            log.debug("Already at start of history, aborted");
            return;
        }
        ClusteringChangeEvent cce = clusterNavHistory.get(currentNavIndex);
        ClusteredGraph cg = (ClusteredGraph) getBase();
        cg.clusteringChangePerformed(cce.getUndoEvent());
        log.debug("Done. Now at " + currentNavIndex);
    }

    /**
     * Returns the whole navigatino history, as a set of ClusteringChangeEvents.
     */
    public ArrayList<ClusteringChangeEvent> getClusterNavHistory() {
        return clusterNavHistory;
    }

    /**
     * Update the navigation history after a navigation event.
     * 'undo' events are ignored; and, when we are not top of the pile, 
     * a non-undo event will purge future events from the history
     */
    public void clusteringChangePerformed(ClusteringChangeEvent evt) {
        log.debug("Start of history update: size is " + clusterNavHistory.size() + ", index is " + currentNavIndex);

        if (evt.isUndoEvent()) {
            currentNavIndex--;
        } else {
            if (clusterNavHistory.size() <= currentNavIndex + 1) {
                clusterNavHistory.add(evt);
                currentNavIndex++;
            } else {
                // within the stack; the navIndex points to the redo action OR a new action
                ClusteringChangeEvent other = clusterNavHistory.get(currentNavIndex + 1);
                if (evt == other) {
                    // do nothing
                    log.debug("(forward navigation in history - nothing to update)");
                } else {
                    log.debug("Navigation is new; throwing away 'future' events");

                    // store & purge history from future events
                    while (currentNavIndex + 1 < clusterNavHistory.size()) {
                        clusterNavHistory.remove(clusterNavHistory.size() - 1);
                    }
                    clusterNavHistory.add(evt);
                }
                currentNavIndex++;
            }
        }

        log.debug("End of history update: size is " + clusterNavHistory.size() + ", index is " + currentNavIndex);
    }

    /**
     * Highlights the currently hovered-over cell and all its outgoing and incoming
     * edges. Demonstrates the use of listeners and plans and animators to achieve
     * interaction.
     *
     * Includes "aura effect", highlighting the nodes that will be expanded
     * and/or collapsed should the user click on the present one. The listener
     * auto-inhibits if there is already any plan running.
     */
    private class AuraMouseListener extends MouseMotionAdapter {

        private DefaultGraphCell lastCell;

        /**
         * Exit from the previous cell, if any - and highlight this cell
         * and all incoming and outgoing edges
         */
        public void mouseMoved(MouseEvent e) {
            DefaultGraphCell c = ViewHelper.getVertexCell(e);
            if (c == null) {
                highlighter.clearHighlight();
            } else if (c != lastCell) {
                highlighter.startFocusChangePlan(c);
                lastCell = c;
            }
        }

        public void mouseExit(Event evt, int x, int y) {
            highlighter.clearHighlight();
            lastCell = null;
        }
    }

    /**
     * Default mouse click listener
     */
    private class VertexCellClickListener extends MouseAdapter {
        public void mouseClicked(MouseEvent e) {

            int left = MouseEvent.BUTTON1;
            int right = MouseEvent.BUTTON3;
            int shift = MouseEvent.SHIFT_DOWN_MASK;

            if (e.getClickCount() == 1 && e.getButton() == right) {
                System.err.println("hi mom!");
                setSelectionCell(viewGraph.getModelAdapter().getVertexCell(getCurrentPoI()));
            }

            if (e.getClickCount() == 1 && e.getButton() == left) {

                //                System.err.println("Entering click handling: "+e.getModifiersExText(e.getModifiersEx()));
                DefaultGraphCell cell = ViewHelper.getVertexCell(e);
                if (cell == null)
                    return;
                Object v = ViewHelper.getVertex(cell);
                if (v == null)
                    return;

                // straight click, not shifted
                if ((e.getModifiersEx() & (shift)) == 0) {
                    if (clusterLock || !(v instanceof Cluster.Vertex)) {
                        setCurrentPoI(v);
                    } else {
                        Cluster c = ((Cluster.Vertex) v).getCluster();
                        setCurrentPoI(((Cluster) c.getChildAt(0)).getVertex());
                    }
                    //                    System.err.println("Prev sel: " + getSelectionCell());
                }
                // straight click, shifted (= wants to select without triggering changes)
                else if ((e.getModifiersEx() & (shift)) == shift) {
                    setSelectionCell(cell);
                    //                    System.err.println("Changed sel to: " + getSelectionCell());
                }
            }
        }
    }

    /**
     * Return the set of "frozen" vertices. These vertices will not partake
     * in expansion or collapse operations
     */
    public HashSet getFrozen() {
        return frozen;
    }

    /**
     * Change the set of "frozen" vertices for a new one. 
     * These vertices will not partake in expansion or collapse operations
     */
    public void setFrozen(HashSet frozen) {
        this.frozen = frozen;
    }

    /**
     * Returns the current vertex click listener; allows a vertex click to 
     * be simulated programatically without an awt.Robot
     */
    public VertexCellClickListener getVertexClickListener() {
        return vertexClickListener;
    }

    /**
     * Return the set of "frozen" vertices. These vertices will not partake
     * in expansion or collapse operations
     */
    public AuraMouseListener getAuraMouseListener() {
        return auraMouseListener;
    }

    /**
     * Returns the highlighter currently in use.
     */
    public Highlighter getHighlighter() {
        return highlighter;
    }

    /**
     * Changes the current highlighter for another one.
     */
    public void setHighlighter(Highlighter highlighter) {
        this.highlighter = highlighter;
    }

    /**
     * Saves this view as an element of a JDom tree.
     */
    public void save(Element e) {
        super.save(e);

        StringBuffer sb = null;

        e.setAttribute("poiVertex", getBase().getId(getCurrentPoI()));
        e.setAttribute("focusSize", "" + getFocusSize());
        e.setAttribute("maxClusters", "" + maxClusters);
        e.setAttribute("isClusterLock", isClusterLock() ? "true" : "false");

        // frozen set (may be empty)
        sb = new StringBuffer();
        if (!frozen.isEmpty()) {
            for (Object v : frozen) {
                sb.append(getBase().getId(v) + ",");
            }
            sb.deleteCharAt(sb.length() - 1);
        }
        e.setAttribute("frozenVertices", sb.toString());

        // visible slice
        sb = new StringBuffer();
        for (Cluster c : ((ClusteredGraph) getBase()).getSlice()) {
            sb.append(getBase().getId(c.getVertex()) + ",");
        }
        sb.deleteCharAt(sb.length() - 1);
        e.setAttribute("visibleSlice", sb.toString());
    }

    /**
     * Restores the current view from an element of a JDom tree.
     */
    public void restore(Element e) {
        super.restore(e);

        StringTokenizer st = null;

        // restore slice
        st = new StringTokenizer(e.getAttributeValue("visibleSlice"), ",");
        ArrayList<String> ids = new ArrayList<String>();
        while (st.hasMoreTokens()) {
            ids.add(st.nextToken().trim());
        }
        ClusteredGraph cg = (ClusteredGraph) getBase();
        String poiVertexName = e.getAttributeValue("poiVertex");
        HashMap<String, Cluster> map = cg.restoreSlice(ids, poiVertexName);

        // restore frozen
        st = new StringTokenizer(e.getAttributeValue("frozenVertices"), ",");
        frozen.clear();
        while (st.hasMoreTokens()) {
            frozen.add(map.get(st.nextToken().trim()).getVertex());
        }

        // restore remaining settings
        focusSize = Integer.parseInt(e.getAttributeValue("focusSize"));
        maxClusters = Integer.parseInt(e.getAttributeValue("maxClusters"));
        clusterLock = Boolean.parseBoolean(e.getAttributeValue("isClusterLock"));
    }
}