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 ("albert".equals(System
.getProperty("user.name"))) UIManager
.setLookAndFeel("com.sun.java.swing.plaf.nimbus.NimbusLookAndFeel");
75 } catch (Exception e
) {
76 Utils
.log("Failed to set System Look and Feel");
80 /* // using virtual frame buffer instead, since the trees are needed
81 public static final boolean headless = isHeadless();
83 private static boolean isHeadless() {
84 return Boolean.parseBoolean(System.getProperty("java.awt.headless"));
88 /** Keep track of all open projects. */
89 static private ArrayList
<Project
> al_open_projects
= new ArrayList
<Project
>();
91 private Loader loader
;
93 private TemplateTree template_tree
= null;
95 private ProjectTree project_tree
= null;
97 /** The root Thing that holds the project. */
98 private ProjectThing root_pt
;
100 /** The root LayerThing of the LayerTree. */
101 private LayerThing root_lt
;
103 /** The root TemplateThing of the TemplateTree. */
104 private TemplateThing root_tt
;
106 /** The root LayerSet that holds the layers. */
107 private LayerSet layer_set
;
109 static private TemplateThing layer_template
= null;
110 static private TemplateThing layer_set_template
= null;
112 /** The hashtable of unique TemplateThing types; the key is the type (String). */
113 private HashMap
<String
,TemplateThing
> ht_unique_tt
= null;
115 private LayerTree layer_tree
= null;
117 private String title
= "Project"; // default // TODO should be an attribute in the ProjectThing that holds it
119 private final HashMap
<String
,String
> ht_props
= new HashMap
<String
,String
>();
121 /** Intercept ImageJ menu commands if the front image is a FakeImagePlus. */
122 static private final ImageJCommandListener command_listener
= new ImageJCommandListener();
124 /** Universal near-unique id for this project, consisting of:
125 * <creation-time>.<storage-folder-hashcode>.<username-hashcode> */
126 private String unuid
= null;
128 /** The constructor used by the static methods present in this class. */
129 private Project(Loader loader
) {
131 this.loader
= loader
;
132 this.project
= this; // for the superclass DBObject
133 loader
.addToDatabase(this);
136 /** Constructor used by the Loader to find projects. These projects contain no loader. */
137 public Project(long id
, String title
) {
143 static public Project
getProject(final String title
) {
144 for (final Project pr
: al_open_projects
) {
145 if (pr
.title
.equals(title
)) return pr
;
150 /** Return a copy of the list of all open projects. */
151 static public ArrayList
<Project
> getProjects() {
152 return (ArrayList
<Project
>)al_open_projects
.clone();
155 /** Create a new PostgreSQL-based TrakEM2 project. */
156 static public Project
newDBProject() {
157 if (Utils
.wrongImageJVersion()) return null;
159 DBLoader loader
= new DBLoader();
160 // check connection settings
161 if (!loader
.isReady()) return null;
163 if (!loader
.isConnected()) {
164 Utils
.showMessage("Can't talk to database.");
167 return createNewProject(loader
, true);
170 /** Open a TrakEM2 project from the database. Queries the database for existing projects and if more than one, asks which one to open. */
171 static public Project
openDBProject() {
172 if (Utils
.wrongImageJVersion()) return null;
173 DBLoader loader
= new DBLoader();
174 if (!loader
.isReady()) return null;
176 if (!loader
.isConnected()) {
177 Utils
.showMessage("Can't talk to database.");
181 // query the database for existing projects
182 Project
[] projects
= loader
.getProjects();
183 if (null == projects
) {
184 Utils
.showMessage("Can't talk to database (null list).");
188 Project project
= null;
189 if (null == projects
) {
190 Utils
.showMessage("Can't fetch list of projects.");
193 } else if (0 == projects
.length
) {
194 Utils
.showMessage("No projects in this database.");
197 } else if (1 == projects
.length
) {
198 project
= projects
[0];
201 String
[] titles
= new String
[projects
.length
];
202 for (int i
=0; i
<projects
.length
; i
++) {
203 titles
[i
] = projects
[i
].title
;
205 GenericDialog gd
= new GenericDialog("Choose");
206 gd
.addMessage("Choose project to open:");
207 gd
.addChoice("project: ", titles
, titles
[titles
.length
-1]);
209 if (gd
.wasCanceled()) {
213 project
= projects
[gd
.getNextChoiceIndex()];
215 // check if the selected project is open already
216 Iterator it
= al_open_projects
.iterator();
217 while (it
.hasNext()) {
218 Project p
= (Project
)it
.next();
219 if (loader
.isIdenticalProjectSource(p
.loader
) && p
.id
== project
.id
&& p
.title
.equals(project
.title
)) {
220 Utils
.showMessage("A project with title " + p
.title
+ " and id " + p
.id
+ " from the same database is already open.");
226 // now, open the selected project
229 project
.loader
= loader
;
230 // grab the XML template
231 TemplateThing template_root
= loader
.getTemplateRoot(project
);
232 if (null == template_root
) {
233 Utils
.showMessage("Failed to retrieve the template tree.");
237 project
.template_tree
= new TemplateTree(project
, template_root
);
238 project
.ht_unique_tt
= template_root
.getUniqueTypes(new HashMap
<String
,TemplateThing
>());
239 // create the project Thing, to be root of the whole user Thing tree (and load all its objects)
240 HashMap hs_d
= new HashMap(); // to collect all created displayables, and then reassign to the proper layers.
242 // create a template for the project Thing
243 TemplateThing project_template
= new TemplateThing("project");
244 project
.ht_unique_tt
.put("project", project_template
);
245 project_template
.addChild(template_root
);
246 project
.root_pt
= loader
.getRootProjectThing(project
, template_root
, project_template
, hs_d
);
247 // restore parent/child and attribute ownership and values (now that all Things exist)
248 project
.root_pt
.setup();
249 } catch (Exception e
) {
250 Utils
.showMessage("Failed to retrieve the Thing tree for the project.");
255 // create the user objects tree
256 project
.project_tree
= new ProjectTree(project
, project
.root_pt
);
257 // restore the expanded state of each node
258 loader
.restoreNodesExpandedState(project
);
260 // create the layers templates
261 project
.createLayerTemplates();
262 // 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.
263 LayerThing root_layer_thing
= null;
265 root_layer_thing
= loader
.getRootLayerThing(project
, project
.root_pt
, project
.layer_set_template
, project
.layer_template
);
266 if (null == root_layer_thing
) {
268 Utils
.showMessage("Could not retrieve the root layer thing.");
271 // set the child/parent relationships now that everything exists
272 root_layer_thing
.setup();
273 project
.layer_set
= (LayerSet
)root_layer_thing
.getObject();
274 if (null == project
.layer_set
) {
276 Utils
.showMessage("Could not retrieve the root layer set.");
279 project
.layer_set
.setup(); // set the active layer to each ZDisplayable
282 //Utils.log2("$$$ root_lt: " + root_layer_thing + " ob: " + root_layer_thing.getObject().getClass().getName() + "\n children: " + ((LayerSet)root_layer_thing.getObject()).getLayers().size());
284 project
.layer_tree
= new LayerTree(project
, root_layer_thing
);
285 project
.root_lt
= root_layer_thing
;
286 } catch (Exception e
) {
287 Utils
.showMessage("Failed to retrieve the Layer tree for the project.");
293 // if all when well, register as open:
294 al_open_projects
.add(project
);
295 // create the project control window, containing the trees in a double JSplitPane
296 ControlWindow
.add(project
, project
.template_tree
, project
.project_tree
, project
.layer_tree
);
297 // now open the displays that were stored for later, if any:
304 /** 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. */
305 static public Project
newFSProject(String arg
) {
306 return newFSProject(arg
, null);
309 static public Project
newFSProject(String arg
, TemplateThing template_root
) {
310 return newFSProject(arg
, null, null);
313 /** 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. */
314 static public Project
newFSProject(String arg
, TemplateThing template_root
, String storage_folder
) {
315 if (Utils
.wrongImageJVersion()) return null;
317 String dir_project
= storage_folder
;
318 if (null == dir_project
|| !new File(dir_project
).isDirectory()) {
319 DirectoryChooser dc
= new DirectoryChooser("Select storage folder");
320 dir_project
= dc
.getDirectory();
321 if (null == dir_project
) return null; // user cancelled dialog
322 if (!Loader
.canReadAndWriteTo(dir_project
)) {
323 Utils
.showMessage("Can't read/write to the selected storage folder.\nPlease check folder permissions.");
327 FSLoader loader
= new FSLoader(dir_project
);
328 if (!loader
.isReady()) return null;
329 Project project
= createNewProject(loader
, !("blank".equals(arg
) || "amira".equals(arg
)), template_root
);
331 // help the helpless users:
332 if (null != project
&& ControlWindow
.isGUIEnabled()) {
333 Utils
.log2("Creating automatic Display.");
334 // add a default layer
335 Layer layer
= new Layer(project
, 0, 1, project
.layer_set
);
336 project
.layer_set
.add(layer
);
337 project
.layer_tree
.addLayer(project
.layer_set
, layer
);
338 Display
.createDisplay(project
, layer
);
341 Thread
.sleep(200); // waiting cheaply for asynchronous swing calls
342 } catch (InterruptedException ie
) {
343 ie
.printStackTrace();
346 if (arg
.equals("amira") || arg
.equals("stack")) {
347 // forks into a task thread
348 loader
.importStack(project
.layer_set
.getLayer(0), null, true);
352 } catch (Exception e
) {
358 static public Project
openFSProject(final String path
) {
359 return openFSProject(path
, true);
362 /** Opens a project from an .xml file. If the path is null it'll be asked for.
363 * Only one project may be opened at a time.
365 synchronized static public Project
openFSProject(final String path
, final boolean open_displays
) {
366 if (Utils
.wrongImageJVersion()) return null;
367 final FSLoader loader
= new FSLoader();
368 final Object
[] data
= loader
.openFSProject(path
, open_displays
);
372 //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.
373 final TemplateThing root_tt
= (TemplateThing
)data
[0];
374 final ProjectThing root_pt
= (ProjectThing
)data
[1];
375 final LayerThing root_lt
= (LayerThing
)data
[2];
376 final HashMap ht_pt_expanded
= (HashMap
)data
[3];
378 final Project project
= (Project
)root_pt
.getObject();
379 project
.createLayerTemplates();
380 project
.template_tree
= new TemplateTree(project
, root_tt
);
381 project
.root_tt
= root_tt
;
382 project
.root_pt
= root_pt
;
383 project
.project_tree
= new ProjectTree(project
, project
.root_pt
);
384 project
.layer_tree
= new LayerTree(project
, root_lt
);
385 project
.root_lt
= root_lt
;
386 project
.layer_set
= (LayerSet
)root_lt
.getObject();
388 // if all when well, register as open:
389 al_open_projects
.add(project
);
390 // create the project control window, containing the trees in a double JSplitPane
391 ControlWindow
.add(project
, project
.template_tree
, project
.project_tree
, project
.layer_tree
);
393 // debug: print the entire root project tree
394 //project.root_pt.debug("");
396 // set ProjectThing nodes expanded state, now that the trees exist
398 java
.lang
.reflect
.Field f
= JTree
.class.getDeclaredField("expandedState");
399 f
.setAccessible(true);
400 Hashtable ht_exp
= (Hashtable
)f
.get(project
.project_tree
);
401 for (Iterator it
= ht_pt_expanded
.entrySet().iterator(); it
.hasNext(); ) {
402 Map
.Entry entry
= (Map
.Entry
)it
.next();
403 ProjectThing pt
= (ProjectThing
)entry
.getKey();
404 Boolean expanded
= (Boolean
)entry
.getValue();
405 //project.project_tree.expandPath(new TreePath(project.project_tree.findNode(pt, project.project_tree).getPath()));
406 // WARNING the above is wrong in that it will expand the whole thing, not just set the state of the node!!
407 // 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 DefaultMutableTreeNode nd
= project
.project_tree
.findNode(pt
, project
.project_tree
);
410 //if (null == nd) Utils.log2("null node for " + pt);
411 //else Utils.log2("path: " + new TreePath(nd.getPath()));
413 Utils
.log2("Can't find node for " + pt
);
415 ht_exp
.put(new TreePath(nd
.getPath()), expanded
);
418 project
.project_tree
.updateUILater(); // very important!!
419 } catch (Exception e
) {
422 // open any stored displays
424 final Bureaucrat burro
= Display
.openLater();
426 final Runnable ru
= new Runnable() {
428 // wait until the Bureaucrat finishes
429 try { burro
.join(); } catch (InterruptedException ie
) {}
430 // restore to non-changes (crude, but works)
431 project
.loader
.setChanged(false);
432 Utils
.log2("C set to false");
437 setPriority(Thread
.NORM_PRIORITY
);
438 // avoiding "can't call invokeAndWait from the EventDispatch thread" error
440 javax
.swing
.SwingUtilities
.invokeAndWait(ru
);
441 } catch (Exception e
) {
442 Utils
.log2("ERROR: " + e
);
446 // SO: WAIT TILL THE END OF TIME!
447 new Thread() { public void run() {
449 Thread
.sleep(4000); // ah, the pain in my veins. I can't take this shitty setup anymore.
450 javax
.swing
.SwingUtilities
.invokeAndWait(new Runnable() { public void run() {
451 project
.getLoader().setChanged(false);
452 Utils
.log2("D set to false");
454 project
.getTemplateTree().updateUILater(); // repainting to fix gross errors in tree rendering
455 project
.getProjectTree().updateUILater(); // idem
456 } catch (Exception ie
) {}
459 // help the helpless users
460 Display
.createDisplay(project
, project
.layer_set
.getLayer(0));
466 static private Project
createNewProject(Loader loader
, boolean ask_for_template
) {
467 return createNewProject(loader
, ask_for_template
, null);
470 static private Project
createNewSubProject(Project source
, Loader loader
) {
471 return createNewProject(loader
, false, source
.root_tt
, true);
474 static private Project
createNewProject(Loader loader
, boolean ask_for_template
, TemplateThing template_root
) {
475 return createNewProject(loader
, ask_for_template
, template_root
, false);
478 static private Project
createNewProject(Loader loader
, boolean ask_for_template
, TemplateThing template_root
, boolean clone_ids
) {
479 Project project
= new Project(loader
);
480 // ask for an XML properties file that defines the Thing objects that can be created
481 // (the XML file will be parsed into a TemplateTree filled with TemplateThing objects)
482 //Utils.log2("ask_for_template: " + ask_for_template);
483 if (ask_for_template
) template_root
= project
.loader
.askForXMLTemplate(project
);
484 if (null == template_root
) {
485 template_root
= new TemplateThing("anything");
486 } else if (clone_ids
) {
487 // the given template_root belongs to another project from which we are cloning
488 template_root
= template_root
.clone(project
, true);
489 } // else, use the given template_root as is.
491 project
.template_tree
= new TemplateTree(project
, template_root
);
492 project
.root_tt
= template_root
;
493 // collect unique TemplateThing instances
494 project
.ht_unique_tt
= template_root
.getUniqueTypes(new HashMap
<String
,TemplateThing
>());
495 // add all TemplateThing objects to the database, recursively
496 if (!clone_ids
) template_root
.addToDatabase(project
);
497 // else already done when cloning the root_tt
499 // create a non-database bound template for the project Thing
500 TemplateThing project_template
= new TemplateThing("project");
501 project
.ht_unique_tt
.put("project", project_template
);
502 project_template
.addChild(template_root
);
503 // create the project Thing, to be root of the whole project thing tree
505 project
.root_pt
= new ProjectThing(project_template
, project
, project
);
506 } catch (Exception e
) { IJError
.print(e
); }
507 // create the user objects tree
508 project
.project_tree
= new ProjectTree(project
, project
.root_pt
);
509 // create the layer's tree
510 project
.createLayerTemplates();
511 project
.layer_set
= new LayerSet(project
, "Top Level", 0, 0, null, 2048, 2048); // initialized with default values, and null parent to signal 'root'
513 project
.root_lt
= new LayerThing(project
.layer_set_template
, project
, project
.layer_set
);
514 project
.layer_tree
= new LayerTree(project
, project
.root_lt
);
515 } catch (Exception e
) {
519 // create the project control window, containing the trees in a double JSplitPane
520 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 al_open_projects
.add(project
);
528 public void setTempLoader(Loader loader
) {
529 if (null == this.loader
) {
530 this.loader
= loader
;
532 Utils
.log2("Project.setTempLoader: already have one.");
536 public final Loader
getLoader() {
540 public String
getType() {
544 public String
save() {
545 Thread
.yield(); // let it repaint the log window
546 String path
= loader
.save(this); // TODO: put in a bkgd task, and show a progress bar
550 public String
saveAs(String xml_path
, boolean overwrite
) {
551 return loader
.saveAs(xml_path
, overwrite
);
554 public boolean destroy() {
555 if (loader
.hasChanges() && !getBooleanProperty("no_shutdown_hook")) { // DBLoader always returns false
556 if (ControlWindow
.isGUIEnabled()) {
557 final YesNoDialog yn
= ControlWindow
.makeYesNoDialog("TrakEM2", "There are unsaved changes in project " + title
+ ". Save them?");
558 if (yn
.yesPressed()) {
562 Utils
.log2("WARNING: closing project '" + title
+ "' with unsaved changes.");
565 al_open_projects
.remove(this);
567 if (null != loader
) { // the last project is destroyed twice for some reason, if several are open. This is a PATCH
568 loader
.destroy(); // and disconnect
571 ControlWindow
.remove(this); // AFTER loader.destroy() call.
572 if (null != template_tree
) template_tree
.destroy();
573 if (null != project_tree
) project_tree
.destroy();
574 if (null != layer_tree
) layer_tree
.destroy();
575 Polyline
.flushTraceCache(this);
576 Compare
.removeProject(this);
577 this.template_tree
= null; // flag to mean: we're closing
578 // close all open Displays
583 public boolean isBeingDestroyed() {
584 return null == template_tree
;
587 /** Remove the project from the database and release memory. */
588 public void remove() {
589 removeFromDatabase();
593 /** Remove the project from the database and release memory. */
594 public boolean remove(boolean check
) {
595 if (!Utils
.check("Delete the project " + toString() + " from the database?")) return false;
596 removeFromDatabase();
601 public void setTitle(String title
) {
602 if (null == title
) return;
604 ControlWindow
.updateTitle(this);
605 loader
.updateInDatabase(this, "title");
608 public String
toString() {
609 if (null == title
|| title
.equals("Project")) {
611 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.
612 } catch (Exception e
) { Utils
.log2("Swing again."); }
617 public String
getTitle() {
621 public TemplateTree
getTemplateTree() {
622 return template_tree
;
625 public LayerTree
getLayerTree() {
629 public ProjectTree
getProjectTree() {
633 /** Make an object of the type the TemplateThing can hold. */
634 public Object
makeObject(final TemplateThing tt
) {
635 final String type
= tt
.getType();
636 if (type
.equals("profile")) {
637 ProjectToolbar
.setTool(ProjectToolbar
.PENCIL
); // this should go elsewhere, in display issues.
638 return new Profile(this, "profile", 0, 0);
639 } else if (type
.equals("pipe")) {
640 ProjectToolbar
.setTool(ProjectToolbar
.PEN
);
641 return new Pipe(this, "pipe", 0, 0);
642 } else if (type
.equals("polyline")) {
643 ProjectToolbar
.setTool(ProjectToolbar
.PEN
);
644 return new Polyline(this, "polyline");
645 } else if (type
.equals("area_list")) {
646 ProjectToolbar
.setTool(ProjectToolbar
.PEN
);
647 return new AreaList(this, "area_list", 0, 0);
648 } else if (type
.equals("ball")) {
649 ProjectToolbar
.setTool(ProjectToolbar
.PEN
);
650 return new Ball(this, "ball", 0, 0);
651 } else if (type
.equals("dissector")) {
652 ProjectToolbar
.setTool(ProjectToolbar
.PEN
);
653 return new Dissector(this, "dissector", 0, 0);
654 } else if (type
.equals("label")) {
655 return new DLabel(this, " ", 0, 0); // never used so far
657 // just the name, for the abstract ones
662 /** Returns true if the type is 'patch', 'layer', 'layer_set', 'profile', 'profile_list' 'pipe'. */
663 static public boolean isBasicType(String type
) {
664 if (isProjectType(type
)) return true;
665 if (isLayerType(type
)) return true;
669 static public boolean isProjectType(String type
) {
670 type
= type
.toLowerCase();
671 if (type
.equals("profile_list")) return true;
675 static public boolean isLayerType(String type
) {
676 type
= type
.toLowerCase().replace(' ', '_');
677 if (type
.equals("patch")) return true;
678 if (type
.equals("area_list")) return true;
679 if (type
.equals("label")) return true;
680 if (type
.equals("profile")) return true;
681 if (type
.equals("pipe")) return true;
682 if (type
.equals("polyline")) return true;
683 if (type
.equals("ball")) return true;
684 if (type
.equals("layer")) return true;
685 if (type
.equals("layer set")) return true; // for XML ...
689 /** Remove the ProjectThing that contains the given object, which will remove the object itself as well. */
690 public boolean removeProjectThing(Object object
, boolean check
) {
691 return removeProjectThing(object
, check
, false, 0);
694 /** Remove the ProjectThing that contains the given object, which will remove the object itself as well. */
695 public boolean removeProjectThing(Object object
, boolean check
, boolean remove_empty_parents
, int levels
) {
697 Utils
.log2("Project.removeProjectThing: levels must be zero or above.");
701 DefaultMutableTreeNode root
= (DefaultMutableTreeNode
)project_tree
.getModel().getRoot();
702 Enumeration e
= root
.depthFirstEnumeration();
703 DefaultMutableTreeNode node
= null;
704 while (e
.hasMoreElements()) {
705 node
= (DefaultMutableTreeNode
)e
.nextElement();
706 Object ob
= node
.getUserObject();
707 if (ob
instanceof ProjectThing
&& ((ProjectThing
)ob
).getObject() == object
) {
708 if (check
&& !Utils
.check("Remove " + object
.toString() + "?")) return false;
709 // remove the ProjectThing, its object and the node that holds it.
710 project_tree
.remove(node
, false, remove_empty_parents
, levels
);
712 } // 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.
719 /** Find the node in the layer tree with a Thing that contains the given object, and set it selected/highlighted, deselecting everything else first. */
720 public void select(final Layer layer
) {
721 select(layer
, layer_tree
);
723 /** Find the node in any tree with a Thing that contains the given Displayable, and set it selected/highlighted, deselecting everything else first. */
724 public void select(final Displayable d
) {
725 if (d
.getClass() == LayerSet
.class) select(d
, layer_tree
);
726 else select(d
, project_tree
);
729 private final void select(final Object ob
, final JTree tree
) {
730 // Find the Thing that contains the object
731 final Thing root_thing
= (Thing
)((DefaultMutableTreeNode
)tree
.getModel().getRoot()).getUserObject();
732 final Thing child_thing
= root_thing
.findChild(ob
);
733 // find the node that contains the Thing, and select it
734 DNDTree
.selectNode(child_thing
, tree
);
737 /** Find the ProjectThing instance with the given id. */
738 public ProjectThing
find(final long id
) {
739 // can't be the Project itself
740 return root_pt
.findChild(id
);
743 public DBObject
findById(final long id
) {
744 if (this.id
== id
) return this;
745 DBObject dbo
= layer_set
.findById(id
);
746 if (null != dbo
) return dbo
;
747 dbo
= root_pt
.findChild(id
); // could call findObject(id), but all objects must exist in layer sets anyway.
748 if (null != dbo
) return dbo
;
749 return (DBObject
)root_tt
.findChild(id
);
752 /** Find a LayerThing that contains the given object. */
753 public LayerThing
findLayerThing(final Object ob
) {
754 final Object lob
= root_lt
.findChild(ob
);
755 return null != lob ?
(LayerThing
)lob
: null;
758 /** Find a ProjectThing that contains the given object. */
759 public ProjectThing
findProjectThing(final Object ob
) {
760 final Object pob
= root_pt
.findChild(ob
);
761 return null != pob ?
(ProjectThing
)pob
: null;
764 public ProjectThing
getRootProjectThing() {
768 public LayerSet
getRootLayerSet() {
772 /** Returns the title of the enclosing abstract node in the ProjectTree.*/
773 public String
getParentTitle(final Displayable d
) {
775 ProjectThing thing
= (ProjectThing
)this.root_pt
.findChild(d
);
776 ProjectThing parent
= (ProjectThing
)thing
.getParent();
777 if (d
instanceof Profile
) {
778 parent
= (ProjectThing
)parent
.getParent(); // skip the profile_list
780 if (null == parent
) Utils
.log2("null parent for " + d
);
781 if (null != parent
&& null == parent
.getObject()) {
782 Utils
.log2("null ob for parent " + parent
+ " of " + d
);
784 return parent
.getObject().toString(); // the abstract thing should be enclosing a String object
785 } catch (Exception e
) { IJError
.print(e
); return null; }
788 /** 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. */
789 public String
getMeaningfulTitle(final Displayable d
) {
790 ProjectThing thing
= (ProjectThing
)this.root_pt
.findChild(d
);
791 if (null == thing
) return d
.getTitle(); // happens if there is no associated node
792 String title
= new StringBuffer(!thing
.getType().equals(d
.getTitle()) ? d
.getTitle() + " [" : "[").append(thing
.getType()).append(' ').append('#').append(d
.getId()).append(']').toString();
794 if (!thing
.getType().equals(d
.getTitle())) {
798 ProjectThing parent
= (ProjectThing
)thing
.getParent();
799 StringBuffer sb
= new StringBuffer(title
);
800 while (null != parent
) {
801 Object ob
= parent
.getObject();
802 if (ob
.getClass() == Project
.class) break;
803 String type
= parent
.getType();
804 if (!ob
.equals(type
)) { // meaning, something else was typed in as a title
805 sb
.insert(0, new StringBuffer(ob
.toString()).append(' ').append('[').append(type
).append(']').append('/').toString());
806 //title = ob.toString() + " [" + type + "]/" + title;
811 //title = type + "/" + title;
812 parent
= (ProjectThing
)parent
.getParent();
815 return sb
.toString();
818 /** Returns the first upstream user-defined name and type, and the id of the displayable tagged at the end.
819 * If no user-defined name is found, then the type is prepended to the id.
821 public String
getShortMeaningfulTitle(final Displayable d
) {
822 ProjectThing thing
= (ProjectThing
)this.root_pt
.findChild(d
);
823 if (null == thing
) return d
.getTitle(); // happens if there is no associated node
824 return getShortMeaningfulTitle(thing
, d
);
826 public String
getShortMeaningfulTitle(final ProjectThing thing
, final Displayable d
) {
827 if (thing
.getObject() != d
) {
828 return thing
.toString();
830 ProjectThing parent
= (ProjectThing
)thing
.getParent();
831 String title
= "#" + d
.getId();
832 while (null != parent
) {
833 Object ob
= parent
.getObject();
834 String type
= parent
.getType();
835 if (!ob
.equals(type
)) { // meaning, something else was typed in as a title
836 title
= ob
.toString() + " [" + type
+ "] " + title
;
839 parent
= (ProjectThing
)parent
.getParent();
841 // if nothing found, prepend the type
842 if ('#' == title
.charAt(0)) title
= Project
.getName(d
.getClass()) + " " + title
;
846 static public String
getType(final Class c
) {
847 if (AreaList
.class == c
) return "area_list";
848 if (DLabel
.class == c
) return "label";
849 return c
.getName().toLowerCase();
852 /** Returns the proper TemplateThing for the given type, complete with children and attributes if any. */
853 public TemplateThing
getTemplateThing(String type
) {
854 return ht_unique_tt
.get(type
);
857 /** 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. */
858 public String
[] getUniqueTypes() {
859 // ensure the basic types (pipe, ball, profile, profile_list) are present
860 if (!ht_unique_tt
.containsKey("profile")) ht_unique_tt
.put("profile", new TemplateThing("profile"));
861 if (!ht_unique_tt
.containsKey("profile_list")) {
862 TemplateThing tpl
= new TemplateThing("profile_list");
863 tpl
.addChild((TemplateThing
) ht_unique_tt
.get("profile"));
864 ht_unique_tt
.put("profile_list", tpl
);
866 if (!ht_unique_tt
.containsKey("pipe")) ht_unique_tt
.put("pipe", new TemplateThing("pipe"));
867 if (!ht_unique_tt
.containsKey("polyline")) ht_unique_tt
.put("polyline", new TemplateThing("polyline"));
868 if (!ht_unique_tt
.containsKey("ball")) ht_unique_tt
.put("ball", new TemplateThing("ball"));
869 if (!ht_unique_tt
.containsKey("area_list")) ht_unique_tt
.put("area_list", new TemplateThing("area_list"));
870 if (!ht_unique_tt
.containsKey("dissector")) ht_unique_tt
.put("dissector", new TemplateThing("dissector"));
871 // 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.
873 TemplateThing project_tt
= ht_unique_tt
.remove("project");
875 for (Iterator it = ht_unique_tt.keySet().iterator(); it.hasNext(); ) {
876 Utils.log2("class: " + it.next().getClass().getName());
878 final String
[] ut
= new String
[ht_unique_tt
.size()];
879 ht_unique_tt
.keySet().toArray(ut
);
880 ht_unique_tt
.put("project", project_tt
);
885 /** Remove a unique type from the HashMap. Basic types can't be removed. */
886 public boolean removeUniqueType(String type
) {
887 if (null == type
|| isBasicType(type
)) return false;
888 return null != ht_unique_tt
.remove(type
);
891 public boolean typeExists(String type
) {
892 return ht_unique_tt
.containsKey(type
);
895 /** Returns false if the type exists already. */
896 public boolean addUniqueType(TemplateThing tt
) {
897 if (null == ht_unique_tt
) this.ht_unique_tt
= new HashMap();
898 if (ht_unique_tt
.containsKey(tt
.getType())) return false;
899 ht_unique_tt
.put(tt
.getType(), tt
);
903 public boolean updateTypeName(String old_type
, String new_type
) {
904 if (ht_unique_tt
.containsKey(new_type
)) {
905 Utils
.showMessage("Can't rename type '" + old_type
+ "' : a type named '"+new_type
+"' already exists!");
908 ht_unique_tt
.put(new_type
, ht_unique_tt
.remove(old_type
));
912 private void createLayerTemplates() {
913 if (null == layer_template
) {
914 layer_template
= new TemplateThing("layer");
915 layer_set_template
= new TemplateThing("layer_set");
916 layer_set_template
.addChild(layer_template
);
917 layer_template
.addChild(layer_set_template
); // adding a new instance to keep parent/child relationships clean
918 // No need, there won't ever be a loop so far WARNING may change in the future.
922 /** 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. */
923 public void exportXML(final java
.io
.Writer writer
, String indent
, Object any
) throws Exception
{
924 StringBuffer sb_body
= new StringBuffer();
926 sb_body
.append(indent
).append("<trakem2>\n");
927 String in
= indent
+ "\t";
928 // 2 - the project itself
929 sb_body
.append(in
).append("<project \n")
930 .append(in
).append("\tid=\"").append(id
).append("\"\n")
931 .append(in
).append("\ttitle=\"").append(title
).append("\"\n");
932 loader
.insertXMLOptions(sb_body
, in
+ "\t");
933 for (Iterator it
= ht_props
.entrySet().iterator(); it
.hasNext(); ) {
934 Map
.Entry prop
= (Map
.Entry
)it
.next();
935 sb_body
.append(in
).append('\t').append((String
)prop
.getKey()).append("=\"").append((String
)prop
.getValue()).append("\"\n");
937 sb_body
.append(in
).append(">\n");
938 // 3 - export ProjectTree abstract hierachy (skip the root since it wraps the project itself)
939 if (null != root_pt
.getChildren()) {
940 String in2
= in
+ "\t";
941 for (Iterator it
= root_pt
.getChildren().iterator(); it
.hasNext(); ) {
942 ((ProjectThing
)it
.next()).exportXML(sb_body
, in2
, any
);
945 sb_body
.append(in
).append("</project>\n");
946 writer
.write(sb_body
.toString());
947 sb_body
.setLength(0);
949 // 4 - export LayerSet hierarchy of Layer, LayerSet and Displayable objects
950 layer_set
.exportXML(writer
, in
, any
);
951 // 5 - export Display objects
952 Display
.exportXML(this, writer
, in
, any
);
954 writer
.write("</trakem2>\n");
957 /** Export a complete DTD listing to export the project as XML. */
958 public void exportDTD(StringBuffer sb_header
, HashSet hs
, String indent
) {
959 // 1 - TrakEM2 tag that encloses all hierarchies
960 sb_header
.append(indent
).append("<!ELEMENT ").append("trakem2 (project,t2_layer_set,t2_display)>\n");
961 // 2 - export user-defined templates
962 //TemplateThing root_tt = (TemplateThing)((DefaultMutableTreeNode)((DefaultTreeModel)template_tree.getModel()).getRoot()).getUserObject();
963 sb_header
.append(indent
).append("<!ELEMENT ").append("project (").append(root_tt
.getType()).append(")>\n");
964 sb_header
.append(indent
).append("<!ATTLIST project id NMTOKEN #REQUIRED>\n");
965 sb_header
.append(indent
).append("<!ATTLIST project unuid NMTOKEN #REQUIRED>\n");
966 sb_header
.append(indent
).append("<!ATTLIST project title NMTOKEN #REQUIRED>\n");
967 sb_header
.append(indent
).append("<!ATTLIST project preprocessor NMTOKEN #REQUIRED>\n");
968 sb_header
.append(indent
).append("<!ATTLIST project mipmaps_folder NMTOKEN #REQUIRED>\n");
969 sb_header
.append(indent
).append("<!ATTLIST project storage_folder NMTOKEN #REQUIRED>\n");
970 for (Iterator it
= ht_props
.entrySet().iterator(); it
.hasNext(); ) {
971 Map
.Entry prop
= (Map
.Entry
)it
.next();
972 sb_header
.append(indent
).append("<!ATTLIST project ").append((String
)prop
.getKey()).append(" NMTOKEN #REQUIRED>\n");
974 root_tt
.exportDTD(sb_header
, hs
, indent
);
975 // 3 - export all project objects DTD in the Top Level LayerSet
976 Layer
.exportDTD(sb_header
, hs
, indent
);
977 LayerSet
.exportDTD(sb_header
, hs
, indent
);
978 Ball
.exportDTD(sb_header
, hs
, indent
);
979 DLabel
.exportDTD(sb_header
, hs
, indent
);
980 Patch
.exportDTD(sb_header
, hs
, indent
);
981 Pipe
.exportDTD(sb_header
, hs
, indent
);
982 Polyline
.exportDTD(sb_header
, hs
, indent
);
983 Profile
.exportDTD(sb_header
, hs
, indent
);
984 AreaList
.exportDTD(sb_header
, hs
, indent
);
985 Dissector
.exportDTD(sb_header
, hs
, indent
);
986 Displayable
.exportDTD(sb_header
, hs
, indent
); // the subtypes of all Displayable types
987 // 4 - export Display
988 Display
.exportDTD(sb_header
, hs
, indent
);
989 // all the above could be done with reflection, automatically detecting the presence of an exportDTD method.
992 /** Returns the String to be used as Document Type of the XML file, generated from the name of the root template thing.*/
993 public String
getDocType() {
994 //TemplateThing root_tt = (TemplateThing)((DefaultMutableTreeNode)((DefaultTreeModel)template_tree.getModel()).getRoot()).getUserObject();
995 return "trakem2_" + root_tt
.getType();
998 /** Find an instance containing the given tree. */
999 static public Project
getInstance(final DNDTree ob
) {
1000 for (Iterator it
= al_open_projects
.iterator(); it
.hasNext(); ) {
1001 Project project
= (Project
)it
.next();
1002 if (project
.layer_tree
.equals(ob
)) return project
;
1003 if (project
.project_tree
.equals(ob
)) return project
;
1004 if (project
.template_tree
.equals(ob
)) return project
;
1009 /** Returns a user-understandable name for the given class. */
1010 static public String
getName(final Class c
) {
1011 String name
= c
.getName();
1012 name
= name
.substring(name
.lastIndexOf('.') + 1);
1013 if (name
.equals("DLabel")) return "Label";
1014 else if (name
.equals("Patch")) return "Image";
1015 //else if (name.equals("Pipe")) return "Tube";
1016 //else if (name.equals("Ball")) return "Sphere group"; // TODO revise consistency with XML templates and so on
1020 public String
getInfo() {
1021 StringBuffer sb
= new StringBuffer("Project id: ");
1022 sb
.append(this.id
).append("\nProject name: ").append(this.title
)
1023 .append("\nTrees:\n")
1024 .append(project_tree
.getInfo()).append("\n")
1025 .append(layer_tree
.getInfo())
1027 return sb
.toString();
1030 static public Project
findProject(Loader loader
) {
1031 for (Iterator it
= al_open_projects
.iterator(); it
.hasNext(); ) {
1032 Project pro
= (Project
)it
.next();
1033 if (pro
.getLoader() == loader
) return pro
;
1038 private boolean input_disabled
= false;
1040 /** Tells the displays concerning this Project to accept/reject input. */
1041 public void setReceivesInput(boolean b
) {
1042 this.input_disabled
= !b
;
1043 Display
.setReceivesInput(this, b
);
1046 public boolean isInputEnabled() {
1047 return !input_disabled
;
1050 /** Create a new subproject for the given layer range and ROI.
1051 * 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. */
1052 public Project
createSubproject(final Rectangle roi
, final Layer first
, final Layer last
) {
1054 // The order matters.
1055 final Project pr
= new Project(new FSLoader(this.getLoader()));
1058 pr
.title
= this.title
;
1059 pr
.ht_props
.putAll(this.ht_props
);
1061 pr
.root_tt
= this.root_tt
.clone(pr
, true);
1062 pr
.template_tree
= new TemplateTree(pr
, pr
.root_tt
);
1063 pr
.ht_unique_tt
= root_tt
.getUniqueTypes(new HashMap());
1064 TemplateThing project_template
= new TemplateThing("project");
1065 project_template
.addChild(pr
.root_tt
);
1066 pr
.ht_unique_tt
.put("project", project_template
);
1067 // create the layers templates
1068 pr
.createLayerTemplates();
1069 // copy LayerSet and all involved Displayable objects
1070 pr
.layer_set
= (LayerSet
)this.layer_set
.clone(pr
, first
, last
, roi
, false, true);
1071 // create layer tree
1072 pr
.root_lt
= new LayerThing(pr
.layer_set_template
, pr
, pr
.layer_set
);
1073 pr
.layer_tree
= new LayerTree(pr
, pr
.root_lt
);
1074 // add layer nodes to the layer tree (solving chicken-and-egg problem)
1075 pr
.layer_set
.updateLayerTree();
1076 // copy project tree
1077 pr
.root_pt
= this.root_pt
.subclone(pr
);
1078 pr
.project_tree
= new ProjectTree(pr
, pr
.root_pt
);
1079 // not copying node expanded state.
1081 al_open_projects
.add(pr
);
1083 ControlWindow
.add(pr
, pr
.template_tree
, pr
.project_tree
, pr
.layer_tree
);
1085 // Above, the id of each object is preserved from this project into the subproject.
1087 // The abstract structure should be copied in full regardless, without the basic objects
1088 // included if they intersect the roi.
1092 } catch (Exception e
) { e
.printStackTrace(); }
1096 public void parseXMLOptions(final HashMap ht_attributes
) {
1097 ((FSLoader
)this.project
.getLoader()).parseXMLOptions(ht_attributes
);
1098 // all keys that remain are properties
1099 ht_props
.putAll(ht_attributes
);
1100 for (Iterator it
= ht_props
.entrySet().iterator(); it
.hasNext(); ) {
1101 Map
.Entry prop
= (Map
.Entry
)it
.next();
1102 Utils
.log2("parsed: " + prop
.getKey() + "=" + prop
.getValue());
1105 public HashMap
<String
,String
> getPropertiesCopy() {
1106 return (HashMap
<String
,String
>)ht_props
.clone();
1108 /** Returns null if not defined. */
1109 public String
getProperty(final String key
) {
1110 return ht_props
.get(key
);
1112 /** Returns the default value if not defined, or if not a number or not parsable as a number. */
1113 public float getProperty(final String key
, final float default_value
) {
1115 final String s
= ht_props
.get(key
);
1116 if (null == s
) return default_value
;
1117 final float num
= Float
.parseFloat(s
);
1118 if (Float
.isNaN(num
)) return default_value
;
1120 } catch (NumberFormatException nfe
) {
1123 return default_value
;
1125 public boolean getBooleanProperty(final String key
) {
1126 return "true".equals(ht_props
.get(key
));
1128 public void setProperty(final String key
, final String value
) {
1129 if (null == value
) ht_props
.remove(key
);
1130 else ht_props
.put(key
, value
);
1132 private final boolean addBox(final GenericDialog gd
, final Class c
) {
1133 final String name
= Project
.getName(c
);
1134 final boolean link
= "true".equals(ht_props
.get(name
.toLowerCase() + "_nolinks"));
1135 gd
.addCheckbox(name
, link
);
1138 private final void setLinkProp(final boolean before
, final boolean after
, final Class c
) {
1140 if (!after
) ht_props
.remove(Project
.getName(c
).toLowerCase()+"_nolinks");
1142 ht_props
.put(Project
.getName(c
).toLowerCase()+"_nolinks", "true");
1144 // setting to false would have no meaning, so the link prop is removed
1146 /** Returns true if there were any changes. */
1147 private final boolean adjustProp(final String prop
, final boolean before
, final boolean after
) {
1149 if (!after
) ht_props
.remove(prop
);
1151 ht_props
.put(prop
, "true");
1153 return before
!= after
;
1155 public void adjustProperties() {
1156 // should be more generic, but for now it'll do
1157 GenericDialog gd
= new GenericDialog("Properties");
1158 gd
.addMessage("Ignore image linking for:");
1159 boolean link_labels
= addBox(gd
, DLabel
.class);
1160 boolean link_arealist
= addBox(gd
, AreaList
.class);
1161 boolean link_pipes
= addBox(gd
, Pipe
.class);
1162 boolean link_polylines
= addBox(gd
, Polyline
.class);
1163 boolean link_balls
= addBox(gd
, Ball
.class);
1164 boolean link_dissectors
= addBox(gd
, Dissector
.class);
1165 boolean dissector_zoom
= "true".equals(ht_props
.get("dissector_zoom"));
1166 gd
.addCheckbox("Zoom-invariant markers for Dissector", dissector_zoom
);
1167 boolean no_color_cues
= "true".equals(ht_props
.get("no_color_cues"));
1168 gd
.addCheckbox("Paint_color_cues", !no_color_cues
);
1169 gd
.addMessage("Currently linked objects\nwill remain so unless\nexplicitly unlinked.");
1170 String current_mode
= ht_props
.get("image_resizing_mode");
1171 // Forbid area averaging: doesn't work, and it's not faster than gaussian.
1172 if (Utils
.indexOf(current_mode
, Loader
.modes
) >= Loader
.modes
.length
) current_mode
= Loader
.modes
[3]; // GAUSSIAN
1173 gd
.addChoice("Image_resizing_mode: ", Loader
.modes
, null == current_mode ? Loader
.modes
[3] : current_mode
);
1174 int current_R
= (int)(100 * ini
.trakem2
.imaging
.StitchingTEM
.DEFAULT_MIN_R
); // make the float a percent
1176 String scR
= ht_props
.get("min_R");
1177 if (null != scR
) current_R
= (int)(Double
.parseDouble(scR
) * 100);
1178 } catch (Exception nfe
) {
1181 gd
.addSlider("min_R: ", 0, 100, current_R
);
1183 boolean layer_mipmaps
= "true".equals(ht_props
.get("layer_mipmaps"));
1184 gd
.addCheckbox("Layer_mipmaps", layer_mipmaps
);
1185 boolean keep_mipmaps
= "true".equals(ht_props
.get("keep_mipmaps"));
1186 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.
1187 int bucket_side
= (int)getProperty("bucket_side", Bucket
.MIN_BUCKET_SIZE
);
1188 gd
.addNumericField("Bucket side length: ", bucket_side
, 0);
1189 boolean no_shutdown_hook
= "true".equals(ht_props
.get("no_shutdown_hook"));
1190 gd
.addCheckbox("No_shutdown_hook to save the project", no_shutdown_hook
);
1194 if (gd
.wasCanceled()) return;
1195 setLinkProp(link_labels
, gd
.getNextBoolean(), DLabel
.class);
1196 setLinkProp(link_arealist
, gd
.getNextBoolean(), AreaList
.class);
1197 setLinkProp(link_pipes
, gd
.getNextBoolean(), Pipe
.class);
1198 setLinkProp(link_polylines
, gd
.getNextBoolean(), Polyline
.class);
1199 setLinkProp(link_balls
, gd
.getNextBoolean(), Ball
.class);
1200 setLinkProp(link_dissectors
, gd
.getNextBoolean(), Dissector
.class);
1201 if (adjustProp("dissector_zoom", dissector_zoom
, gd
.getNextBoolean())) {
1202 Display
.repaint(layer_set
); // TODO: should repaint nested LayerSets as well
1204 if (adjustProp("no_color_cues", no_color_cues
, !gd
.getNextBoolean())) {
1205 Display
.repaint(layer_set
);
1207 setProperty("image_resizing_mode", Loader
.modes
[gd
.getNextChoiceIndex()]);
1208 setProperty("min_R", new Float((float)gd
.getNextNumber() / 100).toString());
1209 boolean layer_mipmaps2
= gd
.getNextBoolean();
1210 if (adjustProp("layer_mipmaps", layer_mipmaps
, layer_mipmaps2
)) {
1211 if (layer_mipmaps
&& !layer_mipmaps2
) {
1214 // 2 - remove all existing images from layer.mipmaps folder
1215 } else if (!layer_mipmaps
&& layer_mipmaps2
) {
1218 // 2 - create de novo all layer mipmaps in a background task
1221 adjustProp("keep_mipmaps", keep_mipmaps
, gd
.getNextBoolean());
1222 Utils
.log2("keep_mipmaps: " + getBooleanProperty("keep_mipmaps"));
1224 bucket_side
= (int)gd
.getNextNumber();
1225 if (bucket_side
> Bucket
.MIN_BUCKET_SIZE
) {
1226 setProperty("bucket_side", Integer
.toString(bucket_side
));
1227 layer_set
.recreateBuckets(true);
1229 adjustProp("no_shutdown_hook", no_shutdown_hook
, gd
.getNextBoolean());
1232 /** Return the Universal Near-Unique Id of this project, which may be null for non-FSLoader projects. */
1233 public String
getUNUId() {
1234 return loader
.getUNUId();