From b3ad53f493838261118cdfce29b493744bb42289 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Matej=20Kov=C3=A1r?= <xkovar4@fi.muni.cz> Date: Fri, 21 Jan 2022 17:32:24 +0100 Subject: [PATCH] Resolve "Add basic JSON scheme for project" --- .../java/cz/fidentis/analyst/Project.java | 132 +++- .../analyst/ProjectConfiguration.java | 147 ++++ GUI/pom.xml | 13 +- .../cz/fidentis/analyst/canvas/Canvas.java | 2 +- .../cz/fidentis/analyst/core/FaceTab.java | 197 ++++++ .../fidentis/analyst/core/FaceToFaceTab.java | 102 --- .../fidentis/analyst/core/ManyToManyTab.java | 85 --- .../fidentis/analyst/core/ProjectTopComp.form | 150 +++- .../fidentis/analyst/core/ProjectTopComp.java | 668 ++++++++++++++---- .../fidentis/analyst/core/SingleFaceTab.java | 100 --- .../analyst/dashboard/FaceStatePanel.java | 8 +- .../analyst/dashboard/ModelsTableModel.java | 2 +- .../fidentis/analyst/core/Bundle.properties | 24 +- GUI/src/main/resources/new.png | Bin 0 -> 3149 bytes GUI/src/main/resources/new100x24.png | Bin 0 -> 1345 bytes GUI/src/main/resources/open.png | Bin 0 -> 3700 bytes GUI/src/main/resources/open100x24.png | Bin 0 -> 1538 bytes GUI/src/main/resources/save100x24.png | Bin 0 -> 1479 bytes 18 files changed, 1145 insertions(+), 485 deletions(-) create mode 100644 Comparison/src/main/java/cz/fidentis/analyst/ProjectConfiguration.java create mode 100644 GUI/src/main/java/cz/fidentis/analyst/core/FaceTab.java delete mode 100644 GUI/src/main/java/cz/fidentis/analyst/core/FaceToFaceTab.java delete mode 100644 GUI/src/main/java/cz/fidentis/analyst/core/ManyToManyTab.java delete mode 100644 GUI/src/main/java/cz/fidentis/analyst/core/SingleFaceTab.java create mode 100644 GUI/src/main/resources/new.png create mode 100644 GUI/src/main/resources/new100x24.png create mode 100644 GUI/src/main/resources/open.png create mode 100644 GUI/src/main/resources/open100x24.png create mode 100644 GUI/src/main/resources/save100x24.png diff --git a/Comparison/src/main/java/cz/fidentis/analyst/Project.java b/Comparison/src/main/java/cz/fidentis/analyst/Project.java index 7f9d8810..1a006fe9 100644 --- a/Comparison/src/main/java/cz/fidentis/analyst/Project.java +++ b/Comparison/src/main/java/cz/fidentis/analyst/Project.java @@ -1,6 +1,9 @@ package cz.fidentis.analyst; import cz.fidentis.analyst.face.HumanFace; +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -14,8 +17,85 @@ import java.util.List; */ public class Project { + private boolean saved = true; + private List<HumanFace> faces = new ArrayList<>(); + /* Project data (paths to faces, opened tabs..) */ + private ProjectConfiguration cfg = new ProjectConfiguration(); + + /** + * Asks whether project is saved or some change was made + * @return true if project is saved, false otherwise + */ + public boolean isSaved() { + return saved; + } + + public void setSaved(boolean saved) { + this.saved = saved; + } + + public ProjectConfiguration getCfg() { + return cfg; + } + + public void setCfg(ProjectConfiguration cfg) { + this.cfg = cfg; + } + + /** + * Adds new path to project configuration + * @param path Path to be added + * @return true if path was successfully added + */ + public boolean addNewPath(Path path) { + return this.cfg.addPath(path); + } + + /** + * Checks whether path is already loaded in project configuration + * @param path Path to be checked + * @return true if path was loaded, false otherwise + */ + public boolean pathLoaded(Path path) { + return this.cfg.getPaths().contains(path); + } + + /** + * Adds new FaceToFace tab to project configuration + * @param name1 String name of first face + * @param name2 String name of second face + */ + public void addNewFaceToFaceTabFace(String name1, String name2) { + this.cfg.addFaceToFaceTabFace(name1, name2); + } + + /** + * Adds new face to FaceTab + * @param name String name of face + */ + public void addNewSingleFaceTabFace(String name) { + this.cfg.addSingleFaceTabFace(name); + } + + /** + * Removes FaceTab + * @param name String name of face + */ + public void removeFaceTab(String name) { + this.cfg.removeFaceTab(name); + } + + /** + * Removes FaceToFace tab + * @param name1 String name of first face + * @param name2 String name of second face + */ + public void removeFaceToFaceTab(String name1, String name2) { + this.cfg.removeFaceToFaceTab(name1, name2); + } + /** * Returns list of HumanFace secondary faces * @@ -73,6 +153,29 @@ public class Project { } } + /** + * Removes face by providing its name + * + * @param name name of the face to be removed + */ + public void removeFaceByName(String name) { + HumanFace face = this.getFaceByName(name); + if (face != null) { + this.removeFace(face); + } + + this.cfg.removePath(name); + //this.cfg.removeFaceTab(name); + + } + + /** + * Removes all faces from list of faces + */ + public void removeAll() { + faces.clear(); + } + /** * Removes faces which are sent to this function by list of HumanFace * from faces @@ -101,5 +204,32 @@ public class Project { } return null; } - + + /** + * Loads face from path + * @param name String name of face + * @return loaded HumanFace + */ + public HumanFace loadFace(String name) { + //File[] files = new File[getCfg().getPaths().size()]; + //files = getCfg().openFiles().toArray(files); + HumanFace face = this.getFaceByName(name); + //File file = new File(path); + //for () + //Path path = this.getCfg().pathToFaceByName(name); + if (face == null) { + try { + Logger out = Logger.measureTime(); + Path path = this.getCfg().getPathToFaceByName(name); + File file = path.toFile(); + face = new HumanFace(file, true); // loads also landmarks, if exist + out.printDuration("Loaded model " + face.getShortName() +" with " + face.getMeshModel().getNumVertices() + " vertices"); + } catch (IOException ex) { + //ex.printStackTrace(); + Logger.print(ex.toString()); + } + } + return face; + } + } diff --git a/Comparison/src/main/java/cz/fidentis/analyst/ProjectConfiguration.java b/Comparison/src/main/java/cz/fidentis/analyst/ProjectConfiguration.java new file mode 100644 index 00000000..fb66bec4 --- /dev/null +++ b/Comparison/src/main/java/cz/fidentis/analyst/ProjectConfiguration.java @@ -0,0 +1,147 @@ +package cz.fidentis.analyst; + +import java.io.File; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * This class encapsulates data important for project (re)storing such as paths + * to faces or which tabs were opened + * @author Matej Kovar + */ +public class ProjectConfiguration { + + /* Paths to loaded models */ + private List<Path> paths = new ArrayList<>(); + + private List<String> singleTabFaces = new ArrayList<>(); + + // f.e. [face1 : [face2, face3], face2 : [face4, face5, face6], face3 : [face4]...] + private Map<String, List<String>> faceToFaceTabFaces = new HashMap<>(); + + //private Map<HumanFace, SingleFaceTab> singleFaceTabs = new HashMap<>(); + //private Map<HumanFace, FaceToFaceTab> faceToFaceTabs = new HashMap<>(); + + + public List<Path> getPaths() { + return paths; + } + + public void setPaths(List<Path> paths) { + this.paths = paths; + } + + /** + * Adds path to paths + * @param path Path to be added + * @return true if path was successfully added + */ + public boolean addPath(Path path) { + return paths.add(path); + } + + /** + * Removes specific path from paths + * @param name String of face + */ + public void removePath(String name) { + paths.removeIf(p -> p.toString().substring(p.toString().lastIndexOf(File.separatorChar) + 1, p.toString().lastIndexOf('.')).equals(name)); + } + + /** + * Returns path to face with specified name of file + * @param name String name of file + * @return Path to face + */ + public Path getPathToFaceByName(String name) { + for (Path p : paths) { + if (p.toString().substring(p.toString().lastIndexOf(File.separatorChar) + 1, p.toString().lastIndexOf('.')).equals(name)) { + return p; + } + } + return null; + } + + /** + * Opens all files in paths + * @return List<File> list of files with faces + */ + public List<File> openFiles() { + List<File> f = new ArrayList<>(); + + paths.forEach(p -> { + f.add(p.toFile()); + }); + + return f; + } + + /** + * Removes all paths + */ + public void clearPaths() { + paths.clear(); + } + + public List<String> getSingleTabFaces() { + return singleTabFaces; + } + + public void setSingleTabFaces(List<String> singleTabFaces) { + this.singleTabFaces = singleTabFaces; + } + + public Map<String, List<String>> getFaceToFaceTabFaces() { + return faceToFaceTabFaces; + } + + public void setFaceToFaceTabFaces(Map<String, List<String>> faceToFaceTabFaces) { + this.faceToFaceTabFaces = faceToFaceTabFaces; + } + + /** + * Adds SingleFace tab + * @param name String name of face + */ + public void addSingleFaceTabFace(String name) { + singleTabFaces.add(name); + } + + /** + * Adds FaceToFace tab + * @param name1 String name of first face + * @param name2 String name of second face + */ + public void addFaceToFaceTabFace(String name1, String name2) { + if (faceToFaceTabFaces.containsKey(name1)) { + faceToFaceTabFaces.get(name1).add(name2); + } else { + List<String> faces = new ArrayList<>(); + faces.add(name2); + faceToFaceTabFaces.put(name1, faces); + } + } + + /** + * Removes SingleFace tab + * @param name String name of face + */ + public void removeFaceTab(String name) { + singleTabFaces.remove(name); + } + + /** + * Removes FaceToFace tab + * @param name1 String name of first face + * @param name2 String name of second face + */ + public void removeFaceToFaceTab(String name1, String name2) { + faceToFaceTabFaces.get(name1).remove(name2); + if (faceToFaceTabFaces.get(name1).isEmpty()) { + faceToFaceTabFaces.remove(name1); + } + } +} diff --git a/GUI/pom.xml b/GUI/pom.xml index 3cdd34b4..5caa94cf 100644 --- a/GUI/pom.xml +++ b/GUI/pom.xml @@ -137,7 +137,18 @@ <artifactId>AbsoluteLayout</artifactId> <version>RELEASE123</version> </dependency> - + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-databind</artifactId> + <version>2.13.0</version> + <type>jar</type> + </dependency> + <dependency> + <groupId>com.fasterxml.jackson.core</groupId> + <artifactId>jackson-core</artifactId> + <version>2.13.0</version> + <type>jar</type> + </dependency> </dependencies> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> diff --git a/GUI/src/main/java/cz/fidentis/analyst/canvas/Canvas.java b/GUI/src/main/java/cz/fidentis/analyst/canvas/Canvas.java index 2cd001b0..c0be1ee3 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/canvas/Canvas.java +++ b/GUI/src/main/java/cz/fidentis/analyst/canvas/Canvas.java @@ -52,7 +52,7 @@ public class Canvas extends JPanel { private final Scene scene = new Scene(); private final SceneRenderer sceneRenderer; private final Camera camera = new Camera(); - private final List<HumanFace> faces = new ArrayList(); + private final List<HumanFace> faces = new ArrayList<>(); // Listeners: private final CanvasListener listener; diff --git a/GUI/src/main/java/cz/fidentis/analyst/core/FaceTab.java b/GUI/src/main/java/cz/fidentis/analyst/core/FaceTab.java new file mode 100644 index 00000000..edbfc4a0 --- /dev/null +++ b/GUI/src/main/java/cz/fidentis/analyst/core/FaceTab.java @@ -0,0 +1,197 @@ +package cz.fidentis.analyst.core; + +import cz.fidentis.analyst.batch.BatchAction; +import cz.fidentis.analyst.canvas.Canvas; +import cz.fidentis.analyst.canvas.toolbar.SceneToolboxFaceToFace; +import cz.fidentis.analyst.canvas.toolbar.SceneToolboxSingleFace; +import cz.fidentis.analyst.curvature.CurvatureAction; +import cz.fidentis.analyst.distance.DistanceAction; +import cz.fidentis.analyst.face.HumanFace; +import cz.fidentis.analyst.registration.RegistrationAction; +import cz.fidentis.analyst.symmetry.ProfilesAction; +import cz.fidentis.analyst.symmetry.SymmetryAction; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; +import java.awt.event.ComponentAdapter; +import java.awt.event.ComponentEvent; +import java.util.Objects; +import javax.swing.GroupLayout; +import javax.swing.JScrollPane; +import javax.swing.LayoutStyle; +import org.openide.windows.TopComponent; + +/** + * The non-singleton window/tab for the analysis of one, two or many to many faces + * + * @author Matej Kovar + */ +public class FaceTab extends TopComponent { + + private final Canvas canvas ; + private final TopControlPanel controlPanel; + private final JScrollPane scrollPane; + + private String nameOfFace1 = null; + private String nameOfFace2 = null; + private ActionListener listener = null; + + /** + * Constructor. + * @param primary Primary face + * @param secondary Secondary face + * @param name Tab name + * @param listener action listener + */ + public FaceTab(HumanFace primary, HumanFace secondary, String name, ActionListener listener) { + canvas = new Canvas(); + this.listener = listener; + + if (primary == null) { // N:N + canvas.addToolBox(new SceneToolboxSingleFace(canvas)); + + } else { + nameOfFace1 = primary.getShortName(); + + canvas.addPrimaryFace(primary); + + if (secondary == null) { // single face analysis + canvas.getScene().setDefaultColors(); + canvas.addToolBox(new SceneToolboxSingleFace(canvas)); + nameOfFace2 = null; + } else { // 1:1 + canvas.addSecondaryFace(secondary); + canvas.getScene().setDefaultColors(); + canvas.addToolBox(new SceneToolboxFaceToFace(canvas)); + nameOfFace2 = secondary.getShortName(); + } + } + + controlPanel = new TopControlPanel(); + scrollPane = new JScrollPane(controlPanel); + + setName(name); + initComponents(); + + // change the height so that it corresponds to the height of the OpenGL window + canvas.addComponentListener(new ComponentAdapter() { + @Override + public void componentResized(ComponentEvent e) { + scrollPane.setSize(ControlPanel.CONTROL_PANEL_WIDTH, canvas.getHeight()); + } + }); + + controlPanel.addChangeListener(e -> getCanvas().renderScene()); + + if (primary == null) { // N:N + new BatchAction(canvas, controlPanel); + } else { + if (secondary == null) { // single face analysis + new CurvatureAction(getCanvas(), controlPanel); + new SymmetryAction(getCanvas(), controlPanel); + new ProfilesAction(getCanvas(), controlPanel); + } else { // 1:1 + new RegistrationAction(canvas, controlPanel); + new DistanceAction(canvas, controlPanel); + new SymmetryAction(canvas, controlPanel); + new ProfilesAction(canvas, controlPanel); + } + } + + + } + + @Override + public int getPersistenceType() { + return TopComponent.PERSISTENCE_NEVER; // TO DO: change to .PERSISTENCE_ONLY_OPENED when we can re-create the ProjectTC + } + + private void initComponents() { + GroupLayout layout = new GroupLayout(this); + this.setLayout(layout); + layout.setHorizontalGroup( + layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) + .addComponent(canvas, GroupLayout.DEFAULT_SIZE, 651, Short.MAX_VALUE) + .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) +// .addComponent(renderingToolBar, GroupLayout.PREFERRED_SIZE, RenderingToolBar.WIDTH, GroupLayout.PREFERRED_SIZE) + .addComponent( + scrollPane, + ControlPanel.CONTROL_PANEL_WIDTH, + ControlPanel.CONTROL_PANEL_WIDTH, + ControlPanel.CONTROL_PANEL_WIDTH + ) + ) + ); + layout.setVerticalGroup( + layout.createParallelGroup(GroupLayout.Alignment.LEADING) + .addGroup(layout.createSequentialGroup() + .addGroup(layout.createBaselineGroup(true, true) + .addComponent(canvas) +// .addComponent(renderingToolBar) + .addComponent(scrollPane) + )) + ); + } + + public String getNameOfFace1() { + return nameOfFace1; + } + + public String getNameOfFace2() { + return nameOfFace2; + } + + /** + * Checks whether this tab contains name of face + * @param name String name of face + * @return true if face with this name is in this tab + */ + public boolean hasFace(String name) { + return (name.equals(nameOfFace1) || name.equals(nameOfFace2)); + } + + public Canvas getCanvas() { + return canvas; + } + + + @Override + public boolean canClose() { + if (nameOfFace1 != null && nameOfFace2 == null) { + listener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "FaceTab")); + } else if (nameOfFace1 != null && nameOfFace2 != null) { + listener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "FaceToFaceTab")); + } else { + listener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "ManyToManyTab")); + } + return super.canClose(); //To change body of generated methods, choose Tools | Templates. + } + + + @Override + public int hashCode() { + int hash = 5; + return hash; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + final FaceTab other = (FaceTab) obj; + if (!Objects.equals(this.nameOfFace1, other.nameOfFace1)) { + return false; + } + return Objects.equals(this.nameOfFace2, other.nameOfFace2); + } + + +} \ No newline at end of file diff --git a/GUI/src/main/java/cz/fidentis/analyst/core/FaceToFaceTab.java b/GUI/src/main/java/cz/fidentis/analyst/core/FaceToFaceTab.java deleted file mode 100644 index ee502c41..00000000 --- a/GUI/src/main/java/cz/fidentis/analyst/core/FaceToFaceTab.java +++ /dev/null @@ -1,102 +0,0 @@ -package cz.fidentis.analyst.core; - -import cz.fidentis.analyst.canvas.Canvas; -import cz.fidentis.analyst.canvas.toolbar.SceneToolboxFaceToFace; -import cz.fidentis.analyst.distance.DistanceAction; -import cz.fidentis.analyst.face.HumanFace; -import cz.fidentis.analyst.registration.RegistrationAction; -import cz.fidentis.analyst.symmetry.ProfilesAction; -import cz.fidentis.analyst.symmetry.SymmetryAction; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; -import javax.swing.GroupLayout; -import javax.swing.JScrollPane; -import javax.swing.LayoutStyle; -import org.openide.windows.TopComponent; - -/** - * A non-singleton window/tab for the analysis of two faces. - * - * @author Radek Oslejsek - */ -public class FaceToFaceTab extends TopComponent { - - private final Canvas canvas ; - private final TopControlPanel controlPanel; - private final JScrollPane scrollPane; - - /** - * Constructor. - * @param primary Primary face - * @param secondary Secondary face - * @param name Tab name - */ - public FaceToFaceTab(HumanFace primary, HumanFace secondary, String name) { - canvas = new Canvas(); - canvas.addPrimaryFace(primary); - canvas.addSecondaryFace(secondary); - canvas.getScene().setDefaultColors(); - canvas.addToolBox(new SceneToolboxFaceToFace(canvas)); - controlPanel = new TopControlPanel(); - - scrollPane = new JScrollPane(controlPanel); - - setName(name); - initComponents(); - - // change the height so that it corresponds to the height of the OpenGL window - canvas.addComponentListener(new ComponentAdapter() { - @Override - public void componentResized(ComponentEvent e) { - scrollPane.setSize(ControlPanel.CONTROL_PANEL_WIDTH, canvas.getHeight()); - } - }); - - // (Re)render scene after all change listeners have been called - // (the first added listener is called last) - controlPanel.addChangeListener(e -> getCanvas().renderScene()); - new RegistrationAction(canvas, controlPanel); - new DistanceAction(canvas, controlPanel); - new SymmetryAction(canvas, controlPanel); - new ProfilesAction(canvas, controlPanel); - } - - @Override - public int getPersistenceType() { - return TopComponent.PERSISTENCE_NEVER; // TO DO: change to .PERSISTENCE_ONLY_OPENED when we can re-create the ProjectTC - } - - private void initComponents() { - GroupLayout layout = new GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(canvas, GroupLayout.DEFAULT_SIZE, 651, Short.MAX_VALUE) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) -// .addComponent(renderingToolBar, GroupLayout.PREFERRED_SIZE, RenderingToolBar.WIDTH, GroupLayout.PREFERRED_SIZE) - .addComponent( - scrollPane, - ControlPanel.CONTROL_PANEL_WIDTH, - ControlPanel.CONTROL_PANEL_WIDTH, - ControlPanel.CONTROL_PANEL_WIDTH - ) - ) - ); - layout.setVerticalGroup( - layout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createBaselineGroup(true, true) - .addComponent(canvas) -// .addComponent(renderingToolBar) - .addComponent(scrollPane) - )) - ); - } - - public Canvas getCanvas() { - return canvas; - } - -} diff --git a/GUI/src/main/java/cz/fidentis/analyst/core/ManyToManyTab.java b/GUI/src/main/java/cz/fidentis/analyst/core/ManyToManyTab.java deleted file mode 100644 index a92d4566..00000000 --- a/GUI/src/main/java/cz/fidentis/analyst/core/ManyToManyTab.java +++ /dev/null @@ -1,85 +0,0 @@ -package cz.fidentis.analyst.core; - -import cz.fidentis.analyst.canvas.Canvas; -import cz.fidentis.analyst.canvas.toolbar.SceneToolboxSingleFace; -import cz.fidentis.analyst.batch.BatchAction; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; -import javax.swing.GroupLayout; -import javax.swing.JScrollPane; -import javax.swing.LayoutStyle; -import org.openide.windows.TopComponent; - -/** - * A non-singleton window/tab for the batch N:N analysis. - * - * @author Radek Oslejsek - */ -public class ManyToManyTab extends TopComponent { - - private final Canvas canvas ; - private final TopControlPanel controlPanel; - private final JScrollPane scrollPane; - - /** - * Constructor. - * - * @param name Tab name - */ - public ManyToManyTab(String name) { - canvas = new Canvas(); - //canvas.initScene(faces.get(0)); // !!! - canvas.addToolBox(new SceneToolboxSingleFace(canvas)); // !!! - controlPanel = new TopControlPanel(); - - scrollPane = new JScrollPane(controlPanel); - - setName(name); - initComponents(); - - // change the height so that it corresponds to the height of the OpenGL window - canvas.addComponentListener(new ComponentAdapter() { - @Override - public void componentResized(ComponentEvent e) { - scrollPane.setSize(ControlPanel.CONTROL_PANEL_WIDTH, canvas.getHeight()); - } - }); - - // (Re)render scene after all change listeners have been called - // (the first added listener is called last) - controlPanel.addChangeListener(e -> canvas.renderScene()); - new BatchAction(canvas, controlPanel); - //new DistanceAction(canvas, controlPanel); - //new SymmetryAction(canvas, controlPanel); - //new ProfilesAction(canvas, controlPanel); - } - - private void initComponents() { - GroupLayout layout = new GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(canvas, GroupLayout.DEFAULT_SIZE, 651, Short.MAX_VALUE) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) -// .addComponent(renderingToolBar, GroupLayout.PREFERRED_SIZE, RenderingToolBar.WIDTH, GroupLayout.PREFERRED_SIZE) - .addComponent( - scrollPane, - ControlPanel.CONTROL_PANEL_WIDTH, - ControlPanel.CONTROL_PANEL_WIDTH, - ControlPanel.CONTROL_PANEL_WIDTH - ) - ) - ); - layout.setVerticalGroup( - layout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createBaselineGroup(true, true) - .addComponent(canvas) -// .addComponent(renderingToolBar) - .addComponent(scrollPane) - )) - ); - } -} diff --git a/GUI/src/main/java/cz/fidentis/analyst/core/ProjectTopComp.form b/GUI/src/main/java/cz/fidentis/analyst/core/ProjectTopComp.form index d2f0b232..8f592816 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/core/ProjectTopComp.form +++ b/GUI/src/main/java/cz/fidentis/analyst/core/ProjectTopComp.form @@ -40,7 +40,7 @@ <Layout> <DimensionLayout dim="0"> <Group type="103" groupAlignment="0" attributes="0"> - <Component id="mainScrollPanel" alignment="1" pref="1217" max="32767" attributes="0"/> + <Component id="mainScrollPanel" alignment="1" max="32767" attributes="0"/> </Group> </DimensionLayout> <DimensionLayout dim="1"> @@ -65,13 +65,22 @@ <Group type="102" attributes="0"> <EmptySpace max="-2" attributes="0"/> <Group type="103" groupAlignment="0" max="-2" attributes="0"> - <Component id="faceTableScrollPanel" pref="0" max="32767" attributes="0"/> - <Component id="buttonsPanel" pref="792" max="32767" attributes="0"/> + <Component id="buttonsPanel" max="32767" attributes="0"/> + <Component id="faceTableScrollPanel" pref="863" max="32767" attributes="0"/> </Group> - <EmptySpace type="separate" max="32767" attributes="0"/> - <Group type="103" groupAlignment="0" max="-2" attributes="0"> - <Component id="filterPanel" pref="363" max="32767" attributes="0"/> - <Component id="infoPanel" pref="363" max="32767" attributes="0"/> + <EmptySpace max="32767" attributes="0"/> + <Group type="103" groupAlignment="1" attributes="0"> + <Group type="102" alignment="1" attributes="0"> + <Component id="newProjectButton" min="-2" max="-2" attributes="0"/> + <EmptySpace type="separate" max="-2" attributes="0"/> + <Component id="saveProjectButton" min="-2" max="-2" attributes="0"/> + <EmptySpace type="separate" max="-2" attributes="0"/> + <Component id="openProjectButton" min="-2" max="-2" attributes="0"/> + </Group> + <Group type="103" alignment="1" groupAlignment="0" max="-2" attributes="0"> + <Component id="filterPanel" pref="363" max="32767" attributes="0"/> + <Component id="infoPanel" pref="363" max="32767" attributes="0"/> + </Group> </Group> <EmptySpace min="-2" pref="20" max="-2" attributes="0"/> </Group> @@ -81,8 +90,25 @@ <Group type="103" groupAlignment="0" attributes="0"> <Group type="102" attributes="0"> <EmptySpace max="-2" attributes="0"/> - <Component id="buttonsPanel" min="-2" max="-2" attributes="0"/> - <EmptySpace max="-2" attributes="0"/> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="103" groupAlignment="0" attributes="0"> + <Group type="102" attributes="0"> + <Component id="buttonsPanel" min="-2" max="-2" attributes="0"/> + <EmptySpace min="-2" pref="6" max="-2" attributes="0"/> + </Group> + <Group type="102" alignment="1" attributes="0"> + <Group type="103" groupAlignment="3" attributes="0"> + <Component id="saveProjectButton" alignment="3" min="-2" max="-2" attributes="0"/> + <Component id="openProjectButton" alignment="3" min="-2" max="-2" attributes="0"/> + </Group> + <EmptySpace type="separate" max="-2" attributes="0"/> + </Group> + </Group> + <Group type="102" alignment="1" attributes="0"> + <Component id="newProjectButton" min="-2" max="-2" attributes="0"/> + <EmptySpace type="separate" max="-2" attributes="0"/> + </Group> + </Group> <Group type="103" groupAlignment="0" max="-2" attributes="0"> <Group type="102" attributes="0"> <Component id="filterPanel" min="-2" max="-2" attributes="0"/> @@ -111,17 +137,17 @@ <Layout class="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout"/> <SubComponents> - <Component class="javax.swing.JButton" name="addButton1"> + <Component class="javax.swing.JButton" name="addButton"> <Properties> <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> <Font name="Tahoma" size="12" style="0"/> </Property> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.addButton1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.addButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> </Property> </Properties> <Events> - <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="addButton1MouseClicked"/> + <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="addButtonMouseClicked"/> </Events> <Constraints> <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> @@ -129,17 +155,17 @@ </Constraint> </Constraints> </Component> - <Component class="javax.swing.JButton" name="removeButton1"> + <Component class="javax.swing.JButton" name="removeButton"> <Properties> <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> <Font name="Tahoma" size="12" style="0"/> </Property> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.removeButton1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.removeButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> </Property> </Properties> <Events> - <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="removeButton1MouseClicked"/> + <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="removeButtonMouseClicked"/> </Events> <Constraints> <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> @@ -147,17 +173,17 @@ </Constraint> </Constraints> </Component> - <Component class="javax.swing.JButton" name="selectAllButton1"> + <Component class="javax.swing.JButton" name="selectAllButton"> <Properties> <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> <Font name="Tahoma" size="12" style="0"/> </Property> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.selectAllButton1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.selectAllButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> </Property> </Properties> <Events> - <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="selectAllButton1MouseClicked"/> + <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="selectAllButtonMouseClicked"/> </Events> <Constraints> <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> @@ -165,17 +191,17 @@ </Constraint> </Constraints> </Component> - <Component class="javax.swing.JButton" name="deselectAllButton1"> + <Component class="javax.swing.JButton" name="deselectAllButton"> <Properties> <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> <Font name="Tahoma" size="12" style="0"/> </Property> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.deselectAllButton1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.deselectAllButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> </Property> </Properties> <Events> - <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="deselectAllButton1MouseClicked"/> + <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="deselectAllButtonMouseClicked"/> </Events> <Constraints> <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> @@ -183,18 +209,18 @@ </Constraint> </Constraints> </Component> - <Component class="javax.swing.JButton" name="inflateButton1"> + <Component class="javax.swing.JButton" name="inflateButton"> <Properties> <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> <Font name="Tahoma" size="12" style="0"/> </Property> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.inflateButton1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.inflateButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> </Property> <Property name="alignmentX" type="float" value="0.5"/> </Properties> <Events> - <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="inflateButton1MouseClicked"/> + <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="inflateButtonMouseClicked"/> </Events> <Constraints> <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> @@ -202,58 +228,58 @@ </Constraint> </Constraints> </Component> - <Component class="javax.swing.JButton" name="oneOnOneButton1"> + <Component class="javax.swing.JButton" name="oneOnOneButton"> <Properties> <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> <Font name="Tahoma" size="12" style="1"/> </Property> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.oneOnOneButton1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.oneOnOneButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> </Property> <Property name="alignmentX" type="float" value="0.5"/> </Properties> <Events> - <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="oneOnOneButton1MouseClicked"/> + <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="oneOnOneButtonMouseClicked"/> </Events> <Constraints> <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> - <GridBagConstraints gridX="5" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="16" insetsLeft="55" insetsBottom="13" insetsRight="4" anchor="18" weightX="0.0" weightY="0.0"/> + <GridBagConstraints gridX="6" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="16" insetsLeft="11" insetsBottom="13" insetsRight="4" anchor="18" weightX="0.0" weightY="0.0"/> </Constraint> </Constraints> </Component> - <Component class="javax.swing.JButton" name="analyseButton1"> + <Component class="javax.swing.JButton" name="manyToManyButton"> <Properties> <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> <Font name="Tahoma" size="12" style="1"/> </Property> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.analyseButton1.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.manyToManyButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> </Property> </Properties> <Events> - <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="analyseButton1MouseClicked"/> + <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="manyToManyButtonMouseClicked"/> </Events> <Constraints> <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> - <GridBagConstraints gridX="6" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="16" insetsLeft="16" insetsBottom="13" insetsRight="4" anchor="10" weightX="0.0" weightY="0.0"/> + <GridBagConstraints gridX="5" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="16" insetsLeft="30" insetsBottom="13" insetsRight="4" anchor="18" weightX="0.0" weightY="0.0"/> </Constraint> </Constraints> </Component> - <Component class="javax.swing.JButton" name="manyToManyButton"> + <Component class="javax.swing.JButton" name="analyseButton"> <Properties> <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> - <Font name="Ubuntu" size="14" style="1"/> + <Font name="Tahoma" size="12" style="1"/> </Property> <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> - <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.manyToManyButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.analyseButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> </Property> </Properties> <Events> - <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="manyToManyButtonMouseClicked"/> + <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="analyseButtonMouseClicked"/> </Events> <Constraints> <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription"> - <GridBagConstraints gridX="-1" gridY="-1" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="0" insetsLeft="0" insetsBottom="0" insetsRight="0" anchor="10" weightX="0.0" weightY="0.0"/> + <GridBagConstraints gridX="7" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="16" insetsLeft="11" insetsBottom="13" insetsRight="4" anchor="10" weightX="0.0" weightY="0.0"/> </Constraint> </Constraints> </Component> @@ -357,6 +383,56 @@ </DimensionLayout> </Layout> </Container> + <Component class="javax.swing.JButton" name="saveProjectButton"> + <Properties> + <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> + <Font name="Tahoma" size="12" style="0"/> + </Property> + <Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor"> + <Image iconType="3" name="/save100x24.png"/> + </Property> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.saveProjectButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + <AccessibilityProperties> + <Property name="AccessibleContext.accessibleName" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.saveProjectButton.AccessibleContext.accessibleName" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </AccessibilityProperties> + <Events> + <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="saveProjectButtonMouseClicked"/> + </Events> + </Component> + <Component class="javax.swing.JButton" name="openProjectButton"> + <Properties> + <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor"> + <Font name="Tahoma" size="12" style="0"/> + </Property> + <Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor"> + <Image iconType="3" name="/open100x24.png"/> + </Property> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.openProjectButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + <Events> + <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="openProjectButtonMouseClicked"/> + </Events> + </Component> + <Component class="javax.swing.JButton" name="newProjectButton"> + <Properties> + <Property name="icon" type="javax.swing.Icon" editor="org.netbeans.modules.form.editors2.IconEditor"> + <Image iconType="3" name="/new100x24.png"/> + </Property> + <Property name="text" type="java.lang.String" editor="org.netbeans.modules.i18n.form.FormI18nStringEditor"> + <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.newProjectButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, "{key}")"/> + </Property> + </Properties> + <Events> + <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="newProjectButtonMouseClicked"/> + </Events> + </Component> </SubComponents> </Container> </SubComponents> diff --git a/GUI/src/main/java/cz/fidentis/analyst/core/ProjectTopComp.java b/GUI/src/main/java/cz/fidentis/analyst/core/ProjectTopComp.java index d60fc747..40caf20e 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/core/ProjectTopComp.java +++ b/GUI/src/main/java/cz/fidentis/analyst/core/ProjectTopComp.java @@ -1,6 +1,7 @@ package cz.fidentis.analyst.core; -import cz.fidentis.analyst.Logger; +import cz.fidentis.analyst.ProjectConfiguration; +import com.fasterxml.jackson.databind.ObjectMapper; import org.netbeans.api.settings.ConvertAsProperties; import org.openide.awt.ActionID; import org.openide.awt.ActionReference; @@ -9,23 +10,25 @@ import org.openide.util.NbBundle.Messages; import cz.fidentis.analyst.Project; import cz.fidentis.analyst.face.HumanFace; import cz.fidentis.analyst.dashboard.ModelsTableModel; -import cz.fidentis.analyst.dashboard.FaceStatePanel; import cz.fidentis.analyst.dashboard.FilterPanel; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.File; import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collections; -import java.util.HashMap; import java.util.List; -import java.util.Map; import javax.swing.AbstractAction; +import javax.swing.ImageIcon; +import javax.swing.JFileChooser; import javax.swing.JOptionPane; import javax.swing.event.TableModelEvent; import javax.swing.event.TableModelListener; import javax.swing.filechooser.FileNameExtensionFilter; import org.openide.filesystems.FileChooserBuilder; +import org.openide.util.Exceptions; /** * The main panel enabling analysts to select the primary and secondary faces, @@ -57,10 +60,9 @@ import org.openide.filesystems.FileChooserBuilder; }) public final class ProjectTopComp extends TopComponent { - private final Project project; + private Project project; - private Map<HumanFace, SingleFaceTab> singleFaceTabs = new HashMap<>(); - private Map<HumanFace, FaceToFaceTab> faceToFaceTabs = new HashMap<>(); + private List<FaceTab> tabs = new ArrayList<>(); private ModelsTableModel model = new ModelsTableModel(new Object[]{"", "Models", "KD-tree"}, 0); @@ -69,11 +71,14 @@ public final class ProjectTopComp extends TopComponent { /* List of indexes of selected Rows */ private List<Integer> selectedRows = new ArrayList<>(); + private ObjectMapper mapper = new ObjectMapper(); + /** * Creates new ProjectTopComp, initializes new project */ public ProjectTopComp() { project = new Project(); + initComponents(); setName(Bundle.CTL_ProjectTopCompTopComponent()); setToolTipText(Bundle.HINT_ProjectTopCompTopComponent()); @@ -88,12 +93,15 @@ public final class ProjectTopComp extends TopComponent { } }; - fp = new FilterPanel(filterPanel, listener); + //fp = new FilterPanel(filterPanel, listener); + // Execute infinite OutputWindowThread that redirects messages logged // via Logger into the standard output window OutputWindowThread.execute(); + openExistingOrNewProject(); + } /** @@ -108,18 +116,21 @@ public final class ProjectTopComp extends TopComponent { mainScrollPanel = new javax.swing.JScrollPane(); mainPanel = new javax.swing.JPanel(); buttonsPanel = new javax.swing.JPanel(); - addButton1 = new javax.swing.JButton(); - removeButton1 = new javax.swing.JButton(); - selectAllButton1 = new javax.swing.JButton(); - deselectAllButton1 = new javax.swing.JButton(); - inflateButton1 = new javax.swing.JButton(); - oneOnOneButton1 = new javax.swing.JButton(); - analyseButton1 = new javax.swing.JButton(); + addButton = new javax.swing.JButton(); + removeButton = new javax.swing.JButton(); + selectAllButton = new javax.swing.JButton(); + deselectAllButton = new javax.swing.JButton(); + inflateButton = new javax.swing.JButton(); + oneOnOneButton = new javax.swing.JButton(); manyToManyButton = new javax.swing.JButton(); + analyseButton = new javax.swing.JButton(); faceTableScrollPanel = new javax.swing.JScrollPane(); table = new javax.swing.JTable(); filterPanel = new javax.swing.JPanel(); infoPanel = new javax.swing.JPanel(); + saveProjectButton = new javax.swing.JButton(); + openProjectButton = new javax.swing.JButton(); + newProjectButton = new javax.swing.JButton(); setOpaque(true); @@ -127,11 +138,11 @@ public final class ProjectTopComp extends TopComponent { buttonsPanel.setMinimumSize(new java.awt.Dimension(0, 0)); buttonsPanel.setLayout(new java.awt.GridBagLayout()); - addButton1.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(addButton1, org.openide.util.NbBundle.getMessage(ProjectTopComp.class, "ProjectTopComp.addButton1.text")); // NOI18N - addButton1.addMouseListener(new java.awt.event.MouseAdapter() { + addButton.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(addButton, org.openide.util.NbBundle.getMessage(ProjectTopComp.class, "ProjectTopComp.addButton.text")); // NOI18N + addButton.addMouseListener(new java.awt.event.MouseAdapter() { public void mouseClicked(java.awt.event.MouseEvent evt) { - addButton1MouseClicked(evt); + addButtonMouseClicked(evt); } }); gridBagConstraints = new java.awt.GridBagConstraints(); @@ -140,13 +151,13 @@ public final class ProjectTopComp extends TopComponent { gridBagConstraints.ipadx = 20; gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST; gridBagConstraints.insets = new java.awt.Insets(16, 16, 13, 4); - buttonsPanel.add(addButton1, gridBagConstraints); + buttonsPanel.add(addButton, gridBagConstraints); - removeButton1.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(removeButton1, org.openide.util.NbBundle.getMessage(ProjectTopComp.class, "ProjectTopComp.removeButton1.text")); // NOI18N - removeButton1.addMouseListener(new java.awt.event.MouseAdapter() { + removeButton.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(removeButton, org.openide.util.NbBundle.getMessage(ProjectTopComp.class, "ProjectTopComp.removeButton.text")); // NOI18N + removeButton.addMouseListener(new java.awt.event.MouseAdapter() { public void mouseClicked(java.awt.event.MouseEvent evt) { - removeButton1MouseClicked(evt); + removeButtonMouseClicked(evt); } }); gridBagConstraints = new java.awt.GridBagConstraints(); @@ -154,13 +165,13 @@ public final class ProjectTopComp extends TopComponent { gridBagConstraints.gridy = 0; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints.insets = new java.awt.Insets(16, 22, 13, 4); - buttonsPanel.add(removeButton1, gridBagConstraints); + buttonsPanel.add(removeButton, gridBagConstraints); - selectAllButton1.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(selectAllButton1, org.openide.util.NbBundle.getMessage(ProjectTopComp.class, "ProjectTopComp.selectAllButton1.text")); // NOI18N - selectAllButton1.addMouseListener(new java.awt.event.MouseAdapter() { + selectAllButton.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(selectAllButton, org.openide.util.NbBundle.getMessage(ProjectTopComp.class, "ProjectTopComp.selectAllButton.text")); // NOI18N + selectAllButton.addMouseListener(new java.awt.event.MouseAdapter() { public void mouseClicked(java.awt.event.MouseEvent evt) { - selectAllButton1MouseClicked(evt); + selectAllButtonMouseClicked(evt); } }); gridBagConstraints = new java.awt.GridBagConstraints(); @@ -168,13 +179,13 @@ public final class ProjectTopComp extends TopComponent { gridBagConstraints.gridy = 0; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints.insets = new java.awt.Insets(16, 22, 13, 4); - buttonsPanel.add(selectAllButton1, gridBagConstraints); + buttonsPanel.add(selectAllButton, gridBagConstraints); - deselectAllButton1.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(deselectAllButton1, org.openide.util.NbBundle.getMessage(ProjectTopComp.class, "ProjectTopComp.deselectAllButton1.text")); // NOI18N - deselectAllButton1.addMouseListener(new java.awt.event.MouseAdapter() { + deselectAllButton.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(deselectAllButton, org.openide.util.NbBundle.getMessage(ProjectTopComp.class, "ProjectTopComp.deselectAllButton.text")); // NOI18N + deselectAllButton.addMouseListener(new java.awt.event.MouseAdapter() { public void mouseClicked(java.awt.event.MouseEvent evt) { - deselectAllButton1MouseClicked(evt); + deselectAllButtonMouseClicked(evt); } }); gridBagConstraints = new java.awt.GridBagConstraints(); @@ -182,14 +193,14 @@ public final class ProjectTopComp extends TopComponent { gridBagConstraints.gridy = 0; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints.insets = new java.awt.Insets(16, 22, 13, 4); - buttonsPanel.add(deselectAllButton1, gridBagConstraints); + buttonsPanel.add(deselectAllButton, gridBagConstraints); - inflateButton1.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(inflateButton1, org.openide.util.NbBundle.getMessage(ProjectTopComp.class, "ProjectTopComp.inflateButton1.text")); // NOI18N - inflateButton1.setAlignmentX(0.5F); - inflateButton1.addMouseListener(new java.awt.event.MouseAdapter() { + inflateButton.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(inflateButton, org.openide.util.NbBundle.getMessage(ProjectTopComp.class, "ProjectTopComp.inflateButton.text")); // NOI18N + inflateButton.setAlignmentX(0.5F); + inflateButton.addMouseListener(new java.awt.event.MouseAdapter() { public void mouseClicked(java.awt.event.MouseEvent evt) { - inflateButton1MouseClicked(evt); + inflateButtonMouseClicked(evt); } }); gridBagConstraints = new java.awt.GridBagConstraints(); @@ -197,44 +208,49 @@ public final class ProjectTopComp extends TopComponent { gridBagConstraints.gridy = 0; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; gridBagConstraints.insets = new java.awt.Insets(16, 22, 13, 4); - buttonsPanel.add(inflateButton1, gridBagConstraints); + buttonsPanel.add(inflateButton, gridBagConstraints); - oneOnOneButton1.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(oneOnOneButton1, org.openide.util.NbBundle.getMessage(ProjectTopComp.class, "ProjectTopComp.oneOnOneButton1.text")); // NOI18N - oneOnOneButton1.setAlignmentX(0.5F); - oneOnOneButton1.addMouseListener(new java.awt.event.MouseAdapter() { + oneOnOneButton.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(oneOnOneButton, org.openide.util.NbBundle.getMessage(ProjectTopComp.class, "ProjectTopComp.oneOnOneButton.text")); // NOI18N + oneOnOneButton.setAlignmentX(0.5F); + oneOnOneButton.addMouseListener(new java.awt.event.MouseAdapter() { public void mouseClicked(java.awt.event.MouseEvent evt) { - oneOnOneButton1MouseClicked(evt); + oneOnOneButtonMouseClicked(evt); } }); gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 5; + gridBagConstraints.gridx = 6; gridBagConstraints.gridy = 0; gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; - gridBagConstraints.insets = new java.awt.Insets(16, 55, 13, 4); - buttonsPanel.add(oneOnOneButton1, gridBagConstraints); + gridBagConstraints.insets = new java.awt.Insets(16, 11, 13, 4); + buttonsPanel.add(oneOnOneButton, gridBagConstraints); - analyseButton1.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(analyseButton1, org.openide.util.NbBundle.getMessage(ProjectTopComp.class, "ProjectTopComp.analyseButton1.text")); // NOI18N - analyseButton1.addMouseListener(new java.awt.event.MouseAdapter() { + manyToManyButton.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(manyToManyButton, org.openide.util.NbBundle.getMessage(ProjectTopComp.class, "ProjectTopComp.manyToManyButton.text")); // NOI18N + manyToManyButton.addMouseListener(new java.awt.event.MouseAdapter() { public void mouseClicked(java.awt.event.MouseEvent evt) { - analyseButton1MouseClicked(evt); + manyToManyButtonMouseClicked(evt); } }); gridBagConstraints = new java.awt.GridBagConstraints(); - gridBagConstraints.gridx = 6; + gridBagConstraints.gridx = 5; gridBagConstraints.gridy = 0; - gridBagConstraints.insets = new java.awt.Insets(16, 16, 13, 4); - buttonsPanel.add(analyseButton1, gridBagConstraints); + gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST; + gridBagConstraints.insets = new java.awt.Insets(16, 30, 13, 4); + buttonsPanel.add(manyToManyButton, gridBagConstraints); - manyToManyButton.setFont(new java.awt.Font("Ubuntu", 1, 14)); // NOI18N - org.openide.awt.Mnemonics.setLocalizedText(manyToManyButton, org.openide.util.NbBundle.getMessage(ProjectTopComp.class, "ProjectTopComp.manyToManyButton.text")); // NOI18N - manyToManyButton.addMouseListener(new java.awt.event.MouseAdapter() { + analyseButton.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(analyseButton, org.openide.util.NbBundle.getMessage(ProjectTopComp.class, "ProjectTopComp.analyseButton.text")); // NOI18N + analyseButton.addMouseListener(new java.awt.event.MouseAdapter() { public void mouseClicked(java.awt.event.MouseEvent evt) { - manyToManyButtonMouseClicked(evt); + analyseButtonMouseClicked(evt); } }); - buttonsPanel.add(manyToManyButton, new java.awt.GridBagConstraints()); + gridBagConstraints = new java.awt.GridBagConstraints(); + gridBagConstraints.gridx = 7; + gridBagConstraints.gridy = 0; + gridBagConstraints.insets = new java.awt.Insets(16, 11, 13, 4); + buttonsPanel.add(analyseButton, gridBagConstraints); faceTableScrollPanel.setPreferredSize(new java.awt.Dimension(812, 750)); @@ -297,6 +313,32 @@ public final class ProjectTopComp extends TopComponent { infoPanel.setVisible(false); + saveProjectButton.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N + saveProjectButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/save100x24.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(saveProjectButton, org.openide.util.NbBundle.getMessage(ProjectTopComp.class, "ProjectTopComp.saveProjectButton.text")); // NOI18N + saveProjectButton.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + saveProjectButtonMouseClicked(evt); + } + }); + + openProjectButton.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N + openProjectButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/open100x24.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(openProjectButton, org.openide.util.NbBundle.getMessage(ProjectTopComp.class, "ProjectTopComp.openProjectButton.text")); // NOI18N + openProjectButton.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + openProjectButtonMouseClicked(evt); + } + }); + + newProjectButton.setIcon(new javax.swing.ImageIcon(getClass().getResource("/new100x24.png"))); // NOI18N + org.openide.awt.Mnemonics.setLocalizedText(newProjectButton, org.openide.util.NbBundle.getMessage(ProjectTopComp.class, "ProjectTopComp.newProjectButton.text")); // NOI18N + newProjectButton.addMouseListener(new java.awt.event.MouseAdapter() { + public void mouseClicked(java.awt.event.MouseEvent evt) { + newProjectButtonMouseClicked(evt); + } + }); + javax.swing.GroupLayout mainPanelLayout = new javax.swing.GroupLayout(mainPanel); mainPanel.setLayout(mainPanelLayout); mainPanelLayout.setHorizontalGroup( @@ -304,20 +346,40 @@ public final class ProjectTopComp extends TopComponent { .addGroup(mainPanelLayout.createSequentialGroup() .addContainerGap() .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(faceTableScrollPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE) - .addComponent(buttonsPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 792, Short.MAX_VALUE)) - .addGap(18, 18, Short.MAX_VALUE) - .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) - .addComponent(filterPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 363, Short.MAX_VALUE) - .addComponent(infoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 363, Short.MAX_VALUE)) + .addComponent(buttonsPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addComponent(faceTableScrollPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 863, Short.MAX_VALUE)) + .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING) + .addGroup(mainPanelLayout.createSequentialGroup() + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, 52, Short.MAX_VALUE) + .addComponent(newProjectButton) + .addGap(18, 18, 18) + .addComponent(saveProjectButton) + .addGap(18, 18, 18) + .addComponent(openProjectButton)) + .addGroup(mainPanelLayout.createSequentialGroup() + .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE) + .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) + .addComponent(filterPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 363, Short.MAX_VALUE) + .addComponent(infoPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 363, Short.MAX_VALUE)))) .addGap(20, 20, 20)) ); mainPanelLayout.setVerticalGroup( mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) .addGroup(mainPanelLayout.createSequentialGroup() .addContainerGap() - .addComponent(buttonsPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) - .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED) + .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) + .addGroup(mainPanelLayout.createSequentialGroup() + .addComponent(buttonsPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) + .addGap(6, 6, 6)) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, mainPanelLayout.createSequentialGroup() + .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE) + .addComponent(saveProjectButton) + .addComponent(openProjectButton)) + .addGap(18, 18, 18))) + .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, mainPanelLayout.createSequentialGroup() + .addComponent(newProjectButton) + .addGap(18, 18, 18))) .addGroup(mainPanelLayout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false) .addGroup(mainPanelLayout.createSequentialGroup() .addComponent(filterPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE) @@ -327,6 +389,8 @@ public final class ProjectTopComp extends TopComponent { .addContainerGap()) ); + saveProjectButton.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(ProjectTopComp.class, "ProjectTopComp.saveProjectButton.AccessibleContext.accessibleName")); // NOI18N + mainScrollPanel.setViewportView(mainPanel); mainScrollPanel.setSize(ControlPanel.CONTROL_PANEL_WIDTH, ControlPanel.HEIGHT); @@ -335,7 +399,7 @@ public final class ProjectTopComp extends TopComponent { this.setLayout(layout); layout.setHorizontalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) - .addComponent(mainScrollPanel, javax.swing.GroupLayout.Alignment.TRAILING, javax.swing.GroupLayout.DEFAULT_SIZE, 1217, Short.MAX_VALUE) + .addComponent(mainScrollPanel, javax.swing.GroupLayout.Alignment.TRAILING) ); layout.setVerticalGroup( layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING) @@ -344,27 +408,38 @@ public final class ProjectTopComp extends TopComponent { }// </editor-fold>//GEN-END:initComponents /** - * Opens analysis of one selected face, otherwise pops message dialog that - * you should select just one face + * Opens many to many tab + * @param evt + */ + private void manyToManyButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_manyToManyButtonMouseClicked + createManyToManyTab("N:N"); + }//GEN-LAST:event_manyToManyButtonMouseClicked + + /** + * Opens 1:1 tab with two selected faces, otherwise pops message that you + * should select two faces * @param evt */ - private void analyseButton1MouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_analyseButton1MouseClicked + private void oneOnOneButton1MouseClicked(java.awt.event.MouseEvent evt) { + //loadTwoModels(); - if (selectedRows.size() == 1) { + if (selectedRows.size() == 2) { - String name = model.getValueAt(selectedRows.get(0), 1).toString(); - HumanFace face = project.getFaceByName(name); - createSingleFaceTab(face, name); + String name1 = model.getValueAt(selectedRows.get(0), 1).toString(); + String name2 = model.getValueAt(selectedRows.get(1), 1).toString(); + HumanFace face1 = project.getFaceByName(name1); + HumanFace face2 = project.getFaceByName(name2); + createFaceToFaceTab(face1, face2, name1 + ":" + name2, false); } else { - JOptionPane.showMessageDialog(this, "Select one model"); + JOptionPane.showMessageDialog(this, "Select two models"); } - }//GEN-LAST:event_analyseButton1MouseClicked + } /** * Inflates models (selected will be deselected and vice versa) * @param evt */ - private void inflateButton1MouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_inflateButton1MouseClicked + private void inflateButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_inflateButtonMouseClicked for (int i = 0; i < model.getRowCount(); i++) { if (model.getValueAt(i, 0) == (Object) true) { @@ -373,16 +448,20 @@ public final class ProjectTopComp extends TopComponent { model.setValueAt(true, i, 0); } } - }//GEN-LAST:event_inflateButton1MouseClicked + }//GEN-LAST:event_inflateButtonMouseClicked /** * Deselects all models from list of models * @param evt */ - private void deselectAllButton1MouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_deselectAllButton1MouseClicked + private void deselectAllButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_deselectAllButtonMouseClicked deselectAllRows(); - }//GEN-LAST:event_deselectAllButton1MouseClicked + }//GEN-LAST:event_deselectAllButtonMouseClicked + /** + * Deselects all rows + * TODO : deselect only rows which are selected (checking row by row is slow) + */ private void deselectAllRows() { for (int i = 0; i < model.getRowCount(); i++) { model.setValueAt(false, i, 0); @@ -393,40 +472,52 @@ public final class ProjectTopComp extends TopComponent { * Selects all models from list of models * @param evt */ - private void selectAllButton1MouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_selectAllButton1MouseClicked + private void selectAllButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_selectAllButtonMouseClicked for (int i = 0; i < model.getRowCount(); i++) { model.setValueAt(true, i, 0); } - }//GEN-LAST:event_selectAllButton1MouseClicked + }//GEN-LAST:event_selectAllButtonMouseClicked //GEN-FIRST:event_removeButton1MouseClicked /** * Removes selected models from list and project * @param evt Removes selected faces */ - private void removeButton1MouseClicked(java.awt.event.MouseEvent evt) { - + private void removeButtonMouseClicked(java.awt.event.MouseEvent evt) { removeSelectedFaces(); } //GEN-LAST:event_removeButton1MouseClicked + /** + * Removes selected faces (those which are checked in check boxes) + */ private void removeSelectedFaces() { Collections.sort(selectedRows, Collections.reverseOrder()); selectedRows.forEach(row -> { - HumanFace face = this.project.getFaceByName(model.getValueAt(row, 1).toString()); - this.project.removeFace(face); + String name = model.getValueAt(row, 1).toString(); + List<FaceTab> tabsToClose = new ArrayList<>(); + tabs.stream().filter(t -> (t.hasFace(name))).forEachOrdered(t -> { + tabsToClose.add(t); + }); + + while(!tabsToClose.isEmpty()) { + tabsToClose.remove(0).close(); + } + + project.removeFaceByName(name); model.removeRow(row); }); selectedRows.clear(); + this.requestActive(); } /** * Adds new model * @param evt */ - private void addButton1MouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_addButton1MouseClicked - loadModel(); - }//GEN-LAST:event_addButton1MouseClicked + private void addButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_addButtonMouseClicked + loadModel(true); + }//GEN-LAST:event_addButtonMouseClicked /** * Shows face state panel after clicking on face in list. Also double-click @@ -441,38 +532,95 @@ public final class ProjectTopComp extends TopComponent { if (evt.getClickCount() == 2) { deselectAllRows(); model.setValueAt(true, table.getSelectedRow(), 0); - analyseButton1MouseClicked(evt); + analyseButtonMouseClicked(evt); } infoPanel.setVisible(true); checkFaceState(table.getValueAt(table.getSelectedRow(), 1).toString()); } }//GEN-LAST:event_tableMouseClicked + /** + * Saves current project + * @param evt + */ + private void saveProjectButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_saveProjectButtonMouseClicked + saveProject(); + }//GEN-LAST:event_saveProjectButtonMouseClicked + + /** + * Open new project + * @param evt + */ + private void openProjectButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_openProjectButtonMouseClicked + if (loadNewProject()) { + openProject(); + } + }//GEN-LAST:event_openProjectButtonMouseClicked + + /** + * Creates new project + * @param evt + */ + private void newProjectButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_newProjectButtonMouseClicked + if (loadNewProject()) { + newProject(); + } + }//GEN-LAST:event_newProjectButtonMouseClicked + /** * Opens 1:1 tab with two selected faces, otherwise pops message that you * should select two faces * @param evt */ - private void oneOnOneButton1MouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_oneOnOneButton1MouseClicked - //loadTwoModels(); + private void oneOnOneButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_oneOnOneButtonMouseClicked if (selectedRows.size() == 2) { String name1 = model.getValueAt(selectedRows.get(0), 1).toString(); String name2 = model.getValueAt(selectedRows.get(1), 1).toString(); - HumanFace face1 = project.getFaceByName(name1); - HumanFace face2 = project.getFaceByName(name2); - createFaceToFaceTab(face1, face2, name1 + ":" + name2); + + HumanFace face1 = project.loadFace(name1); + HumanFace face2 = project.loadFace(name2); + + if (project.getFaceByName(name1) == null) { + face1.registerListener(model); + project.addFace(face1); + } + + if (project.getFaceByName(name2) == null) { + face2.registerListener(model); + project.addFace(face2); + } + + createFaceToFaceTab(face1, face2, name1 + ":" + name2, false); } else { JOptionPane.showMessageDialog(this, "Select two models"); } - }//GEN-LAST:event_oneOnOneButton1MouseClicked - - private void manyToManyButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_manyToManyButtonMouseClicked - createManyToManyTab("N:N"); - }//GEN-LAST:event_manyToManyButtonMouseClicked + }//GEN-LAST:event_oneOnOneButtonMouseClicked + /** + * Opens analysis of one selected face, otherwise pops message dialog that + * you should select just one face + * @param evt + */ + private void analyseButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_analyseButtonMouseClicked + + if (selectedRows.size() == 1) { + + String name = model.getValueAt(selectedRows.get(0), 1).toString(); + HumanFace face = project.loadFace(name); + if (project.getFaceByName(name) == null) { + face.registerListener(model); + project.addFace(face); + } + + createSingleFaceTab(face, name, false); + } else { + JOptionPane.showMessageDialog(this, "Select one model"); + } + }//GEN-LAST:event_analyseButtonMouseClicked + /** * Updates selectedRows - adds new selected rows or removes deselected rows * @param e TableModelEvent @@ -491,24 +639,29 @@ public final class ProjectTopComp extends TopComponent { selectedRows.remove((Integer)row); } } + } else if (e.getType() == javax.swing.event.TableModelEvent.INSERT || e.getType() == javax.swing.event.TableModelEvent.DELETE) { + project.setSaved(false); } } // Variables declaration - do not modify//GEN-BEGIN:variables - private javax.swing.JButton addButton1; - private javax.swing.JButton analyseButton1; + private javax.swing.JButton addButton; + private javax.swing.JButton analyseButton; private javax.swing.JPanel buttonsPanel; - private javax.swing.JButton deselectAllButton1; + private javax.swing.JButton deselectAllButton; private javax.swing.JScrollPane faceTableScrollPanel; private javax.swing.JPanel filterPanel; - private javax.swing.JButton inflateButton1; + private javax.swing.JButton inflateButton; private javax.swing.JPanel infoPanel; private javax.swing.JPanel mainPanel; private javax.swing.JScrollPane mainScrollPanel; private javax.swing.JButton manyToManyButton; - private javax.swing.JButton oneOnOneButton1; - private javax.swing.JButton removeButton1; - private javax.swing.JButton selectAllButton1; + private javax.swing.JButton newProjectButton; + private javax.swing.JButton oneOnOneButton; + private javax.swing.JButton openProjectButton; + private javax.swing.JButton removeButton; + private javax.swing.JButton saveProjectButton; + private javax.swing.JButton selectAllButton; private javax.swing.JTable table; // End of variables declaration//GEN-END:variables @Override @@ -535,34 +688,35 @@ public final class ProjectTopComp extends TopComponent { /** * Loads model selected in file chooser by user + * @param loadFromFile true if models are loaded from file, else user chooses + * from FileChooserBuilder */ - public void loadModel() { - File[] files = new FileChooserBuilder(ProjectTopComp.class) + public void loadModel(boolean loadFromFile) { + File[] files; + + // Selects files with faces (by dialog or from JSON file) + if (loadFromFile) { + files = new FileChooserBuilder(ProjectTopComp.class) .setTitle("Open human face(s)") .setDefaultWorkingDirectory(new File(System.getProperty("user.home"))) //.setApproveText("Add") .setFileFilter(new FileNameExtensionFilter("obj files (*.obj)", "obj")) .setAcceptAllFileFilterUsed(true) - .showMultiOpenDialog(); - + .showMultiOpenDialog(); + } else { + files = new File[project.getCfg().getPaths().size()]; + files = project.getCfg().openFiles().toArray(files); + project.getCfg().clearPaths(); + } if (files == null) { System.out.print("No file chosen."); } else { - Logger out = Logger.measureTime(); for (File file : files) { - HumanFace face = null; - try { - face = new HumanFace(file, true); - out.printDuration("Loaded model " + face.getShortName() +" with " + face.getMeshModel().getNumVertices() + " vertices"); - } catch (IOException ex) { - Logger.print(ex.toString()); - } - - String name = face.getShortName(); - if (this.project.getFaceByName(name) == null) { - this.project.addFace(face); - face.registerListener(model); + + Path path = Paths.get(file.getAbsolutePath()); + String name = path.toString().substring(path.toString().lastIndexOf(File.separatorChar) + 1, path.toString().lastIndexOf('.')); + if (project.addNewPath(path)) { model.addRowWithName(name, false); } else { JOptionPane.showMessageDialog(this, String.format("Model with name %s is already loaded", name)); @@ -570,17 +724,54 @@ public final class ProjectTopComp extends TopComponent { } } } + + /** + * Loads tabs from project file + */ + private void loadTabs() { + + project.getCfg().getSingleTabFaces().forEach(name -> { + HumanFace face1 = project.loadFace(name); + createSingleFaceTab(face1, name, true); + }); + + project.getCfg().getFaceToFaceTabFaces().keySet().forEach(name -> { + HumanFace face1 = project.loadFace(name); + project.getCfg().getFaceToFaceTabFaces().get(name).forEach(otherName -> { + HumanFace face2 = project.loadFace(otherName); + createFaceToFaceTab(face1, face2, name + ":" + otherName, true); + }); + }); + + this.toFront(); + this.openAtTabPosition(0); + this.requestActive(); + + } /** * Creates and opens tab with one face * @param face which will be analyzed * @param name name of the tab (name of the model) */ - private void createSingleFaceTab(HumanFace face, String name) { - SingleFaceTab newTab = new SingleFaceTab(face, name); - this.singleFaceTabs.put(face, newTab); - newTab.open(); - newTab.requestActive(); + private void createSingleFaceTab(HumanFace face, String name, boolean loadFromFile) { + ActionListener tabCloseListener = new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + closeTab(e); + } + }; + FaceTab newTab = new FaceTab(face, null, name, tabCloseListener); + + if (!tabs.contains(newTab)) { + tabs.add(newTab); + if (!loadFromFile) { + project.addNewSingleFaceTabFace(name); + } + newTab.open(); + newTab.requestActive(); + this.project.setSaved(false); + } } /** @@ -589,12 +780,26 @@ public final class ProjectTopComp extends TopComponent { * @param face2 which will be analyzed * @param name name of the tab */ - private void createFaceToFaceTab(HumanFace face1, HumanFace face2, String name) { - FaceToFaceTab newTab = new FaceToFaceTab(face1, face2, name); - this.faceToFaceTabs.put(face1, newTab); - this.faceToFaceTabs.put(face2, newTab); - newTab.open(); - newTab.requestActive(); + private void createFaceToFaceTab(HumanFace face1, HumanFace face2, String nameOfTab, boolean loadFromFile) { + ActionListener tabCloseListener = new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + closeTab(e); + } + }; + + FaceTab newTab = new FaceTab(face1, face2, nameOfTab, tabCloseListener); + + if (!tabs.contains(newTab)) { + tabs.add(newTab); + + if (!loadFromFile) { + project.addNewFaceToFaceTabFace(face1.getShortName(), face2.getShortName()); + } + newTab.open(); + newTab.requestActive(); + this.project.setSaved(false); + } } /** @@ -603,7 +808,16 @@ public final class ProjectTopComp extends TopComponent { * @param name name of the tab */ private void createManyToManyTab(String name) { - ManyToManyTab newTab = new ManyToManyTab(name); + ActionListener tabCloseListener = new AbstractAction() { + @Override + public void actionPerformed(ActionEvent e) { + closeTab(e); + } + }; + FaceTab newTab = new FaceTab(null, null, name, tabCloseListener); + if (!tabs.contains(newTab)) { + tabs.add(newTab); + } newTab.open(); newTab.requestActive(); } @@ -614,9 +828,13 @@ public final class ProjectTopComp extends TopComponent { */ private void checkFaceState(String faceName) { HumanFace face = project.getFaceByName(faceName); - FaceStatePanel fsp = new FaceStatePanel(infoPanel, face); + //FaceStatePanel fsp = new FaceStatePanel(infoPanel, face); + } + /** + * Sorts faces by alphabet + */ private void alphabeticalFilter() { this.deselectAllRows(); /* @@ -642,21 +860,17 @@ public final class ProjectTopComp extends TopComponent { /** * Removes faces from project (and table of faces) based on filter configuration - * */ private void applyFilter() { - deselectAllRows(); - for (int i = 0; i < model.getRowCount(); i++) { - + for (int i = 0; i < model.getRowCount(); i++) { HumanFace face = project.getFaceByName(model.getValueAt(i, 1).toString()); if ((fp.isKdTreeFilter() && !face.hasKdTree()) || (fp.isFeaturePointsFilter() && !face.hasFeaturePoints())) { model.setValueAt(true, i, 0); } - } - + } removeSelectedFaces(); if (fp.isAlphaBeticalFilter()) { @@ -664,4 +878,166 @@ public final class ProjectTopComp extends TopComponent { } } + /** + * Asks user whether he wants to create new empty project, or open existing + * project + */ + private void openExistingOrNewProject() { + ImageIcon newProjectImage = new ImageIcon(ProjectTopComp.class.getClassLoader().getResource("/" + "new.png")); + ImageIcon existingProjectImage = new ImageIcon(ProjectTopComp.class.getClassLoader().getResource("/" + "open.png")); + Object[] options = {newProjectImage, existingProjectImage}; + + + int choice = JOptionPane.showOptionDialog(this, + "Would you like to create a new project or open an existing project?", + "Select project", + JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, + null, + options, + options[0]); + + if (choice == 1) { + openProject(); + } + } + + /** + * Resets all project attributes + */ + private void resetProject() { + + while (!tabs.isEmpty()) { + tabs.get(0).close(); + } + project.removeAll(); + model.setRowCount(0); + selectedRows.clear(); + } + + /** + * Checks whether current project is saved, if not then asks user if he wants + * to save it + * @return + */ + private boolean loadNewProject() { + if (!project.isSaved()) { + int showConfirmDialog = JOptionPane.showConfirmDialog(this, "Project is not saved. Would you like to save project?", "Save project", JOptionPane.YES_NO_CANCEL_OPTION); + + switch (showConfirmDialog) { + + case JOptionPane.YES_OPTION: + saveProject(); + break; + case JOptionPane.CANCEL_OPTION: + case JOptionPane.CLOSED_OPTION: + return false; + default: + break; + } + } + return true; + } + + /** + * Saves current project + */ + private void saveProject() { + + JFileChooser chooser = new JFileChooser(); + //chooser.setFileSelectionMode(JFileChooser.DIRECTORIES_ONLY); + chooser.setFileFilter(new FileNameExtensionFilter("json files (*.json)", "json")); + chooser.setAcceptAllFileFilterUsed(true); + chooser.showSaveDialog(null); + + File file = chooser.getSelectedFile(); + + if (file != null) { + String filePath = file.getAbsolutePath(); + if (!filePath.endsWith(".json")) { + file = new File(filePath.concat(".json")); + } + + try { + mapper.writeValue(file, project.getCfg()); + project.setSaved(true); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + } + + + } + /** + * Opens project, which user selects in File Chooser + */ + private void openProject() { + + File f; + f = new FileChooserBuilder(ProjectTopComp.class) + .setTitle("Choose existing project") + .setDefaultWorkingDirectory(new File(System.getProperty("user.home"))) + .setFileFilter(new FileNameExtensionFilter("json files (*.json)", "json")) + .setAcceptAllFileFilterUsed(true) + .showOpenDialog(); + + if (f != null) { + try { + resetProject(); + project.setCfg(mapper.readValue(f, ProjectConfiguration.class)); + loadModel(false); + loadTabs(); + project.setSaved(true); + } catch (IOException ex) { + Exceptions.printStackTrace(ex); + } + + } + } + + /** + * Creates new project + */ + private void newProject() { + resetProject(); + project.setCfg(new ProjectConfiguration()); + project.setSaved(true); + } + + /** + * Closes tab + * @param e ActionEvent + */ + private void closeTab(ActionEvent e) { + FaceTab tab = (FaceTab)e.getSource(); + + for (FaceTab t : tabs) { + if (t.equals(tab)) { + + if (e.getActionCommand().equals("FaceTab")) { + this.project.removeFaceTab(t.getNameOfFace1()); + } else if (e.getActionCommand().equals("FaceToFaceTab")){ + this.project.removeFaceToFaceTab(t.getNameOfFace1(), t.getNameOfFace2()); + } + tabs.remove(t); + break; + } + } + + this.requestActive(); + } + + @Override + public boolean canClose() { + if (!project.isSaved()) { + + int save = JOptionPane.showConfirmDialog(null, + "Project is not saved. Would you like to save project?", "Save project", JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); + + if (save == JOptionPane.YES_OPTION) { + saveProject(); + } + } + return super.canClose(); //To change body of generated methods, choose Tools | Templates. + } + } diff --git a/GUI/src/main/java/cz/fidentis/analyst/core/SingleFaceTab.java b/GUI/src/main/java/cz/fidentis/analyst/core/SingleFaceTab.java deleted file mode 100644 index 8733b245..00000000 --- a/GUI/src/main/java/cz/fidentis/analyst/core/SingleFaceTab.java +++ /dev/null @@ -1,100 +0,0 @@ -package cz.fidentis.analyst.core; - -import cz.fidentis.analyst.canvas.Canvas; -import cz.fidentis.analyst.canvas.toolbar.SceneToolboxSingleFace; -import cz.fidentis.analyst.curvature.CurvatureAction; -import cz.fidentis.analyst.face.HumanFace; -import cz.fidentis.analyst.symmetry.ProfilesAction; -import cz.fidentis.analyst.symmetry.SymmetryAction; -import java.awt.event.ComponentAdapter; -import java.awt.event.ComponentEvent; -import javax.swing.GroupLayout; -import javax.swing.JScrollPane; -import javax.swing.LayoutStyle; -import org.openide.windows.TopComponent; - -/** - * The non-singleton window/tab for detail inspection of a single face. - * - * @author Radek Oslejsek - */ -public final class SingleFaceTab extends TopComponent { - - private final Canvas canvas; - private final TopControlPanel controlPanel; - private final JScrollPane scrollPane; - - /** - * Constructor. - * @param face Face - * @param name Tab name - */ - public SingleFaceTab(HumanFace face, String name) { - canvas = new Canvas(); - canvas.addPrimaryFace(face); - canvas.getScene().setDefaultColors(); - canvas.addToolBox(new SceneToolboxSingleFace(canvas)); - controlPanel = new TopControlPanel(); - - scrollPane = new JScrollPane(controlPanel); - - setName(name); - initComponents(); - - // change the height so that it corresponds to the height of the OpenGL window - canvas.addComponentListener(new ComponentAdapter() { - @Override - public void componentResized(ComponentEvent e) { - scrollPane.setSize(ControlPanel.CONTROL_PANEL_WIDTH, canvas.getHeight()); - } - }); - - /* - * Add and open controll panels: - */ - // (Re)render scene after all change listeners have been called - // (the first added listener is called last) - controlPanel.addChangeListener(e -> getCanvas().renderScene()); - new CurvatureAction(getCanvas(), controlPanel); - new SymmetryAction(getCanvas(), controlPanel); - new ProfilesAction(getCanvas(), controlPanel); - } - - @Override - public int getPersistenceType() { - return TopComponent.PERSISTENCE_NEVER; // TO DO: change to .PERSISTENCE_ONLY_OPENED when we can re-create the ProjectTC - } - - private void initComponents() { - GroupLayout layout = new GroupLayout(this); - this.setLayout(layout); - layout.setHorizontalGroup( - layout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) - .addComponent(canvas, GroupLayout.DEFAULT_SIZE, 651, Short.MAX_VALUE) - .addPreferredGap(LayoutStyle.ComponentPlacement.RELATED) -// .addComponent(renderingToolBar, GroupLayout.PREFERRED_SIZE, RenderingToolBar.WIDTH, GroupLayout.PREFERRED_SIZE) - .addComponent( - scrollPane, - ControlPanel.CONTROL_PANEL_WIDTH, - ControlPanel.CONTROL_PANEL_WIDTH, - ControlPanel.CONTROL_PANEL_WIDTH - ) - ) - ); - layout.setVerticalGroup( - layout.createParallelGroup(GroupLayout.Alignment.LEADING) - .addGroup(layout.createSequentialGroup() - .addGroup(layout.createBaselineGroup(true, true) - .addComponent(canvas) -// .addComponent(renderingToolBar) - .addComponent(scrollPane) - )) - ); - } - - public Canvas getCanvas() { - return canvas; - } -} diff --git a/GUI/src/main/java/cz/fidentis/analyst/dashboard/FaceStatePanel.java b/GUI/src/main/java/cz/fidentis/analyst/dashboard/FaceStatePanel.java index 70890dee..173f96e0 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/dashboard/FaceStatePanel.java +++ b/GUI/src/main/java/cz/fidentis/analyst/dashboard/FaceStatePanel.java @@ -44,7 +44,7 @@ public final class FaceStatePanel extends JPanel { hasKDtree.setIcon(notCheck); } builder.addLine(); - + hasFeaturePoints = builder.addLabelLine("Has Feature points"); if (face.hasFeaturePoints()) { hasFeaturePoints.setIcon(check); @@ -52,15 +52,15 @@ public final class FaceStatePanel extends JPanel { hasFeaturePoints.setIcon(notCheck); } builder.addLine(); - + tmp1 = builder.addLabelLine("..."); tmp1.setIcon(notCheck); builder.addLine(); - + tmp2 = builder.addLabelLine("..."); tmp2.setIcon(notCheck); builder.addLine(); - + tmp3 = builder.addLabelLine("..."); tmp3.setIcon(notCheck); builder.addLine(); diff --git a/GUI/src/main/java/cz/fidentis/analyst/dashboard/ModelsTableModel.java b/GUI/src/main/java/cz/fidentis/analyst/dashboard/ModelsTableModel.java index 7367bb56..e7e979ed 100644 --- a/GUI/src/main/java/cz/fidentis/analyst/dashboard/ModelsTableModel.java +++ b/GUI/src/main/java/cz/fidentis/analyst/dashboard/ModelsTableModel.java @@ -58,7 +58,7 @@ public class ModelsTableModel extends DefaultTableModel implements HumanFaceList /** * Adds new row to model * @param name String name of the face - * @param hasKD boolean if face has KD tree calculated + * @param hasKD Boolean if face has KD tree calculated */ public void addRowWithName(String name, boolean hasKD) { if (hasKD) { diff --git a/GUI/src/main/resources/cz/fidentis/analyst/core/Bundle.properties b/GUI/src/main/resources/cz/fidentis/analyst/core/Bundle.properties index 4b6044b0..fb98808f 100644 --- a/GUI/src/main/resources/cz/fidentis/analyst/core/Bundle.properties +++ b/GUI/src/main/resources/cz/fidentis/analyst/core/Bundle.properties @@ -14,12 +14,6 @@ ProjectTopComp.jTable1.columnModel.title3=Title 4 ProjectTopComp.jTable1.columnModel.title1=Title 2 ProjectTopComp.jTable1.columnModel.title0_1=Models ProjectTopComp.jTable1.columnModel.title1_1= -ProjectTopComp.analyseButton1.text=Analyse -ProjectTopComp.inflateButton1.text=Inflate -ProjectTopComp.deselectAllButton1.text=Deselect all -ProjectTopComp.selectAllButton1.text=Select all -ProjectTopComp.removeButton1.text=Remove -ProjectTopComp.addButton1.text=Add ProjectTopComp.jTable2.columnModel.title3=Title 4 ProjectTopComp.jTable2.columnModel.title2=Title 3 ProjectTopComp.jTable2.columnModel.title1=Title 2 @@ -29,5 +23,21 @@ MeasurementsPanel.jLabel3.text=Feature points distance: MeasurementsPanel.jTextField1.text= MeasurementsPanel.jTextField2.text= MeasurementsPanel.jTextField3.text= -ProjectTopComp.oneOnOneButton1.text=Open 1:1 +StartFrame.jButton1.text=jButton1 +StartFrame.jButton2.text=jButton2 +StartFrame.jButton3.text=jButton3 +StartPanel.jButton1.text= +StartPanel.jButton2.text= +StartPanel.jButton3.text=Cancel +ProjectTopComp.saveProjectButton.AccessibleContext.accessibleName=SaveProject +ProjectTopComp.saveProjectButton.text= +ProjectTopComp.openProjectButton.text= +ProjectTopComp.newProjectButton.text= ProjectTopComp.manyToManyButton.text=N:N +ProjectTopComp.analyseButton.text=Analyse +ProjectTopComp.addButton.text=Add +ProjectTopComp.removeButton.text=Remove +ProjectTopComp.selectAllButton.text=Select all +ProjectTopComp.deselectAllButton.text=Deselect all +ProjectTopComp.inflateButton.text=Inflate +ProjectTopComp.oneOnOneButton.text=Open 1:1 diff --git a/GUI/src/main/resources/new.png b/GUI/src/main/resources/new.png new file mode 100644 index 0000000000000000000000000000000000000000..3b7bc90aedd63fb996407cb0ebb8ab2543385f9b GIT binary patch literal 3149 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 literal 0 HcmV?d00001 diff --git a/GUI/src/main/resources/new100x24.png b/GUI/src/main/resources/new100x24.png new file mode 100644 index 0000000000000000000000000000000000000000..8bef62ef23ae0aec69a1614b16bf2993ff692102 GIT binary patch literal 1345 zcmV-H1-|-;P)<h;3K|Lk000e1NJLTq003kF000;W1^@s63<6m000001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1lCDJK~!i%?U;F} z6;&L^uco$|Emm4uVv(s-T2Lt#NFn%#h-pTv6p=-=P$3y2l@gSS7L^4-?wD9n(FUc_ zY8r)=?Li?zmRe}5nf3YHx#xKp=FYu$#D_fQ3m@*xyqP)Y{MIwCQmRy`QXNC*!f)_P z9ah01P^z6Y9Nvb*>u@u?4u?Xic90HvGdavuXSfw!gTuVggbsuQlEXH2f^NxShnDMX zD2-ZFyWtVgAuFIe<Vj~d50^naxxVl$98o>n|6~A7ficyyV(JeQp`lau#v$rocnIt# z%OJ0(IGJPME;!i>HFbgGAfAiIUQb_(m0ny^5m-P5-qY}Y^{klAhEtNmd=+ts<tZ*v zI%OW52FH6LM?ZrZbq}PS_h0SzSTG`Ag?qtw_5jDQcPXRxTJXB^{6RROdbUTm!^L0_ zyabnm<x%H(Z4@kptH1y_4;I1_7+JgKy`y0WD95@GyzgNc1R)rl&rM)(UIC6Z0Pcj# z;ReXHc@c*=;BCoan|8rJ$ziT^-lNbHCO{ZJEJ!DraYup%c9-`A=q$%L1JqG)4V+p% zt5o`d5xBW}wnt{-ec<~S!HuBtJvV@K%8T$3TmbXo9e4v~LYSlX_69W$row9Y5_HO$ zpz#f$bHIvmJ&c8I@F95brw{_KWu=?CJzx&32VXoJVuyYXd1-kN7^NC>7_0*gW_@+S z_7Jz)FN1wW1DGB5rxD<MT(A*(5ln|WK;zy5lOQdf|2TB5o+a<s0SChZFcX#QFjFo8 z-zSdo99Rv^&MU$9yZ~o`yGywm?uQfMUFZWoKN!p;WoF(DUe8r48DcH{41NS7(c@O- zgp806K*LM}jT5AUT*L<Ws)pr4=NJf<B(p~c`TWq@xrWx+X*u2+2-*8R7!Yni{M!$p zH1yFBB3Ql!jqLn_A$`tl(D0VupTG<@gDkaXgWqUA?;6asmeh(vT+~zW3fKpXFrCz_ z;^+uFw3A>wgn3w^+=-bj_6rSQd3y$ahqpjyn5|!f3%3tw*e#HyxZi2qU*XT{S@OJc zK9<usI2_{-@OqGCa}($QHz;mK{Iy?rug_%JG+ukIT5(8_={qYqY+1no%4o6{`z>R1 zD$AHovQBHrfuQqkjLH&fX<7?9$l$PXuY`KCq}XHZPd3$0z-MC_Y<>h=`V0h1(Pa1- z3=-wscfc2L9q2@{Z2Dd+hYoRLU|F^Q7{I|9abVO{5r^!JjH1{n_KpX@g?imZxj;7# z8(}#-36?Hpbm}ylu(BM5o>os9+6*&`ELR3oJcppi;4jeN-+=edfH*^Z&fewD%6n|o z_Q1tpsj|npiEu2PI~RQBJ8+lfT69(+HTqIdt5H)|p}EE~DAHf*Za0m#tovP1h&q40 zH%tCaBl{uhd(!2^^YpX6+eo%lhQImiPD+CYIbX9{;<-Df8P*HpjBs4bn|&tCJyY$S zAz52v(|%`i*d}ZB-;i~`5gQ`hU+=9a7w<j3$M-o_kmu=VeYYjbjP-9i!|kw*Jl;|} z->o&`xjWW&Fw;Y+Q;zHNZUVyGGu6l;e(0RvB>Urkel0&5r>NYj`!5f7OYeDMUn?(t zPKWSO){K0Y`*`boN0LFS4Vtw>sZym%l`2)LRHgDi`<Mjg7A~Mq00000NkvXXu0mjf Dxw~^c literal 0 HcmV?d00001 diff --git a/GUI/src/main/resources/open.png b/GUI/src/main/resources/open.png new file mode 100644 index 0000000000000000000000000000000000000000..b13c5d7b0152df6b11dd21505414718ce9c34d37 GIT binary patch literal 3700 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 VXUbnh=D#-*KpR+GtJKFw{2wys?>+zk literal 0 HcmV?d00001 diff --git a/GUI/src/main/resources/open100x24.png b/GUI/src/main/resources/open100x24.png new file mode 100644 index 0000000000000000000000000000000000000000..0e8ad33d6b8af6869176aa13660230ff6bf0d839 GIT binary patch literal 1538 zcmV+d2L1VoP)<h;3K|Lk000e1NJLTq003kF000;W1^@s63<6m000001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1(!)gK~!i%?bmgP zK1CeI@t@t@-GPOQV4@<Th}eqVir5MkwxTH5iHe1RpnoYA7B&`k7dCcd*XMP2^K)-+ z_qjVg1%K`b-aOCl&dknyYi7rF>eQ)+CK~u{(j;Nx{=b9tTllsAZ>O<Vn?n{3r-sk^ zuKtwvbA-dfYi@M<ceH#sG)z`ws&Gd*CvZ%sF;-JaDpHy}SGo$Wp9(XF&BJJ={}f@$ zz`?yr=7g@swYM2$g|JarG|cHnMfzMFW(eoGF+$B2whfyG+4WesDm)Z^3jZY?6CMw@ z_g#%%^M}>K^+Di|SKT4Ygx$g`Vft`$m^{=qbyz8k)FHEm%fkEN%J6kqGVBw)<HGPu zm^3&r$Ru;~|MyVjo@ee=7vi4lvYu+6e?p6dufnH&SEJX4VdZdq7~~LTsH%0s7r}<@ zrOL{JVeT3~hgrgN1OJE9LBRqJ5AOyGc{w~7jtY;4Z^K35%W!PiA#mV(fq}hm?{Grk zOmj4*Og%UJ5KanP1kXMWRdL$BVcxJoI4*Fw;A0eHd&iMMRd^;W8yIcpAdBA&+U^+i zeLD!Eb%S-R9n9BZ$efkt?Sger7hVbrgi`{CDBmi@_y97=s$84kH-VvF3?}w;4G}&~ zI5C(|FL}ORxF*z{_g1(stQiD{Qpr#b4mSmcULsi7X<?plMzCPcn>`qRt*}?PF1#Kt z4h|qy$)3dmr!kI<<phrROnE*!+#8gm6~m>0J<LT=?H9CNI+Rk*;0Ff5^+q@==&RiU zVY6^o*gk9-1Q8o*>$hh%8+`S^Fiu&k22sNxRTd9Bg<Hc+1NTr$Mpeq&{h{qtCTc|t zBRv_m3Pp#6Bf@gwo}l*Hix@+h+a#DDqsba_;E-{gRTYPG@a3Td5yP3AYmSx;f>K+C zRZ`2EIN^c7(U%1CoHnq5Q>g$2yPO@YTd=Ve`ISk=vp);wdO{4nQIT>bqC~dRROPlH z(nR(X1NZ!j;JWM$(wxJZq~s`5q^+@v{I{<uGphtCFRPS}U4!%|C9r%mDJ4Jl{~aRF z9uD3^N)9nHL%kEs)!MDaiLykn_i7_>ij&M;&>Hij0c7q%L7=d&vP-W9nbI>OdO5_T z?H9Z%ovZk8oG*w7|J`F$rK~Qs_mLo)>+ciI>aNHm&y88+Fu)Msg*$_6^j!2Y%=w|H zEVLM_NygW&s5qiHu#CYe?p5dFaHD^5CULO&EfwYp>?M1hCiORVQ%P@~W1B;~$l~`8 zO#{|t&taS<htLIK>!4<v@Qgtj@`3qmkX>Za+9)@oY|o%1yb$!;I53jTy)igCjz-pN zgEqEz*>?m>eQ$GZPw-q&XuC&nP_PFO=Y1Yz?KXj<*zMq8?+~Ect{=SDLE_pV`^Y+m zIHt`ZAB67%kB>xZw&QD?oUH6mBFUzy=5vN`5F4p1Dv>JU+`lC#mkjnvuyJyh62KYW z=b3)hs3j5JR2mrCUUWlHUS#>nq2AUt<0*}T#r*B9Hg-lYGH<6Vf#KBnd~hngB6#M+ z<{+RnYG-dT2iK0izQfu|d7sLr?#OF0CN><Rz*l=~8df9#M4RJYJ@b6`FtF&?rs+#w zAAPIO{A%BJU1Xy^GdNZ0uUu6FvW#1mK1!U7HfE92m*aJjz788Do2t@ZU|V0AllMC0 z$ofI^tY-)V+st+kHqwzu#QS2^cQ}b$ks}l8QC%ix9Bo8WeN27ytv)O5+pg<!Uw;+A ze2U7rRmleBSotxgIj#{L#Xk&1`pRO53+<}XU&T?*%bdJdh8kngJnK>97`RIiJ#|kv z$-t)vHU96)My-4~npPJ(RuX*n1gD+5-S`WV3HC~5)w(%iOjUOXxz}{RNxa(@i4&?g o_W!u<PMtb+>eQ*z$Te=<AFdx<sA`L|hyVZp07*qoM6N<$f}B<25&!@I literal 0 HcmV?d00001 diff --git a/GUI/src/main/resources/save100x24.png b/GUI/src/main/resources/save100x24.png new file mode 100644 index 0000000000000000000000000000000000000000..defc919847ac755bbfb5677d7cd733c904ecafaa GIT binary patch literal 1479 zcmV;&1vvVNP)<h;3K|Lk000e1NJLTq003kF000;W1^@s63<6m000001b5ch_0Itp) z=>Px#1ZP1_K>z@;j|==^1poj532;bRa{vGi!vFvd!vV){sAK>D1zbr)K~!i%?bijU zbVV4(@wL0V18fxR09&yeu@S*Q#THRaR8SBR3l%>s#1>mo6brksy8}_N#g6B<yCdiB zJ$vznaPP-?;Lo0zbI#1X&->17t5KsyjV51B5$5b1HX0AwEc_7GYo9e5b4}U({jXXw z%p4YPpEVkD4L2Z4)@bDUX~J~f-_gErqN?w>hZ~U5XzH+OxGX##E)QFW>BB_Qabd&G z;fS<I*f!M8{cr>FclUQBT0a=fb>V<;O*ka%9;*FtJXQC%sJEXSR&`l9@9VrV)a~K5 z&f$o(YFM#zI9LWg9IY0(`j+sL3&YSp;l*%u_%yuTMW7xIe}$F8k>RMYTzEbF5l#-T zhMxkh5_S&Hg;~Si;n=Wncq9B0s!|@kS73Ju2ZgW0$AQWA3)V($>>_=4*>FtQDcH#9 zvpzcae&^<I-Qc-3!$;w}K+5ut;m|N`cqc3tjKw^9m-!fA@?Pf=mu(VeZ=VfRdAKf9 zOvc)wpG%(!24f;^3QXkF;pkw1ON9%<`(aX$+GmH~gK{Tu-mTBu2Lm_Ia|72g$^7Bc zFk`67GiwBGX9-V)^TI}fIQF!_ASO=0uMEB+IA?^tg72>$77Da$I3au(%*iEzkp$}S zz$o5%L||m&{3Lu4J_~j8I7st`Z^DJ`vsP<?;G?c9HD0o3STKmTiKV*&cN%b=baIEF z9Vf|()5Cm$?BKY~<Z(0?JsC_o_bG8)H&=Kd2&(Hh27S=ZdL#oGqbkqc9DJ`-g>AwO z!S@8`#O|^C=LchTE(q&{`-4DR136zt-4T`!#VGrRQvx$T6a<H4zJO@Ml;+d%Ak|&u zMjt4P?J|Lj)qayf^8~f@;K0c=Lok>!;HtcDrf^%>Dy$Gr3W9o3aF25g<g9Q-xGD&) z;Jgy_NBfn7z;l?s$(Ap}kKy~EBrO_x^=o)E{MkNhwO$U|mGOQMlo5fI0nc{N8Su>@ zsQRO=Yf78{W6uZQ{Wi!z0b*XI)|g0nzcYB>kgAPRS0;iU32ORU;dmD!PBR&5Z&~0B zjOx<RyKV|f0jbF{#3HZM8JtpfSWs8b3EWmD&)T8AKc)g;aKDA0gUsPv69&fM{9YNW zdqo`Xy}nT<nM|58S#7KT+LX5B_}#F45J1Lc82uJ}-|J`2zN!mI=@_Sw4%(th;W9xw ztZd1W(%xkBlgX1Erv^b$Vz`K#KM!iPMbviq*1-K`_f_7n)a@3ezx9Z$LCS(P&!ky2 z?+r4rS2B!&WVZkw814?mwdFhds#KcTIfCbGNG#&AfV7pB);k6f02yI%_uN;(Tv_*w zVT&N^i)np-$iP%(&G6w~mwCaKL5gnNJ+I0nS-@s%pAD7(>##{Tn74vXE)hsTtPSox z76e3znLCgP*QYZ>ah^VG9!#J<TY&cuy+&geC099A?wH`>pj490#2%f)wv53Vs*DMQ z4MXXtJ_`b=688mxu^DjBny6paG2?L0uT2Hn6d1eB$51BzS0y0Vx6cO3hx)zbsW4PP z$nzYh7IR5&N_)3^ezn!6I=P-LnOl|ivQC-tT(8`}Hf#{Q(>JQ$7MR{~akEk?qpJ7p zzP9Tew&i_Fm3gbS(+?RYkls`J<-5w8Qaz}zb)6Ssi<ne?FPISNSJG0tdX8gFjt^+7 zbXdeVz#=!EWTLoNR#=NhB4f7mv%cBn$jUKSZ)%hJrw-dIa}xJ(AE$7F1<bEf;=23V z*~;D%>~_YRm`F(}f7(VO2DIsTFudTx|Dn1JA+9g=wWzLbSqRCoT}xRcCr1?nPtId( h)TmLTMiWx4)<1%g6ZvWHUEBZw002ovPDHLkV1ll*%>e)a literal 0 HcmV?d00001 -- GitLab