Open Image and Open Original Image.
[trakem2/tony-azevedo.git] / ini / trakem2 / display / Display.java
blob08ba8ed874ff7e57f989af844585e0243b5c0fc7
1 /**
3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2005-2009 Albert Cardona and Rodney Douglas.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation (http://www.gnu.org/licenses/gpl.txt )
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 You may contact Albert Cardona at acardona at ini.phys.ethz.ch
20 Institute of Neuroinformatics, University of Zurich / ETH, Switzerland.
21 **/
23 package ini.trakem2.display;
25 import ij.*;
26 import ij.gui.*;
27 import ij.io.OpenDialog;
28 import ij.io.DirectoryChooser;
29 import ij.io.SaveDialog;
30 import ij.measure.Calibration;
31 import ij.measure.ResultsTable;
32 import ij.process.ImageProcessor;
33 import ini.trakem2.Project;
34 import ini.trakem2.ControlWindow;
35 import ini.trakem2.parallel.Process;
36 import ini.trakem2.parallel.TaskFactory;
37 import ini.trakem2.persistence.DBObject;
38 import ini.trakem2.persistence.XMLOptions;
39 import ini.trakem2.persistence.Loader;
40 import ini.trakem2.persistence.ProjectTiler;
41 import ini.trakem2.utils.IJError;
42 import ini.trakem2.analysis.Graph;
43 import ini.trakem2.display.inspect.InspectPatchTrianglesMode;
44 import ini.trakem2.imaging.LayerStack;
45 import ini.trakem2.imaging.PatchStack;
46 import ini.trakem2.imaging.Blending;
47 import ini.trakem2.imaging.Segmentation;
48 import ini.trakem2.imaging.filters.FilterEditor;
49 import ini.trakem2.utils.AreaUtils;
50 import ini.trakem2.utils.Operation;
51 import ini.trakem2.utils.ProjectToolbar;
52 import ini.trakem2.utils.Saver;
53 import ini.trakem2.utils.Utils;
54 import ini.trakem2.utils.DNDInsertImage;
55 import ini.trakem2.utils.Search;
56 import ini.trakem2.utils.Bureaucrat;
57 import ini.trakem2.utils.Worker;
58 import ini.trakem2.utils.Dispatcher;
59 import ini.trakem2.utils.M;
60 import ini.trakem2.utils.Filter;
61 import ini.trakem2.utils.OptionPanel;
62 import ini.trakem2.tree.*;
63 import ini.trakem2.io.NeuroML;
65 import javax.swing.*;
66 import javax.swing.text.Document;
67 import javax.swing.event.*;
69 import mpicbg.trakem2.align.AlignLayersTask;
70 import mpicbg.trakem2.align.AlignTask;
72 import java.awt.*;
73 import java.awt.geom.AffineTransform;
74 import java.awt.geom.Area;
75 import java.awt.geom.Line2D;
76 import java.awt.geom.NoninvertibleTransformException;
77 import java.awt.image.BufferedImage;
78 import java.awt.event.*;
79 import java.util.*;
80 import java.util.List;
81 import java.lang.reflect.Field;
82 import java.lang.reflect.Method;
83 import java.io.BufferedOutputStream;
84 import java.io.FileOutputStream;
85 import java.io.OutputStreamWriter;
86 import java.io.Writer;
87 import java.io.File;
88 import java.util.concurrent.Future;
89 import java.util.concurrent.Callable;
90 import java.util.regex.Pattern;
92 import lenscorrection.DistortionCorrectionTask;
93 import lenscorrection.NonLinearTransform;
94 import mpicbg.ij.clahe.Flat;
95 import mpicbg.models.PointMatch;
96 import mpicbg.trakem2.transform.AffineModel3D;
97 import mpicbg.trakem2.transform.CoordinateTransform;
98 import mpicbg.trakem2.transform.CoordinateTransformList;
100 /** A Display is a class to show a Layer and enable mouse and keyboard manipulation of all its components. */
101 public final class Display extends DBObject implements ActionListener, IJEventListener {
103 /** coordinate transform transfer modes */
104 final static public int CT_REPLACE = 0;
105 final static public int CT_APPEND = 1;
106 final static public int CT_PREAPPEND = 2;
108 /** The Layer this Display is showing. */
109 private Layer layer;
111 private Displayable active = null;
112 /** All selected Displayable objects, including the active one. */
113 final private Selection selection = new Selection(this);
115 private JFrame frame;
116 private JTabbedPane tabs;
118 private Hashtable<Class<?>,RollingPanel> ht_tabs;
119 private RollingPanel panel_patches;
120 private RollingPanel panel_profiles;
121 private RollingPanel panel_zdispl;
122 private JScrollPane scroll_channels;
123 private JPanel panel_channels;
124 private RollingPanel panel_labels;
126 private JPanel panel_layers;
127 private JScrollPane scroll_layers;
128 private Hashtable<Layer,LayerPanel> layer_panels = new Hashtable<Layer,LayerPanel>();
130 private OptionPanel tool_options;
131 private JScrollPane scroll_options;
133 private OptionPanel filter_options;
134 private JScrollPane scroll_filter_options;
136 private JEditorPane annot_editor;
137 private JLabel annot_label;
138 private JPanel annot_panel;
139 static private HashMap<Displayable,Document> annot_docs = new HashMap<Displayable,Document>();
141 private JSlider transp_slider;
142 private DisplayNavigator navigator;
143 private JScrollBar scroller;
145 private DisplayCanvas canvas; // WARNING this is an AWT component, since it extends ImageCanvas
146 private JPanel all;
148 private JPopupMenu popup = null;
150 private ToolbarPanel toolbar_panel = null;
152 /** Contains the packed alphas of every channel. */
153 private int c_alphas = 0xffffffff; // all 100 % visible
154 private Channel[] channels;
156 private Hashtable<Displayable,DisplayablePanel> ht_panels = new Hashtable<Displayable,DisplayablePanel>();
158 /** Handle drop events, to insert image files. */
159 private DNDInsertImage dnd;
161 private int scroll_step = 1;
163 static private final Object DISPLAY_LOCK = new Object();
165 /** Keep track of all existing Display objects. */
166 static private Set<Display> al_displays = new HashSet<Display>();
167 /** The currently focused Display, if any. */
168 static private Display front = null;
170 /** Displays to open when all objects have been reloaded from the database. */
171 static private final Hashtable<Display,Object[]> ht_later = new Hashtable<Display,Object[]>();
173 /** A thread to handle user actions, for example an event sent from a popup menu. */
174 protected final Dispatcher dispatcher = new Dispatcher("Display GUI Updater");
176 static private WindowAdapter window_listener = new WindowAdapter() {
177 /** Unregister the closed Display. */
178 public void windowClosing(WindowEvent we) {
179 final Object source = we.getSource();
180 for (final Display d : al_displays) {
181 if (source == d.frame) {
182 d.remove(false); // calls destroy, which calls removeDisplay
183 break;
187 /** Set the source Display as front. */
188 public void windowActivated(WindowEvent we) {
189 // find which was it to make it be the front
190 ImageJ ij = IJ.getInstance();
191 if (null != ij && ij.quitting()) return;
192 final Object source = we.getSource();
193 for (final Display d : al_displays) {
194 if (source == d.frame) {
195 front = d;
196 // set toolbar
197 ProjectToolbar.setProjectToolbar();
198 // now, select the layer in the LayerTree
199 front.getProject().select(front.layer);
200 // finally, set the virtual ImagePlus that ImageJ will see
201 d.setTempCurrentImage();
202 // copied from ij.gui.ImageWindow, with modifications
203 if (IJ.isMacintosh() && IJ.getInstance()!=null) {
204 IJ.wait(10); // may be needed for Java 1.4 on OS X
205 d.frame.setMenuBar(Menus.getMenuBar());
207 return;
210 // else, restore the ImageJ toolbar for non-project images
211 //if (!source.equals(IJ.getInstance())) {
212 // ProjectToolbar.setImageJToolbar();
215 /** Restore the ImageJ toolbar */
216 public void windowDeactivated(WindowEvent we) {
217 // 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();
219 /** Call a pack() when the window is maximized to fit the canvas correctly. */
220 public void windowStateChanged(WindowEvent we) {
221 final Object source = we.getSource();
222 for (final Display d : al_displays) {
223 if (source != d.frame) continue;
224 d.pack();
225 break;
230 static public final Vector<Display> getDisplays() {
231 return new Vector<Display>(al_displays);
234 static public final int getDisplayCount() {
235 return al_displays.size();
238 private ComponentListener canvas_size_listener = new ComponentAdapter() {
239 public void componentResized(ComponentEvent ce) {
240 canvas.adjustDimensions();
241 canvas.repaint(true);
242 navigator.repaint(false); // update srcRect red frame position/size
246 /*// Currently not in use
247 private ComponentListener display_frame_listener = new ComponentAdapter() {
248 public void componentMoved(ComponentEvent ce) {
249 updateInDatabase("position");
254 static private ChangeListener tabs_listener = new ChangeListener() {
255 /** Listen to tab changes. */
256 public void stateChanged(final ChangeEvent ce) {
257 final Object source = ce.getSource();
258 for (final Display d : al_displays) {
259 if (source != d.tabs) continue;
260 // creating tabs fires the event!!!
261 if (null == d.frame || null == d.canvas) return;
262 final Container tab = (Container)d.tabs.getSelectedComponent();
263 if (tab == d.scroll_channels) {
264 // find active channel if any
265 for (int i=0; i<d.channels.length; i++) {
266 if (d.channels[i].isActive()) {
267 d.transp_slider.setValue((int)(d.channels[i].getAlpha() * 100));
268 break;
271 } else {
272 // recreate contents
273 JPanel p = null;
274 if (tab == d.panel_zdispl) {
275 p = d.panel_zdispl;
276 } else if (tab == d.panel_patches) {
277 p = d.panel_patches;
278 } else if (tab == d.panel_labels) {
279 p = d.panel_labels;
280 } else if (tab == d.panel_profiles) {
281 p = d.panel_profiles;
282 } else if (tab == d.scroll_layers) {
283 // nothing to do
284 return;
285 } else if (tab == d.scroll_options) {
286 // Choose according to tool
287 d.updateToolTab();
288 return;
291 d.updateTab(p);
293 if (null != d.active) {
294 // set the transp slider to the alpha value of the active Displayable if any
295 d.transp_slider.setValue((int)(d.active.getAlpha() * 100));
298 break;
304 private final AdjustmentListener scroller_listener = new AdjustmentListener() {
305 public void adjustmentValueChanged(final AdjustmentEvent ae) {
306 int index = ae.getValue();
307 Layer la = layer.getParent().getLayer(index);
308 if (la != Display.this.layer) slt.set(la);
312 private final SetLayerThread slt = new SetLayerThread();
314 private class SetLayerThread extends Thread {
316 private volatile Layer layer;
317 private final Object lock = new Object();
319 SetLayerThread() {
320 super("SetLayerThread");
321 setPriority(Thread.NORM_PRIORITY);
322 setDaemon(true);
323 start();
326 public final void set(final Layer layer) {
327 synchronized (lock) {
328 this.layer = layer;
330 synchronized (this) {
331 notify();
335 // 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.)
336 final void setAndWait(final Layer layer) {
337 if (null != layer) {
339 if (layer.getParent().preload_ahead > 0) {
340 preloadImagesAhead(Display.this.layer, layer, layer.getParent().preload_ahead);
343 Display.this.setLayer(layer);
344 Display.this.updateInDatabase("layer_id");
345 createColumnScreenshots();
349 public void run() {
350 while (!isInterrupted()) {
351 while (null == this.layer) {
352 synchronized (this) {
353 if (isInterrupted()) return;
354 try { wait(); } catch (InterruptedException ie) {}
357 Layer layer = null;
358 synchronized (lock) {
359 layer = this.layer;
360 this.layer = null;
363 if (isInterrupted()) return; // after nullifying layer
365 setAndWait(layer);
369 public void quit() {
370 interrupt();
371 synchronized (this) {
372 notify();
377 static final public void clearColumnScreenshots(final LayerSet ls) {
378 for (final Display d : al_displays) {
379 if (d.layer.getParent() == ls) d.clearColumnScreenshots();
383 private final ImagePreloader imagePreloader = new ImagePreloader();
385 private final class ImagePreloader extends Thread {
386 private Layer oldLayer, newLayer;
387 private int nLayers;
388 private boolean restart = false;
390 ImagePreloader() {
391 setPriority(Thread.NORM_PRIORITY);
392 setDaemon(true);
393 start();
396 final void reset(final Layer oldLayer, final Layer newLayer, final int nLayers) {
397 synchronized (this) {
398 this.restart = true;
399 this.oldLayer = oldLayer;
400 this.newLayer = newLayer;
401 this.nLayers = nLayers;
402 notify();
406 @Override
407 public void run() {
408 while (!isInterrupted()) {
409 final Layer oldLayer, newLayer;
410 final int nLayers;
411 synchronized (this) {
412 try {
413 if (!restart) wait();
414 } catch (InterruptedException e) {
415 return;
417 oldLayer = this.oldLayer;
418 newLayer = this.newLayer;
419 this.oldLayer = null;
420 this.newLayer = null;
421 nLayers = this.nLayers;
422 restart = false;
425 final LayerSet ls = oldLayer.getParent();
426 final int old_layer_index = ls.indexOf(oldLayer);
427 final int new_layer_index = ls.indexOf(newLayer);
428 final int sign = new_layer_index - old_layer_index;
430 final Area aroi = new Area(canvas.getSrcRect());
431 final double mag = canvas.getMagnification();
433 // Preload from most distant ahead to least.
434 // The assumption being that least distant are already cached.
435 //Utils.log2("-- started");
436 for (final Layer la : ls.getLayers(Math.max(0, Math.min(new_layer_index + sign * nLayers, ls.size() -1)), new_layer_index)) {
437 if (restart) break;
438 for (final Displayable d : la.getDisplayables(Patch.class, aroi, true)) {
439 if (restart) break;
440 if (isInterrupted()) return;
441 project.getLoader().fetchImage((Patch)d, mag);
442 //Utils.log2("preloaded #" + d.getId() + " from layer " + la.getParent().indexOf(la));
445 //Utils.log2("-- completed: restart is " + restart);
450 private final void preloadImagesAhead(final Layer oldLayer, final Layer newLayer, final int nLayers) {
451 imagePreloader.reset(oldLayer, newLayer, nLayers);
454 final public void clearColumnScreenshots() {
455 getLayerSet().clearScreenshots();
458 /** Only for DefaultMode. */
459 final private void createColumnScreenshots() {
460 final int n;
461 try {
462 if (mode.getClass() == DefaultMode.class) {
463 int ahead = project.getProperty("look_ahead_cache", 0);
464 if (ahead < 0) ahead = 0;
465 if (0 == ahead) return;
466 n = ahead;
467 } else return;
468 } catch (Exception e) {
469 IJError.print(e);
470 return;
472 project.getLoader().doLater(new Callable<Object>() {
473 public Object call() {
474 final Layer current = Display.this.layer;
475 // 1 - Create DisplayCanvas.Screenshot instances for the next 5 and previous 5 layers
476 final ArrayList<DisplayCanvas.Screenshot> s = new ArrayList<DisplayCanvas.Screenshot>();
477 Layer now = current;
478 Layer prev = now.getParent().previous(now);
479 int i = 0;
480 Layer next = now.getParent().next(now);
481 while (now != next && i < n) {
482 s.add(canvas.createScreenshot(next));
483 now = next;
484 next = now.getParent().next(now);
485 i++;
487 now = current;
488 i = 0;
489 while (now != prev && i < n) {
490 s.add(0, canvas.createScreenshot(prev));
491 now = prev;
492 prev = now.getParent().previous(now);
493 i++;
495 // Store them all into the LayerSet offscreens hashmap, but trigger image creation in parallel threads.
496 for (final DisplayCanvas.Screenshot sc : s) {
497 if (!current.getParent().containsScreenshot(sc)) {
498 sc.init();
499 current.getParent().storeScreenshot(sc);
500 project.getLoader().doLater(new Callable<Object>() {
501 public Object call() {
502 sc.createImage();
503 return null;
508 current.getParent().trimScreenshots();
509 return null;
514 /** Creates a new Display with adjusted magnification to fit in the screen. */
515 static public void createDisplay(final Project project, final Layer layer) {
516 // Really execute in a second round of event dispatch thread
517 // to enable the calling component to repaint back to normal
518 // and the events to terminate.
519 SwingUtilities.invokeLater(new Runnable() { public void run() {
520 Display display = new Display(project, layer);
521 Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
522 Rectangle srcRect = new Rectangle(0, 0, (int)layer.getLayerWidth(), (int)layer.getLayerHeight());
523 double mag = screen.width / layer.getLayerWidth();
524 if (mag * layer.getLayerHeight() > screen.height) mag = screen.height / layer.getLayerHeight();
525 mag = display.canvas.getLowerZoomLevel2(mag);
526 if (mag > 1.0) mag = 1.0;
527 //display.getCanvas().setup(mag, srcRect); // would call pack() at the wrong time!
528 // ... so instead: manually
529 display.getCanvas().setMagnification(mag);
530 display.getCanvas().setSrcRect(srcRect.x, srcRect.y, srcRect.width, srcRect.height);
531 display.getCanvas().setDrawingSize((int)Math.ceil(srcRect.width * mag), (int)Math.ceil(srcRect.height * mag));
533 display.updateFrameTitle(layer);
534 ij.gui.GUI.center(display.frame);
535 display.frame.pack();
536 }});
540 // The only two methods that ever modify the set of al_displays
543 /** Swap the current al_displays list with a new list that has the @param display in it. */
544 static private final void addDisplay(final Display display) {
545 if (null == display) return;
546 synchronized (DISPLAY_LOCK) {
547 final Set<Display> a = new HashSet<Display>();
548 if (null != al_displays) a.addAll(al_displays);
549 a.add(display);
550 al_displays = a;
551 front = display;
555 /** Swap the current al_displays list with a new list that lacks the @param dispaly, and set a new front if needed. */
556 static private final void removeDisplay(final Display display) {
557 if (null == display) return;
558 synchronized (DISPLAY_LOCK) {
559 Set<Display> a = new HashSet<Display>(al_displays);
560 a.remove(display);
561 if (null == front || front == display) {
562 if (a.size() > 0) {
563 front = a.iterator().next();
564 } else {
565 front = null;
568 al_displays = a;
572 /** For reconstruction purposes. The Display will be stored in the ht_later.*/
573 public Display(Project project, long id, Layer layer, Object[] props) {
574 super(project, id);
575 synchronized (ht_later) {
576 Display.ht_later.put(this, props);
578 this.layer = layer;
579 IJ.addEventListener(this);
580 Display.addDisplay(this);
583 /** A new Display from scratch, to show the given Layer. */
584 public Display(Project project, final Layer layer) {
585 this(project, layer, null);
588 /** Open a new Display centered around the given Displayable. */
589 public Display(Project project, Layer layer, Displayable displ) {
590 super(project);
591 active = displ;
592 this.layer = layer;
593 makeGUI(layer, null);
594 IJ.addEventListener(this);
595 setLayer(layer);
596 this.layer = layer; // after set layer!
597 addToDatabase();
598 addDisplay(this); // last: if there is an Exception, it won't be added
601 /** Reconstruct a Display from an XML entry, to be opened when everything is ready. */
602 public Display(Project project, long id, Layer layer, HashMap<String,String> ht) {
603 super(project, id);
604 if (null == layer) {
605 Utils.log2("Display: need a non-null Layer for id=" + id);
606 return;
608 Rectangle srcRect = new Rectangle(0, 0, (int)layer.getLayerWidth(), (int)layer.getLayerHeight());
609 double magnification = 0.25;
610 Point p = new Point(0, 0);
611 int c_alphas = 0xffffffff;
612 int c_alphas_state = 0xffffffff;
613 String data = ht.get("srcrect_x");
614 if (null != data) srcRect.x = Integer.parseInt(data);
615 data = ht.get("srcrect_y");
616 if (null != data) srcRect.y = Integer.parseInt(data);
617 data = ht.get("srcrect_width");
618 if (null != data) srcRect.width = Integer.parseInt(data);
619 data = ht.get("srcrect_height");
620 if (null != data) srcRect.height = Integer.parseInt(data);
621 data = ht.get("magnification");
622 if (null != data) magnification = Double.parseDouble(data);
623 data = ht.get("x");
624 if (null != data) p.x = Integer.parseInt(data);
625 data = ht.get("y");
626 if (null != data) p.y = Integer.parseInt(data);
627 data = ht.get("c_alphas");
628 if (null != data) {
629 try {
630 c_alphas = Integer.parseInt(data);
631 } catch (Exception ex) {
632 c_alphas = 0xffffffff;
635 data = ht.get("c_alphas_state");
636 if (null != data) {
637 try {
638 c_alphas_state = Integer.parseInt(data);
639 } catch (Exception ex) {
640 IJError.print(ex);
641 c_alphas_state = 0xffffffff;
644 data = ht.get("scroll_step");
645 if (null != data) {
646 try {
647 setScrollStep(Integer.parseInt(data));
648 } catch (Exception ex) {
649 IJError.print(ex);
650 setScrollStep(1);
653 data = ht.get("filter_enabled");
654 if (null != data) filter_enabled = Boolean.parseBoolean(data);
655 data = ht.get("filter_min_max_enabled");
656 if (null != data) filter_min_max_enabled = Boolean.parseBoolean(data);
657 data = ht.get("filter_min");
658 if (null != data) filter_min = Integer.parseInt(data);
659 data = ht.get("filter_max");
660 if (null != data) filter_max = Integer.parseInt(data);
661 data = ht.get("filter_invert");
662 if (null != data) filter_invert = Boolean.parseBoolean(data);
663 data = ht.get("filter_clahe_enabled");
664 if (null != data) filter_clahe_enabled = Boolean.parseBoolean(data);
665 data = ht.get("filter_clahe_block_size");
666 if (null != data) filter_clahe_block_size = Integer.parseInt(data);
667 data = ht.get("filter_clahe_histogram_bins");
668 if (null != data) filter_clahe_histogram_bins = Integer.parseInt(data);
669 data = ht.get("filter_clahe_max_slope");
670 if (null != data) filter_clahe_max_slope = Float.parseFloat(data);
672 // TODO the above is insecure, in that data is not fully checked to be within bounds.
675 Object[] props = new Object[]{p, new Double(magnification), srcRect, new Long(layer.getId()), new Integer(c_alphas), new Integer(c_alphas_state)};
676 synchronized (ht_later) {
677 Display.ht_later.put(this, props);
679 this.layer = layer;
680 IJ.addEventListener(this);
682 // addDisplay(this) will be called inside the loop in openLater, see below
685 /** After reloading a project from the database, open the Displays that the project had. */
686 static public Bureaucrat openLater() {
687 final HashMap<Display,Object[]> ht_later_local;
688 final Project[] ps;
689 synchronized (ht_later) {
690 if (0 == ht_later.size()) return null;
691 ht_later_local = new HashMap<Display,Object[]>(ht_later);
692 ht_later.keySet().removeAll(ht_later_local.keySet());
693 HashSet<Project> unique = new HashSet<Project>();
694 for (Display d : ht_later_local.keySet()) {
695 unique.add(d.project);
697 ps = unique.toArray(new Project[unique.size()]);
699 final Worker worker = new Worker.Task("Opening displays") {
700 public void exec() {
701 try {
702 Thread.sleep(300); // waiting for Swing
704 for (final Display d : ht_later_local.keySet()) {
705 addDisplay(d); // must be set as front before repainting any ZDisplayable!
706 Object[] props = ht_later_local.get(d);
707 if (ControlWindow.isGUIEnabled()) d.makeGUI(d.layer, props);
708 d.setLayerLater(d.layer, d.layer.get(((Long)props[3]).longValue())); //important to do it after makeGUI
709 if (!ControlWindow.isGUIEnabled()) continue;
711 d.updateFrameTitle(d.layer);
712 // 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
713 if (d.canvas.getMagnification() > 0.499) {
714 Utils.invokeLater(new Runnable() { public void run() {
715 Display.repaint(d.layer);
716 d.project.getLoader().setChanged(false);
717 Utils.log2("A set to false");
718 }});
720 d.project.getLoader().setChanged(false);
721 Utils.log2("B set to false");
723 if (null != front) front.getProject().select(front.layer);
725 } catch (Throwable t) {
726 IJError.print(t);
730 return Bureaucrat.createAndStart(worker, ps);
733 private final class ScrollerModel extends DefaultBoundedRangeModel {
734 private static final long serialVersionUID = 1L;
735 int index;
736 ScrollerModel(final Layer la) {
737 this.index = la.getParent().indexOf(la);
739 public void setValueWithoutEvent(int index) {
740 this.index = index;
741 scroller.updateUI(); // so the model needs to update the UI: how pretty!
743 @Override
744 public void setValue(int index) {
745 this.index = index;
746 super.setValue(index);
748 @Override
749 public int getValue() {
750 return this.index;
754 private void makeGUI(final Layer layer, final Object[] props) {
755 // gather properties
756 final Point p;
757 double mag = 1.0D;
758 Rectangle srcRect = null;
759 if (null != props) {
760 p = (Point)props[0];
761 mag = ((Double)props[1]).doubleValue();
762 srcRect = (Rectangle)props[2];
763 } else {
764 p = null;
767 // transparency slider
768 this.transp_slider = new JSlider(javax.swing.SwingConstants.HORIZONTAL, 0, 100, 100);
769 this.transp_slider.setBackground(Color.white);
770 this.transp_slider.setMinimumSize(new Dimension(250, 20));
771 this.transp_slider.setMaximumSize(new Dimension(250, 20));
772 this.transp_slider.setPreferredSize(new Dimension(250, 20));
773 TransparencySliderListener tsl = new TransparencySliderListener();
774 this.transp_slider.addChangeListener(tsl);
775 this.transp_slider.addMouseListener(tsl);
776 for (final KeyListener kl : this.transp_slider.getKeyListeners()) {
777 this.transp_slider.removeKeyListener(kl);
780 // Tabbed pane on the left
781 this.tabs = new JTabbedPane();
782 this.tabs.setMinimumSize(new Dimension(250, 300));
783 this.tabs.setBackground(Color.white);
784 this.tabs.addChangeListener(tabs_listener);
786 // Tab 1: Patches
787 this.panel_patches = new RollingPanel(this, Patch.class);
788 this.addTab("Patches", panel_patches);
790 // Tab 2: Profiles
791 this.panel_profiles = new RollingPanel(this, Profile.class);
792 this.addTab("Profiles", panel_profiles);
794 // Tab 3: ZDisplayables
795 this.panel_zdispl = new RollingPanel(this, ZDisplayable.class);
796 this.addTab("Z space", panel_zdispl);
798 // Tab 4: channels
799 this.panel_channels = makeTabPanel();
800 this.scroll_channels = makeScrollPane(panel_channels);
801 this.channels = new Channel[4];
802 this.channels[0] = new Channel(this, Channel.MONO);
803 this.channels[1] = new Channel(this, Channel.RED);
804 this.channels[2] = new Channel(this, Channel.GREEN);
805 this.channels[3] = new Channel(this, Channel.BLUE);
806 //this.panel_channels.add(this.channels[0]);
807 addGBRow(this.panel_channels, this.channels[1], null);
808 addGBRow(this.panel_channels, this.channels[2], this.channels[1]);
809 addGBRow(this.panel_channels, this.channels[3], this.channels[2]);
810 this.addTab("Opacity", scroll_channels);
812 // Tab 5: labels
813 this.panel_labels = new RollingPanel(this, DLabel.class);
814 this.addTab("Labels", panel_labels);
816 // Tab 6: layers
817 this.panel_layers = makeTabPanel();
818 this.scroll_layers = makeScrollPane(panel_layers);
819 recreateLayerPanels(layer);
820 this.scroll_layers.addMouseWheelListener(canvas);
821 this.addTab("Layers", scroll_layers);
823 // Tab 7: tool options
824 this.tool_options = new OptionPanel(); // empty
825 this.scroll_options = makeScrollPane(this.tool_options);
826 this.scroll_options.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
827 this.addTab("Tool options", this.scroll_options);
829 // Tab 8: annotations
830 this.annot_editor = new JEditorPane();
831 this.annot_editor.setEnabled(false); // by default, nothing is selected
832 this.annot_editor.setMinimumSize(new Dimension(200, 50));
833 this.annot_label = new JLabel("(No selected object)");
834 this.annot_panel = makeAnnotationsPanel(this.annot_editor, this.annot_label);
835 this.addTab("Annotations", this.annot_panel);
837 // Tab 9: filter options
838 this.filter_options = createFilterOptionPanel();
839 this.scroll_filter_options = makeScrollPane(this.filter_options);
840 this.scroll_filter_options.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
841 this.addTab("Live filter", this.scroll_filter_options);
843 this.ht_tabs = new Hashtable<Class<?>,RollingPanel>();
844 this.ht_tabs.put(Patch.class, panel_patches);
845 this.ht_tabs.put(Profile.class, panel_profiles);
846 this.ht_tabs.put(ZDisplayable.class, panel_zdispl);
847 this.ht_tabs.put(AreaList.class, panel_zdispl);
848 this.ht_tabs.put(Pipe.class, panel_zdispl);
849 this.ht_tabs.put(Polyline.class, panel_zdispl);
850 this.ht_tabs.put(Treeline.class, panel_zdispl);
851 this.ht_tabs.put(AreaTree.class, panel_zdispl);
852 this.ht_tabs.put(Connector.class, panel_zdispl);
853 this.ht_tabs.put(Ball.class, panel_zdispl);
854 this.ht_tabs.put(Dissector.class, panel_zdispl);
855 this.ht_tabs.put(DLabel.class, panel_labels);
856 this.ht_tabs.put(Stack.class, panel_zdispl);
857 // channels not included
858 // layers not included
859 // tools not included
860 // annotations not included
862 // Navigator
863 this.navigator = new DisplayNavigator(this, layer.getLayerWidth(), layer.getLayerHeight());
864 // Layer scroller (to scroll slices)
865 int extent = (int)(250.0 / layer.getParent().size());
866 if (extent < 10) extent = 10;
867 this.scroller = new JScrollBar(JScrollBar.HORIZONTAL);
868 this.scroller.setModel(new ScrollerModel(layer));
869 updateLayerScroller(layer);
870 this.scroller.addAdjustmentListener(scroller_listener);
872 // LAYOUT
873 final GridBagLayout layout = new GridBagLayout();
874 final GridBagConstraints c = new GridBagConstraints();
875 c.anchor = GridBagConstraints.NORTHWEST;
876 c.fill = GridBagConstraints.NONE;
877 c.weightx = 0;
878 c.weighty = 0;
879 c.gridx = 0;
880 c.gridy = 0;
881 c.ipadx = 0;
882 c.ipady = 0;
883 c.gridwidth = 1;
884 c.gridheight = 1;
886 Display.this.all = new JPanel();
887 all.setBackground(Color.white);
888 all.setLayout(layout);
890 c.insets = new Insets(0, 0, 0, 5);
892 // 1
893 toolbar_panel = new ToolbarPanel(); // fixed dimensions
894 layout.setConstraints(toolbar_panel, c);
895 all.add(toolbar_panel);
897 // 2
898 c.gridy++;
899 c.fill = GridBagConstraints.HORIZONTAL;
900 layout.setConstraints(transp_slider, c);
901 all.add(transp_slider);
903 // 3
904 c.gridy++;
905 c.weighty = 1;
906 c.fill = GridBagConstraints.BOTH;
907 layout.setConstraints(tabs, c);
908 all.add(tabs);
910 // 4
911 c.gridy++;
912 c.weighty = 0;
913 c.fill = GridBagConstraints.NONE;
914 layout.setConstraints(navigator, c);
915 all.add(navigator);
917 // 5
918 c.gridy++;
919 c.fill = GridBagConstraints.HORIZONTAL;
920 layout.setConstraints(scroller, c);
921 all.add(scroller);
923 // Canvas
924 this.canvas = new DisplayCanvas(this, (int)Math.ceil(layer.getLayerWidth()), (int)Math.ceil(layer.getLayerHeight()));
926 c.insets = new Insets(0, 0, 0, 0);
927 c.fill = GridBagConstraints.BOTH;
928 c.anchor = GridBagConstraints.NORTHWEST;
929 c.gridx = 1;
930 c.gridy = 0;
931 c.gridheight = GridBagConstraints.REMAINDER;
932 c.weightx = 1;
933 c.weighty = 1;
934 layout.setConstraints(Display.this.canvas, c);
935 all.add(canvas);
937 // prevent new Displays from screwing up if input is globally disabled
938 if (!project.isInputEnabled()) Display.this.canvas.setReceivesInput(false);
940 this.canvas.addComponentListener(canvas_size_listener);
941 this.navigator.addMouseWheelListener(canvas);
942 this.transp_slider.addKeyListener(canvas);
944 // JFrame to show the split pane
945 this.frame = ControlWindow.createJFrame(layer.toString());
946 this.frame.setBackground(Color.white);
947 this.frame.getContentPane().setBackground(Color.white);
948 if (IJ.isMacintosh() && IJ.getInstance()!=null) {
949 IJ.wait(10); // may be needed for Java 1.4 on OS X
950 this.frame.setMenuBar(ij.Menus.getMenuBar());
952 this.frame.addWindowListener(window_listener);
953 //this.frame.addComponentListener(display_frame_listener);
954 this.frame.getContentPane().add(all);
956 if (null != props) {
957 // restore canvas
958 canvas.setup(mag, srcRect);
959 // restore visibility of each channel
960 int cs = ((Integer)props[5]).intValue(); // aka c_alphas_state
961 int[] sel = new int[4];
962 sel[0] = ((cs&0xff000000)>>24);
963 sel[1] = ((cs&0xff0000)>>16);
964 sel[2] = ((cs&0xff00)>>8);
965 sel[3] = (cs&0xff);
966 // restore channel alphas
967 Display.this.c_alphas = ((Integer)props[4]).intValue();
968 channels[0].setAlpha( (float)((c_alphas&0xff000000)>>24) / 255.0f , 0 != sel[0]);
969 channels[1].setAlpha( (float)((c_alphas&0xff0000)>>16) / 255.0f , 0 != sel[1]);
970 channels[2].setAlpha( (float)((c_alphas&0xff00)>>8) / 255.0f , 0 != sel[2]);
971 channels[3].setAlpha( (float) (c_alphas&0xff) / 255.0f , 0 != sel[3]);
972 // restore visibility in the working c_alphas
973 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);
976 if (null != active && null != layer) {
977 Rectangle r = active.getBoundingBox();
978 r.x -= r.width/2;
979 r.y -= r.height/2;
980 r.width += r.width;
981 r.height += r.height;
982 if (r.x < 0) r.x = 0;
983 if (r.y < 0) r.y = 0;
984 if (r.width > layer.getLayerWidth()) r.width = (int)layer.getLayerWidth();
985 if (r.height> layer.getLayerHeight())r.height= (int)layer.getLayerHeight();
986 double magn = layer.getLayerWidth() / (double)r.width;
987 canvas.setup(magn, r);
990 // add keyListener to the whole frame
991 this.tabs.addKeyListener(canvas);
992 this.frame.addKeyListener(canvas);
994 // create a drag and drop listener
995 dnd = new DNDInsertImage(Display.this);
997 Utils.invokeLater(new Runnable() {
998 public void run() {
999 Display.this.frame.pack();
1000 ij.gui.GUI.center(Display.this.frame);
1001 Display.this.frame.setVisible(true);
1002 ProjectToolbar.setProjectToolbar(); // doesn't get it through events
1004 final Dimension screen = Toolkit.getDefaultToolkit().getScreenSize();
1006 if (null != props) {
1007 // fix positioning outside the screen (dual to single monitor)
1008 if (p.x >= 0 && p.x < screen.width - 50 && p.y >= 0 && p.y <= screen.height - 50) Display.this.frame.setLocation(p);
1009 else frame.setLocation(0, 0);
1012 // fix excessive size
1013 final Rectangle box = Display.this.frame.getBounds();
1014 int x = box.x;
1015 int y = box.y;
1016 int width = box.width;
1017 int height = box.height;
1018 if (box.width > screen.width) { x = 0; width = screen.width; }
1019 if (box.height > screen.height) { y = 0; height = screen.height; }
1020 if (x != box.x || y != box.y) {
1021 Display.this.frame.setLocation(x, y + (0 == y ? 30 : 0)); // added insets for bad window managers
1022 updateInDatabase("position");
1024 if (width != box.width || height != box.height) {
1025 Display.this.frame.setSize(new Dimension(width -10, height -30)); // added insets for bad window managers
1027 if (null == props) {
1028 // try to optimize canvas dimensions and magn
1029 double magn = layer.getLayerHeight() / screen.height;
1030 if (magn > 1.0) magn = 1.0;
1031 long size = 0;
1032 // limit magnification if appropriate
1033 for (final Displayable pa : layer.getDisplayables(Patch.class)) {
1034 final Rectangle ba = pa.getBoundingBox();
1035 size += (long)(ba.width * ba.height);
1037 if (size > 10000000) canvas.setInitialMagnification(0.25); // 10 Mb
1038 else {
1039 Display.this.frame.setSize(new Dimension((int)(screen.width * 0.66), (int)(screen.height * 0.66)));
1043 updateTab(panel_patches);
1045 // re-layout:
1046 tabs.validate();
1048 // Set the calibration of the FakeImagePlus to that of the LayerSet
1049 ((FakeImagePlus)canvas.getFakeImagePlus()).setCalibrationSuper(layer.getParent().getCalibrationCopy());
1051 updateFrameTitle(layer);
1052 // Set the FakeImagePlus as the current image
1053 setTempCurrentImage();
1055 // start a repainting thread
1056 if (null != props) {
1057 canvas.repaint(true); // repaint() is unreliable
1060 ControlWindow.setLookAndFeel();
1061 }});
1064 static public void repaintToolbar() {
1065 for (final Display d : al_displays) {
1066 if (null == d.toolbar_panel) continue; // not yet ready
1067 Utils.invokeLater(new Runnable() { public void run() {
1068 d.toolbar_panel.repaint();
1069 }});
1073 private class ToolbarPanel extends JPanel implements MouseListener {
1074 private static final long serialVersionUID = 1L;
1075 Method drawButton;
1076 Field lineType;
1077 Field SIZE;
1078 Field OFFSET;
1079 Toolbar toolbar = Toolbar.getInstance();
1080 int size;
1081 //int offset;
1082 ToolbarPanel() {
1083 setBackground(Color.white);
1084 addMouseListener(this);
1085 try {
1086 drawButton = Toolbar.class.getDeclaredMethod("drawButton", Graphics.class, Integer.TYPE);
1087 drawButton.setAccessible(true);
1088 lineType = Toolbar.class.getDeclaredField("lineType");
1089 lineType.setAccessible(true);
1090 SIZE = Toolbar.class.getDeclaredField("SIZE");
1091 SIZE.setAccessible(true);
1092 OFFSET = Toolbar.class.getDeclaredField("OFFSET");
1093 OFFSET.setAccessible(true);
1094 size = ((Integer)SIZE.get(null)).intValue();
1095 //offset = ((Integer)OFFSET.get(null)).intValue();
1096 } catch (Exception e) {
1097 IJError.print(e);
1099 // Magic cocktail:
1100 Dimension dim = new Dimension(250, size+size);
1101 setMinimumSize(dim);
1102 setPreferredSize(dim);
1103 setMaximumSize(dim);
1105 public void update(Graphics g) { paint(g); }
1106 public void paint(Graphics gr) {
1107 try {
1108 // Either extend the heavy-weight Canvas, or use an image to paint to.
1109 // Otherwise, rearrangements of the layout while painting will result
1110 // in incorrectly positioned toolbar buttons.
1111 BufferedImage bi = new BufferedImage(getWidth(), getHeight(), BufferedImage.TYPE_INT_RGB);
1112 Graphics g = bi.getGraphics();
1113 g.setColor(Color.white);
1114 g.fillRect(0, 0, getWidth(), getHeight());
1115 int i = 0;
1116 for (; i<Toolbar.LINE; i++) {
1117 drawButton.invoke(toolbar, g, i);
1119 drawButton.invoke(toolbar, g, lineType.get(toolbar));
1120 for (; i<=Toolbar.TEXT; i++) {
1121 drawButton.invoke(toolbar, g, i);
1123 drawButton.invoke(toolbar, g, Toolbar.ANGLE);
1124 // newline
1125 AffineTransform aff = new AffineTransform();
1126 aff.translate(-size*Toolbar.TEXT, size-1);
1127 ((Graphics2D)g).setTransform(aff);
1128 for (; i<18; i++) {
1129 drawButton.invoke(toolbar, g, i);
1131 gr.drawImage(bi, 0, 0, null);
1132 bi.flush();
1133 g.dispose();
1134 } catch (Exception e) {
1135 IJError.print(e);
1139 // Fails: "origin not in parent's hierarchy" ... right.
1140 private void showPopup(String name, int x, int y) {
1141 try {
1142 Field f = Toolbar.getInstance().getClass().getDeclaredField(name);
1143 f.setAccessible(true);
1144 PopupMenu p = (PopupMenu) f.get(Toolbar.getInstance());
1145 p.show(this, x, y);
1146 } catch (Throwable t) {
1147 IJError.print(t);
1151 public void mousePressed(MouseEvent me) {
1152 int x = me.getX();
1153 int y = me.getY();
1154 if (y > size) {
1155 if (x > size * 7) return; // off limits
1156 x += size * 9;
1157 y -= size;
1158 } else {
1159 if (x > size * 9) return; // off limits
1162 if (Utils.isPopupTrigger(me)) {
1163 if (x >= size && x <= size * 2 && y >= 0 && y <= size) {
1164 showPopup("ovalPopup", x, y);
1165 return;
1166 } else if (x >= size * 4 && x <= size * 5 && y >= 0 && y <= size) {
1167 showPopup("linePopup", x, y);
1168 return;
1172 Toolbar.getInstance().mousePressed(new MouseEvent(toolbar, me.getID(), System.currentTimeMillis(), me.getModifiers(), x, y, me.getClickCount(), me.isPopupTrigger()));
1173 repaint();
1174 Display.toolChanged(ProjectToolbar.getToolId()); // should fire on its own but it does not (?) TODO
1176 public void mouseReleased(MouseEvent me) {}
1177 public void mouseClicked(MouseEvent me) {}
1178 public void mouseEntered(MouseEvent me) {}
1179 public void mouseExited(MouseEvent me) {}
1182 private JPanel makeTabPanel() {
1183 JPanel panel = new JPanel();
1184 panel.setLayout(new GridBagLayout());
1185 return panel;
1188 private JScrollPane makeScrollPane(Component c) {
1189 JPanel p = new JPanel();
1190 GridBagLayout gb = new GridBagLayout();
1191 p.setLayout(gb);
1193 GridBagConstraints co = new GridBagConstraints();
1194 co.anchor = GridBagConstraints.NORTHWEST;
1195 co.fill = GridBagConstraints.HORIZONTAL;
1196 co.gridy = 0;
1197 co.weighty = 0;
1198 gb.setConstraints(c, co);
1199 p.add(c);
1201 JPanel padding = new JPanel();
1202 padding.setPreferredSize(new Dimension(0,0));
1203 co.fill = GridBagConstraints.BOTH;
1204 co.gridy = 1;
1205 co.weighty = 1;
1206 gb.setConstraints(padding, co);
1207 p.add(padding);
1209 JScrollPane jsp = new JScrollPane(p);
1210 jsp.setBackground(Color.white); // no effect
1211 jsp.getViewport().setBackground(Color.white); // no effect
1212 // adjust scrolling to use one DisplayablePanel as the minimal unit
1213 jsp.getVerticalScrollBar().setBlockIncrement(DisplayablePanel.HEIGHT); // clicking within the track
1214 jsp.getVerticalScrollBar().setUnitIncrement(DisplayablePanel.HEIGHT); // clicking on an arrow
1215 jsp.setHorizontalScrollBarPolicy(javax.swing.ScrollPaneConstants.HORIZONTAL_SCROLLBAR_NEVER);
1216 jsp.setPreferredSize(new Dimension(250, 300));
1217 jsp.setMinimumSize(new Dimension(250, 300));
1218 return jsp;
1221 private void addGBRow(Container container, Component comp, Component previous) {
1222 GridBagLayout gb = (GridBagLayout) container.getLayout();
1223 GridBagConstraints c = null;
1224 if (null == previous) {
1225 c = new GridBagConstraints();
1226 c.anchor = GridBagConstraints.NORTHWEST;
1227 c.fill = GridBagConstraints.HORIZONTAL;
1228 c.gridy = 0;
1229 } else {
1230 c = gb.getConstraints(previous);
1231 c.gridy += 1;
1233 gb.setConstraints(comp, c);
1234 container.add(comp);
1237 private JPanel makeAnnotationsPanel(JEditorPane ep, JLabel label) {
1238 JPanel p = new JPanel();
1239 GridBagLayout gb = new GridBagLayout();
1240 GridBagConstraints c = new GridBagConstraints();
1241 p.setLayout(gb);
1242 c.fill = GridBagConstraints.HORIZONTAL;
1243 c.anchor = GridBagConstraints.NORTHWEST;
1244 c.ipadx = 5;
1245 c.ipady = 5;
1246 c.gridx = 0;
1247 c.gridy = 0;
1248 c.weightx = 0;
1249 c.weighty = 0;
1250 JLabel title = new JLabel("Annotate:");
1251 gb.setConstraints(title, c);
1252 p.add(title);
1253 c.gridy++;
1254 gb.setConstraints(label, c);
1255 p.add(label);
1256 c.weighty = 1;
1257 c.gridy++;
1258 c.fill = GridBagConstraints.BOTH;
1259 c.ipadx = 0;
1260 c.ipady = 0;
1261 JScrollPane sp = new JScrollPane(ep, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
1262 sp.setPreferredSize(new Dimension(250, 300));
1263 sp.setMinimumSize(new Dimension(250, 300));
1264 gb.setConstraints(sp, c);
1265 p.add(sp);
1266 return p;
1269 public DisplayCanvas getCanvas() {
1270 return canvas;
1273 public synchronized void setLayer(final Layer new_layer) {
1274 setLayer(new_layer, false);
1277 private synchronized void setLayer(final Layer new_layer, final boolean bypass_checks) {
1278 if (!bypass_checks) {
1279 if (null == new_layer || new_layer == this.layer || new_layer.getParent() != this.layer.getParent()) return;
1282 final Layer current_layer = this.layer;
1284 if (!mode.canChangeLayer()) {
1285 Utils.invokeLater(new Runnable() { public void run() {
1286 ((ScrollerModel)scroller.getModel()).setValueWithoutEvent(current_layer.getParent().indexOf(current_layer));
1287 }});
1288 return;
1291 // Set:
1292 this.layer = new_layer;
1294 // deselect all except ZDisplayables
1295 final ArrayList<Displayable> sel = selection.getSelected();
1296 final Displayable last_active = this.active;
1297 int sel_next = -1;
1298 for (final Iterator<Displayable> it = sel.iterator(); it.hasNext(); ) {
1299 final Displayable d = it.next();
1300 if (!(d instanceof ZDisplayable)) {
1301 it.remove();
1302 selection.remove(d);
1303 if (d == last_active && sel.size() > 0) {
1304 // set as active (by selecting it) the last one of the remaining, if any
1305 sel_next = sel.size()-1;
1309 if (-1 != sel_next && sel.size() > 0) select(sel.get(sel_next), true);
1311 Utils.invokeLater(new Runnable() { public void run() {
1312 translateLayerColors(current_layer, new_layer);
1313 if (tabs.getSelectedComponent() == scroll_layers) {
1314 scrollToShow(scroll_layers, layer_panels.get(new_layer));
1316 // Below, will fire the event as well, and call stl.set(layer) which calls setLayer with the same layer, and returns.
1317 // But just scroller.getModel().setValue(int) will ALSO fire the event. So let it do the loop.
1318 int index = Display.this.layer.getParent().indexOf(new_layer);
1319 if (scroller.getValue() != index) {
1320 ((ScrollerModel)scroller.getModel()).setValueWithoutEvent(index);
1323 /* // OBSOLETE
1324 // update the current Layer pointer in ZDisplayable objects
1325 for (Iterator it = layer.getParent().getZDisplayables().iterator(); it.hasNext(); ) {
1326 ((ZDisplayable)it.next()).setLayer(layer); // the active layer
1330 if (tabs.getSelectedComponent() == panel_zdispl) {
1331 // no need, doesn't change
1332 } else {
1333 updateVisibleDisplayableTab();
1336 updateFrameTitle(new_layer); // to show the new 'z'
1337 // select the Layer in the LayerTree
1338 project.select(new_layer); // does so in a separate thread
1339 // update active Displayable:
1341 // trigger repaints
1342 navigator.repaint(true);
1343 canvas.repaint(true);
1345 // repaint tabs (hard as hell)
1346 Utils.updateComponent(tabs);
1347 // @#$%^! The above works half the times, so explicit repaint as well:
1348 Component c = tabs.getSelectedComponent();
1349 if (null == c) {
1350 c = panel_patches;
1351 tabs.setSelectedComponent(panel_patches);
1353 Utils.updateComponent(c);
1355 // update the coloring in the ProjectTree
1356 project.getProjectTree().updateUILater();
1358 setTempCurrentImage();
1359 }});
1362 static public void updateVisibleTabs() {
1363 for (final Display d : al_displays) {
1364 d.updateVisibleDisplayableTab();
1367 static public void updateVisibleTabs(final Project p) {
1368 for (final Display d : al_displays) {
1369 if (d.project == p) d.updateVisibleDisplayableTab();
1373 /** Recreate the tab that is being shown. */
1374 private void updateVisibleDisplayableTab() {
1375 Utils.invokeLater(new Runnable() { public void run() {
1376 Component c = tabs.getSelectedComponent();
1377 if (c instanceof RollingPanel) {
1378 ((RollingPanel)c).updateList();
1380 }});
1383 private void setLayerLater(final Layer layer, final Displayable active) {
1384 if (null == layer) return;
1385 this.layer = layer;
1386 if (!ControlWindow.isGUIEnabled()) return;
1387 Utils.invokeLater(new Runnable() { public void run() {
1388 navigator.repaint(true); // was not done when adding
1389 // Order matters: first set active
1390 setActive(active);
1391 Container c = (Container)tabs.getSelectedComponent();
1392 if (c != panel_zdispl) updateTab(c);
1393 }});
1396 /** A class to listen to the transparency_slider of the DisplayablesSelectorWindow. */
1397 private class TransparencySliderListener extends MouseAdapter implements ChangeListener {
1399 public void stateChanged(ChangeEvent ce) {
1400 //change the transparency value of the current active displayable
1401 float new_value = (float)((JSlider)ce.getSource()).getValue();
1402 setTransparency(new_value / 100.0f);
1405 public void mouseReleased(MouseEvent me) {
1406 // update navigator window
1407 navigator.repaint(true);
1411 /** Context-sensitive: to a Displayable, or to a channel. */
1412 private void setTransparency(final float value) {
1413 Component scroll = tabs.getSelectedComponent();
1414 if (scroll == scroll_channels) {
1415 for (int i=0; i<4; i++) {
1416 if (channels[i].getBackground() == Color.cyan) {
1417 channels[i].setAlpha(value); // will call back and repaint the Display
1418 return;
1421 } else if (null != active) {
1422 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.
1423 selection.setAlpha(value);
1424 Display.repaint(active.getLayerSet(), active, active.getBoundingBox(), 5, false);
1429 public void setTransparencySlider(final float transp) {
1430 if (transp >= 0.0f && transp <= 1.0f) {
1431 // fire event
1432 Utils.invokeLater(new Runnable() { public void run() {
1433 transp_slider.setValue((int)(transp * 100));
1434 }});
1438 /** Mark the canvas for updating the offscreen images if the given Displayable is NOT the active. */ // Used by the Displayable.setVisible for example.
1439 static public void setUpdateGraphics(final Layer layer, final Displayable displ) {
1440 for (final Display d : al_displays) {
1441 if (layer == d.layer && null != d.active && d.active != displ) {
1442 d.canvas.setUpdateGraphics(true);
1447 /** Flag the DisplayCanvas of Displays showing the given Layer to update their offscreen images.*/
1448 static public void setUpdateGraphics(final Layer layer, final boolean update) {
1449 for (final Display d : al_displays) {
1450 if (layer == d.layer) {
1451 d.canvas.setUpdateGraphics(update);
1456 /** Whether to update the offscreen images or not. */
1457 public void setUpdateGraphics(boolean b) {
1458 canvas.setUpdateGraphics(b);
1461 /** Update the entire GUI:
1462 * 1 - The layer scroller
1463 * 2 - The visible tab panels
1464 * 3 - The toolbar
1465 * 4 - The navigator
1466 * 5 - The canvas
1468 static public void update() {
1469 for (final Display d : al_displays) {
1470 d.updateLayerScroller(d.layer);
1471 d.updateVisibleDisplayableTab();
1472 d.toolbar_panel.repaint();
1473 d.navigator.repaint(true);
1474 d.canvas.repaint(true);
1478 /** Find all Display instances that contain the layer and repaint them, in the Swing GUI thread. */
1479 static public void update(final Layer layer) {
1480 if (null == layer) return;
1481 for (final Display d : al_displays) {
1482 if (d.isShowing(layer)) {
1483 d.repaintAll();
1488 static public void update(final LayerSet set) {
1489 update(set, true);
1492 /** 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. */
1493 static public void update(final LayerSet set, final boolean update_canvas_dimensions) {
1494 if (null == set) return;
1495 for (final Display d : al_displays) {
1496 if (d.layer.getParent() == set) {
1497 d.updateSnapshots();
1498 if (update_canvas_dimensions) d.canvas.setDimensions(set.getLayerWidth(), set.getLayerHeight());
1499 d.repaintAll();
1504 /** Release all resources held by this Display and close the frame. */
1505 protected void destroy() {
1506 // Set a new front if any and remove from the list of open Displays
1507 removeDisplay(this);
1508 // Inactivate this Display:
1509 dispatcher.quit();
1510 canvas.setReceivesInput(false);
1511 slt.quit();
1512 imagePreloader.interrupt();
1514 // update the coloring in the ProjectTree and LayerTree
1515 if (!project.isBeingDestroyed()) {
1516 try {
1517 project.getProjectTree().updateUILater();
1518 project.getLayerTree().updateUILater();
1519 } catch (Exception e) {
1520 Utils.log2("updateUI failed at Display.destroy()");
1524 //frame.removeComponentListener(component_listener);
1525 frame.removeWindowListener(window_listener);
1526 frame.removeWindowFocusListener(window_listener);
1527 frame.removeWindowStateListener(window_listener);
1528 frame.removeKeyListener(canvas);
1529 //frame.removeMouseListener(frame_mouse_listener);
1530 canvas.removeKeyListener(canvas);
1531 tabs.removeChangeListener(tabs_listener);
1532 tabs.removeKeyListener(canvas);
1533 IJ.removeEventListener(this);
1534 bytypelistener = null;
1535 canvas.destroy();
1536 navigator.destroy();
1537 scroller.removeAdjustmentListener(scroller_listener);
1538 frame.setVisible(false);
1539 //no need, and throws exception//frame.dispose();
1540 active = null;
1541 if (null != selection) selection.clear();
1543 // repaint layer tree (to update the label color)
1544 try {
1545 project.getLayerTree().updateUILater(); // works only after setting the front above
1546 } catch (Exception e) {} // ignore swing sync bullshit when closing everything too fast
1547 // remove the drag and drop listener
1548 dnd.destroy();
1551 /** Find all Display instances that contain a Layer of the given project and close them without removing the Display entries from the database. */
1552 static synchronized public void close(final Project project) {
1553 Display[] d = new Display[al_displays.size()];
1554 al_displays.toArray(d);
1555 for (int i=0; i<d.length; i++) {
1556 if (d[i].getProject() == project) {
1557 removeDisplay(d[i]);
1558 d[i].destroy();
1563 /** Find all Display instances that contain the layer and close them and remove the Display from the database. */
1564 static public void close(final Layer layer) {
1565 for (final Display d : al_displays) {
1566 if (d.isShowing(layer)) {
1567 d.remove(false); // calls destroy which calls removeDisplay
1572 /** Find all Display instances that are showing the layer and either move to the next or previous layer, or close it if none. */
1573 static public void remove(final Layer layer) {
1574 for (final Display d : al_displays) {
1575 if (d.isShowing(layer)) {
1576 Layer la = layer.getParent().next(layer);
1577 if (layer == la || null == la) la = layer.getParent().previous(layer);
1578 if (null == la || layer == la) {
1579 d.remove(false); // will call destroy which calls removeDisplay
1580 } else {
1581 d.slt.set(la);
1587 /** Close this Display window. */
1588 public boolean remove(boolean check) {
1589 if (check) {
1590 if (!Utils.check("Delete the Display ?")) return false;
1592 // flush the offscreen images and close the frame
1593 destroy();
1594 removeFromDatabase();
1595 return true;
1598 public Layer getLayer() {
1599 return layer;
1602 public LayerSet getLayerSet() {
1603 return layer.getParent();
1606 public boolean isShowing(final Layer layer) {
1607 return this.layer == layer;
1610 public DisplayNavigator getNavigator() {
1611 return navigator;
1614 /** Repaint both the canvas and the navigator, updating the graphics, and the title and tabs. */
1615 public void repaintAll() {
1616 if (repaint_disabled) return;
1617 navigator.repaint(true);
1618 canvas.repaint(true);
1619 Utils.updateComponent(tabs);
1620 updateFrameTitle();
1623 /** Repaint the canvas updating graphics, the navigator without updating graphics, and the title. */
1624 public void repaintAll2() {
1625 if (repaint_disabled) return;
1626 navigator.repaint(false);
1627 canvas.repaint(true);
1628 updateFrameTitle();
1631 /** Repaint the canvas updating graphics, and the navigator without updating graphics. */
1632 public void repaintAll3() {
1633 if (repaint_disabled) return;
1634 navigator.repaint(false);
1635 canvas.repaint(true);
1636 updateFrameTitle();
1639 static protected void repaintSnapshots(final LayerSet set) {
1640 if (repaint_disabled) return;
1641 for (final Display d : al_displays) {
1642 if (d.getLayer().getParent() == set) {
1643 d.navigator.repaint(true);
1644 Utils.updateComponent(d.tabs);
1648 static protected void repaintSnapshots(final Layer layer) {
1649 if (repaint_disabled) return;
1650 for (final Display d : al_displays) {
1651 if (d.getLayer() == layer) {
1652 d.navigator.repaint(true);
1653 Utils.updateComponent(d.tabs);
1658 public void pack() {
1659 Utils.invokeLater(new Runnable() { public void run() {
1660 frame.pack();
1661 navigator.repaint(false); // update srcRect red frame position/size
1662 }});
1665 static public void pack(final LayerSet ls) {
1666 for (final Display d : al_displays) {
1667 if (d.layer.getParent() == ls) d.pack();
1671 /** Grab the last selected display (or create an new one if none) and show in it the layer,centered on the Displayable object. */
1672 static public void setFront(final Layer layer, final Displayable displ) {
1673 if (null == front) {
1674 Display display = new Display(layer.getProject(), layer); // gets set to front
1675 display.showCentered(displ);
1676 } else if (layer == front.layer) {
1677 front.showCentered(displ);
1678 } else {
1679 // find one:
1680 for (final Display d : al_displays) {
1681 if (d.layer == layer) {
1682 d.frame.toFront();
1683 d.showCentered(displ);
1684 return;
1687 // else, open new one
1688 new Display(layer.getProject(), layer).showCentered(displ);
1692 /** 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. */
1693 static protected void add(final Layer layer, final Displayable displ, final boolean activate) {
1694 for (final Display d : al_displays) {
1695 if (d.layer == layer) {
1696 if (front == d) {
1697 d.add(displ, activate, true);
1698 //front.frame.toFront();
1699 } else {
1700 d.add(displ, false, true);
1706 static protected void add(final Layer layer, final Displayable displ) {
1707 add(layer, displ, true);
1710 /** Add the ZDisplayable to all Displays that show a Layer belonging to the given LayerSet. */
1711 static protected void add(final LayerSet set, final ZDisplayable zdispl) {
1712 for (final Display d : al_displays) {
1713 if (d.layer.getParent() == set) {
1714 if (front == d) {
1715 zdispl.setLayer(d.layer); // the active one
1716 d.add(zdispl, true, true); // calling add(Displayable, boolean, boolean)
1717 //front.frame.toFront();
1718 } else {
1719 d.add(zdispl, false, true);
1725 static protected void addAll(final Layer layer, final Collection<? extends Displayable> coll) {
1726 for (final Display d : al_displays) {
1727 if (d.layer == layer) {
1728 d.addAll(coll);
1733 static protected void addAll(final LayerSet set, final Collection<? extends ZDisplayable> coll) {
1734 for (final Display d : al_displays) {
1735 if (d.layer.getParent() == set) {
1736 for (final ZDisplayable zd : coll) {
1737 if (front == d) zd.setLayer(d.layer); // this is obsolete now, TODO
1739 d.addAll(coll);
1744 private final void addAll(final Collection<? extends Displayable> coll) {
1745 // if any of the elements in the collection matches the type of the current tab, update that tab
1746 // ... it's easier to just update the front tab
1747 updateVisibleDisplayableTab();
1748 selection.clear();
1749 navigator.repaint(true);
1752 /** Add it to the proper panel, at the top, and set it active. */
1753 private final void add(final Displayable d, final boolean activate, final boolean repaint_snapshot) {
1754 if (activate) {
1755 updateVisibleDisplayableTab();
1756 selection.clear();
1757 selection.add(d);
1758 Display.repaint(d.getLayerSet()); // update the al_top list to contain the active one, or background image for a new Patch.
1759 Utils.log2("Added " + d);
1761 if (repaint_snapshot) navigator.repaint(true);
1764 /** Find the displays that show the given Layer, and remove the given Displayable from the GUI. */
1765 static public void remove(final Layer layer, final Displayable displ) {
1766 for (final Display d : al_displays) {
1767 if (layer == d.layer) d.remove(displ);
1771 private void remove(final Displayable displ) {
1772 final DisplayablePanel ob = ht_panels.remove(displ);
1773 if (null != ob) {
1774 final RollingPanel rp = ht_tabs.get(displ.getClass());
1775 if (null != rp) {
1776 Utils.invokeLater(new Runnable() { public void run() {
1777 rp.updateList();
1778 }});
1781 canvas.setUpdateGraphics(true);
1782 repaint(displ, null, 5, true, false);
1783 // from Selection.deleteAll this method is called ... but it's ok: same thread, no locking problems.
1784 selection.remove(displ);
1787 static public void remove(final ZDisplayable zdispl) {
1788 for (final Display d : al_displays) {
1789 if (zdispl.getLayerSet() == d.layer.getParent()) {
1790 d.remove((Displayable)zdispl);
1795 static public void repaint(final Layer layer, final Displayable displ, final int extra) {
1796 repaint(layer, displ, displ.getBoundingBox(), extra);
1799 static public void repaint(final Layer layer, final Displayable displ, final Rectangle r, final int extra) {
1800 repaint(layer, displ, r, extra, true);
1803 /** Find the displays that show the given Layer, and repaint the given Displayable; does NOT update graphics for the offscreen image. */
1804 static public void repaint(final Layer layer, final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator) {
1805 repaint(layer, displ, r, extra, false, repaint_navigator);
1809 * @param layer The layer to repaint
1810 * @param r The Rectangle to repaint, in world coordinates (aka pixel coordinates or canvas coordinates).
1811 * @param extra The number of pixels to pad @param r with.
1812 * @param update_graphics Whether to recreate the offscreen image of the canvas, which is necessary for images.
1813 * @param repaint_navigator Whether to repaint the navigator.
1815 static public void repaint(final Layer layer, final Displayable displ, final Rectangle r, final int extra, final boolean update_graphics, final boolean repaint_navigator) {
1816 if (repaint_disabled) return;
1817 for (final Display d : al_displays) {
1818 if (layer == d.layer) {
1819 d.repaint(displ, r, extra, repaint_navigator, update_graphics);
1824 static public void repaint(final Displayable d) {
1825 if (d instanceof ZDisplayable) repaint(d.getLayerSet(), d, d.getBoundingBox(null), 5, true);
1826 repaint(d.getLayer(), d, d.getBoundingBox(null), 5, true);
1829 /** Repaint as much as the bounding box around the given Displayable, or the r if not null.
1830 * @param update_graphics will be made true if the @param displ is a Patch or it's not the active Displayable. */
1831 private void repaint(final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator, boolean update_graphics) {
1832 if (repaint_disabled || null == displ) return;
1833 update_graphics = (update_graphics || displ.getClass() == Patch.class || displ != active);
1834 if (null != r) canvas.repaint(r, extra, update_graphics);
1835 else canvas.repaint(displ, extra, update_graphics);
1836 if (repaint_navigator) {
1837 final DisplayablePanel dp = ht_panels.get(displ);
1838 if (null != dp) {
1839 Utils.invokeLater(new Runnable() { public void run() {
1840 dp.repaint(); // is null when creating it, or after deleting it
1841 }});
1843 navigator.repaint(true); // everything
1847 /** 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. */
1848 static public void repaintSnapshot(final Displayable displ) {
1849 for (final Display d : al_displays) {
1850 if (d.layer.contains(displ)) {
1851 if (!d.navigator.isPainted(displ)) {
1852 DisplayablePanel dp = d.ht_panels.get(displ);
1853 if (null != dp) dp.repaint(); // is null when creating it, or after deleting it
1854 d.navigator.repaint(displ);
1860 /** Repaint the given Rectangle in all Displays showing the layer, updating the offscreen image if any. */
1861 static public void repaint(final Layer layer, final Rectangle r, final int extra) {
1862 repaint(layer, extra, r, true, true);
1865 static public void repaint(final Layer layer, final int extra, final Rectangle r, final boolean update_navigator) {
1866 repaint(layer, extra, r, update_navigator, true);
1869 static public void repaint(final Layer layer, final int extra, final Rectangle r, final boolean update_navigator, final boolean update_graphics) {
1870 if (repaint_disabled) return;
1871 for (final Display d : al_displays) {
1872 if (layer == d.layer) {
1873 d.canvas.setUpdateGraphics(update_graphics);
1874 d.canvas.repaint(r, extra);
1875 if (update_navigator) {
1876 d.navigator.repaint(true);
1877 Utils.updateComponent(d.tabs.getSelectedComponent());
1884 /** Repaint the given Rectangle in all Displays showing the layer, optionally updating the offscreen image (if any). */
1885 static public void repaint(final Layer layer, final Rectangle r, final int extra, final boolean update_graphics) {
1886 if (repaint_disabled) return;
1887 for (final Display d : al_displays) {
1888 if (layer == d.layer) {
1889 d.canvas.setUpdateGraphics(update_graphics);
1890 d.canvas.repaint(r, extra);
1891 d.navigator.repaint(update_graphics);
1892 if (update_graphics) Utils.updateComponent(d.tabs.getSelectedComponent());
1897 /** Repaint the DisplayablePanel (and DisplayNavigator) only for the given Displayable, in all Displays showing the given Layer. */
1898 static public void repaint(final Layer layer, final Displayable displ) {
1899 if (repaint_disabled) return;
1900 for (final Display d : al_displays) {
1901 if (layer == d.layer) {
1902 DisplayablePanel dp = d.ht_panels.get(displ);
1903 if (null != dp) dp.repaint();
1904 d.navigator.repaint(true);
1909 static public void repaint(LayerSet set, Displayable displ, int extra) {
1910 repaint(set, displ, null, extra);
1913 static public void repaint(LayerSet set, Displayable displ, Rectangle r, int extra) {
1914 repaint(set, displ, r, extra, true);
1917 /** Repaint the Displayable in every Display that shows a Layer belonging to the given LayerSet. */
1918 static public void repaint(final LayerSet set, final Displayable displ, final Rectangle r, final int extra, final boolean repaint_navigator) {
1919 if (repaint_disabled) return;
1920 if (null == set) return;
1921 for (final Display d : al_displays) {
1922 if (d.layer.getParent() == set) {
1923 if (repaint_navigator) {
1924 if (null != displ) {
1925 DisplayablePanel dp = d.ht_panels.get(displ);
1926 if (null != dp) dp.repaint();
1928 d.navigator.repaint(true);
1930 if (null == displ || displ != d.active || displ instanceof ImageData) d.setUpdateGraphics(true); // safeguard
1931 // paint the given box or the actual Displayable's box
1932 if (null != r) d.canvas.repaint(r, extra);
1933 else d.canvas.repaint(displ, extra);
1938 /** Repaint the entire LayerSet, in all Displays showing a Layer of it.*/
1939 static public void repaint(final LayerSet set) {
1940 if (repaint_disabled) return;
1941 for (final Display d : al_displays) {
1942 if (d.layer.getParent() == set) {
1943 d.navigator.repaint(true);
1944 d.canvas.repaint(true);
1948 /** Repaint the given box in the LayerSet, in all Displays showing a Layer of it.*/
1949 static public void repaint(final LayerSet set, final Rectangle box) {
1950 if (repaint_disabled) return;
1951 for (final Display d : al_displays) {
1952 if (d.layer.getParent() == set) {
1953 d.navigator.repaint(box);
1954 d.canvas.repaint(box, 0, true);
1958 /** Repaint the entire Layer, in all Displays showing it, including the tabs.*/
1959 static public void repaint(final Layer layer) { // TODO this method overlaps with update(layer)
1960 if (repaint_disabled) return;
1961 for (final Display d : al_displays) {
1962 if (layer == d.layer) {
1963 d.navigator.repaint(true);
1964 d.canvas.repaint(true);
1969 /** Call repaint on all open Displays. */
1970 static public void repaint() {
1971 if (repaint_disabled) {
1972 Utils.logAll("Can't repaint -- repainting is disabled!");
1973 return;
1975 for (final Display d : al_displays) {
1976 d.navigator.repaint(true);
1977 d.canvas.repaint(true);
1981 static private boolean repaint_disabled = false;
1983 /** Set a flag to enable/disable repainting of all Display instances. */
1984 static protected void setRepaint(boolean b) {
1985 repaint_disabled = !b;
1988 public Rectangle getBounds() {
1989 return frame.getBounds();
1992 public Point getLocation() {
1993 return frame.getLocation();
1996 public JFrame getFrame() {
1997 return frame;
2000 /** Feel free to add more tabs. Don't remove any of the existing tabs or the sky will fall on your head. */
2001 public JTabbedPane getTabbedPane() {
2002 return tabs;
2005 /** Returns the tab index in this Display's JTabbedPane. */
2006 public int addTab(final String title, final Component comp) {
2007 this.tabs.add(title, comp);
2008 return this.tabs.getTabCount() -1;
2011 public void setLocation(Point p) {
2012 this.frame.setLocation(p);
2015 public Displayable getActive() {
2016 return active; //TODO this should return selection.active !!
2019 public void select(Displayable d) {
2020 select(d, false);
2023 /** Select/deselect accordingly to the current state and the shift key. */
2024 public void select(final Displayable d, final boolean shift_down) {
2025 if (null != active && active != d && active.getClass() != Patch.class) {
2026 // active is being deselected, so link underlying patches
2027 final String prop = active.getClass() == DLabel.class ? project.getProperty("label_nolinks")
2028 : project.getProperty("segmentations_nolinks");
2029 HashSet<Displayable> glinked = null;
2030 if (null != prop && prop.equals("true")) {
2031 // do nothing: linking disabled for active's type
2032 } else if (active.linkPatches()) {
2033 // Locking state changed:
2034 glinked = active.getLinkedGroup(null);
2035 Display.updateCheckboxes(glinked, DisplayablePanel.LOCK_STATE, true);
2037 // Update link icons:
2038 Display.updateCheckboxes(null == glinked ? active.getLinkedGroup(null) : glinked, DisplayablePanel.LINK_STATE);
2040 if (null == d) {
2041 //Utils.log2("Display.select: clearing selection");
2042 canvas.setUpdateGraphics(true);
2043 selection.clear();
2044 return;
2046 if (!shift_down) {
2047 //Utils.log2("Display.select: single selection");
2048 if (d != active) {
2049 selection.clear();
2050 selection.add(d);
2052 } else if (selection.contains(d)) {
2053 if (active == d) {
2054 selection.remove(d);
2055 //Utils.log2("Display.select: removing from a selection");
2056 } else {
2057 //Utils.log2("Display.select: activating within a selection");
2058 selection.setActive(d);
2060 } else {
2061 //Utils.log2("Display.select: adding to an existing selection");
2062 selection.add(d);
2064 // update the image shown to ImageJ
2065 // NO longer necessary, always he same FakeImagePlus // setTempCurrentImage();
2068 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p, final Class<?> c) {
2069 choose(screen_x_p, screen_y_p, x_p, y_p, false, c);
2071 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p) {
2072 choose(screen_x_p, screen_y_p, x_p, y_p, false, null);
2075 /** 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. */
2076 protected void choose(int screen_x_p, int screen_y_p, int x_p, int y_p, boolean shift_down, Class<?> c) {
2077 //Utils.log("Display.choose: x,y " + x_p + "," + y_p);
2078 final ArrayList<Displayable> al = new ArrayList<Displayable>(layer.find(x_p, y_p, true));
2079 al.addAll(layer.getParent().findZDisplayables(layer, x_p, y_p, true)); // only visible ones
2080 if (al.isEmpty()) {
2081 Displayable act = this.active;
2082 selection.clear();
2083 canvas.setUpdateGraphics(true);
2084 //Utils.log("choose: set active to null");
2085 // fixing lack of repainting for unknown reasons, of the active one TODO this is a temporary solution
2086 if (null != act) Display.repaint(layer, act, 5);
2087 } else if (1 == al.size()) {
2088 Displayable d = (Displayable)al.get(0);
2089 if (null != c && d.getClass() != c) {
2090 selection.clear();
2091 return;
2093 select(d, shift_down);
2094 //Utils.log("choose 1: set active to " + active);
2095 } else {
2096 if (al.contains(active) && !shift_down) {
2097 // do nothing
2098 } else {
2099 if (null != c) {
2100 // check if at least one of them is of class c
2101 // if only one is of class c, set as selected
2102 // else show menu
2103 for (Iterator<?> it = al.iterator(); it.hasNext(); ) {
2104 Object ob = it.next();
2105 if (ob.getClass() != c) it.remove();
2107 if (0 == al.size()) {
2108 // deselect
2109 selection.clear();
2110 return;
2112 if (1 == al.size()) {
2113 select((Displayable)al.get(0), shift_down);
2114 return;
2116 // else, choose among the many
2118 choose(screen_x_p, screen_y_p, al, shift_down, x_p, y_p);
2120 //Utils.log("choose many: set active to " + active);
2124 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) {
2125 // show a popup on the canvas to choose
2126 new Thread() {
2127 { setPriority(Thread.NORM_PRIORITY); }
2128 public void run() {
2129 final Object lock = new Object();
2130 final DisplayableChooser d_chooser = new DisplayableChooser(al, lock);
2131 final JPopupMenu pop = new JPopupMenu("Select:");
2132 for (final Displayable d : al) {
2133 JMenuItem menu_item = new JMenuItem(d.toString());
2134 menu_item.addActionListener(d_chooser);
2135 pop.add(menu_item);
2138 SwingUtilities.invokeLater(new Runnable() { public void run() {
2139 pop.show(canvas, screen_x_p, screen_y_p);
2140 }});
2142 //now wait until selecting something
2143 synchronized(lock) {
2144 do {
2145 try {
2146 lock.wait();
2147 } catch (InterruptedException ie) {}
2148 } while (d_chooser.isWaiting() && pop.isShowing());
2151 //grab the chosen Displayable object
2152 Displayable d = d_chooser.getChosen();
2153 //Utils.log("Chosen: " + d.toString());
2154 if (null == d) { Utils.log2("Display.choose: returning a null!"); }
2155 select(d, shift_down);
2156 pop.setVisible(false);
2158 // fix selection bug: never receives mouseReleased event when the popup shows
2159 getMode().mouseReleased(null, x_p, y_p, x_p, y_p, x_p, y_p);
2161 }.start();
2164 /** 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. */
2165 protected void setActive(final Displayable displ) {
2166 final Displayable prev_active = this.active;
2167 this.active = displ;
2168 Utils.invokeLater(new Runnable() { public void run() {
2169 if (null != displ && displ == prev_active && tabs.getSelectedComponent() != annot_panel) {
2170 // make sure the proper tab is selected.
2171 selectTab(displ);
2172 return; // the same
2174 // deactivate previously active
2175 if (null != prev_active) {
2176 // erase "decorations" of the previously active
2177 canvas.repaint(selection.getBox(), 4);
2178 // Adjust annotation doc
2179 synchronized (annot_docs) {
2180 boolean remove_doc = true;
2181 for (final Display d : al_displays) {
2182 if (prev_active == d.active) {
2183 remove_doc = false;
2184 break;
2187 if (remove_doc) annot_docs.remove(prev_active);
2190 // activate the new active
2191 if (null != displ) {
2192 updateInDatabase("active_displayable_id");
2193 if (displ.getClass() != Patch.class) project.select(displ); // select the node in the corresponding tree, if any.
2194 // select the proper tab, and scroll to visible
2195 if (tabs.getSelectedComponent() != annot_panel) { // don't swap tab if its the annotation one
2196 selectTab(displ);
2198 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
2199 repaint(displ, null, 5, false, update_graphics); // to show the border, and to repaint out of the background image
2200 transp_slider.setValue((int)(displ.getAlpha() * 100));
2201 // Adjust annotation tab:
2202 synchronized (annot_docs) {
2203 annot_label.setText(displ.toString());
2204 Document doc = annot_docs.get(displ); // could be open in another Display
2205 if (null == doc) {
2206 doc = annot_editor.getEditorKit().createDefaultDocument();
2207 doc.addDocumentListener(new DocumentListener() {
2208 public void changedUpdate(DocumentEvent e) {}
2209 public void insertUpdate(DocumentEvent e) { push(); }
2210 public void removeUpdate(DocumentEvent e) { push(); }
2211 private void push() {
2212 displ.setAnnotation(annot_editor.getText());
2215 annot_docs.put(displ, doc);
2217 annot_editor.setDocument(doc);
2218 if (null != displ.getAnnotation()) annot_editor.setText(displ.getAnnotation());
2220 annot_editor.setEnabled(true);
2221 } else {
2222 //ensure decorations are removed from the panels, for Displayables in a selection besides the active one
2223 Utils.updateComponent(tabs.getSelectedComponent());
2224 annot_label.setText("(No selected object)");
2225 annot_editor.setDocument(annot_editor.getEditorKit().createDefaultDocument()); // a clear, empty one
2226 annot_editor.setEnabled(false);
2228 }});
2231 /** If the other paints under the base. */
2232 public boolean paintsBelow(Displayable base, Displayable other) {
2233 boolean zd_base = base instanceof ZDisplayable;
2234 boolean zd_other = other instanceof ZDisplayable;
2235 if (zd_other) {
2236 if (base instanceof DLabel) return true; // zd paints under label
2237 if (!zd_base) return false; // any zd paints over a mere displ if not a label
2238 else {
2239 // both zd, compare indices
2240 ArrayList<ZDisplayable> al = other.getLayerSet().getZDisplayables();
2241 return al.indexOf(base) > al.indexOf(other);
2243 } else {
2244 if (!zd_base) {
2245 // both displ, compare indices
2246 ArrayList<Displayable> al = other.getLayer().getDisplayables();
2247 return al.indexOf(base) > al.indexOf(other);
2248 } else {
2249 // base is zd, other is d
2250 if (other instanceof DLabel) return false;
2251 return true;
2256 /** 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. */
2257 private void selectTab(final Displayable displ) {
2258 Method method = null;
2259 try {
2260 if (!(displ instanceof LayerSet)) {
2261 method = Display.class.getDeclaredMethod("selectTab", new Class[]{displ.getClass()});
2263 } catch (Exception e) {
2264 IJError.print(e);
2266 if (null != method) {
2267 final Method me = method;
2268 dispatcher.exec(new Runnable() { public void run() {
2269 try {
2270 me.setAccessible(true);
2271 me.invoke(Display.this, new Object[]{displ});
2272 } catch (Exception e) { IJError.print(e); }
2273 }});
2277 // Methods used by reflection:
2279 @SuppressWarnings("unused")
2280 private void selectTab(Patch patch) {
2281 tabs.setSelectedComponent(panel_patches);
2282 panel_patches.scrollToShow(patch);
2285 @SuppressWarnings("unused")
2286 private void selectTab(Profile profile) {
2287 tabs.setSelectedComponent(panel_profiles);
2288 panel_profiles.scrollToShow(profile);
2291 @SuppressWarnings("unused")
2292 private void selectTab(DLabel label) {
2293 tabs.setSelectedComponent(panel_labels);
2294 panel_labels.scrollToShow(label);
2297 private void selectTab(ZDisplayable zd) {
2298 tabs.setSelectedComponent(panel_zdispl);
2299 panel_zdispl.scrollToShow(zd);
2302 @SuppressWarnings("unused")
2303 private void selectTab(Pipe d) { selectTab((ZDisplayable)d); }
2304 @SuppressWarnings("unused")
2305 private void selectTab(Polyline d) { selectTab((ZDisplayable)d); }
2306 @SuppressWarnings("unused")
2307 private void selectTab(Treeline d) { selectTab((ZDisplayable)d); }
2308 @SuppressWarnings("unused")
2309 private void selectTab(AreaTree d) { selectTab((ZDisplayable)d); }
2310 @SuppressWarnings("unused")
2311 private void selectTab(Connector d) { selectTab((ZDisplayable)d); }
2312 @SuppressWarnings("unused")
2313 private void selectTab(AreaList d) { selectTab((ZDisplayable)d); }
2314 @SuppressWarnings("unused")
2315 private void selectTab(Ball d) { selectTab((ZDisplayable)d); }
2316 @SuppressWarnings("unused")
2317 private void selectTab(Dissector d) { selectTab((ZDisplayable)d); }
2318 @SuppressWarnings("unused")
2319 private void selectTab(Stack d) { selectTab((ZDisplayable)d); }
2321 /** Must be invoked in the event dispatch thread. */
2322 private void updateTab(Container tab) {
2323 if (tab instanceof RollingPanel) {
2324 ((RollingPanel)tab).updateList();
2328 static public void setActive(final Object event, final Displayable displ) {
2329 if (!(event instanceof InputEvent)) return;
2330 // find which Display
2331 for (final Display d : al_displays) {
2332 if (d.isOrigin((InputEvent)event)) {
2333 d.setActive(displ);
2334 break;
2339 /** Find out whether this Display is Transforming its active Displayable. */
2340 public boolean isTransforming() {
2341 return canvas.isTransforming();
2344 /** Find whether any Display is transforming the given Displayable. */
2345 static public boolean isTransforming(final Displayable displ) {
2346 for (final Display d : al_displays) {
2347 if (null != d.active && d.active == displ && d.canvas.isTransforming()) return true;
2349 return false;
2352 /** Check whether the source of the event is located in this instance.*/
2353 private boolean isOrigin(InputEvent event) {
2354 Object source = event.getSource();
2355 // find it ... check the canvas for now TODO
2356 if (canvas == source) {
2357 return true;
2359 return false;
2362 /** Get the layer of the front Display, or null if none.*/
2363 static public Layer getFrontLayer() {
2364 Display d = front;
2365 if (null == d) return null;
2366 return d.layer;
2369 /** Get the layer of an open Display of the given Project, or null if none.*/
2370 static public Layer getFrontLayer(final Project project) {
2371 Display df = front;
2372 if (null == df) return null;
2373 if (df.project == project) return df.layer;
2375 // else, find an open Display for the given Project, if any
2376 for (final Display d : al_displays) {
2377 if (d.project == project) {
2378 d.frame.toFront();
2379 return d.layer;
2382 return null; // none found
2385 /** Get a pointer to a Display for @param project, or null if none. */
2386 static public Display getFront(final Project project) {
2387 Display df = front;
2388 if (null == df) return null;
2389 if (df.project == project) return df;
2390 for (final Display d : al_displays) {
2391 if (d.project == project) {
2392 d.frame.toFront();
2393 return d;
2396 return null;
2399 /** Return the list of selected Displayable objects of the front Display, or an emtpy list if no Display or none selected. */
2400 static public List<Displayable> getSelected() {
2401 Display d = front;
2402 if (null == d) return new ArrayList<Displayable>();
2403 return d.selection.getSelected();
2405 /** 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. */
2406 static public List<? extends Displayable> getSelected(final Class<? extends Displayable> c) {
2407 Display d = front;
2408 if (null == d) return new ArrayList<Displayable>();
2409 return d.selection.getSelected(c);
2412 public boolean isReadOnly() {
2413 // TEMPORARY: in the future one will be able show displays as read-only to other people, remotely
2414 return false;
2417 protected void showPopup(Component c, int x, int y) {
2418 Display d = front;
2419 if (null == d) return;
2420 d.getPopupMenu().show(c, x, y);
2423 /** Return a context-sensitive popup menu. */
2424 protected JPopupMenu getPopupMenu() { // called from canvas
2425 // get the job canceling dialog
2426 if (!canvas.isInputEnabled()) {
2427 return project.getLoader().getJobsPopup(this);
2430 // create new
2431 this.popup = new JPopupMenu();
2432 JMenuItem item = null;
2433 JMenu menu = null;
2435 if (mode instanceof InspectPatchTrianglesMode) {
2436 item = new JMenuItem("Exit inspection"); item.addActionListener(this); popup.add(item);
2437 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2438 return popup;
2439 } else if (canvas.isTransforming()) {
2440 item = new JMenuItem("Apply transform"); item.addActionListener(this); popup.add(item);
2441 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
2442 item = new JMenuItem("Apply transform propagating to last layer"); item.addActionListener(this); popup.add(item);
2443 if (layer.getParent().indexOf(layer) == layer.getParent().size() -1) item.setEnabled(false);
2444 if (!(getMode().getClass() == AffineTransformMode.class || getMode().getClass() == NonLinearTransformMode.class)) item.setEnabled(false);
2445 item = new JMenuItem("Apply transform propagating to first layer"); item.addActionListener(this); popup.add(item);
2446 if (0 == layer.getParent().indexOf(layer)) item.setEnabled(false);
2447 if (!(getMode().getClass() == AffineTransformMode.class || getMode().getClass() == NonLinearTransformMode.class)) item.setEnabled(false);
2448 item = new JMenuItem("Cancel transform"); item.addActionListener(this); popup.add(item);
2449 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
2450 item = new JMenuItem("Specify transform..."); item.addActionListener(this); popup.add(item);
2451 if (getMode().getClass() != AffineTransformMode.class) item.setEnabled(false);
2452 if (getMode().getClass() == ManualAlignMode.class) {
2453 final JMenuItem lexport = new JMenuItem("Export landmarks"); popup.add(lexport);
2454 final JMenuItem limport = new JMenuItem("Import landmarks"); popup.add(limport);
2455 ActionListener a = new ActionListener() {
2456 public void actionPerformed(ActionEvent ae) {
2457 ManualAlignMode mam = (ManualAlignMode)getMode();
2458 Object source = ae.getSource();
2459 if (lexport == source) {
2460 mam.exportLandmarks();
2461 } else if (limport == source) {
2462 mam.importLandmarks();
2466 lexport.addActionListener(a);
2467 limport.addActionListener(a);
2469 return popup;
2472 final Class<?> aclass = null == active ? null : active.getClass();
2474 if (null != active) {
2475 if (Profile.class == aclass) {
2476 item = new JMenuItem("Duplicate, link and send to next layer"); item.addActionListener(this); popup.add(item);
2477 Layer nl = layer.getParent().next(layer);
2478 if (nl == layer) item.setEnabled(false);
2479 item = new JMenuItem("Duplicate, link and send to previous layer"); item.addActionListener(this); popup.add(item);
2480 nl = layer.getParent().previous(layer);
2481 if (nl == layer) item.setEnabled(false);
2483 menu = new JMenu("Duplicate, link and send to");
2484 int i = 1;
2485 for (final Layer la : layer.getParent().getLayers()) {
2486 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
2487 if (la == this.layer) item.setEnabled(false);
2488 i++;
2490 popup.add(menu);
2491 item = new JMenuItem("Duplicate, link and send to..."); item.addActionListener(this); popup.add(item);
2493 popup.addSeparator();
2495 item = new JMenuItem("Unlink from images"); item.addActionListener(this); popup.add(item);
2496 if (!active.isLinked()) item.setEnabled(false); // isLinked() checks if it's linked to a Patch in its own layer
2497 item = new JMenuItem("Show in 3D"); item.addActionListener(this); popup.add(item);
2498 popup.addSeparator();
2499 } else if (Patch.class == aclass) {
2500 JMenu m = new JMenu("Patch");
2501 item = new JMenuItem("Fill ROI in alpha mask"); item.addActionListener(this); m.add(item);
2502 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, 0));
2503 item.setEnabled(null != getRoi());
2504 item = new JMenuItem("Fill inverse ROI in alpha mask"); item.addActionListener(this); m.add(item);
2505 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, Event.SHIFT_MASK));
2506 item.setEnabled(null != getRoi());
2507 item = new JMenuItem("Remove alpha mask"); item.addActionListener(this); m.add(item);
2508 if ( ! ((Patch)active).hasAlphaMask()) item.setEnabled(false);
2509 item = new JMenuItem("Unlink from images"); item.addActionListener(this); m.add(item);
2510 if (!active.isLinked(Patch.class)) item.setEnabled(false);
2511 if (((Patch)active).isStack()) {
2512 item = new JMenuItem("Unlink slices"); item.addActionListener(this); m.add(item);
2514 int n_sel_patches = selection.getSelected(Patch.class).size();
2515 item = new JMenuItem("Snap"); item.addActionListener(this); m.add(item);
2516 item.setEnabled(1 == n_sel_patches);
2517 item = new JMenuItem("Montage"); item.addActionListener(this); m.add(item);
2518 item.setEnabled(n_sel_patches > 1);
2519 item = new JMenuItem("Lens correction"); item.addActionListener(this); m.add(item);
2520 item.setEnabled(n_sel_patches > 1);
2521 item = new JMenuItem("Blend"); item.addActionListener(this); m.add(item);
2522 item.setEnabled(n_sel_patches > 1);
2523 item = new JMenuItem("Open image"); item.addActionListener(new ActionListener() {
2524 @Override
2525 public void actionPerformed(ActionEvent e) {
2526 for (final Patch p : selection.get(Patch.class)) {
2527 p.getImagePlus().show();
2530 }); m.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_D, KeyEvent.SHIFT_MASK, true));
2531 item = new JMenuItem("Open original image"); item.addActionListener(new ActionListener() {
2532 @Override
2533 public void actionPerformed(ActionEvent e) {
2534 for (final Patch p : selection.get(Patch.class)) {
2535 p.getProject().getLoader().releaseToFit(p.getOWidth(), p.getOHeight(), p.getType(), 5);
2536 p.getProject().getLoader().openImagePlus(p.getImageFilePath()).show();
2540 item = new JMenuItem("View volume"); item.addActionListener(this); m.add(item);
2541 HashSet<Displayable> hs = active.getLinked(Patch.class);
2542 if (null == hs || 0 == hs.size()) item.setEnabled(false);
2543 item = new JMenuItem("View orthoslices"); item.addActionListener(this); m.add(item);
2544 if (null == hs || 0 == hs.size()) item.setEnabled(false); // if no Patch instances among the directly linked, then it's not a stack
2545 popup.add(m);
2546 popup.addSeparator();
2547 } else {
2548 item = new JMenuItem("Unlink"); item.addActionListener(this); popup.add(item);
2549 item = new JMenuItem("Show in 3D"); item.addActionListener(this); popup.add(item);
2550 popup.addSeparator();
2553 if (AreaList.class == aclass) {
2554 item = new JMenuItem("Merge"); item.addActionListener(this); popup.add(item);
2555 ArrayList<?> al = selection.getSelected();
2556 int n = 0;
2557 for (Iterator<?> it = al.iterator(); it.hasNext(); ) {
2558 if (it.next().getClass() == AreaList.class) n++;
2560 if (n < 2) item.setEnabled(false);
2561 addAreaListAreasMenu(popup, active);
2562 popup.addSeparator();
2563 } else if (Pipe.class == aclass) {
2564 item = new JMenuItem("Reverse point order"); item.addActionListener(this); popup.add(item);
2565 popup.addSeparator();
2566 } else if (Treeline.class == aclass || AreaTree.class == aclass) {
2567 if (AreaTree.class == aclass) addAreaTreeAreasMenu(popup, (AreaTree)active);
2568 item = new JMenuItem("Reroot"); item.addActionListener(this); popup.add(item);
2569 item = new JMenuItem("Part subtree"); item.addActionListener(this); popup.add(item);
2570 item = new JMenuItem("Join"); item.addActionListener(this); popup.add(item);
2571 item = new JMenuItem("Show tabular view"); item.addActionListener(this); popup.add(item);
2572 final Collection<Tree> trees = selection.get(Tree.class);
2575 JMenu nodeMenu = new JMenu("Nodes");
2576 item = new JMenuItem("Mark"); item.addActionListener(this); nodeMenu.add(item);
2577 item = new JMenuItem("Clear marks (selected Trees)"); item.addActionListener(this); nodeMenu.add(item);
2578 final JMenuItem nodeColor = new JMenuItem("Color..."); nodeMenu.add(nodeColor);
2579 nodeColor.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, KeyEvent.SHIFT_MASK, true));
2580 final JMenuItem nodePairColor = new JMenuItem("Color path between two nodes tagged as..."); nodeMenu.add(nodePairColor);
2581 final JMenuItem nodeRadius = active instanceof Treeline ? new JMenuItem("Radius...") : null;
2582 if (null != nodeRadius) {
2583 nodeMenu.add(nodeRadius);
2584 nodeRadius.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_O, 0, true));
2586 final JMenuItem removeAllTags = new JMenuItem("Drop all tags (selected trees)"); nodeMenu.add(removeAllTags);
2587 final JMenuItem removeTag = new JMenuItem("Drop all occurrences of tag..."); nodeMenu.add(removeTag);
2588 final JMenuItem colorizeByNodeCentrality = new JMenuItem("Colorize by node betweenness centrality"); nodeMenu.add(colorizeByNodeCentrality);
2589 final JMenuItem colorizeByBranchCentrality = new JMenuItem("Colorize by branch betweenness centrality"); nodeMenu.add(colorizeByBranchCentrality);
2591 popup.add(nodeMenu);
2592 final ActionListener ln = new ActionListener() {
2593 @Override
2594 public void actionPerformed(ActionEvent ae) {
2595 if (null == active) {
2596 Utils.showMessage("No tree selected!");
2597 return;
2599 if (!(active instanceof Tree)) {
2600 Utils.showMessage("The selected object is not a Tree!");
2601 return;
2603 final Tree tree = (Tree)active;
2604 final Object src = ae.getSource();
2606 if (src == nodeColor) {
2607 Node nd = tree.getLastVisited();
2608 if (null == nd) {
2609 Utils.showMessage("Select a node first by clicking on it\nor moving the mouse over it and pushing 'g'.");
2610 return;
2612 tree.adjustNodeColors(nd); // sets an undo step
2613 } else if (src == nodePairColor) {
2614 final TreeMap<String,Tag> sm = getTags(tree);
2615 if (null == sm) return;
2616 if (1 == sm.size()) {
2617 Utils.showMessage("Need at least two different tags in the tree!");
2618 return;
2620 Color color = tree.getColor();
2621 GenericDialog gd = new GenericDialog("Node colors");
2622 gd.addSlider("Red: ", 0, 255, color.getRed());
2623 gd.addSlider("Green: ", 0, 255, color.getGreen());
2624 gd.addSlider("Blue: ", 0, 255, color.getBlue());
2625 final String[] stags = asStrings(sm);
2626 sm.keySet().toArray(stags);
2627 gd.addChoice("Upstream tag:", stags, stags[0]);
2628 gd.addChoice("Downstream tag:", stags, stags[1]);
2629 gd.showDialog();
2630 if (gd.wasCanceled()) return;
2631 final Color newColor = new Color((int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
2632 final Tag upstreamTag = sm.get(gd.getNextChoice());
2633 final Tag downstreamTag = sm.get(gd.getNextChoice());
2634 final List<Tree<?>.NodePath> pairs = tree.findTaggedPairs(upstreamTag, downstreamTag);
2635 if (null == pairs || pairs.isEmpty()) {
2636 Utils.showMessage("No pairs found for '" + upstreamTag + "' and '" + downstreamTag + "'");
2637 return;
2639 getLayerSet().addDataEditStep(tree);
2640 for (final Tree<?>.NodePath pair : pairs) {
2641 for (final Node<?> nd : pair.path) {
2642 nd.setColor(newColor);
2645 getLayerSet().addDataEditStep(tree);
2646 Display.repaint();
2647 } else if (src == nodeRadius) {
2648 if (!(tree instanceof Treeline)) return;
2649 Node nd = tree.getLastVisited();
2650 if (null == nd) {
2651 Utils.showMessage("Select a node first by clicking on it\nor moving the mouse over it and pushing 'g'.");
2652 return;
2654 ((Treeline)tree).askAdjustRadius(nd); // sets an undo step
2655 } else if (src == removeAllTags) {
2656 if (!Utils.check("Really remove all tags from all selected trees?")) return;
2657 List<Tree> sel = selection.get(Tree.class);
2658 getLayerSet().addDataEditStep(new HashSet<Displayable>(sel));
2659 try {
2660 for (Tree t : sel) {
2661 t.dropAllTags();
2663 getLayerSet().addDataEditStep(new HashSet<Displayable>(sel)); // current state
2664 } catch (Exception e) {
2665 getLayerSet().undoOneStep();
2666 IJError.print(e);
2668 Display.repaint();
2669 } else if (src == removeTag) {
2670 TreeMap<String,Tag> tags = getTags(tree);
2671 String[] ts = asStrings(tags);
2672 GenericDialog gd = new GenericDialog("Remove tags");
2673 gd.addChoice("Tag:", ts, ts[0]);
2674 String[] c = new String[]{"Active tree", "All selected trees and connectors", "All trees and connectors"};
2675 gd.addChoice("From: ", c, c[0]);
2676 gd.showDialog();
2677 if (gd.wasCanceled()) return;
2678 HashSet<Displayable> ds = new HashSet<Displayable>();
2679 final Tag tag = tags.get(gd.getNextChoice());
2680 switch (gd.getNextChoiceIndex()) {
2681 case 0: ds.add(tree); break;
2682 case 1: ds.addAll(selection.get(Tree.class));
2683 case 2: ds.addAll(getLayerSet().getZDisplayables(Tree.class, true));
2685 getLayerSet().addDataEditStep(ds);
2686 try {
2687 for (Displayable d : ds) {
2688 Tree t = (Tree)d;
2689 t.removeTag(tag);
2691 getLayerSet().addDataEditStep(ds);
2692 } catch (Exception e) {
2693 getLayerSet().undoOneStep();
2694 IJError.print(e);
2696 Display.repaint();
2697 } else if (src == colorizeByNodeCentrality) {
2698 List<Tree> ts = selection.get(Tree.class);
2699 HashSet<Tree> ds = new HashSet<Tree>(ts);
2700 getLayerSet().addDataEditStep(ds);
2701 try {
2702 for (Tree t : ts) {
2703 t.colorizeByNodeBetweennessCentrality();
2705 getLayerSet().addDataEditStep(ds);
2706 Display.repaint();
2707 } catch (Exception e) {
2708 getLayerSet().undoOneStep();
2709 IJError.print(e);
2711 } else if (src == colorizeByBranchCentrality) {
2712 List<Tree> ts = selection.get(Tree.class);
2713 HashSet<Tree> ds = new HashSet<Tree>(ts);
2714 getLayerSet().addDataEditStep(ds);
2715 try {
2716 for (Tree t : ts) {
2717 t.colorizeByBranchBetweennessCentrality(2);
2719 getLayerSet().addDataEditStep(ds);
2720 Display.repaint();
2721 } catch (Exception e) {
2722 getLayerSet().undoOneStep();
2723 IJError.print(e);
2728 for (JMenuItem a : new JMenuItem[]{nodeColor, nodePairColor, nodeRadius,
2729 removeAllTags, removeTag, colorizeByNodeCentrality, colorizeByBranchCentrality}) {
2730 if (null == a) continue;
2731 a.addActionListener(ln);
2735 JMenu review = new JMenu("Review");
2736 final JMenuItem tgenerate = new JMenuItem("Generate review stacks (selected Trees)"); review.add(tgenerate);
2737 tgenerate.setEnabled(trees.size() > 0);
2738 final JMenuItem tslab = new JMenuItem("Generate review stack for current slab"); review.add(tslab);
2739 final JMenuItem tsubtree = new JMenuItem("Generate review stacks for subtree"); review.add(tsubtree);
2740 final JMenuItem tremove = new JMenuItem("Remove reviews (selected Trees)"); review.add(tremove);
2741 tremove.setEnabled(trees.size() > 0);
2742 final JMenuItem tconnectors = new JMenuItem("View table of outgoing/incoming connectors"); review.add(tconnectors);
2743 ActionListener l = new ActionListener() {
2744 public void actionPerformed(final ActionEvent ae) {
2745 if (!Utils.check("Really " + ae.getActionCommand())) {
2746 return;
2748 dispatcher.exec(new Runnable() {
2749 public void run() {
2750 int count = 0;
2751 for (final Tree<?> t : trees) {
2752 Utils.log("Processing " + (++count) + "/" + trees.size());
2753 Bureaucrat bu = null;
2754 if (ae.getSource() == tgenerate) bu = t.generateAllReviewStacks();
2755 else if (ae.getSource() == tremove) bu = t.removeReviews();
2756 else if (ae.getSource() == tslab) {
2757 Point po = canvas.consumeLastPopupPoint();
2758 Utils.log2(po, layer, 1.0);
2759 bu = t.generateReviewStackForSlab(po.x, po.y, Display.this.layer, 1.0);
2760 } else if (ae.getSource() == tsubtree) {
2761 Point po = canvas.consumeLastPopupPoint();
2762 bu = t.generateSubtreeReviewStacks(po.x, po.y, Display.this.layer, 1.0);
2764 if (null != bu) try {
2765 bu.getWorker().join();
2766 } catch (InterruptedException ie) { return; }
2772 for (JMenuItem c : new JMenuItem[]{tgenerate, tslab, tsubtree, tremove}) c.addActionListener(l);
2773 tconnectors.addActionListener(new ActionListener() {
2774 public void actionPerformed(ActionEvent ae) {
2775 for (final Tree<?> t : trees) TreeConnectorsView.create(t);
2778 popup.add(review);
2780 JMenu go = new JMenu("Go");
2781 item = new JMenuItem("Previous branch node or start"); item.addActionListener(this); go.add(item);
2782 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_B, 0, true));
2783 item = new JMenuItem("Next branch node or end"); item.addActionListener(this); go.add(item);
2784 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_N, 0, true));
2785 item = new JMenuItem("Root"); item.addActionListener(this); go.add(item);
2786 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_R, 0, true));
2787 go.addSeparator();
2788 item = new JMenuItem("Last added node"); item.addActionListener(this); go.add(item);
2789 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_L, 0, true));
2790 item = new JMenuItem("Last edited node"); item.addActionListener(this); go.add(item);
2791 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_E, 0, true));
2792 popup.add(go);
2794 JMenu tmeasure = new JMenu("Measure");
2795 JMenuItem dist_to_root = new JMenuItem("Distance from this node to root"); tmeasure.add(dist_to_root);
2796 JMenuItem dist_to_tag = new JMenuItem("Distance from this node to all nodes tagged as..."); tmeasure.add(dist_to_tag);
2797 JMenuItem dist_to_mark = new JMenuItem("Distance from this node to the marked node"); tmeasure.add(dist_to_mark);
2798 JMenuItem dist_pairs = new JMenuItem("Shortest distances between all pairs of nodes tagged as..."); tmeasure.add(dist_pairs);
2799 final ActionListener tma = getTreePathMeasureListener((Tree<?>)active);
2800 for (JMenuItem mi : new JMenuItem[]{dist_to_root, dist_to_tag, dist_to_mark, dist_pairs}) {
2801 mi.addActionListener(tma);
2803 popup.add(tmeasure);
2805 final String[] name = new String[]{AreaTree.class.getSimpleName(), Treeline.class.getSimpleName()};
2806 if (Treeline.class == aclass) {
2807 String a = name[0];
2808 name[0] = name[1];
2809 name[1] = a;
2811 item = new JMenuItem("Duplicate " + name[0] + " as " + name[1]);
2812 item.addActionListener(new ActionListener() {
2813 public void actionPerformed(ActionEvent e) {
2814 Bureaucrat.createAndStart(new Worker.Task("Converting") {
2815 public void exec() {
2816 try {
2817 getLayerSet().addChangeTreesStep();
2818 Map<Tree<?>,Tree<?>> m = Tree.duplicateAs(selection.getSelected(), Treeline.class == aclass ? AreaTree.class : Treeline.class);
2819 if (m.isEmpty()) {
2820 getLayerSet().removeLastUndoStep();
2821 } else {
2822 getLayerSet().addChangeTreesStep();
2824 } catch (Exception e) {
2825 IJError.print(e);
2828 }, getProject());
2831 popup.add(item);
2832 popup.addSeparator();
2833 } else if (Connector.class == aclass) {
2834 item = new JMenuItem("Merge"); item.addActionListener(new ActionListener() {
2835 public void actionPerformed(ActionEvent ae) {
2836 if (null == getActive() || getActive().getClass() != Connector.class) {
2837 Utils.log("Active object must be a Connector!");
2838 return;
2840 final List<Connector> col = selection.get(Connector.class);
2841 if (col.size() < 2) {
2842 Utils.log("Select more than one Connector!");
2843 return;
2845 if (col.get(0) != getActive()) {
2846 if (col.remove(getActive())) {
2847 col.add(0, (Connector)getActive());
2848 } else {
2849 Utils.log("ERROR: cannot find active object in selection list!");
2850 return;
2853 Bureaucrat.createAndStart(new Worker.Task("Merging connectors") {
2854 public void exec() {
2855 getLayerSet().addChangeTreesStep();
2856 Connector base = null;
2857 try {
2858 base = Connector.merge(col);
2859 } catch (Exception e) {
2860 IJError.print(e);
2862 if (null == base) {
2863 Utils.log("ERROR: could not merge connectors!");
2864 getLayerSet().undoOneStep();
2865 } else {
2866 getLayerSet().addChangeTreesStep();
2868 Display.repaint();
2870 }, getProject());
2873 popup.add(item);
2874 item.setEnabled(selection.getSelected(Connector.class).size() > 1);
2875 popup.addSeparator();
2878 item = new JMenuItem("Duplicate"); item.addActionListener(this); popup.add(item);
2879 item = new JMenuItem("Color..."); item.addActionListener(this); popup.add(item);
2880 if (active instanceof LayerSet) item.setEnabled(false);
2881 if (active.isLocked()) {
2882 item = new JMenuItem("Unlock"); item.addActionListener(this); popup.add(item);
2883 } else {
2884 item = new JMenuItem("Lock"); item.addActionListener(this); popup.add(item);
2886 menu = new JMenu("Move");
2887 popup.addSeparator();
2888 LayerSet ls = layer.getParent();
2889 item = new JMenuItem("Move to top"); item.addActionListener(this); menu.add(item);
2890 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.
2891 if (ls.isTop(active)) item.setEnabled(false);
2892 item = new JMenuItem("Move up"); item.addActionListener(this); menu.add(item);
2893 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0, true));
2894 if (ls.isTop(active)) item.setEnabled(false);
2895 item = new JMenuItem("Move down"); item.addActionListener(this); menu.add(item);
2896 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0, true));
2897 if (ls.isBottom(active)) item.setEnabled(false);
2898 item = new JMenuItem("Move to bottom"); item.addActionListener(this); menu.add(item);
2899 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_END, 0, true));
2900 if (ls.isBottom(active)) item.setEnabled(false);
2902 popup.add(menu);
2903 popup.addSeparator();
2904 item = new JMenuItem("Delete..."); item.addActionListener(this); popup.add(item);
2905 try {
2906 if (Patch.class == aclass) {
2907 if (!active.isOnlyLinkedTo(Patch.class)) {
2908 item.setEnabled(false);
2911 } catch (Exception e) { IJError.print(e); item.setEnabled(false); }
2913 if (Patch.class == aclass) {
2914 item = new JMenuItem("Revert"); item.addActionListener(this); popup.add(item);
2915 if ( null == ((Patch)active).getOriginalPath()) item.setEnabled(false);
2916 popup.addSeparator();
2918 item = new JMenuItem("Properties..."); item.addActionListener(this); popup.add(item);
2919 item = new JMenuItem("Show centered"); item.addActionListener(this); popup.add(item);
2921 popup.addSeparator();
2923 if (! (active instanceof ZDisplayable)) {
2924 int i_layer = layer.getParent().indexOf(layer);
2925 int n_layers = layer.getParent().size();
2926 item = new JMenuItem("Send to previous layer"); item.addActionListener(this); popup.add(item);
2927 if (1 == n_layers || 0 == i_layer || active.isLinked()) item.setEnabled(false);
2928 // 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
2929 else if (active instanceof Profile && !active.canSendTo(layer.getParent().previous(layer))) item.setEnabled(false);
2930 item = new JMenuItem("Send to next layer"); item.addActionListener(this); popup.add(item);
2931 if (1 == n_layers || n_layers -1 == i_layer || active.isLinked()) item.setEnabled(false);
2932 else if (active instanceof Profile && !active.canSendTo(layer.getParent().next(layer))) item.setEnabled(false);
2935 menu = new JMenu("Send linked group to...");
2936 if (active.hasLinkedGroupWithinLayer(this.layer)) {
2937 int i = 1;
2938 for (final Layer la : ls.getLayers()) {
2939 String layer_title = i + ": " + la.getTitle();
2940 if (-1 == layer_title.indexOf(' ')) layer_title += " ";
2941 item = new JMenuItem(layer_title); item.addActionListener(this); menu.add(item);
2942 if (la == this.layer) item.setEnabled(false);
2943 i++;
2945 popup.add(menu);
2946 } else {
2947 menu.setEnabled(false);
2948 //Utils.log("Active's linked group not within layer.");
2950 popup.add(menu);
2951 popup.addSeparator();
2955 item = new JMenuItem("Undo");item.addActionListener(this); popup.add(item);
2956 if (!layer.getParent().canUndo() || canvas.isTransforming()) item.setEnabled(false);
2957 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Utils.getControlModifier(), true));
2958 item = new JMenuItem("Redo");item.addActionListener(this); popup.add(item);
2959 if (!layer.getParent().canRedo() || canvas.isTransforming()) item.setEnabled(false);
2960 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_Z, Event.SHIFT_MASK | Utils.getControlModifier(), true));
2961 popup.addSeparator();
2963 // Would get so much simpler with a clojure macro ...
2965 try {
2966 menu = new JMenu("Hide/Unhide");
2967 item = new JMenuItem("Hide deselected"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.SHIFT_MASK, true));
2968 boolean none = 0 == selection.getNSelected();
2969 if (none) item.setEnabled(false);
2970 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));
2971 if (none) item.setEnabled(false);
2972 item = new JMenuItem("Hide selected"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, 0, true));
2973 if (none) item.setEnabled(false);
2974 none = ! layer.getParent().containsDisplayable(DLabel.class);
2975 item = new JMenuItem("Hide all labels"); item.addActionListener(this); menu.add(item);
2976 if (none) item.setEnabled(false);
2977 item = new JMenuItem("Unhide all labels"); item.addActionListener(this); menu.add(item);
2978 if (none) item.setEnabled(false);
2979 none = ! layer.getParent().contains(AreaList.class);
2980 item = new JMenuItem("Hide all arealists"); item.addActionListener(this); menu.add(item);
2981 if (none) item.setEnabled(false);
2982 item = new JMenuItem("Unhide all arealists"); item.addActionListener(this); menu.add(item);
2983 if (none) item.setEnabled(false);
2984 none = ! layer.contains(Profile.class);
2985 item = new JMenuItem("Hide all profiles"); item.addActionListener(this); menu.add(item);
2986 if (none) item.setEnabled(false);
2987 item = new JMenuItem("Unhide all profiles"); item.addActionListener(this); menu.add(item);
2988 if (none) item.setEnabled(false);
2989 none = ! layer.getParent().contains(Pipe.class);
2990 item = new JMenuItem("Hide all pipes"); item.addActionListener(this); menu.add(item);
2991 if (none) item.setEnabled(false);
2992 item = new JMenuItem("Unhide all pipes"); item.addActionListener(this); menu.add(item);
2993 if (none) item.setEnabled(false);
2994 none = ! layer.getParent().contains(Polyline.class);
2995 item = new JMenuItem("Hide all polylines"); item.addActionListener(this); menu.add(item);
2996 if (none) item.setEnabled(false);
2997 item = new JMenuItem("Unhide all polylines"); item.addActionListener(this); menu.add(item);
2998 if (none) item.setEnabled(false);
2999 none = ! layer.getParent().contains(Treeline.class);
3000 item = new JMenuItem("Hide all treelines"); item.addActionListener(this); menu.add(item);
3001 if (none) item.setEnabled(false);
3002 item = new JMenuItem("Unhide all treelines"); item.addActionListener(this); menu.add(item);
3003 if (none) item.setEnabled(false);
3004 none = ! layer.getParent().contains(AreaTree.class);
3005 item = new JMenuItem("Hide all areatrees"); item.addActionListener(this); menu.add(item);
3006 if (none) item.setEnabled(false);
3007 item = new JMenuItem("Unhide all areatrees"); item.addActionListener(this); menu.add(item);
3008 if (none) item.setEnabled(false);
3009 none = ! layer.getParent().contains(Ball.class);
3010 item = new JMenuItem("Hide all balls"); item.addActionListener(this); menu.add(item);
3011 if (none) item.setEnabled(false);
3012 item = new JMenuItem("Unhide all balls"); item.addActionListener(this); menu.add(item);
3013 if (none) item.setEnabled(false);
3014 none = ! layer.getParent().contains(Connector.class);
3015 item = new JMenuItem("Hide all connectors"); item.addActionListener(this); menu.add(item);
3016 if (none) item.setEnabled(false);
3017 item = new JMenuItem("Unhide all connectors"); item.addActionListener(this); menu.add(item);
3018 if (none) item.setEnabled(false);
3019 none = ! layer.getParent().containsDisplayable(Patch.class);
3020 item = new JMenuItem("Hide all images"); item.addActionListener(this); menu.add(item);
3021 if (none) item.setEnabled(false);
3022 item = new JMenuItem("Unhide all images"); item.addActionListener(this); menu.add(item);
3023 if (none) item.setEnabled(false);
3024 item = new JMenuItem("Hide all but images"); item.addActionListener(this); menu.add(item);
3025 item = new JMenuItem("Unhide all"); item.addActionListener(this); menu.add(item); item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_H, Event.ALT_MASK, true));
3027 popup.add(menu);
3028 } catch (Exception e) { IJError.print(e); }
3030 // plugins, if any
3031 Utils.addPlugIns(popup, "Display", project, new Callable<Displayable>() { public Displayable call() { return Display.this.getActive(); }});
3033 JMenu align_menu = new JMenu("Align");
3034 item = new JMenuItem("Align stack slices"); item.addActionListener(this); align_menu.add(item);
3035 if (selection.isEmpty() || ! (getActive().getClass() == Patch.class && ((Patch)getActive()).isStack())) item.setEnabled(false);
3036 item = new JMenuItem("Align layers"); item.addActionListener(this); align_menu.add(item);
3037 if (1 == layer.getParent().size()) item.setEnabled(false);
3038 item = new JMenuItem("Align layers manually with landmarks"); item.addActionListener(this); align_menu.add(item);
3039 if (1 == layer.getParent().size()) item.setEnabled(false);
3040 item = new JMenuItem("Align multi-layer mosaic"); item.addActionListener(this); align_menu.add(item);
3041 if (1 == layer.getParent().size()) item.setEnabled(false);
3042 item = new JMenuItem("Montage all images in this layer"); item.addActionListener(this); align_menu.add(item);
3043 if (layer.getDisplayables(Patch.class).size() < 2) item.setEnabled(false);
3044 item = new JMenuItem("Montage selected images"); item.addActionListener(this); align_menu.add(item);
3045 if (selection.getSelected(Patch.class).size() < 2) item.setEnabled(false);
3046 item = new JMenuItem("Montage multiple layers"); item.addActionListener(this); align_menu.add(item);
3047 popup.add(align_menu);
3049 JMenuItem st = new JMenu("Transform");
3050 StartTransformMenuListener tml = new StartTransformMenuListener();
3051 item = new JMenuItem("Transform (affine)"); item.addActionListener(tml); st.add(item);
3052 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, 0, true));
3053 if (null == active) item.setEnabled(false);
3054 item = new JMenuItem("Transform (non-linear)"); item.addActionListener(tml); st.add(item);
3055 if (null == active) item.setEnabled(false);
3056 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_T, Event.SHIFT_MASK, true));
3057 item = new JMenuItem("Cancel transform"); st.add(item);
3058 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
3059 item.setEnabled(false); // just added as a self-documenting cue; no listener
3060 item = new JMenuItem("Remove rotation, scaling and shear (selected images)"); item.addActionListener(tml); st.add(item);
3061 if (null == active) item.setEnabled(false);
3062 item = new JMenuItem("Remove rotation, scaling and shear layer-wise"); item.addActionListener(tml); st.add(item);
3063 item = new JMenuItem("Remove coordinate transforms (selected images)"); item.addActionListener(tml); st.add(item);
3064 if (null == active) item.setEnabled(false);
3065 item = new JMenuItem("Remove coordinate transforms layer-wise"); item.addActionListener(tml); st.add(item);
3066 item = new JMenuItem("Adjust mesh resolution (selected images)"); item.addActionListener(tml); st.add(item);
3067 if (null == active) item.setEnabled(false);
3068 item = new JMenuItem("Adjust mesh resolution layer-wise"); item.addActionListener(tml); st.add(item);
3069 item = new JMenuItem("Set coordinate transform of selected image to other selected images"); item.addActionListener(tml); st.add(item);
3070 if (null == active) item.setEnabled(false);
3071 item = new JMenuItem("Set coordinate transform of selected image layer-wise"); item.addActionListener(tml); st.add(item);
3072 if (null == active) item.setEnabled(false);
3073 item = new JMenuItem("Set affine transform of selected image to other selected images"); item.addActionListener(tml); st.add(item);
3074 if (null == active) item.setEnabled(false);
3075 item = new JMenuItem("Set affine transform of selected image layer-wise"); item.addActionListener(tml); st.add(item);
3076 if (null == active) item.setEnabled(false);
3077 popup.add(st);
3079 JMenu link_menu = new JMenu("Link");
3080 item = new JMenuItem("Link images..."); item.addActionListener(this); link_menu.add(item);
3081 item = new JMenuItem("Unlink all selected images"); item.addActionListener(this); link_menu.add(item);
3082 item.setEnabled(selection.getSelected(Patch.class).size() > 0);
3083 item = new JMenuItem("Unlink all"); item.addActionListener(this); link_menu.add(item);
3084 popup.add(link_menu);
3086 JMenu adjust_menu = new JMenu("Adjust images");
3087 item = new JMenuItem("Enhance contrast layer-wise..."); item.addActionListener(this); adjust_menu.add(item);
3088 item = new JMenuItem("Enhance contrast (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
3089 if (selection.isEmpty()) item.setEnabled(false);
3090 item = new JMenuItem("Adjust image filters (selected images)"); item.addActionListener(this); adjust_menu.add(item);
3091 if (selection.isEmpty()) item.setEnabled(false);
3092 item = new JMenuItem("Set Min and Max layer-wise..."); item.addActionListener(this); adjust_menu.add(item);
3093 item = new JMenuItem("Set Min and Max (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
3094 if (selection.isEmpty()) item.setEnabled(false);
3095 item = new JMenuItem("Adjust min and max (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
3096 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_J, 0));
3097 if (selection.isEmpty()) item.setEnabled(false);
3098 item = new JMenuItem("Mask image borders (layer-wise)..."); item.addActionListener(this); adjust_menu.add(item);
3099 item = new JMenuItem("Mask image borders (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
3100 if (selection.isEmpty()) item.setEnabled(false);
3101 item = new JMenuItem("Remove alpha masks (layer-wise)..."); item.addActionListener(this); adjust_menu.add(item);
3102 item = new JMenuItem("Remove alpha masks (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
3103 if (selection.isEmpty()) item.setEnabled(false);
3104 item = new JMenuItem("Split images under polyline ROI"); item.addActionListener(this); adjust_menu.add(item);
3105 Roi roi = canvas.getFakeImagePlus().getRoi();
3106 if (null == roi || roi.getType() != Roi.POLYLINE) item.setEnabled(false);
3107 item = new JMenuItem("Blend (layer-wise)..."); item.addActionListener(this); adjust_menu.add(item);
3108 item = new JMenuItem("Blend (selected images)..."); item.addActionListener(this); adjust_menu.add(item);
3109 if (selection.isEmpty()) item.setEnabled(false);
3110 popup.add(adjust_menu);
3112 JMenu script = new JMenu("Script");
3113 MenuScriptListener msl = new MenuScriptListener();
3114 item = new JMenuItem("Set preprocessor script layer-wise..."); item.addActionListener(msl); script.add(item);
3115 item = new JMenuItem("Set preprocessor script (selected images)..."); item.addActionListener(msl); script.add(item);
3116 if (selection.isEmpty()) item.setEnabled(false);
3117 item = new JMenuItem("Remove preprocessor script layer-wise..."); item.addActionListener(msl); script.add(item);
3118 item = new JMenuItem("Remove preprocessor script (selected images)..."); item.addActionListener(msl); script.add(item);
3119 if (selection.isEmpty()) item.setEnabled(false);
3120 popup.add(script);
3122 menu = new JMenu("Import");
3123 item = new JMenuItem("Import image"); item.addActionListener(this); menu.add(item);
3124 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_I, Event.ALT_MASK & Event.SHIFT_MASK, true));
3125 item = new JMenuItem("Import stack..."); item.addActionListener(this); menu.add(item);
3126 item = new JMenuItem("Import stack with landmarks..."); item.addActionListener(this); menu.add(item);
3127 item = new JMenuItem("Import grid..."); item.addActionListener(this); menu.add(item);
3128 item = new JMenuItem("Import sequence as grid..."); item.addActionListener(this); menu.add(item);
3129 item = new JMenuItem("Import from text file..."); item.addActionListener(this); menu.add(item);
3130 item = new JMenuItem("Import labels as arealists..."); item.addActionListener(this); menu.add(item);
3131 item = new JMenuItem("Tags ..."); item.addActionListener(this); menu.add(item);
3132 popup.add(menu);
3134 menu = new JMenu("Export");
3135 final boolean has_arealists = layer.getParent().contains(AreaList.class);
3136 item = new JMenuItem("Make flat image..."); item.addActionListener(this); menu.add(item);
3137 item = new JMenuItem("Arealists as labels (tif)"); item.addActionListener(this); menu.add(item);
3138 item.setEnabled(has_arealists);
3139 item = new JMenuItem("Arealists as labels (amira)"); item.addActionListener(this); menu.add(item);
3140 item.setEnabled(has_arealists);
3141 item = new JMenuItem("Image stack under selected Arealist"); item.addActionListener(this); menu.add(item);
3142 item.setEnabled(null != active && AreaList.class == active.getClass());
3143 item = new JMenuItem("Fly through selected Treeline/AreaTree"); item.addActionListener(this); menu.add(item);
3144 item.setEnabled(null != active && Tree.class.isInstance(active));
3145 item = new JMenuItem("Tags..."); item.addActionListener(this); menu.add(item);
3146 item = new JMenuItem("Connectivity graph..."); item.addActionListener(this); menu.add(item);
3147 item = new JMenuItem("NeuroML..."); item.addActionListener(this); menu.add(item);
3148 popup.add(menu);
3150 menu = new JMenu("Display");
3151 item = new JMenuItem("Resize canvas/LayerSet..."); item.addActionListener(this); menu.add(item);
3152 item = new JMenuItem("Autoresize canvas/LayerSet"); item.addActionListener(this); menu.add(item);
3153 item = new JMenuItem("Resize canvas/LayerSet to ROI"); item.addActionListener(this); menu.add(item);
3154 item.setEnabled(null != canvas.getFakeImagePlus().getRoi());
3155 item = new JMenuItem("Properties ..."); item.addActionListener(this); menu.add(item);
3156 item = new JMenuItem("Calibration..."); item.addActionListener(this); menu.add(item);
3157 item = new JMenuItem("Grid overlay..."); item.addActionListener(this); menu.add(item);
3158 item = new JMenuItem("Adjust snapping parameters..."); item.addActionListener(this); menu.add(item);
3159 item = new JMenuItem("Adjust fast-marching parameters..."); item.addActionListener(this); menu.add(item);
3160 item = new JMenuItem("Adjust arealist paint parameters..."); item.addActionListener(this); menu.add(item);
3161 item = new JMenuItem("Show current 2D position in 3D"); item.addActionListener(this); menu.add(item);
3162 item = new JMenuItem("Show layers as orthoslices in 3D"); item.addActionListener(this); menu.add(item);
3163 item = new JMenuItem("Inspect image mesh triangles"); item.addActionListener(this); menu.add(item);
3164 popup.add(menu);
3166 menu = new JMenu("Project");
3167 this.project.getLoader().setupMenuItems(menu, this.getProject());
3168 item = new JMenuItem("Project properties..."); item.addActionListener(this); menu.add(item);
3169 item = new JMenuItem("Create subproject"); item.addActionListener(this); menu.add(item);
3170 item = new JMenuItem("Create sibling project with retiled layers"); item.addActionListener(this); menu.add(item);
3171 item = new JMenuItem("Release memory..."); item.addActionListener(this); menu.add(item);
3172 item = new JMenuItem("Flush image cache"); item.addActionListener(this); menu.add(item);
3173 item = new JMenuItem("Regenerate all mipmaps"); item.addActionListener(this); menu.add(item);
3174 item = new JMenuItem("Regenerate mipmaps (selected images)"); item.addActionListener(this); menu.add(item);
3175 popup.add(menu);
3177 menu = new JMenu("Selection");
3178 item = new JMenuItem("Select all"); item.addActionListener(this); menu.add(item);
3179 item = new JMenuItem("Select all visible"); item.addActionListener(this); menu.add(item);
3180 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_A, Utils.getControlModifier(), true));
3181 if (0 == layer.getDisplayableList().size() && 0 == layer.getParent().getDisplayableList().size()) item.setEnabled(false);
3182 item = new JMenuItem("Select all that match..."); item.addActionListener(this); menu.add(item);
3183 item = new JMenuItem("Select none"); item.addActionListener(this); menu.add(item);
3184 if (0 == selection.getNSelected()) item.setEnabled(false);
3185 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0, true));
3187 JMenu bytype = new JMenu("Select all by type");
3188 item = new JMenuItem("AreaList"); item.addActionListener(bytypelistener); bytype.add(item);
3189 item = new JMenuItem("AreaTree"); item.addActionListener(bytypelistener); bytype.add(item);
3190 item = new JMenuItem("Ball"); item.addActionListener(bytypelistener); bytype.add(item);
3191 item = new JMenuItem("Connector"); item.addActionListener(bytypelistener); bytype.add(item);
3192 item = new JMenuItem("Dissector"); item.addActionListener(bytypelistener); bytype.add(item);
3193 item = new JMenuItem("Image"); item.addActionListener(bytypelistener); bytype.add(item);
3194 item = new JMenuItem("Pipe"); item.addActionListener(bytypelistener); bytype.add(item);
3195 item = new JMenuItem("Polyline"); item.addActionListener(bytypelistener); bytype.add(item);
3196 item = new JMenuItem("Profile"); item.addActionListener(bytypelistener); bytype.add(item);
3197 item = new JMenuItem("Text"); item.addActionListener(bytypelistener); bytype.add(item);
3198 item = new JMenuItem("Treeline"); item.addActionListener(bytypelistener); bytype.add(item);
3199 menu.add(bytype);
3201 item = new JMenuItem("Restore selection"); item.addActionListener(this); menu.add(item);
3202 item = new JMenuItem("Select under ROI"); item.addActionListener(this); menu.add(item);
3203 if (canvas.getFakeImagePlus().getRoi() == null) item.setEnabled(false);
3204 JMenu graph = new JMenu("Graph");
3205 GraphMenuListener gl = new GraphMenuListener();
3206 item = new JMenuItem("Select outgoing Connectors"); item.addActionListener(gl); graph.add(item);
3207 item = new JMenuItem("Select incoming Connectors"); item.addActionListener(gl); graph.add(item);
3208 item = new JMenuItem("Select downstream targets"); item.addActionListener(gl); graph.add(item);
3209 item = new JMenuItem("Select upstream targets"); item.addActionListener(gl); graph.add(item);
3210 graph.setEnabled(!selection.isEmpty());
3211 menu.add(graph);
3213 item = new JMenuItem("Measure"); item.addActionListener(this); menu.add(item);
3214 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_M, 0, true));
3215 item.setEnabled(!selection.isEmpty());
3217 popup.add(menu);
3219 menu = new JMenu("Tool");
3220 item = new JMenuItem("Rectangular ROI"); item.addActionListener(new SetToolListener(Toolbar.RECTANGLE)); menu.add(item);
3221 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F1, 0, true));
3222 item = new JMenuItem("Polygon ROI"); item.addActionListener(new SetToolListener(Toolbar.POLYGON)); menu.add(item);
3223 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F2, 0, true));
3224 item = new JMenuItem("Freehand ROI"); item.addActionListener(new SetToolListener(Toolbar.FREEROI)); menu.add(item);
3225 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F3, 0, true));
3226 item = new JMenuItem("Text"); item.addActionListener(new SetToolListener(Toolbar.TEXT)); menu.add(item);
3227 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F4, 0, true));
3228 item = new JMenuItem("Magnifier glass"); item.addActionListener(new SetToolListener(Toolbar.MAGNIFIER)); menu.add(item);
3229 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F5, 0, true));
3230 item = new JMenuItem("Hand"); item.addActionListener(new SetToolListener(Toolbar.HAND)); menu.add(item);
3231 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F6, 0, true));
3232 item = new JMenuItem("Select"); item.addActionListener(new SetToolListener(ProjectToolbar.SELECT)); menu.add(item);
3233 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F9, 0, true));
3234 item = new JMenuItem("Pencil"); item.addActionListener(new SetToolListener(ProjectToolbar.PENCIL)); menu.add(item);
3235 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F10, 0, true));
3236 item = new JMenuItem("Pen"); item.addActionListener(new SetToolListener(ProjectToolbar.PEN)); menu.add(item);
3237 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F11, 0, true));
3239 popup.add(menu);
3241 item = new JMenuItem("Search..."); item.addActionListener(this); popup.add(item);
3242 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_F, Utils.getControlModifier(), true));
3244 //canvas.add(popup);
3245 return popup;
3248 private void addAreaTreeAreasMenu(final JPopupMenu popup, final AreaTree atree) {
3249 final ActionListener listener = new ActionListener() {
3250 private final Node<?> findNearestNode() {
3251 final Layer la = getLayer();
3252 final Point p = canvas.consumeLastPopupPoint();
3253 final Node<?> lv = atree.getLastVisited();
3254 boolean use_last_visited = false;
3255 if (null != lv) {
3256 float[] xy = new float[]{lv.x, lv.y};
3257 atree.getAffineTransform().transform(xy, 0, xy, 0, 1);
3258 use_last_visited = lv.getLayer() == la && canvas.getSrcRect().contains((int)xy[0], (int)xy[1]);
3260 // Last visited node must be within the field of view in order to be used
3261 // if no node lays near the clicked point.
3262 return atree.findNodeNear(p.x, p.y, la, canvas, use_last_visited);
3264 @Override
3265 public void actionPerformed(final ActionEvent ae) {
3266 final String command = ae.getActionCommand();
3267 final LayerSet ls = atree.getLayerSet();
3269 Bureaucrat.createAndStart(new Worker.Task(command) {
3270 @Override
3271 public void exec() {
3272 final Node<?> nd = findNearestNode();
3273 if (null == nd) {
3274 Utils.log("No node found in the field of view!");
3275 return;
3277 if (command.equals("Copy area")) {
3278 Area area = (Area) nd.getData();
3279 if (null == area) return;
3280 DisplayCanvas.setCopyBuffer(atree.getClass(), area.createTransformedArea(atree.getAffineTransform()));
3281 } else if (command.equals("Paste area")) {
3282 Area wa = (Area) DisplayCanvas.getCopyBuffer(atree.getClass());
3283 if (null == wa) return;
3284 try {
3285 getLayerSet().addDataEditStep(atree);
3286 atree.addWorldAreaTo(nd, wa);
3287 atree.calculateBoundingBox(nd.getLayer());
3288 getLayerSet().addDataEditStep(atree);
3289 } catch (Exception e) {
3290 IJError.print(e);
3291 getLayerSet().removeLastUndoStep();
3293 } else if (command.equals("Interpolate gaps towards parent (node-centric)")) {
3294 interpolate(nd, true);
3295 } else if (command.equals("Interpolate gaps towards parent (absolute)")) {
3296 interpolate(nd, false);
3297 } else if (command.equals("Interpolate all gaps")) {
3298 GenericDialog gd = new GenericDialog("Interpolate");
3299 String[] a = new String[]{"node-centric", "absolute"};
3300 gd.addChoice("Mode", a, a[0]);
3301 gd.addCheckbox("Always use distance map", project.getBooleanProperty(AreaUtils.always_interpolate_areas_with_distance_map));
3302 String[] b = new String[]{"All selected AreaTrees", "Active AreaTree"};
3303 gd.addChoice("Process", b, b[0]);
3304 gd.showDialog();
3305 if (gd.wasCanceled()) return;
3306 boolean node_centric = 0 == gd.getNextChoiceIndex();
3307 boolean use_distance_map = gd.getNextBoolean();
3308 boolean all = 0 == gd.getNextChoiceIndex();
3309 Set<Displayable> s = new HashSet<Displayable>();
3310 if (all) s.addAll(selection.get(AreaTree.class));
3311 else s.add(atree);
3312 // Store current state for undo
3313 ls.addDataEditStep(s);
3314 try {
3315 for (final Displayable d : s) {
3316 ((AreaTree)d).interpolateAllGaps(node_centric, use_distance_map);
3318 ls.addDataEditStep(s);
3319 } catch (Exception e) {
3320 IJError.print(e);
3321 ls.undoOneStep();
3323 Display.repaint();
3326 private final void interpolate(final Node<?> nd, final boolean node_centric) {
3327 if (null == nd.getDataCopy() || ((Area)nd.getData()).isEmpty()) {
3328 Utils.log("Can't interpolate: node lacks an area!");
3329 return;
3331 ls.addDataEditStep(atree);
3332 try {
3333 if (atree.interpolateTowardsParent((AreaTree.AreaNode)nd, node_centric, project.getBooleanProperty(AreaUtils.always_interpolate_areas_with_distance_map))) {
3334 ls.addDataEditStep(atree);
3335 } else {
3336 Utils.log("Nothing to interpolate: the parent node already has an area.");
3337 ls.removeLastUndoStep();
3339 } catch (Exception e) {
3340 IJError.print(e);
3341 ls.undoOneStep();
3343 Display.repaint();
3345 }, atree.getProject());
3349 JMenu interpolate = new JMenu("Areas");
3350 JMenuItem item = new JMenuItem("Interpolate gaps towards parent (node-centric)"); item.addActionListener(listener); interpolate.add(item);
3351 item = new JMenuItem("Interpolate gaps towards parent (absolute)"); item.addActionListener(listener); interpolate.add(item);
3352 item = new JMenuItem("Interpolate all gaps"); item.addActionListener(listener); interpolate.add(item);
3353 item = new JMenuItem("Area interpolation options..."); item.addActionListener(Display.this); interpolate.add(item);
3354 interpolate.addSeparator();
3355 item = new JMenuItem("Copy area"); item.addActionListener(listener); interpolate.add(item);
3356 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0, true));
3357 item = new JMenuItem("Paste area"); item.addActionListener(listener); interpolate.add(item);
3358 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, 0, true));
3359 item.setEnabled(null != DisplayCanvas.getCopyBuffer(active.getClass()));
3360 popup.add(interpolate);
3363 private void addAreaListAreasMenu(final JPopupMenu popup2, final Displayable active) {
3364 final ActionListener listener = new ActionListener() {
3365 @Override
3366 public void actionPerformed(final ActionEvent ae) {
3367 final String command = ae.getActionCommand();
3368 Bureaucrat.createAndStart(new Worker.Task(command) {
3369 public void exec() {
3370 if (command.equals("Copy area")) {
3371 if (null == active || !(active instanceof AreaList)) return;
3372 AreaList ali = (AreaList)active;
3373 Area area = ali.getArea(getLayer());
3374 if (null == area) return;
3375 DisplayCanvas.setCopyBuffer(ali.getClass(), area.createTransformedArea(ali.getAffineTransform()));
3376 } else if (command.equals("Paste area")) {
3377 if (null == active || !(active instanceof AreaList)) return;
3378 AreaList ali = (AreaList)active;
3379 Area wa = (Area) DisplayCanvas.getCopyBuffer(ali.getClass());
3380 if (null == wa) return;
3381 try {
3382 getLayerSet().addDataEditStep(ali);
3383 ali.addArea(getLayer().getId(), wa.createTransformedArea(ali.getAffineTransform().createInverse()));
3384 ali.calculateBoundingBox(getLayer());
3385 getLayerSet().addDataEditStep(ali);
3386 } catch (NoninvertibleTransformException e) {
3387 IJError.print(e);
3388 getLayerSet().undoOneStep();
3390 } else if (command.equals("Interpolate gaps towards previous area")) {
3391 if (null == active || !(active instanceof AreaList)) return;
3392 AreaList ali = (AreaList)active;
3393 // Is there an area in this layer?
3394 Layer current = getLayer();
3395 if (null == ali.getArea(current)) return;
3396 // Find a layer before the current that has an area
3397 LayerSet ls = getLayerSet();
3398 if (0 == ls.indexOf(current)) return; // already at first
3399 Layer previous = null;
3400 // Iterate layers towards the first layer
3401 for (ListIterator<Layer> it = ls.getLayers().listIterator(ls.indexOf(current)); it.hasPrevious(); ) {
3402 Layer la = it.previous();
3403 if (null != ali.getArea(la)) {
3404 previous = la;
3405 break;
3408 if (null == previous) return; // all empty
3409 try {
3410 ls.addDataEditStep(ali);
3411 ali.interpolate(previous, current, project.getBooleanProperty(AreaUtils.always_interpolate_areas_with_distance_map));
3412 ls.addDataEditStep(ali);
3413 } catch (Exception e) {
3414 IJError.print(e);
3415 ls.undoOneStep();
3417 } else if (command.equals("Interpolate gaps towards next area")) {
3418 if (null == active || !(active instanceof AreaList)) return;
3419 AreaList ali = (AreaList)active;
3420 // Is there an area in this layer?
3421 Layer current = getLayer();
3422 if (null == ali.getArea(current)) return;
3423 // Find a layer after the current that has an area
3424 LayerSet ls = getLayerSet();
3425 if (ls.size() -1 == ls.indexOf(current)) return; // already at the end
3426 Layer next = null;
3427 // Iterate towards the next layer
3428 for (ListIterator<Layer> it = ls.getLayers().listIterator(ls.indexOf(current)+1); it.hasNext(); ) {
3429 Layer la = it.next();
3430 if (null != ali.getArea(la)) {
3431 next = la;
3432 break;
3435 if (null == next) return; // all empty
3436 try {
3437 ls.addDataEditStep(ali);
3438 ali.interpolate(current, next, project.getBooleanProperty(AreaUtils.always_interpolate_areas_with_distance_map));
3439 ls.addDataEditStep(ali);
3440 } catch (Exception e) {
3441 IJError.print(e);
3442 ls.undoOneStep();
3444 } else if (command.equals("Interpolate all gaps")) {
3445 if (null == active || !(active instanceof AreaList)) return;
3446 AreaList ali = (AreaList)active;
3447 // find the first and last layers with areas
3448 Layer first = null;
3449 Layer last = null;
3450 LayerSet ls = getLayerSet();
3451 List<Layer> las = ls.getLayers();
3452 for (Layer la : las) {
3453 if (null == first && null != ali.getArea(la)) {
3454 first = la;
3455 break;
3458 for (ListIterator<Layer> it = las.listIterator(las.size()); it.hasPrevious(); ) {
3459 Layer la = it.previous();
3460 if (null == last && null != ali.getArea(la)) {
3461 last = la;
3462 break;
3465 Utils.log2(first, last);
3466 if (null != first && first != last) {
3467 try {
3468 ls.addDataEditStep(ali);
3469 ali.interpolate(first, last, project.getBooleanProperty(AreaUtils.always_interpolate_areas_with_distance_map));
3470 ls.addDataEditStep(ali);
3471 } catch (Exception e) {
3472 IJError.print(e);
3473 ls.undoOneStep();
3478 Display.repaint(getLayer());
3480 }, active.getProject());
3484 JMenu interpolate = new JMenu("Areas");
3485 JMenuItem item = new JMenuItem("Interpolate gaps towards previous area"); item.addActionListener(listener); interpolate.add(item);
3486 item = new JMenuItem("Interpolate gaps towards next area"); item.addActionListener(listener); interpolate.add(item);
3487 item = new JMenuItem("Interpolate all gaps"); item.addActionListener(listener); interpolate.add(item);
3488 item = new JMenuItem("Area interpolation options..."); item.addActionListener(Display.this); interpolate.add(item);
3489 interpolate.addSeparator();
3490 item = new JMenuItem("Copy area"); item.addActionListener(listener); interpolate.add(item);
3491 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_C, 0, true));
3492 item = new JMenuItem("Paste area"); item.addActionListener(listener); interpolate.add(item);
3493 item.setAccelerator(KeyStroke.getKeyStroke(KeyEvent.VK_V, 0, true));
3494 item.setEnabled(null != DisplayCanvas.getCopyBuffer(active.getClass()));
3495 popup.add(interpolate);
3498 static private final TreeMap<String,Tag> getTags(final Tree tree) {
3499 final Set<Tag> tags = tree.findTags();
3500 if (tags.isEmpty()) {
3501 Utils.log("The nodes of the tree '" + tree + "' don't have any tags!");
3502 return null;
3504 TreeMap<String,Tag> sm = new TreeMap<String,Tag>();
3505 for (final Tag t : tags) sm.put(t.toString(), t);
3506 return sm;
3508 static private final String[] asStrings(final TreeMap<String,Tag> tags) {
3509 if (null == tags) return null;
3510 final String[] stags = new String[tags.size()];
3511 tags.keySet().toArray(stags);
3512 return stags;
3515 private ActionListener getTreePathMeasureListener(final Tree tree) {
3516 return new ActionListener() {
3517 public void actionPerformed(ActionEvent ae) {
3518 final String command = ae.getActionCommand();
3519 if (command.equals("Shortest distances between all pairs of nodes tagged as...")) {
3520 final TreeMap<String,Tag> sm = getTags(tree);
3521 if (null == sm) return;
3522 if (1 == sm.size()) {
3523 Utils.showMessage("Need at least two different tags in the tree!");
3524 return;
3526 final String[] stags = asStrings(sm);
3527 sm.keySet().toArray(stags);
3528 GenericDialog gd = new GenericDialog("Choose tag");
3529 gd.addChoice("Upstream tag:", stags, stags[0]);
3530 gd.addChoice("Downstream tag:", stags, stags[1]);
3531 gd.addNumericField("Scale:", 1, 2);
3532 LayerSet ls = tree.getLayerSet();
3533 final int resample = Display3D.estimateResamplingFactor(ls, ls.getLayerWidth(), ls.getLayerHeight());
3534 gd.addSlider("Resample: ", 1, Math.max(resample, 100), resample);
3535 gd.showDialog();
3536 if (gd.wasCanceled()) return;
3537 final Tag upstreamTag = sm.get(gd.getNextChoice());
3538 final Tag downstreamTag = sm.get(gd.getNextChoice());
3539 final List<Tree<?>.MeasurementPair> pairs = tree.measureTaggedPairs(upstreamTag, downstreamTag);
3540 ResultsTable rt = null;
3541 int index = 1;
3542 for (final Tree<?>.MeasurementPair pair : pairs) {
3543 rt = pair.toResultsTable(rt, index++, 1.0, resample);
3544 Utils.showProgress(((double)index) / pairs.size());
3546 if (index > 0) {
3547 rt.show(pairs.get(0).getResultsTableTitle());
3548 } else {
3549 Utils.logAll("No pairs found for '" + upstreamTag + "' and '" + downstreamTag + "'");
3551 return;
3553 // Measurements related to the node under the mouse
3554 Point p = getCanvas().consumeLastPopupPoint();
3555 Node clicked = tree.findClosestNodeW(p.x, p.y, getLayer(), canvas.getMagnification());
3556 if (null == clicked) {
3557 Calibration cal = getLayerSet().getCalibration();
3558 Utils.log("No node found at " + p.x * cal.pixelWidth + ", " + p.y * cal.pixelHeight);
3559 return;
3561 ResultsTable rt = null;
3562 if (command.equals("Distance from this node to root")) {
3563 rt = tree.measurePathDistance(clicked, tree.getRoot(), null);
3564 } else if (command.equals("Distance from this node to the marked node")) {
3565 if (null == tree.getMarked()) {
3566 Utils.log("No marked node!");
3567 return;
3569 rt = tree.measurePathDistance(clicked, tree.getMarked(), null);
3570 } else if (command.equals("Distance from this node to all nodes tagged as...")) {
3571 final Set<Tag> tags = tree.findTags();
3572 if (tags.isEmpty()) {
3573 Utils.log("The nodes of the tree '" + tree + "' don't have any tags!");
3574 return;
3576 TreeMap<String,Tag> sm = new TreeMap<String,Tag>();
3577 for (final Tag t : tags) sm.put(t.toString(), t);
3578 final String[] stags = new String[sm.size()];
3579 sm.keySet().toArray(stags);
3580 GenericDialog gd = new GenericDialog("Choose tag");
3581 gd.addChoice("Tag:", stags, stags[0]);
3582 gd.showDialog();
3583 if (gd.wasCanceled()) return;
3584 // So we have a Tag:
3585 final Tag tag = sm.get(gd.getNextChoice());
3586 // Measure distance to each node that has the tag
3587 for (final Node nd : (Collection<Node>)tree.getRoot().getSubtreeNodes()) {
3588 if (nd.hasTag(tag)) {
3589 rt = tree.measurePathDistance(clicked, nd, rt);
3593 if (null == rt) Utils.log("No nodes found!");
3594 else rt.show("Tree path measurements");
3599 private final class GraphMenuListener implements ActionListener {
3600 public void actionPerformed(ActionEvent ae) {
3601 final String command = ae.getActionCommand();
3602 final Collection<Displayable> sel = selection.getSelected();
3603 if (null == sel || sel.isEmpty()) return;
3605 Bureaucrat.createAndStart(new Worker.Task(command) {
3606 public void exec() {
3609 final Collection<Connector> connectors = (Collection<Connector>) (Collection) getLayerSet().getZDisplayables(Connector.class);
3610 final HashSet<Displayable> to_select = new HashSet<Displayable>();
3612 if (command.equals("Select outgoing Connectors")) {
3613 for (final Connector con : connectors) {
3614 Set<Displayable> origins = con.getOrigins();
3615 origins.retainAll(sel);
3616 if (origins.isEmpty()) continue;
3617 to_select.add(con);
3619 } else if (command.equals("Select incoming Connectors")) {
3620 for (final Connector con : connectors) {
3621 for (final Set<Displayable> targets : con.getTargets()) {
3622 targets.retainAll(sel);
3623 if (targets.isEmpty()) continue;
3624 to_select.add(con);
3627 } else if (command.equals("Select downstream targets")) {
3628 for (final Connector con : connectors) {
3629 Set<Displayable> origins = con.getOrigins();
3630 origins.retainAll(sel);
3631 if (origins.isEmpty()) continue;
3632 // else, add all targets
3633 for (final Set<Displayable> targets : con.getTargets()) {
3634 to_select.addAll(targets);
3637 } else if (command.equals("Select upstream targets")) {
3638 for (final Connector con : connectors) {
3639 for (final Set<Displayable> targets : con.getTargets()) {
3640 targets.retainAll(sel);
3641 if (targets.isEmpty()) continue;
3642 to_select.addAll(con.getOrigins());
3643 break; // origins will be the same for all targets of 'con'
3648 selection.selectAll(new ArrayList<Displayable>(to_select));
3650 }}, Display.this.project);
3654 protected class GridOverlay {
3655 ArrayList<Line2D> lines = new ArrayList<Line2D>();
3656 int ox=0, oy=0,
3657 width=(int)layer.getLayerWidth(),
3658 height=(int)layer.getLayerHeight(),
3659 xoffset=0, yoffset=0,
3660 tilewidth=100, tileheight=100,
3661 linewidth=1;
3662 boolean visible = true;
3663 Color color = new Color(255,255,0,255); // yellow with full alpha
3665 /** Expects values in pixels. */
3666 void init() {
3667 lines.clear();
3668 // Vertical lines:
3669 if (0 != xoffset) {
3670 lines.add(new Line2D.Float(ox, oy, ox, oy+height));
3672 lines.add(new Line2D.Float(ox+width, oy, ox+width, oy+height));
3673 for (int x = ox + xoffset; x <= ox + width; x += tilewidth) {
3674 lines.add(new Line2D.Float(x, oy, x, oy + height));
3676 // Horizontal lines:
3677 if (0 != yoffset) {
3678 lines.add(new Line2D.Float(ox, oy, ox+width, oy));
3680 lines.add(new Line2D.Float(ox, oy+height, ox+width, oy+height));
3681 for (int y = oy + yoffset; y <= oy + height; y += tileheight) {
3682 lines.add(new Line2D.Float(ox, y, ox + width, y));
3685 protected void paint(final Graphics2D g) {
3686 if (!visible) return;
3687 g.setStroke(new BasicStroke((float)(linewidth/canvas.getMagnification())));
3688 g.setColor(color);
3689 for (final Line2D line : lines) {
3690 g.draw(line);
3693 void setup(Roi roi) {
3694 GenericDialog gd = new GenericDialog("Grid overlay");
3695 Calibration cal = getLayerSet().getCalibration();
3696 gd.addNumericField("Top-left corner X:", ox*cal.pixelWidth, 1, 10, cal.getUnits());
3697 gd.addNumericField("Top-left corner Y:", oy*cal.pixelHeight, 1, 10, cal.getUnits());
3698 gd.addNumericField("Grid total width:", width*cal.pixelWidth, 1, 10, cal.getUnits());
3699 gd.addNumericField("Grid total height:", height*cal.pixelHeight, 1, 10, cal.getUnits());
3700 gd.addCheckbox("Read bounds from ROI", null != roi);
3701 ((Component)gd.getCheckboxes().get(0)).setEnabled(null != roi);
3702 gd.addMessage("");
3703 gd.addNumericField("Tile width:", tilewidth*cal.pixelWidth, 1, 10, cal.getUnits());
3704 gd.addNumericField("Tile height:", tileheight*cal.pixelHeight, 1, 10, cal.getUnits());
3705 gd.addNumericField("Tile offset X:", xoffset*cal.pixelWidth, 1, 10, cal.getUnits());
3706 gd.addNumericField("Tile offset Y:", yoffset*cal.pixelHeight, 1, 10, cal.getUnits());
3707 gd.addMessage("");
3708 gd.addNumericField("Line width:", linewidth, 1, 10, "pixels");
3709 gd.addSlider("Red: ", 0, 255, color.getRed());
3710 gd.addSlider("Green: ", 0, 255, color.getGreen());
3711 gd.addSlider("Blue: ", 0, 255, color.getBlue());
3712 gd.addSlider("Alpha: ", 0, 255, color.getAlpha());
3713 gd.addMessage("");
3714 gd.addCheckbox("Visible", visible);
3715 gd.showDialog();
3716 if (gd.wasCanceled()) return;
3717 this.ox = (int)(gd.getNextNumber() / cal.pixelWidth);
3718 this.oy = (int)(gd.getNextNumber() / cal.pixelHeight);
3719 this.width = (int)(gd.getNextNumber() / cal.pixelWidth);
3720 this.height = (int)(gd.getNextNumber() / cal.pixelHeight);
3721 if (gd.getNextBoolean() && null != roi) {
3722 Rectangle r = roi.getBounds();
3723 this.ox = r.x;
3724 this.oy = r.y;
3725 this.width = r.width;
3726 this.height = r.height;
3728 this.tilewidth = (int)(gd.getNextNumber() / cal.pixelWidth);
3729 this.tileheight = (int)(gd.getNextNumber() / cal.pixelHeight);
3730 this.xoffset = (int)(gd.getNextNumber() / cal.pixelWidth) % tilewidth;
3731 this.yoffset = (int)(gd.getNextNumber() / cal.pixelHeight) % tileheight;
3732 this.linewidth = (int)gd.getNextNumber();
3733 this.color = new Color((int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
3734 this.visible = gd.getNextBoolean();
3735 init();
3739 protected GridOverlay gridoverlay = null;
3742 private class StartTransformMenuListener implements ActionListener {
3743 public void actionPerformed(ActionEvent ae) {
3744 String command = ae.getActionCommand();
3745 if (command.equals("Transform (affine)")) {
3746 if (null == active) return;
3747 getLayerSet().addTransformStepWithData(selection.getAffected());
3748 setMode(new AffineTransformMode(Display.this));
3749 } else if (command.equals("Transform (non-linear)")) {
3750 if (null == active) return;
3751 getLayerSet().addTransformStepWithData(selection.getAffected());
3752 List<Displayable> col = selection.getSelected(Patch.class);
3753 for (final Displayable d : col) {
3754 if (d.isLinked()) {
3755 Utils.showMessage("Can't enter manual non-linear transformation mode:\nat least one image is linked.");
3756 return;
3759 setMode(new NonLinearTransformMode(Display.this, col));
3760 } else if (command.equals("Remove coordinate transforms (selected images)")) {
3761 if (null == active) return;
3762 final List<Displayable> col = selection.getSelected(Patch.class);
3763 if (col.isEmpty()) return;
3764 removeCoordinateTransforms( (List<Patch>) (List) col);
3765 } else if (command.equals("Remove coordinate transforms layer-wise")) {
3766 GenericDialog gd = new GenericDialog("Remove Coordinate Transforms");
3767 gd.addMessage("Remove coordinate transforms");
3768 gd.addMessage("for all images in:");
3769 Utils.addLayerRangeChoices(Display.this.layer, gd);
3770 gd.showDialog();
3771 if (gd.wasCanceled()) return;
3772 final ArrayList<Displayable> patches = new ArrayList<Displayable>();
3773 for (final Layer layer : getLayerSet().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex()+1)) {
3774 patches.addAll(layer.getDisplayables(Patch.class));
3776 removeCoordinateTransforms( (List<Patch>) (List) patches);
3777 } else if (command.equals("Remove rotation, scaling and shear (selected images)")) {
3778 if (null == active) return;
3779 final List<Displayable> col = selection.getSelected(Patch.class);
3780 if (col.isEmpty()) return;
3781 removeScalingRotationShear( (List<Patch>) (List) col);
3782 } else if (command.equals("Remove rotation, scaling and shear layer-wise")) {
3783 // Because we love copy-paste
3784 GenericDialog gd = new GenericDialog("Remove Scaling/Rotation/Shear");
3785 gd.addMessage("Remove scaling, translation");
3786 gd.addMessage("and shear for all images in:");
3787 Utils.addLayerRangeChoices(Display.this.layer, gd);
3788 gd.showDialog();
3789 if (gd.wasCanceled()) return;
3790 final ArrayList<Displayable> patches = new ArrayList<Displayable>();
3791 for (final Layer layer : getLayerSet().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex()+1)) {
3792 patches.addAll(layer.getDisplayables(Patch.class));
3794 removeScalingRotationShear( (List<Patch>) (List) patches);
3795 } else if (command.equals("Adjust mesh resolution (selected images)")) {
3796 if (null == active) return;
3797 final List<Patch> col = selection.get(Patch.class);
3798 if (col.isEmpty()) return;
3799 GenericDialog gd = new GenericDialog("Adjust mesh resolution");
3800 gd.addSlider("Mesh resolution:", 2, 512,
3801 ((Patch)(active.getClass() == Patch.class ? active : col.get(0))).getMeshResolution());
3802 gd.showDialog();
3803 if (gd.wasCanceled()) return;
3804 setMeshResolution(col, (int)gd.getNextNumber());
3805 } else if (command.equals("Adjust mesh resolution layer-wise")) {
3806 GenericDialog gd = new GenericDialog("Adjust mesh resolution");
3807 Utils.addLayerRangeChoices(Display.this.layer, gd);
3808 gd.addSlider("Mesh resolution:", 2, 512, project.getProperty("mesh_resolution", 32));
3809 gd.showDialog();
3810 if (gd.wasCanceled()) return;
3811 final ArrayList<Patch> patches = new ArrayList<Patch>();
3812 for (final Layer layer : getLayerSet().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex()+1)) {
3813 patches.addAll(layer.getAll(Patch.class));
3815 setMeshResolution(patches, (int)gd.getNextNumber());
3816 } else if (command.startsWith("Set coordinate transform of selected image")) { // )) {
3817 if (null == active || !(active instanceof Patch)) return;
3818 CoordinateTransform ct = ((Patch)active).getCoordinateTransform();
3819 if (null == ct) {
3820 Utils.showMessage("The selected image does not have a coordinate transform!");
3821 return;
3823 final List<Patch> patches;
3824 final GenericDialog gd = new GenericDialog("Set coordinate transform");
3825 gd.addChoice(
3826 "Existing coordinate transform",
3827 new String[]{ "Replace", "Append", "Pre-append" },
3828 "Replace" );
3829 gd.addCheckbox("Only the lens distortion correction", false);
3830 if (command.endsWith("to other selected images")) {
3831 patches = selection.get(Patch.class);
3832 patches.remove((Patch)active);
3833 if (patches.isEmpty()) {
3834 Utils.showMessage("Select more than one image!");
3835 return;
3837 gd.showDialog();
3838 if (gd.wasCanceled()) return;
3840 } else if (command.endsWith("layer-wise")) {
3841 Utils.addLayerRangeChoices(Display.this.layer, gd);
3842 gd.showDialog();
3843 if (gd.wasCanceled()) return;
3844 patches = new ArrayList<Patch>();
3845 for (final Layer layer : getLayerSet().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex()+1)) {
3846 patches.addAll(layer.getAll(Patch.class));
3848 } else {
3849 return;
3851 int existingCT = gd.getNextChoiceIndex();
3852 boolean only_lens_model = gd.getNextBoolean();
3854 if (only_lens_model) {
3855 ct = findFirstLensDeformationModel(ct);
3856 if (null == ct) {
3857 Utils.showMessage("Could not find a lens distortion correction model\n in image " + active);
3858 return;
3862 setCoordinateTransform(patches, ct, existingCT);
3863 } else if (command.equals("Set affine transform of selected image to other selected images")) {
3864 if (null == active || !(active instanceof Patch)) return;
3865 final AffineTransform aff = active.getAffineTransformCopy();
3866 final HashSet<Layer> layers = new HashSet<Layer>();
3867 final Collection<Displayable> patches = selection.getSelected(Patch.class);
3868 getLayerSet().addTransformStep(patches);
3869 for (final Displayable p : patches) {
3870 if (p == active) continue;
3871 p.setAffineTransform(aff);
3872 layers.add(p.getLayer());
3874 for (final Layer l : layers) {
3875 l.recreateBuckets();
3877 // Current state
3878 getLayerSet().addTransformStep(patches);
3879 } else if (command.equals("Set affine transform of selected image layer-wise")) {
3880 if (null == active || !(active instanceof Patch)) return;
3881 final AffineTransform aff = active.getAffineTransformCopy();
3882 final GenericDialog gd = new GenericDialog("Choose range of layers");
3883 Utils.addLayerRangeChoices(Display.this.layer, gd);
3884 gd.showDialog();
3885 if (gd.wasCanceled()) return;
3886 final ArrayList<Patch> patches = new ArrayList<Patch>();
3887 final List<Layer> layers = getLayerSet().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex()+1);
3888 for (final Layer layer : layers) {
3889 patches.addAll(layer.getAll(Patch.class));
3891 getLayerSet().addTransformStep(patches);
3892 for (final Patch p: patches) {
3893 p.setAffineTransform(aff);
3895 for (final Layer l : layers) {
3896 l.recreateBuckets();
3898 // Current state
3899 getLayerSet().addTransformStep(patches);
3901 repaint();
3903 private NonLinearTransform findFirstLensDeformationModel(CoordinateTransform ct) {
3904 /* unwind CT lists to get the very first actual CT */
3905 while ( CoordinateTransformList.class.isInstance( ct ) )
3906 ct = ( ( CoordinateTransformList< CoordinateTransform > )ct ).get( 0 );
3907 if (ct instanceof NonLinearTransform) return (NonLinearTransform) ct;
3908 // Not found
3909 return null;
3913 public Bureaucrat removeScalingRotationShear(final List<Patch> patches) {
3914 return Bureaucrat.createAndStart(new Worker.Task("Removing coordinate transforms") { public void exec() {
3915 getLayerSet().addTransformStep(patches);
3916 for (final Patch p : patches) {
3917 Rectangle box = p.getBoundingBox();
3918 final AffineTransform aff = new AffineTransform();
3919 // translate so that the center remains where it is
3920 aff.setToTranslation(box.x + (box.width - p.getWidth())/2, box.y + (box.height - p.getHeight())/2);
3921 p.setAffineTransform(aff);
3923 getLayerSet().addTransformStep(patches);
3924 Display.repaint();
3925 }}, this.project);
3928 /** Meant for tasks that require setting an undo and regenerating mipmaps.
3929 * The method will NOT run if any Patch is linked.
3931 * @param patches
3932 * @param task
3933 * @param filter
3934 * @param taskTitle
3935 * @return the {@link Bureaucrat} in charge of the task.
3937 public Bureaucrat applyPatchTask(final List<Patch> patches, final String taskTitle, final Operation<Boolean,Patch> task, final Filter<Patch> filter) {
3938 return Bureaucrat.createAndStart(new Worker.Task(taskTitle) { public void exec() {
3939 final HashSet<Patch> ds = new HashSet<Patch>();
3940 for (final Patch p : patches) {
3941 // Check if any are linked: cannot remove, would break image-to-segmentation relationship
3942 if (p.isLinked()) {
3943 Utils.logAll("Cannot apply task: some images are linked to segmentations!");
3944 return;
3946 if (Thread.currentThread().isInterrupted() || hasQuitted()) return;
3947 if (filter.accept(p)) {
3948 ds.add(p);
3952 if (ds.isEmpty()) {
3953 Utils.log("Nothing to do.");
3954 return;
3957 // Add undo step:
3958 getLayerSet().addDataEditStep(ds);
3960 // Execute
3961 final ArrayList<Future<?>> fus = new ArrayList<Future<?>>();
3962 for (final Patch p : ds) {
3963 if (Thread.currentThread().isInterrupted() || hasQuitted()) return;
3964 if (task.apply(p)) {
3965 fus.add(p.getProject().getLoader().regenerateMipMaps(p)); // queue
3968 Utils.wait(fus);
3970 // Set current state
3971 getLayerSet().addDataEditStep(ds);
3972 }}, project);
3975 public Bureaucrat removeCoordinateTransforms(final List<Patch> patches) {
3976 return applyPatchTask(
3977 patches,
3978 "Removing coordinate transforms",
3979 new Operation<Boolean, Patch>() {
3980 @Override
3981 public Boolean apply(Patch o) {
3982 o.setCoordinateTransform(null);
3983 return true;
3986 new Filter<Patch>() {
3987 @Override
3988 public boolean accept(Patch t) {
3989 return t.hasCoordinateTransform();
3994 public Bureaucrat setCoordinateTransform(final List<Patch> patches, final CoordinateTransform ct, final int existingTransform) {
3995 return applyPatchTask(
3996 patches,
3997 "Set coordinate transform",
3998 new Operation<Boolean, Patch>() {
3999 @Override
4000 public Boolean apply(Patch o) {
4001 switch ( existingTransform )
4003 case CT_REPLACE:
4004 o.setCoordinateTransform(ct);
4005 break;
4006 case CT_APPEND:
4007 o.appendCoordinateTransform(ct);
4008 break;
4009 case CT_PREAPPEND:
4010 o.preAppendCoordinateTransform(ct);
4012 return true;
4015 new Filter<Patch>() {
4016 @Override
4017 public boolean accept(Patch t) {
4018 return true;
4025 * @depracated Use {@link #setCoordinateTransform(List, CoordinateTransform, int)} instead which implements pre-appending as a third mode.
4027 * @param patches
4028 * @param ct
4029 * @param append
4030 * @return
4032 @Deprecated
4033 public Bureaucrat setCoordinateTransform(final List<Patch> patches, final CoordinateTransform ct, final boolean append) {
4034 return setCoordinateTransform( patches, ct, append ? CT_APPEND : CT_REPLACE );
4037 public Bureaucrat setMeshResolution(final List<Patch> patches, final int meshResolution) {
4038 if (meshResolution < 1) {
4039 Utils.log("Cannot apply a mesh resolution smaller than 1!");
4040 return null;
4042 return applyPatchTask(
4043 patches,
4044 "Alter mesh resolution",
4045 new Operation<Boolean, Patch>() {
4046 @Override
4047 public Boolean apply(Patch o) {
4048 o.setMeshResolution(meshResolution);
4049 // Return false to avoid regenerating mipmaps when there isn't a CoordinateTransform
4050 return null != o.getCoordinateTransform();
4053 new Filter<Patch>() {
4054 @Override
4055 public boolean accept(Patch t) {
4056 return t.getMeshResolution() != meshResolution;
4061 private class MenuScriptListener implements ActionListener {
4062 public void actionPerformed(ActionEvent ae) {
4063 final String command = ae.getActionCommand();
4064 Bureaucrat.createAndStart(new Worker.Task("Setting preprocessor script") { public void exec() {
4065 try{
4066 if (command.equals("Set preprocessor script layer-wise...")) {
4067 Collection<Layer> ls = getLayerList("Set preprocessor script");
4068 if (null == ls) return;
4069 String path = getScriptPath();
4070 if (null == path) return;
4071 setScriptPathToLayers(ls, path);
4072 } else if (command.equals("Set preprocessor script (selected images)...")) {
4073 if (selection.isEmpty()) return;
4074 String path = getScriptPath();
4075 if (null == path) return;
4076 setScriptPath(selection.get(Patch.class), path);
4077 } else if (command.equals("Remove preprocessor script layer-wise...")) {
4078 Collection<Layer> ls = getLayerList("Remove preprocessor script");
4079 if (null == ls) return;
4080 setScriptPathToLayers(ls, null);
4081 } else if (command.equals("Remove preprocessor script (selected images)...")) {
4082 if (selection.isEmpty()) return;
4083 setScriptPath(selection.get(Patch.class), null);
4085 } catch (Exception e) {
4086 IJError.print(e);
4088 }}, Display.this.project);
4090 private void setScriptPathToLayers(final Collection<Layer> ls, final String script) throws Exception {
4091 final ArrayList<Patch> ds = new ArrayList<Patch>();
4092 for (final Layer la : ls) {
4093 if (Thread.currentThread().isInterrupted()) return;
4094 ds.addAll(la.getAll(Patch.class));
4096 setScriptPath(ds, script); // no lazy sequences ...
4098 /** Accepts null script, to remove it if there. */
4099 private void setScriptPath(final Collection<Patch> list, final String script) throws Exception {
4100 Process.progressive(list, new TaskFactory<Patch,Object>() {
4101 public Object process(final Patch p) {
4102 p.setPreprocessorScriptPath(script);
4103 try {
4104 p.updateMipMaps().get(); // wait for mipmap regeneration so that the processed image is in cache for mipmap regeneration
4105 } catch (Throwable t) {
4106 IJError.print(t);
4108 return null;
4110 }, Math.max(1, Runtime.getRuntime().availableProcessors() -1));
4112 private Collection<Layer> getLayerList(String title) {
4113 final GenericDialog gd = new GenericDialog(title);
4114 Utils.addLayerRangeChoices(Display.this.layer, gd);
4115 gd.showDialog();
4116 if (gd.wasCanceled()) return null;
4117 return layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1); // exclusive end
4119 private String getScriptPath() {
4120 OpenDialog od = new OpenDialog("Select script", OpenDialog.getLastDirectory(), null);
4121 String dir = od.getDirectory();
4122 if (null == dir) return null;
4123 if (IJ.isWindows()) dir = dir.replace('\\','/');
4124 if (!dir.endsWith("/")) dir += "/";
4125 return dir + od.getFileName();
4129 private class SetToolListener implements ActionListener {
4130 final int tool;
4131 SetToolListener(int tool) {
4132 this.tool = tool;
4134 public void actionPerformed(ActionEvent ae) {
4135 ProjectToolbar.setTool(tool);
4136 toolbar_panel.repaint();
4140 private ByTypeListener bytypelistener = new ByTypeListener(this);
4142 static private class ByTypeListener implements ActionListener {
4143 final Display d;
4144 ByTypeListener(final Display d) {
4145 this.d = d;
4147 public void actionPerformed(final ActionEvent ae) {
4148 final String command = ae.getActionCommand();
4150 final Area aroi = M.getArea(d.canvas.getFakeImagePlus().getRoi());
4152 d.dispatcher.exec(new Runnable() { public void run() {
4154 try {
4155 String type = command;
4156 if (type.equals("Image")) type = "Patch";
4157 else if (type.equals("Text")) type = "DLabel";
4158 Class<?> c = Class.forName("ini.trakem2.display." + type);
4160 java.util.List<Displayable> a = new ArrayList<Displayable>();
4161 if (null != aroi) {
4162 a.addAll(d.layer.getDisplayables(c, aroi, true));
4163 a.addAll(d.layer.getParent().findZDisplayables(c, d.layer, aroi, true, true));
4164 } else {
4165 a.addAll(d.layer.getDisplayables(c));
4166 a.addAll(d.layer.getParent().getZDisplayables(c));
4167 // Remove non-visible ones
4168 for (final Iterator<Displayable> it = a.iterator(); it.hasNext(); ) {
4169 if (!it.next().isVisible()) it.remove();
4173 if (0 == a.size()) return;
4175 boolean selected = false;
4177 if (0 == ae.getModifiers()) {
4178 Utils.log2("first");
4179 d.selection.clear();
4180 d.selection.selectAll(a);
4181 selected = true;
4182 } else if (0 == (ae.getModifiers() ^ Event.SHIFT_MASK)) {
4183 Utils.log2("with shift");
4184 d.selection.selectAll(a); // just add them to the current selection
4185 selected = true;
4187 if (selected) {
4188 // Activate last:
4189 d.selection.setActive(a.get(a.size() -1));
4192 } catch (ClassNotFoundException e) {
4193 Utils.log2(e.toString());
4196 }});
4200 /** Check if a panel for the given Displayable is completely visible in the JScrollPane */
4201 public boolean isWithinViewport(final Displayable d) {
4202 Component comp = tabs.getSelectedComponent();
4203 if (!(comp instanceof RollingPanel)) return false;
4204 final RollingPanel rp = (RollingPanel)tabs.getSelectedComponent();
4205 if (ht_tabs.get(d.getClass()) == rp) return rp.isShowing(d);
4206 return false;
4209 /** Check if a panel for the given Displayable is partially visible in the JScrollPane */
4210 public boolean isPartiallyWithinViewport(final Displayable d) {
4211 final RollingPanel rp = ht_tabs.get(d.getClass());
4212 return rp.isShowing(d);
4215 // for Layer panels
4216 private void scrollToShow(final JScrollPane scroll, final JPanel dp) {
4217 if (null == dp) return;
4218 Utils.invokeLater(new Runnable() { public void run() {
4219 JViewport view = scroll.getViewport();
4220 Point current = view.getViewPosition();
4221 Dimension extent = view.getExtentSize();
4222 int panel_y = dp.getY();
4223 if ((panel_y + DisplayablePanel.HEIGHT - current.y) <= extent.height && panel_y >= current.y) {
4224 // it's completely visible already
4225 return;
4226 } else {
4227 // scroll just enough
4228 // if it's above, show at the top
4229 if (panel_y - current.y < 0) {
4230 view.setViewPosition(new Point(0, panel_y));
4232 // if it's below (even if partially), show at the bottom
4233 else if (panel_y + 50 > current.y + extent.height) {
4234 view.setViewPosition(new Point(0, panel_y - extent.height + 50));
4235 //Utils.log("Display.scrollToShow: panel_y: " + panel_y + " current.y: " + current.y + " extent.height: " + extent.height);
4238 }});
4241 /** Update the title of the given Displayable in its DisplayablePanel, if any. */
4242 static public void updateTitle(final Layer layer, final Displayable displ) {
4243 for (final Display d : al_displays) {
4244 if (layer == d.layer) {
4245 DisplayablePanel dp = d.ht_panels.get(displ);
4246 if (null != dp) dp.updateTitle();
4251 /** Update the Display's title in all Displays showing the given Layer. */
4252 static public void updateTitle(final Layer layer) {
4253 for (final Display d : al_displays) {
4254 if (d.layer == layer) d.updateFrameTitle();
4257 static public void updateTitle(final Project project) {
4258 for (final Display d : al_displays) {
4259 if (d.project == project) d.updateFrameTitle();
4262 /** Update the Display's title in all Displays showing a Layer of the given LayerSet. */
4263 static public void updateTitle(final LayerSet ls) {
4264 for (final Display d : al_displays) {
4265 if (d.layer.getParent() == ls) d.updateFrameTitle(d.layer);
4269 /** Set a new title in the JFrame, showing info on the layer 'z' and the magnification. */
4270 public void updateFrameTitle() {
4271 updateFrameTitle(layer);
4273 private void updateFrameTitle(final Layer layer) {
4274 // From ij.ImagePlus class, the solution:
4275 String scale = "";
4276 final double magnification = canvas.getMagnification();
4277 if (magnification!=1.0) {
4278 final double percent = magnification*100.0;
4279 scale = new StringBuilder(" (").append(Utils.d2s(percent, percent==(int)percent ? 0 : 1)).append("%)").toString();
4281 final LayerSet ls = layer.getParent();
4282 final Calibration cal = ls.getCalibration();
4283 final Layer last = ls.getLayer(ls.size()-1);
4284 final double depth = (last.getZ() - ls.getLayer(0).getZ() + last.getThickness()) * cal.pixelWidth;
4285 final String title = new StringBuilder(100)
4286 .append(layer.getParent().indexOf(layer) + 1).append('/').append(layer.getParent().size())
4287 .append(" z:").append(layer.getZ() * cal.pixelWidth).append(' ').append(cal.getUnits()).append(' ') // Not pixelDepth
4288 .append(' ').append(layer.getLayerThingTitle())
4289 .append(scale)
4290 .append(" -- ").append(getProject().toString())
4291 .append(' ').append(' ').append(Utils.cutNumber(layer.getParent().getLayerWidth() * cal.pixelWidth, 2, true))
4292 .append('x').append(Utils.cutNumber(layer.getParent().getLayerHeight() * cal.pixelHeight, 2, true))
4293 .append('x').append(Utils.cutNumber(depth, 2, true))
4294 .append(' ').append(cal.getUnit()).toString();
4295 Utils.invokeLater(new Runnable() { public void run() {
4296 frame.setTitle(title);
4297 }});
4298 // fix the title for the FakeImageWindow and thus the WindowManager listing in the menus
4299 canvas.getFakeImagePlus().setTitle(title);
4302 /** 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. */
4303 public void nextLayer(final int modifiers) {
4304 final Layer l;
4305 if (0 == (modifiers ^ Event.SHIFT_MASK)) {
4306 l = layer.getParent().nextNonEmpty(layer);
4307 } else if (scroll_step > 1) {
4308 int i = layer.getParent().indexOf(this.layer);
4309 Layer la = layer.getParent().getLayer(i + scroll_step);
4310 if (null != la) l = la;
4311 else l = null;
4312 } else {
4313 l = layer.getParent().next(layer);
4315 if (l != layer) {
4316 slt.set(l);
4317 updateInDatabase("layer_id");
4321 /** Should be invoked within event dispatch thread. */
4322 private final void translateLayerColors(final Layer current, final Layer other) {
4323 if (current == other) return;
4324 if (layer_channels.size() > 0) {
4325 final LayerSet ls = getLayerSet();
4326 // translate colors by distance from current layer to new Layer l
4327 final int dist = ls.indexOf(other) - ls.indexOf(current);
4328 translateLayerColor(Color.red, dist);
4329 translateLayerColor(Color.blue, dist);
4333 private final void translateLayerColor(final Color color, final int dist) {
4334 final LayerSet ls = getLayerSet();
4335 final Layer l = layer_channels.get(color);
4336 if (null == l) return;
4337 updateColor(Color.white, layer_panels.get(l));
4338 final Layer l2 = ls.getLayer(ls.indexOf(l) + dist);
4339 if (null != l2) updateColor(color, layer_panels.get(l2));
4342 private final void updateColor(final Color color, final LayerPanel lp) {
4343 lp.setColor(color);
4344 setColorChannel(lp.layer, color);
4347 /** Calls setLayer(la) on the SetLayerThread. */
4348 public void toLayer(final Layer la) {
4349 if (la.getParent() != layer.getParent()) return; // not of the same LayerSet
4350 if (la == layer) return; // nothing to do
4351 slt.set(la);
4352 updateInDatabase("layer_id");
4355 /** 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. */
4356 public void previousLayer(final int modifiers) {
4357 final Layer l;
4358 if (0 == (modifiers ^ Event.SHIFT_MASK)) {
4359 l = layer.getParent().previousNonEmpty(layer);
4360 } else if (scroll_step > 1) {
4361 int i = layer.getParent().indexOf(this.layer);
4362 Layer la = layer.getParent().getLayer(i - scroll_step);
4363 if (null != la) l = la;
4364 else l = null;
4365 } else {
4366 l = layer.getParent().previous(layer);
4368 if (l != layer) {
4369 slt.set(l);
4370 updateInDatabase("layer_id");
4374 static public void updateLayerScroller(LayerSet set) {
4375 for (final Display d : al_displays) {
4376 if (d.layer.getParent() == set) {
4377 d.updateLayerScroller(d.layer);
4382 private void updateLayerScroller(final Layer layer) {
4383 Utils.invokeLater(new Runnable() { public void run() {
4384 int size = layer.getParent().size();
4385 if (size <= 1) {
4386 scroller.setValues(0, 1, 0, 0);
4387 scroller.setEnabled(false);
4388 } else {
4389 scroller.setEnabled(true);
4390 scroller.setValues(layer.getParent().indexOf(layer), 1, 0, size);
4392 recreateLayerPanels(layer);
4393 }});
4396 // Can't use this.layer, may still be null. User argument instead.
4397 private synchronized void recreateLayerPanels(final Layer layer) {
4398 synchronized (layer_channels) {
4399 panel_layers.removeAll();
4401 final GridBagLayout gb = (GridBagLayout) panel_layers.getLayout();
4402 panel_layers.setLayout(gb);
4404 final GridBagConstraints c = new GridBagConstraints();
4405 c.anchor = GridBagConstraints.NORTHWEST;
4406 c.fill = GridBagConstraints.HORIZONTAL;
4407 c.gridx = 0;
4408 c.gridy = 0;
4410 if (0 == layer_panels.size()) {
4411 for (final Layer la : layer.getParent().getLayers()) {
4412 final LayerPanel lp = new LayerPanel(this, la);
4413 layer_panels.put(la, lp);
4414 gb.setConstraints(lp, c);
4415 this.panel_layers.add(lp);
4416 c.gridy += 1;
4418 } else {
4419 // Set theory at work: keep old to reuse
4420 layer_panels.keySet().retainAll(layer.getParent().getLayers());
4421 for (final Layer la : layer.getParent().getLayers()) {
4422 LayerPanel lp = layer_panels.get(la);
4423 if (null == lp) {
4424 lp = new LayerPanel(this, la);
4425 layer_panels.put(la, lp);
4427 gb.setConstraints(lp, c);
4428 this.panel_layers.add(lp);
4429 c.gridy += 1;
4431 for (final Iterator<Map.Entry<Integer,LayerPanel>> it = layer_alpha.entrySet().iterator(); it.hasNext(); ) {
4432 final Map.Entry<Integer,LayerPanel> e = it.next();
4433 if (-1 == getLayerSet().indexOf(e.getValue().layer)) it.remove();
4435 for (final Iterator<Map.Entry<Color,Layer>> it = layer_channels.entrySet().iterator(); it.hasNext(); ) {
4436 final Map.Entry<Color,Layer> e = it.next();
4437 if (-1 == getLayerSet().indexOf(e.getValue())) it.remove();
4439 scroll_layers.repaint();
4444 private void updateSnapshots() {
4445 Utils.invokeLater(new Runnable() { public void run() {
4446 Enumeration<DisplayablePanel> e = ht_panels.elements();
4447 while (e.hasMoreElements()) {
4448 e.nextElement().repaint();
4450 Utils.updateComponent(tabs.getSelectedComponent());
4451 }});
4454 static public void updatePanel(Layer layer, final Displayable displ) {
4455 if (null == layer && null != front) layer = front.layer; // the front layer
4456 for (final Display d : al_displays) {
4457 if (d.layer == layer) {
4458 d.updatePanel(displ);
4463 private void updatePanel(Displayable d) {
4464 JPanel c = null;
4465 if (d instanceof Profile) {
4466 c = panel_profiles;
4467 } else if (d instanceof Patch) {
4468 c = panel_patches;
4469 } else if (d instanceof DLabel) {
4470 c = panel_labels;
4471 } else if (d instanceof Pipe) {
4472 c = panel_zdispl;
4474 if (null == c) return;
4475 DisplayablePanel dp = ht_panels.get(d);
4476 if (null != dp) {
4477 dp.repaint();
4478 Utils.updateComponent(c);
4482 @Deprecated
4483 static public void updatePanelIndex(final Layer layer, final Displayable displ) {
4484 for (final Display d : al_displays) {
4485 if (d.layer == layer || displ instanceof ZDisplayable) {
4486 d.updatePanelIndex(displ);
4491 @Deprecated
4492 private void updatePanelIndex(final Displayable d) {
4493 Utils.invokeLater(new Runnable() { public void run() {
4494 ht_tabs.get(d.getClass()).updateList();
4495 }});
4498 /** Repair possibly missing panels and other components by simply resetting the same Layer */
4499 public void repairGUI() {
4500 setLayer(layer, true);
4503 public void actionPerformed(final ActionEvent ae) {
4504 dispatcher.exec(new Runnable() { public void run() {
4506 String command = ae.getActionCommand();
4507 if (command.startsWith("Job")) {
4508 if (Utils.checkYN("Really cancel job?")) {
4509 project.getLoader().quitJob(command);
4510 repairGUI();
4512 return;
4513 } else if (command.equals("Move to top")) {
4514 if (null == active) return;
4515 canvas.setUpdateGraphics(true);
4516 getLayerSet().addUndoMoveStep(active);
4517 layer.getParent().move(LayerSet.TOP, active);
4518 getLayerSet().addUndoMoveStep(active);
4519 Display.repaint(layer.getParent(), active, 5);
4520 //Display.updatePanelIndex(layer, active);
4521 } else if (command.equals("Move up")) {
4522 if (null == active) return;
4523 canvas.setUpdateGraphics(true);
4524 getLayerSet().addUndoMoveStep(active);
4525 layer.getParent().move(LayerSet.UP, active);
4526 getLayerSet().addUndoMoveStep(active);
4527 Display.repaint(layer.getParent(), active, 5);
4528 //Display.updatePanelIndex(layer, active);
4529 } else if (command.equals("Move down")) {
4530 if (null == active) return;
4531 canvas.setUpdateGraphics(true);
4532 getLayerSet().addUndoMoveStep(active);
4533 layer.getParent().move(LayerSet.DOWN, active);
4534 getLayerSet().addUndoMoveStep(active);
4535 Display.repaint(layer.getParent(), active, 5);
4536 //Display.updatePanelIndex(layer, active);
4537 } else if (command.equals("Move to bottom")) {
4538 if (null == active) return;
4539 canvas.setUpdateGraphics(true);
4540 getLayerSet().addUndoMoveStep(active);
4541 layer.getParent().move(LayerSet.BOTTOM, active);
4542 getLayerSet().addUndoMoveStep(active);
4543 Display.repaint(layer.getParent(), active, 5);
4544 //Display.updatePanelIndex(layer, active);
4545 } else if (command.equals("Duplicate, link and send to next layer")) {
4546 duplicateLinkAndSendTo(active, 1, layer.getParent().next(layer));
4547 } else if (command.equals("Duplicate, link and send to previous layer")) {
4548 duplicateLinkAndSendTo(active, 0, layer.getParent().previous(layer));
4549 } else if (command.equals("Duplicate, link and send to...")) {
4550 // fix non-scrolling popup menu
4551 Utils.invokeLater(new Runnable() { public void run() {
4552 GenericDialog gd = new GenericDialog("Send to");
4553 gd.addMessage("Duplicate, link and send to...");
4554 String[] sl = new String[layer.getParent().size()];
4555 int next = 0;
4556 for (final Layer la : layer.getParent().getLayers()) {
4557 sl[next++] = project.findLayerThing(la).toString();
4559 gd.addChoice("Layer: ", sl, sl[layer.getParent().indexOf(layer)]);
4560 gd.showDialog();
4561 if (gd.wasCanceled()) return;
4562 Layer la = layer.getParent().getLayer(gd.getNextChoiceIndex());
4563 if (layer == la) {
4564 Utils.showMessage("Can't duplicate, link and send to the same layer.");
4565 return;
4567 duplicateLinkAndSendTo(active, 0, la);
4568 }});
4569 } else if (-1 != command.indexOf("z = ")) {
4570 // this is an item from the "Duplicate, link and send to" menu of layer z's
4571 Layer target_layer = layer.getParent().getLayer(Double.parseDouble(command.substring(command.lastIndexOf(' ') +1)));
4572 Utils.log2("layer: __" +command.substring(command.lastIndexOf(' ') +1) + "__");
4573 if (null == target_layer) return;
4574 duplicateLinkAndSendTo(active, 0, target_layer);
4575 } else if (-1 != command.indexOf("z=")) {
4576 // WARNING the indexOf is very similar to the previous one
4577 // Send the linked group to the selected layer
4578 int iz = command.indexOf("z=")+2;
4579 Utils.log2("iz=" + iz + " other: " + command.indexOf(' ', iz+2));
4580 int end = command.indexOf(' ', iz);
4581 if (-1 == end) end = command.length();
4582 double lz = Double.parseDouble(command.substring(iz, end));
4583 Layer target = layer.getParent().getLayer(lz);
4584 layer.getParent().move(selection.getAffected(), active.getLayer(), target); // TODO what happens when ZDisplayable are selected?
4585 } else if (command.equals("Unlink")) {
4586 if (null == active || active instanceof Patch) return;
4587 active.unlink();
4588 updateSelection();//selection.update();
4589 } else if (command.equals("Unlink from images")) {
4590 if (null == active) return;
4591 try {
4592 for (Displayable displ: selection.getSelected()) {
4593 displ.unlinkAll(Patch.class);
4595 updateSelection();//selection.update();
4596 } catch (Exception e) { IJError.print(e); }
4597 } else if (command.equals("Unlink slices")) {
4598 YesNoCancelDialog yn = new YesNoCancelDialog(frame, "Attention", "Really unlink all slices from each other?\nThere is no undo.");
4599 if (!yn.yesPressed()) return;
4600 final ArrayList<Patch> pa = ((Patch)active).getStackPatches();
4601 for (int i=pa.size()-1; i>0; i--) {
4602 pa.get(i).unlink(pa.get(i-1));
4604 } else if (command.equals("Send to next layer")) {
4605 Rectangle box = selection.getBox();
4606 try {
4607 // unlink Patch instances
4608 for (final Displayable displ : selection.getSelected()) {
4609 displ.unlinkAll(Patch.class);
4611 updateSelection();//selection.update();
4612 } catch (Exception e) { IJError.print(e); }
4613 //layer.getParent().moveDown(layer, active); // will repaint whatever appropriate layers
4614 selection.moveDown();
4615 repaint(layer.getParent(), box);
4616 } else if (command.equals("Send to previous layer")) {
4617 Rectangle box = selection.getBox();
4618 try {
4619 // unlink Patch instances
4620 for (final Displayable displ : selection.getSelected()) {
4621 displ.unlinkAll(Patch.class);
4623 updateSelection();//selection.update();
4624 } catch (Exception e) { IJError.print(e); }
4625 //layer.getParent().moveUp(layer, active); // will repaint whatever appropriate layers
4626 selection.moveUp();
4627 repaint(layer.getParent(), box);
4628 } else if (command.equals("Show centered")) {
4629 if (active == null) return;
4630 showCentered(active);
4631 } else if (command.equals("Delete...")) {
4632 // remove all selected objects
4633 selection.deleteAll();
4634 } else if (command.equals("Color...")) {
4635 IJ.doCommand("Color Picker...");
4636 } else if (command.equals("Revert")) {
4637 if (null == active || active.getClass() != Patch.class) return;
4638 Patch p = (Patch)active;
4639 if (!p.revert()) {
4640 if (null == p.getOriginalPath()) Utils.log("No editions to save for patch " + p.getTitle() + " #" + p.getId());
4641 else Utils.log("Could not revert Patch " + p.getTitle() + " #" + p.getId());
4643 } else if (command.equals("Remove alpha mask")) {
4644 Display.removeAlphaMasks(selection.get(Patch.class));
4645 } else if (command.equals("Undo")) {
4646 Bureaucrat.createAndStart(new Worker.Task("Undo") { public void exec() {
4647 layer.getParent().undoOneStep();
4648 Display.repaint(layer.getParent());
4649 }}, project);
4650 } else if (command.equals("Redo")) {
4651 Bureaucrat.createAndStart(new Worker.Task("Redo") { public void exec() {
4652 layer.getParent().redoOneStep();
4653 Display.repaint(layer.getParent());
4654 }}, project);
4655 } else if (command.equals("Apply transform")) {
4656 canvas.applyTransform();
4657 } else if (command.equals("Apply transform propagating to last layer")) {
4658 if (mode.getClass() == AffineTransformMode.class || mode.getClass() == NonLinearTransformMode.class) {
4659 final LayerSet ls = getLayerSet();
4660 final HashSet<Layer> subset = new HashSet<Layer>(ls.getLayers(ls.indexOf(Display.this.layer)+1, ls.size()-1)); // +1 to exclude current layer
4661 if (mode.getClass() == AffineTransformMode.class) ((AffineTransformMode)mode).applyAndPropagate(subset);
4662 else if (mode.getClass() == NonLinearTransformMode.class) ((NonLinearTransformMode)mode).apply(subset);
4663 setMode(new DefaultMode(Display.this));
4665 } else if (command.equals("Apply transform propagating to first layer")) {
4666 if (mode.getClass() == AffineTransformMode.class || mode.getClass() == NonLinearTransformMode.class) {
4667 final LayerSet ls = getLayerSet();
4668 final HashSet<Layer> subset = new HashSet<Layer>(ls.getLayers(0, ls.indexOf(Display.this.layer) -1)); // -1 to exclude current layer
4669 if (mode.getClass() == AffineTransformMode.class) ((AffineTransformMode)mode).applyAndPropagate(subset);
4670 else if (mode.getClass() == NonLinearTransformMode.class) ((NonLinearTransformMode)mode).apply(subset);
4671 setMode(new DefaultMode(Display.this));
4673 } else if (command.equals("Cancel transform")) {
4674 canvas.cancelTransform(); // calls getMode().cancel()
4675 } else if (command.equals("Specify transform...")) {
4676 if (null == active) return;
4677 selection.specify();
4678 } else if (command.equals("Exit inspection")) {
4679 getMode().cancel();
4680 setMode(new DefaultMode(Display.this));
4681 } else if (command.equals("Inspect image mesh triangles")) {
4682 setMode(new InspectPatchTrianglesMode(Display.this));
4683 } else if (command.equals("Hide all but images")) {
4684 ArrayList<Class<?>> type = new ArrayList<Class<?>>();
4685 type.add(Patch.class);
4686 type.add(Stack.class);
4687 Collection<Displayable> col = layer.getParent().hideExcept(type, false);
4688 selection.removeAll(col);
4689 Display.updateCheckboxes(col, DisplayablePanel.VISIBILITY_STATE);
4690 Display.update(layer.getParent(), false);
4691 } else if (command.equals("Unhide all")) {
4692 Display.updateCheckboxes(layer.getParent().setAllVisible(false), DisplayablePanel.VISIBILITY_STATE);
4693 Display.update(layer.getParent(), false);
4694 } else if (command.startsWith("Hide all ")) {
4695 String type = command.substring(9, command.length() -1); // skip the ending plural 's'
4696 Collection<Displayable> col = layer.getParent().setVisible(type, false, true);
4697 selection.removeAll(col);
4698 Display.updateCheckboxes(col, DisplayablePanel.VISIBILITY_STATE);
4699 } else if (command.startsWith("Unhide all ")) {
4700 String type = command.substring(11, command.length() -1); // skip the ending plural 's'
4701 type = type.substring(0, 1).toUpperCase() + type.substring(1);
4702 updateCheckboxes(layer.getParent().setVisible(type, true, true), DisplayablePanel.VISIBILITY_STATE);
4703 } else if (command.equals("Hide deselected")) {
4704 hideDeselected(0 != (ActionEvent.ALT_MASK & ae.getModifiers()));
4705 } else if (command.equals("Hide deselected except images")) {
4706 hideDeselected(true);
4707 } else if (command.equals("Hide selected")) {
4708 selection.setVisible(false); // TODO should deselect them too? I don't think so.
4709 Display.updateCheckboxes(selection.getSelected(), DisplayablePanel.VISIBILITY_STATE);
4710 } else if (command.equals("Resize canvas/LayerSet...")) {
4711 resizeCanvas();
4712 } else if (command.equals("Autoresize canvas/LayerSet")) {
4713 layer.getParent().setMinimumDimensions();
4714 } else if (command.equals("Resize canvas/LayerSet to ROI")) {
4715 Roi roi = canvas.getFakeImagePlus().getRoi();
4716 if (null == roi) {
4717 Utils.log("No ROI present!");
4718 return;
4720 resizeCanvas(roi.getBounds());
4721 } else if (command.equals("Import image")) {
4722 importImage();
4723 } else if (command.equals("Import next image")) {
4724 importNextImage();
4725 } else if (command.equals("Import stack...")) {
4726 Display.this.getLayerSet().addChangeTreesStep();
4727 Rectangle sr = getCanvas().getSrcRect();
4728 Bureaucrat burro = project.getLoader().importStack(layer, sr.x + sr.width/2, sr.y + sr.height/2, null, true, null, false);
4729 burro.addPostTask(new Runnable() { public void run() {
4730 Display.this.getLayerSet().addChangeTreesStep();
4731 }});
4732 } else if (command.equals("Import stack with landmarks...")) {
4733 // 1 - Find out if there's any other project open
4734 List<Project> pr = Project.getProjects();
4735 if (1 == pr.size()) {
4736 Utils.logAll("Need another project open!");
4737 return;
4739 // 2 - Ask for a "landmarks" type
4740 GenericDialog gd = new GenericDialog("Landmarks");
4741 gd.addStringField("landmarks type:", "landmarks");
4742 final String[] none = {"-- None --"};
4743 final Hashtable<String,Project> mpr = new Hashtable<String,Project>();
4744 for (Project p : pr) {
4745 if (p == project) continue;
4746 mpr.put(p.toString(), p);
4748 final String[] project_titles = mpr.keySet().toArray(new String[0]);
4750 final Hashtable<String,ProjectThing> map_target = findLandmarkNodes(project, "landmarks");
4751 String[] target_landmark_titles = map_target.isEmpty() ? none : map_target.keySet().toArray(new String[0]);
4752 gd.addChoice("Landmarks node in this project:", target_landmark_titles, target_landmark_titles[0]);
4754 gd.addMessage("");
4755 gd.addChoice("Source project:", project_titles, project_titles[0]);
4757 final Hashtable<String,ProjectThing> map_source = findLandmarkNodes(mpr.get(project_titles[0]), "landmarks");
4758 String[] source_landmark_titles = map_source.isEmpty() ? none : map_source.keySet().toArray(new String[0]);
4759 gd.addChoice("Landmarks node in source project:", source_landmark_titles, source_landmark_titles[0]);
4761 final List<Patch> stacks = Display.getPatchStacks(mpr.get(project_titles[0]).getRootLayerSet());
4763 String[] stack_titles;
4764 if (stacks.isEmpty()) {
4765 if (1 == mpr.size()) {
4766 IJ.showMessage("Project " + project_titles[0] + " does not contain any Stack.");
4767 return;
4769 stack_titles = none;
4770 } else {
4771 stack_titles = new String[stacks.size()];
4772 int next = 0;
4773 for (Patch pa : stacks) stack_titles[next++] = pa.toString();
4775 gd.addChoice("Stacks:", stack_titles, stack_titles[0]);
4777 Vector<?> vc = gd.getChoices();
4778 final Choice choice_target_landmarks = (Choice) vc.get(0);
4779 final Choice choice_source_projects = (Choice) vc.get(1);
4780 final Choice choice_source_landmarks = (Choice) vc.get(2);
4781 final Choice choice_stacks = (Choice) vc.get(3);
4783 final TextField input = (TextField) gd.getStringFields().get(0);
4784 input.addTextListener(new TextListener() {
4785 public void textValueChanged(TextEvent te) {
4786 final String text = input.getText();
4787 update(choice_target_landmarks, Display.this.project, text, map_target);
4788 update(choice_source_landmarks, mpr.get(choice_source_projects.getSelectedItem()), text, map_source);
4790 private void update(Choice c, Project p, String type, Hashtable<String,ProjectThing> table) {
4791 table.clear();
4792 table.putAll(findLandmarkNodes(p, type));
4793 c.removeAll();
4794 if (table.isEmpty()) c.add(none[0]);
4795 else for (String t : table.keySet()) c.add(t);
4799 choice_source_projects.addItemListener(new ItemListener() {
4800 public void itemStateChanged(ItemEvent e) {
4801 String item = (String) e.getItem();
4802 Project p = mpr.get(choice_source_projects.getSelectedItem());
4803 // 1 - Update choice of landmark items
4804 map_source.clear();
4805 map_source.putAll(findLandmarkNodes(p, input.getText()));
4806 choice_target_landmarks.removeAll();
4807 if (map_source.isEmpty()) choice_target_landmarks.add(none[0]);
4808 else for (String t : map_source.keySet()) choice_target_landmarks.add(t);
4809 // 2 - Update choice of Stack items
4810 stacks.clear();
4811 choice_stacks.removeAll();
4812 stacks.addAll(Display.getPatchStacks(mpr.get(project_titles[0]).getRootLayerSet()));
4813 if (stacks.isEmpty()) choice_stacks.add(none[0]);
4814 else for (Patch pa : stacks) choice_stacks.add(pa.toString());
4818 gd.showDialog();
4819 if (gd.wasCanceled()) return;
4821 String type = gd.getNextString();
4822 if (null == type || 0 == type.trim().length()) {
4823 Utils.log("Invalid landmarks node type!");
4824 return;
4826 ProjectThing target_landmarks_node = map_target.get(gd.getNextChoice());
4827 Project source = mpr.get(gd.getNextChoice());
4828 ProjectThing source_landmarks_node = map_source.get(gd.getNextChoice());
4829 Patch stack_patch = stacks.get(gd.getNextChoiceIndex());
4831 // Store current state
4832 Display.this.getLayerSet().addLayerContentStep(layer);
4834 // Insert stack
4835 insertStack(target_landmarks_node, source, source_landmarks_node, stack_patch);
4837 // Store new state
4838 Display.this.getLayerSet().addChangeTreesStep();
4839 } else if (command.equals("Import grid...")) {
4840 Display.this.getLayerSet().addLayerContentStep(layer);
4841 Bureaucrat burro = project.getLoader().importGrid(layer);
4842 if (null != burro)
4843 burro.addPostTask(new Runnable() { public void run() {
4844 Display.this.getLayerSet().addLayerContentStep(layer);
4845 }});
4846 } else if (command.equals("Import sequence as grid...")) {
4847 Display.this.getLayerSet().addChangeTreesStep();
4848 Bureaucrat burro = project.getLoader().importSequenceAsGrid(layer);
4849 if (null != burro)
4850 burro.addPostTask(new Runnable() { public void run() {
4851 Display.this.getLayerSet().addChangeTreesStep();
4852 }});
4853 } else if (command.equals("Import from text file...")) {
4854 Display.this.getLayerSet().addChangeTreesStep();
4855 Bureaucrat burro = project.getLoader().importImages(layer);
4856 if (null != burro)
4857 burro.addPostTask(new Runnable() { public void run() {
4858 Display.this.getLayerSet().addChangeTreesStep();
4859 }});
4860 } else if (command.equals("Import labels as arealists...")) {
4861 Display.this.getLayerSet().addChangeTreesStep();
4862 Bureaucrat burro = project.getLoader().importLabelsAsAreaLists(layer, null, Double.MAX_VALUE, 0, 0.4f, false);
4863 burro.addPostTask(new Runnable() { public void run() {
4864 Display.this.getLayerSet().addChangeTreesStep();
4865 }});
4866 } else if (command.equals("Make flat image...")) {
4867 // if there's a ROI, just use that as cropping rectangle
4868 Rectangle srcRect = null;
4869 Roi roi = canvas.getFakeImagePlus().getRoi();
4870 if (null != roi) {
4871 srcRect = roi.getBounds();
4872 } else {
4873 // otherwise, whatever is visible
4874 //srcRect = canvas.getSrcRect();
4875 // The above is confusing. That is what ROIs are for. So paint all:
4876 srcRect = new Rectangle(0, 0, (int)Math.ceil(layer.getParent().getLayerWidth()), (int)Math.ceil(layer.getParent().getLayerHeight()));
4878 double scale = 1.0;
4879 final String[] types = new String[]{"8-bit grayscale", "RGB Color"};
4880 int the_type = ImagePlus.GRAY8;
4881 final GenericDialog gd = new GenericDialog("Choose", frame);
4882 gd.addSlider("Scale: ", 1, 100, 100);
4883 gd.addNumericField("Width: ", srcRect.width, 0);
4884 gd.addNumericField("height: ", srcRect.height, 0);
4885 // connect the above 3 fields:
4886 Vector<?> numfields = gd.getNumericFields();
4887 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));
4888 for (Object ob : numfields) ((TextField)ob).addTextListener(udf);
4890 gd.addChoice("Type: ", types, types[0]);
4891 if (layer.getParent().size() > 1) {
4892 Utils.addLayerRangeChoices(Display.this.layer, gd); /// $#%! where are my lisp macros
4893 gd.addCheckbox("Include non-empty layers only", true);
4895 gd.addMessage("Background color:");
4896 Utils.addRGBColorSliders(gd, Color.black);
4897 gd.addCheckbox("Best quality", false);
4898 gd.addMessage("");
4899 final String[] choices = new String[]{"Show", "Save to file", "Save for web (CATMAID)"};
4900 gd.addChoice("Export:", choices, choices[0]);
4901 final String[] formats = Saver.formats();
4902 gd.addChoice("Format:", formats, formats[0]);
4903 gd.addNumericField("Tile_side", 256, 0);
4904 final Choice cformats = (Choice)gd.getChoices().get(gd.getChoices().size() -1);
4905 cformats.setEnabled(false);
4906 final Choice cchoices = (Choice)gd.getChoices().get(gd.getChoices().size() -2);
4907 final TextField tf = (TextField)gd.getNumericFields().get(gd.getNumericFields().size() -1);
4908 tf.setEnabled(false);
4909 cchoices.addItemListener(new ItemListener() {
4910 @Override
4911 public void itemStateChanged(ItemEvent e) {
4912 cformats.setEnabled(cchoices.getSelectedIndex() > 0);
4913 if (2 == cchoices.getSelectedIndex()) {
4914 cformats.select(".jpg");
4915 tf.setEnabled(true);
4916 } else {
4917 tf.setEnabled(false);
4921 gd.addCheckbox("Use original images", true);
4922 gd.showDialog();
4923 if (gd.wasCanceled()) return;
4925 scale = gd.getNextNumber() / 100;
4926 the_type = (0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY8 : ImagePlus.COLOR_RGB);
4927 if (Double.isNaN(scale) || scale <= 0.0) {
4928 Utils.showMessage("Invalid scale.");
4929 return;
4932 // consuming and ignoring width and height:
4933 gd.getNextNumber();
4934 gd.getNextNumber();
4936 Layer[] layer_array = null;
4937 boolean non_empty_only = false;
4938 if (layer.getParent().size() > 1) {
4939 non_empty_only = gd.getNextBoolean();
4940 int i_start = gd.getNextChoiceIndex();
4941 int i_end = gd.getNextChoiceIndex();
4942 ArrayList<Layer> al = new ArrayList<Layer>();
4943 ArrayList<ZDisplayable> al_zd = layer.getParent().getZDisplayables();
4944 ZDisplayable[] zd = new ZDisplayable[al_zd.size()];
4945 al_zd.toArray(zd);
4946 for (int i=i_start, j=0; i <= i_end; i++, j++) {
4947 Layer la = layer.getParent().getLayer(i);
4948 if (!la.isEmpty() || !non_empty_only) al.add(la); // checks both the Layer and the ZDisplayable objects in the parent LayerSet
4950 if (0 == al.size()) {
4951 Utils.showMessage("All layers are empty!");
4952 return;
4954 layer_array = new Layer[al.size()];
4955 al.toArray(layer_array);
4956 } else {
4957 layer_array = new Layer[]{Display.this.layer};
4959 final Color background = new Color((int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
4960 final boolean quality = gd.getNextBoolean();
4962 final int choice = gd.getNextChoiceIndex();
4963 final boolean save_to_file = 1 == choice;
4964 final boolean save_for_web = 2 == choice;
4965 final String format = gd.getNextChoice();
4966 final Saver saver = new Saver(format);
4967 final int tile_side = (int)gd.getNextNumber();
4968 final boolean use_original_images = gd.getNextBoolean();
4969 // in its own thread
4970 if (save_for_web) project.getLoader().makePrescaledTiles(layer_array, Patch.class, srcRect, scale, c_alphas, the_type, null, use_original_images, saver, tile_side);
4971 else project.getLoader().makeFlatImage(layer_array, srcRect, scale, c_alphas, the_type, save_to_file, format, quality, background);
4973 } else if (command.equals("Lock")) {
4974 selection.setLocked(true);
4975 Utils.revalidateComponent(tabs.getSelectedComponent());
4976 } else if (command.equals("Unlock")) {
4977 selection.setLocked(false);
4978 Utils.revalidateComponent(tabs.getSelectedComponent());
4979 } else if (command.equals("Properties...")) {
4980 switch(selection.getSelected().size()) {
4981 case 0:
4982 return;
4983 case 1:
4984 active.adjustProperties();
4985 break;
4986 default:
4987 adjustGroupProperties(selection.getSelected());
4988 break;
4990 updateSelection();
4991 } else if (command.equals("Show current 2D position in 3D")) {
4992 Point p = canvas.consumeLastPopupPoint();
4993 if (null == p) return;
4994 Display3D.addFatPoint("Current 2D Position", getLayerSet(), p.x, p.y, layer.getZ(), 10, Color.magenta);
4995 } else if (command.equals("Show layers as orthoslices in 3D")) {
4996 GenericDialog gd = new GenericDialog("Options");
4997 Roi roi = canvas.getFakeImagePlus().getRoi();
4998 Rectangle r = null == roi ? getLayerSet().get2DBounds() : roi.getBounds();
4999 gd.addMessage("ROI 2D bounds:");
5000 gd.addNumericField("x:", r.x, 0, 30, "pixels");
5001 gd.addNumericField("y:", r.y, 0, 30, "pixels");
5002 gd.addNumericField("width:", r.width, 0, 30, "pixels");
5003 gd.addNumericField("height:", r.height, 0, 30, "pixels");
5004 gd.addMessage("Layers to include:");
5005 Utils.addLayerRangeChoices(layer, gd);
5006 gd.addMessage("Constrain dimensions to:");
5007 gd.addNumericField("max width and height:", getLayerSet().getPixelsMaxDimension(), 0, 30, "pixels");
5008 gd.addMessage("Options:");
5009 final String[] types = {"Greyscale", "Color RGB"};
5010 gd.addChoice("Image type:", types, types[0]);
5011 gd.addCheckbox("Invert images", false);
5012 gd.showDialog();
5013 if (gd.wasCanceled()) return;
5014 int x = (int)gd.getNextNumber(),
5015 y = (int)gd.getNextNumber(),
5016 width = (int)gd.getNextNumber(),
5017 height = (int)gd.getNextNumber();
5018 final int first = gd.getNextChoiceIndex(),
5019 last = gd.getNextChoiceIndex();
5020 final List<Layer> layers = getLayerSet().getLayers(first, last);
5021 final int max_dim = Math.min((int)gd.getNextNumber(), Math.max(width, height));
5022 float scale = 1;
5023 if (max_dim < Math.max(width, height)) {
5024 scale = max_dim / (float)Math.max(width, height);
5026 final int type = 0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY8 : ImagePlus.COLOR_RGB;
5027 final boolean invert = gd.getNextBoolean();
5028 final LayerStack stack = new LayerStack(layers, new Rectangle(x, y, width, height), scale, type, Patch.class, max_dim, invert);
5029 Display3D.showOrthoslices(stack.getImagePlus(), "LayerSet [" + x + "," + y + "," + width + "," + height + "] " + first + "--" + last, x, y, scale, layers.get(0));
5030 } else if (command.equals("Align stack slices")) {
5031 if (getActive() instanceof Patch) {
5032 final Patch slice = (Patch)getActive();
5033 if (slice.isStack()) {
5034 // check linked group
5035 final HashSet hs = slice.getLinkedGroup(new HashSet());
5036 for (Iterator it = hs.iterator(); it.hasNext(); ) {
5037 if (it.next().getClass() != Patch.class) {
5038 Utils.showMessage("Images are linked to other objects, can't proceed to cross-correlate them."); // labels should be fine, need to check that
5039 return;
5042 final LayerSet ls = slice.getLayerSet();
5043 final HashSet<Displayable> linked = slice.getLinkedGroup(null);
5044 ls.addTransformStepWithData(linked);
5045 Bureaucrat burro = AlignTask.registerStackSlices((Patch)getActive()); // will repaint
5046 burro.addPostTask(new Runnable() { public void run() {
5047 ls.enlargeToFit(linked);
5048 // The current state when done
5049 ls.addTransformStepWithData(linked);
5050 }});
5051 } else {
5052 Utils.log("Align stack slices: selected image is not part of a stack.");
5055 } else if (command.equals("Align layers manually with landmarks")) {
5056 setMode(new ManualAlignMode(Display.this));
5057 } else if (command.equals("Align layers")) {
5058 Roi roi = canvas.getFakeImagePlus().getRoi();
5059 if (null != roi) {
5060 YesNoCancelDialog yn = new YesNoCancelDialog(frame, "Use ROI?", "Snapshot layers using the ROI bounds?\n" + roi.getBounds());
5061 if (yn.cancelPressed()) return;
5062 if (!yn.yesPressed()) {
5063 roi = null;
5066 final Layer la = layer; // caching, since scroll wheel may change it
5067 la.getParent().addTransformStep(la.getParent().getLayers());
5068 Bureaucrat burro = AlignLayersTask.alignLayersTask( la, null == roi ? null : roi.getBounds() );
5069 burro.addPostTask(new Runnable() { public void run() {
5070 getLayerSet().enlargeToFit(getLayerSet().getDisplayables(Patch.class));
5071 la.getParent().addTransformStep(la.getParent().getLayers());
5072 }});
5073 } else if (command.equals("Align multi-layer mosaic")) {
5074 final Layer la = layer; // caching, since scroll wheel may change it
5075 la.getParent().addTransformStep();
5076 Bureaucrat burro = AlignTask.alignMultiLayerMosaicTask( la, active instanceof Patch ? (Patch)active : null );
5077 burro.addPostTask(new Runnable() { public void run() {
5078 getLayerSet().enlargeToFit(getLayerSet().getDisplayables(Patch.class));
5079 la.getParent().addTransformStep();
5080 }});
5081 } else if (command.equals("Montage all images in this layer")) {
5082 final Layer la = layer;
5083 final List<Patch> patches = new ArrayList<Patch>( (List<Patch>) (List) la.getDisplayables(Patch.class, true));
5084 if (patches.size() < 2) {
5085 Utils.showMessage("Montage needs 2 or more visible images");
5086 return;
5088 final Collection<Displayable> col = la.getParent().addTransformStepWithDataForAll(Arrays.asList(new Layer[]{la}));
5090 // find any locked or selected patches
5091 final ArrayList<Patch> fixed = new ArrayList<Patch>();
5092 for (final Patch p : patches) {
5093 if (p.isLocked2() || selection.contains(p)) fixed.add(p);
5096 if (patches.size() == fixed.size()) {
5097 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.");
5098 return;
5101 Utils.log("Using " + fixed.size() + " image" + (fixed.size() == 1 ? "" : "s") + " as reference.");
5103 Bureaucrat burro = AlignTask.alignPatchesTask(patches, fixed);
5104 burro.addPostTask(new Runnable() { public void run() {
5105 getLayerSet().enlargeToFit(patches);
5106 la.getParent().addTransformStepWithData(col);
5107 }});
5108 } else if (command.equals("Montage selected images")) {
5109 final Layer la = layer;
5110 if (selection.getSelected(Patch.class).size() < 2) {
5111 Utils.showMessage("Montage needs 2 or more images selected");
5112 return;
5114 final Collection<Displayable> col = la.getParent().addTransformStepWithDataForAll(Arrays.asList(new Layer[]{la}));
5115 Bureaucrat burro = AlignTask.alignSelectionTask(selection);
5116 if (null == burro) return;
5117 burro.addPostTask(new Runnable() { public void run() {
5118 la.getParent().enlargeToFit(selection.getAffected());
5119 la.getParent().addTransformStepWithData(col);
5120 }});
5121 } else if (command.equals("Montage multiple layers")) {
5122 final GenericDialog gd = new GenericDialog("Choose range");
5123 Utils.addLayerRangeChoices(Display.this.layer, gd);
5124 gd.showDialog();
5125 if (gd.wasCanceled()) return;
5126 final List<Layer> layers = getLayerSet().getLayers(gd.getNextChoiceIndex(), gd.getNextChoiceIndex());
5127 final Collection<Displayable> col = getLayerSet().addTransformStepWithDataForAll(layers);
5128 Bureaucrat burro = AlignTask.montageLayersTask(layers);
5129 burro.addPostTask(new Runnable() { public void run() {
5130 Collection<Displayable> ds = new ArrayList<Displayable>();
5131 for (Layer la : layers) ds.addAll(la.getDisplayables(Patch.class));
5132 getLayerSet().enlargeToFit(ds);
5133 getLayerSet().addTransformStepWithData(col);
5134 }});
5135 } else if (command.equals("Properties ...")) { // NOTE the space before the dots, to distinguish from the "Properties..." command that works on Displayable objects.
5136 adjustProperties();
5137 } else if (command.equals("Adjust snapping parameters...")) {
5138 AlignTask.p_snap.setup("Snap");
5139 } else if (command.equals("Adjust fast-marching parameters...")) {
5140 Segmentation.fmp.setup();
5141 } else if (command.equals("Adjust arealist paint parameters...")) {
5142 AreaWrapper.PP.setup();
5143 } else if (command.equals("Fill ROI in alpha mask")) {
5144 if (active.getClass() == Patch.class) {
5145 ((Patch)active).keyPressed(new KeyEvent(getCanvas(), -1, System.currentTimeMillis(), 0, KeyEvent.VK_F, 'f'));
5147 } else if (command.equals("Fill inverse ROI in alpha mask")) {
5148 if (active.getClass() == Patch.class) {
5149 ((Patch)active).keyPressed(new KeyEvent(getCanvas(), -1, System.currentTimeMillis(), Event.SHIFT_MASK, KeyEvent.VK_F, 'f'));
5151 } else if (command.equals("Search...")) {
5152 Search.showWindow();
5153 } else if (command.equals("Select all")) {
5154 selection.selectAll();
5155 repaint(Display.this.layer, selection.getBox(), 0);
5156 } else if (command.equals("Select all visible")) {
5157 selection.selectAllVisible();
5158 repaint(Display.this.layer, selection.getBox(), 0);
5159 } else if (command.equals("Select all that match...")) {
5160 List<Displayable> ds = find();
5161 selection.selectAll(ds);
5162 Utils.showStatus("Added " + ds.size() + " to selection.");
5163 } else if (command.equals("Select none")) {
5164 Rectangle box = selection.getBox();
5165 selection.clear();
5166 repaint(Display.this.layer, box, 0);
5167 } else if (command.equals("Restore selection")) {
5168 selection.restore();
5169 } else if (command.equals("Select under ROI")) {
5170 Roi roi = canvas.getFakeImagePlus().getRoi();
5171 if (null == roi) return;
5172 selection.selectAll(roi, true);
5173 } else if (command.equals("Merge")) {
5174 Bureaucrat burro = Bureaucrat.create(new Worker.Task("Merging AreaLists") {
5175 public void exec() {
5176 ArrayList<Displayable> al_sel = selection.getSelected(AreaList.class);
5177 // put active at the beginning, to work as the base on which other's will get merged
5178 al_sel.remove(Display.this.active);
5179 al_sel.add(0, Display.this.active);
5180 Set<DoStep> dataedits = new HashSet<DoStep>();
5181 dataedits.add(new Displayable.DoEdit(Display.this.active).init(Display.this.active, new String[]{"data"}));
5182 getLayerSet().addChangeTreesStep(dataedits);
5183 AreaList ali = AreaList.merge(al_sel);
5184 if (null != ali) {
5185 // remove all but the first from the selection
5186 for (int i=1; i<al_sel.size(); i++) {
5187 Object ob = al_sel.get(i);
5188 if (ob.getClass() == AreaList.class) {
5189 selection.remove((Displayable)ob);
5192 selection.updateTransform(ali);
5193 repaint(ali.getLayerSet(), ali, 0);
5196 }, Display.this.project);
5197 burro.addPostTask(new Runnable() { public void run() {
5198 Set<DoStep> dataedits = new HashSet<DoStep>();
5199 dataedits.add(new Displayable.DoEdit(Display.this.active).init(Display.this.active, new String[]{"data"}));
5200 getLayerSet().addChangeTreesStep(dataedits);
5201 }});
5202 burro.goHaveBreakfast();
5203 } else if (command.equals("Reroot")) {
5204 if (!(active instanceof Tree<?>)) return;
5205 getLayerSet().addDataEditStep(active);
5206 if (((Tree)active).reRoot(((Tree)active).getLastVisited())) {
5207 getLayerSet().addDataEditStep(active);
5208 Display.repaint(getLayerSet());
5209 } else {
5210 getLayerSet().removeLastUndoStep();
5212 } else if (command.equals("Part subtree")) {
5213 if (!(active instanceof Tree<?>)) return;
5214 if (!Utils.check("Really part the subtree?")) return;
5215 LayerSet.DoChangeTrees step = getLayerSet().addChangeTreesStep();
5216 Set<DoStep> deps = new HashSet<DoStep>();
5217 deps.add(new Displayable.DoEdit(active).init(active, new String[]{"data"})); // I hate java
5218 step.addDependents(deps);
5219 List<ZDisplayable> ts = ((Tree)active).splitAt(((Tree)active).getLastVisited());
5220 if (null == ts) {
5221 getLayerSet().removeLastUndoStep();
5222 return;
5224 Displayable elder = Display.this.active;
5225 HashSet<DoStep> deps2 = new HashSet<DoStep>();
5226 for (ZDisplayable t : ts) {
5227 deps2.add(new Displayable.DoEdit(t).init(t, new String[]{"data"}));
5228 if (t == elder) continue;
5229 getLayerSet().add(t); // will change Display.this.active !
5230 project.getProjectTree().addSibling(elder, t);
5232 selection.clear();
5233 selection.selectAll(ts);
5234 selection.add(elder);
5235 LayerSet.DoChangeTrees step2 = getLayerSet().addChangeTreesStep();
5236 step2.addDependents(deps2);
5237 Display.repaint(getLayerSet());
5238 } else if (command.equals("Show tabular view")) {
5239 if (!(active instanceof Tree<?>)) return;
5240 ((Tree<?>)active).createMultiTableView();
5241 } else if (command.equals("Mark")) {
5242 if (!(active instanceof Tree<?>)) return;
5243 Point p = canvas.consumeLastPopupPoint();
5244 if (null == p) return;
5245 if (((Tree<?>)active).markNear(p.x, p.y, layer, canvas.getMagnification())) {
5246 Display.repaint(getLayerSet());
5248 } else if (command.equals("Clear marks (selected Trees)")) {
5249 for (Tree<?> t : selection.get(Tree.class)) {
5250 t.unmark();
5252 Display.repaint(getLayerSet());
5253 } else if (command.equals("Join")) {
5254 if (!(active instanceof Tree<?>)) return;
5255 final List<Tree<?>> tlines = (List<Tree<?>>) selection.get(active.getClass());
5256 if (((Tree)active).canJoin(tlines)) {
5257 int nNodes_active = ((Tree)active).getRoot().getSubtreeNodes().size();
5258 String warning = "";
5259 for (final Tree<?> t : tlines) {
5260 if (active == t) continue;
5261 if (null == t.getRoot()) {
5262 Utils.log("Removed empty tree #" + t.getId() + " from those to join.");
5263 tlines.remove(t);
5264 continue;
5266 if (t.getRoot().getSubtreeNodes().size() > nNodes_active) {
5267 warning = "\nWARNING joining into a tree that is not the largest!";
5268 break;
5271 if (!Utils.check("Join these " + tlines.size() + " trees into the tree " + active + " ?" + warning)) return;
5272 // Record current state
5273 Set<DoStep> dataedits = new HashSet<DoStep>(tlines.size());
5274 for (final Tree<?> tl : tlines) {
5275 dataedits.add(new Displayable.DoEdit(tl).init(tl, new String[]{"data"}));
5277 getLayerSet().addChangeTreesStep(dataedits);
5279 ((Tree)active).join(tlines);
5280 for (final Tree<?> tl : tlines) {
5281 if (tl == active) continue;
5282 tl.remove2(false);
5284 Display.repaint(getLayerSet());
5285 // Again, to record current state (just the joined tree this time)
5286 Set<DoStep> dataedits2 = new HashSet<DoStep>(1);
5287 dataedits2.add(new Displayable.DoEdit(active).init(active, new String[]{"data"}));
5288 getLayerSet().addChangeTreesStep(dataedits2);
5289 } else {
5290 Utils.showMessage("Can't do", "Only one tree is selected.\nSelect more than one tree to perform a join operation!");
5292 } else if (command.equals("Previous branch node or start")) {
5293 if (!(active instanceof Tree<?>)) return;
5294 Point p = canvas.consumeLastPopupPoint();
5295 if (null == p) return;
5296 center(((Treeline)active).findPreviousBranchOrRootPoint(p.x, p.y, layer, canvas));
5297 } else if (command.equals("Next branch node or end")) {
5298 if (!(active instanceof Tree<?>)) return;
5299 Point p = canvas.consumeLastPopupPoint();
5300 if (null == p) return;
5301 center(((Tree<?>)active).findNextBranchOrEndPoint(p.x, p.y, layer, canvas));
5302 } else if (command.equals("Root")) {
5303 if (!(active instanceof Tree<?>)) return;
5304 Point p = canvas.consumeLastPopupPoint();
5305 if (null == p) return;
5306 center(((Tree)active).createCoordinate(((Tree<?>)active).getRoot()));
5307 } else if (command.equals("Last added node")) {
5308 if (!(active instanceof Tree<?>)) return;
5309 center(((Treeline)active).getLastAdded());
5310 } else if (command.equals("Last edited node")) {
5311 if (!(active instanceof Tree<?>)) return;
5312 center(((Treeline)active).getLastEdited());
5313 } else if (command.equals("Reverse point order")) {
5314 if (!(active instanceof Pipe)) return;
5315 getLayerSet().addDataEditStep(active);
5316 ((Pipe)active).reverse();
5317 Display.repaint(Display.this.layer);
5318 getLayerSet().addDataEditStep(active);
5319 } else if (command.equals("View orthoslices")) {
5320 if (!(active instanceof Patch)) return;
5321 Display3D.showOrthoslices(((Patch)active));
5322 } else if (command.equals("View volume")) {
5323 if (!(active instanceof Patch)) return;
5324 Display3D.showVolume(((Patch)active));
5325 } else if (command.equals("Show in 3D")) {
5326 for (ZDisplayable zd : selection.get(ZDisplayable.class)) {
5327 Display3D.show(zd.getProject().findProjectThing(zd));
5329 // handle profile lists ...
5330 HashSet<ProjectThing> hs = new HashSet<ProjectThing>();
5331 for (Profile d : selection.get(Profile.class)) {
5332 ProjectThing profile_list = (ProjectThing)d.getProject().findProjectThing(d).getParent();
5333 if (!hs.contains(profile_list)) {
5334 Display3D.show(profile_list);
5335 hs.add(profile_list);
5338 } else if (command.equals("Snap")) {
5339 // Take the active if it's a Patch
5340 if (!(active instanceof Patch)) return;
5341 Display.snap((Patch)active);
5342 } else if (command.equals("Blend") || command.equals("Blend (selected images)...")) {
5343 HashSet<Patch> patches = new HashSet<Patch>(selection.get(Patch.class));
5344 if (patches.size() > 1) {
5345 GenericDialog gd = new GenericDialog("Blending");
5346 gd.addCheckbox("Respect current alpha mask", true);
5347 gd.showDialog();
5348 if (gd.wasCanceled()) return;
5349 Blending.blend(patches, gd.getNextBoolean());
5350 } else {
5351 IJ.log("Please select more than one overlapping image.");
5353 } else if (command.equals("Blend (layer-wise)...")) {
5354 GenericDialog gd = new GenericDialog("Blending");
5355 Utils.addLayerRangeChoices(Display.this.layer, gd);
5356 gd.addCheckbox("Respect current alpha mask", true);
5357 gd.addMessage("Filter:");
5358 gd.addStringField("Use only images whose title matches:", "", 30);
5359 gd.addCheckbox("Blend visible patches only", true);
5360 gd.showDialog();
5361 if (gd.wasCanceled()) return;
5362 final boolean respect_alpha_mask = gd.getNextBoolean();
5363 final String toMatch = gd.getNextString().trim();
5364 final String regex = 0 == toMatch.length() ? null : ".*" + toMatch + ".*";
5365 final boolean visible_only = gd.getNextBoolean();
5366 Blending.blendLayerWise(getLayerSet().getLayers(gd.getNextChoiceIndex(), gd.getNextChoiceIndex()),
5367 respect_alpha_mask,
5368 new Filter<Patch>() {
5369 @Override
5370 public final boolean accept(final Patch patch) {
5371 if (visible_only && !patch.isVisible()) return false;
5372 if (null == regex) return true;
5373 return patch.getTitle().matches(regex);
5376 } else if (command.equals("Montage")) {
5377 final Set<Displayable> affected = new HashSet<Displayable>(selection.getAffected());
5378 // make an undo step!
5379 final LayerSet ls = layer.getParent();
5380 ls.addTransformStepWithData(affected);
5381 Bureaucrat burro = AlignTask.alignSelectionTask( selection );
5382 burro.addPostTask(new Runnable() { public void run() {
5383 ls.enlargeToFit(affected);
5384 ls.addTransformStepWithData(affected);
5385 }});
5386 } else if (command.equals("Lens correction")) {
5387 final Layer la = layer;
5388 la.getParent().addDataEditStep(new HashSet<Displayable>(la.getParent().getDisplayables()));
5389 Bureaucrat burro = DistortionCorrectionTask.correctDistortionFromSelection( selection );
5390 burro.addPostTask(new Runnable() { public void run() {
5391 // no means to know which where modified and from which layers!
5392 la.getParent().addDataEditStep(new HashSet<Displayable>(la.getParent().getDisplayables()));
5393 }});
5394 } else if (command.equals("Link images...")) {
5395 GenericDialog gd = new GenericDialog("Options");
5396 gd.addMessage("Linking images to images (within their own layer only):");
5397 String[] options = {"all images to all images", "each image with any other overlapping image"};
5398 gd.addChoice("Link: ", options, options[1]);
5399 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"};
5400 gd.addChoice("Apply to: ", options2, options2[0]);
5401 gd.showDialog();
5402 if (gd.wasCanceled()) return;
5403 Layer lay = layer;
5404 final HashSet<Displayable> ds = new HashSet<Displayable>(lay.getParent().getDisplayables());
5405 lay.getParent().addDataEditStep(ds, new String[]{"data"});
5406 boolean overlapping_only = 1 == gd.getNextChoiceIndex();
5407 Collection<Displayable> coll = null;
5408 switch (gd.getNextChoiceIndex()) {
5409 case 0:
5410 coll = selection.getSelected(Patch.class);
5411 Patch.crosslink(coll, overlapping_only);
5412 break;
5413 case 1:
5414 coll = lay.getDisplayables(Patch.class);
5415 Patch.crosslink(coll, overlapping_only);
5416 break;
5417 case 2:
5418 coll = new ArrayList<Displayable>();
5419 for (final Layer la : lay.getParent().getLayers()) {
5420 Collection<Displayable> acoll = la.getDisplayables(Patch.class);
5421 Patch.crosslink(acoll, overlapping_only);
5422 coll.addAll(acoll);
5424 break;
5425 case 3:
5426 ArrayList<Layer> layers = lay.getParent().getLayers();
5427 Collection<Displayable> lc1 = layers.get(0).getDisplayables(Patch.class);
5428 if (lay == layers.get(0)) coll = lc1;
5429 for (int i=1; i<layers.size(); i++) {
5430 Collection<Displayable> lc2 = layers.get(i).getDisplayables(Patch.class);
5431 if (null == coll && Display.this.layer == layers.get(i)) coll = lc2;
5432 Collection<Displayable> both = new ArrayList<Displayable>();
5433 both.addAll(lc1);
5434 both.addAll(lc2);
5435 Patch.crosslink(both, overlapping_only);
5436 lc1 = lc2;
5438 break;
5440 if (null != coll) Display.updateCheckboxes(coll, DisplayablePanel.LINK_STATE, true);
5441 lay.getParent().addDataEditStep(ds);
5442 } else if (command.equals("Unlink all selected images")) {
5443 if (Utils.check("Really unlink selected images?")) {
5444 Collection<Displayable> ds = selection.getSelected(Patch.class);
5445 for (final Displayable d : ds) {
5446 d.unlink();
5448 Display.updateCheckboxes(ds, DisplayablePanel.LINK_STATE);
5450 } else if (command.equals("Unlink all")) {
5451 if (Utils.check("Really unlink all objects from all layers?")) {
5452 Collection<Displayable> ds = layer.getParent().getDisplayables();
5453 for (final Displayable d : ds) {
5454 d.unlink();
5456 Display.updateCheckboxes(ds, DisplayablePanel.LINK_STATE);
5458 } else if (command.equals("Calibration...")) {
5459 try {
5460 IJ.run(canvas.getFakeImagePlus(), "Properties...", "");
5461 Display.updateTitle(getLayerSet());
5462 project.getLayerTree().updateUILater(); // repaint layer names
5463 } catch (RuntimeException re) {
5464 Utils.log2("Calibration dialog canceled.");
5466 } else if (command.equals("Grid overlay...")) {
5467 if (null == gridoverlay) gridoverlay = new GridOverlay();
5468 gridoverlay.setup(canvas.getFakeImagePlus().getRoi());
5469 canvas.repaint(false);
5470 } else if (command.equals("Enhance contrast (selected images)...")) {
5471 final Layer la = layer;
5472 ArrayList<Displayable> selected = selection.getSelected(Patch.class);
5473 final HashSet<Displayable> ds = new HashSet<Displayable>(selected);
5474 la.getParent().addDataEditStep(ds);
5475 Displayable active = Display.this.getActive();
5476 Patch ref = active.getClass() == Patch.class ? (Patch)active : null;
5477 Bureaucrat burro = getProject().getLoader().enhanceContrast(selected, ref);
5478 burro.addPostTask(new Runnable() { public void run() {
5479 la.getParent().addDataEditStep(ds);
5480 }});
5481 } else if (command.equals("Enhance contrast layer-wise...")) {
5482 // ask for range of layers
5483 final GenericDialog gd = new GenericDialog("Choose range");
5484 Utils.addLayerRangeChoices(Display.this.layer, gd);
5485 gd.showDialog();
5486 if (gd.wasCanceled()) return;
5487 java.util.List<Layer> layers = layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1); // exclusive end
5488 final HashSet<Displayable> ds = new HashSet<Displayable>();
5489 for (final Layer l : layers) ds.addAll(l.getDisplayables(Patch.class));
5490 getLayerSet().addDataEditStep(ds);
5491 Bureaucrat burro = project.getLoader().enhanceContrast(layers);
5492 burro.addPostTask(new Runnable() { public void run() {
5493 getLayerSet().addDataEditStep(ds);
5494 }});
5495 } else if (command.equals("Adjust image filters (selected images)")) {
5496 if (selection.isEmpty() || !(active instanceof Patch)) return;
5497 FilterEditor.GUI(selection.get(Patch.class), (Patch)active);
5498 } else if (command.equals("Set Min and Max layer-wise...")) {
5499 Displayable active = getActive();
5500 double min = 0;
5501 double max = 0;
5502 if (null != active && active.getClass() == Patch.class) {
5503 min = ((Patch)active).getMin();
5504 max = ((Patch)active).getMax();
5506 final GenericDialog gd = new GenericDialog("Min and Max");
5507 gd.addMessage("Set min and max to all images in the layer range");
5508 Utils.addLayerRangeChoices(Display.this.layer, gd);
5509 gd.addNumericField("min: ", min, 2);
5510 gd.addNumericField("max: ", max, 2);
5511 gd.showDialog();
5512 if (gd.wasCanceled()) return;
5514 min = gd.getNextNumber();
5515 max = gd.getNextNumber();
5516 ArrayList<Displayable> al = new ArrayList<Displayable>();
5517 for (final Layer la : layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1)) { // exclusive end
5518 al.addAll(la.getDisplayables(Patch.class));
5520 final HashSet<Displayable> ds = new HashSet<Displayable>(al);
5521 getLayerSet().addDataEditStep(ds);
5522 Bureaucrat burro = project.getLoader().setMinAndMax(al, min, max);
5523 burro.addPostTask(new Runnable() { public void run() {
5524 getLayerSet().addDataEditStep(ds);
5525 }});
5526 } else if (command.equals("Set Min and Max (selected images)...")) {
5527 Displayable active = getActive();
5528 double min = 0;
5529 double max = 0;
5530 if (null != active && active.getClass() == Patch.class) {
5531 min = ((Patch)active).getMin();
5532 max = ((Patch)active).getMax();
5534 final GenericDialog gd = new GenericDialog("Min and Max");
5535 gd.addMessage("Set min and max to all selected images");
5536 gd.addNumericField("min: ", min, 2);
5537 gd.addNumericField("max: ", max, 2);
5538 gd.showDialog();
5539 if (gd.wasCanceled()) return;
5541 min = gd.getNextNumber();
5542 max = gd.getNextNumber();
5543 final HashSet<Displayable> ds = new HashSet<Displayable>(selection.getSelected(Patch.class));
5544 getLayerSet().addDataEditStep(ds);
5545 Bureaucrat burro = project.getLoader().setMinAndMax(selection.getSelected(Patch.class), min, max);
5546 burro.addPostTask(new Runnable() { public void run() {
5547 getLayerSet().addDataEditStep(ds);
5548 }});
5549 } else if (command.equals("Adjust min and max (selected images)...")) {
5550 adjustMinAndMaxGUI();
5551 } else if (command.equals("Mask image borders (layer-wise)...")) {
5552 final GenericDialog gd = new GenericDialog("Mask borders");
5553 Utils.addLayerRangeChoices(Display.this.layer, gd);
5554 gd.addMessage("Borders:");
5555 gd.addNumericField("left: ", 6, 2);
5556 gd.addNumericField("top: ", 6, 2);
5557 gd.addNumericField("right: ", 6, 2);
5558 gd.addNumericField("bottom: ", 6, 2);
5559 gd.showDialog();
5560 if (gd.wasCanceled()) return;
5561 Collection<Layer> layers = layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1);
5562 final HashSet<Displayable> ds = new HashSet<Displayable>();
5563 for (Layer l : layers) ds.addAll(l.getDisplayables(Patch.class));
5564 getLayerSet().addDataEditStep(ds);
5565 Bureaucrat burro = project.getLoader().maskBordersLayerWise(layers, (int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
5566 burro.addPostTask(new Runnable() { public void run() {
5567 getLayerSet().addDataEditStep(ds);
5568 }});
5569 } else if (command.equals("Mask image borders (selected images)...")) {
5570 final GenericDialog gd = new GenericDialog("Mask borders");
5571 gd.addMessage("Borders:");
5572 gd.addNumericField("left: ", 6, 2);
5573 gd.addNumericField("top: ", 6, 2);
5574 gd.addNumericField("right: ", 6, 2);
5575 gd.addNumericField("bottom: ", 6, 2);
5576 gd.showDialog();
5577 if (gd.wasCanceled()) return;
5578 Collection<Displayable> patches = selection.getSelected(Patch.class);
5579 final HashSet<Displayable> ds = new HashSet<Displayable>(patches);
5580 getLayerSet().addDataEditStep(ds);
5581 Bureaucrat burro = project.getLoader().maskBorders(patches, (int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
5582 burro.addPostTask(new Runnable() { public void run() {
5583 getLayerSet().addDataEditStep(ds);
5584 }});
5585 } else if (command.equals("Remove alpha masks (layer-wise)...")) {
5586 final GenericDialog gd = new GenericDialog("Remove alpha masks");
5587 Utils.addLayerRangeChoices(Display.this.layer, gd);
5588 gd.addCheckbox("Visible only", true);
5589 gd.showDialog();
5590 if (gd.wasCanceled()) return;
5591 Collection<Layer> layers = layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1);
5592 final boolean visible_only = gd.getNextBoolean();
5593 final Collection<Patch> patches = new ArrayList<Patch>();
5594 for (Layer l : layers) {
5595 patches.addAll((Collection<Patch>)(Collection)l.getDisplayables(Patch.class, visible_only));
5597 Display.removeAlphaMasks(patches);
5598 } else if (command.equals("Remove alpha masks (selected images)...")) {
5599 Display.removeAlphaMasks(selection.get(Patch.class));
5600 } else if (command.equals("Split images under polyline ROI")) {
5601 Roi roi = canvas.getFakeImagePlus().getRoi();
5602 if (null == roi) return;
5603 if (roi.getType() != Roi.POLYLINE) {
5604 Utils.showMessage("Need a polyline ROI, not just any ROI!");
5605 return;
5607 if (!Utils.check("Really split images under the polyline ROI?")) {
5608 return;
5610 // OK identify images whose contour intersects the ROI
5611 final Set<Displayable> col = new HashSet<Displayable>();
5612 PolygonRoi proi = (PolygonRoi)roi;
5613 int[] x = proi.getXCoordinates(),
5614 y = proi.getYCoordinates();
5615 Rectangle b = proi.getBounds();
5616 Polygon[] pols = new Polygon[proi.getNCoordinates() -1];
5617 for (int i=0; i<pols.length; i++) {
5618 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},
5619 new int[]{b.y + y[i], b.y + y[i], b.y + y[i+1], b.y + y[i+1]}, 4);
5621 for (final Patch p : getLayer().getAll(Patch.class)) {
5622 if (!p.isVisible()) continue;
5623 final Area a = p.getArea();
5624 for (int i=0; i<pols.length; i++) {
5625 Area c = new Area(pols[i]);
5626 c.intersect(a);
5627 if (M.isEmpty(c)) continue;
5628 // Else, add it:
5629 col.add(p);
5630 break;
5634 if (col.isEmpty()) {
5635 Utils.showMessage("No images intersect the ROI!");
5636 return;
5638 // Create the area that will be "one half"
5639 // and overlay it in the display, repaint, and ask for "yes/no" to continue.
5641 for (int i=1; i<proi.getNCoordinates(); i++) {
5642 for (int k=i+2; k<proi.getNCoordinates(); k++) { // skip the immediate next segment
5643 // check if the two segments intersect
5644 if (null != M.computeSegmentsIntersection(x[i-1], y[i-1], x[i], y[i],
5645 x[k-1], y[k-1], x[k], y[k])) {
5646 Utils.showMessage("Cannot split images with a polygon ROI that intersects itself!");
5647 return;
5651 final Area[] as = M.splitArea(new Area(getLayerSet().get2DBounds()), proi, getLayerSet().get2DBounds());
5652 Color[] c = new Color[]{Color.blue, Color.red};
5653 int i = 0;
5654 for (Area a : as) {
5655 //Utils.log2("Added overlay " + i + " with color " + c[i] + " and area " + AreaCalculations.area(a.getPathIterator(null)));
5656 getLayer().getOverlay().add(a, c[i++], null, true, false, 0.4f);
5658 Display.repaint(getLayer());
5660 final YesNoDialog yn = new YesNoDialog(frame, "Check", "Does the splitting match your expectations?\nPush 'yes' to split the images.", false);
5661 yn.setModal(false);
5662 for (WindowListener wl : yn.getWindowListeners()) yn.removeWindowListener(wl);
5663 yn.setClosingTask(new Runnable() {
5664 @Override
5665 public void run() {
5666 try {
5667 // Remove overlay shapes
5668 for (Area a : as) {
5669 getLayer().getOverlay().remove(a);
5671 if (!yn.yesPressed()) {
5672 Utils.log2("Pushed 'no'");
5673 return;
5675 // Split intersecting patches
5676 // Duplicate each intersecting patch, and assign a[0] to the original and a[1] to the copy, as mask.
5677 Bureaucrat.createAndStart(new Worker.Task("Spliting images") {
5678 public void exec() {
5679 final Roi r1 = new ShapeRoi(as[0]),
5680 r2 = new ShapeRoi(as[1]);
5681 ArrayList<Future<?>> fus = new ArrayList<Future<?>>();
5682 for (final Patch p : (Collection<Patch>)(Collection)col) {
5683 Patch copy = (Patch) p.clone(p.getProject(), false);
5684 p.addAlphaMask(r1, 0);
5685 copy.addAlphaMask(r2, 0);
5686 fus.add(p.updateMipMaps());
5687 fus.add(copy.updateMipMaps());
5688 p.getLayer().add(copy); // after submitting mipmaps, since it will get added to all Displays and repainted.
5690 Utils.wait(fus);
5692 }, project);
5693 } catch (Throwable t) {
5694 IJError.print(t);
5695 } finally {
5696 yn.dispose();
5697 Display.repaint(getLayer());
5701 yn.setVisible(true);
5702 } else if (command.equals("Duplicate")) {
5703 // only Patch and DLabel, i.e. Layer-only resident objects that don't exist in the Project Tree
5704 final HashSet<Class> accepted = new HashSet<Class>();
5705 accepted.add(Patch.class);
5706 accepted.add(DLabel.class);
5707 accepted.add(Stack.class);
5708 final ArrayList<Displayable> originals = new ArrayList<Displayable>();
5709 final ArrayList<Displayable> selected = selection.getSelected();
5710 for (final Displayable d : selected) {
5711 if (accepted.contains(d.getClass())) {
5712 originals.add(d);
5715 if (originals.size() > 0) {
5716 getLayerSet().addChangeTreesStep();
5717 for (final Displayable d : originals) {
5718 if (d instanceof ZDisplayable) {
5719 d.getLayerSet().add((ZDisplayable)d.clone());
5720 } else {
5721 d.getLayer().add(d.clone());
5724 getLayerSet().addChangeTreesStep();
5725 } else if (selected.size() > 0) {
5726 Utils.log("Can only duplicate images and text labels.\nDuplicate *other* objects in the Project Tree.\n");
5728 } else if (command.equals("Create subproject")) {
5729 // Choose a 2D rectangle
5730 Roi roi = canvas.getFakeImagePlus().getRoi();
5731 Rectangle bounds;
5732 if (null != roi) {
5733 if (!Utils.check("Use bounds as defined by the ROI:\n" + roi.getBounds() + " ?")) return;
5734 bounds = roi.getBounds();
5735 } else bounds = getLayerSet().get2DBounds();
5736 // Choose a layer range, and whether to ignore hidden images
5737 GenericDialog gd = new GenericDialog("Choose layer range");
5738 Utils.addLayerRangeChoices(layer, gd);
5739 gd.addCheckbox("Ignore hidden images", true);
5740 gd.showDialog();
5741 if (gd.wasCanceled()) return;
5742 Layer first = layer.getParent().getLayer(gd.getNextChoiceIndex());
5743 Layer last = layer.getParent().getLayer(gd.getNextChoiceIndex());
5744 boolean ignore_hidden_patches = gd.getNextBoolean();
5745 Project sub = getProject().createSubproject(bounds, first, last, ignore_hidden_patches);
5746 if (null == sub) {
5747 Utils.log("ERROR: failed to create subproject.");
5748 return;
5750 final LayerSet subls = sub.getRootLayerSet();
5751 Display.createDisplay(sub, subls.getLayer(0));
5752 } else if (command.startsWith("Image stack under selected Arealist")) {
5753 if (null == active || active.getClass() != AreaList.class) return;
5754 GenericDialog gd = new GenericDialog("Stack options");
5755 String[] types = {"8-bit", "16-bit", "32-bit", "RGB"};
5756 gd.addChoice("type:", types, types[0]);
5757 gd.addSlider("Scale: ", 1, 100, 100);
5758 gd.showDialog();
5759 if (gd.wasCanceled()) return;
5760 final int type;
5761 switch (gd.getNextChoiceIndex()) {
5762 case 0: type = ImagePlus.GRAY8; break;
5763 case 1: type = ImagePlus.GRAY16; break;
5764 case 2: type = ImagePlus.GRAY32; break;
5765 case 3: type = ImagePlus.COLOR_RGB; break;
5766 default: type = ImagePlus.GRAY8; break;
5768 ImagePlus imp = ((AreaList)active).getStack(type, gd.getNextNumber()/100);
5769 if (null != imp) imp.show();
5770 } else if (command.equals("Fly through selected Treeline/AreaTree")) {
5771 if (null == active || !(active instanceof Tree<?>)) return;
5772 Bureaucrat.createAndStart(new Worker.Task("Creating fly through", true) {
5773 public void exec() {
5774 GenericDialog gd = new GenericDialog("Fly through");
5775 gd.addNumericField("Width", 512, 0);
5776 gd.addNumericField("Height", 512, 0);
5777 String[] types = new String[]{"8-bit gray", "Color RGB"};
5778 gd.addChoice("Image type", types, types[0]);
5779 gd.addSlider("scale", 0, 100, 100);
5780 gd.addCheckbox("save to file", false);
5781 gd.showDialog();
5782 if (gd.wasCanceled()) return;
5783 int w = (int)gd.getNextNumber();
5784 int h = (int)gd.getNextNumber();
5785 int type = 0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY8 : ImagePlus.COLOR_RGB;
5786 double scale = gd.getNextNumber();
5787 if (w <=0 || h <=0) {
5788 Utils.log("Invalid width or height: " + w + ", " + h);
5789 return;
5791 if (0 == scale || Double.isNaN(scale)) {
5792 Utils.log("Invalid scale: " + scale);
5793 return;
5795 String dir = null;
5796 if (gd.getNextBoolean()) {
5797 DirectoryChooser dc = new DirectoryChooser("Target directory");
5798 dir = dc.getDirectory();
5799 if (null == dir) return; // canceled
5800 dir = Utils.fixDir(dir);
5802 ImagePlus imp = ((Tree<?>)active).flyThroughMarked(w, h, scale/100, type, dir);
5803 if (null == imp) {
5804 Utils.log("Mark a node first!");
5805 return;
5807 imp.show();
5809 }, project);
5810 } else if (command.startsWith("Arealists as labels")) {
5811 GenericDialog gd = new GenericDialog("Export labels");
5812 gd.addSlider("Scale: ", 1, 100, 100);
5813 final String[] options = {"All area list", "Selected area lists"};
5814 gd.addChoice("Export: ", options, options[0]);
5815 Utils.addLayerRangeChoices(layer, gd);
5816 gd.addCheckbox("Visible only", true);
5817 gd.showDialog();
5818 if (gd.wasCanceled()) return;
5819 final float scale = (float)(gd.getNextNumber() / 100);
5820 java.util.List<Displayable> al = (java.util.List<Displayable>)(0 == gd.getNextChoiceIndex() ? layer.getParent().getZDisplayables(AreaList.class) : selection.getSelectedSorted(AreaList.class));
5821 if (null == al) {
5822 Utils.log("No area lists found to export.");
5823 return;
5825 // 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?
5827 int first = gd.getNextChoiceIndex();
5828 int last = gd.getNextChoiceIndex();
5829 boolean visible_only = gd.getNextBoolean();
5830 if (-1 != command.indexOf("(amira)")) {
5831 AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, true, true);
5832 } else if (-1 != command.indexOf("(tif)")) {
5833 AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, false, false);
5835 } else if (command.equals("Project properties...")) {
5836 project.adjustProperties();
5837 } else if (command.equals("Release memory...")) {
5838 Bureaucrat.createAndStart(new Worker("Releasing memory") {
5839 public void run() {
5840 startedWorking();
5841 try {
5842 GenericDialog gd = new GenericDialog("Release Memory");
5843 int max = (int)(IJ.maxMemory() / 1000000);
5844 gd.addSlider("Megabytes: ", 0, max, max/2);
5845 gd.showDialog();
5846 if (!gd.wasCanceled()) {
5847 int n_mb = (int)gd.getNextNumber();
5848 project.getLoader().releaseToFit((long)n_mb*1000000);
5850 } catch (Throwable e) {
5851 IJError.print(e);
5852 } finally {
5853 finishedWorking();
5856 }, project);
5857 } else if (command.equals("Create sibling project with retiled layers")) {
5858 final GenericDialog gd = new GenericDialog("Export flattened layers");
5859 gd.addNumericField("Tile_width", 2048, 0);
5860 gd.addNumericField("Tile_height", 2048, 0);
5861 String[] types = new String[]{"16-bit", "RGB color"};
5862 gd.addChoice("Export_image_type", types, types[0]);
5863 gd.addCheckbox("Create mipmaps", true);
5864 gd.addNumericField("Number_of_threads_to_use", Runtime.getRuntime().availableProcessors(), 0);
5865 gd.showDialog();
5866 if (gd.wasCanceled()) return;
5868 DirectoryChooser dc = new DirectoryChooser("Choose target folder");
5869 final String folder = dc.getDirectory();
5870 if (null == folder) return;
5872 final int tileWidth = (int)gd.getNextNumber(),
5873 tileHeight = (int)gd.getNextNumber();
5874 if (tileWidth < 0 || tileHeight < 0) {
5875 Utils.showMessage("Invalid tile sizes: " + tileWidth + ", " + tileHeight);
5876 return;
5879 if (tileWidth != tileHeight) {
5880 if (!Utils.check("The tile width (" + tileWidth + ") differs from the tile height (" + tileHeight + ").\nContinue anyway?")) {
5881 return;
5885 final int imageType = 0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY16 : ImagePlus.COLOR_RGB;
5886 final boolean createMipMaps = gd.getNextBoolean();
5887 final int nThreads = (int)gd.getNextNumber();
5889 Bureaucrat.createAndStart(new Worker.Task("Export flattened sibling project") {
5890 @Override
5891 public void exec() {
5892 try {
5893 ProjectTiler.createRetiledSibling(
5894 project,
5895 folder,
5896 tileWidth,
5897 tileHeight,
5898 imageType,
5899 true,
5900 nThreads,
5901 createMipMaps);
5902 } catch (Throwable t) {
5903 Utils.showMessage("ERROR: " + t);
5904 IJError.print(t);
5907 }, project);
5909 } else if (command.equals("Flush image cache")) {
5910 Loader.releaseAllCaches();
5911 } else if (command.equals("Regenerate all mipmaps")) {
5912 project.getLoader().regenerateMipMaps(getLayerSet().getDisplayables(Patch.class));
5913 } else if (command.equals("Regenerate mipmaps (selected images)")) {
5914 project.getLoader().regenerateMipMaps(selection.getSelected(Patch.class));
5915 } else if (command.equals("Tags...")) {
5916 // get a file first
5917 File f = Utils.chooseFile(null, "tags", ".xml");
5918 if (null == f) return;
5919 if (!Utils.saveToFile(f, getLayerSet().exportTags())) {
5920 Utils.logAll("ERROR when saving tags to file " + f.getAbsolutePath());
5922 } else if (command.equals("Tags ...")) {
5923 String[] ff = Utils.selectFile("Import tags");
5924 if (null == ff) return;
5925 GenericDialog gd = new GenericDialog("Import tags");
5926 String[] modes = new String[]{"Append to current tags", "Replace current tags"};
5927 gd.addChoice("Import tags mode:", modes, modes[0]);
5928 gd.addMessage("Replacing current tags\nwill remove all tags\n from all nodes first!");
5929 gd.showDialog();
5930 if (gd.wasCanceled()) return;
5931 getLayerSet().importTags(new StringBuilder(ff[0]).append('/').append(ff[1]).toString(), 1 == gd.getNextChoiceIndex());
5932 } else if (command.equals("Connectivity graph...")) {
5933 Bureaucrat.createAndStart(new Worker.Task("Connectivity graph") {
5934 public void exec() {
5935 Graph.extractAndShowGraph(getLayerSet());
5937 }, getProject());
5938 } else if (command.equals("NeuroML...")) {
5939 GenericDialog gd = new GenericDialog("Export NeuroML");
5940 String[] a = new String[]{"NeuroML (arbors and synapses)", "MorphML (arbors)"};
5941 gd.addChoice("Type:", a, a[0]);
5942 String[] b = new String[]{"All treelines and areatrees", "Selected treelines and areatrees"};
5943 gd.addChoice("Export:", b, b[0]);
5944 gd.showDialog();
5945 if (gd.wasCanceled()) return;
5946 final int type = gd.getNextChoiceIndex();
5947 final int export = gd.getNextChoiceIndex();
5949 SaveDialog sd = new SaveDialog("Choose .mml file", null, ".mml");
5950 String filename = sd.getFileName();
5951 if (null == filename) return; // canceled
5952 final File f = new File(sd.getDirectory() + filename);
5954 Bureaucrat.createAndStart(new Worker.Task("Export NeuroML") {
5955 public void exec() {
5956 OutputStreamWriter w = null;
5957 try {
5958 final Set<Tree<?>> trees = new HashSet<Tree<?>>();
5959 Collection<? extends Displayable> ds = null;
5960 switch (export) {
5961 case 0:
5962 ds = getLayerSet().getZDisplayables();
5963 break;
5964 case 1:
5965 ds = selection.getSelected();
5966 break;
5968 for (final Displayable d : ds) {
5969 if (d.getClass() == Treeline.class || d.getClass() == AreaTree.class) {
5970 trees.add((Tree<?>)d);
5973 if (trees.isEmpty()) {
5974 Utils.showMessage("No trees to export!");
5975 return;
5978 w = new OutputStreamWriter(new BufferedOutputStream(new FileOutputStream(f), 65536), "8859_1"); // encoding in Latin 1 (for macosx not to mess around
5980 switch (type) {
5981 case 0:
5982 NeuroML.exportNeuroML(trees, w);
5983 break;
5984 case 1:
5985 NeuroML.exportMorphML(trees, w);
5986 break;
5989 w.flush();
5990 w.close();
5992 } catch (Throwable t) {
5993 IJError.print(t);
5994 try {
5995 if (null != w) w.close();
5996 } catch (Exception ee) { IJError.print(ee); }
5999 }, getProject());
6000 } else if (command.equals("Measure")) {
6001 if (selection.isEmpty()) {
6002 Utils.log("Nothing selected to measure!");
6003 return;
6005 selection.measure();
6006 } else if (command.equals("Area interpolation options...")) {
6007 GenericDialog gd = new GenericDialog("Area interpolation");
6008 boolean a = project.getBooleanProperty(AreaUtils.always_interpolate_areas_with_distance_map);
6009 gd.addCheckbox("Always use distance map method", a);
6010 gd.showDialog();
6011 if (gd.wasCanceled()) return;
6012 project.setProperty(AreaUtils.always_interpolate_areas_with_distance_map, gd.getNextBoolean() ? "true" : null);
6013 } else {
6014 Utils.log2("Display: don't know what to do with command " + command);
6016 }});
6019 public void adjustMinAndMaxGUI() {
6020 final List<Displayable> list = selection.getSelected(Patch.class);
6021 if (list.isEmpty()) {
6022 Utils.log("No images selected!");
6023 return;
6025 Bureaucrat.createAndStart(new Worker.Task("Init contrast adjustment") {
6026 public void exec() {
6027 try {
6028 setMode(new ContrastAdjustmentMode(Display.this, list));
6029 } catch (Exception e) {
6030 e.printStackTrace();
6031 Utils.log("All images must be of the same type!");
6034 }, list.get(0).getProject());
6037 /** Pops up a dialog to adjust the alpha, visible, color, locked and compositeMode of all Displayables in {@param col}.
6039 * @param col
6041 public void adjustGroupProperties(final Collection<Displayable> col) {
6042 if (col.isEmpty()) return;
6043 final Displayable first = col.iterator().next();
6044 final GenericDialog gd = new GenericDialog("Properties of selected");
6045 gd.addSlider("alpha: ", 0, 100, (int)(first.getAlpha()*100));
6046 gd.addCheckbox("visible", first.isVisible());
6047 gd.addSlider("Red: ", 0, 255, first.getColor().getRed());
6048 gd.addSlider("Green: ", 0, 255, first.getColor().getGreen());
6049 gd.addSlider("Blue: ", 0, 255, first.getColor().getBlue());
6050 gd.addCheckbox("locked", first.isLocked2());
6051 gd.addChoice( "composite mode: ", Displayable.compositeModes, Displayable.compositeModes[ first.getCompositeMode() ] );
6052 gd.showDialog();
6054 if (gd.wasCanceled()) return;
6056 final HashSet<Displayable> hs = new HashSet<Displayable>(col);
6057 final String[] fields = new String[]{"alpha", "visible", "color", "locked", "compositeMode"};
6058 getLayerSet().addDataEditStep(hs, fields);
6060 float alpha = (float)gd.getNextNumber();
6061 boolean visible = gd.getNextBoolean();
6062 Color color = new Color((int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
6063 boolean locked = gd.getNextBoolean();
6064 byte compositeMode = (byte)gd.getNextChoiceIndex();
6066 for (final Displayable d : col) {
6067 d.setAlpha(alpha);
6068 d.setVisible(visible);
6069 d.setColor(color);
6070 d.setLocked(locked);
6071 d.setCompositeMode(compositeMode);
6074 // Record the current state
6075 getLayerSet().addDataEditStep(hs, fields);
6078 public void adjustProperties() {
6079 GenericDialog gd = new GenericDialog("Properties", Display.this.frame);
6080 //gd.addNumericField("layer_scroll_step: ", this.scroll_step, 0);
6081 gd.addSlider("layer_scroll_step: ", 1, layer.getParent().size(), Display.this.scroll_step);
6082 gd.addChoice("snapshots_mode", LayerSet.snapshot_modes, LayerSet.snapshot_modes[layer.getParent().getSnapshotsMode()]);
6083 gd.addCheckbox("prefer_snapshots_quality", layer.getParent().snapshotsQuality());
6084 Loader lo = getProject().getLoader();
6085 boolean using_mipmaps = lo.isMipMapsRegenerationEnabled();
6086 gd.addCheckbox("enable_mipmaps", using_mipmaps);
6087 gd.addCheckbox("enable_layer_pixels virtualization", layer.getParent().isPixelsVirtualizationEnabled());
6088 double max = layer.getParent().getLayerWidth() < layer.getParent().getLayerHeight() ? layer.getParent().getLayerWidth() : layer.getParent().getLayerHeight();
6089 gd.addSlider("max_dimension of virtualized layer pixels: ", 0, max, layer.getParent().getPixelsMaxDimension());
6090 gd.addCheckbox("Show arrow heads in Treeline/AreaTree", layer.getParent().paint_arrows);
6091 gd.addCheckbox("Show edge confidence boxes in Treeline/AreaTree", layer.getParent().paint_edge_confidence_boxes);
6092 gd.addCheckbox("Show color cues", layer.getParent().color_cues);
6093 gd.addSlider("+/- layers to color cue", 0, 10, layer.getParent().n_layers_color_cue);
6094 gd.addCheckbox("Show color cues for areas", layer.getParent().area_color_cues);
6095 gd.addCheckbox("Use red/blue for color cues", layer.getParent().use_color_cue_colors);
6096 gd.addCheckbox("Prepaint images", layer.getParent().prepaint);
6097 gd.addSlider("Preload ahead from sections: ", 0, layer.getParent().size(), layer.getParent().preload_ahead);
6098 // --------
6099 gd.showDialog();
6100 if (gd.wasCanceled()) return;
6101 // --------
6102 int sc = (int) gd.getNextNumber();
6103 if (sc < 1) sc = 1;
6104 Display.this.scroll_step = sc;
6105 updateInDatabase("scroll_step");
6107 layer.getParent().setSnapshotsMode(gd.getNextChoiceIndex());
6108 layer.getParent().setSnapshotsQuality(gd.getNextBoolean());
6110 boolean using_mipmaps2 = gd.getNextBoolean();
6111 if (using_mipmaps2 == using_mipmaps) {
6112 // Nothing changed
6113 } else if (!using_mipmaps2 && using_mipmaps) {
6114 // Desactivate mipmaps
6115 lo.setMipMapsRegeneration(false);
6116 lo.flushMipMaps(true);
6117 } else if (using_mipmaps2 && !using_mipmaps) {
6118 // Reactivate mipmaps
6119 lo.setMipMapsRegeneration(true);
6120 lo.generateMipMaps(layer.getParent().getDisplayables(Patch.class));
6123 layer.getParent().setPixelsVirtualizationEnabled(gd.getNextBoolean());
6124 layer.getParent().setPixelsMaxDimension((int)gd.getNextNumber());
6125 layer.getParent().paint_arrows = gd.getNextBoolean();
6126 layer.getParent().paint_edge_confidence_boxes = gd.getNextBoolean();
6127 layer.getParent().color_cues = gd.getNextBoolean();
6128 layer.getParent().n_layers_color_cue = (int)gd.getNextNumber();
6129 layer.getParent().area_color_cues = gd.getNextBoolean();
6130 layer.getParent().use_color_cue_colors = gd.getNextBoolean();
6131 layer.getParent().prepaint = gd.getNextBoolean();
6132 layer.getParent().preload_ahead = (int) Math.min(gd.getNextNumber(), layer.getParent().size());
6133 Display.repaint(layer.getParent());
6136 private static class UpdateDimensionField implements TextListener {
6137 final TextField width, height, scale;
6138 final Scrollbar bar;
6139 final int initial_width, initial_height;
6140 UpdateDimensionField(int initial_width, int initial_height, TextField width, TextField height, TextField scale, Scrollbar bar) {
6141 this.initial_width = initial_width;
6142 this.initial_height = initial_height;
6143 this.width = width;
6144 this.height = height;
6145 this.scale = scale;
6146 this.bar = bar;
6148 public void textValueChanged(TextEvent e) {
6149 try {
6150 final TextField source = (TextField) e.getSource();
6151 if (scale == source && (scale.isFocusOwner() || bar.isFocusOwner())) {
6152 final double sc = Double.parseDouble(scale.getText()) / 100;
6153 // update both
6154 width.setText(Integer.toString((int) (sc * initial_width + 0.5)));
6155 height.setText(Integer.toString((int) (sc * initial_height + 0.5)));
6156 } else if (width == source && width.isFocusOwner()) {
6158 final int width = Integer.toString((int) (width.getText() + 0.5));
6159 final double sc = width / (double)initial_width;
6160 scale.setText(Integer.toString((int)(sc * 100 + 0.5)));
6161 height.setText(Integer.toString((int)(sc * initial_height + 0.5)));
6163 set(width, height, initial_width, initial_height);
6164 } else if (height == source && height.isFocusOwner()) {
6165 set(height, width, initial_height, initial_width);
6167 } catch (NumberFormatException nfe) {
6168 Utils.logAll("Unparsable number: " + nfe.getMessage());
6169 } catch (Exception ee) {
6170 IJError.print(ee);
6173 private void set(TextField source, TextField target, int initial_source, int initial_target) {
6174 final int dim = (int) ((Double.parseDouble(source.getText()) + 0.5));
6175 final double sc = dim / (double)initial_source;
6176 scale.setText(Utils.cutNumber(sc * 100, 3));
6177 target.setText(Integer.toString((int)(sc * initial_target + 0.5)));
6182 /** Update in all displays the Transform for the given Displayable if it's selected. */
6183 static public void updateTransform(final Displayable displ) {
6184 for (final Display d : al_displays) {
6185 if (d.selection.contains(displ)) d.selection.updateTransform(displ);
6189 /** Order the profiles of the parent profile_list by Z order, and fix the ProjectTree.*/
6191 private void fixZOrdering(Profile profile) {
6192 ProjectThing thing = project.findProjectThing(profile);
6193 if (null == thing) {
6194 Utils.log2("Display.fixZOrdering: null thing?");
6195 return;
6197 ((ProjectThing)thing.getParent()).fixZOrdering();
6198 project.getProjectTree().updateList(thing.getParent());
6202 /** The number of layers to scroll through with the wheel; 1 by default.*/
6203 public int getScrollStep() { return this.scroll_step; }
6205 public void setScrollStep(int scroll_step) {
6206 if (scroll_step < 1) scroll_step = 1;
6207 this.scroll_step = scroll_step;
6208 updateInDatabase("scroll_step");
6211 protected Bureaucrat importImage() {
6212 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
6213 public void run() {
6214 startedWorking();
6215 try {
6218 Rectangle srcRect = canvas.getSrcRect();
6219 int x = srcRect.x + srcRect.width / 2;
6220 int y = srcRect.y + srcRect.height/ 2;
6221 Patch p = project.getLoader().importImage(project, x, y);
6222 if (null == p) {
6223 finishedWorking();
6224 Utils.showMessage("Could not open the image.");
6225 return;
6228 Display.this.getLayerSet().addLayerContentStep(layer);
6230 layer.add(p); // will add it to the proper Displays
6232 Display.this.getLayerSet().addLayerContentStep(layer);
6235 } catch (Exception e) {
6236 IJError.print(e);
6238 finishedWorking();
6241 return Bureaucrat.createAndStart(worker, getProject());
6244 protected Bureaucrat importNextImage() {
6245 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
6246 public void run() {
6247 startedWorking();
6248 try {
6250 Rectangle srcRect = canvas.getSrcRect();
6251 int x = srcRect.x + srcRect.width / 2;// - imp.getWidth() / 2;
6252 int y = srcRect.y + srcRect.height/ 2;// - imp.getHeight()/ 2;
6253 Patch p = project.getLoader().importNextImage(project, x, y);
6254 if (null == p) {
6255 Utils.showMessage("Could not open next image.");
6256 finishedWorking();
6257 return;
6260 Display.this.getLayerSet().addLayerContentStep(layer);
6262 layer.add(p); // will add it to the proper Displays
6264 Display.this.getLayerSet().addLayerContentStep(layer);
6266 } catch (Exception e) {
6267 IJError.print(e);
6269 finishedWorking();
6272 return Bureaucrat.createAndStart(worker, getProject());
6276 /** Make the given channel have the given alpha (transparency). */
6277 public void setChannel(int c, float alpha) {
6278 int a = (int)(255 * alpha);
6279 int l = (c_alphas&0xff000000)>>24;
6280 int r = (c_alphas&0xff0000)>>16;
6281 int g = (c_alphas&0xff00)>>8;
6282 int b = c_alphas&0xff;
6283 switch (c) {
6284 case Channel.MONO:
6285 // all to the given alpha
6286 c_alphas = (l<<24) + (r<<16) + (g<<8) + b; // parenthesis are NECESSARY
6287 break;
6288 case Channel.RED:
6289 // modify only the red
6290 c_alphas = (l<<24) + (a<<16) + (g<<8) + b;
6291 break;
6292 case Channel.GREEN:
6293 c_alphas = (l<<24) + (r<<16) + (a<<8) + b;
6294 break;
6295 case Channel.BLUE:
6296 c_alphas = (l<<24) + (r<<16) + (g<<8) + a;
6297 break;
6299 //Utils.log2("c_alphas: " + c_alphas);
6300 //canvas.setUpdateGraphics(true);
6301 canvas.repaint(true);
6302 updateInDatabase("c_alphas");
6305 /** Set the channel as active and the others as inactive. */
6306 public void setActiveChannel(Channel channel) {
6307 for (int i=0; i<4; i++) {
6308 if (channel != channels[i]) channels[i].setActive(false);
6309 else channel.setActive(true);
6311 Utils.updateComponent(panel_channels);
6312 transp_slider.setValue((int)(channel.getAlpha() * 100));
6315 public int getDisplayChannelAlphas() { return c_alphas; }
6317 // rename this method and the getDisplayChannelAlphas ! They sound the same!
6318 public int getChannelAlphas() {
6319 return ((int)(channels[0].getAlpha() * 255)<<24) + ((int)(channels[1].getAlpha() * 255)<<16) + ((int)(channels[2].getAlpha() * 255)<<8) + (int)(channels[3].getAlpha() * 255);
6322 public int getChannelAlphasState() {
6323 return ((channels[0].isSelected() ? 255 : 0)<<24)
6324 + ((channels[1].isSelected() ? 255 : 0)<<16)
6325 + ((channels[2].isSelected() ? 255 : 0)<<8)
6326 + (channels[3].isSelected() ? 255 : 0);
6329 /** Show the layer in the front Display, or in a new Display if the front Display is showing a layer from a different LayerSet. */
6330 static public void showFront(final Layer layer) {
6331 Display display = front;
6332 if (null == display || display.layer.getParent() != layer.getParent()) {
6333 display = new Display(layer.getProject(), layer, null); // gets set to front
6334 } else {
6335 display.setLayer(layer);
6339 /** Show the given Displayable centered and selected. If select is false, the selection is cleared. */
6340 static public void showCentered(Layer layer, Displayable displ, boolean select, boolean shift_down) {
6341 // see if the given layer belongs to the layer set being displayed
6342 Display display = front; // to ensure thread consistency to some extent
6343 if (null == display || display.layer.getParent() != layer.getParent()) {
6344 display = new Display(layer.getProject(), layer, displ); // gets set to front
6346 display.show(layer, displ, select, shift_down);
6348 /** Set this Display to show the specific layer, centered at the @param displ, and perhaps selected,
6349 * adding to the selection instead of clearing it if @param shift_down is true. */
6350 public void show(Layer layer, Displayable displ, boolean select, boolean shift_down) {
6351 if (this.layer != layer) {
6352 setLayer(layer);
6354 if (select) {
6355 if (!shift_down) selection.clear();
6356 selection.add(displ);
6357 } else {
6358 selection.clear();
6360 showCentered(displ);
6363 /** 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. */
6364 public final void center(final double x, final double y) {
6365 Utils.invokeLater(new Runnable() { public void run() {
6366 Rectangle r = (Rectangle)canvas.getSrcRect().clone();
6367 r.x = (int)x - r.width/2;
6368 r.y = (int)y - r.height/2;
6369 canvas.center(r, canvas.getMagnification());
6370 }});
6373 public final void center(final Coordinate<?> c) {
6374 if (null == c) return;
6375 slt.set(c.layer);
6376 center(c.x, c.y);
6379 public final void centerIfNotWithinSrcRect(final Coordinate<?> c) {
6380 if (null == c) return;
6381 slt.set(c.layer);
6382 Rectangle srcRect = canvas.getSrcRect();
6383 if (srcRect.contains((int)(c.x+0.5), (int)(c.y+0.5))) return;
6384 center(c.x, c.y);
6387 public final void animateBrowsingTo(final Coordinate<?> c) {
6388 if (null == c) return;
6389 final double padding = 50/canvas.getMagnification(); // 50 screen pixels
6390 canvas.animateBrowsing(new Rectangle((int)(c.x - padding), (int)(c.y - padding), (int)(2*padding), (int)(2*padding)), c.layer);
6393 static public final void centerAt(final Coordinate c) {
6394 centerAt(c, false, false);
6396 static public final void centerAt(final Coordinate<Displayable> c, final boolean select, final boolean shift_down) {
6397 if (null == c) return;
6398 Utils.invokeLater(new Runnable() { public void run() {
6399 Layer la = c.layer;
6400 if (null == la) {
6401 if (null == c.object) return;
6402 la = c.object.getProject().getRootLayerSet().getLayer(0);
6403 if (null == la) return; // nothing to center on
6405 Display display = front;
6406 if (null == display || la.getParent() != display.getLayerSet()) {
6407 display = new Display(la.getProject(), la); // gets set to front
6409 display.center(c);
6411 if (select) {
6412 if (!shift_down) display.selection.clear();
6413 display.selection.add(c.object);
6415 }});
6418 private final void showCentered(final Displayable displ) {
6419 if (null == displ) return;
6420 Utils.invokeLater(new Runnable() { public void run() {
6421 displ.setVisible(true);
6422 Rectangle box = displ.getBoundingBox();
6424 if (0 == box.width && 0 == box.height) {
6425 box.width = 100; // old: (int)layer.getLayerWidth();
6426 box.height = 100; // old: (int)layer.getLayerHeight();
6427 } else if (0 == box.width) {
6428 box.width = box.height;
6429 } else if (0 == box.height) {
6430 box.height = box.width;
6433 canvas.showCentered(box);
6434 ht_tabs.get(displ.getClass()).scrollToShow(displ);
6435 if (displ instanceof ZDisplayable) {
6436 // scroll to first layer that has a point
6437 ZDisplayable zd = (ZDisplayable)displ;
6438 setLayer(zd.getFirstLayer());
6440 }});
6443 public void eventOccurred(final int eventID) {
6444 if (IJEventListener.FOREGROUND_COLOR_CHANGED == eventID) {
6445 if (this != front || null == active || !project.isInputEnabled()) return;
6446 selection.setColor(Toolbar.getForegroundColor());
6447 Display.repaint(front.layer, selection.getBox(), 0);
6448 } else if (IJEventListener.TOOL_CHANGED == eventID) {
6449 Display.repaintToolbar();
6453 public void imageClosed(ImagePlus imp) {}
6454 public void imageOpened(ImagePlus imp) {}
6456 /** Release memory captured by the offscreen images */
6457 static public void flushAll() {
6458 for (final Display d : al_displays) {
6459 d.canvas.flush();
6461 //System.gc();
6462 Thread.yield();
6465 /** Can be null. */
6466 static public Display getFront() {
6467 Collection<Display> ds = al_displays;
6468 if (null == front && ds.size() > 0) {
6469 // Should never happen, this is a safety net
6470 Utils.log2("Fixing error with null 'Display.getFront()'");
6471 Display d = ds.iterator().next();
6472 d.frame.toFront();
6473 front = d;
6475 return front;
6478 static public Display getOrCreateFront(final Project project) {
6479 Display df = front;
6480 if (null != df && df.project == project) return df;
6481 for (final Display d : al_displays) {
6482 if (d.project == project) {
6483 d.frame.toFront();
6484 return d;
6487 LayerSet ls = project.getRootLayerSet();
6488 if (0 == ls.size()) return null;
6489 return new Display(project, ls.getLayer(0)); // sets it to front
6492 static public void setCursorToAll(final Cursor c) {
6493 for (final Display d : al_displays) {
6494 if (null != d.frame) d.frame.setCursor(c);
6498 /** Used by the Displayable to update the visibility and locking state checkboxes in other Displays. */
6499 static public void updateCheckboxes(final Displayable displ, final int cb, final boolean state) {
6500 for (final Display d : al_displays) {
6501 final DisplayablePanel dp = d.ht_panels.get(displ);
6502 if (null != dp) {
6503 Utils.invokeLater(new Runnable() { public void run() {
6504 dp.updateCheckbox(cb, state);
6505 }});
6509 /** Set the checkbox @param cb state to @param state value, for each Displayable. Assumes all Displayable objects belong to one specific project. */
6510 static public void updateCheckboxes(final Collection<Displayable> displs, final int cb, final boolean state) {
6511 if (null == displs || 0 == displs.size()) return;
6512 final Project p = displs.iterator().next().getProject();
6513 for (final Display d : al_displays) {
6514 if (d.getProject() != p) continue;
6515 Utils.invokeLater(new Runnable() { public void run() {
6516 for (final Displayable displ : displs) {
6517 DisplayablePanel dp = d.ht_panels.get(displ);
6518 if (null != dp) {
6519 dp.updateCheckbox(cb, state);
6522 }});
6525 /** Update the checkbox @param cb state to an appropriate value for each Displayable. Assumes all Displayable objects belong to one specific project. */
6526 static public void updateCheckboxes(final Collection<Displayable> displs, final int cb) {
6527 if (null == displs || 0 == displs.size()) return;
6528 final Project p = displs.iterator().next().getProject();
6529 for (final Display d : al_displays) {
6530 if (d.getProject() != p) continue;
6531 Utils.invokeLater(new Runnable() { public void run() {
6532 for (final Displayable displ : displs) {
6533 DisplayablePanel dp = d.ht_panels.get(displ);
6534 if (null != dp) {
6535 dp.updateCheckbox(cb);
6538 }});
6542 protected boolean isActiveWindow() {
6543 return frame.isActive();
6546 /** Toggle user input; pan and zoom are always enabled though.*/
6547 static public void setReceivesInput(final Project project, final boolean b) {
6548 for (final Display d : al_displays) {
6549 if (d.project == project) d.canvas.setReceivesInput(b);
6553 /** Export the DTD that defines this object. */
6554 static public void exportDTD(final StringBuilder sb_header, final HashSet<String> hs, final String indent) {
6555 if (hs.contains("t2_display")) return; // TODO to avoid collisions the type shoud be in a namespace such as tm2:display
6556 hs.add("t2_display");
6557 sb_header.append(indent).append("<!ELEMENT t2_display EMPTY>\n")
6558 .append(indent).append("<!ATTLIST t2_display id NMTOKEN #REQUIRED>\n")
6559 .append(indent).append("<!ATTLIST t2_display layer_id NMTOKEN #REQUIRED>\n")
6560 .append(indent).append("<!ATTLIST t2_display x NMTOKEN #REQUIRED>\n")
6561 .append(indent).append("<!ATTLIST t2_display y NMTOKEN #REQUIRED>\n")
6562 .append(indent).append("<!ATTLIST t2_display magnification NMTOKEN #REQUIRED>\n")
6563 .append(indent).append("<!ATTLIST t2_display srcrect_x NMTOKEN #REQUIRED>\n")
6564 .append(indent).append("<!ATTLIST t2_display srcrect_y NMTOKEN #REQUIRED>\n")
6565 .append(indent).append("<!ATTLIST t2_display srcrect_width NMTOKEN #REQUIRED>\n")
6566 .append(indent).append("<!ATTLIST t2_display srcrect_height NMTOKEN #REQUIRED>\n")
6567 .append(indent).append("<!ATTLIST t2_display scroll_step NMTOKEN #REQUIRED>\n")
6568 .append(indent).append("<!ATTLIST t2_display c_alphas NMTOKEN #REQUIRED>\n")
6569 .append(indent).append("<!ATTLIST t2_display c_alphas_state NMTOKEN #REQUIRED>\n")
6570 .append(indent).append("<!ATTLIST t2_display filter_enabled NMTOKEN #REQUIRED>\n")
6571 .append(indent).append("<!ATTLIST t2_display filter_min_max_enabled NMTOKEN #REQUIRED>\n")
6572 .append(indent).append("<!ATTLIST t2_display filter_min NMTOKEN #REQUIRED>\n")
6573 .append(indent).append("<!ATTLIST t2_display filter_max NMTOKEN #REQUIRED>\n")
6574 .append(indent).append("<!ATTLIST t2_display filter_invert NMTOKEN #REQUIRED>\n")
6575 .append(indent).append("<!ATTLIST t2_display filter_clahe_enabled NMTOKEN #REQUIRED>\n")
6576 .append(indent).append("<!ATTLIST t2_display filter_clahe_block_size NMTOKEN #REQUIRED>\n")
6577 .append(indent).append("<!ATTLIST t2_display filter_clahe_histogram_bins NMTOKEN #REQUIRED>\n")
6578 .append(indent).append("<!ATTLIST t2_display filter_clahe_max_slope NMTOKEN #REQUIRED>\n")
6581 /** Export all displays of the given project as XML entries. */
6582 static public void exportXML(final Project project, final Writer writer, final String indent, final XMLOptions options) throws Exception {
6583 final StringBuilder sb_body = new StringBuilder();
6584 final String in = indent + "\t";
6585 for (final Display d : al_displays) {
6586 if (d.project != project) continue;
6587 final Rectangle r = d.frame.getBounds();
6588 final Rectangle srcRect = d.canvas.getSrcRect();
6589 final double magnification = d.canvas.getMagnification();
6590 sb_body.append(indent).append("<t2_display id=\"").append(d.id).append("\"\n")
6591 .append(in).append("layer_id=\"").append(d.layer.getId()).append("\"\n")
6592 .append(in).append("c_alphas=\"").append(d.c_alphas).append("\"\n")
6593 .append(in).append("c_alphas_state=\"").append(d.getChannelAlphasState()).append("\"\n")
6594 .append(in).append("x=\"").append(r.x).append("\"\n")
6595 .append(in).append("y=\"").append(r.y).append("\"\n")
6596 .append(in).append("magnification=\"").append(magnification).append("\"\n")
6597 .append(in).append("srcrect_x=\"").append(srcRect.x).append("\"\n")
6598 .append(in).append("srcrect_y=\"").append(srcRect.y).append("\"\n")
6599 .append(in).append("srcrect_width=\"").append(srcRect.width).append("\"\n")
6600 .append(in).append("srcrect_height=\"").append(srcRect.height).append("\"\n")
6601 .append(in).append("scroll_step=\"").append(d.scroll_step).append("\"\n")
6602 .append(in).append("filter_enabled=\"").append(d.filter_enabled).append("\"\n")
6603 .append(in).append("filter_min_max_enabled=\"").append(d.filter_min_max_enabled).append("\"\n")
6604 .append(in).append("filter_min=\"").append(d.filter_min).append("\"\n")
6605 .append(in).append("filter_max=\"").append(d.filter_max).append("\"\n")
6606 .append(in).append("filter_invert=\"").append(d.filter_invert).append("\"\n")
6607 .append(in).append("filter_clahe_enabled=\"").append(d.filter_clahe_enabled).append("\"\n")
6608 .append(in).append("filter_clahe_block_size=\"").append(d.filter_clahe_block_size).append("\"\n")
6609 .append(in).append("filter_clahe_histogram_bins=\"").append(d.filter_clahe_histogram_bins).append("\"\n")
6610 .append(in).append("filter_clahe_max_slope=\"").append(d.filter_clahe_max_slope).append("\"\n")
6612 sb_body.append(indent).append("/>\n");
6614 writer.write(sb_body.toString());
6617 private void updateToolTab() {
6618 OptionPanel op = null;
6619 switch (ProjectToolbar.getToolId()) {
6620 case ProjectToolbar.PENCIL:
6621 op = Segmentation.fmp.asOptionPanel();
6622 break;
6623 case ProjectToolbar.BRUSH:
6624 op = AreaWrapper.PP.asOptionPanel();
6625 break;
6626 default:
6627 break;
6629 scroll_options.getViewport().removeAll();
6630 if (null != op) {
6631 op.bottomPadding();
6632 scroll_options.setViewportView(op);
6634 scroll_options.invalidate();
6635 scroll_options.validate();
6636 scroll_options.repaint();
6639 // Never called; ProjectToolbar.toolChanged is also never called, which should forward here.
6640 static public void toolChanged(final String tool_name) {
6641 Utils.log2("tool name: " + tool_name);
6642 for (final Display d : al_displays) {
6643 d.updateToolTab();
6644 Utils.updateComponent(d.toolbar_panel);
6645 Utils.log2("updating toolbar_panel");
6649 static public void toolChanged(final int tool) {
6650 //Utils.log2("int tool is " + tool);
6651 if (ProjectToolbar.PEN == tool) {
6652 // erase bounding boxes
6653 HashSet<Layer> s = new HashSet<Layer>();
6654 for (final Display d : al_displays) {
6655 if (null != d.active && !s.contains(d.layer)) {
6656 Display.repaint(d.layer, d.selection.getBox(), 2);
6657 s.add(d.layer);
6661 for (final Display d: al_displays) {
6662 d.updateToolTab();
6664 if (null != front) {
6665 try {
6666 WindowManager.setTempCurrentImage(front.canvas.getFakeImagePlus());
6667 } catch (Exception e) {} // may fail when changing tools while opening a Display
6671 /* Filter the Display images */
6672 private boolean filter_enabled = false,
6673 filter_invert = false,
6674 filter_clahe_enabled = false,
6675 filter_min_max_enabled = false;
6676 private int filter_clahe_block_size = 127,
6677 filter_clahe_histogram_bins = 256,
6678 filter_min = 0,
6679 filter_max = 255;
6680 private float filter_clahe_max_slope = 3;
6682 public boolean isLiveFilteringEnabled() { return filter_enabled; }
6684 private OptionPanel createFilterOptionPanel() {
6685 OptionPanel fop = new OptionPanel();
6686 Runnable reaction = new Runnable() {
6687 public void run() {
6688 Display.repaint(getLayer());
6691 fop.addCheckbox("Live filters enabled", filter_enabled, new OptionPanel.BooleanSetter(this, "filter_enabled", reaction));
6692 fop.addMessage("Contrast:");
6693 fop.addCheckbox("Min/Max enabled", filter_min_max_enabled, new OptionPanel.BooleanSetter(this, "filter_min_max_enabled", reaction));
6694 fop.addNumericField("Min:", filter_min, 0, new OptionPanel.IntSetter(this, "filter_min", reaction, 0, 255));
6695 fop.addNumericField("Max:", filter_max, 0, new OptionPanel.IntSetter(this, "filter_max", reaction, 0, 255));
6696 fop.addCheckbox("Invert", filter_invert, new OptionPanel.BooleanSetter(this, "filter_invert", reaction));
6697 fop.addMessage("CLAHE options:");
6698 fop.addCheckbox("CLAHE enabled", filter_clahe_enabled, new OptionPanel.BooleanSetter(this, "filter_clahe_enabled", reaction));
6699 fop.addNumericField("block size:", filter_clahe_block_size, 0, new OptionPanel.IntSetter(this, "filter_clahe_block_size", reaction, 1, Integer.MAX_VALUE));
6700 fop.addNumericField("histogram bins:", filter_clahe_histogram_bins, 0, new OptionPanel.IntSetter(this, "filter_clahe_histogram_bins", reaction, 1, Integer.MAX_VALUE));
6701 fop.addNumericField("max slope:", filter_clahe_max_slope, 2, new OptionPanel.FloatSetter(this, "filter_clahe_max_slope", reaction, 0, Integer.MAX_VALUE));
6702 return fop;
6705 protected Image applyFilters(final Image img) {
6706 if (!filter_enabled) return img;
6707 return applyFilters(new ImagePlus("filtered", img)).getProcessor().createImage();
6710 protected ImagePlus applyFilters(final ImageProcessor ip) {
6711 ImagePlus imp = new ImagePlus("filtered", ip);
6712 applyFilters(imp);
6713 return imp;
6716 protected ImagePlus applyFilters(final ImagePlus imp) {
6717 // Currently the order is hard-coded
6718 // 0: enabled?
6719 if (!filter_enabled) return imp;
6720 // 1: min/max?
6721 if (filter_min_max_enabled) imp.getProcessor().setMinAndMax(filter_min, filter_max);
6722 // 2: invert?
6723 if (filter_invert) imp.getProcessor().invert();
6724 // 3: CLAHE?
6725 if (filter_clahe_enabled) {
6726 Flat.getFastInstance().run(imp, filter_clahe_block_size, filter_clahe_histogram_bins, filter_clahe_max_slope, null, false);
6729 return imp;
6732 /////
6734 public Selection getSelection() {
6735 return selection;
6738 public final boolean isSelected(final Displayable d) {
6739 return selection.contains(d);
6742 static public void updateSelection() {
6743 Display.updateSelection(null);
6745 static public void updateSelection(final Display calling) {
6746 final HashSet<Layer> hs = new HashSet<Layer>();
6747 for (final Display d : al_displays) {
6748 if (hs.contains(d.layer)) continue;
6749 hs.add(d.layer);
6750 if (null == d || null == d.selection) {
6751 Utils.log2("d is : "+ d + " d.selection is " + d.selection);
6752 } else {
6753 d.selection.update(); // recomputes box
6755 if (d != calling) { // TODO this is so dirty!
6756 if (d.selection.getNLinked() > 1) d.canvas.setUpdateGraphics(true); // this is overkill anyway
6757 d.canvas.repaint(d.selection.getLinkedBox(), Selection.PADDING);
6758 d.navigator.repaint(true); // everything
6763 static public void clearSelection(final Layer layer) {
6764 for (final Display d : al_displays) {
6765 if (d.layer == layer) d.selection.clear();
6768 static public void clearSelection() {
6769 for (final Display d : al_displays) {
6770 d.selection.clear();
6773 static public void clearSelection(final Project p) {
6774 for (final Display d : al_displays) {
6775 if (d.project == p) d.selection.clear();
6779 private void setTempCurrentImage() {
6780 WindowManager.setCurrentWindow(canvas.getFakeImagePlus().getWindow());
6781 WindowManager.setTempCurrentImage(canvas.getFakeImagePlus());
6784 /** Check if any display will paint the given Displayable within its srcRect. */
6785 static public boolean willPaint(final Displayable displ) {
6786 Rectangle box = null;
6787 for (final Display d : al_displays) {
6788 if (displ.getLayer() == d.layer) {
6789 if (null == box) box = displ.getBoundingBox(null);
6790 if (d.canvas.getSrcRect().intersects(box)) {
6791 return true;
6795 return false;
6798 @SuppressWarnings({ "unchecked", "rawtypes" })
6799 public void hideDeselected(final boolean not_images) {
6800 // hide deselected
6801 final ArrayList all = layer.getParent().getZDisplayables(); // a copy
6802 all.addAll(layer.getDisplayables());
6803 all.removeAll(selection.getSelected());
6804 if (not_images) all.removeAll(layer.getDisplayables(Patch.class));
6805 for (final Displayable d : (ArrayList<Displayable>)all) {
6806 if (d.isVisible()) {
6807 d.setVisible(false);
6808 Display.updateCheckboxes(d, DisplayablePanel.VISIBILITY_STATE, false);
6811 Display.update(layer);
6814 /** Cleanup internal lists that may contain the given Displayable. */
6815 static public void flush(final Displayable displ) {
6816 for (final Display d : al_displays) {
6817 d.selection.removeFromPrev(displ);
6821 public void resizeCanvas(final Rectangle bounds) {
6822 if (bounds.width <= 0|| bounds.height <= 0) throw new IllegalArgumentException("width and height must be larger than zero.");
6823 layer.getParent().setDimensions(bounds.x, bounds.y, bounds.width, bounds.height);
6826 public void resizeCanvas() {
6827 GenericDialog gd = new GenericDialog("Resize LayerSet");
6828 gd.addNumericField("new width: ", layer.getLayerWidth(), 1, 8, "pixels");
6829 gd.addNumericField("new height: ", layer.getLayerHeight(), 1, 8, "pixels");
6830 gd.addChoice("Anchor: ", LayerSet.ANCHORS, LayerSet.ANCHORS[7]);
6831 gd.showDialog();
6832 if (gd.wasCanceled()) return;
6833 float new_width = (float)gd.getNextNumber();
6834 float new_height = (float)gd.getNextNumber();
6835 layer.getParent().setDimensions(new_width, new_height, gd.getNextChoiceIndex()); // will complain and prevent cropping existing Displayable objects
6839 // To record layer changes -- but it's annoying, this is visualization not data.
6840 static class DoSetLayer implements DoStep {
6841 final Display display;
6842 final Layer layer;
6843 DoSetLayer(final Display display) {
6844 this.display = display;
6845 this.layer = display.layer;
6847 public Displayable getD() { return null; }
6848 public boolean isEmpty() { return false; }
6849 public boolean apply(final int action) {
6850 display.setLayer(layer);
6852 public boolean isIdenticalTo(final Object ob) {
6853 if (!ob instanceof DoSetLayer) return false;
6854 final DoSetLayer dsl = (DoSetLayer) ob;
6855 return dsl.display == this.display && dsl.layer == this.layer;
6860 protected void duplicateLinkAndSendTo(final Displayable active, final int position, final Layer other_layer) {
6861 if (null == active || !(active instanceof Profile)) return;
6862 if (active.getLayer() == other_layer) return; // can't do that!
6863 // set current state
6864 Set<DoStep> dataedits = new HashSet<DoStep>();
6865 dataedits.add(new Displayable.DoEdit(active).init(active, new String[]{"data"})); // the links!
6866 getLayerSet().addChangeTreesStep(dataedits);
6867 Profile profile = project.getProjectTree().duplicateChild((Profile)active, position, other_layer);
6868 if (null == profile) {
6869 getLayerSet().removeLastUndoStep();
6870 return;
6872 active.link(profile);
6873 other_layer.add(profile);
6874 slt.setAndWait(other_layer);
6875 selection.add(profile);
6876 // set new state
6877 dataedits = new HashSet<DoStep>();
6878 dataedits.add(new Displayable.DoEdit(active).init(active, new String[]{"data"})); // the links!
6879 dataedits.add(new Displayable.DoEdit(profile).init(profile, new String[]{"data"})); // the links!
6880 getLayerSet().addChangeTreesStep(dataedits);
6883 private final HashMap<Color,Layer> layer_channels = new HashMap<Color,Layer>();
6884 private final TreeMap<Integer,LayerPanel> layer_alpha = new TreeMap<Integer,LayerPanel>();
6885 private final HashMap<Layer,Byte> layer_composites = new HashMap<Layer,Byte>();
6886 boolean invert_colors = false,
6887 transp_overlay_images = true,
6888 transp_overlay_areas = false,
6889 transp_overlay_text_labels = false;
6890 Set<Class<?>> classes_to_multipaint = getClassesToMultiPaint();
6892 protected void setTranspOverlayImages(final boolean b) {
6893 this.transp_overlay_images = b;
6894 updateMultiPaint();
6896 protected void setTranspOverlayAreas(final boolean b) {
6897 this.transp_overlay_areas = b;
6898 updateMultiPaint();
6900 protected void setTranspOverlayTextLabels(final boolean b) {
6901 this.transp_overlay_text_labels = b;
6902 updateMultiPaint();
6904 protected void updateMultiPaint() {
6905 this.classes_to_multipaint = getClassesToMultiPaint();
6906 this.canvas.repaint(true);
6908 /** Only Patch, Stack; AreaList, Profile; and DLabel are considered.
6909 * The rest paints in other layers with color cues. */
6910 protected Set<Class<?>> getClassesToMultiPaint() {
6911 final HashSet<Class<?>> include = new HashSet<Class<?>>();
6912 if (transp_overlay_images) {
6913 include.add(Patch.class);
6914 include.add(Stack.class);
6916 if (transp_overlay_areas) {
6917 include.add(AreaList.class);
6918 include.add(Profile.class);
6920 if (transp_overlay_text_labels) {
6921 include.add(DLabel.class);
6923 return include;
6926 protected byte getLayerCompositeMode(final Layer layer) {
6927 synchronized (layer_composites) {
6928 Byte b = layer_composites.get(layer);
6929 return null == b ? Displayable.COMPOSITE_NORMAL : b;
6933 protected void setLayerCompositeMode(final Layer layer, final byte compositeMode) {
6934 synchronized (layer_composites) {
6935 if (-1 == compositeMode || Displayable.COMPOSITE_NORMAL == compositeMode) {
6936 layer_composites.remove(layer);
6937 } else {
6938 layer_composites.put(layer, compositeMode);
6943 protected void resetLayerComposites() {
6944 synchronized (layer_composites) {
6945 layer_composites.clear();
6947 canvas.repaint(true);
6950 /** Remove all red/blue coloring of layers, and repaint canvas. */
6951 protected void resetLayerColors() {
6952 synchronized (layer_channels) {
6953 for (final Layer l : new ArrayList<Layer>(layer_channels.values())) { // avoid concurrent modification exception
6954 final LayerPanel lp = layer_panels.get(l);
6955 lp.setColor(Color.white);
6956 setColorChannel(lp.layer, Color.white);
6957 lp.slider.setEnabled(true);
6959 layer_channels.clear();
6961 canvas.repaint(true);
6964 /** Set all layer alphas to zero, and repaint canvas. */
6965 protected void resetLayerAlphas() {
6966 synchronized (layer_channels) {
6967 for (final LayerPanel lp : new ArrayList<LayerPanel>(layer_alpha.values())) {
6968 lp.setAlpha(0);
6970 layer_alpha.clear(); // should have already been cleared
6972 canvas.repaint(true);
6975 /** Add to layer_alpha table, or remove if alpha is zero. */
6976 protected void storeLayerAlpha(final LayerPanel lp, final float a) {
6977 synchronized (layer_channels) {
6978 if (M.equals(0, a)) {
6979 layer_alpha.remove(lp.layer.getParent().indexOf(lp.layer));
6980 } else {
6981 layer_alpha.put(lp.layer.getParent().indexOf(lp.layer), lp);
6986 static protected final int REPAINT_SINGLE_LAYER = 0;
6987 static protected final int REPAINT_MULTI_LAYER = 1;
6988 static protected final int REPAINT_RGB_LAYER = 2;
6990 /** Sets the values atomically, returns the painting mode. */
6991 protected int getPaintMode(final HashMap<Color,Layer> hm, final ArrayList<LayerPanel> list) {
6992 synchronized (layer_channels) {
6993 if (layer_channels.size() > 0) {
6994 hm.putAll(layer_channels);
6995 hm.put(Color.green, this.layer);
6996 return REPAINT_RGB_LAYER;
6998 list.addAll(layer_alpha.values());
6999 final int len = list.size();
7000 if (len > 1) return REPAINT_MULTI_LAYER;
7001 if (1 == len) {
7002 if (list.get(0).layer == this.layer) return REPAINT_SINGLE_LAYER; // normal mode
7003 return REPAINT_MULTI_LAYER;
7005 return REPAINT_SINGLE_LAYER;
7009 /** Set a layer to be painted as a specific color channel in the canvas.
7010 * Only Color.red and Color.blue are accepted.
7011 * Color.green is reserved for the current layer. */
7012 protected void setColorChannel(final Layer layer, final Color color) {
7013 synchronized (layer_channels) {
7014 if (Color.white == color) {
7015 // Remove
7016 for (final Iterator<Layer> it = layer_channels.values().iterator(); it.hasNext(); ) {
7017 if (it.next() == layer) {
7018 it.remove();
7019 break;
7022 canvas.repaint();
7023 } else if (Color.red == color || Color.blue == color) {
7024 // Reset current of that color, if any, to white
7025 final Layer l = layer_channels.remove(color);
7026 if (null != l) layer_panels.get(l).setColor(Color.white);
7027 // Replace or set new
7028 layer_channels.put(color, layer);
7029 tabs.repaint();
7030 canvas.repaint();
7031 } else {
7032 Utils.log2("Trying to set unacceptable color for layer " + layer + " : " + color);
7034 // enable/disable sliders
7035 final boolean b = 0 == layer_channels.size();
7036 for (final LayerPanel lp : layer_panels.values()) lp.slider.setEnabled(b);
7038 this.canvas.repaint(true);
7041 static public final void updateComponentTreeUI() {
7042 try {
7043 for (final Display d : al_displays) SwingUtilities.updateComponentTreeUI(d.frame);
7044 } catch (Exception e) {
7045 IJError.print(e);
7049 /** Snap a Patch to the most overlapping Patch, if any.
7050 * This method is a shallow wrap around AlignTask.snap, setting proper undo steps. */
7051 static public final Bureaucrat snap(final Patch patch) {
7052 final Set<Displayable> linked = patch.getLinkedGroup(null);
7053 patch.getLayerSet().addTransformStep(linked);
7054 Bureaucrat burro = AlignTask.snap(patch, null, false);
7055 burro.addPostTask(new Runnable() { public void run() {
7056 patch.getLayerSet().addTransformStep(linked);
7057 }});
7058 return burro;
7061 private Mode mode = new DefaultMode(this);
7063 public void setMode(final Mode mode) {
7064 ProjectToolbar.setTool(ProjectToolbar.SELECT);
7065 this.mode = mode;
7066 canvas.repaint(true);
7067 Utils.invokeLater(new Runnable() { public void run() {
7068 scroller.setEnabled(mode.canChangeLayer());
7069 }});
7072 public Mode getMode() {
7073 return mode;
7077 static private final Hashtable<String,ProjectThing> findLandmarkNodes(Project p, String landmarks_type) {
7078 Set<ProjectThing> landmark_nodes = p.getRootProjectThing().findChildrenOfTypeR(landmarks_type);
7079 Hashtable<String,ProjectThing> map = new Hashtable<String,ProjectThing>();
7080 for (ProjectThing pt : landmark_nodes) {
7081 map.put(pt.toString() + "# " + pt.getId(), pt);
7083 return map;
7086 /** @param stack_patch is just a Patch of a series of Patch that make a stack of Patches. */
7087 private boolean insertStack(ProjectThing target_landmarks, Project source, ProjectThing source_landmarks, Patch stack_patch) {
7088 List<Ball> l1 = new ArrayList<Ball>();
7089 List<Ball> l2 = new ArrayList<Ball>();
7090 Collection<ProjectThing> b1s = source_landmarks.findChildrenOfType("ball"); // source is the one that has the stack_patch
7091 Collection<ProjectThing> b2s = target_landmarks.findChildrenOfType("ball"); // target is this
7092 HashSet<String> seen = new HashSet<String>();
7093 for (ProjectThing b1 : b1s) {
7094 Ball ball1 = (Ball) b1.getObject();
7095 if (null == ball1) {
7096 Utils.log("ERROR: there's an empty 'ball' node in target project" + project.toString());
7097 return false;
7099 String title1 = ball1.getTitle();
7100 for (ProjectThing b2 : b2s) {
7101 Ball ball2 = (Ball) b2.getObject();
7102 if (null == ball2) {
7103 Utils.log("ERROR: there's an empty 'ball' node in source project" + source.toString());
7104 return false;
7106 if (title1.equals(ball2.getTitle())) {
7107 if (seen.contains(title1)) continue;
7108 seen.add(title1);
7109 l1.add(ball1);
7110 l2.add(ball2);
7114 if (l1.size() < 4) {
7115 Utils.log("ERROR: found only " + l1.size() + " common landmarks: needs at least 4!");
7116 return false;
7118 // Extract coordinates of source project landmarks, in patch stack coordinate space
7119 List<float[]> c1 = new ArrayList<float[]>();
7120 for (Ball ball1 : l1) {
7121 Map<Layer,double[]> m = ball1.getRawBalls();
7122 if (1 != m.size()) {
7123 Utils.log("ERROR: ball object " + ball1 + " from target project " + project + " has " + m.size() + " balls instead of just 1.");
7124 return false;
7126 Map.Entry<Layer,double[]> e = m.entrySet().iterator().next();
7127 Layer layer = e.getKey();
7128 double[] xyr = e.getValue();
7129 float[] fin = new float[]{(float)xyr[0], (float)xyr[1]};
7130 AffineTransform affine = ball1.getAffineTransformCopy();
7131 try {
7132 affine.preConcatenate(stack_patch.getAffineTransform().createInverse());
7133 } catch (Exception nite) {
7134 IJError.print(nite);
7135 return false;
7137 float[] fout = new float[2];
7138 affine.transform(fin, 0, fout, 0, 1);
7139 c1.add(new float[]{fout[0], fout[1], layer.getParent().indexOf(layer)});
7142 // Extract coordinates of target (this) project landmarks, in calibrated world space
7143 List<float[]> c2 = new ArrayList<float[]>();
7144 for (Ball ball2 : l2) {
7145 double[][] b = ball2.getBalls();
7146 if (1 != b.length) {
7147 Utils.log("ERROR: ball object " + ball2 + " from source project " + source + " has " + b.length + " balls instead of just 1.");
7148 return false;
7150 float[] fin = new float[]{(float)b[0][0], (float)b[0][1]};
7151 AffineTransform affine = ball2.getAffineTransformCopy();
7152 float[] fout = new float[2];
7153 affine.transform(fin, 0, fout, 0, 1);
7154 c2.add(new float[]{fout[0], fout[1], (float)b[0][2]});
7157 // Print landmarks:
7158 Utils.log("Landmarks:");
7159 for (Iterator<float[]> it1 = c1.iterator(), it2 = c2.iterator(); it1.hasNext(); ) {
7160 Utils.log(Utils.toString(it1.next()) + " <--> " + Utils.toString(it2.next()));
7163 // Create point matches
7164 List<PointMatch> pm = new ArrayList<PointMatch>();
7165 for (Iterator<float[]> it1 = c1.iterator(), it2 = c2.iterator(); it1.hasNext(); ) {
7166 pm.add(new mpicbg.models.PointMatch(new mpicbg.models.Point(it1.next()), new mpicbg.models.Point(it2.next())));
7169 // Estimate AffineModel3D
7170 AffineModel3D aff3d = new AffineModel3D();
7171 try {
7172 aff3d.fit(pm);
7173 } catch (Exception e) {
7174 IJError.print(e);
7175 return false;
7178 // Create and add the Stack
7179 String path = stack_patch.getImageFilePath();
7180 Stack st = new Stack(project, new File(path).getName(), 0, 0, getLayerSet().getLayers().get(0), path);
7181 st.setInvertibleCoordinateTransform(aff3d);
7182 getLayerSet().add(st);
7183 return true;
7186 static private List<Patch> getPatchStacks(final LayerSet ls) {
7187 HashSet<Patch> stacks = new HashSet<Patch>();
7188 for (Patch pa : ls.getAll(Patch.class)) {
7189 if (stacks.contains(pa)) continue;
7190 PatchStack ps = pa.makePatchStack();
7191 if (1 == ps.getNSlices()) continue;
7192 stacks.add(ps.getPatch(0));
7194 return new ArrayList<Patch>(stacks);
7197 static public Bureaucrat removeAlphaMasks(final Collection<Patch> patches) {
7198 return Bureaucrat.createAndStart(new Worker.Task("Remove alpha masks" + (patches.size() > 1 ? "s" : "")) {
7199 public void exec() {
7200 if (null == patches || patches.isEmpty()) return;
7201 final ArrayList<Future<Boolean>> jobs = new ArrayList<Future<Boolean>>();
7202 for (final Patch p : patches) {
7203 p.setAlphaMask(null);
7204 Future<Boolean> job = p.getProject().getLoader().regenerateMipMaps(p); // submit to queue
7205 if (null != job) jobs.add(job);
7207 // join all
7208 for (final Future<?> job : jobs) try {
7209 job.get();
7210 } catch (Exception ie) {}
7211 }}, patches.iterator().next().getProject());
7214 /** Get the current {@link Roi}, if any. */
7215 public Roi getRoi() {
7216 return canvas.getFakeImagePlus().getRoi();
7219 /** Open a {@link GenericDialog} to ask for parameters to find all {@link Displayable} that abide to them.
7221 * @return The list of {@link Displayable} instances found.
7222 * @see Display#find(String, boolean, Class, List)
7224 public <T extends Displayable> List<T> find() {
7225 GenericDialog gd = new GenericDialog("Select matching");
7226 Utils.addLayerRangeChoices(layer, gd);
7227 gd.addStringField("Regular expression:", "");
7228 TreeMap<String,Class<?>> types = new TreeMap<String,Class<?>>();
7229 types.put("01 - All", Displayable.class);
7230 types.put("02 - Image (Patch, Stack)", ImageData.class);
7231 types.put("03 - Non-image (VectorData subtypes)", VectorData.class);
7232 types.put("04 - Tree (Treeline, AreaTree, Connector)", Tree.class);
7233 types.put("05 - Area container (AreaTree, AreaList)", AreaContainer.class);
7234 types.put("06 - Text labels", DLabel.class);
7235 types.put("07 - Patch (image)", Patch.class);
7236 types.put("08 - Stack (image)", Stack.class);
7237 types.put("09 - AreaList", AreaList.class);
7238 types.put("10 - AreaTree", AreaTree.class);
7239 types.put("11 - Treeline", Treeline.class);
7240 types.put("12 - Connector", Connector.class);
7241 types.put("13 - Ball", Ball.class);
7242 types.put("14 - Pipe", Pipe.class);
7243 types.put("15 - Polyline", Polyline.class);
7244 types.put("16 - Profile", Profile.class);
7245 types.put("17 - Dissector", Dissector.class);
7247 gd.addChoice("Type:", types.keySet().toArray(new String[types.size()]), types.firstKey());
7248 gd.addCheckbox("Visible only", true);
7249 gd.showDialog();
7250 if (gd.wasCanceled()) return Collections.EMPTY_LIST;
7251 int first = gd.getNextChoiceIndex();
7252 int last = gd.getNextChoiceIndex();
7253 return find(gd.getNextString(), gd.getNextBoolean(), (Class<T>)types.get(gd.getNextChoice()), first, last);
7256 /** @see Display#find(String, boolean, Class, List) */
7257 public <T extends Displayable> List<T> find(final String regex, final boolean visible_only, final Class<T> c, final int firstLayerIndex, final int lastLayerIndex) {
7258 return find(regex, visible_only, c, layer.getParent().getLayers(firstLayerIndex, lastLayerIndex));
7261 static private final String fixRegex(String regex) {
7262 if (regex.charAt(0) != '^') {
7263 if (regex.startsWith(".*")) regex = "^" + regex;
7264 else regex = "^.*" + regex;
7266 if (regex.charAt(regex.length()-1) != '$') {
7267 if (regex.endsWith(".*")) regex += "$";
7268 else regex += ".*$";
7270 return regex;
7275 * @param regex The regular expression to match against the title of a {@link Displayable}.
7276 * @param visible_only Whether to gather only {@link Displayable} instances that are visible.
7277 * @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.
7278 * @param layers The range of {@link Layer} to search in.
7279 * @return
7281 static public <T extends Displayable> List<T> find(final String regex, final boolean visible_only, final Class<T> c, final List<Layer> layers) {
7282 Utils.log2(regex, visible_only, c, layers);
7283 final List<T> ds = new ArrayList<T>();
7284 final Pattern pattern = (null == regex || 0 == regex.length()) ? null : Pattern.compile(fixRegex(regex));
7285 for (final Layer l : layers) {
7286 for (final Displayable d : l.getDisplayables()) {
7287 if (visible_only && !d.isVisible()) continue;
7288 if (c.isInstance(d)) {
7289 if (null == pattern) ds.add((T)d);
7290 else if (pattern.matcher(d.getTitle()).matches()) ds.add((T)d);
7294 for (final ZDisplayable d : layers.get(0).getParent().getZDisplayables()) {
7295 if (visible_only && !d.isVisible()) continue;
7296 if (c.isInstance(d)
7297 && (null == pattern || pattern.matcher(d.getTitle()).matches())) {
7298 for (final Layer l : layers) {
7299 if (d.paintsAt(l)) {
7300 ds.add((T)d);
7301 break;
7306 return ds;