3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2005-2009 Albert Cardona and Rodney Douglas.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation (http://www.gnu.org/licenses/gpl.txt )
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 You may contact Albert Cardona at acardona at ini.phys.ethz.ch
20 Institute of Neuroinformatics, University of Zurich / ETH, Switzerland.
23 package ini
.trakem2
.display
;
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
.*;
49 import javax
.swing
.event
.*;
51 import mpicbg
.trakem2
.align
.AlignTask
;
54 import java
.awt
.event
.*;
56 import java
.lang
.reflect
.Method
;
57 import java
.io
.Writer
;
58 import java
.util
.concurrent
.Future
;
60 import lenscorrection
.DistortionCorrectionTask
;
62 /** A Display is a class to show a Layer and enable mouse and keyboard manipulation of all its components. */
63 public final class Display
extends DBObject
implements ActionListener
, ImageListener
{
65 /** The Layer this Display is showing. */
68 private Displayable active
= null;
69 /** All selected Displayable objects, including the active one. */
70 final private Selection selection
= new Selection(this);
73 private JTabbedPane tabs
;
74 private Hashtable
<Class
,JScrollPane
> ht_tabs
;
75 private JScrollPane scroll_patches
;
76 private JPanel panel_patches
;
77 private JScrollPane scroll_profiles
;
78 private JPanel panel_profiles
;
79 private JScrollPane scroll_zdispl
;
80 private JPanel panel_zdispl
;
81 private JScrollPane scroll_channels
;
82 private JPanel panel_channels
;
83 private JScrollPane scroll_labels
;
84 private JPanel panel_labels
;
86 private JPanel panel_layers
;
87 private JScrollPane scroll_layers
;
88 private Hashtable
<Layer
,LayerPanel
> layer_panels
= new Hashtable
<Layer
,LayerPanel
>();
90 private JSlider transp_slider
;
91 private DisplayNavigator navigator
;
92 private JScrollBar scroller
;
94 private DisplayCanvas canvas
; // WARNING this is an AWT component, since it extends ImageCanvas
95 private JPanel canvas_panel
; // and this is a workaround, to better (perhaps) integrate the awt canvas inside a JSplitPane
96 private JSplitPane split
;
98 private JPopupMenu popup
= null;
100 /** Contains the packed alphas of every channel. */
101 private int c_alphas
= 0xffffffff; // all 100 % visible
102 private Channel
[] channels
;
104 private Hashtable
<Displayable
,DisplayablePanel
> ht_panels
= new Hashtable
<Displayable
,DisplayablePanel
>();
106 /** Handle drop events, to insert image files. */
107 private DNDInsertImage dnd
;
109 private boolean size_adjusted
= false;
111 private int scroll_step
= 1;
113 /** Keep track of all existing Display objects. */
114 static private ArrayList
<Display
> al_displays
= new ArrayList
<Display
>();
115 /** The currently focused Display, if any. */
116 static private Display front
= null;
118 /** Displays to open when all objects have been reloaded from the database. */
119 static private final Hashtable ht_later
= new Hashtable();
121 /** A thread to handle user actions, for example an event sent from a popup menu. */
122 private final Dispatcher dispatcher
= new Dispatcher();
124 static private WindowAdapter window_listener
= new WindowAdapter() {
125 /** Unregister the closed Display. */
126 public void windowClosing(WindowEvent we
) {
127 final Object source
= we
.getSource();
128 for (Iterator it
= al_displays
.iterator(); it
.hasNext(); ) {
129 Display d
= (Display
)it
.next();
130 if (source
== d
.frame
) {
132 if (d
== front
) front
= null;
133 d
.remove(false); //calls destroy
138 /** Set the source Display as front. */
139 public void windowActivated(WindowEvent we
) {
140 // find which was it to make it be the front
141 final Object source
= we
.getSource();
142 for (final Display d
: al_displays
) {
143 if (source
== d
.frame
) {
146 ProjectToolbar
.setProjectToolbar();
147 // now, select the layer in the LayerTree
148 front
.getProject().select(front
.layer
);
149 // finally, set the virtual ImagePlus that ImageJ will see
150 d
.setTempCurrentImage();
151 // copied from ij.gui.ImageWindow, with modifications
152 if (IJ
.isMacintosh() && IJ
.getInstance()!=null) {
153 IJ
.wait(10); // may be needed for Java 1.4 on OS X
154 d
.frame
.setMenuBar(ij
.Menus
.getMenuBar());
159 // else, restore the ImageJ toolbar for non-project images
160 //if (!source.equals(IJ.getInstance())) {
161 // ProjectToolbar.setImageJToolbar();
164 /** Restore the ImageJ toolbar */
165 public void windowDeactivated(WindowEvent we
) {
166 // Can't, the user can never click the ProjectToolbar then. This has to be done in a different way, for example checking who is the WindowManager.getCurrentImage (and maybe setting a dummy image into it) //ProjectToolbar.setImageJToolbar();
168 /** Call a pack() when the window is maximized to fit the canvas correctly. */
169 public void windowStateChanged(WindowEvent we
) {
170 final Object source
= we
.getSource();
171 for (final Display d
: al_displays
) {
172 if (source
!= d
.frame
) continue;
179 static private MouseListener frame_mouse_listener
= new MouseAdapter() {
180 public void mouseReleased(MouseEvent me
) {
181 Object source
= me
.getSource();
182 for (final Display d
: al_displays
) {
183 if (d
.frame
== source
) {
184 if (d
.size_adjusted
) {
186 d
.size_adjusted
= false;
187 Utils
.log2("mouse released on JFrame");
195 private int last_frame_state
= frame
.NORMAL
;
197 // THIS WHOLE SYSTEM OF LISTENERS IS BROKEN:
198 // * when zooming in, the window growths in width a few pixels.
199 // * when enlarging the window quickly, the canvas is not resized as large as it should.
200 // -- the whole problem: swing threading, which I am not handling properly. It's hard.
201 static private ComponentListener component_listener
= new ComponentAdapter() {
202 public void componentResized(ComponentEvent ce
) {
203 final Display d
= getDisplaySource(ce
);
205 d
.size_adjusted
= true; // works in combination with mouseReleased to call pack(), avoiding infinite loops.
207 int frame_state
= d
.frame
.getExtendedState();
208 if (frame_state
!= d
.last_frame_state
) { // this setup avoids infinite loops (for pack() calls componentResized as well
209 d
.last_frame_state
= frame_state
;
210 if (d
.frame
.ICONIFIED
!= frame_state
) d
.pack();
214 public void componentMoved(ComponentEvent ce
) {
215 Display d
= getDisplaySource(ce
);
216 if (null != d
) d
.updateInDatabase("position");
218 private Display
getDisplaySource(ComponentEvent ce
) {
219 final Object source
= ce
.getSource();
220 for (final Display d
: al_displays
) {
221 if (source
== d
.frame
) {
229 static private ChangeListener tabs_listener
= new ChangeListener() {
230 /** Listen to tab changes. */
231 public void stateChanged(final ChangeEvent ce
) {
232 final Object source
= ce
.getSource();
233 for (final Display d
: al_displays
) {
234 if (source
== d
.tabs
) {
235 d
.dispatcher
.exec(new Runnable() { public void run() {
236 // creating tabs fires the event!!!
237 if (null == d
.frame
|| null == d
.canvas
) return;
238 final Container tab
= (Container
)d
.tabs
.getSelectedComponent();
239 if (tab
== d
.scroll_channels
) {
240 // find active channel if any
241 for (int i
=0; i
<d
.channels
.length
; i
++) {
242 if (d
.channels
[i
].isActive()) {
243 d
.transp_slider
.setValue((int)(d
.channels
[i
].getAlpha() * 100));
250 int count = tab.getComponentCount();
251 if (0 == count || (1 == count && tab.getComponent(0).getClass().equals(JLabel.class))) {
252 */ // ALWAYS, because it could be the case that the user changes layer while on one specific tab, and then clicks on the other tab which may not be empty and shows totally the wrong contents (i.e. for another layer)
257 if (tab
== d
.scroll_zdispl
) {
258 label
= "Z-space objects";
259 al
= d
.layer
.getParent().getZDisplayables();
261 } else if (tab
== d
.scroll_patches
) {
263 al
= d
.layer
.getDisplayables(Patch
.class);
265 } else if (tab
== d
.scroll_labels
) {
267 al
= d
.layer
.getDisplayables(DLabel
.class);
269 } else if (tab
== d
.scroll_profiles
) {
271 al
= d
.layer
.getDisplayables(Profile
.class);
272 p
= d
.panel_profiles
;
273 } else if (tab
== d
.scroll_layers
) {
278 d
.updateTab(p
, label
, al
);
279 //Utils.updateComponent(d.tabs.getSelectedComponent());
280 //Utils.log2("updated tab: " + p + " with " + al.size() + " objects.");
283 if (null != d
.active
) {
284 // set the transp slider to the alpha value of the active Displayable if any
285 d
.transp_slider
.setValue((int)(d
.active
.getAlpha() * 100));
286 DisplayablePanel dp
= d
.ht_panels
.get(d
.active
);
287 if (null != dp
) dp
.setActive(true);
297 private final ScrollLayerListener scroller_listener
= new ScrollLayerListener();
299 private class ScrollLayerListener
implements AdjustmentListener
{
301 public void adjustmentValueChanged(final AdjustmentEvent ae
) {
302 final int index
= scroller
.getValue();
303 slt
.set(layer
.getParent().getLayer(index
));
307 private final SetLayerThread slt
= new SetLayerThread();
309 private class SetLayerThread
extends Thread
{
311 private boolean go
= true;
313 private final Lock lock
= new Lock();
314 private final Lock lock2
= new Lock();
317 setPriority(Thread
.NORM_PRIORITY
);
322 public final void set(final Layer layer
) {
323 synchronized (lock
) {
326 synchronized (this) {
331 public final void setAndWait(final Layer layer
) {
338 while (null == this.layer
) {
339 synchronized (this) {
340 try { wait(); } catch (InterruptedException ie
) {}
344 synchronized (lock
) {
349 if (!go
) return; // after nullifying layer
352 Display
.this.setLayer(layer
);
353 Display
.this.updateInDatabase("layer_id");
355 // unlock any calls waiting on setAndWait
356 synchronized (lock2
) {
361 synchronized (lock2
) {
366 public void waitForLayer() {
367 while (null != layer
&& go
) {
368 try { Thread
.sleep(10); } catch (Exception e
) {}
377 /** Creates a new Display with adjusted magnification to fit in the screen. */
378 static public void createDisplay(final Project project
, final Layer layer
) {
379 SwingUtilities
.invokeLater(new Runnable() { public void run() {
380 Display display
= new Display(project
, layer
);
381 Dimension screen
= Toolkit
.getDefaultToolkit().getScreenSize();
382 Rectangle srcRect
= new Rectangle(0, 0, (int)layer
.getLayerWidth(), (int)layer
.getLayerHeight());
383 double mag
= screen
.width
/ layer
.getLayerWidth();
384 if (mag
* layer
.getLayerHeight() > screen
.height
) mag
= screen
.height
/ layer
.getLayerHeight();
385 mag
= display
.canvas
.getLowerZoomLevel2(mag
);
386 if (mag
> 1.0) mag
= 1.0;
387 //display.getCanvas().setup(mag, srcRect); // would call pack() at the wrong time!
388 // ... so instead: manually
389 display
.getCanvas().setMagnification(mag
);
390 display
.getCanvas().setSrcRect(srcRect
.x
, srcRect
.y
, srcRect
.width
, srcRect
.height
);
391 display
.getCanvas().setDrawingSize((int)Math
.ceil(srcRect
.width
* mag
), (int)Math
.ceil(srcRect
.height
* mag
));
393 display
.updateFrameTitle(layer
);
394 ij
.gui
.GUI
.center(display
.frame
);
395 display
.frame
.pack();
399 /** A new Display from scratch, to show the given Layer. */
400 public Display(Project project
, final Layer layer
) {
403 makeGUI(layer
, null);
404 ImagePlus
.addImageListener(this);
406 this.layer
= layer
; // after, or it doesn't update properly
407 al_displays
.add(this);
411 /** For reconstruction purposes. The Display will be stored in the ht_later.*/
412 public Display(Project project
, long id
, Layer layer
, Object
[] props
) {
414 synchronized (ht_later
) {
415 Display
.ht_later
.put(this, props
);
420 /** Open a new Display centered around the given Displayable. */
421 public Display(Project project
, Layer layer
, Displayable displ
) {
425 makeGUI(layer
, null);
426 ImagePlus
.addImageListener(this);
428 this.layer
= layer
; // after set layer!
429 al_displays
.add(this);
433 /** Reconstruct a Display from an XML entry, to be opened when everything is ready. */
434 public Display(Project project
, long id
, Layer layer
, HashMap ht_attributes
) {
437 Utils
.log2("Display: need a non-null Layer for id=" + id
);
440 Rectangle srcRect
= new Rectangle(0, 0, (int)layer
.getLayerWidth(), (int)layer
.getLayerHeight());
441 double magnification
= 0.25;
442 Point p
= new Point(0, 0);
443 int c_alphas
= 0xffffffff;
444 int c_alphas_state
= 0xffffffff;
445 for (Iterator it
= ht_attributes
.entrySet().iterator(); it
.hasNext(); ) {
446 Map
.Entry entry
= (Map
.Entry
)it
.next();
447 String key
= (String
)entry
.getKey();
448 String data
= (String
)entry
.getValue();
449 if (key
.equals("srcrect_x")) { // reflection! Reflection!
450 srcRect
.x
= Integer
.parseInt(data
);
451 } else if (key
.equals("srcrect_y")) {
452 srcRect
.y
= Integer
.parseInt(data
);
453 } else if (key
.equals("srcrect_width")) {
454 srcRect
.width
= Integer
.parseInt(data
);
455 } else if (key
.equals("srcrect_height")) {
456 srcRect
.height
= Integer
.parseInt(data
);
457 } else if (key
.equals("magnification")) {
458 magnification
= Double
.parseDouble(data
);
459 } else if (key
.equals("x")) {
460 p
.x
= Integer
.parseInt(data
);
461 } else if (key
.equals("y")) {
462 p
.y
= Integer
.parseInt(data
);
463 } else if (key
.equals("c_alphas")) {
465 c_alphas
= Integer
.parseInt(data
);
466 } catch (Exception ex
) {
467 c_alphas
= 0xffffffff;
469 } else if (key
.equals("c_alphas_state")) {
471 c_alphas_state
= Integer
.parseInt(data
);
472 } catch (Exception ex
) {
474 c_alphas_state
= 0xffffffff;
476 } else if (key
.equals("scroll_step")) {
478 setScrollStep(Integer
.parseInt(data
));
479 } catch (Exception ex
) {
484 // TODO the above is insecure, in that data is not fully checked to be within bounds.
486 Object
[] props
= new Object
[]{p
, new Double(magnification
), srcRect
, new Long(layer
.getId()), new Integer(c_alphas
), new Integer(c_alphas_state
)};
487 synchronized (ht_later
) {
488 Display
.ht_later
.put(this, props
);
493 /** After reloading a project from the database, open the Displays that the project had. */
494 static public Bureaucrat
openLater() {
495 final Hashtable ht_later_local
;
496 synchronized (ht_later
) {
497 if (0 == ht_later
.size()) return null;
498 ht_later_local
= new Hashtable(ht_later
);
499 ht_later
.keySet().removeAll(ht_later_local
.keySet());
501 final Worker worker
= new Worker("Opening displays") {
505 Thread
.sleep(300); // waiting for Swing
507 for (Enumeration e
= ht_later_local
.keys(); e
.hasMoreElements(); ) {
508 final Display d
= (Display
)e
.nextElement();
509 front
= d
; // must be set before repainting any ZDisplayable!
510 Object
[] props
= (Object
[])ht_later_local
.get(d
);
511 if (ControlWindow
.isGUIEnabled()) d
.makeGUI(d
.layer
, props
);
512 d
.setLayerLater(d
.layer
, d
.layer
.get(((Long
)props
[3]).longValue())); //important to do it after makeGUI
513 if (!ControlWindow
.isGUIEnabled()) continue;
514 ImagePlus
.addImageListener(d
);
516 d
.updateFrameTitle(d
.layer
);
517 // force a repaint if a prePaint was done TODO this should be properly managed with repaints using always the invokeLater, but then it's DOG SLOW
518 if (d
.canvas
.getMagnification() > 0.499) {
519 SwingUtilities
.invokeLater(new Runnable() { public void run() {
521 d
.project
.getLoader().setChanged(false);
522 Utils
.log2("A set to false");
525 d
.project
.getLoader().setChanged(false);
526 Utils
.log2("B set to false");
528 if (null != front
) front
.getProject().select(front
.layer
);
530 } catch (Throwable t
) {
537 return Bureaucrat
.createAndStart(worker
, ((Display
)ht_later_local
.keySet().iterator().next()).getProject()); // gets the project from the first Display
540 private void makeGUI(final Layer layer
, final Object
[] props
) {
544 Rectangle srcRect
= null;
547 mag
= ((Double
)props
[1]).doubleValue();
548 srcRect
= (Rectangle
)props
[2];
551 // transparency slider
552 this.transp_slider
= new JSlider(javax
.swing
.SwingConstants
.HORIZONTAL
, 0, 100, 100);
553 this.transp_slider
.setBackground(Color
.white
);
554 this.transp_slider
.setMinimumSize(new Dimension(250, 20));
555 this.transp_slider
.setMaximumSize(new Dimension(250, 20));
556 this.transp_slider
.setPreferredSize(new Dimension(250, 20));
557 TransparencySliderListener tsl
= new TransparencySliderListener();
558 this.transp_slider
.addChangeListener(tsl
);
559 this.transp_slider
.addMouseListener(tsl
);
560 for (final KeyListener kl
: this.transp_slider
.getKeyListeners()) {
561 this.transp_slider
.removeKeyListener(kl
);
564 // Tabbed pane on the left
565 this.tabs
= new JTabbedPane();
566 this.tabs
.setMinimumSize(new Dimension(250, 300));
567 this.tabs
.setBackground(Color
.white
);
568 this.tabs
.addChangeListener(tabs_listener
);
571 this.panel_patches
= makeTabPanel();
572 this.panel_patches
.add(new JLabel("No patches."));
573 this.scroll_patches
= makeScrollPane(panel_patches
);
574 this.tabs
.add("Patches", scroll_patches
);
577 this.panel_profiles
= makeTabPanel();
578 this.panel_profiles
.add(new JLabel("No profiles."));
579 this.scroll_profiles
= makeScrollPane(panel_profiles
);
580 this.tabs
.add("Profiles", scroll_profiles
);
583 this.panel_zdispl
= makeTabPanel();
584 this.panel_zdispl
.add(new JLabel("No objects."));
585 this.scroll_zdispl
= makeScrollPane(panel_zdispl
);
586 this.tabs
.add("Z space", scroll_zdispl
);
589 this.panel_channels
= makeTabPanel();
590 this.scroll_channels
= makeScrollPane(panel_channels
);
591 this.channels
= new Channel
[4];
592 this.channels
[0] = new Channel(this, Channel
.MONO
);
593 this.channels
[1] = new Channel(this, Channel
.RED
);
594 this.channels
[2] = new Channel(this, Channel
.GREEN
);
595 this.channels
[3] = new Channel(this, Channel
.BLUE
);
596 //this.panel_channels.add(this.channels[0]);
597 this.panel_channels
.add(this.channels
[1]);
598 this.panel_channels
.add(this.channels
[2]);
599 this.panel_channels
.add(this.channels
[3]);
600 this.tabs
.add("Opacity", scroll_channels
);
603 this.panel_labels
= makeTabPanel();
604 this.panel_labels
.add(new JLabel("No labels."));
605 this.scroll_labels
= makeScrollPane(panel_labels
);
606 this.tabs
.add("Labels", scroll_labels
);
609 this.panel_layers
= makeTabPanel();
610 this.scroll_layers
= makeScrollPane(panel_layers
);
611 recreateLayerPanels(layer
);
612 this.scroll_layers
.addMouseWheelListener(canvas
);
613 this.tabs
.add("Layers", scroll_layers
);
615 this.ht_tabs
= new Hashtable
<Class
,JScrollPane
>();
616 this.ht_tabs
.put(Patch
.class, scroll_patches
);
617 this.ht_tabs
.put(Profile
.class, scroll_profiles
);
618 this.ht_tabs
.put(ZDisplayable
.class, scroll_zdispl
);
619 this.ht_tabs
.put(AreaList
.class, scroll_zdispl
);
620 this.ht_tabs
.put(Pipe
.class, scroll_zdispl
);
621 this.ht_tabs
.put(Polyline
.class, scroll_zdispl
);
622 this.ht_tabs
.put(Ball
.class, scroll_zdispl
);
623 this.ht_tabs
.put(Dissector
.class, scroll_zdispl
);
624 this.ht_tabs
.put(DLabel
.class, scroll_labels
);
625 // channels not included
626 // layers not included
629 this.navigator
= new DisplayNavigator(this, layer
.getLayerWidth(), layer
.getLayerHeight());
630 // Layer scroller (to scroll slices)
631 int extent
= (int)(250.0 / layer
.getParent().size());
632 if (extent
< 10) extent
= 10;
633 this.scroller
= new JScrollBar(JScrollBar
.HORIZONTAL
);
634 updateLayerScroller(layer
);
635 this.scroller
.addAdjustmentListener(scroller_listener
);
638 // Left panel, contains the transp slider, the tabbed pane, the navigation panel and the layer scroller
639 JPanel left
= new JPanel();
640 left
.setBackground(Color
.white
);
641 BoxLayout left_layout
= new BoxLayout(left
, BoxLayout
.Y_AXIS
);
642 left
.setLayout(left_layout
);
643 left
.add(transp_slider
);
649 this.canvas
= new DisplayCanvas(this, (int)Math
.ceil(layer
.getLayerWidth()), (int)Math
.ceil(layer
.getLayerHeight()));
650 this.canvas_panel
= new JPanel();
651 GridBagLayout gb
= new GridBagLayout();
652 this.canvas_panel
.setLayout(gb
);
653 GridBagConstraints c
= new GridBagConstraints();
654 c
.fill
= GridBagConstraints
.BOTH
;
655 c
.anchor
= GridBagConstraints
.NORTHWEST
;
656 gb
.setConstraints(this.canvas_panel
, c
);
657 gb
.setConstraints(this.canvas
, c
);
659 // prevent new Displays from screweing up if input is globally disabled
660 if (!project
.isInputEnabled()) this.canvas
.setReceivesInput(false);
662 this.canvas_panel
.add(canvas
);
664 this.navigator
.addMouseWheelListener(canvas
);
666 this.transp_slider
.addKeyListener(canvas
);
668 // Split pane to contain everything
669 this.split
= new JSplitPane(JSplitPane
.HORIZONTAL_SPLIT
, left
, canvas_panel
);
670 this.split
.setOneTouchExpandable(true); // NOT present in all L&F (?)
671 this.split
.setBackground(Color
.white
);
674 gb
.setConstraints(split
.getRightComponent(), c
);
676 // JFrame to show the split pane
677 this.frame
= ControlWindow
.createJFrame(layer
.toString());
678 this.frame
.setBackground(Color
.white
);
679 this.frame
.getContentPane().setBackground(Color
.white
);
680 if (IJ
.isMacintosh() && IJ
.getInstance()!=null) {
681 IJ
.wait(10); // may be needed for Java 1.4 on OS X
682 this.frame
.setMenuBar(ij
.Menus
.getMenuBar());
684 this.frame
.addWindowListener(window_listener
);
685 this.frame
.addComponentListener(component_listener
);
686 this.frame
.getContentPane().add(split
);
687 this.frame
.addMouseListener(frame_mouse_listener
);
688 //doesn't exist//this.frame.setMinimumSize(new Dimension(270, 600));
692 canvas
.setup(mag
, srcRect
);
693 // restore visibility of each channel
694 int cs
= ((Integer
)props
[5]).intValue(); // aka c_alphas_state
695 int[] sel
= new int[4];
696 sel
[0] = ((cs
&0xff000000)>>24);
697 sel
[1] = ((cs
&0xff0000)>>16);
698 sel
[2] = ((cs
&0xff00)>>8);
700 // restore channel alphas
701 this.c_alphas
= ((Integer
)props
[4]).intValue();
702 channels
[0].setAlpha( (float)((c_alphas
&0xff000000)>>24) / 255.0f
, 0 != sel
[0]);
703 channels
[1].setAlpha( (float)((c_alphas
&0xff0000)>>16) / 255.0f
, 0 != sel
[1]);
704 channels
[2].setAlpha( (float)((c_alphas
&0xff00)>>8) / 255.0f
, 0 != sel
[2]);
705 channels
[3].setAlpha( (float) (c_alphas
&0xff) / 255.0f
, 0 != sel
[3]);
706 // restore visibility in the working c_alphas
707 this.c_alphas
= ((0 != sel
[0] ?
(int)(255 * channels
[0].getAlpha()) : 0)<<24) + ((0 != sel
[1] ?
(int)(255 * channels
[1].getAlpha()) : 0)<<16) + ((0 != sel
[2] ?
(int)(255 * channels
[2].getAlpha()) : 0)<<8) + (0 != sel
[3] ?
(int)(255 * channels
[3].getAlpha()) : 0);
710 if (null != active
&& null != layer
) {
711 Rectangle r
= active
.getBoundingBox();
715 r
.height
+= r
.height
;
716 if (r
.x
< 0) r
.x
= 0;
717 if (r
.y
< 0) r
.y
= 0;
718 if (r
.width
> layer
.getLayerWidth()) r
.width
= (int)layer
.getLayerWidth();
719 if (r
.height
> layer
.getLayerHeight())r
.height
= (int)layer
.getLayerHeight();
720 double magn
= layer
.getLayerWidth() / (double)r
.width
;
721 canvas
.setup(magn
, r
);
724 // add keyListener to the whole frame
725 this.tabs
.addKeyListener(canvas
);
726 this.canvas_panel
.addKeyListener(canvas
);
727 this.frame
.addKeyListener(canvas
);
730 ij
.gui
.GUI
.center(this.frame
);
731 this.frame
.setVisible(true);
732 ProjectToolbar
.setProjectToolbar(); // doesn't get it through events
734 final Dimension screen
= Toolkit
.getDefaultToolkit().getScreenSize();
737 // fix positioning outside the screen (dual to single monitor)
738 if (p
.x
>= 0 && p
.x
< screen
.width
- 50 && p
.y
>= 0 && p
.y
<= screen
.height
- 50) this.frame
.setLocation(p
);
739 else frame
.setLocation(0, 0);
742 // fix excessive size
743 final Rectangle box
= this.frame
.getBounds();
746 int width
= box
.width
;
747 int height
= box
.height
;
748 if (box
.width
> screen
.width
) { x
= 0; width
= screen
.width
; }
749 if (box
.height
> screen
.height
) { y
= 0; height
= screen
.height
; }
750 if (x
!= box
.x
|| y
!= box
.y
) {
751 this.frame
.setLocation(x
, y
+ (0 == y ?
30 : 0)); // added insets for bad window managers
752 updateInDatabase("position");
754 if (width
!= box
.width
|| height
!= box
.height
) {
755 this.frame
.setSize(new Dimension(width
-10, height
-30)); // added insets for bad window managers
758 // try to optimize canvas dimensions and magn
759 double magn
= layer
.getLayerHeight() / screen
.height
;
760 if (magn
> 1.0) magn
= 1.0;
762 // limit magnification if appropriate
763 for (Iterator it
= layer
.getDisplayables(Patch
.class).iterator(); it
.hasNext(); ) {
764 final Patch pa
= (Patch
)it
.next();
765 final Rectangle ba
= pa
.getBoundingBox();
766 size
+= (long)(ba
.width
* ba
.height
);
768 if (size
> 10000000) canvas
.setInitialMagnification(0.25); // 10 Mb
770 this.frame
.setSize(new Dimension((int)(screen
.width
* 0.66), (int)(screen
.height
* 0.66)));
774 Utils
.updateComponent(tabs
); // otherwise fails in FreeBSD java 1.4.2 when reconstructing
777 // Set the calibration of the FakeImagePlus to that of the LayerSet
778 ((FakeImagePlus
)canvas
.getFakeImagePlus()).setCalibrationSuper(layer
.getParent().getCalibrationCopy());
780 updateFrameTitle(layer
);
781 // Set the FakeImagePlus as the current image
782 setTempCurrentImage();
784 // create a drag and drop listener
785 dnd
= new DNDInsertImage(this);
787 // start a repainting thread
789 canvas
.repaint(true); // repaint() is unreliable
792 // Set the minimum size of the tabbed pane on the left, so it can be completely collapsed now that it has been properly displayed. This is a patch to the lack of respect for the setDividerLocation method.
793 SwingUtilities
.invokeLater(new Runnable() {
795 tabs
.setMinimumSize(new Dimension(0, 100));
796 Display
.scrollbar_width
= Display
.this.scroll_patches
.getVerticalScrollBar().getPreferredSize().width
; // using scroll_patches since it's the one selected by default and thus visible and painted
797 ControlWindow
.setLookAndFeel();
802 private JPanel
makeTabPanel() {
803 JPanel panel
= new JPanel();
804 BoxLayout layout
= new BoxLayout(panel
, BoxLayout
.Y_AXIS
);
805 panel
.setLayout(layout
);
809 private JScrollPane
makeScrollPane(Component c
) {
810 JScrollPane jsp
= new JScrollPane(c
);
811 jsp
.setBackground(Color
.white
); // no effect
812 jsp
.getViewport().setBackground(Color
.white
); // no effect
813 // adjust scrolling to use one DisplayablePanel as the minimal unit
814 jsp
.getVerticalScrollBar().setBlockIncrement(DisplayablePanel
.HEIGHT
); // clicking within the track
815 jsp
.getVerticalScrollBar().setUnitIncrement(DisplayablePanel
.HEIGHT
); // clicking on an arrow
816 jsp
.setHorizontalScrollBarPolicy(javax
.swing
.ScrollPaneConstants
.HORIZONTAL_SCROLLBAR_NEVER
);
817 jsp
.setPreferredSize(new Dimension(250, 300));
818 jsp
.setMinimumSize(new Dimension(250, 300));
822 static protected int scrollbar_width
= 0;
824 public JPanel
getCanvasPanel() {
828 public DisplayCanvas
getCanvas() {
832 public synchronized void setLayer(final Layer layer
) {
833 if (null == layer
|| layer
== this.layer
) return;
834 translateLayerColors(this.layer
, layer
);
835 if (tabs
.getSelectedComponent() == scroll_layers
) {
836 SwingUtilities
.invokeLater(new Runnable() { public void run() {
837 scrollToShow(scroll_layers
, layer_panels
.get(layer
));
840 final boolean set_zdispl
= null == Display
.this.layer
|| layer
.getParent() != Display
.this.layer
.getParent();
841 if (selection
.isTransforming()) {
842 Utils
.log("Can't browse layers while transforming.\nCANCEL the transform first with the ESCAPE key or right-click -> cancel.");
843 scroller
.setValue(Display
.this.layer
.getParent().getLayerIndex(Display
.this.layer
.getId()));
847 scroller
.setValue(layer
.getParent().getLayerIndex(layer
.getId()));
849 // update the current Layer pointer in ZDisplayable objects
850 for (Iterator it
= layer
.getParent().getZDisplayables().iterator(); it
.hasNext(); ) {
851 ((ZDisplayable
)it
.next()).setLayer(layer
); // the active layer
854 updateVisibleTab(set_zdispl
);
856 // see if a lot has to be reloaded, put the relevant ones at the end
857 project
.getLoader().prepare(layer
);
858 updateFrameTitle(layer
); // to show the new 'z'
859 // select the Layer in the LayerTree
860 project
.select(Display
.this.layer
); // does so in a separate thread
861 // update active Displayable:
863 // deselect all except ZDisplayables
864 final ArrayList
<Displayable
> sel
= selection
.getSelected();
865 final Displayable last_active
= Display
.this.active
;
867 for (final Iterator
<Displayable
> it
= sel
.iterator(); it
.hasNext(); ) {
868 final Displayable d
= it
.next();
869 if (!(d
instanceof ZDisplayable
)) {
872 if (d
== last_active
&& sel
.size() > 0) {
873 // select the last one of the remaining, if any
874 sel_next
= sel
.size()-1;
878 if (-1 != sel_next
&& sel
.size() > 0) select(sel
.get(sel_next
), true);
880 // Keep Profile chain selected, for best ease of use:
881 if (null != last_active
&& last_active
.getClass() == Profile
.class && last_active
.isLinked(Profile
.class)) {
882 Utils
.log2("last active was a profile: " + last_active
);
883 Displayable other
= null;
884 for (final Displayable prof
: last_active
.getLinked(Profile
.class)) {
885 if (prof
.getLayer() == layer
) {
890 if (null != other
) selection
.add(other
);
893 // repaint everything
894 navigator
.repaint(true);
895 canvas
.repaint(true);
897 // repaint tabs (hard as hell)
898 Utils
.updateComponent(tabs
);
899 // @#$%^! The above works half the times, so explicit repaint as well:
900 Component c
= tabs
.getSelectedComponent();
903 tabs
.setSelectedComponent(scroll_patches
);
905 Utils
.updateComponent(c
);
907 project
.getLoader().setMassiveMode(false); // resetting if it was set true
909 // update the coloring in the ProjectTree
910 project
.getProjectTree().updateUILater();
912 setTempCurrentImage();
915 static public void updateVisibleTabs() {
916 for (final Display d
: al_displays
) {
917 d
.updateVisibleTab(true);
921 /** Recreate the tab that is being shown. */
922 public void updateVisibleTab(boolean set_zdispl
) {
923 // update only the visible tab
924 switch (tabs
.getSelectedIndex()) {
927 updateTab(panel_patches
, "Patches", layer
.getDisplayables(Patch
.class));
931 updateTab(panel_profiles
, "Profiles", layer
.getDisplayables(Profile
.class));
936 updateTab(panel_zdispl
, "Z-space objects", layer
.getParent().getZDisplayables());
939 // case 3: channel opacities
942 updateTab(panel_labels
, "Labels", layer
.getDisplayables(DLabel
.class));
944 // case 5: layer panels
949 private void setLayerLater(final Layer layer
, final Displayable active
) {
950 if (null == layer
) return;
952 if (!ControlWindow
.isGUIEnabled()) return;
953 SwingUtilities
.invokeLater(new Runnable() { public void run() {
954 // empty the tabs, except channels and pipes
955 clearTab(panel_profiles
, "Profiles");
956 clearTab(panel_patches
, "Patches");
957 clearTab(panel_labels
, "Labels");
958 // distribute Displayable to the tabs. Ignore LayerSet instances.
959 if (null == ht_panels
) ht_panels
= new Hashtable
<Displayable
,DisplayablePanel
>();
960 else ht_panels
.clear();
961 for (final Displayable d
: layer
.getDisplayables()) {
962 add(d
, false, false);
964 for (final Displayable d
: layer
.getParent().getZDisplayables()) {
966 add(d
, false, false);
968 navigator
.repaint(true); // was not done when adding
969 Utils
.updateComponent(tabs
.getSelectedComponent());
977 setPriority(Thread.NORM_PRIORITY);
978 try { Thread.sleep(1000); } catch (Exception e) {}
985 /** Remove all components from the tab and add a "No [label]" label to each. */
986 private void clearTab(final Container c
, final String label
) {
988 c
.add(new JLabel("No " + label
+ "."));
990 if (tabs
.getSelectedComponent() == c
) {
991 Utils
.updateComponent(c
);
995 /** A class to listen to the transparency_slider of the DisplayablesSelectorWindow. */
996 private class TransparencySliderListener
extends MouseAdapter
implements ChangeListener
{
998 public void stateChanged(ChangeEvent ce
) {
999 //change the transparency value of the current active displayable
1000 float new_value
= (float)((JSlider
)ce
.getSource()).getValue();
1001 setTransparency(new_value
/ 100.0f
);
1004 public void mousePressed(MouseEvent me
) {
1005 JScrollPane scroll
= (JScrollPane
)tabs
.getSelectedComponent();
1006 if (scroll
!= scroll_channels
&& !selection
.isEmpty()) selection
.addDataEditStep(new String
[]{"alpha"});
1009 public void mouseReleased(MouseEvent me
) {
1010 // update navigator window
1011 navigator
.repaint(true);
1012 JScrollPane scroll
= (JScrollPane
)tabs
.getSelectedComponent();
1013 if (scroll
!= scroll_channels
&& !selection
.isEmpty()) selection
.addDataEditStep(new String
[]{"alpha"});
1017 /** Context-sensitive: to a Displayable, or to a channel. */
1018 private void setTransparency(final float value
) {
1019 JScrollPane scroll
= (JScrollPane
)tabs
.getSelectedComponent();
1020 if (scroll
== scroll_channels
) {
1021 for (int i
=0; i
<4; i
++) {
1022 if (channels
[i
].getBackground() == Color
.cyan
) {
1023 channels
[i
].setAlpha(value
); // will call back and repaint the Display
1027 } else if (null != active
) {
1028 if (value
!= active
.getAlpha()) { // because there's a callback from setActive that would then affect all other selected Displayable without having dragged the slider, i.e. just by being selected.
1029 canvas
.invalidateVolatile();
1030 selection
.setAlpha(value
);
1035 public void setTransparencySlider(final float transp
) {
1036 if (transp
>= 0.0f
&& transp
<= 1.0f
) {
1038 transp_slider
.setValue((int)(transp
* 100));
1042 /** Mark the canvas for updating the offscreen images if the given Displayable is NOT the active. */ // Used by the Displayable.setVisible for example.
1043 static public void setUpdateGraphics(final Layer layer
, final Displayable displ
) {
1044 for (final Display d
: al_displays
) {
1045 if (layer
== d
.layer
&& null != d
.active
&& d
.active
!= displ
) {
1046 d
.canvas
.setUpdateGraphics(true);
1051 /** Flag the DisplayCanvas of Displays showing the given Layer to update their offscreen images.*/
1052 static public void setUpdateGraphics(final Layer layer
, final boolean update
) {
1053 for (final Display d
: al_displays
) {
1054 if (layer
== d
.layer
) {
1055 d
.canvas
.setUpdateGraphics(update
);
1060 /** Whether to update the offscreen images or not. */
1061 public void setUpdateGraphics(boolean b
) {
1062 canvas
.setUpdateGraphics(b
);
1065 /** Find all Display instances that contain the layer and repaint them, in the Swing GUI thread. */
1066 static public void update(final Layer layer
) {
1067 if (null == layer
) return;
1068 SwingUtilities
.invokeLater(new Runnable() { public void run() {
1069 for (final Display d
: al_displays
) {
1070 if (d
.isShowing(layer
)) {
1077 static public void update(final LayerSet set
) {
1081 /** Find all Display instances showing a Layer of this LayerSet, and update the dimensions of the navigator and canvas and snapshots, and repaint, in the Swing GUI thread. */
1082 static public void update(final LayerSet set
, final boolean update_canvas_dimensions
) {
1083 if (null == set
) return;
1084 SwingUtilities
.invokeLater(new Runnable() { public void run() {
1085 for (final Display d
: al_displays
) {
1086 if (set
.contains(d
.layer
)) {
1087 d
.updateSnapshots();
1088 if (update_canvas_dimensions
) d
.canvas
.setDimensions(set
.getLayerWidth(), set
.getLayerHeight());
1095 /** Release all resources held by this Display and close the frame. */
1096 protected void destroy() {
1098 canvas
.setReceivesInput(false);
1101 // update the coloring in the ProjectTree and LayerTree
1102 if (!project
.isBeingDestroyed()) {
1104 project
.getProjectTree().updateUILater();
1105 project
.getLayerTree().updateUILater();
1106 } catch (Exception e
) {
1107 Utils
.log2("updateUI failed at Display.destroy()");
1111 frame
.removeComponentListener(component_listener
);
1112 frame
.removeWindowListener(window_listener
);
1113 frame
.removeWindowFocusListener(window_listener
);
1114 frame
.removeWindowStateListener(window_listener
);
1115 frame
.removeKeyListener(canvas
);
1116 frame
.removeMouseListener(frame_mouse_listener
);
1117 canvas_panel
.removeKeyListener(canvas
);
1118 canvas
.removeKeyListener(canvas
);
1119 tabs
.removeChangeListener(tabs_listener
);
1120 tabs
.removeKeyListener(canvas
);
1121 ImagePlus
.removeImageListener(this);
1122 bytypelistener
= null;
1124 navigator
.destroy();
1125 scroller
.removeAdjustmentListener(scroller_listener
);
1126 frame
.setVisible(false);
1127 //no need, and throws exception//frame.dispose();
1129 if (null != selection
) selection
.clear();
1130 //Utils.log2("destroying selection");
1132 // below, need for SetLayerThread threads to quit
1134 // set a new front if any
1135 if (null == front
&& al_displays
.size() > 0) {
1136 front
= (Display
)al_displays
.get(al_displays
.size() -1);
1138 // repaint layer tree (to update the label color)
1140 project
.getLayerTree().updateUILater(); // works only after setting the front above
1141 } catch (Exception e
) {} // ignore swing sync bullshit when closing everything too fast
1142 // remove the drag and drop listener
1146 /** Find all Display instances that contain a Layer of the given project and close them without removing the Display entries from the database. */
1147 static synchronized public void close(final Project project
) {
1148 /* // concurrent modifications if more than 1 Display are being removed asynchronously
1149 for (final Display d : al_displays) {
1150 if (d.getLayer().getProject().equals(project)) {
1156 Display
[] d
= new Display
[al_displays
.size()];
1157 al_displays
.toArray(d
);
1158 for (int i
=0; i
<d
.length
; i
++) {
1159 if (d
[i
].getProject() == project
) {
1160 al_displays
.remove(d
[i
]);
1166 /** Find all Display instances that contain the layer and close them and remove the Display from the database. */
1167 static public void close(final Layer layer
) {
1168 for (Iterator it
= al_displays
.iterator(); it
.hasNext(); ) {
1169 Display d
= (Display
)it
.next();
1170 if (d
.isShowing(layer
)) {
1177 /** Find all Display instances that are showing the layer and either move to the next or previous layer, or close it if none. */
1178 static public void remove(final Layer layer
) {
1179 for (Iterator
<Display
> it
= al_displays
.iterator(); it
.hasNext(); ) {
1180 final Display d
= it
.next();
1181 if (d
.isShowing(layer
)) {
1182 Layer la
= layer
.getParent().next(layer
);
1183 if (layer
== la
|| null == la
) la
= layer
.getParent().previous(layer
);
1184 if (null == la
|| layer
== la
) {
1194 public boolean remove(boolean check
) {
1196 if (!Utils
.check("Delete the Display ?")) return false;
1198 // flush the offscreen images and close the frame
1200 removeFromDatabase();
1204 public Layer
getLayer() {
1208 public LayerSet
getLayerSet() {
1209 return layer
.getParent();
1212 public boolean isShowing(final Layer layer
) {
1213 return this.layer
== layer
;
1216 public DisplayNavigator
getNavigator() {
1220 /** Repaint both the canvas and the navigator, updating the graphics, and the title and tabs. */
1221 public void repaintAll() {
1222 if (repaint_disabled
) return;
1223 navigator
.repaint(true);
1224 canvas
.repaint(true);
1225 Utils
.updateComponent(tabs
);
1229 /** Repaint the canvas updating graphics, the navigator without updating graphics, and the title. */
1230 public void repaintAll2() {
1231 if (repaint_disabled
) return;
1232 navigator
.repaint(false);
1233 canvas
.repaint(true);
1237 static public void repaintSnapshots(final LayerSet set
) {
1238 if (repaint_disabled
) return;
1239 for (final Display d
: al_displays
) {
1240 if (d
.getLayer().getParent() == set
) {
1241 d
.navigator
.repaint(true);
1242 Utils
.updateComponent(d
.tabs
);
1246 static public void repaintSnapshots(final Layer layer
) {
1247 if (repaint_disabled
) return;
1248 for (final Display d
: al_displays
) {
1249 if (d
.getLayer() == layer
) {
1250 d
.navigator
.repaint(true);
1251 Utils
.updateComponent(d
.tabs
);
1256 public void pack() {
1257 dispatcher
.exec(new Runnable() { public void run() {
1259 Thread
.currentThread().sleep(100);
1260 SwingUtilities
.invokeAndWait(new Runnable() { public void run() {
1263 } catch (Exception e
) { IJError
.print(e
); }
1267 static public void pack(final LayerSet ls
) {
1268 for (final Display d
: al_displays
) {
1269 if (d
.layer
.getParent() == ls
) d
.pack();
1273 private void adjustCanvas() {
1274 SwingUtilities
.invokeLater(new Runnable() { public void run() {
1275 Rectangle r
= split
.getRightComponent().getBounds();
1276 canvas
.setDrawingSize(r
.width
, r
.height
, true);
1277 // fix not-on-top-left problem
1278 canvas
.setLocation(0, 0);
1279 //frame.pack(); // don't! Would go into an infinite loop
1280 canvas
.repaint(true);
1281 updateInDatabase("srcRect");
1285 /** Grab the last selected display (or create an new one if none) and show in it the layer,centered on the Displayable object. */
1286 static public void setFront(final Layer layer
, final Displayable displ
) {
1287 if (null == front
) {
1288 Display display
= new Display(layer
.getProject(), layer
); // gets set to front
1289 display
.showCentered(displ
);
1290 } else if (layer
== front
.layer
) {
1291 front
.showCentered(displ
);
1294 for (final Display d
: al_displays
) {
1295 if (d
.layer
== layer
) {
1297 d
.showCentered(displ
);
1301 // else, open new one
1302 new Display(layer
.getProject(), layer
).showCentered(displ
);
1306 /** Find the displays that show the given Layer, and add the given Displayable to the GUI and sets it active only in the front Display and only if 'activate' is true. */
1307 static public void add(final Layer layer
, final Displayable displ
, final boolean activate
) {
1308 for (final Display d
: al_displays
) {
1309 if (d
.layer
== layer
) {
1311 d
.add(displ
, activate
, true);
1312 //front.frame.toFront();
1314 d
.add(displ
, false, true);
1320 static public void add(final Layer layer
, final Displayable displ
) {
1321 add(layer
, displ
, true);
1324 /** Add the ZDisplayable to all Displays that show a Layer belonging to the given LayerSet. */
1325 static public void add(final LayerSet set
, final ZDisplayable zdispl
) {
1326 for (final Display d
: al_displays
) {
1327 if (set
.contains(d
.layer
)) {
1329 zdispl
.setLayer(d
.layer
); // the active one
1330 d
.add(zdispl
, true, true); // calling add(Displayable, boolean, boolean)
1331 //front.frame.toFront();
1333 d
.add(zdispl
, false, true);
1339 static public void addAll(final Layer layer
, final Collection
<?
extends Displayable
> coll
) {
1340 for (final Display d
: al_displays
) {
1341 if (d
.layer
== layer
) {
1347 static public void addAll(final LayerSet set
, final Collection
<?
extends ZDisplayable
> coll
) {
1348 for (final Display d
: al_displays
) {
1349 if (set
.contains(d
.layer
)) {
1350 for (final ZDisplayable zd
: coll
) {
1351 if (front
== d
) zd
.setLayer(d
.layer
);
1358 private final void addAll(final Collection
<?
extends Displayable
> coll
) {
1359 for (final Displayable d
: coll
) {
1360 add(d
, false, false);
1363 Utils
.updateComponent(tabs
);
1364 navigator
.repaint(true);
1367 // TODO this very old method could take some improvement:
1368 // - there is no need to create a new DisplayablePanel if its panel is not shown
1369 // - other issues; the method looks overly "if a dog barks and a duck quacks during a lunar eclipse then .."
1370 /** Add it to the proper panel, at the top, and set it active. */
1371 private final void add(final Displayable d
, final boolean activate
, final boolean repaint_snapshot
) {
1372 DisplayablePanel dp
= ht_panels
.get(d
);
1373 if (null != dp
&& activate
) { // for ZDisplayable objects (TODO I think this is not used anymore)
1380 // add to the proper list
1382 if (d
instanceof Profile
) {
1384 } else if (d
instanceof Patch
) {
1386 } else if (d
instanceof DLabel
) {
1388 } else if (d
instanceof ZDisplayable
) { //both pipes and balls and AreaList
1394 dp
= new DisplayablePanel(this, d
); // TODO: instead of destroying/recreating, we could just recycle them by reassigning a different Displayable. See how it goes! It'd need a pool of objects
1395 addToPanel(p
, 0, dp
, activate
);
1396 ht_panels
.put(d
, dp
);
1403 if (repaint_snapshot
) navigator
.repaint(true);
1406 private void addToPanel(JPanel panel
, int index
, DisplayablePanel dp
, boolean repaint
) {
1408 if (1 == panel
.getComponentCount() && panel
.getComponent(0) instanceof JLabel
) {
1411 panel
.add(dp
, index
);
1413 Utils
.updateComponent(tabs
);
1417 /** Find the displays that show the given Layer, and remove the given Displayable from the GUI. */
1418 static public void remove(final Layer layer
, final Displayable displ
) {
1419 for (final Display d
: al_displays
) {
1420 if (layer
== d
.layer
) d
.remove(displ
);
1424 private void remove(final Displayable displ
) {
1425 DisplayablePanel ob
= ht_panels
.remove(displ
);
1427 final JScrollPane jsp
= ht_tabs
.get(displ
.getClass());
1429 JPanel p
= (JPanel
)jsp
.getViewport().getView();
1430 p
.remove((Component
)ob
);
1431 Utils
.revalidateComponent(p
);
1434 if (null == active
|| !selection
.contains(displ
)) {
1435 canvas
.setUpdateGraphics(true);
1437 canvas
.invalidateVolatile(); // removing active, no need to update offscreen but yes the volatile
1438 repaint(displ
, null, 5, true, false);
1439 // from Selection.deleteAll this method is called ... but it's ok: same thread, no locking problems.
1440 selection
.remove(displ
);
1443 static public void remove(final ZDisplayable zdispl
) {
1444 for (final Display d
: al_displays
) {
1445 if (zdispl
.getLayerSet() == d
.layer
.getParent()) {
1446 d
.remove((Displayable
)zdispl
);
1451 static public void repaint(final Layer layer
, final Displayable displ
, final int extra
) {
1452 repaint(layer
, displ
, displ
.getBoundingBox(), extra
);
1455 static public void repaint(final Layer layer
, final Displayable displ
, final Rectangle r
, final int extra
) {
1456 repaint(layer
, displ
, r
, extra
, true);
1459 /** Find the displays that show the given Layer, and repaint the given Displayable. */
1460 static public void repaint(final Layer layer
, final Displayable displ
, final Rectangle r
, final int extra
, final boolean repaint_navigator
) {
1461 if (repaint_disabled
) return;
1462 for (final Display d
: al_displays
) {
1463 if (layer
== d
.layer
) {
1464 d
.repaint(displ
, r
, extra
, repaint_navigator
, false);
1469 static public void repaint(final Displayable d
) {
1470 if (d
instanceof ZDisplayable
) repaint(d
.getLayerSet(), d
, d
.getBoundingBox(null), 5, true);
1471 repaint(d
.getLayer(), d
, d
.getBoundingBox(null), 5, true);
1474 /** Repaint as much as the bounding box around the given Displayable, or the r if not null. */
1475 private void repaint(final Displayable displ
, final Rectangle r
, final int extra
, final boolean repaint_navigator
, final boolean update_graphics
) {
1476 if (repaint_disabled
|| null == displ
) return;
1477 if (update_graphics
|| displ
.getClass() == Patch
.class || displ
!= active
) {
1478 canvas
.setUpdateGraphics(true);
1480 if (null != r
) canvas
.repaint(r
, extra
);
1481 else canvas
.repaint(displ
, extra
);
1482 if (repaint_navigator
) {
1483 DisplayablePanel dp
= ht_panels
.get(displ
);
1484 if (null != dp
) dp
.repaint(); // is null when creating it, or after deleting it
1485 navigator
.repaint(true); // everything
1489 /** Repaint the snapshot for the given Displayable both at the DisplayNavigator and on its panel,and only if it has not been painted before. This method is intended for the loader to know when to paint a snap, to avoid overhead. */
1490 static public void repaintSnapshot(final Displayable displ
) {
1491 for (final Display d
: al_displays
) {
1492 if (d
.layer
.contains(displ
)) {
1493 if (!d
.navigator
.isPainted(displ
)) {
1494 DisplayablePanel dp
= d
.ht_panels
.get(displ
);
1495 if (null != dp
) dp
.repaint(); // is null when creating it, or after deleting it
1496 d
.navigator
.repaint(displ
);
1502 /** Repaint the given Rectangle in all Displays showing the layer, updating the offscreen image if any. */
1503 static public void repaint(final Layer layer
, final Rectangle r
, final int extra
) {
1504 repaint(layer
, extra
, r
, true, true);
1507 static public void repaint(final Layer layer
, final int extra
, final Rectangle r
, final boolean update_navigator
) {
1508 repaint(layer
, extra
, r
, update_navigator
, true);
1511 static public void repaint(final Layer layer
, final int extra
, final Rectangle r
, final boolean update_navigator
, final boolean update_graphics
) {
1512 if (repaint_disabled
) return;
1513 for (final Display d
: al_displays
) {
1514 if (layer
== d
.layer
) {
1515 d
.canvas
.setUpdateGraphics(update_graphics
);
1516 d
.canvas
.repaint(r
, extra
);
1517 if (update_navigator
) {
1518 d
.navigator
.repaint(true);
1519 Utils
.updateComponent(d
.tabs
.getSelectedComponent());
1526 /** Repaint the given Rectangle in all Displays showing the layer, optionally updating the offscreen image (if any). */
1527 static public void repaint(final Layer layer
, final Rectangle r
, final int extra
, final boolean update_graphics
) {
1528 if (repaint_disabled
) return;
1529 for (final Display d
: al_displays
) {
1530 if (layer
== d
.layer
) {
1531 d
.canvas
.setUpdateGraphics(update_graphics
);
1532 d
.canvas
.repaint(r
, extra
);
1533 d
.navigator
.repaint(update_graphics
);
1534 if (update_graphics
) Utils
.updateComponent(d
.tabs
.getSelectedComponent());
1539 /** Repaint the DisplayablePanel (and DisplayNavigator) only for the given Displayable, in all Displays showing the given Layer. */
1540 static public void repaint(final Layer layer
, final Displayable displ
) {
1541 if (repaint_disabled
) return;
1542 for (final Display d
: al_displays
) {
1543 if (layer
== d
.layer
) {
1544 DisplayablePanel dp
= d
.ht_panels
.get(displ
);
1545 if (null != dp
) dp
.repaint();
1546 d
.navigator
.repaint(true);
1551 static public void repaint(LayerSet set
, Displayable displ
, int extra
) {
1552 repaint(set
, displ
, null, extra
);
1555 static public void repaint(LayerSet set
, Displayable displ
, Rectangle r
, int extra
) {
1556 repaint(set
, displ
, r
, extra
, true);
1559 /** Repaint the Displayable in every Display that shows a Layer belonging to the given LayerSet. */
1560 static public void repaint(final LayerSet set
, final Displayable displ
, final Rectangle r
, final int extra
, final boolean repaint_navigator
) {
1561 if (repaint_disabled
) return;
1562 for (final Display d
: al_displays
) {
1563 if (set
.contains(d
.layer
)) {
1564 if (repaint_navigator
) {
1565 if (null != displ
) {
1566 DisplayablePanel dp
= d
.ht_panels
.get(displ
);
1567 if (null != dp
) dp
.repaint();
1569 d
.navigator
.repaint(true);
1571 if (null == displ
|| displ
!= d
.active
) d
.setUpdateGraphics(true); // safeguard
1572 // paint the given box or the actual Displayable's box
1573 if (null != r
) d
.canvas
.repaint(r
, extra
);
1574 else d
.canvas
.repaint(displ
, extra
);
1579 /** Repaint the entire LayerSet, in all Displays showing a Layer of it.*/
1580 static public void repaint(final LayerSet set
) {
1581 if (repaint_disabled
) return;
1582 for (final Display d
: al_displays
) {
1583 if (set
.contains(d
.layer
)) {
1584 d
.navigator
.repaint(true);
1585 d
.canvas
.repaint(true);
1589 /** Repaint the given box in the LayerSet, in all Displays showing a Layer of it.*/
1590 static public void repaint(final LayerSet set
, final Rectangle box
) {
1591 if (repaint_disabled
) return;
1592 for (final Display d
: al_displays
) {
1593 if (set
.contains(d
.layer
)) {
1594 d
.navigator
.repaint(box
);
1595 d
.canvas
.repaint(box
, 0, true);
1599 /** Repaint the entire Layer, in all Displays showing it, including the tabs.*/
1600 static public void repaint(final Layer layer
) { // TODO this method overlaps with update(layer)
1601 if (repaint_disabled
) return;
1602 for (final Display d
: al_displays
) {
1603 if (layer
== d
.layer
) {
1604 d
.navigator
.repaint(true);
1605 d
.canvas
.repaint(true);
1610 /** Call repaint on all open Displays. */
1611 static public void repaint() {
1612 if (repaint_disabled
) {
1613 Utils
.logAll("Can't repaint -- repainting is disabled!");
1616 for (final Display d
: al_displays
) {
1617 d
.navigator
.repaint(true);
1618 d
.canvas
.repaint(true);
1622 static private boolean repaint_disabled
= false;
1624 /** Set a flag to enable/disable repainting of all Display instances. */
1625 static protected void setRepaint(boolean b
) {
1626 repaint_disabled
= !b
;
1629 public Rectangle
getBounds() {
1630 return frame
.getBounds();
1633 public Point
getLocation() {
1634 return frame
.getLocation();
1637 public JFrame
getFrame() {
1641 public void setLocation(Point p
) {
1642 this.frame
.setLocation(p
);
1645 public Displayable
getActive() {
1646 return active
; //TODO this should return selection.active !!
1649 public void select(Displayable d
) {
1653 /** Select/deselect accordingly to the current state and the shift key. */
1654 public void select(final Displayable d
, final boolean shift_down
) {
1655 if (null != active
&& active
!= d
&& active
.getClass() != Patch
.class) {
1656 // active is being deselected, so link underlying patches
1657 active
.linkPatches();
1660 //Utils.log2("Display.select: clearing selection");
1661 canvas
.setUpdateGraphics(true);
1666 //Utils.log2("Display.select: single selection");
1671 } else if (selection
.contains(d
)) {
1673 selection
.remove(d
);
1674 //Utils.log2("Display.select: removing from a selection");
1676 //Utils.log2("Display.select: activing within a selection");
1677 selection
.setActive(d
);
1680 //Utils.log2("Display.select: adding to an existing selection");
1683 // update the image shown to ImageJ
1684 // NO longer necessary, always he same FakeImagePlus // setTempCurrentImage();
1687 protected void choose(int screen_x_p
, int screen_y_p
, int x_p
, int y_p
, final Class c
) {
1688 choose(screen_x_p
, screen_y_p
, x_p
, y_p
, false, c
);
1690 protected void choose(int screen_x_p
, int screen_y_p
, int x_p
, int y_p
) {
1691 choose(screen_x_p
, screen_y_p
, x_p
, y_p
, false, null);
1694 /** Find a Displayable to add to the selection under the given point (which is in offscreen coords); will use a popup menu to give the user a range of Displayable objects to select from. */
1695 protected void choose(int screen_x_p
, int screen_y_p
, int x_p
, int y_p
, boolean shift_down
, Class c
) {
1696 //Utils.log("Display.choose: x,y " + x_p + "," + y_p);
1697 final ArrayList
<Displayable
> al
= new ArrayList
<Displayable
>(layer
.find(x_p
, y_p
, true));
1698 al
.addAll(layer
.getParent().findZDisplayables(layer
, x_p
, y_p
, true)); // only visible ones
1700 Displayable act
= this.active
;
1702 canvas
.setUpdateGraphics(true);
1703 //Utils.log("choose: set active to null");
1704 // fixing lack of repainting for unknown reasons, of the active one TODO this is a temporary solution
1705 if (null != act
) Display
.repaint(layer
, act
, 5);
1706 } else if (1 == al
.size()) {
1707 Displayable d
= (Displayable
)al
.get(0);
1708 if (null != c
&& d
.getClass() != c
) {
1712 select(d
, shift_down
);
1713 //Utils.log("choose 1: set active to " + active);
1715 if (al
.contains(active
) && !shift_down
) {
1719 // check if at least one of them is of class c
1720 // if only one is of class c, set as selected
1722 for (Iterator it
= al
.iterator(); it
.hasNext(); ) {
1723 Object ob
= it
.next();
1724 if (ob
.getClass() != c
) it
.remove();
1726 if (0 == al
.size()) {
1731 if (1 == al
.size()) {
1732 select((Displayable
)al
.get(0), shift_down
);
1735 // else, choose among the many
1737 choose(screen_x_p
, screen_y_p
, al
, shift_down
, x_p
, y_p
);
1739 //Utils.log("choose many: set active to " + active);
1743 private void choose(final int screen_x_p
, final int screen_y_p
, final Collection al
, final boolean shift_down
, final int x_p
, final int y_p
) {
1744 // show a popup on the canvas to choose
1747 final Object lock
= new Object();
1748 final DisplayableChooser d_chooser
= new DisplayableChooser(al
, lock
);
1749 final JPopupMenu pop
= new JPopupMenu("Select:");
1750 final Iterator itu
= al
.iterator();
1751 while (itu
.hasNext()) {
1752 Displayable d
= (Displayable
)itu
.next();
1753 JMenuItem menu_item
= new JMenuItem(d
.toString());
1754 menu_item
.addActionListener(d_chooser
);
1760 pop
.show(canvas
, screen_x_p
, screen_y_p
);
1764 //now wait until selecting something
1765 synchronized(lock
) {
1769 } catch (InterruptedException ie
) {}
1770 } while (d_chooser
.isWaiting() && pop
.isShowing());
1773 //grab the chosen Displayable object
1774 Displayable d
= d_chooser
.getChosen();
1775 //Utils.log("Chosen: " + d.toString());
1776 if (null == d
) { Utils
.log2("Display.choose: returning a null!"); }
1777 select(d
, shift_down
);
1778 pop
.setVisible(false);
1780 // fix selection bug: never receives mouseReleased event when the popup shows
1781 selection
.mouseReleased(null, x_p
, y_p
, x_p
, y_p
, x_p
, y_p
);
1786 /** Used by the Selection exclusively. This method will change a lot in the near future, and may disappear in favor of getSelection().getActive(). All this method does is update GUI components related to the currently active and the newly active Displayable; called through SwingUtilities.invokeLater. */
1787 protected void setActive(final Displayable displ
) {
1788 final Displayable prev_active
= this.active
;
1789 this.active
= displ
;
1790 SwingUtilities
.invokeLater(new Runnable() { public void run() {
1792 // renew current image if necessary
1793 if (null != displ
&& displ
== prev_active
) {
1794 // make sure the proper tab is selected.
1798 // deactivate previously active
1799 if (null != prev_active
) {
1800 final DisplayablePanel ob
= ht_panels
.get(prev_active
);
1801 if (null != ob
) ob
.setActive(false);
1802 // erase "decorations" of the previously active
1803 canvas
.repaint(selection
.getBox(), 4);
1805 // activate the new active
1806 if (null != displ
) {
1807 final DisplayablePanel ob
= ht_panels
.get(displ
);
1808 if (null != ob
) ob
.setActive(true);
1809 updateInDatabase("active_displayable_id");
1810 if (displ
.getClass() != Patch
.class) project
.select(displ
); // select the node in the corresponding tree, if any.
1811 // select the proper tab, and scroll to visible
1813 boolean update_graphics
= null == prev_active
|| paintsBelow(prev_active
, displ
); // or if it's an image, but that's by default in the repaint method
1814 repaint(displ
, null, 5, false, update_graphics
); // to show the border, and to repaint out of the background image
1815 transp_slider
.setValue((int)(displ
.getAlpha() * 100));
1817 //ensure decorations are removed from the panels, for Displayables in a selection besides the active one
1818 Utils
.updateComponent(tabs
.getSelectedComponent());
1823 /** If the other paints under the base. */
1824 public boolean paintsBelow(Displayable base
, Displayable other
) {
1825 boolean zd_base
= base
instanceof ZDisplayable
;
1826 boolean zd_other
= other
instanceof ZDisplayable
;
1828 if (base
instanceof DLabel
) return true; // zd paints under label
1829 if (!zd_base
) return false; // any zd paints over a mere displ if not a label
1831 // both zd, compare indices
1832 ArrayList
<ZDisplayable
> al
= other
.getLayerSet().getZDisplayables();
1833 return al
.indexOf(base
) > al
.indexOf(other
);
1837 // both displ, compare indices
1838 ArrayList
<Displayable
> al
= other
.getLayer().getDisplayables();
1839 return al
.indexOf(base
) > al
.indexOf(other
);
1841 // base is zd, other is d
1842 if (other
instanceof DLabel
) return false;
1848 /** Select the proper tab, and also scroll it to show the given Displayable -unless it's a LayerSet, and unless the proper tab is already showing. */
1849 private void selectTab(final Displayable displ
) {
1850 Method method
= null;
1852 if (!(displ
instanceof LayerSet
)) {
1853 method
= Display
.class.getDeclaredMethod("selectTab", new Class
[]{displ
.getClass()});
1855 } catch (Exception e
) {
1858 if (null != method
) {
1859 final Method me
= method
;
1860 dispatcher
.exec(new Runnable() { public void run() {
1862 me
.setAccessible(true);
1863 me
.invoke(Display
.this, new Object
[]{displ
});
1864 } catch (Exception e
) { IJError
.print(e
); }
1869 private void selectTab(Patch patch
) {
1870 tabs
.setSelectedComponent(scroll_patches
);
1871 scrollToShow(scroll_patches
, ht_panels
.get(patch
));
1874 private void selectTab(Profile profile
) {
1875 tabs
.setSelectedComponent(scroll_profiles
);
1876 scrollToShow(scroll_profiles
, ht_panels
.get(profile
));
1879 private void selectTab(DLabel label
) {
1880 tabs
.setSelectedComponent(scroll_labels
);
1881 scrollToShow(scroll_labels
, ht_panels
.get(label
));
1884 private void selectTab(ZDisplayable zd
) {
1885 tabs
.setSelectedComponent(scroll_zdispl
);
1886 scrollToShow(scroll_zdispl
, ht_panels
.get(zd
));
1889 private void selectTab(Pipe d
) { selectTab((ZDisplayable
)d
); }
1890 private void selectTab(Polyline d
) { selectTab((ZDisplayable
)d
); }
1891 private void selectTab(AreaList d
) { selectTab((ZDisplayable
)d
); }
1892 private void selectTab(Ball d
) { selectTab((ZDisplayable
)d
); }
1893 private void selectTab(Dissector d
) { selectTab((ZDisplayable
)d
); }
1895 /** A method to update the given tab, creating a new DisplayablePanel for each Displayable present in the given ArrayList, and storing it in the ht_panels (which is cleared first). */
1896 private void updateTab(final Container tab
, final String label
, final ArrayList al
) {
1897 final boolean[] recreated
= new boolean[]{false, true, true};
1898 dispatcher
.execSwing(new Runnable() { public void run() {
1900 if (0 == al
.size()) {
1902 tab
.add(new JLabel("No " + label
+ "."));
1904 Component
[] comp
= tab
.getComponents();
1906 if (1 == comp
.length
&& comp
[0].getClass() == JLabel
.class) {
1910 for (Iterator it
= al
.iterator(); it
.hasNext(); ) {
1911 Displayable d
= (Displayable
)it
.next();
1912 DisplayablePanel dp
= null;
1913 if (next
< comp
.length
) {
1914 dp
= (DisplayablePanel
)comp
[next
++]; // recycling panels
1917 dp
= new DisplayablePanel(Display
.this, d
);
1920 ht_panels
.put(d
, dp
);
1922 if (next
< comp
.length
) {
1923 // remove from the end, to avoid potential repaints of other panels
1924 for (int i
=comp
.length
-1; i
>=next
; i
--) {
1928 recreated
[0] = true;
1935 if (null != Display
.this.active
) scrollToShow(Display
.this.active
);
1936 } catch (Throwable e
) { IJError
.print(e
); }
1940 static public void setActive(final Object event
, final Displayable displ
) {
1941 if (!(event
instanceof InputEvent
)) return;
1942 // find which Display
1943 for (final Display d
: al_displays
) {
1944 if (d
.isOrigin((InputEvent
)event
)) {
1951 /** Find out whether this Display is Transforming its active Displayable. */
1952 public boolean isTransforming() {
1953 return canvas
.isTransforming();
1956 /** Find whether any Display is transforming the given Displayable. */
1957 static public boolean isTransforming(final Displayable displ
) {
1958 for (final Display d
: al_displays
) {
1959 if (null != d
.active
&& d
.active
== displ
&& d
.canvas
.isTransforming()) return true;
1964 static public boolean isAligning(final LayerSet set
) {
1965 for (final Display d
: al_displays
) {
1966 if (d
.layer
.getParent() == set
&& set
.isAligning()) {
1973 /** Set the front Display to transform the Displayable only if no other canvas is transforming it. */
1974 static public void setTransforming(final Displayable displ
) {
1975 if (null == front
) return;
1976 if (front
.active
!= displ
) return;
1977 for (final Display d
: al_displays
) {
1978 if (d
.active
== displ
) {
1979 if (d
.canvas
.isTransforming()) {
1980 Utils
.showMessage("Already transforming " + displ
.getTitle());
1985 front
.canvas
.setTransforming(true);
1988 /** Check whether the source of the event is located in this instance.*/
1989 private boolean isOrigin(InputEvent event
) {
1990 Object source
= event
.getSource();
1991 // find it ... check the canvas for now TODO
1992 if (canvas
== source
) {
1998 /** Get the layer of the front Display, or null if none.*/
1999 static public Layer
getFrontLayer() {
2000 if (null == front
) return null;
2004 /** Get the layer of an open Display of the given Project, or null if none.*/
2005 static public Layer
getFrontLayer(final Project project
) {
2006 if (null == front
) return null;
2007 if (front
.project
== project
) return front
.layer
;
2008 // else, find an open Display for the given Project, if any
2009 for (final Display d
: al_displays
) {
2010 if (d
.project
== project
) {
2015 return null; // none found
2018 static public Display
getFront(final Project project
) {
2019 if (null == front
) return null;
2020 if (front
.project
== project
) return front
;
2021 for (final Display d
: al_displays
) {
2022 if (d
.project
== project
) {
2030 public boolean isReadOnly() {
2031 // TEMPORARY: in the future one will be able show displays as read-only to other people, remotely
2035 static public void showPopup(Component c
, int x
, int y
) {
2036 if (null != front
) front
.getPopupMenu().show(c
, x
, y
);
2039 /** Return a context-sensitive popup menu. */
2040 public JPopupMenu
getPopupMenu() { // called from canvas
2041 // get the job canceling dialog
2042 if (!canvas
.isInputEnabled()) {
2043 return project
.getLoader().getJobsPopup(this);
2047 this.popup
= new JPopupMenu();
2048 JMenuItem item
= null;
2051 if (ProjectToolbar
.ALIGN
== Toolbar
.getToolId()) {
2052 boolean aligning
= layer
.getParent().isAligning();
2053 item
= new JMenuItem("Cancel alignment"); item
.addActionListener(this); popup
.add(item
);
2054 item
.setAccelerator(KeyStroke
.getKeyStroke(KeyEvent
.VK_ESCAPE
, 0, true));
2055 if (!aligning
) item
.setEnabled(false);
2056 item
= new JMenuItem("Align with landmarks"); item
.addActionListener(this); popup
.add(item
);
2057 item
.setAccelerator(KeyStroke
.getKeyStroke(KeyEvent
.VK_ENTER
, 0, true));
2058 if (!aligning
) item
.setEnabled(false);
2059 item
= new JMenuItem("Align and register"); item
.addActionListener(this); popup
.add(item
);
2060 if (!aligning
) item
.setEnabled(false);
2061 item
= new JMenuItem("Align using profiles"); item
.addActionListener(this); popup
.add(item
);
2062 if (!aligning
|| selection
.isEmpty() || !selection
.contains(Profile
.class)) item
.setEnabled(false);
2063 item
= new JMenuItem("Align stack slices"); item
.addActionListener(this); popup
.add(item
);
2064 if (selection
.isEmpty() || ! (getActive().getClass() == Patch
.class && ((Patch
)getActive()).isStack())) item
.setEnabled(false);
2065 item
= new JMenuItem("Align layers"); item
.addActionListener(this); popup
.add(item
);
2066 if (1 == layer
.getParent().size()) item
.setEnabled(false);
2067 item
= new JMenuItem("Align multi-layer mosaic"); item
.addActionListener(this); popup
.add(item
);
2068 if (1 == layer
.getParent().size()) item
.setEnabled(false);
2073 if (null != active
) {
2074 if (!canvas
.isTransforming()) {
2075 if (active
instanceof Profile
) {
2076 item
= new JMenuItem("Duplicate, link and send to next layer"); item
.addActionListener(this); popup
.add(item
);
2077 Layer nl
= layer
.getParent().next(layer
);
2078 if (nl
== layer
) item
.setEnabled(false);
2079 item
= new JMenuItem("Duplicate, link and send to previous layer"); item
.addActionListener(this); popup
.add(item
);
2080 nl
= layer
.getParent().previous(layer
);
2081 if (nl
== layer
) item
.setEnabled(false);
2083 menu
= new JMenu("Duplicate, link and send to");
2084 ArrayList al
= layer
.getParent().getLayers();
2085 final Iterator it
= al
.iterator();
2087 while (it
.hasNext()) {
2088 Layer la
= (Layer
)it
.next();
2089 item
= new JMenuItem(i
+ ": z = " + la
.getZ()); item
.addActionListener(this); menu
.add(item
); // TODO should label which layers contain Profile instances linked to the one being duplicated
2090 if (la
== this.layer
) item
.setEnabled(false);
2094 item
= new JMenuItem("Duplicate, link and send to..."); item
.addActionListener(this); popup
.add(item
);
2096 popup
.addSeparator();
2098 item
= new JMenuItem("Unlink from images"); item
.addActionListener(this); popup
.add(item
);
2099 if (!active
.isLinked()) item
.setEnabled(false); // isLinked() checks if it's linked to a Patch in its own layer
2100 item
= new JMenuItem("Show in 3D"); item
.addActionListener(this); popup
.add(item
);
2101 popup
.addSeparator();
2102 } else if (active
instanceof Patch
) {
2103 item
= new JMenuItem("Unlink from images"); item
.addActionListener(this); popup
.add(item
);
2104 if (!active
.isLinked(Patch
.class)) item
.setEnabled(false);
2105 if (((Patch
)active
).isStack()) {
2106 item
= new JMenuItem("Unlink slices"); item
.addActionListener(this); popup
.add(item
);
2108 int n_sel_patches
= selection
.getSelected(Patch
.class).size();
2109 if (1 == n_sel_patches
) {
2110 item
= new JMenuItem("Snap"); item
.addActionListener(this); popup
.add(item
);
2111 } else if (n_sel_patches
> 1) {
2112 item
= new JMenuItem("Montage"); item
.addActionListener(this); popup
.add(item
);
2113 item
= new JMenuItem("Lens correction"); item
.addActionListener(this); popup
.add(item
);
2114 item
= new JMenuItem("Blend"); item
.addActionListener(this); popup
.add(item
);
2116 item
= new JMenuItem("Remove alpha mask"); item
.addActionListener(this); popup
.add(item
);
2117 if ( ! ((Patch
)active
).hasAlphaMask()) item
.setEnabled(false);
2118 item
= new JMenuItem("Link images..."); item
.addActionListener(this); popup
.add(item
);
2119 item
= new JMenuItem("View volume"); item
.addActionListener(this); popup
.add(item
);
2120 HashSet hs
= active
.getLinked(Patch
.class);
2121 if (null == hs
|| 0 == hs
.size()) item
.setEnabled(false);
2122 item
= new JMenuItem("View orthoslices"); item
.addActionListener(this); popup
.add(item
);
2123 if (null == hs
|| 0 == hs
.size()) item
.setEnabled(false); // if no Patch instances among the directly linked, then it's not a stack
2124 popup
.addSeparator();
2126 item
= new JMenuItem("Unlink"); item
.addActionListener(this); popup
.add(item
);
2127 item
= new JMenuItem("Show in 3D"); item
.addActionListener(this); popup
.add(item
);
2128 popup
.addSeparator();
2130 if (active
instanceof AreaList
) {
2131 item
= new JMenuItem("Merge"); item
.addActionListener(this); popup
.add(item
);
2132 ArrayList al
= selection
.getSelected();
2134 for (Iterator it
= al
.iterator(); it
.hasNext(); ) {
2135 if (it
.next().getClass() == AreaList
.class) n
++;
2137 if (n
< 2) item
.setEnabled(false);
2138 } else if (active
instanceof Pipe
) {
2139 item
= new JMenuItem("Identify..."); item
.addActionListener(this); popup
.add(item
);
2140 item
= new JMenuItem("Identify with axes..."); item
.addActionListener(this); popup
.add(item
);
2143 if (canvas
.isTransforming()) {
2144 item
= new JMenuItem("Apply transform"); item
.addActionListener(this); popup
.add(item
);
2145 item
.setAccelerator(KeyStroke
.getKeyStroke(KeyEvent
.VK_ENTER
, 0, true)); // dummy, for I don't add a MenuKeyListener, but "works" through the normal key listener. It's here to provide a visual cue
2146 item
= new JMenuItem("Apply transform propagating to last layer"); item
.addActionListener(this); popup
.add(item
);
2147 if (layer
.getParent().indexOf(layer
) == layer
.getParent().size() -1) item
.setEnabled(false);
2148 item
= new JMenuItem("Apply transform propagating to first layer"); item
.addActionListener(this); popup
.add(item
);
2149 if (0 == layer
.getParent().indexOf(layer
)) item
.setEnabled(false);
2151 item
= new JMenuItem("Transform"); item
.addActionListener(this); popup
.add(item
);
2152 item
.setAccelerator(KeyStroke
.getKeyStroke(KeyEvent
.VK_T
, 0, true));
2154 item
= new JMenuItem("Cancel transform"); item
.addActionListener(this); popup
.add(item
);
2155 item
.setAccelerator(KeyStroke
.getKeyStroke(KeyEvent
.VK_ESCAPE
, 0, true));
2156 if (!canvas
.isTransforming()) item
.setEnabled(false);
2157 if (canvas
.isTransforming()) {
2158 item
= new JMenuItem("Specify transform..."); item
.addActionListener(this); popup
.add(item
);
2161 if (!canvas
.isTransforming()) {
2162 item
= new JMenuItem("Duplicate"); item
.addActionListener(this); popup
.add(item
);
2163 item
= new JMenuItem("Color..."); item
.addActionListener(this); popup
.add(item
);
2164 if (active
instanceof LayerSet
) item
.setEnabled(false);
2165 if (active
.isLocked()) {
2166 item
= new JMenuItem("Unlock"); item
.addActionListener(this); popup
.add(item
);
2168 item
= new JMenuItem("Lock"); item
.addActionListener(this); popup
.add(item
);
2170 menu
= new JMenu("Move");
2171 popup
.addSeparator();
2172 LayerSet ls
= layer
.getParent();
2173 item
= new JMenuItem("Move to top"); item
.addActionListener(this); menu
.add(item
);
2174 item
.setAccelerator(KeyStroke
.getKeyStroke(KeyEvent
.VK_HOME
, 0, true)); // this is just to draw the key name by the menu; it does not incur on any event being generated (that I know if), and certainly not any event being listened to by TrakEM2.
2175 if (ls
.isTop(active
)) item
.setEnabled(false);
2176 item
= new JMenuItem("Move up"); item
.addActionListener(this); menu
.add(item
);
2177 item
.setAccelerator(KeyStroke
.getKeyStroke(KeyEvent
.VK_PAGE_UP
, 0, true));
2178 if (ls
.isTop(active
)) item
.setEnabled(false);
2179 item
= new JMenuItem("Move down"); item
.addActionListener(this); menu
.add(item
);
2180 item
.setAccelerator(KeyStroke
.getKeyStroke(KeyEvent
.VK_PAGE_DOWN
, 0, true));
2181 if (ls
.isBottom(active
)) item
.setEnabled(false);
2182 item
= new JMenuItem("Move to bottom"); item
.addActionListener(this); menu
.add(item
);
2183 item
.setAccelerator(KeyStroke
.getKeyStroke(KeyEvent
.VK_END
, 0, true));
2184 if (ls
.isBottom(active
)) item
.setEnabled(false);
2187 popup
.addSeparator();
2188 item
= new JMenuItem("Delete..."); item
.addActionListener(this); popup
.add(item
);
2190 if (active
instanceof Patch
) {
2191 if (!active
.isOnlyLinkedTo(Patch
.class)) {
2192 item
.setEnabled(false);
2194 } else if (!(active
instanceof DLabel
)) { // can't delete elements from the trees (Profile, Pipe, LayerSet)
2195 item
.setEnabled(false);
2197 } catch (Exception e
) { IJError
.print(e
); item
.setEnabled(false); }
2199 if (active
instanceof Patch
) {
2200 item
= new JMenuItem("Revert"); item
.addActionListener(this); popup
.add(item
);
2201 popup
.addSeparator();
2203 item
= new JMenuItem("Properties..."); item
.addActionListener(this); popup
.add(item
);
2204 item
= new JMenuItem("Show centered"); item
.addActionListener(this); popup
.add(item
);
2206 popup
.addSeparator();
2208 if (! (active
instanceof ZDisplayable
)) {
2209 ArrayList al_layers
= layer
.getParent().getLayers();
2210 int i_layer
= al_layers
.indexOf(layer
);
2211 int n_layers
= al_layers
.size();
2212 item
= new JMenuItem("Send to previous layer"); item
.addActionListener(this); popup
.add(item
);
2213 if (1 == n_layers
|| 0 == i_layer
|| active
.isLinked()) item
.setEnabled(false);
2214 // check if the active is a profile and contains a link to another profile in the layer it is going to be sent to, or it is linked
2215 else if (active
instanceof Profile
&& !active
.canSendTo(layer
.getParent().previous(layer
))) item
.setEnabled(false);
2216 item
= new JMenuItem("Send to next layer"); item
.addActionListener(this); popup
.add(item
);
2217 if (1 == n_layers
|| n_layers
-1 == i_layer
|| active
.isLinked()) item
.setEnabled(false);
2218 else if (active
instanceof Profile
&& !active
.canSendTo(layer
.getParent().next(layer
))) item
.setEnabled(false);
2221 menu
= new JMenu("Send linked group to...");
2222 if (active
.hasLinkedGroupWithinLayer(this.layer
)) {
2224 for (final Layer la
: ls
.getLayers()) {
2225 String layer_title
= i
+ ": " + la
.getTitle();
2226 if (-1 == layer_title
.indexOf(' ')) layer_title
+= " ";
2227 item
= new JMenuItem(layer_title
); item
.addActionListener(this); menu
.add(item
);
2228 if (la
== this.layer
) item
.setEnabled(false);
2233 menu
.setEnabled(false);
2234 //Utils.log("Active's linked group not within layer.");
2237 popup
.addSeparator();
2242 if (!canvas
.isTransforming()) {
2244 item
= new JMenuItem("Undo");item
.addActionListener(this); popup
.add(item
);
2245 if (!layer
.getParent().canUndo() || canvas
.isTransforming()) item
.setEnabled(false);
2246 item
.setAccelerator(KeyStroke
.getKeyStroke(KeyEvent
.VK_Z
, Utils
.getControlModifier(), true));
2247 item
= new JMenuItem("Redo");item
.addActionListener(this); popup
.add(item
);
2248 if (!layer
.getParent().canRedo() || canvas
.isTransforming()) item
.setEnabled(false);
2249 item
.setAccelerator(KeyStroke
.getKeyStroke(KeyEvent
.VK_Z
, Event
.SHIFT_MASK
| Event
.CTRL_MASK
, true));
2250 popup
.addSeparator();
2252 // Would get so much simpler with a clojure macro ...
2255 menu
= new JMenu("Hide/Unhide");
2256 item
= new JMenuItem("Hide deselected"); item
.addActionListener(this); menu
.add(item
); item
.setAccelerator(KeyStroke
.getKeyStroke(KeyEvent
.VK_H
, Event
.SHIFT_MASK
, true));
2257 boolean none
= 0 == selection
.getNSelected();
2258 if (none
) item
.setEnabled(false);
2259 item
= new JMenuItem("Hide deselected except images"); item
.addActionListener(this); menu
.add(item
); item
.setAccelerator(KeyStroke
.getKeyStroke(KeyEvent
.VK_H
, Event
.SHIFT_MASK
| Event
.ALT_MASK
, true));
2260 if (none
) item
.setEnabled(false);
2261 item
= new JMenuItem("Hide selected"); item
.addActionListener(this); menu
.add(item
); item
.setAccelerator(KeyStroke
.getKeyStroke(KeyEvent
.VK_H
, 0, true));
2262 if (none
) item
.setEnabled(false);
2263 none
= ! layer
.getParent().containsDisplayable(DLabel
.class);
2264 item
= new JMenuItem("Hide all labels"); item
.addActionListener(this); menu
.add(item
);
2265 if (none
) item
.setEnabled(false);
2266 item
= new JMenuItem("Unhide all labels"); item
.addActionListener(this); menu
.add(item
);
2267 if (none
) item
.setEnabled(false);
2268 none
= ! layer
.getParent().contains(AreaList
.class);
2269 item
= new JMenuItem("Hide all arealists"); item
.addActionListener(this); menu
.add(item
);
2270 if (none
) item
.setEnabled(false);
2271 item
= new JMenuItem("Unhide all arealists"); item
.addActionListener(this); menu
.add(item
);
2272 if (none
) item
.setEnabled(false);
2273 none
= ! layer
.contains(Profile
.class);
2274 item
= new JMenuItem("Hide all profiles"); item
.addActionListener(this); menu
.add(item
);
2275 if (none
) item
.setEnabled(false);
2276 item
= new JMenuItem("Unhide all profiles"); item
.addActionListener(this); menu
.add(item
);
2277 if (none
) item
.setEnabled(false);
2278 none
= ! layer
.getParent().contains(Pipe
.class);
2279 item
= new JMenuItem("Hide all pipes"); item
.addActionListener(this); menu
.add(item
);
2280 if (none
) item
.setEnabled(false);
2281 item
= new JMenuItem("Unhide all pipes"); item
.addActionListener(this); menu
.add(item
);
2282 if (none
) item
.setEnabled(false);
2283 none
= ! layer
.getParent().contains(Polyline
.class);
2284 item
= new JMenuItem("Hide all polylines"); item
.addActionListener(this); menu
.add(item
);
2285 if (none
) item
.setEnabled(false);
2286 item
= new JMenuItem("Unhide all polylines"); item
.addActionListener(this); menu
.add(item
);
2287 if (none
) item
.setEnabled(false);
2288 none
= ! layer
.getParent().contains(Ball
.class);
2289 item
= new JMenuItem("Hide all balls"); item
.addActionListener(this); menu
.add(item
);
2290 if (none
) item
.setEnabled(false);
2291 item
= new JMenuItem("Unhide all balls"); item
.addActionListener(this); menu
.add(item
);
2292 if (none
) item
.setEnabled(false);
2293 none
= ! layer
.getParent().containsDisplayable(Patch
.class);
2294 item
= new JMenuItem("Hide all images"); item
.addActionListener(this); menu
.add(item
);
2295 if (none
) item
.setEnabled(false);
2296 item
= new JMenuItem("Unhide all images"); item
.addActionListener(this); menu
.add(item
);
2297 if (none
) item
.setEnabled(false);
2298 item
= new JMenuItem("Hide all but images"); item
.addActionListener(this); menu
.add(item
);
2299 item
= new JMenuItem("Unhide all"); item
.addActionListener(this); menu
.add(item
); item
.setAccelerator(KeyStroke
.getKeyStroke(KeyEvent
.VK_H
, Event
.ALT_MASK
, true));
2302 } catch (Exception e
) { IJError
.print(e
); }
2304 JMenu adjust_menu
= new JMenu("Adjust");
2305 item
= new JMenuItem("Calibration..."); item
.addActionListener(this); adjust_menu
.add(item
);
2306 item
= new JMenuItem("Enhance contrast layer-wise..."); item
.addActionListener(this); adjust_menu
.add(item
);
2307 item
= new JMenuItem("Enhance contrast (selected images)..."); item
.addActionListener(this); adjust_menu
.add(item
);
2308 if (selection
.isEmpty()) item
.setEnabled(false);
2309 item
= new JMenuItem("Set Min and Max layer-wise..."); item
.addActionListener(this); adjust_menu
.add(item
);
2310 item
= new JMenuItem("Set Min and Max (selected images)..."); item
.addActionListener(this); adjust_menu
.add(item
);
2311 if (selection
.isEmpty()) item
.setEnabled(false);
2312 popup
.add(adjust_menu
);
2314 menu
= new JMenu("Import");
2315 item
= new JMenuItem("Import image"); item
.addActionListener(this); menu
.add(item
);
2316 item
.setAccelerator(KeyStroke
.getKeyStroke(KeyEvent
.VK_I
, Event
.ALT_MASK
& Event
.SHIFT_MASK
, true));
2317 item
= new JMenuItem("Import stack..."); item
.addActionListener(this); menu
.add(item
);
2318 item
= new JMenuItem("Import grid..."); item
.addActionListener(this); menu
.add(item
);
2319 item
= new JMenuItem("Import sequence as grid..."); item
.addActionListener(this); menu
.add(item
);
2320 item
= new JMenuItem("Import from text file..."); item
.addActionListener(this); menu
.add(item
);
2321 item
= new JMenuItem("Import labels as arealists..."); item
.addActionListener(this); menu
.add(item
);
2324 menu
= new JMenu("Export");
2325 item
= new JMenuItem("Make flat image..."); item
.addActionListener(this); menu
.add(item
);
2326 item
= new JMenuItem("Arealists as labels (tif)"); item
.addActionListener(this); menu
.add(item
);
2327 if (0 == layer
.getParent().getZDisplayables(AreaList
.class).size()) item
.setEnabled(false);
2328 item
= new JMenuItem("Arealists as labels (amira)"); item
.addActionListener(this); menu
.add(item
);
2329 if (0 == layer
.getParent().getZDisplayables(AreaList
.class).size()) item
.setEnabled(false);
2332 menu
= new JMenu("Display");
2333 item
= new JMenuItem("Resize canvas/LayerSet..."); item
.addActionListener(this); menu
.add(item
);
2334 item
= new JMenuItem("Autoresize canvas/LayerSet"); item
.addActionListener(this); menu
.add(item
);
2335 // OBSOLETE // item = new JMenuItem("Rotate Layer/LayerSet..."); item.addActionListener(this); menu.add(item);
2336 item
= new JMenuItem("Properties ..."); item
.addActionListener(this); menu
.add(item
);
2339 menu
= new JMenu("Project");
2340 this.project
.getLoader().setupMenuItems(menu
, this.getProject());
2341 item
= new JMenuItem("Project properties..."); item
.addActionListener(this); menu
.add(item
);
2342 item
= new JMenuItem("Create subproject"); item
.addActionListener(this); menu
.add(item
);
2343 if (null == canvas
.getFakeImagePlus().getRoi()) item
.setEnabled(false);
2344 item
= new JMenuItem("Release memory..."); item
.addActionListener(this); menu
.add(item
);
2345 item
= new JMenuItem("Flush image cache"); item
.addActionListener(this); menu
.add(item
);
2346 item
= new JMenuItem("Regenerate all mipmaps"); item
.addActionListener(this); menu
.add(item
);
2349 menu
= new JMenu("Selection");
2350 item
= new JMenuItem("Select all"); item
.addActionListener(this); menu
.add(item
);
2351 item
.setAccelerator(KeyStroke
.getKeyStroke(KeyEvent
.VK_A
, Utils
.getControlModifier(), true));
2352 if (0 == layer
.getDisplayables().size() && 0 == layer
.getParent().getZDisplayables().size()) item
.setEnabled(false);
2353 item
= new JMenuItem("Select none"); item
.addActionListener(this); menu
.add(item
);
2354 if (0 == selection
.getNSelected()) item
.setEnabled(false);
2355 item
.setAccelerator(KeyStroke
.getKeyStroke(KeyEvent
.VK_ESCAPE
, 0, true));
2357 JMenu bytype
= new JMenu("Select all by type");
2358 item
= new JMenuItem("AreaList"); item
.addActionListener(bytypelistener
); bytype
.add(item
);
2359 item
= new JMenuItem("Ball"); item
.addActionListener(bytypelistener
); bytype
.add(item
);
2360 item
= new JMenuItem("Dissector"); item
.addActionListener(bytypelistener
); bytype
.add(item
);
2361 item
= new JMenuItem("Image"); item
.addActionListener(bytypelistener
); bytype
.add(item
);
2362 item
= new JMenuItem("Text"); item
.addActionListener(bytypelistener
); bytype
.add(item
);
2363 item
= new JMenuItem("Pipe"); item
.addActionListener(bytypelistener
); bytype
.add(item
);
2364 item
= new JMenuItem("Polyline"); item
.addActionListener(bytypelistener
); bytype
.add(item
);
2365 item
= new JMenuItem("Profile"); item
.addActionListener(bytypelistener
); bytype
.add(item
);
2368 item
= new JMenuItem("Restore selection"); item
.addActionListener(this); menu
.add(item
);
2369 item
= new JMenuItem("Select under ROI"); item
.addActionListener(this); menu
.add(item
);
2370 if (canvas
.getFakeImagePlus().getRoi() == null) item
.setEnabled(false);
2372 item
= new JMenuItem("Search..."); item
.addActionListener(this); popup
.add(item
);
2375 //canvas.add(popup);
2379 private ByTypeListener bytypelistener
= new ByTypeListener(this);
2381 static private class ByTypeListener
implements ActionListener
{
2383 ByTypeListener(final Display d
) {
2386 public void actionPerformed(final ActionEvent ae
) {
2387 final String command
= ae
.getActionCommand();
2389 final java
.awt
.geom
.Area aroi
= M
.getArea(d
.canvas
.getFakeImagePlus().getRoi());
2391 d
.dispatcher
.exec(new Runnable() { public void run() {
2394 String type
= command
;
2395 if (type
.equals("Image")) type
= "Patch";
2396 Class c
= Class
.forName("ini.trakem2.display." + type
);
2398 java
.util
.List
<Displayable
> a
= new ArrayList
<Displayable
>();
2400 a
.addAll(d
.layer
.getDisplayables(c
, aroi
, true));
2401 a
.addAll(d
.layer
.getParent().getZDisplayables(c
, d
.layer
, aroi
, true));
2403 a
.addAll(d
.layer
.getDisplayables(c
));
2404 a
.addAll(d
.layer
.getParent().getZDisplayables(c
));
2405 // Remove non-visible ones
2406 for (final Iterator
<Displayable
> it
= a
.iterator(); it
.hasNext(); ) {
2407 if (!it
.next().isVisible()) it
.remove();
2411 if (0 == a
.size()) return;
2413 boolean selected
= false;
2415 if (0 == ae
.getModifiers()) {
2416 Utils
.log2("first");
2417 d
.selection
.clear();
2418 d
.selection
.selectAll(a
);
2420 } else if (0 == (ae
.getModifiers() ^ Event
.SHIFT_MASK
)) {
2421 Utils
.log2("with shift");
2422 d
.selection
.selectAll(a
); // just add them to the current selection
2427 d
.selection
.setActive(a
.get(a
.size() -1));
2430 } catch (ClassNotFoundException e
) {
2431 Utils
.log2(e
.toString());
2438 /** Check if a panel for the given Displayable is completely visible in the JScrollPane */
2439 public boolean isWithinViewport(final Displayable d
) {
2440 final JScrollPane scroll
= (JScrollPane
)tabs
.getSelectedComponent();
2441 if (ht_tabs
.get(d
.getClass()) == scroll
) return isWithinViewport(scroll
, ht_panels
.get(d
));
2445 private boolean isWithinViewport(JScrollPane scroll
, DisplayablePanel dp
) {
2446 if(null == dp
) return false;
2447 JViewport view
= scroll
.getViewport();
2448 java
.awt
.Dimension dimensions
= view
.getExtentSize();
2449 java
.awt
.Point p
= view
.getViewPosition();
2451 if ((y
+ DisplayablePanel
.HEIGHT
- p
.y
) <= dimensions
.height
&& y
>= p
.y
) {
2457 /** Check if a panel for the given Displayable is partially visible in the JScrollPane */
2458 public boolean isPartiallyWithinViewport(final Displayable d
) {
2459 final JScrollPane scroll
= ht_tabs
.get(d
.getClass());
2460 if (tabs
.getSelectedComponent() == scroll
) return isPartiallyWithinViewport(scroll
, ht_panels
.get(d
));
2464 /** Check if a panel for the given Displayable is at least partially visible in the JScrollPane */
2465 private boolean isPartiallyWithinViewport(final JScrollPane scroll
, final DisplayablePanel dp
) {
2467 //Utils.log2("Display.isPartiallyWithinViewport: null DisplayablePanel ??");
2468 return false; // to fast for you baby
2470 JViewport view
= scroll
.getViewport();
2471 java
.awt
.Dimension dimensions
= view
.getExtentSize();
2472 java
.awt
.Point p
= view
.getViewPosition();
2474 if ( ((y
+ DisplayablePanel
.HEIGHT
- p
.y
) <= dimensions
.height
&& y
>= p
.y
) // completely visible
2475 || ((y
+ DisplayablePanel
.HEIGHT
- p
.y
) > dimensions
.height
&& y
< p
.y
+ dimensions
.height
) // partially hovering at the bottom
2476 || ((y
+ DisplayablePanel
.HEIGHT
) > p
.y
&& y
< p
.y
) // partially hovering at the top
2483 /** A function to make a Displayable panel be visible in the screen, by scrolling the viewport of the JScrollPane. */
2484 private void scrollToShow(final Displayable d
) {
2485 dispatcher
.execSwing(new Runnable() { public void run() {
2486 final JScrollPane scroll
= (JScrollPane
)tabs
.getSelectedComponent();
2487 if (d
instanceof ZDisplayable
&& scroll
== scroll_zdispl
) {
2488 scrollToShow(scroll_zdispl
, ht_panels
.get(d
));
2491 final Class c
= d
.getClass();
2492 if (Patch
.class == c
&& scroll
== scroll_patches
) {
2493 scrollToShow(scroll_patches
, ht_panels
.get(d
));
2494 } else if (DLabel
.class == c
&& scroll
== scroll_labels
) {
2495 scrollToShow(scroll_labels
, ht_panels
.get(d
));
2496 } else if (Profile
.class == c
&& scroll
== scroll_profiles
) {
2497 scrollToShow(scroll_profiles
, ht_panels
.get(d
));
2502 private void scrollToShow(final JScrollPane scroll
, final JPanel dp
) {
2503 if (null == dp
) return;
2504 JViewport view
= scroll
.getViewport();
2505 Point current
= view
.getViewPosition();
2506 Dimension extent
= view
.getExtentSize();
2507 int panel_y
= dp
.getY();
2508 if ((panel_y
+ DisplayablePanel
.HEIGHT
- current
.y
) <= extent
.height
&& panel_y
>= current
.y
) {
2509 // it's completely visible already
2512 // scroll just enough
2513 // if it's above, show at the top
2514 if (panel_y
- current
.y
< 0) {
2515 view
.setViewPosition(new Point(0, panel_y
));
2517 // if it's below (even if partially), show at the bottom
2518 else if (panel_y
+ 50 > current
.y
+ extent
.height
) {
2519 view
.setViewPosition(new Point(0, panel_y
- extent
.height
+ 50));
2520 //Utils.log("Display.scrollToShow: panel_y: " + panel_y + " current.y: " + current.y + " extent.height: " + extent.height);
2525 /** Update the title of the given Displayable in its DisplayablePanel, if any. */
2526 static public void updateTitle(final Layer layer
, final Displayable displ
) {
2527 for (final Display d
: al_displays
) {
2528 if (layer
== d
.layer
) {
2529 DisplayablePanel dp
= d
.ht_panels
.get(displ
);
2530 if (null != dp
) dp
.updateTitle();
2535 /** Update the Display's title in all Displays showing the given Layer. */
2536 static public void updateTitle(final Layer layer
) {
2537 for (final Display d
: al_displays
) {
2538 if (d
.layer
== layer
) {
2539 d
.updateFrameTitle();
2543 /** Update the Display's title in all Displays showing a Layer of the given LayerSet. */
2544 static public void updateTitle(final LayerSet ls
) {
2545 for (final Display d
: al_displays
) {
2546 if (d
.layer
.getParent() == ls
) {
2547 d
.updateFrameTitle();
2552 /** Set a new title in the JFrame, showing info on the layer 'z' and the magnification. */
2553 public void updateFrameTitle() {
2554 updateFrameTitle(layer
);
2556 private void updateFrameTitle(Layer layer
) {
2557 // From ij.ImagePlus class, the solution:
2559 final double magnification
= canvas
.getMagnification();
2560 if (magnification
!=1.0) {
2561 final double percent
= magnification
*100.0;
2562 scale
= new StringBuilder(" (").append(Utils
.d2s(percent
, percent
==(int)percent ?
0 : 1)).append("%)").toString();
2564 final Calibration cal
= layer
.getParent().getCalibration();
2565 String title
= new StringBuilder().append(layer
.getParent().indexOf(layer
) + 1).append('/').append(layer
.getParent().size()).append(' ').append((null == layer
.getTitle() ?
"" : layer
.getTitle())).append(scale
).append(" -- ").append(getProject().toString()).append(' ').append(' ').append(Utils
.cutNumber(layer
.getParent().getLayerWidth() * cal
.pixelWidth
, 2, true)).append('x').append(Utils
.cutNumber(layer
.getParent().getLayerHeight() * cal
.pixelHeight
, 2, true)).append(' ').append(cal
.getUnit()).toString();
2566 frame
.setTitle(title
);
2567 // fix the title for the FakeImageWindow and thus the WindowManager listing in the menus
2568 canvas
.getFakeImagePlus().setTitle(title
);
2571 /** If shift is down, scroll to the next non-empty layer; otherwise, if scroll_step is larger than 1, then scroll 'scroll_step' layers ahead; else just the next Layer. */
2572 public void nextLayer(final int modifiers
) {
2574 if (0 == (modifiers ^ Event
.SHIFT_MASK
)) {
2575 l
= layer
.getParent().nextNonEmpty(layer
);
2576 } else if (scroll_step
> 1) {
2577 int i
= layer
.getParent().indexOf(this.layer
);
2578 Layer la
= layer
.getParent().getLayer(i
+ scroll_step
);
2579 if (null != la
) l
= la
;
2582 l
= layer
.getParent().next(layer
);
2586 updateInDatabase("layer_id");
2590 private final void translateLayerColors(final Layer current
, final Layer other
) {
2591 if (current
== other
) return;
2592 if (layer_channels
.size() > 0) {
2593 final LayerSet ls
= getLayerSet();
2594 // translate colors by distance from current layer to new Layer l
2595 final int dist
= ls
.indexOf(other
) - ls
.indexOf(current
);
2596 translateLayerColor(Color
.red
, dist
);
2597 translateLayerColor(Color
.blue
, dist
);
2601 private final void translateLayerColor(final Color color
, final int dist
) {
2602 final LayerSet ls
= getLayerSet();
2603 final Layer l
= layer_channels
.get(color
);
2604 if (null == l
) return;
2605 updateColor(Color
.white
, layer_panels
.get(l
));
2606 final Layer l2
= ls
.getLayer(ls
.indexOf(l
) + dist
);
2607 if (null != l2
) updateColor(color
, layer_panels
.get(l2
));
2610 private final void updateColor(final Color color
, final LayerPanel lp
) {
2612 setColorChannel(lp
.layer
, color
);
2615 /** Calls setLayer(la) on the SetLayerThread. */
2616 public void toLayer(final Layer la
) {
2617 if (la
.getParent() != layer
.getParent()) return; // not of the same LayerSet
2618 if (la
== layer
) return; // nothing to do
2620 updateInDatabase("layer_id");
2623 /** If shift is down, scroll to the previous non-empty layer; otherwise, if scroll_step is larger than 1, then scroll 'scroll_step' layers backward; else just the previous Layer. */
2624 public void previousLayer(final int modifiers
) {
2626 if (0 == (modifiers ^ Event
.SHIFT_MASK
)) {
2627 l
= layer
.getParent().previousNonEmpty(layer
);
2628 } else if (scroll_step
> 1) {
2629 int i
= layer
.getParent().indexOf(this.layer
);
2630 Layer la
= layer
.getParent().getLayer(i
- scroll_step
);
2631 if (null != la
) l
= la
;
2634 l
= layer
.getParent().previous(layer
);
2638 updateInDatabase("layer_id");
2642 static public void updateLayerScroller(LayerSet set
) {
2643 for (final Display d
: al_displays
) {
2644 if (d
.layer
.getParent() == set
) {
2645 d
.updateLayerScroller(d
.layer
);
2650 private void updateLayerScroller(Layer layer
) {
2651 int size
= layer
.getParent().size();
2653 scroller
.setValues(0, 1, 0, 0);
2654 scroller
.setEnabled(false);
2656 scroller
.setEnabled(true);
2657 scroller
.setValues(layer
.getParent().getLayerIndex(layer
.getId()), 1, 0, size
);
2659 recreateLayerPanels(layer
);
2662 // Can't use this.layer, may still be null. User argument instead.
2663 private synchronized void recreateLayerPanels(final Layer layer
) {
2664 synchronized (layer_channels
) {
2665 panel_layers
.removeAll();
2667 if (0 == layer_panels
.size()) {
2668 for (final Layer la
: layer
.getParent().getLayers()) {
2669 final LayerPanel lp
= new LayerPanel(this, la
);
2670 layer_panels
.put(la
, lp
);
2671 this.panel_layers
.add(lp
);
2674 // Set theory at work: keep old to reuse
2675 layer_panels
.keySet().retainAll(layer
.getParent().getLayers());
2676 for (final Layer la
: layer
.getParent().getLayers()) {
2677 LayerPanel lp
= layer_panels
.get(la
);
2679 lp
= new LayerPanel(this, la
);
2680 layer_panels
.put(la
, lp
);
2682 this.panel_layers
.add(lp
);
2684 for (final Iterator
<Map
.Entry
<Integer
,LayerPanel
>> it
= layer_alpha
.entrySet().iterator(); it
.hasNext(); ) {
2685 final Map
.Entry
<Integer
,LayerPanel
> e
= it
.next();
2686 if (-1 == getLayerSet().indexOf(e
.getValue().layer
)) it
.remove();
2688 for (final Iterator
<Map
.Entry
<Color
,Layer
>> it
= layer_channels
.entrySet().iterator(); it
.hasNext(); ) {
2689 final Map
.Entry
<Color
,Layer
> e
= it
.next();
2690 if (-1 == getLayerSet().indexOf(e
.getValue())) it
.remove();
2692 scroll_layers
.repaint();
2697 private void updateSnapshots() {
2698 Enumeration
<DisplayablePanel
> e
= ht_panels
.elements();
2699 while (e
.hasMoreElements()) {
2700 e
.nextElement().remake();
2702 Utils
.updateComponent(tabs
.getSelectedComponent());
2705 static public void updatePanel(Layer layer
, final Displayable displ
) {
2706 if (null == layer
&& null != front
) layer
= front
.layer
; // the front layer
2707 for (final Display d
: al_displays
) {
2708 if (d
.layer
== layer
) {
2709 d
.updatePanel(displ
);
2714 private void updatePanel(Displayable d
) {
2716 if (d
instanceof Profile
) {
2718 } else if (d
instanceof Patch
) {
2720 } else if (d
instanceof DLabel
) {
2722 } else if (d
instanceof Pipe
) {
2725 if (null == c
) return;
2726 DisplayablePanel dp
= ht_panels
.get(d
);
2728 Utils
.updateComponent(c
);
2731 static public void updatePanelIndex(final Layer layer
, final Displayable displ
) {
2732 for (final Display d
: al_displays
) {
2733 if (d
.layer
== layer
|| displ
instanceof ZDisplayable
) {
2734 d
.updatePanelIndex(displ
);
2739 private void updatePanelIndex(final Displayable d
) {
2740 // find first of the kind, then remove and insert its panel
2743 if (d
instanceof ZDisplayable
) {
2744 i
= layer
.getParent().indexOf((ZDisplayable
)d
);
2747 i
= layer
.relativeIndexOf(d
);
2748 if (d
instanceof Profile
) {
2750 } else if (d
instanceof Patch
) {
2752 } else if (d
instanceof DLabel
) {
2756 if (null == c
) return;
2757 DisplayablePanel dp
= ht_panels
.get(d
);
2758 if (null == dp
) return; // may be half-baked, wait
2760 c
.add(dp
, i
); // java and its fabulous consistency
2761 // not enough! Utils.updateComponent(c);
2765 Utils
.updateComponent(c
);
2768 /** Repair possibly missing panels and other components by simply resetting the same Layer */
2769 public void repairGUI() {
2770 Layer layer
= this.layer
;
2775 public void actionPerformed(final ActionEvent ae
) {
2776 dispatcher
.exec(new Runnable() { public void run() {
2778 String command
= ae
.getActionCommand();
2779 if (command
.startsWith("Job")) {
2780 if (Utils
.checkYN("Really cancel job?")) {
2781 project
.getLoader().quitJob(command
);
2785 } else if (command
.equals("Move to top")) {
2786 if (null == active
) return;
2787 canvas
.setUpdateGraphics(true);
2788 layer
.getParent().move(LayerSet
.TOP
, active
);
2789 Display
.repaint(layer
.getParent(), active
, 5);
2790 //Display.updatePanelIndex(layer, active);
2791 } else if (command
.equals("Move up")) {
2792 if (null == active
) return;
2793 canvas
.setUpdateGraphics(true);
2794 layer
.getParent().move(LayerSet
.UP
, active
);
2795 Display
.repaint(layer
.getParent(), active
, 5);
2796 //Display.updatePanelIndex(layer, active);
2797 } else if (command
.equals("Move down")) {
2798 if (null == active
) return;
2799 canvas
.setUpdateGraphics(true);
2800 layer
.getParent().move(LayerSet
.DOWN
, active
);
2801 Display
.repaint(layer
.getParent(), active
, 5);
2802 //Display.updatePanelIndex(layer, active);
2803 } else if (command
.equals("Move to bottom")) {
2804 if (null == active
) return;
2805 canvas
.setUpdateGraphics(true);
2806 layer
.getParent().move(LayerSet
.BOTTOM
, active
);
2807 Display
.repaint(layer
.getParent(), active
, 5);
2808 //Display.updatePanelIndex(layer, active);
2809 } else if (command
.equals("Duplicate, link and send to next layer")) {
2810 duplicateLinkAndSendTo(active
, 1, layer
.getParent().next(layer
));
2811 } else if (command
.equals("Duplicate, link and send to previous layer")) {
2812 duplicateLinkAndSendTo(active
, 0, layer
.getParent().previous(layer
));
2813 } else if (command
.equals("Duplicate, link and send to...")) {
2814 // fix non-scrolling popup menu
2815 GenericDialog gd
= new GenericDialog("Send to");
2816 gd
.addMessage("Duplicate, link and send to...");
2817 String
[] sl
= new String
[layer
.getParent().size()];
2819 for (Iterator it
= layer
.getParent().getLayers().iterator(); it
.hasNext(); ) {
2820 sl
[next
++] = project
.findLayerThing(it
.next()).toString();
2822 gd
.addChoice("Layer: ", sl
, sl
[layer
.getParent().indexOf(layer
)]);
2824 if (gd
.wasCanceled()) return;
2825 Layer la
= layer
.getParent().getLayer(gd
.getNextChoiceIndex());
2827 Utils
.showMessage("Can't duplicate, link and send to the same layer.");
2830 duplicateLinkAndSendTo(active
, 0, la
);
2831 } else if (-1 != command
.indexOf("z = ")) {
2832 // this is an item from the "Duplicate, link and send to" menu of layer z's
2833 Layer target_layer
= layer
.getParent().getLayer(Double
.parseDouble(command
.substring(command
.lastIndexOf(' ') +1)));
2834 Utils
.log2("layer: __" +command
.substring(command
.lastIndexOf(' ') +1) + "__");
2835 if (null == target_layer
) return;
2836 duplicateLinkAndSendTo(active
, 0, target_layer
);
2837 } else if (-1 != command
.indexOf("z=")) {
2838 // WARNING the indexOf is very similar to the previous one
2839 // Send the linked group to the selected layer
2840 int iz
= command
.indexOf("z=")+2;
2841 Utils
.log2("iz=" + iz
+ " other: " + command
.indexOf(' ', iz
+2));
2842 int end
= command
.indexOf(' ', iz
);
2843 if (-1 == end
) end
= command
.length();
2844 double lz
= Double
.parseDouble(command
.substring(iz
, end
));
2845 Layer target
= layer
.getParent().getLayer(lz
);
2846 HashSet hs
= active
.getLinkedGroup(new HashSet());
2847 layer
.getParent().move(hs
, active
.getLayer(), target
);
2848 } else if (command
.equals("Unlink")) {
2849 if (null == active
|| active
instanceof Patch
) return;
2851 updateSelection();//selection.update();
2852 } else if (command
.equals("Unlink from images")) {
2853 if (null == active
) return;
2855 for (Displayable displ
: selection
.getSelected()) {
2856 displ
.unlinkAll(Patch
.class);
2858 updateSelection();//selection.update();
2859 } catch (Exception e
) { IJError
.print(e
); }
2860 } else if (command
.equals("Unlink slices")) {
2861 YesNoCancelDialog yn
= new YesNoCancelDialog(frame
, "Attention", "Really unlink all slices from each other?\nThere is no undo.");
2862 if (!yn
.yesPressed()) return;
2863 final ArrayList
<Patch
> pa
= ((Patch
)active
).getStackPatches();
2864 for (int i
=pa
.size()-1; i
>0; i
--) {
2865 pa
.get(i
).unlink(pa
.get(i
-1));
2867 } else if (command
.equals("Send to next layer")) {
2868 Rectangle box
= selection
.getBox();
2870 // unlink Patch instances
2871 for (final Displayable displ
: selection
.getSelected()) {
2872 displ
.unlinkAll(Patch
.class);
2874 updateSelection();//selection.update();
2875 } catch (Exception e
) { IJError
.print(e
); }
2876 //layer.getParent().moveDown(layer, active); // will repaint whatever appropriate layers
2877 selection
.moveDown();
2878 repaint(layer
.getParent(), box
);
2879 } else if (command
.equals("Send to previous layer")) {
2880 Rectangle box
= selection
.getBox();
2882 // unlink Patch instances
2883 for (final Displayable displ
: selection
.getSelected()) {
2884 displ
.unlinkAll(Patch
.class);
2886 updateSelection();//selection.update();
2887 } catch (Exception e
) { IJError
.print(e
); }
2888 //layer.getParent().moveUp(layer, active); // will repaint whatever appropriate layers
2890 repaint(layer
.getParent(), box
);
2891 } else if (command
.equals("Show centered")) {
2892 if (active
== null) return;
2893 showCentered(active
);
2894 } else if (command
.equals("Delete...")) {
2896 if (null != active) {
2897 Displayable d = active;
2898 selection.remove(d);
2899 d.remove(true); // will repaint
2902 // remove all selected objects
2903 selection
.deleteAll();
2904 } else if (command
.equals("Color...")) {
2905 IJ
.doCommand("Color Picker...");
2906 } else if (command
.equals("Revert")) {
2907 if (null == active
|| active
.getClass() != Patch
.class) return;
2908 Patch p
= (Patch
)active
;
2910 if (null == p
.getOriginalPath()) Utils
.log("No editions to save for patch " + p
.getTitle() + " #" + p
.getId());
2911 else Utils
.log("Could not revert Patch " + p
.getTitle() + " #" + p
.getId());
2913 } else if (command
.equals("Remove alpha mask")) {
2914 final ArrayList
<Displayable
> patches
= selection
.getSelected(Patch
.class);
2915 if (patches
.size() > 0) {
2916 Bureaucrat
.createAndStart(new Worker
.Task("Removing alpha mask" + (patches
.size() > 1 ?
"s" : "")) { public void exec() {
2917 final ArrayList
<Future
> jobs
= new ArrayList
<Future
>();
2918 for (final Displayable d
: patches
) {
2919 final Patch p
= (Patch
) d
;
2920 p
.setAlphaMask(null);
2921 Future job
= p
.getProject().getLoader().regenerateMipMaps(p
); // submit to queue
2922 if (null != job
) jobs
.add(job
);
2925 for (final Future job
: jobs
) try {
2927 } catch (Exception ie
) {}
2928 }}, patches
.get(0).getProject());
2930 } else if (command
.equals("Undo")) {
2931 Bureaucrat
.createAndStart(new Worker
.Task("Undo") { public void exec() {
2932 layer
.getParent().undoOneStep();
2933 Display
.repaint(layer
.getParent());
2935 } else if (command
.equals("Redo")) {
2936 Bureaucrat
.createAndStart(new Worker
.Task("Redo") { public void exec() {
2937 layer
.getParent().redoOneStep();
2938 Display
.repaint(layer
.getParent());
2940 } else if (command
.equals("Transform")) {
2941 if (null == active
) return;
2942 canvas
.setTransforming(true);
2943 } else if (command
.equals("Apply transform")) {
2944 if (null == active
) return;
2945 canvas
.setTransforming(false);
2946 } else if (command
.equals("Apply transform propagating to last layer")) {
2947 if (selection
.isTransforming()) {
2948 final java
.util
.List
<Layer
> layers
= layer
.getParent().getLayers();
2949 selection
.applyAndPropagate(new HashSet
<Layer
>(layers
.subList(layers
.indexOf(Display
.this.layer
)+1, layers
.size()))); // +1 to exclude current layer
2951 } else if (command
.equals("Apply transform propagating to first layer")) {
2952 if (selection
.isTransforming()) {
2953 final java
.util
.List
<Layer
> layers
= layer
.getParent().getLayers();
2954 selection
.applyAndPropagate(new HashSet
<Layer
>(layers
.subList(0, layers
.indexOf(Display
.this.layer
))));
2956 } else if (command
.equals("Cancel transform")) {
2957 if (null == active
) return;
2958 canvas
.cancelTransform();
2959 } else if (command
.equals("Specify transform...")) {
2960 if (null == active
) return;
2961 selection
.specify();
2962 } else if (command
.equals("Hide all but images")) {
2963 ArrayList
<Class
> type
= new ArrayList
<Class
>();
2964 type
.add(Patch
.class);
2965 selection
.removeAll(layer
.getParent().hideExcept(type
, false));
2966 Display
.update(layer
.getParent(), false);
2967 } else if (command
.equals("Unhide all")) {
2968 layer
.getParent().setAllVisible(false);
2969 Display
.update(layer
.getParent(), false);
2970 } else if (command
.startsWith("Hide all ")) {
2971 String type
= command
.substring(9, command
.length() -1); // skip the ending plural 's'
2972 type
= type
.substring(0, 1).toUpperCase() + type
.substring(1);
2973 selection
.removeAll(layer
.getParent().setVisible(type
, false, true));
2974 } else if (command
.startsWith("Unhide all ")) {
2975 String type
= command
.substring(11, command
.length() -1); // skip the ending plural 's'
2976 type
= type
.substring(0, 1).toUpperCase() + type
.substring(1);
2977 layer
.getParent().setVisible(type
, true, true);
2978 } else if (command
.equals("Hide deselected")) {
2979 hideDeselected(0 != (ActionEvent
.ALT_MASK
& ae
.getModifiers()));
2980 } else if (command
.equals("Hide deselected except images")) {
2981 hideDeselected(true);
2982 } else if (command
.equals("Hide selected")) {
2983 selection
.setVisible(false); // TODO should deselect them too? I don't think so.
2984 } else if (command
.equals("Resize canvas/LayerSet...")) {
2986 } else if (command
.equals("Autoresize canvas/LayerSet")) {
2987 layer
.getParent().setMinimumDimensions();
2988 } else if (command
.equals("Import image")) {
2990 } else if (command
.equals("Import next image")) {
2992 } else if (command
.equals("Import stack...")) {
2993 Display
.this.getLayerSet().addLayerContentStep(layer
);
2994 Rectangle sr
= getCanvas().getSrcRect();
2995 Bureaucrat burro
= project
.getLoader().importStack(layer
, sr
.x
+ sr
.width
/2, sr
.y
+ sr
.height
/2, null, true, null);
2996 burro
.addPostTask(new Runnable() { public void run() {
2997 Display
.this.getLayerSet().addLayerContentStep(layer
);
2999 } else if (command
.equals("Import grid...")) {
3000 Display
.this.getLayerSet().addLayerContentStep(layer
);
3001 Bureaucrat burro
= project
.getLoader().importGrid(layer
);
3002 burro
.addPostTask(new Runnable() { public void run() {
3003 Display
.this.getLayerSet().addLayerContentStep(layer
);
3005 } else if (command
.equals("Import sequence as grid...")) {
3006 Display
.this.getLayerSet().addLayerContentStep(layer
);
3007 Bureaucrat burro
= project
.getLoader().importSequenceAsGrid(layer
);
3008 burro
.addPostTask(new Runnable() { public void run() {
3009 Display
.this.getLayerSet().addLayerContentStep(layer
);
3011 } else if (command
.equals("Import from text file...")) {
3012 Display
.this.getLayerSet().addLayerContentStep(layer
);
3013 Bureaucrat burro
= project
.getLoader().importImages(layer
);
3014 burro
.addPostTask(new Runnable() { public void run() {
3015 Display
.this.getLayerSet().addLayerContentStep(layer
);
3017 } else if (command
.equals("Import labels as arealists...")) {
3018 Display
.this.getLayerSet().addChangeTreesStep();
3019 Bureaucrat burro
= project
.getLoader().importLabelsAsAreaLists(layer
, null, Double
.MAX_VALUE
, 0, 0.4f
, false);
3020 burro
.addPostTask(new Runnable() { public void run() {
3021 Display
.this.getLayerSet().addChangeTreesStep();
3023 } else if (command
.equals("Make flat image...")) {
3024 // if there's a ROI, just use that as cropping rectangle
3025 Rectangle srcRect
= null;
3026 Roi roi
= canvas
.getFakeImagePlus().getRoi();
3028 srcRect
= roi
.getBounds();
3030 // otherwise, whatever is visible
3031 //srcRect = canvas.getSrcRect();
3032 // The above is confusing. That is what ROIs are for. So paint all:
3033 srcRect
= new Rectangle(0, 0, (int)Math
.ceil(layer
.getParent().getLayerWidth()), (int)Math
.ceil(layer
.getParent().getLayerHeight()));
3036 final String
[] types
= new String
[]{"8-bit grayscale", "RGB Color"};
3037 int the_type
= ImagePlus
.GRAY8
;
3038 final GenericDialog gd
= new GenericDialog("Choose", frame
);
3039 gd
.addSlider("Scale: ", 1, 100, 100);
3040 gd
.addChoice("Type: ", types
, types
[0]);
3041 if (layer
.getParent().size() > 1) {
3043 String[] layers = new String[layer.getParent().size()];
3045 for (Iterator it = layer.getParent().getLayers().iterator(); it.hasNext(); ) {
3046 layers[i] = layer.getProject().findLayerThing((Layer)it.next()).toString();
3049 int i_layer = layer.getParent().indexOf(layer);
3050 gd.addChoice("Start: ", layers, layers[i_layer]);
3051 gd.addChoice("End: ", layers, layers[i_layer]);
3053 Utils
.addLayerRangeChoices(Display
.this.layer
, gd
); /// $#%! where are my lisp macros
3054 gd
.addCheckbox("Include non-empty layers only", true);
3056 gd
.addMessage("Background color:");
3057 Utils
.addRGBColorSliders(gd
, Color
.black
);
3058 gd
.addCheckbox("Best quality", false);
3060 gd
.addCheckbox("Save to file", false);
3061 gd
.addCheckbox("Save for web", false);
3063 if (gd
.wasCanceled()) return;
3064 scale
= gd
.getNextNumber() / 100;
3065 the_type
= (0 == gd
.getNextChoiceIndex() ? ImagePlus
.GRAY8
: ImagePlus
.COLOR_RGB
);
3066 if (Double
.isNaN(scale
) || scale
<= 0.0) {
3067 Utils
.showMessage("Invalid scale.");
3070 Layer
[] layer_array
= null;
3071 boolean non_empty_only
= false;
3072 if (layer
.getParent().size() > 1) {
3073 non_empty_only
= gd
.getNextBoolean();
3074 int i_start
= gd
.getNextChoiceIndex();
3075 int i_end
= gd
.getNextChoiceIndex();
3076 ArrayList al
= new ArrayList();
3077 ArrayList al_zd
= layer
.getParent().getZDisplayables();
3078 ZDisplayable
[] zd
= new ZDisplayable
[al_zd
.size()];
3080 for (int i
=i_start
, j
=0; i
<= i_end
; i
++, j
++) {
3081 Layer la
= layer
.getParent().getLayer(i
);
3082 if (!la
.isEmpty() || !non_empty_only
) al
.add(la
); // checks both the Layer and the ZDisplayable objects in the parent LayerSet
3084 if (0 == al
.size()) {
3085 Utils
.showMessage("All layers are empty!");
3088 layer_array
= new Layer
[al
.size()];
3089 al
.toArray(layer_array
);
3091 layer_array
= new Layer
[]{Display
.this.layer
};
3093 final Color background
= new Color((int)gd
.getNextNumber(), (int)gd
.getNextNumber(), (int)gd
.getNextNumber());
3094 final boolean quality
= gd
.getNextBoolean();
3095 final boolean save_to_file
= gd
.getNextBoolean();
3096 final boolean save_for_web
= gd
.getNextBoolean();
3097 // in its own thread
3098 if (save_for_web
) project
.getLoader().makePrescaledTiles(layer_array
, Patch
.class, srcRect
, scale
, c_alphas
, the_type
);
3099 else project
.getLoader().makeFlatImage(layer_array
, srcRect
, scale
, c_alphas
, the_type
, save_to_file
, quality
, background
);
3101 } else if (command
.equals("Lock")) {
3102 selection
.setLocked(true);
3103 } else if (command
.equals("Unlock")) {
3104 selection
.setLocked(false);
3105 } else if (command
.equals("Properties...")) {
3106 active
.adjustProperties();
3108 } else if (command
.equals("Cancel alignment")) {
3109 layer
.getParent().cancelAlign();
3110 } else if (command
.equals("Align with landmarks")) {
3111 layer
.getParent().applyAlign(false);
3112 } else if (command
.equals("Align and register")) {
3113 layer
.getParent().applyAlign(true);
3114 } else if (command
.equals("Align using profiles")) {
3115 if (!selection
.contains(Profile
.class)) {
3116 Utils
.showMessage("No profiles are selected.");
3119 // ask for range of layers
3120 final GenericDialog gd
= new GenericDialog("Choose range");
3121 Utils
.addLayerRangeChoices(Display
.this.layer
, gd
);
3123 if (gd
.wasCanceled()) return;
3124 Layer la_start
= layer
.getParent().getLayer(gd
.getNextChoiceIndex());
3125 Layer la_end
= layer
.getParent().getLayer(gd
.getNextChoiceIndex());
3126 if (la_start
== la_end
) {
3127 Utils
.showMessage("Need at least two layers.");
3130 if (selection
.isLocked()) {
3131 Utils
.showMessage("There are locked objects.");
3134 layer
.getParent().startAlign(Display
.this);
3135 layer
.getParent().applyAlign(la_start
, la_end
, selection
);
3136 } else if (command
.equals("Align stack slices")) {
3137 if (getActive() instanceof Patch
) {
3138 final Patch slice
= (Patch
)getActive();
3139 if (slice
.isStack()) {
3140 // check linked group
3141 final HashSet hs
= slice
.getLinkedGroup(new HashSet());
3142 for (Iterator it
= hs
.iterator(); it
.hasNext(); ) {
3143 if (it
.next().getClass() != Patch
.class) {
3144 Utils
.showMessage("Images are linked to other objects, can't proceed to cross-correlate them."); // labels should be fine, need to check that
3148 final LayerSet ls
= slice
.getLayerSet();
3149 final HashSet
<Displayable
> linked
= slice
.getLinkedGroup(null);
3150 ls
.addTransformStep(linked
);
3151 Bureaucrat burro
= Registration
.registerStackSlices((Patch
)getActive()); // will repaint
3152 burro
.addPostTask(new Runnable() { public void run() {
3153 // The current state when done
3154 ls
.addTransformStep(linked
);
3157 Utils
.log("Align stack slices: selected image is not part of a stack.");
3160 } else if (command
.equals("Align layers")) {
3161 final Layer la
= layer
;; // caching, since scroll wheel may change it
3162 la
.getParent().addTransformStep(la
);
3163 Bureaucrat burro
= AlignTask
.alignLayersLinearlyTask( la
);
3164 burro
.addPostTask(new Runnable() { public void run() {
3165 la
.getParent().addTransformStep(la
);
3167 } else if (command
.equals("Align multi-layer mosaic")) {
3168 final Layer la
= layer
; // caching, since scroll wheel may change it
3169 la
.getParent().addTransformStep();
3170 Bureaucrat burro
= AlignTask
.alignMultiLayerMosaicTask( la
);
3171 burro
.addPostTask(new Runnable() { public void run() {
3172 la
.getParent().addTransformStep();
3174 } else if (command
.equals("Properties ...")) { // NOTE the space before the dots, to distinguish from the "Properties..." command that works on Displayable objects.
3175 GenericDialog gd
= new GenericDialog("Properties", Display
.this.frame
);
3176 //gd.addNumericField("layer_scroll_step: ", this.scroll_step, 0);
3177 gd
.addSlider("layer_scroll_step: ", 1, layer
.getParent().size(), Display
.this.scroll_step
);
3178 gd
.addChoice("snapshots_mode", LayerSet
.snapshot_modes
, LayerSet
.snapshot_modes
[layer
.getParent().getSnapshotsMode()]);
3179 gd
.addCheckbox("prefer_snapshots_quality", layer
.getParent().snapshotsQuality());
3180 Loader lo
= getProject().getLoader();
3181 boolean using_mipmaps
= lo
.isMipMapsEnabled();
3182 gd
.addCheckbox("enable_mipmaps", using_mipmaps
);
3183 String preprocessor
= project
.getLoader().getPreprocessor();
3184 gd
.addStringField("image_preprocessor: ", null == preprocessor ?
"" : preprocessor
);
3185 gd
.addCheckbox("enable_layer_pixels virtualization", layer
.getParent().isPixelsVirtualizationEnabled());
3186 double max
= layer
.getParent().getLayerWidth() < layer
.getParent().getLayerHeight() ? layer
.getParent().getLayerWidth() : layer
.getParent().getLayerHeight();
3187 gd
.addSlider("max_dimension of virtualized layer pixels: ", 0, max
, layer
.getParent().getPixelsMaxDimension());
3190 if (gd
.wasCanceled()) return;
3192 int sc
= (int) gd
.getNextNumber();
3194 Display
.this.scroll_step
= sc
;
3195 updateInDatabase("scroll_step");
3197 layer
.getParent().setSnapshotsMode(gd
.getNextChoiceIndex());
3198 layer
.getParent().setSnapshotsQuality(gd
.getNextBoolean());
3200 boolean generate_mipmaps
= gd
.getNextBoolean();
3201 if (using_mipmaps
&& generate_mipmaps
) {
3204 if (using_mipmaps
) { // and !generate_mipmaps
3205 lo
.flushMipMaps(true);
3207 // not using mipmaps before, and true == generate_mipmaps
3208 lo
.generateMipMaps(layer
.getParent().getDisplayables(Patch
.class));
3212 final String prepro
= gd
.getNextString();
3213 if (!project
.getLoader().setPreprocessor(prepro
.trim())) {
3214 Utils
.showMessage("Could NOT set the preprocessor to " + prepro
);
3217 layer
.getParent().setPixelsVirtualizationEnabled(gd
.getNextBoolean());
3218 layer
.getParent().setPixelsMaxDimension((int)gd
.getNextNumber());
3219 } else if (command
.equals("Search...")) {
3221 } else if (command
.equals("Select all")) {
3222 selection
.selectAll();
3223 repaint(Display
.this.layer
, selection
.getBox(), 0);
3224 } else if (command
.equals("Select none")) {
3225 Rectangle box
= selection
.getBox();
3227 repaint(Display
.this.layer
, box
, 0);
3228 } else if (command
.equals("Restore selection")) {
3229 selection
.restore();
3230 } else if (command
.equals("Select under ROI")) {
3231 Roi roi
= canvas
.getFakeImagePlus().getRoi();
3232 if (null == roi
) return;
3233 selection
.selectAll(roi
, true);
3234 } else if (command
.equals("Merge")) {
3235 ArrayList al_sel
= selection
.getSelected();
3236 // put active at the beginning, to work as the base on which other's will get merged
3237 al_sel
.remove(Display
.this.active
);
3238 al_sel
.add(0, Display
.this.active
);
3239 AreaList ali
= AreaList
.merge(al_sel
);
3241 // remove all but the first from the selection
3242 for (int i
=1; i
<al_sel
.size(); i
++) {
3243 Object ob
= al_sel
.get(i
);
3244 if (ob
.getClass() == AreaList
.class) {
3245 selection
.remove((Displayable
)ob
);
3248 selection
.updateTransform(ali
);
3249 repaint(ali
.getLayerSet(), ali
, 0);
3251 } else if (command
.equals("Identify...")) {
3252 // for pipes only for now
3253 if (!(active
instanceof Pipe
)) return;
3254 ini
.trakem2
.vector
.Compare
.findSimilar((Pipe
)active
);
3255 } else if (command
.equals("Identify with axes...")) {
3256 if (!(active
instanceof Pipe
)) return;
3257 if (Project
.getProjects().size() < 2) {
3258 Utils
.showMessage("You need at least two projects open:\n-A reference project\n-The current project with the pipe to identify");
3261 ini
.trakem2
.vector
.Compare
.findSimilarWithAxes((Pipe
)active
);
3262 } else if (command
.equals("View orthoslices")) {
3263 if (!(active
instanceof Patch
)) return;
3264 Display3D
.showOrthoslices(((Patch
)active
));
3265 } else if (command
.equals("View volume")) {
3266 if (!(active
instanceof Patch
)) return;
3267 Display3D
.showVolume(((Patch
)active
));
3268 } else if (command
.equals("Show in 3D")) {
3269 for (Iterator it
= selection
.getSelected(ZDisplayable
.class).iterator(); it
.hasNext(); ) {
3270 ZDisplayable zd
= (ZDisplayable
)it
.next();
3271 Display3D
.show(zd
.getProject().findProjectThing(zd
));
3273 // handle profile lists ...
3274 HashSet hs
= new HashSet();
3275 for (Iterator it
= selection
.getSelected(Profile
.class).iterator(); it
.hasNext(); ) {
3276 Displayable d
= (Displayable
)it
.next();
3277 ProjectThing profile_list
= (ProjectThing
)d
.getProject().findProjectThing(d
).getParent();
3278 if (!hs
.contains(profile_list
)) {
3279 Display3D
.show(profile_list
);
3280 hs
.add(profile_list
);
3283 } else if (command
.equals("Snap")) {
3284 if (!(active
instanceof Patch
)) return;
3285 StitchingTEM
.snap(getActive(), Display
.this);
3286 } else if (command
.equals("Blend")) {
3287 HashSet
<Patch
> patches
= new HashSet
<Patch
>();
3288 for (final Displayable d
: selection
.getSelected()) {
3289 if (d
.getClass() == Patch
.class) patches
.add((Patch
)d
);
3291 if (patches
.size() > 1) {
3292 GenericDialog gd
= new GenericDialog("Blending");
3293 gd
.addCheckbox("Respect current alpha mask", true);
3295 if (gd
.wasCanceled()) return;
3296 Blending
.blend(patches
, gd
.getNextBoolean());
3298 IJ
.log("Please select more than one overlapping image.");
3300 } else if (command
.equals("Montage")) {
3301 if (!(active
instanceof Patch
)) {
3302 Utils
.showMessage("Please select only images.");
3305 final Set
<Displayable
> affected
= new HashSet
<Displayable
>(selection
.getAffected());
3306 for (final Displayable d
: affected
)
3308 Utils
.showMessage( "You cannot montage linked objects." );
3311 // make an undo step!
3312 final LayerSet ls
= layer
.getParent();
3313 ls
.addTransformStep(affected
);
3314 Bureaucrat burro
= AlignTask
.alignSelectionTask( selection
);
3315 burro
.addPostTask(new Runnable() { public void run() {
3316 ls
.addTransformStep(affected
);
3318 } else if (command
.equals("Lens correction")) {
3319 final Layer la
= layer
;
3320 la
.getParent().addDataEditStep(new HashSet
<Displayable
>(la
.getParent().getDisplayables()));
3321 Bureaucrat burro
= DistortionCorrectionTask
.correctDistortionFromSelection( selection
);
3322 burro
.addPostTask(new Runnable() { public void run() {
3323 // no means to know which where modified and from which layers!
3324 la
.getParent().addDataEditStep(new HashSet
<Displayable
>(la
.getParent().getDisplayables()));
3326 } else if (command
.equals("Link images...")) {
3327 GenericDialog gd
= new GenericDialog("Options");
3328 gd
.addMessage("Linking images to images (within their own layer only):");
3329 String
[] options
= {"all images to all images", "each image with any other overlapping image"};
3330 gd
.addChoice("Link: ", options
, options
[1]);
3331 String
[] options2
= {"selected images only", "all images in this layer", "all images in all layers"};
3332 gd
.addChoice("Apply to: ", options2
, options2
[0]);
3334 if (gd
.wasCanceled()) return;
3336 final HashSet
<Displayable
> ds
= new HashSet
<Displayable
>(lay
.getParent().getDisplayables());
3337 lay
.getParent().addDataEditStep(ds
);
3338 boolean overlapping_only
= 1 == gd
.getNextChoiceIndex();
3339 switch (gd
.getNextChoiceIndex()) {
3341 Patch
.crosslink(selection
.getSelected(Patch
.class), overlapping_only
);
3344 Patch
.crosslink(lay
.getDisplayables(Patch
.class), overlapping_only
);
3347 for (final Layer la
: lay
.getParent().getLayers()) {
3348 Patch
.crosslink(la
.getDisplayables(Patch
.class), overlapping_only
);
3352 lay
.getParent().addDataEditStep(ds
);
3353 } else if (command
.equals("Calibration...")) {
3355 IJ
.run(canvas
.getFakeImagePlus(), "Properties...", "");
3356 } catch (RuntimeException re
) {
3357 Utils
.log2("Calibration dialog canceled.");
3359 } else if (command
.equals("Enhance contrast (selected images)...")) {
3360 final Layer la
= layer
;
3361 final HashSet
<Displayable
> ds
= new HashSet
<Displayable
>(la
.getParent().getDisplayables());
3362 la
.getParent().addDataEditStep(ds
);
3363 ArrayList al
= selection
.getSelected(Patch
.class);
3364 Bureaucrat burro
= getProject().getLoader().homogenizeContrast(al
);
3365 burro
.addPostTask(new Runnable() { public void run() {
3366 la
.getParent().addDataEditStep(ds
);
3368 } else if (command
.equals("Enhance contrast layer-wise...")) {
3369 // ask for range of layers
3370 final GenericDialog gd
= new GenericDialog("Choose range");
3371 Utils
.addLayerRangeChoices(Display
.this.layer
, gd
);
3373 if (gd
.wasCanceled()) return;
3374 java
.util
.List list
= layer
.getParent().getLayers().subList(gd
.getNextChoiceIndex(), gd
.getNextChoiceIndex() +1); // exclusive end
3375 Layer
[] la
= new Layer
[list
.size()];
3377 final HashSet
<Displayable
> ds
= new HashSet
<Displayable
>();
3378 for (final Layer l
: la
) ds
.addAll(l
.getDisplayables(Patch
.class));
3379 getLayerSet().addDataEditStep(ds
);
3380 Bureaucrat burro
= project
.getLoader().homogenizeContrast(la
);
3381 burro
.addPostTask(new Runnable() { public void run() {
3382 getLayerSet().addDataEditStep(ds
);
3384 } else if (command
.equals("Set Min and Max layer-wise...")) {
3385 Displayable active
= getActive();
3388 if (null != active
&& active
.getClass() == Patch
.class) {
3389 min
= ((Patch
)active
).getMin();
3390 max
= ((Patch
)active
).getMax();
3392 final GenericDialog gd
= new GenericDialog("Min and Max");
3393 gd
.addMessage("Set min and max to all images in the layer range");
3394 Utils
.addLayerRangeChoices(Display
.this.layer
, gd
);
3395 gd
.addNumericField("min: ", min
, 2);
3396 gd
.addNumericField("max: ", max
, 2);
3398 if (gd
.wasCanceled()) return;
3400 min
= gd
.getNextNumber();
3401 max
= gd
.getNextNumber();
3402 ArrayList
<Displayable
> al
= new ArrayList
<Displayable
>();
3403 for (final Layer la
: layer
.getParent().getLayers().subList(gd
.getNextChoiceIndex(), gd
.getNextChoiceIndex() +1)) { // exclusive end
3404 al
.addAll(la
.getDisplayables(Patch
.class));
3406 final HashSet
<Displayable
> ds
= new HashSet
<Displayable
>(al
);
3407 getLayerSet().addDataEditStep(ds
);
3408 Bureaucrat burro
= project
.getLoader().setMinAndMax(al
, min
, max
);
3409 burro
.addPostTask(new Runnable() { public void run() {
3410 getLayerSet().addDataEditStep(ds
);
3412 } else if (command
.equals("Set Min and Max (selected images)...")) {
3413 Displayable active
= getActive();
3416 if (null != active
&& active
.getClass() == Patch
.class) {
3417 min
= ((Patch
)active
).getMin();
3418 max
= ((Patch
)active
).getMax();
3420 final GenericDialog gd
= new GenericDialog("Min and Max");
3421 gd
.addMessage("Set min and max to all selected images");
3422 gd
.addNumericField("min: ", min
, 2);
3423 gd
.addNumericField("max: ", max
, 2);
3425 if (gd
.wasCanceled()) return;
3427 min
= gd
.getNextNumber();
3428 max
= gd
.getNextNumber();
3429 final HashSet
<Displayable
> ds
= new HashSet
<Displayable
>(selection
.getSelected(Patch
.class));
3430 getLayerSet().addDataEditStep(ds
);
3431 Bureaucrat burro
= project
.getLoader().setMinAndMax(selection
.getSelected(Patch
.class), min
, max
);
3432 burro
.addPostTask(new Runnable() { public void run() {
3433 getLayerSet().addDataEditStep(ds
);
3435 } else if (command
.equals("Duplicate")) {
3436 // only Patch and DLabel, i.e. Layer-only resident objects that don't exist in the Project Tree
3437 final HashSet
<Class
> accepted
= new HashSet
<Class
>();
3438 accepted
.add(Patch
.class);
3439 accepted
.add(DLabel
.class);
3440 final ArrayList
<Displayable
> originals
= new ArrayList
<Displayable
>();
3441 final ArrayList
<Displayable
> selected
= selection
.getSelected();
3442 for (final Displayable d
: selected
) {
3443 if (accepted
.contains(d
.getClass())) {
3447 if (originals
.size() > 0) {
3448 getLayerSet().addChangeTreesStep();
3449 for (final Displayable d
: originals
) {
3450 d
.getLayer().add(d
.clone());
3452 getLayerSet().addChangeTreesStep();
3453 } else if (selected
.size() > 0) {
3454 Utils
.log("Can only duplicate images and text labels.\nDuplicate *other* objects in the Project Tree.\n");
3456 } else if (command
.equals("Create subproject")) {
3457 Roi roi
= canvas
.getFakeImagePlus().getRoi();
3458 if (null == roi
) return; // the menu item is not active unless there is a ROI
3460 if (1 == layer
.getParent().size()) {
3461 first
= last
= layer
;
3463 GenericDialog gd
= new GenericDialog("Choose layer range");
3464 Utils
.addLayerRangeChoices(layer
, gd
);
3466 if (gd
.wasCanceled()) return;
3467 first
= layer
.getParent().getLayer(gd
.getNextChoiceIndex());
3468 last
= layer
.getParent().getLayer(gd
.getNextChoiceIndex());
3469 Utils
.log2("first, last: " + first
+ ", " + last
);
3471 Project sub
= getProject().createSubproject(roi
.getBounds(), first
, last
);
3472 final LayerSet subls
= sub
.getRootLayerSet();
3473 final Display d
= new Display(sub
, subls
.getLayer(0));
3474 SwingUtilities
.invokeLater(new Runnable() { public void run() {
3475 d
.canvas
.showCentered(new Rectangle(0, 0, (int)subls
.getLayerWidth(), (int)subls
.getLayerHeight()));
3477 } else if (command
.startsWith("Arealists as labels")) {
3478 GenericDialog gd
= new GenericDialog("Export labels");
3479 gd
.addSlider("Scale: ", 1, 100, 100);
3480 final String
[] options
= {"All area list", "Selected area lists"};
3481 gd
.addChoice("Export: ", options
, options
[0]);
3482 Utils
.addLayerRangeChoices(layer
, gd
);
3483 gd
.addCheckbox("Visible only", true);
3485 if (gd
.wasCanceled()) return;
3486 final float scale
= (float)(gd
.getNextNumber() / 100);
3487 java
.util
.List al
= 0 == gd
.getNextChoiceIndex() ? layer
.getParent().getZDisplayables(AreaList
.class) : selection
.getSelected(AreaList
.class);
3489 Utils
.log("No area lists found to export.");
3492 // Generics are ... a pain? I don't understand them? They fail when they shouldn't? And so easy to workaround that they are a shame?
3493 al
= (java
.util
.List
<Displayable
>) al
;
3495 int first
= gd
.getNextChoiceIndex();
3496 int last
= gd
.getNextChoiceIndex();
3497 boolean visible_only
= gd
.getNextBoolean();
3498 if (-1 != command
.indexOf("(amira)")) {
3499 AreaList
.exportAsLabels(al
, canvas
.getFakeImagePlus().getRoi(), scale
, first
, last
, visible_only
, true, true);
3500 } else if (-1 != command
.indexOf("(tif)")) {
3501 AreaList
.exportAsLabels(al
, canvas
.getFakeImagePlus().getRoi(), scale
, first
, last
, visible_only
, false, false);
3503 } else if (command
.equals("Project properties...")) {
3504 project
.adjustProperties();
3505 } else if (command
.equals("Release memory...")) {
3506 Bureaucrat
.createAndStart(new Worker("Releasing memory") {
3510 GenericDialog gd
= new GenericDialog("Release Memory");
3511 int max
= (int)(IJ
.maxMemory() / 1000000);
3512 gd
.addSlider("Megabytes: ", 0, max
, max
/2);
3514 if (!gd
.wasCanceled()) {
3515 int n_mb
= (int)gd
.getNextNumber();
3516 project
.getLoader().releaseToFit((long)n_mb
*1000000);
3518 } catch (Throwable e
) {
3525 } else if (command
.equals("Flush image cache")) {
3526 Loader
.releaseAllCaches();
3527 } else if (command
.equals("Regenerate all mipmaps")) {
3528 for (final Displayable d
: getLayerSet().getDisplayables(Patch
.class)) {
3529 d
.getProject().getLoader().regenerateMipMaps((Patch
) d
);
3532 Utils
.log2("Display: don't know what to do with command " + command
);
3537 /** Update in all displays the Transform for the given Displayable if it's selected. */
3538 static public void updateTransform(final Displayable displ
) {
3539 for (final Display d
: al_displays
) {
3540 if (d
.selection
.contains(displ
)) d
.selection
.updateTransform(displ
);
3544 /** Order the profiles of the parent profile_list by Z order, and fix the ProjectTree.*/
3546 private void fixZOrdering(Profile profile) {
3547 ProjectThing thing = project.findProjectThing(profile);
3548 if (null == thing) {
3549 Utils.log2("Display.fixZOrdering: null thing?");
3552 ((ProjectThing)thing.getParent()).fixZOrdering();
3553 project.getProjectTree().updateList(thing.getParent());
3557 /** The number of layers to scroll through with the wheel; 1 by default.*/
3558 public int getScrollStep() { return this.scroll_step
; }
3560 public void setScrollStep(int scroll_step
) {
3561 if (scroll_step
< 1) scroll_step
= 1;
3562 this.scroll_step
= scroll_step
;
3563 updateInDatabase("scroll_step");
3566 protected Bureaucrat
importImage() {
3567 Worker worker
= new Worker("Import image") { /// all this verbosity is what happens when functions are not first class citizens. I could abstract it away by passing a string name "importImage" and invoking it with reflection, but that is an even bigger PAIN
3573 Rectangle srcRect
= canvas
.getSrcRect();
3574 int x
= srcRect
.x
+ srcRect
.width
/ 2;
3575 int y
= srcRect
.y
+ srcRect
.height
/ 2;
3576 Patch p
= project
.getLoader().importImage(project
, x
, y
);
3579 Utils
.showMessage("Could not open the image.");
3583 Display
.this.getLayerSet().addLayerContentStep(layer
);
3585 layer
.add(p
); // will add it to the proper Displays
3587 Display
.this.getLayerSet().addLayerContentStep(layer
);
3590 } catch (Exception e
) {
3596 return Bureaucrat
.createAndStart(worker
, getProject());
3599 protected Bureaucrat
importNextImage() {
3600 Worker worker
= new Worker("Import image") { /// all this verbosity is what happens when functions are not first class citizens. I could abstract it away by passing a string name "importImage" and invoking it with reflection, but that is an even bigger PAIN
3605 Rectangle srcRect
= canvas
.getSrcRect();
3606 int x
= srcRect
.x
+ srcRect
.width
/ 2;// - imp.getWidth() / 2;
3607 int y
= srcRect
.y
+ srcRect
.height
/ 2;// - imp.getHeight()/ 2;
3608 Patch p
= project
.getLoader().importNextImage(project
, x
, y
);
3610 Utils
.showMessage("Could not open next image.");
3615 Display
.this.getLayerSet().addLayerContentStep(layer
);
3617 layer
.add(p
); // will add it to the proper Displays
3619 Display
.this.getLayerSet().addLayerContentStep(layer
);
3621 } catch (Exception e
) {
3627 return Bureaucrat
.createAndStart(worker
, getProject());
3631 /** Make the given channel have the given alpha (transparency). */
3632 public void setChannel(int c
, float alpha
) {
3633 int a
= (int)(255 * alpha
);
3634 int l
= (c_alphas
&0xff000000)>>24;
3635 int r
= (c_alphas
&0xff0000)>>16;
3636 int g
= (c_alphas
&0xff00)>>8;
3637 int b
= c_alphas
&0xff;
3640 // all to the given alpha
3641 c_alphas
= (l
<<24) + (r
<<16) + (g
<<8) + b
; // parenthesis are NECESSARY
3644 // modify only the red
3645 c_alphas
= (l
<<24) + (a
<<16) + (g
<<8) + b
;
3648 c_alphas
= (l
<<24) + (r
<<16) + (a
<<8) + b
;
3651 c_alphas
= (l
<<24) + (r
<<16) + (g
<<8) + a
;
3654 //Utils.log2("c_alphas: " + c_alphas);
3655 //canvas.setUpdateGraphics(true);
3656 canvas
.repaint(true);
3657 updateInDatabase("c_alphas");
3660 /** Set the channel as active and the others as inactive. */
3661 public void setActiveChannel(Channel channel
) {
3662 for (int i
=0; i
<4; i
++) {
3663 if (channel
!= channels
[i
]) channels
[i
].setActive(false);
3664 else channel
.setActive(true);
3666 Utils
.updateComponent(panel_channels
);
3667 transp_slider
.setValue((int)(channel
.getAlpha() * 100));
3670 public int getDisplayChannelAlphas() { return c_alphas
; }
3672 // rename this method and the getDisplayChannelAlphas ! They sound the same!
3673 public int getChannelAlphas() {
3674 return ((int)(channels
[0].getAlpha() * 255)<<24) + ((int)(channels
[1].getAlpha() * 255)<<16) + ((int)(channels
[2].getAlpha() * 255)<<8) + (int)(channels
[3].getAlpha() * 255);
3677 public int getChannelAlphasState() {
3678 return ((channels
[0].isSelected() ?
255 : 0)<<24)
3679 + ((channels
[1].isSelected() ?
255 : 0)<<16)
3680 + ((channels
[2].isSelected() ?
255 : 0)<<8)
3681 + (channels
[3].isSelected() ?
255 : 0);
3684 /** Show the layer in the front Display, or in a new Display if the front Display is showing a layer from a different LayerSet. */
3685 static public void showFront(final Layer layer
) {
3686 Display display
= front
;
3687 if (null == display
|| display
.layer
.getParent() != layer
.getParent()) {
3688 display
= new Display(layer
.getProject(), layer
, null); // gets set to front
3690 display
.setLayer(layer
);
3694 /** Show the given Displayable centered and selected. If select is false, the selection is cleared. */
3695 static public void showCentered(Layer layer
, Displayable displ
, boolean select
, boolean shift_down
) {
3696 // see if the given layer belongs to the layer set being displayed
3697 Display display
= front
; // to ensure thread consistency to some extent
3698 if (null == display
|| display
.layer
.getParent() != layer
.getParent()) {
3699 display
= new Display(layer
.getProject(), layer
, displ
); // gets set to front
3700 } else if (display
.layer
!= layer
) {
3701 display
.setLayer(layer
);
3704 if (!shift_down
) display
.selection
.clear();
3705 display
.selection
.add(displ
);
3707 display
.selection
.clear();
3709 display
.showCentered(displ
);
3712 private final void showCentered(final Displayable displ
) {
3713 if (null == displ
) return;
3714 SwingUtilities
.invokeLater(new Runnable() { public void run() {
3715 displ
.setVisible(true);
3716 Rectangle box
= displ
.getBoundingBox();
3717 if (0 == box
.width
|| 0 == box
.height
) {
3718 box
.width
= (int)layer
.getLayerWidth();
3719 box
.height
= (int)layer
.getLayerHeight();
3721 canvas
.showCentered(box
);
3722 scrollToShow(displ
);
3723 if (displ
instanceof ZDisplayable
) {
3724 // scroll to first layer that has a point
3725 ZDisplayable zd
= (ZDisplayable
)displ
;
3726 setLayer(zd
.getFirstLayer());
3731 /** Listen to interesting updates, such as the ColorPicker and updates to Patch objects. */
3732 public void imageUpdated(ImagePlus updated
) {
3733 // detect ColorPicker WARNING this will work even if the Display is not the window immediately active under the color picker.
3734 if (this == front
&& updated
instanceof ij
.plugin
.ColorPicker
) {
3735 if (null != active
&& project
.isInputEnabled()) {
3736 selection
.setColor(Toolbar
.getForegroundColor());
3737 Display
.repaint(front
.layer
, selection
.getBox(), 0);
3741 // $%#@!! LUT changes don't set the image as changed
3742 //if (updated instanceof PatchStack) {
3743 // updated.changes = 1
3746 //Utils.log2("imageUpdated: " + updated + " " + updated.getClass());
3748 /* // never gets called (?)
3749 // the above is overkill. Instead:
3750 if (updated instanceof PatchStack) {
3751 Patch p = ((PatchStack)updated).getCurrentPatch();
3752 ImageProcessor ip = updated.getProcessor();
3753 p.setMinAndMax(ip.getMin(), ip.getMax());
3754 Utils.log2("setting min and max: " + ip.getMin() + ", " + ip.getMax());
3755 project.getLoader().decacheAWT(p.getId()); // including level 0, which will be editable
3756 // on repaint, it will be recreated
3757 //((PatchStack)updated).decacheAll(); // so that it will repaint with a newly created image
3761 // detect LUT changes: DONE at PatchStack, which is the active (virtual) image
3762 //Utils.log2("calling decache for " + updated);
3763 //getProject().getLoader().decache(updated);
3766 public void imageClosed(ImagePlus imp
) {}
3767 public void imageOpened(ImagePlus imp
) {}
3769 /** Release memory captured by the offscreen images */
3770 static public void flushAll() {
3771 for (final Display d
: al_displays
) {
3779 static public Display
getFront() {
3783 static public void setCursorToAll(final Cursor c
) {
3784 for (final Display d
: al_displays
) {
3785 d
.frame
.setCursor(c
);
3789 protected void setCursor(Cursor c
) {
3793 /** Used by the Displayable to update the visibility checkbox in other Displays. */
3794 static protected void updateVisibilityCheckbox(final Layer layer
, final Displayable displ
, final Display calling_display
) {
3795 //LOCKS ALL //SwingUtilities.invokeLater(new Runnable() { public void run() {
3796 for (final Display d
: al_displays
) {
3797 if (d
== calling_display
) continue;
3798 if (d
.layer
.contains(displ
) || (displ
instanceof ZDisplayable
&& d
.layer
.getParent().contains((ZDisplayable
)displ
))) {
3799 DisplayablePanel dp
= d
.ht_panels
.get(displ
);
3800 if (null != dp
) dp
.updateVisibilityCheckbox();
3806 protected boolean isActiveWindow() {
3807 return frame
.isActive();
3810 /** Toggle user input; pan and zoom are always enabled though.*/
3811 static public void setReceivesInput(final Project project
, final boolean b
) {
3812 for (final Display d
: al_displays
) {
3813 if (d
.project
== project
) d
.canvas
.setReceivesInput(b
);
3817 /** Export the DTD that defines this object. */
3818 static public void exportDTD(StringBuffer sb_header
, HashSet hs
, String indent
) {
3819 if (hs
.contains("t2_display")) return; // TODO to avoid collisions the type shoud be in a namespace such as tm2:display
3820 hs
.add("t2_display");
3821 sb_header
.append(indent
).append("<!ELEMENT t2_display EMPTY>\n")
3822 .append(indent
).append("<!ATTLIST t2_display id NMTOKEN #REQUIRED>\n")
3823 .append(indent
).append("<!ATTLIST t2_display layer_id NMTOKEN #REQUIRED>\n")
3824 .append(indent
).append("<!ATTLIST t2_display x NMTOKEN #REQUIRED>\n")
3825 .append(indent
).append("<!ATTLIST t2_display y NMTOKEN #REQUIRED>\n")
3826 .append(indent
).append("<!ATTLIST t2_display magnification NMTOKEN #REQUIRED>\n")
3827 .append(indent
).append("<!ATTLIST t2_display srcrect_x NMTOKEN #REQUIRED>\n")
3828 .append(indent
).append("<!ATTLIST t2_display srcrect_y NMTOKEN #REQUIRED>\n")
3829 .append(indent
).append("<!ATTLIST t2_display srcrect_width NMTOKEN #REQUIRED>\n")
3830 .append(indent
).append("<!ATTLIST t2_display srcrect_height NMTOKEN #REQUIRED>\n")
3831 .append(indent
).append("<!ATTLIST t2_display scroll_step NMTOKEN #REQUIRED>\n")
3832 .append(indent
).append("<!ATTLIST t2_display c_alphas NMTOKEN #REQUIRED>\n")
3833 .append(indent
).append("<!ATTLIST t2_display c_alphas_state NMTOKEN #REQUIRED>\n")
3836 /** Export all displays of the given project as XML entries. */
3837 static public void exportXML(final Project project
, final Writer writer
, final String indent
, final Object any
) throws Exception
{
3838 final StringBuffer sb_body
= new StringBuffer();
3839 final String in
= indent
+ "\t";
3840 for (final Display d
: al_displays
) {
3841 if (d
.project
!= project
) continue;
3842 final Rectangle r
= d
.frame
.getBounds();
3843 final Rectangle srcRect
= d
.canvas
.getSrcRect();
3844 final double magnification
= d
.canvas
.getMagnification();
3845 sb_body
.append(indent
).append("<t2_display id=\"").append(d
.id
).append("\"\n")
3846 .append(in
).append("layer_id=\"").append(d
.layer
.getId()).append("\"\n")
3847 .append(in
).append("c_alphas=\"").append(d
.c_alphas
).append("\"\n")
3848 .append(in
).append("c_alphas_state=\"").append(d
.getChannelAlphasState()).append("\"\n")
3849 .append(in
).append("x=\"").append(r
.x
).append("\"\n")
3850 .append(in
).append("y=\"").append(r
.y
).append("\"\n")
3851 .append(in
).append("magnification=\"").append(magnification
).append("\"\n")
3852 .append(in
).append("srcrect_x=\"").append(srcRect
.x
).append("\"\n")
3853 .append(in
).append("srcrect_y=\"").append(srcRect
.y
).append("\"\n")
3854 .append(in
).append("srcrect_width=\"").append(srcRect
.width
).append("\"\n")
3855 .append(in
).append("srcrect_height=\"").append(srcRect
.height
).append("\"\n")
3856 .append(in
).append("scroll_step=\"").append(d
.scroll_step
).append("\"\n")
3858 sb_body
.append(indent
).append("/>\n");
3860 writer
.write(sb_body
.toString());
3863 static public void toolChanged(final String tool_name
) {
3864 Utils
.log2("tool name: " + tool_name
);
3865 if (!tool_name
.equals("ALIGN")) {
3866 for (final Display d
: al_displays
) {
3867 d
.layer
.getParent().cancelAlign();
3872 static public void toolChanged(final int tool
) {
3873 //Utils.log2("int tool is " + tool);
3874 if (ProjectToolbar
.PEN
== tool
) {
3875 // erase bounding boxes
3876 for (final Display d
: al_displays
) {
3877 if (null != d
.active
) d
.repaint(d
.layer
, d
.selection
.getBox(), 2);
3880 if (null != front
) {
3881 WindowManager
.setTempCurrentImage(front
.canvas
.getFakeImagePlus());
3885 public Selection
getSelection() {
3889 public boolean isSelected(Displayable d
) {
3890 return selection
.contains(d
);
3893 static public void updateSelection() {
3894 Display
.updateSelection(null);
3896 static public void updateSelection(final Display calling
) {
3897 final HashSet hs
= new HashSet();
3898 for (final Display d
: al_displays
) {
3899 if (hs
.contains(d
.layer
)) continue;
3901 if (null == d
|| null == d
.selection
) {
3902 Utils
.log2("d is : "+ d
+ " d.selection is " + d
.selection
);
3904 d
.selection
.update(); // recomputes box
3906 if (d
!= calling
) { // TODO this is so dirty!
3907 if (d
.selection
.getNLinked() > 1) d
.canvas
.setUpdateGraphics(true); // this is overkill anyway
3908 d
.canvas
.repaint(d
.selection
.getLinkedBox(), Selection
.PADDING
);
3909 d
.navigator
.repaint(true); // everything
3914 static public void clearSelection(final Layer layer
) {
3915 for (final Display d
: al_displays
) {
3916 if (d
.layer
== layer
) d
.selection
.clear();
3919 static public void clearSelection() {
3920 for (final Display d
: al_displays
) {
3921 d
.selection
.clear();
3925 private void setTempCurrentImage() {
3926 WindowManager
.setCurrentWindow(canvas
.getFakeImagePlus().getWindow(), true);
3927 WindowManager
.setTempCurrentImage(canvas
.getFakeImagePlus());
3930 /** Check if any display will paint the given Displayable at the given magnification. */
3931 static public boolean willPaint(final Displayable displ
, final double magnification
) {
3932 Rectangle box
= null; ;
3933 for (final Display d
: al_displays
) {
3934 /* // Can no longer do this check, because 'magnification' is now affected by the Displayable AffineTransform! And thus it would not paint after the prePaint.
3935 if (Math.abs(d.canvas.getMagnification() - magnification) > 0.00000001) {
3939 if (null == box
) box
= displ
.getBoundingBox(null);
3940 if (d
.canvas
.getSrcRect().intersects(box
)) {
3947 public void hideDeselected(final boolean not_images
) {
3949 final ArrayList all
= layer
.getParent().getZDisplayables(); // a copy
3950 all
.addAll(layer
.getDisplayables());
3951 all
.removeAll(selection
.getSelected());
3952 if (not_images
) all
.removeAll(layer
.getDisplayables(Patch
.class));
3953 for (final Displayable d
: (ArrayList
<Displayable
>)all
) {
3954 if (d
.isVisible()) d
.setVisible(false);
3956 Display
.update(layer
);
3959 /** Cleanup internal lists that may contain the given Displayable. */
3960 static public void flush(final Displayable displ
) {
3961 for (final Display d
: al_displays
) {
3962 d
.selection
.removeFromPrev(displ
);
3966 public void resizeCanvas() {
3967 GenericDialog gd
= new GenericDialog("Resize LayerSet");
3968 gd
.addNumericField("new width: ", layer
.getLayerWidth(), 3);
3969 gd
.addNumericField("new height: ",layer
.getLayerHeight(),3);
3970 gd
.addChoice("Anchor: ", LayerSet
.ANCHORS
, LayerSet
.ANCHORS
[7]);
3972 if (gd
.wasCanceled()) return;
3973 double new_width
= gd
.getNextNumber();
3974 double new_height
=gd
.getNextNumber();
3975 layer
.getParent().setDimensions(new_width
, new_height
, gd
.getNextChoiceIndex()); // will complain and prevent cropping existing Displayable objects
3979 // To record layer changes -- but it's annoying, this is visualization not data.
3980 static class DoSetLayer implements DoStep {
3981 final Display display;
3983 DoSetLayer(final Display display) {
3984 this.display = display;
3985 this.layer = display.layer;
3987 public Displayable getD() { return null; }
3988 public boolean isEmpty() { return false; }
3989 public boolean apply(final int action) {
3990 display.setLayer(layer);
3992 public boolean isIdenticalTo(final Object ob) {
3993 if (!ob instanceof DoSetLayer) return false;
3994 final DoSetLayer dsl = (DoSetLayer) ob;
3995 return dsl.display == this.display && dsl.layer == this.layer;
4000 protected void duplicateLinkAndSendTo(final Displayable active
, final int position
, final Layer other_layer
) {
4001 if (null == active
|| !(active
instanceof Profile
)) return;
4002 if (active
.getLayer() == other_layer
) return; // can't do that!
4003 Profile profile
= project
.getProjectTree().duplicateChild((Profile
)active
, position
, other_layer
);
4004 if (null == profile
) return;
4005 active
.link(profile
);
4006 slt
.setAndWait(other_layer
);
4007 other_layer
.add(profile
);
4008 selection
.add(profile
);
4011 private final HashMap
<Color
,Layer
> layer_channels
= new HashMap
<Color
,Layer
>();
4012 private final TreeMap
<Integer
,LayerPanel
> layer_alpha
= new TreeMap
<Integer
,LayerPanel
>();
4014 /** Remove all red/blue coloring of layers, and repaint canvas. */
4015 protected void resetLayerColors() {
4016 synchronized (layer_channels
) {
4017 for (final Layer l
: new ArrayList
<Layer
>(layer_channels
.values())) { // avoid concurrent modification exception
4018 final LayerPanel lp
= layer_panels
.get(l
);
4019 lp
.setColor(Color
.white
);
4020 setColorChannel(lp
.layer
, Color
.white
);
4021 lp
.slider
.setEnabled(true);
4023 layer_channels
.clear();
4028 /** Set all layer alphas to zero, and repaint canvas. */
4029 protected void resetLayerAlphas() {
4030 synchronized (layer_channels
) {
4031 for (final LayerPanel lp
: new ArrayList
<LayerPanel
>(layer_alpha
.values())) {
4034 layer_alpha
.clear(); // should have already been cleared
4039 /** Add to layer_alpha table, or remove if alpha is zero. */
4040 protected void storeLayerAlpha(final LayerPanel lp
, final float a
) {
4041 synchronized (layer_channels
) {
4042 if (M
.equals(0, a
)) {
4043 layer_alpha
.remove(lp
.layer
.getParent().indexOf(lp
.layer
));
4045 layer_alpha
.put(lp
.layer
.getParent().indexOf(lp
.layer
), lp
);
4050 static protected final int REPAINT_SINGLE_LAYER
= 0;
4051 static protected final int REPAINT_MULTI_LAYER
= 1;
4052 static protected final int REPAINT_RGB_LAYER
= 2;
4054 /** Sets the values atomically, returns the painting mode. */
4055 protected int getPaintMode(final HashMap
<Color
,Layer
> hm
, final ArrayList
<LayerPanel
> list
) {
4056 synchronized (layer_channels
) {
4057 if (layer_channels
.size() > 0) {
4058 hm
.putAll(layer_channels
);
4059 hm
.put(Color
.green
, this.layer
);
4060 return REPAINT_RGB_LAYER
;
4062 list
.addAll(layer_alpha
.values());
4063 final int len
= list
.size();
4064 if (len
> 1) return REPAINT_MULTI_LAYER
;
4066 if (list
.get(0).layer
== this.layer
) return REPAINT_SINGLE_LAYER
; // normal mode
4067 return REPAINT_MULTI_LAYER
;
4069 return REPAINT_SINGLE_LAYER
;
4073 /** Set a layer to be painted as a specific color channel in the canvas.
4074 * Only Color.red and Color.blue are accepted.
4075 * Color.green is reserved for the current layer. */
4076 protected void setColorChannel(final Layer layer
, final Color color
) {
4077 synchronized (layer_channels
) {
4078 if (Color
.white
== color
) {
4080 for (final Iterator
<Layer
> it
= layer_channels
.values().iterator(); it
.hasNext(); ) {
4081 if (it
.next() == layer
) {
4087 } else if (Color
.red
== color
|| Color
.blue
== color
) {
4088 // Reset current of that color, if any, to white
4089 final Layer l
= layer_channels
.remove(color
);
4090 if (null != l
) layer_panels
.get(l
).setColor(Color
.white
);
4091 // Replace or set new
4092 layer_channels
.put(color
, layer
);
4096 Utils
.log2("Trying to set unacceptable color for layer " + layer
+ " : " + color
);
4098 // enable/disable sliders
4099 final boolean b
= 0 == layer_channels
.size();
4100 for (final LayerPanel lp
: layer_panels
.values()) lp
.slider
.setEnabled(b
);
4102 this.canvas
.repaint(true);
4105 static public final void updateComponentTreeUI() {
4107 for (final Display d
: al_displays
) SwingUtilities
.updateComponentTreeUI(d
.frame
);
4108 } catch (Exception e
) {