1 /*
   2  * $Id$
   3  *
   4  * Copyright (c) 2001, 2009, Oracle and/or its affiliates. All rights reserved.
   5  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   6  *
   7  * This code is free software; you can redistribute it and/or modify it
   8  * under the terms of the GNU General Public License version 2 only, as
   9  * published by the Free Software Foundation.  Oracle designates this
  10  * particular file as subject to the "Classpath" exception as provided
  11  * by Oracle in the LICENSE file that accompanied this code.
  12  *
  13  * This code is distributed in the hope that it will be useful, but WITHOUT
  14  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  15  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  16  * version 2 for more details (a copy is included in the LICENSE file that
  17  * accompanied this code).
  18  *
  19  * You should have received a copy of the GNU General Public License version
  20  * 2 along with this work; if not, write to the Free Software Foundation,
  21  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  22  *
  23  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  24  * or visit www.oracle.com if you need additional information or have any
  25  * questions.
  26  */
  27 package com.sun.javatest.exec;
  28 
  29 import java.awt.EventQueue;
  30 import java.util.Enumeration;
  31 
  32 import javax.swing.JTree;
  33 import javax.swing.ToolTipManager;
  34 import javax.swing.event.TreeModelEvent;
  35 import javax.swing.event.TreeModelListener;
  36 import javax.swing.tree.TreeModel;
  37 import javax.swing.tree.TreePath;
  38 
  39 import com.sun.javatest.Parameters;
  40 import com.sun.javatest.TestFilter;
  41 import com.sun.javatest.TestResult;
  42 import com.sun.javatest.TestResultTable;
  43 import com.sun.javatest.tool.UIFactory;
  44 import com.sun.javatest.util.Debug;
  45 import com.sun.javatest.util.DynamicArray;
  46 import javax.swing.tree.TreeSelectionModel;
  47 
  48 /**
  49  * Tree to render test structure.
  50  */
  51 class TestTree extends JTree {
  52     /**
  53      * @param uif The UI factory object to use.
  54      * @param model The GUI model object.
  55      * @param fConfig The active filter configuration object.
  56      * @param treeModel The data model.
  57      */
  58     public TestTree(UIFactory uif, TreePanelModel model, FilterSelectionHandler fh,
  59                     TestTreeModel treeModel) {
  60         super();
  61 
  62         this.uif = uif;
  63         this.tpm = model;
  64         this.filterHandler = fh;
  65 
  66         filterHandler.addObserver(watcher);
  67 
  68         //currModel = new TestTreeModel(null);
  69         currModel = treeModel;
  70         setModel(currModel);
  71         setLargeModel(true);
  72         setScrollsOnExpand(true);
  73         setName("tree");
  74         ToolTipManager.sharedInstance().registerComponent(this);
  75     }
  76 
  77     /**
  78      * Call when it is likely that the Parameters or filters have changed internally.
  79      */
  80     public void updateGUI() {
  81         // delete this if the model becomes top-level
  82 
  83         // invalidate the node info cache because we don't know what part
  84         // of the parameters have changed.  A filter change is likely if
  85         // the user has just completed an interview
  86         repaint();
  87     }
  88 
  89     public void setModel(TreeModel newModel) {
  90         super.setModel(newModel);
  91         newModel.addTreeModelListener(watcher);
  92     }
  93 
  94     // ---------- private ----------
  95     void setParameters(Parameters p) {
  96         // this is only here to notify the model when we change things which
  97         // affect the tree structure itself.
  98         // otherwise, most changes are the result of filter changes
  99     }
 100 
 101     /**
 102      * For proper operation, this method is recommended over the method on JTree.
 103      *
 104      * @param mod The new data model.
 105      */
 106     void setTreeModel(TestTreeModel mod) {
 107         if (currModel != null)
 108             currModel.removeTreeModelListener(watcher);
 109 
 110         currModel = mod;
 111         setModel(currModel);
 112     }
 113 
 114     TestResultTable getTestResultTable() {
 115         // remove this method when the model becomes independent
 116         return currModel.getTestResultTable();
 117     }
 118 
 119     TreePath[] snapshotSelectedPaths() {
 120 
 121         TreeSelectionModel tsm = getSelectionModel();
 122         if (tsm == null) return null;
 123 
 124         return tsm.getSelectionPaths();
 125 
 126     }
 127 
 128     void restoreSelection(String[] selectedUrls) {
 129         if (selectedUrls == null || selectedUrls.length == 0)
 130             return;
 131 
 132         if (currModel instanceof TestTreeModel) {
 133             final TreePath[] paths = currModel.urlsToPaths(selectedUrls);
 134 
 135             if (!EventQueue.isDispatchThread()) {
 136                 EventQueue.invokeLater(new Runnable() {
 137                     @Override
 138                     public void run() {
 139                         restoreSelection(paths);
 140                     }
 141 
 142                 });
 143             }
 144             else
 145                 restoreSelection(paths);
 146         }
 147     }
 148 
 149     private void restoreSelection(TreePath[] selectedPaths) {
 150         if (selectedPaths == null || selectedPaths.length == 0)
 151             return;
 152 
 153         TreeSelectionModel tsm = getSelectionModel();
 154         if (tsm == null)
 155             return;
 156 
 157         // add or set ???
 158         tsm.addSelectionPaths(selectedPaths);
 159     }
 160 
 161 
 162     /**
 163      * Record which tree nodes are currently visible.
 164      * @see #restorePaths
 165      */
 166     TreePath[] snapshotOpenPaths() {
 167         // currModel.getRoot()==null may indicate that the GUI has been
 168         // disposed already (?).  We have seen exceptions in the code
 169         // below because this situation occurs.
 170         if (currModel == null || currModel.getRoot() == null)
 171             return null;
 172 
 173         TreePath[] paths = new TreePath[0];
 174         Enumeration e = getDescendantToggledPaths(new TreePath(currModel.getRoot()));
 175 
 176         while (e != null && e.hasMoreElements()) {
 177             TreePath tp = (TreePath)(e.nextElement());
 178             if (!isVisible(tp))     // if we can't see it, we don't care
 179                 continue;
 180             if (!isExpanded(tp))    // if it's not expanded, we don't need it
 181                 continue;
 182 
 183             paths = DynamicArray.append(paths, tp);
 184         }   // while
 185 
 186         return paths;
 187     }
 188 
 189     /**
 190      * Restore open paths in the tree.  Should be called on the event thread.
 191      * @param openUrls The path urls to restore.
 192      * @param queue True if the request should be put into the event queue at the
 193      *  end - this can be important for proper queuing of operations.
 194      */
 195     void restorePaths(final String[] openUrls, boolean queue) {
 196         if (openUrls == null || openUrls.length == 0)
 197             return;
 198 
 199         if (currModel instanceof TestTreeModel) {
 200             final TreePath[] paths = currModel.urlsToPaths(openUrls);
 201 
 202             if (!EventQueue.isDispatchThread() || queue) {
 203                 EventQueue.invokeLater(new Runnable() {
 204                     public void run() {
 205                         restorePaths(paths);
 206                     }
 207                 });
 208             }
 209             else
 210                 restorePaths(paths);
 211         }
 212     }
 213 
 214     /**
 215      * Attempt to restore paths which were previously recorded.
 216      * @see #snapshotOpenPaths
 217      */
 218     void restorePaths(TreePath[] paths) {
 219         // make sure root still matches
 220         if (paths == null || paths.length == 0)
 221             return;
 222 
 223         for (int i = 0; i < paths.length; i++) {
 224             if (paths[i].getPathCount() == 1)
 225                 continue;
 226 
 227             //expandPath(paths[i]);
 228             setExpandedState(paths[i], true);
 229             makeVisible(paths[i]);
 230         }   // for
 231     }
 232 
 233     private UIFactory uif;
 234     private TestTreeModel currModel;
 235     private TreePanelModel tpm;
 236     private FilterSelectionHandler filterHandler;
 237     private EventWatcher watcher = new EventWatcher();
 238 
 239     //protected static boolean debug = boolean.getboolean("debug." + testtree.class.getname());
 240     private static int debug = Debug.getInt("debug." + TestTree.class.getName());
 241 
 242     private class EventWatcher implements TreeModelListener, FilterSelectionHandler.Observer {
 243         // TreeModelListener - on event thread
 244         public void treeNodesChanged(TreeModelEvent e) {
 245             // NOTE: cannot get selected item from tree because if it has
 246             // been replaced already, there may be nothing selected
 247             String activeTest = tpm.getSelectedTest();
 248             if (activeTest == null) {
 249                 return;
 250             }
 251 
 252             Object[] targets = e.getChildren();
 253             if (targets == null)
 254                 return;     // don't care then
 255 
 256             for (int i = 0; i < targets.length; i++)
 257                 if (targets[i] instanceof TT_TestNode) {
 258                     TestResult tr = ((TT_TestNode)targets[i]).getTestResult();
 259 
 260                     if (tr.getTestName().equals(activeTest))
 261                         tpm.showTest(tr);
 262                 }
 263 
 264             repaint();
 265         }
 266 
 267         public void treeNodesInserted(TreeModelEvent e) {
 268             // NOTE: cannot get selected item from tree because if it has
 269             // been replaced already, there may be nothing selected
 270             String activeTest = tpm.getSelectedTest();
 271             if (activeTest == null) {
 272                 return;
 273             }
 274 
 275             Object[] targets = e.getChildren();
 276             if (targets == null)
 277                 return;     // don't care then
 278 
 279             for (int i = 0; i < targets.length; i++)
 280                 if (targets[i] instanceof TT_TestNode) {
 281                     TestResult tr = ((TT_TestNode)targets[i]).getTestResult();
 282 
 283                     if (tr.getTestName().equals(activeTest))
 284                         tpm.showTest(tr);
 285                 }
 286         }
 287 
 288         public void treeNodesRemoved(TreeModelEvent e) {
 289             // XXX need to handle case when shown node is removed
 290             // select root in that case?
 291         }
 292 
 293         public void treeStructureChanged(TreeModelEvent e) {
 294             TreePath[] tp = snapshotOpenPaths();
 295 
 296             TreePath[] selected = snapshotSelectedPaths();
 297 
 298             //tpm.showNode(getModel().getRoot(), new TreePath(getModel().getRoot()));
 299 
 300             if (tp != null && tp.length != 0)
 301                 restorePaths(tp);
 302 
 303             restoreSelection(selected);
 304         }
 305 
 306         // FilterConfig.Observer - may not be on event thread
 307         public void filterUpdated(TestFilter f) {
 308             updateGUI();
 309         }
 310 
 311         public void filterSelected(TestFilter f) {
 312             updateGUI();
 313         }
 314 
 315         public void filterAdded(TestFilter f) {
 316             // don't care
 317         }
 318 
 319         public void filterRemoved(TestFilter f) {
 320             // don't care
 321         }
 322 
 323     }
 324 }