Fixed potential threading issue with Swing and the Layer panels when
[trakem2.git] / ini / trakem2 / display / Display.java
blob97609383fd35eb00186f1f2089dfbf747c5e5f32
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;
58 import java.util.concurrent.Future;
60 import lenscorrection.DistortionCorrectionTask;
62 /** A Display is a class to show a Layer and enable mouse and keyboard manipulation of all its components. */
63 public final class Display extends DBObject implements ActionListener, ImageListener {
65 /** The Layer this Display is showing. */
66 private Layer layer;
68 private Displayable active = null;
69 /** All selected Displayable objects, including the active one. */
70 final private Selection selection = new Selection(this);
72 private JFrame frame;
73 private JTabbedPane tabs;
74 private Hashtable<Class,JScrollPane> ht_tabs;
75 private JScrollPane scroll_patches;
76 private JPanel panel_patches;
77 private JScrollPane scroll_profiles;
78 private JPanel panel_profiles;
79 private JScrollPane scroll_zdispl;
80 private JPanel panel_zdispl;
81 private JScrollPane scroll_channels;
82 private JPanel panel_channels;
83 private JScrollPane scroll_labels;
84 private JPanel panel_labels;
86 private JPanel panel_layers;
87 private JScrollPane scroll_layers;
88 private Hashtable<Layer,LayerPanel> layer_panels = new Hashtable<Layer,LayerPanel>();
90 private JSlider transp_slider;
91 private DisplayNavigator navigator;
92 private JScrollBar scroller;
94 private DisplayCanvas canvas; // WARNING this is an AWT component, since it extends ImageCanvas
95 private JPanel canvas_panel; // and this is a workaround, to better (perhaps) integrate the awt canvas inside a JSplitPane
96 private JSplitPane split;
98 private JPopupMenu popup = null;
100 /** Contains the packed alphas of every channel. */
101 private int c_alphas = 0xffffffff; // all 100 % visible
102 private Channel[] channels;
104 private Hashtable<Displayable,DisplayablePanel> ht_panels = new Hashtable<Displayable,DisplayablePanel>();
106 /** Handle drop events, to insert image files. */
107 private DNDInsertImage dnd;
109 private boolean size_adjusted = false;
111 private int scroll_step = 1;
113 /** Keep track of all existing Display objects. */
114 static private ArrayList<Display> al_displays = new ArrayList<Display>();
115 /** The currently focused Display, if any. */
116 static private Display front = null;
118 /** Displays to open when all objects have been reloaded from the database. */
119 static private final Hashtable ht_later = new Hashtable();
121 /** A thread to handle user actions, for example an event sent from a popup menu. */
122 private final Dispatcher dispatcher = new Dispatcher();
124 static private WindowAdapter window_listener = new WindowAdapter() {
125 /** Unregister the closed Display. */
126 public void windowClosing(WindowEvent we) {
127 final Object source = we.getSource();
128 for (Iterator it = al_displays.iterator(); it.hasNext(); ) {
129 Display d = (Display)it.next();
130 if (source == d.frame) {
131 it.remove();
132 if (d == front) front = null;
133 d.remove(false); //calls destroy
134 break;
138 /** Set the source Display as front. */
139 public void windowActivated(WindowEvent we) {
140 // find which was it to make it be the front
141 final Object source = we.getSource();
142 for (final Display d : al_displays) {
143 if (source == d.frame) {
144 front = d;
145 // set toolbar
146 ProjectToolbar.setProjectToolbar();
147 // now, select the layer in the LayerTree
148 front.getProject().select(front.layer);
149 // finally, set the virtual ImagePlus that ImageJ will see
150 d.setTempCurrentImage();
151 // copied from ij.gui.ImageWindow, with modifications
152 if (IJ.isMacintosh() && IJ.getInstance()!=null) {
153 IJ.wait(10); // may be needed for Java 1.4 on OS X
154 d.frame.setMenuBar(ij.Menus.getMenuBar());
156 return;
159 // else, restore the ImageJ toolbar for non-project images
160 //if (!source.equals(IJ.getInstance())) {
161 // ProjectToolbar.setImageJToolbar();
164 /** Restore the ImageJ toolbar */
165 public void windowDeactivated(WindowEvent we) {
166 // Can't, the user can never click the ProjectToolbar then. This has to be done in a different way, for example checking who is the WindowManager.getCurrentImage (and maybe setting a dummy image into it) //ProjectToolbar.setImageJToolbar();
168 /** Call a pack() when the window is maximized to fit the canvas correctly. */
169 public void windowStateChanged(WindowEvent we) {
170 final Object source = we.getSource();
171 for (final Display d : al_displays) {
172 if (source != d.frame) continue;
173 d.pack();
174 break;
179 static private MouseListener frame_mouse_listener = new MouseAdapter() {
180 public void mouseReleased(MouseEvent me) {
181 Object source = me.getSource();
182 for (final Display d : al_displays) {
183 if (d.frame == source) {
184 if (d.size_adjusted) {
185 d.pack();
186 d.size_adjusted = false;
187 Utils.log2("mouse released on JFrame");
189 break;
195 private int last_frame_state = frame.NORMAL;
197 // THIS WHOLE SYSTEM OF LISTENERS IS BROKEN:
198 // * when zooming in, the window growths in width a few pixels.
199 // * when enlarging the window quickly, the canvas is not resized as large as it should.
200 // -- the whole problem: swing threading, which I am not handling properly. It's hard.
201 static private ComponentListener component_listener = new ComponentAdapter() {
202 public void componentResized(ComponentEvent ce) {
203 final Display d = getDisplaySource(ce);
204 if (null != d) {
205 d.size_adjusted = true; // works in combination with mouseReleased to call pack(), avoiding infinite loops.
206 d.adjustCanvas();
207 int frame_state = d.frame.getExtendedState();
208 if (frame_state != d.last_frame_state) { // this setup avoids infinite loops (for pack() calls componentResized as well
209 d.last_frame_state = frame_state;
210 if (d.frame.ICONIFIED != frame_state) d.pack();
214 public void componentMoved(ComponentEvent ce) {
215 Display d = getDisplaySource(ce);
216 if (null != d) d.updateInDatabase("position");
218 private Display getDisplaySource(ComponentEvent ce) {
219 final Object source = ce.getSource();
220 for (final Display d : al_displays) {
221 if (source == d.frame) {
222 return d;
225 return null;
229 static private ChangeListener tabs_listener = new ChangeListener() {
230 /** Listen to tab changes. */
231 public void stateChanged(final ChangeEvent ce) {
232 final Object source = ce.getSource();
233 for (final Display d : al_displays) {
234 if (source == d.tabs) {
235 d.dispatcher.exec(new Runnable() { public void run() {
236 // creating tabs fires the event!!!
237 if (null == d.frame || null == d.canvas) return;
238 final Container tab = (Container)d.tabs.getSelectedComponent();
239 if (tab == d.scroll_channels) {
240 // find active channel if any
241 for (int i=0; i<d.channels.length; i++) {
242 if (d.channels[i].isActive()) {
243 d.transp_slider.setValue((int)(d.channels[i].getAlpha() * 100));
244 break;
247 } else {
248 // recreate contents
250 int count = tab.getComponentCount();
251 if (0 == count || (1 == count && tab.getComponent(0).getClass().equals(JLabel.class))) {
252 */ // ALWAYS, because it could be the case that the user changes layer while on one specific tab, and then clicks on the other tab which may not be empty and shows totally the wrong contents (i.e. for another layer)
254 String label = null;
255 ArrayList al = null;
256 JPanel p = null;
257 if (tab == d.scroll_zdispl) {
258 label = "Z-space objects";
259 al = d.layer.getParent().getZDisplayables();
260 p = d.panel_zdispl;
261 } else if (tab == d.scroll_patches) {
262 label = "Patches";
263 al = d.layer.getDisplayables(Patch.class);
264 p = d.panel_patches;
265 } else if (tab == d.scroll_labels) {
266 label = "Labels";
267 al = d.layer.getDisplayables(DLabel.class);
268 p = d.panel_labels;
269 } else if (tab == d.scroll_profiles) {
270 label = "Profiles";
271 al = d.layer.getDisplayables(Profile.class);
272 p = d.panel_profiles;
273 } else if (tab == d.scroll_layers) {
274 // nothing to do
275 return;
278 d.updateTab(p, label, al);
279 //Utils.updateComponent(d.tabs.getSelectedComponent());
280 //Utils.log2("updated tab: " + p + " with " + al.size() + " objects.");
283 if (null != d.active) {
284 // set the transp slider to the alpha value of the active Displayable if any
285 d.transp_slider.setValue((int)(d.active.getAlpha() * 100));
286 DisplayablePanel dp = d.ht_panels.get(d.active);
287 if (null != dp) dp.setActive(true);
290 }});
291 break;
297 private final ScrollLayerListener scroller_listener = new ScrollLayerListener();
299 private class ScrollLayerListener implements AdjustmentListener {
301 public void adjustmentValueChanged(final AdjustmentEvent ae) {
302 final int index = scroller.getValue();
303 slt.set(layer.getParent().getLayer(index));
307 private final SetLayerThread slt = new SetLayerThread();
309 private class SetLayerThread extends Thread {
311 private boolean go = true;
312 private Layer layer;
313 private final Lock lock = new Lock();
314 private final Lock lock2 = new Lock();
316 SetLayerThread() {
317 setPriority(Thread.NORM_PRIORITY);
318 setDaemon(true);
319 start();
322 public final void set(final Layer layer) {
323 synchronized (lock) {
324 this.layer = layer;
326 synchronized (this) {
327 notify();
331 public final void setAndWait(final Layer layer) {
332 lock2.lock();
333 set(layer);
336 public void run() {
337 while (go) {
338 while (null == this.layer) {
339 synchronized (this) {
340 try { wait(); } catch (InterruptedException ie) {}
343 Layer layer = null;
344 synchronized (lock) {
345 layer = this.layer;
346 this.layer = null;
349 if (!go) return; // after nullifying layer
351 if (null != layer) {
352 Display.this.setLayer(layer);
353 Display.this.updateInDatabase("layer_id");
355 // unlock any calls waiting on setAndWait
356 synchronized (lock2) {
357 lock2.unlock();
360 // cleanup:
361 synchronized (lock2) {
362 lock2.unlock();
366 public void waitForLayer() {
367 while (null != layer && go) {
368 try { Thread.sleep(10); } catch (Exception e) {}
372 public void quit() {
373 go = false;
377 /** Creates a new Display with adjusted magnification to fit in the screen. */
378 static public void createDisplay(final Project project, final Layer layer) {
379 SwingUtilities.invokeLater(new Runnable() { public void run() {
380 Display display = new Display(project, layer);
381 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
382 Rectangle srcRect = new Rectangle(0, 0, (int)layer.getLayerWidth(), (int)layer.getLayerHeight());
383 double mag = screen.width / layer.getLayerWidth();
384 if (mag * layer.getLayerHeight() > screen.height) mag = screen.height / layer.getLayerHeight();
385 mag = display.canvas.getLowerZoomLevel2(mag);
386 if (mag > 1.0) mag = 1.0;
387 //display.getCanvas().setup(mag, srcRect); // would call pack() at the wrong time!
388 // ... so instead: manually
389 display.getCanvas().setMagnification(mag);
390 display.getCanvas().setSrcRect(srcRect.x, srcRect.y, srcRect.width, srcRect.height);
391 display.getCanvas().setDrawingSize((int)Math.ceil(srcRect.width * mag), (int)Math.ceil(srcRect.height * mag));
393 display.updateTitle();
394 ij.gui.GUI.center(display.frame);
395 display.frame.pack();
396 }});
399 /** A new Display from scratch, to show the given Layer. */
400 public Display(Project project, final Layer layer) {
401 super(project);
402 front = this;
403 makeGUI(layer, null);
404 ImagePlus.addImageListener(this);
405 setLayer(layer);
406 this.layer = layer; // after, or it doesn't update properly
407 al_displays.add(this);
408 addToDatabase();
411 /** For reconstruction purposes. The Display will be stored in the ht_later.*/
412 public Display(Project project, long id, Layer layer, Object[] props) {
413 super(project, id);
414 synchronized (ht_later) {
415 Display.ht_later.put(this, props);
417 this.layer = layer;
420 /** Open a new Display centered around the given Displayable. */
421 public Display(Project project, Layer layer, Displayable displ) {
422 super(project);
423 front = this;
424 active = displ;
425 makeGUI(layer, null);
426 ImagePlus.addImageListener(this);
427 setLayer(layer);
428 this.layer = layer; // after set layer!
429 al_displays.add(this);
430 addToDatabase();
433 /** Reconstruct a Display from an XML entry, to be opened when everything is ready. */
434 public Display(Project project, long id, Layer layer, HashMap ht_attributes) {
435 super(project, id);
436 if (null == layer) {
437 Utils.log2("Display: need a non-null Layer for id=" + id);
438 return;
440 Rectangle srcRect = new Rectangle(0, 0, (int)layer.getLayerWidth(), (int)layer.getLayerHeight());
441 double magnification = 0.25;
442 Point p = new Point(0, 0);
443 int c_alphas = 0xffffffff;
444 int c_alphas_state = 0xffffffff;
445 for (Iterator it = ht_attributes.entrySet().iterator(); it.hasNext(); ) {
446 Map.Entry entry = (Map.Entry)it.next();
447 String key = (String)entry.getKey();
448 String data = (String)entry.getValue();
449 if (key.equals("srcrect_x")) { // reflection! Reflection!
450 srcRect.x = Integer.parseInt(data);
451 } else if (key.equals("srcrect_y")) {
452 srcRect.y = Integer.parseInt(data);
453 } else if (key.equals("srcrect_width")) {
454 srcRect.width = Integer.parseInt(data);
455 } else if (key.equals("srcrect_height")) {
456 srcRect.height = Integer.parseInt(data);
457 } else if (key.equals("magnification")) {
458 magnification = Double.parseDouble(data);
459 } else if (key.equals("x")) {
460 p.x = Integer.parseInt(data);
461 } else if (key.equals("y")) {
462 p.y = Integer.parseInt(data);
463 } else if (key.equals("c_alphas")) {
464 try {
465 c_alphas = Integer.parseInt(data);
466 } catch (Exception ex) {
467 c_alphas = 0xffffffff;
469 } else if (key.equals("c_alphas_state")) {
470 try {
471 c_alphas_state = Integer.parseInt(data);
472 } catch (Exception ex) {
473 IJError.print(ex);
474 c_alphas_state = 0xffffffff;
476 } else if (key.equals("scroll_step")) {
477 try {
478 setScrollStep(Integer.parseInt(data));
479 } catch (Exception ex) {
480 IJError.print(ex);
481 setScrollStep(1);
484 // TODO the above is insecure, in that data is not fully checked to be within bounds.
486 Object[] props = new Object[]{p, new Double(magnification), srcRect, new Long(layer.getId()), new Integer(c_alphas), new Integer(c_alphas_state)};
487 synchronized (ht_later) {
488 Display.ht_later.put(this, props);
490 this.layer = layer;
493 /** After reloading a project from the database, open the Displays that the project had. */
494 static public Bureaucrat openLater() {
495 final Hashtable ht_later_local;
496 synchronized (ht_later) {
497 if (0 == ht_later.size()) return null;
498 ht_later_local = new Hashtable(ht_later);
499 ht_later.keySet().removeAll(ht_later_local.keySet());
501 final Worker worker = new Worker("Opening displays") {
502 public void run() {
503 startedWorking();
504 try {
505 Thread.sleep(300); // waiting for Swing
507 for (Enumeration e = ht_later_local.keys(); e.hasMoreElements(); ) {
508 final Display d = (Display)e.nextElement();
509 front = d; // must be set before repainting any ZDisplayable!
510 Object[] props = (Object[])ht_later_local.get(d);
511 if (ControlWindow.isGUIEnabled()) d.makeGUI(d.layer, props);
512 d.setLayerLater(d.layer, d.layer.get(((Long)props[3]).longValue())); //important to do it after makeGUI
513 if (!ControlWindow.isGUIEnabled()) continue;
514 ImagePlus.addImageListener(d);
515 al_displays.add(d);
516 d.updateTitle();
517 // force a repaint if a prePaint was done TODO this should be properly managed with repaints using always the invokeLater, but then it's DOG SLOW
518 if (d.canvas.getMagnification() > 0.499) {
519 SwingUtilities.invokeLater(new Runnable() { public void run() {
520 d.repaint(d.layer);
521 d.project.getLoader().setChanged(false);
522 Utils.log2("A set to false");
523 }});
525 d.project.getLoader().setChanged(false);
526 Utils.log2("B set to false");
528 if (null != front) front.getProject().select(front.layer);
530 } catch (Throwable t) {
531 IJError.print(t);
532 } finally {
533 finishedWorking();
537 return Bureaucrat.createAndStart(worker, ((Display)ht_later_local.keySet().iterator().next()).getProject()); // gets the project from the first Display
540 private void makeGUI(final Layer layer, final Object[] props) {
541 // gather properties
542 Point p = null;
543 double mag = 1.0D;
544 Rectangle srcRect = null;
545 if (null != props) {
546 p = (Point)props[0];
547 mag = ((Double)props[1]).doubleValue();
548 srcRect = (Rectangle)props[2];
551 // transparency slider
552 this.transp_slider = new JSlider(javax.swing.SwingConstants.HORIZONTAL, 0, 100, 100);
553 this.transp_slider.setBackground(Color.white);
554 this.transp_slider.setMinimumSize(new Dimension(250, 20));
555 this.transp_slider.setMaximumSize(new Dimension(250, 20));
556 this.transp_slider.setPreferredSize(new Dimension(250, 20));
557 TransparencySliderListener tsl = new TransparencySliderListener();
558 this.transp_slider.addChangeListener(tsl);
559 this.transp_slider.addMouseListener(tsl);
560 for (final KeyListener kl : this.transp_slider.getKeyListeners()) {
561 this.transp_slider.removeKeyListener(kl);
564 // Tabbed pane on the left
565 this.tabs = new JTabbedPane();
566 this.tabs.setMinimumSize(new Dimension(250, 300));
567 this.tabs.setBackground(Color.white);
568 this.tabs.addChangeListener(tabs_listener);
570 // Tab 1: Patches
571 this.panel_patches = makeTabPanel();
572 this.panel_patches.add(new JLabel("No patches."));
573 this.scroll_patches = makeScrollPane(panel_patches);
574 this.tabs.add("Patches", scroll_patches);
576 // Tab 2: Profiles
577 this.panel_profiles = makeTabPanel();
578 this.panel_profiles.add(new JLabel("No profiles."));
579 this.scroll_profiles = makeScrollPane(panel_profiles);
580 this.tabs.add("Profiles", scroll_profiles);
582 // Tab 3: pipes
583 this.panel_zdispl = makeTabPanel();
584 this.panel_zdispl.add(new JLabel("No objects."));
585 this.scroll_zdispl = makeScrollPane(panel_zdispl);
586 this.tabs.add("Z space", scroll_zdispl);
588 // Tab 4: channels
589 this.panel_channels = makeTabPanel();
590 this.scroll_channels = makeScrollPane(panel_channels);
591 this.channels = new Channel[4];
592 this.channels[0] = new Channel(this, Channel.MONO);
593 this.channels[1] = new Channel(this, Channel.RED);
594 this.channels[2] = new Channel(this, Channel.GREEN);
595 this.channels[3] = new Channel(this, Channel.BLUE);
596 //this.panel_channels.add(this.channels[0]);
597 this.panel_channels.add(this.channels[1]);
598 this.panel_channels.add(this.channels[2]);
599 this.panel_channels.add(this.channels[3]);
600 this.tabs.add("Opacity", scroll_channels);
602 // Tab 5: labels
603 this.panel_labels = makeTabPanel();
604 this.panel_labels.add(new JLabel("No labels."));
605 this.scroll_labels = makeScrollPane(panel_labels);
606 this.tabs.add("Labels", scroll_labels);
608 // Tab 6: layers
609 this.panel_layers = makeTabPanel();
610 this.scroll_layers = makeScrollPane(panel_layers);
611 recreateLayerPanels(layer);
612 this.scroll_layers.addMouseWheelListener(canvas);
613 this.tabs.add("Layers", scroll_layers);
615 this.ht_tabs = new Hashtable<Class,JScrollPane>();
616 this.ht_tabs.put(Patch.class, scroll_patches);
617 this.ht_tabs.put(Profile.class, scroll_profiles);
618 this.ht_tabs.put(ZDisplayable.class, scroll_zdispl);
619 this.ht_tabs.put(AreaList.class, scroll_zdispl);
620 this.ht_tabs.put(Pipe.class, scroll_zdispl);
621 this.ht_tabs.put(Polyline.class, scroll_zdispl);
622 this.ht_tabs.put(Ball.class, scroll_zdispl);
623 this.ht_tabs.put(Dissector.class, scroll_zdispl);
624 this.ht_tabs.put(DLabel.class, scroll_labels);
625 // channels not included
626 // layers not included
628 // Navigator
629 this.navigator = new DisplayNavigator(this, layer.getLayerWidth(), layer.getLayerHeight());
630 // Layer scroller (to scroll slices)
631 int extent = (int)(250.0 / layer.getParent().size());
632 if (extent < 10) extent = 10;
633 this.scroller = new JScrollBar(JScrollBar.HORIZONTAL);
634 updateLayerScroller(layer);
635 this.scroller.addAdjustmentListener(scroller_listener);
638 // Left panel, contains the transp slider, the tabbed pane, the navigation panel and the layer scroller
639 JPanel left = new JPanel();
640 left.setBackground(Color.white);
641 BoxLayout left_layout = new BoxLayout(left, BoxLayout.Y_AXIS);
642 left.setLayout(left_layout);
643 left.add(transp_slider);
644 left.add(tabs);
645 left.add(navigator);
646 left.add(scroller);
648 // Canvas
649 this.canvas = new DisplayCanvas(this, (int)Math.ceil(layer.getLayerWidth()), (int)Math.ceil(layer.getLayerHeight()));
650 this.canvas_panel = new JPanel();
651 GridBagLayout gb = new GridBagLayout();
652 this.canvas_panel.setLayout(gb);
653 GridBagConstraints c = new GridBagConstraints();
654 c.fill = GridBagConstraints.BOTH;
655 c.anchor = GridBagConstraints.NORTHWEST;
656 gb.setConstraints(this.canvas_panel, c);
657 gb.setConstraints(this.canvas, c);
659 // prevent new Displays from screweing up if input is globally disabled
660 if (!project.isInputEnabled()) this.canvas.setReceivesInput(false);
662 this.canvas_panel.add(canvas);
664 this.navigator.addMouseWheelListener(canvas);
666 this.transp_slider.addKeyListener(canvas);
668 // Split pane to contain everything
669 this.split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, left, canvas_panel);
670 this.split.setOneTouchExpandable(true); // NOT present in all L&F (?)
671 this.split.setBackground(Color.white);
673 // fix
674 gb.setConstraints(split.getRightComponent(), c);
676 // JFrame to show the split pane
677 this.frame = ControlWindow.createJFrame(layer.toString());
678 this.frame.setBackground(Color.white);
679 this.frame.getContentPane().setBackground(Color.white);
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 jsp.setBackground(Color.white); // no effect
810 jsp.getViewport().setBackground(Color.white); // no effect
811 // adjust scrolling to use one DisplayablePanel as the minimal unit
812 jsp.getVerticalScrollBar().setBlockIncrement(DisplayablePanel.HEIGHT); // clicking within the track
813 jsp.getVerticalScrollBar().setUnitIncrement(DisplayablePanel.HEIGHT); // clicking on an arrow
814 jsp.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
815 jsp.setPreferredSize(new Dimension(250, 300));
816 jsp.setMinimumSize(new Dimension(250, 300));
817 return jsp;
820 static protected int scrollbar_width = 0;
822 public JPanel getCanvasPanel() {
823 return canvas_panel;
826 public DisplayCanvas getCanvas() {
827 return canvas;
830 public synchronized void setLayer(final Layer layer) {
831 if (null == layer || layer == this.layer) return;
832 translateLayerColors(this.layer, layer);
833 if (tabs.getSelectedComponent() == scroll_layers) {
834 SwingUtilities.invokeLater(new Runnable() { public void run() {
835 scrollToShow(scroll_layers, layer_panels.get(layer));
836 }});
838 final boolean set_zdispl = null == Display.this.layer || layer.getParent() != Display.this.layer.getParent();
839 if (selection.isTransforming()) {
840 Utils.log("Can't browse layers while transforming.\nCANCEL the transform first with the ESCAPE key or right-click -> cancel.");
841 scroller.setValue(Display.this.layer.getParent().getLayerIndex(Display.this.layer.getId()));
842 return;
844 this.layer = layer;
845 scroller.setValue(layer.getParent().getLayerIndex(layer.getId()));
847 // update the current Layer pointer in ZDisplayable objects
848 for (Iterator it = layer.getParent().getZDisplayables().iterator(); it.hasNext(); ) {
849 ((ZDisplayable)it.next()).setLayer(layer); // the active layer
852 updateVisibleTab(set_zdispl);
854 // see if a lot has to be reloaded, put the relevant ones at the end
855 project.getLoader().prepare(layer);
856 updateTitle(); // to show the new 'z'
857 // select the Layer in the LayerTree
858 project.select(Display.this.layer); // does so in a separate thread
859 // update active Displayable:
861 // deselect all except ZDisplayables
862 final ArrayList<Displayable> sel = selection.getSelected();
863 final Displayable last_active = Display.this.active;
864 int sel_next = -1;
865 for (final Iterator<Displayable> it = sel.iterator(); it.hasNext(); ) {
866 final Displayable d = it.next();
867 if (!(d instanceof ZDisplayable)) {
868 it.remove();
869 selection.remove(d);
870 if (d == last_active && sel.size() > 0) {
871 // select the last one of the remaining, if any
872 sel_next = sel.size()-1;
876 if (-1 != sel_next && sel.size() > 0) select(sel.get(sel_next), true);
878 // Keep Profile chain selected, for best ease of use:
879 if (null != last_active && last_active.getClass() == Profile.class && last_active.isLinked(Profile.class)) {
880 Utils.log2("last active was a profile: " + last_active);
881 Displayable other = null;
882 for (final Displayable prof : last_active.getLinked(Profile.class)) {
883 if (prof.getLayer() == layer) {
884 other = prof;
885 break;
888 if (null != other) selection.add(other);
891 // repaint everything
892 navigator.repaint(true);
893 canvas.repaint(true);
895 // repaint tabs (hard as hell)
896 Utils.updateComponent(tabs);
897 // @#$%^! The above works half the times, so explicit repaint as well:
898 Component c = tabs.getSelectedComponent();
899 if (null == c) {
900 c = scroll_patches;
901 tabs.setSelectedComponent(scroll_patches);
903 Utils.updateComponent(c);
905 project.getLoader().setMassiveMode(false); // resetting if it was set true
907 // update the coloring in the ProjectTree
908 project.getProjectTree().updateUILater();
910 setTempCurrentImage();
913 static public void updateVisibleTabs() {
914 for (final Display d : al_displays) {
915 d.updateVisibleTab(true);
919 /** Recreate the tab that is being shown. */
920 public void updateVisibleTab(boolean set_zdispl) {
921 // update only the visible tab
922 switch (tabs.getSelectedIndex()) {
923 case 0:
924 ht_panels.clear();
925 updateTab(panel_patches, "Patches", layer.getDisplayables(Patch.class));
926 break;
927 case 1:
928 ht_panels.clear();
929 updateTab(panel_profiles, "Profiles", layer.getDisplayables(Profile.class));
930 break;
931 case 2:
932 if (set_zdispl) {
933 ht_panels.clear();
934 updateTab(panel_zdispl, "Z-space objects", layer.getParent().getZDisplayables());
936 break;
937 // case 3: channel opacities
938 case 4:
939 ht_panels.clear();
940 updateTab(panel_labels, "Labels", layer.getDisplayables(DLabel.class));
941 break;
942 // case 5: layer panels
947 private void setLayerLater(final Layer layer, final Displayable active) {
948 if (null == layer) return;
949 this.layer = layer;
950 if (!ControlWindow.isGUIEnabled()) return;
951 SwingUtilities.invokeLater(new Runnable() { public void run() {
952 // empty the tabs, except channels and pipes
953 clearTab(panel_profiles, "Profiles");
954 clearTab(panel_patches, "Patches");
955 clearTab(panel_labels, "Labels");
956 // distribute Displayable to the tabs. Ignore LayerSet instances.
957 if (null == ht_panels) ht_panels = new Hashtable<Displayable,DisplayablePanel>();
958 else ht_panels.clear();
959 Iterator it = layer.getDisplayables().iterator();
960 while (it.hasNext()) {
961 add((Displayable)it.next(), false, false);
963 it = layer.getParent().getZDisplayables().iterator(); // the pipes, that live in the LayerSet
964 while (it.hasNext()) {
965 add((Displayable)it.next(), false, false);
967 navigator.repaint(true); // was not done when adding
968 Utils.updateComponent(tabs.getSelectedComponent());
970 setActive(active);
971 }});
972 // swing issues:
974 new Thread() {
975 public void run() {
976 setPriority(Thread.NORM_PRIORITY);
977 try { Thread.sleep(1000); } catch (Exception e) {}
978 setActive(active);
980 }.start();
984 /** Remove all components from the tab and add a "No [label]" label to each. */
985 private void clearTab(final Container c, final String label) {
986 c.removeAll();
987 c.add(new JLabel("No " + label + "."));
988 // magic cocktail:
989 if (tabs.getSelectedComponent() == c) {
990 Utils.updateComponent(c);
994 /** A class to listen to the transparency_slider of the DisplayablesSelectorWindow. */
995 private class TransparencySliderListener extends MouseAdapter implements ChangeListener {
997 public void stateChanged(ChangeEvent ce) {
998 //change the transparency value of the current active displayable
999 float new_value = (float)((JSlider)ce.getSource()).getValue();
1000 setTransparency(new_value / 100.0f);
1003 public void mousePressed(MouseEvent me) {
1004 JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
1005 if (scroll != scroll_channels && !selection.isEmpty()) selection.addDataEditStep(new String[]{"alpha"});
1008 public void mouseReleased(MouseEvent me) {
1009 // update navigator window
1010 navigator.repaint(true);
1011 JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
1012 if (scroll != scroll_channels && !selection.isEmpty()) selection.addDataEditStep(new String[]{"alpha"});
1016 /** Context-sensitive: to a Displayable, or to a channel. */
1017 private void setTransparency(final float value) {
1018 JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
1019 if (scroll == scroll_channels) {
1020 for (int i=0; i<4; i++) {
1021 if (channels[i].getBackground() == Color.cyan) {
1022 channels[i].setAlpha(value); // will call back and repaint the Display
1023 return;
1026 } else if (null != active) {
1027 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.
1028 canvas.invalidateVolatile();
1029 selection.setAlpha(value);
1034 public void setTransparencySlider(final float transp) {
1035 if (transp >= 0.0f && transp <= 1.0f) {
1036 // fire event
1037 transp_slider.setValue((int)(transp * 100));
1041 /** Mark the canvas for updating the offscreen images if the given Displayable is NOT the active. */ // Used by the Displayable.setVisible for example.
1042 static public void setUpdateGraphics(final Layer layer, final Displayable displ) {
1043 for (final Display d : al_displays) {
1044 if (layer == d.layer && null != d.active && d.active != displ) {
1045 d.canvas.setUpdateGraphics(true);
1050 /** Flag the DisplayCanvas of Displays showing the given Layer to update their offscreen images.*/
1051 static public void setUpdateGraphics(final Layer layer, final boolean update) {
1052 for (final Display d : al_displays) {
1053 if (layer == d.layer) {
1054 d.canvas.setUpdateGraphics(update);
1059 /** Whether to update the offscreen images or not. */
1060 public void setUpdateGraphics(boolean b) {
1061 canvas.setUpdateGraphics(b);
1064 /** Find all Display instances that contain the layer and repaint them, in the Swing GUI thread. */
1065 static public void update(final Layer layer) {
1066 if (null == layer) return;
1067 SwingUtilities.invokeLater(new Runnable() { public void run() {
1068 for (final Display d : al_displays) {
1069 if (d.isShowing(layer)) {
1070 d.repaintAll();
1073 }});
1076 static public void update(final LayerSet set) {
1077 update(set, true);
1080 /** 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. */
1081 static public void update(final LayerSet set, final boolean update_canvas_dimensions) {
1082 if (null == set) return;
1083 SwingUtilities.invokeLater(new Runnable() { public void run() {
1084 for (final Display d : al_displays) {
1085 if (set.contains(d.layer)) {
1086 d.updateSnapshots();
1087 if (update_canvas_dimensions) d.canvas.setDimensions(set.getLayerWidth(), set.getLayerHeight());
1088 d.repaintAll();
1091 }});
1094 /** Release all resources held by this Display and close the frame. */
1095 protected void destroy() {
1096 dispatcher.quit();
1097 canvas.setReceivesInput(false);
1098 slt.quit();
1100 // update the coloring in the ProjectTree and LayerTree
1101 if (!project.isBeingDestroyed()) {
1102 try {
1103 project.getProjectTree().updateUILater();
1104 project.getLayerTree().updateUILater();
1105 } catch (Exception e) {
1106 Utils.log2("updateUI failed at Display.destroy()");
1110 frame.removeComponentListener(component_listener);
1111 frame.removeWindowListener(window_listener);
1112 frame.removeWindowFocusListener(window_listener);
1113 frame.removeWindowStateListener(window_listener);
1114 frame.removeKeyListener(canvas);
1115 frame.removeMouseListener(frame_mouse_listener);
1116 canvas_panel.removeKeyListener(canvas);
1117 canvas.removeKeyListener(canvas);
1118 tabs.removeChangeListener(tabs_listener);
1119 tabs.removeKeyListener(canvas);
1120 ImagePlus.removeImageListener(this);
1121 bytypelistener = null;
1122 canvas.destroy();
1123 navigator.destroy();
1124 scroller.removeAdjustmentListener(scroller_listener);
1125 frame.setVisible(false);
1126 //no need, and throws exception//frame.dispose();
1127 active = null;
1128 if (null != selection) selection.clear();
1129 //Utils.log2("destroying selection");
1131 // below, need for SetLayerThread threads to quit
1132 slt.quit();
1133 // set a new front if any
1134 if (null == front && al_displays.size() > 0) {
1135 front = (Display)al_displays.get(al_displays.size() -1);
1137 // repaint layer tree (to update the label color)
1138 try {
1139 project.getLayerTree().updateUILater(); // works only after setting the front above
1140 } catch (Exception e) {} // ignore swing sync bullshit when closing everything too fast
1141 // remove the drag and drop listener
1142 dnd.destroy();
1145 /** Find all Display instances that contain a Layer of the given project and close them without removing the Display entries from the database. */
1146 static synchronized public void close(final Project project) {
1147 /* // concurrent modifications if more than 1 Display are being removed asynchronously
1148 for (final Display d : al_displays) {
1149 if (d.getLayer().getProject().equals(project)) {
1150 it.remove();
1151 d.destroy();
1155 Display[] d = new Display[al_displays.size()];
1156 al_displays.toArray(d);
1157 for (int i=0; i<d.length; i++) {
1158 if (d[i].getProject() == project) {
1159 al_displays.remove(d[i]);
1160 d[i].destroy();
1165 /** Find all Display instances that contain the layer and close them and remove the Display from the database. */
1166 static public void close(final Layer layer) {
1167 for (Iterator it = al_displays.iterator(); it.hasNext(); ) {
1168 Display d = (Display)it.next();
1169 if (d.isShowing(layer)) {
1170 d.remove(false);
1171 it.remove();
1176 /** Find all Display instances that are showing the layer and either move to the next or previous layer, or close it if none. */
1177 static public void remove(final Layer layer) {
1178 for (Iterator<Display> it = al_displays.iterator(); it.hasNext(); ) {
1179 final Display d = it.next();
1180 if (d.isShowing(layer)) {
1181 Layer la = layer.getParent().next(layer);
1182 if (layer == la || null == la) la = layer.getParent().previous(layer);
1183 if (null == la || layer == la) {
1184 d.remove(false);
1185 it.remove();
1186 } else {
1187 d.slt.set(la);
1193 public boolean remove(boolean check) {
1194 if (check) {
1195 if (!Utils.check("Delete the Display ?")) return false;
1197 // flush the offscreen images and close the frame
1198 destroy();
1199 removeFromDatabase();
1200 return true;
1203 public Layer getLayer() {
1204 return layer;
1207 public LayerSet getLayerSet() {
1208 return layer.getParent();
1211 public boolean isShowing(final Layer layer) {
1212 return this.layer == layer;
1215 public DisplayNavigator getNavigator() {
1216 return navigator;
1219 /** Repaint both the canvas and the navigator, updating the graphics, and the title and tabs. */
1220 public void repaintAll() {
1221 if (repaint_disabled) return;
1222 navigator.repaint(true);
1223 canvas.repaint(true);
1224 Utils.updateComponent(tabs);
1225 updateTitle();
1228 /** Repaint the canvas updating graphics, the navigator without updating graphics, and the title. */
1229 public void repaintAll2() {
1230 if (repaint_disabled) return;
1231 navigator.repaint(false);
1232 canvas.repaint(true);
1233 updateTitle();
1236 static public void repaintSnapshots(final LayerSet set) {
1237 if (repaint_disabled) return;
1238 for (final Display d : al_displays) {
1239 if (d.getLayer().getParent() == set) {
1240 d.navigator.repaint(true);
1241 Utils.updateComponent(d.tabs);
1245 static public void repaintSnapshots(final Layer layer) {
1246 if (repaint_disabled) return;
1247 for (final Display d : al_displays) {
1248 if (d.getLayer() == layer) {
1249 d.navigator.repaint(true);
1250 Utils.updateComponent(d.tabs);
1255 public void pack() {
1256 dispatcher.exec(new Runnable() { public void run() {
1257 try {
1258 Thread.currentThread().sleep(100);
1259 SwingUtilities.invokeAndWait(new Runnable() { public void run() {
1260 frame.pack();
1261 }});
1262 } catch (Exception e) { IJError.print(e); }
1263 }});
1266 static public void pack(final LayerSet ls) {
1267 for (final Display d : al_displays) {
1268 if (d.layer.getParent() == ls) d.pack();
1272 private void adjustCanvas() {
1273 SwingUtilities.invokeLater(new Runnable() { public void run() {
1274 Rectangle r = split.getRightComponent().getBounds();
1275 canvas.setDrawingSize(r.width, r.height, true);
1276 // fix not-on-top-left problem
1277 canvas.setLocation(0, 0);
1278 //frame.pack(); // don't! Would go into an infinite loop
1279 canvas.repaint(true);
1280 updateInDatabase("srcRect");
1281 }});
1284 /** Grab the last selected display (or create an new one if none) and show in it the layer,centered on the Displayable object. */
1285 static public void setFront(final Layer layer, final Displayable displ) {
1286 if (null == front) {
1287 Display display = new Display(layer.getProject(), layer); // gets set to front
1288 display.showCentered(displ);
1289 } else if (layer == front.layer) {
1290 front.showCentered(displ);
1291 } else {
1292 // find one:
1293 for (final Display d : al_displays) {
1294 if (d.layer == layer) {
1295 d.frame.toFront();
1296 d.showCentered(displ);
1297 return;
1300 // else, open new one
1301 new Display(layer.getProject(), layer).showCentered(displ);
1305 /** 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. */
1306 static public void add(final Layer layer, final Displayable displ, final boolean activate) {
1307 for (final Display d : al_displays) {
1308 if (d.layer == layer) {
1309 if (front == d) {
1310 d.add(displ, activate, true);
1311 //front.frame.toFront();
1312 } else {
1313 d.add(displ, false, true);
1319 static public void add(final Layer layer, final Displayable displ) {
1320 add(layer, displ, true);
1323 /** Add the ZDisplayable to all Displays that show a Layer belonging to the given LayerSet. */
1324 static public void add(final LayerSet set, final ZDisplayable zdispl) {
1325 for (final Display d : al_displays) {
1326 if (set.contains(d.layer)) {
1327 if (front == d) {
1328 zdispl.setLayer(d.layer); // the active one
1329 d.add(zdispl, true, true); // calling add(Displayable, boolean, boolean)
1330 //front.frame.toFront();
1331 } else {
1332 d.add(zdispl, false, true);
1338 static public void addAll(final Layer layer, final Collection<? extends Displayable> coll) {
1339 for (final Display d : al_displays) {
1340 if (d.layer == layer) {
1341 d.addAll(coll);
1346 static public void addAll(final LayerSet set, final Collection<? extends ZDisplayable> coll) {
1347 for (final Display d : al_displays) {
1348 if (set.contains(d.layer)) {
1349 for (final ZDisplayable zd : coll) {
1350 if (front == d) zd.setLayer(d.layer);
1352 d.addAll(coll);
1357 private final void addAll(final Collection<? extends Displayable> coll) {
1358 for (final Displayable d : coll) {
1359 add(d, false, false);
1361 selection.clear();
1362 Utils.updateComponent(tabs);
1363 navigator.repaint(true);
1366 // TODO this very old method could take some improvement:
1367 // - there is no need to create a new DisplayablePanel if its panel is not shown
1368 // - other issues; the method looks overly "if a dog barks and a duck quacks during a lunar eclipse then .."
1369 /** Add it to the proper panel, at the top, and set it active. */
1370 private final void add(final Displayable d, final boolean activate, final boolean repaint_snapshot) {
1371 DisplayablePanel dp = ht_panels.get(d);
1372 if (null != dp && activate) { // for ZDisplayable objects (TODO I think this is not used anymore)
1373 dp.setActive(true);
1374 //setActive(d);
1375 selection.clear();
1376 selection.add(d);
1377 return;
1379 // add to the proper list
1380 JPanel p = null;
1381 if (d instanceof Profile) {
1382 p = panel_profiles;
1383 } else if (d instanceof Patch) {
1384 p = panel_patches;
1385 } else if (d instanceof DLabel) {
1386 p = panel_labels;
1387 } else if (d instanceof ZDisplayable) { //both pipes and balls and AreaList
1388 p = panel_zdispl;
1389 } else {
1390 // LayerSet objects
1391 return;
1393 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
1394 addToPanel(p, 0, dp, activate);
1395 ht_panels.put(d, dp);
1396 if (activate) {
1397 dp.setActive(true);
1398 //setActive(d);
1399 selection.clear();
1400 selection.add(d);
1402 if (repaint_snapshot) navigator.repaint(true);
1405 private void addToPanel(JPanel panel, int index, DisplayablePanel dp, boolean repaint) {
1406 // remove the label
1407 if (1 == panel.getComponentCount() && panel.getComponent(0) instanceof JLabel) {
1408 panel.removeAll();
1410 panel.add(dp, index);
1411 if (repaint) {
1412 Utils.updateComponent(tabs);
1416 /** Find the displays that show the given Layer, and remove the given Displayable from the GUI. */
1417 static public void remove(final Layer layer, final Displayable displ) {
1418 for (final Display d : al_displays) {
1419 if (layer == d.layer) d.remove(displ);
1423 private void remove(final Displayable displ) {
1424 DisplayablePanel ob = ht_panels.remove(displ);
1425 if (null != ob) {
1426 final JScrollPane jsp = ht_tabs.get(displ.getClass());
1427 if (null != jsp) {
1428 JPanel p = (JPanel)jsp.getViewport().getView();
1429 p.remove((Component)ob);
1430 Utils.revalidateComponent(p);
1433 if (null == active || !selection.contains(displ)) {
1434 canvas.setUpdateGraphics(true);
1436 canvas.invalidateVolatile(); // removing active, no need to update offscreen but yes the volatile
1437 repaint(displ, null, 5, true, false);
1438 // from Selection.deleteAll this method is called ... but it's ok: same thread, no locking problems.
1439 selection.remove(displ);
1442 static public void remove(final ZDisplayable zdispl) {
1443 for (final Display d : al_displays) {
1444 if (zdispl.getLayerSet() == d.layer.getParent()) {
1445 d.remove((Displayable)zdispl);
1450 static public void repaint(final Layer layer, final Displayable displ, final int extra) {
1451 repaint(layer, displ, displ.getBoundingBox(), extra);
1454 static public void repaint(final Layer layer, final Displayable displ, final Rectangle r, final int extra) {
1455 repaint(layer, displ, r, extra, true);
1458 /** Find the displays that show the given Layer, and repaint the given Displayable. */
1459 static public void repaint(final Layer layer, final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator) {
1460 if (repaint_disabled) return;
1461 for (final Display d : al_displays) {
1462 if (layer == d.layer) {
1463 d.repaint(displ, r, extra, repaint_navigator, false);
1468 static public void repaint(final Displayable d) {
1469 if (d instanceof ZDisplayable) repaint(d.getLayerSet(), d, d.getBoundingBox(null), 5, true);
1470 repaint(d.getLayer(), d, d.getBoundingBox(null), 5, true);
1473 /** Repaint as much as the bounding box around the given Displayable, or the r if not null. */
1474 private void repaint(final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator, final boolean update_graphics) {
1475 if (repaint_disabled || null == displ) return;
1476 if (update_graphics || displ.getClass() == Patch.class || displ != active) {
1477 canvas.setUpdateGraphics(true);
1479 if (null != r) canvas.repaint(r, extra);
1480 else canvas.repaint(displ, extra);
1481 if (repaint_navigator) {
1482 DisplayablePanel dp = ht_panels.get(displ);
1483 if (null != dp) dp.repaint(); // is null when creating it, or after deleting it
1484 navigator.repaint(true); // everything
1488 /** 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. */
1489 static public void repaintSnapshot(final Displayable displ) {
1490 for (final Display d : al_displays) {
1491 if (d.layer.contains(displ)) {
1492 if (!d.navigator.isPainted(displ)) {
1493 DisplayablePanel dp = d.ht_panels.get(displ);
1494 if (null != dp) dp.repaint(); // is null when creating it, or after deleting it
1495 d.navigator.repaint(displ);
1501 /** Repaint the given Rectangle in all Displays showing the layer, updating the offscreen image if any. */
1502 static public void repaint(final Layer layer, final Rectangle r, final int extra) {
1503 repaint(layer, extra, r, true, true);
1506 static public void repaint(final Layer layer, final int extra, final Rectangle r, final boolean update_navigator) {
1507 repaint(layer, extra, r, update_navigator, true);
1510 static public void repaint(final Layer layer, final int extra, final Rectangle r, final boolean update_navigator, final boolean update_graphics) {
1511 if (repaint_disabled) return;
1512 for (final Display d : al_displays) {
1513 if (layer == d.layer) {
1514 d.canvas.setUpdateGraphics(update_graphics);
1515 d.canvas.repaint(r, extra);
1516 if (update_navigator) {
1517 d.navigator.repaint(true);
1518 Utils.updateComponent(d.tabs.getSelectedComponent());
1525 /** Repaint the given Rectangle in all Displays showing the layer, optionally updating the offscreen image (if any). */
1526 static public void repaint(final Layer layer, final Rectangle r, final int extra, final boolean update_graphics) {
1527 if (repaint_disabled) return;
1528 for (final Display d : al_displays) {
1529 if (layer == d.layer) {
1530 d.canvas.setUpdateGraphics(update_graphics);
1531 d.canvas.repaint(r, extra);
1532 d.navigator.repaint(update_graphics);
1533 if (update_graphics) Utils.updateComponent(d.tabs.getSelectedComponent());
1538 /** Repaint the DisplayablePanel (and DisplayNavigator) only for the given Displayable, in all Displays showing the given Layer. */
1539 static public void repaint(final Layer layer, final Displayable displ) {
1540 if (repaint_disabled) return;
1541 for (final Display d : al_displays) {
1542 if (layer == d.layer) {
1543 DisplayablePanel dp = d.ht_panels.get(displ);
1544 if (null != dp) dp.repaint();
1545 d.navigator.repaint(true);
1550 static public void repaint(LayerSet set, Displayable displ, int extra) {
1551 repaint(set, displ, null, extra);
1554 static public void repaint(LayerSet set, Displayable displ, Rectangle r, int extra) {
1555 repaint(set, displ, r, extra, true);
1558 /** Repaint the Displayable in every Display that shows a Layer belonging to the given LayerSet. */
1559 static public void repaint(final LayerSet set, final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator) {
1560 if (repaint_disabled) return;
1561 for (final Display d : al_displays) {
1562 if (set.contains(d.layer)) {
1563 if (repaint_navigator) {
1564 if (null != displ) {
1565 DisplayablePanel dp = d.ht_panels.get(displ);
1566 if (null != dp) dp.repaint();
1568 d.navigator.repaint(true);
1570 if (null == displ || displ != d.active) d.setUpdateGraphics(true); // safeguard
1571 // paint the given box or the actual Displayable's box
1572 if (null != r) d.canvas.repaint(r, extra);
1573 else d.canvas.repaint(displ, extra);
1578 /** Repaint the entire LayerSet, in all Displays showing a Layer of it.*/
1579 static public void repaint(final LayerSet set) {
1580 if (repaint_disabled) return;
1581 for (final Display d : al_displays) {
1582 if (set.contains(d.layer)) {
1583 d.navigator.repaint(true);
1584 d.canvas.repaint(true);
1588 /** Repaint the given box in the LayerSet, in all Displays showing a Layer of it.*/
1589 static public void repaint(final LayerSet set, final Rectangle box) {
1590 if (repaint_disabled) return;
1591 for (final Display d : al_displays) {
1592 if (set.contains(d.layer)) {
1593 d.navigator.repaint(box);
1594 d.canvas.repaint(box, 0, true);
1598 /** Repaint the entire Layer, in all Displays showing it, including the tabs.*/
1599 static public void repaint(final Layer layer) { // TODO this method overlaps with update(layer)
1600 if (repaint_disabled) return;
1601 for (final Display d : al_displays) {
1602 if (layer == d.layer) {
1603 d.navigator.repaint(true);
1604 d.canvas.repaint(true);
1609 /** Call repaint on all open Displays. */
1610 static public void repaint() {
1611 if (repaint_disabled) {
1612 Utils.logAll("Can't repaint -- repainting is disabled!");
1613 return;
1615 for (final Display d : al_displays) {
1616 d.navigator.repaint(true);
1617 d.canvas.repaint(true);
1621 static private boolean repaint_disabled = false;
1623 /** Set a flag to enable/disable repainting of all Display instances. */
1624 static protected void setRepaint(boolean b) {
1625 repaint_disabled = !b;
1628 public Rectangle getBounds() {
1629 return frame.getBounds();
1632 public Point getLocation() {
1633 return frame.getLocation();
1636 public JFrame getFrame() {
1637 return frame;
1640 public void setLocation(Point p) {
1641 this.frame.setLocation(p);
1644 public Displayable getActive() {
1645 return active; //TODO this should return selection.active !!
1648 public void select(Displayable d) {
1649 select(d, false);
1652 /** Select/deselect accordingly to the current state and the shift key. */
1653 public void select(final Displayable d, final boolean shift_down) {
1654 if (null != active && active != d && active.getClass() != Patch.class) {
1655 // active is being deselected, so link underlying patches
1656 active.linkPatches();
1658 if (null == d) {
1659 //Utils.log2("Display.select: clearing selection");
1660 canvas.setUpdateGraphics(true);
1661 selection.clear();
1662 return;
1664 if (!shift_down) {
1665 //Utils.log2("Display.select: single selection");
1666 if (d != active) {
1667 selection.clear();
1668 selection.add(d);
1670 } else if (selection.contains(d)) {
1671 if (active == d) {
1672 selection.remove(d);
1673 //Utils.log2("Display.select: removing from a selection");
1674 } else {
1675 //Utils.log2("Display.select: activing within a selection");
1676 selection.setActive(d);
1678 } else {
1679 //Utils.log2("Display.select: adding to an existing selection");
1680 selection.add(d);
1682 // update the image shown to ImageJ
1683 // NO longer necessary, always he same FakeImagePlus // setTempCurrentImage();
1686 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p, final Class c) {
1687 choose(screen_x_p, screen_y_p, x_p, y_p, false, c);
1689 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p) {
1690 choose(screen_x_p, screen_y_p, x_p, y_p, false, null);
1693 /** 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. */
1694 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p, boolean shift_down, Class c) {
1695 //Utils.log("Display.choose: x,y " + x_p + "," + y_p);
1696 final ArrayList<Displayable> al = new ArrayList<Displayable>(layer.find(x_p, y_p, true));
1697 al.addAll(layer.getParent().findZDisplayables(layer, x_p, y_p, true)); // only visible ones
1698 if (al.isEmpty()) {
1699 Displayable act = this.active;
1700 selection.clear();
1701 canvas.setUpdateGraphics(true);
1702 //Utils.log("choose: set active to null");
1703 // fixing lack of repainting for unknown reasons, of the active one TODO this is a temporary solution
1704 if (null != act) Display.repaint(layer, act, 5);
1705 } else if (1 == al.size()) {
1706 Displayable d = (Displayable)al.get(0);
1707 if (null != c && d.getClass() != c) {
1708 selection.clear();
1709 return;
1711 select(d, shift_down);
1712 //Utils.log("choose 1: set active to " + active);
1713 } else {
1714 if (al.contains(active) && !shift_down) {
1715 // do nothing
1716 } else {
1717 if (null != c) {
1718 // check if at least one of them is of class c
1719 // if only one is of class c, set as selected
1720 // else show menu
1721 for (Iterator it = al.iterator(); it.hasNext(); ) {
1722 Object ob = it.next();
1723 if (ob.getClass() != c) it.remove();
1725 if (0 == al.size()) {
1726 // deselect
1727 selection.clear();
1728 return;
1730 if (1 == al.size()) {
1731 select((Displayable)al.get(0), shift_down);
1732 return;
1734 // else, choose among the many
1736 choose(screen_x_p, screen_y_p, al, shift_down, x_p, y_p);
1738 //Utils.log("choose many: set active to " + active);
1742 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) {
1743 // show a popup on the canvas to choose
1744 new Thread() {
1745 public void run() {
1746 final Object lock = new Object();
1747 final DisplayableChooser d_chooser = new DisplayableChooser(al, lock);
1748 final JPopupMenu pop = new JPopupMenu("Select:");
1749 final Iterator itu = al.iterator();
1750 while (itu.hasNext()) {
1751 Displayable d = (Displayable)itu.next();
1752 JMenuItem menu_item = new JMenuItem(d.toString());
1753 menu_item.addActionListener(d_chooser);
1754 pop.add(menu_item);
1757 new Thread() {
1758 public void run() {
1759 pop.show(canvas, screen_x_p, screen_y_p);
1761 }.start();
1763 //now wait until selecting something
1764 synchronized(lock) {
1765 do {
1766 try {
1767 lock.wait();
1768 } catch (InterruptedException ie) {}
1769 } while (d_chooser.isWaiting() && pop.isShowing());
1772 //grab the chosen Displayable object
1773 Displayable d = d_chooser.getChosen();
1774 //Utils.log("Chosen: " + d.toString());
1775 if (null == d) { Utils.log2("Display.choose: returning a null!"); }
1776 select(d, shift_down);
1777 pop.setVisible(false);
1779 // fix selection bug: never receives mouseReleased event when the popup shows
1780 selection.mouseReleased(null, x_p, y_p, x_p, y_p, x_p, y_p);
1782 }.start();
1785 /** 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. */
1786 protected void setActive(final Displayable displ) {
1787 final Displayable prev_active = this.active;
1788 this.active = displ;
1789 SwingUtilities.invokeLater(new Runnable() { public void run() {
1791 // renew current image if necessary
1792 if (null != displ && displ == prev_active) {
1793 // make sure the proper tab is selected.
1794 selectTab(displ);
1795 return; // the same
1797 // deactivate previously active
1798 if (null != prev_active) {
1799 final DisplayablePanel ob = ht_panels.get(prev_active);
1800 if (null != ob) ob.setActive(false);
1801 // erase "decorations" of the previously active
1802 canvas.repaint(selection.getBox(), 4);
1804 // activate the new active
1805 if (null != displ) {
1806 final DisplayablePanel ob = ht_panels.get(displ);
1807 if (null != ob) ob.setActive(true);
1808 updateInDatabase("active_displayable_id");
1809 if (displ.getClass() != Patch.class) project.select(displ); // select the node in the corresponding tree, if any.
1810 // select the proper tab, and scroll to visible
1811 selectTab(displ);
1812 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
1813 repaint(displ, null, 5, false, update_graphics); // to show the border, and to repaint out of the background image
1814 transp_slider.setValue((int)(displ.getAlpha() * 100));
1815 } else {
1816 //ensure decorations are removed from the panels, for Displayables in a selection besides the active one
1817 Utils.updateComponent(tabs.getSelectedComponent());
1819 }});
1822 /** If the other paints under the base. */
1823 public boolean paintsBelow(Displayable base, Displayable other) {
1824 boolean zd_base = base instanceof ZDisplayable;
1825 boolean zd_other = other instanceof ZDisplayable;
1826 if (zd_other) {
1827 if (base instanceof DLabel) return true; // zd paints under label
1828 if (!zd_base) return false; // any zd paints over a mere displ if not a label
1829 else {
1830 // both zd, compare indices
1831 ArrayList<ZDisplayable> al = other.getLayerSet().getZDisplayables();
1832 return al.indexOf(base) > al.indexOf(other);
1834 } else {
1835 if (!zd_base) {
1836 // both displ, compare indices
1837 ArrayList<Displayable> al = other.getLayer().getDisplayables();
1838 return al.indexOf(base) > al.indexOf(other);
1839 } else {
1840 // base is zd, other is d
1841 if (other instanceof DLabel) return false;
1842 return true;
1847 /** 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. */
1848 private void selectTab(final Displayable displ) {
1849 Method method = null;
1850 try {
1851 if (!(displ instanceof LayerSet)) {
1852 method = Display.class.getDeclaredMethod("selectTab", new Class[]{displ.getClass()});
1854 } catch (Exception e) {
1855 IJError.print(e);
1857 if (null != method) {
1858 final Method me = method;
1859 dispatcher.exec(new Runnable() { public void run() {
1860 try {
1861 me.setAccessible(true);
1862 me.invoke(Display.this, new Object[]{displ});
1863 } catch (Exception e) { IJError.print(e); }
1864 }});
1868 private void selectTab(Patch patch) {
1869 tabs.setSelectedComponent(scroll_patches);
1870 scrollToShow(scroll_patches, ht_panels.get(patch));
1873 private void selectTab(Profile profile) {
1874 tabs.setSelectedComponent(scroll_profiles);
1875 scrollToShow(scroll_profiles, ht_panels.get(profile));
1878 private void selectTab(DLabel label) {
1879 tabs.setSelectedComponent(scroll_labels);
1880 scrollToShow(scroll_labels, ht_panels.get(label));
1883 private void selectTab(ZDisplayable zd) {
1884 tabs.setSelectedComponent(scroll_zdispl);
1885 scrollToShow(scroll_zdispl, ht_panels.get(zd));
1888 private void selectTab(Pipe d) { selectTab((ZDisplayable)d); }
1889 private void selectTab(Polyline d) { selectTab((ZDisplayable)d); }
1890 private void selectTab(AreaList d) { selectTab((ZDisplayable)d); }
1891 private void selectTab(Ball d) { selectTab((ZDisplayable)d); }
1892 private void selectTab(Dissector d) { selectTab((ZDisplayable)d); }
1894 /** 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). */
1895 private void updateTab(final Container tab, final String label, final ArrayList al) {
1896 final boolean[] recreated = new boolean[]{false, true, true};
1897 dispatcher.execSwing(new Runnable() { public void run() {
1898 try {
1899 if (0 == al.size()) {
1900 tab.removeAll();
1901 tab.add(new JLabel("No " + label + "."));
1902 } else {
1903 Component[] comp = tab.getComponents();
1904 int next = 0;
1905 if (1 == comp.length && comp[0].getClass() == JLabel.class) {
1906 next = 1;
1907 tab.remove(0);
1909 for (Iterator it = al.iterator(); it.hasNext(); ) {
1910 Displayable d = (Displayable)it.next();
1911 DisplayablePanel dp = null;
1912 if (next < comp.length) {
1913 dp = (DisplayablePanel)comp[next++]; // recycling panels
1914 dp.set(d);
1915 } else {
1916 dp = new DisplayablePanel(Display.this, d);
1917 tab.add(dp);
1919 ht_panels.put(d, dp);
1921 if (next < comp.length) {
1922 // remove from the end, to avoid potential repaints of other panels
1923 for (int i=comp.length-1; i>=next; i--) {
1924 tab.remove(i);
1927 recreated[0] = true;
1929 if (recreated[0]) {
1930 tab.invalidate();
1931 tab.validate();
1932 tab.repaint();
1934 if (null != Display.this.active) scrollToShow(Display.this.active);
1935 } catch (Throwable e) { IJError.print(e); }
1936 }});
1939 static public void setActive(final Object event, final Displayable displ) {
1940 if (!(event instanceof InputEvent)) return;
1941 // find which Display
1942 for (final Display d : al_displays) {
1943 if (d.isOrigin((InputEvent)event)) {
1944 d.setActive(displ);
1945 break;
1950 /** Find out whether this Display is Transforming its active Displayable. */
1951 public boolean isTransforming() {
1952 return canvas.isTransforming();
1955 /** Find whether any Display is transforming the given Displayable. */
1956 static public boolean isTransforming(final Displayable displ) {
1957 for (final Display d : al_displays) {
1958 if (null != d.active && d.active == displ && d.canvas.isTransforming()) return true;
1960 return false;
1963 static public boolean isAligning(final LayerSet set) {
1964 for (final Display d : al_displays) {
1965 if (d.layer.getParent() == set && set.isAligning()) {
1966 return true;
1969 return false;
1972 /** Set the front Display to transform the Displayable only if no other canvas is transforming it. */
1973 static public void setTransforming(final Displayable displ) {
1974 if (null == front) return;
1975 if (front.active != displ) return;
1976 for (final Display d : al_displays) {
1977 if (d.active == displ) {
1978 if (d.canvas.isTransforming()) {
1979 Utils.showMessage("Already transforming " + displ.getTitle());
1980 return;
1984 front.canvas.setTransforming(true);
1987 /** Check whether the source of the event is located in this instance.*/
1988 private boolean isOrigin(InputEvent event) {
1989 Object source = event.getSource();
1990 // find it ... check the canvas for now TODO
1991 if (canvas == source) {
1992 return true;
1994 return false;
1997 /** Get the layer of the front Display, or null if none.*/
1998 static public Layer getFrontLayer() {
1999 if (null == front) return null;
2000 return front.layer;
2003 /** Get the layer of an open Display of the given Project, or null if none.*/
2004 static public Layer getFrontLayer(final Project project) {
2005 if (null == front) return null;
2006 if (front.project == project) return front.layer;
2007 // else, find an open Display for the given Project, if any
2008 for (final Display d : al_displays) {
2009 if (d.project == project) {
2010 d.frame.toFront();
2011 return d.layer;
2014 return null; // none found
2017 static public Display getFront(final Project project) {
2018 if (null == front) return null;
2019 if (front.project == project) return front;
2020 for (final Display d : al_displays) {
2021 if (d.project == project) {
2022 d.frame.toFront();
2023 return d;
2026 return null;
2029 public boolean isReadOnly() {
2030 // TEMPORARY: in the future one will be able show displays as read-only to other people, remotely
2031 return false;
2034 static public void showPopup(Component c, int x, int y) {
2035 if (null != front) front.getPopupMenu().show(c, x, y);
2038 /** Return a context-sensitive popup menu. */
2039 public JPopupMenu getPopupMenu() { // called from canvas
2040 // get the job canceling dialog
2041 if (!canvas.isInputEnabled()) {
2042 return project.getLoader().getJobsPopup(this);
2045 // create new
2046 this.popup = new JPopupMenu();
2047 JMenuItem item = null;
2048 JMenu menu = null;
2050 if (ProjectToolbar.ALIGN == Toolbar.getToolId()) {
2051 boolean aligning = layer.getParent().isAligning();
2052 item = new JMenuItem("Cancel alignment"); item.addActionListener(this); popup.add(item);
2053 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2054 if (!aligning) item.setEnabled(false);
2055 item = new JMenuItem("Align with landmarks"); item.addActionListener(this); popup.add(item);
2056 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true));
2057 if (!aligning) item.setEnabled(false);
2058 item = new JMenuItem("Align and register"); item.addActionListener(this); popup.add(item);
2059 if (!aligning) item.setEnabled(false);
2060 item = new JMenuItem("Align using profiles"); item.addActionListener(this); popup.add(item);
2061 if (!aligning || selection.isEmpty() || !selection.contains(Profile.class)) item.setEnabled(false);
2062 item = new JMenuItem("Align stack slices"); item.addActionListener(this); popup.add(item);
2063 if (selection.isEmpty() || ! (getActive().getClass() == Patch.class && ((Patch)getActive()).isStack())) item.setEnabled(false);
2064 item = new JMenuItem("Align layers"); item.addActionListener(this); popup.add(item);
2065 if (1 == layer.getParent().size()) item.setEnabled(false);
2066 item = new JMenuItem("Align multi-layer mosaic"); item.addActionListener(this); popup.add(item);
2067 if (1 == layer.getParent().size()) item.setEnabled(false);
2068 return popup;
2072 if (null != active) {
2073 if (!canvas.isTransforming()) {
2074 if (active instanceof Profile) {
2075 item = new JMenuItem("Duplicate, link and send to next layer"); item.addActionListener(this); popup.add(item);
2076 Layer nl = layer.getParent().next(layer);
2077 if (nl == layer) item.setEnabled(false);
2078 item = new JMenuItem("Duplicate, link and send to previous layer"); item.addActionListener(this); popup.add(item);
2079 nl = layer.getParent().previous(layer);
2080 if (nl == layer) item.setEnabled(false);
2082 menu = new JMenu("Duplicate, link and send to");
2083 ArrayList al = layer.getParent().getLayers();
2084 final Iterator it = al.iterator();
2085 int i = 1;
2086 while (it.hasNext()) {
2087 Layer la = (Layer)it.next();
2088 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
2089 if (la == this.layer) item.setEnabled(false);
2090 i++;
2092 popup.add(menu);
2093 item = new JMenuItem("Duplicate, link and send to..."); item.addActionListener(this); popup.add(item);
2095 popup.addSeparator();
2097 item = new JMenuItem("Unlink from images"); item.addActionListener(this); popup.add(item);
2098 if (!active.isLinked()) item.setEnabled(false); // isLinked() checks if it's linked to a Patch in its own layer
2099 item = new JMenuItem("Show in 3D"); item.addActionListener(this); popup.add(item);
2100 popup.addSeparator();
2101 } else if (active instanceof Patch) {
2102 item = new JMenuItem("Unlink from images"); item.addActionListener(this); popup.add(item);
2103 if (!active.isLinked(Patch.class)) item.setEnabled(false);
2104 if (((Patch)active).isStack()) {
2105 item = new JMenuItem("Unlink slices"); item.addActionListener(this); popup.add(item);
2107 int n_sel_patches = selection.getSelected(Patch.class).size();
2108 if (1 == n_sel_patches) {
2109 item = new JMenuItem("Snap"); item.addActionListener(this); popup.add(item);
2110 } else if (n_sel_patches > 1) {
2111 item = new JMenuItem("Montage"); item.addActionListener(this); popup.add(item);
2112 item = new JMenuItem("Lens correction"); item.addActionListener(this); popup.add(item);
2113 item = new JMenuItem("Blend"); item.addActionListener(this); popup.add(item);
2115 item = new JMenuItem("Remove alpha mask"); item.addActionListener(this); popup.add(item);
2116 if ( ! ((Patch)active).hasAlphaMask()) item.setEnabled(false);
2117 item = new JMenuItem("Link images..."); item.addActionListener(this); popup.add(item);
2118 item = new JMenuItem("View volume"); item.addActionListener(this); popup.add(item);
2119 HashSet hs = active.getLinked(Patch.class);
2120 if (null == hs || 0 == hs.size()) item.setEnabled(false);
2121 item = new JMenuItem("View orthoslices"); item.addActionListener(this); popup.add(item);
2122 if (null == hs || 0 == hs.size()) item.setEnabled(false); // if no Patch instances among the directly linked, then it's not a stack
2123 popup.addSeparator();
2124 } else {
2125 item = new JMenuItem("Unlink"); item.addActionListener(this); popup.add(item);
2126 item = new JMenuItem("Show in 3D"); item.addActionListener(this); popup.add(item);
2127 popup.addSeparator();
2129 if (active instanceof AreaList) {
2130 item = new JMenuItem("Merge"); item.addActionListener(this); popup.add(item);
2131 ArrayList al = selection.getSelected();
2132 int n = 0;
2133 for (Iterator it = al.iterator(); it.hasNext(); ) {
2134 if (it.next().getClass() == AreaList.class) n++;
2136 if (n < 2) item.setEnabled(false);
2137 } else if (active instanceof Pipe) {
2138 item = new JMenuItem("Identify..."); item.addActionListener(this); popup.add(item);
2139 item = new JMenuItem("Identify with axes..."); item.addActionListener(this); popup.add(item);
2142 if (canvas.isTransforming()) {
2143 item = new JMenuItem("Apply transform"); item.addActionListener(this); popup.add(item);
2144 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
2145 item = new JMenuItem("Apply transform propagating to last layer"); item.addActionListener(this); popup.add(item);
2146 if (layer.getParent().indexOf(layer) == layer.getParent().size() -1) item.setEnabled(false);
2147 item = new JMenuItem("Apply transform propagating to first layer"); item.addActionListener(this); popup.add(item);
2148 if (0 == layer.getParent().indexOf(layer)) item.setEnabled(false);
2149 } else {
2150 item = new JMenuItem("Transform"); item.addActionListener(this); popup.add(item);
2151 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0, true));
2153 item = new JMenuItem("Cancel transform"); item.addActionListener(this); popup.add(item);
2154 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2155 if (!canvas.isTransforming()) item.setEnabled(false);
2156 if (canvas.isTransforming()) {
2157 item = new JMenuItem("Specify transform..."); item.addActionListener(this); popup.add(item);
2160 if (!canvas.isTransforming()) {
2161 item = new JMenuItem("Duplicate"); item.addActionListener(this); popup.add(item);
2162 item = new JMenuItem("Color..."); item.addActionListener(this); popup.add(item);
2163 if (active instanceof LayerSet) item.setEnabled(false);
2164 if (active.isLocked()) {
2165 item = new JMenuItem("Unlock"); item.addActionListener(this); popup.add(item);
2166 } else {
2167 item = new JMenuItem("Lock"); item.addActionListener(this); popup.add(item);
2169 menu = new JMenu("Move");
2170 popup.addSeparator();
2171 LayerSet ls = layer.getParent();
2172 item = new JMenuItem("Move to top"); item.addActionListener(this); menu.add(item);
2173 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.
2174 if (ls.isTop(active)) item.setEnabled(false);
2175 item = new JMenuItem("Move up"); item.addActionListener(this); menu.add(item);
2176 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0, true));
2177 if (ls.isTop(active)) item.setEnabled(false);
2178 item = new JMenuItem("Move down"); item.addActionListener(this); menu.add(item);
2179 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0, true));
2180 if (ls.isBottom(active)) item.setEnabled(false);
2181 item = new JMenuItem("Move to bottom"); item.addActionListener(this); menu.add(item);
2182 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_END, 0, true));
2183 if (ls.isBottom(active)) item.setEnabled(false);
2185 popup.add(menu);
2186 popup.addSeparator();
2187 item = new JMenuItem("Delete..."); item.addActionListener(this); popup.add(item);
2188 try {
2189 if (active instanceof Patch) {
2190 if (!active.isOnlyLinkedTo(Patch.class)) {
2191 item.setEnabled(false);
2193 } else if (!(active instanceof DLabel)) { // can't delete elements from the trees (Profile, Pipe, LayerSet)
2194 item.setEnabled(false);
2196 } catch (Exception e) { IJError.print(e); item.setEnabled(false); }
2198 if (active instanceof Patch) {
2199 item = new JMenuItem("Revert"); item.addActionListener(this); popup.add(item);
2200 popup.addSeparator();
2202 item = new JMenuItem("Properties..."); item.addActionListener(this); popup.add(item);
2203 item = new JMenuItem("Show centered"); item.addActionListener(this); popup.add(item);
2205 popup.addSeparator();
2207 if (! (active instanceof ZDisplayable)) {
2208 ArrayList al_layers = layer.getParent().getLayers();
2209 int i_layer = al_layers.indexOf(layer);
2210 int n_layers = al_layers.size();
2211 item = new JMenuItem("Send to previous layer"); item.addActionListener(this); popup.add(item);
2212 if (1 == n_layers || 0 == i_layer || active.isLinked()) item.setEnabled(false);
2213 // 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
2214 else if (active instanceof Profile && !active.canSendTo(layer.getParent().previous(layer))) item.setEnabled(false);
2215 item = new JMenuItem("Send to next layer"); item.addActionListener(this); popup.add(item);
2216 if (1 == n_layers || n_layers -1 == i_layer || active.isLinked()) item.setEnabled(false);
2217 else if (active instanceof Profile && !active.canSendTo(layer.getParent().next(layer))) item.setEnabled(false);
2220 menu = new JMenu("Send linked group to...");
2221 if (active.hasLinkedGroupWithinLayer(this.layer)) {
2222 int i = 1;
2223 for (final Layer la : ls.getLayers()) {
2224 String layer_title = i + ": " + la.getTitle();
2225 if (-1 == layer_title.indexOf(' ')) layer_title += " ";
2226 item = new JMenuItem(layer_title); item.addActionListener(this); menu.add(item);
2227 if (la == this.layer) item.setEnabled(false);
2228 i++;
2230 popup.add(menu);
2231 } else {
2232 menu.setEnabled(false);
2233 //Utils.log("Active's linked group not within layer.");
2235 popup.add(menu);
2236 popup.addSeparator();
2241 if (!canvas.isTransforming()) {
2243 item = new JMenuItem("Undo");item.addActionListener(this); popup.add(item);
2244 if (!layer.getParent().canUndo() || canvas.isTransforming()) item.setEnabled(false);
2245 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Utils.getControlModifier(), true));
2246 item = new JMenuItem("Redo");item.addActionListener(this); popup.add(item);
2247 if (!layer.getParent().canRedo() || canvas.isTransforming()) item.setEnabled(false);
2248 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Event.SHIFT_MASK | Event.CTRL_MASK, true));
2249 popup.addSeparator();
2251 // Would get so much simpler with a clojure macro ...
2253 try {
2254 menu = new JMenu("Hide/Unhide");
2255 item = new JMenuItem("Hide deselected"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.SHIFT_MASK, true));
2256 boolean none = 0 == selection.getNSelected();
2257 if (none) item.setEnabled(false);
2258 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));
2259 if (none) item.setEnabled(false);
2260 item = new JMenuItem("Hide selected"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, 0, true));
2261 if (none) item.setEnabled(false);
2262 none = ! layer.getParent().containsDisplayable(DLabel.class);
2263 item = new JMenuItem("Hide all labels"); item.addActionListener(this); menu.add(item);
2264 if (none) item.setEnabled(false);
2265 item = new JMenuItem("Unhide all labels"); item.addActionListener(this); menu.add(item);
2266 if (none) item.setEnabled(false);
2267 none = ! layer.getParent().contains(AreaList.class);
2268 item = new JMenuItem("Hide all arealists"); item.addActionListener(this); menu.add(item);
2269 if (none) item.setEnabled(false);
2270 item = new JMenuItem("Unhide all arealists"); item.addActionListener(this); menu.add(item);
2271 if (none) item.setEnabled(false);
2272 none = ! layer.contains(Profile.class);
2273 item = new JMenuItem("Hide all profiles"); item.addActionListener(this); menu.add(item);
2274 if (none) item.setEnabled(false);
2275 item = new JMenuItem("Unhide all profiles"); item.addActionListener(this); menu.add(item);
2276 if (none) item.setEnabled(false);
2277 none = ! layer.getParent().contains(Pipe.class);
2278 item = new JMenuItem("Hide all pipes"); item.addActionListener(this); menu.add(item);
2279 if (none) item.setEnabled(false);
2280 item = new JMenuItem("Unhide all pipes"); item.addActionListener(this); menu.add(item);
2281 if (none) item.setEnabled(false);
2282 none = ! layer.getParent().contains(Polyline.class);
2283 item = new JMenuItem("Hide all polylines"); item.addActionListener(this); menu.add(item);
2284 if (none) item.setEnabled(false);
2285 item = new JMenuItem("Unhide all polylines"); item.addActionListener(this); menu.add(item);
2286 if (none) item.setEnabled(false);
2287 none = ! layer.getParent().contains(Ball.class);
2288 item = new JMenuItem("Hide all balls"); item.addActionListener(this); menu.add(item);
2289 if (none) item.setEnabled(false);
2290 item = new JMenuItem("Unhide all balls"); item.addActionListener(this); menu.add(item);
2291 if (none) item.setEnabled(false);
2292 none = ! layer.getParent().containsDisplayable(Patch.class);
2293 item = new JMenuItem("Hide all images"); item.addActionListener(this); menu.add(item);
2294 if (none) item.setEnabled(false);
2295 item = new JMenuItem("Unhide all images"); item.addActionListener(this); menu.add(item);
2296 if (none) item.setEnabled(false);
2297 item = new JMenuItem("Hide all but images"); item.addActionListener(this); menu.add(item);
2298 item = new JMenuItem("Unhide all"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.ALT_MASK, true));
2300 popup.add(menu);
2301 } catch (Exception e) { IJError.print(e); }
2303 JMenu adjust_menu = new JMenu("Adjust");
2304 item = new JMenuItem("Enhance contrast layer-wise..."); item.addActionListener(this); adjust_menu.add(item);
2305 item = new JMenuItem("Enhance contrast (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
2306 if (selection.isEmpty()) item.setEnabled(false);
2307 item = new JMenuItem("Set Min and Max layer-wise..."); item.addActionListener(this); adjust_menu.add(item);
2308 item = new JMenuItem("Set Min and Max (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
2309 if (selection.isEmpty()) item.setEnabled(false);
2310 popup.add(adjust_menu);
2312 menu = new JMenu("Import");
2313 item = new JMenuItem("Import image"); item.addActionListener(this); menu.add(item);
2314 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, Event.ALT_MASK & Event.SHIFT_MASK, true));
2315 item = new JMenuItem("Import stack..."); item.addActionListener(this); menu.add(item);
2316 item = new JMenuItem("Import grid..."); item.addActionListener(this); menu.add(item);
2317 item = new JMenuItem("Import sequence as grid..."); item.addActionListener(this); menu.add(item);
2318 item = new JMenuItem("Import from text file..."); item.addActionListener(this); menu.add(item);
2319 item = new JMenuItem("Import labels as arealists..."); item.addActionListener(this); menu.add(item);
2320 popup.add(menu);
2322 menu = new JMenu("Export");
2323 item = new JMenuItem("Make flat image..."); item.addActionListener(this); menu.add(item);
2324 item = new JMenuItem("Arealists as labels (tif)"); item.addActionListener(this); menu.add(item);
2325 if (0 == layer.getParent().getZDisplayables(AreaList.class).size()) item.setEnabled(false);
2326 item = new JMenuItem("Arealists as labels (amira)"); item.addActionListener(this); menu.add(item);
2327 if (0 == layer.getParent().getZDisplayables(AreaList.class).size()) item.setEnabled(false);
2328 popup.add(menu);
2330 menu = new JMenu("Display");
2331 item = new JMenuItem("Resize canvas/LayerSet..."); item.addActionListener(this); menu.add(item);
2332 item = new JMenuItem("Autoresize canvas/LayerSet"); item.addActionListener(this); menu.add(item);
2333 // OBSOLETE // item = new JMenuItem("Rotate Layer/LayerSet..."); item.addActionListener(this); menu.add(item);
2334 item = new JMenuItem("Properties ..."); item.addActionListener(this); menu.add(item);
2335 popup.add(menu);
2337 menu = new JMenu("Project");
2338 this.project.getLoader().setupMenuItems(menu, this.getProject());
2339 item = new JMenuItem("Project properties..."); item.addActionListener(this); menu.add(item);
2340 item = new JMenuItem("Create subproject"); item.addActionListener(this); menu.add(item);
2341 if (null == canvas.getFakeImagePlus().getRoi()) item.setEnabled(false);
2342 item = new JMenuItem("Release memory..."); item.addActionListener(this); menu.add(item);
2343 item = new JMenuItem("Flush image cache"); item.addActionListener(this); menu.add(item);
2344 item = new JMenuItem("Regenerate all mipmaps"); item.addActionListener(this); menu.add(item);
2345 popup.add(menu);
2347 menu = new JMenu("Selection");
2348 item = new JMenuItem("Select all"); item.addActionListener(this); menu.add(item);
2349 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, Utils.getControlModifier(), true));
2350 if (0 == layer.getDisplayables().size() && 0 == layer.getParent().getZDisplayables().size()) item.setEnabled(false);
2351 item = new JMenuItem("Select none"); item.addActionListener(this); menu.add(item);
2352 if (0 == selection.getNSelected()) item.setEnabled(false);
2353 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2355 JMenu bytype = new JMenu("Select all by type");
2356 item = new JMenuItem("AreaList"); item.addActionListener(bytypelistener); bytype.add(item);
2357 item = new JMenuItem("Ball"); item.addActionListener(bytypelistener); bytype.add(item);
2358 item = new JMenuItem("Dissector"); item.addActionListener(bytypelistener); bytype.add(item);
2359 item = new JMenuItem("Image"); item.addActionListener(bytypelistener); bytype.add(item);
2360 item = new JMenuItem("Text"); item.addActionListener(bytypelistener); bytype.add(item);
2361 item = new JMenuItem("Pipe"); item.addActionListener(bytypelistener); bytype.add(item);
2362 item = new JMenuItem("Polyline"); item.addActionListener(bytypelistener); bytype.add(item);
2363 item = new JMenuItem("Profile"); item.addActionListener(bytypelistener); bytype.add(item);
2364 menu.add(bytype);
2366 item = new JMenuItem("Restore selection"); item.addActionListener(this); menu.add(item);
2367 item = new JMenuItem("Select under ROI"); item.addActionListener(this); menu.add(item);
2368 if (canvas.getFakeImagePlus().getRoi() == null) item.setEnabled(false);
2369 popup.add(menu);
2370 item = new JMenuItem("Search..."); item.addActionListener(this); popup.add(item);
2373 //canvas.add(popup);
2374 return popup;
2377 private ByTypeListener bytypelistener = new ByTypeListener(this);
2379 static private class ByTypeListener implements ActionListener {
2380 final Display d;
2381 ByTypeListener(final Display d) {
2382 this.d = d;
2384 public void actionPerformed(final ActionEvent ae) {
2385 final String command = ae.getActionCommand();
2387 final java.awt.geom.Area aroi = M.getArea(d.canvas.getFakeImagePlus().getRoi());
2389 d.dispatcher.exec(new Runnable() { public void run() {
2391 try {
2392 String type = command;
2393 if (type.equals("Image")) type = "Patch";
2394 Class c = Class.forName("ini.trakem2.display." + type);
2396 java.util.List<Displayable> a = new ArrayList<Displayable>();
2397 if (null != aroi) {
2398 a.addAll(d.layer.getDisplayables(c, aroi, true));
2399 a.addAll(d.layer.getParent().getZDisplayables(c, d.layer, aroi, true));
2400 } else {
2401 a.addAll(d.layer.getDisplayables(c));
2402 a.addAll(d.layer.getParent().getZDisplayables(c));
2403 // Remove non-visible ones
2404 for (final Iterator<Displayable> it = a.iterator(); it.hasNext(); ) {
2405 if (!it.next().isVisible()) it.remove();
2409 if (0 == a.size()) return;
2411 boolean selected = false;
2413 if (0 == ae.getModifiers()) {
2414 Utils.log2("first");
2415 d.selection.clear();
2416 d.selection.selectAll(a);
2417 selected = true;
2418 } else if (0 == (ae.getModifiers() ^ Event.SHIFT_MASK)) {
2419 Utils.log2("with shift");
2420 d.selection.selectAll(a); // just add them to the current selection
2421 selected = true;
2423 if (selected) {
2424 // Activate last:
2425 d.selection.setActive(a.get(a.size() -1));
2428 } catch (ClassNotFoundException e) {
2429 Utils.log2(e.toString());
2432 }});
2436 /** Check if a panel for the given Displayable is completely visible in the JScrollPane */
2437 public boolean isWithinViewport(final Displayable d) {
2438 final JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
2439 if (ht_tabs.get(d.getClass()) == scroll) return isWithinViewport(scroll, ht_panels.get(d));
2440 return false;
2443 private boolean isWithinViewport(JScrollPane scroll, DisplayablePanel dp) {
2444 if(null == dp) return false;
2445 JViewport view = scroll.getViewport();
2446 java.awt.Dimension dimensions = view.getExtentSize();
2447 java.awt.Point p = view.getViewPosition();
2448 int y = dp.getY();
2449 if ((y + DisplayablePanel.HEIGHT - p.y) <= dimensions.height && y >= p.y) {
2450 return true;
2452 return false;
2455 /** Check if a panel for the given Displayable is partially visible in the JScrollPane */
2456 public boolean isPartiallyWithinViewport(final Displayable d) {
2457 final JScrollPane scroll = ht_tabs.get(d.getClass());
2458 if (tabs.getSelectedComponent() == scroll) return isPartiallyWithinViewport(scroll, ht_panels.get(d));
2459 return false;
2462 /** Check if a panel for the given Displayable is at least partially visible in the JScrollPane */
2463 private boolean isPartiallyWithinViewport(final JScrollPane scroll, final DisplayablePanel dp) {
2464 if(null == dp) {
2465 //Utils.log2("Display.isPartiallyWithinViewport: null DisplayablePanel ??");
2466 return false; // to fast for you baby
2468 JViewport view = scroll.getViewport();
2469 java.awt.Dimension dimensions = view.getExtentSize();
2470 java.awt.Point p = view.getViewPosition();
2471 int y = dp.getY();
2472 if ( ((y + DisplayablePanel.HEIGHT - p.y) <= dimensions.height && y >= p.y) // completely visible
2473 || ((y + DisplayablePanel.HEIGHT - p.y) > dimensions.height && y < p.y + dimensions.height) // partially hovering at the bottom
2474 || ((y + DisplayablePanel.HEIGHT) > p.y && y < p.y) // partially hovering at the top
2476 return true;
2478 return false;
2481 /** A function to make a Displayable panel be visible in the screen, by scrolling the viewport of the JScrollPane. */
2482 private void scrollToShow(final Displayable d) {
2483 dispatcher.execSwing(new Runnable() { public void run() {
2484 final JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
2485 if (d instanceof ZDisplayable && scroll == scroll_zdispl) {
2486 scrollToShow(scroll_zdispl, ht_panels.get(d));
2487 return;
2489 final Class c = d.getClass();
2490 if (Patch.class == c && scroll == scroll_patches) {
2491 scrollToShow(scroll_patches, ht_panels.get(d));
2492 } else if (DLabel.class == c && scroll == scroll_labels) {
2493 scrollToShow(scroll_labels, ht_panels.get(d));
2494 } else if (Profile.class == c && scroll == scroll_profiles) {
2495 scrollToShow(scroll_profiles, ht_panels.get(d));
2497 }});
2500 private void scrollToShow(final JScrollPane scroll, final JPanel dp) {
2501 if (null == dp) return;
2502 JViewport view = scroll.getViewport();
2503 Point current = view.getViewPosition();
2504 Dimension extent = view.getExtentSize();
2505 int panel_y = dp.getY();
2506 if ((panel_y + DisplayablePanel.HEIGHT - current.y) <= extent.height && panel_y >= current.y) {
2507 // it's completely visible already
2508 return;
2509 } else {
2510 // scroll just enough
2511 // if it's above, show at the top
2512 if (panel_y - current.y < 0) {
2513 view.setViewPosition(new Point(0, panel_y));
2515 // if it's below (even if partially), show at the bottom
2516 else if (panel_y + 50 > current.y + extent.height) {
2517 view.setViewPosition(new Point(0, panel_y - extent.height + 50));
2518 //Utils.log("Display.scrollToShow: panel_y: " + panel_y + " current.y: " + current.y + " extent.height: " + extent.height);
2523 /** Update the title of the given Displayable in its DisplayablePanel, if any. */
2524 static public void updateTitle(final Layer layer, final Displayable displ) {
2525 for (final Display d : al_displays) {
2526 if (layer == d.layer) {
2527 DisplayablePanel dp = d.ht_panels.get(displ);
2528 if (null != dp) dp.updateTitle();
2533 /** Update the Display's title in all Displays showing the given Layer. */
2534 static public void updateTitle(final Layer layer) {
2535 for (final Display d : al_displays) {
2536 if (d.layer == layer) {
2537 d.updateTitle();
2541 /** Update the Display's title in all Displays showing a Layer of the given LayerSet. */
2542 static public void updateTitle(final LayerSet ls) {
2543 for (final Display d : al_displays) {
2544 if (d.layer.getParent() == ls) {
2545 d.updateTitle();
2550 /** Set a new title in the JFrame, showing info on the layer 'z' and the magnification. */
2551 public void updateTitle() {
2552 // From ij.ImagePlus class, the solution:
2553 String scale = "";
2554 final double magnification = canvas.getMagnification();
2555 if (magnification!=1.0) {
2556 final double percent = magnification*100.0;
2557 scale = new StringBuffer(" (").append(Utils.d2s(percent, percent==(int)percent ? 0 : 1)).append("%)").toString();
2559 final Calibration cal = layer.getParent().getCalibration();
2560 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();
2561 frame.setTitle(title);
2562 // fix the title for the FakeImageWindow and thus the WindowManager listing in the menus
2563 canvas.getFakeImagePlus().setTitle(title);
2566 /** 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. */
2567 public void nextLayer(final int modifiers) {
2568 final Layer l;
2569 if (0 == (modifiers ^ Event.SHIFT_MASK)) {
2570 l = layer.getParent().nextNonEmpty(layer);
2571 } else if (scroll_step > 1) {
2572 int i = layer.getParent().indexOf(this.layer);
2573 Layer la = layer.getParent().getLayer(i + scroll_step);
2574 if (null != la) l = la;
2575 else l = null;
2576 } else {
2577 l = layer.getParent().next(layer);
2579 if (l != layer) {
2580 slt.set(l);
2581 updateInDatabase("layer_id");
2585 private final void translateLayerColors(final Layer current, final Layer other) {
2586 if (current == other) return;
2587 if (layer_channels.size() > 0) {
2588 final LayerSet ls = getLayerSet();
2589 // translate colors by distance from current layer to new Layer l
2590 final int dist = ls.indexOf(other) - ls.indexOf(current);
2591 translateLayerColor(Color.red, dist);
2592 translateLayerColor(Color.blue, dist);
2596 private final void translateLayerColor(final Color color, final int dist) {
2597 final LayerSet ls = getLayerSet();
2598 final Layer l = layer_channels.get(color);
2599 if (null == l) return;
2600 updateColor(Color.white, layer_panels.get(l));
2601 final Layer l2 = ls.getLayer(ls.indexOf(l) + dist);
2602 if (null != l2) updateColor(color, layer_panels.get(l2));
2605 private final void updateColor(final Color color, final LayerPanel lp) {
2606 lp.setColor(color);
2607 setColorChannel(lp.layer, color);
2610 /** Calls setLayer(la) on the SetLayerThread. */
2611 public void toLayer(final Layer la) {
2612 if (la.getParent() != layer.getParent()) return; // not of the same LayerSet
2613 if (la == layer) return; // nothing to do
2614 slt.set(la);
2615 updateInDatabase("layer_id");
2618 /** 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. */
2619 public void previousLayer(final int modifiers) {
2620 final Layer l;
2621 if (0 == (modifiers ^ Event.SHIFT_MASK)) {
2622 l = layer.getParent().previousNonEmpty(layer);
2623 } else if (scroll_step > 1) {
2624 int i = layer.getParent().indexOf(this.layer);
2625 Layer la = layer.getParent().getLayer(i - scroll_step);
2626 if (null != la) l = la;
2627 else l = null;
2628 } else {
2629 l = layer.getParent().previous(layer);
2631 if (l != layer) {
2632 slt.set(l);
2633 updateInDatabase("layer_id");
2637 static public void updateLayerScroller(LayerSet set) {
2638 for (final Display d : al_displays) {
2639 if (d.layer.getParent() == set) {
2640 d.updateLayerScroller(d.layer);
2645 private void updateLayerScroller(Layer layer) {
2646 int size = layer.getParent().size();
2647 if (size <= 1) {
2648 scroller.setValues(0, 1, 0, 0);
2649 scroller.setEnabled(false);
2650 } else {
2651 scroller.setEnabled(true);
2652 scroller.setValues(layer.getParent().getLayerIndex(layer.getId()), 1, 0, size);
2654 recreateLayerPanels(layer);
2657 // Can't use this.layer, may still be null. User argument instead.
2658 private synchronized void recreateLayerPanels(final Layer layer) {
2659 synchronized (layer_channels) {
2660 panel_layers.removeAll();
2662 if (0 == layer_panels.size()) {
2663 for (final Layer la : layer.getParent().getLayers()) {
2664 final LayerPanel lp = new LayerPanel(this, la);
2665 layer_panels.put(la, lp);
2666 this.panel_layers.add(lp);
2668 } else {
2669 // Set theory at work: keep old to reuse
2670 layer_panels.keySet().retainAll(layer.getParent().getLayers());
2671 for (final Layer la : layer.getParent().getLayers()) {
2672 LayerPanel lp = layer_panels.get(la);
2673 if (null == lp) {
2674 lp = new LayerPanel(this, la);
2675 layer_panels.put(la, lp);
2677 this.panel_layers.add(lp);
2679 for (final Iterator<Map.Entry<Integer,LayerPanel>> it = layer_alpha.entrySet().iterator(); it.hasNext(); ) {
2680 final Map.Entry<Integer,LayerPanel> e = it.next();
2681 if (-1 == getLayerSet().indexOf(e.getValue().layer)) it.remove();
2683 for (final Iterator<Map.Entry<Color,Layer>> it = layer_channels.entrySet().iterator(); it.hasNext(); ) {
2684 final Map.Entry<Color,Layer> e = it.next();
2685 if (-1 == getLayerSet().indexOf(e.getValue())) it.remove();
2687 scroll_layers.repaint();
2692 private void updateSnapshots() {
2693 Enumeration<DisplayablePanel> e = ht_panels.elements();
2694 while (e.hasMoreElements()) {
2695 e.nextElement().remake();
2697 Utils.updateComponent(tabs.getSelectedComponent());
2700 static public void updatePanel(Layer layer, final Displayable displ) {
2701 if (null == layer && null != front) layer = front.layer; // the front layer
2702 for (final Display d : al_displays) {
2703 if (d.layer == layer) {
2704 d.updatePanel(displ);
2709 private void updatePanel(Displayable d) {
2710 JPanel c = null;
2711 if (d instanceof Profile) {
2712 c = panel_profiles;
2713 } else if (d instanceof Patch) {
2714 c = panel_patches;
2715 } else if (d instanceof DLabel) {
2716 c = panel_labels;
2717 } else if (d instanceof Pipe) {
2718 c = panel_zdispl;
2720 if (null == c) return;
2721 DisplayablePanel dp = ht_panels.get(d);
2722 dp.remake();
2723 Utils.updateComponent(c);
2726 static public void updatePanelIndex(final Layer layer, final Displayable displ) {
2727 for (final Display d : al_displays) {
2728 if (d.layer == layer || displ instanceof ZDisplayable) {
2729 d.updatePanelIndex(displ);
2734 private void updatePanelIndex(final Displayable d) {
2735 // find first of the kind, then remove and insert its panel
2736 int i = 0;
2737 JPanel c = null;
2738 if (d instanceof ZDisplayable) {
2739 i = layer.getParent().indexOf((ZDisplayable)d);
2740 c = panel_zdispl;
2741 } else {
2742 i = layer.relativeIndexOf(d);
2743 if (d instanceof Profile) {
2744 c = panel_profiles;
2745 } else if (d instanceof Patch) {
2746 c = panel_patches;
2747 } else if (d instanceof DLabel) {
2748 c = panel_labels;
2751 if (null == c) return;
2752 DisplayablePanel dp = ht_panels.get(d);
2753 if (null == dp) return; // may be half-baked, wait
2754 c.remove(dp);
2755 c.add(dp, i); // java and its fabulous consistency
2756 // not enough! Utils.updateComponent(c);
2757 // So, cocktail:
2758 c.invalidate();
2759 c.validate();
2760 Utils.updateComponent(c);
2763 /** Repair possibly missing panels and other components by simply resetting the same Layer */
2764 public void repairGUI() {
2765 Layer layer = this.layer;
2766 this.layer = null;
2767 setLayer(layer);
2770 public void actionPerformed(final ActionEvent ae) {
2771 dispatcher.exec(new Runnable() { public void run() {
2773 String command = ae.getActionCommand();
2774 if (command.startsWith("Job")) {
2775 if (Utils.checkYN("Really cancel job?")) {
2776 project.getLoader().quitJob(command);
2777 repairGUI();
2779 return;
2780 } else if (command.equals("Move to top")) {
2781 if (null == active) return;
2782 canvas.setUpdateGraphics(true);
2783 layer.getParent().move(LayerSet.TOP, active);
2784 Display.repaint(layer.getParent(), active, 5);
2785 //Display.updatePanelIndex(layer, active);
2786 } else if (command.equals("Move up")) {
2787 if (null == active) return;
2788 canvas.setUpdateGraphics(true);
2789 layer.getParent().move(LayerSet.UP, active);
2790 Display.repaint(layer.getParent(), active, 5);
2791 //Display.updatePanelIndex(layer, active);
2792 } else if (command.equals("Move down")) {
2793 if (null == active) return;
2794 canvas.setUpdateGraphics(true);
2795 layer.getParent().move(LayerSet.DOWN, active);
2796 Display.repaint(layer.getParent(), active, 5);
2797 //Display.updatePanelIndex(layer, active);
2798 } else if (command.equals("Move to bottom")) {
2799 if (null == active) return;
2800 canvas.setUpdateGraphics(true);
2801 layer.getParent().move(LayerSet.BOTTOM, active);
2802 Display.repaint(layer.getParent(), active, 5);
2803 //Display.updatePanelIndex(layer, active);
2804 } else if (command.equals("Duplicate, link and send to next layer")) {
2805 duplicateLinkAndSendTo(active, 1, layer.getParent().next(layer));
2806 } else if (command.equals("Duplicate, link and send to previous layer")) {
2807 duplicateLinkAndSendTo(active, 0, layer.getParent().previous(layer));
2808 } else if (command.equals("Duplicate, link and send to...")) {
2809 // fix non-scrolling popup menu
2810 GenericDialog gd = new GenericDialog("Send to");
2811 gd.addMessage("Duplicate, link and send to...");
2812 String[] sl = new String[layer.getParent().size()];
2813 int next = 0;
2814 for (Iterator it = layer.getParent().getLayers().iterator(); it.hasNext(); ) {
2815 sl[next++] = project.findLayerThing(it.next()).toString();
2817 gd.addChoice("Layer: ", sl, sl[layer.getParent().indexOf(layer)]);
2818 gd.showDialog();
2819 if (gd.wasCanceled()) return;
2820 Layer la = layer.getParent().getLayer(gd.getNextChoiceIndex());
2821 if (layer == la) {
2822 Utils.showMessage("Can't duplicate, link and send to the same layer.");
2823 return;
2825 duplicateLinkAndSendTo(active, 0, la);
2826 } else if (-1 != command.indexOf("z = ")) {
2827 // this is an item from the "Duplicate, link and send to" menu of layer z's
2828 Layer target_layer = layer.getParent().getLayer(Double.parseDouble(command.substring(command.lastIndexOf(' ') +1)));
2829 Utils.log2("layer: __" +command.substring(command.lastIndexOf(' ') +1) + "__");
2830 if (null == target_layer) return;
2831 duplicateLinkAndSendTo(active, 0, target_layer);
2832 } else if (-1 != command.indexOf("z=")) {
2833 // WARNING the indexOf is very similar to the previous one
2834 // Send the linked group to the selected layer
2835 int iz = command.indexOf("z=")+2;
2836 Utils.log2("iz=" + iz + " other: " + command.indexOf(' ', iz+2));
2837 int end = command.indexOf(' ', iz);
2838 if (-1 == end) end = command.length();
2839 double lz = Double.parseDouble(command.substring(iz, end));
2840 Layer target = layer.getParent().getLayer(lz);
2841 HashSet hs = active.getLinkedGroup(new HashSet());
2842 layer.getParent().move(hs, active.getLayer(), target);
2843 } else if (command.equals("Unlink")) {
2844 if (null == active || active instanceof Patch) return;
2845 active.unlink();
2846 updateSelection();//selection.update();
2847 } else if (command.equals("Unlink from images")) {
2848 if (null == active) return;
2849 try {
2850 for (Displayable displ: selection.getSelected()) {
2851 displ.unlinkAll(Patch.class);
2853 updateSelection();//selection.update();
2854 } catch (Exception e) { IJError.print(e); }
2855 } else if (command.equals("Unlink slices")) {
2856 YesNoCancelDialog yn = new YesNoCancelDialog(frame, "Attention", "Really unlink all slices from each other?\nThere is no undo.");
2857 if (!yn.yesPressed()) return;
2858 final ArrayList<Patch> pa = ((Patch)active).getStackPatches();
2859 for (int i=pa.size()-1; i>0; i--) {
2860 pa.get(i).unlink(pa.get(i-1));
2862 } else if (command.equals("Send to next layer")) {
2863 Rectangle box = selection.getBox();
2864 try {
2865 // unlink Patch instances
2866 for (final Displayable displ : selection.getSelected()) {
2867 displ.unlinkAll(Patch.class);
2869 updateSelection();//selection.update();
2870 } catch (Exception e) { IJError.print(e); }
2871 //layer.getParent().moveDown(layer, active); // will repaint whatever appropriate layers
2872 selection.moveDown();
2873 repaint(layer.getParent(), box);
2874 } else if (command.equals("Send to previous layer")) {
2875 Rectangle box = selection.getBox();
2876 try {
2877 // unlink Patch instances
2878 for (final Displayable displ : selection.getSelected()) {
2879 displ.unlinkAll(Patch.class);
2881 updateSelection();//selection.update();
2882 } catch (Exception e) { IJError.print(e); }
2883 //layer.getParent().moveUp(layer, active); // will repaint whatever appropriate layers
2884 selection.moveUp();
2885 repaint(layer.getParent(), box);
2886 } else if (command.equals("Show centered")) {
2887 if (active == null) return;
2888 showCentered(active);
2889 } else if (command.equals("Delete...")) {
2891 if (null != active) {
2892 Displayable d = active;
2893 selection.remove(d);
2894 d.remove(true); // will repaint
2897 // remove all selected objects
2898 selection.deleteAll();
2899 } else if (command.equals("Color...")) {
2900 IJ.doCommand("Color Picker...");
2901 } else if (command.equals("Revert")) {
2902 if (null == active || active.getClass() != Patch.class) return;
2903 Patch p = (Patch)active;
2904 if (!p.revert()) {
2905 if (null == p.getOriginalPath()) Utils.log("No editions to save for patch " + p.getTitle() + " #" + p.getId());
2906 else Utils.log("Could not revert Patch " + p.getTitle() + " #" + p.getId());
2908 } else if (command.equals("Remove alpha mask")) {
2909 final ArrayList<Displayable> patches = selection.getSelected(Patch.class);
2910 if (patches.size() > 0) {
2911 Bureaucrat.createAndStart(new Worker.Task("Removing alpha mask" + (patches.size() > 1 ? "s" : "")) { public void exec() {
2912 final ArrayList<Future> jobs = new ArrayList<Future>();
2913 for (final Displayable d : patches) {
2914 final Patch p = (Patch) d;
2915 p.setAlphaMask(null);
2916 Future job = p.getProject().getLoader().regenerateMipMaps(p); // submit to queue
2917 if (null != job) jobs.add(job);
2919 // join all
2920 for (final Future job : jobs) try {
2921 job.get();
2922 } catch (Exception ie) {}
2923 }}, patches.get(0).getProject());
2925 } else if (command.equals("Undo")) {
2926 Bureaucrat.createAndStart(new Worker.Task("Undo") { public void exec() {
2927 layer.getParent().undoOneStep();
2928 Display.repaint(layer.getParent());
2929 }}, project);
2930 } else if (command.equals("Redo")) {
2931 Bureaucrat.createAndStart(new Worker.Task("Redo") { public void exec() {
2932 layer.getParent().redoOneStep();
2933 Display.repaint(layer.getParent());
2934 }}, project);
2935 } else if (command.equals("Transform")) {
2936 if (null == active) return;
2937 canvas.setTransforming(true);
2938 } else if (command.equals("Apply transform")) {
2939 if (null == active) return;
2940 canvas.setTransforming(false);
2941 } else if (command.equals("Apply transform propagating to last layer")) {
2942 if (selection.isTransforming()) {
2943 final java.util.List<Layer> layers = layer.getParent().getLayers();
2944 selection.applyAndPropagate(new HashSet<Layer>(layers.subList(layers.indexOf(Display.this.layer)+1, layers.size()))); // +1 to exclude current layer
2946 } else if (command.equals("Apply transform propagating to first layer")) {
2947 if (selection.isTransforming()) {
2948 final java.util.List<Layer> layers = layer.getParent().getLayers();
2949 selection.applyAndPropagate(new HashSet<Layer>(layers.subList(0, layers.indexOf(Display.this.layer))));
2951 } else if (command.equals("Cancel transform")) {
2952 if (null == active) return;
2953 canvas.cancelTransform();
2954 } else if (command.equals("Specify transform...")) {
2955 if (null == active) return;
2956 selection.specify();
2957 } else if (command.equals("Hide all but images")) {
2958 ArrayList<Class> type = new ArrayList<Class>();
2959 type.add(Patch.class);
2960 selection.removeAll(layer.getParent().hideExcept(type, false));
2961 Display.update(layer.getParent(), false);
2962 } else if (command.equals("Unhide all")) {
2963 layer.getParent().setAllVisible(false);
2964 Display.update(layer.getParent(), false);
2965 } else if (command.startsWith("Hide all ")) {
2966 String type = command.substring(9, command.length() -1); // skip the ending plural 's'
2967 type = type.substring(0, 1).toUpperCase() + type.substring(1);
2968 selection.removeAll(layer.getParent().setVisible(type, false, true));
2969 } else if (command.startsWith("Unhide all ")) {
2970 String type = command.substring(11, command.length() -1); // skip the ending plural 's'
2971 type = type.substring(0, 1).toUpperCase() + type.substring(1);
2972 layer.getParent().setVisible(type, true, true);
2973 } else if (command.equals("Hide deselected")) {
2974 hideDeselected(0 != (ActionEvent.ALT_MASK & ae.getModifiers()));
2975 } else if (command.equals("Hide deselected except images")) {
2976 hideDeselected(true);
2977 } else if (command.equals("Hide selected")) {
2978 selection.setVisible(false); // TODO should deselect them too? I don't think so.
2979 } else if (command.equals("Resize canvas/LayerSet...")) {
2980 resizeCanvas();
2981 } else if (command.equals("Autoresize canvas/LayerSet")) {
2982 layer.getParent().setMinimumDimensions();
2983 } else if (command.equals("Import image")) {
2984 importImage();
2985 } else if (command.equals("Import next image")) {
2986 importNextImage();
2987 } else if (command.equals("Import stack...")) {
2988 Display.this.getLayerSet().addLayerContentStep(layer);
2989 Rectangle sr = getCanvas().getSrcRect();
2990 Bureaucrat burro = project.getLoader().importStack(layer, sr.x + sr.width/2, sr.y + sr.height/2, null, true, null);
2991 burro.addPostTask(new Runnable() { public void run() {
2992 Display.this.getLayerSet().addLayerContentStep(layer);
2993 }});
2994 } else if (command.equals("Import grid...")) {
2995 Display.this.getLayerSet().addLayerContentStep(layer);
2996 Bureaucrat burro = project.getLoader().importGrid(layer);
2997 burro.addPostTask(new Runnable() { public void run() {
2998 Display.this.getLayerSet().addLayerContentStep(layer);
2999 }});
3000 } else if (command.equals("Import sequence as grid...")) {
3001 Display.this.getLayerSet().addLayerContentStep(layer);
3002 Bureaucrat burro = project.getLoader().importSequenceAsGrid(layer);
3003 burro.addPostTask(new Runnable() { public void run() {
3004 Display.this.getLayerSet().addLayerContentStep(layer);
3005 }});
3006 } else if (command.equals("Import from text file...")) {
3007 Display.this.getLayerSet().addLayerContentStep(layer);
3008 Bureaucrat burro = project.getLoader().importImages(layer);
3009 burro.addPostTask(new Runnable() { public void run() {
3010 Display.this.getLayerSet().addLayerContentStep(layer);
3011 }});
3012 } else if (command.equals("Import labels as arealists...")) {
3013 Display.this.getLayerSet().addChangeTreesStep();
3014 Bureaucrat burro = project.getLoader().importLabelsAsAreaLists(layer, null, Double.MAX_VALUE, 0, 0.4f, false);
3015 burro.addPostTask(new Runnable() { public void run() {
3016 Display.this.getLayerSet().addChangeTreesStep();
3017 }});
3018 } else if (command.equals("Make flat image...")) {
3019 // if there's a ROI, just use that as cropping rectangle
3020 Rectangle srcRect = null;
3021 Roi roi = canvas.getFakeImagePlus().getRoi();
3022 if (null != roi) {
3023 srcRect = roi.getBounds();
3024 } else {
3025 // otherwise, whatever is visible
3026 //srcRect = canvas.getSrcRect();
3027 // The above is confusing. That is what ROIs are for. So paint all:
3028 srcRect = new Rectangle(0, 0, (int)Math.ceil(layer.getParent().getLayerWidth()), (int)Math.ceil(layer.getParent().getLayerHeight()));
3030 double scale = 1.0;
3031 final String[] types = new String[]{"8-bit grayscale", "RGB Color"};
3032 int the_type = ImagePlus.GRAY8;
3033 final GenericDialog gd = new GenericDialog("Choose", frame);
3034 gd.addSlider("Scale: ", 1, 100, 100);
3035 gd.addChoice("Type: ", types, types[0]);
3036 if (layer.getParent().size() > 1) {
3038 String[] layers = new String[layer.getParent().size()];
3039 int i = 0;
3040 for (Iterator it = layer.getParent().getLayers().iterator(); it.hasNext(); ) {
3041 layers[i] = layer.getProject().findLayerThing((Layer)it.next()).toString();
3042 i++;
3044 int i_layer = layer.getParent().indexOf(layer);
3045 gd.addChoice("Start: ", layers, layers[i_layer]);
3046 gd.addChoice("End: ", layers, layers[i_layer]);
3048 Utils.addLayerRangeChoices(Display.this.layer, gd); /// $#%! where are my lisp macros
3049 gd.addCheckbox("Include non-empty layers only", true);
3051 gd.addMessage("Background color:");
3052 Utils.addRGBColorSliders(gd, Color.black);
3053 gd.addCheckbox("Best quality", false);
3054 gd.addMessage("");
3055 gd.addCheckbox("Save to file", false);
3056 gd.addCheckbox("Save for web", false);
3057 gd.showDialog();
3058 if (gd.wasCanceled()) return;
3059 scale = gd.getNextNumber() / 100;
3060 the_type = (0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY8 : ImagePlus.COLOR_RGB);
3061 if (Double.isNaN(scale) || scale <= 0.0) {
3062 Utils.showMessage("Invalid scale.");
3063 return;
3065 Layer[] layer_array = null;
3066 boolean non_empty_only = false;
3067 if (layer.getParent().size() > 1) {
3068 non_empty_only = gd.getNextBoolean();
3069 int i_start = gd.getNextChoiceIndex();
3070 int i_end = gd.getNextChoiceIndex();
3071 ArrayList al = new ArrayList();
3072 ArrayList al_zd = layer.getParent().getZDisplayables();
3073 ZDisplayable[] zd = new ZDisplayable[al_zd.size()];
3074 al_zd.toArray(zd);
3075 for (int i=i_start, j=0; i <= i_end; i++, j++) {
3076 Layer la = layer.getParent().getLayer(i);
3077 if (!la.isEmpty() || !non_empty_only) al.add(la); // checks both the Layer and the ZDisplayable objects in the parent LayerSet
3079 if (0 == al.size()) {
3080 Utils.showMessage("All layers are empty!");
3081 return;
3083 layer_array = new Layer[al.size()];
3084 al.toArray(layer_array);
3085 } else {
3086 layer_array = new Layer[]{Display.this.layer};
3088 final Color background = new Color((int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
3089 final boolean quality = gd.getNextBoolean();
3090 final boolean save_to_file = gd.getNextBoolean();
3091 final boolean save_for_web = gd.getNextBoolean();
3092 // in its own thread
3093 if (save_for_web) project.getLoader().makePrescaledTiles(layer_array, Patch.class, srcRect, scale, c_alphas, the_type);
3094 else project.getLoader().makeFlatImage(layer_array, srcRect, scale, c_alphas, the_type, save_to_file, quality, background);
3096 } else if (command.equals("Lock")) {
3097 selection.setLocked(true);
3098 } else if (command.equals("Unlock")) {
3099 selection.setLocked(false);
3100 } else if (command.equals("Properties...")) {
3101 active.adjustProperties();
3102 updateSelection();
3103 } else if (command.equals("Cancel alignment")) {
3104 layer.getParent().cancelAlign();
3105 } else if (command.equals("Align with landmarks")) {
3106 layer.getParent().applyAlign(false);
3107 } else if (command.equals("Align and register")) {
3108 layer.getParent().applyAlign(true);
3109 } else if (command.equals("Align using profiles")) {
3110 if (!selection.contains(Profile.class)) {
3111 Utils.showMessage("No profiles are selected.");
3112 return;
3114 // ask for range of layers
3115 final GenericDialog gd = new GenericDialog("Choose range");
3116 Utils.addLayerRangeChoices(Display.this.layer, gd);
3117 gd.showDialog();
3118 if (gd.wasCanceled()) return;
3119 Layer la_start = layer.getParent().getLayer(gd.getNextChoiceIndex());
3120 Layer la_end = layer.getParent().getLayer(gd.getNextChoiceIndex());
3121 if (la_start == la_end) {
3122 Utils.showMessage("Need at least two layers.");
3123 return;
3125 if (selection.isLocked()) {
3126 Utils.showMessage("There are locked objects.");
3127 return;
3129 layer.getParent().startAlign(Display.this);
3130 layer.getParent().applyAlign(la_start, la_end, selection);
3131 } else if (command.equals("Align stack slices")) {
3132 if (getActive() instanceof Patch) {
3133 final Patch slice = (Patch)getActive();
3134 if (slice.isStack()) {
3135 // check linked group
3136 final HashSet hs = slice.getLinkedGroup(new HashSet());
3137 for (Iterator it = hs.iterator(); it.hasNext(); ) {
3138 if (it.next().getClass() != Patch.class) {
3139 Utils.showMessage("Images are linked to other objects, can't proceed to cross-correlate them."); // labels should be fine, need to check that
3140 return;
3143 final LayerSet ls = slice.getLayerSet();
3144 final HashSet<Displayable> linked = slice.getLinkedGroup(null);
3145 ls.addTransformStep(linked);
3146 Bureaucrat burro = Registration.registerStackSlices((Patch)getActive()); // will repaint
3147 burro.addPostTask(new Runnable() { public void run() {
3148 // The current state when done
3149 ls.addTransformStep(linked);
3150 }});
3151 } else {
3152 Utils.log("Align stack slices: selected image is not part of a stack.");
3155 } else if (command.equals("Align layers")) {
3156 final Layer la = layer;; // caching, since scroll wheel may change it
3157 la.getParent().addTransformStep(la);
3158 Bureaucrat burro = AlignTask.alignLayersLinearlyTask( la );
3159 burro.addPostTask(new Runnable() { public void run() {
3160 la.getParent().addTransformStep(la);
3161 }});
3162 } else if (command.equals("Align multi-layer mosaic")) {
3163 final Layer la = layer; // caching, since scroll wheel may change it
3164 la.getParent().addTransformStep();
3165 Bureaucrat burro = AlignTask.alignMultiLayerMosaicTask( la );
3166 burro.addPostTask(new Runnable() { public void run() {
3167 la.getParent().addTransformStep();
3168 }});
3169 } else if (command.equals("Properties ...")) { // NOTE the space before the dots, to distinguish from the "Properties..." command that works on Displayable objects.
3170 GenericDialog gd = new GenericDialog("Properties", Display.this.frame);
3171 //gd.addNumericField("layer_scroll_step: ", this.scroll_step, 0);
3172 gd.addSlider("layer_scroll_step: ", 1, layer.getParent().size(), Display.this.scroll_step);
3173 gd.addChoice("snapshots_mode", LayerSet.snapshot_modes, LayerSet.snapshot_modes[layer.getParent().getSnapshotsMode()]);
3174 gd.addCheckbox("prefer_snapshots_quality", layer.getParent().snapshotsQuality());
3175 Loader lo = getProject().getLoader();
3176 boolean using_mipmaps = lo.isMipMapsEnabled();
3177 gd.addCheckbox("enable_mipmaps", using_mipmaps);
3178 String preprocessor = project.getLoader().getPreprocessor();
3179 gd.addStringField("image_preprocessor: ", null == preprocessor ? "" : preprocessor);
3180 gd.addCheckbox("enable_layer_pixels virtualization", layer.getParent().isPixelsVirtualizationEnabled());
3181 double max = layer.getParent().getLayerWidth() < layer.getParent().getLayerHeight() ? layer.getParent().getLayerWidth() : layer.getParent().getLayerHeight();
3182 gd.addSlider("max_dimension of virtualized layer pixels: ", 0, max, layer.getParent().getPixelsMaxDimension());
3183 // --------
3184 gd.showDialog();
3185 if (gd.wasCanceled()) return;
3186 // --------
3187 int sc = (int) gd.getNextNumber();
3188 if (sc < 1) sc = 1;
3189 Display.this.scroll_step = sc;
3190 updateInDatabase("scroll_step");
3192 layer.getParent().setSnapshotsMode(gd.getNextChoiceIndex());
3193 layer.getParent().setSnapshotsQuality(gd.getNextBoolean());
3195 boolean generate_mipmaps = gd.getNextBoolean();
3196 if (using_mipmaps && generate_mipmaps) {
3197 // nothing changed
3198 } else {
3199 if (using_mipmaps) { // and !generate_mipmaps
3200 lo.flushMipMaps(true);
3201 } else {
3202 // not using mipmaps before, and true == generate_mipmaps
3203 lo.generateMipMaps(layer.getParent().getDisplayables(Patch.class));
3207 final String prepro = gd.getNextString();
3208 if (!project.getLoader().setPreprocessor(prepro.trim())) {
3209 Utils.showMessage("Could NOT set the preprocessor to " + prepro);
3212 layer.getParent().setPixelsVirtualizationEnabled(gd.getNextBoolean());
3213 layer.getParent().setPixelsMaxDimension((int)gd.getNextNumber());
3214 } else if (command.equals("Search...")) {
3215 new Search();
3216 } else if (command.equals("Select all")) {
3217 selection.selectAll();
3218 repaint(Display.this.layer, selection.getBox(), 0);
3219 } else if (command.equals("Select none")) {
3220 Rectangle box = selection.getBox();
3221 selection.clear();
3222 repaint(Display.this.layer, box, 0);
3223 } else if (command.equals("Restore selection")) {
3224 selection.restore();
3225 } else if (command.equals("Select under ROI")) {
3226 Roi roi = canvas.getFakeImagePlus().getRoi();
3227 if (null == roi) return;
3228 selection.selectAll(roi, true);
3229 } else if (command.equals("Merge")) {
3230 ArrayList al_sel = selection.getSelected();
3231 // put active at the beginning, to work as the base on which other's will get merged
3232 al_sel.remove(Display.this.active);
3233 al_sel.add(0, Display.this.active);
3234 AreaList ali = AreaList.merge(al_sel);
3235 if (null != ali) {
3236 // remove all but the first from the selection
3237 for (int i=1; i<al_sel.size(); i++) {
3238 Object ob = al_sel.get(i);
3239 if (ob.getClass() == AreaList.class) {
3240 selection.remove((Displayable)ob);
3243 selection.updateTransform(ali);
3244 repaint(ali.getLayerSet(), ali, 0);
3246 } else if (command.equals("Identify...")) {
3247 // for pipes only for now
3248 if (!(active instanceof Pipe)) return;
3249 ini.trakem2.vector.Compare.findSimilar((Pipe)active);
3250 } else if (command.equals("Identify with axes...")) {
3251 if (!(active instanceof Pipe)) return;
3252 if (Project.getProjects().size() < 2) {
3253 Utils.showMessage("You need at least two projects open:\n-A reference project\n-The current project with the pipe to identify");
3254 return;
3256 ini.trakem2.vector.Compare.findSimilarWithAxes((Pipe)active);
3257 } else if (command.equals("View orthoslices")) {
3258 if (!(active instanceof Patch)) return;
3259 Display3D.showOrthoslices(((Patch)active));
3260 } else if (command.equals("View volume")) {
3261 if (!(active instanceof Patch)) return;
3262 Display3D.showVolume(((Patch)active));
3263 } else if (command.equals("Show in 3D")) {
3264 for (Iterator it = selection.getSelected(ZDisplayable.class).iterator(); it.hasNext(); ) {
3265 ZDisplayable zd = (ZDisplayable)it.next();
3266 Display3D.show(zd.getProject().findProjectThing(zd));
3268 // handle profile lists ...
3269 HashSet hs = new HashSet();
3270 for (Iterator it = selection.getSelected(Profile.class).iterator(); it.hasNext(); ) {
3271 Displayable d = (Displayable)it.next();
3272 ProjectThing profile_list = (ProjectThing)d.getProject().findProjectThing(d).getParent();
3273 if (!hs.contains(profile_list)) {
3274 Display3D.show(profile_list);
3275 hs.add(profile_list);
3278 } else if (command.equals("Snap")) {
3279 if (!(active instanceof Patch)) return;
3280 StitchingTEM.snap(getActive(), Display.this);
3281 } else if (command.equals("Blend")) {
3282 HashSet<Patch> patches = new HashSet<Patch>();
3283 for (final Displayable d : selection.getSelected()) {
3284 if (d.getClass() == Patch.class) patches.add((Patch)d);
3286 if (patches.size() > 1) {
3287 GenericDialog gd = new GenericDialog("Blending");
3288 gd.addCheckbox("Respect current alpha mask", true);
3289 gd.showDialog();
3290 if (gd.wasCanceled()) return;
3291 Blending.blend(patches, gd.getNextBoolean());
3292 } else {
3293 IJ.log("Please select more than one overlapping image.");
3295 } else if (command.equals("Montage")) {
3296 if (!(active instanceof Patch)) {
3297 Utils.showMessage("Please select only images.");
3298 return;
3300 final Set<Displayable> affected = new HashSet<Displayable>(selection.getAffected());
3301 for (final Displayable d : affected)
3302 if (d.isLinked()) {
3303 Utils.showMessage( "You cannot montage linked objects." );
3304 return;
3306 // make an undo step!
3307 final LayerSet ls = layer.getParent();
3308 ls.addTransformStep(affected);
3309 Bureaucrat burro = AlignTask.alignSelectionTask( selection );
3310 burro.addPostTask(new Runnable() { public void run() {
3311 ls.addTransformStep(affected);
3312 }});
3313 } else if (command.equals("Lens correction")) {
3314 final Layer la = layer;
3315 la.getParent().addDataEditStep(new HashSet<Displayable>(la.getParent().getDisplayables()));
3316 Bureaucrat burro = DistortionCorrectionTask.correctDistortionFromSelection( selection );
3317 burro.addPostTask(new Runnable() { public void run() {
3318 // no means to know which where modified and from which layers!
3319 la.getParent().addDataEditStep(new HashSet<Displayable>(la.getParent().getDisplayables()));
3320 }});
3321 } else if (command.equals("Link images...")) {
3322 GenericDialog gd = new GenericDialog("Options");
3323 gd.addMessage("Linking images to images (within their own layer only):");
3324 String[] options = {"all images to all images", "each image with any other overlapping image"};
3325 gd.addChoice("Link: ", options, options[1]);
3326 String[] options2 = {"selected images only", "all images in this layer", "all images in all layers"};
3327 gd.addChoice("Apply to: ", options2, options2[0]);
3328 gd.showDialog();
3329 if (gd.wasCanceled()) return;
3330 Layer lay = layer;
3331 final HashSet<Displayable> ds = new HashSet<Displayable>(lay.getParent().getDisplayables());
3332 lay.getParent().addDataEditStep(ds);
3333 boolean overlapping_only = 1 == gd.getNextChoiceIndex();
3334 switch (gd.getNextChoiceIndex()) {
3335 case 0:
3336 Patch.crosslink(selection.getSelected(Patch.class), overlapping_only);
3337 break;
3338 case 1:
3339 Patch.crosslink(lay.getDisplayables(Patch.class), overlapping_only);
3340 break;
3341 case 2:
3342 for (final Layer la : lay.getParent().getLayers()) {
3343 Patch.crosslink(la.getDisplayables(Patch.class), overlapping_only);
3345 break;
3347 lay.getParent().addDataEditStep(ds);
3348 } else if (command.equals("Enhance contrast (selected images)...")) {
3349 final Layer la = layer;
3350 final HashSet<Displayable> ds = new HashSet<Displayable>(la.getParent().getDisplayables());
3351 la.getParent().addDataEditStep(ds);
3352 ArrayList al = selection.getSelected(Patch.class);
3353 Bureaucrat burro = getProject().getLoader().homogenizeContrast(al);
3354 burro.addPostTask(new Runnable() { public void run() {
3355 la.getParent().addDataEditStep(ds);
3356 }});
3357 } else if (command.equals("Enhance contrast layer-wise...")) {
3358 // ask for range of layers
3359 final GenericDialog gd = new GenericDialog("Choose range");
3360 Utils.addLayerRangeChoices(Display.this.layer, gd);
3361 gd.showDialog();
3362 if (gd.wasCanceled()) return;
3363 java.util.List list = layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1); // exclusive end
3364 Layer[] la = new Layer[list.size()];
3365 list.toArray(la);
3366 final HashSet<Displayable> ds = new HashSet<Displayable>();
3367 for (final Layer l : la) ds.addAll(l.getDisplayables(Patch.class));
3368 getLayerSet().addDataEditStep(ds);
3369 Bureaucrat burro = project.getLoader().homogenizeContrast(la);
3370 burro.addPostTask(new Runnable() { public void run() {
3371 getLayerSet().addDataEditStep(ds);
3372 }});
3373 } else if (command.equals("Set Min and Max layer-wise...")) {
3374 Displayable active = getActive();
3375 double min = 0;
3376 double max = 0;
3377 if (null != active && active.getClass() == Patch.class) {
3378 min = ((Patch)active).getMin();
3379 max = ((Patch)active).getMax();
3381 final GenericDialog gd = new GenericDialog("Min and Max");
3382 gd.addMessage("Set min and max to all images in the layer range");
3383 Utils.addLayerRangeChoices(Display.this.layer, gd);
3384 gd.addNumericField("min: ", min, 2);
3385 gd.addNumericField("max: ", max, 2);
3386 gd.showDialog();
3387 if (gd.wasCanceled()) return;
3389 min = gd.getNextNumber();
3390 max = gd.getNextNumber();
3391 ArrayList<Displayable> al = new ArrayList<Displayable>();
3392 for (final Layer la : layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1)) { // exclusive end
3393 al.addAll(la.getDisplayables(Patch.class));
3395 final HashSet<Displayable> ds = new HashSet<Displayable>(al);
3396 getLayerSet().addDataEditStep(ds);
3397 Bureaucrat burro = project.getLoader().setMinAndMax(al, min, max);
3398 burro.addPostTask(new Runnable() { public void run() {
3399 getLayerSet().addDataEditStep(ds);
3400 }});
3401 } else if (command.equals("Set Min and Max (selected images)...")) {
3402 Displayable active = getActive();
3403 double min = 0;
3404 double max = 0;
3405 if (null != active && active.getClass() == Patch.class) {
3406 min = ((Patch)active).getMin();
3407 max = ((Patch)active).getMax();
3409 final GenericDialog gd = new GenericDialog("Min and Max");
3410 gd.addMessage("Set min and max to all selected images");
3411 gd.addNumericField("min: ", min, 2);
3412 gd.addNumericField("max: ", max, 2);
3413 gd.showDialog();
3414 if (gd.wasCanceled()) return;
3416 min = gd.getNextNumber();
3417 max = gd.getNextNumber();
3418 final HashSet<Displayable> ds = new HashSet<Displayable>(selection.getSelected(Patch.class));
3419 getLayerSet().addDataEditStep(ds);
3420 Bureaucrat burro = project.getLoader().setMinAndMax(selection.getSelected(Patch.class), min, max);
3421 burro.addPostTask(new Runnable() { public void run() {
3422 getLayerSet().addDataEditStep(ds);
3423 }});
3424 } else if (command.equals("Duplicate")) {
3425 // only Patch and DLabel, i.e. Layer-only resident objects that don't exist in the Project Tree
3426 final HashSet<Class> accepted = new HashSet<Class>();
3427 accepted.add(Patch.class);
3428 accepted.add(DLabel.class);
3429 final ArrayList<Displayable> originals = new ArrayList<Displayable>();
3430 final ArrayList<Displayable> selected = selection.getSelected();
3431 for (final Displayable d : selected) {
3432 if (accepted.contains(d.getClass())) {
3433 originals.add(d);
3436 if (originals.size() > 0) {
3437 getLayerSet().addChangeTreesStep();
3438 for (final Displayable d : originals) {
3439 d.getLayer().add(d.clone());
3441 getLayerSet().addChangeTreesStep();
3442 } else if (selected.size() > 0) {
3443 Utils.log("Can only duplicate images and text labels.\nDuplicate *other* objects in the Project Tree.\n");
3445 } else if (command.equals("Create subproject")) {
3446 Roi roi = canvas.getFakeImagePlus().getRoi();
3447 if (null == roi) return; // the menu item is not active unless there is a ROI
3448 Layer first, last;
3449 if (1 == layer.getParent().size()) {
3450 first = last = layer;
3451 } else {
3452 GenericDialog gd = new GenericDialog("Choose layer range");
3453 Utils.addLayerRangeChoices(layer, gd);
3454 gd.showDialog();
3455 if (gd.wasCanceled()) return;
3456 first = layer.getParent().getLayer(gd.getNextChoiceIndex());
3457 last = layer.getParent().getLayer(gd.getNextChoiceIndex());
3458 Utils.log2("first, last: " + first + ", " + last);
3460 Project sub = getProject().createSubproject(roi.getBounds(), first, last);
3461 final LayerSet subls = sub.getRootLayerSet();
3462 final Display d = new Display(sub, subls.getLayer(0));
3463 SwingUtilities.invokeLater(new Runnable() { public void run() {
3464 d.canvas.showCentered(new Rectangle(0, 0, (int)subls.getLayerWidth(), (int)subls.getLayerHeight()));
3465 }});
3466 } else if (command.startsWith("Arealists as labels")) {
3467 GenericDialog gd = new GenericDialog("Export labels");
3468 gd.addSlider("Scale: ", 1, 100, 100);
3469 final String[] options = {"All area list", "Selected area lists"};
3470 gd.addChoice("Export: ", options, options[0]);
3471 Utils.addLayerRangeChoices(layer, gd);
3472 gd.addCheckbox("Visible only", true);
3473 gd.showDialog();
3474 if (gd.wasCanceled()) return;
3475 final float scale = (float)(gd.getNextNumber() / 100);
3476 java.util.List al = 0 == gd.getNextChoiceIndex() ? layer.getParent().getZDisplayables(AreaList.class) : selection.getSelected(AreaList.class);
3477 if (null == al) {
3478 Utils.log("No area lists found to export.");
3479 return;
3481 // 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?
3482 al = (java.util.List<Displayable>) al;
3484 int first = gd.getNextChoiceIndex();
3485 int last = gd.getNextChoiceIndex();
3486 boolean visible_only = gd.getNextBoolean();
3487 if (-1 != command.indexOf("(amira)")) {
3488 AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, true, true);
3489 } else if (-1 != command.indexOf("(tif)")) {
3490 AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, false, false);
3492 } else if (command.equals("Project properties...")) {
3493 project.adjustProperties();
3494 } else if (command.equals("Release memory...")) {
3495 Bureaucrat.createAndStart(new Worker("Releasing memory") {
3496 public void run() {
3497 startedWorking();
3498 try {
3499 GenericDialog gd = new GenericDialog("Release Memory");
3500 int max = (int)(IJ.maxMemory() / 1000000);
3501 gd.addSlider("Megabytes: ", 0, max, max/2);
3502 gd.showDialog();
3503 if (!gd.wasCanceled()) {
3504 int n_mb = (int)gd.getNextNumber();
3505 project.getLoader().releaseToFit((long)n_mb*1000000);
3507 } catch (Throwable e) {
3508 IJError.print(e);
3509 } finally {
3510 finishedWorking();
3513 }, project);
3514 } else if (command.equals("Flush image cache")) {
3515 Loader.releaseAllCaches();
3516 } else if (command.equals("Regenerate all mipmaps")) {
3517 for (final Displayable d : getLayerSet().getDisplayables(Patch.class)) {
3518 d.getProject().getLoader().regenerateMipMaps((Patch) d);
3520 } else {
3521 Utils.log2("Display: don't know what to do with command " + command);
3523 }});
3526 /** Update in all displays the Transform for the given Displayable if it's selected. */
3527 static public void updateTransform(final Displayable displ) {
3528 for (final Display d : al_displays) {
3529 if (d.selection.contains(displ)) d.selection.updateTransform(displ);
3533 /** Order the profiles of the parent profile_list by Z order, and fix the ProjectTree.*/
3535 private void fixZOrdering(Profile profile) {
3536 ProjectThing thing = project.findProjectThing(profile);
3537 if (null == thing) {
3538 Utils.log2("Display.fixZOrdering: null thing?");
3539 return;
3541 ((ProjectThing)thing.getParent()).fixZOrdering();
3542 project.getProjectTree().updateList(thing.getParent());
3546 /** The number of layers to scroll through with the wheel; 1 by default.*/
3547 public int getScrollStep() { return this.scroll_step; }
3549 public void setScrollStep(int scroll_step) {
3550 if (scroll_step < 1) scroll_step = 1;
3551 this.scroll_step = scroll_step;
3552 updateInDatabase("scroll_step");
3555 protected Bureaucrat importImage() {
3556 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
3557 public void run() {
3558 startedWorking();
3559 try {
3562 Rectangle srcRect = canvas.getSrcRect();
3563 int x = srcRect.x + srcRect.width / 2;
3564 int y = srcRect.y + srcRect.height/ 2;
3565 Patch p = project.getLoader().importImage(project, x, y);
3566 if (null == p) {
3567 finishedWorking();
3568 Utils.showMessage("Could not open the image.");
3569 return;
3572 Display.this.getLayerSet().addLayerContentStep(layer);
3574 layer.add(p); // will add it to the proper Displays
3576 Display.this.getLayerSet().addLayerContentStep(layer);
3579 } catch (Exception e) {
3580 IJError.print(e);
3582 finishedWorking();
3585 return Bureaucrat.createAndStart(worker, getProject());
3588 protected Bureaucrat importNextImage() {
3589 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
3590 public void run() {
3591 startedWorking();
3592 try {
3594 Rectangle srcRect = canvas.getSrcRect();
3595 int x = srcRect.x + srcRect.width / 2;// - imp.getWidth() / 2;
3596 int y = srcRect.y + srcRect.height/ 2;// - imp.getHeight()/ 2;
3597 Patch p = project.getLoader().importNextImage(project, x, y);
3598 if (null == p) {
3599 Utils.showMessage("Could not open next image.");
3600 finishedWorking();
3601 return;
3604 Display.this.getLayerSet().addLayerContentStep(layer);
3606 layer.add(p); // will add it to the proper Displays
3608 Display.this.getLayerSet().addLayerContentStep(layer);
3610 } catch (Exception e) {
3611 IJError.print(e);
3613 finishedWorking();
3616 return Bureaucrat.createAndStart(worker, getProject());
3620 /** Make the given channel have the given alpha (transparency). */
3621 public void setChannel(int c, float alpha) {
3622 int a = (int)(255 * alpha);
3623 int l = (c_alphas&0xff000000)>>24;
3624 int r = (c_alphas&0xff0000)>>16;
3625 int g = (c_alphas&0xff00)>>8;
3626 int b = c_alphas&0xff;
3627 switch (c) {
3628 case Channel.MONO:
3629 // all to the given alpha
3630 c_alphas = (l<<24) + (r<<16) + (g<<8) + b; // parenthesis are NECESSARY
3631 break;
3632 case Channel.RED:
3633 // modify only the red
3634 c_alphas = (l<<24) + (a<<16) + (g<<8) + b;
3635 break;
3636 case Channel.GREEN:
3637 c_alphas = (l<<24) + (r<<16) + (a<<8) + b;
3638 break;
3639 case Channel.BLUE:
3640 c_alphas = (l<<24) + (r<<16) + (g<<8) + a;
3641 break;
3643 //Utils.log2("c_alphas: " + c_alphas);
3644 //canvas.setUpdateGraphics(true);
3645 canvas.repaint(true);
3646 updateInDatabase("c_alphas");
3649 /** Set the channel as active and the others as inactive. */
3650 public void setActiveChannel(Channel channel) {
3651 for (int i=0; i<4; i++) {
3652 if (channel != channels[i]) channels[i].setActive(false);
3653 else channel.setActive(true);
3655 Utils.updateComponent(panel_channels);
3656 transp_slider.setValue((int)(channel.getAlpha() * 100));
3659 public int getDisplayChannelAlphas() { return c_alphas; }
3661 // rename this method and the getDisplayChannelAlphas ! They sound the same!
3662 public int getChannelAlphas() {
3663 return ((int)(channels[0].getAlpha() * 255)<<24) + ((int)(channels[1].getAlpha() * 255)<<16) + ((int)(channels[2].getAlpha() * 255)<<8) + (int)(channels[3].getAlpha() * 255);
3666 public int getChannelAlphasState() {
3667 return ((channels[0].isSelected() ? 255 : 0)<<24)
3668 + ((channels[1].isSelected() ? 255 : 0)<<16)
3669 + ((channels[2].isSelected() ? 255 : 0)<<8)
3670 + (channels[3].isSelected() ? 255 : 0);
3673 /** Show the layer in the front Display, or in a new Display if the front Display is showing a layer from a different LayerSet. */
3674 static public void showFront(final Layer layer) {
3675 Display display = front;
3676 if (null == display || display.layer.getParent() != layer.getParent()) {
3677 display = new Display(layer.getProject(), layer, null); // gets set to front
3678 } else {
3679 display.setLayer(layer);
3683 /** Show the given Displayable centered and selected. If select is false, the selection is cleared. */
3684 static public void showCentered(Layer layer, Displayable displ, boolean select, boolean shift_down) {
3685 // see if the given layer belongs to the layer set being displayed
3686 Display display = front; // to ensure thread consistency to some extent
3687 if (null == display || display.layer.getParent() != layer.getParent()) {
3688 display = new Display(layer.getProject(), layer, displ); // gets set to front
3689 } else if (display.layer != layer) {
3690 display.setLayer(layer);
3692 if (select) {
3693 if (!shift_down) display.selection.clear();
3694 display.selection.add(displ);
3695 } else {
3696 display.selection.clear();
3698 display.showCentered(displ);
3701 private final void showCentered(final Displayable displ) {
3702 if (null == displ) return;
3703 SwingUtilities.invokeLater(new Runnable() { public void run() {
3704 displ.setVisible(true);
3705 Rectangle box = displ.getBoundingBox();
3706 if (0 == box.width || 0 == box.height) {
3707 box.width = (int)layer.getLayerWidth();
3708 box.height = (int)layer.getLayerHeight();
3710 canvas.showCentered(box);
3711 scrollToShow(displ);
3712 if (displ instanceof ZDisplayable) {
3713 // scroll to first layer that has a point
3714 ZDisplayable zd = (ZDisplayable)displ;
3715 setLayer(zd.getFirstLayer());
3717 }});
3720 /** Listen to interesting updates, such as the ColorPicker and updates to Patch objects. */
3721 public void imageUpdated(ImagePlus updated) {
3722 // detect ColorPicker WARNING this will work even if the Display is not the window immediately active under the color picker.
3723 if (this == front && updated instanceof ij.plugin.ColorPicker) {
3724 if (null != active && project.isInputEnabled()) {
3725 selection.setColor(Toolbar.getForegroundColor());
3726 Display.repaint(front.layer, selection.getBox(), 0);
3728 return;
3730 // $%#@!! LUT changes don't set the image as changed
3731 //if (updated instanceof PatchStack) {
3732 // updated.changes = 1
3735 //Utils.log2("imageUpdated: " + updated + " " + updated.getClass());
3737 /* // never gets called (?)
3738 // the above is overkill. Instead:
3739 if (updated instanceof PatchStack) {
3740 Patch p = ((PatchStack)updated).getCurrentPatch();
3741 ImageProcessor ip = updated.getProcessor();
3742 p.setMinAndMax(ip.getMin(), ip.getMax());
3743 Utils.log2("setting min and max: " + ip.getMin() + ", " + ip.getMax());
3744 project.getLoader().decacheAWT(p.getId()); // including level 0, which will be editable
3745 // on repaint, it will be recreated
3746 //((PatchStack)updated).decacheAll(); // so that it will repaint with a newly created image
3750 // detect LUT changes: DONE at PatchStack, which is the active (virtual) image
3751 //Utils.log2("calling decache for " + updated);
3752 //getProject().getLoader().decache(updated);
3755 public void imageClosed(ImagePlus imp) {}
3756 public void imageOpened(ImagePlus imp) {}
3758 /** Release memory captured by the offscreen images */
3759 static public void flushAll() {
3760 for (final Display d : al_displays) {
3761 d.canvas.flush();
3763 //System.gc();
3764 Thread.yield();
3767 /** Can be null. */
3768 static public Display getFront() {
3769 return front;
3772 static public void setCursorToAll(final Cursor c) {
3773 for (final Display d : al_displays) {
3774 d.frame.setCursor(c);
3778 protected void setCursor(Cursor c) {
3779 frame.setCursor(c);
3782 /** Used by the Displayable to update the visibility checkbox in other Displays. */
3783 static protected void updateVisibilityCheckbox(final Layer layer, final Displayable displ, final Display calling_display) {
3784 //LOCKS ALL //SwingUtilities.invokeLater(new Runnable() { public void run() {
3785 for (final Display d : al_displays) {
3786 if (d == calling_display) continue;
3787 if (d.layer.contains(displ) || (displ instanceof ZDisplayable && d.layer.getParent().contains((ZDisplayable)displ))) {
3788 DisplayablePanel dp = d.ht_panels.get(displ);
3789 if (null != dp) dp.updateVisibilityCheckbox();
3792 //}});
3795 protected boolean isActiveWindow() {
3796 return frame.isActive();
3799 /** Toggle user input; pan and zoom are always enabled though.*/
3800 static public void setReceivesInput(final Project project, final boolean b) {
3801 for (final Display d : al_displays) {
3802 if (d.project == project) d.canvas.setReceivesInput(b);
3806 /** Export the DTD that defines this object. */
3807 static public void exportDTD(StringBuffer sb_header, HashSet hs, String indent) {
3808 if (hs.contains("t2_display")) return; // TODO to avoid collisions the type shoud be in a namespace such as tm2:display
3809 hs.add("t2_display");
3810 sb_header.append(indent).append("<!ELEMENT t2_display EMPTY>\n")
3811 .append(indent).append("<!ATTLIST t2_display id NMTOKEN #REQUIRED>\n")
3812 .append(indent).append("<!ATTLIST t2_display layer_id NMTOKEN #REQUIRED>\n")
3813 .append(indent).append("<!ATTLIST t2_display x NMTOKEN #REQUIRED>\n")
3814 .append(indent).append("<!ATTLIST t2_display y NMTOKEN #REQUIRED>\n")
3815 .append(indent).append("<!ATTLIST t2_display magnification NMTOKEN #REQUIRED>\n")
3816 .append(indent).append("<!ATTLIST t2_display srcrect_x NMTOKEN #REQUIRED>\n")
3817 .append(indent).append("<!ATTLIST t2_display srcrect_y NMTOKEN #REQUIRED>\n")
3818 .append(indent).append("<!ATTLIST t2_display srcrect_width NMTOKEN #REQUIRED>\n")
3819 .append(indent).append("<!ATTLIST t2_display srcrect_height NMTOKEN #REQUIRED>\n")
3820 .append(indent).append("<!ATTLIST t2_display scroll_step NMTOKEN #REQUIRED>\n")
3821 .append(indent).append("<!ATTLIST t2_display c_alphas NMTOKEN #REQUIRED>\n")
3822 .append(indent).append("<!ATTLIST t2_display c_alphas_state NMTOKEN #REQUIRED>\n")
3825 /** Export all displays of the given project as XML entries. */
3826 static public void exportXML(final Project project, final Writer writer, final String indent, final Object any) throws Exception {
3827 final StringBuffer sb_body = new StringBuffer();
3828 final String in = indent + "\t";
3829 for (final Display d : al_displays) {
3830 if (d.project != project) continue;
3831 final Rectangle r = d.frame.getBounds();
3832 final Rectangle srcRect = d.canvas.getSrcRect();
3833 final double magnification = d.canvas.getMagnification();
3834 sb_body.append(indent).append("<t2_display id=\"").append(d.id).append("\"\n")
3835 .append(in).append("layer_id=\"").append(d.layer.getId()).append("\"\n")
3836 .append(in).append("c_alphas=\"").append(d.c_alphas).append("\"\n")
3837 .append(in).append("c_alphas_state=\"").append(d.getChannelAlphasState()).append("\"\n")
3838 .append(in).append("x=\"").append(r.x).append("\"\n")
3839 .append(in).append("y=\"").append(r.y).append("\"\n")
3840 .append(in).append("magnification=\"").append(magnification).append("\"\n")
3841 .append(in).append("srcrect_x=\"").append(srcRect.x).append("\"\n")
3842 .append(in).append("srcrect_y=\"").append(srcRect.y).append("\"\n")
3843 .append(in).append("srcrect_width=\"").append(srcRect.width).append("\"\n")
3844 .append(in).append("srcrect_height=\"").append(srcRect.height).append("\"\n")
3845 .append(in).append("scroll_step=\"").append(d.scroll_step).append("\"\n")
3847 sb_body.append(indent).append("/>\n");
3849 writer.write(sb_body.toString());
3852 static public void toolChanged(final String tool_name) {
3853 Utils.log2("tool name: " + tool_name);
3854 if (!tool_name.equals("ALIGN")) {
3855 for (final Display d : al_displays) {
3856 d.layer.getParent().cancelAlign();
3861 static public void toolChanged(final int tool) {
3862 //Utils.log2("int tool is " + tool);
3863 if (ProjectToolbar.PEN == tool) {
3864 // erase bounding boxes
3865 for (final Display d : al_displays) {
3866 if (null != d.active) d.repaint(d.layer, d.selection.getBox(), 2);
3869 if (null != front) {
3870 WindowManager.setTempCurrentImage(front.canvas.getFakeImagePlus());
3874 public Selection getSelection() {
3875 return selection;
3878 public boolean isSelected(Displayable d) {
3879 return selection.contains(d);
3882 static public void updateSelection() {
3883 Display.updateSelection(null);
3885 static public void updateSelection(final Display calling) {
3886 final HashSet hs = new HashSet();
3887 for (final Display d : al_displays) {
3888 if (hs.contains(d.layer)) continue;
3889 hs.add(d.layer);
3890 if (null == d || null == d.selection) {
3891 Utils.log2("d is : "+ d + " d.selection is " + d.selection);
3892 } else {
3893 d.selection.update(); // recomputes box
3895 if (d != calling) { // TODO this is so dirty!
3896 if (d.selection.getNLinked() > 1) d.canvas.setUpdateGraphics(true); // this is overkill anyway
3897 d.canvas.repaint(d.selection.getLinkedBox(), Selection.PADDING);
3898 d.navigator.repaint(true); // everything
3903 static public void clearSelection(final Layer layer) {
3904 for (final Display d : al_displays) {
3905 if (d.layer == layer) d.selection.clear();
3908 static public void clearSelection() {
3909 for (final Display d : al_displays) {
3910 d.selection.clear();
3914 private void setTempCurrentImage() {
3915 WindowManager.setTempCurrentImage(canvas.getFakeImagePlus());
3918 /** Check if any display will paint the given Displayable at the given magnification. */
3919 static public boolean willPaint(final Displayable displ, final double magnification) {
3920 Rectangle box = null; ;
3921 for (final Display d : al_displays) {
3922 /* // Can no longer do this check, because 'magnification' is now affected by the Displayable AffineTransform! And thus it would not paint after the prePaint.
3923 if (Math.abs(d.canvas.getMagnification() - magnification) > 0.00000001) {
3924 continue;
3927 if (null == box) box = displ.getBoundingBox(null);
3928 if (d.canvas.getSrcRect().intersects(box)) {
3929 return true;
3932 return false;
3935 public void hideDeselected(final boolean not_images) {
3936 // hide deselected
3937 final ArrayList all = layer.getParent().getZDisplayables(); // a copy
3938 all.addAll(layer.getDisplayables());
3939 all.removeAll(selection.getSelected());
3940 if (not_images) all.removeAll(layer.getDisplayables(Patch.class));
3941 for (final Displayable d : (ArrayList<Displayable>)all) {
3942 if (d.isVisible()) d.setVisible(false);
3944 Display.update(layer);
3947 /** Cleanup internal lists that may contain the given Displayable. */
3948 static public void flush(final Displayable displ) {
3949 for (final Display d : al_displays) {
3950 d.selection.removeFromPrev(displ);
3954 public void resizeCanvas() {
3955 GenericDialog gd = new GenericDialog("Resize LayerSet");
3956 gd.addNumericField("new width: ", layer.getLayerWidth(), 3);
3957 gd.addNumericField("new height: ",layer.getLayerHeight(),3);
3958 gd.addChoice("Anchor: ", LayerSet.ANCHORS, LayerSet.ANCHORS[7]);
3959 gd.showDialog();
3960 if (gd.wasCanceled()) return;
3961 double new_width = gd.getNextNumber();
3962 double new_height =gd.getNextNumber();
3963 layer.getParent().setDimensions(new_width, new_height, gd.getNextChoiceIndex()); // will complain and prevent cropping existing Displayable objects
3967 // To record layer changes -- but it's annoying, this is visualization not data.
3968 static class DoSetLayer implements DoStep {
3969 final Display display;
3970 final Layer layer;
3971 DoSetLayer(final Display display) {
3972 this.display = display;
3973 this.layer = display.layer;
3975 public Displayable getD() { return null; }
3976 public boolean isEmpty() { return false; }
3977 public boolean apply(final int action) {
3978 display.setLayer(layer);
3980 public boolean isIdenticalTo(final Object ob) {
3981 if (!ob instanceof DoSetLayer) return false;
3982 final DoSetLayer dsl = (DoSetLayer) ob;
3983 return dsl.display == this.display && dsl.layer == this.layer;
3988 protected void duplicateLinkAndSendTo(final Displayable active, final int position, final Layer other_layer) {
3989 if (null == active || !(active instanceof Profile)) return;
3990 if (active.getLayer() == other_layer) return; // can't do that!
3991 Profile profile = project.getProjectTree().duplicateChild((Profile)active, position, other_layer);
3992 if (null == profile) return;
3993 active.link(profile);
3994 slt.setAndWait(other_layer);
3995 other_layer.add(profile);
3996 selection.add(profile);
3999 private final HashMap<Color,Layer> layer_channels = new HashMap<Color,Layer>();
4000 private final TreeMap<Integer,LayerPanel> layer_alpha = new TreeMap<Integer,LayerPanel>();
4002 /** Remove all red/blue coloring of layers, and repaint canvas. */
4003 protected void resetLayerColors() {
4004 synchronized (layer_channels) {
4005 for (final Layer l : new ArrayList<Layer>(layer_channels.values())) { // avoid concurrent modification exception
4006 final LayerPanel lp = layer_panels.get(l);
4007 lp.setColor(Color.white);
4008 setColorChannel(lp.layer, Color.white);
4009 lp.slider.setEnabled(true);
4011 layer_channels.clear();
4013 canvas.repaint();
4016 /** Set all layer alphas to zero, and repaint canvas. */
4017 protected void resetLayerAlphas() {
4018 synchronized (layer_channels) {
4019 for (final LayerPanel lp : new ArrayList<LayerPanel>(layer_alpha.values())) {
4020 lp.setAlpha(0);
4022 layer_alpha.clear(); // should have already been cleared
4024 canvas.repaint();
4027 /** Add to layer_alpha table, or remove if alpha is zero. */
4028 protected void storeLayerAlpha(final LayerPanel lp, final float a) {
4029 synchronized (layer_channels) {
4030 if (M.equals(0, a)) {
4031 layer_alpha.remove(lp.layer.getParent().indexOf(lp.layer));
4032 } else {
4033 layer_alpha.put(lp.layer.getParent().indexOf(lp.layer), lp);
4038 static protected final int REPAINT_SINGLE_LAYER = 0;
4039 static protected final int REPAINT_MULTI_LAYER = 1;
4040 static protected final int REPAINT_RGB_LAYER = 2;
4042 /** Sets the values atomically, returns the painting mode. */
4043 protected int getPaintMode(final HashMap<Color,Layer> hm, final ArrayList<LayerPanel> list) {
4044 synchronized (layer_channels) {
4045 if (layer_channels.size() > 0) {
4046 hm.putAll(layer_channels);
4047 hm.put(Color.green, this.layer);
4048 return REPAINT_RGB_LAYER;
4050 list.addAll(layer_alpha.values());
4051 final int len = list.size();
4052 if (len > 1) return REPAINT_MULTI_LAYER;
4053 if (1 == len) {
4054 if (list.get(0).layer == this.layer) return REPAINT_SINGLE_LAYER; // normal mode
4055 return REPAINT_MULTI_LAYER;
4057 return REPAINT_SINGLE_LAYER;
4061 /** Set a layer to be painted as a specific color channel in the canvas.
4062 * Only Color.red and Color.blue are accepted.
4063 * Color.green is reserved for the current layer. */
4064 protected void setColorChannel(final Layer layer, final Color color) {
4065 synchronized (layer_channels) {
4066 if (Color.white == color) {
4067 // Remove
4068 for (final Iterator<Layer> it = layer_channels.values().iterator(); it.hasNext(); ) {
4069 if (it.next() == layer) {
4070 it.remove();
4071 break;
4074 canvas.repaint();
4075 } else if (Color.red == color || Color.blue == color) {
4076 // Reset current of that color, if any, to white
4077 final Layer l = layer_channels.remove(color);
4078 if (null != l) layer_panels.get(l).setColor(Color.white);
4079 // Replace or set new
4080 layer_channels.put(color, layer);
4081 tabs.repaint();
4082 canvas.repaint();
4083 } else {
4084 Utils.log2("Trying to set unacceptable color for layer " + layer + " : " + color);
4086 // enable/disable sliders
4087 final boolean b = 0 == layer_channels.size();
4088 for (final LayerPanel lp : layer_panels.values()) lp.slider.setEnabled(b);
4090 this.canvas.repaint(true);
4093 static public final void updateComponentTreeUI() {
4094 try {
4095 for (final Display d : al_displays) SwingUtilities.updateComponentTreeUI(d.frame);
4096 } catch (Exception e) {
4097 IJError.print(e);