package plab.prefuseTests;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.Font;
import java.awt.geom.Point2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;


import javax.swing.AbstractAction;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
import javax.swing.JSplitPane;
import javax.swing.KeyStroke;
import javax.swing.event.ChangeEvent;
import javax.swing.event.ChangeListener;
import javax.swing.JToggleButton;
import javax.swing.ButtonGroup;
import javax.swing.JRadioButton;
import javax.swing.JMenuItem;

import prefuse.Display;
import prefuse.Visualization;
import prefuse.action.ActionList;
import prefuse.action.RepaintAction;
import prefuse.action.assignment.ColorAction;
import prefuse.action.filter.GraphDistanceFilter;
import prefuse.action.layout.graph.ForceDirectedLayout;
import prefuse.controls.DragControl;
import prefuse.controls.NeighborHighlightControl;
import prefuse.controls.PanControl;
import prefuse.controls.WheelZoomControl;
import prefuse.controls.ZoomControl;
import prefuse.controls.ZoomToFitControl;
import prefuse.data.Graph;
import prefuse.data.Tuple;
import prefuse.data.event.TupleSetListener;
import prefuse.data.io.GraphMLReader;
import prefuse.data.tuple.TupleSet;
import prefuse.render.DefaultRendererFactory;
import prefuse.render.LabelRenderer;
import prefuse.util.ColorLib;
import prefuse.util.GraphLib;
import prefuse.util.force.ForceSimulator;
import prefuse.util.io.IOLib;
import prefuse.util.ui.JForcePanel;
import prefuse.util.ui.JValueSlider;
import prefuse.visual.VisualGraph;
import prefuse.visual.VisualItem;
import prefuse.action.assignment.StrokeAction;
import prefuse.util.StrokeLib;
import prefuse.action.assignment.DataSizeAction;
import prefuse.Constants;
import prefuse.data.Node;
import java.util.Iterator;
import javax.imageio.ImageIO;
import javax.swing.ImageIcon;
import javax.swing.JLabel;
import prefuse.util.ui.UILib;
import prefuse.util.force.Force;
import prefuse.action.assignment.DataColorAction;
import prefuse.render.ShapeRenderer;
import prefuse.data.tuple.DefaultTupleSet;
import prefuse.render.EdgeRenderer;
import prefuse.util.display.ExportDisplayAction;
import prefuse.util.ui.JSearchPanel;
import prefuse.util.FontLib;
import prefuse.data.query.SearchQueryBinding;
import prefuse.data.search.KeywordSearchTupleSet;
import prefuse.data.search.SearchTupleSet;
import prefuse.data.Table;


/**
 * Class to execute MSMExplorer, a visualization module for
 * protein folding Markov State Models generated with
 * MSMBuilder.
 *
 * Built on GraphView framework by Jeffrey Heer.
 *
 * @author Bryce Cronkite-Ratcliff, brycecr@stanford.edu
 */
public final class MSMExplorer extends JPanel {

	private static final int SIZE_THRESHOLD = 250;
	private static final String graph = "graph";
	private static final String nodes = "graph.nodes";
	private static final String edges = "graph.edges";
	private Visualization m_vis;
	private static JFrame frame;
	private static final String version = "alpha v0.01";

	/**
	 * Constructor
	 *
	 * @param g
	 * @param label
	 */
	public MSMExplorer(final Graph g, String label) {
		super(new BorderLayout());

		boolean isBigGraph = (g.getNodeCount() > SIZE_THRESHOLD);

		// create a new, empty visualization for our data
		m_vis = new Visualization();


		final LabelRenderer tr = new LabelRenderer();
		tr.setVerticalAlignment(Constants.BOTTOM);
		tr.setRoundedCorner(8, 8);
		// selfref renderer is for self-transitions. However, at this point,
		// it does not respond to probability color adjustments.
		m_vis.setRendererFactory(
			new DefaultRendererFactory(tr/*, new SelfRefEdgeRenderer()*/));
		// These tis line will create curved transition edges. This is useful
		// because it separates the two edges of bidirectional transitions,
		// thus making both colors more visible. However, this also makes
		// rendering substantially slower and makes the graph more dense.
//       ((EdgeRenderer)((DefaultRendererFactory)m_vis.getRendererFactory()).getDefaultEdgeRenderer()).setEdgeType(Constants.EDGE_TYPE_CURVE);

		// --------------------------------------------------------------------
		// register the data with a visualization

		// adds graph to visualization and sets renderer label field
		setGraph(g, label);

		// fix selected focus nodes
		final TupleSet focusGroup = m_vis.getGroup(Visualization.FOCUS_ITEMS);
		focusGroup.addTupleSetListener(new TupleSetListener() {

			public void tupleSetChanged(TupleSet ts, Tuple[] add, Tuple[] rem) {
				for (int i = 0; i < rem.length; ++i) {
					((VisualItem) rem[i]).setFixed(false);
					Iterator focusEdges = ((Node) rem[i]).edges();
					while (focusEdges.hasNext()) {
						VisualItem currEdge = (VisualItem) focusEdges.next();
						currEdge.setHover(false);
					}
				}
				for (int i = 0; i < add.length; ++i) {
					((VisualItem) add[i]).setFixed(false);
					Iterator focusEdges = ((Node) add[i]).edges();
					while (focusEdges.hasNext()) {
						VisualItem currEdge = (VisualItem) focusEdges.next();
						currEdge.setHover(false);
					}
					((VisualItem) add[i]).setFixed(true);
					Iterator focusEdges2 = ((Node) add[i]).edges();
					while (focusEdges2.hasNext()) {
						VisualItem currEdge = (VisualItem) focusEdges2.next();
						currEdge.setHover(true);
					}
				}
				if (ts.getTupleCount() == 0) {
					ts.addTuple(rem[0]);
					((VisualItem) rem[0]).setFixed(false);
					Iterator focusEdges2 = ((Node) rem[0]).edges();
					while (focusEdges2.hasNext()) {
						VisualItem currEdge = (VisualItem) focusEdges2.next();
						currEdge.setHover(false);
					}
				}
				if (ts.getTupleCount() > 1) {
					Iterator tuples = ts.tuples();

					//TODO: enable selection of sets of points for target/source
					DefaultTupleSet A = new DefaultTupleSet();
					A.addTuple((Tuple) tuples.next());

					DefaultTupleSet B = new DefaultTupleSet();
					B.addTuple((Tuple) tuples.next());

					TPTWindow tptw = new TPTWindow(g, A, B);

					focusGroup.clear();
				}

				m_vis.run("draw");
			}
		});

		initGraph(g, m_vis);
		if (g.getNodeCount() < SIZE_THRESHOLD) {
			tr.getImageFactory().preloadImages(g.nodes(), "image");
		}


		// --------------------------------------------------------------------
		// set up a display to show the visualization

		final Display display = new Display(m_vis);
		display.setSize(700, 700);
		display.pan(350, 350);
		display.setForeground(Color.GRAY);
		display.setBackground(Color.WHITE);

		// main display controls
		display.addControlListener(new FocusControlWithDeselect(1));
		display.addControlListener(new DragControl());
		display.addControlListener(new PanControl());
		display.addControlListener(new ZoomControl());
		display.addControlListener(new WheelZoomControl());
		display.addControlListener(new ZoomToFitControl());
		display.addControlListener(new NeighborHighlightControl());

		display.setForeground(Color.GRAY);
		display.setBackground(Color.WHITE);

		display.setHighQuality(true);

		JPanel fpanel = new JPanel();
		fpanel.setLayout(new BoxLayout(fpanel, BoxLayout.Y_AXIS));
		fpanel.setBackground(Color.WHITE);



		final JValueSlider slider = new JValueSlider("Distance", 0, 40, 37);
		slider.addChangeListener(new ChangeListener() {

			public void stateChanged(ChangeEvent e) {
				((GraphDistanceFilter) ((ActionList) m_vis.getAction("draw")).get(0)).setDistance(slider.getValue().intValue());
				m_vis.run("draw");
			}
		});
		slider.setBackground(Color.WHITE);
		slider.setPreferredSize(new Dimension(300, 30));
		slider.setMaximumSize(new Dimension(300, 30));

		Box cf = new Box(BoxLayout.Y_AXIS);
		cf.add(slider);
		cf.setBorder(BorderFactory.createTitledBorder("Connectivity Filter"));
		fpanel.add(cf);

		JToggleButton togglePM = new JToggleButton("Picture Mode", false);
		togglePM.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				display.setHighQuality(((JToggleButton)ae.getSource()).isSelected());
			}
		});
		togglePM.setSelected(true);

		JToggleButton curveBtn = new JToggleButton("Edge Type", false);
		curveBtn.addActionListener( new ActionListener () {
			public void actionPerformed( ActionEvent ae ) {
				if (((JToggleButton)ae.getSource()).isSelected())
					((EdgeRenderer)((DefaultRendererFactory)
						m_vis.getRendererFactory()).
						getDefaultEdgeRenderer()).
						setEdgeType(Constants.EDGE_TYPE_CURVE);
				else
					((EdgeRenderer)((DefaultRendererFactory)
						m_vis.getRendererFactory()).
						getDefaultEdgeRenderer()).
						setEdgeType(Constants.EDGE_TYPE_LINE);

				m_vis.run("draw");

			}
		});

		Box rendBox = new Box(BoxLayout.X_AXIS);
		rendBox.setBorder(BorderFactory.createTitledBorder("Gen. Renderer"));
		rendBox.add(togglePM);
		rendBox.add(curveBtn);
		fpanel.add(rendBox);

		ButtonGroup nodeRenderers = new ButtonGroup();

		JRadioButton circleRB = new JRadioButton("Circle", false);
		circleRB.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent ae) {
				JRadioButton circleRB = (JRadioButton) ae.getSource();
				if (circleRB.isSelected()) {
					m_vis.setRendererFactory(new DefaultRendererFactory(new ShapeRenderer()/*, new SelfRefEdgeRenderer()*/));
					m_vis.setValue(nodes, null, VisualItem.SHAPE, Constants.SHAPE_ELLIPSE);
				}

			}
		});
		nodeRenderers.add(circleRB);

		JRadioButton labelRB = new JRadioButton("Label", false);
		labelRB.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent ae) {
				JRadioButton labelRB = (JRadioButton) ae.getSource();
				if (labelRB.isSelected()) {
					m_vis.setRendererFactory(new DefaultRendererFactory(tr/*, new SelfRefEdgeRenderer()*/));
				}

			}
		});
		nodeRenderers.add(labelRB);

		Box nrBox = new Box(BoxLayout.X_AXIS);
		nrBox.setBorder(BorderFactory.createTitledBorder("Node Renderer"));
		nrBox.add(circleRB);
		nrBox.add(labelRB);
		labelRB.setSelected(true);

		fpanel.add(nrBox);

		JButton pause = new JButton("Stop Layout");
		pause.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				//if (((ActionList)m_vis.getAction("lll")).isRunning())
				((ActionList) m_vis.getAction("lll")).cancel();
			}
		});
		if (g.getNodeCount() > SIZE_THRESHOLD) //HERE
		{
			pause.setEnabled(false);
		}

		JButton start = new JButton("Run Layout");
		start.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				//if (!lll.isScheduled())
				((ActionList) m_vis.getAction("lll")).run();
			}
		});
		if (g.getNodeCount() > SIZE_THRESHOLD) //HERE
		{
			start.setEnabled(false);
		}

		Box runControls = new Box(BoxLayout.X_AXIS);
		runControls.add(start);
		runControls.add(pause);
		runControls.setBorder(
			BorderFactory.createTitledBorder("Run Control"));
		fpanel.add(runControls);

		JButton openImg = new JButton("Open Image");
		openImg.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent ae) {
				Tuple focus = (Tuple) m_vis.getGroup(Visualization.FOCUS_ITEMS).tuples().next();
				Picture imgFrame = new Picture((String) focus.get("image"));
				imgFrame.show();


			}
		});

		JButton exportDisplay = new JButton("Save Image");
		exportDisplay.addActionListener(new ExportDisplayAction(display));

		Box imgControls = new Box(BoxLayout.X_AXIS);
		imgControls.setBorder(BorderFactory.createTitledBorder("Image Controls"));
		imgControls.add(openImg);
		imgControls.add(exportDisplay);
		fpanel.add(imgControls);

		JToggleButton togglePics = new JToggleButton("Show Images", false);
		togglePics.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent ae) {
				JToggleButton tp = (JToggleButton) ae.getSource();
				if (!tp.isSelected()) {
					tr.setImageField(null);
					((DataSizeAction) m_vis.getAction("nodeSize")).setMaximumSize(50.0);
					m_vis.run("nodeSize");
				} else {
					((DataSizeAction) m_vis.getAction("nodeSize")).setMaximumSize(1.0);
					m_vis.run("nodeSize");
					tr.setImageField("image");
					tr.setImagePosition(Constants.TOP);
					tr.getImageFactory().setMaxImageDimensions(100, 100);
				}
				m_vis.run("draw");
			}
		});




		JButton runTPT = new JButton("TPT Selector");
		runTPT.addActionListener( new ActionListener () {
			public void actionPerformed( ActionEvent ae ) {
				TPTSetupBox tsb = new TPTSetupBox(g, true, m_vis);
			}
		});
		Box renderControls = new Box(BoxLayout.X_AXIS);
		renderControls.setBorder(BorderFactory.createTitledBorder("Function Control"));
		renderControls.add(togglePics);
		renderControls.add(runTPT);
		fpanel.add(renderControls);

		 SearchTupleSet searchGroup = new KeywordSearchTupleSet();
		 m_vis.addFocusGroup(Visualization.SEARCH_ITEMS, searchGroup);
		 searchGroup.addTupleSetListener( new TupleSetListener() {
			 public void tupleSetChanged(TupleSet ts, Tuple[] add, Tuple[] rem) {
				if ( add.length > 0 ) {
					Point2D p = new Point2D.Double();
					p.setLocation(((VisualItem)add[0]).getX(), ((VisualItem)add[0]).getY());
					display.animatePanToAbs(p, 1000);
				 }
			 }
		 });

		 SearchQueryBinding sq = new SearchQueryBinding((Table)m_vis.getGroup(nodes), "label",
			 (SearchTupleSet)m_vis.getGroup(Visualization.SEARCH_ITEMS));

		JSearchPanel search = sq.createSearchPanel(false);
//		JSearchPanel search = new JSearchPanel(m_vis, nodes, Visualization.SEARCH_ITEMS, "label", true, false);
		search.setShowResultCount(true);
		search.setBorder(BorderFactory.createEmptyBorder(5, 5, 4, 0));

		Box searchBox = new Box(BoxLayout.X_AXIS);
		searchBox.setBorder(BorderFactory.createTitledBorder("Search"));
		searchBox.add(search);
		fpanel.add(searchBox);



		Display overview = new Display(m_vis);
		overview.setSize(290, 290);
		overview.addItemBoundsListener(new FitOverviewListener());
		overview.setHighQuality(true);



		JPanel opanel = new JPanel();
		opanel.setBorder(BorderFactory.createTitledBorder("Overview"));
		opanel.setBackground(Color.WHITE);
		opanel.add(overview);

		fpanel.add(opanel);

		fpanel.add(Box.createVerticalGlue());



		// create a new JSplitPane to present the interface
		JSplitPane split = new JSplitPane();
		split.setLeftComponent(display);
		split.setRightComponent(fpanel);
		split.setOneTouchExpandable(true);
		split.setContinuousLayout(false);
		split.setDividerLocation(700);

		// now we run our action list
		m_vis.run("draw");
		m_vis.run("lll"); //HERE
		m_vis.run("nodeSize");

		add(split);
	}

	public void setGraph(Graph g, String label) {
		// update labeling
		DefaultRendererFactory drf = (DefaultRendererFactory) m_vis.getRendererFactory();
		//((LabelRenderer)drf.getDefaultRenderer()).setTextField(label);

		// update graph
		m_vis.removeGroup(graph);
		VisualGraph vg = m_vis.addGraph(graph, g);
		m_vis.setValue(edges, null, VisualItem.INTERACTIVE, Boolean.FALSE);
		VisualItem f = (VisualItem) vg.getNode(0);
		m_vis.getGroup(Visualization.FOCUS_ITEMS).setTuple(f);
		f.setFixed(false);
	}

	public void initGraph(Graph g, Visualization vis) {

		ColorAction fill = new NodeColorAction(nodes);

		/*ColorAction fill = new ColorAction(nodes,
			VisualItem.FILLCOLOR, ColorLib.rgb(179, 255, 156));
		fill.add(VisualItem.FIXED, ColorLib.rgb(255, 100, 100));
		fill.add(VisualItem.HIGHLIGHT, ColorLib.rgb(255, 200, 125));*/

		DataColorAction edgeColor = new DataColorAction(edges, "probability",
			Constants.NOMINAL, VisualItem.STROKECOLOR,
			ColorLib.getGrayscalePalette());
		edgeColor.add(VisualItem.HOVER, ColorLib.rgb(200, 40, 60));
		edgeColor.add(VisualItem.HIGHLIGHT, ColorLib.rgb(255, 150, 68));

		DataColorAction edgeFill = new DataColorAction(edges, "probability",
			Constants.NOMINAL, VisualItem.FILLCOLOR,
			ColorLib.getGrayscalePalette());
		edgeFill.add(VisualItem.HIGHLIGHT, ColorLib.rgb(200, 0, 0));

		StrokeAction edgeWeight = new StrokeAction(edges,
			StrokeLib.getStroke(1.0f));
		edgeWeight.add(VisualItem.HIGHLIGHT, StrokeLib.getStroke(2.0f));

		StrokeAction nodeWeight = new StrokeAction(nodes,
			StrokeLib.getStroke(1.0f));
		edgeWeight.add(VisualItem.HIGHLIGHT, StrokeLib.getStroke(2.0f));

		DataSizeAction nodeSize = new DataSizeAction(nodes,
			"eqProb", 50, Constants.LOG_SCALE);
		nodeSize.setMaximumSize(50.0);

		g.getNodes().addColumn("image", "CONCAT('/images/macro',label,'.png')");

		int hops = 5;
		final GraphDistanceFilter filter = new GraphDistanceFilter(graph, 40);


		final ActionList draw = new ActionList();
		draw.add(filter);
		draw.add(edgeFill);
		draw.add(new ColorAction(nodes, VisualItem.TEXTCOLOR, ColorLib.rgb(0, 0, 0)));
		draw.add(new ColorAction(nodes, VisualItem.STROKECOLOR, ColorLib.gray(50)));

		ActionList animate = new ActionList(ActionList.INFINITY);
		animate.add(edgeWeight);
		animate.add(edgeColor);
		animate.add(fill);
		animate.add(new RepaintAction());

		final ActionList lll = new ActionList();
		if (g.getNodeCount() > SIZE_THRESHOLD) //If graph is large
		{
			lll.add(new ForceDirectedLayout(graph, false, true)); //Then run-once
		} else {
			lll.setDuration(ActionList.INFINITY);
			lll.add(new ForceDirectedLayout(graph));              //Else, continually animate
		}

		// finally, we register our ActionList with the Visualization.
		// we can later execute our Actions by invoking a method on our
		// Visualization, using the name we've chosen below.
		m_vis.putAction("draw", draw);
		m_vis.putAction("lll", lll);
		m_vis.putAction("nodeSize", nodeSize);
		m_vis.putAction("layout", animate);

		m_vis.runAfter("draw", "layout");

		ForceSimulator fsim = ((ForceDirectedLayout) ((ActionList) m_vis.getAction("lll")).get(0)).getForceSimulator(); //HERE
		Force[] forces = fsim.getForces();

		final Force NBodyForce = forces[0];
		//final Force DragForce = forces[1];
		final Force SpringForce = forces[2];

		SetForces:
		{
			NBodyForce.setMinValue(0, -40.0f);
			NBodyForce.setParameter(0, -40.0f);
			NBodyForce.setParameter(1, -1);

			SpringForce.setParameter(0, .000001f);
			SpringForce.setParameter(1, 200f);
		}

	}



	private static void preemptiveTPT(JFrame selector) {
		Graph g = IOLib.getGraphFile(null);
		if (g == null)
			return;

		selector.dispose();

		TPTSetupBox setup = new TPTSetupBox(g, false);

	}

	// ------------------------------------------------------------------------
	// Main and demo methods
	// ------------------------------------------------------------------------
	public static void main(String[] args) {
		UILib.setPlatformLookAndFeel();

		final JFrame selector = new JFrame("Start Screen");
		selector.setLayout(new BorderLayout());

		JButton graphButton = new JButton("Whole Graph");
		graphButton.addActionListener( new ActionListener() {
			public void actionPerformed( ActionEvent ae ) {
				selector.dispose();
				frame = demo("5macro.xml", "label"); //HERE
				frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
			}
		});

		JButton tptButton = new JButton("Just TPT");
		tptButton.addActionListener( new ActionListener() {
			public void actionPerformed( ActionEvent ae ) {
				preemptiveTPT(selector);
			}
		});


		Box container = new Box(BoxLayout.X_AXIS);
		container.add(Box.createHorizontalStrut(130));
		container.add(graphButton);
		container.add(tptButton);
		container.add(Box.createHorizontalGlue());
		JLabel versionLabel = new JLabel(version + "     ");
		versionLabel.setFont(FontLib.getFont("Tahoma", 10));
		container.add(versionLabel);
		container.setBackground(Color.WHITE);


		BufferedImage image;
		try {
			String curDir = System.getProperty("user.dir");
			System.out.println(curDir);
			image = ImageIO.read(new File(curDir + "/splash.jpg"));
		} catch (IOException ex) {
			System.err.println("Could not find splash image");
			image = new BufferedImage(500,300,BufferedImage.TYPE_INT_RGB);

		}
		ImageIcon splash = new ImageIcon(image);
		selector.add(new JLabel(splash), BorderLayout.CENTER);
		selector.add(container, BorderLayout.SOUTH);

		selector.setBackground(Color.WHITE);
		selector.pack();
		selector.setSize(500, 380);
		selector.setLocationRelativeTo(null);
		selector.setVisible(true);
	}   // end of main

	public static JFrame demo(String datafile, String label) {
		Graph g = null;
		if (datafile == null) {
			g = GraphLib.getGrid(15, 15);
			label = "label";
		} else {
			try {
				g = new GraphMLReader().readGraph(datafile);
			} catch (Exception e) {
				System.err.println(e.getMessage());
				System.err.println(e.getCause());
				System.err.println("fuck. =[");
				System.exit(1);
			}
		}
		return demo(g, label);
	} // end of class demo(String, String)

	public static JFrame demo(final Graph g, String label) {
		final MSMExplorer view = new MSMExplorer(g, label);

		JMenuItem openAdj = new JMenuItem("Adjustment Panel");
		openAdj.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent ae) {
				JFrame forceFrame = new JFrame("Force Adjustment");

				ForceSimulator fsim = ((ForceDirectedLayout) ((ActionList) view.m_vis.getAction("lll")).get(0)).getForceSimulator();
				JForcePanel fPanel = new JForcePanel(fsim);
				forceFrame.add(fPanel);
				forceFrame.pack();
				forceFrame.setVisible(true);
			}
		});

		JMenuItem makeMovie = new JMenuItem("Create PDB Movie");
		makeMovie.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent ae) {
				PDBFrame pdbf = new PDBFrame(g, view.m_vis);
			}
		});

		// set up menu
		JMenu dataMenu = new JMenu("Data");
		dataMenu.add(openAdj);
		dataMenu.add(makeMovie);
		dataMenu.add(new OpenGraphAction(view));

		JMenuBar menubar = new JMenuBar();
		menubar.add(dataMenu);

		// launch window
		JFrame frame = new JFrame("p r e f u s e  |  g r a p h v i e w");
		frame.setJMenuBar(menubar);
		frame.setContentPane(view);
		frame.pack();
		frame.setVisible(true);

		frame.addWindowListener(new WindowAdapter() {

			@Override
			public void windowActivated(WindowEvent e) {
				view.m_vis.run("layout");
			}

			@Override
			public void windowDeactivated(WindowEvent e) {
				if ((((JFrame) e.getWindow()).getTitle()).equals("Force Adjustment")) {
					view.m_vis.cancel("layout");
				}
			}
		});

		return frame;
	}   //end of class demo(Graph, String)

	// ------------------------------------------------------------------------
	//  Utility classes
	// ------------------------------------------------------------------------
	/**
	 * Really awfully messy method to open a new graph from
	 * the menu "Open Graph" option. TODO: fix this shit.
	 *
	 */
	public static class OpenGraphAction extends AbstractAction {

		private MSMExplorer m_view;

		public OpenGraphAction(MSMExplorer view) {
			m_view = view;
			this.putValue(AbstractAction.NAME, "Open File...");
			this.putValue(AbstractAction.ACCELERATOR_KEY,
				KeyStroke.getKeyStroke("ctrl O"));
		}

		public void actionPerformed(ActionEvent e) {
			Graph g = IOLib.getGraphFile(m_view);
			if (g != null) {
				frame.dispose();
				frame = demo(g, "label");
			}
		}
	} // end of class OpenGraphAction

	public static class NodeColorAction extends ColorAction {

		public NodeColorAction(String group) {
			super(group, VisualItem.FILLCOLOR);
		}

		@Override
		public int getColor(VisualItem item) {
			if (m_vis.isInGroup(item, Visualization.SEARCH_ITEMS)) {
				return ColorLib.rgb(255, 120, 120);
			} else if (item.isFixed()) {
				return ColorLib.rgb(245, 150, 50);
			} else if (item.isHighlighted()) {
				return ColorLib.rgb(245, 200, 100);
			} else {
				return ColorLib.rgb(245, 230, 210);
			}
		}
	} // end of inner class NodeColorAction
} // end of class GraphView

