Fixed makeGUI: recreateLayerPanels cannot depend on Display.this.Layer
[trakem2.git] / ini / trakem2 / display / Display.java
blobaae2210e74d83350b40212cc5726dd630098dd1c
1 /**
3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2005, 2006 Albert Cardona and Rodney Douglas.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation (http://www.gnu.org/licenses/gpl.txt )
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 You may contact Albert Cardona at acardona at ini.phys.ethz.ch
20 Institute of Neuroinformatics, University of Zurich / ETH, Switzerland.
21 **/
23 package ini.trakem2.display;
25 import ij.*;
26 import ij.gui.*;
27 import ij.measure.Calibration;
28 import ini.trakem2.Project;
29 import ini.trakem2.ControlWindow;
30 import ini.trakem2.persistence.DBObject;
31 import ini.trakem2.persistence.Loader;
32 import ini.trakem2.utils.IJError;
33 import ini.trakem2.imaging.PatchStack;
34 import ini.trakem2.imaging.Registration;
35 import ini.trakem2.imaging.StitchingTEM;
36 import ini.trakem2.imaging.Blending;
37 import ini.trakem2.utils.ProjectToolbar;
38 import ini.trakem2.utils.Utils;
39 import ini.trakem2.utils.DNDInsertImage;
40 import ini.trakem2.utils.Search;
41 import ini.trakem2.utils.Bureaucrat;
42 import ini.trakem2.utils.Worker;
43 import ini.trakem2.utils.Dispatcher;
44 import ini.trakem2.utils.Lock;
45 import ini.trakem2.utils.M;
46 import ini.trakem2.tree.*;
48 import javax.swing.*;
49 import javax.swing.event.*;
51 import mpicbg.trakem2.align.AlignTask;
53 import java.awt.*;
54 import java.awt.event.*;
55 import java.util.*;
56 import java.lang.reflect.Method;
57 import java.io.Writer;
59 import lenscorrection.DistortionCorrectionTask;
61 /** A Display is a class to show a Layer and enable mouse and keyboard manipulation of all its components. */
62 public final class Display extends DBObject implements ActionListener, ImageListener {
64 /** The Layer this Display is showing. */
65 private Layer layer;
67 private Displayable active = null;
68 /** All selected Displayable objects, including the active one. */
69 final private Selection selection = new Selection(this);
71 private ImagePlus last_temp = null;
73 private JFrame frame;
74 private JTabbedPane tabs;
75 private Hashtable<Class,JScrollPane> ht_tabs;
76 private JScrollPane scroll_patches;
77 private JPanel panel_patches;
78 private JScrollPane scroll_profiles;
79 private JPanel panel_profiles;
80 private JScrollPane scroll_zdispl;
81 private JPanel panel_zdispl;
82 private JScrollPane scroll_channels;
83 private JPanel panel_channels;
84 private JScrollPane scroll_labels;
85 private JPanel panel_labels;
87 private JPanel panel_layers;
88 private JScrollPane scroll_layers;
89 private Hashtable<Layer,LayerPanel> layer_panels = new Hashtable<Layer,LayerPanel>();
91 private JSlider transp_slider;
92 private DisplayNavigator navigator;
93 private JScrollBar scroller;
95 private DisplayCanvas canvas; // WARNING this is an AWT component, since it extends ImageCanvas
96 private JPanel canvas_panel; // and this is a workaround, to better (perhaps) integrate the awt canvas inside a JSplitPane
97 private JSplitPane split;
99 private JPopupMenu popup = null;
101 /** Contains the packed alphas of every channel. */
102 private int c_alphas = 0xffffffff; // all 100 % visible
103 private Channel[] channels;
105 private Hashtable<Displayable,DisplayablePanel> ht_panels = new Hashtable<Displayable,DisplayablePanel>();
107 /** Handle drop events, to insert image files. */
108 private DNDInsertImage dnd;
110 private boolean size_adjusted = false;
112 private int scroll_step = 1;
114 /** Keep track of all existing Display objects. */
115 static private ArrayList<Display> al_displays = new ArrayList<Display>();
116 /** The currently focused Display, if any. */
117 static private Display front = null;
119 /** Displays to open when all objects have been reloaded from the database. */
120 static private final Hashtable ht_later = new Hashtable();
122 /** A thread to handle user actions, for example an event sent from a popup menu. */
123 private final Dispatcher dispatcher = new Dispatcher();
125 static private WindowAdapter window_listener = new WindowAdapter() {
126 /** Unregister the closed Display. */
127 public void windowClosing(WindowEvent we) {
128 final Object source = we.getSource();
129 for (Iterator it = al_displays.iterator(); it.hasNext(); ) {
130 Display d = (Display)it.next();
131 if (source == d.frame) {
132 it.remove();
133 if (d == front) front = null;
134 d.remove(false); //calls destroy
135 break;
139 /** Set the source Display as front. */
140 public void windowActivated(WindowEvent we) {
141 // find which was it to make it be the front
142 final Object source = we.getSource();
143 for (final Display d : al_displays) {
144 if (source == d.frame) {
145 front = d;
146 // set toolbar
147 ProjectToolbar.setProjectToolbar();
148 // now, select the layer in the LayerTree
149 front.getProject().select(front.layer);
150 // finally, set the virtual ImagePlus that ImageJ will see
151 d.setTempCurrentImage();
152 // copied from ij.gui.ImageWindow, with modifications
153 if (IJ.isMacintosh() && IJ.getInstance()!=null) {
154 IJ.wait(10); // may be needed for Java 1.4 on OS X
155 d.frame.setMenuBar(ij.Menus.getMenuBar());
157 return;
160 // else, restore the ImageJ toolbar for non-project images
161 //if (!source.equals(IJ.getInstance())) {
162 // ProjectToolbar.setImageJToolbar();
165 /** Restore the ImageJ toolbar */
166 public void windowDeactivated(WindowEvent we) {
167 // Can't, the user can never click the ProjectToolbar then. This has to be done in a different way, for example checking who is the WindowManager.getCurrentImage (and maybe setting a dummy image into it) //ProjectToolbar.setImageJToolbar();
169 /** Call a pack() when the window is maximized to fit the canvas correctly. */
170 public void windowStateChanged(WindowEvent we) {
171 final Object source = we.getSource();
172 for (final Display d : al_displays) {
173 if (source != d.frame) continue;
174 d.pack();
175 break;
180 static private MouseListener frame_mouse_listener = new MouseAdapter() {
181 public void mouseReleased(MouseEvent me) {
182 Object source = me.getSource();
183 for (final Display d : al_displays) {
184 if (d.frame == source) {
185 if (d.size_adjusted) {
186 d.pack();
187 d.size_adjusted = false;
188 Utils.log2("mouse released on JFrame");
190 break;
196 private int last_frame_state = frame.NORMAL;
198 // THIS WHOLE SYSTEM OF LISTENERS IS BROKEN:
199 // * when zooming in, the window growths in width a few pixels.
200 // * when enlarging the window quickly, the canvas is not resized as large as it should.
201 // -- the whole problem: swing threading, which I am not handling properly. It's hard.
202 static private ComponentListener component_listener = new ComponentAdapter() {
203 public void componentResized(ComponentEvent ce) {
204 final Display d = getDisplaySource(ce);
205 if (null != d) {
206 d.size_adjusted = true; // works in combination with mouseReleased to call pack(), avoiding infinite loops.
207 d.adjustCanvas();
208 int frame_state = d.frame.getExtendedState();
209 if (frame_state != d.last_frame_state) { // this setup avoids infinite loops (for pack() calls componentResized as well
210 d.last_frame_state = frame_state;
211 if (d.frame.ICONIFIED != frame_state) d.pack();
215 public void componentMoved(ComponentEvent ce) {
216 Display d = getDisplaySource(ce);
217 if (null != d) d.updateInDatabase("position");
219 private Display getDisplaySource(ComponentEvent ce) {
220 final Object source = ce.getSource();
221 for (final Display d : al_displays) {
222 if (source == d.frame) {
223 return d;
226 return null;
230 static private ChangeListener tabs_listener = new ChangeListener() {
231 /** Listen to tab changes. */
232 public void stateChanged(final ChangeEvent ce) {
233 final Object source = ce.getSource();
234 for (final Display d : al_displays) {
235 if (source == d.tabs) {
236 d.dispatcher.exec(new Runnable() { public void run() {
237 // creating tabs fires the event!!!
238 if (null == d.frame || null == d.canvas) return;
239 final Container tab = (Container)d.tabs.getSelectedComponent();
240 if (tab == d.scroll_channels) {
241 // find active channel if any
242 for (int i=0; i<d.channels.length; i++) {
243 if (d.channels[i].isActive()) {
244 d.transp_slider.setValue((int)(d.channels[i].getAlpha() * 100));
245 break;
248 } else {
249 // recreate contents
251 int count = tab.getComponentCount();
252 if (0 == count || (1 == count && tab.getComponent(0).getClass().equals(JLabel.class))) {
253 */ // ALWAYS, because it could be the case that the user changes layer while on one specific tab, and then clicks on the other tab which may not be empty and shows totally the wrong contents (i.e. for another layer)
255 String label = null;
256 ArrayList al = null;
257 JPanel p = null;
258 if (tab == d.scroll_zdispl) {
259 label = "Z-space objects";
260 al = d.layer.getParent().getZDisplayables();
261 p = d.panel_zdispl;
262 } else if (tab == d.scroll_patches) {
263 label = "Patches";
264 al = d.layer.getDisplayables(Patch.class);
265 p = d.panel_patches;
266 } else if (tab == d.scroll_labels) {
267 label = "Labels";
268 al = d.layer.getDisplayables(DLabel.class);
269 p = d.panel_labels;
270 } else if (tab == d.scroll_profiles) {
271 label = "Profiles";
272 al = d.layer.getDisplayables(Profile.class);
273 p = d.panel_profiles;
274 } else if (tab == d.scroll_layers) {
275 // nothing to do
276 return;
279 d.updateTab(p, label, al);
280 //Utils.updateComponent(d.tabs.getSelectedComponent());
281 //Utils.log2("updated tab: " + p + " with " + al.size() + " objects.");
284 if (null != d.active) {
285 // set the transp slider to the alpha value of the active Displayable if any
286 d.transp_slider.setValue((int)(d.active.getAlpha() * 100));
287 DisplayablePanel dp = d.ht_panels.get(d.active);
288 if (null != dp) dp.setActive(true);
291 }});
292 break;
298 private final ScrollLayerListener scroller_listener = new ScrollLayerListener();
300 private class ScrollLayerListener implements AdjustmentListener {
302 public void adjustmentValueChanged(final AdjustmentEvent ae) {
303 final int index = scroller.getValue();
304 slt.set(layer.getParent().getLayer(index));
308 private final SetLayerThread slt = new SetLayerThread();
310 private class SetLayerThread extends Thread {
312 private boolean go = true;
313 private Layer layer;
314 private final Lock lock = new Lock();
315 private final Lock lock2 = new Lock();
317 SetLayerThread() {
318 setPriority(Thread.NORM_PRIORITY);
319 setDaemon(true);
320 start();
323 public final void set(final Layer layer) {
324 synchronized (lock) {
325 this.layer = layer;
327 synchronized (this) {
328 notify();
332 public final void setAndWait(final Layer layer) {
333 lock2.lock();
334 set(layer);
337 public void run() {
338 while (go) {
339 while (null == this.layer) {
340 synchronized (this) {
341 try { wait(); } catch (InterruptedException ie) {}
344 Layer layer = null;
345 synchronized (lock) {
346 layer = this.layer;
347 this.layer = null;
350 if (!go) return; // after nullifying layer
352 if (null != layer) {
353 Display.this.setLayer(layer);
354 Display.this.updateInDatabase("layer_id");
356 // unlock any calls waiting on setAndWait
357 synchronized (lock2) {
358 lock2.unlock();
361 // cleanup:
362 synchronized (lock2) {
363 lock2.unlock();
367 public void waitForLayer() {
368 while (null != layer && go) {
369 try { Thread.sleep(10); } catch (Exception e) {}
373 public void quit() {
374 go = false;
378 /** Creates a new Display with adjusted magnification to fit in the screen. */
379 static public void createDisplay(final Project project, final Layer layer) {
380 SwingUtilities.invokeLater(new Runnable() { public void run() {
381 Display display = new Display(project, layer);
382 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
383 Rectangle srcRect = new Rectangle(0, 0, (int)layer.getLayerWidth(), (int)layer.getLayerHeight());
384 double mag = screen.width / layer.getLayerWidth();
385 if (mag * layer.getLayerHeight() > screen.height) mag = screen.height / layer.getLayerHeight();
386 mag = display.canvas.getLowerZoomLevel2(mag);
387 if (mag > 1.0) mag = 1.0;
388 //display.getCanvas().setup(mag, srcRect); // would call pack() at the wrong time!
389 // ... so instead: manually
390 display.getCanvas().setMagnification(mag);
391 display.getCanvas().setSrcRect(srcRect.x, srcRect.y, srcRect.width, srcRect.height);
392 display.getCanvas().setDrawingSize((int)Math.ceil(srcRect.width * mag), (int)Math.ceil(srcRect.height * mag));
394 display.updateTitle();
395 ij.gui.GUI.center(display.frame);
396 display.frame.pack();
397 }});
400 /** A new Display from scratch, to show the given Layer. */
401 public Display(Project project, final Layer layer) {
402 super(project);
403 front = this;
404 makeGUI(layer, null);
405 ImagePlus.addImageListener(this);
406 setLayer(layer);
407 this.layer = layer; // after, or it doesn't update properly
408 al_displays.add(this);
409 addToDatabase();
412 /** For reconstruction purposes. The Display will be stored in the ht_later.*/
413 public Display(Project project, long id, Layer layer, Object[] props) {
414 super(project, id);
415 synchronized (ht_later) {
416 Display.ht_later.put(this, props);
418 this.layer = layer;
421 /** Open a new Display centered around the given Displayable. */
422 public Display(Project project, Layer layer, Displayable displ) {
423 super(project);
424 front = this;
425 active = displ;
426 makeGUI(layer, null);
427 ImagePlus.addImageListener(this);
428 setLayer(layer);
429 this.layer = layer; // after set layer!
430 al_displays.add(this);
431 addToDatabase();
434 /** Reconstruct a Display from an XML entry, to be opened when everything is ready. */
435 public Display(Project project, long id, Layer layer, HashMap ht_attributes) {
436 super(project, id);
437 if (null == layer) {
438 Utils.log2("Display: need a non-null Layer for id=" + id);
439 return;
441 Rectangle srcRect = new Rectangle(0, 0, (int)layer.getLayerWidth(), (int)layer.getLayerHeight());
442 double magnification = 0.25;
443 Point p = new Point(0, 0);
444 int c_alphas = 0xffffffff;
445 int c_alphas_state = 0xffffffff;
446 for (Iterator it = ht_attributes.entrySet().iterator(); it.hasNext(); ) {
447 Map.Entry entry = (Map.Entry)it.next();
448 String key = (String)entry.getKey();
449 String data = (String)entry.getValue();
450 if (key.equals("srcrect_x")) { // reflection! Reflection!
451 srcRect.x = Integer.parseInt(data);
452 } else if (key.equals("srcrect_y")) {
453 srcRect.y = Integer.parseInt(data);
454 } else if (key.equals("srcrect_width")) {
455 srcRect.width = Integer.parseInt(data);
456 } else if (key.equals("srcrect_height")) {
457 srcRect.height = Integer.parseInt(data);
458 } else if (key.equals("magnification")) {
459 magnification = Double.parseDouble(data);
460 } else if (key.equals("x")) {
461 p.x = Integer.parseInt(data);
462 } else if (key.equals("y")) {
463 p.y = Integer.parseInt(data);
464 } else if (key.equals("c_alphas")) {
465 try {
466 c_alphas = Integer.parseInt(data);
467 } catch (Exception ex) {
468 c_alphas = 0xffffffff;
470 } else if (key.equals("c_alphas_state")) {
471 try {
472 c_alphas_state = Integer.parseInt(data);
473 } catch (Exception ex) {
474 IJError.print(ex);
475 c_alphas_state = 0xffffffff;
477 } else if (key.equals("scroll_step")) {
478 try {
479 setScrollStep(Integer.parseInt(data));
480 } catch (Exception ex) {
481 IJError.print(ex);
482 setScrollStep(1);
485 // TODO the above is insecure, in that data is not fully checked to be within bounds.
487 Object[] props = new Object[]{p, new Double(magnification), srcRect, new Long(layer.getId()), new Integer(c_alphas), new Integer(c_alphas_state)};
488 synchronized (ht_later) {
489 Display.ht_later.put(this, props);
491 this.layer = layer;
494 /** After reloading a project from the database, open the Displays that the project had. */
495 static public Bureaucrat openLater() {
496 final Hashtable ht_later_local;
497 synchronized (ht_later) {
498 if (0 == ht_later.size()) return null;
499 ht_later_local = new Hashtable(ht_later);
500 ht_later.keySet().removeAll(ht_later_local.keySet());
502 final Worker worker = new Worker("Opening displays") {
503 public void run() {
504 startedWorking();
505 try {
506 Thread.sleep(300); // waiting for Swing
508 for (Enumeration e = ht_later_local.keys(); e.hasMoreElements(); ) {
509 final Display d = (Display)e.nextElement();
510 front = d; // must be set before repainting any ZDisplayable!
511 Object[] props = (Object[])ht_later_local.get(d);
512 if (ControlWindow.isGUIEnabled()) d.makeGUI(d.layer, props);
513 d.setLayerLater(d.layer, d.layer.get(((Long)props[3]).longValue())); //important to do it after makeGUI
514 if (!ControlWindow.isGUIEnabled()) continue;
515 ImagePlus.addImageListener(d);
516 al_displays.add(d);
517 d.updateTitle();
518 // force a repaint if a prePaint was done TODO this should be properly managed with repaints using always the invokeLater, but then it's DOG SLOW
519 if (d.canvas.getMagnification() > 0.499) {
520 SwingUtilities.invokeLater(new Runnable() { public void run() {
521 d.repaint(d.layer);
522 d.project.getLoader().setChanged(false);
523 Utils.log2("A set to false");
524 }});
526 d.project.getLoader().setChanged(false);
527 Utils.log2("B set to false");
529 if (null != front) front.getProject().select(front.layer);
531 } catch (Throwable t) {
532 IJError.print(t);
533 } finally {
534 finishedWorking();
538 return Bureaucrat.createAndStart(worker, ((Display)ht_later_local.keySet().iterator().next()).getProject()); // gets the project from the first Display
541 private void makeGUI(final Layer layer, final Object[] props) {
542 // gather properties
543 Point p = null;
544 double mag = 1.0D;
545 Rectangle srcRect = null;
546 if (null != props) {
547 p = (Point)props[0];
548 mag = ((Double)props[1]).doubleValue();
549 srcRect = (Rectangle)props[2];
552 // transparency slider
553 this.transp_slider = new JSlider(javax.swing.SwingConstants.HORIZONTAL, 0, 100, 100);
554 this.transp_slider.setBackground(Color.white);
555 this.transp_slider.setMinimumSize(new Dimension(250, 20));
556 this.transp_slider.setMaximumSize(new Dimension(250, 20));
557 this.transp_slider.setPreferredSize(new Dimension(250, 20));
558 TransparencySliderListener tsl = new TransparencySliderListener();
559 this.transp_slider.addChangeListener(tsl);
560 this.transp_slider.addMouseListener(tsl);
561 for (final KeyListener kl : this.transp_slider.getKeyListeners()) {
562 this.transp_slider.removeKeyListener(kl);
565 // Tabbed pane on the left
566 this.tabs = new JTabbedPane();
567 this.tabs.setMinimumSize(new Dimension(250, 300));
568 this.tabs.setBackground(Color.white);
569 this.tabs.addChangeListener(tabs_listener);
571 // Tab 1: Patches
572 this.panel_patches = makeTabPanel();
573 this.panel_patches.add(new JLabel("No patches."));
574 this.scroll_patches = makeScrollPane(panel_patches);
575 this.tabs.add("Patches", scroll_patches);
577 // Tab 2: Profiles
578 this.panel_profiles = makeTabPanel();
579 this.panel_profiles.add(new JLabel("No profiles."));
580 this.scroll_profiles = makeScrollPane(panel_profiles);
581 this.tabs.add("Profiles", scroll_profiles);
583 // Tab 3: pipes
584 this.panel_zdispl = makeTabPanel();
585 this.panel_zdispl.add(new JLabel("No objects."));
586 this.scroll_zdispl = makeScrollPane(panel_zdispl);
587 this.tabs.add("Z space", scroll_zdispl);
589 // Tab 4: channels
590 this.panel_channels = makeTabPanel();
591 this.scroll_channels = makeScrollPane(panel_channels);
592 this.channels = new Channel[4];
593 this.channels[0] = new Channel(this, Channel.MONO);
594 this.channels[1] = new Channel(this, Channel.RED);
595 this.channels[2] = new Channel(this, Channel.GREEN);
596 this.channels[3] = new Channel(this, Channel.BLUE);
597 //this.panel_channels.add(this.channels[0]);
598 this.panel_channels.add(this.channels[1]);
599 this.panel_channels.add(this.channels[2]);
600 this.panel_channels.add(this.channels[3]);
601 this.tabs.add("Opacity", scroll_channels);
603 // Tab 5: labels
604 this.panel_labels = makeTabPanel();
605 this.panel_labels.add(new JLabel("No labels."));
606 this.scroll_labels = makeScrollPane(panel_labels);
607 this.tabs.add("Labels", scroll_labels);
609 // Tab 6: layers
610 this.panel_layers = makeTabPanel();
611 this.scroll_layers = makeScrollPane(panel_layers);
612 recreateLayerPanels(layer);
613 this.scroll_layers.addMouseWheelListener(canvas);
614 this.tabs.add("Layers", scroll_layers);
616 this.ht_tabs = new Hashtable<Class,JScrollPane>();
617 this.ht_tabs.put(Patch.class, scroll_patches);
618 this.ht_tabs.put(Profile.class, scroll_profiles);
619 this.ht_tabs.put(ZDisplayable.class, scroll_zdispl);
620 this.ht_tabs.put(AreaList.class, scroll_zdispl);
621 this.ht_tabs.put(Pipe.class, scroll_zdispl);
622 this.ht_tabs.put(Polyline.class, scroll_zdispl);
623 this.ht_tabs.put(Ball.class, scroll_zdispl);
624 this.ht_tabs.put(Dissector.class, scroll_zdispl);
625 this.ht_tabs.put(DLabel.class, scroll_labels);
626 // channels not included
627 // layers not included
629 // Navigator
630 this.navigator = new DisplayNavigator(this, layer.getLayerWidth(), layer.getLayerHeight());
631 // Layer scroller (to scroll slices)
632 int extent = (int)(250.0 / layer.getParent().size());
633 if (extent < 10) extent = 10;
634 this.scroller = new JScrollBar(JScrollBar.HORIZONTAL);
635 updateLayerScroller(layer);
636 this.scroller.addAdjustmentListener(scroller_listener);
639 // Left panel, contains the transp slider, the tabbed pane, the navigation panel and the layer scroller
640 JPanel left = new JPanel();
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 (?)
672 // fix
673 gb.setConstraints(split.getRightComponent(), c);
675 // JFrame to show the split pane
676 this.frame = ControlWindow.createJFrame(layer.toString());
677 if (IJ.isMacintosh() && IJ.getInstance()!=null) {
678 IJ.wait(10); // may be needed for Java 1.4 on OS X
679 this.frame.setMenuBar(ij.Menus.getMenuBar());
681 this.frame.addWindowListener(window_listener);
682 this.frame.addComponentListener(component_listener);
683 this.frame.getContentPane().add(split);
684 this.frame.addMouseListener(frame_mouse_listener);
685 //doesn't exist//this.frame.setMinimumSize(new Dimension(270, 600));
687 if (null != props) {
688 // restore canvas
689 canvas.setup(mag, srcRect);
690 // restore visibility of each channel
691 int cs = ((Integer)props[5]).intValue(); // aka c_alphas_state
692 int[] sel = new int[4];
693 sel[0] = ((cs&0xff000000)>>24);
694 sel[1] = ((cs&0xff0000)>>16);
695 sel[2] = ((cs&0xff00)>>8);
696 sel[3] = (cs&0xff);
697 // restore channel alphas
698 this.c_alphas = ((Integer)props[4]).intValue();
699 channels[0].setAlpha( (float)((c_alphas&0xff000000)>>24) / 255.0f , 0 != sel[0]);
700 channels[1].setAlpha( (float)((c_alphas&0xff0000)>>16) / 255.0f , 0 != sel[1]);
701 channels[2].setAlpha( (float)((c_alphas&0xff00)>>8) / 255.0f , 0 != sel[2]);
702 channels[3].setAlpha( (float) (c_alphas&0xff) / 255.0f , 0 != sel[3]);
703 // restore visibility in the working c_alphas
704 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);
707 if (null != active && null != layer) {
708 Rectangle r = active.getBoundingBox();
709 r.x -= r.width/2;
710 r.y -= r.height/2;
711 r.width += r.width;
712 r.height += r.height;
713 if (r.x < 0) r.x = 0;
714 if (r.y < 0) r.y = 0;
715 if (r.width > layer.getLayerWidth()) r.width = (int)layer.getLayerWidth();
716 if (r.height> layer.getLayerHeight())r.height= (int)layer.getLayerHeight();
717 double magn = layer.getLayerWidth() / (double)r.width;
718 canvas.setup(magn, r);
721 // add keyListener to the whole frame
722 this.tabs.addKeyListener(canvas);
723 this.canvas_panel.addKeyListener(canvas);
724 this.frame.addKeyListener(canvas);
726 this.frame.pack();
727 ij.gui.GUI.center(this.frame);
728 this.frame.setVisible(true);
729 ProjectToolbar.setProjectToolbar(); // doesn't get it through events
731 final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
733 if (null != props) {
734 // fix positioning outside the screen (dual to single monitor)
735 if (p.x >= 0 && p.x < screen.width - 50 && p.y >= 0 && p.y <= screen.height - 50) this.frame.setLocation(p);
736 else frame.setLocation(0, 0);
739 // fix excessive size
740 final Rectangle box = this.frame.getBounds();
741 int x = box.x;
742 int y = box.y;
743 int width = box.width;
744 int height = box.height;
745 if (box.width > screen.width) { x = 0; width = screen.width; }
746 if (box.height > screen.height) { y = 0; height = screen.height; }
747 if (x != box.x || y != box.y) {
748 this.frame.setLocation(x, y + (0 == y ? 30 : 0)); // added insets for bad window managers
749 updateInDatabase("position");
751 if (width != box.width || height != box.height) {
752 this.frame.setSize(new Dimension(width -10, height -30)); // added insets for bad window managers
754 if (null == props) {
755 // try to optimize canvas dimensions and magn
756 double magn = layer.getLayerHeight() / screen.height;
757 if (magn > 1.0) magn = 1.0;
758 long size = 0;
759 // limit magnification if appropriate
760 for (Iterator it = layer.getDisplayables(Patch.class).iterator(); it.hasNext(); ) {
761 final Patch pa = (Patch)it.next();
762 final Rectangle ba = pa.getBoundingBox();
763 size += (long)(ba.width * ba.height);
765 if (size > 10000000) canvas.setInitialMagnification(0.25); // 10 Mb
766 else {
767 this.frame.setSize(new Dimension((int)(screen.width * 0.66), (int)(screen.height * 0.66)));
771 Utils.updateComponent(tabs); // otherwise fails in FreeBSD java 1.4.2 when reconstructing
774 // Set the calibration of the FakeImagePlus to that of the LayerSet
775 ((FakeImagePlus)canvas.getFakeImagePlus()).setCalibrationSuper(layer.getParent().getCalibrationCopy());
777 // Set the FakeImagePlus as the current image
778 setTempCurrentImage();
780 // create a drag and drop listener
781 dnd = new DNDInsertImage(this);
783 // start a repainting thread
784 if (null != props) {
785 canvas.repaint(true); // repaint() is unreliable
788 // 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.
789 SwingUtilities.invokeLater(new Runnable() {
790 public void run() {
791 tabs.setMinimumSize(new Dimension(0, 100));
792 Display.scrollbar_width = Display.this.scroll_patches.getVerticalScrollBar().getPreferredSize().width; // using scroll_patches since it's the one selected by default and thus visible and painted
797 private JPanel makeTabPanel() {
798 JPanel panel = new JPanel();
799 BoxLayout layout = new BoxLayout(panel, BoxLayout.Y_AXIS);
800 panel.setLayout(layout);
801 return panel;
804 private JScrollPane makeScrollPane(Component c) {
805 JScrollPane jsp = new JScrollPane(c);
806 // adjust scrolling to use one DisplayablePanel as the minimal unit
807 jsp.getVerticalScrollBar().setBlockIncrement(DisplayablePanel.HEIGHT); // clicking within the track
808 jsp.getVerticalScrollBar().setUnitIncrement(DisplayablePanel.HEIGHT); // clicking on an arrow
809 jsp.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
810 jsp.setPreferredSize(new Dimension(250, 300));
811 jsp.setMinimumSize(new Dimension(250, 300));
812 return jsp;
815 static protected int scrollbar_width = 0;
817 public JPanel getCanvasPanel() {
818 return canvas_panel;
821 public DisplayCanvas getCanvas() {
822 return canvas;
825 public synchronized void setLayer(final Layer layer) {
826 if (null == layer || layer == this.layer) return;
827 translateLayerColors(this.layer, layer);
828 if (tabs.getSelectedComponent() == scroll_layers) scrollToShow(scroll_layers, layer_panels.get(layer));
829 final boolean set_zdispl = null == Display.this.layer || layer.getParent() != Display.this.layer.getParent();
830 if (selection.isTransforming()) {
831 Utils.log("Can't browse layers while transforming.\nCANCEL the transform first with the ESCAPE key or right-click -> cancel.");
832 scroller.setValue(Display.this.layer.getParent().getLayerIndex(Display.this.layer.getId()));
833 return;
835 this.layer = layer;
836 scroller.setValue(layer.getParent().getLayerIndex(layer.getId()));
838 // update the current Layer pointer in ZDisplayable objects
839 for (Iterator it = layer.getParent().getZDisplayables().iterator(); it.hasNext(); ) {
840 ((ZDisplayable)it.next()).setLayer(layer); // the active layer
843 updateVisibleTab(set_zdispl);
845 // see if a lot has to be reloaded, put the relevant ones at the end
846 project.getLoader().prepare(layer);
847 updateTitle(); // to show the new 'z'
848 // select the Layer in the LayerTree
849 project.select(Display.this.layer); // does so in a separate thread
850 // update active Displayable:
852 // deselect all except ZDisplayables
853 final ArrayList sel = selection.getSelected();
854 final Displayable last_active = Display.this.active;
855 int sel_next = -1;
856 for (Iterator it = sel.iterator(); it.hasNext(); ) {
857 Displayable d = (Displayable)it.next();
858 if (!(d instanceof ZDisplayable)) {
859 it.remove();
860 selection.remove(d);
861 if (d == last_active && sel.size() > 0) {
862 // select the last one of the remaining, if any
863 sel_next = sel.size()-1;
867 if (-1 != sel_next && sel.size() > 0) select((Displayable)sel.get(sel_next), true);
868 else if (null != last_active && last_active.getClass() == Patch.class && null != last_temp && last_temp instanceof PatchStack) {
869 Displayable d = ((PatchStack)last_temp).getPatch(layer, (Patch)last_active);
870 if (null != d) selection.add(d);
872 // TODO last_temp doesn't remain the PatchStack // Utils.log2("last_temp is: " + last_temp.getClass().getName());
874 // Keep Profile chain selected, for best ease of use:
875 if (null != last_active && last_active.getClass() == Profile.class && last_active.isLinked(Profile.class)) {
876 Displayable other = null;
877 for (final Displayable prof : last_active.getLinked(Profile.class)) {
878 if (prof.getLayer() == layer) {
879 other = prof;
880 break;
883 if (null != other) selection.add(other);
886 // repaint everything
887 navigator.repaint(true);
888 canvas.repaint(true);
890 // repaint tabs (hard as hell)
891 Utils.updateComponent(tabs);
892 // @#$%^! The above works half the times, so explicit repaint as well:
893 Component c = tabs.getSelectedComponent();
894 if (null == c) {
895 c = scroll_patches;
896 tabs.setSelectedComponent(scroll_patches);
898 Utils.updateComponent(c);
900 project.getLoader().setMassiveMode(false); // resetting if it was set true
902 // update the coloring in the ProjectTree
903 project.getProjectTree().updateUILater();
905 setTempCurrentImage();
908 static public void updateVisibleTabs() {
909 for (final Display d : al_displays) {
910 d.updateVisibleTab(true);
914 /** Recreate the tab that is being shown. */
915 public void updateVisibleTab(boolean set_zdispl) {
916 // update only the visible tab
917 switch (tabs.getSelectedIndex()) {
918 case 0:
919 ht_panels.clear();
920 updateTab(panel_patches, "Patches", layer.getDisplayables(Patch.class));
921 break;
922 case 1:
923 ht_panels.clear();
924 updateTab(panel_profiles, "Profiles", layer.getDisplayables(Profile.class));
925 break;
926 case 2:
927 if (set_zdispl) {
928 ht_panels.clear();
929 updateTab(panel_zdispl, "Z-space objects", layer.getParent().getZDisplayables());
931 break;
932 // case 3: channel opacities
933 case 4:
934 ht_panels.clear();
935 updateTab(panel_labels, "Labels", layer.getDisplayables(DLabel.class));
936 break;
941 private void setLayerLater(final Layer layer, final Displayable active) {
942 if (null == layer) return;
943 this.layer = layer;
944 if (!ControlWindow.isGUIEnabled()) return;
945 SwingUtilities.invokeLater(new Runnable() { public void run() {
946 // empty the tabs, except channels and pipes
947 clearTab(panel_profiles, "Profiles");
948 clearTab(panel_patches, "Patches");
949 clearTab(panel_labels, "Labels");
950 // distribute Displayable to the tabs. Ignore LayerSet instances.
951 if (null == ht_panels) ht_panels = new Hashtable<Displayable,DisplayablePanel>();
952 else ht_panels.clear();
953 Iterator it = layer.getDisplayables().iterator();
954 while (it.hasNext()) {
955 add((Displayable)it.next(), false, false);
957 it = layer.getParent().getZDisplayables().iterator(); // the pipes, that live in the LayerSet
958 while (it.hasNext()) {
959 add((Displayable)it.next(), false, false);
961 navigator.repaint(true); // was not done when adding
962 Utils.updateComponent(tabs.getSelectedComponent());
964 setActive(active);
965 }});
966 // swing issues:
968 new Thread() {
969 public void run() {
970 setPriority(Thread.NORM_PRIORITY);
971 try { Thread.sleep(1000); } catch (Exception e) {}
972 setActive(active);
974 }.start();
978 /** Remove all components from the tab and add a "No [label]" label to each. */
979 private void clearTab(final Container c, final String label) {
980 c.removeAll();
981 c.add(new JLabel("No " + label + "."));
982 // magic cocktail:
983 if (tabs.getSelectedComponent() == c) {
984 Utils.updateComponent(c);
988 /** A class to listen to the transparency_slider of the DisplayablesSelectorWindow. */
989 private class TransparencySliderListener extends MouseAdapter implements ChangeListener {
991 public void stateChanged(ChangeEvent ce) {
992 //change the transparency value of the current active displayable
993 float new_value = (float)((JSlider)ce.getSource()).getValue();
994 setTransparency(new_value / 100.0f);
997 public void mousePressed(MouseEvent me) {
998 JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
999 if (scroll != scroll_channels && !selection.isEmpty()) selection.addDataEditStep(new String[]{"alpha"});
1002 public void mouseReleased(MouseEvent me) {
1003 // update navigator window
1004 navigator.repaint(true);
1005 JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
1006 if (scroll != scroll_channels && !selection.isEmpty()) selection.addDataEditStep(new String[]{"alpha"});
1010 /** Context-sensitive: to a Displayable, or to a channel. */
1011 private void setTransparency(final float value) {
1012 JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
1013 if (scroll == scroll_channels) {
1014 for (int i=0; i<4; i++) {
1015 if (channels[i].getBackground() == Color.cyan) {
1016 channels[i].setAlpha(value); // will call back and repaint the Display
1017 return;
1020 } else if (null != active) {
1021 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.
1022 canvas.invalidateVolatile();
1023 selection.setAlpha(value);
1028 public void setTransparencySlider(final float transp) {
1029 if (transp >= 0.0f && transp <= 1.0f) {
1030 // fire event
1031 transp_slider.setValue((int)(transp * 100));
1035 /** Mark the canvas for updating the offscreen images if the given Displayable is NOT the active. */ // Used by the Displayable.setVisible for example.
1036 static public void setUpdateGraphics(final Layer layer, final Displayable displ) {
1037 for (final Display d : al_displays) {
1038 if (layer == d.layer && null != d.active && d.active != displ) {
1039 d.canvas.setUpdateGraphics(true);
1044 /** Flag the DisplayCanvas of Displays showing the given Layer to update their offscreen images.*/
1045 static public void setUpdateGraphics(final Layer layer, final boolean update) {
1046 for (final Display d : al_displays) {
1047 if (layer == d.layer) {
1048 d.canvas.setUpdateGraphics(update);
1053 /** Whether to update the offscreen images or not. */
1054 public void setUpdateGraphics(boolean b) {
1055 canvas.setUpdateGraphics(b);
1058 /** Find all Display instances that contain the layer and repaint them, in the Swing GUI thread. */
1059 static public void update(final Layer layer) {
1060 if (null == layer) return;
1061 SwingUtilities.invokeLater(new Runnable() { public void run() {
1062 for (final Display d : al_displays) {
1063 if (d.isShowing(layer)) {
1064 d.repaintAll();
1067 }});
1070 static public void update(final LayerSet set) {
1071 update(set, true);
1074 /** 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. */
1075 static public void update(final LayerSet set, final boolean update_canvas_dimensions) {
1076 if (null == set) return;
1077 SwingUtilities.invokeLater(new Runnable() { public void run() {
1078 for (final Display d : al_displays) {
1079 if (set.contains(d.layer)) {
1080 d.updateSnapshots();
1081 if (update_canvas_dimensions) d.canvas.setDimensions(set.getLayerWidth(), set.getLayerHeight());
1082 d.repaintAll();
1085 }});
1088 /** Release all resources held by this Display and close the frame. */
1089 protected void destroy() {
1090 dispatcher.quit();
1091 canvas.setReceivesInput(false);
1092 slt.quit();
1094 // update the coloring in the ProjectTree and LayerTree
1095 if (!project.isBeingDestroyed()) {
1096 try {
1097 project.getProjectTree().updateUILater();
1098 project.getLayerTree().updateUILater();
1099 } catch (Exception e) {
1100 Utils.log2("updateUI failed at Display.destroy()");
1104 frame.removeComponentListener(component_listener);
1105 frame.removeWindowListener(window_listener);
1106 frame.removeWindowFocusListener(window_listener);
1107 frame.removeWindowStateListener(window_listener);
1108 frame.removeKeyListener(canvas);
1109 frame.removeMouseListener(frame_mouse_listener);
1110 canvas_panel.removeKeyListener(canvas);
1111 canvas.removeKeyListener(canvas);
1112 tabs.removeChangeListener(tabs_listener);
1113 tabs.removeKeyListener(canvas);
1114 ImagePlus.removeImageListener(this);
1115 bytypelistener = null;
1116 canvas.destroy();
1117 navigator.destroy();
1118 scroller.removeAdjustmentListener(scroller_listener);
1119 frame.setVisible(false);
1120 //no need, and throws exception//frame.dispose();
1121 active = null;
1122 if (null != selection) selection.clear();
1123 //Utils.log2("destroying selection");
1125 // below, need for SetLayerThread threads to quit
1126 slt.quit();
1127 // set a new front if any
1128 if (null == front && al_displays.size() > 0) {
1129 front = (Display)al_displays.get(al_displays.size() -1);
1131 // repaint layer tree (to update the label color)
1132 try {
1133 project.getLayerTree().updateUILater(); // works only after setting the front above
1134 } catch (Exception e) {} // ignore swing sync bullshit when closing everything too fast
1135 // remove the drag and drop listener
1136 dnd.destroy();
1139 /** Find all Display instances that contain a Layer of the given project and close them without removing the Display entries from the database. */
1140 static synchronized public void close(final Project project) {
1141 /* // concurrent modifications if more than 1 Display are being removed asynchronously
1142 for (final Display d : al_displays) {
1143 if (d.getLayer().getProject().equals(project)) {
1144 it.remove();
1145 d.destroy();
1149 Display[] d = new Display[al_displays.size()];
1150 al_displays.toArray(d);
1151 for (int i=0; i<d.length; i++) {
1152 if (d[i].getProject() == project) {
1153 al_displays.remove(d[i]);
1154 d[i].destroy();
1159 /** Find all Display instances that contain the layer and close them and remove the Display from the database. */
1160 static public void close(final Layer layer) {
1161 for (Iterator it = al_displays.iterator(); it.hasNext(); ) {
1162 Display d = (Display)it.next();
1163 if (d.isShowing(layer)) {
1164 d.remove(false);
1165 it.remove();
1170 public boolean remove(boolean check) {
1171 if (check) {
1172 if (!Utils.check("Delete the Display ?")) return false;
1174 // flush the offscreen images and close the frame
1175 destroy();
1176 removeFromDatabase();
1177 return true;
1180 public Layer getLayer() {
1181 return layer;
1184 public LayerSet getLayerSet() {
1185 return layer.getParent();
1188 public boolean isShowing(final Layer layer) {
1189 return this.layer == layer;
1192 public DisplayNavigator getNavigator() {
1193 return navigator;
1196 /** Repaint both the canvas and the navigator, updating the graphics, and the title and tabs. */
1197 public void repaintAll() {
1198 if (repaint_disabled) return;
1199 navigator.repaint(true);
1200 canvas.repaint(true);
1201 Utils.updateComponent(tabs);
1202 updateTitle();
1205 /** Repaint the canvas updating graphics, the navigator without updating graphics, and the title. */
1206 public void repaintAll2() {
1207 if (repaint_disabled) return;
1208 navigator.repaint(false);
1209 canvas.repaint(true);
1210 updateTitle();
1213 static public void repaintSnapshots(final LayerSet set) {
1214 if (repaint_disabled) return;
1215 for (final Display d : al_displays) {
1216 if (d.getLayer().getParent() == set) {
1217 d.navigator.repaint(true);
1218 Utils.updateComponent(d.tabs);
1222 static public void repaintSnapshots(final Layer layer) {
1223 if (repaint_disabled) return;
1224 for (final Display d : al_displays) {
1225 if (d.getLayer() == layer) {
1226 d.navigator.repaint(true);
1227 Utils.updateComponent(d.tabs);
1232 public void pack() {
1233 dispatcher.exec(new Runnable() { public void run() {
1234 try {
1235 Thread.currentThread().sleep(100);
1236 SwingUtilities.invokeAndWait(new Runnable() { public void run() {
1237 frame.pack();
1238 }});
1239 } catch (Exception e) { IJError.print(e); }
1240 }});
1243 static public void pack(final LayerSet ls) {
1244 for (final Display d : al_displays) {
1245 if (d.layer.getParent() == ls) d.pack();
1249 private void adjustCanvas() {
1250 SwingUtilities.invokeLater(new Runnable() { public void run() {
1251 Rectangle r = split.getRightComponent().getBounds();
1252 canvas.setDrawingSize(r.width, r.height, true);
1253 // fix not-on-top-left problem
1254 canvas.setLocation(0, 0);
1255 //frame.pack(); // don't! Would go into an infinite loop
1256 canvas.repaint(true);
1257 updateInDatabase("srcRect");
1258 }});
1261 /** Grab the last selected display (or create an new one if none) and show in it the layer,centered on the Displayable object. */
1262 static public void setFront(final Layer layer, final Displayable displ) {
1263 if (null == front) {
1264 Display display = new Display(layer.getProject(), layer); // gets set to front
1265 display.showCentered(displ);
1266 } else if (layer == front.layer) {
1267 front.showCentered(displ);
1268 } else {
1269 // find one:
1270 for (final Display d : al_displays) {
1271 if (d.layer == layer) {
1272 d.frame.toFront();
1273 d.showCentered(displ);
1274 return;
1277 // else, open new one
1278 new Display(layer.getProject(), layer).showCentered(displ);
1282 /** 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. */
1283 static public void add(final Layer layer, final Displayable displ, final boolean activate) {
1284 for (final Display d : al_displays) {
1285 if (d.layer == layer) {
1286 if (front == d) {
1287 d.add(displ, activate, true);
1288 //front.frame.toFront();
1289 } else {
1290 d.add(displ, false, true);
1296 static public void add(final Layer layer, final Displayable displ) {
1297 add(layer, displ, true);
1300 /** Add the ZDisplayable to all Displays that show a Layer belonging to the given LayerSet. */
1301 static public void add(final LayerSet set, final ZDisplayable zdispl) {
1302 for (final Display d : al_displays) {
1303 if (set.contains(d.layer)) {
1304 if (front == d) {
1305 zdispl.setLayer(d.layer); // the active one
1306 d.add(zdispl, true, true); // calling add(Displayable, boolean, boolean)
1307 //front.frame.toFront();
1308 } else {
1309 d.add(zdispl, false, true);
1315 static public void addAll(final Layer layer, final Collection<? extends Displayable> coll) {
1316 for (final Display d : al_displays) {
1317 if (d.layer == layer) {
1318 d.addAll(coll);
1323 static public void addAll(final LayerSet set, final Collection<? extends ZDisplayable> coll) {
1324 for (final Display d : al_displays) {
1325 if (set.contains(d.layer)) {
1326 for (final ZDisplayable zd : coll) {
1327 if (front == d) zd.setLayer(d.layer);
1329 d.addAll(coll);
1334 private final void addAll(final Collection<? extends Displayable> coll) {
1335 for (final Displayable d : coll) {
1336 add(d, false, false);
1338 selection.clear();
1339 Utils.updateComponent(tabs);
1340 navigator.repaint(true);
1343 // TODO this very old method could take some improvement:
1344 // - there is no need to create a new DisplayablePanel if its panel is not shown
1345 // - other issues; the method looks overly "if a dog barks and a duck quacks during a lunar eclipse then .."
1346 /** Add it to the proper panel, at the top, and set it active. */
1347 private final void add(final Displayable d, final boolean activate, final boolean repaint_snapshot) {
1348 DisplayablePanel dp = ht_panels.get(d);
1349 if (null != dp && activate) { // for ZDisplayable objects (TODO I think this is not used anymore)
1350 dp.setActive(true);
1351 //setActive(d);
1352 selection.clear();
1353 selection.add(d);
1354 return;
1356 // add to the proper list
1357 JPanel p = null;
1358 if (d instanceof Profile) {
1359 p = panel_profiles;
1360 } else if (d instanceof Patch) {
1361 p = panel_patches;
1362 } else if (d instanceof DLabel) {
1363 p = panel_labels;
1364 } else if (d instanceof ZDisplayable) { //both pipes and balls and AreaList
1365 p = panel_zdispl;
1366 } else {
1367 // LayerSet objects
1368 return;
1370 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
1371 addToPanel(p, 0, dp, activate);
1372 ht_panels.put(d, dp);
1373 if (activate) {
1374 dp.setActive(true);
1375 //setActive(d);
1376 selection.clear();
1377 selection.add(d);
1379 if (repaint_snapshot) navigator.repaint(true);
1382 private void addToPanel(JPanel panel, int index, DisplayablePanel dp, boolean repaint) {
1383 // remove the label
1384 if (1 == panel.getComponentCount() && panel.getComponent(0) instanceof JLabel) {
1385 panel.removeAll();
1387 panel.add(dp, index);
1388 if (repaint) {
1389 Utils.updateComponent(tabs);
1393 /** Find the displays that show the given Layer, and remove the given Displayable from the GUI. */
1394 static public void remove(final Layer layer, final Displayable displ) {
1395 for (final Display d : al_displays) {
1396 if (layer == d.layer) d.remove(displ);
1400 private void remove(final Displayable displ) {
1401 DisplayablePanel ob = ht_panels.remove(displ);
1402 if (null != ob) {
1403 final JScrollPane jsp = ht_tabs.get(displ.getClass());
1404 if (null != jsp) {
1405 JPanel p = (JPanel)jsp.getViewport().getView();
1406 p.remove((Component)ob);
1407 Utils.revalidateComponent(p);
1410 if (null == active || !selection.contains(displ)) {
1411 canvas.setUpdateGraphics(true);
1413 canvas.invalidateVolatile(); // removing active, no need to update offscreen but yes the volatile
1414 repaint(displ, null, 5, true, false);
1415 // from Selection.deleteAll this method is called ... but it's ok: same thread, no locking problems.
1416 selection.remove(displ);
1419 static public void remove(final ZDisplayable zdispl) {
1420 for (final Display d : al_displays) {
1421 if (zdispl.getLayerSet() == d.layer.getParent()) {
1422 d.remove((Displayable)zdispl);
1427 static public void repaint(final Layer layer, final Displayable displ, final int extra) {
1428 repaint(layer, displ, displ.getBoundingBox(), extra);
1431 static public void repaint(final Layer layer, final Displayable displ, final Rectangle r, final int extra) {
1432 repaint(layer, displ, r, extra, true);
1435 /** Find the displays that show the given Layer, and repaint the given Displayable. */
1436 static public void repaint(final Layer layer, final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator) {
1437 if (repaint_disabled) return;
1438 for (final Display d : al_displays) {
1439 if (layer == d.layer) {
1440 d.repaint(displ, r, extra, repaint_navigator, false);
1445 static public void repaint(final Displayable d) {
1446 if (d instanceof ZDisplayable) repaint(d.getLayerSet(), d, d.getBoundingBox(null), 5, true);
1447 repaint(d.getLayer(), d, d.getBoundingBox(null), 5, true);
1450 /** Repaint as much as the bounding box around the given Displayable, or the r if not null. */
1451 private void repaint(final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator, final boolean update_graphics) {
1452 if (repaint_disabled || null == displ) return;
1453 if (update_graphics || displ.getClass() == Patch.class || displ != active) {
1454 canvas.setUpdateGraphics(true);
1456 if (null != r) canvas.repaint(r, extra);
1457 else canvas.repaint(displ, extra);
1458 if (repaint_navigator) {
1459 DisplayablePanel dp = ht_panels.get(displ);
1460 if (null != dp) dp.repaint(); // is null when creating it, or after deleting it
1461 navigator.repaint(true); // everything
1465 /** 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. */
1466 static public void repaintSnapshot(final Displayable displ) {
1467 for (final Display d : al_displays) {
1468 if (d.layer.contains(displ)) {
1469 if (!d.navigator.isPainted(displ)) {
1470 DisplayablePanel dp = d.ht_panels.get(displ);
1471 if (null != dp) dp.repaint(); // is null when creating it, or after deleting it
1472 d.navigator.repaint(displ);
1478 /** Repaint the given Rectangle in all Displays showing the layer, updating the offscreen image if any. */
1479 static public void repaint(final Layer layer, final Rectangle r, final int extra) {
1480 repaint(layer, extra, r, true, true);
1483 static public void repaint(final Layer layer, final int extra, final Rectangle r, final boolean update_navigator) {
1484 repaint(layer, extra, r, update_navigator, true);
1487 static public void repaint(final Layer layer, final int extra, final Rectangle r, final boolean update_navigator, final boolean update_graphics) {
1488 if (repaint_disabled) return;
1489 for (final Display d : al_displays) {
1490 if (layer == d.layer) {
1491 d.canvas.setUpdateGraphics(update_graphics);
1492 d.canvas.repaint(r, extra);
1493 if (update_navigator) {
1494 d.navigator.repaint(true);
1495 Utils.updateComponent(d.tabs.getSelectedComponent());
1502 /** Repaint the given Rectangle in all Displays showing the layer, optionally updating the offscreen image (if any). */
1503 static public void repaint(final Layer layer, final Rectangle r, final int extra, final boolean update_graphics) {
1504 if (repaint_disabled) return;
1505 for (final Display d : al_displays) {
1506 if (layer == d.layer) {
1507 d.canvas.setUpdateGraphics(update_graphics);
1508 d.canvas.repaint(r, extra);
1509 d.navigator.repaint(update_graphics);
1510 if (update_graphics) Utils.updateComponent(d.tabs.getSelectedComponent());
1515 /** Repaint the DisplayablePanel (and DisplayNavigator) only for the given Displayable, in all Displays showing the given Layer. */
1516 static public void repaint(final Layer layer, final Displayable displ) {
1517 if (repaint_disabled) return;
1518 for (final Display d : al_displays) {
1519 if (layer == d.layer) {
1520 DisplayablePanel dp = d.ht_panels.get(displ);
1521 if (null != dp) dp.repaint();
1522 d.navigator.repaint(true);
1527 static public void repaint(LayerSet set, Displayable displ, int extra) {
1528 repaint(set, displ, null, extra);
1531 static public void repaint(LayerSet set, Displayable displ, Rectangle r, int extra) {
1532 repaint(set, displ, r, extra, true);
1535 /** Repaint the Displayable in every Display that shows a Layer belonging to the given LayerSet. */
1536 static public void repaint(final LayerSet set, final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator) {
1537 if (repaint_disabled) return;
1538 for (final Display d : al_displays) {
1539 if (set.contains(d.layer)) {
1540 if (repaint_navigator) {
1541 if (null != displ) {
1542 DisplayablePanel dp = d.ht_panels.get(displ);
1543 if (null != dp) dp.repaint();
1545 d.navigator.repaint(true);
1547 if (null == displ || displ != d.active) d.setUpdateGraphics(true); // safeguard
1548 // paint the given box or the actual Displayable's box
1549 if (null != r) d.canvas.repaint(r, extra);
1550 else d.canvas.repaint(displ, extra);
1555 /** Repaint the entire LayerSet, in all Displays showing a Layer of it.*/
1556 static public void repaint(final LayerSet set) {
1557 if (repaint_disabled) return;
1558 for (final Display d : al_displays) {
1559 if (set.contains(d.layer)) {
1560 d.navigator.repaint(true);
1561 d.canvas.repaint(true);
1565 /** Repaint the given box in the LayerSet, in all Displays showing a Layer of it.*/
1566 static public void repaint(final LayerSet set, final Rectangle box) {
1567 if (repaint_disabled) return;
1568 for (final Display d : al_displays) {
1569 if (set.contains(d.layer)) {
1570 d.navigator.repaint(box);
1571 d.canvas.repaint(box, 0, true);
1575 /** Repaint the entire Layer, in all Displays showing it, including the tabs.*/
1576 static public void repaint(final Layer layer) { // TODO this method overlaps with update(layer)
1577 if (repaint_disabled) return;
1578 for (final Display d : al_displays) {
1579 if (layer == d.layer) {
1580 d.navigator.repaint(true);
1581 d.canvas.repaint(true);
1586 /** Call repaint on all open Displays. */
1587 static public void repaint() {
1588 if (repaint_disabled) {
1589 Utils.logAll("Can't repaint -- repainting is disabled!");
1590 return;
1592 for (final Display d : al_displays) {
1593 d.navigator.repaint(true);
1594 d.canvas.repaint(true);
1598 static private boolean repaint_disabled = false;
1600 /** Set a flag to enable/disable repainting of all Display instances. */
1601 static protected void setRepaint(boolean b) {
1602 repaint_disabled = !b;
1605 public Rectangle getBounds() {
1606 return frame.getBounds();
1609 public Point getLocation() {
1610 return frame.getLocation();
1613 public JFrame getFrame() {
1614 return frame;
1617 public void setLocation(Point p) {
1618 this.frame.setLocation(p);
1621 public Displayable getActive() {
1622 return active; //TODO this should return selection.active !!
1625 public void select(Displayable d) {
1626 select(d, false);
1629 /** Select/deselect accordingly to the current state and the shift key. */
1630 public void select(final Displayable d, final boolean shift_down) {
1631 if (null != active && active != d && active.getClass() != Patch.class) {
1632 // active is being deselected, so link underlying patches
1633 active.linkPatches();
1635 if (null == d) {
1636 //Utils.log2("Display.select: clearing selection");
1637 canvas.setUpdateGraphics(true);
1638 selection.clear();
1639 return;
1641 if (!shift_down) {
1642 //Utils.log2("Display.select: single selection");
1643 if (d != active) {
1644 selection.clear();
1645 selection.add(d);
1647 } else if (selection.contains(d)) {
1648 if (active == d) {
1649 selection.remove(d);
1650 //Utils.log2("Display.select: removing from a selection");
1651 } else {
1652 //Utils.log2("Display.select: activing within a selection");
1653 selection.setActive(d);
1655 } else {
1656 //Utils.log2("Display.select: adding to an existing selection");
1657 selection.add(d);
1659 // update the image shown to ImageJ
1660 // NO longer necessary, always he same FakeImagePlus // setTempCurrentImage();
1663 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p, final Class c) {
1664 choose(screen_x_p, screen_y_p, x_p, y_p, false, c);
1666 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p) {
1667 choose(screen_x_p, screen_y_p, x_p, y_p, false, null);
1670 /** 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. */
1671 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p, boolean shift_down, Class c) {
1672 //Utils.log("Display.choose: x,y " + x_p + "," + y_p);
1673 final ArrayList<Displayable> al = new ArrayList<Displayable>(layer.find(x_p, y_p, true));
1674 al.addAll(layer.getParent().findZDisplayables(layer, x_p, y_p, true)); // only visible ones
1675 if (al.isEmpty()) {
1676 Displayable act = this.active;
1677 selection.clear();
1678 canvas.setUpdateGraphics(true);
1679 //Utils.log("choose: set active to null");
1680 // fixing lack of repainting for unknown reasons, of the active one TODO this is a temporary solution
1681 if (null != act) Display.repaint(layer, act, 5);
1682 } else if (1 == al.size()) {
1683 Displayable d = (Displayable)al.get(0);
1684 if (null != c && d.getClass() != c) {
1685 selection.clear();
1686 return;
1688 select(d, shift_down);
1689 //Utils.log("choose 1: set active to " + active);
1690 } else {
1691 if (al.contains(active) && !shift_down) {
1692 // do nothing
1693 } else {
1694 if (null != c) {
1695 // check if at least one of them is of class c
1696 // if only one is of class c, set as selected
1697 // else show menu
1698 for (Iterator it = al.iterator(); it.hasNext(); ) {
1699 Object ob = it.next();
1700 if (ob.getClass() != c) it.remove();
1702 if (0 == al.size()) {
1703 // deselect
1704 selection.clear();
1705 return;
1707 if (1 == al.size()) {
1708 select((Displayable)al.get(0), shift_down);
1709 return;
1711 // else, choose among the many
1713 choose(screen_x_p, screen_y_p, al, shift_down, x_p, y_p);
1715 //Utils.log("choose many: set active to " + active);
1719 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) {
1720 // show a popup on the canvas to choose
1721 new Thread() {
1722 public void run() {
1723 final Object lock = new Object();
1724 final DisplayableChooser d_chooser = new DisplayableChooser(al, lock);
1725 final JPopupMenu pop = new JPopupMenu("Select:");
1726 final Iterator itu = al.iterator();
1727 while (itu.hasNext()) {
1728 Displayable d = (Displayable)itu.next();
1729 JMenuItem menu_item = new JMenuItem(d.toString());
1730 menu_item.addActionListener(d_chooser);
1731 pop.add(menu_item);
1734 new Thread() {
1735 public void run() {
1736 pop.show(canvas, screen_x_p, screen_y_p);
1738 }.start();
1740 //now wait until selecting something
1741 synchronized(lock) {
1742 do {
1743 try {
1744 lock.wait();
1745 } catch (InterruptedException ie) {}
1746 } while (d_chooser.isWaiting() && pop.isShowing());
1749 //grab the chosen Displayable object
1750 Displayable d = d_chooser.getChosen();
1751 //Utils.log("Chosen: " + d.toString());
1752 if (null == d) { Utils.log2("Display.choose: returning a null!"); }
1753 select(d, shift_down);
1754 pop.setVisible(false);
1756 // fix selection bug: never receives mouseReleased event when the popup shows
1757 selection.mouseReleased(null, x_p, y_p, x_p, y_p, x_p, y_p);
1759 }.start();
1762 /** 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. */
1763 protected void setActive(final Displayable displ) {
1764 final Displayable prev_active = this.active;
1765 this.active = displ;
1766 SwingUtilities.invokeLater(new Runnable() { public void run() {
1768 // renew current image if necessary
1769 if (null != displ && displ == prev_active) {
1770 // make sure the proper tab is selected.
1771 selectTab(displ);
1772 return; // the same
1774 // deactivate previously active
1775 if (null != prev_active) {
1776 final DisplayablePanel ob = ht_panels.get(prev_active);
1777 if (null != ob) ob.setActive(false);
1778 // erase "decorations" of the previously active
1779 canvas.repaint(selection.getBox(), 4);
1781 // activate the new active
1782 if (null != displ) {
1783 final DisplayablePanel ob = ht_panels.get(displ);
1784 if (null != ob) ob.setActive(true);
1785 updateInDatabase("active_displayable_id");
1786 if (displ.getClass() != Patch.class) project.select(displ); // select the node in the corresponding tree, if any.
1787 // select the proper tab, and scroll to visible
1788 selectTab(displ);
1789 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
1790 repaint(displ, null, 5, false, update_graphics); // to show the border, and to repaint out of the background image
1791 transp_slider.setValue((int)(displ.getAlpha() * 100));
1792 } else {
1793 //ensure decorations are removed from the panels, for Displayables in a selection besides the active one
1794 Utils.updateComponent(tabs.getSelectedComponent());
1796 }});
1799 /** If the other paints under the base. */
1800 public boolean paintsBelow(Displayable base, Displayable other) {
1801 boolean zd_base = base instanceof ZDisplayable;
1802 boolean zd_other = other instanceof ZDisplayable;
1803 if (zd_other) {
1804 if (base instanceof DLabel) return true; // zd paints under label
1805 if (!zd_base) return false; // any zd paints over a mere displ if not a label
1806 else {
1807 // both zd, compare indices
1808 ArrayList<ZDisplayable> al = other.getLayerSet().getZDisplayables();
1809 return al.indexOf(base) > al.indexOf(other);
1811 } else {
1812 if (!zd_base) {
1813 // both displ, compare indices
1814 ArrayList<Displayable> al = other.getLayer().getDisplayables();
1815 return al.indexOf(base) > al.indexOf(other);
1816 } else {
1817 // base is zd, other is d
1818 if (other instanceof DLabel) return false;
1819 return true;
1824 /** 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. */
1825 private void selectTab(final Displayable displ) {
1826 Method method = null;
1827 try {
1828 if (!(displ instanceof LayerSet)) {
1829 method = Display.class.getDeclaredMethod("selectTab", new Class[]{displ.getClass()});
1831 } catch (Exception e) {
1832 IJError.print(e);
1834 if (null != method) {
1835 final Method me = method;
1836 dispatcher.exec(new Runnable() { public void run() {
1837 try {
1838 me.setAccessible(true);
1839 me.invoke(Display.this, new Object[]{displ});
1840 } catch (Exception e) { IJError.print(e); }
1841 }});
1845 private void selectTab(Patch patch) {
1846 tabs.setSelectedComponent(scroll_patches);
1847 scrollToShow(scroll_patches, ht_panels.get(patch));
1850 private void selectTab(Profile profile) {
1851 tabs.setSelectedComponent(scroll_profiles);
1852 scrollToShow(scroll_profiles, ht_panels.get(profile));
1855 private void selectTab(DLabel label) {
1856 tabs.setSelectedComponent(scroll_labels);
1857 scrollToShow(scroll_labels, ht_panels.get(label));
1860 private void selectTab(ZDisplayable zd) {
1861 tabs.setSelectedComponent(scroll_zdispl);
1862 scrollToShow(scroll_zdispl, ht_panels.get(zd));
1865 private void selectTab(Pipe d) { selectTab((ZDisplayable)d); }
1866 private void selectTab(Polyline d) { selectTab((ZDisplayable)d); }
1867 private void selectTab(AreaList d) { selectTab((ZDisplayable)d); }
1868 private void selectTab(Ball d) { selectTab((ZDisplayable)d); }
1869 private void selectTab(Dissector d) { selectTab((ZDisplayable)d); }
1871 /** 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). */
1872 private void updateTab(final Container tab, final String label, final ArrayList al) {
1873 final boolean[] recreated = new boolean[]{false, true, true};
1874 dispatcher.execSwing(new Runnable() { public void run() {
1875 try {
1876 if (0 == al.size()) {
1877 tab.removeAll();
1878 tab.add(new JLabel("No " + label + "."));
1879 } else {
1880 Component[] comp = tab.getComponents();
1881 int next = 0;
1882 if (1 == comp.length && comp[0].getClass() == JLabel.class) {
1883 next = 1;
1884 tab.remove(0);
1886 for (Iterator it = al.iterator(); it.hasNext(); ) {
1887 Displayable d = (Displayable)it.next();
1888 DisplayablePanel dp = null;
1889 if (next < comp.length) {
1890 dp = (DisplayablePanel)comp[next++]; // recycling panels
1891 dp.set(d);
1892 } else {
1893 dp = new DisplayablePanel(Display.this, d);
1894 tab.add(dp);
1896 ht_panels.put(d, dp);
1898 if (next < comp.length) {
1899 // remove from the end, to avoid potential repaints of other panels
1900 for (int i=comp.length-1; i>=next; i--) {
1901 tab.remove(i);
1904 recreated[0] = true;
1906 if (recreated[0]) {
1907 tab.invalidate();
1908 tab.validate();
1909 tab.repaint();
1911 if (null != Display.this.active) scrollToShow(Display.this.active);
1912 } catch (Throwable e) { IJError.print(e); }
1913 }});
1916 static public void setActive(final Object event, final Displayable displ) {
1917 if (!(event instanceof InputEvent)) return;
1918 // find which Display
1919 for (final Display d : al_displays) {
1920 if (d.isOrigin((InputEvent)event)) {
1921 d.setActive(displ);
1922 break;
1927 /** Find out whether this Display is Transforming its active Displayable. */
1928 public boolean isTransforming() {
1929 return canvas.isTransforming();
1932 /** Find whether any Display is transforming the given Displayable. */
1933 static public boolean isTransforming(final Displayable displ) {
1934 for (final Display d : al_displays) {
1935 if (null != d.active && d.active == displ && d.canvas.isTransforming()) return true;
1937 return false;
1940 static public boolean isAligning(final LayerSet set) {
1941 for (final Display d : al_displays) {
1942 if (d.layer.getParent() == set && set.isAligning()) {
1943 return true;
1946 return false;
1949 /** Set the front Display to transform the Displayable only if no other canvas is transforming it. */
1950 static public void setTransforming(final Displayable displ) {
1951 if (null == front) return;
1952 if (front.active != displ) return;
1953 for (final Display d : al_displays) {
1954 if (d.active == displ) {
1955 if (d.canvas.isTransforming()) {
1956 Utils.showMessage("Already transforming " + displ.getTitle());
1957 return;
1961 front.canvas.setTransforming(true);
1964 /** Check whether the source of the event is located in this instance.*/
1965 private boolean isOrigin(InputEvent event) {
1966 Object source = event.getSource();
1967 // find it ... check the canvas for now TODO
1968 if (canvas == source) {
1969 return true;
1971 return false;
1974 /** Get the layer of the front Display, or null if none.*/
1975 static public Layer getFrontLayer() {
1976 if (null == front) return null;
1977 return front.layer;
1980 /** Get the layer of an open Display of the given Project, or null if none.*/
1981 static public Layer getFrontLayer(final Project project) {
1982 if (null == front) return null;
1983 if (front.project == project) return front.layer;
1984 // else, find an open Display for the given Project, if any
1985 for (final Display d : al_displays) {
1986 if (d.project == project) {
1987 d.frame.toFront();
1988 return d.layer;
1991 return null; // none found
1994 static public Display getFront(final Project project) {
1995 if (null == front) return null;
1996 if (front.project == project) return front;
1997 for (final Display d : al_displays) {
1998 if (d.project == project) {
1999 d.frame.toFront();
2000 return d;
2003 return null;
2006 public boolean isReadOnly() {
2007 // TEMPORARY: in the future one will be able show displays as read-only to other people, remotely
2008 return false;
2011 static public void showPopup(Component c, int x, int y) {
2012 if (null != front) front.getPopupMenu().show(c, x, y);
2015 /** Return a context-sensitive popup menu. */
2016 public JPopupMenu getPopupMenu() { // called from canvas
2017 // get the job canceling dialog
2018 if (!canvas.isInputEnabled()) {
2019 return project.getLoader().getJobsPopup(this);
2022 // create new
2023 this.popup = new JPopupMenu();
2024 JMenuItem item = null;
2025 JMenu menu = null;
2027 if (ProjectToolbar.ALIGN == Toolbar.getToolId()) {
2028 boolean aligning = layer.getParent().isAligning();
2029 item = new JMenuItem("Cancel alignment"); item.addActionListener(this); popup.add(item);
2030 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2031 if (!aligning) item.setEnabled(false);
2032 item = new JMenuItem("Align with landmarks"); item.addActionListener(this); popup.add(item);
2033 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, true));
2034 if (!aligning) item.setEnabled(false);
2035 item = new JMenuItem("Align and register"); item.addActionListener(this); popup.add(item);
2036 if (!aligning) item.setEnabled(false);
2037 item = new JMenuItem("Align using profiles"); item.addActionListener(this); popup.add(item);
2038 if (!aligning || selection.isEmpty() || !selection.contains(Profile.class)) item.setEnabled(false);
2039 item = new JMenuItem("Align stack slices"); item.addActionListener(this); popup.add(item);
2040 if (selection.isEmpty() || ! (getActive().getClass() == Patch.class && ((Patch)getActive()).isStack())) item.setEnabled(false);
2041 item = new JMenuItem("Align layers"); item.addActionListener(this); popup.add(item);
2042 if (1 == layer.getParent().size()) item.setEnabled(false);
2043 item = new JMenuItem("Align multi-layer mosaic"); item.addActionListener(this); popup.add(item);
2044 if (1 == layer.getParent().size()) item.setEnabled(false);
2045 return popup;
2049 JMenu adjust_menu = new JMenu("Adjust");
2051 if (null != active) {
2052 if (!canvas.isTransforming()) {
2053 if (active instanceof Profile) {
2054 item = new JMenuItem("Duplicate, link and send to next layer"); item.addActionListener(this); popup.add(item);
2055 Layer nl = layer.getParent().next(layer);
2056 if (nl == layer) item.setEnabled(false);
2057 item = new JMenuItem("Duplicate, link and send to previous layer"); item.addActionListener(this); popup.add(item);
2058 nl = layer.getParent().previous(layer);
2059 if (nl == layer) item.setEnabled(false);
2061 menu = new JMenu("Duplicate, link and send to");
2062 ArrayList al = layer.getParent().getLayers();
2063 Iterator it = al.iterator();
2064 int i = 1;
2065 while (it.hasNext()) {
2066 Layer la = (Layer)it.next();
2067 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
2068 if (la == this.layer) item.setEnabled(false);
2069 i++;
2071 popup.add(menu);
2072 item = new JMenuItem("Duplicate, link and send to..."); item.addActionListener(this); popup.add(item);
2074 popup.addSeparator();
2076 item = new JMenuItem("Unlink from images"); item.addActionListener(this); popup.add(item);
2077 if (!active.isLinked()) item.setEnabled(false); // isLinked() checks if it's linked to a Patch in its own layer
2078 item = new JMenuItem("Show in 3D"); item.addActionListener(this); popup.add(item);
2079 popup.addSeparator();
2080 } else if (active instanceof Patch) {
2081 item = new JMenuItem("Unlink from images"); item.addActionListener(this); popup.add(item);
2082 if (!active.isLinked(Patch.class)) item.setEnabled(false);
2083 if (((Patch)active).isStack()) {
2084 item = new JMenuItem("Unlink slices"); item.addActionListener(this); popup.add(item);
2086 int n_sel_patches = selection.getSelected(Patch.class).size();
2087 if (1 == n_sel_patches) {
2088 item = new JMenuItem("Snap"); item.addActionListener(this); popup.add(item);
2089 } else if (n_sel_patches > 1) {
2090 item = new JMenuItem("Montage"); item.addActionListener(this); popup.add(item);
2091 item = new JMenuItem("Lens correction"); item.addActionListener(this); popup.add(item);
2092 item = new JMenuItem("Blend"); item.addActionListener(this); popup.add(item);
2094 item = new JMenuItem("Link images..."); item.addActionListener(this); popup.add(item);
2095 item = new JMenuItem("View volume"); item.addActionListener(this); popup.add(item);
2096 HashSet hs = active.getLinked(Patch.class);
2097 if (null == hs || 0 == hs.size()) item.setEnabled(false);
2098 item = new JMenuItem("View orthoslices"); item.addActionListener(this); popup.add(item);
2099 if (null == hs || 0 == hs.size()) item.setEnabled(false); // if no Patch instances among the directly linked, then it's not a stack
2100 popup.addSeparator();
2101 } else {
2102 item = new JMenuItem("Unlink"); item.addActionListener(this); popup.add(item);
2103 item = new JMenuItem("Show in 3D"); item.addActionListener(this); popup.add(item);
2104 popup.addSeparator();
2106 if (active instanceof AreaList) {
2107 item = new JMenuItem("Merge"); item.addActionListener(this); popup.add(item);
2108 ArrayList al = selection.getSelected();
2109 int n = 0;
2110 for (Iterator it = al.iterator(); it.hasNext(); ) {
2111 if (it.next().getClass() == AreaList.class) n++;
2113 if (n < 2) item.setEnabled(false);
2114 } else if (active instanceof Pipe) {
2115 item = new JMenuItem("Identify..."); item.addActionListener(this); popup.add(item);
2116 item = new JMenuItem("Identify with axes..."); item.addActionListener(this); popup.add(item);
2119 if (canvas.isTransforming()) {
2120 item = new JMenuItem("Apply transform"); item.addActionListener(this); popup.add(item);
2121 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
2122 } else {
2123 item = new JMenuItem("Transform"); item.addActionListener(this); popup.add(item);
2124 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0, true));
2126 item = new JMenuItem("Cancel transform"); item.addActionListener(this); popup.add(item);
2127 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2128 if (!canvas.isTransforming()) item.setEnabled(false);
2129 if (canvas.isTransforming()) {
2130 item = new JMenuItem("Specify transform..."); item.addActionListener(this); popup.add(item);
2133 if (!canvas.isTransforming()) {
2134 item = new JMenuItem("Color..."); item.addActionListener(this); popup.add(item);
2135 if (active instanceof LayerSet) item.setEnabled(false);
2136 if (active.isLocked()) {
2137 item = new JMenuItem("Unlock"); item.addActionListener(this); popup.add(item);
2138 } else {
2139 item = new JMenuItem("Lock"); item.addActionListener(this); popup.add(item);
2141 menu = new JMenu("Move");
2142 popup.addSeparator();
2143 LayerSet ls = layer.getParent();
2144 item = new JMenuItem("Move to top"); item.addActionListener(this); menu.add(item);
2145 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.
2146 if (ls.isTop(active)) item.setEnabled(false);
2147 item = new JMenuItem("Move up"); item.addActionListener(this); menu.add(item);
2148 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0, true));
2149 if (ls.isTop(active)) item.setEnabled(false);
2150 item = new JMenuItem("Move down"); item.addActionListener(this); menu.add(item);
2151 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0, true));
2152 if (ls.isBottom(active)) item.setEnabled(false);
2153 item = new JMenuItem("Move to bottom"); item.addActionListener(this); menu.add(item);
2154 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_END, 0, true));
2155 if (ls.isBottom(active)) item.setEnabled(false);
2157 popup.add(menu);
2158 popup.addSeparator();
2159 item = new JMenuItem("Delete..."); item.addActionListener(this); popup.add(item);
2160 try {
2161 if (active instanceof Patch) {
2162 if (!active.isOnlyLinkedTo(Patch.class)) {
2163 item.setEnabled(false);
2165 } else if (!(active instanceof DLabel)) { // can't delete elements from the trees (Profile, Pipe, LayerSet)
2166 item.setEnabled(false);
2168 } catch (Exception e) { IJError.print(e); item.setEnabled(false); }
2170 if (active instanceof Patch) {
2171 item = new JMenuItem("Revert"); item.addActionListener(this); popup.add(item);
2172 popup.addSeparator();
2174 item = new JMenuItem("Properties..."); item.addActionListener(this); popup.add(item);
2175 item = new JMenuItem("Show centered"); item.addActionListener(this); popup.add(item);
2177 popup.addSeparator();
2179 if (! (active instanceof ZDisplayable)) {
2180 ArrayList al_layers = layer.getParent().getLayers();
2181 int i_layer = al_layers.indexOf(layer);
2182 int n_layers = al_layers.size();
2183 item = new JMenuItem("Send to previous layer"); item.addActionListener(this); popup.add(item);
2184 if (1 == n_layers || 0 == i_layer || active.isLinked()) item.setEnabled(false);
2185 // 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
2186 else if (active instanceof Profile && !active.canSendTo(layer.getParent().previous(layer))) item.setEnabled(false);
2187 item = new JMenuItem("Send to next layer"); item.addActionListener(this); popup.add(item);
2188 if (1 == n_layers || n_layers -1 == i_layer || active.isLinked()) item.setEnabled(false);
2189 else if (active instanceof Profile && !active.canSendTo(layer.getParent().next(layer))) item.setEnabled(false);
2192 menu = new JMenu("Send linked group to...");
2193 if (active.hasLinkedGroupWithinLayer(this.layer)) {
2194 int i = 1;
2195 for (final Layer la : ls.getLayers()) {
2196 String layer_title = i + ": " + la.getTitle();
2197 if (-1 == layer_title.indexOf(' ')) layer_title += " ";
2198 item = new JMenuItem(layer_title); item.addActionListener(this); menu.add(item);
2199 if (la == this.layer) item.setEnabled(false);
2200 i++;
2202 popup.add(menu);
2203 } else {
2204 menu.setEnabled(false);
2205 //Utils.log("Active's linked group not within layer.");
2207 popup.add(menu);
2208 popup.addSeparator();
2213 if (!canvas.isTransforming()) {
2215 item = new JMenuItem("Undo");item.addActionListener(this); popup.add(item);
2216 if (!layer.getParent().canUndo() || canvas.isTransforming()) item.setEnabled(false);
2217 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Utils.getControlModifier(), true));
2218 item = new JMenuItem("Redo");item.addActionListener(this); popup.add(item);
2219 if (!layer.getParent().canRedo() || canvas.isTransforming()) item.setEnabled(false);
2220 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Event.ALT_MASK, true));
2222 item = new JMenuItem("Enhance contrast layer-wise..."); item.addActionListener(this); adjust_menu.add(item);
2223 item = new JMenuItem("Enhance contrast (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
2224 if (selection.isEmpty()) item.setEnabled(false);
2225 item = new JMenuItem("Set Min and Max layer-wise..."); item.addActionListener(this); adjust_menu.add(item);
2226 item = new JMenuItem("Set Min and Max (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
2227 if (selection.isEmpty()) item.setEnabled(false);
2228 popup.add(adjust_menu);
2229 popup.addSeparator();
2231 // Would get so much simpler with a clojure macro ...
2233 try {
2234 menu = new JMenu("Hide/Unhide");
2235 item = new JMenuItem("Hide deselected"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.SHIFT_MASK, true));
2236 boolean none = 0 == selection.getNSelected();
2237 if (none) item.setEnabled(false);
2238 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));
2239 if (none) item.setEnabled(false);
2240 item = new JMenuItem("Hide selected"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, 0, true));
2241 if (none) item.setEnabled(false);
2242 none = ! layer.getParent().containsDisplayable(DLabel.class);
2243 item = new JMenuItem("Hide all labels"); item.addActionListener(this); menu.add(item);
2244 if (none) item.setEnabled(false);
2245 item = new JMenuItem("Unhide all labels"); item.addActionListener(this); menu.add(item);
2246 if (none) item.setEnabled(false);
2247 none = ! layer.getParent().contains(AreaList.class);
2248 item = new JMenuItem("Hide all arealists"); item.addActionListener(this); menu.add(item);
2249 if (none) item.setEnabled(false);
2250 item = new JMenuItem("Unhide all arealists"); item.addActionListener(this); menu.add(item);
2251 if (none) item.setEnabled(false);
2252 none = ! layer.contains(Profile.class);
2253 item = new JMenuItem("Hide all profiles"); item.addActionListener(this); menu.add(item);
2254 if (none) item.setEnabled(false);
2255 item = new JMenuItem("Unhide all profiles"); item.addActionListener(this); menu.add(item);
2256 if (none) item.setEnabled(false);
2257 none = ! layer.getParent().contains(Pipe.class);
2258 item = new JMenuItem("Hide all pipes"); item.addActionListener(this); menu.add(item);
2259 if (none) item.setEnabled(false);
2260 item = new JMenuItem("Unhide all pipes"); item.addActionListener(this); menu.add(item);
2261 if (none) item.setEnabled(false);
2262 none = ! layer.getParent().contains(Polyline.class);
2263 item = new JMenuItem("Hide all polylines"); item.addActionListener(this); menu.add(item);
2264 if (none) item.setEnabled(false);
2265 item = new JMenuItem("Unhide all polylines"); item.addActionListener(this); menu.add(item);
2266 if (none) item.setEnabled(false);
2267 none = ! layer.getParent().contains(Ball.class);
2268 item = new JMenuItem("Hide all balls"); item.addActionListener(this); menu.add(item);
2269 if (none) item.setEnabled(false);
2270 item = new JMenuItem("Unhide all balls"); item.addActionListener(this); menu.add(item);
2271 if (none) item.setEnabled(false);
2272 none = ! layer.getParent().containsDisplayable(Patch.class);
2273 item = new JMenuItem("Hide all images"); item.addActionListener(this); menu.add(item);
2274 if (none) item.setEnabled(false);
2275 item = new JMenuItem("Unhide all images"); item.addActionListener(this); menu.add(item);
2276 if (none) item.setEnabled(false);
2277 item = new JMenuItem("Hide all but images"); item.addActionListener(this); menu.add(item);
2278 item = new JMenuItem("Unhide all"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.ALT_MASK, true));
2280 popup.add(menu);
2281 } catch (Exception e) { IJError.print(e); }
2283 menu = new JMenu("Import");
2284 item = new JMenuItem("Import image"); item.addActionListener(this); menu.add(item);
2285 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, Event.ALT_MASK & Event.SHIFT_MASK, true));
2286 item = new JMenuItem("Import stack..."); item.addActionListener(this); menu.add(item);
2287 item = new JMenuItem("Import grid..."); item.addActionListener(this); menu.add(item);
2288 item = new JMenuItem("Import sequence as grid..."); item.addActionListener(this); menu.add(item);
2289 item = new JMenuItem("Import from text file..."); item.addActionListener(this); menu.add(item);
2290 item = new JMenuItem("Import labels as arealists..."); item.addActionListener(this); menu.add(item);
2291 popup.add(menu);
2293 menu = new JMenu("Export");
2294 item = new JMenuItem("Make flat image..."); item.addActionListener(this); menu.add(item);
2295 item = new JMenuItem("Arealists as labels (tif)"); item.addActionListener(this); menu.add(item);
2296 if (0 == layer.getParent().getZDisplayables(AreaList.class).size()) item.setEnabled(false);
2297 item = new JMenuItem("Arealists as labels (amira)"); item.addActionListener(this); menu.add(item);
2298 if (0 == layer.getParent().getZDisplayables(AreaList.class).size()) item.setEnabled(false);
2299 popup.add(menu);
2301 menu = new JMenu("Display");
2302 item = new JMenuItem("Resize canvas/LayerSet..."); item.addActionListener(this); menu.add(item);
2303 item = new JMenuItem("Autoresize canvas/LayerSet"); item.addActionListener(this); menu.add(item);
2304 // OBSOLETE // item = new JMenuItem("Rotate Layer/LayerSet..."); item.addActionListener(this); menu.add(item);
2305 item = new JMenuItem("Properties ..."); item.addActionListener(this); menu.add(item);
2306 popup.add(menu);
2308 menu = new JMenu("Project");
2309 this.project.getLoader().setupMenuItems(menu, this.getProject());
2310 item = new JMenuItem("Project properties..."); item.addActionListener(this); menu.add(item);
2311 item = new JMenuItem("Create subproject"); item.addActionListener(this); menu.add(item);
2312 if (null == canvas.getFakeImagePlus().getRoi()) item.setEnabled(false);
2313 item = new JMenuItem("Release memory..."); item.addActionListener(this); menu.add(item);
2314 item = new JMenuItem("Flush image cache"); item.addActionListener(this); menu.add(item);
2315 popup.add(menu);
2317 menu = new JMenu("Selection");
2318 item = new JMenuItem("Select all"); item.addActionListener(this); menu.add(item);
2319 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, Utils.getControlModifier(), true));
2320 if (0 == layer.getDisplayables().size() && 0 == layer.getParent().getZDisplayables().size()) item.setEnabled(false);
2321 item = new JMenuItem("Select none"); item.addActionListener(this); menu.add(item);
2322 if (0 == selection.getNSelected()) item.setEnabled(false);
2323 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2325 JMenu bytype = new JMenu("Select all by type");
2326 item = new JMenuItem("AreaList"); item.addActionListener(bytypelistener); bytype.add(item);
2327 item = new JMenuItem("Ball"); item.addActionListener(bytypelistener); bytype.add(item);
2328 item = new JMenuItem("Dissector"); item.addActionListener(bytypelistener); bytype.add(item);
2329 item = new JMenuItem("Image"); item.addActionListener(bytypelistener); bytype.add(item);
2330 item = new JMenuItem("Text"); item.addActionListener(bytypelistener); bytype.add(item);
2331 item = new JMenuItem("Pipe"); item.addActionListener(bytypelistener); bytype.add(item);
2332 item = new JMenuItem("Polyline"); item.addActionListener(bytypelistener); bytype.add(item);
2333 item = new JMenuItem("Profile"); item.addActionListener(bytypelistener); bytype.add(item);
2334 menu.add(bytype);
2336 item = new JMenuItem("Restore selection"); item.addActionListener(this); menu.add(item);
2337 item = new JMenuItem("Select under ROI"); item.addActionListener(this); menu.add(item);
2338 if (canvas.getFakeImagePlus().getRoi() == null) item.setEnabled(false);
2339 popup.add(menu);
2340 item = new JMenuItem("Search..."); item.addActionListener(this); popup.add(item);
2343 //canvas.add(popup);
2344 return popup;
2347 private ByTypeListener bytypelistener = new ByTypeListener(this);
2349 static private class ByTypeListener implements ActionListener {
2350 final Display d;
2351 ByTypeListener(final Display d) {
2352 this.d = d;
2354 public void actionPerformed(final ActionEvent ae) {
2355 final String command = ae.getActionCommand();
2357 final java.awt.geom.Area aroi = Utils.getArea(d.canvas.getFakeImagePlus().getRoi());
2359 d.dispatcher.exec(new Runnable() { public void run() {
2361 try {
2362 String type = command;
2363 if (type.equals("Image")) type = "Patch";
2364 Class c = Class.forName("ini.trakem2.display." + type);
2366 java.util.List<Displayable> a = new ArrayList<Displayable>();
2367 if (null != aroi) {
2368 a.addAll(d.layer.getDisplayables(c, aroi, true));
2369 a.addAll(d.layer.getParent().getZDisplayables(c, d.layer, aroi, true));
2370 } else {
2371 a.addAll(d.layer.getDisplayables(c));
2372 a.addAll(d.layer.getParent().getZDisplayables(c));
2373 // Remove non-visible ones
2374 for (final Iterator<Displayable> it = a.iterator(); it.hasNext(); ) {
2375 if (!it.next().isVisible()) it.remove();
2379 if (0 == a.size()) return;
2381 boolean selected = false;
2383 if (0 == ae.getModifiers()) {
2384 Utils.log2("first");
2385 d.selection.clear();
2386 d.selection.selectAll(a);
2387 selected = true;
2388 } else if (0 == (ae.getModifiers() ^ Event.SHIFT_MASK)) {
2389 Utils.log2("with shift");
2390 d.selection.selectAll(a); // just add them to the current selection
2391 selected = true;
2393 if (selected) {
2394 // Activate last:
2395 d.selection.setActive(a.get(a.size() -1));
2398 } catch (ClassNotFoundException e) {
2399 Utils.log2(e.toString());
2402 }});
2406 /** Check if a panel for the given Displayable is completely visible in the JScrollPane */
2407 public boolean isWithinViewport(final Displayable d) {
2408 final JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
2409 if (ht_tabs.get(d.getClass()) == scroll) return isWithinViewport(scroll, ht_panels.get(d));
2410 return false;
2413 private boolean isWithinViewport(JScrollPane scroll, DisplayablePanel dp) {
2414 if(null == dp) return false;
2415 JViewport view = scroll.getViewport();
2416 java.awt.Dimension dimensions = view.getExtentSize();
2417 java.awt.Point p = view.getViewPosition();
2418 int y = dp.getY();
2419 if ((y + DisplayablePanel.HEIGHT - p.y) <= dimensions.height && y >= p.y) {
2420 return true;
2422 return false;
2425 /** Check if a panel for the given Displayable is partially visible in the JScrollPane */
2426 public boolean isPartiallyWithinViewport(final Displayable d) {
2427 final JScrollPane scroll = ht_tabs.get(d.getClass());
2428 if (tabs.getSelectedComponent() == scroll) return isPartiallyWithinViewport(scroll, ht_panels.get(d));
2429 return false;
2432 /** Check if a panel for the given Displayable is at least partially visible in the JScrollPane */
2433 private boolean isPartiallyWithinViewport(final JScrollPane scroll, final DisplayablePanel dp) {
2434 if(null == dp) {
2435 //Utils.log2("Display.isPartiallyWithinViewport: null DisplayablePanel ??");
2436 return false; // to fast for you baby
2438 JViewport view = scroll.getViewport();
2439 java.awt.Dimension dimensions = view.getExtentSize();
2440 java.awt.Point p = view.getViewPosition();
2441 int y = dp.getY();
2442 if ( ((y + DisplayablePanel.HEIGHT - p.y) <= dimensions.height && y >= p.y) // completely visible
2443 || ((y + DisplayablePanel.HEIGHT - p.y) > dimensions.height && y < p.y + dimensions.height) // partially hovering at the bottom
2444 || ((y + DisplayablePanel.HEIGHT) > p.y && y < p.y) // partially hovering at the top
2446 return true;
2448 return false;
2451 /** A function to make a Displayable panel be visible in the screen, by scrolling the viewport of the JScrollPane. */
2452 private void scrollToShow(final Displayable d) {
2453 dispatcher.execSwing(new Runnable() { public void run() {
2454 final JScrollPane scroll = (JScrollPane)tabs.getSelectedComponent();
2455 if (d instanceof ZDisplayable && scroll == scroll_zdispl) {
2456 scrollToShow(scroll_zdispl, ht_panels.get(d));
2457 return;
2459 final Class c = d.getClass();
2460 if (Patch.class == c && scroll == scroll_patches) {
2461 scrollToShow(scroll_patches, ht_panels.get(d));
2462 } else if (DLabel.class == c && scroll == scroll_labels) {
2463 scrollToShow(scroll_labels, ht_panels.get(d));
2464 } else if (Profile.class == c && scroll == scroll_profiles) {
2465 scrollToShow(scroll_profiles, ht_panels.get(d));
2467 }});
2470 private void scrollToShow(final JScrollPane scroll, final JPanel dp) {
2471 if (null == dp) return;
2472 JViewport view = scroll.getViewport();
2473 Point current = view.getViewPosition();
2474 Dimension extent = view.getExtentSize();
2475 int panel_y = dp.getY();
2476 if ((panel_y + DisplayablePanel.HEIGHT - current.y) <= extent.height && panel_y >= current.y) {
2477 // it's completely visible already
2478 return;
2479 } else {
2480 // scroll just enough
2481 // if it's above, show at the top
2482 if (panel_y - current.y < 0) {
2483 view.setViewPosition(new Point(0, panel_y));
2485 // if it's below (even if partially), show at the bottom
2486 else if (panel_y + 50 > current.y + extent.height) {
2487 view.setViewPosition(new Point(0, panel_y - extent.height + 50));
2488 //Utils.log("Display.scrollToShow: panel_y: " + panel_y + " current.y: " + current.y + " extent.height: " + extent.height);
2493 /** Update the title of the given Displayable in its DisplayablePanel, if any. */
2494 static public void updateTitle(final Layer layer, final Displayable displ) {
2495 for (final Display d : al_displays) {
2496 if (layer == d.layer) {
2497 DisplayablePanel dp = d.ht_panels.get(displ);
2498 if (null != dp) dp.updateTitle();
2503 /** Update the Display's title in all Displays showing the given Layer. */
2504 static public void updateTitle(final Layer layer) {
2505 for (final Display d : al_displays) {
2506 if (d.layer == layer) {
2507 d.updateTitle();
2511 /** Update the Display's title in all Displays showing a Layer of the given LayerSet. */
2512 static public void updateTitle(final LayerSet ls) {
2513 for (final Display d : al_displays) {
2514 if (d.layer.getParent() == ls) {
2515 d.updateTitle();
2520 /** Set a new title in the JFrame, showing info on the layer 'z' and the magnification. */
2521 public void updateTitle() {
2522 // From ij.ImagePlus class, the solution:
2523 String scale = "";
2524 final double magnification = canvas.getMagnification();
2525 if (magnification!=1.0) {
2526 final double percent = magnification*100.0;
2527 scale = new StringBuffer(" (").append(Utils.d2s(percent, percent==(int)percent ? 0 : 1)).append("%)").toString();
2529 final Calibration cal = layer.getParent().getCalibration();
2530 String title = new StringBuffer().append(layer.getParent().indexOf(layer) + 1).append('/').append(layer.getParent().size()).append(' ').append((null == layer.getTitle() ? "" : layer.getTitle())).append(scale).append(" -- ").append(getProject().toString()).append(' ').append(' ').append(Utils.cutNumber(layer.getParent().getLayerWidth() * cal.pixelWidth, 2, true)).append('x').append(Utils.cutNumber(layer.getParent().getLayerHeight() * cal.pixelHeight, 2, true)).append(' ').append(cal.getUnit()).toString();
2531 frame.setTitle(title);
2532 // fix the title for the FakeImageWindow and thus the WindowManager listing in the menus
2533 canvas.getFakeImagePlus().setTitle(title);
2536 /** 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. */
2537 public void nextLayer(final int modifiers) {
2538 final Layer l;
2539 if (0 == (modifiers ^ Event.SHIFT_MASK)) {
2540 l = layer.getParent().nextNonEmpty(layer);
2541 } else if (scroll_step > 1) {
2542 int i = layer.getParent().indexOf(this.layer);
2543 Layer la = layer.getParent().getLayer(i + scroll_step);
2544 if (null != la) l = la;
2545 else l = null;
2546 } else {
2547 l = layer.getParent().next(layer);
2549 if (l != layer) {
2550 slt.set(l);
2551 updateInDatabase("layer_id");
2555 private final void translateLayerColors(final Layer current, final Layer other) {
2556 if (current == other) return;
2557 if (layer_channels.size() > 0) {
2558 final LayerSet ls = getLayerSet();
2559 // translate colors by distance from current layer to new Layer l
2560 final int dist = ls.indexOf(other) - ls.indexOf(current);
2561 translateLayerColor(Color.red, dist);
2562 translateLayerColor(Color.blue, dist);
2566 private final void translateLayerColor(final Color color, final int dist) {
2567 final LayerSet ls = getLayerSet();
2568 final Layer l = layer_channels.get(color);
2569 if (null == l) return;
2570 updateColor(Color.white, layer_panels.get(l));
2571 final Layer l2 = ls.getLayer(ls.indexOf(l) + dist);
2572 if (null != l2) updateColor(color, layer_panels.get(l2));
2575 private final void updateColor(final Color color, final LayerPanel lp) {
2576 lp.setColor(color);
2577 setColorChannel(lp.layer, color);
2580 /** Calls setLayer(la) on the SetLayerThread. */
2581 public void toLayer(final Layer la) {
2582 if (la.getParent() != layer.getParent()) return; // not of the same LayerSet
2583 if (la == layer) return; // nothing to do
2584 slt.set(la);
2585 updateInDatabase("layer_id");
2588 /** 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. */
2589 public void previousLayer(final int modifiers) {
2590 final Layer l;
2591 if (0 == (modifiers ^ Event.SHIFT_MASK)) {
2592 l = layer.getParent().previousNonEmpty(layer);
2593 } else if (scroll_step > 1) {
2594 int i = layer.getParent().indexOf(this.layer);
2595 Layer la = layer.getParent().getLayer(i - scroll_step);
2596 if (null != la) l = la;
2597 else l = null;
2598 } else {
2599 l = layer.getParent().previous(layer);
2601 if (l != layer) {
2602 slt.set(l);
2603 updateInDatabase("layer_id");
2607 static public void updateLayerScroller(LayerSet set) {
2608 for (final Display d : al_displays) {
2609 if (d.layer.getParent() == set) {
2610 d.updateLayerScroller(d.layer);
2615 private void updateLayerScroller(Layer layer) {
2616 int size = layer.getParent().size();
2617 if (size <= 1) {
2618 scroller.setValues(0, 1, 0, 0);
2619 scroller.setEnabled(false);
2620 } else {
2621 scroller.setEnabled(true);
2622 scroller.setValues(layer.getParent().getLayerIndex(layer.getId()), 1, 0, size);
2624 recreateLayerPanels(layer);
2627 // Can't use this.layer, may still be null. User argument instead.
2628 private synchronized void recreateLayerPanels(final Layer layer) {
2629 synchronized (layer_channels) {
2630 panel_layers.removeAll();
2632 if (0 == layer_panels.size()) {
2633 for (final Layer la : layer.getParent().getLayers()) {
2634 final LayerPanel lp = new LayerPanel(this, la);
2635 layer_panels.put(la, lp);
2636 this.panel_layers.add(lp);
2638 } else {
2639 // Set theory at work: keep old to reuse
2640 layer_panels.keySet().retainAll(layer.getParent().getLayers());
2641 for (final Layer la : layer.getParent().getLayers()) {
2642 LayerPanel lp = layer_panels.get(la);
2643 if (null == lp) {
2644 lp = new LayerPanel(this, la);
2645 layer_panels.put(la, lp);
2647 this.panel_layers.add(lp);
2649 for (final Iterator<Map.Entry<Integer,LayerPanel>> it = layer_alpha.entrySet().iterator(); it.hasNext(); ) {
2650 final Map.Entry<Integer,LayerPanel> e = it.next();
2651 if (-1 == getLayerSet().indexOf(e.getValue().layer)) it.remove();
2653 for (final Iterator<Map.Entry<Color,Layer>> it = layer_channels.entrySet().iterator(); it.hasNext(); ) {
2654 final Map.Entry<Color,Layer> e = it.next();
2655 if (-1 == getLayerSet().indexOf(e.getValue())) it.remove();
2657 scroll_layers.repaint();
2662 private void updateSnapshots() {
2663 Enumeration<DisplayablePanel> e = ht_panels.elements();
2664 while (e.hasMoreElements()) {
2665 e.nextElement().remake();
2667 Utils.updateComponent(tabs.getSelectedComponent());
2670 static public void updatePanel(Layer layer, final Displayable displ) {
2671 if (null == layer && null != front) layer = front.layer; // the front layer
2672 for (final Display d : al_displays) {
2673 if (d.layer == layer) {
2674 d.updatePanel(displ);
2679 private void updatePanel(Displayable d) {
2680 JPanel c = null;
2681 if (d instanceof Profile) {
2682 c = panel_profiles;
2683 } else if (d instanceof Patch) {
2684 c = panel_patches;
2685 } else if (d instanceof DLabel) {
2686 c = panel_labels;
2687 } else if (d instanceof Pipe) {
2688 c = panel_zdispl;
2690 if (null == c) return;
2691 DisplayablePanel dp = ht_panels.get(d);
2692 dp.remake();
2693 Utils.updateComponent(c);
2696 static public void updatePanelIndex(final Layer layer, final Displayable displ) {
2697 for (final Display d : al_displays) {
2698 if (d.layer == layer || displ instanceof ZDisplayable) {
2699 d.updatePanelIndex(displ);
2704 private void updatePanelIndex(final Displayable d) {
2705 // find first of the kind, then remove and insert its panel
2706 int i = 0;
2707 JPanel c = null;
2708 if (d instanceof ZDisplayable) {
2709 i = layer.getParent().indexOf((ZDisplayable)d);
2710 c = panel_zdispl;
2711 } else {
2712 i = layer.relativeIndexOf(d);
2713 if (d instanceof Profile) {
2714 c = panel_profiles;
2715 } else if (d instanceof Patch) {
2716 c = panel_patches;
2717 } else if (d instanceof DLabel) {
2718 c = panel_labels;
2721 if (null == c) return;
2722 DisplayablePanel dp = ht_panels.get(d);
2723 if (null == dp) return; // may be half-baked, wait
2724 c.remove(dp);
2725 c.add(dp, i); // java and its fabulous consistency
2726 // not enough! Utils.updateComponent(c);
2727 // So, cocktail:
2728 c.invalidate();
2729 c.validate();
2730 Utils.updateComponent(c);
2733 /** Repair possibly missing panels and other components by simply resetting the same Layer */
2734 public void repairGUI() {
2735 Layer layer = this.layer;
2736 this.layer = null;
2737 setLayer(layer);
2740 public void actionPerformed(final ActionEvent ae) {
2741 dispatcher.exec(new Runnable() { public void run() {
2743 String command = ae.getActionCommand();
2744 if (command.startsWith("Job")) {
2745 if (Utils.checkYN("Really cancel job?")) {
2746 project.getLoader().quitJob(command);
2747 repairGUI();
2749 return;
2750 } else if (command.equals("Move to top")) {
2751 if (null == active) return;
2752 canvas.setUpdateGraphics(true);
2753 layer.getParent().move(LayerSet.TOP, active);
2754 Display.repaint(layer.getParent(), active, 5);
2755 //Display.updatePanelIndex(layer, active);
2756 } else if (command.equals("Move up")) {
2757 if (null == active) return;
2758 canvas.setUpdateGraphics(true);
2759 layer.getParent().move(LayerSet.UP, active);
2760 Display.repaint(layer.getParent(), active, 5);
2761 //Display.updatePanelIndex(layer, active);
2762 } else if (command.equals("Move down")) {
2763 if (null == active) return;
2764 canvas.setUpdateGraphics(true);
2765 layer.getParent().move(LayerSet.DOWN, active);
2766 Display.repaint(layer.getParent(), active, 5);
2767 //Display.updatePanelIndex(layer, active);
2768 } else if (command.equals("Move to bottom")) {
2769 if (null == active) return;
2770 canvas.setUpdateGraphics(true);
2771 layer.getParent().move(LayerSet.BOTTOM, active);
2772 Display.repaint(layer.getParent(), active, 5);
2773 //Display.updatePanelIndex(layer, active);
2774 } else if (command.equals("Duplicate, link and send to next layer")) {
2775 duplicateLinkAndSendTo(active, 1, layer.getParent().next(layer));
2776 } else if (command.equals("Duplicate, link and send to previous layer")) {
2777 duplicateLinkAndSendTo(active, 0, layer.getParent().previous(layer));
2778 } else if (command.equals("Duplicate, link and send to...")) {
2779 // fix non-scrolling popup menu
2780 GenericDialog gd = new GenericDialog("Send to");
2781 gd.addMessage("Duplicate, link and send to...");
2782 String[] sl = new String[layer.getParent().size()];
2783 int next = 0;
2784 for (Iterator it = layer.getParent().getLayers().iterator(); it.hasNext(); ) {
2785 sl[next++] = project.findLayerThing(it.next()).toString();
2787 gd.addChoice("Layer: ", sl, sl[layer.getParent().indexOf(layer)]);
2788 gd.showDialog();
2789 if (gd.wasCanceled()) return;
2790 Layer la = layer.getParent().getLayer(gd.getNextChoiceIndex());
2791 if (layer == la) {
2792 Utils.showMessage("Can't duplicate, link and send to the same layer.");
2793 return;
2795 duplicateLinkAndSendTo(active, 0, la);
2796 } else if (-1 != command.indexOf("z = ")) {
2797 // this is an item from the "Duplicate, link and send to" menu of layer z's
2798 Layer target_layer = layer.getParent().getLayer(Double.parseDouble(command.substring(command.lastIndexOf(' ') +1)));
2799 Utils.log2("layer: __" +command.substring(command.lastIndexOf(' ') +1) + "__");
2800 if (null == target_layer) return;
2801 duplicateLinkAndSendTo(active, 0, target_layer);
2802 } else if (-1 != command.indexOf("z=")) {
2803 // WARNING the indexOf is very similar to the previous one
2804 // Send the linked group to the selected layer
2805 int iz = command.indexOf("z=")+2;
2806 Utils.log2("iz=" + iz + " other: " + command.indexOf(' ', iz+2));
2807 int end = command.indexOf(' ', iz);
2808 if (-1 == end) end = command.length();
2809 double lz = Double.parseDouble(command.substring(iz, end));
2810 Layer target = layer.getParent().getLayer(lz);
2811 HashSet hs = active.getLinkedGroup(new HashSet());
2812 layer.getParent().move(hs, active.getLayer(), target);
2813 } else if (command.equals("Unlink")) {
2814 if (null == active || active instanceof Patch) return;
2815 active.unlink();
2816 updateSelection();//selection.update();
2817 } else if (command.equals("Unlink from images")) {
2818 if (null == active) return;
2819 try {
2820 for (Displayable displ: selection.getSelected()) {
2821 displ.unlinkAll(Patch.class);
2823 updateSelection();//selection.update();
2824 } catch (Exception e) { IJError.print(e); }
2825 } else if (command.equals("Unlink slices")) {
2826 YesNoCancelDialog yn = new YesNoCancelDialog(frame, "Attention", "Really unlink all slices from each other?\nThere is no undo.");
2827 if (!yn.yesPressed()) return;
2828 final ArrayList<Patch> pa = ((Patch)active).getStackPatches();
2829 for (int i=pa.size()-1; i>0; i--) {
2830 pa.get(i).unlink(pa.get(i-1));
2832 } else if (command.equals("Send to next layer")) {
2833 Rectangle box = selection.getBox();
2834 try {
2835 // unlink Patch instances
2836 for (final Displayable displ : selection.getSelected()) {
2837 displ.unlinkAll(Patch.class);
2839 updateSelection();//selection.update();
2840 } catch (Exception e) { IJError.print(e); }
2841 //layer.getParent().moveDown(layer, active); // will repaint whatever appropriate layers
2842 selection.moveDown();
2843 repaint(layer.getParent(), box);
2844 } else if (command.equals("Send to previous layer")) {
2845 Rectangle box = selection.getBox();
2846 try {
2847 // unlink Patch instances
2848 for (final Displayable displ : selection.getSelected()) {
2849 displ.unlinkAll(Patch.class);
2851 updateSelection();//selection.update();
2852 } catch (Exception e) { IJError.print(e); }
2853 //layer.getParent().moveUp(layer, active); // will repaint whatever appropriate layers
2854 selection.moveUp();
2855 repaint(layer.getParent(), box);
2856 } else if (command.equals("Show centered")) {
2857 if (active == null) return;
2858 showCentered(active);
2859 } else if (command.equals("Delete...")) {
2861 if (null != active) {
2862 Displayable d = active;
2863 selection.remove(d);
2864 d.remove(true); // will repaint
2867 // remove all selected objects
2868 selection.deleteAll();
2869 } else if (command.equals("Color...")) {
2870 IJ.doCommand("Color Picker...");
2871 } else if (command.equals("Revert")) {
2872 if (null == active || active.getClass() != Patch.class) return;
2873 Patch p = (Patch)active;
2874 if (!p.revert()) {
2875 if (null == p.getOriginalPath()) Utils.log("No editions to save for patch " + p.getTitle() + " #" + p.getId());
2876 else Utils.log("Could not revert Patch " + p.getTitle() + " #" + p.getId());
2878 } else if (command.equals("Undo")) {
2879 Bureaucrat.createAndStart(new Worker.Task("Undo") { public void exec() {
2880 layer.getParent().undoOneStep();
2881 Display.repaint(layer.getParent());
2882 }}, project);
2883 } else if (command.equals("Redo")) {
2884 Bureaucrat.createAndStart(new Worker.Task("Redo") { public void exec() {
2885 layer.getParent().redoOneStep();
2886 Display.repaint(layer.getParent());
2887 }}, project);
2888 } else if (command.equals("Transform")) {
2889 if (null == active) return;
2890 canvas.setTransforming(true);
2891 } else if (command.equals("Apply transform")) {
2892 if (null == active) return;
2893 canvas.setTransforming(false);
2894 } else if (command.equals("Cancel transform")) {
2895 if (null == active) return;
2896 canvas.cancelTransform();
2897 } else if (command.equals("Specify transform...")) {
2898 if (null == active) return;
2899 selection.specify();
2900 } else if (command.equals("Hide all but images")) {
2901 ArrayList<Class> type = new ArrayList<Class>();
2902 type.add(Patch.class);
2903 selection.removeAll(layer.getParent().hideExcept(type, false));
2904 Display.update(layer.getParent(), false);
2905 } else if (command.equals("Unhide all")) {
2906 layer.getParent().setAllVisible(false);
2907 Display.update(layer.getParent(), false);
2908 } else if (command.startsWith("Hide all ")) {
2909 String type = command.substring(9, command.length() -1); // skip the ending plural 's'
2910 type = type.substring(0, 1).toUpperCase() + type.substring(1);
2911 selection.removeAll(layer.getParent().setVisible(type, false, true));
2912 } else if (command.startsWith("Unhide all ")) {
2913 String type = command.substring(11, command.length() -1); // skip the ending plural 's'
2914 type = type.substring(0, 1).toUpperCase() + type.substring(1);
2915 layer.getParent().setVisible(type, true, true);
2916 } else if (command.equals("Hide deselected")) {
2917 hideDeselected(0 != (ActionEvent.ALT_MASK & ae.getModifiers()));
2918 } else if (command.equals("Hide deselected except images")) {
2919 hideDeselected(true);
2920 } else if (command.equals("Hide selected")) {
2921 selection.setVisible(false); // TODO should deselect them too? I don't think so.
2922 } else if (command.equals("Resize canvas/LayerSet...")) {
2923 resizeCanvas();
2924 } else if (command.equals("Autoresize canvas/LayerSet")) {
2925 layer.getParent().setMinimumDimensions();
2926 } else if (command.equals("Import image")) {
2927 importImage();
2928 } else if (command.equals("Import next image")) {
2929 importNextImage();
2930 } else if (command.equals("Import stack...")) {
2931 Display.this.getLayerSet().addLayerContentStep(layer);
2932 Rectangle sr = getCanvas().getSrcRect();
2933 Bureaucrat burro = project.getLoader().importStack(layer, sr.x + sr.width/2, sr.y + sr.height/2, null, true, null);
2934 burro.addPostTask(new Runnable() { public void run() {
2935 Display.this.getLayerSet().addLayerContentStep(layer);
2936 }});
2937 } else if (command.equals("Import grid...")) {
2938 Display.this.getLayerSet().addLayerContentStep(layer);
2939 Bureaucrat burro = project.getLoader().importGrid(layer);
2940 burro.addPostTask(new Runnable() { public void run() {
2941 Display.this.getLayerSet().addLayerContentStep(layer);
2942 }});
2943 } else if (command.equals("Import sequence as grid...")) {
2944 Display.this.getLayerSet().addLayerContentStep(layer);
2945 Bureaucrat burro = project.getLoader().importSequenceAsGrid(layer);
2946 burro.addPostTask(new Runnable() { public void run() {
2947 Display.this.getLayerSet().addLayerContentStep(layer);
2948 }});
2949 } else if (command.equals("Import from text file...")) {
2950 Display.this.getLayerSet().addLayerContentStep(layer);
2951 Bureaucrat burro = project.getLoader().importImages(layer);
2952 burro.addPostTask(new Runnable() { public void run() {
2953 Display.this.getLayerSet().addLayerContentStep(layer);
2954 }});
2955 } else if (command.equals("Import labels as arealists...")) {
2956 Display.this.getLayerSet().addChangeTreesStep();
2957 Bureaucrat burro = project.getLoader().importLabelsAsAreaLists(layer, null, Double.MAX_VALUE, 0, 0.4f, false);
2958 burro.addPostTask(new Runnable() { public void run() {
2959 Display.this.getLayerSet().addChangeTreesStep();
2960 }});
2961 } else if (command.equals("Make flat image...")) {
2962 // if there's a ROI, just use that as cropping rectangle
2963 Rectangle srcRect = null;
2964 Roi roi = canvas.getFakeImagePlus().getRoi();
2965 if (null != roi) {
2966 srcRect = roi.getBounds();
2967 } else {
2968 // otherwise, whatever is visible
2969 //srcRect = canvas.getSrcRect();
2970 // The above is confusing. That is what ROIs are for. So paint all:
2971 srcRect = new Rectangle(0, 0, (int)Math.ceil(layer.getParent().getLayerWidth()), (int)Math.ceil(layer.getParent().getLayerHeight()));
2973 double scale = 1.0;
2974 final String[] types = new String[]{"8-bit grayscale", "RGB Color"};
2975 int the_type = ImagePlus.GRAY8;
2976 final GenericDialog gd = new GenericDialog("Choose", frame);
2977 gd.addSlider("Scale: ", 1, 100, 100);
2978 gd.addChoice("Type: ", types, types[0]);
2979 if (layer.getParent().size() > 1) {
2981 String[] layers = new String[layer.getParent().size()];
2982 int i = 0;
2983 for (Iterator it = layer.getParent().getLayers().iterator(); it.hasNext(); ) {
2984 layers[i] = layer.getProject().findLayerThing((Layer)it.next()).toString();
2985 i++;
2987 int i_layer = layer.getParent().indexOf(layer);
2988 gd.addChoice("Start: ", layers, layers[i_layer]);
2989 gd.addChoice("End: ", layers, layers[i_layer]);
2991 Utils.addLayerRangeChoices(Display.this.layer, gd); /// $#%! where are my lisp macros
2992 gd.addCheckbox("Include non-empty layers only", true);
2994 gd.addMessage("Background color:");
2995 Utils.addRGBColorSliders(gd, Color.black);
2996 gd.addCheckbox("Best quality", false);
2997 gd.addMessage("");
2998 gd.addCheckbox("Save to file", false);
2999 gd.addCheckbox("Save for web", false);
3000 gd.showDialog();
3001 if (gd.wasCanceled()) return;
3002 scale = gd.getNextNumber() / 100;
3003 the_type = (0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY8 : ImagePlus.COLOR_RGB);
3004 if (Double.isNaN(scale) || scale <= 0.0) {
3005 Utils.showMessage("Invalid scale.");
3006 return;
3008 Layer[] layer_array = null;
3009 boolean non_empty_only = false;
3010 if (layer.getParent().size() > 1) {
3011 non_empty_only = gd.getNextBoolean();
3012 int i_start = gd.getNextChoiceIndex();
3013 int i_end = gd.getNextChoiceIndex();
3014 ArrayList al = new ArrayList();
3015 ArrayList al_zd = layer.getParent().getZDisplayables();
3016 ZDisplayable[] zd = new ZDisplayable[al_zd.size()];
3017 al_zd.toArray(zd);
3018 for (int i=i_start, j=0; i <= i_end; i++, j++) {
3019 Layer la = layer.getParent().getLayer(i);
3020 if (!la.isEmpty() || !non_empty_only) al.add(la); // checks both the Layer and the ZDisplayable objects in the parent LayerSet
3022 if (0 == al.size()) {
3023 Utils.showMessage("All layers are empty!");
3024 return;
3026 layer_array = new Layer[al.size()];
3027 al.toArray(layer_array);
3028 } else {
3029 layer_array = new Layer[]{Display.this.layer};
3031 final Color background = new Color((int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
3032 final boolean quality = gd.getNextBoolean();
3033 final boolean save_to_file = gd.getNextBoolean();
3034 final boolean save_for_web = gd.getNextBoolean();
3035 // in its own thread
3036 if (save_for_web) project.getLoader().makePrescaledTiles(layer_array, Patch.class, srcRect, scale, c_alphas, the_type);
3037 else project.getLoader().makeFlatImage(layer_array, srcRect, scale, c_alphas, the_type, save_to_file, quality, background);
3039 } else if (command.equals("Lock")) {
3040 selection.setLocked(true);
3041 } else if (command.equals("Unlock")) {
3042 selection.setLocked(false);
3043 } else if (command.equals("Properties...")) {
3044 active.adjustProperties();
3045 updateSelection();
3046 } else if (command.equals("Cancel alignment")) {
3047 layer.getParent().cancelAlign();
3048 } else if (command.equals("Align with landmarks")) {
3049 layer.getParent().applyAlign(false);
3050 } else if (command.equals("Align and register")) {
3051 layer.getParent().applyAlign(true);
3052 } else if (command.equals("Align using profiles")) {
3053 if (!selection.contains(Profile.class)) {
3054 Utils.showMessage("No profiles are selected.");
3055 return;
3057 // ask for range of layers
3058 final GenericDialog gd = new GenericDialog("Choose range");
3059 Utils.addLayerRangeChoices(Display.this.layer, gd);
3060 gd.showDialog();
3061 if (gd.wasCanceled()) return;
3062 Layer la_start = layer.getParent().getLayer(gd.getNextChoiceIndex());
3063 Layer la_end = layer.getParent().getLayer(gd.getNextChoiceIndex());
3064 if (la_start == la_end) {
3065 Utils.showMessage("Need at least two layers.");
3066 return;
3068 if (selection.isLocked()) {
3069 Utils.showMessage("There are locked objects.");
3070 return;
3072 layer.getParent().startAlign(Display.this);
3073 layer.getParent().applyAlign(la_start, la_end, selection);
3074 } else if (command.equals("Align stack slices")) {
3075 if (getActive() instanceof Patch) {
3076 final Patch slice = (Patch)getActive();
3077 if (slice.isStack()) {
3078 // check linked group
3079 final HashSet hs = slice.getLinkedGroup(new HashSet());
3080 for (Iterator it = hs.iterator(); it.hasNext(); ) {
3081 if (it.next().getClass() != Patch.class) {
3082 Utils.showMessage("Images are linked to other objects, can't proceed to cross-correlate them."); // labels should be fine, need to check that
3083 return;
3086 final LayerSet ls = slice.getLayerSet();
3087 final HashSet<Displayable> linked = slice.getLinkedGroup(null);
3088 ls.addTransformStep(linked);
3089 Bureaucrat burro = Registration.registerStackSlices((Patch)getActive()); // will repaint
3090 burro.addPostTask(new Runnable() { public void run() {
3091 // The current state when done
3092 ls.addTransformStep(linked);
3093 }});
3094 } else {
3095 Utils.log("Align stack slices: selected image is not part of a stack.");
3098 } else if (command.equals("Align layers")) {
3099 final Layer la = layer;; // caching, since scroll wheel may change it
3100 la.getParent().addTransformStep(la);
3101 Bureaucrat burro = AlignTask.alignLayersLinearlyTask( la );
3102 burro.addPostTask(new Runnable() { public void run() {
3103 la.getParent().addTransformStep(la);
3104 }});
3105 } else if (command.equals("Align multi-layer mosaic")) {
3106 final Layer la = layer; // caching, since scroll wheel may change it
3107 la.getParent().addTransformStep();
3108 Bureaucrat burro = AlignTask.alignMultiLayerMosaicTask( la );
3109 burro.addPostTask(new Runnable() { public void run() {
3110 la.getParent().addTransformStep();
3111 }});
3112 } else if (command.equals("Properties ...")) { // NOTE the space before the dots, to distinguish from the "Properties..." command that works on Displayable objects.
3113 GenericDialog gd = new GenericDialog("Properties", Display.this.frame);
3114 //gd.addNumericField("layer_scroll_step: ", this.scroll_step, 0);
3115 gd.addSlider("layer_scroll_step: ", 1, layer.getParent().size(), Display.this.scroll_step);
3116 gd.addChoice("snapshots_mode", LayerSet.snapshot_modes, LayerSet.snapshot_modes[layer.getParent().getSnapshotsMode()]);
3117 gd.addCheckbox("prefer_snapshots_quality", layer.getParent().snapshotsQuality());
3118 Loader lo = getProject().getLoader();
3119 boolean using_mipmaps = lo.isMipMapsEnabled();
3120 gd.addCheckbox("enable_mipmaps", using_mipmaps);
3121 String preprocessor = project.getLoader().getPreprocessor();
3122 gd.addStringField("image_preprocessor: ", null == preprocessor ? "" : preprocessor);
3123 gd.addCheckbox("enable_layer_pixels virtualization", layer.getParent().isPixelsVirtualizationEnabled());
3124 double max = layer.getParent().getLayerWidth() < layer.getParent().getLayerHeight() ? layer.getParent().getLayerWidth() : layer.getParent().getLayerHeight();
3125 gd.addSlider("max_dimension of virtualized layer pixels: ", 0, max, layer.getParent().getPixelsMaxDimension());
3126 // --------
3127 gd.showDialog();
3128 if (gd.wasCanceled()) return;
3129 // --------
3130 int sc = (int) gd.getNextNumber();
3131 if (sc < 1) sc = 1;
3132 Display.this.scroll_step = sc;
3133 updateInDatabase("scroll_step");
3135 layer.getParent().setSnapshotsMode(gd.getNextChoiceIndex());
3136 layer.getParent().setSnapshotsQuality(gd.getNextBoolean());
3138 boolean generate_mipmaps = gd.getNextBoolean();
3139 if (using_mipmaps && generate_mipmaps) {
3140 // nothing changed
3141 } else {
3142 if (using_mipmaps) { // and !generate_mipmaps
3143 lo.flushMipMaps(true);
3144 } else {
3145 // not using mipmaps before, and true == generate_mipmaps
3146 lo.generateMipMaps(layer.getParent().getDisplayables(Patch.class));
3150 final String prepro = gd.getNextString();
3151 if (!project.getLoader().setPreprocessor(prepro.trim())) {
3152 Utils.showMessage("Could NOT set the preprocessor to " + prepro);
3155 layer.getParent().setPixelsVirtualizationEnabled(gd.getNextBoolean());
3156 layer.getParent().setPixelsMaxDimension((int)gd.getNextNumber());
3157 } else if (command.equals("Search...")) {
3158 new Search();
3159 } else if (command.equals("Select all")) {
3160 selection.selectAll();
3161 repaint(Display.this.layer, selection.getBox(), 0);
3162 } else if (command.equals("Select none")) {
3163 Rectangle box = selection.getBox();
3164 selection.clear();
3165 repaint(Display.this.layer, box, 0);
3166 } else if (command.equals("Restore selection")) {
3167 selection.restore();
3168 } else if (command.equals("Select under ROI")) {
3169 Roi roi = canvas.getFakeImagePlus().getRoi();
3170 if (null == roi) return;
3171 selection.selectAll(roi, true);
3172 } else if (command.equals("Merge")) {
3173 ArrayList al_sel = selection.getSelected();
3174 // put active at the beginning, to work as the base on which other's will get merged
3175 al_sel.remove(Display.this.active);
3176 al_sel.add(0, Display.this.active);
3177 AreaList ali = AreaList.merge(al_sel);
3178 if (null != ali) {
3179 // remove all but the first from the selection
3180 for (int i=1; i<al_sel.size(); i++) {
3181 Object ob = al_sel.get(i);
3182 if (ob.getClass() == AreaList.class) {
3183 selection.remove((Displayable)ob);
3186 selection.updateTransform(ali);
3187 repaint(ali.getLayerSet(), ali, 0);
3189 } else if (command.equals("Identify...")) {
3190 // for pipes only for now
3191 if (!(active instanceof Pipe)) return;
3192 ini.trakem2.vector.Compare.findSimilar((Pipe)active);
3193 } else if (command.equals("Identify with axes...")) {
3194 if (!(active instanceof Pipe)) return;
3195 if (Project.getProjects().size() < 2) {
3196 Utils.showMessage("You need at least two projects open:\n-A reference project\n-The current project with the pipe to identify");
3197 return;
3199 ini.trakem2.vector.Compare.findSimilarWithAxes((Pipe)active);
3200 } else if (command.equals("View orthoslices")) {
3201 if (!(active instanceof Patch)) return;
3202 Display3D.showOrthoslices(((Patch)active));
3203 } else if (command.equals("View volume")) {
3204 if (!(active instanceof Patch)) return;
3205 Display3D.showVolume(((Patch)active));
3206 } else if (command.equals("Show in 3D")) {
3207 for (Iterator it = selection.getSelected(ZDisplayable.class).iterator(); it.hasNext(); ) {
3208 ZDisplayable zd = (ZDisplayable)it.next();
3209 Display3D.show(zd.getProject().findProjectThing(zd));
3211 // handle profile lists ...
3212 HashSet hs = new HashSet();
3213 for (Iterator it = selection.getSelected(Profile.class).iterator(); it.hasNext(); ) {
3214 Displayable d = (Displayable)it.next();
3215 ProjectThing profile_list = (ProjectThing)d.getProject().findProjectThing(d).getParent();
3216 if (!hs.contains(profile_list)) {
3217 Display3D.show(profile_list);
3218 hs.add(profile_list);
3221 } else if (command.equals("Snap")) {
3222 if (!(active instanceof Patch)) return;
3223 StitchingTEM.snap(getActive(), Display.this);
3224 } else if (command.equals("Blend")) {
3225 HashSet<Patch> patches = new HashSet<Patch>();
3226 for (final Displayable d : selection.getSelected()) {
3227 if (d.getClass() == Patch.class) patches.add((Patch)d);
3229 if (patches.size() > 1) {
3230 GenericDialog gd = new GenericDialog("Blending");
3231 gd.addCheckbox("Respect current alpha mask", true);
3232 gd.showDialog();
3233 if (gd.wasCanceled()) return;
3234 Blending.blend(patches, gd.getNextBoolean());
3235 } else {
3236 IJ.log("Please select more than one overlapping image.");
3238 } else if (command.equals("Montage")) {
3239 if (!(active instanceof Patch)) {
3240 Utils.showMessage("Please select only images.");
3241 return;
3243 final Set<Displayable> affected = new HashSet<Displayable>(selection.getAffected());
3244 for (final Displayable d : affected)
3245 if (d.isLinked()) {
3246 Utils.showMessage( "You cannot montage linked objects." );
3247 return;
3249 // make an undo step!
3250 final LayerSet ls = layer.getParent();
3251 ls.addTransformStep(affected);
3252 Bureaucrat burro = AlignTask.alignSelectionTask( selection );
3253 burro.addPostTask(new Runnable() { public void run() {
3254 ls.addTransformStep(affected);
3255 }});
3256 } else if (command.equals("Lens correction")) {
3257 final Layer la = layer;
3258 la.getParent().addDataEditStep(new HashSet<Displayable>(la.getParent().getDisplayables()));
3259 Bureaucrat burro = DistortionCorrectionTask.correctDistortionFromSelection( selection );
3260 burro.addPostTask(new Runnable() { public void run() {
3261 // no means to know which where modified and from which layers!
3262 la.getParent().addDataEditStep(new HashSet<Displayable>(la.getParent().getDisplayables()));
3263 }});
3264 } else if (command.equals("Link images...")) {
3265 GenericDialog gd = new GenericDialog("Options");
3266 gd.addMessage("Linking images to images (within their own layer only):");
3267 String[] options = {"all images to all images", "each image with any other overlapping image"};
3268 gd.addChoice("Link: ", options, options[1]);
3269 String[] options2 = {"selected images only", "all images in this layer", "all images in all layers"};
3270 gd.addChoice("Apply to: ", options2, options2[0]);
3271 gd.showDialog();
3272 if (gd.wasCanceled()) return;
3273 Layer lay = layer;
3274 final HashSet<Displayable> ds = new HashSet<Displayable>(lay.getParent().getDisplayables());
3275 lay.getParent().addDataEditStep(ds);
3276 boolean overlapping_only = 1 == gd.getNextChoiceIndex();
3277 switch (gd.getNextChoiceIndex()) {
3278 case 0:
3279 Patch.crosslink(selection.getSelected(Patch.class), overlapping_only);
3280 break;
3281 case 1:
3282 Patch.crosslink(lay.getDisplayables(Patch.class), overlapping_only);
3283 break;
3284 case 2:
3285 for (final Layer la : lay.getParent().getLayers()) {
3286 Patch.crosslink(la.getDisplayables(Patch.class), overlapping_only);
3288 break;
3290 lay.getParent().addDataEditStep(ds);
3291 } else if (command.equals("Enhance contrast (selected images)...")) {
3292 final Layer la = layer;
3293 final HashSet<Displayable> ds = new HashSet<Displayable>(la.getParent().getDisplayables());
3294 la.getParent().addDataEditStep(ds);
3295 ArrayList al = selection.getSelected(Patch.class);
3296 Bureaucrat burro = getProject().getLoader().homogenizeContrast(al);
3297 burro.addPostTask(new Runnable() { public void run() {
3298 la.getParent().addDataEditStep(ds);
3299 }});
3300 } else if (command.equals("Enhance contrast layer-wise...")) {
3301 // ask for range of layers
3302 final GenericDialog gd = new GenericDialog("Choose range");
3303 Utils.addLayerRangeChoices(Display.this.layer, gd);
3304 gd.showDialog();
3305 if (gd.wasCanceled()) return;
3306 java.util.List list = layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1); // exclusive end
3307 Layer[] la = new Layer[list.size()];
3308 list.toArray(la);
3309 final HashSet<Displayable> ds = new HashSet<Displayable>();
3310 for (final Layer l : la) ds.addAll(l.getDisplayables(Patch.class));
3311 getLayerSet().addDataEditStep(ds);
3312 Bureaucrat burro = project.getLoader().homogenizeContrast(la);
3313 burro.addPostTask(new Runnable() { public void run() {
3314 getLayerSet().addDataEditStep(ds);
3315 }});
3316 } else if (command.equals("Set Min and Max layer-wise...")) {
3317 Displayable active = getActive();
3318 double min = 0;
3319 double max = 0;
3320 if (null != active && active.getClass() == Patch.class) {
3321 min = ((Patch)active).getMin();
3322 max = ((Patch)active).getMax();
3324 final GenericDialog gd = new GenericDialog("Min and Max");
3325 gd.addMessage("Set min and max to all images in the layer range");
3326 Utils.addLayerRangeChoices(Display.this.layer, gd);
3327 gd.addNumericField("min: ", min, 2);
3328 gd.addNumericField("max: ", max, 2);
3329 gd.showDialog();
3330 if (gd.wasCanceled()) return;
3332 min = gd.getNextNumber();
3333 max = gd.getNextNumber();
3334 ArrayList<Displayable> al = new ArrayList<Displayable>();
3335 for (final Layer la : layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1)) { // exclusive end
3336 al.addAll(la.getDisplayables(Patch.class));
3338 final HashSet<Displayable> ds = new HashSet<Displayable>(al);
3339 getLayerSet().addDataEditStep(ds);
3340 Bureaucrat burro = project.getLoader().setMinAndMax(al, min, max);
3341 burro.addPostTask(new Runnable() { public void run() {
3342 getLayerSet().addDataEditStep(ds);
3343 }});
3344 } else if (command.equals("Set Min and Max (selected images)...")) {
3345 Displayable active = getActive();
3346 double min = 0;
3347 double max = 0;
3348 if (null != active && active.getClass() == Patch.class) {
3349 min = ((Patch)active).getMin();
3350 max = ((Patch)active).getMax();
3352 final GenericDialog gd = new GenericDialog("Min and Max");
3353 gd.addMessage("Set min and max to all selected images");
3354 gd.addNumericField("min: ", min, 2);
3355 gd.addNumericField("max: ", max, 2);
3356 gd.showDialog();
3357 if (gd.wasCanceled()) return;
3359 min = gd.getNextNumber();
3360 max = gd.getNextNumber();
3361 final HashSet<Displayable> ds = new HashSet<Displayable>(selection.getSelected(Patch.class));
3362 getLayerSet().addDataEditStep(ds);
3363 Bureaucrat burro = project.getLoader().setMinAndMax(selection.getSelected(Patch.class), min, max);
3364 burro.addPostTask(new Runnable() { public void run() {
3365 getLayerSet().addDataEditStep(ds);
3366 }});
3367 } else if (command.equals("Create subproject")) {
3368 Roi roi = canvas.getFakeImagePlus().getRoi();
3369 if (null == roi) return; // the menu item is not active unless there is a ROI
3370 Layer first, last;
3371 if (1 == layer.getParent().size()) {
3372 first = last = layer;
3373 } else {
3374 GenericDialog gd = new GenericDialog("Choose layer range");
3375 Utils.addLayerRangeChoices(layer, gd);
3376 gd.showDialog();
3377 if (gd.wasCanceled()) return;
3378 first = layer.getParent().getLayer(gd.getNextChoiceIndex());
3379 last = layer.getParent().getLayer(gd.getNextChoiceIndex());
3380 Utils.log2("first, last: " + first + ", " + last);
3382 Project sub = getProject().createSubproject(roi.getBounds(), first, last);
3383 final LayerSet subls = sub.getRootLayerSet();
3384 final Display d = new Display(sub, subls.getLayer(0));
3385 SwingUtilities.invokeLater(new Runnable() { public void run() {
3386 d.canvas.showCentered(new Rectangle(0, 0, (int)subls.getLayerWidth(), (int)subls.getLayerHeight()));
3387 }});
3388 } else if (command.startsWith("Arealists as labels")) {
3389 GenericDialog gd = new GenericDialog("Export labels");
3390 gd.addSlider("Scale: ", 1, 100, 100);
3391 final String[] options = {"All area list", "Selected area lists"};
3392 gd.addChoice("Export: ", options, options[0]);
3393 Utils.addLayerRangeChoices(layer, gd);
3394 gd.addCheckbox("Visible only", true);
3395 gd.showDialog();
3396 if (gd.wasCanceled()) return;
3397 final float scale = (float)(gd.getNextNumber() / 100);
3398 java.util.List al = 0 == gd.getNextChoiceIndex() ? layer.getParent().getZDisplayables(AreaList.class) : selection.getSelected(AreaList.class);
3399 if (null == al) {
3400 Utils.log("No area lists found to export.");
3401 return;
3403 // 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?
3404 al = (java.util.List<Displayable>) al;
3406 int first = gd.getNextChoiceIndex();
3407 int last = gd.getNextChoiceIndex();
3408 boolean visible_only = gd.getNextBoolean();
3409 if (-1 != command.indexOf("(amira)")) {
3410 AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, true, true);
3411 } else if (-1 != command.indexOf("(tif)")) {
3412 AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, false, false);
3414 } else if (command.equals("Project properties...")) {
3415 project.adjustProperties();
3416 } else if (command.equals("Release memory...")) {
3417 Bureaucrat.createAndStart(new Worker("Releasing memory") {
3418 public void run() {
3419 startedWorking();
3420 try {
3421 GenericDialog gd = new GenericDialog("Release Memory");
3422 int max = (int)(IJ.maxMemory() / 1000000);
3423 gd.addSlider("Megabytes: ", 0, max, max/2);
3424 gd.showDialog();
3425 if (!gd.wasCanceled()) {
3426 int n_mb = (int)gd.getNextNumber();
3427 project.getLoader().releaseToFit((long)n_mb*1000000);
3429 } catch (Throwable e) {
3430 IJError.print(e);
3431 } finally {
3432 finishedWorking();
3435 }, project);
3436 } else if (command.equals("Flush image cache")) {
3437 Loader.releaseAllCaches();
3438 } else {
3439 Utils.log2("Display: don't know what to do with command " + command);
3441 }});
3444 /** Update in all displays the Transform for the given Displayable if it's selected. */
3445 static public void updateTransform(final Displayable displ) {
3446 for (final Display d : al_displays) {
3447 if (d.selection.contains(displ)) d.selection.updateTransform(displ);
3451 /** Order the profiles of the parent profile_list by Z order, and fix the ProjectTree.*/
3453 private void fixZOrdering(Profile profile) {
3454 ProjectThing thing = project.findProjectThing(profile);
3455 if (null == thing) {
3456 Utils.log2("Display.fixZOrdering: null thing?");
3457 return;
3459 ((ProjectThing)thing.getParent()).fixZOrdering();
3460 project.getProjectTree().updateList(thing.getParent());
3464 /** The number of layers to scroll through with the wheel; 1 by default.*/
3465 public int getScrollStep() { return this.scroll_step; }
3467 public void setScrollStep(int scroll_step) {
3468 if (scroll_step < 1) scroll_step = 1;
3469 this.scroll_step = scroll_step;
3470 updateInDatabase("scroll_step");
3473 protected Bureaucrat importImage() {
3474 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
3475 public void run() {
3476 startedWorking();
3477 try {
3480 Rectangle srcRect = canvas.getSrcRect();
3481 int x = srcRect.x + srcRect.width / 2;
3482 int y = srcRect.y + srcRect.height/ 2;
3483 Patch p = project.getLoader().importImage(project, x, y);
3484 if (null == p) {
3485 finishedWorking();
3486 Utils.showMessage("Could not open the image.");
3487 return;
3490 Display.this.getLayerSet().addLayerContentStep(layer);
3492 layer.add(p); // will add it to the proper Displays
3494 Display.this.getLayerSet().addLayerContentStep(layer);
3497 } catch (Exception e) {
3498 IJError.print(e);
3500 finishedWorking();
3503 return Bureaucrat.createAndStart(worker, getProject());
3506 protected Bureaucrat importNextImage() {
3507 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
3508 public void run() {
3509 startedWorking();
3510 try {
3512 Rectangle srcRect = canvas.getSrcRect();
3513 int x = srcRect.x + srcRect.width / 2;// - imp.getWidth() / 2;
3514 int y = srcRect.y + srcRect.height/ 2;// - imp.getHeight()/ 2;
3515 Patch p = project.getLoader().importNextImage(project, x, y);
3516 if (null == p) {
3517 Utils.showMessage("Could not open next image.");
3518 finishedWorking();
3519 return;
3522 Display.this.getLayerSet().addLayerContentStep(layer);
3524 layer.add(p); // will add it to the proper Displays
3526 Display.this.getLayerSet().addLayerContentStep(layer);
3528 } catch (Exception e) {
3529 IJError.print(e);
3531 finishedWorking();
3534 return Bureaucrat.createAndStart(worker, getProject());
3538 /** Make the given channel have the given alpha (transparency). */
3539 public void setChannel(int c, float alpha) {
3540 int a = (int)(255 * alpha);
3541 int l = (c_alphas&0xff000000)>>24;
3542 int r = (c_alphas&0xff0000)>>16;
3543 int g = (c_alphas&0xff00)>>8;
3544 int b = c_alphas&0xff;
3545 switch (c) {
3546 case Channel.MONO:
3547 // all to the given alpha
3548 c_alphas = (l<<24) + (r<<16) + (g<<8) + b; // parenthesis are NECESSARY
3549 break;
3550 case Channel.RED:
3551 // modify only the red
3552 c_alphas = (l<<24) + (a<<16) + (g<<8) + b;
3553 break;
3554 case Channel.GREEN:
3555 c_alphas = (l<<24) + (r<<16) + (a<<8) + b;
3556 break;
3557 case Channel.BLUE:
3558 c_alphas = (l<<24) + (r<<16) + (g<<8) + a;
3559 break;
3561 //Utils.log2("c_alphas: " + c_alphas);
3562 //canvas.setUpdateGraphics(true);
3563 canvas.repaint(true);
3564 updateInDatabase("c_alphas");
3567 /** Set the channel as active and the others as inactive. */
3568 public void setActiveChannel(Channel channel) {
3569 for (int i=0; i<4; i++) {
3570 if (channel != channels[i]) channels[i].setActive(false);
3571 else channel.setActive(true);
3573 Utils.updateComponent(panel_channels);
3574 transp_slider.setValue((int)(channel.getAlpha() * 100));
3577 public int getDisplayChannelAlphas() { return c_alphas; }
3579 // rename this method and the getDisplayChannelAlphas ! They sound the same!
3580 public int getChannelAlphas() {
3581 return ((int)(channels[0].getAlpha() * 255)<<24) + ((int)(channels[1].getAlpha() * 255)<<16) + ((int)(channels[2].getAlpha() * 255)<<8) + (int)(channels[3].getAlpha() * 255);
3584 public int getChannelAlphasState() {
3585 return ((channels[0].isSelected() ? 255 : 0)<<24)
3586 + ((channels[1].isSelected() ? 255 : 0)<<16)
3587 + ((channels[2].isSelected() ? 255 : 0)<<8)
3588 + (channels[3].isSelected() ? 255 : 0);
3591 /** Show the layer in the front Display, or in a new Display if the front Display is showing a layer from a different LayerSet. */
3592 static public void showFront(final Layer layer) {
3593 Display display = front;
3594 if (null == display || display.layer.getParent() != layer.getParent()) {
3595 display = new Display(layer.getProject(), layer, null); // gets set to front
3596 } else {
3597 display.setLayer(layer);
3601 /** Show the given Displayable centered and selected. If select is false, the selection is cleared. */
3602 static public void showCentered(Layer layer, Displayable displ, boolean select, boolean shift_down) {
3603 // see if the given layer belongs to the layer set being displayed
3604 Display display = front; // to ensure thread consistency to some extent
3605 if (null == display || display.layer.getParent() != layer.getParent()) {
3606 display = new Display(layer.getProject(), layer, displ); // gets set to front
3607 } else if (display.layer != layer) {
3608 display.setLayer(layer);
3610 if (select) {
3611 if (!shift_down) display.selection.clear();
3612 display.selection.add(displ);
3613 } else {
3614 display.selection.clear();
3616 display.showCentered(displ);
3619 private final void showCentered(final Displayable displ) {
3620 if (null == displ) return;
3621 SwingUtilities.invokeLater(new Runnable() { public void run() {
3622 displ.setVisible(true);
3623 Rectangle box = displ.getBoundingBox();
3624 if (0 == box.width || 0 == box.height) {
3625 box.width = (int)layer.getLayerWidth();
3626 box.height = (int)layer.getLayerHeight();
3628 canvas.showCentered(box);
3629 scrollToShow(displ);
3630 if (displ instanceof ZDisplayable) {
3631 // scroll to first layer that has a point
3632 ZDisplayable zd = (ZDisplayable)displ;
3633 setLayer(zd.getFirstLayer());
3635 }});
3638 /** Listen to interesting updates, such as the ColorPicker and updates to Patch objects. */
3639 public void imageUpdated(ImagePlus updated) {
3640 // detect ColorPicker WARNING this will work even if the Display is not the window immediately active under the color picker.
3641 if (this == front && updated instanceof ij.plugin.ColorPicker) {
3642 if (null != active && project.isInputEnabled()) {
3643 selection.setColor(Toolbar.getForegroundColor());
3644 Display.repaint(front.layer, selection.getBox(), 0);
3646 return;
3648 // $%#@!! LUT changes don't set the image as changed
3649 //if (updated instanceof PatchStack) {
3650 // updated.changes = 1
3653 //Utils.log2("imageUpdated: " + updated + " " + updated.getClass());
3655 /* // never gets called (?)
3656 // the above is overkill. Instead:
3657 if (updated instanceof PatchStack) {
3658 Patch p = ((PatchStack)updated).getCurrentPatch();
3659 ImageProcessor ip = updated.getProcessor();
3660 p.setMinAndMax(ip.getMin(), ip.getMax());
3661 Utils.log2("setting min and max: " + ip.getMin() + ", " + ip.getMax());
3662 project.getLoader().decacheAWT(p.getId()); // including level 0, which will be editable
3663 // on repaint, it will be recreated
3664 //((PatchStack)updated).decacheAll(); // so that it will repaint with a newly created image
3668 // detect LUT changes: DONE at PatchStack, which is the active (virtual) image
3669 //Utils.log2("calling decache for " + updated);
3670 //getProject().getLoader().decache(updated);
3673 public void imageClosed(ImagePlus imp) {}
3674 public void imageOpened(ImagePlus imp) {}
3676 /** Release memory captured by the offscreen images */
3677 static public void flushAll() {
3678 for (final Display d : al_displays) {
3679 d.canvas.flush();
3681 //System.gc();
3682 Thread.yield();
3685 /** Can be null. */
3686 static public Display getFront() {
3687 return front;
3690 static public void setCursorToAll(final Cursor c) {
3691 for (final Display d : al_displays) {
3692 d.frame.setCursor(c);
3696 protected void setCursor(Cursor c) {
3697 frame.setCursor(c);
3700 /** Used by the Displayable to update the visibility checkbox in other Displays. */
3701 static protected void updateVisibilityCheckbox(final Layer layer, final Displayable displ, final Display calling_display) {
3702 //LOCKS ALL //SwingUtilities.invokeLater(new Runnable() { public void run() {
3703 for (final Display d : al_displays) {
3704 if (d == calling_display) continue;
3705 if (d.layer.contains(displ) || (displ instanceof ZDisplayable && d.layer.getParent().contains((ZDisplayable)displ))) {
3706 DisplayablePanel dp = d.ht_panels.get(displ);
3707 if (null != dp) dp.updateVisibilityCheckbox();
3710 //}});
3713 protected boolean isActiveWindow() {
3714 return frame.isActive();
3717 /** Toggle user input; pan and zoom are always enabled though.*/
3718 static public void setReceivesInput(final Project project, final boolean b) {
3719 for (final Display d : al_displays) {
3720 if (d.project == project) d.canvas.setReceivesInput(b);
3724 /** Export the DTD that defines this object. */
3725 static public void exportDTD(StringBuffer sb_header, HashSet hs, String indent) {
3726 if (hs.contains("t2_display")) return; // TODO to avoid collisions the type shoud be in a namespace such as tm2:display
3727 hs.add("t2_display");
3728 sb_header.append(indent).append("<!ELEMENT t2_display EMPTY>\n")
3729 .append(indent).append("<!ATTLIST t2_display id NMTOKEN #REQUIRED>\n")
3730 .append(indent).append("<!ATTLIST t2_display layer_id NMTOKEN #REQUIRED>\n")
3731 .append(indent).append("<!ATTLIST t2_display x NMTOKEN #REQUIRED>\n")
3732 .append(indent).append("<!ATTLIST t2_display y NMTOKEN #REQUIRED>\n")
3733 .append(indent).append("<!ATTLIST t2_display magnification NMTOKEN #REQUIRED>\n")
3734 .append(indent).append("<!ATTLIST t2_display srcrect_x NMTOKEN #REQUIRED>\n")
3735 .append(indent).append("<!ATTLIST t2_display srcrect_y NMTOKEN #REQUIRED>\n")
3736 .append(indent).append("<!ATTLIST t2_display srcrect_width NMTOKEN #REQUIRED>\n")
3737 .append(indent).append("<!ATTLIST t2_display srcrect_height NMTOKEN #REQUIRED>\n")
3738 .append(indent).append("<!ATTLIST t2_display scroll_step NMTOKEN #REQUIRED>\n")
3739 .append(indent).append("<!ATTLIST t2_display c_alphas NMTOKEN #REQUIRED>\n")
3740 .append(indent).append("<!ATTLIST t2_display c_alphas_state NMTOKEN #REQUIRED>\n")
3743 /** Export all displays of the given project as XML entries. */
3744 static public void exportXML(final Project project, final Writer writer, final String indent, final Object any) throws Exception {
3745 final StringBuffer sb_body = new StringBuffer();
3746 final String in = indent + "\t";
3747 for (final Display d : al_displays) {
3748 if (d.project != project) continue;
3749 final Rectangle r = d.frame.getBounds();
3750 final Rectangle srcRect = d.canvas.getSrcRect();
3751 final double magnification = d.canvas.getMagnification();
3752 sb_body.append(indent).append("<t2_display id=\"").append(d.id).append("\"\n")
3753 .append(in).append("layer_id=\"").append(d.layer.getId()).append("\"\n")
3754 .append(in).append("c_alphas=\"").append(d.c_alphas).append("\"\n")
3755 .append(in).append("c_alphas_state=\"").append(d.getChannelAlphasState()).append("\"\n")
3756 .append(in).append("x=\"").append(r.x).append("\"\n")
3757 .append(in).append("y=\"").append(r.y).append("\"\n")
3758 .append(in).append("magnification=\"").append(magnification).append("\"\n")
3759 .append(in).append("srcrect_x=\"").append(srcRect.x).append("\"\n")
3760 .append(in).append("srcrect_y=\"").append(srcRect.y).append("\"\n")
3761 .append(in).append("srcrect_width=\"").append(srcRect.width).append("\"\n")
3762 .append(in).append("srcrect_height=\"").append(srcRect.height).append("\"\n")
3763 .append(in).append("scroll_step=\"").append(d.scroll_step).append("\"\n")
3765 sb_body.append(indent).append("/>\n");
3767 writer.write(sb_body.toString());
3770 static public void toolChanged(final String tool_name) {
3771 Utils.log2("tool name: " + tool_name);
3772 if (!tool_name.equals("ALIGN")) {
3773 for (final Display d : al_displays) {
3774 d.layer.getParent().cancelAlign();
3779 static public void toolChanged(final int tool) {
3780 //Utils.log2("int tool is " + tool);
3781 if (ProjectToolbar.PEN == tool) {
3782 // erase bounding boxes
3783 for (final Display d : al_displays) {
3784 if (null != d.active) d.repaint(d.layer, d.selection.getBox(), 2);
3787 if (null != front) {
3788 WindowManager.setTempCurrentImage(front.canvas.getFakeImagePlus());
3792 public Selection getSelection() {
3793 return selection;
3796 public boolean isSelected(Displayable d) {
3797 return selection.contains(d);
3800 static public void updateSelection() {
3801 Display.updateSelection(null);
3803 static public void updateSelection(final Display calling) {
3804 final HashSet hs = new HashSet();
3805 for (final Display d : al_displays) {
3806 if (hs.contains(d.layer)) continue;
3807 hs.add(d.layer);
3808 if (null == d || null == d.selection) {
3809 Utils.log2("d is : "+ d + " d.selection is " + d.selection);
3810 } else {
3811 d.selection.update(); // recomputes box
3813 if (d != calling) { // TODO this is so dirty!
3814 if (d.selection.getNLinked() > 1) d.canvas.setUpdateGraphics(true); // this is overkill anyway
3815 d.canvas.repaint(d.selection.getLinkedBox(), Selection.PADDING);
3816 d.navigator.repaint(true); // everything
3821 static public void clearSelection(final Layer layer) {
3822 for (final Display d : al_displays) {
3823 if (d.layer == layer) d.selection.clear();
3826 static public void clearSelection() {
3827 for (final Display d : al_displays) {
3828 d.selection.clear();
3832 private void setTempCurrentImage() {
3833 WindowManager.setTempCurrentImage(canvas.getFakeImagePlus());
3836 /** Check if any display will paint the given Displayable at the given magnification. */
3837 static public boolean willPaint(final Displayable displ, final double magnification) {
3838 Rectangle box = null; ;
3839 for (final Display d : al_displays) {
3840 /* // Can no longer do this check, because 'magnification' is now affected by the Displayable AffineTransform! And thus it would not paint after the prePaint.
3841 if (Math.abs(d.canvas.getMagnification() - magnification) > 0.00000001) {
3842 continue;
3845 if (null == box) box = displ.getBoundingBox(null);
3846 if (d.canvas.getSrcRect().intersects(box)) {
3847 return true;
3850 return false;
3853 public void hideDeselected(final boolean not_images) {
3854 // hide deselected
3855 final ArrayList all = layer.getParent().getZDisplayables(); // a copy
3856 all.addAll(layer.getDisplayables());
3857 all.removeAll(selection.getSelected());
3858 if (not_images) all.removeAll(layer.getDisplayables(Patch.class));
3859 for (final Displayable d : (ArrayList<Displayable>)all) {
3860 if (d.isVisible()) d.setVisible(false);
3862 Display.update(layer);
3865 /** Cleanup internal lists that may contain the given Displayable. */
3866 static public void flush(final Displayable displ) {
3867 for (final Display d : al_displays) {
3868 d.selection.removeFromPrev(displ);
3872 public void resizeCanvas() {
3873 GenericDialog gd = new GenericDialog("Resize LayerSet");
3874 gd.addNumericField("new width: ", layer.getLayerWidth(), 3);
3875 gd.addNumericField("new height: ",layer.getLayerHeight(),3);
3876 gd.addChoice("Anchor: ", LayerSet.ANCHORS, LayerSet.ANCHORS[7]);
3877 gd.showDialog();
3878 if (gd.wasCanceled()) return;
3879 double new_width = gd.getNextNumber();
3880 double new_height =gd.getNextNumber();
3881 layer.getParent().setDimensions(new_width, new_height, gd.getNextChoiceIndex()); // will complain and prevent cropping existing Displayable objects
3885 // To record layer changes -- but it's annoying, this is visualization not data.
3886 static class DoSetLayer implements DoStep {
3887 final Display display;
3888 final Layer layer;
3889 DoSetLayer(final Display display) {
3890 this.display = display;
3891 this.layer = display.layer;
3893 public Displayable getD() { return null; }
3894 public boolean isEmpty() { return false; }
3895 public boolean apply(final int action) {
3896 display.setLayer(layer);
3898 public boolean isIdenticalTo(final Object ob) {
3899 if (!ob instanceof DoSetLayer) return false;
3900 final DoSetLayer dsl = (DoSetLayer) ob;
3901 return dsl.display == this.display && dsl.layer == this.layer;
3906 protected void duplicateLinkAndSendTo(final Displayable active, final int position, final Layer other_layer) {
3907 if (null == active || !(active instanceof Profile)) return;
3908 if (active.getLayer() == other_layer) return; // can't do that!
3909 Profile profile = project.getProjectTree().duplicateChild((Profile)active, position, other_layer);
3910 if (null == profile) return;
3911 active.link(profile);
3912 slt.setAndWait(other_layer);
3913 other_layer.add(profile);
3914 selection.add(profile);
3917 private final HashMap<Color,Layer> layer_channels = new HashMap<Color,Layer>();
3918 private final TreeMap<Integer,LayerPanel> layer_alpha = new TreeMap<Integer,LayerPanel>();
3920 /** Remove all red/blue coloring of layers, and repaint canvas. */
3921 protected void resetLayerColors() {
3922 synchronized (layer_channels) {
3923 for (final Layer l : new ArrayList<Layer>(layer_channels.values())) { // avoid concurrent modification exception
3924 final LayerPanel lp = layer_panels.get(l);
3925 lp.setColor(Color.white);
3926 setColorChannel(lp.layer, Color.white);
3928 layer_channels.clear();
3930 canvas.repaint();
3933 /** Set all layer alphas to zero, and repaint canvas. */
3934 protected void resetLayerAlphas() {
3935 synchronized (layer_channels) {
3936 for (final LayerPanel lp : new ArrayList<LayerPanel>(layer_alpha.values())) {
3937 lp.setAlpha(0);
3939 layer_alpha.clear(); // should have already been cleared
3941 canvas.repaint();
3944 /** Add to layer_alpha table, or remove if alpha is zero. */
3945 protected void storeLayerAlpha(final LayerPanel lp, final float a) {
3946 synchronized (layer_channels) {
3947 if (M.equals(0, a)) {
3948 layer_alpha.remove(lp.layer.getParent().indexOf(lp.layer));
3949 } else {
3950 layer_alpha.put(lp.layer.getParent().indexOf(lp.layer), lp);
3955 static protected final int REPAINT_SINGLE_LAYER = 0;
3956 static protected final int REPAINT_MULTI_LAYER = 1;
3957 static protected final int REPAINT_RGB_LAYER = 2;
3959 /** Sets the values atomically, returns the painting mode. */
3960 protected int getPaintMode(final HashMap<Color,Layer> hm, final ArrayList<LayerPanel> list) {
3961 synchronized (layer_channels) {
3962 if (layer_channels.size() > 0) {
3963 hm.putAll(layer_channels);
3964 hm.put(Color.green, this.layer);
3965 return REPAINT_RGB_LAYER;
3967 list.addAll(layer_alpha.values());
3968 final int len = list.size();
3969 if (len > 1) return REPAINT_MULTI_LAYER;
3970 if (1 == len) {
3971 if (list.get(0).layer == this.layer) return REPAINT_SINGLE_LAYER; // normal mode
3972 return REPAINT_MULTI_LAYER;
3974 return REPAINT_SINGLE_LAYER;
3978 /** Set a layer to be painted as a specific color channel in the canvas.
3979 * Only Color.red and Color.blue are accepted.
3980 * Color.green is reserved for the current layer. */
3981 protected void setColorChannel(final Layer layer, final Color color) {
3982 synchronized (layer_channels) {
3983 if (Color.white == color) {
3984 // Remove
3985 for (final Iterator<Layer> it = layer_channels.values().iterator(); it.hasNext(); ) {
3986 if (it.next() == layer) {
3987 it.remove();
3988 break;
3991 canvas.repaint();
3992 } else if (Color.red == color || Color.blue == color) {
3993 // Reset current of that color, if any, to white
3994 final Layer l = layer_channels.remove(color);
3995 if (null != l) layer_panels.get(l).setColor(Color.white);
3996 // Replace or set new
3997 layer_channels.put(color, layer);
3998 tabs.repaint();
3999 canvas.repaint();
4000 } else {
4001 Utils.log2("Trying to set unacceptable color for layer " + layer + " : " + color);
4004 this.canvas.repaint(true);