From f5315c9b29beb93215aa08b819c9122fdeb248ea Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Matej=20Kov=C3=A1r?= <xkovar4@fi.muni.cz>
Date: Wed, 23 Feb 2022 07:47:46 +0100
Subject: [PATCH] Resolve "Upgrade project tab (opening and closing app
 changes; analyse models; opening preview image)"

---
 .../java/cz/fidentis/analyst/Project.java     |   6 +-
 .../analyst/ProjectConfiguration.java         |   4 +-
 .../cz/fidentis/analyst/gui/Installer.java    |  27 +-
 .../analyst/project/FaceStatePanel.form       |   3 +
 .../analyst/project/FaceStatePanel.java       |  28 +-
 .../analyst/project/ProjectPanel.form         |  65 +---
 .../analyst/project/ProjectPanel.java         | 298 ++++++++++--------
 .../analyst/project/ProjectTopComp.java       |  16 +-
 .../analyst/project/Bundle.properties         |   2 -
 GUI/src/main/resources/recent_project.png     | Bin 0 -> 3724 bytes
 10 files changed, 258 insertions(+), 191 deletions(-)
 create mode 100644 GUI/src/main/resources/recent_project.png

diff --git a/Comparison/src/main/java/cz/fidentis/analyst/Project.java b/Comparison/src/main/java/cz/fidentis/analyst/Project.java
index 14db43ce..ffcf7cfa 100644
--- a/Comparison/src/main/java/cz/fidentis/analyst/Project.java
+++ b/Comparison/src/main/java/cz/fidentis/analyst/Project.java
@@ -4,7 +4,6 @@ import cz.fidentis.analyst.face.HumanFace;
 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.List;
@@ -219,10 +218,7 @@ public class Project {
                 Path path = this.getCfg().getPathToFaceByName(name);
                 File file = path.toFile();
                 face = new HumanFace(file, true); // loads also landmarks, if exist
-                
-                String pathString = path.toString();
-                Path preview = Paths.get(pathString.substring(0, pathString.lastIndexOf(".")).concat("_preview.jpg"));
-                //Path previewSmall = Paths.get(pathString.substring(0, pathString.lastIndexOf(".")).concat("_preview_small.png"));
+                Path preview = path.resolveSibling(name.concat("_preview.jpg"));
                 face.setPreview(preview);
                 this.addFace(face);
                 out.printDuration("Loaded model " + face.getShortName() +" with " + face.getMeshModel().getNumVertices() + " vertices");
diff --git a/Comparison/src/main/java/cz/fidentis/analyst/ProjectConfiguration.java b/Comparison/src/main/java/cz/fidentis/analyst/ProjectConfiguration.java
index 3f42efb6..c9e9e711 100644
--- a/Comparison/src/main/java/cz/fidentis/analyst/ProjectConfiguration.java
+++ b/Comparison/src/main/java/cz/fidentis/analyst/ProjectConfiguration.java
@@ -81,7 +81,9 @@ public class ProjectConfiguration {
         List<File> f = new ArrayList<>();
             
         paths.forEach(p -> {
-            f.add(p.toFile());
+            if (p.toFile().exists()) {
+                f.add(p.toFile());
+            }
         });
         
         return f;
diff --git a/GUI/src/main/java/cz/fidentis/analyst/gui/Installer.java b/GUI/src/main/java/cz/fidentis/analyst/gui/Installer.java
index 3204b823..cb50c9a7 100644
--- a/GUI/src/main/java/cz/fidentis/analyst/gui/Installer.java
+++ b/GUI/src/main/java/cz/fidentis/analyst/gui/Installer.java
@@ -1,5 +1,6 @@
 package cz.fidentis.analyst.gui;
 
+import cz.fidentis.analyst.project.ProjectTopComp;
 import javax.swing.JOptionPane;
 import javax.swing.UIManager;
 import javax.swing.UnsupportedLookAndFeelException;
@@ -11,7 +12,24 @@ import org.openide.modules.ModuleInstall;
  * @author Radek Oslejsek
  */
 public class Installer extends ModuleInstall {
-
+    
+    private ProjectTopComp projectTopComp;
+    
+    /**
+     * Constructor with projectTopComp
+     * @param projectTopComp ProjectTopComp
+     */
+    public Installer(ProjectTopComp projectTopComp) {
+        this.projectTopComp = projectTopComp;   
+    }
+    
+    /**
+     * Default constructor
+     */
+    public Installer() {
+        //super();
+    }
+    
     @Override
     public void restored() {
         try {
@@ -40,9 +58,14 @@ public class Installer extends ModuleInstall {
     
     @Override
     public boolean closing() {
+        
+        if (projectTopComp != null) {
+            projectTopComp.saveProjectOnClose();
+        }
+        
         int answer = JOptionPane.showConfirmDialog(null,
                 "Do you really want to close the application?", "", JOptionPane.YES_NO_OPTION, JOptionPane.WARNING_MESSAGE);
-        return answer == JOptionPane.YES_OPTION;     
+        return answer == JOptionPane.YES_OPTION;
     }
 
     
diff --git a/GUI/src/main/java/cz/fidentis/analyst/project/FaceStatePanel.form b/GUI/src/main/java/cz/fidentis/analyst/project/FaceStatePanel.form
index e7dae1ad..99a03f52 100644
--- a/GUI/src/main/java/cz/fidentis/analyst/project/FaceStatePanel.form
+++ b/GUI/src/main/java/cz/fidentis/analyst/project/FaceStatePanel.form
@@ -301,6 +301,9 @@
               <ResourceString bundle="cz/fidentis/analyst/project/Bundle.properties" key="FaceStatePanel.photo.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
             </Property>
           </Properties>
+          <Events>
+            <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="photoMouseClicked"/>
+          </Events>
         </Component>
       </SubComponents>
     </Container>
diff --git a/GUI/src/main/java/cz/fidentis/analyst/project/FaceStatePanel.java b/GUI/src/main/java/cz/fidentis/analyst/project/FaceStatePanel.java
index 814b860e..1d0eb6d9 100644
--- a/GUI/src/main/java/cz/fidentis/analyst/project/FaceStatePanel.java
+++ b/GUI/src/main/java/cz/fidentis/analyst/project/FaceStatePanel.java
@@ -2,7 +2,9 @@ package cz.fidentis.analyst.project;
 
 import cz.fidentis.analyst.core.ControlPanel;
 import cz.fidentis.analyst.face.HumanFace;
+import java.awt.Dimension;
 import java.awt.Image;
+import java.awt.Toolkit;
 import java.awt.event.ActionListener;
 import java.io.IOException;
 import java.nio.file.Files;
@@ -10,6 +12,7 @@ import java.nio.file.Path;
 import java.nio.file.Paths;
 import javax.imageio.ImageIO;
 import javax.swing.ImageIcon;
+import javax.swing.JOptionPane;
 
 /**
  *
@@ -21,6 +24,7 @@ public class FaceStatePanel extends ControlPanel {
     private final ImageIcon check = new ImageIcon(FaceStatePanel.class.getClassLoader().getResource("/" + "check16x16.png"));
     //private final ImageIcon warning = new ImageIcon(FaceStatePanel.class.getClassLoader().getResource("/" + "warning16x16.png"));
     private final ImageIcon anonymousFace = new ImageIcon(FaceStatePanel.class.getClassLoader().getResource("/" + "face160x160.png"));
+    private ImageIcon previewFace = null;
     
     public static final String ICON = "head28x28.png";
     public static final String NAME = "Face State";
@@ -175,6 +179,11 @@ public class FaceStatePanel extends ControlPanel {
 
         photo.setIcon(new javax.swing.ImageIcon(getClass().getResource("/face160x160.png"))); // NOI18N
         org.openide.awt.Mnemonics.setLocalizedText(photo, org.openide.util.NbBundle.getMessage(FaceStatePanel.class, "FaceStatePanel.photo.text")); // NOI18N
+        photo.addMouseListener(new java.awt.event.MouseAdapter() {
+            public void mouseClicked(java.awt.event.MouseEvent evt) {
+                photoMouseClicked(evt);
+            }
+        });
 
         javax.swing.GroupLayout photoPanelLayout = new javax.swing.GroupLayout(photoPanel);
         photoPanel.setLayout(photoPanelLayout);
@@ -219,6 +228,14 @@ public class FaceStatePanel extends ControlPanel {
 
         filePanel.getAccessibleContext().setAccessibleName(org.openide.util.NbBundle.getMessage(FaceStatePanel.class, "FaceStatePanel.filePanel.AccessibleContext.accessibleName")); // NOI18N
     }// </editor-fold>//GEN-END:initComponents
+
+    private void photoMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_photoMouseClicked
+        
+        if (previewFace != null) {
+            JOptionPane.showMessageDialog(this.getParent(), previewFace, "Preview Image", JOptionPane.PLAIN_MESSAGE);
+        }
+       
+    }//GEN-LAST:event_photoMouseClicked
     
     /**
      * Shows face information on panel
@@ -250,11 +267,16 @@ public class FaceStatePanel extends ControlPanel {
     private ImageIcon getPhoto(HumanFace face, Path path) {
         
         ImageIcon image;
+        previewFace = null;
+        Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize();
         
         if (face != null) {
             if (face.getPreview() != null) {
+                previewFace = new ImageIcon(face.getPreview().getScaledInstance((int)screenSize.getWidth() - 100, (int)screenSize.getHeight() - 100, Image.SCALE_FAST));
+                photo.setToolTipText("Click to enlarge the image");
                 return new ImageIcon(face.getPreview().getScaledInstance(240, 160, Image.SCALE_FAST));
             }
+            photo.setToolTipText("");
             return anonymousFace;
         }
         
@@ -264,12 +286,16 @@ public class FaceStatePanel extends ControlPanel {
             Path preview = Paths.get(pathString.substring(0, pathString.lastIndexOf(".")).concat("_preview.jpg"));
                 
             try {
-                image = new ImageIcon(ImageIO.read(preview.toFile()).getScaledInstance(240, 160, Image.SCALE_FAST));
+                
+                previewFace = new ImageIcon(ImageIO.read(preview.toFile()).getScaledInstance((int)screenSize.getWidth() - 200, (int)screenSize.getHeight() - 200, Image.SCALE_FAST));
+                image = new ImageIcon(previewFace.getImage().getScaledInstance(240, 160, Image.SCALE_FAST));
+                photo.setToolTipText("Click to enlarge the image");
                 return image;
             } catch (IOException ex) {
                 //Exceptions.printStackTrace(ex);)
             }
         }
+        photo.setToolTipText("");
         return anonymousFace;
     }
     
diff --git a/GUI/src/main/java/cz/fidentis/analyst/project/ProjectPanel.form b/GUI/src/main/java/cz/fidentis/analyst/project/ProjectPanel.form
index f6842bed..f11e510f 100644
--- a/GUI/src/main/java/cz/fidentis/analyst/project/ProjectPanel.form
+++ b/GUI/src/main/java/cz/fidentis/analyst/project/ProjectPanel.form
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="UTF-8" ?>
 
-<Form version="1.5" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
+<Form version="1.9" maxVersion="1.9" type="org.netbeans.modules.form.forminfo.JPanelFormInfo">
   <AuxValues>
     <AuxValue name="FormSettings_autoResourcing" type="java.lang.Integer" value="1"/>
     <AuxValue name="FormSettings_autoSetComponentName" type="java.lang.Boolean" value="false"/>
@@ -21,9 +21,9 @@
               <Group type="103" groupAlignment="1" attributes="0">
                   <Component id="saveProjectButton" min="-2" max="-2" attributes="0"/>
                   <Group type="102" alignment="1" attributes="0">
-                      <Group type="103" groupAlignment="0" max="-2" attributes="0">
-                          <Component id="buttonsPanel" alignment="0" max="32767" attributes="0"/>
-                          <Component id="faceTableScrollPanel" alignment="0" min="-2" pref="863" max="-2" attributes="0"/>
+                      <Group type="103" groupAlignment="1" max="-2" attributes="0">
+                          <Component id="faceTableScrollPanel" pref="863" max="32767" attributes="0"/>
+                          <Component id="buttonsPanel" max="32767" attributes="0"/>
                       </Group>
                       <Group type="103" groupAlignment="0" attributes="0">
                           <Group type="102" alignment="0" attributes="0">
@@ -44,17 +44,17 @@
     <DimensionLayout dim="1">
       <Group type="103" groupAlignment="0" attributes="0">
           <Group type="102" alignment="0" attributes="0">
-              <EmptySpace min="-2" pref="12" max="-2" attributes="0"/>
               <Group type="103" groupAlignment="0" attributes="0">
-                  <Group type="102" alignment="0" attributes="0">
-                      <Component id="buttonsPanel" min="-2" max="-2" attributes="0"/>
-                      <EmptySpace min="-2" pref="6" max="-2" attributes="0"/>
+                  <Group type="102" attributes="0">
+                      <EmptySpace min="-2" pref="23" max="-2" attributes="0"/>
+                      <Component id="newProjectButton" min="-2" max="-2" attributes="0"/>
                   </Group>
                   <Group type="102" alignment="1" attributes="0">
-                      <Component id="newProjectButton" min="-2" max="-2" attributes="0"/>
-                      <EmptySpace type="separate" max="-2" attributes="0"/>
+                      <EmptySpace max="-2" attributes="0"/>
+                      <Component id="buttonsPanel" max="-2" attributes="0"/>
                   </Group>
               </Group>
+              <EmptySpace max="-2" attributes="0"/>
               <Group type="103" groupAlignment="0" attributes="0">
                   <Component id="faceTableScrollPanel" min="-2" pref="768" max="-2" attributes="0"/>
                   <Group type="102" alignment="0" attributes="0">
@@ -63,7 +63,7 @@
                       <Component id="openProjectButton" min="-2" max="-2" attributes="0"/>
                   </Group>
               </Group>
-              <EmptySpace max="32767" attributes="0"/>
+              <EmptySpace pref="12" max="32767" attributes="0"/>
           </Group>
       </Group>
     </DimensionLayout>
@@ -97,7 +97,7 @@
           </Events>
           <Constraints>
             <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
-              <GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="20" ipadY="0" insetsTop="16" insetsLeft="16" insetsBottom="13" insetsRight="4" anchor="13" weightX="0.0" weightY="0.0"/>
+              <GridBagConstraints gridX="0" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="20" ipadY="0" insetsTop="16" insetsLeft="0" insetsBottom="13" insetsRight="4" anchor="13" weightX="0.0" weightY="0.0"/>
             </Constraint>
           </Constraints>
         </Component>
@@ -174,43 +174,6 @@
             </Constraint>
           </Constraints>
         </Component>
-        <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/project/Bundle.properties" key="ProjectPanel.oneOnOneButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
-            </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="oneOnOneButtonMouseClicked"/>
-          </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="11" insetsBottom="13" insetsRight="4" anchor="18" weightX="0.0" weightY="0.0"/>
-            </Constraint>
-          </Constraints>
-        </Component>
-        <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/project/Bundle.properties" key="ProjectPanel.manyToManyButton.text" replaceFormat="org.openide.util.NbBundle.getMessage({sourceFileName}.class, &quot;{key}&quot;)"/>
-            </Property>
-          </Properties>
-          <Events>
-            <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="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="analyseButton">
           <Properties>
             <Property name="font" type="java.awt.Font" editor="org.netbeans.beaninfo.editors.FontEditor">
@@ -221,11 +184,11 @@
             </Property>
           </Properties>
           <Events>
-            <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="analyseButtonMouseClicked"/>
+            <EventHandler event="mouseClicked" listener="java.awt.event.MouseListener" parameters="java.awt.event.MouseEvent" handler="analyseFaces"/>
           </Events>
           <Constraints>
             <Constraint layoutClass="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout" value="org.netbeans.modules.form.compat2.layouts.DesignGridBagLayout$GridBagConstraintsDescription">
-              <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"/>
+              <GridBagConstraints gridX="5" gridY="0" gridWidth="1" gridHeight="1" fill="0" ipadX="0" ipadY="0" insetsTop="16" insetsLeft="75" insetsBottom="13" insetsRight="4" anchor="10" weightX="0.0" weightY="0.0"/>
             </Constraint>
           </Constraints>
         </Component>
diff --git a/GUI/src/main/java/cz/fidentis/analyst/project/ProjectPanel.java b/GUI/src/main/java/cz/fidentis/analyst/project/ProjectPanel.java
index 0a6bddff..a9807f15 100644
--- a/GUI/src/main/java/cz/fidentis/analyst/project/ProjectPanel.java
+++ b/GUI/src/main/java/cz/fidentis/analyst/project/ProjectPanel.java
@@ -14,6 +14,7 @@ import java.nio.file.Paths;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
+import java.util.prefs.Preferences;
 import java.util.stream.Collectors;
 import javax.swing.AbstractAction;
 import javax.swing.ImageIcon;
@@ -43,6 +44,8 @@ public class ProjectPanel extends JPanel {
     
     private ObjectMapper mapper = new ObjectMapper();
     
+    private Preferences userPreferences = Preferences.userNodeForPackage(Project.class); 
+    
     private FaceStatePanel faceStatePanel;
     
     private FilterPanel filterPanel;
@@ -55,7 +58,6 @@ public class ProjectPanel extends JPanel {
         project = new Project();
         
         initComponents();
-        
     }
 
     /**
@@ -74,8 +76,6 @@ public class ProjectPanel extends JPanel {
         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();
@@ -99,7 +99,7 @@ public class ProjectPanel extends JPanel {
         gridBagConstraints.gridy = 0;
         gridBagConstraints.ipadx = 20;
         gridBagConstraints.anchor = java.awt.GridBagConstraints.EAST;
-        gridBagConstraints.insets = new java.awt.Insets(16, 16, 13, 4);
+        gridBagConstraints.insets = new java.awt.Insets(16, 0, 13, 4);
         buttonsPanel.add(addButton, gridBagConstraints);
 
         removeButton.setFont(new java.awt.Font("Tahoma", 0, 12)); // NOI18N
@@ -159,46 +159,17 @@ public class ProjectPanel extends JPanel {
         gridBagConstraints.insets = new java.awt.Insets(16, 22, 13, 4);
         buttonsPanel.add(inflateButton, gridBagConstraints);
 
-        oneOnOneButton.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N
-        org.openide.awt.Mnemonics.setLocalizedText(oneOnOneButton, org.openide.util.NbBundle.getMessage(ProjectPanel.class, "ProjectPanel.oneOnOneButton.text")); // NOI18N
-        oneOnOneButton.setAlignmentX(0.5F);
-        oneOnOneButton.addMouseListener(new java.awt.event.MouseAdapter() {
-            public void mouseClicked(java.awt.event.MouseEvent evt) {
-                oneOnOneButtonMouseClicked(evt);
-            }
-        });
-        gridBagConstraints = new java.awt.GridBagConstraints();
-        gridBagConstraints.gridx = 6;
-        gridBagConstraints.gridy = 0;
-        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
-        gridBagConstraints.insets = new java.awt.Insets(16, 11, 13, 4);
-        buttonsPanel.add(oneOnOneButton, gridBagConstraints);
-
-        manyToManyButton.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N
-        org.openide.awt.Mnemonics.setLocalizedText(manyToManyButton, org.openide.util.NbBundle.getMessage(ProjectPanel.class, "ProjectPanel.manyToManyButton.text")); // NOI18N
-        manyToManyButton.addMouseListener(new java.awt.event.MouseAdapter() {
-            public void mouseClicked(java.awt.event.MouseEvent evt) {
-                manyToManyButtonMouseClicked(evt);
-            }
-        });
-        gridBagConstraints = new java.awt.GridBagConstraints();
-        gridBagConstraints.gridx = 5;
-        gridBagConstraints.gridy = 0;
-        gridBagConstraints.anchor = java.awt.GridBagConstraints.NORTHWEST;
-        gridBagConstraints.insets = new java.awt.Insets(16, 30, 13, 4);
-        buttonsPanel.add(manyToManyButton, gridBagConstraints);
-
         analyseButton.setFont(new java.awt.Font("Tahoma", 1, 12)); // NOI18N
         org.openide.awt.Mnemonics.setLocalizedText(analyseButton, org.openide.util.NbBundle.getMessage(ProjectPanel.class, "ProjectPanel.analyseButton.text")); // NOI18N
         analyseButton.addMouseListener(new java.awt.event.MouseAdapter() {
             public void mouseClicked(java.awt.event.MouseEvent evt) {
-                analyseButtonMouseClicked(evt);
+                analyseFaces(evt);
             }
         });
         gridBagConstraints = new java.awt.GridBagConstraints();
-        gridBagConstraints.gridx = 7;
+        gridBagConstraints.gridx = 5;
         gridBagConstraints.gridy = 0;
-        gridBagConstraints.insets = new java.awt.Insets(16, 11, 13, 4);
+        gridBagConstraints.insets = new java.awt.Insets(16, 75, 13, 4);
         buttonsPanel.add(analyseButton, gridBagConstraints);
 
         faceTableScrollPanel.setPreferredSize(new java.awt.Dimension(812, 750));
@@ -265,9 +236,9 @@ public class ProjectPanel extends JPanel {
                 .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING)
                     .addComponent(saveProjectButton)
                     .addGroup(layout.createSequentialGroup()
-                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
-                            .addComponent(buttonsPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
-                            .addComponent(faceTableScrollPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 863, javax.swing.GroupLayout.PREFERRED_SIZE))
+                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.TRAILING, false)
+                            .addComponent(faceTableScrollPanel, javax.swing.GroupLayout.DEFAULT_SIZE, 863, Short.MAX_VALUE)
+                            .addComponent(buttonsPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
                         .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                             .addGroup(layout.createSequentialGroup()
                                 .addGap(78, 78, 78)
@@ -280,14 +251,14 @@ public class ProjectPanel extends JPanel {
         layout.setVerticalGroup(
             layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
             .addGroup(layout.createSequentialGroup()
-                .addGap(12, 12, 12)
                 .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                     .addGroup(layout.createSequentialGroup()
-                        .addComponent(buttonsPanel, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
-                        .addGap(6, 6, 6))
+                        .addGap(23, 23, 23)
+                        .addComponent(newProjectButton))
                     .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
-                        .addComponent(newProjectButton)
-                        .addGap(18, 18, 18)))
+                        .addContainerGap()
+                        .addComponent(buttonsPanel, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)))
+                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                 .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                     .addComponent(faceTableScrollPanel, javax.swing.GroupLayout.PREFERRED_SIZE, 768, javax.swing.GroupLayout.PREFERRED_SIZE)
                     .addGroup(layout.createSequentialGroup()
@@ -333,69 +304,63 @@ public class ProjectPanel extends JPanel {
             if (evt.getClickCount() == 2) {
                 deselectAllRows();
                 model.setValueAt(true, table.getSelectedRow(), 0);
-                analyseButtonMouseClicked(evt);
+                analyseSingleFace();
             }
             checkFaceState(table.getValueAt(table.getSelectedRow(), 1).toString(), true);
 
         }
     }//GEN-LAST:event_tableMouseClicked
 
-    private void analyseButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_analyseButtonMouseClicked
+    /**
+     * Opens analysis of single selected face
+     */
+    private void analyseSingleFace() {
+        
+        String name = model.getValueAt(selectedRows.get(0), 1).toString();
+        HumanFace face = project.loadFace(name);
 
-        if (selectedRows.size() == 1) {
+        if (project.getFaceByName(name) == null) {    
+            project.addFace(face);
+        }
 
-            String name = model.getValueAt(selectedRows.get(0), 1).toString();
-            HumanFace face = project.loadFace(name);
+        createSingleFaceTab(face, name, false);
+    }
+    
+    /**
+     * Opens analysis of two selected faces - 1:1
+     */
+    private void analyseOneOnOneFaces() {
+        
+        String name1 = model.getValueAt(selectedRows.get(0), 1).toString();
+        String name2 = model.getValueAt(selectedRows.get(1), 1).toString();
 
-            if (project.getFaceByName(name) == null) {    
-                project.addFace(face);
-            }
-            
-            
-            
-            createSingleFaceTab(face, name, false);
-        } else {
-            JOptionPane.showMessageDialog(this, "Select one model");
+        HumanFace face1 = project.loadFace(name1);
+        HumanFace face2 = project.loadFace(name2);
+
+        if (project.getFaceByName(name1) == null) {
+            project.addFace(face1);
+        }
+
+        if (project.getFaceByName(name2) == null) {
+            project.addFace(face2);
         }
-    }//GEN-LAST:event_analyseButtonMouseClicked
 
-    private void manyToManyButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_manyToManyButtonMouseClicked
+        createFaceToFaceTab(face1, face2, name1 + ":" + name2, false);
+    }
+    
+    /**
+     * Opens analysis of N:N selected faces
+     */
+    private void analyseManyToManyFaces() {
+        
         List<Path> faces = selectedRows.stream()
                 .map(i -> model.getValueAt(selectedRows.get(i), 1).toString())
                 .map(s -> project.getCfg().getPathToFaceByName(s))
                 .collect(Collectors.toList());
         
-        if (faces.size() > 2) {
-            createManyToManyTab("N:N", faces);
-        } else {
-            JOptionPane.showMessageDialog(this, "Select at least three models");
-        }
-    }//GEN-LAST:event_manyToManyButtonMouseClicked
-
-    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.loadFace(name1);
-            HumanFace face2 = project.loadFace(name2);
-
-            if (project.getFaceByName(name1) == null) {
-                project.addFace(face1);
-            }
-
-            if (project.getFaceByName(name2) == null) {
-                project.addFace(face2);
-            }
-
-            createFaceToFaceTab(face1, face2, name1 + ":" + name2, false);
-        } else {
-            JOptionPane.showMessageDialog(this, "Select two models");
-        }
-    }//GEN-LAST:event_oneOnOneButtonMouseClicked
-
+        createManyToManyTab("N:N", faces);
+    }
+    
     private void inflateButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_inflateButtonMouseClicked
 
         for (int i = 0; i < model.getRowCount(); i++) {
@@ -424,6 +389,28 @@ public class ProjectPanel extends JPanel {
     private void addButtonMouseClicked(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_addButtonMouseClicked
         loadModel(false);
     }//GEN-LAST:event_addButtonMouseClicked
+
+    private void analyseFaces(java.awt.event.MouseEvent evt) {//GEN-FIRST:event_analyseFaces
+        
+        switch (selectedRows.size()) {
+            
+            case 0: 
+                JOptionPane.showMessageDialog(this, "None faces selected");
+                break;
+                
+            case 1:
+                analyseSingleFace();
+                break;
+                
+            case 2:
+                analyseOneOnOneFaces();
+                break;
+                
+            default:
+                analyseManyToManyFaces();
+                break;
+        }
+    }//GEN-LAST:event_analyseFaces
     
     /**
      * Removes selected faces (those which are checked in check boxes)
@@ -510,19 +497,20 @@ public class ProjectPanel extends JPanel {
             for (File file : files) {
                 
                 Path path = Paths.get(file.getAbsolutePath());
-                String name = path.toString().substring(path.toString().lastIndexOf(File.separatorChar) + 1, path.toString().lastIndexOf('.'));
+                String name = path.toString().substring(path.toString()
+                                .lastIndexOf(File.separatorChar) + 1, 
+                                path.toString().lastIndexOf('.'));
                 
                 if (project.addNewPath(path)) {  
                     
-                    String pathString = path.toString();
-                    Path preview = Paths.get(pathString.substring(0, pathString.lastIndexOf(".")).concat("_preview_small.png"));
-                    
+                    Path preview = path.resolveSibling(name.concat("_preview_small.jpg"));
                     model.addRowWithName(name, preview);
-                    
                     filterPanel.checkAllFacesLoaded(false);
 
                 } else {
-                    JOptionPane.showMessageDialog(this, String.format("Model with name %s is already loaded", name));
+                    JOptionPane.showMessageDialog(this, 
+                            String.format("Model %s is already loaded", 
+                            name));
                 }
             }
         }
@@ -533,17 +521,25 @@ public class ProjectPanel extends JPanel {
      */
     private void loadTabs() {
         
+        // Load Single face tabs
         project.getCfg().getSingleTabFaces().forEach(name -> {
             HumanFace face1 = project.loadFace(name);
-            createSingleFaceTab(face1, name, true);
+            if (face1 != null) {
+                createSingleFaceTab(face1, name, true);
+            }
         });
         
+        // Load Face to face tabs
         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);
-            });
+            if (face1 != null) {
+                project.getCfg().getFaceToFaceTabFaces().get(name).forEach(otherName -> {
+                    HumanFace face2 = project.loadFace(otherName);
+                    if (face2 != null) {
+                        createFaceToFaceTab(face1, face2, name + ":" + otherName, true);
+                    }
+                });
+            }
         });
         
     }
@@ -556,6 +552,7 @@ public class ProjectPanel extends JPanel {
      * @return either loaded face from project or newly opened face from file
      */
     private HumanFace getLoadedOrNewFace(String tabName, HumanFace face) {
+        
         for (FaceTab t : tabs) {
             
             if (t.hasFace(face.getShortName()) && !t.getName().equals(tabName)) {
@@ -585,10 +582,11 @@ public class ProjectPanel extends JPanel {
         
         };
         
+        // Check if face is already loaded in some other tab - if not, face 
+        // is not open from path but loaded from project
         face = getLoadedOrNewFace(name, face);
         FaceTab newTab = new FaceTab(face, null, name, tabCloseListener);
 
-        
         if (!tabs.contains(newTab)) {
             tabs.add(newTab);
             if (!loadFromFile) {
@@ -620,13 +618,14 @@ public class ProjectPanel extends JPanel {
             }
         };
         
+        // Check if face is already loaded in some other tab - if not, face 
+        // is not open from path but loaded from project
         face1 = getLoadedOrNewFace(nameOfTab, face1);
         face2 = getLoadedOrNewFace(nameOfTab, face2);
         FaceTab newTab = new FaceTab(face1, face2, nameOfTab, tabCloseListener);
         
         if (!tabs.contains(newTab)) {
 
-            
             tabs.add(newTab);
             if (!loadFromFile) {
                 project.addNewFaceToFaceTabFace(face1.getShortName(), face2.getShortName());
@@ -728,21 +727,37 @@ public class ProjectPanel extends JPanel {
      * project
      */
     public void openExistingOrNewProject() {
+        ImageIcon recentProjectImage = new ImageIcon(ProjectTopComp.class.getClassLoader().getResource("/" + "recent_project.png"));
         ImageIcon newProjectImage = new ImageIcon(ProjectTopComp.class.getClassLoader().getResource("/" + "new.png"));
         ImageIcon existingProjectImage = new ImageIcon(ProjectTopComp.class.getClassLoader().getResource("/" + "open.png"));
-        Object[] options = {newProjectImage, existingProjectImage};
+        Object[] options = {recentProjectImage, newProjectImage, existingProjectImage};
+        File recentProjectFile = getRecentProject();
+        String recentProjectInfo;
         
+        if (recentProjectFile != null) {
+            recentProjectInfo = "\n\nMost recent project is : " + recentProjectFile.getAbsolutePath();
+        } else {
+            recentProjectInfo = "\n\nCouldn't find most recent project";
+        }
         
         int choice = JOptionPane.showOptionDialog(this, 
-                "Would you like to create a new project or open an existing project?", 
+                "Would you like to create a new project or open an existing project?"
+                        + recentProjectInfo, 
                 "Select project", 
                 JOptionPane.DEFAULT_OPTION, JOptionPane.QUESTION_MESSAGE, 
                 null, 
                 options, 
-                options[0]);
+                options[1]);
         
-        if (choice == 1) {
-            openProject();
+        switch (choice) {
+            case 0:
+                openRecentProject();
+                break;
+            case 2:
+                openProject();
+                break;
+            default:
+                break;
         }
     }
     
@@ -805,39 +820,76 @@ public class ProjectPanel extends JPanel {
 
             try {
                 mapper.writeValue(file, project.getCfg());
+                userPreferences.put("pathToMostRecentProject", file.getAbsolutePath());
                 project.setSaved(true);
             } catch (IOException ex) {
                 Exceptions.printStackTrace(ex);
             }
         }
-        
    
     }
+    
     /**
-     * Opens project, which user selects in File Chooser
+     * Opens project from file
+     * @param f File
      */
-    private void openProject() {
+    private void openProjectFromFile(File f) {
         
-        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(true);    
                 loadTabs();
+                userPreferences.put("pathToMostRecentProject", f.getAbsolutePath());
                 project.setSaved(true);
             } catch (IOException ex) {
-                Exceptions.printStackTrace(ex);
+                //Exceptions.printStackTrace(ex);
+                JOptionPane.showMessageDialog(this, 
+                        "Couldn't load project", 
+                        "Loading project failed", 
+                        JOptionPane.ERROR_MESSAGE);
             }
 
-        }  
+        } 
+    }
+    
+    /**
+     * Opens most recent project (loads project from user preferences)
+     */
+    private void openRecentProject() {
+        File f = getRecentProject();
+        openProjectFromFile(f);
+    }
+    
+    /**
+     * Loads most recent project from user preferences
+     * @return File where project is saved, null if no project was found
+     */
+    public File getRecentProject() {
+        
+        String path = userPreferences.get("pathToMostRecentProject", "");
+        if (path.equals("")) {
+            return null;
+        }
+        Path f = Paths.get(path);
+        return f.toFile();
+    }
+    
+    /**
+     * 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();
+
+        openProjectFromFile(f);
     }
     
     /**
@@ -923,9 +975,7 @@ public class ProjectPanel extends JPanel {
     private javax.swing.JButton deselectAllButton;
     private javax.swing.JScrollPane faceTableScrollPanel;
     private javax.swing.JButton inflateButton;
-    private javax.swing.JButton manyToManyButton;
     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;
diff --git a/GUI/src/main/java/cz/fidentis/analyst/project/ProjectTopComp.java b/GUI/src/main/java/cz/fidentis/analyst/project/ProjectTopComp.java
index f9581d2e..06ae6879 100644
--- a/GUI/src/main/java/cz/fidentis/analyst/project/ProjectTopComp.java
+++ b/GUI/src/main/java/cz/fidentis/analyst/project/ProjectTopComp.java
@@ -3,9 +3,11 @@ package cz.fidentis.analyst.project;
 import cz.fidentis.analyst.core.ControlPanel;
 import cz.fidentis.analyst.core.OutputWindowThread;
 import cz.fidentis.analyst.core.TopControlPanel;
+import cz.fidentis.analyst.gui.Installer;
 import java.awt.event.ActionEvent;
 import java.awt.event.ActionListener;
 import javax.swing.GroupLayout;
+import javax.swing.JOptionPane;
 import javax.swing.JScrollPane;
 import javax.swing.LayoutStyle;
 import org.netbeans.api.settings.ConvertAsProperties;
@@ -107,6 +109,8 @@ public final class ProjectTopComp extends TopComponent {
         // Asks user whether he wants to create new project or open existing
         projectPanel.openExistingOrNewProject();
         
+        // Pass this class to installer so it can call method of this class on close
+        Installer inst = new Installer(this);
     }
     
         private void initComponents() {
@@ -160,9 +164,12 @@ public final class ProjectTopComp extends TopComponent {
         // TODO read your settings according to their version
     }
     
-    /*
-    @Override
-    public boolean canClose() {
+    
+    /**
+     * Checks whether currently opened project is saved or not and asks user
+     * whether he wants to save it or not on close
+     */
+    public void saveProjectOnClose() {
         if (!projectPanel.isProjectSaved()) {
 
             int save = JOptionPane.showConfirmDialog(null,
@@ -172,6 +179,5 @@ public final class ProjectTopComp extends TopComponent {
                 projectPanel.saveProject();
             }
         }
-        return super.canClose(); //To change body of generated methods, choose Tools | Templates.
-    }*/
+    }
 }
diff --git a/GUI/src/main/resources/cz/fidentis/analyst/project/Bundle.properties b/GUI/src/main/resources/cz/fidentis/analyst/project/Bundle.properties
index 0b7316a4..da9519e2 100644
--- a/GUI/src/main/resources/cz/fidentis/analyst/project/Bundle.properties
+++ b/GUI/src/main/resources/cz/fidentis/analyst/project/Bundle.properties
@@ -26,8 +26,6 @@ ProjectPanel.newProjectButton.text=
 ProjectPanel.openProjectButton.text=
 ProjectPanel.saveProjectButton.text=
 ProjectPanel.analyseButton.text=Analyse
-ProjectPanel.manyToManyButton.text=N:N
-ProjectPanel.oneOnOneButton.text=Open 1:1
 ProjectPanel.inflateButton.text=Inflate
 ProjectPanel.deselectAllButton.text=Deselect all
 ProjectPanel.selectAllButton.text=Select all
diff --git a/GUI/src/main/resources/recent_project.png b/GUI/src/main/resources/recent_project.png
new file mode 100644
index 0000000000000000000000000000000000000000..bff1ad9403bf425ba13c3246a114ab287e04fbc4
GIT binary patch
literal 3724
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

literal 0
HcmV?d00001

-- 
GitLab