Fixed profile list 'b','n' to keep the new duplicated profile active.
[trakem2.git] / ini / trakem2 / display / Display.java
blob2b120fc1594d5d3690c4d82e7c01240bf07290b1
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();
315 SetLayerThread() {
316 setPriority(Thread.NORM_PRIORITY);
317 setDaemon(true);
318 start();
321 public final void set(final Layer layer) {
322 synchronized (lock) {
323 this.layer = layer;
325 synchronized (this) {
326 notify();
330 // Does not use the thread, rather just sets it within the context of the calling thread (would be the same as making the caller thread wait.)
331 final void setAndWait(final Layer layer) {
332 if (null != layer) {
333 Display.this.setLayer(layer);
334 Display.this.updateInDatabase("layer_id");
338 public void run() {
339 while (go) {
340 while (null == this.layer) {
341 synchronized (this) {
342 try { wait(); } catch (InterruptedException ie) {}
345 Layer layer = null;
346 synchronized (lock) {
347 layer = this.layer;
348 this.layer = null;
351 if (!go) return; // after nullifying layer
353 setAndWait(layer);
357 public void quit() {
358 go = false;
362 /** Creates a new Display with adjusted magnification to fit in the screen. */
363 static public void createDisplay(final Project project, final Layer layer) {
364 SwingUtilities.invokeLater(new Runnable() { public void run() {
365 Display display = new Display(project, layer);
366 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
367 Rectangle srcRect = new Rectangle(0, 0, (int)layer.getLayerWidth(), (int)layer.getLayerHeight());
368 double mag = screen.width / layer.getLayerWidth();
369 if (mag * layer.getLayerHeight() > screen.height) mag = screen.height / layer.getLayerHeight();
370 mag = display.canvas.getLowerZoomLevel2(mag);
371 if (mag > 1.0) mag = 1.0;
372 //display.getCanvas().setup(mag, srcRect); // would call pack() at the wrong time!
373 // ... so instead: manually
374 display.getCanvas().setMagnification(mag);
375 display.getCanvas().setSrcRect(srcRect.x, srcRect.y, srcRect.width, srcRect.height);
376 display.getCanvas().setDrawingSize((int)Math.ceil(srcRect.width * mag), (int)Math.ceil(srcRect.height * mag));
378 display.updateFrameTitle(layer);
379 ij.gui.GUI.center(display.frame);
380 display.frame.pack();
381 }});
384 /** A new Display from scratch, to show the given Layer. */
385 public Display(Project project, final Layer layer) {
386 super(project);
387 front = this;
388 makeGUI(layer, null);
389 ImagePlus.addImageListener(this);
390 setLayer(layer);
391 this.layer = layer; // after, or it doesn't update properly
392 al_displays.add(this);
393 addToDatabase();
396 /** For reconstruction purposes. The Display will be stored in the ht_later.*/
397 public Display(Project project, long id, Layer layer, Object[] props) {
398 super(project, id);
399 synchronized (ht_later) {
400 Display.ht_later.put(this, props);
402 this.layer = layer;
405 /** Open a new Display centered around the given Displayable. */
406 public Display(Project project, Layer layer, Displayable displ) {
407 super(project);
408 front = this;
409 active = displ;
410 makeGUI(layer, null);
411 ImagePlus.addImageListener(this);
412 setLayer(layer);
413 this.layer = layer; // after set layer!
414 al_displays.add(this);
415 addToDatabase();
418 /** Reconstruct a Display from an XML entry, to be opened when everything is ready. */
419 public Display(Project project, long id, Layer layer, HashMap ht_attributes) {
420 super(project, id);
421 if (null == layer) {
422 Utils.log2("Display: need a non-null Layer for id=" + id);
423 return;
425 Rectangle srcRect = new Rectangle(0, 0, (int)layer.getLayerWidth(), (int)layer.getLayerHeight());
426 double magnification = 0.25;
427 Point p = new Point(0, 0);
428 int c_alphas = 0xffffffff;
429 int c_alphas_state = 0xffffffff;
430 for (Iterator it = ht_attributes.entrySet().iterator(); it.hasNext(); ) {
431 Map.Entry entry = (Map.Entry)it.next();
432 String key = (String)entry.getKey();
433 String data = (String)entry.getValue();
434 if (key.equals("srcrect_x")) { // reflection! Reflection!
435 srcRect.x = Integer.parseInt(data);
436 } else if (key.equals("srcrect_y")) {
437 srcRect.y = Integer.parseInt(data);
438 } else if (key.equals("srcrect_width")) {
439 srcRect.width = Integer.parseInt(data);
440 } else if (key.equals("srcrect_height")) {
441 srcRect.height = Integer.parseInt(data);
442 } else if (key.equals("magnification")) {
443 magnification = Double.parseDouble(data);
444 } else if (key.equals("x")) {
445 p.x = Integer.parseInt(data);
446 } else if (key.equals("y")) {
447 p.y = Integer.parseInt(data);
448 } else if (key.equals("c_alphas")) {
449 try {
450 c_alphas = Integer.parseInt(data);
451 } catch (Exception ex) {
452 c_alphas = 0xffffffff;
454 } else if (key.equals("c_alphas_state")) {
455 try {
456 c_alphas_state = Integer.parseInt(data);
457 } catch (Exception ex) {
458 IJError.print(ex);
459 c_alphas_state = 0xffffffff;
461 } else if (key.equals("scroll_step")) {
462 try {
463 setScrollStep(Integer.parseInt(data));
464 } catch (Exception ex) {
465 IJError.print(ex);
466 setScrollStep(1);
469 // TODO the above is insecure, in that data is not fully checked to be within bounds.
471 Object[] props = new Object[]{p, new Double(magnification), srcRect, new Long(layer.getId()), new Integer(c_alphas), new Integer(c_alphas_state)};
472 synchronized (ht_later) {
473 Display.ht_later.put(this, props);
475 this.layer = layer;
478 /** After reloading a project from the database, open the Displays that the project had. */
479 static public Bureaucrat openLater() {
480 final Hashtable ht_later_local;
481 synchronized (ht_later) {
482 if (0 == ht_later.size()) return null;
483 ht_later_local = new Hashtable(ht_later);
484 ht_later.keySet().removeAll(ht_later_local.keySet());
486 final Worker worker = new Worker("Opening displays") {
487 public void run() {
488 startedWorking();
489 try {
490 Thread.sleep(300); // waiting for Swing
492 for (Enumeration e = ht_later_local.keys(); e.hasMoreElements(); ) {
493 final Display d = (Display)e.nextElement();
494 front = d; // must be set before repainting any ZDisplayable!
495 Object[] props = (Object[])ht_later_local.get(d);
496 if (ControlWindow.isGUIEnabled()) d.makeGUI(d.layer, props);
497 d.setLayerLater(d.layer, d.layer.get(((Long)props[3]).longValue())); //important to do it after makeGUI
498 if (!ControlWindow.isGUIEnabled()) continue;
499 ImagePlus.addImageListener(d);
500 al_displays.add(d);
501 d.updateFrameTitle(d.layer);
502 // 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
503 if (d.canvas.getMagnification() > 0.499) {
504 SwingUtilities.invokeLater(new Runnable() { public void run() {
505 d.repaint(d.layer);
506 d.project.getLoader().setChanged(false);
507 Utils.log2("A set to false");
508 }});
510 d.project.getLoader().setChanged(false);
511 Utils.log2("B set to false");
513 if (null != front) front.getProject().select(front.layer);
515 } catch (Throwable t) {
516 IJError.print(t);
517 } finally {
518 finishedWorking();
522 return Bureaucrat.createAndStart(worker, ((Display)ht_later_local.keySet().iterator().next()).getProject()); // gets the project from the first Display
525 private void makeGUI(final Layer layer, final Object[] props) {
526 // gather properties
527 Point p = null;
528 double mag = 1.0D;
529 Rectangle srcRect = null;
530 if (null != props) {
531 p = (Point)props[0];
532 mag = ((Double)props[1]).doubleValue();
533 srcRect = (Rectangle)props[2];
536 // transparency slider
537 this.transp_slider = new JSlider(javax.swing.SwingConstants.HORIZONTAL, 0, 100, 100);
538 this.transp_slider.setBackground(Color.white);
539 this.transp_slider.setMinimumSize(new Dimension(250, 20));
540 this.transp_slider.setMaximumSize(new Dimension(250, 20));
541 this.transp_slider.setPreferredSize(new Dimension(250, 20));
542 TransparencySliderListener tsl = new TransparencySliderListener();
543 this.transp_slider.addChangeListener(tsl);
544 this.transp_slider.addMouseListener(tsl);
545 for (final KeyListener kl : this.transp_slider.getKeyListeners()) {
546 this.transp_slider.removeKeyListener(kl);
549 // Tabbed pane on the left
550 this.tabs = new JTabbedPane();
551 this.tabs.setMinimumSize(new Dimension(250, 300));
552 this.tabs.setBackground(Color.white);
553 this.tabs.addChangeListener(tabs_listener);
555 // Tab 1: Patches
556 this.panel_patches = makeTabPanel();
557 this.panel_patches.add(new JLabel("No patches."));
558 this.scroll_patches = makeScrollPane(panel_patches);
559 this.tabs.add("Patches", scroll_patches);
561 // Tab 2: Profiles
562 this.panel_profiles = makeTabPanel();
563 this.panel_profiles.add(new JLabel("No profiles."));
564 this.scroll_profiles = makeScrollPane(panel_profiles);
565 this.tabs.add("Profiles", scroll_profiles);
567 // Tab 3: pipes
568 this.panel_zdispl = makeTabPanel();
569 this.panel_zdispl.add(new JLabel("No objects."));
570 this.scroll_zdispl = makeScrollPane(panel_zdispl);
571 this.tabs.add("Z space", scroll_zdispl);
573 // Tab 4: channels
574 this.panel_channels = makeTabPanel();
575 this.scroll_channels = makeScrollPane(panel_channels);
576 this.channels = new Channel[4];
577 this.channels[0] = new Channel(this, Channel.MONO);
578 this.channels[1] = new Channel(this, Channel.RED);
579 this.channels[2] = new Channel(this, Channel.GREEN);
580 this.channels[3] = new Channel(this, Channel.BLUE);
581 //this.panel_channels.add(this.channels[0]);
582 this.panel_channels.add(this.channels[1]);
583 this.panel_channels.add(this.channels[2]);
584 this.panel_channels.add(this.channels[3]);
585 this.tabs.add("Opacity", scroll_channels);
587 // Tab 5: labels
588 this.panel_labels = makeTabPanel();
589 this.panel_labels.add(new JLabel("No labels."));
590 this.scroll_labels = makeScrollPane(panel_labels);
591 this.tabs.add("Labels", scroll_labels);
593 // Tab 6: layers
594 this.panel_layers = makeTabPanel();
595 this.scroll_layers = makeScrollPane(panel_layers);
596 recreateLayerPanels(layer);
597 this.scroll_layers.addMouseWheelListener(canvas);
598 this.tabs.add("Layers", scroll_layers);
600 this.ht_tabs = new Hashtable<Class,JScrollPane>();
601 this.ht_tabs.put(Patch.class, scroll_patches);
602 this.ht_tabs.put(Profile.class, scroll_profiles);
603 this.ht_tabs.put(ZDisplayable.class, scroll_zdispl);
604 this.ht_tabs.put(AreaList.class, scroll_zdispl);
605 this.ht_tabs.put(Pipe.class, scroll_zdispl);
606 this.ht_tabs.put(Polyline.class, scroll_zdispl);
607 this.ht_tabs.put(Ball.class, scroll_zdispl);
608 this.ht_tabs.put(Dissector.class, scroll_zdispl);
609 this.ht_tabs.put(DLabel.class, scroll_labels);
610 // channels not included
611 // layers not included
613 // Navigator
614 this.navigator = new DisplayNavigator(this, layer.getLayerWidth(), layer.getLayerHeight());
615 // Layer scroller (to scroll slices)
616 int extent = (int)(250.0 / layer.getParent().size());
617 if (extent < 10) extent = 10;
618 this.scroller = new JScrollBar(JScrollBar.HORIZONTAL);
619 updateLayerScroller(layer);
620 this.scroller.addAdjustmentListener(scroller_listener);
623 // Left panel, contains the transp slider, the tabbed pane, the navigation panel and the layer scroller
624 JPanel left = new JPanel();
625 left.setBackground(Color.white);
626 BoxLayout left_layout = new BoxLayout(left, BoxLayout.Y_AXIS);
627 left.setLayout(left_layout);
628 left.add(transp_slider);
629 left.add(tabs);
630 left.add(navigator);
631 left.add(scroller);
633 // Canvas
634 this.canvas = new DisplayCanvas(this, (int)Math.ceil(layer.getLayerWidth()), (int)Math.ceil(layer.getLayerHeight()));
635 this.canvas_panel = new JPanel();
636 GridBagLayout gb = new GridBagLayout();
637 this.canvas_panel.setLayout(gb);
638 GridBagConstraints c = new GridBagConstraints();
639 c.fill = GridBagConstraints.BOTH;
640 c.anchor = GridBagConstraints.NORTHWEST;
641 gb.setConstraints(this.canvas_panel, c);
642 gb.setConstraints(this.canvas, c);
644 // prevent new Displays from screweing up if input is globally disabled
645 if (!project.isInputEnabled()) this.canvas.setReceivesInput(false);
647 this.canvas_panel.add(canvas);
649 this.navigator.addMouseWheelListener(canvas);
651 this.transp_slider.addKeyListener(canvas);
653 // Split pane to contain everything
654 this.split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, left, canvas_panel);
655 this.split.setOneTouchExpandable(true); // NOT present in all L&F (?)
656 this.split.setBackground(Color.white);
658 // fix
659 gb.setConstraints(split.getRightComponent(), c);
661 // JFrame to show the split pane
662 this.frame = ControlWindow.createJFrame(layer.toString());
663 this.frame.setBackground(Color.white);
664 this.frame.getContentPane().setBackground(Color.white);
665 if (IJ.isMacintosh() && IJ.getInstance()!=null) {
666 IJ.wait(10); // may be needed for Java 1.4 on OS X
667 this.frame.setMenuBar(ij.Menus.getMenuBar());
669 this.frame.addWindowListener(window_listener);
670 this.frame.addComponentListener(component_listener);
671 this.frame.getContentPane().add(split);
672 this.frame.addMouseListener(frame_mouse_listener);
673 //doesn't exist//this.frame.setMinimumSize(new Dimension(270, 600));
675 if (null != props) {
676 // restore canvas
677 canvas.setup(mag, srcRect);
678 // restore visibility of each channel
679 int cs = ((Integer)props[5]).intValue(); // aka c_alphas_state
680 int[] sel = new int[4];
681 sel[0] = ((cs&0xff000000)>>24);
682 sel[1] = ((cs&0xff0000)>>16);
683 sel[2] = ((cs&0xff00)>>8);
684 sel[3] = (cs&0xff);
685 // restore channel alphas
686 this.c_alphas = ((Integer)props[4]).intValue();
687 channels[0].setAlpha( (float)((c_alphas&0xff000000)>>24) / 255.0f , 0 != sel[0]);
688 channels[1].setAlpha( (float)((c_alphas&0xff0000)>>16) / 255.0f , 0 != sel[1]);
689 channels[2].setAlpha( (float)((c_alphas&0xff00)>>8) / 255.0f , 0 != sel[2]);
690 channels[3].setAlpha( (float) (c_alphas&0xff) / 255.0f , 0 != sel[3]);
691 // restore visibility in the working c_alphas
692 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);
695 if (null != active && null != layer) {
696 Rectangle r = active.getBoundingBox();
697 r.x -= r.width/2;
698 r.y -= r.height/2;
699 r.width += r.width;
700 r.height += r.height;
701 if (r.x < 0) r.x = 0;
702 if (r.y < 0) r.y = 0;
703 if (r.width > layer.getLayerWidth()) r.width = (int)layer.getLayerWidth();
704 if (r.height> layer.getLayerHeight())r.height= (int)layer.getLayerHeight();
705 double magn = layer.getLayerWidth() / (double)r.width;
706 canvas.setup(magn, r);
709 // add keyListener to the whole frame
710 this.tabs.addKeyListener(canvas);
711 this.canvas_panel.addKeyListener(canvas);
712 this.frame.addKeyListener(canvas);
714 this.frame.pack();
715 ij.gui.GUI.center(this.frame);
716 this.frame.setVisible(true);
717 ProjectToolbar.setProjectToolbar(); // doesn't get it through events
719 final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
721 if (null != props) {
722 // fix positioning outside the screen (dual to single monitor)
723 if (p.x >= 0 && p.x < screen.width - 50 && p.y >= 0 && p.y <= screen.height - 50) this.frame.setLocation(p);
724 else frame.setLocation(0, 0);
727 // fix excessive size
728 final Rectangle box = this.frame.getBounds();
729 int x = box.x;
730 int y = box.y;
731 int width = box.width;
732 int height = box.height;
733 if (box.width > screen.width) { x = 0; width = screen.width; }
734 if (box.height > screen.height) { y = 0; height = screen.height; }
735 if (x != box.x || y != box.y) {
736 this.frame.setLocation(x, y + (0 == y ? 30 : 0)); // added insets for bad window managers
737 updateInDatabase("position");
739 if (width != box.width || height != box.height) {
740 this.frame.setSize(new Dimension(width -10, height -30)); // added insets for bad window managers
742 if (null == props) {
743 // try to optimize canvas dimensions and magn
744 double magn = layer.getLayerHeight() / screen.height;
745 if (magn > 1.0) magn = 1.0;
746 long size = 0;
747 // limit magnification if appropriate
748 for (Iterator it = layer.getDisplayables(Patch.class).iterator(); it.hasNext(); ) {
749 final Patch pa = (Patch)it.next();
750 final Rectangle ba = pa.getBoundingBox();
751 size += (long)(ba.width * ba.height);
753 if (size > 10000000) canvas.setInitialMagnification(0.25); // 10 Mb
754 else {
755 this.frame.setSize(new Dimension((int)(screen.width * 0.66), (int)(screen.height * 0.66)));
759 Utils.updateComponent(tabs); // otherwise fails in FreeBSD java 1.4.2 when reconstructing
762 // Set the calibration of the FakeImagePlus to that of the LayerSet
763 ((FakeImagePlus)canvas.getFakeImagePlus()).setCalibrationSuper(layer.getParent().getCalibrationCopy());
765 updateFrameTitle(layer);
766 // Set the FakeImagePlus as the current image
767 setTempCurrentImage();
769 // create a drag and drop listener
770 dnd = new DNDInsertImage(this);
772 // start a repainting thread
773 if (null != props) {
774 canvas.repaint(true); // repaint() is unreliable
777 // 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.
778 SwingUtilities.invokeLater(new Runnable() {
779 public void run() {
780 tabs.setMinimumSize(new Dimension(0, 100));
781 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
782 ControlWindow.setLookAndFeel();
787 private JPanel makeTabPanel() {
788 JPanel panel = new JPanel();
789 BoxLayout layout = new BoxLayout(panel, BoxLayout.Y_AXIS);
790 panel.setLayout(layout);
791 return panel;
794 private JScrollPane makeScrollPane(Component c) {
795 JScrollPane jsp = new JScrollPane(c);
796 jsp.setBackground(Color.white); // no effect
797 jsp.getViewport().setBackground(Color.white); // no effect
798 // adjust scrolling to use one DisplayablePanel as the minimal unit
799 jsp.getVerticalScrollBar().setBlockIncrement(DisplayablePanel.HEIGHT); // clicking within the track
800 jsp.getVerticalScrollBar().setUnitIncrement(DisplayablePanel.HEIGHT); // clicking on an arrow
801 jsp.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
802 jsp.setPreferredSize(new Dimension(250, 300));
803 jsp.setMinimumSize(new Dimension(250, 300));
804 return jsp;
807 static protected int scrollbar_width = 0;
809 public JPanel getCanvasPanel() {
810 return canvas_panel;
813 public DisplayCanvas getCanvas() {
814 return canvas;
817 public synchronized void setLayer(final Layer layer) {
818 if (null == layer || layer == this.layer) return;
819 translateLayerColors(this.layer, layer);
820 if (tabs.getSelectedComponent() == scroll_layers) {
821 SwingUtilities.invokeLater(new Runnable() { public void run() {
822 scrollToShow(scroll_layers, layer_panels.get(layer));
823 }});
825 final boolean set_zdispl = null == Display.this.layer || layer.getParent() != Display.this.layer.getParent();
826 if (selection.isTransforming()) {
827 Utils.log("Can't browse layers while transforming.\nCANCEL the transform first with the ESCAPE key or right-click -> cancel.");
828 scroller.setValue(Display.this.layer.getParent().getLayerIndex(Display.this.layer.getId()));
829 return;
831 this.layer = layer;
832 scroller.setValue(layer.getParent().getLayerIndex(layer.getId()));
834 // update the current Layer pointer in ZDisplayable objects
835 for (Iterator it = layer.getParent().getZDisplayables().iterator(); it.hasNext(); ) {
836 ((ZDisplayable)it.next()).setLayer(layer); // the active layer
839 updateVisibleTab(set_zdispl);
841 // see if a lot has to be reloaded, put the relevant ones at the end
842 project.getLoader().prepare(layer);
843 updateFrameTitle(layer); // to show the new 'z'
844 // select the Layer in the LayerTree
845 project.select(Display.this.layer); // does so in a separate thread
846 // update active Displayable:
848 // deselect all except ZDisplayables
849 final ArrayList<Displayable> sel = selection.getSelected();
850 final Displayable last_active = Display.this.active;
851 int sel_next = -1;
852 for (final Iterator<Displayable> it = sel.iterator(); it.hasNext(); ) {
853 final Displayable d = it.next();
854 if (!(d instanceof ZDisplayable)) {
855 it.remove();
856 selection.remove(d);
857 if (d == last_active && sel.size() > 0) {
858 // select the last one of the remaining, if any
859 sel_next = sel.size()-1;
863 if (-1 != sel_next && sel.size() > 0) select(sel.get(sel_next), true);
865 // repaint everything
866 navigator.repaint(true);
867 canvas.repaint(true);
869 // repaint tabs (hard as hell)
870 Utils.updateComponent(tabs);
871 // @#$%^! The above works half the times, so explicit repaint as well:
872 Component c = tabs.getSelectedComponent();
873 if (null == c) {
874 c = scroll_patches;
875 tabs.setSelectedComponent(scroll_patches);
877 Utils.updateComponent(c);
879 project.getLoader().setMassiveMode(false); // resetting if it was set true
881 // update the coloring in the ProjectTree
882 project.getProjectTree().updateUILater();
884 setTempCurrentImage();
887 static public void updateVisibleTabs() {
888 for (final Display d : al_displays) {
889 d.updateVisibleTab(true);
893 /** Recreate the tab that is being shown. */
894 public void updateVisibleTab(boolean set_zdispl) {
895 // update only the visible tab
896 switch (tabs.getSelectedIndex()) {
897 case 0:
898 ht_panels.clear();
899 updateTab(panel_patches, "Patches", layer.getDisplayables(Patch.class));
900 break;
901 case 1:
902 ht_panels.clear();
903 updateTab(panel_profiles, "Profiles", layer.getDisplayables(Profile.class));
904 break;
905 case 2:
906 if (set_zdispl) {
907 ht_panels.clear();
908 updateTab(panel_zdispl, "Z-space objects", layer.getParent().getZDisplayables());
910 break;
911 // case 3: channel opacities
912 case 4:
913 ht_panels.clear();
914 updateTab(panel_labels, "Labels", layer.getDisplayables(DLabel.class));
915 break;
916 // case 5: layer panels
921 private void setLayerLater(final Layer layer, final Displayable active) {
922 if (null == layer) return;
923 this.layer = layer;
924 if (!ControlWindow.isGUIEnabled()) return;
925 SwingUtilities.invokeLater(new Runnable() { public void run() {
926 // empty the tabs, except channels and pipes
927 clearTab(panel_profiles, "Profiles");
928 clearTab(panel_patches, "Patches");
929 clearTab(panel_labels, "Labels");
930 // distribute Displayable to the tabs. Ignore LayerSet instances.
931 if (null == ht_panels) ht_panels = new Hashtable<Displayable,DisplayablePanel>();
932 else ht_panels.clear();
933 for (final Displayable d : layer.getDisplayables()) {
934 add(d, false, false);
936 for (final Displayable d : layer.getParent().getZDisplayables()) {
937 d.setLayer(layer);
938 add(d, false, false);
940 navigator.repaint(true); // was not done when adding
941 Utils.updateComponent(tabs.getSelectedComponent());
943 setActive(active);
944 }});
945 // swing issues:
947 new Thread() {
948 public void run() {
949 setPriority(Thread.NORM_PRIORITY);
950 try { Thread.sleep(1000); } catch (Exception e) {}
951 setActive(active);
953 }.start();
957 /** Remove all components from the tab and add a "No [label]" label to each. */
958 private void clearTab(final Container c, final String label) {
959 c.removeAll();
960 c.add(new JLabel("No " + label + "."));
961 // magic cocktail:
962 if (tabs.getSelectedComponent() == c) {
963 Utils.updateComponent(c);
967 /** A class to listen to the transparency_slider of the DisplayablesSelectorWindow. */
968 private class TransparencySliderListener extends MouseAdapter implements ChangeListener {
970 public void stateChanged(ChangeEvent ce) {
971 //change the transparency value of the current active displayable
972 float new_value = (float)((JSlider)ce.getSource()).getValue();
973 setTransparency(new_value / 100.0f);
976 public void mousePressed(MouseEvent me) {
977 JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
978 if (scroll != scroll_channels && !selection.isEmpty()) selection.addDataEditStep(new String[]{"alpha"});
981 public void mouseReleased(MouseEvent me) {
982 // update navigator window
983 navigator.repaint(true);
984 JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
985 if (scroll != scroll_channels && !selection.isEmpty()) selection.addDataEditStep(new String[]{"alpha"});
989 /** Context-sensitive: to a Displayable, or to a channel. */
990 private void setTransparency(final float value) {
991 JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
992 if (scroll == scroll_channels) {
993 for (int i=0; i<4; i++) {
994 if (channels[i].getBackground() == Color.cyan) {
995 channels[i].setAlpha(value); // will call back and repaint the Display
996 return;
999 } else if (null != active) {
1000 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.
1001 canvas.invalidateVolatile();
1002 selection.setAlpha(value);
1007 public void setTransparencySlider(final float transp) {
1008 if (transp >= 0.0f && transp <= 1.0f) {
1009 // fire event
1010 transp_slider.setValue((int)(transp * 100));
1014 /** Mark the canvas for updating the offscreen images if the given Displayable is NOT the active. */ // Used by the Displayable.setVisible for example.
1015 static public void setUpdateGraphics(final Layer layer, final Displayable displ) {
1016 for (final Display d : al_displays) {
1017 if (layer == d.layer && null != d.active && d.active != displ) {
1018 d.canvas.setUpdateGraphics(true);
1023 /** Flag the DisplayCanvas of Displays showing the given Layer to update their offscreen images.*/
1024 static public void setUpdateGraphics(final Layer layer, final boolean update) {
1025 for (final Display d : al_displays) {
1026 if (layer == d.layer) {
1027 d.canvas.setUpdateGraphics(update);
1032 /** Whether to update the offscreen images or not. */
1033 public void setUpdateGraphics(boolean b) {
1034 canvas.setUpdateGraphics(b);
1037 /** Find all Display instances that contain the layer and repaint them, in the Swing GUI thread. */
1038 static public void update(final Layer layer) {
1039 if (null == layer) return;
1040 SwingUtilities.invokeLater(new Runnable() { public void run() {
1041 for (final Display d : al_displays) {
1042 if (d.isShowing(layer)) {
1043 d.repaintAll();
1046 }});
1049 static public void update(final LayerSet set) {
1050 update(set, true);
1053 /** 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. */
1054 static public void update(final LayerSet set, final boolean update_canvas_dimensions) {
1055 if (null == set) return;
1056 SwingUtilities.invokeLater(new Runnable() { public void run() {
1057 for (final Display d : al_displays) {
1058 if (set.contains(d.layer)) {
1059 d.updateSnapshots();
1060 if (update_canvas_dimensions) d.canvas.setDimensions(set.getLayerWidth(), set.getLayerHeight());
1061 d.repaintAll();
1064 }});
1067 /** Release all resources held by this Display and close the frame. */
1068 protected void destroy() {
1069 dispatcher.quit();
1070 canvas.setReceivesInput(false);
1071 slt.quit();
1073 // update the coloring in the ProjectTree and LayerTree
1074 if (!project.isBeingDestroyed()) {
1075 try {
1076 project.getProjectTree().updateUILater();
1077 project.getLayerTree().updateUILater();
1078 } catch (Exception e) {
1079 Utils.log2("updateUI failed at Display.destroy()");
1083 frame.removeComponentListener(component_listener);
1084 frame.removeWindowListener(window_listener);
1085 frame.removeWindowFocusListener(window_listener);
1086 frame.removeWindowStateListener(window_listener);
1087 frame.removeKeyListener(canvas);
1088 frame.removeMouseListener(frame_mouse_listener);
1089 canvas_panel.removeKeyListener(canvas);
1090 canvas.removeKeyListener(canvas);
1091 tabs.removeChangeListener(tabs_listener);
1092 tabs.removeKeyListener(canvas);
1093 ImagePlus.removeImageListener(this);
1094 bytypelistener = null;
1095 canvas.destroy();
1096 navigator.destroy();
1097 scroller.removeAdjustmentListener(scroller_listener);
1098 frame.setVisible(false);
1099 //no need, and throws exception//frame.dispose();
1100 active = null;
1101 if (null != selection) selection.clear();
1102 //Utils.log2("destroying selection");
1104 // below, need for SetLayerThread threads to quit
1105 slt.quit();
1106 // set a new front if any
1107 if (null == front && al_displays.size() > 0) {
1108 front = (Display)al_displays.get(al_displays.size() -1);
1110 // repaint layer tree (to update the label color)
1111 try {
1112 project.getLayerTree().updateUILater(); // works only after setting the front above
1113 } catch (Exception e) {} // ignore swing sync bullshit when closing everything too fast
1114 // remove the drag and drop listener
1115 dnd.destroy();
1118 /** Find all Display instances that contain a Layer of the given project and close them without removing the Display entries from the database. */
1119 static synchronized public void close(final Project project) {
1120 /* // concurrent modifications if more than 1 Display are being removed asynchronously
1121 for (final Display d : al_displays) {
1122 if (d.getLayer().getProject().equals(project)) {
1123 it.remove();
1124 d.destroy();
1128 Display[] d = new Display[al_displays.size()];
1129 al_displays.toArray(d);
1130 for (int i=0; i<d.length; i++) {
1131 if (d[i].getProject() == project) {
1132 al_displays.remove(d[i]);
1133 d[i].destroy();
1138 /** Find all Display instances that contain the layer and close them and remove the Display from the database. */
1139 static public void close(final Layer layer) {
1140 for (Iterator it = al_displays.iterator(); it.hasNext(); ) {
1141 Display d = (Display)it.next();
1142 if (d.isShowing(layer)) {
1143 d.remove(false);
1144 it.remove();
1149 /** Find all Display instances that are showing the layer and either move to the next or previous layer, or close it if none. */
1150 static public void remove(final Layer layer) {
1151 for (Iterator<Display> it = al_displays.iterator(); it.hasNext(); ) {
1152 final Display d = it.next();
1153 if (d.isShowing(layer)) {
1154 Layer la = layer.getParent().next(layer);
1155 if (layer == la || null == la) la = layer.getParent().previous(layer);
1156 if (null == la || layer == la) {
1157 d.remove(false);
1158 it.remove();
1159 } else {
1160 d.slt.set(la);
1166 public boolean remove(boolean check) {
1167 if (check) {
1168 if (!Utils.check("Delete the Display ?")) return false;
1170 // flush the offscreen images and close the frame
1171 destroy();
1172 removeFromDatabase();
1173 return true;
1176 public Layer getLayer() {
1177 return layer;
1180 public LayerSet getLayerSet() {
1181 return layer.getParent();
1184 public boolean isShowing(final Layer layer) {
1185 return this.layer == layer;
1188 public DisplayNavigator getNavigator() {
1189 return navigator;
1192 /** Repaint both the canvas and the navigator, updating the graphics, and the title and tabs. */
1193 public void repaintAll() {
1194 if (repaint_disabled) return;
1195 navigator.repaint(true);
1196 canvas.repaint(true);
1197 Utils.updateComponent(tabs);
1198 updateFrameTitle();
1201 /** Repaint the canvas updating graphics, the navigator without updating graphics, and the title. */
1202 public void repaintAll2() {
1203 if (repaint_disabled) return;
1204 navigator.repaint(false);
1205 canvas.repaint(true);
1206 updateFrameTitle();
1209 static public void repaintSnapshots(final LayerSet set) {
1210 if (repaint_disabled) return;
1211 for (final Display d : al_displays) {
1212 if (d.getLayer().getParent() == set) {
1213 d.navigator.repaint(true);
1214 Utils.updateComponent(d.tabs);
1218 static public void repaintSnapshots(final Layer layer) {
1219 if (repaint_disabled) return;
1220 for (final Display d : al_displays) {
1221 if (d.getLayer() == layer) {
1222 d.navigator.repaint(true);
1223 Utils.updateComponent(d.tabs);
1228 public void pack() {
1229 dispatcher.exec(new Runnable() { public void run() {
1230 try {
1231 Thread.currentThread().sleep(100);
1232 SwingUtilities.invokeAndWait(new Runnable() { public void run() {
1233 frame.pack();
1234 }});
1235 } catch (Exception e) { IJError.print(e); }
1236 }});
1239 static public void pack(final LayerSet ls) {
1240 for (final Display d : al_displays) {
1241 if (d.layer.getParent() == ls) d.pack();
1245 private void adjustCanvas() {
1246 SwingUtilities.invokeLater(new Runnable() { public void run() {
1247 Rectangle r = split.getRightComponent().getBounds();
1248 canvas.setDrawingSize(r.width, r.height, true);
1249 // fix not-on-top-left problem
1250 canvas.setLocation(0, 0);
1251 //frame.pack(); // don't! Would go into an infinite loop
1252 canvas.repaint(true);
1253 updateInDatabase("srcRect");
1254 }});
1257 /** Grab the last selected display (or create an new one if none) and show in it the layer,centered on the Displayable object. */
1258 static public void setFront(final Layer layer, final Displayable displ) {
1259 if (null == front) {
1260 Display display = new Display(layer.getProject(), layer); // gets set to front
1261 display.showCentered(displ);
1262 } else if (layer == front.layer) {
1263 front.showCentered(displ);
1264 } else {
1265 // find one:
1266 for (final Display d : al_displays) {
1267 if (d.layer == layer) {
1268 d.frame.toFront();
1269 d.showCentered(displ);
1270 return;
1273 // else, open new one
1274 new Display(layer.getProject(), layer).showCentered(displ);
1278 /** 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. */
1279 static public void add(final Layer layer, final Displayable displ, final boolean activate) {
1280 for (final Display d : al_displays) {
1281 if (d.layer == layer) {
1282 if (front == d) {
1283 d.add(displ, activate, true);
1284 //front.frame.toFront();
1285 } else {
1286 d.add(displ, false, true);
1292 static public void add(final Layer layer, final Displayable displ) {
1293 add(layer, displ, true);
1296 /** Add the ZDisplayable to all Displays that show a Layer belonging to the given LayerSet. */
1297 static public void add(final LayerSet set, final ZDisplayable zdispl) {
1298 for (final Display d : al_displays) {
1299 if (set.contains(d.layer)) {
1300 if (front == d) {
1301 zdispl.setLayer(d.layer); // the active one
1302 d.add(zdispl, true, true); // calling add(Displayable, boolean, boolean)
1303 //front.frame.toFront();
1304 } else {
1305 d.add(zdispl, false, true);
1311 static public void addAll(final Layer layer, final Collection<? extends Displayable> coll) {
1312 for (final Display d : al_displays) {
1313 if (d.layer == layer) {
1314 d.addAll(coll);
1319 static public void addAll(final LayerSet set, final Collection<? extends ZDisplayable> coll) {
1320 for (final Display d : al_displays) {
1321 if (set.contains(d.layer)) {
1322 for (final ZDisplayable zd : coll) {
1323 if (front == d) zd.setLayer(d.layer);
1325 d.addAll(coll);
1330 private final void addAll(final Collection<? extends Displayable> coll) {
1331 for (final Displayable d : coll) {
1332 add(d, false, false);
1334 selection.clear();
1335 Utils.updateComponent(tabs);
1336 navigator.repaint(true);
1339 // TODO this very old method could take some improvement:
1340 // - there is no need to create a new DisplayablePanel if its panel is not shown
1341 // - other issues; the method looks overly "if a dog barks and a duck quacks during a lunar eclipse then .."
1342 /** Add it to the proper panel, at the top, and set it active. */
1343 private final void add(final Displayable d, final boolean activate, final boolean repaint_snapshot) {
1344 DisplayablePanel dp = ht_panels.get(d);
1345 if (null != dp && activate) { // for ZDisplayable objects (TODO I think this is not used anymore)
1346 dp.setActive(true);
1347 //setActive(d);
1348 selection.clear();
1349 selection.add(d);
1350 return;
1352 // add to the proper list
1353 JPanel p = null;
1354 if (d instanceof Profile) {
1355 p = panel_profiles;
1356 } else if (d instanceof Patch) {
1357 p = panel_patches;
1358 } else if (d instanceof DLabel) {
1359 p = panel_labels;
1360 } else if (d instanceof ZDisplayable) { //both pipes and balls and AreaList
1361 p = panel_zdispl;
1362 } else {
1363 // LayerSet objects
1364 return;
1366 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
1367 addToPanel(p, 0, dp, activate);
1368 ht_panels.put(d, dp);
1369 if (activate) {
1370 dp.setActive(true);
1371 //setActive(d);
1372 selection.clear();
1373 selection.add(d);
1375 if (repaint_snapshot) navigator.repaint(true);
1378 private void addToPanel(JPanel panel, int index, DisplayablePanel dp, boolean repaint) {
1379 // remove the label
1380 if (1 == panel.getComponentCount() && panel.getComponent(0) instanceof JLabel) {
1381 panel.removeAll();
1383 panel.add(dp, index);
1384 if (repaint) {
1385 Utils.updateComponent(tabs);
1389 /** Find the displays that show the given Layer, and remove the given Displayable from the GUI. */
1390 static public void remove(final Layer layer, final Displayable displ) {
1391 for (final Display d : al_displays) {
1392 if (layer == d.layer) d.remove(displ);
1396 private void remove(final Displayable displ) {
1397 DisplayablePanel ob = ht_panels.remove(displ);
1398 if (null != ob) {
1399 final JScrollPane jsp = ht_tabs.get(displ.getClass());
1400 if (null != jsp) {
1401 JPanel p = (JPanel)jsp.getViewport().getView();
1402 p.remove((Component)ob);
1403 Utils.revalidateComponent(p);
1406 if (null == active || !selection.contains(displ)) {
1407 canvas.setUpdateGraphics(true);
1409 canvas.invalidateVolatile(); // removing active, no need to update offscreen but yes the volatile
1410 repaint(displ, null, 5, true, false);
1411 // from Selection.deleteAll this method is called ... but it's ok: same thread, no locking problems.
1412 selection.remove(displ);
1415 static public void remove(final ZDisplayable zdispl) {
1416 for (final Display d : al_displays) {
1417 if (zdispl.getLayerSet() == d.layer.getParent()) {
1418 d.remove((Displayable)zdispl);
1423 static public void repaint(final Layer layer, final Displayable displ, final int extra) {
1424 repaint(layer, displ, displ.getBoundingBox(), extra);
1427 static public void repaint(final Layer layer, final Displayable displ, final Rectangle r, final int extra) {
1428 repaint(layer, displ, r, extra, true);
1431 /** Find the displays that show the given Layer, and repaint the given Displayable. */
1432 static public void repaint(final Layer layer, final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator) {
1433 if (repaint_disabled) return;
1434 for (final Display d : al_displays) {
1435 if (layer == d.layer) {
1436 d.repaint(displ, r, extra, repaint_navigator, false);
1441 static public void repaint(final Displayable d) {
1442 if (d instanceof ZDisplayable) repaint(d.getLayerSet(), d, d.getBoundingBox(null), 5, true);
1443 repaint(d.getLayer(), d, d.getBoundingBox(null), 5, true);
1446 /** Repaint as much as the bounding box around the given Displayable, or the r if not null. */
1447 private void repaint(final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator, final boolean update_graphics) {
1448 if (repaint_disabled || null == displ) return;
1449 if (update_graphics || displ.getClass() == Patch.class || displ != active) {
1450 canvas.setUpdateGraphics(true);
1452 if (null != r) canvas.repaint(r, extra);
1453 else canvas.repaint(displ, extra);
1454 if (repaint_navigator) {
1455 DisplayablePanel dp = ht_panels.get(displ);
1456 if (null != dp) dp.repaint(); // is null when creating it, or after deleting it
1457 navigator.repaint(true); // everything
1461 /** 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. */
1462 static public void repaintSnapshot(final Displayable displ) {
1463 for (final Display d : al_displays) {
1464 if (d.layer.contains(displ)) {
1465 if (!d.navigator.isPainted(displ)) {
1466 DisplayablePanel dp = d.ht_panels.get(displ);
1467 if (null != dp) dp.repaint(); // is null when creating it, or after deleting it
1468 d.navigator.repaint(displ);
1474 /** Repaint the given Rectangle in all Displays showing the layer, updating the offscreen image if any. */
1475 static public void repaint(final Layer layer, final Rectangle r, final int extra) {
1476 repaint(layer, extra, r, true, true);
1479 static public void repaint(final Layer layer, final int extra, final Rectangle r, final boolean update_navigator) {
1480 repaint(layer, extra, r, update_navigator, true);
1483 static public void repaint(final Layer layer, final int extra, final Rectangle r, final boolean update_navigator, final boolean update_graphics) {
1484 if (repaint_disabled) return;
1485 for (final Display d : al_displays) {
1486 if (layer == d.layer) {
1487 d.canvas.setUpdateGraphics(update_graphics);
1488 d.canvas.repaint(r, extra);
1489 if (update_navigator) {
1490 d.navigator.repaint(true);
1491 Utils.updateComponent(d.tabs.getSelectedComponent());
1498 /** Repaint the given Rectangle in all Displays showing the layer, optionally updating the offscreen image (if any). */
1499 static public void repaint(final Layer layer, final Rectangle r, final int extra, final boolean update_graphics) {
1500 if (repaint_disabled) return;
1501 for (final Display d : al_displays) {
1502 if (layer == d.layer) {
1503 d.canvas.setUpdateGraphics(update_graphics);
1504 d.canvas.repaint(r, extra);
1505 d.navigator.repaint(update_graphics);
1506 if (update_graphics) Utils.updateComponent(d.tabs.getSelectedComponent());
1511 /** Repaint the DisplayablePanel (and DisplayNavigator) only for the given Displayable, in all Displays showing the given Layer. */
1512 static public void repaint(final Layer layer, final Displayable displ) {
1513 if (repaint_disabled) return;
1514 for (final Display d : al_displays) {
1515 if (layer == d.layer) {
1516 DisplayablePanel dp = d.ht_panels.get(displ);
1517 if (null != dp) dp.repaint();
1518 d.navigator.repaint(true);
1523 static public void repaint(LayerSet set, Displayable displ, int extra) {
1524 repaint(set, displ, null, extra);
1527 static public void repaint(LayerSet set, Displayable displ, Rectangle r, int extra) {
1528 repaint(set, displ, r, extra, true);
1531 /** Repaint the Displayable in every Display that shows a Layer belonging to the given LayerSet. */
1532 static public void repaint(final LayerSet set, final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator) {
1533 if (repaint_disabled) return;
1534 for (final Display d : al_displays) {
1535 if (set.contains(d.layer)) {
1536 if (repaint_navigator) {
1537 if (null != displ) {
1538 DisplayablePanel dp = d.ht_panels.get(displ);
1539 if (null != dp) dp.repaint();
1541 d.navigator.repaint(true);
1543 if (null == displ || displ != d.active) d.setUpdateGraphics(true); // safeguard
1544 // paint the given box or the actual Displayable's box
1545 if (null != r) d.canvas.repaint(r, extra);
1546 else d.canvas.repaint(displ, extra);
1551 /** Repaint the entire LayerSet, in all Displays showing a Layer of it.*/
1552 static public void repaint(final LayerSet set) {
1553 if (repaint_disabled) return;
1554 for (final Display d : al_displays) {
1555 if (set.contains(d.layer)) {
1556 d.navigator.repaint(true);
1557 d.canvas.repaint(true);
1561 /** Repaint the given box in the LayerSet, in all Displays showing a Layer of it.*/
1562 static public void repaint(final LayerSet set, final Rectangle box) {
1563 if (repaint_disabled) return;
1564 for (final Display d : al_displays) {
1565 if (set.contains(d.layer)) {
1566 d.navigator.repaint(box);
1567 d.canvas.repaint(box, 0, true);
1571 /** Repaint the entire Layer, in all Displays showing it, including the tabs.*/
1572 static public void repaint(final Layer layer) { // TODO this method overlaps with update(layer)
1573 if (repaint_disabled) return;
1574 for (final Display d : al_displays) {
1575 if (layer == d.layer) {
1576 d.navigator.repaint(true);
1577 d.canvas.repaint(true);
1582 /** Call repaint on all open Displays. */
1583 static public void repaint() {
1584 if (repaint_disabled) {
1585 Utils.logAll("Can't repaint -- repainting is disabled!");
1586 return;
1588 for (final Display d : al_displays) {
1589 d.navigator.repaint(true);
1590 d.canvas.repaint(true);
1594 static private boolean repaint_disabled = false;
1596 /** Set a flag to enable/disable repainting of all Display instances. */
1597 static protected void setRepaint(boolean b) {
1598 repaint_disabled = !b;
1601 public Rectangle getBounds() {
1602 return frame.getBounds();
1605 public Point getLocation() {
1606 return frame.getLocation();
1609 public JFrame getFrame() {
1610 return frame;
1613 public void setLocation(Point p) {
1614 this.frame.setLocation(p);
1617 public Displayable getActive() {
1618 return active; //TODO this should return selection.active !!
1621 public void select(Displayable d) {
1622 select(d, false);
1625 /** Select/deselect accordingly to the current state and the shift key. */
1626 public void select(final Displayable d, final boolean shift_down) {
1627 if (null != active && active != d && active.getClass() != Patch.class) {
1628 // active is being deselected, so link underlying patches
1629 active.linkPatches();
1631 if (null == d) {
1632 //Utils.log2("Display.select: clearing selection");
1633 canvas.setUpdateGraphics(true);
1634 selection.clear();
1635 return;
1637 if (!shift_down) {
1638 //Utils.log2("Display.select: single selection");
1639 if (d != active) {
1640 selection.clear();
1641 selection.add(d);
1643 } else if (selection.contains(d)) {
1644 if (active == d) {
1645 selection.remove(d);
1646 //Utils.log2("Display.select: removing from a selection");
1647 } else {
1648 //Utils.log2("Display.select: activing within a selection");
1649 selection.setActive(d);
1651 } else {
1652 //Utils.log2("Display.select: adding to an existing selection");
1653 selection.add(d);
1655 // update the image shown to ImageJ
1656 // NO longer necessary, always he same FakeImagePlus // setTempCurrentImage();
1659 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p, final Class c) {
1660 choose(screen_x_p, screen_y_p, x_p, y_p, false, c);
1662 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p) {
1663 choose(screen_x_p, screen_y_p, x_p, y_p, false, null);
1666 /** 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. */
1667 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p, boolean shift_down, Class c) {
1668 //Utils.log("Display.choose: x,y " + x_p + "," + y_p);
1669 final ArrayList<Displayable> al = new ArrayList<Displayable>(layer.find(x_p, y_p, true));
1670 al.addAll(layer.getParent().findZDisplayables(layer, x_p, y_p, true)); // only visible ones
1671 if (al.isEmpty()) {
1672 Displayable act = this.active;
1673 selection.clear();
1674 canvas.setUpdateGraphics(true);
1675 //Utils.log("choose: set active to null");
1676 // fixing lack of repainting for unknown reasons, of the active one TODO this is a temporary solution
1677 if (null != act) Display.repaint(layer, act, 5);
1678 } else if (1 == al.size()) {
1679 Displayable d = (Displayable)al.get(0);
1680 if (null != c && d.getClass() != c) {
1681 selection.clear();
1682 return;
1684 select(d, shift_down);
1685 //Utils.log("choose 1: set active to " + active);
1686 } else {
1687 if (al.contains(active) && !shift_down) {
1688 // do nothing
1689 } else {
1690 if (null != c) {
1691 // check if at least one of them is of class c
1692 // if only one is of class c, set as selected
1693 // else show menu
1694 for (Iterator it = al.iterator(); it.hasNext(); ) {
1695 Object ob = it.next();
1696 if (ob.getClass() != c) it.remove();
1698 if (0 == al.size()) {
1699 // deselect
1700 selection.clear();
1701 return;
1703 if (1 == al.size()) {
1704 select((Displayable)al.get(0), shift_down);
1705 return;
1707 // else, choose among the many
1709 choose(screen_x_p, screen_y_p, al, shift_down, x_p, y_p);
1711 //Utils.log("choose many: set active to " + active);
1715 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) {
1716 // show a popup on the canvas to choose
1717 new Thread() {
1718 public void run() {
1719 final Object lock = new Object();
1720 final DisplayableChooser d_chooser = new DisplayableChooser(al, lock);
1721 final JPopupMenu pop = new JPopupMenu("Select:");
1722 final Iterator itu = al.iterator();
1723 while (itu.hasNext()) {
1724 Displayable d = (Displayable)itu.next();
1725 JMenuItem menu_item = new JMenuItem(d.toString());
1726 menu_item.addActionListener(d_chooser);
1727 pop.add(menu_item);
1730 new Thread() {
1731 public void run() {
1732 pop.show(canvas, screen_x_p, screen_y_p);
1734 }.start();
1736 //now wait until selecting something
1737 synchronized(lock) {
1738 do {
1739 try {
1740 lock.wait();
1741 } catch (InterruptedException ie) {}
1742 } while (d_chooser.isWaiting() && pop.isShowing());
1745 //grab the chosen Displayable object
1746 Displayable d = d_chooser.getChosen();
1747 //Utils.log("Chosen: " + d.toString());
1748 if (null == d) { Utils.log2("Display.choose: returning a null!"); }
1749 select(d, shift_down);
1750 pop.setVisible(false);
1752 // fix selection bug: never receives mouseReleased event when the popup shows
1753 selection.mouseReleased(null, x_p, y_p, x_p, y_p, x_p, y_p);
1755 }.start();
1758 /** 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. */
1759 protected void setActive(final Displayable displ) {
1760 final Displayable prev_active = this.active;
1761 this.active = displ;
1762 SwingUtilities.invokeLater(new Runnable() { public void run() {
1764 // renew current image if necessary
1765 if (null != displ && displ == prev_active) {
1766 // make sure the proper tab is selected.
1767 selectTab(displ);
1768 return; // the same
1770 // deactivate previously active
1771 if (null != prev_active) {
1772 final DisplayablePanel ob = ht_panels.get(prev_active);
1773 if (null != ob) ob.setActive(false);
1774 // erase "decorations" of the previously active
1775 canvas.repaint(selection.getBox(), 4);
1777 // activate the new active
1778 if (null != displ) {
1779 final DisplayablePanel ob = ht_panels.get(displ);
1780 if (null != ob) ob.setActive(true);
1781 updateInDatabase("active_displayable_id");
1782 if (displ.getClass() != Patch.class) project.select(displ); // select the node in the corresponding tree, if any.
1783 // select the proper tab, and scroll to visible
1784 selectTab(displ);
1785 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
1786 repaint(displ, null, 5, false, update_graphics); // to show the border, and to repaint out of the background image
1787 transp_slider.setValue((int)(displ.getAlpha() * 100));
1788 } else {
1789 //ensure decorations are removed from the panels, for Displayables in a selection besides the active one
1790 Utils.updateComponent(tabs.getSelectedComponent());
1792 }});
1795 /** If the other paints under the base. */
1796 public boolean paintsBelow(Displayable base, Displayable other) {
1797 boolean zd_base = base instanceof ZDisplayable;
1798 boolean zd_other = other instanceof ZDisplayable;
1799 if (zd_other) {
1800 if (base instanceof DLabel) return true; // zd paints under label
1801 if (!zd_base) return false; // any zd paints over a mere displ if not a label
1802 else {
1803 // both zd, compare indices
1804 ArrayList<ZDisplayable> al = other.getLayerSet().getZDisplayables();
1805 return al.indexOf(base) > al.indexOf(other);
1807 } else {
1808 if (!zd_base) {
1809 // both displ, compare indices
1810 ArrayList<Displayable> al = other.getLayer().getDisplayables();
1811 return al.indexOf(base) > al.indexOf(other);
1812 } else {
1813 // base is zd, other is d
1814 if (other instanceof DLabel) return false;
1815 return true;
1820 /** 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. */
1821 private void selectTab(final Displayable displ) {
1822 Method method = null;
1823 try {
1824 if (!(displ instanceof LayerSet)) {
1825 method = Display.class.getDeclaredMethod("selectTab", new Class[]{displ.getClass()});
1827 } catch (Exception e) {
1828 IJError.print(e);
1830 if (null != method) {
1831 final Method me = method;
1832 dispatcher.exec(new Runnable() { public void run() {
1833 try {
1834 me.setAccessible(true);
1835 me.invoke(Display.this, new Object[]{displ});
1836 } catch (Exception e) { IJError.print(e); }
1837 }});
1841 private void selectTab(Patch patch) {
1842 tabs.setSelectedComponent(scroll_patches);
1843 scrollToShow(scroll_patches, ht_panels.get(patch));
1846 private void selectTab(Profile profile) {
1847 tabs.setSelectedComponent(scroll_profiles);
1848 scrollToShow(scroll_profiles, ht_panels.get(profile));
1851 private void selectTab(DLabel label) {
1852 tabs.setSelectedComponent(scroll_labels);
1853 scrollToShow(scroll_labels, ht_panels.get(label));
1856 private void selectTab(ZDisplayable zd) {
1857 tabs.setSelectedComponent(scroll_zdispl);
1858 scrollToShow(scroll_zdispl, ht_panels.get(zd));
1861 private void selectTab(Pipe d) { selectTab((ZDisplayable)d); }
1862 private void selectTab(Polyline d) { selectTab((ZDisplayable)d); }
1863 private void selectTab(AreaList d) { selectTab((ZDisplayable)d); }
1864 private void selectTab(Ball d) { selectTab((ZDisplayable)d); }
1865 private void selectTab(Dissector d) { selectTab((ZDisplayable)d); }
1867 /** 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). */
1868 private void updateTab(final Container tab, final String label, final ArrayList al) {
1869 final boolean[] recreated = new boolean[]{false, true, true};
1870 dispatcher.execSwing(new Runnable() { public void run() {
1871 try {
1872 if (0 == al.size()) {
1873 tab.removeAll();
1874 tab.add(new JLabel("No " + label + "."));
1875 } else {
1876 Component[] comp = tab.getComponents();
1877 int next = 0;
1878 if (1 == comp.length && comp[0].getClass() == JLabel.class) {
1879 next = 1;
1880 tab.remove(0);
1882 for (Iterator it = al.iterator(); it.hasNext(); ) {
1883 Displayable d = (Displayable)it.next();
1884 DisplayablePanel dp = null;
1885 if (next < comp.length) {
1886 dp = (DisplayablePanel)comp[next++]; // recycling panels
1887 dp.set(d);
1888 } else {
1889 dp = new DisplayablePanel(Display.this, d);
1890 tab.add(dp);
1892 ht_panels.put(d, dp);
1894 if (next < comp.length) {
1895 // remove from the end, to avoid potential repaints of other panels
1896 for (int i=comp.length-1; i>=next; i--) {
1897 tab.remove(i);
1900 recreated[0] = true;
1902 if (recreated[0]) {
1903 tab.invalidate();
1904 tab.validate();
1905 tab.repaint();
1907 if (null != Display.this.active) scrollToShow(Display.this.active);
1908 } catch (Throwable e) { IJError.print(e); }
1909 }});
1912 static public void setActive(final Object event, final Displayable displ) {
1913 if (!(event instanceof InputEvent)) return;
1914 // find which Display
1915 for (final Display d : al_displays) {
1916 if (d.isOrigin((InputEvent)event)) {
1917 d.setActive(displ);
1918 break;
1923 /** Find out whether this Display is Transforming its active Displayable. */
1924 public boolean isTransforming() {
1925 return canvas.isTransforming();
1928 /** Find whether any Display is transforming the given Displayable. */
1929 static public boolean isTransforming(final Displayable displ) {
1930 for (final Display d : al_displays) {
1931 if (null != d.active && d.active == displ && d.canvas.isTransforming()) return true;
1933 return false;
1936 static public boolean isAligning(final LayerSet set) {
1937 for (final Display d : al_displays) {
1938 if (d.layer.getParent() == set && set.isAligning()) {
1939 return true;
1942 return false;
1945 /** Set the front Display to transform the Displayable only if no other canvas is transforming it. */
1946 static public void setTransforming(final Displayable displ) {
1947 if (null == front) return;
1948 if (front.active != displ) return;
1949 for (final Display d : al_displays) {
1950 if (d.active == displ) {
1951 if (d.canvas.isTransforming()) {
1952 Utils.showMessage("Already transforming " + displ.getTitle());
1953 return;
1957 front.canvas.setTransforming(true);
1960 /** Check whether the source of the event is located in this instance.*/
1961 private boolean isOrigin(InputEvent event) {
1962 Object source = event.getSource();
1963 // find it ... check the canvas for now TODO
1964 if (canvas == source) {
1965 return true;
1967 return false;
1970 /** Get the layer of the front Display, or null if none.*/
1971 static public Layer getFrontLayer() {
1972 if (null == front) return null;
1973 return front.layer;
1976 /** Get the layer of an open Display of the given Project, or null if none.*/
1977 static public Layer getFrontLayer(final Project project) {
1978 if (null == front) return null;
1979 if (front.project == project) return front.layer;
1980 // else, find an open Display for the given Project, if any
1981 for (final Display d : al_displays) {
1982 if (d.project == project) {
1983 d.frame.toFront();
1984 return d.layer;
1987 return null; // none found
1990 static public Display getFront(final Project project) {
1991 if (null == front) return null;
1992 if (front.project == project) return front;
1993 for (final Display d : al_displays) {
1994 if (d.project == project) {
1995 d.frame.toFront();
1996 return d;
1999 return null;
2002 public boolean isReadOnly() {
2003 // TEMPORARY: in the future one will be able show displays as read-only to other people, remotely
2004 return false;
2007 static public void showPopup(Component c, int x, int y) {
2008 if (null != front) front.getPopupMenu().show(c, x, y);
2011 /** Return a context-sensitive popup menu. */
2012 public JPopupMenu getPopupMenu() { // called from canvas
2013 // get the job canceling dialog
2014 if (!canvas.isInputEnabled()) {
2015 return project.getLoader().getJobsPopup(this);
2018 // create new
2019 this.popup = new JPopupMenu();
2020 JMenuItem item = null;
2021 JMenu menu = null;
2023 if (ProjectToolbar.ALIGN == Toolbar.getToolId()) {
2024 boolean aligning = layer.getParent().isAligning();
2025 item = new JMenuItem("Cancel alignment"); item.addActionListener(this); popup.add(item);
2026 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2027 if (!aligning) item.setEnabled(false);
2028 item = new JMenuItem("Align with landmarks"); item.addActionListener(this); popup.add(item);
2029 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true));
2030 if (!aligning) item.setEnabled(false);
2031 item = new JMenuItem("Align and register"); item.addActionListener(this); popup.add(item);
2032 if (!aligning) item.setEnabled(false);
2033 item = new JMenuItem("Align using profiles"); item.addActionListener(this); popup.add(item);
2034 if (!aligning || selection.isEmpty() || !selection.contains(Profile.class)) item.setEnabled(false);
2035 item = new JMenuItem("Align stack slices"); item.addActionListener(this); popup.add(item);
2036 if (selection.isEmpty() || ! (getActive().getClass() == Patch.class && ((Patch)getActive()).isStack())) item.setEnabled(false);
2037 item = new JMenuItem("Align layers"); item.addActionListener(this); popup.add(item);
2038 if (1 == layer.getParent().size()) item.setEnabled(false);
2039 item = new JMenuItem("Align multi-layer mosaic"); item.addActionListener(this); popup.add(item);
2040 if (1 == layer.getParent().size()) item.setEnabled(false);
2041 return popup;
2045 if (null != active) {
2046 if (!canvas.isTransforming()) {
2047 if (active instanceof Profile) {
2048 item = new JMenuItem("Duplicate, link and send to next layer"); item.addActionListener(this); popup.add(item);
2049 Layer nl = layer.getParent().next(layer);
2050 if (nl == layer) item.setEnabled(false);
2051 item = new JMenuItem("Duplicate, link and send to previous layer"); item.addActionListener(this); popup.add(item);
2052 nl = layer.getParent().previous(layer);
2053 if (nl == layer) item.setEnabled(false);
2055 menu = new JMenu("Duplicate, link and send to");
2056 ArrayList al = layer.getParent().getLayers();
2057 final Iterator it = al.iterator();
2058 int i = 1;
2059 while (it.hasNext()) {
2060 Layer la = (Layer)it.next();
2061 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
2062 if (la == this.layer) item.setEnabled(false);
2063 i++;
2065 popup.add(menu);
2066 item = new JMenuItem("Duplicate, link and send to..."); item.addActionListener(this); popup.add(item);
2068 popup.addSeparator();
2070 item = new JMenuItem("Unlink from images"); item.addActionListener(this); popup.add(item);
2071 if (!active.isLinked()) item.setEnabled(false); // isLinked() checks if it's linked to a Patch in its own layer
2072 item = new JMenuItem("Show in 3D"); item.addActionListener(this); popup.add(item);
2073 popup.addSeparator();
2074 } else if (active instanceof Patch) {
2075 item = new JMenuItem("Unlink from images"); item.addActionListener(this); popup.add(item);
2076 if (!active.isLinked(Patch.class)) item.setEnabled(false);
2077 if (((Patch)active).isStack()) {
2078 item = new JMenuItem("Unlink slices"); item.addActionListener(this); popup.add(item);
2080 int n_sel_patches = selection.getSelected(Patch.class).size();
2081 if (1 == n_sel_patches) {
2082 item = new JMenuItem("Snap"); item.addActionListener(this); popup.add(item);
2083 } else if (n_sel_patches > 1) {
2084 item = new JMenuItem("Montage"); item.addActionListener(this); popup.add(item);
2085 item = new JMenuItem("Lens correction"); item.addActionListener(this); popup.add(item);
2086 item = new JMenuItem("Blend"); item.addActionListener(this); popup.add(item);
2088 item = new JMenuItem("Remove alpha mask"); item.addActionListener(this); popup.add(item);
2089 if ( ! ((Patch)active).hasAlphaMask()) item.setEnabled(false);
2090 item = new JMenuItem("Link images..."); item.addActionListener(this); popup.add(item);
2091 item = new JMenuItem("View volume"); item.addActionListener(this); popup.add(item);
2092 HashSet hs = active.getLinked(Patch.class);
2093 if (null == hs || 0 == hs.size()) item.setEnabled(false);
2094 item = new JMenuItem("View orthoslices"); item.addActionListener(this); popup.add(item);
2095 if (null == hs || 0 == hs.size()) item.setEnabled(false); // if no Patch instances among the directly linked, then it's not a stack
2096 popup.addSeparator();
2097 } else {
2098 item = new JMenuItem("Unlink"); item.addActionListener(this); popup.add(item);
2099 item = new JMenuItem("Show in 3D"); item.addActionListener(this); popup.add(item);
2100 popup.addSeparator();
2102 if (active instanceof AreaList) {
2103 item = new JMenuItem("Merge"); item.addActionListener(this); popup.add(item);
2104 ArrayList al = selection.getSelected();
2105 int n = 0;
2106 for (Iterator it = al.iterator(); it.hasNext(); ) {
2107 if (it.next().getClass() == AreaList.class) n++;
2109 if (n < 2) item.setEnabled(false);
2110 } else if (active instanceof Pipe) {
2111 item = new JMenuItem("Identify..."); item.addActionListener(this); popup.add(item);
2112 item = new JMenuItem("Identify with axes..."); item.addActionListener(this); popup.add(item);
2115 if (canvas.isTransforming()) {
2116 item = new JMenuItem("Apply transform"); item.addActionListener(this); popup.add(item);
2117 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
2118 item = new JMenuItem("Apply transform propagating to last layer"); item.addActionListener(this); popup.add(item);
2119 if (layer.getParent().indexOf(layer) == layer.getParent().size() -1) item.setEnabled(false);
2120 item = new JMenuItem("Apply transform propagating to first layer"); item.addActionListener(this); popup.add(item);
2121 if (0 == layer.getParent().indexOf(layer)) item.setEnabled(false);
2122 } else {
2123 item = new JMenuItem("Transform"); item.addActionListener(this); popup.add(item);
2124 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0, true));
2126 item = new JMenuItem("Cancel transform"); item.addActionListener(this); popup.add(item);
2127 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2128 if (!canvas.isTransforming()) item.setEnabled(false);
2129 if (canvas.isTransforming()) {
2130 item = new JMenuItem("Specify transform..."); item.addActionListener(this); popup.add(item);
2133 if (!canvas.isTransforming()) {
2134 item = new JMenuItem("Duplicate"); item.addActionListener(this); popup.add(item);
2135 item = new JMenuItem("Color..."); item.addActionListener(this); popup.add(item);
2136 if (active instanceof LayerSet) item.setEnabled(false);
2137 if (active.isLocked()) {
2138 item = new JMenuItem("Unlock"); item.addActionListener(this); popup.add(item);
2139 } else {
2140 item = new JMenuItem("Lock"); item.addActionListener(this); popup.add(item);
2142 menu = new JMenu("Move");
2143 popup.addSeparator();
2144 LayerSet ls = layer.getParent();
2145 item = new JMenuItem("Move to top"); item.addActionListener(this); menu.add(item);
2146 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0, true)); // this is just to draw the key name by the menu; it does not incur on any event being generated (that I know if), and certainly not any event being listened to by TrakEM2.
2147 if (ls.isTop(active)) item.setEnabled(false);
2148 item = new JMenuItem("Move up"); item.addActionListener(this); menu.add(item);
2149 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0, true));
2150 if (ls.isTop(active)) item.setEnabled(false);
2151 item = new JMenuItem("Move down"); item.addActionListener(this); menu.add(item);
2152 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0, true));
2153 if (ls.isBottom(active)) item.setEnabled(false);
2154 item = new JMenuItem("Move to bottom"); item.addActionListener(this); menu.add(item);
2155 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_END, 0, true));
2156 if (ls.isBottom(active)) item.setEnabled(false);
2158 popup.add(menu);
2159 popup.addSeparator();
2160 item = new JMenuItem("Delete..."); item.addActionListener(this); popup.add(item);
2161 try {
2162 if (active instanceof Patch) {
2163 if (!active.isOnlyLinkedTo(Patch.class)) {
2164 item.setEnabled(false);
2166 } else if (!(active instanceof DLabel)) { // can't delete elements from the trees (Profile, Pipe, LayerSet)
2167 item.setEnabled(false);
2169 } catch (Exception e) { IJError.print(e); item.setEnabled(false); }
2171 if (active instanceof Patch) {
2172 item = new JMenuItem("Revert"); item.addActionListener(this); popup.add(item);
2173 popup.addSeparator();
2175 item = new JMenuItem("Properties..."); item.addActionListener(this); popup.add(item);
2176 item = new JMenuItem("Show centered"); item.addActionListener(this); popup.add(item);
2178 popup.addSeparator();
2180 if (! (active instanceof ZDisplayable)) {
2181 ArrayList al_layers = layer.getParent().getLayers();
2182 int i_layer = al_layers.indexOf(layer);
2183 int n_layers = al_layers.size();
2184 item = new JMenuItem("Send to previous layer"); item.addActionListener(this); popup.add(item);
2185 if (1 == n_layers || 0 == i_layer || active.isLinked()) item.setEnabled(false);
2186 // check if the active is a profile and contains a link to another profile in the layer it is going to be sent to, or it is linked
2187 else if (active instanceof Profile && !active.canSendTo(layer.getParent().previous(layer))) item.setEnabled(false);
2188 item = new JMenuItem("Send to next layer"); item.addActionListener(this); popup.add(item);
2189 if (1 == n_layers || n_layers -1 == i_layer || active.isLinked()) item.setEnabled(false);
2190 else if (active instanceof Profile && !active.canSendTo(layer.getParent().next(layer))) item.setEnabled(false);
2193 menu = new JMenu("Send linked group to...");
2194 if (active.hasLinkedGroupWithinLayer(this.layer)) {
2195 int i = 1;
2196 for (final Layer la : ls.getLayers()) {
2197 String layer_title = i + ": " + la.getTitle();
2198 if (-1 == layer_title.indexOf(' ')) layer_title += " ";
2199 item = new JMenuItem(layer_title); item.addActionListener(this); menu.add(item);
2200 if (la == this.layer) item.setEnabled(false);
2201 i++;
2203 popup.add(menu);
2204 } else {
2205 menu.setEnabled(false);
2206 //Utils.log("Active's linked group not within layer.");
2208 popup.add(menu);
2209 popup.addSeparator();
2214 if (!canvas.isTransforming()) {
2216 item = new JMenuItem("Undo");item.addActionListener(this); popup.add(item);
2217 if (!layer.getParent().canUndo() || canvas.isTransforming()) item.setEnabled(false);
2218 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Utils.getControlModifier(), true));
2219 item = new JMenuItem("Redo");item.addActionListener(this); popup.add(item);
2220 if (!layer.getParent().canRedo() || canvas.isTransforming()) item.setEnabled(false);
2221 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Event.SHIFT_MASK | Event.CTRL_MASK, true));
2222 popup.addSeparator();
2224 // Would get so much simpler with a clojure macro ...
2226 try {
2227 menu = new JMenu("Hide/Unhide");
2228 item = new JMenuItem("Hide deselected"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.SHIFT_MASK, true));
2229 boolean none = 0 == selection.getNSelected();
2230 if (none) item.setEnabled(false);
2231 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));
2232 if (none) item.setEnabled(false);
2233 item = new JMenuItem("Hide selected"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, 0, true));
2234 if (none) item.setEnabled(false);
2235 none = ! layer.getParent().containsDisplayable(DLabel.class);
2236 item = new JMenuItem("Hide all labels"); item.addActionListener(this); menu.add(item);
2237 if (none) item.setEnabled(false);
2238 item = new JMenuItem("Unhide all labels"); item.addActionListener(this); menu.add(item);
2239 if (none) item.setEnabled(false);
2240 none = ! layer.getParent().contains(AreaList.class);
2241 item = new JMenuItem("Hide all arealists"); item.addActionListener(this); menu.add(item);
2242 if (none) item.setEnabled(false);
2243 item = new JMenuItem("Unhide all arealists"); item.addActionListener(this); menu.add(item);
2244 if (none) item.setEnabled(false);
2245 none = ! layer.contains(Profile.class);
2246 item = new JMenuItem("Hide all profiles"); item.addActionListener(this); menu.add(item);
2247 if (none) item.setEnabled(false);
2248 item = new JMenuItem("Unhide all profiles"); item.addActionListener(this); menu.add(item);
2249 if (none) item.setEnabled(false);
2250 none = ! layer.getParent().contains(Pipe.class);
2251 item = new JMenuItem("Hide all pipes"); item.addActionListener(this); menu.add(item);
2252 if (none) item.setEnabled(false);
2253 item = new JMenuItem("Unhide all pipes"); item.addActionListener(this); menu.add(item);
2254 if (none) item.setEnabled(false);
2255 none = ! layer.getParent().contains(Polyline.class);
2256 item = new JMenuItem("Hide all polylines"); item.addActionListener(this); menu.add(item);
2257 if (none) item.setEnabled(false);
2258 item = new JMenuItem("Unhide all polylines"); item.addActionListener(this); menu.add(item);
2259 if (none) item.setEnabled(false);
2260 none = ! layer.getParent().contains(Ball.class);
2261 item = new JMenuItem("Hide all balls"); item.addActionListener(this); menu.add(item);
2262 if (none) item.setEnabled(false);
2263 item = new JMenuItem("Unhide all balls"); item.addActionListener(this); menu.add(item);
2264 if (none) item.setEnabled(false);
2265 none = ! layer.getParent().containsDisplayable(Patch.class);
2266 item = new JMenuItem("Hide all images"); item.addActionListener(this); menu.add(item);
2267 if (none) item.setEnabled(false);
2268 item = new JMenuItem("Unhide all images"); item.addActionListener(this); menu.add(item);
2269 if (none) item.setEnabled(false);
2270 item = new JMenuItem("Hide all but images"); item.addActionListener(this); menu.add(item);
2271 item = new JMenuItem("Unhide all"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.ALT_MASK, true));
2273 popup.add(menu);
2274 } catch (Exception e) { IJError.print(e); }
2276 JMenu adjust_menu = new JMenu("Adjust");
2277 item = new JMenuItem("Calibration..."); item.addActionListener(this); adjust_menu.add(item);
2278 item = new JMenuItem("Enhance contrast layer-wise..."); item.addActionListener(this); adjust_menu.add(item);
2279 item = new JMenuItem("Enhance contrast (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
2280 if (selection.isEmpty()) item.setEnabled(false);
2281 item = new JMenuItem("Set Min and Max layer-wise..."); item.addActionListener(this); adjust_menu.add(item);
2282 item = new JMenuItem("Set Min and Max (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
2283 if (selection.isEmpty()) item.setEnabled(false);
2284 popup.add(adjust_menu);
2286 menu = new JMenu("Import");
2287 item = new JMenuItem("Import image"); item.addActionListener(this); menu.add(item);
2288 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, Event.ALT_MASK & Event.SHIFT_MASK, true));
2289 item = new JMenuItem("Import stack..."); item.addActionListener(this); menu.add(item);
2290 item = new JMenuItem("Import grid..."); item.addActionListener(this); menu.add(item);
2291 item = new JMenuItem("Import sequence as grid..."); item.addActionListener(this); menu.add(item);
2292 item = new JMenuItem("Import from text file..."); item.addActionListener(this); menu.add(item);
2293 item = new JMenuItem("Import labels as arealists..."); item.addActionListener(this); menu.add(item);
2294 popup.add(menu);
2296 menu = new JMenu("Export");
2297 item = new JMenuItem("Make flat image..."); item.addActionListener(this); menu.add(item);
2298 item = new JMenuItem("Arealists as labels (tif)"); item.addActionListener(this); menu.add(item);
2299 if (0 == layer.getParent().getZDisplayables(AreaList.class).size()) item.setEnabled(false);
2300 item = new JMenuItem("Arealists as labels (amira)"); item.addActionListener(this); menu.add(item);
2301 if (0 == layer.getParent().getZDisplayables(AreaList.class).size()) item.setEnabled(false);
2302 popup.add(menu);
2304 menu = new JMenu("Display");
2305 item = new JMenuItem("Resize canvas/LayerSet..."); item.addActionListener(this); menu.add(item);
2306 item = new JMenuItem("Autoresize canvas/LayerSet"); item.addActionListener(this); menu.add(item);
2307 // OBSOLETE // item = new JMenuItem("Rotate Layer/LayerSet..."); item.addActionListener(this); menu.add(item);
2308 item = new JMenuItem("Properties ..."); item.addActionListener(this); menu.add(item);
2309 popup.add(menu);
2311 menu = new JMenu("Project");
2312 this.project.getLoader().setupMenuItems(menu, this.getProject());
2313 item = new JMenuItem("Project properties..."); item.addActionListener(this); menu.add(item);
2314 item = new JMenuItem("Create subproject"); item.addActionListener(this); menu.add(item);
2315 if (null == canvas.getFakeImagePlus().getRoi()) item.setEnabled(false);
2316 item = new JMenuItem("Release memory..."); item.addActionListener(this); menu.add(item);
2317 item = new JMenuItem("Flush image cache"); item.addActionListener(this); menu.add(item);
2318 item = new JMenuItem("Regenerate all mipmaps"); item.addActionListener(this); menu.add(item);
2319 popup.add(menu);
2321 menu = new JMenu("Selection");
2322 item = new JMenuItem("Select all"); item.addActionListener(this); menu.add(item);
2323 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, Utils.getControlModifier(), true));
2324 if (0 == layer.getDisplayables().size() && 0 == layer.getParent().getZDisplayables().size()) item.setEnabled(false);
2325 item = new JMenuItem("Select none"); item.addActionListener(this); menu.add(item);
2326 if (0 == selection.getNSelected()) item.setEnabled(false);
2327 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2329 JMenu bytype = new JMenu("Select all by type");
2330 item = new JMenuItem("AreaList"); item.addActionListener(bytypelistener); bytype.add(item);
2331 item = new JMenuItem("Ball"); item.addActionListener(bytypelistener); bytype.add(item);
2332 item = new JMenuItem("Dissector"); item.addActionListener(bytypelistener); bytype.add(item);
2333 item = new JMenuItem("Image"); item.addActionListener(bytypelistener); bytype.add(item);
2334 item = new JMenuItem("Text"); item.addActionListener(bytypelistener); bytype.add(item);
2335 item = new JMenuItem("Pipe"); item.addActionListener(bytypelistener); bytype.add(item);
2336 item = new JMenuItem("Polyline"); item.addActionListener(bytypelistener); bytype.add(item);
2337 item = new JMenuItem("Profile"); item.addActionListener(bytypelistener); bytype.add(item);
2338 menu.add(bytype);
2340 item = new JMenuItem("Restore selection"); item.addActionListener(this); menu.add(item);
2341 item = new JMenuItem("Select under ROI"); item.addActionListener(this); menu.add(item);
2342 if (canvas.getFakeImagePlus().getRoi() == null) item.setEnabled(false);
2343 popup.add(menu);
2344 item = new JMenuItem("Search..."); item.addActionListener(this); popup.add(item);
2347 //canvas.add(popup);
2348 return popup;
2351 private ByTypeListener bytypelistener = new ByTypeListener(this);
2353 static private class ByTypeListener implements ActionListener {
2354 final Display d;
2355 ByTypeListener(final Display d) {
2356 this.d = d;
2358 public void actionPerformed(final ActionEvent ae) {
2359 final String command = ae.getActionCommand();
2361 final java.awt.geom.Area aroi = M.getArea(d.canvas.getFakeImagePlus().getRoi());
2363 d.dispatcher.exec(new Runnable() { public void run() {
2365 try {
2366 String type = command;
2367 if (type.equals("Image")) type = "Patch";
2368 Class c = Class.forName("ini.trakem2.display." + type);
2370 java.util.List<Displayable> a = new ArrayList<Displayable>();
2371 if (null != aroi) {
2372 a.addAll(d.layer.getDisplayables(c, aroi, true));
2373 a.addAll(d.layer.getParent().getZDisplayables(c, d.layer, aroi, true));
2374 } else {
2375 a.addAll(d.layer.getDisplayables(c));
2376 a.addAll(d.layer.getParent().getZDisplayables(c));
2377 // Remove non-visible ones
2378 for (final Iterator<Displayable> it = a.iterator(); it.hasNext(); ) {
2379 if (!it.next().isVisible()) it.remove();
2383 if (0 == a.size()) return;
2385 boolean selected = false;
2387 if (0 == ae.getModifiers()) {
2388 Utils.log2("first");
2389 d.selection.clear();
2390 d.selection.selectAll(a);
2391 selected = true;
2392 } else if (0 == (ae.getModifiers() ^ Event.SHIFT_MASK)) {
2393 Utils.log2("with shift");
2394 d.selection.selectAll(a); // just add them to the current selection
2395 selected = true;
2397 if (selected) {
2398 // Activate last:
2399 d.selection.setActive(a.get(a.size() -1));
2402 } catch (ClassNotFoundException e) {
2403 Utils.log2(e.toString());
2406 }});
2410 /** Check if a panel for the given Displayable is completely visible in the JScrollPane */
2411 public boolean isWithinViewport(final Displayable d) {
2412 final JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
2413 if (ht_tabs.get(d.getClass()) == scroll) return isWithinViewport(scroll, ht_panels.get(d));
2414 return false;
2417 private boolean isWithinViewport(JScrollPane scroll, DisplayablePanel dp) {
2418 if(null == dp) return false;
2419 JViewport view = scroll.getViewport();
2420 java.awt.Dimension dimensions = view.getExtentSize();
2421 java.awt.Point p = view.getViewPosition();
2422 int y = dp.getY();
2423 if ((y + DisplayablePanel.HEIGHT - p.y) <= dimensions.height && y >= p.y) {
2424 return true;
2426 return false;
2429 /** Check if a panel for the given Displayable is partially visible in the JScrollPane */
2430 public boolean isPartiallyWithinViewport(final Displayable d) {
2431 final JScrollPane scroll = ht_tabs.get(d.getClass());
2432 if (tabs.getSelectedComponent() == scroll) return isPartiallyWithinViewport(scroll, ht_panels.get(d));
2433 return false;
2436 /** Check if a panel for the given Displayable is at least partially visible in the JScrollPane */
2437 private boolean isPartiallyWithinViewport(final JScrollPane scroll, final DisplayablePanel dp) {
2438 if(null == dp) {
2439 //Utils.log2("Display.isPartiallyWithinViewport: null DisplayablePanel ??");
2440 return false; // to fast for you baby
2442 JViewport view = scroll.getViewport();
2443 java.awt.Dimension dimensions = view.getExtentSize();
2444 java.awt.Point p = view.getViewPosition();
2445 int y = dp.getY();
2446 if ( ((y + DisplayablePanel.HEIGHT - p.y) <= dimensions.height && y >= p.y) // completely visible
2447 || ((y + DisplayablePanel.HEIGHT - p.y) > dimensions.height && y < p.y + dimensions.height) // partially hovering at the bottom
2448 || ((y + DisplayablePanel.HEIGHT) > p.y && y < p.y) // partially hovering at the top
2450 return true;
2452 return false;
2455 /** A function to make a Displayable panel be visible in the screen, by scrolling the viewport of the JScrollPane. */
2456 private void scrollToShow(final Displayable d) {
2457 dispatcher.execSwing(new Runnable() { public void run() {
2458 final JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
2459 if (d instanceof ZDisplayable && scroll == scroll_zdispl) {
2460 scrollToShow(scroll_zdispl, ht_panels.get(d));
2461 return;
2463 final Class c = d.getClass();
2464 if (Patch.class == c && scroll == scroll_patches) {
2465 scrollToShow(scroll_patches, ht_panels.get(d));
2466 } else if (DLabel.class == c && scroll == scroll_labels) {
2467 scrollToShow(scroll_labels, ht_panels.get(d));
2468 } else if (Profile.class == c && scroll == scroll_profiles) {
2469 scrollToShow(scroll_profiles, ht_panels.get(d));
2471 }});
2474 private void scrollToShow(final JScrollPane scroll, final JPanel dp) {
2475 if (null == dp) return;
2476 JViewport view = scroll.getViewport();
2477 Point current = view.getViewPosition();
2478 Dimension extent = view.getExtentSize();
2479 int panel_y = dp.getY();
2480 if ((panel_y + DisplayablePanel.HEIGHT - current.y) <= extent.height && panel_y >= current.y) {
2481 // it's completely visible already
2482 return;
2483 } else {
2484 // scroll just enough
2485 // if it's above, show at the top
2486 if (panel_y - current.y < 0) {
2487 view.setViewPosition(new Point(0, panel_y));
2489 // if it's below (even if partially), show at the bottom
2490 else if (panel_y + 50 > current.y + extent.height) {
2491 view.setViewPosition(new Point(0, panel_y - extent.height + 50));
2492 //Utils.log("Display.scrollToShow: panel_y: " + panel_y + " current.y: " + current.y + " extent.height: " + extent.height);
2497 /** Update the title of the given Displayable in its DisplayablePanel, if any. */
2498 static public void updateTitle(final Layer layer, final Displayable displ) {
2499 for (final Display d : al_displays) {
2500 if (layer == d.layer) {
2501 DisplayablePanel dp = d.ht_panels.get(displ);
2502 if (null != dp) dp.updateTitle();
2507 /** Update the Display's title in all Displays showing the given Layer. */
2508 static public void updateTitle(final Layer layer) {
2509 for (final Display d : al_displays) {
2510 if (d.layer == layer) {
2511 d.updateFrameTitle();
2515 /** Update the Display's title in all Displays showing a Layer of the given LayerSet. */
2516 static public void updateTitle(final LayerSet ls) {
2517 for (final Display d : al_displays) {
2518 if (d.layer.getParent() == ls) {
2519 d.updateFrameTitle();
2524 /** Set a new title in the JFrame, showing info on the layer 'z' and the magnification. */
2525 public void updateFrameTitle() {
2526 updateFrameTitle(layer);
2528 private void updateFrameTitle(Layer layer) {
2529 // From ij.ImagePlus class, the solution:
2530 String scale = "";
2531 final double magnification = canvas.getMagnification();
2532 if (magnification!=1.0) {
2533 final double percent = magnification*100.0;
2534 scale = new StringBuilder(" (").append(Utils.d2s(percent, percent==(int)percent ? 0 : 1)).append("%)").toString();
2536 final Calibration cal = layer.getParent().getCalibration();
2537 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();
2538 frame.setTitle(title);
2539 // fix the title for the FakeImageWindow and thus the WindowManager listing in the menus
2540 canvas.getFakeImagePlus().setTitle(title);
2543 /** 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. */
2544 public void nextLayer(final int modifiers) {
2545 final Layer l;
2546 if (0 == (modifiers ^ Event.SHIFT_MASK)) {
2547 l = layer.getParent().nextNonEmpty(layer);
2548 } else if (scroll_step > 1) {
2549 int i = layer.getParent().indexOf(this.layer);
2550 Layer la = layer.getParent().getLayer(i + scroll_step);
2551 if (null != la) l = la;
2552 else l = null;
2553 } else {
2554 l = layer.getParent().next(layer);
2556 if (l != layer) {
2557 slt.set(l);
2558 updateInDatabase("layer_id");
2562 private final void translateLayerColors(final Layer current, final Layer other) {
2563 if (current == other) return;
2564 if (layer_channels.size() > 0) {
2565 final LayerSet ls = getLayerSet();
2566 // translate colors by distance from current layer to new Layer l
2567 final int dist = ls.indexOf(other) - ls.indexOf(current);
2568 translateLayerColor(Color.red, dist);
2569 translateLayerColor(Color.blue, dist);
2573 private final void translateLayerColor(final Color color, final int dist) {
2574 final LayerSet ls = getLayerSet();
2575 final Layer l = layer_channels.get(color);
2576 if (null == l) return;
2577 updateColor(Color.white, layer_panels.get(l));
2578 final Layer l2 = ls.getLayer(ls.indexOf(l) + dist);
2579 if (null != l2) updateColor(color, layer_panels.get(l2));
2582 private final void updateColor(final Color color, final LayerPanel lp) {
2583 lp.setColor(color);
2584 setColorChannel(lp.layer, color);
2587 /** Calls setLayer(la) on the SetLayerThread. */
2588 public void toLayer(final Layer la) {
2589 if (la.getParent() != layer.getParent()) return; // not of the same LayerSet
2590 if (la == layer) return; // nothing to do
2591 slt.set(la);
2592 updateInDatabase("layer_id");
2595 /** 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. */
2596 public void previousLayer(final int modifiers) {
2597 final Layer l;
2598 if (0 == (modifiers ^ Event.SHIFT_MASK)) {
2599 l = layer.getParent().previousNonEmpty(layer);
2600 } else if (scroll_step > 1) {
2601 int i = layer.getParent().indexOf(this.layer);
2602 Layer la = layer.getParent().getLayer(i - scroll_step);
2603 if (null != la) l = la;
2604 else l = null;
2605 } else {
2606 l = layer.getParent().previous(layer);
2608 if (l != layer) {
2609 slt.set(l);
2610 updateInDatabase("layer_id");
2614 static public void updateLayerScroller(LayerSet set) {
2615 for (final Display d : al_displays) {
2616 if (d.layer.getParent() == set) {
2617 d.updateLayerScroller(d.layer);
2622 private void updateLayerScroller(Layer layer) {
2623 int size = layer.getParent().size();
2624 if (size <= 1) {
2625 scroller.setValues(0, 1, 0, 0);
2626 scroller.setEnabled(false);
2627 } else {
2628 scroller.setEnabled(true);
2629 scroller.setValues(layer.getParent().getLayerIndex(layer.getId()), 1, 0, size);
2631 recreateLayerPanels(layer);
2634 // Can't use this.layer, may still be null. User argument instead.
2635 private synchronized void recreateLayerPanels(final Layer layer) {
2636 synchronized (layer_channels) {
2637 panel_layers.removeAll();
2639 if (0 == layer_panels.size()) {
2640 for (final Layer la : layer.getParent().getLayers()) {
2641 final LayerPanel lp = new LayerPanel(this, la);
2642 layer_panels.put(la, lp);
2643 this.panel_layers.add(lp);
2645 } else {
2646 // Set theory at work: keep old to reuse
2647 layer_panels.keySet().retainAll(layer.getParent().getLayers());
2648 for (final Layer la : layer.getParent().getLayers()) {
2649 LayerPanel lp = layer_panels.get(la);
2650 if (null == lp) {
2651 lp = new LayerPanel(this, la);
2652 layer_panels.put(la, lp);
2654 this.panel_layers.add(lp);
2656 for (final Iterator<Map.Entry<Integer,LayerPanel>> it = layer_alpha.entrySet().iterator(); it.hasNext(); ) {
2657 final Map.Entry<Integer,LayerPanel> e = it.next();
2658 if (-1 == getLayerSet().indexOf(e.getValue().layer)) it.remove();
2660 for (final Iterator<Map.Entry<Color,Layer>> it = layer_channels.entrySet().iterator(); it.hasNext(); ) {
2661 final Map.Entry<Color,Layer> e = it.next();
2662 if (-1 == getLayerSet().indexOf(e.getValue())) it.remove();
2664 scroll_layers.repaint();
2669 private void updateSnapshots() {
2670 Enumeration<DisplayablePanel> e = ht_panels.elements();
2671 while (e.hasMoreElements()) {
2672 e.nextElement().remake();
2674 Utils.updateComponent(tabs.getSelectedComponent());
2677 static public void updatePanel(Layer layer, final Displayable displ) {
2678 if (null == layer && null != front) layer = front.layer; // the front layer
2679 for (final Display d : al_displays) {
2680 if (d.layer == layer) {
2681 d.updatePanel(displ);
2686 private void updatePanel(Displayable d) {
2687 JPanel c = null;
2688 if (d instanceof Profile) {
2689 c = panel_profiles;
2690 } else if (d instanceof Patch) {
2691 c = panel_patches;
2692 } else if (d instanceof DLabel) {
2693 c = panel_labels;
2694 } else if (d instanceof Pipe) {
2695 c = panel_zdispl;
2697 if (null == c) return;
2698 DisplayablePanel dp = ht_panels.get(d);
2699 dp.remake();
2700 Utils.updateComponent(c);
2703 static public void updatePanelIndex(final Layer layer, final Displayable displ) {
2704 for (final Display d : al_displays) {
2705 if (d.layer == layer || displ instanceof ZDisplayable) {
2706 d.updatePanelIndex(displ);
2711 private void updatePanelIndex(final Displayable d) {
2712 // find first of the kind, then remove and insert its panel
2713 int i = 0;
2714 JPanel c = null;
2715 if (d instanceof ZDisplayable) {
2716 i = layer.getParent().indexOf((ZDisplayable)d);
2717 c = panel_zdispl;
2718 } else {
2719 i = layer.relativeIndexOf(d);
2720 if (d instanceof Profile) {
2721 c = panel_profiles;
2722 } else if (d instanceof Patch) {
2723 c = panel_patches;
2724 } else if (d instanceof DLabel) {
2725 c = panel_labels;
2728 if (null == c) return;
2729 DisplayablePanel dp = ht_panels.get(d);
2730 if (null == dp) return; // may be half-baked, wait
2731 c.remove(dp);
2732 c.add(dp, i); // java and its fabulous consistency
2733 // not enough! Utils.updateComponent(c);
2734 // So, cocktail:
2735 c.invalidate();
2736 c.validate();
2737 Utils.updateComponent(c);
2740 /** Repair possibly missing panels and other components by simply resetting the same Layer */
2741 public void repairGUI() {
2742 Layer layer = this.layer;
2743 this.layer = null;
2744 setLayer(layer);
2747 public void actionPerformed(final ActionEvent ae) {
2748 dispatcher.exec(new Runnable() { public void run() {
2750 String command = ae.getActionCommand();
2751 if (command.startsWith("Job")) {
2752 if (Utils.checkYN("Really cancel job?")) {
2753 project.getLoader().quitJob(command);
2754 repairGUI();
2756 return;
2757 } else if (command.equals("Move to top")) {
2758 if (null == active) return;
2759 canvas.setUpdateGraphics(true);
2760 layer.getParent().move(LayerSet.TOP, active);
2761 Display.repaint(layer.getParent(), active, 5);
2762 //Display.updatePanelIndex(layer, active);
2763 } else if (command.equals("Move up")) {
2764 if (null == active) return;
2765 canvas.setUpdateGraphics(true);
2766 layer.getParent().move(LayerSet.UP, active);
2767 Display.repaint(layer.getParent(), active, 5);
2768 //Display.updatePanelIndex(layer, active);
2769 } else if (command.equals("Move down")) {
2770 if (null == active) return;
2771 canvas.setUpdateGraphics(true);
2772 layer.getParent().move(LayerSet.DOWN, active);
2773 Display.repaint(layer.getParent(), active, 5);
2774 //Display.updatePanelIndex(layer, active);
2775 } else if (command.equals("Move to bottom")) {
2776 if (null == active) return;
2777 canvas.setUpdateGraphics(true);
2778 layer.getParent().move(LayerSet.BOTTOM, active);
2779 Display.repaint(layer.getParent(), active, 5);
2780 //Display.updatePanelIndex(layer, active);
2781 } else if (command.equals("Duplicate, link and send to next layer")) {
2782 duplicateLinkAndSendTo(active, 1, layer.getParent().next(layer));
2783 } else if (command.equals("Duplicate, link and send to previous layer")) {
2784 duplicateLinkAndSendTo(active, 0, layer.getParent().previous(layer));
2785 } else if (command.equals("Duplicate, link and send to...")) {
2786 // fix non-scrolling popup menu
2787 GenericDialog gd = new GenericDialog("Send to");
2788 gd.addMessage("Duplicate, link and send to...");
2789 String[] sl = new String[layer.getParent().size()];
2790 int next = 0;
2791 for (Iterator it = layer.getParent().getLayers().iterator(); it.hasNext(); ) {
2792 sl[next++] = project.findLayerThing(it.next()).toString();
2794 gd.addChoice("Layer: ", sl, sl[layer.getParent().indexOf(layer)]);
2795 gd.showDialog();
2796 if (gd.wasCanceled()) return;
2797 Layer la = layer.getParent().getLayer(gd.getNextChoiceIndex());
2798 if (layer == la) {
2799 Utils.showMessage("Can't duplicate, link and send to the same layer.");
2800 return;
2802 duplicateLinkAndSendTo(active, 0, la);
2803 } else if (-1 != command.indexOf("z = ")) {
2804 // this is an item from the "Duplicate, link and send to" menu of layer z's
2805 Layer target_layer = layer.getParent().getLayer(Double.parseDouble(command.substring(command.lastIndexOf(' ') +1)));
2806 Utils.log2("layer: __" +command.substring(command.lastIndexOf(' ') +1) + "__");
2807 if (null == target_layer) return;
2808 duplicateLinkAndSendTo(active, 0, target_layer);
2809 } else if (-1 != command.indexOf("z=")) {
2810 // WARNING the indexOf is very similar to the previous one
2811 // Send the linked group to the selected layer
2812 int iz = command.indexOf("z=")+2;
2813 Utils.log2("iz=" + iz + " other: " + command.indexOf(' ', iz+2));
2814 int end = command.indexOf(' ', iz);
2815 if (-1 == end) end = command.length();
2816 double lz = Double.parseDouble(command.substring(iz, end));
2817 Layer target = layer.getParent().getLayer(lz);
2818 HashSet hs = active.getLinkedGroup(new HashSet());
2819 layer.getParent().move(hs, active.getLayer(), target);
2820 } else if (command.equals("Unlink")) {
2821 if (null == active || active instanceof Patch) return;
2822 active.unlink();
2823 updateSelection();//selection.update();
2824 } else if (command.equals("Unlink from images")) {
2825 if (null == active) return;
2826 try {
2827 for (Displayable displ: selection.getSelected()) {
2828 displ.unlinkAll(Patch.class);
2830 updateSelection();//selection.update();
2831 } catch (Exception e) { IJError.print(e); }
2832 } else if (command.equals("Unlink slices")) {
2833 YesNoCancelDialog yn = new YesNoCancelDialog(frame, "Attention", "Really unlink all slices from each other?\nThere is no undo.");
2834 if (!yn.yesPressed()) return;
2835 final ArrayList<Patch> pa = ((Patch)active).getStackPatches();
2836 for (int i=pa.size()-1; i>0; i--) {
2837 pa.get(i).unlink(pa.get(i-1));
2839 } else if (command.equals("Send to next layer")) {
2840 Rectangle box = selection.getBox();
2841 try {
2842 // unlink Patch instances
2843 for (final Displayable displ : selection.getSelected()) {
2844 displ.unlinkAll(Patch.class);
2846 updateSelection();//selection.update();
2847 } catch (Exception e) { IJError.print(e); }
2848 //layer.getParent().moveDown(layer, active); // will repaint whatever appropriate layers
2849 selection.moveDown();
2850 repaint(layer.getParent(), box);
2851 } else if (command.equals("Send to previous layer")) {
2852 Rectangle box = selection.getBox();
2853 try {
2854 // unlink Patch instances
2855 for (final Displayable displ : selection.getSelected()) {
2856 displ.unlinkAll(Patch.class);
2858 updateSelection();//selection.update();
2859 } catch (Exception e) { IJError.print(e); }
2860 //layer.getParent().moveUp(layer, active); // will repaint whatever appropriate layers
2861 selection.moveUp();
2862 repaint(layer.getParent(), box);
2863 } else if (command.equals("Show centered")) {
2864 if (active == null) return;
2865 showCentered(active);
2866 } else if (command.equals("Delete...")) {
2868 if (null != active) {
2869 Displayable d = active;
2870 selection.remove(d);
2871 d.remove(true); // will repaint
2874 // remove all selected objects
2875 selection.deleteAll();
2876 } else if (command.equals("Color...")) {
2877 IJ.doCommand("Color Picker...");
2878 } else if (command.equals("Revert")) {
2879 if (null == active || active.getClass() != Patch.class) return;
2880 Patch p = (Patch)active;
2881 if (!p.revert()) {
2882 if (null == p.getOriginalPath()) Utils.log("No editions to save for patch " + p.getTitle() + " #" + p.getId());
2883 else Utils.log("Could not revert Patch " + p.getTitle() + " #" + p.getId());
2885 } else if (command.equals("Remove alpha mask")) {
2886 final ArrayList<Displayable> patches = selection.getSelected(Patch.class);
2887 if (patches.size() > 0) {
2888 Bureaucrat.createAndStart(new Worker.Task("Removing alpha mask" + (patches.size() > 1 ? "s" : "")) { public void exec() {
2889 final ArrayList<Future> jobs = new ArrayList<Future>();
2890 for (final Displayable d : patches) {
2891 final Patch p = (Patch) d;
2892 p.setAlphaMask(null);
2893 Future job = p.getProject().getLoader().regenerateMipMaps(p); // submit to queue
2894 if (null != job) jobs.add(job);
2896 // join all
2897 for (final Future job : jobs) try {
2898 job.get();
2899 } catch (Exception ie) {}
2900 }}, patches.get(0).getProject());
2902 } else if (command.equals("Undo")) {
2903 Bureaucrat.createAndStart(new Worker.Task("Undo") { public void exec() {
2904 layer.getParent().undoOneStep();
2905 Display.repaint(layer.getParent());
2906 }}, project);
2907 } else if (command.equals("Redo")) {
2908 Bureaucrat.createAndStart(new Worker.Task("Redo") { public void exec() {
2909 layer.getParent().redoOneStep();
2910 Display.repaint(layer.getParent());
2911 }}, project);
2912 } else if (command.equals("Transform")) {
2913 if (null == active) return;
2914 canvas.setTransforming(true);
2915 } else if (command.equals("Apply transform")) {
2916 if (null == active) return;
2917 canvas.setTransforming(false);
2918 } else if (command.equals("Apply transform propagating to last layer")) {
2919 if (selection.isTransforming()) {
2920 final java.util.List<Layer> layers = layer.getParent().getLayers();
2921 selection.applyAndPropagate(new HashSet<Layer>(layers.subList(layers.indexOf(Display.this.layer)+1, layers.size()))); // +1 to exclude current layer
2923 } else if (command.equals("Apply transform propagating to first layer")) {
2924 if (selection.isTransforming()) {
2925 final java.util.List<Layer> layers = layer.getParent().getLayers();
2926 selection.applyAndPropagate(new HashSet<Layer>(layers.subList(0, layers.indexOf(Display.this.layer))));
2928 } else if (command.equals("Cancel transform")) {
2929 if (null == active) return;
2930 canvas.cancelTransform();
2931 } else if (command.equals("Specify transform...")) {
2932 if (null == active) return;
2933 selection.specify();
2934 } else if (command.equals("Hide all but images")) {
2935 ArrayList<Class> type = new ArrayList<Class>();
2936 type.add(Patch.class);
2937 selection.removeAll(layer.getParent().hideExcept(type, false));
2938 Display.update(layer.getParent(), false);
2939 } else if (command.equals("Unhide all")) {
2940 layer.getParent().setAllVisible(false);
2941 Display.update(layer.getParent(), false);
2942 } else if (command.startsWith("Hide all ")) {
2943 String type = command.substring(9, command.length() -1); // skip the ending plural 's'
2944 type = type.substring(0, 1).toUpperCase() + type.substring(1);
2945 selection.removeAll(layer.getParent().setVisible(type, false, true));
2946 } else if (command.startsWith("Unhide all ")) {
2947 String type = command.substring(11, command.length() -1); // skip the ending plural 's'
2948 type = type.substring(0, 1).toUpperCase() + type.substring(1);
2949 layer.getParent().setVisible(type, true, true);
2950 } else if (command.equals("Hide deselected")) {
2951 hideDeselected(0 != (ActionEvent.ALT_MASK & ae.getModifiers()));
2952 } else if (command.equals("Hide deselected except images")) {
2953 hideDeselected(true);
2954 } else if (command.equals("Hide selected")) {
2955 selection.setVisible(false); // TODO should deselect them too? I don't think so.
2956 } else if (command.equals("Resize canvas/LayerSet...")) {
2957 resizeCanvas();
2958 } else if (command.equals("Autoresize canvas/LayerSet")) {
2959 layer.getParent().setMinimumDimensions();
2960 } else if (command.equals("Import image")) {
2961 importImage();
2962 } else if (command.equals("Import next image")) {
2963 importNextImage();
2964 } else if (command.equals("Import stack...")) {
2965 Display.this.getLayerSet().addLayerContentStep(layer);
2966 Rectangle sr = getCanvas().getSrcRect();
2967 Bureaucrat burro = project.getLoader().importStack(layer, sr.x + sr.width/2, sr.y + sr.height/2, null, true, null);
2968 burro.addPostTask(new Runnable() { public void run() {
2969 Display.this.getLayerSet().addLayerContentStep(layer);
2970 }});
2971 } else if (command.equals("Import grid...")) {
2972 Display.this.getLayerSet().addLayerContentStep(layer);
2973 Bureaucrat burro = project.getLoader().importGrid(layer);
2974 burro.addPostTask(new Runnable() { public void run() {
2975 Display.this.getLayerSet().addLayerContentStep(layer);
2976 }});
2977 } else if (command.equals("Import sequence as grid...")) {
2978 Display.this.getLayerSet().addLayerContentStep(layer);
2979 Bureaucrat burro = project.getLoader().importSequenceAsGrid(layer);
2980 burro.addPostTask(new Runnable() { public void run() {
2981 Display.this.getLayerSet().addLayerContentStep(layer);
2982 }});
2983 } else if (command.equals("Import from text file...")) {
2984 Display.this.getLayerSet().addLayerContentStep(layer);
2985 Bureaucrat burro = project.getLoader().importImages(layer);
2986 burro.addPostTask(new Runnable() { public void run() {
2987 Display.this.getLayerSet().addLayerContentStep(layer);
2988 }});
2989 } else if (command.equals("Import labels as arealists...")) {
2990 Display.this.getLayerSet().addChangeTreesStep();
2991 Bureaucrat burro = project.getLoader().importLabelsAsAreaLists(layer, null, Double.MAX_VALUE, 0, 0.4f, false);
2992 burro.addPostTask(new Runnable() { public void run() {
2993 Display.this.getLayerSet().addChangeTreesStep();
2994 }});
2995 } else if (command.equals("Make flat image...")) {
2996 // if there's a ROI, just use that as cropping rectangle
2997 Rectangle srcRect = null;
2998 Roi roi = canvas.getFakeImagePlus().getRoi();
2999 if (null != roi) {
3000 srcRect = roi.getBounds();
3001 } else {
3002 // otherwise, whatever is visible
3003 //srcRect = canvas.getSrcRect();
3004 // The above is confusing. That is what ROIs are for. So paint all:
3005 srcRect = new Rectangle(0, 0, (int)Math.ceil(layer.getParent().getLayerWidth()), (int)Math.ceil(layer.getParent().getLayerHeight()));
3007 double scale = 1.0;
3008 final String[] types = new String[]{"8-bit grayscale", "RGB Color"};
3009 int the_type = ImagePlus.GRAY8;
3010 final GenericDialog gd = new GenericDialog("Choose", frame);
3011 gd.addSlider("Scale: ", 1, 100, 100);
3012 gd.addChoice("Type: ", types, types[0]);
3013 if (layer.getParent().size() > 1) {
3015 String[] layers = new String[layer.getParent().size()];
3016 int i = 0;
3017 for (Iterator it = layer.getParent().getLayers().iterator(); it.hasNext(); ) {
3018 layers[i] = layer.getProject().findLayerThing((Layer)it.next()).toString();
3019 i++;
3021 int i_layer = layer.getParent().indexOf(layer);
3022 gd.addChoice("Start: ", layers, layers[i_layer]);
3023 gd.addChoice("End: ", layers, layers[i_layer]);
3025 Utils.addLayerRangeChoices(Display.this.layer, gd); /// $#%! where are my lisp macros
3026 gd.addCheckbox("Include non-empty layers only", true);
3028 gd.addMessage("Background color:");
3029 Utils.addRGBColorSliders(gd, Color.black);
3030 gd.addCheckbox("Best quality", false);
3031 gd.addMessage("");
3032 gd.addCheckbox("Save to file", false);
3033 gd.addCheckbox("Save for web", false);
3034 gd.showDialog();
3035 if (gd.wasCanceled()) return;
3036 scale = gd.getNextNumber() / 100;
3037 the_type = (0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY8 : ImagePlus.COLOR_RGB);
3038 if (Double.isNaN(scale) || scale <= 0.0) {
3039 Utils.showMessage("Invalid scale.");
3040 return;
3042 Layer[] layer_array = null;
3043 boolean non_empty_only = false;
3044 if (layer.getParent().size() > 1) {
3045 non_empty_only = gd.getNextBoolean();
3046 int i_start = gd.getNextChoiceIndex();
3047 int i_end = gd.getNextChoiceIndex();
3048 ArrayList al = new ArrayList();
3049 ArrayList al_zd = layer.getParent().getZDisplayables();
3050 ZDisplayable[] zd = new ZDisplayable[al_zd.size()];
3051 al_zd.toArray(zd);
3052 for (int i=i_start, j=0; i <= i_end; i++, j++) {
3053 Layer la = layer.getParent().getLayer(i);
3054 if (!la.isEmpty() || !non_empty_only) al.add(la); // checks both the Layer and the ZDisplayable objects in the parent LayerSet
3056 if (0 == al.size()) {
3057 Utils.showMessage("All layers are empty!");
3058 return;
3060 layer_array = new Layer[al.size()];
3061 al.toArray(layer_array);
3062 } else {
3063 layer_array = new Layer[]{Display.this.layer};
3065 final Color background = new Color((int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
3066 final boolean quality = gd.getNextBoolean();
3067 final boolean save_to_file = gd.getNextBoolean();
3068 final boolean save_for_web = gd.getNextBoolean();
3069 // in its own thread
3070 if (save_for_web) project.getLoader().makePrescaledTiles(layer_array, Patch.class, srcRect, scale, c_alphas, the_type);
3071 else project.getLoader().makeFlatImage(layer_array, srcRect, scale, c_alphas, the_type, save_to_file, quality, background);
3073 } else if (command.equals("Lock")) {
3074 selection.setLocked(true);
3075 } else if (command.equals("Unlock")) {
3076 selection.setLocked(false);
3077 } else if (command.equals("Properties...")) {
3078 active.adjustProperties();
3079 updateSelection();
3080 } else if (command.equals("Cancel alignment")) {
3081 layer.getParent().cancelAlign();
3082 } else if (command.equals("Align with landmarks")) {
3083 layer.getParent().applyAlign(false);
3084 } else if (command.equals("Align and register")) {
3085 layer.getParent().applyAlign(true);
3086 } else if (command.equals("Align using profiles")) {
3087 if (!selection.contains(Profile.class)) {
3088 Utils.showMessage("No profiles are selected.");
3089 return;
3091 // ask for range of layers
3092 final GenericDialog gd = new GenericDialog("Choose range");
3093 Utils.addLayerRangeChoices(Display.this.layer, gd);
3094 gd.showDialog();
3095 if (gd.wasCanceled()) return;
3096 Layer la_start = layer.getParent().getLayer(gd.getNextChoiceIndex());
3097 Layer la_end = layer.getParent().getLayer(gd.getNextChoiceIndex());
3098 if (la_start == la_end) {
3099 Utils.showMessage("Need at least two layers.");
3100 return;
3102 if (selection.isLocked()) {
3103 Utils.showMessage("There are locked objects.");
3104 return;
3106 layer.getParent().startAlign(Display.this);
3107 layer.getParent().applyAlign(la_start, la_end, selection);
3108 } else if (command.equals("Align stack slices")) {
3109 if (getActive() instanceof Patch) {
3110 final Patch slice = (Patch)getActive();
3111 if (slice.isStack()) {
3112 // check linked group
3113 final HashSet hs = slice.getLinkedGroup(new HashSet());
3114 for (Iterator it = hs.iterator(); it.hasNext(); ) {
3115 if (it.next().getClass() != Patch.class) {
3116 Utils.showMessage("Images are linked to other objects, can't proceed to cross-correlate them."); // labels should be fine, need to check that
3117 return;
3120 final LayerSet ls = slice.getLayerSet();
3121 final HashSet<Displayable> linked = slice.getLinkedGroup(null);
3122 ls.addTransformStep(linked);
3123 Bureaucrat burro = Registration.registerStackSlices((Patch)getActive()); // will repaint
3124 burro.addPostTask(new Runnable() { public void run() {
3125 // The current state when done
3126 ls.addTransformStep(linked);
3127 }});
3128 } else {
3129 Utils.log("Align stack slices: selected image is not part of a stack.");
3132 } else if (command.equals("Align layers")) {
3133 final Layer la = layer;; // caching, since scroll wheel may change it
3134 la.getParent().addTransformStep(la);
3135 Bureaucrat burro = AlignTask.alignLayersLinearlyTask( la );
3136 burro.addPostTask(new Runnable() { public void run() {
3137 la.getParent().addTransformStep(la);
3138 }});
3139 } else if (command.equals("Align multi-layer mosaic")) {
3140 final Layer la = layer; // caching, since scroll wheel may change it
3141 la.getParent().addTransformStep();
3142 Bureaucrat burro = AlignTask.alignMultiLayerMosaicTask( la );
3143 burro.addPostTask(new Runnable() { public void run() {
3144 la.getParent().addTransformStep();
3145 }});
3146 } else if (command.equals("Properties ...")) { // NOTE the space before the dots, to distinguish from the "Properties..." command that works on Displayable objects.
3147 GenericDialog gd = new GenericDialog("Properties", Display.this.frame);
3148 //gd.addNumericField("layer_scroll_step: ", this.scroll_step, 0);
3149 gd.addSlider("layer_scroll_step: ", 1, layer.getParent().size(), Display.this.scroll_step);
3150 gd.addChoice("snapshots_mode", LayerSet.snapshot_modes, LayerSet.snapshot_modes[layer.getParent().getSnapshotsMode()]);
3151 gd.addCheckbox("prefer_snapshots_quality", layer.getParent().snapshotsQuality());
3152 Loader lo = getProject().getLoader();
3153 boolean using_mipmaps = lo.isMipMapsEnabled();
3154 gd.addCheckbox("enable_mipmaps", using_mipmaps);
3155 String preprocessor = project.getLoader().getPreprocessor();
3156 gd.addStringField("image_preprocessor: ", null == preprocessor ? "" : preprocessor);
3157 gd.addCheckbox("enable_layer_pixels virtualization", layer.getParent().isPixelsVirtualizationEnabled());
3158 double max = layer.getParent().getLayerWidth() < layer.getParent().getLayerHeight() ? layer.getParent().getLayerWidth() : layer.getParent().getLayerHeight();
3159 gd.addSlider("max_dimension of virtualized layer pixels: ", 0, max, layer.getParent().getPixelsMaxDimension());
3160 // --------
3161 gd.showDialog();
3162 if (gd.wasCanceled()) return;
3163 // --------
3164 int sc = (int) gd.getNextNumber();
3165 if (sc < 1) sc = 1;
3166 Display.this.scroll_step = sc;
3167 updateInDatabase("scroll_step");
3169 layer.getParent().setSnapshotsMode(gd.getNextChoiceIndex());
3170 layer.getParent().setSnapshotsQuality(gd.getNextBoolean());
3172 boolean generate_mipmaps = gd.getNextBoolean();
3173 if (using_mipmaps && generate_mipmaps) {
3174 // nothing changed
3175 } else {
3176 if (using_mipmaps) { // and !generate_mipmaps
3177 lo.flushMipMaps(true);
3178 } else {
3179 // not using mipmaps before, and true == generate_mipmaps
3180 lo.generateMipMaps(layer.getParent().getDisplayables(Patch.class));
3184 final String prepro = gd.getNextString();
3185 if (!project.getLoader().setPreprocessor(prepro.trim())) {
3186 Utils.showMessage("Could NOT set the preprocessor to " + prepro);
3189 layer.getParent().setPixelsVirtualizationEnabled(gd.getNextBoolean());
3190 layer.getParent().setPixelsMaxDimension((int)gd.getNextNumber());
3191 } else if (command.equals("Search...")) {
3192 new Search();
3193 } else if (command.equals("Select all")) {
3194 selection.selectAll();
3195 repaint(Display.this.layer, selection.getBox(), 0);
3196 } else if (command.equals("Select none")) {
3197 Rectangle box = selection.getBox();
3198 selection.clear();
3199 repaint(Display.this.layer, box, 0);
3200 } else if (command.equals("Restore selection")) {
3201 selection.restore();
3202 } else if (command.equals("Select under ROI")) {
3203 Roi roi = canvas.getFakeImagePlus().getRoi();
3204 if (null == roi) return;
3205 selection.selectAll(roi, true);
3206 } else if (command.equals("Merge")) {
3207 ArrayList al_sel = selection.getSelected();
3208 // put active at the beginning, to work as the base on which other's will get merged
3209 al_sel.remove(Display.this.active);
3210 al_sel.add(0, Display.this.active);
3211 AreaList ali = AreaList.merge(al_sel);
3212 if (null != ali) {
3213 // remove all but the first from the selection
3214 for (int i=1; i<al_sel.size(); i++) {
3215 Object ob = al_sel.get(i);
3216 if (ob.getClass() == AreaList.class) {
3217 selection.remove((Displayable)ob);
3220 selection.updateTransform(ali);
3221 repaint(ali.getLayerSet(), ali, 0);
3223 } else if (command.equals("Identify...")) {
3224 // for pipes only for now
3225 if (!(active instanceof Pipe)) return;
3226 ini.trakem2.vector.Compare.findSimilar((Pipe)active);
3227 } else if (command.equals("Identify with axes...")) {
3228 if (!(active instanceof Pipe)) return;
3229 if (Project.getProjects().size() < 2) {
3230 Utils.showMessage("You need at least two projects open:\n-A reference project\n-The current project with the pipe to identify");
3231 return;
3233 ini.trakem2.vector.Compare.findSimilarWithAxes((Pipe)active);
3234 } else if (command.equals("View orthoslices")) {
3235 if (!(active instanceof Patch)) return;
3236 Display3D.showOrthoslices(((Patch)active));
3237 } else if (command.equals("View volume")) {
3238 if (!(active instanceof Patch)) return;
3239 Display3D.showVolume(((Patch)active));
3240 } else if (command.equals("Show in 3D")) {
3241 for (Iterator it = selection.getSelected(ZDisplayable.class).iterator(); it.hasNext(); ) {
3242 ZDisplayable zd = (ZDisplayable)it.next();
3243 Display3D.show(zd.getProject().findProjectThing(zd));
3245 // handle profile lists ...
3246 HashSet hs = new HashSet();
3247 for (Iterator it = selection.getSelected(Profile.class).iterator(); it.hasNext(); ) {
3248 Displayable d = (Displayable)it.next();
3249 ProjectThing profile_list = (ProjectThing)d.getProject().findProjectThing(d).getParent();
3250 if (!hs.contains(profile_list)) {
3251 Display3D.show(profile_list);
3252 hs.add(profile_list);
3255 } else if (command.equals("Snap")) {
3256 if (!(active instanceof Patch)) return;
3257 StitchingTEM.snap(getActive(), Display.this);
3258 } else if (command.equals("Blend")) {
3259 HashSet<Patch> patches = new HashSet<Patch>();
3260 for (final Displayable d : selection.getSelected()) {
3261 if (d.getClass() == Patch.class) patches.add((Patch)d);
3263 if (patches.size() > 1) {
3264 GenericDialog gd = new GenericDialog("Blending");
3265 gd.addCheckbox("Respect current alpha mask", true);
3266 gd.showDialog();
3267 if (gd.wasCanceled()) return;
3268 Blending.blend(patches, gd.getNextBoolean());
3269 } else {
3270 IJ.log("Please select more than one overlapping image.");
3272 } else if (command.equals("Montage")) {
3273 if (!(active instanceof Patch)) {
3274 Utils.showMessage("Please select only images.");
3275 return;
3277 final Set<Displayable> affected = new HashSet<Displayable>(selection.getAffected());
3278 for (final Displayable d : affected)
3279 if (d.isLinked()) {
3280 Utils.showMessage( "You cannot montage linked objects." );
3281 return;
3283 // make an undo step!
3284 final LayerSet ls = layer.getParent();
3285 ls.addTransformStep(affected);
3286 Bureaucrat burro = AlignTask.alignSelectionTask( selection );
3287 burro.addPostTask(new Runnable() { public void run() {
3288 ls.addTransformStep(affected);
3289 }});
3290 } else if (command.equals("Lens correction")) {
3291 final Layer la = layer;
3292 la.getParent().addDataEditStep(new HashSet<Displayable>(la.getParent().getDisplayables()));
3293 Bureaucrat burro = DistortionCorrectionTask.correctDistortionFromSelection( selection );
3294 burro.addPostTask(new Runnable() { public void run() {
3295 // no means to know which where modified and from which layers!
3296 la.getParent().addDataEditStep(new HashSet<Displayable>(la.getParent().getDisplayables()));
3297 }});
3298 } else if (command.equals("Link images...")) {
3299 GenericDialog gd = new GenericDialog("Options");
3300 gd.addMessage("Linking images to images (within their own layer only):");
3301 String[] options = {"all images to all images", "each image with any other overlapping image"};
3302 gd.addChoice("Link: ", options, options[1]);
3303 String[] options2 = {"selected images only", "all images in this layer", "all images in all layers"};
3304 gd.addChoice("Apply to: ", options2, options2[0]);
3305 gd.showDialog();
3306 if (gd.wasCanceled()) return;
3307 Layer lay = layer;
3308 final HashSet<Displayable> ds = new HashSet<Displayable>(lay.getParent().getDisplayables());
3309 lay.getParent().addDataEditStep(ds);
3310 boolean overlapping_only = 1 == gd.getNextChoiceIndex();
3311 switch (gd.getNextChoiceIndex()) {
3312 case 0:
3313 Patch.crosslink(selection.getSelected(Patch.class), overlapping_only);
3314 break;
3315 case 1:
3316 Patch.crosslink(lay.getDisplayables(Patch.class), overlapping_only);
3317 break;
3318 case 2:
3319 for (final Layer la : lay.getParent().getLayers()) {
3320 Patch.crosslink(la.getDisplayables(Patch.class), overlapping_only);
3322 break;
3324 lay.getParent().addDataEditStep(ds);
3325 } else if (command.equals("Calibration...")) {
3326 try {
3327 IJ.run(canvas.getFakeImagePlus(), "Properties...", "");
3328 } catch (RuntimeException re) {
3329 Utils.log2("Calibration dialog canceled.");
3331 } else if (command.equals("Enhance contrast (selected images)...")) {
3332 final Layer la = layer;
3333 final HashSet<Displayable> ds = new HashSet<Displayable>(la.getParent().getDisplayables());
3334 la.getParent().addDataEditStep(ds);
3335 ArrayList al = selection.getSelected(Patch.class);
3336 Bureaucrat burro = getProject().getLoader().homogenizeContrast(al);
3337 burro.addPostTask(new Runnable() { public void run() {
3338 la.getParent().addDataEditStep(ds);
3339 }});
3340 } else if (command.equals("Enhance contrast layer-wise...")) {
3341 // ask for range of layers
3342 final GenericDialog gd = new GenericDialog("Choose range");
3343 Utils.addLayerRangeChoices(Display.this.layer, gd);
3344 gd.showDialog();
3345 if (gd.wasCanceled()) return;
3346 java.util.List list = layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1); // exclusive end
3347 Layer[] la = new Layer[list.size()];
3348 list.toArray(la);
3349 final HashSet<Displayable> ds = new HashSet<Displayable>();
3350 for (final Layer l : la) ds.addAll(l.getDisplayables(Patch.class));
3351 getLayerSet().addDataEditStep(ds);
3352 Bureaucrat burro = project.getLoader().homogenizeContrast(la);
3353 burro.addPostTask(new Runnable() { public void run() {
3354 getLayerSet().addDataEditStep(ds);
3355 }});
3356 } else if (command.equals("Set Min and Max layer-wise...")) {
3357 Displayable active = getActive();
3358 double min = 0;
3359 double max = 0;
3360 if (null != active && active.getClass() == Patch.class) {
3361 min = ((Patch)active).getMin();
3362 max = ((Patch)active).getMax();
3364 final GenericDialog gd = new GenericDialog("Min and Max");
3365 gd.addMessage("Set min and max to all images in the layer range");
3366 Utils.addLayerRangeChoices(Display.this.layer, gd);
3367 gd.addNumericField("min: ", min, 2);
3368 gd.addNumericField("max: ", max, 2);
3369 gd.showDialog();
3370 if (gd.wasCanceled()) return;
3372 min = gd.getNextNumber();
3373 max = gd.getNextNumber();
3374 ArrayList<Displayable> al = new ArrayList<Displayable>();
3375 for (final Layer la : layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1)) { // exclusive end
3376 al.addAll(la.getDisplayables(Patch.class));
3378 final HashSet<Displayable> ds = new HashSet<Displayable>(al);
3379 getLayerSet().addDataEditStep(ds);
3380 Bureaucrat burro = project.getLoader().setMinAndMax(al, min, max);
3381 burro.addPostTask(new Runnable() { public void run() {
3382 getLayerSet().addDataEditStep(ds);
3383 }});
3384 } else if (command.equals("Set Min and Max (selected images)...")) {
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 selected images");
3394 gd.addNumericField("min: ", min, 2);
3395 gd.addNumericField("max: ", max, 2);
3396 gd.showDialog();
3397 if (gd.wasCanceled()) return;
3399 min = gd.getNextNumber();
3400 max = gd.getNextNumber();
3401 final HashSet<Displayable> ds = new HashSet<Displayable>(selection.getSelected(Patch.class));
3402 getLayerSet().addDataEditStep(ds);
3403 Bureaucrat burro = project.getLoader().setMinAndMax(selection.getSelected(Patch.class), min, max);
3404 burro.addPostTask(new Runnable() { public void run() {
3405 getLayerSet().addDataEditStep(ds);
3406 }});
3407 } else if (command.equals("Duplicate")) {
3408 // only Patch and DLabel, i.e. Layer-only resident objects that don't exist in the Project Tree
3409 final HashSet<Class> accepted = new HashSet<Class>();
3410 accepted.add(Patch.class);
3411 accepted.add(DLabel.class);
3412 final ArrayList<Displayable> originals = new ArrayList<Displayable>();
3413 final ArrayList<Displayable> selected = selection.getSelected();
3414 for (final Displayable d : selected) {
3415 if (accepted.contains(d.getClass())) {
3416 originals.add(d);
3419 if (originals.size() > 0) {
3420 getLayerSet().addChangeTreesStep();
3421 for (final Displayable d : originals) {
3422 d.getLayer().add(d.clone());
3424 getLayerSet().addChangeTreesStep();
3425 } else if (selected.size() > 0) {
3426 Utils.log("Can only duplicate images and text labels.\nDuplicate *other* objects in the Project Tree.\n");
3428 } else if (command.equals("Create subproject")) {
3429 Roi roi = canvas.getFakeImagePlus().getRoi();
3430 if (null == roi) return; // the menu item is not active unless there is a ROI
3431 Layer first, last;
3432 if (1 == layer.getParent().size()) {
3433 first = last = layer;
3434 } else {
3435 GenericDialog gd = new GenericDialog("Choose layer range");
3436 Utils.addLayerRangeChoices(layer, gd);
3437 gd.showDialog();
3438 if (gd.wasCanceled()) return;
3439 first = layer.getParent().getLayer(gd.getNextChoiceIndex());
3440 last = layer.getParent().getLayer(gd.getNextChoiceIndex());
3441 Utils.log2("first, last: " + first + ", " + last);
3443 Project sub = getProject().createSubproject(roi.getBounds(), first, last);
3444 final LayerSet subls = sub.getRootLayerSet();
3445 final Display d = new Display(sub, subls.getLayer(0));
3446 SwingUtilities.invokeLater(new Runnable() { public void run() {
3447 d.canvas.showCentered(new Rectangle(0, 0, (int)subls.getLayerWidth(), (int)subls.getLayerHeight()));
3448 }});
3449 } else if (command.startsWith("Arealists as labels")) {
3450 GenericDialog gd = new GenericDialog("Export labels");
3451 gd.addSlider("Scale: ", 1, 100, 100);
3452 final String[] options = {"All area list", "Selected area lists"};
3453 gd.addChoice("Export: ", options, options[0]);
3454 Utils.addLayerRangeChoices(layer, gd);
3455 gd.addCheckbox("Visible only", true);
3456 gd.showDialog();
3457 if (gd.wasCanceled()) return;
3458 final float scale = (float)(gd.getNextNumber() / 100);
3459 java.util.List al = 0 == gd.getNextChoiceIndex() ? layer.getParent().getZDisplayables(AreaList.class) : selection.getSelected(AreaList.class);
3460 if (null == al) {
3461 Utils.log("No area lists found to export.");
3462 return;
3464 // 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?
3465 al = (java.util.List<Displayable>) al;
3467 int first = gd.getNextChoiceIndex();
3468 int last = gd.getNextChoiceIndex();
3469 boolean visible_only = gd.getNextBoolean();
3470 if (-1 != command.indexOf("(amira)")) {
3471 AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, true, true);
3472 } else if (-1 != command.indexOf("(tif)")) {
3473 AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, false, false);
3475 } else if (command.equals("Project properties...")) {
3476 project.adjustProperties();
3477 } else if (command.equals("Release memory...")) {
3478 Bureaucrat.createAndStart(new Worker("Releasing memory") {
3479 public void run() {
3480 startedWorking();
3481 try {
3482 GenericDialog gd = new GenericDialog("Release Memory");
3483 int max = (int)(IJ.maxMemory() / 1000000);
3484 gd.addSlider("Megabytes: ", 0, max, max/2);
3485 gd.showDialog();
3486 if (!gd.wasCanceled()) {
3487 int n_mb = (int)gd.getNextNumber();
3488 project.getLoader().releaseToFit((long)n_mb*1000000);
3490 } catch (Throwable e) {
3491 IJError.print(e);
3492 } finally {
3493 finishedWorking();
3496 }, project);
3497 } else if (command.equals("Flush image cache")) {
3498 Loader.releaseAllCaches();
3499 } else if (command.equals("Regenerate all mipmaps")) {
3500 for (final Displayable d : getLayerSet().getDisplayables(Patch.class)) {
3501 d.getProject().getLoader().regenerateMipMaps((Patch) d);
3503 } else {
3504 Utils.log2("Display: don't know what to do with command " + command);
3506 }});
3509 /** Update in all displays the Transform for the given Displayable if it's selected. */
3510 static public void updateTransform(final Displayable displ) {
3511 for (final Display d : al_displays) {
3512 if (d.selection.contains(displ)) d.selection.updateTransform(displ);
3516 /** Order the profiles of the parent profile_list by Z order, and fix the ProjectTree.*/
3518 private void fixZOrdering(Profile profile) {
3519 ProjectThing thing = project.findProjectThing(profile);
3520 if (null == thing) {
3521 Utils.log2("Display.fixZOrdering: null thing?");
3522 return;
3524 ((ProjectThing)thing.getParent()).fixZOrdering();
3525 project.getProjectTree().updateList(thing.getParent());
3529 /** The number of layers to scroll through with the wheel; 1 by default.*/
3530 public int getScrollStep() { return this.scroll_step; }
3532 public void setScrollStep(int scroll_step) {
3533 if (scroll_step < 1) scroll_step = 1;
3534 this.scroll_step = scroll_step;
3535 updateInDatabase("scroll_step");
3538 protected Bureaucrat importImage() {
3539 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
3540 public void run() {
3541 startedWorking();
3542 try {
3545 Rectangle srcRect = canvas.getSrcRect();
3546 int x = srcRect.x + srcRect.width / 2;
3547 int y = srcRect.y + srcRect.height/ 2;
3548 Patch p = project.getLoader().importImage(project, x, y);
3549 if (null == p) {
3550 finishedWorking();
3551 Utils.showMessage("Could not open the image.");
3552 return;
3555 Display.this.getLayerSet().addLayerContentStep(layer);
3557 layer.add(p); // will add it to the proper Displays
3559 Display.this.getLayerSet().addLayerContentStep(layer);
3562 } catch (Exception e) {
3563 IJError.print(e);
3565 finishedWorking();
3568 return Bureaucrat.createAndStart(worker, getProject());
3571 protected Bureaucrat importNextImage() {
3572 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
3573 public void run() {
3574 startedWorking();
3575 try {
3577 Rectangle srcRect = canvas.getSrcRect();
3578 int x = srcRect.x + srcRect.width / 2;// - imp.getWidth() / 2;
3579 int y = srcRect.y + srcRect.height/ 2;// - imp.getHeight()/ 2;
3580 Patch p = project.getLoader().importNextImage(project, x, y);
3581 if (null == p) {
3582 Utils.showMessage("Could not open next image.");
3583 finishedWorking();
3584 return;
3587 Display.this.getLayerSet().addLayerContentStep(layer);
3589 layer.add(p); // will add it to the proper Displays
3591 Display.this.getLayerSet().addLayerContentStep(layer);
3593 } catch (Exception e) {
3594 IJError.print(e);
3596 finishedWorking();
3599 return Bureaucrat.createAndStart(worker, getProject());
3603 /** Make the given channel have the given alpha (transparency). */
3604 public void setChannel(int c, float alpha) {
3605 int a = (int)(255 * alpha);
3606 int l = (c_alphas&0xff000000)>>24;
3607 int r = (c_alphas&0xff0000)>>16;
3608 int g = (c_alphas&0xff00)>>8;
3609 int b = c_alphas&0xff;
3610 switch (c) {
3611 case Channel.MONO:
3612 // all to the given alpha
3613 c_alphas = (l<<24) + (r<<16) + (g<<8) + b; // parenthesis are NECESSARY
3614 break;
3615 case Channel.RED:
3616 // modify only the red
3617 c_alphas = (l<<24) + (a<<16) + (g<<8) + b;
3618 break;
3619 case Channel.GREEN:
3620 c_alphas = (l<<24) + (r<<16) + (a<<8) + b;
3621 break;
3622 case Channel.BLUE:
3623 c_alphas = (l<<24) + (r<<16) + (g<<8) + a;
3624 break;
3626 //Utils.log2("c_alphas: " + c_alphas);
3627 //canvas.setUpdateGraphics(true);
3628 canvas.repaint(true);
3629 updateInDatabase("c_alphas");
3632 /** Set the channel as active and the others as inactive. */
3633 public void setActiveChannel(Channel channel) {
3634 for (int i=0; i<4; i++) {
3635 if (channel != channels[i]) channels[i].setActive(false);
3636 else channel.setActive(true);
3638 Utils.updateComponent(panel_channels);
3639 transp_slider.setValue((int)(channel.getAlpha() * 100));
3642 public int getDisplayChannelAlphas() { return c_alphas; }
3644 // rename this method and the getDisplayChannelAlphas ! They sound the same!
3645 public int getChannelAlphas() {
3646 return ((int)(channels[0].getAlpha() * 255)<<24) + ((int)(channels[1].getAlpha() * 255)<<16) + ((int)(channels[2].getAlpha() * 255)<<8) + (int)(channels[3].getAlpha() * 255);
3649 public int getChannelAlphasState() {
3650 return ((channels[0].isSelected() ? 255 : 0)<<24)
3651 + ((channels[1].isSelected() ? 255 : 0)<<16)
3652 + ((channels[2].isSelected() ? 255 : 0)<<8)
3653 + (channels[3].isSelected() ? 255 : 0);
3656 /** Show the layer in the front Display, or in a new Display if the front Display is showing a layer from a different LayerSet. */
3657 static public void showFront(final Layer layer) {
3658 Display display = front;
3659 if (null == display || display.layer.getParent() != layer.getParent()) {
3660 display = new Display(layer.getProject(), layer, null); // gets set to front
3661 } else {
3662 display.setLayer(layer);
3666 /** Show the given Displayable centered and selected. If select is false, the selection is cleared. */
3667 static public void showCentered(Layer layer, Displayable displ, boolean select, boolean shift_down) {
3668 // see if the given layer belongs to the layer set being displayed
3669 Display display = front; // to ensure thread consistency to some extent
3670 if (null == display || display.layer.getParent() != layer.getParent()) {
3671 display = new Display(layer.getProject(), layer, displ); // gets set to front
3672 } else if (display.layer != layer) {
3673 display.setLayer(layer);
3675 if (select) {
3676 if (!shift_down) display.selection.clear();
3677 display.selection.add(displ);
3678 } else {
3679 display.selection.clear();
3681 display.showCentered(displ);
3684 private final void showCentered(final Displayable displ) {
3685 if (null == displ) return;
3686 SwingUtilities.invokeLater(new Runnable() { public void run() {
3687 displ.setVisible(true);
3688 Rectangle box = displ.getBoundingBox();
3689 if (0 == box.width || 0 == box.height) {
3690 box.width = (int)layer.getLayerWidth();
3691 box.height = (int)layer.getLayerHeight();
3693 canvas.showCentered(box);
3694 scrollToShow(displ);
3695 if (displ instanceof ZDisplayable) {
3696 // scroll to first layer that has a point
3697 ZDisplayable zd = (ZDisplayable)displ;
3698 setLayer(zd.getFirstLayer());
3700 }});
3703 /** Listen to interesting updates, such as the ColorPicker and updates to Patch objects. */
3704 public void imageUpdated(ImagePlus updated) {
3705 // detect ColorPicker WARNING this will work even if the Display is not the window immediately active under the color picker.
3706 if (this == front && updated instanceof ij.plugin.ColorPicker) {
3707 if (null != active && project.isInputEnabled()) {
3708 selection.setColor(Toolbar.getForegroundColor());
3709 Display.repaint(front.layer, selection.getBox(), 0);
3711 return;
3713 // $%#@!! LUT changes don't set the image as changed
3714 //if (updated instanceof PatchStack) {
3715 // updated.changes = 1
3718 //Utils.log2("imageUpdated: " + updated + " " + updated.getClass());
3720 /* // never gets called (?)
3721 // the above is overkill. Instead:
3722 if (updated instanceof PatchStack) {
3723 Patch p = ((PatchStack)updated).getCurrentPatch();
3724 ImageProcessor ip = updated.getProcessor();
3725 p.setMinAndMax(ip.getMin(), ip.getMax());
3726 Utils.log2("setting min and max: " + ip.getMin() + ", " + ip.getMax());
3727 project.getLoader().decacheAWT(p.getId()); // including level 0, which will be editable
3728 // on repaint, it will be recreated
3729 //((PatchStack)updated).decacheAll(); // so that it will repaint with a newly created image
3733 // detect LUT changes: DONE at PatchStack, which is the active (virtual) image
3734 //Utils.log2("calling decache for " + updated);
3735 //getProject().getLoader().decache(updated);
3738 public void imageClosed(ImagePlus imp) {}
3739 public void imageOpened(ImagePlus imp) {}
3741 /** Release memory captured by the offscreen images */
3742 static public void flushAll() {
3743 for (final Display d : al_displays) {
3744 d.canvas.flush();
3746 //System.gc();
3747 Thread.yield();
3750 /** Can be null. */
3751 static public Display getFront() {
3752 return front;
3755 static public void setCursorToAll(final Cursor c) {
3756 for (final Display d : al_displays) {
3757 d.frame.setCursor(c);
3761 protected void setCursor(Cursor c) {
3762 frame.setCursor(c);
3765 /** Used by the Displayable to update the visibility checkbox in other Displays. */
3766 static protected void updateVisibilityCheckbox(final Layer layer, final Displayable displ, final Display calling_display) {
3767 //LOCKS ALL //SwingUtilities.invokeLater(new Runnable() { public void run() {
3768 for (final Display d : al_displays) {
3769 if (d == calling_display) continue;
3770 if (d.layer.contains(displ) || (displ instanceof ZDisplayable && d.layer.getParent().contains((ZDisplayable)displ))) {
3771 DisplayablePanel dp = d.ht_panels.get(displ);
3772 if (null != dp) dp.updateVisibilityCheckbox();
3775 //}});
3778 protected boolean isActiveWindow() {
3779 return frame.isActive();
3782 /** Toggle user input; pan and zoom are always enabled though.*/
3783 static public void setReceivesInput(final Project project, final boolean b) {
3784 for (final Display d : al_displays) {
3785 if (d.project == project) d.canvas.setReceivesInput(b);
3789 /** Export the DTD that defines this object. */
3790 static public void exportDTD(StringBuffer sb_header, HashSet hs, String indent) {
3791 if (hs.contains("t2_display")) return; // TODO to avoid collisions the type shoud be in a namespace such as tm2:display
3792 hs.add("t2_display");
3793 sb_header.append(indent).append("<!ELEMENT t2_display EMPTY>\n")
3794 .append(indent).append("<!ATTLIST t2_display id NMTOKEN #REQUIRED>\n")
3795 .append(indent).append("<!ATTLIST t2_display layer_id NMTOKEN #REQUIRED>\n")
3796 .append(indent).append("<!ATTLIST t2_display x NMTOKEN #REQUIRED>\n")
3797 .append(indent).append("<!ATTLIST t2_display y NMTOKEN #REQUIRED>\n")
3798 .append(indent).append("<!ATTLIST t2_display magnification NMTOKEN #REQUIRED>\n")
3799 .append(indent).append("<!ATTLIST t2_display srcrect_x NMTOKEN #REQUIRED>\n")
3800 .append(indent).append("<!ATTLIST t2_display srcrect_y NMTOKEN #REQUIRED>\n")
3801 .append(indent).append("<!ATTLIST t2_display srcrect_width NMTOKEN #REQUIRED>\n")
3802 .append(indent).append("<!ATTLIST t2_display srcrect_height NMTOKEN #REQUIRED>\n")
3803 .append(indent).append("<!ATTLIST t2_display scroll_step NMTOKEN #REQUIRED>\n")
3804 .append(indent).append("<!ATTLIST t2_display c_alphas NMTOKEN #REQUIRED>\n")
3805 .append(indent).append("<!ATTLIST t2_display c_alphas_state NMTOKEN #REQUIRED>\n")
3808 /** Export all displays of the given project as XML entries. */
3809 static public void exportXML(final Project project, final Writer writer, final String indent, final Object any) throws Exception {
3810 final StringBuffer sb_body = new StringBuffer();
3811 final String in = indent + "\t";
3812 for (final Display d : al_displays) {
3813 if (d.project != project) continue;
3814 final Rectangle r = d.frame.getBounds();
3815 final Rectangle srcRect = d.canvas.getSrcRect();
3816 final double magnification = d.canvas.getMagnification();
3817 sb_body.append(indent).append("<t2_display id=\"").append(d.id).append("\"\n")
3818 .append(in).append("layer_id=\"").append(d.layer.getId()).append("\"\n")
3819 .append(in).append("c_alphas=\"").append(d.c_alphas).append("\"\n")
3820 .append(in).append("c_alphas_state=\"").append(d.getChannelAlphasState()).append("\"\n")
3821 .append(in).append("x=\"").append(r.x).append("\"\n")
3822 .append(in).append("y=\"").append(r.y).append("\"\n")
3823 .append(in).append("magnification=\"").append(magnification).append("\"\n")
3824 .append(in).append("srcrect_x=\"").append(srcRect.x).append("\"\n")
3825 .append(in).append("srcrect_y=\"").append(srcRect.y).append("\"\n")
3826 .append(in).append("srcrect_width=\"").append(srcRect.width).append("\"\n")
3827 .append(in).append("srcrect_height=\"").append(srcRect.height).append("\"\n")
3828 .append(in).append("scroll_step=\"").append(d.scroll_step).append("\"\n")
3830 sb_body.append(indent).append("/>\n");
3832 writer.write(sb_body.toString());
3835 static public void toolChanged(final String tool_name) {
3836 Utils.log2("tool name: " + tool_name);
3837 if (!tool_name.equals("ALIGN")) {
3838 for (final Display d : al_displays) {
3839 d.layer.getParent().cancelAlign();
3844 static public void toolChanged(final int tool) {
3845 //Utils.log2("int tool is " + tool);
3846 if (ProjectToolbar.PEN == tool) {
3847 // erase bounding boxes
3848 for (final Display d : al_displays) {
3849 if (null != d.active) d.repaint(d.layer, d.selection.getBox(), 2);
3852 if (null != front) {
3853 WindowManager.setTempCurrentImage(front.canvas.getFakeImagePlus());
3857 public Selection getSelection() {
3858 return selection;
3861 public boolean isSelected(Displayable d) {
3862 return selection.contains(d);
3865 static public void updateSelection() {
3866 Display.updateSelection(null);
3868 static public void updateSelection(final Display calling) {
3869 final HashSet hs = new HashSet();
3870 for (final Display d : al_displays) {
3871 if (hs.contains(d.layer)) continue;
3872 hs.add(d.layer);
3873 if (null == d || null == d.selection) {
3874 Utils.log2("d is : "+ d + " d.selection is " + d.selection);
3875 } else {
3876 d.selection.update(); // recomputes box
3878 if (d != calling) { // TODO this is so dirty!
3879 if (d.selection.getNLinked() > 1) d.canvas.setUpdateGraphics(true); // this is overkill anyway
3880 d.canvas.repaint(d.selection.getLinkedBox(), Selection.PADDING);
3881 d.navigator.repaint(true); // everything
3886 static public void clearSelection(final Layer layer) {
3887 for (final Display d : al_displays) {
3888 if (d.layer == layer) d.selection.clear();
3891 static public void clearSelection() {
3892 for (final Display d : al_displays) {
3893 d.selection.clear();
3897 private void setTempCurrentImage() {
3898 WindowManager.setCurrentWindow(canvas.getFakeImagePlus().getWindow(), true);
3899 WindowManager.setTempCurrentImage(canvas.getFakeImagePlus());
3902 /** Check if any display will paint the given Displayable at the given magnification. */
3903 static public boolean willPaint(final Displayable displ, final double magnification) {
3904 Rectangle box = null; ;
3905 for (final Display d : al_displays) {
3906 /* // Can no longer do this check, because 'magnification' is now affected by the Displayable AffineTransform! And thus it would not paint after the prePaint.
3907 if (Math.abs(d.canvas.getMagnification() - magnification) > 0.00000001) {
3908 continue;
3911 if (null == box) box = displ.getBoundingBox(null);
3912 if (d.canvas.getSrcRect().intersects(box)) {
3913 return true;
3916 return false;
3919 public void hideDeselected(final boolean not_images) {
3920 // hide deselected
3921 final ArrayList all = layer.getParent().getZDisplayables(); // a copy
3922 all.addAll(layer.getDisplayables());
3923 all.removeAll(selection.getSelected());
3924 if (not_images) all.removeAll(layer.getDisplayables(Patch.class));
3925 for (final Displayable d : (ArrayList<Displayable>)all) {
3926 if (d.isVisible()) d.setVisible(false);
3928 Display.update(layer);
3931 /** Cleanup internal lists that may contain the given Displayable. */
3932 static public void flush(final Displayable displ) {
3933 for (final Display d : al_displays) {
3934 d.selection.removeFromPrev(displ);
3938 public void resizeCanvas() {
3939 GenericDialog gd = new GenericDialog("Resize LayerSet");
3940 gd.addNumericField("new width: ", layer.getLayerWidth(), 3);
3941 gd.addNumericField("new height: ",layer.getLayerHeight(),3);
3942 gd.addChoice("Anchor: ", LayerSet.ANCHORS, LayerSet.ANCHORS[7]);
3943 gd.showDialog();
3944 if (gd.wasCanceled()) return;
3945 double new_width = gd.getNextNumber();
3946 double new_height =gd.getNextNumber();
3947 layer.getParent().setDimensions(new_width, new_height, gd.getNextChoiceIndex()); // will complain and prevent cropping existing Displayable objects
3951 // To record layer changes -- but it's annoying, this is visualization not data.
3952 static class DoSetLayer implements DoStep {
3953 final Display display;
3954 final Layer layer;
3955 DoSetLayer(final Display display) {
3956 this.display = display;
3957 this.layer = display.layer;
3959 public Displayable getD() { return null; }
3960 public boolean isEmpty() { return false; }
3961 public boolean apply(final int action) {
3962 display.setLayer(layer);
3964 public boolean isIdenticalTo(final Object ob) {
3965 if (!ob instanceof DoSetLayer) return false;
3966 final DoSetLayer dsl = (DoSetLayer) ob;
3967 return dsl.display == this.display && dsl.layer == this.layer;
3972 protected void duplicateLinkAndSendTo(final Displayable active, final int position, final Layer other_layer) {
3973 if (null == active || !(active instanceof Profile)) return;
3974 if (active.getLayer() == other_layer) return; // can't do that!
3975 Profile profile = project.getProjectTree().duplicateChild((Profile)active, position, other_layer);
3976 if (null == profile) return;
3977 active.link(profile);
3978 other_layer.add(profile);
3979 slt.setAndWait(other_layer);
3980 selection.add(profile);
3983 private final HashMap<Color,Layer> layer_channels = new HashMap<Color,Layer>();
3984 private final TreeMap<Integer,LayerPanel> layer_alpha = new TreeMap<Integer,LayerPanel>();
3986 /** Remove all red/blue coloring of layers, and repaint canvas. */
3987 protected void resetLayerColors() {
3988 synchronized (layer_channels) {
3989 for (final Layer l : new ArrayList<Layer>(layer_channels.values())) { // avoid concurrent modification exception
3990 final LayerPanel lp = layer_panels.get(l);
3991 lp.setColor(Color.white);
3992 setColorChannel(lp.layer, Color.white);
3993 lp.slider.setEnabled(true);
3995 layer_channels.clear();
3997 canvas.repaint();
4000 /** Set all layer alphas to zero, and repaint canvas. */
4001 protected void resetLayerAlphas() {
4002 synchronized (layer_channels) {
4003 for (final LayerPanel lp : new ArrayList<LayerPanel>(layer_alpha.values())) {
4004 lp.setAlpha(0);
4006 layer_alpha.clear(); // should have already been cleared
4008 canvas.repaint();
4011 /** Add to layer_alpha table, or remove if alpha is zero. */
4012 protected void storeLayerAlpha(final LayerPanel lp, final float a) {
4013 synchronized (layer_channels) {
4014 if (M.equals(0, a)) {
4015 layer_alpha.remove(lp.layer.getParent().indexOf(lp.layer));
4016 } else {
4017 layer_alpha.put(lp.layer.getParent().indexOf(lp.layer), lp);
4022 static protected final int REPAINT_SINGLE_LAYER = 0;
4023 static protected final int REPAINT_MULTI_LAYER = 1;
4024 static protected final int REPAINT_RGB_LAYER = 2;
4026 /** Sets the values atomically, returns the painting mode. */
4027 protected int getPaintMode(final HashMap<Color,Layer> hm, final ArrayList<LayerPanel> list) {
4028 synchronized (layer_channels) {
4029 if (layer_channels.size() > 0) {
4030 hm.putAll(layer_channels);
4031 hm.put(Color.green, this.layer);
4032 return REPAINT_RGB_LAYER;
4034 list.addAll(layer_alpha.values());
4035 final int len = list.size();
4036 if (len > 1) return REPAINT_MULTI_LAYER;
4037 if (1 == len) {
4038 if (list.get(0).layer == this.layer) return REPAINT_SINGLE_LAYER; // normal mode
4039 return REPAINT_MULTI_LAYER;
4041 return REPAINT_SINGLE_LAYER;
4045 /** Set a layer to be painted as a specific color channel in the canvas.
4046 * Only Color.red and Color.blue are accepted.
4047 * Color.green is reserved for the current layer. */
4048 protected void setColorChannel(final Layer layer, final Color color) {
4049 synchronized (layer_channels) {
4050 if (Color.white == color) {
4051 // Remove
4052 for (final Iterator<Layer> it = layer_channels.values().iterator(); it.hasNext(); ) {
4053 if (it.next() == layer) {
4054 it.remove();
4055 break;
4058 canvas.repaint();
4059 } else if (Color.red == color || Color.blue == color) {
4060 // Reset current of that color, if any, to white
4061 final Layer l = layer_channels.remove(color);
4062 if (null != l) layer_panels.get(l).setColor(Color.white);
4063 // Replace or set new
4064 layer_channels.put(color, layer);
4065 tabs.repaint();
4066 canvas.repaint();
4067 } else {
4068 Utils.log2("Trying to set unacceptable color for layer " + layer + " : " + color);
4070 // enable/disable sliders
4071 final boolean b = 0 == layer_channels.size();
4072 for (final LayerPanel lp : layer_panels.values()) lp.slider.setEnabled(b);
4074 this.canvas.repaint(true);
4077 static public final void updateComponentTreeUI() {
4078 try {
4079 for (final Display d : al_displays) SwingUtilities.updateComponentTreeUI(d.frame);
4080 } catch (Exception e) {
4081 IJError.print(e);