/* * CCVisu is a tool for visual graph clustering * and general force-directed graph layout. * This file is part of CCVisu. * * Copyright (C) 2005-2007 Dirk Beyer * * CCVisu is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License * as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * CCVisu is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with CCVisu; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * Please find the GNU Lesser General Public License in file * license_lgpl.txt or http://www.gnu.org/licenses/lgpl.txt * * Dirk Beyer (firstname.lastname@sfu.ca) * Simon Fraser University (SFU), B.C., Canada */ package ccvisu; import java.awt.BorderLayout; import java.awt.Button; import java.awt.Checkbox; import java.awt.Choice; import java.awt.Color; import java.awt.Dialog; import java.awt.FileDialog; import java.awt.GridLayout; import java.awt.GridBagLayout; import java.awt.GridBagConstraints; import java.awt.Label; import java.awt.List; import java.awt.Panel; import java.awt.TextField; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.ItemEvent; import java.awt.event.ItemListener; import java.awt.event.KeyAdapter; import java.awt.event.KeyEvent; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.File; import java.io.FileReader; import java.io.FileWriter; import java.io.FilenameFilter; import java.io.IOException; import java.io.PrintWriter; /** * a GUI to manage the clusters define new, remove, ... * @version $Revision$; $Date$ * @author Damien Zufferey */ public class ClusterManager extends Dialog { private static final long serialVersionUID = 5669096380733602612L; /** what display the cluster's names */ private List lst; /** to choose the color to display */ private Choice color; /** to choose if the cluster should be diplayed */ private Checkbox visible; /** to choose if the default cluster should be diplayed */ private Checkbox defaultVisible; /** to choose if the cluster's info should be diplayed */ private Checkbox infoVisible; /** to choose if the cluster's nodes should be diplayed */ private Button showLabel; /** to choose if the cluster's nodes should be hidden */ private Button hideLabel; /** display informations */ private Label numberOfNode; /** display informations */ private Label radius; /** save clusters */ private Button save; /** load clusters */ private Button load; /** to add a new cluster */ private Button newCluster; /** to edit a cluster */ private Button editCluster; /** to remove a cluster */ private Button removeCluster; /** to change the position of a cluster in the rendering order */ private Button up; /** to change the position of a cluster in the rendering order */ private Button down; // temporarily used private Dialog diag; private Dialog diag2; /** cluster curently edited */ private Cluster curClt; // used to get some input private TextField txt; // nodes list from a specific cluster private List cltNodes; // list from all nodes in the graph private List allNodes; // to select the way the pattern handled private Choice mode;// equals,contains,... private Choice mode2;// keep, remove /** where the clusters are stocked */ private WriterDataGraphicsDISP parent; /** notify when significant changes are done */ private ScreenDisplay display; /** * Constructor * @param pparent - WriterDataGraphicsDISP containing the clusters * @param disp - a GraphEventListener charged of the rendering */ public ClusterManager(ScreenDisplay disp,WriterDataGraphicsDISP pparent){ super(disp,"Group highlighting"); this.parent = pparent; this.display = disp; this.setLayout(new BorderLayout()); save = new Button("Save as..."); load = new Button("Load ..."); Panel north = new Panel(); north.add(save); north.add(load); add(north,BorderLayout.NORTH); lst = new List(10, false); refreshList(); add(lst, BorderLayout.CENTER); radius = new Label(" "); radius.setAlignment(Label.RIGHT); numberOfNode = new Label(" "); numberOfNode.setAlignment(Label.RIGHT); visible = new Checkbox("Show group", true); infoVisible = new Checkbox("Graphic informations", false); defaultVisible = new Checkbox("Show group-free vertices",true); color = new Choice(); color.add("red"); color.add("green"); color.add("blue"); color.add("yellow"); color.add("magenta"); color.add("cyan"); color.add("light red"); color.add("light green"); color.add("light blue"); color.add("dark yellow"); color.add("dark magenta"); color.add("dark cyan"); color.add("pink"); color.add("white"); color.add("light gray"); color.add("gray"); color.add("dark gray"); color.add("black"); color.select(0); newCluster = new Button("New"); removeCluster = new Button("Del"); editCluster = new Button("Edit"); up = new Button("up"); down = new Button("down"); showLabel = new Button("Show labels"); hideLabel = new Button("Hide labels"); //Panel info = new Panel(new GridLayout(12,1)); Panel info = new Panel(new GridLayout(10,1)); info.add(new Panel().add(defaultVisible)); info.add(new Panel().add(visible)); info.add(new Panel().add(infoVisible)); info.add(new Panel().add(showLabel)); info.add(new Panel().add(hideLabel)); Panel p1 = new Panel(); p1.add(new Label("Color:")); p1.add(color); info.add(p1); info.add(new Label("Number of Vertices:")); info.add(numberOfNode); info.add(new Label("Average radius:")); info.add(radius); add(info, BorderLayout.EAST); Panel buttons = new Panel(new GridBagLayout()); GridBagConstraints c = new GridBagConstraints(); c.fill = GridBagConstraints.HORIZONTAL; c.gridx = 0; c.weightx = 0.5; c.weighty = 0.5; int y = 0; c.gridy = y; buttons.add(newCluster,c); c.gridy = ++y; buttons.add(editCluster,c); c.gridy = ++y; buttons.add(removeCluster,c); c.gridy = ++y; buttons.add(up,c); c.gridy = ++y; buttons.add(down,c); add(buttons, BorderLayout.WEST); setLocation(900,150); pack(); //setVisible(true); //construct the choices for a later usage //it always use the same object => it "remembers" last choice mode = new Choice(); mode.add("Equals"); mode.add("Contains"); mode.add("Starts with"); mode.add("Ends with"); mode2 = new Choice(); mode2.add("Keep"); mode2.add("Remove"); // Listeners save.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { saveClt(); } }); load.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { loadClt(); } }); // set the selected color to the selected cluster color.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent evt) { Choice ch = (Choice)evt.getSource(); // string (not index) // possible to change the order without changing this part String choice = ch.getSelectedItem(); Color tmp = CCVisu.green; if(choice.equals("red")){ tmp = CCVisu.red; }else if(choice.equals("green")){ tmp = CCVisu.green; }else if(choice.equals("blue")){ tmp = CCVisu.blue; }else if(choice.equals("yellow")){ tmp = CCVisu.yellow; }else if(choice.equals("magenta")){ tmp = CCVisu.magenta; }else if(choice.equals("cyan")){ tmp = CCVisu.cyan; }else if(choice.equals("light red")){ tmp = CCVisu.lightRed; }else if(choice.equals("light green")){ tmp = CCVisu.lightGreen; }else if(choice.equals("light blue")){ tmp = CCVisu.lightBlue; }else if(choice.equals("dark yellow")){ tmp = CCVisu.darkYellow; }else if(choice.equals("dark magenta")){ tmp = CCVisu.darkMagenta; }else if(choice.equals("dark cyan")){ tmp = CCVisu.darkCyan; }else if(choice.equals("pink")){ tmp = CCVisu.pink; }else if(choice.equals("white")){ tmp = CCVisu.white; }else if(choice.equals("light gray")){ tmp = CCVisu.lightGray; }else if(choice.equals("gray")){ tmp = CCVisu.gray; }else if(choice.equals("dark gray")){ tmp = CCVisu.darkGray; }else if(choice.equals("black")){ tmp = CCVisu.black; } int index = lst.getSelectedIndex(); int end = parent.getNbOfCluster()-1; Cluster clt = parent.getCluster(end-index); if(clt != null){ clt.setColor(tmp); display.onGraphEvent(new GraphEvent(this)); } } }); // toggle the visible attribut of the selected cluster visible.addItemListener(new ItemListener(){ public void itemStateChanged(ItemEvent evt) { Cluster clt = getClusterFromListIndex(lst.getSelectedIndex()); if(clt != null){ clt.visible = visible.getState(); display.onGraphEvent(new GraphEvent(this)); } } }); // toggle the visible attribut of the selected cluster infoVisible.addItemListener(new ItemListener(){ public void itemStateChanged(ItemEvent evt) { Cluster clt = getClusterFromListIndex(lst.getSelectedIndex()); if(clt != null){ clt.info = infoVisible.getState(); display.onGraphEvent(new GraphEvent(this)); } } }); // toggle the visible attribut of the default cluster defaultVisible.addItemListener(new ItemListener(){ public void itemStateChanged(ItemEvent evt) { parent.getCluster(0).visible = defaultVisible.getState(); display.onGraphEvent(new GraphEvent(this)); } }); // call the method in charge of creating a new cluster newCluster.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { if(diag != null){ diag.setVisible(false); diag.dispose(); } intputDialog(); refreshList(); } }); // open a dialog to edit the nodes of the selected cluster editCluster.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { Cluster clt = getClusterFromListIndex(lst.getSelectedIndex()); if(clt != null){ if(diag != null){ diag.setVisible(false); diag.dispose(); } editDialog(clt); } } }); // remove the selected cluster removeCluster.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { int index = lst.getSelectedIndex(); if(index >= 0){ parent.removeCluster(parent.getNbOfCluster()-1-index); refreshList(); display.onGraphEvent(new GraphEvent(this)); } } }); // put the selected cluster one rank higher in the rendering list up.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { int index = lst.getSelectedIndex(); if(index >= 0){ parent.moveClusterDown(parent.getNbOfCluster()-1-index); refreshList(); display.onGraphEvent(new GraphEvent(this)); } } }); // put the selected cluster one rank lower in the rendering list down.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { int index = lst.getSelectedIndex(); if(index >= 0){ parent.moveClusterUp(parent.getNbOfCluster()-1-index); refreshList(); display.onGraphEvent(new GraphEvent(this)); } } }); //diplays the label of the nodes in the cluster showLabel.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { Cluster clt = getClusterFromListIndex(lst.getSelectedIndex()); if(clt != null){ int end = clt.size(); for( int i = 0; i < end; ++i){ parent.showLabel(clt.getNode(i), true); } display.onGraphEvent(new GraphEvent(this)); } } }); //hides the label of the nodes in the cluster hideLabel.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { Cluster clt = getClusterFromListIndex(lst.getSelectedIndex()); if(clt != null){ int end = clt.size(); for( int i = 0; i < end; ++i){ parent.showLabel(clt.getNode(i), false); } display.onGraphEvent(new GraphEvent(this)); } } }); // refresh displayed attributs lst.addItemListener(new ItemListener(){ public void itemStateChanged(ItemEvent evt) { refreshInfo(); } }); // process the windowClosing event addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent evt) { setVisible(false); } }); } /** * refresh the list that contains the clusters WARNING: the order of the * list is the inverse of the groups order */ private void refreshList(){ int nbClt = parent.getNbOfCluster(); lst.removeAll(); for(int i = 1; i < nbClt; ++i){// begin at one to ignore default // cluster Cluster clt = parent.getCluster(i); lst.add(clt.getName(), 0); } } /** * return a cluster in function of an integer * * @param index * the index of the selected list item * @return the selected cluster */ private Cluster getClusterFromListIndex(int index){ int end = parent.getNbOfCluster()-1; if(index >= 0){ return parent.getCluster(end-index); }else{ return null; } } /** * refresh the list that contains the nodes of the current cluster */ private void refreshCltNodes(){ int end = curClt.size(); cltNodes.removeAll(); for(int i = 0; i < end; ++i){ cltNodes.add(curClt.getNode(i).name); } } /** * create a dialog that asks the user for a name and create a new cluster */ private void intputDialog(){ diag = new Dialog(this, "Name of the new group:",true); txt = new TextField(30); diag.add(txt,BorderLayout.CENTER); Button ok = new Button("Ok"); Button cancel = new Button("Cancel"); Panel btt = new Panel(); btt.add(ok); btt.add(cancel); diag.add(btt,BorderLayout.SOUTH); ok.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { String name = txt.getText(); if(name != null){ if(parent.getCluster(name) == null){ parent.addCluster(new Cluster(name)); } } txt = null; diag.setVisible(false); diag.dispose(); diag = null; } }); txt.addKeyListener( new KeyAdapter() { public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER) { String name = txt.getText(); if(name != null){ if(parent.getCluster(name) == null){ parent.addCluster(new Cluster(name)); } } txt = null; diag.setVisible(false); diag.dispose(); diag = null; } } } ); cancel.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) {; diag.setVisible(false); diag.dispose(); diag = null; } }); diag.pack(); diag.setLocationRelativeTo(null);// center of screen diag.setVisible(true); } /** * create a dialog to edit the cluster * @param clt - the cluster to edit */ private void editDialog(Cluster clt){ curClt = clt; cltNodes = new List(40); refreshCltNodes(); cltNodes.setMultipleMode(true); allNodes = new List(40); allNodes.setMultipleMode(true); GraphData graph = parent.graph; for (int i = 0; i < graph.vertices.size(); ++i){ allNodes.add(graph.vertices.get(i).name); } diag = new Dialog(this,clt.getName()); Button addNode = new Button("Add"); Button addPattern = new Button("Add (pattern)"); Button filter = new Button("filter (pattern)"); Button remove = new Button("Remove"); Panel up = new Panel(new GridLayout(1,2,20,10)); up.add(new Label("Vertices of "+ clt.getName())); up.add(new Label("List of all vertices")); diag.add(up, BorderLayout.NORTH); Panel center = new Panel(new GridLayout(1,2,20,10)); center.add(cltNodes); center.add(allNodes); diag.add(center, BorderLayout.CENTER); Panel buttons = new Panel(); buttons.add(addNode); buttons.add(addPattern); buttons.add(filter); buttons.add(remove); diag.add(buttons, BorderLayout.SOUTH); diag.setSize(330,300); diag.setLocationRelativeTo(this);// center of screen diag.setVisible(true); diag.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent evt) { diag.setVisible(false); diag.dispose(); // Close. diag = null; } } ); addNode.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { int selected[] = allNodes.getSelectedIndexes(); for( int i = 0; i < selected.length; ++i){ curClt.addNodeByIndex(selected[i]); } refreshCltNodes(); display.onGraphEvent(new GraphEvent(this)); refreshInfo(); } }); remove.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { // affect selected node to the default cluster int selected[] = cltNodes.getSelectedIndexes(); GraphVertex vertex[] = new GraphVertex[selected.length]; for( int i = 0; i < selected.length; ++i){ vertex[i] = curClt.getNode(selected[i]); } for( int i = 0; i < selected.length; ++i){ ((Cluster)parent.getCluster(0)).addNode(vertex[i]); } refreshCltNodes(); display.onGraphEvent(new GraphEvent(this)); refreshInfo(); } }); addPattern.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { diag2 = new Dialog(diag,"Enter a pattern",true); txt = new TextField(30); Panel center = new Panel(new GridLayout(2,1)); center.add(txt); center.add(mode); diag2.add(center,BorderLayout.CENTER); Button ok = new Button("Ok"); Button cancel = new Button("Cancel"); Panel btt = new Panel(); btt.add(ok); btt.add(cancel); diag2.add(btt,BorderLayout.SOUTH); AddPatternOk listener = new AddPatternOk(); txt.addKeyListener(listener); ok.addActionListener(listener); cancel.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) {; diag2.setVisible(false); diag2.dispose(); } }); diag2.pack(); diag2.setLocationRelativeTo(null);// center of screen diag2.setVisible(true); } }); filter.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) { diag2 = new Dialog(diag,"Enter a pattern",true); txt = new TextField(30); Panel center = new Panel(new GridLayout(3,1)); center.add(txt); center.add(mode); center.add(mode2); diag2.add(center,BorderLayout.CENTER); Button ok = new Button("Ok"); Button cancel = new Button("Cancel"); Panel btt = new Panel(); btt.add(ok); btt.add(cancel); diag2.add(btt,BorderLayout.SOUTH); FilterPatternOk listener = new FilterPatternOk(); txt.addKeyListener(listener); ok.addActionListener(listener); cancel.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent arg0) {; diag2.setVisible(false); diag2.dispose(); } }); diag2.pack(); diag2.setLocationRelativeTo(null);// center of screen diag2.setVisible(true); } }); } /** * refresh the list of clusters and closes the dialogs (edit,new,...) * method invoqued by ScreenDisplay */ public void refresh(){ if (diag != null){ diag.setVisible(false); diag.dispose(); diag = null; } if(diag2 != null){ diag2.setVisible(false); diag2.dispose(); diag2 = null; } refreshList(); } /** * Create a fileDialog and try to load the info from the selected file * Callback method */ private void loadClt(){ FileDialog fileDialog = new FileDialog(display, "Load groups", FileDialog.LOAD); fileDialog.setFilenameFilter( new FilenameFilter() { public boolean accept(File dir, String name) { if (name.endsWith(".grp")) { return true; } return false; } } ); fileDialog.setResizable(true); fileDialog.setFile(".clt"); fileDialog.pack(); fileDialog.setVisible(true); if (fileDialog.getDirectory() != null && fileDialog.getFile() != null) { String fileName = fileDialog.getDirectory() + fileDialog.getFile(); BufferedReader in = null; try { in = new BufferedReader(new FileReader(fileName)); } catch (Exception e) { System.err.println("Exception while opening file '" + fileName + "' for reading: "); System.err.println(e); } // Read layout from file. ClusterReaderWriter.read(in, parent); refreshList(); display.onGraphEvent(new GraphEvent(this)); // Close the input file. try { in.close(); } catch (Exception e) { System.err.println("Exception while closing input file: "); System.err.println(e); } } } /** * Create a fileDialog and save the info in the selected file * Callback method */ private void saveClt(){ FileDialog fileDialog = new FileDialog(display, "Save layout", FileDialog.SAVE); fileDialog.setResizable(true); fileDialog.setFile(".grp"); fileDialog.pack(); fileDialog.setVisible(true); if (fileDialog.getDirectory() != null && fileDialog.getFile() != null) { String fileName = fileDialog.getDirectory() + fileDialog.getFile(); try { PrintWriter out = new PrintWriter( new BufferedWriter(new FileWriter(fileName))); ClusterReaderWriter.write(out, parent); System.err.println("Wrote groups informations to output '" + fileName + "'."); out.close(); } catch (IOException e) { System.err.println("error while writing (ClusterManager.saveClt):"); e.printStackTrace(); } } } /** * refresh the informations of the selected cluster * */ private void refreshInfo(){ int index = lst.getSelectedIndex(); int end = parent.getNbOfCluster()-1; Cluster clt = parent.getCluster(end-index); if(clt != null){ // refresh the display with the correct informations radius.setText(Float.toString(clt.getAverageRadius())); numberOfNode.setText(Integer.toString(clt.size())); visible.setState(clt.visible); infoVisible.setState(clt.info); // select matching color Color tmp = clt.getColor(); if(tmp.equals(CCVisu.red)){ color.select("red"); }else if(tmp.equals(CCVisu.green)){ color.select("green"); }else if(tmp.equals(CCVisu.blue)){ color.select("blue"); }else if(tmp.equals(CCVisu.yellow)){ color.select("yellow"); }else if(tmp.equals(CCVisu.magenta)){ color.select("magenta"); }else if(tmp.equals(CCVisu.cyan)){ color.select("cyan"); }else if(tmp.equals(CCVisu.lightRed)){ color.select("light red"); }else if(tmp.equals(CCVisu.lightGreen)){ color.select("light green"); }else if(tmp.equals(CCVisu.lightBlue)){ color.select("light blue"); }else if(tmp.equals(CCVisu.darkYellow)){ color.select("dark yellow"); }else if(tmp.equals(CCVisu.darkMagenta)){ color.select("dark magenta"); }else if(tmp.equals(CCVisu.darkCyan)){ color.select("dark cyan"); }else if(tmp.equals(CCVisu.pink)){ color.select("pink"); }else if(tmp.equals(CCVisu.white)){ tmp = CCVisu.white; }else if(tmp.equals(CCVisu.lightGray)){ color.select("light gray"); }else if(tmp.equals(CCVisu.gray)){ color.select("gray"); }else if(tmp.equals(CCVisu.darkGray)){ color.select("dark gray"); }else if(tmp.equals(CCVisu.black)){ color.select("black"); } } } private class AddPatternOk extends KeyAdapter implements ActionListener{ //ok button event //same effect as Enter key public void actionPerformed(ActionEvent arg0) { process(); } //Enter key //same effect as ok button public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER) { process(); } } private void process(){ String pattern = txt.getText(); if(pattern != null){ curClt.addPattern(pattern, mode.getSelectedIndex()); refreshCltNodes(); display.onGraphEvent(new GraphEvent(this)); refreshInfo(); } txt = null; diag2.setVisible(false); diag2.dispose(); } } private class FilterPatternOk extends KeyAdapter implements ActionListener{ //ok button event //same effect as Enter key public void actionPerformed(ActionEvent arg0) { process(); } //Enter key //same effect as ok button public void keyPressed(KeyEvent e) { if (e.getKeyCode() == KeyEvent.VK_ENTER) { process(); } } private void process(){ String pattern = txt.getText(); if(pattern != null){ boolean keep = true; if(mode2.getSelectedIndex() == 1){ keep = false; } curClt.filter(pattern, mode.getSelectedIndex(), keep); refreshCltNodes(); display.onGraphEvent(new GraphEvent(this)); refreshInfo(); } txt = null; diag2.setVisible(false); diag2.dispose(); } } }