	public void actionPerformed(final ActionEvent ae) {
		dispatcher.exec(new Runnable() { public void run() {
		String command = ae.getActionCommand();
		if (command.startsWith("Job")) {
			if (Utils.checkYN("Really cancel job?")) {
				project.getLoader().quitJob(command);
				repairGUI();
			}
			return;
		} else if (command.equals("Move to top")) {
			if (null == active) return;
			canvas.setUpdateGraphics(true);
			layer.getParent().move(LayerSet.TOP, active);
			Display.repaint(layer.getParent(), active, 5);
		} else if (command.equals("Move up")) {
			if (null == active) return;
			canvas.setUpdateGraphics(true);
			layer.getParent().move(LayerSet.UP, active);
			Display.repaint(layer.getParent(), active, 5);
		} else if (command.equals("Move down")) {
			if (null == active) return;
			canvas.setUpdateGraphics(true);
			layer.getParent().move(LayerSet.DOWN, active);
			Display.repaint(layer.getParent(), active, 5);
		} else if (command.equals("Move to bottom")) {
			if (null == active) return;
			canvas.setUpdateGraphics(true);
			layer.getParent().move(LayerSet.BOTTOM, active);
			Display.repaint(layer.getParent(), active, 5);
		} else if (command.equals("Duplicate, link and send to next layer")) {
			duplicateLinkAndSendTo(active, 1, layer.getParent().next(layer));
		} else if (command.equals("Duplicate, link and send to previous layer")) {
			duplicateLinkAndSendTo(active, 0, layer.getParent().previous(layer));
		} else if (command.equals("Duplicate, link and send to...")) {
			GenericDialog gd = new GenericDialog("Send to");
			gd.addMessage("Duplicate, link and send to...");
			String[] sl = new String[layer.getParent().size()];
			int next = 0;
			for (Iterator it = layer.getParent().getLayers().iterator(); it.hasNext(); ) {
				sl[next++] = project.findLayerThing(it.next()).toString();
			}
			gd.addChoice("Layer: ", sl, sl[layer.getParent().indexOf(layer)]);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			Layer la = layer.getParent().getLayer(gd.getNextChoiceIndex());
			if (layer == la) {
				Utils.showMessage("Can't duplicate, link and send to the same layer.");
				return;
			}
			duplicateLinkAndSendTo(active, 0, la);
		} else if (-1 != command.indexOf("z = ")) {
			Layer target_layer = layer.getParent().getLayer(Double.parseDouble(command.substring(command.lastIndexOf(' ') +1)));
			Utils.log2("layer: __" +command.substring(command.lastIndexOf(' ') +1) + "__");
			if (null == target_layer) return;
			duplicateLinkAndSendTo(active, 0, target_layer);
		} else if (-1 != command.indexOf("z=")) {
			int iz = command.indexOf("z=")+2;
			Utils.log2("iz=" + iz + "  other: " + command.indexOf(' ', iz+2));
			int end = command.indexOf(' ', iz);
			if (-1 == end) end = command.length();
			double lz = Double.parseDouble(command.substring(iz, end));
			Layer target = layer.getParent().getLayer(lz);
			layer.getParent().move(selection.getAffected(), active.getLayer(), target); 
		} else if (command.equals("Unlink")) {
			if (null == active || active instanceof Patch) return;
			active.unlink();
			updateSelection();
		} else if (command.equals("Unlink from images")) {
			if (null == active) return;
			try {
				for (Displayable displ: selection.getSelected()) {
					displ.unlinkAll(Patch.class);
				}
				updateSelection();
			} catch (Exception e) { IJError.print(e); }
		} else if (command.equals("Unlink slices")) {
			YesNoCancelDialog yn = new YesNoCancelDialog(frame, "Attention", "Really unlink all slices from each other?\nThere is no undo.");
			if (!yn.yesPressed()) return;
			final ArrayList<Patch> pa = ((Patch)active).getStackPatches();
			for (int i=pa.size()-1; i>0; i--) {
				pa.get(i).unlink(pa.get(i-1));
			}
		} else if (command.equals("Send to next layer")) {
			Rectangle box = selection.getBox();
			try {
				for (final Displayable displ : selection.getSelected()) {
					displ.unlinkAll(Patch.class);
				}
				updateSelection();
			} catch (Exception e) { IJError.print(e); }
			selection.moveDown();
			repaint(layer.getParent(), box);
		} else if (command.equals("Send to previous layer")) {
			Rectangle box = selection.getBox();
			try {
				for (final Displayable displ : selection.getSelected()) {
					displ.unlinkAll(Patch.class);
				}
				updateSelection();
			} catch (Exception e) { IJError.print(e); }
			selection.moveUp();
			repaint(layer.getParent(), box);
		} else if (command.equals("Show centered")) {
			if (active == null) return;
			showCentered(active);
		} else if (command.equals("Delete...")) {
			selection.deleteAll();
		} else if (command.equals("Color...")) {
			IJ.doCommand("Color Picker...");
		} else if (command.equals("Revert")) {
			if (null == active || active.getClass() != Patch.class) return;
			Patch p = (Patch)active;
			if (!p.revert()) {
				if (null == p.getOriginalPath()) Utils.log("No editions to save for patch " + p.getTitle() + " #" + p.getId());
				else Utils.log("Could not revert Patch " + p.getTitle() + " #" + p.getId());
			}
		} else if (command.equals("Remove alpha mask")) {
			final ArrayList<Displayable> patches = selection.getSelected(Patch.class); 
			if (patches.size() > 0) {
				Bureaucrat.createAndStart(new Worker.Task("Removing alpha mask" + (patches.size() > 1 ? "s" : "")) { public void exec() {
					final ArrayList<Future> jobs = new ArrayList<Future>();
					for (final Displayable d : patches) {
						final Patch p = (Patch) d;
						p.setAlphaMask(null);
						Future job = p.getProject().getLoader().regenerateMipMaps(p); 
						if (null != job) jobs.add(job);
					}
					for (final Future job : jobs) try {
						job.get();
					} catch (Exception ie) {}
				}}, patches.get(0).getProject());
			}
		} else if (command.equals("Undo")) {
			Bureaucrat.createAndStart(new Worker.Task("Undo") { public void exec() {
				layer.getParent().undoOneStep();
				Display.repaint(layer.getParent());
			}}, project);
		} else if (command.equals("Redo")) {
			Bureaucrat.createAndStart(new Worker.Task("Redo") { public void exec() {
				layer.getParent().redoOneStep();
				Display.repaint(layer.getParent());
			}}, project);
		} else if (command.equals("Apply transform")) {
			canvas.applyTransform();
		} else if (command.equals("Apply transform propagating to last layer")) {
			if (mode.getClass() == AffineTransformMode.class || mode.getClass() == NonLinearTransformMode.class) {
				final LayerSet ls = getLayerSet();
				final HashSet<Layer> subset = new HashSet<Layer>(ls.getLayers(ls.indexOf(Display.this.layer)+1, ls.size()-1)); 
				if (mode.getClass() == AffineTransformMode.class) ((AffineTransformMode)mode).applyAndPropagate(subset);
				else if (mode.getClass() == NonLinearTransformMode.class) ((NonLinearTransformMode)mode).apply(subset);
				setMode(new DefaultMode(Display.this));
			}
		} else if (command.equals("Apply transform propagating to first layer")) {
			if (mode.getClass() == AffineTransformMode.class || mode.getClass() == NonLinearTransformMode.class) {
				final LayerSet ls = getLayerSet();
				final HashSet<Layer> subset = new HashSet<Layer>(ls.getLayers(0, ls.indexOf(Display.this.layer) -1)); 
				if (mode.getClass() == AffineTransformMode.class) ((AffineTransformMode)mode).applyAndPropagate(subset);
				else if (mode.getClass() == NonLinearTransformMode.class) ((NonLinearTransformMode)mode).apply(subset);
				setMode(new DefaultMode(Display.this));
			}
		} else if (command.equals("Cancel transform")) {
			canvas.cancelTransform(); 
		} else if (command.equals("Specify transform...")) {
			if (null == active) return;
			selection.specify();
		} else if (command.equals("Hide all but images")) {
			ArrayList<Class<?>> type = new ArrayList<Class<?>>();
			type.add(Patch.class);
			type.add(Stack.class);
			Collection<Displayable> col = layer.getParent().hideExcept(type, false);
			selection.removeAll(col);
			Display.updateCheckboxes(col, DisplayablePanel.VISIBILITY_STATE);
			Display.update(layer.getParent(), false);
		} else if (command.equals("Unhide all")) {
			Display.updateCheckboxes(layer.getParent().setAllVisible(false), DisplayablePanel.VISIBILITY_STATE);
			Display.update(layer.getParent(), false);
		} else if (command.startsWith("Hide all ")) {
			String type = command.substring(9, command.length() -1); 
			Collection<Displayable> col = layer.getParent().setVisible(type, false, true);
			selection.removeAll(col);
			Display.updateCheckboxes(col, DisplayablePanel.VISIBILITY_STATE);
		} else if (command.startsWith("Unhide all ")) {
			String type = command.substring(11, command.length() -1); 
			type = type.substring(0, 1).toUpperCase() + type.substring(1);
			updateCheckboxes(layer.getParent().setVisible(type, true, true), DisplayablePanel.VISIBILITY_STATE);
		} else if (command.equals("Hide deselected")) {
			hideDeselected(0 != (ActionEvent.ALT_MASK & ae.getModifiers()));
		} else if (command.equals("Hide deselected except images")) {
			hideDeselected(true);
		} else if (command.equals("Hide selected")) {
			selection.setVisible(false); 
			Display.updateCheckboxes(selection.getSelected(), DisplayablePanel.VISIBILITY_STATE);
		} else if (command.equals("Resize canvas/LayerSet...")) {
			resizeCanvas();
		} else if (command.equals("Autoresize canvas/LayerSet")) {
			layer.getParent().setMinimumDimensions();
		} else if (command.equals("Import image")) {
			importImage();
		} else if (command.equals("Import next image")) {
			importNextImage();
		} else if (command.equals("Import stack...")) {
			Display.this.getLayerSet().addChangeTreesStep();
			Rectangle sr = getCanvas().getSrcRect();
			Bureaucrat burro = project.getLoader().importStack(layer, sr.x + sr.width/2, sr.y + sr.height/2, null, true, null, false);
			burro.addPostTask(new Runnable() { public void run() {
				Display.this.getLayerSet().addChangeTreesStep();
			}});
		} else if (command.equals("Import stack with landmarks...")) {
			List<Project> pr = Project.getProjects();
			if (1 == pr.size()) {
				Utils.logAll("Need another project open!");
				return;
			}
			GenericDialog gd = new GenericDialog("Landmarks");
			gd.addStringField("landmarks type:", "landmarks");
			final String[] none = {"-- None --"};
			final Hashtable<String,Project> mpr = new Hashtable<String,Project>();
			for (Project p : pr) {
				if (p == project) continue;
				mpr.put(p.toString(), p);
			}
			final String[] project_titles = mpr.keySet().toArray(new String[0]);
			final Hashtable<String,ProjectThing> map_target = findLandmarkNodes(project, "landmarks");
			String[] target_landmark_titles = map_target.isEmpty() ? none : map_target.keySet().toArray(new String[0]);
			gd.addChoice("Landmarks node in this project:", target_landmark_titles, target_landmark_titles[0]);
			gd.addMessage("");
			gd.addChoice("Source project:", project_titles, project_titles[0]);
			final Hashtable<String,ProjectThing> map_source = findLandmarkNodes(mpr.get(project_titles[0]), "landmarks");
			String[] source_landmark_titles = map_source.isEmpty() ? none : map_source.keySet().toArray(new String[0]);
			gd.addChoice("Landmarks node in source project:", source_landmark_titles, source_landmark_titles[0]);
			final List<Patch> stacks = Display.getPatchStacks(mpr.get(project_titles[0]).getRootLayerSet());
			String[] stack_titles;
			if (stacks.isEmpty()) {
				if (1 == mpr.size()) {
					IJ.showMessage("Project " + project_titles[0] + " does not contain any Stack.");
					return;
				}
				stack_titles = none;
			} else {
				stack_titles = new String[stacks.size()];
				int next = 0;
				for (Patch pa : stacks) stack_titles[next++] = pa.toString();
			}
			gd.addChoice("Stacks:", stack_titles, stack_titles[0]);
			Vector vc = gd.getChoices();
			final Choice choice_target_landmarks = (Choice) vc.get(0);
			final Choice choice_source_projects = (Choice) vc.get(1);
			final Choice choice_source_landmarks = (Choice) vc.get(2);
			final Choice choice_stacks = (Choice) vc.get(3);
			final TextField input = (TextField) gd.getStringFields().get(0);
			input.addTextListener(new TextListener() {
				public void textValueChanged(TextEvent te) {
					final String text = input.getText();
					update(choice_target_landmarks, Display.this.project, text, map_target);
					update(choice_source_landmarks, mpr.get(choice_source_projects.getSelectedItem()), text, map_source);
				}
				private void update(Choice c, Project p, String type, Hashtable<String,ProjectThing> table) {
					table.clear();
					table.putAll(findLandmarkNodes(p, type));
					c.removeAll();
					if (table.isEmpty()) c.add(none[0]);
					else for (String t : table.keySet()) c.add(t);
				}
			});
			choice_source_projects.addItemListener(new ItemListener() {
				public void itemStateChanged(ItemEvent e) {
					String item = (String) e.getItem();
					Project p = mpr.get(choice_source_projects.getSelectedItem());
					map_source.clear();
					map_source.putAll(findLandmarkNodes(p, input.getText()));
					choice_target_landmarks.removeAll();
					if (map_source.isEmpty()) choice_target_landmarks.add(none[0]);
					else for (String t : map_source.keySet()) choice_target_landmarks.add(t);
					stacks.clear();
					choice_stacks.removeAll();
					stacks.addAll(Display.getPatchStacks(mpr.get(project_titles[0]).getRootLayerSet()));
					if (stacks.isEmpty()) choice_stacks.add(none[0]);
					else for (Patch pa : stacks) choice_stacks.add(pa.toString());
				}
			});
			gd.showDialog();
			if (gd.wasCanceled()) return;
			String type = gd.getNextString();
			if (null == type || 0 == type.trim().length()) {
				Utils.log("Invalid landmarks node type!");
				return;
			}
			ProjectThing target_landmarks_node = map_target.get(gd.getNextChoice());
			Project source = mpr.get(gd.getNextChoice());
			ProjectThing source_landmarks_node = map_source.get(gd.getNextChoice());
			Patch stack_patch = stacks.get(gd.getNextChoiceIndex());
			Display.this.getLayerSet().addLayerContentStep(layer);
			insertStack(target_landmarks_node, source, source_landmarks_node, stack_patch);
			Display.this.getLayerSet().addChangeTreesStep();
		} else if (command.equals("Import grid...")) {
			Display.this.getLayerSet().addLayerContentStep(layer);
			Bureaucrat burro = project.getLoader().importGrid(layer);
			if (null != burro)
				burro.addPostTask(new Runnable() { public void run() {
					Display.this.getLayerSet().addLayerContentStep(layer);
				}});
		} else if (command.equals("Import sequence as grid...")) {
			Display.this.getLayerSet().addChangeTreesStep();
			Bureaucrat burro = project.getLoader().importSequenceAsGrid(layer);
			if (null != burro)
				burro.addPostTask(new Runnable() { public void run() {
					Display.this.getLayerSet().addChangeTreesStep();
				}});
		} else if (command.equals("Import from text file...")) {
			Display.this.getLayerSet().addChangeTreesStep();
			Bureaucrat burro = project.getLoader().importImages(layer);
			if (null != burro)
				burro.addPostTask(new Runnable() { public void run() {
					Display.this.getLayerSet().addChangeTreesStep();
				}});
		} else if (command.equals("Import labels as arealists...")) {
			Display.this.getLayerSet().addChangeTreesStep();
			Bureaucrat burro = project.getLoader().importLabelsAsAreaLists(layer, null, Double.MAX_VALUE, 0, 0.4f, false);
			burro.addPostTask(new Runnable() { public void run() {
				Display.this.getLayerSet().addChangeTreesStep();
			}});
		} else if (command.equals("Make flat image...")) {
			Rectangle srcRect = null;
			Roi roi = canvas.getFakeImagePlus().getRoi();
			if (null != roi) {
				srcRect = roi.getBounds();
			} else {
				srcRect = new Rectangle(0, 0, (int)Math.ceil(layer.getParent().getLayerWidth()), (int)Math.ceil(layer.getParent().getLayerHeight()));
			}
			double scale = 1.0;
			final String[] types = new String[]{"8-bit grayscale", "RGB Color"};
			int the_type = ImagePlus.GRAY8;
			final GenericDialog gd = new GenericDialog("Choose", frame);
			gd.addSlider("Scale: ", 1, 100, 100);
			gd.addNumericField("Width: ", srcRect.width, 0);
			gd.addNumericField("height: ", srcRect.height, 0);
			Vector numfields = gd.getNumericFields();
			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));
			for (Object ob : numfields) ((TextField)ob).addTextListener(udf);
			gd.addChoice("Type: ", types, types[0]);
			if (layer.getParent().size() > 1) {
				Utils.addLayerRangeChoices(Display.this.layer, gd); 
				gd.addCheckbox("Include non-empty layers only", true);
			}
			gd.addMessage("Background color:");
			Utils.addRGBColorSliders(gd, Color.black);
			gd.addCheckbox("Best quality", false);
			gd.addMessage("");
			gd.addCheckbox("Save to file", false);
			gd.addCheckbox("Save for web", false);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			scale = gd.getNextNumber() / 100;
			the_type = (0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY8 : ImagePlus.COLOR_RGB);
			if (Double.isNaN(scale) || scale <= 0.0) {
				Utils.showMessage("Invalid scale.");
				return;
			}
			gd.getNextNumber();
			gd.getNextNumber();
			Layer[] layer_array = null;
			boolean non_empty_only = false;
			if (layer.getParent().size() > 1) {
				non_empty_only = gd.getNextBoolean();
				int i_start = gd.getNextChoiceIndex();
				int i_end = gd.getNextChoiceIndex();
				ArrayList<Layer> al = new ArrayList<Layer>();
				ArrayList al_zd = layer.getParent().getZDisplayables();
				ZDisplayable[] zd = new ZDisplayable[al_zd.size()];
				al_zd.toArray(zd);
				for (int i=i_start, j=0; i <= i_end; i++, j++) {
					Layer la = layer.getParent().getLayer(i);
					if (!la.isEmpty() || !non_empty_only) al.add(la); 
				}
				if (0 == al.size()) {
					Utils.showMessage("All layers are empty!");
					return;
				}
				layer_array = new Layer[al.size()];
				al.toArray(layer_array);
			} else {
				layer_array = new Layer[]{Display.this.layer};
			}
			final Color background = new Color((int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
			final boolean quality = gd.getNextBoolean();
			final boolean save_to_file = gd.getNextBoolean();
			final boolean save_for_web = gd.getNextBoolean();
			if (save_for_web) project.getLoader().makePrescaledTiles(layer_array, Patch.class, srcRect, scale, c_alphas, the_type);
			else project.getLoader().makeFlatImage(layer_array, srcRect, scale, c_alphas, the_type, save_to_file, quality, background);
		} else if (command.equals("Lock")) {
			selection.setLocked(true);
			Utils.revalidateComponent(tabs.getSelectedComponent());
		} else if (command.equals("Unlock")) {
			selection.setLocked(false);
			Utils.revalidateComponent(tabs.getSelectedComponent());
		} else if (command.equals("Properties...")) {
			active.adjustProperties();
			updateSelection();
		} else if (command.equals("Show current 2D position in 3D")) {
			Point p = canvas.consumeLastPopupPoint();
			if (null == p) return;
			Display3D.addFatPoint("Current 2D Position", getLayerSet(), p.x, p.y, layer.getZ(), 10, Color.magenta);
		} else if (command.equals("Show layers as orthoslices in 3D")) {
			GenericDialog gd = new GenericDialog("Options");
			Roi roi = canvas.getFakeImagePlus().getRoi();
			Rectangle r = null == roi ? getLayerSet().get2DBounds() : roi.getBounds();
			gd.addMessage("ROI 2D bounds:");
			gd.addNumericField("x:", r.x, 0, 30, "pixels");
			gd.addNumericField("y:", r.y, 0, 30, "pixels");
			gd.addNumericField("width:", r.width, 0, 30, "pixels");
			gd.addNumericField("height:", r.height, 0, 30, "pixels");
			gd.addMessage("Layers to include:");
			Utils.addLayerRangeChoices(layer, gd);
			gd.addMessage("Constrain dimensions to:");
			gd.addNumericField("max width and height:", getLayerSet().getPixelsMaxDimension(), 0, 30, "pixels");
			gd.addMessage("Options:");
			final String[] types = {"Greyscale", "Color RGB"};
			gd.addChoice("Image type:", types, types[0]);
			gd.addCheckbox("Invert images", false);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			int x = (int)gd.getNextNumber(),
			    y = (int)gd.getNextNumber(),
			    width = (int)gd.getNextNumber(),
			    height = (int)gd.getNextNumber();
			final int first = gd.getNextChoiceIndex(),
					  last = gd.getNextChoiceIndex();
			final List<Layer> layers = getLayerSet().getLayers(first, last);
			final int max_dim = (int)gd.getNextNumber();
			float scale = 1;
			if (max_dim < Math.max(width, height)) {
				scale = max_dim / (float)Math.max(width, height);
			}
			final int type = 0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY8 : ImagePlus.COLOR_RGB;
			final boolean invert = gd.getNextBoolean();
			final LayerStack stack = new LayerStack(layers, new Rectangle(x, y, width, height), scale, type, Patch.class, max_dim, invert);
			Display3D.showOrthoslices(stack.getImagePlus(), "LayerSet [" + x + "," + y + "," + width + "," + height + "] " + first + "--" + last, x, y, scale, layers.get(0));
		} else if (command.equals("Align stack slices")) {
			if (getActive() instanceof Patch) {
				final Patch slice = (Patch)getActive();
				if (slice.isStack()) {
					final HashSet hs = slice.getLinkedGroup(new HashSet());
					for (Iterator it = hs.iterator(); it.hasNext(); ) {
						if (it.next().getClass() != Patch.class) {
							Utils.showMessage("Images are linked to other objects, can't proceed to cross-correlate them."); 
							return;
						}
					}
					final LayerSet ls = slice.getLayerSet();
					final HashSet<Displayable> linked = slice.getLinkedGroup(null);
					ls.addTransformStepWithData(linked);
					Bureaucrat burro = AlignTask.registerStackSlices((Patch)getActive()); 
					burro.addPostTask(new Runnable() { public void run() {
						ls.enlargeToFit(linked);
						ls.addTransformStepWithData(linked);
					}});
				} else {
					Utils.log("Align stack slices: selected image is not part of a stack.");
				}
			}
		} else if (command.equals("Align layers with manual landmarks")) {
			setMode(new ManualAlignMode(Display.this));
		} else if (command.equals("Align layers")) {
			final Layer la = layer; 
			la.getParent().addTransformStep(la.getParent().getLayers());
			Bureaucrat burro = AlignLayersTask.alignLayersTask( la );
			burro.addPostTask(new Runnable() { public void run() {
				getLayerSet().enlargeToFit(getLayerSet().getDisplayables(Patch.class));
				la.getParent().addTransformStep(la.getParent().getLayers());
			}});
		} else if (command.equals("Align multi-layer mosaic")) {
			final Layer la = layer; 
			la.getParent().addTransformStep();
			Bureaucrat burro = AlignTask.alignMultiLayerMosaicTask( la );
			burro.addPostTask(new Runnable() { public void run() {
				getLayerSet().enlargeToFit(getLayerSet().getDisplayables(Patch.class));
				la.getParent().addTransformStep();
			}});
		} else if (command.equals("Montage all images in this layer")) {
			final Layer la = layer;
			final List<Patch> patches = new ArrayList<Patch>( (List<Patch>) (List) la.getDisplayables(Patch.class));
			if (patches.size() < 2) {
				Utils.showMessage("Montage needs 2 or more images selected");
				return;
			}
			final Collection<Displayable> col = la.getParent().addTransformStepWithData(Arrays.asList(new Layer[]{la}));
			final ArrayList<Patch> fixed = new ArrayList<Patch>();
			for (final Patch p : patches) {
				if (p.isLocked2()) fixed.add(p);
			}
			if (fixed.isEmpty()) fixed.add(patches.get(0));
			Bureaucrat burro = AlignTask.alignPatchesTask(patches, fixed);
			burro.addPostTask(new Runnable() { public void run() {
				getLayerSet().enlargeToFit(patches);
				la.getParent().addTransformStepWithData(col);
			}});
		} else if (command.equals("Montage selected images (SIFT)")) {
			montage(0);
		} else if (command.equals("Montage selected images (phase correlation)")) {
			montage(1);
		} else if (command.equals("Montage multiple layers (phase correlation)")) {
			final GenericDialog gd = new GenericDialog("Choose range");
			Utils.addLayerRangeChoices(Display.this.layer, gd);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			final List<Layer> layers = getLayerSet().getLayers(gd.getNextChoiceIndex(), gd.getNextChoiceIndex());
			final Collection<Displayable> col = getLayerSet().addTransformStepWithData(layers);
			Bureaucrat burro = StitchingTEM.montageWithPhaseCorrelation(layers);
			if (null == burro) return;
			burro.addPostTask(new Runnable() { public void run() {
				Collection<Displayable> ds = new ArrayList<Displayable>();
				for (Layer la : layers) ds.addAll(la.getDisplayables(Patch.class));
				getLayerSet().enlargeToFit(ds);
				getLayerSet().addTransformStepWithData(col);
			}});
		} else if (command.equals("Montage multiple layers (SIFT)")) {
			final GenericDialog gd = new GenericDialog("Choose range");
			Utils.addLayerRangeChoices(Display.this.layer, gd);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			final List<Layer> layers = getLayerSet().getLayers(gd.getNextChoiceIndex(), gd.getNextChoiceIndex());
			final Collection<Displayable> col = getLayerSet().addTransformStepWithData(layers);
			Bureaucrat burro = AlignTask.montageLayersTask(layers);
			burro.addPostTask(new Runnable() { public void run() {
				Collection<Displayable> ds = new ArrayList<Displayable>();
				for (Layer la : layers) ds.addAll(la.getDisplayables(Patch.class));
				getLayerSet().enlargeToFit(ds);
				getLayerSet().addTransformStepWithData(col);
			}});
		} else if (command.equals("Properties ...")) { 
			GenericDialog gd = new GenericDialog("Properties", Display.this.frame);
			gd.addSlider("layer_scroll_step: ", 1, layer.getParent().size(), Display.this.scroll_step);
			gd.addChoice("snapshots_mode", LayerSet.snapshot_modes, LayerSet.snapshot_modes[layer.getParent().getSnapshotsMode()]);
			gd.addCheckbox("prefer_snapshots_quality", layer.getParent().snapshotsQuality());
			Loader lo = getProject().getLoader();
			boolean using_mipmaps = lo.isMipMapsEnabled();
			gd.addCheckbox("enable_mipmaps", using_mipmaps);
			gd.addCheckbox("enable_layer_pixels virtualization", layer.getParent().isPixelsVirtualizationEnabled());
			double max = layer.getParent().getLayerWidth() < layer.getParent().getLayerHeight() ? layer.getParent().getLayerWidth() : layer.getParent().getLayerHeight();
			gd.addSlider("max_dimension of virtualized layer pixels: ", 0, max, layer.getParent().getPixelsMaxDimension());
			gd.addCheckbox("Show arrow heads in Treeline/AreaTree", layer.getParent().paint_arrows);
			gd.addCheckbox("Show edge confidence boxes in Treeline/AreaTree", layer.getParent().paint_edge_confidence_boxes);
			gd.addCheckbox("Show color cues", layer.getParent().color_cues);
			gd.addSlider("+/- layers to color cue", 0, 10, layer.getParent().n_layers_color_cue);
			gd.addCheckbox("Show color cues for areas", layer.getParent().area_color_cues);
			gd.addCheckbox("Use red/blue for color cues", layer.getParent().use_color_cue_colors);
			gd.addCheckbox("Prepaint images", layer.getParent().prepaint);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			int sc = (int) gd.getNextNumber();
			if (sc < 1) sc = 1;
			Display.this.scroll_step = sc;
			updateInDatabase("scroll_step");
			layer.getParent().setSnapshotsMode(gd.getNextChoiceIndex());
			layer.getParent().setSnapshotsQuality(gd.getNextBoolean());
			boolean generate_mipmaps = gd.getNextBoolean();
			if (using_mipmaps && generate_mipmaps) {
			} else {
				if (using_mipmaps) { 
					lo.flushMipMaps(true);
				} else {
					lo.generateMipMaps(layer.getParent().getDisplayables(Patch.class));
				}
			}
			layer.getParent().setPixelsVirtualizationEnabled(gd.getNextBoolean());
			layer.getParent().setPixelsMaxDimension((int)gd.getNextNumber());
			layer.getParent().paint_arrows = gd.getNextBoolean();
			layer.getParent().paint_edge_confidence_boxes = gd.getNextBoolean();
			layer.getParent().color_cues = gd.getNextBoolean();
			layer.getParent().n_layers_color_cue = (int)gd.getNextNumber();
			layer.getParent().area_color_cues = gd.getNextBoolean();
			layer.getParent().use_color_cue_colors = gd.getNextBoolean();
			layer.getParent().prepaint = gd.getNextBoolean();
			Display.repaint(layer.getParent());
		} else if (command.equals("Adjust snapping parameters...")) {
			AlignTask.p_snap.setup("Snap");
		} else if (command.equals("Adjust fast-marching parameters...")) {
			Segmentation.fmp.setup();
		} else if (command.equals("Adjust arealist paint parameters...")) {
			AreaWrapper.PP.setup();
		} else if (command.equals("Search...")) {
			new Search();
		} else if (command.equals("Select all")) {
			selection.selectAll();
			repaint(Display.this.layer, selection.getBox(), 0);
		} else if (command.equals("Select all visible")) {
			selection.selectAllVisible();
			repaint(Display.this.layer, selection.getBox(), 0);
		} else if (command.equals("Select none")) {
			Rectangle box = selection.getBox();
			selection.clear();
			repaint(Display.this.layer, box, 0);
		} else if (command.equals("Restore selection")) {
			selection.restore();
		} else if (command.equals("Select under ROI")) {
			Roi roi = canvas.getFakeImagePlus().getRoi();
			if (null == roi) return;
			selection.selectAll(roi, true);
		} else if (command.equals("Merge")) {
			Bureaucrat burro = Bureaucrat.create(new Worker.Task("Merging AreaLists") {
				public void exec() {
					ArrayList<Displayable> al_sel = selection.getSelected(AreaList.class);
					al_sel.remove(Display.this.active);
					al_sel.add(0, Display.this.active);
					Set<DoStep> dataedits = new HashSet<DoStep>();
					dataedits.add(new Displayable.DoEdit(Display.this.active).init(Display.this.active, new String[]{"data"}));
					getLayerSet().addChangeTreesStep(dataedits);
					AreaList ali = AreaList.merge(al_sel);
					if (null != ali) {
						for (int i=1; i<al_sel.size(); i++) {
							Object ob = al_sel.get(i);
							if (ob.getClass() == AreaList.class) {
								selection.remove((Displayable)ob);
							}
						}
						selection.updateTransform(ali);
						repaint(ali.getLayerSet(), ali, 0);
					}
				}
			}, Display.this.project);
			burro.addPostTask(new Runnable() { public void run() {
				Set<DoStep> dataedits = new HashSet<DoStep>();
				dataedits.add(new Displayable.DoEdit(Display.this.active).init(Display.this.active, new String[]{"data"}));
				getLayerSet().addChangeTreesStep(dataedits);
			}});
			burro.goHaveBreakfast();
		} else if (command.equals("Reroot")) {
			if (!(active instanceof Tree<?>)) return;
			Point p = canvas.consumeLastPopupPoint();
			if (null == p) return;
			getLayerSet().addDataEditStep(active);
			((Tree)active).reRoot(p.x, p.y, layer, canvas.getMagnification());
			getLayerSet().addDataEditStep(active);
			Display.repaint(getLayerSet());
		} else if (command.equals("Part subtree")) {
			if (!(active instanceof Tree<?>)) return;
			if (!Utils.check("Really part the subtree?")) return;
			Point p = canvas.consumeLastPopupPoint();
			if (null == p) return;
			List<ZDisplayable> ts = ((Tree)active).splitNear(p.x, p.y, layer, canvas.getMagnification());
			if (null == ts) return;
			Displayable elder = Display.this.active;
			for (ZDisplayable t : ts) {
				if (t == elder) continue;
				getLayerSet().add(t); 
				project.getProjectTree().addSibling(elder, t);
			}
			selection.clear();
			selection.selectAll(ts);
			selection.add(elder);
			getLayerSet().addChangeTreesStep();
			Display.repaint(getLayerSet());
		} else if (command.equals("Show tabular view")) {
			if (!(active instanceof Tree<?>)) return;
			((Tree<?>)active).createMultiTableView();
		} else if (command.equals("Mark")) {
			if (!(active instanceof Tree<?>)) return;
			Point p = canvas.consumeLastPopupPoint();
			if (null == p) return;
			if (((Tree<?>)active).markNear(p.x, p.y, layer, canvas.getMagnification())) {
				Display.repaint(getLayerSet());
			}
		} else if (command.equals("Clear marks (selected Trees)")) {
			for (Tree<?> t : selection.get(Tree.class)) {
				t.unmark();
			}
			Display.repaint(getLayerSet());
		} else if (command.equals("Join")) {
			if (!(active instanceof Tree<?>)) return;
			final List<Tree<?>> tlines = (List<Tree<?>>) selection.get(active.getClass());
			if (((Tree)active).canJoin(tlines)) {
				if (!Utils.check("Join these " + tlines.size() + " trees into the tree " + active + " ?")) return;
				Set<DoStep> dataedits = new HashSet<DoStep>(tlines.size());
				for (final Tree<?> tl : tlines) {
					dataedits.add(new Displayable.DoEdit(tl).init(tl, new String[]{"data"}));
				}
				getLayerSet().addChangeTreesStep(dataedits);
				((Tree)active).join(tlines);
				for (final Tree<?> tl : tlines) {
					if (tl == active) continue;
					tl.remove2(false);
				}
				Display.repaint(getLayerSet());
				Set<DoStep> dataedits2 = new HashSet<DoStep>(1);
				dataedits2.add(new Displayable.DoEdit(active).init(active, new String[]{"data"}));
				getLayerSet().addChangeTreesStep(dataedits2);
			}
		} else if (command.equals("Previous branch point or start")) {
			if (!(active instanceof Tree<?>)) return;
			Point p = canvas.consumeLastPopupPoint();
			if (null == p) return;
			center(((Treeline)active).findPreviousBranchOrRootPoint(p.x, p.y, layer, canvas));
		} else if (command.equals("Next branch point or end")) {
			if (!(active instanceof Tree<?>)) return;
			Point p = canvas.consumeLastPopupPoint();
			if (null == p) return;
			center(((Tree<?>)active).findNextBranchOrEndPoint(p.x, p.y, layer, canvas));
		} else if (command.equals("Root")) {
			if (!(active instanceof Tree<?>)) return;
			Point p = canvas.consumeLastPopupPoint();
			if (null == p) return;
			center(((Tree)active).createCoordinate(((Tree<?>)active).getRoot()));
		} else if (command.equals("Last added point")) {
			if (!(active instanceof Tree<?>)) return;
			center(((Treeline)active).getLastAdded());
		} else if (command.equals("Last edited point")) {
			if (!(active instanceof Tree<?>)) return;
			center(((Treeline)active).getLastEdited());
		} else if (command.equals("Reverse point order")) {
			if (!(active instanceof Pipe)) return;
			getLayerSet().addDataEditStep(active);
			((Pipe)active).reverse();
			Display.repaint(Display.this.layer);
			getLayerSet().addDataEditStep(active);
		} else if (command.equals("View orthoslices")) {
			if (!(active instanceof Patch)) return;
			Display3D.showOrthoslices(((Patch)active));
		} else if (command.equals("View volume")) {
			if (!(active instanceof Patch)) return;
			Display3D.showVolume(((Patch)active));
		} else if (command.equals("Show in 3D")) {
			for (ZDisplayable zd : selection.get(ZDisplayable.class)) {
				Display3D.show(zd.getProject().findProjectThing(zd));
			}
			HashSet<ProjectThing> hs = new HashSet<ProjectThing>();
			for (Profile d : selection.get(Profile.class)) {
				ProjectThing profile_list = (ProjectThing)d.getProject().findProjectThing(d).getParent();
				if (!hs.contains(profile_list)) {
					Display3D.show(profile_list);
					hs.add(profile_list);
				}
			}
		} else if (command.equals("Snap")) {
			if (!(active instanceof Patch)) return;
			Display.snap((Patch)active);
		} else if (command.equals("Blend")) {
			HashSet<Patch> patches = new HashSet<Patch>();
			for (final Displayable d : selection.getSelected()) {
				if (d.getClass() == Patch.class) patches.add((Patch)d);
			}
			if (patches.size() > 1) {
				GenericDialog gd = new GenericDialog("Blending");
				gd.addCheckbox("Respect current alpha mask", true);
				gd.showDialog();
				if (gd.wasCanceled()) return;
				Blending.blend(patches, gd.getNextBoolean());
			} else {
				IJ.log("Please select more than one overlapping image.");
			}
		} else if (command.equals("Montage")) {
			final Set<Displayable> affected = new HashSet<Displayable>(selection.getAffected());
			final LayerSet ls = layer.getParent();
			ls.addTransformStepWithData(affected);
			Bureaucrat burro = AlignTask.alignSelectionTask( selection );
			burro.addPostTask(new Runnable() { public void run() {
				ls.enlargeToFit(affected);
				ls.addTransformStepWithData(affected);
			}});
		} else if (command.equals("Lens correction")) {
			final Layer la = layer;
			la.getParent().addDataEditStep(new HashSet<Displayable>(la.getParent().getDisplayables()));
			Bureaucrat burro = DistortionCorrectionTask.correctDistortionFromSelection( selection );
			burro.addPostTask(new Runnable() { public void run() {
				la.getParent().addDataEditStep(new HashSet<Displayable>(la.getParent().getDisplayables()));
			}});
		} else if (command.equals("Link images...")) {
			GenericDialog gd = new GenericDialog("Options");
			gd.addMessage("Linking images to images (within their own layer only):");
			String[] options = {"all images to all images", "each image with any other overlapping image"};
			gd.addChoice("Link: ", options, options[1]);
			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"};
			gd.addChoice("Apply to: ", options2, options2[0]);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			Layer lay = layer;
			final HashSet<Displayable> ds = new HashSet<Displayable>(lay.getParent().getDisplayables());
			lay.getParent().addDataEditStep(ds, new String[]{"data"});
			boolean overlapping_only = 1 == gd.getNextChoiceIndex();
			Collection<Displayable> coll = null;
			switch (gd.getNextChoiceIndex()) {
				case 0:
					coll = selection.getSelected(Patch.class);
					Patch.crosslink(coll, overlapping_only);
					break;
				case 1:
					coll = lay.getDisplayables(Patch.class);
					Patch.crosslink(coll, overlapping_only);
					break;
				case 2:
					coll = new ArrayList<Displayable>();
					for (final Layer la : lay.getParent().getLayers()) {
						Collection<Displayable> acoll = la.getDisplayables(Patch.class);
						Patch.crosslink(acoll, overlapping_only);
						coll.addAll(acoll);
					}
					break;
				case 3:
					ArrayList<Layer> layers = lay.getParent().getLayers();
					Collection<Displayable> lc1 = layers.get(0).getDisplayables(Patch.class);
					if (lay == layers.get(0)) coll = lc1;
					for (int i=1; i<layers.size(); i++) {
						Collection<Displayable> lc2 = layers.get(i).getDisplayables(Patch.class);
						if (null == coll && Display.this.layer == layers.get(i)) coll = lc2;
						Collection<Displayable> both = new ArrayList<Displayable>();
						both.addAll(lc1);
						both.addAll(lc2);
						Patch.crosslink(both, overlapping_only);
						lc1 = lc2;
					}
					break;
			}
			if (null != coll) Display.updateCheckboxes(coll, DisplayablePanel.LINK_STATE, true);
			lay.getParent().addDataEditStep(ds);
		} else if (command.equals("Unlink all selected images")) {
			if (Utils.check("Really unlink selected images?")) {
				for (final Displayable d : selection.getSelected(Patch.class)) {
					d.unlink();
				}
			}
		} else if (command.equals("Unlink all")) {
			if (Utils.check("Really unlink all objects from all layers?")) {
				Collection<Displayable> ds = layer.getParent().getDisplayables();
				for (final Displayable d : ds) {
					d.unlink();
				}
				Display.updateCheckboxes(ds, DisplayablePanel.LOCK_STATE);
			}
		} else if (command.equals("Calibration...")) {
			try {
				IJ.run(canvas.getFakeImagePlus(), "Properties...", "");
				Display.updateTitle(getLayerSet());
			} catch (RuntimeException re) {
				Utils.log2("Calibration dialog canceled.");
			}
		} else if (command.equals("Grid overlay...")) {
			if (null == gridoverlay) gridoverlay = new GridOverlay();
			gridoverlay.setup(canvas.getFakeImagePlus().getRoi());
			canvas.repaint(false);
		} else if (command.equals("Enhance contrast (selected images)...")) {
			final Layer la = layer;
			ArrayList<Displayable> selected = selection.getSelected(Patch.class);
			final HashSet<Displayable> ds = new HashSet<Displayable>(selected);
			la.getParent().addDataEditStep(ds);
			Displayable active = Display.this.getActive();
			Patch ref = active.getClass() == Patch.class ? (Patch)active : null;
			Bureaucrat burro = getProject().getLoader().enhanceContrast(selected, ref);
			burro.addPostTask(new Runnable() { public void run() {
				la.getParent().addDataEditStep(ds);
			}});
		} else if (command.equals("Enhance contrast layer-wise...")) {
			final GenericDialog gd = new GenericDialog("Choose range");
			Utils.addLayerRangeChoices(Display.this.layer, gd);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			java.util.List<Layer> layers = layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1); 
			final HashSet<Displayable> ds = new HashSet<Displayable>();
			for (final Layer l : layers) ds.addAll(l.getDisplayables(Patch.class));
			getLayerSet().addDataEditStep(ds);
			Bureaucrat burro = project.getLoader().enhanceContrast(layers);
			burro.addPostTask(new Runnable() { public void run() {
				getLayerSet().addDataEditStep(ds);
			}});
		} else if (command.equals("Set Min and Max layer-wise...")) {
			Displayable active = getActive();
			double min = 0;
			double max = 0;
			if (null != active && active.getClass() == Patch.class) {
				min = ((Patch)active).getMin();
				max = ((Patch)active).getMax();
			}
			final GenericDialog gd = new GenericDialog("Min and Max");
			gd.addMessage("Set min and max to all images in the layer range");
			Utils.addLayerRangeChoices(Display.this.layer, gd);
			gd.addNumericField("min: ", min, 2);
			gd.addNumericField("max: ", max, 2);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			min = gd.getNextNumber();
			max = gd.getNextNumber();
			ArrayList<Displayable> al = new ArrayList<Displayable>();
			for (final Layer la : layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1)) { 
				al.addAll(la.getDisplayables(Patch.class));
			}
			final HashSet<Displayable> ds = new HashSet<Displayable>(al);
			getLayerSet().addDataEditStep(ds);
			Bureaucrat burro = project.getLoader().setMinAndMax(al, min, max);
			burro.addPostTask(new Runnable() { public void run() {
				getLayerSet().addDataEditStep(ds);
			}});
		} else if (command.equals("Set Min and Max (selected images)...")) {
			Displayable active = getActive();
			double min = 0;
			double max = 0;
			if (null != active && active.getClass() == Patch.class) {
				min = ((Patch)active).getMin();
				max = ((Patch)active).getMax();
			}
			final GenericDialog gd = new GenericDialog("Min and Max");
			gd.addMessage("Set min and max to all selected images");
			gd.addNumericField("min: ", min, 2);
			gd.addNumericField("max: ", max, 2);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			min = gd.getNextNumber();
			max = gd.getNextNumber();
			final HashSet<Displayable> ds = new HashSet<Displayable>(selection.getSelected(Patch.class));
			getLayerSet().addDataEditStep(ds);
			Bureaucrat burro = project.getLoader().setMinAndMax(selection.getSelected(Patch.class), min, max);
			burro.addPostTask(new Runnable() { public void run() {
				getLayerSet().addDataEditStep(ds);
			}});
		} else if (command.equals("Adjust min and max (selected images)...")) {
			final List<Displayable> list = selection.getSelected(Patch.class);
			if (list.isEmpty()) {
				Utils.log("No images selected!");
				return;
			}
			Bureaucrat.createAndStart(new Worker.Task("Init contrast adjustment") {
				public void exec() {
					try {
						setMode(new ContrastAdjustmentMode(Display.this, list));
					} catch (Exception e) {
						Utils.log("All images must be of the same type!");
					}
				}
			}, list.get(0).getProject());
		} else if (command.equals("Mask image borders (layer-wise)...")) {
			final GenericDialog gd = new GenericDialog("Mask borders");
			Utils.addLayerRangeChoices(Display.this.layer, gd);
			gd.addMessage("Borders:");
			gd.addNumericField("left: ", 6, 2);
			gd.addNumericField("top: ", 6, 2);
			gd.addNumericField("right: ", 6, 2);
			gd.addNumericField("bottom: ", 6, 2);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			Collection<Layer> layers = layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1);
			final HashSet<Displayable> ds = new HashSet<Displayable>();
			for (Layer l : layers) ds.addAll(l.getDisplayables(Patch.class));
			getLayerSet().addDataEditStep(ds);
			Bureaucrat burro = project.getLoader().maskBordersLayerWise(layers, (int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
			burro.addPostTask(new Runnable() { public void run() {
				getLayerSet().addDataEditStep(ds);
			}});
		} else if (command.equals("Mask image borders (selected images)...")) {
			final GenericDialog gd = new GenericDialog("Mask borders");
			gd.addMessage("Borders:");
			gd.addNumericField("left: ", 6, 2);
			gd.addNumericField("top: ", 6, 2);
			gd.addNumericField("right: ", 6, 2);
			gd.addNumericField("bottom: ", 6, 2);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			Collection<Displayable> patches = selection.getSelected(Patch.class);
			final HashSet<Displayable> ds = new HashSet<Displayable>(patches);
			getLayerSet().addDataEditStep(ds);
			Bureaucrat burro = project.getLoader().maskBorders(patches, (int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
			burro.addPostTask(new Runnable() { public void run() {
				getLayerSet().addDataEditStep(ds);
			}});
		} else if (command.equals("Split images under polyline ROI")) {
			Roi roi = canvas.getFakeImagePlus().getRoi();
			if (null == roi) return;
			if (roi.getType() != Roi.POLYLINE) {
				Utils.showMessage("Need a polyline ROI, not just any ROI!");
				return;
			}
			if (!Utils.check("Really split images under the polyline ROI?")) {
				return;
			}
			final Set<Displayable> col = new HashSet<Displayable>();
			PolygonRoi proi = (PolygonRoi)roi;
			int[] x = proi.getXCoordinates(),
				  y = proi.getYCoordinates();
			Rectangle b = proi.getBounds();
			Polygon[] pols = new Polygon[proi.getNCoordinates() -1];
			for (int i=0; i<pols.length; i++) {
				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},
									  new int[]{b.y + y[i], b.y + y[i], b.y + y[i+1], b.y + y[i+1]}, 4);
			}
			for (final Patch p : getLayer().getAll(Patch.class)) {
				if (!p.isVisible()) continue;
				final Area a = p.getArea();
				for (int i=0; i<pols.length; i++) {
					Area c = new Area(pols[i]);
					c.intersect(a);
					if (M.isEmpty(c)) continue;
					col.add(p);
					break;
				}
			}
			if (col.isEmpty()) {
				Utils.showMessage("No images intersect the ROI!");
				return;
			}
			for (int i=1; i<proi.getNCoordinates(); i++) {
				for (int k=i+2; k<proi.getNCoordinates(); k++) { 
					if (null != M.computeSegmentsIntersection(x[i-1], y[i-1], x[i], y[i],
															  x[k-1], y[k-1], x[k], y[k])) {
						Utils.showMessage("Cannot split images with a polygon ROI that intersects itself!");
						return;
					}
				}					
			}
			final Area[] as = M.splitArea(new Area(getLayerSet().get2DBounds()), proi, getLayerSet().get2DBounds());
			Color[] c = new Color[]{Color.blue, Color.red};
			int i = 0;
			for (Area a : as) {
				getLayer().getOverlay().add(a, c[i++], null, true, false, 0.4f);
			}
			Display.repaint(getLayer());
			final YesNoDialog yn = new YesNoDialog(frame, "Check", "Does the splitting match your expectations?\nPush 'yes' to split the images.", false);
			yn.setModal(false);
			for (WindowListener wl : yn.getWindowListeners()) yn.removeWindowListener(wl);
			yn.setClosingTask(new Runnable() {
				@Override
				public void run() {
					try {
						for (Area a : as) {
							getLayer().getOverlay().remove(a);
						}
						if (!yn.yesPressed()) {
							Utils.log2("Pushed 'no'");
							return;
						}
						Bureaucrat.createAndStart(new Worker.Task("Spliting images") {
							public void exec() {
								final Roi r1 = new ShapeRoi(as[0]),
										  r2 = new ShapeRoi(as[1]);
								ArrayList<Future<?>> fus = new ArrayList<Future<?>>();
								for (final Patch p : (Collection<Patch>)(Collection)col) {
									Patch copy = (Patch) p.clone(p.getProject(), false);
									p.addAlphaMask(r1, 0);
									copy.addAlphaMask(r2, 0);
									fus.add(p.updateMipMaps());
									fus.add(copy.updateMipMaps());
									p.getLayer().add(copy); 
								}
								Utils.wait(fus);
							}
						}, project);
					} catch (Throwable t) {
						IJError.print(t);
					} finally {
						yn.dispose();
						Display.repaint(getLayer());
					}
				}
			});
			yn.setVisible(true);
		} else if (command.equals("Duplicate")) {
			final HashSet<Class> accepted = new HashSet<Class>();
			accepted.add(Patch.class);
			accepted.add(DLabel.class);
			accepted.add(Stack.class);
			final ArrayList<Displayable> originals = new ArrayList<Displayable>();
			final ArrayList<Displayable> selected = selection.getSelected();
			for (final Displayable d : selected) {
				if (accepted.contains(d.getClass())) {
					originals.add(d);
				}
			}
			if (originals.size() > 0) {
				getLayerSet().addChangeTreesStep();
				for (final Displayable d : originals) {
					if (d instanceof ZDisplayable) {
						d.getLayerSet().add((ZDisplayable)d.clone());
					} else {
						d.getLayer().add(d.clone());
					}
				}
				getLayerSet().addChangeTreesStep();
			} else if (selected.size() > 0) {
				Utils.log("Can only duplicate images and text labels.\nDuplicate *other* objects in the Project Tree.\n");
			}
		} else if (command.equals("Create subproject")) {
			Roi roi = canvas.getFakeImagePlus().getRoi();
			if (null == roi) return; 
			Layer first, last;
			if (1 == layer.getParent().size()) {
				first = last = layer;
			} else {
				GenericDialog gd = new GenericDialog("Choose layer range");
				Utils.addLayerRangeChoices(layer, gd);
				gd.showDialog();
				if (gd.wasCanceled()) return;
				first = layer.getParent().getLayer(gd.getNextChoiceIndex());
				last = layer.getParent().getLayer(gd.getNextChoiceIndex());
				Utils.log2("first, last: " + first + ", " + last);
			}
			Project sub = getProject().createSubproject(roi.getBounds(), first, last);
			if (null == sub) {
				Utils.log("ERROR: failed to create subproject.");
				return;
			}
			final LayerSet subls = sub.getRootLayerSet();
			Display.createDisplay(sub, subls.getLayer(0));
		} else if (command.startsWith("Image stack under selected Arealist")) {
			if (null == active || active.getClass() != AreaList.class) return;
			GenericDialog gd = new GenericDialog("Stack options");
			String[] types = {"8-bit", "16-bit", "32-bit", "RGB"};
			gd.addChoice("type:", types, types[0]);
			gd.addSlider("Scale: ", 1, 100, 100);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			final int type;
			switch (gd.getNextChoiceIndex()) {
				case 0: type = ImagePlus.GRAY8; break;
				case 1: type = ImagePlus.GRAY16; break;
				case 2: type = ImagePlus.GRAY32; break;
				case 3: type = ImagePlus.COLOR_RGB; break;
				default: type = ImagePlus.GRAY8; break;
			}
			ImagePlus imp = ((AreaList)active).getStack(type, gd.getNextNumber()/100);
			if (null != imp) imp.show();
		} else if (command.equals("Fly through selected Treeline/AreaTree")) {
			if (null == active || !(active instanceof Tree<?>)) return;
			Bureaucrat.createAndStart(new Worker.Task("Creating fly through", true) {
				public void exec() {
					GenericDialog gd = new GenericDialog("Fly through");
					gd.addNumericField("Width", 512, 0);
					gd.addNumericField("Height", 512, 0);
					String[] types = new String[]{"8-bit gray", "Color RGB"};
					gd.addChoice("Image type", types, types[0]);
					gd.addSlider("scale", 0, 100, 100);
					gd.addCheckbox("save to file", false);
					gd.showDialog();
					if (gd.wasCanceled()) return;
					int w = (int)gd.getNextNumber();
					int h = (int)gd.getNextNumber();
					int type = 0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY8 : ImagePlus.COLOR_RGB;
					double scale = gd.getNextNumber();
					if (w <=0 || h <=0) {
						Utils.log("Invalid width or height: " + w + ", " + h);
						return;
					}
					if (0 == scale || Double.isNaN(scale)) {
						Utils.log("Invalid scale: " + scale);
						return;
					}
					String dir = null;
					if (gd.getNextBoolean()) {
						DirectoryChooser dc = new DirectoryChooser("Target directory");
						dir = dc.getDirectory();
						if (null == dir) return; 
						dir = Utils.fixDir(dir);
					}
					ImagePlus imp = ((Tree<?>)active).flyThroughMarked(w, h, scale/100, type, dir);
					if (null == imp) {
						Utils.log("Mark a node first!");
						return;
					}
					imp.show();
				}
			}, project);
		} else if (command.startsWith("Arealists as labels")) {
			GenericDialog gd = new GenericDialog("Export labels");
			gd.addSlider("Scale: ", 1, 100, 100);
			final String[] options = {"All area list", "Selected area lists"};
			gd.addChoice("Export: ", options, options[0]);
			Utils.addLayerRangeChoices(layer, gd);
			gd.addCheckbox("Visible only", true);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			final float scale = (float)(gd.getNextNumber() / 100);
			java.util.List<Displayable> al = (java.util.List<Displayable>)(0 == gd.getNextChoiceIndex() ? layer.getParent().getZDisplayables(AreaList.class) : selection.getSelectedSorted(AreaList.class));
			if (null == al) {
				Utils.log("No area lists found to export.");
				return;
			}
			int first = gd.getNextChoiceIndex();
			int last  = gd.getNextChoiceIndex();
			boolean visible_only = gd.getNextBoolean();
			if (-1 != command.indexOf("(amira)")) {
				AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, true, true);
			} else if (-1 != command.indexOf("(tif)")) {
				AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, false, false);
			}
		} else if (command.equals("Project properties...")) {
			project.adjustProperties();
		} else if (command.equals("Release memory...")) {
			Bureaucrat.createAndStart(new Worker("Releasing memory") {
				public void run() {
					startedWorking();
					try {
			GenericDialog gd = new GenericDialog("Release Memory");
			int max = (int)(IJ.maxMemory() / 1000000);
			gd.addSlider("Megabytes: ", 0, max, max/2);
			gd.showDialog();
			if (!gd.wasCanceled()) {
				int n_mb = (int)gd.getNextNumber();
				project.getLoader().releaseToFit((long)n_mb*1000000);
			}
					} catch (Throwable e) {
						IJError.print(e);
					} finally {
						finishedWorking();
					}
				}
			}, project);
		} else if (command.equals("Flush image cache")) {
			Loader.releaseAllCaches();
		} else if (command.equals("Regenerate all mipmaps")) {
			project.getLoader().regenerateMipMaps(getLayerSet().getDisplayables(Patch.class));
		} else if (command.equals("Regenerate mipmaps (selected images)")) {
			project.getLoader().regenerateMipMaps(selection.getSelected(Patch.class));
		} else if (command.equals("Tags...")) {
			File f = Utils.chooseFile(null, "tags", ".xml");
			if (null == f) return;
			if (!Utils.saveToFile(f, getLayerSet().exportTags())) {
				Utils.logAll("ERROR when saving tags to file " + f.getAbsolutePath());
			}
		} else if (command.equals("Tags ...")) {
			String[] ff = Utils.selectFile("Import tags");
			if (null == ff) return;
			GenericDialog gd = new GenericDialog("Import tags");
			String[] modes = new String[]{"Append to current tags", "Replace current tags"};
			gd.addChoice("Import tags mode:", modes, modes[0]);
			gd.addMessage("Replacing current tags\nwill remove all tags\n from all nodes first!");
			gd.showDialog();
			if (gd.wasCanceled()) return;
			getLayerSet().importTags(new StringBuilder(ff[0]).append('/').append(ff[1]).toString(), 1 == gd.getNextChoiceIndex());
		} else if (command.equals("Connectivity graph...")) {
			Bureaucrat.createAndStart(new Worker.Task("Connectivity graph") {
				public void exec() {
					Graph.extractAndShowGraph(getLayerSet());
				}
			}, getProject());
		} else if (command.equals("Measure")) {
			if (selection.isEmpty()) {
				Utils.log("Nothing selected to measure!");
				return;
			}
			selection.measure();
		} else {
			Utils.log2("Display: don't know what to do with command " + command);
		}
		}});

	public void actionPerformed(final ActionEvent ae) {
		dispatcher.exec(new Runnable() { public void run() {
		String command = ae.getActionCommand();
		if (command.startsWith("Job")) {
			if (Utils.checkYN("Really cancel job?")) {
				project.getLoader().quitJob(command);
				repairGUI();
			}
			return;
		} else if (command.equals("Move to top")) {
			if (null == active) return;
			canvas.setUpdateGraphics(true);
			layer.getParent().move(LayerSet.TOP, active);
			Display.repaint(layer.getParent(), active, 5);
		} else if (command.equals("Move up")) {
			if (null == active) return;
			canvas.setUpdateGraphics(true);
			layer.getParent().move(LayerSet.UP, active);
			Display.repaint(layer.getParent(), active, 5);
		} else if (command.equals("Move down")) {
			if (null == active) return;
			canvas.setUpdateGraphics(true);
			layer.getParent().move(LayerSet.DOWN, active);
			Display.repaint(layer.getParent(), active, 5);
		} else if (command.equals("Move to bottom")) {
			if (null == active) return;
			canvas.setUpdateGraphics(true);
			layer.getParent().move(LayerSet.BOTTOM, active);
			Display.repaint(layer.getParent(), active, 5);
		} else if (command.equals("Duplicate, link and send to next layer")) {
			duplicateLinkAndSendTo(active, 1, layer.getParent().next(layer));
		} else if (command.equals("Duplicate, link and send to previous layer")) {
			duplicateLinkAndSendTo(active, 0, layer.getParent().previous(layer));
		} else if (command.equals("Duplicate, link and send to...")) {
			GenericDialog gd = new GenericDialog("Send to");
			gd.addMessage("Duplicate, link and send to...");
			String[] sl = new String[layer.getParent().size()];
			int next = 0;
			for (Iterator it = layer.getParent().getLayers().iterator(); it.hasNext(); ) {
				sl[next++] = project.findLayerThing(it.next()).toString();
			}
			gd.addChoice("Layer: ", sl, sl[layer.getParent().indexOf(layer)]);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			Layer la = layer.getParent().getLayer(gd.getNextChoiceIndex());
			if (layer == la) {
				Utils.showMessage("Can't duplicate, link and send to the same layer.");
				return;
			}
			duplicateLinkAndSendTo(active, 0, la);
		} else if (-1 != command.indexOf("z = ")) {
			Layer target_layer = layer.getParent().getLayer(Double.parseDouble(command.substring(command.lastIndexOf(' ') +1)));
			Utils.log2("layer: __" +command.substring(command.lastIndexOf(' ') +1) + "__");
			if (null == target_layer) return;
			duplicateLinkAndSendTo(active, 0, target_layer);
		} else if (-1 != command.indexOf("z=")) {
			int iz = command.indexOf("z=")+2;
			Utils.log2("iz=" + iz + "  other: " + command.indexOf(' ', iz+2));
			int end = command.indexOf(' ', iz);
			if (-1 == end) end = command.length();
			double lz = Double.parseDouble(command.substring(iz, end));
			Layer target = layer.getParent().getLayer(lz);
			layer.getParent().move(selection.getAffected(), active.getLayer(), target); 
		} else if (command.equals("Unlink")) {
			if (null == active || active instanceof Patch) return;
			active.unlink();
			updateSelection();
		} else if (command.equals("Unlink from images")) {
			if (null == active) return;
			try {
				for (Displayable displ: selection.getSelected()) {
					displ.unlinkAll(Patch.class);
				}
				updateSelection();
			} catch (Exception e) { IJError.print(e); }
		} else if (command.equals("Unlink slices")) {
			YesNoCancelDialog yn = new YesNoCancelDialog(frame, "Attention", "Really unlink all slices from each other?\nThere is no undo.");
			if (!yn.yesPressed()) return;
			final ArrayList<Patch> pa = ((Patch)active).getStackPatches();
			for (int i=pa.size()-1; i>0; i--) {
				pa.get(i).unlink(pa.get(i-1));
			}
		} else if (command.equals("Send to next layer")) {
			Rectangle box = selection.getBox();
			try {
				for (final Displayable displ : selection.getSelected()) {
					displ.unlinkAll(Patch.class);
				}
				updateSelection();
			} catch (Exception e) { IJError.print(e); }
			selection.moveDown();
			repaint(layer.getParent(), box);
		} else if (command.equals("Send to previous layer")) {
			Rectangle box = selection.getBox();
			try {
				for (final Displayable displ : selection.getSelected()) {
					displ.unlinkAll(Patch.class);
				}
				updateSelection();
			} catch (Exception e) { IJError.print(e); }
			selection.moveUp();
			repaint(layer.getParent(), box);
		} else if (command.equals("Show centered")) {
			if (active == null) return;
			showCentered(active);
		} else if (command.equals("Delete...")) {
			selection.deleteAll();
		} else if (command.equals("Color...")) {
			IJ.doCommand("Color Picker...");
		} else if (command.equals("Revert")) {
			if (null == active || active.getClass() != Patch.class) return;
			Patch p = (Patch)active;
			if (!p.revert()) {
				if (null == p.getOriginalPath()) Utils.log("No editions to save for patch " + p.getTitle() + " #" + p.getId());
				else Utils.log("Could not revert Patch " + p.getTitle() + " #" + p.getId());
			}
		} else if (command.equals("Remove alpha mask")) {
			final ArrayList<Displayable> patches = selection.getSelected(Patch.class); 
			if (patches.size() > 0) {
				Bureaucrat.createAndStart(new Worker.Task("Removing alpha mask" + (patches.size() > 1 ? "s" : "")) { public void exec() {
					final ArrayList<Future> jobs = new ArrayList<Future>();
					for (final Displayable d : patches) {
						final Patch p = (Patch) d;
						p.setAlphaMask(null);
						Future job = p.getProject().getLoader().regenerateMipMaps(p); 
						if (null != job) jobs.add(job);
					}
					for (final Future job : jobs) try {
						job.get();
					} catch (Exception ie) {}
				}}, patches.get(0).getProject());
			}
		} else if (command.equals("Undo")) {
			Bureaucrat.createAndStart(new Worker.Task("Undo") { public void exec() {
				layer.getParent().undoOneStep();
				Display.repaint(layer.getParent());
			}}, project);
		} else if (command.equals("Redo")) {
			Bureaucrat.createAndStart(new Worker.Task("Redo") { public void exec() {
				layer.getParent().redoOneStep();
				Display.repaint(layer.getParent());
			}}, project);
		} else if (command.equals("Apply transform")) {
			canvas.applyTransform();
		} else if (command.equals("Apply transform propagating to last layer")) {
			if (mode.getClass() == AffineTransformMode.class || mode.getClass() == NonLinearTransformMode.class) {
				final LayerSet ls = getLayerSet();
				final HashSet<Layer> subset = new HashSet<Layer>(ls.getLayers(ls.indexOf(Display.this.layer)+1, ls.size()-1)); 
				if (mode.getClass() == AffineTransformMode.class) ((AffineTransformMode)mode).applyAndPropagate(subset);
				else if (mode.getClass() == NonLinearTransformMode.class) ((NonLinearTransformMode)mode).apply(subset);
				setMode(new DefaultMode(Display.this));
			}
		} else if (command.equals("Apply transform propagating to first layer")) {
			if (mode.getClass() == AffineTransformMode.class || mode.getClass() == NonLinearTransformMode.class) {
				final LayerSet ls = getLayerSet();
				final HashSet<Layer> subset = new HashSet<Layer>(ls.getLayers(0, ls.indexOf(Display.this.layer) -1)); 
				if (mode.getClass() == AffineTransformMode.class) ((AffineTransformMode)mode).applyAndPropagate(subset);
				else if (mode.getClass() == NonLinearTransformMode.class) ((NonLinearTransformMode)mode).apply(subset);
				setMode(new DefaultMode(Display.this));
			}
		} else if (command.equals("Cancel transform")) {
			canvas.cancelTransform(); 
		} else if (command.equals("Specify transform...")) {
			if (null == active) return;
			selection.specify();
		} else if (command.equals("Hide all but images")) {
			ArrayList<Class<?>> type = new ArrayList<Class<?>>();
			type.add(Patch.class);
			type.add(Stack.class);
			Collection<Displayable> col = layer.getParent().hideExcept(type, false);
			selection.removeAll(col);
			Display.updateCheckboxes(col, DisplayablePanel.VISIBILITY_STATE);
			Display.update(layer.getParent(), false);
		} else if (command.equals("Unhide all")) {
			Display.updateCheckboxes(layer.getParent().setAllVisible(false), DisplayablePanel.VISIBILITY_STATE);
			Display.update(layer.getParent(), false);
		} else if (command.startsWith("Hide all ")) {
			String type = command.substring(9, command.length() -1); 
			Collection<Displayable> col = layer.getParent().setVisible(type, false, true);
			selection.removeAll(col);
			Display.updateCheckboxes(col, DisplayablePanel.VISIBILITY_STATE);
		} else if (command.startsWith("Unhide all ")) {
			String type = command.substring(11, command.length() -1); 
			type = type.substring(0, 1).toUpperCase() + type.substring(1);
			updateCheckboxes(layer.getParent().setVisible(type, true, true), DisplayablePanel.VISIBILITY_STATE);
		} else if (command.equals("Hide deselected")) {
			hideDeselected(0 != (ActionEvent.ALT_MASK & ae.getModifiers()));
		} else if (command.equals("Hide deselected except images")) {
			hideDeselected(true);
		} else if (command.equals("Hide selected")) {
			selection.setVisible(false); 
			Display.updateCheckboxes(selection.getSelected(), DisplayablePanel.VISIBILITY_STATE);
		} else if (command.equals("Resize canvas/LayerSet...")) {
			resizeCanvas();
		} else if (command.equals("Autoresize canvas/LayerSet")) {
			layer.getParent().setMinimumDimensions();
		} else if (command.equals("Import image")) {
			importImage();
		} else if (command.equals("Import next image")) {
			importNextImage();
		} else if (command.equals("Import stack...")) {
			Display.this.getLayerSet().addChangeTreesStep();
			Rectangle sr = getCanvas().getSrcRect();
			Bureaucrat burro = project.getLoader().importStack(layer, sr.x + sr.width/2, sr.y + sr.height/2, null, true, null, false);
			burro.addPostTask(new Runnable() { public void run() {
				Display.this.getLayerSet().addChangeTreesStep();
			}});
		} else if (command.equals("Import stack with landmarks...")) {
			List<Project> pr = Project.getProjects();
			if (1 == pr.size()) {
				Utils.logAll("Need another project open!");
				return;
			}
			GenericDialog gd = new GenericDialog("Landmarks");
			gd.addStringField("landmarks type:", "landmarks");
			final String[] none = {"-- None --"};
			final Hashtable<String,Project> mpr = new Hashtable<String,Project>();
			for (Project p : pr) {
				if (p == project) continue;
				mpr.put(p.toString(), p);
			}
			final String[] project_titles = mpr.keySet().toArray(new String[0]);
			final Hashtable<String,ProjectThing> map_target = findLandmarkNodes(project, "landmarks");
			String[] target_landmark_titles = map_target.isEmpty() ? none : map_target.keySet().toArray(new String[0]);
			gd.addChoice("Landmarks node in this project:", target_landmark_titles, target_landmark_titles[0]);
			gd.addMessage("");
			gd.addChoice("Source project:", project_titles, project_titles[0]);
			final Hashtable<String,ProjectThing> map_source = findLandmarkNodes(mpr.get(project_titles[0]), "landmarks");
			String[] source_landmark_titles = map_source.isEmpty() ? none : map_source.keySet().toArray(new String[0]);
			gd.addChoice("Landmarks node in source project:", source_landmark_titles, source_landmark_titles[0]);
			final List<Patch> stacks = Display.getPatchStacks(mpr.get(project_titles[0]).getRootLayerSet());
			String[] stack_titles;
			if (stacks.isEmpty()) {
				if (1 == mpr.size()) {
					IJ.showMessage("Project " + project_titles[0] + " does not contain any Stack.");
					return;
				}
				stack_titles = none;
			} else {
				stack_titles = new String[stacks.size()];
				int next = 0;
				for (Patch pa : stacks) stack_titles[next++] = pa.toString();
			}
			gd.addChoice("Stacks:", stack_titles, stack_titles[0]);
			Vector vc = gd.getChoices();
			final Choice choice_target_landmarks = (Choice) vc.get(0);
			final Choice choice_source_projects = (Choice) vc.get(1);
			final Choice choice_source_landmarks = (Choice) vc.get(2);
			final Choice choice_stacks = (Choice) vc.get(3);
			final TextField input = (TextField) gd.getStringFields().get(0);
			input.addTextListener(new TextListener() {
				public void textValueChanged(TextEvent te) {
					final String text = input.getText();
					update(choice_target_landmarks, Display.this.project, text, map_target);
					update(choice_source_landmarks, mpr.get(choice_source_projects.getSelectedItem()), text, map_source);
				}
				private void update(Choice c, Project p, String type, Hashtable<String,ProjectThing> table) {
					table.clear();
					table.putAll(findLandmarkNodes(p, type));
					c.removeAll();
					if (table.isEmpty()) c.add(none[0]);
					else for (String t : table.keySet()) c.add(t);
				}
			});
			choice_source_projects.addItemListener(new ItemListener() {
				public void itemStateChanged(ItemEvent e) {
					String item = (String) e.getItem();
					Project p = mpr.get(choice_source_projects.getSelectedItem());
					map_source.clear();
					map_source.putAll(findLandmarkNodes(p, input.getText()));
					choice_target_landmarks.removeAll();
					if (map_source.isEmpty()) choice_target_landmarks.add(none[0]);
					else for (String t : map_source.keySet()) choice_target_landmarks.add(t);
					stacks.clear();
					choice_stacks.removeAll();
					stacks.addAll(Display.getPatchStacks(mpr.get(project_titles[0]).getRootLayerSet()));
					if (stacks.isEmpty()) choice_stacks.add(none[0]);
					else for (Patch pa : stacks) choice_stacks.add(pa.toString());
				}
			});
			gd.showDialog();
			if (gd.wasCanceled()) return;
			String type = gd.getNextString();
			if (null == type || 0 == type.trim().length()) {
				Utils.log("Invalid landmarks node type!");
				return;
			}
			ProjectThing target_landmarks_node = map_target.get(gd.getNextChoice());
			Project source = mpr.get(gd.getNextChoice());
			ProjectThing source_landmarks_node = map_source.get(gd.getNextChoice());
			Patch stack_patch = stacks.get(gd.getNextChoiceIndex());
			Display.this.getLayerSet().addLayerContentStep(layer);
			insertStack(target_landmarks_node, source, source_landmarks_node, stack_patch);
			Display.this.getLayerSet().addChangeTreesStep();
		} else if (command.equals("Import grid...")) {
			Display.this.getLayerSet().addLayerContentStep(layer);
			Bureaucrat burro = project.getLoader().importGrid(layer);
			if (null != burro)
				burro.addPostTask(new Runnable() { public void run() {
					Display.this.getLayerSet().addLayerContentStep(layer);
				}});
		} else if (command.equals("Import sequence as grid...")) {
			Display.this.getLayerSet().addChangeTreesStep();
			Bureaucrat burro = project.getLoader().importSequenceAsGrid(layer);
			if (null != burro)
				burro.addPostTask(new Runnable() { public void run() {
					Display.this.getLayerSet().addChangeTreesStep();
				}});
		} else if (command.equals("Import from text file...")) {
			Display.this.getLayerSet().addChangeTreesStep();
			Bureaucrat burro = project.getLoader().importImages(layer);
			if (null != burro)
				burro.addPostTask(new Runnable() { public void run() {
					Display.this.getLayerSet().addChangeTreesStep();
				}});
		} else if (command.equals("Import labels as arealists...")) {
			Display.this.getLayerSet().addChangeTreesStep();
			Bureaucrat burro = project.getLoader().importLabelsAsAreaLists(layer, null, Double.MAX_VALUE, 0, 0.4f, false);
			burro.addPostTask(new Runnable() { public void run() {
				Display.this.getLayerSet().addChangeTreesStep();
			}});
		} else if (command.equals("Make flat image...")) {
			Rectangle srcRect = null;
			Roi roi = canvas.getFakeImagePlus().getRoi();
			if (null != roi) {
				srcRect = roi.getBounds();
			} else {
				srcRect = new Rectangle(0, 0, (int)Math.ceil(layer.getParent().getLayerWidth()), (int)Math.ceil(layer.getParent().getLayerHeight()));
			}
			double scale = 1.0;
			final String[] types = new String[]{"8-bit grayscale", "RGB Color"};
			int the_type = ImagePlus.GRAY8;
			final GenericDialog gd = new GenericDialog("Choose", frame);
			gd.addSlider("Scale: ", 1, 100, 100);
			gd.addNumericField("Width: ", srcRect.width, 0);
			gd.addNumericField("height: ", srcRect.height, 0);
			Vector numfields = gd.getNumericFields();
			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));
			for (Object ob : numfields) ((TextField)ob).addTextListener(udf);
			gd.addChoice("Type: ", types, types[0]);
			if (layer.getParent().size() > 1) {
				Utils.addLayerRangeChoices(Display.this.layer, gd); 
				gd.addCheckbox("Include non-empty layers only", true);
			}
			gd.addMessage("Background color:");
			Utils.addRGBColorSliders(gd, Color.black);
			gd.addCheckbox("Best quality", false);
			gd.addMessage("");
			gd.addCheckbox("Save to file", false);
			gd.addCheckbox("Save for web", false);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			scale = gd.getNextNumber() / 100;
			the_type = (0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY8 : ImagePlus.COLOR_RGB);
			if (Double.isNaN(scale) || scale <= 0.0) {
				Utils.showMessage("Invalid scale.");
				return;
			}
			gd.getNextNumber();
			gd.getNextNumber();
			Layer[] layer_array = null;
			boolean non_empty_only = false;
			if (layer.getParent().size() > 1) {
				non_empty_only = gd.getNextBoolean();
				int i_start = gd.getNextChoiceIndex();
				int i_end = gd.getNextChoiceIndex();
				ArrayList<Layer> al = new ArrayList<Layer>();
				ArrayList al_zd = layer.getParent().getZDisplayables();
				ZDisplayable[] zd = new ZDisplayable[al_zd.size()];
				al_zd.toArray(zd);
				for (int i=i_start, j=0; i <= i_end; i++, j++) {
					Layer la = layer.getParent().getLayer(i);
					if (!la.isEmpty() || !non_empty_only) al.add(la); 
				}
				if (0 == al.size()) {
					Utils.showMessage("All layers are empty!");
					return;
				}
				layer_array = new Layer[al.size()];
				al.toArray(layer_array);
			} else {
				layer_array = new Layer[]{Display.this.layer};
			}
			final Color background = new Color((int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
			final boolean quality = gd.getNextBoolean();
			final boolean save_to_file = gd.getNextBoolean();
			final boolean save_for_web = gd.getNextBoolean();
			if (save_for_web) project.getLoader().makePrescaledTiles(layer_array, Patch.class, srcRect, scale, c_alphas, the_type);
			else project.getLoader().makeFlatImage(layer_array, srcRect, scale, c_alphas, the_type, save_to_file, quality, background);
		} else if (command.equals("Lock")) {
			selection.setLocked(true);
			Utils.revalidateComponent(tabs.getSelectedComponent());
		} else if (command.equals("Unlock")) {
			selection.setLocked(false);
			Utils.revalidateComponent(tabs.getSelectedComponent());
		} else if (command.equals("Properties...")) {
			active.adjustProperties();
			updateSelection();
		} else if (command.equals("Show current 2D position in 3D")) {
			Point p = canvas.consumeLastPopupPoint();
			if (null == p) return;
			Display3D.addFatPoint("Current 2D Position", getLayerSet(), p.x, p.y, layer.getZ(), 10, Color.magenta);
		} else if (command.equals("Show layers as orthoslices in 3D")) {
			GenericDialog gd = new GenericDialog("Options");
			Roi roi = canvas.getFakeImagePlus().getRoi();
			Rectangle r = null == roi ? getLayerSet().get2DBounds() : roi.getBounds();
			gd.addMessage("ROI 2D bounds:");
			gd.addNumericField("x:", r.x, 0, 30, "pixels");
			gd.addNumericField("y:", r.y, 0, 30, "pixels");
			gd.addNumericField("width:", r.width, 0, 30, "pixels");
			gd.addNumericField("height:", r.height, 0, 30, "pixels");
			gd.addMessage("Layers to include:");
			Utils.addLayerRangeChoices(layer, gd);
			gd.addMessage("Constrain dimensions to:");
			gd.addNumericField("max width and height:", getLayerSet().getPixelsMaxDimension(), 0, 30, "pixels");
			gd.addMessage("Options:");
			final String[] types = {"Greyscale", "Color RGB"};
			gd.addChoice("Image type:", types, types[0]);
			gd.addCheckbox("Invert images", false);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			int x = (int)gd.getNextNumber(),
			    y = (int)gd.getNextNumber(),
			    width = (int)gd.getNextNumber(),
			    height = (int)gd.getNextNumber();
			final int first = gd.getNextChoiceIndex(),
					  last = gd.getNextChoiceIndex();
			final List<Layer> layers = getLayerSet().getLayers(first, last);
			final int max_dim = (int)gd.getNextNumber();
			float scale = 1;
			if (max_dim < Math.max(width, height)) {
				scale = max_dim / (float)Math.max(width, height);
			}
			final int type = 0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY8 : ImagePlus.COLOR_RGB;
			final boolean invert = gd.getNextBoolean();
			final LayerStack stack = new LayerStack(layers, new Rectangle(x, y, width, height), scale, type, Patch.class, max_dim, invert);
			Display3D.showOrthoslices(stack.getImagePlus(), "LayerSet [" + x + "," + y + "," + width + "," + height + "] " + first + "--" + last, x, y, scale, layers.get(0));
		} else if (command.equals("Align stack slices")) {
			if (getActive() instanceof Patch) {
				final Patch slice = (Patch)getActive();
				if (slice.isStack()) {
					final HashSet hs = slice.getLinkedGroup(new HashSet());
					for (Iterator it = hs.iterator(); it.hasNext(); ) {
						if (it.next().getClass() != Patch.class) {
							Utils.showMessage("Images are linked to other objects, can't proceed to cross-correlate them."); 
							return;
						}
					}
					final LayerSet ls = slice.getLayerSet();
					final HashSet<Displayable> linked = slice.getLinkedGroup(null);
					ls.addTransformStepWithData(linked);
					Bureaucrat burro = AlignTask.registerStackSlices((Patch)getActive()); 
					burro.addPostTask(new Runnable() { public void run() {
						ls.enlargeToFit(linked);
						ls.addTransformStepWithData(linked);
					}});
				} else {
					Utils.log("Align stack slices: selected image is not part of a stack.");
				}
			}
		} else if (command.equals("Align layers with manual landmarks")) {
			setMode(new ManualAlignMode(Display.this));
		} else if (command.equals("Align layers")) {
			final Layer la = layer; 
			la.getParent().addTransformStep(la.getParent().getLayers());
			Bureaucrat burro = AlignLayersTask.alignLayersTask( la );
			burro.addPostTask(new Runnable() { public void run() {
				getLayerSet().enlargeToFit(getLayerSet().getDisplayables(Patch.class));
				la.getParent().addTransformStep(la.getParent().getLayers());
			}});
		} else if (command.equals("Align multi-layer mosaic")) {
			final Layer la = layer; 
			la.getParent().addTransformStep();
			Bureaucrat burro = AlignTask.alignMultiLayerMosaicTask( la );
			burro.addPostTask(new Runnable() { public void run() {
				getLayerSet().enlargeToFit(getLayerSet().getDisplayables(Patch.class));
				la.getParent().addTransformStep();
			}});
		} else if (command.equals("Montage all images in this layer")) {
			final Layer la = layer;
			final List<Patch> patches = new ArrayList<Patch>( (List<Patch>) (List) la.getDisplayables(Patch.class));
			if (patches.size() < 2) {
				Utils.showMessage("Montage needs 2 or more images selected");
				return;
			}
			final Collection<Displayable> col = la.getParent().addTransformStepWithData(Arrays.asList(new Layer[]{la}));
			final ArrayList<Patch> fixed = new ArrayList<Patch>();
			for (final Patch p : patches) {
				if (p.isLocked2()) fixed.add(p);
			}
			if (fixed.isEmpty()) fixed.add(patches.get(0));
			Bureaucrat burro = AlignTask.alignPatchesTask(patches, fixed);
			burro.addPostTask(new Runnable() { public void run() {
				getLayerSet().enlargeToFit(patches);
				la.getParent().addTransformStepWithData(col);
			}});
		} else if (command.equals("Montage selected images (SIFT)")) {
			montage(0);
		} else if (command.equals("Montage selected images (phase correlation)")) {
			montage(1);
		} else if (command.equals("Montage multiple layers (phase correlation)")) {
			final GenericDialog gd = new GenericDialog("Choose range");
			Utils.addLayerRangeChoices(Display.this.layer, gd);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			final List<Layer> layers = getLayerSet().getLayers(gd.getNextChoiceIndex(), gd.getNextChoiceIndex());
			final Collection<Displayable> col = getLayerSet().addTransformStepWithData(layers);
			Bureaucrat burro = StitchingTEM.montageWithPhaseCorrelation(layers);
			if (null == burro) return;
			burro.addPostTask(new Runnable() { public void run() {
				Collection<Displayable> ds = new ArrayList<Displayable>();
				for (Layer la : layers) ds.addAll(la.getDisplayables(Patch.class));
				getLayerSet().enlargeToFit(ds);
				getLayerSet().addTransformStepWithData(col);
			}});
		} else if (command.equals("Montage multiple layers (SIFT)")) {
			final GenericDialog gd = new GenericDialog("Choose range");
			Utils.addLayerRangeChoices(Display.this.layer, gd);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			final List<Layer> layers = getLayerSet().getLayers(gd.getNextChoiceIndex(), gd.getNextChoiceIndex());
			final Collection<Displayable> col = getLayerSet().addTransformStepWithData(layers);
			Bureaucrat burro = AlignTask.montageLayersTask(layers);
			burro.addPostTask(new Runnable() { public void run() {
				Collection<Displayable> ds = new ArrayList<Displayable>();
				for (Layer la : layers) ds.addAll(la.getDisplayables(Patch.class));
				getLayerSet().enlargeToFit(ds);
				getLayerSet().addTransformStepWithData(col);
			}});
		} else if (command.equals("Properties ...")) { 
			GenericDialog gd = new GenericDialog("Properties", Display.this.frame);
			gd.addSlider("layer_scroll_step: ", 1, layer.getParent().size(), Display.this.scroll_step);
			gd.addChoice("snapshots_mode", LayerSet.snapshot_modes, LayerSet.snapshot_modes[layer.getParent().getSnapshotsMode()]);
			gd.addCheckbox("prefer_snapshots_quality", layer.getParent().snapshotsQuality());
			Loader lo = getProject().getLoader();
			boolean using_mipmaps = lo.isMipMapsEnabled();
			gd.addCheckbox("enable_mipmaps", using_mipmaps);
			gd.addCheckbox("enable_layer_pixels virtualization", layer.getParent().isPixelsVirtualizationEnabled());
			double max = layer.getParent().getLayerWidth() < layer.getParent().getLayerHeight() ? layer.getParent().getLayerWidth() : layer.getParent().getLayerHeight();
			gd.addSlider("max_dimension of virtualized layer pixels: ", 0, max, layer.getParent().getPixelsMaxDimension());
			gd.addCheckbox("Show arrow heads in Treeline/AreaTree", layer.getParent().paint_arrows);
			gd.addCheckbox("Show edge confidence boxes in Treeline/AreaTree", layer.getParent().paint_edge_confidence_boxes);
			gd.addCheckbox("Show color cues", layer.getParent().color_cues);
			gd.addSlider("+/- layers to color cue", 0, 10, layer.getParent().n_layers_color_cue);
			gd.addCheckbox("Show color cues for areas", layer.getParent().area_color_cues);
			gd.addCheckbox("Use red/blue for color cues", layer.getParent().use_color_cue_colors);
			gd.addCheckbox("Prepaint images", layer.getParent().prepaint);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			int sc = (int) gd.getNextNumber();
			if (sc < 1) sc = 1;
			Display.this.scroll_step = sc;
			updateInDatabase("scroll_step");
			layer.getParent().setSnapshotsMode(gd.getNextChoiceIndex());
			layer.getParent().setSnapshotsQuality(gd.getNextBoolean());
			boolean generate_mipmaps = gd.getNextBoolean();
			if (using_mipmaps && generate_mipmaps) {
			} else {
				if (using_mipmaps) { 
					lo.flushMipMaps(true);
				} else {
					lo.generateMipMaps(layer.getParent().getDisplayables(Patch.class));
				}
			}
			layer.getParent().setPixelsVirtualizationEnabled(gd.getNextBoolean());
			layer.getParent().setPixelsMaxDimension((int)gd.getNextNumber());
			layer.getParent().paint_arrows = gd.getNextBoolean();
			layer.getParent().paint_edge_confidence_boxes = gd.getNextBoolean();
			layer.getParent().color_cues = gd.getNextBoolean();
			layer.getParent().n_layers_color_cue = (int)gd.getNextNumber();
			layer.getParent().area_color_cues = gd.getNextBoolean();
			layer.getParent().use_color_cue_colors = gd.getNextBoolean();
			layer.getParent().prepaint = gd.getNextBoolean();
			Display.repaint(layer.getParent());
		} else if (command.equals("Adjust snapping parameters...")) {
			AlignTask.p_snap.setup("Snap");
		} else if (command.equals("Adjust fast-marching parameters...")) {
			Segmentation.fmp.setup();
		} else if (command.equals("Adjust arealist paint parameters...")) {
			AreaWrapper.PP.setup();
		} else if (command.equals("Search...")) {
			new Search();
		} else if (command.equals("Select all")) {
			selection.selectAll();
			repaint(Display.this.layer, selection.getBox(), 0);
		} else if (command.equals("Select all visible")) {
			selection.selectAllVisible();
			repaint(Display.this.layer, selection.getBox(), 0);
		} else if (command.equals("Select none")) {
			Rectangle box = selection.getBox();
			selection.clear();
			repaint(Display.this.layer, box, 0);
		} else if (command.equals("Restore selection")) {
			selection.restore();
		} else if (command.equals("Select under ROI")) {
			Roi roi = canvas.getFakeImagePlus().getRoi();
			if (null == roi) return;
			selection.selectAll(roi, true);
		} else if (command.equals("Merge")) {
			Bureaucrat burro = Bureaucrat.create(new Worker.Task("Merging AreaLists") {
				public void exec() {
					ArrayList<Displayable> al_sel = selection.getSelected(AreaList.class);
					al_sel.remove(Display.this.active);
					al_sel.add(0, Display.this.active);
					Set<DoStep> dataedits = new HashSet<DoStep>();
					dataedits.add(new Displayable.DoEdit(Display.this.active).init(Display.this.active, new String[]{"data"}));
					getLayerSet().addChangeTreesStep(dataedits);
					AreaList ali = AreaList.merge(al_sel);
					if (null != ali) {
						for (int i=1; i<al_sel.size(); i++) {
							Object ob = al_sel.get(i);
							if (ob.getClass() == AreaList.class) {
								selection.remove((Displayable)ob);
							}
						}
						selection.updateTransform(ali);
						repaint(ali.getLayerSet(), ali, 0);
					}
				}
			}, Display.this.project);
			burro.addPostTask(new Runnable() { public void run() {
				Set<DoStep> dataedits = new HashSet<DoStep>();
				dataedits.add(new Displayable.DoEdit(Display.this.active).init(Display.this.active, new String[]{"data"}));
				getLayerSet().addChangeTreesStep(dataedits);
			}});
			burro.goHaveBreakfast();
		} else if (command.equals("Reroot")) {
			if (!(active instanceof Tree<?>)) return;
			Point p = canvas.consumeLastPopupPoint();
			if (null == p) return;
			getLayerSet().addDataEditStep(active);
			((Tree)active).reRoot(p.x, p.y, layer, canvas.getMagnification());
			getLayerSet().addDataEditStep(active);
			Display.repaint(getLayerSet());
		} else if (command.equals("Part subtree")) {
			if (!(active instanceof Tree<?>)) return;
			if (!Utils.check("Really part the subtree?")) return;
			Point p = canvas.consumeLastPopupPoint();
			if (null == p) return;
			List<ZDisplayable> ts = ((Tree)active).splitNear(p.x, p.y, layer, canvas.getMagnification());
			if (null == ts) return;
			Displayable elder = Display.this.active;
			for (ZDisplayable t : ts) {
				if (t == elder) continue;
				getLayerSet().add(t); 
				project.getProjectTree().addSibling(elder, t);
			}
			selection.clear();
			selection.selectAll(ts);
			selection.add(elder);
			getLayerSet().addChangeTreesStep();
			Display.repaint(getLayerSet());
		} else if (command.equals("Show tabular view")) {
			if (!(active instanceof Tree<?>)) return;
			((Tree<?>)active).createMultiTableView();
		} else if (command.equals("Mark")) {
			if (!(active instanceof Tree<?>)) return;
			Point p = canvas.consumeLastPopupPoint();
			if (null == p) return;
			if (((Tree<?>)active).markNear(p.x, p.y, layer, canvas.getMagnification())) {
				Display.repaint(getLayerSet());
			}
		} else if (command.equals("Clear marks (selected Trees)")) {
			for (Tree<?> t : selection.get(Tree.class)) {
				t.unmark();
			}
			Display.repaint(getLayerSet());
		} else if (command.equals("Join")) {
			if (!(active instanceof Tree<?>)) return;
			final List<Tree<?>> tlines = (List<Tree<?>>) selection.get(active.getClass());
			if (((Tree)active).canJoin(tlines)) {
				if (!Utils.check("Join these " + tlines.size() + " trees into the tree " + active + " ?")) return;
				Set<DoStep> dataedits = new HashSet<DoStep>(tlines.size());
				for (final Tree<?> tl : tlines) {
					dataedits.add(new Displayable.DoEdit(tl).init(tl, new String[]{"data"}));
				}
				getLayerSet().addChangeTreesStep(dataedits);
				((Tree)active).join(tlines);
				for (final Tree<?> tl : tlines) {
					if (tl == active) continue;
					tl.remove2(false);
				}
				Display.repaint(getLayerSet());
				Set<DoStep> dataedits2 = new HashSet<DoStep>(1);
				dataedits2.add(new Displayable.DoEdit(active).init(active, new String[]{"data"}));
				getLayerSet().addChangeTreesStep(dataedits2);
			}
		} else if (command.equals("Previous branch point or start")) {
			if (!(active instanceof Tree<?>)) return;
			Point p = canvas.consumeLastPopupPoint();
			if (null == p) return;
			center(((Treeline)active).findPreviousBranchOrRootPoint(p.x, p.y, layer, canvas));
		} else if (command.equals("Next branch point or end")) {
			if (!(active instanceof Tree<?>)) return;
			Point p = canvas.consumeLastPopupPoint();
			if (null == p) return;
			center(((Tree<?>)active).findNextBranchOrEndPoint(p.x, p.y, layer, canvas));
		} else if (command.equals("Root")) {
			if (!(active instanceof Tree<?>)) return;
			Point p = canvas.consumeLastPopupPoint();
			if (null == p) return;
			center(((Tree)active).createCoordinate(((Tree<?>)active).getRoot()));
		} else if (command.equals("Last added point")) {
			if (!(active instanceof Tree<?>)) return;
			center(((Treeline)active).getLastAdded());
		} else if (command.equals("Last edited point")) {
			if (!(active instanceof Tree<?>)) return;
			center(((Treeline)active).getLastEdited());
		} else if (command.equals("Reverse point order")) {
			if (!(active instanceof Pipe)) return;
			getLayerSet().addDataEditStep(active);
			((Pipe)active).reverse();
			Display.repaint(Display.this.layer);
			getLayerSet().addDataEditStep(active);
		} else if (command.equals("View orthoslices")) {
			if (!(active instanceof Patch)) return;
			Display3D.showOrthoslices(((Patch)active));
		} else if (command.equals("View volume")) {
			if (!(active instanceof Patch)) return;
			Display3D.showVolume(((Patch)active));
		} else if (command.equals("Show in 3D")) {
			for (ZDisplayable zd : selection.get(ZDisplayable.class)) {
				Display3D.show(zd.getProject().findProjectThing(zd));
			}
			HashSet<ProjectThing> hs = new HashSet<ProjectThing>();
			for (Profile d : selection.get(Profile.class)) {
				ProjectThing profile_list = (ProjectThing)d.getProject().findProjectThing(d).getParent();
				if (!hs.contains(profile_list)) {
					Display3D.show(profile_list);
					hs.add(profile_list);
				}
			}
		} else if (command.equals("Snap")) {
			if (!(active instanceof Patch)) return;
			Display.snap((Patch)active);
		} else if (command.equals("Blend")) {
			HashSet<Patch> patches = new HashSet<Patch>();
			for (final Displayable d : selection.getSelected()) {
				if (d.getClass() == Patch.class) patches.add((Patch)d);
			}
			if (patches.size() > 1) {
				GenericDialog gd = new GenericDialog("Blending");
				gd.addCheckbox("Respect current alpha mask", true);
				gd.showDialog();
				if (gd.wasCanceled()) return;
				Blending.blend(patches, gd.getNextBoolean());
			} else {
				IJ.log("Please select more than one overlapping image.");
			}
		} else if (command.equals("Montage")) {
			final Set<Displayable> affected = new HashSet<Displayable>(selection.getAffected());
			final LayerSet ls = layer.getParent();
			ls.addTransformStepWithData(affected);
			Bureaucrat burro = AlignTask.alignSelectionTask( selection );
			burro.addPostTask(new Runnable() { public void run() {
				ls.enlargeToFit(affected);
				ls.addTransformStepWithData(affected);
			}});
		} else if (command.equals("Lens correction")) {
			final Layer la = layer;
			la.getParent().addDataEditStep(new HashSet<Displayable>(la.getParent().getDisplayables()));
			Bureaucrat burro = DistortionCorrectionTask.correctDistortionFromSelection( selection );
			burro.addPostTask(new Runnable() { public void run() {
				la.getParent().addDataEditStep(new HashSet<Displayable>(la.getParent().getDisplayables()));
			}});
		} else if (command.equals("Link images...")) {
			GenericDialog gd = new GenericDialog("Options");
			gd.addMessage("Linking images to images (within their own layer only):");
			String[] options = {"all images to all images", "each image with any other overlapping image"};
			gd.addChoice("Link: ", options, options[1]);
			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"};
			gd.addChoice("Apply to: ", options2, options2[0]);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			Layer lay = layer;
			final HashSet<Displayable> ds = new HashSet<Displayable>(lay.getParent().getDisplayables());
			lay.getParent().addDataEditStep(ds, new String[]{"data"});
			boolean overlapping_only = 1 == gd.getNextChoiceIndex();
			Collection<Displayable> coll = null;
			switch (gd.getNextChoiceIndex()) {
				case 0:
					coll = selection.getSelected(Patch.class);
					Patch.crosslink(coll, overlapping_only);
					break;
				case 1:
					coll = lay.getDisplayables(Patch.class);
					Patch.crosslink(coll, overlapping_only);
					break;
				case 2:
					coll = new ArrayList<Displayable>();
					for (final Layer la : lay.getParent().getLayers()) {
						Collection<Displayable> acoll = la.getDisplayables(Patch.class);
						Patch.crosslink(acoll, overlapping_only);
						coll.addAll(acoll);
					}
					break;
				case 3:
					ArrayList<Layer> layers = lay.getParent().getLayers();
					Collection<Displayable> lc1 = layers.get(0).getDisplayables(Patch.class);
					if (lay == layers.get(0)) coll = lc1;
					for (int i=1; i<layers.size(); i++) {
						Collection<Displayable> lc2 = layers.get(i).getDisplayables(Patch.class);
						if (null == coll && Display.this.layer == layers.get(i)) coll = lc2;
						Collection<Displayable> both = new ArrayList<Displayable>();
						both.addAll(lc1);
						both.addAll(lc2);
						Patch.crosslink(both, overlapping_only);
						lc1 = lc2;
					}
					break;
			}
			if (null != coll) Display.updateCheckboxes(coll, DisplayablePanel.LINK_STATE, true);
			lay.getParent().addDataEditStep(ds);
		} else if (command.equals("Unlink all selected images")) {
			if (Utils.check("Really unlink selected images?")) {
				for (final Displayable d : selection.getSelected(Patch.class)) {
					d.unlink();
				}
			}
		} else if (command.equals("Unlink all")) {
			if (Utils.check("Really unlink all objects from all layers?")) {
				Collection<Displayable> ds = layer.getParent().getDisplayables();
				for (final Displayable d : ds) {
					d.unlink();
				}
				Display.updateCheckboxes(ds, DisplayablePanel.LOCK_STATE);
			}
		} else if (command.equals("Calibration...")) {
			try {
				IJ.run(canvas.getFakeImagePlus(), "Properties...", "");
				Display.updateTitle(getLayerSet());
			} catch (RuntimeException re) {
				Utils.log2("Calibration dialog canceled.");
			}
		} else if (command.equals("Grid overlay...")) {
			if (null == gridoverlay) gridoverlay = new GridOverlay();
			gridoverlay.setup(canvas.getFakeImagePlus().getRoi());
			canvas.repaint(false);
		} else if (command.equals("Enhance contrast (selected images)...")) {
			final Layer la = layer;
			ArrayList<Displayable> selected = selection.getSelected(Patch.class);
			final HashSet<Displayable> ds = new HashSet<Displayable>(selected);
			la.getParent().addDataEditStep(ds);
			Displayable active = Display.this.getActive();
			Patch ref = active.getClass() == Patch.class ? (Patch)active : null;
			Bureaucrat burro = getProject().getLoader().enhanceContrast(selected, ref);
			burro.addPostTask(new Runnable() { public void run() {
				la.getParent().addDataEditStep(ds);
			}});
		} else if (command.equals("Enhance contrast layer-wise...")) {
			final GenericDialog gd = new GenericDialog("Choose range");
			Utils.addLayerRangeChoices(Display.this.layer, gd);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			java.util.List<Layer> layers = layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1); 
			final HashSet<Displayable> ds = new HashSet<Displayable>();
			for (final Layer l : layers) ds.addAll(l.getDisplayables(Patch.class));
			getLayerSet().addDataEditStep(ds);
			Bureaucrat burro = project.getLoader().enhanceContrast(layers);
			burro.addPostTask(new Runnable() { public void run() {
				getLayerSet().addDataEditStep(ds);
			}});
		} else if (command.equals("Set Min and Max layer-wise...")) {
			Displayable active = getActive();
			double min = 0;
			double max = 0;
			if (null != active && active.getClass() == Patch.class) {
				min = ((Patch)active).getMin();
				max = ((Patch)active).getMax();
			}
			final GenericDialog gd = new GenericDialog("Min and Max");
			gd.addMessage("Set min and max to all images in the layer range");
			Utils.addLayerRangeChoices(Display.this.layer, gd);
			gd.addNumericField("min: ", min, 2);
			gd.addNumericField("max: ", max, 2);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			min = gd.getNextNumber();
			max = gd.getNextNumber();
			ArrayList<Displayable> al = new ArrayList<Displayable>();
			for (final Layer la : layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1)) { 
				al.addAll(la.getDisplayables(Patch.class));
			}
			final HashSet<Displayable> ds = new HashSet<Displayable>(al);
			getLayerSet().addDataEditStep(ds);
			Bureaucrat burro = project.getLoader().setMinAndMax(al, min, max);
			burro.addPostTask(new Runnable() { public void run() {
				getLayerSet().addDataEditStep(ds);
			}});
		} else if (command.equals("Set Min and Max (selected images)...")) {
			Displayable active = getActive();
			double min = 0;
			double max = 0;
			if (null != active && active.getClass() == Patch.class) {
				min = ((Patch)active).getMin();
				max = ((Patch)active).getMax();
			}
			final GenericDialog gd = new GenericDialog("Min and Max");
			gd.addMessage("Set min and max to all selected images");
			gd.addNumericField("min: ", min, 2);
			gd.addNumericField("max: ", max, 2);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			min = gd.getNextNumber();
			max = gd.getNextNumber();
			final HashSet<Displayable> ds = new HashSet<Displayable>(selection.getSelected(Patch.class));
			getLayerSet().addDataEditStep(ds);
			Bureaucrat burro = project.getLoader().setMinAndMax(selection.getSelected(Patch.class), min, max);
			burro.addPostTask(new Runnable() { public void run() {
				getLayerSet().addDataEditStep(ds);
			}});
		} else if (command.equals("Adjust min and max (selected images)...")) {
			final List<Displayable> list = selection.getSelected(Patch.class);
			if (list.isEmpty()) {
				Utils.log("No images selected!");
				return;
			}
			Bureaucrat.createAndStart(new Worker.Task("Init contrast adjustment") {
				public void exec() {
					try {
						setMode(new ContrastAdjustmentMode(Display.this, list));
					} catch (Exception e) {
						e.printStackTrace();
						Utils.log("All images must be of the same type!");
					}
				}
			}, list.get(0).getProject());
		} else if (command.equals("Mask image borders (layer-wise)...")) {
			final GenericDialog gd = new GenericDialog("Mask borders");
			Utils.addLayerRangeChoices(Display.this.layer, gd);
			gd.addMessage("Borders:");
			gd.addNumericField("left: ", 6, 2);
			gd.addNumericField("top: ", 6, 2);
			gd.addNumericField("right: ", 6, 2);
			gd.addNumericField("bottom: ", 6, 2);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			Collection<Layer> layers = layer.getParent().getLayers().subList(gd.getNextChoiceIndex(), gd.getNextChoiceIndex() +1);
			final HashSet<Displayable> ds = new HashSet<Displayable>();
			for (Layer l : layers) ds.addAll(l.getDisplayables(Patch.class));
			getLayerSet().addDataEditStep(ds);
			Bureaucrat burro = project.getLoader().maskBordersLayerWise(layers, (int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
			burro.addPostTask(new Runnable() { public void run() {
				getLayerSet().addDataEditStep(ds);
			}});
		} else if (command.equals("Mask image borders (selected images)...")) {
			final GenericDialog gd = new GenericDialog("Mask borders");
			gd.addMessage("Borders:");
			gd.addNumericField("left: ", 6, 2);
			gd.addNumericField("top: ", 6, 2);
			gd.addNumericField("right: ", 6, 2);
			gd.addNumericField("bottom: ", 6, 2);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			Collection<Displayable> patches = selection.getSelected(Patch.class);
			final HashSet<Displayable> ds = new HashSet<Displayable>(patches);
			getLayerSet().addDataEditStep(ds);
			Bureaucrat burro = project.getLoader().maskBorders(patches, (int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber(), (int)gd.getNextNumber());
			burro.addPostTask(new Runnable() { public void run() {
				getLayerSet().addDataEditStep(ds);
			}});
		} else if (command.equals("Split images under polyline ROI")) {
			Roi roi = canvas.getFakeImagePlus().getRoi();
			if (null == roi) return;
			if (roi.getType() != Roi.POLYLINE) {
				Utils.showMessage("Need a polyline ROI, not just any ROI!");
				return;
			}
			if (!Utils.check("Really split images under the polyline ROI?")) {
				return;
			}
			final Set<Displayable> col = new HashSet<Displayable>();
			PolygonRoi proi = (PolygonRoi)roi;
			int[] x = proi.getXCoordinates(),
				  y = proi.getYCoordinates();
			Rectangle b = proi.getBounds();
			Polygon[] pols = new Polygon[proi.getNCoordinates() -1];
			for (int i=0; i<pols.length; i++) {
				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},
									  new int[]{b.y + y[i], b.y + y[i], b.y + y[i+1], b.y + y[i+1]}, 4);
			}
			for (final Patch p : getLayer().getAll(Patch.class)) {
				if (!p.isVisible()) continue;
				final Area a = p.getArea();
				for (int i=0; i<pols.length; i++) {
					Area c = new Area(pols[i]);
					c.intersect(a);
					if (M.isEmpty(c)) continue;
					col.add(p);
					break;
				}
			}
			if (col.isEmpty()) {
				Utils.showMessage("No images intersect the ROI!");
				return;
			}
			for (int i=1; i<proi.getNCoordinates(); i++) {
				for (int k=i+2; k<proi.getNCoordinates(); k++) { 
					if (null != M.computeSegmentsIntersection(x[i-1], y[i-1], x[i], y[i],
															  x[k-1], y[k-1], x[k], y[k])) {
						Utils.showMessage("Cannot split images with a polygon ROI that intersects itself!");
						return;
					}
				}					
			}
			final Area[] as = M.splitArea(new Area(getLayerSet().get2DBounds()), proi, getLayerSet().get2DBounds());
			Color[] c = new Color[]{Color.blue, Color.red};
			int i = 0;
			for (Area a : as) {
				getLayer().getOverlay().add(a, c[i++], null, true, false, 0.4f);
			}
			Display.repaint(getLayer());
			final YesNoDialog yn = new YesNoDialog(frame, "Check", "Does the splitting match your expectations?\nPush 'yes' to split the images.", false);
			yn.setModal(false);
			for (WindowListener wl : yn.getWindowListeners()) yn.removeWindowListener(wl);
			yn.setClosingTask(new Runnable() {
				@Override
				public void run() {
					try {
						for (Area a : as) {
							getLayer().getOverlay().remove(a);
						}
						if (!yn.yesPressed()) {
							Utils.log2("Pushed 'no'");
							return;
						}
						Bureaucrat.createAndStart(new Worker.Task("Spliting images") {
							public void exec() {
								final Roi r1 = new ShapeRoi(as[0]),
										  r2 = new ShapeRoi(as[1]);
								ArrayList<Future<?>> fus = new ArrayList<Future<?>>();
								for (final Patch p : (Collection<Patch>)(Collection)col) {
									Patch copy = (Patch) p.clone(p.getProject(), false);
									p.addAlphaMask(r1, 0);
									copy.addAlphaMask(r2, 0);
									fus.add(p.updateMipMaps());
									fus.add(copy.updateMipMaps());
									p.getLayer().add(copy); 
								}
								Utils.wait(fus);
							}
						}, project);
					} catch (Throwable t) {
						IJError.print(t);
					} finally {
						yn.dispose();
						Display.repaint(getLayer());
					}
				}
			});
			yn.setVisible(true);
		} else if (command.equals("Duplicate")) {
			final HashSet<Class> accepted = new HashSet<Class>();
			accepted.add(Patch.class);
			accepted.add(DLabel.class);
			accepted.add(Stack.class);
			final ArrayList<Displayable> originals = new ArrayList<Displayable>();
			final ArrayList<Displayable> selected = selection.getSelected();
			for (final Displayable d : selected) {
				if (accepted.contains(d.getClass())) {
					originals.add(d);
				}
			}
			if (originals.size() > 0) {
				getLayerSet().addChangeTreesStep();
				for (final Displayable d : originals) {
					if (d instanceof ZDisplayable) {
						d.getLayerSet().add((ZDisplayable)d.clone());
					} else {
						d.getLayer().add(d.clone());
					}
				}
				getLayerSet().addChangeTreesStep();
			} else if (selected.size() > 0) {
				Utils.log("Can only duplicate images and text labels.\nDuplicate *other* objects in the Project Tree.\n");
			}
		} else if (command.equals("Create subproject")) {
			Roi roi = canvas.getFakeImagePlus().getRoi();
			if (null == roi) return; 
			Layer first, last;
			if (1 == layer.getParent().size()) {
				first = last = layer;
			} else {
				GenericDialog gd = new GenericDialog("Choose layer range");
				Utils.addLayerRangeChoices(layer, gd);
				gd.showDialog();
				if (gd.wasCanceled()) return;
				first = layer.getParent().getLayer(gd.getNextChoiceIndex());
				last = layer.getParent().getLayer(gd.getNextChoiceIndex());
				Utils.log2("first, last: " + first + ", " + last);
			}
			Project sub = getProject().createSubproject(roi.getBounds(), first, last);
			if (null == sub) {
				Utils.log("ERROR: failed to create subproject.");
				return;
			}
			final LayerSet subls = sub.getRootLayerSet();
			Display.createDisplay(sub, subls.getLayer(0));
		} else if (command.startsWith("Image stack under selected Arealist")) {
			if (null == active || active.getClass() != AreaList.class) return;
			GenericDialog gd = new GenericDialog("Stack options");
			String[] types = {"8-bit", "16-bit", "32-bit", "RGB"};
			gd.addChoice("type:", types, types[0]);
			gd.addSlider("Scale: ", 1, 100, 100);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			final int type;
			switch (gd.getNextChoiceIndex()) {
				case 0: type = ImagePlus.GRAY8; break;
				case 1: type = ImagePlus.GRAY16; break;
				case 2: type = ImagePlus.GRAY32; break;
				case 3: type = ImagePlus.COLOR_RGB; break;
				default: type = ImagePlus.GRAY8; break;
			}
			ImagePlus imp = ((AreaList)active).getStack(type, gd.getNextNumber()/100);
			if (null != imp) imp.show();
		} else if (command.equals("Fly through selected Treeline/AreaTree")) {
			if (null == active || !(active instanceof Tree<?>)) return;
			Bureaucrat.createAndStart(new Worker.Task("Creating fly through", true) {
				public void exec() {
					GenericDialog gd = new GenericDialog("Fly through");
					gd.addNumericField("Width", 512, 0);
					gd.addNumericField("Height", 512, 0);
					String[] types = new String[]{"8-bit gray", "Color RGB"};
					gd.addChoice("Image type", types, types[0]);
					gd.addSlider("scale", 0, 100, 100);
					gd.addCheckbox("save to file", false);
					gd.showDialog();
					if (gd.wasCanceled()) return;
					int w = (int)gd.getNextNumber();
					int h = (int)gd.getNextNumber();
					int type = 0 == gd.getNextChoiceIndex() ? ImagePlus.GRAY8 : ImagePlus.COLOR_RGB;
					double scale = gd.getNextNumber();
					if (w <=0 || h <=0) {
						Utils.log("Invalid width or height: " + w + ", " + h);
						return;
					}
					if (0 == scale || Double.isNaN(scale)) {
						Utils.log("Invalid scale: " + scale);
						return;
					}
					String dir = null;
					if (gd.getNextBoolean()) {
						DirectoryChooser dc = new DirectoryChooser("Target directory");
						dir = dc.getDirectory();
						if (null == dir) return; 
						dir = Utils.fixDir(dir);
					}
					ImagePlus imp = ((Tree<?>)active).flyThroughMarked(w, h, scale/100, type, dir);
					if (null == imp) {
						Utils.log("Mark a node first!");
						return;
					}
					imp.show();
				}
			}, project);
		} else if (command.startsWith("Arealists as labels")) {
			GenericDialog gd = new GenericDialog("Export labels");
			gd.addSlider("Scale: ", 1, 100, 100);
			final String[] options = {"All area list", "Selected area lists"};
			gd.addChoice("Export: ", options, options[0]);
			Utils.addLayerRangeChoices(layer, gd);
			gd.addCheckbox("Visible only", true);
			gd.showDialog();
			if (gd.wasCanceled()) return;
			final float scale = (float)(gd.getNextNumber() / 100);
			java.util.List<Displayable> al = (java.util.List<Displayable>)(0 == gd.getNextChoiceIndex() ? layer.getParent().getZDisplayables(AreaList.class) : selection.getSelectedSorted(AreaList.class));
			if (null == al) {
				Utils.log("No area lists found to export.");
				return;
			}
			int first = gd.getNextChoiceIndex();
			int last  = gd.getNextChoiceIndex();
			boolean visible_only = gd.getNextBoolean();
			if (-1 != command.indexOf("(amira)")) {
				AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, true, true);
			} else if (-1 != command.indexOf("(tif)")) {
				AreaList.exportAsLabels(al, canvas.getFakeImagePlus().getRoi(), scale, first, last, visible_only, false, false);
			}
		} else if (command.equals("Project properties...")) {
			project.adjustProperties();
		} else if (command.equals("Release memory...")) {
			Bureaucrat.createAndStart(new Worker("Releasing memory") {
				public void run() {
					startedWorking();
					try {
			GenericDialog gd = new GenericDialog("Release Memory");
			int max = (int)(IJ.maxMemory() / 1000000);
			gd.addSlider("Megabytes: ", 0, max, max/2);
			gd.showDialog();
			if (!gd.wasCanceled()) {
				int n_mb = (int)gd.getNextNumber();
				project.getLoader().releaseToFit((long)n_mb*1000000);
			}
					} catch (Throwable e) {
						IJError.print(e);
					} finally {
						finishedWorking();
					}
				}
			}, project);
		} else if (command.equals("Flush image cache")) {
			Loader.releaseAllCaches();
		} else if (command.equals("Regenerate all mipmaps")) {
			project.getLoader().regenerateMipMaps(getLayerSet().getDisplayables(Patch.class));
		} else if (command.equals("Regenerate mipmaps (selected images)")) {
			project.getLoader().regenerateMipMaps(selection.getSelected(Patch.class));
		} else if (command.equals("Tags...")) {
			File f = Utils.chooseFile(null, "tags", ".xml");
			if (null == f) return;
			if (!Utils.saveToFile(f, getLayerSet().exportTags())) {
				Utils.logAll("ERROR when saving tags to file " + f.getAbsolutePath());
			}
		} else if (command.equals("Tags ...")) {
			String[] ff = Utils.selectFile("Import tags");
			if (null == ff) return;
			GenericDialog gd = new GenericDialog("Import tags");
			String[] modes = new String[]{"Append to current tags", "Replace current tags"};
			gd.addChoice("Import tags mode:", modes, modes[0]);
			gd.addMessage("Replacing current tags\nwill remove all tags\n from all nodes first!");
			gd.showDialog();
			if (gd.wasCanceled()) return;
			getLayerSet().importTags(new StringBuilder(ff[0]).append('/').append(ff[1]).toString(), 1 == gd.getNextChoiceIndex());
		} else if (command.equals("Connectivity graph...")) {
			Bureaucrat.createAndStart(new Worker.Task("Connectivity graph") {
				public void exec() {
					Graph.extractAndShowGraph(getLayerSet());
				}
			}, getProject());
		} else if (command.equals("Measure")) {
			if (selection.isEmpty()) {
				Utils.log("Nothing selected to measure!");
				return;
			}
			selection.measure();
		} else {
			Utils.log2("Display: don't know what to do with command " + command);
		}
		}});
