Package org.graphstream.ui.view

Source Code of org.graphstream.ui.view.Viewer

/*
* Copyright 2006 - 2013
*     Stefan Balev     <stefan.balev@graphstream-project.org>
*     Julien Baudry    <julien.baudry@graphstream-project.org>
*     Antoine Dutot    <antoine.dutot@graphstream-project.org>
*     Yoann Pigné      <yoann.pigne@graphstream-project.org>
*     Guilhelm Savin   <guilhelm.savin@graphstream-project.org>
*
* This file is part of GraphStream <http://graphstream-project.org>.
*
* GraphStream is a library whose purpose is to handle static or dynamic
* graph, create them from scratch, file or any source and display them.
*
* This program is free software distributed under the terms of two licenses, the
* CeCILL-C license that fits European law, and the GNU Lesser General Public
* License. You can  use, modify and/ or redistribute the software under the terms
* of the CeCILL-C license as circulated by CEA, CNRS and INRIA at the following
* URL <http://www.cecill.info> or under the terms of the GNU LGPL as published by
* the Free Software Foundation, either version 3 of the License, or (at your
* option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>.
*
* The fact that you are presently reading this means that you have had
* knowledge of the CeCILL-C and LGPL licenses and that you accept their terms.
*/
package org.graphstream.ui.view;

import org.graphstream.graph.Edge;
import org.graphstream.graph.Graph;
import org.graphstream.graph.Node;
import org.graphstream.stream.ProxyPipe;
import org.graphstream.stream.Source;
import org.graphstream.stream.thread.ThreadProxyPipe;
import org.graphstream.ui.geom.Point3;
import org.graphstream.ui.graphicGraph.GraphicGraph;
import org.graphstream.ui.layout.Layout;
import org.graphstream.ui.layout.LayoutRunner;
import org.graphstream.ui.layout.Layouts;
import org.graphstream.ui.swingViewer.DefaultView;
import org.graphstream.ui.swingViewer.GraphRenderer;
import org.graphstream.ui.swingViewer.ViewPanel;
import org.graphstream.ui.swingViewer.basicRenderer.SwingBasicGraphRenderer;

import javax.swing.Timer;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.security.AccessControlException;
import java.util.Map;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
* Set of views on a graphic graph.
*
* <p>
* The viewer class is in charge of maintaining :
* <ul>
* <li>A "graphic graph" (a special graph that internally stores the graph under
* the form of style sets of "graphic" elements, suitable to draw the graph, but
* not to adapted to used it as a general graph),</li>
* <li>The eventual proxy pipe from which the events come from (but graph events
* can come from any kind of source),</li>
* <li>A default view, and eventually more views on the graphic graph.</li>
* <li>A flag that allows to repaint the view only if the graphic graph changed.
* <li>
* </ul>
* </p>
*
* <p>
* The graphic graph can be created by the viewer or given at construction (to
* share it with another viewer).
* </p>
*
* <p>
* <u>Once created, the viewer runs in a loop inside the Swing thread. You
* cannot call methods on it directly if you are not in this thread</u>. The
* only operation that you can use in other threads is the constructor, the
* {@link #addView(View)}, {@link #removeView(String)} and the {@link #close()}
* methods. Other methods are not protected from concurrent accesses.
* </p>
*
* <p>
* Some constructors allow a {@link ProxyPipe} as argument. If given, the
* graphic graph is made listener of this pipe and the pipe is "pumped" during
* the view loop. This allows to run algorithms on a graph in the main thread
* (or any other thread) while letting the viewer run in the swing thread.
* </p>
*
* <p>
* Be very careful: due to the nature of graph events in GraphStream, the viewer
* is not aware of events that occured on the graph <u>before</u> its creation.
* There is a special mechanism that replay the graph if you use a proxy pipe or
* if you pass the graph directly. However, when you create the viewer by
* yourself and only pass a {@link Source}, the viewer <u>will not</u> display
* the events that occured on the source before it is connected to it.
* </p>
*/
public class Viewer implements ActionListener {

    /**
     * class level logger
     */
    private static final Logger logger = Logger.getLogger(Viewer.class.getName());

  // Attributes

  /**
   * Name of the default view.
   */
  public static String DEFAULT_VIEW_ID = "defaultView";

  /**
   * What to do when a view frame is closed.
   */
  public static enum CloseFramePolicy {
    CLOSE_VIEWER, HIDE_ONLY, EXIT
  };

  /**
   * How does the viewer synchronise its internal graphic graph with the graph
   * displayed. The graph we display can be in the Swing thread (as will be
   * the viewer, therefore in the same thread as the viewer), in another
   * thread, or on a distant machine.
   */
  public enum ThreadingModel {
        GRAPH_IN_GUI_THREAD, GRAPH_IN_ANOTHER_THREAD, GRAPH_ON_NETWORK
  };

  // Attribute

  /**
   * If true the graph we display is in another thread, the synchronisation
   * between the graph and the graphic graph must therefore use thread
   * proxies.
   */
  protected boolean graphInAnotherThread = true;

  /**
   * The graph observed by the views.
   */
  protected GraphicGraph graph;

  /**
   * If we have to pump events by ourself.
   */
  protected ProxyPipe pumpPipe;

  /**
   * If we take graph events from a source in this thread.
   */
  protected Source sourceInSameThread;

  /**
   * Timer in the Swing thread.
   */
  protected Timer timer;

  /**
   * Delay in milliseconds between frames.
   */
  protected int delay = 40;

  /**
   * The set of views.
   */
  protected final Map<String, View> views = new TreeMap<String, View>();

  /**
   * What to do when a view frame is closed.
   */
  protected CloseFramePolicy closeFramePolicy = CloseFramePolicy.EXIT;

  // Attribute

  /**
   * Optional layout algorithm running in another thread.
   */
  protected LayoutRunner optLayout = null;

  /**
   * If there is a layout in another thread, this is the pipe coming from it.
   */
  protected ProxyPipe layoutPipeIn = null;

  // Construction

  /**
   * The graph or source of graph events is in another thread or on another
   * machine, but the pipe already exists. The graphic graph displayed by this
   * viewer is created.
   *
   * @param source
   *            The source of graph events.
   */
  public Viewer(ProxyPipe source) {
    graphInAnotherThread = true;
    init(new GraphicGraph(newGGId()), source, (Source) null);
  }

  /**
   * We draw a pre-existing graphic graph. The graphic graph is maintained by
   * its creator.
   *
   * @param graph
   *            THe graph to draw.
   */
  public Viewer(GraphicGraph graph) {
    graphInAnotherThread = false;
    init(graph, (ProxyPipe) null, (Source) null);
  }

  /**
   * New viewer on an existing graph. The viewer always run in the Swing
   * thread, therefore, you must specify how it will take graph events from
   * the graph you give. If the graph you give will be accessed only from the
   * Swing thread use ThreadingModel.GRAPH_IN_GUI_THREAD. If the graph you
   * use is accessed in another thread use
   * ThreadingModel.GRAPH_IN_ANOTHER_THREAD. This last scheme is more powerful
   * since it allows to run algorithms on the graph in parallel with the
   * viewer.
   *
   * @param graph
   *            The graph to render.
   * @param threadingModel
   *            The threading model.
   */
  public Viewer(Graph graph, ThreadingModel threadingModel) {
    switch (threadingModel) {
    case GRAPH_IN_GUI_THREAD:
      graphInAnotherThread = false;
      init(new GraphicGraph(newGGId()), (ProxyPipe) null, graph);
      enableXYZfeedback(true);
      break;
    case GRAPH_IN_ANOTHER_THREAD:
      graphInAnotherThread = true;
     
      ThreadProxyPipe tpp = new ThreadProxyPipe();
      tpp.init(graph, true);

      init(new GraphicGraph(newGGId()), tpp, (Source) null);
      enableXYZfeedback(false);
      break;
    case GRAPH_ON_NETWORK:
      throw new RuntimeException("TO DO, sorry !:-)");
    }
  }

  /**
   * Create a new unique identifier for a graph.
   *
   * @return The new identifier.
   */
  protected String newGGId() {
    return String.format("GraphicGraph_%d", (int) (Math.random() * 10000));
  }

  /**
   * Initialise the viewer.
   *
   * @param graph
   *            The graphic graph.
   * @param ppipe
   *            The source of events from another thread or machine (null if
   *            source != null).
   * @param source
   *            The source of events from this thread (null if ppipe != null).
   */
  protected void init(GraphicGraph graph, ProxyPipe ppipe, Source source) {
    this.graph = graph;
    this.pumpPipe = ppipe;
    this.sourceInSameThread = source;
    this.timer = new Timer(delay, this);

    assert ((ppipe != null && source == null) || (ppipe == null && source != null));

    if (pumpPipe != null)
      pumpPipe.addSink(graph);
    if (sourceInSameThread != null) {
      if (source instanceof Graph)
        replayGraph((Graph) source);
      sourceInSameThread.addSink(graph);
    }

    timer.setCoalesce(true);
    timer.setRepeats(true);
    timer.start();
  }

  /**
   * Close definitively this viewer and all its views.
   */
  public void close() {
    synchronized (views) {
      disableAutoLayout();

      for (View view : views.values())
        view.close(graph);

      timer.stop();
      timer.removeActionListener(this);

      if (pumpPipe != null)
        pumpPipe.removeSink(graph);
      if (sourceInSameThread != null)
        sourceInSameThread.removeSink(graph);

      graph = null;
      pumpPipe = null;
      sourceInSameThread = null;
      timer = null;
    }
  }

  // Access

  /**
   * Create a new instance of the default graph renderer. The default graph
   * renderer class is given by the "org.graphstream.ui.renderer" system
   * property. If the class indicated by this property is not usable (not in
   * the class path, not of the correct type, etc.) or if the property is not
   * present a SwingBasicGraphRenderer is returned.
   */
  public static GraphRenderer newGraphRenderer() {
    String rendererClassName;

    try {
      rendererClassName = System.getProperty("gs.ui.renderer");

      if (rendererClassName != null) {
                logger.warning("\"gs.ui.renderer\" is deprecated, use \"org.graphstream.ui.renderer\" instead.");
      } else {
        rendererClassName = System.getProperty("org.graphstream.ui.renderer");
      }
    } catch (AccessControlException e) {
      rendererClassName = null;
    }

    if (rendererClassName == null)
      return new SwingBasicGraphRenderer();

    try {
      Class<?> c = Class.forName(rendererClassName);
      Object object = c.newInstance();

      if (object instanceof GraphRenderer) {
        return (GraphRenderer) object;
      } else {
        logger.warning(String.format("Class '%s' is not a 'GraphRenderer'.", object));
      }
    } catch (Exception e) {
            logger.log(Level.WARNING, "Cannot create graph renderer.", e);
    }

    return new SwingBasicGraphRenderer();
  }

  /**
   * What to do when a frame is closed.
   */
  public CloseFramePolicy getCloseFramePolicy() {
    return closeFramePolicy;
  }

  /**
   * New proxy pipe on events coming from the viewer through a thread.
   *
   * @return The new proxy pipe.
   */
  public ProxyPipe newThreadProxyOnGraphicGraph() {
    ThreadProxyPipe tpp = new ThreadProxyPipe();
    tpp.init(graph);
    return tpp;
  }

  /**
   * New viewer pipe on the events coming from the viewer through a thread.
   *
   * @return The new viewer pipe.
   */
  public ViewerPipe newViewerPipe() {
    ThreadProxyPipe tpp = new ThreadProxyPipe();
    tpp.init(graph, false);

    enableXYZfeedback(true);

    return new ViewerPipe(String.format("viewer_%d",
        (int) (Math.random() * 10000)), tpp);
  }

  /**
   * The underlying graphic graph. Caution : Use the returned graph only in
   * the Swing thread !!
   */
  public GraphicGraph getGraphicGraph() {
    return graph;
  }

  /**
   * The view that correspond to the given identifier.
   *
   * @param id
   *            The view identifier.
   * @return A view or null if not found.
   */
  public View getView(String id) {
    synchronized (views) {
      return views.get(id);
    }
  }

  /**
   * The default view. This is a shortcut to a call to
   * {@link #getView(String)} with {@link #DEFAULT_VIEW_ID} as parameter.
   *
   * @return The default view or null if no default view has been installed.
   */
  public ViewPanel getDefaultView() {
    return (DefaultView) getView(DEFAULT_VIEW_ID);
  }

  // Command

  /**
   * Build the default graph view and insert it. The view identifier is
   * {@link #DEFAULT_VIEW_ID}. You can request the view to be open in its own
   * frame.
   *
   * @param openInAFrame
   *            It true, the view is placed in a frame, else the view is only
   *            created and you must embed it yourself in your application.
   */
  public ViewPanel addDefaultView(boolean openInAFrame) {
    synchronized (views) {
            DefaultView view = new DefaultView(this, DEFAULT_VIEW_ID,
          newGraphRenderer());
      addView(view);

      if (openInAFrame)
        view.openInAFrame(true);

      return view;
    }
  }

  /**
   * Add a view using its identifier. If there was already a view with this
   * identifier, it is closed and returned (if different of the one added).
   *
   * @param view
   *            The view to add.
   * @return The old view that was at the given identifier, if any, else null.
   */
  public View addView(View view) {
    synchronized (views) {
      View old = views.put(view.getId(), view);

      if (old != null && old != view)
        old.close(graph);

      return old;
    }
  }

  /**
   * Add a new default view with a specific renderer. If a view with the same
   * id exists, it is removed and closed. By default the view is open in a
   * frame.
   *
   * @param id
   *            The new view identifier.
   * @param renderer
   *            The renderer to use.
   * @return The created view.
   */
  public ViewPanel addView(String id, GraphRenderer renderer) {
    return addView(id, renderer, true);
  }

  /**
   * Same as {@link #addView(String, GraphRenderer)} but allows to specify
   * that the view uses a frame or not.
   *
   * @param id
   *            The new view identifier.
   * @param renderer
   *            The renderer to use.
   * @param openInAFrame
   *            If true the view is open in a frame, else the returned view is
   *            a JPanel that can be inserted in a GUI.
   * @return The created view.
   */
  public ViewPanel addView(String id, GraphRenderer renderer, boolean openInAFrame) {
    synchronized (views) {
            DefaultView view = new DefaultView(this, id, renderer);
      addView(view);

      if (openInAFrame)
        view.openInAFrame(true);

      return view;
    }
  }

  /**
   * Remove a view. The view is not closed.
   *
   * @param id
   *            The view identifier.
   */
  public void removeView(String id) {
    synchronized (views) {
      views.remove(id);
    }
  }

  /**
   * Called on a regular basis by the timer. Checks if some events occurred
   * from the graph pipe or from the layout pipe, and if the graph changed,
   * triggers a repaint. Never call this method, it is called by a Swing Timer
   * automatically.
   */
  public void actionPerformed(ActionEvent e) {
    synchronized (views) {
      // long t1=System.currentTimeMillis();
      // long gsize1=graph.getNodeCount();
      if (pumpPipe != null)
        pumpPipe.pump();
      // long gsize2=graph.getNodeCount();
      // long t2=System.currentTimeMillis();

      if (layoutPipeIn != null)
        layoutPipeIn.pump();
      // long t3=System.currentTimeMillis();

      boolean changed = graph.graphChangedFlag();

      if (changed) {
        computeGraphMetrics();
        // long t4=System.currentTimeMillis();

        for (View view : views.values())
          view.display(graph, changed);
      }
      // long t5=System.currentTimeMillis();

      graph.resetGraphChangedFlag();

      // System.err.printf("display pump=%f  layoutPump=%f  metrics=%f  display=%f (size delta=%d  size1=%d size2=%d)%n",
      // (t2-t1)/1000.0, (t3-t2)/1000.0, (t4-t3)/1000.0, (t5-t4)/1000.0,
      // (gsize2-gsize1), gsize1, gsize2);
    }
  }

  /**
   * Compute the overall bounds of the graphic graph according to the nodes
   * and sprites positions. We can only compute the graph bounds from the
   * nodes and sprites centres since the node and graph bounds may in certain
   * circumstances be computed according to the graph bounds. The bounds are
   * stored in the graph metrics.
   */
  protected void computeGraphMetrics() {
    graph.computeBounds();

    synchronized (views) {
      Point3 lo = graph.getMinPos();
            Point3 hi = graph.getMaxPos();
      for (final View view : views.values()) {
                Camera camera = view.getCamera();
                if (camera != null) {
                    camera.setBounds(lo.x, lo.y, lo.z, hi.x, hi.y, hi.z);
                }
            }
    }
  }

  /**
   * What to do when the frame containing one or more views is closed.
   *
   * @param policy
   *            The close frame policy.
   */
  public void setCloseFramePolicy(CloseFramePolicy policy) {
    synchronized (views) {
      closeFramePolicy = policy;
    }
  }

  // Optional layout algorithm

  /**
   * Enable or disable the "xyz" attribute change when a node is moved in the
   * views. By default the "xyz" attribute is changed.
   *
   * By default, each time a node of the graphic graph is moved, its "xyz"
   * attribute is reset to follow the node position. This is useful only if
   * someone listen at the graphic graph or use the graphic graph directly.
   * But this operation is quite costly. Therefore by default if this viewer
   * runs in its own thread, and the main graph is in another thread, xyz
   * attribute change will be disabled until a listener is added.
   *
   * When the viewer is created to be used only in the swing thread, this
   * feature is always on.
   */
  public void enableXYZfeedback(boolean on) {
    synchronized (views) {
      graph.feedbackXYZ(on);
    }
  }

  /**
   * Launch an automatic layout process that will position nodes in the
   * background.
   */
  public void enableAutoLayout() {
    enableAutoLayout(Layouts.newLayoutAlgorithm());
  }

  /**
   * Launch an automatic layout process that will position nodes in the
   * background.
   *
   * @param layoutAlgorithm
   *            The algorithm to use (see Layouts.newLayoutAlgorithm() for the
   *            default algorithm).
   */
  public void enableAutoLayout(Layout layoutAlgorithm) {
    synchronized (views) {
      if (optLayout == null) {
        // optLayout = new LayoutRunner(graph, layoutAlgorithm, true,
        // true);
        optLayout = new LayoutRunner(graph, layoutAlgorithm, true,
            false);
        graph.replay();
        layoutPipeIn = optLayout.newLayoutPipe();
        layoutPipeIn.addAttributeSink(graph);
      }
    }
  }

  /**
   * Disable the running automatic layout process, if any.
   */
  public void disableAutoLayout() {
    synchronized (views) {
      if (optLayout != null) {
        ((ThreadProxyPipe) layoutPipeIn).unregisterFromSource();
        layoutPipeIn.removeSink(graph);
        layoutPipeIn = null;
        optLayout.release();
        optLayout = null;
      }
    }
  }

  /** Dirty replay of the graph. */
  protected void replayGraph(Graph graph) {
    // Replay all graph attributes.

    if (graph.getAttributeKeySet() != null)
      for (String key : graph.getAttributeKeySet()) {
        this.graph.addAttribute(key, graph.getAttribute(key));
      }

    // Replay all nodes and their attributes.

    for (Node node : graph) {
      Node n = this.graph.addNode(node.getId());

      if (node.getAttributeKeySet() != null) {
        for (String key : node.getAttributeKeySet()) {
          n.addAttribute(key, node.getAttribute(key));
        }
      }
    }

    // Replay all edges and their attributes.

    for (Edge edge : graph.getEachEdge()) {
      Edge e = this.graph.addEdge(edge.getId(), edge.getSourceNode()
          .getId(), edge.getTargetNode().getId(), edge.isDirected());

      if (edge.getAttributeKeySet() != null) {
        for (String key : edge.getAttributeKeySet()) {
          e.addAttribute(key, edge.getAttribute(key));
        }
      }
    }
  }
}
TOP

Related Classes of org.graphstream.ui.view.Viewer

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.