KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > ca > directory > jxplorer > tree > SmartTree


1 package com.ca.directory.jxplorer.tree;
2
3 import com.ca.commons.cbutil.*;
4 import com.ca.commons.naming.*;
5 import com.ca.directory.jxplorer.*;
6 import com.ca.directory.jxplorer.event.JXplorerEvent;
7 import com.ca.directory.jxplorer.event.JXplorerEventGenerator;
8 import com.ca.directory.jxplorer.search.SearchGUI;
9 import com.ca.directory.jxplorer.viewer.AttributeDisplay;
10 import com.ca.directory.jxplorer.viewer.PluggableEditor;
11
12 import javax.naming.*;
13 import javax.naming.directory.*;
14 import javax.swing.*;
15 import javax.swing.event.*;
16 import javax.swing.tree.*;
17 import java.awt.*;
18 import java.awt.datatransfer.Transferable JavaDoc;
19 import java.awt.datatransfer.UnsupportedFlavorException JavaDoc;
20 import java.awt.dnd.*;
21 import java.awt.dnd.peer.DragSourceContextPeer;
22 import java.awt.event.*;
23 import java.io.IOException JavaDoc;
24 import java.util.Enumeration JavaDoc;
25 import java.util.Vector JavaDoc;
26 import java.util.logging.Level JavaDoc;
27 import java.util.logging.Logger JavaDoc;
28
29
30 /**
31  * SmartTree displays the directory, using configurable
32  * icons. The SmartTree uses an internal 'SmartNode' class
33  * which stores information about a visible Entry. The
34  * display information is separate from the underlying
35  * directory; if the class is modified or extended, care
36  * must be taken to always keep the two in sych.
37  */

38
39 // The drag and drop handling in this code owes a lot to the java world tutorial:
40
// http://www.javaworld.com/javaworld/javatips/jw-javatip97.html
41

42 public class SmartTree extends JTree
43         implements TreeSelectionListener, DataListener,
44         TreeExpansionListener, JXplorerEventGenerator,
45         DragGestureListener, DropTargetListener, DragSourceListener
46
47 {
48     boolean setup = false; // whether the delayed graphics constructor has been called.
49
boolean activated = false; // whether the delayed action stuff has been called.
50

51
52     public static String JavaDoc NODATA = CBIntText.get("cn=no entries");
53
54     JXplorerEventGenerator eventPublisher; // if registered, this object is used to publish external events
55
// (to programs that are using JXplorer as an embedded component)
56

57     //final JTree tree; // the main JTree over which everything is built.
58

59     Frame owner; // used for Swing/graphics L&F continuity
60

61     SmartNode root; // the node representing the first RDN of the rootDN
62
SmartNode rootDNBase; // the node representing the lowest RDN of the rootDN (may = top if only 1 rdn in rootDN)
63

64     DN rootDN; // the full root DN
65

66     SmartModel treeModel; // the tree model used to track tree data
67

68     DN currentDN; // the currently selected DN
69

70     SmartPopupTool popupTreeTool; // the right-mouse-click tree tools menu
71

72     DefaultTreeCellEditor treeEditor; // widget that allows user to edit node names
73

74     TreeCellEditor innerEditor; // the custom widget that actually creates the editor component.
75

76     SmartTreeCellRenderer treeRenderer; // widget that display the nodes (text + icons)
77

78     public boolean rootSet = false; // whether the root node is set.
79

80     DataSource treeDataSource; // where the tree obtains its data
81
Vector JavaDoc treeDataSinks = new Vector JavaDoc(); // a list of objects interested in tree changes
82

83     //int treeCapabilities; // a bit mask of DataQueries to respond to.
84

85     public DXEntry entry; //TE: the current entry.
86

87     String JavaDoc name; // a unique name for the tree.
88

89     AttributeDisplay pluggableEditorSource = null; // if initialised, this can find pluggable editors to set extended tree behaviour
90

91     private SearchGUI searchGUI = null;
92     static int treeNo = 0;
93
94
95     public boolean dragging = false; // whether a drag 'n drop operation is in progress.
96

97     /* Variables needed for DnD */
98     private DragSource dragSource = null;
99     //private DragSourceContext dragSourceContext = null;
100
private Point cursorLocation = null;
101
102     /**
103      * A holder for the number of results returned by a search.
104      * This is used in the status bar for user info.
105      */

106     public int numOfResults = 0;
107
108     private static Logger JavaDoc log = Logger.getLogger(SmartTree.class.getName());
109
110
111     /**
112      * Constructor for SmartTree. The Tree starts off disconnected, and
113      * must be later linked to a data source (using @RegisterDataSource)
114      * to become active.
115      *
116      * @param Owner the owning awt component - used for look and feel updates
117      * @param name the 'name' of the tree - used for debugging,
118      * @param resourceLoader - a resource Loader used to get extra tree icons. May be null.
119      */

120
121     public SmartTree(Frame Owner, String JavaDoc name, CBResourceLoader resourceLoader)
122     {
123         treeNo++;
124         owner = Owner;
125
126         this.name = name;
127
128         setRoot(NODATA);
129
130         setup = true; // one way or another, only do this once!
131

132         SmartNode.init(resourceLoader);
133
134         /*
135          * a custom renderer, shows mutli valued attributes and icons.
136          */

137
138         treeRenderer = new SmartTreeCellRenderer();
139         setCellRenderer(treeRenderer);
140
141         /*
142          * custom editor, allows editing of multi-valued rdn.
143          */

144
145         //treeEditor = new SmartTreeCellEditor(this, new DefaultTreeCellRenderer());
146
treeEditor = new SmartTreeCellEditor(this, treeRenderer);
147
148         setCellEditor(treeEditor);
149
150         treeModel = new SmartModel(root);
151         setModel(treeModel);
152
153         registerPopupTool(new SmartPopupTool(this));
154         // disallow multiple selections...
155
getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
156         addTreeSelectionListener(this);
157         addTreeExpansionListener(this);
158
159         // use the default editor, but nobble it to avoid editing when drag and drop
160
// is operating, and stick special handling in for multi valued rdn editing...
161

162         setTreeMouseListener();
163         setTreeCellEditorListener();
164
165         setEditable(true); // make sure the user can edit tree cells.
166

167         setupDragAndDrop();
168     }
169
170     public String JavaDoc getName()
171     {
172         return name;
173     }
174
175     public String JavaDoc toString()
176     {
177         return name;
178     }
179
180     /**
181      * Returns the currently selected Node.
182      */

183     protected SmartNode getSelectedNode()
184     {
185         if (getSelectionPath() == null) return null;
186         return (SmartNode) getSelectionPath().getLastPathComponent();
187     }
188
189     /**
190      * Set the popup tool that will be used by the tree.
191      */

192
193     public void registerPopupTool(SmartPopupTool tool)
194     {
195         popupTreeTool = tool;
196     }
197
198     /**
199      * returns a vector of DNs being the all inclusive list of all DNs
200      * in the subtree from the given apex DN. Used by ldif save ftn.
201      *
202      * @return a Vector of DNs
203      */

204
205     public Vector JavaDoc getAllNodes(DN start)
206     {
207         if (start == null) return new Vector JavaDoc(0); // sanity check
208
SmartNode apex = treeModel.getNodeForDN(start); // need the smart node to find children
209
if (apex == null) return new Vector JavaDoc(0); // another sanity check
210

211         try
212         {
213             Vector JavaDoc result = new Vector JavaDoc(10);
214
215             result.add(start); //
216

217             Enumeration JavaDoc children = apex.children(); // find children of 'from'
218
while (children.hasMoreElements())
219             {
220                 DN next = new DN(start);
221                 next.addChildRDN(((SmartNode) children.nextElement()).getRDN());
222                 result.addAll(getAllNodes(next));
223             }
224             return result;
225         }
226         catch (Exception JavaDoc e)
227         {
228             log.log(Level.WARNING, "error in SmartTree dump: ", e);
229             return new Vector JavaDoc(0);
230         }
231     }
232
233     /**
234      * This sets the root of the tree, creating the nodes
235      * necessary to display it. A small wrinkle: the
236      * 'rootDN' is the base DN as given by the user, e.g.
237      * 'o=Democorp,c=us'. This may contain multiple RDNs.
238      * the root node, however, is the top level node in the
239      * display tree; it has only a single RDN (i.e. 'c=us'
240      * in the above example.) The rootDN therefore may be
241      * displayed as a number of nodes in the tree (maybe the
242      * rootDN should be renamed the 'baseDN' or something...)
243      *
244      * @param rootString the DN of the root as a string
245      */

246
247     public void setRoot(String JavaDoc rootString)
248     {
249         if (rootString == null)
250             rootString = "";
251
252         rootDN = new DN(rootString); // rootString MAY BE BLANK!
253
setRoot(rootDN);
254     }
255
256     /**
257      * This sets the root of the tree, creating the nodes
258      * necessary to display it. A small wrinkle: the
259      * 'rootDN' is the base DN as given by the user, e.g.
260      * 'o=Democorp,c=us'. This may contain multiple RDNs.
261      * the root node, however, is the top level node in the
262      * display tree; it has only a single RDN (i.e. 'c=us'
263      * in the above example.) The rootDN therefore may be
264      * displayed as a number of nodes in the tree (maybe the
265      * rootDN should be renamed the 'baseDN' or something...)
266      *
267      * @param rootDN the DN of the root as a DN object
268      */

269
270     public void setRoot(DN rootDN)
271     {
272         rootSet = true;
273         if (rootDN == null) rootDN = new DN(); // equivalent to empty DN, 'cn=World'
274

275         /*
276          * Special handling for 'No Data' trees...
277          */

278
279         if (NODATA.equals(rootDN.toString()))
280         {
281             rootDN = new DN(NODATA);
282             root = new SmartNode(NODATA);
283             rootDNBase = root;
284             rootSet = false;
285             if (treeModel != null)
286             {
287                 treeModel.setRoot(root);
288                 treeModel.reload();
289             }
290             return;
291         }
292
293         this.rootDN = rootDN;
294
295         root = new SmartNode(""); // equivalent to new SmartNode("cn=World")
296
root.setRoot(true);
297         root.setStructural(true);
298
299         treeModel.setRoot(root); // reset the tree Model
300

301         // set up the path to the lowest node of the DN, creating smart nodes for each RDN.
302

303         SmartNode parent = root;
304         rootDNBase = root;
305
306         for (int i = 0; i < rootDN.size(); i++)
307         {
308             SmartNode child = new SmartNode(rootDN.getRDN(i));
309             child.setStructural(true);
310             parent.add(child);
311             parent = child;
312         }
313         rootDNBase = parent;
314         rootDNBase.add(new SmartNode()); // stick in a dummy node so there's something to expand to...
315

316         treeModel.reload();
317
318         if (rootDN.size() > 0)
319             expandPath(treeModel.getPathForNode(rootDNBase));
320         else
321             collapseRow(0);
322
323
324     }
325
326
327     /**
328      * Forces a particular DN that is already in the tree to be displayed.
329      */

330
331     public void expandDN(DN dn)
332     {
333         TreePath path = treeModel.getPathForDN(dn);
334
335         expandPath(path.getParentPath()); // without the 'getParentPath()' the next level children are shown as well.
336
}
337
338     /**
339      * Forces the root node of the tree to expand.
340      * (May trigger a list request)
341      */

342
343     public void expandRoot()
344     {
345         expandRow(0);
346     }
347
348     /**
349      * Get the rootDN of the tree (often the same as the directory DSE
350      * naming prefix, e.g. 'o=DemoCorp,c=au'.
351      *
352      * @return the root DN
353      */

354
355     public DN getRootDN()
356     {
357         return (rootSet) ? rootDN : null;
358     }
359
360     /**
361      * Get the root node of tree. Needed for root.setStructural() hack
362      */

363     public SmartNode getRootNode()
364     {
365         return root;
366     }
367
368     /**
369      * Get the lowest smart node of the baseDN.
370      * For example, if base dn is ou=R&D,o=Democorp,c=au, this
371      * returns the SmartNode corresponding to ou=R&D.
372      */

373     public SmartNode getLowestRootNode()
374     {
375         return rootDNBase;
376     }
377
378     /**
379      * Returns the tree model used by the Smart Tree to store node data in.
380      */

381
382     public SmartModel getTreeModel()
383     {
384         return treeModel;
385     }
386
387     /**
388      * registers the data source the tree is to use to read its
389      * data (e.g. a JNDIBroker). There can be only one of these.
390      *
391      * @param s the source of data for initialising SmartNodes etc.
392      */

393
394     public void registerDataSource(DataSource s)
395     {
396         if (s == null) return; // sanity check
397

398         log.fine("registering data source for tree " + getName());
399         treeDataSource = s;
400
401         // Register ourselves as interested in *all* data events that pass through this data source...
402
treeDataSource.addDataListener(this);
403
404         //setRoot(rootDN);
405
}
406
407     /**
408      * Returns the active data source that has been registered for the tree.
409      *
410      * @return the data source used by the tree.
411      */

412
413     public DataSource getDataSource()
414     {
415         return treeDataSource;
416     }
417
418     /**
419      * Register a <code>DataSink</code>.
420      * DataSinks are entities that are interested in
421      * displaying the data corresponding to a particular
422      * SmartNode (that is, the attributes of the node). There
423      * may be a number of such sinks in operation simultaneously.
424      * This registration method adds a new data sink to the list
425      * of active data sinks.
426      *
427      * @param s a new data sink to add to the list of active sinks.
428      */

429
430     public void registerDataSink(DataSink s)
431     {
432         treeDataSinks.addElement(s);
433         if (s instanceof AttributeDisplay)
434             registerPluggableEditorSource((AttributeDisplay) s);
435     }
436
437     /**
438      * Quick Hack to clear out a tree preparitory to loading a new
439      * one. May need revisiting to do a neater data clean up.
440      */

441     public void clearTree()
442     {
443         root.removeAllChildren();
444         setRoot(NODATA);
445         treeModel.setRoot(root);
446         treeModel.reload();
447         clearEntry();
448         for (int i = 0; i < treeDataSinks.size(); i++)
449             ((DataSink) treeDataSinks.elementAt(i)).displayEntry(null, null);
450     }
451
452
453     /**
454      * Takes a node, and adds a bunch of children.
455      *
456      * @param parent the node to add the children too
457      * @param children an enumeration of NameClassPair containing
458      * the names of the child nodes
459      */

460
461     public void addCutting(SmartNode parent, NamingEnumeration children)
462     {
463         // sanity checks
464
if (parent == null)
465         {
466             return;
467         }
468         if (children == null)
469         {
470             log.warning("null child list in addCutting...!");
471             return;
472         }
473
474         if (children != null)
475         {
476             // Step 1: Clear any dummy nodes prior to adding real ones.
477
if (parent.getChildCount() == 1 && ((SmartNode) parent.getChildAt(0)).isDummy())
478             {
479                 parent.removeAllChildren();
480             }
481             // Step 2: Add new children (nb - there may be *zero* children to add, in which case parent is actually a leaf node
482
while (children.hasMoreElements())
483             {
484                 NameClassPair np = (NameClassPair) children.nextElement();
485
486                 SmartNode child;
487
488                 // &(*%& pointless bloody jndi; the one time it would be useful for them
489
// to use 'Name' objects they use strings; in 'NameClassPair' no less. What a joke.
490

491                 DN temp = new DN(np.getName());
492                 child = new SmartNode(temp.getRDN(temp.size() - 1));
493
494                 if (parent.hasChild(child.toString()) == false) // don't add a child twice!
495
{
496                     parent.add(child);
497                     //treeModel.insertNodeInto(child, parent, parent.getChildCount());
498

499                     // Step 3: try to recover the object class list for the node to use.
500

501                     if (np instanceof SearchResult)
502                     {
503                         doObjectClassSpecificHandling(child, ((SearchResult) np));
504                     }
505
506                 }
507
508                 //XXX is it more efficient to work on nodes, then call nodeStructureChanged?
509
//XXX i.e. parent.add(child);
510

511                 if (child.getAllowsChildren())
512                 // treeModel.insertNodeInto(new SmartNode(), child, 0);
513
child.add(new SmartNode());
514
515                 //parent.add(child);
516
}
517
518             parent.sort();
519
520             treeModel.nodeStructureChanged(parent);
521         }
522     }
523
524     public void registerPluggableEditorSource(AttributeDisplay display)
525     {
526         pluggableEditorSource = display;
527     }
528
529     /**
530      * HERE BE MAGIC
531      * <p/>
532      * In order for pluggable editors to truncate trees, special displays for aliases,
533      * object class based icons, and object class based popup menus, we need special
534      * handling for nodes where the object class is known.<p><
535      * <p/>
536      * This registers the object class with a node, and checks to see if any pluggable
537      * editors are known corresponding to these object classes that are unique, and
538      * have special requests (for icons/popup menus/etc.)<p>
539      * <p/>
540      * This is all optional stuff - the browser will work fine if these details are
541      * not available, but more complex pluggable functionality will not be possible.
542      *
543      * @param child the smart node to register the object classes with
544      * @param ocs a search result containing the object class attribute (we hope).
545      */

546
547     protected void doObjectClassSpecificHandling(SmartNode child, SearchResult ocs)
548     {
549         if (ocs == null) return; // can't do anything.
550
Attributes atts = ocs.getAttributes();
551
552         if (atts == null || atts.size() == 0) return; // still can't do anything.
553

554         Attribute OC;
555
556         try // the usual fuss and bother to retrieve the object class attribute. X500 does this so much better...
557
{
558             OC = atts.get("objectClass");
559             if (OC == null)
560                 OC = atts.get("objectclass");
561
562             if (OC == null) // there *may* only be one attribute, which *may* be a wierd capitalisation of object class...
563
{
564                 Attribute test = (Attribute) atts.getAll().next();
565                 if ("objectclass".equals(test.getID().toLowerCase()))
566                     OC = test;
567             }
568
569             if (OC == null) // give up! It doesn't have one.
570
return;
571
572             if ((OC instanceof DXAttribute) == false)
573                 OC = new DXAttribute(OC);
574             OC = DXAttributes.getAllObjectClasses((DXAttribute) OC); // order it...
575

576             doObjectClassSpecificHandling(child, OC);
577         }
578         catch (Exception JavaDoc e)
579         {
580             log.warning("Warning error doing object class specific handling for tree nodes: " + e);
581         }
582     }
583
584
585     /**
586      * HERE BE MAGIC
587      * <p/>
588      * In order for pluggable editors to truncate trees, special displays for aliases,
589      * object class based icons, and object class based popup menus, we need special
590      * handling for nodes where the object class is known.<p><
591      * <p/>
592      * This registers the object class with a node, and checks to see if any pluggable
593      * editors are known corresponding to these object classes that are unique, and
594      * have special requests (for icons/popup menus/etc.)<p>
595      * <p/>
596      * This is all optional stuff - the browser will work fine if these details are
597      * not available, but more complex pluggable functionality will not be possible.
598      *
599      * @param child the smart node to register the object classes with
600      * @param OC objectclass attribute
601      */

602     protected void doObjectClassSpecificHandling(SmartNode child, Attribute OC)
603     {
604         if (OC instanceof DXAttribute == false)
605             OC = new DXAttribute(OC);
606         child.setTrueObjectClass((DXAttribute) OC); // and register it with smartnode
607

608         // *** TRICKYNESS ***
609

610         //
611
// The code below looks for, and interogates, any pluggable editors
612
// that are related to the node in order to determine any special
613
// handling requirements such as custom popup menus, icons, or
614
// subtree truncations.
615

616         if (pluggableEditorSource != null)
617         {
618             PluggableEditor editor = pluggableEditorSource.getUniqueEditor(OC);
619
620             if (editor != null)
621             {
622
623                 if (editor.hideSubEntries(child.toString()))
624                     child.setAllowsChildren(false);
625
626                 ImageIcon newIcon = editor.getTreeIcon(child.toString());
627                 if (newIcon != null)
628                     child.setIcon(newIcon);
629
630                 if (editor.getPopupMenu(child.toString()) != null)
631                     child.setPopupMenu(editor.getPopupMenu(child.toString()));
632             }
633         }
634     }
635
636
637     /**
638      * Takes an entry, usually from a search result list, and adds it to
639      * the tree, creating parent nodes if necessary.
640      *
641      * @param newDN the new DN to create a smart Node for, and add to the tree.
642      */

643
644     public SmartNode addNode(DN newDN)
645     {
646         if (newDN == null) return null;
647
648         SmartNode parent, child = null;
649
650         if (rootDN.toString().equals(NODATA))
651         {
652             setRoot(""); // the same as 'cn=World'
653
}
654
655         // Walk through the current newDN, creating new nodes
656
// as necessary until we can add a new node corresponding to
657
// the lowest RDN of the newDN.
658

659         parent = root;
660         RDN rdn;
661
662         for (int i = 0; i < newDN.size(); i++)
663         {
664             rdn = newDN.getRDN(i);
665             Enumeration JavaDoc children = parent.children();
666
667             child = null;
668             while (children.hasMoreElements())
669             {
670                 child = (SmartNode) children.nextElement();
671
672                 if (child.isDummy()) // strip any dummy nodes en passent
673
parent.remove(child);
674                 else if (child.rdnEquals(rdn)) // and check if the node already exists (before overwritting it below)
675
break;
676
677                 child = null;
678             }
679
680             if (child == null) // if the node doesn't exist...
681
{
682                 child = new SmartNode(rdn); // ... create it
683
if (i < newDN.size() - 1)
684                 {
685                     child.setStructural(true);
686                 }
687                 parent.add(child); // ... add it to the parent
688
parent.sort();
689                 treeModel.nodeStructureChanged(parent);
690
691                 parent = child;
692             }
693             else
694             {
695                 parent = child; // reset parent pointer for next turn around
696
if (i == newDN.size() - 1)
697                     child.setStructural(false);
698             }
699         }
700 // setSelectionPath(treeModel.getPathForDN(newDN)); //TE: make sure its selected.
701

702         return parent;
703     }
704
705
706     /**
707      * Refresh the display of a given volatile dn or node.
708      */

709
710     public void refresh(DN dn)
711     {
712         if (dn != null) //TE: make sure the dn is not null.
713
{
714             treeDataSource.getChildren(dn);
715         }
716     }
717
718
719     /**
720      * Forces a refresh of the Editor Pane. Currently is used by the tab listener in
721      * JXplorer for when the user changes tabs the correct entry display is updated
722      * depending on the entry selected in the tree of the tab that is selected.
723      * (see Bug 2243).
724      * .
725      */

726
727     public void refreshEditorPane()
728     {
729         //TE: was... if(entry!=null && treeDataSource!=null)
730
pluggableEditorSource.refreshEditors(entry, treeDataSource);
731     }
732
733
734     /**
735      * Gets the DN of the currently selected tree node (or the root Node,
736      * if no node is selected).
737      *
738      * @return DN the distinguished name of the current SmartNode.
739      */

740
741     public DN getCurrentDN()
742     {
743         return (currentDN == null) ? rootDN : currentDN;
744     }
745
746
747     /**
748      * Displays a null entry in the table editor and the html view.
749      * Intended to be used if an entry has been deleted.
750      */

751
752     public void clearEntry()
753     {
754         setEntry(null);
755         pluggableEditorSource.displayEntry(null, treeDataSource);
756     }
757
758
759     /**
760      * removes everything except the root DN node(s), and
761      * sets things up as they were in the beginning, the
762      * point being to force the tree to reload from the
763      * data source, which has changed independantly of the
764      * tree...
765      */

766
767     public void collapse()
768     {
769
770         String JavaDoc dn = rootDN.toString();
771
772         if (dn.equals(NODATA)) return; // don't bother!
773

774         try
775         {
776             NamingEnumeration en = treeDataSource.getChildren(rootDN).getEnumeration();
777
778             if (en != null)
779             {
780                 clearTree();
781                 setRoot(dn);
782                 addCutting(rootDNBase, en);
783             }
784         }
785         catch (NamingException e)
786         {
787             CBUtility.error(CBIntText.get("threaded broker error: "), e);
788         } // XXXTHREAD
789
}
790
791
792     /**
793      * removes a SmartNode and its children from the tree.
794      * Only affects the GUI tree - does nothing to the underlying data.
795      *
796      * @param apex the root of the subtree to be deleted (may be a leaf).
797      */

798
799     protected void deleteTreeNode(SmartNode apex)
800     {
801         treeModel.removeNodeFromParent(apex);
802     }
803
804     /**
805      * changes the RDN of a tree node to the lowest level
806      * RDN of the supplied DN.
807      * (usually to mirror a change in the underlying DIT)
808      *
809      * @param node the node to modify
810      * @param newDN the DN providing the new lowest level RDN
811      * to modify the node RDN to.
812      */

813
814     public void renameTreeNode(SmartNode node, DN newDN)
815     {
816         node.update(newDN.getLowestRDN());
817     }
818
819     /**
820      * moves a subtree (may be a leaf) to a
821      * new position. This only modifies the
822      * tree, it <i>does not</i> modify the
823      * underlying directory.
824      *
825      * @param node the node to move.
826      * @param to the DN of the position to move it to.
827      */

828
829     public void moveTreeNode(SmartNode node, DN to)
830     {
831         DN from = treeModel.getDNForNode(node);
832         if (from.sharesParent(to)) // we may only need to do a rename...
833
{
834             renameTreeNode(node, to);
835         }
836         else
837         {
838             // step 1; remove node from old position
839
SmartNode parent = (SmartNode) node.getParent();
840             treeModel.removeNodeFromParent(node);
841             treeModel.nodeStructureChanged(parent);
842
843             // step 2: modify the nodes RDN
844
node.update(to.getLowestRDN());
845
846             // step 3: add it to new position
847
parent = treeModel.getNodeForDN(to.parentDN());
848
849             if (parent.getChildCount() == 1 && ((SmartNode) parent.getChildAt(0)).isDummy())
850             {
851                 return; // tree hasn't been read from the directory yet - wait until it is before adding anything
852
}
853
854             parent.add(node);
855             parent.sort();
856             treeModel.nodeStructureChanged(parent);
857         }
858     }
859
860     /**
861      * This copies a tree node and its children to a newly
862      * created tree node with the given name, <i>without affecting
863      * the underlying directory</i>.
864      *
865      * @param node the tree node to copy data from
866      * @param to the name of the new tree node to create and, if
867      * necessary, populate.
868      */

869
870     public void copyTreeNode(SmartNode node, DN to)
871     {
872         // find the parent corresponding to the target DN
873
SmartNode parent = treeModel.getNodeForDN(to.parentDN());
874
875         // sanity check
876
if (parent == null)
877         {
878             CBUtility.error(this, CBIntText.get("unable to copy node {0}.", new String JavaDoc[]{node.toString()}), null);
879             return;
880         }
881
882         // copy the node (and children) and note the resulting node created
883
SmartNode newCopy = copyTreeNodes(node, parent);
884
885         // see if the newly created node has the right RDN
886
// (it may not if it has been changed to avoid a 'copy'ing
887
// name collision.
888

889
890         if (newCopy.getRDN().equals(to.getLowestRDN()) == false)
891         {
892             // we must be doing a 'copy' with a naming problem, so
893
// update the newCopy node with it's new, unique RDN.
894
newCopy.update(to.getLowestRDN());
895         }
896
897         parent.sort();
898         // signal a change to the tree for a display update...
899
treeModel.nodeStructureChanged(parent);
900     }
901
902     /**
903      * Copies the node 'from' (creating a new node to hold the copy),
904      * adding it as a child to 'toParent',
905      * and recursively copies all the children held by 'from' into
906      * the newly created copy.
907      * This only affects the tree, <i>not</i> the directory.
908      *
909      * @param from the node to copy.
910      * @param toParent the node which receives the from node as a child.
911      * @return the copy of from that is added to toParent
912      */

913
914     public SmartNode copyTreeNodes(SmartNode from, SmartNode toParent)
915     {
916         SmartNode fromCopy = new SmartNode(from); // make copy of 'from' called 'fromCopy
917

918         /*
919          * Time saver/Bug Fix - don't bother updating the tree
920          * if the parent hasn't had its children read yet.
921          */

922          
923         if (toParent.hasDummy())
924             return fromCopy;
925
926         toParent.add(fromCopy); // add 'fromCopy' to 'toParent'
927

928         Enumeration JavaDoc children = from.children(); // find children of 'from'
929
while (children.hasMoreElements())
930         {
931             SmartNode child = (SmartNode) children.nextElement();
932             copyTreeNodes(child, fromCopy); // and add them recursively to 'fromCopy'
933
}
934         fromCopy.sort();
935         return fromCopy;
936     }
937
938     /**
939      * fully expands all nodes of the tree.
940      * (Mainly used for debugging; this could take an
941      * unreasonable time if used on a large production
942      * directory.)
943      */

944
945     public void expandAll()
946     {
947         int rows = 0;
948         while (rows != getRowCount()) // i.e. tree is still expanding...
949
{
950             rows = getRowCount();
951             for (int i = 0; i < rows; i++)
952                 expandRow(i);
953         }
954
955     }
956
957     /**
958      * This makes the internal tree object available, in case
959      * different cell editors/renderers and so on need to be
960      * registered.
961      *
962      * @return the internal tree object
963      */

964
965     public JTree getTree()
966     {
967         return this;
968     }
969
970     /**
971      * Starts the process of making a new entry. As an aid to the user,
972      * it tries to find any children of the current node, and if it can
973      * find such, it uses them as a template for the new object.
974      */

975
976     public void makeNewEntry(DN parentDN)
977     {
978         if (treeDataSource.getSchemaOps() == null)
979         {
980             JOptionPane.showMessageDialog(owner, CBIntText.get("Because there is no schema currently published by the\ndirectory, adding a new entry is unavailable."), CBIntText.get("No Schema"), JOptionPane.INFORMATION_MESSAGE);
981             return;
982         }
983         else
984         {
985             // step 1: find a child to use as a template
986

987             SmartNode parent = treeModel.getNodeForDN(parentDN);
988             DN childDN = null;
989             if (parent == null)
990             {
991                 log.warning("unable to find " + parentDN + " in tree!");
992                 return;
993             }
994
995             if (parent.getChildCount() > 0)
996             {
997                 SmartNode child = (SmartNode) parent.getChildAt(0);
998                 if ((child != null) && (child.isDummy() == false))
999                 {
1000                    RDN childRDN = child.getRDN();
1001                    childDN = new DN(parentDN);
1002                    try
1003                    {
1004                        childDN.addChildRDN(childRDN);
1005                    }
1006                    catch (InvalidNameException e)
1007                    {
1008                        log.log(Level.WARNING, "ERROR: makeNewEntry(DN parentDN) " + parentDN, e);
1009                    }
1010                }
1011                else // children are not currently displayed - send of a query to get them...
1012
{
1013                    refresh(parentDN);
1014                }
1015
1016            }
1017
1018// step 2: find a datasink that can handle a partially created entry
1019

1020            DataSink editor = null;
1021            for (int i = 0; i < treeDataSinks.size(); i++)
1022            {
1023                if (((DataSink) treeDataSinks.get(i)).canCreateEntry())
1024                    editor = (DataSink) treeDataSinks.get(i);
1025            }
1026
1027            if (editor == null)
1028            {
1029                CBUtility.error("Unable to create a new entry!", new Exception JavaDoc("No available entry editors"));
1030                return;
1031            }
1032
1033// step 3: open a NewEntryWin (see) and pass the found child to it.
1034

1035
1036            NewEntryWin userData = new NewEntryWin(parentDN, childDN, treeDataSource, editor, owner);
1037
1038            userData.setSize(400, 300);
1039            CBUtility.center(userData, owner); // TE: centres window.
1040
userData.setVisible(true);
1041        }
1042    }
1043
1044/*
1045    public boolean exists(DN nodeDN)
1046    {
1047        if (nodeDN == null) return false;
1048        try
1049        {
1050            return treeDataSource.exists(nodeDN).getStatus();
1051        } catch (NamingException e) {CBUtility.error("threaded broker error: ", e); } // XXXTHREAD
1052        return false;
1053    }
1054*/

1055
1056    /**
1057     * Returns the current popup tool.
1058     * This is used by the menu bar and others to trigger edits
1059     * etc, since all that functionality lives in SmartPopupTool.
1060     */

1061
1062    public SmartPopupTool getPopupTool()
1063    {
1064        return popupTreeTool;
1065    }
1066
1067    public void registerEventPublisher(JXplorerEventGenerator gen)
1068    {
1069        eventPublisher = gen;
1070    }
1071
1072    public void fireJXplorerEvent(JXplorerEvent e)
1073    {
1074        if (eventPublisher != null)
1075            eventPublisher.fireJXplorerEvent(e);
1076    }
1077
1078
1079    public boolean isModifiable()
1080    {
1081        return treeDataSource.isModifiable();
1082    }
1083
1084    public DirContext getDirContext()
1085    {
1086        return (treeDataSource == null) ? null : treeDataSource.getDirContext();
1087
1088    }
1089
1090    /**
1091     * This files a request with the directory broker to modify (move / delete / add)
1092     * an entry. If oldEntry is null this is an add, if newEntry is null it is a
1093     * delete, otherwise it is (when called by the tree) usually a rename.
1094     */

1095
1096    public void modifyEntry(DXEntry oldEntry, DXEntry newEntry)
1097    {
1098        if (oldEntry == null && newEntry == null) return; // nothing to do.
1099

1100        treeDataSource.modifyEntry(oldEntry, newEntry); // queue directory request
1101
}
1102
1103
1104    /**
1105     * This files a request with the directory broker to copy a node or subtree.
1106     */

1107
1108    //XXX This should be done in the thread that actually does the operation - it is
1109
//XXX possible for the user to 'beat' this method by quickly copying multiples of
1110
//XXX the same entry. Also, it produces extra naming attributes.
1111

1112    public void copyTree(DN oldNodeDN, DN newNodeDN)
1113    {
1114
1115        // 'copy'ing a tree means placing the old tree *under*
1116
// the newly selected tree... hence deepen 'activeDN' by
1117
// one new level. (i.e. copying ou=eng,o=uni,c=au moved
1118
// to o=biz,c=au requires activeDN extended to ou=eng,o=biz,c=au before move
1119

1120        // * first check name is not already there; if it is, create a
1121
// unique name of the form "copy [(n)] of ..." first.
1122
// * if adding this to the *display* tree fails display error message.
1123
// * if directory mod fails, clean up display tree...
1124

1125        String JavaDoc uniqueRDN = treeModel.getUniqueCopyRDN(newNodeDN, oldNodeDN);
1126
1127        // check not recursively pasting
1128
try
1129        {
1130            newNodeDN.addChildRDN(uniqueRDN);
1131        }
1132        catch (javax.naming.InvalidNameException JavaDoc e)
1133        {
1134            CBUtility.error(this, CBIntText.get("Unable to add {0} due to bad name", new String JavaDoc[]{newNodeDN.toString()}), e);
1135            return;
1136        }
1137
1138        treeDataSource.copyTree(oldNodeDN, newNodeDN); // queue directory request
1139
}
1140
1141
1142    /**
1143     * Displays the result of a expand result, triggered from the data listener.
1144     * This (should) run from the directory connection thread.
1145     *
1146     * @param result the directory read result
1147     */

1148
1149    protected void displayExpandedNodeResult(DataQuery result)
1150    {
1151        // 1) Find node for result
1152

1153        SmartNode node = treeModel.getNodeForDN(result.requestDN());
1154
1155        if (node == null)
1156        {
1157            node = addNode(result.requestDN());
1158        }
1159
1160        try
1161        {
1162            // XXX EXTREME HACKINESS AS WE TRY TO SIMULTANEOUSLY SATISFY RICK AND SCOTT'S CONFLICTING REQUIREMENTS
1163
// 1b) Special Hack for structural nodes to make work nicely for both x500 and ldap...
1164
/**
1165             * What a debacle. If null prefix router has knowledge of democorp (o=democorp,c=au) then a list of 'root' fails
1166             * horribly, because no-one has the node 'c=au'. So we hack it to not delete root nodes even if it gets zero
1167             * results from a list. Whoo hoo. *But*, we still need to keep it all clear in the circumstance that it is
1168             * just an empty dsa with a null root. So we have to check IF the node is a 'base DN' type node (alwaysRefresh())
1169             * AND we got no search results THEN if it is an empty DSA (only has a single dummy child) close things up,
1170             * OTHERWISE keep the 'fake' node (e.g. c=AU).
1171             **/

1172            if (node.isAlwaysRefresh() && result.getEnumeration().size() == 0)
1173            {
1174                if (node.getChildCount() != 1 || ((SmartNode) node.getChildAt(0)).isDummy() == false)
1175                {
1176                    return; // don't remove structural nodes children, even if server returns no entries. Assume server is wrong. (for Scott)
1177
}
1178            }
1179
1180            // 2) clear old data
1181

1182            node.removeAllChildren();
1183
1184            // 3) add new data
1185

1186            addCutting(node, result.getEnumeration());
1187
1188            // XXX (another) pki hack
1189

1190            if (node == getLowestRootNode() && node.getChildCount() == 0)
1191            {
1192                pluggableEditorSource.displaySpecialEntry(null, treeDataSource, JXplorer.getProperty("null.entry.editor"));
1193            }
1194
1195
1196            // 4) Make sure the newly added data nodes are visible
1197

1198            expandPath(treeModel.getPathForDN(result.requestDN()));
1199
1200        }
1201        catch (NamingException e)
1202        {
1203            result.setException(e); // register error and trust someone else to handle it...
1204
node.removeAllChildren();
1205        }
1206    }
1207
1208
1209    /**
1210     * Takes a DN and reads and displays the corresponding entry.
1211     * Then reads all unexpanded parent nodes. These calls are done
1212     * via the broker thread.
1213     * TE: only displays the entry if the supplied dn is below the root
1214     * DN (which actually is the baseDN) and if the prefix is the same.
1215     *
1216     * @param dn the end DN to display.
1217     */

1218
1219    public void readAndExpandDN(DN dn)
1220    {
1221        if (currentDN.size() < rootDN.size())
1222        { //TE: if the user has selected a node above the prefix (i.e. c AU instead of c AU o DEMOCORP
1223
// in the case of a router being off line) - re select the prefix otherwise a read error may occur.
1224
refresh(rootDN);
1225            setSelectionPath(treeModel.getPathForDN(rootDN));
1226        }
1227
1228        if (dn.size() < rootDN.size() || !dn.getPrefix(rootDN.size()).toString().equalsIgnoreCase(rootDN.toString()))
1229        {
1230            //TE: only display the entry if the dn is below the root DN (baseDN) and if the prefix is the same.
1231

1232            JOptionPane.showMessageDialog(owner, CBIntText.get("The entry {0}\nwill not be displayed because it is either above the baseDN\n{1}\n" +
1233                    "that you are connected with or it has a different prefix.", new String JavaDoc[]{dn.toString(), rootDN.toString()}),
1234                    CBIntText.get("Display Error"), JOptionPane.ERROR_MESSAGE);
1235            return;
1236        }
1237
1238        log.warning("Opening '" + dn + "' from root DN '" + getRootDN()); //TE: fixes bug 2540 - don't ask me how!
1239

1240        // work down the DN, reading children as required...
1241

1242        for (int level = 1; level <= dn.size(); level++)
1243        {
1244            DN ancestor = (DN) dn.getPrefix(level);
1245
1246            if (ancestor.size() >= rootDN.size())
1247            {
1248                SmartNode node = treeModel.getNodeForDN(ancestor);
1249
1250                /*
1251                 * Check if node hasn't been read - if so, read it and all its sibling
1252                 * nodes, by 'listing' all the children of its parent.
1253                 */

1254                if (node == null)
1255                {
1256                    treeDataSource.getChildren(ancestor.parentDN());
1257                }
1258                /*
1259                 * This check shouldn't be called, if the baseDN is skipped.
1260                 */

1261                else if (node.isStructural())
1262                {
1263                    treeDataSource.getChildren(ancestor); //TE: for some unknown reason the structural nodes (root dn) need to be read for the correct behavor to occur.
1264
}
1265                /*
1266                 * The node hasn't been read yet - read its sibling nodes by 'listing'
1267                 * all the children of its parent.
1268                 */

1269                else if (node.isDummy())
1270                {
1271                    treeDataSource.getChildren(ancestor.parentDN());
1272                }
1273                /*
1274                 * The node already exists in the tree - make sure its visible.
1275                 */

1276                else
1277                {
1278                    //was: expandDN(ancestor);
1279
treeDataSource.getChildren(ancestor.parentDN());
1280                }
1281            }
1282        }
1283        treeDataSource.getEntry(dn);
1284    }
1285
1286
1287    /**
1288     * Displays the result of an entry read result, triggered from the data listener.
1289     * This (should) run from the directory connection thread.
1290     *
1291     * @param node the directory read result. If null, indicates a 'no data' display should
1292     * be shown.
1293     */

1294
1295    public void displayReadNodeResult(DataQuery node)
1296    {
1297        // sanity check
1298
if (treeDataSinks.size() == 0)
1299        {
1300            log.warning("no data sink in display Node");
1301            return;
1302        }
1303        setEntry(null);
1304
1305        try
1306        {
1307            if (node != null)
1308            {
1309                setEntry(node.getEntry());
1310                currentDN = node.requestDN();
1311            }
1312            else
1313            {
1314                currentDN = null;
1315            }
1316
1317            publishData(entry, treeDataSource);
1318
1319
1320        }
1321        catch (NamingException e)
1322        {
1323            CBUtility.error("unexpected naming error trying to \ndisplay: " + node, e);
1324        }
1325
1326
1327        if (node != null)
1328        {
1329            TreePath current = treeModel.getPathForDN(node.requestDN());
1330
1331            if (current == null)
1332            {
1333                log.warning("Unable to find tree path for DN: " + node.requestDN());
1334                return;
1335            }
1336
1337// 'User' demand seems to oscillate between wanting the next level displayed, and not
1338
// wanting it displayed. Comment out appropriate section below...
1339

1340// just expand node
1341
if (isExpanded(current.getParentPath()) == false)
1342                expandPath(current.getParentPath());
1343
1344// expand node and children
1345
// if (isExpanded(current) == false)
1346
// expandPath(current);
1347

1348            if (current.equals(getSelectionPath()) == false)
1349            {
1350                setSelectionPath(current);
1351            }
1352
1353            if (currentDN.size() <= rootDN.size())
1354            //TE: if the user has connected using a baseDN for example, and then moves
1355
//TE: up the tree, change the rootDN to the entry that the user has moved to.
1356
rootDN = currentDN;
1357        }
1358    }
1359
1360    /**
1361     * sets the current entry highlighted by the tree.
1362     */

1363    public void setEntry(DXEntry newEntry)
1364    {
1365        entry = newEntry;
1366    }
1367
1368    /**
1369     * This publishes entry data to all available data listeners,
1370     * along with a data source.
1371     * note that both entry and treeDataSource can legitimately be null here
1372     *
1373     * @param entry the entry to publish. Null indicates no data
1374     * @param dataSource the datasource to use for further info/operations.
1375     * Null indicates no available data source.
1376     */

1377    public void publishData(DXEntry entry, DataSource dataSource)
1378    {
1379        for (int i = 0; i < treeDataSinks.size(); i++)
1380        {
1381            ((DataSink) treeDataSinks.elementAt(i)).displayEntry(entry, treeDataSource);
1382        }
1383    }
1384
1385
1386    /**
1387     * This is called when a modify request has been completed.
1388     */

1389
1390    protected void displayModifyResult(DataQuery result)
1391    {
1392
1393        try
1394        {
1395            if (result.getStatus() == true)
1396            {
1397                DXEntry oldEntry = result.oldEntry();
1398                DXEntry newEntry = result.newEntry();
1399
1400                // first, do the 'easy' pure adds and pure deletes.
1401

1402                if (oldEntry == null || (newEntry != null) && (newEntry.isNewEntry())) // add
1403
{
1404                    SmartNode node = addNode(newEntry.getDN());
1405                    node.add(new SmartNode()); // stick in a dummy node so there's something to expand to...
1406
doObjectClassSpecificHandling(node, newEntry.getAllObjectClasses());
1407                    
1408                    //XXX *** WARNING ***
1409
//
1410
// This code is a little naughty. We reset the status of
1411
// the entry, assuming that no other editor cares about it.
1412
// - this *should* be done by the broker, but for some reason
1413
// isn't...
1414

1415                    newEntry.setStatus(DXEntry.NEW_WRITTEN);
1416                    publishData(newEntry, treeDataSource);
1417                }
1418                else if (newEntry == null) // delete
1419
{
1420                    deleteTreeNode(treeModel.getNodeForDN(oldEntry.getDN()));
1421                }
1422                else if (oldEntry.getDN().equals(newEntry.getDN()) == false) // check for a change of name
1423
{
1424                    SmartNode node = treeModel.getNodeForDN(oldEntry.getDN());
1425
1426                    /*
1427                     * If the old node is not null, then move it to the right place. If it
1428                     * *is* null, it should be because it has been directly edited (and hence
1429                     * the tree model is already up to date).
1430                     */

1431                     
1432                    if (node != null)
1433                    {
1434                        moveTreeNode(node, newEntry.getDN());
1435                        treeModel.nodeChanged(node);
1436                    }
1437                    
1438                    /**
1439                     * If the newEntry is empty (i.e. we're just doing a name change)
1440                     * use the atts from the old entry...
1441                     */

1442                     
1443                    if (newEntry.size() == 0)
1444                    {
1445                        newEntry.put(oldEntry.getAll());
1446                    }
1447                    
1448                    // re-read node (to get attributes)
1449
// XXX is this always necessary?
1450

1451                    treeDataSource.getEntry(newEntry.getDN());
1452                }
1453                else // update editors
1454
{
1455                    // re-read node so as to force the browser to correctly display the current state
1456
// (A bit heavy, but solves a bunch of consistancy problems)
1457

1458                    treeDataSource.getEntry(newEntry.getDN());
1459
1460                }
1461                // don't need to worry about a change of attributes, since the tree doesn't use them...
1462
}
1463        }
1464        catch (NamingException e)
1465        {
1466            result.setException(e); // XXX set the exception on the result object, let someone else handle it.
1467
}
1468        catch (Exception JavaDoc e)
1469        {
1470            e.printStackTrace();
1471
1472        }
1473    }
1474
1475    /**
1476     * Displays a copy result, triggered from the data listener.
1477     * This (should) run from the directory connection thread.
1478     *
1479     * @param result the directory copy result.
1480     * be shown.
1481     */

1482
1483    protected void displayCopyResult(DataQuery result)
1484    {
1485        try
1486        {
1487            if (result.getStatus() == true)
1488            {
1489                copyTreeNode(treeModel.getNodeForDN(result.oldDN()), result.requestDN());
1490            }
1491        }
1492        catch (NamingException e)
1493        {
1494            result.setException(e); // XXX set the exception on the result object, let someone else handle it.
1495
}
1496    }
1497
1498    /**
1499     * Displays a search result, triggered from the data listener.
1500     * This (should) run from the directory connection thread.
1501     *
1502     * @param result the directory copy result.
1503     * be shown.
1504     */

1505
1506    protected void displaySearchResult(DataQuery result)
1507    {
1508
1509// XXX Currently search results aren't sorted: do want to make them sorted?
1510
// XXX (easy way is to sort result.getEnumeration() as DXNamingEnumeration,
1511
// XXX but this may be expensive...
1512
setNumOfResults(0);
1513
1514        try
1515        {
1516            NamingEnumeration results = result.getEnumeration();
1517
1518            while (results.hasMoreElements())
1519            {
1520                SearchResult sr = (SearchResult) results.nextElement();
1521                //Attribute obClass = sr.getAttributes().get("objectClass");
1522
String JavaDoc search = sr.getName();
1523                if (search == null || search.length() == 0)
1524                {
1525                    addNode(new DN(SmartTree.NODATA));
1526                }
1527                else
1528                {
1529                    DN searchDN = new DN(search);
1530                    addNode(searchDN);
1531                    numOfResults++;
1532                }
1533            }
1534            //TE: task 4648...
1535
if (owner instanceof JXplorer)
1536                ((JXplorer) owner).setStatus("Number of search results: " + String.valueOf(numOfResults));
1537
1538            expandAll();
1539
1540        }
1541        catch (NamingException e)
1542        {
1543            result.setException(e); // XXX set the exception on the result object, let someone else handle it.
1544
}
1545    }
1546
1547    /**
1548     * @return the numOfResults.
1549     */

1550
1551    public int getNumOfResults()
1552    {
1553        return numOfResults;
1554    }
1555
1556    /**
1557     * Sets numOfResults.
1558     *
1559     * @param numOfResults a holder for the number of results returned by a search.
1560     */

1561
1562    public void setNumOfResults(int numOfResults)
1563    {
1564        this.numOfResults = numOfResults;
1565    }
1566
1567    /**
1568     * By default the tree can handle dataQueries of type LIST, COPY, MODIFY, and READENTRY.
1569     * This method allows these capabilities to be modified (for example to include SEARCH).
1570     * @see com.ca.directory.jxplorer.DataQuery .
1571     */

1572/*
1573    public void setCapabilities(int cap)
1574    {
1575        treeCapabilities = cap;
1576    }
1577*/

1578    //
1579
//
1580
// Graphicsy overhead / user i/o functions...
1581
//
1582
//
1583

1584
1585    /**
1586     * Sets up a listener to monitor whether the user has
1587     * finished editing a tree cell...
1588     */

1589
1590    protected void setTreeCellEditorListener()
1591    {
1592
1593        // We are unable to distinguish between keyboard 'esc'
1594
// and mouse clicking outside the cell, we'll pretend
1595
// the user wants their changes to go through.
1596
// XXX may still need to work out some way to distinguish
1597
// XXX between different cancel modes...
1598

1599        CellEditorListener cl = new CellEditorListener()
1600        {
1601            public void editingCanceled(ChangeEvent e)
1602            {
1603                changeDN();
1604            }
1605
1606            public void editingStopped(ChangeEvent e)
1607            {
1608                changeDN();
1609            }
1610
1611            /**
1612             * This method is called when the user has changed the name of an
1613             * entry directly, using a tree cell editor or the multi-valued
1614             * RDN editor.
1615             */

1616
1617            protected void changeDN()
1618            {
1619                // o.k., we're not connected to anything...
1620
if (isActive() == false)
1621                    return;
1622
1623                RDN rdn = (RDN) treeEditor.getCellEditorValue();
1624
1625                DN newDN = new DN(currentDN);
1626
1627                newDN.setRDN(rdn, newDN.size() - 1);
1628
1629                // check if anything actually changed...
1630
if (currentDN.toString().equals(newDN.toString()))
1631                    return;
1632
1633                //TE: Bug 3172 - if the name exists in the tree, don't attempt a rename...
1634

1635                if (treeModel.exists(newDN))
1636                {
1637                    new CBErrorWin(owner, "The name you are trying to use already exists - " +
1638                            "please choose another name or delete the original entry.",
1639                            "Name already exists");
1640                    refresh(currentDN.parentDN());
1641                    return;
1642                }
1643
1644                // modify entry will sort out all the yucky details for us...
1645
treeDataSource.modifyEntry(new DXEntry(currentDN), new DXEntry(newDN));
1646            }
1647        };
1648
1649        treeEditor.addCellEditorListener(cl);
1650    }
1651
1652    /**
1653     * sets up the mouse listener to monitor mouse clicks. At
1654     * the moment, the sole use of this is to check whether the
1655     * popup menu has been triggered.
1656     */

1657
1658    protected void setTreeMouseListener()
1659    {
1660        MouseListener ml = new MouseAdapter()
1661        {
1662            public void mousePressed(MouseEvent e)
1663            {
1664                if (!doPopupStuff(e)) super.mousePressed(e);
1665            }
1666
1667            public void mouseReleased(MouseEvent e)
1668            {
1669                if (!doPopupStuff(e)) super.mouseReleased(e);
1670            }
1671
1672            public boolean doPopupStuff(MouseEvent e)
1673            {
1674                if (isActive() == false) return false; // o.k., we're not connected to anything...
1675

1676                if (e.isPopupTrigger() == false) return false;
1677
1678                TreePath path = getPathForLocation(e.getX(), e.getY());
1679                if (path == null)
1680                {
1681                    return false;
1682                }
1683
1684                setSelectionPath(path); // make sure highlighting stays around
1685

1686                // this probably isn't necessary, but just to make sure that currentDN is set
1687

1688                DN thisDN = treeModel.getDNForPath(path);
1689                if (thisDN.equals(currentDN) == false)
1690                {
1691                    currentDN = thisDN;
1692                }
1693
1694                if (treeDataSource != null)
1695                {
1696                    popupTreeTool.setModifiable(treeDataSource.isModifiable()); // whether the user can change anything...
1697

1698                    // XXX el hack - check to see if entry has a *special* popup tool to use instead...
1699
if (getSelectedNode().getPopupMenu() != null)
1700                        getSelectedNode().getPopupMenu().show(SmartTree.this, e.getX(), e.getY());
1701                    else
1702                    { //TE: this should be improved...
1703
Toolkit toolKit = Toolkit.getDefaultToolkit();
1704
1705                        popupTreeTool.show(SmartTree.this, e.getX(), e.getY()); //TE: displays the popup menu.
1706

1707                        if ((int) popupTreeTool.getLocationOnScreen().getY() > toolKit.getScreenSize().height - (popupTreeTool.getHeight() + 30)) //TE: if the popup menu extends off the bottom of the screen...
1708
{
1709                            popupTreeTool.show(SmartTree.this, e.getX(), e.getY() - popupTreeTool.getHeight()); //TE: ...reposition it so that the menu ascends from the node rather than descends!
1710
}
1711                    }
1712                }
1713                return true;
1714            }
1715        };
1716        addMouseListener(ml);
1717    }
1718
1719    /**
1720     * null implementation to satisfy @TreeExpansionListener interface
1721     *
1722     * @param e tree event, implicitly specifying the expanding node.
1723     */

1724
1725    public void treeCollapsed(TreeExpansionEvent e)
1726    {
1727    }
1728
1729    /**
1730     * The user has asked the tree to expand. Check the node,
1731     * and if it is a null placeholder, read the node properly from the
1732     * directory before expanding and displaying.
1733     *
1734     * @param e tree event, implicitly specifying the expanding node.
1735     */

1736
1737    public void treeExpanded(TreeExpansionEvent e)
1738    {
1739        if (isActive() == false) return; // o.k., we're not connected to anything...
1740
SmartNode current = (SmartNode) e.getPath().getLastPathComponent();
1741
1742
1743        try
1744        {
1745            if (((SmartNode) current.getFirstChild()).isDummy() == true)
1746            {
1747                treeDataSource.getChildren(treeModel.getDNForNode(current));
1748            }
1749            else if (current.isAlwaysRefresh())
1750            {
1751                treeDataSource.getChildren(treeModel.getDNForNode(current));
1752            }
1753        }
1754        catch (java.util.NoSuchElementException JavaDoc err)
1755        {
1756        } // why would it be trying to expand anyway?
1757
}
1758
1759    /**
1760     * a node value has changed, so redisplay it...
1761     *
1762     * @param e tree event, implicitly specifying the changed node.
1763     */

1764
1765    public void valueChanged(TreeSelectionEvent e)
1766    {
1767        if (isActive() == false) return; // o.k., we're not connected to anything...
1768

1769        if (getSelectionPath() == null)
1770            return;
1771
1772        if (e.isAddedPath() == false) // deletion occured
1773
{
1774            setSelectionPath(null); // clear the 'currently selected' data object in popupTreeTool
1775
displayReadNodeResult(null); // clear the editor
1776
}
1777        else // addition occured
1778
{
1779            DN addedDN = treeModel.getDNForPath(getSelectionPath());
1780
1781            if (addedDN.equals(currentDN) == false)
1782            {
1783                treeDataSource.getEntry(treeModel.getDNForNode(getSelectedNode()));
1784            }
1785        }
1786    }
1787
1788
1789    /**
1790     * Returns whether the tree is active - i.e. has a valid data source,
1791     * which is active, and the
1792     * tree has it's root set.
1793     */

1794
1795    protected boolean isActive()
1796    {
1797        if (treeDataSource == null) return false;
1798        if (treeDataSource.isActive() == false) return false;
1799        if (rootSet == false) return false;
1800        return true;
1801    }
1802
1803    /**
1804     * This is the data listener interface - this method is called when a data query is finished
1805     * by a Broker. The tree listens to these results, and adjusts itself to reflect successfull
1806     * directory operations.
1807     */

1808
1809    public void dataReady(DataQuery result)
1810    {
1811        int type = result.getType();
1812
1813        if (result.hasException())
1814        {
1815            String JavaDoc exception = result.getException().toString(); //TE: quick solution to bug 561...if dsa is offine keep the tree but set everything else to disconnected mode.
1816
if (exception.indexOf("Socket closed") > -1)
1817                if (owner instanceof JXplorer)
1818                    ((JXplorer) owner).setDisconnectView();
1819
1820            CBUtility.error("Unable to perform " + result.getTypeString() + " operation.", result.getException());
1821
1822            if (type == DataQuery.LIST) // clean up failed list result...
1823
{
1824                SmartNode node = treeModel.getNodeForDN(result.requestDN());
1825                if (!node.isAlwaysRefresh()) // XXX Hack to avoid losing tree when get error reading non-existant base DN node.
1826
{
1827                    node.removeAllChildren();
1828                    treeModel.nodeStructureChanged(node);
1829                }
1830            }
1831
1832            return;
1833        }
1834        else
1835        {
1836            switch (type)
1837            {
1838                case DataQuery.LIST:
1839                    displayExpandedNodeResult(result);
1840                    break;
1841
1842                case DataQuery.COPY:
1843                    displayCopyResult(result);
1844                    break;
1845
1846                case DataQuery.MODIFY:
1847                    displayModifyResult(result);
1848                    break;
1849
1850                case DataQuery.SEARCH:
1851                    displaySearchResult(result);
1852                    break;
1853
1854                case DataQuery.READENTRY:
1855                    displayReadNodeResult(result);
1856                    break;
1857            }
1858
1859            if (result.hasException())
1860            {
1861                CBUtility.error("Exception occurred during tree display of " + result.getTypeString() + ".\n\n(Error caught by display tree)", result.getException());
1862                return;
1863            }
1864        }
1865    }
1866
1867
1868    public void validate()
1869    {
1870        super.validate();
1871    }
1872
1873    // ********************
1874
//
1875
// *** DRAG 'N DROP ***
1876
//
1877
// ********************
1878

1879
1880    protected void setupDragAndDrop()
1881    {
1882// XXX Disable Drag and Drop on Solaris. Doesn't work worth a damn, and
1883
// XXX has some *VERY* strange behaviour
1884

1885        if (JXplorer.isSolaris()) return;
1886
1887// Disable Drag and Drop if the user has set the option to do so... ('true' is default though).
1888
if (!JXplorer.getProperty("option.drag.and.drop").equals("true"))
1889            return;
1890
1891        /* Custom dragsource object: needed to handle DnD in a JTree.
1892          * This is pretty ugly. I had to overide (labotimize) the updateCurrentCursor
1893         * method to get the cursor to update properly.
1894         */

1895
1896        dragSource = new DragSource()
1897        {
1898            protected DragSourceContext createDragSourceContext
1899                    (DragSourceContextPeer dscp, DragGestureEvent dgl, Cursor dragCursor,
1900                     Image dragImage, Point imageOffset, Transferable JavaDoc t,
1901                     DragSourceListener dsl)
1902            {
1903                return new DragSourceContext(dscp, dgl, dragCursor, dragImage, imageOffset, t, dsl)
1904                {
1905                    protected void updateCurrentCursor(int dropOp, int targetAct, int status)
1906                    {
1907                    }
1908                };
1909            }
1910        };
1911
1912
1913        DragGestureRecognizer dgr = dragSource.createDefaultDragGestureRecognizer(this,
1914                DnDConstants.ACTION_COPY_OR_MOVE, this);
1915
1916
1917        /*
1918* Eliminates right mouse clicks as valid actions - useful especially
1919          * if you implement a JPopupMenu for the JTree
1920         */

1921//? dgr.setSourceActions(dgr.getSourceActions() & ~InputEvent.BUTTON3_MASK);
1922

1923        dgr.setSourceActions(dgr.getSourceActions() + InputEvent.BUTTON1_MASK);
1924
1925        /* First argument: Component to associate the target with
1926         * Second argument: DropTargetListener
1927        */

1928        new DropTarget(this, this);
1929
1930    }
1931
1932    /**
1933     * DragGestureListener interface method
1934     */

1935    public void dragGestureRecognized(DragGestureEvent e)
1936    {
1937        //Get the selected node
1938
SmartNode dragNode = getSelectedNode();
1939        if (dragNode != null)
1940        {
1941            dragging = true;
1942
1943            //Get the Transferable Object
1944
Transferable JavaDoc transferable = (Transferable JavaDoc) dragNode;
1945
1946            //Select the appropriate cursor;
1947
Cursor cursor = DragSource.DefaultCopyDrop;
1948            int action = e.getDragAction();
1949            if (action == DnDConstants.ACTION_MOVE)
1950                cursor = DragSource.DefaultMoveDrop;
1951
1952            //begin the drag
1953
dragSource.startDrag(e, cursor, transferable, this);
1954        }
1955    }
1956
1957    /**
1958     * DragSourceListener interface method
1959     */

1960    public void dragDropEnd(DragSourceDropEvent dsde)
1961    {
1962        dragging = false;
1963    }
1964
1965    /**
1966     * DragSourceListener interface method
1967     */

1968    public void dragEnter(DragSourceDragEvent dsde)
1969    {
1970        setCursor(dsde);
1971    }
1972
1973    /**
1974     * DragSourceListener interface method
1975     */

1976    public void dragOver(DragSourceDragEvent dsde)
1977    {
1978        setCursor(dsde);
1979    }
1980
1981    /**
1982     * DragSourceListener interface method
1983     */

1984    public void dropActionChanged(DragSourceDragEvent dsde)
1985    {
1986    }
1987
1988    /**
1989     * DragSourceListener interface method
1990     */

1991    public void dragExit(DragSourceEvent dsde)
1992    {
1993    }
1994
1995    /**
1996     * DragSourceListener interface method. This is a bit ugly.
1997     * This is where we set the cursor depending on whether
1998     * we are allowed to drop our Transferable object. However,
1999     * we need to have the location of the mouse and the object
2000     * that sets the cursor, which come from two different events.
2001     * Its not pretty but I set a global variable with the location
2002     * in the DropTargetListener dragOver method.
2003     */

2004    private void setCursor(DragSourceDragEvent dsde)
2005    {
2006        //if we dont know the cursor location, don't do anything.
2007
if (cursorLocation == null) return;
2008
2009        TreePath destinationPath =
2010                getPathForLocation(cursorLocation.x, cursorLocation.y);
2011
2012        //get the object that sets the cursor type
2013
DragSourceContext dsc = dsde.getDragSourceContext();
2014
2015        //TE: if copy & if destination path is okay set cursor to allow drop...
2016
if (testDropTarget(destinationPath, getSelectionPath()) == null && dsde.getDropAction() == 1)
2017            dsc.setCursor(DragSource.DefaultCopyDrop);
2018
2019        //TE: otherwise...if copy...set to drop not allowed
2020
else if (dsde.getDropAction() == 1)
2021            dsc.setCursor(DragSource.DefaultCopyNoDrop);
2022
2023        //TE: if move & if destination path is okay set cursor to allow drop...
2024
else if (testDropTarget(destinationPath, getSelectionPath()) == null && dsde.getDropAction() == 2)
2025            dsc.setCursor(DragSource.DefaultMoveDrop);
2026
2027        //TE: ...otherwise set to drop not allowed
2028
else
2029            dsc.setCursor(DragSource.DefaultMoveNoDrop);
2030    }
2031
2032
2033    /**
2034     * DropTargetListener interface method - What we do when drag is released
2035     */

2036    public void drop(DropTargetDropEvent e)
2037    {
2038        try
2039        {
2040            Transferable JavaDoc tr = e.getTransferable();
2041
2042            //flavor not supported, reject drop
2043
if (!tr.isDataFlavorSupported(SmartNode.UNICODETEXT))
2044            {
2045                e.rejectDrop();
2046                return;
2047            }
2048
2049            //cast into appropriate data type
2050
String JavaDoc bloop = tr.getTransferData(SmartNode.UNICODETEXT).toString();
2051
2052            //get new parent node
2053
Point loc = e.getLocation();
2054            TreePath destinationPath = getPathForLocation(loc.x, loc.y);
2055
2056            final String JavaDoc msg = testDropTarget(destinationPath, getSelectionPath());
2057
2058            if (msg != null)
2059            {
2060                e.rejectDrop();
2061
2062                SwingUtilities.invokeLater(new Runnable JavaDoc()
2063                {
2064                    public void run()
2065                    {
2066                        //JOptionPane.showMessageDialog(Parent, msg, "Error Dialog", JOptionPane.ERROR_MESSAGE);
2067
CBUtility.error(msg);
2068                    }
2069                });
2070
2071                return;
2072            }
2073
2074            SmartNode newParent = (SmartNode) destinationPath.getLastPathComponent();
2075            DN parentDN = treeModel.getDNForNode(newParent);
2076
2077            SmartNode oldNode = (SmartNode) getSelectedNode();
2078            DN oldDN = treeModel.getDNForNode(oldNode);
2079
2080            int action = e.getDropAction();
2081            boolean copyAction = (action == DnDConstants.ACTION_COPY);
2082
2083            if (copyAction)
2084            {
2085                if (System.getProperty("java.version").startsWith("1.4.0"))
2086                    popupTreeTool.dragCopy(oldDN, parentDN);
2087                else
2088                    popupTreeTool.copy(oldDN, parentDN); //TE: XXXXXXXX crashes! CB: Not any more - Swing bug fixed in 1.4.1+
2089
}
2090            else
2091            {
2092                if (System.getProperty("java.version").startsWith("1.4.0"))
2093                    popupTreeTool.dragMove(oldDN, parentDN);
2094                else
2095                    popupTreeTool.move(oldDN, parentDN); //TE: XXXXXXXX crashes! CB: Not any more - Swing bug fixed in 1.4.1+
2096
}
2097
2098            e.acceptDrop(action);
2099            e.getDropTargetContext().dropComplete(true);
2100        }
2101        catch (IOException JavaDoc io)
2102        {
2103            e.rejectDrop();
2104        }
2105        catch (UnsupportedFlavorException JavaDoc ufe)
2106        {
2107            e.rejectDrop();
2108        }
2109    } //end of method
2110

2111
2112    /**
2113     * DropTargetListener interface method
2114     */

2115    public void dragEnter(DropTargetDragEvent e)
2116    {
2117    }
2118
2119    /**
2120     * DropTargetListener interface method
2121     */

2122    public void dragExit(DropTargetEvent e)
2123    {
2124    }
2125
2126    /**
2127     * DropTargetListener interface method
2128     */

2129    public void dragOver(DropTargetDragEvent e)
2130    {
2131        //set global cursor location. Needed in setCursor method. El Hack.
2132
cursorLocation = e.getLocation();
2133    }
2134
2135    /**
2136     * DropTargetListener interface method
2137     */

2138    public void dropActionChanged(DropTargetDragEvent e)
2139    {
2140    }
2141
2142
2143    /**
2144     * Convenience method to test whether drop location is valid
2145     *
2146     * @param destination The destination path
2147     * @param dropper The path for the node to be dropped
2148     * @return null if no problems, otherwise an explanation
2149     */

2150
2151    private String JavaDoc testDropTarget(TreePath destination, TreePath dropper)
2152    {
2153        //Typical Tests for dropping
2154

2155        //Test 1.
2156
boolean destinationPathIsNull = destination == null;
2157        if (destinationPathIsNull)
2158            return CBIntText.get("Invalid drop location.");
2159
2160        //Test 2.
2161
// PersonNode node = (PersonNode) destination.getLastPathComponent();
2162
// if ( !node.getAllowsChildren() )
2163
// return "This node does not allow children";
2164

2165        if (destination.equals(dropper))
2166            return CBIntText.get("Destination cannot be same as source");
2167
2168        //Test 3.
2169
if (dropper.isDescendant(destination))
2170            return CBIntText.get("Destination node cannot be a descendant.");
2171
2172        //Test 4.
2173
if (dropper.getParentPath().equals(destination))
2174            return CBIntText.get("Destination node cannot be a parent.");
2175
2176        return null;
2177    }
2178
2179
2180    /**
2181     * Opens the delete bookmark dialog.
2182     */

2183    public void openDeleteBookmarkDialog()
2184    {
2185        BookMarks bm = new BookMarks((JXplorer) owner);
2186        bm.getDeleteDialog();
2187    }
2188
2189    /**
2190     * Opens the edit bookmark dialog.
2191     */

2192    public void openEditBookmarkDialog()
2193    {
2194        BookMarks bm = new BookMarks((JXplorer) owner);
2195        bm.getEditDialog();
2196    }
2197
2198    /**
2199     * Opens the add bookmark dialog.
2200     *
2201     * @param dn the DN of the bookmark to add.
2202     */

2203    public void openAddBookmarkDialog(DN dn)
2204    {
2205        BookMarks bm = new BookMarks((JXplorer) owner);
2206        BookMarks.AddDialog addDialog = bm.getAddDialog(dn.toString(), false);
2207        addDialog.setVisible(true);
2208    }
2209
2210    /**
2211     * Opens the search dialog.
2212     */

2213    public void openSearch()
2214    {
2215        openSearch(currentDN);
2216    }
2217
2218    /**
2219     * Opens the search dialog.
2220     *
2221     * @param dn the DN to search from.
2222     */

2223    public void openSearch(DN dn)
2224    {
2225        JXplorer jx = (JXplorer) owner;
2226        if (searchGUI == null)
2227            searchGUI = new SearchGUI(dn, jx);
2228
2229        searchGUI.setBaseDN(dn);
2230        searchGUI.setVisible(true);
2231    }
2232
2233    /**
2234     * @return the search GUI.
2235     */

2236    public SearchGUI getSearchGUI()
2237    {
2238        return searchGUI;
2239    }
2240
2241    /**
2242     * Set the search GUI.
2243     *
2244     * @param searchGUI
2245     */

2246    public void setSearchGUI(SearchGUI searchGUI)
2247    {
2248        this.searchGUI = searchGUI;
2249    }
2250}
Popular Tags