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, &quot;{key}&quot;)"/>
+                      <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.addButton.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="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, &quot;{key}&quot;)"/>
+                      <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.removeButton.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="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, &quot;{key}&quot;)"/>
+                      <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.selectAllButton.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="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, &quot;{key}&quot;)"/>
+                      <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.deselectAllButton.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="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, &quot;{key}&quot;)"/>
+                      <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.inflateButton.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="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, &quot;{key}&quot;)"/>
+                      <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.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="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, &quot;{key}&quot;)"/>
+                      <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.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="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, &quot;{key}&quot;)"/>
+                      <ResourceString bundle="cz/fidentis/analyst/core/Bundle.properties" key="ProjectTopComp.analyseButton.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"/>
+                    <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, &quot;{key}&quot;)"/>
+                </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, &quot;{key}&quot;)"/>
+                </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, &quot;{key}&quot;)"/>
+                </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, &quot;{key}&quot;)"/>
+                </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