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.
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
;
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
;
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
{
70 //UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
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
) {
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
) {
144 static public Project
getProject(final String title
) {
145 for (final Project pr
: al_open_projects
) {
146 if (pr
.title
.equals(title
)) return pr
;
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;
160 DBLoader loader
= new DBLoader();
161 // check connection settings
162 if (!loader
.isReady()) return null;
164 if (!loader
.isConnected()) {
165 Utils
.showMessage("Can't talk to database.");
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;
177 if (!loader
.isConnected()) {
178 Utils
.showMessage("Can't talk to database.");
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).");
189 Project project
= null;
190 if (null == projects
) {
191 Utils
.showMessage("Can't fetch list of projects.");
194 } else if (0 == projects
.length
) {
195 Utils
.showMessage("No projects in this database.");
198 } else if (1 == projects
.length
) {
199 project
= projects
[0];
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]);
210 if (gd
.wasCanceled()) {
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.");
227 // now, open the selected project
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.");
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.
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.");
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;
266 root_layer_thing
= loader
.getRootLayerThing(project
, project
.root_pt
, project
.layer_set_template
, project
.layer_template
);
267 if (null == root_layer_thing
) {
269 Utils
.showMessage("Could not retrieve the root layer thing.");
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
) {
277 Utils
.showMessage("Could not retrieve the root layer set.");
280 project
.layer_set
.setup(); // set the active layer to each ZDisplayable
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.");
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:
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;
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.");
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
);
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);
353 } catch (Exception e
) {
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
);
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
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
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()));
414 Utils
.log2("Can't find node for " + pt
);
416 ht_exp
.put(new TreePath(nd
.getPath()), expanded
);
419 project
.project_tree
.updateUILater(); // very important!!
420 } catch (Exception e
) {
423 // open any stored displays
425 final Bureaucrat burro
= Display
.openLater();
427 final Runnable ru
= new Runnable() {
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");
438 setPriority(Thread
.NORM_PRIORITY
);
439 // avoiding "can't call invokeAndWait from the EventDispatch thread" error
441 javax
.swing
.SwingUtilities
.invokeAndWait(ru
);
442 } catch (Exception e
) {
443 Utils
.log2("ERROR: " + e
);
447 // SO: WAIT TILL THE END OF TIME!
448 new Thread() { public void run() {
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");
455 project
.getTemplateTree().updateUILater(); // repainting to fix gross errors in tree rendering
456 project
.getProjectTree().updateUILater(); // idem
457 } catch (Exception ie
) {}
460 // help the helpless users
461 Display
.createDisplay(project
, project
.layer_set
.getLayer(0));
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.
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
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'
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
) {
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.
523 al_open_projects
.add(project
);
529 public void setTempLoader(Loader loader
) {
530 if (null == this.loader
) {
531 this.loader
= loader
;
533 Utils
.log2("Project.setTempLoader: already have one.");
537 public final Loader
getLoader() {
541 public String
getType() {
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
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()) {
563 Utils
.log2("WARNING: closing project '" + title
+ "' with unsaved changes.");
566 al_open_projects
.remove(this);
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
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
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();
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();
602 public void setTitle(String title
) {
603 if (null == title
) return;
605 ControlWindow
.updateTitle(this);
606 loader
.updateInDatabase(this, "title");
609 public String
toString() {
610 if (null == title
|| title
.equals("Project")) {
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."); }
618 public String
getTitle() {
622 public TemplateTree
getTemplateTree() {
623 return template_tree
;
626 public LayerTree
getLayerTree() {
630 public ProjectTree
getProjectTree() {
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
658 // just the name, for the abstract ones
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;
670 static public boolean isProjectType(String type
) {
671 type
= type
.toLowerCase();
672 if (type
.equals("profile_list")) return true;
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 ...
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
) {
698 Utils
.log2("Project.removeProjectThing: levels must be zero or above.");
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
);
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.
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() {
769 public LayerSet
getRootLayerSet() {
773 /** Returns the title of the enclosing abstract node in the ProjectTree.*/
774 public String
getParentTitle(final Displayable d
) {
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())) {
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;
812 //title = type + "/" + title;
813 parent
= (ProjectThing
)parent
.getParent();
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
;
840 parent
= (ProjectThing
)parent
.getParent();
842 // if nothing found, prepend the type
843 if ('#' == title
.charAt(0)) title
= Project
.getName(d
.getClass()) + " " + 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");
876 for (Iterator it = ht_unique_tt.keySet().iterator(); it.hasNext(); ) {
877 Utils.log2("class: " + it.next().getClass().getName());
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
);
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
);
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!");
909 ht_unique_tt
.put(new_type
, ht_unique_tt
.remove(old_type
));
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();
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);
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
);
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
;
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
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
;
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
) {
1055 // The order matters.
1056 final Project pr
= new Project(new FSLoader(this.getLoader()));
1059 pr
.title
= this.title
;
1060 pr
.ht_props
.putAll(this.ht_props
);
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.
1082 al_open_projects
.add(pr
);
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.
1093 } catch (Exception e
) { e
.printStackTrace(); }
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
) {
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
;
1121 } catch (NumberFormatException nfe
) {
1124 return default_value
;
1127 public int getProperty(final String key
, final int default_value
) {
1129 final String s
= ht_props
.get(key
);
1130 if (null == s
) return default_value
;
1131 return Integer
.parseInt(s
);
1132 } catch (NumberFormatException 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
);
1151 private final void setLinkProp(final boolean before
, final boolean after
, final Class c
) {
1153 if (!after
) ht_props
.remove(Project
.getName(c
).toLowerCase()+"_nolinks");
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
) {
1162 if (!after
) ht_props
.remove(prop
);
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
1189 String scR
= ht_props
.get("min_R");
1190 if (null != scR
) current_R
= (int)(Double
.parseDouble(scR
) * 100);
1191 } catch (Exception 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
);
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
) {
1229 // 2 - remove all existing images from layer.mipmaps folder
1230 } else if (!layer_mipmaps
&& layer_mipmaps2
) {
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();