Almost there, need to fix an rgb bug and add layer remove/add awareness hooks.
[trakem2.git] / ini / trakem2 / display / Display.java
blobd34b5794f5bff1896df4bce924521c6d27e8ff0c
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.utils.M;
46 import ini.trakem2.tree.*;
48 import javax.swing.*;
49 import javax.swing.event.*;
51 import mpicbg.trakem2.align.AlignTask;
53 import java.awt.*;
54 import java.awt.event.*;
55 import java.util.*;
56 import java.lang.reflect.Method;
57 import java.io.Writer;
59 import lenscorrection.DistortionCorrectionTask;
61 /** A Display is a class to show a Layer and enable mouse and keyboard manipulation of all its components. */
62 public final class Display extends DBObject implements ActionListener, ImageListener {
64 /** The Layer this Display is showing. */
65 private Layer layer;
67 private Displayable active = null;
68 /** All selected Displayable objects, including the active one. */
69 final private Selection selection = new Selection(this);
71 private ImagePlus last_temp = null;
73 private JFrame frame;
74 private JTabbedPane tabs;
75 private Hashtable<Class,JScrollPane> ht_tabs;
76 private JScrollPane scroll_patches;
77 private JPanel panel_patches;
78 private JScrollPane scroll_profiles;
79 private JPanel panel_profiles;
80 private JScrollPane scroll_zdispl;
81 private JPanel panel_zdispl;
82 private JScrollPane scroll_channels;
83 private JPanel panel_channels;
84 private JScrollPane scroll_labels;
85 private JPanel panel_labels;
87 private JPanel panel_layers;
88 private JScrollPane scroll_layers;
89 private Hashtable<Layer,LayerPanel> layer_panels = new Hashtable<Layer,LayerPanel>();
91 private JSlider transp_slider;
92 private DisplayNavigator navigator;
93 private JScrollBar scroller;
95 private DisplayCanvas canvas; // WARNING this is an AWT component, since it extends ImageCanvas
96 private JPanel canvas_panel; // and this is a workaround, to better (perhaps) integrate the awt canvas inside a JSplitPane
97 private JSplitPane split;
99 private JPopupMenu popup = null;
101 /** Contains the packed alphas of every channel. */
102 private int c_alphas = 0xffffffff; // all 100 % visible
103 private Channel[] channels;
105 private Hashtable<Displayable,DisplayablePanel> ht_panels = new Hashtable<Displayable,DisplayablePanel>();
107 /** Handle drop events, to insert image files. */
108 private DNDInsertImage dnd;
110 private boolean size_adjusted = false;
112 private int scroll_step = 1;
114 /** Keep track of all existing Display objects. */
115 static private ArrayList<Display> al_displays = new ArrayList<Display>();
116 /** The currently focused Display, if any. */
117 static private Display front = null;
119 /** Displays to open when all objects have been reloaded from the database. */
120 static private final Hashtable ht_later = new Hashtable();
122 /** A thread to handle user actions, for example an event sent from a popup menu. */
123 private final Dispatcher dispatcher = new Dispatcher();
125 static private WindowAdapter window_listener = new WindowAdapter() {
126 /** Unregister the closed Display. */
127 public void windowClosing(WindowEvent we) {
128 final Object source = we.getSource();
129 for (Iterator it = al_displays.iterator(); it.hasNext(); ) {
130 Display d = (Display)it.next();
131 if (source == d.frame) {
132 it.remove();
133 if (d == front) front = null;
134 d.remove(false); //calls destroy
135 break;
139 /** Set the source Display as front. */
140 public void windowActivated(WindowEvent we) {
141 // find which was it to make it be the front
142 final Object source = we.getSource();
143 for (final Display d : al_displays) {
144 if (source == d.frame) {
145 front = d;
146 // set toolbar
147 ProjectToolbar.setProjectToolbar();
148 // now, select the layer in the LayerTree
149 front.getProject().select(front.layer);
150 // finally, set the virtual ImagePlus that ImageJ will see
151 d.setTempCurrentImage();
152 // copied from ij.gui.ImageWindow, with modifications
153 if (IJ.isMacintosh() && IJ.getInstance()!=null) {
154 IJ.wait(10); // may be needed for Java 1.4 on OS X
155 d.frame.setMenuBar(ij.Menus.getMenuBar());
157 return;
160 // else, restore the ImageJ toolbar for non-project images
161 //if (!source.equals(IJ.getInstance())) {
162 // ProjectToolbar.setImageJToolbar();
165 /** Restore the ImageJ toolbar */
166 public void windowDeactivated(WindowEvent we) {
167 // 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();
169 /** Call a pack() when the window is maximized to fit the canvas correctly. */
170 public void windowStateChanged(WindowEvent we) {
171 final Object source = we.getSource();
172 for (final Display d : al_displays) {
173 if (source != d.frame) continue;
174 d.pack();
175 break;
180 static private MouseListener frame_mouse_listener = new MouseAdapter() {
181 public void mouseReleased(MouseEvent me) {
182 Object source = me.getSource();
183 for (final Display d : al_displays) {
184 if (d.frame == source) {
185 if (d.size_adjusted) {
186 d.pack();
187 d.size_adjusted = false;
188 Utils.log2("mouse released on JFrame");
190 break;
196 private int last_frame_state = frame.NORMAL;
198 // THIS WHOLE SYSTEM OF LISTENERS IS BROKEN:
199 // * when zooming in, the window growths in width a few pixels.
200 // * when enlarging the window quickly, the canvas is not resized as large as it should.
201 // -- the whole problem: swing threading, which I am not handling properly. It's hard.
202 static private ComponentListener component_listener = new ComponentAdapter() {
203 public void componentResized(ComponentEvent ce) {
204 final Display d = getDisplaySource(ce);
205 if (null != d) {
206 d.size_adjusted = true; // works in combination with mouseReleased to call pack(), avoiding infinite loops.
207 d.adjustCanvas();
208 int frame_state = d.frame.getExtendedState();
209 if (frame_state != d.last_frame_state) { // this setup avoids infinite loops (for pack() calls componentResized as well
210 d.last_frame_state = frame_state;
211 if (d.frame.ICONIFIED != frame_state) d.pack();
215 public void componentMoved(ComponentEvent ce) {
216 Display d = getDisplaySource(ce);
217 if (null != d) d.updateInDatabase("position");
219 private Display getDisplaySource(ComponentEvent ce) {
220 final Object source = ce.getSource();
221 for (final Display d : al_displays) {
222 if (source == d.frame) {
223 return d;
226 return null;
230 static private ChangeListener tabs_listener = new ChangeListener() {
231 /** Listen to tab changes. */
232 public void stateChanged(final ChangeEvent ce) {
233 final Object source = ce.getSource();
234 for (final Display d : al_displays) {
235 if (source == d.tabs) {
236 d.dispatcher.exec(new Runnable() { public void run() {
237 // creating tabs fires the event!!!
238 if (null == d.frame || null == d.canvas) return;
239 final Container tab = (Container)d.tabs.getSelectedComponent();
240 if (tab == d.scroll_channels) {
241 // find active channel if any
242 for (int i=0; i<d.channels.length; i++) {
243 if (d.channels[i].isActive()) {
244 d.transp_slider.setValue((int)(d.channels[i].getAlpha() * 100));
245 break;
248 } else {
249 // recreate contents
251 int count = tab.getComponentCount();
252 if (0 == count || (1 == count && tab.getComponent(0).getClass().equals(JLabel.class))) {
253 */ // 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)
255 String label = null;
256 ArrayList al = null;
257 JPanel p = null;
258 if (tab == d.scroll_zdispl) {
259 label = "Z-space objects";
260 al = d.layer.getParent().getZDisplayables();
261 p = d.panel_zdispl;
262 } else if (tab == d.scroll_patches) {
263 label = "Patches";
264 al = d.layer.getDisplayables(Patch.class);
265 p = d.panel_patches;
266 } else if (tab == d.scroll_labels) {
267 label = "Labels";
268 al = d.layer.getDisplayables(DLabel.class);
269 p = d.panel_labels;
270 } else if (tab == d.scroll_profiles) {
271 label = "Profiles";
272 al = d.layer.getDisplayables(Profile.class);
273 p = d.panel_profiles;
274 } else if (tab == d.scroll_layers) {
275 // nothing to do
276 return;
279 d.updateTab(p, label, al);
280 //Utils.updateComponent(d.tabs.getSelectedComponent());
281 //Utils.log2("updated tab: " + p + " with " + al.size() + " objects.");
284 if (null != d.active) {
285 // set the transp slider to the alpha value of the active Displayable if any
286 d.transp_slider.setValue((int)(d.active.getAlpha() * 100));
287 DisplayablePanel dp = d.ht_panels.get(d.active);
288 if (null != dp) dp.setActive(true);
291 }});
292 break;
298 private final ScrollLayerListener scroller_listener = new ScrollLayerListener();
300 private class ScrollLayerListener implements AdjustmentListener {
302 public void adjustmentValueChanged(final AdjustmentEvent ae) {
303 final int index = scroller.getValue();
304 slt.set(layer.getParent().getLayer(index));
308 private final SetLayerThread slt = new SetLayerThread();
310 private class SetLayerThread extends Thread {
312 private boolean go = true;
313 private Layer layer;
314 private final Lock lock = new Lock();
315 private final Lock lock2 = new Lock();
317 SetLayerThread() {
318 setPriority(Thread.NORM_PRIORITY);
319 setDaemon(true);
320 start();
323 public final void set(final Layer layer) {
324 synchronized (lock) {
325 this.layer = layer;
327 synchronized (this) {
328 notify();
332 public final void setAndWait(final Layer layer) {
333 lock2.lock();
334 set(layer);
337 public void run() {
338 while (go) {
339 while (null == this.layer) {
340 synchronized (this) {
341 try { wait(); } catch (InterruptedException ie) {}
344 Layer layer = null;
345 synchronized (lock) {
346 layer = this.layer;
347 this.layer = null;
350 if (!go) return; // after nullifying layer
352 if (null != layer) {
353 Display.this.setLayer(layer);
354 Display.this.updateInDatabase("layer_id");
356 // unlock any calls waiting on setAndWait
357 synchronized (lock2) {
358 lock2.unlock();
361 // cleanup:
362 synchronized (lock2) {
363 lock2.unlock();
367 public void waitForLayer() {
368 while (null != layer && go) {
369 try { Thread.sleep(10); } catch (Exception e) {}
373 public void quit() {
374 go = false;
378 /** Creates a new Display with adjusted magnification to fit in the screen. */
379 static public void createDisplay(final Project project, final Layer layer) {
380 SwingUtilities.invokeLater(new Runnable() { public void run() {
381 Display display = new Display(project, layer);
382 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
383 Rectangle srcRect = new Rectangle(0, 0, (int)layer.getLayerWidth(), (int)layer.getLayerHeight());
384 double mag = screen.width / layer.getLayerWidth();
385 if (mag * layer.getLayerHeight() > screen.height) mag = screen.height / layer.getLayerHeight();
386 mag = display.canvas.getLowerZoomLevel2(mag);
387 if (mag > 1.0) mag = 1.0;
388 //display.getCanvas().setup(mag, srcRect); // would call pack() at the wrong time!
389 // ... so instead: manually
390 display.getCanvas().setMagnification(mag);
391 display.getCanvas().setSrcRect(srcRect.x, srcRect.y, srcRect.width, srcRect.height);
392 display.getCanvas().setDrawingSize((int)Math.ceil(srcRect.width * mag), (int)Math.ceil(srcRect.height * mag));
394 display.updateTitle();
395 ij.gui.GUI.center(display.frame);
396 display.frame.pack();
397 }});
400 /** A new Display from scratch, to show the given Layer. */
401 public Display(Project project, final Layer layer) {
402 super(project);
403 front = this;
404 makeGUI(layer, null);
405 ImagePlus.addImageListener(this);
406 setLayer(layer);
407 this.layer = layer; // after, or it doesn't update properly
408 al_displays.add(this);
409 addToDatabase();
412 /** For reconstruction purposes. The Display will be stored in the ht_later.*/
413 public Display(Project project, long id, Layer layer, Object[] props) {
414 super(project, id);
415 synchronized (ht_later) {
416 Display.ht_later.put(this, props);
418 this.layer = layer;
421 /** Open a new Display centered around the given Displayable. */
422 public Display(Project project, Layer layer, Displayable displ) {
423 super(project);
424 front = this;
425 active = displ;
426 makeGUI(layer, null);
427 ImagePlus.addImageListener(this);
428 setLayer(layer);
429 this.layer = layer; // after set layer!
430 al_displays.add(this);
431 addToDatabase();
434 /** Reconstruct a Display from an XML entry, to be opened when everything is ready. */
435 public Display(Project project, long id, Layer layer, HashMap ht_attributes) {
436 super(project, id);
437 if (null == layer) {
438 Utils.log2("Display: need a non-null Layer for id=" + id);
439 return;
441 Rectangle srcRect = new Rectangle(0, 0, (int)layer.getLayerWidth(), (int)layer.getLayerHeight());
442 double magnification = 0.25;
443 Point p = new Point(0, 0);
444 int c_alphas = 0xffffffff;
445 int c_alphas_state = 0xffffffff;
446 for (Iterator it = ht_attributes.entrySet().iterator(); it.hasNext(); ) {
447 Map.Entry entry = (Map.Entry)it.next();
448 String key = (String)entry.getKey();
449 String data = (String)entry.getValue();
450 if (key.equals("srcrect_x")) { // reflection! Reflection!
451 srcRect.x = Integer.parseInt(data);
452 } else if (key.equals("srcrect_y")) {
453 srcRect.y = Integer.parseInt(data);
454 } else if (key.equals("srcrect_width")) {
455 srcRect.width = Integer.parseInt(data);
456 } else if (key.equals("srcrect_height")) {
457 srcRect.height = Integer.parseInt(data);
458 } else if (key.equals("magnification")) {
459 magnification = Double.parseDouble(data);
460 } else if (key.equals("x")) {
461 p.x = Integer.parseInt(data);
462 } else if (key.equals("y")) {
463 p.y = Integer.parseInt(data);
464 } else if (key.equals("c_alphas")) {
465 try {
466 c_alphas = Integer.parseInt(data);
467 } catch (Exception ex) {
468 c_alphas = 0xffffffff;
470 } else if (key.equals("c_alphas_state")) {
471 try {
472 c_alphas_state = Integer.parseInt(data);
473 } catch (Exception ex) {
474 IJError.print(ex);
475 c_alphas_state = 0xffffffff;
477 } else if (key.equals("scroll_step")) {
478 try {
479 setScrollStep(Integer.parseInt(data));
480 } catch (Exception ex) {
481 IJError.print(ex);
482 setScrollStep(1);
485 // TODO the above is insecure, in that data is not fully checked to be within bounds.
487 Object[] props = new Object[]{p, new Double(magnification), srcRect, new Long(layer.getId()), new Integer(c_alphas), new Integer(c_alphas_state)};
488 synchronized (ht_later) {
489 Display.ht_later.put(this, props);
491 this.layer = layer;
494 /** After reloading a project from the database, open the Displays that the project had. */
495 static public Bureaucrat openLater() {
496 final Hashtable ht_later_local;
497 synchronized (ht_later) {
498 if (0 == ht_later.size()) return null;
499 ht_later_local = new Hashtable(ht_later);
500 ht_later.keySet().removeAll(ht_later_local.keySet());
502 final Worker worker = new Worker("Opening displays") {
503 public void run() {
504 startedWorking();
505 try {
506 Thread.sleep(300); // waiting for Swing
508 for (Enumeration e = ht_later_local.keys(); e.hasMoreElements(); ) {
509 final Display d = (Display)e.nextElement();
510 front = d; // must be set before repainting any ZDisplayable!
511 Object[] props = (Object[])ht_later_local.get(d);
512 if (ControlWindow.isGUIEnabled()) d.makeGUI(d.layer, props);
513 d.setLayerLater(d.layer, d.layer.get(((Long)props[3]).longValue())); //important to do it after makeGUI
514 if (!ControlWindow.isGUIEnabled()) continue;
515 ImagePlus.addImageListener(d);
516 al_displays.add(d);
517 d.updateTitle();
518 // 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
519 if (d.canvas.getMagnification() > 0.499) {
520 SwingUtilities.invokeLater(new Runnable() { public void run() {
521 d.repaint(d.layer);
522 d.project.getLoader().setChanged(false);
523 Utils.log2("A set to false");
524 }});
526 d.project.getLoader().setChanged(false);
527 Utils.log2("B set to false");
529 if (null != front) front.getProject().select(front.layer);
531 } catch (Throwable t) {
532 IJError.print(t);
533 } finally {
534 finishedWorking();
538 return Bureaucrat.createAndStart(worker, ((Display)ht_later_local.keySet().iterator().next()).getProject()); // gets the project from the first Display
541 private void makeGUI(final Layer layer, final Object[] props) {
542 // gather properties
543 Point p = null;
544 double mag = 1.0D;
545 Rectangle srcRect = null;
546 if (null != props) {
547 p = (Point)props[0];
548 mag = ((Double)props[1]).doubleValue();
549 srcRect = (Rectangle)props[2];
552 // transparency slider
553 this.transp_slider = new JSlider(javax.swing.SwingConstants.HORIZONTAL, 0, 100, 100);
554 this.transp_slider.setBackground(Color.white);
555 this.transp_slider.setMinimumSize(new Dimension(250, 20));
556 this.transp_slider.setMaximumSize(new Dimension(250, 20));
557 this.transp_slider.setPreferredSize(new Dimension(250, 20));
558 TransparencySliderListener tsl = new TransparencySliderListener();
559 this.transp_slider.addChangeListener(tsl);
560 this.transp_slider.addMouseListener(tsl);
561 for (final KeyListener kl : this.transp_slider.getKeyListeners()) {
562 this.transp_slider.removeKeyListener(kl);
565 // Tabbed pane on the left
566 this.tabs = new JTabbedPane();
567 this.tabs.setMinimumSize(new Dimension(250, 300));
568 this.tabs.setBackground(Color.white);
569 this.tabs.addChangeListener(tabs_listener);
571 // Tab 1: Patches
572 this.panel_patches = makeTabPanel();
573 this.panel_patches.add(new JLabel("No patches."));
574 this.scroll_patches = makeScrollPane(panel_patches);
575 this.tabs.add("Patches", scroll_patches);
577 // Tab 2: Profiles
578 this.panel_profiles = makeTabPanel();
579 this.panel_profiles.add(new JLabel("No profiles."));
580 this.scroll_profiles = makeScrollPane(panel_profiles);
581 this.tabs.add("Profiles", scroll_profiles);
583 // Tab 3: pipes
584 this.panel_zdispl = makeTabPanel();
585 this.panel_zdispl.add(new JLabel("No objects."));
586 this.scroll_zdispl = makeScrollPane(panel_zdispl);
587 this.tabs.add("Z space", scroll_zdispl);
589 // Tab 4: channels
590 this.panel_channels = makeTabPanel();
591 this.scroll_channels = makeScrollPane(panel_channels);
592 this.channels = new Channel[4];
593 this.channels[0] = new Channel(this, Channel.MONO);
594 this.channels[1] = new Channel(this, Channel.RED);
595 this.channels[2] = new Channel(this, Channel.GREEN);
596 this.channels[3] = new Channel(this, Channel.BLUE);
597 //this.panel_channels.add(this.channels[0]);
598 this.panel_channels.add(this.channels[1]);
599 this.panel_channels.add(this.channels[2]);
600 this.panel_channels.add(this.channels[3]);
601 this.tabs.add("Opacity", scroll_channels);
603 // Tab 5: labels
604 this.panel_labels = makeTabPanel();
605 this.panel_labels.add(new JLabel("No labels."));
606 this.scroll_labels = makeScrollPane(panel_labels);
607 this.tabs.add("Labels", scroll_labels);
609 // Tab 6: layers
610 this.panel_layers = makeTabPanel();
611 this.scroll_layers = makeScrollPane(panel_layers);
612 for (final Layer la : layer.getParent().getLayers()) {
613 LayerPanel lp = new LayerPanel(this, la);
614 layer_panels.put(la, lp);
615 this.panel_layers.add(lp);
617 this.tabs.add("Layers", scroll_layers);
619 this.ht_tabs = new Hashtable<Class,JScrollPane>();
620 this.ht_tabs.put(Patch.class, scroll_patches);
621 this.ht_tabs.put(Profile.class, scroll_profiles);
622 this.ht_tabs.put(ZDisplayable.class, scroll_zdispl);
623 this.ht_tabs.put(AreaList.class, scroll_zdispl);
624 this.ht_tabs.put(Pipe.class, scroll_zdispl);
625 this.ht_tabs.put(Polyline.class, scroll_zdispl);
626 this.ht_tabs.put(Ball.class, scroll_zdispl);
627 this.ht_tabs.put(Dissector.class, scroll_zdispl);
628 this.ht_tabs.put(DLabel.class, scroll_labels);
629 // channels not included
630 // layers not included
632 // Navigator
633 this.navigator = new DisplayNavigator(this, layer.getLayerWidth(), layer.getLayerHeight());
634 // Layer scroller (to scroll slices)
635 int extent = (int)(250.0 / layer.getParent().size());
636 if (extent < 10) extent = 10;
637 this.scroller = new JScrollBar(JScrollBar.HORIZONTAL);
638 updateLayerScroller(layer);
639 this.scroller.addAdjustmentListener(scroller_listener);
642 // Left panel, contains the transp slider, the tabbed pane, the navigation panel and the layer scroller
643 JPanel left = new JPanel();
644 BoxLayout left_layout = new BoxLayout(left, BoxLayout.Y_AXIS);
645 left.setLayout(left_layout);
646 left.add(transp_slider);
647 left.add(tabs);
648 left.add(navigator);
649 left.add(scroller);
651 // Canvas
652 this.canvas = new DisplayCanvas(this, (int)Math.ceil(layer.getLayerWidth()), (int)Math.ceil(layer.getLayerHeight()));
653 this.canvas_panel = new JPanel();
654 GridBagLayout gb = new GridBagLayout();
655 this.canvas_panel.setLayout(gb);
656 GridBagConstraints c = new GridBagConstraints();
657 c.fill = GridBagConstraints.BOTH;
658 c.anchor = GridBagConstraints.NORTHWEST;
659 gb.setConstraints(this.canvas_panel, c);
660 gb.setConstraints(this.canvas, c);
662 // prevent new Displays from screweing up if input is globally disabled
663 if (!project.isInputEnabled()) this.canvas.setReceivesInput(false);
665 this.canvas_panel.add(canvas);
667 this.navigator.addMouseWheelListener(canvas);
669 this.transp_slider.addKeyListener(canvas);
671 // Split pane to contain everything
672 this.split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, left, canvas_panel);
673 this.split.setOneTouchExpandable(true); // NOT present in all L&F (?)
675 // fix
676 gb.setConstraints(split.getRightComponent(), c);
678 // JFrame to show the split pane
679 this.frame = ControlWindow.createJFrame(layer.toString());
680 if (IJ.isMacintosh() && IJ.getInstance()!=null) {
681 IJ.wait(10); // may be needed for Java 1.4 on OS X
682 this.frame.setMenuBar(ij.Menus.getMenuBar());
684 this.frame.addWindowListener(window_listener);
685 this.frame.addComponentListener(component_listener);
686 this.frame.getContentPane().add(split);
687 this.frame.addMouseListener(frame_mouse_listener);
688 //doesn't exist//this.frame.setMinimumSize(new Dimension(270, 600));
690 if (null != props) {
691 // restore canvas
692 canvas.setup(mag, srcRect);
693 // restore visibility of each channel
694 int cs = ((Integer)props[5]).intValue(); // aka c_alphas_state
695 int[] sel = new int[4];
696 sel[0] = ((cs&0xff000000)>>24);
697 sel[1] = ((cs&0xff0000)>>16);
698 sel[2] = ((cs&0xff00)>>8);
699 sel[3] = (cs&0xff);
700 // restore channel alphas
701 this.c_alphas = ((Integer)props[4]).intValue();
702 channels[0].setAlpha( (float)((c_alphas&0xff000000)>>24) / 255.0f , 0 != sel[0]);
703 channels[1].setAlpha( (float)((c_alphas&0xff0000)>>16) / 255.0f , 0 != sel[1]);
704 channels[2].setAlpha( (float)((c_alphas&0xff00)>>8) / 255.0f , 0 != sel[2]);
705 channels[3].setAlpha( (float) (c_alphas&0xff) / 255.0f , 0 != sel[3]);
706 // restore visibility in the working c_alphas
707 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);
710 if (null != active && null != layer) {
711 Rectangle r = active.getBoundingBox();
712 r.x -= r.width/2;
713 r.y -= r.height/2;
714 r.width += r.width;
715 r.height += r.height;
716 if (r.x < 0) r.x = 0;
717 if (r.y < 0) r.y = 0;
718 if (r.width > layer.getLayerWidth()) r.width = (int)layer.getLayerWidth();
719 if (r.height> layer.getLayerHeight())r.height= (int)layer.getLayerHeight();
720 double magn = layer.getLayerWidth() / (double)r.width;
721 canvas.setup(magn, r);
724 // add keyListener to the whole frame
725 this.tabs.addKeyListener(canvas);
726 this.canvas_panel.addKeyListener(canvas);
727 this.frame.addKeyListener(canvas);
729 this.frame.pack();
730 ij.gui.GUI.center(this.frame);
731 this.frame.setVisible(true);
732 ProjectToolbar.setProjectToolbar(); // doesn't get it through events
734 final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
736 if (null != props) {
737 // fix positioning outside the screen (dual to single monitor)
738 if (p.x >= 0 && p.x < screen.width - 50 && p.y >= 0 && p.y <= screen.height - 50) this.frame.setLocation(p);
739 else frame.setLocation(0, 0);
742 // fix excessive size
743 final Rectangle box = this.frame.getBounds();
744 int x = box.x;
745 int y = box.y;
746 int width = box.width;
747 int height = box.height;
748 if (box.width > screen.width) { x = 0; width = screen.width; }
749 if (box.height > screen.height) { y = 0; height = screen.height; }
750 if (x != box.x || y != box.y) {
751 this.frame.setLocation(x, y + (0 == y ? 30 : 0)); // added insets for bad window managers
752 updateInDatabase("position");
754 if (width != box.width || height != box.height) {
755 this.frame.setSize(new Dimension(width -10, height -30)); // added insets for bad window managers
757 if (null == props) {
758 // try to optimize canvas dimensions and magn
759 double magn = layer.getLayerHeight() / screen.height;
760 if (magn > 1.0) magn = 1.0;
761 long size = 0;
762 // limit magnification if appropriate
763 for (Iterator it = layer.getDisplayables(Patch.class).iterator(); it.hasNext(); ) {
764 final Patch pa = (Patch)it.next();
765 final Rectangle ba = pa.getBoundingBox();
766 size += (long)(ba.width * ba.height);
768 if (size > 10000000) canvas.setInitialMagnification(0.25); // 10 Mb
769 else {
770 this.frame.setSize(new Dimension((int)(screen.width * 0.66), (int)(screen.height * 0.66)));
774 Utils.updateComponent(tabs); // otherwise fails in FreeBSD java 1.4.2 when reconstructing
777 // Set the calibration of the FakeImagePlus to that of the LayerSet
778 ((FakeImagePlus)canvas.getFakeImagePlus()).setCalibrationSuper(layer.getParent().getCalibrationCopy());
780 // Set the FakeImagePlus as the current image
781 setTempCurrentImage();
783 // create a drag and drop listener
784 dnd = new DNDInsertImage(this);
786 // start a repainting thread
787 if (null != props) {
788 canvas.repaint(true); // repaint() is unreliable
791 // 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.
792 SwingUtilities.invokeLater(new Runnable() {
793 public void run() {
794 tabs.setMinimumSize(new Dimension(0, 100));
795 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
800 private JPanel makeTabPanel() {
801 JPanel panel = new JPanel();
802 BoxLayout layout = new BoxLayout(panel, BoxLayout.Y_AXIS);
803 panel.setLayout(layout);
804 return panel;
807 private JScrollPane makeScrollPane(Component c) {
808 JScrollPane jsp = new JScrollPane(c);
809 // adjust scrolling to use one DisplayablePanel as the minimal unit
810 jsp.getVerticalScrollBar().setBlockIncrement(DisplayablePanel.HEIGHT); // clicking within the track
811 jsp.getVerticalScrollBar().setUnitIncrement(DisplayablePanel.HEIGHT); // clicking on an arrow
812 jsp.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
813 jsp.setPreferredSize(new Dimension(250, 300));
814 jsp.setMinimumSize(new Dimension(250, 300));
815 return jsp;
818 static protected int scrollbar_width = 0;
820 public JPanel getCanvasPanel() {
821 return canvas_panel;
824 public DisplayCanvas getCanvas() {
825 return canvas;
828 public void setLayer(final Layer layer) {
829 if (null == layer || layer == this.layer) return;
830 final boolean set_zdispl = null == Display.this.layer || layer.getParent() != Display.this.layer.getParent();
831 if (selection.isTransforming()) {
832 Utils.log("Can't browse layers while transforming.\nCANCEL the transform first with the ESCAPE key or right-click -> cancel.");
833 scroller.setValue(Display.this.layer.getParent().getLayerIndex(Display.this.layer.getId()));
834 return;
836 this.layer = layer;
837 scroller.setValue(layer.getParent().getLayerIndex(layer.getId()));
839 // update the current Layer pointer in ZDisplayable objects
840 for (Iterator it = layer.getParent().getZDisplayables().iterator(); it.hasNext(); ) {
841 ((ZDisplayable)it.next()).setLayer(layer); // the active layer
844 updateVisibleTab(set_zdispl);
846 // see if a lot has to be reloaded, put the relevant ones at the end
847 project.getLoader().prepare(layer);
848 updateTitle(); // to show the new 'z'
849 // select the Layer in the LayerTree
850 project.select(Display.this.layer); // does so in a separate thread
851 // update active Displayable:
853 // deselect all except ZDisplayables
854 final ArrayList sel = selection.getSelected();
855 final Displayable last_active = Display.this.active;
856 int sel_next = -1;
857 for (Iterator it = sel.iterator(); it.hasNext(); ) {
858 Displayable d = (Displayable)it.next();
859 if (!(d instanceof ZDisplayable)) {
860 it.remove();
861 selection.remove(d);
862 if (d == last_active && sel.size() > 0) {
863 // select the last one of the remaining, if any
864 sel_next = sel.size()-1;
868 if (-1 != sel_next && sel.size() > 0) select((Displayable)sel.get(sel_next), true);
869 else if (null != last_active && last_active.getClass() == Patch.class && null != last_temp && last_temp instanceof PatchStack) {
870 Displayable d = ((PatchStack)last_temp).getPatch(layer, (Patch)last_active);
871 if (null != d) selection.add(d);
873 // TODO last_temp doesn't remain the PatchStack // Utils.log2("last_temp is: " + last_temp.getClass().getName());
875 // Keep Profile chain selected, for best ease of use:
876 if (null != last_active && last_active.getClass() == Profile.class && last_active.isLinked(Profile.class)) {
877 Displayable other = null;
878 for (final Displayable prof : last_active.getLinked(Profile.class)) {
879 if (prof.getLayer() == layer) {
880 other = prof;
881 break;
884 if (null != other) selection.add(other);
887 // repaint everything
888 navigator.repaint(true);
889 canvas.repaint(true);
891 // repaint tabs (hard as hell)
892 Utils.updateComponent(tabs);
893 // @#$%^! The above works half the times, so explicit repaint as well:
894 Component c = tabs.getSelectedComponent();
895 if (null == c) {
896 c = scroll_patches;
897 tabs.setSelectedComponent(scroll_patches);
899 Utils.updateComponent(c);
901 project.getLoader().setMassiveMode(false); // resetting if it was set true
903 // update the coloring in the ProjectTree
904 project.getProjectTree().updateUILater();
906 setTempCurrentImage();
909 static public void updateVisibleTabs() {
910 for (final Display d : al_displays) {
911 d.updateVisibleTab(true);
915 /** Recreate the tab that is being shown. */
916 public void updateVisibleTab(boolean set_zdispl) {
917 // update only the visible tab
918 switch (tabs.getSelectedIndex()) {
919 case 0:
920 ht_panels.clear();
921 updateTab(panel_patches, "Patches", layer.getDisplayables(Patch.class));
922 break;
923 case 1:
924 ht_panels.clear();
925 updateTab(panel_profiles, "Profiles", layer.getDisplayables(Profile.class));
926 break;
927 case 2:
928 if (set_zdispl) {
929 ht_panels.clear();
930 updateTab(panel_zdispl, "Z-space objects", layer.getParent().getZDisplayables());
932 break;
933 // case 3: channel opacities
934 case 4:
935 ht_panels.clear();
936 updateTab(panel_labels, "Labels", layer.getDisplayables(DLabel.class));
937 break;
942 private void setLayerLater(final Layer layer, final Displayable active) {
943 if (null == layer) return;
944 this.layer = layer;
945 if (!ControlWindow.isGUIEnabled()) return;
946 SwingUtilities.invokeLater(new Runnable() { public void run() {
947 // empty the tabs, except channels and pipes
948 clearTab(panel_profiles, "Profiles");
949 clearTab(panel_patches, "Patches");
950 clearTab(panel_labels, "Labels");
951 // distribute Displayable to the tabs. Ignore LayerSet instances.
952 if (null == ht_panels) ht_panels = new Hashtable<Displayable,DisplayablePanel>();
953 else ht_panels.clear();
954 Iterator it = layer.getDisplayables().iterator();
955 while (it.hasNext()) {
956 add((Displayable)it.next(), false, false);
958 it = layer.getParent().getZDisplayables().iterator(); // the pipes, that live in the LayerSet
959 while (it.hasNext()) {
960 add((Displayable)it.next(), false, false);
962 navigator.repaint(true); // was not done when adding
963 Utils.updateComponent(tabs.getSelectedComponent());
965 setActive(active);
966 }});
967 // swing issues:
969 new Thread() {
970 public void run() {
971 setPriority(Thread.NORM_PRIORITY);
972 try { Thread.sleep(1000); } catch (Exception e) {}
973 setActive(active);
975 }.start();
979 /** Remove all components from the tab and add a "No [label]" label to each. */
980 private void clearTab(final Container c, final String label) {
981 c.removeAll();
982 c.add(new JLabel("No " + label + "."));
983 // magic cocktail:
984 if (tabs.getSelectedComponent() == c) {
985 Utils.updateComponent(c);
989 /** A class to listen to the transparency_slider of the DisplayablesSelectorWindow. */
990 private class TransparencySliderListener extends MouseAdapter implements ChangeListener {
992 public void stateChanged(ChangeEvent ce) {
993 //change the transparency value of the current active displayable
994 float new_value = (float)((JSlider)ce.getSource()).getValue();
995 setTransparency(new_value / 100.0f);
998 public void mousePressed(MouseEvent me) {
999 JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
1000 if (scroll != scroll_channels && !selection.isEmpty()) selection.addDataEditStep(new String[]{"alpha"});
1003 public void mouseReleased(MouseEvent me) {
1004 // update navigator window
1005 navigator.repaint(true);
1006 JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
1007 if (scroll != scroll_channels && !selection.isEmpty()) selection.addDataEditStep(new String[]{"alpha"});
1011 /** Context-sensitive: to a Displayable, or to a channel. */
1012 private void setTransparency(final float value) {
1013 JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
1014 if (scroll == scroll_channels) {
1015 for (int i=0; i<4; i++) {
1016 if (channels[i].getBackground() == Color.cyan) {
1017 channels[i].setAlpha(value); // will call back and repaint the Display
1018 return;
1021 } else if (null != active) {
1022 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.
1023 canvas.invalidateVolatile();
1024 selection.setAlpha(value);
1029 public void setTransparencySlider(final float transp) {
1030 if (transp >= 0.0f && transp <= 1.0f) {
1031 // fire event
1032 transp_slider.setValue((int)(transp * 100));
1036 /** Mark the canvas for updating the offscreen images if the given Displayable is NOT the active. */ // Used by the Displayable.setVisible for example.
1037 static public void setUpdateGraphics(final Layer layer, final Displayable displ) {
1038 for (final Display d : al_displays) {
1039 if (layer == d.layer && null != d.active && d.active != displ) {
1040 d.canvas.setUpdateGraphics(true);
1045 /** Flag the DisplayCanvas of Displays showing the given Layer to update their offscreen images.*/
1046 static public void setUpdateGraphics(final Layer layer, final boolean update) {
1047 for (final Display d : al_displays) {
1048 if (layer == d.layer) {
1049 d.canvas.setUpdateGraphics(update);
1054 /** Whether to update the offscreen images or not. */
1055 public void setUpdateGraphics(boolean b) {
1056 canvas.setUpdateGraphics(b);
1059 /** Find all Display instances that contain the layer and repaint them, in the Swing GUI thread. */
1060 static public void update(final Layer layer) {
1061 if (null == layer) return;
1062 SwingUtilities.invokeLater(new Runnable() { public void run() {
1063 for (final Display d : al_displays) {
1064 if (d.isShowing(layer)) {
1065 d.repaintAll();
1068 }});
1071 static public void update(final LayerSet set) {
1072 update(set, true);
1075 /** 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. */
1076 static public void update(final LayerSet set, final boolean update_canvas_dimensions) {
1077 if (null == set) return;
1078 SwingUtilities.invokeLater(new Runnable() { public void run() {
1079 for (final Display d : al_displays) {
1080 if (set.contains(d.layer)) {
1081 d.updateSnapshots();
1082 if (update_canvas_dimensions) d.canvas.setDimensions(set.getLayerWidth(), set.getLayerHeight());
1083 d.repaintAll();
1086 }});
1089 /** Release all resources held by this Display and close the frame. */
1090 protected void destroy() {
1091 dispatcher.quit();
1092 canvas.setReceivesInput(false);
1093 slt.quit();
1095 // update the coloring in the ProjectTree and LayerTree
1096 if (!project.isBeingDestroyed()) {
1097 try {
1098 project.getProjectTree().updateUILater();
1099 project.getLayerTree().updateUILater();
1100 } catch (Exception e) {
1101 Utils.log2("updateUI failed at Display.destroy()");
1105 frame.removeComponentListener(component_listener);
1106 frame.removeWindowListener(window_listener);
1107 frame.removeWindowFocusListener(window_listener);
1108 frame.removeWindowStateListener(window_listener);
1109 frame.removeKeyListener(canvas);
1110 frame.removeMouseListener(frame_mouse_listener);
1111 canvas_panel.removeKeyListener(canvas);
1112 canvas.removeKeyListener(canvas);
1113 tabs.removeChangeListener(tabs_listener);
1114 tabs.removeKeyListener(canvas);
1115 ImagePlus.removeImageListener(this);
1116 bytypelistener = null;
1117 canvas.destroy();
1118 navigator.destroy();
1119 scroller.removeAdjustmentListener(scroller_listener);
1120 frame.setVisible(false);
1121 //no need, and throws exception//frame.dispose();
1122 active = null;
1123 if (null != selection) selection.clear();
1124 //Utils.log2("destroying selection");
1126 // below, need for SetLayerThread threads to quit
1127 slt.quit();
1128 // set a new front if any
1129 if (null == front && al_displays.size() > 0) {
1130 front = (Display)al_displays.get(al_displays.size() -1);
1132 // repaint layer tree (to update the label color)
1133 try {
1134 project.getLayerTree().updateUILater(); // works only after setting the front above
1135 } catch (Exception e) {} // ignore swing sync bullshit when closing everything too fast
1136 // remove the drag and drop listener
1137 dnd.destroy();
1140 /** Find all Display instances that contain a Layer of the given project and close them without removing the Display entries from the database. */
1141 static synchronized public void close(final Project project) {
1142 /* // concurrent modifications if more than 1 Display are being removed asynchronously
1143 for (final Display d : al_displays) {
1144 if (d.getLayer().getProject().equals(project)) {
1145 it.remove();
1146 d.destroy();
1150 Display[] d = new Display[al_displays.size()];
1151 al_displays.toArray(d);
1152 for (int i=0; i<d.length; i++) {
1153 if (d[i].getProject() == project) {
1154 al_displays.remove(d[i]);
1155 d[i].destroy();
1160 /** Find all Display instances that contain the layer and close them and remove the Display from the database. */
1161 static public void close(final Layer layer) {
1162 for (Iterator it = al_displays.iterator(); it.hasNext(); ) {
1163 Display d = (Display)it.next();
1164 if (d.isShowing(layer)) {
1165 d.remove(false);
1166 it.remove();
1171 public boolean remove(boolean check) {
1172 if (check) {
1173 if (!Utils.check("Delete the Display ?")) return false;
1175 // flush the offscreen images and close the frame
1176 destroy();
1177 removeFromDatabase();
1178 return true;
1181 public Layer getLayer() {
1182 return layer;
1185 public LayerSet getLayerSet() {
1186 return layer.getParent();
1189 public boolean isShowing(final Layer layer) {
1190 return this.layer == layer;
1193 public DisplayNavigator getNavigator() {
1194 return navigator;
1197 /** Repaint both the canvas and the navigator, updating the graphics, and the title and tabs. */
1198 public void repaintAll() {
1199 if (repaint_disabled) return;
1200 navigator.repaint(true);
1201 canvas.repaint(true);
1202 Utils.updateComponent(tabs);
1203 updateTitle();
1206 /** Repaint the canvas updating graphics, the navigator without updating graphics, and the title. */
1207 public void repaintAll2() {
1208 if (repaint_disabled) return;
1209 navigator.repaint(false);
1210 canvas.repaint(true);
1211 updateTitle();
1214 static public void repaintSnapshots(final LayerSet set) {
1215 if (repaint_disabled) return;
1216 for (final Display d : al_displays) {
1217 if (d.getLayer().getParent() == set) {
1218 d.navigator.repaint(true);
1219 Utils.updateComponent(d.tabs);
1223 static public void repaintSnapshots(final Layer layer) {
1224 if (repaint_disabled) return;
1225 for (final Display d : al_displays) {
1226 if (d.getLayer() == layer) {
1227 d.navigator.repaint(true);
1228 Utils.updateComponent(d.tabs);
1233 public void pack() {
1234 dispatcher.exec(new Runnable() { public void run() {
1235 try {
1236 Thread.currentThread().sleep(100);
1237 SwingUtilities.invokeAndWait(new Runnable() { public void run() {
1238 frame.pack();
1239 }});
1240 } catch (Exception e) { IJError.print(e); }
1241 }});
1244 static public void pack(final LayerSet ls) {
1245 for (final Display d : al_displays) {
1246 if (d.layer.getParent() == ls) d.pack();
1250 private void adjustCanvas() {
1251 SwingUtilities.invokeLater(new Runnable() { public void run() {
1252 Rectangle r = split.getRightComponent().getBounds();
1253 canvas.setDrawingSize(r.width, r.height, true);
1254 // fix not-on-top-left problem
1255 canvas.setLocation(0, 0);
1256 //frame.pack(); // don't! Would go into an infinite loop
1257 canvas.repaint(true);
1258 updateInDatabase("srcRect");
1259 }});
1262 /** Grab the last selected display (or create an new one if none) and show in it the layer,centered on the Displayable object. */
1263 static public void setFront(final Layer layer, final Displayable displ) {
1264 if (null == front) {
1265 Display display = new Display(layer.getProject(), layer); // gets set to front
1266 display.showCentered(displ);
1267 } else if (layer == front.layer) {
1268 front.showCentered(displ);
1269 } else {
1270 // find one:
1271 for (final Display d : al_displays) {
1272 if (d.layer == layer) {
1273 d.frame.toFront();
1274 d.showCentered(displ);
1275 return;
1278 // else, open new one
1279 new Display(layer.getProject(), layer).showCentered(displ);
1283 /** 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. */
1284 static public void add(final Layer layer, final Displayable displ, final boolean activate) {
1285 for (final Display d : al_displays) {
1286 if (d.layer == layer) {
1287 if (front == d) {
1288 d.add(displ, activate, true);
1289 //front.frame.toFront();
1290 } else {
1291 d.add(displ, false, true);
1297 static public void add(final Layer layer, final Displayable displ) {
1298 add(layer, displ, true);
1301 /** Add the ZDisplayable to all Displays that show a Layer belonging to the given LayerSet. */
1302 static public void add(final LayerSet set, final ZDisplayable zdispl) {
1303 for (final Display d : al_displays) {
1304 if (set.contains(d.layer)) {
1305 if (front == d) {
1306 zdispl.setLayer(d.layer); // the active one
1307 d.add(zdispl, true, true); // calling add(Displayable, boolean, boolean)
1308 //front.frame.toFront();
1309 } else {
1310 d.add(zdispl, false, true);
1316 static public void addAll(final Layer layer, final Collection<? extends Displayable> coll) {
1317 for (final Display d : al_displays) {
1318 if (d.layer == layer) {
1319 d.addAll(coll);
1324 static public void addAll(final LayerSet set, final Collection<? extends ZDisplayable> coll) {
1325 for (final Display d : al_displays) {
1326 if (set.contains(d.layer)) {
1327 for (final ZDisplayable zd : coll) {
1328 if (front == d) zd.setLayer(d.layer);
1330 d.addAll(coll);
1335 private final void addAll(final Collection<? extends Displayable> coll) {
1336 for (final Displayable d : coll) {
1337 add(d, false, false);
1339 selection.clear();
1340 Utils.updateComponent(tabs);
1341 navigator.repaint(true);
1344 // TODO this very old method could take some improvement:
1345 // - there is no need to create a new DisplayablePanel if its panel is not shown
1346 // - other issues; the method looks overly "if a dog barks and a duck quacks during a lunar eclipse then .."
1347 /** Add it to the proper panel, at the top, and set it active. */
1348 private final void add(final Displayable d, final boolean activate, final boolean repaint_snapshot) {
1349 DisplayablePanel dp = ht_panels.get(d);
1350 if (null != dp && activate) { // for ZDisplayable objects (TODO I think this is not used anymore)
1351 dp.setActive(true);
1352 //setActive(d);
1353 selection.clear();
1354 selection.add(d);
1355 return;
1357 // add to the proper list
1358 JPanel p = null;
1359 if (d instanceof Profile) {
1360 p = panel_profiles;
1361 } else if (d instanceof Patch) {
1362 p = panel_patches;
1363 } else if (d instanceof DLabel) {
1364 p = panel_labels;
1365 } else if (d instanceof ZDisplayable) { //both pipes and balls and AreaList
1366 p = panel_zdispl;
1367 } else {
1368 // LayerSet objects
1369 return;
1371 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
1372 addToPanel(p, 0, dp, activate);
1373 ht_panels.put(d, dp);
1374 if (activate) {
1375 dp.setActive(true);
1376 //setActive(d);
1377 selection.clear();
1378 selection.add(d);
1380 if (repaint_snapshot) navigator.repaint(true);
1383 private void addToPanel(JPanel panel, int index, DisplayablePanel dp, boolean repaint) {
1384 // remove the label
1385 if (1 == panel.getComponentCount() && panel.getComponent(0) instanceof JLabel) {
1386 panel.removeAll();
1388 panel.add(dp, index);
1389 if (repaint) {
1390 Utils.updateComponent(tabs);
1394 /** Find the displays that show the given Layer, and remove the given Displayable from the GUI. */
1395 static public void remove(final Layer layer, final Displayable displ) {
1396 for (final Display d : al_displays) {
1397 if (layer == d.layer) d.remove(displ);
1401 private void remove(final Displayable displ) {
1402 DisplayablePanel ob = ht_panels.remove(displ);
1403 if (null != ob) {
1404 final JScrollPane jsp = ht_tabs.get(displ.getClass());
1405 if (null != jsp) {
1406 JPanel p = (JPanel)jsp.getViewport().getView();
1407 p.remove((Component)ob);
1408 Utils.revalidateComponent(p);
1411 if (null == active || !selection.contains(displ)) {
1412 canvas.setUpdateGraphics(true);
1414 canvas.invalidateVolatile(); // removing active, no need to update offscreen but yes the volatile
1415 repaint(displ, null, 5, true, false);
1416 // from Selection.deleteAll this method is called ... but it's ok: same thread, no locking problems.
1417 selection.remove(displ);
1420 static public void remove(final ZDisplayable zdispl) {
1421 for (final Display d : al_displays) {
1422 if (zdispl.getLayerSet() == d.layer.getParent()) {
1423 d.remove((Displayable)zdispl);
1428 static public void repaint(final Layer layer, final Displayable displ, final int extra) {
1429 repaint(layer, displ, displ.getBoundingBox(), extra);
1432 static public void repaint(final Layer layer, final Displayable displ, final Rectangle r, final int extra) {
1433 repaint(layer, displ, r, extra, true);
1436 /** Find the displays that show the given Layer, and repaint the given Displayable. */
1437 static public void repaint(final Layer layer, final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator) {
1438 if (repaint_disabled) return;
1439 for (final Display d : al_displays) {
1440 if (layer == d.layer) {
1441 d.repaint(displ, r, extra, repaint_navigator, false);
1446 static public void repaint(final Displayable d) {
1447 if (d instanceof ZDisplayable) repaint(d.getLayerSet(), d, d.getBoundingBox(null), 5, true);
1448 repaint(d.getLayer(), d, d.getBoundingBox(null), 5, true);
1451 /** Repaint as much as the bounding box around the given Displayable, or the r if not null. */
1452 private void repaint(final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator, final boolean update_graphics) {
1453 if (repaint_disabled || null == displ) return;
1454 if (update_graphics || displ.getClass() == Patch.class || displ != active) {
1455 canvas.setUpdateGraphics(true);
1457 if (null != r) canvas.repaint(r, extra);
1458 else canvas.repaint(displ, extra);
1459 if (repaint_navigator) {
1460 DisplayablePanel dp = ht_panels.get(displ);
1461 if (null != dp) dp.repaint(); // is null when creating it, or after deleting it
1462 navigator.repaint(true); // everything
1466 /** 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. */
1467 static public void repaintSnapshot(final Displayable displ) {
1468 for (final Display d : al_displays) {
1469 if (d.layer.contains(displ)) {
1470 if (!d.navigator.isPainted(displ)) {
1471 DisplayablePanel dp = d.ht_panels.get(displ);
1472 if (null != dp) dp.repaint(); // is null when creating it, or after deleting it
1473 d.navigator.repaint(displ);
1479 /** Repaint the given Rectangle in all Displays showing the layer, updating the offscreen image if any. */
1480 static public void repaint(final Layer layer, final Rectangle r, final int extra) {
1481 repaint(layer, extra, r, true, true);
1484 static public void repaint(final Layer layer, final int extra, final Rectangle r, final boolean update_navigator) {
1485 repaint(layer, extra, r, update_navigator, true);
1488 static public void repaint(final Layer layer, final int extra, final Rectangle r, final boolean update_navigator, final boolean update_graphics) {
1489 if (repaint_disabled) return;
1490 for (final Display d : al_displays) {
1491 if (layer == d.layer) {
1492 d.canvas.setUpdateGraphics(update_graphics);
1493 d.canvas.repaint(r, extra);
1494 if (update_navigator) {
1495 d.navigator.repaint(true);
1496 Utils.updateComponent(d.tabs.getSelectedComponent());
1503 /** Repaint the given Rectangle in all Displays showing the layer, optionally updating the offscreen image (if any). */
1504 static public void repaint(final Layer layer, final Rectangle r, final int extra, final boolean update_graphics) {
1505 if (repaint_disabled) return;
1506 for (final Display d : al_displays) {
1507 if (layer == d.layer) {
1508 d.canvas.setUpdateGraphics(update_graphics);
1509 d.canvas.repaint(r, extra);
1510 d.navigator.repaint(update_graphics);
1511 if (update_graphics) Utils.updateComponent(d.tabs.getSelectedComponent());
1516 /** Repaint the DisplayablePanel (and DisplayNavigator) only for the given Displayable, in all Displays showing the given Layer. */
1517 static public void repaint(final Layer layer, final Displayable displ) {
1518 if (repaint_disabled) return;
1519 for (final Display d : al_displays) {
1520 if (layer == d.layer) {
1521 DisplayablePanel dp = d.ht_panels.get(displ);
1522 if (null != dp) dp.repaint();
1523 d.navigator.repaint(true);
1528 static public void repaint(LayerSet set, Displayable displ, int extra) {
1529 repaint(set, displ, null, extra);
1532 static public void repaint(LayerSet set, Displayable displ, Rectangle r, int extra) {
1533 repaint(set, displ, r, extra, true);
1536 /** Repaint the Displayable in every Display that shows a Layer belonging to the given LayerSet. */
1537 static public void repaint(final LayerSet set, final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator) {
1538 if (repaint_disabled) return;
1539 for (final Display d : al_displays) {
1540 if (set.contains(d.layer)) {
1541 if (repaint_navigator) {
1542 if (null != displ) {
1543 DisplayablePanel dp = d.ht_panels.get(displ);
1544 if (null != dp) dp.repaint();
1546 d.navigator.repaint(true);
1548 if (null == displ || displ != d.active) d.setUpdateGraphics(true); // safeguard
1549 // paint the given box or the actual Displayable's box
1550 if (null != r) d.canvas.repaint(r, extra);
1551 else d.canvas.repaint(displ, extra);
1556 /** Repaint the entire LayerSet, in all Displays showing a Layer of it.*/
1557 static public void repaint(final LayerSet set) {
1558 if (repaint_disabled) return;
1559 for (final Display d : al_displays) {
1560 if (set.contains(d.layer)) {
1561 d.navigator.repaint(true);
1562 d.canvas.repaint(true);
1566 /** Repaint the given box in the LayerSet, in all Displays showing a Layer of it.*/
1567 static public void repaint(final LayerSet set, final Rectangle box) {
1568 if (repaint_disabled) return;
1569 for (final Display d : al_displays) {
1570 if (set.contains(d.layer)) {
1571 d.navigator.repaint(box);
1572 d.canvas.repaint(box, 0, true);
1576 /** Repaint the entire Layer, in all Displays showing it, including the tabs.*/
1577 static public void repaint(final Layer layer) { // TODO this method overlaps with update(layer)
1578 if (repaint_disabled) return;
1579 for (final Display d : al_displays) {
1580 if (layer == d.layer) {
1581 d.navigator.repaint(true);
1582 d.canvas.repaint(true);
1587 /** Call repaint on all open Displays. */
1588 static public void repaint() {
1589 if (repaint_disabled) {
1590 Utils.logAll("Can't repaint -- repainting is disabled!");
1591 return;
1593 for (final Display d : al_displays) {
1594 d.navigator.repaint(true);
1595 d.canvas.repaint(true);
1599 static private boolean repaint_disabled = false;
1601 /** Set a flag to enable/disable repainting of all Display instances. */
1602 static protected void setRepaint(boolean b) {
1603 repaint_disabled = !b;
1606 public Rectangle getBounds() {
1607 return frame.getBounds();
1610 public Point getLocation() {
1611 return frame.getLocation();
1614 public JFrame getFrame() {
1615 return frame;
1618 public void setLocation(Point p) {
1619 this.frame.setLocation(p);
1622 public Displayable getActive() {
1623 return active; //TODO this should return selection.active !!
1626 public void select(Displayable d) {
1627 select(d, false);
1630 /** Select/deselect accordingly to the current state and the shift key. */
1631 public void select(final Displayable d, final boolean shift_down) {
1632 if (null != active && active != d && active.getClass() != Patch.class) {
1633 // active is being deselected, so link underlying patches
1634 active.linkPatches();
1636 if (null == d) {
1637 //Utils.log2("Display.select: clearing selection");
1638 canvas.setUpdateGraphics(true);
1639 selection.clear();
1640 return;
1642 if (!shift_down) {
1643 //Utils.log2("Display.select: single selection");
1644 if (d != active) {
1645 selection.clear();
1646 selection.add(d);
1648 } else if (selection.contains(d)) {
1649 if (active == d) {
1650 selection.remove(d);
1651 //Utils.log2("Display.select: removing from a selection");
1652 } else {
1653 //Utils.log2("Display.select: activing within a selection");
1654 selection.setActive(d);
1656 } else {
1657 //Utils.log2("Display.select: adding to an existing selection");
1658 selection.add(d);
1660 // update the image shown to ImageJ
1661 // NO longer necessary, always he same FakeImagePlus // setTempCurrentImage();
1664 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p, final Class c) {
1665 choose(screen_x_p, screen_y_p, x_p, y_p, false, c);
1667 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p) {
1668 choose(screen_x_p, screen_y_p, x_p, y_p, false, null);
1671 /** 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. */
1672 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p, boolean shift_down, Class c) {
1673 //Utils.log("Display.choose: x,y " + x_p + "," + y_p);
1674 final ArrayList<Displayable> al = new ArrayList<Displayable>(layer.find(x_p, y_p, true));
1675 al.addAll(layer.getParent().findZDisplayables(layer, x_p, y_p, true)); // only visible ones
1676 if (al.isEmpty()) {
1677 Displayable act = this.active;
1678 selection.clear();
1679 canvas.setUpdateGraphics(true);
1680 //Utils.log("choose: set active to null");
1681 // fixing lack of repainting for unknown reasons, of the active one TODO this is a temporary solution
1682 if (null != act) Display.repaint(layer, act, 5);
1683 } else if (1 == al.size()) {
1684 Displayable d = (Displayable)al.get(0);
1685 if (null != c && d.getClass() != c) {
1686 selection.clear();
1687 return;
1689 select(d, shift_down);
1690 //Utils.log("choose 1: set active to " + active);
1691 } else {
1692 if (al.contains(active) && !shift_down) {
1693 // do nothing
1694 } else {
1695 if (null != c) {
1696 // check if at least one of them is of class c
1697 // if only one is of class c, set as selected
1698 // else show menu
1699 for (Iterator it = al.iterator(); it.hasNext(); ) {
1700 Object ob = it.next();
1701 if (ob.getClass() != c) it.remove();
1703 if (0 == al.size()) {
1704 // deselect
1705 selection.clear();
1706 return;
1708 if (1 == al.size()) {
1709 select((Displayable)al.get(0), shift_down);
1710 return;
1712 // else, choose among the many
1714 choose(screen_x_p, screen_y_p, al, shift_down, x_p, y_p);
1716 //Utils.log("choose many: set active to " + active);
1720 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) {
1721 // show a popup on the canvas to choose
1722 new Thread() {
1723 public void run() {
1724 final Object lock = new Object();
1725 final DisplayableChooser d_chooser = new DisplayableChooser(al, lock);
1726 final JPopupMenu pop = new JPopupMenu("Select:");
1727 final Iterator itu = al.iterator();
1728 while (itu.hasNext()) {
1729 Displayable d = (Displayable)itu.next();
1730 JMenuItem menu_item = new JMenuItem(d.toString());
1731 menu_item.addActionListener(d_chooser);
1732 pop.add(menu_item);
1735 new Thread() {
1736 public void run() {
1737 pop.show(canvas, screen_x_p, screen_y_p);
1739 }.start();
1741 //now wait until selecting something
1742 synchronized(lock) {
1743 do {
1744 try {
1745 lock.wait();
1746 } catch (InterruptedException ie) {}
1747 } while (d_chooser.isWaiting() && pop.isShowing());
1750 //grab the chosen Displayable object
1751 Displayable d = d_chooser.getChosen();
1752 //Utils.log("Chosen: " + d.toString());
1753 if (null == d) { Utils.log2("Display.choose: returning a null!"); }
1754 select(d, shift_down);
1755 pop.setVisible(false);
1757 // fix selection bug: never receives mouseReleased event when the popup shows
1758 selection.mouseReleased(null, x_p, y_p, x_p, y_p, x_p, y_p);
1760 }.start();
1763 /** 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. */
1764 protected void setActive(final Displayable displ) {
1765 final Displayable prev_active = this.active;
1766 this.active = displ;
1767 SwingUtilities.invokeLater(new Runnable() { public void run() {
1769 // renew current image if necessary
1770 if (null != displ && displ == prev_active) {
1771 // make sure the proper tab is selected.
1772 selectTab(displ);
1773 return; // the same
1775 // deactivate previously active
1776 if (null != prev_active) {
1777 final DisplayablePanel ob = ht_panels.get(prev_active);
1778 if (null != ob) ob.setActive(false);
1779 // erase "decorations" of the previously active
1780 canvas.repaint(selection.getBox(), 4);
1782 // activate the new active
1783 if (null != displ) {
1784 final DisplayablePanel ob = ht_panels.get(displ);
1785 if (null != ob) ob.setActive(true);
1786 updateInDatabase("active_displayable_id");
1787 if (displ.getClass() != Patch.class) project.select(displ); // select the node in the corresponding tree, if any.
1788 // select the proper tab, and scroll to visible
1789 selectTab(displ);
1790 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
1791 repaint(displ, null, 5, false, update_graphics); // to show the border, and to repaint out of the background image
1792 transp_slider.setValue((int)(displ.getAlpha() * 100));
1793 } else {
1794 //ensure decorations are removed from the panels, for Displayables in a selection besides the active one
1795 Utils.updateComponent(tabs.getSelectedComponent());
1797 }});
1800 /** If the other paints under the base. */
1801 public boolean paintsBelow(Displayable base, Displayable other) {
1802 boolean zd_base = base instanceof ZDisplayable;
1803 boolean zd_other = other instanceof ZDisplayable;
1804 if (zd_other) {
1805 if (base instanceof DLabel) return true; // zd paints under label
1806 if (!zd_base) return false; // any zd paints over a mere displ if not a label
1807 else {
1808 // both zd, compare indices
1809 ArrayList<ZDisplayable> al = other.getLayerSet().getZDisplayables();
1810 return al.indexOf(base) > al.indexOf(other);
1812 } else {
1813 if (!zd_base) {
1814 // both displ, compare indices
1815 ArrayList<Displayable> al = other.getLayer().getDisplayables();
1816 return al.indexOf(base) > al.indexOf(other);
1817 } else {
1818 // base is zd, other is d
1819 if (other instanceof DLabel) return false;
1820 return true;
1825 /** 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. */
1826 private void selectTab(final Displayable displ) {
1827 Method method = null;
1828 try {
1829 if (!(displ instanceof LayerSet)) {
1830 method = Display.class.getDeclaredMethod("selectTab", new Class[]{displ.getClass()});
1832 } catch (Exception e) {
1833 IJError.print(e);
1835 if (null != method) {
1836 final Method me = method;
1837 dispatcher.exec(new Runnable() { public void run() {
1838 try {
1839 me.setAccessible(true);
1840 me.invoke(Display.this, new Object[]{displ});
1841 } catch (Exception e) { IJError.print(e); }
1842 }});
1846 private void selectTab(Patch patch) {
1847 tabs.setSelectedComponent(scroll_patches);
1848 scrollToShow(scroll_patches, ht_panels.get(patch));
1851 private void selectTab(Profile profile) {
1852 tabs.setSelectedComponent(scroll_profiles);
1853 scrollToShow(scroll_profiles, ht_panels.get(profile));
1856 private void selectTab(DLabel label) {
1857 tabs.setSelectedComponent(scroll_labels);
1858 scrollToShow(scroll_labels, ht_panels.get(label));
1861 private void selectTab(ZDisplayable zd) {
1862 tabs.setSelectedComponent(scroll_zdispl);
1863 scrollToShow(scroll_zdispl, ht_panels.get(zd));
1866 private void selectTab(Pipe d) { selectTab((ZDisplayable)d); }
1867 private void selectTab(Polyline d) { selectTab((ZDisplayable)d); }
1868 private void selectTab(AreaList d) { selectTab((ZDisplayable)d); }
1869 private void selectTab(Ball d) { selectTab((ZDisplayable)d); }
1870 private void selectTab(Dissector d) { selectTab((ZDisplayable)d); }
1872 /** 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). */
1873 private void updateTab(final Container tab, final String label, final ArrayList al) {
1874 final boolean[] recreated = new boolean[]{false, true, true};
1875 dispatcher.execSwing(new Runnable() { public void run() {
1876 try {
1877 if (0 == al.size()) {
1878 tab.removeAll();
1879 tab.add(new JLabel("No " + label + "."));
1880 } else {
1881 Component[] comp = tab.getComponents();
1882 int next = 0;
1883 if (1 == comp.length && comp[0].getClass() == JLabel.class) {
1884 next = 1;
1885 tab.remove(0);
1887 for (Iterator it = al.iterator(); it.hasNext(); ) {
1888 Displayable d = (Displayable)it.next();
1889 DisplayablePanel dp = null;
1890 if (next < comp.length) {
1891 dp = (DisplayablePanel)comp[next++]; // recycling panels
1892 dp.set(d);
1893 } else {
1894 dp = new DisplayablePanel(Display.this, d);
1895 tab.add(dp);
1897 ht_panels.put(d, dp);
1899 if (next < comp.length) {
1900 // remove from the end, to avoid potential repaints of other panels
1901 for (int i=comp.length-1; i>=next; i--) {
1902 tab.remove(i);
1905 recreated[0] = true;
1907 if (recreated[0]) {
1908 tab.invalidate();
1909 tab.validate();
1910 tab.repaint();
1912 if (null != Display.this.active) scrollToShow(Display.this.active);
1913 } catch (Throwable e) { IJError.print(e); }
1914 }});
1917 static public void setActive(final Object event, final Displayable displ) {
1918 if (!(event instanceof InputEvent)) return;
1919 // find which Display
1920 for (final Display d : al_displays) {
1921 if (d.isOrigin((InputEvent)event)) {
1922 d.setActive(displ);
1923 break;
1928 /** Find out whether this Display is Transforming its active Displayable. */
1929 public boolean isTransforming() {
1930 return canvas.isTransforming();
1933 /** Find whether any Display is transforming the given Displayable. */
1934 static public boolean isTransforming(final Displayable displ) {
1935 for (final Display d : al_displays) {
1936 if (null != d.active && d.active == displ && d.canvas.isTransforming()) return true;
1938 return false;
1941 static public boolean isAligning(final LayerSet set) {
1942 for (final Display d : al_displays) {
1943 if (d.layer.getParent() == set && set.isAligning()) {
1944 return true;
1947 return false;
1950 /** Set the front Display to transform the Displayable only if no other canvas is transforming it. */
1951 static public void setTransforming(final Displayable displ) {
1952 if (null == front) return;
1953 if (front.active != displ) return;
1954 for (final Display d : al_displays) {
1955 if (d.active == displ) {
1956 if (d.canvas.isTransforming()) {
1957 Utils.showMessage("Already transforming " + displ.getTitle());
1958 return;
1962 front.canvas.setTransforming(true);
1965 /** Check whether the source of the event is located in this instance.*/
1966 private boolean isOrigin(InputEvent event) {
1967 Object source = event.getSource();
1968 // find it ... check the canvas for now TODO
1969 if (canvas == source) {
1970 return true;
1972 return false;
1975 /** Get the layer of the front Display, or null if none.*/
1976 static public Layer getFrontLayer() {
1977 if (null == front) return null;
1978 return front.layer;
1981 /** Get the layer of an open Display of the given Project, or null if none.*/
1982 static public Layer getFrontLayer(final Project project) {
1983 if (null == front) return null;
1984 if (front.project == project) return front.layer;
1985 // else, find an open Display for the given Project, if any
1986 for (final Display d : al_displays) {
1987 if (d.project == project) {
1988 d.frame.toFront();
1989 return d.layer;
1992 return null; // none found
1995 static public Display getFront(final Project project) {
1996 if (null == front) return null;
1997 if (front.project == project) return front;
1998 for (final Display d : al_displays) {
1999 if (d.project == project) {
2000 d.frame.toFront();
2001 return d;
2004 return null;
2007 public boolean isReadOnly() {
2008 // TEMPORARY: in the future one will be able show displays as read-only to other people, remotely
2009 return false;
2012 static public void showPopup(Component c, int x, int y) {
2013 if (null != front) front.getPopupMenu().show(c, x, y);
2016 /** Return a context-sensitive popup menu. */
2017 public JPopupMenu getPopupMenu() { // called from canvas
2018 // get the job canceling dialog
2019 if (!canvas.isInputEnabled()) {
2020 return project.getLoader().getJobsPopup(this);
2023 // create new
2024 this.popup = new JPopupMenu();
2025 JMenuItem item = null;
2026 JMenu menu = null;
2028 if (ProjectToolbar.ALIGN == Toolbar.getToolId()) {
2029 boolean aligning = layer.getParent().isAligning();
2030 item = new JMenuItem("Cancel alignment"); item.addActionListener(this); popup.add(item);
2031 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2032 if (!aligning) item.setEnabled(false);
2033 item = new JMenuItem("Align with landmarks"); item.addActionListener(this); popup.add(item);
2034 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true));
2035 if (!aligning) item.setEnabled(false);
2036 item = new JMenuItem("Align and register"); item.addActionListener(this); popup.add(item);
2037 if (!aligning) item.setEnabled(false);
2038 item = new JMenuItem("Align using profiles"); item.addActionListener(this); popup.add(item);
2039 if (!aligning || selection.isEmpty() || !selection.contains(Profile.class)) item.setEnabled(false);
2040 item = new JMenuItem("Align stack slices"); item.addActionListener(this); popup.add(item);
2041 if (selection.isEmpty() || ! (getActive().getClass() == Patch.class && ((Patch)getActive()).isStack())) item.setEnabled(false);
2042 item = new JMenuItem("Align layers"); item.addActionListener(this); popup.add(item);
2043 if (1 == layer.getParent().size()) item.setEnabled(false);
2044 item = new JMenuItem("Align multi-layer mosaic"); item.addActionListener(this); popup.add(item);
2045 if (1 == layer.getParent().size()) item.setEnabled(false);
2046 return popup;
2050 JMenu adjust_menu = new JMenu("Adjust");
2052 if (null != active) {
2053 if (!canvas.isTransforming()) {
2054 if (active instanceof Profile) {
2055 item = new JMenuItem("Duplicate, link and send to next layer"); item.addActionListener(this); popup.add(item);
2056 Layer nl = layer.getParent().next(layer);
2057 if (nl == layer) item.setEnabled(false);
2058 item = new JMenuItem("Duplicate, link and send to previous layer"); item.addActionListener(this); popup.add(item);
2059 nl = layer.getParent().previous(layer);
2060 if (nl == layer) item.setEnabled(false);
2062 menu = new JMenu("Duplicate, link and send to");
2063 ArrayList al = layer.getParent().getLayers();
2064 Iterator it = al.iterator();
2065 int i = 1;
2066 while (it.hasNext()) {
2067 Layer la = (Layer)it.next();
2068 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
2069 if (la == this.layer) item.setEnabled(false);
2070 i++;
2072 popup.add(menu);
2073 item = new JMenuItem("Duplicate, link and send to..."); item.addActionListener(this); popup.add(item);
2075 popup.addSeparator();
2077 item = new JMenuItem("Unlink from images"); item.addActionListener(this); popup.add(item);
2078 if (!active.isLinked()) item.setEnabled(false); // isLinked() checks if it's linked to a Patch in its own layer
2079 item = new JMenuItem("Show in 3D"); item.addActionListener(this); popup.add(item);
2080 popup.addSeparator();
2081 } else if (active instanceof Patch) {
2082 item = new JMenuItem("Unlink from images"); item.addActionListener(this); popup.add(item);
2083 if (!active.isLinked(Patch.class)) item.setEnabled(false);
2084 if (((Patch)active).isStack()) {
2085 item = new JMenuItem("Unlink slices"); item.addActionListener(this); popup.add(item);
2087 int n_sel_patches = selection.getSelected(Patch.class).size();
2088 if (1 == n_sel_patches) {
2089 item = new JMenuItem("Snap"); item.addActionListener(this); popup.add(item);
2090 } else if (n_sel_patches > 1) {
2091 item = new JMenuItem("Montage"); item.addActionListener(this); popup.add(item);
2092 item = new JMenuItem("Lens correction"); item.addActionListener(this); popup.add(item);
2093 item = new JMenuItem("Blend"); item.addActionListener(this); popup.add(item);
2095 item = new JMenuItem("Link images..."); item.addActionListener(this); popup.add(item);
2096 item = new JMenuItem("View volume"); item.addActionListener(this); popup.add(item);
2097 HashSet hs = active.getLinked(Patch.class);
2098 if (null == hs || 0 == hs.size()) item.setEnabled(false);
2099 item = new JMenuItem("View orthoslices"); item.addActionListener(this); popup.add(item);
2100 if (null == hs || 0 == hs.size()) item.setEnabled(false); // if no Patch instances among the directly linked, then it's not a stack
2101 popup.addSeparator();
2102 } else {
2103 item = new JMenuItem("Unlink"); item.addActionListener(this); popup.add(item);
2104 item = new JMenuItem("Show in 3D"); item.addActionListener(this); popup.add(item);
2105 popup.addSeparator();
2107 if (active instanceof AreaList) {
2108 item = new JMenuItem("Merge"); item.addActionListener(this); popup.add(item);
2109 ArrayList al = selection.getSelected();
2110 int n = 0;
2111 for (Iterator it = al.iterator(); it.hasNext(); ) {
2112 if (it.next().getClass() == AreaList.class) n++;
2114 if (n < 2) item.setEnabled(false);
2115 } else if (active instanceof Pipe) {
2116 item = new JMenuItem("Identify..."); item.addActionListener(this); popup.add(item);
2117 item = new JMenuItem("Identify with axes..."); item.addActionListener(this); popup.add(item);
2120 if (canvas.isTransforming()) {
2121 item = new JMenuItem("Apply transform"); item.addActionListener(this); popup.add(item);
2122 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
2123 } else {
2124 item = new JMenuItem("Transform"); item.addActionListener(this); popup.add(item);
2125 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0, true));
2127 item = new JMenuItem("Cancel transform"); item.addActionListener(this); popup.add(item);
2128 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2129 if (!canvas.isTransforming()) item.setEnabled(false);
2130 if (canvas.isTransforming()) {
2131 item = new JMenuItem("Specify transform..."); item.addActionListener(this); popup.add(item);
2134 if (!canvas.isTransforming()) {
2135 item = new JMenuItem("Color..."); item.addActionListener(this); popup.add(item);
2136 if (active instanceof LayerSet) item.setEnabled(false);
2137 if (active.isLocked()) {
2138 item = new JMenuItem("Unlock"); item.addActionListener(this); popup.add(item);
2139 } else {
2140 item = new JMenuItem("Lock"); item.addActionListener(this); popup.add(item);
2142 menu = new JMenu("Move");
2143 popup.addSeparator();
2144 LayerSet ls = layer.getParent();
2145 item = new JMenuItem("Move to top"); item.addActionListener(this); menu.add(item);
2146 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.
2147 if (ls.isTop(active)) item.setEnabled(false);
2148 item = new JMenuItem("Move up"); item.addActionListener(this); menu.add(item);
2149 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0, true));
2150 if (ls.isTop(active)) item.setEnabled(false);
2151 item = new JMenuItem("Move down"); item.addActionListener(this); menu.add(item);
2152 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0, true));
2153 if (ls.isBottom(active)) item.setEnabled(false);
2154 item = new JMenuItem("Move to bottom"); item.addActionListener(this); menu.add(item);
2155 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_END, 0, true));
2156 if (ls.isBottom(active)) item.setEnabled(false);
2158 popup.add(menu);
2159 popup.addSeparator();
2160 item = new JMenuItem("Delete..."); item.addActionListener(this); popup.add(item);
2161 try {
2162 if (active instanceof Patch) {
2163 if (!active.isOnlyLinkedTo(Patch.class)) {
2164 item.setEnabled(false);
2166 } else if (!(active instanceof DLabel)) { // can't delete elements from the trees (Profile, Pipe, LayerSet)
2167 item.setEnabled(false);
2169 } catch (Exception e) { IJError.print(e); item.setEnabled(false); }
2171 if (active instanceof Patch) {
2172 item = new JMenuItem("Revert"); item.addActionListener(this); popup.add(item);
2173 popup.addSeparator();
2175 item = new JMenuItem("Properties..."); item.addActionListener(this); popup.add(item);
2176 item = new JMenuItem("Show centered"); item.addActionListener(this); popup.add(item);
2178 popup.addSeparator();
2180 if (! (active instanceof ZDisplayable)) {
2181 ArrayList al_layers = layer.getParent().getLayers();
2182 int i_layer = al_layers.indexOf(layer);
2183 int n_layers = al_layers.size();
2184 item = new JMenuItem("Send to previous layer"); item.addActionListener(this); popup.add(item);
2185 if (1 == n_layers || 0 == i_layer || active.isLinked()) item.setEnabled(false);
2186 // 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
2187 else if (active instanceof Profile && !active.canSendTo(layer.getParent().previous(layer))) item.setEnabled(false);
2188 item = new JMenuItem("Send to next layer"); item.addActionListener(this); popup.add(item);
2189 if (1 == n_layers || n_layers -1 == i_layer || active.isLinked()) item.setEnabled(false);
2190 else if (active instanceof Profile && !active.canSendTo(layer.getParent().next(layer))) item.setEnabled(false);
2193 menu = new JMenu("Send linked group to...");
2194 if (active.hasLinkedGroupWithinLayer(this.layer)) {
2195 int i = 1;
2196 for (final Layer la : ls.getLayers()) {
2197 String layer_title = i + ": " + la.getTitle();
2198 if (-1 == layer_title.indexOf(' ')) layer_title += " ";
2199 item = new JMenuItem(layer_title); item.addActionListener(this); menu.add(item);
2200 if (la == this.layer) item.setEnabled(false);
2201 i++;
2203 popup.add(menu);
2204 } else {
2205 menu.setEnabled(false);
2206 //Utils.log("Active's linked group not within layer.");
2208 popup.add(menu);
2209 popup.addSeparator();
2214 if (!canvas.isTransforming()) {
2216 item = new JMenuItem("Undo");item.addActionListener(this); popup.add(item);
2217 if (!layer.getParent().canUndo() || canvas.isTransforming()) item.setEnabled(false);
2218 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Utils.getControlModifier(), true));
2219 item = new JMenuItem("Redo");item.addActionListener(this); popup.add(item);
2220 if (!layer.getParent().canRedo() || canvas.isTransforming()) item.setEnabled(false);
2221 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Event.ALT_MASK, true));
2223 item = new JMenuItem("Enhance contrast layer-wise..."); item.addActionListener(this); adjust_menu.add(item);
2224 item = new JMenuItem("Enhance contrast (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
2225 if (selection.isEmpty()) item.setEnabled(false);
2226 item = new JMenuItem("Set Min and Max layer-wise..."); item.addActionListener(this); adjust_menu.add(item);
2227 item = new JMenuItem("Set Min and Max (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
2228 if (selection.isEmpty()) item.setEnabled(false);
2229 popup.add(adjust_menu);
2230 popup.addSeparator();
2232 // Would get so much simpler with a clojure macro ...
2234 try {
2235 menu = new JMenu("Hide/Unhide");
2236 item = new JMenuItem("Hide deselected"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.SHIFT_MASK, true));
2237 boolean none = 0 == selection.getNSelected();
2238 if (none) item.setEnabled(false);
2239 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));
2240 if (none) item.setEnabled(false);
2241 item = new JMenuItem("Hide selected"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, 0, true));
2242 if (none) item.setEnabled(false);
2243 none = ! layer.getParent().containsDisplayable(DLabel.class);
2244 item = new JMenuItem("Hide all labels"); item.addActionListener(this); menu.add(item);
2245 if (none) item.setEnabled(false);
2246 item = new JMenuItem("Unhide all labels"); item.addActionListener(this); menu.add(item);
2247 if (none) item.setEnabled(false);
2248 none = ! layer.getParent().contains(AreaList.class);
2249 item = new JMenuItem("Hide all arealists"); item.addActionListener(this); menu.add(item);
2250 if (none) item.setEnabled(false);
2251 item = new JMenuItem("Unhide all arealists"); item.addActionListener(this); menu.add(item);
2252 if (none) item.setEnabled(false);
2253 none = ! layer.contains(Profile.class);
2254 item = new JMenuItem("Hide all profiles"); item.addActionListener(this); menu.add(item);
2255 if (none) item.setEnabled(false);
2256 item = new JMenuItem("Unhide all profiles"); item.addActionListener(this); menu.add(item);
2257 if (none) item.setEnabled(false);
2258 none = ! layer.getParent().contains(Pipe.class);
2259 item = new JMenuItem("Hide all pipes"); item.addActionListener(this); menu.add(item);
2260 if (none) item.setEnabled(false);
2261 item = new JMenuItem("Unhide all pipes"); item.addActionListener(this); menu.add(item);
2262 if (none) item.setEnabled(false);
2263 none = ! layer.getParent().contains(Polyline.class);
2264 item = new JMenuItem("Hide all polylines"); item.addActionListener(this); menu.add(item);
2265 if (none) item.setEnabled(false);
2266 item = new JMenuItem("Unhide all polylines"); item.addActionListener(this); menu.add(item);
2267 if (none) item.setEnabled(false);
2268 none = ! layer.getParent().contains(Ball.class);
2269 item = new JMenuItem("Hide all balls"); item.addActionListener(this); menu.add(item);
2270 if (none) item.setEnabled(false);
2271 item = new JMenuItem("Unhide all balls"); item.addActionListener(this); menu.add(item);
2272 if (none) item.setEnabled(false);
2273 none = ! layer.getParent().containsDisplayable(Patch.class);
2274 item = new JMenuItem("Hide all images"); item.addActionListener(this); menu.add(item);
2275 if (none) item.setEnabled(false);
2276 item = new JMenuItem("Unhide all images"); item.addActionListener(this); menu.add(item);
2277 if (none) item.setEnabled(false);
2278 item = new JMenuItem("Hide all but images"); item.addActionListener(this); menu.add(item);
2279 item = new JMenuItem("Unhide all"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.ALT_MASK, true));
2281 popup.add(menu);
2282 } catch (Exception e) { IJError.print(e); }
2284 menu = new JMenu("Import");
2285 item = new JMenuItem("Import image"); item.addActionListener(this); menu.add(item);
2286 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, Event.ALT_MASK & Event.SHIFT_MASK, true));
2287 item = new JMenuItem("Import stack..."); item.addActionListener(this); menu.add(item);
2288 item = new JMenuItem("Import grid..."); item.addActionListener(this); menu.add(item);
2289 item = new JMenuItem("Import sequence as grid..."); item.addActionListener(this); menu.add(item);
2290 item = new JMenuItem("Import from text file..."); item.addActionListener(this); menu.add(item);
2291 item = new JMenuItem("Import labels as arealists..."); item.addActionListener(this); menu.add(item);
2292 popup.add(menu);
2294 menu = new JMenu("Export");
2295 item = new JMenuItem("Make flat image..."); item.addActionListener(this); menu.add(item);
2296 item = new JMenuItem("Arealists as labels (tif)"); item.addActionListener(this); menu.add(item);
2297 if (0 == layer.getParent().getZDisplayables(AreaList.class).size()) item.setEnabled(false);
2298 item = new JMenuItem("Arealists as labels (amira)"); item.addActionListener(this); menu.add(item);
2299 if (0 == layer.getParent().getZDisplayables(AreaList.class).size()) item.setEnabled(false);
2300 popup.add(menu);
2302 menu = new JMenu("Display");
2303 item = new JMenuItem("Resize canvas/LayerSet..."); item.addActionListener(this); menu.add(item);
2304 item = new JMenuItem("Autoresize canvas/LayerSet"); item.addActionListener(this); menu.add(item);
2305 // OBSOLETE // item = new JMenuItem("Rotate Layer/LayerSet..."); item.addActionListener(this); menu.add(item);
2306 item = new JMenuItem("Properties ..."); item.addActionListener(this); menu.add(item);
2307 popup.add(menu);
2309 menu = new JMenu("Project");
2310 this.project.getLoader().setupMenuItems(menu, this.getProject());
2311 item = new JMenuItem("Project properties..."); item.addActionListener(this); menu.add(item);
2312 item = new JMenuItem("Create subproject"); item.addActionListener(this); menu.add(item);
2313 if (null == canvas.getFakeImagePlus().getRoi()) item.setEnabled(false);
2314 item = new JMenuItem("Release memory..."); item.addActionListener(this); menu.add(item);
2315 item = new JMenuItem("Flush image cache"); item.addActionListener(this); menu.add(item);
2316 popup.add(menu);
2318 menu = new JMenu("Selection");
2319 item = new JMenuItem("Select all"); item.addActionListener(this); menu.add(item);
2320 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, Utils.getControlModifier(), true));
2321 if (0 == layer.getDisplayables().size() && 0 == layer.getParent().getZDisplayables().size()) item.setEnabled(false);
2322 item = new JMenuItem("Select none"); item.addActionListener(this); menu.add(item);
2323 if (0 == selection.getNSelected()) item.setEnabled(false);
2324 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2326 JMenu bytype = new JMenu("Select all by type");
2327 item = new JMenuItem("AreaList"); item.addActionListener(bytypelistener); bytype.add(item);
2328 item = new JMenuItem("Ball"); item.addActionListener(bytypelistener); bytype.add(item);
2329 item = new JMenuItem("Dissector"); item.addActionListener(bytypelistener); bytype.add(item);
2330 item = new JMenuItem("Image"); item.addActionListener(bytypelistener); bytype.add(item);
2331 item = new JMenuItem("Text"); item.addActionListener(bytypelistener); bytype.add(item);
2332 item = new JMenuItem("Pipe"); item.addActionListener(bytypelistener); bytype.add(item);
2333 item = new JMenuItem("Polyline"); item.addActionListener(bytypelistener); bytype.add(item);
2334 item = new JMenuItem("Profile"); item.addActionListener(bytypelistener); bytype.add(item);
2335 menu.add(bytype);
2337 item = new JMenuItem("Restore selection"); item.addActionListener(this); menu.add(item);
2338 item = new JMenuItem("Select under ROI"); item.addActionListener(this); menu.add(item);
2339 if (canvas.getFakeImagePlus().getRoi() == null) item.setEnabled(false);
2340 popup.add(menu);
2341 item = new JMenuItem("Search..."); item.addActionListener(this); popup.add(item);
2344 //canvas.add(popup);
2345 return popup;
2348 private ByTypeListener bytypelistener = new ByTypeListener(this);
2350 static private class ByTypeListener implements ActionListener {
2351 final Display d;
2352 ByTypeListener(final Display d) {
2353 this.d = d;
2355 public void actionPerformed(final ActionEvent ae) {
2356 final String command = ae.getActionCommand();
2358 final java.awt.geom.Area aroi = Utils.getArea(d.canvas.getFakeImagePlus().getRoi());
2360 d.dispatcher.exec(new Runnable() { public void run() {
2362 try {
2363 String type = command;
2364 if (type.equals("Image")) type = "Patch";
2365 Class c = Class.forName("ini.trakem2.display." + type);
2367 java.util.List<Displayable> a = new ArrayList<Displayable>();
2368 if (null != aroi) {
2369 a.addAll(d.layer.getDisplayables(c, aroi, true));
2370 a.addAll(d.layer.getParent().getZDisplayables(c, d.layer, aroi, true));
2371 } else {
2372 a.addAll(d.layer.getDisplayables(c));
2373 a.addAll(d.layer.getParent().getZDisplayables(c));
2374 // Remove non-visible ones
2375 for (final Iterator<Displayable> it = a.iterator(); it.hasNext(); ) {
2376 if (!it.next().isVisible()) it.remove();
2380 if (0 == a.size()) return;
2382 boolean selected = false;
2384 if (0 == ae.getModifiers()) {
2385 Utils.log2("first");
2386 d.selection.clear();
2387 d.selection.selectAll(a);
2388 selected = true;
2389 } else if (0 == (ae.getModifiers() ^ Event.SHIFT_MASK)) {
2390 Utils.log2("with shift");
2391 d.selection.selectAll(a); // just add them to the current selection
2392 selected = true;
2394 if (selected) {
2395 // Activate last:
2396 d.selection.setActive(a.get(a.size() -1));
2399 } catch (ClassNotFoundException e) {
2400 Utils.log2(e.toString());
2403 }});
2407 /** Check if a panel for the given Displayable is completely visible in the JScrollPane */
2408 public boolean isWithinViewport(final Displayable d) {
2409 final JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
2410 if (ht_tabs.get(d.getClass()) == scroll) return isWithinViewport(scroll, ht_panels.get(d));
2411 return false;
2414 private boolean isWithinViewport(JScrollPane scroll, DisplayablePanel dp) {
2415 if(null == dp) return false;
2416 JViewport view = scroll.getViewport();
2417 java.awt.Dimension dimensions = view.getExtentSize();
2418 java.awt.Point p = view.getViewPosition();
2419 int y = dp.getY();
2420 if ((y + DisplayablePanel.HEIGHT - p.y) <= dimensions.height && y >= p.y) {
2421 return true;
2423 return false;
2426 /** Check if a panel for the given Displayable is partially visible in the JScrollPane */
2427 public boolean isPartiallyWithinViewport(final Displayable d) {
2428 final JScrollPane scroll = ht_tabs.get(d.getClass());
2429 if (tabs.getSelectedComponent() == scroll) return isPartiallyWithinViewport(scroll, ht_panels.get(d));
2430 return false;
2433 /** Check if a panel for the given Displayable is at least partially visible in the JScrollPane */
2434 private boolean isPartiallyWithinViewport(final JScrollPane scroll, final DisplayablePanel dp) {
2435 if(null == dp) {
2436 //Utils.log2("Display.isPartiallyWithinViewport: null DisplayablePanel ??");
2437 return false; // to fast for you baby
2439 JViewport view = scroll.getViewport();
2440 java.awt.Dimension dimensions = view.getExtentSize();
2441 java.awt.Point p = view.getViewPosition();
2442 int y = dp.getY();
2443 if ( ((y + DisplayablePanel.HEIGHT - p.y) <= dimensions.height && y >= p.y) // completely visible
2444 || ((y + DisplayablePanel.HEIGHT - p.y) > dimensions.height && y < p.y + dimensions.height) // partially hovering at the bottom
2445 || ((y + DisplayablePanel.HEIGHT) > p.y && y < p.y) // partially hovering at the top
2447 return true;
2449 return false;
2452 /** A function to make a Displayable panel be visible in the screen, by scrolling the viewport of the JScrollPane. */
2453 private void scrollToShow(final Displayable d) {
2454 dispatcher.execSwing(new Runnable() { public void run() {
2455 final JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
2456 if (d instanceof ZDisplayable && scroll == scroll_zdispl) {
2457 scrollToShow(scroll_zdispl, ht_panels.get(d));
2458 return;
2460 final Class c = d.getClass();
2461 if (Patch.class == c && scroll == scroll_patches) {
2462 scrollToShow(scroll_patches, ht_panels.get(d));
2463 } else if (DLabel.class == c && scroll == scroll_labels) {
2464 scrollToShow(scroll_labels, ht_panels.get(d));
2465 } else if (Profile.class == c && scroll == scroll_profiles) {
2466 scrollToShow(scroll_profiles, ht_panels.get(d));
2468 }});
2471 private void scrollToShow(final JScrollPane scroll, final DisplayablePanel dp) {
2472 if (null == dp) return;
2473 JViewport view = scroll.getViewport();
2474 Point current = view.getViewPosition();
2475 Dimension extent = view.getExtentSize();
2476 int panel_y = dp.getY();
2477 if ((panel_y + DisplayablePanel.HEIGHT - current.y) <= extent.height && panel_y >= current.y) {
2478 // it's completely visible already
2479 return;
2480 } else {
2481 // scroll just enough
2482 // if it's above, show at the top
2483 if (panel_y - current.y < 0) {
2484 view.setViewPosition(new Point(0, panel_y));
2486 // if it's below (even if partially), show at the bottom
2487 else if (panel_y + 50 > current.y + extent.height) {
2488 view.setViewPosition(new Point(0, panel_y - extent.height + 50));
2489 //Utils.log("Display.scrollToShow: panel_y: " + panel_y + " current.y: " + current.y + " extent.height: " + extent.height);
2494 /** Update the title of the given Displayable in its DisplayablePanel, if any. */
2495 static public void updateTitle(final Layer layer, final Displayable displ) {
2496 for (final Display d : al_displays) {
2497 if (layer == d.layer) {
2498 DisplayablePanel dp = d.ht_panels.get(displ);
2499 if (null != dp) dp.updateTitle();
2504 /** Update the Display's title in all Displays showing the given Layer. */
2505 static public void updateTitle(final Layer layer) {
2506 for (final Display d : al_displays) {
2507 if (d.layer == layer) {
2508 d.updateTitle();
2512 /** Update the Display's title in all Displays showing a Layer of the given LayerSet. */
2513 static public void updateTitle(final LayerSet ls) {
2514 for (final Display d : al_displays) {
2515 if (d.layer.getParent() == ls) {
2516 d.updateTitle();
2521 /** Set a new title in the JFrame, showing info on the layer 'z' and the magnification. */
2522 public void updateTitle() {
2523 // From ij.ImagePlus class, the solution:
2524 String scale = "";
2525 final double magnification = canvas.getMagnification();
2526 if (magnification!=1.0) {
2527 final double percent = magnification*100.0;
2528 scale = new StringBuffer(" (").append(Utils.d2s(percent, percent==(int)percent ? 0 : 1)).append("%)").toString();
2530 final Calibration cal = layer.getParent().getCalibration();
2531 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();
2532 frame.setTitle(title);
2533 // fix the title for the FakeImageWindow and thus the WindowManager listing in the menus
2534 canvas.getFakeImagePlus().setTitle(title);
2537 /** 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. */
2538 public void nextLayer(final int modifiers) {
2539 //setLayer(layer.getParent().next(layer));
2540 //scroller.setValue(layer.getParent().getLayerIndex(layer.getId()));
2541 if (0 == (modifiers ^ Event.SHIFT_MASK)) {
2542 slt.set(layer.getParent().nextNonEmpty(layer));
2543 } else if (scroll_step > 1) {
2544 int i = layer.getParent().indexOf(this.layer);
2545 Layer la = layer.getParent().getLayer(i + scroll_step);
2546 if (null != la) slt.set(la);
2547 } else {
2548 slt.set(layer.getParent().next(layer));
2550 updateInDatabase("layer_id");
2553 /** Calls setLayer(la) on the SetLayerThread. */
2554 public void toLayer(final Layer la) {
2555 if (la.getParent() != layer.getParent()) return; // not of the same LayerSet
2556 if (la == layer) return; // nothing to do
2557 slt.set(la);
2558 updateInDatabase("layer_id");
2561 /** 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. */
2562 public void previousLayer(final int modifiers) {
2563 //setLayer(layer.getParent().previous(layer));
2564 //scroller.setValue(layer.getParent().getLayerIndex(layer.getId()));
2565 if (0 == (modifiers ^ Event.SHIFT_MASK)) {
2566 slt.set(layer.getParent().previousNonEmpty(layer));
2567 } else if (scroll_step > 1) {
2568 int i = layer.getParent().indexOf(this.layer);
2569 Layer la = layer.getParent().getLayer(i - scroll_step);
2570 if (null != la) slt.set(la);
2571 } else {
2572 slt.set(layer.getParent().previous(layer));
2574 updateInDatabase("layer_id");
2577 static public void updateLayerScroller(LayerSet set) {
2578 for (final Display d : al_displays) {
2579 if (d.layer.getParent() == set) {
2580 d.updateLayerScroller(d.layer);
2585 private void updateLayerScroller(Layer layer) {
2586 int size = layer.getParent().size();
2587 if (size <= 1) {
2588 scroller.setValues(0, 1, 0, 0);
2589 scroller.setEnabled(false);
2590 } else {
2591 scroller.setEnabled(true);
2592 scroller.setValues(layer.getParent().getLayerIndex(layer.getId()), 1, 0, size);
2596 private void updateSnapshots() {
2597 Enumeration<DisplayablePanel> e = ht_panels.elements();
2598 while (e.hasMoreElements()) {
2599 e.nextElement().remake();
2601 Utils.updateComponent(tabs.getSelectedComponent());
2604 static public void updatePanel(Layer layer, final Displayable displ) {
2605 if (null == layer && null != front) layer = front.layer; // the front layer
2606 for (final Display d : al_displays) {
2607 if (d.layer == layer) {
2608 d.updatePanel(displ);
2613 private void updatePanel(Displayable d) {
2614 JPanel c = null;
2615 if (d instanceof Profile) {
2616 c = panel_profiles;
2617 } else if (d instanceof Patch) {
2618 c = panel_patches;
2619 } else if (d instanceof DLabel) {
2620 c = panel_labels;
2621 } else if (d instanceof Pipe) {
2622 c = panel_zdispl;
2624 if (null == c) return;
2625 DisplayablePanel dp = ht_panels.get(d);
2626 dp.remake();
2627 Utils.updateComponent(c);
2630 static public void updatePanelIndex(final Layer layer, final Displayable displ) {
2631 for (final Display d : al_displays) {
2632 if (d.layer == layer || displ instanceof ZDisplayable) {
2633 d.updatePanelIndex(displ);
2638 private void updatePanelIndex(final Displayable d) {
2639 // find first of the kind, then remove and insert its panel
2640 int i = 0;
2641 JPanel c = null;
2642 if (d instanceof ZDisplayable) {
2643 i = layer.getParent().indexOf((ZDisplayable)d);
2644 c = panel_zdispl;
2645 } else {
2646 i = layer.relativeIndexOf(d);
2647 if (d instanceof Profile) {
2648 c = panel_profiles;
2649 } else if (d instanceof Patch) {
2650 c = panel_patches;
2651 } else if (d instanceof DLabel) {
2652 c = panel_labels;
2655 if (null == c) return;
2656 DisplayablePanel dp = ht_panels.get(d);
2657 if (null == dp) return; // may be half-baked, wait
2658 c.remove(dp);
2659 c.add(dp, i); // java and its fabulous consistency
2660 // not enough! Utils.updateComponent(c);
2661 // So, cocktail:
2662 c.invalidate();
2663 c.validate();
2664 Utils.updateComponent(c);
2667 /** Repair possibly missing panels and other components by simply resetting the same Layer */
2668 public void repairGUI() {
2669 Layer layer = this.layer;
2670 this.layer = null;
2671 setLayer(layer);
2674 public void actionPerformed(final ActionEvent ae) {
2675 dispatcher.exec(new Runnable() { public void run() {
2677 String command = ae.getActionCommand();
2678 if (command.startsWith("Job")) {
2679 if (Utils.checkYN("Really cancel job?")) {
2680 project.getLoader().quitJob(command);
2681 repairGUI();
2683 return;
2684 } else if (command.equals("Move to top")) {
2685 if (null == active) return;
2686 canvas.setUpdateGraphics(true);
2687 layer.getParent().move(LayerSet.TOP, active);
2688 Display.repaint(layer.getParent(), active, 5);
2689 //Display.updatePanelIndex(layer, active);
2690 } else if (command.equals("Move up")) {
2691 if (null == active) return;
2692 canvas.setUpdateGraphics(true);
2693 layer.getParent().move(LayerSet.UP, active);
2694 Display.repaint(layer.getParent(), active, 5);
2695 //Display.updatePanelIndex(layer, active);
2696 } else if (command.equals("Move down")) {
2697 if (null == active) return;
2698 canvas.setUpdateGraphics(true);
2699 layer.getParent().move(LayerSet.DOWN, active);
2700 Display.repaint(layer.getParent(), active, 5);
2701 //Display.updatePanelIndex(layer, active);
2702 } else if (command.equals("Move to bottom")) {
2703 if (null == active) return;
2704 canvas.setUpdateGraphics(true);
2705 layer.getParent().move(LayerSet.BOTTOM, active);
2706 Display.repaint(layer.getParent(), active, 5);
2707 //Display.updatePanelIndex(layer, active);
2708 } else if (command.equals("Duplicate, link and send to next layer")) {
2709 duplicateLinkAndSendTo(active, 1, layer.getParent().next(layer));
2710 } else if (command.equals("Duplicate, link and send to previous layer")) {
2711 duplicateLinkAndSendTo(active, 0, layer.getParent().previous(layer));
2712 } else if (command.equals("Duplicate, link and send to...")) {
2713 // fix non-scrolling popup menu
2714 GenericDialog gd = new GenericDialog("Send to");
2715 gd.addMessage("Duplicate, link and send to...");
2716 String[] sl = new String[layer.getParent().size()];
2717 int next = 0;
2718 for (Iterator it = layer.getParent().getLayers().iterator(); it.hasNext(); ) {
2719 sl[next++] = project.findLayerThing(it.next()).toString();
2721 gd.addChoice("Layer: ", sl, sl[layer.getParent().indexOf(layer)]);
2722 gd.showDialog();
2723 if (gd.wasCanceled()) return;
2724 Layer la = layer.getParent().getLayer(gd.getNextChoiceIndex());
2725 if (layer == la) {
2726 Utils.showMessage("Can't duplicate, link and send to the same layer.");
2727 return;
2729 duplicateLinkAndSendTo(active, 0, la);
2730 } else if (-1 != command.indexOf("z = ")) {
2731 // this is an item from the "Duplicate, link and send to" menu of layer z's
2732 Layer target_layer = layer.getParent().getLayer(Double.parseDouble(command.substring(command.lastIndexOf(' ') +1)));
2733 Utils.log2("layer: __" +command.substring(command.lastIndexOf(' ') +1) + "__");
2734 if (null == target_layer) return;
2735 duplicateLinkAndSendTo(active, 0, target_layer);
2736 } else if (-1 != command.indexOf("z=")) {
2737 // WARNING the indexOf is very similar to the previous one
2738 // Send the linked group to the selected layer
2739 int iz = command.indexOf("z=")+2;
2740 Utils.log2("iz=" + iz + " other: " + command.indexOf(' ', iz+2));
2741 int end = command.indexOf(' ', iz);
2742 if (-1 == end) end = command.length();
2743 double lz = Double.parseDouble(command.substring(iz, end));
2744 Layer target = layer.getParent().getLayer(lz);
2745 HashSet hs = active.getLinkedGroup(new HashSet());
2746 layer.getParent().move(hs, active.getLayer(), target);
2747 } else if (command.equals("Unlink")) {
2748 if (null == active || active instanceof Patch) return;
2749 active.unlink();
2750 updateSelection();//selection.update();
2751 } else if (command.equals("Unlink from images")) {
2752 if (null == active) return;
2753 try {
2754 for (Displayable displ: selection.getSelected()) {
2755 displ.unlinkAll(Patch.class);
2757 updateSelection();//selection.update();
2758 } catch (Exception e) { IJError.print(e); }
2759 } else if (command.equals("Unlink slices")) {
2760 YesNoCancelDialog yn = new YesNoCancelDialog(frame, "Attention", "Really unlink all slices from each other?\nThere is no undo.");
2761 if (!yn.yesPressed()) return;
2762 final ArrayList<Patch> pa = ((Patch)active).getStackPatches();
2763 for (int i=pa.size()-1; i>0; i--) {
2764 pa.get(i).unlink(pa.get(i-1));
2766 } else if (command.equals("Send to next layer")) {
2767 Rectangle box = selection.getBox();
2768 try {
2769 // unlink Patch instances
2770 for (final Displayable displ : selection.getSelected()) {
2771 displ.unlinkAll(Patch.class);
2773 updateSelection();//selection.update();
2774 } catch (Exception e) { IJError.print(e); }
2775 //layer.getParent().moveDown(layer, active); // will repaint whatever appropriate layers
2776 selection.moveDown();
2777 repaint(layer.getParent(), box);
2778 } else if (command.equals("Send to previous layer")) {
2779 Rectangle box = selection.getBox();
2780 try {
2781 // unlink Patch instances
2782 for (final Displayable displ : selection.getSelected()) {
2783 displ.unlinkAll(Patch.class);
2785 updateSelection();//selection.update();
2786 } catch (Exception e) { IJError.print(e); }
2787 //layer.getParent().moveUp(layer, active); // will repaint whatever appropriate layers
2788 selection.moveUp();
2789 repaint(layer.getParent(), box);
2790 } else if (command.equals("Show centered")) {
2791 if (active == null) return;
2792 showCentered(active);
2793 } else if (command.equals("Delete...")) {
2795 if (null != active) {
2796 Displayable d = active;
2797 selection.remove(d);
2798 d.remove(true); // will repaint
2801 // remove all selected objects
2802 selection.deleteAll();
2803 } else if (command.equals("Color...")) {
2804 IJ.doCommand("Color Picker...");
2805 } else if (command.equals("Revert")) {
2806 if (null == active || active.getClass() != Patch.class) return;
2807 Patch p = (Patch)active;
2808 if (!p.revert()) {
2809 if (null == p.getOriginalPath()) Utils.log("No editions to save for patch " + p.getTitle() + " #" + p.getId());
2810 else Utils.log("Could not revert Patch " + p.getTitle() + " #" + p.getId());
2812 } else if (command.equals("Undo")) {
2813 Bureaucrat.createAndStart(new Worker.Task("Undo") { public void exec() {
2814 layer.getParent().undoOneStep();
2815 Display.repaint(layer.getParent());
2816 }}, project);
2817 } else if (command.equals("Redo")) {
2818 Bureaucrat.createAndStart(new Worker.Task("Redo") { public void exec() {
2819 layer.getParent().redoOneStep();
2820 Display.repaint(layer.getParent());
2821 }}, project);
2822 } else if (command.equals("Transform")) {
2823 if (null == active) return;
2824 canvas.setTransforming(true);
2825 } else if (command.equals("Apply transform")) {
2826 if (null == active) return;
2827 canvas.setTransforming(false);
2828 } else if (command.equals("Cancel transform")) {
2829 if (null == active) return;
2830 canvas.cancelTransform();
2831 } else if (command.equals("Specify transform...")) {
2832 if (null == active) return;
2833 selection.specify();
2834 } else if (command.equals("Hide all but images")) {
2835 ArrayList<Class> type = new ArrayList<Class>();
2836 type.add(Patch.class);
2837 selection.removeAll(layer.getParent().hideExcept(type, false));
2838 Display.update(layer.getParent(), false);
2839 } else if (command.equals("Unhide all")) {
2840 layer.getParent().setAllVisible(false);
2841 Display.update(layer.getParent(), false);
2842 } else if (command.startsWith("Hide all ")) {
2843 String type = command.substring(9, command.length() -1); // skip the ending plural 's'
2844 type = type.substring(0, 1).toUpperCase() + type.substring(1);
2845 selection.removeAll(layer.getParent().setVisible(type, false, true));
2846 } else if (command.startsWith("Unhide all ")) {
2847 String type = command.substring(11, command.length() -1); // skip the ending plural 's'
2848 type = type.substring(0, 1).toUpperCase() + type.substring(1);
2849 layer.getParent().setVisible(type, true, true);
2850 } else if (command.equals("Hide deselected")) {
2851 hideDeselected(0 != (ActionEvent.ALT_MASK & ae.getModifiers()));
2852 } else if (command.equals("Hide deselected except images")) {
2853 hideDeselected(true);
2854 } else if (command.equals("Hide selected")) {
2855 selection.setVisible(false); // TODO should deselect them too? I don't think so.
2856 } else if (command.equals("Resize canvas/LayerSet...")) {
2857 resizeCanvas();
2858 } else if (command.equals("Autoresize canvas/LayerSet")) {
2859 layer.getParent().setMinimumDimensions();
2860 } else if (command.equals("Import image")) {
2861 importImage();
2862 } else if (command.equals("Import next image")) {
2863 importNextImage();
2864 } else if (command.equals("Import stack...")) {
2865 Display.this.getLayerSet().addLayerContentStep(layer);
2866 Rectangle sr = getCanvas().getSrcRect();
2867 Bureaucrat burro = project.getLoader().importStack(layer, sr.x + sr.width/2, sr.y + sr.height/2, null, true, null);
2868 burro.addPostTask(new Runnable() { public void run() {
2869 Display.this.getLayerSet().addLayerContentStep(layer);
2870 }});
2871 } else if (command.equals("Import grid...")) {
2872 Display.this.getLayerSet().addLayerContentStep(layer);
2873 Bureaucrat burro = project.getLoader().importGrid(layer);
2874 burro.addPostTask(new Runnable() { public void run() {
2875 Display.this.getLayerSet().addLayerContentStep(layer);
2876 }});
2877 } else if (command.equals("Import sequence as grid...")) {
2878 Display.this.getLayerSet().addLayerContentStep(layer);
2879 Bureaucrat burro = project.getLoader().importSequenceAsGrid(layer);
2880 burro.addPostTask(new Runnable() { public void run() {
2881 Display.this.getLayerSet().addLayerContentStep(layer);
2882 }});
2883 } else if (command.equals("Import from text file...")) {
2884 Display.this.getLayerSet().addLayerContentStep(layer);
2885 Bureaucrat burro = project.getLoader().importImages(layer);
2886 burro.addPostTask(new Runnable() { public void run() {
2887 Display.this.getLayerSet().addLayerContentStep(layer);
2888 }});
2889 } else if (command.equals("Import labels as arealists...")) {
2890 Display.this.getLayerSet().addChangeTreesStep();
2891 Bureaucrat burro = project.getLoader().importLabelsAsAreaLists(layer, null, Double.MAX_VALUE, 0, 0.4f, false);
2892 burro.addPostTask(new Runnable() { public void run() {
2893 Display.this.getLayerSet().addChangeTreesStep();
2894 }});
2895 } else if (command.equals("Make flat image...")) {
2896 // if there's a ROI, just use that as cropping rectangle
2897 Rectangle srcRect = null;
2898 Roi roi = canvas.getFakeImagePlus().getRoi();
2899 if (null != roi) {
2900 srcRect = roi.getBounds();
2901 } else {
2902 // otherwise, whatever is visible
2903 //srcRect = canvas.getSrcRect();
2904 // The above is confusing. That is what ROIs are for. So paint all:
2905 srcRect = new Rectangle(0, 0, (int)Math.ceil(layer.getParent().getLayerWidth()), (int)Math.ceil(layer.getParent().getLayerHeight()));
2907 double scale = 1.0;
2908 final String[] types = new String[]{"8-bit grayscale", "RGB Color"};
2909 int the_type = ImagePlus.GRAY8;
2910 final GenericDialog gd = new GenericDialog("Choose", frame);
2911 gd.addSlider("Scale: ", 1, 100, 100);
2912 gd.addChoice("Type: ", types, types[0]);
2913 if (layer.getParent().size() > 1) {
2915 String[] layers = new String[layer.getParent().size()];
2916 int i = 0;
2917 for (Iterator it = layer.getParent().getLayers().iterator(); it.hasNext(); ) {
2918 layers[i] = layer.getProject().findLayerThing((Layer)it.next()).toString();
2919 i++;
2921 int i_layer = layer.getParent().indexOf(layer);
2922 gd.addChoice("Start: ", layers, layers[i_layer]);
2923 gd.addChoice("End: ", layers, layers[i_layer]);
2925 Utils.addLayerRangeChoices(Display.this.layer, gd); /// $#%! where are my lisp macros
2926 gd.addCheckbox("Include non-empty layers only", true);
2928 gd.addMessage("Background color:");
2929 Utils.addRGBColorSliders(gd, Color.black);
2930 gd.addCheckbox("Best quality", false);
2931 gd.addMessage("");
2932 gd.addCheckbox("Save to file", false);
2933 gd.addCheckbox("Save for web", false);
2934 gd.showDialog();
2935 if (gd.wasCanceled()) return;
2936 scale = gd.getNextNumber() / 100;
2937 the_type = (0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY8 : ImagePlus.COLOR_RGB);
2938 if (Double.isNaN(scale) || scale <= 0.0) {
2939 Utils.showMessage("Invalid scale.");
2940 return;
2942 Layer[] layer_array = null;
2943 boolean non_empty_only = false;
2944 if (layer.getParent().size() > 1) {
2945 non_empty_only = gd.getNextBoolean();
2946 int i_start = gd.getNextChoiceIndex();
2947 int i_end = gd.getNextChoiceIndex();
2948 ArrayList al = new ArrayList();
2949 ArrayList al_zd = layer.getParent().getZDisplayables();
2950 ZDisplayable[] zd = new ZDisplayable[al_zd.size()];
2951 al_zd.toArray(zd);
2952 for (int i=i_start, j=0; i <= i_end; i++, j++) {
2953 Layer la = layer.getParent().getLayer(i);
2954 if (!la.isEmpty() || !non_empty_only) al.add(la); // checks both the Layer and the ZDisplayable objects in the parent LayerSet
2956 if (0 == al.size()) {
2957 Utils.showMessage("All layers are empty!");
2958 return;
2960 layer_array = new Layer[al.size()];
2961 al.toArray(layer_array);
2962 } else {
2963 layer_array = new Layer[]{Display.this.layer};
2965 final Color background = new Color((int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
2966 final boolean quality = gd.getNextBoolean();
2967 final boolean save_to_file = gd.getNextBoolean();
2968 final boolean save_for_web = gd.getNextBoolean();
2969 // in its own thread
2970 if (save_for_web) project.getLoader().makePrescaledTiles(layer_array, Patch.class, srcRect, scale, c_alphas, the_type);
2971 else project.getLoader().makeFlatImage(layer_array, srcRect, scale, c_alphas, the_type, save_to_file, quality, background);
2973 } else if (command.equals("Lock")) {
2974 selection.setLocked(true);
2975 } else if (command.equals("Unlock")) {
2976 selection.setLocked(false);
2977 } else if (command.equals("Properties...")) {
2978 active.adjustProperties();
2979 updateSelection();
2980 } else if (command.equals("Cancel alignment")) {
2981 layer.getParent().cancelAlign();
2982 } else if (command.equals("Align with landmarks")) {
2983 layer.getParent().applyAlign(false);
2984 } else if (command.equals("Align and register")) {
2985 layer.getParent().applyAlign(true);
2986 } else if (command.equals("Align using profiles")) {
2987 if (!selection.contains(Profile.class)) {
2988 Utils.showMessage("No profiles are selected.");
2989 return;
2991 // ask for range of layers
2992 final GenericDialog gd = new GenericDialog("Choose range");
2993 Utils.addLayerRangeChoices(Display.this.layer, gd);
2994 gd.showDialog();
2995 if (gd.wasCanceled()) return;
2996 Layer la_start = layer.getParent().getLayer(gd.getNextChoiceIndex());
2997 Layer la_end = layer.getParent().getLayer(gd.getNextChoiceIndex());
2998 if (la_start == la_end) {
2999 Utils.showMessage("Need at least two layers.");
3000 return;
3002 if (selection.isLocked()) {
3003 Utils.showMessage("There are locked objects.");
3004 return;
3006 layer.getParent().startAlign(Display.this);
3007 layer.getParent().applyAlign(la_start, la_end, selection);
3008 } else if (command.equals("Align stack slices")) {
3009 if (getActive() instanceof Patch) {
3010 final Patch slice = (Patch)getActive();
3011 if (slice.isStack()) {
3012 // check linked group
3013 final HashSet hs = slice.getLinkedGroup(new HashSet());
3014 for (Iterator it = hs.iterator(); it.hasNext(); ) {
3015 if (it.next().getClass() != Patch.class) {
3016 Utils.showMessage("Images are linked to other objects, can't proceed to cross-correlate them."); // labels should be fine, need to check that
3017 return;
3020 final LayerSet ls = slice.getLayerSet();
3021 final HashSet<Displayable> linked = slice.getLinkedGroup(null);
3022 ls.addTransformStep(linked);
3023 Bureaucrat burro = Registration.registerStackSlices((Patch)getActive()); // will repaint
3024 burro.addPostTask(new Runnable() { public void run() {
3025 // The current state when done
3026 ls.addTransformStep(linked);
3027 }});
3028 } else {
3029 Utils.log("Align stack slices: selected image is not part of a stack.");
3032 } else if (command.equals("Align layers")) {
3033 final Layer la = layer;; // caching, since scroll wheel may change it
3034 la.getParent().addTransformStep(la);
3035 Bureaucrat burro = AlignTask.alignLayersLinearlyTask( la );
3036 burro.addPostTask(new Runnable() { public void run() {
3037 la.getParent().addTransformStep(la);
3038 }});
3039 } else if (command.equals("Align multi-layer mosaic")) {
3040 final Layer la = layer; // caching, since scroll wheel may change it
3041 la.getParent().addTransformStep();
3042 Bureaucrat burro = AlignTask.alignMultiLayerMosaicTask( la );
3043 burro.addPostTask(new Runnable() { public void run() {
3044 la.getParent().addTransformStep();
3045 }});
3046 } else if (command.equals("Properties ...")) { // NOTE the space before the dots, to distinguish from the "Properties..." command that works on Displayable objects.
3047 GenericDialog gd = new GenericDialog("Properties", Display.this.frame);
3048 //gd.addNumericField("layer_scroll_step: ", this.scroll_step, 0);
3049 gd.addSlider("layer_scroll_step: ", 1, layer.getParent().size(), Display.this.scroll_step);
3050 gd.addChoice("snapshots_mode", LayerSet.snapshot_modes, LayerSet.snapshot_modes[layer.getParent().getSnapshotsMode()]);
3051 gd.addCheckbox("prefer_snapshots_quality", layer.getParent().snapshotsQuality());
3052 Loader lo = getProject().getLoader();
3053 boolean using_mipmaps = lo.isMipMapsEnabled();
3054 gd.addCheckbox("enable_mipmaps", using_mipmaps);
3055 String preprocessor = project.getLoader().getPreprocessor();
3056 gd.addStringField("image_preprocessor: ", null == preprocessor ? "" : preprocessor);
3057 gd.addCheckbox("enable_layer_pixels virtualization", layer.getParent().isPixelsVirtualizationEnabled());
3058 double max = layer.getParent().getLayerWidth() < layer.getParent().getLayerHeight() ? layer.getParent().getLayerWidth() : layer.getParent().getLayerHeight();
3059 gd.addSlider("max_dimension of virtualized layer pixels: ", 0, max, layer.getParent().getPixelsMaxDimension());
3060 // --------
3061 gd.showDialog();
3062 if (gd.wasCanceled()) return;
3063 // --------
3064 int sc = (int) gd.getNextNumber();
3065 if (sc < 1) sc = 1;
3066 Display.this.scroll_step = sc;
3067 updateInDatabase("scroll_step");
3069 layer.getParent().setSnapshotsMode(gd.getNextChoiceIndex());
3070 layer.getParent().setSnapshotsQuality(gd.getNextBoolean());
3072 boolean generate_mipmaps = gd.getNextBoolean();
3073 if (using_mipmaps && generate_mipmaps) {
3074 // nothing changed
3075 } else {
3076 if (using_mipmaps) { // and !generate_mipmaps
3077 lo.flushMipMaps(true);
3078 } else {
3079 // not using mipmaps before, and true == generate_mipmaps
3080 lo.generateMipMaps(layer.getParent().getDisplayables(Patch.class));
3084 final String prepro = gd.getNextString();
3085 if (!project.getLoader().setPreprocessor(prepro.trim())) {
3086 Utils.showMessage("Could NOT set the preprocessor to " + prepro);
3089 layer.getParent().setPixelsVirtualizationEnabled(gd.getNextBoolean());
3090 layer.getParent().setPixelsMaxDimension((int)gd.getNextNumber());
3091 } else if (command.equals("Search...")) {
3092 new Search();
3093 } else if (command.equals("Select all")) {
3094 selection.selectAll();
3095 repaint(Display.this.layer, selection.getBox(), 0);
3096 } else if (command.equals("Select none")) {
3097 Rectangle box = selection.getBox();
3098 selection.clear();
3099 repaint(Display.this.layer, box, 0);
3100 } else if (command.equals("Restore selection")) {
3101 selection.restore();
3102 } else if (command.equals("Select under ROI")) {
3103 Roi roi = canvas.getFakeImagePlus().getRoi();
3104 if (null == roi) return;
3105 selection.selectAll(roi, true);
3106 } else if (command.equals("Merge")) {
3107 ArrayList al_sel = selection.getSelected();
3108 // put active at the beginning, to work as the base on which other's will get merged
3109 al_sel.remove(Display.this.active);
3110 al_sel.add(0, Display.this.active);
3111 AreaList ali = AreaList.merge(al_sel);
3112 if (null != ali) {
3113 // remove all but the first from the selection
3114 for (int i=1; i<al_sel.size(); i++) {
3115 Object ob = al_sel.get(i);
3116 if (ob.getClass() == AreaList.class) {
3117 selection.remove((Displayable)ob);
3120 selection.updateTransform(ali);
3121 repaint(ali.getLayerSet(), ali, 0);
3123 } else if (command.equals("Identify...")) {
3124 // for pipes only for now
3125 if (!(active instanceof Pipe)) return;
3126 ini.trakem2.vector.Compare.findSimilar((Pipe)active);
3127 } else if (command.equals("Identify with axes...")) {
3128 if (!(active instanceof Pipe)) return;
3129 if (Project.getProjects().size() < 2) {
3130 Utils.showMessage("You need at least two projects open:\n-A reference project\n-The current project with the pipe to identify");
3131 return;
3133 ini.trakem2.vector.Compare.findSimilarWithAxes((Pipe)active);
3134 } else if (command.equals("View orthoslices")) {
3135 if (!(active instanceof Patch)) return;
3136 Display3D.showOrthoslices(((Patch)active));
3137 } else if (command.equals("View volume")) {
3138 if (!(active instanceof Patch)) return;
3139 Display3D.showVolume(((Patch)active));
3140 } else if (command.equals("Show in 3D")) {
3141 for (Iterator it = selection.getSelected(ZDisplayable.class).iterator(); it.hasNext(); ) {
3142 ZDisplayable zd = (ZDisplayable)it.next();
3143 Display3D.show(zd.getProject().findProjectThing(zd));
3145 // handle profile lists ...
3146 HashSet hs = new HashSet();
3147 for (Iterator it = selection.getSelected(Profile.class).iterator(); it.hasNext(); ) {
3148 Displayable d = (Displayable)it.next();
3149 ProjectThing profile_list = (ProjectThing)d.getProject().findProjectThing(d).getParent();
3150 if (!hs.contains(profile_list)) {
3151 Display3D.show(profile_list);
3152 hs.add(profile_list);
3155 } else if (command.equals("Snap")) {
3156 if (!(active instanceof Patch)) return;
3157 StitchingTEM.snap(getActive(), Display.this);
3158 } else if (command.equals("Blend")) {
3159 HashSet<Patch> patches = new HashSet<Patch>();
3160 for (final Displayable d : selection.getSelected()) {
3161 if (d.getClass() == Patch.class) patches.add((Patch)d);
3163 if (patches.size() > 1) {
3164 GenericDialog gd = new GenericDialog("Blending");
3165 gd.addCheckbox("Respect current alpha mask", true);
3166 gd.showDialog();
3167 if (gd.wasCanceled()) return;
3168 Blending.blend(patches, gd.getNextBoolean());
3169 } else {
3170 IJ.log("Please select more than one overlapping image.");
3172 } else if (command.equals("Montage")) {
3173 if (!(active instanceof Patch)) {
3174 Utils.showMessage("Please select only images.");
3175 return;
3177 final Set<Displayable> affected = new HashSet<Displayable>(selection.getAffected());
3178 for (final Displayable d : affected)
3179 if (d.isLinked()) {
3180 Utils.showMessage( "You cannot montage linked objects." );
3181 return;
3183 // make an undo step!
3184 final LayerSet ls = layer.getParent();
3185 ls.addTransformStep(affected);
3186 Bureaucrat burro = AlignTask.alignSelectionTask( selection );
3187 burro.addPostTask(new Runnable() { public void run() {
3188 ls.addTransformStep(affected);
3189 }});
3190 } else if (command.equals("Lens correction")) {
3191 final Layer la = layer;
3192 la.getParent().addDataEditStep(new HashSet<Displayable>(la.getParent().getDisplayables()));
3193 Bureaucrat burro = DistortionCorrectionTask.correctDistortionFromSelection( selection );
3194 burro.addPostTask(new Runnable() { public void run() {
3195 // no means to know which where modified and from which layers!
3196 la.getParent().addDataEditStep(new HashSet<Displayable>(la.getParent().getDisplayables()));
3197 }});
3198 } else if (command.equals("Link images...")) {
3199 GenericDialog gd = new GenericDialog("Options");
3200 gd.addMessage("Linking images to images (within their own layer only):");
3201 String[] options = {"all images to all images", "each image with any other overlapping image"};
3202 gd.addChoice("Link: ", options, options[1]);
3203 String[] options2 = {"selected images only", "all images in this layer", "all images in all layers"};
3204 gd.addChoice("Apply to: ", options2, options2[0]);
3205 gd.showDialog();
3206 if (gd.wasCanceled()) return;
3207 Layer lay = layer;
3208 final HashSet<Displayable> ds = new HashSet<Displayable>(lay.getParent().getDisplayables());
3209 lay.getParent().addDataEditStep(ds);
3210 boolean overlapping_only = 1 == gd.getNextChoiceIndex();
3211 switch (gd.getNextChoiceIndex()) {
3212 case 0:
3213 Patch.crosslink(selection.getSelected(Patch.class), overlapping_only);
3214 break;
3215 case 1:
3216 Patch.crosslink(lay.getDisplayables(Patch.class), overlapping_only);
3217 break;
3218 case 2:
3219 for (final Layer la : lay.getParent().getLayers()) {
3220 Patch.crosslink(la.getDisplayables(Patch.class), overlapping_only);
3222 break;
3224 lay.getParent().addDataEditStep(ds);
3225 } else if (command.equals("Enhance contrast (selected images)...")) {
3226 final Layer la = layer;
3227 final HashSet<Displayable> ds = new HashSet<Displayable>(la.getParent().getDisplayables());
3228 la.getParent().addDataEditStep(ds);
3229 ArrayList al = selection.getSelected(Patch.class);
3230 Bureaucrat burro = getProject().getLoader().homogenizeContrast(al);
3231 burro.addPostTask(new Runnable() { public void run() {
3232 la.getParent().addDataEditStep(ds);
3233 }});
3234 } else if (command.equals("Enhance contrast layer-wise...")) {
3235 // ask for range of layers
3236 final GenericDialog gd = new GenericDialog("Choose range");
3237 Utils.addLayerRangeChoices(Display.this.layer, gd);
3238 gd.showDialog();
3239 if (gd.wasCanceled()) return;
3240 java.util.List list = layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1); // exclusive end
3241 Layer[] la = new Layer[list.size()];
3242 list.toArray(la);
3243 final HashSet<Displayable> ds = new HashSet<Displayable>();
3244 for (final Layer l : la) ds.addAll(l.getDisplayables(Patch.class));
3245 getLayerSet().addDataEditStep(ds);
3246 Bureaucrat burro = project.getLoader().homogenizeContrast(la);
3247 burro.addPostTask(new Runnable() { public void run() {
3248 getLayerSet().addDataEditStep(ds);
3249 }});
3250 } else if (command.equals("Set Min and Max layer-wise...")) {
3251 Displayable active = getActive();
3252 double min = 0;
3253 double max = 0;
3254 if (null != active && active.getClass() == Patch.class) {
3255 min = ((Patch)active).getMin();
3256 max = ((Patch)active).getMax();
3258 final GenericDialog gd = new GenericDialog("Min and Max");
3259 gd.addMessage("Set min and max to all images in the layer range");
3260 Utils.addLayerRangeChoices(Display.this.layer, gd);
3261 gd.addNumericField("min: ", min, 2);
3262 gd.addNumericField("max: ", max, 2);
3263 gd.showDialog();
3264 if (gd.wasCanceled()) return;
3266 min = gd.getNextNumber();
3267 max = gd.getNextNumber();
3268 ArrayList<Displayable> al = new ArrayList<Displayable>();
3269 for (final Layer la : layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1)) { // exclusive end
3270 al.addAll(la.getDisplayables(Patch.class));
3272 final HashSet<Displayable> ds = new HashSet<Displayable>(al);
3273 getLayerSet().addDataEditStep(ds);
3274 Bureaucrat burro = project.getLoader().setMinAndMax(al, min, max);
3275 burro.addPostTask(new Runnable() { public void run() {
3276 getLayerSet().addDataEditStep(ds);
3277 }});
3278 } else if (command.equals("Set Min and Max (selected images)...")) {
3279 Displayable active = getActive();
3280 double min = 0;
3281 double max = 0;
3282 if (null != active && active.getClass() == Patch.class) {
3283 min = ((Patch)active).getMin();
3284 max = ((Patch)active).getMax();
3286 final GenericDialog gd = new GenericDialog("Min and Max");
3287 gd.addMessage("Set min and max to all selected images");
3288 gd.addNumericField("min: ", min, 2);
3289 gd.addNumericField("max: ", max, 2);
3290 gd.showDialog();
3291 if (gd.wasCanceled()) return;
3293 min = gd.getNextNumber();
3294 max = gd.getNextNumber();
3295 final HashSet<Displayable> ds = new HashSet<Displayable>(selection.getSelected(Patch.class));
3296 getLayerSet().addDataEditStep(ds);
3297 Bureaucrat burro = project.getLoader().setMinAndMax(selection.getSelected(Patch.class), min, max);
3298 burro.addPostTask(new Runnable() { public void run() {
3299 getLayerSet().addDataEditStep(ds);
3300 }});
3301 } else if (command.equals("Create subproject")) {
3302 Roi roi = canvas.getFakeImagePlus().getRoi();
3303 if (null == roi) return; // the menu item is not active unless there is a ROI
3304 Layer first, last;
3305 if (1 == layer.getParent().size()) {
3306 first = last = layer;
3307 } else {
3308 GenericDialog gd = new GenericDialog("Choose layer range");
3309 Utils.addLayerRangeChoices(layer, gd);
3310 gd.showDialog();
3311 if (gd.wasCanceled()) return;
3312 first = layer.getParent().getLayer(gd.getNextChoiceIndex());
3313 last = layer.getParent().getLayer(gd.getNextChoiceIndex());
3314 Utils.log2("first, last: " + first + ", " + last);
3316 Project sub = getProject().createSubproject(roi.getBounds(), first, last);
3317 final LayerSet subls = sub.getRootLayerSet();
3318 final Display d = new Display(sub, subls.getLayer(0));
3319 SwingUtilities.invokeLater(new Runnable() { public void run() {
3320 d.canvas.showCentered(new Rectangle(0, 0, (int)subls.getLayerWidth(), (int)subls.getLayerHeight()));
3321 }});
3322 } else if (command.startsWith("Arealists as labels")) {
3323 GenericDialog gd = new GenericDialog("Export labels");
3324 gd.addSlider("Scale: ", 1, 100, 100);
3325 final String[] options = {"All area list", "Selected area lists"};
3326 gd.addChoice("Export: ", options, options[0]);
3327 Utils.addLayerRangeChoices(layer, gd);
3328 gd.addCheckbox("Visible only", true);
3329 gd.showDialog();
3330 if (gd.wasCanceled()) return;
3331 final float scale = (float)(gd.getNextNumber() / 100);
3332 java.util.List al = 0 == gd.getNextChoiceIndex() ? layer.getParent().getZDisplayables(AreaList.class) : selection.getSelected(AreaList.class);
3333 if (null == al) {
3334 Utils.log("No area lists found to export.");
3335 return;
3337 // 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?
3338 al = (java.util.List<Displayable>) al;
3340 int first = gd.getNextChoiceIndex();
3341 int last = gd.getNextChoiceIndex();
3342 boolean visible_only = gd.getNextBoolean();
3343 if (-1 != command.indexOf("(amira)")) {
3344 AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, true, true);
3345 } else if (-1 != command.indexOf("(tif)")) {
3346 AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, false, false);
3348 } else if (command.equals("Project properties...")) {
3349 project.adjustProperties();
3350 } else if (command.equals("Release memory...")) {
3351 Bureaucrat.createAndStart(new Worker("Releasing memory") {
3352 public void run() {
3353 startedWorking();
3354 try {
3355 GenericDialog gd = new GenericDialog("Release Memory");
3356 int max = (int)(IJ.maxMemory() / 1000000);
3357 gd.addSlider("Megabytes: ", 0, max, max/2);
3358 gd.showDialog();
3359 if (!gd.wasCanceled()) {
3360 int n_mb = (int)gd.getNextNumber();
3361 project.getLoader().releaseToFit((long)n_mb*1000000);
3363 } catch (Throwable e) {
3364 IJError.print(e);
3365 } finally {
3366 finishedWorking();
3369 }, project);
3370 } else if (command.equals("Flush image cache")) {
3371 Loader.releaseAllCaches();
3372 } else {
3373 Utils.log2("Display: don't know what to do with command " + command);
3375 }});
3378 /** Update in all displays the Transform for the given Displayable if it's selected. */
3379 static public void updateTransform(final Displayable displ) {
3380 for (final Display d : al_displays) {
3381 if (d.selection.contains(displ)) d.selection.updateTransform(displ);
3385 /** Order the profiles of the parent profile_list by Z order, and fix the ProjectTree.*/
3387 private void fixZOrdering(Profile profile) {
3388 ProjectThing thing = project.findProjectThing(profile);
3389 if (null == thing) {
3390 Utils.log2("Display.fixZOrdering: null thing?");
3391 return;
3393 ((ProjectThing)thing.getParent()).fixZOrdering();
3394 project.getProjectTree().updateList(thing.getParent());
3398 /** The number of layers to scroll through with the wheel; 1 by default.*/
3399 public int getScrollStep() { return this.scroll_step; }
3401 public void setScrollStep(int scroll_step) {
3402 if (scroll_step < 1) scroll_step = 1;
3403 this.scroll_step = scroll_step;
3404 updateInDatabase("scroll_step");
3407 protected Bureaucrat importImage() {
3408 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
3409 public void run() {
3410 startedWorking();
3411 try {
3414 Rectangle srcRect = canvas.getSrcRect();
3415 int x = srcRect.x + srcRect.width / 2;
3416 int y = srcRect.y + srcRect.height/ 2;
3417 Patch p = project.getLoader().importImage(project, x, y);
3418 if (null == p) {
3419 finishedWorking();
3420 Utils.showMessage("Could not open the image.");
3421 return;
3424 Display.this.getLayerSet().addLayerContentStep(layer);
3426 layer.add(p); // will add it to the proper Displays
3428 Display.this.getLayerSet().addLayerContentStep(layer);
3431 } catch (Exception e) {
3432 IJError.print(e);
3434 finishedWorking();
3437 return Bureaucrat.createAndStart(worker, getProject());
3440 protected Bureaucrat importNextImage() {
3441 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
3442 public void run() {
3443 startedWorking();
3444 try {
3446 Rectangle srcRect = canvas.getSrcRect();
3447 int x = srcRect.x + srcRect.width / 2;// - imp.getWidth() / 2;
3448 int y = srcRect.y + srcRect.height/ 2;// - imp.getHeight()/ 2;
3449 Patch p = project.getLoader().importNextImage(project, x, y);
3450 if (null == p) {
3451 Utils.showMessage("Could not open next image.");
3452 finishedWorking();
3453 return;
3456 Display.this.getLayerSet().addLayerContentStep(layer);
3458 layer.add(p); // will add it to the proper Displays
3460 Display.this.getLayerSet().addLayerContentStep(layer);
3462 } catch (Exception e) {
3463 IJError.print(e);
3465 finishedWorking();
3468 return Bureaucrat.createAndStart(worker, getProject());
3472 /** Make the given channel have the given alpha (transparency). */
3473 public void setChannel(int c, float alpha) {
3474 int a = (int)(255 * alpha);
3475 int l = (c_alphas&0xff000000)>>24;
3476 int r = (c_alphas&0xff0000)>>16;
3477 int g = (c_alphas&0xff00)>>8;
3478 int b = c_alphas&0xff;
3479 switch (c) {
3480 case Channel.MONO:
3481 // all to the given alpha
3482 c_alphas = (l<<24) + (r<<16) + (g<<8) + b; // parenthesis are NECESSARY
3483 break;
3484 case Channel.RED:
3485 // modify only the red
3486 c_alphas = (l<<24) + (a<<16) + (g<<8) + b;
3487 break;
3488 case Channel.GREEN:
3489 c_alphas = (l<<24) + (r<<16) + (a<<8) + b;
3490 break;
3491 case Channel.BLUE:
3492 c_alphas = (l<<24) + (r<<16) + (g<<8) + a;
3493 break;
3495 //Utils.log2("c_alphas: " + c_alphas);
3496 //canvas.setUpdateGraphics(true);
3497 canvas.repaint(true);
3498 updateInDatabase("c_alphas");
3501 /** Set the channel as active and the others as inactive. */
3502 public void setActiveChannel(Channel channel) {
3503 for (int i=0; i<4; i++) {
3504 if (channel != channels[i]) channels[i].setActive(false);
3505 else channel.setActive(true);
3507 Utils.updateComponent(panel_channels);
3508 transp_slider.setValue((int)(channel.getAlpha() * 100));
3511 public int getDisplayChannelAlphas() { return c_alphas; }
3513 // rename this method and the getDisplayChannelAlphas ! They sound the same!
3514 public int getChannelAlphas() {
3515 return ((int)(channels[0].getAlpha() * 255)<<24) + ((int)(channels[1].getAlpha() * 255)<<16) + ((int)(channels[2].getAlpha() * 255)<<8) + (int)(channels[3].getAlpha() * 255);
3518 public int getChannelAlphasState() {
3519 return ((channels[0].isSelected() ? 255 : 0)<<24)
3520 + ((channels[1].isSelected() ? 255 : 0)<<16)
3521 + ((channels[2].isSelected() ? 255 : 0)<<8)
3522 + (channels[3].isSelected() ? 255 : 0);
3525 /** Show the layer in the front Display, or in a new Display if the front Display is showing a layer from a different LayerSet. */
3526 static public void showFront(final Layer layer) {
3527 Display display = front;
3528 if (null == display || display.layer.getParent() != layer.getParent()) {
3529 display = new Display(layer.getProject(), layer, null); // gets set to front
3530 } else {
3531 display.setLayer(layer);
3535 /** Show the given Displayable centered and selected. If select is false, the selection is cleared. */
3536 static public void showCentered(Layer layer, Displayable displ, boolean select, boolean shift_down) {
3537 // see if the given layer belongs to the layer set being displayed
3538 Display display = front; // to ensure thread consistency to some extent
3539 if (null == display || display.layer.getParent() != layer.getParent()) {
3540 display = new Display(layer.getProject(), layer, displ); // gets set to front
3541 } else if (display.layer != layer) {
3542 display.setLayer(layer);
3544 if (select) {
3545 if (!shift_down) display.selection.clear();
3546 display.selection.add(displ);
3547 } else {
3548 display.selection.clear();
3550 display.showCentered(displ);
3553 private final void showCentered(final Displayable displ) {
3554 if (null == displ) return;
3555 SwingUtilities.invokeLater(new Runnable() { public void run() {
3556 displ.setVisible(true);
3557 Rectangle box = displ.getBoundingBox();
3558 if (0 == box.width || 0 == box.height) {
3559 box.width = (int)layer.getLayerWidth();
3560 box.height = (int)layer.getLayerHeight();
3562 canvas.showCentered(box);
3563 scrollToShow(displ);
3564 if (displ instanceof ZDisplayable) {
3565 // scroll to first layer that has a point
3566 ZDisplayable zd = (ZDisplayable)displ;
3567 setLayer(zd.getFirstLayer());
3569 }});
3572 /** Listen to interesting updates, such as the ColorPicker and updates to Patch objects. */
3573 public void imageUpdated(ImagePlus updated) {
3574 // detect ColorPicker WARNING this will work even if the Display is not the window immediately active under the color picker.
3575 if (this == front && updated instanceof ij.plugin.ColorPicker) {
3576 if (null != active && project.isInputEnabled()) {
3577 selection.setColor(Toolbar.getForegroundColor());
3578 Display.repaint(front.layer, selection.getBox(), 0);
3580 return;
3582 // $%#@!! LUT changes don't set the image as changed
3583 //if (updated instanceof PatchStack) {
3584 // updated.changes = 1
3587 //Utils.log2("imageUpdated: " + updated + " " + updated.getClass());
3589 /* // never gets called (?)
3590 // the above is overkill. Instead:
3591 if (updated instanceof PatchStack) {
3592 Patch p = ((PatchStack)updated).getCurrentPatch();
3593 ImageProcessor ip = updated.getProcessor();
3594 p.setMinAndMax(ip.getMin(), ip.getMax());
3595 Utils.log2("setting min and max: " + ip.getMin() + ", " + ip.getMax());
3596 project.getLoader().decacheAWT(p.getId()); // including level 0, which will be editable
3597 // on repaint, it will be recreated
3598 //((PatchStack)updated).decacheAll(); // so that it will repaint with a newly created image
3602 // detect LUT changes: DONE at PatchStack, which is the active (virtual) image
3603 //Utils.log2("calling decache for " + updated);
3604 //getProject().getLoader().decache(updated);
3607 public void imageClosed(ImagePlus imp) {}
3608 public void imageOpened(ImagePlus imp) {}
3610 /** Release memory captured by the offscreen images */
3611 static public void flushAll() {
3612 for (final Display d : al_displays) {
3613 d.canvas.flush();
3615 //System.gc();
3616 Thread.yield();
3619 /** Can be null. */
3620 static public Display getFront() {
3621 return front;
3624 static public void setCursorToAll(final Cursor c) {
3625 for (final Display d : al_displays) {
3626 d.frame.setCursor(c);
3630 protected void setCursor(Cursor c) {
3631 frame.setCursor(c);
3634 /** Used by the Displayable to update the visibility checkbox in other Displays. */
3635 static protected void updateVisibilityCheckbox(final Layer layer, final Displayable displ, final Display calling_display) {
3636 //LOCKS ALL //SwingUtilities.invokeLater(new Runnable() { public void run() {
3637 for (final Display d : al_displays) {
3638 if (d == calling_display) continue;
3639 if (d.layer.contains(displ) || (displ instanceof ZDisplayable && d.layer.getParent().contains((ZDisplayable)displ))) {
3640 DisplayablePanel dp = d.ht_panels.get(displ);
3641 if (null != dp) dp.updateVisibilityCheckbox();
3644 //}});
3647 protected boolean isActiveWindow() {
3648 return frame.isActive();
3651 /** Toggle user input; pan and zoom are always enabled though.*/
3652 static public void setReceivesInput(final Project project, final boolean b) {
3653 for (final Display d : al_displays) {
3654 if (d.project == project) d.canvas.setReceivesInput(b);
3658 /** Export the DTD that defines this object. */
3659 static public void exportDTD(StringBuffer sb_header, HashSet hs, String indent) {
3660 if (hs.contains("t2_display")) return; // TODO to avoid collisions the type shoud be in a namespace such as tm2:display
3661 hs.add("t2_display");
3662 sb_header.append(indent).append("<!ELEMENT t2_display EMPTY>\n")
3663 .append(indent).append("<!ATTLIST t2_display id NMTOKEN #REQUIRED>\n")
3664 .append(indent).append("<!ATTLIST t2_display layer_id NMTOKEN #REQUIRED>\n")
3665 .append(indent).append("<!ATTLIST t2_display x NMTOKEN #REQUIRED>\n")
3666 .append(indent).append("<!ATTLIST t2_display y NMTOKEN #REQUIRED>\n")
3667 .append(indent).append("<!ATTLIST t2_display magnification NMTOKEN #REQUIRED>\n")
3668 .append(indent).append("<!ATTLIST t2_display srcrect_x NMTOKEN #REQUIRED>\n")
3669 .append(indent).append("<!ATTLIST t2_display srcrect_y NMTOKEN #REQUIRED>\n")
3670 .append(indent).append("<!ATTLIST t2_display srcrect_width NMTOKEN #REQUIRED>\n")
3671 .append(indent).append("<!ATTLIST t2_display srcrect_height NMTOKEN #REQUIRED>\n")
3672 .append(indent).append("<!ATTLIST t2_display scroll_step NMTOKEN #REQUIRED>\n")
3673 .append(indent).append("<!ATTLIST t2_display c_alphas NMTOKEN #REQUIRED>\n")
3674 .append(indent).append("<!ATTLIST t2_display c_alphas_state NMTOKEN #REQUIRED>\n")
3677 /** Export all displays of the given project as XML entries. */
3678 static public void exportXML(final Project project, final Writer writer, final String indent, final Object any) throws Exception {
3679 final StringBuffer sb_body = new StringBuffer();
3680 final String in = indent + "\t";
3681 for (final Display d : al_displays) {
3682 if (d.project != project) continue;
3683 final Rectangle r = d.frame.getBounds();
3684 final Rectangle srcRect = d.canvas.getSrcRect();
3685 final double magnification = d.canvas.getMagnification();
3686 sb_body.append(indent).append("<t2_display id=\"").append(d.id).append("\"\n")
3687 .append(in).append("layer_id=\"").append(d.layer.getId()).append("\"\n")
3688 .append(in).append("c_alphas=\"").append(d.c_alphas).append("\"\n")
3689 .append(in).append("c_alphas_state=\"").append(d.getChannelAlphasState()).append("\"\n")
3690 .append(in).append("x=\"").append(r.x).append("\"\n")
3691 .append(in).append("y=\"").append(r.y).append("\"\n")
3692 .append(in).append("magnification=\"").append(magnification).append("\"\n")
3693 .append(in).append("srcrect_x=\"").append(srcRect.x).append("\"\n")
3694 .append(in).append("srcrect_y=\"").append(srcRect.y).append("\"\n")
3695 .append(in).append("srcrect_width=\"").append(srcRect.width).append("\"\n")
3696 .append(in).append("srcrect_height=\"").append(srcRect.height).append("\"\n")
3697 .append(in).append("scroll_step=\"").append(d.scroll_step).append("\"\n")
3699 sb_body.append(indent).append("/>\n");
3701 writer.write(sb_body.toString());
3704 static public void toolChanged(final String tool_name) {
3705 Utils.log2("tool name: " + tool_name);
3706 if (!tool_name.equals("ALIGN")) {
3707 for (final Display d : al_displays) {
3708 d.layer.getParent().cancelAlign();
3713 static public void toolChanged(final int tool) {
3714 //Utils.log2("int tool is " + tool);
3715 if (ProjectToolbar.PEN == tool) {
3716 // erase bounding boxes
3717 for (final Display d : al_displays) {
3718 if (null != d.active) d.repaint(d.layer, d.selection.getBox(), 2);
3721 if (null != front) {
3722 WindowManager.setTempCurrentImage(front.canvas.getFakeImagePlus());
3726 public Selection getSelection() {
3727 return selection;
3730 public boolean isSelected(Displayable d) {
3731 return selection.contains(d);
3734 static public void updateSelection() {
3735 Display.updateSelection(null);
3737 static public void updateSelection(final Display calling) {
3738 final HashSet hs = new HashSet();
3739 for (final Display d : al_displays) {
3740 if (hs.contains(d.layer)) continue;
3741 hs.add(d.layer);
3742 if (null == d || null == d.selection) {
3743 Utils.log2("d is : "+ d + " d.selection is " + d.selection);
3744 } else {
3745 d.selection.update(); // recomputes box
3747 if (d != calling) { // TODO this is so dirty!
3748 if (d.selection.getNLinked() > 1) d.canvas.setUpdateGraphics(true); // this is overkill anyway
3749 d.canvas.repaint(d.selection.getLinkedBox(), Selection.PADDING);
3750 d.navigator.repaint(true); // everything
3755 static public void clearSelection(final Layer layer) {
3756 for (final Display d : al_displays) {
3757 if (d.layer == layer) d.selection.clear();
3760 static public void clearSelection() {
3761 for (final Display d : al_displays) {
3762 d.selection.clear();
3766 private void setTempCurrentImage() {
3767 WindowManager.setTempCurrentImage(canvas.getFakeImagePlus());
3770 /** Check if any display will paint the given Displayable at the given magnification. */
3771 static public boolean willPaint(final Displayable displ, final double magnification) {
3772 Rectangle box = null; ;
3773 for (final Display d : al_displays) {
3774 /* // Can no longer do this check, because 'magnification' is now affected by the Displayable AffineTransform! And thus it would not paint after the prePaint.
3775 if (Math.abs(d.canvas.getMagnification() - magnification) > 0.00000001) {
3776 continue;
3779 if (null == box) box = displ.getBoundingBox(null);
3780 if (d.canvas.getSrcRect().intersects(box)) {
3781 return true;
3784 return false;
3787 public void hideDeselected(final boolean not_images) {
3788 // hide deselected
3789 final ArrayList all = layer.getParent().getZDisplayables(); // a copy
3790 all.addAll(layer.getDisplayables());
3791 all.removeAll(selection.getSelected());
3792 if (not_images) all.removeAll(layer.getDisplayables(Patch.class));
3793 for (final Displayable d : (ArrayList<Displayable>)all) {
3794 if (d.isVisible()) d.setVisible(false);
3796 Display.update(layer);
3799 /** Cleanup internal lists that may contain the given Displayable. */
3800 static public void flush(final Displayable displ) {
3801 for (final Display d : al_displays) {
3802 d.selection.removeFromPrev(displ);
3806 public void resizeCanvas() {
3807 GenericDialog gd = new GenericDialog("Resize LayerSet");
3808 gd.addNumericField("new width: ", layer.getLayerWidth(), 3);
3809 gd.addNumericField("new height: ",layer.getLayerHeight(),3);
3810 gd.addChoice("Anchor: ", LayerSet.ANCHORS, LayerSet.ANCHORS[7]);
3811 gd.showDialog();
3812 if (gd.wasCanceled()) return;
3813 double new_width = gd.getNextNumber();
3814 double new_height =gd.getNextNumber();
3815 layer.getParent().setDimensions(new_width, new_height, gd.getNextChoiceIndex()); // will complain and prevent cropping existing Displayable objects
3819 // To record layer changes -- but it's annoying, this is visualization not data.
3820 static class DoSetLayer implements DoStep {
3821 final Display display;
3822 final Layer layer;
3823 DoSetLayer(final Display display) {
3824 this.display = display;
3825 this.layer = display.layer;
3827 public Displayable getD() { return null; }
3828 public boolean isEmpty() { return false; }
3829 public boolean apply(final int action) {
3830 display.setLayer(layer);
3832 public boolean isIdenticalTo(final Object ob) {
3833 if (!ob instanceof DoSetLayer) return false;
3834 final DoSetLayer dsl = (DoSetLayer) ob;
3835 return dsl.display == this.display && dsl.layer == this.layer;
3840 protected void duplicateLinkAndSendTo(final Displayable active, final int position, final Layer other_layer) {
3841 if (null == active || !(active instanceof Profile)) return;
3842 if (active.getLayer() == other_layer) return; // can't do that!
3843 Profile profile = project.getProjectTree().duplicateChild((Profile)active, position, other_layer);
3844 if (null == profile) return;
3845 active.link(profile);
3846 slt.setAndWait(other_layer);
3847 other_layer.add(profile);
3848 selection.add(profile);
3851 private final HashMap<Color,Layer> layer_channels = new HashMap<Color,Layer>();
3852 private final TreeMap<Integer,LayerPanel> layer_alpha = new TreeMap<Integer,LayerPanel>();
3854 protected void setLayerAlpha(final LayerPanel lp, final float a) {
3855 if (M.equals(0, a)) {
3856 layer_alpha.remove(lp.layer.getParent().indexOf(lp.layer));
3857 } else {
3858 layer_alpha.put(lp.layer.getParent().indexOf(lp.layer), lp);
3862 static protected final int REPAINT_SINGLE_LAYER = 0;
3863 static protected final int REPAINT_MULTI_LAYER = 1;
3864 static protected final int REPAINT_RGB_LAYER = 2;
3866 /** Sets the values atomically, returns the painting mode. */
3867 protected int getPaintMode(final HashMap<Color,Layer> hm, final ArrayList<LayerPanel> list) {
3868 synchronized (layer_channels) {
3869 if (layer_channels.size() > 0) {
3870 hm.putAll(layer_channels);
3871 hm.put(Color.green, this.layer);
3872 return REPAINT_RGB_LAYER;
3874 list.addAll(layer_alpha.values());
3875 final int len = list.size();
3876 if (len > 1) return REPAINT_MULTI_LAYER;
3877 if (1 == len) {
3878 if (list.get(0).layer == this.layer) return REPAINT_SINGLE_LAYER; // normal mode
3879 return REPAINT_MULTI_LAYER;
3881 return REPAINT_SINGLE_LAYER;
3885 /** Set a layer to be painted as a specific color channel in the canvas.
3886 * Only Color.red and Color.blue are accepted.
3887 * Color.green is reserved for the current layer. */
3888 protected void setColorChannel(final Layer layer, final Color color) {
3889 synchronized (layer_channels) {
3890 if (Color.white == color) {
3891 // Remove
3892 for (final Iterator<Layer> it = layer_channels.values().iterator(); it.hasNext(); ) {
3893 if (it.next() == layer) {
3894 it.remove();
3895 break;
3898 canvas.repaint();
3899 } else if (Color.red == color || Color.blue == color) {
3900 // Replace or set new
3901 layer_channels.put(color, layer);
3902 // Reset all others of the same color to white
3903 for (final LayerPanel lp : layer_panels.values()) {
3904 Utils.log2("examining lp " + lp + " -- color: " + lp.getColor());
3905 if (lp.layer == layer || lp.getColor() != color) continue;
3906 Utils.log2("Setting white color to " + lp);
3907 lp.setColor(Color.white);
3909 tabs.repaint();
3910 canvas.repaint();
3911 } else {
3912 Utils.log2("Trying to set unacceptable color for layer " + layer + " : " + color);
3915 this.canvas.repaint(true);