Fixed improper layer pointer for ZDisplayable objects when recreating a
[trakem2.git] / ini / trakem2 / display / Display.java
blob3f37ebbbd7786eb1f2cbdb82844f07df4909cd6f
1 /**
3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2005-2009 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.updateFrameTitle(layer);
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.updateFrameTitle(d.layer);
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 updateFrameTitle(layer);
781 // Set the FakeImagePlus as the current image
782 setTempCurrentImage();
784 // create a drag and drop listener
785 dnd = new DNDInsertImage(this);
787 // start a repainting thread
788 if (null != props) {
789 canvas.repaint(true); // repaint() is unreliable
792 // Set the minimum size of the tabbed pane on the left, so it can be completely collapsed now that it has been properly displayed. This is a patch to the lack of respect for the setDividerLocation method.
793 SwingUtilities.invokeLater(new Runnable() {
794 public void run() {
795 tabs.setMinimumSize(new Dimension(0, 100));
796 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
797 ControlWindow.setLookAndFeel();
802 private JPanel makeTabPanel() {
803 JPanel panel = new JPanel();
804 BoxLayout layout = new BoxLayout(panel, BoxLayout.Y_AXIS);
805 panel.setLayout(layout);
806 return panel;
809 private JScrollPane makeScrollPane(Component c) {
810 JScrollPane jsp = new JScrollPane(c);
811 jsp.setBackground(Color.white); // no effect
812 jsp.getViewport().setBackground(Color.white); // no effect
813 // adjust scrolling to use one DisplayablePanel as the minimal unit
814 jsp.getVerticalScrollBar().setBlockIncrement(DisplayablePanel.HEIGHT); // clicking within the track
815 jsp.getVerticalScrollBar().setUnitIncrement(DisplayablePanel.HEIGHT); // clicking on an arrow
816 jsp.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
817 jsp.setPreferredSize(new Dimension(250, 300));
818 jsp.setMinimumSize(new Dimension(250, 300));
819 return jsp;
822 static protected int scrollbar_width = 0;
824 public JPanel getCanvasPanel() {
825 return canvas_panel;
828 public DisplayCanvas getCanvas() {
829 return canvas;
832 public synchronized void setLayer(final Layer layer) {
833 if (null == layer || layer == this.layer) return;
834 translateLayerColors(this.layer, layer);
835 if (tabs.getSelectedComponent() == scroll_layers) {
836 SwingUtilities.invokeLater(new Runnable() { public void run() {
837 scrollToShow(scroll_layers, layer_panels.get(layer));
838 }});
840 final boolean set_zdispl = null == Display.this.layer || layer.getParent() != Display.this.layer.getParent();
841 if (selection.isTransforming()) {
842 Utils.log("Can't browse layers while transforming.\nCANCEL the transform first with the ESCAPE key or right-click -> cancel.");
843 scroller.setValue(Display.this.layer.getParent().getLayerIndex(Display.this.layer.getId()));
844 return;
846 this.layer = layer;
847 scroller.setValue(layer.getParent().getLayerIndex(layer.getId()));
849 // update the current Layer pointer in ZDisplayable objects
850 for (Iterator it = layer.getParent().getZDisplayables().iterator(); it.hasNext(); ) {
851 ((ZDisplayable)it.next()).setLayer(layer); // the active layer
854 updateVisibleTab(set_zdispl);
856 // see if a lot has to be reloaded, put the relevant ones at the end
857 project.getLoader().prepare(layer);
858 updateFrameTitle(layer); // to show the new 'z'
859 // select the Layer in the LayerTree
860 project.select(Display.this.layer); // does so in a separate thread
861 // update active Displayable:
863 // deselect all except ZDisplayables
864 final ArrayList<Displayable> sel = selection.getSelected();
865 final Displayable last_active = Display.this.active;
866 int sel_next = -1;
867 for (final Iterator<Displayable> it = sel.iterator(); it.hasNext(); ) {
868 final Displayable d = it.next();
869 if (!(d instanceof ZDisplayable)) {
870 it.remove();
871 selection.remove(d);
872 if (d == last_active && sel.size() > 0) {
873 // select the last one of the remaining, if any
874 sel_next = sel.size()-1;
878 if (-1 != sel_next && sel.size() > 0) select(sel.get(sel_next), true);
880 // Keep Profile chain selected, for best ease of use:
881 if (null != last_active && last_active.getClass() == Profile.class && last_active.isLinked(Profile.class)) {
882 Utils.log2("last active was a profile: " + last_active);
883 Displayable other = null;
884 for (final Displayable prof : last_active.getLinked(Profile.class)) {
885 if (prof.getLayer() == layer) {
886 other = prof;
887 break;
890 if (null != other) selection.add(other);
893 // repaint everything
894 navigator.repaint(true);
895 canvas.repaint(true);
897 // repaint tabs (hard as hell)
898 Utils.updateComponent(tabs);
899 // @#$%^! The above works half the times, so explicit repaint as well:
900 Component c = tabs.getSelectedComponent();
901 if (null == c) {
902 c = scroll_patches;
903 tabs.setSelectedComponent(scroll_patches);
905 Utils.updateComponent(c);
907 project.getLoader().setMassiveMode(false); // resetting if it was set true
909 // update the coloring in the ProjectTree
910 project.getProjectTree().updateUILater();
912 setTempCurrentImage();
915 static public void updateVisibleTabs() {
916 for (final Display d : al_displays) {
917 d.updateVisibleTab(true);
921 /** Recreate the tab that is being shown. */
922 public void updateVisibleTab(boolean set_zdispl) {
923 // update only the visible tab
924 switch (tabs.getSelectedIndex()) {
925 case 0:
926 ht_panels.clear();
927 updateTab(panel_patches, "Patches", layer.getDisplayables(Patch.class));
928 break;
929 case 1:
930 ht_panels.clear();
931 updateTab(panel_profiles, "Profiles", layer.getDisplayables(Profile.class));
932 break;
933 case 2:
934 if (set_zdispl) {
935 ht_panels.clear();
936 updateTab(panel_zdispl, "Z-space objects", layer.getParent().getZDisplayables());
938 break;
939 // case 3: channel opacities
940 case 4:
941 ht_panels.clear();
942 updateTab(panel_labels, "Labels", layer.getDisplayables(DLabel.class));
943 break;
944 // case 5: layer panels
949 private void setLayerLater(final Layer layer, final Displayable active) {
950 if (null == layer) return;
951 this.layer = layer;
952 if (!ControlWindow.isGUIEnabled()) return;
953 SwingUtilities.invokeLater(new Runnable() { public void run() {
954 // empty the tabs, except channels and pipes
955 clearTab(panel_profiles, "Profiles");
956 clearTab(panel_patches, "Patches");
957 clearTab(panel_labels, "Labels");
958 // distribute Displayable to the tabs. Ignore LayerSet instances.
959 if (null == ht_panels) ht_panels = new Hashtable<Displayable,DisplayablePanel>();
960 else ht_panels.clear();
961 for (final Displayable d : layer.getDisplayables()) {
962 add(d, false, false);
964 for (final Displayable d : layer.getParent().getZDisplayables()) {
965 d.setLayer(layer);
966 add(d, false, false);
968 navigator.repaint(true); // was not done when adding
969 Utils.updateComponent(tabs.getSelectedComponent());
971 setActive(active);
972 }});
973 // swing issues:
975 new Thread() {
976 public void run() {
977 setPriority(Thread.NORM_PRIORITY);
978 try { Thread.sleep(1000); } catch (Exception e) {}
979 setActive(active);
981 }.start();
985 /** Remove all components from the tab and add a "No [label]" label to each. */
986 private void clearTab(final Container c, final String label) {
987 c.removeAll();
988 c.add(new JLabel("No " + label + "."));
989 // magic cocktail:
990 if (tabs.getSelectedComponent() == c) {
991 Utils.updateComponent(c);
995 /** A class to listen to the transparency_slider of the DisplayablesSelectorWindow. */
996 private class TransparencySliderListener extends MouseAdapter implements ChangeListener {
998 public void stateChanged(ChangeEvent ce) {
999 //change the transparency value of the current active displayable
1000 float new_value = (float)((JSlider)ce.getSource()).getValue();
1001 setTransparency(new_value / 100.0f);
1004 public void mousePressed(MouseEvent me) {
1005 JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
1006 if (scroll != scroll_channels && !selection.isEmpty()) selection.addDataEditStep(new String[]{"alpha"});
1009 public void mouseReleased(MouseEvent me) {
1010 // update navigator window
1011 navigator.repaint(true);
1012 JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
1013 if (scroll != scroll_channels && !selection.isEmpty()) selection.addDataEditStep(new String[]{"alpha"});
1017 /** Context-sensitive: to a Displayable, or to a channel. */
1018 private void setTransparency(final float value) {
1019 JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
1020 if (scroll == scroll_channels) {
1021 for (int i=0; i<4; i++) {
1022 if (channels[i].getBackground() == Color.cyan) {
1023 channels[i].setAlpha(value); // will call back and repaint the Display
1024 return;
1027 } else if (null != active) {
1028 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.
1029 canvas.invalidateVolatile();
1030 selection.setAlpha(value);
1035 public void setTransparencySlider(final float transp) {
1036 if (transp >= 0.0f && transp <= 1.0f) {
1037 // fire event
1038 transp_slider.setValue((int)(transp * 100));
1042 /** Mark the canvas for updating the offscreen images if the given Displayable is NOT the active. */ // Used by the Displayable.setVisible for example.
1043 static public void setUpdateGraphics(final Layer layer, final Displayable displ) {
1044 for (final Display d : al_displays) {
1045 if (layer == d.layer && null != d.active && d.active != displ) {
1046 d.canvas.setUpdateGraphics(true);
1051 /** Flag the DisplayCanvas of Displays showing the given Layer to update their offscreen images.*/
1052 static public void setUpdateGraphics(final Layer layer, final boolean update) {
1053 for (final Display d : al_displays) {
1054 if (layer == d.layer) {
1055 d.canvas.setUpdateGraphics(update);
1060 /** Whether to update the offscreen images or not. */
1061 public void setUpdateGraphics(boolean b) {
1062 canvas.setUpdateGraphics(b);
1065 /** Find all Display instances that contain the layer and repaint them, in the Swing GUI thread. */
1066 static public void update(final Layer layer) {
1067 if (null == layer) return;
1068 SwingUtilities.invokeLater(new Runnable() { public void run() {
1069 for (final Display d : al_displays) {
1070 if (d.isShowing(layer)) {
1071 d.repaintAll();
1074 }});
1077 static public void update(final LayerSet set) {
1078 update(set, true);
1081 /** 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. */
1082 static public void update(final LayerSet set, final boolean update_canvas_dimensions) {
1083 if (null == set) return;
1084 SwingUtilities.invokeLater(new Runnable() { public void run() {
1085 for (final Display d : al_displays) {
1086 if (set.contains(d.layer)) {
1087 d.updateSnapshots();
1088 if (update_canvas_dimensions) d.canvas.setDimensions(set.getLayerWidth(), set.getLayerHeight());
1089 d.repaintAll();
1092 }});
1095 /** Release all resources held by this Display and close the frame. */
1096 protected void destroy() {
1097 dispatcher.quit();
1098 canvas.setReceivesInput(false);
1099 slt.quit();
1101 // update the coloring in the ProjectTree and LayerTree
1102 if (!project.isBeingDestroyed()) {
1103 try {
1104 project.getProjectTree().updateUILater();
1105 project.getLayerTree().updateUILater();
1106 } catch (Exception e) {
1107 Utils.log2("updateUI failed at Display.destroy()");
1111 frame.removeComponentListener(component_listener);
1112 frame.removeWindowListener(window_listener);
1113 frame.removeWindowFocusListener(window_listener);
1114 frame.removeWindowStateListener(window_listener);
1115 frame.removeKeyListener(canvas);
1116 frame.removeMouseListener(frame_mouse_listener);
1117 canvas_panel.removeKeyListener(canvas);
1118 canvas.removeKeyListener(canvas);
1119 tabs.removeChangeListener(tabs_listener);
1120 tabs.removeKeyListener(canvas);
1121 ImagePlus.removeImageListener(this);
1122 bytypelistener = null;
1123 canvas.destroy();
1124 navigator.destroy();
1125 scroller.removeAdjustmentListener(scroller_listener);
1126 frame.setVisible(false);
1127 //no need, and throws exception//frame.dispose();
1128 active = null;
1129 if (null != selection) selection.clear();
1130 //Utils.log2("destroying selection");
1132 // below, need for SetLayerThread threads to quit
1133 slt.quit();
1134 // set a new front if any
1135 if (null == front && al_displays.size() > 0) {
1136 front = (Display)al_displays.get(al_displays.size() -1);
1138 // repaint layer tree (to update the label color)
1139 try {
1140 project.getLayerTree().updateUILater(); // works only after setting the front above
1141 } catch (Exception e) {} // ignore swing sync bullshit when closing everything too fast
1142 // remove the drag and drop listener
1143 dnd.destroy();
1146 /** Find all Display instances that contain a Layer of the given project and close them without removing the Display entries from the database. */
1147 static synchronized public void close(final Project project) {
1148 /* // concurrent modifications if more than 1 Display are being removed asynchronously
1149 for (final Display d : al_displays) {
1150 if (d.getLayer().getProject().equals(project)) {
1151 it.remove();
1152 d.destroy();
1156 Display[] d = new Display[al_displays.size()];
1157 al_displays.toArray(d);
1158 for (int i=0; i<d.length; i++) {
1159 if (d[i].getProject() == project) {
1160 al_displays.remove(d[i]);
1161 d[i].destroy();
1166 /** Find all Display instances that contain the layer and close them and remove the Display from the database. */
1167 static public void close(final Layer layer) {
1168 for (Iterator it = al_displays.iterator(); it.hasNext(); ) {
1169 Display d = (Display)it.next();
1170 if (d.isShowing(layer)) {
1171 d.remove(false);
1172 it.remove();
1177 /** Find all Display instances that are showing the layer and either move to the next or previous layer, or close it if none. */
1178 static public void remove(final Layer layer) {
1179 for (Iterator<Display> it = al_displays.iterator(); it.hasNext(); ) {
1180 final Display d = it.next();
1181 if (d.isShowing(layer)) {
1182 Layer la = layer.getParent().next(layer);
1183 if (layer == la || null == la) la = layer.getParent().previous(layer);
1184 if (null == la || layer == la) {
1185 d.remove(false);
1186 it.remove();
1187 } else {
1188 d.slt.set(la);
1194 public boolean remove(boolean check) {
1195 if (check) {
1196 if (!Utils.check("Delete the Display ?")) return false;
1198 // flush the offscreen images and close the frame
1199 destroy();
1200 removeFromDatabase();
1201 return true;
1204 public Layer getLayer() {
1205 return layer;
1208 public LayerSet getLayerSet() {
1209 return layer.getParent();
1212 public boolean isShowing(final Layer layer) {
1213 return this.layer == layer;
1216 public DisplayNavigator getNavigator() {
1217 return navigator;
1220 /** Repaint both the canvas and the navigator, updating the graphics, and the title and tabs. */
1221 public void repaintAll() {
1222 if (repaint_disabled) return;
1223 navigator.repaint(true);
1224 canvas.repaint(true);
1225 Utils.updateComponent(tabs);
1226 updateFrameTitle();
1229 /** Repaint the canvas updating graphics, the navigator without updating graphics, and the title. */
1230 public void repaintAll2() {
1231 if (repaint_disabled) return;
1232 navigator.repaint(false);
1233 canvas.repaint(true);
1234 updateFrameTitle();
1237 static public void repaintSnapshots(final LayerSet set) {
1238 if (repaint_disabled) return;
1239 for (final Display d : al_displays) {
1240 if (d.getLayer().getParent() == set) {
1241 d.navigator.repaint(true);
1242 Utils.updateComponent(d.tabs);
1246 static public void repaintSnapshots(final Layer layer) {
1247 if (repaint_disabled) return;
1248 for (final Display d : al_displays) {
1249 if (d.getLayer() == layer) {
1250 d.navigator.repaint(true);
1251 Utils.updateComponent(d.tabs);
1256 public void pack() {
1257 dispatcher.exec(new Runnable() { public void run() {
1258 try {
1259 Thread.currentThread().sleep(100);
1260 SwingUtilities.invokeAndWait(new Runnable() { public void run() {
1261 frame.pack();
1262 }});
1263 } catch (Exception e) { IJError.print(e); }
1264 }});
1267 static public void pack(final LayerSet ls) {
1268 for (final Display d : al_displays) {
1269 if (d.layer.getParent() == ls) d.pack();
1273 private void adjustCanvas() {
1274 SwingUtilities.invokeLater(new Runnable() { public void run() {
1275 Rectangle r = split.getRightComponent().getBounds();
1276 canvas.setDrawingSize(r.width, r.height, true);
1277 // fix not-on-top-left problem
1278 canvas.setLocation(0, 0);
1279 //frame.pack(); // don't! Would go into an infinite loop
1280 canvas.repaint(true);
1281 updateInDatabase("srcRect");
1282 }});
1285 /** Grab the last selected display (or create an new one if none) and show in it the layer,centered on the Displayable object. */
1286 static public void setFront(final Layer layer, final Displayable displ) {
1287 if (null == front) {
1288 Display display = new Display(layer.getProject(), layer); // gets set to front
1289 display.showCentered(displ);
1290 } else if (layer == front.layer) {
1291 front.showCentered(displ);
1292 } else {
1293 // find one:
1294 for (final Display d : al_displays) {
1295 if (d.layer == layer) {
1296 d.frame.toFront();
1297 d.showCentered(displ);
1298 return;
1301 // else, open new one
1302 new Display(layer.getProject(), layer).showCentered(displ);
1306 /** 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. */
1307 static public void add(final Layer layer, final Displayable displ, final boolean activate) {
1308 for (final Display d : al_displays) {
1309 if (d.layer == layer) {
1310 if (front == d) {
1311 d.add(displ, activate, true);
1312 //front.frame.toFront();
1313 } else {
1314 d.add(displ, false, true);
1320 static public void add(final Layer layer, final Displayable displ) {
1321 add(layer, displ, true);
1324 /** Add the ZDisplayable to all Displays that show a Layer belonging to the given LayerSet. */
1325 static public void add(final LayerSet set, final ZDisplayable zdispl) {
1326 for (final Display d : al_displays) {
1327 if (set.contains(d.layer)) {
1328 if (front == d) {
1329 zdispl.setLayer(d.layer); // the active one
1330 d.add(zdispl, true, true); // calling add(Displayable, boolean, boolean)
1331 //front.frame.toFront();
1332 } else {
1333 d.add(zdispl, false, true);
1339 static public void addAll(final Layer layer, final Collection<? extends Displayable> coll) {
1340 for (final Display d : al_displays) {
1341 if (d.layer == layer) {
1342 d.addAll(coll);
1347 static public void addAll(final LayerSet set, final Collection<? extends ZDisplayable> coll) {
1348 for (final Display d : al_displays) {
1349 if (set.contains(d.layer)) {
1350 for (final ZDisplayable zd : coll) {
1351 if (front == d) zd.setLayer(d.layer);
1353 d.addAll(coll);
1358 private final void addAll(final Collection<? extends Displayable> coll) {
1359 for (final Displayable d : coll) {
1360 add(d, false, false);
1362 selection.clear();
1363 Utils.updateComponent(tabs);
1364 navigator.repaint(true);
1367 // TODO this very old method could take some improvement:
1368 // - there is no need to create a new DisplayablePanel if its panel is not shown
1369 // - other issues; the method looks overly "if a dog barks and a duck quacks during a lunar eclipse then .."
1370 /** Add it to the proper panel, at the top, and set it active. */
1371 private final void add(final Displayable d, final boolean activate, final boolean repaint_snapshot) {
1372 DisplayablePanel dp = ht_panels.get(d);
1373 if (null != dp && activate) { // for ZDisplayable objects (TODO I think this is not used anymore)
1374 dp.setActive(true);
1375 //setActive(d);
1376 selection.clear();
1377 selection.add(d);
1378 return;
1380 // add to the proper list
1381 JPanel p = null;
1382 if (d instanceof Profile) {
1383 p = panel_profiles;
1384 } else if (d instanceof Patch) {
1385 p = panel_patches;
1386 } else if (d instanceof DLabel) {
1387 p = panel_labels;
1388 } else if (d instanceof ZDisplayable) { //both pipes and balls and AreaList
1389 p = panel_zdispl;
1390 } else {
1391 // LayerSet objects
1392 return;
1394 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
1395 addToPanel(p, 0, dp, activate);
1396 ht_panels.put(d, dp);
1397 if (activate) {
1398 dp.setActive(true);
1399 //setActive(d);
1400 selection.clear();
1401 selection.add(d);
1403 if (repaint_snapshot) navigator.repaint(true);
1406 private void addToPanel(JPanel panel, int index, DisplayablePanel dp, boolean repaint) {
1407 // remove the label
1408 if (1 == panel.getComponentCount() && panel.getComponent(0) instanceof JLabel) {
1409 panel.removeAll();
1411 panel.add(dp, index);
1412 if (repaint) {
1413 Utils.updateComponent(tabs);
1417 /** Find the displays that show the given Layer, and remove the given Displayable from the GUI. */
1418 static public void remove(final Layer layer, final Displayable displ) {
1419 for (final Display d : al_displays) {
1420 if (layer == d.layer) d.remove(displ);
1424 private void remove(final Displayable displ) {
1425 DisplayablePanel ob = ht_panels.remove(displ);
1426 if (null != ob) {
1427 final JScrollPane jsp = ht_tabs.get(displ.getClass());
1428 if (null != jsp) {
1429 JPanel p = (JPanel)jsp.getViewport().getView();
1430 p.remove((Component)ob);
1431 Utils.revalidateComponent(p);
1434 if (null == active || !selection.contains(displ)) {
1435 canvas.setUpdateGraphics(true);
1437 canvas.invalidateVolatile(); // removing active, no need to update offscreen but yes the volatile
1438 repaint(displ, null, 5, true, false);
1439 // from Selection.deleteAll this method is called ... but it's ok: same thread, no locking problems.
1440 selection.remove(displ);
1443 static public void remove(final ZDisplayable zdispl) {
1444 for (final Display d : al_displays) {
1445 if (zdispl.getLayerSet() == d.layer.getParent()) {
1446 d.remove((Displayable)zdispl);
1451 static public void repaint(final Layer layer, final Displayable displ, final int extra) {
1452 repaint(layer, displ, displ.getBoundingBox(), extra);
1455 static public void repaint(final Layer layer, final Displayable displ, final Rectangle r, final int extra) {
1456 repaint(layer, displ, r, extra, true);
1459 /** Find the displays that show the given Layer, and repaint the given Displayable. */
1460 static public void repaint(final Layer layer, final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator) {
1461 if (repaint_disabled) return;
1462 for (final Display d : al_displays) {
1463 if (layer == d.layer) {
1464 d.repaint(displ, r, extra, repaint_navigator, false);
1469 static public void repaint(final Displayable d) {
1470 if (d instanceof ZDisplayable) repaint(d.getLayerSet(), d, d.getBoundingBox(null), 5, true);
1471 repaint(d.getLayer(), d, d.getBoundingBox(null), 5, true);
1474 /** Repaint as much as the bounding box around the given Displayable, or the r if not null. */
1475 private void repaint(final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator, final boolean update_graphics) {
1476 if (repaint_disabled || null == displ) return;
1477 if (update_graphics || displ.getClass() == Patch.class || displ != active) {
1478 canvas.setUpdateGraphics(true);
1480 if (null != r) canvas.repaint(r, extra);
1481 else canvas.repaint(displ, extra);
1482 if (repaint_navigator) {
1483 DisplayablePanel dp = ht_panels.get(displ);
1484 if (null != dp) dp.repaint(); // is null when creating it, or after deleting it
1485 navigator.repaint(true); // everything
1489 /** 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. */
1490 static public void repaintSnapshot(final Displayable displ) {
1491 for (final Display d : al_displays) {
1492 if (d.layer.contains(displ)) {
1493 if (!d.navigator.isPainted(displ)) {
1494 DisplayablePanel dp = d.ht_panels.get(displ);
1495 if (null != dp) dp.repaint(); // is null when creating it, or after deleting it
1496 d.navigator.repaint(displ);
1502 /** Repaint the given Rectangle in all Displays showing the layer, updating the offscreen image if any. */
1503 static public void repaint(final Layer layer, final Rectangle r, final int extra) {
1504 repaint(layer, extra, r, true, true);
1507 static public void repaint(final Layer layer, final int extra, final Rectangle r, final boolean update_navigator) {
1508 repaint(layer, extra, r, update_navigator, true);
1511 static public void repaint(final Layer layer, final int extra, final Rectangle r, final boolean update_navigator, final boolean update_graphics) {
1512 if (repaint_disabled) return;
1513 for (final Display d : al_displays) {
1514 if (layer == d.layer) {
1515 d.canvas.setUpdateGraphics(update_graphics);
1516 d.canvas.repaint(r, extra);
1517 if (update_navigator) {
1518 d.navigator.repaint(true);
1519 Utils.updateComponent(d.tabs.getSelectedComponent());
1526 /** Repaint the given Rectangle in all Displays showing the layer, optionally updating the offscreen image (if any). */
1527 static public void repaint(final Layer layer, final Rectangle r, final int extra, final boolean update_graphics) {
1528 if (repaint_disabled) return;
1529 for (final Display d : al_displays) {
1530 if (layer == d.layer) {
1531 d.canvas.setUpdateGraphics(update_graphics);
1532 d.canvas.repaint(r, extra);
1533 d.navigator.repaint(update_graphics);
1534 if (update_graphics) Utils.updateComponent(d.tabs.getSelectedComponent());
1539 /** Repaint the DisplayablePanel (and DisplayNavigator) only for the given Displayable, in all Displays showing the given Layer. */
1540 static public void repaint(final Layer layer, final Displayable displ) {
1541 if (repaint_disabled) return;
1542 for (final Display d : al_displays) {
1543 if (layer == d.layer) {
1544 DisplayablePanel dp = d.ht_panels.get(displ);
1545 if (null != dp) dp.repaint();
1546 d.navigator.repaint(true);
1551 static public void repaint(LayerSet set, Displayable displ, int extra) {
1552 repaint(set, displ, null, extra);
1555 static public void repaint(LayerSet set, Displayable displ, Rectangle r, int extra) {
1556 repaint(set, displ, r, extra, true);
1559 /** Repaint the Displayable in every Display that shows a Layer belonging to the given LayerSet. */
1560 static public void repaint(final LayerSet set, final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator) {
1561 if (repaint_disabled) return;
1562 for (final Display d : al_displays) {
1563 if (set.contains(d.layer)) {
1564 if (repaint_navigator) {
1565 if (null != displ) {
1566 DisplayablePanel dp = d.ht_panels.get(displ);
1567 if (null != dp) dp.repaint();
1569 d.navigator.repaint(true);
1571 if (null == displ || displ != d.active) d.setUpdateGraphics(true); // safeguard
1572 // paint the given box or the actual Displayable's box
1573 if (null != r) d.canvas.repaint(r, extra);
1574 else d.canvas.repaint(displ, extra);
1579 /** Repaint the entire LayerSet, in all Displays showing a Layer of it.*/
1580 static public void repaint(final LayerSet set) {
1581 if (repaint_disabled) return;
1582 for (final Display d : al_displays) {
1583 if (set.contains(d.layer)) {
1584 d.navigator.repaint(true);
1585 d.canvas.repaint(true);
1589 /** Repaint the given box in the LayerSet, in all Displays showing a Layer of it.*/
1590 static public void repaint(final LayerSet set, final Rectangle box) {
1591 if (repaint_disabled) return;
1592 for (final Display d : al_displays) {
1593 if (set.contains(d.layer)) {
1594 d.navigator.repaint(box);
1595 d.canvas.repaint(box, 0, true);
1599 /** Repaint the entire Layer, in all Displays showing it, including the tabs.*/
1600 static public void repaint(final Layer layer) { // TODO this method overlaps with update(layer)
1601 if (repaint_disabled) return;
1602 for (final Display d : al_displays) {
1603 if (layer == d.layer) {
1604 d.navigator.repaint(true);
1605 d.canvas.repaint(true);
1610 /** Call repaint on all open Displays. */
1611 static public void repaint() {
1612 if (repaint_disabled) {
1613 Utils.logAll("Can't repaint -- repainting is disabled!");
1614 return;
1616 for (final Display d : al_displays) {
1617 d.navigator.repaint(true);
1618 d.canvas.repaint(true);
1622 static private boolean repaint_disabled = false;
1624 /** Set a flag to enable/disable repainting of all Display instances. */
1625 static protected void setRepaint(boolean b) {
1626 repaint_disabled = !b;
1629 public Rectangle getBounds() {
1630 return frame.getBounds();
1633 public Point getLocation() {
1634 return frame.getLocation();
1637 public JFrame getFrame() {
1638 return frame;
1641 public void setLocation(Point p) {
1642 this.frame.setLocation(p);
1645 public Displayable getActive() {
1646 return active; //TODO this should return selection.active !!
1649 public void select(Displayable d) {
1650 select(d, false);
1653 /** Select/deselect accordingly to the current state and the shift key. */
1654 public void select(final Displayable d, final boolean shift_down) {
1655 if (null != active && active != d && active.getClass() != Patch.class) {
1656 // active is being deselected, so link underlying patches
1657 active.linkPatches();
1659 if (null == d) {
1660 //Utils.log2("Display.select: clearing selection");
1661 canvas.setUpdateGraphics(true);
1662 selection.clear();
1663 return;
1665 if (!shift_down) {
1666 //Utils.log2("Display.select: single selection");
1667 if (d != active) {
1668 selection.clear();
1669 selection.add(d);
1671 } else if (selection.contains(d)) {
1672 if (active == d) {
1673 selection.remove(d);
1674 //Utils.log2("Display.select: removing from a selection");
1675 } else {
1676 //Utils.log2("Display.select: activing within a selection");
1677 selection.setActive(d);
1679 } else {
1680 //Utils.log2("Display.select: adding to an existing selection");
1681 selection.add(d);
1683 // update the image shown to ImageJ
1684 // NO longer necessary, always he same FakeImagePlus // setTempCurrentImage();
1687 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p, final Class c) {
1688 choose(screen_x_p, screen_y_p, x_p, y_p, false, c);
1690 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p) {
1691 choose(screen_x_p, screen_y_p, x_p, y_p, false, null);
1694 /** 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. */
1695 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p, boolean shift_down, Class c) {
1696 //Utils.log("Display.choose: x,y " + x_p + "," + y_p);
1697 final ArrayList<Displayable> al = new ArrayList<Displayable>(layer.find(x_p, y_p, true));
1698 al.addAll(layer.getParent().findZDisplayables(layer, x_p, y_p, true)); // only visible ones
1699 if (al.isEmpty()) {
1700 Displayable act = this.active;
1701 selection.clear();
1702 canvas.setUpdateGraphics(true);
1703 //Utils.log("choose: set active to null");
1704 // fixing lack of repainting for unknown reasons, of the active one TODO this is a temporary solution
1705 if (null != act) Display.repaint(layer, act, 5);
1706 } else if (1 == al.size()) {
1707 Displayable d = (Displayable)al.get(0);
1708 if (null != c && d.getClass() != c) {
1709 selection.clear();
1710 return;
1712 select(d, shift_down);
1713 //Utils.log("choose 1: set active to " + active);
1714 } else {
1715 if (al.contains(active) && !shift_down) {
1716 // do nothing
1717 } else {
1718 if (null != c) {
1719 // check if at least one of them is of class c
1720 // if only one is of class c, set as selected
1721 // else show menu
1722 for (Iterator it = al.iterator(); it.hasNext(); ) {
1723 Object ob = it.next();
1724 if (ob.getClass() != c) it.remove();
1726 if (0 == al.size()) {
1727 // deselect
1728 selection.clear();
1729 return;
1731 if (1 == al.size()) {
1732 select((Displayable)al.get(0), shift_down);
1733 return;
1735 // else, choose among the many
1737 choose(screen_x_p, screen_y_p, al, shift_down, x_p, y_p);
1739 //Utils.log("choose many: set active to " + active);
1743 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) {
1744 // show a popup on the canvas to choose
1745 new Thread() {
1746 public void run() {
1747 final Object lock = new Object();
1748 final DisplayableChooser d_chooser = new DisplayableChooser(al, lock);
1749 final JPopupMenu pop = new JPopupMenu("Select:");
1750 final Iterator itu = al.iterator();
1751 while (itu.hasNext()) {
1752 Displayable d = (Displayable)itu.next();
1753 JMenuItem menu_item = new JMenuItem(d.toString());
1754 menu_item.addActionListener(d_chooser);
1755 pop.add(menu_item);
1758 new Thread() {
1759 public void run() {
1760 pop.show(canvas, screen_x_p, screen_y_p);
1762 }.start();
1764 //now wait until selecting something
1765 synchronized(lock) {
1766 do {
1767 try {
1768 lock.wait();
1769 } catch (InterruptedException ie) {}
1770 } while (d_chooser.isWaiting() && pop.isShowing());
1773 //grab the chosen Displayable object
1774 Displayable d = d_chooser.getChosen();
1775 //Utils.log("Chosen: " + d.toString());
1776 if (null == d) { Utils.log2("Display.choose: returning a null!"); }
1777 select(d, shift_down);
1778 pop.setVisible(false);
1780 // fix selection bug: never receives mouseReleased event when the popup shows
1781 selection.mouseReleased(null, x_p, y_p, x_p, y_p, x_p, y_p);
1783 }.start();
1786 /** 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. */
1787 protected void setActive(final Displayable displ) {
1788 final Displayable prev_active = this.active;
1789 this.active = displ;
1790 SwingUtilities.invokeLater(new Runnable() { public void run() {
1792 // renew current image if necessary
1793 if (null != displ && displ == prev_active) {
1794 // make sure the proper tab is selected.
1795 selectTab(displ);
1796 return; // the same
1798 // deactivate previously active
1799 if (null != prev_active) {
1800 final DisplayablePanel ob = ht_panels.get(prev_active);
1801 if (null != ob) ob.setActive(false);
1802 // erase "decorations" of the previously active
1803 canvas.repaint(selection.getBox(), 4);
1805 // activate the new active
1806 if (null != displ) {
1807 final DisplayablePanel ob = ht_panels.get(displ);
1808 if (null != ob) ob.setActive(true);
1809 updateInDatabase("active_displayable_id");
1810 if (displ.getClass() != Patch.class) project.select(displ); // select the node in the corresponding tree, if any.
1811 // select the proper tab, and scroll to visible
1812 selectTab(displ);
1813 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
1814 repaint(displ, null, 5, false, update_graphics); // to show the border, and to repaint out of the background image
1815 transp_slider.setValue((int)(displ.getAlpha() * 100));
1816 } else {
1817 //ensure decorations are removed from the panels, for Displayables in a selection besides the active one
1818 Utils.updateComponent(tabs.getSelectedComponent());
1820 }});
1823 /** If the other paints under the base. */
1824 public boolean paintsBelow(Displayable base, Displayable other) {
1825 boolean zd_base = base instanceof ZDisplayable;
1826 boolean zd_other = other instanceof ZDisplayable;
1827 if (zd_other) {
1828 if (base instanceof DLabel) return true; // zd paints under label
1829 if (!zd_base) return false; // any zd paints over a mere displ if not a label
1830 else {
1831 // both zd, compare indices
1832 ArrayList<ZDisplayable> al = other.getLayerSet().getZDisplayables();
1833 return al.indexOf(base) > al.indexOf(other);
1835 } else {
1836 if (!zd_base) {
1837 // both displ, compare indices
1838 ArrayList<Displayable> al = other.getLayer().getDisplayables();
1839 return al.indexOf(base) > al.indexOf(other);
1840 } else {
1841 // base is zd, other is d
1842 if (other instanceof DLabel) return false;
1843 return true;
1848 /** 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. */
1849 private void selectTab(final Displayable displ) {
1850 Method method = null;
1851 try {
1852 if (!(displ instanceof LayerSet)) {
1853 method = Display.class.getDeclaredMethod("selectTab", new Class[]{displ.getClass()});
1855 } catch (Exception e) {
1856 IJError.print(e);
1858 if (null != method) {
1859 final Method me = method;
1860 dispatcher.exec(new Runnable() { public void run() {
1861 try {
1862 me.setAccessible(true);
1863 me.invoke(Display.this, new Object[]{displ});
1864 } catch (Exception e) { IJError.print(e); }
1865 }});
1869 private void selectTab(Patch patch) {
1870 tabs.setSelectedComponent(scroll_patches);
1871 scrollToShow(scroll_patches, ht_panels.get(patch));
1874 private void selectTab(Profile profile) {
1875 tabs.setSelectedComponent(scroll_profiles);
1876 scrollToShow(scroll_profiles, ht_panels.get(profile));
1879 private void selectTab(DLabel label) {
1880 tabs.setSelectedComponent(scroll_labels);
1881 scrollToShow(scroll_labels, ht_panels.get(label));
1884 private void selectTab(ZDisplayable zd) {
1885 tabs.setSelectedComponent(scroll_zdispl);
1886 scrollToShow(scroll_zdispl, ht_panels.get(zd));
1889 private void selectTab(Pipe d) { selectTab((ZDisplayable)d); }
1890 private void selectTab(Polyline d) { selectTab((ZDisplayable)d); }
1891 private void selectTab(AreaList d) { selectTab((ZDisplayable)d); }
1892 private void selectTab(Ball d) { selectTab((ZDisplayable)d); }
1893 private void selectTab(Dissector d) { selectTab((ZDisplayable)d); }
1895 /** 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). */
1896 private void updateTab(final Container tab, final String label, final ArrayList al) {
1897 final boolean[] recreated = new boolean[]{false, true, true};
1898 dispatcher.execSwing(new Runnable() { public void run() {
1899 try {
1900 if (0 == al.size()) {
1901 tab.removeAll();
1902 tab.add(new JLabel("No " + label + "."));
1903 } else {
1904 Component[] comp = tab.getComponents();
1905 int next = 0;
1906 if (1 == comp.length && comp[0].getClass() == JLabel.class) {
1907 next = 1;
1908 tab.remove(0);
1910 for (Iterator it = al.iterator(); it.hasNext(); ) {
1911 Displayable d = (Displayable)it.next();
1912 DisplayablePanel dp = null;
1913 if (next < comp.length) {
1914 dp = (DisplayablePanel)comp[next++]; // recycling panels
1915 dp.set(d);
1916 } else {
1917 dp = new DisplayablePanel(Display.this, d);
1918 tab.add(dp);
1920 ht_panels.put(d, dp);
1922 if (next < comp.length) {
1923 // remove from the end, to avoid potential repaints of other panels
1924 for (int i=comp.length-1; i>=next; i--) {
1925 tab.remove(i);
1928 recreated[0] = true;
1930 if (recreated[0]) {
1931 tab.invalidate();
1932 tab.validate();
1933 tab.repaint();
1935 if (null != Display.this.active) scrollToShow(Display.this.active);
1936 } catch (Throwable e) { IJError.print(e); }
1937 }});
1940 static public void setActive(final Object event, final Displayable displ) {
1941 if (!(event instanceof InputEvent)) return;
1942 // find which Display
1943 for (final Display d : al_displays) {
1944 if (d.isOrigin((InputEvent)event)) {
1945 d.setActive(displ);
1946 break;
1951 /** Find out whether this Display is Transforming its active Displayable. */
1952 public boolean isTransforming() {
1953 return canvas.isTransforming();
1956 /** Find whether any Display is transforming the given Displayable. */
1957 static public boolean isTransforming(final Displayable displ) {
1958 for (final Display d : al_displays) {
1959 if (null != d.active && d.active == displ && d.canvas.isTransforming()) return true;
1961 return false;
1964 static public boolean isAligning(final LayerSet set) {
1965 for (final Display d : al_displays) {
1966 if (d.layer.getParent() == set && set.isAligning()) {
1967 return true;
1970 return false;
1973 /** Set the front Display to transform the Displayable only if no other canvas is transforming it. */
1974 static public void setTransforming(final Displayable displ) {
1975 if (null == front) return;
1976 if (front.active != displ) return;
1977 for (final Display d : al_displays) {
1978 if (d.active == displ) {
1979 if (d.canvas.isTransforming()) {
1980 Utils.showMessage("Already transforming " + displ.getTitle());
1981 return;
1985 front.canvas.setTransforming(true);
1988 /** Check whether the source of the event is located in this instance.*/
1989 private boolean isOrigin(InputEvent event) {
1990 Object source = event.getSource();
1991 // find it ... check the canvas for now TODO
1992 if (canvas == source) {
1993 return true;
1995 return false;
1998 /** Get the layer of the front Display, or null if none.*/
1999 static public Layer getFrontLayer() {
2000 if (null == front) return null;
2001 return front.layer;
2004 /** Get the layer of an open Display of the given Project, or null if none.*/
2005 static public Layer getFrontLayer(final Project project) {
2006 if (null == front) return null;
2007 if (front.project == project) return front.layer;
2008 // else, find an open Display for the given Project, if any
2009 for (final Display d : al_displays) {
2010 if (d.project == project) {
2011 d.frame.toFront();
2012 return d.layer;
2015 return null; // none found
2018 static public Display getFront(final Project project) {
2019 if (null == front) return null;
2020 if (front.project == project) return front;
2021 for (final Display d : al_displays) {
2022 if (d.project == project) {
2023 d.frame.toFront();
2024 return d;
2027 return null;
2030 public boolean isReadOnly() {
2031 // TEMPORARY: in the future one will be able show displays as read-only to other people, remotely
2032 return false;
2035 static public void showPopup(Component c, int x, int y) {
2036 if (null != front) front.getPopupMenu().show(c, x, y);
2039 /** Return a context-sensitive popup menu. */
2040 public JPopupMenu getPopupMenu() { // called from canvas
2041 // get the job canceling dialog
2042 if (!canvas.isInputEnabled()) {
2043 return project.getLoader().getJobsPopup(this);
2046 // create new
2047 this.popup = new JPopupMenu();
2048 JMenuItem item = null;
2049 JMenu menu = null;
2051 if (ProjectToolbar.ALIGN == Toolbar.getToolId()) {
2052 boolean aligning = layer.getParent().isAligning();
2053 item = new JMenuItem("Cancel alignment"); item.addActionListener(this); popup.add(item);
2054 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2055 if (!aligning) item.setEnabled(false);
2056 item = new JMenuItem("Align with landmarks"); item.addActionListener(this); popup.add(item);
2057 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true));
2058 if (!aligning) item.setEnabled(false);
2059 item = new JMenuItem("Align and register"); item.addActionListener(this); popup.add(item);
2060 if (!aligning) item.setEnabled(false);
2061 item = new JMenuItem("Align using profiles"); item.addActionListener(this); popup.add(item);
2062 if (!aligning || selection.isEmpty() || !selection.contains(Profile.class)) item.setEnabled(false);
2063 item = new JMenuItem("Align stack slices"); item.addActionListener(this); popup.add(item);
2064 if (selection.isEmpty() || ! (getActive().getClass() == Patch.class && ((Patch)getActive()).isStack())) item.setEnabled(false);
2065 item = new JMenuItem("Align layers"); item.addActionListener(this); popup.add(item);
2066 if (1 == layer.getParent().size()) item.setEnabled(false);
2067 item = new JMenuItem("Align multi-layer mosaic"); item.addActionListener(this); popup.add(item);
2068 if (1 == layer.getParent().size()) item.setEnabled(false);
2069 return popup;
2073 if (null != active) {
2074 if (!canvas.isTransforming()) {
2075 if (active instanceof Profile) {
2076 item = new JMenuItem("Duplicate, link and send to next layer"); item.addActionListener(this); popup.add(item);
2077 Layer nl = layer.getParent().next(layer);
2078 if (nl == layer) item.setEnabled(false);
2079 item = new JMenuItem("Duplicate, link and send to previous layer"); item.addActionListener(this); popup.add(item);
2080 nl = layer.getParent().previous(layer);
2081 if (nl == layer) item.setEnabled(false);
2083 menu = new JMenu("Duplicate, link and send to");
2084 ArrayList al = layer.getParent().getLayers();
2085 final Iterator it = al.iterator();
2086 int i = 1;
2087 while (it.hasNext()) {
2088 Layer la = (Layer)it.next();
2089 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
2090 if (la == this.layer) item.setEnabled(false);
2091 i++;
2093 popup.add(menu);
2094 item = new JMenuItem("Duplicate, link and send to..."); item.addActionListener(this); popup.add(item);
2096 popup.addSeparator();
2098 item = new JMenuItem("Unlink from images"); item.addActionListener(this); popup.add(item);
2099 if (!active.isLinked()) item.setEnabled(false); // isLinked() checks if it's linked to a Patch in its own layer
2100 item = new JMenuItem("Show in 3D"); item.addActionListener(this); popup.add(item);
2101 popup.addSeparator();
2102 } else if (active instanceof Patch) {
2103 item = new JMenuItem("Unlink from images"); item.addActionListener(this); popup.add(item);
2104 if (!active.isLinked(Patch.class)) item.setEnabled(false);
2105 if (((Patch)active).isStack()) {
2106 item = new JMenuItem("Unlink slices"); item.addActionListener(this); popup.add(item);
2108 int n_sel_patches = selection.getSelected(Patch.class).size();
2109 if (1 == n_sel_patches) {
2110 item = new JMenuItem("Snap"); item.addActionListener(this); popup.add(item);
2111 } else if (n_sel_patches > 1) {
2112 item = new JMenuItem("Montage"); item.addActionListener(this); popup.add(item);
2113 item = new JMenuItem("Lens correction"); item.addActionListener(this); popup.add(item);
2114 item = new JMenuItem("Blend"); item.addActionListener(this); popup.add(item);
2116 item = new JMenuItem("Remove alpha mask"); item.addActionListener(this); popup.add(item);
2117 if ( ! ((Patch)active).hasAlphaMask()) item.setEnabled(false);
2118 item = new JMenuItem("Link images..."); item.addActionListener(this); popup.add(item);
2119 item = new JMenuItem("View volume"); item.addActionListener(this); popup.add(item);
2120 HashSet hs = active.getLinked(Patch.class);
2121 if (null == hs || 0 == hs.size()) item.setEnabled(false);
2122 item = new JMenuItem("View orthoslices"); item.addActionListener(this); popup.add(item);
2123 if (null == hs || 0 == hs.size()) item.setEnabled(false); // if no Patch instances among the directly linked, then it's not a stack
2124 popup.addSeparator();
2125 } else {
2126 item = new JMenuItem("Unlink"); item.addActionListener(this); popup.add(item);
2127 item = new JMenuItem("Show in 3D"); item.addActionListener(this); popup.add(item);
2128 popup.addSeparator();
2130 if (active instanceof AreaList) {
2131 item = new JMenuItem("Merge"); item.addActionListener(this); popup.add(item);
2132 ArrayList al = selection.getSelected();
2133 int n = 0;
2134 for (Iterator it = al.iterator(); it.hasNext(); ) {
2135 if (it.next().getClass() == AreaList.class) n++;
2137 if (n < 2) item.setEnabled(false);
2138 } else if (active instanceof Pipe) {
2139 item = new JMenuItem("Identify..."); item.addActionListener(this); popup.add(item);
2140 item = new JMenuItem("Identify with axes..."); item.addActionListener(this); popup.add(item);
2143 if (canvas.isTransforming()) {
2144 item = new JMenuItem("Apply transform"); item.addActionListener(this); popup.add(item);
2145 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
2146 item = new JMenuItem("Apply transform propagating to last layer"); item.addActionListener(this); popup.add(item);
2147 if (layer.getParent().indexOf(layer) == layer.getParent().size() -1) item.setEnabled(false);
2148 item = new JMenuItem("Apply transform propagating to first layer"); item.addActionListener(this); popup.add(item);
2149 if (0 == layer.getParent().indexOf(layer)) item.setEnabled(false);
2150 } else {
2151 item = new JMenuItem("Transform"); item.addActionListener(this); popup.add(item);
2152 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0, true));
2154 item = new JMenuItem("Cancel transform"); item.addActionListener(this); popup.add(item);
2155 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2156 if (!canvas.isTransforming()) item.setEnabled(false);
2157 if (canvas.isTransforming()) {
2158 item = new JMenuItem("Specify transform..."); item.addActionListener(this); popup.add(item);
2161 if (!canvas.isTransforming()) {
2162 item = new JMenuItem("Duplicate"); item.addActionListener(this); popup.add(item);
2163 item = new JMenuItem("Color..."); item.addActionListener(this); popup.add(item);
2164 if (active instanceof LayerSet) item.setEnabled(false);
2165 if (active.isLocked()) {
2166 item = new JMenuItem("Unlock"); item.addActionListener(this); popup.add(item);
2167 } else {
2168 item = new JMenuItem("Lock"); item.addActionListener(this); popup.add(item);
2170 menu = new JMenu("Move");
2171 popup.addSeparator();
2172 LayerSet ls = layer.getParent();
2173 item = new JMenuItem("Move to top"); item.addActionListener(this); menu.add(item);
2174 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.
2175 if (ls.isTop(active)) item.setEnabled(false);
2176 item = new JMenuItem("Move up"); item.addActionListener(this); menu.add(item);
2177 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0, true));
2178 if (ls.isTop(active)) item.setEnabled(false);
2179 item = new JMenuItem("Move down"); item.addActionListener(this); menu.add(item);
2180 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0, true));
2181 if (ls.isBottom(active)) item.setEnabled(false);
2182 item = new JMenuItem("Move to bottom"); item.addActionListener(this); menu.add(item);
2183 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_END, 0, true));
2184 if (ls.isBottom(active)) item.setEnabled(false);
2186 popup.add(menu);
2187 popup.addSeparator();
2188 item = new JMenuItem("Delete..."); item.addActionListener(this); popup.add(item);
2189 try {
2190 if (active instanceof Patch) {
2191 if (!active.isOnlyLinkedTo(Patch.class)) {
2192 item.setEnabled(false);
2194 } else if (!(active instanceof DLabel)) { // can't delete elements from the trees (Profile, Pipe, LayerSet)
2195 item.setEnabled(false);
2197 } catch (Exception e) { IJError.print(e); item.setEnabled(false); }
2199 if (active instanceof Patch) {
2200 item = new JMenuItem("Revert"); item.addActionListener(this); popup.add(item);
2201 popup.addSeparator();
2203 item = new JMenuItem("Properties..."); item.addActionListener(this); popup.add(item);
2204 item = new JMenuItem("Show centered"); item.addActionListener(this); popup.add(item);
2206 popup.addSeparator();
2208 if (! (active instanceof ZDisplayable)) {
2209 ArrayList al_layers = layer.getParent().getLayers();
2210 int i_layer = al_layers.indexOf(layer);
2211 int n_layers = al_layers.size();
2212 item = new JMenuItem("Send to previous layer"); item.addActionListener(this); popup.add(item);
2213 if (1 == n_layers || 0 == i_layer || active.isLinked()) item.setEnabled(false);
2214 // 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
2215 else if (active instanceof Profile && !active.canSendTo(layer.getParent().previous(layer))) item.setEnabled(false);
2216 item = new JMenuItem("Send to next layer"); item.addActionListener(this); popup.add(item);
2217 if (1 == n_layers || n_layers -1 == i_layer || active.isLinked()) item.setEnabled(false);
2218 else if (active instanceof Profile && !active.canSendTo(layer.getParent().next(layer))) item.setEnabled(false);
2221 menu = new JMenu("Send linked group to...");
2222 if (active.hasLinkedGroupWithinLayer(this.layer)) {
2223 int i = 1;
2224 for (final Layer la : ls.getLayers()) {
2225 String layer_title = i + ": " + la.getTitle();
2226 if (-1 == layer_title.indexOf(' ')) layer_title += " ";
2227 item = new JMenuItem(layer_title); item.addActionListener(this); menu.add(item);
2228 if (la == this.layer) item.setEnabled(false);
2229 i++;
2231 popup.add(menu);
2232 } else {
2233 menu.setEnabled(false);
2234 //Utils.log("Active's linked group not within layer.");
2236 popup.add(menu);
2237 popup.addSeparator();
2242 if (!canvas.isTransforming()) {
2244 item = new JMenuItem("Undo");item.addActionListener(this); popup.add(item);
2245 if (!layer.getParent().canUndo() || canvas.isTransforming()) item.setEnabled(false);
2246 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Utils.getControlModifier(), true));
2247 item = new JMenuItem("Redo");item.addActionListener(this); popup.add(item);
2248 if (!layer.getParent().canRedo() || canvas.isTransforming()) item.setEnabled(false);
2249 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Event.SHIFT_MASK | Event.CTRL_MASK, true));
2250 popup.addSeparator();
2252 // Would get so much simpler with a clojure macro ...
2254 try {
2255 menu = new JMenu("Hide/Unhide");
2256 item = new JMenuItem("Hide deselected"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.SHIFT_MASK, true));
2257 boolean none = 0 == selection.getNSelected();
2258 if (none) item.setEnabled(false);
2259 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));
2260 if (none) item.setEnabled(false);
2261 item = new JMenuItem("Hide selected"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, 0, true));
2262 if (none) item.setEnabled(false);
2263 none = ! layer.getParent().containsDisplayable(DLabel.class);
2264 item = new JMenuItem("Hide all labels"); item.addActionListener(this); menu.add(item);
2265 if (none) item.setEnabled(false);
2266 item = new JMenuItem("Unhide all labels"); item.addActionListener(this); menu.add(item);
2267 if (none) item.setEnabled(false);
2268 none = ! layer.getParent().contains(AreaList.class);
2269 item = new JMenuItem("Hide all arealists"); item.addActionListener(this); menu.add(item);
2270 if (none) item.setEnabled(false);
2271 item = new JMenuItem("Unhide all arealists"); item.addActionListener(this); menu.add(item);
2272 if (none) item.setEnabled(false);
2273 none = ! layer.contains(Profile.class);
2274 item = new JMenuItem("Hide all profiles"); item.addActionListener(this); menu.add(item);
2275 if (none) item.setEnabled(false);
2276 item = new JMenuItem("Unhide all profiles"); item.addActionListener(this); menu.add(item);
2277 if (none) item.setEnabled(false);
2278 none = ! layer.getParent().contains(Pipe.class);
2279 item = new JMenuItem("Hide all pipes"); item.addActionListener(this); menu.add(item);
2280 if (none) item.setEnabled(false);
2281 item = new JMenuItem("Unhide all pipes"); item.addActionListener(this); menu.add(item);
2282 if (none) item.setEnabled(false);
2283 none = ! layer.getParent().contains(Polyline.class);
2284 item = new JMenuItem("Hide all polylines"); item.addActionListener(this); menu.add(item);
2285 if (none) item.setEnabled(false);
2286 item = new JMenuItem("Unhide all polylines"); item.addActionListener(this); menu.add(item);
2287 if (none) item.setEnabled(false);
2288 none = ! layer.getParent().contains(Ball.class);
2289 item = new JMenuItem("Hide all balls"); item.addActionListener(this); menu.add(item);
2290 if (none) item.setEnabled(false);
2291 item = new JMenuItem("Unhide all balls"); item.addActionListener(this); menu.add(item);
2292 if (none) item.setEnabled(false);
2293 none = ! layer.getParent().containsDisplayable(Patch.class);
2294 item = new JMenuItem("Hide all images"); item.addActionListener(this); menu.add(item);
2295 if (none) item.setEnabled(false);
2296 item = new JMenuItem("Unhide all images"); item.addActionListener(this); menu.add(item);
2297 if (none) item.setEnabled(false);
2298 item = new JMenuItem("Hide all but images"); item.addActionListener(this); menu.add(item);
2299 item = new JMenuItem("Unhide all"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.ALT_MASK, true));
2301 popup.add(menu);
2302 } catch (Exception e) { IJError.print(e); }
2304 JMenu adjust_menu = new JMenu("Adjust");
2305 item = new JMenuItem("Calibration..."); item.addActionListener(this); adjust_menu.add(item);
2306 item = new JMenuItem("Enhance contrast layer-wise..."); item.addActionListener(this); adjust_menu.add(item);
2307 item = new JMenuItem("Enhance contrast (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
2308 if (selection.isEmpty()) item.setEnabled(false);
2309 item = new JMenuItem("Set Min and Max layer-wise..."); item.addActionListener(this); adjust_menu.add(item);
2310 item = new JMenuItem("Set Min and Max (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
2311 if (selection.isEmpty()) item.setEnabled(false);
2312 popup.add(adjust_menu);
2314 menu = new JMenu("Import");
2315 item = new JMenuItem("Import image"); item.addActionListener(this); menu.add(item);
2316 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, Event.ALT_MASK & Event.SHIFT_MASK, true));
2317 item = new JMenuItem("Import stack..."); item.addActionListener(this); menu.add(item);
2318 item = new JMenuItem("Import grid..."); item.addActionListener(this); menu.add(item);
2319 item = new JMenuItem("Import sequence as grid..."); item.addActionListener(this); menu.add(item);
2320 item = new JMenuItem("Import from text file..."); item.addActionListener(this); menu.add(item);
2321 item = new JMenuItem("Import labels as arealists..."); item.addActionListener(this); menu.add(item);
2322 popup.add(menu);
2324 menu = new JMenu("Export");
2325 item = new JMenuItem("Make flat image..."); item.addActionListener(this); menu.add(item);
2326 item = new JMenuItem("Arealists as labels (tif)"); item.addActionListener(this); menu.add(item);
2327 if (0 == layer.getParent().getZDisplayables(AreaList.class).size()) item.setEnabled(false);
2328 item = new JMenuItem("Arealists as labels (amira)"); item.addActionListener(this); menu.add(item);
2329 if (0 == layer.getParent().getZDisplayables(AreaList.class).size()) item.setEnabled(false);
2330 popup.add(menu);
2332 menu = new JMenu("Display");
2333 item = new JMenuItem("Resize canvas/LayerSet..."); item.addActionListener(this); menu.add(item);
2334 item = new JMenuItem("Autoresize canvas/LayerSet"); item.addActionListener(this); menu.add(item);
2335 // OBSOLETE // item = new JMenuItem("Rotate Layer/LayerSet..."); item.addActionListener(this); menu.add(item);
2336 item = new JMenuItem("Properties ..."); item.addActionListener(this); menu.add(item);
2337 popup.add(menu);
2339 menu = new JMenu("Project");
2340 this.project.getLoader().setupMenuItems(menu, this.getProject());
2341 item = new JMenuItem("Project properties..."); item.addActionListener(this); menu.add(item);
2342 item = new JMenuItem("Create subproject"); item.addActionListener(this); menu.add(item);
2343 if (null == canvas.getFakeImagePlus().getRoi()) item.setEnabled(false);
2344 item = new JMenuItem("Release memory..."); item.addActionListener(this); menu.add(item);
2345 item = new JMenuItem("Flush image cache"); item.addActionListener(this); menu.add(item);
2346 item = new JMenuItem("Regenerate all mipmaps"); item.addActionListener(this); menu.add(item);
2347 popup.add(menu);
2349 menu = new JMenu("Selection");
2350 item = new JMenuItem("Select all"); item.addActionListener(this); menu.add(item);
2351 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, Utils.getControlModifier(), true));
2352 if (0 == layer.getDisplayables().size() && 0 == layer.getParent().getZDisplayables().size()) item.setEnabled(false);
2353 item = new JMenuItem("Select none"); item.addActionListener(this); menu.add(item);
2354 if (0 == selection.getNSelected()) item.setEnabled(false);
2355 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2357 JMenu bytype = new JMenu("Select all by type");
2358 item = new JMenuItem("AreaList"); item.addActionListener(bytypelistener); bytype.add(item);
2359 item = new JMenuItem("Ball"); item.addActionListener(bytypelistener); bytype.add(item);
2360 item = new JMenuItem("Dissector"); item.addActionListener(bytypelistener); bytype.add(item);
2361 item = new JMenuItem("Image"); item.addActionListener(bytypelistener); bytype.add(item);
2362 item = new JMenuItem("Text"); item.addActionListener(bytypelistener); bytype.add(item);
2363 item = new JMenuItem("Pipe"); item.addActionListener(bytypelistener); bytype.add(item);
2364 item = new JMenuItem("Polyline"); item.addActionListener(bytypelistener); bytype.add(item);
2365 item = new JMenuItem("Profile"); item.addActionListener(bytypelistener); bytype.add(item);
2366 menu.add(bytype);
2368 item = new JMenuItem("Restore selection"); item.addActionListener(this); menu.add(item);
2369 item = new JMenuItem("Select under ROI"); item.addActionListener(this); menu.add(item);
2370 if (canvas.getFakeImagePlus().getRoi() == null) item.setEnabled(false);
2371 popup.add(menu);
2372 item = new JMenuItem("Search..."); item.addActionListener(this); popup.add(item);
2375 //canvas.add(popup);
2376 return popup;
2379 private ByTypeListener bytypelistener = new ByTypeListener(this);
2381 static private class ByTypeListener implements ActionListener {
2382 final Display d;
2383 ByTypeListener(final Display d) {
2384 this.d = d;
2386 public void actionPerformed(final ActionEvent ae) {
2387 final String command = ae.getActionCommand();
2389 final java.awt.geom.Area aroi = M.getArea(d.canvas.getFakeImagePlus().getRoi());
2391 d.dispatcher.exec(new Runnable() { public void run() {
2393 try {
2394 String type = command;
2395 if (type.equals("Image")) type = "Patch";
2396 Class c = Class.forName("ini.trakem2.display." + type);
2398 java.util.List<Displayable> a = new ArrayList<Displayable>();
2399 if (null != aroi) {
2400 a.addAll(d.layer.getDisplayables(c, aroi, true));
2401 a.addAll(d.layer.getParent().getZDisplayables(c, d.layer, aroi, true));
2402 } else {
2403 a.addAll(d.layer.getDisplayables(c));
2404 a.addAll(d.layer.getParent().getZDisplayables(c));
2405 // Remove non-visible ones
2406 for (final Iterator<Displayable> it = a.iterator(); it.hasNext(); ) {
2407 if (!it.next().isVisible()) it.remove();
2411 if (0 == a.size()) return;
2413 boolean selected = false;
2415 if (0 == ae.getModifiers()) {
2416 Utils.log2("first");
2417 d.selection.clear();
2418 d.selection.selectAll(a);
2419 selected = true;
2420 } else if (0 == (ae.getModifiers() ^ Event.SHIFT_MASK)) {
2421 Utils.log2("with shift");
2422 d.selection.selectAll(a); // just add them to the current selection
2423 selected = true;
2425 if (selected) {
2426 // Activate last:
2427 d.selection.setActive(a.get(a.size() -1));
2430 } catch (ClassNotFoundException e) {
2431 Utils.log2(e.toString());
2434 }});
2438 /** Check if a panel for the given Displayable is completely visible in the JScrollPane */
2439 public boolean isWithinViewport(final Displayable d) {
2440 final JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
2441 if (ht_tabs.get(d.getClass()) == scroll) return isWithinViewport(scroll, ht_panels.get(d));
2442 return false;
2445 private boolean isWithinViewport(JScrollPane scroll, DisplayablePanel dp) {
2446 if(null == dp) return false;
2447 JViewport view = scroll.getViewport();
2448 java.awt.Dimension dimensions = view.getExtentSize();
2449 java.awt.Point p = view.getViewPosition();
2450 int y = dp.getY();
2451 if ((y + DisplayablePanel.HEIGHT - p.y) <= dimensions.height && y >= p.y) {
2452 return true;
2454 return false;
2457 /** Check if a panel for the given Displayable is partially visible in the JScrollPane */
2458 public boolean isPartiallyWithinViewport(final Displayable d) {
2459 final JScrollPane scroll = ht_tabs.get(d.getClass());
2460 if (tabs.getSelectedComponent() == scroll) return isPartiallyWithinViewport(scroll, ht_panels.get(d));
2461 return false;
2464 /** Check if a panel for the given Displayable is at least partially visible in the JScrollPane */
2465 private boolean isPartiallyWithinViewport(final JScrollPane scroll, final DisplayablePanel dp) {
2466 if(null == dp) {
2467 //Utils.log2("Display.isPartiallyWithinViewport: null DisplayablePanel ??");
2468 return false; // to fast for you baby
2470 JViewport view = scroll.getViewport();
2471 java.awt.Dimension dimensions = view.getExtentSize();
2472 java.awt.Point p = view.getViewPosition();
2473 int y = dp.getY();
2474 if ( ((y + DisplayablePanel.HEIGHT - p.y) <= dimensions.height && y >= p.y) // completely visible
2475 || ((y + DisplayablePanel.HEIGHT - p.y) > dimensions.height && y < p.y + dimensions.height) // partially hovering at the bottom
2476 || ((y + DisplayablePanel.HEIGHT) > p.y && y < p.y) // partially hovering at the top
2478 return true;
2480 return false;
2483 /** A function to make a Displayable panel be visible in the screen, by scrolling the viewport of the JScrollPane. */
2484 private void scrollToShow(final Displayable d) {
2485 dispatcher.execSwing(new Runnable() { public void run() {
2486 final JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
2487 if (d instanceof ZDisplayable && scroll == scroll_zdispl) {
2488 scrollToShow(scroll_zdispl, ht_panels.get(d));
2489 return;
2491 final Class c = d.getClass();
2492 if (Patch.class == c && scroll == scroll_patches) {
2493 scrollToShow(scroll_patches, ht_panels.get(d));
2494 } else if (DLabel.class == c && scroll == scroll_labels) {
2495 scrollToShow(scroll_labels, ht_panels.get(d));
2496 } else if (Profile.class == c && scroll == scroll_profiles) {
2497 scrollToShow(scroll_profiles, ht_panels.get(d));
2499 }});
2502 private void scrollToShow(final JScrollPane scroll, final JPanel dp) {
2503 if (null == dp) return;
2504 JViewport view = scroll.getViewport();
2505 Point current = view.getViewPosition();
2506 Dimension extent = view.getExtentSize();
2507 int panel_y = dp.getY();
2508 if ((panel_y + DisplayablePanel.HEIGHT - current.y) <= extent.height && panel_y >= current.y) {
2509 // it's completely visible already
2510 return;
2511 } else {
2512 // scroll just enough
2513 // if it's above, show at the top
2514 if (panel_y - current.y < 0) {
2515 view.setViewPosition(new Point(0, panel_y));
2517 // if it's below (even if partially), show at the bottom
2518 else if (panel_y + 50 > current.y + extent.height) {
2519 view.setViewPosition(new Point(0, panel_y - extent.height + 50));
2520 //Utils.log("Display.scrollToShow: panel_y: " + panel_y + " current.y: " + current.y + " extent.height: " + extent.height);
2525 /** Update the title of the given Displayable in its DisplayablePanel, if any. */
2526 static public void updateTitle(final Layer layer, final Displayable displ) {
2527 for (final Display d : al_displays) {
2528 if (layer == d.layer) {
2529 DisplayablePanel dp = d.ht_panels.get(displ);
2530 if (null != dp) dp.updateTitle();
2535 /** Update the Display's title in all Displays showing the given Layer. */
2536 static public void updateTitle(final Layer layer) {
2537 for (final Display d : al_displays) {
2538 if (d.layer == layer) {
2539 d.updateFrameTitle();
2543 /** Update the Display's title in all Displays showing a Layer of the given LayerSet. */
2544 static public void updateTitle(final LayerSet ls) {
2545 for (final Display d : al_displays) {
2546 if (d.layer.getParent() == ls) {
2547 d.updateFrameTitle();
2552 /** Set a new title in the JFrame, showing info on the layer 'z' and the magnification. */
2553 public void updateFrameTitle() {
2554 updateFrameTitle(layer);
2556 private void updateFrameTitle(Layer layer) {
2557 // From ij.ImagePlus class, the solution:
2558 String scale = "";
2559 final double magnification = canvas.getMagnification();
2560 if (magnification!=1.0) {
2561 final double percent = magnification*100.0;
2562 scale = new StringBuilder(" (").append(Utils.d2s(percent, percent==(int)percent ? 0 : 1)).append("%)").toString();
2564 final Calibration cal = layer.getParent().getCalibration();
2565 String title = new StringBuilder().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();
2566 frame.setTitle(title);
2567 // fix the title for the FakeImageWindow and thus the WindowManager listing in the menus
2568 canvas.getFakeImagePlus().setTitle(title);
2571 /** 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. */
2572 public void nextLayer(final int modifiers) {
2573 final Layer l;
2574 if (0 == (modifiers ^ Event.SHIFT_MASK)) {
2575 l = layer.getParent().nextNonEmpty(layer);
2576 } else if (scroll_step > 1) {
2577 int i = layer.getParent().indexOf(this.layer);
2578 Layer la = layer.getParent().getLayer(i + scroll_step);
2579 if (null != la) l = la;
2580 else l = null;
2581 } else {
2582 l = layer.getParent().next(layer);
2584 if (l != layer) {
2585 slt.set(l);
2586 updateInDatabase("layer_id");
2590 private final void translateLayerColors(final Layer current, final Layer other) {
2591 if (current == other) return;
2592 if (layer_channels.size() > 0) {
2593 final LayerSet ls = getLayerSet();
2594 // translate colors by distance from current layer to new Layer l
2595 final int dist = ls.indexOf(other) - ls.indexOf(current);
2596 translateLayerColor(Color.red, dist);
2597 translateLayerColor(Color.blue, dist);
2601 private final void translateLayerColor(final Color color, final int dist) {
2602 final LayerSet ls = getLayerSet();
2603 final Layer l = layer_channels.get(color);
2604 if (null == l) return;
2605 updateColor(Color.white, layer_panels.get(l));
2606 final Layer l2 = ls.getLayer(ls.indexOf(l) + dist);
2607 if (null != l2) updateColor(color, layer_panels.get(l2));
2610 private final void updateColor(final Color color, final LayerPanel lp) {
2611 lp.setColor(color);
2612 setColorChannel(lp.layer, color);
2615 /** Calls setLayer(la) on the SetLayerThread. */
2616 public void toLayer(final Layer la) {
2617 if (la.getParent() != layer.getParent()) return; // not of the same LayerSet
2618 if (la == layer) return; // nothing to do
2619 slt.set(la);
2620 updateInDatabase("layer_id");
2623 /** 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. */
2624 public void previousLayer(final int modifiers) {
2625 final Layer l;
2626 if (0 == (modifiers ^ Event.SHIFT_MASK)) {
2627 l = layer.getParent().previousNonEmpty(layer);
2628 } else if (scroll_step > 1) {
2629 int i = layer.getParent().indexOf(this.layer);
2630 Layer la = layer.getParent().getLayer(i - scroll_step);
2631 if (null != la) l = la;
2632 else l = null;
2633 } else {
2634 l = layer.getParent().previous(layer);
2636 if (l != layer) {
2637 slt.set(l);
2638 updateInDatabase("layer_id");
2642 static public void updateLayerScroller(LayerSet set) {
2643 for (final Display d : al_displays) {
2644 if (d.layer.getParent() == set) {
2645 d.updateLayerScroller(d.layer);
2650 private void updateLayerScroller(Layer layer) {
2651 int size = layer.getParent().size();
2652 if (size <= 1) {
2653 scroller.setValues(0, 1, 0, 0);
2654 scroller.setEnabled(false);
2655 } else {
2656 scroller.setEnabled(true);
2657 scroller.setValues(layer.getParent().getLayerIndex(layer.getId()), 1, 0, size);
2659 recreateLayerPanels(layer);
2662 // Can't use this.layer, may still be null. User argument instead.
2663 private synchronized void recreateLayerPanels(final Layer layer) {
2664 synchronized (layer_channels) {
2665 panel_layers.removeAll();
2667 if (0 == layer_panels.size()) {
2668 for (final Layer la : layer.getParent().getLayers()) {
2669 final LayerPanel lp = new LayerPanel(this, la);
2670 layer_panels.put(la, lp);
2671 this.panel_layers.add(lp);
2673 } else {
2674 // Set theory at work: keep old to reuse
2675 layer_panels.keySet().retainAll(layer.getParent().getLayers());
2676 for (final Layer la : layer.getParent().getLayers()) {
2677 LayerPanel lp = layer_panels.get(la);
2678 if (null == lp) {
2679 lp = new LayerPanel(this, la);
2680 layer_panels.put(la, lp);
2682 this.panel_layers.add(lp);
2684 for (final Iterator<Map.Entry<Integer,LayerPanel>> it = layer_alpha.entrySet().iterator(); it.hasNext(); ) {
2685 final Map.Entry<Integer,LayerPanel> e = it.next();
2686 if (-1 == getLayerSet().indexOf(e.getValue().layer)) it.remove();
2688 for (final Iterator<Map.Entry<Color,Layer>> it = layer_channels.entrySet().iterator(); it.hasNext(); ) {
2689 final Map.Entry<Color,Layer> e = it.next();
2690 if (-1 == getLayerSet().indexOf(e.getValue())) it.remove();
2692 scroll_layers.repaint();
2697 private void updateSnapshots() {
2698 Enumeration<DisplayablePanel> e = ht_panels.elements();
2699 while (e.hasMoreElements()) {
2700 e.nextElement().remake();
2702 Utils.updateComponent(tabs.getSelectedComponent());
2705 static public void updatePanel(Layer layer, final Displayable displ) {
2706 if (null == layer && null != front) layer = front.layer; // the front layer
2707 for (final Display d : al_displays) {
2708 if (d.layer == layer) {
2709 d.updatePanel(displ);
2714 private void updatePanel(Displayable d) {
2715 JPanel c = null;
2716 if (d instanceof Profile) {
2717 c = panel_profiles;
2718 } else if (d instanceof Patch) {
2719 c = panel_patches;
2720 } else if (d instanceof DLabel) {
2721 c = panel_labels;
2722 } else if (d instanceof Pipe) {
2723 c = panel_zdispl;
2725 if (null == c) return;
2726 DisplayablePanel dp = ht_panels.get(d);
2727 dp.remake();
2728 Utils.updateComponent(c);
2731 static public void updatePanelIndex(final Layer layer, final Displayable displ) {
2732 for (final Display d : al_displays) {
2733 if (d.layer == layer || displ instanceof ZDisplayable) {
2734 d.updatePanelIndex(displ);
2739 private void updatePanelIndex(final Displayable d) {
2740 // find first of the kind, then remove and insert its panel
2741 int i = 0;
2742 JPanel c = null;
2743 if (d instanceof ZDisplayable) {
2744 i = layer.getParent().indexOf((ZDisplayable)d);
2745 c = panel_zdispl;
2746 } else {
2747 i = layer.relativeIndexOf(d);
2748 if (d instanceof Profile) {
2749 c = panel_profiles;
2750 } else if (d instanceof Patch) {
2751 c = panel_patches;
2752 } else if (d instanceof DLabel) {
2753 c = panel_labels;
2756 if (null == c) return;
2757 DisplayablePanel dp = ht_panels.get(d);
2758 if (null == dp) return; // may be half-baked, wait
2759 c.remove(dp);
2760 c.add(dp, i); // java and its fabulous consistency
2761 // not enough! Utils.updateComponent(c);
2762 // So, cocktail:
2763 c.invalidate();
2764 c.validate();
2765 Utils.updateComponent(c);
2768 /** Repair possibly missing panels and other components by simply resetting the same Layer */
2769 public void repairGUI() {
2770 Layer layer = this.layer;
2771 this.layer = null;
2772 setLayer(layer);
2775 public void actionPerformed(final ActionEvent ae) {
2776 dispatcher.exec(new Runnable() { public void run() {
2778 String command = ae.getActionCommand();
2779 if (command.startsWith("Job")) {
2780 if (Utils.checkYN("Really cancel job?")) {
2781 project.getLoader().quitJob(command);
2782 repairGUI();
2784 return;
2785 } else if (command.equals("Move to top")) {
2786 if (null == active) return;
2787 canvas.setUpdateGraphics(true);
2788 layer.getParent().move(LayerSet.TOP, active);
2789 Display.repaint(layer.getParent(), active, 5);
2790 //Display.updatePanelIndex(layer, active);
2791 } else if (command.equals("Move up")) {
2792 if (null == active) return;
2793 canvas.setUpdateGraphics(true);
2794 layer.getParent().move(LayerSet.UP, active);
2795 Display.repaint(layer.getParent(), active, 5);
2796 //Display.updatePanelIndex(layer, active);
2797 } else if (command.equals("Move down")) {
2798 if (null == active) return;
2799 canvas.setUpdateGraphics(true);
2800 layer.getParent().move(LayerSet.DOWN, active);
2801 Display.repaint(layer.getParent(), active, 5);
2802 //Display.updatePanelIndex(layer, active);
2803 } else if (command.equals("Move to bottom")) {
2804 if (null == active) return;
2805 canvas.setUpdateGraphics(true);
2806 layer.getParent().move(LayerSet.BOTTOM, active);
2807 Display.repaint(layer.getParent(), active, 5);
2808 //Display.updatePanelIndex(layer, active);
2809 } else if (command.equals("Duplicate, link and send to next layer")) {
2810 duplicateLinkAndSendTo(active, 1, layer.getParent().next(layer));
2811 } else if (command.equals("Duplicate, link and send to previous layer")) {
2812 duplicateLinkAndSendTo(active, 0, layer.getParent().previous(layer));
2813 } else if (command.equals("Duplicate, link and send to...")) {
2814 // fix non-scrolling popup menu
2815 GenericDialog gd = new GenericDialog("Send to");
2816 gd.addMessage("Duplicate, link and send to...");
2817 String[] sl = new String[layer.getParent().size()];
2818 int next = 0;
2819 for (Iterator it = layer.getParent().getLayers().iterator(); it.hasNext(); ) {
2820 sl[next++] = project.findLayerThing(it.next()).toString();
2822 gd.addChoice("Layer: ", sl, sl[layer.getParent().indexOf(layer)]);
2823 gd.showDialog();
2824 if (gd.wasCanceled()) return;
2825 Layer la = layer.getParent().getLayer(gd.getNextChoiceIndex());
2826 if (layer == la) {
2827 Utils.showMessage("Can't duplicate, link and send to the same layer.");
2828 return;
2830 duplicateLinkAndSendTo(active, 0, la);
2831 } else if (-1 != command.indexOf("z = ")) {
2832 // this is an item from the "Duplicate, link and send to" menu of layer z's
2833 Layer target_layer = layer.getParent().getLayer(Double.parseDouble(command.substring(command.lastIndexOf(' ') +1)));
2834 Utils.log2("layer: __" +command.substring(command.lastIndexOf(' ') +1) + "__");
2835 if (null == target_layer) return;
2836 duplicateLinkAndSendTo(active, 0, target_layer);
2837 } else if (-1 != command.indexOf("z=")) {
2838 // WARNING the indexOf is very similar to the previous one
2839 // Send the linked group to the selected layer
2840 int iz = command.indexOf("z=")+2;
2841 Utils.log2("iz=" + iz + " other: " + command.indexOf(' ', iz+2));
2842 int end = command.indexOf(' ', iz);
2843 if (-1 == end) end = command.length();
2844 double lz = Double.parseDouble(command.substring(iz, end));
2845 Layer target = layer.getParent().getLayer(lz);
2846 HashSet hs = active.getLinkedGroup(new HashSet());
2847 layer.getParent().move(hs, active.getLayer(), target);
2848 } else if (command.equals("Unlink")) {
2849 if (null == active || active instanceof Patch) return;
2850 active.unlink();
2851 updateSelection();//selection.update();
2852 } else if (command.equals("Unlink from images")) {
2853 if (null == active) return;
2854 try {
2855 for (Displayable displ: selection.getSelected()) {
2856 displ.unlinkAll(Patch.class);
2858 updateSelection();//selection.update();
2859 } catch (Exception e) { IJError.print(e); }
2860 } else if (command.equals("Unlink slices")) {
2861 YesNoCancelDialog yn = new YesNoCancelDialog(frame, "Attention", "Really unlink all slices from each other?\nThere is no undo.");
2862 if (!yn.yesPressed()) return;
2863 final ArrayList<Patch> pa = ((Patch)active).getStackPatches();
2864 for (int i=pa.size()-1; i>0; i--) {
2865 pa.get(i).unlink(pa.get(i-1));
2867 } else if (command.equals("Send to next layer")) {
2868 Rectangle box = selection.getBox();
2869 try {
2870 // unlink Patch instances
2871 for (final Displayable displ : selection.getSelected()) {
2872 displ.unlinkAll(Patch.class);
2874 updateSelection();//selection.update();
2875 } catch (Exception e) { IJError.print(e); }
2876 //layer.getParent().moveDown(layer, active); // will repaint whatever appropriate layers
2877 selection.moveDown();
2878 repaint(layer.getParent(), box);
2879 } else if (command.equals("Send to previous layer")) {
2880 Rectangle box = selection.getBox();
2881 try {
2882 // unlink Patch instances
2883 for (final Displayable displ : selection.getSelected()) {
2884 displ.unlinkAll(Patch.class);
2886 updateSelection();//selection.update();
2887 } catch (Exception e) { IJError.print(e); }
2888 //layer.getParent().moveUp(layer, active); // will repaint whatever appropriate layers
2889 selection.moveUp();
2890 repaint(layer.getParent(), box);
2891 } else if (command.equals("Show centered")) {
2892 if (active == null) return;
2893 showCentered(active);
2894 } else if (command.equals("Delete...")) {
2896 if (null != active) {
2897 Displayable d = active;
2898 selection.remove(d);
2899 d.remove(true); // will repaint
2902 // remove all selected objects
2903 selection.deleteAll();
2904 } else if (command.equals("Color...")) {
2905 IJ.doCommand("Color Picker...");
2906 } else if (command.equals("Revert")) {
2907 if (null == active || active.getClass() != Patch.class) return;
2908 Patch p = (Patch)active;
2909 if (!p.revert()) {
2910 if (null == p.getOriginalPath()) Utils.log("No editions to save for patch " + p.getTitle() + " #" + p.getId());
2911 else Utils.log("Could not revert Patch " + p.getTitle() + " #" + p.getId());
2913 } else if (command.equals("Remove alpha mask")) {
2914 final ArrayList<Displayable> patches = selection.getSelected(Patch.class);
2915 if (patches.size() > 0) {
2916 Bureaucrat.createAndStart(new Worker.Task("Removing alpha mask" + (patches.size() > 1 ? "s" : "")) { public void exec() {
2917 final ArrayList<Future> jobs = new ArrayList<Future>();
2918 for (final Displayable d : patches) {
2919 final Patch p = (Patch) d;
2920 p.setAlphaMask(null);
2921 Future job = p.getProject().getLoader().regenerateMipMaps(p); // submit to queue
2922 if (null != job) jobs.add(job);
2924 // join all
2925 for (final Future job : jobs) try {
2926 job.get();
2927 } catch (Exception ie) {}
2928 }}, patches.get(0).getProject());
2930 } else if (command.equals("Undo")) {
2931 Bureaucrat.createAndStart(new Worker.Task("Undo") { public void exec() {
2932 layer.getParent().undoOneStep();
2933 Display.repaint(layer.getParent());
2934 }}, project);
2935 } else if (command.equals("Redo")) {
2936 Bureaucrat.createAndStart(new Worker.Task("Redo") { public void exec() {
2937 layer.getParent().redoOneStep();
2938 Display.repaint(layer.getParent());
2939 }}, project);
2940 } else if (command.equals("Transform")) {
2941 if (null == active) return;
2942 canvas.setTransforming(true);
2943 } else if (command.equals("Apply transform")) {
2944 if (null == active) return;
2945 canvas.setTransforming(false);
2946 } else if (command.equals("Apply transform propagating to last layer")) {
2947 if (selection.isTransforming()) {
2948 final java.util.List<Layer> layers = layer.getParent().getLayers();
2949 selection.applyAndPropagate(new HashSet<Layer>(layers.subList(layers.indexOf(Display.this.layer)+1, layers.size()))); // +1 to exclude current layer
2951 } else if (command.equals("Apply transform propagating to first layer")) {
2952 if (selection.isTransforming()) {
2953 final java.util.List<Layer> layers = layer.getParent().getLayers();
2954 selection.applyAndPropagate(new HashSet<Layer>(layers.subList(0, layers.indexOf(Display.this.layer))));
2956 } else if (command.equals("Cancel transform")) {
2957 if (null == active) return;
2958 canvas.cancelTransform();
2959 } else if (command.equals("Specify transform...")) {
2960 if (null == active) return;
2961 selection.specify();
2962 } else if (command.equals("Hide all but images")) {
2963 ArrayList<Class> type = new ArrayList<Class>();
2964 type.add(Patch.class);
2965 selection.removeAll(layer.getParent().hideExcept(type, false));
2966 Display.update(layer.getParent(), false);
2967 } else if (command.equals("Unhide all")) {
2968 layer.getParent().setAllVisible(false);
2969 Display.update(layer.getParent(), false);
2970 } else if (command.startsWith("Hide all ")) {
2971 String type = command.substring(9, command.length() -1); // skip the ending plural 's'
2972 type = type.substring(0, 1).toUpperCase() + type.substring(1);
2973 selection.removeAll(layer.getParent().setVisible(type, false, true));
2974 } else if (command.startsWith("Unhide all ")) {
2975 String type = command.substring(11, command.length() -1); // skip the ending plural 's'
2976 type = type.substring(0, 1).toUpperCase() + type.substring(1);
2977 layer.getParent().setVisible(type, true, true);
2978 } else if (command.equals("Hide deselected")) {
2979 hideDeselected(0 != (ActionEvent.ALT_MASK & ae.getModifiers()));
2980 } else if (command.equals("Hide deselected except images")) {
2981 hideDeselected(true);
2982 } else if (command.equals("Hide selected")) {
2983 selection.setVisible(false); // TODO should deselect them too? I don't think so.
2984 } else if (command.equals("Resize canvas/LayerSet...")) {
2985 resizeCanvas();
2986 } else if (command.equals("Autoresize canvas/LayerSet")) {
2987 layer.getParent().setMinimumDimensions();
2988 } else if (command.equals("Import image")) {
2989 importImage();
2990 } else if (command.equals("Import next image")) {
2991 importNextImage();
2992 } else if (command.equals("Import stack...")) {
2993 Display.this.getLayerSet().addLayerContentStep(layer);
2994 Rectangle sr = getCanvas().getSrcRect();
2995 Bureaucrat burro = project.getLoader().importStack(layer, sr.x + sr.width/2, sr.y + sr.height/2, null, true, null);
2996 burro.addPostTask(new Runnable() { public void run() {
2997 Display.this.getLayerSet().addLayerContentStep(layer);
2998 }});
2999 } else if (command.equals("Import grid...")) {
3000 Display.this.getLayerSet().addLayerContentStep(layer);
3001 Bureaucrat burro = project.getLoader().importGrid(layer);
3002 burro.addPostTask(new Runnable() { public void run() {
3003 Display.this.getLayerSet().addLayerContentStep(layer);
3004 }});
3005 } else if (command.equals("Import sequence as grid...")) {
3006 Display.this.getLayerSet().addLayerContentStep(layer);
3007 Bureaucrat burro = project.getLoader().importSequenceAsGrid(layer);
3008 burro.addPostTask(new Runnable() { public void run() {
3009 Display.this.getLayerSet().addLayerContentStep(layer);
3010 }});
3011 } else if (command.equals("Import from text file...")) {
3012 Display.this.getLayerSet().addLayerContentStep(layer);
3013 Bureaucrat burro = project.getLoader().importImages(layer);
3014 burro.addPostTask(new Runnable() { public void run() {
3015 Display.this.getLayerSet().addLayerContentStep(layer);
3016 }});
3017 } else if (command.equals("Import labels as arealists...")) {
3018 Display.this.getLayerSet().addChangeTreesStep();
3019 Bureaucrat burro = project.getLoader().importLabelsAsAreaLists(layer, null, Double.MAX_VALUE, 0, 0.4f, false);
3020 burro.addPostTask(new Runnable() { public void run() {
3021 Display.this.getLayerSet().addChangeTreesStep();
3022 }});
3023 } else if (command.equals("Make flat image...")) {
3024 // if there's a ROI, just use that as cropping rectangle
3025 Rectangle srcRect = null;
3026 Roi roi = canvas.getFakeImagePlus().getRoi();
3027 if (null != roi) {
3028 srcRect = roi.getBounds();
3029 } else {
3030 // otherwise, whatever is visible
3031 //srcRect = canvas.getSrcRect();
3032 // The above is confusing. That is what ROIs are for. So paint all:
3033 srcRect = new Rectangle(0, 0, (int)Math.ceil(layer.getParent().getLayerWidth()), (int)Math.ceil(layer.getParent().getLayerHeight()));
3035 double scale = 1.0;
3036 final String[] types = new String[]{"8-bit grayscale", "RGB Color"};
3037 int the_type = ImagePlus.GRAY8;
3038 final GenericDialog gd = new GenericDialog("Choose", frame);
3039 gd.addSlider("Scale: ", 1, 100, 100);
3040 gd.addChoice("Type: ", types, types[0]);
3041 if (layer.getParent().size() > 1) {
3043 String[] layers = new String[layer.getParent().size()];
3044 int i = 0;
3045 for (Iterator it = layer.getParent().getLayers().iterator(); it.hasNext(); ) {
3046 layers[i] = layer.getProject().findLayerThing((Layer)it.next()).toString();
3047 i++;
3049 int i_layer = layer.getParent().indexOf(layer);
3050 gd.addChoice("Start: ", layers, layers[i_layer]);
3051 gd.addChoice("End: ", layers, layers[i_layer]);
3053 Utils.addLayerRangeChoices(Display.this.layer, gd); /// $#%! where are my lisp macros
3054 gd.addCheckbox("Include non-empty layers only", true);
3056 gd.addMessage("Background color:");
3057 Utils.addRGBColorSliders(gd, Color.black);
3058 gd.addCheckbox("Best quality", false);
3059 gd.addMessage("");
3060 gd.addCheckbox("Save to file", false);
3061 gd.addCheckbox("Save for web", false);
3062 gd.showDialog();
3063 if (gd.wasCanceled()) return;
3064 scale = gd.getNextNumber() / 100;
3065 the_type = (0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY8 : ImagePlus.COLOR_RGB);
3066 if (Double.isNaN(scale) || scale <= 0.0) {
3067 Utils.showMessage("Invalid scale.");
3068 return;
3070 Layer[] layer_array = null;
3071 boolean non_empty_only = false;
3072 if (layer.getParent().size() > 1) {
3073 non_empty_only = gd.getNextBoolean();
3074 int i_start = gd.getNextChoiceIndex();
3075 int i_end = gd.getNextChoiceIndex();
3076 ArrayList al = new ArrayList();
3077 ArrayList al_zd = layer.getParent().getZDisplayables();
3078 ZDisplayable[] zd = new ZDisplayable[al_zd.size()];
3079 al_zd.toArray(zd);
3080 for (int i=i_start, j=0; i <= i_end; i++, j++) {
3081 Layer la = layer.getParent().getLayer(i);
3082 if (!la.isEmpty() || !non_empty_only) al.add(la); // checks both the Layer and the ZDisplayable objects in the parent LayerSet
3084 if (0 == al.size()) {
3085 Utils.showMessage("All layers are empty!");
3086 return;
3088 layer_array = new Layer[al.size()];
3089 al.toArray(layer_array);
3090 } else {
3091 layer_array = new Layer[]{Display.this.layer};
3093 final Color background = new Color((int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
3094 final boolean quality = gd.getNextBoolean();
3095 final boolean save_to_file = gd.getNextBoolean();
3096 final boolean save_for_web = gd.getNextBoolean();
3097 // in its own thread
3098 if (save_for_web) project.getLoader().makePrescaledTiles(layer_array, Patch.class, srcRect, scale, c_alphas, the_type);
3099 else project.getLoader().makeFlatImage(layer_array, srcRect, scale, c_alphas, the_type, save_to_file, quality, background);
3101 } else if (command.equals("Lock")) {
3102 selection.setLocked(true);
3103 } else if (command.equals("Unlock")) {
3104 selection.setLocked(false);
3105 } else if (command.equals("Properties...")) {
3106 active.adjustProperties();
3107 updateSelection();
3108 } else if (command.equals("Cancel alignment")) {
3109 layer.getParent().cancelAlign();
3110 } else if (command.equals("Align with landmarks")) {
3111 layer.getParent().applyAlign(false);
3112 } else if (command.equals("Align and register")) {
3113 layer.getParent().applyAlign(true);
3114 } else if (command.equals("Align using profiles")) {
3115 if (!selection.contains(Profile.class)) {
3116 Utils.showMessage("No profiles are selected.");
3117 return;
3119 // ask for range of layers
3120 final GenericDialog gd = new GenericDialog("Choose range");
3121 Utils.addLayerRangeChoices(Display.this.layer, gd);
3122 gd.showDialog();
3123 if (gd.wasCanceled()) return;
3124 Layer la_start = layer.getParent().getLayer(gd.getNextChoiceIndex());
3125 Layer la_end = layer.getParent().getLayer(gd.getNextChoiceIndex());
3126 if (la_start == la_end) {
3127 Utils.showMessage("Need at least two layers.");
3128 return;
3130 if (selection.isLocked()) {
3131 Utils.showMessage("There are locked objects.");
3132 return;
3134 layer.getParent().startAlign(Display.this);
3135 layer.getParent().applyAlign(la_start, la_end, selection);
3136 } else if (command.equals("Align stack slices")) {
3137 if (getActive() instanceof Patch) {
3138 final Patch slice = (Patch)getActive();
3139 if (slice.isStack()) {
3140 // check linked group
3141 final HashSet hs = slice.getLinkedGroup(new HashSet());
3142 for (Iterator it = hs.iterator(); it.hasNext(); ) {
3143 if (it.next().getClass() != Patch.class) {
3144 Utils.showMessage("Images are linked to other objects, can't proceed to cross-correlate them."); // labels should be fine, need to check that
3145 return;
3148 final LayerSet ls = slice.getLayerSet();
3149 final HashSet<Displayable> linked = slice.getLinkedGroup(null);
3150 ls.addTransformStep(linked);
3151 Bureaucrat burro = Registration.registerStackSlices((Patch)getActive()); // will repaint
3152 burro.addPostTask(new Runnable() { public void run() {
3153 // The current state when done
3154 ls.addTransformStep(linked);
3155 }});
3156 } else {
3157 Utils.log("Align stack slices: selected image is not part of a stack.");
3160 } else if (command.equals("Align layers")) {
3161 final Layer la = layer;; // caching, since scroll wheel may change it
3162 la.getParent().addTransformStep(la);
3163 Bureaucrat burro = AlignTask.alignLayersLinearlyTask( la );
3164 burro.addPostTask(new Runnable() { public void run() {
3165 la.getParent().addTransformStep(la);
3166 }});
3167 } else if (command.equals("Align multi-layer mosaic")) {
3168 final Layer la = layer; // caching, since scroll wheel may change it
3169 la.getParent().addTransformStep();
3170 Bureaucrat burro = AlignTask.alignMultiLayerMosaicTask( la );
3171 burro.addPostTask(new Runnable() { public void run() {
3172 la.getParent().addTransformStep();
3173 }});
3174 } else if (command.equals("Properties ...")) { // NOTE the space before the dots, to distinguish from the "Properties..." command that works on Displayable objects.
3175 GenericDialog gd = new GenericDialog("Properties", Display.this.frame);
3176 //gd.addNumericField("layer_scroll_step: ", this.scroll_step, 0);
3177 gd.addSlider("layer_scroll_step: ", 1, layer.getParent().size(), Display.this.scroll_step);
3178 gd.addChoice("snapshots_mode", LayerSet.snapshot_modes, LayerSet.snapshot_modes[layer.getParent().getSnapshotsMode()]);
3179 gd.addCheckbox("prefer_snapshots_quality", layer.getParent().snapshotsQuality());
3180 Loader lo = getProject().getLoader();
3181 boolean using_mipmaps = lo.isMipMapsEnabled();
3182 gd.addCheckbox("enable_mipmaps", using_mipmaps);
3183 String preprocessor = project.getLoader().getPreprocessor();
3184 gd.addStringField("image_preprocessor: ", null == preprocessor ? "" : preprocessor);
3185 gd.addCheckbox("enable_layer_pixels virtualization", layer.getParent().isPixelsVirtualizationEnabled());
3186 double max = layer.getParent().getLayerWidth() < layer.getParent().getLayerHeight() ? layer.getParent().getLayerWidth() : layer.getParent().getLayerHeight();
3187 gd.addSlider("max_dimension of virtualized layer pixels: ", 0, max, layer.getParent().getPixelsMaxDimension());
3188 // --------
3189 gd.showDialog();
3190 if (gd.wasCanceled()) return;
3191 // --------
3192 int sc = (int) gd.getNextNumber();
3193 if (sc < 1) sc = 1;
3194 Display.this.scroll_step = sc;
3195 updateInDatabase("scroll_step");
3197 layer.getParent().setSnapshotsMode(gd.getNextChoiceIndex());
3198 layer.getParent().setSnapshotsQuality(gd.getNextBoolean());
3200 boolean generate_mipmaps = gd.getNextBoolean();
3201 if (using_mipmaps && generate_mipmaps) {
3202 // nothing changed
3203 } else {
3204 if (using_mipmaps) { // and !generate_mipmaps
3205 lo.flushMipMaps(true);
3206 } else {
3207 // not using mipmaps before, and true == generate_mipmaps
3208 lo.generateMipMaps(layer.getParent().getDisplayables(Patch.class));
3212 final String prepro = gd.getNextString();
3213 if (!project.getLoader().setPreprocessor(prepro.trim())) {
3214 Utils.showMessage("Could NOT set the preprocessor to " + prepro);
3217 layer.getParent().setPixelsVirtualizationEnabled(gd.getNextBoolean());
3218 layer.getParent().setPixelsMaxDimension((int)gd.getNextNumber());
3219 } else if (command.equals("Search...")) {
3220 new Search();
3221 } else if (command.equals("Select all")) {
3222 selection.selectAll();
3223 repaint(Display.this.layer, selection.getBox(), 0);
3224 } else if (command.equals("Select none")) {
3225 Rectangle box = selection.getBox();
3226 selection.clear();
3227 repaint(Display.this.layer, box, 0);
3228 } else if (command.equals("Restore selection")) {
3229 selection.restore();
3230 } else if (command.equals("Select under ROI")) {
3231 Roi roi = canvas.getFakeImagePlus().getRoi();
3232 if (null == roi) return;
3233 selection.selectAll(roi, true);
3234 } else if (command.equals("Merge")) {
3235 ArrayList al_sel = selection.getSelected();
3236 // put active at the beginning, to work as the base on which other's will get merged
3237 al_sel.remove(Display.this.active);
3238 al_sel.add(0, Display.this.active);
3239 AreaList ali = AreaList.merge(al_sel);
3240 if (null != ali) {
3241 // remove all but the first from the selection
3242 for (int i=1; i<al_sel.size(); i++) {
3243 Object ob = al_sel.get(i);
3244 if (ob.getClass() == AreaList.class) {
3245 selection.remove((Displayable)ob);
3248 selection.updateTransform(ali);
3249 repaint(ali.getLayerSet(), ali, 0);
3251 } else if (command.equals("Identify...")) {
3252 // for pipes only for now
3253 if (!(active instanceof Pipe)) return;
3254 ini.trakem2.vector.Compare.findSimilar((Pipe)active);
3255 } else if (command.equals("Identify with axes...")) {
3256 if (!(active instanceof Pipe)) return;
3257 if (Project.getProjects().size() < 2) {
3258 Utils.showMessage("You need at least two projects open:\n-A reference project\n-The current project with the pipe to identify");
3259 return;
3261 ini.trakem2.vector.Compare.findSimilarWithAxes((Pipe)active);
3262 } else if (command.equals("View orthoslices")) {
3263 if (!(active instanceof Patch)) return;
3264 Display3D.showOrthoslices(((Patch)active));
3265 } else if (command.equals("View volume")) {
3266 if (!(active instanceof Patch)) return;
3267 Display3D.showVolume(((Patch)active));
3268 } else if (command.equals("Show in 3D")) {
3269 for (Iterator it = selection.getSelected(ZDisplayable.class).iterator(); it.hasNext(); ) {
3270 ZDisplayable zd = (ZDisplayable)it.next();
3271 Display3D.show(zd.getProject().findProjectThing(zd));
3273 // handle profile lists ...
3274 HashSet hs = new HashSet();
3275 for (Iterator it = selection.getSelected(Profile.class).iterator(); it.hasNext(); ) {
3276 Displayable d = (Displayable)it.next();
3277 ProjectThing profile_list = (ProjectThing)d.getProject().findProjectThing(d).getParent();
3278 if (!hs.contains(profile_list)) {
3279 Display3D.show(profile_list);
3280 hs.add(profile_list);
3283 } else if (command.equals("Snap")) {
3284 if (!(active instanceof Patch)) return;
3285 StitchingTEM.snap(getActive(), Display.this);
3286 } else if (command.equals("Blend")) {
3287 HashSet<Patch> patches = new HashSet<Patch>();
3288 for (final Displayable d : selection.getSelected()) {
3289 if (d.getClass() == Patch.class) patches.add((Patch)d);
3291 if (patches.size() > 1) {
3292 GenericDialog gd = new GenericDialog("Blending");
3293 gd.addCheckbox("Respect current alpha mask", true);
3294 gd.showDialog();
3295 if (gd.wasCanceled()) return;
3296 Blending.blend(patches, gd.getNextBoolean());
3297 } else {
3298 IJ.log("Please select more than one overlapping image.");
3300 } else if (command.equals("Montage")) {
3301 if (!(active instanceof Patch)) {
3302 Utils.showMessage("Please select only images.");
3303 return;
3305 final Set<Displayable> affected = new HashSet<Displayable>(selection.getAffected());
3306 for (final Displayable d : affected)
3307 if (d.isLinked()) {
3308 Utils.showMessage( "You cannot montage linked objects." );
3309 return;
3311 // make an undo step!
3312 final LayerSet ls = layer.getParent();
3313 ls.addTransformStep(affected);
3314 Bureaucrat burro = AlignTask.alignSelectionTask( selection );
3315 burro.addPostTask(new Runnable() { public void run() {
3316 ls.addTransformStep(affected);
3317 }});
3318 } else if (command.equals("Lens correction")) {
3319 final Layer la = layer;
3320 la.getParent().addDataEditStep(new HashSet<Displayable>(la.getParent().getDisplayables()));
3321 Bureaucrat burro = DistortionCorrectionTask.correctDistortionFromSelection( selection );
3322 burro.addPostTask(new Runnable() { public void run() {
3323 // no means to know which where modified and from which layers!
3324 la.getParent().addDataEditStep(new HashSet<Displayable>(la.getParent().getDisplayables()));
3325 }});
3326 } else if (command.equals("Link images...")) {
3327 GenericDialog gd = new GenericDialog("Options");
3328 gd.addMessage("Linking images to images (within their own layer only):");
3329 String[] options = {"all images to all images", "each image with any other overlapping image"};
3330 gd.addChoice("Link: ", options, options[1]);
3331 String[] options2 = {"selected images only", "all images in this layer", "all images in all layers"};
3332 gd.addChoice("Apply to: ", options2, options2[0]);
3333 gd.showDialog();
3334 if (gd.wasCanceled()) return;
3335 Layer lay = layer;
3336 final HashSet<Displayable> ds = new HashSet<Displayable>(lay.getParent().getDisplayables());
3337 lay.getParent().addDataEditStep(ds);
3338 boolean overlapping_only = 1 == gd.getNextChoiceIndex();
3339 switch (gd.getNextChoiceIndex()) {
3340 case 0:
3341 Patch.crosslink(selection.getSelected(Patch.class), overlapping_only);
3342 break;
3343 case 1:
3344 Patch.crosslink(lay.getDisplayables(Patch.class), overlapping_only);
3345 break;
3346 case 2:
3347 for (final Layer la : lay.getParent().getLayers()) {
3348 Patch.crosslink(la.getDisplayables(Patch.class), overlapping_only);
3350 break;
3352 lay.getParent().addDataEditStep(ds);
3353 } else if (command.equals("Calibration...")) {
3354 try {
3355 IJ.run(canvas.getFakeImagePlus(), "Properties...", "");
3356 } catch (RuntimeException re) {
3357 Utils.log2("Calibration dialog canceled.");
3359 } else if (command.equals("Enhance contrast (selected images)...")) {
3360 final Layer la = layer;
3361 final HashSet<Displayable> ds = new HashSet<Displayable>(la.getParent().getDisplayables());
3362 la.getParent().addDataEditStep(ds);
3363 ArrayList al = selection.getSelected(Patch.class);
3364 Bureaucrat burro = getProject().getLoader().homogenizeContrast(al);
3365 burro.addPostTask(new Runnable() { public void run() {
3366 la.getParent().addDataEditStep(ds);
3367 }});
3368 } else if (command.equals("Enhance contrast layer-wise...")) {
3369 // ask for range of layers
3370 final GenericDialog gd = new GenericDialog("Choose range");
3371 Utils.addLayerRangeChoices(Display.this.layer, gd);
3372 gd.showDialog();
3373 if (gd.wasCanceled()) return;
3374 java.util.List list = layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1); // exclusive end
3375 Layer[] la = new Layer[list.size()];
3376 list.toArray(la);
3377 final HashSet<Displayable> ds = new HashSet<Displayable>();
3378 for (final Layer l : la) ds.addAll(l.getDisplayables(Patch.class));
3379 getLayerSet().addDataEditStep(ds);
3380 Bureaucrat burro = project.getLoader().homogenizeContrast(la);
3381 burro.addPostTask(new Runnable() { public void run() {
3382 getLayerSet().addDataEditStep(ds);
3383 }});
3384 } else if (command.equals("Set Min and Max layer-wise...")) {
3385 Displayable active = getActive();
3386 double min = 0;
3387 double max = 0;
3388 if (null != active && active.getClass() == Patch.class) {
3389 min = ((Patch)active).getMin();
3390 max = ((Patch)active).getMax();
3392 final GenericDialog gd = new GenericDialog("Min and Max");
3393 gd.addMessage("Set min and max to all images in the layer range");
3394 Utils.addLayerRangeChoices(Display.this.layer, gd);
3395 gd.addNumericField("min: ", min, 2);
3396 gd.addNumericField("max: ", max, 2);
3397 gd.showDialog();
3398 if (gd.wasCanceled()) return;
3400 min = gd.getNextNumber();
3401 max = gd.getNextNumber();
3402 ArrayList<Displayable> al = new ArrayList<Displayable>();
3403 for (final Layer la : layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1)) { // exclusive end
3404 al.addAll(la.getDisplayables(Patch.class));
3406 final HashSet<Displayable> ds = new HashSet<Displayable>(al);
3407 getLayerSet().addDataEditStep(ds);
3408 Bureaucrat burro = project.getLoader().setMinAndMax(al, min, max);
3409 burro.addPostTask(new Runnable() { public void run() {
3410 getLayerSet().addDataEditStep(ds);
3411 }});
3412 } else if (command.equals("Set Min and Max (selected images)...")) {
3413 Displayable active = getActive();
3414 double min = 0;
3415 double max = 0;
3416 if (null != active && active.getClass() == Patch.class) {
3417 min = ((Patch)active).getMin();
3418 max = ((Patch)active).getMax();
3420 final GenericDialog gd = new GenericDialog("Min and Max");
3421 gd.addMessage("Set min and max to all selected images");
3422 gd.addNumericField("min: ", min, 2);
3423 gd.addNumericField("max: ", max, 2);
3424 gd.showDialog();
3425 if (gd.wasCanceled()) return;
3427 min = gd.getNextNumber();
3428 max = gd.getNextNumber();
3429 final HashSet<Displayable> ds = new HashSet<Displayable>(selection.getSelected(Patch.class));
3430 getLayerSet().addDataEditStep(ds);
3431 Bureaucrat burro = project.getLoader().setMinAndMax(selection.getSelected(Patch.class), min, max);
3432 burro.addPostTask(new Runnable() { public void run() {
3433 getLayerSet().addDataEditStep(ds);
3434 }});
3435 } else if (command.equals("Duplicate")) {
3436 // only Patch and DLabel, i.e. Layer-only resident objects that don't exist in the Project Tree
3437 final HashSet<Class> accepted = new HashSet<Class>();
3438 accepted.add(Patch.class);
3439 accepted.add(DLabel.class);
3440 final ArrayList<Displayable> originals = new ArrayList<Displayable>();
3441 final ArrayList<Displayable> selected = selection.getSelected();
3442 for (final Displayable d : selected) {
3443 if (accepted.contains(d.getClass())) {
3444 originals.add(d);
3447 if (originals.size() > 0) {
3448 getLayerSet().addChangeTreesStep();
3449 for (final Displayable d : originals) {
3450 d.getLayer().add(d.clone());
3452 getLayerSet().addChangeTreesStep();
3453 } else if (selected.size() > 0) {
3454 Utils.log("Can only duplicate images and text labels.\nDuplicate *other* objects in the Project Tree.\n");
3456 } else if (command.equals("Create subproject")) {
3457 Roi roi = canvas.getFakeImagePlus().getRoi();
3458 if (null == roi) return; // the menu item is not active unless there is a ROI
3459 Layer first, last;
3460 if (1 == layer.getParent().size()) {
3461 first = last = layer;
3462 } else {
3463 GenericDialog gd = new GenericDialog("Choose layer range");
3464 Utils.addLayerRangeChoices(layer, gd);
3465 gd.showDialog();
3466 if (gd.wasCanceled()) return;
3467 first = layer.getParent().getLayer(gd.getNextChoiceIndex());
3468 last = layer.getParent().getLayer(gd.getNextChoiceIndex());
3469 Utils.log2("first, last: " + first + ", " + last);
3471 Project sub = getProject().createSubproject(roi.getBounds(), first, last);
3472 final LayerSet subls = sub.getRootLayerSet();
3473 final Display d = new Display(sub, subls.getLayer(0));
3474 SwingUtilities.invokeLater(new Runnable() { public void run() {
3475 d.canvas.showCentered(new Rectangle(0, 0, (int)subls.getLayerWidth(), (int)subls.getLayerHeight()));
3476 }});
3477 } else if (command.startsWith("Arealists as labels")) {
3478 GenericDialog gd = new GenericDialog("Export labels");
3479 gd.addSlider("Scale: ", 1, 100, 100);
3480 final String[] options = {"All area list", "Selected area lists"};
3481 gd.addChoice("Export: ", options, options[0]);
3482 Utils.addLayerRangeChoices(layer, gd);
3483 gd.addCheckbox("Visible only", true);
3484 gd.showDialog();
3485 if (gd.wasCanceled()) return;
3486 final float scale = (float)(gd.getNextNumber() / 100);
3487 java.util.List al = 0 == gd.getNextChoiceIndex() ? layer.getParent().getZDisplayables(AreaList.class) : selection.getSelected(AreaList.class);
3488 if (null == al) {
3489 Utils.log("No area lists found to export.");
3490 return;
3492 // 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?
3493 al = (java.util.List<Displayable>) al;
3495 int first = gd.getNextChoiceIndex();
3496 int last = gd.getNextChoiceIndex();
3497 boolean visible_only = gd.getNextBoolean();
3498 if (-1 != command.indexOf("(amira)")) {
3499 AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, true, true);
3500 } else if (-1 != command.indexOf("(tif)")) {
3501 AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, false, false);
3503 } else if (command.equals("Project properties...")) {
3504 project.adjustProperties();
3505 } else if (command.equals("Release memory...")) {
3506 Bureaucrat.createAndStart(new Worker("Releasing memory") {
3507 public void run() {
3508 startedWorking();
3509 try {
3510 GenericDialog gd = new GenericDialog("Release Memory");
3511 int max = (int)(IJ.maxMemory() / 1000000);
3512 gd.addSlider("Megabytes: ", 0, max, max/2);
3513 gd.showDialog();
3514 if (!gd.wasCanceled()) {
3515 int n_mb = (int)gd.getNextNumber();
3516 project.getLoader().releaseToFit((long)n_mb*1000000);
3518 } catch (Throwable e) {
3519 IJError.print(e);
3520 } finally {
3521 finishedWorking();
3524 }, project);
3525 } else if (command.equals("Flush image cache")) {
3526 Loader.releaseAllCaches();
3527 } else if (command.equals("Regenerate all mipmaps")) {
3528 for (final Displayable d : getLayerSet().getDisplayables(Patch.class)) {
3529 d.getProject().getLoader().regenerateMipMaps((Patch) d);
3531 } else {
3532 Utils.log2("Display: don't know what to do with command " + command);
3534 }});
3537 /** Update in all displays the Transform for the given Displayable if it's selected. */
3538 static public void updateTransform(final Displayable displ) {
3539 for (final Display d : al_displays) {
3540 if (d.selection.contains(displ)) d.selection.updateTransform(displ);
3544 /** Order the profiles of the parent profile_list by Z order, and fix the ProjectTree.*/
3546 private void fixZOrdering(Profile profile) {
3547 ProjectThing thing = project.findProjectThing(profile);
3548 if (null == thing) {
3549 Utils.log2("Display.fixZOrdering: null thing?");
3550 return;
3552 ((ProjectThing)thing.getParent()).fixZOrdering();
3553 project.getProjectTree().updateList(thing.getParent());
3557 /** The number of layers to scroll through with the wheel; 1 by default.*/
3558 public int getScrollStep() { return this.scroll_step; }
3560 public void setScrollStep(int scroll_step) {
3561 if (scroll_step < 1) scroll_step = 1;
3562 this.scroll_step = scroll_step;
3563 updateInDatabase("scroll_step");
3566 protected Bureaucrat importImage() {
3567 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
3568 public void run() {
3569 startedWorking();
3570 try {
3573 Rectangle srcRect = canvas.getSrcRect();
3574 int x = srcRect.x + srcRect.width / 2;
3575 int y = srcRect.y + srcRect.height/ 2;
3576 Patch p = project.getLoader().importImage(project, x, y);
3577 if (null == p) {
3578 finishedWorking();
3579 Utils.showMessage("Could not open the image.");
3580 return;
3583 Display.this.getLayerSet().addLayerContentStep(layer);
3585 layer.add(p); // will add it to the proper Displays
3587 Display.this.getLayerSet().addLayerContentStep(layer);
3590 } catch (Exception e) {
3591 IJError.print(e);
3593 finishedWorking();
3596 return Bureaucrat.createAndStart(worker, getProject());
3599 protected Bureaucrat importNextImage() {
3600 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
3601 public void run() {
3602 startedWorking();
3603 try {
3605 Rectangle srcRect = canvas.getSrcRect();
3606 int x = srcRect.x + srcRect.width / 2;// - imp.getWidth() / 2;
3607 int y = srcRect.y + srcRect.height/ 2;// - imp.getHeight()/ 2;
3608 Patch p = project.getLoader().importNextImage(project, x, y);
3609 if (null == p) {
3610 Utils.showMessage("Could not open next image.");
3611 finishedWorking();
3612 return;
3615 Display.this.getLayerSet().addLayerContentStep(layer);
3617 layer.add(p); // will add it to the proper Displays
3619 Display.this.getLayerSet().addLayerContentStep(layer);
3621 } catch (Exception e) {
3622 IJError.print(e);
3624 finishedWorking();
3627 return Bureaucrat.createAndStart(worker, getProject());
3631 /** Make the given channel have the given alpha (transparency). */
3632 public void setChannel(int c, float alpha) {
3633 int a = (int)(255 * alpha);
3634 int l = (c_alphas&0xff000000)>>24;
3635 int r = (c_alphas&0xff0000)>>16;
3636 int g = (c_alphas&0xff00)>>8;
3637 int b = c_alphas&0xff;
3638 switch (c) {
3639 case Channel.MONO:
3640 // all to the given alpha
3641 c_alphas = (l<<24) + (r<<16) + (g<<8) + b; // parenthesis are NECESSARY
3642 break;
3643 case Channel.RED:
3644 // modify only the red
3645 c_alphas = (l<<24) + (a<<16) + (g<<8) + b;
3646 break;
3647 case Channel.GREEN:
3648 c_alphas = (l<<24) + (r<<16) + (a<<8) + b;
3649 break;
3650 case Channel.BLUE:
3651 c_alphas = (l<<24) + (r<<16) + (g<<8) + a;
3652 break;
3654 //Utils.log2("c_alphas: " + c_alphas);
3655 //canvas.setUpdateGraphics(true);
3656 canvas.repaint(true);
3657 updateInDatabase("c_alphas");
3660 /** Set the channel as active and the others as inactive. */
3661 public void setActiveChannel(Channel channel) {
3662 for (int i=0; i<4; i++) {
3663 if (channel != channels[i]) channels[i].setActive(false);
3664 else channel.setActive(true);
3666 Utils.updateComponent(panel_channels);
3667 transp_slider.setValue((int)(channel.getAlpha() * 100));
3670 public int getDisplayChannelAlphas() { return c_alphas; }
3672 // rename this method and the getDisplayChannelAlphas ! They sound the same!
3673 public int getChannelAlphas() {
3674 return ((int)(channels[0].getAlpha() * 255)<<24) + ((int)(channels[1].getAlpha() * 255)<<16) + ((int)(channels[2].getAlpha() * 255)<<8) + (int)(channels[3].getAlpha() * 255);
3677 public int getChannelAlphasState() {
3678 return ((channels[0].isSelected() ? 255 : 0)<<24)
3679 + ((channels[1].isSelected() ? 255 : 0)<<16)
3680 + ((channels[2].isSelected() ? 255 : 0)<<8)
3681 + (channels[3].isSelected() ? 255 : 0);
3684 /** Show the layer in the front Display, or in a new Display if the front Display is showing a layer from a different LayerSet. */
3685 static public void showFront(final Layer layer) {
3686 Display display = front;
3687 if (null == display || display.layer.getParent() != layer.getParent()) {
3688 display = new Display(layer.getProject(), layer, null); // gets set to front
3689 } else {
3690 display.setLayer(layer);
3694 /** Show the given Displayable centered and selected. If select is false, the selection is cleared. */
3695 static public void showCentered(Layer layer, Displayable displ, boolean select, boolean shift_down) {
3696 // see if the given layer belongs to the layer set being displayed
3697 Display display = front; // to ensure thread consistency to some extent
3698 if (null == display || display.layer.getParent() != layer.getParent()) {
3699 display = new Display(layer.getProject(), layer, displ); // gets set to front
3700 } else if (display.layer != layer) {
3701 display.setLayer(layer);
3703 if (select) {
3704 if (!shift_down) display.selection.clear();
3705 display.selection.add(displ);
3706 } else {
3707 display.selection.clear();
3709 display.showCentered(displ);
3712 private final void showCentered(final Displayable displ) {
3713 if (null == displ) return;
3714 SwingUtilities.invokeLater(new Runnable() { public void run() {
3715 displ.setVisible(true);
3716 Rectangle box = displ.getBoundingBox();
3717 if (0 == box.width || 0 == box.height) {
3718 box.width = (int)layer.getLayerWidth();
3719 box.height = (int)layer.getLayerHeight();
3721 canvas.showCentered(box);
3722 scrollToShow(displ);
3723 if (displ instanceof ZDisplayable) {
3724 // scroll to first layer that has a point
3725 ZDisplayable zd = (ZDisplayable)displ;
3726 setLayer(zd.getFirstLayer());
3728 }});
3731 /** Listen to interesting updates, such as the ColorPicker and updates to Patch objects. */
3732 public void imageUpdated(ImagePlus updated) {
3733 // detect ColorPicker WARNING this will work even if the Display is not the window immediately active under the color picker.
3734 if (this == front && updated instanceof ij.plugin.ColorPicker) {
3735 if (null != active && project.isInputEnabled()) {
3736 selection.setColor(Toolbar.getForegroundColor());
3737 Display.repaint(front.layer, selection.getBox(), 0);
3739 return;
3741 // $%#@!! LUT changes don't set the image as changed
3742 //if (updated instanceof PatchStack) {
3743 // updated.changes = 1
3746 //Utils.log2("imageUpdated: " + updated + " " + updated.getClass());
3748 /* // never gets called (?)
3749 // the above is overkill. Instead:
3750 if (updated instanceof PatchStack) {
3751 Patch p = ((PatchStack)updated).getCurrentPatch();
3752 ImageProcessor ip = updated.getProcessor();
3753 p.setMinAndMax(ip.getMin(), ip.getMax());
3754 Utils.log2("setting min and max: " + ip.getMin() + ", " + ip.getMax());
3755 project.getLoader().decacheAWT(p.getId()); // including level 0, which will be editable
3756 // on repaint, it will be recreated
3757 //((PatchStack)updated).decacheAll(); // so that it will repaint with a newly created image
3761 // detect LUT changes: DONE at PatchStack, which is the active (virtual) image
3762 //Utils.log2("calling decache for " + updated);
3763 //getProject().getLoader().decache(updated);
3766 public void imageClosed(ImagePlus imp) {}
3767 public void imageOpened(ImagePlus imp) {}
3769 /** Release memory captured by the offscreen images */
3770 static public void flushAll() {
3771 for (final Display d : al_displays) {
3772 d.canvas.flush();
3774 //System.gc();
3775 Thread.yield();
3778 /** Can be null. */
3779 static public Display getFront() {
3780 return front;
3783 static public void setCursorToAll(final Cursor c) {
3784 for (final Display d : al_displays) {
3785 d.frame.setCursor(c);
3789 protected void setCursor(Cursor c) {
3790 frame.setCursor(c);
3793 /** Used by the Displayable to update the visibility checkbox in other Displays. */
3794 static protected void updateVisibilityCheckbox(final Layer layer, final Displayable displ, final Display calling_display) {
3795 //LOCKS ALL //SwingUtilities.invokeLater(new Runnable() { public void run() {
3796 for (final Display d : al_displays) {
3797 if (d == calling_display) continue;
3798 if (d.layer.contains(displ) || (displ instanceof ZDisplayable && d.layer.getParent().contains((ZDisplayable)displ))) {
3799 DisplayablePanel dp = d.ht_panels.get(displ);
3800 if (null != dp) dp.updateVisibilityCheckbox();
3803 //}});
3806 protected boolean isActiveWindow() {
3807 return frame.isActive();
3810 /** Toggle user input; pan and zoom are always enabled though.*/
3811 static public void setReceivesInput(final Project project, final boolean b) {
3812 for (final Display d : al_displays) {
3813 if (d.project == project) d.canvas.setReceivesInput(b);
3817 /** Export the DTD that defines this object. */
3818 static public void exportDTD(StringBuffer sb_header, HashSet hs, String indent) {
3819 if (hs.contains("t2_display")) return; // TODO to avoid collisions the type shoud be in a namespace such as tm2:display
3820 hs.add("t2_display");
3821 sb_header.append(indent).append("<!ELEMENT t2_display EMPTY>\n")
3822 .append(indent).append("<!ATTLIST t2_display id NMTOKEN #REQUIRED>\n")
3823 .append(indent).append("<!ATTLIST t2_display layer_id NMTOKEN #REQUIRED>\n")
3824 .append(indent).append("<!ATTLIST t2_display x NMTOKEN #REQUIRED>\n")
3825 .append(indent).append("<!ATTLIST t2_display y NMTOKEN #REQUIRED>\n")
3826 .append(indent).append("<!ATTLIST t2_display magnification NMTOKEN #REQUIRED>\n")
3827 .append(indent).append("<!ATTLIST t2_display srcrect_x NMTOKEN #REQUIRED>\n")
3828 .append(indent).append("<!ATTLIST t2_display srcrect_y NMTOKEN #REQUIRED>\n")
3829 .append(indent).append("<!ATTLIST t2_display srcrect_width NMTOKEN #REQUIRED>\n")
3830 .append(indent).append("<!ATTLIST t2_display srcrect_height NMTOKEN #REQUIRED>\n")
3831 .append(indent).append("<!ATTLIST t2_display scroll_step NMTOKEN #REQUIRED>\n")
3832 .append(indent).append("<!ATTLIST t2_display c_alphas NMTOKEN #REQUIRED>\n")
3833 .append(indent).append("<!ATTLIST t2_display c_alphas_state NMTOKEN #REQUIRED>\n")
3836 /** Export all displays of the given project as XML entries. */
3837 static public void exportXML(final Project project, final Writer writer, final String indent, final Object any) throws Exception {
3838 final StringBuffer sb_body = new StringBuffer();
3839 final String in = indent + "\t";
3840 for (final Display d : al_displays) {
3841 if (d.project != project) continue;
3842 final Rectangle r = d.frame.getBounds();
3843 final Rectangle srcRect = d.canvas.getSrcRect();
3844 final double magnification = d.canvas.getMagnification();
3845 sb_body.append(indent).append("<t2_display id=\"").append(d.id).append("\"\n")
3846 .append(in).append("layer_id=\"").append(d.layer.getId()).append("\"\n")
3847 .append(in).append("c_alphas=\"").append(d.c_alphas).append("\"\n")
3848 .append(in).append("c_alphas_state=\"").append(d.getChannelAlphasState()).append("\"\n")
3849 .append(in).append("x=\"").append(r.x).append("\"\n")
3850 .append(in).append("y=\"").append(r.y).append("\"\n")
3851 .append(in).append("magnification=\"").append(magnification).append("\"\n")
3852 .append(in).append("srcrect_x=\"").append(srcRect.x).append("\"\n")
3853 .append(in).append("srcrect_y=\"").append(srcRect.y).append("\"\n")
3854 .append(in).append("srcrect_width=\"").append(srcRect.width).append("\"\n")
3855 .append(in).append("srcrect_height=\"").append(srcRect.height).append("\"\n")
3856 .append(in).append("scroll_step=\"").append(d.scroll_step).append("\"\n")
3858 sb_body.append(indent).append("/>\n");
3860 writer.write(sb_body.toString());
3863 static public void toolChanged(final String tool_name) {
3864 Utils.log2("tool name: " + tool_name);
3865 if (!tool_name.equals("ALIGN")) {
3866 for (final Display d : al_displays) {
3867 d.layer.getParent().cancelAlign();
3872 static public void toolChanged(final int tool) {
3873 //Utils.log2("int tool is " + tool);
3874 if (ProjectToolbar.PEN == tool) {
3875 // erase bounding boxes
3876 for (final Display d : al_displays) {
3877 if (null != d.active) d.repaint(d.layer, d.selection.getBox(), 2);
3880 if (null != front) {
3881 WindowManager.setTempCurrentImage(front.canvas.getFakeImagePlus());
3885 public Selection getSelection() {
3886 return selection;
3889 public boolean isSelected(Displayable d) {
3890 return selection.contains(d);
3893 static public void updateSelection() {
3894 Display.updateSelection(null);
3896 static public void updateSelection(final Display calling) {
3897 final HashSet hs = new HashSet();
3898 for (final Display d : al_displays) {
3899 if (hs.contains(d.layer)) continue;
3900 hs.add(d.layer);
3901 if (null == d || null == d.selection) {
3902 Utils.log2("d is : "+ d + " d.selection is " + d.selection);
3903 } else {
3904 d.selection.update(); // recomputes box
3906 if (d != calling) { // TODO this is so dirty!
3907 if (d.selection.getNLinked() > 1) d.canvas.setUpdateGraphics(true); // this is overkill anyway
3908 d.canvas.repaint(d.selection.getLinkedBox(), Selection.PADDING);
3909 d.navigator.repaint(true); // everything
3914 static public void clearSelection(final Layer layer) {
3915 for (final Display d : al_displays) {
3916 if (d.layer == layer) d.selection.clear();
3919 static public void clearSelection() {
3920 for (final Display d : al_displays) {
3921 d.selection.clear();
3925 private void setTempCurrentImage() {
3926 WindowManager.setCurrentWindow(canvas.getFakeImagePlus().getWindow(), true);
3927 WindowManager.setTempCurrentImage(canvas.getFakeImagePlus());
3930 /** Check if any display will paint the given Displayable at the given magnification. */
3931 static public boolean willPaint(final Displayable displ, final double magnification) {
3932 Rectangle box = null; ;
3933 for (final Display d : al_displays) {
3934 /* // Can no longer do this check, because 'magnification' is now affected by the Displayable AffineTransform! And thus it would not paint after the prePaint.
3935 if (Math.abs(d.canvas.getMagnification() - magnification) > 0.00000001) {
3936 continue;
3939 if (null == box) box = displ.getBoundingBox(null);
3940 if (d.canvas.getSrcRect().intersects(box)) {
3941 return true;
3944 return false;
3947 public void hideDeselected(final boolean not_images) {
3948 // hide deselected
3949 final ArrayList all = layer.getParent().getZDisplayables(); // a copy
3950 all.addAll(layer.getDisplayables());
3951 all.removeAll(selection.getSelected());
3952 if (not_images) all.removeAll(layer.getDisplayables(Patch.class));
3953 for (final Displayable d : (ArrayList<Displayable>)all) {
3954 if (d.isVisible()) d.setVisible(false);
3956 Display.update(layer);
3959 /** Cleanup internal lists that may contain the given Displayable. */
3960 static public void flush(final Displayable displ) {
3961 for (final Display d : al_displays) {
3962 d.selection.removeFromPrev(displ);
3966 public void resizeCanvas() {
3967 GenericDialog gd = new GenericDialog("Resize LayerSet");
3968 gd.addNumericField("new width: ", layer.getLayerWidth(), 3);
3969 gd.addNumericField("new height: ",layer.getLayerHeight(),3);
3970 gd.addChoice("Anchor: ", LayerSet.ANCHORS, LayerSet.ANCHORS[7]);
3971 gd.showDialog();
3972 if (gd.wasCanceled()) return;
3973 double new_width = gd.getNextNumber();
3974 double new_height =gd.getNextNumber();
3975 layer.getParent().setDimensions(new_width, new_height, gd.getNextChoiceIndex()); // will complain and prevent cropping existing Displayable objects
3979 // To record layer changes -- but it's annoying, this is visualization not data.
3980 static class DoSetLayer implements DoStep {
3981 final Display display;
3982 final Layer layer;
3983 DoSetLayer(final Display display) {
3984 this.display = display;
3985 this.layer = display.layer;
3987 public Displayable getD() { return null; }
3988 public boolean isEmpty() { return false; }
3989 public boolean apply(final int action) {
3990 display.setLayer(layer);
3992 public boolean isIdenticalTo(final Object ob) {
3993 if (!ob instanceof DoSetLayer) return false;
3994 final DoSetLayer dsl = (DoSetLayer) ob;
3995 return dsl.display == this.display && dsl.layer == this.layer;
4000 protected void duplicateLinkAndSendTo(final Displayable active, final int position, final Layer other_layer) {
4001 if (null == active || !(active instanceof Profile)) return;
4002 if (active.getLayer() == other_layer) return; // can't do that!
4003 Profile profile = project.getProjectTree().duplicateChild((Profile)active, position, other_layer);
4004 if (null == profile) return;
4005 active.link(profile);
4006 slt.setAndWait(other_layer);
4007 other_layer.add(profile);
4008 selection.add(profile);
4011 private final HashMap<Color,Layer> layer_channels = new HashMap<Color,Layer>();
4012 private final TreeMap<Integer,LayerPanel> layer_alpha = new TreeMap<Integer,LayerPanel>();
4014 /** Remove all red/blue coloring of layers, and repaint canvas. */
4015 protected void resetLayerColors() {
4016 synchronized (layer_channels) {
4017 for (final Layer l : new ArrayList<Layer>(layer_channels.values())) { // avoid concurrent modification exception
4018 final LayerPanel lp = layer_panels.get(l);
4019 lp.setColor(Color.white);
4020 setColorChannel(lp.layer, Color.white);
4021 lp.slider.setEnabled(true);
4023 layer_channels.clear();
4025 canvas.repaint();
4028 /** Set all layer alphas to zero, and repaint canvas. */
4029 protected void resetLayerAlphas() {
4030 synchronized (layer_channels) {
4031 for (final LayerPanel lp : new ArrayList<LayerPanel>(layer_alpha.values())) {
4032 lp.setAlpha(0);
4034 layer_alpha.clear(); // should have already been cleared
4036 canvas.repaint();
4039 /** Add to layer_alpha table, or remove if alpha is zero. */
4040 protected void storeLayerAlpha(final LayerPanel lp, final float a) {
4041 synchronized (layer_channels) {
4042 if (M.equals(0, a)) {
4043 layer_alpha.remove(lp.layer.getParent().indexOf(lp.layer));
4044 } else {
4045 layer_alpha.put(lp.layer.getParent().indexOf(lp.layer), lp);
4050 static protected final int REPAINT_SINGLE_LAYER = 0;
4051 static protected final int REPAINT_MULTI_LAYER = 1;
4052 static protected final int REPAINT_RGB_LAYER = 2;
4054 /** Sets the values atomically, returns the painting mode. */
4055 protected int getPaintMode(final HashMap<Color,Layer> hm, final ArrayList<LayerPanel> list) {
4056 synchronized (layer_channels) {
4057 if (layer_channels.size() > 0) {
4058 hm.putAll(layer_channels);
4059 hm.put(Color.green, this.layer);
4060 return REPAINT_RGB_LAYER;
4062 list.addAll(layer_alpha.values());
4063 final int len = list.size();
4064 if (len > 1) return REPAINT_MULTI_LAYER;
4065 if (1 == len) {
4066 if (list.get(0).layer == this.layer) return REPAINT_SINGLE_LAYER; // normal mode
4067 return REPAINT_MULTI_LAYER;
4069 return REPAINT_SINGLE_LAYER;
4073 /** Set a layer to be painted as a specific color channel in the canvas.
4074 * Only Color.red and Color.blue are accepted.
4075 * Color.green is reserved for the current layer. */
4076 protected void setColorChannel(final Layer layer, final Color color) {
4077 synchronized (layer_channels) {
4078 if (Color.white == color) {
4079 // Remove
4080 for (final Iterator<Layer> it = layer_channels.values().iterator(); it.hasNext(); ) {
4081 if (it.next() == layer) {
4082 it.remove();
4083 break;
4086 canvas.repaint();
4087 } else if (Color.red == color || Color.blue == color) {
4088 // Reset current of that color, if any, to white
4089 final Layer l = layer_channels.remove(color);
4090 if (null != l) layer_panels.get(l).setColor(Color.white);
4091 // Replace or set new
4092 layer_channels.put(color, layer);
4093 tabs.repaint();
4094 canvas.repaint();
4095 } else {
4096 Utils.log2("Trying to set unacceptable color for layer " + layer + " : " + color);
4098 // enable/disable sliders
4099 final boolean b = 0 == layer_channels.size();
4100 for (final LayerPanel lp : layer_panels.values()) lp.slider.setEnabled(b);
4102 this.canvas.repaint(true);
4105 static public final void updateComponentTreeUI() {
4106 try {
4107 for (final Display d : al_displays) SwingUtilities.updateComponentTreeUI(d.frame);
4108 } catch (Exception e) {
4109 IJError.print(e);