3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2005,2006 Albert Cardona and Rodney Douglas.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation (http://www.gnu.org/licenses/gpl.txt )
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 You may contact Albert Cardona at acardona at ini.phys.ethz.ch
20 Institute of Neuroinformatics, University of Zurich / ETH, Switzerland.
23 package ini
.trakem2
.tree
;
26 import ij
.gui
.GenericDialog
;
27 import ini
.trakem2
.persistence
.DBObject
;
28 import ini
.trakem2
.ControlWindow
;
29 import ini
.trakem2
.Project
;
30 import ini
.trakem2
.display
.*;
31 import ini
.trakem2
.utils
.IJError
;
32 import ini
.trakem2
.utils
.Render
;
33 import ini
.trakem2
.utils
.Utils
;
34 import ini
.trakem2
.utils
.Bureaucrat
;
35 import ini
.trakem2
.utils
.Worker
;
36 import ini
.trakem2
.utils
.Dispatcher
;
38 import java
.awt
.Color
;
39 import java
.awt
.Event
;
40 import java
.awt
.event
.KeyEvent
;
41 import javax
.swing
.KeyStroke
;
42 import javax
.swing
.JPopupMenu
;
43 import javax
.swing
.JMenuItem
;
44 import javax
.swing
.JMenu
;
45 import java
.awt
.event
.MouseEvent
;
46 import java
.awt
.event
.MouseListener
;
47 import java
.awt
.event
.ActionEvent
;
48 import java
.awt
.event
.ActionListener
;
49 import javax
.swing
.tree
.*;
50 import java
.util
.HashSet
;
51 import java
.util
.HashMap
;
52 import java
.util
.Iterator
;
53 import java
.util
.ArrayList
;
54 import java
.util
.List
;
55 import java
.util
.Collection
;
56 import java
.util
.Enumeration
;
58 import java
.util
.Hashtable
;
59 import java
.util
.Collections
;
62 /** A class to hold a tree of Thing nodes */
63 public final class ProjectTree
extends DNDTree
implements MouseListener
, ActionListener
{
67 System.setProperty("j3d.noOffScreen", "true");
71 private DefaultMutableTreeNode selected_node
= null;
73 public ProjectTree(Project project
, ProjectThing project_thing
) {
74 super(project
, DNDTree
.makeNode(project_thing
), new Color(240,230,255)); // new Color(185,156,255));
75 setEditable(false); // the titles
76 addMouseListener(this);
77 addTreeExpansionListener(this);
80 /** Get a custom, context-sensitive popup menu for the selected node. */
81 private JPopupMenu
getPopupMenu(DefaultMutableTreeNode node
) {
82 Object ob
= node
.getUserObject();
83 ProjectThing thing
= null;
84 if (ob
instanceof ProjectThing
) {
85 thing
= (ProjectThing
)ob
;
89 // context-sensitive popup
90 JMenuItem
[] items
= thing
.getPopupItems(this);
91 if (0 == items
.length
) return null;
92 JPopupMenu popup
= new JPopupMenu();
93 for (int i
=0; i
<items
.length
; i
++) {
96 JMenu node_menu
= new JMenu("Node");
97 JMenuItem item
= new JMenuItem("Move up"); item
.addActionListener(this); item
.setAccelerator(KeyStroke
.getKeyStroke(KeyEvent
.VK_PAGE_UP
, 0, true)); node_menu
.add(item
);
98 item
= new JMenuItem("Move down"); item
.addActionListener(this); item
.setAccelerator(KeyStroke
.getKeyStroke(KeyEvent
.VK_PAGE_DOWN
, 0, true)); node_menu
.add(item
);
100 popup
.add(node_menu
);
104 public void mousePressed(final MouseEvent me
) {
105 super.dispatcher
.execSwing(new Runnable() { public void run() {
106 if (!me
.getSource().equals(ProjectTree
.this) || !Project
.getInstance(ProjectTree
.this).isInputEnabled()) {
109 final int x
= me
.getX();
110 final int y
= me
.getY();
111 // find the node and set it selected
112 final TreePath path
= getPathForLocation(x
, y
);
116 ProjectTree
.this.setSelectionPath(path
);
117 selected_node
= (DefaultMutableTreeNode
)path
.getLastPathComponent();
119 if (2 == me
.getClickCount() && !me
.isPopupTrigger() && MouseEvent
.BUTTON1
== me
.getButton()) {
120 // show in the front Display
121 if (null == selected_node
) return;
122 Object obt
= selected_node
.getUserObject();
123 if (!(obt
instanceof ProjectThing
)) return;
124 ProjectThing thing
= (ProjectThing
)obt
;
125 thing
.setVisible(true);
126 Object obd
= thing
.getObject();
127 if (obd
instanceof Displayable
) {
128 // additionaly, get the front Display (or make a new one if none) and show in it the layer in which the Displayable object is contained.
129 Displayable displ
= (Displayable
)obd
;
130 Display
.showCentered(displ
.getLayer(), displ
, true, me
.isShiftDown());
133 } else if (me
.isPopupTrigger() || (ij
.IJ
.isMacOSX() && me
.isControlDown()) || MouseEvent
.BUTTON2
== me
.getButton() || 0 != (me
.getModifiers() & Event
.META_MASK
)) { // the last block is from ij.gui.ImageCanvas, aparently to make the right-click work on windows?
134 JPopupMenu popup
= getPopupMenu(selected_node
);
135 if (null == popup
) return;
136 popup
.show(ProjectTree
.this, x
, y
);
142 public void rename(ProjectThing thing
) {
143 Object ob
= thing
.getObject();
144 String old_title
= null;
145 if (null == ob
) old_title
= thing
.getType();
146 else if (ob
instanceof DBObject
) old_title
= ((DBObject
)ob
).getTitle();
147 else old_title
= ob
.toString();
148 GenericDialog gd
= ControlWindow
.makeGenericDialog("New name");
149 gd
.addMessage("Old name: " + old_title
);
150 gd
.addStringField("New name: ", old_title
);
152 if (gd
.wasCanceled()) return;
153 String title
= gd
.getNextString();
154 title
= title
.replace('"', '\'').trim(); // avoid XML problems - could also replace by double '', then replace again by " when reading.
155 thing
.setTitle(title
);
156 this.updateUILater();
160 public void mouseDragged(MouseEvent me
) { }
161 public void mouseReleased(MouseEvent me
) { }
162 public void mouseEntered(MouseEvent me
) { }
163 public void mouseExited(MouseEvent me
) { }
164 public void mouseClicked(MouseEvent me
) { }
166 public void actionPerformed(final ActionEvent ae
) {
167 if (!Project
.getInstance(this).isInputEnabled()) return;
168 super.dispatcher
.exec(new Runnable() { public void run() {
170 if (null == selected_node
) return;
171 final Object ob
= selected_node
.getUserObject();
172 if (!(ob
instanceof ProjectThing
)) return;
173 final ProjectThing thing
= (ProjectThing
)ob
;
175 String command
= ae
.getActionCommand();
176 final Object obd
= thing
.getObject();
178 if (command
.startsWith("new ") || command
.equals("Duplicate")) {
179 ProjectThing new_thing
= null;
180 if (command
.startsWith("new ")) {
181 new_thing
= thing
.createChild(command
.substring(4)); // if it's a Displayable, it will be added to whatever layer is in the front Display
182 } else if (command
.equals("Duplicate")) { // just to keep myself from screwing in the future
183 if (Project
.isBasicType(thing
.getType()) && null != thing
.getParent()) {
184 new_thing
= ((ProjectThing
)thing
.getParent()).createClonedChild(thing
);
187 selected_node
= (DefaultMutableTreeNode
)selected_node
.getParent();
190 if (null != new_thing
) {
191 DefaultMutableTreeNode new_node
= new DefaultMutableTreeNode(new_thing
);
192 ((DefaultTreeModel
)ProjectTree
.this.getModel()).insertNodeInto(new_node
, selected_node
, i_position
);
193 TreePath treePath
= new TreePath(new_node
.getPath());
194 ProjectTree
.this.scrollPathToVisible(treePath
);
195 ProjectTree
.this.setSelectionPath(treePath
);
197 // bring the display to front
198 if (new_thing
.getObject() instanceof Displayable
) {
199 Display
.getFront().getFrame().toFront();
201 } else if (command
.equals("many...")) {
202 ArrayList children
= thing
.getTemplate().getChildren();
203 if (null == children
|| 0 == children
.size()) return;
204 String
[] cn
= new String
[children
.size()];
206 for (Iterator it
= children
.iterator(); it
.hasNext(); ) {
207 cn
[i
] = ((TemplateThing
)it
.next()).getType();
210 GenericDialog gd
= new GenericDialog("Add many children");
211 gd
.addNumericField("Amount: ", 1, 0);
212 gd
.addChoice("New child: ", cn
, cn
[0]);
213 gd
.addCheckbox("Recursive", true);
215 if (gd
.wasCanceled()) return;
216 int amount
= (int)gd
.getNextNumber();
218 Utils
.showMessage("Makes no sense to create less than 1 child!");
221 final ArrayList nc
= thing
.createChildren(cn
[gd
.getNextChoiceIndex()], amount
, gd
.getNextBoolean());
222 addLeafs((ArrayList
<Thing
>)nc
);
223 } else if (command
.equals("Unhide")) {
224 thing
.setVisible(true);
225 } else if (command
.equals("Select in display")) {
226 boolean shift_down
= 0 != (ae
.getModifiers() & ActionEvent
.SHIFT_MASK
);
227 selectInDisplay(thing
, shift_down
);
228 } else if (command
.equals("Identify...")) {
229 // for pipes only for now
230 if (!(obd
instanceof Line3D
)) return;
231 ini
.trakem2
.vector
.Compare
.findSimilar((Line3D
)obd
);
232 } else if (command
.equals("Identify with axes...")) {
233 if (!(obd
instanceof Line3D
)) return;
234 if (Project
.getProjects().size() < 2) {
235 Utils
.showMessage("You need at least two projects open:\n-A reference project\n-The current project with the pipe to identify");
238 ini
.trakem2
.vector
.Compare
.findSimilarWithAxes((Line3D
)obd
);
239 } else if (command
.equals("Show centered in Display")) {
240 if (obd
instanceof Displayable
) {
241 Displayable displ
= (Displayable
)obd
;
242 Display
.showCentered(displ
.getLayer(), displ
, true, 0 != (ae
.getModifiers() & ActionEvent
.SHIFT_MASK
));
244 } else if (command
.equals("Show in 3D")) {
245 ini
.trakem2
.display
.Display3D
.showAndResetView(thing
);
246 } else if (command
.equals("Hide")) {
247 // find all Thing objects in this subtree starting at Thing and hide their Displayable objects.
248 thing
.setVisible(false);
249 } else if (command
.equals("Delete...")) {
250 remove(true, thing
, selected_node
);
252 } else if (command
.equals("Rename...")) {
253 //if (!Project.isBasicType(thing.getType())) {
256 } else if (command
.equals("Measure")) {
257 // block displays while measuring
258 Bureaucrat
.createAndStart(new Worker("Measuring") { public void run() {
262 } catch (Throwable e
) {
267 }}, thing
.getProject());
268 }/* else if (command.equals("Export 3D...")) {
269 GenericDialog gd = ControlWindow.makeGenericDialog("Export 3D");
270 String[] choice = new String[]{".svg [preserves links and hierarchical grouping]", ".shapes [limited to one profile per layer per profile_list]"};
271 gd.addChoice("Export to: ", choice, choice[0]);
272 gd.addNumericField("Z scaling: ", 1, 2);
274 if (gd.wasCanceled()) return;
275 double z_scale = gd.getNextNumber();
276 switch (gd.getNextChoiceIndex()) {
278 Render.exportSVG(thing, z_scale);
281 new Render(thing).save(z_scale);
284 }*/ else if (command
.equals("Export project...") || command
.equals("Save as...")) { // "Save as..." is for a FS project
285 Utils
.log2("Calling export project at " + System
.currentTimeMillis());
286 thing
.getProject().getLoader().saveAs(thing
.getProject());
287 } else if (command
.equals("Save")) {
288 // overwrite the xml file of a FSProject
289 // Just do the same as in "Save as..." but without saving the images and overwritting the XML file without asking.
290 thing
.getProject().getLoader().save(thing
.getProject());
291 } else if (command
.equals("Info")) {
294 } else if (command
.equals("Move up")) {
295 move(selected_node
, -1);
296 } else if (command
.equals("Move down")) {
297 move(selected_node
, 1);
299 Utils
.log("ProjectTree.actionPerformed: don't know what to do with the command: " + command
);
302 } catch (Exception e
) {
308 /** Remove the node, its Thing and the object hold by the thing from the database. */
309 public void remove(DefaultMutableTreeNode node
, boolean check
, boolean remove_empty_parents
, int levels
) {
310 Object uob
= node
.getUserObject();
311 if (!(uob
instanceof ProjectThing
)) return;
312 ProjectThing thing
= (ProjectThing
)uob
;
313 Display3D
.remove(thing
);
314 ProjectThing parent
= (ProjectThing
)thing
.getParent();
315 if (!thing
.remove(check
)) return;
316 ((DefaultTreeModel
)this.getModel()).removeNodeFromParent(node
);
317 if (remove_empty_parents
) removeProjectThingLadder(parent
, levels
);
318 this.updateUILater();
321 /** Recursive as long as levels is above zero. Levels defines the number of possibly emtpy parent levels to remove. */
322 public void removeProjectThingLadder(ProjectThing lowest
, int levels
) {
323 if (0 == levels
) return;
324 if (lowest
.getType().toLowerCase().equals("project")) return; // avoid Project node
326 if (null == lowest
.getChildren() || 0 == lowest
.getChildren().size()) {
327 ProjectThing parent
= (ProjectThing
)lowest
.getParent();
328 if (!lowest
.remove(false)) {
329 Utils
.log("Failed to remove parent in the ladder: " + lowest
);
333 DefaultMutableTreeNode node
= DNDTree
.findNode(lowest
, this);
334 ((DefaultTreeModel
)this.getModel()).removeNodeFromParent(node
);
335 removeProjectThingLadder(parent
, levels
--);
339 /** Implements the "Duplicate, link and send to next/previous layer" functionality. The 'position' can be zero (before) or 1 (after). The profile has no layer assigned. */
340 public Profile
duplicateChild(Profile original
, int position
, Layer layer
) {
341 Utils
.log2("ProjectTree: Called duplicateChild " + System
.currentTimeMillis() + " for original id = " + original
.getId() + " at position " + position
);
342 // find the Thing that holds it
343 DefaultMutableTreeNode root
= (DefaultMutableTreeNode
)this.getModel().getRoot();
344 ProjectThing root_thing
= (ProjectThing
)root
.getUserObject();
345 Thing child
= root_thing
.findChild(original
);
347 Utils
.log("ProjectTree.duplicateChild: node not found for original " + original
);
350 Profile copy
= (Profile
)original
.clone();
351 copy
.setLayer(layer
); // for the Z ordering
352 ProjectThing new_thing
= null;
354 new_thing
= new ProjectThing(((ProjectThing
)child
.getParent()).getChildTemplate(child
.getType()), original
.getProject(), copy
);
355 } catch (Exception e
) {
359 DefaultMutableTreeNode child_node
= (DefaultMutableTreeNode
)findNode(child
, this);
360 DefaultMutableTreeNode parent_node
= (DefaultMutableTreeNode
)child_node
.getParent();
361 ProjectThing parent_thing
= (ProjectThing
)parent_node
.getUserObject();
363 if (position
< 0) position
= 0;
364 else if (position
> 1) position
= 1;
365 int index
= parent_node
.getIndex(child_node
) + position
;
366 if (index
< 0) index
= 0;
367 if (index
> parent_node
.getChildCount()) index
= parent_node
.getChildCount() -1;
368 if (!parent_thing
.addChild(new_thing
, index
)) return null;
369 DefaultMutableTreeNode new_node
= new DefaultMutableTreeNode(new_thing
);
370 ((DefaultTreeModel
)this.getModel()).insertNodeInto(new_node
, parent_node
, index
/*parent_node.getIndex(child_node) + position*/);
371 // relist properly the nodes
372 updateList(parent_node
);
373 TreePath treePath
= new TreePath(new_node
.getPath());
374 this.scrollPathToVisible(treePath
);
375 this.setSelectionPath(treePath
);
380 public void destroy() {
382 this.selected_node
= null;
385 /* // makes no sense, because there may be multiple projects open and thus the viewport and position may interfere with each other across multiple projects. Saving the collapsed node state suffices.
386 public void exportXML(final StringBuffer sb_body, String indent, JScrollPane jsp) {
387 Point p = jsp.getViewport().getViewPosition();
388 Dimension d = jsp.getSize(null);
389 sb_body.append(indent).append("<t2_tree")
390 .append(" width=\"").append(d.width).append('\"')
391 .append(" height=\"").append(d.height).append('\"')
392 .append(" viewport_x=\"").append(p.e).append('\"')
393 .append(" viewport_y=\"").append(p.y).append('\"')
395 sb_body.append("\n").append(indent).append("</t2_tree>\n");
399 /** Creates a new node of basic type for each AreaList, Ball, Pipe or Polyline present in the ArrayList. Other elements are ignored. */
400 public void insertSegmentations(final Project project
, final Collection al
) {
401 final TemplateThing tt_root
= (TemplateThing
)project
.getTemplateTree().getRoot().getUserObject();
402 // create a new abstract node called "imported_segmentations", if not there
403 final String imported_labels
= "imported_labels";
404 if (!project
.typeExists(imported_labels
)) {
406 TemplateThing tet
= new TemplateThing(imported_labels
, project
); // yes I know I should check for the project of each Displayable in the ArrayList
407 project
.addUniqueType(tet
);
408 DefaultMutableTreeNode root
= project
.getTemplateTree().getRoot();
409 tt_root
.addChild(tet
);
410 DefaultMutableTreeNode child_node
= addChild(tet
, root
);
411 DNDTree
.expandNode(project
.getTemplateTree(), child_node
);
412 // JTree is serious pain
414 TemplateThing tt_is
= project
.getTemplateThing(imported_labels
); // it's the same as 'tet' above, unless it existed
415 // create a project node from "imported_segmentations" template under a new top node
416 DefaultMutableTreeNode project_node
= project
.getProjectTree().getRoot();
417 ProjectThing project_pt
= (ProjectThing
)project_node
.getUserObject();
418 ProjectThing ct
= project_pt
.createChild(tt_root
.getType());
419 DefaultMutableTreeNode ctn
= addChild(ct
, project_node
);
420 ProjectThing pt_is
= ct
.createChild(imported_labels
);
421 DefaultMutableTreeNode node_pt_is
= addChild(pt_is
, ctn
);
423 // fails when importing labels from Amira TODO
424 this.scrollPathToVisible(new TreePath(node_pt_is
.getPath()));
425 } catch (Exception e
) {
429 // now, insert a new ProjectThing if of type AreaList, Ball and/or Pipe under node_child
430 for (Iterator it
= al
.iterator(); it
.hasNext(); ) {
431 Object ob
= it
.next();
432 TemplateThing tt
= null;
433 if (ob
instanceof AreaList
) {
434 tt
= getOrCreateChildTemplateThing(tt_is
, "area_list");
435 } else if (ob
instanceof Pipe
) {
436 tt
= getOrCreateChildTemplateThing(tt_is
, "pipe");
437 } else if (ob
instanceof Polyline
) {
438 tt
= getOrCreateChildTemplateThing(tt_is
, "polyline");
439 } else if (ob
instanceof Ball
) {
440 tt
= getOrCreateChildTemplateThing(tt_is
, "ball");
442 Utils
.log("insertSegmentations: ignoring " + ob
);
445 //Utils.log2("tt is " + tt);
447 ProjectThing one
= new ProjectThing(tt
, project
, ob
);
449 addChild(one
, node_pt_is
);
450 //Utils.log2("one parent : " + one.getParent());
451 } catch (Exception e
) {
455 DNDTree
.expandNode(this, DNDTree
.findNode(pt_is
, this));
458 private final TemplateThing
getOrCreateChildTemplateThing(TemplateThing parent
, String type
) {
459 TemplateThing tt
= parent
.getChildTemplate(type
);
461 tt
= new TemplateThing(type
, parent
.getProject());
462 tt
.getProject().addUniqueType(tt
);
468 public void keyPressed(final KeyEvent ke
) {
469 super.keyPressed(ke
);
470 if (ke
.isConsumed()) return;
471 super.dispatcher
.execSwing(new Runnable() { public void run() {
472 if (!ke
.getSource().equals(ProjectTree
.this) || !Project
.getInstance(ProjectTree
.this).isInputEnabled()) {
475 // get the first selected node only
476 TreePath path
= getSelectionPath();
477 if (null == path
) return;
478 DefaultMutableTreeNode node
= (DefaultMutableTreeNode
)path
.getLastPathComponent();
479 if (null == node
) return;
480 final ProjectThing pt
= (ProjectThing
)node
.getUserObject();
481 if (null == pt
) return;
483 int key_code
= ke
.getKeyCode();
485 case KeyEvent
.VK_PAGE_UP
:
487 ke
.consume(); // in any case
489 case KeyEvent
.VK_PAGE_DOWN
:
491 ke
.consume(); // in any case
502 /** Move up (-1) or down (1). */
503 private void move(final DefaultMutableTreeNode node
, final int direction
) {
504 final ProjectThing pt
= (ProjectThing
)node
.getUserObject();
505 if (null == pt
) return;
506 // Move: within the list of children of the parent ProjectThing:
507 if (!pt
.move(direction
)) return;
508 // If moved, reposition within the list of children
509 final DefaultMutableTreeNode parent_node
= (DefaultMutableTreeNode
)node
.getParent();
510 int index
= parent_node
.getChildCount() -1;
511 ((DefaultTreeModel
)this.getModel()).removeNodeFromParent(node
);
512 index
= pt
.getParent().getChildren().indexOf(pt
);
513 ((DefaultTreeModel
)this.getModel()).insertNodeInto(node
, parent_node
, index
);
514 // restore selection path
515 final TreePath trp
= new TreePath(node
.getPath());
516 this.scrollPathToVisible(trp
);
517 this.setSelectionPath(trp
);
518 this.updateUILater();
521 /** If the given node is null, it will be searched for. */
522 public boolean remove(boolean check
, ProjectThing thing
, DefaultMutableTreeNode node
) {
523 Object obd
= thing
.getObject();
524 if (obd
instanceof Project
) return ((Project
)obd
).remove(check
); // shortcut to remove everything regardless.
525 boolean b
= thing
.remove(true) && removeNode(null != node ? node
: findNode(thing
, this));
526 // This is a patch: removal from buckets is subtly broken
527 // thing.getProject().getRootLayerSet().recreateBuckets(true);
528 // The true problem is that the offscreen repaint thread sets the DisplayCanvas.al_top list before, not at the end of removing all.
529 // --> actually no: querying the LayerSet buckets for ZDisplayables still returns them, but al_zdispl doesn't have them.
530 thing
.getProject().getRootLayerSet().recreateBuckets(true);
535 public void showInfo(ProjectThing thing
) {
536 if (null == thing
) return;
537 HashMap
<String
,ArrayList
<ProjectThing
>> ht
= thing
.getByType();
538 ArrayList
<String
> types
= new ArrayList
<String
>();
539 types
.addAll(ht
.keySet());
540 Collections
.sort(types
);
541 StringBuffer sb
= new StringBuffer(thing
.getNodeInfo());
542 sb
.append("\nCounts:\n");
543 for (String type
: types
) {
544 sb
.append(type
).append(": ").append(ht
.get(type
).size()).append('\n');
547 sb
.append(thing
.getInfo().replaceAll("\t", " ")); // TextWindow can't handle tabs
548 new ij
.text
.TextWindow("Info", sb
.toString(), 500, 500);
551 public void selectInDisplay(final ProjectThing thing
, final boolean shift_down
) {
552 Object obd
= thing
.getObject();
553 if (obd
instanceof Displayable
) {
554 Displayable d
= (Displayable
)obd
;
555 if (!d
.isVisible()) d
.setVisible(true);
556 Display display
= Display
.getFront(d
.getProject());
557 if (null == display
) return;
558 display
.select(d
, shift_down
);
560 // select all basic types under this leaf
561 HashSet hs
= thing
.findBasicTypeChildren();
562 boolean first
= true;
563 Display display
= null;
564 for (Iterator it
= hs
.iterator(); it
.hasNext(); ) {
565 Object ptob
= ((ProjectThing
)it
.next()).getObject();
566 if (!(ptob
instanceof Displayable
)) {
567 Utils
.log2("Skipping non-Displayable object " + ptob
);
570 Displayable d
= (Displayable
)ptob
;
571 if (null == display
) {
572 display
= Display
.getFront(d
.getProject());
573 if (null == display
) return;
575 if (!d
.isVisible()) d
.setVisible(true);
577 display
.select(d
, shift_down
);
580 display
.select(d
, true);
586 /** Finds the node for the elder and adds the sibling next to it, under the same parent. */
587 public DefaultMutableTreeNode
addSibling(final Displayable elder
, final Displayable sibling
) {
588 if (null == elder
|| null == sibling
) return null;
589 if (elder
.getProject() != sibling
.getProject()) {
590 Utils
.log2("Can't mix projects!");
593 DefaultMutableTreeNode enode
= DNDTree
.findNode2(elder
, this);
595 Utils
.log2("Could not find a tree node for elder " + elder
);
598 ProjectThing parent
= (ProjectThing
)((ProjectThing
)enode
.getUserObject()).getParent();
599 if (null == parent
) {
600 Utils
.log2("No parent for elder " + elder
);
603 TemplateThing tt
= elder
.getProject().getTemplateThing(Project
.getType(sibling
.getClass()));
605 Utils
.log2("Could not find a template for class " + sibling
.getClass());
610 pt
= new ProjectThing(tt
, sibling
.getProject(), sibling
);
611 } catch (Exception e
) {
617 DefaultMutableTreeNode node
= new DefaultMutableTreeNode(pt
);
618 int index
= enode
.getParent().getIndex(enode
);
619 ((DefaultTreeModel
)getModel()).insertNodeInto(node
, (DefaultMutableTreeNode
)enode
.getParent(), index
+ 1);