Layer overlays GUI is ready.
[trakem2.git] / ini / trakem2 / display / Display.java
blob2010bf6794817448f94de9953a83ea6627c10e57
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.imaging.Blending;
37 import ini.trakem2.utils.ProjectToolbar;
38 import ini.trakem2.utils.Utils;
39 import ini.trakem2.utils.DNDInsertImage;
40 import ini.trakem2.utils.Search;
41 import ini.trakem2.utils.Bureaucrat;
42 import ini.trakem2.utils.Worker;
43 import ini.trakem2.utils.Dispatcher;
44 import ini.trakem2.utils.Lock;
45 import ini.trakem2.tree.*;
47 import javax.swing.*;
48 import javax.swing.event.*;
50 import mpicbg.trakem2.align.AlignTask;
52 import java.awt.*;
53 import java.awt.event.*;
54 import java.util.*;
55 import java.lang.reflect.Method;
56 import java.io.Writer;
58 import lenscorrection.DistortionCorrectionTask;
60 /** A Display is a class to show a Layer and enable mouse and keyboard manipulation of all its components. */
61 public final class Display extends DBObject implements ActionListener, ImageListener {
63 /** The Layer this Display is showing. */
64 private Layer layer;
66 private Displayable active = null;
67 /** All selected Displayable objects, including the active one. */
68 final private Selection selection = new Selection(this);
70 private ImagePlus last_temp = null;
72 private JFrame frame;
73 private JTabbedPane tabs;
74 private Hashtable<Class,JScrollPane> ht_tabs;
75 private JScrollPane scroll_patches;
76 private JPanel panel_patches;
77 private JScrollPane scroll_profiles;
78 private JPanel panel_profiles;
79 private JScrollPane scroll_zdispl;
80 private JPanel panel_zdispl;
81 private JScrollPane scroll_channels;
82 private JPanel panel_channels;
83 private JScrollPane scroll_labels;
84 private JPanel panel_labels;
86 private JPanel panel_layers;
87 private JScrollPane scroll_layers;
88 private Hashtable<Layer,LayerPanel> layer_panels = new Hashtable<Layer,LayerPanel>();
90 private JSlider transp_slider;
91 private DisplayNavigator navigator;
92 private JScrollBar scroller;
94 private DisplayCanvas canvas; // WARNING this is an AWT component, since it extends ImageCanvas
95 private JPanel canvas_panel; // and this is a workaround, to better (perhaps) integrate the awt canvas inside a JSplitPane
96 private JSplitPane split;
98 private JPopupMenu popup = null;
100 /** Contains the packed alphas of every channel. */
101 private int c_alphas = 0xffffffff; // all 100 % visible
102 private Channel[] channels;
104 private Hashtable<Displayable,DisplayablePanel> ht_panels = new Hashtable<Displayable,DisplayablePanel>();
106 /** Handle drop events, to insert image files. */
107 private DNDInsertImage dnd;
109 private boolean size_adjusted = false;
111 private int scroll_step = 1;
113 /** Keep track of all existing Display objects. */
114 static private ArrayList<Display> al_displays = new ArrayList<Display>();
115 /** The currently focused Display, if any. */
116 static private Display front = null;
118 /** Displays to open when all objects have been reloaded from the database. */
119 static private final Hashtable ht_later = new Hashtable();
121 /** A thread to handle user actions, for example an event sent from a popup menu. */
122 private final Dispatcher dispatcher = new Dispatcher();
124 static private WindowAdapter window_listener = new WindowAdapter() {
125 /** Unregister the closed Display. */
126 public void windowClosing(WindowEvent we) {
127 final Object source = we.getSource();
128 for (Iterator it = al_displays.iterator(); it.hasNext(); ) {
129 Display d = (Display)it.next();
130 if (source == d.frame) {
131 it.remove();
132 if (d == front) front = null;
133 d.remove(false); //calls destroy
134 break;
138 /** Set the source Display as front. */
139 public void windowActivated(WindowEvent we) {
140 // find which was it to make it be the front
141 final Object source = we.getSource();
142 for (final Display d : al_displays) {
143 if (source == d.frame) {
144 front = d;
145 // set toolbar
146 ProjectToolbar.setProjectToolbar();
147 // now, select the layer in the LayerTree
148 front.getProject().select(front.layer);
149 // finally, set the virtual ImagePlus that ImageJ will see
150 d.setTempCurrentImage();
151 // copied from ij.gui.ImageWindow, with modifications
152 if (IJ.isMacintosh() && IJ.getInstance()!=null) {
153 IJ.wait(10); // may be needed for Java 1.4 on OS X
154 d.frame.setMenuBar(ij.Menus.getMenuBar());
156 return;
159 // else, restore the ImageJ toolbar for non-project images
160 //if (!source.equals(IJ.getInstance())) {
161 // ProjectToolbar.setImageJToolbar();
164 /** Restore the ImageJ toolbar */
165 public void windowDeactivated(WindowEvent we) {
166 // 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();
168 /** Call a pack() when the window is maximized to fit the canvas correctly. */
169 public void windowStateChanged(WindowEvent we) {
170 final Object source = we.getSource();
171 for (final Display d : al_displays) {
172 if (source != d.frame) continue;
173 d.pack();
174 break;
179 static private MouseListener frame_mouse_listener = new MouseAdapter() {
180 public void mouseReleased(MouseEvent me) {
181 Object source = me.getSource();
182 for (final Display d : al_displays) {
183 if (d.frame == source) {
184 if (d.size_adjusted) {
185 d.pack();
186 d.size_adjusted = false;
187 Utils.log2("mouse released on JFrame");
189 break;
195 private int last_frame_state = frame.NORMAL;
197 // THIS WHOLE SYSTEM OF LISTENERS IS BROKEN:
198 // * when zooming in, the window growths in width a few pixels.
199 // * when enlarging the window quickly, the canvas is not resized as large as it should.
200 // -- the whole problem: swing threading, which I am not handling properly. It's hard.
201 static private ComponentListener component_listener = new ComponentAdapter() {
202 public void componentResized(ComponentEvent ce) {
203 final Display d = getDisplaySource(ce);
204 if (null != d) {
205 d.size_adjusted = true; // works in combination with mouseReleased to call pack(), avoiding infinite loops.
206 d.adjustCanvas();
207 int frame_state = d.frame.getExtendedState();
208 if (frame_state != d.last_frame_state) { // this setup avoids infinite loops (for pack() calls componentResized as well
209 d.last_frame_state = frame_state;
210 if (d.frame.ICONIFIED != frame_state) d.pack();
214 public void componentMoved(ComponentEvent ce) {
215 Display d = getDisplaySource(ce);
216 if (null != d) d.updateInDatabase("position");
218 private Display getDisplaySource(ComponentEvent ce) {
219 final Object source = ce.getSource();
220 for (final Display d : al_displays) {
221 if (source == d.frame) {
222 return d;
225 return null;
229 static private ChangeListener tabs_listener = new ChangeListener() {
230 /** Listen to tab changes. */
231 public void stateChanged(final ChangeEvent ce) {
232 final Object source = ce.getSource();
233 for (final Display d : al_displays) {
234 if (source == d.tabs) {
235 d.dispatcher.exec(new Runnable() { public void run() {
236 // creating tabs fires the event!!!
237 if (null == d.frame || null == d.canvas) return;
238 final Container tab = (Container)d.tabs.getSelectedComponent();
239 if (tab == d.scroll_channels) {
240 // find active channel if any
241 for (int i=0; i<d.channels.length; i++) {
242 if (d.channels[i].isActive()) {
243 d.transp_slider.setValue((int)(d.channels[i].getAlpha() * 100));
244 break;
247 } else {
248 // recreate contents
250 int count = tab.getComponentCount();
251 if (0 == count || (1 == count && tab.getComponent(0).getClass().equals(JLabel.class))) {
252 */ // 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)
254 String label = null;
255 ArrayList al = null;
256 JPanel p = null;
257 if (tab == d.scroll_zdispl) {
258 label = "Z-space objects";
259 al = d.layer.getParent().getZDisplayables();
260 p = d.panel_zdispl;
261 } else if (tab == d.scroll_patches) {
262 label = "Patches";
263 al = d.layer.getDisplayables(Patch.class);
264 p = d.panel_patches;
265 } else if (tab == d.scroll_labels) {
266 label = "Labels";
267 al = d.layer.getDisplayables(DLabel.class);
268 p = d.panel_labels;
269 } else if (tab == d.scroll_profiles) {
270 label = "Profiles";
271 al = d.layer.getDisplayables(Profile.class);
272 p = d.panel_profiles;
273 } else if (tab == d.scroll_layers) {
274 // nothing to do
275 return;
278 d.updateTab(p, label, al);
279 //Utils.updateComponent(d.tabs.getSelectedComponent());
280 //Utils.log2("updated tab: " + p + " with " + al.size() + " objects.");
283 if (null != d.active) {
284 // set the transp slider to the alpha value of the active Displayable if any
285 d.transp_slider.setValue((int)(d.active.getAlpha() * 100));
286 DisplayablePanel dp = d.ht_panels.get(d.active);
287 if (null != dp) dp.setActive(true);
290 }});
291 break;
297 private final ScrollLayerListener scroller_listener = new ScrollLayerListener();
299 private class ScrollLayerListener implements AdjustmentListener {
301 public void adjustmentValueChanged(final AdjustmentEvent ae) {
302 final int index = scroller.getValue();
303 slt.set(layer.getParent().getLayer(index));
307 private final SetLayerThread slt = new SetLayerThread();
309 private class SetLayerThread extends Thread {
311 private boolean go = true;
312 private Layer layer;
313 private final Lock lock = new Lock();
314 private final Lock lock2 = new Lock();
316 SetLayerThread() {
317 setPriority(Thread.NORM_PRIORITY);
318 setDaemon(true);
319 start();
322 public final void set(final Layer layer) {
323 synchronized (lock) {
324 this.layer = layer;
326 synchronized (this) {
327 notify();
331 public final void setAndWait(final Layer layer) {
332 lock2.lock();
333 set(layer);
336 public void run() {
337 while (go) {
338 while (null == this.layer) {
339 synchronized (this) {
340 try { wait(); } catch (InterruptedException ie) {}
343 Layer layer = null;
344 synchronized (lock) {
345 layer = this.layer;
346 this.layer = null;
349 if (!go) return; // after nullifying layer
351 if (null != layer) {
352 Display.this.setLayer(layer);
353 Display.this.updateInDatabase("layer_id");
355 // unlock any calls waiting on setAndWait
356 synchronized (lock2) {
357 lock2.unlock();
360 // cleanup:
361 synchronized (lock2) {
362 lock2.unlock();
366 public void waitForLayer() {
367 while (null != layer && go) {
368 try { Thread.sleep(10); } catch (Exception e) {}
372 public void quit() {
373 go = false;
377 /** Creates a new Display with adjusted magnification to fit in the screen. */
378 static public void createDisplay(final Project project, final Layer layer) {
379 SwingUtilities.invokeLater(new Runnable() { public void run() {
380 Display display = new Display(project, layer);
381 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
382 Rectangle srcRect = new Rectangle(0, 0, (int)layer.getLayerWidth(), (int)layer.getLayerHeight());
383 double mag = screen.width / layer.getLayerWidth();
384 if (mag * layer.getLayerHeight() > screen.height) mag = screen.height / layer.getLayerHeight();
385 mag = display.canvas.getLowerZoomLevel2(mag);
386 if (mag > 1.0) mag = 1.0;
387 //display.getCanvas().setup(mag, srcRect); // would call pack() at the wrong time!
388 // ... so instead: manually
389 display.getCanvas().setMagnification(mag);
390 display.getCanvas().setSrcRect(srcRect.x, srcRect.y, srcRect.width, srcRect.height);
391 display.getCanvas().setDrawingSize((int)Math.ceil(srcRect.width * mag), (int)Math.ceil(srcRect.height * mag));
393 display.updateTitle();
394 ij.gui.GUI.center(display.frame);
395 display.frame.pack();
396 }});
399 /** A new Display from scratch, to show the given Layer. */
400 public Display(Project project, final Layer layer) {
401 super(project);
402 front = this;
403 makeGUI(layer, null);
404 ImagePlus.addImageListener(this);
405 setLayer(layer);
406 this.layer = layer; // after, or it doesn't update properly
407 al_displays.add(this);
408 addToDatabase();
411 /** For reconstruction purposes. The Display will be stored in the ht_later.*/
412 public Display(Project project, long id, Layer layer, Object[] props) {
413 super(project, id);
414 synchronized (ht_later) {
415 Display.ht_later.put(this, props);
417 this.layer = layer;
420 /** Open a new Display centered around the given Displayable. */
421 public Display(Project project, Layer layer, Displayable displ) {
422 super(project);
423 front = this;
424 active = displ;
425 makeGUI(layer, null);
426 ImagePlus.addImageListener(this);
427 setLayer(layer);
428 this.layer = layer; // after set layer!
429 al_displays.add(this);
430 addToDatabase();
433 /** Reconstruct a Display from an XML entry, to be opened when everything is ready. */
434 public Display(Project project, long id, Layer layer, HashMap ht_attributes) {
435 super(project, id);
436 if (null == layer) {
437 Utils.log2("Display: need a non-null Layer for id=" + id);
438 return;
440 Rectangle srcRect = new Rectangle(0, 0, (int)layer.getLayerWidth(), (int)layer.getLayerHeight());
441 double magnification = 0.25;
442 Point p = new Point(0, 0);
443 int c_alphas = 0xffffffff;
444 int c_alphas_state = 0xffffffff;
445 for (Iterator it = ht_attributes.entrySet().iterator(); it.hasNext(); ) {
446 Map.Entry entry = (Map.Entry)it.next();
447 String key = (String)entry.getKey();
448 String data = (String)entry.getValue();
449 if (key.equals("srcrect_x")) { // reflection! Reflection!
450 srcRect.x = Integer.parseInt(data);
451 } else if (key.equals("srcrect_y")) {
452 srcRect.y = Integer.parseInt(data);
453 } else if (key.equals("srcrect_width")) {
454 srcRect.width = Integer.parseInt(data);
455 } else if (key.equals("srcrect_height")) {
456 srcRect.height = Integer.parseInt(data);
457 } else if (key.equals("magnification")) {
458 magnification = Double.parseDouble(data);
459 } else if (key.equals("x")) {
460 p.x = Integer.parseInt(data);
461 } else if (key.equals("y")) {
462 p.y = Integer.parseInt(data);
463 } else if (key.equals("c_alphas")) {
464 try {
465 c_alphas = Integer.parseInt(data);
466 } catch (Exception ex) {
467 c_alphas = 0xffffffff;
469 } else if (key.equals("c_alphas_state")) {
470 try {
471 c_alphas_state = Integer.parseInt(data);
472 } catch (Exception ex) {
473 IJError.print(ex);
474 c_alphas_state = 0xffffffff;
476 } else if (key.equals("scroll_step")) {
477 try {
478 setScrollStep(Integer.parseInt(data));
479 } catch (Exception ex) {
480 IJError.print(ex);
481 setScrollStep(1);
484 // TODO the above is insecure, in that data is not fully checked to be within bounds.
486 Object[] props = new Object[]{p, new Double(magnification), srcRect, new Long(layer.getId()), new Integer(c_alphas), new Integer(c_alphas_state)};
487 synchronized (ht_later) {
488 Display.ht_later.put(this, props);
490 this.layer = layer;
493 /** After reloading a project from the database, open the Displays that the project had. */
494 static public Bureaucrat openLater() {
495 final Hashtable ht_later_local;
496 synchronized (ht_later) {
497 if (0 == ht_later.size()) return null;
498 ht_later_local = new Hashtable(ht_later);
499 ht_later.keySet().removeAll(ht_later_local.keySet());
501 final Worker worker = new Worker("Opening displays") {
502 public void run() {
503 startedWorking();
504 try {
505 Thread.sleep(300); // waiting for Swing
507 for (Enumeration e = ht_later_local.keys(); e.hasMoreElements(); ) {
508 final Display d = (Display)e.nextElement();
509 front = d; // must be set before repainting any ZDisplayable!
510 Object[] props = (Object[])ht_later_local.get(d);
511 if (ControlWindow.isGUIEnabled()) d.makeGUI(d.layer, props);
512 d.setLayerLater(d.layer, d.layer.get(((Long)props[3]).longValue())); //important to do it after makeGUI
513 if (!ControlWindow.isGUIEnabled()) continue;
514 ImagePlus.addImageListener(d);
515 al_displays.add(d);
516 d.updateTitle();
517 // 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
518 if (d.canvas.getMagnification() > 0.499) {
519 SwingUtilities.invokeLater(new Runnable() { public void run() {
520 d.repaint(d.layer);
521 d.project.getLoader().setChanged(false);
522 Utils.log2("A set to false");
523 }});
525 d.project.getLoader().setChanged(false);
526 Utils.log2("B set to false");
528 if (null != front) front.getProject().select(front.layer);
530 } catch (Throwable t) {
531 IJError.print(t);
532 } finally {
533 finishedWorking();
537 return Bureaucrat.createAndStart(worker, ((Display)ht_later_local.keySet().iterator().next()).getProject()); // gets the project from the first Display
540 private void makeGUI(final Layer layer, final Object[] props) {
541 // gather properties
542 Point p = null;
543 double mag = 1.0D;
544 Rectangle srcRect = null;
545 if (null != props) {
546 p = (Point)props[0];
547 mag = ((Double)props[1]).doubleValue();
548 srcRect = (Rectangle)props[2];
551 // transparency slider
552 this.transp_slider = new JSlider(javax.swing.SwingConstants.HORIZONTAL, 0, 100, 100);
553 this.transp_slider.setBackground(Color.white);
554 this.transp_slider.setMinimumSize(new Dimension(250, 20));
555 this.transp_slider.setMaximumSize(new Dimension(250, 20));
556 this.transp_slider.setPreferredSize(new Dimension(250, 20));
557 TransparencySliderListener tsl = new TransparencySliderListener();
558 this.transp_slider.addChangeListener(tsl);
559 this.transp_slider.addMouseListener(tsl);
560 for (final KeyListener kl : this.transp_slider.getKeyListeners()) {
561 this.transp_slider.removeKeyListener(kl);
564 // Tabbed pane on the left
565 this.tabs = new JTabbedPane();
566 this.tabs.setMinimumSize(new Dimension(250, 300));
567 this.tabs.setBackground(Color.white);
568 this.tabs.addChangeListener(tabs_listener);
570 // Tab 1: Patches
571 this.panel_patches = makeTabPanel();
572 this.panel_patches.add(new JLabel("No patches."));
573 this.scroll_patches = makeScrollPane(panel_patches);
574 this.tabs.add("Patches", scroll_patches);
576 // Tab 2: Profiles
577 this.panel_profiles = makeTabPanel();
578 this.panel_profiles.add(new JLabel("No profiles."));
579 this.scroll_profiles = makeScrollPane(panel_profiles);
580 this.tabs.add("Profiles", scroll_profiles);
582 // Tab 3: pipes
583 this.panel_zdispl = makeTabPanel();
584 this.panel_zdispl.add(new JLabel("No objects."));
585 this.scroll_zdispl = makeScrollPane(panel_zdispl);
586 this.tabs.add("Z space", scroll_zdispl);
588 // Tab 4: channels
589 this.panel_channels = makeTabPanel();
590 this.scroll_channels = makeScrollPane(panel_channels);
591 this.channels = new Channel[4];
592 this.channels[0] = new Channel(this, Channel.MONO);
593 this.channels[1] = new Channel(this, Channel.RED);
594 this.channels[2] = new Channel(this, Channel.GREEN);
595 this.channels[3] = new Channel(this, Channel.BLUE);
596 //this.panel_channels.add(this.channels[0]);
597 this.panel_channels.add(this.channels[1]);
598 this.panel_channels.add(this.channels[2]);
599 this.panel_channels.add(this.channels[3]);
600 this.tabs.add("Opacity", scroll_channels);
602 // Tab 5: labels
603 this.panel_labels = makeTabPanel();
604 this.panel_labels.add(new JLabel("No labels."));
605 this.scroll_labels = makeScrollPane(panel_labels);
606 this.tabs.add("Labels", scroll_labels);
608 // Tab 6: layers
609 this.panel_layers = makeTabPanel();
610 this.scroll_layers = makeScrollPane(panel_layers);
611 for (final Layer la : layer.getParent().getLayers()) {
612 LayerPanel lp = new LayerPanel(this, la);
613 layer_panels.put(la, lp);
614 this.panel_layers.add(lp);
616 this.tabs.add("Layers", scroll_layers);
618 this.ht_tabs = new Hashtable<Class,JScrollPane>();
619 this.ht_tabs.put(Patch.class, scroll_patches);
620 this.ht_tabs.put(Profile.class, scroll_profiles);
621 this.ht_tabs.put(ZDisplayable.class, scroll_zdispl);
622 this.ht_tabs.put(AreaList.class, scroll_zdispl);
623 this.ht_tabs.put(Pipe.class, scroll_zdispl);
624 this.ht_tabs.put(Polyline.class, scroll_zdispl);
625 this.ht_tabs.put(Ball.class, scroll_zdispl);
626 this.ht_tabs.put(Dissector.class, scroll_zdispl);
627 this.ht_tabs.put(DLabel.class, scroll_labels);
628 // channels not included
629 // layers not included
631 // Navigator
632 this.navigator = new DisplayNavigator(this, layer.getLayerWidth(), layer.getLayerHeight());
633 // Layer scroller (to scroll slices)
634 int extent = (int)(250.0 / layer.getParent().size());
635 if (extent < 10) extent = 10;
636 this.scroller = new JScrollBar(JScrollBar.HORIZONTAL);
637 updateLayerScroller(layer);
638 this.scroller.addAdjustmentListener(scroller_listener);
641 // Left panel, contains the transp slider, the tabbed pane, the navigation panel and the layer scroller
642 JPanel left = new JPanel();
643 BoxLayout left_layout = new BoxLayout(left, BoxLayout.Y_AXIS);
644 left.setLayout(left_layout);
645 left.add(transp_slider);
646 left.add(tabs);
647 left.add(navigator);
648 left.add(scroller);
650 // Canvas
651 this.canvas = new DisplayCanvas(this, (int)Math.ceil(layer.getLayerWidth()), (int)Math.ceil(layer.getLayerHeight()));
652 this.canvas_panel = new JPanel();
653 GridBagLayout gb = new GridBagLayout();
654 this.canvas_panel.setLayout(gb);
655 GridBagConstraints c = new GridBagConstraints();
656 c.fill = GridBagConstraints.BOTH;
657 c.anchor = GridBagConstraints.NORTHWEST;
658 gb.setConstraints(this.canvas_panel, c);
659 gb.setConstraints(this.canvas, c);
661 // prevent new Displays from screweing up if input is globally disabled
662 if (!project.isInputEnabled()) this.canvas.setReceivesInput(false);
664 this.canvas_panel.add(canvas);
666 this.navigator.addMouseWheelListener(canvas);
668 this.transp_slider.addKeyListener(canvas);
670 // Split pane to contain everything
671 this.split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, left, canvas_panel);
672 this.split.setOneTouchExpandable(true); // NOT present in all L&F (?)
674 // fix
675 gb.setConstraints(split.getRightComponent(), c);
677 // JFrame to show the split pane
678 this.frame = ControlWindow.createJFrame(layer.toString());
679 if (IJ.isMacintosh() && IJ.getInstance()!=null) {
680 IJ.wait(10); // may be needed for Java 1.4 on OS X
681 this.frame.setMenuBar(ij.Menus.getMenuBar());
683 this.frame.addWindowListener(window_listener);
684 this.frame.addComponentListener(component_listener);
685 this.frame.getContentPane().add(split);
686 this.frame.addMouseListener(frame_mouse_listener);
687 //doesn't exist//this.frame.setMinimumSize(new Dimension(270, 600));
689 if (null != props) {
690 // restore canvas
691 canvas.setup(mag, srcRect);
692 // restore visibility of each channel
693 int cs = ((Integer)props[5]).intValue(); // aka c_alphas_state
694 int[] sel = new int[4];
695 sel[0] = ((cs&0xff000000)>>24);
696 sel[1] = ((cs&0xff0000)>>16);
697 sel[2] = ((cs&0xff00)>>8);
698 sel[3] = (cs&0xff);
699 // restore channel alphas
700 this.c_alphas = ((Integer)props[4]).intValue();
701 channels[0].setAlpha( (float)((c_alphas&0xff000000)>>24) / 255.0f , 0 != sel[0]);
702 channels[1].setAlpha( (float)((c_alphas&0xff0000)>>16) / 255.0f , 0 != sel[1]);
703 channels[2].setAlpha( (float)((c_alphas&0xff00)>>8) / 255.0f , 0 != sel[2]);
704 channels[3].setAlpha( (float) (c_alphas&0xff) / 255.0f , 0 != sel[3]);
705 // restore visibility in the working c_alphas
706 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);
709 if (null != active && null != layer) {
710 Rectangle r = active.getBoundingBox();
711 r.x -= r.width/2;
712 r.y -= r.height/2;
713 r.width += r.width;
714 r.height += r.height;
715 if (r.x < 0) r.x = 0;
716 if (r.y < 0) r.y = 0;
717 if (r.width > layer.getLayerWidth()) r.width = (int)layer.getLayerWidth();
718 if (r.height> layer.getLayerHeight())r.height= (int)layer.getLayerHeight();
719 double magn = layer.getLayerWidth() / (double)r.width;
720 canvas.setup(magn, r);
723 // add keyListener to the whole frame
724 this.tabs.addKeyListener(canvas);
725 this.canvas_panel.addKeyListener(canvas);
726 this.frame.addKeyListener(canvas);
728 this.frame.pack();
729 ij.gui.GUI.center(this.frame);
730 this.frame.setVisible(true);
731 ProjectToolbar.setProjectToolbar(); // doesn't get it through events
733 final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
735 if (null != props) {
736 // fix positioning outside the screen (dual to single monitor)
737 if (p.x >= 0 && p.x < screen.width - 50 && p.y >= 0 && p.y <= screen.height - 50) this.frame.setLocation(p);
738 else frame.setLocation(0, 0);
741 // fix excessive size
742 final Rectangle box = this.frame.getBounds();
743 int x = box.x;
744 int y = box.y;
745 int width = box.width;
746 int height = box.height;
747 if (box.width > screen.width) { x = 0; width = screen.width; }
748 if (box.height > screen.height) { y = 0; height = screen.height; }
749 if (x != box.x || y != box.y) {
750 this.frame.setLocation(x, y + (0 == y ? 30 : 0)); // added insets for bad window managers
751 updateInDatabase("position");
753 if (width != box.width || height != box.height) {
754 this.frame.setSize(new Dimension(width -10, height -30)); // added insets for bad window managers
756 if (null == props) {
757 // try to optimize canvas dimensions and magn
758 double magn = layer.getLayerHeight() / screen.height;
759 if (magn > 1.0) magn = 1.0;
760 long size = 0;
761 // limit magnification if appropriate
762 for (Iterator it = layer.getDisplayables(Patch.class).iterator(); it.hasNext(); ) {
763 final Patch pa = (Patch)it.next();
764 final Rectangle ba = pa.getBoundingBox();
765 size += (long)(ba.width * ba.height);
767 if (size > 10000000) canvas.setInitialMagnification(0.25); // 10 Mb
768 else {
769 this.frame.setSize(new Dimension((int)(screen.width * 0.66), (int)(screen.height * 0.66)));
773 Utils.updateComponent(tabs); // otherwise fails in FreeBSD java 1.4.2 when reconstructing
776 // Set the calibration of the FakeImagePlus to that of the LayerSet
777 ((FakeImagePlus)canvas.getFakeImagePlus()).setCalibrationSuper(layer.getParent().getCalibrationCopy());
779 // Set the FakeImagePlus as the current image
780 setTempCurrentImage();
782 // create a drag and drop listener
783 dnd = new DNDInsertImage(this);
785 // start a repainting thread
786 if (null != props) {
787 canvas.repaint(true); // repaint() is unreliable
790 // 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.
791 SwingUtilities.invokeLater(new Runnable() {
792 public void run() {
793 tabs.setMinimumSize(new Dimension(0, 100));
794 Display.scrollbar_width = Display.this.scroll_patches.getVerticalScrollBar().getPreferredSize().width; // using scroll_patches since it's the one selected by default and thus visible and painted
799 private JPanel makeTabPanel() {
800 JPanel panel = new JPanel();
801 BoxLayout layout = new BoxLayout(panel, BoxLayout.Y_AXIS);
802 panel.setLayout(layout);
803 return panel;
806 private JScrollPane makeScrollPane(Component c) {
807 JScrollPane jsp = new JScrollPane(c);
808 // adjust scrolling to use one DisplayablePanel as the minimal unit
809 jsp.getVerticalScrollBar().setBlockIncrement(DisplayablePanel.HEIGHT); // clicking within the track
810 jsp.getVerticalScrollBar().setUnitIncrement(DisplayablePanel.HEIGHT); // clicking on an arrow
811 jsp.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
812 jsp.setPreferredSize(new Dimension(250, 300));
813 jsp.setMinimumSize(new Dimension(250, 300));
814 return jsp;
817 static protected int scrollbar_width = 0;
819 public JPanel getCanvasPanel() {
820 return canvas_panel;
823 public DisplayCanvas getCanvas() {
824 return canvas;
827 public void setLayer(final Layer layer) {
828 if (null == layer || layer == this.layer) return;
829 final boolean set_zdispl = null == Display.this.layer || layer.getParent() != Display.this.layer.getParent();
830 if (selection.isTransforming()) {
831 Utils.log("Can't browse layers while transforming.\nCANCEL the transform first with the ESCAPE key or right-click -> cancel.");
832 scroller.setValue(Display.this.layer.getParent().getLayerIndex(Display.this.layer.getId()));
833 return;
835 this.layer = layer;
836 scroller.setValue(layer.getParent().getLayerIndex(layer.getId()));
838 // update the current Layer pointer in ZDisplayable objects
839 for (Iterator it = layer.getParent().getZDisplayables().iterator(); it.hasNext(); ) {
840 ((ZDisplayable)it.next()).setLayer(layer); // the active layer
843 updateVisibleTab(set_zdispl);
845 // see if a lot has to be reloaded, put the relevant ones at the end
846 project.getLoader().prepare(layer);
847 updateTitle(); // to show the new 'z'
848 // select the Layer in the LayerTree
849 project.select(Display.this.layer); // does so in a separate thread
850 // update active Displayable:
852 // deselect all except ZDisplayables
853 final ArrayList sel = selection.getSelected();
854 final Displayable last_active = Display.this.active;
855 int sel_next = -1;
856 for (Iterator it = sel.iterator(); it.hasNext(); ) {
857 Displayable d = (Displayable)it.next();
858 if (!(d instanceof ZDisplayable)) {
859 it.remove();
860 selection.remove(d);
861 if (d == last_active && sel.size() > 0) {
862 // select the last one of the remaining, if any
863 sel_next = sel.size()-1;
867 if (-1 != sel_next && sel.size() > 0) select((Displayable)sel.get(sel_next), true);
868 else if (null != last_active && last_active.getClass() == Patch.class && null != last_temp && last_temp instanceof PatchStack) {
869 Displayable d = ((PatchStack)last_temp).getPatch(layer, (Patch)last_active);
870 if (null != d) selection.add(d);
872 // TODO last_temp doesn't remain the PatchStack // Utils.log2("last_temp is: " + last_temp.getClass().getName());
874 // Keep Profile chain selected, for best ease of use:
875 if (null != last_active && last_active.getClass() == Profile.class && last_active.isLinked(Profile.class)) {
876 Displayable other = null;
877 for (final Displayable prof : last_active.getLinked(Profile.class)) {
878 if (prof.getLayer() == layer) {
879 other = prof;
880 break;
883 if (null != other) selection.add(other);
886 // repaint everything
887 navigator.repaint(true);
888 canvas.repaint(true);
890 // repaint tabs (hard as hell)
891 Utils.updateComponent(tabs);
892 // @#$%^! The above works half the times, so explicit repaint as well:
893 Component c = tabs.getSelectedComponent();
894 if (null == c) {
895 c = scroll_patches;
896 tabs.setSelectedComponent(scroll_patches);
898 Utils.updateComponent(c);
900 project.getLoader().setMassiveMode(false); // resetting if it was set true
902 // update the coloring in the ProjectTree
903 project.getProjectTree().updateUILater();
905 setTempCurrentImage();
908 static public void updateVisibleTabs() {
909 for (final Display d : al_displays) {
910 d.updateVisibleTab(true);
914 /** Recreate the tab that is being shown. */
915 public void updateVisibleTab(boolean set_zdispl) {
916 // update only the visible tab
917 switch (tabs.getSelectedIndex()) {
918 case 0:
919 ht_panels.clear();
920 updateTab(panel_patches, "Patches", layer.getDisplayables(Patch.class));
921 break;
922 case 1:
923 ht_panels.clear();
924 updateTab(panel_profiles, "Profiles", layer.getDisplayables(Profile.class));
925 break;
926 case 2:
927 if (set_zdispl) {
928 ht_panels.clear();
929 updateTab(panel_zdispl, "Z-space objects", layer.getParent().getZDisplayables());
931 break;
932 // case 3: channel opacities
933 case 4:
934 ht_panels.clear();
935 updateTab(panel_labels, "Labels", layer.getDisplayables(DLabel.class));
936 break;
941 private void setLayerLater(final Layer layer, final Displayable active) {
942 if (null == layer) return;
943 this.layer = layer;
944 if (!ControlWindow.isGUIEnabled()) return;
945 SwingUtilities.invokeLater(new Runnable() { public void run() {
946 // empty the tabs, except channels and pipes
947 clearTab(panel_profiles, "Profiles");
948 clearTab(panel_patches, "Patches");
949 clearTab(panel_labels, "Labels");
950 // distribute Displayable to the tabs. Ignore LayerSet instances.
951 if (null == ht_panels) ht_panels = new Hashtable<Displayable,DisplayablePanel>();
952 else ht_panels.clear();
953 Iterator it = layer.getDisplayables().iterator();
954 while (it.hasNext()) {
955 add((Displayable)it.next(), false, false);
957 it = layer.getParent().getZDisplayables().iterator(); // the pipes, that live in the LayerSet
958 while (it.hasNext()) {
959 add((Displayable)it.next(), false, false);
961 navigator.repaint(true); // was not done when adding
962 Utils.updateComponent(tabs.getSelectedComponent());
964 setActive(active);
965 }});
966 // swing issues:
968 new Thread() {
969 public void run() {
970 setPriority(Thread.NORM_PRIORITY);
971 try { Thread.sleep(1000); } catch (Exception e) {}
972 setActive(active);
974 }.start();
978 /** Remove all components from the tab and add a "No [label]" label to each. */
979 private void clearTab(final Container c, final String label) {
980 c.removeAll();
981 c.add(new JLabel("No " + label + "."));
982 // magic cocktail:
983 if (tabs.getSelectedComponent() == c) {
984 Utils.updateComponent(c);
988 /** A class to listen to the transparency_slider of the DisplayablesSelectorWindow. */
989 private class TransparencySliderListener extends MouseAdapter implements ChangeListener {
991 public void stateChanged(ChangeEvent ce) {
992 //change the transparency value of the current active displayable
993 float new_value = (float)((JSlider)ce.getSource()).getValue();
994 setTransparency(new_value / 100.0f);
997 public void mousePressed(MouseEvent me) {
998 JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
999 if (scroll != scroll_channels && !selection.isEmpty()) selection.addDataEditStep(new String[]{"alpha"});
1002 public void mouseReleased(MouseEvent me) {
1003 // update navigator window
1004 navigator.repaint(true);
1005 JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
1006 if (scroll != scroll_channels && !selection.isEmpty()) selection.addDataEditStep(new String[]{"alpha"});
1010 /** Context-sensitive: to a Displayable, or to a channel. */
1011 private void setTransparency(final float value) {
1012 JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
1013 if (scroll == scroll_channels) {
1014 for (int i=0; i<4; i++) {
1015 if (channels[i].getBackground() == Color.cyan) {
1016 channels[i].setAlpha(value); // will call back and repaint the Display
1017 return;
1020 } else if (null != active) {
1021 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.
1022 canvas.invalidateVolatile();
1023 selection.setAlpha(value);
1028 public void setTransparencySlider(final float transp) {
1029 if (transp >= 0.0f && transp <= 1.0f) {
1030 // fire event
1031 transp_slider.setValue((int)(transp * 100));
1035 /** Mark the canvas for updating the offscreen images if the given Displayable is NOT the active. */ // Used by the Displayable.setVisible for example.
1036 static public void setUpdateGraphics(final Layer layer, final Displayable displ) {
1037 for (final Display d : al_displays) {
1038 if (layer == d.layer && null != d.active && d.active != displ) {
1039 d.canvas.setUpdateGraphics(true);
1044 /** Flag the DisplayCanvas of Displays showing the given Layer to update their offscreen images.*/
1045 static public void setUpdateGraphics(final Layer layer, final boolean update) {
1046 for (final Display d : al_displays) {
1047 if (layer == d.layer) {
1048 d.canvas.setUpdateGraphics(update);
1053 /** Whether to update the offscreen images or not. */
1054 public void setUpdateGraphics(boolean b) {
1055 canvas.setUpdateGraphics(b);
1058 /** Find all Display instances that contain the layer and repaint them, in the Swing GUI thread. */
1059 static public void update(final Layer layer) {
1060 if (null == layer) return;
1061 SwingUtilities.invokeLater(new Runnable() { public void run() {
1062 for (final Display d : al_displays) {
1063 if (d.isShowing(layer)) {
1064 d.repaintAll();
1067 }});
1070 static public void update(final LayerSet set) {
1071 update(set, true);
1074 /** 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. */
1075 static public void update(final LayerSet set, final boolean update_canvas_dimensions) {
1076 if (null == set) return;
1077 SwingUtilities.invokeLater(new Runnable() { public void run() {
1078 for (final Display d : al_displays) {
1079 if (set.contains(d.layer)) {
1080 d.updateSnapshots();
1081 if (update_canvas_dimensions) d.canvas.setDimensions(set.getLayerWidth(), set.getLayerHeight());
1082 d.repaintAll();
1085 }});
1088 /** Release all resources held by this Display and close the frame. */
1089 protected void destroy() {
1090 dispatcher.quit();
1091 canvas.setReceivesInput(false);
1092 slt.quit();
1094 // update the coloring in the ProjectTree and LayerTree
1095 if (!project.isBeingDestroyed()) {
1096 try {
1097 project.getProjectTree().updateUILater();
1098 project.getLayerTree().updateUILater();
1099 } catch (Exception e) {
1100 Utils.log2("updateUI failed at Display.destroy()");
1104 frame.removeComponentListener(component_listener);
1105 frame.removeWindowListener(window_listener);
1106 frame.removeWindowFocusListener(window_listener);
1107 frame.removeWindowStateListener(window_listener);
1108 frame.removeKeyListener(canvas);
1109 frame.removeMouseListener(frame_mouse_listener);
1110 canvas_panel.removeKeyListener(canvas);
1111 canvas.removeKeyListener(canvas);
1112 tabs.removeChangeListener(tabs_listener);
1113 tabs.removeKeyListener(canvas);
1114 ImagePlus.removeImageListener(this);
1115 bytypelistener = null;
1116 canvas.destroy();
1117 navigator.destroy();
1118 scroller.removeAdjustmentListener(scroller_listener);
1119 frame.setVisible(false);
1120 //no need, and throws exception//frame.dispose();
1121 active = null;
1122 if (null != selection) selection.clear();
1123 //Utils.log2("destroying selection");
1125 // below, need for SetLayerThread threads to quit
1126 slt.quit();
1127 // set a new front if any
1128 if (null == front && al_displays.size() > 0) {
1129 front = (Display)al_displays.get(al_displays.size() -1);
1131 // repaint layer tree (to update the label color)
1132 try {
1133 project.getLayerTree().updateUILater(); // works only after setting the front above
1134 } catch (Exception e) {} // ignore swing sync bullshit when closing everything too fast
1135 // remove the drag and drop listener
1136 dnd.destroy();
1139 /** Find all Display instances that contain a Layer of the given project and close them without removing the Display entries from the database. */
1140 static synchronized public void close(final Project project) {
1141 /* // concurrent modifications if more than 1 Display are being removed asynchronously
1142 for (final Display d : al_displays) {
1143 if (d.getLayer().getProject().equals(project)) {
1144 it.remove();
1145 d.destroy();
1149 Display[] d = new Display[al_displays.size()];
1150 al_displays.toArray(d);
1151 for (int i=0; i<d.length; i++) {
1152 if (d[i].getProject() == project) {
1153 al_displays.remove(d[i]);
1154 d[i].destroy();
1159 /** Find all Display instances that contain the layer and close them and remove the Display from the database. */
1160 static public void close(final Layer layer) {
1161 for (Iterator it = al_displays.iterator(); it.hasNext(); ) {
1162 Display d = (Display)it.next();
1163 if (d.isShowing(layer)) {
1164 d.remove(false);
1165 it.remove();
1170 public boolean remove(boolean check) {
1171 if (check) {
1172 if (!Utils.check("Delete the Display ?")) return false;
1174 // flush the offscreen images and close the frame
1175 destroy();
1176 removeFromDatabase();
1177 return true;
1180 public Layer getLayer() {
1181 return layer;
1184 public LayerSet getLayerSet() {
1185 return layer.getParent();
1188 public boolean isShowing(final Layer layer) {
1189 return this.layer == layer;
1192 public DisplayNavigator getNavigator() {
1193 return navigator;
1196 /** Repaint both the canvas and the navigator, updating the graphics, and the title and tabs. */
1197 public void repaintAll() {
1198 if (repaint_disabled) return;
1199 navigator.repaint(true);
1200 canvas.repaint(true);
1201 Utils.updateComponent(tabs);
1202 updateTitle();
1205 /** Repaint the canvas updating graphics, the navigator without updating graphics, and the title. */
1206 public void repaintAll2() {
1207 if (repaint_disabled) return;
1208 navigator.repaint(false);
1209 canvas.repaint(true);
1210 updateTitle();
1213 static public void repaintSnapshots(final LayerSet set) {
1214 if (repaint_disabled) return;
1215 for (final Display d : al_displays) {
1216 if (d.getLayer().getParent() == set) {
1217 d.navigator.repaint(true);
1218 Utils.updateComponent(d.tabs);
1222 static public void repaintSnapshots(final Layer layer) {
1223 if (repaint_disabled) return;
1224 for (final Display d : al_displays) {
1225 if (d.getLayer() == layer) {
1226 d.navigator.repaint(true);
1227 Utils.updateComponent(d.tabs);
1232 public void pack() {
1233 dispatcher.exec(new Runnable() { public void run() {
1234 try {
1235 Thread.currentThread().sleep(100);
1236 SwingUtilities.invokeAndWait(new Runnable() { public void run() {
1237 frame.pack();
1238 }});
1239 } catch (Exception e) { IJError.print(e); }
1240 }});
1243 static public void pack(final LayerSet ls) {
1244 for (final Display d : al_displays) {
1245 if (d.layer.getParent() == ls) d.pack();
1249 private void adjustCanvas() {
1250 SwingUtilities.invokeLater(new Runnable() { public void run() {
1251 Rectangle r = split.getRightComponent().getBounds();
1252 canvas.setDrawingSize(r.width, r.height, true);
1253 // fix not-on-top-left problem
1254 canvas.setLocation(0, 0);
1255 //frame.pack(); // don't! Would go into an infinite loop
1256 canvas.repaint(true);
1257 updateInDatabase("srcRect");
1258 }});
1261 /** Grab the last selected display (or create an new one if none) and show in it the layer,centered on the Displayable object. */
1262 static public void setFront(final Layer layer, final Displayable displ) {
1263 if (null == front) {
1264 Display display = new Display(layer.getProject(), layer); // gets set to front
1265 display.showCentered(displ);
1266 } else if (layer == front.layer) {
1267 front.showCentered(displ);
1268 } else {
1269 // find one:
1270 for (final Display d : al_displays) {
1271 if (d.layer == layer) {
1272 d.frame.toFront();
1273 d.showCentered(displ);
1274 return;
1277 // else, open new one
1278 new Display(layer.getProject(), layer).showCentered(displ);
1282 /** 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. */
1283 static public void add(final Layer layer, final Displayable displ, final boolean activate) {
1284 for (final Display d : al_displays) {
1285 if (d.layer == layer) {
1286 if (front == d) {
1287 d.add(displ, activate, true);
1288 //front.frame.toFront();
1289 } else {
1290 d.add(displ, false, true);
1296 static public void add(final Layer layer, final Displayable displ) {
1297 add(layer, displ, true);
1300 /** Add the ZDisplayable to all Displays that show a Layer belonging to the given LayerSet. */
1301 static public void add(final LayerSet set, final ZDisplayable zdispl) {
1302 for (final Display d : al_displays) {
1303 if (set.contains(d.layer)) {
1304 if (front == d) {
1305 zdispl.setLayer(d.layer); // the active one
1306 d.add(zdispl, true, true); // calling add(Displayable, boolean, boolean)
1307 //front.frame.toFront();
1308 } else {
1309 d.add(zdispl, false, true);
1315 static public void addAll(final Layer layer, final Collection<? extends Displayable> coll) {
1316 for (final Display d : al_displays) {
1317 if (d.layer == layer) {
1318 d.addAll(coll);
1323 static public void addAll(final LayerSet set, final Collection<? extends ZDisplayable> coll) {
1324 for (final Display d : al_displays) {
1325 if (set.contains(d.layer)) {
1326 for (final ZDisplayable zd : coll) {
1327 if (front == d) zd.setLayer(d.layer);
1329 d.addAll(coll);
1334 private final void addAll(final Collection<? extends Displayable> coll) {
1335 for (final Displayable d : coll) {
1336 add(d, false, false);
1338 selection.clear();
1339 Utils.updateComponent(tabs);
1340 navigator.repaint(true);
1343 // TODO this very old method could take some improvement:
1344 // - there is no need to create a new DisplayablePanel if its panel is not shown
1345 // - other issues; the method looks overly "if a dog barks and a duck quacks during a lunar eclipse then .."
1346 /** Add it to the proper panel, at the top, and set it active. */
1347 private final void add(final Displayable d, final boolean activate, final boolean repaint_snapshot) {
1348 DisplayablePanel dp = ht_panels.get(d);
1349 if (null != dp && activate) { // for ZDisplayable objects (TODO I think this is not used anymore)
1350 dp.setActive(true);
1351 //setActive(d);
1352 selection.clear();
1353 selection.add(d);
1354 return;
1356 // add to the proper list
1357 JPanel p = null;
1358 if (d instanceof Profile) {
1359 p = panel_profiles;
1360 } else if (d instanceof Patch) {
1361 p = panel_patches;
1362 } else if (d instanceof DLabel) {
1363 p = panel_labels;
1364 } else if (d instanceof ZDisplayable) { //both pipes and balls and AreaList
1365 p = panel_zdispl;
1366 } else {
1367 // LayerSet objects
1368 return;
1370 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
1371 addToPanel(p, 0, dp, activate);
1372 ht_panels.put(d, dp);
1373 if (activate) {
1374 dp.setActive(true);
1375 //setActive(d);
1376 selection.clear();
1377 selection.add(d);
1379 if (repaint_snapshot) navigator.repaint(true);
1382 private void addToPanel(JPanel panel, int index, DisplayablePanel dp, boolean repaint) {
1383 // remove the label
1384 if (1 == panel.getComponentCount() && panel.getComponent(0) instanceof JLabel) {
1385 panel.removeAll();
1387 panel.add(dp, index);
1388 if (repaint) {
1389 Utils.updateComponent(tabs);
1393 /** Find the displays that show the given Layer, and remove the given Displayable from the GUI. */
1394 static public void remove(final Layer layer, final Displayable displ) {
1395 for (final Display d : al_displays) {
1396 if (layer == d.layer) d.remove(displ);
1400 private void remove(final Displayable displ) {
1401 DisplayablePanel ob = ht_panels.remove(displ);
1402 if (null != ob) {
1403 final JScrollPane jsp = ht_tabs.get(displ.getClass());
1404 if (null != jsp) {
1405 JPanel p = (JPanel)jsp.getViewport().getView();
1406 p.remove((Component)ob);
1407 Utils.revalidateComponent(p);
1410 if (null == active || !selection.contains(displ)) {
1411 canvas.setUpdateGraphics(true);
1413 canvas.invalidateVolatile(); // removing active, no need to update offscreen but yes the volatile
1414 repaint(displ, null, 5, true, false);
1415 // from Selection.deleteAll this method is called ... but it's ok: same thread, no locking problems.
1416 selection.remove(displ);
1419 static public void remove(final ZDisplayable zdispl) {
1420 for (final Display d : al_displays) {
1421 if (zdispl.getLayerSet() == d.layer.getParent()) {
1422 d.remove((Displayable)zdispl);
1427 static public void repaint(final Layer layer, final Displayable displ, final int extra) {
1428 repaint(layer, displ, displ.getBoundingBox(), extra);
1431 static public void repaint(final Layer layer, final Displayable displ, final Rectangle r, final int extra) {
1432 repaint(layer, displ, r, extra, true);
1435 /** Find the displays that show the given Layer, and repaint the given Displayable. */
1436 static public void repaint(final Layer layer, final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator) {
1437 if (repaint_disabled) return;
1438 for (final Display d : al_displays) {
1439 if (layer == d.layer) {
1440 d.repaint(displ, r, extra, repaint_navigator, false);
1445 static public void repaint(final Displayable d) {
1446 if (d instanceof ZDisplayable) repaint(d.getLayerSet(), d, d.getBoundingBox(null), 5, true);
1447 repaint(d.getLayer(), d, d.getBoundingBox(null), 5, true);
1450 /** Repaint as much as the bounding box around the given Displayable, or the r if not null. */
1451 private void repaint(final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator, final boolean update_graphics) {
1452 if (repaint_disabled || null == displ) return;
1453 if (update_graphics || displ.getClass() == Patch.class || displ != active) {
1454 canvas.setUpdateGraphics(true);
1456 if (null != r) canvas.repaint(r, extra);
1457 else canvas.repaint(displ, extra);
1458 if (repaint_navigator) {
1459 DisplayablePanel dp = ht_panels.get(displ);
1460 if (null != dp) dp.repaint(); // is null when creating it, or after deleting it
1461 navigator.repaint(true); // everything
1465 /** 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. */
1466 static public void repaintSnapshot(final Displayable displ) {
1467 for (final Display d : al_displays) {
1468 if (d.layer.contains(displ)) {
1469 if (!d.navigator.isPainted(displ)) {
1470 DisplayablePanel dp = d.ht_panels.get(displ);
1471 if (null != dp) dp.repaint(); // is null when creating it, or after deleting it
1472 d.navigator.repaint(displ);
1478 /** Repaint the given Rectangle in all Displays showing the layer, updating the offscreen image if any. */
1479 static public void repaint(final Layer layer, final Rectangle r, final int extra) {
1480 repaint(layer, extra, r, true, true);
1483 static public void repaint(final Layer layer, final int extra, final Rectangle r, final boolean update_navigator) {
1484 repaint(layer, extra, r, update_navigator, true);
1487 static public void repaint(final Layer layer, final int extra, final Rectangle r, final boolean update_navigator, final boolean update_graphics) {
1488 if (repaint_disabled) return;
1489 for (final Display d : al_displays) {
1490 if (layer == d.layer) {
1491 d.canvas.setUpdateGraphics(update_graphics);
1492 d.canvas.repaint(r, extra);
1493 if (update_navigator) {
1494 d.navigator.repaint(true);
1495 Utils.updateComponent(d.tabs.getSelectedComponent());
1502 /** Repaint the given Rectangle in all Displays showing the layer, optionally updating the offscreen image (if any). */
1503 static public void repaint(final Layer layer, final Rectangle r, final int extra, final boolean update_graphics) {
1504 if (repaint_disabled) return;
1505 for (final Display d : al_displays) {
1506 if (layer == d.layer) {
1507 d.canvas.setUpdateGraphics(update_graphics);
1508 d.canvas.repaint(r, extra);
1509 d.navigator.repaint(update_graphics);
1510 if (update_graphics) Utils.updateComponent(d.tabs.getSelectedComponent());
1515 /** Repaint the DisplayablePanel (and DisplayNavigator) only for the given Displayable, in all Displays showing the given Layer. */
1516 static public void repaint(final Layer layer, final Displayable displ) {
1517 if (repaint_disabled) return;
1518 for (final Display d : al_displays) {
1519 if (layer == d.layer) {
1520 DisplayablePanel dp = d.ht_panels.get(displ);
1521 if (null != dp) dp.repaint();
1522 d.navigator.repaint(true);
1527 static public void repaint(LayerSet set, Displayable displ, int extra) {
1528 repaint(set, displ, null, extra);
1531 static public void repaint(LayerSet set, Displayable displ, Rectangle r, int extra) {
1532 repaint(set, displ, r, extra, true);
1535 /** Repaint the Displayable in every Display that shows a Layer belonging to the given LayerSet. */
1536 static public void repaint(final LayerSet set, final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator) {
1537 if (repaint_disabled) return;
1538 for (final Display d : al_displays) {
1539 if (set.contains(d.layer)) {
1540 if (repaint_navigator) {
1541 if (null != displ) {
1542 DisplayablePanel dp = d.ht_panels.get(displ);
1543 if (null != dp) dp.repaint();
1545 d.navigator.repaint(true);
1547 if (null == displ || displ != d.active) d.setUpdateGraphics(true); // safeguard
1548 // paint the given box or the actual Displayable's box
1549 if (null != r) d.canvas.repaint(r, extra);
1550 else d.canvas.repaint(displ, extra);
1555 /** Repaint the entire LayerSet, in all Displays showing a Layer of it.*/
1556 static public void repaint(final LayerSet set) {
1557 if (repaint_disabled) return;
1558 for (final Display d : al_displays) {
1559 if (set.contains(d.layer)) {
1560 d.navigator.repaint(true);
1561 d.canvas.repaint(true);
1565 /** Repaint the given box in the LayerSet, in all Displays showing a Layer of it.*/
1566 static public void repaint(final LayerSet set, final Rectangle box) {
1567 if (repaint_disabled) return;
1568 for (final Display d : al_displays) {
1569 if (set.contains(d.layer)) {
1570 d.navigator.repaint(box);
1571 d.canvas.repaint(box, 0, true);
1575 /** Repaint the entire Layer, in all Displays showing it, including the tabs.*/
1576 static public void repaint(final Layer layer) { // TODO this method overlaps with update(layer)
1577 if (repaint_disabled) return;
1578 for (final Display d : al_displays) {
1579 if (layer == d.layer) {
1580 d.navigator.repaint(true);
1581 d.canvas.repaint(true);
1586 /** Call repaint on all open Displays. */
1587 static public void repaint() {
1588 if (repaint_disabled) {
1589 Utils.logAll("Can't repaint -- repainting is disabled!");
1590 return;
1592 for (final Display d : al_displays) {
1593 d.navigator.repaint(true);
1594 d.canvas.repaint(true);
1598 static private boolean repaint_disabled = false;
1600 /** Set a flag to enable/disable repainting of all Display instances. */
1601 static protected void setRepaint(boolean b) {
1602 repaint_disabled = !b;
1605 public Rectangle getBounds() {
1606 return frame.getBounds();
1609 public Point getLocation() {
1610 return frame.getLocation();
1613 public JFrame getFrame() {
1614 return frame;
1617 public void setLocation(Point p) {
1618 this.frame.setLocation(p);
1621 public Displayable getActive() {
1622 return active; //TODO this should return selection.active !!
1625 public void select(Displayable d) {
1626 select(d, false);
1629 /** Select/deselect accordingly to the current state and the shift key. */
1630 public void select(final Displayable d, final boolean shift_down) {
1631 if (null != active && active != d && active.getClass() != Patch.class) {
1632 // active is being deselected, so link underlying patches
1633 active.linkPatches();
1635 if (null == d) {
1636 //Utils.log2("Display.select: clearing selection");
1637 canvas.setUpdateGraphics(true);
1638 selection.clear();
1639 return;
1641 if (!shift_down) {
1642 //Utils.log2("Display.select: single selection");
1643 if (d != active) {
1644 selection.clear();
1645 selection.add(d);
1647 } else if (selection.contains(d)) {
1648 if (active == d) {
1649 selection.remove(d);
1650 //Utils.log2("Display.select: removing from a selection");
1651 } else {
1652 //Utils.log2("Display.select: activing within a selection");
1653 selection.setActive(d);
1655 } else {
1656 //Utils.log2("Display.select: adding to an existing selection");
1657 selection.add(d);
1659 // update the image shown to ImageJ
1660 // NO longer necessary, always he same FakeImagePlus // setTempCurrentImage();
1663 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p, final Class c) {
1664 choose(screen_x_p, screen_y_p, x_p, y_p, false, c);
1666 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p) {
1667 choose(screen_x_p, screen_y_p, x_p, y_p, false, null);
1670 /** 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. */
1671 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p, boolean shift_down, Class c) {
1672 //Utils.log("Display.choose: x,y " + x_p + "," + y_p);
1673 final ArrayList<Displayable> al = new ArrayList<Displayable>(layer.find(x_p, y_p, true));
1674 al.addAll(layer.getParent().findZDisplayables(layer, x_p, y_p, true)); // only visible ones
1675 if (al.isEmpty()) {
1676 Displayable act = this.active;
1677 selection.clear();
1678 canvas.setUpdateGraphics(true);
1679 //Utils.log("choose: set active to null");
1680 // fixing lack of repainting for unknown reasons, of the active one TODO this is a temporary solution
1681 if (null != act) Display.repaint(layer, act, 5);
1682 } else if (1 == al.size()) {
1683 Displayable d = (Displayable)al.get(0);
1684 if (null != c && d.getClass() != c) {
1685 selection.clear();
1686 return;
1688 select(d, shift_down);
1689 //Utils.log("choose 1: set active to " + active);
1690 } else {
1691 if (al.contains(active) && !shift_down) {
1692 // do nothing
1693 } else {
1694 if (null != c) {
1695 // check if at least one of them is of class c
1696 // if only one is of class c, set as selected
1697 // else show menu
1698 for (Iterator it = al.iterator(); it.hasNext(); ) {
1699 Object ob = it.next();
1700 if (ob.getClass() != c) it.remove();
1702 if (0 == al.size()) {
1703 // deselect
1704 selection.clear();
1705 return;
1707 if (1 == al.size()) {
1708 select((Displayable)al.get(0), shift_down);
1709 return;
1711 // else, choose among the many
1713 choose(screen_x_p, screen_y_p, al, shift_down, x_p, y_p);
1715 //Utils.log("choose many: set active to " + active);
1719 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) {
1720 // show a popup on the canvas to choose
1721 new Thread() {
1722 public void run() {
1723 final Object lock = new Object();
1724 final DisplayableChooser d_chooser = new DisplayableChooser(al, lock);
1725 final JPopupMenu pop = new JPopupMenu("Select:");
1726 final Iterator itu = al.iterator();
1727 while (itu.hasNext()) {
1728 Displayable d = (Displayable)itu.next();
1729 JMenuItem menu_item = new JMenuItem(d.toString());
1730 menu_item.addActionListener(d_chooser);
1731 pop.add(menu_item);
1734 new Thread() {
1735 public void run() {
1736 pop.show(canvas, screen_x_p, screen_y_p);
1738 }.start();
1740 //now wait until selecting something
1741 synchronized(lock) {
1742 do {
1743 try {
1744 lock.wait();
1745 } catch (InterruptedException ie) {}
1746 } while (d_chooser.isWaiting() && pop.isShowing());
1749 //grab the chosen Displayable object
1750 Displayable d = d_chooser.getChosen();
1751 //Utils.log("Chosen: " + d.toString());
1752 if (null == d) { Utils.log2("Display.choose: returning a null!"); }
1753 select(d, shift_down);
1754 pop.setVisible(false);
1756 // fix selection bug: never receives mouseReleased event when the popup shows
1757 selection.mouseReleased(null, x_p, y_p, x_p, y_p, x_p, y_p);
1759 }.start();
1762 /** 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. */
1763 protected void setActive(final Displayable displ) {
1764 final Displayable prev_active = this.active;
1765 this.active = displ;
1766 SwingUtilities.invokeLater(new Runnable() { public void run() {
1768 // renew current image if necessary
1769 if (null != displ && displ == prev_active) {
1770 // make sure the proper tab is selected.
1771 selectTab(displ);
1772 return; // the same
1774 // deactivate previously active
1775 if (null != prev_active) {
1776 final DisplayablePanel ob = ht_panels.get(prev_active);
1777 if (null != ob) ob.setActive(false);
1778 // erase "decorations" of the previously active
1779 canvas.repaint(selection.getBox(), 4);
1781 // activate the new active
1782 if (null != displ) {
1783 final DisplayablePanel ob = ht_panels.get(displ);
1784 if (null != ob) ob.setActive(true);
1785 updateInDatabase("active_displayable_id");
1786 if (displ.getClass() != Patch.class) project.select(displ); // select the node in the corresponding tree, if any.
1787 // select the proper tab, and scroll to visible
1788 selectTab(displ);
1789 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
1790 repaint(displ, null, 5, false, update_graphics); // to show the border, and to repaint out of the background image
1791 transp_slider.setValue((int)(displ.getAlpha() * 100));
1792 } else {
1793 //ensure decorations are removed from the panels, for Displayables in a selection besides the active one
1794 Utils.updateComponent(tabs.getSelectedComponent());
1796 }});
1799 /** If the other paints under the base. */
1800 public boolean paintsBelow(Displayable base, Displayable other) {
1801 boolean zd_base = base instanceof ZDisplayable;
1802 boolean zd_other = other instanceof ZDisplayable;
1803 if (zd_other) {
1804 if (base instanceof DLabel) return true; // zd paints under label
1805 if (!zd_base) return false; // any zd paints over a mere displ if not a label
1806 else {
1807 // both zd, compare indices
1808 ArrayList<ZDisplayable> al = other.getLayerSet().getZDisplayables();
1809 return al.indexOf(base) > al.indexOf(other);
1811 } else {
1812 if (!zd_base) {
1813 // both displ, compare indices
1814 ArrayList<Displayable> al = other.getLayer().getDisplayables();
1815 return al.indexOf(base) > al.indexOf(other);
1816 } else {
1817 // base is zd, other is d
1818 if (other instanceof DLabel) return false;
1819 return true;
1824 /** 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. */
1825 private void selectTab(final Displayable displ) {
1826 Method method = null;
1827 try {
1828 if (!(displ instanceof LayerSet)) {
1829 method = Display.class.getDeclaredMethod("selectTab", new Class[]{displ.getClass()});
1831 } catch (Exception e) {
1832 IJError.print(e);
1834 if (null != method) {
1835 final Method me = method;
1836 dispatcher.exec(new Runnable() { public void run() {
1837 try {
1838 me.setAccessible(true);
1839 me.invoke(Display.this, new Object[]{displ});
1840 } catch (Exception e) { IJError.print(e); }
1841 }});
1845 private void selectTab(Patch patch) {
1846 tabs.setSelectedComponent(scroll_patches);
1847 scrollToShow(scroll_patches, ht_panels.get(patch));
1850 private void selectTab(Profile profile) {
1851 tabs.setSelectedComponent(scroll_profiles);
1852 scrollToShow(scroll_profiles, ht_panels.get(profile));
1855 private void selectTab(DLabel label) {
1856 tabs.setSelectedComponent(scroll_labels);
1857 scrollToShow(scroll_labels, ht_panels.get(label));
1860 private void selectTab(ZDisplayable zd) {
1861 tabs.setSelectedComponent(scroll_zdispl);
1862 scrollToShow(scroll_zdispl, ht_panels.get(zd));
1865 private void selectTab(Pipe d) { selectTab((ZDisplayable)d); }
1866 private void selectTab(Polyline d) { selectTab((ZDisplayable)d); }
1867 private void selectTab(AreaList d) { selectTab((ZDisplayable)d); }
1868 private void selectTab(Ball d) { selectTab((ZDisplayable)d); }
1869 private void selectTab(Dissector d) { selectTab((ZDisplayable)d); }
1871 /** 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). */
1872 private void updateTab(final Container tab, final String label, final ArrayList al) {
1873 final boolean[] recreated = new boolean[]{false, true, true};
1874 dispatcher.execSwing(new Runnable() { public void run() {
1875 try {
1876 if (0 == al.size()) {
1877 tab.removeAll();
1878 tab.add(new JLabel("No " + label + "."));
1879 } else {
1880 Component[] comp = tab.getComponents();
1881 int next = 0;
1882 if (1 == comp.length && comp[0].getClass() == JLabel.class) {
1883 next = 1;
1884 tab.remove(0);
1886 for (Iterator it = al.iterator(); it.hasNext(); ) {
1887 Displayable d = (Displayable)it.next();
1888 DisplayablePanel dp = null;
1889 if (next < comp.length) {
1890 dp = (DisplayablePanel)comp[next++]; // recycling panels
1891 dp.set(d);
1892 } else {
1893 dp = new DisplayablePanel(Display.this, d);
1894 tab.add(dp);
1896 ht_panels.put(d, dp);
1898 if (next < comp.length) {
1899 // remove from the end, to avoid potential repaints of other panels
1900 for (int i=comp.length-1; i>=next; i--) {
1901 tab.remove(i);
1904 recreated[0] = true;
1906 if (recreated[0]) {
1907 tab.invalidate();
1908 tab.validate();
1909 tab.repaint();
1911 if (null != Display.this.active) scrollToShow(Display.this.active);
1912 } catch (Throwable e) { IJError.print(e); }
1913 }});
1916 static public void setActive(final Object event, final Displayable displ) {
1917 if (!(event instanceof InputEvent)) return;
1918 // find which Display
1919 for (final Display d : al_displays) {
1920 if (d.isOrigin((InputEvent)event)) {
1921 d.setActive(displ);
1922 break;
1927 /** Find out whether this Display is Transforming its active Displayable. */
1928 public boolean isTransforming() {
1929 return canvas.isTransforming();
1932 /** Find whether any Display is transforming the given Displayable. */
1933 static public boolean isTransforming(final Displayable displ) {
1934 for (final Display d : al_displays) {
1935 if (null != d.active && d.active == displ && d.canvas.isTransforming()) return true;
1937 return false;
1940 static public boolean isAligning(final LayerSet set) {
1941 for (final Display d : al_displays) {
1942 if (d.layer.getParent() == set && set.isAligning()) {
1943 return true;
1946 return false;
1949 /** Set the front Display to transform the Displayable only if no other canvas is transforming it. */
1950 static public void setTransforming(final Displayable displ) {
1951 if (null == front) return;
1952 if (front.active != displ) return;
1953 for (final Display d : al_displays) {
1954 if (d.active == displ) {
1955 if (d.canvas.isTransforming()) {
1956 Utils.showMessage("Already transforming " + displ.getTitle());
1957 return;
1961 front.canvas.setTransforming(true);
1964 /** Check whether the source of the event is located in this instance.*/
1965 private boolean isOrigin(InputEvent event) {
1966 Object source = event.getSource();
1967 // find it ... check the canvas for now TODO
1968 if (canvas == source) {
1969 return true;
1971 return false;
1974 /** Get the layer of the front Display, or null if none.*/
1975 static public Layer getFrontLayer() {
1976 if (null == front) return null;
1977 return front.layer;
1980 /** Get the layer of an open Display of the given Project, or null if none.*/
1981 static public Layer getFrontLayer(final Project project) {
1982 if (null == front) return null;
1983 if (front.project == project) return front.layer;
1984 // else, find an open Display for the given Project, if any
1985 for (final Display d : al_displays) {
1986 if (d.project == project) {
1987 d.frame.toFront();
1988 return d.layer;
1991 return null; // none found
1994 static public Display getFront(final Project project) {
1995 if (null == front) return null;
1996 if (front.project == project) return front;
1997 for (final Display d : al_displays) {
1998 if (d.project == project) {
1999 d.frame.toFront();
2000 return d;
2003 return null;
2006 public boolean isReadOnly() {
2007 // TEMPORARY: in the future one will be able show displays as read-only to other people, remotely
2008 return false;
2011 static public void showPopup(Component c, int x, int y) {
2012 if (null != front) front.getPopupMenu().show(c, x, y);
2015 /** Return a context-sensitive popup menu. */
2016 public JPopupMenu getPopupMenu() { // called from canvas
2017 // get the job canceling dialog
2018 if (!canvas.isInputEnabled()) {
2019 return project.getLoader().getJobsPopup(this);
2022 // create new
2023 this.popup = new JPopupMenu();
2024 JMenuItem item = null;
2025 JMenu menu = null;
2027 if (ProjectToolbar.ALIGN == Toolbar.getToolId()) {
2028 boolean aligning = layer.getParent().isAligning();
2029 item = new JMenuItem("Cancel alignment"); item.addActionListener(this); popup.add(item);
2030 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2031 if (!aligning) item.setEnabled(false);
2032 item = new JMenuItem("Align with landmarks"); item.addActionListener(this); popup.add(item);
2033 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true));
2034 if (!aligning) item.setEnabled(false);
2035 item = new JMenuItem("Align and register"); item.addActionListener(this); popup.add(item);
2036 if (!aligning) item.setEnabled(false);
2037 item = new JMenuItem("Align using profiles"); item.addActionListener(this); popup.add(item);
2038 if (!aligning || selection.isEmpty() || !selection.contains(Profile.class)) item.setEnabled(false);
2039 item = new JMenuItem("Align stack slices"); item.addActionListener(this); popup.add(item);
2040 if (selection.isEmpty() || ! (getActive().getClass() == Patch.class && ((Patch)getActive()).isStack())) item.setEnabled(false);
2041 item = new JMenuItem("Align layers"); item.addActionListener(this); popup.add(item);
2042 if (1 == layer.getParent().size()) item.setEnabled(false);
2043 item = new JMenuItem("Align multi-layer mosaic"); item.addActionListener(this); popup.add(item);
2044 if (1 == layer.getParent().size()) item.setEnabled(false);
2045 return popup;
2049 JMenu adjust_menu = new JMenu("Adjust");
2051 if (null != active) {
2052 if (!canvas.isTransforming()) {
2053 if (active instanceof Profile) {
2054 item = new JMenuItem("Duplicate, link and send to next layer"); item.addActionListener(this); popup.add(item);
2055 Layer nl = layer.getParent().next(layer);
2056 if (nl == layer) item.setEnabled(false);
2057 item = new JMenuItem("Duplicate, link and send to previous layer"); item.addActionListener(this); popup.add(item);
2058 nl = layer.getParent().previous(layer);
2059 if (nl == layer) item.setEnabled(false);
2061 menu = new JMenu("Duplicate, link and send to");
2062 ArrayList al = layer.getParent().getLayers();
2063 Iterator it = al.iterator();
2064 int i = 1;
2065 while (it.hasNext()) {
2066 Layer la = (Layer)it.next();
2067 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
2068 if (la == this.layer) item.setEnabled(false);
2069 i++;
2071 popup.add(menu);
2072 item = new JMenuItem("Duplicate, link and send to..."); item.addActionListener(this); popup.add(item);
2074 popup.addSeparator();
2076 item = new JMenuItem("Unlink from images"); item.addActionListener(this); popup.add(item);
2077 if (!active.isLinked()) item.setEnabled(false); // isLinked() checks if it's linked to a Patch in its own layer
2078 item = new JMenuItem("Show in 3D"); item.addActionListener(this); popup.add(item);
2079 popup.addSeparator();
2080 } else if (active instanceof Patch) {
2081 item = new JMenuItem("Unlink from images"); item.addActionListener(this); popup.add(item);
2082 if (!active.isLinked(Patch.class)) item.setEnabled(false);
2083 if (((Patch)active).isStack()) {
2084 item = new JMenuItem("Unlink slices"); item.addActionListener(this); popup.add(item);
2086 int n_sel_patches = selection.getSelected(Patch.class).size();
2087 if (1 == n_sel_patches) {
2088 item = new JMenuItem("Snap"); item.addActionListener(this); popup.add(item);
2089 } else if (n_sel_patches > 1) {
2090 item = new JMenuItem("Montage"); item.addActionListener(this); popup.add(item);
2091 item = new JMenuItem("Lens correction"); item.addActionListener(this); popup.add(item);
2092 item = new JMenuItem("Blend"); item.addActionListener(this); popup.add(item);
2094 item = new JMenuItem("Link images..."); item.addActionListener(this); popup.add(item);
2095 item = new JMenuItem("View volume"); item.addActionListener(this); popup.add(item);
2096 HashSet hs = active.getLinked(Patch.class);
2097 if (null == hs || 0 == hs.size()) item.setEnabled(false);
2098 item = new JMenuItem("View orthoslices"); item.addActionListener(this); popup.add(item);
2099 if (null == hs || 0 == hs.size()) item.setEnabled(false); // if no Patch instances among the directly linked, then it's not a stack
2100 popup.addSeparator();
2101 } else {
2102 item = new JMenuItem("Unlink"); item.addActionListener(this); popup.add(item);
2103 item = new JMenuItem("Show in 3D"); item.addActionListener(this); popup.add(item);
2104 popup.addSeparator();
2106 if (active instanceof AreaList) {
2107 item = new JMenuItem("Merge"); item.addActionListener(this); popup.add(item);
2108 ArrayList al = selection.getSelected();
2109 int n = 0;
2110 for (Iterator it = al.iterator(); it.hasNext(); ) {
2111 if (it.next().getClass() == AreaList.class) n++;
2113 if (n < 2) item.setEnabled(false);
2114 } else if (active instanceof Pipe) {
2115 item = new JMenuItem("Identify..."); item.addActionListener(this); popup.add(item);
2116 item = new JMenuItem("Identify with axes..."); item.addActionListener(this); popup.add(item);
2119 if (canvas.isTransforming()) {
2120 item = new JMenuItem("Apply transform"); item.addActionListener(this); popup.add(item);
2121 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
2122 } else {
2123 item = new JMenuItem("Transform"); item.addActionListener(this); popup.add(item);
2124 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0, true));
2126 item = new JMenuItem("Cancel transform"); item.addActionListener(this); popup.add(item);
2127 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2128 if (!canvas.isTransforming()) item.setEnabled(false);
2129 if (canvas.isTransforming()) {
2130 item = new JMenuItem("Specify transform..."); item.addActionListener(this); popup.add(item);
2133 if (!canvas.isTransforming()) {
2134 item = new JMenuItem("Color..."); item.addActionListener(this); popup.add(item);
2135 if (active instanceof LayerSet) item.setEnabled(false);
2136 if (active.isLocked()) {
2137 item = new JMenuItem("Unlock"); item.addActionListener(this); popup.add(item);
2138 } else {
2139 item = new JMenuItem("Lock"); item.addActionListener(this); popup.add(item);
2141 menu = new JMenu("Move");
2142 popup.addSeparator();
2143 LayerSet ls = layer.getParent();
2144 item = new JMenuItem("Move to top"); item.addActionListener(this); menu.add(item);
2145 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.
2146 if (ls.isTop(active)) item.setEnabled(false);
2147 item = new JMenuItem("Move up"); item.addActionListener(this); menu.add(item);
2148 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0, true));
2149 if (ls.isTop(active)) item.setEnabled(false);
2150 item = new JMenuItem("Move down"); item.addActionListener(this); menu.add(item);
2151 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0, true));
2152 if (ls.isBottom(active)) item.setEnabled(false);
2153 item = new JMenuItem("Move to bottom"); item.addActionListener(this); menu.add(item);
2154 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_END, 0, true));
2155 if (ls.isBottom(active)) item.setEnabled(false);
2157 popup.add(menu);
2158 popup.addSeparator();
2159 item = new JMenuItem("Delete..."); item.addActionListener(this); popup.add(item);
2160 try {
2161 if (active instanceof Patch) {
2162 if (!active.isOnlyLinkedTo(Patch.class)) {
2163 item.setEnabled(false);
2165 } else if (!(active instanceof DLabel)) { // can't delete elements from the trees (Profile, Pipe, LayerSet)
2166 item.setEnabled(false);
2168 } catch (Exception e) { IJError.print(e); item.setEnabled(false); }
2170 if (active instanceof Patch) {
2171 item = new JMenuItem("Revert"); item.addActionListener(this); popup.add(item);
2172 popup.addSeparator();
2174 item = new JMenuItem("Properties..."); item.addActionListener(this); popup.add(item);
2175 item = new JMenuItem("Show centered"); item.addActionListener(this); popup.add(item);
2177 popup.addSeparator();
2179 if (! (active instanceof ZDisplayable)) {
2180 ArrayList al_layers = layer.getParent().getLayers();
2181 int i_layer = al_layers.indexOf(layer);
2182 int n_layers = al_layers.size();
2183 item = new JMenuItem("Send to previous layer"); item.addActionListener(this); popup.add(item);
2184 if (1 == n_layers || 0 == i_layer || active.isLinked()) item.setEnabled(false);
2185 // 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
2186 else if (active instanceof Profile && !active.canSendTo(layer.getParent().previous(layer))) item.setEnabled(false);
2187 item = new JMenuItem("Send to next layer"); item.addActionListener(this); popup.add(item);
2188 if (1 == n_layers || n_layers -1 == i_layer || active.isLinked()) item.setEnabled(false);
2189 else if (active instanceof Profile && !active.canSendTo(layer.getParent().next(layer))) item.setEnabled(false);
2192 menu = new JMenu("Send linked group to...");
2193 if (active.hasLinkedGroupWithinLayer(this.layer)) {
2194 int i = 1;
2195 for (final Layer la : ls.getLayers()) {
2196 String layer_title = i + ": " + la.getTitle();
2197 if (-1 == layer_title.indexOf(' ')) layer_title += " ";
2198 item = new JMenuItem(layer_title); item.addActionListener(this); menu.add(item);
2199 if (la == this.layer) item.setEnabled(false);
2200 i++;
2202 popup.add(menu);
2203 } else {
2204 menu.setEnabled(false);
2205 //Utils.log("Active's linked group not within layer.");
2207 popup.add(menu);
2208 popup.addSeparator();
2213 if (!canvas.isTransforming()) {
2215 item = new JMenuItem("Undo");item.addActionListener(this); popup.add(item);
2216 if (!layer.getParent().canUndo() || canvas.isTransforming()) item.setEnabled(false);
2217 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Utils.getControlModifier(), true));
2218 item = new JMenuItem("Redo");item.addActionListener(this); popup.add(item);
2219 if (!layer.getParent().canRedo() || canvas.isTransforming()) item.setEnabled(false);
2220 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Event.ALT_MASK, true));
2222 item = new JMenuItem("Enhance contrast layer-wise..."); item.addActionListener(this); adjust_menu.add(item);
2223 item = new JMenuItem("Enhance contrast (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
2224 if (selection.isEmpty()) item.setEnabled(false);
2225 item = new JMenuItem("Set Min and Max layer-wise..."); item.addActionListener(this); adjust_menu.add(item);
2226 item = new JMenuItem("Set Min and Max (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
2227 if (selection.isEmpty()) item.setEnabled(false);
2228 popup.add(adjust_menu);
2229 popup.addSeparator();
2231 // Would get so much simpler with a clojure macro ...
2233 try {
2234 menu = new JMenu("Hide/Unhide");
2235 item = new JMenuItem("Hide deselected"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.SHIFT_MASK, true));
2236 boolean none = 0 == selection.getNSelected();
2237 if (none) item.setEnabled(false);
2238 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));
2239 if (none) item.setEnabled(false);
2240 item = new JMenuItem("Hide selected"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, 0, true));
2241 if (none) item.setEnabled(false);
2242 none = ! layer.getParent().containsDisplayable(DLabel.class);
2243 item = new JMenuItem("Hide all labels"); item.addActionListener(this); menu.add(item);
2244 if (none) item.setEnabled(false);
2245 item = new JMenuItem("Unhide all labels"); item.addActionListener(this); menu.add(item);
2246 if (none) item.setEnabled(false);
2247 none = ! layer.getParent().contains(AreaList.class);
2248 item = new JMenuItem("Hide all arealists"); item.addActionListener(this); menu.add(item);
2249 if (none) item.setEnabled(false);
2250 item = new JMenuItem("Unhide all arealists"); item.addActionListener(this); menu.add(item);
2251 if (none) item.setEnabled(false);
2252 none = ! layer.contains(Profile.class);
2253 item = new JMenuItem("Hide all profiles"); item.addActionListener(this); menu.add(item);
2254 if (none) item.setEnabled(false);
2255 item = new JMenuItem("Unhide all profiles"); item.addActionListener(this); menu.add(item);
2256 if (none) item.setEnabled(false);
2257 none = ! layer.getParent().contains(Pipe.class);
2258 item = new JMenuItem("Hide all pipes"); item.addActionListener(this); menu.add(item);
2259 if (none) item.setEnabled(false);
2260 item = new JMenuItem("Unhide all pipes"); item.addActionListener(this); menu.add(item);
2261 if (none) item.setEnabled(false);
2262 none = ! layer.getParent().contains(Polyline.class);
2263 item = new JMenuItem("Hide all polylines"); item.addActionListener(this); menu.add(item);
2264 if (none) item.setEnabled(false);
2265 item = new JMenuItem("Unhide all polylines"); item.addActionListener(this); menu.add(item);
2266 if (none) item.setEnabled(false);
2267 none = ! layer.getParent().contains(Ball.class);
2268 item = new JMenuItem("Hide all balls"); item.addActionListener(this); menu.add(item);
2269 if (none) item.setEnabled(false);
2270 item = new JMenuItem("Unhide all balls"); item.addActionListener(this); menu.add(item);
2271 if (none) item.setEnabled(false);
2272 none = ! layer.getParent().containsDisplayable(Patch.class);
2273 item = new JMenuItem("Hide all images"); item.addActionListener(this); menu.add(item);
2274 if (none) item.setEnabled(false);
2275 item = new JMenuItem("Unhide all images"); item.addActionListener(this); menu.add(item);
2276 if (none) item.setEnabled(false);
2277 item = new JMenuItem("Hide all but images"); item.addActionListener(this); menu.add(item);
2278 item = new JMenuItem("Unhide all"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.ALT_MASK, true));
2280 popup.add(menu);
2281 } catch (Exception e) { IJError.print(e); }
2283 menu = new JMenu("Import");
2284 item = new JMenuItem("Import image"); item.addActionListener(this); menu.add(item);
2285 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, Event.ALT_MASK & Event.SHIFT_MASK, true));
2286 item = new JMenuItem("Import stack..."); item.addActionListener(this); menu.add(item);
2287 item = new JMenuItem("Import grid..."); item.addActionListener(this); menu.add(item);
2288 item = new JMenuItem("Import sequence as grid..."); item.addActionListener(this); menu.add(item);
2289 item = new JMenuItem("Import from text file..."); item.addActionListener(this); menu.add(item);
2290 item = new JMenuItem("Import labels as arealists..."); item.addActionListener(this); menu.add(item);
2291 popup.add(menu);
2293 menu = new JMenu("Export");
2294 item = new JMenuItem("Make flat image..."); item.addActionListener(this); menu.add(item);
2295 item = new JMenuItem("Arealists as labels (tif)"); item.addActionListener(this); menu.add(item);
2296 if (0 == layer.getParent().getZDisplayables(AreaList.class).size()) item.setEnabled(false);
2297 item = new JMenuItem("Arealists as labels (amira)"); item.addActionListener(this); menu.add(item);
2298 if (0 == layer.getParent().getZDisplayables(AreaList.class).size()) item.setEnabled(false);
2299 popup.add(menu);
2301 menu = new JMenu("Display");
2302 item = new JMenuItem("Resize canvas/LayerSet..."); item.addActionListener(this); menu.add(item);
2303 item = new JMenuItem("Autoresize canvas/LayerSet"); item.addActionListener(this); menu.add(item);
2304 // OBSOLETE // item = new JMenuItem("Rotate Layer/LayerSet..."); item.addActionListener(this); menu.add(item);
2305 item = new JMenuItem("Properties ..."); item.addActionListener(this); menu.add(item);
2306 popup.add(menu);
2308 menu = new JMenu("Project");
2309 this.project.getLoader().setupMenuItems(menu, this.getProject());
2310 item = new JMenuItem("Project properties..."); item.addActionListener(this); menu.add(item);
2311 item = new JMenuItem("Create subproject"); item.addActionListener(this); menu.add(item);
2312 if (null == canvas.getFakeImagePlus().getRoi()) item.setEnabled(false);
2313 item = new JMenuItem("Release memory..."); item.addActionListener(this); menu.add(item);
2314 item = new JMenuItem("Flush image cache"); item.addActionListener(this); menu.add(item);
2315 popup.add(menu);
2317 menu = new JMenu("Selection");
2318 item = new JMenuItem("Select all"); item.addActionListener(this); menu.add(item);
2319 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, Utils.getControlModifier(), true));
2320 if (0 == layer.getDisplayables().size() && 0 == layer.getParent().getZDisplayables().size()) item.setEnabled(false);
2321 item = new JMenuItem("Select none"); item.addActionListener(this); menu.add(item);
2322 if (0 == selection.getNSelected()) item.setEnabled(false);
2323 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2325 JMenu bytype = new JMenu("Select all by type");
2326 item = new JMenuItem("AreaList"); item.addActionListener(bytypelistener); bytype.add(item);
2327 item = new JMenuItem("Ball"); item.addActionListener(bytypelistener); bytype.add(item);
2328 item = new JMenuItem("Dissector"); item.addActionListener(bytypelistener); bytype.add(item);
2329 item = new JMenuItem("Image"); item.addActionListener(bytypelistener); bytype.add(item);
2330 item = new JMenuItem("Text"); item.addActionListener(bytypelistener); bytype.add(item);
2331 item = new JMenuItem("Pipe"); item.addActionListener(bytypelistener); bytype.add(item);
2332 item = new JMenuItem("Polyline"); item.addActionListener(bytypelistener); bytype.add(item);
2333 item = new JMenuItem("Profile"); item.addActionListener(bytypelistener); bytype.add(item);
2334 menu.add(bytype);
2336 item = new JMenuItem("Restore selection"); item.addActionListener(this); menu.add(item);
2337 item = new JMenuItem("Select under ROI"); item.addActionListener(this); menu.add(item);
2338 if (canvas.getFakeImagePlus().getRoi() == null) item.setEnabled(false);
2339 popup.add(menu);
2340 item = new JMenuItem("Search..."); item.addActionListener(this); popup.add(item);
2343 //canvas.add(popup);
2344 return popup;
2347 private ByTypeListener bytypelistener = new ByTypeListener(this);
2349 static private class ByTypeListener implements ActionListener {
2350 final Display d;
2351 ByTypeListener(final Display d) {
2352 this.d = d;
2354 public void actionPerformed(final ActionEvent ae) {
2355 final String command = ae.getActionCommand();
2357 final java.awt.geom.Area aroi = Utils.getArea(d.canvas.getFakeImagePlus().getRoi());
2359 d.dispatcher.exec(new Runnable() { public void run() {
2361 try {
2362 String type = command;
2363 if (type.equals("Image")) type = "Patch";
2364 Class c = Class.forName("ini.trakem2.display." + type);
2366 java.util.List<Displayable> a = new ArrayList<Displayable>();
2367 if (null != aroi) {
2368 a.addAll(d.layer.getDisplayables(c, aroi, true));
2369 a.addAll(d.layer.getParent().getZDisplayables(c, d.layer, aroi, true));
2370 } else {
2371 a.addAll(d.layer.getDisplayables(c));
2372 a.addAll(d.layer.getParent().getZDisplayables(c));
2373 // Remove non-visible ones
2374 for (final Iterator<Displayable> it = a.iterator(); it.hasNext(); ) {
2375 if (!it.next().isVisible()) it.remove();
2379 if (0 == a.size()) return;
2381 boolean selected = false;
2383 if (0 == ae.getModifiers()) {
2384 Utils.log2("first");
2385 d.selection.clear();
2386 d.selection.selectAll(a);
2387 selected = true;
2388 } else if (0 == (ae.getModifiers() ^ Event.SHIFT_MASK)) {
2389 Utils.log2("with shift");
2390 d.selection.selectAll(a); // just add them to the current selection
2391 selected = true;
2393 if (selected) {
2394 // Activate last:
2395 d.selection.setActive(a.get(a.size() -1));
2398 } catch (ClassNotFoundException e) {
2399 Utils.log2(e.toString());
2402 }});
2406 /** Check if a panel for the given Displayable is completely visible in the JScrollPane */
2407 public boolean isWithinViewport(final Displayable d) {
2408 final JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
2409 if (ht_tabs.get(d.getClass()) == scroll) return isWithinViewport(scroll, ht_panels.get(d));
2410 return false;
2413 private boolean isWithinViewport(JScrollPane scroll, DisplayablePanel dp) {
2414 if(null == dp) return false;
2415 JViewport view = scroll.getViewport();
2416 java.awt.Dimension dimensions = view.getExtentSize();
2417 java.awt.Point p = view.getViewPosition();
2418 int y = dp.getY();
2419 if ((y + DisplayablePanel.HEIGHT - p.y) <= dimensions.height && y >= p.y) {
2420 return true;
2422 return false;
2425 /** Check if a panel for the given Displayable is partially visible in the JScrollPane */
2426 public boolean isPartiallyWithinViewport(final Displayable d) {
2427 final JScrollPane scroll = ht_tabs.get(d.getClass());
2428 if (tabs.getSelectedComponent() == scroll) return isPartiallyWithinViewport(scroll, ht_panels.get(d));
2429 return false;
2432 /** Check if a panel for the given Displayable is at least partially visible in the JScrollPane */
2433 private boolean isPartiallyWithinViewport(final JScrollPane scroll, final DisplayablePanel dp) {
2434 if(null == dp) {
2435 //Utils.log2("Display.isPartiallyWithinViewport: null DisplayablePanel ??");
2436 return false; // to fast for you baby
2438 JViewport view = scroll.getViewport();
2439 java.awt.Dimension dimensions = view.getExtentSize();
2440 java.awt.Point p = view.getViewPosition();
2441 int y = dp.getY();
2442 if ( ((y + DisplayablePanel.HEIGHT - p.y) <= dimensions.height && y >= p.y) // completely visible
2443 || ((y + DisplayablePanel.HEIGHT - p.y) > dimensions.height && y < p.y + dimensions.height) // partially hovering at the bottom
2444 || ((y + DisplayablePanel.HEIGHT) > p.y && y < p.y) // partially hovering at the top
2446 return true;
2448 return false;
2451 /** A function to make a Displayable panel be visible in the screen, by scrolling the viewport of the JScrollPane. */
2452 private void scrollToShow(final Displayable d) {
2453 dispatcher.execSwing(new Runnable() { public void run() {
2454 final JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
2455 if (d instanceof ZDisplayable && scroll == scroll_zdispl) {
2456 scrollToShow(scroll_zdispl, ht_panels.get(d));
2457 return;
2459 final Class c = d.getClass();
2460 if (Patch.class == c && scroll == scroll_patches) {
2461 scrollToShow(scroll_patches, ht_panels.get(d));
2462 } else if (DLabel.class == c && scroll == scroll_labels) {
2463 scrollToShow(scroll_labels, ht_panels.get(d));
2464 } else if (Profile.class == c && scroll == scroll_profiles) {
2465 scrollToShow(scroll_profiles, ht_panels.get(d));
2467 }});
2470 private void scrollToShow(final JScrollPane scroll, final DisplayablePanel dp) {
2471 if (null == dp) return;
2472 JViewport view = scroll.getViewport();
2473 Point current = view.getViewPosition();
2474 Dimension extent = view.getExtentSize();
2475 int panel_y = dp.getY();
2476 if ((panel_y + DisplayablePanel.HEIGHT - current.y) <= extent.height && panel_y >= current.y) {
2477 // it's completely visible already
2478 return;
2479 } else {
2480 // scroll just enough
2481 // if it's above, show at the top
2482 if (panel_y - current.y < 0) {
2483 view.setViewPosition(new Point(0, panel_y));
2485 // if it's below (even if partially), show at the bottom
2486 else if (panel_y + 50 > current.y + extent.height) {
2487 view.setViewPosition(new Point(0, panel_y - extent.height + 50));
2488 //Utils.log("Display.scrollToShow: panel_y: " + panel_y + " current.y: " + current.y + " extent.height: " + extent.height);
2493 /** Update the title of the given Displayable in its DisplayablePanel, if any. */
2494 static public void updateTitle(final Layer layer, final Displayable displ) {
2495 for (final Display d : al_displays) {
2496 if (layer == d.layer) {
2497 DisplayablePanel dp = d.ht_panels.get(displ);
2498 if (null != dp) dp.updateTitle();
2503 /** Update the Display's title in all Displays showing the given Layer. */
2504 static public void updateTitle(final Layer layer) {
2505 for (final Display d : al_displays) {
2506 if (d.layer == layer) {
2507 d.updateTitle();
2511 /** Update the Display's title in all Displays showing a Layer of the given LayerSet. */
2512 static public void updateTitle(final LayerSet ls) {
2513 for (final Display d : al_displays) {
2514 if (d.layer.getParent() == ls) {
2515 d.updateTitle();
2520 /** Set a new title in the JFrame, showing info on the layer 'z' and the magnification. */
2521 public void updateTitle() {
2522 // From ij.ImagePlus class, the solution:
2523 String scale = "";
2524 final double magnification = canvas.getMagnification();
2525 if (magnification!=1.0) {
2526 final double percent = magnification*100.0;
2527 scale = new StringBuffer(" (").append(Utils.d2s(percent, percent==(int)percent ? 0 : 1)).append("%)").toString();
2529 final Calibration cal = layer.getParent().getCalibration();
2530 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();
2531 frame.setTitle(title);
2532 // fix the title for the FakeImageWindow and thus the WindowManager listing in the menus
2533 canvas.getFakeImagePlus().setTitle(title);
2536 /** 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. */
2537 public void nextLayer(final int modifiers) {
2538 //setLayer(layer.getParent().next(layer));
2539 //scroller.setValue(layer.getParent().getLayerIndex(layer.getId()));
2540 if (0 == (modifiers ^ Event.SHIFT_MASK)) {
2541 slt.set(layer.getParent().nextNonEmpty(layer));
2542 } else if (scroll_step > 1) {
2543 int i = layer.getParent().indexOf(this.layer);
2544 Layer la = layer.getParent().getLayer(i + scroll_step);
2545 if (null != la) slt.set(la);
2546 } else {
2547 slt.set(layer.getParent().next(layer));
2549 updateInDatabase("layer_id");
2552 /** Calls setLayer(la) on the SetLayerThread. */
2553 public void toLayer(final Layer la) {
2554 if (la.getParent() != layer.getParent()) return; // not of the same LayerSet
2555 if (la == layer) return; // nothing to do
2556 slt.set(la);
2557 updateInDatabase("layer_id");
2560 /** 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. */
2561 public void previousLayer(final int modifiers) {
2562 //setLayer(layer.getParent().previous(layer));
2563 //scroller.setValue(layer.getParent().getLayerIndex(layer.getId()));
2564 if (0 == (modifiers ^ Event.SHIFT_MASK)) {
2565 slt.set(layer.getParent().previousNonEmpty(layer));
2566 } else if (scroll_step > 1) {
2567 int i = layer.getParent().indexOf(this.layer);
2568 Layer la = layer.getParent().getLayer(i - scroll_step);
2569 if (null != la) slt.set(la);
2570 } else {
2571 slt.set(layer.getParent().previous(layer));
2573 updateInDatabase("layer_id");
2576 static public void updateLayerScroller(LayerSet set) {
2577 for (final Display d : al_displays) {
2578 if (d.layer.getParent() == set) {
2579 d.updateLayerScroller(d.layer);
2584 private void updateLayerScroller(Layer layer) {
2585 int size = layer.getParent().size();
2586 if (size <= 1) {
2587 scroller.setValues(0, 1, 0, 0);
2588 scroller.setEnabled(false);
2589 } else {
2590 scroller.setEnabled(true);
2591 scroller.setValues(layer.getParent().getLayerIndex(layer.getId()), 1, 0, size);
2595 private void updateSnapshots() {
2596 Enumeration<DisplayablePanel> e = ht_panels.elements();
2597 while (e.hasMoreElements()) {
2598 e.nextElement().remake();
2600 Utils.updateComponent(tabs.getSelectedComponent());
2603 static public void updatePanel(Layer layer, final Displayable displ) {
2604 if (null == layer && null != front) layer = front.layer; // the front layer
2605 for (final Display d : al_displays) {
2606 if (d.layer == layer) {
2607 d.updatePanel(displ);
2612 private void updatePanel(Displayable d) {
2613 JPanel c = null;
2614 if (d instanceof Profile) {
2615 c = panel_profiles;
2616 } else if (d instanceof Patch) {
2617 c = panel_patches;
2618 } else if (d instanceof DLabel) {
2619 c = panel_labels;
2620 } else if (d instanceof Pipe) {
2621 c = panel_zdispl;
2623 if (null == c) return;
2624 DisplayablePanel dp = ht_panels.get(d);
2625 dp.remake();
2626 Utils.updateComponent(c);
2629 static public void updatePanelIndex(final Layer layer, final Displayable displ) {
2630 for (final Display d : al_displays) {
2631 if (d.layer == layer || displ instanceof ZDisplayable) {
2632 d.updatePanelIndex(displ);
2637 private void updatePanelIndex(final Displayable d) {
2638 // find first of the kind, then remove and insert its panel
2639 int i = 0;
2640 JPanel c = null;
2641 if (d instanceof ZDisplayable) {
2642 i = layer.getParent().indexOf((ZDisplayable)d);
2643 c = panel_zdispl;
2644 } else {
2645 i = layer.relativeIndexOf(d);
2646 if (d instanceof Profile) {
2647 c = panel_profiles;
2648 } else if (d instanceof Patch) {
2649 c = panel_patches;
2650 } else if (d instanceof DLabel) {
2651 c = panel_labels;
2654 if (null == c) return;
2655 DisplayablePanel dp = ht_panels.get(d);
2656 if (null == dp) return; // may be half-baked, wait
2657 c.remove(dp);
2658 c.add(dp, i); // java and its fabulous consistency
2659 // not enough! Utils.updateComponent(c);
2660 // So, cocktail:
2661 c.invalidate();
2662 c.validate();
2663 Utils.updateComponent(c);
2666 /** Repair possibly missing panels and other components by simply resetting the same Layer */
2667 public void repairGUI() {
2668 Layer layer = this.layer;
2669 this.layer = null;
2670 setLayer(layer);
2673 public void actionPerformed(final ActionEvent ae) {
2674 dispatcher.exec(new Runnable() { public void run() {
2676 String command = ae.getActionCommand();
2677 if (command.startsWith("Job")) {
2678 if (Utils.checkYN("Really cancel job?")) {
2679 project.getLoader().quitJob(command);
2680 repairGUI();
2682 return;
2683 } else if (command.equals("Move to top")) {
2684 if (null == active) return;
2685 canvas.setUpdateGraphics(true);
2686 layer.getParent().move(LayerSet.TOP, active);
2687 Display.repaint(layer.getParent(), active, 5);
2688 //Display.updatePanelIndex(layer, active);
2689 } else if (command.equals("Move up")) {
2690 if (null == active) return;
2691 canvas.setUpdateGraphics(true);
2692 layer.getParent().move(LayerSet.UP, active);
2693 Display.repaint(layer.getParent(), active, 5);
2694 //Display.updatePanelIndex(layer, active);
2695 } else if (command.equals("Move down")) {
2696 if (null == active) return;
2697 canvas.setUpdateGraphics(true);
2698 layer.getParent().move(LayerSet.DOWN, active);
2699 Display.repaint(layer.getParent(), active, 5);
2700 //Display.updatePanelIndex(layer, active);
2701 } else if (command.equals("Move to bottom")) {
2702 if (null == active) return;
2703 canvas.setUpdateGraphics(true);
2704 layer.getParent().move(LayerSet.BOTTOM, active);
2705 Display.repaint(layer.getParent(), active, 5);
2706 //Display.updatePanelIndex(layer, active);
2707 } else if (command.equals("Duplicate, link and send to next layer")) {
2708 duplicateLinkAndSendTo(active, 1, layer.getParent().next(layer));
2709 } else if (command.equals("Duplicate, link and send to previous layer")) {
2710 duplicateLinkAndSendTo(active, 0, layer.getParent().previous(layer));
2711 } else if (command.equals("Duplicate, link and send to...")) {
2712 // fix non-scrolling popup menu
2713 GenericDialog gd = new GenericDialog("Send to");
2714 gd.addMessage("Duplicate, link and send to...");
2715 String[] sl = new String[layer.getParent().size()];
2716 int next = 0;
2717 for (Iterator it = layer.getParent().getLayers().iterator(); it.hasNext(); ) {
2718 sl[next++] = project.findLayerThing(it.next()).toString();
2720 gd.addChoice("Layer: ", sl, sl[layer.getParent().indexOf(layer)]);
2721 gd.showDialog();
2722 if (gd.wasCanceled()) return;
2723 Layer la = layer.getParent().getLayer(gd.getNextChoiceIndex());
2724 if (layer == la) {
2725 Utils.showMessage("Can't duplicate, link and send to the same layer.");
2726 return;
2728 duplicateLinkAndSendTo(active, 0, la);
2729 } else if (-1 != command.indexOf("z = ")) {
2730 // this is an item from the "Duplicate, link and send to" menu of layer z's
2731 Layer target_layer = layer.getParent().getLayer(Double.parseDouble(command.substring(command.lastIndexOf(' ') +1)));
2732 Utils.log2("layer: __" +command.substring(command.lastIndexOf(' ') +1) + "__");
2733 if (null == target_layer) return;
2734 duplicateLinkAndSendTo(active, 0, target_layer);
2735 } else if (-1 != command.indexOf("z=")) {
2736 // WARNING the indexOf is very similar to the previous one
2737 // Send the linked group to the selected layer
2738 int iz = command.indexOf("z=")+2;
2739 Utils.log2("iz=" + iz + " other: " + command.indexOf(' ', iz+2));
2740 int end = command.indexOf(' ', iz);
2741 if (-1 == end) end = command.length();
2742 double lz = Double.parseDouble(command.substring(iz, end));
2743 Layer target = layer.getParent().getLayer(lz);
2744 HashSet hs = active.getLinkedGroup(new HashSet());
2745 layer.getParent().move(hs, active.getLayer(), target);
2746 } else if (command.equals("Unlink")) {
2747 if (null == active || active instanceof Patch) return;
2748 active.unlink();
2749 updateSelection();//selection.update();
2750 } else if (command.equals("Unlink from images")) {
2751 if (null == active) return;
2752 try {
2753 for (Displayable displ: selection.getSelected()) {
2754 displ.unlinkAll(Patch.class);
2756 updateSelection();//selection.update();
2757 } catch (Exception e) { IJError.print(e); }
2758 } else if (command.equals("Unlink slices")) {
2759 YesNoCancelDialog yn = new YesNoCancelDialog(frame, "Attention", "Really unlink all slices from each other?\nThere is no undo.");
2760 if (!yn.yesPressed()) return;
2761 final ArrayList<Patch> pa = ((Patch)active).getStackPatches();
2762 for (int i=pa.size()-1; i>0; i--) {
2763 pa.get(i).unlink(pa.get(i-1));
2765 } else if (command.equals("Send to next layer")) {
2766 Rectangle box = selection.getBox();
2767 try {
2768 // unlink Patch instances
2769 for (final Displayable displ : selection.getSelected()) {
2770 displ.unlinkAll(Patch.class);
2772 updateSelection();//selection.update();
2773 } catch (Exception e) { IJError.print(e); }
2774 //layer.getParent().moveDown(layer, active); // will repaint whatever appropriate layers
2775 selection.moveDown();
2776 repaint(layer.getParent(), box);
2777 } else if (command.equals("Send to previous layer")) {
2778 Rectangle box = selection.getBox();
2779 try {
2780 // unlink Patch instances
2781 for (final Displayable displ : selection.getSelected()) {
2782 displ.unlinkAll(Patch.class);
2784 updateSelection();//selection.update();
2785 } catch (Exception e) { IJError.print(e); }
2786 //layer.getParent().moveUp(layer, active); // will repaint whatever appropriate layers
2787 selection.moveUp();
2788 repaint(layer.getParent(), box);
2789 } else if (command.equals("Show centered")) {
2790 if (active == null) return;
2791 showCentered(active);
2792 } else if (command.equals("Delete...")) {
2794 if (null != active) {
2795 Displayable d = active;
2796 selection.remove(d);
2797 d.remove(true); // will repaint
2800 // remove all selected objects
2801 selection.deleteAll();
2802 } else if (command.equals("Color...")) {
2803 IJ.doCommand("Color Picker...");
2804 } else if (command.equals("Revert")) {
2805 if (null == active || active.getClass() != Patch.class) return;
2806 Patch p = (Patch)active;
2807 if (!p.revert()) {
2808 if (null == p.getOriginalPath()) Utils.log("No editions to save for patch " + p.getTitle() + " #" + p.getId());
2809 else Utils.log("Could not revert Patch " + p.getTitle() + " #" + p.getId());
2811 } else if (command.equals("Undo")) {
2812 Bureaucrat.createAndStart(new Worker.Task("Undo") { public void exec() {
2813 layer.getParent().undoOneStep();
2814 Display.repaint(layer.getParent());
2815 }}, project);
2816 } else if (command.equals("Redo")) {
2817 Bureaucrat.createAndStart(new Worker.Task("Redo") { public void exec() {
2818 layer.getParent().redoOneStep();
2819 Display.repaint(layer.getParent());
2820 }}, project);
2821 } else if (command.equals("Transform")) {
2822 if (null == active) return;
2823 canvas.setTransforming(true);
2824 } else if (command.equals("Apply transform")) {
2825 if (null == active) return;
2826 canvas.setTransforming(false);
2827 } else if (command.equals("Cancel transform")) {
2828 if (null == active) return;
2829 canvas.cancelTransform();
2830 } else if (command.equals("Specify transform...")) {
2831 if (null == active) return;
2832 selection.specify();
2833 } else if (command.equals("Hide all but images")) {
2834 ArrayList<Class> type = new ArrayList<Class>();
2835 type.add(Patch.class);
2836 selection.removeAll(layer.getParent().hideExcept(type, false));
2837 Display.update(layer.getParent(), false);
2838 } else if (command.equals("Unhide all")) {
2839 layer.getParent().setAllVisible(false);
2840 Display.update(layer.getParent(), false);
2841 } else if (command.startsWith("Hide all ")) {
2842 String type = command.substring(9, command.length() -1); // skip the ending plural 's'
2843 type = type.substring(0, 1).toUpperCase() + type.substring(1);
2844 selection.removeAll(layer.getParent().setVisible(type, false, true));
2845 } else if (command.startsWith("Unhide all ")) {
2846 String type = command.substring(11, command.length() -1); // skip the ending plural 's'
2847 type = type.substring(0, 1).toUpperCase() + type.substring(1);
2848 layer.getParent().setVisible(type, true, true);
2849 } else if (command.equals("Hide deselected")) {
2850 hideDeselected(0 != (ActionEvent.ALT_MASK & ae.getModifiers()));
2851 } else if (command.equals("Hide deselected except images")) {
2852 hideDeselected(true);
2853 } else if (command.equals("Hide selected")) {
2854 selection.setVisible(false); // TODO should deselect them too? I don't think so.
2855 } else if (command.equals("Resize canvas/LayerSet...")) {
2856 resizeCanvas();
2857 } else if (command.equals("Autoresize canvas/LayerSet")) {
2858 layer.getParent().setMinimumDimensions();
2859 } else if (command.equals("Import image")) {
2860 importImage();
2861 } else if (command.equals("Import next image")) {
2862 importNextImage();
2863 } else if (command.equals("Import stack...")) {
2864 Display.this.getLayerSet().addLayerContentStep(layer);
2865 Rectangle sr = getCanvas().getSrcRect();
2866 Bureaucrat burro = project.getLoader().importStack(layer, sr.x + sr.width/2, sr.y + sr.height/2, null, true, null);
2867 burro.addPostTask(new Runnable() { public void run() {
2868 Display.this.getLayerSet().addLayerContentStep(layer);
2869 }});
2870 } else if (command.equals("Import grid...")) {
2871 Display.this.getLayerSet().addLayerContentStep(layer);
2872 Bureaucrat burro = project.getLoader().importGrid(layer);
2873 burro.addPostTask(new Runnable() { public void run() {
2874 Display.this.getLayerSet().addLayerContentStep(layer);
2875 }});
2876 } else if (command.equals("Import sequence as grid...")) {
2877 Display.this.getLayerSet().addLayerContentStep(layer);
2878 Bureaucrat burro = project.getLoader().importSequenceAsGrid(layer);
2879 burro.addPostTask(new Runnable() { public void run() {
2880 Display.this.getLayerSet().addLayerContentStep(layer);
2881 }});
2882 } else if (command.equals("Import from text file...")) {
2883 Display.this.getLayerSet().addLayerContentStep(layer);
2884 Bureaucrat burro = project.getLoader().importImages(layer);
2885 burro.addPostTask(new Runnable() { public void run() {
2886 Display.this.getLayerSet().addLayerContentStep(layer);
2887 }});
2888 } else if (command.equals("Import labels as arealists...")) {
2889 Display.this.getLayerSet().addChangeTreesStep();
2890 Bureaucrat burro = project.getLoader().importLabelsAsAreaLists(layer, null, Double.MAX_VALUE, 0, 0.4f, false);
2891 burro.addPostTask(new Runnable() { public void run() {
2892 Display.this.getLayerSet().addChangeTreesStep();
2893 }});
2894 } else if (command.equals("Make flat image...")) {
2895 // if there's a ROI, just use that as cropping rectangle
2896 Rectangle srcRect = null;
2897 Roi roi = canvas.getFakeImagePlus().getRoi();
2898 if (null != roi) {
2899 srcRect = roi.getBounds();
2900 } else {
2901 // otherwise, whatever is visible
2902 //srcRect = canvas.getSrcRect();
2903 // The above is confusing. That is what ROIs are for. So paint all:
2904 srcRect = new Rectangle(0, 0, (int)Math.ceil(layer.getParent().getLayerWidth()), (int)Math.ceil(layer.getParent().getLayerHeight()));
2906 double scale = 1.0;
2907 final String[] types = new String[]{"8-bit grayscale", "RGB Color"};
2908 int the_type = ImagePlus.GRAY8;
2909 final GenericDialog gd = new GenericDialog("Choose", frame);
2910 gd.addSlider("Scale: ", 1, 100, 100);
2911 gd.addChoice("Type: ", types, types[0]);
2912 if (layer.getParent().size() > 1) {
2914 String[] layers = new String[layer.getParent().size()];
2915 int i = 0;
2916 for (Iterator it = layer.getParent().getLayers().iterator(); it.hasNext(); ) {
2917 layers[i] = layer.getProject().findLayerThing((Layer)it.next()).toString();
2918 i++;
2920 int i_layer = layer.getParent().indexOf(layer);
2921 gd.addChoice("Start: ", layers, layers[i_layer]);
2922 gd.addChoice("End: ", layers, layers[i_layer]);
2924 Utils.addLayerRangeChoices(Display.this.layer, gd); /// $#%! where are my lisp macros
2925 gd.addCheckbox("Include non-empty layers only", true);
2927 gd.addMessage("Background color:");
2928 Utils.addRGBColorSliders(gd, Color.black);
2929 gd.addCheckbox("Best quality", false);
2930 gd.addMessage("");
2931 gd.addCheckbox("Save to file", false);
2932 gd.addCheckbox("Save for web", false);
2933 gd.showDialog();
2934 if (gd.wasCanceled()) return;
2935 scale = gd.getNextNumber() / 100;
2936 the_type = (0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY8 : ImagePlus.COLOR_RGB);
2937 if (Double.isNaN(scale) || scale <= 0.0) {
2938 Utils.showMessage("Invalid scale.");
2939 return;
2941 Layer[] layer_array = null;
2942 boolean non_empty_only = false;
2943 if (layer.getParent().size() > 1) {
2944 non_empty_only = gd.getNextBoolean();
2945 int i_start = gd.getNextChoiceIndex();
2946 int i_end = gd.getNextChoiceIndex();
2947 ArrayList al = new ArrayList();
2948 ArrayList al_zd = layer.getParent().getZDisplayables();
2949 ZDisplayable[] zd = new ZDisplayable[al_zd.size()];
2950 al_zd.toArray(zd);
2951 for (int i=i_start, j=0; i <= i_end; i++, j++) {
2952 Layer la = layer.getParent().getLayer(i);
2953 if (!la.isEmpty() || !non_empty_only) al.add(la); // checks both the Layer and the ZDisplayable objects in the parent LayerSet
2955 if (0 == al.size()) {
2956 Utils.showMessage("All layers are empty!");
2957 return;
2959 layer_array = new Layer[al.size()];
2960 al.toArray(layer_array);
2961 } else {
2962 layer_array = new Layer[]{Display.this.layer};
2964 final Color background = new Color((int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
2965 final boolean quality = gd.getNextBoolean();
2966 final boolean save_to_file = gd.getNextBoolean();
2967 final boolean save_for_web = gd.getNextBoolean();
2968 // in its own thread
2969 if (save_for_web) project.getLoader().makePrescaledTiles(layer_array, Patch.class, srcRect, scale, c_alphas, the_type);
2970 else project.getLoader().makeFlatImage(layer_array, srcRect, scale, c_alphas, the_type, save_to_file, quality, background);
2972 } else if (command.equals("Lock")) {
2973 selection.setLocked(true);
2974 } else if (command.equals("Unlock")) {
2975 selection.setLocked(false);
2976 } else if (command.equals("Properties...")) {
2977 active.adjustProperties();
2978 updateSelection();
2979 } else if (command.equals("Cancel alignment")) {
2980 layer.getParent().cancelAlign();
2981 } else if (command.equals("Align with landmarks")) {
2982 layer.getParent().applyAlign(false);
2983 } else if (command.equals("Align and register")) {
2984 layer.getParent().applyAlign(true);
2985 } else if (command.equals("Align using profiles")) {
2986 if (!selection.contains(Profile.class)) {
2987 Utils.showMessage("No profiles are selected.");
2988 return;
2990 // ask for range of layers
2991 final GenericDialog gd = new GenericDialog("Choose range");
2992 Utils.addLayerRangeChoices(Display.this.layer, gd);
2993 gd.showDialog();
2994 if (gd.wasCanceled()) return;
2995 Layer la_start = layer.getParent().getLayer(gd.getNextChoiceIndex());
2996 Layer la_end = layer.getParent().getLayer(gd.getNextChoiceIndex());
2997 if (la_start == la_end) {
2998 Utils.showMessage("Need at least two layers.");
2999 return;
3001 if (selection.isLocked()) {
3002 Utils.showMessage("There are locked objects.");
3003 return;
3005 layer.getParent().startAlign(Display.this);
3006 layer.getParent().applyAlign(la_start, la_end, selection);
3007 } else if (command.equals("Align stack slices")) {
3008 if (getActive() instanceof Patch) {
3009 final Patch slice = (Patch)getActive();
3010 if (slice.isStack()) {
3011 // check linked group
3012 final HashSet hs = slice.getLinkedGroup(new HashSet());
3013 for (Iterator it = hs.iterator(); it.hasNext(); ) {
3014 if (it.next().getClass() != Patch.class) {
3015 Utils.showMessage("Images are linked to other objects, can't proceed to cross-correlate them."); // labels should be fine, need to check that
3016 return;
3019 final LayerSet ls = slice.getLayerSet();
3020 final HashSet<Displayable> linked = slice.getLinkedGroup(null);
3021 ls.addTransformStep(linked);
3022 Bureaucrat burro = Registration.registerStackSlices((Patch)getActive()); // will repaint
3023 burro.addPostTask(new Runnable() { public void run() {
3024 // The current state when done
3025 ls.addTransformStep(linked);
3026 }});
3027 } else {
3028 Utils.log("Align stack slices: selected image is not part of a stack.");
3031 } else if (command.equals("Align layers")) {
3032 final Layer la = layer;; // caching, since scroll wheel may change it
3033 la.getParent().addTransformStep(la);
3034 Bureaucrat burro = AlignTask.alignLayersLinearlyTask( la );
3035 burro.addPostTask(new Runnable() { public void run() {
3036 la.getParent().addTransformStep(la);
3037 }});
3038 } else if (command.equals("Align multi-layer mosaic")) {
3039 final Layer la = layer; // caching, since scroll wheel may change it
3040 la.getParent().addTransformStep();
3041 Bureaucrat burro = AlignTask.alignMultiLayerMosaicTask( la );
3042 burro.addPostTask(new Runnable() { public void run() {
3043 la.getParent().addTransformStep();
3044 }});
3045 } else if (command.equals("Properties ...")) { // NOTE the space before the dots, to distinguish from the "Properties..." command that works on Displayable objects.
3046 GenericDialog gd = new GenericDialog("Properties", Display.this.frame);
3047 //gd.addNumericField("layer_scroll_step: ", this.scroll_step, 0);
3048 gd.addSlider("layer_scroll_step: ", 1, layer.getParent().size(), Display.this.scroll_step);
3049 gd.addChoice("snapshots_mode", LayerSet.snapshot_modes, LayerSet.snapshot_modes[layer.getParent().getSnapshotsMode()]);
3050 gd.addCheckbox("prefer_snapshots_quality", layer.getParent().snapshotsQuality());
3051 Loader lo = getProject().getLoader();
3052 boolean using_mipmaps = lo.isMipMapsEnabled();
3053 gd.addCheckbox("enable_mipmaps", using_mipmaps);
3054 String preprocessor = project.getLoader().getPreprocessor();
3055 gd.addStringField("image_preprocessor: ", null == preprocessor ? "" : preprocessor);
3056 gd.addCheckbox("enable_layer_pixels virtualization", layer.getParent().isPixelsVirtualizationEnabled());
3057 double max = layer.getParent().getLayerWidth() < layer.getParent().getLayerHeight() ? layer.getParent().getLayerWidth() : layer.getParent().getLayerHeight();
3058 gd.addSlider("max_dimension of virtualized layer pixels: ", 0, max, layer.getParent().getPixelsMaxDimension());
3059 // --------
3060 gd.showDialog();
3061 if (gd.wasCanceled()) return;
3062 // --------
3063 int sc = (int) gd.getNextNumber();
3064 if (sc < 1) sc = 1;
3065 Display.this.scroll_step = sc;
3066 updateInDatabase("scroll_step");
3068 layer.getParent().setSnapshotsMode(gd.getNextChoiceIndex());
3069 layer.getParent().setSnapshotsQuality(gd.getNextBoolean());
3071 boolean generate_mipmaps = gd.getNextBoolean();
3072 if (using_mipmaps && generate_mipmaps) {
3073 // nothing changed
3074 } else {
3075 if (using_mipmaps) { // and !generate_mipmaps
3076 lo.flushMipMaps(true);
3077 } else {
3078 // not using mipmaps before, and true == generate_mipmaps
3079 lo.generateMipMaps(layer.getParent().getDisplayables(Patch.class));
3083 final String prepro = gd.getNextString();
3084 if (!project.getLoader().setPreprocessor(prepro.trim())) {
3085 Utils.showMessage("Could NOT set the preprocessor to " + prepro);
3088 layer.getParent().setPixelsVirtualizationEnabled(gd.getNextBoolean());
3089 layer.getParent().setPixelsMaxDimension((int)gd.getNextNumber());
3090 } else if (command.equals("Search...")) {
3091 new Search();
3092 } else if (command.equals("Select all")) {
3093 selection.selectAll();
3094 repaint(Display.this.layer, selection.getBox(), 0);
3095 } else if (command.equals("Select none")) {
3096 Rectangle box = selection.getBox();
3097 selection.clear();
3098 repaint(Display.this.layer, box, 0);
3099 } else if (command.equals("Restore selection")) {
3100 selection.restore();
3101 } else if (command.equals("Select under ROI")) {
3102 Roi roi = canvas.getFakeImagePlus().getRoi();
3103 if (null == roi) return;
3104 selection.selectAll(roi, true);
3105 } else if (command.equals("Merge")) {
3106 ArrayList al_sel = selection.getSelected();
3107 // put active at the beginning, to work as the base on which other's will get merged
3108 al_sel.remove(Display.this.active);
3109 al_sel.add(0, Display.this.active);
3110 AreaList ali = AreaList.merge(al_sel);
3111 if (null != ali) {
3112 // remove all but the first from the selection
3113 for (int i=1; i<al_sel.size(); i++) {
3114 Object ob = al_sel.get(i);
3115 if (ob.getClass() == AreaList.class) {
3116 selection.remove((Displayable)ob);
3119 selection.updateTransform(ali);
3120 repaint(ali.getLayerSet(), ali, 0);
3122 } else if (command.equals("Identify...")) {
3123 // for pipes only for now
3124 if (!(active instanceof Pipe)) return;
3125 ini.trakem2.vector.Compare.findSimilar((Pipe)active);
3126 } else if (command.equals("Identify with axes...")) {
3127 if (!(active instanceof Pipe)) return;
3128 if (Project.getProjects().size() < 2) {
3129 Utils.showMessage("You need at least two projects open:\n-A reference project\n-The current project with the pipe to identify");
3130 return;
3132 ini.trakem2.vector.Compare.findSimilarWithAxes((Pipe)active);
3133 } else if (command.equals("View orthoslices")) {
3134 if (!(active instanceof Patch)) return;
3135 Display3D.showOrthoslices(((Patch)active));
3136 } else if (command.equals("View volume")) {
3137 if (!(active instanceof Patch)) return;
3138 Display3D.showVolume(((Patch)active));
3139 } else if (command.equals("Show in 3D")) {
3140 for (Iterator it = selection.getSelected(ZDisplayable.class).iterator(); it.hasNext(); ) {
3141 ZDisplayable zd = (ZDisplayable)it.next();
3142 Display3D.show(zd.getProject().findProjectThing(zd));
3144 // handle profile lists ...
3145 HashSet hs = new HashSet();
3146 for (Iterator it = selection.getSelected(Profile.class).iterator(); it.hasNext(); ) {
3147 Displayable d = (Displayable)it.next();
3148 ProjectThing profile_list = (ProjectThing)d.getProject().findProjectThing(d).getParent();
3149 if (!hs.contains(profile_list)) {
3150 Display3D.show(profile_list);
3151 hs.add(profile_list);
3154 } else if (command.equals("Snap")) {
3155 if (!(active instanceof Patch)) return;
3156 StitchingTEM.snap(getActive(), Display.this);
3157 } else if (command.equals("Blend")) {
3158 HashSet<Patch> patches = new HashSet<Patch>();
3159 for (final Displayable d : selection.getSelected()) {
3160 if (d.getClass() == Patch.class) patches.add((Patch)d);
3162 if (patches.size() > 1) {
3163 GenericDialog gd = new GenericDialog("Blending");
3164 gd.addCheckbox("Respect current alpha mask", true);
3165 gd.showDialog();
3166 if (gd.wasCanceled()) return;
3167 Blending.blend(patches, gd.getNextBoolean());
3168 } else {
3169 IJ.log("Please select more than one overlapping image.");
3171 } else if (command.equals("Montage")) {
3172 if (!(active instanceof Patch)) {
3173 Utils.showMessage("Please select only images.");
3174 return;
3176 final Set<Displayable> affected = new HashSet<Displayable>(selection.getAffected());
3177 for (final Displayable d : affected)
3178 if (d.isLinked()) {
3179 Utils.showMessage( "You cannot montage linked objects." );
3180 return;
3182 // make an undo step!
3183 final LayerSet ls = layer.getParent();
3184 ls.addTransformStep(affected);
3185 Bureaucrat burro = AlignTask.alignSelectionTask( selection );
3186 burro.addPostTask(new Runnable() { public void run() {
3187 ls.addTransformStep(affected);
3188 }});
3189 } else if (command.equals("Lens correction")) {
3190 final Layer la = layer;
3191 la.getParent().addDataEditStep(new HashSet<Displayable>(la.getParent().getDisplayables()));
3192 Bureaucrat burro = DistortionCorrectionTask.correctDistortionFromSelection( selection );
3193 burro.addPostTask(new Runnable() { public void run() {
3194 // no means to know which where modified and from which layers!
3195 la.getParent().addDataEditStep(new HashSet<Displayable>(la.getParent().getDisplayables()));
3196 }});
3197 } else if (command.equals("Link images...")) {
3198 GenericDialog gd = new GenericDialog("Options");
3199 gd.addMessage("Linking images to images (within their own layer only):");
3200 String[] options = {"all images to all images", "each image with any other overlapping image"};
3201 gd.addChoice("Link: ", options, options[1]);
3202 String[] options2 = {"selected images only", "all images in this layer", "all images in all layers"};
3203 gd.addChoice("Apply to: ", options2, options2[0]);
3204 gd.showDialog();
3205 if (gd.wasCanceled()) return;
3206 Layer lay = layer;
3207 final HashSet<Displayable> ds = new HashSet<Displayable>(lay.getParent().getDisplayables());
3208 lay.getParent().addDataEditStep(ds);
3209 boolean overlapping_only = 1 == gd.getNextChoiceIndex();
3210 switch (gd.getNextChoiceIndex()) {
3211 case 0:
3212 Patch.crosslink(selection.getSelected(Patch.class), overlapping_only);
3213 break;
3214 case 1:
3215 Patch.crosslink(lay.getDisplayables(Patch.class), overlapping_only);
3216 break;
3217 case 2:
3218 for (final Layer la : lay.getParent().getLayers()) {
3219 Patch.crosslink(la.getDisplayables(Patch.class), overlapping_only);
3221 break;
3223 lay.getParent().addDataEditStep(ds);
3224 } else if (command.equals("Enhance contrast (selected images)...")) {
3225 final Layer la = layer;
3226 final HashSet<Displayable> ds = new HashSet<Displayable>(la.getParent().getDisplayables());
3227 la.getParent().addDataEditStep(ds);
3228 ArrayList al = selection.getSelected(Patch.class);
3229 Bureaucrat burro = getProject().getLoader().homogenizeContrast(al);
3230 burro.addPostTask(new Runnable() { public void run() {
3231 la.getParent().addDataEditStep(ds);
3232 }});
3233 } else if (command.equals("Enhance contrast layer-wise...")) {
3234 // ask for range of layers
3235 final GenericDialog gd = new GenericDialog("Choose range");
3236 Utils.addLayerRangeChoices(Display.this.layer, gd);
3237 gd.showDialog();
3238 if (gd.wasCanceled()) return;
3239 java.util.List list = layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1); // exclusive end
3240 Layer[] la = new Layer[list.size()];
3241 list.toArray(la);
3242 final HashSet<Displayable> ds = new HashSet<Displayable>();
3243 for (final Layer l : la) ds.addAll(l.getDisplayables(Patch.class));
3244 getLayerSet().addDataEditStep(ds);
3245 Bureaucrat burro = project.getLoader().homogenizeContrast(la);
3246 burro.addPostTask(new Runnable() { public void run() {
3247 getLayerSet().addDataEditStep(ds);
3248 }});
3249 } else if (command.equals("Set Min and Max layer-wise...")) {
3250 Displayable active = getActive();
3251 double min = 0;
3252 double max = 0;
3253 if (null != active && active.getClass() == Patch.class) {
3254 min = ((Patch)active).getMin();
3255 max = ((Patch)active).getMax();
3257 final GenericDialog gd = new GenericDialog("Min and Max");
3258 gd.addMessage("Set min and max to all images in the layer range");
3259 Utils.addLayerRangeChoices(Display.this.layer, gd);
3260 gd.addNumericField("min: ", min, 2);
3261 gd.addNumericField("max: ", max, 2);
3262 gd.showDialog();
3263 if (gd.wasCanceled()) return;
3265 min = gd.getNextNumber();
3266 max = gd.getNextNumber();
3267 ArrayList<Displayable> al = new ArrayList<Displayable>();
3268 for (final Layer la : layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1)) { // exclusive end
3269 al.addAll(la.getDisplayables(Patch.class));
3271 final HashSet<Displayable> ds = new HashSet<Displayable>(al);
3272 getLayerSet().addDataEditStep(ds);
3273 Bureaucrat burro = project.getLoader().setMinAndMax(al, min, max);
3274 burro.addPostTask(new Runnable() { public void run() {
3275 getLayerSet().addDataEditStep(ds);
3276 }});
3277 } else if (command.equals("Set Min and Max (selected images)...")) {
3278 Displayable active = getActive();
3279 double min = 0;
3280 double max = 0;
3281 if (null != active && active.getClass() == Patch.class) {
3282 min = ((Patch)active).getMin();
3283 max = ((Patch)active).getMax();
3285 final GenericDialog gd = new GenericDialog("Min and Max");
3286 gd.addMessage("Set min and max to all selected images");
3287 gd.addNumericField("min: ", min, 2);
3288 gd.addNumericField("max: ", max, 2);
3289 gd.showDialog();
3290 if (gd.wasCanceled()) return;
3292 min = gd.getNextNumber();
3293 max = gd.getNextNumber();
3294 final HashSet<Displayable> ds = new HashSet<Displayable>(selection.getSelected(Patch.class));
3295 getLayerSet().addDataEditStep(ds);
3296 Bureaucrat burro = project.getLoader().setMinAndMax(selection.getSelected(Patch.class), min, max);
3297 burro.addPostTask(new Runnable() { public void run() {
3298 getLayerSet().addDataEditStep(ds);
3299 }});
3300 } else if (command.equals("Create subproject")) {
3301 Roi roi = canvas.getFakeImagePlus().getRoi();
3302 if (null == roi) return; // the menu item is not active unless there is a ROI
3303 Layer first, last;
3304 if (1 == layer.getParent().size()) {
3305 first = last = layer;
3306 } else {
3307 GenericDialog gd = new GenericDialog("Choose layer range");
3308 Utils.addLayerRangeChoices(layer, gd);
3309 gd.showDialog();
3310 if (gd.wasCanceled()) return;
3311 first = layer.getParent().getLayer(gd.getNextChoiceIndex());
3312 last = layer.getParent().getLayer(gd.getNextChoiceIndex());
3313 Utils.log2("first, last: " + first + ", " + last);
3315 Project sub = getProject().createSubproject(roi.getBounds(), first, last);
3316 final LayerSet subls = sub.getRootLayerSet();
3317 final Display d = new Display(sub, subls.getLayer(0));
3318 SwingUtilities.invokeLater(new Runnable() { public void run() {
3319 d.canvas.showCentered(new Rectangle(0, 0, (int)subls.getLayerWidth(), (int)subls.getLayerHeight()));
3320 }});
3321 } else if (command.startsWith("Arealists as labels")) {
3322 GenericDialog gd = new GenericDialog("Export labels");
3323 gd.addSlider("Scale: ", 1, 100, 100);
3324 final String[] options = {"All area list", "Selected area lists"};
3325 gd.addChoice("Export: ", options, options[0]);
3326 Utils.addLayerRangeChoices(layer, gd);
3327 gd.addCheckbox("Visible only", true);
3328 gd.showDialog();
3329 if (gd.wasCanceled()) return;
3330 final float scale = (float)(gd.getNextNumber() / 100);
3331 java.util.List al = 0 == gd.getNextChoiceIndex() ? layer.getParent().getZDisplayables(AreaList.class) : selection.getSelected(AreaList.class);
3332 if (null == al) {
3333 Utils.log("No area lists found to export.");
3334 return;
3336 // 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?
3337 al = (java.util.List<Displayable>) al;
3339 int first = gd.getNextChoiceIndex();
3340 int last = gd.getNextChoiceIndex();
3341 boolean visible_only = gd.getNextBoolean();
3342 if (-1 != command.indexOf("(amira)")) {
3343 AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, true, true);
3344 } else if (-1 != command.indexOf("(tif)")) {
3345 AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, false, false);
3347 } else if (command.equals("Project properties...")) {
3348 project.adjustProperties();
3349 } else if (command.equals("Release memory...")) {
3350 Bureaucrat.createAndStart(new Worker("Releasing memory") {
3351 public void run() {
3352 startedWorking();
3353 try {
3354 GenericDialog gd = new GenericDialog("Release Memory");
3355 int max = (int)(IJ.maxMemory() / 1000000);
3356 gd.addSlider("Megabytes: ", 0, max, max/2);
3357 gd.showDialog();
3358 if (!gd.wasCanceled()) {
3359 int n_mb = (int)gd.getNextNumber();
3360 project.getLoader().releaseToFit((long)n_mb*1000000);
3362 } catch (Throwable e) {
3363 IJError.print(e);
3364 } finally {
3365 finishedWorking();
3368 }, project);
3369 } else if (command.equals("Flush image cache")) {
3370 Loader.releaseAllCaches();
3371 } else {
3372 Utils.log2("Display: don't know what to do with command " + command);
3374 }});
3377 /** Update in all displays the Transform for the given Displayable if it's selected. */
3378 static public void updateTransform(final Displayable displ) {
3379 for (final Display d : al_displays) {
3380 if (d.selection.contains(displ)) d.selection.updateTransform(displ);
3384 /** Order the profiles of the parent profile_list by Z order, and fix the ProjectTree.*/
3386 private void fixZOrdering(Profile profile) {
3387 ProjectThing thing = project.findProjectThing(profile);
3388 if (null == thing) {
3389 Utils.log2("Display.fixZOrdering: null thing?");
3390 return;
3392 ((ProjectThing)thing.getParent()).fixZOrdering();
3393 project.getProjectTree().updateList(thing.getParent());
3397 /** The number of layers to scroll through with the wheel; 1 by default.*/
3398 public int getScrollStep() { return this.scroll_step; }
3400 public void setScrollStep(int scroll_step) {
3401 if (scroll_step < 1) scroll_step = 1;
3402 this.scroll_step = scroll_step;
3403 updateInDatabase("scroll_step");
3406 protected Bureaucrat importImage() {
3407 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
3408 public void run() {
3409 startedWorking();
3410 try {
3413 Rectangle srcRect = canvas.getSrcRect();
3414 int x = srcRect.x + srcRect.width / 2;
3415 int y = srcRect.y + srcRect.height/ 2;
3416 Patch p = project.getLoader().importImage(project, x, y);
3417 if (null == p) {
3418 finishedWorking();
3419 Utils.showMessage("Could not open the image.");
3420 return;
3423 Display.this.getLayerSet().addLayerContentStep(layer);
3425 layer.add(p); // will add it to the proper Displays
3427 Display.this.getLayerSet().addLayerContentStep(layer);
3430 } catch (Exception e) {
3431 IJError.print(e);
3433 finishedWorking();
3436 return Bureaucrat.createAndStart(worker, getProject());
3439 protected Bureaucrat importNextImage() {
3440 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
3441 public void run() {
3442 startedWorking();
3443 try {
3445 Rectangle srcRect = canvas.getSrcRect();
3446 int x = srcRect.x + srcRect.width / 2;// - imp.getWidth() / 2;
3447 int y = srcRect.y + srcRect.height/ 2;// - imp.getHeight()/ 2;
3448 Patch p = project.getLoader().importNextImage(project, x, y);
3449 if (null == p) {
3450 Utils.showMessage("Could not open next image.");
3451 finishedWorking();
3452 return;
3455 Display.this.getLayerSet().addLayerContentStep(layer);
3457 layer.add(p); // will add it to the proper Displays
3459 Display.this.getLayerSet().addLayerContentStep(layer);
3461 } catch (Exception e) {
3462 IJError.print(e);
3464 finishedWorking();
3467 return Bureaucrat.createAndStart(worker, getProject());
3471 /** Make the given channel have the given alpha (transparency). */
3472 public void setChannel(int c, float alpha) {
3473 int a = (int)(255 * alpha);
3474 int l = (c_alphas&0xff000000)>>24;
3475 int r = (c_alphas&0xff0000)>>16;
3476 int g = (c_alphas&0xff00)>>8;
3477 int b = c_alphas&0xff;
3478 switch (c) {
3479 case Channel.MONO:
3480 // all to the given alpha
3481 c_alphas = (l<<24) + (r<<16) + (g<<8) + b; // parenthesis are NECESSARY
3482 break;
3483 case Channel.RED:
3484 // modify only the red
3485 c_alphas = (l<<24) + (a<<16) + (g<<8) + b;
3486 break;
3487 case Channel.GREEN:
3488 c_alphas = (l<<24) + (r<<16) + (a<<8) + b;
3489 break;
3490 case Channel.BLUE:
3491 c_alphas = (l<<24) + (r<<16) + (g<<8) + a;
3492 break;
3494 //Utils.log2("c_alphas: " + c_alphas);
3495 //canvas.setUpdateGraphics(true);
3496 canvas.repaint(true);
3497 updateInDatabase("c_alphas");
3500 /** Set the channel as active and the others as inactive. */
3501 public void setActiveChannel(Channel channel) {
3502 for (int i=0; i<4; i++) {
3503 if (channel != channels[i]) channels[i].setActive(false);
3504 else channel.setActive(true);
3506 Utils.updateComponent(panel_channels);
3507 transp_slider.setValue((int)(channel.getAlpha() * 100));
3510 public int getDisplayChannelAlphas() { return c_alphas; }
3512 // rename this method and the getDisplayChannelAlphas ! They sound the same!
3513 public int getChannelAlphas() {
3514 return ((int)(channels[0].getAlpha() * 255)<<24) + ((int)(channels[1].getAlpha() * 255)<<16) + ((int)(channels[2].getAlpha() * 255)<<8) + (int)(channels[3].getAlpha() * 255);
3517 public int getChannelAlphasState() {
3518 return ((channels[0].isSelected() ? 255 : 0)<<24)
3519 + ((channels[1].isSelected() ? 255 : 0)<<16)
3520 + ((channels[2].isSelected() ? 255 : 0)<<8)
3521 + (channels[3].isSelected() ? 255 : 0);
3524 /** Show the layer in the front Display, or in a new Display if the front Display is showing a layer from a different LayerSet. */
3525 static public void showFront(final Layer layer) {
3526 Display display = front;
3527 if (null == display || display.layer.getParent() != layer.getParent()) {
3528 display = new Display(layer.getProject(), layer, null); // gets set to front
3529 } else {
3530 display.setLayer(layer);
3534 /** Show the given Displayable centered and selected. If select is false, the selection is cleared. */
3535 static public void showCentered(Layer layer, Displayable displ, boolean select, boolean shift_down) {
3536 // see if the given layer belongs to the layer set being displayed
3537 Display display = front; // to ensure thread consistency to some extent
3538 if (null == display || display.layer.getParent() != layer.getParent()) {
3539 display = new Display(layer.getProject(), layer, displ); // gets set to front
3540 } else if (display.layer != layer) {
3541 display.setLayer(layer);
3543 if (select) {
3544 if (!shift_down) display.selection.clear();
3545 display.selection.add(displ);
3546 } else {
3547 display.selection.clear();
3549 display.showCentered(displ);
3552 private final void showCentered(final Displayable displ) {
3553 if (null == displ) return;
3554 SwingUtilities.invokeLater(new Runnable() { public void run() {
3555 displ.setVisible(true);
3556 Rectangle box = displ.getBoundingBox();
3557 if (0 == box.width || 0 == box.height) {
3558 box.width = (int)layer.getLayerWidth();
3559 box.height = (int)layer.getLayerHeight();
3561 canvas.showCentered(box);
3562 scrollToShow(displ);
3563 if (displ instanceof ZDisplayable) {
3564 // scroll to first layer that has a point
3565 ZDisplayable zd = (ZDisplayable)displ;
3566 setLayer(zd.getFirstLayer());
3568 }});
3571 /** Listen to interesting updates, such as the ColorPicker and updates to Patch objects. */
3572 public void imageUpdated(ImagePlus updated) {
3573 // detect ColorPicker WARNING this will work even if the Display is not the window immediately active under the color picker.
3574 if (this == front && updated instanceof ij.plugin.ColorPicker) {
3575 if (null != active && project.isInputEnabled()) {
3576 selection.setColor(Toolbar.getForegroundColor());
3577 Display.repaint(front.layer, selection.getBox(), 0);
3579 return;
3581 // $%#@!! LUT changes don't set the image as changed
3582 //if (updated instanceof PatchStack) {
3583 // updated.changes = 1
3586 //Utils.log2("imageUpdated: " + updated + " " + updated.getClass());
3588 /* // never gets called (?)
3589 // the above is overkill. Instead:
3590 if (updated instanceof PatchStack) {
3591 Patch p = ((PatchStack)updated).getCurrentPatch();
3592 ImageProcessor ip = updated.getProcessor();
3593 p.setMinAndMax(ip.getMin(), ip.getMax());
3594 Utils.log2("setting min and max: " + ip.getMin() + ", " + ip.getMax());
3595 project.getLoader().decacheAWT(p.getId()); // including level 0, which will be editable
3596 // on repaint, it will be recreated
3597 //((PatchStack)updated).decacheAll(); // so that it will repaint with a newly created image
3601 // detect LUT changes: DONE at PatchStack, which is the active (virtual) image
3602 //Utils.log2("calling decache for " + updated);
3603 //getProject().getLoader().decache(updated);
3606 public void imageClosed(ImagePlus imp) {}
3607 public void imageOpened(ImagePlus imp) {}
3609 /** Release memory captured by the offscreen images */
3610 static public void flushAll() {
3611 for (final Display d : al_displays) {
3612 d.canvas.flush();
3614 //System.gc();
3615 Thread.yield();
3618 /** Can be null. */
3619 static public Display getFront() {
3620 return front;
3623 static public void setCursorToAll(final Cursor c) {
3624 for (final Display d : al_displays) {
3625 d.frame.setCursor(c);
3629 protected void setCursor(Cursor c) {
3630 frame.setCursor(c);
3633 /** Used by the Displayable to update the visibility checkbox in other Displays. */
3634 static protected void updateVisibilityCheckbox(final Layer layer, final Displayable displ, final Display calling_display) {
3635 //LOCKS ALL //SwingUtilities.invokeLater(new Runnable() { public void run() {
3636 for (final Display d : al_displays) {
3637 if (d == calling_display) continue;
3638 if (d.layer.contains(displ) || (displ instanceof ZDisplayable && d.layer.getParent().contains((ZDisplayable)displ))) {
3639 DisplayablePanel dp = d.ht_panels.get(displ);
3640 if (null != dp) dp.updateVisibilityCheckbox();
3643 //}});
3646 protected boolean isActiveWindow() {
3647 return frame.isActive();
3650 /** Toggle user input; pan and zoom are always enabled though.*/
3651 static public void setReceivesInput(final Project project, final boolean b) {
3652 for (final Display d : al_displays) {
3653 if (d.project == project) d.canvas.setReceivesInput(b);
3657 /** Export the DTD that defines this object. */
3658 static public void exportDTD(StringBuffer sb_header, HashSet hs, String indent) {
3659 if (hs.contains("t2_display")) return; // TODO to avoid collisions the type shoud be in a namespace such as tm2:display
3660 hs.add("t2_display");
3661 sb_header.append(indent).append("<!ELEMENT t2_display EMPTY>\n")
3662 .append(indent).append("<!ATTLIST t2_display id NMTOKEN #REQUIRED>\n")
3663 .append(indent).append("<!ATTLIST t2_display layer_id NMTOKEN #REQUIRED>\n")
3664 .append(indent).append("<!ATTLIST t2_display x NMTOKEN #REQUIRED>\n")
3665 .append(indent).append("<!ATTLIST t2_display y NMTOKEN #REQUIRED>\n")
3666 .append(indent).append("<!ATTLIST t2_display magnification NMTOKEN #REQUIRED>\n")
3667 .append(indent).append("<!ATTLIST t2_display srcrect_x NMTOKEN #REQUIRED>\n")
3668 .append(indent).append("<!ATTLIST t2_display srcrect_y NMTOKEN #REQUIRED>\n")
3669 .append(indent).append("<!ATTLIST t2_display srcrect_width NMTOKEN #REQUIRED>\n")
3670 .append(indent).append("<!ATTLIST t2_display srcrect_height NMTOKEN #REQUIRED>\n")
3671 .append(indent).append("<!ATTLIST t2_display scroll_step NMTOKEN #REQUIRED>\n")
3672 .append(indent).append("<!ATTLIST t2_display c_alphas NMTOKEN #REQUIRED>\n")
3673 .append(indent).append("<!ATTLIST t2_display c_alphas_state NMTOKEN #REQUIRED>\n")
3676 /** Export all displays of the given project as XML entries. */
3677 static public void exportXML(final Project project, final Writer writer, final String indent, final Object any) throws Exception {
3678 final StringBuffer sb_body = new StringBuffer();
3679 final String in = indent + "\t";
3680 for (final Display d : al_displays) {
3681 if (d.project != project) continue;
3682 final Rectangle r = d.frame.getBounds();
3683 final Rectangle srcRect = d.canvas.getSrcRect();
3684 final double magnification = d.canvas.getMagnification();
3685 sb_body.append(indent).append("<t2_display id=\"").append(d.id).append("\"\n")
3686 .append(in).append("layer_id=\"").append(d.layer.getId()).append("\"\n")
3687 .append(in).append("c_alphas=\"").append(d.c_alphas).append("\"\n")
3688 .append(in).append("c_alphas_state=\"").append(d.getChannelAlphasState()).append("\"\n")
3689 .append(in).append("x=\"").append(r.x).append("\"\n")
3690 .append(in).append("y=\"").append(r.y).append("\"\n")
3691 .append(in).append("magnification=\"").append(magnification).append("\"\n")
3692 .append(in).append("srcrect_x=\"").append(srcRect.x).append("\"\n")
3693 .append(in).append("srcrect_y=\"").append(srcRect.y).append("\"\n")
3694 .append(in).append("srcrect_width=\"").append(srcRect.width).append("\"\n")
3695 .append(in).append("srcrect_height=\"").append(srcRect.height).append("\"\n")
3696 .append(in).append("scroll_step=\"").append(d.scroll_step).append("\"\n")
3698 sb_body.append(indent).append("/>\n");
3700 writer.write(sb_body.toString());
3703 static public void toolChanged(final String tool_name) {
3704 Utils.log2("tool name: " + tool_name);
3705 if (!tool_name.equals("ALIGN")) {
3706 for (final Display d : al_displays) {
3707 d.layer.getParent().cancelAlign();
3712 static public void toolChanged(final int tool) {
3713 //Utils.log2("int tool is " + tool);
3714 if (ProjectToolbar.PEN == tool) {
3715 // erase bounding boxes
3716 for (final Display d : al_displays) {
3717 if (null != d.active) d.repaint(d.layer, d.selection.getBox(), 2);
3720 if (null != front) {
3721 WindowManager.setTempCurrentImage(front.canvas.getFakeImagePlus());
3725 public Selection getSelection() {
3726 return selection;
3729 public boolean isSelected(Displayable d) {
3730 return selection.contains(d);
3733 static public void updateSelection() {
3734 Display.updateSelection(null);
3736 static public void updateSelection(final Display calling) {
3737 final HashSet hs = new HashSet();
3738 for (final Display d : al_displays) {
3739 if (hs.contains(d.layer)) continue;
3740 hs.add(d.layer);
3741 if (null == d || null == d.selection) {
3742 Utils.log2("d is : "+ d + " d.selection is " + d.selection);
3743 } else {
3744 d.selection.update(); // recomputes box
3746 if (d != calling) { // TODO this is so dirty!
3747 if (d.selection.getNLinked() > 1) d.canvas.setUpdateGraphics(true); // this is overkill anyway
3748 d.canvas.repaint(d.selection.getLinkedBox(), Selection.PADDING);
3749 d.navigator.repaint(true); // everything
3754 static public void clearSelection(final Layer layer) {
3755 for (final Display d : al_displays) {
3756 if (d.layer == layer) d.selection.clear();
3759 static public void clearSelection() {
3760 for (final Display d : al_displays) {
3761 d.selection.clear();
3765 private void setTempCurrentImage() {
3766 WindowManager.setTempCurrentImage(canvas.getFakeImagePlus());
3769 /** Check if any display will paint the given Displayable at the given magnification. */
3770 static public boolean willPaint(final Displayable displ, final double magnification) {
3771 Rectangle box = null; ;
3772 for (final Display d : al_displays) {
3773 /* // Can no longer do this check, because 'magnification' is now affected by the Displayable AffineTransform! And thus it would not paint after the prePaint.
3774 if (Math.abs(d.canvas.getMagnification() - magnification) > 0.00000001) {
3775 continue;
3778 if (null == box) box = displ.getBoundingBox(null);
3779 if (d.canvas.getSrcRect().intersects(box)) {
3780 return true;
3783 return false;
3786 public void hideDeselected(final boolean not_images) {
3787 // hide deselected
3788 final ArrayList all = layer.getParent().getZDisplayables(); // a copy
3789 all.addAll(layer.getDisplayables());
3790 all.removeAll(selection.getSelected());
3791 if (not_images) all.removeAll(layer.getDisplayables(Patch.class));
3792 for (final Displayable d : (ArrayList<Displayable>)all) {
3793 if (d.isVisible()) d.setVisible(false);
3795 Display.update(layer);
3798 /** Cleanup internal lists that may contain the given Displayable. */
3799 static public void flush(final Displayable displ) {
3800 for (final Display d : al_displays) {
3801 d.selection.removeFromPrev(displ);
3805 public void resizeCanvas() {
3806 GenericDialog gd = new GenericDialog("Resize LayerSet");
3807 gd.addNumericField("new width: ", layer.getLayerWidth(), 3);
3808 gd.addNumericField("new height: ",layer.getLayerHeight(),3);
3809 gd.addChoice("Anchor: ", LayerSet.ANCHORS, LayerSet.ANCHORS[7]);
3810 gd.showDialog();
3811 if (gd.wasCanceled()) return;
3812 double new_width = gd.getNextNumber();
3813 double new_height =gd.getNextNumber();
3814 layer.getParent().setDimensions(new_width, new_height, gd.getNextChoiceIndex()); // will complain and prevent cropping existing Displayable objects
3818 // To record layer changes -- but it's annoying, this is visualization not data.
3819 static class DoSetLayer implements DoStep {
3820 final Display display;
3821 final Layer layer;
3822 DoSetLayer(final Display display) {
3823 this.display = display;
3824 this.layer = display.layer;
3826 public Displayable getD() { return null; }
3827 public boolean isEmpty() { return false; }
3828 public boolean apply(final int action) {
3829 display.setLayer(layer);
3831 public boolean isIdenticalTo(final Object ob) {
3832 if (!ob instanceof DoSetLayer) return false;
3833 final DoSetLayer dsl = (DoSetLayer) ob;
3834 return dsl.display == this.display && dsl.layer == this.layer;
3839 protected void duplicateLinkAndSendTo(final Displayable active, final int position, final Layer other_layer) {
3840 if (null == active || !(active instanceof Profile)) return;
3841 if (active.getLayer() == other_layer) return; // can't do that!
3842 Profile profile = project.getProjectTree().duplicateChild((Profile)active, position, other_layer);
3843 if (null == profile) return;
3844 active.link(profile);
3845 slt.setAndWait(other_layer);
3846 other_layer.add(profile);
3847 selection.add(profile);
3850 private final HashMap<Color,Layer> layer_channels = new HashMap<Color,Layer>();
3852 /** Set a layer to be painted as a specific color channel in the canvas.
3853 * Only Color.red and Color.blue are accepted.
3854 * Color.green is reserved for the current layer. */
3855 protected void setColorChannel(final Layer layer, final Color color) {
3856 if (Color.white == color) {
3857 // Remove
3858 for (final Iterator<Layer> it = layer_channels.values().iterator(); it.hasNext(); ) {
3859 if (it.next() == layer) {
3860 it.remove();
3861 break;
3864 canvas.repaint();
3865 } else if (Color.red == color || Color.blue == color) {
3866 // Replace or set new
3867 layer_channels.put(color, layer);
3868 // Reset all others of the same color to white
3869 for (final LayerPanel lp : layer_panels.values()) {
3870 Utils.log2("examining lp " + lp + " -- color: " + lp.getColor());
3871 if (lp.layer == layer || lp.getColor() != color) continue;
3872 Utils.log2("Setting white color to " + lp);
3873 lp.setColor(Color.white);
3875 tabs.repaint();
3876 canvas.repaint();
3877 } else {
3878 Utils.log2("Trying to set unacceptable color for layer " + layer + " : " + color);