public void actionPerformed(ActionEvent ae) {
			try {
				String[] options = new String[m_BayesNet.getNrOfNodes()];
				for (int i = 0; i < options.length; i++) {
					options[i] = m_BayesNet.getNodeName(i);
				}
				String sChild = (String) JOptionPane.showInputDialog(null, "Select child node", "Nodes", 0, null,
						options, options[0]);
				if (sChild == null || sChild.equals("")) {
					return;
				}
				int iChild = m_BayesNet.getNode(sChild);
				addArcInto(iChild);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	} // class ActionAddArc

	class ActionDeleteArc extends MyAction {
		/** for serialization */
		private static final long serialVersionUID = -2038914085935519L;

		public ActionDeleteArc() {
			super("Delete Arc", "Delete Arc", "delarc", "");
		} // c'tor

		public void actionPerformed(ActionEvent ae) {
			int nEdges = 0;
			for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
				nEdges += m_BayesNet.getNrOfParents(iNode);
			}
			String[] options = new String[nEdges];
			int i = 0;
			for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
				for (int iParent = 0; iParent < m_BayesNet.getNrOfParents(iNode); iParent++) {
					int nParent = m_BayesNet.getParent(iNode, iParent);
					String sEdge = m_BayesNet.getNodeName(nParent);
					sEdge += " -> ";
					sEdge += m_BayesNet.getNodeName(iNode);
					options[i++] = sEdge;
				}
				
			}
			deleteArc(options);
		}
	} // class ActionDeleteArc

	class ActionNew extends MyAction {
		/** for serialization */
		private static final long serialVersionUID = -2038911085935515L;

		public ActionNew() {
			super("New", "New Network", "new", "");
		} // c'tor

		public void actionPerformed(ActionEvent ae) {
			m_sFileName = "";
			m_BayesNet = new EditableBayesNet(true);
			updateStatus();
			layoutGraph();
			a_datagenerator.setEnabled(false);
			m_BayesNet.clearUndoStack();
		    m_jStatusBar.setText("New Network");
			m_Selection = new Selection();
			repaint();
		}
	} // class ActionNew

	class ActionLoad extends MyAction {
		/** for serialization */
		private static final long serialVersionUID = -2038911085935515L;

		public ActionLoad() {
			super("Load", "Load Graph", "open", "ctrl O");
		} // c'tor

		public void actionPerformed(ActionEvent ae) {
			JFileChooser fc = new JFileChooser(System.getProperty("user.dir"));
			ExtensionFileFilter ef1 = new ExtensionFileFilter(".arff", "ARFF files");
			ExtensionFileFilter ef2 = new ExtensionFileFilter(".xml", "XML BIF files");
			fc.addChoosableFileFilter(ef1);
			fc.addChoosableFileFilter(ef2);
			fc.setDialogTitle("Load Graph");
			int rval = fc.showOpenDialog(GUI.this);

			if (rval == JFileChooser.APPROVE_OPTION) {
				String sFileName = fc.getSelectedFile().toString();
				if (sFileName.endsWith(ef1.getExtensions()[0])) {
					initFromArffFile(sFileName);
				} else {
					try {
						readBIFFromFile(sFileName);
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			    m_jStatusBar.setText("Loaded " + sFileName);
			    updateStatus();
			}
		}
	} // class ActionLoad
	
	
	class ActionViewStatusbar extends MyAction {
		/** for serialization */
		private static final long serialVersionUID = -20389330812354L;

		public ActionViewStatusbar() {
			super("View statusbar", "View statusbar", "statusbar", "");
		} // c'tor

		public void actionPerformed(ActionEvent ae) {
			m_jStatusBar.setVisible(!m_jStatusBar.isVisible());
		} // actionPerformed
	} // class ActionViewStatusbar

	class ActionViewToolbar extends MyAction {
		/** for serialization */
		private static final long serialVersionUID = -20389110812354L;

		public ActionViewToolbar() {
			super("View toolbar", "View toolbar", "toolbar", "");
		} // c'tor

		public void actionPerformed(ActionEvent ae) {
			m_jTbTools.setVisible(!m_jTbTools.isVisible());
		} // actionPerformed
	} // class ActionViewToolbar

	class ActionSave extends MyAction {
		/** for serialization */
		private static final long serialVersionUID = -20389110859355156L;

		public ActionSave() {
			super("Save", "Save Graph", "save", "ctrl S");
		} // c'tor

		public ActionSave(String sName, String sToolTipText, String sIcon, String sAcceleratorKey) {
			super(sName, sToolTipText, sIcon, sAcceleratorKey);
		} // c'tor

		public void actionPerformed(ActionEvent ae) {
			if (!m_sFileName.equals("")) {
				saveFile(m_sFileName);
				m_BayesNet.isSaved();
			    m_jStatusBar.setText("Saved as " + m_sFileName);
			} else {
				if (saveAs()) {
					m_BayesNet.isSaved();
				    m_jStatusBar.setText("Saved as " + m_sFileName);					
				}
			}
		} // actionPerformed


		ExtensionFileFilter ef1 = new ExtensionFileFilter(".xml", "XML BIF files");

		boolean saveAs() {
			JFileChooser fc = new JFileChooser(System.getProperty("user.dir"));
			fc.addChoosableFileFilter(ef1);
			fc.setDialogTitle("Save Graph As");
			if (!m_sFileName.equals("")) {
				// can happen on actionQuit
				fc.setSelectedFile(new File(m_sFileName));
			}
			int rval = fc.showSaveDialog(GUI.this);

			if (rval == JFileChooser.APPROVE_OPTION) {
				// System.out.println("Saving to file \""+
				// f.getAbsoluteFile().toString()+"\"");
				String sFileName = fc.getSelectedFile().toString();
				if (!sFileName.endsWith(".xml"))
					sFileName = sFileName.concat(".xml");
				saveFile(sFileName);
				return true;
			}
			return false;
		} // saveAs

		protected void saveFile(String sFileName) {
		    try {
		        FileWriter outfile = new FileWriter(sFileName);
		        outfile.write(m_BayesNet.toXMLBIF03());
		        outfile.close();
				m_sFileName = sFileName;
			    m_jStatusBar.setText("Saved as " + m_sFileName);
		      }
		      catch(IOException e) { 
		    	  e.printStackTrace(); 
		      }
		  } // saveFile
	} // class ActionSave
	
	class ActionSaveAs extends ActionSave {
		/** for serialization */
		private static final long serialVersionUID = -20389110859354L;

		public ActionSaveAs() {
			super("Save As", "Save Graph As", "saveas", "");
		} // c'tor

		public void actionPerformed(ActionEvent ae) {
			saveAs();
		} // actionPerformed
	} // class ActionSaveAs

	class ActionPrint extends ActionSave {
		/** for serialization */
		private static final long serialVersionUID = -20389001859354L;
		boolean m_bIsPrinting = false;
		public ActionPrint() {
			super("Print", "Print Graph", "print", "ctrl P");
		} // c'tor

		public void actionPerformed(ActionEvent ae) {
		    PrinterJob printJob = PrinterJob.getPrinterJob();
		    printJob.setPrintable(m_GraphPanel);
		    if (printJob.printDialog())
		      try { 
		  		m_bIsPrinting = true;
		        printJob.print();
				m_bIsPrinting = false;
		      } catch(PrinterException pe) {
		        m_jStatusBar.setText("Error printing: " + pe);
				m_bIsPrinting = false;
		      }
			    m_jStatusBar.setText("Print");
		} // actionPerformed
		public boolean isPrinting() {return m_bIsPrinting;}

	} // class ActionPrint

	class ActionQuit extends ActionSave {
		/** for serialization */
		private static final long serialVersionUID = -2038911085935515L;

		public ActionQuit() {
			super("Exit", "Exit Program", "exit", "");
		} // c'tor

		public void actionPerformed(ActionEvent ae) {
			if (m_BayesNet.isChanged()) {
				int result = JOptionPane.showConfirmDialog(null, "Network changed. Do you want to save it?", "Save before closing?", JOptionPane.YES_NO_CANCEL_OPTION);
				if (result == JOptionPane.CANCEL_OPTION) {
					return;
				}
				if (result == JOptionPane.YES_OPTION) {
					if (!saveAs()) {
						return;
					}
				}
			}
			System.exit(0);
		}
	} // class ActionQuit

	class ActionHelp extends MyAction {
		/** for serialization */
		private static final long serialVersionUID = -20389110859354L;

		public ActionHelp() {
			super("Help", "Bayesian Network Workbench Help", "help", "");
		} // c'tor

		public void actionPerformed(ActionEvent ae) {
			JOptionPane.showMessageDialog(null, "See Weka Homepage\nhttp://www.cs.waikato.ac.nz/ml", "Help Message",
					JOptionPane.PLAIN_MESSAGE);
		}
	} // class ActionHelp

	class ActionAbout extends MyAction {
		/** for serialization */
		private static final long serialVersionUID = -20389110859353L;

		public ActionAbout() {
			super("About", "Help about", "about", "");
		} // c'tor

		public void actionPerformed(ActionEvent ae) {
			JOptionPane.showMessageDialog(null, "Bayesian Network Workbench\nPart of Weka\n2007", "About Message",
					JOptionPane.PLAIN_MESSAGE);
		}
	} // class ActionAbout

	class ActionZoomIn extends MyAction {
		/** for serialization */
		private static final long serialVersionUID = -2038911085935515L;

		public ActionZoomIn() {
			super("Zoom in", "Zoom in", "zoomin", "+");
		} // c'tor

		public void actionPerformed(ActionEvent ae) {
			int i = 0, s = (int) (m_fScale * 100);
			if (s < 300)
				i = s / 25;
			else if (s < 700)
				i = 6 + s / 50;
			else
				i = 13 + s / 100;

			if (s >= 999) {
				setEnabled(false);
				return;
			} else if (s >= 10) {
				if (i >= 22) {
					setEnabled(false);
				}
				if (s == 10 && !a_zoomout.isEnabled()) {
					a_zoomout.setEnabled(true);
				}
				m_jTfZoom.setText(m_nZoomPercents[i + 1] + "%");
				m_fScale = m_nZoomPercents[i + 1] / 100D;
			} else {
				if (!a_zoomout.isEnabled())
					a_zoomout.setEnabled(true);
				m_jTfZoom.setText(m_nZoomPercents[0] + "%");
				m_fScale = m_nZoomPercents[0] / 100D;
			}
			setAppropriateSize();
			m_GraphPanel.repaint();
			m_GraphPanel.invalidate();
			m_jScrollPane.revalidate();
		    m_jStatusBar.setText("Zooming in");
		}
	} // class ActionZoomIn

	class ActionZoomOut extends MyAction {
		/** for serialization */
		private static final long serialVersionUID = -203891108593551L;

		public ActionZoomOut() {
			super("Zoom out", "Zoom out", "zoomout", "-");
		} // c'tor

		public void actionPerformed(ActionEvent ae) {
			int i = 0, s = (int) (m_fScale * 100);
			if (s < 300)
				i = (int) Math.ceil(s / 25D);
			else if (s < 700)
				i = 6 + (int) Math.ceil(s / 50D);
			else
				i = 13 + (int) Math.ceil(s / 100D);

			if (s <= 10) {
				setEnabled(false);
			} else if (s < 999) {
				if (i <= 1) {
					setEnabled(false);
				}
				m_jTfZoom.setText(m_nZoomPercents[i - 1] + "%");
				m_fScale = m_nZoomPercents[i - 1] / 100D;
			} else {
				if (!a_zoomin.isEnabled())
					a_zoomin.setEnabled(true);
				m_jTfZoom.setText(m_nZoomPercents[22] + "%");
				m_fScale = m_nZoomPercents[22] / 100D;
			}
			setAppropriateSize();
			m_GraphPanel.repaint();
			m_GraphPanel.invalidate();
			m_jScrollPane.revalidate();
		    m_jStatusBar.setText("Zooming out");
		}
	} // class ActionZoomOut

	class ActionLayout extends MyAction {
		/** for serialization */
		private static final long serialVersionUID = -203891108593551L;

		public ActionLayout() {
			super("Layout", "Layout Graph", "layout", "ctrl L");
		} // c'tor

		JDialog dlg = null;

		public void actionPerformed(ActionEvent ae) {
			if (dlg == null) {
				dlg = new JDialog();
				dlg.setTitle("Graph Layout Options");
				final JCheckBox jCbCustomNodeSize = new JCheckBox("Custom Node Size");
				final JLabel jLbNodeWidth = new JLabel("Width");
				final JLabel jLbNodeHeight = new JLabel("Height");

				m_jTfNodeWidth.setHorizontalAlignment(JTextField.CENTER);
				m_jTfNodeWidth.setText("" + m_nNodeWidth);
				m_jTfNodeHeight.setHorizontalAlignment(JTextField.CENTER);
				m_jTfNodeHeight.setText("" + m_nNodeHeight);
				jLbNodeWidth.setEnabled(false);
				m_jTfNodeWidth.setEnabled(false);
				jLbNodeHeight.setEnabled(false);
				m_jTfNodeHeight.setEnabled(false);

				jCbCustomNodeSize.addActionListener(new ActionListener() {
					public void actionPerformed(ActionEvent ae) {
						if (((JCheckBox) ae.getSource()).isSelected()) {
							jLbNodeWidth.setEnabled(true);
							m_jTfNodeWidth.setEnabled(true);
							jLbNodeHeight.setEnabled(true);
							m_jTfNodeHeight.setEnabled(true);
						} else {
							jLbNodeWidth.setEnabled(false);
							m_jTfNodeWidth.setEnabled(false);
							jLbNodeHeight.setEnabled(false);
							m_jTfNodeHeight.setEnabled(false);
							setAppropriateSize();
							setAppropriateNodeSize();
						}
					}
				});
				JButton jBtLayout;
				jBtLayout = new JButton("Layout Graph");
				jBtLayout.setMnemonic('L');

				jBtLayout.addActionListener(new ActionListener() {
					public void actionPerformed(ActionEvent ae) {
						int tmpW, tmpH;

						if (jCbCustomNodeSize.isSelected()) {
							try {
								tmpW = Integer.parseInt(m_jTfNodeWidth.getText());
							} catch (NumberFormatException ne) {
								JOptionPane.showMessageDialog(GUI.this.getParent(),
										"Invalid integer entered for node width.", "Error", JOptionPane.ERROR_MESSAGE);
								tmpW = m_nNodeWidth;
								m_jTfNodeWidth.setText("" + m_nNodeWidth);

							}
							try {
								tmpH = Integer.parseInt(m_jTfNodeHeight.getText());
							} catch (NumberFormatException ne) {
								JOptionPane.showMessageDialog(GUI.this.getParent(),
										"Invalid integer entered for node height.", "Error", JOptionPane.ERROR_MESSAGE);
								tmpH = m_nNodeHeight;
								m_jTfNodeWidth.setText("" + m_nNodeHeight);
							}

							if (tmpW != m_nNodeWidth || tmpH != m_nNodeHeight) {
								m_nNodeWidth = tmpW;
								m_nPaddedNodeWidth = m_nNodeWidth + PADDING;
								m_nNodeHeight = tmpH;
							}
						}
						// JButton bt = (JButton) ae.getSource();
						// bt.setEnabled(false);
						dlg.setVisible(false);
						updateStatus();
						layoutGraph();
					    m_jStatusBar.setText("Laying out Bayes net");
					}
				});
				JButton jBtCancel;
				jBtCancel = new JButton("Cancel");
				jBtCancel.setMnemonic('C');
				jBtCancel.addActionListener(new ActionListener() {
					public void actionPerformed(ActionEvent ae) {
						dlg.setVisible(false);
					}
				});
				GridBagConstraints gbc = new GridBagConstraints();
				dlg.setLayout(new GridBagLayout());
				//dlg.add(m_le.getControlPanel());

				Container c = new Container();
				c.setLayout(new GridBagLayout());

				gbc.gridwidth = 1;
				gbc.insets = new Insets(8, 0, 0, 0);
				gbc.anchor = GridBagConstraints.NORTHWEST;
				gbc.gridwidth = GridBagConstraints.REMAINDER;
				c.add(jCbCustomNodeSize, gbc);
				gbc.gridwidth = GridBagConstraints.RELATIVE;
				c.add(jLbNodeWidth, gbc);
				gbc.gridwidth = GridBagConstraints.REMAINDER;
				c.add(m_jTfNodeWidth, gbc);
				gbc.gridwidth = GridBagConstraints.RELATIVE;
				c.add(jLbNodeHeight, gbc);
				gbc.gridwidth = GridBagConstraints.REMAINDER;
				c.add(m_jTfNodeHeight, gbc);
				gbc.fill = GridBagConstraints.HORIZONTAL;
				dlg.add(c, gbc);
				dlg.add(jBtLayout);
				gbc.gridwidth = GridBagConstraints.REMAINDER;
				dlg.add(jBtCancel);
			}
			dlg.setLocation(100, 100);
			dlg.setVisible(true);
			dlg.setSize(dlg.getPreferredSize());
			dlg.setVisible(false);
			dlg.setVisible(true);
			dlg.repaint();
		}
	} // class ActionLayout

	/**
	 * Constructor<br>
	 * Sets up the gui and initializes all the other previously uninitialized
	 * variables.
	 */
	public GUI() {
		m_GraphPanel = new GraphPanel();
		m_jScrollPane = new JScrollPane(m_GraphPanel);

		// creating a new layout engine and adding this class as its listener
		// to receive layoutComplete events
	
		m_jTfZoom = new JTextField("100%");
		m_jTfZoom.setMinimumSize(m_jTfZoom.getPreferredSize());
		m_jTfZoom.setHorizontalAlignment(JTextField.CENTER);
		m_jTfZoom.setToolTipText("Zoom");

		m_jTfZoom.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent ae) {
				JTextField jt = (JTextField) ae.getSource();
				try {
					int i = -1;
					i = jt.getText().indexOf('%');
					if (i == -1)
						i = Integer.parseInt(jt.getText());
					else
						i = Integer.parseInt(jt.getText().substring(0, i));

					if (i <= 999)
						m_fScale = i / 100D;

					jt.setText((int) (m_fScale * 100) + "%");
					if (m_fScale > 0.1) {
						if (!a_zoomout.isEnabled())
							a_zoomout.setEnabled(true);
					} else
						a_zoomout.setEnabled(false);
					if (m_fScale < 9.99) {
						if (!a_zoomin.isEnabled())
							a_zoomin.setEnabled(true);
					} else
						a_zoomin.setEnabled(false);
					setAppropriateSize();
					// m_GraphPanel.clearBuffer();
					m_GraphPanel.repaint();
					m_GraphPanel.invalidate();
					m_jScrollPane.revalidate();
				} catch (NumberFormatException ne) {
					JOptionPane.showMessageDialog(GUI.this.getParent(),
							"Invalid integer entered for zoom.", "Error", JOptionPane.ERROR_MESSAGE);
					jt.setText((m_fScale * 100) + "%");
				}
			}
		});

		GridBagConstraints gbc = new GridBagConstraints();

		final JPanel p = new JPanel(new GridBagLayout());
		p.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createTitledBorder("ExtraControls"), BorderFactory
				.createEmptyBorder(4, 4, 4, 4)));
		p.setPreferredSize(new Dimension(0, 0));

		m_jTbTools = new JToolBar();
		m_jTbTools.setFloatable(false);
		m_jTbTools.setLayout(new GridBagLayout());
		gbc.anchor = GridBagConstraints.NORTHWEST;
		gbc.gridwidth = GridBagConstraints.REMAINDER;
		gbc.insets = new Insets(0, 0, 0, 0);
		m_jTbTools.add(p, gbc);
		gbc.gridwidth = 1;


		m_jTbTools.add(a_new);
		m_jTbTools.add(a_save);
		m_jTbTools.add(a_load);
		m_jTbTools.addSeparator(new Dimension(2, 2));
		m_jTbTools.add(a_cutnode);
		m_jTbTools.add(a_copynode);
		m_jTbTools.add(a_pastenode);
		m_jTbTools.addSeparator(new Dimension(2, 2));
		m_jTbTools.add(a_undo);
		m_jTbTools.add(a_redo);
		m_jTbTools.addSeparator(new Dimension(2, 2));
		m_jTbTools.add(a_alignleft);
		m_jTbTools.add(a_alignright);
		m_jTbTools.add(a_aligntop);
		m_jTbTools.add(a_alignbottom);
		m_jTbTools.add(a_centerhorizontal);
		m_jTbTools.add(a_centervertical);
		m_jTbTools.add(a_spacehorizontal);
		m_jTbTools.add(a_spacevertical);

		
		m_jTbTools.addSeparator(new Dimension(2, 2));
		m_jTbTools.add(a_zoomin);

		gbc.fill = GridBagConstraints.VERTICAL;
		gbc.weighty = 1;
		JPanel p2 = new JPanel(new BorderLayout());
		p2.setPreferredSize(m_jTfZoom.getPreferredSize());
		p2.setMinimumSize(m_jTfZoom.getPreferredSize());
		p2.add(m_jTfZoom, BorderLayout.CENTER);
		m_jTbTools.add(p2, gbc);
		gbc.weighty = 0;
		gbc.fill = GridBagConstraints.NONE;

		m_jTbTools.add(a_zoomout);
		m_jTbTools.addSeparator(new Dimension(2, 2));

		// jTbTools.add(jBtExtraControls, gbc);
		m_jTbTools.add(a_layout);
		m_jTbTools.addSeparator(new Dimension(4, 2));
		gbc.weightx = 1;
		gbc.fill = GridBagConstraints.BOTH;
		//jTbTools.add(m_layoutEngine.getProgressBar(), gbc);
		m_jStatusBar = new JLabel("Status bar");

		this.setLayout(new BorderLayout());
		this.add(m_jTbTools, BorderLayout.NORTH);
		this.add(m_jScrollPane, BorderLayout.CENTER);
		this.add(m_jStatusBar, BorderLayout.SOUTH);

		updateStatus();
		a_datagenerator.setEnabled(false);
		
		makeMenuBar();
	}
	
	/**
	 * Get the menu bar for this application.
	 * 
	 * @return the menu bar
	 */
	public JMenuBar getMenuBar() {
	  return m_menuBar;
	}
	
	private void makeMenuBar() {
          m_menuBar = new JMenuBar();
          JMenu fileMenu = new JMenu("File");
          fileMenu.setMnemonic('F');

          m_menuBar.add(fileMenu);
          fileMenu.add(a_new);
          fileMenu.add(a_load);
          fileMenu.add(a_save);
          fileMenu.add(a_saveas);
          fileMenu.addSeparator();
          fileMenu.add(a_print);
          fileMenu.add(a_export);
          fileMenu.addSeparator();
          fileMenu.add(a_quit);
          JMenu editMenu = new JMenu("Edit");
          editMenu.setMnemonic('E');
          m_menuBar.add(editMenu);
          editMenu.add(a_undo);
          editMenu.add(a_redo);
          editMenu.addSeparator();
          editMenu.add(a_selectall);
          editMenu.add(a_delnode);
          editMenu.add(a_cutnode);
          editMenu.add(a_copynode);
          editMenu.add(a_pastenode);
          editMenu.addSeparator();
          editMenu.add(a_addnode);
          editMenu.add(a_addarc);
          editMenu.add(a_delarc);
          editMenu.addSeparator();
          editMenu.add(a_alignleft);
          editMenu.add(a_alignright);
          editMenu.add(a_aligntop);
          editMenu.add(a_alignbottom);
          editMenu.add(a_centerhorizontal);
          editMenu.add(a_centervertical);
          editMenu.add(a_spacehorizontal);
          editMenu.add(a_spacevertical);

          JMenu toolMenu = new JMenu("Tools");
          toolMenu.setMnemonic('T');
          toolMenu.add(a_networkgenerator);
          toolMenu.add(a_datagenerator);
          toolMenu.add(a_datasetter);
          toolMenu.add(a_learn);
          toolMenu.add(a_learnCPT);
          toolMenu.addSeparator();
          toolMenu.add(a_layout);
          toolMenu.addSeparator();
          final JCheckBoxMenuItem viewMargins = new JCheckBoxMenuItem("Show Margins", false);
          viewMargins.addActionListener(new ActionListener() {
                  public void actionPerformed(ActionEvent ae) {
                          boolean bPrev = m_bViewMargins; 
                          m_bViewMargins = viewMargins.getState();
                          if (bPrev == false && viewMargins.getState() == true) {
                                  updateStatus();
                          }
                          repaint();
                  }
          });
          toolMenu.add(viewMargins);
          final JCheckBoxMenuItem viewCliques = new JCheckBoxMenuItem("Show Cliques", false);
          viewCliques.addActionListener(new ActionListener() {
                  public void actionPerformed(ActionEvent ae) {
                          boolean bPrev = m_bViewCliques; 
                          m_bViewCliques = viewCliques.getState();
                          if (bPrev == false && viewCliques.getState() == true) {
                                  updateStatus();
                          }
                          repaint();
                  }
          });
          toolMenu.add(viewCliques);

          m_menuBar.add(toolMenu);
          JMenu viewMenu = new JMenu("View");
          viewMenu.setMnemonic('V');
          m_menuBar.add(viewMenu);
          viewMenu.add(a_zoomin);
          viewMenu.add(a_zoomout);
          viewMenu.addSeparator();
          viewMenu.add(a_viewtoolbar);
          viewMenu.add(a_viewstatusbar);

          JMenu helpMenu = new JMenu("Help");
          helpMenu.setMnemonic('H');
          m_menuBar.add(helpMenu);
          helpMenu.add(a_help);
          helpMenu.add(a_about);
	}

	/**
	 * This method sets the node size that is appropriate considering the
	 * maximum label size that is present. It is used internally when custom
	 * node size checkbox is unchecked.
	 */
	protected void setAppropriateNodeSize() {
		int strWidth;
		FontMetrics fm = this.getFontMetrics(this.getFont());
		int nMaxStringWidth = DEFAULT_NODE_WIDTH;
		if (nMaxStringWidth == 0)
			for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
				strWidth = fm.stringWidth(m_BayesNet.getNodeName(iNode));
				if (strWidth > nMaxStringWidth)
					nMaxStringWidth = strWidth;
			}
		m_nNodeWidth = nMaxStringWidth + 4;
		m_nPaddedNodeWidth = m_nNodeWidth + PADDING;
		m_jTfNodeWidth.setText("" + m_nNodeWidth);

		m_nNodeHeight = 2 * fm.getHeight();
		m_jTfNodeHeight.setText("" + m_nNodeHeight);
	}

	/**
	 * Sets the preferred size for m_GraphPanel GraphPanel to the minimum size that is
	 * neccessary to display the graph.
	 */
	public void setAppropriateSize() {
		int maxX = 0, maxY = 0;

		m_GraphPanel.setScale(m_fScale, m_fScale);

		for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
			int nPosX = m_BayesNet.getPositionX(iNode); 
			int nPosY = m_BayesNet.getPositionY(iNode); 
			if (maxX < nPosX)
				maxX = nPosX + 100;
			if (maxY < nPosY)
				maxY = nPosY;
		}
		m_GraphPanel.setPreferredSize(new Dimension((int) ((maxX + m_nPaddedNodeWidth + 2) * m_fScale),
				(int) ((maxY + m_nNodeHeight + 2) * m_fScale)));
		m_GraphPanel.revalidate();
	} // setAppropriateSize

	/**
	 * This method is an implementation for LayoutCompleteEventListener class.
	 * It sets the size appropriate for m_GraphPanel GraphPanel and and revalidates it's
	 * container JScrollPane once a LayoutCompleteEvent is received from the
	 * LayoutEngine. Also, it updates positions of the Bayesian network stored
	 * in m_BayesNet.
	 */
	public void layoutCompleted(LayoutCompleteEvent le) {
		LayoutEngine layoutEngine  = m_layoutEngine; // (LayoutEngine) le.getSource();
		FastVector nPosX = new FastVector(m_BayesNet.getNrOfNodes());
		FastVector nPosY = new FastVector(m_BayesNet.getNrOfNodes());
		for (int iNode = 0; iNode < layoutEngine.getNodes().size(); iNode++) {
			GraphNode gNode = (GraphNode) layoutEngine.getNodes().elementAt(iNode);
			if (gNode.nodeType == GraphNode.NORMAL) {
				nPosX.addElement(gNode.x);
				nPosY.addElement(gNode.y);
			}
		}
		m_BayesNet.layoutGraph(nPosX, nPosY);
		m_jStatusBar.setText("Graph layed out");
		a_undo.setEnabled(true);
		a_redo.setEnabled(false);
		setAppropriateSize();
		m_GraphPanel.invalidate();
		m_jScrollPane.revalidate();
		m_GraphPanel.repaint();
	} // layoutCompleted


	/**
	 * BIF reader<br>
	 * Reads a graph description in XMLBIF03 from an file
	 * with name sFileName
	 */
	public void readBIFFromFile(String sFileName) throws BIFFormatException, IOException {
		m_sFileName = sFileName;
		try {

			BIFReader bayesNet = new BIFReader();
			bayesNet.processFile(sFileName);
			m_BayesNet = new EditableBayesNet(bayesNet);
			updateStatus();
			a_datagenerator.setEnabled(m_BayesNet.getNrOfNodes() > 0);
			m_BayesNet.clearUndoStack();
		} catch (Exception ex) {
			ex.printStackTrace();
			return;
		}

		setAppropriateNodeSize();
		setAppropriateSize();
	} // readBIFFromFile

	/* read arff file from file sFileName 
	 * and start new Bayesian network with nodes 
	 * representing attributes in data set.
	 */
	void initFromArffFile(String sFileName) {
		try {
			Instances instances = new Instances(new FileReader(sFileName));
			m_BayesNet = new EditableBayesNet(instances);
			m_Instances = instances;
			a_learn.setEnabled(true);
			a_learnCPT.setEnabled(true);
			setAppropriateNodeSize();
			setAppropriateSize();
		} catch (Exception ex) {
			ex.printStackTrace();
			return;
		}
	} // initFromArffFile
	
	/**
	 * The panel which contains the actual Bayeian network.
	 */
	private class GraphPanel extends PrintablePanel implements Printable {

		/** for serialization */
		private static final long serialVersionUID = -3562813603236753173L;

		/** node drawing modes */
		final static int HIGHLIGHTED = 1;
		final static int NORMAL = 0;

		public GraphPanel() {
			super();
			this.addMouseListener(new GraphVisualizerMouseListener());
			this.addMouseMotionListener(new GraphVisualizerMouseMotionListener());
			this.setToolTipText("");
		} // c'tor

		/* For showing instructions when hovering over a node
		 *  (non-Javadoc)
		 * @see javax.swing.JComponent#getToolTipText(java.awt.event.MouseEvent)
		 */
		public String getToolTipText(MouseEvent me) {
			int x, y;
			Rectangle r;
			x = y  = 0;

			r = new Rectangle(0, 0, (int) (m_nPaddedNodeWidth * m_fScale), (int) (m_nNodeHeight * m_fScale));
			x += me.getX();
			y += me.getY();
			
			for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
				r.x = (int) (m_BayesNet.getPositionX(iNode) * m_fScale);
				r.y = (int) (m_BayesNet.getPositionY(iNode) * m_fScale);
				if (r.contains(x, y)) {
					return m_BayesNet.getNodeName(iNode) + " (right click to manipulate this node)";
				}
			}
			return null;
		} // getToolTipText

		/* Code for showing the graph in the panel.
		 *  (non-Javadoc)
		 * @see javax.swing.JComponent#paintComponent(java.awt.Graphics)
		 */
		public void paintComponent(Graphics gr) {
			Graphics2D g = (Graphics2D) gr;
			RenderingHints rh = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
			rh.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
			g.setRenderingHints(rh);
			g.scale(m_fScale, m_fScale);
			Rectangle r = g.getClipBounds();
			g.clearRect(r.x, r.y, r.width, r.height);

			if (m_bViewCliques) {
				m_nClique = 1;
				viewCliques(g, m_marginCalculator.m_root);
			}			
			for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
				drawNode(g, iNode, NORMAL);
			}
			if (!a_export.isExporting() && !a_print.isPrinting()) {
				m_Selection.draw(g);
			}
			if (m_nSelectedRect != null) {
				g.drawRect((int)(m_nSelectedRect.x/ m_fScale), 
						(int)(m_nSelectedRect.y/ m_fScale), 
						(int)(m_nSelectedRect.width/ m_fScale), 
						(int)(m_nSelectedRect.height/ m_fScale));
			}
		} // paintComponent

		/** number of the clique being drawn. Used for selecting the color of the clique */
		int m_nClique = 1;

		/* draws cliques in junction tree. 
		 * 
		 */
		void viewCliques(Graphics g, JunctionTreeNode node) {
					int [] nodes = node.m_nNodes;
					g.setColor(
							new Color(m_nClique % 7 * 256 /7, 
							(m_nClique % 2 * 256 / 2),
							(m_nClique % 3 * 256 / 3))
							);
					int dX = m_nPaddedNodeWidth / 2 + m_nClique;
					int dY = m_nNodeHeight / 2;
					int nPosX = 0;
					int nPosY = 0;
					String sStr = "";
					for (int j = 0; j < nodes.length; j++) {
						nPosX += m_BayesNet.getPositionX(nodes[j]);
						nPosY += m_BayesNet.getPositionY(nodes[j]);
						sStr += " " + nodes[j];
						for (int k = j+1; k < nodes.length; k++) {
							g.drawLine(
									m_BayesNet.getPositionX(nodes[j]) + dX,
									m_BayesNet.getPositionY(nodes[j]) + dY,
									m_BayesNet.getPositionX(nodes[k]) + dX,
									m_BayesNet.getPositionY(nodes[k]) + dY
									);
						}
					}
					m_nClique++;
					nPosX /= nodes.length;
					nPosY /= nodes.length;
					g.drawString("Clique " + m_nClique + "("+sStr+")", nPosX, nPosY);
					for (int iChild = 0; iChild < node.m_children.size(); iChild++) {
						viewCliques(g, (JunctionTreeNode) node.m_children.elementAt(iChild));
					}
		} // viewCliques
		
		
		/* Draw a node with index iNode on Graphics g at position
		 * Drawing mode can be NORMAL or HIGHLIGHTED.
		 */
		protected void drawNode(Graphics g, int iNode, int mode) {
			int nPosX = m_BayesNet.getPositionX(iNode);
			int nPosY = m_BayesNet.getPositionY(iNode);
			g.setColor(this.getBackground().darker().darker());
			FontMetrics fm = getFontMetrics(getFont());
			
			if (mode == HIGHLIGHTED) {
				g.setXORMode(Color.green); // g.setColor(Color.green);
			}
			g.fillOval(nPosX + m_nPaddedNodeWidth - m_nNodeWidth - (m_nPaddedNodeWidth - m_nNodeWidth) / 2, nPosY,
					m_nNodeWidth, m_nNodeHeight);
			g.setColor(Color.white);
			if (mode == HIGHLIGHTED) {
				g.setXORMode(Color.red);
			}

			// Draw the node's label if it can fit inside the node's
			// current width otherwise just display its node nr
			// if it can fit in node's current width
			if (fm.stringWidth(m_BayesNet.getNodeName(iNode)) <= m_nNodeWidth) {
				g.drawString(m_BayesNet.getNodeName(iNode), nPosX + m_nPaddedNodeWidth / 2
						- fm.stringWidth(m_BayesNet.getNodeName(iNode)) / 2, nPosY + m_nNodeHeight / 2
						+ fm.getHeight() / 2 - 2);
			} else if (fm.stringWidth("" + iNode) <= m_nNodeWidth) {
				g.drawString("" + iNode, nPosX + m_nPaddedNodeWidth / 2 - fm.stringWidth("" + iNode) / 2, 
						nPosY + m_nNodeHeight / 2 + fm.getHeight() / 2 - 2);
			}

			if (mode == HIGHLIGHTED) {
				g.setXORMode(Color.green);
			}

			if (m_bViewMargins) {
				if (m_BayesNet.getEvidence(iNode) < 0) {
					g.setColor(new Color(0, 128, 0));
				} else {
					g.setColor(new Color(128, 0, 0));
				}
				double[] P = m_BayesNet.getMargin(iNode);
				for (int iValue = 0; iValue < P.length; iValue++) {
					String sP = P[iValue] + "";
					if (sP.charAt(0) == '0') {
						sP = sP.substring(1);
					}
					if (sP.length() > 5) {
						sP = sP.substring(1, 5);
					}
					g.fillRect(nPosX + m_nPaddedNodeWidth, nPosY + iValue * 10 + 2, (int) (P[iValue] * 100), 8);
					g.drawString(m_BayesNet.getNodeValue(iNode, iValue) + " " + sP, nPosX + m_nPaddedNodeWidth
							+ (int) (P[iValue] * 100), nPosY + iValue * 10 + 10);

				}
			}
			if (m_bViewCliques) {
					return;
			}
			g.setColor(Color.black);
			// Drawing all incoming edges into the node,
			for (int iParent = 0; iParent < m_BayesNet.getNrOfParents(iNode); iParent++) {
				int nParent = m_BayesNet.getParent(iNode, iParent);
				int nPosX1 = nPosX + m_nPaddedNodeWidth / 2;
				int nPosY1 = nPosY + m_nNodeHeight;
				int nPosX2 = m_BayesNet.getPositionX(nParent);
				int nPosY2 = m_BayesNet.getPositionY(nParent);
				int nPosX2b = nPosX2 + m_nPaddedNodeWidth / 2;
				int nPosY2b = nPosY2;

				double phi = Math.atan2((nPosX2b - nPosX1 + 0.0) * m_nNodeHeight, (nPosY2b - nPosY1 + 0.0) * m_nNodeWidth);
				nPosX1 = (int) (nPosX + m_nPaddedNodeWidth / 2 + Math.sin(phi) * m_nNodeWidth / 2);
				nPosY1 = (int) (nPosY + m_nNodeHeight / 2 + Math.cos(phi) * m_nNodeHeight / 2);
				nPosX2b = (int) (nPosX2 + m_nPaddedNodeWidth / 2 - Math.sin(phi) * m_nNodeWidth / 2);
				nPosY2b = (int) (nPosY2 + m_nNodeHeight / 2 - Math.cos(phi) * m_nNodeHeight / 2);
				drawArrow(g, nPosX2b, nPosY2b, nPosX1, nPosY1);
			}
			if (mode == HIGHLIGHTED) {
			FastVector children = m_BayesNet.getChildren(iNode);
			for (int iChild = 0; iChild < children.size(); iChild++) {
				int nChild = (Integer) children.elementAt(iChild);
				int nPosX1 = nPosX + m_nPaddedNodeWidth / 2;
				int nPosY1 = nPosY;
				int nPosX2 = m_BayesNet.getPositionX(nChild);
				int nPosY2 = m_BayesNet.getPositionY(nChild);
				int nPosX2b = nPosX2 + m_nPaddedNodeWidth / 2;
				int nPosY2b = nPosY2 + m_nNodeHeight;

				double phi = Math.atan2((nPosX2b - nPosX1 + 0.0) * m_nNodeHeight, (nPosY2b - nPosY1 + 0.0) * m_nNodeWidth);
				nPosX1 = (int) (nPosX + m_nPaddedNodeWidth / 2 + Math.sin(phi) * m_nNodeWidth / 2);
				nPosY1 = (int) (nPosY + m_nNodeHeight / 2 + Math.cos(phi) * m_nNodeHeight / 2);
				nPosX2b = (int) (nPosX2 + m_nPaddedNodeWidth / 2 - Math.sin(phi) * m_nNodeWidth / 2);
				nPosY2b = (int) (nPosY2 + m_nNodeHeight / 2 - Math.cos(phi) * m_nNodeHeight / 2);
				drawArrow(g, nPosX1, nPosY1, nPosX2b, nPosY2b);
			}
			}
		} // drawNode

		
		/**
		 * This method draws an arrow on a line from (x1,y1) to (x2,y2). The
		 * arrow head is seated on (x2,y2) and is in the direction of the line.
		 * If the arrow is needed to be drawn in the opposite direction then
		 * simply swap the order of (x1, y1) and (x2, y2) when calling this
		 * function.
		 */
		protected void drawArrow(Graphics g, int nPosX1, int nPosY1, int nPosX2, int nPosY2) {
			g.drawLine(nPosX1, nPosY1, nPosX2, nPosY2);

			if (nPosX1 == nPosX2) {
				if (nPosY1 < nPosY2) {
					g.drawLine(nPosX2, nPosY2, nPosX2 + 4, nPosY2 - 8);
					g.drawLine(nPosX2, nPosY2, nPosX2 - 4, nPosY2 - 8);
				} else {
					g.drawLine(nPosX2, nPosY2, nPosX2 + 4, nPosY2 + 8);
					g.drawLine(nPosX2, nPosY2, nPosX2 - 4, nPosY2 + 8);
				}
			} else {
				// theta=line's angle from base, beta=angle of arrow's side from
				// line
				double hyp = 0, base = 0, perp = 0, theta, beta;
				int nPosX3 = 0, nPosY3 = 0;

				if (nPosX2 < nPosX1) {
					base = nPosX1 - nPosX2;
					hyp = Math.sqrt((nPosX2 - nPosX1) * (nPosX2 - nPosX1) + (nPosY2 - nPosY1) * (nPosY2 - nPosY1));
					theta = Math.acos(base / hyp);
				} else { // x1>x2 as we already checked x1==x2 before
					base = nPosX1 - nPosX2;
					hyp = Math.sqrt((nPosX2 - nPosX1) * (nPosX2 - nPosX1) + (nPosY2 - nPosY1) * (nPosY2 - nPosY1));
					theta = Math.acos(base / hyp);
				}
				beta = 30 * Math.PI / 180;

				hyp = 8;
				base = Math.cos(theta - beta) * hyp;
				perp = Math.sin(theta - beta) * hyp;

				nPosX3 = (int) (nPosX2 + base);
				if (nPosY1 < nPosY2)
					nPosY3 = (int) (nPosY2 - perp);
				else
					nPosY3 = (int) (nPosY2 + perp);

				g.drawLine(nPosX2, nPosY2, nPosX3, nPosY3);

				base = Math.cos(theta + beta) * hyp;
				perp = Math.sin(theta + beta) * hyp;

				nPosX3 = (int) (nPosX2 + base);
				if (nPosY1 < nPosY2)
					nPosY3 = (int) (nPosY2 - perp);
				else
					nPosY3 = (int) (nPosY2 + perp);
				g.drawLine(nPosX2, nPosY2, nPosX3, nPosY3);
			}
		} // drawArrow

		/**
		 * This method highlights a given node and all its incoming and outgoing arcs
		 */
		public void highLight(int iNode) {
			Graphics2D g = (Graphics2D) this.getGraphics();
			RenderingHints rh = new RenderingHints(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
			rh.put(RenderingHints.KEY_RENDERING, RenderingHints.VALUE_RENDER_SPEED);
			g.setRenderingHints(rh);
			g.setPaintMode();
			g.scale(m_fScale, m_fScale);
			drawNode(g, iNode, HIGHLIGHTED);
		} // highlight

	    /** implementation of Printable, used for printing
	     * @see Printable
	     */
		public int print(Graphics g, PageFormat pageFormat, int pageIndex) {
	          if (pageIndex > 0) {
	            return(NO_SUCH_PAGE);
	          } else {
	            Graphics2D g2d = (Graphics2D)g;
	            g2d.translate(pageFormat.getImageableX(), pageFormat.getImageableY());
	            double fHeight = pageFormat.getImageableHeight();
	            double fWidth = pageFormat.getImageableWidth();
	            int xMax = 1;
	            int yMax = 1;
	            for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
	            	if (xMax < m_BayesNet.getPositionX(iNode)) {
	            		xMax = m_BayesNet.getPositionX(iNode);
	            	}
	            	if (yMax < m_BayesNet.getPositionY(iNode)) {
	            		yMax = m_BayesNet.getPositionY(iNode);
	            	}
	            }
	            double fCurrentScale = m_fScale;
	            xMax += m_nPaddedNodeWidth + 100;
	            if (fWidth/xMax < fHeight/yMax) {
	            	m_fScale = fWidth/xMax; 
	            } else {
	            	m_fScale = fHeight/yMax;
	            }
	            
	            // Turn off double buffering
	            paint(g2d);
	            m_fScale = fCurrentScale;
	            // Turn double buffering back on
	            return(PAGE_EXISTS);
	          }
	        } // print
				
	} // class GraphPanel

	/**
	 * Table Model for the Table for editing CPTs
	 */
	private class GraphVisualizerTableModel extends AbstractTableModel {

		/** for serialization */
		private static final long serialVersionUID = -4789813491347366596L;
		/** labels for the columns */
		final String [] m_sColumnNames;
		/** probability table data **/
		final double [][] m_fProbs;
		/** nr of node for currently editted CPT */
		int m_iNode;

		public GraphVisualizerTableModel(int iNode) {
			m_iNode = iNode;
			double [][] probs = m_BayesNet.getDistribution(iNode);
			m_fProbs = new double[probs.length][probs[0].length];
			for (int i = 0; i < probs.length; i++) {
				for (int j = 0; j < probs[0].length; j++) {
					m_fProbs[i][j] = probs[i][j];
				}
			}
			m_sColumnNames = m_BayesNet.getValues(iNode);
		} // c'tor

		/** method that generates random CPTs
		 */
		public void randomize() {
			int nProbs = m_fProbs[0].length;
			Random random = new Random();
			for (int i = 0; i < m_fProbs.length; i++) {
				// get random nrs
				for (int j = 0; j < nProbs-1; j++) {
					m_fProbs[i][j] = random.nextDouble();
				}
				// sort
				for (int j = 0; j < nProbs-1; j++) {
					for (int k = j+1; k < nProbs-1; k++) {
						if (m_fProbs[i][j] > m_fProbs[i][k]) {
							double h = m_fProbs[i][j]; 
							m_fProbs[i][j] = m_fProbs[i][k];
							m_fProbs[i][k] = h;
						}
					}
				}
				double sum = m_fProbs[i][0];
				for (int j = 1; j < nProbs-1; j++) {
					m_fProbs[i][j] = m_fProbs[i][j] - sum;
					sum += m_fProbs[i][j]; 
				}
				m_fProbs[i][nProbs - 1] = 1.0 - sum;
			}
		} // randomize
		
		public void setData() {}

		/** return nr of colums */
		public int getColumnCount() {
			return m_sColumnNames.length;
		}

		/** return nr of rows */
		public int getRowCount() {
			return m_fProbs.length;
		}

		/** return name of specified colum 
		 * @param iCol index of the column
		 */
		public String getColumnName(int iCol) {
			return m_sColumnNames[iCol];
		}
		/** return data point 
		 * @param iRow index of row in table
		 * @param iCol index of column in table
		 */
		public Object getValueAt(int iRow, int iCol) {
			return new Double(m_fProbs[iRow][iCol]);
		}

		/** Set data point, assigns value to CPT entry
		 * specified by row and column. The remainder of the
		 * CPT is normalized so that the values add up to 1.
		 * IF a value below zero of over 1 is given, no changes
		 * take place.
		 * @param oProb data point
		 * @param iRow index of row in table
		 * @param iCol index of column in table
		 */
		public void setValueAt(Object oProb, int iRow, int iCol) {
			Double fProb = (Double) oProb;
			if (fProb < 0 || fProb > 1) {
				return;
			}
			m_fProbs[iRow][iCol] = (double) fProb;
			double sum = 0;
			for (int i = 0; i < m_fProbs[iRow].length; i++) {
				sum += m_fProbs[iRow][i];
			}

			if (sum > 1) {
				// handle overflow
				int i = m_fProbs[iRow].length - 1;
				while (sum > 1) {
					if (i != iCol) {
						if (m_fProbs[iRow][i] > sum - 1) {
							m_fProbs[iRow][i] -= sum - 1;
							sum = 1;
						} else {
							sum -= m_fProbs[iRow][i];
							m_fProbs[iRow][i] = 0;
						}
					}
					i--;
				}
			} else {
				// handle underflow
				int i = m_fProbs[iRow].length - 1;
				while (sum < 1) {
					if (i != iCol) {
						m_fProbs[iRow][i] += 1 - sum;
						sum = 1;
					}
					i--;
				}

			}
			validate();
		} // setData

		/*
		 * JTable uses this method to determine the default renderer/ editor for
		 * each cell.
		 */
		public Class getColumnClass(int c) {
			return getValueAt(0, c).getClass();
		}

		/*
		 * Implemented this to make sure the table is uneditable.
		 */
		public boolean isCellEditable(int row, int col) {
			return true;
		}
	} // class GraphVisualizerTableModel

	/**
	 * Listener class for processing mouseClicked
	 */
	private class GraphVisualizerMouseListener extends MouseAdapter {

		/** A left mouseclick on a node adds node to selection (depending
		 * on shift and ctrl keys).
		 * A right mouseclick on a node pops up menu with actions to be
		 * performed on the node.
		 * A right mouseclick outside another node pops up menu.
		 */
		public void mouseClicked(MouseEvent me) {
			int x, y;

			Rectangle r = new Rectangle(0, 0, (int) (m_nPaddedNodeWidth * m_fScale), (int) (m_nNodeHeight * m_fScale));
			x = me.getX();
			y = me.getY();

			for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {
				r.x = (int) (m_BayesNet.getPositionX(iNode) * m_fScale);
				r.y = (int) (m_BayesNet.getPositionY(iNode) * m_fScale);
				if (r.contains(x, y)) {
					m_nCurrentNode = iNode;					
					if (me.getButton() == MouseEvent.BUTTON3) {
						handleRightNodeClick(me);
					}
					if (me.getButton() == MouseEvent.BUTTON1) {
						if ((me.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) != 0) {
							m_Selection.toggleSelection(m_nCurrentNode);
						} else if ((me.getModifiersEx() & MouseEvent.SHIFT_DOWN_MASK) != 0) {
							m_Selection.addToSelection(m_nCurrentNode);
						} else {
							m_Selection.clear();
							m_Selection.addToSelection(m_nCurrentNode);
						}
						repaint();
					}
					return;
				}
			}
			if (me.getButton() == MouseEvent.BUTTON3) {
				handleRightClick(me, (int)(x/m_fScale), (int)(y/m_fScale));
			}
		} // mouseClicked
		
		/* update selection
		 *  (non-Javadoc)
		 * @see java.awt.event.MouseListener#mouseReleased(java.awt.event.MouseEvent)
		 */
		public void mouseReleased(MouseEvent me) {
	    	if (m_nSelectedRect != null) {
				if ((me.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) != 0) {
					m_Selection.toggleSelection(m_nSelectedRect);
				} else if ((me.getModifiersEx() & MouseEvent.SHIFT_DOWN_MASK) != 0) {
					m_Selection.addToSelection(m_nSelectedRect);
				} else {
					m_Selection.clear();
		    		m_Selection.addToSelection(m_nSelectedRect);
				}
	    		m_nSelectedRect = null;
	    		repaint();
	    	}
	    } // mouseReleased

		/** position clicked on */
	    int m_nPosX = 0, m_nPosY = 0;
	    
	    /* pop up menu with actions that apply in general or to selection (if any exists)
	     */
	    void handleRightClick(MouseEvent me, int nPosX, int nPosY) {
	
			ActionListener act = new ActionListener() {
				public void actionPerformed(ActionEvent ae) {
					if (ae.getActionCommand().equals("Add node")) {
						a_addnode.addNode(m_nPosX, m_nPosY);
						return;
					}
					repaint();
				}
			};
			JPopupMenu popupMenu = new JPopupMenu("Choose a value");

			JMenuItem addNodeItem = new JMenuItem("Add node");
			addNodeItem.addActionListener(act);
			popupMenu.add(addNodeItem);

			FastVector selected = m_Selection.getSelected();
			JMenu addArcMenu = new JMenu("Add parent");
			popupMenu.add(addArcMenu);
			if (selected.size() == 0) {
				addArcMenu.setEnabled(false);
			} else {
			int nNodes = m_BayesNet.getNrOfNodes();
			boolean[] isNotAllowedAsParent = new boolean[nNodes];
			// prevent it being a parent of itself
			for (int iNode = 0; iNode < selected.size(); iNode++) {
				isNotAllowedAsParent[(Integer) selected.elementAt(iNode)] = 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 iNode = 0; iNode < selected.size(); iNode++) {
				int nNode = (Integer) selected.elementAt(iNode);
				for (int iParent = 0; iParent < m_BayesNet.getNrOfParents(nNode); iParent++) {
					isNotAllowedAsParent[m_BayesNet.getParent(nNode, iParent)] = true;
				}
			}
			ActionListener addParentAction = new ActionListener() {
				public void actionPerformed(ActionEvent ae) {
					try {
						m_BayesNet.addArc(ae.getActionCommand(), m_Selection.getSelected());
						m_jStatusBar.setText(m_BayesNet.lastActionMsg());
						updateStatus();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			};
			// count nr of remaining candidates
			int nCandidates = 0;
			for (int i = 0; i < nNodes; i++) {
				if (!isNotAllowedAsParent[i]) {
					JMenuItem item = new JMenuItem(m_BayesNet.getNodeName(i));
					item.addActionListener(addParentAction);
					addArcMenu.add(item);
					nCandidates++;
				}
			}
			if (nCandidates == 0) {
				addArcMenu.setEnabled(false);
			}
			}
			m_nPosX = nPosX;
			m_nPosY = nPosY;
			popupMenu.setLocation(me.getX(), me.getY());
			popupMenu.show(m_GraphPanel, me.getX(), me.getY());
	    } // handleRightClick
		
	    /* pop up menu with actions that apply to node that was clicked on
	     */
	    void handleRightNodeClick(MouseEvent me) {
			m_Selection.clear();
			repaint();
			ActionListener renameValueAction = new ActionListener() {
				public void actionPerformed(ActionEvent ae) {
					renameValue(m_nCurrentNode, ae.getActionCommand());
				}
			};
			ActionListener delValueAction = new ActionListener() {
				public void actionPerformed(ActionEvent ae) {
					delValue(m_nCurrentNode, ae.getActionCommand());
				}
			};
			ActionListener addParentAction = new ActionListener() {
				public void actionPerformed(ActionEvent ae) {
					try {
						m_BayesNet.addArc(ae.getActionCommand(), m_BayesNet.getNodeName(m_nCurrentNode));
						m_jStatusBar.setText(m_BayesNet.lastActionMsg());
						updateStatus();
					} catch (Exception e) {
						e.printStackTrace();
					}
				}
			};
			ActionListener delParentAction = new ActionListener() {
				public void actionPerformed(ActionEvent ae) {
					deleteArc(m_nCurrentNode, ae.getActionCommand());
				}
			};
			ActionListener delChildAction = new ActionListener() {
				public void actionPerformed(ActionEvent ae) {
					deleteArc(ae.getActionCommand(), m_nCurrentNode);
				}
			};
			ActionListener setAvidenceAction = new ActionListener() {
				public void actionPerformed(ActionEvent ae) {
					try {
						String [] outcomes = m_BayesNet.getValues(m_nCurrentNode);
						int iValue = 0;
						while (iValue < outcomes.length && !outcomes[iValue].equals(ae.getActionCommand())) {
							iValue++;
						}

						if (iValue == outcomes.length) {
							iValue = -1;
						}
						if (iValue < outcomes.length) {
						    m_jStatusBar.setText("Set evidence for " + m_BayesNet.getNodeName(m_nCurrentNode));
								if (m_BayesNet.getEvidence(m_nCurrentNode) < 0 && iValue >= 0) {
									m_BayesNet.setEvidence(m_nCurrentNode, iValue);
									m_marginCalculatorWithEvidence.setEvidence(m_nCurrentNode, iValue);
								} else {
									m_BayesNet.setEvidence(m_nCurrentNode, iValue);
									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();
				}
			};
			
			ActionListener act = new ActionListener() {
				public void actionPerformed(ActionEvent ae) {
					if (ae.getActionCommand().equals("Rename")) {
						renameNode(m_nCurrentNode);
						return;
					}
					if (ae.getActionCommand().equals("Add parent")) {
						addArcInto(m_nCurrentNode);
						return;
					}
					if (ae.getActionCommand().equals("Add value")) {
						addValue();
						return;
					}
					if (ae.getActionCommand().equals("Delete node")) {
						deleteNode(m_nCurrentNode);
						return;
					}
					if (ae.getActionCommand().equals("Edit CPT")) {
						editCPT(m_nCurrentNode);
						return;
					}
					repaint();
				}
			};
			try {
			JPopupMenu popupMenu = new JPopupMenu("Choose a value");

			JMenu setEvidenceMenu = new JMenu("Set evidence");
			String [] outcomes = m_BayesNet.getValues(m_nCurrentNode);
			for (int iValue = 0; iValue < outcomes.length; iValue++) {
				JMenuItem item = new JMenuItem(outcomes[iValue]);
				item.addActionListener(setAvidenceAction);
				setEvidenceMenu.add(item);
			}
			setEvidenceMenu.addSeparator();
			JMenuItem item = new JMenuItem("Clear");
			item.addActionListener(setAvidenceAction);
			setEvidenceMenu.add(item);
			popupMenu.add(setEvidenceMenu);

			setEvidenceMenu.setEnabled(m_bViewMargins);

			popupMenu.addSeparator();

			JMenuItem renameItem = new JMenuItem("Rename");
			renameItem.addActionListener(act);
			popupMenu.add(renameItem);

			JMenuItem delNodeItem = new JMenuItem("Delete node");
			delNodeItem.addActionListener(act);
			popupMenu.add(delNodeItem);

			JMenuItem editCPTItem = new JMenuItem("Edit CPT");
			editCPTItem.addActionListener(act);
			popupMenu.add(editCPTItem);

			popupMenu.addSeparator();
			
			JMenu addArcMenu = new JMenu("Add parent");
			popupMenu.add(addArcMenu);
			int nNodes = m_BayesNet.getNrOfNodes();
			boolean[] isNotAllowedAsParent = new boolean[nNodes];
			// prevent it being a parent of itself
			isNotAllowedAsParent[m_nCurrentNode] = 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(m_nCurrentNode); iParent++) {
				isNotAllowedAsParent[m_BayesNet.getParent(m_nCurrentNode, iParent)] = true;
			}
			// count nr of remaining candidates
			int nCandidates = 0;
			for (int i = 0; i < nNodes; i++) {
				if (!isNotAllowedAsParent[i]) {
					item = new JMenuItem(m_BayesNet.getNodeName(i));
					item.addActionListener(addParentAction);
					addArcMenu.add(item);
					nCandidates++;
				}
			}
			if (nCandidates == 0) {
				addArcMenu.setEnabled(false);
			}
									
			JMenu delArcMenu = new JMenu("Delete parent");
			popupMenu.add(delArcMenu);
			if (m_BayesNet.getNrOfParents(m_nCurrentNode) == 0) {
				delArcMenu.setEnabled(false);
			}
			for (int iParent = 0; iParent < m_BayesNet.getNrOfParents(m_nCurrentNode); iParent++) {
				item = new JMenuItem(m_BayesNet.getNodeName(m_BayesNet.getParent(m_nCurrentNode, iParent)));
				item.addActionListener(delParentAction);
				delArcMenu.add(item);
			}
			JMenu delChildMenu = new JMenu("Delete child");
			popupMenu.add(delChildMenu);
			FastVector nChildren = m_BayesNet.getChildren(m_nCurrentNode); 
			if (nChildren.size() == 0) {
				delChildMenu.setEnabled(false);
			}
			for (int iChild = 0; iChild < nChildren.size(); iChild++) {
				item = new JMenuItem(m_BayesNet.getNodeName((Integer) nChildren.elementAt(iChild)));
				item.addActionListener(delChildAction);
				delChildMenu.add(item);
			}

			popupMenu.addSeparator();

			JMenuItem addValueItem = new JMenuItem("Add value");
			addValueItem.addActionListener(act);
			popupMenu.add(addValueItem);

			JMenu renameValue = new JMenu("Rename value");
			popupMenu.add(renameValue);
			for (int iValue = 0; iValue < outcomes.length; iValue++) {
				item = new JMenuItem(outcomes[iValue]);
				item.addActionListener(renameValueAction);
				renameValue.add(item);
			}

			JMenu delValue = new JMenu("Delete value");
			popupMenu.add(delValue);
			if (m_BayesNet.getCardinality(m_nCurrentNode) <= 2) {
				delValue.setEnabled(false);
			}
			for (int iValue = 0; iValue < outcomes.length; iValue++) {
				JMenuItem delValueItem = new JMenuItem(outcomes[iValue]);
				delValueItem.addActionListener(delValueAction);
				delValue.add(delValueItem);
			}
			
			popupMenu.setLocation(me.getX(), me.getY());
			popupMenu.show(m_GraphPanel, me.getX(), me.getY());
			} catch (Exception e) {
				e.printStackTrace();
			}
		} // handleRightNodeClick
	} // class GraphVisualizerMouseListener

	/**
	 * private class for handling mouseMoved events to highlight nodes if the
	 * the mouse is moved on one, move it around or move selection around
	 */
	private class GraphVisualizerMouseMotionListener extends MouseMotionAdapter {

		/* last node moved over. Used for turning highlight on and off */
		int m_nLastNode = -1;
		/* current mouse position clicked */
		int m_nPosX, m_nPosY;

		/* identify the node under the mouse
		 * @returns node index of node under mouse, or -1 if there is no such node 
		 */
		int getGraphNode(MouseEvent me) {
			m_nPosX = m_nPosY  = 0;

			Rectangle r = new Rectangle(0, 0, (int) (m_nPaddedNodeWidth * m_fScale), (int) (m_nNodeHeight * m_fScale));
			m_nPosX += me.getX();
			m_nPosY += me.getY();

			for (int iNode = 0; iNode < m_BayesNet.getNrOfNodes(); iNode++) {				
				r.x = (int) (m_BayesNet.getPositionX(iNode) * m_fScale);
				r.y = (int) (m_BayesNet.getPositionY(iNode) * m_fScale);
				if (r.contains(m_nPosX, m_nPosY)) {
					return iNode;
				}
			}
			return -1;
		} // getGraphNode

		/* handle mouse dragging event
		 *  (non-Javadoc)
		 * @see java.awt.event.MouseMotionListener#mouseDragged(java.awt.event.MouseEvent)
		 */
		public void mouseDragged(MouseEvent me) {
			if (m_nSelectedRect != null) {
				m_nSelectedRect.width = me.getPoint().x - m_nSelectedRect.x;
				m_nSelectedRect.height = me.getPoint().y - m_nSelectedRect.y;
				repaint();
				return;
			}
			int iNode = getGraphNode(me);
			if (iNode >= 0) {
				if (m_Selection.getSelected().size() > 0) {
					if (m_Selection.getSelected().contains(iNode)) {
						m_BayesNet.setPosition(iNode, (int) ((m_nPosX / m_fScale - m_nPaddedNodeWidth / 2)),
							(int) ((m_nPosY / m_fScale - m_nNodeHeight / 2)), m_Selection.getSelected());
					} else {
						m_Selection.clear();
						m_BayesNet.setPosition(iNode, (int) ((m_nPosX / m_fScale - m_nPaddedNodeWidth / 2)),
								(int) ((m_nPosY / m_fScale - m_nNodeHeight / 2)));
					}
					repaint();
				} else {
					m_BayesNet.setPosition(iNode, (int) ((m_nPosX / m_fScale - m_nPaddedNodeWidth / 2)),
						(int) ((m_nPosY / m_fScale - m_nNodeHeight / 2)));
				}
			    m_jStatusBar.setText(m_BayesNet.lastActionMsg());
				a_undo.setEnabled(true);
				a_redo.setEnabled(false);
				m_GraphPanel.highLight(iNode);
			}
			if (iNode < 0) {
				if (m_nLastNode >= 0) {
					m_GraphPanel.repaint();
					m_nLastNode = -1;
				} else {
					m_nSelectedRect = new Rectangle(me.getPoint().x, me.getPoint().y, 1, 1);
					m_GraphPanel.repaint();
				}
			}
		} // mouseDragged
		
		/* handles mouse move event
		 *  (non-Javadoc)
		 * @see java.awt.event.MouseMotionListener#mouseMoved(java.awt.event.MouseEvent)
		 */
		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);
			}
		}