put match intensities commands into workers to block input accordingly
[trakem2.git] / TrakEM2_ / src / main / java / ini / trakem2 / display / Display.java
blobdb37cb232dfc0b8da015d8adf3afc01294028145
1 /**
3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2005-2009 Albert Cardona and Rodney Douglas.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation (http://www.gnu.org/licenses/gpl.txt )
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 You may contact Albert Cardona at acardona at ini.phys.ethz.ch
20 Institute of Neuroinformatics, University of Zurich / ETH, Switzerland.
21 **/
23 package ini.trakem2.display;
25 import ij.IJ;
26 import ij.IJEventListener;
27 import ij.ImageJ;
28 import ij.ImagePlus;
29 import ij.Menus;
30 import ij.WindowManager;
31 import ij.gui.GenericDialog;
32 import ij.gui.PolygonRoi;
33 import ij.gui.Roi;
34 import ij.gui.ShapeRoi;
35 import ij.gui.Toolbar;
36 import ij.gui.YesNoCancelDialog;
37 import ij.io.DirectoryChooser;
38 import ij.io.OpenDialog;
39 import ij.io.SaveDialog;
40 import ij.measure.Calibration;
41 import ij.measure.ResultsTable;
42 import ij.process.ImageProcessor;
43 import ini.trakem2.ControlWindow;
44 import ini.trakem2.Project;
45 import ini.trakem2.analysis.Graph;
46 import ini.trakem2.display.inspect.InspectPatchTrianglesMode;
47 import ini.trakem2.imaging.Blending;
48 import ini.trakem2.imaging.LayerStack;
49 import ini.trakem2.imaging.PatchStack;
50 import ini.trakem2.imaging.Segmentation;
51 import ini.trakem2.imaging.filters.FilterEditor;
52 import ini.trakem2.io.NeuroML;
53 import ini.trakem2.parallel.Process;
54 import ini.trakem2.parallel.TaskFactory;
55 import ini.trakem2.persistence.DBObject;
56 import ini.trakem2.persistence.Loader;
57 import ini.trakem2.persistence.ProjectTiler;
58 import ini.trakem2.persistence.XMLOptions;
59 import ini.trakem2.tree.ProjectThing;
60 import ini.trakem2.utils.AreaUtils;
61 import ini.trakem2.utils.Bureaucrat;
62 import ini.trakem2.utils.DNDInsertImage;
63 import ini.trakem2.utils.Dispatcher;
64 import ini.trakem2.utils.Filter;
65 import ini.trakem2.utils.IJError;
66 import ini.trakem2.utils.M;
67 import ini.trakem2.utils.Operation;
68 import ini.trakem2.utils.OptionPanel;
69 import ini.trakem2.utils.ProjectToolbar;
70 import ini.trakem2.utils.Saver;
71 import ini.trakem2.utils.Search;
72 import ini.trakem2.utils.Utils;
73 import ini.trakem2.utils.Worker;
75 import java.awt.BasicStroke;
76 import java.awt.Choice;
77 import java.awt.Color;
78 import java.awt.Component;
79 import java.awt.Container;
80 import java.awt.Cursor;
81 import java.awt.Dimension;
82 import java.awt.Event;
83 import java.awt.Graphics;
84 import java.awt.Graphics2D;
85 import java.awt.GridBagConstraints;
86 import java.awt.GridBagLayout;
87 import java.awt.Image;
88 import java.awt.Insets;
89 import java.awt.Point;
90 import java.awt.Polygon;
91 import java.awt.Rectangle;
92 import java.awt.Scrollbar;
93 import java.awt.TextField;
94 import java.awt.Toolkit;
95 import java.awt.event.ActionEvent;
96 import java.awt.event.ActionListener;
97 import java.awt.event.AdjustmentEvent;
98 import java.awt.event.AdjustmentListener;
99 import java.awt.event.ComponentAdapter;
100 import java.awt.event.ComponentEvent;
101 import java.awt.event.ComponentListener;
102 import java.awt.event.InputEvent;
103 import java.awt.event.ItemEvent;
104 import java.awt.event.ItemListener;
105 import java.awt.event.KeyEvent;
106 import java.awt.event.KeyListener;
107 import java.awt.event.MouseAdapter;
108 import java.awt.event.MouseEvent;
109 import java.awt.event.MouseListener;
110 import java.awt.event.TextEvent;
111 import java.awt.event.TextListener;
112 import java.awt.event.WindowAdapter;
113 import java.awt.event.WindowEvent;
114 import java.awt.event.WindowListener;
115 import java.awt.geom.AffineTransform;
116 import java.awt.geom.Area;
117 import java.awt.geom.Line2D;
118 import java.awt.geom.NoninvertibleTransformException;
119 import java.awt.image.BufferedImage;
120 import java.io.BufferedOutputStream;
121 import java.io.File;
122 import java.io.FileOutputStream;
123 import java.io.OutputStreamWriter;
124 import java.io.Writer;
125 import java.lang.reflect.Field;
126 import java.lang.reflect.Method;
127 import java.util.ArrayList;
128 import java.util.Arrays;
129 import java.util.Collection;
130 import java.util.Collections;
131 import java.util.Enumeration;
132 import java.util.HashMap;
133 import java.util.HashSet;
134 import java.util.Hashtable;
135 import java.util.Iterator;
136 import java.util.List;
137 import java.util.ListIterator;
138 import java.util.Map;
139 import java.util.Set;
140 import java.util.TreeMap;
141 import java.util.Vector;
142 import java.util.concurrent.Callable;
143 import java.util.concurrent.Future;
144 import java.util.regex.Pattern;
146 import javax.swing.DefaultBoundedRangeModel;
147 import javax.swing.JEditorPane;
148 import javax.swing.JFrame;
149 import javax.swing.JLabel;
150 import javax.swing.JMenu;
151 import javax.swing.JMenuItem;
152 import javax.swing.JPanel;
153 import javax.swing.JPopupMenu;
154 import javax.swing.JScrollBar;
155 import javax.swing.JScrollPane;
156 import javax.swing.JSlider;
157 import javax.swing.JTabbedPane;
158 import javax.swing.JViewport;
159 import javax.swing.KeyStroke;
160 import javax.swing.SwingUtilities;
161 import javax.swing.event.ChangeEvent;
162 import javax.swing.event.ChangeListener;
163 import javax.swing.event.DocumentEvent;
164 import javax.swing.event.DocumentListener;
165 import javax.swing.text.Document;
167 import lenscorrection.DistortionCorrectionTask;
168 import lenscorrection.NonLinearTransform;
169 import mpicbg.ij.clahe.Flat;
170 import mpicbg.models.PointMatch;
171 import mpicbg.trakem2.align.AlignLayersTask;
172 import mpicbg.trakem2.align.AlignTask;
173 import mpicbg.trakem2.transform.AffineModel3D;
174 import mpicbg.trakem2.transform.CoordinateTransform;
175 import mpicbg.trakem2.transform.CoordinateTransformList;
177 import org.janelia.intensity.MatchIntensities;
179 /** A Display is a class to show a Layer and enable mouse and keyboard manipulation of all its components. */
180 public final class Display extends DBObject implements ActionListener, IJEventListener {
182 /** coordinate transform transfer modes */
183 final static public int CT_REPLACE = 0;
184 final static public int CT_APPEND = 1;
185 final static public int CT_PREAPPEND = 2;
187 /** The Layer this Display is showing. */
188 private Layer layer;
190 private Displayable active = null;
191 /** All selected Displayable objects, including the active one. */
192 final private Selection selection = new Selection(this);
194 private JFrame frame;
195 private JTabbedPane tabs;
197 private Hashtable<Class<?>,RollingPanel> ht_tabs;
198 private RollingPanel panel_patches;
199 private RollingPanel panel_profiles;
200 private RollingPanel panel_zdispl;
201 private JScrollPane scroll_channels;
202 private JPanel panel_channels;
203 private RollingPanel panel_labels;
205 private JPanel panel_layers;
206 private JScrollPane scroll_layers;
207 private final Hashtable<Layer,LayerPanel> layer_panels = new Hashtable<Layer,LayerPanel>();
209 private OptionPanel tool_options;
210 private JScrollPane scroll_options;
212 private OptionPanel filter_options;
213 private JScrollPane scroll_filter_options;
215 private JEditorPane annot_editor;
216 private JLabel annot_label;
217 private JPanel annot_panel;
218 static private HashMap<Displayable,Document> annot_docs = new HashMap<Displayable,Document>();
220 private JSlider transp_slider;
221 private DisplayNavigator navigator;
222 private JScrollBar scroller;
224 private DisplayCanvas canvas; // WARNING this is an AWT component, since it extends ImageCanvas
225 private JPanel all;
227 private JPopupMenu popup = null;
229 private ToolbarPanel toolbar_panel = null;
231 /** Contains the packed alphas of every channel. */
232 private int c_alphas = 0xffffffff; // all 100 % visible
233 private Channel[] channels;
235 private final Hashtable<Displayable,DisplayablePanel> ht_panels = new Hashtable<Displayable,DisplayablePanel>();
237 /** Handle drop events, to insert image files. */
238 private DNDInsertImage dnd;
240 private int scroll_step = 1;
242 static private final Object DISPLAY_LOCK = new Object();
244 /** Keep track of all existing Display objects. */
245 static private Set<Display> al_displays = new HashSet<Display>();
246 /** The currently focused Display, if any. */
247 static private Display front = null;
249 /** Displays to open when all objects have been reloaded from the database. */
250 static private final Hashtable<Display,Object[]> ht_later = new Hashtable<Display,Object[]>();
252 /** A thread to handle user actions, for example an event sent from a popup menu. */
253 protected final Dispatcher dispatcher = new Dispatcher("Display GUI Updater");
255 static private WindowAdapter window_listener = new WindowAdapter() {
256 /** Unregister the closed Display. */
257 @Override
258 public void windowClosing(final WindowEvent we) {
259 final Object source = we.getSource();
260 for (final Display d : al_displays) {
261 if (source == d.frame) {
262 d.remove(false); // calls destroy, which calls removeDisplay
263 break;
267 /** Set the source Display as front. */
268 @Override
269 public void windowActivated(final WindowEvent we) {
270 // find which was it to make it be the front
271 final ImageJ ij = IJ.getInstance();
272 if (null != ij && ij.quitting()) return;
273 final Object source = we.getSource();
274 for (final Display d : al_displays) {
275 if (source == d.frame) {
276 front = d;
277 // set toolbar
278 ProjectToolbar.setProjectToolbar();
279 // now, select the layer in the LayerTree
280 front.getProject().select(front.layer);
281 // finally, set the virtual ImagePlus that ImageJ will see
282 d.setTempCurrentImage();
283 // copied from ij.gui.ImageWindow, with modifications
284 if (IJ.isMacintosh() && IJ.getInstance()!=null) {
285 IJ.wait(10); // may be needed for Java 1.4 on OS X
286 d.frame.setMenuBar(Menus.getMenuBar());
288 return;
291 // else, restore the ImageJ toolbar for non-project images
292 //if (!source.equals(IJ.getInstance())) {
293 // ProjectToolbar.setImageJToolbar();
296 /** Restore the ImageJ toolbar */
297 @Override
298 public void windowDeactivated(final WindowEvent we) {
299 // 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();
301 /** Call a pack() when the window is maximized to fit the canvas correctly. */
302 @Override
303 public void windowStateChanged(final WindowEvent we) {
304 final Object source = we.getSource();
305 for (final Display d : al_displays) {
306 if (source != d.frame) continue;
307 d.pack();
308 break;
313 static public final Vector<Display> getDisplays() {
314 return new Vector<Display>(al_displays);
317 static public final int getDisplayCount() {
318 return al_displays.size();
321 private final ComponentListener canvas_size_listener = new ComponentAdapter() {
322 @Override
323 public void componentResized(final ComponentEvent ce) {
324 canvas.adjustDimensions();
325 canvas.repaint(true);
326 navigator.repaint(false); // update srcRect red frame position/size
330 /*// Currently not in use
331 private ComponentListener display_frame_listener = new ComponentAdapter() {
332 public void componentMoved(ComponentEvent ce) {
333 updateInDatabase("position");
338 static private ChangeListener tabs_listener = new ChangeListener() {
339 /** Listen to tab changes. */
340 @Override
341 public void stateChanged(final ChangeEvent ce) {
342 final Object source = ce.getSource();
343 for (final Display d : al_displays) {
344 if (source != d.tabs) continue;
345 // creating tabs fires the event!!!
346 if (null == d.frame || null == d.canvas) return;
347 final Container tab = (Container)d.tabs.getSelectedComponent();
348 if (tab == d.scroll_channels) {
349 // find active channel if any
350 for (int i=0; i<d.channels.length; i++) {
351 if (d.channels[i].isActive()) {
352 d.transp_slider.setValue((int)(d.channels[i].getAlpha() * 100));
353 break;
356 } else {
357 // recreate contents
358 JPanel p = null;
359 if (tab == d.panel_zdispl) {
360 p = d.panel_zdispl;
361 } else if (tab == d.panel_patches) {
362 p = d.panel_patches;
363 } else if (tab == d.panel_labels) {
364 p = d.panel_labels;
365 } else if (tab == d.panel_profiles) {
366 p = d.panel_profiles;
367 } else if (tab == d.scroll_layers) {
368 // nothing to do
369 return;
370 } else if (tab == d.scroll_options) {
371 // Choose according to tool
372 d.updateToolTab();
373 return;
376 d.updateTab(p);
378 if (null != d.active) {
379 // set the transp slider to the alpha value of the active Displayable if any
380 d.transp_slider.setValue((int)(d.active.getAlpha() * 100));
383 break;
389 private final AdjustmentListener scroller_listener = new AdjustmentListener() {
390 @Override
391 public void adjustmentValueChanged(final AdjustmentEvent ae) {
392 final int index = ae.getValue();
393 final Layer la = layer.getParent().getLayer(index);
394 if (la != Display.this.layer) slt.set(la);
398 private final SetLayerThread slt = new SetLayerThread();
400 private class SetLayerThread extends Thread {
402 private volatile Layer layer;
403 private final Object lock = new Object();
405 SetLayerThread() {
406 super("SetLayerThread");
407 setPriority(Thread.NORM_PRIORITY);
408 setDaemon(true);
409 start();
412 public final void set(final Layer layer) {
413 synchronized (lock) {
414 this.layer = layer;
416 synchronized (this) {
417 notify();
421 // Does not use the thread, rather just sets it within the context of the calling thread (would be the same as making the caller thread wait.)
422 final void setAndWait(final Layer layer) {
423 if (null != layer) {
425 if (layer.getParent().preload_ahead > 0) {
426 preloadImagesAhead(Display.this.layer, layer, layer.getParent().preload_ahead);
429 Display.this.setLayer(layer);
430 Display.this.updateInDatabase("layer_id");
431 createColumnScreenshots();
435 @Override
436 public void run() {
437 while (!isInterrupted()) {
438 while (null == this.layer) {
439 synchronized (this) {
440 if (isInterrupted()) return;
441 try { wait(); } catch (final InterruptedException ie) {}
444 Layer layer = null;
445 synchronized (lock) {
446 layer = this.layer;
447 this.layer = null;
450 if (isInterrupted()) return; // after nullifying layer
452 setAndWait(layer);
456 public void quit() {
457 interrupt();
458 synchronized (this) {
459 notify();
464 static final public void clearColumnScreenshots(final LayerSet ls) {
465 for (final Display d : al_displays) {
466 if (d.layer.getParent() == ls) d.clearColumnScreenshots();
470 private final ImagePreloader imagePreloader = new ImagePreloader();
472 private final class ImagePreloader extends Thread {
473 private Layer oldLayer, newLayer;
474 private int nLayers;
475 private boolean restart = false;
477 ImagePreloader() {
478 setPriority(Thread.NORM_PRIORITY);
479 setDaemon(true);
480 start();
483 final void reset(final Layer oldLayer, final Layer newLayer, final int nLayers) {
484 synchronized (this) {
485 this.restart = true;
486 this.oldLayer = oldLayer;
487 this.newLayer = newLayer;
488 this.nLayers = nLayers;
489 notify();
493 @Override
494 public void run() {
495 while (!isInterrupted()) {
496 final Layer oldLayer, newLayer;
497 final int nLayers;
498 synchronized (this) {
499 try {
500 if (!restart) wait();
501 } catch (final InterruptedException e) {
502 return;
504 oldLayer = this.oldLayer;
505 newLayer = this.newLayer;
506 this.oldLayer = null;
507 this.newLayer = null;
508 nLayers = this.nLayers;
509 restart = false;
512 final LayerSet ls = oldLayer.getParent();
513 final int old_layer_index = ls.indexOf(oldLayer);
514 final int new_layer_index = ls.indexOf(newLayer);
515 final int sign = new_layer_index - old_layer_index;
517 final Area aroi = new Area(canvas.getSrcRect());
518 final double mag = canvas.getMagnification();
520 // Preload from most distant ahead to least.
521 // The assumption being that least distant are already cached.
522 //Utils.log2("-- started");
523 for (final Layer la : ls.getLayers(Math.max(0, Math.min(new_layer_index + sign * nLayers, ls.size() -1)), new_layer_index)) {
524 if (restart) break;
525 for (final Displayable d : la.getDisplayables(Patch.class, aroi, true)) {
526 if (restart) break;
527 if (isInterrupted()) return;
528 project.getLoader().fetchImage((Patch)d, mag);
529 //Utils.log2("preloaded #" + d.getId() + " from layer " + la.getParent().indexOf(la));
532 //Utils.log2("-- completed: restart is " + restart);
537 private final void preloadImagesAhead(final Layer oldLayer, final Layer newLayer, final int nLayers) {
538 imagePreloader.reset(oldLayer, newLayer, nLayers);
541 final public void clearColumnScreenshots() {
542 getLayerSet().clearScreenshots();
545 /** Only for DefaultMode. */
546 final private void createColumnScreenshots() {
547 final int n;
548 try {
549 if (mode.getClass() == DefaultMode.class) {
550 int ahead = project.getProperty("look_ahead_cache", 0);
551 if (ahead < 0) ahead = 0;
552 if (0 == ahead) return;
553 n = ahead;
554 } else return;
555 } catch (final Exception e) {
556 IJError.print(e);
557 return;
559 project.getLoader().doLater(new Callable<Object>() {
560 @Override
561 public Object call() {
562 final Layer current = Display.this.layer;
563 // 1 - Create DisplayCanvas.Screenshot instances for the next 5 and previous 5 layers
564 final ArrayList<DisplayCanvas.Screenshot> s = new ArrayList<DisplayCanvas.Screenshot>();
565 Layer now = current;
566 Layer prev = now.getParent().previous(now);
567 int i = 0;
568 Layer next = now.getParent().next(now);
569 while (now != next && i < n) {
570 s.add(canvas.createScreenshot(next));
571 now = next;
572 next = now.getParent().next(now);
573 i++;
575 now = current;
576 i = 0;
577 while (now != prev && i < n) {
578 s.add(0, canvas.createScreenshot(prev));
579 now = prev;
580 prev = now.getParent().previous(now);
581 i++;
583 // Store them all into the LayerSet offscreens hashmap, but trigger image creation in parallel threads.
584 for (final DisplayCanvas.Screenshot sc : s) {
585 if (!current.getParent().containsScreenshot(sc)) {
586 sc.init();
587 current.getParent().storeScreenshot(sc);
588 project.getLoader().doLater(new Callable<Object>() {
589 @Override
590 public Object call() {
591 sc.createImage();
592 return null;
597 current.getParent().trimScreenshots();
598 return null;
603 /** Creates a new Display with adjusted magnification to fit in the screen. */
604 static public void createDisplay(final Project project, final Layer layer) {
605 // Really execute in a second round of event dispatch thread
606 // to enable the calling component to repaint back to normal
607 // and the events to terminate.
608 SwingUtilities.invokeLater(new Runnable() { @Override
609 public void run() {
610 final Display display = new Display(project, layer);
611 final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
612 final Rectangle srcRect = new Rectangle(0, 0, (int)layer.getLayerWidth(), (int)layer.getLayerHeight());
613 double mag = screen.width / layer.getLayerWidth();
614 if (mag * layer.getLayerHeight() > screen.height) mag = screen.height / layer.getLayerHeight();
615 mag = display.canvas.getLowerZoomLevel2(mag);
616 if (mag > 1.0) mag = 1.0;
617 //display.getCanvas().setup(mag, srcRect); // would call pack() at the wrong time!
618 // ... so instead: manually
619 display.getCanvas().setMagnification(mag);
620 display.getCanvas().setSrcRect(srcRect.x, srcRect.y, srcRect.width, srcRect.height);
621 display.getCanvas().setDrawingSize((int)Math.ceil(srcRect.width * mag), (int)Math.ceil(srcRect.height * mag));
623 display.updateFrameTitle(layer);
624 ij.gui.GUI.center(display.frame);
625 display.frame.pack();
626 }});
630 // The only two methods that ever modify the set of al_displays
633 /** Swap the current al_displays list with a new list that has the @param display in it. */
634 static private final void addDisplay(final Display display) {
635 if (null == display) return;
636 synchronized (DISPLAY_LOCK) {
637 final Set<Display> a = new HashSet<Display>();
638 if (null != al_displays) a.addAll(al_displays);
639 a.add(display);
640 al_displays = a;
641 front = display;
645 /** Swap the current al_displays list with a new list that lacks the @param dispaly, and set a new front if needed. */
646 static private final void removeDisplay(final Display display) {
647 if (null == display) return;
648 synchronized (DISPLAY_LOCK) {
649 final Set<Display> a = new HashSet<Display>(al_displays);
650 a.remove(display);
651 if (null == front || front == display) {
652 if (a.size() > 0) {
653 front = a.iterator().next();
654 } else {
655 front = null;
658 al_displays = a;
662 /** For reconstruction purposes. The Display will be stored in the ht_later.*/
663 public Display(final Project project, final long id, final Layer layer, final Object[] props) {
664 super(project, id);
665 synchronized (ht_later) {
666 Display.ht_later.put(this, props);
668 this.layer = layer;
669 IJ.addEventListener(this);
670 Display.addDisplay(this);
673 /** A new Display from scratch, to show the given Layer. */
674 public Display(final Project project, final Layer layer) {
675 this(project, layer, null);
678 /** Open a new Display centered around the given Displayable. */
679 public Display(final Project project, final Layer layer, final Displayable displ) {
680 super(project);
681 active = displ;
682 this.layer = layer;
683 makeGUI(layer, null);
684 IJ.addEventListener(this);
685 setLayer(layer);
686 this.layer = layer; // after set layer!
687 addToDatabase();
688 addDisplay(this); // last: if there is an Exception, it won't be added
691 /** Reconstruct a Display from an XML entry, to be opened when everything is ready. */
692 public Display(final Project project, final long id, final Layer layer, final HashMap<String,String> ht) {
693 super(project, id);
694 if (null == layer) {
695 Utils.log2("Display: need a non-null Layer for id=" + id);
696 return;
698 final Rectangle srcRect = new Rectangle(0, 0, (int)layer.getLayerWidth(), (int)layer.getLayerHeight());
699 double magnification = 0.25;
700 final Point p = new Point(0, 0);
701 int c_alphas = 0xffffffff;
702 int c_alphas_state = 0xffffffff;
703 String data = ht.get("srcrect_x");
704 if (null != data) srcRect.x = Integer.parseInt(data);
705 data = ht.get("srcrect_y");
706 if (null != data) srcRect.y = Integer.parseInt(data);
707 data = ht.get("srcrect_width");
708 if (null != data) srcRect.width = Integer.parseInt(data);
709 data = ht.get("srcrect_height");
710 if (null != data) srcRect.height = Integer.parseInt(data);
711 data = ht.get("magnification");
712 if (null != data) magnification = Double.parseDouble(data);
713 data = ht.get("x");
714 if (null != data) p.x = Integer.parseInt(data);
715 data = ht.get("y");
716 if (null != data) p.y = Integer.parseInt(data);
717 data = ht.get("c_alphas");
718 if (null != data) {
719 try {
720 c_alphas = Integer.parseInt(data);
721 } catch (final Exception ex) {
722 c_alphas = 0xffffffff;
725 data = ht.get("c_alphas_state");
726 if (null != data) {
727 try {
728 c_alphas_state = Integer.parseInt(data);
729 } catch (final Exception ex) {
730 IJError.print(ex);
731 c_alphas_state = 0xffffffff;
734 data = ht.get("scroll_step");
735 if (null != data) {
736 try {
737 setScrollStep(Integer.parseInt(data));
738 } catch (final Exception ex) {
739 IJError.print(ex);
740 setScrollStep(1);
743 data = ht.get("filter_enabled");
744 if (null != data) filter_enabled = Boolean.parseBoolean(data);
745 data = ht.get("filter_min_max_enabled");
746 if (null != data) filter_min_max_enabled = Boolean.parseBoolean(data);
747 data = ht.get("filter_min");
748 if (null != data) filter_min = Integer.parseInt(data);
749 data = ht.get("filter_max");
750 if (null != data) filter_max = Integer.parseInt(data);
751 data = ht.get("filter_invert");
752 if (null != data) filter_invert = Boolean.parseBoolean(data);
753 data = ht.get("filter_clahe_enabled");
754 if (null != data) filter_clahe_enabled = Boolean.parseBoolean(data);
755 data = ht.get("filter_clahe_block_size");
756 if (null != data) filter_clahe_block_size = Integer.parseInt(data);
757 data = ht.get("filter_clahe_histogram_bins");
758 if (null != data) filter_clahe_histogram_bins = Integer.parseInt(data);
759 data = ht.get("filter_clahe_max_slope");
760 if (null != data) filter_clahe_max_slope = Float.parseFloat(data);
762 // TODO the above is insecure, in that data is not fully checked to be within bounds.
765 final Object[] props = new Object[]{p, new Double(magnification), srcRect, new Long(layer.getId()), new Integer(c_alphas), new Integer(c_alphas_state)};
766 synchronized (ht_later) {
767 Display.ht_later.put(this, props);
769 this.layer = layer;
770 IJ.addEventListener(this);
772 // addDisplay(this) will be called inside the loop in openLater, see below
775 /** After reloading a project from the database, open the Displays that the project had. */
776 static public Bureaucrat openLater() {
777 final HashMap<Display,Object[]> ht_later_local;
778 final Project[] ps;
779 synchronized (ht_later) {
780 if (0 == ht_later.size()) return null;
781 ht_later_local = new HashMap<Display,Object[]>(ht_later);
782 ht_later.keySet().removeAll(ht_later_local.keySet());
783 final HashSet<Project> unique = new HashSet<Project>();
784 for (final Display d : ht_later_local.keySet()) {
785 unique.add(d.project);
787 ps = unique.toArray(new Project[unique.size()]);
789 final Worker worker = new Worker.Task("Opening displays") {
790 @Override
791 public void exec() {
792 try {
793 Thread.sleep(300); // waiting for Swing
795 for (final Display d : ht_later_local.keySet()) {
796 addDisplay(d); // must be set as front before repainting any ZDisplayable!
797 final Object[] props = ht_later_local.get(d);
798 if (ControlWindow.isGUIEnabled()) d.makeGUI(d.layer, props);
799 d.setLayerLater(d.layer, d.layer.get(((Long)props[3]).longValue())); //important to do it after makeGUI
800 if (!ControlWindow.isGUIEnabled()) continue;
802 d.updateFrameTitle(d.layer);
803 // 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
804 if (d.canvas.getMagnification() > 0.499) {
805 Utils.invokeLater(new Runnable() { @Override
806 public void run() {
807 Display.repaint(d.layer);
808 d.project.getLoader().setChanged(false);
809 Utils.log2("A set to false");
810 }});
812 d.project.getLoader().setChanged(false);
813 Utils.log2("B set to false");
815 if (null != front) front.getProject().select(front.layer);
817 } catch (final Throwable t) {
818 IJError.print(t);
822 return Bureaucrat.createAndStart(worker, ps);
825 private final class ScrollerModel extends DefaultBoundedRangeModel {
826 private static final long serialVersionUID = 1L;
827 int index;
828 ScrollerModel(final Layer la) {
829 this.index = la.getParent().indexOf(la);
831 public void setValueWithoutEvent(final int index) {
832 this.index = index;
833 scroller.updateUI(); // so the model needs to update the UI: how pretty!
835 @Override
836 public void setValue(final int index) {
837 this.index = index;
838 super.setValue(index);
840 @Override
841 public int getValue() {
842 return this.index;
846 private void makeGUI(final Layer layer, final Object[] props) {
847 // gather properties
848 final Point p;
849 double mag = 1.0D;
850 Rectangle srcRect = null;
851 if (null != props) {
852 p = (Point)props[0];
853 mag = ((Double)props[1]).doubleValue();
854 srcRect = (Rectangle)props[2];
855 } else {
856 p = null;
859 // transparency slider
860 this.transp_slider = new JSlider(javax.swing.SwingConstants.HORIZONTAL, 0, 100, 100);
861 this.transp_slider.setBackground(Color.white);
862 this.transp_slider.setMinimumSize(new Dimension(250, 20));
863 this.transp_slider.setMaximumSize(new Dimension(250, 20));
864 this.transp_slider.setPreferredSize(new Dimension(250, 20));
865 final TransparencySliderListener tsl = new TransparencySliderListener();
866 this.transp_slider.addChangeListener(tsl);
867 this.transp_slider.addMouseListener(tsl);
868 for (final KeyListener kl : this.transp_slider.getKeyListeners()) {
869 this.transp_slider.removeKeyListener(kl);
872 // Tabbed pane on the left
873 this.tabs = new JTabbedPane();
874 this.tabs.setMinimumSize(new Dimension(250, 300));
875 this.tabs.setBackground(Color.white);
876 this.tabs.addChangeListener(tabs_listener);
878 // Tab 1: Patches
879 this.panel_patches = new RollingPanel(this, Patch.class);
880 this.addTab("Patches", panel_patches);
882 // Tab 2: Profiles
883 this.panel_profiles = new RollingPanel(this, Profile.class);
884 this.addTab("Profiles", panel_profiles);
886 // Tab 3: ZDisplayables
887 this.panel_zdispl = new RollingPanel(this, ZDisplayable.class);
888 this.addTab("Z space", panel_zdispl);
890 // Tab 4: channels
891 this.panel_channels = makeTabPanel();
892 this.scroll_channels = makeScrollPane(panel_channels);
893 this.channels = new Channel[4];
894 this.channels[0] = new Channel(this, Channel.MONO);
895 this.channels[1] = new Channel(this, Channel.RED);
896 this.channels[2] = new Channel(this, Channel.GREEN);
897 this.channels[3] = new Channel(this, Channel.BLUE);
898 //this.panel_channels.add(this.channels[0]);
899 addGBRow(this.panel_channels, this.channels[1], null);
900 addGBRow(this.panel_channels, this.channels[2], this.channels[1]);
901 addGBRow(this.panel_channels, this.channels[3], this.channels[2]);
902 this.addTab("Opacity", scroll_channels);
904 // Tab 5: labels
905 this.panel_labels = new RollingPanel(this, DLabel.class);
906 this.addTab("Labels", panel_labels);
908 // Tab 6: layers
909 this.panel_layers = makeTabPanel();
910 this.scroll_layers = makeScrollPane(panel_layers);
911 recreateLayerPanels(layer);
912 this.scroll_layers.addMouseWheelListener(canvas);
913 this.addTab("Layers", scroll_layers);
915 // Tab 7: tool options
916 this.tool_options = new OptionPanel(); // empty
917 this.scroll_options = makeScrollPane(this.tool_options);
918 this.scroll_options.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
919 this.addTab("Tool options", this.scroll_options);
921 // Tab 8: annotations
922 this.annot_editor = new JEditorPane();
923 this.annot_editor.setEnabled(false); // by default, nothing is selected
924 this.annot_editor.setMinimumSize(new Dimension(200, 50));
925 this.annot_label = new JLabel("(No selected object)");
926 this.annot_panel = makeAnnotationsPanel(this.annot_editor, this.annot_label);
927 this.addTab("Annotations", this.annot_panel);
929 // Tab 9: filter options
930 this.filter_options = createFilterOptionPanel();
931 this.scroll_filter_options = makeScrollPane(this.filter_options);
932 this.scroll_filter_options.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
933 this.addTab("Live filter", this.scroll_filter_options);
935 this.ht_tabs = new Hashtable<Class<?>,RollingPanel>();
936 this.ht_tabs.put(Patch.class, panel_patches);
937 this.ht_tabs.put(Profile.class, panel_profiles);
938 this.ht_tabs.put(ZDisplayable.class, panel_zdispl);
939 this.ht_tabs.put(AreaList.class, panel_zdispl);
940 this.ht_tabs.put(Pipe.class, panel_zdispl);
941 this.ht_tabs.put(Polyline.class, panel_zdispl);
942 this.ht_tabs.put(Treeline.class, panel_zdispl);
943 this.ht_tabs.put(AreaTree.class, panel_zdispl);
944 this.ht_tabs.put(Connector.class, panel_zdispl);
945 this.ht_tabs.put(Ball.class, panel_zdispl);
946 this.ht_tabs.put(Dissector.class, panel_zdispl);
947 this.ht_tabs.put(DLabel.class, panel_labels);
948 this.ht_tabs.put(Stack.class, panel_zdispl);
949 // channels not included
950 // layers not included
951 // tools not included
952 // annotations not included
954 // Navigator
955 this.navigator = new DisplayNavigator(this, layer.getLayerWidth(), layer.getLayerHeight());
956 // Layer scroller (to scroll slices)
957 int extent = (int)(250.0 / layer.getParent().size());
958 if (extent < 10) extent = 10;
959 this.scroller = new JScrollBar(JScrollBar.HORIZONTAL);
960 this.scroller.setModel(new ScrollerModel(layer));
961 updateLayerScroller(layer);
962 this.scroller.addAdjustmentListener(scroller_listener);
964 // LAYOUT
965 final GridBagLayout layout = new GridBagLayout();
966 final GridBagConstraints c = new GridBagConstraints();
967 c.anchor = GridBagConstraints.NORTHWEST;
968 c.fill = GridBagConstraints.NONE;
969 c.weightx = 0;
970 c.weighty = 0;
971 c.gridx = 0;
972 c.gridy = 0;
973 c.ipadx = 0;
974 c.ipady = 0;
975 c.gridwidth = 1;
976 c.gridheight = 1;
978 Display.this.all = new JPanel();
979 all.setBackground(Color.white);
980 all.setLayout(layout);
982 c.insets = new Insets(0, 0, 0, 5);
984 // 1
985 toolbar_panel = new ToolbarPanel(); // fixed dimensions
986 layout.setConstraints(toolbar_panel, c);
987 all.add(toolbar_panel);
989 // 2
990 c.gridy++;
991 c.fill = GridBagConstraints.HORIZONTAL;
992 layout.setConstraints(transp_slider, c);
993 all.add(transp_slider);
995 // 3
996 c.gridy++;
997 c.weighty = 1;
998 c.fill = GridBagConstraints.BOTH;
999 layout.setConstraints(tabs, c);
1000 all.add(tabs);
1002 // 4
1003 c.gridy++;
1004 c.weighty = 0;
1005 c.fill = GridBagConstraints.NONE;
1006 layout.setConstraints(navigator, c);
1007 all.add(navigator);
1009 // 5
1010 c.gridy++;
1011 c.fill = GridBagConstraints.HORIZONTAL;
1012 layout.setConstraints(scroller, c);
1013 all.add(scroller);
1015 // Canvas
1016 this.canvas = new DisplayCanvas(this, (int)Math.ceil(layer.getLayerWidth()), (int)Math.ceil(layer.getLayerHeight()));
1018 c.insets = new Insets(0, 0, 0, 0);
1019 c.fill = GridBagConstraints.BOTH;
1020 c.anchor = GridBagConstraints.NORTHWEST;
1021 c.gridx = 1;
1022 c.gridy = 0;
1023 c.gridheight = GridBagConstraints.REMAINDER;
1024 c.weightx = 1;
1025 c.weighty = 1;
1026 layout.setConstraints(Display.this.canvas, c);
1027 all.add(canvas);
1029 // prevent new Displays from screwing up if input is globally disabled
1030 if (!project.isInputEnabled()) Display.this.canvas.setReceivesInput(false);
1032 this.canvas.addComponentListener(canvas_size_listener);
1033 this.navigator.addMouseWheelListener(canvas);
1034 this.transp_slider.addKeyListener(canvas);
1036 // JFrame to show the split pane
1037 this.frame = ControlWindow.createJFrame(layer.toString());
1038 this.frame.setBackground(Color.white);
1039 this.frame.getContentPane().setBackground(Color.white);
1040 if (IJ.isMacintosh() && IJ.getInstance()!=null) {
1041 IJ.wait(10); // may be needed for Java 1.4 on OS X
1042 this.frame.setMenuBar(ij.Menus.getMenuBar());
1044 this.frame.addWindowListener(window_listener);
1045 //this.frame.addComponentListener(display_frame_listener);
1046 this.frame.getContentPane().add(all);
1048 if (null != props) {
1049 // restore canvas
1050 canvas.setup(mag, srcRect);
1051 // restore visibility of each channel
1052 final int cs = ((Integer)props[5]).intValue(); // aka c_alphas_state
1053 final int[] sel = new int[4];
1054 sel[0] = ((cs&0xff000000)>>24);
1055 sel[1] = ((cs&0xff0000)>>16);
1056 sel[2] = ((cs&0xff00)>>8);
1057 sel[3] = (cs&0xff);
1058 // restore channel alphas
1059 Display.this.c_alphas = ((Integer)props[4]).intValue();
1060 channels[0].setAlpha( (float)((c_alphas&0xff000000)>>24) / 255.0f , 0 != sel[0]);
1061 channels[1].setAlpha( (float)((c_alphas&0xff0000)>>16) / 255.0f , 0 != sel[1]);
1062 channels[2].setAlpha( (float)((c_alphas&0xff00)>>8) / 255.0f , 0 != sel[2]);
1063 channels[3].setAlpha( (float) (c_alphas&0xff) / 255.0f , 0 != sel[3]);
1064 // restore visibility in the working c_alphas
1065 Display.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);
1068 if (null != active && null != layer) {
1069 final Rectangle r = active.getBoundingBox();
1070 r.x -= r.width/2;
1071 r.y -= r.height/2;
1072 r.width += r.width;
1073 r.height += r.height;
1074 if (r.x < 0) r.x = 0;
1075 if (r.y < 0) r.y = 0;
1076 if (r.width > layer.getLayerWidth()) r.width = (int)layer.getLayerWidth();
1077 if (r.height> layer.getLayerHeight())r.height= (int)layer.getLayerHeight();
1078 final double magn = layer.getLayerWidth() / (double)r.width;
1079 canvas.setup(magn, r);
1082 // add keyListener to the whole frame
1083 this.tabs.addKeyListener(canvas);
1084 this.frame.addKeyListener(canvas);
1086 // create a drag and drop listener
1087 dnd = new DNDInsertImage(Display.this);
1089 Utils.invokeLater(new Runnable() {
1090 @Override
1091 public void run() {
1092 Display.this.frame.pack();
1093 ij.gui.GUI.center(Display.this.frame);
1094 Display.this.frame.setVisible(true);
1095 ProjectToolbar.setProjectToolbar(); // doesn't get it through events
1097 final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1099 if (null != props) {
1100 // fix positioning outside the screen (dual to single monitor)
1101 if (p.x >= 0 && p.x < screen.width - 50 && p.y >= 0 && p.y <= screen.height - 50) Display.this.frame.setLocation(p);
1102 else frame.setLocation(0, 0);
1105 // fix excessive size
1106 final Rectangle box = Display.this.frame.getBounds();
1107 int x = box.x;
1108 int y = box.y;
1109 int width = box.width;
1110 int height = box.height;
1111 if (box.width > screen.width) { x = 0; width = screen.width; }
1112 if (box.height > screen.height) { y = 0; height = screen.height; }
1113 if (x != box.x || y != box.y) {
1114 Display.this.frame.setLocation(x, y + (0 == y ? 30 : 0)); // added insets for bad window managers
1115 updateInDatabase("position");
1117 if (width != box.width || height != box.height) {
1118 Display.this.frame.setSize(new Dimension(width -10, height -30)); // added insets for bad window managers
1120 if (null == props) {
1121 // try to optimize canvas dimensions and magn
1122 double magn = layer.getLayerHeight() / screen.height;
1123 if (magn > 1.0) magn = 1.0;
1124 long size = 0;
1125 // limit magnification if appropriate
1126 for (final Displayable pa : layer.getDisplayables(Patch.class)) {
1127 final Rectangle ba = pa.getBoundingBox();
1128 size += (long)(ba.width * ba.height);
1130 if (size > 10000000) canvas.setInitialMagnification(0.25); // 10 Mb
1131 else {
1132 Display.this.frame.setSize(new Dimension((int)(screen.width * 0.66), (int)(screen.height * 0.66)));
1136 updateTab(panel_patches);
1138 // re-layout:
1139 tabs.validate();
1141 // Set the calibration of the FakeImagePlus to that of the LayerSet
1142 ((FakeImagePlus)canvas.getFakeImagePlus()).setCalibrationSuper(layer.getParent().getCalibrationCopy());
1144 updateFrameTitle(layer);
1145 // Set the FakeImagePlus as the current image
1146 setTempCurrentImage();
1148 // start a repainting thread
1149 if (null != props) {
1150 canvas.repaint(true); // repaint() is unreliable
1153 ControlWindow.setLookAndFeel();
1154 }});
1157 static public void repaintToolbar() {
1158 for (final Display d : al_displays) {
1159 if (null == d.toolbar_panel) continue; // not yet ready
1160 Utils.invokeLater(new Runnable() { @Override
1161 public void run() {
1162 d.toolbar_panel.repaint();
1163 }});
1167 private class ToolbarPanel extends JPanel implements MouseListener {
1168 private static final long serialVersionUID = 1L;
1169 Method drawButton;
1170 Field lineType;
1171 Field SIZE;
1172 Field OFFSET;
1173 Toolbar toolbar = Toolbar.getInstance();
1174 int size;
1175 //int offset;
1176 ToolbarPanel() {
1177 setBackground(Color.white);
1178 addMouseListener(this);
1179 try {
1180 drawButton = Toolbar.class.getDeclaredMethod("drawButton", Graphics.class, Integer.TYPE);
1181 drawButton.setAccessible(true);
1182 lineType = Toolbar.class.getDeclaredField("lineType");
1183 lineType.setAccessible(true);
1184 SIZE = Toolbar.class.getDeclaredField("SIZE");
1185 SIZE.setAccessible(true);
1186 OFFSET = Toolbar.class.getDeclaredField("OFFSET");
1187 OFFSET.setAccessible(true);
1188 size = ((Integer)SIZE.get(null)).intValue();
1189 //offset = ((Integer)OFFSET.get(null)).intValue();
1190 } catch (final Exception e) {
1191 IJError.print(e);
1193 // Magic cocktail:
1194 final Dimension dim = new Dimension(250, size+size);
1195 setMinimumSize(dim);
1196 setPreferredSize(dim);
1197 setMaximumSize(dim);
1199 @Override
1200 public void update(final Graphics g) { paint(g); }
1201 @Override
1202 public void paint(final Graphics gr) {
1203 try {
1204 // Either extend the heavy-weight Canvas, or use an image to paint to.
1205 // Otherwise, rearrangements of the layout while painting will result
1206 // in incorrectly positioned toolbar buttons.
1207 final BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
1208 final Graphics g = bi.getGraphics();
1209 g.setColor(Color.white);
1210 g.fillRect(0, 0, getWidth(), getHeight());
1211 int i = 0;
1212 for (; i<Toolbar.LINE; i++) {
1213 drawButton.invoke(toolbar, g, i);
1215 drawButton.invoke(toolbar, g, lineType.get(toolbar));
1216 for (; i<=Toolbar.TEXT; i++) {
1217 drawButton.invoke(toolbar, g, i);
1219 drawButton.invoke(toolbar, g, Toolbar.ANGLE);
1220 // newline
1221 final AffineTransform aff = new AffineTransform();
1222 aff.translate(-size*Toolbar.TEXT, size-1);
1223 ((Graphics2D)g).setTransform(aff);
1224 for (; i<18; i++) {
1225 drawButton.invoke(toolbar, g, i);
1227 gr.drawImage(bi, 0, 0, null);
1228 bi.flush();
1229 g.dispose();
1230 } catch (final Exception e) {
1231 IJError.print(e);
1235 // Fails: "origin not in parent's hierarchy" ... right.
1236 private void showPopup(String name, int x, int y) {
1237 try {
1238 Field f = Toolbar.getInstance().getClass().getDeclaredField(name);
1239 f.setAccessible(true);
1240 PopupMenu p = (PopupMenu) f.get(Toolbar.getInstance());
1241 p.show(this, x, y);
1242 } catch (Throwable t) {
1243 IJError.print(t);
1247 @Override
1248 public void mousePressed(final MouseEvent me) {
1249 int x = me.getX();
1250 int y = me.getY();
1251 if (y > size) {
1252 if (x > size * 7) return; // off limits
1253 x += size * 9;
1254 y -= size;
1255 } else {
1256 if (x > size * 9) return; // off limits
1259 if (Utils.isPopupTrigger(me)) {
1260 if (x >= size && x <= size * 2 && y >= 0 && y <= size) {
1261 showPopup("ovalPopup", x, y);
1262 return;
1263 } else if (x >= size * 4 && x <= size * 5 && y >= 0 && y <= size) {
1264 showPopup("linePopup", x, y);
1265 return;
1269 Toolbar.getInstance().mousePressed(new MouseEvent(toolbar, me.getID(), System.currentTimeMillis(), me.getModifiers(), x, y, me.getClickCount(), me.isPopupTrigger()));
1270 repaint();
1271 Display.toolChanged(ProjectToolbar.getToolId()); // should fire on its own but it does not (?) TODO
1273 @Override
1274 public void mouseReleased(final MouseEvent me) {}
1275 @Override
1276 public void mouseClicked(final MouseEvent me) {}
1277 @Override
1278 public void mouseEntered(final MouseEvent me) {}
1279 @Override
1280 public void mouseExited(final MouseEvent me) {}
1283 private JPanel makeTabPanel() {
1284 final JPanel panel = new JPanel();
1285 panel.setLayout(new GridBagLayout());
1286 return panel;
1289 private JScrollPane makeScrollPane(final Component c) {
1290 final JPanel p = new JPanel();
1291 final GridBagLayout gb = new GridBagLayout();
1292 p.setLayout(gb);
1294 final GridBagConstraints co = new GridBagConstraints();
1295 co.anchor = GridBagConstraints.NORTHWEST;
1296 co.fill = GridBagConstraints.HORIZONTAL;
1297 co.gridy = 0;
1298 co.weighty = 0;
1299 gb.setConstraints(c, co);
1300 p.add(c);
1302 final JPanel padding = new JPanel();
1303 padding.setPreferredSize(new Dimension(0,0));
1304 co.fill = GridBagConstraints.BOTH;
1305 co.gridy = 1;
1306 co.weighty = 1;
1307 gb.setConstraints(padding, co);
1308 p.add(padding);
1310 final JScrollPane jsp = new JScrollPane(p);
1311 jsp.setBackground(Color.white); // no effect
1312 jsp.getViewport().setBackground(Color.white); // no effect
1313 // adjust scrolling to use one DisplayablePanel as the minimal unit
1314 jsp.getVerticalScrollBar().setBlockIncrement(DisplayablePanel.HEIGHT); // clicking within the track
1315 jsp.getVerticalScrollBar().setUnitIncrement(DisplayablePanel.HEIGHT); // clicking on an arrow
1316 jsp.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
1317 jsp.setPreferredSize(new Dimension(250, 300));
1318 jsp.setMinimumSize(new Dimension(250, 300));
1319 return jsp;
1322 private void addGBRow(final Container container, final Component comp, final Component previous) {
1323 final GridBagLayout gb = (GridBagLayout) container.getLayout();
1324 GridBagConstraints c = null;
1325 if (null == previous) {
1326 c = new GridBagConstraints();
1327 c.anchor = GridBagConstraints.NORTHWEST;
1328 c.fill = GridBagConstraints.HORIZONTAL;
1329 c.gridy = 0;
1330 } else {
1331 c = gb.getConstraints(previous);
1332 c.gridy += 1;
1334 gb.setConstraints(comp, c);
1335 container.add(comp);
1338 private JPanel makeAnnotationsPanel(final JEditorPane ep, final JLabel label) {
1339 final JPanel p = new JPanel();
1340 final GridBagLayout gb = new GridBagLayout();
1341 final GridBagConstraints c = new GridBagConstraints();
1342 p.setLayout(gb);
1343 c.fill = GridBagConstraints.HORIZONTAL;
1344 c.anchor = GridBagConstraints.NORTHWEST;
1345 c.ipadx = 5;
1346 c.ipady = 5;
1347 c.gridx = 0;
1348 c.gridy = 0;
1349 c.weightx = 0;
1350 c.weighty = 0;
1351 final JLabel title = new JLabel("Annotate:");
1352 gb.setConstraints(title, c);
1353 p.add(title);
1354 c.gridy++;
1355 gb.setConstraints(label, c);
1356 p.add(label);
1357 c.weighty = 1;
1358 c.gridy++;
1359 c.fill = GridBagConstraints.BOTH;
1360 c.ipadx = 0;
1361 c.ipady = 0;
1362 final JScrollPane sp = new JScrollPane(ep, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
1363 sp.setPreferredSize(new Dimension(250, 300));
1364 sp.setMinimumSize(new Dimension(250, 300));
1365 gb.setConstraints(sp, c);
1366 p.add(sp);
1367 return p;
1370 public DisplayCanvas getCanvas() {
1371 return canvas;
1374 public synchronized void setLayer(final Layer new_layer) {
1375 setLayer(new_layer, false);
1378 private synchronized void setLayer(final Layer new_layer, final boolean bypass_checks) {
1379 if (!bypass_checks) {
1380 if (null == new_layer || new_layer == this.layer || new_layer.getParent() != this.layer.getParent()) return;
1383 final Layer current_layer = this.layer;
1385 if (!mode.canChangeLayer()) {
1386 Utils.invokeLater(new Runnable() { @Override
1387 public void run() {
1388 ((ScrollerModel)scroller.getModel()).setValueWithoutEvent(current_layer.getParent().indexOf(current_layer));
1389 }});
1390 return;
1393 // Set:
1394 this.layer = new_layer;
1396 // deselect all except ZDisplayables
1397 final ArrayList<Displayable> sel = selection.getSelected();
1398 final Displayable last_active = this.active;
1399 int sel_next = -1;
1400 for (final Iterator<Displayable> it = sel.iterator(); it.hasNext(); ) {
1401 final Displayable d = it.next();
1402 if (!(d instanceof ZDisplayable)) {
1403 it.remove();
1404 selection.remove(d);
1405 if (d == last_active && sel.size() > 0) {
1406 // set as active (by selecting it) the last one of the remaining, if any
1407 sel_next = sel.size()-1;
1411 if (-1 != sel_next && sel.size() > 0) select(sel.get(sel_next), true);
1413 Utils.invokeLater(new Runnable() { @Override
1414 public void run() {
1415 translateLayerColors(current_layer, new_layer);
1416 if (tabs.getSelectedComponent() == scroll_layers) {
1417 scrollToShow(scroll_layers, layer_panels.get(new_layer));
1419 // Below, will fire the event as well, and call stl.set(layer) which calls setLayer with the same layer, and returns.
1420 // But just scroller.getModel().setValue(int) will ALSO fire the event. So let it do the loop.
1421 final int index = Display.this.layer.getParent().indexOf(new_layer);
1422 if (scroller.getValue() != index) {
1423 ((ScrollerModel)scroller.getModel()).setValueWithoutEvent(index);
1426 /* // OBSOLETE
1427 // update the current Layer pointer in ZDisplayable objects
1428 for (Iterator it = layer.getParent().getZDisplayables().iterator(); it.hasNext(); ) {
1429 ((ZDisplayable)it.next()).setLayer(layer); // the active layer
1433 if (tabs.getSelectedComponent() == panel_zdispl) {
1434 // no need, doesn't change
1435 } else {
1436 updateVisibleDisplayableTab();
1439 updateFrameTitle(new_layer); // to show the new 'z'
1440 // select the Layer in the LayerTree
1441 project.select(new_layer); // does so in a separate thread
1442 // update active Displayable:
1444 // trigger repaints
1445 navigator.repaint(true);
1446 canvas.repaint(true);
1448 // repaint tabs (hard as hell)
1449 Utils.updateComponent(tabs);
1450 // @#$%^! The above works half the times, so explicit repaint as well:
1451 Component c = tabs.getSelectedComponent();
1452 if (null == c) {
1453 c = panel_patches;
1454 tabs.setSelectedComponent(panel_patches);
1456 Utils.updateComponent(c);
1458 // update the coloring in the ProjectTree
1459 project.getProjectTree().updateUILater();
1461 setTempCurrentImage();
1462 }});
1465 static public void updateVisibleTabs() {
1466 for (final Display d : al_displays) {
1467 d.updateVisibleDisplayableTab();
1470 static public void updateVisibleTabs(final Project p) {
1471 for (final Display d : al_displays) {
1472 if (d.project == p) d.updateVisibleDisplayableTab();
1476 /** Recreate the tab that is being shown. */
1477 private void updateVisibleDisplayableTab() {
1478 Utils.invokeLater(new Runnable() { @Override
1479 public void run() {
1480 final Component c = tabs.getSelectedComponent();
1481 if (c instanceof RollingPanel) {
1482 ((RollingPanel)c).updateList();
1484 }});
1487 private void setLayerLater(final Layer layer, final Displayable active) {
1488 if (null == layer) return;
1489 this.layer = layer;
1490 if (!ControlWindow.isGUIEnabled()) return;
1491 Utils.invokeLater(new Runnable() { @Override
1492 public void run() {
1493 navigator.repaint(true); // was not done when adding
1494 // Order matters: first set active
1495 setActive(active);
1496 final Container c = (Container)tabs.getSelectedComponent();
1497 if (c != panel_zdispl) updateTab(c);
1498 }});
1501 /** A class to listen to the transparency_slider of the DisplayablesSelectorWindow. */
1502 private class TransparencySliderListener extends MouseAdapter implements ChangeListener {
1504 @Override
1505 public void stateChanged(final ChangeEvent ce) {
1506 //change the transparency value of the current active displayable
1507 final float new_value = (float)((JSlider)ce.getSource()).getValue();
1508 setTransparency(new_value / 100.0f);
1511 @Override
1512 public void mouseReleased(final MouseEvent me) {
1513 // update navigator window
1514 navigator.repaint(true);
1518 /** Context-sensitive: to a Displayable, or to a channel. */
1519 private void setTransparency(final float value) {
1520 final Component scroll = tabs.getSelectedComponent();
1521 if (scroll == scroll_channels) {
1522 for (int i=0; i<4; i++) {
1523 if (channels[i].getBackground() == Color.cyan) {
1524 channels[i].setAlpha(value); // will call back and repaint the Display
1525 return;
1528 } else if (null != active) {
1529 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.
1530 selection.setAlpha(value);
1531 Display.repaint(active.getLayerSet(), active, active.getBoundingBox(), 5, false);
1536 public void setTransparencySlider(final float transp) {
1537 if (transp >= 0.0f && transp <= 1.0f) {
1538 // fire event
1539 Utils.invokeLater(new Runnable() { @Override
1540 public void run() {
1541 transp_slider.setValue((int)(transp * 100));
1542 }});
1546 /** Mark the canvas for updating the offscreen images if the given Displayable is NOT the active. */ // Used by the Displayable.setVisible for example.
1547 static public void setUpdateGraphics(final Layer layer, final Displayable displ) {
1548 for (final Display d : al_displays) {
1549 if (layer == d.layer && null != d.active && d.active != displ) {
1550 d.canvas.setUpdateGraphics(true);
1555 /** Flag the DisplayCanvas of Displays showing the given Layer to update their offscreen images.*/
1556 static public void setUpdateGraphics(final Layer layer, final boolean update) {
1557 for (final Display d : al_displays) {
1558 if (layer == d.layer) {
1559 d.canvas.setUpdateGraphics(update);
1564 /** Whether to update the offscreen images or not. */
1565 public void setUpdateGraphics(final boolean b) {
1566 canvas.setUpdateGraphics(b);
1569 /** Update the entire GUI:
1570 * 1 - The layer scroller
1571 * 2 - The visible tab panels
1572 * 3 - The toolbar
1573 * 4 - The navigator
1574 * 5 - The canvas
1576 static public void update() {
1577 for (final Display d : al_displays) {
1578 d.updateLayerScroller(d.layer);
1579 d.updateVisibleDisplayableTab();
1580 d.toolbar_panel.repaint();
1581 d.navigator.repaint(true);
1582 d.canvas.repaint(true);
1586 /** Find all Display instances that contain the layer and repaint them, in the Swing GUI thread. */
1587 static public void update(final Layer layer) {
1588 if (null == layer) return;
1589 for (final Display d : al_displays) {
1590 if (d.isShowing(layer)) {
1591 d.repaintAll();
1596 static public void update(final LayerSet set) {
1597 update(set, true);
1600 /** 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. */
1601 static public void update(final LayerSet set, final boolean update_canvas_dimensions) {
1602 if (null == set) return;
1603 for (final Display d : al_displays) {
1604 if (d.layer.getParent() == set) {
1605 d.updateSnapshots();
1606 if (update_canvas_dimensions) d.canvas.setDimensions(set.getLayerWidth(), set.getLayerHeight());
1607 d.repaintAll();
1612 /** Release all resources held by this Display and close the frame. */
1613 protected void destroy() {
1614 // Set a new front if any and remove from the list of open Displays
1615 removeDisplay(this);
1616 // Inactivate this Display:
1617 dispatcher.quit();
1618 canvas.setReceivesInput(false);
1619 slt.quit();
1620 imagePreloader.interrupt();
1622 // update the coloring in the ProjectTree and LayerTree
1623 if (!project.isBeingDestroyed()) {
1624 try {
1625 project.getProjectTree().updateUILater();
1626 project.getLayerTree().updateUILater();
1627 } catch (final Exception e) {
1628 Utils.log2("updateUI failed at Display.destroy()");
1632 //frame.removeComponentListener(component_listener);
1633 frame.removeWindowListener(window_listener);
1634 frame.removeWindowFocusListener(window_listener);
1635 frame.removeWindowStateListener(window_listener);
1636 frame.removeKeyListener(canvas);
1637 //frame.removeMouseListener(frame_mouse_listener);
1638 canvas.removeKeyListener(canvas);
1639 tabs.removeChangeListener(tabs_listener);
1640 tabs.removeKeyListener(canvas);
1641 IJ.removeEventListener(this);
1642 bytypelistener = null;
1643 canvas.destroy();
1644 navigator.destroy();
1645 scroller.removeAdjustmentListener(scroller_listener);
1646 frame.setVisible(false);
1647 //no need, and throws exception//frame.dispose();
1648 active = null;
1649 if (null != selection) selection.clear();
1651 // repaint layer tree (to update the label color)
1652 try {
1653 project.getLayerTree().updateUILater(); // works only after setting the front above
1654 } catch (final Exception e) {} // ignore swing sync bullshit when closing everything too fast
1655 // remove the drag and drop listener
1656 dnd.destroy();
1659 /** Find all Display instances that contain a Layer of the given project and close them without removing the Display entries from the database. */
1660 static synchronized public void close(final Project project) {
1661 final Display[] d = new Display[al_displays.size()];
1662 al_displays.toArray(d);
1663 for (int i=0; i<d.length; i++) {
1664 if (d[i].getProject() == project) {
1665 removeDisplay(d[i]);
1666 d[i].destroy();
1671 /** Find all Display instances that contain the layer and close them and remove the Display from the database. */
1672 static public void close(final Layer layer) {
1673 for (final Display d : al_displays) {
1674 if (d.isShowing(layer)) {
1675 d.remove(false); // calls destroy which calls removeDisplay
1680 /** Find all Display instances that are showing the layer and either move to the next or previous layer, or close it if none. */
1681 static public void remove(final Layer layer) {
1682 for (final Display d : al_displays) {
1683 if (d.isShowing(layer)) {
1684 Layer la = layer.getParent().next(layer);
1685 if (layer == la || null == la) la = layer.getParent().previous(layer);
1686 if (null == la || layer == la) {
1687 d.remove(false); // will call destroy which calls removeDisplay
1688 } else {
1689 d.slt.set(la);
1695 /** Close this Display window. */
1696 @Override
1697 public boolean remove(final boolean check) {
1698 if (check) {
1699 if (!Utils.check("Delete the Display ?")) return false;
1701 // flush the offscreen images and close the frame
1702 destroy();
1703 removeFromDatabase();
1704 return true;
1707 public Layer getLayer() {
1708 return layer;
1711 public LayerSet getLayerSet() {
1712 return layer.getParent();
1715 public boolean isShowing(final Layer layer) {
1716 return this.layer == layer;
1719 public DisplayNavigator getNavigator() {
1720 return navigator;
1723 /** Repaint both the canvas and the navigator, updating the graphics, and the title and tabs. */
1724 public void repaintAll() {
1725 if (repaint_disabled) return;
1726 navigator.repaint(true);
1727 canvas.repaint(true);
1728 Utils.updateComponent(tabs);
1729 updateFrameTitle();
1732 /** Repaint the canvas updating graphics, the navigator without updating graphics, and the title. */
1733 public void repaintAll2() {
1734 if (repaint_disabled) return;
1735 navigator.repaint(false);
1736 canvas.repaint(true);
1737 updateFrameTitle();
1740 /** Repaint the canvas updating graphics, and the navigator without updating graphics. */
1741 public void repaintAll3() {
1742 if (repaint_disabled) return;
1743 navigator.repaint(false);
1744 canvas.repaint(true);
1745 updateFrameTitle();
1748 static protected void repaintSnapshots(final LayerSet set) {
1749 if (repaint_disabled) return;
1750 for (final Display d : al_displays) {
1751 if (d.getLayer().getParent() == set) {
1752 d.navigator.repaint(true);
1753 Utils.updateComponent(d.tabs);
1757 static protected void repaintSnapshots(final Layer layer) {
1758 if (repaint_disabled) return;
1759 for (final Display d : al_displays) {
1760 if (d.getLayer() == layer) {
1761 d.navigator.repaint(true);
1762 Utils.updateComponent(d.tabs);
1767 public void pack() {
1768 Utils.invokeLater(new Runnable() { @Override
1769 public void run() {
1770 frame.pack();
1771 navigator.repaint(false); // update srcRect red frame position/size
1772 }});
1775 static public void pack(final LayerSet ls) {
1776 for (final Display d : al_displays) {
1777 if (d.layer.getParent() == ls) d.pack();
1781 /** Grab the last selected display (or create an new one if none) and show in it the layer,centered on the Displayable object. */
1782 static public void setFront(final Layer layer, final Displayable displ) {
1783 if (null == front) {
1784 final Display display = new Display(layer.getProject(), layer); // gets set to front
1785 display.showCentered(displ);
1786 } else if (layer == front.layer) {
1787 front.showCentered(displ);
1788 } else {
1789 // find one:
1790 for (final Display d : al_displays) {
1791 if (d.layer == layer) {
1792 d.frame.toFront();
1793 d.showCentered(displ);
1794 return;
1797 // else, open new one
1798 new Display(layer.getProject(), layer).showCentered(displ);
1802 /** 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. */
1803 static protected void add(final Layer layer, final Displayable displ, final boolean activate) {
1804 for (final Display d : al_displays) {
1805 if (d.layer == layer) {
1806 if (front == d) {
1807 d.add(displ, activate, true);
1808 //front.frame.toFront();
1809 } else {
1810 d.add(displ, false, true);
1816 static protected void add(final Layer layer, final Displayable displ) {
1817 add(layer, displ, true);
1820 /** Add the ZDisplayable to all Displays that show a Layer belonging to the given LayerSet. */
1821 static protected void add(final LayerSet set, final ZDisplayable zdispl) {
1822 for (final Display d : al_displays) {
1823 if (d.layer.getParent() == set) {
1824 if (front == d) {
1825 zdispl.setLayer(d.layer); // the active one
1826 d.add(zdispl, true, true); // calling add(Displayable, boolean, boolean)
1827 //front.frame.toFront();
1828 } else {
1829 d.add(zdispl, false, true);
1835 static protected void addAll(final Layer layer, final Collection<? extends Displayable> coll) {
1836 for (final Display d : al_displays) {
1837 if (d.layer == layer) {
1838 d.addAll(coll);
1843 static protected void addAll(final LayerSet set, final Collection<? extends ZDisplayable> coll) {
1844 for (final Display d : al_displays) {
1845 if (d.layer.getParent() == set) {
1846 for (final ZDisplayable zd : coll) {
1847 if (front == d) zd.setLayer(d.layer); // this is obsolete now, TODO
1849 d.addAll(coll);
1854 private final void addAll(final Collection<? extends Displayable> coll) {
1855 // if any of the elements in the collection matches the type of the current tab, update that tab
1856 // ... it's easier to just update the front tab
1857 updateVisibleDisplayableTab();
1858 selection.clear();
1859 navigator.repaint(true);
1862 /** Add it to the proper panel, at the top, and set it active. */
1863 private final void add(final Displayable d, final boolean activate, final boolean repaint_snapshot) {
1864 if (activate) {
1865 updateVisibleDisplayableTab();
1866 selection.clear();
1867 selection.add(d);
1868 Display.repaint(d.getLayerSet()); // update the al_top list to contain the active one, or background image for a new Patch.
1869 Utils.log2("Added " + d);
1871 if (repaint_snapshot) navigator.repaint(true);
1874 /** Find the displays that show the given Layer, and remove the given Displayable from the GUI. */
1875 static public void remove(final Layer layer, final Displayable displ) {
1876 for (final Display d : al_displays) {
1877 if (layer == d.layer) d.remove(displ);
1881 private void remove(final Displayable displ) {
1882 final DisplayablePanel ob = ht_panels.remove(displ);
1883 if (null != ob) {
1884 final RollingPanel rp = ht_tabs.get(displ.getClass());
1885 if (null != rp) {
1886 Utils.invokeLater(new Runnable() { @Override
1887 public void run() {
1888 rp.updateList();
1889 }});
1892 canvas.setUpdateGraphics(true);
1893 repaint(displ, null, 5, true, false);
1894 // from Selection.deleteAll this method is called ... but it's ok: same thread, no locking problems.
1895 selection.remove(displ);
1898 static public void remove(final ZDisplayable zdispl) {
1899 for (final Display d : al_displays) {
1900 if (zdispl.getLayerSet() == d.layer.getParent()) {
1901 d.remove((Displayable)zdispl);
1906 static public void repaint(final Layer layer, final Displayable displ, final int extra) {
1907 repaint(layer, displ, displ.getBoundingBox(), extra);
1910 static public void repaint(final Layer layer, final Displayable displ, final Rectangle r, final int extra) {
1911 repaint(layer, displ, r, extra, true);
1914 /** Find the displays that show the given Layer, and repaint the given Displayable; does NOT update graphics for the offscreen image. */
1915 static public void repaint(final Layer layer, final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator) {
1916 repaint(layer, displ, r, extra, false, repaint_navigator);
1920 * @param layer The layer to repaint
1921 * @param r The Rectangle to repaint, in world coordinates (aka pixel coordinates or canvas coordinates).
1922 * @param extra The number of pixels to pad @param r with.
1923 * @param update_graphics Whether to recreate the offscreen image of the canvas, which is necessary for images.
1924 * @param repaint_navigator Whether to repaint the navigator.
1926 static public void repaint(final Layer layer, final Displayable displ, final Rectangle r, final int extra, final boolean update_graphics, final boolean repaint_navigator) {
1927 if (repaint_disabled) return;
1928 for (final Display d : al_displays) {
1929 if (layer == d.layer) {
1930 d.repaint(displ, r, extra, repaint_navigator, update_graphics);
1935 static public void repaint(final Displayable d) {
1936 if (d instanceof ZDisplayable) repaint(d.getLayerSet(), d, d.getBoundingBox(null), 5, true);
1937 repaint(d.getLayer(), d, d.getBoundingBox(null), 5, true);
1940 /** Repaint as much as the bounding box around the given Displayable, or the r if not null.
1941 * @param update_graphics will be made true if the @param displ is a Patch or it's not the active Displayable. */
1942 private void repaint(final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator, boolean update_graphics) {
1943 if (repaint_disabled || null == displ) return;
1944 update_graphics = (update_graphics || displ.getClass() == Patch.class || displ != active);
1945 if (null != r) canvas.repaint(r, extra, update_graphics);
1946 else canvas.repaint(displ, extra, update_graphics);
1947 if (repaint_navigator) {
1948 final DisplayablePanel dp = ht_panels.get(displ);
1949 if (null != dp) {
1950 Utils.invokeLater(new Runnable() { @Override
1951 public void run() {
1952 dp.repaint(); // is null when creating it, or after deleting it
1953 }});
1955 navigator.repaint(true); // everything
1959 /** 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. */
1960 static public void repaintSnapshot(final Displayable displ) {
1961 for (final Display d : al_displays) {
1962 if (d.layer.contains(displ)) {
1963 if (!d.navigator.isPainted(displ)) {
1964 final DisplayablePanel dp = d.ht_panels.get(displ);
1965 if (null != dp) dp.repaint(); // is null when creating it, or after deleting it
1966 d.navigator.repaint(displ);
1972 /** Repaint the given Rectangle in all Displays showing the layer, updating the offscreen image if any. */
1973 static public void repaint(final Layer layer, final Rectangle r, final int extra) {
1974 repaint(layer, extra, r, true, true);
1977 static public void repaint(final Layer layer, final int extra, final Rectangle r, final boolean update_navigator) {
1978 repaint(layer, extra, r, update_navigator, true);
1981 static public void repaint(final Layer layer, final int extra, final Rectangle r, final boolean update_navigator, final boolean update_graphics) {
1982 if (repaint_disabled) return;
1983 for (final Display d : al_displays) {
1984 if (layer == d.layer) {
1985 d.canvas.setUpdateGraphics(update_graphics);
1986 d.canvas.repaint(r, extra);
1987 if (update_navigator) {
1988 d.navigator.repaint(true);
1989 Utils.updateComponent(d.tabs.getSelectedComponent());
1996 /** Repaint the given Rectangle in all Displays showing the layer, optionally updating the offscreen image (if any). */
1997 static public void repaint(final Layer layer, final Rectangle r, final int extra, final boolean update_graphics) {
1998 if (repaint_disabled) return;
1999 for (final Display d : al_displays) {
2000 if (layer == d.layer) {
2001 d.canvas.setUpdateGraphics(update_graphics);
2002 d.canvas.repaint(r, extra);
2003 d.navigator.repaint(update_graphics);
2004 if (update_graphics) Utils.updateComponent(d.tabs.getSelectedComponent());
2009 /** Repaint the DisplayablePanel (and DisplayNavigator) only for the given Displayable, in all Displays showing the given Layer. */
2010 static public void repaint(final Layer layer, final Displayable displ) {
2011 if (repaint_disabled) return;
2012 for (final Display d : al_displays) {
2013 if (layer == d.layer) {
2014 final DisplayablePanel dp = d.ht_panels.get(displ);
2015 if (null != dp) dp.repaint();
2016 d.navigator.repaint(true);
2021 static public void repaint(final LayerSet set, final Displayable displ, final int extra) {
2022 repaint(set, displ, null, extra);
2025 static public void repaint(final LayerSet set, final Displayable displ, final Rectangle r, final int extra) {
2026 repaint(set, displ, r, extra, true);
2029 /** Repaint the Displayable in every Display that shows a Layer belonging to the given LayerSet. */
2030 static public void repaint(final LayerSet set, final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator) {
2031 if (repaint_disabled) return;
2032 if (null == set) return;
2033 for (final Display d : al_displays) {
2034 if (d.layer.getParent() == set) {
2035 if (repaint_navigator) {
2036 if (null != displ) {
2037 final DisplayablePanel dp = d.ht_panels.get(displ);
2038 if (null != dp) dp.repaint();
2040 d.navigator.repaint(true);
2042 if (null == displ || displ != d.active || displ instanceof ImageData) d.setUpdateGraphics(true); // safeguard
2043 // paint the given box or the actual Displayable's box
2044 if (null != r) d.canvas.repaint(r, extra);
2045 else d.canvas.repaint(displ, extra);
2050 /** Repaint the entire LayerSet, in all Displays showing a Layer of it.*/
2051 static public void repaint(final LayerSet set) {
2052 if (repaint_disabled) return;
2053 for (final Display d : al_displays) {
2054 if (d.layer.getParent() == set) {
2055 d.navigator.repaint(true);
2056 d.canvas.repaint(true);
2060 /** Repaint the given box in the LayerSet, in all Displays showing a Layer of it.*/
2061 static public void repaint(final LayerSet set, final Rectangle box) {
2062 if (repaint_disabled) return;
2063 for (final Display d : al_displays) {
2064 if (d.layer.getParent() == set) {
2065 d.navigator.repaint(box);
2066 d.canvas.repaint(box, 0, true);
2070 /** Repaint the entire Layer, in all Displays showing it, including the tabs.*/
2071 static public void repaint(final Layer layer) { // TODO this method overlaps with update(layer)
2072 if (repaint_disabled) return;
2073 for (final Display d : al_displays) {
2074 if (layer == d.layer) {
2075 d.navigator.repaint(true);
2076 d.canvas.repaint(true);
2081 /** Call repaint on all open Displays. */
2082 static public void repaint() {
2083 if (repaint_disabled) {
2084 Utils.logAll("Can't repaint -- repainting is disabled!");
2085 return;
2087 for (final Display d : al_displays) {
2088 d.navigator.repaint(true);
2089 d.canvas.repaint(true);
2093 static private boolean repaint_disabled = false;
2095 /** Set a flag to enable/disable repainting of all Display instances. */
2096 static protected void setRepaint(final boolean b) {
2097 repaint_disabled = !b;
2100 public Rectangle getBounds() {
2101 return frame.getBounds();
2104 public Point getLocation() {
2105 return frame.getLocation();
2108 public JFrame getFrame() {
2109 return frame;
2112 /** Feel free to add more tabs. Don't remove any of the existing tabs or the sky will fall on your head. */
2113 public JTabbedPane getTabbedPane() {
2114 return tabs;
2117 /** Returns the tab index in this Display's JTabbedPane. */
2118 public int addTab(final String title, final Component comp) {
2119 this.tabs.add(title, comp);
2120 return this.tabs.getTabCount() -1;
2123 public void setLocation(final Point p) {
2124 this.frame.setLocation(p);
2127 public Displayable getActive() {
2128 return active; //TODO this should return selection.active !!
2131 public void select(final Displayable d) {
2132 select(d, false);
2135 /** Select/deselect accordingly to the current state and the shift key. */
2136 public void select(final Displayable d, final boolean shift_down) {
2137 if (null != active && active != d && active.getClass() != Patch.class) {
2138 // active is being deselected, so link underlying patches
2139 final String prop = active.getClass() == DLabel.class ? project.getProperty("label_nolinks")
2140 : project.getProperty("segmentations_nolinks");
2141 HashSet<Displayable> glinked = null;
2142 if (null != prop && prop.equals("true")) {
2143 // do nothing: linking disabled for active's type
2144 } else if (active.linkPatches()) {
2145 // Locking state changed:
2146 glinked = active.getLinkedGroup(null);
2147 Display.updateCheckboxes(glinked, DisplayablePanel.LOCK_STATE, true);
2149 // Update link icons:
2150 Display.updateCheckboxes(null == glinked ? active.getLinkedGroup(null) : glinked, DisplayablePanel.LINK_STATE);
2152 if (null == d) {
2153 //Utils.log2("Display.select: clearing selection");
2154 canvas.setUpdateGraphics(true);
2155 selection.clear();
2156 return;
2158 if (!shift_down) {
2159 //Utils.log2("Display.select: single selection");
2160 if (d != active) {
2161 selection.clear();
2162 selection.add(d);
2164 } else if (selection.contains(d)) {
2165 if (active == d) {
2166 selection.remove(d);
2167 //Utils.log2("Display.select: removing from a selection");
2168 } else {
2169 //Utils.log2("Display.select: activating within a selection");
2170 selection.setActive(d);
2172 } else {
2173 //Utils.log2("Display.select: adding to an existing selection");
2174 selection.add(d);
2176 // update the image shown to ImageJ
2177 // NO longer necessary, always he same FakeImagePlus // setTempCurrentImage();
2180 protected void choose(final int screen_x_p, final int screen_y_p, final int x_p, final int y_p, final Class<?> c) {
2181 choose(screen_x_p, screen_y_p, x_p, y_p, false, c);
2183 protected void choose(final int screen_x_p, final int screen_y_p, final int x_p, final int y_p) {
2184 choose(screen_x_p, screen_y_p, x_p, y_p, false, null);
2187 /** 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. */
2188 protected void choose(final int screen_x_p, final int screen_y_p, final int x_p, final int y_p, final boolean shift_down, final Class<?> c) {
2189 //Utils.log("Display.choose: x,y " + x_p + "," + y_p);
2190 final ArrayList<Displayable> al = new ArrayList<Displayable>(layer.find(x_p, y_p, true));
2191 al.addAll(layer.getParent().findZDisplayables(layer, x_p, y_p, true)); // only visible ones
2192 if (al.isEmpty()) {
2193 final Displayable act = this.active;
2194 selection.clear();
2195 canvas.setUpdateGraphics(true);
2196 //Utils.log("choose: set active to null");
2197 // fixing lack of repainting for unknown reasons, of the active one TODO this is a temporary solution
2198 if (null != act) Display.repaint(layer, act, 5);
2199 } else if (1 == al.size()) {
2200 final Displayable d = (Displayable)al.get(0);
2201 if (null != c && d.getClass() != c) {
2202 selection.clear();
2203 return;
2205 select(d, shift_down);
2206 //Utils.log("choose 1: set active to " + active);
2207 } else {
2208 if (al.contains(active) && !shift_down) {
2209 // do nothing
2210 } else {
2211 if (null != c) {
2212 // check if at least one of them is of class c
2213 // if only one is of class c, set as selected
2214 // else show menu
2215 for (final Iterator<?> it = al.iterator(); it.hasNext(); ) {
2216 final Object ob = it.next();
2217 if (ob.getClass() != c) it.remove();
2219 if (0 == al.size()) {
2220 // deselect
2221 selection.clear();
2222 return;
2224 if (1 == al.size()) {
2225 select((Displayable)al.get(0), shift_down);
2226 return;
2228 // else, choose among the many
2230 choose(screen_x_p, screen_y_p, al, shift_down, x_p, y_p);
2232 //Utils.log("choose many: set active to " + active);
2236 private void choose(final int screen_x_p, final int screen_y_p, final Collection<Displayable> al, final boolean shift_down, final int x_p, final int y_p) {
2237 // show a popup on the canvas to choose
2238 new Thread() {
2239 { setPriority(Thread.NORM_PRIORITY); }
2240 @Override
2241 public void run() {
2242 final Object lock = new Object();
2243 final DisplayableChooser d_chooser = new DisplayableChooser(al, lock);
2244 final JPopupMenu pop = new JPopupMenu("Select:");
2245 for (final Displayable d : al) {
2246 final JMenuItem menu_item = new JMenuItem(d.toString());
2247 menu_item.addActionListener(d_chooser);
2248 pop.add(menu_item);
2251 SwingUtilities.invokeLater(new Runnable() { @Override
2252 public void run() {
2253 pop.show(canvas, screen_x_p, screen_y_p);
2254 }});
2256 //now wait until selecting something
2257 synchronized(lock) {
2258 do {
2259 try {
2260 lock.wait();
2261 } catch (final InterruptedException ie) {}
2262 } while (d_chooser.isWaiting() && pop.isShowing());
2265 //grab the chosen Displayable object
2266 final Displayable d = d_chooser.getChosen();
2267 //Utils.log("Chosen: " + d.toString());
2268 if (null == d) { Utils.log2("Display.choose: returning a null!"); }
2269 select(d, shift_down);
2270 pop.setVisible(false);
2272 // fix selection bug: never receives mouseReleased event when the popup shows
2273 getMode().mouseReleased(null, x_p, y_p, x_p, y_p, x_p, y_p);
2275 }.start();
2278 /** 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. */
2279 protected void setActive(final Displayable displ) {
2280 final Displayable prev_active = this.active;
2281 this.active = displ;
2282 Utils.invokeLater(new Runnable() { @Override
2283 public void run() {
2284 if (null != displ && displ == prev_active && tabs.getSelectedComponent() != annot_panel) {
2285 // make sure the proper tab is selected.
2286 selectTab(displ);
2287 return; // the same
2289 // deactivate previously active
2290 if (null != prev_active) {
2291 // erase "decorations" of the previously active
2292 canvas.repaint(selection.getBox(), 4);
2293 // Adjust annotation doc
2294 synchronized (annot_docs) {
2295 boolean remove_doc = true;
2296 for (final Display d : al_displays) {
2297 if (prev_active == d.active) {
2298 remove_doc = false;
2299 break;
2302 if (remove_doc) annot_docs.remove(prev_active);
2305 // activate the new active
2306 if (null != displ) {
2307 updateInDatabase("active_displayable_id");
2308 if (displ.getClass() != Patch.class) project.select(displ); // select the node in the corresponding tree, if any.
2309 // select the proper tab, and scroll to visible
2310 if (tabs.getSelectedComponent() != annot_panel) { // don't swap tab if its the annotation one
2311 selectTab(displ);
2313 final 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
2314 repaint(displ, null, 5, false, update_graphics); // to show the border, and to repaint out of the background image
2315 transp_slider.setValue((int)(displ.getAlpha() * 100));
2316 // Adjust annotation tab:
2317 synchronized (annot_docs) {
2318 annot_label.setText(displ.toString());
2319 Document doc = annot_docs.get(displ); // could be open in another Display
2320 if (null == doc) {
2321 doc = annot_editor.getEditorKit().createDefaultDocument();
2322 doc.addDocumentListener(new DocumentListener() {
2323 @Override
2324 public void changedUpdate(final DocumentEvent e) {}
2325 @Override
2326 public void insertUpdate(final DocumentEvent e) { push(); }
2327 @Override
2328 public void removeUpdate(final DocumentEvent e) { push(); }
2329 private void push() {
2330 displ.setAnnotation(annot_editor.getText());
2333 annot_docs.put(displ, doc);
2335 annot_editor.setDocument(doc);
2336 if (null != displ.getAnnotation()) annot_editor.setText(displ.getAnnotation());
2338 annot_editor.setEnabled(true);
2339 } else {
2340 //ensure decorations are removed from the panels, for Displayables in a selection besides the active one
2341 Utils.updateComponent(tabs.getSelectedComponent());
2342 annot_label.setText("(No selected object)");
2343 annot_editor.setDocument(annot_editor.getEditorKit().createDefaultDocument()); // a clear, empty one
2344 annot_editor.setEnabled(false);
2346 }});
2349 /** If the other paints under the base. */
2350 public boolean paintsBelow(final Displayable base, final Displayable other) {
2351 final boolean zd_base = base instanceof ZDisplayable;
2352 final boolean zd_other = other instanceof ZDisplayable;
2353 if (zd_other) {
2354 if (base instanceof DLabel) return true; // zd paints under label
2355 if (!zd_base) return false; // any zd paints over a mere displ if not a label
2356 else {
2357 // both zd, compare indices
2358 final ArrayList<ZDisplayable> al = other.getLayerSet().getZDisplayables();
2359 return al.indexOf(base) > al.indexOf(other);
2361 } else {
2362 if (!zd_base) {
2363 // both displ, compare indices
2364 final ArrayList<Displayable> al = other.getLayer().getDisplayables();
2365 return al.indexOf(base) > al.indexOf(other);
2366 } else {
2367 // base is zd, other is d
2368 if (other instanceof DLabel) return false;
2369 return true;
2374 /** 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. */
2375 private void selectTab(final Displayable displ) {
2376 Method method = null;
2377 try {
2378 if (!(displ instanceof LayerSet)) {
2379 method = Display.class.getDeclaredMethod("selectTab", new Class[]{displ.getClass()});
2381 } catch (final Exception e) {
2382 IJError.print(e);
2384 if (null != method) {
2385 final Method me = method;
2386 dispatcher.exec(new Runnable() { @Override
2387 public void run() {
2388 try {
2389 me.setAccessible(true);
2390 me.invoke(Display.this, new Object[]{displ});
2391 } catch (final Exception e) { IJError.print(e); }
2392 }});
2396 // Methods used by reflection:
2398 @SuppressWarnings("unused")
2399 private void selectTab(final Patch patch) {
2400 tabs.setSelectedComponent(panel_patches);
2401 panel_patches.scrollToShow(patch);
2404 @SuppressWarnings("unused")
2405 private void selectTab(final Profile profile) {
2406 tabs.setSelectedComponent(panel_profiles);
2407 panel_profiles.scrollToShow(profile);
2410 @SuppressWarnings("unused")
2411 private void selectTab(final DLabel label) {
2412 tabs.setSelectedComponent(panel_labels);
2413 panel_labels.scrollToShow(label);
2416 private void selectTab(final ZDisplayable zd) {
2417 tabs.setSelectedComponent(panel_zdispl);
2418 panel_zdispl.scrollToShow(zd);
2421 @SuppressWarnings("unused")
2422 private void selectTab(final Pipe d) { selectTab((ZDisplayable)d); }
2423 @SuppressWarnings("unused")
2424 private void selectTab(final Polyline d) { selectTab((ZDisplayable)d); }
2425 @SuppressWarnings("unused")
2426 private void selectTab(final Treeline d) { selectTab((ZDisplayable)d); }
2427 @SuppressWarnings("unused")
2428 private void selectTab(final AreaTree d) { selectTab((ZDisplayable)d); }
2429 @SuppressWarnings("unused")
2430 private void selectTab(final Connector d) { selectTab((ZDisplayable)d); }
2431 @SuppressWarnings("unused")
2432 private void selectTab(final AreaList d) { selectTab((ZDisplayable)d); }
2433 @SuppressWarnings("unused")
2434 private void selectTab(final Ball d) { selectTab((ZDisplayable)d); }
2435 @SuppressWarnings("unused")
2436 private void selectTab(final Dissector d) { selectTab((ZDisplayable)d); }
2437 @SuppressWarnings("unused")
2438 private void selectTab(final Stack d) { selectTab((ZDisplayable)d); }
2440 /** Must be invoked in the event dispatch thread. */
2441 private void updateTab(final Container tab) {
2442 if (tab instanceof RollingPanel) {
2443 ((RollingPanel)tab).updateList();
2447 static public void setActive(final Object event, final Displayable displ) {
2448 if (!(event instanceof InputEvent)) return;
2449 // find which Display
2450 for (final Display d : al_displays) {
2451 if (d.isOrigin((InputEvent)event)) {
2452 d.setActive(displ);
2453 break;
2458 /** Find out whether this Display is Transforming its active Displayable. */
2459 public boolean isTransforming() {
2460 return canvas.isTransforming();
2463 /** Find whether any Display is transforming the given Displayable. */
2464 static public boolean isTransforming(final Displayable displ) {
2465 for (final Display d : al_displays) {
2466 if (null != d.active && d.active == displ && d.canvas.isTransforming()) return true;
2468 return false;
2471 /** Check whether the source of the event is located in this instance.*/
2472 private boolean isOrigin(final InputEvent event) {
2473 final Object source = event.getSource();
2474 // find it ... check the canvas for now TODO
2475 if (canvas == source) {
2476 return true;
2478 return false;
2481 /** Get the layer of the front Display, or null if none.*/
2482 static public Layer getFrontLayer() {
2483 final Display d = front;
2484 if (null == d) return null;
2485 return d.layer;
2488 /** Get the layer of an open Display of the given Project, or null if none.*/
2489 static public Layer getFrontLayer(final Project project) {
2490 final Display df = front;
2491 if (null == df) return null;
2492 if (df.project == project) return df.layer;
2494 // else, find an open Display for the given Project, if any
2495 for (final Display d : al_displays) {
2496 if (d.project == project) {
2497 d.frame.toFront();
2498 return d.layer;
2501 return null; // none found
2504 /** Get a pointer to a Display for @param project, or null if none. */
2505 static public Display getFront(final Project project) {
2506 final Display df = front;
2507 if (null == df) return null;
2508 if (df.project == project) return df;
2509 for (final Display d : al_displays) {
2510 if (d.project == project) {
2511 d.frame.toFront();
2512 return d;
2515 return null;
2518 /** Return the list of selected Displayable objects of the front Display, or an emtpy list if no Display or none selected. */
2519 static public List<Displayable> getSelected() {
2520 final Display d = front;
2521 if (null == d) return new ArrayList<Displayable>();
2522 return d.selection.getSelected();
2524 /** Return the list of selected Displayable objects of class @param c of the front Display, or an emtpy list if no Display or none selected. */
2525 static public List<? extends Displayable> getSelected(final Class<? extends Displayable> c) {
2526 final Display d = front;
2527 if (null == d) return new ArrayList<Displayable>();
2528 return d.selection.getSelected(c);
2531 public boolean isReadOnly() {
2532 // TEMPORARY: in the future one will be able show displays as read-only to other people, remotely
2533 return false;
2536 protected void showPopup(final Component c, final int x, final int y) {
2537 final Display d = front;
2538 if (null == d) return;
2539 d.getPopupMenu().show(c, x, y);
2542 /** Return a context-sensitive popup menu. */
2543 protected JPopupMenu getPopupMenu() { // called from canvas
2544 // get the job canceling dialog
2545 if (!canvas.isInputEnabled()) {
2546 return project.getLoader().getJobsPopup(this);
2549 // create new
2550 this.popup = new JPopupMenu();
2551 JMenuItem item = null;
2552 JMenu menu = null;
2554 if (mode instanceof InspectPatchTrianglesMode) {
2555 item = new JMenuItem("Exit inspection"); item.addActionListener(this); popup.add(item);
2556 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2557 return popup;
2558 } else if (canvas.isTransforming()) {
2559 item = new JMenuItem("Apply transform"); item.addActionListener(this); popup.add(item);
2560 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
2561 item = new JMenuItem("Apply transform propagating to last layer"); item.addActionListener(this); popup.add(item);
2562 if (layer.getParent().indexOf(layer) == layer.getParent().size() -1) item.setEnabled(false);
2563 if (!(getMode().getClass() == AffineTransformMode.class || getMode().getClass() == NonLinearTransformMode.class)) item.setEnabled(false);
2564 item = new JMenuItem("Apply transform propagating to first layer"); item.addActionListener(this); popup.add(item);
2565 if (0 == layer.getParent().indexOf(layer)) item.setEnabled(false);
2566 if (!(getMode().getClass() == AffineTransformMode.class || getMode().getClass() == NonLinearTransformMode.class)) item.setEnabled(false);
2567 item = new JMenuItem("Cancel transform"); item.addActionListener(this); popup.add(item);
2568 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2569 item = new JMenuItem("Specify transform..."); item.addActionListener(this); popup.add(item);
2570 if (getMode().getClass() != AffineTransformMode.class) item.setEnabled(false);
2571 if (getMode().getClass() == ManualAlignMode.class) {
2572 final JMenuItem lexport = new JMenuItem("Export landmarks"); popup.add(lexport);
2573 final JMenuItem limport = new JMenuItem("Import landmarks"); popup.add(limport);
2574 final ActionListener a = new ActionListener() {
2575 @Override
2576 public void actionPerformed(final ActionEvent ae) {
2577 final ManualAlignMode mam = (ManualAlignMode)getMode();
2578 final Object source = ae.getSource();
2579 if (lexport == source) {
2580 mam.exportLandmarks();
2581 } else if (limport == source) {
2582 mam.importLandmarks();
2586 lexport.addActionListener(a);
2587 limport.addActionListener(a);
2589 return popup;
2592 final Class<?> aclass = null == active ? null : active.getClass();
2594 if (null != active) {
2595 if (Profile.class == aclass) {
2596 item = new JMenuItem("Duplicate, link and send to next layer"); item.addActionListener(this); popup.add(item);
2597 Layer nl = layer.getParent().next(layer);
2598 if (nl == layer) item.setEnabled(false);
2599 item = new JMenuItem("Duplicate, link and send to previous layer"); item.addActionListener(this); popup.add(item);
2600 nl = layer.getParent().previous(layer);
2601 if (nl == layer) item.setEnabled(false);
2603 menu = new JMenu("Duplicate, link and send to");
2604 int i = 1;
2605 for (final Layer la : layer.getParent().getLayers()) {
2606 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
2607 if (la == this.layer) item.setEnabled(false);
2608 i++;
2610 popup.add(menu);
2611 item = new JMenuItem("Duplicate, link and send to..."); item.addActionListener(this); popup.add(item);
2613 popup.addSeparator();
2615 item = new JMenuItem("Unlink from images"); item.addActionListener(this); popup.add(item);
2616 if (!active.isLinked()) item.setEnabled(false); // isLinked() checks if it's linked to a Patch in its own layer
2617 item = new JMenuItem("Show in 3D"); item.addActionListener(this); popup.add(item);
2618 popup.addSeparator();
2619 } else if (Patch.class == aclass) {
2620 final JMenu m = new JMenu("Patch");
2621 item = new JMenuItem("Fill ROI in alpha mask"); item.addActionListener(this); m.add(item);
2622 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, 0));
2623 item.setEnabled(null != getRoi());
2624 item = new JMenuItem("Fill inverse ROI in alpha mask"); item.addActionListener(this); m.add(item);
2625 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, Event.SHIFT_MASK));
2626 item.setEnabled(null != getRoi());
2627 item = new JMenuItem("Remove alpha mask"); item.addActionListener(this); m.add(item);
2628 if ( ! ((Patch)active).hasAlphaMask()) item.setEnabled(false);
2629 item = new JMenuItem("Unlink from images"); item.addActionListener(this); m.add(item);
2630 if (!active.isLinked(Patch.class)) item.setEnabled(false);
2631 if (((Patch)active).isStack()) {
2632 item = new JMenuItem("Unlink slices"); item.addActionListener(this); m.add(item);
2634 final int n_sel_patches = selection.getSelected(Patch.class).size();
2635 item = new JMenuItem("Snap"); item.addActionListener(this); m.add(item);
2636 item.setEnabled(1 == n_sel_patches);
2637 item = new JMenuItem("Montage"); item.addActionListener(this); m.add(item);
2638 item.setEnabled(n_sel_patches > 1);
2639 item = new JMenuItem("Lens correction"); item.addActionListener(this); m.add(item);
2640 item.setEnabled(n_sel_patches > 1);
2641 item = new JMenuItem("Blend"); item.addActionListener(this); m.add(item);
2642 item.setEnabled(n_sel_patches > 1);
2643 item = new JMenuItem("Open image"); item.addActionListener(new ActionListener() {
2644 @Override
2645 public void actionPerformed(final ActionEvent e) {
2646 for (final Patch p : selection.get(Patch.class)) {
2647 p.getImagePlus().show();
2650 }); m.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, KeyEvent.SHIFT_MASK, true));
2651 item = new JMenuItem("Open original image"); item.addActionListener(new ActionListener() {
2652 @Override
2653 public void actionPerformed(final ActionEvent e) {
2654 for (final Patch p : selection.get(Patch.class)) {
2655 p.getProject().getLoader().releaseToFit(p.getOWidth(), p.getOHeight(), p.getType(), 5);
2656 p.getProject().getLoader().openImagePlus(p.getImageFilePath()).show();
2660 item = new JMenuItem("View volume"); item.addActionListener(this); m.add(item);
2661 final HashSet<Displayable> hs = active.getLinked(Patch.class);
2662 if (null == hs || 0 == hs.size()) item.setEnabled(false);
2663 item = new JMenuItem("View orthoslices"); item.addActionListener(this); m.add(item);
2664 if (null == hs || 0 == hs.size()) item.setEnabled(false); // if no Patch instances among the directly linked, then it's not a stack
2665 popup.add(m);
2666 popup.addSeparator();
2667 } else {
2668 item = new JMenuItem("Unlink"); item.addActionListener(this); popup.add(item);
2669 item = new JMenuItem("Show in 3D"); item.addActionListener(this); popup.add(item);
2670 popup.addSeparator();
2673 if (AreaList.class == aclass) {
2674 item = new JMenuItem("Merge"); item.addActionListener(this); popup.add(item);
2675 final ArrayList<?> al = selection.getSelected();
2676 int n = 0;
2677 for (final Iterator<?> it = al.iterator(); it.hasNext(); ) {
2678 if (it.next().getClass() == AreaList.class) n++;
2680 if (n < 2) item.setEnabled(false);
2681 addAreaListAreasMenu(popup, active);
2682 popup.addSeparator();
2683 } else if (Pipe.class == aclass) {
2684 item = new JMenuItem("Reverse point order"); item.addActionListener(this); popup.add(item);
2685 popup.addSeparator();
2686 } else if (Treeline.class == aclass || AreaTree.class == aclass) {
2687 if (AreaTree.class == aclass) addAreaTreeAreasMenu(popup, (AreaTree)active);
2688 item = new JMenuItem("Reroot"); item.addActionListener(this); popup.add(item);
2689 item = new JMenuItem("Part subtree"); item.addActionListener(this); popup.add(item);
2690 item = new JMenuItem("Join"); item.addActionListener(this); popup.add(item);
2691 item = new JMenuItem("Show tabular view"); item.addActionListener(this); popup.add(item);
2692 final Collection<Tree> trees = selection.get(Tree.class);
2695 final JMenu nodeMenu = new JMenu("Nodes");
2696 item = new JMenuItem("Mark"); item.addActionListener(this); nodeMenu.add(item);
2697 item = new JMenuItem("Clear marks (selected Trees)"); item.addActionListener(this); nodeMenu.add(item);
2698 final JMenuItem nodeColor = new JMenuItem("Color..."); nodeMenu.add(nodeColor);
2699 nodeColor.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.SHIFT_MASK, true));
2700 final JMenuItem nodePairColor = new JMenuItem("Color path between two nodes tagged as..."); nodeMenu.add(nodePairColor);
2701 final JMenuItem nodeRadius = active instanceof Treeline ? new JMenuItem("Radius...") : null;
2702 if (null != nodeRadius) {
2703 nodeMenu.add(nodeRadius);
2704 nodeRadius.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, 0, true));
2706 final JMenuItem removeAllTags = new JMenuItem("Drop all tags (selected trees)"); nodeMenu.add(removeAllTags);
2707 final JMenuItem removeTag = new JMenuItem("Drop all occurrences of tag..."); nodeMenu.add(removeTag);
2708 final JMenuItem colorizeByNodeCentrality = new JMenuItem("Colorize by node betweenness centrality"); nodeMenu.add(colorizeByNodeCentrality);
2709 final JMenuItem colorizeByBranchCentrality = new JMenuItem("Colorize by branch betweenness centrality"); nodeMenu.add(colorizeByBranchCentrality);
2711 popup.add(nodeMenu);
2712 final ActionListener ln = new ActionListener() {
2713 @Override
2714 public void actionPerformed(final ActionEvent ae) {
2715 if (null == active) {
2716 Utils.showMessage("No tree selected!");
2717 return;
2719 if (!(active instanceof Tree)) {
2720 Utils.showMessage("The selected object is not a Tree!");
2721 return;
2723 final Tree tree = (Tree)active;
2724 final Object src = ae.getSource();
2726 if (src == nodeColor) {
2727 final Node nd = tree.getLastVisited();
2728 if (null == nd) {
2729 Utils.showMessage("Select a node first by clicking on it\nor moving the mouse over it and pushing 'g'.");
2730 return;
2732 tree.adjustNodeColors(nd); // sets an undo step
2733 } else if (src == nodePairColor) {
2734 final TreeMap<String,Tag> sm = getTags(tree);
2735 if (null == sm) return;
2736 if (1 == sm.size()) {
2737 Utils.showMessage("Need at least two different tags in the tree!");
2738 return;
2740 final Color color = tree.getColor();
2741 final GenericDialog gd = new GenericDialog("Node colors");
2742 gd.addSlider("Red: ", 0, 255, color.getRed());
2743 gd.addSlider("Green: ", 0, 255, color.getGreen());
2744 gd.addSlider("Blue: ", 0, 255, color.getBlue());
2745 final String[] stags = asStrings(sm);
2746 sm.keySet().toArray(stags);
2747 gd.addChoice("Upstream tag:", stags, stags[0]);
2748 gd.addChoice("Downstream tag:", stags, stags[1]);
2749 gd.showDialog();
2750 if (gd.wasCanceled()) return;
2751 final Color newColor = new Color((int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
2752 final Tag upstreamTag = sm.get(gd.getNextChoice());
2753 final Tag downstreamTag = sm.get(gd.getNextChoice());
2754 final List<Tree<?>.NodePath> pairs = tree.findTaggedPairs(upstreamTag, downstreamTag);
2755 if (null == pairs || pairs.isEmpty()) {
2756 Utils.showMessage("No pairs found for '" + upstreamTag + "' and '" + downstreamTag + "'");
2757 return;
2759 getLayerSet().addDataEditStep(tree);
2760 for (final Tree<?>.NodePath pair : pairs) {
2761 for (final Node<?> nd : pair.path) {
2762 nd.setColor(newColor);
2765 getLayerSet().addDataEditStep(tree);
2766 Display.repaint();
2767 } else if (src == nodeRadius) {
2768 if (!(tree instanceof Treeline)) return;
2769 final Node nd = tree.getLastVisited();
2770 if (null == nd) {
2771 Utils.showMessage("Select a node first by clicking on it\nor moving the mouse over it and pushing 'g'.");
2772 return;
2774 ((Treeline)tree).askAdjustRadius(nd); // sets an undo step
2775 } else if (src == removeAllTags) {
2776 if (!Utils.check("Really remove all tags from all selected trees?")) return;
2777 final List<Tree> sel = selection.get(Tree.class);
2778 getLayerSet().addDataEditStep(new HashSet<Displayable>(sel));
2779 try {
2780 for (final Tree t : sel) {
2781 t.dropAllTags();
2783 getLayerSet().addDataEditStep(new HashSet<Displayable>(sel)); // current state
2784 } catch (final Exception e) {
2785 getLayerSet().undoOneStep();
2786 IJError.print(e);
2788 Display.repaint();
2789 } else if (src == removeTag) {
2790 final TreeMap<String,Tag> tags = getTags(tree);
2791 final String[] ts = asStrings(tags);
2792 final GenericDialog gd = new GenericDialog("Remove tags");
2793 gd.addChoice("Tag:", ts, ts[0]);
2794 final String[] c = new String[]{"Active tree", "All selected trees and connectors", "All trees and connectors"};
2795 gd.addChoice("From: ", c, c[0]);
2796 gd.showDialog();
2797 if (gd.wasCanceled()) return;
2798 final HashSet<Displayable> ds = new HashSet<Displayable>();
2799 final Tag tag = tags.get(gd.getNextChoice());
2800 switch (gd.getNextChoiceIndex()) {
2801 case 0: ds.add(tree); break;
2802 case 1: ds.addAll(selection.get(Tree.class));
2803 case 2: ds.addAll(getLayerSet().getZDisplayables(Tree.class, true));
2805 getLayerSet().addDataEditStep(ds);
2806 try {
2807 for (final Displayable d : ds) {
2808 final Tree t = (Tree)d;
2809 t.removeTag(tag);
2811 getLayerSet().addDataEditStep(ds);
2812 } catch (final Exception e) {
2813 getLayerSet().undoOneStep();
2814 IJError.print(e);
2816 Display.repaint();
2817 } else if (src == colorizeByNodeCentrality) {
2818 final List<Tree> ts = selection.get(Tree.class);
2819 final HashSet<Tree> ds = new HashSet<Tree>(ts);
2820 getLayerSet().addDataEditStep(ds);
2821 try {
2822 for (final Tree t : ts) {
2823 t.colorizeByNodeBetweennessCentrality();
2825 getLayerSet().addDataEditStep(ds);
2826 Display.repaint();
2827 } catch (final Exception e) {
2828 getLayerSet().undoOneStep();
2829 IJError.print(e);
2831 } else if (src == colorizeByBranchCentrality) {
2832 final List<Tree> ts = selection.get(Tree.class);
2833 final HashSet<Tree> ds = new HashSet<Tree>(ts);
2834 getLayerSet().addDataEditStep(ds);
2835 try {
2836 for (final Tree t : ts) {
2837 t.colorizeByBranchBetweennessCentrality(2);
2839 getLayerSet().addDataEditStep(ds);
2840 Display.repaint();
2841 } catch (final Exception e) {
2842 getLayerSet().undoOneStep();
2843 IJError.print(e);
2848 for (final JMenuItem a : new JMenuItem[]{nodeColor, nodePairColor, nodeRadius,
2849 removeAllTags, removeTag, colorizeByNodeCentrality, colorizeByBranchCentrality}) {
2850 if (null == a) continue;
2851 a.addActionListener(ln);
2855 final JMenu review = new JMenu("Review");
2856 final JMenuItem tgenerate = new JMenuItem("Generate review stacks (selected Trees)"); review.add(tgenerate);
2857 tgenerate.setEnabled(trees.size() > 0);
2858 final JMenuItem tslab = new JMenuItem("Generate review stack for current slab"); review.add(tslab);
2859 final JMenuItem tsubtree = new JMenuItem("Generate review stacks for subtree"); review.add(tsubtree);
2860 final JMenuItem tremove = new JMenuItem("Remove reviews (selected Trees)"); review.add(tremove);
2861 tremove.setEnabled(trees.size() > 0);
2862 final JMenuItem tconnectors = new JMenuItem("View table of outgoing/incoming connectors"); review.add(tconnectors);
2863 final ActionListener l = new ActionListener() {
2864 @Override
2865 public void actionPerformed(final ActionEvent ae) {
2866 if (!Utils.check("Really " + ae.getActionCommand())) {
2867 return;
2869 dispatcher.exec(new Runnable() {
2870 @Override
2871 public void run() {
2872 int count = 0;
2873 for (final Tree<?> t : trees) {
2874 Utils.log("Processing " + (++count) + "/" + trees.size());
2875 Bureaucrat bu = null;
2876 if (ae.getSource() == tgenerate) bu = t.generateAllReviewStacks();
2877 else if (ae.getSource() == tremove) bu = t.removeReviews();
2878 else if (ae.getSource() == tslab) {
2879 final Point po = canvas.consumeLastPopupPoint();
2880 Utils.log2(po, layer, 1.0);
2881 bu = t.generateReviewStackForSlab(po.x, po.y, Display.this.layer, 1.0);
2882 } else if (ae.getSource() == tsubtree) {
2883 final Point po = canvas.consumeLastPopupPoint();
2884 bu = t.generateSubtreeReviewStacks(po.x, po.y, Display.this.layer, 1.0);
2886 if (null != bu) try {
2887 bu.getWorker().join();
2888 } catch (final InterruptedException ie) { return; }
2894 for (final JMenuItem c : new JMenuItem[]{tgenerate, tslab, tsubtree, tremove}) c.addActionListener(l);
2895 tconnectors.addActionListener(new ActionListener() {
2896 @Override
2897 public void actionPerformed(final ActionEvent ae) {
2898 for (final Tree<?> t : trees) TreeConnectorsView.create(t);
2901 popup.add(review);
2903 final JMenu go = new JMenu("Go");
2904 item = new JMenuItem("Previous branch node or start"); item.addActionListener(this); go.add(item);
2905 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_B, 0, true));
2906 item = new JMenuItem("Next branch node or end"); item.addActionListener(this); go.add(item);
2907 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, 0, true));
2908 item = new JMenuItem("Root"); item.addActionListener(this); go.add(item);
2909 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0, true));
2910 go.addSeparator();
2911 item = new JMenuItem("Last added node"); item.addActionListener(this); go.add(item);
2912 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_L, 0, true));
2913 item = new JMenuItem("Last edited node"); item.addActionListener(this); go.add(item);
2914 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, 0, true));
2915 popup.add(go);
2917 final JMenu tmeasure = new JMenu("Measure");
2918 final JMenuItem dist_to_root = new JMenuItem("Distance from this node to root"); tmeasure.add(dist_to_root);
2919 final JMenuItem dist_to_tag = new JMenuItem("Distance from this node to all nodes tagged as..."); tmeasure.add(dist_to_tag);
2920 final JMenuItem dist_to_mark = new JMenuItem("Distance from this node to the marked node"); tmeasure.add(dist_to_mark);
2921 final JMenuItem dist_pairs = new JMenuItem("Shortest distances between all pairs of nodes tagged as..."); tmeasure.add(dist_pairs);
2922 final ActionListener tma = getTreePathMeasureListener((Tree<?>)active);
2923 for (final JMenuItem mi : new JMenuItem[]{dist_to_root, dist_to_tag, dist_to_mark, dist_pairs}) {
2924 mi.addActionListener(tma);
2926 popup.add(tmeasure);
2928 final String[] name = new String[]{AreaTree.class.getSimpleName(), Treeline.class.getSimpleName()};
2929 if (Treeline.class == aclass) {
2930 final String a = name[0];
2931 name[0] = name[1];
2932 name[1] = a;
2934 item = new JMenuItem("Duplicate " + name[0] + " as " + name[1]);
2935 item.addActionListener(new ActionListener() {
2936 @Override
2937 public void actionPerformed(final ActionEvent e) {
2938 Bureaucrat.createAndStart(new Worker.Task("Converting") {
2939 @Override
2940 public void exec() {
2941 try {
2942 getLayerSet().addChangeTreesStep();
2943 final Map<Tree<?>,Tree<?>> m = Tree.duplicateAs(selection.getSelected(), Treeline.class == aclass ? AreaTree.class : Treeline.class);
2944 if (m.isEmpty()) {
2945 getLayerSet().removeLastUndoStep();
2946 } else {
2947 getLayerSet().addChangeTreesStep();
2949 } catch (final Exception e) {
2950 IJError.print(e);
2953 }, getProject());
2956 popup.add(item);
2957 popup.addSeparator();
2958 } else if (Connector.class == aclass) {
2959 item = new JMenuItem("Merge"); item.addActionListener(new ActionListener() {
2960 @Override
2961 public void actionPerformed(final ActionEvent ae) {
2962 if (null == getActive() || getActive().getClass() != Connector.class) {
2963 Utils.log("Active object must be a Connector!");
2964 return;
2966 final List<Connector> col = selection.get(Connector.class);
2967 if (col.size() < 2) {
2968 Utils.log("Select more than one Connector!");
2969 return;
2971 if (col.get(0) != getActive()) {
2972 if (col.remove(getActive())) {
2973 col.add(0, (Connector)getActive());
2974 } else {
2975 Utils.log("ERROR: cannot find active object in selection list!");
2976 return;
2979 Bureaucrat.createAndStart(new Worker.Task("Merging connectors") {
2980 @Override
2981 public void exec() {
2982 getLayerSet().addChangeTreesStep();
2983 Connector base = null;
2984 try {
2985 base = Connector.merge(col);
2986 } catch (final Exception e) {
2987 IJError.print(e);
2989 if (null == base) {
2990 Utils.log("ERROR: could not merge connectors!");
2991 getLayerSet().undoOneStep();
2992 } else {
2993 getLayerSet().addChangeTreesStep();
2995 Display.repaint();
2997 }, getProject());
3000 popup.add(item);
3001 item.setEnabled(selection.getSelected(Connector.class).size() > 1);
3002 popup.addSeparator();
3005 item = new JMenuItem("Duplicate"); item.addActionListener(this); popup.add(item);
3006 item = new JMenuItem("Color..."); item.addActionListener(this); popup.add(item);
3007 if (active instanceof LayerSet) item.setEnabled(false);
3008 if (active.isLocked()) {
3009 item = new JMenuItem("Unlock"); item.addActionListener(this); popup.add(item);
3010 } else {
3011 item = new JMenuItem("Lock"); item.addActionListener(this); popup.add(item);
3013 menu = new JMenu("Move");
3014 popup.addSeparator();
3015 final LayerSet ls = layer.getParent();
3016 item = new JMenuItem("Move to top"); item.addActionListener(this); menu.add(item);
3017 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.
3018 if (ls.isTop(active)) item.setEnabled(false);
3019 item = new JMenuItem("Move up"); item.addActionListener(this); menu.add(item);
3020 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0, true));
3021 if (ls.isTop(active)) item.setEnabled(false);
3022 item = new JMenuItem("Move down"); item.addActionListener(this); menu.add(item);
3023 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0, true));
3024 if (ls.isBottom(active)) item.setEnabled(false);
3025 item = new JMenuItem("Move to bottom"); item.addActionListener(this); menu.add(item);
3026 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_END, 0, true));
3027 if (ls.isBottom(active)) item.setEnabled(false);
3029 popup.add(menu);
3030 popup.addSeparator();
3031 item = new JMenuItem("Delete..."); item.addActionListener(this); popup.add(item);
3032 try {
3033 if (Patch.class == aclass) {
3034 if (!active.isOnlyLinkedTo(Patch.class)) {
3035 item.setEnabled(false);
3038 } catch (final Exception e) { IJError.print(e); item.setEnabled(false); }
3040 if (Patch.class == aclass) {
3041 item = new JMenuItem("Revert"); item.addActionListener(this); popup.add(item);
3042 if ( null == ((Patch)active).getOriginalPath()) item.setEnabled(false);
3043 popup.addSeparator();
3045 item = new JMenuItem("Properties..."); item.addActionListener(this); popup.add(item);
3046 item = new JMenuItem("Show centered"); item.addActionListener(this); popup.add(item);
3048 popup.addSeparator();
3050 if (! (active instanceof ZDisplayable)) {
3051 final int i_layer = layer.getParent().indexOf(layer);
3052 final int n_layers = layer.getParent().size();
3053 item = new JMenuItem("Send to previous layer"); item.addActionListener(this); popup.add(item);
3054 if (1 == n_layers || 0 == i_layer || active.isLinked()) item.setEnabled(false);
3055 // 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
3056 else if (active instanceof Profile && !active.canSendTo(layer.getParent().previous(layer))) item.setEnabled(false);
3057 item = new JMenuItem("Send to next layer"); item.addActionListener(this); popup.add(item);
3058 if (1 == n_layers || n_layers -1 == i_layer || active.isLinked()) item.setEnabled(false);
3059 else if (active instanceof Profile && !active.canSendTo(layer.getParent().next(layer))) item.setEnabled(false);
3062 menu = new JMenu("Send linked group to...");
3063 if (active.hasLinkedGroupWithinLayer(this.layer)) {
3064 int i = 1;
3065 for (final Layer la : ls.getLayers()) {
3066 String layer_title = i + ": " + la.getTitle();
3067 if (-1 == layer_title.indexOf(' ')) layer_title += " ";
3068 item = new JMenuItem(layer_title); item.addActionListener(this); menu.add(item);
3069 if (la == this.layer) item.setEnabled(false);
3070 i++;
3072 popup.add(menu);
3073 } else {
3074 menu.setEnabled(false);
3075 //Utils.log("Active's linked group not within layer.");
3077 popup.add(menu);
3078 popup.addSeparator();
3082 item = new JMenuItem("Undo");item.addActionListener(this); popup.add(item);
3083 if (!layer.getParent().canUndo() || canvas.isTransforming()) item.setEnabled(false);
3084 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Utils.getControlModifier(), true));
3085 item = new JMenuItem("Redo");item.addActionListener(this); popup.add(item);
3086 if (!layer.getParent().canRedo() || canvas.isTransforming()) item.setEnabled(false);
3087 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Event.SHIFT_MASK | Utils.getControlModifier(), true));
3088 popup.addSeparator();
3090 // Would get so much simpler with a clojure macro ...
3092 try {
3093 menu = new JMenu("Hide/Unhide");
3094 item = new JMenuItem("Hide deselected"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.SHIFT_MASK, true));
3095 boolean none = 0 == selection.getNSelected();
3096 if (none) item.setEnabled(false);
3097 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));
3098 if (none) item.setEnabled(false);
3099 item = new JMenuItem("Hide selected"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, 0, true));
3100 if (none) item.setEnabled(false);
3101 none = ! layer.getParent().containsDisplayable(DLabel.class);
3102 item = new JMenuItem("Hide all labels"); item.addActionListener(this); menu.add(item);
3103 if (none) item.setEnabled(false);
3104 item = new JMenuItem("Unhide all labels"); item.addActionListener(this); menu.add(item);
3105 if (none) item.setEnabled(false);
3106 none = ! layer.getParent().contains(AreaList.class);
3107 item = new JMenuItem("Hide all arealists"); item.addActionListener(this); menu.add(item);
3108 if (none) item.setEnabled(false);
3109 item = new JMenuItem("Unhide all arealists"); item.addActionListener(this); menu.add(item);
3110 if (none) item.setEnabled(false);
3111 none = ! layer.contains(Profile.class);
3112 item = new JMenuItem("Hide all profiles"); item.addActionListener(this); menu.add(item);
3113 if (none) item.setEnabled(false);
3114 item = new JMenuItem("Unhide all profiles"); item.addActionListener(this); menu.add(item);
3115 if (none) item.setEnabled(false);
3116 none = ! layer.getParent().contains(Pipe.class);
3117 item = new JMenuItem("Hide all pipes"); item.addActionListener(this); menu.add(item);
3118 if (none) item.setEnabled(false);
3119 item = new JMenuItem("Unhide all pipes"); item.addActionListener(this); menu.add(item);
3120 if (none) item.setEnabled(false);
3121 none = ! layer.getParent().contains(Polyline.class);
3122 item = new JMenuItem("Hide all polylines"); item.addActionListener(this); menu.add(item);
3123 if (none) item.setEnabled(false);
3124 item = new JMenuItem("Unhide all polylines"); item.addActionListener(this); menu.add(item);
3125 if (none) item.setEnabled(false);
3126 none = ! layer.getParent().contains(Treeline.class);
3127 item = new JMenuItem("Hide all treelines"); item.addActionListener(this); menu.add(item);
3128 if (none) item.setEnabled(false);
3129 item = new JMenuItem("Unhide all treelines"); item.addActionListener(this); menu.add(item);
3130 if (none) item.setEnabled(false);
3131 none = ! layer.getParent().contains(AreaTree.class);
3132 item = new JMenuItem("Hide all areatrees"); item.addActionListener(this); menu.add(item);
3133 if (none) item.setEnabled(false);
3134 item = new JMenuItem("Unhide all areatrees"); item.addActionListener(this); menu.add(item);
3135 if (none) item.setEnabled(false);
3136 none = ! layer.getParent().contains(Ball.class);
3137 item = new JMenuItem("Hide all balls"); item.addActionListener(this); menu.add(item);
3138 if (none) item.setEnabled(false);
3139 item = new JMenuItem("Unhide all balls"); item.addActionListener(this); menu.add(item);
3140 if (none) item.setEnabled(false);
3141 none = ! layer.getParent().contains(Connector.class);
3142 item = new JMenuItem("Hide all connectors"); item.addActionListener(this); menu.add(item);
3143 if (none) item.setEnabled(false);
3144 item = new JMenuItem("Unhide all connectors"); item.addActionListener(this); menu.add(item);
3145 if (none) item.setEnabled(false);
3146 none = ! layer.getParent().containsDisplayable(Patch.class);
3147 item = new JMenuItem("Hide all images"); item.addActionListener(this); menu.add(item);
3148 if (none) item.setEnabled(false);
3149 item = new JMenuItem("Unhide all images"); item.addActionListener(this); menu.add(item);
3150 if (none) item.setEnabled(false);
3151 item = new JMenuItem("Hide all but images"); item.addActionListener(this); menu.add(item);
3152 item = new JMenuItem("Unhide all"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.ALT_MASK, true));
3154 popup.add(menu);
3155 } catch (final Exception e) { IJError.print(e); }
3157 // plugins, if any
3158 Utils.addPlugIns(popup, "Display", project, new Callable<Displayable>() { @Override
3159 public Displayable call() { return Display.this.getActive(); }});
3161 final JMenu align_menu = new JMenu("Align");
3162 item = new JMenuItem("Align stack slices"); item.addActionListener(this); align_menu.add(item);
3163 if (selection.isEmpty() || ! (getActive().getClass() == Patch.class && ((Patch)getActive()).isStack())) item.setEnabled(false);
3164 item = new JMenuItem("Align layers"); item.addActionListener(this); align_menu.add(item);
3165 if (1 == layer.getParent().size()) item.setEnabled(false);
3166 item = new JMenuItem("Align layers manually with landmarks"); item.addActionListener(this); align_menu.add(item);
3167 if (1 == layer.getParent().size()) item.setEnabled(false);
3168 item = new JMenuItem("Align multi-layer mosaic"); item.addActionListener(this); align_menu.add(item);
3169 if (1 == layer.getParent().size()) item.setEnabled(false);
3170 item = new JMenuItem("Montage all images in this layer"); item.addActionListener(this); align_menu.add(item);
3171 if (layer.getDisplayables(Patch.class).size() < 2) item.setEnabled(false);
3172 item = new JMenuItem("Montage selected images"); item.addActionListener(this); align_menu.add(item);
3173 if (selection.getSelected(Patch.class).size() < 2) item.setEnabled(false);
3174 item = new JMenuItem("Montage multiple layers"); item.addActionListener(this); align_menu.add(item);
3175 popup.add(align_menu);
3177 final JMenuItem st = new JMenu("Transform");
3178 final StartTransformMenuListener tml = new StartTransformMenuListener();
3179 item = new JMenuItem("Transform (affine)"); item.addActionListener(tml); st.add(item);
3180 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0, true));
3181 if (null == active) item.setEnabled(false);
3182 item = new JMenuItem("Transform (non-linear)"); item.addActionListener(tml); st.add(item);
3183 if (null == active) item.setEnabled(false);
3184 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, Event.SHIFT_MASK, true));
3185 item = new JMenuItem("Cancel transform"); st.add(item);
3186 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
3187 item.setEnabled(false); // just added as a self-documenting cue; no listener
3188 item = new JMenuItem("Remove rotation, scaling and shear (selected images)"); item.addActionListener(tml); st.add(item);
3189 if (null == active) item.setEnabled(false);
3190 item = new JMenuItem("Remove rotation, scaling and shear layer-wise"); item.addActionListener(tml); st.add(item);
3191 item = new JMenuItem("Remove coordinate transforms (selected images)"); item.addActionListener(tml); st.add(item);
3192 if (null == active) item.setEnabled(false);
3193 item = new JMenuItem("Remove coordinate transforms layer-wise"); item.addActionListener(tml); st.add(item);
3194 item = new JMenuItem("Adjust mesh resolution (selected images)"); item.addActionListener(tml); st.add(item);
3195 if (null == active) item.setEnabled(false);
3196 item = new JMenuItem("Adjust mesh resolution layer-wise"); item.addActionListener(tml); st.add(item);
3197 item = new JMenuItem("Set coordinate transform of selected image to other selected images"); item.addActionListener(tml); st.add(item);
3198 if (null == active) item.setEnabled(false);
3199 item = new JMenuItem("Set coordinate transform of selected image layer-wise"); item.addActionListener(tml); st.add(item);
3200 if (null == active) item.setEnabled(false);
3201 item = new JMenuItem("Set affine transform of selected image to other selected images"); item.addActionListener(tml); st.add(item);
3202 if (null == active) item.setEnabled(false);
3203 item = new JMenuItem("Set affine transform of selected image layer-wise"); item.addActionListener(tml); st.add(item);
3204 if (null == active) item.setEnabled(false);
3205 popup.add(st);
3207 final JMenu link_menu = new JMenu("Link");
3208 item = new JMenuItem("Link images..."); item.addActionListener(this); link_menu.add(item);
3209 item = new JMenuItem("Unlink all selected images"); item.addActionListener(this); link_menu.add(item);
3210 item.setEnabled(selection.getSelected(Patch.class).size() > 0);
3211 item = new JMenuItem("Unlink all"); item.addActionListener(this); link_menu.add(item);
3212 popup.add(link_menu);
3214 final JMenu adjust_menu = new JMenu("Adjust images");
3215 item = new JMenuItem("Enhance contrast layer-wise..."); item.addActionListener(this); adjust_menu.add(item);
3216 item = new JMenuItem("Enhance contrast (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
3217 if (selection.isEmpty()) item.setEnabled(false);
3218 item = new JMenuItem("Adjust image filters (selected images)"); item.addActionListener(this); adjust_menu.add(item);
3219 if (selection.isEmpty()) item.setEnabled(false);
3220 item = new JMenuItem("Set Min and Max layer-wise..."); item.addActionListener(this); adjust_menu.add(item);
3221 item = new JMenuItem("Set Min and Max (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
3222 if (selection.isEmpty()) item.setEnabled(false);
3223 item = new JMenuItem("Adjust min and max (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
3224 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_J, 0));
3225 if (selection.isEmpty()) item.setEnabled(false);
3226 item = new JMenuItem("Mask image borders (layer-wise)..."); item.addActionListener(this); adjust_menu.add(item);
3227 item = new JMenuItem("Mask image borders (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
3228 if (selection.isEmpty()) item.setEnabled(false);
3229 item = new JMenuItem("Remove alpha masks (layer-wise)..."); item.addActionListener(this); adjust_menu.add(item);
3230 item = new JMenuItem("Remove alpha masks (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
3231 if (selection.isEmpty()) item.setEnabled(false);
3232 item = new JMenuItem("Split images under polyline ROI"); item.addActionListener(this); adjust_menu.add(item);
3233 final Roi roi = canvas.getFakeImagePlus().getRoi();
3234 if (null == roi || roi.getType() != Roi.POLYLINE) item.setEnabled(false);
3235 item = new JMenuItem("Blend (layer-wise)..."); item.addActionListener(this); adjust_menu.add(item);
3236 item = new JMenuItem("Blend (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
3237 if (selection.isEmpty()) item.setEnabled(false);
3238 item = new JMenuItem("Match intensities (layer-wise)..."); item.addActionListener(this); adjust_menu.add(item);
3239 item = new JMenuItem("Remove intensity maps (layer-wise)..."); item.addActionListener(this); adjust_menu.add(item);
3240 popup.add(adjust_menu);
3242 final JMenu script = new JMenu("Script");
3243 final MenuScriptListener msl = new MenuScriptListener();
3244 item = new JMenuItem("Set preprocessor script layer-wise..."); item.addActionListener(msl); script.add(item);
3245 item = new JMenuItem("Set preprocessor script (selected images)..."); item.addActionListener(msl); script.add(item);
3246 if (selection.isEmpty()) item.setEnabled(false);
3247 item = new JMenuItem("Remove preprocessor script layer-wise..."); item.addActionListener(msl); script.add(item);
3248 item = new JMenuItem("Remove preprocessor script (selected images)..."); item.addActionListener(msl); script.add(item);
3249 if (selection.isEmpty()) item.setEnabled(false);
3250 popup.add(script);
3252 menu = new JMenu("Import");
3253 item = new JMenuItem("Import image"); item.addActionListener(this); menu.add(item);
3254 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, Event.ALT_MASK & Event.SHIFT_MASK, true));
3255 item = new JMenuItem("Import stack..."); item.addActionListener(this); menu.add(item);
3256 item = new JMenuItem("Import stack with landmarks..."); item.addActionListener(this); menu.add(item);
3257 item = new JMenuItem("Import grid..."); item.addActionListener(this); menu.add(item);
3258 item = new JMenuItem("Import sequence as grid..."); item.addActionListener(this); menu.add(item);
3259 item = new JMenuItem("Import from text file..."); item.addActionListener(this); menu.add(item);
3260 item = new JMenuItem("Import labels as arealists..."); item.addActionListener(this); menu.add(item);
3261 item = new JMenuItem("Tags ..."); item.addActionListener(this); menu.add(item);
3262 popup.add(menu);
3264 menu = new JMenu("Export");
3265 final boolean has_arealists = layer.getParent().contains(AreaList.class);
3266 item = new JMenuItem("Make flat image..."); item.addActionListener(this); menu.add(item);
3267 item = new JMenuItem("Arealists as labels (tif)"); item.addActionListener(this); menu.add(item);
3268 item.setEnabled(has_arealists);
3269 item = new JMenuItem("Arealists as labels (amira)"); item.addActionListener(this); menu.add(item);
3270 item.setEnabled(has_arealists);
3271 item = new JMenuItem("Image stack under selected Arealist"); item.addActionListener(this); menu.add(item);
3272 item.setEnabled(null != active && AreaList.class == active.getClass());
3273 item = new JMenuItem("Fly through selected Treeline/AreaTree"); item.addActionListener(this); menu.add(item);
3274 item.setEnabled(null != active && Tree.class.isInstance(active));
3275 item = new JMenuItem("Tags..."); item.addActionListener(this); menu.add(item);
3276 item = new JMenuItem("Connectivity graph..."); item.addActionListener(this); menu.add(item);
3277 item = new JMenuItem("NeuroML..."); item.addActionListener(this); menu.add(item);
3278 popup.add(menu);
3280 menu = new JMenu("Display");
3281 item = new JMenuItem("Resize canvas/LayerSet..."); item.addActionListener(this); menu.add(item);
3282 item = new JMenuItem("Autoresize canvas/LayerSet"); item.addActionListener(this); menu.add(item);
3283 item = new JMenuItem("Resize canvas/LayerSet to ROI"); item.addActionListener(this); menu.add(item);
3284 item.setEnabled(null != canvas.getFakeImagePlus().getRoi());
3285 item = new JMenuItem("Properties ..."); item.addActionListener(this); menu.add(item);
3286 item = new JMenuItem("Calibration..."); item.addActionListener(this); menu.add(item);
3287 item = new JMenuItem("Grid overlay..."); item.addActionListener(this); menu.add(item);
3288 item = new JMenuItem("Adjust snapping parameters..."); item.addActionListener(this); menu.add(item);
3289 item = new JMenuItem("Adjust fast-marching parameters..."); item.addActionListener(this); menu.add(item);
3290 item = new JMenuItem("Adjust arealist paint parameters..."); item.addActionListener(this); menu.add(item);
3291 item = new JMenuItem("Show current 2D position in 3D"); item.addActionListener(this); menu.add(item);
3292 item = new JMenuItem("Show layers as orthoslices in 3D"); item.addActionListener(this); menu.add(item);
3293 item = new JMenuItem("Inspect image mesh triangles"); item.addActionListener(this); menu.add(item);
3294 popup.add(menu);
3296 menu = new JMenu("Project");
3297 this.project.getLoader().setupMenuItems(menu, this.getProject());
3298 item = new JMenuItem("Project properties..."); item.addActionListener(this); menu.add(item);
3299 item = new JMenuItem("Create subproject"); item.addActionListener(this); menu.add(item);
3300 item = new JMenuItem("Create sibling project with retiled layers"); item.addActionListener(this); menu.add(item);
3301 item = new JMenuItem("Release memory..."); item.addActionListener(this); menu.add(item);
3302 item = new JMenuItem("Flush image cache"); item.addActionListener(this); menu.add(item);
3303 item = new JMenuItem("Regenerate all mipmaps"); item.addActionListener(this); menu.add(item);
3304 item = new JMenuItem("Regenerate mipmaps (selected images)"); item.addActionListener(this); menu.add(item);
3305 menu.addSeparator();
3306 item = new JMenuItem("Measurement options..."); item.addActionListener(this); menu.add(item);
3307 popup.add(menu);
3309 menu = new JMenu("Selection");
3310 item = new JMenuItem("Select all"); item.addActionListener(this); menu.add(item);
3311 item = new JMenuItem("Select all visible"); item.addActionListener(this); menu.add(item);
3312 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, Utils.getControlModifier(), true));
3313 if (0 == layer.getDisplayableList().size() && 0 == layer.getParent().getDisplayableList().size()) item.setEnabled(false);
3314 item = new JMenuItem("Select all that match..."); item.addActionListener(this); menu.add(item);
3315 item = new JMenuItem("Select none"); item.addActionListener(this); menu.add(item);
3316 if (0 == selection.getNSelected()) item.setEnabled(false);
3317 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
3319 final JMenu bytype = new JMenu("Select all by type");
3320 item = new JMenuItem("AreaList"); item.addActionListener(bytypelistener); bytype.add(item);
3321 item = new JMenuItem("AreaTree"); item.addActionListener(bytypelistener); bytype.add(item);
3322 item = new JMenuItem("Ball"); item.addActionListener(bytypelistener); bytype.add(item);
3323 item = new JMenuItem("Connector"); item.addActionListener(bytypelistener); bytype.add(item);
3324 item = new JMenuItem("Dissector"); item.addActionListener(bytypelistener); bytype.add(item);
3325 item = new JMenuItem("Image"); item.addActionListener(bytypelistener); bytype.add(item);
3326 item = new JMenuItem("Pipe"); item.addActionListener(bytypelistener); bytype.add(item);
3327 item = new JMenuItem("Polyline"); item.addActionListener(bytypelistener); bytype.add(item);
3328 item = new JMenuItem("Profile"); item.addActionListener(bytypelistener); bytype.add(item);
3329 item = new JMenuItem("Text"); item.addActionListener(bytypelistener); bytype.add(item);
3330 item = new JMenuItem("Treeline"); item.addActionListener(bytypelistener); bytype.add(item);
3331 menu.add(bytype);
3333 item = new JMenuItem("Restore selection"); item.addActionListener(this); menu.add(item);
3334 item = new JMenuItem("Select under ROI"); item.addActionListener(this); menu.add(item);
3335 if (canvas.getFakeImagePlus().getRoi() == null) item.setEnabled(false);
3336 final JMenu graph = new JMenu("Graph");
3337 final GraphMenuListener gl = new GraphMenuListener();
3338 item = new JMenuItem("Select outgoing Connectors"); item.addActionListener(gl); graph.add(item);
3339 item = new JMenuItem("Select incoming Connectors"); item.addActionListener(gl); graph.add(item);
3340 item = new JMenuItem("Select downstream targets"); item.addActionListener(gl); graph.add(item);
3341 item = new JMenuItem("Select upstream targets"); item.addActionListener(gl); graph.add(item);
3342 graph.setEnabled(!selection.isEmpty());
3343 menu.add(graph);
3345 item = new JMenuItem("Measure"); item.addActionListener(this); menu.add(item);
3346 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, 0, true));
3347 item.setEnabled(!selection.isEmpty());
3349 popup.add(menu);
3351 menu = new JMenu("Tool");
3352 item = new JMenuItem("Rectangular ROI"); item.addActionListener(new SetToolListener(Toolbar.RECTANGLE)); menu.add(item);
3353 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0, true));
3354 item = new JMenuItem("Polygon ROI"); item.addActionListener(new SetToolListener(Toolbar.POLYGON)); menu.add(item);
3355 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0, true));
3356 item = new JMenuItem("Freehand ROI"); item.addActionListener(new SetToolListener(Toolbar.FREEROI)); menu.add(item);
3357 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0, true));
3358 item = new JMenuItem("Text"); item.addActionListener(new SetToolListener(Toolbar.TEXT)); menu.add(item);
3359 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, 0, true));
3360 item = new JMenuItem("Magnifier glass"); item.addActionListener(new SetToolListener(Toolbar.MAGNIFIER)); menu.add(item);
3361 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0, true));
3362 item = new JMenuItem("Hand"); item.addActionListener(new SetToolListener(Toolbar.HAND)); menu.add(item);
3363 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F6, 0, true));
3364 item = new JMenuItem("Select"); item.addActionListener(new SetToolListener(ProjectToolbar.SELECT)); menu.add(item);
3365 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F9, 0, true));
3366 item = new JMenuItem("Pencil"); item.addActionListener(new SetToolListener(ProjectToolbar.PENCIL)); menu.add(item);
3367 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F10, 0, true));
3368 item = new JMenuItem("Pen"); item.addActionListener(new SetToolListener(ProjectToolbar.PEN)); menu.add(item);
3369 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F11, 0, true));
3371 popup.add(menu);
3373 item = new JMenuItem("Search..."); item.addActionListener(this); popup.add(item);
3374 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, Utils.getControlModifier(), true));
3376 //canvas.add(popup);
3377 return popup;
3380 private void addAreaTreeAreasMenu(final JPopupMenu popup, final AreaTree atree) {
3381 final ActionListener listener = new ActionListener() {
3382 private final Node<?> findNearestNode() {
3383 final Layer la = getLayer();
3384 final Point p = canvas.consumeLastPopupPoint();
3385 final Node<?> lv = atree.getLastVisited();
3386 boolean use_last_visited = false;
3387 if (null != lv) {
3388 final float[] xy = new float[]{lv.x, lv.y};
3389 atree.getAffineTransform().transform(xy, 0, xy, 0, 1);
3390 use_last_visited = lv.getLayer() == la && canvas.getSrcRect().contains((int)xy[0], (int)xy[1]);
3392 // Last visited node must be within the field of view in order to be used
3393 // if no node lays near the clicked point.
3394 return atree.findNodeNear(p.x, p.y, la, canvas, use_last_visited);
3396 @Override
3397 public void actionPerformed(final ActionEvent ae) {
3398 final String command = ae.getActionCommand();
3399 final LayerSet ls = atree.getLayerSet();
3401 Bureaucrat.createAndStart(new Worker.Task(command) {
3402 @Override
3403 public void exec() {
3404 final Node<?> nd = findNearestNode();
3405 if (null == nd) {
3406 Utils.log("No node found in the field of view!");
3407 return;
3409 if (command.equals("Copy area")) {
3410 final Area area = (Area) nd.getData();
3411 if (null == area) return;
3412 DisplayCanvas.setCopyBuffer(atree.getClass(), area.createTransformedArea(atree.getAffineTransform()));
3413 } else if (command.equals("Paste area")) {
3414 final Area wa = (Area) DisplayCanvas.getCopyBuffer(atree.getClass());
3415 if (null == wa) return;
3416 try {
3417 getLayerSet().addDataEditStep(atree);
3418 atree.addWorldAreaTo(nd, wa);
3419 atree.calculateBoundingBox(nd.getLayer());
3420 getLayerSet().addDataEditStep(atree);
3421 } catch (final Exception e) {
3422 IJError.print(e);
3423 getLayerSet().removeLastUndoStep();
3425 } else if (command.equals("Interpolate gaps towards parent (node-centric)")) {
3426 interpolate(nd, true);
3427 } else if (command.equals("Interpolate gaps towards parent (absolute)")) {
3428 interpolate(nd, false);
3429 } else if (command.equals("Interpolate all gaps")) {
3430 final GenericDialog gd = new GenericDialog("Interpolate");
3431 final String[] a = new String[]{"node-centric", "absolute"};
3432 gd.addChoice("Mode", a, a[0]);
3433 gd.addCheckbox("Always use distance map", project.getBooleanProperty(AreaUtils.always_interpolate_areas_with_distance_map));
3434 final String[] b = new String[]{"All selected AreaTrees", "Active AreaTree"};
3435 gd.addChoice("Process", b, b[0]);
3436 gd.showDialog();
3437 if (gd.wasCanceled()) return;
3438 final boolean node_centric = 0 == gd.getNextChoiceIndex();
3439 final boolean use_distance_map = gd.getNextBoolean();
3440 final boolean all = 0 == gd.getNextChoiceIndex();
3441 final Set<Displayable> s = new HashSet<Displayable>();
3442 if (all) s.addAll(selection.get(AreaTree.class));
3443 else s.add(atree);
3444 // Store current state for undo
3445 ls.addDataEditStep(s);
3446 try {
3447 for (final Displayable d : s) {
3448 ((AreaTree)d).interpolateAllGaps(node_centric, use_distance_map);
3450 ls.addDataEditStep(s);
3451 } catch (final Exception e) {
3452 IJError.print(e);
3453 ls.undoOneStep();
3455 Display.repaint();
3458 private final void interpolate(final Node<?> nd, final boolean node_centric) {
3459 if (null == nd.getDataCopy() || ((Area)nd.getData()).isEmpty()) {
3460 Utils.log("Can't interpolate: node lacks an area!");
3461 return;
3463 ls.addDataEditStep(atree);
3464 try {
3465 if (atree.interpolateTowardsParent((AreaTree.AreaNode)nd, node_centric, project.getBooleanProperty(AreaUtils.always_interpolate_areas_with_distance_map))) {
3466 ls.addDataEditStep(atree);
3467 } else {
3468 Utils.log("Nothing to interpolate: the parent node already has an area.");
3469 ls.removeLastUndoStep();
3471 } catch (final Exception e) {
3472 IJError.print(e);
3473 ls.undoOneStep();
3475 Display.repaint();
3477 }, atree.getProject());
3481 final JMenu interpolate = new JMenu("Areas");
3482 JMenuItem item = new JMenuItem("Interpolate gaps towards parent (node-centric)"); item.addActionListener(listener); interpolate.add(item);
3483 item = new JMenuItem("Interpolate gaps towards parent (absolute)"); item.addActionListener(listener); interpolate.add(item);
3484 item = new JMenuItem("Interpolate all gaps"); item.addActionListener(listener); interpolate.add(item);
3485 item = new JMenuItem("Area interpolation options..."); item.addActionListener(Display.this); interpolate.add(item);
3486 interpolate.addSeparator();
3487 item = new JMenuItem("Copy area"); item.addActionListener(listener); interpolate.add(item);
3488 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0, true));
3489 item = new JMenuItem("Paste area"); item.addActionListener(listener); interpolate.add(item);
3490 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, 0, true));
3491 item.setEnabled(null != DisplayCanvas.getCopyBuffer(active.getClass()));
3492 popup.add(interpolate);
3495 private void addAreaListAreasMenu(final JPopupMenu popup2, final Displayable active) {
3496 final ActionListener listener = new ActionListener() {
3497 @Override
3498 public void actionPerformed(final ActionEvent ae) {
3499 final String command = ae.getActionCommand();
3500 Bureaucrat.createAndStart(new Worker.Task(command) {
3501 @Override
3502 public void exec() {
3503 if (command.equals("Copy area")) {
3504 if (null == active || !(active instanceof AreaList)) return;
3505 final AreaList ali = (AreaList)active;
3506 final Area area = ali.getArea(getLayer());
3507 if (null == area) return;
3508 DisplayCanvas.setCopyBuffer(ali.getClass(), area.createTransformedArea(ali.getAffineTransform()));
3509 } else if (command.equals("Paste area")) {
3510 if (null == active || !(active instanceof AreaList)) return;
3511 final AreaList ali = (AreaList)active;
3512 final Area wa = (Area) DisplayCanvas.getCopyBuffer(ali.getClass());
3513 if (null == wa) return;
3514 try {
3515 getLayerSet().addDataEditStep(ali);
3516 ali.addArea(getLayer().getId(), wa.createTransformedArea(ali.getAffineTransform().createInverse()));
3517 ali.calculateBoundingBox(getLayer());
3518 getLayerSet().addDataEditStep(ali);
3519 } catch (final NoninvertibleTransformException e) {
3520 IJError.print(e);
3521 getLayerSet().undoOneStep();
3523 } else if (command.equals("Interpolate gaps towards previous area")) {
3524 if (null == active || !(active instanceof AreaList)) return;
3525 final AreaList ali = (AreaList)active;
3526 // Is there an area in this layer?
3527 final Layer current = getLayer();
3528 if (null == ali.getArea(current)) return;
3529 // Find a layer before the current that has an area
3530 final LayerSet ls = getLayerSet();
3531 if (0 == ls.indexOf(current)) return; // already at first
3532 Layer previous = null;
3533 // Iterate layers towards the first layer
3534 for (final ListIterator<Layer> it = ls.getLayers().listIterator(ls.indexOf(current)); it.hasPrevious(); ) {
3535 final Layer la = it.previous();
3536 if (null != ali.getArea(la)) {
3537 previous = la;
3538 break;
3541 if (null == previous) return; // all empty
3542 try {
3543 ls.addDataEditStep(ali);
3544 ali.interpolate(previous, current, project.getBooleanProperty(AreaUtils.always_interpolate_areas_with_distance_map));
3545 ls.addDataEditStep(ali);
3546 } catch (final Exception e) {
3547 IJError.print(e);
3548 ls.undoOneStep();
3550 } else if (command.equals("Interpolate gaps towards next area")) {
3551 if (null == active || !(active instanceof AreaList)) return;
3552 final AreaList ali = (AreaList)active;
3553 // Is there an area in this layer?
3554 final Layer current = getLayer();
3555 if (null == ali.getArea(current)) return;
3556 // Find a layer after the current that has an area
3557 final LayerSet ls = getLayerSet();
3558 if (ls.size() -1 == ls.indexOf(current)) return; // already at the end
3559 Layer next = null;
3560 // Iterate towards the next layer
3561 for (final ListIterator<Layer> it = ls.getLayers().listIterator(ls.indexOf(current)+1); it.hasNext(); ) {
3562 final Layer la = it.next();
3563 if (null != ali.getArea(la)) {
3564 next = la;
3565 break;
3568 if (null == next) return; // all empty
3569 try {
3570 ls.addDataEditStep(ali);
3571 ali.interpolate(current, next, project.getBooleanProperty(AreaUtils.always_interpolate_areas_with_distance_map));
3572 ls.addDataEditStep(ali);
3573 } catch (final Exception e) {
3574 IJError.print(e);
3575 ls.undoOneStep();
3577 } else if (command.equals("Interpolate all gaps")) {
3578 if (null == active || !(active instanceof AreaList)) return;
3579 final AreaList ali = (AreaList)active;
3580 // find the first and last layers with areas
3581 Layer first = null;
3582 Layer last = null;
3583 final LayerSet ls = getLayerSet();
3584 final List<Layer> las = ls.getLayers();
3585 for (final Layer la : las) {
3586 if (null == first && null != ali.getArea(la)) {
3587 first = la;
3588 break;
3591 for (final ListIterator<Layer> it = las.listIterator(las.size()); it.hasPrevious(); ) {
3592 final Layer la = it.previous();
3593 if (null == last && null != ali.getArea(la)) {
3594 last = la;
3595 break;
3598 Utils.log2(first, last);
3599 if (null != first && first != last) {
3600 try {
3601 ls.addDataEditStep(ali);
3602 ali.interpolate(first, last, project.getBooleanProperty(AreaUtils.always_interpolate_areas_with_distance_map));
3603 ls.addDataEditStep(ali);
3604 } catch (final Exception e) {
3605 IJError.print(e);
3606 ls.undoOneStep();
3611 Display.repaint(getLayer());
3613 }, active.getProject());
3617 final JMenu interpolate = new JMenu("Areas");
3618 JMenuItem item = new JMenuItem("Interpolate gaps towards previous area"); item.addActionListener(listener); interpolate.add(item);
3619 item = new JMenuItem("Interpolate gaps towards next area"); item.addActionListener(listener); interpolate.add(item);
3620 item = new JMenuItem("Interpolate all gaps"); item.addActionListener(listener); interpolate.add(item);
3621 item = new JMenuItem("Area interpolation options..."); item.addActionListener(Display.this); interpolate.add(item);
3622 interpolate.addSeparator();
3623 item = new JMenuItem("Copy area"); item.addActionListener(listener); interpolate.add(item);
3624 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0, true));
3625 item = new JMenuItem("Paste area"); item.addActionListener(listener); interpolate.add(item);
3626 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, 0, true));
3627 item.setEnabled(null != DisplayCanvas.getCopyBuffer(active.getClass()));
3628 popup.add(interpolate);
3631 static private final TreeMap<String,Tag> getTags(final Tree tree) {
3632 final Set<Tag> tags = tree.findTags();
3633 if (tags.isEmpty()) {
3634 Utils.log("The nodes of the tree '" + tree + "' don't have any tags!");
3635 return null;
3637 final TreeMap<String,Tag> sm = new TreeMap<String,Tag>();
3638 for (final Tag t : tags) sm.put(t.toString(), t);
3639 return sm;
3641 static private final String[] asStrings(final TreeMap<String,Tag> tags) {
3642 if (null == tags) return null;
3643 final String[] stags = new String[tags.size()];
3644 tags.keySet().toArray(stags);
3645 return stags;
3648 private ActionListener getTreePathMeasureListener(final Tree tree) {
3649 return new ActionListener() {
3650 @Override
3651 public void actionPerformed(final ActionEvent ae) {
3652 final String command = ae.getActionCommand();
3653 if (command.equals("Shortest distances between all pairs of nodes tagged as...")) {
3654 final TreeMap<String,Tag> sm = getTags(tree);
3655 if (null == sm) return;
3656 if (1 == sm.size()) {
3657 Utils.showMessage("Need at least two different tags in the tree!");
3658 return;
3660 final String[] stags = asStrings(sm);
3661 sm.keySet().toArray(stags);
3662 final GenericDialog gd = new GenericDialog("Choose tag");
3663 gd.addChoice("Upstream tag:", stags, stags[0]);
3664 gd.addChoice("Downstream tag:", stags, stags[1]);
3665 gd.addNumericField("Scale:", 1, 2);
3666 final LayerSet ls = tree.getLayerSet();
3667 final int resample = Display3D.estimateResamplingFactor(ls, ls.getLayerWidth(), ls.getLayerHeight());
3668 gd.addSlider("Resample: ", 1, Math.max(resample, 100), resample);
3669 gd.showDialog();
3670 if (gd.wasCanceled()) return;
3671 final Tag upstreamTag = sm.get(gd.getNextChoice());
3672 final Tag downstreamTag = sm.get(gd.getNextChoice());
3673 final List<Tree<?>.MeasurementPair> pairs = tree.measureTaggedPairs(upstreamTag, downstreamTag);
3674 ResultsTable rt = null;
3675 int index = 1;
3676 for (final Tree<?>.MeasurementPair pair : pairs) {
3677 rt = pair.toResultsTable(rt, index++, 1.0, resample);
3678 Utils.showProgress(((double)index) / pairs.size());
3680 if (index > 0) {
3681 rt.show(pairs.get(0).getResultsTableTitle());
3682 } else {
3683 Utils.logAll("No pairs found for '" + upstreamTag + "' and '" + downstreamTag + "'");
3685 return;
3687 // Measurements related to the node under the mouse
3688 final Point p = getCanvas().consumeLastPopupPoint();
3689 final Node clicked = tree.findClosestNodeW(p.x, p.y, getLayer(), canvas.getMagnification());
3690 if (null == clicked) {
3691 final Calibration cal = getLayerSet().getCalibration();
3692 Utils.log("No node found at " + p.x * cal.pixelWidth + ", " + p.y * cal.pixelHeight);
3693 return;
3695 ResultsTable rt = null;
3696 if (command.equals("Distance from this node to root")) {
3697 rt = tree.measurePathDistance(clicked, tree.getRoot(), null);
3698 } else if (command.equals("Distance from this node to the marked node")) {
3699 if (null == tree.getMarked()) {
3700 Utils.log("No marked node!");
3701 return;
3703 rt = tree.measurePathDistance(clicked, tree.getMarked(), null);
3704 } else if (command.equals("Distance from this node to all nodes tagged as...")) {
3705 final Set<Tag> tags = tree.findTags();
3706 if (tags.isEmpty()) {
3707 Utils.log("The nodes of the tree '" + tree + "' don't have any tags!");
3708 return;
3710 final TreeMap<String,Tag> sm = new TreeMap<String,Tag>();
3711 for (final Tag t : tags) sm.put(t.toString(), t);
3712 final String[] stags = new String[sm.size()];
3713 sm.keySet().toArray(stags);
3714 final GenericDialog gd = new GenericDialog("Choose tag");
3715 gd.addChoice("Tag:", stags, stags[0]);
3716 gd.showDialog();
3717 if (gd.wasCanceled()) return;
3718 // So we have a Tag:
3719 final Tag tag = sm.get(gd.getNextChoice());
3720 // Measure distance to each node that has the tag
3721 for (final Node nd : (Collection<Node>)tree.getRoot().getSubtreeNodes()) {
3722 if (nd.hasTag(tag)) {
3723 rt = tree.measurePathDistance(clicked, nd, rt);
3727 if (null == rt) Utils.log("No nodes found!");
3728 else rt.show("Tree path measurements");
3733 private final class GraphMenuListener implements ActionListener {
3734 @Override
3735 public void actionPerformed(final ActionEvent ae) {
3736 final String command = ae.getActionCommand();
3737 final Collection<Displayable> sel = selection.getSelected();
3738 if (null == sel || sel.isEmpty()) return;
3740 Bureaucrat.createAndStart(new Worker.Task(command) {
3741 @Override
3742 public void exec() {
3745 final Collection<Connector> connectors = (Collection<Connector>) (Collection) getLayerSet().getZDisplayables(Connector.class);
3746 final HashSet<Displayable> to_select = new HashSet<Displayable>();
3748 if (command.equals("Select outgoing Connectors")) {
3749 for (final Connector con : connectors) {
3750 final Set<Displayable> origins = con.getOrigins();
3751 origins.retainAll(sel);
3752 if (origins.isEmpty()) continue;
3753 to_select.add(con);
3755 } else if (command.equals("Select incoming Connectors")) {
3756 for (final Connector con : connectors) {
3757 for (final Set<Displayable> targets : con.getTargets()) {
3758 targets.retainAll(sel);
3759 if (targets.isEmpty()) continue;
3760 to_select.add(con);
3763 } else if (command.equals("Select downstream targets")) {
3764 for (final Connector con : connectors) {
3765 final Set<Displayable> origins = con.getOrigins();
3766 origins.retainAll(sel);
3767 if (origins.isEmpty()) continue;
3768 // else, add all targets
3769 for (final Set<Displayable> targets : con.getTargets()) {
3770 to_select.addAll(targets);
3773 } else if (command.equals("Select upstream targets")) {
3774 for (final Connector con : connectors) {
3775 for (final Set<Displayable> targets : con.getTargets()) {
3776 targets.retainAll(sel);
3777 if (targets.isEmpty()) continue;
3778 to_select.addAll(con.getOrigins());
3779 break; // origins will be the same for all targets of 'con'
3784 selection.selectAll(new ArrayList<Displayable>(to_select));
3786 }}, Display.this.project);
3790 protected class GridOverlay {
3791 ArrayList<Line2D> lines = new ArrayList<Line2D>();
3792 int ox=0, oy=0,
3793 width=(int)layer.getLayerWidth(),
3794 height=(int)layer.getLayerHeight(),
3795 xoffset=0, yoffset=0,
3796 tilewidth=100, tileheight=100,
3797 linewidth=1;
3798 boolean visible = true;
3799 Color color = new Color(255,255,0,255); // yellow with full alpha
3801 /** Expects values in pixels. */
3802 void init() {
3803 lines.clear();
3804 // Vertical lines:
3805 if (0 != xoffset) {
3806 lines.add(new Line2D.Float(ox, oy, ox, oy+height));
3808 lines.add(new Line2D.Float(ox+width, oy, ox+width, oy+height));
3809 for (int x = ox + xoffset; x <= ox + width; x += tilewidth) {
3810 lines.add(new Line2D.Float(x, oy, x, oy + height));
3812 // Horizontal lines:
3813 if (0 != yoffset) {
3814 lines.add(new Line2D.Float(ox, oy, ox+width, oy));
3816 lines.add(new Line2D.Float(ox, oy+height, ox+width, oy+height));
3817 for (int y = oy + yoffset; y <= oy + height; y += tileheight) {
3818 lines.add(new Line2D.Float(ox, y, ox + width, y));
3821 protected void paint(final Graphics2D g) {
3822 if (!visible) return;
3823 g.setStroke(new BasicStroke((float)(linewidth/canvas.getMagnification())));
3824 g.setColor(color);
3825 for (final Line2D line : lines) {
3826 g.draw(line);
3829 void setup(final Roi roi) {
3830 final GenericDialog gd = new GenericDialog("Grid overlay");
3831 final Calibration cal = getLayerSet().getCalibration();
3832 gd.addNumericField("Top-left corner X:", ox*cal.pixelWidth, 1, 10, cal.getUnits());
3833 gd.addNumericField("Top-left corner Y:", oy*cal.pixelHeight, 1, 10, cal.getUnits());
3834 gd.addNumericField("Grid total width:", width*cal.pixelWidth, 1, 10, cal.getUnits());
3835 gd.addNumericField("Grid total height:", height*cal.pixelHeight, 1, 10, cal.getUnits());
3836 gd.addCheckbox("Read bounds from ROI", null != roi);
3837 ((Component)gd.getCheckboxes().get(0)).setEnabled(null != roi);
3838 gd.addMessage("");
3839 gd.addNumericField("Tile width:", tilewidth*cal.pixelWidth, 1, 10, cal.getUnits());
3840 gd.addNumericField("Tile height:", tileheight*cal.pixelHeight, 1, 10, cal.getUnits());
3841 gd.addNumericField("Tile offset X:", xoffset*cal.pixelWidth, 1, 10, cal.getUnits());
3842 gd.addNumericField("Tile offset Y:", yoffset*cal.pixelHeight, 1, 10, cal.getUnits());
3843 gd.addMessage("");
3844 gd.addNumericField("Line width:", linewidth, 1, 10, "pixels");
3845 gd.addSlider("Red: ", 0, 255, color.getRed());
3846 gd.addSlider("Green: ", 0, 255, color.getGreen());
3847 gd.addSlider("Blue: ", 0, 255, color.getBlue());
3848 gd.addSlider("Alpha: ", 0, 255, color.getAlpha());
3849 gd.addMessage("");
3850 gd.addCheckbox("Visible", visible);
3851 gd.showDialog();
3852 if (gd.wasCanceled()) return;
3853 this.ox = (int)(gd.getNextNumber() / cal.pixelWidth);
3854 this.oy = (int)(gd.getNextNumber() / cal.pixelHeight);
3855 this.width = (int)(gd.getNextNumber() / cal.pixelWidth);
3856 this.height = (int)(gd.getNextNumber() / cal.pixelHeight);
3857 if (gd.getNextBoolean() && null != roi) {
3858 final Rectangle r = roi.getBounds();
3859 this.ox = r.x;
3860 this.oy = r.y;
3861 this.width = r.width;
3862 this.height = r.height;
3864 this.tilewidth = (int)(gd.getNextNumber() / cal.pixelWidth);
3865 this.tileheight = (int)(gd.getNextNumber() / cal.pixelHeight);
3866 this.xoffset = (int)(gd.getNextNumber() / cal.pixelWidth) % tilewidth;
3867 this.yoffset = (int)(gd.getNextNumber() / cal.pixelHeight) % tileheight;
3868 this.linewidth = (int)gd.getNextNumber();
3869 this.color = new Color((int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
3870 this.visible = gd.getNextBoolean();
3871 init();
3875 protected GridOverlay gridoverlay = null;
3878 private class StartTransformMenuListener implements ActionListener {
3879 @Override
3880 public void actionPerformed(final ActionEvent ae) {
3881 final String command = ae.getActionCommand();
3882 if (command.equals("Transform (affine)")) {
3883 if (null == active) return;
3884 getLayerSet().addTransformStepWithData(selection.getAffected());
3885 setMode(new AffineTransformMode(Display.this));
3886 } else if (command.equals("Transform (non-linear)")) {
3887 if (null == active) return;
3888 getLayerSet().addTransformStepWithData(selection.getAffected());
3889 final List<Displayable> col = selection.getSelected(Patch.class);
3890 for (final Displayable d : col) {
3891 if (d.isLinked()) {
3892 Utils.showMessage("Can't enter manual non-linear transformation mode:\nat least one image is linked.");
3893 return;
3896 setMode(new NonLinearTransformMode(Display.this, col));
3897 } else if (command.equals("Remove coordinate transforms (selected images)")) {
3898 if (null == active) return;
3899 final List<Displayable> col = selection.getSelected(Patch.class);
3900 if (col.isEmpty()) return;
3901 removeCoordinateTransforms( (List<Patch>) (List) col);
3902 } else if (command.equals("Remove coordinate transforms layer-wise")) {
3903 final GenericDialog gd = new GenericDialog("Remove Coordinate Transforms");
3904 gd.addMessage("Remove coordinate transforms");
3905 gd.addMessage("for all images in:");
3906 Utils.addLayerRangeChoices(Display.this.layer, gd);
3907 gd.showDialog();
3908 if (gd.wasCanceled()) return;
3909 final ArrayList<Displayable> patches = new ArrayList<Displayable>();
3910 for (final Layer layer : getLayerSet().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex()+1)) {
3911 patches.addAll(layer.getDisplayables(Patch.class));
3913 removeCoordinateTransforms( (List<Patch>) (List) patches);
3914 } else if (command.equals("Remove rotation, scaling and shear (selected images)")) {
3915 if (null == active) return;
3916 final List<Displayable> col = selection.getSelected(Patch.class);
3917 if (col.isEmpty()) return;
3918 removeScalingRotationShear( (List<Patch>) (List) col);
3919 } else if (command.equals("Remove rotation, scaling and shear layer-wise")) {
3920 // Because we love copy-paste
3921 final GenericDialog gd = new GenericDialog("Remove Scaling/Rotation/Shear");
3922 gd.addMessage("Remove scaling, translation");
3923 gd.addMessage("and shear for all images in:");
3924 Utils.addLayerRangeChoices(Display.this.layer, gd);
3925 gd.showDialog();
3926 if (gd.wasCanceled()) return;
3927 final ArrayList<Displayable> patches = new ArrayList<Displayable>();
3928 for (final Layer layer : getLayerSet().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex()+1)) {
3929 patches.addAll(layer.getDisplayables(Patch.class));
3931 removeScalingRotationShear( (List<Patch>) (List) patches);
3932 } else if (command.equals("Adjust mesh resolution (selected images)")) {
3933 if (null == active) return;
3934 final List<Patch> col = selection.get(Patch.class);
3935 if (col.isEmpty()) return;
3936 final GenericDialog gd = new GenericDialog("Adjust mesh resolution");
3937 gd.addSlider("Mesh resolution:", 2, 512,
3938 ((Patch)(active.getClass() == Patch.class ? active : col.get(0))).getMeshResolution());
3939 gd.showDialog();
3940 if (gd.wasCanceled()) return;
3941 setMeshResolution(col, (int)gd.getNextNumber());
3942 } else if (command.equals("Adjust mesh resolution layer-wise")) {
3943 final GenericDialog gd = new GenericDialog("Adjust mesh resolution");
3944 Utils.addLayerRangeChoices(Display.this.layer, gd);
3945 gd.addSlider("Mesh resolution:", 2, 512, project.getProperty("mesh_resolution", 32));
3946 gd.showDialog();
3947 if (gd.wasCanceled()) return;
3948 final ArrayList<Patch> patches = new ArrayList<Patch>();
3949 for (final Layer layer : getLayerSet().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex()+1)) {
3950 patches.addAll(layer.getAll(Patch.class));
3952 setMeshResolution(patches, (int)gd.getNextNumber());
3953 } else if (command.startsWith("Set coordinate transform of selected image")) { // )) {
3954 if (null == active || !(active instanceof Patch)) return;
3955 CoordinateTransform ct = ((Patch)active).getCoordinateTransform();
3956 if (null == ct) {
3957 Utils.showMessage("The selected image does not have a coordinate transform!");
3958 return;
3960 final List<Patch> patches;
3961 final GenericDialog gd = new GenericDialog("Set coordinate transform");
3962 gd.addChoice(
3963 "Existing coordinate transform",
3964 new String[]{ "Replace", "Append", "Pre-append" },
3965 "Replace" );
3966 gd.addCheckbox("Only the lens distortion correction", false);
3967 if (command.endsWith("to other selected images")) {
3968 patches = selection.get(Patch.class);
3969 patches.remove((Patch)active);
3970 if (patches.isEmpty()) {
3971 Utils.showMessage("Select more than one image!");
3972 return;
3974 gd.showDialog();
3975 if (gd.wasCanceled()) return;
3977 } else if (command.endsWith("layer-wise")) {
3978 Utils.addLayerRangeChoices(Display.this.layer, gd);
3979 gd.showDialog();
3980 if (gd.wasCanceled()) return;
3981 patches = new ArrayList<Patch>();
3982 for (final Layer layer : getLayerSet().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex()+1)) {
3983 patches.addAll(layer.getAll(Patch.class));
3985 } else {
3986 return;
3988 final int existingCT = gd.getNextChoiceIndex();
3989 final boolean only_lens_model = gd.getNextBoolean();
3991 if (only_lens_model) {
3992 ct = findFirstLensDeformationModel(ct);
3993 if (null == ct) {
3994 Utils.showMessage("Could not find a lens distortion correction model\n in image " + active);
3995 return;
3999 setCoordinateTransform(patches, ct, existingCT);
4000 } else if (command.equals("Set affine transform of selected image to other selected images")) {
4001 if (null == active || !(active instanceof Patch)) return;
4002 final AffineTransform aff = active.getAffineTransformCopy();
4003 final HashSet<Layer> layers = new HashSet<Layer>();
4004 final Collection<Displayable> patches = selection.getSelected(Patch.class);
4005 getLayerSet().addTransformStep(patches);
4006 for (final Displayable p : patches) {
4007 if (p == active) continue;
4008 p.setAffineTransform(aff);
4009 layers.add(p.getLayer());
4011 for (final Layer l : layers) {
4012 l.recreateBuckets();
4014 // Current state
4015 getLayerSet().addTransformStep(patches);
4016 } else if (command.equals("Set affine transform of selected image layer-wise")) {
4017 if (null == active || !(active instanceof Patch)) return;
4018 final AffineTransform aff = active.getAffineTransformCopy();
4019 final GenericDialog gd = new GenericDialog("Choose range of layers");
4020 Utils.addLayerRangeChoices(Display.this.layer, gd);
4021 gd.showDialog();
4022 if (gd.wasCanceled()) return;
4023 final ArrayList<Patch> patches = new ArrayList<Patch>();
4024 final List<Layer> layers = getLayerSet().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex()+1);
4025 for (final Layer layer : layers) {
4026 patches.addAll(layer.getAll(Patch.class));
4028 getLayerSet().addTransformStep(patches);
4029 for (final Patch p: patches) {
4030 p.setAffineTransform(aff);
4032 for (final Layer l : layers) {
4033 l.recreateBuckets();
4035 // Current state
4036 getLayerSet().addTransformStep(patches);
4038 repaint();
4040 private NonLinearTransform findFirstLensDeformationModel(CoordinateTransform ct) {
4041 /* unwind CT lists to get the very first actual CT */
4042 while ( CoordinateTransformList.class.isInstance( ct ) )
4043 ct = ( ( CoordinateTransformList< CoordinateTransform > )ct ).get( 0 );
4044 if (ct instanceof NonLinearTransform) return (NonLinearTransform) ct;
4045 // Not found
4046 return null;
4050 public Bureaucrat removeScalingRotationShear(final List<Patch> patches) {
4051 return Bureaucrat.createAndStart(new Worker.Task("Removing coordinate transforms") { @Override
4052 public void exec() {
4053 getLayerSet().addTransformStep(patches);
4054 for (final Patch p : patches) {
4055 final Rectangle box = p.getBoundingBox();
4056 final AffineTransform aff = new AffineTransform();
4057 // translate so that the center remains where it is
4058 aff.setToTranslation(box.x + (box.width - p.getWidth())/2, box.y + (box.height - p.getHeight())/2);
4059 p.setAffineTransform(aff);
4061 getLayerSet().addTransformStep(patches);
4062 Display.repaint();
4063 }}, this.project);
4066 /** Meant for tasks that require setting an undo and regenerating mipmaps.
4067 * The method will NOT run if any Patch is linked.
4069 * @param patches
4070 * @param task
4071 * @param filter
4072 * @param taskTitle
4073 * @return the {@link Bureaucrat} in charge of the task.
4075 public Bureaucrat applyPatchTask(final List<Patch> patches, final String taskTitle, final Operation<Boolean,Patch> task, final Filter<Patch> filter) {
4076 return Bureaucrat.createAndStart(new Worker.Task(taskTitle) { @Override
4077 public void exec() {
4078 final HashSet<Patch> ds = new HashSet<Patch>();
4079 for (final Patch p : patches) {
4080 // Check if any are linked: cannot remove, would break image-to-segmentation relationship
4081 if (p.isLinked()) {
4082 Utils.logAll("Cannot apply task: some images are linked to segmentations!");
4083 return;
4085 if (Thread.currentThread().isInterrupted() || hasQuitted()) return;
4086 if (filter.accept(p)) {
4087 ds.add(p);
4091 if (ds.isEmpty()) {
4092 Utils.log("Nothing to do.");
4093 return;
4096 // Add undo step:
4097 getLayerSet().addDataEditStep(ds);
4099 // Execute
4100 final ArrayList<Future<?>> fus = new ArrayList<Future<?>>();
4101 for (final Patch p : ds) {
4102 if (Thread.currentThread().isInterrupted() || hasQuitted()) return;
4103 if (task.apply(p)) {
4104 fus.add(p.getProject().getLoader().regenerateMipMaps(p)); // queue
4107 Utils.wait(fus);
4109 // Set current state
4110 getLayerSet().addDataEditStep(ds);
4111 }}, project);
4114 public Bureaucrat removeCoordinateTransforms(final List<Patch> patches) {
4115 return applyPatchTask(
4116 patches,
4117 "Removing coordinate transforms",
4118 new Operation<Boolean, Patch>() {
4119 @Override
4120 public Boolean apply(final Patch o) {
4121 o.setCoordinateTransform(null);
4122 return true;
4125 new Filter<Patch>() {
4126 @Override
4127 public boolean accept(final Patch t) {
4128 return t.hasCoordinateTransform();
4133 public Bureaucrat setCoordinateTransform(final List<Patch> patches, final CoordinateTransform ct, final int existingTransform) {
4134 return applyPatchTask(
4135 patches,
4136 "Set coordinate transform",
4137 new Operation<Boolean, Patch>() {
4138 @Override
4139 public Boolean apply(final Patch o) {
4140 switch ( existingTransform )
4142 case CT_REPLACE:
4143 o.setCoordinateTransform(ct);
4144 break;
4145 case CT_APPEND:
4146 o.appendCoordinateTransform(ct);
4147 break;
4148 case CT_PREAPPEND:
4149 o.preAppendCoordinateTransform(ct);
4151 return true;
4154 new Filter<Patch>() {
4155 @Override
4156 public boolean accept(final Patch t) {
4157 return true;
4164 * @depracated Use {@link #setCoordinateTransform(List, CoordinateTransform, int)} instead which implements pre-appending as a third mode.
4166 * @param patches
4167 * @param ct
4168 * @param append
4169 * @return
4171 @Deprecated
4172 public Bureaucrat setCoordinateTransform(final List<Patch> patches, final CoordinateTransform ct, final boolean append) {
4173 return setCoordinateTransform( patches, ct, append ? CT_APPEND : CT_REPLACE );
4176 public Bureaucrat setMeshResolution(final List<Patch> patches, final int meshResolution) {
4177 if (meshResolution < 1) {
4178 Utils.log("Cannot apply a mesh resolution smaller than 1!");
4179 return null;
4181 return applyPatchTask(
4182 patches,
4183 "Alter mesh resolution",
4184 new Operation<Boolean, Patch>() {
4185 @Override
4186 public Boolean apply(final Patch o) {
4187 o.setMeshResolution(meshResolution);
4188 // Return false to avoid regenerating mipmaps when there isn't a CoordinateTransform
4189 return null != o.getCoordinateTransform();
4192 new Filter<Patch>() {
4193 @Override
4194 public boolean accept(final Patch t) {
4195 return t.getMeshResolution() != meshResolution;
4200 private class MenuScriptListener implements ActionListener {
4201 @Override
4202 public void actionPerformed(final ActionEvent ae) {
4203 final String command = ae.getActionCommand();
4204 Bureaucrat.createAndStart(new Worker.Task("Setting preprocessor script") { @Override
4205 public void exec() {
4206 try{
4207 if (command.equals("Set preprocessor script layer-wise...")) {
4208 final Collection<Layer> ls = getLayerList("Set preprocessor script");
4209 if (null == ls) return;
4210 final String path = getScriptPath();
4211 if (null == path) return;
4212 setScriptPathToLayers(ls, path);
4213 } else if (command.equals("Set preprocessor script (selected images)...")) {
4214 if (selection.isEmpty()) return;
4215 final String path = getScriptPath();
4216 if (null == path) return;
4217 setScriptPath(selection.get(Patch.class), path);
4218 } else if (command.equals("Remove preprocessor script layer-wise...")) {
4219 final Collection<Layer> ls = getLayerList("Remove preprocessor script");
4220 if (null == ls) return;
4221 setScriptPathToLayers(ls, null);
4222 } else if (command.equals("Remove preprocessor script (selected images)...")) {
4223 if (selection.isEmpty()) return;
4224 setScriptPath(selection.get(Patch.class), null);
4226 } catch (final Exception e) {
4227 IJError.print(e);
4229 }}, Display.this.project);
4231 private void setScriptPathToLayers(final Collection<Layer> ls, final String script) throws Exception {
4232 final ArrayList<Patch> ds = new ArrayList<Patch>();
4233 for (final Layer la : ls) {
4234 if (Thread.currentThread().isInterrupted()) return;
4235 ds.addAll(la.getAll(Patch.class));
4237 setScriptPath(ds, script); // no lazy sequences ...
4239 /** Accepts null script, to remove it if there. */
4240 private void setScriptPath(final Collection<Patch> list, final String script) throws Exception {
4241 Process.progressive(list, new TaskFactory<Patch,Object>() {
4242 @Override
4243 public Object process(final Patch p) {
4244 p.setPreprocessorScriptPath(script);
4245 try {
4246 p.updateMipMaps().get(); // wait for mipmap regeneration so that the processed image is in cache for mipmap regeneration
4247 } catch (final Throwable t) {
4248 IJError.print(t);
4250 return null;
4252 }, Math.max(1, Runtime.getRuntime().availableProcessors() -1));
4254 private Collection<Layer> getLayerList(final String title) {
4255 final GenericDialog gd = new GenericDialog(title);
4256 Utils.addLayerRangeChoices(Display.this.layer, gd);
4257 gd.showDialog();
4258 if (gd.wasCanceled()) return null;
4259 return layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1); // exclusive end
4261 private String getScriptPath() {
4262 final OpenDialog od = new OpenDialog("Select script", OpenDialog.getLastDirectory(), null);
4263 String dir = od.getDirectory();
4264 if (null == dir) return null;
4265 if (IJ.isWindows()) dir = dir.replace('\\','/');
4266 if (!dir.endsWith("/")) dir += "/";
4267 return dir + od.getFileName();
4271 private class SetToolListener implements ActionListener {
4272 final int tool;
4273 SetToolListener(final int tool) {
4274 this.tool = tool;
4276 @Override
4277 public void actionPerformed(final ActionEvent ae) {
4278 ProjectToolbar.setTool(tool);
4279 toolbar_panel.repaint();
4283 private ByTypeListener bytypelistener = new ByTypeListener(this);
4285 static private class ByTypeListener implements ActionListener {
4286 final Display d;
4287 ByTypeListener(final Display d) {
4288 this.d = d;
4290 @Override
4291 public void actionPerformed(final ActionEvent ae) {
4292 final String command = ae.getActionCommand();
4294 final Area aroi = M.getArea(d.canvas.getFakeImagePlus().getRoi());
4296 d.dispatcher.exec(new Runnable() { @Override
4297 public void run() {
4299 try {
4300 String type = command;
4301 if (type.equals("Image")) type = "Patch";
4302 else if (type.equals("Text")) type = "DLabel";
4303 final Class<?> c = Class.forName("ini.trakem2.display." + type);
4305 final java.util.List<Displayable> a = new ArrayList<Displayable>();
4306 if (null != aroi) {
4307 a.addAll(d.layer.getDisplayables(c, aroi, true));
4308 a.addAll(d.layer.getParent().findZDisplayables(c, d.layer, aroi, true, true));
4309 } else {
4310 a.addAll(d.layer.getDisplayables(c));
4311 a.addAll(d.layer.getParent().getZDisplayables(c));
4312 // Remove non-visible ones
4313 for (final Iterator<Displayable> it = a.iterator(); it.hasNext(); ) {
4314 if (!it.next().isVisible()) it.remove();
4318 if (0 == a.size()) return;
4320 boolean selected = false;
4322 if (0 == ae.getModifiers()) {
4323 Utils.log2("first");
4324 d.selection.clear();
4325 d.selection.selectAll(a);
4326 selected = true;
4327 } else if (0 == (ae.getModifiers() ^ Event.SHIFT_MASK)) {
4328 Utils.log2("with shift");
4329 d.selection.selectAll(a); // just add them to the current selection
4330 selected = true;
4332 if (selected) {
4333 // Activate last:
4334 d.selection.setActive(a.get(a.size() -1));
4337 } catch (final ClassNotFoundException e) {
4338 Utils.log2(e.toString());
4341 }});
4345 /** Check if a panel for the given Displayable is completely visible in the JScrollPane */
4346 public boolean isWithinViewport(final Displayable d) {
4347 final Component comp = tabs.getSelectedComponent();
4348 if (!(comp instanceof RollingPanel)) return false;
4349 final RollingPanel rp = (RollingPanel)tabs.getSelectedComponent();
4350 if (ht_tabs.get(d.getClass()) == rp) return rp.isShowing(d);
4351 return false;
4354 /** Check if a panel for the given Displayable is partially visible in the JScrollPane */
4355 public boolean isPartiallyWithinViewport(final Displayable d) {
4356 final RollingPanel rp = ht_tabs.get(d.getClass());
4357 return rp.isShowing(d);
4360 // for Layer panels
4361 private void scrollToShow(final JScrollPane scroll, final JPanel dp) {
4362 if (null == dp) return;
4363 Utils.invokeLater(new Runnable() { @Override
4364 public void run() {
4365 final JViewport view = scroll.getViewport();
4366 final Point current = view.getViewPosition();
4367 final Dimension extent = view.getExtentSize();
4368 final int panel_y = dp.getY();
4369 if ((panel_y + DisplayablePanel.HEIGHT - current.y) <= extent.height && panel_y >= current.y) {
4370 // it's completely visible already
4371 return;
4372 } else {
4373 // scroll just enough
4374 // if it's above, show at the top
4375 if (panel_y - current.y < 0) {
4376 view.setViewPosition(new Point(0, panel_y));
4378 // if it's below (even if partially), show at the bottom
4379 else if (panel_y + 50 > current.y + extent.height) {
4380 view.setViewPosition(new Point(0, panel_y - extent.height + 50));
4381 //Utils.log("Display.scrollToShow: panel_y: " + panel_y + " current.y: " + current.y + " extent.height: " + extent.height);
4384 }});
4387 /** Update the title of the given Displayable in its DisplayablePanel, if any. */
4388 static public void updateTitle(final Layer layer, final Displayable displ) {
4389 for (final Display d : al_displays) {
4390 if (layer == d.layer) {
4391 final DisplayablePanel dp = d.ht_panels.get(displ);
4392 if (null != dp) dp.updateTitle();
4397 /** Update the Display's title in all Displays showing the given Layer. */
4398 static public void updateTitle(final Layer layer) {
4399 for (final Display d : al_displays) {
4400 if (d.layer == layer) d.updateFrameTitle();
4403 static public void updateTitle(final Project project) {
4404 for (final Display d : al_displays) {
4405 if (d.project == project) d.updateFrameTitle();
4408 /** Update the Display's title in all Displays showing a Layer of the given LayerSet. */
4409 static public void updateTitle(final LayerSet ls) {
4410 for (final Display d : al_displays) {
4411 if (d.layer.getParent() == ls) d.updateFrameTitle(d.layer);
4415 /** Set a new title in the JFrame, showing info on the layer 'z' and the magnification. */
4416 public void updateFrameTitle() {
4417 updateFrameTitle(layer);
4419 private void updateFrameTitle(final Layer layer) {
4420 // From ij.ImagePlus class, the solution:
4421 String scale = "";
4422 final double magnification = canvas.getMagnification();
4423 if (magnification!=1.0) {
4424 final double percent = magnification*100.0;
4425 scale = new StringBuilder(" (").append(Utils.d2s(percent, percent==(int)percent ? 0 : 1)).append("%)").toString();
4427 final LayerSet ls = layer.getParent();
4428 final Calibration cal = ls.getCalibration();
4429 final Layer last = ls.getLayer(ls.size()-1);
4430 final double depth = (last.getZ() - ls.getLayer(0).getZ() + last.getThickness()) * cal.pixelWidth;
4431 final String title = new StringBuilder(100)
4432 .append(layer.getParent().indexOf(layer) + 1).append('/').append(layer.getParent().size())
4433 .append(" z:").append(layer.getZ() * cal.pixelWidth).append(' ').append(cal.getUnits()).append(' ') // Not pixelDepth
4434 .append(' ').append(layer.getLayerThingTitle())
4435 .append(scale)
4436 .append(" -- ").append(getProject().toString())
4437 .append(' ').append(' ').append(Utils.cutNumber(layer.getParent().getLayerWidth() * cal.pixelWidth, 2, true))
4438 .append('x').append(Utils.cutNumber(layer.getParent().getLayerHeight() * cal.pixelHeight, 2, true))
4439 .append('x').append(Utils.cutNumber(depth, 2, true))
4440 .append(' ').append(cal.getUnit()).toString();
4441 Utils.invokeLater(new Runnable() { @Override
4442 public void run() {
4443 frame.setTitle(title);
4444 }});
4445 // fix the title for the FakeImageWindow and thus the WindowManager listing in the menus
4446 canvas.getFakeImagePlus().setTitle(title);
4449 /** 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. */
4450 public void nextLayer(final int modifiers) {
4451 final Layer l;
4452 if (0 == (modifiers ^ Event.SHIFT_MASK)) {
4453 l = layer.getParent().nextNonEmpty(layer);
4454 } else if (scroll_step > 1) {
4455 final int i = layer.getParent().indexOf(this.layer);
4456 final Layer la = layer.getParent().getLayer(i + scroll_step);
4457 if (null != la) l = la;
4458 else l = null;
4459 } else {
4460 l = layer.getParent().next(layer);
4462 if (l != layer) {
4463 slt.set(l);
4464 updateInDatabase("layer_id");
4468 /** Should be invoked within event dispatch thread. */
4469 private final void translateLayerColors(final Layer current, final Layer other) {
4470 if (current == other) return;
4471 if (layer_channels.size() > 0) {
4472 final LayerSet ls = getLayerSet();
4473 // translate colors by distance from current layer to new Layer l
4474 final int dist = ls.indexOf(other) - ls.indexOf(current);
4475 translateLayerColor(Color.red, dist);
4476 translateLayerColor(Color.blue, dist);
4480 private final void translateLayerColor(final Color color, final int dist) {
4481 final LayerSet ls = getLayerSet();
4482 final Layer l = layer_channels.get(color);
4483 if (null == l) return;
4484 updateColor(Color.white, layer_panels.get(l));
4485 final Layer l2 = ls.getLayer(ls.indexOf(l) + dist);
4486 if (null != l2) updateColor(color, layer_panels.get(l2));
4489 private final void updateColor(final Color color, final LayerPanel lp) {
4490 lp.setColor(color);
4491 setColorChannel(lp.layer, color);
4494 /** Calls setLayer(la) on the SetLayerThread. */
4495 public void toLayer(final Layer la) {
4496 if (la.getParent() != layer.getParent()) return; // not of the same LayerSet
4497 if (la == layer) return; // nothing to do
4498 slt.set(la);
4499 updateInDatabase("layer_id");
4502 /** 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. */
4503 public void previousLayer(final int modifiers) {
4504 final Layer l;
4505 if (0 == (modifiers ^ Event.SHIFT_MASK)) {
4506 l = layer.getParent().previousNonEmpty(layer);
4507 } else if (scroll_step > 1) {
4508 final int i = layer.getParent().indexOf(this.layer);
4509 final Layer la = layer.getParent().getLayer(i - scroll_step);
4510 if (null != la) l = la;
4511 else l = null;
4512 } else {
4513 l = layer.getParent().previous(layer);
4515 if (l != layer) {
4516 slt.set(l);
4517 updateInDatabase("layer_id");
4521 static public void updateLayerScroller(final LayerSet set) {
4522 for (final Display d : al_displays) {
4523 if (d.layer.getParent() == set) {
4524 d.updateLayerScroller(d.layer);
4529 private void updateLayerScroller(final Layer layer) {
4530 Utils.invokeLater(new Runnable() { @Override
4531 public void run() {
4532 final int size = layer.getParent().size();
4533 if (size <= 1) {
4534 scroller.setValues(0, 1, 0, 0);
4535 scroller.setEnabled(false);
4536 } else {
4537 scroller.setEnabled(true);
4538 scroller.setValues(layer.getParent().indexOf(layer), 1, 0, size);
4540 recreateLayerPanels(layer);
4541 }});
4544 // Can't use this.layer, may still be null. User argument instead.
4545 private synchronized void recreateLayerPanels(final Layer layer) {
4546 synchronized (layer_channels) {
4547 panel_layers.removeAll();
4549 final GridBagLayout gb = (GridBagLayout) panel_layers.getLayout();
4550 panel_layers.setLayout(gb);
4552 final GridBagConstraints c = new GridBagConstraints();
4553 c.anchor = GridBagConstraints.NORTHWEST;
4554 c.fill = GridBagConstraints.HORIZONTAL;
4555 c.gridx = 0;
4556 c.gridy = 0;
4558 if (0 == layer_panels.size()) {
4559 for (final Layer la : layer.getParent().getLayers()) {
4560 final LayerPanel lp = new LayerPanel(this, la);
4561 layer_panels.put(la, lp);
4562 gb.setConstraints(lp, c);
4563 this.panel_layers.add(lp);
4564 c.gridy += 1;
4566 } else {
4567 // Set theory at work: keep old to reuse
4568 layer_panels.keySet().retainAll(layer.getParent().getLayers());
4569 for (final Layer la : layer.getParent().getLayers()) {
4570 LayerPanel lp = layer_panels.get(la);
4571 if (null == lp) {
4572 lp = new LayerPanel(this, la);
4573 layer_panels.put(la, lp);
4575 gb.setConstraints(lp, c);
4576 this.panel_layers.add(lp);
4577 c.gridy += 1;
4579 for (final Iterator<Map.Entry<Integer,LayerPanel>> it = layer_alpha.entrySet().iterator(); it.hasNext(); ) {
4580 final Map.Entry<Integer,LayerPanel> e = it.next();
4581 if (-1 == getLayerSet().indexOf(e.getValue().layer)) it.remove();
4583 for (final Iterator<Map.Entry<Color,Layer>> it = layer_channels.entrySet().iterator(); it.hasNext(); ) {
4584 final Map.Entry<Color,Layer> e = it.next();
4585 if (-1 == getLayerSet().indexOf(e.getValue())) it.remove();
4587 scroll_layers.repaint();
4592 private void updateSnapshots() {
4593 Utils.invokeLater(new Runnable() { @Override
4594 public void run() {
4595 final Enumeration<DisplayablePanel> e = ht_panels.elements();
4596 while (e.hasMoreElements()) {
4597 e.nextElement().repaint();
4599 Utils.updateComponent(tabs.getSelectedComponent());
4600 }});
4603 static public void updatePanel(Layer layer, final Displayable displ) {
4604 if (null == layer && null != front) layer = front.layer; // the front layer
4605 for (final Display d : al_displays) {
4606 if (d.layer == layer) {
4607 d.updatePanel(displ);
4612 private void updatePanel(final Displayable d) {
4613 JPanel c = null;
4614 if (d instanceof Profile) {
4615 c = panel_profiles;
4616 } else if (d instanceof Patch) {
4617 c = panel_patches;
4618 } else if (d instanceof DLabel) {
4619 c = panel_labels;
4620 } else if (d instanceof Pipe) {
4621 c = panel_zdispl;
4623 if (null == c) return;
4624 final DisplayablePanel dp = ht_panels.get(d);
4625 if (null != dp) {
4626 dp.repaint();
4627 Utils.updateComponent(c);
4631 @Deprecated
4632 static public void updatePanelIndex(final Layer layer, final Displayable displ) {
4633 for (final Display d : al_displays) {
4634 if (d.layer == layer || displ instanceof ZDisplayable) {
4635 d.updatePanelIndex(displ);
4640 @Deprecated
4641 private void updatePanelIndex(final Displayable d) {
4642 Utils.invokeLater(new Runnable() { @Override
4643 public void run() {
4644 ht_tabs.get(d.getClass()).updateList();
4645 }});
4648 /** Repair possibly missing panels and other components by simply resetting the same Layer */
4649 public void repairGUI() {
4650 setLayer(layer, true);
4653 @Override
4654 public void actionPerformed(final ActionEvent ae) {
4655 dispatcher.exec(new Runnable() { @Override
4656 public void run() {
4658 final String command = ae.getActionCommand();
4659 if (command.startsWith("Job")) {
4660 if (Utils.checkYN("Really cancel job?")) {
4661 project.getLoader().quitJob(command);
4662 repairGUI();
4664 return;
4665 } else if (command.equals("Move to top")) {
4666 if (null == active) return;
4667 canvas.setUpdateGraphics(true);
4668 getLayerSet().addUndoMoveStep(active);
4669 layer.getParent().move(LayerSet.TOP, active);
4670 getLayerSet().addUndoMoveStep(active);
4671 Display.repaint(layer.getParent(), active, 5);
4672 //Display.updatePanelIndex(layer, active);
4673 } else if (command.equals("Move up")) {
4674 if (null == active) return;
4675 canvas.setUpdateGraphics(true);
4676 getLayerSet().addUndoMoveStep(active);
4677 layer.getParent().move(LayerSet.UP, active);
4678 getLayerSet().addUndoMoveStep(active);
4679 Display.repaint(layer.getParent(), active, 5);
4680 //Display.updatePanelIndex(layer, active);
4681 } else if (command.equals("Move down")) {
4682 if (null == active) return;
4683 canvas.setUpdateGraphics(true);
4684 getLayerSet().addUndoMoveStep(active);
4685 layer.getParent().move(LayerSet.DOWN, active);
4686 getLayerSet().addUndoMoveStep(active);
4687 Display.repaint(layer.getParent(), active, 5);
4688 //Display.updatePanelIndex(layer, active);
4689 } else if (command.equals("Move to bottom")) {
4690 if (null == active) return;
4691 canvas.setUpdateGraphics(true);
4692 getLayerSet().addUndoMoveStep(active);
4693 layer.getParent().move(LayerSet.BOTTOM, active);
4694 getLayerSet().addUndoMoveStep(active);
4695 Display.repaint(layer.getParent(), active, 5);
4696 //Display.updatePanelIndex(layer, active);
4697 } else if (command.equals("Duplicate, link and send to next layer")) {
4698 duplicateLinkAndSendTo(active, 1, layer.getParent().next(layer));
4699 } else if (command.equals("Duplicate, link and send to previous layer")) {
4700 duplicateLinkAndSendTo(active, 0, layer.getParent().previous(layer));
4701 } else if (command.equals("Duplicate, link and send to...")) {
4702 // fix non-scrolling popup menu
4703 Utils.invokeLater(new Runnable() { @Override
4704 public void run() {
4705 final GenericDialog gd = new GenericDialog("Send to");
4706 gd.addMessage("Duplicate, link and send to...");
4707 final String[] sl = new String[layer.getParent().size()];
4708 int next = 0;
4709 for (final Layer la : layer.getParent().getLayers()) {
4710 sl[next++] = project.findLayerThing(la).toString();
4712 gd.addChoice("Layer: ", sl, sl[layer.getParent().indexOf(layer)]);
4713 gd.showDialog();
4714 if (gd.wasCanceled()) return;
4715 final Layer la = layer.getParent().getLayer(gd.getNextChoiceIndex());
4716 if (layer == la) {
4717 Utils.showMessage("Can't duplicate, link and send to the same layer.");
4718 return;
4720 duplicateLinkAndSendTo(active, 0, la);
4721 }});
4722 } else if (-1 != command.indexOf("z = ")) {
4723 // this is an item from the "Duplicate, link and send to" menu of layer z's
4724 final Layer target_layer = layer.getParent().getLayer(Double.parseDouble(command.substring(command.lastIndexOf(' ') +1)));
4725 Utils.log2("layer: __" +command.substring(command.lastIndexOf(' ') +1) + "__");
4726 if (null == target_layer) return;
4727 duplicateLinkAndSendTo(active, 0, target_layer);
4728 } else if (-1 != command.indexOf("z=")) {
4729 // WARNING the indexOf is very similar to the previous one
4730 // Send the linked group to the selected layer
4731 final int iz = command.indexOf("z=")+2;
4732 Utils.log2("iz=" + iz + " other: " + command.indexOf(' ', iz+2));
4733 int end = command.indexOf(' ', iz);
4734 if (-1 == end) end = command.length();
4735 final double lz = Double.parseDouble(command.substring(iz, end));
4736 final Layer target = layer.getParent().getLayer(lz);
4737 layer.getParent().move(selection.getAffected(), active.getLayer(), target); // TODO what happens when ZDisplayable are selected?
4738 } else if (command.equals("Unlink")) {
4739 if (null == active || active instanceof Patch) return;
4740 active.unlink();
4741 updateSelection();//selection.update();
4742 } else if (command.equals("Unlink from images")) {
4743 if (null == active) return;
4744 try {
4745 for (final Displayable displ: selection.getSelected()) {
4746 displ.unlinkAll(Patch.class);
4748 updateSelection();//selection.update();
4749 } catch (final Exception e) { IJError.print(e); }
4750 } else if (command.equals("Unlink slices")) {
4751 final YesNoCancelDialog yn = new YesNoCancelDialog(frame, "Attention", "Really unlink all slices from each other?\nThere is no undo.");
4752 if (!yn.yesPressed()) return;
4753 final ArrayList<Patch> pa = ((Patch)active).getStackPatches();
4754 for (int i=pa.size()-1; i>0; i--) {
4755 pa.get(i).unlink(pa.get(i-1));
4757 } else if (command.equals("Send to next layer")) {
4758 final Rectangle box = selection.getBox();
4759 try {
4760 // unlink Patch instances
4761 for (final Displayable displ : selection.getSelected()) {
4762 displ.unlinkAll(Patch.class);
4764 updateSelection();//selection.update();
4765 } catch (final Exception e) { IJError.print(e); }
4766 //layer.getParent().moveDown(layer, active); // will repaint whatever appropriate layers
4767 selection.moveDown();
4768 repaint(layer.getParent(), box);
4769 } else if (command.equals("Send to previous layer")) {
4770 final Rectangle box = selection.getBox();
4771 try {
4772 // unlink Patch instances
4773 for (final Displayable displ : selection.getSelected()) {
4774 displ.unlinkAll(Patch.class);
4776 updateSelection();//selection.update();
4777 } catch (final Exception e) { IJError.print(e); }
4778 //layer.getParent().moveUp(layer, active); // will repaint whatever appropriate layers
4779 selection.moveUp();
4780 repaint(layer.getParent(), box);
4781 } else if (command.equals("Show centered")) {
4782 if (active == null) return;
4783 showCentered(active);
4784 } else if (command.equals("Delete...")) {
4785 // remove all selected objects
4786 selection.deleteAll();
4787 } else if (command.equals("Color...")) {
4788 IJ.doCommand("Color Picker...");
4789 } else if (command.equals("Revert")) {
4790 if (null == active || active.getClass() != Patch.class) return;
4791 final Patch p = (Patch)active;
4792 if (!p.revert()) {
4793 if (null == p.getOriginalPath()) Utils.log("No editions to save for patch " + p.getTitle() + " #" + p.getId());
4794 else Utils.log("Could not revert Patch " + p.getTitle() + " #" + p.getId());
4796 } else if (command.equals("Remove alpha mask")) {
4797 Display.removeAlphaMasks(selection.get(Patch.class));
4798 } else if (command.equals("Undo")) {
4799 Bureaucrat.createAndStart(new Worker.Task("Undo") { @Override
4800 public void exec() {
4801 layer.getParent().undoOneStep();
4802 Display.repaint(layer.getParent());
4803 }}, project);
4804 } else if (command.equals("Redo")) {
4805 Bureaucrat.createAndStart(new Worker.Task("Redo") { @Override
4806 public void exec() {
4807 layer.getParent().redoOneStep();
4808 Display.repaint(layer.getParent());
4809 }}, project);
4810 } else if (command.equals("Apply transform")) {
4811 canvas.applyTransform();
4812 } else if (command.equals("Apply transform propagating to last layer")) {
4813 if (mode.getClass() == AffineTransformMode.class || mode.getClass() == NonLinearTransformMode.class) {
4814 final LayerSet ls = getLayerSet();
4815 final HashSet<Layer> subset = new HashSet<Layer>(ls.getLayers(ls.indexOf(Display.this.layer)+1, ls.size()-1)); // +1 to exclude current layer
4816 if (mode.getClass() == AffineTransformMode.class) ((AffineTransformMode)mode).applyAndPropagate(subset);
4817 else if (mode.getClass() == NonLinearTransformMode.class) ((NonLinearTransformMode)mode).apply(subset);
4818 setMode(new DefaultMode(Display.this));
4820 } else if (command.equals("Apply transform propagating to first layer")) {
4821 if (mode.getClass() == AffineTransformMode.class || mode.getClass() == NonLinearTransformMode.class) {
4822 final LayerSet ls = getLayerSet();
4823 final HashSet<Layer> subset = new HashSet<Layer>(ls.getLayers(0, ls.indexOf(Display.this.layer) -1)); // -1 to exclude current layer
4824 if (mode.getClass() == AffineTransformMode.class) ((AffineTransformMode)mode).applyAndPropagate(subset);
4825 else if (mode.getClass() == NonLinearTransformMode.class) ((NonLinearTransformMode)mode).apply(subset);
4826 setMode(new DefaultMode(Display.this));
4828 } else if (command.equals("Cancel transform")) {
4829 canvas.cancelTransform(); // calls getMode().cancel()
4830 } else if (command.equals("Specify transform...")) {
4831 if (null == active) return;
4832 selection.specify();
4833 } else if (command.equals("Exit inspection")) {
4834 getMode().cancel();
4835 setMode(new DefaultMode(Display.this));
4836 } else if (command.equals("Inspect image mesh triangles")) {
4837 setMode(new InspectPatchTrianglesMode(Display.this));
4838 } else if (command.equals("Hide all but images")) {
4839 final ArrayList<Class<?>> type = new ArrayList<Class<?>>();
4840 type.add(Patch.class);
4841 type.add(Stack.class);
4842 final Collection<Displayable> col = layer.getParent().hideExcept(type, false);
4843 selection.removeAll(col);
4844 Display.updateCheckboxes(col, DisplayablePanel.VISIBILITY_STATE);
4845 Display.update(layer.getParent(), false);
4846 } else if (command.equals("Unhide all")) {
4847 Display.updateCheckboxes(layer.getParent().setAllVisible(false), DisplayablePanel.VISIBILITY_STATE);
4848 Display.update(layer.getParent(), false);
4849 } else if (command.startsWith("Hide all ")) {
4850 final String type = command.substring(9, command.length() -1); // skip the ending plural 's'
4851 final Collection<Displayable> col = layer.getParent().setVisible(type, false, true);
4852 selection.removeAll(col);
4853 Display.updateCheckboxes(col, DisplayablePanel.VISIBILITY_STATE);
4854 } else if (command.startsWith("Unhide all ")) {
4855 String type = command.substring(11, command.length() -1); // skip the ending plural 's'
4856 type = type.substring(0, 1).toUpperCase() + type.substring(1);
4857 updateCheckboxes(layer.getParent().setVisible(type, true, true), DisplayablePanel.VISIBILITY_STATE);
4858 } else if (command.equals("Hide deselected")) {
4859 hideDeselected(0 != (ActionEvent.ALT_MASK & ae.getModifiers()));
4860 } else if (command.equals("Hide deselected except images")) {
4861 hideDeselected(true);
4862 } else if (command.equals("Hide selected")) {
4863 selection.setVisible(false); // TODO should deselect them too? I don't think so.
4864 Display.updateCheckboxes(selection.getSelected(), DisplayablePanel.VISIBILITY_STATE);
4865 } else if (command.equals("Resize canvas/LayerSet...")) {
4866 resizeCanvas();
4867 } else if (command.equals("Autoresize canvas/LayerSet")) {
4868 layer.getParent().setMinimumDimensions();
4869 } else if (command.equals("Resize canvas/LayerSet to ROI")) {
4870 final Roi roi = canvas.getFakeImagePlus().getRoi();
4871 if (null == roi) {
4872 Utils.log("No ROI present!");
4873 return;
4875 resizeCanvas(roi.getBounds());
4876 } else if (command.equals("Import image")) {
4877 importImage();
4878 } else if (command.equals("Import next image")) {
4879 importNextImage();
4880 } else if (command.equals("Import stack...")) {
4881 Display.this.getLayerSet().addChangeTreesStep();
4882 final Rectangle sr = getCanvas().getSrcRect();
4883 final Bureaucrat burro = project.getLoader().importStack(layer, sr.x + sr.width/2, sr.y + sr.height/2, null, true, null, false);
4884 burro.addPostTask(new Runnable() { @Override
4885 public void run() {
4886 Display.this.getLayerSet().addChangeTreesStep();
4887 }});
4888 } else if (command.equals("Import stack with landmarks...")) {
4889 // 1 - Find out if there's any other project open
4890 final List<Project> pr = Project.getProjects();
4891 if (1 == pr.size()) {
4892 Utils.logAll("Need another project open!");
4893 return;
4895 // 2 - Ask for a "landmarks" type
4896 final GenericDialog gd = new GenericDialog("Landmarks");
4897 gd.addStringField("landmarks type:", "landmarks");
4898 final String[] none = {"-- None --"};
4899 final Hashtable<String,Project> mpr = new Hashtable<String,Project>();
4900 for (final Project p : pr) {
4901 if (p == project) continue;
4902 mpr.put(p.toString(), p);
4904 final String[] project_titles = mpr.keySet().toArray(new String[0]);
4906 final Hashtable<String,ProjectThing> map_target = findLandmarkNodes(project, "landmarks");
4907 final String[] target_landmark_titles = map_target.isEmpty() ? none : map_target.keySet().toArray(new String[0]);
4908 gd.addChoice("Landmarks node in this project:", target_landmark_titles, target_landmark_titles[0]);
4910 gd.addMessage("");
4911 gd.addChoice("Source project:", project_titles, project_titles[0]);
4913 final Hashtable<String,ProjectThing> map_source = findLandmarkNodes(mpr.get(project_titles[0]), "landmarks");
4914 final String[] source_landmark_titles = map_source.isEmpty() ? none : map_source.keySet().toArray(new String[0]);
4915 gd.addChoice("Landmarks node in source project:", source_landmark_titles, source_landmark_titles[0]);
4917 final List<Patch> stacks = Display.getPatchStacks(mpr.get(project_titles[0]).getRootLayerSet());
4919 String[] stack_titles;
4920 if (stacks.isEmpty()) {
4921 if (1 == mpr.size()) {
4922 IJ.showMessage("Project " + project_titles[0] + " does not contain any Stack.");
4923 return;
4925 stack_titles = none;
4926 } else {
4927 stack_titles = new String[stacks.size()];
4928 int next = 0;
4929 for (final Patch pa : stacks) stack_titles[next++] = pa.toString();
4931 gd.addChoice("Stacks:", stack_titles, stack_titles[0]);
4933 final Vector<?> vc = gd.getChoices();
4934 final Choice choice_target_landmarks = (Choice) vc.get(0);
4935 final Choice choice_source_projects = (Choice) vc.get(1);
4936 final Choice choice_source_landmarks = (Choice) vc.get(2);
4937 final Choice choice_stacks = (Choice) vc.get(3);
4939 final TextField input = (TextField) gd.getStringFields().get(0);
4940 input.addTextListener(new TextListener() {
4941 @Override
4942 public void textValueChanged(final TextEvent te) {
4943 final String text = input.getText();
4944 update(choice_target_landmarks, Display.this.project, text, map_target);
4945 update(choice_source_landmarks, mpr.get(choice_source_projects.getSelectedItem()), text, map_source);
4947 private void update(final Choice c, final Project p, final String type, final Hashtable<String,ProjectThing> table) {
4948 table.clear();
4949 table.putAll(findLandmarkNodes(p, type));
4950 c.removeAll();
4951 if (table.isEmpty()) c.add(none[0]);
4952 else for (final String t : table.keySet()) c.add(t);
4956 choice_source_projects.addItemListener(new ItemListener() {
4957 @Override
4958 public void itemStateChanged(final ItemEvent e) {
4959 final String item = (String) e.getItem();
4960 final Project p = mpr.get(choice_source_projects.getSelectedItem());
4961 // 1 - Update choice of landmark items
4962 map_source.clear();
4963 map_source.putAll(findLandmarkNodes(p, input.getText()));
4964 choice_target_landmarks.removeAll();
4965 if (map_source.isEmpty()) choice_target_landmarks.add(none[0]);
4966 else for (final String t : map_source.keySet()) choice_target_landmarks.add(t);
4967 // 2 - Update choice of Stack items
4968 stacks.clear();
4969 choice_stacks.removeAll();
4970 stacks.addAll(Display.getPatchStacks(mpr.get(project_titles[0]).getRootLayerSet()));
4971 if (stacks.isEmpty()) choice_stacks.add(none[0]);
4972 else for (final Patch pa : stacks) choice_stacks.add(pa.toString());
4976 gd.showDialog();
4977 if (gd.wasCanceled()) return;
4979 final String type = gd.getNextString();
4980 if (null == type || 0 == type.trim().length()) {
4981 Utils.log("Invalid landmarks node type!");
4982 return;
4984 final ProjectThing target_landmarks_node = map_target.get(gd.getNextChoice());
4985 final Project source = mpr.get(gd.getNextChoice());
4986 final ProjectThing source_landmarks_node = map_source.get(gd.getNextChoice());
4987 final Patch stack_patch = stacks.get(gd.getNextChoiceIndex());
4989 // Store current state
4990 Display.this.getLayerSet().addLayerContentStep(layer);
4992 // Insert stack
4993 insertStack(target_landmarks_node, source, source_landmarks_node, stack_patch);
4995 // Store new state
4996 Display.this.getLayerSet().addChangeTreesStep();
4997 } else if (command.equals("Import grid...")) {
4998 Display.this.getLayerSet().addLayerContentStep(layer);
4999 final Bureaucrat burro = project.getLoader().importGrid(layer);
5000 if (null != burro)
5001 burro.addPostTask(new Runnable() { @Override
5002 public void run() {
5003 Display.this.getLayerSet().addLayerContentStep(layer);
5004 }});
5005 } else if (command.equals("Import sequence as grid...")) {
5006 Display.this.getLayerSet().addChangeTreesStep();
5007 final Bureaucrat burro = project.getLoader().importSequenceAsGrid(layer);
5008 if (null != burro)
5009 burro.addPostTask(new Runnable() { @Override
5010 public void run() {
5011 Display.this.getLayerSet().addChangeTreesStep();
5012 }});
5013 } else if (command.equals("Import from text file...")) {
5014 Display.this.getLayerSet().addChangeTreesStep();
5015 final Bureaucrat burro = project.getLoader().importImages(layer);
5016 if (null != burro)
5017 burro.addPostTask(new Runnable() { @Override
5018 public void run() {
5019 Display.this.getLayerSet().addChangeTreesStep();
5020 }});
5021 } else if (command.equals("Import labels as arealists...")) {
5022 Display.this.getLayerSet().addChangeTreesStep();
5023 final Bureaucrat burro = project.getLoader().importLabelsAsAreaLists(layer, null, Double.MAX_VALUE, 0, 0.4f, false);
5024 burro.addPostTask(new Runnable() { @Override
5025 public void run() {
5026 Display.this.getLayerSet().addChangeTreesStep();
5027 }});
5028 } else if (command.equals("Make flat image...")) {
5029 // if there's a ROI, just use that as cropping rectangle
5030 Rectangle srcRect = null;
5031 final Roi roi = canvas.getFakeImagePlus().getRoi();
5032 if (null != roi) {
5033 srcRect = roi.getBounds();
5034 } else {
5035 // otherwise, whatever is visible
5036 //srcRect = canvas.getSrcRect();
5037 // The above is confusing. That is what ROIs are for. So paint all:
5038 srcRect = new Rectangle(0, 0, (int)Math.ceil(layer.getParent().getLayerWidth()), (int)Math.ceil(layer.getParent().getLayerHeight()));
5040 double scale = 1.0;
5041 final String[] types = new String[]{"8-bit grayscale", "RGB Color"};
5042 int the_type = ImagePlus.GRAY8;
5043 final GenericDialog gd = new GenericDialog("Choose", frame);
5044 gd.addSlider("Scale: ", 1, 100, 100);
5045 gd.addNumericField("Width: ", srcRect.width, 0);
5046 gd.addNumericField("height: ", srcRect.height, 0);
5047 // connect the above 3 fields:
5048 final Vector<?> numfields = gd.getNumericFields();
5049 final UpdateDimensionField udf = new UpdateDimensionField(srcRect.width, srcRect.height, (TextField) numfields.get(1), (TextField) numfields.get(2), (TextField) numfields.get(0), (Scrollbar) gd.getSliders().get(0));
5050 for (final Object ob : numfields) ((TextField)ob).addTextListener(udf);
5052 gd.addChoice("Type: ", types, types[0]);
5053 if (layer.getParent().size() > 1) {
5054 Utils.addLayerRangeChoices(Display.this.layer, gd); /// $#%! where are my lisp macros
5055 gd.addCheckbox("Include non-empty layers only", true);
5057 gd.addMessage("Background color:");
5058 Utils.addRGBColorSliders(gd, Color.black);
5059 gd.addCheckbox("Best quality", false);
5060 gd.addMessage("");
5061 final String[] choices = new String[]{"Show", "Save to file", "Save for web (CATMAID)"};
5062 gd.addChoice("Export:", choices, choices[0]);
5063 final String[] formats = Saver.formats();
5064 gd.addChoice("Format:", formats, formats[0]);
5065 gd.addNumericField("Tile_side", 256, 0);
5066 final Choice cformats = (Choice)gd.getChoices().get(gd.getChoices().size() -1);
5067 cformats.setEnabled(false);
5068 final Choice cchoices = (Choice)gd.getChoices().get(gd.getChoices().size() -2);
5069 final TextField tf = (TextField)gd.getNumericFields().get(gd.getNumericFields().size() -1);
5070 tf.setEnabled(false);
5071 cchoices.addItemListener(new ItemListener() {
5072 @Override
5073 public void itemStateChanged(final ItemEvent e) {
5074 cformats.setEnabled(cchoices.getSelectedIndex() > 0);
5075 if (2 == cchoices.getSelectedIndex()) {
5076 cformats.select(".jpg");
5077 tf.setEnabled(true);
5078 } else {
5079 tf.setEnabled(false);
5083 gd.addCheckbox("Use original images", true);
5084 gd.showDialog();
5085 if (gd.wasCanceled()) return;
5087 scale = gd.getNextNumber() / 100;
5088 the_type = (0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY8 : ImagePlus.COLOR_RGB);
5089 if (Double.isNaN(scale) || scale <= 0.0) {
5090 Utils.showMessage("Invalid scale.");
5091 return;
5094 // consuming and ignoring width and height:
5095 gd.getNextNumber();
5096 gd.getNextNumber();
5098 Layer[] layer_array = null;
5099 boolean non_empty_only = false;
5100 if (layer.getParent().size() > 1) {
5101 non_empty_only = gd.getNextBoolean();
5102 final int i_start = gd.getNextChoiceIndex();
5103 final int i_end = gd.getNextChoiceIndex();
5104 final ArrayList<Layer> al = new ArrayList<Layer>();
5105 final ArrayList<ZDisplayable> al_zd = layer.getParent().getZDisplayables();
5106 final ZDisplayable[] zd = new ZDisplayable[al_zd.size()];
5107 al_zd.toArray(zd);
5108 for (int i=i_start, j=0; i <= i_end; i++, j++) {
5109 final Layer la = layer.getParent().getLayer(i);
5110 if (!la.isEmpty() || !non_empty_only) al.add(la); // checks both the Layer and the ZDisplayable objects in the parent LayerSet
5112 if (0 == al.size()) {
5113 Utils.showMessage("All layers are empty!");
5114 return;
5116 layer_array = new Layer[al.size()];
5117 al.toArray(layer_array);
5118 } else {
5119 layer_array = new Layer[]{Display.this.layer};
5121 final Color background = new Color((int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
5122 final boolean quality = gd.getNextBoolean();
5124 final int choice = gd.getNextChoiceIndex();
5125 final boolean save_to_file = 1 == choice;
5126 final boolean save_for_web = 2 == choice;
5127 final String format = gd.getNextChoice();
5128 final Saver saver = new Saver(format);
5129 final int tile_side = (int)gd.getNextNumber();
5130 final boolean use_original_images = gd.getNextBoolean();
5131 // in its own thread
5132 if (save_for_web) project.getLoader().makePrescaledTiles(layer_array, Patch.class, srcRect, scale, c_alphas, the_type, null, use_original_images, saver, tile_side);
5133 else project.getLoader().makeFlatImage(layer_array, srcRect, scale, c_alphas, the_type, save_to_file, format, quality, background);
5135 } else if (command.equals("Lock")) {
5136 selection.setLocked(true);
5137 Utils.revalidateComponent(tabs.getSelectedComponent());
5138 } else if (command.equals("Unlock")) {
5139 selection.setLocked(false);
5140 Utils.revalidateComponent(tabs.getSelectedComponent());
5141 } else if (command.equals("Properties...")) {
5142 switch(selection.getSelected().size()) {
5143 case 0:
5144 return;
5145 case 1:
5146 active.adjustProperties();
5147 break;
5148 default:
5149 adjustGroupProperties(selection.getSelected());
5150 break;
5152 updateSelection();
5153 } else if (command.equals("Measurement options...")) {
5154 adjustMeasurementOptions();
5155 } else if (command.equals("Show current 2D position in 3D")) {
5156 final Point p = canvas.consumeLastPopupPoint();
5157 if (null == p) return;
5158 Display3D.addFatPoint("Current 2D Position", getLayerSet(), p.x, p.y, layer.getZ(), 10, Color.magenta);
5159 } else if (command.equals("Show layers as orthoslices in 3D")) {
5160 final GenericDialog gd = new GenericDialog("Options");
5161 final Roi roi = canvas.getFakeImagePlus().getRoi();
5162 final Rectangle r = null == roi ? getLayerSet().get2DBounds() : roi.getBounds();
5163 gd.addMessage("ROI 2D bounds:");
5164 gd.addNumericField("x:", r.x, 0, 30, "pixels");
5165 gd.addNumericField("y:", r.y, 0, 30, "pixels");
5166 gd.addNumericField("width:", r.width, 0, 30, "pixels");
5167 gd.addNumericField("height:", r.height, 0, 30, "pixels");
5168 gd.addMessage("Layers to include:");
5169 Utils.addLayerRangeChoices(layer, gd);
5170 gd.addMessage("Constrain dimensions to:");
5171 gd.addNumericField("max width and height:", getLayerSet().getPixelsMaxDimension(), 0, 30, "pixels");
5172 gd.addMessage("Options:");
5173 final String[] types = {"Greyscale", "Color RGB"};
5174 gd.addChoice("Image type:", types, types[0]);
5175 gd.addCheckbox("Invert images", false);
5176 gd.showDialog();
5177 if (gd.wasCanceled()) return;
5178 final int x = (int)gd.getNextNumber(),
5179 y = (int)gd.getNextNumber(),
5180 width = (int)gd.getNextNumber(),
5181 height = (int)gd.getNextNumber();
5182 final int first = gd.getNextChoiceIndex(),
5183 last = gd.getNextChoiceIndex();
5184 final List<Layer> layers = getLayerSet().getLayers(first, last);
5185 final int max_dim = Math.min((int)gd.getNextNumber(), Math.max(width, height));
5186 float scale = 1;
5187 if (max_dim < Math.max(width, height)) {
5188 scale = max_dim / (float)Math.max(width, height);
5190 final int type = 0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY8 : ImagePlus.COLOR_RGB;
5191 final boolean invert = gd.getNextBoolean();
5192 final LayerStack stack = new LayerStack(layers, new Rectangle(x, y, width, height), scale, type, Patch.class, max_dim, invert);
5193 Display3D.showOrthoslices(stack.getImagePlus(), "LayerSet [" + x + "," + y + "," + width + "," + height + "] " + first + "--" + last, x, y, scale, layers.get(0));
5194 } else if (command.equals("Align stack slices")) {
5195 if (getActive() instanceof Patch) {
5196 final Patch slice = (Patch)getActive();
5197 if (slice.isStack()) {
5198 // check linked group
5199 final HashSet hs = slice.getLinkedGroup(new HashSet());
5200 for (final Iterator it = hs.iterator(); it.hasNext(); ) {
5201 if (it.next().getClass() != Patch.class) {
5202 Utils.showMessage("Images are linked to other objects, can't proceed to cross-correlate them."); // labels should be fine, need to check that
5203 return;
5206 final LayerSet ls = slice.getLayerSet();
5207 final HashSet<Displayable> linked = slice.getLinkedGroup(null);
5208 ls.addTransformStepWithData(linked);
5209 final Bureaucrat burro = AlignTask.registerStackSlices((Patch)getActive()); // will repaint
5210 burro.addPostTask(new Runnable() { @Override
5211 public void run() {
5212 ls.enlargeToFit(linked);
5213 // The current state when done
5214 ls.addTransformStepWithData(linked);
5215 }});
5216 } else {
5217 Utils.log("Align stack slices: selected image is not part of a stack.");
5220 } else if (command.equals("Align layers manually with landmarks")) {
5221 setMode(new ManualAlignMode(Display.this));
5222 } else if (command.equals("Align layers")) {
5223 Roi roi = canvas.getFakeImagePlus().getRoi();
5224 if (null != roi) {
5225 final YesNoCancelDialog yn = new YesNoCancelDialog(frame, "Use ROI?", "Snapshot layers using the ROI bounds?\n" + roi.getBounds());
5226 if (yn.cancelPressed()) return;
5227 if (!yn.yesPressed()) {
5228 roi = null;
5231 final Layer la = layer; // caching, since scroll wheel may change it
5232 la.getParent().addTransformStep(la.getParent().getLayers());
5233 final Bureaucrat burro = AlignLayersTask.alignLayersTask( la, null == roi ? null : roi.getBounds() );
5234 burro.addPostTask(new Runnable() { @Override
5235 public void run() {
5236 getLayerSet().enlargeToFit(getLayerSet().getDisplayables(Patch.class));
5237 la.getParent().addTransformStep(la.getParent().getLayers());
5238 }});
5239 } else if (command.equals("Align multi-layer mosaic")) {
5240 final Layer la = layer; // caching, since scroll wheel may change it
5241 la.getParent().addTransformStep();
5242 final Bureaucrat burro = AlignTask.alignMultiLayerMosaicTask( la, active instanceof Patch ? (Patch)active : null );
5243 burro.addPostTask(new Runnable() { @Override
5244 public void run() {
5245 getLayerSet().enlargeToFit(getLayerSet().getDisplayables(Patch.class));
5246 la.getParent().addTransformStep();
5247 }});
5248 } else if (command.equals("Montage all images in this layer")) {
5249 final Layer la = layer;
5250 final List<Patch> patches = new ArrayList<Patch>( (List<Patch>) (List) la.getDisplayables(Patch.class, true));
5251 if (patches.size() < 2) {
5252 Utils.showMessage("Montage needs 2 or more visible images");
5253 return;
5255 final Collection<Displayable> col = la.getParent().addTransformStepWithDataForAll(Arrays.asList(new Layer[]{la}));
5257 // find any locked or selected patches
5258 final HashSet<Patch> fixed = new HashSet<Patch>();
5259 for (final Patch p : patches) {
5260 if (p.isLocked2() || selection.contains(p)) fixed.add(p);
5263 if (patches.size() == fixed.size()) {
5264 Utils.showMessage("Can't do", "No montage possible: all images are selected,\nand hence all are considered locked.\nSelect only one image to be used as reference, or none.");
5265 return;
5268 Utils.log("Using " + fixed.size() + " image" + (fixed.size() == 1 ? "" : "s") + " as reference.");
5270 final Bureaucrat burro = AlignTask.alignPatchesTask(patches, fixed);
5271 burro.addPostTask(new Runnable() { @Override
5272 public void run() {
5273 getLayerSet().enlargeToFit(patches);
5274 la.getParent().addTransformStepWithData(col);
5275 }});
5276 } else if (command.equals("Montage selected images")) {
5277 final Layer la = layer;
5278 if (selection.getSelected(Patch.class).size() < 2) {
5279 Utils.showMessage("Montage needs 2 or more images selected");
5280 return;
5282 final Collection<Displayable> col = la.getParent().addTransformStepWithDataForAll(Arrays.asList(new Layer[]{la}));
5283 final Bureaucrat burro = AlignTask.alignSelectionTask(selection);
5284 if (null == burro) return;
5285 burro.addPostTask(new Runnable() { @Override
5286 public void run() {
5287 la.getParent().enlargeToFit(selection.getAffected());
5288 la.getParent().addTransformStepWithData(col);
5289 }});
5290 } else if (command.equals("Montage multiple layers")) {
5291 final GenericDialog gd = new GenericDialog("Choose range");
5292 Utils.addLayerRangeChoices(Display.this.layer, gd);
5293 gd.showDialog();
5294 if (gd.wasCanceled()) return;
5295 final List<Layer> layers = getLayerSet().getLayers(gd.getNextChoiceIndex(), gd.getNextChoiceIndex());
5296 final Collection<Displayable> col = getLayerSet().addTransformStepWithDataForAll(layers);
5297 final Bureaucrat burro = AlignTask.montageLayersTask(layers);
5298 burro.addPostTask(new Runnable() { @Override
5299 public void run() {
5300 final Collection<Displayable> ds = new ArrayList<Displayable>();
5301 for (final Layer la : layers) ds.addAll(la.getDisplayables(Patch.class));
5302 getLayerSet().enlargeToFit(ds);
5303 getLayerSet().addTransformStepWithData(col);
5304 }});
5305 } else if (command.equals("Properties ...")) { // NOTE the space before the dots, to distinguish from the "Properties..." command that works on Displayable objects.
5306 adjustProperties();
5307 } else if (command.equals("Adjust snapping parameters...")) {
5308 AlignTask.p_snap.setup("Snap");
5309 } else if (command.equals("Adjust fast-marching parameters...")) {
5310 Segmentation.fmp.setup();
5311 } else if (command.equals("Adjust arealist paint parameters...")) {
5312 AreaWrapper.PP.setup();
5313 } else if (command.equals("Fill ROI in alpha mask")) {
5314 if (active.getClass() == Patch.class) {
5315 ((Patch)active).keyPressed(new KeyEvent(getCanvas(), -1, System.currentTimeMillis(), 0, KeyEvent.VK_F, 'f'));
5317 } else if (command.equals("Fill inverse ROI in alpha mask")) {
5318 if (active.getClass() == Patch.class) {
5319 ((Patch)active).keyPressed(new KeyEvent(getCanvas(), -1, System.currentTimeMillis(), Event.SHIFT_MASK, KeyEvent.VK_F, 'f'));
5321 } else if (command.equals("Search...")) {
5322 Search.showWindow();
5323 } else if (command.equals("Select all")) {
5324 selection.selectAll();
5325 repaint(Display.this.layer, selection.getBox(), 0);
5326 } else if (command.equals("Select all visible")) {
5327 selection.selectAllVisible();
5328 repaint(Display.this.layer, selection.getBox(), 0);
5329 } else if (command.equals("Select all that match...")) {
5330 final List<Displayable> ds = find();
5331 selection.selectAll(ds);
5332 Utils.showStatus("Added " + ds.size() + " to selection.");
5333 } else if (command.equals("Select none")) {
5334 final Rectangle box = selection.getBox();
5335 selection.clear();
5336 repaint(Display.this.layer, box, 0);
5337 } else if (command.equals("Restore selection")) {
5338 selection.restore();
5339 } else if (command.equals("Select under ROI")) {
5340 final Roi roi = canvas.getFakeImagePlus().getRoi();
5341 if (null == roi) return;
5342 selection.selectAll(roi, true);
5343 } else if (command.equals("Merge")) {
5344 final Bureaucrat burro = Bureaucrat.create(new Worker.Task("Merging AreaLists") {
5345 @Override
5346 public void exec() {
5347 final ArrayList<Displayable> al_sel = selection.getSelected(AreaList.class);
5348 // put active at the beginning, to work as the base on which other's will get merged
5349 al_sel.remove(Display.this.active);
5350 al_sel.add(0, Display.this.active);
5351 final Set<DoStep> dataedits = new HashSet<DoStep>();
5352 dataedits.add(new Displayable.DoEdit(Display.this.active).init(Display.this.active, new String[]{"data"}));
5353 getLayerSet().addChangeTreesStep(dataedits);
5354 final AreaList ali = AreaList.merge(al_sel);
5355 if (null != ali) {
5356 // remove all but the first from the selection
5357 for (int i=1; i<al_sel.size(); i++) {
5358 final Object ob = al_sel.get(i);
5359 if (ob.getClass() == AreaList.class) {
5360 selection.remove((Displayable)ob);
5363 selection.updateTransform(ali);
5364 repaint(ali.getLayerSet(), ali, 0);
5367 }, Display.this.project);
5368 burro.addPostTask(new Runnable() { @Override
5369 public void run() {
5370 final Set<DoStep> dataedits = new HashSet<DoStep>();
5371 dataedits.add(new Displayable.DoEdit(Display.this.active).init(Display.this.active, new String[]{"data"}));
5372 getLayerSet().addChangeTreesStep(dataedits);
5373 }});
5374 burro.goHaveBreakfast();
5375 } else if (command.equals("Reroot")) {
5376 if (!(active instanceof Tree<?>)) return;
5377 getLayerSet().addDataEditStep(active);
5378 if (((Tree)active).reRoot(((Tree)active).getLastVisited())) {
5379 getLayerSet().addDataEditStep(active);
5380 Display.repaint(getLayerSet());
5381 } else {
5382 getLayerSet().removeLastUndoStep();
5384 } else if (command.equals("Part subtree")) {
5385 if (!(active instanceof Tree<?>)) return;
5386 if (!Utils.check("Really part the subtree?")) return;
5387 final LayerSet.DoChangeTrees step = getLayerSet().addChangeTreesStep();
5388 final Set<DoStep> deps = new HashSet<DoStep>();
5389 deps.add(new Displayable.DoEdit(active).init(active, new String[]{"data"})); // I hate java
5390 step.addDependents(deps);
5391 final List<ZDisplayable> ts = ((Tree)active).splitAt(((Tree)active).getLastVisited());
5392 if (null == ts) {
5393 getLayerSet().removeLastUndoStep();
5394 return;
5396 final Displayable elder = Display.this.active;
5397 final HashSet<DoStep> deps2 = new HashSet<DoStep>();
5398 for (final ZDisplayable t : ts) {
5399 deps2.add(new Displayable.DoEdit(t).init(t, new String[]{"data"}));
5400 if (t == elder) continue;
5401 getLayerSet().add(t); // will change Display.this.active !
5402 project.getProjectTree().addSibling(elder, t);
5404 selection.clear();
5405 selection.selectAll(ts);
5406 selection.add(elder);
5407 final LayerSet.DoChangeTrees step2 = getLayerSet().addChangeTreesStep();
5408 step2.addDependents(deps2);
5409 Display.repaint(getLayerSet());
5410 } else if (command.equals("Show tabular view")) {
5411 if (!(active instanceof Tree<?>)) return;
5412 ((Tree<?>)active).createMultiTableView();
5413 } else if (command.equals("Mark")) {
5414 if (!(active instanceof Tree<?>)) return;
5415 final Point p = canvas.consumeLastPopupPoint();
5416 if (null == p) return;
5417 if (((Tree<?>)active).markNear(p.x, p.y, layer, canvas.getMagnification())) {
5418 Display.repaint(getLayerSet());
5420 } else if (command.equals("Clear marks (selected Trees)")) {
5421 for (final Tree<?> t : selection.get(Tree.class)) {
5422 t.unmark();
5424 Display.repaint(getLayerSet());
5425 } else if (command.equals("Join")) {
5426 if (!(active instanceof Tree<?>)) return;
5427 final List<Tree<?>> tlines = (List<Tree<?>>) selection.get(active.getClass());
5428 if (((Tree)active).canJoin(tlines)) {
5429 final int nNodes_active = ((Tree)active).getRoot().getSubtreeNodes().size();
5430 String warning = "";
5431 for (final Tree<?> t : tlines) {
5432 if (active == t) continue;
5433 if (null == t.getRoot()) {
5434 Utils.log("Removed empty tree #" + t.getId() + " from those to join.");
5435 tlines.remove(t);
5436 continue;
5438 if (t.getRoot().getSubtreeNodes().size() > nNodes_active) {
5439 warning = "\nWARNING joining into a tree that is not the largest!";
5440 break;
5443 if (!Utils.check("Join these " + tlines.size() + " trees into the tree " + active + " ?" + warning)) return;
5444 // Record current state
5445 final Set<DoStep> dataedits = new HashSet<DoStep>(tlines.size());
5446 for (final Tree<?> tl : tlines) {
5447 dataedits.add(new Displayable.DoEdit(tl).init(tl, new String[]{"data"}));
5449 getLayerSet().addChangeTreesStep(dataedits);
5451 ((Tree)active).join(tlines);
5452 for (final Tree<?> tl : tlines) {
5453 if (tl == active) continue;
5454 tl.remove2(false);
5456 Display.repaint(getLayerSet());
5457 // Again, to record current state (just the joined tree this time)
5458 final Set<DoStep> dataedits2 = new HashSet<DoStep>(1);
5459 dataedits2.add(new Displayable.DoEdit(active).init(active, new String[]{"data"}));
5460 getLayerSet().addChangeTreesStep(dataedits2);
5461 } else {
5462 Utils.showMessage("Can't do", "Only one tree is selected.\nSelect more than one tree to perform a join operation!");
5464 } else if (command.equals("Previous branch node or start")) {
5465 if (!(active instanceof Tree<?>)) return;
5466 final Point p = canvas.consumeLastPopupPoint();
5467 if (null == p) return;
5468 center(((Treeline)active).findPreviousBranchOrRootPoint(p.x, p.y, layer, canvas));
5469 } else if (command.equals("Next branch node or end")) {
5470 if (!(active instanceof Tree<?>)) return;
5471 final Point p = canvas.consumeLastPopupPoint();
5472 if (null == p) return;
5473 center(((Tree<?>)active).findNextBranchOrEndPoint(p.x, p.y, layer, canvas));
5474 } else if (command.equals("Root")) {
5475 if (!(active instanceof Tree<?>)) return;
5476 final Point p = canvas.consumeLastPopupPoint();
5477 if (null == p) return;
5478 center(((Tree)active).createCoordinate(((Tree<?>)active).getRoot()));
5479 } else if (command.equals("Last added node")) {
5480 if (!(active instanceof Tree<?>)) return;
5481 center(((Treeline)active).getLastAdded());
5482 } else if (command.equals("Last edited node")) {
5483 if (!(active instanceof Tree<?>)) return;
5484 center(((Treeline)active).getLastEdited());
5485 } else if (command.equals("Reverse point order")) {
5486 if (!(active instanceof Pipe)) return;
5487 getLayerSet().addDataEditStep(active);
5488 ((Pipe)active).reverse();
5489 Display.repaint(Display.this.layer);
5490 getLayerSet().addDataEditStep(active);
5491 } else if (command.equals("View orthoslices")) {
5492 if (!(active instanceof Patch)) return;
5493 Display3D.showOrthoslices(((Patch)active));
5494 } else if (command.equals("View volume")) {
5495 if (!(active instanceof Patch)) return;
5496 Display3D.showVolume(((Patch)active));
5497 } else if (command.equals("Show in 3D")) {
5498 for (final ZDisplayable zd : selection.get(ZDisplayable.class)) {
5499 Display3D.show(zd.getProject().findProjectThing(zd));
5501 // handle profile lists ...
5502 final HashSet<ProjectThing> hs = new HashSet<ProjectThing>();
5503 for (final Profile d : selection.get(Profile.class)) {
5504 final ProjectThing profile_list = (ProjectThing)d.getProject().findProjectThing(d).getParent();
5505 if (!hs.contains(profile_list)) {
5506 Display3D.show(profile_list);
5507 hs.add(profile_list);
5510 } else if (command.equals("Snap")) {
5511 // Take the active if it's a Patch
5512 if (!(active instanceof Patch)) return;
5513 Display.snap((Patch)active);
5514 } else if (command.equals("Blend") || command.equals("Blend (selected images)...")) {
5515 final HashSet<Patch> patches = new HashSet<Patch>(selection.get(Patch.class));
5516 if (patches.size() > 1) {
5517 final GenericDialog gd = new GenericDialog("Blending");
5518 gd.addCheckbox("Respect current alpha mask", true);
5519 gd.showDialog();
5520 if (gd.wasCanceled()) return;
5521 Blending.blend(patches, gd.getNextBoolean());
5522 } else {
5523 IJ.log("Please select more than one overlapping image.");
5525 } else if (command.equals("Blend (layer-wise)...")) {
5526 final GenericDialog gd = new GenericDialog("Blending");
5527 Utils.addLayerRangeChoices(Display.this.layer, gd);
5528 gd.addCheckbox("Respect current alpha mask", true);
5529 gd.addMessage("Filter:");
5530 gd.addStringField("Use only images whose title matches:", "", 30);
5531 gd.addCheckbox("Blend visible patches only", true);
5532 gd.showDialog();
5533 if (gd.wasCanceled()) return;
5534 final boolean respect_alpha_mask = gd.getNextBoolean();
5535 final String toMatch = gd.getNextString().trim();
5536 final String regex = 0 == toMatch.length() ? null : ".*" + toMatch + ".*";
5537 final boolean visible_only = gd.getNextBoolean();
5538 Blending.blendLayerWise(getLayerSet().getLayers(gd.getNextChoiceIndex(), gd.getNextChoiceIndex()),
5539 respect_alpha_mask,
5540 new Filter<Patch>() {
5541 @Override
5542 public final boolean accept(final Patch patch) {
5543 if (visible_only && !patch.isVisible()) return false;
5544 if (null == regex) return true;
5545 return patch.getTitle().matches(regex);
5548 } else if (command.equals("Match intensities (layer-wise)...")) {
5549 Bureaucrat.createAndStart(new Worker.Task("Match intensities") {
5550 @Override
5551 public void exec() {
5552 final MatchIntensities matching = new MatchIntensities();
5553 matching.invoke(getActive());
5555 }, project);
5556 } else if (command.equals("Remove intensity maps (layer-wise)...")) {
5557 final GenericDialog gd = new GenericDialog("Remove intensity maps");
5558 Utils.addLayerRangeChoices(Display.this.layer, gd);
5559 gd.showDialog();
5560 if (gd.wasCanceled()) return;
5561 Bureaucrat.createAndStart(new Worker.Task("Match intensities") {
5562 @Override
5563 public void exec() {
5564 for (final Layer layer : getLayerSet().getLayers(gd.getNextChoiceIndex(), gd.getNextChoiceIndex())) {
5565 for (final Displayable p : layer.getDisplayables(Patch.class)) {
5566 final Patch patch = (Patch)p;
5567 if (patch.clearIntensityMap()) {
5568 patch.updateMipMaps();
5573 }, project);
5574 } else if (command.equals("Montage")) {
5575 final Set<Displayable> affected = new HashSet<Displayable>(selection.getAffected());
5576 // make an undo step!
5577 final LayerSet ls = layer.getParent();
5578 ls.addTransformStepWithData(affected);
5579 final Bureaucrat burro = AlignTask.alignSelectionTask( selection );
5580 burro.addPostTask(new Runnable() { @Override
5581 public void run() {
5582 ls.enlargeToFit(affected);
5583 ls.addTransformStepWithData(affected);
5584 }});
5585 } else if (command.equals("Lens correction")) {
5586 final Layer la = layer;
5587 la.getParent().addDataEditStep(new HashSet<Displayable>(la.getParent().getDisplayables()));
5588 final Bureaucrat burro = DistortionCorrectionTask.correctDistortionFromSelection( selection );
5589 burro.addPostTask(new Runnable() { @Override
5590 public void run() {
5591 // no means to know which where modified and from which layers!
5592 la.getParent().addDataEditStep(new HashSet<Displayable>(la.getParent().getDisplayables()));
5593 }});
5594 } else if (command.equals("Link images...")) {
5595 final GenericDialog gd = new GenericDialog("Options");
5596 gd.addMessage("Linking images to images (within their own layer only):");
5597 final String[] options = {"all images to all images", "each image with any other overlapping image"};
5598 gd.addChoice("Link: ", options, options[1]);
5599 final String[] options2 = {"selected images only", "all images in this layer", "all images in all layers, within the layer only", "all images in all layers, within and across consecutive layers"};
5600 gd.addChoice("Apply to: ", options2, options2[0]);
5601 gd.showDialog();
5602 if (gd.wasCanceled()) return;
5603 final Layer lay = layer;
5604 final HashSet<Displayable> ds = new HashSet<Displayable>(lay.getParent().getDisplayables());
5605 lay.getParent().addDataEditStep(ds, new String[]{"data"});
5606 final boolean overlapping_only = 1 == gd.getNextChoiceIndex();
5607 Collection<Displayable> coll = null;
5608 switch (gd.getNextChoiceIndex()) {
5609 case 0:
5610 coll = selection.getSelected(Patch.class);
5611 Patch.crosslink(coll, overlapping_only);
5612 break;
5613 case 1:
5614 coll = lay.getDisplayables(Patch.class);
5615 Patch.crosslink(coll, overlapping_only);
5616 break;
5617 case 2:
5618 coll = new ArrayList<Displayable>();
5619 for (final Layer la : lay.getParent().getLayers()) {
5620 final Collection<Displayable> acoll = la.getDisplayables(Patch.class);
5621 Patch.crosslink(acoll, overlapping_only);
5622 coll.addAll(acoll);
5624 break;
5625 case 3:
5626 final ArrayList<Layer> layers = lay.getParent().getLayers();
5627 Collection<Displayable> lc1 = layers.get(0).getDisplayables(Patch.class);
5628 if (lay == layers.get(0)) coll = lc1;
5629 for (int i=1; i<layers.size(); i++) {
5630 final Collection<Displayable> lc2 = layers.get(i).getDisplayables(Patch.class);
5631 if (null == coll && Display.this.layer == layers.get(i)) coll = lc2;
5632 final Collection<Displayable> both = new ArrayList<Displayable>();
5633 both.addAll(lc1);
5634 both.addAll(lc2);
5635 Patch.crosslink(both, overlapping_only);
5636 lc1 = lc2;
5638 break;
5640 if (null != coll) Display.updateCheckboxes(coll, DisplayablePanel.LINK_STATE, true);
5641 lay.getParent().addDataEditStep(ds);
5642 } else if (command.equals("Unlink all selected images")) {
5643 if (Utils.check("Really unlink selected images?")) {
5644 final Collection<Displayable> ds = selection.getSelected(Patch.class);
5645 for (final Displayable d : ds) {
5646 d.unlink();
5648 Display.updateCheckboxes(ds, DisplayablePanel.LINK_STATE);
5650 } else if (command.equals("Unlink all")) {
5651 if (Utils.check("Really unlink all objects from all layers?")) {
5652 final Collection<Displayable> ds = layer.getParent().getDisplayables();
5653 for (final Displayable d : ds) {
5654 d.unlink();
5656 Display.updateCheckboxes(ds, DisplayablePanel.LINK_STATE);
5658 } else if (command.equals("Calibration...")) {
5659 try {
5660 IJ.run(canvas.getFakeImagePlus(), "Properties...", "");
5661 Display.updateTitle(getLayerSet());
5662 project.getLayerTree().updateUILater(); // repaint layer names
5663 } catch (final RuntimeException re) {
5664 Utils.log2("Calibration dialog canceled.");
5666 } else if (command.equals("Grid overlay...")) {
5667 if (null == gridoverlay) gridoverlay = new GridOverlay();
5668 gridoverlay.setup(canvas.getFakeImagePlus().getRoi());
5669 canvas.repaint(false);
5670 } else if (command.equals("Enhance contrast (selected images)...")) {
5671 final Layer la = layer;
5672 final ArrayList<Displayable> selected = selection.getSelected(Patch.class);
5673 final HashSet<Displayable> ds = new HashSet<Displayable>(selected);
5674 la.getParent().addDataEditStep(ds);
5675 final Displayable active = Display.this.getActive();
5676 final Patch ref = active.getClass() == Patch.class ? (Patch)active : null;
5677 final Bureaucrat burro = getProject().getLoader().enhanceContrast(selected, ref);
5678 burro.addPostTask(new Runnable() { @Override
5679 public void run() {
5680 la.getParent().addDataEditStep(ds);
5681 }});
5682 } else if (command.equals("Enhance contrast layer-wise...")) {
5683 // ask for range of layers
5684 final GenericDialog gd = new GenericDialog("Choose range");
5685 Utils.addLayerRangeChoices(Display.this.layer, gd);
5686 gd.showDialog();
5687 if (gd.wasCanceled()) return;
5688 final java.util.List<Layer> layers = layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1); // exclusive end
5689 final HashSet<Displayable> ds = new HashSet<Displayable>();
5690 for (final Layer l : layers) ds.addAll(l.getDisplayables(Patch.class));
5691 getLayerSet().addDataEditStep(ds);
5692 final Bureaucrat burro = project.getLoader().enhanceContrast(layers);
5693 burro.addPostTask(new Runnable() { @Override
5694 public void run() {
5695 getLayerSet().addDataEditStep(ds);
5696 }});
5697 } else if (command.equals("Adjust image filters (selected images)")) {
5698 if (selection.isEmpty() || !(active instanceof Patch)) return;
5699 FilterEditor.GUI(selection.get(Patch.class), (Patch)active);
5700 } else if (command.equals("Set Min and Max layer-wise...")) {
5701 final Displayable active = getActive();
5702 double min = 0;
5703 double max = 0;
5704 if (null != active && active.getClass() == Patch.class) {
5705 min = ((Patch)active).getMin();
5706 max = ((Patch)active).getMax();
5708 final GenericDialog gd = new GenericDialog("Min and Max");
5709 gd.addMessage("Set min and max to all images in the layer range");
5710 Utils.addLayerRangeChoices(Display.this.layer, gd);
5711 gd.addNumericField("min: ", min, 2);
5712 gd.addNumericField("max: ", max, 2);
5713 gd.showDialog();
5714 if (gd.wasCanceled()) return;
5716 min = gd.getNextNumber();
5717 max = gd.getNextNumber();
5718 final ArrayList<Displayable> al = new ArrayList<Displayable>();
5719 for (final Layer la : layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1)) { // exclusive end
5720 al.addAll(la.getDisplayables(Patch.class));
5722 final HashSet<Displayable> ds = new HashSet<Displayable>(al);
5723 getLayerSet().addDataEditStep(ds);
5724 final Bureaucrat burro = project.getLoader().setMinAndMax(al, min, max);
5725 burro.addPostTask(new Runnable() { @Override
5726 public void run() {
5727 getLayerSet().addDataEditStep(ds);
5728 }});
5729 } else if (command.equals("Set Min and Max (selected images)...")) {
5730 final Displayable active = getActive();
5731 double min = 0;
5732 double max = 0;
5733 if (null != active && active.getClass() == Patch.class) {
5734 min = ((Patch)active).getMin();
5735 max = ((Patch)active).getMax();
5737 final GenericDialog gd = new GenericDialog("Min and Max");
5738 gd.addMessage("Set min and max to all selected images");
5739 gd.addNumericField("min: ", min, 2);
5740 gd.addNumericField("max: ", max, 2);
5741 gd.showDialog();
5742 if (gd.wasCanceled()) return;
5744 min = gd.getNextNumber();
5745 max = gd.getNextNumber();
5746 final HashSet<Displayable> ds = new HashSet<Displayable>(selection.getSelected(Patch.class));
5747 getLayerSet().addDataEditStep(ds);
5748 final Bureaucrat burro = project.getLoader().setMinAndMax(selection.getSelected(Patch.class), min, max);
5749 burro.addPostTask(new Runnable() { @Override
5750 public void run() {
5751 getLayerSet().addDataEditStep(ds);
5752 }});
5753 } else if (command.equals("Adjust min and max (selected images)...")) {
5754 adjustMinAndMaxGUI();
5755 } else if (command.equals("Mask image borders (layer-wise)...")) {
5756 final GenericDialog gd = new GenericDialog("Mask borders");
5757 Utils.addLayerRangeChoices(Display.this.layer, gd);
5758 gd.addMessage("Borders:");
5759 gd.addNumericField("left: ", 6, 2);
5760 gd.addNumericField("top: ", 6, 2);
5761 gd.addNumericField("right: ", 6, 2);
5762 gd.addNumericField("bottom: ", 6, 2);
5763 gd.showDialog();
5764 if (gd.wasCanceled()) return;
5765 final Collection<Layer> layers = layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1);
5766 final HashSet<Displayable> ds = new HashSet<Displayable>();
5767 for (final Layer l : layers) ds.addAll(l.getDisplayables(Patch.class));
5768 getLayerSet().addDataEditStep(ds);
5769 final Bureaucrat burro = project.getLoader().maskBordersLayerWise(layers, (int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
5770 burro.addPostTask(new Runnable() { @Override
5771 public void run() {
5772 getLayerSet().addDataEditStep(ds);
5773 }});
5774 } else if (command.equals("Mask image borders (selected images)...")) {
5775 final GenericDialog gd = new GenericDialog("Mask borders");
5776 gd.addMessage("Borders:");
5777 gd.addNumericField("left: ", 6, 2);
5778 gd.addNumericField("top: ", 6, 2);
5779 gd.addNumericField("right: ", 6, 2);
5780 gd.addNumericField("bottom: ", 6, 2);
5781 gd.showDialog();
5782 if (gd.wasCanceled()) return;
5783 final Collection<Displayable> patches = selection.getSelected(Patch.class);
5784 final HashSet<Displayable> ds = new HashSet<Displayable>(patches);
5785 getLayerSet().addDataEditStep(ds);
5786 final Bureaucrat burro = project.getLoader().maskBorders(patches, (int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
5787 burro.addPostTask(new Runnable() { @Override
5788 public void run() {
5789 getLayerSet().addDataEditStep(ds);
5790 }});
5791 } else if (command.equals("Remove alpha masks (layer-wise)...")) {
5792 final GenericDialog gd = new GenericDialog("Remove alpha masks");
5793 Utils.addLayerRangeChoices(Display.this.layer, gd);
5794 gd.addCheckbox("Visible only", true);
5795 gd.showDialog();
5796 if (gd.wasCanceled()) return;
5797 final Collection<Layer> layers = layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1);
5798 final boolean visible_only = gd.getNextBoolean();
5799 final Collection<Patch> patches = new ArrayList<Patch>();
5800 for (final Layer l : layers) {
5801 patches.addAll((Collection<Patch>)(Collection)l.getDisplayables(Patch.class, visible_only));
5803 Display.removeAlphaMasks(patches);
5804 } else if (command.equals("Remove alpha masks (selected images)...")) {
5805 Display.removeAlphaMasks(selection.get(Patch.class));
5806 } else if (command.equals("Split images under polyline ROI")) {
5807 final Roi roi = canvas.getFakeImagePlus().getRoi();
5808 if (null == roi) return;
5809 if (roi.getType() != Roi.POLYLINE) {
5810 Utils.showMessage("Need a polyline ROI, not just any ROI!");
5811 return;
5813 if (!Utils.check("Really split images under the polyline ROI?")) {
5814 return;
5816 // OK identify images whose contour intersects the ROI
5817 final Set<Displayable> col = new HashSet<Displayable>();
5818 final PolygonRoi proi = (PolygonRoi)roi;
5819 final int[] x = proi.getXCoordinates(),
5820 y = proi.getYCoordinates();
5821 final Rectangle b = proi.getBounds();
5822 final Polygon[] pols = new Polygon[proi.getNCoordinates() -1];
5823 for (int i=0; i<pols.length; i++) {
5824 pols[i] = new Polygon(new int[]{b.x + x[i], b.x + x[i] + 1, b.x + x[i+1], b.x + x[i+1] + 1},
5825 new int[]{b.y + y[i], b.y + y[i], b.y + y[i+1], b.y + y[i+1]}, 4);
5827 for (final Patch p : getLayer().getAll(Patch.class)) {
5828 if (!p.isVisible()) continue;
5829 final Area a = p.getArea();
5830 for (int i=0; i<pols.length; i++) {
5831 final Area c = new Area(pols[i]);
5832 c.intersect(a);
5833 if (M.isEmpty(c)) continue;
5834 // Else, add it:
5835 col.add(p);
5836 break;
5840 if (col.isEmpty()) {
5841 Utils.showMessage("No images intersect the ROI!");
5842 return;
5844 // Create the area that will be "one half"
5845 // and overlay it in the display, repaint, and ask for "yes/no" to continue.
5847 for (int i=1; i<proi.getNCoordinates(); i++) {
5848 for (int k=i+2; k<proi.getNCoordinates(); k++) { // skip the immediate next segment
5849 // check if the two segments intersect
5850 if (null != M.computeSegmentsIntersection(x[i-1], y[i-1], x[i], y[i],
5851 x[k-1], y[k-1], x[k], y[k])) {
5852 Utils.showMessage("Cannot split images with a polygon ROI that intersects itself!");
5853 return;
5857 final Area[] as = M.splitArea(new Area(getLayerSet().get2DBounds()), proi, getLayerSet().get2DBounds());
5858 final Color[] c = new Color[]{Color.blue, Color.red};
5859 int i = 0;
5860 for (final Area a : as) {
5861 //Utils.log2("Added overlay " + i + " with color " + c[i] + " and area " + AreaCalculations.area(a.getPathIterator(null)));
5862 getLayer().getOverlay().add(a, c[i++], null, true, false, 0.4f);
5864 Display.repaint(getLayer());
5866 final YesNoDialog yn = new YesNoDialog(frame, "Check", "Does the splitting match your expectations?\nPush 'yes' to split the images.", false);
5867 yn.setModal(false);
5868 for (final WindowListener wl : yn.getWindowListeners()) yn.removeWindowListener(wl);
5869 yn.setClosingTask(new Runnable() {
5870 @Override
5871 public void run() {
5872 try {
5873 // Remove overlay shapes
5874 for (final Area a : as) {
5875 getLayer().getOverlay().remove(a);
5877 if (!yn.yesPressed()) {
5878 Utils.log2("Pushed 'no'");
5879 return;
5881 // Split intersecting patches
5882 // Duplicate each intersecting patch, and assign a[0] to the original and a[1] to the copy, as mask.
5883 Bureaucrat.createAndStart(new Worker.Task("Spliting images") {
5884 @Override
5885 public void exec() {
5886 final Roi r1 = new ShapeRoi(as[0]),
5887 r2 = new ShapeRoi(as[1]);
5888 final ArrayList<Future<?>> fus = new ArrayList<Future<?>>();
5889 for (final Patch p : (Collection<Patch>)(Collection)col) {
5890 final Patch copy = (Patch) p.clone(p.getProject(), false);
5891 p.addAlphaMask(r1, 0);
5892 copy.addAlphaMask(r2, 0);
5893 fus.add(p.updateMipMaps());
5894 fus.add(copy.updateMipMaps());
5895 p.getLayer().add(copy); // after submitting mipmaps, since it will get added to all Displays and repainted.
5897 Utils.wait(fus);
5899 }, project);
5900 } catch (final Throwable t) {
5901 IJError.print(t);
5902 } finally {
5903 yn.dispose();
5904 Display.repaint(getLayer());
5908 yn.setVisible(true);
5909 } else if (command.equals("Duplicate")) {
5910 // only Patch and DLabel, i.e. Layer-only resident objects that don't exist in the Project Tree
5911 final HashSet<Class> accepted = new HashSet<Class>();
5912 accepted.add(Patch.class);
5913 accepted.add(DLabel.class);
5914 accepted.add(Stack.class);
5915 final ArrayList<Displayable> originals = new ArrayList<Displayable>();
5916 final ArrayList<Displayable> selected = selection.getSelected();
5917 for (final Displayable d : selected) {
5918 if (accepted.contains(d.getClass())) {
5919 originals.add(d);
5922 if (originals.size() > 0) {
5923 getLayerSet().addChangeTreesStep();
5924 for (final Displayable d : originals) {
5925 if (d instanceof ZDisplayable) {
5926 d.getLayerSet().add((ZDisplayable)d.clone());
5927 } else {
5928 d.getLayer().add(d.clone());
5931 getLayerSet().addChangeTreesStep();
5932 } else if (selected.size() > 0) {
5933 Utils.log("Can only duplicate images and text labels.\nDuplicate *other* objects in the Project Tree.\n");
5935 } else if (command.equals("Create subproject")) {
5936 // Choose a 2D rectangle
5937 final Roi roi = canvas.getFakeImagePlus().getRoi();
5938 Rectangle bounds;
5939 if (null != roi) {
5940 if (!Utils.check("Use bounds as defined by the ROI:\n" + roi.getBounds() + " ?")) return;
5941 bounds = roi.getBounds();
5942 } else bounds = getLayerSet().get2DBounds();
5943 // Choose a layer range, and whether to ignore hidden images
5944 final GenericDialog gd = new GenericDialog("Choose layer range");
5945 Utils.addLayerRangeChoices(layer, gd);
5946 gd.addCheckbox("Ignore hidden images", true);
5947 gd.showDialog();
5948 if (gd.wasCanceled()) return;
5949 final Layer first = layer.getParent().getLayer(gd.getNextChoiceIndex());
5950 final Layer last = layer.getParent().getLayer(gd.getNextChoiceIndex());
5951 final boolean ignore_hidden_patches = gd.getNextBoolean();
5952 final Project sub = getProject().createSubproject(bounds, first, last, ignore_hidden_patches);
5953 if (null == sub) {
5954 Utils.log("ERROR: failed to create subproject.");
5955 return;
5957 final LayerSet subls = sub.getRootLayerSet();
5958 Display.createDisplay(sub, subls.getLayer(0));
5959 } else if (command.startsWith("Image stack under selected Arealist")) {
5960 if (null == active || active.getClass() != AreaList.class) return;
5961 final GenericDialog gd = new GenericDialog("Stack options");
5962 final String[] types = {"8-bit", "16-bit", "32-bit", "RGB"};
5963 gd.addChoice("type:", types, types[0]);
5964 gd.addSlider("Scale: ", 1, 100, 100);
5965 gd.showDialog();
5966 if (gd.wasCanceled()) return;
5967 final int type;
5968 switch (gd.getNextChoiceIndex()) {
5969 case 0: type = ImagePlus.GRAY8; break;
5970 case 1: type = ImagePlus.GRAY16; break;
5971 case 2: type = ImagePlus.GRAY32; break;
5972 case 3: type = ImagePlus.COLOR_RGB; break;
5973 default: type = ImagePlus.GRAY8; break;
5975 final ImagePlus imp = ((AreaList)active).getStack(type, gd.getNextNumber()/100);
5976 if (null != imp) imp.show();
5977 } else if (command.equals("Fly through selected Treeline/AreaTree")) {
5978 if (null == active || !(active instanceof Tree<?>)) return;
5979 Bureaucrat.createAndStart(new Worker.Task("Creating fly through", true) {
5980 @Override
5981 public void exec() {
5982 final GenericDialog gd = new GenericDialog("Fly through");
5983 gd.addNumericField("Width", 512, 0);
5984 gd.addNumericField("Height", 512, 0);
5985 final String[] types = new String[]{"8-bit gray", "Color RGB"};
5986 gd.addChoice("Image type", types, types[0]);
5987 gd.addSlider("scale", 0, 100, 100);
5988 gd.addCheckbox("save to file", false);
5989 gd.showDialog();
5990 if (gd.wasCanceled()) return;
5991 final int w = (int)gd.getNextNumber();
5992 final int h = (int)gd.getNextNumber();
5993 final int type = 0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY8 : ImagePlus.COLOR_RGB;
5994 final double scale = gd.getNextNumber();
5995 if (w <=0 || h <=0) {
5996 Utils.log("Invalid width or height: " + w + ", " + h);
5997 return;
5999 if (0 == scale || Double.isNaN(scale)) {
6000 Utils.log("Invalid scale: " + scale);
6001 return;
6003 String dir = null;
6004 if (gd.getNextBoolean()) {
6005 final DirectoryChooser dc = new DirectoryChooser("Target directory");
6006 dir = dc.getDirectory();
6007 if (null == dir) return; // canceled
6008 dir = Utils.fixDir(dir);
6010 final ImagePlus imp = ((Tree<?>)active).flyThroughMarked(w, h, scale/100, type, dir);
6011 if (null == imp) {
6012 Utils.log("Mark a node first!");
6013 return;
6015 imp.show();
6017 }, project);
6018 } else if (command.startsWith("Arealists as labels")) {
6019 final GenericDialog gd = new GenericDialog("Export labels");
6020 gd.addSlider("Scale: ", 1, 100, 100);
6021 final String[] options = {"All area list", "Selected area lists"};
6022 gd.addChoice("Export: ", options, options[0]);
6023 Utils.addLayerRangeChoices(layer, gd);
6024 gd.addCheckbox("Visible only", true);
6025 gd.showDialog();
6026 if (gd.wasCanceled()) return;
6027 final float scale = (float)(gd.getNextNumber() / 100);
6028 final java.util.List<Displayable> al = (java.util.List<Displayable>)(0 == gd.getNextChoiceIndex() ? layer.getParent().getZDisplayables(AreaList.class) : selection.getSelectedSorted(AreaList.class));
6029 if (null == al) {
6030 Utils.log("No area lists found to export.");
6031 return;
6033 // 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?
6035 final int first = gd.getNextChoiceIndex();
6036 final int last = gd.getNextChoiceIndex();
6037 final boolean visible_only = gd.getNextBoolean();
6038 if (-1 != command.indexOf("(amira)")) {
6039 AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, true, true);
6040 } else if (-1 != command.indexOf("(tif)")) {
6041 AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, false, false);
6043 } else if (command.equals("Project properties...")) {
6044 project.adjustProperties();
6045 } else if (command.equals("Release memory...")) {
6046 Bureaucrat.createAndStart(new Worker("Releasing memory") {
6047 @Override
6048 public void run() {
6049 startedWorking();
6050 try {
6051 final GenericDialog gd = new GenericDialog("Release Memory");
6052 final int max = (int)(IJ.maxMemory() / 1000000);
6053 gd.addSlider("Megabytes: ", 0, max, max/2);
6054 gd.showDialog();
6055 if (!gd.wasCanceled()) {
6056 final int n_mb = (int)gd.getNextNumber();
6057 project.getLoader().releaseToFit((long)n_mb*1000000);
6059 } catch (final Throwable e) {
6060 IJError.print(e);
6061 } finally {
6062 finishedWorking();
6065 }, project);
6066 } else if (command.equals("Create sibling project with retiled layers")) {
6067 final GenericDialog gd = new GenericDialog("Export flattened layers");
6068 gd.addNumericField("Tile_width", 2048, 0);
6069 gd.addNumericField("Tile_height", 2048, 0);
6070 final String[] types = new String[]{"16-bit", "RGB color"};
6071 gd.addChoice("Export_image_type", types, types[0]);
6072 gd.addCheckbox("Create mipmaps", true);
6073 gd.addNumericField("Number_of_threads_to_use", Runtime.getRuntime().availableProcessors(), 0);
6074 gd.showDialog();
6075 if (gd.wasCanceled()) return;
6077 final DirectoryChooser dc = new DirectoryChooser("Choose target folder");
6078 final String folder = dc.getDirectory();
6079 if (null == folder) return;
6081 final int tileWidth = (int)gd.getNextNumber(),
6082 tileHeight = (int)gd.getNextNumber();
6083 if (tileWidth < 0 || tileHeight < 0) {
6084 Utils.showMessage("Invalid tile sizes: " + tileWidth + ", " + tileHeight);
6085 return;
6088 if (tileWidth != tileHeight) {
6089 if (!Utils.check("The tile width (" + tileWidth + ") differs from the tile height (" + tileHeight + ").\nContinue anyway?")) {
6090 return;
6094 final int imageType = 0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY16 : ImagePlus.COLOR_RGB;
6095 final boolean createMipMaps = gd.getNextBoolean();
6096 final int nThreads = (int)gd.getNextNumber();
6098 Bureaucrat.createAndStart(new Worker.Task("Export flattened sibling project") {
6099 @Override
6100 public void exec() {
6101 try {
6102 ProjectTiler.createRetiledSibling(
6103 project,
6104 folder,
6105 tileWidth,
6106 tileHeight,
6107 imageType,
6108 true,
6109 nThreads,
6110 createMipMaps);
6111 } catch (final Throwable t) {
6112 Utils.showMessage("ERROR: " + t);
6113 IJError.print(t);
6116 }, project);
6118 } else if (command.equals("Flush image cache")) {
6119 Loader.releaseAllCaches();
6120 } else if (command.equals("Regenerate all mipmaps")) {
6121 project.getLoader().regenerateMipMaps(getLayerSet().getDisplayables(Patch.class));
6122 } else if (command.equals("Regenerate mipmaps (selected images)")) {
6123 project.getLoader().regenerateMipMaps(selection.getSelected(Patch.class));
6124 } else if (command.equals("Tags...")) {
6125 // get a file first
6126 final File f = Utils.chooseFile(null, "tags", ".xml");
6127 if (null == f) return;
6128 if (!Utils.saveToFile(f, getLayerSet().exportTags())) {
6129 Utils.logAll("ERROR when saving tags to file " + f.getAbsolutePath());
6131 } else if (command.equals("Tags ...")) {
6132 final String[] ff = Utils.selectFile("Import tags");
6133 if (null == ff) return;
6134 final GenericDialog gd = new GenericDialog("Import tags");
6135 final String[] modes = new String[]{"Append to current tags", "Replace current tags"};
6136 gd.addChoice("Import tags mode:", modes, modes[0]);
6137 gd.addMessage("Replacing current tags\nwill remove all tags\n from all nodes first!");
6138 gd.showDialog();
6139 if (gd.wasCanceled()) return;
6140 getLayerSet().importTags(new StringBuilder(ff[0]).append('/').append(ff[1]).toString(), 1 == gd.getNextChoiceIndex());
6141 } else if (command.equals("Connectivity graph...")) {
6142 Bureaucrat.createAndStart(new Worker.Task("Connectivity graph") {
6143 @Override
6144 public void exec() {
6145 Graph.extractAndShowGraph(getLayerSet());
6147 }, getProject());
6148 } else if (command.equals("NeuroML...")) {
6149 final GenericDialog gd = new GenericDialog("Export NeuroML");
6150 final String[] a = new String[]{"NeuroML (arbors and synapses)", "MorphML (arbors)"};
6151 gd.addChoice("Type:", a, a[0]);
6152 final String[] b = new String[]{"All treelines and areatrees", "Selected treelines and areatrees"};
6153 gd.addChoice("Export:", b, b[0]);
6154 gd.showDialog();
6155 if (gd.wasCanceled()) return;
6156 final int type = gd.getNextChoiceIndex();
6157 final int export = gd.getNextChoiceIndex();
6159 final SaveDialog sd = new SaveDialog("Choose .mml file", null, ".mml");
6160 final String filename = sd.getFileName();
6161 if (null == filename) return; // canceled
6162 final File f = new File(sd.getDirectory() + filename);
6164 Bureaucrat.createAndStart(new Worker.Task("Export NeuroML") {
6165 @Override
6166 public void exec() {
6167 OutputStreamWriter w = null;
6168 try {
6169 final Set<Tree<?>> trees = new HashSet<Tree<?>>();
6170 Collection<? extends Displayable> ds = null;
6171 switch (export) {
6172 case 0:
6173 ds = getLayerSet().getZDisplayables();
6174 break;
6175 case 1:
6176 ds = selection.getSelected();
6177 break;
6179 for (final Displayable d : ds) {
6180 if (d.getClass() == Treeline.class || d.getClass() == AreaTree.class) {
6181 trees.add((Tree<?>)d);
6184 if (trees.isEmpty()) {
6185 Utils.showMessage("No trees to export!");
6186 return;
6189 w = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(f), 65536), "8859_1"); // encoding in Latin 1 (for macosx not to mess around
6191 switch (type) {
6192 case 0:
6193 NeuroML.exportNeuroML(trees, w);
6194 break;
6195 case 1:
6196 NeuroML.exportMorphML(trees, w);
6197 break;
6200 w.flush();
6201 w.close();
6203 } catch (final Throwable t) {
6204 IJError.print(t);
6205 try {
6206 if (null != w) w.close();
6207 } catch (final Exception ee) { IJError.print(ee); }
6210 }, getProject());
6211 } else if (command.equals("Measure")) {
6212 if (selection.isEmpty()) {
6213 Utils.log("Nothing selected to measure!");
6214 return;
6216 selection.measure();
6217 } else if (command.equals("Area interpolation options...")) {
6218 final GenericDialog gd = new GenericDialog("Area interpolation");
6219 final boolean a = project.getBooleanProperty(AreaUtils.always_interpolate_areas_with_distance_map);
6220 gd.addCheckbox("Always use distance map method", a);
6221 gd.showDialog();
6222 if (gd.wasCanceled()) return;
6223 project.setProperty(AreaUtils.always_interpolate_areas_with_distance_map, gd.getNextBoolean() ? "true" : null);
6224 } else {
6225 Utils.log2("Display: don't know what to do with command " + command);
6227 }});
6230 public void adjustMinAndMaxGUI() {
6231 final List<Displayable> list = selection.getSelected(Patch.class);
6232 if (list.isEmpty()) {
6233 Utils.log("No images selected!");
6234 return;
6236 Bureaucrat.createAndStart(new Worker.Task("Init contrast adjustment") {
6237 @Override
6238 public void exec() {
6239 try {
6240 setMode(new ContrastAdjustmentMode(Display.this, list));
6241 } catch (final Exception e) {
6242 e.printStackTrace();
6243 Utils.log("All images must be of the same type!");
6246 }, list.get(0).getProject());
6249 public void adjustMeasurementOptions() {
6250 final GenericDialog gd = new GenericDialog("Measurement options");
6251 gd.addMessage("The point interdistance for resampling the contours\nof AreaLists when measuring areas and volumes.\nThe recommended value is one calibrated unit, in pixels.\nThe default value is one pixel.");
6252 gd.addNumericField("Resolution", project.getProperty("measurement_resampling_delta", 1.0f), 1, 10, "pixels");
6253 gd.addMessage("Whether to measure the largest diameter of an AreaList\nwhich is the largest distance between any two points of its contours.\nA very expensive operation, by default it is turned off.");
6254 final boolean diameters = project.getBooleanProperty("measure_largest_diameter");
6255 gd.addCheckbox("Measure_largest_diameter", diameters);
6256 gd.showDialog();
6258 if (gd.wasCanceled()) return;
6260 final float delta = (float)gd.getNextNumber();
6261 if (Float.isNaN(delta) || delta <= 0) {
6262 Utils.log("Rejected resampling resolution of " + delta + ", which should be larger than zero.");
6263 } else {
6264 project.setProperty("measurement_resampling_delta", Float.toString(delta));
6267 final boolean diameters2 = gd.getNextBoolean();
6268 if (diameters != diameters2) {
6269 project.setProperty("measure_largest_diameter", Boolean.toString(diameters2));
6273 /** Pops up a dialog to adjust the alpha, visible, color, locked and compositeMode of all Displayables in {@param col}.
6275 * @param col
6277 public void adjustGroupProperties(final Collection<Displayable> col) {
6278 if (col.isEmpty()) return;
6279 final Displayable first = col.iterator().next();
6280 final GenericDialog gd = new GenericDialog("Properties of selected");
6281 gd.addSlider("alpha: ", 0, 100, (int)(first.getAlpha()*100));
6282 gd.addCheckbox("visible", first.isVisible());
6283 gd.addSlider("Red: ", 0, 255, first.getColor().getRed());
6284 gd.addSlider("Green: ", 0, 255, first.getColor().getGreen());
6285 gd.addSlider("Blue: ", 0, 255, first.getColor().getBlue());
6286 gd.addCheckbox("locked", first.isLocked2());
6287 gd.addChoice( "composite mode: ", Displayable.compositeModes, Displayable.compositeModes[ first.getCompositeMode() ] );
6288 gd.showDialog();
6290 if (gd.wasCanceled()) return;
6292 final HashSet<Displayable> hs = new HashSet<Displayable>(col);
6293 final String[] fields = new String[]{"alpha", "visible", "color", "locked", "compositeMode"};
6294 getLayerSet().addDataEditStep(hs, fields);
6296 final float alpha = (float)gd.getNextNumber();
6297 final boolean visible = gd.getNextBoolean();
6298 final Color color = new Color((int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
6299 final boolean locked = gd.getNextBoolean();
6300 final byte compositeMode = (byte)gd.getNextChoiceIndex();
6302 for (final Displayable d : col) {
6303 d.setAlpha(alpha);
6304 d.setVisible(visible);
6305 d.setColor(color);
6306 d.setLocked(locked);
6307 d.setCompositeMode(compositeMode);
6310 // Record the current state
6311 getLayerSet().addDataEditStep(hs, fields);
6314 public void adjustProperties() {
6315 final GenericDialog gd = new GenericDialog("Properties", Display.this.frame);
6316 //gd.addNumericField("layer_scroll_step: ", this.scroll_step, 0);
6317 gd.addSlider("layer_scroll_step: ", 1, layer.getParent().size(), Display.this.scroll_step);
6318 gd.addChoice("snapshots_mode", LayerSet.snapshot_modes, LayerSet.snapshot_modes[layer.getParent().getSnapshotsMode()]);
6319 gd.addCheckbox("prefer_snapshots_quality", layer.getParent().snapshotsQuality());
6320 final Loader lo = getProject().getLoader();
6321 final boolean using_mipmaps = lo.isMipMapsRegenerationEnabled();
6322 gd.addCheckbox("enable_mipmaps", using_mipmaps);
6323 gd.addCheckbox("enable_layer_pixels virtualization", layer.getParent().isPixelsVirtualizationEnabled());
6324 final double max = layer.getParent().getLayerWidth() < layer.getParent().getLayerHeight() ? layer.getParent().getLayerWidth() : layer.getParent().getLayerHeight();
6325 gd.addSlider("max_dimension of virtualized layer pixels: ", 0, max, layer.getParent().getPixelsMaxDimension());
6326 gd.addCheckbox("Show arrow heads in Treeline/AreaTree", layer.getParent().paint_arrows);
6327 gd.addCheckbox("Show edge confidence boxes in Treeline/AreaTree", layer.getParent().paint_edge_confidence_boxes);
6328 gd.addCheckbox("Show color cues", layer.getParent().color_cues);
6329 gd.addSlider("+/- layers to color cue", 0, 10, layer.getParent().n_layers_color_cue);
6330 gd.addCheckbox("Show color cues for areas", layer.getParent().area_color_cues);
6331 gd.addCheckbox("Use red/blue for color cues", layer.getParent().use_color_cue_colors);
6332 gd.addCheckbox("Prepaint images", layer.getParent().prepaint);
6333 gd.addSlider("Preload ahead from sections: ", 0, layer.getParent().size(), layer.getParent().preload_ahead);
6334 // --------
6335 gd.showDialog();
6336 if (gd.wasCanceled()) return;
6337 // --------
6338 int sc = (int) gd.getNextNumber();
6339 if (sc < 1) sc = 1;
6340 Display.this.scroll_step = sc;
6341 updateInDatabase("scroll_step");
6343 layer.getParent().setSnapshotsMode(gd.getNextChoiceIndex());
6344 layer.getParent().setSnapshotsQuality(gd.getNextBoolean());
6346 final boolean using_mipmaps2 = gd.getNextBoolean();
6347 if (using_mipmaps2 == using_mipmaps) {
6348 // Nothing changed
6349 } else if (!using_mipmaps2 && using_mipmaps) {
6350 // Desactivate mipmaps
6351 lo.setMipMapsRegeneration(false);
6352 lo.flushMipMaps(true);
6353 } else if (using_mipmaps2 && !using_mipmaps) {
6354 // Reactivate mipmaps
6355 lo.setMipMapsRegeneration(true);
6356 lo.generateMipMaps(layer.getParent().getDisplayables(Patch.class));
6359 layer.getParent().setPixelsVirtualizationEnabled(gd.getNextBoolean());
6360 layer.getParent().setPixelsMaxDimension((int)gd.getNextNumber());
6361 layer.getParent().paint_arrows = gd.getNextBoolean();
6362 layer.getParent().paint_edge_confidence_boxes = gd.getNextBoolean();
6363 layer.getParent().color_cues = gd.getNextBoolean();
6364 layer.getParent().n_layers_color_cue = (int)gd.getNextNumber();
6365 layer.getParent().area_color_cues = gd.getNextBoolean();
6366 layer.getParent().use_color_cue_colors = gd.getNextBoolean();
6367 layer.getParent().prepaint = gd.getNextBoolean();
6368 layer.getParent().preload_ahead = (int) Math.min(gd.getNextNumber(), layer.getParent().size());
6369 Display.repaint(layer.getParent());
6372 private static class UpdateDimensionField implements TextListener {
6373 final TextField width, height, scale;
6374 final Scrollbar bar;
6375 final int initial_width, initial_height;
6376 UpdateDimensionField(final int initial_width, final int initial_height, final TextField width, final TextField height, final TextField scale, final Scrollbar bar) {
6377 this.initial_width = initial_width;
6378 this.initial_height = initial_height;
6379 this.width = width;
6380 this.height = height;
6381 this.scale = scale;
6382 this.bar = bar;
6384 @Override
6385 public void textValueChanged(final TextEvent e) {
6386 try {
6387 final TextField source = (TextField) e.getSource();
6388 if (scale == source && (scale.isFocusOwner() || bar.isFocusOwner())) {
6389 final double sc = Double.parseDouble(scale.getText()) / 100;
6390 // update both
6391 width.setText(Integer.toString((int) (sc * initial_width + 0.5)));
6392 height.setText(Integer.toString((int) (sc * initial_height + 0.5)));
6393 } else if (width == source && width.isFocusOwner()) {
6395 final int width = Integer.toString((int) (width.getText() + 0.5));
6396 final double sc = width / (double)initial_width;
6397 scale.setText(Integer.toString((int)(sc * 100 + 0.5)));
6398 height.setText(Integer.toString((int)(sc * initial_height + 0.5)));
6400 set(width, height, initial_width, initial_height);
6401 } else if (height == source && height.isFocusOwner()) {
6402 set(height, width, initial_height, initial_width);
6404 } catch (final NumberFormatException nfe) {
6405 Utils.logAll("Unparsable number: " + nfe.getMessage());
6406 } catch (final Exception ee) {
6407 IJError.print(ee);
6410 private void set(final TextField source, final TextField target, final int initial_source, final int initial_target) {
6411 final int dim = (int) ((Double.parseDouble(source.getText()) + 0.5));
6412 final double sc = dim / (double)initial_source;
6413 scale.setText(Utils.cutNumber(sc * 100, 3));
6414 target.setText(Integer.toString((int)(sc * initial_target + 0.5)));
6419 /** Update in all displays the Transform for the given Displayable if it's selected. */
6420 static public void updateTransform(final Displayable displ) {
6421 for (final Display d : al_displays) {
6422 if (d.selection.contains(displ)) d.selection.updateTransform(displ);
6426 /** Order the profiles of the parent profile_list by Z order, and fix the ProjectTree.*/
6428 private void fixZOrdering(Profile profile) {
6429 ProjectThing thing = project.findProjectThing(profile);
6430 if (null == thing) {
6431 Utils.log2("Display.fixZOrdering: null thing?");
6432 return;
6434 ((ProjectThing)thing.getParent()).fixZOrdering();
6435 project.getProjectTree().updateList(thing.getParent());
6439 /** The number of layers to scroll through with the wheel; 1 by default.*/
6440 public int getScrollStep() { return this.scroll_step; }
6442 public void setScrollStep(int scroll_step) {
6443 if (scroll_step < 1) scroll_step = 1;
6444 this.scroll_step = scroll_step;
6445 updateInDatabase("scroll_step");
6448 protected Bureaucrat importImage() {
6449 final 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
6450 @Override
6451 public void run() {
6452 startedWorking();
6453 try {
6456 final Rectangle srcRect = canvas.getSrcRect();
6457 final int x = srcRect.x + srcRect.width / 2;
6458 final int y = srcRect.y + srcRect.height/ 2;
6459 final Patch p = project.getLoader().importImage(project, x, y);
6460 if (null == p) {
6461 finishedWorking();
6462 Utils.showMessage("Could not open the image.");
6463 return;
6466 Display.this.getLayerSet().addLayerContentStep(layer);
6468 layer.add(p); // will add it to the proper Displays
6470 Display.this.getLayerSet().addLayerContentStep(layer);
6473 } catch (final Exception e) {
6474 IJError.print(e);
6476 finishedWorking();
6479 return Bureaucrat.createAndStart(worker, getProject());
6482 protected Bureaucrat importNextImage() {
6483 final 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
6484 @Override
6485 public void run() {
6486 startedWorking();
6487 try {
6489 final Rectangle srcRect = canvas.getSrcRect();
6490 final int x = srcRect.x + srcRect.width / 2;// - imp.getWidth() / 2;
6491 final int y = srcRect.y + srcRect.height/ 2;// - imp.getHeight()/ 2;
6492 final Patch p = project.getLoader().importNextImage(project, x, y);
6493 if (null == p) {
6494 Utils.showMessage("Could not open next image.");
6495 finishedWorking();
6496 return;
6499 Display.this.getLayerSet().addLayerContentStep(layer);
6501 layer.add(p); // will add it to the proper Displays
6503 Display.this.getLayerSet().addLayerContentStep(layer);
6505 } catch (final Exception e) {
6506 IJError.print(e);
6508 finishedWorking();
6511 return Bureaucrat.createAndStart(worker, getProject());
6515 /** Make the given channel have the given alpha (transparency). */
6516 public void setChannel(final int c, final float alpha) {
6517 final int a = (int)(255 * alpha);
6518 final int l = (c_alphas&0xff000000)>>24;
6519 final int r = (c_alphas&0xff0000)>>16;
6520 final int g = (c_alphas&0xff00)>>8;
6521 final int b = c_alphas&0xff;
6522 switch (c) {
6523 case Channel.MONO:
6524 // all to the given alpha
6525 c_alphas = (l<<24) + (r<<16) + (g<<8) + b; // parenthesis are NECESSARY
6526 break;
6527 case Channel.RED:
6528 // modify only the red
6529 c_alphas = (l<<24) + (a<<16) + (g<<8) + b;
6530 break;
6531 case Channel.GREEN:
6532 c_alphas = (l<<24) + (r<<16) + (a<<8) + b;
6533 break;
6534 case Channel.BLUE:
6535 c_alphas = (l<<24) + (r<<16) + (g<<8) + a;
6536 break;
6538 //Utils.log2("c_alphas: " + c_alphas);
6539 //canvas.setUpdateGraphics(true);
6540 canvas.repaint(true);
6541 updateInDatabase("c_alphas");
6544 /** Set the channel as active and the others as inactive. */
6545 public void setActiveChannel(final Channel channel) {
6546 for (int i=0; i<4; i++) {
6547 if (channel != channels[i]) channels[i].setActive(false);
6548 else channel.setActive(true);
6550 Utils.updateComponent(panel_channels);
6551 transp_slider.setValue((int)(channel.getAlpha() * 100));
6554 public int getDisplayChannelAlphas() { return c_alphas; }
6556 // rename this method and the getDisplayChannelAlphas ! They sound the same!
6557 public int getChannelAlphas() {
6558 return ((int)(channels[0].getAlpha() * 255)<<24) + ((int)(channels[1].getAlpha() * 255)<<16) + ((int)(channels[2].getAlpha() * 255)<<8) + (int)(channels[3].getAlpha() * 255);
6561 public int getChannelAlphasState() {
6562 return ((channels[0].isSelected() ? 255 : 0)<<24)
6563 + ((channels[1].isSelected() ? 255 : 0)<<16)
6564 + ((channels[2].isSelected() ? 255 : 0)<<8)
6565 + (channels[3].isSelected() ? 255 : 0);
6568 /** Show the layer in the front Display, or in a new Display if the front Display is showing a layer from a different LayerSet. */
6569 static public void showFront(final Layer layer) {
6570 Display display = front;
6571 if (null == display || display.layer.getParent() != layer.getParent()) {
6572 display = new Display(layer.getProject(), layer, null); // gets set to front
6573 } else {
6574 display.setLayer(layer);
6578 /** Show the given Displayable centered and selected. If select is false, the selection is cleared. */
6579 static public void showCentered(final Layer layer, final Displayable displ, final boolean select, final boolean shift_down) {
6580 // see if the given layer belongs to the layer set being displayed
6581 Display display = front; // to ensure thread consistency to some extent
6582 if (null == display || display.layer.getParent() != layer.getParent()) {
6583 display = new Display(layer.getProject(), layer, displ); // gets set to front
6585 display.show(layer, displ, select, shift_down);
6587 /** Set this Display to show the specific layer, centered at the @param displ, and perhaps selected,
6588 * adding to the selection instead of clearing it if @param shift_down is true. */
6589 public void show(final Layer layer, final Displayable displ, final boolean select, final boolean shift_down) {
6590 if (this.layer != layer) {
6591 setLayer(layer);
6593 if (select) {
6594 if (!shift_down) selection.clear();
6595 selection.add(displ);
6596 } else {
6597 selection.clear();
6599 showCentered(displ);
6602 /** Center the view, if possible, on x,y. It's not possible when zoomed out, in which case it will try to do its best. */
6603 public final void center(final double x, final double y) {
6604 Utils.invokeLater(new Runnable() { @Override
6605 public void run() {
6606 final Rectangle r = (Rectangle)canvas.getSrcRect().clone();
6607 r.x = (int)x - r.width/2;
6608 r.y = (int)y - r.height/2;
6609 canvas.center(r, canvas.getMagnification());
6610 }});
6613 public final void center(final Coordinate<?> c) {
6614 if (null == c) return;
6615 slt.set(c.layer);
6616 center(c.x, c.y);
6619 public final void centerIfNotWithinSrcRect(final Coordinate<?> c) {
6620 if (null == c) return;
6621 slt.set(c.layer);
6622 final Rectangle srcRect = canvas.getSrcRect();
6623 if (srcRect.contains((int)(c.x+0.5), (int)(c.y+0.5))) return;
6624 center(c.x, c.y);
6627 public final void animateBrowsingTo(final Coordinate<?> c) {
6628 if (null == c) return;
6629 final double padding = 50/canvas.getMagnification(); // 50 screen pixels
6630 canvas.animateBrowsing(new Rectangle((int)(c.x - padding), (int)(c.y - padding), (int)(2*padding), (int)(2*padding)), c.layer);
6633 static public final void centerAt(final Coordinate c) {
6634 centerAt(c, false, false);
6636 static public final void centerAt(final Coordinate<Displayable> c, final boolean select, final boolean shift_down) {
6637 if (null == c) return;
6638 Utils.invokeLater(new Runnable() { @Override
6639 public void run() {
6640 Layer la = c.layer;
6641 if (null == la) {
6642 if (null == c.object) return;
6643 la = c.object.getProject().getRootLayerSet().getLayer(0);
6644 if (null == la) return; // nothing to center on
6646 Display display = front;
6647 if (null == display || la.getParent() != display.getLayerSet()) {
6648 display = new Display(la.getProject(), la); // gets set to front
6650 display.center(c);
6652 if (select) {
6653 if (!shift_down) display.selection.clear();
6654 display.selection.add(c.object);
6656 }});
6659 private final void showCentered(final Displayable displ) {
6660 if (null == displ) return;
6661 Utils.invokeLater(new Runnable() { @Override
6662 public void run() {
6663 displ.setVisible(true);
6664 final Rectangle box = displ.getBoundingBox();
6666 if (0 == box.width && 0 == box.height) {
6667 box.width = 100; // old: (int)layer.getLayerWidth();
6668 box.height = 100; // old: (int)layer.getLayerHeight();
6669 } else if (0 == box.width) {
6670 box.width = box.height;
6671 } else if (0 == box.height) {
6672 box.height = box.width;
6675 canvas.showCentered(box);
6676 ht_tabs.get(displ.getClass()).scrollToShow(displ);
6677 if (displ instanceof ZDisplayable) {
6678 // scroll to first layer that has a point
6679 final ZDisplayable zd = (ZDisplayable)displ;
6680 setLayer(zd.getFirstLayer());
6682 }});
6685 @Override
6686 public void eventOccurred(final int eventID) {
6687 if (IJEventListener.FOREGROUND_COLOR_CHANGED == eventID) {
6688 if (this != front || null == active || !project.isInputEnabled()) return;
6689 selection.setColor(Toolbar.getForegroundColor());
6690 Display.repaint(front.layer, selection.getBox(), 0);
6691 } else if (IJEventListener.TOOL_CHANGED == eventID) {
6692 Display.repaintToolbar();
6696 public void imageClosed(final ImagePlus imp) {}
6697 public void imageOpened(final ImagePlus imp) {}
6699 /** Release memory captured by the offscreen images */
6700 static public void flushAll() {
6701 for (final Display d : al_displays) {
6702 d.canvas.flush();
6704 //System.gc();
6705 Thread.yield();
6708 /** Can be null. */
6709 static public Display getFront() {
6710 final Collection<Display> ds = al_displays;
6711 if (null == front && ds.size() > 0) {
6712 // Should never happen, this is a safety net
6713 Utils.log2("Fixing error with null 'Display.getFront()'");
6714 final Display d = ds.iterator().next();
6715 d.frame.toFront();
6716 front = d;
6718 return front;
6721 static public Display getOrCreateFront(final Project project) {
6722 final Display df = front;
6723 if (null != df && df.project == project) return df;
6724 for (final Display d : al_displays) {
6725 if (d.project == project) {
6726 d.frame.toFront();
6727 return d;
6730 final LayerSet ls = project.getRootLayerSet();
6731 if (0 == ls.size()) return null;
6732 return new Display(project, ls.getLayer(0)); // sets it to front
6735 static public void setCursorToAll(final Cursor c) {
6736 for (final Display d : al_displays) {
6737 if (null != d.frame) d.frame.setCursor(c);
6741 /** Used by the Displayable to update the visibility and locking state checkboxes in other Displays. */
6742 static public void updateCheckboxes(final Displayable displ, final int cb, final boolean state) {
6743 for (final Display d : al_displays) {
6744 final DisplayablePanel dp = d.ht_panels.get(displ);
6745 if (null != dp) {
6746 Utils.invokeLater(new Runnable() { @Override
6747 public void run() {
6748 dp.updateCheckbox(cb, state);
6749 }});
6753 /** Set the checkbox @param cb state to @param state value, for each Displayable. Assumes all Displayable objects belong to one specific project. */
6754 static public void updateCheckboxes(final Collection<Displayable> displs, final int cb, final boolean state) {
6755 if (null == displs || 0 == displs.size()) return;
6756 final Project p = displs.iterator().next().getProject();
6757 for (final Display d : al_displays) {
6758 if (d.getProject() != p) continue;
6759 Utils.invokeLater(new Runnable() { @Override
6760 public void run() {
6761 for (final Displayable displ : displs) {
6762 final DisplayablePanel dp = d.ht_panels.get(displ);
6763 if (null != dp) {
6764 dp.updateCheckbox(cb, state);
6767 }});
6770 /** Update the checkbox @param cb state to an appropriate value for each Displayable. Assumes all Displayable objects belong to one specific project. */
6771 static public void updateCheckboxes(final Collection<Displayable> displs, final int cb) {
6772 if (null == displs || 0 == displs.size()) return;
6773 final Project p = displs.iterator().next().getProject();
6774 for (final Display d : al_displays) {
6775 if (d.getProject() != p) continue;
6776 Utils.invokeLater(new Runnable() { @Override
6777 public void run() {
6778 for (final Displayable displ : displs) {
6779 final DisplayablePanel dp = d.ht_panels.get(displ);
6780 if (null != dp) {
6781 dp.updateCheckbox(cb);
6784 }});
6788 protected boolean isActiveWindow() {
6789 return frame.isActive();
6792 /** Toggle user input; pan and zoom are always enabled though.*/
6793 static public void setReceivesInput(final Project project, final boolean b) {
6794 for (final Display d : al_displays) {
6795 if (d.project == project) d.canvas.setReceivesInput(b);
6799 /** Export the DTD that defines this object. */
6800 static public void exportDTD(final StringBuilder sb_header, final HashSet<String> hs, final String indent) {
6801 if (hs.contains("t2_display")) return; // TODO to avoid collisions the type shoud be in a namespace such as tm2:display
6802 hs.add("t2_display");
6803 sb_header.append(indent).append("<!ELEMENT t2_display EMPTY>\n")
6804 .append(indent).append("<!ATTLIST t2_display id NMTOKEN #REQUIRED>\n")
6805 .append(indent).append("<!ATTLIST t2_display layer_id NMTOKEN #REQUIRED>\n")
6806 .append(indent).append("<!ATTLIST t2_display x NMTOKEN #REQUIRED>\n")
6807 .append(indent).append("<!ATTLIST t2_display y NMTOKEN #REQUIRED>\n")
6808 .append(indent).append("<!ATTLIST t2_display magnification NMTOKEN #REQUIRED>\n")
6809 .append(indent).append("<!ATTLIST t2_display srcrect_x NMTOKEN #REQUIRED>\n")
6810 .append(indent).append("<!ATTLIST t2_display srcrect_y NMTOKEN #REQUIRED>\n")
6811 .append(indent).append("<!ATTLIST t2_display srcrect_width NMTOKEN #REQUIRED>\n")
6812 .append(indent).append("<!ATTLIST t2_display srcrect_height NMTOKEN #REQUIRED>\n")
6813 .append(indent).append("<!ATTLIST t2_display scroll_step NMTOKEN #REQUIRED>\n")
6814 .append(indent).append("<!ATTLIST t2_display c_alphas NMTOKEN #REQUIRED>\n")
6815 .append(indent).append("<!ATTLIST t2_display c_alphas_state NMTOKEN #REQUIRED>\n")
6816 .append(indent).append("<!ATTLIST t2_display filter_enabled NMTOKEN #REQUIRED>\n")
6817 .append(indent).append("<!ATTLIST t2_display filter_min_max_enabled NMTOKEN #REQUIRED>\n")
6818 .append(indent).append("<!ATTLIST t2_display filter_min NMTOKEN #REQUIRED>\n")
6819 .append(indent).append("<!ATTLIST t2_display filter_max NMTOKEN #REQUIRED>\n")
6820 .append(indent).append("<!ATTLIST t2_display filter_invert NMTOKEN #REQUIRED>\n")
6821 .append(indent).append("<!ATTLIST t2_display filter_clahe_enabled NMTOKEN #REQUIRED>\n")
6822 .append(indent).append("<!ATTLIST t2_display filter_clahe_block_size NMTOKEN #REQUIRED>\n")
6823 .append(indent).append("<!ATTLIST t2_display filter_clahe_histogram_bins NMTOKEN #REQUIRED>\n")
6824 .append(indent).append("<!ATTLIST t2_display filter_clahe_max_slope NMTOKEN #REQUIRED>\n")
6827 /** Export all displays of the given project as XML entries. */
6828 static public void exportXML(final Project project, final Writer writer, final String indent, final XMLOptions options) throws Exception {
6829 final StringBuilder sb_body = new StringBuilder();
6830 final String in = indent + "\t";
6831 for (final Display d : al_displays) {
6832 if (d.project != project) continue;
6833 final Rectangle r = d.frame.getBounds();
6834 final Rectangle srcRect = d.canvas.getSrcRect();
6835 final double magnification = d.canvas.getMagnification();
6836 sb_body.append(indent).append("<t2_display id=\"").append(d.id).append("\"\n")
6837 .append(in).append("layer_id=\"").append(d.layer.getId()).append("\"\n")
6838 .append(in).append("c_alphas=\"").append(d.c_alphas).append("\"\n")
6839 .append(in).append("c_alphas_state=\"").append(d.getChannelAlphasState()).append("\"\n")
6840 .append(in).append("x=\"").append(r.x).append("\"\n")
6841 .append(in).append("y=\"").append(r.y).append("\"\n")
6842 .append(in).append("magnification=\"").append(magnification).append("\"\n")
6843 .append(in).append("srcrect_x=\"").append(srcRect.x).append("\"\n")
6844 .append(in).append("srcrect_y=\"").append(srcRect.y).append("\"\n")
6845 .append(in).append("srcrect_width=\"").append(srcRect.width).append("\"\n")
6846 .append(in).append("srcrect_height=\"").append(srcRect.height).append("\"\n")
6847 .append(in).append("scroll_step=\"").append(d.scroll_step).append("\"\n")
6848 .append(in).append("filter_enabled=\"").append(d.filter_enabled).append("\"\n")
6849 .append(in).append("filter_min_max_enabled=\"").append(d.filter_min_max_enabled).append("\"\n")
6850 .append(in).append("filter_min=\"").append(d.filter_min).append("\"\n")
6851 .append(in).append("filter_max=\"").append(d.filter_max).append("\"\n")
6852 .append(in).append("filter_invert=\"").append(d.filter_invert).append("\"\n")
6853 .append(in).append("filter_clahe_enabled=\"").append(d.filter_clahe_enabled).append("\"\n")
6854 .append(in).append("filter_clahe_block_size=\"").append(d.filter_clahe_block_size).append("\"\n")
6855 .append(in).append("filter_clahe_histogram_bins=\"").append(d.filter_clahe_histogram_bins).append("\"\n")
6856 .append(in).append("filter_clahe_max_slope=\"").append(d.filter_clahe_max_slope).append("\"\n")
6858 sb_body.append(indent).append("/>\n");
6860 writer.write(sb_body.toString());
6863 private void updateToolTab() {
6864 OptionPanel op = null;
6865 switch (ProjectToolbar.getToolId()) {
6866 case ProjectToolbar.PENCIL:
6867 op = Segmentation.fmp.asOptionPanel();
6868 break;
6869 case ProjectToolbar.BRUSH:
6870 op = AreaWrapper.PP.asOptionPanel();
6871 break;
6872 default:
6873 break;
6875 scroll_options.getViewport().removeAll();
6876 if (null != op) {
6877 op.bottomPadding();
6878 scroll_options.setViewportView(op);
6880 scroll_options.invalidate();
6881 scroll_options.validate();
6882 scroll_options.repaint();
6885 // Never called; ProjectToolbar.toolChanged is also never called, which should forward here.
6886 static public void toolChanged(final String tool_name) {
6887 Utils.log2("tool name: " + tool_name);
6888 for (final Display d : al_displays) {
6889 d.updateToolTab();
6890 Utils.updateComponent(d.toolbar_panel);
6891 Utils.log2("updating toolbar_panel");
6895 static public void toolChanged(final int tool) {
6896 //Utils.log2("int tool is " + tool);
6897 if (ProjectToolbar.PEN == tool) {
6898 // erase bounding boxes
6899 final HashSet<Layer> s = new HashSet<Layer>();
6900 for (final Display d : al_displays) {
6901 if (null != d.active && !s.contains(d.layer)) {
6902 Display.repaint(d.layer, d.selection.getBox(), 2);
6903 s.add(d.layer);
6907 for (final Display d: al_displays) {
6908 d.updateToolTab();
6910 if (null != front) {
6911 try {
6912 WindowManager.setTempCurrentImage(front.canvas.getFakeImagePlus());
6913 } catch (final Exception e) {} // may fail when changing tools while opening a Display
6917 /* Filter the Display images */
6918 private boolean filter_enabled = false,
6919 filter_invert = false,
6920 filter_clahe_enabled = false,
6921 filter_min_max_enabled = false;
6922 private int filter_clahe_block_size = 127,
6923 filter_clahe_histogram_bins = 256,
6924 filter_min = 0,
6925 filter_max = 255;
6926 private float filter_clahe_max_slope = 3;
6928 public boolean isLiveFilteringEnabled() { return filter_enabled; }
6930 private OptionPanel createFilterOptionPanel() {
6931 final OptionPanel fop = new OptionPanel();
6932 final Runnable reaction = new Runnable() {
6933 @Override
6934 public void run() {
6935 Display.repaint(getLayer());
6938 fop.addCheckbox("Live filters enabled", filter_enabled, new OptionPanel.BooleanSetter(this, "filter_enabled", reaction));
6939 fop.addMessage("Contrast:");
6940 fop.addCheckbox("Min/Max enabled", filter_min_max_enabled, new OptionPanel.BooleanSetter(this, "filter_min_max_enabled", reaction));
6941 fop.addNumericField("Min:", filter_min, 0, new OptionPanel.IntSetter(this, "filter_min", reaction, 0, 255));
6942 fop.addNumericField("Max:", filter_max, 0, new OptionPanel.IntSetter(this, "filter_max", reaction, 0, 255));
6943 fop.addCheckbox("Invert", filter_invert, new OptionPanel.BooleanSetter(this, "filter_invert", reaction));
6944 fop.addMessage("CLAHE options:");
6945 fop.addCheckbox("CLAHE enabled", filter_clahe_enabled, new OptionPanel.BooleanSetter(this, "filter_clahe_enabled", reaction));
6946 fop.addNumericField("block size:", filter_clahe_block_size, 0, new OptionPanel.IntSetter(this, "filter_clahe_block_size", reaction, 1, Integer.MAX_VALUE));
6947 fop.addNumericField("histogram bins:", filter_clahe_histogram_bins, 0, new OptionPanel.IntSetter(this, "filter_clahe_histogram_bins", reaction, 1, Integer.MAX_VALUE));
6948 fop.addNumericField("max slope:", filter_clahe_max_slope, 2, new OptionPanel.FloatSetter(this, "filter_clahe_max_slope", reaction, 0, Integer.MAX_VALUE));
6949 return fop;
6952 protected Image applyFilters(final Image img) {
6953 if (!filter_enabled) return img;
6954 return applyFilters(new ImagePlus("filtered", img)).getProcessor().createImage();
6957 protected ImagePlus applyFilters(final ImageProcessor ip) {
6958 final ImagePlus imp = new ImagePlus("filtered", ip);
6959 applyFilters(imp);
6960 return imp;
6963 protected ImagePlus applyFilters(final ImagePlus imp) {
6964 // Currently the order is hard-coded
6965 // 0: enabled?
6966 if (!filter_enabled) return imp;
6967 // 1: min/max?
6968 if (filter_min_max_enabled) imp.getProcessor().setMinAndMax(filter_min, filter_max);
6969 // 2: invert?
6970 if (filter_invert) imp.getProcessor().invert();
6971 // 3: CLAHE?
6972 if (filter_clahe_enabled) {
6973 Flat.getFastInstance().run(imp, filter_clahe_block_size, filter_clahe_histogram_bins, filter_clahe_max_slope, null, false);
6976 return imp;
6979 /////
6981 public Selection getSelection() {
6982 return selection;
6985 public final boolean isSelected(final Displayable d) {
6986 return selection.contains(d);
6989 static public void updateSelection() {
6990 Display.updateSelection(null);
6992 static public void updateSelection(final Display calling) {
6993 final HashSet<Layer> hs = new HashSet<Layer>();
6994 for (final Display d : al_displays) {
6995 if (hs.contains(d.layer)) continue;
6996 hs.add(d.layer);
6997 if (null == d || null == d.selection) {
6998 Utils.log2("d is : "+ d + " d.selection is " + d.selection);
6999 } else {
7000 d.selection.update(); // recomputes box
7002 if (d != calling) { // TODO this is so dirty!
7003 if (d.selection.getNLinked() > 1) d.canvas.setUpdateGraphics(true); // this is overkill anyway
7004 d.canvas.repaint(d.selection.getLinkedBox(), Selection.PADDING);
7005 d.navigator.repaint(true); // everything
7010 static public void clearSelection(final Layer layer) {
7011 for (final Display d : al_displays) {
7012 if (d.layer == layer) d.selection.clear();
7015 static public void clearSelection() {
7016 for (final Display d : al_displays) {
7017 d.selection.clear();
7020 static public void clearSelection(final Project p) {
7021 for (final Display d : al_displays) {
7022 if (d.project == p) d.selection.clear();
7026 private void setTempCurrentImage() {
7027 WindowManager.setCurrentWindow(canvas.getFakeImagePlus().getWindow());
7028 WindowManager.setTempCurrentImage(canvas.getFakeImagePlus());
7031 /** Check if any display will paint the given Displayable within its srcRect. */
7032 static public boolean willPaint(final Displayable displ) {
7033 Rectangle box = null;
7034 for (final Display d : al_displays) {
7035 if (displ.getLayer() == d.layer) {
7036 if (null == box) box = displ.getBoundingBox(null);
7037 if (d.canvas.getSrcRect().intersects(box)) {
7038 return true;
7042 return false;
7045 @SuppressWarnings({ "unchecked", "rawtypes" })
7046 public void hideDeselected(final boolean not_images) {
7047 // hide deselected
7048 final ArrayList all = layer.getParent().getZDisplayables(); // a copy
7049 all.addAll(layer.getDisplayables());
7050 all.removeAll(selection.getSelected());
7051 if (not_images) all.removeAll(layer.getDisplayables(Patch.class));
7052 for (final Displayable d : (ArrayList<Displayable>)all) {
7053 if (d.isVisible()) {
7054 d.setVisible(false);
7055 Display.updateCheckboxes(d, DisplayablePanel.VISIBILITY_STATE, false);
7058 Display.update(layer);
7061 /** Cleanup internal lists that may contain the given Displayable. */
7062 static public void flush(final Displayable displ) {
7063 for (final Display d : al_displays) {
7064 d.selection.removeFromPrev(displ);
7068 public void resizeCanvas(final Rectangle bounds) {
7069 if (bounds.width <= 0|| bounds.height <= 0) throw new IllegalArgumentException("width and height must be larger than zero.");
7070 layer.getParent().setDimensions(bounds.x, bounds.y, bounds.width, bounds.height);
7073 public void resizeCanvas() {
7074 final GenericDialog gd = new GenericDialog("Resize LayerSet");
7075 gd.addNumericField("new width: ", layer.getLayerWidth(), 1, 8, "pixels");
7076 gd.addNumericField("new height: ", layer.getLayerHeight(), 1, 8, "pixels");
7077 gd.addChoice("Anchor: ", LayerSet.ANCHORS, LayerSet.ANCHORS[7]);
7078 gd.showDialog();
7079 if (gd.wasCanceled()) return;
7080 final float new_width = (float)gd.getNextNumber();
7081 final float new_height = (float)gd.getNextNumber();
7082 layer.getParent().setDimensions(new_width, new_height, gd.getNextChoiceIndex()); // will complain and prevent cropping existing Displayable objects
7086 // To record layer changes -- but it's annoying, this is visualization not data.
7087 static class DoSetLayer implements DoStep {
7088 final Display display;
7089 final Layer layer;
7090 DoSetLayer(final Display display) {
7091 this.display = display;
7092 this.layer = display.layer;
7094 public Displayable getD() { return null; }
7095 public boolean isEmpty() { return false; }
7096 public boolean apply(final int action) {
7097 display.setLayer(layer);
7099 public boolean isIdenticalTo(final Object ob) {
7100 if (!ob instanceof DoSetLayer) return false;
7101 final DoSetLayer dsl = (DoSetLayer) ob;
7102 return dsl.display == this.display && dsl.layer == this.layer;
7107 protected void duplicateLinkAndSendTo(final Displayable active, final int position, final Layer other_layer) {
7108 if (null == active || !(active instanceof Profile)) return;
7109 if (active.getLayer() == other_layer) return; // can't do that!
7110 // set current state
7111 Set<DoStep> dataedits = new HashSet<DoStep>();
7112 dataedits.add(new Displayable.DoEdit(active).init(active, new String[]{"data"})); // the links!
7113 getLayerSet().addChangeTreesStep(dataedits);
7114 final Profile profile = project.getProjectTree().duplicateChild((Profile)active, position, other_layer);
7115 if (null == profile) {
7116 getLayerSet().removeLastUndoStep();
7117 return;
7119 active.link(profile);
7120 other_layer.add(profile);
7121 slt.setAndWait(other_layer);
7122 selection.add(profile);
7123 // set new state
7124 dataedits = new HashSet<DoStep>();
7125 dataedits.add(new Displayable.DoEdit(active).init(active, new String[]{"data"})); // the links!
7126 dataedits.add(new Displayable.DoEdit(profile).init(profile, new String[]{"data"})); // the links!
7127 getLayerSet().addChangeTreesStep(dataedits);
7130 private final HashMap<Color,Layer> layer_channels = new HashMap<Color,Layer>();
7131 private final TreeMap<Integer,LayerPanel> layer_alpha = new TreeMap<Integer,LayerPanel>();
7132 private final HashMap<Layer,Byte> layer_composites = new HashMap<Layer,Byte>();
7133 boolean invert_colors = false,
7134 transp_overlay_images = true,
7135 transp_overlay_areas = false,
7136 transp_overlay_text_labels = false;
7137 Set<Class<?>> classes_to_multipaint = getClassesToMultiPaint();
7139 protected void setTranspOverlayImages(final boolean b) {
7140 this.transp_overlay_images = b;
7141 updateMultiPaint();
7143 protected void setTranspOverlayAreas(final boolean b) {
7144 this.transp_overlay_areas = b;
7145 updateMultiPaint();
7147 protected void setTranspOverlayTextLabels(final boolean b) {
7148 this.transp_overlay_text_labels = b;
7149 updateMultiPaint();
7151 protected void updateMultiPaint() {
7152 this.classes_to_multipaint = getClassesToMultiPaint();
7153 this.canvas.repaint(true);
7155 /** Only Patch, Stack; AreaList, Profile; and DLabel are considered.
7156 * The rest paints in other layers with color cues. */
7157 protected Set<Class<?>> getClassesToMultiPaint() {
7158 final HashSet<Class<?>> include = new HashSet<Class<?>>();
7159 if (transp_overlay_images) {
7160 include.add(Patch.class);
7161 include.add(Stack.class);
7163 if (transp_overlay_areas) {
7164 include.add(AreaList.class);
7165 include.add(Profile.class);
7167 if (transp_overlay_text_labels) {
7168 include.add(DLabel.class);
7170 return include;
7173 protected byte getLayerCompositeMode(final Layer layer) {
7174 synchronized (layer_composites) {
7175 final Byte b = layer_composites.get(layer);
7176 return null == b ? Displayable.COMPOSITE_NORMAL : b;
7180 protected void setLayerCompositeMode(final Layer layer, final byte compositeMode) {
7181 synchronized (layer_composites) {
7182 if (-1 == compositeMode || Displayable.COMPOSITE_NORMAL == compositeMode) {
7183 layer_composites.remove(layer);
7184 } else {
7185 layer_composites.put(layer, compositeMode);
7190 protected void resetLayerComposites() {
7191 synchronized (layer_composites) {
7192 layer_composites.clear();
7194 canvas.repaint(true);
7197 /** Remove all red/blue coloring of layers, and repaint canvas. */
7198 protected void resetLayerColors() {
7199 synchronized (layer_channels) {
7200 for (final Layer l : new ArrayList<Layer>(layer_channels.values())) { // avoid concurrent modification exception
7201 final LayerPanel lp = layer_panels.get(l);
7202 lp.setColor(Color.white);
7203 setColorChannel(lp.layer, Color.white);
7204 lp.slider.setEnabled(true);
7206 layer_channels.clear();
7208 canvas.repaint(true);
7211 /** Set all layer alphas to zero, and repaint canvas. */
7212 protected void resetLayerAlphas() {
7213 synchronized (layer_channels) {
7214 for (final LayerPanel lp : new ArrayList<LayerPanel>(layer_alpha.values())) {
7215 lp.setAlpha(0);
7217 layer_alpha.clear(); // should have already been cleared
7219 canvas.repaint(true);
7222 /** Add to layer_alpha table, or remove if alpha is zero. */
7223 protected void storeLayerAlpha(final LayerPanel lp, final float a) {
7224 synchronized (layer_channels) {
7225 if (M.equals(0, a)) {
7226 layer_alpha.remove(lp.layer.getParent().indexOf(lp.layer));
7227 } else {
7228 layer_alpha.put(lp.layer.getParent().indexOf(lp.layer), lp);
7233 static protected final int REPAINT_SINGLE_LAYER = 0;
7234 static protected final int REPAINT_MULTI_LAYER = 1;
7235 static protected final int REPAINT_RGB_LAYER = 2;
7237 /** Sets the values atomically, returns the painting mode. */
7238 protected int getPaintMode(final HashMap<Color,Layer> hm, final ArrayList<LayerPanel> list) {
7239 synchronized (layer_channels) {
7240 if (layer_channels.size() > 0) {
7241 hm.putAll(layer_channels);
7242 hm.put(Color.green, this.layer);
7243 return REPAINT_RGB_LAYER;
7245 list.addAll(layer_alpha.values());
7246 final int len = list.size();
7247 if (len > 1) return REPAINT_MULTI_LAYER;
7248 if (1 == len) {
7249 if (list.get(0).layer == this.layer) return REPAINT_SINGLE_LAYER; // normal mode
7250 return REPAINT_MULTI_LAYER;
7252 return REPAINT_SINGLE_LAYER;
7256 /** Set a layer to be painted as a specific color channel in the canvas.
7257 * Only Color.red and Color.blue are accepted.
7258 * Color.green is reserved for the current layer. */
7259 protected void setColorChannel(final Layer layer, final Color color) {
7260 synchronized (layer_channels) {
7261 if (Color.white == color) {
7262 // Remove
7263 for (final Iterator<Layer> it = layer_channels.values().iterator(); it.hasNext(); ) {
7264 if (it.next() == layer) {
7265 it.remove();
7266 break;
7269 canvas.repaint();
7270 } else if (Color.red == color || Color.blue == color) {
7271 // Reset current of that color, if any, to white
7272 final Layer l = layer_channels.remove(color);
7273 if (null != l) layer_panels.get(l).setColor(Color.white);
7274 // Replace or set new
7275 layer_channels.put(color, layer);
7276 tabs.repaint();
7277 canvas.repaint();
7278 } else {
7279 Utils.log2("Trying to set unacceptable color for layer " + layer + " : " + color);
7281 // enable/disable sliders
7282 final boolean b = 0 == layer_channels.size();
7283 for (final LayerPanel lp : layer_panels.values()) lp.slider.setEnabled(b);
7285 this.canvas.repaint(true);
7288 static public final void updateComponentTreeUI() {
7289 try {
7290 for (final Display d : al_displays) SwingUtilities.updateComponentTreeUI(d.frame);
7291 } catch (final Exception e) {
7292 IJError.print(e);
7296 /** Snap a Patch to the most overlapping Patch, if any.
7297 * This method is a shallow wrap around AlignTask.snap, setting proper undo steps. */
7298 static public final Bureaucrat snap(final Patch patch) {
7299 final Set<Displayable> linked = patch.getLinkedGroup(null);
7300 patch.getLayerSet().addTransformStep(linked);
7301 final Bureaucrat burro = AlignTask.snap(patch, null, false);
7302 burro.addPostTask(new Runnable() { @Override
7303 public void run() {
7304 patch.getLayerSet().addTransformStep(linked);
7305 }});
7306 return burro;
7309 private Mode mode = new DefaultMode(this);
7311 public void setMode(final Mode mode) {
7312 ProjectToolbar.setTool(ProjectToolbar.SELECT);
7313 this.mode = mode;
7314 canvas.repaint(true);
7315 Utils.invokeLater(new Runnable() { @Override
7316 public void run() {
7317 scroller.setEnabled(mode.canChangeLayer());
7318 }});
7321 public Mode getMode() {
7322 return mode;
7326 static private final Hashtable<String,ProjectThing> findLandmarkNodes(final Project p, final String landmarks_type) {
7327 final Set<ProjectThing> landmark_nodes = p.getRootProjectThing().findChildrenOfTypeR(landmarks_type);
7328 final Hashtable<String,ProjectThing> map = new Hashtable<String,ProjectThing>();
7329 for (final ProjectThing pt : landmark_nodes) {
7330 map.put(pt.toString() + "# " + pt.getId(), pt);
7332 return map;
7335 /** @param stack_patch is just a Patch of a series of Patch that make a stack of Patches. */
7336 private boolean insertStack(final ProjectThing target_landmarks, final Project source, final ProjectThing source_landmarks, final Patch stack_patch) {
7337 final List<Ball> l1 = new ArrayList<Ball>();
7338 final List<Ball> l2 = new ArrayList<Ball>();
7339 final Collection<ProjectThing> b1s = source_landmarks.findChildrenOfType("ball"); // source is the one that has the stack_patch
7340 final Collection<ProjectThing> b2s = target_landmarks.findChildrenOfType("ball"); // target is this
7341 final HashSet<String> seen = new HashSet<String>();
7342 for (final ProjectThing b1 : b1s) {
7343 final Ball ball1 = (Ball) b1.getObject();
7344 if (null == ball1) {
7345 Utils.log("ERROR: there's an empty 'ball' node in target project" + project.toString());
7346 return false;
7348 final String title1 = ball1.getTitle();
7349 for (final ProjectThing b2 : b2s) {
7350 final Ball ball2 = (Ball) b2.getObject();
7351 if (null == ball2) {
7352 Utils.log("ERROR: there's an empty 'ball' node in source project" + source.toString());
7353 return false;
7355 if (title1.equals(ball2.getTitle())) {
7356 if (seen.contains(title1)) continue;
7357 seen.add(title1);
7358 l1.add(ball1);
7359 l2.add(ball2);
7363 if (l1.size() < 4) {
7364 Utils.log("ERROR: found only " + l1.size() + " common landmarks: needs at least 4!");
7365 return false;
7367 // Extract coordinates of source project landmarks, in patch stack coordinate space
7368 final List<double[]> c1 = new ArrayList<double[]>();
7369 for (final Ball ball1 : l1) {
7370 final Map<Layer,double[]> m = ball1.getRawBalls();
7371 if (1 != m.size()) {
7372 Utils.log("ERROR: ball object " + ball1 + " from target project " + project + " has " + m.size() + " balls instead of just 1.");
7373 return false;
7375 final Map.Entry<Layer,double[]> e = m.entrySet().iterator().next();
7376 final Layer layer = e.getKey();
7377 final double[] xyr = e.getValue();
7378 final double[] fin = new double[]{xyr[0], xyr[1]};
7379 final AffineTransform affine = ball1.getAffineTransformCopy();
7380 try {
7381 affine.preConcatenate(stack_patch.getAffineTransform().createInverse());
7382 } catch (final Exception nite) {
7383 IJError.print(nite);
7384 return false;
7386 final double[] fout = new double[2];
7387 affine.transform(fin, 0, fout, 0, 1);
7388 c1.add(new double[]{fout[0], fout[1], layer.getParent().indexOf(layer)});
7391 // Extract coordinates of target (this) project landmarks, in calibrated world space
7392 final List<double[]> c2 = new ArrayList<double[]>();
7393 for (final Ball ball2 : l2) {
7394 final double[][] b = ball2.getBalls();
7395 if (1 != b.length) {
7396 Utils.log("ERROR: ball object " + ball2 + " from source project " + source + " has " + b.length + " balls instead of just 1.");
7397 return false;
7399 final double[] fin = new double[]{b[0][0], b[0][1]};
7400 final AffineTransform affine = ball2.getAffineTransformCopy();
7401 final double[] fout = new double[2];
7402 affine.transform(fin, 0, fout, 0, 1);
7403 c2.add(new double[]{fout[0], fout[1], b[0][2]});
7406 // Print landmarks:
7407 Utils.log("Landmarks:");
7408 for (Iterator<double[]> it1 = c1.iterator(), it2 = c2.iterator(); it1.hasNext(); ) {
7409 Utils.log(Utils.toString(it1.next()) + " <--> " + Utils.toString(it2.next()));
7412 // Create point matches
7413 final List<PointMatch> pm = new ArrayList<PointMatch>();
7414 for (Iterator<double[]> it1 = c1.iterator(), it2 = c2.iterator(); it1.hasNext(); ) {
7415 pm.add(new mpicbg.models.PointMatch(new mpicbg.models.Point(it1.next()), new mpicbg.models.Point(it2.next())));
7418 // Estimate AffineModel3D
7419 final AffineModel3D aff3d = new AffineModel3D();
7420 try {
7421 aff3d.fit(pm);
7422 } catch (final Exception e) {
7423 IJError.print(e);
7424 return false;
7427 // Create and add the Stack
7428 final String path = stack_patch.getImageFilePath();
7429 final Stack st = new Stack(project, new File(path).getName(), 0, 0, getLayerSet().getLayers().get(0), path);
7430 st.setInvertibleCoordinateTransform(aff3d);
7431 getLayerSet().add(st);
7432 return true;
7435 static private List<Patch> getPatchStacks(final LayerSet ls) {
7436 final HashSet<Patch> stacks = new HashSet<Patch>();
7437 for (final Patch pa : ls.getAll(Patch.class)) {
7438 if (stacks.contains(pa)) continue;
7439 final PatchStack ps = pa.makePatchStack();
7440 if (1 == ps.getNSlices()) continue;
7441 stacks.add(ps.getPatch(0));
7443 return new ArrayList<Patch>(stacks);
7446 static public Bureaucrat removeAlphaMasks(final Collection<Patch> patches) {
7447 return Bureaucrat.createAndStart(new Worker.Task("Remove alpha masks" + (patches.size() > 1 ? "s" : "")) {
7448 @Override
7449 public void exec() {
7450 if (null == patches || patches.isEmpty()) return;
7451 final ArrayList<Future<Boolean>> jobs = new ArrayList<Future<Boolean>>();
7452 for (final Patch p : patches) {
7453 p.setAlphaMask(null);
7454 final Future<Boolean> job = p.getProject().getLoader().regenerateMipMaps(p); // submit to queue
7455 if (null != job) jobs.add(job);
7457 // join all
7458 for (final Future<?> job : jobs) try {
7459 job.get();
7460 } catch (final Exception ie) {}
7461 }}, patches.iterator().next().getProject());
7464 /** Get the current {@link Roi}, if any. */
7465 public Roi getRoi() {
7466 return canvas.getFakeImagePlus().getRoi();
7469 /** Open a {@link GenericDialog} to ask for parameters to find all {@link Displayable} that abide to them.
7471 * @return The list of {@link Displayable} instances found.
7472 * @see Display#find(String, boolean, Class, List)
7474 public <T extends Displayable> List<T> find() {
7475 final GenericDialog gd = new GenericDialog("Select matching");
7476 Utils.addLayerRangeChoices(layer, gd);
7477 gd.addStringField("Regular expression:", "");
7478 final TreeMap<String,Class<?>> types = new TreeMap<String,Class<?>>();
7479 types.put("01 - All", Displayable.class);
7480 types.put("02 - Image (Patch, Stack)", ImageData.class);
7481 types.put("03 - Non-image (VectorData subtypes)", VectorData.class);
7482 types.put("04 - Tree (Treeline, AreaTree, Connector)", Tree.class);
7483 types.put("05 - Area container (AreaTree, AreaList)", AreaContainer.class);
7484 types.put("06 - Text labels", DLabel.class);
7485 types.put("07 - Patch (image)", Patch.class);
7486 types.put("08 - Stack (image)", Stack.class);
7487 types.put("09 - AreaList", AreaList.class);
7488 types.put("10 - AreaTree", AreaTree.class);
7489 types.put("11 - Treeline", Treeline.class);
7490 types.put("12 - Connector", Connector.class);
7491 types.put("13 - Ball", Ball.class);
7492 types.put("14 - Pipe", Pipe.class);
7493 types.put("15 - Polyline", Polyline.class);
7494 types.put("16 - Profile", Profile.class);
7495 types.put("17 - Dissector", Dissector.class);
7497 gd.addChoice("Type:", types.keySet().toArray(new String[types.size()]), types.firstKey());
7498 gd.addCheckbox("Visible only", true);
7499 gd.showDialog();
7500 if (gd.wasCanceled()) return Collections.EMPTY_LIST;
7501 final int first = gd.getNextChoiceIndex();
7502 final int last = gd.getNextChoiceIndex();
7503 return find(gd.getNextString(), gd.getNextBoolean(), (Class<T>)types.get(gd.getNextChoice()), first, last);
7506 /** @see Display#find(String, boolean, Class, List) */
7507 public <T extends Displayable> List<T> find(final String regex, final boolean visible_only, final Class<T> c, final int firstLayerIndex, final int lastLayerIndex) {
7508 return find(regex, visible_only, c, layer.getParent().getLayers(firstLayerIndex, lastLayerIndex));
7511 static private final String fixRegex(String regex) {
7512 if (regex.charAt(0) != '^') {
7513 if (regex.startsWith(".*")) regex = "^" + regex;
7514 else regex = "^.*" + regex;
7516 if (regex.charAt(regex.length()-1) != '$') {
7517 if (regex.endsWith(".*")) regex += "$";
7518 else regex += ".*$";
7520 return regex;
7525 * @param regex The regular expression to match against the title of a {@link Displayable}.
7526 * @param visible_only Whether to gather only {@link Displayable} instances that are visible.
7527 * @param c The specific class of {@link Displayable}; can be an interface like {@link VectorData}, {@link ImageData} or {@link AreaContainer}. Use {@Displayable} to mean any.
7528 * @param layers The range of {@link Layer} to search in.
7529 * @return
7531 static public <T extends Displayable> List<T> find(final String regex, final boolean visible_only, final Class<T> c, final List<Layer> layers) {
7532 Utils.log2(regex, visible_only, c, layers);
7533 final List<T> ds = new ArrayList<T>();
7534 final Pattern pattern = (null == regex || 0 == regex.length()) ? null : Pattern.compile(fixRegex(regex));
7535 for (final Layer l : layers) {
7536 for (final Displayable d : l.getDisplayables()) {
7537 if (visible_only && !d.isVisible()) continue;
7538 if (c.isInstance(d)) {
7539 if (null == pattern) ds.add((T)d);
7540 else if (pattern.matcher(d.getTitle()).matches()) ds.add((T)d);
7544 for (final ZDisplayable d : layers.get(0).getParent().getZDisplayables()) {
7545 if (visible_only && !d.isVisible()) continue;
7546 if (c.isInstance(d)
7547 && (null == pattern || pattern.matcher(d.getTitle()).matches())) {
7548 for (final Layer l : layers) {
7549 if (d.paintsAt(l)) {
7550 ds.add((T)d);
7551 break;
7556 return ds;