Multi-layer mosaicking with SIFT using the proper new mpicbg_.jar is almost
[trakem2.git] / ini / trakem2 / display / Display.java
blob568dee9b94e48b05b84c66d6e14b48ef132cd861
1 /**
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.
21 **/
23 package ini.trakem2.display;
25 import ij.*;
26 import ij.gui.*;
27 import ij.measure.Calibration;
28 import ini.trakem2.Project;
29 import ini.trakem2.ControlWindow;
30 import ini.trakem2.persistence.DBObject;
31 import ini.trakem2.persistence.Loader;
32 import ini.trakem2.utils.IJError;
33 import ini.trakem2.imaging.PatchStack;
34 import ini.trakem2.imaging.Registration;
35 import ini.trakem2.imaging.StitchingTEM;
36 import ini.trakem2.utils.ProjectToolbar;
37 import ini.trakem2.utils.Utils;
38 import ini.trakem2.utils.DNDInsertImage;
39 import ini.trakem2.utils.Search;
40 import ini.trakem2.utils.Bureaucrat;
41 import ini.trakem2.utils.Worker;
42 import ini.trakem2.utils.Dispatcher;
43 import ini.trakem2.utils.Lock;
44 import ini.trakem2.tree.*;
46 import javax.swing.*;
47 import javax.swing.event.*;
49 import mpicbg.trakem2.align.AlignTask;
51 import java.awt.*;
52 import java.awt.event.*;
53 import java.util.*;
54 import java.lang.reflect.Method;
55 import java.io.Writer;
57 import lenscorrection.DistortionCorrectionTask;
59 /** A Display is a class to show a Layer and enable mouse and keyboard manipulation of all its components. */
60 public final class Display extends DBObject implements ActionListener, ImageListener {
62 /** The Layer this Display is showing. */
63 private Layer layer;
65 private Displayable active = null;
66 /** All selected Displayable objects, including the active one. */
67 final private Selection selection = new Selection(this);
69 private ImagePlus last_temp = null;
71 private JFrame frame;
72 private JTabbedPane tabs;
73 private Hashtable<Class,JScrollPane> ht_tabs;
74 private JScrollPane scroll_patches;
75 private JPanel panel_patches;
76 private JScrollPane scroll_profiles;
77 private JPanel panel_profiles;
78 private JScrollPane scroll_zdispl;
79 private JPanel panel_zdispl;
80 private JScrollPane scroll_channels;
81 private JPanel panel_channels;
82 private JScrollPane scroll_labels;
83 private JPanel panel_labels;
85 private JSlider transp_slider;
86 private DisplayNavigator navigator;
87 private JScrollBar scroller;
89 private DisplayCanvas canvas; // WARNING this is an AWT component, since it extends ImageCanvas
90 private JPanel canvas_panel; // and this is a workaround, to better (perhaps) integrate the awt canvas inside a JSplitPane
91 private JSplitPane split;
93 private JPopupMenu popup = null;
95 /** Contains the packed alphas of every channel. */
96 private int c_alphas = 0xffffffff; // all 100 % visible
97 private Channel[] channels;
99 private Hashtable<Displayable,DisplayablePanel> ht_panels = new Hashtable<Displayable,DisplayablePanel>();
101 /** Handle drop events, to insert image files. */
102 private DNDInsertImage dnd;
104 private boolean size_adjusted = false;
106 private int scroll_step = 1;
108 /** Keep track of all existing Display objects. */
109 static private ArrayList<Display> al_displays = new ArrayList<Display>();
110 /** The currently focused Display, if any. */
111 static private Display front = null;
113 /** Displays to open when all objects have been reloaded from the database. */
114 static private final Hashtable ht_later = new Hashtable();
116 /** A thread to handle user actions, for example an event sent from a popup menu. */
117 private final Dispatcher dispatcher = new Dispatcher();
119 static private WindowAdapter window_listener = new WindowAdapter() {
120 /** Unregister the closed Display. */
121 public void windowClosing(WindowEvent we) {
122 final Object source = we.getSource();
123 for (Iterator it = al_displays.iterator(); it.hasNext(); ) {
124 Display d = (Display)it.next();
125 if (source == d.frame) {
126 it.remove();
127 if (d == front) front = null;
128 d.remove(false); //calls destroy
129 break;
133 /** Set the source Display as front. */
134 public void windowActivated(WindowEvent we) {
135 // find which was it to make it be the front
136 final Object source = we.getSource();
137 for (final Display d : al_displays) {
138 if (source == d.frame) {
139 front = d;
140 // set toolbar
141 ProjectToolbar.setProjectToolbar();
142 // now, select the layer in the LayerTree
143 front.getProject().select(front.layer);
144 // finally, set the virtual ImagePlus that ImageJ will see
145 d.setTempCurrentImage();
146 // copied from ij.gui.ImageWindow, with modifications
147 if (IJ.isMacintosh() && IJ.getInstance()!=null) {
148 IJ.wait(10); // may be needed for Java 1.4 on OS X
149 d.frame.setMenuBar(ij.Menus.getMenuBar());
151 return;
154 // else, restore the ImageJ toolbar for non-project images
155 //if (!source.equals(IJ.getInstance())) {
156 // ProjectToolbar.setImageJToolbar();
159 /** Restore the ImageJ toolbar */
160 public void windowDeactivated(WindowEvent we) {
161 // Can't, the user can never click the ProjectToolbar then. This has to be done in a different way, for example checking who is the WindowManager.getCurrentImage (and maybe setting a dummy image into it) //ProjectToolbar.setImageJToolbar();
163 /** Call a pack() when the window is maximized to fit the canvas correctly. */
164 public void windowStateChanged(WindowEvent we) {
165 final Object source = we.getSource();
166 for (final Display d : al_displays) {
167 if (source != d.frame) continue;
168 d.pack();
169 break;
174 static private MouseListener frame_mouse_listener = new MouseAdapter() {
175 public void mouseReleased(MouseEvent me) {
176 Object source = me.getSource();
177 for (final Display d : al_displays) {
178 if (d.frame == source) {
179 if (d.size_adjusted) {
180 d.pack();
181 d.size_adjusted = false;
182 Utils.log2("mouse released on JFrame");
184 break;
190 private int last_frame_state = frame.NORMAL;
192 // THIS WHOLE SYSTEM OF LISTENERS IS BROKEN:
193 // * when zooming in, the window growths in width a few pixels.
194 // * when enlarging the window quickly, the canvas is not resized as large as it should.
195 // -- the whole problem: swing threading, which I am not handling properly. It's hard.
196 static private ComponentListener component_listener = new ComponentAdapter() {
197 public void componentResized(ComponentEvent ce) {
198 final Display d = getDisplaySource(ce);
199 if (null != d) {
200 d.size_adjusted = true; // works in combination with mouseReleased to call pack(), avoiding infinite loops.
201 d.adjustCanvas();
202 int frame_state = d.frame.getExtendedState();
203 if (frame_state != d.last_frame_state) { // this setup avoids infinite loops (for pack() calls componentResized as well
204 d.last_frame_state = frame_state;
205 if (d.frame.ICONIFIED != frame_state) d.pack();
209 public void componentMoved(ComponentEvent ce) {
210 Display d = getDisplaySource(ce);
211 if (null != d) d.updateInDatabase("position");
213 private Display getDisplaySource(ComponentEvent ce) {
214 final Object source = ce.getSource();
215 for (final Display d : al_displays) {
216 if (source == d.frame) {
217 return d;
220 return null;
224 static private ChangeListener tabs_listener = new ChangeListener() {
225 /** Listen to tab changes. */
226 public void stateChanged(final ChangeEvent ce) {
227 final Object source = ce.getSource();
228 for (final Display d : al_displays) {
229 if (source == d.tabs) {
230 d.dispatcher.exec(new Runnable() { public void run() {
231 // creating tabs fires the event!!!
232 if (null == d.frame || null == d.canvas) return;
233 final Container tab = (Container)d.tabs.getSelectedComponent();
234 if (tab == d.scroll_channels) {
235 // find active channel if any
236 for (int i=0; i<d.channels.length; i++) {
237 if (d.channels[i].isActive()) {
238 d.transp_slider.setValue((int)(d.channels[i].getAlpha() * 100));
239 break;
242 } else {
243 // recreate contents
245 int count = tab.getComponentCount();
246 if (0 == count || (1 == count && tab.getComponent(0).getClass().equals(JLabel.class))) {
247 */ // ALWAYS, because it could be the case that the user changes layer while on one specific tab, and then clicks on the other tab which may not be empty and shows totally the wrong contents (i.e. for another layer)
249 String label = null;
250 ArrayList al = null;
251 JPanel p = null;
252 if (tab == d.scroll_zdispl) {
253 label = "Z-space objects";
254 al = d.layer.getParent().getZDisplayables();
255 p = d.panel_zdispl;
256 } else if (tab == d.scroll_patches) {
257 label = "Patches";
258 al = d.layer.getDisplayables(Patch.class);
259 p = d.panel_patches;
260 } else if (tab == d.scroll_labels) {
261 label = "Labels";
262 al = d.layer.getDisplayables(DLabel.class);
263 p = d.panel_labels;
264 } else if (tab == d.scroll_profiles) {
265 label = "Profiles";
266 al = d.layer.getDisplayables(Profile.class);
267 p = d.panel_profiles;
270 d.updateTab(p, label, al);
271 //Utils.updateComponent(d.tabs.getSelectedComponent());
272 //Utils.log2("updated tab: " + p + " with " + al.size() + " objects.");
275 if (null != d.active) {
276 // set the transp slider to the alpha value of the active Displayable if any
277 d.transp_slider.setValue((int)(d.active.getAlpha() * 100));
278 DisplayablePanel dp = d.ht_panels.get(d.active);
279 if (null != dp) dp.setActive(true);
282 }});
283 break;
289 private final ScrollLayerListener scroller_listener = new ScrollLayerListener();
291 private class ScrollLayerListener implements AdjustmentListener {
293 public void adjustmentValueChanged(AdjustmentEvent ae) {
294 int index = scroller.getValue();
295 slt.set(layer.getParent().getLayer(index));
296 return;
300 private final SetLayerThread slt = new SetLayerThread();
302 private class SetLayerThread extends Thread {
304 private boolean go = true;
305 private Layer layer;
306 private final Lock lock = new Lock();
307 private final Lock lock2 = new Lock();
309 SetLayerThread() {
310 setPriority(Thread.NORM_PRIORITY);
311 setDaemon(true);
312 start();
315 public final void set(final Layer layer) {
316 synchronized (lock) {
317 this.layer = layer;
319 synchronized (this) {
320 notify();
324 public final void setAndWait(final Layer layer) {
325 lock2.lock();
326 set(layer);
329 public void run() {
330 while (go) {
331 while (null == this.layer) {
332 synchronized (this) {
333 try { wait(); } catch (InterruptedException ie) {}
336 Layer layer = null;
337 synchronized (lock) {
338 layer = this.layer;
339 this.layer = null;
342 if (!go) return; // after nullifying layer
344 if (null != layer) {
345 Display.this.setLayer(layer);
346 Display.this.updateInDatabase("layer_id");
348 // unlock any calls waiting on setAndWait
349 synchronized (lock2) {
350 lock2.unlock();
353 // cleanup:
354 synchronized (lock2) {
355 lock2.unlock();
359 public void waitForLayer() {
360 while (null != layer && go) {
361 try { Thread.sleep(10); } catch (Exception e) {}
365 public void quit() {
366 go = false;
370 /** Creates a new Display with adjusted magnification to fit in the screen. */
371 static public void createDisplay(final Project project, final Layer layer) {
372 SwingUtilities.invokeLater(new Runnable() { public void run() {
373 Display display = new Display(project, layer);
374 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
375 Rectangle srcRect = new Rectangle(0, 0, (int)layer.getLayerWidth(), (int)layer.getLayerHeight());
376 double mag = screen.width / layer.getLayerWidth();
377 if (mag * layer.getLayerHeight() > screen.height) mag = screen.height / layer.getLayerHeight();
378 mag = display.canvas.getLowerZoomLevel2(mag);
379 if (mag > 1.0) mag = 1.0;
380 //display.getCanvas().setup(mag, srcRect); // would call pack() at the wrong time!
381 // ... so instead: manually
382 display.getCanvas().setMagnification(mag);
383 display.getCanvas().setSrcRect(srcRect.x, srcRect.y, srcRect.width, srcRect.height);
384 display.getCanvas().setDrawingSize((int)Math.ceil(srcRect.width * mag), (int)Math.ceil(srcRect.height * mag));
386 display.updateTitle();
387 ij.gui.GUI.center(display.frame);
388 display.frame.pack();
389 }});
392 /** A new Display from scratch, to show the given Layer. */
393 public Display(Project project, final Layer layer) {
394 super(project);
395 front = this;
396 makeGUI(layer, null);
397 ImagePlus.addImageListener(this);
398 setLayer(layer);
399 this.layer = layer; // after, or it doesn't update properly
400 al_displays.add(this);
401 addToDatabase();
404 /** For reconstruction purposes. The Display will be stored in the ht_later.*/
405 public Display(Project project, long id, Layer layer, Object[] props) {
406 super(project, id);
407 synchronized (ht_later) {
408 Display.ht_later.put(this, props);
410 this.layer = layer;
413 /** Open a new Display centered around the given Displayable. */
414 public Display(Project project, Layer layer, Displayable displ) {
415 super(project);
416 front = this;
417 active = displ;
418 makeGUI(layer, null);
419 ImagePlus.addImageListener(this);
420 setLayer(layer);
421 this.layer = layer; // after set layer!
422 al_displays.add(this);
423 addToDatabase();
426 /** Reconstruct a Display from an XML entry, to be opened when everything is ready. */
427 public Display(Project project, long id, Layer layer, HashMap ht_attributes) {
428 super(project, id);
429 if (null == layer) {
430 Utils.log2("Display: need a non-null Layer for id=" + id);
431 return;
433 Rectangle srcRect = new Rectangle(0, 0, (int)layer.getLayerWidth(), (int)layer.getLayerHeight());
434 double magnification = 0.25;
435 Point p = new Point(0, 0);
436 int c_alphas = 0xffffffff;
437 int c_alphas_state = 0xffffffff;
438 for (Iterator it = ht_attributes.entrySet().iterator(); it.hasNext(); ) {
439 Map.Entry entry = (Map.Entry)it.next();
440 String key = (String)entry.getKey();
441 String data = (String)entry.getValue();
442 if (key.equals("srcrect_x")) { // reflection! Reflection!
443 srcRect.x = Integer.parseInt(data);
444 } else if (key.equals("srcrect_y")) {
445 srcRect.y = Integer.parseInt(data);
446 } else if (key.equals("srcrect_width")) {
447 srcRect.width = Integer.parseInt(data);
448 } else if (key.equals("srcrect_height")) {
449 srcRect.height = Integer.parseInt(data);
450 } else if (key.equals("magnification")) {
451 magnification = Double.parseDouble(data);
452 } else if (key.equals("x")) {
453 p.x = Integer.parseInt(data);
454 } else if (key.equals("y")) {
455 p.y = Integer.parseInt(data);
456 } else if (key.equals("c_alphas")) {
457 try {
458 c_alphas = Integer.parseInt(data);
459 } catch (Exception ex) {
460 c_alphas = 0xffffffff;
462 } else if (key.equals("c_alphas_state")) {
463 try {
464 c_alphas_state = Integer.parseInt(data);
465 } catch (Exception ex) {
466 IJError.print(ex);
467 c_alphas_state = 0xffffffff;
469 } else if (key.equals("scroll_step")) {
470 try {
471 setScrollStep(Integer.parseInt(data));
472 } catch (Exception ex) {
473 IJError.print(ex);
474 setScrollStep(1);
477 // TODO the above is insecure, in that data is not fully checked to be within bounds.
479 Object[] props = new Object[]{p, new Double(magnification), srcRect, new Long(layer.getId()), new Integer(c_alphas), new Integer(c_alphas_state)};
480 synchronized (ht_later) {
481 Display.ht_later.put(this, props);
483 this.layer = layer;
486 /** After reloading a project from the database, open the Displays that the project had. */
487 static public Bureaucrat openLater() {
488 final Hashtable ht_later_local;
489 synchronized (ht_later) {
490 if (0 == ht_later.size()) return null;
491 ht_later_local = new Hashtable(ht_later);
492 ht_later.keySet().removeAll(ht_later_local.keySet());
494 final Worker worker = new Worker("Opening displays") {
495 public void run() {
496 startedWorking();
497 try {
498 Thread.sleep(300); // waiting for Swing
500 for (Enumeration e = ht_later_local.keys(); e.hasMoreElements(); ) {
501 final Display d = (Display)e.nextElement();
502 front = d; // must be set before repainting any ZDisplayable!
503 Object[] props = (Object[])ht_later_local.get(d);
504 if (ControlWindow.isGUIEnabled()) d.makeGUI(d.layer, props);
505 d.setLayerLater(d.layer, d.layer.get(((Long)props[3]).longValue())); //important to do it after makeGUI
506 if (!ControlWindow.isGUIEnabled()) continue;
507 ImagePlus.addImageListener(d);
508 al_displays.add(d);
509 d.updateTitle();
510 // force a repaint if a prePaint was done TODO this should be properly managed with repaints using always the invokeLater, but then it's DOG SLOW
511 if (d.canvas.getMagnification() > 0.499) {
512 SwingUtilities.invokeLater(new Runnable() { public void run() {
513 d.repaint(d.layer);
514 d.project.getLoader().setChanged(false);
515 Utils.log2("A set to false");
516 }});
518 d.project.getLoader().setChanged(false);
519 Utils.log2("B set to false");
521 if (null != front) front.getProject().select(front.layer);
523 } catch (Throwable t) {
524 IJError.print(t);
525 } finally {
526 finishedWorking();
530 return Bureaucrat.createAndStart(worker, ((Display)ht_later_local.keySet().iterator().next()).getProject()); // gets the project from the first Display
533 private void makeGUI(final Layer layer, final Object[] props) {
534 // gather properties
535 Point p = null;
536 double mag = 1.0D;
537 Rectangle srcRect = null;
538 if (null != props) {
539 p = (Point)props[0];
540 mag = ((Double)props[1]).doubleValue();
541 srcRect = (Rectangle)props[2];
544 // transparency slider
545 this.transp_slider = new JSlider(javax.swing.SwingConstants.HORIZONTAL, 0, 100, 100);
546 this.transp_slider.setBackground(Color.white);
547 this.transp_slider.setMinimumSize(new Dimension(250, 20));
548 this.transp_slider.setMaximumSize(new Dimension(250, 20));
549 this.transp_slider.setPreferredSize(new Dimension(250, 20));
550 TransparencySliderListener tsl = new TransparencySliderListener();
551 this.transp_slider.addChangeListener(tsl);
552 this.transp_slider.addMouseListener(tsl);
553 for (final KeyListener kl : this.transp_slider.getKeyListeners()) {
554 this.transp_slider.removeKeyListener(kl);
557 // Tabbed pane on the left
558 this.tabs = new JTabbedPane();
559 this.tabs.setMinimumSize(new Dimension(250, 300));
560 this.tabs.setBackground(Color.white);
561 this.tabs.addChangeListener(tabs_listener);
563 // Tab 1: Patches
564 this.panel_patches = new JPanel();
565 BoxLayout patches_layout = new BoxLayout(panel_patches, BoxLayout.Y_AXIS);
566 this.panel_patches.setLayout(patches_layout);
567 this.panel_patches.add(new JLabel("No patches."));
568 this.scroll_patches = makeScrollPane(panel_patches);
569 this.scroll_patches.setPreferredSize(new Dimension(250, 300));
570 this.scroll_patches.setMinimumSize(new Dimension(250, 300));
571 this.tabs.add("Patches", scroll_patches);
573 // Tab 2: Profiles
574 this.panel_profiles = new JPanel();
575 BoxLayout profiles_layout = new BoxLayout(panel_profiles, BoxLayout.Y_AXIS);
576 this.panel_profiles.setLayout(profiles_layout);
577 this.panel_profiles.add(new JLabel("No profiles."));
578 this.scroll_profiles = makeScrollPane(panel_profiles);
579 this.scroll_profiles.setPreferredSize(new Dimension(250, 300));
580 this.scroll_profiles.setMinimumSize(new Dimension(250, 300));
581 this.tabs.add("Profiles", scroll_profiles);
583 // Tab 3: pipes
584 this.panel_zdispl = new JPanel();
585 BoxLayout pipes_layout = new BoxLayout(panel_zdispl, BoxLayout.Y_AXIS);
586 this.panel_zdispl.setLayout(pipes_layout);
587 this.panel_zdispl.add(new JLabel("No objects."));
588 this.scroll_zdispl = makeScrollPane(panel_zdispl);
589 this.scroll_zdispl.setPreferredSize(new Dimension(250, 300));
590 this.scroll_zdispl.setMinimumSize(new Dimension(250, 300));
591 this.tabs.add("Z space", scroll_zdispl);
593 // Tab 4: channels
594 this.panel_channels = new JPanel();
595 BoxLayout channels_layout = new BoxLayout(panel_channels, BoxLayout.Y_AXIS);
596 this.panel_channels.setLayout(channels_layout);
597 this.scroll_channels = makeScrollPane(panel_channels);
598 this.scroll_channels.setPreferredSize(new Dimension(250, 300));
599 this.scroll_channels.setMinimumSize(new Dimension(250, 300));
600 this.channels = new Channel[4];
601 this.channels[0] = new Channel(this, Channel.MONO);
602 this.channels[1] = new Channel(this, Channel.RED);
603 this.channels[2] = new Channel(this, Channel.GREEN);
604 this.channels[3] = new Channel(this, Channel.BLUE);
605 //this.panel_channels.add(this.channels[0]);
606 this.panel_channels.add(this.channels[1]);
607 this.panel_channels.add(this.channels[2]);
608 this.panel_channels.add(this.channels[3]);
609 this.tabs.add("Opacity", scroll_channels);
611 // Tab 5: labels
612 this.panel_labels = new JPanel();
613 BoxLayout labels_layout = new BoxLayout(panel_labels, BoxLayout.Y_AXIS);
614 this.panel_labels.setLayout(labels_layout);
615 this.panel_labels.add(new JLabel("No labels."));
616 this.scroll_labels = makeScrollPane(panel_labels);
617 this.scroll_labels.setPreferredSize(new Dimension(250, 300));
618 this.scroll_labels.setMinimumSize(new Dimension(250, 300));
619 this.tabs.add("Labels", scroll_labels);
621 this.ht_tabs = new Hashtable<Class,JScrollPane>();
622 this.ht_tabs.put(Patch.class, scroll_patches);
623 this.ht_tabs.put(Profile.class, scroll_profiles);
624 this.ht_tabs.put(ZDisplayable.class, scroll_zdispl);
625 this.ht_tabs.put(AreaList.class, scroll_zdispl);
626 this.ht_tabs.put(Pipe.class, scroll_zdispl);
627 this.ht_tabs.put(Polyline.class, scroll_zdispl);
628 this.ht_tabs.put(Ball.class, scroll_zdispl);
629 this.ht_tabs.put(Dissector.class, scroll_zdispl);
630 this.ht_tabs.put(DLabel.class, scroll_labels);
631 // channels not included
633 // Navigator
634 this.navigator = new DisplayNavigator(this, layer.getLayerWidth(), layer.getLayerHeight());
635 // Layer scroller (to scroll slices)
636 int extent = (int)(250.0 / layer.getParent().size());
637 if (extent < 10) extent = 10;
638 this.scroller = new JScrollBar(JScrollBar.HORIZONTAL);
639 updateLayerScroller(layer);
640 this.scroller.addAdjustmentListener(scroller_listener);
643 // Left panel, contains the transp slider, the tabbed pane, the navigation panel and the layer scroller
644 JPanel left = new JPanel();
645 BoxLayout left_layout = new BoxLayout(left, BoxLayout.Y_AXIS);
646 left.setLayout(left_layout);
647 left.add(transp_slider);
648 left.add(tabs);
649 left.add(navigator);
650 left.add(scroller);
652 // Canvas
653 this.canvas = new DisplayCanvas(this, (int)Math.ceil(layer.getLayerWidth()), (int)Math.ceil(layer.getLayerHeight()));
654 this.canvas_panel = new JPanel();
655 GridBagLayout gb = new GridBagLayout();
656 this.canvas_panel.setLayout(gb);
657 GridBagConstraints c = new GridBagConstraints();
658 c.fill = GridBagConstraints.BOTH;
659 c.anchor = GridBagConstraints.NORTHWEST;
660 gb.setConstraints(this.canvas_panel, c);
661 gb.setConstraints(this.canvas, c);
663 // prevent new Displays from screweing up if input is globally disabled
664 if (!project.isInputEnabled()) this.canvas.setReceivesInput(false);
666 this.canvas_panel.add(canvas);
668 this.navigator.addMouseWheelListener(canvas);
670 this.transp_slider.addKeyListener(canvas);
672 // Split pane to contain everything
673 this.split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, left, canvas_panel);
674 this.split.setOneTouchExpandable(true); // NOT present in all L&F (?)
676 // fix
677 gb.setConstraints(split.getRightComponent(), c);
679 // JFrame to show the split pane
680 this.frame = ControlWindow.createJFrame(layer.toString());
681 if (IJ.isMacintosh() && IJ.getInstance()!=null) {
682 IJ.wait(10); // may be needed for Java 1.4 on OS X
683 this.frame.setMenuBar(ij.Menus.getMenuBar());
685 this.frame.addWindowListener(window_listener);
686 this.frame.addComponentListener(component_listener);
687 this.frame.getContentPane().add(split);
688 this.frame.addMouseListener(frame_mouse_listener);
689 //doesn't exist//this.frame.setMinimumSize(new Dimension(270, 600));
691 if (null != props) {
692 // restore canvas
693 canvas.setup(mag, srcRect);
694 // restore visibility of each channel
695 int cs = ((Integer)props[5]).intValue(); // aka c_alphas_state
696 int[] sel = new int[4];
697 sel[0] = ((cs&0xff000000)>>24);
698 sel[1] = ((cs&0xff0000)>>16);
699 sel[2] = ((cs&0xff00)>>8);
700 sel[3] = (cs&0xff);
701 // restore channel alphas
702 this.c_alphas = ((Integer)props[4]).intValue();
703 channels[0].setAlpha( (float)((c_alphas&0xff000000)>>24) / 255.0f , 0 != sel[0]);
704 channels[1].setAlpha( (float)((c_alphas&0xff0000)>>16) / 255.0f , 0 != sel[1]);
705 channels[2].setAlpha( (float)((c_alphas&0xff00)>>8) / 255.0f , 0 != sel[2]);
706 channels[3].setAlpha( (float) (c_alphas&0xff) / 255.0f , 0 != sel[3]);
707 // restore visibility in the working c_alphas
708 this.c_alphas = ((0 != sel[0] ? (int)(255 * channels[0].getAlpha()) : 0)<<24) + ((0 != sel[1] ? (int)(255 * channels[1].getAlpha()) : 0)<<16) + ((0 != sel[2] ? (int)(255 * channels[2].getAlpha()) : 0)<<8) + (0 != sel[3] ? (int)(255 * channels[3].getAlpha()) : 0);
711 if (null != active && null != layer) {
712 Rectangle r = active.getBoundingBox();
713 r.x -= r.width/2;
714 r.y -= r.height/2;
715 r.width += r.width;
716 r.height += r.height;
717 if (r.x < 0) r.x = 0;
718 if (r.y < 0) r.y = 0;
719 if (r.width > layer.getLayerWidth()) r.width = (int)layer.getLayerWidth();
720 if (r.height> layer.getLayerHeight())r.height= (int)layer.getLayerHeight();
721 double magn = layer.getLayerWidth() / (double)r.width;
722 canvas.setup(magn, r);
725 // add keyListener to the whole frame
726 this.tabs.addKeyListener(canvas);
727 this.canvas_panel.addKeyListener(canvas);
728 this.frame.addKeyListener(canvas);
730 this.frame.pack();
731 ij.gui.GUI.center(this.frame);
732 this.frame.setVisible(true);
733 ProjectToolbar.setProjectToolbar(); // doesn't get it through events
735 final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
737 if (null != props) {
738 // fix positioning outside the screen (dual to single monitor)
739 if (p.x >= 0 && p.x < screen.width - 50 && p.y >= 0 && p.y <= screen.height - 50) this.frame.setLocation(p);
740 else frame.setLocation(0, 0);
743 // fix excessive size
744 final Rectangle box = this.frame.getBounds();
745 int x = box.x;
746 int y = box.y;
747 int width = box.width;
748 int height = box.height;
749 if (box.width > screen.width) { x = 0; width = screen.width; }
750 if (box.height > screen.height) { y = 0; height = screen.height; }
751 if (x != box.x || y != box.y) {
752 this.frame.setLocation(x, y + (0 == y ? 30 : 0)); // added insets for bad window managers
753 updateInDatabase("position");
755 if (width != box.width || height != box.height) {
756 this.frame.setSize(new Dimension(width -10, height -30)); // added insets for bad window managers
758 if (null == props) {
759 // try to optimize canvas dimensions and magn
760 double magn = layer.getLayerHeight() / screen.height;
761 if (magn > 1.0) magn = 1.0;
762 long size = 0;
763 // limit magnification if appropriate
764 for (Iterator it = layer.getDisplayables(Patch.class).iterator(); it.hasNext(); ) {
765 final Patch pa = (Patch)it.next();
766 final Rectangle ba = pa.getBoundingBox();
767 size += (long)(ba.width * ba.height);
769 if (size > 10000000) canvas.setInitialMagnification(0.25); // 10 Mb
770 else {
771 this.frame.setSize(new Dimension((int)(screen.width * 0.66), (int)(screen.height * 0.66)));
775 Utils.updateComponent(tabs); // otherwise fails in FreeBSD java 1.4.2 when reconstructing
778 // Set the calibration of the FakeImagePlus to that of the LayerSet
779 ((FakeImagePlus)canvas.getFakeImagePlus()).setCalibrationSuper(layer.getParent().getCalibrationCopy());
781 // Set the FakeImagePlus as the current image
782 setTempCurrentImage();
784 // create a drag and drop listener
785 dnd = new DNDInsertImage(this);
787 // start a repainting thread
788 if (null != props) {
789 canvas.repaint(true); // repaint() is unreliable
792 // Set the minimum size of the tabbed pane on the left, so it can be completely collapsed now that it has been properly displayed. This is a patch to the lack of respect for the setDividerLocation method.
793 SwingUtilities.invokeLater(new Runnable() {
794 public void run() {
795 tabs.setMinimumSize(new Dimension(0, 100));
800 private JScrollPane makeScrollPane(Component c) {
801 JScrollPane jsp = new JScrollPane(c);
802 // adjust scrolling to use one DisplayablePanel as the minimal unit
803 jsp.getVerticalScrollBar().setBlockIncrement(DisplayablePanel.HEIGHT); // clicking within the track
804 jsp.getVerticalScrollBar().setUnitIncrement(DisplayablePanel.HEIGHT); // clicking on an arrow
805 return jsp;
808 public JPanel getCanvasPanel() {
809 return canvas_panel;
812 public DisplayCanvas getCanvas() {
813 return canvas;
816 public void setLayer(final Layer layer) {
817 if (null == layer || layer == this.layer) return;
818 final boolean set_zdispl = null == Display.this.layer || layer.getParent() != Display.this.layer.getParent();
819 if (selection.isTransforming()) {
820 Utils.log("Can't browse layers while transforming.\nCANCEL the transform first with the ESCAPE key or right-click -> cancel.");
821 scroller.setValue(Display.this.layer.getParent().getLayerIndex(Display.this.layer.getId()));
822 return;
824 this.layer = layer;
825 scroller.setValue(layer.getParent().getLayerIndex(layer.getId()));
827 // update the current Layer pointer in ZDisplayable objects
828 for (Iterator it = layer.getParent().getZDisplayables().iterator(); it.hasNext(); ) {
829 ((ZDisplayable)it.next()).setLayer(layer); // the active layer
832 updateVisibleTab(set_zdispl);
834 // see if a lot has to be reloaded, put the relevant ones at the end
835 project.getLoader().prepare(layer);
836 updateTitle(); // to show the new 'z'
837 // select the Layer in the LayerTree
838 project.select(Display.this.layer); // does so in a separate thread
839 // update active Displayable:
841 // deselect all except ZDisplayables
842 final ArrayList sel = selection.getSelected();
843 final Displayable last_active = Display.this.active;
844 int sel_next = -1;
845 for (Iterator it = sel.iterator(); it.hasNext(); ) {
846 Displayable d = (Displayable)it.next();
847 if (!(d instanceof ZDisplayable)) {
848 it.remove();
849 selection.remove(d);
850 if (d == last_active && sel.size() > 0) {
851 // select the last one of the remaining, if any
852 sel_next = sel.size()-1;
856 if (-1 != sel_next && sel.size() > 0) select((Displayable)sel.get(sel_next), true);
857 else if (null != last_active && last_active.getClass() == Patch.class && null != last_temp && last_temp instanceof PatchStack) {
858 Displayable d = ((PatchStack)last_temp).getPatch(layer, (Patch)last_active);
859 if (null != d) selection.add(d);
861 // TODO last_temp doesn't remain the PatchStack // Utils.log2("last_temp is: " + last_temp.getClass().getName());
863 // Keep Profile chain selected, for best ease of use:
864 if (null != last_active && last_active.getClass() == Profile.class && last_active.isLinked(Profile.class)) {
865 Displayable other = null;
866 for (final Displayable prof : last_active.getLinked(Profile.class)) {
867 if (prof.getLayer() == layer) {
868 other = prof;
869 break;
872 if (null != other) selection.add(other);
875 // repaint everything
876 navigator.repaint(true);
877 canvas.repaint(true);
879 // repaint tabs (hard as hell)
880 Utils.updateComponent(tabs);
881 // @#$%^! The above works half the times, so explicit repaint as well:
882 Component c = tabs.getSelectedComponent();
883 if (null == c) {
884 c = scroll_patches;
885 tabs.setSelectedComponent(scroll_patches);
887 Utils.updateComponent(c);
889 project.getLoader().setMassiveMode(false); // resetting if it was set true
891 // update the coloring in the ProjectTree
892 project.getProjectTree().updateUILater();
894 setTempCurrentImage();
897 static public void updateVisibleTabs() {
898 for (final Display d : al_displays) {
899 d.updateVisibleTab(true);
903 /** Recreate the tab that is being shown. */
904 public void updateVisibleTab(boolean set_zdispl) {
905 // update only the visible tab
906 switch (tabs.getSelectedIndex()) {
907 case 0:
908 ht_panels.clear();
909 updateTab(panel_patches, "Patches", layer.getDisplayables(Patch.class));
910 break;
911 case 1:
912 ht_panels.clear();
913 updateTab(panel_profiles, "Profiles", layer.getDisplayables(Profile.class));
914 break;
915 case 2:
916 if (set_zdispl) {
917 ht_panels.clear();
918 updateTab(panel_zdispl, "Z-space objects", layer.getParent().getZDisplayables());
920 break;
921 // case 3: channel opacities
922 case 4:
923 ht_panels.clear();
924 updateTab(panel_labels, "Labels", layer.getDisplayables(DLabel.class));
925 break;
930 private void setLayerLater(final Layer layer, final Displayable active) {
931 if (null == layer) return;
932 this.layer = layer;
933 if (!ControlWindow.isGUIEnabled()) return;
934 SwingUtilities.invokeLater(new Runnable() { public void run() {
935 // empty the tabs, except channels and pipes
936 clearTab(panel_profiles, "Profiles");
937 clearTab(panel_patches, "Patches");
938 clearTab(panel_labels, "Labels");
939 // distribute Displayable to the tabs. Ignore LayerSet instances.
940 if (null == ht_panels) ht_panels = new Hashtable<Displayable,DisplayablePanel>();
941 else ht_panels.clear();
942 Iterator it = layer.getDisplayables().iterator();
943 while (it.hasNext()) {
944 add((Displayable)it.next(), false, false);
946 it = layer.getParent().getZDisplayables().iterator(); // the pipes, that live in the LayerSet
947 while (it.hasNext()) {
948 add((Displayable)it.next(), false, false);
950 navigator.repaint(true); // was not done when adding
951 Utils.updateComponent(tabs.getSelectedComponent());
953 setActive(active);
954 }});
955 // swing issues:
957 new Thread() {
958 public void run() {
959 setPriority(Thread.NORM_PRIORITY);
960 try { Thread.sleep(1000); } catch (Exception e) {}
961 setActive(active);
963 }.start();
967 /** Remove all components from the tab and add a "No [label]" label to each. */
968 private void clearTab(final Container c, final String label) {
969 c.removeAll();
970 c.add(new JLabel("No " + label + "."));
971 // magic cocktail:
972 if (tabs.getSelectedComponent() == c) {
973 Utils.updateComponent(c);
977 /** A class to listen to the transparency_slider of the DisplayablesSelectorWindow. */
978 private class TransparencySliderListener extends MouseAdapter implements ChangeListener {
980 public void stateChanged(ChangeEvent ce) {
981 //change the transparency value of the current active displayable
982 float new_value = (float)((JSlider)ce.getSource()).getValue();
983 setTransparency(new_value / 100.0f);
986 public void mousePressed(MouseEvent me) {
987 JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
988 if (scroll != scroll_channels && !selection.isEmpty()) selection.addDataEditStep(new String[]{"alpha"});
991 public void mouseReleased(MouseEvent me) {
992 // update navigator window
993 navigator.repaint(true);
994 JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
995 if (scroll != scroll_channels && !selection.isEmpty()) selection.addDataEditStep(new String[]{"alpha"});
999 /** Context-sensitive: to a Displayable, or to a channel. */
1000 private void setTransparency(final float value) {
1001 JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
1002 if (scroll == scroll_channels) {
1003 for (int i=0; i<4; i++) {
1004 if (channels[i].getBackground() == Color.cyan) {
1005 channels[i].setAlpha(value); // will call back and repaint the Display
1006 return;
1009 } else if (null != active) {
1010 if (value != active.getAlpha()) { // because there's a callback from setActive that would then affect all other selected Displayable without having dragged the slider, i.e. just by being selected.
1011 canvas.invalidateVolatile();
1012 selection.setAlpha(value);
1017 public void setTransparencySlider(final float transp) {
1018 if (transp >= 0.0f && transp <= 1.0f) {
1019 // fire event
1020 transp_slider.setValue((int)(transp * 100));
1024 /** Mark the canvas for updating the offscreen images if the given Displayable is NOT the active. */ // Used by the Displayable.setVisible for example.
1025 static public void setUpdateGraphics(final Layer layer, final Displayable displ) {
1026 for (final Display d : al_displays) {
1027 if (layer == d.layer && null != d.active && d.active != displ) {
1028 d.canvas.setUpdateGraphics(true);
1033 /** Flag the DisplayCanvas of Displays showing the given Layer to update their offscreen images.*/
1034 static public void setUpdateGraphics(final Layer layer, final boolean update) {
1035 for (final Display d : al_displays) {
1036 if (layer == d.layer) {
1037 d.canvas.setUpdateGraphics(update);
1042 /** Whether to update the offscreen images or not. */
1043 public void setUpdateGraphics(boolean b) {
1044 canvas.setUpdateGraphics(b);
1047 /** Find all Display instances that contain the layer and repaint them, in the Swing GUI thread. */
1048 static public void update(final Layer layer) {
1049 if (null == layer) return;
1050 SwingUtilities.invokeLater(new Runnable() { public void run() {
1051 for (final Display d : al_displays) {
1052 if (d.isShowing(layer)) {
1053 d.repaintAll();
1056 }});
1059 static public void update(final LayerSet set) {
1060 update(set, true);
1063 /** Find all Display instances showing a Layer of this LayerSet, and update the dimensions of the navigator and canvas and snapshots, and repaint, in the Swing GUI thread. */
1064 static public void update(final LayerSet set, final boolean update_canvas_dimensions) {
1065 if (null == set) return;
1066 SwingUtilities.invokeLater(new Runnable() { public void run() {
1067 for (final Display d : al_displays) {
1068 if (set.contains(d.layer)) {
1069 d.updateSnapshots();
1070 if (update_canvas_dimensions) d.canvas.setDimensions(set.getLayerWidth(), set.getLayerHeight());
1071 d.repaintAll();
1074 }});
1077 /** Release all resources held by this Display and close the frame. */
1078 protected void destroy() {
1079 dispatcher.quit();
1080 canvas.setReceivesInput(false);
1081 slt.quit();
1083 // update the coloring in the ProjectTree and LayerTree
1084 if (!project.isBeingDestroyed()) {
1085 try {
1086 project.getProjectTree().updateUILater();
1087 project.getLayerTree().updateUILater();
1088 } catch (Exception e) {
1089 Utils.log2("updateUI failed at Display.destroy()");
1093 frame.removeComponentListener(component_listener);
1094 frame.removeWindowListener(window_listener);
1095 frame.removeWindowFocusListener(window_listener);
1096 frame.removeWindowStateListener(window_listener);
1097 frame.removeKeyListener(canvas);
1098 frame.removeMouseListener(frame_mouse_listener);
1099 canvas_panel.removeKeyListener(canvas);
1100 canvas.removeKeyListener(canvas);
1101 tabs.removeChangeListener(tabs_listener);
1102 tabs.removeKeyListener(canvas);
1103 ImagePlus.removeImageListener(this);
1104 bytypelistener = null;
1105 canvas.destroy();
1106 navigator.destroy();
1107 scroller.removeAdjustmentListener(scroller_listener);
1108 frame.setVisible(false);
1109 //no need, and throws exception//frame.dispose();
1110 active = null;
1111 if (null != selection) selection.clear();
1112 //Utils.log2("destroying selection");
1114 // below, need for SetLayerThread threads to quit
1115 slt.quit();
1116 // set a new front if any
1117 if (null == front && al_displays.size() > 0) {
1118 front = (Display)al_displays.get(al_displays.size() -1);
1120 // repaint layer tree (to update the label color)
1121 try {
1122 project.getLayerTree().updateUILater(); // works only after setting the front above
1123 } catch (Exception e) {} // ignore swing sync bullshit when closing everything too fast
1124 // remove the drag and drop listener
1125 dnd.destroy();
1128 /** Find all Display instances that contain a Layer of the given project and close them without removing the Display entries from the database. */
1129 static synchronized public void close(final Project project) {
1130 /* // concurrent modifications if more than 1 Display are being removed asynchronously
1131 for (final Display d : al_displays) {
1132 if (d.getLayer().getProject().equals(project)) {
1133 it.remove();
1134 d.destroy();
1138 Display[] d = new Display[al_displays.size()];
1139 al_displays.toArray(d);
1140 for (int i=0; i<d.length; i++) {
1141 if (d[i].getProject() == project) {
1142 al_displays.remove(d[i]);
1143 d[i].destroy();
1148 /** Find all Display instances that contain the layer and close them and remove the Display from the database. */
1149 static public void close(final Layer layer) {
1150 for (Iterator it = al_displays.iterator(); it.hasNext(); ) {
1151 Display d = (Display)it.next();
1152 if (d.isShowing(layer)) {
1153 d.remove(false);
1154 it.remove();
1159 public boolean remove(boolean check) {
1160 if (check) {
1161 if (!Utils.check("Delete the Display ?")) return false;
1163 // flush the offscreen images and close the frame
1164 destroy();
1165 removeFromDatabase();
1166 return true;
1169 public Layer getLayer() {
1170 return layer;
1173 public LayerSet getLayerSet() {
1174 return layer.getParent();
1177 public boolean isShowing(final Layer layer) {
1178 return this.layer == layer;
1181 public DisplayNavigator getNavigator() {
1182 return navigator;
1185 /** Repaint both the canvas and the navigator, updating the graphics, and the title and tabs. */
1186 public void repaintAll() {
1187 if (repaint_disabled) return;
1188 navigator.repaint(true);
1189 canvas.repaint(true);
1190 Utils.updateComponent(tabs);
1191 updateTitle();
1194 /** Repaint the canvas updating graphics, the navigator without updating graphics, and the title. */
1195 public void repaintAll2() {
1196 if (repaint_disabled) return;
1197 navigator.repaint(false);
1198 canvas.repaint(true);
1199 updateTitle();
1202 static public void repaintSnapshots(final LayerSet set) {
1203 if (repaint_disabled) return;
1204 for (final Display d : al_displays) {
1205 if (d.getLayer().getParent() == set) {
1206 d.navigator.repaint(true);
1207 Utils.updateComponent(d.tabs);
1211 static public void repaintSnapshots(final Layer layer) {
1212 if (repaint_disabled) return;
1213 for (final Display d : al_displays) {
1214 if (d.getLayer() == layer) {
1215 d.navigator.repaint(true);
1216 Utils.updateComponent(d.tabs);
1221 public void pack() {
1222 dispatcher.exec(new Runnable() { public void run() {
1223 try {
1224 Thread.currentThread().sleep(100);
1225 SwingUtilities.invokeAndWait(new Runnable() { public void run() {
1226 frame.pack();
1227 }});
1228 } catch (Exception e) { IJError.print(e); }
1229 }});
1232 static public void pack(final LayerSet ls) {
1233 for (final Display d : al_displays) {
1234 if (d.layer.getParent() == ls) d.pack();
1238 private void adjustCanvas() {
1239 SwingUtilities.invokeLater(new Runnable() { public void run() {
1240 Rectangle r = split.getRightComponent().getBounds();
1241 canvas.setDrawingSize(r.width, r.height, true);
1242 // fix not-on-top-left problem
1243 canvas.setLocation(0, 0);
1244 //frame.pack(); // don't! Would go into an infinite loop
1245 canvas.repaint(true);
1246 updateInDatabase("srcRect");
1247 }});
1250 /** Grab the last selected display (or create an new one if none) and show in it the layer,centered on the Displayable object. */
1251 static public void setFront(final Layer layer, final Displayable displ) {
1252 if (null == front) {
1253 Display display = new Display(layer.getProject(), layer); // gets set to front
1254 display.showCentered(displ);
1255 } else if (layer == front.layer) {
1256 front.showCentered(displ);
1257 } else {
1258 // find one:
1259 for (final Display d : al_displays) {
1260 if (d.layer == layer) {
1261 d.frame.toFront();
1262 d.showCentered(displ);
1263 return;
1266 // else, open new one
1267 new Display(layer.getProject(), layer).showCentered(displ);
1271 /** Find the displays that show the given Layer, and add the given Displayable to the GUI and sets it active only in the front Display and only if 'activate' is true. */
1272 static public void add(final Layer layer, final Displayable displ, final boolean activate) {
1273 for (final Display d : al_displays) {
1274 if (d.layer == layer) {
1275 if (front == d) {
1276 d.add(displ, activate, true);
1277 //front.frame.toFront();
1278 } else {
1279 d.add(displ, false, true);
1285 static public void add(final Layer layer, final Displayable displ) {
1286 add(layer, displ, true);
1289 /** Add the ZDisplayable to all Displays that show a Layer belonging to the given LayerSet. */
1290 static public void add(final LayerSet set, final ZDisplayable zdispl) {
1291 for (final Display d : al_displays) {
1292 if (set.contains(d.layer)) {
1293 if (front == d) {
1294 zdispl.setLayer(d.layer); // the active one
1295 d.add(zdispl, true, true); // calling add(Displayable, boolean, boolean)
1296 //front.frame.toFront();
1297 } else {
1298 d.add(zdispl, false, true);
1304 static public void addAll(final Layer layer, final Collection<? extends Displayable> coll) {
1305 for (final Display d : al_displays) {
1306 if (d.layer == layer) {
1307 d.addAll(coll);
1312 static public void addAll(final LayerSet set, final Collection<? extends ZDisplayable> coll) {
1313 for (final Display d : al_displays) {
1314 if (set.contains(d.layer)) {
1315 for (final ZDisplayable zd : coll) {
1316 if (front == d) zd.setLayer(d.layer);
1318 d.addAll(coll);
1323 private final void addAll(final Collection<? extends Displayable> coll) {
1324 for (final Displayable d : coll) {
1325 add(d, false, false);
1327 selection.clear();
1328 Utils.updateComponent(tabs);
1329 navigator.repaint(true);
1332 // TODO this very old method could take some improvement:
1333 // - there is no need to create a new DisplayablePanel if its panel is not shown
1334 // - other issues; the method looks overly "if a dog barks and a duck quacks during a lunar eclipse then .."
1335 /** Add it to the proper panel, at the top, and set it active. */
1336 private final void add(final Displayable d, final boolean activate, final boolean repaint_snapshot) {
1337 DisplayablePanel dp = ht_panels.get(d);
1338 if (null != dp && activate) { // for ZDisplayable objects (TODO I think this is not used anymore)
1339 dp.setActive(true);
1340 //setActive(d);
1341 selection.clear();
1342 selection.add(d);
1343 return;
1345 // add to the proper list
1346 JPanel p = null;
1347 if (d instanceof Profile) {
1348 p = panel_profiles;
1349 } else if (d instanceof Patch) {
1350 p = panel_patches;
1351 } else if (d instanceof DLabel) {
1352 p = panel_labels;
1353 } else if (d instanceof ZDisplayable) { //both pipes and balls and AreaList
1354 p = panel_zdispl;
1355 } else {
1356 // LayerSet objects
1357 return;
1359 dp = new DisplayablePanel(this, d); // TODO: instead of destroying/recreating, we could just recycle them by reassigning a different Displayable. See how it goes! It'd need a pool of objects
1360 addToPanel(p, 0, dp, activate);
1361 ht_panels.put(d, dp);
1362 if (activate) {
1363 dp.setActive(true);
1364 //setActive(d);
1365 selection.clear();
1366 selection.add(d);
1368 if (repaint_snapshot) navigator.repaint(true);
1371 private void addToPanel(JPanel panel, int index, DisplayablePanel dp, boolean repaint) {
1372 // remove the label
1373 if (1 == panel.getComponentCount() && panel.getComponent(0) instanceof JLabel) {
1374 panel.removeAll();
1376 panel.add(dp, index);
1377 if (repaint) {
1378 Utils.updateComponent(tabs);
1382 /** Find the displays that show the given Layer, and remove the given Displayable from the GUI. */
1383 static public void remove(final Layer layer, final Displayable displ) {
1384 for (final Display d : al_displays) {
1385 if (layer == d.layer) d.remove(displ);
1389 private void remove(final Displayable displ) {
1390 DisplayablePanel ob = ht_panels.remove(displ);
1391 if (null != ob) {
1392 final JScrollPane jsp = ht_tabs.get(displ.getClass());
1393 if (null != jsp) {
1394 JPanel p = (JPanel)jsp.getViewport().getView();
1395 p.remove((Component)ob);
1396 Utils.revalidateComponent(p);
1399 if (null == active || !selection.contains(displ)) {
1400 canvas.setUpdateGraphics(true);
1402 canvas.invalidateVolatile(); // removing active, no need to update offscreen but yes the volatile
1403 repaint(displ, null, 5, true, false);
1404 // from Selection.deleteAll this method is called ... but it's ok: same thread, no locking problems.
1405 selection.remove(displ);
1408 static public void remove(final ZDisplayable zdispl) {
1409 for (final Display d : al_displays) {
1410 if (zdispl.getLayerSet() == d.layer.getParent()) {
1411 d.remove((Displayable)zdispl);
1416 static public void repaint(final Layer layer, final Displayable displ, final int extra) {
1417 repaint(layer, displ, displ.getBoundingBox(), extra);
1420 static public void repaint(final Layer layer, final Displayable displ, final Rectangle r, final int extra) {
1421 repaint(layer, displ, r, extra, true);
1424 /** Find the displays that show the given Layer, and repaint the given Displayable. */
1425 static public void repaint(final Layer layer, final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator) {
1426 if (repaint_disabled) return;
1427 for (final Display d : al_displays) {
1428 if (layer == d.layer) {
1429 d.repaint(displ, r, extra, repaint_navigator, false);
1434 static public void repaint(final Displayable d) {
1435 if (d instanceof ZDisplayable) repaint(d.getLayerSet(), d, d.getBoundingBox(null), 5, true);
1436 repaint(d.getLayer(), d, d.getBoundingBox(null), 5, true);
1439 /** Repaint as much as the bounding box around the given Displayable, or the r if not null. */
1440 private void repaint(final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator, final boolean update_graphics) {
1441 if (repaint_disabled || null == displ) return;
1442 if (update_graphics || displ.getClass() == Patch.class || displ != active) {
1443 canvas.setUpdateGraphics(true);
1445 if (null != r) canvas.repaint(r, extra);
1446 else canvas.repaint(displ, extra);
1447 if (repaint_navigator) {
1448 DisplayablePanel dp = ht_panels.get(displ);
1449 if (null != dp) dp.repaint(); // is null when creating it, or after deleting it
1450 navigator.repaint(true); // everything
1454 /** Repaint the snapshot for the given Displayable both at the DisplayNavigator and on its panel,and only if it has not been painted before. This method is intended for the loader to know when to paint a snap, to avoid overhead. */
1455 static public void repaintSnapshot(final Displayable displ) {
1456 for (final Display d : al_displays) {
1457 if (d.layer.contains(displ)) {
1458 if (!d.navigator.isPainted(displ)) {
1459 DisplayablePanel dp = d.ht_panels.get(displ);
1460 if (null != dp) dp.repaint(); // is null when creating it, or after deleting it
1461 d.navigator.repaint(displ);
1467 /** Repaint the given Rectangle in all Displays showing the layer, updating the offscreen image if any. */
1468 static public void repaint(final Layer layer, final Rectangle r, final int extra) {
1469 repaint(layer, extra, r, true, true);
1472 static public void repaint(final Layer layer, final int extra, final Rectangle r, final boolean update_navigator) {
1473 repaint(layer, extra, r, update_navigator, true);
1476 static public void repaint(final Layer layer, final int extra, final Rectangle r, final boolean update_navigator, final boolean update_graphics) {
1477 if (repaint_disabled) return;
1478 for (final Display d : al_displays) {
1479 if (layer == d.layer) {
1480 d.canvas.setUpdateGraphics(update_graphics);
1481 d.canvas.repaint(r, extra);
1482 if (update_navigator) {
1483 d.navigator.repaint(true);
1484 Utils.updateComponent(d.tabs.getSelectedComponent());
1491 /** Repaint the given Rectangle in all Displays showing the layer, optionally updating the offscreen image (if any). */
1492 static public void repaint(final Layer layer, final Rectangle r, final int extra, final boolean update_graphics) {
1493 if (repaint_disabled) return;
1494 for (final Display d : al_displays) {
1495 if (layer == d.layer) {
1496 d.canvas.setUpdateGraphics(update_graphics);
1497 d.canvas.repaint(r, extra);
1498 d.navigator.repaint(update_graphics);
1499 if (update_graphics) Utils.updateComponent(d.tabs.getSelectedComponent());
1504 /** Repaint the DisplayablePanel (and DisplayNavigator) only for the given Displayable, in all Displays showing the given Layer. */
1505 static public void repaint(final Layer layer, final Displayable displ) {
1506 if (repaint_disabled) return;
1507 for (final Display d : al_displays) {
1508 if (layer == d.layer) {
1509 DisplayablePanel dp = d.ht_panels.get(displ);
1510 if (null != dp) dp.repaint();
1511 d.navigator.repaint(true);
1516 static public void repaint(LayerSet set, Displayable displ, int extra) {
1517 repaint(set, displ, null, extra);
1520 static public void repaint(LayerSet set, Displayable displ, Rectangle r, int extra) {
1521 repaint(set, displ, r, extra, true);
1524 /** Repaint the Displayable in every Display that shows a Layer belonging to the given LayerSet. */
1525 static public void repaint(final LayerSet set, final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator) {
1526 if (repaint_disabled) return;
1527 for (final Display d : al_displays) {
1528 if (set.contains(d.layer)) {
1529 if (repaint_navigator) {
1530 if (null != displ) {
1531 DisplayablePanel dp = d.ht_panels.get(displ);
1532 if (null != dp) dp.repaint();
1534 d.navigator.repaint(true);
1536 if (null == displ || displ != d.active) d.setUpdateGraphics(true); // safeguard
1537 // paint the given box or the actual Displayable's box
1538 if (null != r) d.canvas.repaint(r, extra);
1539 else d.canvas.repaint(displ, extra);
1544 /** Repaint the entire LayerSet, in all Displays showing a Layer of it.*/
1545 static public void repaint(final LayerSet set) {
1546 if (repaint_disabled) return;
1547 for (final Display d : al_displays) {
1548 if (set.contains(d.layer)) {
1549 d.navigator.repaint(true);
1550 d.canvas.repaint(true);
1554 /** Repaint the given box in the LayerSet, in all Displays showing a Layer of it.*/
1555 static public void repaint(final LayerSet set, final Rectangle box) {
1556 if (repaint_disabled) return;
1557 for (final Display d : al_displays) {
1558 if (set.contains(d.layer)) {
1559 d.navigator.repaint(box);
1560 d.canvas.repaint(box, 0, true);
1564 /** Repaint the entire Layer, in all Displays showing it, including the tabs.*/
1565 static public void repaint(final Layer layer) { // TODO this method overlaps with update(layer)
1566 if (repaint_disabled) return;
1567 for (final Display d : al_displays) {
1568 if (layer == d.layer) {
1569 d.navigator.repaint(true);
1570 d.canvas.repaint(true);
1575 /** Call repaint on all open Displays. */
1576 static public void repaint() {
1577 if (repaint_disabled) {
1578 Utils.logAll("Can't repaint -- repainting is disabled!");
1579 return;
1581 for (final Display d : al_displays) {
1582 d.navigator.repaint(true);
1583 d.canvas.repaint(true);
1587 static private boolean repaint_disabled = false;
1589 /** Set a flag to enable/disable repainting of all Display instances. */
1590 static protected void setRepaint(boolean b) {
1591 repaint_disabled = !b;
1594 public Rectangle getBounds() {
1595 return frame.getBounds();
1598 public Point getLocation() {
1599 return frame.getLocation();
1602 public JFrame getFrame() {
1603 return frame;
1606 public void setLocation(Point p) {
1607 this.frame.setLocation(p);
1610 public Displayable getActive() {
1611 return active; //TODO this should return selection.active !!
1614 public void select(Displayable d) {
1615 select(d, false);
1618 /** Select/deselect accordingly to the current state and the shift key. */
1619 public void select(final Displayable d, final boolean shift_down) {
1620 if (null != active && active != d && active.getClass() != Patch.class) {
1621 // active is being deselected, so link underlying patches
1622 active.linkPatches();
1624 if (null == d) {
1625 //Utils.log2("Display.select: clearing selection");
1626 canvas.setUpdateGraphics(true);
1627 selection.clear();
1628 return;
1630 if (!shift_down) {
1631 //Utils.log2("Display.select: single selection");
1632 if (d != active) {
1633 selection.clear();
1634 selection.add(d);
1636 } else if (selection.contains(d)) {
1637 if (active == d) {
1638 selection.remove(d);
1639 //Utils.log2("Display.select: removing from a selection");
1640 } else {
1641 //Utils.log2("Display.select: activing within a selection");
1642 selection.setActive(d);
1644 } else {
1645 //Utils.log2("Display.select: adding to an existing selection");
1646 selection.add(d);
1648 // update the image shown to ImageJ
1649 // NO longer necessary, always he same FakeImagePlus // setTempCurrentImage();
1652 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p, final Class c) {
1653 choose(screen_x_p, screen_y_p, x_p, y_p, false, c);
1655 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p) {
1656 choose(screen_x_p, screen_y_p, x_p, y_p, false, null);
1659 /** Find a Displayable to add to the selection under the given point (which is in offscreen coords); will use a popup menu to give the user a range of Displayable objects to select from. */
1660 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p, boolean shift_down, Class c) {
1661 //Utils.log("Display.choose: x,y " + x_p + "," + y_p);
1662 final ArrayList<Displayable> al = new ArrayList<Displayable>(layer.find(x_p, y_p, true));
1663 al.addAll(layer.getParent().findZDisplayables(layer, x_p, y_p, true)); // only visible ones
1664 if (al.isEmpty()) {
1665 Displayable act = this.active;
1666 selection.clear();
1667 canvas.setUpdateGraphics(true);
1668 //Utils.log("choose: set active to null");
1669 // fixing lack of repainting for unknown reasons, of the active one TODO this is a temporary solution
1670 if (null != act) Display.repaint(layer, act, 5);
1671 } else if (1 == al.size()) {
1672 Displayable d = (Displayable)al.get(0);
1673 if (null != c && d.getClass() != c) {
1674 selection.clear();
1675 return;
1677 select(d, shift_down);
1678 //Utils.log("choose 1: set active to " + active);
1679 } else {
1680 if (al.contains(active) && !shift_down) {
1681 // do nothing
1682 } else {
1683 if (null != c) {
1684 // check if at least one of them is of class c
1685 // if only one is of class c, set as selected
1686 // else show menu
1687 for (Iterator it = al.iterator(); it.hasNext(); ) {
1688 Object ob = it.next();
1689 if (ob.getClass() != c) it.remove();
1691 if (0 == al.size()) {
1692 // deselect
1693 selection.clear();
1694 return;
1696 if (1 == al.size()) {
1697 select((Displayable)al.get(0), shift_down);
1698 return;
1700 // else, choose among the many
1702 choose(screen_x_p, screen_y_p, al, shift_down, x_p, y_p);
1704 //Utils.log("choose many: set active to " + active);
1708 private void choose(final int screen_x_p, final int screen_y_p, final Collection al, final boolean shift_down, final int x_p, final int y_p) {
1709 // show a popup on the canvas to choose
1710 new Thread() {
1711 public void run() {
1712 final Object lock = new Object();
1713 final DisplayableChooser d_chooser = new DisplayableChooser(al, lock);
1714 final JPopupMenu pop = new JPopupMenu("Select:");
1715 final Iterator itu = al.iterator();
1716 while (itu.hasNext()) {
1717 Displayable d = (Displayable)itu.next();
1718 JMenuItem menu_item = new JMenuItem(d.toString());
1719 menu_item.addActionListener(d_chooser);
1720 pop.add(menu_item);
1723 new Thread() {
1724 public void run() {
1725 pop.show(canvas, screen_x_p, screen_y_p);
1727 }.start();
1729 //now wait until selecting something
1730 synchronized(lock) {
1731 do {
1732 try {
1733 lock.wait();
1734 } catch (InterruptedException ie) {}
1735 } while (d_chooser.isWaiting() && pop.isShowing());
1738 //grab the chosen Displayable object
1739 Displayable d = d_chooser.getChosen();
1740 //Utils.log("Chosen: " + d.toString());
1741 if (null == d) { Utils.log2("Display.choose: returning a null!"); }
1742 select(d, shift_down);
1743 pop.setVisible(false);
1745 // fix selection bug: never receives mouseReleased event when the popup shows
1746 selection.mouseReleased(null, x_p, y_p, x_p, y_p, x_p, y_p);
1748 }.start();
1751 /** Used by the Selection exclusively. This method will change a lot in the near future, and may disappear in favor of getSelection().getActive(). All this method does is update GUI components related to the currently active and the newly active Displayable; called through SwingUtilities.invokeLater. */
1752 protected void setActive(final Displayable displ) {
1753 final Displayable prev_active = this.active;
1754 this.active = displ;
1755 SwingUtilities.invokeLater(new Runnable() { public void run() {
1757 // renew current image if necessary
1758 if (null != displ && displ == prev_active) {
1759 // make sure the proper tab is selected.
1760 selectTab(displ);
1761 return; // the same
1763 // deactivate previously active
1764 if (null != prev_active) {
1765 final DisplayablePanel ob = ht_panels.get(prev_active);
1766 if (null != ob) ob.setActive(false);
1767 // erase "decorations" of the previously active
1768 canvas.repaint(selection.getBox(), 4);
1770 // activate the new active
1771 if (null != displ) {
1772 final DisplayablePanel ob = ht_panels.get(displ);
1773 if (null != ob) ob.setActive(true);
1774 updateInDatabase("active_displayable_id");
1775 if (displ.getClass() != Patch.class) project.select(displ); // select the node in the corresponding tree, if any.
1776 // select the proper tab, and scroll to visible
1777 selectTab(displ);
1778 boolean update_graphics = null == prev_active || paintsBelow(prev_active, displ); // or if it's an image, but that's by default in the repaint method
1779 repaint(displ, null, 5, false, update_graphics); // to show the border, and to repaint out of the background image
1780 transp_slider.setValue((int)(displ.getAlpha() * 100));
1781 } else {
1782 //ensure decorations are removed from the panels, for Displayables in a selection besides the active one
1783 Utils.updateComponent(tabs.getSelectedComponent());
1785 }});
1788 /** If the other paints under the base. */
1789 public boolean paintsBelow(Displayable base, Displayable other) {
1790 boolean zd_base = base instanceof ZDisplayable;
1791 boolean zd_other = other instanceof ZDisplayable;
1792 if (zd_other) {
1793 if (base instanceof DLabel) return true; // zd paints under label
1794 if (!zd_base) return false; // any zd paints over a mere displ if not a label
1795 else {
1796 // both zd, compare indices
1797 ArrayList<ZDisplayable> al = other.getLayerSet().getZDisplayables();
1798 return al.indexOf(base) > al.indexOf(other);
1800 } else {
1801 if (!zd_base) {
1802 // both displ, compare indices
1803 ArrayList<Displayable> al = other.getLayer().getDisplayables();
1804 return al.indexOf(base) > al.indexOf(other);
1805 } else {
1806 // base is zd, other is d
1807 if (other instanceof DLabel) return false;
1808 return true;
1813 /** Select the proper tab, and also scroll it to show the given Displayable -unless it's a LayerSet, and unless the proper tab is already showing. */
1814 private void selectTab(final Displayable displ) {
1815 Method method = null;
1816 try {
1817 if (!(displ instanceof LayerSet)) {
1818 method = Display.class.getDeclaredMethod("selectTab", new Class[]{displ.getClass()});
1820 } catch (Exception e) {
1821 IJError.print(e);
1823 if (null != method) {
1824 final Method me = method;
1825 dispatcher.exec(new Runnable() { public void run() {
1826 try {
1827 me.setAccessible(true);
1828 me.invoke(Display.this, new Object[]{displ});
1829 } catch (Exception e) { IJError.print(e); }
1830 }});
1834 private void selectTab(Patch patch) {
1835 tabs.setSelectedComponent(scroll_patches);
1836 scrollToShow(scroll_patches, ht_panels.get(patch));
1839 private void selectTab(Profile profile) {
1840 tabs.setSelectedComponent(scroll_profiles);
1841 scrollToShow(scroll_profiles, ht_panels.get(profile));
1844 private void selectTab(DLabel label) {
1845 tabs.setSelectedComponent(scroll_labels);
1846 scrollToShow(scroll_labels, ht_panels.get(label));
1849 private void selectTab(ZDisplayable zd) {
1850 tabs.setSelectedComponent(scroll_zdispl);
1851 scrollToShow(scroll_zdispl, ht_panels.get(zd));
1854 private void selectTab(Pipe d) { selectTab((ZDisplayable)d); }
1855 private void selectTab(Polyline d) { selectTab((ZDisplayable)d); }
1856 private void selectTab(AreaList d) { selectTab((ZDisplayable)d); }
1857 private void selectTab(Ball d) { selectTab((ZDisplayable)d); }
1858 private void selectTab(Dissector d) { selectTab((ZDisplayable)d); }
1860 /** A method to update the given tab, creating a new DisplayablePanel for each Displayable present in the given ArrayList, and storing it in the ht_panels (which is cleared first). */
1861 private void updateTab(final Container tab, final String label, final ArrayList al) {
1862 final boolean[] recreated = new boolean[]{false, true, true};
1863 dispatcher.execSwing(new Runnable() { public void run() {
1864 try {
1865 if (0 == al.size()) {
1866 tab.removeAll();
1867 tab.add(new JLabel("No " + label + "."));
1868 } else {
1869 Component[] comp = tab.getComponents();
1870 int next = 0;
1871 if (1 == comp.length && comp[0].getClass() == JLabel.class) {
1872 next = 1;
1873 tab.remove(0);
1875 for (Iterator it = al.iterator(); it.hasNext(); ) {
1876 Displayable d = (Displayable)it.next();
1877 DisplayablePanel dp = null;
1878 if (next < comp.length) {
1879 dp = (DisplayablePanel)comp[next++]; // recycling panels
1880 dp.set(d);
1881 } else {
1882 dp = new DisplayablePanel(Display.this, d);
1883 tab.add(dp);
1885 ht_panels.put(d, dp);
1887 if (next < comp.length) {
1888 // remove from the end, to avoid potential repaints of other panels
1889 for (int i=comp.length-1; i>=next; i--) {
1890 tab.remove(i);
1893 recreated[0] = true;
1895 if (recreated[0]) {
1896 tab.invalidate();
1897 tab.validate();
1898 tab.repaint();
1900 if (null != Display.this.active) scrollToShow(Display.this.active);
1901 } catch (Throwable e) { IJError.print(e); }
1902 }});
1905 static public void setActive(final Object event, final Displayable displ) {
1906 if (!(event instanceof InputEvent)) return;
1907 // find which Display
1908 for (final Display d : al_displays) {
1909 if (d.isOrigin((InputEvent)event)) {
1910 d.setActive(displ);
1911 break;
1916 /** Find out whether this Display is Transforming its active Displayable. */
1917 public boolean isTransforming() {
1918 return canvas.isTransforming();
1921 /** Find whether any Display is transforming the given Displayable. */
1922 static public boolean isTransforming(final Displayable displ) {
1923 for (final Display d : al_displays) {
1924 if (null != d.active && d.active == displ && d.canvas.isTransforming()) return true;
1926 return false;
1929 static public boolean isAligning(final LayerSet set) {
1930 for (final Display d : al_displays) {
1931 if (d.layer.getParent() == set && set.isAligning()) {
1932 return true;
1935 return false;
1938 /** Set the front Display to transform the Displayable only if no other canvas is transforming it. */
1939 static public void setTransforming(final Displayable displ) {
1940 if (null == front) return;
1941 if (front.active != displ) return;
1942 for (final Display d : al_displays) {
1943 if (d.active == displ) {
1944 if (d.canvas.isTransforming()) {
1945 Utils.showMessage("Already transforming " + displ.getTitle());
1946 return;
1950 front.canvas.setTransforming(true);
1953 /** Check whether the source of the event is located in this instance.*/
1954 private boolean isOrigin(InputEvent event) {
1955 Object source = event.getSource();
1956 // find it ... check the canvas for now TODO
1957 if (canvas == source) {
1958 return true;
1960 return false;
1963 /** Get the layer of the front Display, or null if none.*/
1964 static public Layer getFrontLayer() {
1965 if (null == front) return null;
1966 return front.layer;
1969 /** Get the layer of an open Display of the given Project, or null if none.*/
1970 static public Layer getFrontLayer(final Project project) {
1971 if (null == front) return null;
1972 if (front.project == project) return front.layer;
1973 // else, find an open Display for the given Project, if any
1974 for (final Display d : al_displays) {
1975 if (d.project == project) {
1976 d.frame.toFront();
1977 return d.layer;
1980 return null; // none found
1983 static public Display getFront(final Project project) {
1984 if (null == front) return null;
1985 if (front.project == project) return front;
1986 for (final Display d : al_displays) {
1987 if (d.project == project) {
1988 d.frame.toFront();
1989 return d;
1992 return null;
1995 public boolean isReadOnly() {
1996 // TEMPORARY: in the future one will be able show displays as read-only to other people, remotely
1997 return false;
2000 static public void showPopup(Component c, int x, int y) {
2001 if (null != front) front.getPopupMenu().show(c, x, y);
2004 /** Return a context-sensitive popup menu. */
2005 public JPopupMenu getPopupMenu() { // called from canvas
2006 // get the job canceling dialog
2007 if (!canvas.isInputEnabled()) {
2008 return project.getLoader().getJobsPopup(this);
2011 // create new
2012 this.popup = new JPopupMenu();
2013 JMenuItem item = null;
2014 JMenu menu = null;
2016 if (ProjectToolbar.ALIGN == Toolbar.getToolId()) {
2017 boolean aligning = layer.getParent().isAligning();
2018 item = new JMenuItem("Cancel alignment"); item.addActionListener(this); popup.add(item);
2019 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2020 if (!aligning) item.setEnabled(false);
2021 item = new JMenuItem("Align with landmarks"); item.addActionListener(this); popup.add(item);
2022 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true));
2023 if (!aligning) item.setEnabled(false);
2024 item = new JMenuItem("Align and register"); item.addActionListener(this); popup.add(item);
2025 if (!aligning) item.setEnabled(false);
2026 item = new JMenuItem("Align using profiles"); item.addActionListener(this); popup.add(item);
2027 if (!aligning || selection.isEmpty() || !selection.contains(Profile.class)) item.setEnabled(false);
2028 item = new JMenuItem("Align stack slices"); item.addActionListener(this); popup.add(item);
2029 if (selection.isEmpty() || ! (getActive().getClass() == Patch.class && ((Patch)getActive()).isStack())) item.setEnabled(false);
2030 item = new JMenuItem("Align layers (layer-wise)"); item.addActionListener(this); popup.add(item);
2031 if (1 == layer.getParent().size()) item.setEnabled(false);
2032 item = new JMenuItem("Align layers (tile-wise global minimization)"); item.addActionListener(this); popup.add(item);
2033 if (1 == layer.getParent().size()) item.setEnabled(false);
2034 return popup;
2038 JMenu adjust_menu = new JMenu("Adjust");
2040 if (null != active) {
2041 if (!canvas.isTransforming()) {
2042 if (active instanceof Profile) {
2043 item = new JMenuItem("Duplicate, link and send to next layer"); item.addActionListener(this); popup.add(item);
2044 Layer nl = layer.getParent().next(layer);
2045 if (nl == layer) item.setEnabled(false);
2046 item = new JMenuItem("Duplicate, link and send to previous layer"); item.addActionListener(this); popup.add(item);
2047 nl = layer.getParent().previous(layer);
2048 if (nl == layer) item.setEnabled(false);
2050 menu = new JMenu("Duplicate, link and send to");
2051 ArrayList al = layer.getParent().getLayers();
2052 Iterator it = al.iterator();
2053 int i = 1;
2054 while (it.hasNext()) {
2055 Layer la = (Layer)it.next();
2056 item = new JMenuItem(i + ": z = " + la.getZ()); item.addActionListener(this); menu.add(item); // TODO should label which layers contain Profile instances linked to the one being duplicated
2057 if (la == this.layer) item.setEnabled(false);
2058 i++;
2060 popup.add(menu);
2061 item = new JMenuItem("Duplicate, link and send to..."); item.addActionListener(this); popup.add(item);
2063 popup.addSeparator();
2065 item = new JMenuItem("Unlink from images"); item.addActionListener(this); popup.add(item);
2066 if (!active.isLinked()) item.setEnabled(false); // isLinked() checks if it's linked to a Patch in its own layer
2067 item = new JMenuItem("Show in 3D"); item.addActionListener(this); popup.add(item);
2068 popup.addSeparator();
2069 } else if (active instanceof Patch) {
2070 item = new JMenuItem("Unlink from images"); item.addActionListener(this); popup.add(item);
2071 if (!active.isLinked(Patch.class)) item.setEnabled(false);
2072 if (((Patch)active).isStack()) {
2073 item = new JMenuItem("Unlink slices"); item.addActionListener(this); popup.add(item);
2075 int n_sel_patches = selection.getSelected(Patch.class).size();
2076 if (1 == n_sel_patches) {
2077 item = new JMenuItem("Snap"); item.addActionListener(this); popup.add(item);
2078 } else if (n_sel_patches > 1) {
2079 item = new JMenuItem("Montage"); item.addActionListener(this); popup.add(item);
2080 item = new JMenuItem("Lens correction"); item.addActionListener(this); popup.add(item);
2082 item = new JMenuItem("Link images..."); item.addActionListener(this); popup.add(item);
2083 item = new JMenuItem("View volume"); item.addActionListener(this); popup.add(item);
2084 HashSet hs = active.getLinked(Patch.class);
2085 if (null == hs || 0 == hs.size()) item.setEnabled(false);
2086 item = new JMenuItem("View orthoslices"); item.addActionListener(this); popup.add(item);
2087 if (null == hs || 0 == hs.size()) item.setEnabled(false); // if no Patch instances among the directly linked, then it's not a stack
2088 popup.addSeparator();
2089 } else {
2090 item = new JMenuItem("Unlink"); item.addActionListener(this); popup.add(item);
2091 item = new JMenuItem("Show in 3D"); item.addActionListener(this); popup.add(item);
2092 popup.addSeparator();
2094 if (active instanceof AreaList) {
2095 item = new JMenuItem("Merge"); item.addActionListener(this); popup.add(item);
2096 ArrayList al = selection.getSelected();
2097 int n = 0;
2098 for (Iterator it = al.iterator(); it.hasNext(); ) {
2099 if (it.next().getClass() == AreaList.class) n++;
2101 if (n < 2) item.setEnabled(false);
2102 } else if (active instanceof Pipe) {
2103 item = new JMenuItem("Identify..."); item.addActionListener(this); popup.add(item);
2104 item = new JMenuItem("Identify with axes..."); item.addActionListener(this); popup.add(item);
2107 if (canvas.isTransforming()) {
2108 item = new JMenuItem("Apply transform"); item.addActionListener(this); popup.add(item);
2109 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true)); // dummy, for I don't add a MenuKeyListener, but "works" through the normal key listener. It's here to provide a visual cue
2110 } else {
2111 item = new JMenuItem("Transform"); item.addActionListener(this); popup.add(item);
2112 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0, true));
2114 item = new JMenuItem("Cancel transform"); item.addActionListener(this); popup.add(item);
2115 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2116 if (!canvas.isTransforming()) item.setEnabled(false);
2117 if (canvas.isTransforming()) {
2118 item = new JMenuItem("Specify transform..."); item.addActionListener(this); popup.add(item);
2121 if (!canvas.isTransforming()) {
2122 item = new JMenuItem("Color..."); item.addActionListener(this); popup.add(item);
2123 if (active instanceof LayerSet) item.setEnabled(false);
2124 if (active.isLocked()) {
2125 item = new JMenuItem("Unlock"); item.addActionListener(this); popup.add(item);
2126 } else {
2127 item = new JMenuItem("Lock"); item.addActionListener(this); popup.add(item);
2129 menu = new JMenu("Move");
2130 popup.addSeparator();
2131 LayerSet ls = layer.getParent();
2132 item = new JMenuItem("Move to top"); item.addActionListener(this); menu.add(item);
2133 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0, true)); // this is just to draw the key name by the menu; it does not incur on any event being generated (that I know if), and certainly not any event being listened to by TrakEM2.
2134 if (ls.isTop(active)) item.setEnabled(false);
2135 item = new JMenuItem("Move up"); item.addActionListener(this); menu.add(item);
2136 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0, true));
2137 if (ls.isTop(active)) item.setEnabled(false);
2138 item = new JMenuItem("Move down"); item.addActionListener(this); menu.add(item);
2139 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0, true));
2140 if (ls.isBottom(active)) item.setEnabled(false);
2141 item = new JMenuItem("Move to bottom"); item.addActionListener(this); menu.add(item);
2142 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_END, 0, true));
2143 if (ls.isBottom(active)) item.setEnabled(false);
2145 popup.add(menu);
2146 popup.addSeparator();
2147 item = new JMenuItem("Delete..."); item.addActionListener(this); popup.add(item);
2148 try {
2149 if (active instanceof Patch) {
2150 if (!active.isOnlyLinkedTo(Patch.class)) {
2151 item.setEnabled(false);
2153 } else if (!(active instanceof DLabel)) { // can't delete elements from the trees (Profile, Pipe, LayerSet)
2154 item.setEnabled(false);
2156 } catch (Exception e) { IJError.print(e); item.setEnabled(false); }
2158 if (active instanceof Patch) {
2159 item = new JMenuItem("Revert"); item.addActionListener(this); popup.add(item);
2160 popup.addSeparator();
2162 item = new JMenuItem("Properties..."); item.addActionListener(this); popup.add(item);
2163 item = new JMenuItem("Show centered"); item.addActionListener(this); popup.add(item);
2165 popup.addSeparator();
2167 if (! (active instanceof ZDisplayable)) {
2168 ArrayList al_layers = layer.getParent().getLayers();
2169 int i_layer = al_layers.indexOf(layer);
2170 int n_layers = al_layers.size();
2171 item = new JMenuItem("Send to previous layer"); item.addActionListener(this); popup.add(item);
2172 if (1 == n_layers || 0 == i_layer || active.isLinked()) item.setEnabled(false);
2173 // check if the active is a profile and contains a link to another profile in the layer it is going to be sent to, or it is linked
2174 else if (active instanceof Profile && !active.canSendTo(layer.getParent().previous(layer))) item.setEnabled(false);
2175 item = new JMenuItem("Send to next layer"); item.addActionListener(this); popup.add(item);
2176 if (1 == n_layers || n_layers -1 == i_layer || active.isLinked()) item.setEnabled(false);
2177 else if (active instanceof Profile && !active.canSendTo(layer.getParent().next(layer))) item.setEnabled(false);
2180 menu = new JMenu("Send linked group to...");
2181 if (active.hasLinkedGroupWithinLayer(this.layer)) {
2182 int i = 1;
2183 for (final Layer la : ls.getLayers()) {
2184 String layer_title = i + ": " + la.getTitle();
2185 if (-1 == layer_title.indexOf(' ')) layer_title += " ";
2186 item = new JMenuItem(layer_title); item.addActionListener(this); menu.add(item);
2187 if (la == this.layer) item.setEnabled(false);
2188 i++;
2190 popup.add(menu);
2191 } else {
2192 menu.setEnabled(false);
2193 //Utils.log("Active's linked group not within layer.");
2195 popup.add(menu);
2196 popup.addSeparator();
2201 if (!canvas.isTransforming()) {
2203 item = new JMenuItem("Undo");item.addActionListener(this); popup.add(item);
2204 if (!layer.getParent().canUndo() || canvas.isTransforming()) item.setEnabled(false);
2205 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Utils.getControlModifier(), true));
2206 item = new JMenuItem("Redo");item.addActionListener(this); popup.add(item);
2207 if (!layer.getParent().canRedo() || canvas.isTransforming()) item.setEnabled(false);
2208 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Event.ALT_MASK, true));
2210 item = new JMenuItem("Enhance contrast layer-wise..."); item.addActionListener(this); adjust_menu.add(item);
2211 item = new JMenuItem("Enhance contrast (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
2212 if (selection.isEmpty()) item.setEnabled(false);
2213 item = new JMenuItem("Set Min and Max layer-wise..."); item.addActionListener(this); adjust_menu.add(item);
2214 item = new JMenuItem("Set Min and Max (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
2215 if (selection.isEmpty()) item.setEnabled(false);
2216 popup.add(adjust_menu);
2217 popup.addSeparator();
2219 // Would get so much simpler with a clojure macro ...
2221 try {
2222 menu = new JMenu("Hide/Unhide");
2223 item = new JMenuItem("Hide deselected"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.SHIFT_MASK, true));
2224 boolean none = 0 == selection.getNSelected();
2225 if (none) item.setEnabled(false);
2226 item = new JMenuItem("Hide deselected except images"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.SHIFT_MASK | Event.ALT_MASK, true));
2227 if (none) item.setEnabled(false);
2228 item = new JMenuItem("Hide selected"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, 0, true));
2229 if (none) item.setEnabled(false);
2230 none = ! layer.getParent().containsDisplayable(DLabel.class);
2231 item = new JMenuItem("Hide all labels"); item.addActionListener(this); menu.add(item);
2232 if (none) item.setEnabled(false);
2233 item = new JMenuItem("Unhide all labels"); item.addActionListener(this); menu.add(item);
2234 if (none) item.setEnabled(false);
2235 none = ! layer.getParent().contains(AreaList.class);
2236 item = new JMenuItem("Hide all arealists"); item.addActionListener(this); menu.add(item);
2237 if (none) item.setEnabled(false);
2238 item = new JMenuItem("Unhide all arealists"); item.addActionListener(this); menu.add(item);
2239 if (none) item.setEnabled(false);
2240 none = ! layer.contains(Profile.class);
2241 item = new JMenuItem("Hide all profiles"); item.addActionListener(this); menu.add(item);
2242 if (none) item.setEnabled(false);
2243 item = new JMenuItem("Unhide all profiles"); item.addActionListener(this); menu.add(item);
2244 if (none) item.setEnabled(false);
2245 none = ! layer.getParent().contains(Pipe.class);
2246 item = new JMenuItem("Hide all pipes"); item.addActionListener(this); menu.add(item);
2247 if (none) item.setEnabled(false);
2248 item = new JMenuItem("Unhide all pipes"); item.addActionListener(this); menu.add(item);
2249 if (none) item.setEnabled(false);
2250 none = ! layer.getParent().contains(Polyline.class);
2251 item = new JMenuItem("Hide all polylines"); item.addActionListener(this); menu.add(item);
2252 if (none) item.setEnabled(false);
2253 item = new JMenuItem("Unhide all polylines"); item.addActionListener(this); menu.add(item);
2254 if (none) item.setEnabled(false);
2255 none = ! layer.getParent().contains(Ball.class);
2256 item = new JMenuItem("Hide all balls"); item.addActionListener(this); menu.add(item);
2257 if (none) item.setEnabled(false);
2258 item = new JMenuItem("Unhide all balls"); item.addActionListener(this); menu.add(item);
2259 if (none) item.setEnabled(false);
2260 none = ! layer.getParent().containsDisplayable(Patch.class);
2261 item = new JMenuItem("Hide all images"); item.addActionListener(this); menu.add(item);
2262 if (none) item.setEnabled(false);
2263 item = new JMenuItem("Unhide all images"); item.addActionListener(this); menu.add(item);
2264 if (none) item.setEnabled(false);
2265 item = new JMenuItem("Hide all but images"); item.addActionListener(this); menu.add(item);
2266 item = new JMenuItem("Unhide all"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.ALT_MASK, true));
2268 popup.add(menu);
2269 } catch (Exception e) { IJError.print(e); }
2271 menu = new JMenu("Import");
2272 item = new JMenuItem("Import image"); item.addActionListener(this); menu.add(item);
2273 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, Event.ALT_MASK & Event.SHIFT_MASK, true));
2274 item = new JMenuItem("Import stack..."); item.addActionListener(this); menu.add(item);
2275 item = new JMenuItem("Import grid..."); item.addActionListener(this); menu.add(item);
2276 item = new JMenuItem("Import sequence as grid..."); item.addActionListener(this); menu.add(item);
2277 item = new JMenuItem("Import from text file..."); item.addActionListener(this); menu.add(item);
2278 item = new JMenuItem("Import labels as arealists..."); item.addActionListener(this); menu.add(item);
2279 popup.add(menu);
2281 menu = new JMenu("Export");
2282 item = new JMenuItem("Make flat image..."); item.addActionListener(this); menu.add(item);
2283 item = new JMenuItem("Arealists as labels (tif)"); item.addActionListener(this); menu.add(item);
2284 if (0 == layer.getParent().getZDisplayables(AreaList.class).size()) item.setEnabled(false);
2285 item = new JMenuItem("Arealists as labels (amira)"); item.addActionListener(this); menu.add(item);
2286 if (0 == layer.getParent().getZDisplayables(AreaList.class).size()) item.setEnabled(false);
2287 popup.add(menu);
2289 menu = new JMenu("Display");
2290 item = new JMenuItem("Resize canvas/LayerSet..."); item.addActionListener(this); menu.add(item);
2291 item = new JMenuItem("Autoresize canvas/LayerSet"); item.addActionListener(this); menu.add(item);
2292 // OBSOLETE // item = new JMenuItem("Rotate Layer/LayerSet..."); item.addActionListener(this); menu.add(item);
2293 item = new JMenuItem("Properties ..."); item.addActionListener(this); menu.add(item);
2294 popup.add(menu);
2296 menu = new JMenu("Project");
2297 this.project.getLoader().setupMenuItems(menu, this.getProject());
2298 item = new JMenuItem("Project properties..."); item.addActionListener(this); menu.add(item);
2299 item = new JMenuItem("Create subproject"); item.addActionListener(this); menu.add(item);
2300 if (null == canvas.getFakeImagePlus().getRoi()) item.setEnabled(false);
2301 item = new JMenuItem("Release memory..."); item.addActionListener(this); menu.add(item);
2302 item = new JMenuItem("Flush image cache"); item.addActionListener(this); menu.add(item);
2303 popup.add(menu);
2305 menu = new JMenu("Selection");
2306 item = new JMenuItem("Select all"); item.addActionListener(this); menu.add(item);
2307 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, Utils.getControlModifier(), true));
2308 if (0 == layer.getDisplayables().size() && 0 == layer.getParent().getZDisplayables().size()) item.setEnabled(false);
2309 item = new JMenuItem("Select none"); item.addActionListener(this); menu.add(item);
2310 if (0 == selection.getNSelected()) item.setEnabled(false);
2311 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2313 JMenu bytype = new JMenu("Select all by type");
2314 item = new JMenuItem("AreaList"); item.addActionListener(bytypelistener); bytype.add(item);
2315 item = new JMenuItem("Ball"); item.addActionListener(bytypelistener); bytype.add(item);
2316 item = new JMenuItem("Dissector"); item.addActionListener(bytypelistener); bytype.add(item);
2317 item = new JMenuItem("Image"); item.addActionListener(bytypelistener); bytype.add(item);
2318 item = new JMenuItem("Text"); item.addActionListener(bytypelistener); bytype.add(item);
2319 item = new JMenuItem("Pipe"); item.addActionListener(bytypelistener); bytype.add(item);
2320 item = new JMenuItem("Polyline"); item.addActionListener(bytypelistener); bytype.add(item);
2321 item = new JMenuItem("Profile"); item.addActionListener(bytypelistener); bytype.add(item);
2322 menu.add(bytype);
2324 item = new JMenuItem("Restore selection"); item.addActionListener(this); menu.add(item);
2325 item = new JMenuItem("Select under ROI"); item.addActionListener(this); menu.add(item);
2326 if (canvas.getFakeImagePlus().getRoi() == null) item.setEnabled(false);
2327 popup.add(menu);
2328 item = new JMenuItem("Search..."); item.addActionListener(this); popup.add(item);
2331 //canvas.add(popup);
2332 return popup;
2335 private ByTypeListener bytypelistener = new ByTypeListener(this);
2337 static private class ByTypeListener implements ActionListener {
2338 final Display d;
2339 ByTypeListener(final Display d) {
2340 this.d = d;
2342 public void actionPerformed(final ActionEvent ae) {
2343 final String command = ae.getActionCommand();
2345 final java.awt.geom.Area aroi = Utils.getArea(d.canvas.getFakeImagePlus().getRoi());
2347 d.dispatcher.exec(new Runnable() { public void run() {
2349 try {
2350 String type = command;
2351 if (type.equals("Image")) type = "Patch";
2352 Class c = Class.forName("ini.trakem2.display." + type);
2354 java.util.List<Displayable> a = new ArrayList<Displayable>();
2355 if (null != aroi) {
2356 a.addAll(d.layer.getDisplayables(c, aroi, true));
2357 a.addAll(d.layer.getParent().getZDisplayables(c, d.layer, aroi, true));
2358 } else {
2359 a.addAll(d.layer.getDisplayables(c));
2360 a.addAll(d.layer.getParent().getZDisplayables(c));
2361 // Remove non-visible ones
2362 for (final Iterator<Displayable> it = a.iterator(); it.hasNext(); ) {
2363 if (!it.next().isVisible()) it.remove();
2367 if (0 == a.size()) return;
2369 boolean selected = false;
2371 if (0 == ae.getModifiers()) {
2372 Utils.log2("first");
2373 d.selection.clear();
2374 d.selection.selectAll(a);
2375 selected = true;
2376 } else if (0 == (ae.getModifiers() ^ Event.SHIFT_MASK)) {
2377 Utils.log2("with shift");
2378 d.selection.selectAll(a); // just add them to the current selection
2379 selected = true;
2381 if (selected) {
2382 // Activate last:
2383 d.selection.setActive(a.get(a.size() -1));
2386 } catch (ClassNotFoundException e) {
2387 Utils.log2(e.toString());
2390 }});
2394 /** Check if a panel for the given Displayable is completely visible in the JScrollPane */
2395 public boolean isWithinViewport(final Displayable d) {
2396 final JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
2397 if (ht_tabs.get(d.getClass()) == scroll) return isWithinViewport(scroll, ht_panels.get(d));
2398 return false;
2401 private boolean isWithinViewport(JScrollPane scroll, DisplayablePanel dp) {
2402 if(null == dp) return false;
2403 JViewport view = scroll.getViewport();
2404 java.awt.Dimension dimensions = view.getExtentSize();
2405 java.awt.Point p = view.getViewPosition();
2406 int y = dp.getY();
2407 if ((y + DisplayablePanel.HEIGHT - p.y) <= dimensions.height && y >= p.y) {
2408 return true;
2410 return false;
2413 /** Check if a panel for the given Displayable is partially visible in the JScrollPane */
2414 public boolean isPartiallyWithinViewport(final Displayable d) {
2415 final JScrollPane scroll = ht_tabs.get(d.getClass());
2416 if (tabs.getSelectedComponent() == scroll) return isPartiallyWithinViewport(scroll, ht_panels.get(d));
2417 return false;
2420 /** Check if a panel for the given Displayable is at least partially visible in the JScrollPane */
2421 private boolean isPartiallyWithinViewport(final JScrollPane scroll, final DisplayablePanel dp) {
2422 if(null == dp) {
2423 //Utils.log2("Display.isPartiallyWithinViewport: null DisplayablePanel ??");
2424 return false; // to fast for you baby
2426 JViewport view = scroll.getViewport();
2427 java.awt.Dimension dimensions = view.getExtentSize();
2428 java.awt.Point p = view.getViewPosition();
2429 int y = dp.getY();
2430 if ( ((y + DisplayablePanel.HEIGHT - p.y) <= dimensions.height && y >= p.y) // completely visible
2431 || ((y + DisplayablePanel.HEIGHT - p.y) > dimensions.height && y < p.y + dimensions.height) // partially hovering at the bottom
2432 || ((y + DisplayablePanel.HEIGHT) > p.y && y < p.y) // partially hovering at the top
2434 return true;
2436 return false;
2439 /** A function to make a Displayable panel be visible in the screen, by scrolling the viewport of the JScrollPane. */
2440 private void scrollToShow(final Displayable d) {
2441 dispatcher.execSwing(new Runnable() { public void run() {
2442 final JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
2443 if (d instanceof ZDisplayable && scroll == scroll_zdispl) {
2444 scrollToShow(scroll_zdispl, ht_panels.get(d));
2445 return;
2447 final Class c = d.getClass();
2448 if (Patch.class == c && scroll == scroll_patches) {
2449 scrollToShow(scroll_patches, ht_panels.get(d));
2450 } else if (DLabel.class == c && scroll == scroll_labels) {
2451 scrollToShow(scroll_labels, ht_panels.get(d));
2452 } else if (Profile.class == c && scroll == scroll_profiles) {
2453 scrollToShow(scroll_profiles, ht_panels.get(d));
2455 }});
2458 private void scrollToShow(final JScrollPane scroll, final DisplayablePanel dp) {
2459 if (null == dp) return;
2460 JViewport view = scroll.getViewport();
2461 Point current = view.getViewPosition();
2462 Dimension extent = view.getExtentSize();
2463 int panel_y = dp.getY();
2464 if ((panel_y + DisplayablePanel.HEIGHT - current.y) <= extent.height && panel_y >= current.y) {
2465 // it's completely visible already
2466 return;
2467 } else {
2468 // scroll just enough
2469 // if it's above, show at the top
2470 if (panel_y - current.y < 0) {
2471 view.setViewPosition(new Point(0, panel_y));
2473 // if it's below (even if partially), show at the bottom
2474 else if (panel_y + 50 > current.y + extent.height) {
2475 view.setViewPosition(new Point(0, panel_y - extent.height + 50));
2476 //Utils.log("Display.scrollToShow: panel_y: " + panel_y + " current.y: " + current.y + " extent.height: " + extent.height);
2481 /** Update the title of the given Displayable in its DisplayablePanel, if any. */
2482 static public void updateTitle(final Layer layer, final Displayable displ) {
2483 for (final Display d : al_displays) {
2484 if (layer == d.layer) {
2485 DisplayablePanel dp = d.ht_panels.get(displ);
2486 if (null != dp) dp.updateTitle();
2491 /** Update the Display's title in all Displays showing the given Layer. */
2492 static public void updateTitle(final Layer layer) {
2493 for (final Display d : al_displays) {
2494 if (d.layer == layer) {
2495 d.updateTitle();
2499 /** Update the Display's title in all Displays showing a Layer of the given LayerSet. */
2500 static public void updateTitle(final LayerSet ls) {
2501 for (final Display d : al_displays) {
2502 if (d.layer.getParent() == ls) {
2503 d.updateTitle();
2508 /** Set a new title in the JFrame, showing info on the layer 'z' and the magnification. */
2509 public void updateTitle() {
2510 // From ij.ImagePlus class, the solution:
2511 String scale = "";
2512 final double magnification = canvas.getMagnification();
2513 if (magnification!=1.0) {
2514 final double percent = magnification*100.0;
2515 scale = new StringBuffer(" (").append(Utils.d2s(percent, percent==(int)percent ? 0 : 1)).append("%)").toString();
2517 final Calibration cal = layer.getParent().getCalibration();
2518 String title = new StringBuffer().append(layer.getParent().indexOf(layer) + 1).append('/').append(layer.getParent().size()).append(' ').append((null == layer.getTitle() ? "" : layer.getTitle())).append(scale).append(" -- ").append(getProject().toString()).append(' ').append(' ').append(Utils.cutNumber(layer.getParent().getLayerWidth() * cal.pixelWidth, 2, true)).append('x').append(Utils.cutNumber(layer.getParent().getLayerHeight() * cal.pixelHeight, 2, true)).append(' ').append(cal.getUnit()).toString();
2519 frame.setTitle(title);
2520 // fix the title for the FakeImageWindow and thus the WindowManager listing in the menus
2521 canvas.getFakeImagePlus().setTitle(title);
2524 /** If shift is down, scroll to the next non-empty layer; otherwise, if scroll_step is larger than 1, then scroll 'scroll_step' layers ahead; else just the next Layer. */
2525 public void nextLayer(final int modifiers) {
2526 //setLayer(layer.getParent().next(layer));
2527 //scroller.setValue(layer.getParent().getLayerIndex(layer.getId()));
2528 if (0 == (modifiers ^ Event.SHIFT_MASK)) {
2529 slt.set(layer.getParent().nextNonEmpty(layer));
2530 } else if (scroll_step > 1) {
2531 int i = layer.getParent().indexOf(this.layer);
2532 Layer la = layer.getParent().getLayer(i + scroll_step);
2533 if (null != la) slt.set(la);
2534 } else {
2535 slt.set(layer.getParent().next(layer));
2537 updateInDatabase("layer_id");
2540 /** If shift is down, scroll to the previous non-empty layer; otherwise, if scroll_step is larger than 1, then scroll 'scroll_step' layers backward; else just the previous Layer. */
2541 public void previousLayer(final int modifiers) {
2542 //setLayer(layer.getParent().previous(layer));
2543 //scroller.setValue(layer.getParent().getLayerIndex(layer.getId()));
2544 if (0 == (modifiers ^ Event.SHIFT_MASK)) {
2545 slt.set(layer.getParent().previousNonEmpty(layer));
2546 } else if (scroll_step > 1) {
2547 int i = layer.getParent().indexOf(this.layer);
2548 Layer la = layer.getParent().getLayer(i - scroll_step);
2549 if (null != la) slt.set(la);
2550 } else {
2551 slt.set(layer.getParent().previous(layer));
2553 updateInDatabase("layer_id");
2556 static public void updateLayerScroller(LayerSet set) {
2557 for (final Display d : al_displays) {
2558 if (d.layer.getParent() == set) {
2559 d.updateLayerScroller(d.layer);
2564 private void updateLayerScroller(Layer layer) {
2565 int size = layer.getParent().size();
2566 if (size <= 1) {
2567 scroller.setValues(0, 1, 0, 0);
2568 scroller.setEnabled(false);
2569 } else {
2570 scroller.setEnabled(true);
2571 scroller.setValues(layer.getParent().getLayerIndex(layer.getId()), 1, 0, size);
2575 private void updateSnapshots() {
2576 Enumeration<DisplayablePanel> e = ht_panels.elements();
2577 while (e.hasMoreElements()) {
2578 e.nextElement().remake();
2580 Utils.updateComponent(tabs.getSelectedComponent());
2583 static public void updatePanel(Layer layer, final Displayable displ) {
2584 if (null == layer && null != front) layer = front.layer; // the front layer
2585 for (final Display d : al_displays) {
2586 if (d.layer == layer) {
2587 d.updatePanel(displ);
2592 private void updatePanel(Displayable d) {
2593 JPanel c = null;
2594 if (d instanceof Profile) {
2595 c = panel_profiles;
2596 } else if (d instanceof Patch) {
2597 c = panel_patches;
2598 } else if (d instanceof DLabel) {
2599 c = panel_labels;
2600 } else if (d instanceof Pipe) {
2601 c = panel_zdispl;
2603 if (null == c) return;
2604 DisplayablePanel dp = ht_panels.get(d);
2605 dp.remake();
2606 Utils.updateComponent(c);
2609 static public void updatePanelIndex(final Layer layer, final Displayable displ) {
2610 for (final Display d : al_displays) {
2611 if (d.layer == layer || displ instanceof ZDisplayable) {
2612 d.updatePanelIndex(displ);
2617 private void updatePanelIndex(final Displayable d) {
2618 // find first of the kind, then remove and insert its panel
2619 int i = 0;
2620 JPanel c = null;
2621 if (d instanceof ZDisplayable) {
2622 i = layer.getParent().indexOf((ZDisplayable)d);
2623 c = panel_zdispl;
2624 } else {
2625 i = layer.relativeIndexOf(d);
2626 if (d instanceof Profile) {
2627 c = panel_profiles;
2628 } else if (d instanceof Patch) {
2629 c = panel_patches;
2630 } else if (d instanceof DLabel) {
2631 c = panel_labels;
2634 if (null == c) return;
2635 DisplayablePanel dp = ht_panels.get(d);
2636 if (null == dp) return; // may be half-baked, wait
2637 c.remove(dp);
2638 c.add(dp, i); // java and its fabulous consistency
2639 // not enough! Utils.updateComponent(c);
2640 // So, cocktail:
2641 c.invalidate();
2642 c.validate();
2643 Utils.updateComponent(c);
2646 /** Repair possibly missing panels and other components by simply resetting the same Layer */
2647 public void repairGUI() {
2648 Layer layer = this.layer;
2649 this.layer = null;
2650 setLayer(layer);
2653 public void actionPerformed(final ActionEvent ae) {
2654 dispatcher.exec(new Runnable() { public void run() {
2656 String command = ae.getActionCommand();
2657 if (command.startsWith("Job")) {
2658 if (Utils.checkYN("Really cancel job?")) {
2659 project.getLoader().quitJob(command);
2660 repairGUI();
2662 return;
2663 } else if (command.equals("Move to top")) {
2664 if (null == active) return;
2665 canvas.setUpdateGraphics(true);
2666 layer.getParent().move(LayerSet.TOP, active);
2667 Display.repaint(layer.getParent(), active, 5);
2668 //Display.updatePanelIndex(layer, active);
2669 } else if (command.equals("Move up")) {
2670 if (null == active) return;
2671 canvas.setUpdateGraphics(true);
2672 layer.getParent().move(LayerSet.UP, active);
2673 Display.repaint(layer.getParent(), active, 5);
2674 //Display.updatePanelIndex(layer, active);
2675 } else if (command.equals("Move down")) {
2676 if (null == active) return;
2677 canvas.setUpdateGraphics(true);
2678 layer.getParent().move(LayerSet.DOWN, active);
2679 Display.repaint(layer.getParent(), active, 5);
2680 //Display.updatePanelIndex(layer, active);
2681 } else if (command.equals("Move to bottom")) {
2682 if (null == active) return;
2683 canvas.setUpdateGraphics(true);
2684 layer.getParent().move(LayerSet.BOTTOM, active);
2685 Display.repaint(layer.getParent(), active, 5);
2686 //Display.updatePanelIndex(layer, active);
2687 } else if (command.equals("Duplicate, link and send to next layer")) {
2688 duplicateLinkAndSendTo(active, 1, layer.getParent().next(layer));
2689 } else if (command.equals("Duplicate, link and send to previous layer")) {
2690 duplicateLinkAndSendTo(active, 0, layer.getParent().previous(layer));
2691 } else if (command.equals("Duplicate, link and send to...")) {
2692 // fix non-scrolling popup menu
2693 GenericDialog gd = new GenericDialog("Send to");
2694 gd.addMessage("Duplicate, link and send to...");
2695 String[] sl = new String[layer.getParent().size()];
2696 int next = 0;
2697 for (Iterator it = layer.getParent().getLayers().iterator(); it.hasNext(); ) {
2698 sl[next++] = project.findLayerThing(it.next()).toString();
2700 gd.addChoice("Layer: ", sl, sl[layer.getParent().indexOf(layer)]);
2701 gd.showDialog();
2702 if (gd.wasCanceled()) return;
2703 Layer la = layer.getParent().getLayer(gd.getNextChoiceIndex());
2704 if (layer == la) {
2705 Utils.showMessage("Can't duplicate, link and send to the same layer.");
2706 return;
2708 duplicateLinkAndSendTo(active, 0, la);
2709 } else if (-1 != command.indexOf("z = ")) {
2710 // this is an item from the "Duplicate, link and send to" menu of layer z's
2711 Layer target_layer = layer.getParent().getLayer(Double.parseDouble(command.substring(command.lastIndexOf(' ') +1)));
2712 Utils.log2("layer: __" +command.substring(command.lastIndexOf(' ') +1) + "__");
2713 if (null == target_layer) return;
2714 duplicateLinkAndSendTo(active, 0, target_layer);
2715 } else if (-1 != command.indexOf("z=")) {
2716 // WARNING the indexOf is very similar to the previous one
2717 // Send the linked group to the selected layer
2718 int iz = command.indexOf("z=")+2;
2719 Utils.log2("iz=" + iz + " other: " + command.indexOf(' ', iz+2));
2720 int end = command.indexOf(' ', iz);
2721 if (-1 == end) end = command.length();
2722 double lz = Double.parseDouble(command.substring(iz, end));
2723 Layer target = layer.getParent().getLayer(lz);
2724 HashSet hs = active.getLinkedGroup(new HashSet());
2725 layer.getParent().move(hs, active.getLayer(), target);
2726 } else if (command.equals("Unlink")) {
2727 if (null == active || active instanceof Patch) return;
2728 active.unlink();
2729 updateSelection();//selection.update();
2730 } else if (command.equals("Unlink from images")) {
2731 if (null == active) return;
2732 try {
2733 for (Displayable displ: selection.getSelected()) {
2734 displ.unlinkAll(Patch.class);
2736 updateSelection();//selection.update();
2737 } catch (Exception e) { IJError.print(e); }
2738 } else if (command.equals("Unlink slices")) {
2739 YesNoCancelDialog yn = new YesNoCancelDialog(frame, "Attention", "Really unlink all slices from each other?\nThere is no undo.");
2740 if (!yn.yesPressed()) return;
2741 final ArrayList<Patch> pa = ((Patch)active).getStackPatches();
2742 for (int i=pa.size()-1; i>0; i--) {
2743 pa.get(i).unlink(pa.get(i-1));
2745 } else if (command.equals("Send to next layer")) {
2746 Rectangle box = selection.getBox();
2747 try {
2748 // unlink Patch instances
2749 for (final Displayable displ : selection.getSelected()) {
2750 displ.unlinkAll(Patch.class);
2752 updateSelection();//selection.update();
2753 } catch (Exception e) { IJError.print(e); }
2754 //layer.getParent().moveDown(layer, active); // will repaint whatever appropriate layers
2755 selection.moveDown();
2756 repaint(layer.getParent(), box);
2757 } else if (command.equals("Send to previous layer")) {
2758 Rectangle box = selection.getBox();
2759 try {
2760 // unlink Patch instances
2761 for (final Displayable displ : selection.getSelected()) {
2762 displ.unlinkAll(Patch.class);
2764 updateSelection();//selection.update();
2765 } catch (Exception e) { IJError.print(e); }
2766 //layer.getParent().moveUp(layer, active); // will repaint whatever appropriate layers
2767 selection.moveUp();
2768 repaint(layer.getParent(), box);
2769 } else if (command.equals("Show centered")) {
2770 if (active == null) return;
2771 showCentered(active);
2772 } else if (command.equals("Delete...")) {
2774 if (null != active) {
2775 Displayable d = active;
2776 selection.remove(d);
2777 d.remove(true); // will repaint
2780 // remove all selected objects
2781 selection.deleteAll();
2782 } else if (command.equals("Color...")) {
2783 IJ.doCommand("Color Picker...");
2784 } else if (command.equals("Revert")) {
2785 if (null == active || active.getClass() != Patch.class) return;
2786 Patch p = (Patch)active;
2787 if (!p.revert()) {
2788 if (null == p.getOriginalPath()) Utils.log("No editions to save for patch " + p.getTitle() + " #" + p.getId());
2789 else Utils.log("Could not revert Patch " + p.getTitle() + " #" + p.getId());
2791 } else if (command.equals("Undo")) {
2792 layer.getParent().undoOneStep();
2793 Display.repaint(layer.getParent());
2794 } else if (command.equals("Redo")) {
2795 layer.getParent().redoOneStep();
2796 Display.repaint(layer.getParent());
2797 } else if (command.equals("Transform")) {
2798 if (null == active) return;
2799 canvas.setTransforming(true);
2800 } else if (command.equals("Apply transform")) {
2801 if (null == active) return;
2802 canvas.setTransforming(false);
2803 } else if (command.equals("Cancel transform")) {
2804 if (null == active) return;
2805 canvas.cancelTransform();
2806 } else if (command.equals("Specify transform...")) {
2807 if (null == active) return;
2808 selection.specify();
2809 } else if (command.equals("Hide all but images")) {
2810 ArrayList<Class> type = new ArrayList<Class>();
2811 type.add(Patch.class);
2812 selection.removeAll(layer.getParent().hideExcept(type, false));
2813 Display.update(layer.getParent(), false);
2814 } else if (command.equals("Unhide all")) {
2815 layer.getParent().setAllVisible(false);
2816 Display.update(layer.getParent(), false);
2817 } else if (command.startsWith("Hide all ")) {
2818 String type = command.substring(9, command.length() -1); // skip the ending plural 's'
2819 type = type.substring(0, 1).toUpperCase() + type.substring(1);
2820 selection.removeAll(layer.getParent().setVisible(type, false, true));
2821 } else if (command.startsWith("Unhide all ")) {
2822 String type = command.substring(11, command.length() -1); // skip the ending plural 's'
2823 type = type.substring(0, 1).toUpperCase() + type.substring(1);
2824 layer.getParent().setVisible(type, true, true);
2825 } else if (command.equals("Hide deselected")) {
2826 hideDeselected(0 != (ActionEvent.ALT_MASK & ae.getModifiers()));
2827 } else if (command.equals("Hide deselected except images")) {
2828 hideDeselected(true);
2829 } else if (command.equals("Hide selected")) {
2830 selection.setVisible(false); // TODO should deselect them too? I don't think so.
2831 } else if (command.equals("Resize canvas/LayerSet...")) {
2832 resizeCanvas();
2833 } else if (command.equals("Autoresize canvas/LayerSet")) {
2834 layer.getParent().setMinimumDimensions();
2835 } else if (command.equals("Import image")) {
2836 importImage();
2837 } else if (command.equals("Import next image")) {
2838 importNextImage();
2839 } else if (command.equals("Import stack...")) {
2840 Display.this.getLayerSet().addLayerContentStep(layer);
2841 Rectangle sr = getCanvas().getSrcRect();
2842 Bureaucrat burro = project.getLoader().importStack(layer, sr.x + sr.width/2, sr.y + sr.height/2, null, true, null);
2843 burro.addPostTask(new Runnable() { public void run() {
2844 Display.this.getLayerSet().addLayerContentStep(layer);
2845 }});
2846 } else if (command.equals("Import grid...")) {
2847 Display.this.getLayerSet().addLayerContentStep(layer);
2848 Bureaucrat burro = project.getLoader().importGrid(layer);
2849 burro.addPostTask(new Runnable() { public void run() {
2850 Display.this.getLayerSet().addLayerContentStep(layer);
2851 }});
2852 } else if (command.equals("Import sequence as grid...")) {
2853 Display.this.getLayerSet().addLayerContentStep(layer);
2854 Bureaucrat burro = project.getLoader().importSequenceAsGrid(layer);
2855 burro.addPostTask(new Runnable() { public void run() {
2856 Display.this.getLayerSet().addLayerContentStep(layer);
2857 }});
2858 } else if (command.equals("Import from text file...")) {
2859 Display.this.getLayerSet().addLayerContentStep(layer);
2860 Bureaucrat burro = project.getLoader().importImages(layer);
2861 burro.addPostTask(new Runnable() { public void run() {
2862 Display.this.getLayerSet().addLayerContentStep(layer);
2863 }});
2864 } else if (command.equals("Import labels as arealists...")) {
2865 Display.this.getLayerSet().addChangeTreesStep();
2866 Bureaucrat burro = project.getLoader().importLabelsAsAreaLists(layer, null, Double.MAX_VALUE, 0, 0.4f, false);
2867 burro.addPostTask(new Runnable() { public void run() {
2868 Display.this.getLayerSet().addChangeTreesStep();
2869 }});
2870 } else if (command.equals("Make flat image...")) {
2871 // if there's a ROI, just use that as cropping rectangle
2872 Rectangle srcRect = null;
2873 Roi roi = canvas.getFakeImagePlus().getRoi();
2874 if (null != roi) {
2875 srcRect = roi.getBounds();
2876 } else {
2877 // otherwise, whatever is visible
2878 //srcRect = canvas.getSrcRect();
2879 // The above is confusing. That is what ROIs are for. So paint all:
2880 srcRect = new Rectangle(0, 0, (int)Math.ceil(layer.getParent().getLayerWidth()), (int)Math.ceil(layer.getParent().getLayerHeight()));
2882 double scale = 1.0;
2883 final String[] types = new String[]{"8-bit grayscale", "RGB Color"};
2884 int the_type = ImagePlus.GRAY8;
2885 final GenericDialog gd = new GenericDialog("Choose", frame);
2886 gd.addSlider("Scale: ", 1, 100, 100);
2887 gd.addChoice("Type: ", types, types[0]);
2888 if (layer.getParent().size() > 1) {
2890 String[] layers = new String[layer.getParent().size()];
2891 int i = 0;
2892 for (Iterator it = layer.getParent().getLayers().iterator(); it.hasNext(); ) {
2893 layers[i] = layer.getProject().findLayerThing((Layer)it.next()).toString();
2894 i++;
2896 int i_layer = layer.getParent().indexOf(layer);
2897 gd.addChoice("Start: ", layers, layers[i_layer]);
2898 gd.addChoice("End: ", layers, layers[i_layer]);
2900 Utils.addLayerRangeChoices(Display.this.layer, gd); /// $#%! where are my lisp macros
2901 gd.addCheckbox("Include non-empty layers only", true);
2903 gd.addMessage("Background color:");
2904 Utils.addRGBColorSliders(gd, Color.black);
2905 gd.addCheckbox("Best quality", false);
2906 gd.addMessage("");
2907 gd.addCheckbox("Save to file", false);
2908 gd.addCheckbox("Save for web", false);
2909 gd.showDialog();
2910 if (gd.wasCanceled()) return;
2911 scale = gd.getNextNumber() / 100;
2912 the_type = (0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY8 : ImagePlus.COLOR_RGB);
2913 if (Double.isNaN(scale) || scale <= 0.0) {
2914 Utils.showMessage("Invalid scale.");
2915 return;
2917 Layer[] layer_array = null;
2918 boolean non_empty_only = false;
2919 if (layer.getParent().size() > 1) {
2920 non_empty_only = gd.getNextBoolean();
2921 int i_start = gd.getNextChoiceIndex();
2922 int i_end = gd.getNextChoiceIndex();
2923 ArrayList al = new ArrayList();
2924 ArrayList al_zd = layer.getParent().getZDisplayables();
2925 ZDisplayable[] zd = new ZDisplayable[al_zd.size()];
2926 al_zd.toArray(zd);
2927 for (int i=i_start, j=0; i <= i_end; i++, j++) {
2928 Layer la = layer.getParent().getLayer(i);
2929 if (!la.isEmpty() || !non_empty_only) al.add(la); // checks both the Layer and the ZDisplayable objects in the parent LayerSet
2931 if (0 == al.size()) {
2932 Utils.showMessage("All layers are empty!");
2933 return;
2935 layer_array = new Layer[al.size()];
2936 al.toArray(layer_array);
2937 } else {
2938 layer_array = new Layer[]{Display.this.layer};
2940 final Color background = new Color((int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
2941 final boolean quality = gd.getNextBoolean();
2942 final boolean save_to_file = gd.getNextBoolean();
2943 final boolean save_for_web = gd.getNextBoolean();
2944 // in its own thread
2945 if (save_for_web) project.getLoader().makePrescaledTiles(layer_array, Patch.class, srcRect, scale, c_alphas, the_type);
2946 else project.getLoader().makeFlatImage(layer_array, srcRect, scale, c_alphas, the_type, save_to_file, quality, background);
2948 } else if (command.equals("Lock")) {
2949 selection.setLocked(true);
2950 } else if (command.equals("Unlock")) {
2951 selection.setLocked(false);
2952 } else if (command.equals("Properties...")) {
2953 active.adjustProperties();
2954 updateSelection();
2955 } else if (command.equals("Cancel alignment")) {
2956 layer.getParent().cancelAlign();
2957 } else if (command.equals("Align with landmarks")) {
2958 layer.getParent().applyAlign(false);
2959 } else if (command.equals("Align and register")) {
2960 layer.getParent().applyAlign(true);
2961 } else if (command.equals("Align using profiles")) {
2962 if (!selection.contains(Profile.class)) {
2963 Utils.showMessage("No profiles are selected.");
2964 return;
2966 // ask for range of layers
2967 final GenericDialog gd = new GenericDialog("Choose range");
2968 Utils.addLayerRangeChoices(Display.this.layer, gd);
2969 gd.showDialog();
2970 if (gd.wasCanceled()) return;
2971 Layer la_start = layer.getParent().getLayer(gd.getNextChoiceIndex());
2972 Layer la_end = layer.getParent().getLayer(gd.getNextChoiceIndex());
2973 if (la_start == la_end) {
2974 Utils.showMessage("Need at least two layers.");
2975 return;
2977 if (selection.isLocked()) {
2978 Utils.showMessage("There are locked objects.");
2979 return;
2981 layer.getParent().startAlign(Display.this);
2982 layer.getParent().applyAlign(la_start, la_end, selection);
2983 } else if (command.equals("Align stack slices")) {
2984 if (getActive() instanceof Patch) {
2985 final Patch slice = (Patch)getActive();
2986 if (slice.isStack()) {
2987 // check linked group
2988 final HashSet hs = slice.getLinkedGroup(new HashSet());
2989 for (Iterator it = hs.iterator(); it.hasNext(); ) {
2990 if (it.next().getClass() != Patch.class) {
2991 Utils.showMessage("Images are linked to other objects, can't proceed to cross-correlate them."); // labels should be fine, need to check that
2992 return;
2995 final LayerSet ls = slice.getLayerSet();
2996 final HashSet<Displayable> linked = slice.getLinkedGroup(null);
2997 ls.addTransformStep(linked);
2998 Bureaucrat burro = Registration.registerStackSlices((Patch)getActive()); // will repaint
2999 burro.addPostTask(new Runnable() { public void run() {
3000 // The current state when done
3001 ls.addTransformStep(linked);
3002 }});
3003 } else {
3004 Utils.log("Align stack slices: selected image is not part of a stack.");
3007 } else if (command.equals("Align layers (layer-wise)")) {
3008 final Layer la = layer;
3009 la.getParent().addTransformStep(la);
3010 Bureaucrat burro = AlignTask.alignLayersLinearlyTask( layer );
3011 burro.addPostTask(new Runnable() { public void run() {
3012 la.getParent().addTransformStep(la);
3013 }});
3014 } else if (command.equals("Align layers (tile-wise global minimization)")) {
3015 final Layer la = layer; // caching, since scroll wheel may change it
3016 la.getParent().addTransformStep();
3017 Bureaucrat burro = Registration.registerLayers(la, Registration.GLOBAL_MINIMIZATION);
3018 burro.addPostTask(new Runnable() { public void run() {
3019 la.getParent().addTransformStep();
3020 }});
3021 } else if (command.equals("Properties ...")) { // NOTE the space before the dots, to distinguish from the "Properties..." command that works on Displayable objects.
3022 GenericDialog gd = new GenericDialog("Properties", Display.this.frame);
3023 //gd.addNumericField("layer_scroll_step: ", this.scroll_step, 0);
3024 gd.addSlider("layer_scroll_step: ", 1, layer.getParent().size(), Display.this.scroll_step);
3025 gd.addChoice("snapshots_mode", LayerSet.snapshot_modes, LayerSet.snapshot_modes[layer.getParent().getSnapshotsMode()]);
3026 gd.addCheckbox("prefer_snapshots_quality", layer.getParent().snapshotsQuality());
3027 Loader lo = getProject().getLoader();
3028 boolean using_mipmaps = lo.isMipMapsEnabled();
3029 gd.addCheckbox("enable_mipmaps", using_mipmaps);
3030 String preprocessor = project.getLoader().getPreprocessor();
3031 gd.addStringField("image_preprocessor: ", null == preprocessor ? "" : preprocessor);
3032 gd.addCheckbox("enable_layer_pixels virtualization", layer.getParent().isPixelsVirtualizationEnabled());
3033 double max = layer.getParent().getLayerWidth() < layer.getParent().getLayerHeight() ? layer.getParent().getLayerWidth() : layer.getParent().getLayerHeight();
3034 gd.addSlider("max_dimension of virtualized layer pixels: ", 0, max, layer.getParent().getPixelsMaxDimension());
3035 // --------
3036 gd.showDialog();
3037 if (gd.wasCanceled()) return;
3038 // --------
3039 int sc = (int) gd.getNextNumber();
3040 if (sc < 1) sc = 1;
3041 Display.this.scroll_step = sc;
3042 updateInDatabase("scroll_step");
3044 layer.getParent().setSnapshotsMode(gd.getNextChoiceIndex());
3045 layer.getParent().setSnapshotsQuality(gd.getNextBoolean());
3047 boolean generate_mipmaps = gd.getNextBoolean();
3048 if (using_mipmaps && generate_mipmaps) {
3049 // nothing changed
3050 } else {
3051 if (using_mipmaps) { // and !generate_mipmaps
3052 lo.flushMipMaps(true);
3053 } else {
3054 // not using mipmaps before, and true == generate_mipmaps
3055 lo.generateMipMaps(layer.getParent().getDisplayables(Patch.class));
3059 final String prepro = gd.getNextString();
3060 if (!project.getLoader().setPreprocessor(prepro.trim())) {
3061 Utils.showMessage("Could NOT set the preprocessor to " + prepro);
3064 layer.getParent().setPixelsVirtualizationEnabled(gd.getNextBoolean());
3065 layer.getParent().setPixelsMaxDimension((int)gd.getNextNumber());
3066 } else if (command.equals("Search...")) {
3067 new Search();
3068 } else if (command.equals("Select all")) {
3069 selection.selectAll();
3070 repaint(Display.this.layer, selection.getBox(), 0);
3071 } else if (command.equals("Select none")) {
3072 Rectangle box = selection.getBox();
3073 selection.clear();
3074 repaint(Display.this.layer, box, 0);
3075 } else if (command.equals("Restore selection")) {
3076 selection.restore();
3077 } else if (command.equals("Select under ROI")) {
3078 Roi roi = canvas.getFakeImagePlus().getRoi();
3079 if (null == roi) return;
3080 selection.selectAll(roi, true);
3081 } else if (command.equals("Merge")) {
3082 ArrayList al_sel = selection.getSelected();
3083 // put active at the beginning, to work as the base on which other's will get merged
3084 al_sel.remove(Display.this.active);
3085 al_sel.add(0, Display.this.active);
3086 AreaList ali = AreaList.merge(al_sel);
3087 if (null != ali) {
3088 // remove all but the first from the selection
3089 for (int i=1; i<al_sel.size(); i++) {
3090 Object ob = al_sel.get(i);
3091 if (ob.getClass() == AreaList.class) {
3092 selection.remove((Displayable)ob);
3095 selection.updateTransform(ali);
3096 repaint(ali.getLayerSet(), ali, 0);
3098 } else if (command.equals("Identify...")) {
3099 // for pipes only for now
3100 if (!(active instanceof Pipe)) return;
3101 ini.trakem2.vector.Compare.findSimilar((Pipe)active);
3102 } else if (command.equals("Identify with axes...")) {
3103 if (!(active instanceof Pipe)) return;
3104 if (Project.getProjects().size() < 2) {
3105 Utils.showMessage("You need at least two projects open:\n-A reference project\n-The current project with the pipe to identify");
3106 return;
3108 ini.trakem2.vector.Compare.findSimilarWithAxes((Pipe)active);
3109 } else if (command.equals("View orthoslices")) {
3110 if (!(active instanceof Patch)) return;
3111 Display3D.showOrthoslices(((Patch)active));
3112 } else if (command.equals("View volume")) {
3113 if (!(active instanceof Patch)) return;
3114 Display3D.showVolume(((Patch)active));
3115 } else if (command.equals("Show in 3D")) {
3116 for (Iterator it = selection.getSelected(ZDisplayable.class).iterator(); it.hasNext(); ) {
3117 ZDisplayable zd = (ZDisplayable)it.next();
3118 Display3D.show(zd.getProject().findProjectThing(zd));
3120 // handle profile lists ...
3121 HashSet hs = new HashSet();
3122 for (Iterator it = selection.getSelected(Profile.class).iterator(); it.hasNext(); ) {
3123 Displayable d = (Displayable)it.next();
3124 ProjectThing profile_list = (ProjectThing)d.getProject().findProjectThing(d).getParent();
3125 if (!hs.contains(profile_list)) {
3126 Display3D.show(profile_list);
3127 hs.add(profile_list);
3130 } else if (command.equals("Snap")) {
3131 if (!(active instanceof Patch)) return;
3132 StitchingTEM.snap(getActive(), Display.this);
3133 } else if (command.equals("Montage")) {
3134 if (!(active instanceof Patch)) {
3135 Utils.showMessage("Please select only images.");
3136 return;
3138 final Set<Displayable> affected = new HashSet<Displayable>(selection.getAffected());
3139 for (final Displayable d : affected)
3140 if (d.isLinked()) {
3141 Utils.showMessage( "You cannot montage linked objects." );
3142 return;
3144 // make an undo step!
3145 final LayerSet ls = layer.getParent();
3146 ls.addTransformStep(affected);
3147 Bureaucrat burro = AlignTask.alignSelectionTask( selection );
3148 burro.addPostTask(new Runnable() { public void run() {
3149 ls.addTransformStep(affected);
3150 }});
3151 } else if (command.equals("Lens correction")) {
3152 DistortionCorrectionTask.correctDistortionFromSelection( selection );
3153 } else if (command.equals("Link images...")) {
3154 GenericDialog gd = new GenericDialog("Options");
3155 gd.addMessage("Linking images to images (within their own layer only):");
3156 String[] options = {"all images to all images", "each image with any other overlapping image"};
3157 gd.addChoice("Link: ", options, options[1]);
3158 String[] options2 = {"selected images only", "all images in this layer", "all images in all layers"};
3159 gd.addChoice("Apply to: ", options2, options2[0]);
3160 gd.showDialog();
3161 if (gd.wasCanceled()) return;
3162 boolean overlapping_only = 1 == gd.getNextChoiceIndex();
3163 switch (gd.getNextChoiceIndex()) {
3164 case 0:
3165 Patch.crosslink(selection.getSelected(Patch.class), overlapping_only);
3166 break;
3167 case 1:
3168 Patch.crosslink(layer.getDisplayables(Patch.class), overlapping_only);
3169 break;
3170 case 2:
3171 for (final Layer la : layer.getParent().getLayers()) {
3172 Patch.crosslink(la.getDisplayables(Patch.class), overlapping_only);
3174 break;
3176 } else if (command.equals("Enhance contrast (selected images)...")) {
3177 ArrayList al = selection.getSelected(Patch.class);
3178 getProject().getLoader().homogenizeContrast(al);
3179 } else if (command.equals("Enhance contrast layer-wise...")) {
3180 // ask for range of layers
3181 final GenericDialog gd = new GenericDialog("Choose range");
3182 Utils.addLayerRangeChoices(Display.this.layer, gd);
3183 gd.showDialog();
3184 if (gd.wasCanceled()) return;
3185 java.util.List list = layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1); // exclusive end
3186 Layer[] la = new Layer[list.size()];
3187 list.toArray(la);
3188 project.getLoader().homogenizeContrast(la);
3189 } else if (command.equals("Set Min and Max layer-wise...")) {
3190 Displayable active = getActive();
3191 double min = 0;
3192 double max = 0;
3193 if (null != active && active.getClass() == Patch.class) {
3194 min = ((Patch)active).getMin();
3195 max = ((Patch)active).getMax();
3197 final GenericDialog gd = new GenericDialog("Min and Max");
3198 gd.addMessage("Set min and max to all images in the layer range");
3199 Utils.addLayerRangeChoices(Display.this.layer, gd);
3200 gd.addNumericField("min: ", min, 2);
3201 gd.addNumericField("max: ", max, 2);
3202 gd.showDialog();
3203 if (gd.wasCanceled()) return;
3205 min = gd.getNextNumber();
3206 max = gd.getNextNumber();
3207 ArrayList<Displayable> al = new ArrayList<Displayable>();
3208 for (final Layer la : layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1)) { // exclusive end
3209 al.addAll(la.getDisplayables(Patch.class));
3211 project.getLoader().setMinAndMax(al, min, max);
3212 } else if (command.equals("Set Min and Max (selected images)...")) {
3213 Displayable active = getActive();
3214 double min = 0;
3215 double max = 0;
3216 if (null != active && active.getClass() == Patch.class) {
3217 min = ((Patch)active).getMin();
3218 max = ((Patch)active).getMax();
3220 final GenericDialog gd = new GenericDialog("Min and Max");
3221 gd.addMessage("Set min and max to all selected images");
3222 gd.addNumericField("min: ", min, 2);
3223 gd.addNumericField("max: ", max, 2);
3224 gd.showDialog();
3225 if (gd.wasCanceled()) return;
3227 min = gd.getNextNumber();
3228 max = gd.getNextNumber();
3229 project.getLoader().setMinAndMax(selection.getSelected(Patch.class), min, max);
3230 } else if (command.equals("Enhance contrast (selected images)...")) {
3231 Utils.log("Not implemented yet");
3232 } else if (command.equals("Enhance contrast layer-wise...")) {
3233 Utils.log("Not implemented yet");
3234 } else if (command.equals("Create subproject")) {
3235 Roi roi = canvas.getFakeImagePlus().getRoi();
3236 if (null == roi) return; // the menu item is not active unless there is a ROI
3237 Layer first, last;
3238 if (1 == layer.getParent().size()) {
3239 first = last = layer;
3240 } else {
3241 GenericDialog gd = new GenericDialog("Choose layer range");
3242 Utils.addLayerRangeChoices(layer, gd);
3243 gd.showDialog();
3244 if (gd.wasCanceled()) return;
3245 first = layer.getParent().getLayer(gd.getNextChoiceIndex());
3246 last = layer.getParent().getLayer(gd.getNextChoiceIndex());
3247 Utils.log2("first, last: " + first + ", " + last);
3249 Project sub = getProject().createSubproject(roi.getBounds(), first, last);
3250 final LayerSet subls = sub.getRootLayerSet();
3251 final Display d = new Display(sub, subls.getLayer(0));
3252 SwingUtilities.invokeLater(new Runnable() { public void run() {
3253 d.canvas.showCentered(new Rectangle(0, 0, (int)subls.getLayerWidth(), (int)subls.getLayerHeight()));
3254 }});
3255 } else if (command.startsWith("Arealists as labels")) {
3256 GenericDialog gd = new GenericDialog("Export labels");
3257 gd.addSlider("Scale: ", 1, 100, 100);
3258 final String[] options = {"All area list", "Selected area lists"};
3259 gd.addChoice("Export: ", options, options[0]);
3260 Utils.addLayerRangeChoices(layer, gd);
3261 gd.addCheckbox("Visible only", true);
3262 gd.showDialog();
3263 if (gd.wasCanceled()) return;
3264 final float scale = (float)(gd.getNextNumber() / 100);
3265 java.util.List al = 0 == gd.getNextChoiceIndex() ? layer.getParent().getZDisplayables(AreaList.class) : selection.getSelected(AreaList.class);
3266 if (null == al) {
3267 Utils.log("No area lists found to export.");
3268 return;
3270 // Generics are ... a pain? I don't understand them? They fail when they shouldn't? And so easy to workaround that they are a shame?
3271 al = (java.util.List<Displayable>) al;
3273 int first = gd.getNextChoiceIndex();
3274 int last = gd.getNextChoiceIndex();
3275 boolean visible_only = gd.getNextBoolean();
3276 if (-1 != command.indexOf("(amira)")) {
3277 AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, true, true);
3278 } else if (-1 != command.indexOf("(tif)")) {
3279 AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, false, false);
3281 } else if (command.equals("Project properties...")) {
3282 project.adjustProperties();
3283 } else if (command.equals("Release memory...")) {
3284 Bureaucrat.createAndStart(new Worker("Releasing memory") {
3285 public void run() {
3286 startedWorking();
3287 try {
3288 GenericDialog gd = new GenericDialog("Release Memory");
3289 int max = (int)(IJ.maxMemory() / 1000000);
3290 gd.addSlider("Megabytes: ", 0, max, max/2);
3291 gd.showDialog();
3292 if (!gd.wasCanceled()) {
3293 int n_mb = (int)gd.getNextNumber();
3294 project.getLoader().releaseToFit((long)n_mb*1000000);
3296 } catch (Throwable e) {
3297 IJError.print(e);
3298 } finally {
3299 finishedWorking();
3302 }, project);
3303 } else if (command.equals("Flush image cache")) {
3304 Loader.releaseAllCaches();
3305 } else {
3306 Utils.log2("Display: don't know what to do with command " + command);
3308 }});
3311 /** Update in all displays the Transform for the given Displayable if it's selected. */
3312 static public void updateTransform(final Displayable displ) {
3313 for (final Display d : al_displays) {
3314 if (d.selection.contains(displ)) d.selection.updateTransform(displ);
3318 /** Order the profiles of the parent profile_list by Z order, and fix the ProjectTree.*/
3320 private void fixZOrdering(Profile profile) {
3321 ProjectThing thing = project.findProjectThing(profile);
3322 if (null == thing) {
3323 Utils.log2("Display.fixZOrdering: null thing?");
3324 return;
3326 ((ProjectThing)thing.getParent()).fixZOrdering();
3327 project.getProjectTree().updateList(thing.getParent());
3331 /** The number of layers to scroll through with the wheel; 1 by default.*/
3332 public int getScrollStep() { return this.scroll_step; }
3334 public void setScrollStep(int scroll_step) {
3335 if (scroll_step < 1) scroll_step = 1;
3336 this.scroll_step = scroll_step;
3337 updateInDatabase("scroll_step");
3340 protected Bureaucrat importImage() {
3341 Worker worker = new Worker("Import image") { /// all this verbosity is what happens when functions are not first class citizens. I could abstract it away by passing a string name "importImage" and invoking it with reflection, but that is an even bigger PAIN
3342 public void run() {
3343 startedWorking();
3344 try {
3347 Rectangle srcRect = canvas.getSrcRect();
3348 int x = srcRect.x + srcRect.width / 2;
3349 int y = srcRect.y + srcRect.height/ 2;
3350 Patch p = project.getLoader().importImage(project, x, y);
3351 if (null == p) {
3352 finishedWorking();
3353 Utils.showMessage("Could not open the image.");
3354 return;
3357 Display.this.getLayerSet().addLayerContentStep(layer);
3359 layer.add(p); // will add it to the proper Displays
3361 Display.this.getLayerSet().addLayerContentStep(layer);
3364 } catch (Exception e) {
3365 IJError.print(e);
3367 finishedWorking();
3370 return Bureaucrat.createAndStart(worker, getProject());
3373 protected Bureaucrat importNextImage() {
3374 Worker worker = new Worker("Import image") { /// all this verbosity is what happens when functions are not first class citizens. I could abstract it away by passing a string name "importImage" and invoking it with reflection, but that is an even bigger PAIN
3375 public void run() {
3376 startedWorking();
3377 try {
3379 Rectangle srcRect = canvas.getSrcRect();
3380 int x = srcRect.x + srcRect.width / 2;// - imp.getWidth() / 2;
3381 int y = srcRect.y + srcRect.height/ 2;// - imp.getHeight()/ 2;
3382 Patch p = project.getLoader().importNextImage(project, x, y);
3383 if (null == p) {
3384 Utils.showMessage("Could not open next image.");
3385 finishedWorking();
3386 return;
3389 Display.this.getLayerSet().addLayerContentStep(layer);
3391 layer.add(p); // will add it to the proper Displays
3393 Display.this.getLayerSet().addLayerContentStep(layer);
3395 } catch (Exception e) {
3396 IJError.print(e);
3398 finishedWorking();
3401 return Bureaucrat.createAndStart(worker, getProject());
3405 /** Make the given channel have the given alpha (transparency). */
3406 public void setChannel(int c, float alpha) {
3407 int a = (int)(255 * alpha);
3408 int l = (c_alphas&0xff000000)>>24;
3409 int r = (c_alphas&0xff0000)>>16;
3410 int g = (c_alphas&0xff00)>>8;
3411 int b = c_alphas&0xff;
3412 switch (c) {
3413 case Channel.MONO:
3414 // all to the given alpha
3415 c_alphas = (l<<24) + (r<<16) + (g<<8) + b; // parenthesis are NECESSARY
3416 break;
3417 case Channel.RED:
3418 // modify only the red
3419 c_alphas = (l<<24) + (a<<16) + (g<<8) + b;
3420 break;
3421 case Channel.GREEN:
3422 c_alphas = (l<<24) + (r<<16) + (a<<8) + b;
3423 break;
3424 case Channel.BLUE:
3425 c_alphas = (l<<24) + (r<<16) + (g<<8) + a;
3426 break;
3428 //Utils.log2("c_alphas: " + c_alphas);
3429 //canvas.setUpdateGraphics(true);
3430 canvas.repaint(true);
3431 updateInDatabase("c_alphas");
3434 /** Set the channel as active and the others as inactive. */
3435 public void setActiveChannel(Channel channel) {
3436 for (int i=0; i<4; i++) {
3437 if (channel != channels[i]) channels[i].setActive(false);
3438 else channel.setActive(true);
3440 Utils.updateComponent(panel_channels);
3441 transp_slider.setValue((int)(channel.getAlpha() * 100));
3444 public int getDisplayChannelAlphas() { return c_alphas; }
3446 // rename this method and the getDisplayChannelAlphas ! They sound the same!
3447 public int getChannelAlphas() {
3448 return ((int)(channels[0].getAlpha() * 255)<<24) + ((int)(channels[1].getAlpha() * 255)<<16) + ((int)(channels[2].getAlpha() * 255)<<8) + (int)(channels[3].getAlpha() * 255);
3451 public int getChannelAlphasState() {
3452 return ((channels[0].isSelected() ? 255 : 0)<<24)
3453 + ((channels[1].isSelected() ? 255 : 0)<<16)
3454 + ((channels[2].isSelected() ? 255 : 0)<<8)
3455 + (channels[3].isSelected() ? 255 : 0);
3458 /** Show the layer in the front Display, or in a new Display if the front Display is showing a layer from a different LayerSet. */
3459 static public void showFront(final Layer layer) {
3460 Display display = front;
3461 if (null == display || display.layer.getParent() != layer.getParent()) {
3462 display = new Display(layer.getProject(), layer, null); // gets set to front
3463 } else {
3464 display.setLayer(layer);
3468 /** Show the given Displayable centered and selected. If select is false, the selection is cleared. */
3469 static public void showCentered(Layer layer, Displayable displ, boolean select, boolean shift_down) {
3470 // see if the given layer belongs to the layer set being displayed
3471 Display display = front; // to ensure thread consistency to some extent
3472 if (null == display || display.layer.getParent() != layer.getParent()) {
3473 display = new Display(layer.getProject(), layer, displ); // gets set to front
3474 } else if (display.layer != layer) {
3475 display.setLayer(layer);
3477 if (select) {
3478 if (!shift_down) display.selection.clear();
3479 display.selection.add(displ);
3480 } else {
3481 display.selection.clear();
3483 display.showCentered(displ);
3486 private final void showCentered(final Displayable displ) {
3487 if (null == displ) return;
3488 SwingUtilities.invokeLater(new Runnable() { public void run() {
3489 displ.setVisible(true);
3490 Rectangle box = displ.getBoundingBox();
3491 if (0 == box.width || 0 == box.height) {
3492 box.width = (int)layer.getLayerWidth();
3493 box.height = (int)layer.getLayerHeight();
3495 canvas.showCentered(box);
3496 scrollToShow(displ);
3497 if (displ instanceof ZDisplayable) {
3498 // scroll to first layer that has a point
3499 ZDisplayable zd = (ZDisplayable)displ;
3500 setLayer(zd.getFirstLayer());
3502 }});
3505 /** Listen to interesting updates, such as the ColorPicker and updates to Patch objects. */
3506 public void imageUpdated(ImagePlus updated) {
3507 // detect ColorPicker WARNING this will work even if the Display is not the window immediately active under the color picker.
3508 if (this == front && updated instanceof ij.plugin.ColorPicker) {
3509 if (null != active && project.isInputEnabled()) {
3510 selection.setColor(Toolbar.getForegroundColor());
3511 Display.repaint(front.layer, selection.getBox(), 0);
3513 return;
3515 // $%#@!! LUT changes don't set the image as changed
3516 //if (updated instanceof PatchStack) {
3517 // updated.changes = 1
3520 //Utils.log2("imageUpdated: " + updated + " " + updated.getClass());
3522 /* // never gets called (?)
3523 // the above is overkill. Instead:
3524 if (updated instanceof PatchStack) {
3525 Patch p = ((PatchStack)updated).getCurrentPatch();
3526 ImageProcessor ip = updated.getProcessor();
3527 p.setMinAndMax(ip.getMin(), ip.getMax());
3528 Utils.log2("setting min and max: " + ip.getMin() + ", " + ip.getMax());
3529 project.getLoader().decacheAWT(p.getId()); // including level 0, which will be editable
3530 // on repaint, it will be recreated
3531 //((PatchStack)updated).decacheAll(); // so that it will repaint with a newly created image
3535 // detect LUT changes: DONE at PatchStack, which is the active (virtual) image
3536 //Utils.log2("calling decache for " + updated);
3537 //getProject().getLoader().decache(updated);
3540 public void imageClosed(ImagePlus imp) {}
3541 public void imageOpened(ImagePlus imp) {}
3543 /** Release memory captured by the offscreen images */
3544 static public void flushAll() {
3545 for (final Display d : al_displays) {
3546 d.canvas.flush();
3548 //System.gc();
3549 Thread.yield();
3552 /** Can be null. */
3553 static public Display getFront() {
3554 return front;
3557 static public void setCursorToAll(final Cursor c) {
3558 for (final Display d : al_displays) {
3559 d.frame.setCursor(c);
3563 protected void setCursor(Cursor c) {
3564 frame.setCursor(c);
3567 /** Used by the Displayable to update the visibility checkbox in other Displays. */
3568 static protected void updateVisibilityCheckbox(final Layer layer, final Displayable displ, final Display calling_display) {
3569 //LOCKS ALL //SwingUtilities.invokeLater(new Runnable() { public void run() {
3570 for (final Display d : al_displays) {
3571 if (d == calling_display) continue;
3572 if (d.layer.contains(displ) || (displ instanceof ZDisplayable && d.layer.getParent().contains((ZDisplayable)displ))) {
3573 DisplayablePanel dp = d.ht_panels.get(displ);
3574 if (null != dp) dp.updateVisibilityCheckbox();
3577 //}});
3580 protected boolean isActiveWindow() {
3581 return frame.isActive();
3584 /** Toggle user input; pan and zoom are always enabled though.*/
3585 static public void setReceivesInput(final Project project, final boolean b) {
3586 for (final Display d : al_displays) {
3587 if (d.project == project) d.canvas.setReceivesInput(b);
3591 /** Export the DTD that defines this object. */
3592 static public void exportDTD(StringBuffer sb_header, HashSet hs, String indent) {
3593 if (hs.contains("t2_display")) return; // TODO to avoid collisions the type shoud be in a namespace such as tm2:display
3594 hs.add("t2_display");
3595 sb_header.append(indent).append("<!ELEMENT t2_display EMPTY>\n")
3596 .append(indent).append("<!ATTLIST t2_display id NMTOKEN #REQUIRED>\n")
3597 .append(indent).append("<!ATTLIST t2_display layer_id NMTOKEN #REQUIRED>\n")
3598 .append(indent).append("<!ATTLIST t2_display x NMTOKEN #REQUIRED>\n")
3599 .append(indent).append("<!ATTLIST t2_display y NMTOKEN #REQUIRED>\n")
3600 .append(indent).append("<!ATTLIST t2_display magnification NMTOKEN #REQUIRED>\n")
3601 .append(indent).append("<!ATTLIST t2_display srcrect_x NMTOKEN #REQUIRED>\n")
3602 .append(indent).append("<!ATTLIST t2_display srcrect_y NMTOKEN #REQUIRED>\n")
3603 .append(indent).append("<!ATTLIST t2_display srcrect_width NMTOKEN #REQUIRED>\n")
3604 .append(indent).append("<!ATTLIST t2_display srcrect_height NMTOKEN #REQUIRED>\n")
3605 .append(indent).append("<!ATTLIST t2_display scroll_step NMTOKEN #REQUIRED>\n")
3606 .append(indent).append("<!ATTLIST t2_display c_alphas NMTOKEN #REQUIRED>\n")
3607 .append(indent).append("<!ATTLIST t2_display c_alphas_state NMTOKEN #REQUIRED>\n")
3610 /** Export all displays of the given project as XML entries. */
3611 static public void exportXML(final Project project, final Writer writer, final String indent, final Object any) throws Exception {
3612 final StringBuffer sb_body = new StringBuffer();
3613 final String in = indent + "\t";
3614 for (final Display d : al_displays) {
3615 if (d.project != project) continue;
3616 final Rectangle r = d.frame.getBounds();
3617 final Rectangle srcRect = d.canvas.getSrcRect();
3618 final double magnification = d.canvas.getMagnification();
3619 sb_body.append(indent).append("<t2_display id=\"").append(d.id).append("\"\n")
3620 .append(in).append("layer_id=\"").append(d.layer.getId()).append("\"\n")
3621 .append(in).append("c_alphas=\"").append(d.c_alphas).append("\"\n")
3622 .append(in).append("c_alphas_state=\"").append(d.getChannelAlphasState()).append("\"\n")
3623 .append(in).append("x=\"").append(r.x).append("\"\n")
3624 .append(in).append("y=\"").append(r.y).append("\"\n")
3625 .append(in).append("magnification=\"").append(magnification).append("\"\n")
3626 .append(in).append("srcrect_x=\"").append(srcRect.x).append("\"\n")
3627 .append(in).append("srcrect_y=\"").append(srcRect.y).append("\"\n")
3628 .append(in).append("srcrect_width=\"").append(srcRect.width).append("\"\n")
3629 .append(in).append("srcrect_height=\"").append(srcRect.height).append("\"\n")
3630 .append(in).append("scroll_step=\"").append(d.scroll_step).append("\"\n")
3632 sb_body.append(indent).append("/>\n");
3634 writer.write(sb_body.toString());
3637 static public void toolChanged(final String tool_name) {
3638 Utils.log2("tool name: " + tool_name);
3639 if (!tool_name.equals("ALIGN")) {
3640 for (final Display d : al_displays) {
3641 d.layer.getParent().cancelAlign();
3646 static public void toolChanged(final int tool) {
3647 //Utils.log2("int tool is " + tool);
3648 if (ProjectToolbar.PEN == tool) {
3649 // erase bounding boxes
3650 for (final Display d : al_displays) {
3651 if (null != d.active) d.repaint(d.layer, d.selection.getBox(), 2);
3654 if (null != front) {
3655 WindowManager.setTempCurrentImage(front.canvas.getFakeImagePlus());
3659 public Selection getSelection() {
3660 return selection;
3663 public boolean isSelected(Displayable d) {
3664 return selection.contains(d);
3667 static public void updateSelection() {
3668 Display.updateSelection(null);
3670 static public void updateSelection(final Display calling) {
3671 final HashSet hs = new HashSet();
3672 for (final Display d : al_displays) {
3673 if (hs.contains(d.layer)) continue;
3674 hs.add(d.layer);
3675 if (null == d || null == d.selection) {
3676 Utils.log2("d is : "+ d + " d.selection is " + d.selection);
3677 } else {
3678 d.selection.update(); // recomputes box
3680 if (d != calling) { // TODO this is so dirty!
3681 if (d.selection.getNLinked() > 1) d.canvas.setUpdateGraphics(true); // this is overkill anyway
3682 d.canvas.repaint(d.selection.getLinkedBox(), Selection.PADDING);
3683 d.navigator.repaint(true); // everything
3688 static public void clearSelection(final Layer layer) {
3689 for (final Display d : al_displays) {
3690 if (d.layer == layer) d.selection.clear();
3693 static public void clearSelection() {
3694 for (final Display d : al_displays) {
3695 d.selection.clear();
3699 private void setTempCurrentImage() {
3700 WindowManager.setTempCurrentImage(canvas.getFakeImagePlus());
3703 /** Check if any display will paint the given Displayable at the given magnification. */
3704 static public boolean willPaint(final Displayable displ, final double magnification) {
3705 Rectangle box = null; ;
3706 for (final Display d : al_displays) {
3707 /* // Can no longer do this check, because 'magnification' is now affected by the Displayable AffineTransform! And thus it would not paint after the prePaint.
3708 if (Math.abs(d.canvas.getMagnification() - magnification) > 0.00000001) {
3709 continue;
3712 if (null == box) box = displ.getBoundingBox(null);
3713 if (d.canvas.getSrcRect().intersects(box)) {
3714 return true;
3717 return false;
3720 public void hideDeselected(final boolean not_images) {
3721 // hide deselected
3722 final ArrayList all = layer.getParent().getZDisplayables(); // a copy
3723 all.addAll(layer.getDisplayables());
3724 all.removeAll(selection.getSelected());
3725 if (not_images) all.removeAll(layer.getDisplayables(Patch.class));
3726 for (final Displayable d : (ArrayList<Displayable>)all) {
3727 if (d.isVisible()) d.setVisible(false);
3729 Display.update(layer);
3732 /** Cleanup internal lists that may contain the given Displayable. */
3733 static public void flush(final Displayable displ) {
3734 for (final Display d : al_displays) {
3735 d.selection.removeFromPrev(displ);
3739 public void resizeCanvas() {
3740 GenericDialog gd = new GenericDialog("Resize LayerSet");
3741 gd.addNumericField("new width: ", layer.getLayerWidth(), 3);
3742 gd.addNumericField("new height: ",layer.getLayerHeight(),3);
3743 gd.addChoice("Anchor: ", LayerSet.ANCHORS, LayerSet.ANCHORS[7]);
3744 gd.showDialog();
3745 if (gd.wasCanceled()) return;
3746 double new_width = gd.getNextNumber();
3747 double new_height =gd.getNextNumber();
3748 layer.getParent().setDimensions(new_width, new_height, gd.getNextChoiceIndex()); // will complain and prevent cropping existing Displayable objects
3752 // To record layer changes -- but it's annoying, this is visualization not data.
3753 static class DoSetLayer implements DoStep {
3754 final Display display;
3755 final Layer layer;
3756 DoSetLayer(final Display display) {
3757 this.display = display;
3758 this.layer = display.layer;
3760 public Displayable getD() { return null; }
3761 public boolean isEmpty() { return false; }
3762 public boolean apply(final int action) {
3763 display.setLayer(layer);
3765 public boolean isIdenticalTo(final Object ob) {
3766 if (!ob instanceof DoSetLayer) return false;
3767 final DoSetLayer dsl = (DoSetLayer) ob;
3768 return dsl.display == this.display && dsl.layer == this.layer;
3773 protected void duplicateLinkAndSendTo(final Displayable active, final int position, final Layer other_layer) {
3774 if (null == active || !(active instanceof Profile)) return;
3775 if (active.getLayer() == other_layer) return; // can't do that!
3776 Profile profile = project.getProjectTree().duplicateChild((Profile)active, position, other_layer);
3777 if (null == profile) return;
3778 active.link(profile);
3779 slt.setAndWait(other_layer);
3780 other_layer.add(profile);
3781 selection.add(profile);