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.
27 import ij
.gui
.GenericDialog
;
28 import ij
.gui
.YesNoCancelDialog
;
29 import ini
.trakem2
.display
.YesNoDialog
;
30 import ini
.trakem2
.display
.Display3D
;
31 import ini
.trakem2
.display
.Display
;
32 import ini
.trakem2
.tree
.LayerTree
;
33 import ini
.trakem2
.tree
.ProjectTree
;
34 import ini
.trakem2
.tree
.TemplateTree
;
35 import ini
.trakem2
.utils
.ProjectToolbar
;
36 import ini
.trakem2
.utils
.Utils
;
37 import ini
.trakem2
.utils
.IJError
;
38 import ini
.trakem2
.utils
.StdOutWindow
;
39 import ini
.trakem2
.vector
.Compare
;
40 import ini
.trakem2
.persistence
.Loader
;
44 import java
.awt
.image
.BufferedImage
;
45 import java
.awt
.image
.ImageProducer
;
46 import java
.lang
.reflect
.Field
;
48 import java
.util
.Enumeration
;
49 import java
.util
.Hashtable
;
50 import java
.util
.Iterator
;
53 import java
.awt
.event
.*;
56 /** Static class that shows one project per tab in a JFrame.
57 * Creates itself when a project requests to be have its trees displayed.
58 * Destroys itself when there are no more projects to show.
61 public class ControlWindow
{
63 static private JFrame frame
= null;
64 static private JTabbedPane tabs
= null;
65 /** Project instances are keys, JSplitPane are the objects. */
66 static private Hashtable ht_projects
= null;
67 static private ControlWindow instance
= null;
69 static private boolean gui_enabled
= true;
71 private ControlWindow() {
72 if (null != ij
.gui
.Toolbar
.getInstance()) {
73 ij
.gui
.Toolbar
.getInstance().addMouseListener(tool_listener
);
77 Loader
.setupPreloader(this);
78 if (IJ
.isWindows() && isGUIEnabled()) StdOutWindow
.start();
80 if ("albert".equals(System
.getProperty("user.name"))) {
82 UIManager
.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
83 if (null != frame
) SwingUtilities
.updateComponentTreeUI(frame
);
84 Display
.updateComponentTreeUI();
85 } catch (ClassNotFoundException cnfe
) {
86 Utils
.log2("Could not find Nimbus L&F");
87 } catch (Exception e
) {
94 /** Prevents ControlWindow from displaying projects.*/
95 static public void setGUIEnabled(boolean b
) {
97 if (gui_enabled
&& null != frame
) frame
.setVisible(true);
100 static public final boolean isGUIEnabled() {
104 /** Returns null if there are no projects */
105 synchronized static public Set
getProjects() {
106 if (null == ht_projects
) return null;
107 return ht_projects
.keySet();
110 static private MouseListener tool_listener
= new MouseAdapter() {
111 private int last_tool
= ij
.gui
.Toolbar
.RECTANGLE
;
112 public void mousePressed(MouseEvent me
) {
113 int tool
= ini
.trakem2
.utils
.ProjectToolbar
.getToolId();
114 if (tool
!= last_tool
) {
116 ini
.trakem2
.display
.Display
.toolChanged(tool
);
121 static private void destroy() {
122 if (null == instance
) return;
123 synchronized(instance
) {
124 if (IJ
.isWindows()) StdOutWindow
.quit();
127 if (null != ht_projects
) {
128 // destroy open projects, release memory
129 Enumeration e
= ht_projects
.keys();
130 Project
[] project
= new Project
[ht_projects
.size()]; //concurrent modifications ..
132 while (e
.hasMoreElements()) {
133 project
[next
++] = (Project
)e
.nextElement();
135 for (int i
=0; i
<next
; i
++) {
136 ht_projects
.remove(project
[i
]);
137 if (!project
[i
].destroy()) {
144 tabs
.removeMouseListener((tabs
.getMouseListeners())[0]);
148 frame
.setVisible(false);
151 ProjectToolbar
.destroy();
152 if (null != ij
.gui
.Toolbar
.getInstance()) ij
.gui
.Toolbar
.getInstance().repaint();
153 //ij.WindowManager.removeWindow(frame);
155 if (null != tool_listener
&& null != ij
.gui
.Toolbar
.getInstance()) {
156 ij
.gui
.Toolbar
.getInstance().removeMouseListener(tool_listener
);
158 Utils
.destroy(instance
);
159 Loader
.destroyPreloader(instance
);
164 static private boolean hooked
= false;
166 /** Beware that this method is asynchronous, as it delegates the launching to the SwingUtilities.invokeLater method to avoid havoc with Swing components. */
167 static public void add(final Project project
, final TemplateTree template_tree
, final ProjectTree thing_tree
, final LayerTree layer_tree
) {
169 final Runnable
[] other
= new Runnable
[2];
171 final Runnable gui_thread
= new Runnable() {
175 if (null == instance
) {
176 instance
= new ControlWindow();
179 synchronized (instance
) {
182 Runtime
.getRuntime().addShutdownHook(new Thread() { // necessary to disconnect properly from the database instead of with an EOF, and also to ask to save changes for FSLoader projects.
184 // threaded quit???// if (null != IJ.getInstance() && !IJ.getInstance().quitting()) IJ.getInstance().quit(); // to ensure the Project offers a YesNoDialog, not a YesNoCancelDialog
185 ControlWindow
.destroy();
190 frame
= createJFrame("TrakEM2");
191 frame
.setBackground(Color
.white
);
192 frame
.getContentPane().setBackground(Color
.white
);
193 frame
.setDefaultCloseOperation(JFrame
.DO_NOTHING_ON_CLOSE
);
194 frame
.addWindowListener(new WindowAdapter() {
195 public void windowClosing(WindowEvent we
) {
196 synchronized (instance
) {
197 if (!Utils
.check("Close " + (1 == ht_projects
.size() ?
"the project?" : "all projects?"))) {
203 public void windowClosed(WindowEvent we
) {
204 // ImageJ is quitting (never detected, so I added the dispose extension above)
208 tabs
= new JTabbedPane(JTabbedPane
.TOP
);
209 tabs
.setBackground(Color
.white
);
210 tabs
.setMinimumSize(new Dimension(500, 400));
211 tabs
.addMouseListener(new TabListener());
212 frame
.getContentPane().add(tabs
);
213 // register with ij.WindowManager so that when ImageJ quits it can be detected
214 // ADDS annoying dialog "Are you sure you want to close ImageJ?"//ij.WindowManager.addWindow(frame);
215 // Make the JPopupMenu instances be heavy weight components by default in Windows and elsewhere, not macosx.
216 if (!ij
.IJ
.isMacOSX()) JPopupMenu
.setDefaultLightWeightPopupEnabled(false);
217 // make the tool tip text for JLabel be heavy weight so they don't hide under the AWT DisplayCanvas
218 javax
.swing
.ToolTipManager
.sharedInstance().setLightWeightPopupEnabled(false);
222 final JSplitPane tab
= new JSplitPane(JSplitPane
.HORIZONTAL_SPLIT
);
223 tab
.setBackground(Color
.white
);
224 // store the tab linked to the project (before setting the trees, so that they won't get repainted and get in trouble not being able to get a project title if the project has no name)
225 if (null == ht_projects
) ht_projects
= new Hashtable();
226 ht_projects
.put(project
, tab
);
228 // create a scrolling pane for the template_tree
229 final JScrollPane scroll_template
= new JScrollPane(template_tree
);
230 scroll_template
.setBackground(Color
.white
);
231 scroll_template
.setBorder(BorderFactory
.createTitledBorder(BorderFactory
.createEmptyBorder(0,5,0,5), "Template"));
232 scroll_template
.setMinimumSize(new Dimension(0, 100));
233 scroll_template
.setPreferredSize(new Dimension(300, 400));
235 // create a scrolling pane for the thing_tree
236 final JScrollPane scroll_things
= new JScrollPane(thing_tree
);
237 scroll_things
.setBackground(Color
.white
);
238 scroll_things
.setBorder(BorderFactory
.createTitledBorder(BorderFactory
.createEmptyBorder(0,5,0,5), "Project Objects"));
239 scroll_things
.setMinimumSize(new Dimension(0, 100));
240 scroll_things
.setPreferredSize(new Dimension(300, 400));
242 // create a scrolling pane for the layer_tree
243 final JScrollPane scroll_layers
= new JScrollPane(layer_tree
);
244 scroll_layers
.setBackground(Color
.white
);
245 scroll_layers
.setBorder(BorderFactory
.createTitledBorder(BorderFactory
.createEmptyBorder(0,5,0,5), "Layers"));
246 scroll_layers
.setMinimumSize(new Dimension(0, 100));
247 scroll_layers
.setPreferredSize(new Dimension(300, 400));
249 // make a new tab for the project
250 final JSplitPane left
= new JSplitPane(JSplitPane
.HORIZONTAL_SPLIT
, scroll_template
, scroll_things
);
251 left
.setBackground(Color
.white
);
252 left
.setPreferredSize(new Dimension(600, 400));
255 tab
.setBackground(Color
.white
);
256 tab
.setLeftComponent(left
);
257 tab
.setRightComponent(scroll_layers
);
258 tab
.setPreferredSize(new Dimension(900, 400));
260 // add the tab, titled with the project title
261 tabs
.addTab(project
.toString(), new CloseIcon(), tab
);
262 tabs
.setSelectedIndex(tabs
.getTabCount() -1);
264 // the frame is created ANYWAY, it is just not made visible if !gui_enabled
265 if (!frame
.isVisible() && gui_enabled
) {
267 frame
.setVisible(true);
270 Rectangle bounds
= frame
.getBounds();
271 if (bounds
.width
< 200) {
272 frame
.setSize(new Dimension(200, bounds
.height
> 100 ? bounds
.height
: 100));
275 // now set minimum size again, after showing it (stupid Java), so they are shown correctly (opened) but can be completely collapsed to the sides.
276 try { Thread
.sleep(100); } catch (Exception e
) {}
277 //scroll_template.setMinimumSize(new Dimension(0, 100));
278 //scroll_things.setMinimumSize(new Dimension(0, 100));
279 //scroll_layers.setMinimumSize(new Dimension(0, 100));
280 tab
.setDividerLocation(0.66D
); // first, so that left is visible! setDividerLocation depends on the dimensions as they are when painted on the screen
281 left
.setDividerLocation(0.5D
);
283 // select the SELECT tool if it's the first open project
284 if (1 == ht_projects
.size() && gui_enabled
) {
285 ProjectToolbar
.setTool(ProjectToolbar
.SELECT
);
288 // so wait until the setDividerLocation of the 'tab' has finished, then do the left one
289 other
[0] = new Runnable() {
291 tab
.setDividerLocation(0.66D
);
294 other
[1] = new Runnable() {
296 left
.setDividerLocation(0.5D
);
299 // FINALLY! WHAT DEGREE OF IDIOCY POSSESSED SWING DEVELOPERS?
304 // I hate java: can't call invokeLater from the EventDispatch thread
308 SwingUtilities
.invokeAndWait(gui_thread
);
309 for (int i
=0; i
<other
.length
; i
++) {
310 SwingUtilities
.invokeAndWait(other
[i
]);
312 //Utils.log2("done");
313 } catch (Exception e
) { IJError
.print(e
); }
318 synchronized static public Project
getActive() {
319 if (null == tabs
|| 0 == ht_projects
.size()) return null;
320 if (1 == ht_projects
.size()) return (Project
)ht_projects
.keySet().iterator().next();
322 Component c
= tabs
.getSelectedComponent();
323 for (Iterator it
= ht_projects
.entrySet().iterator(); it
.hasNext(); ) {
324 Map
.Entry entry
= (Map
.Entry
)it
.next();
325 if (entry
.getValue().equals(c
)) return (Project
)entry
.getKey();
331 static public void remove(final Project project
) {
332 if (null == tabs
|| null == ht_projects
) return;
333 if (null == instance
) return;
334 if (ht_projects
.containsKey(project
)) {
336 synchronized (instance
) {
337 JSplitPane tab
= (JSplitPane
)ht_projects
.get(project
);
339 ht_projects
.remove(project
);
340 n_tabs
= tabs
.getTabCount();
342 // close the ControlWindow if no projects remain open.
349 static public void updateTitle(final Project project
) {
350 if (null == tabs
) return;
351 if (ht_projects
.containsKey(project
)) {
352 SwingUtilities
.invokeLater(new Runnable() {
354 if (null == instance
) return;
355 synchronized (instance
) {
356 JSplitPane tab
= (JSplitPane
)ht_projects
.get(project
);
357 int index
= tabs
.indexOfComponent(tab
);
359 tabs
.setTitleAt(index
, project
.toString());
367 private static class TabListener
extends MouseAdapter
{
368 public void mouseReleased(MouseEvent me
) {
369 if (me
.isConsumed()) return;
371 Component comp
= null;
372 synchronized (instance
) {
373 int i_tab
= tabs
.getSelectedIndex();
374 comp
= tabs
.getComponentAt(i_tab
);
375 icon
= tabs
.getIconAt(i_tab
);
377 if (icon
instanceof CloseIcon
) {
378 CloseIcon ci
= (CloseIcon
)icon
;
380 Project project
= null;
381 synchronized (instance
) {
382 Enumeration e
= ht_projects
.keys();
383 while (e
.hasMoreElements()) {
384 project
= (Project
)e
.nextElement();
385 if (comp
.equals(ht_projects
.get(project
))) {
390 if (ci
.contains(me
.getX(), me
.getY())) {
391 if (null == project
) return;
392 // ask for confirmation before closing
393 if (!Utils
.check("Close the project " + project
.toString() + " ?")) {
397 if (project
.destroy()) { // will call ControlWindow.remove(project)
400 } else if (2 == me
.getClickCount()) {
401 // pop dialog to rename the project
402 if (null == project
) return;
403 project
.getProjectTree().rename(project
.getRootProjectThing());
409 static private class CloseIcon
implements Icon
{
412 private BufferedImage img
;
417 img
= frame
.getGraphicsConfiguration().createCompatibleImage(20, 16, Transparency
.TRANSLUCENT
);
418 Graphics2D g
= img
.createGraphics();
419 g
.setColor(Color
.black
);
420 g
.setRenderingHint(RenderingHints
.KEY_INTERPOLATION
, RenderingHints
.VALUE_INTERPOLATION_BICUBIC
);
421 g
.setRenderingHint(RenderingHints
.KEY_ANTIALIASING
, RenderingHints
.VALUE_ANTIALIAS_ON
);
422 g
.drawOval(4 + 2, 2, 12, 12);
423 g
.setStroke(new BasicStroke(1.0f
, BasicStroke
.CAP_ROUND
, BasicStroke
.JOIN_ROUND
));
424 g
.drawLine(4 + 4, 4, 4 + 11, 12);
425 g
.drawLine(4 + 4, 12, 4 + 11, 4);
426 icon
= new ImageIcon(img
);
429 public void paintIcon(Component c
, Graphics g
, int x
, int y
) {
430 // store coordinates of the last painting event
433 icon
.paintIcon(c
, g
, x
, y
);
436 public boolean contains(int x
, int y
) {
437 return new Rectangle(this.x
, this.y
, icon
.getIconWidth(), icon
.getIconHeight()).contains(x
, y
);
440 public int getIconWidth() { return icon
.getIconWidth(); }
441 public int getIconHeight() { return icon
.getIconHeight(); }
443 public void flush() {
451 /** For the generic dialogs to be parented properly. */
452 static public GenericDialog
makeGenericDialog(String title
) {
453 Frame f
= (null == frame ? IJ
.getInstance() : (java
.awt
.Frame
)frame
);
454 return new GenericDialog(title
, f
);
457 /** For the YesNoCancelDialog dialogs to be parented properly. */
458 static public YesNoCancelDialog
makeYesNoCancelDialog(String title
, String msg
) {
459 Frame f
= (null == frame ? IJ
.getInstance() : (java
.awt
.Frame
)frame
);
460 return new YesNoCancelDialog(f
, title
, msg
);
462 /** For the YesNoDialog dialogs to be parented properly. */
463 static public YesNoDialog
makeYesNoDialog(String title
, String msg
) {
464 Frame f
= (null == frame ? IJ
.getInstance() : (java
.awt
.Frame
)frame
);
465 return new YesNoDialog(f
, title
, msg
);
468 static public void toFront() {
469 synchronized (instance
) {
470 if (null != frame
) frame
.toFront();
474 /** Appends to the buffer data relative to the viewport of the given tree. */
476 static public void exportTreesXML(final Project project, final StringBuffer sb_data, final String indent, final JTree tree) {
477 // find the JSplitPane of the given tree
478 JScrollPane[] jsp = new JScrollPane[1];
480 findJSP((Container)ht_projects.get(project), tree, jsp);
481 if (null == jsp[0]) {
482 Utils.log2("Cound not find a JScrollPane for the tree.");
486 tree.exportXML(sb_data, indent, jsp[0]);
492 static private void findJSP(final Container parent, final JTree tree, final JScrollPane[] jsp) {
493 if (null != jsp[0]) return;
494 Component[] comps = parent.getComponents();
495 for (int i=0; i<comps.length; i++) {
496 if (comps[i] instanceof Container) {
497 findJSP(comps[i], tree, jsp);
498 } else if (comps[i].equals(tree)) {
499 jsp[0] = (JScrollPane)parent; // MUST be
506 static public void startWaitingCursor() { setCursor(Cursor
.getPredefinedCursor(Cursor
.WAIT_CURSOR
)); }
508 static public void endWaitingCursor() { setCursor(Cursor
.getDefaultCursor()); }
510 static private void setCursor(final Cursor c
) {
511 if (null != IJ
.getInstance()) IJ
.getInstance().setCursor(c
);
512 ini
.trakem2
.display
.Display
.setCursorToAll(c
);
513 if (null != frame
&& frame
.isVisible()) frame
.setCursor(c
); // the ControlWindow frame
516 /** Returns -1 if not found. */
517 synchronized static public int getTabIndex(final Project project
) {
518 if (null == project
|| null == ht_projects
) return -1;
519 Component tab
= (Component
)ht_projects
.get(project
);
520 if (null == tab
) return -1;
521 return tabs
.indexOfComponent(tab
);
524 static private Image icon
= null;
526 /** Returns a new JFrame with the proper icon from ImageJ.iconPath set, if any. */
527 static public JFrame
createJFrame(final String title
) {
528 if (null == instance
) return new JFrame(title
);
529 return instance
.newJFrame(title
);
531 synchronized private JFrame
newJFrame(final String title
) {
532 final JFrame frame
= new JFrame(title
);
536 Field mic
= ImageJ
.class.getDeclaredField("iconPath");
537 mic
.setAccessible(true);
538 String path
= (String
) mic
.get(IJ
.getInstance());
539 icon
= IJ
.getInstance().createImage((ImageProducer
) new URL("file:" + path
).getContent());
540 } catch (Exception e
) {}
543 if (null != icon
) frame
.setIconImage(icon
);