*/
		public void mouseMoved(MouseEvent me) {
			int iNode = getGraphNode(me);
			if (iNode >= 0) {
				if (iNode != m_nLastNode) {
					m_GraphPanel.highLight(iNode);
					if (m_nLastNode >= 0) {
						m_GraphPanel.highLight(m_nLastNode);
					}
					m_nLastNode = iNode;
				}
			}
			if (iNode < 0 && m_nLastNode >= 0) {
				m_GraphPanel.repaint();
				m_nLastNode = -1;
			}
		} // mouseMoved
		
	} // class GraphVisualizerMouseMotionListener

	/* apply graph layout algorithm to Bayesian network 
	 */
	void layoutGraph() {
		if (m_BayesNet.getNrOfNodes() == 0) {
			return;
		}
		try {
			FastVector m_nodes = new FastVector();
			FastVector m_edges = new FastVector();
			BIFParser bp = new BIFParser(m_BayesNet.toXMLBIF03(), m_nodes, m_edges);
			bp.parse();
			updateStatus();
			m_layoutEngine = new HierarchicalBCEngine(m_nodes, m_edges, m_nPaddedNodeWidth, m_nNodeHeight);
			m_layoutEngine.addLayoutCompleteEventListener(this);
			m_layoutEngine.layoutGraph();
		} catch (Exception e) {
			e.printStackTrace();
		}
	} // layoutGraph
	
	/* Update status of various items that need regular updating
	 * such as enabled status of some menu items, marginal distributions
	 * if shown, repainting of graph.
	 */
	void updateStatus() {
		a_undo.setEnabled(m_BayesNet.canUndo());
		a_redo.setEnabled(m_BayesNet.canRedo());

		a_datagenerator.setEnabled(m_BayesNet.getNrOfNodes() > 0);

		if (!m_bViewMargins && !m_bViewCliques) {
			repaint();
			return;
		}

		try {
			m_marginCalculator = new MarginCalculator();
			m_marginCalculator.calcMargins(m_BayesNet);
			SerializedObject so = new SerializedObject(m_marginCalculator);
			m_marginCalculatorWithEvidence = (MarginCalculator) so.getObject();
			for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
				if (m_BayesNet.getEvidence(iNode) >= 0) {
					m_marginCalculatorWithEvidence.setEvidence(iNode, m_BayesNet.getEvidence(iNode));
				}
			}
			for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
				m_BayesNet.setMargin(iNode, m_marginCalculatorWithEvidence.getMargin(iNode));
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		repaint();
	} // updateStatus

	/* add arc with node iChild as child. 
	 * This pops up a selection list with potential parents for the child.
	 * All decendants and current parents are excluded from the list as is
	 * the child node itself.
	 * @param iChild index of the node for which to add an arc
	 */
	void addArcInto(int iChild) {
		String sChild = m_BayesNet.getNodeName(iChild);
		try {
			int nNodes = m_BayesNet.getNrOfNodes();
			boolean[] isNotAllowedAsParent = new boolean[nNodes];
			// prevent it being a parent of itself
			isNotAllowedAsParent[iChild] = true;
			// prevent a descendant being a parent, since it introduces cycles
			for (int i = 0; i < nNodes; i++) {
				for (int iNode = 0; iNode < nNodes; iNode++) {
					for (int iParent = 0; iParent < m_BayesNet.getNrOfParents(iNode); iParent++) {
						if (isNotAllowedAsParent[m_BayesNet.getParent(iNode, iParent)]) {
							isNotAllowedAsParent[iNode] = true;
						}
					}
				}
			}
			// prevent nodes that are already a parent
			for (int iParent = 0; iParent < m_BayesNet.getNrOfParents(iChild); iParent++) {
				isNotAllowedAsParent[m_BayesNet.getParent(iChild, iParent)] = true;
			}
			// count nr of remaining candidates
			int nCandidates = 0;
			for (int i = 0; i < nNodes; i++) {
				if (!isNotAllowedAsParent[i]) {
					nCandidates++;
				}
			}
			if (nCandidates == 0) {
				JOptionPane.showMessageDialog(null, "No potential parents available for this node (" + sChild
						+ "). Choose another node as child node.");
				return;
			}
			String[] options = new String[nCandidates];
			int k = 0;
			for (int i = 0; i < nNodes; i++) {
				if (!isNotAllowedAsParent[i]) {
					options[k++] = m_BayesNet.getNodeName(i);
				}
			}
			String sParent = (String) JOptionPane.showInputDialog(null, "Select parent node for " + sChild, "Nodes", 0,
					null, options, options[0]);
			if (sParent == null || sParent.equals("")) {
				return;
			}
			// update all data structures
			m_BayesNet.addArc(sParent, sChild);
		    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
		    updateStatus();
		} catch (Exception e) {
			e.printStackTrace();
		}
	} // addArcInto

	/* deletes arc from node with name sParent into child with index iChild
	 * 
	 */
	void deleteArc(int iChild, String sParent) {
		try {
			m_BayesNet.deleteArc(m_BayesNet.getNode(sParent), iChild);
		    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
		} catch (Exception e) {
			e.printStackTrace();
		}
		updateStatus();
	} // deleteArc
	
	/* deletes arc from node with index iParent into child with name sChild
	 * 
	 */
	void deleteArc(String sChild, int iParent) {
		try {
			m_BayesNet.deleteArc(iParent, m_BayesNet.getNode(sChild));
		    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
		} catch (Exception e) {
			e.printStackTrace();
		}
		updateStatus();
	} // deleteArc

	/* deletes arc. Pops up list of arcs listed in 'options' as 
	 * "<Node1> -> <Node2>".
	 */
	void deleteArc(String[] options) {
		String sResult = (String) JOptionPane.showInputDialog(null, "Select arc to delete", "Arcs", 0, null, options,
				options[0]);
		if (sResult != null && !sResult.equals("")) {
			int nPos = sResult.indexOf(" -> ");
			String sParent = sResult.substring(0, nPos);
			String sChild = sResult.substring(nPos + 4);
			try {
				m_BayesNet.deleteArc(sParent, sChild);
			    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
			} catch (Exception e) {
				e.printStackTrace();
			}
			updateStatus();
		}
	} // deleteArc

	/* Rename node with index nTargetNode.
	 * Pops up window that allwos for entering a new name.
	 */
	void renameNode(int nTargetNode) {
		String sName = (String) JOptionPane.showInputDialog(null, m_BayesNet.getNodeName(nTargetNode), "New name for node",
				JOptionPane.OK_CANCEL_OPTION);
		if (sName == null || sName.equals("")) {
			return;
		}
		try {
			while (m_BayesNet.getNode2(sName) >= 0) {
				sName = (String) JOptionPane.showInputDialog(null, "Cannot rename to " + sName
						+ ".\nNode with that name already exists.");
				if (sName == null || sName.equals("")) {
					return;
				}
			}
			m_BayesNet.setNodeName(nTargetNode, sName);
		    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
		} catch (Exception e) {
			e.printStackTrace();
		}
		repaint();
	} // renameNode

	/* Rename value with name sValeu of a node with index nTargetNode.
	 * Pops up window that allows entering a new name.
	 */
	void renameValue(int nTargetNode, String sValue) {
		String sNewValue = (String) JOptionPane.showInputDialog(null, "New name for value " + sValue, "Node "
				+ m_BayesNet.getNodeName(nTargetNode), JOptionPane.OK_CANCEL_OPTION);
		if (sNewValue == null || sNewValue.equals("")) {
			return;
		}
		m_BayesNet.renameNodeValue(nTargetNode, sValue, sNewValue);
	    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
		a_undo.setEnabled(true);
		a_redo.setEnabled(false);
		repaint();
	} // renameValue

	/* delete a single node with index iNode */
	void deleteNode(int iNode) {
		try {
			m_BayesNet.deleteNode(iNode);
		    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
		} catch (Exception e) {
			e.printStackTrace();
		}
		updateStatus();
	} // deleteNode

	/* Add a value to currently selected node.
	 * Shows window that allows to enter the name of the value. 
	 */
	void addValue() {
		//GraphNode n = (GraphNode) m_nodes.elementAt(m_nCurrentNode);
		String sValue = "Value" + (m_BayesNet.getCardinality(m_nCurrentNode) + 1);
		String sNewValue = (String) JOptionPane.showInputDialog(null, "New value " + sValue, "Node " + m_BayesNet.getNodeName(m_nCurrentNode),
				JOptionPane.OK_CANCEL_OPTION);
		if (sNewValue == null || sNewValue.equals("")) {
			return;
		}
		try {
			m_BayesNet.addNodeValue(m_nCurrentNode, sNewValue);
		    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
			//n.outcomes = m_BayesNet.getValues(m_nCurrentNode);
			//for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
			//	n = (GraphNode) m_nodes.elementAt(iNode);
			//	n.probs = m_BayesNet.getDistribution(iNode);
			//}
		} catch (Exception e) {
			e.printStackTrace();
		}
		updateStatus();
	} // addValue

	/* remove value with name sValue from the node with index nTargetNode
	 */
	void delValue(int nTargetNode, String sValue) {
		try {
			m_BayesNet.delNodeValue(nTargetNode, sValue);
		    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
		} catch (Exception e) {
			e.printStackTrace();
		}
		updateStatus();
	} // delValue

	/* Edits CPT of node with index nTargetNode. 
	 * Pops up table with probability table that the user can change or just view.
	 */
	void editCPT(int nTargetNode) {
		m_nCurrentNode = nTargetNode;
		final GraphVisualizerTableModel tm = new GraphVisualizerTableModel(nTargetNode);

		JTable jTblProbs = new JTable(tm); 

		JScrollPane js = new JScrollPane(jTblProbs);

		int nParents = m_BayesNet.getNrOfParents(nTargetNode);
		if (nParents > 0) {
			GridBagConstraints gbc = new GridBagConstraints();
			JPanel jPlRowHeader = new JPanel(new GridBagLayout());

			// indices of the parent nodes in the Vector
			int[] idx = new int[nParents];
			// max length of values of each parent
			int[] lengths = new int[nParents];

			// Adding labels for rows
			gbc.anchor = GridBagConstraints.NORTHWEST;
			gbc.fill = GridBagConstraints.HORIZONTAL;
			gbc.insets = new Insets(0, 1, 0, 0);
			int addNum = 0, temp = 0;
			boolean dark = false;
			while (true) {
				gbc.gridwidth = 1;
				for (int k = 0; k < nParents; k++) {
					int iParent2 = m_BayesNet.getParent(nTargetNode, k); 
					JLabel lb = new JLabel(m_BayesNet.getValueName(iParent2,idx[k]));
					lb.setFont(new Font("Dialog", Font.PLAIN, 12));
					lb.setOpaque(true);
					lb.setBorder(BorderFactory.createEmptyBorder(1, 2, 1, 1));
					lb.setHorizontalAlignment(JLabel.CENTER);
					if (dark) {
						lb.setBackground(lb.getBackground().darker());
						lb.setForeground(Color.white);
					} else
						lb.setForeground(Color.black);

					temp = lb.getPreferredSize().width;
					lb.setPreferredSize(new Dimension(temp, jTblProbs.getRowHeight()));
					if (lengths[k] < temp)
						lengths[k] = temp;
					temp = 0;

					if (k == nParents - 1) {
						gbc.gridwidth = GridBagConstraints.REMAINDER;
						dark = (dark == true) ? false : true;
					}
					jPlRowHeader.add(lb, gbc);
					addNum++;
				}

				for (int k = nParents - 1; k >= 0; k--) {
					int iParent2 = m_BayesNet.getParent(m_nCurrentNode, k);
					if (idx[k] == m_BayesNet.getCardinality(iParent2) - 1 && k != 0) {
						idx[k] = 0;
						continue;
					} else {
						idx[k]++;
						break;
					}
				}

				int iParent2 = m_BayesNet.getParent(m_nCurrentNode, 0);
				if (idx[0] == m_BayesNet.getCardinality(iParent2)) {
					JLabel lb = (JLabel) jPlRowHeader.getComponent(addNum - 1);
					jPlRowHeader.remove(addNum - 1);
					lb.setPreferredSize(new Dimension(lb.getPreferredSize().width, jTblProbs
							.getRowHeight()));
					gbc.gridwidth = GridBagConstraints.REMAINDER;
					gbc.weighty = 1;
					jPlRowHeader.add(lb, gbc);
					gbc.weighty = 0;
					break;
				}
			}

			gbc.gridwidth = 1;
			// The following panel contains the names of the
			// parents
			// and is displayed above the row names to identify
			// which value belongs to which parent
			JPanel jPlRowNames = new JPanel(new GridBagLayout());
			for (int j = 0; j < nParents; j++) {
				JLabel lb2;
				JLabel lb1 = new JLabel(m_BayesNet.getNodeName(m_BayesNet.getParent(nTargetNode, j)));
				lb1.setBorder(BorderFactory.createEmptyBorder(1, 2, 1, 1));
				Dimension tempd = lb1.getPreferredSize();
				if (tempd.width < lengths[j]) {
					lb1.setPreferredSize(new Dimension(lengths[j], tempd.height));
					lb1.setHorizontalAlignment(JLabel.CENTER);
					lb1.setMinimumSize(new Dimension(lengths[j], tempd.height));
				} else if (tempd.width > lengths[j]) {
					lb2 = (JLabel) jPlRowHeader.getComponent(j);
					lb2.setPreferredSize(new Dimension(tempd.width, lb2.getPreferredSize().height));
				}
				jPlRowNames.add(lb1, gbc);
			}
			js.setRowHeaderView(jPlRowHeader);
			js.setCorner(JScrollPane.UPPER_LEFT_CORNER, jPlRowNames);
		}

		final JDialog dlg = new JDialog((Frame) GUI.this.getTopLevelAncestor(),
				"Probability Distribution Table For " + m_BayesNet.getNodeName(nTargetNode), true);
		dlg.setSize(500, 400);
		dlg.setLocation(GUI.this.getLocation().x + GUI.this.getWidth() / 2
				- 250, GUI.this.getLocation().y + GUI.this.getHeight() / 2
				- 200);

		dlg.getContentPane().setLayout(new BorderLayout());
		dlg.getContentPane().add(js, BorderLayout.CENTER);

		JButton jBtRandomize = new JButton("Randomize");
		jBtRandomize.setMnemonic('R');
		jBtRandomize.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				tm.randomize();
				dlg.repaint();
			}
		});
		
		JButton jBtOk = new JButton("Ok");
		jBtOk.setMnemonic('O');
		jBtOk.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				tm.setData();
				try {
					m_BayesNet.setDistribution(m_nCurrentNode, tm.m_fProbs);
				    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
					updateStatus();
				} catch (Exception e) {
					e.printStackTrace();
				}
				dlg.setVisible(false);
			}
		});
		JButton jBtCancel = new JButton("Cancel");
		jBtCancel.setMnemonic('C');
		jBtCancel.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				dlg.setVisible(false);
			}
		});
		Container c = new Container();
		c.setLayout(new GridBagLayout());
		c.add(jBtRandomize);
		c.add(jBtOk);
		c.add(jBtCancel);

		dlg.getContentPane().add(c, BorderLayout.SOUTH);
		dlg.setVisible(true);
	} // editCPT

	/**
	 * Main method. Builds up menus and reads from file if one is specified.
	 */
	public static void main(String[] args) {

		weka.core.logging.Logger.log(weka.core.logging.Logger.Level.INFO, "Logging started");
	    
		LookAndFeel.setLookAndFeel();
		
		JFrame jf = new JFrame("Bayes Network Editor");
		final GUI g = new GUI();
		JMenuBar menuBar = g.getMenuBar();

		if (args.length>0) {
			try {
				g.readBIFFromFile(args[0]);
			} catch (IOException ex) {
				ex.printStackTrace();
			} catch (BIFFormatException bf) {
				bf.printStackTrace();
				System.exit(-1);
			}
		}