Fixes to homogenizeContrast
[trakem2.git] / ini / trakem2 / Project.java
blob8fc9c319fad94feaef8f66d5cca6961d5c751cdf
1 /**
3 TrakEM2 plugin for ImageJ(C).
4 Copyright (C) 2005 Albert Cardona and Rodney Douglas.
6 This program is free software; you can redistribute it and/or
7 modify it under the terms of the GNU General Public License
8 as published by the Free Software Foundation (http://www.gnu.org/licenses/gpl.txt )
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program; if not, write to the Free Software
17 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 You may contact Albert Cardona at acardona at ini.phys.ethz.ch
20 Institute of Neuroinformatics, University of Zurich / ETH, Switzerland.
21 **/
23 package ini.trakem2;
26 import ij.IJ;
27 import ij.Macro;
28 import ij.gui.GenericDialog;
29 import ij.gui.YesNoCancelDialog;
30 import ij.io.DirectoryChooser;
31 import ini.trakem2.display.*;
32 import ini.trakem2.persistence.DBObject;
33 import ini.trakem2.persistence.DBLoader;
34 import ini.trakem2.persistence.FSLoader;
35 import ini.trakem2.persistence.Loader;
36 import ini.trakem2.tree.DTDParser;
37 import ini.trakem2.tree.DNDTree;
38 import ini.trakem2.tree.LayerThing;
39 import ini.trakem2.tree.LayerTree;
40 import ini.trakem2.tree.ProjectThing;
41 import ini.trakem2.tree.ProjectTree;
42 import ini.trakem2.tree.TemplateThing;
43 import ini.trakem2.tree.TemplateTree;
44 import ini.trakem2.tree.Thing;
45 import ini.trakem2.utils.IJError;
46 import ini.trakem2.utils.ProjectToolbar;
47 import ini.trakem2.utils.Utils;
48 import ini.trakem2.utils.Bureaucrat;
49 import ini.trakem2.vector.Compare;
51 import java.io.File;
52 import java.util.ArrayList;
53 import java.util.Arrays;
54 import java.util.Enumeration;
55 import java.util.HashMap;
56 import java.util.Hashtable;
57 import java.util.HashSet;
58 import java.util.Iterator;
59 import java.util.Map;
60 import javax.swing.tree.*;
61 import javax.swing.JTree;
62 import java.awt.Rectangle;
63 import javax.swing.UIManager;
65 /** The top-level class in control. */
66 public class Project extends DBObject {
68 static {
69 try {
70 //UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
71 if (IJ.isLinux()) {
72 UIManager.setLookAndFeel("javax.swing.plaf.metal.MetalLookAndFeel");
73 if (null != IJ.getInstance()) javax.swing.SwingUtilities.updateComponentTreeUI(IJ.getInstance());
74 //if ("albert".equals(System.getProperty("user.name"))) UIManager.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
76 } catch (Exception e) {
77 Utils.log("Failed to set System Look and Feel");
81 /* // using virtual frame buffer instead, since the trees are needed
82 public static final boolean headless = isHeadless();
84 private static boolean isHeadless() {
85 return Boolean.parseBoolean(System.getProperty("java.awt.headless"));
89 /** Keep track of all open projects. */
90 static private ArrayList<Project> al_open_projects = new ArrayList<Project>();
92 private Loader loader;
94 private TemplateTree template_tree = null;
96 private ProjectTree project_tree = null;
98 /** The root Thing that holds the project. */
99 private ProjectThing root_pt;
101 /** The root LayerThing of the LayerTree. */
102 private LayerThing root_lt;
104 /** The root TemplateThing of the TemplateTree. */
105 private TemplateThing root_tt;
107 /** The root LayerSet that holds the layers. */
108 private LayerSet layer_set;
110 static private TemplateThing layer_template = null;
111 static private TemplateThing layer_set_template = null;
113 /** The hashtable of unique TemplateThing types; the key is the type (String). */
114 private HashMap<String,TemplateThing> ht_unique_tt = null;
116 private LayerTree layer_tree = null;
118 private String title = "Project"; // default // TODO should be an attribute in the ProjectThing that holds it
120 private final HashMap<String,String> ht_props = new HashMap<String,String>();
122 /** Intercept ImageJ menu commands if the front image is a FakeImagePlus. */
123 static private final ImageJCommandListener command_listener = new ImageJCommandListener();
125 /** Universal near-unique id for this project, consisting of:
126 * <creation-time>.<storage-folder-hashcode>.<username-hashcode> */
127 private String unuid = null;
129 /** The constructor used by the static methods present in this class. */
130 private Project(Loader loader) {
131 super(loader);
132 this.loader = loader;
133 this.project = this; // for the superclass DBObject
134 loader.addToDatabase(this);
137 /** Constructor used by the Loader to find projects. These projects contain no loader. */
138 public Project(long id, String title) {
139 super(null, id);
140 this.title = title;
141 this.project = this;
144 static public Project getProject(final String title) {
145 for (final Project pr : al_open_projects) {
146 if (pr.title.equals(title)) return pr;
148 return null;
151 /** Return a copy of the list of all open projects. */
152 static public ArrayList<Project> getProjects() {
153 return (ArrayList<Project>)al_open_projects.clone();
156 /** Create a new PostgreSQL-based TrakEM2 project. */
157 static public Project newDBProject() {
158 if (Utils.wrongImageJVersion()) return null;
159 // create
160 DBLoader loader = new DBLoader();
161 // check connection settings
162 if (!loader.isReady()) return null;
163 // check connection
164 if (!loader.isConnected()) {
165 Utils.showMessage("Can't talk to database.");
166 return null;
168 return createNewProject(loader, true);
171 /** Open a TrakEM2 project from the database. Queries the database for existing projects and if more than one, asks which one to open. */
172 static public Project openDBProject() {
173 if (Utils.wrongImageJVersion()) return null;
174 DBLoader loader = new DBLoader();
175 if (!loader.isReady()) return null;
176 // check connection
177 if (!loader.isConnected()) {
178 Utils.showMessage("Can't talk to database.");
179 loader.destroy();
180 return null;
182 // query the database for existing projects
183 Project[] projects = loader.getProjects();
184 if (null == projects) {
185 Utils.showMessage("Can't talk to database (null list).");
186 loader.destroy();
187 return null;
189 Project project = null;
190 if (null == projects) {
191 Utils.showMessage("Can't fetch list of projects.");
192 loader.destroy();
193 return null;
194 } else if (0 == projects.length) {
195 Utils.showMessage("No projects in this database.");
196 loader.destroy();
197 return null;
198 } else if (1 == projects.length) {
199 project = projects[0];
200 } else {
201 // ask to choose one
202 String[] titles = new String[projects.length];
203 for (int i=0; i<projects.length; i++) {
204 titles[i] = projects[i].title;
206 GenericDialog gd = new GenericDialog("Choose");
207 gd.addMessage("Choose project to open:");
208 gd.addChoice("project: ", titles, titles[titles.length -1]);
209 gd.showDialog();
210 if (gd.wasCanceled()) {
211 loader.destroy();
212 return null;
214 project = projects[gd.getNextChoiceIndex()];
216 // check if the selected project is open already
217 Iterator it = al_open_projects.iterator();
218 while (it.hasNext()) {
219 Project p = (Project)it.next();
220 if (loader.isIdenticalProjectSource(p.loader) && p.id == project.id && p.title.equals(project.title)) {
221 Utils.showMessage("A project with title " + p.title + " and id " + p.id + " from the same database is already open.");
222 loader.destroy();
223 return null;
227 // now, open the selected project
229 // assign loader
230 project.loader = loader;
231 // grab the XML template
232 TemplateThing template_root = loader.getTemplateRoot(project);
233 if (null == template_root) {
234 Utils.showMessage("Failed to retrieve the template tree.");
235 project.destroy();
236 return null;
238 project.template_tree = new TemplateTree(project, template_root);
239 project.ht_unique_tt = template_root.getUniqueTypes(new HashMap<String,TemplateThing>());
240 // create the project Thing, to be root of the whole user Thing tree (and load all its objects)
241 HashMap hs_d = new HashMap(); // to collect all created displayables, and then reassign to the proper layers.
242 try {
243 // create a template for the project Thing
244 TemplateThing project_template = new TemplateThing("project");
245 project.ht_unique_tt.put("project", project_template);
246 project_template.addChild(template_root);
247 project.root_pt = loader.getRootProjectThing(project, template_root, project_template, hs_d);
248 // restore parent/child and attribute ownership and values (now that all Things exist)
249 project.root_pt.setup();
250 } catch (Exception e) {
251 Utils.showMessage("Failed to retrieve the Thing tree for the project.");
252 IJError.print(e);
253 project.destroy();
254 return null;
256 // create the user objects tree
257 project.project_tree = new ProjectTree(project, project.root_pt);
258 // restore the expanded state of each node
259 loader.restoreNodesExpandedState(project);
261 // create the layers templates
262 project.createLayerTemplates();
263 // fetch the root layer thing and the root layer set (will load all layers and layer sets, with minimal contents of patches; gets the basic objects -profile, pipe, etc.- from the project.root_pt). Will open all existing displays for each layer.
264 LayerThing root_layer_thing = null;
265 try {
266 root_layer_thing = loader.getRootLayerThing(project, project.root_pt, project.layer_set_template, project.layer_template);
267 if (null == root_layer_thing) {
268 project.destroy();
269 Utils.showMessage("Could not retrieve the root layer thing.");
270 return null;
272 // set the child/parent relationships now that everything exists
273 root_layer_thing.setup();
274 project.layer_set = (LayerSet)root_layer_thing.getObject();
275 if (null == project.layer_set) {
276 project.destroy();
277 Utils.showMessage("Could not retrieve the root layer set.");
278 return null;
280 project.layer_set.setup(); // set the active layer to each ZDisplayable
282 // debug:
283 //Utils.log2("$$$ root_lt: " + root_layer_thing + " ob: " + root_layer_thing.getObject().getClass().getName() + "\n children: " + ((LayerSet)root_layer_thing.getObject()).getLayers().size());
285 project.layer_tree = new LayerTree(project, root_layer_thing);
286 project.root_lt = root_layer_thing;
287 } catch (Exception e) {
288 Utils.showMessage("Failed to retrieve the Layer tree for the project.");
289 IJError.print(e);
290 project.destroy();
291 return null;
294 // if all when well, register as open:
295 al_open_projects.add(project);
296 // create the project control window, containing the trees in a double JSplitPane
297 ControlWindow.add(project, project.template_tree, project.project_tree, project.layer_tree);
298 // now open the displays that were stored for later, if any:
299 Display.openLater();
301 return project;
305 /** Creates a new project to be based on .xml and image files, not a database. Images are left where they are, keeping the path to them. If the arg equals 'blank', then no template is asked for. */
306 static public Project newFSProject(String arg) {
307 return newFSProject(arg, null);
310 static public Project newFSProject(String arg, TemplateThing template_root) {
311 return newFSProject(arg, null, null);
314 /** Creates a new project to be based on .xml and image files, not a database. Images are left where they are, keeping the path to them. If the arg equals 'blank', then no template is asked for; if template_root is not null that is used; else, a template file is asked for. */
315 static public Project newFSProject(String arg, TemplateThing template_root, String storage_folder) {
316 if (Utils.wrongImageJVersion()) return null;
317 try {
318 String dir_project = storage_folder;
319 if (null == dir_project || !new File(dir_project).isDirectory()) {
320 DirectoryChooser dc = new DirectoryChooser("Select storage folder");
321 dir_project = dc.getDirectory();
322 if (null == dir_project) return null; // user cancelled dialog
323 if (!Loader.canReadAndWriteTo(dir_project)) {
324 Utils.showMessage("Can't read/write to the selected storage folder.\nPlease check folder permissions.");
325 return null;
328 FSLoader loader = new FSLoader(dir_project);
329 if (!loader.isReady()) return null;
330 Project project = createNewProject(loader, !("blank".equals(arg) || "amira".equals(arg)), template_root);
332 // help the helpless users:
333 if (null != project && ControlWindow.isGUIEnabled()) {
334 Utils.log2("Creating automatic Display.");
335 // add a default layer
336 Layer layer = new Layer(project, 0, 1, project.layer_set);
337 project.layer_set.add(layer);
338 project.layer_tree.addLayer(project.layer_set, layer);
339 Display.createDisplay(project, layer);
341 try {
342 Thread.sleep(200); // waiting cheaply for asynchronous swing calls
343 } catch (InterruptedException ie) {
344 ie.printStackTrace();
347 if (arg.equals("amira") || arg.equals("stack")) {
348 // forks into a task thread
349 loader.importStack(project.layer_set.getLayer(0), null, true);
352 return project;
353 } catch (Exception e) {
354 IJError.print(e);
356 return null;
359 static public Project openFSProject(final String path) {
360 return openFSProject(path, true);
363 /** Opens a project from an .xml file. If the path is null it'll be asked for.
364 * Only one project may be opened at a time.
366 synchronized static public Project openFSProject(final String path, final boolean open_displays) {
367 if (Utils.wrongImageJVersion()) return null;
368 final FSLoader loader = new FSLoader();
369 final Object[] data = loader.openFSProject(path, open_displays);
370 if (null == data) {
371 return null;
373 //Macro.setOptions("xml_path=" + loader.getProjectXMLPath()); // TODO gets overwritten by the file dialog, but still, the value is the same. Only the key is different.
374 final TemplateThing root_tt = (TemplateThing)data[0];
375 final ProjectThing root_pt = (ProjectThing)data[1];
376 final LayerThing root_lt = (LayerThing)data[2];
377 final HashMap ht_pt_expanded = (HashMap)data[3];
379 final Project project = (Project)root_pt.getObject();
380 project.createLayerTemplates();
381 project.template_tree = new TemplateTree(project, root_tt);
382 project.root_tt = root_tt;
383 project.root_pt= root_pt;
384 project.project_tree = new ProjectTree(project, project.root_pt);
385 project.layer_tree = new LayerTree(project, root_lt);
386 project.root_lt = root_lt;
387 project.layer_set = (LayerSet)root_lt.getObject();
389 // if all when well, register as open:
390 al_open_projects.add(project);
391 // create the project control window, containing the trees in a double JSplitPane
392 ControlWindow.add(project, project.template_tree, project.project_tree, project.layer_tree);
394 // debug: print the entire root project tree
395 //project.root_pt.debug("");
397 // set ProjectThing nodes expanded state, now that the trees exist
398 try {
399 java.lang.reflect.Field f = JTree.class.getDeclaredField("expandedState");
400 f.setAccessible(true);
401 Hashtable ht_exp = (Hashtable)f.get(project.project_tree);
402 for (Iterator it = ht_pt_expanded.entrySet().iterator(); it.hasNext(); ) {
403 Map.Entry entry = (Map.Entry)it.next();
404 ProjectThing pt = (ProjectThing)entry.getKey();
405 Boolean expanded = (Boolean)entry.getValue();
406 //project.project_tree.expandPath(new TreePath(project.project_tree.findNode(pt, project.project_tree).getPath()));
407 // WARNING the above is wrong in that it will expand the whole thing, not just set the state of the node!!
408 // So the ONLY way to do it is to start from the child-most leafs of the tree, and apply the expanding to them upward. This is RIDICULOUS, how can it be so broken
409 // so, hackerous:
410 DefaultMutableTreeNode nd = project.project_tree.findNode(pt, project.project_tree);
411 //if (null == nd) Utils.log2("null node for " + pt);
412 //else Utils.log2("path: " + new TreePath(nd.getPath()));
413 if (null == nd) {
414 Utils.log2("Can't find node for " + pt);
415 } else {
416 ht_exp.put(new TreePath(nd.getPath()), expanded);
419 project.project_tree.updateUILater(); // very important!!
420 } catch (Exception e) {
421 IJError.print(e);
423 // open any stored displays
424 if (open_displays) {
425 final Bureaucrat burro = Display.openLater();
426 if (null != burro) {
427 final Runnable ru = new Runnable() {
428 public void run() {
429 // wait until the Bureaucrat finishes
430 try { burro.join(); } catch (InterruptedException ie) {}
431 // restore to non-changes (crude, but works)
432 project.loader.setChanged(false);
433 Utils.log2("C set to false");
436 new Thread() {
437 public void run() {
438 setPriority(Thread.NORM_PRIORITY);
439 // avoiding "can't call invokeAndWait from the EventDispatch thread" error
440 try {
441 javax.swing.SwingUtilities.invokeAndWait(ru);
442 } catch (Exception e) {
443 Utils.log2("ERROR: " + e);
446 }.start();
447 // SO: WAIT TILL THE END OF TIME!
448 new Thread() { public void run() {
449 try {
450 Thread.sleep(4000); // ah, the pain in my veins. I can't take this shitty setup anymore.
451 javax.swing.SwingUtilities.invokeAndWait(new Runnable() { public void run() {
452 project.getLoader().setChanged(false);
453 Utils.log2("D set to false");
454 }});
455 project.getTemplateTree().updateUILater(); // repainting to fix gross errors in tree rendering
456 project.getProjectTree().updateUILater(); // idem
457 } catch (Exception ie) {}
458 }}.start();
459 } else {
460 // help the helpless users
461 Display.createDisplay(project, project.layer_set.getLayer(0));
464 return project;
467 static private Project createNewProject(Loader loader, boolean ask_for_template) {
468 return createNewProject(loader, ask_for_template, null);
471 static private Project createNewSubProject(Project source, Loader loader) {
472 return createNewProject(loader, false, source.root_tt, true);
475 static private Project createNewProject(Loader loader, boolean ask_for_template, TemplateThing template_root) {
476 return createNewProject(loader, ask_for_template, template_root, false);
479 static private Project createNewProject(Loader loader, boolean ask_for_template, TemplateThing template_root, boolean clone_ids) {
480 Project project = new Project(loader);
481 // ask for an XML properties file that defines the Thing objects that can be created
482 // (the XML file will be parsed into a TemplateTree filled with TemplateThing objects)
483 //Utils.log2("ask_for_template: " + ask_for_template);
484 if (ask_for_template) template_root = project.loader.askForXMLTemplate(project);
485 if (null == template_root) {
486 template_root = new TemplateThing("anything");
487 } else if (clone_ids) {
488 // the given template_root belongs to another project from which we are cloning
489 template_root = template_root.clone(project, true);
490 } // else, use the given template_root as is.
491 // create tree
492 project.template_tree = new TemplateTree(project, template_root);
493 project.root_tt = template_root;
494 // collect unique TemplateThing instances
495 project.ht_unique_tt = template_root.getUniqueTypes(new HashMap<String,TemplateThing>());
496 // add all TemplateThing objects to the database, recursively
497 if (!clone_ids) template_root.addToDatabase(project);
498 // else already done when cloning the root_tt
500 // create a non-database bound template for the project Thing
501 TemplateThing project_template = new TemplateThing("project");
502 project.ht_unique_tt.put("project", project_template);
503 project_template.addChild(template_root);
504 // create the project Thing, to be root of the whole project thing tree
505 try {
506 project.root_pt= new ProjectThing(project_template, project, project);
507 } catch (Exception e) { IJError.print(e); }
508 // create the user objects tree
509 project.project_tree = new ProjectTree(project, project.root_pt);
510 // create the layer's tree
511 project.createLayerTemplates();
512 project.layer_set = new LayerSet(project, "Top Level", 0, 0, null, 2048, 2048); // initialized with default values, and null parent to signal 'root'
513 try {
514 project.root_lt = new LayerThing(project.layer_set_template, project, project.layer_set);
515 project.layer_tree = new LayerTree(project, project.root_lt);
516 } catch (Exception e) {
517 project.remove();
518 IJError.print(e);
520 // create the project control window, containing the trees in a double JSplitPane
521 ControlWindow.add(project, project.template_tree, project.project_tree, project.layer_tree); // beware that this call is asynchronous, dispatched by the SwingUtilities.invokeLater to avoid havok with Swing components.
522 // register
523 al_open_projects.add(project);
525 return project;
529 public void setTempLoader(Loader loader) {
530 if (null == this.loader) {
531 this.loader = loader;
532 } else {
533 Utils.log2("Project.setTempLoader: already have one.");
537 public final Loader getLoader() {
538 return loader;
541 public String getType() {
542 return "project";
545 public String save() {
546 Thread.yield(); // let it repaint the log window
547 String path = loader.save(this); // TODO: put in a bkgd task, and show a progress bar
548 return path;
551 public String saveAs(String xml_path, boolean overwrite) {
552 return loader.saveAs(xml_path, overwrite);
555 public boolean destroy() {
556 if (loader.hasChanges() && !getBooleanProperty("no_shutdown_hook")) { // DBLoader always returns false
557 if (ControlWindow.isGUIEnabled()) {
558 final YesNoDialog yn = ControlWindow.makeYesNoDialog("TrakEM2", "There are unsaved changes in project " + title + ". Save them?");
559 if (yn.yesPressed()) {
560 loader.save(this);
562 } else {
563 Utils.log2("WARNING: closing project '" + title + "' with unsaved changes.");
566 al_open_projects.remove(this);
567 // flush all memory
568 if (null != loader) { // the last project is destroyed twice for some reason, if several are open. This is a PATCH
569 loader.destroy(); // and disconnect
570 loader = null;
572 ControlWindow.remove(this); // AFTER loader.destroy() call.
573 if (null != template_tree) template_tree.destroy();
574 if (null != project_tree) project_tree.destroy();
575 if (null != layer_tree) layer_tree.destroy();
576 Polyline.flushTraceCache(this);
577 Compare.removeProject(this);
578 this.template_tree = null; // flag to mean: we're closing
579 // close all open Displays
580 Display.close(this);
581 return true;
584 public boolean isBeingDestroyed() {
585 return null == template_tree;
588 /** Remove the project from the database and release memory. */
589 public void remove() {
590 removeFromDatabase();
591 destroy();
594 /** Remove the project from the database and release memory. */
595 public boolean remove(boolean check) {
596 if (!Utils.check("Delete the project " + toString() + " from the database?")) return false;
597 removeFromDatabase();
598 destroy();
599 return true;
602 public void setTitle(String title) {
603 if (null == title) return;
604 this.title = title;
605 ControlWindow.updateTitle(this);
606 loader.updateInDatabase(this, "title");
609 public String toString() {
610 if (null == title || title.equals("Project")) {
611 try {
612 return loader.makeProjectName(); // can't use this.id, because the id system is project-centric and thus all FSLoader projects would have the same id.
613 } catch (Exception e) { Utils.log2("Swing again."); }
615 return title;
618 public String getTitle() {
619 return title;
622 public TemplateTree getTemplateTree() {
623 return template_tree;
626 public LayerTree getLayerTree() {
627 return layer_tree;
630 public ProjectTree getProjectTree() {
631 return project_tree;
634 /** Make an object of the type the TemplateThing can hold. */
635 public Object makeObject(final TemplateThing tt) {
636 final String type = tt.getType();
637 if (type.equals("profile")) {
638 ProjectToolbar.setTool(ProjectToolbar.PENCIL); // this should go elsewhere, in display issues.
639 return new Profile(this, "profile", 0, 0);
640 } else if (type.equals("pipe")) {
641 ProjectToolbar.setTool(ProjectToolbar.PEN);
642 return new Pipe(this, "pipe", 0, 0);
643 } else if (type.equals("polyline")) {
644 ProjectToolbar.setTool(ProjectToolbar.PEN);
645 return new Polyline(this, "polyline");
646 } else if (type.equals("area_list")) {
647 ProjectToolbar.setTool(ProjectToolbar.PEN);
648 return new AreaList(this, "area_list", 0, 0);
649 } else if (type.equals("ball")) {
650 ProjectToolbar.setTool(ProjectToolbar.PEN);
651 return new Ball(this, "ball", 0, 0);
652 } else if (type.equals("dissector")) {
653 ProjectToolbar.setTool(ProjectToolbar.PEN);
654 return new Dissector(this, "dissector", 0, 0);
655 } else if (type.equals("label")) {
656 return new DLabel(this, " ", 0, 0); // never used so far
657 } else {
658 // just the name, for the abstract ones
659 return type;
663 /** Returns true if the type is 'patch', 'layer', 'layer_set', 'profile', 'profile_list' 'pipe'. */
664 static public boolean isBasicType(String type) {
665 if (isProjectType(type)) return true;
666 if (isLayerType(type)) return true;
667 return false;
670 static public boolean isProjectType(String type) {
671 type = type.toLowerCase();
672 if (type.equals("profile_list")) return true;
673 return false;
676 static public boolean isLayerType(String type) {
677 type = type.toLowerCase().replace(' ', '_');
678 if (type.equals("patch")) return true;
679 if (type.equals("area_list")) return true;
680 if (type.equals("label")) return true;
681 if (type.equals("profile")) return true;
682 if (type.equals("pipe")) return true;
683 if (type.equals("polyline")) return true;
684 if (type.equals("ball")) return true;
685 if (type.equals("layer")) return true;
686 if (type.equals("layer set")) return true; // for XML ...
687 return false;
690 /** Remove the ProjectThing that contains the given object, which will remove the object itself as well. */
691 public boolean removeProjectThing(Object object, boolean check) {
692 return removeProjectThing(object, check, false, 0);
695 /** Remove the ProjectThing that contains the given object, which will remove the object itself as well. */
696 public boolean removeProjectThing(Object object, boolean check, boolean remove_empty_parents, int levels) {
697 if (levels < 0) {
698 Utils.log2("Project.removeProjectThing: levels must be zero or above.");
699 return false;
701 // find the Thing
702 DefaultMutableTreeNode root = (DefaultMutableTreeNode)project_tree.getModel().getRoot();
703 Enumeration e = root.depthFirstEnumeration();
704 DefaultMutableTreeNode node = null;
705 while (e.hasMoreElements()) {
706 node = (DefaultMutableTreeNode)e.nextElement();
707 Object ob = node.getUserObject();
708 if (ob instanceof ProjectThing && ((ProjectThing)ob).getObject() == object) {
709 if (check && !Utils.check("Remove " + object.toString() + "?")) return false;
710 // remove the ProjectThing, its object and the node that holds it.
711 project_tree.remove(node, false, remove_empty_parents, levels);
712 return true;
713 } // the above could be done more generic with a Thing.contains(Object), but I want to make sure that the object is contained by a ProjectThing and nothing else.
715 // not found:
716 return false;
720 /** Find the node in the layer tree with a Thing that contains the given object, and set it selected/highlighted, deselecting everything else first. */
721 public void select(final Layer layer) {
722 select(layer, layer_tree);
724 /** Find the node in any tree with a Thing that contains the given Displayable, and set it selected/highlighted, deselecting everything else first. */
725 public void select(final Displayable d) {
726 if (d.getClass() == LayerSet.class) select(d, layer_tree);
727 else select(d, project_tree);
730 private final void select(final Object ob, final JTree tree) {
731 // Find the Thing that contains the object
732 final Thing root_thing = (Thing)((DefaultMutableTreeNode)tree.getModel().getRoot()).getUserObject();
733 final Thing child_thing = root_thing.findChild(ob);
734 // find the node that contains the Thing, and select it
735 DNDTree.selectNode(child_thing, tree);
738 /** Find the ProjectThing instance with the given id. */
739 public ProjectThing find(final long id) {
740 // can't be the Project itself
741 return root_pt.findChild(id);
744 public DBObject findById(final long id) {
745 if (this.id == id) return this;
746 DBObject dbo = layer_set.findById(id);
747 if (null != dbo) return dbo;
748 dbo = root_pt.findChild(id); // could call findObject(id), but all objects must exist in layer sets anyway.
749 if (null != dbo) return dbo;
750 return (DBObject)root_tt.findChild(id);
753 /** Find a LayerThing that contains the given object. */
754 public LayerThing findLayerThing(final Object ob) {
755 final Object lob = root_lt.findChild(ob);
756 return null != lob ? (LayerThing)lob : null;
759 /** Find a ProjectThing that contains the given object. */
760 public ProjectThing findProjectThing(final Object ob) {
761 final Object pob = root_pt.findChild(ob);
762 return null != pob ? (ProjectThing)pob : null;
765 public ProjectThing getRootProjectThing() {
766 return root_pt;
769 public LayerSet getRootLayerSet() {
770 return layer_set;
773 /** Returns the title of the enclosing abstract node in the ProjectTree.*/
774 public String getParentTitle(final Displayable d) {
775 try {
776 ProjectThing thing = (ProjectThing)this.root_pt.findChild(d);
777 ProjectThing parent = (ProjectThing)thing.getParent();
778 if (d instanceof Profile) {
779 parent = (ProjectThing)parent.getParent(); // skip the profile_list
781 if (null == parent) Utils.log2("null parent for " + d);
782 if (null != parent && null == parent.getObject()) {
783 Utils.log2("null ob for parent " + parent + " of " + d);
785 return parent.getObject().toString(); // the abstract thing should be enclosing a String object
786 } catch (Exception e) { IJError.print(e); return null; }
789 /** Searches upstream in the Project tree for things that have a user-defined name, stops at the first and returns it along with all the intermediate ones that only have a type and not a title, appended. */
790 public String getMeaningfulTitle(final Displayable d) {
791 ProjectThing thing = (ProjectThing)this.root_pt.findChild(d);
792 if (null == thing) return d.getTitle(); // happens if there is no associated node
793 String title = new StringBuilder(!thing.getType().equals(d.getTitle()) ? d.getTitle() + " [" : "[").append(thing.getType()).append(' ').append('#').append(d.getId()).append(']').toString();
795 if (!thing.getType().equals(d.getTitle())) {
796 return title;
799 ProjectThing parent = (ProjectThing)thing.getParent();
800 StringBuilder sb = new StringBuilder(title);
801 while (null != parent) {
802 Object ob = parent.getObject();
803 if (ob.getClass() == Project.class) break;
804 String type = parent.getType();
805 if (!ob.equals(type)) { // meaning, something else was typed in as a title
806 sb.insert(0, new StringBuilder(ob.toString()).append(' ').append('[').append(type).append(']').append('/').toString());
807 //title = ob.toString() + " [" + type + "]/" + title;
808 break;
810 sb.insert(0, '/');
811 sb.insert(0, type);
812 //title = type + "/" + title;
813 parent = (ProjectThing)parent.getParent();
815 //return title;
816 return sb.toString();
819 /** Returns the first upstream user-defined name and type, and the id of the displayable tagged at the end.
820 * If no user-defined name is found, then the type is prepended to the id.
822 public String getShortMeaningfulTitle(final Displayable d) {
823 ProjectThing thing = (ProjectThing)this.root_pt.findChild(d);
824 if (null == thing) return d.getTitle(); // happens if there is no associated node
825 return getShortMeaningfulTitle(thing, d);
827 public String getShortMeaningfulTitle(final ProjectThing thing, final Displayable d) {
828 if (thing.getObject() != d) {
829 return thing.toString();
831 ProjectThing parent = (ProjectThing)thing.getParent();
832 String title = "#" + d.getId();
833 while (null != parent) {
834 Object ob = parent.getObject();
835 String type = parent.getType();
836 if (!ob.equals(type)) { // meaning, something else was typed in as a title
837 title = ob.toString() + " [" + type + "] " + title;
838 break;
840 parent = (ProjectThing)parent.getParent();
842 // if nothing found, prepend the type
843 if ('#' == title.charAt(0)) title = Project.getName(d.getClass()) + " " + title;
844 return title;
847 static public String getType(final Class c) {
848 if (AreaList.class == c) return "area_list";
849 if (DLabel.class == c) return "label";
850 return c.getName().toLowerCase();
853 /** Returns the proper TemplateThing for the given type, complete with children and attributes if any. */
854 public TemplateThing getTemplateThing(String type) {
855 return ht_unique_tt.get(type);
858 /** Returns a list of existing unique types in the template tree (thus the 'project' type is not included, nor the label). The basic types are guaranteed to be present even if there are no instances in the template tree. */
859 public String[] getUniqueTypes() {
860 // ensure the basic types (pipe, ball, profile, profile_list) are present
861 if (!ht_unique_tt.containsKey("profile")) ht_unique_tt.put("profile", new TemplateThing("profile"));
862 if (!ht_unique_tt.containsKey("profile_list")) {
863 TemplateThing tpl = new TemplateThing("profile_list");
864 tpl.addChild((TemplateThing) ht_unique_tt.get("profile"));
865 ht_unique_tt.put("profile_list", tpl);
867 if (!ht_unique_tt.containsKey("pipe")) ht_unique_tt.put("pipe", new TemplateThing("pipe"));
868 if (!ht_unique_tt.containsKey("polyline")) ht_unique_tt.put("polyline", new TemplateThing("polyline"));
869 if (!ht_unique_tt.containsKey("ball")) ht_unique_tt.put("ball", new TemplateThing("ball"));
870 if (!ht_unique_tt.containsKey("area_list")) ht_unique_tt.put("area_list", new TemplateThing("area_list"));
871 if (!ht_unique_tt.containsKey("dissector")) ht_unique_tt.put("dissector", new TemplateThing("dissector"));
872 // this should be done automagically by querying the classes in the package ... but java can't do that without peeking into the .jar .class files. Buh.
874 TemplateThing project_tt = ht_unique_tt.remove("project");
875 /* // debug
876 for (Iterator it = ht_unique_tt.keySet().iterator(); it.hasNext(); ) {
877 Utils.log2("class: " + it.next().getClass().getName());
878 } */
879 final String[] ut = new String[ht_unique_tt.size()];
880 ht_unique_tt.keySet().toArray(ut);
881 ht_unique_tt.put("project", project_tt);
882 Arrays.sort(ut);
883 return ut;
886 /** Remove a unique type from the HashMap. Basic types can't be removed. */
887 public boolean removeUniqueType(String type) {
888 if (null == type || isBasicType(type)) return false;
889 return null != ht_unique_tt.remove(type);
892 public boolean typeExists(String type) {
893 return ht_unique_tt.containsKey(type);
896 /** Returns false if the type exists already. */
897 public boolean addUniqueType(TemplateThing tt) {
898 if (null == ht_unique_tt) this.ht_unique_tt = new HashMap();
899 if (ht_unique_tt.containsKey(tt.getType())) return false;
900 ht_unique_tt.put(tt.getType(), tt);
901 return true;
904 public boolean updateTypeName(String old_type, String new_type) {
905 if (ht_unique_tt.containsKey(new_type)) {
906 Utils.showMessage("Can't rename type '" + old_type + "' : a type named '"+new_type+"' already exists!");
907 return false;
909 ht_unique_tt.put(new_type, ht_unique_tt.remove(old_type));
910 return true;
913 private void createLayerTemplates() {
914 if (null == layer_template) {
915 layer_template = new TemplateThing("layer");
916 layer_set_template = new TemplateThing("layer_set");
917 layer_set_template.addChild(layer_template);
918 layer_template.addChild(layer_set_template); // adding a new instance to keep parent/child relationships clean
919 // No need, there won't ever be a loop so far WARNING may change in the future.
923 /** Export the main trakem2 tag wrapping four hierarchies (the project tag, the ProjectTree, and the Top Level LayerSet the latter including all Displayable objects) and a list of displays. */
924 public void exportXML(final java.io.Writer writer, String indent, Object any) throws Exception {
925 StringBuffer sb_body = new StringBuffer();
926 // 1 - opening tag
927 sb_body.append(indent).append("<trakem2>\n");
928 String in = indent + "\t";
929 // 2 - the project itself
930 sb_body.append(in).append("<project \n")
931 .append(in).append("\tid=\"").append(id).append("\"\n")
932 .append(in).append("\ttitle=\"").append(title).append("\"\n");
933 loader.insertXMLOptions(sb_body, in + "\t");
934 for (Iterator it = ht_props.entrySet().iterator(); it.hasNext(); ) {
935 Map.Entry prop = (Map.Entry)it.next();
936 sb_body.append(in).append('\t').append((String)prop.getKey()).append("=\"").append((String)prop.getValue()).append("\"\n");
938 sb_body.append(in).append(">\n");
939 // 3 - export ProjectTree abstract hierachy (skip the root since it wraps the project itself)
940 if (null != root_pt.getChildren()) {
941 String in2 = in + "\t";
942 for (Iterator it = root_pt.getChildren().iterator(); it.hasNext(); ) {
943 ((ProjectThing)it.next()).exportXML(sb_body, in2, any);
946 sb_body.append(in).append("</project>\n");
947 writer.write(sb_body.toString());
948 sb_body.setLength(0);
949 sb_body = null;
950 // 4 - export LayerSet hierarchy of Layer, LayerSet and Displayable objects
951 layer_set.exportXML(writer, in, any);
952 // 5 - export Display objects
953 Display.exportXML(this, writer, in, any);
954 // 6 - closing tag
955 writer.write("</trakem2>\n");
958 /** Export a complete DTD listing to export the project as XML. */
959 public void exportDTD(StringBuffer sb_header, HashSet hs, String indent) {
960 // 1 - TrakEM2 tag that encloses all hierarchies
961 sb_header.append(indent).append("<!ELEMENT ").append("trakem2 (project,t2_layer_set,t2_display)>\n");
962 // 2 - export user-defined templates
963 //TemplateThing root_tt = (TemplateThing)((DefaultMutableTreeNode)((DefaultTreeModel)template_tree.getModel()).getRoot()).getUserObject();
964 sb_header.append(indent).append("<!ELEMENT ").append("project (").append(root_tt.getType()).append(")>\n");
965 sb_header.append(indent).append("<!ATTLIST project id NMTOKEN #REQUIRED>\n");
966 sb_header.append(indent).append("<!ATTLIST project unuid NMTOKEN #REQUIRED>\n");
967 sb_header.append(indent).append("<!ATTLIST project title NMTOKEN #REQUIRED>\n");
968 sb_header.append(indent).append("<!ATTLIST project preprocessor NMTOKEN #REQUIRED>\n");
969 sb_header.append(indent).append("<!ATTLIST project mipmaps_folder NMTOKEN #REQUIRED>\n");
970 sb_header.append(indent).append("<!ATTLIST project storage_folder NMTOKEN #REQUIRED>\n");
971 for (Iterator it = ht_props.entrySet().iterator(); it.hasNext(); ) {
972 Map.Entry prop = (Map.Entry)it.next();
973 sb_header.append(indent).append("<!ATTLIST project ").append((String)prop.getKey()).append(" NMTOKEN #REQUIRED>\n");
975 root_tt.exportDTD(sb_header, hs, indent);
976 // 3 - export all project objects DTD in the Top Level LayerSet
977 Layer.exportDTD(sb_header, hs, indent);
978 LayerSet.exportDTD(sb_header, hs, indent);
979 Ball.exportDTD(sb_header, hs, indent);
980 DLabel.exportDTD(sb_header, hs, indent);
981 Patch.exportDTD(sb_header, hs, indent);
982 Pipe.exportDTD(sb_header, hs, indent);
983 Polyline.exportDTD(sb_header, hs, indent);
984 Profile.exportDTD(sb_header, hs, indent);
985 AreaList.exportDTD(sb_header, hs, indent);
986 Dissector.exportDTD(sb_header, hs, indent);
987 Displayable.exportDTD(sb_header, hs, indent); // the subtypes of all Displayable types
988 // 4 - export Display
989 Display.exportDTD(sb_header, hs, indent);
990 // all the above could be done with reflection, automatically detecting the presence of an exportDTD method.
993 /** Returns the String to be used as Document Type of the XML file, generated from the name of the root template thing.*/
994 public String getDocType() {
995 //TemplateThing root_tt = (TemplateThing)((DefaultMutableTreeNode)((DefaultTreeModel)template_tree.getModel()).getRoot()).getUserObject();
996 return "trakem2_" + root_tt.getType();
999 /** Find an instance containing the given tree. */
1000 static public Project getInstance(final DNDTree ob) {
1001 for (Iterator it = al_open_projects.iterator(); it.hasNext(); ) {
1002 Project project = (Project)it.next();
1003 if (project.layer_tree.equals(ob)) return project;
1004 if (project.project_tree.equals(ob)) return project;
1005 if (project.template_tree.equals(ob)) return project;
1007 return null;
1010 /** Returns a user-understandable name for the given class. */
1011 static public String getName(final Class c) {
1012 String name = c.getName();
1013 name = name.substring(name.lastIndexOf('.') + 1);
1014 if (name.equals("DLabel")) return "Label";
1015 else if (name.equals("Patch")) return "Image";
1016 //else if (name.equals("Pipe")) return "Tube";
1017 //else if (name.equals("Ball")) return "Sphere group"; // TODO revise consistency with XML templates and so on
1018 else return name;
1021 public String getInfo() {
1022 StringBuilder sb = new StringBuilder("Project id: ");
1023 sb.append(this.id).append("\nProject name: ").append(this.title)
1024 .append("\nTrees:\n")
1025 .append(project_tree.getInfo()).append("\n")
1026 .append(layer_tree.getInfo())
1028 return sb.toString();
1031 static public Project findProject(Loader loader) {
1032 for (Iterator it = al_open_projects.iterator(); it.hasNext(); ) {
1033 Project pro = (Project)it.next();
1034 if (pro.getLoader() == loader) return pro;
1036 return null;
1039 private boolean input_disabled = false;
1041 /** Tells the displays concerning this Project to accept/reject input. */
1042 public void setReceivesInput(boolean b) {
1043 this.input_disabled = !b;
1044 Display.setReceivesInput(this, b);
1047 public boolean isInputEnabled() {
1048 return !input_disabled;
1051 /** Create a new subproject for the given layer range and ROI.
1052 * Create a new Project using the given project as template. This means the DTD of the given project is copied, as well as the storage and mipmaps folders; everything else is empty in the new project. */
1053 public Project createSubproject(final Rectangle roi, final Layer first, final Layer last) {
1054 try {
1055 // The order matters.
1056 final Project pr = new Project(new FSLoader(this.getLoader()));
1057 pr.id = this.id;
1058 // copy properties
1059 pr.title = this.title;
1060 pr.ht_props.putAll(this.ht_props);
1061 // copy template
1062 pr.root_tt = this.root_tt.clone(pr, true);
1063 pr.template_tree = new TemplateTree(pr, pr.root_tt);
1064 pr.ht_unique_tt = root_tt.getUniqueTypes(new HashMap());
1065 TemplateThing project_template = new TemplateThing("project");
1066 project_template.addChild(pr.root_tt);
1067 pr.ht_unique_tt.put("project", project_template);
1068 // create the layers templates
1069 pr.createLayerTemplates();
1070 // copy LayerSet and all involved Displayable objects
1071 pr.layer_set = (LayerSet)this.layer_set.clone(pr, first, last, roi, false, true);
1072 // create layer tree
1073 pr.root_lt = new LayerThing(pr.layer_set_template, pr, pr.layer_set);
1074 pr.layer_tree = new LayerTree(pr, pr.root_lt);
1075 // add layer nodes to the layer tree (solving chicken-and-egg problem)
1076 pr.layer_set.updateLayerTree();
1077 // copy project tree
1078 pr.root_pt = this.root_pt.subclone(pr);
1079 pr.project_tree = new ProjectTree(pr, pr.root_pt);
1080 // not copying node expanded state.
1081 // register
1082 al_open_projects.add(pr);
1083 // add to gui:
1084 ControlWindow.add(pr, pr.template_tree, pr.project_tree, pr.layer_tree);
1086 // Above, the id of each object is preserved from this project into the subproject.
1088 // The abstract structure should be copied in full regardless, without the basic objects
1089 // included if they intersect the roi.
1091 return pr;
1093 } catch (Exception e) { e.printStackTrace(); }
1094 return null;
1097 public void parseXMLOptions(final HashMap ht_attributes) {
1098 ((FSLoader)this.project.getLoader()).parseXMLOptions(ht_attributes);
1099 // all keys that remain are properties
1100 ht_props.putAll(ht_attributes);
1101 for (final Iterator it = ht_props.entrySet().iterator(); it.hasNext(); ) {
1102 Map.Entry prop = (Map.Entry)it.next();
1103 Utils.log2("parsed: " + prop.getKey() + "=" + prop.getValue());
1106 public HashMap<String,String> getPropertiesCopy() {
1107 return (HashMap<String,String>)ht_props.clone();
1109 /** Returns null if not defined. */
1110 public String getProperty(final String key) {
1111 return ht_props.get(key);
1113 /** Returns the default value if not defined, or if not a number or not parsable as a number. */
1114 public float getProperty(final String key, final float default_value) {
1115 try {
1116 final String s = ht_props.get(key);
1117 if (null == s) return default_value;
1118 final float num = Float.parseFloat(s);
1119 if (Float.isNaN(num)) return default_value;
1120 return num;
1121 } catch (NumberFormatException nfe) {
1122 IJError.print(nfe);
1124 return default_value;
1127 public int getProperty(final String key, final int default_value) {
1128 try {
1129 final String s = ht_props.get(key);
1130 if (null == s) return default_value;
1131 return Integer.parseInt(s);
1132 } catch (NumberFormatException nfe) {
1133 IJError.print(nfe);
1135 return default_value;
1138 public boolean getBooleanProperty(final String key) {
1139 return "true".equals(ht_props.get(key));
1141 public void setProperty(final String key, final String value) {
1142 if (null == value) ht_props.remove(key);
1143 else ht_props.put(key, value);
1145 private final boolean addBox(final GenericDialog gd, final Class c) {
1146 final String name = Project.getName(c);
1147 final boolean link = "true".equals(ht_props.get(name.toLowerCase() + "_nolinks"));
1148 gd.addCheckbox(name, link);
1149 return link;
1151 private final void setLinkProp(final boolean before, final boolean after, final Class c) {
1152 if (before) {
1153 if (!after) ht_props.remove(Project.getName(c).toLowerCase()+"_nolinks");
1154 } else if (after) {
1155 ht_props.put(Project.getName(c).toLowerCase()+"_nolinks", "true");
1157 // setting to false would have no meaning, so the link prop is removed
1159 /** Returns true if there were any changes. */
1160 private final boolean adjustProp(final String prop, final boolean before, final boolean after) {
1161 if (before) {
1162 if (!after) ht_props.remove(prop);
1163 } else if (after) {
1164 ht_props.put(prop, "true");
1166 return before != after;
1168 public void adjustProperties() {
1169 // should be more generic, but for now it'll do
1170 GenericDialog gd = new GenericDialog("Properties");
1171 gd.addMessage("Ignore image linking for:");
1172 boolean link_labels = addBox(gd, DLabel.class);
1173 boolean link_arealist = addBox(gd, AreaList.class);
1174 boolean link_pipes = addBox(gd, Pipe.class);
1175 boolean link_polylines = addBox(gd, Polyline.class);
1176 boolean link_balls = addBox(gd, Ball.class);
1177 boolean link_dissectors = addBox(gd, Dissector.class);
1178 boolean dissector_zoom = "true".equals(ht_props.get("dissector_zoom"));
1179 gd.addCheckbox("Zoom-invariant markers for Dissector", dissector_zoom);
1180 boolean no_color_cues = "true".equals(ht_props.get("no_color_cues"));
1181 gd.addCheckbox("Paint_color_cues", !no_color_cues);
1182 gd.addMessage("Currently linked objects\nwill remain so unless\nexplicitly unlinked.");
1183 String current_mode = ht_props.get("image_resizing_mode");
1184 // Forbid area averaging: doesn't work, and it's not faster than gaussian.
1185 if (Utils.indexOf(current_mode, Loader.modes) >= Loader.modes.length) current_mode = Loader.modes[3]; // GAUSSIAN
1186 gd.addChoice("Image_resizing_mode: ", Loader.modes, null == current_mode ? Loader.modes[3] : current_mode);
1187 int current_R = (int)(100 * ini.trakem2.imaging.StitchingTEM.DEFAULT_MIN_R); // make the float a percent
1188 try {
1189 String scR = ht_props.get("min_R");
1190 if (null != scR) current_R = (int)(Double.parseDouble(scR) * 100);
1191 } catch (Exception nfe) {
1192 IJError.print(nfe);
1194 gd.addSlider("min_R: ", 0, 100, current_R);
1196 boolean layer_mipmaps = "true".equals(ht_props.get("layer_mipmaps"));
1197 gd.addCheckbox("Layer_mipmaps", layer_mipmaps);
1198 boolean keep_mipmaps = "true".equals(ht_props.get("keep_mipmaps"));
1199 gd.addCheckbox("Keep_mipmaps_when_deleting_images", keep_mipmaps); // coping with the fact that thee is no Action context ... there should be one in the Worker thread.
1200 int bucket_side = (int)getProperty("bucket_side", Bucket.MIN_BUCKET_SIZE);
1201 gd.addNumericField("Bucket side length: ", bucket_side, 0);
1202 boolean no_shutdown_hook = "true".equals(ht_props.get("no_shutdown_hook"));
1203 gd.addCheckbox("No_shutdown_hook to save the project", no_shutdown_hook);
1204 int n_undo_steps = getProperty("n_undo_steps", 32);
1205 gd.addSlider("Undo steps", 32, 200, n_undo_steps);
1207 gd.showDialog();
1209 if (gd.wasCanceled()) return;
1210 setLinkProp(link_labels, gd.getNextBoolean(), DLabel.class);
1211 setLinkProp(link_arealist, gd.getNextBoolean(), AreaList.class);
1212 setLinkProp(link_pipes, gd.getNextBoolean(), Pipe.class);
1213 setLinkProp(link_polylines, gd.getNextBoolean(), Polyline.class);
1214 setLinkProp(link_balls, gd.getNextBoolean(), Ball.class);
1215 setLinkProp(link_dissectors, gd.getNextBoolean(), Dissector.class);
1216 if (adjustProp("dissector_zoom", dissector_zoom, gd.getNextBoolean())) {
1217 Display.repaint(layer_set); // TODO: should repaint nested LayerSets as well
1219 if (adjustProp("no_color_cues", no_color_cues, !gd.getNextBoolean())) {
1220 Display.repaint(layer_set);
1222 setProperty("image_resizing_mode", Loader.modes[gd.getNextChoiceIndex()]);
1223 setProperty("min_R", new Float((float)gd.getNextNumber() / 100).toString());
1224 boolean layer_mipmaps2 = gd.getNextBoolean();
1225 if (adjustProp("layer_mipmaps", layer_mipmaps, layer_mipmaps2)) {
1226 if (layer_mipmaps && !layer_mipmaps2) {
1227 // TODO
1228 // 1 - ask first
1229 // 2 - remove all existing images from layer.mipmaps folder
1230 } else if (!layer_mipmaps && layer_mipmaps2) {
1231 // TODO
1232 // 1 - ask first
1233 // 2 - create de novo all layer mipmaps in a background task
1236 adjustProp("keep_mipmaps", keep_mipmaps, gd.getNextBoolean());
1237 Utils.log2("keep_mipmaps: " + getBooleanProperty("keep_mipmaps"));
1239 bucket_side = (int)gd.getNextNumber();
1240 if (bucket_side > Bucket.MIN_BUCKET_SIZE) {
1241 setProperty("bucket_side", Integer.toString(bucket_side));
1242 layer_set.recreateBuckets(true);
1244 adjustProp("no_shutdown_hook", no_shutdown_hook, gd.getNextBoolean());
1245 n_undo_steps = (int)gd.getNextNumber();
1246 if (n_undo_steps < 0) n_undo_steps = 0;
1247 setProperty("n_undo_steps", Integer.toString(n_undo_steps));
1250 /** Return the Universal Near-Unique Id of this project, which may be null for non-FSLoader projects. */
1251 public String getUNUId() {
1252 return loader.getUNUId();