001    /*
002     * CRIMSON
003     * Copyright (c) 2006, Stephen Fisher, Susan Davidson, and Junhyong Kim, 
004     * University of Pennsylvania.
005     *
006     * This program is free software; you can redistribute it and/or
007     * modify it under the terms of the GNU General Public License as
008     * published by the Free Software Foundation; either version 2 of the
009     * License, or (at your option) any later version.
010     *
011     * This program is distributed in the hope that it will be useful, but
012     * WITHOUT ANY WARRANTY; without even the implied warranty of
013     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014     * General Public License for more details.
015     *
016     * You should have received a copy of the GNU General Public License
017     * along with this program; if not, write to the Free Software
018     * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019     * 02110-1301 USA.
020     *
021     * @(#)TreeUtils.java
022     */
023    
024    package edu.upenn.crimson.gui;
025    
026    import edu.upenn.crimson.*;
027    import edu.upenn.crimson.io.*;
028    import java.io.File;
029    import java.util.ArrayList;
030    import java.util.Iterator;
031    import java.awt.*;
032    import java.awt.event.*;
033    import javax.swing.*;
034    
035    /**
036     * Static methods used throughout the GUI for Tree objects.
037     *
038     * @author  Stephen Fisher
039     * @version $Id: TreeUtils.java,v 1.16 2007/10/09 20:14:16 fisher Exp $
040     */
041    
042    public class TreeUtils {
043    
044        //--------------------------------------------------------------------------
045        // Miscellaneous Methods
046    
047             /** Present a dialog box for selecting a particular Tree. */
048             public static String treeSelector() {
049                      // use this to get the return value from ObjectSelectorDialog
050                      ArrayList out = new ArrayList();
051                      Object[] objects = ObjectHandles.getTrees().toArray();
052                      new ObjectSelectorDialog("Tree Selector", objects, out);
053    
054                      if (out.size() > 0) {
055                                    return (String) out.get(0);
056                      } else {
057                                    return "";
058                      }
059             }
060    
061             /**
062              * This will process the NEXUS file selected and then load the
063              * file into the specified database table.
064              */
065             public static void loadTree() {
066                      // if no database connection then error
067                      if (! Database.isOpen()) {
068                                    CrimsonUtils.printError("Must open a database before loading a tree.");
069                                    return;
070                      }
071    
072                      // get file containing tree
073                      File file = GUIUtils.openFileChooser("Select NEXUS File", null);
074    
075                      // no valid file selected 
076                      if (file == null) return;
077    
078                      // use filename as tree/partition IDs - without the .nex
079                      // extension, if it exists.
080                      String name = file.getName();
081                      if (name.endsWith(".nex")) name = name.substring(0, name.length()-4);
082                      loadTree(file, name, name);
083             }
084    
085             public static void loadTree(File file, String treeID, String partitionID) {
086                      // if invalid file then try again
087                      if (file == null) loadTree();
088    
089                      // get tree/partition names
090                      String[] labels = {" Tree name:", " Partition name:"};
091    
092                      // use this to get the return value from FieldEditDialog
093                      ArrayList textFields = new ArrayList();
094    
095                      // get the value the user entered
096                      JTextField treeTF = new JTextField(20);
097                      treeTF.setText(treeID);
098                      textFields.add(treeTF);
099                      JTextField partitionTF = new JTextField(20);
100                      partitionTF.setText(partitionID);
101                      textFields.add(partitionTF);
102    
103                      new FieldEditDialog("Load Tree", labels, textFields);
104    
105                      // check "exit code" for FieldEditDialog, if false then exit
106                      Boolean exitCode = (Boolean) textFields.get(textFields.size()-1);
107                      if (! exitCode.booleanValue()) return;
108    
109                      // get tree/partition names
110                      treeID = treeTF.getText().toUpperCase();
111                      partitionID = partitionTF.getText().toUpperCase();
112    
113                      // shouldn't need to test both database and treePool, but
114                      // better to be careful
115                      if (Trees.dbContains(treeID) || ObjectHandles.containsTree(treeID)) {
116                                    JOptionPane.showMessageDialog(null, 
117                                                                                                                    "Tree name already used, please select a different name", 
118                                                                                                                    "Name Error", JOptionPane.INFORMATION_MESSAGE);
119    
120                                    // rerun the tree/partition ID dialog
121                                    loadTree(file, treeID, partitionID);
122                                    return;
123                      }
124                      if (Partitions.dbContains(partitionID) || ObjectHandles.containsPartition(partitionID)) {
125                                    JOptionPane.showMessageDialog(null, 
126                                                                                                                    "Partition name already used, please select a different name", 
127                                                                                                                    "Name Error", JOptionPane.INFORMATION_MESSAGE);
128    
129                                    // rerun the tree/partition ID dialog
130                                    loadTree(file, treeID, partitionID);
131                                    return;
132                      }
133    
134                      // convert any '.' to '_' in treeID since table names can not
135                      // contain '.'
136                      String origTreeID = treeID;
137                      treeID = treeID.replace('.', '_');
138                      if (treeID.compareTo(origTreeID) != 0) {
139                                    CrimsonUtils.printWarning("Tree names can not contain '.' and were converted to '_'.");
140                      }
141    
142                      String msg = "loadTree(\"" + GUIUtils.getFilename(file);
143                      msg += "\", \"" + treeID + "\", \"" + partitionID + "\")";
144                      Root.runCommand(msg);
145             }
146    
147             /**
148              * This will process the NEXUS file selected and then append the
149              * file into the specified database table.
150              */
151             public static void appendTree(String treeID) {
152                      // if no database connection then error
153                      if (! Database.isOpen()) {
154                                    CrimsonUtils.printError("Must open a database before appending data to a tree.");
155                                    return;
156                      }
157    
158                      if (CrimsonUtils.isEmpty(treeID)) {
159                                    treeID = treeSelector();
160                                    
161                                    // if the user didn't select a tree, then quit
162                                    if (CrimsonUtils.isEmpty(treeID)) return;
163                      }
164    
165                      // get file containing tree
166                      File file = GUIUtils.openFileChooser("Select NEXUS File", null);
167    
168                      // no valid file selected 
169                      if (file == null) return;
170    
171                      // get tree/partition names
172                      String[] labels = {" Tree name:", " Partition name:"};
173    
174                      // use this to get the return value from FieldEditDialog
175                      ArrayList textFields = new ArrayList();
176    
177                      // get the value the user entered
178                      JTextField treeTF = new JTextField(20);
179                      treeTF.setText(treeID);
180                      treeTF.setEnabled(false);
181                      textFields.add(treeTF);
182                      JTextField partitionTF = new JTextField(20);
183                      // use filename as partition ID, without the .nex extension,
184                      // if it exists.
185                      String name = file.getName();
186                      if (name.endsWith(".nex")) name = name.substring(0, name.length()-4);
187                      partitionTF.setText(name);
188                      textFields.add(partitionTF);
189    
190                      new FieldEditDialog("Append Tree", labels, textFields);
191    
192                      // check "exit code" for FieldEditDialog, if false then exit
193                      Boolean exitCode = (Boolean) textFields.get(textFields.size()-1);
194                      if (! exitCode.booleanValue()) return;
195    
196                      // get tree/partition names
197                      String partitionID = partitionTF.getText().toUpperCase();
198    
199                      String msg = "appendTree(\"" + GUIUtils.getFilename(file);
200                      msg += "\", \"" + treeID + "\", \"" + partitionID + "\")";
201                      Root.runCommand(msg);
202             }
203    
204             /** This will export the tree specified by TreeSelector(). */
205             public static void exportTree(String id) {
206                      // if no database connection then error
207                      if (! Database.isOpen()) {
208                                    CrimsonUtils.printError("Must open a database before exporting a tree.");
209                                    return;
210                      }
211    
212                      if (CrimsonUtils.isEmpty(id)) {
213                                    id = treeSelector();
214    
215                                    // if ID still empty then quit
216                                    if (CrimsonUtils.isEmpty(id)) return;
217                      }
218    
219                      Tree tree = ObjectHandles.getTree(id);
220                      if (tree == null) {
221                                    CrimsonUtils.printError("Invalid tree: " + id);
222                                    return;
223                      }
224    
225                      // if no partitions then only export structure, else ask user
226                      if (tree.getNumPartitions() == 0) {
227                                    Root.runCommand("exportStruct(\"" + id + "\")");
228                      } else {
229                                    String msg = "Do you want to include the partition data?\n";
230                                    msg += "Tree: " + id;
231                                    Object[] options = {"Structure and Data", "Only Structure", "Cancel"};
232                                    int flag = JOptionPane.showOptionDialog(null, msg,
233                                                                                                                                             "Export Tree",
234                                                                                                                                             JOptionPane.YES_NO_CANCEL_OPTION,
235                                                                                                                                             JOptionPane.QUESTION_MESSAGE,
236                                                                                                                                             null,
237                                                                                                                                             options,
238                                                                                                                                             options[1]);
239                                    if (flag == JOptionPane.YES_OPTION) { // include data
240                                             Root.runCommand("exportTree(\"" + id + "\")");
241                                    } else if (flag == JOptionPane.NO_OPTION) { // no data
242                                             Root.runCommand("exportStruct(\"" + id + "\")");
243                                    }
244                      }
245             }
246    
247             /** This will delete the tree specified by TreeSelector(). */
248             public static void deleteTree(String id) {
249                      if (CrimsonUtils.isEmpty(id)) {
250                                    id = treeSelector();
251    
252                                    // if ID still empty then quit
253                                    if (CrimsonUtils.isEmpty(id)) return;
254                      }
255    
256                      id = id.toUpperCase();
257                      String queries = "";
258                      for (Iterator i = ObjectHandles.queryIterator(); i.hasNext();) {
259                                    Query query = (Query) i.next();
260                                    if (id.equals(query.getTreeID())) queries += "   Query: " + query.getID() + "\n";
261                      }
262    
263                      String msg = "Are you sure you want to remove this tree?\n";
264                      msg += "   Tree: " + id;
265    
266                      if (! CrimsonUtils.isEmpty(queries)) {
267                                    msg = "Queries exist that reference this tree.  These queries will be deleted.\n" 
268                                             + queries + "\n" + msg;
269                      }
270    
271                      Object[] options = {"Delete Tree", "Cancel"};
272                      int flag = JOptionPane.showOptionDialog(null, msg,
273                                                                                                                                    "Delete Confirmation",
274                                                                                                                                    JOptionPane.YES_NO_OPTION,
275                                                                                                                                    JOptionPane.QUESTION_MESSAGE,
276                                                                                                                                    null,
277                                                                                                                                    options,
278                                                                                                                                    options[1]);
279                      if (flag == JOptionPane.YES_OPTION) { // "Delete"
280                                    // delete tree
281                                    Root.runCommand("deleteTree(\"" + id + "\", 1)");
282                      }
283             }
284    
285             public static boolean isTreeBuilt(Tree tree) {
286                      if (tree.isBuilt()) { 
287                                    return true;
288                      } else {
289                                    // if less than 1000 leaves, just build the tree
290                                    if (tree.getNumLeaves() < 1000) {
291                                             Root.runCommand("getTree(\"" + tree.getID() + "\").buildTree()");
292                                             return true;
293                                    }
294    
295                                    String msg = "The tree structure " + tree.getID() + " needs to be created.\n";
296                                    msg += "This may take a few minutes for very large trees.\n";
297                                    msg += "Do you want to continue?\n";
298                                    Object[] options = {"Ok", "Cancel"};
299                                    int flag = JOptionPane.showOptionDialog(null, msg,
300                                                                                                                                             "Build Tree",
301                                                                                                                                             JOptionPane.YES_NO_OPTION,
302                                                                                                                                             JOptionPane.QUESTION_MESSAGE,
303                                                                                                                                             null,
304                                                                                                                                             options,
305                                                                                                                                             options[0]);
306                                    if (flag == JOptionPane.YES_OPTION) { // build tree
307                                             Root.runCommand("getTree(\"" + tree.getID() + "\").buildTree()");
308                                             return true;
309                                    }
310                      }
311    
312                      // user pressed cancel button
313                      return false;
314             }
315             
316             /** This will view the tree specified by TreeSelector(). */
317             public static void view3DTree(String id) {
318                 // don't do anything if WALRUS not loaded
319                 if (! CrimsonMain.WALRUS) return;
320    
321                      // if no database connection then error
322                      if (! Database.isOpen()) {
323                                    CrimsonUtils.printError("Must open a database before viewing a tree.");
324                                    return;
325                      }
326    
327                      // if no ID, then use treeSelector() to get tree
328                      if (CrimsonUtils.isEmpty(id)) {
329                                    id = treeSelector();
330    
331                                    // if ID still empty then quit
332                                    if (CrimsonUtils.isEmpty(id)) return;
333                      }
334    
335                      Tree tree = ObjectHandles.getTree(id);
336                      // make sure tree exists
337                      if (tree == null) {
338                                    CrimsonUtils.printError("Invalid tree: " + id);
339                                    return;
340                      }
341    
342                      Root.runCommand("tree2wal(\"" + id + "\")");
343             }
344    
345             /** This will display the tree statistics. */
346             public static void statsTree(String id) {
347                      // if no database connection then error
348                      if (! Database.isOpen()) {
349                                    CrimsonUtils.printError("Must open a database before viewing tree information.");
350                                    return;
351                      }
352    
353                      // if no ID, then use treeSelector() to get tree
354                      if (CrimsonUtils.isEmpty(id)) {
355                                    id = treeSelector();
356    
357                                    // if ID still empty then quit
358                                    if (CrimsonUtils.isEmpty(id)) return;
359                      }
360    
361                      Tree tree = ObjectHandles.getTree(id);
362                      // make sure tree exists
363                      if (tree == null) {
364                                    CrimsonUtils.printError("Invalid tree: " + id);
365                                    return;
366                      }
367    
368                      JOptionPane.showMessageDialog(null, tree.toString(), "Tree Statistics", 
369                                                                                                      JOptionPane.INFORMATION_MESSAGE);
370             }
371    
372             /** This will display the tree's newick string. */
373             public static void newickTree(String id) {
374                      // if no database connection then error
375                      if (! Database.isOpen()) {
376                                    CrimsonUtils.printError("Must open a database before viewing tree information.");
377                                    return;
378                      }
379    
380                      // if no ID, then use treeSelector() to get tree
381                      if (CrimsonUtils.isEmpty(id)) {
382                                    id = treeSelector();
383    
384                                    // if ID still empty then quit
385                                    if (CrimsonUtils.isEmpty(id)) return;
386                      }
387    
388                      Tree tree = ObjectHandles.getTree(id);
389                      // make sure tree exists
390                      if (tree == null) {
391                                    CrimsonUtils.printError("Invalid tree: " + id);
392                                    return;
393                      }
394    
395                      new NewickViewer(tree.getNewick());
396             }
397    
398             static class NewickViewer extends JFrame {
399                      NewickViewer thisFrame;
400             
401                      public NewickViewer(String newick) {
402                                    super("Newick Viewer");
403                      
404                                    // keep pointer to self so can 'dispose' Frame below
405                                    thisFrame = this;
406                      
407                                    setDefaultCloseOperation(DISPOSE_ON_CLOSE);
408                      
409                                    JTextArea text = new JTextArea(20, 110);
410                                    text.setEditable(false);
411                                    JScrollPane textSP = new JScrollPane(text);
412                                    textSP.setBorder(BorderFactory.createLoweredBevelBorder());
413                                    
414                                    // split into lines of length 100 
415                                    int numLines = newick.length() / 100;
416                                    for (int i = 0; i < (numLines * 100); i += 100) {
417                                             text.append(newick.substring(i, i + 100) + "\n");
418                                    }
419                                    // print remaining portion of string
420                                    if ((newick.length() % 100) > 0) {
421                                             text.append(newick.substring(numLines * 100) + "\n");
422                                    }
423                      
424                                    // setup close button
425                                    JButton closeB = new JButton("Close Newick Viewer");
426                                    closeB.addActionListener(new ActionListener() {
427                                                      public void actionPerformed(ActionEvent e) {
428                                                      thisFrame.dispose();
429                                                      }
430                                             });
431                      
432                                    getContentPane().setLayout(new BorderLayout());
433                                    getContentPane().add(textSP, BorderLayout.CENTER);
434                                    getContentPane().add(closeB, BorderLayout.SOUTH);
435                                    pack();
436                                    
437                                    // set the default window size
438                                    setSize(800, 900);
439                                    
440                                    // display the window
441                                    setVisible(true);
442                      }
443             } // NewickViewer
444    
445    } // TreeUtils.java