Better setup of look and feel for linux.
[trakem2.git] / ini / trakem2 / display / Display.java
blob6ea647f5320be602f30669a55425e403b0523e08
1 /**
3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2005, 2006 Albert Cardona and Rodney Douglas.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation (http://www.gnu.org/licenses/gpl.txt )
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 You may contact Albert Cardona at acardona at ini.phys.ethz.ch
20 Institute of Neuroinformatics, University of Zurich / ETH, Switzerland.
21 **/
23 package ini.trakem2.display;
25 import ij.*;
26 import ij.gui.*;
27 import ij.measure.Calibration;
28 import ini.trakem2.Project;
29 import ini.trakem2.ControlWindow;
30 import ini.trakem2.persistence.DBObject;
31 import ini.trakem2.persistence.Loader;
32 import ini.trakem2.utils.IJError;
33 import ini.trakem2.imaging.PatchStack;
34 import ini.trakem2.imaging.Registration;
35 import ini.trakem2.imaging.StitchingTEM;
36 import ini.trakem2.imaging.Blending;
37 import ini.trakem2.utils.ProjectToolbar;
38 import ini.trakem2.utils.Utils;
39 import ini.trakem2.utils.DNDInsertImage;
40 import ini.trakem2.utils.Search;
41 import ini.trakem2.utils.Bureaucrat;
42 import ini.trakem2.utils.Worker;
43 import ini.trakem2.utils.Dispatcher;
44 import ini.trakem2.utils.Lock;
45 import ini.trakem2.utils.M;
46 import ini.trakem2.tree.*;
48 import javax.swing.*;
49 import javax.swing.event.*;
51 import mpicbg.trakem2.align.AlignTask;
53 import java.awt.*;
54 import java.awt.event.*;
55 import java.util.*;
56 import java.lang.reflect.Method;
57 import java.io.Writer;
58 import java.util.concurrent.Future;
60 import lenscorrection.DistortionCorrectionTask;
62 /** A Display is a class to show a Layer and enable mouse and keyboard manipulation of all its components. */
63 public final class Display extends DBObject implements ActionListener, ImageListener {
65 /** The Layer this Display is showing. */
66 private Layer layer;
68 private Displayable active = null;
69 /** All selected Displayable objects, including the active one. */
70 final private Selection selection = new Selection(this);
72 private JFrame frame;
73 private JTabbedPane tabs;
74 private Hashtable<Class,JScrollPane> ht_tabs;
75 private JScrollPane scroll_patches;
76 private JPanel panel_patches;
77 private JScrollPane scroll_profiles;
78 private JPanel panel_profiles;
79 private JScrollPane scroll_zdispl;
80 private JPanel panel_zdispl;
81 private JScrollPane scroll_channels;
82 private JPanel panel_channels;
83 private JScrollPane scroll_labels;
84 private JPanel panel_labels;
86 private JPanel panel_layers;
87 private JScrollPane scroll_layers;
88 private Hashtable<Layer,LayerPanel> layer_panels = new Hashtable<Layer,LayerPanel>();
90 private JSlider transp_slider;
91 private DisplayNavigator navigator;
92 private JScrollBar scroller;
94 private DisplayCanvas canvas; // WARNING this is an AWT component, since it extends ImageCanvas
95 private JPanel canvas_panel; // and this is a workaround, to better (perhaps) integrate the awt canvas inside a JSplitPane
96 private JSplitPane split;
98 private JPopupMenu popup = null;
100 /** Contains the packed alphas of every channel. */
101 private int c_alphas = 0xffffffff; // all 100 % visible
102 private Channel[] channels;
104 private Hashtable<Displayable,DisplayablePanel> ht_panels = new Hashtable<Displayable,DisplayablePanel>();
106 /** Handle drop events, to insert image files. */
107 private DNDInsertImage dnd;
109 private boolean size_adjusted = false;
111 private int scroll_step = 1;
113 /** Keep track of all existing Display objects. */
114 static private ArrayList<Display> al_displays = new ArrayList<Display>();
115 /** The currently focused Display, if any. */
116 static private Display front = null;
118 /** Displays to open when all objects have been reloaded from the database. */
119 static private final Hashtable ht_later = new Hashtable();
121 /** A thread to handle user actions, for example an event sent from a popup menu. */
122 private final Dispatcher dispatcher = new Dispatcher();
124 static private WindowAdapter window_listener = new WindowAdapter() {
125 /** Unregister the closed Display. */
126 public void windowClosing(WindowEvent we) {
127 final Object source = we.getSource();
128 for (Iterator it = al_displays.iterator(); it.hasNext(); ) {
129 Display d = (Display)it.next();
130 if (source == d.frame) {
131 it.remove();
132 if (d == front) front = null;
133 d.remove(false); //calls destroy
134 break;
138 /** Set the source Display as front. */
139 public void windowActivated(WindowEvent we) {
140 // find which was it to make it be the front
141 final Object source = we.getSource();
142 for (final Display d : al_displays) {
143 if (source == d.frame) {
144 front = d;
145 // set toolbar
146 ProjectToolbar.setProjectToolbar();
147 // now, select the layer in the LayerTree
148 front.getProject().select(front.layer);
149 // finally, set the virtual ImagePlus that ImageJ will see
150 d.setTempCurrentImage();
151 // copied from ij.gui.ImageWindow, with modifications
152 if (IJ.isMacintosh() && IJ.getInstance()!=null) {
153 IJ.wait(10); // may be needed for Java 1.4 on OS X
154 d.frame.setMenuBar(ij.Menus.getMenuBar());
156 return;
159 // else, restore the ImageJ toolbar for non-project images
160 //if (!source.equals(IJ.getInstance())) {
161 // ProjectToolbar.setImageJToolbar();
164 /** Restore the ImageJ toolbar */
165 public void windowDeactivated(WindowEvent we) {
166 // Can't, the user can never click the ProjectToolbar then. This has to be done in a different way, for example checking who is the WindowManager.getCurrentImage (and maybe setting a dummy image into it) //ProjectToolbar.setImageJToolbar();
168 /** Call a pack() when the window is maximized to fit the canvas correctly. */
169 public void windowStateChanged(WindowEvent we) {
170 final Object source = we.getSource();
171 for (final Display d : al_displays) {
172 if (source != d.frame) continue;
173 d.pack();
174 break;
179 static private MouseListener frame_mouse_listener = new MouseAdapter() {
180 public void mouseReleased(MouseEvent me) {
181 Object source = me.getSource();
182 for (final Display d : al_displays) {
183 if (d.frame == source) {
184 if (d.size_adjusted) {
185 d.pack();
186 d.size_adjusted = false;
187 Utils.log2("mouse released on JFrame");
189 break;
195 private int last_frame_state = frame.NORMAL;
197 // THIS WHOLE SYSTEM OF LISTENERS IS BROKEN:
198 // * when zooming in, the window growths in width a few pixels.
199 // * when enlarging the window quickly, the canvas is not resized as large as it should.
200 // -- the whole problem: swing threading, which I am not handling properly. It's hard.
201 static private ComponentListener component_listener = new ComponentAdapter() {
202 public void componentResized(ComponentEvent ce) {
203 final Display d = getDisplaySource(ce);
204 if (null != d) {
205 d.size_adjusted = true; // works in combination with mouseReleased to call pack(), avoiding infinite loops.
206 d.adjustCanvas();
207 int frame_state = d.frame.getExtendedState();
208 if (frame_state != d.last_frame_state) { // this setup avoids infinite loops (for pack() calls componentResized as well
209 d.last_frame_state = frame_state;
210 if (d.frame.ICONIFIED != frame_state) d.pack();
214 public void componentMoved(ComponentEvent ce) {
215 Display d = getDisplaySource(ce);
216 if (null != d) d.updateInDatabase("position");
218 private Display getDisplaySource(ComponentEvent ce) {
219 final Object source = ce.getSource();
220 for (final Display d : al_displays) {
221 if (source == d.frame) {
222 return d;
225 return null;
229 static private ChangeListener tabs_listener = new ChangeListener() {
230 /** Listen to tab changes. */
231 public void stateChanged(final ChangeEvent ce) {
232 final Object source = ce.getSource();
233 for (final Display d : al_displays) {
234 if (source == d.tabs) {
235 d.dispatcher.exec(new Runnable() { public void run() {
236 // creating tabs fires the event!!!
237 if (null == d.frame || null == d.canvas) return;
238 final Container tab = (Container)d.tabs.getSelectedComponent();
239 if (tab == d.scroll_channels) {
240 // find active channel if any
241 for (int i=0; i<d.channels.length; i++) {
242 if (d.channels[i].isActive()) {
243 d.transp_slider.setValue((int)(d.channels[i].getAlpha() * 100));
244 break;
247 } else {
248 // recreate contents
250 int count = tab.getComponentCount();
251 if (0 == count || (1 == count && tab.getComponent(0).getClass().equals(JLabel.class))) {
252 */ // ALWAYS, because it could be the case that the user changes layer while on one specific tab, and then clicks on the other tab which may not be empty and shows totally the wrong contents (i.e. for another layer)
254 String label = null;
255 ArrayList al = null;
256 JPanel p = null;
257 if (tab == d.scroll_zdispl) {
258 label = "Z-space objects";
259 al = d.layer.getParent().getZDisplayables();
260 p = d.panel_zdispl;
261 } else if (tab == d.scroll_patches) {
262 label = "Patches";
263 al = d.layer.getDisplayables(Patch.class);
264 p = d.panel_patches;
265 } else if (tab == d.scroll_labels) {
266 label = "Labels";
267 al = d.layer.getDisplayables(DLabel.class);
268 p = d.panel_labels;
269 } else if (tab == d.scroll_profiles) {
270 label = "Profiles";
271 al = d.layer.getDisplayables(Profile.class);
272 p = d.panel_profiles;
273 } else if (tab == d.scroll_layers) {
274 // nothing to do
275 return;
278 d.updateTab(p, label, al);
279 //Utils.updateComponent(d.tabs.getSelectedComponent());
280 //Utils.log2("updated tab: " + p + " with " + al.size() + " objects.");
283 if (null != d.active) {
284 // set the transp slider to the alpha value of the active Displayable if any
285 d.transp_slider.setValue((int)(d.active.getAlpha() * 100));
286 DisplayablePanel dp = d.ht_panels.get(d.active);
287 if (null != dp) dp.setActive(true);
290 }});
291 break;
297 private final ScrollLayerListener scroller_listener = new ScrollLayerListener();
299 private class ScrollLayerListener implements AdjustmentListener {
301 public void adjustmentValueChanged(final AdjustmentEvent ae) {
302 final int index = scroller.getValue();
303 slt.set(layer.getParent().getLayer(index));
307 private final SetLayerThread slt = new SetLayerThread();
309 private class SetLayerThread extends Thread {
311 private boolean go = true;
312 private Layer layer;
313 private final Lock lock = new Lock();
314 private final Lock lock2 = new Lock();
316 SetLayerThread() {
317 setPriority(Thread.NORM_PRIORITY);
318 setDaemon(true);
319 start();
322 public final void set(final Layer layer) {
323 synchronized (lock) {
324 this.layer = layer;
326 synchronized (this) {
327 notify();
331 public final void setAndWait(final Layer layer) {
332 lock2.lock();
333 set(layer);
336 public void run() {
337 while (go) {
338 while (null == this.layer) {
339 synchronized (this) {
340 try { wait(); } catch (InterruptedException ie) {}
343 Layer layer = null;
344 synchronized (lock) {
345 layer = this.layer;
346 this.layer = null;
349 if (!go) return; // after nullifying layer
351 if (null != layer) {
352 Display.this.setLayer(layer);
353 Display.this.updateInDatabase("layer_id");
355 // unlock any calls waiting on setAndWait
356 synchronized (lock2) {
357 lock2.unlock();
360 // cleanup:
361 synchronized (lock2) {
362 lock2.unlock();
366 public void waitForLayer() {
367 while (null != layer && go) {
368 try { Thread.sleep(10); } catch (Exception e) {}
372 public void quit() {
373 go = false;
377 /** Creates a new Display with adjusted magnification to fit in the screen. */
378 static public void createDisplay(final Project project, final Layer layer) {
379 SwingUtilities.invokeLater(new Runnable() { public void run() {
380 Display display = new Display(project, layer);
381 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
382 Rectangle srcRect = new Rectangle(0, 0, (int)layer.getLayerWidth(), (int)layer.getLayerHeight());
383 double mag = screen.width / layer.getLayerWidth();
384 if (mag * layer.getLayerHeight() > screen.height) mag = screen.height / layer.getLayerHeight();
385 mag = display.canvas.getLowerZoomLevel2(mag);
386 if (mag > 1.0) mag = 1.0;
387 //display.getCanvas().setup(mag, srcRect); // would call pack() at the wrong time!
388 // ... so instead: manually
389 display.getCanvas().setMagnification(mag);
390 display.getCanvas().setSrcRect(srcRect.x, srcRect.y, srcRect.width, srcRect.height);
391 display.getCanvas().setDrawingSize((int)Math.ceil(srcRect.width * mag), (int)Math.ceil(srcRect.height * mag));
393 display.updateTitle();
394 ij.gui.GUI.center(display.frame);
395 display.frame.pack();
396 }});
399 /** A new Display from scratch, to show the given Layer. */
400 public Display(Project project, final Layer layer) {
401 super(project);
402 front = this;
403 makeGUI(layer, null);
404 ImagePlus.addImageListener(this);
405 setLayer(layer);
406 this.layer = layer; // after, or it doesn't update properly
407 al_displays.add(this);
408 addToDatabase();
411 /** For reconstruction purposes. The Display will be stored in the ht_later.*/
412 public Display(Project project, long id, Layer layer, Object[] props) {
413 super(project, id);
414 synchronized (ht_later) {
415 Display.ht_later.put(this, props);
417 this.layer = layer;
420 /** Open a new Display centered around the given Displayable. */
421 public Display(Project project, Layer layer, Displayable displ) {
422 super(project);
423 front = this;
424 active = displ;
425 makeGUI(layer, null);
426 ImagePlus.addImageListener(this);
427 setLayer(layer);
428 this.layer = layer; // after set layer!
429 al_displays.add(this);
430 addToDatabase();
433 /** Reconstruct a Display from an XML entry, to be opened when everything is ready. */
434 public Display(Project project, long id, Layer layer, HashMap ht_attributes) {
435 super(project, id);
436 if (null == layer) {
437 Utils.log2("Display: need a non-null Layer for id=" + id);
438 return;
440 Rectangle srcRect = new Rectangle(0, 0, (int)layer.getLayerWidth(), (int)layer.getLayerHeight());
441 double magnification = 0.25;
442 Point p = new Point(0, 0);
443 int c_alphas = 0xffffffff;
444 int c_alphas_state = 0xffffffff;
445 for (Iterator it = ht_attributes.entrySet().iterator(); it.hasNext(); ) {
446 Map.Entry entry = (Map.Entry)it.next();
447 String key = (String)entry.getKey();
448 String data = (String)entry.getValue();
449 if (key.equals("srcrect_x")) { // reflection! Reflection!
450 srcRect.x = Integer.parseInt(data);
451 } else if (key.equals("srcrect_y")) {
452 srcRect.y = Integer.parseInt(data);
453 } else if (key.equals("srcrect_width")) {
454 srcRect.width = Integer.parseInt(data);
455 } else if (key.equals("srcrect_height")) {
456 srcRect.height = Integer.parseInt(data);
457 } else if (key.equals("magnification")) {
458 magnification = Double.parseDouble(data);
459 } else if (key.equals("x")) {
460 p.x = Integer.parseInt(data);
461 } else if (key.equals("y")) {
462 p.y = Integer.parseInt(data);
463 } else if (key.equals("c_alphas")) {
464 try {
465 c_alphas = Integer.parseInt(data);
466 } catch (Exception ex) {
467 c_alphas = 0xffffffff;
469 } else if (key.equals("c_alphas_state")) {
470 try {
471 c_alphas_state = Integer.parseInt(data);
472 } catch (Exception ex) {
473 IJError.print(ex);
474 c_alphas_state = 0xffffffff;
476 } else if (key.equals("scroll_step")) {
477 try {
478 setScrollStep(Integer.parseInt(data));
479 } catch (Exception ex) {
480 IJError.print(ex);
481 setScrollStep(1);
484 // TODO the above is insecure, in that data is not fully checked to be within bounds.
486 Object[] props = new Object[]{p, new Double(magnification), srcRect, new Long(layer.getId()), new Integer(c_alphas), new Integer(c_alphas_state)};
487 synchronized (ht_later) {
488 Display.ht_later.put(this, props);
490 this.layer = layer;
493 /** After reloading a project from the database, open the Displays that the project had. */
494 static public Bureaucrat openLater() {
495 final Hashtable ht_later_local;
496 synchronized (ht_later) {
497 if (0 == ht_later.size()) return null;
498 ht_later_local = new Hashtable(ht_later);
499 ht_later.keySet().removeAll(ht_later_local.keySet());
501 final Worker worker = new Worker("Opening displays") {
502 public void run() {
503 startedWorking();
504 try {
505 Thread.sleep(300); // waiting for Swing
507 for (Enumeration e = ht_later_local.keys(); e.hasMoreElements(); ) {
508 final Display d = (Display)e.nextElement();
509 front = d; // must be set before repainting any ZDisplayable!
510 Object[] props = (Object[])ht_later_local.get(d);
511 if (ControlWindow.isGUIEnabled()) d.makeGUI(d.layer, props);
512 d.setLayerLater(d.layer, d.layer.get(((Long)props[3]).longValue())); //important to do it after makeGUI
513 if (!ControlWindow.isGUIEnabled()) continue;
514 ImagePlus.addImageListener(d);
515 al_displays.add(d);
516 d.updateTitle();
517 // force a repaint if a prePaint was done TODO this should be properly managed with repaints using always the invokeLater, but then it's DOG SLOW
518 if (d.canvas.getMagnification() > 0.499) {
519 SwingUtilities.invokeLater(new Runnable() { public void run() {
520 d.repaint(d.layer);
521 d.project.getLoader().setChanged(false);
522 Utils.log2("A set to false");
523 }});
525 d.project.getLoader().setChanged(false);
526 Utils.log2("B set to false");
528 if (null != front) front.getProject().select(front.layer);
530 } catch (Throwable t) {
531 IJError.print(t);
532 } finally {
533 finishedWorking();
537 return Bureaucrat.createAndStart(worker, ((Display)ht_later_local.keySet().iterator().next()).getProject()); // gets the project from the first Display
540 private void makeGUI(final Layer layer, final Object[] props) {
541 // gather properties
542 Point p = null;
543 double mag = 1.0D;
544 Rectangle srcRect = null;
545 if (null != props) {
546 p = (Point)props[0];
547 mag = ((Double)props[1]).doubleValue();
548 srcRect = (Rectangle)props[2];
551 // transparency slider
552 this.transp_slider = new JSlider(javax.swing.SwingConstants.HORIZONTAL, 0, 100, 100);
553 this.transp_slider.setBackground(Color.white);
554 this.transp_slider.setMinimumSize(new Dimension(250, 20));
555 this.transp_slider.setMaximumSize(new Dimension(250, 20));
556 this.transp_slider.setPreferredSize(new Dimension(250, 20));
557 TransparencySliderListener tsl = new TransparencySliderListener();
558 this.transp_slider.addChangeListener(tsl);
559 this.transp_slider.addMouseListener(tsl);
560 for (final KeyListener kl : this.transp_slider.getKeyListeners()) {
561 this.transp_slider.removeKeyListener(kl);
564 // Tabbed pane on the left
565 this.tabs = new JTabbedPane();
566 this.tabs.setMinimumSize(new Dimension(250, 300));
567 this.tabs.setBackground(Color.white);
568 this.tabs.addChangeListener(tabs_listener);
570 // Tab 1: Patches
571 this.panel_patches = makeTabPanel();
572 this.panel_patches.add(new JLabel("No patches."));
573 this.scroll_patches = makeScrollPane(panel_patches);
574 this.tabs.add("Patches", scroll_patches);
576 // Tab 2: Profiles
577 this.panel_profiles = makeTabPanel();
578 this.panel_profiles.add(new JLabel("No profiles."));
579 this.scroll_profiles = makeScrollPane(panel_profiles);
580 this.tabs.add("Profiles", scroll_profiles);
582 // Tab 3: pipes
583 this.panel_zdispl = makeTabPanel();
584 this.panel_zdispl.add(new JLabel("No objects."));
585 this.scroll_zdispl = makeScrollPane(panel_zdispl);
586 this.tabs.add("Z space", scroll_zdispl);
588 // Tab 4: channels
589 this.panel_channels = makeTabPanel();
590 this.scroll_channels = makeScrollPane(panel_channels);
591 this.channels = new Channel[4];
592 this.channels[0] = new Channel(this, Channel.MONO);
593 this.channels[1] = new Channel(this, Channel.RED);
594 this.channels[2] = new Channel(this, Channel.GREEN);
595 this.channels[3] = new Channel(this, Channel.BLUE);
596 //this.panel_channels.add(this.channels[0]);
597 this.panel_channels.add(this.channels[1]);
598 this.panel_channels.add(this.channels[2]);
599 this.panel_channels.add(this.channels[3]);
600 this.tabs.add("Opacity", scroll_channels);
602 // Tab 5: labels
603 this.panel_labels = makeTabPanel();
604 this.panel_labels.add(new JLabel("No labels."));
605 this.scroll_labels = makeScrollPane(panel_labels);
606 this.tabs.add("Labels", scroll_labels);
608 // Tab 6: layers
609 this.panel_layers = makeTabPanel();
610 this.scroll_layers = makeScrollPane(panel_layers);
611 recreateLayerPanels(layer);
612 this.scroll_layers.addMouseWheelListener(canvas);
613 this.tabs.add("Layers", scroll_layers);
615 this.ht_tabs = new Hashtable<Class,JScrollPane>();
616 this.ht_tabs.put(Patch.class, scroll_patches);
617 this.ht_tabs.put(Profile.class, scroll_profiles);
618 this.ht_tabs.put(ZDisplayable.class, scroll_zdispl);
619 this.ht_tabs.put(AreaList.class, scroll_zdispl);
620 this.ht_tabs.put(Pipe.class, scroll_zdispl);
621 this.ht_tabs.put(Polyline.class, scroll_zdispl);
622 this.ht_tabs.put(Ball.class, scroll_zdispl);
623 this.ht_tabs.put(Dissector.class, scroll_zdispl);
624 this.ht_tabs.put(DLabel.class, scroll_labels);
625 // channels not included
626 // layers not included
628 // Navigator
629 this.navigator = new DisplayNavigator(this, layer.getLayerWidth(), layer.getLayerHeight());
630 // Layer scroller (to scroll slices)
631 int extent = (int)(250.0 / layer.getParent().size());
632 if (extent < 10) extent = 10;
633 this.scroller = new JScrollBar(JScrollBar.HORIZONTAL);
634 updateLayerScroller(layer);
635 this.scroller.addAdjustmentListener(scroller_listener);
638 // Left panel, contains the transp slider, the tabbed pane, the navigation panel and the layer scroller
639 JPanel left = new JPanel();
640 left.setBackground(Color.white);
641 BoxLayout left_layout = new BoxLayout(left, BoxLayout.Y_AXIS);
642 left.setLayout(left_layout);
643 left.add(transp_slider);
644 left.add(tabs);
645 left.add(navigator);
646 left.add(scroller);
648 // Canvas
649 this.canvas = new DisplayCanvas(this, (int)Math.ceil(layer.getLayerWidth()), (int)Math.ceil(layer.getLayerHeight()));
650 this.canvas_panel = new JPanel();
651 GridBagLayout gb = new GridBagLayout();
652 this.canvas_panel.setLayout(gb);
653 GridBagConstraints c = new GridBagConstraints();
654 c.fill = GridBagConstraints.BOTH;
655 c.anchor = GridBagConstraints.NORTHWEST;
656 gb.setConstraints(this.canvas_panel, c);
657 gb.setConstraints(this.canvas, c);
659 // prevent new Displays from screweing up if input is globally disabled
660 if (!project.isInputEnabled()) this.canvas.setReceivesInput(false);
662 this.canvas_panel.add(canvas);
664 this.navigator.addMouseWheelListener(canvas);
666 this.transp_slider.addKeyListener(canvas);
668 // Split pane to contain everything
669 this.split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, left, canvas_panel);
670 this.split.setOneTouchExpandable(true); // NOT present in all L&F (?)
671 this.split.setBackground(Color.white);
673 // fix
674 gb.setConstraints(split.getRightComponent(), c);
676 // JFrame to show the split pane
677 this.frame = ControlWindow.createJFrame(layer.toString());
678 this.frame.setBackground(Color.white);
679 this.frame.getContentPane().setBackground(Color.white);
680 if (IJ.isMacintosh() && IJ.getInstance()!=null) {
681 IJ.wait(10); // may be needed for Java 1.4 on OS X
682 this.frame.setMenuBar(ij.Menus.getMenuBar());
684 this.frame.addWindowListener(window_listener);
685 this.frame.addComponentListener(component_listener);
686 this.frame.getContentPane().add(split);
687 this.frame.addMouseListener(frame_mouse_listener);
688 //doesn't exist//this.frame.setMinimumSize(new Dimension(270, 600));
690 if (null != props) {
691 // restore canvas
692 canvas.setup(mag, srcRect);
693 // restore visibility of each channel
694 int cs = ((Integer)props[5]).intValue(); // aka c_alphas_state
695 int[] sel = new int[4];
696 sel[0] = ((cs&0xff000000)>>24);
697 sel[1] = ((cs&0xff0000)>>16);
698 sel[2] = ((cs&0xff00)>>8);
699 sel[3] = (cs&0xff);
700 // restore channel alphas
701 this.c_alphas = ((Integer)props[4]).intValue();
702 channels[0].setAlpha( (float)((c_alphas&0xff000000)>>24) / 255.0f , 0 != sel[0]);
703 channels[1].setAlpha( (float)((c_alphas&0xff0000)>>16) / 255.0f , 0 != sel[1]);
704 channels[2].setAlpha( (float)((c_alphas&0xff00)>>8) / 255.0f , 0 != sel[2]);
705 channels[3].setAlpha( (float) (c_alphas&0xff) / 255.0f , 0 != sel[3]);
706 // restore visibility in the working c_alphas
707 this.c_alphas = ((0 != sel[0] ? (int)(255 * channels[0].getAlpha()) : 0)<<24) + ((0 != sel[1] ? (int)(255 * channels[1].getAlpha()) : 0)<<16) + ((0 != sel[2] ? (int)(255 * channels[2].getAlpha()) : 0)<<8) + (0 != sel[3] ? (int)(255 * channels[3].getAlpha()) : 0);
710 if (null != active && null != layer) {
711 Rectangle r = active.getBoundingBox();
712 r.x -= r.width/2;
713 r.y -= r.height/2;
714 r.width += r.width;
715 r.height += r.height;
716 if (r.x < 0) r.x = 0;
717 if (r.y < 0) r.y = 0;
718 if (r.width > layer.getLayerWidth()) r.width = (int)layer.getLayerWidth();
719 if (r.height> layer.getLayerHeight())r.height= (int)layer.getLayerHeight();
720 double magn = layer.getLayerWidth() / (double)r.width;
721 canvas.setup(magn, r);
724 // add keyListener to the whole frame
725 this.tabs.addKeyListener(canvas);
726 this.canvas_panel.addKeyListener(canvas);
727 this.frame.addKeyListener(canvas);
729 this.frame.pack();
730 ij.gui.GUI.center(this.frame);
731 this.frame.setVisible(true);
732 ProjectToolbar.setProjectToolbar(); // doesn't get it through events
734 final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
736 if (null != props) {
737 // fix positioning outside the screen (dual to single monitor)
738 if (p.x >= 0 && p.x < screen.width - 50 && p.y >= 0 && p.y <= screen.height - 50) this.frame.setLocation(p);
739 else frame.setLocation(0, 0);
742 // fix excessive size
743 final Rectangle box = this.frame.getBounds();
744 int x = box.x;
745 int y = box.y;
746 int width = box.width;
747 int height = box.height;
748 if (box.width > screen.width) { x = 0; width = screen.width; }
749 if (box.height > screen.height) { y = 0; height = screen.height; }
750 if (x != box.x || y != box.y) {
751 this.frame.setLocation(x, y + (0 == y ? 30 : 0)); // added insets for bad window managers
752 updateInDatabase("position");
754 if (width != box.width || height != box.height) {
755 this.frame.setSize(new Dimension(width -10, height -30)); // added insets for bad window managers
757 if (null == props) {
758 // try to optimize canvas dimensions and magn
759 double magn = layer.getLayerHeight() / screen.height;
760 if (magn > 1.0) magn = 1.0;
761 long size = 0;
762 // limit magnification if appropriate
763 for (Iterator it = layer.getDisplayables(Patch.class).iterator(); it.hasNext(); ) {
764 final Patch pa = (Patch)it.next();
765 final Rectangle ba = pa.getBoundingBox();
766 size += (long)(ba.width * ba.height);
768 if (size > 10000000) canvas.setInitialMagnification(0.25); // 10 Mb
769 else {
770 this.frame.setSize(new Dimension((int)(screen.width * 0.66), (int)(screen.height * 0.66)));
774 Utils.updateComponent(tabs); // otherwise fails in FreeBSD java 1.4.2 when reconstructing
777 // Set the calibration of the FakeImagePlus to that of the LayerSet
778 ((FakeImagePlus)canvas.getFakeImagePlus()).setCalibrationSuper(layer.getParent().getCalibrationCopy());
780 // Set the FakeImagePlus as the current image
781 setTempCurrentImage();
783 // create a drag and drop listener
784 dnd = new DNDInsertImage(this);
786 // start a repainting thread
787 if (null != props) {
788 canvas.repaint(true); // repaint() is unreliable
791 // Set the minimum size of the tabbed pane on the left, so it can be completely collapsed now that it has been properly displayed. This is a patch to the lack of respect for the setDividerLocation method.
792 SwingUtilities.invokeLater(new Runnable() {
793 public void run() {
794 tabs.setMinimumSize(new Dimension(0, 100));
795 Display.scrollbar_width = Display.this.scroll_patches.getVerticalScrollBar().getPreferredSize().width; // using scroll_patches since it's the one selected by default and thus visible and painted
796 ControlWindow.setLookAndFeel();
801 private JPanel makeTabPanel() {
802 JPanel panel = new JPanel();
803 BoxLayout layout = new BoxLayout(panel, BoxLayout.Y_AXIS);
804 panel.setLayout(layout);
805 return panel;
808 private JScrollPane makeScrollPane(Component c) {
809 JScrollPane jsp = new JScrollPane(c);
810 jsp.setBackground(Color.white); // no effect
811 jsp.getViewport().setBackground(Color.white); // no effect
812 // adjust scrolling to use one DisplayablePanel as the minimal unit
813 jsp.getVerticalScrollBar().setBlockIncrement(DisplayablePanel.HEIGHT); // clicking within the track
814 jsp.getVerticalScrollBar().setUnitIncrement(DisplayablePanel.HEIGHT); // clicking on an arrow
815 jsp.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
816 jsp.setPreferredSize(new Dimension(250, 300));
817 jsp.setMinimumSize(new Dimension(250, 300));
818 return jsp;
821 static protected int scrollbar_width = 0;
823 public JPanel getCanvasPanel() {
824 return canvas_panel;
827 public DisplayCanvas getCanvas() {
828 return canvas;
831 public synchronized void setLayer(final Layer layer) {
832 if (null == layer || layer == this.layer) return;
833 translateLayerColors(this.layer, layer);
834 if (tabs.getSelectedComponent() == scroll_layers) {
835 SwingUtilities.invokeLater(new Runnable() { public void run() {
836 scrollToShow(scroll_layers, layer_panels.get(layer));
837 }});
839 final boolean set_zdispl = null == Display.this.layer || layer.getParent() != Display.this.layer.getParent();
840 if (selection.isTransforming()) {
841 Utils.log("Can't browse layers while transforming.\nCANCEL the transform first with the ESCAPE key or right-click -> cancel.");
842 scroller.setValue(Display.this.layer.getParent().getLayerIndex(Display.this.layer.getId()));
843 return;
845 this.layer = layer;
846 scroller.setValue(layer.getParent().getLayerIndex(layer.getId()));
848 // update the current Layer pointer in ZDisplayable objects
849 for (Iterator it = layer.getParent().getZDisplayables().iterator(); it.hasNext(); ) {
850 ((ZDisplayable)it.next()).setLayer(layer); // the active layer
853 updateVisibleTab(set_zdispl);
855 // see if a lot has to be reloaded, put the relevant ones at the end
856 project.getLoader().prepare(layer);
857 updateTitle(); // to show the new 'z'
858 // select the Layer in the LayerTree
859 project.select(Display.this.layer); // does so in a separate thread
860 // update active Displayable:
862 // deselect all except ZDisplayables
863 final ArrayList<Displayable> sel = selection.getSelected();
864 final Displayable last_active = Display.this.active;
865 int sel_next = -1;
866 for (final Iterator<Displayable> it = sel.iterator(); it.hasNext(); ) {
867 final Displayable d = it.next();
868 if (!(d instanceof ZDisplayable)) {
869 it.remove();
870 selection.remove(d);
871 if (d == last_active && sel.size() > 0) {
872 // select the last one of the remaining, if any
873 sel_next = sel.size()-1;
877 if (-1 != sel_next && sel.size() > 0) select(sel.get(sel_next), true);
879 // Keep Profile chain selected, for best ease of use:
880 if (null != last_active && last_active.getClass() == Profile.class && last_active.isLinked(Profile.class)) {
881 Utils.log2("last active was a profile: " + last_active);
882 Displayable other = null;
883 for (final Displayable prof : last_active.getLinked(Profile.class)) {
884 if (prof.getLayer() == layer) {
885 other = prof;
886 break;
889 if (null != other) selection.add(other);
892 // repaint everything
893 navigator.repaint(true);
894 canvas.repaint(true);
896 // repaint tabs (hard as hell)
897 Utils.updateComponent(tabs);
898 // @#$%^! The above works half the times, so explicit repaint as well:
899 Component c = tabs.getSelectedComponent();
900 if (null == c) {
901 c = scroll_patches;
902 tabs.setSelectedComponent(scroll_patches);
904 Utils.updateComponent(c);
906 project.getLoader().setMassiveMode(false); // resetting if it was set true
908 // update the coloring in the ProjectTree
909 project.getProjectTree().updateUILater();
911 setTempCurrentImage();
914 static public void updateVisibleTabs() {
915 for (final Display d : al_displays) {
916 d.updateVisibleTab(true);
920 /** Recreate the tab that is being shown. */
921 public void updateVisibleTab(boolean set_zdispl) {
922 // update only the visible tab
923 switch (tabs.getSelectedIndex()) {
924 case 0:
925 ht_panels.clear();
926 updateTab(panel_patches, "Patches", layer.getDisplayables(Patch.class));
927 break;
928 case 1:
929 ht_panels.clear();
930 updateTab(panel_profiles, "Profiles", layer.getDisplayables(Profile.class));
931 break;
932 case 2:
933 if (set_zdispl) {
934 ht_panels.clear();
935 updateTab(panel_zdispl, "Z-space objects", layer.getParent().getZDisplayables());
937 break;
938 // case 3: channel opacities
939 case 4:
940 ht_panels.clear();
941 updateTab(panel_labels, "Labels", layer.getDisplayables(DLabel.class));
942 break;
943 // case 5: layer panels
948 private void setLayerLater(final Layer layer, final Displayable active) {
949 if (null == layer) return;
950 this.layer = layer;
951 if (!ControlWindow.isGUIEnabled()) return;
952 SwingUtilities.invokeLater(new Runnable() { public void run() {
953 // empty the tabs, except channels and pipes
954 clearTab(panel_profiles, "Profiles");
955 clearTab(panel_patches, "Patches");
956 clearTab(panel_labels, "Labels");
957 // distribute Displayable to the tabs. Ignore LayerSet instances.
958 if (null == ht_panels) ht_panels = new Hashtable<Displayable,DisplayablePanel>();
959 else ht_panels.clear();
960 Iterator it = layer.getDisplayables().iterator();
961 while (it.hasNext()) {
962 add((Displayable)it.next(), false, false);
964 it = layer.getParent().getZDisplayables().iterator(); // the pipes, that live in the LayerSet
965 while (it.hasNext()) {
966 add((Displayable)it.next(), false, false);
968 navigator.repaint(true); // was not done when adding
969 Utils.updateComponent(tabs.getSelectedComponent());
971 setActive(active);
972 }});
973 // swing issues:
975 new Thread() {
976 public void run() {
977 setPriority(Thread.NORM_PRIORITY);
978 try { Thread.sleep(1000); } catch (Exception e) {}
979 setActive(active);
981 }.start();
985 /** Remove all components from the tab and add a "No [label]" label to each. */
986 private void clearTab(final Container c, final String label) {
987 c.removeAll();
988 c.add(new JLabel("No " + label + "."));
989 // magic cocktail:
990 if (tabs.getSelectedComponent() == c) {
991 Utils.updateComponent(c);
995 /** A class to listen to the transparency_slider of the DisplayablesSelectorWindow. */
996 private class TransparencySliderListener extends MouseAdapter implements ChangeListener {
998 public void stateChanged(ChangeEvent ce) {
999 //change the transparency value of the current active displayable
1000 float new_value = (float)((JSlider)ce.getSource()).getValue();
1001 setTransparency(new_value / 100.0f);
1004 public void mousePressed(MouseEvent me) {
1005 JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
1006 if (scroll != scroll_channels && !selection.isEmpty()) selection.addDataEditStep(new String[]{"alpha"});
1009 public void mouseReleased(MouseEvent me) {
1010 // update navigator window
1011 navigator.repaint(true);
1012 JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
1013 if (scroll != scroll_channels && !selection.isEmpty()) selection.addDataEditStep(new String[]{"alpha"});
1017 /** Context-sensitive: to a Displayable, or to a channel. */
1018 private void setTransparency(final float value) {
1019 JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
1020 if (scroll == scroll_channels) {
1021 for (int i=0; i<4; i++) {
1022 if (channels[i].getBackground() == Color.cyan) {
1023 channels[i].setAlpha(value); // will call back and repaint the Display
1024 return;
1027 } else if (null != active) {
1028 if (value != active.getAlpha()) { // because there's a callback from setActive that would then affect all other selected Displayable without having dragged the slider, i.e. just by being selected.
1029 canvas.invalidateVolatile();
1030 selection.setAlpha(value);
1035 public void setTransparencySlider(final float transp) {
1036 if (transp >= 0.0f && transp <= 1.0f) {
1037 // fire event
1038 transp_slider.setValue((int)(transp * 100));
1042 /** Mark the canvas for updating the offscreen images if the given Displayable is NOT the active. */ // Used by the Displayable.setVisible for example.
1043 static public void setUpdateGraphics(final Layer layer, final Displayable displ) {
1044 for (final Display d : al_displays) {
1045 if (layer == d.layer && null != d.active && d.active != displ) {
1046 d.canvas.setUpdateGraphics(true);
1051 /** Flag the DisplayCanvas of Displays showing the given Layer to update their offscreen images.*/
1052 static public void setUpdateGraphics(final Layer layer, final boolean update) {
1053 for (final Display d : al_displays) {
1054 if (layer == d.layer) {
1055 d.canvas.setUpdateGraphics(update);
1060 /** Whether to update the offscreen images or not. */
1061 public void setUpdateGraphics(boolean b) {
1062 canvas.setUpdateGraphics(b);
1065 /** Find all Display instances that contain the layer and repaint them, in the Swing GUI thread. */
1066 static public void update(final Layer layer) {
1067 if (null == layer) return;
1068 SwingUtilities.invokeLater(new Runnable() { public void run() {
1069 for (final Display d : al_displays) {
1070 if (d.isShowing(layer)) {
1071 d.repaintAll();
1074 }});
1077 static public void update(final LayerSet set) {
1078 update(set, true);
1081 /** Find all Display instances showing a Layer of this LayerSet, and update the dimensions of the navigator and canvas and snapshots, and repaint, in the Swing GUI thread. */
1082 static public void update(final LayerSet set, final boolean update_canvas_dimensions) {
1083 if (null == set) return;
1084 SwingUtilities.invokeLater(new Runnable() { public void run() {
1085 for (final Display d : al_displays) {
1086 if (set.contains(d.layer)) {
1087 d.updateSnapshots();
1088 if (update_canvas_dimensions) d.canvas.setDimensions(set.getLayerWidth(), set.getLayerHeight());
1089 d.repaintAll();
1092 }});
1095 /** Release all resources held by this Display and close the frame. */
1096 protected void destroy() {
1097 dispatcher.quit();
1098 canvas.setReceivesInput(false);
1099 slt.quit();
1101 // update the coloring in the ProjectTree and LayerTree
1102 if (!project.isBeingDestroyed()) {
1103 try {
1104 project.getProjectTree().updateUILater();
1105 project.getLayerTree().updateUILater();
1106 } catch (Exception e) {
1107 Utils.log2("updateUI failed at Display.destroy()");
1111 frame.removeComponentListener(component_listener);
1112 frame.removeWindowListener(window_listener);
1113 frame.removeWindowFocusListener(window_listener);
1114 frame.removeWindowStateListener(window_listener);
1115 frame.removeKeyListener(canvas);
1116 frame.removeMouseListener(frame_mouse_listener);
1117 canvas_panel.removeKeyListener(canvas);
1118 canvas.removeKeyListener(canvas);
1119 tabs.removeChangeListener(tabs_listener);
1120 tabs.removeKeyListener(canvas);
1121 ImagePlus.removeImageListener(this);
1122 bytypelistener = null;
1123 canvas.destroy();
1124 navigator.destroy();
1125 scroller.removeAdjustmentListener(scroller_listener);
1126 frame.setVisible(false);
1127 //no need, and throws exception//frame.dispose();
1128 active = null;
1129 if (null != selection) selection.clear();
1130 //Utils.log2("destroying selection");
1132 // below, need for SetLayerThread threads to quit
1133 slt.quit();
1134 // set a new front if any
1135 if (null == front && al_displays.size() > 0) {
1136 front = (Display)al_displays.get(al_displays.size() -1);
1138 // repaint layer tree (to update the label color)
1139 try {
1140 project.getLayerTree().updateUILater(); // works only after setting the front above
1141 } catch (Exception e) {} // ignore swing sync bullshit when closing everything too fast
1142 // remove the drag and drop listener
1143 dnd.destroy();
1146 /** Find all Display instances that contain a Layer of the given project and close them without removing the Display entries from the database. */
1147 static synchronized public void close(final Project project) {
1148 /* // concurrent modifications if more than 1 Display are being removed asynchronously
1149 for (final Display d : al_displays) {
1150 if (d.getLayer().getProject().equals(project)) {
1151 it.remove();
1152 d.destroy();
1156 Display[] d = new Display[al_displays.size()];
1157 al_displays.toArray(d);
1158 for (int i=0; i<d.length; i++) {
1159 if (d[i].getProject() == project) {
1160 al_displays.remove(d[i]);
1161 d[i].destroy();
1166 /** Find all Display instances that contain the layer and close them and remove the Display from the database. */
1167 static public void close(final Layer layer) {
1168 for (Iterator it = al_displays.iterator(); it.hasNext(); ) {
1169 Display d = (Display)it.next();
1170 if (d.isShowing(layer)) {
1171 d.remove(false);
1172 it.remove();
1177 /** Find all Display instances that are showing the layer and either move to the next or previous layer, or close it if none. */
1178 static public void remove(final Layer layer) {
1179 for (Iterator<Display> it = al_displays.iterator(); it.hasNext(); ) {
1180 final Display d = it.next();
1181 if (d.isShowing(layer)) {
1182 Layer la = layer.getParent().next(layer);
1183 if (layer == la || null == la) la = layer.getParent().previous(layer);
1184 if (null == la || layer == la) {
1185 d.remove(false);
1186 it.remove();
1187 } else {
1188 d.slt.set(la);
1194 public boolean remove(boolean check) {
1195 if (check) {
1196 if (!Utils.check("Delete the Display ?")) return false;
1198 // flush the offscreen images and close the frame
1199 destroy();
1200 removeFromDatabase();
1201 return true;
1204 public Layer getLayer() {
1205 return layer;
1208 public LayerSet getLayerSet() {
1209 return layer.getParent();
1212 public boolean isShowing(final Layer layer) {
1213 return this.layer == layer;
1216 public DisplayNavigator getNavigator() {
1217 return navigator;
1220 /** Repaint both the canvas and the navigator, updating the graphics, and the title and tabs. */
1221 public void repaintAll() {
1222 if (repaint_disabled) return;
1223 navigator.repaint(true);
1224 canvas.repaint(true);
1225 Utils.updateComponent(tabs);
1226 updateTitle();
1229 /** Repaint the canvas updating graphics, the navigator without updating graphics, and the title. */
1230 public void repaintAll2() {
1231 if (repaint_disabled) return;
1232 navigator.repaint(false);
1233 canvas.repaint(true);
1234 updateTitle();
1237 static public void repaintSnapshots(final LayerSet set) {
1238 if (repaint_disabled) return;
1239 for (final Display d : al_displays) {
1240 if (d.getLayer().getParent() == set) {
1241 d.navigator.repaint(true);
1242 Utils.updateComponent(d.tabs);
1246 static public void repaintSnapshots(final Layer layer) {
1247 if (repaint_disabled) return;
1248 for (final Display d : al_displays) {
1249 if (d.getLayer() == layer) {
1250 d.navigator.repaint(true);
1251 Utils.updateComponent(d.tabs);
1256 public void pack() {
1257 dispatcher.exec(new Runnable() { public void run() {
1258 try {
1259 Thread.currentThread().sleep(100);
1260 SwingUtilities.invokeAndWait(new Runnable() { public void run() {
1261 frame.pack();
1262 }});
1263 } catch (Exception e) { IJError.print(e); }
1264 }});
1267 static public void pack(final LayerSet ls) {
1268 for (final Display d : al_displays) {
1269 if (d.layer.getParent() == ls) d.pack();
1273 private void adjustCanvas() {
1274 SwingUtilities.invokeLater(new Runnable() { public void run() {
1275 Rectangle r = split.getRightComponent().getBounds();
1276 canvas.setDrawingSize(r.width, r.height, true);
1277 // fix not-on-top-left problem
1278 canvas.setLocation(0, 0);
1279 //frame.pack(); // don't! Would go into an infinite loop
1280 canvas.repaint(true);
1281 updateInDatabase("srcRect");
1282 }});
1285 /** Grab the last selected display (or create an new one if none) and show in it the layer,centered on the Displayable object. */
1286 static public void setFront(final Layer layer, final Displayable displ) {
1287 if (null == front) {
1288 Display display = new Display(layer.getProject(), layer); // gets set to front
1289 display.showCentered(displ);
1290 } else if (layer == front.layer) {
1291 front.showCentered(displ);
1292 } else {
1293 // find one:
1294 for (final Display d : al_displays) {
1295 if (d.layer == layer) {
1296 d.frame.toFront();
1297 d.showCentered(displ);
1298 return;
1301 // else, open new one
1302 new Display(layer.getProject(), layer).showCentered(displ);
1306 /** Find the displays that show the given Layer, and add the given Displayable to the GUI and sets it active only in the front Display and only if 'activate' is true. */
1307 static public void add(final Layer layer, final Displayable displ, final boolean activate) {
1308 for (final Display d : al_displays) {
1309 if (d.layer == layer) {
1310 if (front == d) {
1311 d.add(displ, activate, true);
1312 //front.frame.toFront();
1313 } else {
1314 d.add(displ, false, true);
1320 static public void add(final Layer layer, final Displayable displ) {
1321 add(layer, displ, true);
1324 /** Add the ZDisplayable to all Displays that show a Layer belonging to the given LayerSet. */
1325 static public void add(final LayerSet set, final ZDisplayable zdispl) {
1326 for (final Display d : al_displays) {
1327 if (set.contains(d.layer)) {
1328 if (front == d) {
1329 zdispl.setLayer(d.layer); // the active one
1330 d.add(zdispl, true, true); // calling add(Displayable, boolean, boolean)
1331 //front.frame.toFront();
1332 } else {
1333 d.add(zdispl, false, true);
1339 static public void addAll(final Layer layer, final Collection<? extends Displayable> coll) {
1340 for (final Display d : al_displays) {
1341 if (d.layer == layer) {
1342 d.addAll(coll);
1347 static public void addAll(final LayerSet set, final Collection<? extends ZDisplayable> coll) {
1348 for (final Display d : al_displays) {
1349 if (set.contains(d.layer)) {
1350 for (final ZDisplayable zd : coll) {
1351 if (front == d) zd.setLayer(d.layer);
1353 d.addAll(coll);
1358 private final void addAll(final Collection<? extends Displayable> coll) {
1359 for (final Displayable d : coll) {
1360 add(d, false, false);
1362 selection.clear();
1363 Utils.updateComponent(tabs);
1364 navigator.repaint(true);
1367 // TODO this very old method could take some improvement:
1368 // - there is no need to create a new DisplayablePanel if its panel is not shown
1369 // - other issues; the method looks overly "if a dog barks and a duck quacks during a lunar eclipse then .."
1370 /** Add it to the proper panel, at the top, and set it active. */
1371 private final void add(final Displayable d, final boolean activate, final boolean repaint_snapshot) {
1372 DisplayablePanel dp = ht_panels.get(d);
1373 if (null != dp && activate) { // for ZDisplayable objects (TODO I think this is not used anymore)
1374 dp.setActive(true);
1375 //setActive(d);
1376 selection.clear();
1377 selection.add(d);
1378 return;
1380 // add to the proper list
1381 JPanel p = null;
1382 if (d instanceof Profile) {
1383 p = panel_profiles;
1384 } else if (d instanceof Patch) {
1385 p = panel_patches;
1386 } else if (d instanceof DLabel) {
1387 p = panel_labels;
1388 } else if (d instanceof ZDisplayable) { //both pipes and balls and AreaList
1389 p = panel_zdispl;
1390 } else {
1391 // LayerSet objects
1392 return;
1394 dp = new DisplayablePanel(this, d); // TODO: instead of destroying/recreating, we could just recycle them by reassigning a different Displayable. See how it goes! It'd need a pool of objects
1395 addToPanel(p, 0, dp, activate);
1396 ht_panels.put(d, dp);
1397 if (activate) {
1398 dp.setActive(true);
1399 //setActive(d);
1400 selection.clear();
1401 selection.add(d);
1403 if (repaint_snapshot) navigator.repaint(true);
1406 private void addToPanel(JPanel panel, int index, DisplayablePanel dp, boolean repaint) {
1407 // remove the label
1408 if (1 == panel.getComponentCount() && panel.getComponent(0) instanceof JLabel) {
1409 panel.removeAll();
1411 panel.add(dp, index);
1412 if (repaint) {
1413 Utils.updateComponent(tabs);
1417 /** Find the displays that show the given Layer, and remove the given Displayable from the GUI. */
1418 static public void remove(final Layer layer, final Displayable displ) {
1419 for (final Display d : al_displays) {
1420 if (layer == d.layer) d.remove(displ);
1424 private void remove(final Displayable displ) {
1425 DisplayablePanel ob = ht_panels.remove(displ);
1426 if (null != ob) {
1427 final JScrollPane jsp = ht_tabs.get(displ.getClass());
1428 if (null != jsp) {
1429 JPanel p = (JPanel)jsp.getViewport().getView();
1430 p.remove((Component)ob);
1431 Utils.revalidateComponent(p);
1434 if (null == active || !selection.contains(displ)) {
1435 canvas.setUpdateGraphics(true);
1437 canvas.invalidateVolatile(); // removing active, no need to update offscreen but yes the volatile
1438 repaint(displ, null, 5, true, false);
1439 // from Selection.deleteAll this method is called ... but it's ok: same thread, no locking problems.
1440 selection.remove(displ);
1443 static public void remove(final ZDisplayable zdispl) {
1444 for (final Display d : al_displays) {
1445 if (zdispl.getLayerSet() == d.layer.getParent()) {
1446 d.remove((Displayable)zdispl);
1451 static public void repaint(final Layer layer, final Displayable displ, final int extra) {
1452 repaint(layer, displ, displ.getBoundingBox(), extra);
1455 static public void repaint(final Layer layer, final Displayable displ, final Rectangle r, final int extra) {
1456 repaint(layer, displ, r, extra, true);
1459 /** Find the displays that show the given Layer, and repaint the given Displayable. */
1460 static public void repaint(final Layer layer, final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator) {
1461 if (repaint_disabled) return;
1462 for (final Display d : al_displays) {
1463 if (layer == d.layer) {
1464 d.repaint(displ, r, extra, repaint_navigator, false);
1469 static public void repaint(final Displayable d) {
1470 if (d instanceof ZDisplayable) repaint(d.getLayerSet(), d, d.getBoundingBox(null), 5, true);
1471 repaint(d.getLayer(), d, d.getBoundingBox(null), 5, true);
1474 /** Repaint as much as the bounding box around the given Displayable, or the r if not null. */
1475 private void repaint(final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator, final boolean update_graphics) {
1476 if (repaint_disabled || null == displ) return;
1477 if (update_graphics || displ.getClass() == Patch.class || displ != active) {
1478 canvas.setUpdateGraphics(true);
1480 if (null != r) canvas.repaint(r, extra);
1481 else canvas.repaint(displ, extra);
1482 if (repaint_navigator) {
1483 DisplayablePanel dp = ht_panels.get(displ);
1484 if (null != dp) dp.repaint(); // is null when creating it, or after deleting it
1485 navigator.repaint(true); // everything
1489 /** Repaint the snapshot for the given Displayable both at the DisplayNavigator and on its panel,and only if it has not been painted before. This method is intended for the loader to know when to paint a snap, to avoid overhead. */
1490 static public void repaintSnapshot(final Displayable displ) {
1491 for (final Display d : al_displays) {
1492 if (d.layer.contains(displ)) {
1493 if (!d.navigator.isPainted(displ)) {
1494 DisplayablePanel dp = d.ht_panels.get(displ);
1495 if (null != dp) dp.repaint(); // is null when creating it, or after deleting it
1496 d.navigator.repaint(displ);
1502 /** Repaint the given Rectangle in all Displays showing the layer, updating the offscreen image if any. */
1503 static public void repaint(final Layer layer, final Rectangle r, final int extra) {
1504 repaint(layer, extra, r, true, true);
1507 static public void repaint(final Layer layer, final int extra, final Rectangle r, final boolean update_navigator) {
1508 repaint(layer, extra, r, update_navigator, true);
1511 static public void repaint(final Layer layer, final int extra, final Rectangle r, final boolean update_navigator, final boolean update_graphics) {
1512 if (repaint_disabled) return;
1513 for (final Display d : al_displays) {
1514 if (layer == d.layer) {
1515 d.canvas.setUpdateGraphics(update_graphics);
1516 d.canvas.repaint(r, extra);
1517 if (update_navigator) {
1518 d.navigator.repaint(true);
1519 Utils.updateComponent(d.tabs.getSelectedComponent());
1526 /** Repaint the given Rectangle in all Displays showing the layer, optionally updating the offscreen image (if any). */
1527 static public void repaint(final Layer layer, final Rectangle r, final int extra, final boolean update_graphics) {
1528 if (repaint_disabled) return;
1529 for (final Display d : al_displays) {
1530 if (layer == d.layer) {
1531 d.canvas.setUpdateGraphics(update_graphics);
1532 d.canvas.repaint(r, extra);
1533 d.navigator.repaint(update_graphics);
1534 if (update_graphics) Utils.updateComponent(d.tabs.getSelectedComponent());
1539 /** Repaint the DisplayablePanel (and DisplayNavigator) only for the given Displayable, in all Displays showing the given Layer. */
1540 static public void repaint(final Layer layer, final Displayable displ) {
1541 if (repaint_disabled) return;
1542 for (final Display d : al_displays) {
1543 if (layer == d.layer) {
1544 DisplayablePanel dp = d.ht_panels.get(displ);
1545 if (null != dp) dp.repaint();
1546 d.navigator.repaint(true);
1551 static public void repaint(LayerSet set, Displayable displ, int extra) {
1552 repaint(set, displ, null, extra);
1555 static public void repaint(LayerSet set, Displayable displ, Rectangle r, int extra) {
1556 repaint(set, displ, r, extra, true);
1559 /** Repaint the Displayable in every Display that shows a Layer belonging to the given LayerSet. */
1560 static public void repaint(final LayerSet set, final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator) {
1561 if (repaint_disabled) return;
1562 for (final Display d : al_displays) {
1563 if (set.contains(d.layer)) {
1564 if (repaint_navigator) {
1565 if (null != displ) {
1566 DisplayablePanel dp = d.ht_panels.get(displ);
1567 if (null != dp) dp.repaint();
1569 d.navigator.repaint(true);
1571 if (null == displ || displ != d.active) d.setUpdateGraphics(true); // safeguard
1572 // paint the given box or the actual Displayable's box
1573 if (null != r) d.canvas.repaint(r, extra);
1574 else d.canvas.repaint(displ, extra);
1579 /** Repaint the entire LayerSet, in all Displays showing a Layer of it.*/
1580 static public void repaint(final LayerSet set) {
1581 if (repaint_disabled) return;
1582 for (final Display d : al_displays) {
1583 if (set.contains(d.layer)) {
1584 d.navigator.repaint(true);
1585 d.canvas.repaint(true);
1589 /** Repaint the given box in the LayerSet, in all Displays showing a Layer of it.*/
1590 static public void repaint(final LayerSet set, final Rectangle box) {
1591 if (repaint_disabled) return;
1592 for (final Display d : al_displays) {
1593 if (set.contains(d.layer)) {
1594 d.navigator.repaint(box);
1595 d.canvas.repaint(box, 0, true);
1599 /** Repaint the entire Layer, in all Displays showing it, including the tabs.*/
1600 static public void repaint(final Layer layer) { // TODO this method overlaps with update(layer)
1601 if (repaint_disabled) return;
1602 for (final Display d : al_displays) {
1603 if (layer == d.layer) {
1604 d.navigator.repaint(true);
1605 d.canvas.repaint(true);
1610 /** Call repaint on all open Displays. */
1611 static public void repaint() {
1612 if (repaint_disabled) {
1613 Utils.logAll("Can't repaint -- repainting is disabled!");
1614 return;
1616 for (final Display d : al_displays) {
1617 d.navigator.repaint(true);
1618 d.canvas.repaint(true);
1622 static private boolean repaint_disabled = false;
1624 /** Set a flag to enable/disable repainting of all Display instances. */
1625 static protected void setRepaint(boolean b) {
1626 repaint_disabled = !b;
1629 public Rectangle getBounds() {
1630 return frame.getBounds();
1633 public Point getLocation() {
1634 return frame.getLocation();
1637 public JFrame getFrame() {
1638 return frame;
1641 public void setLocation(Point p) {
1642 this.frame.setLocation(p);
1645 public Displayable getActive() {
1646 return active; //TODO this should return selection.active !!
1649 public void select(Displayable d) {
1650 select(d, false);
1653 /** Select/deselect accordingly to the current state and the shift key. */
1654 public void select(final Displayable d, final boolean shift_down) {
1655 if (null != active && active != d && active.getClass() != Patch.class) {
1656 // active is being deselected, so link underlying patches
1657 active.linkPatches();
1659 if (null == d) {
1660 //Utils.log2("Display.select: clearing selection");
1661 canvas.setUpdateGraphics(true);
1662 selection.clear();
1663 return;
1665 if (!shift_down) {
1666 //Utils.log2("Display.select: single selection");
1667 if (d != active) {
1668 selection.clear();
1669 selection.add(d);
1671 } else if (selection.contains(d)) {
1672 if (active == d) {
1673 selection.remove(d);
1674 //Utils.log2("Display.select: removing from a selection");
1675 } else {
1676 //Utils.log2("Display.select: activing within a selection");
1677 selection.setActive(d);
1679 } else {
1680 //Utils.log2("Display.select: adding to an existing selection");
1681 selection.add(d);
1683 // update the image shown to ImageJ
1684 // NO longer necessary, always he same FakeImagePlus // setTempCurrentImage();
1687 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p, final Class c) {
1688 choose(screen_x_p, screen_y_p, x_p, y_p, false, c);
1690 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p) {
1691 choose(screen_x_p, screen_y_p, x_p, y_p, false, null);
1694 /** Find a Displayable to add to the selection under the given point (which is in offscreen coords); will use a popup menu to give the user a range of Displayable objects to select from. */
1695 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p, boolean shift_down, Class c) {
1696 //Utils.log("Display.choose: x,y " + x_p + "," + y_p);
1697 final ArrayList<Displayable> al = new ArrayList<Displayable>(layer.find(x_p, y_p, true));
1698 al.addAll(layer.getParent().findZDisplayables(layer, x_p, y_p, true)); // only visible ones
1699 if (al.isEmpty()) {
1700 Displayable act = this.active;
1701 selection.clear();
1702 canvas.setUpdateGraphics(true);
1703 //Utils.log("choose: set active to null");
1704 // fixing lack of repainting for unknown reasons, of the active one TODO this is a temporary solution
1705 if (null != act) Display.repaint(layer, act, 5);
1706 } else if (1 == al.size()) {
1707 Displayable d = (Displayable)al.get(0);
1708 if (null != c && d.getClass() != c) {
1709 selection.clear();
1710 return;
1712 select(d, shift_down);
1713 //Utils.log("choose 1: set active to " + active);
1714 } else {
1715 if (al.contains(active) && !shift_down) {
1716 // do nothing
1717 } else {
1718 if (null != c) {
1719 // check if at least one of them is of class c
1720 // if only one is of class c, set as selected
1721 // else show menu
1722 for (Iterator it = al.iterator(); it.hasNext(); ) {
1723 Object ob = it.next();
1724 if (ob.getClass() != c) it.remove();
1726 if (0 == al.size()) {
1727 // deselect
1728 selection.clear();
1729 return;
1731 if (1 == al.size()) {
1732 select((Displayable)al.get(0), shift_down);
1733 return;
1735 // else, choose among the many
1737 choose(screen_x_p, screen_y_p, al, shift_down, x_p, y_p);
1739 //Utils.log("choose many: set active to " + active);
1743 private void choose(final int screen_x_p, final int screen_y_p, final Collection al, final boolean shift_down, final int x_p, final int y_p) {
1744 // show a popup on the canvas to choose
1745 new Thread() {
1746 public void run() {
1747 final Object lock = new Object();
1748 final DisplayableChooser d_chooser = new DisplayableChooser(al, lock);
1749 final JPopupMenu pop = new JPopupMenu("Select:");
1750 final Iterator itu = al.iterator();
1751 while (itu.hasNext()) {
1752 Displayable d = (Displayable)itu.next();
1753 JMenuItem menu_item = new JMenuItem(d.toString());
1754 menu_item.addActionListener(d_chooser);
1755 pop.add(menu_item);
1758 new Thread() {
1759 public void run() {
1760 pop.show(canvas, screen_x_p, screen_y_p);
1762 }.start();
1764 //now wait until selecting something
1765 synchronized(lock) {
1766 do {
1767 try {
1768 lock.wait();
1769 } catch (InterruptedException ie) {}
1770 } while (d_chooser.isWaiting() && pop.isShowing());
1773 //grab the chosen Displayable object
1774 Displayable d = d_chooser.getChosen();
1775 //Utils.log("Chosen: " + d.toString());
1776 if (null == d) { Utils.log2("Display.choose: returning a null!"); }
1777 select(d, shift_down);
1778 pop.setVisible(false);
1780 // fix selection bug: never receives mouseReleased event when the popup shows
1781 selection.mouseReleased(null, x_p, y_p, x_p, y_p, x_p, y_p);
1783 }.start();
1786 /** Used by the Selection exclusively. This method will change a lot in the near future, and may disappear in favor of getSelection().getActive(). All this method does is update GUI components related to the currently active and the newly active Displayable; called through SwingUtilities.invokeLater. */
1787 protected void setActive(final Displayable displ) {
1788 final Displayable prev_active = this.active;
1789 this.active = displ;
1790 SwingUtilities.invokeLater(new Runnable() { public void run() {
1792 // renew current image if necessary
1793 if (null != displ && displ == prev_active) {
1794 // make sure the proper tab is selected.
1795 selectTab(displ);
1796 return; // the same
1798 // deactivate previously active
1799 if (null != prev_active) {
1800 final DisplayablePanel ob = ht_panels.get(prev_active);
1801 if (null != ob) ob.setActive(false);
1802 // erase "decorations" of the previously active
1803 canvas.repaint(selection.getBox(), 4);
1805 // activate the new active
1806 if (null != displ) {
1807 final DisplayablePanel ob = ht_panels.get(displ);
1808 if (null != ob) ob.setActive(true);
1809 updateInDatabase("active_displayable_id");
1810 if (displ.getClass() != Patch.class) project.select(displ); // select the node in the corresponding tree, if any.
1811 // select the proper tab, and scroll to visible
1812 selectTab(displ);
1813 boolean update_graphics = null == prev_active || paintsBelow(prev_active, displ); // or if it's an image, but that's by default in the repaint method
1814 repaint(displ, null, 5, false, update_graphics); // to show the border, and to repaint out of the background image
1815 transp_slider.setValue((int)(displ.getAlpha() * 100));
1816 } else {
1817 //ensure decorations are removed from the panels, for Displayables in a selection besides the active one
1818 Utils.updateComponent(tabs.getSelectedComponent());
1820 }});
1823 /** If the other paints under the base. */
1824 public boolean paintsBelow(Displayable base, Displayable other) {
1825 boolean zd_base = base instanceof ZDisplayable;
1826 boolean zd_other = other instanceof ZDisplayable;
1827 if (zd_other) {
1828 if (base instanceof DLabel) return true; // zd paints under label
1829 if (!zd_base) return false; // any zd paints over a mere displ if not a label
1830 else {
1831 // both zd, compare indices
1832 ArrayList<ZDisplayable> al = other.getLayerSet().getZDisplayables();
1833 return al.indexOf(base) > al.indexOf(other);
1835 } else {
1836 if (!zd_base) {
1837 // both displ, compare indices
1838 ArrayList<Displayable> al = other.getLayer().getDisplayables();
1839 return al.indexOf(base) > al.indexOf(other);
1840 } else {
1841 // base is zd, other is d
1842 if (other instanceof DLabel) return false;
1843 return true;
1848 /** Select the proper tab, and also scroll it to show the given Displayable -unless it's a LayerSet, and unless the proper tab is already showing. */
1849 private void selectTab(final Displayable displ) {
1850 Method method = null;
1851 try {
1852 if (!(displ instanceof LayerSet)) {
1853 method = Display.class.getDeclaredMethod("selectTab", new Class[]{displ.getClass()});
1855 } catch (Exception e) {
1856 IJError.print(e);
1858 if (null != method) {
1859 final Method me = method;
1860 dispatcher.exec(new Runnable() { public void run() {
1861 try {
1862 me.setAccessible(true);
1863 me.invoke(Display.this, new Object[]{displ});
1864 } catch (Exception e) { IJError.print(e); }
1865 }});
1869 private void selectTab(Patch patch) {
1870 tabs.setSelectedComponent(scroll_patches);
1871 scrollToShow(scroll_patches, ht_panels.get(patch));
1874 private void selectTab(Profile profile) {
1875 tabs.setSelectedComponent(scroll_profiles);
1876 scrollToShow(scroll_profiles, ht_panels.get(profile));
1879 private void selectTab(DLabel label) {
1880 tabs.setSelectedComponent(scroll_labels);
1881 scrollToShow(scroll_labels, ht_panels.get(label));
1884 private void selectTab(ZDisplayable zd) {
1885 tabs.setSelectedComponent(scroll_zdispl);
1886 scrollToShow(scroll_zdispl, ht_panels.get(zd));
1889 private void selectTab(Pipe d) { selectTab((ZDisplayable)d); }
1890 private void selectTab(Polyline d) { selectTab((ZDisplayable)d); }
1891 private void selectTab(AreaList d) { selectTab((ZDisplayable)d); }
1892 private void selectTab(Ball d) { selectTab((ZDisplayable)d); }
1893 private void selectTab(Dissector d) { selectTab((ZDisplayable)d); }
1895 /** A method to update the given tab, creating a new DisplayablePanel for each Displayable present in the given ArrayList, and storing it in the ht_panels (which is cleared first). */
1896 private void updateTab(final Container tab, final String label, final ArrayList al) {
1897 final boolean[] recreated = new boolean[]{false, true, true};
1898 dispatcher.execSwing(new Runnable() { public void run() {
1899 try {
1900 if (0 == al.size()) {
1901 tab.removeAll();
1902 tab.add(new JLabel("No " + label + "."));
1903 } else {
1904 Component[] comp = tab.getComponents();
1905 int next = 0;
1906 if (1 == comp.length && comp[0].getClass() == JLabel.class) {
1907 next = 1;
1908 tab.remove(0);
1910 for (Iterator it = al.iterator(); it.hasNext(); ) {
1911 Displayable d = (Displayable)it.next();
1912 DisplayablePanel dp = null;
1913 if (next < comp.length) {
1914 dp = (DisplayablePanel)comp[next++]; // recycling panels
1915 dp.set(d);
1916 } else {
1917 dp = new DisplayablePanel(Display.this, d);
1918 tab.add(dp);
1920 ht_panels.put(d, dp);
1922 if (next < comp.length) {
1923 // remove from the end, to avoid potential repaints of other panels
1924 for (int i=comp.length-1; i>=next; i--) {
1925 tab.remove(i);
1928 recreated[0] = true;
1930 if (recreated[0]) {
1931 tab.invalidate();
1932 tab.validate();
1933 tab.repaint();
1935 if (null != Display.this.active) scrollToShow(Display.this.active);
1936 } catch (Throwable e) { IJError.print(e); }
1937 }});
1940 static public void setActive(final Object event, final Displayable displ) {
1941 if (!(event instanceof InputEvent)) return;
1942 // find which Display
1943 for (final Display d : al_displays) {
1944 if (d.isOrigin((InputEvent)event)) {
1945 d.setActive(displ);
1946 break;
1951 /** Find out whether this Display is Transforming its active Displayable. */
1952 public boolean isTransforming() {
1953 return canvas.isTransforming();
1956 /** Find whether any Display is transforming the given Displayable. */
1957 static public boolean isTransforming(final Displayable displ) {
1958 for (final Display d : al_displays) {
1959 if (null != d.active && d.active == displ && d.canvas.isTransforming()) return true;
1961 return false;
1964 static public boolean isAligning(final LayerSet set) {
1965 for (final Display d : al_displays) {
1966 if (d.layer.getParent() == set && set.isAligning()) {
1967 return true;
1970 return false;
1973 /** Set the front Display to transform the Displayable only if no other canvas is transforming it. */
1974 static public void setTransforming(final Displayable displ) {
1975 if (null == front) return;
1976 if (front.active != displ) return;
1977 for (final Display d : al_displays) {
1978 if (d.active == displ) {
1979 if (d.canvas.isTransforming()) {
1980 Utils.showMessage("Already transforming " + displ.getTitle());
1981 return;
1985 front.canvas.setTransforming(true);
1988 /** Check whether the source of the event is located in this instance.*/
1989 private boolean isOrigin(InputEvent event) {
1990 Object source = event.getSource();
1991 // find it ... check the canvas for now TODO
1992 if (canvas == source) {
1993 return true;
1995 return false;
1998 /** Get the layer of the front Display, or null if none.*/
1999 static public Layer getFrontLayer() {
2000 if (null == front) return null;
2001 return front.layer;
2004 /** Get the layer of an open Display of the given Project, or null if none.*/
2005 static public Layer getFrontLayer(final Project project) {
2006 if (null == front) return null;
2007 if (front.project == project) return front.layer;
2008 // else, find an open Display for the given Project, if any
2009 for (final Display d : al_displays) {
2010 if (d.project == project) {
2011 d.frame.toFront();
2012 return d.layer;
2015 return null; // none found
2018 static public Display getFront(final Project project) {
2019 if (null == front) return null;
2020 if (front.project == project) return front;
2021 for (final Display d : al_displays) {
2022 if (d.project == project) {
2023 d.frame.toFront();
2024 return d;
2027 return null;
2030 public boolean isReadOnly() {
2031 // TEMPORARY: in the future one will be able show displays as read-only to other people, remotely
2032 return false;
2035 static public void showPopup(Component c, int x, int y) {
2036 if (null != front) front.getPopupMenu().show(c, x, y);
2039 /** Return a context-sensitive popup menu. */
2040 public JPopupMenu getPopupMenu() { // called from canvas
2041 // get the job canceling dialog
2042 if (!canvas.isInputEnabled()) {
2043 return project.getLoader().getJobsPopup(this);
2046 // create new
2047 this.popup = new JPopupMenu();
2048 JMenuItem item = null;
2049 JMenu menu = null;
2051 if (ProjectToolbar.ALIGN == Toolbar.getToolId()) {
2052 boolean aligning = layer.getParent().isAligning();
2053 item = new JMenuItem("Cancel alignment"); item.addActionListener(this); popup.add(item);
2054 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2055 if (!aligning) item.setEnabled(false);
2056 item = new JMenuItem("Align with landmarks"); item.addActionListener(this); popup.add(item);
2057 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true));
2058 if (!aligning) item.setEnabled(false);
2059 item = new JMenuItem("Align and register"); item.addActionListener(this); popup.add(item);
2060 if (!aligning) item.setEnabled(false);
2061 item = new JMenuItem("Align using profiles"); item.addActionListener(this); popup.add(item);
2062 if (!aligning || selection.isEmpty() || !selection.contains(Profile.class)) item.setEnabled(false);
2063 item = new JMenuItem("Align stack slices"); item.addActionListener(this); popup.add(item);
2064 if (selection.isEmpty() || ! (getActive().getClass() == Patch.class && ((Patch)getActive()).isStack())) item.setEnabled(false);
2065 item = new JMenuItem("Align layers"); item.addActionListener(this); popup.add(item);
2066 if (1 == layer.getParent().size()) item.setEnabled(false);
2067 item = new JMenuItem("Align multi-layer mosaic"); item.addActionListener(this); popup.add(item);
2068 if (1 == layer.getParent().size()) item.setEnabled(false);
2069 return popup;
2073 if (null != active) {
2074 if (!canvas.isTransforming()) {
2075 if (active instanceof Profile) {
2076 item = new JMenuItem("Duplicate, link and send to next layer"); item.addActionListener(this); popup.add(item);
2077 Layer nl = layer.getParent().next(layer);
2078 if (nl == layer) item.setEnabled(false);
2079 item = new JMenuItem("Duplicate, link and send to previous layer"); item.addActionListener(this); popup.add(item);
2080 nl = layer.getParent().previous(layer);
2081 if (nl == layer) item.setEnabled(false);
2083 menu = new JMenu("Duplicate, link and send to");
2084 ArrayList al = layer.getParent().getLayers();
2085 final Iterator it = al.iterator();
2086 int i = 1;
2087 while (it.hasNext()) {
2088 Layer la = (Layer)it.next();
2089 item = new JMenuItem(i + ": z = " + la.getZ()); item.addActionListener(this); menu.add(item); // TODO should label which layers contain Profile instances linked to the one being duplicated
2090 if (la == this.layer) item.setEnabled(false);
2091 i++;
2093 popup.add(menu);
2094 item = new JMenuItem("Duplicate, link and send to..."); item.addActionListener(this); popup.add(item);
2096 popup.addSeparator();
2098 item = new JMenuItem("Unlink from images"); item.addActionListener(this); popup.add(item);
2099 if (!active.isLinked()) item.setEnabled(false); // isLinked() checks if it's linked to a Patch in its own layer
2100 item = new JMenuItem("Show in 3D"); item.addActionListener(this); popup.add(item);
2101 popup.addSeparator();
2102 } else if (active instanceof Patch) {
2103 item = new JMenuItem("Unlink from images"); item.addActionListener(this); popup.add(item);
2104 if (!active.isLinked(Patch.class)) item.setEnabled(false);
2105 if (((Patch)active).isStack()) {
2106 item = new JMenuItem("Unlink slices"); item.addActionListener(this); popup.add(item);
2108 int n_sel_patches = selection.getSelected(Patch.class).size();
2109 if (1 == n_sel_patches) {
2110 item = new JMenuItem("Snap"); item.addActionListener(this); popup.add(item);
2111 } else if (n_sel_patches > 1) {
2112 item = new JMenuItem("Montage"); item.addActionListener(this); popup.add(item);
2113 item = new JMenuItem("Lens correction"); item.addActionListener(this); popup.add(item);
2114 item = new JMenuItem("Blend"); item.addActionListener(this); popup.add(item);
2116 item = new JMenuItem("Remove alpha mask"); item.addActionListener(this); popup.add(item);
2117 if ( ! ((Patch)active).hasAlphaMask()) item.setEnabled(false);
2118 item = new JMenuItem("Link images..."); item.addActionListener(this); popup.add(item);
2119 item = new JMenuItem("View volume"); item.addActionListener(this); popup.add(item);
2120 HashSet hs = active.getLinked(Patch.class);
2121 if (null == hs || 0 == hs.size()) item.setEnabled(false);
2122 item = new JMenuItem("View orthoslices"); item.addActionListener(this); popup.add(item);
2123 if (null == hs || 0 == hs.size()) item.setEnabled(false); // if no Patch instances among the directly linked, then it's not a stack
2124 popup.addSeparator();
2125 } else {
2126 item = new JMenuItem("Unlink"); item.addActionListener(this); popup.add(item);
2127 item = new JMenuItem("Show in 3D"); item.addActionListener(this); popup.add(item);
2128 popup.addSeparator();
2130 if (active instanceof AreaList) {
2131 item = new JMenuItem("Merge"); item.addActionListener(this); popup.add(item);
2132 ArrayList al = selection.getSelected();
2133 int n = 0;
2134 for (Iterator it = al.iterator(); it.hasNext(); ) {
2135 if (it.next().getClass() == AreaList.class) n++;
2137 if (n < 2) item.setEnabled(false);
2138 } else if (active instanceof Pipe) {
2139 item = new JMenuItem("Identify..."); item.addActionListener(this); popup.add(item);
2140 item = new JMenuItem("Identify with axes..."); item.addActionListener(this); popup.add(item);
2143 if (canvas.isTransforming()) {
2144 item = new JMenuItem("Apply transform"); item.addActionListener(this); popup.add(item);
2145 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true)); // dummy, for I don't add a MenuKeyListener, but "works" through the normal key listener. It's here to provide a visual cue
2146 item = new JMenuItem("Apply transform propagating to last layer"); item.addActionListener(this); popup.add(item);
2147 if (layer.getParent().indexOf(layer) == layer.getParent().size() -1) item.setEnabled(false);
2148 item = new JMenuItem("Apply transform propagating to first layer"); item.addActionListener(this); popup.add(item);
2149 if (0 == layer.getParent().indexOf(layer)) item.setEnabled(false);
2150 } else {
2151 item = new JMenuItem("Transform"); item.addActionListener(this); popup.add(item);
2152 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0, true));
2154 item = new JMenuItem("Cancel transform"); item.addActionListener(this); popup.add(item);
2155 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2156 if (!canvas.isTransforming()) item.setEnabled(false);
2157 if (canvas.isTransforming()) {
2158 item = new JMenuItem("Specify transform..."); item.addActionListener(this); popup.add(item);
2161 if (!canvas.isTransforming()) {
2162 item = new JMenuItem("Duplicate"); item.addActionListener(this); popup.add(item);
2163 item = new JMenuItem("Color..."); item.addActionListener(this); popup.add(item);
2164 if (active instanceof LayerSet) item.setEnabled(false);
2165 if (active.isLocked()) {
2166 item = new JMenuItem("Unlock"); item.addActionListener(this); popup.add(item);
2167 } else {
2168 item = new JMenuItem("Lock"); item.addActionListener(this); popup.add(item);
2170 menu = new JMenu("Move");
2171 popup.addSeparator();
2172 LayerSet ls = layer.getParent();
2173 item = new JMenuItem("Move to top"); item.addActionListener(this); menu.add(item);
2174 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0, true)); // this is just to draw the key name by the menu; it does not incur on any event being generated (that I know if), and certainly not any event being listened to by TrakEM2.
2175 if (ls.isTop(active)) item.setEnabled(false);
2176 item = new JMenuItem("Move up"); item.addActionListener(this); menu.add(item);
2177 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0, true));
2178 if (ls.isTop(active)) item.setEnabled(false);
2179 item = new JMenuItem("Move down"); item.addActionListener(this); menu.add(item);
2180 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0, true));
2181 if (ls.isBottom(active)) item.setEnabled(false);
2182 item = new JMenuItem("Move to bottom"); item.addActionListener(this); menu.add(item);
2183 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_END, 0, true));
2184 if (ls.isBottom(active)) item.setEnabled(false);
2186 popup.add(menu);
2187 popup.addSeparator();
2188 item = new JMenuItem("Delete..."); item.addActionListener(this); popup.add(item);
2189 try {
2190 if (active instanceof Patch) {
2191 if (!active.isOnlyLinkedTo(Patch.class)) {
2192 item.setEnabled(false);
2194 } else if (!(active instanceof DLabel)) { // can't delete elements from the trees (Profile, Pipe, LayerSet)
2195 item.setEnabled(false);
2197 } catch (Exception e) { IJError.print(e); item.setEnabled(false); }
2199 if (active instanceof Patch) {
2200 item = new JMenuItem("Revert"); item.addActionListener(this); popup.add(item);
2201 popup.addSeparator();
2203 item = new JMenuItem("Properties..."); item.addActionListener(this); popup.add(item);
2204 item = new JMenuItem("Show centered"); item.addActionListener(this); popup.add(item);
2206 popup.addSeparator();
2208 if (! (active instanceof ZDisplayable)) {
2209 ArrayList al_layers = layer.getParent().getLayers();
2210 int i_layer = al_layers.indexOf(layer);
2211 int n_layers = al_layers.size();
2212 item = new JMenuItem("Send to previous layer"); item.addActionListener(this); popup.add(item);
2213 if (1 == n_layers || 0 == i_layer || active.isLinked()) item.setEnabled(false);
2214 // check if the active is a profile and contains a link to another profile in the layer it is going to be sent to, or it is linked
2215 else if (active instanceof Profile && !active.canSendTo(layer.getParent().previous(layer))) item.setEnabled(false);
2216 item = new JMenuItem("Send to next layer"); item.addActionListener(this); popup.add(item);
2217 if (1 == n_layers || n_layers -1 == i_layer || active.isLinked()) item.setEnabled(false);
2218 else if (active instanceof Profile && !active.canSendTo(layer.getParent().next(layer))) item.setEnabled(false);
2221 menu = new JMenu("Send linked group to...");
2222 if (active.hasLinkedGroupWithinLayer(this.layer)) {
2223 int i = 1;
2224 for (final Layer la : ls.getLayers()) {
2225 String layer_title = i + ": " + la.getTitle();
2226 if (-1 == layer_title.indexOf(' ')) layer_title += " ";
2227 item = new JMenuItem(layer_title); item.addActionListener(this); menu.add(item);
2228 if (la == this.layer) item.setEnabled(false);
2229 i++;
2231 popup.add(menu);
2232 } else {
2233 menu.setEnabled(false);
2234 //Utils.log("Active's linked group not within layer.");
2236 popup.add(menu);
2237 popup.addSeparator();
2242 if (!canvas.isTransforming()) {
2244 item = new JMenuItem("Undo");item.addActionListener(this); popup.add(item);
2245 if (!layer.getParent().canUndo() || canvas.isTransforming()) item.setEnabled(false);
2246 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Utils.getControlModifier(), true));
2247 item = new JMenuItem("Redo");item.addActionListener(this); popup.add(item);
2248 if (!layer.getParent().canRedo() || canvas.isTransforming()) item.setEnabled(false);
2249 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Event.SHIFT_MASK | Event.CTRL_MASK, true));
2250 popup.addSeparator();
2252 // Would get so much simpler with a clojure macro ...
2254 try {
2255 menu = new JMenu("Hide/Unhide");
2256 item = new JMenuItem("Hide deselected"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.SHIFT_MASK, true));
2257 boolean none = 0 == selection.getNSelected();
2258 if (none) item.setEnabled(false);
2259 item = new JMenuItem("Hide deselected except images"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.SHIFT_MASK | Event.ALT_MASK, true));
2260 if (none) item.setEnabled(false);
2261 item = new JMenuItem("Hide selected"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, 0, true));
2262 if (none) item.setEnabled(false);
2263 none = ! layer.getParent().containsDisplayable(DLabel.class);
2264 item = new JMenuItem("Hide all labels"); item.addActionListener(this); menu.add(item);
2265 if (none) item.setEnabled(false);
2266 item = new JMenuItem("Unhide all labels"); item.addActionListener(this); menu.add(item);
2267 if (none) item.setEnabled(false);
2268 none = ! layer.getParent().contains(AreaList.class);
2269 item = new JMenuItem("Hide all arealists"); item.addActionListener(this); menu.add(item);
2270 if (none) item.setEnabled(false);
2271 item = new JMenuItem("Unhide all arealists"); item.addActionListener(this); menu.add(item);
2272 if (none) item.setEnabled(false);
2273 none = ! layer.contains(Profile.class);
2274 item = new JMenuItem("Hide all profiles"); item.addActionListener(this); menu.add(item);
2275 if (none) item.setEnabled(false);
2276 item = new JMenuItem("Unhide all profiles"); item.addActionListener(this); menu.add(item);
2277 if (none) item.setEnabled(false);
2278 none = ! layer.getParent().contains(Pipe.class);
2279 item = new JMenuItem("Hide all pipes"); item.addActionListener(this); menu.add(item);
2280 if (none) item.setEnabled(false);
2281 item = new JMenuItem("Unhide all pipes"); item.addActionListener(this); menu.add(item);
2282 if (none) item.setEnabled(false);
2283 none = ! layer.getParent().contains(Polyline.class);
2284 item = new JMenuItem("Hide all polylines"); item.addActionListener(this); menu.add(item);
2285 if (none) item.setEnabled(false);
2286 item = new JMenuItem("Unhide all polylines"); item.addActionListener(this); menu.add(item);
2287 if (none) item.setEnabled(false);
2288 none = ! layer.getParent().contains(Ball.class);
2289 item = new JMenuItem("Hide all balls"); item.addActionListener(this); menu.add(item);
2290 if (none) item.setEnabled(false);
2291 item = new JMenuItem("Unhide all balls"); item.addActionListener(this); menu.add(item);
2292 if (none) item.setEnabled(false);
2293 none = ! layer.getParent().containsDisplayable(Patch.class);
2294 item = new JMenuItem("Hide all images"); item.addActionListener(this); menu.add(item);
2295 if (none) item.setEnabled(false);
2296 item = new JMenuItem("Unhide all images"); item.addActionListener(this); menu.add(item);
2297 if (none) item.setEnabled(false);
2298 item = new JMenuItem("Hide all but images"); item.addActionListener(this); menu.add(item);
2299 item = new JMenuItem("Unhide all"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.ALT_MASK, true));
2301 popup.add(menu);
2302 } catch (Exception e) { IJError.print(e); }
2304 JMenu adjust_menu = new JMenu("Adjust");
2305 item = new JMenuItem("Enhance contrast layer-wise..."); item.addActionListener(this); adjust_menu.add(item);
2306 item = new JMenuItem("Enhance contrast (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
2307 if (selection.isEmpty()) item.setEnabled(false);
2308 item = new JMenuItem("Set Min and Max layer-wise..."); item.addActionListener(this); adjust_menu.add(item);
2309 item = new JMenuItem("Set Min and Max (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
2310 if (selection.isEmpty()) item.setEnabled(false);
2311 popup.add(adjust_menu);
2313 menu = new JMenu("Import");
2314 item = new JMenuItem("Import image"); item.addActionListener(this); menu.add(item);
2315 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, Event.ALT_MASK & Event.SHIFT_MASK, true));
2316 item = new JMenuItem("Import stack..."); item.addActionListener(this); menu.add(item);
2317 item = new JMenuItem("Import grid..."); item.addActionListener(this); menu.add(item);
2318 item = new JMenuItem("Import sequence as grid..."); item.addActionListener(this); menu.add(item);
2319 item = new JMenuItem("Import from text file..."); item.addActionListener(this); menu.add(item);
2320 item = new JMenuItem("Import labels as arealists..."); item.addActionListener(this); menu.add(item);
2321 popup.add(menu);
2323 menu = new JMenu("Export");
2324 item = new JMenuItem("Make flat image..."); item.addActionListener(this); menu.add(item);
2325 item = new JMenuItem("Arealists as labels (tif)"); item.addActionListener(this); menu.add(item);
2326 if (0 == layer.getParent().getZDisplayables(AreaList.class).size()) item.setEnabled(false);
2327 item = new JMenuItem("Arealists as labels (amira)"); item.addActionListener(this); menu.add(item);
2328 if (0 == layer.getParent().getZDisplayables(AreaList.class).size()) item.setEnabled(false);
2329 popup.add(menu);
2331 menu = new JMenu("Display");
2332 item = new JMenuItem("Resize canvas/LayerSet..."); item.addActionListener(this); menu.add(item);
2333 item = new JMenuItem("Autoresize canvas/LayerSet"); item.addActionListener(this); menu.add(item);
2334 // OBSOLETE // item = new JMenuItem("Rotate Layer/LayerSet..."); item.addActionListener(this); menu.add(item);
2335 item = new JMenuItem("Properties ..."); item.addActionListener(this); menu.add(item);
2336 popup.add(menu);
2338 menu = new JMenu("Project");
2339 this.project.getLoader().setupMenuItems(menu, this.getProject());
2340 item = new JMenuItem("Project properties..."); item.addActionListener(this); menu.add(item);
2341 item = new JMenuItem("Create subproject"); item.addActionListener(this); menu.add(item);
2342 if (null == canvas.getFakeImagePlus().getRoi()) item.setEnabled(false);
2343 item = new JMenuItem("Release memory..."); item.addActionListener(this); menu.add(item);
2344 item = new JMenuItem("Flush image cache"); item.addActionListener(this); menu.add(item);
2345 item = new JMenuItem("Regenerate all mipmaps"); item.addActionListener(this); menu.add(item);
2346 popup.add(menu);
2348 menu = new JMenu("Selection");
2349 item = new JMenuItem("Select all"); item.addActionListener(this); menu.add(item);
2350 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, Utils.getControlModifier(), true));
2351 if (0 == layer.getDisplayables().size() && 0 == layer.getParent().getZDisplayables().size()) item.setEnabled(false);
2352 item = new JMenuItem("Select none"); item.addActionListener(this); menu.add(item);
2353 if (0 == selection.getNSelected()) item.setEnabled(false);
2354 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2356 JMenu bytype = new JMenu("Select all by type");
2357 item = new JMenuItem("AreaList"); item.addActionListener(bytypelistener); bytype.add(item);
2358 item = new JMenuItem("Ball"); item.addActionListener(bytypelistener); bytype.add(item);
2359 item = new JMenuItem("Dissector"); item.addActionListener(bytypelistener); bytype.add(item);
2360 item = new JMenuItem("Image"); item.addActionListener(bytypelistener); bytype.add(item);
2361 item = new JMenuItem("Text"); item.addActionListener(bytypelistener); bytype.add(item);
2362 item = new JMenuItem("Pipe"); item.addActionListener(bytypelistener); bytype.add(item);
2363 item = new JMenuItem("Polyline"); item.addActionListener(bytypelistener); bytype.add(item);
2364 item = new JMenuItem("Profile"); item.addActionListener(bytypelistener); bytype.add(item);
2365 menu.add(bytype);
2367 item = new JMenuItem("Restore selection"); item.addActionListener(this); menu.add(item);
2368 item = new JMenuItem("Select under ROI"); item.addActionListener(this); menu.add(item);
2369 if (canvas.getFakeImagePlus().getRoi() == null) item.setEnabled(false);
2370 popup.add(menu);
2371 item = new JMenuItem("Search..."); item.addActionListener(this); popup.add(item);
2374 //canvas.add(popup);
2375 return popup;
2378 private ByTypeListener bytypelistener = new ByTypeListener(this);
2380 static private class ByTypeListener implements ActionListener {
2381 final Display d;
2382 ByTypeListener(final Display d) {
2383 this.d = d;
2385 public void actionPerformed(final ActionEvent ae) {
2386 final String command = ae.getActionCommand();
2388 final java.awt.geom.Area aroi = M.getArea(d.canvas.getFakeImagePlus().getRoi());
2390 d.dispatcher.exec(new Runnable() { public void run() {
2392 try {
2393 String type = command;
2394 if (type.equals("Image")) type = "Patch";
2395 Class c = Class.forName("ini.trakem2.display." + type);
2397 java.util.List<Displayable> a = new ArrayList<Displayable>();
2398 if (null != aroi) {
2399 a.addAll(d.layer.getDisplayables(c, aroi, true));
2400 a.addAll(d.layer.getParent().getZDisplayables(c, d.layer, aroi, true));
2401 } else {
2402 a.addAll(d.layer.getDisplayables(c));
2403 a.addAll(d.layer.getParent().getZDisplayables(c));
2404 // Remove non-visible ones
2405 for (final Iterator<Displayable> it = a.iterator(); it.hasNext(); ) {
2406 if (!it.next().isVisible()) it.remove();
2410 if (0 == a.size()) return;
2412 boolean selected = false;
2414 if (0 == ae.getModifiers()) {
2415 Utils.log2("first");
2416 d.selection.clear();
2417 d.selection.selectAll(a);
2418 selected = true;
2419 } else if (0 == (ae.getModifiers() ^ Event.SHIFT_MASK)) {
2420 Utils.log2("with shift");
2421 d.selection.selectAll(a); // just add them to the current selection
2422 selected = true;
2424 if (selected) {
2425 // Activate last:
2426 d.selection.setActive(a.get(a.size() -1));
2429 } catch (ClassNotFoundException e) {
2430 Utils.log2(e.toString());
2433 }});
2437 /** Check if a panel for the given Displayable is completely visible in the JScrollPane */
2438 public boolean isWithinViewport(final Displayable d) {
2439 final JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
2440 if (ht_tabs.get(d.getClass()) == scroll) return isWithinViewport(scroll, ht_panels.get(d));
2441 return false;
2444 private boolean isWithinViewport(JScrollPane scroll, DisplayablePanel dp) {
2445 if(null == dp) return false;
2446 JViewport view = scroll.getViewport();
2447 java.awt.Dimension dimensions = view.getExtentSize();
2448 java.awt.Point p = view.getViewPosition();
2449 int y = dp.getY();
2450 if ((y + DisplayablePanel.HEIGHT - p.y) <= dimensions.height && y >= p.y) {
2451 return true;
2453 return false;
2456 /** Check if a panel for the given Displayable is partially visible in the JScrollPane */
2457 public boolean isPartiallyWithinViewport(final Displayable d) {
2458 final JScrollPane scroll = ht_tabs.get(d.getClass());
2459 if (tabs.getSelectedComponent() == scroll) return isPartiallyWithinViewport(scroll, ht_panels.get(d));
2460 return false;
2463 /** Check if a panel for the given Displayable is at least partially visible in the JScrollPane */
2464 private boolean isPartiallyWithinViewport(final JScrollPane scroll, final DisplayablePanel dp) {
2465 if(null == dp) {
2466 //Utils.log2("Display.isPartiallyWithinViewport: null DisplayablePanel ??");
2467 return false; // to fast for you baby
2469 JViewport view = scroll.getViewport();
2470 java.awt.Dimension dimensions = view.getExtentSize();
2471 java.awt.Point p = view.getViewPosition();
2472 int y = dp.getY();
2473 if ( ((y + DisplayablePanel.HEIGHT - p.y) <= dimensions.height && y >= p.y) // completely visible
2474 || ((y + DisplayablePanel.HEIGHT - p.y) > dimensions.height && y < p.y + dimensions.height) // partially hovering at the bottom
2475 || ((y + DisplayablePanel.HEIGHT) > p.y && y < p.y) // partially hovering at the top
2477 return true;
2479 return false;
2482 /** A function to make a Displayable panel be visible in the screen, by scrolling the viewport of the JScrollPane. */
2483 private void scrollToShow(final Displayable d) {
2484 dispatcher.execSwing(new Runnable() { public void run() {
2485 final JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
2486 if (d instanceof ZDisplayable && scroll == scroll_zdispl) {
2487 scrollToShow(scroll_zdispl, ht_panels.get(d));
2488 return;
2490 final Class c = d.getClass();
2491 if (Patch.class == c && scroll == scroll_patches) {
2492 scrollToShow(scroll_patches, ht_panels.get(d));
2493 } else if (DLabel.class == c && scroll == scroll_labels) {
2494 scrollToShow(scroll_labels, ht_panels.get(d));
2495 } else if (Profile.class == c && scroll == scroll_profiles) {
2496 scrollToShow(scroll_profiles, ht_panels.get(d));
2498 }});
2501 private void scrollToShow(final JScrollPane scroll, final JPanel dp) {
2502 if (null == dp) return;
2503 JViewport view = scroll.getViewport();
2504 Point current = view.getViewPosition();
2505 Dimension extent = view.getExtentSize();
2506 int panel_y = dp.getY();
2507 if ((panel_y + DisplayablePanel.HEIGHT - current.y) <= extent.height && panel_y >= current.y) {
2508 // it's completely visible already
2509 return;
2510 } else {
2511 // scroll just enough
2512 // if it's above, show at the top
2513 if (panel_y - current.y < 0) {
2514 view.setViewPosition(new Point(0, panel_y));
2516 // if it's below (even if partially), show at the bottom
2517 else if (panel_y + 50 > current.y + extent.height) {
2518 view.setViewPosition(new Point(0, panel_y - extent.height + 50));
2519 //Utils.log("Display.scrollToShow: panel_y: " + panel_y + " current.y: " + current.y + " extent.height: " + extent.height);
2524 /** Update the title of the given Displayable in its DisplayablePanel, if any. */
2525 static public void updateTitle(final Layer layer, final Displayable displ) {
2526 for (final Display d : al_displays) {
2527 if (layer == d.layer) {
2528 DisplayablePanel dp = d.ht_panels.get(displ);
2529 if (null != dp) dp.updateTitle();
2534 /** Update the Display's title in all Displays showing the given Layer. */
2535 static public void updateTitle(final Layer layer) {
2536 for (final Display d : al_displays) {
2537 if (d.layer == layer) {
2538 d.updateTitle();
2542 /** Update the Display's title in all Displays showing a Layer of the given LayerSet. */
2543 static public void updateTitle(final LayerSet ls) {
2544 for (final Display d : al_displays) {
2545 if (d.layer.getParent() == ls) {
2546 d.updateTitle();
2551 /** Set a new title in the JFrame, showing info on the layer 'z' and the magnification. */
2552 public void updateTitle() {
2553 // From ij.ImagePlus class, the solution:
2554 String scale = "";
2555 final double magnification = canvas.getMagnification();
2556 if (magnification!=1.0) {
2557 final double percent = magnification*100.0;
2558 scale = new StringBuilder(" (").append(Utils.d2s(percent, percent==(int)percent ? 0 : 1)).append("%)").toString();
2560 final Calibration cal = layer.getParent().getCalibration();
2561 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();
2562 frame.setTitle(title);
2563 // fix the title for the FakeImageWindow and thus the WindowManager listing in the menus
2564 canvas.getFakeImagePlus().setTitle(title);
2567 /** 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. */
2568 public void nextLayer(final int modifiers) {
2569 final Layer l;
2570 if (0 == (modifiers ^ Event.SHIFT_MASK)) {
2571 l = layer.getParent().nextNonEmpty(layer);
2572 } else if (scroll_step > 1) {
2573 int i = layer.getParent().indexOf(this.layer);
2574 Layer la = layer.getParent().getLayer(i + scroll_step);
2575 if (null != la) l = la;
2576 else l = null;
2577 } else {
2578 l = layer.getParent().next(layer);
2580 if (l != layer) {
2581 slt.set(l);
2582 updateInDatabase("layer_id");
2586 private final void translateLayerColors(final Layer current, final Layer other) {
2587 if (current == other) return;
2588 if (layer_channels.size() > 0) {
2589 final LayerSet ls = getLayerSet();
2590 // translate colors by distance from current layer to new Layer l
2591 final int dist = ls.indexOf(other) - ls.indexOf(current);
2592 translateLayerColor(Color.red, dist);
2593 translateLayerColor(Color.blue, dist);
2597 private final void translateLayerColor(final Color color, final int dist) {
2598 final LayerSet ls = getLayerSet();
2599 final Layer l = layer_channels.get(color);
2600 if (null == l) return;
2601 updateColor(Color.white, layer_panels.get(l));
2602 final Layer l2 = ls.getLayer(ls.indexOf(l) + dist);
2603 if (null != l2) updateColor(color, layer_panels.get(l2));
2606 private final void updateColor(final Color color, final LayerPanel lp) {
2607 lp.setColor(color);
2608 setColorChannel(lp.layer, color);
2611 /** Calls setLayer(la) on the SetLayerThread. */
2612 public void toLayer(final Layer la) {
2613 if (la.getParent() != layer.getParent()) return; // not of the same LayerSet
2614 if (la == layer) return; // nothing to do
2615 slt.set(la);
2616 updateInDatabase("layer_id");
2619 /** 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. */
2620 public void previousLayer(final int modifiers) {
2621 final Layer l;
2622 if (0 == (modifiers ^ Event.SHIFT_MASK)) {
2623 l = layer.getParent().previousNonEmpty(layer);
2624 } else if (scroll_step > 1) {
2625 int i = layer.getParent().indexOf(this.layer);
2626 Layer la = layer.getParent().getLayer(i - scroll_step);
2627 if (null != la) l = la;
2628 else l = null;
2629 } else {
2630 l = layer.getParent().previous(layer);
2632 if (l != layer) {
2633 slt.set(l);
2634 updateInDatabase("layer_id");
2638 static public void updateLayerScroller(LayerSet set) {
2639 for (final Display d : al_displays) {
2640 if (d.layer.getParent() == set) {
2641 d.updateLayerScroller(d.layer);
2646 private void updateLayerScroller(Layer layer) {
2647 int size = layer.getParent().size();
2648 if (size <= 1) {
2649 scroller.setValues(0, 1, 0, 0);
2650 scroller.setEnabled(false);
2651 } else {
2652 scroller.setEnabled(true);
2653 scroller.setValues(layer.getParent().getLayerIndex(layer.getId()), 1, 0, size);
2655 recreateLayerPanels(layer);
2658 // Can't use this.layer, may still be null. User argument instead.
2659 private synchronized void recreateLayerPanels(final Layer layer) {
2660 synchronized (layer_channels) {
2661 panel_layers.removeAll();
2663 if (0 == layer_panels.size()) {
2664 for (final Layer la : layer.getParent().getLayers()) {
2665 final LayerPanel lp = new LayerPanel(this, la);
2666 layer_panels.put(la, lp);
2667 this.panel_layers.add(lp);
2669 } else {
2670 // Set theory at work: keep old to reuse
2671 layer_panels.keySet().retainAll(layer.getParent().getLayers());
2672 for (final Layer la : layer.getParent().getLayers()) {
2673 LayerPanel lp = layer_panels.get(la);
2674 if (null == lp) {
2675 lp = new LayerPanel(this, la);
2676 layer_panels.put(la, lp);
2678 this.panel_layers.add(lp);
2680 for (final Iterator<Map.Entry<Integer,LayerPanel>> it = layer_alpha.entrySet().iterator(); it.hasNext(); ) {
2681 final Map.Entry<Integer,LayerPanel> e = it.next();
2682 if (-1 == getLayerSet().indexOf(e.getValue().layer)) it.remove();
2684 for (final Iterator<Map.Entry<Color,Layer>> it = layer_channels.entrySet().iterator(); it.hasNext(); ) {
2685 final Map.Entry<Color,Layer> e = it.next();
2686 if (-1 == getLayerSet().indexOf(e.getValue())) it.remove();
2688 scroll_layers.repaint();
2693 private void updateSnapshots() {
2694 Enumeration<DisplayablePanel> e = ht_panels.elements();
2695 while (e.hasMoreElements()) {
2696 e.nextElement().remake();
2698 Utils.updateComponent(tabs.getSelectedComponent());
2701 static public void updatePanel(Layer layer, final Displayable displ) {
2702 if (null == layer && null != front) layer = front.layer; // the front layer
2703 for (final Display d : al_displays) {
2704 if (d.layer == layer) {
2705 d.updatePanel(displ);
2710 private void updatePanel(Displayable d) {
2711 JPanel c = null;
2712 if (d instanceof Profile) {
2713 c = panel_profiles;
2714 } else if (d instanceof Patch) {
2715 c = panel_patches;
2716 } else if (d instanceof DLabel) {
2717 c = panel_labels;
2718 } else if (d instanceof Pipe) {
2719 c = panel_zdispl;
2721 if (null == c) return;
2722 DisplayablePanel dp = ht_panels.get(d);
2723 dp.remake();
2724 Utils.updateComponent(c);
2727 static public void updatePanelIndex(final Layer layer, final Displayable displ) {
2728 for (final Display d : al_displays) {
2729 if (d.layer == layer || displ instanceof ZDisplayable) {
2730 d.updatePanelIndex(displ);
2735 private void updatePanelIndex(final Displayable d) {
2736 // find first of the kind, then remove and insert its panel
2737 int i = 0;
2738 JPanel c = null;
2739 if (d instanceof ZDisplayable) {
2740 i = layer.getParent().indexOf((ZDisplayable)d);
2741 c = panel_zdispl;
2742 } else {
2743 i = layer.relativeIndexOf(d);
2744 if (d instanceof Profile) {
2745 c = panel_profiles;
2746 } else if (d instanceof Patch) {
2747 c = panel_patches;
2748 } else if (d instanceof DLabel) {
2749 c = panel_labels;
2752 if (null == c) return;
2753 DisplayablePanel dp = ht_panels.get(d);
2754 if (null == dp) return; // may be half-baked, wait
2755 c.remove(dp);
2756 c.add(dp, i); // java and its fabulous consistency
2757 // not enough! Utils.updateComponent(c);
2758 // So, cocktail:
2759 c.invalidate();
2760 c.validate();
2761 Utils.updateComponent(c);
2764 /** Repair possibly missing panels and other components by simply resetting the same Layer */
2765 public void repairGUI() {
2766 Layer layer = this.layer;
2767 this.layer = null;
2768 setLayer(layer);
2771 public void actionPerformed(final ActionEvent ae) {
2772 dispatcher.exec(new Runnable() { public void run() {
2774 String command = ae.getActionCommand();
2775 if (command.startsWith("Job")) {
2776 if (Utils.checkYN("Really cancel job?")) {
2777 project.getLoader().quitJob(command);
2778 repairGUI();
2780 return;
2781 } else if (command.equals("Move to top")) {
2782 if (null == active) return;
2783 canvas.setUpdateGraphics(true);
2784 layer.getParent().move(LayerSet.TOP, active);
2785 Display.repaint(layer.getParent(), active, 5);
2786 //Display.updatePanelIndex(layer, active);
2787 } else if (command.equals("Move up")) {
2788 if (null == active) return;
2789 canvas.setUpdateGraphics(true);
2790 layer.getParent().move(LayerSet.UP, active);
2791 Display.repaint(layer.getParent(), active, 5);
2792 //Display.updatePanelIndex(layer, active);
2793 } else if (command.equals("Move down")) {
2794 if (null == active) return;
2795 canvas.setUpdateGraphics(true);
2796 layer.getParent().move(LayerSet.DOWN, active);
2797 Display.repaint(layer.getParent(), active, 5);
2798 //Display.updatePanelIndex(layer, active);
2799 } else if (command.equals("Move to bottom")) {
2800 if (null == active) return;
2801 canvas.setUpdateGraphics(true);
2802 layer.getParent().move(LayerSet.BOTTOM, active);
2803 Display.repaint(layer.getParent(), active, 5);
2804 //Display.updatePanelIndex(layer, active);
2805 } else if (command.equals("Duplicate, link and send to next layer")) {
2806 duplicateLinkAndSendTo(active, 1, layer.getParent().next(layer));
2807 } else if (command.equals("Duplicate, link and send to previous layer")) {
2808 duplicateLinkAndSendTo(active, 0, layer.getParent().previous(layer));
2809 } else if (command.equals("Duplicate, link and send to...")) {
2810 // fix non-scrolling popup menu
2811 GenericDialog gd = new GenericDialog("Send to");
2812 gd.addMessage("Duplicate, link and send to...");
2813 String[] sl = new String[layer.getParent().size()];
2814 int next = 0;
2815 for (Iterator it = layer.getParent().getLayers().iterator(); it.hasNext(); ) {
2816 sl[next++] = project.findLayerThing(it.next()).toString();
2818 gd.addChoice("Layer: ", sl, sl[layer.getParent().indexOf(layer)]);
2819 gd.showDialog();
2820 if (gd.wasCanceled()) return;
2821 Layer la = layer.getParent().getLayer(gd.getNextChoiceIndex());
2822 if (layer == la) {
2823 Utils.showMessage("Can't duplicate, link and send to the same layer.");
2824 return;
2826 duplicateLinkAndSendTo(active, 0, la);
2827 } else if (-1 != command.indexOf("z = ")) {
2828 // this is an item from the "Duplicate, link and send to" menu of layer z's
2829 Layer target_layer = layer.getParent().getLayer(Double.parseDouble(command.substring(command.lastIndexOf(' ') +1)));
2830 Utils.log2("layer: __" +command.substring(command.lastIndexOf(' ') +1) + "__");
2831 if (null == target_layer) return;
2832 duplicateLinkAndSendTo(active, 0, target_layer);
2833 } else if (-1 != command.indexOf("z=")) {
2834 // WARNING the indexOf is very similar to the previous one
2835 // Send the linked group to the selected layer
2836 int iz = command.indexOf("z=")+2;
2837 Utils.log2("iz=" + iz + " other: " + command.indexOf(' ', iz+2));
2838 int end = command.indexOf(' ', iz);
2839 if (-1 == end) end = command.length();
2840 double lz = Double.parseDouble(command.substring(iz, end));
2841 Layer target = layer.getParent().getLayer(lz);
2842 HashSet hs = active.getLinkedGroup(new HashSet());
2843 layer.getParent().move(hs, active.getLayer(), target);
2844 } else if (command.equals("Unlink")) {
2845 if (null == active || active instanceof Patch) return;
2846 active.unlink();
2847 updateSelection();//selection.update();
2848 } else if (command.equals("Unlink from images")) {
2849 if (null == active) return;
2850 try {
2851 for (Displayable displ: selection.getSelected()) {
2852 displ.unlinkAll(Patch.class);
2854 updateSelection();//selection.update();
2855 } catch (Exception e) { IJError.print(e); }
2856 } else if (command.equals("Unlink slices")) {
2857 YesNoCancelDialog yn = new YesNoCancelDialog(frame, "Attention", "Really unlink all slices from each other?\nThere is no undo.");
2858 if (!yn.yesPressed()) return;
2859 final ArrayList<Patch> pa = ((Patch)active).getStackPatches();
2860 for (int i=pa.size()-1; i>0; i--) {
2861 pa.get(i).unlink(pa.get(i-1));
2863 } else if (command.equals("Send to next layer")) {
2864 Rectangle box = selection.getBox();
2865 try {
2866 // unlink Patch instances
2867 for (final Displayable displ : selection.getSelected()) {
2868 displ.unlinkAll(Patch.class);
2870 updateSelection();//selection.update();
2871 } catch (Exception e) { IJError.print(e); }
2872 //layer.getParent().moveDown(layer, active); // will repaint whatever appropriate layers
2873 selection.moveDown();
2874 repaint(layer.getParent(), box);
2875 } else if (command.equals("Send to previous layer")) {
2876 Rectangle box = selection.getBox();
2877 try {
2878 // unlink Patch instances
2879 for (final Displayable displ : selection.getSelected()) {
2880 displ.unlinkAll(Patch.class);
2882 updateSelection();//selection.update();
2883 } catch (Exception e) { IJError.print(e); }
2884 //layer.getParent().moveUp(layer, active); // will repaint whatever appropriate layers
2885 selection.moveUp();
2886 repaint(layer.getParent(), box);
2887 } else if (command.equals("Show centered")) {
2888 if (active == null) return;
2889 showCentered(active);
2890 } else if (command.equals("Delete...")) {
2892 if (null != active) {
2893 Displayable d = active;
2894 selection.remove(d);
2895 d.remove(true); // will repaint
2898 // remove all selected objects
2899 selection.deleteAll();
2900 } else if (command.equals("Color...")) {
2901 IJ.doCommand("Color Picker...");
2902 } else if (command.equals("Revert")) {
2903 if (null == active || active.getClass() != Patch.class) return;
2904 Patch p = (Patch)active;
2905 if (!p.revert()) {
2906 if (null == p.getOriginalPath()) Utils.log("No editions to save for patch " + p.getTitle() + " #" + p.getId());
2907 else Utils.log("Could not revert Patch " + p.getTitle() + " #" + p.getId());
2909 } else if (command.equals("Remove alpha mask")) {
2910 final ArrayList<Displayable> patches = selection.getSelected(Patch.class);
2911 if (patches.size() > 0) {
2912 Bureaucrat.createAndStart(new Worker.Task("Removing alpha mask" + (patches.size() > 1 ? "s" : "")) { public void exec() {
2913 final ArrayList<Future> jobs = new ArrayList<Future>();
2914 for (final Displayable d : patches) {
2915 final Patch p = (Patch) d;
2916 p.setAlphaMask(null);
2917 Future job = p.getProject().getLoader().regenerateMipMaps(p); // submit to queue
2918 if (null != job) jobs.add(job);
2920 // join all
2921 for (final Future job : jobs) try {
2922 job.get();
2923 } catch (Exception ie) {}
2924 }}, patches.get(0).getProject());
2926 } else if (command.equals("Undo")) {
2927 Bureaucrat.createAndStart(new Worker.Task("Undo") { public void exec() {
2928 layer.getParent().undoOneStep();
2929 Display.repaint(layer.getParent());
2930 }}, project);
2931 } else if (command.equals("Redo")) {
2932 Bureaucrat.createAndStart(new Worker.Task("Redo") { public void exec() {
2933 layer.getParent().redoOneStep();
2934 Display.repaint(layer.getParent());
2935 }}, project);
2936 } else if (command.equals("Transform")) {
2937 if (null == active) return;
2938 canvas.setTransforming(true);
2939 } else if (command.equals("Apply transform")) {
2940 if (null == active) return;
2941 canvas.setTransforming(false);
2942 } else if (command.equals("Apply transform propagating to last layer")) {
2943 if (selection.isTransforming()) {
2944 final java.util.List<Layer> layers = layer.getParent().getLayers();
2945 selection.applyAndPropagate(new HashSet<Layer>(layers.subList(layers.indexOf(Display.this.layer)+1, layers.size()))); // +1 to exclude current layer
2947 } else if (command.equals("Apply transform propagating to first layer")) {
2948 if (selection.isTransforming()) {
2949 final java.util.List<Layer> layers = layer.getParent().getLayers();
2950 selection.applyAndPropagate(new HashSet<Layer>(layers.subList(0, layers.indexOf(Display.this.layer))));
2952 } else if (command.equals("Cancel transform")) {
2953 if (null == active) return;
2954 canvas.cancelTransform();
2955 } else if (command.equals("Specify transform...")) {
2956 if (null == active) return;
2957 selection.specify();
2958 } else if (command.equals("Hide all but images")) {
2959 ArrayList<Class> type = new ArrayList<Class>();
2960 type.add(Patch.class);
2961 selection.removeAll(layer.getParent().hideExcept(type, false));
2962 Display.update(layer.getParent(), false);
2963 } else if (command.equals("Unhide all")) {
2964 layer.getParent().setAllVisible(false);
2965 Display.update(layer.getParent(), false);
2966 } else if (command.startsWith("Hide all ")) {
2967 String type = command.substring(9, command.length() -1); // skip the ending plural 's'
2968 type = type.substring(0, 1).toUpperCase() + type.substring(1);
2969 selection.removeAll(layer.getParent().setVisible(type, false, true));
2970 } else if (command.startsWith("Unhide all ")) {
2971 String type = command.substring(11, command.length() -1); // skip the ending plural 's'
2972 type = type.substring(0, 1).toUpperCase() + type.substring(1);
2973 layer.getParent().setVisible(type, true, true);
2974 } else if (command.equals("Hide deselected")) {
2975 hideDeselected(0 != (ActionEvent.ALT_MASK & ae.getModifiers()));
2976 } else if (command.equals("Hide deselected except images")) {
2977 hideDeselected(true);
2978 } else if (command.equals("Hide selected")) {
2979 selection.setVisible(false); // TODO should deselect them too? I don't think so.
2980 } else if (command.equals("Resize canvas/LayerSet...")) {
2981 resizeCanvas();
2982 } else if (command.equals("Autoresize canvas/LayerSet")) {
2983 layer.getParent().setMinimumDimensions();
2984 } else if (command.equals("Import image")) {
2985 importImage();
2986 } else if (command.equals("Import next image")) {
2987 importNextImage();
2988 } else if (command.equals("Import stack...")) {
2989 Display.this.getLayerSet().addLayerContentStep(layer);
2990 Rectangle sr = getCanvas().getSrcRect();
2991 Bureaucrat burro = project.getLoader().importStack(layer, sr.x + sr.width/2, sr.y + sr.height/2, null, true, null);
2992 burro.addPostTask(new Runnable() { public void run() {
2993 Display.this.getLayerSet().addLayerContentStep(layer);
2994 }});
2995 } else if (command.equals("Import grid...")) {
2996 Display.this.getLayerSet().addLayerContentStep(layer);
2997 Bureaucrat burro = project.getLoader().importGrid(layer);
2998 burro.addPostTask(new Runnable() { public void run() {
2999 Display.this.getLayerSet().addLayerContentStep(layer);
3000 }});
3001 } else if (command.equals("Import sequence as grid...")) {
3002 Display.this.getLayerSet().addLayerContentStep(layer);
3003 Bureaucrat burro = project.getLoader().importSequenceAsGrid(layer);
3004 burro.addPostTask(new Runnable() { public void run() {
3005 Display.this.getLayerSet().addLayerContentStep(layer);
3006 }});
3007 } else if (command.equals("Import from text file...")) {
3008 Display.this.getLayerSet().addLayerContentStep(layer);
3009 Bureaucrat burro = project.getLoader().importImages(layer);
3010 burro.addPostTask(new Runnable() { public void run() {
3011 Display.this.getLayerSet().addLayerContentStep(layer);
3012 }});
3013 } else if (command.equals("Import labels as arealists...")) {
3014 Display.this.getLayerSet().addChangeTreesStep();
3015 Bureaucrat burro = project.getLoader().importLabelsAsAreaLists(layer, null, Double.MAX_VALUE, 0, 0.4f, false);
3016 burro.addPostTask(new Runnable() { public void run() {
3017 Display.this.getLayerSet().addChangeTreesStep();
3018 }});
3019 } else if (command.equals("Make flat image...")) {
3020 // if there's a ROI, just use that as cropping rectangle
3021 Rectangle srcRect = null;
3022 Roi roi = canvas.getFakeImagePlus().getRoi();
3023 if (null != roi) {
3024 srcRect = roi.getBounds();
3025 } else {
3026 // otherwise, whatever is visible
3027 //srcRect = canvas.getSrcRect();
3028 // The above is confusing. That is what ROIs are for. So paint all:
3029 srcRect = new Rectangle(0, 0, (int)Math.ceil(layer.getParent().getLayerWidth()), (int)Math.ceil(layer.getParent().getLayerHeight()));
3031 double scale = 1.0;
3032 final String[] types = new String[]{"8-bit grayscale", "RGB Color"};
3033 int the_type = ImagePlus.GRAY8;
3034 final GenericDialog gd = new GenericDialog("Choose", frame);
3035 gd.addSlider("Scale: ", 1, 100, 100);
3036 gd.addChoice("Type: ", types, types[0]);
3037 if (layer.getParent().size() > 1) {
3039 String[] layers = new String[layer.getParent().size()];
3040 int i = 0;
3041 for (Iterator it = layer.getParent().getLayers().iterator(); it.hasNext(); ) {
3042 layers[i] = layer.getProject().findLayerThing((Layer)it.next()).toString();
3043 i++;
3045 int i_layer = layer.getParent().indexOf(layer);
3046 gd.addChoice("Start: ", layers, layers[i_layer]);
3047 gd.addChoice("End: ", layers, layers[i_layer]);
3049 Utils.addLayerRangeChoices(Display.this.layer, gd); /// $#%! where are my lisp macros
3050 gd.addCheckbox("Include non-empty layers only", true);
3052 gd.addMessage("Background color:");
3053 Utils.addRGBColorSliders(gd, Color.black);
3054 gd.addCheckbox("Best quality", false);
3055 gd.addMessage("");
3056 gd.addCheckbox("Save to file", false);
3057 gd.addCheckbox("Save for web", false);
3058 gd.showDialog();
3059 if (gd.wasCanceled()) return;
3060 scale = gd.getNextNumber() / 100;
3061 the_type = (0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY8 : ImagePlus.COLOR_RGB);
3062 if (Double.isNaN(scale) || scale <= 0.0) {
3063 Utils.showMessage("Invalid scale.");
3064 return;
3066 Layer[] layer_array = null;
3067 boolean non_empty_only = false;
3068 if (layer.getParent().size() > 1) {
3069 non_empty_only = gd.getNextBoolean();
3070 int i_start = gd.getNextChoiceIndex();
3071 int i_end = gd.getNextChoiceIndex();
3072 ArrayList al = new ArrayList();
3073 ArrayList al_zd = layer.getParent().getZDisplayables();
3074 ZDisplayable[] zd = new ZDisplayable[al_zd.size()];
3075 al_zd.toArray(zd);
3076 for (int i=i_start, j=0; i <= i_end; i++, j++) {
3077 Layer la = layer.getParent().getLayer(i);
3078 if (!la.isEmpty() || !non_empty_only) al.add(la); // checks both the Layer and the ZDisplayable objects in the parent LayerSet
3080 if (0 == al.size()) {
3081 Utils.showMessage("All layers are empty!");
3082 return;
3084 layer_array = new Layer[al.size()];
3085 al.toArray(layer_array);
3086 } else {
3087 layer_array = new Layer[]{Display.this.layer};
3089 final Color background = new Color((int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
3090 final boolean quality = gd.getNextBoolean();
3091 final boolean save_to_file = gd.getNextBoolean();
3092 final boolean save_for_web = gd.getNextBoolean();
3093 // in its own thread
3094 if (save_for_web) project.getLoader().makePrescaledTiles(layer_array, Patch.class, srcRect, scale, c_alphas, the_type);
3095 else project.getLoader().makeFlatImage(layer_array, srcRect, scale, c_alphas, the_type, save_to_file, quality, background);
3097 } else if (command.equals("Lock")) {
3098 selection.setLocked(true);
3099 } else if (command.equals("Unlock")) {
3100 selection.setLocked(false);
3101 } else if (command.equals("Properties...")) {
3102 active.adjustProperties();
3103 updateSelection();
3104 } else if (command.equals("Cancel alignment")) {
3105 layer.getParent().cancelAlign();
3106 } else if (command.equals("Align with landmarks")) {
3107 layer.getParent().applyAlign(false);
3108 } else if (command.equals("Align and register")) {
3109 layer.getParent().applyAlign(true);
3110 } else if (command.equals("Align using profiles")) {
3111 if (!selection.contains(Profile.class)) {
3112 Utils.showMessage("No profiles are selected.");
3113 return;
3115 // ask for range of layers
3116 final GenericDialog gd = new GenericDialog("Choose range");
3117 Utils.addLayerRangeChoices(Display.this.layer, gd);
3118 gd.showDialog();
3119 if (gd.wasCanceled()) return;
3120 Layer la_start = layer.getParent().getLayer(gd.getNextChoiceIndex());
3121 Layer la_end = layer.getParent().getLayer(gd.getNextChoiceIndex());
3122 if (la_start == la_end) {
3123 Utils.showMessage("Need at least two layers.");
3124 return;
3126 if (selection.isLocked()) {
3127 Utils.showMessage("There are locked objects.");
3128 return;
3130 layer.getParent().startAlign(Display.this);
3131 layer.getParent().applyAlign(la_start, la_end, selection);
3132 } else if (command.equals("Align stack slices")) {
3133 if (getActive() instanceof Patch) {
3134 final Patch slice = (Patch)getActive();
3135 if (slice.isStack()) {
3136 // check linked group
3137 final HashSet hs = slice.getLinkedGroup(new HashSet());
3138 for (Iterator it = hs.iterator(); it.hasNext(); ) {
3139 if (it.next().getClass() != Patch.class) {
3140 Utils.showMessage("Images are linked to other objects, can't proceed to cross-correlate them."); // labels should be fine, need to check that
3141 return;
3144 final LayerSet ls = slice.getLayerSet();
3145 final HashSet<Displayable> linked = slice.getLinkedGroup(null);
3146 ls.addTransformStep(linked);
3147 Bureaucrat burro = Registration.registerStackSlices((Patch)getActive()); // will repaint
3148 burro.addPostTask(new Runnable() { public void run() {
3149 // The current state when done
3150 ls.addTransformStep(linked);
3151 }});
3152 } else {
3153 Utils.log("Align stack slices: selected image is not part of a stack.");
3156 } else if (command.equals("Align layers")) {
3157 final Layer la = layer;; // caching, since scroll wheel may change it
3158 la.getParent().addTransformStep(la);
3159 Bureaucrat burro = AlignTask.alignLayersLinearlyTask( la );
3160 burro.addPostTask(new Runnable() { public void run() {
3161 la.getParent().addTransformStep(la);
3162 }});
3163 } else if (command.equals("Align multi-layer mosaic")) {
3164 final Layer la = layer; // caching, since scroll wheel may change it
3165 la.getParent().addTransformStep();
3166 Bureaucrat burro = AlignTask.alignMultiLayerMosaicTask( la );
3167 burro.addPostTask(new Runnable() { public void run() {
3168 la.getParent().addTransformStep();
3169 }});
3170 } else if (command.equals("Properties ...")) { // NOTE the space before the dots, to distinguish from the "Properties..." command that works on Displayable objects.
3171 GenericDialog gd = new GenericDialog("Properties", Display.this.frame);
3172 //gd.addNumericField("layer_scroll_step: ", this.scroll_step, 0);
3173 gd.addSlider("layer_scroll_step: ", 1, layer.getParent().size(), Display.this.scroll_step);
3174 gd.addChoice("snapshots_mode", LayerSet.snapshot_modes, LayerSet.snapshot_modes[layer.getParent().getSnapshotsMode()]);
3175 gd.addCheckbox("prefer_snapshots_quality", layer.getParent().snapshotsQuality());
3176 Loader lo = getProject().getLoader();
3177 boolean using_mipmaps = lo.isMipMapsEnabled();
3178 gd.addCheckbox("enable_mipmaps", using_mipmaps);
3179 String preprocessor = project.getLoader().getPreprocessor();
3180 gd.addStringField("image_preprocessor: ", null == preprocessor ? "" : preprocessor);
3181 gd.addCheckbox("enable_layer_pixels virtualization", layer.getParent().isPixelsVirtualizationEnabled());
3182 double max = layer.getParent().getLayerWidth() < layer.getParent().getLayerHeight() ? layer.getParent().getLayerWidth() : layer.getParent().getLayerHeight();
3183 gd.addSlider("max_dimension of virtualized layer pixels: ", 0, max, layer.getParent().getPixelsMaxDimension());
3184 // --------
3185 gd.showDialog();
3186 if (gd.wasCanceled()) return;
3187 // --------
3188 int sc = (int) gd.getNextNumber();
3189 if (sc < 1) sc = 1;
3190 Display.this.scroll_step = sc;
3191 updateInDatabase("scroll_step");
3193 layer.getParent().setSnapshotsMode(gd.getNextChoiceIndex());
3194 layer.getParent().setSnapshotsQuality(gd.getNextBoolean());
3196 boolean generate_mipmaps = gd.getNextBoolean();
3197 if (using_mipmaps && generate_mipmaps) {
3198 // nothing changed
3199 } else {
3200 if (using_mipmaps) { // and !generate_mipmaps
3201 lo.flushMipMaps(true);
3202 } else {
3203 // not using mipmaps before, and true == generate_mipmaps
3204 lo.generateMipMaps(layer.getParent().getDisplayables(Patch.class));
3208 final String prepro = gd.getNextString();
3209 if (!project.getLoader().setPreprocessor(prepro.trim())) {
3210 Utils.showMessage("Could NOT set the preprocessor to " + prepro);
3213 layer.getParent().setPixelsVirtualizationEnabled(gd.getNextBoolean());
3214 layer.getParent().setPixelsMaxDimension((int)gd.getNextNumber());
3215 } else if (command.equals("Search...")) {
3216 new Search();
3217 } else if (command.equals("Select all")) {
3218 selection.selectAll();
3219 repaint(Display.this.layer, selection.getBox(), 0);
3220 } else if (command.equals("Select none")) {
3221 Rectangle box = selection.getBox();
3222 selection.clear();
3223 repaint(Display.this.layer, box, 0);
3224 } else if (command.equals("Restore selection")) {
3225 selection.restore();
3226 } else if (command.equals("Select under ROI")) {
3227 Roi roi = canvas.getFakeImagePlus().getRoi();
3228 if (null == roi) return;
3229 selection.selectAll(roi, true);
3230 } else if (command.equals("Merge")) {
3231 ArrayList al_sel = selection.getSelected();
3232 // put active at the beginning, to work as the base on which other's will get merged
3233 al_sel.remove(Display.this.active);
3234 al_sel.add(0, Display.this.active);
3235 AreaList ali = AreaList.merge(al_sel);
3236 if (null != ali) {
3237 // remove all but the first from the selection
3238 for (int i=1; i<al_sel.size(); i++) {
3239 Object ob = al_sel.get(i);
3240 if (ob.getClass() == AreaList.class) {
3241 selection.remove((Displayable)ob);
3244 selection.updateTransform(ali);
3245 repaint(ali.getLayerSet(), ali, 0);
3247 } else if (command.equals("Identify...")) {
3248 // for pipes only for now
3249 if (!(active instanceof Pipe)) return;
3250 ini.trakem2.vector.Compare.findSimilar((Pipe)active);
3251 } else if (command.equals("Identify with axes...")) {
3252 if (!(active instanceof Pipe)) return;
3253 if (Project.getProjects().size() < 2) {
3254 Utils.showMessage("You need at least two projects open:\n-A reference project\n-The current project with the pipe to identify");
3255 return;
3257 ini.trakem2.vector.Compare.findSimilarWithAxes((Pipe)active);
3258 } else if (command.equals("View orthoslices")) {
3259 if (!(active instanceof Patch)) return;
3260 Display3D.showOrthoslices(((Patch)active));
3261 } else if (command.equals("View volume")) {
3262 if (!(active instanceof Patch)) return;
3263 Display3D.showVolume(((Patch)active));
3264 } else if (command.equals("Show in 3D")) {
3265 for (Iterator it = selection.getSelected(ZDisplayable.class).iterator(); it.hasNext(); ) {
3266 ZDisplayable zd = (ZDisplayable)it.next();
3267 Display3D.show(zd.getProject().findProjectThing(zd));
3269 // handle profile lists ...
3270 HashSet hs = new HashSet();
3271 for (Iterator it = selection.getSelected(Profile.class).iterator(); it.hasNext(); ) {
3272 Displayable d = (Displayable)it.next();
3273 ProjectThing profile_list = (ProjectThing)d.getProject().findProjectThing(d).getParent();
3274 if (!hs.contains(profile_list)) {
3275 Display3D.show(profile_list);
3276 hs.add(profile_list);
3279 } else if (command.equals("Snap")) {
3280 if (!(active instanceof Patch)) return;
3281 StitchingTEM.snap(getActive(), Display.this);
3282 } else if (command.equals("Blend")) {
3283 HashSet<Patch> patches = new HashSet<Patch>();
3284 for (final Displayable d : selection.getSelected()) {
3285 if (d.getClass() == Patch.class) patches.add((Patch)d);
3287 if (patches.size() > 1) {
3288 GenericDialog gd = new GenericDialog("Blending");
3289 gd.addCheckbox("Respect current alpha mask", true);
3290 gd.showDialog();
3291 if (gd.wasCanceled()) return;
3292 Blending.blend(patches, gd.getNextBoolean());
3293 } else {
3294 IJ.log("Please select more than one overlapping image.");
3296 } else if (command.equals("Montage")) {
3297 if (!(active instanceof Patch)) {
3298 Utils.showMessage("Please select only images.");
3299 return;
3301 final Set<Displayable> affected = new HashSet<Displayable>(selection.getAffected());
3302 for (final Displayable d : affected)
3303 if (d.isLinked()) {
3304 Utils.showMessage( "You cannot montage linked objects." );
3305 return;
3307 // make an undo step!
3308 final LayerSet ls = layer.getParent();
3309 ls.addTransformStep(affected);
3310 Bureaucrat burro = AlignTask.alignSelectionTask( selection );
3311 burro.addPostTask(new Runnable() { public void run() {
3312 ls.addTransformStep(affected);
3313 }});
3314 } else if (command.equals("Lens correction")) {
3315 final Layer la = layer;
3316 la.getParent().addDataEditStep(new HashSet<Displayable>(la.getParent().getDisplayables()));
3317 Bureaucrat burro = DistortionCorrectionTask.correctDistortionFromSelection( selection );
3318 burro.addPostTask(new Runnable() { public void run() {
3319 // no means to know which where modified and from which layers!
3320 la.getParent().addDataEditStep(new HashSet<Displayable>(la.getParent().getDisplayables()));
3321 }});
3322 } else if (command.equals("Link images...")) {
3323 GenericDialog gd = new GenericDialog("Options");
3324 gd.addMessage("Linking images to images (within their own layer only):");
3325 String[] options = {"all images to all images", "each image with any other overlapping image"};
3326 gd.addChoice("Link: ", options, options[1]);
3327 String[] options2 = {"selected images only", "all images in this layer", "all images in all layers"};
3328 gd.addChoice("Apply to: ", options2, options2[0]);
3329 gd.showDialog();
3330 if (gd.wasCanceled()) return;
3331 Layer lay = layer;
3332 final HashSet<Displayable> ds = new HashSet<Displayable>(lay.getParent().getDisplayables());
3333 lay.getParent().addDataEditStep(ds);
3334 boolean overlapping_only = 1 == gd.getNextChoiceIndex();
3335 switch (gd.getNextChoiceIndex()) {
3336 case 0:
3337 Patch.crosslink(selection.getSelected(Patch.class), overlapping_only);
3338 break;
3339 case 1:
3340 Patch.crosslink(lay.getDisplayables(Patch.class), overlapping_only);
3341 break;
3342 case 2:
3343 for (final Layer la : lay.getParent().getLayers()) {
3344 Patch.crosslink(la.getDisplayables(Patch.class), overlapping_only);
3346 break;
3348 lay.getParent().addDataEditStep(ds);
3349 } else if (command.equals("Enhance contrast (selected images)...")) {
3350 final Layer la = layer;
3351 final HashSet<Displayable> ds = new HashSet<Displayable>(la.getParent().getDisplayables());
3352 la.getParent().addDataEditStep(ds);
3353 ArrayList al = selection.getSelected(Patch.class);
3354 Bureaucrat burro = getProject().getLoader().homogenizeContrast(al);
3355 burro.addPostTask(new Runnable() { public void run() {
3356 la.getParent().addDataEditStep(ds);
3357 }});
3358 } else if (command.equals("Enhance contrast layer-wise...")) {
3359 // ask for range of layers
3360 final GenericDialog gd = new GenericDialog("Choose range");
3361 Utils.addLayerRangeChoices(Display.this.layer, gd);
3362 gd.showDialog();
3363 if (gd.wasCanceled()) return;
3364 java.util.List list = layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1); // exclusive end
3365 Layer[] la = new Layer[list.size()];
3366 list.toArray(la);
3367 final HashSet<Displayable> ds = new HashSet<Displayable>();
3368 for (final Layer l : la) ds.addAll(l.getDisplayables(Patch.class));
3369 getLayerSet().addDataEditStep(ds);
3370 Bureaucrat burro = project.getLoader().homogenizeContrast(la);
3371 burro.addPostTask(new Runnable() { public void run() {
3372 getLayerSet().addDataEditStep(ds);
3373 }});
3374 } else if (command.equals("Set Min and Max layer-wise...")) {
3375 Displayable active = getActive();
3376 double min = 0;
3377 double max = 0;
3378 if (null != active && active.getClass() == Patch.class) {
3379 min = ((Patch)active).getMin();
3380 max = ((Patch)active).getMax();
3382 final GenericDialog gd = new GenericDialog("Min and Max");
3383 gd.addMessage("Set min and max to all images in the layer range");
3384 Utils.addLayerRangeChoices(Display.this.layer, gd);
3385 gd.addNumericField("min: ", min, 2);
3386 gd.addNumericField("max: ", max, 2);
3387 gd.showDialog();
3388 if (gd.wasCanceled()) return;
3390 min = gd.getNextNumber();
3391 max = gd.getNextNumber();
3392 ArrayList<Displayable> al = new ArrayList<Displayable>();
3393 for (final Layer la : layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1)) { // exclusive end
3394 al.addAll(la.getDisplayables(Patch.class));
3396 final HashSet<Displayable> ds = new HashSet<Displayable>(al);
3397 getLayerSet().addDataEditStep(ds);
3398 Bureaucrat burro = project.getLoader().setMinAndMax(al, min, max);
3399 burro.addPostTask(new Runnable() { public void run() {
3400 getLayerSet().addDataEditStep(ds);
3401 }});
3402 } else if (command.equals("Set Min and Max (selected images)...")) {
3403 Displayable active = getActive();
3404 double min = 0;
3405 double max = 0;
3406 if (null != active && active.getClass() == Patch.class) {
3407 min = ((Patch)active).getMin();
3408 max = ((Patch)active).getMax();
3410 final GenericDialog gd = new GenericDialog("Min and Max");
3411 gd.addMessage("Set min and max to all selected images");
3412 gd.addNumericField("min: ", min, 2);
3413 gd.addNumericField("max: ", max, 2);
3414 gd.showDialog();
3415 if (gd.wasCanceled()) return;
3417 min = gd.getNextNumber();
3418 max = gd.getNextNumber();
3419 final HashSet<Displayable> ds = new HashSet<Displayable>(selection.getSelected(Patch.class));
3420 getLayerSet().addDataEditStep(ds);
3421 Bureaucrat burro = project.getLoader().setMinAndMax(selection.getSelected(Patch.class), min, max);
3422 burro.addPostTask(new Runnable() { public void run() {
3423 getLayerSet().addDataEditStep(ds);
3424 }});
3425 } else if (command.equals("Duplicate")) {
3426 // only Patch and DLabel, i.e. Layer-only resident objects that don't exist in the Project Tree
3427 final HashSet<Class> accepted = new HashSet<Class>();
3428 accepted.add(Patch.class);
3429 accepted.add(DLabel.class);
3430 final ArrayList<Displayable> originals = new ArrayList<Displayable>();
3431 final ArrayList<Displayable> selected = selection.getSelected();
3432 for (final Displayable d : selected) {
3433 if (accepted.contains(d.getClass())) {
3434 originals.add(d);
3437 if (originals.size() > 0) {
3438 getLayerSet().addChangeTreesStep();
3439 for (final Displayable d : originals) {
3440 d.getLayer().add(d.clone());
3442 getLayerSet().addChangeTreesStep();
3443 } else if (selected.size() > 0) {
3444 Utils.log("Can only duplicate images and text labels.\nDuplicate *other* objects in the Project Tree.\n");
3446 } else if (command.equals("Create subproject")) {
3447 Roi roi = canvas.getFakeImagePlus().getRoi();
3448 if (null == roi) return; // the menu item is not active unless there is a ROI
3449 Layer first, last;
3450 if (1 == layer.getParent().size()) {
3451 first = last = layer;
3452 } else {
3453 GenericDialog gd = new GenericDialog("Choose layer range");
3454 Utils.addLayerRangeChoices(layer, gd);
3455 gd.showDialog();
3456 if (gd.wasCanceled()) return;
3457 first = layer.getParent().getLayer(gd.getNextChoiceIndex());
3458 last = layer.getParent().getLayer(gd.getNextChoiceIndex());
3459 Utils.log2("first, last: " + first + ", " + last);
3461 Project sub = getProject().createSubproject(roi.getBounds(), first, last);
3462 final LayerSet subls = sub.getRootLayerSet();
3463 final Display d = new Display(sub, subls.getLayer(0));
3464 SwingUtilities.invokeLater(new Runnable() { public void run() {
3465 d.canvas.showCentered(new Rectangle(0, 0, (int)subls.getLayerWidth(), (int)subls.getLayerHeight()));
3466 }});
3467 } else if (command.startsWith("Arealists as labels")) {
3468 GenericDialog gd = new GenericDialog("Export labels");
3469 gd.addSlider("Scale: ", 1, 100, 100);
3470 final String[] options = {"All area list", "Selected area lists"};
3471 gd.addChoice("Export: ", options, options[0]);
3472 Utils.addLayerRangeChoices(layer, gd);
3473 gd.addCheckbox("Visible only", true);
3474 gd.showDialog();
3475 if (gd.wasCanceled()) return;
3476 final float scale = (float)(gd.getNextNumber() / 100);
3477 java.util.List al = 0 == gd.getNextChoiceIndex() ? layer.getParent().getZDisplayables(AreaList.class) : selection.getSelected(AreaList.class);
3478 if (null == al) {
3479 Utils.log("No area lists found to export.");
3480 return;
3482 // 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?
3483 al = (java.util.List<Displayable>) al;
3485 int first = gd.getNextChoiceIndex();
3486 int last = gd.getNextChoiceIndex();
3487 boolean visible_only = gd.getNextBoolean();
3488 if (-1 != command.indexOf("(amira)")) {
3489 AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, true, true);
3490 } else if (-1 != command.indexOf("(tif)")) {
3491 AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, false, false);
3493 } else if (command.equals("Project properties...")) {
3494 project.adjustProperties();
3495 } else if (command.equals("Release memory...")) {
3496 Bureaucrat.createAndStart(new Worker("Releasing memory") {
3497 public void run() {
3498 startedWorking();
3499 try {
3500 GenericDialog gd = new GenericDialog("Release Memory");
3501 int max = (int)(IJ.maxMemory() / 1000000);
3502 gd.addSlider("Megabytes: ", 0, max, max/2);
3503 gd.showDialog();
3504 if (!gd.wasCanceled()) {
3505 int n_mb = (int)gd.getNextNumber();
3506 project.getLoader().releaseToFit((long)n_mb*1000000);
3508 } catch (Throwable e) {
3509 IJError.print(e);
3510 } finally {
3511 finishedWorking();
3514 }, project);
3515 } else if (command.equals("Flush image cache")) {
3516 Loader.releaseAllCaches();
3517 } else if (command.equals("Regenerate all mipmaps")) {
3518 for (final Displayable d : getLayerSet().getDisplayables(Patch.class)) {
3519 d.getProject().getLoader().regenerateMipMaps((Patch) d);
3521 } else {
3522 Utils.log2("Display: don't know what to do with command " + command);
3524 }});
3527 /** Update in all displays the Transform for the given Displayable if it's selected. */
3528 static public void updateTransform(final Displayable displ) {
3529 for (final Display d : al_displays) {
3530 if (d.selection.contains(displ)) d.selection.updateTransform(displ);
3534 /** Order the profiles of the parent profile_list by Z order, and fix the ProjectTree.*/
3536 private void fixZOrdering(Profile profile) {
3537 ProjectThing thing = project.findProjectThing(profile);
3538 if (null == thing) {
3539 Utils.log2("Display.fixZOrdering: null thing?");
3540 return;
3542 ((ProjectThing)thing.getParent()).fixZOrdering();
3543 project.getProjectTree().updateList(thing.getParent());
3547 /** The number of layers to scroll through with the wheel; 1 by default.*/
3548 public int getScrollStep() { return this.scroll_step; }
3550 public void setScrollStep(int scroll_step) {
3551 if (scroll_step < 1) scroll_step = 1;
3552 this.scroll_step = scroll_step;
3553 updateInDatabase("scroll_step");
3556 protected Bureaucrat importImage() {
3557 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
3558 public void run() {
3559 startedWorking();
3560 try {
3563 Rectangle srcRect = canvas.getSrcRect();
3564 int x = srcRect.x + srcRect.width / 2;
3565 int y = srcRect.y + srcRect.height/ 2;
3566 Patch p = project.getLoader().importImage(project, x, y);
3567 if (null == p) {
3568 finishedWorking();
3569 Utils.showMessage("Could not open the image.");
3570 return;
3573 Display.this.getLayerSet().addLayerContentStep(layer);
3575 layer.add(p); // will add it to the proper Displays
3577 Display.this.getLayerSet().addLayerContentStep(layer);
3580 } catch (Exception e) {
3581 IJError.print(e);
3583 finishedWorking();
3586 return Bureaucrat.createAndStart(worker, getProject());
3589 protected Bureaucrat importNextImage() {
3590 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
3591 public void run() {
3592 startedWorking();
3593 try {
3595 Rectangle srcRect = canvas.getSrcRect();
3596 int x = srcRect.x + srcRect.width / 2;// - imp.getWidth() / 2;
3597 int y = srcRect.y + srcRect.height/ 2;// - imp.getHeight()/ 2;
3598 Patch p = project.getLoader().importNextImage(project, x, y);
3599 if (null == p) {
3600 Utils.showMessage("Could not open next image.");
3601 finishedWorking();
3602 return;
3605 Display.this.getLayerSet().addLayerContentStep(layer);
3607 layer.add(p); // will add it to the proper Displays
3609 Display.this.getLayerSet().addLayerContentStep(layer);
3611 } catch (Exception e) {
3612 IJError.print(e);
3614 finishedWorking();
3617 return Bureaucrat.createAndStart(worker, getProject());
3621 /** Make the given channel have the given alpha (transparency). */
3622 public void setChannel(int c, float alpha) {
3623 int a = (int)(255 * alpha);
3624 int l = (c_alphas&0xff000000)>>24;
3625 int r = (c_alphas&0xff0000)>>16;
3626 int g = (c_alphas&0xff00)>>8;
3627 int b = c_alphas&0xff;
3628 switch (c) {
3629 case Channel.MONO:
3630 // all to the given alpha
3631 c_alphas = (l<<24) + (r<<16) + (g<<8) + b; // parenthesis are NECESSARY
3632 break;
3633 case Channel.RED:
3634 // modify only the red
3635 c_alphas = (l<<24) + (a<<16) + (g<<8) + b;
3636 break;
3637 case Channel.GREEN:
3638 c_alphas = (l<<24) + (r<<16) + (a<<8) + b;
3639 break;
3640 case Channel.BLUE:
3641 c_alphas = (l<<24) + (r<<16) + (g<<8) + a;
3642 break;
3644 //Utils.log2("c_alphas: " + c_alphas);
3645 //canvas.setUpdateGraphics(true);
3646 canvas.repaint(true);
3647 updateInDatabase("c_alphas");
3650 /** Set the channel as active and the others as inactive. */
3651 public void setActiveChannel(Channel channel) {
3652 for (int i=0; i<4; i++) {
3653 if (channel != channels[i]) channels[i].setActive(false);
3654 else channel.setActive(true);
3656 Utils.updateComponent(panel_channels);
3657 transp_slider.setValue((int)(channel.getAlpha() * 100));
3660 public int getDisplayChannelAlphas() { return c_alphas; }
3662 // rename this method and the getDisplayChannelAlphas ! They sound the same!
3663 public int getChannelAlphas() {
3664 return ((int)(channels[0].getAlpha() * 255)<<24) + ((int)(channels[1].getAlpha() * 255)<<16) + ((int)(channels[2].getAlpha() * 255)<<8) + (int)(channels[3].getAlpha() * 255);
3667 public int getChannelAlphasState() {
3668 return ((channels[0].isSelected() ? 255 : 0)<<24)
3669 + ((channels[1].isSelected() ? 255 : 0)<<16)
3670 + ((channels[2].isSelected() ? 255 : 0)<<8)
3671 + (channels[3].isSelected() ? 255 : 0);
3674 /** Show the layer in the front Display, or in a new Display if the front Display is showing a layer from a different LayerSet. */
3675 static public void showFront(final Layer layer) {
3676 Display display = front;
3677 if (null == display || display.layer.getParent() != layer.getParent()) {
3678 display = new Display(layer.getProject(), layer, null); // gets set to front
3679 } else {
3680 display.setLayer(layer);
3684 /** Show the given Displayable centered and selected. If select is false, the selection is cleared. */
3685 static public void showCentered(Layer layer, Displayable displ, boolean select, boolean shift_down) {
3686 // see if the given layer belongs to the layer set being displayed
3687 Display display = front; // to ensure thread consistency to some extent
3688 if (null == display || display.layer.getParent() != layer.getParent()) {
3689 display = new Display(layer.getProject(), layer, displ); // gets set to front
3690 } else if (display.layer != layer) {
3691 display.setLayer(layer);
3693 if (select) {
3694 if (!shift_down) display.selection.clear();
3695 display.selection.add(displ);
3696 } else {
3697 display.selection.clear();
3699 display.showCentered(displ);
3702 private final void showCentered(final Displayable displ) {
3703 if (null == displ) return;
3704 SwingUtilities.invokeLater(new Runnable() { public void run() {
3705 displ.setVisible(true);
3706 Rectangle box = displ.getBoundingBox();
3707 if (0 == box.width || 0 == box.height) {
3708 box.width = (int)layer.getLayerWidth();
3709 box.height = (int)layer.getLayerHeight();
3711 canvas.showCentered(box);
3712 scrollToShow(displ);
3713 if (displ instanceof ZDisplayable) {
3714 // scroll to first layer that has a point
3715 ZDisplayable zd = (ZDisplayable)displ;
3716 setLayer(zd.getFirstLayer());
3718 }});
3721 /** Listen to interesting updates, such as the ColorPicker and updates to Patch objects. */
3722 public void imageUpdated(ImagePlus updated) {
3723 // detect ColorPicker WARNING this will work even if the Display is not the window immediately active under the color picker.
3724 if (this == front && updated instanceof ij.plugin.ColorPicker) {
3725 if (null != active && project.isInputEnabled()) {
3726 selection.setColor(Toolbar.getForegroundColor());
3727 Display.repaint(front.layer, selection.getBox(), 0);
3729 return;
3731 // $%#@!! LUT changes don't set the image as changed
3732 //if (updated instanceof PatchStack) {
3733 // updated.changes = 1
3736 //Utils.log2("imageUpdated: " + updated + " " + updated.getClass());
3738 /* // never gets called (?)
3739 // the above is overkill. Instead:
3740 if (updated instanceof PatchStack) {
3741 Patch p = ((PatchStack)updated).getCurrentPatch();
3742 ImageProcessor ip = updated.getProcessor();
3743 p.setMinAndMax(ip.getMin(), ip.getMax());
3744 Utils.log2("setting min and max: " + ip.getMin() + ", " + ip.getMax());
3745 project.getLoader().decacheAWT(p.getId()); // including level 0, which will be editable
3746 // on repaint, it will be recreated
3747 //((PatchStack)updated).decacheAll(); // so that it will repaint with a newly created image
3751 // detect LUT changes: DONE at PatchStack, which is the active (virtual) image
3752 //Utils.log2("calling decache for " + updated);
3753 //getProject().getLoader().decache(updated);
3756 public void imageClosed(ImagePlus imp) {}
3757 public void imageOpened(ImagePlus imp) {}
3759 /** Release memory captured by the offscreen images */
3760 static public void flushAll() {
3761 for (final Display d : al_displays) {
3762 d.canvas.flush();
3764 //System.gc();
3765 Thread.yield();
3768 /** Can be null. */
3769 static public Display getFront() {
3770 return front;
3773 static public void setCursorToAll(final Cursor c) {
3774 for (final Display d : al_displays) {
3775 d.frame.setCursor(c);
3779 protected void setCursor(Cursor c) {
3780 frame.setCursor(c);
3783 /** Used by the Displayable to update the visibility checkbox in other Displays. */
3784 static protected void updateVisibilityCheckbox(final Layer layer, final Displayable displ, final Display calling_display) {
3785 //LOCKS ALL //SwingUtilities.invokeLater(new Runnable() { public void run() {
3786 for (final Display d : al_displays) {
3787 if (d == calling_display) continue;
3788 if (d.layer.contains(displ) || (displ instanceof ZDisplayable && d.layer.getParent().contains((ZDisplayable)displ))) {
3789 DisplayablePanel dp = d.ht_panels.get(displ);
3790 if (null != dp) dp.updateVisibilityCheckbox();
3793 //}});
3796 protected boolean isActiveWindow() {
3797 return frame.isActive();
3800 /** Toggle user input; pan and zoom are always enabled though.*/
3801 static public void setReceivesInput(final Project project, final boolean b) {
3802 for (final Display d : al_displays) {
3803 if (d.project == project) d.canvas.setReceivesInput(b);
3807 /** Export the DTD that defines this object. */
3808 static public void exportDTD(StringBuffer sb_header, HashSet hs, String indent) {
3809 if (hs.contains("t2_display")) return; // TODO to avoid collisions the type shoud be in a namespace such as tm2:display
3810 hs.add("t2_display");
3811 sb_header.append(indent).append("<!ELEMENT t2_display EMPTY>\n")
3812 .append(indent).append("<!ATTLIST t2_display id NMTOKEN #REQUIRED>\n")
3813 .append(indent).append("<!ATTLIST t2_display layer_id NMTOKEN #REQUIRED>\n")
3814 .append(indent).append("<!ATTLIST t2_display x NMTOKEN #REQUIRED>\n")
3815 .append(indent).append("<!ATTLIST t2_display y NMTOKEN #REQUIRED>\n")
3816 .append(indent).append("<!ATTLIST t2_display magnification NMTOKEN #REQUIRED>\n")
3817 .append(indent).append("<!ATTLIST t2_display srcrect_x NMTOKEN #REQUIRED>\n")
3818 .append(indent).append("<!ATTLIST t2_display srcrect_y NMTOKEN #REQUIRED>\n")
3819 .append(indent).append("<!ATTLIST t2_display srcrect_width NMTOKEN #REQUIRED>\n")
3820 .append(indent).append("<!ATTLIST t2_display srcrect_height NMTOKEN #REQUIRED>\n")
3821 .append(indent).append("<!ATTLIST t2_display scroll_step NMTOKEN #REQUIRED>\n")
3822 .append(indent).append("<!ATTLIST t2_display c_alphas NMTOKEN #REQUIRED>\n")
3823 .append(indent).append("<!ATTLIST t2_display c_alphas_state NMTOKEN #REQUIRED>\n")
3826 /** Export all displays of the given project as XML entries. */
3827 static public void exportXML(final Project project, final Writer writer, final String indent, final Object any) throws Exception {
3828 final StringBuffer sb_body = new StringBuffer();
3829 final String in = indent + "\t";
3830 for (final Display d : al_displays) {
3831 if (d.project != project) continue;
3832 final Rectangle r = d.frame.getBounds();
3833 final Rectangle srcRect = d.canvas.getSrcRect();
3834 final double magnification = d.canvas.getMagnification();
3835 sb_body.append(indent).append("<t2_display id=\"").append(d.id).append("\"\n")
3836 .append(in).append("layer_id=\"").append(d.layer.getId()).append("\"\n")
3837 .append(in).append("c_alphas=\"").append(d.c_alphas).append("\"\n")
3838 .append(in).append("c_alphas_state=\"").append(d.getChannelAlphasState()).append("\"\n")
3839 .append(in).append("x=\"").append(r.x).append("\"\n")
3840 .append(in).append("y=\"").append(r.y).append("\"\n")
3841 .append(in).append("magnification=\"").append(magnification).append("\"\n")
3842 .append(in).append("srcrect_x=\"").append(srcRect.x).append("\"\n")
3843 .append(in).append("srcrect_y=\"").append(srcRect.y).append("\"\n")
3844 .append(in).append("srcrect_width=\"").append(srcRect.width).append("\"\n")
3845 .append(in).append("srcrect_height=\"").append(srcRect.height).append("\"\n")
3846 .append(in).append("scroll_step=\"").append(d.scroll_step).append("\"\n")
3848 sb_body.append(indent).append("/>\n");
3850 writer.write(sb_body.toString());
3853 static public void toolChanged(final String tool_name) {
3854 Utils.log2("tool name: " + tool_name);
3855 if (!tool_name.equals("ALIGN")) {
3856 for (final Display d : al_displays) {
3857 d.layer.getParent().cancelAlign();
3862 static public void toolChanged(final int tool) {
3863 //Utils.log2("int tool is " + tool);
3864 if (ProjectToolbar.PEN == tool) {
3865 // erase bounding boxes
3866 for (final Display d : al_displays) {
3867 if (null != d.active) d.repaint(d.layer, d.selection.getBox(), 2);
3870 if (null != front) {
3871 WindowManager.setTempCurrentImage(front.canvas.getFakeImagePlus());
3875 public Selection getSelection() {
3876 return selection;
3879 public boolean isSelected(Displayable d) {
3880 return selection.contains(d);
3883 static public void updateSelection() {
3884 Display.updateSelection(null);
3886 static public void updateSelection(final Display calling) {
3887 final HashSet hs = new HashSet();
3888 for (final Display d : al_displays) {
3889 if (hs.contains(d.layer)) continue;
3890 hs.add(d.layer);
3891 if (null == d || null == d.selection) {
3892 Utils.log2("d is : "+ d + " d.selection is " + d.selection);
3893 } else {
3894 d.selection.update(); // recomputes box
3896 if (d != calling) { // TODO this is so dirty!
3897 if (d.selection.getNLinked() > 1) d.canvas.setUpdateGraphics(true); // this is overkill anyway
3898 d.canvas.repaint(d.selection.getLinkedBox(), Selection.PADDING);
3899 d.navigator.repaint(true); // everything
3904 static public void clearSelection(final Layer layer) {
3905 for (final Display d : al_displays) {
3906 if (d.layer == layer) d.selection.clear();
3909 static public void clearSelection() {
3910 for (final Display d : al_displays) {
3911 d.selection.clear();
3915 private void setTempCurrentImage() {
3916 WindowManager.setTempCurrentImage(canvas.getFakeImagePlus());
3919 /** Check if any display will paint the given Displayable at the given magnification. */
3920 static public boolean willPaint(final Displayable displ, final double magnification) {
3921 Rectangle box = null; ;
3922 for (final Display d : al_displays) {
3923 /* // Can no longer do this check, because 'magnification' is now affected by the Displayable AffineTransform! And thus it would not paint after the prePaint.
3924 if (Math.abs(d.canvas.getMagnification() - magnification) > 0.00000001) {
3925 continue;
3928 if (null == box) box = displ.getBoundingBox(null);
3929 if (d.canvas.getSrcRect().intersects(box)) {
3930 return true;
3933 return false;
3936 public void hideDeselected(final boolean not_images) {
3937 // hide deselected
3938 final ArrayList all = layer.getParent().getZDisplayables(); // a copy
3939 all.addAll(layer.getDisplayables());
3940 all.removeAll(selection.getSelected());
3941 if (not_images) all.removeAll(layer.getDisplayables(Patch.class));
3942 for (final Displayable d : (ArrayList<Displayable>)all) {
3943 if (d.isVisible()) d.setVisible(false);
3945 Display.update(layer);
3948 /** Cleanup internal lists that may contain the given Displayable. */
3949 static public void flush(final Displayable displ) {
3950 for (final Display d : al_displays) {
3951 d.selection.removeFromPrev(displ);
3955 public void resizeCanvas() {
3956 GenericDialog gd = new GenericDialog("Resize LayerSet");
3957 gd.addNumericField("new width: ", layer.getLayerWidth(), 3);
3958 gd.addNumericField("new height: ",layer.getLayerHeight(),3);
3959 gd.addChoice("Anchor: ", LayerSet.ANCHORS, LayerSet.ANCHORS[7]);
3960 gd.showDialog();
3961 if (gd.wasCanceled()) return;
3962 double new_width = gd.getNextNumber();
3963 double new_height =gd.getNextNumber();
3964 layer.getParent().setDimensions(new_width, new_height, gd.getNextChoiceIndex()); // will complain and prevent cropping existing Displayable objects
3968 // To record layer changes -- but it's annoying, this is visualization not data.
3969 static class DoSetLayer implements DoStep {
3970 final Display display;
3971 final Layer layer;
3972 DoSetLayer(final Display display) {
3973 this.display = display;
3974 this.layer = display.layer;
3976 public Displayable getD() { return null; }
3977 public boolean isEmpty() { return false; }
3978 public boolean apply(final int action) {
3979 display.setLayer(layer);
3981 public boolean isIdenticalTo(final Object ob) {
3982 if (!ob instanceof DoSetLayer) return false;
3983 final DoSetLayer dsl = (DoSetLayer) ob;
3984 return dsl.display == this.display && dsl.layer == this.layer;
3989 protected void duplicateLinkAndSendTo(final Displayable active, final int position, final Layer other_layer) {
3990 if (null == active || !(active instanceof Profile)) return;
3991 if (active.getLayer() == other_layer) return; // can't do that!
3992 Profile profile = project.getProjectTree().duplicateChild((Profile)active, position, other_layer);
3993 if (null == profile) return;
3994 active.link(profile);
3995 slt.setAndWait(other_layer);
3996 other_layer.add(profile);
3997 selection.add(profile);
4000 private final HashMap<Color,Layer> layer_channels = new HashMap<Color,Layer>();
4001 private final TreeMap<Integer,LayerPanel> layer_alpha = new TreeMap<Integer,LayerPanel>();
4003 /** Remove all red/blue coloring of layers, and repaint canvas. */
4004 protected void resetLayerColors() {
4005 synchronized (layer_channels) {
4006 for (final Layer l : new ArrayList<Layer>(layer_channels.values())) { // avoid concurrent modification exception
4007 final LayerPanel lp = layer_panels.get(l);
4008 lp.setColor(Color.white);
4009 setColorChannel(lp.layer, Color.white);
4010 lp.slider.setEnabled(true);
4012 layer_channels.clear();
4014 canvas.repaint();
4017 /** Set all layer alphas to zero, and repaint canvas. */
4018 protected void resetLayerAlphas() {
4019 synchronized (layer_channels) {
4020 for (final LayerPanel lp : new ArrayList<LayerPanel>(layer_alpha.values())) {
4021 lp.setAlpha(0);
4023 layer_alpha.clear(); // should have already been cleared
4025 canvas.repaint();
4028 /** Add to layer_alpha table, or remove if alpha is zero. */
4029 protected void storeLayerAlpha(final LayerPanel lp, final float a) {
4030 synchronized (layer_channels) {
4031 if (M.equals(0, a)) {
4032 layer_alpha.remove(lp.layer.getParent().indexOf(lp.layer));
4033 } else {
4034 layer_alpha.put(lp.layer.getParent().indexOf(lp.layer), lp);
4039 static protected final int REPAINT_SINGLE_LAYER = 0;
4040 static protected final int REPAINT_MULTI_LAYER = 1;
4041 static protected final int REPAINT_RGB_LAYER = 2;
4043 /** Sets the values atomically, returns the painting mode. */
4044 protected int getPaintMode(final HashMap<Color,Layer> hm, final ArrayList<LayerPanel> list) {
4045 synchronized (layer_channels) {
4046 if (layer_channels.size() > 0) {
4047 hm.putAll(layer_channels);
4048 hm.put(Color.green, this.layer);
4049 return REPAINT_RGB_LAYER;
4051 list.addAll(layer_alpha.values());
4052 final int len = list.size();
4053 if (len > 1) return REPAINT_MULTI_LAYER;
4054 if (1 == len) {
4055 if (list.get(0).layer == this.layer) return REPAINT_SINGLE_LAYER; // normal mode
4056 return REPAINT_MULTI_LAYER;
4058 return REPAINT_SINGLE_LAYER;
4062 /** Set a layer to be painted as a specific color channel in the canvas.
4063 * Only Color.red and Color.blue are accepted.
4064 * Color.green is reserved for the current layer. */
4065 protected void setColorChannel(final Layer layer, final Color color) {
4066 synchronized (layer_channels) {
4067 if (Color.white == color) {
4068 // Remove
4069 for (final Iterator<Layer> it = layer_channels.values().iterator(); it.hasNext(); ) {
4070 if (it.next() == layer) {
4071 it.remove();
4072 break;
4075 canvas.repaint();
4076 } else if (Color.red == color || Color.blue == color) {
4077 // Reset current of that color, if any, to white
4078 final Layer l = layer_channels.remove(color);
4079 if (null != l) layer_panels.get(l).setColor(Color.white);
4080 // Replace or set new
4081 layer_channels.put(color, layer);
4082 tabs.repaint();
4083 canvas.repaint();
4084 } else {
4085 Utils.log2("Trying to set unacceptable color for layer " + layer + " : " + color);
4087 // enable/disable sliders
4088 final boolean b = 0 == layer_channels.size();
4089 for (final LayerPanel lp : layer_panels.values()) lp.slider.setEnabled(b);
4091 this.canvas.repaint(true);
4094 static public final void updateComponentTreeUI() {
4095 try {
4096 for (final Display d : al_displays) SwingUtilities.updateComponentTreeUI(d.frame);
4097 } catch (Exception e) {
4098 IJError.print(e);