Reordered gui.HookeFrame methods to group by category
authorW. Trevor King <wking@drexel.edu>
Tue, 27 Jul 2010 20:51:27 +0000 (16:51 -0400)
committerW. Trevor King <wking@drexel.edu>
Tue, 27 Jul 2010 20:51:27 +0000 (16:51 -0400)
hooke/ui/gui/__init__.py
hooke/ui/gui/dialog/points.py [new file with mode: 0644]

index 1a6492e644766d5e971d79ad83e9c305e273f0b7..f4aaaef08f4ab277bcde334498ab65b89239d9a5 100644 (file)
@@ -40,7 +40,7 @@ from . import prettyformat as prettyformat
 from . import statusbar as statusbar\r
 \r
 \r
-class HookeFrame (wx.Frame):\r
+class HookeFrame (PerspectiveHooks, wx.Frame):\r
     """The main Hooke-interface window.\r
 \r
     \r
@@ -99,15 +99,14 @@ class HookeFrame (wx.Frame):
 \r
         name = self.gui.config['active perspective']\r
         return # TODO: cleanup\r
-        menu_item = self.GetPerspectiveMenuItem(name)\r
-        if menu_item is not None:\r
-            self._on_restore_perspective(menu_item)\r
-            #TODO: config setting to remember playlists from last session\r
         self.playlists = self._c['playlists'].Playlists\r
         self._displayed_plot = None\r
         #load default list, if possible\r
         self.do_loadlist(self.GetStringFromConfig('core', 'preferences', 'playlist'))\r
 \r
+\r
+    # GUI maintenance\r
+\r
     def _setup_panels(self):\r
         client_size = self.GetClientSize()\r
         for label,p,style in [\r
@@ -227,6 +226,29 @@ class HookeFrame (wx.Frame):
         #results panel\r
         self.panelResults.results_list.OnCheckItem = self.OnResultsCheck\r
 \r
+    def _on_about(self, *args):\r
+        dialog = wx.MessageDialog(\r
+            parent=self,\r
+            message=self.gui._splash_text(),\r
+            caption='About Hooke',\r
+            style=wx.OK|wx.ICON_INFORMATION)\r
+        dialog.ShowModal()\r
+        dialog.Destroy()\r
+\r
+    def _on_close(self, *args):\r
+        # apply changes\r
+        self.gui.config['main height'] = str(self.GetSize().GetHeight())\r
+        self.gui.config['main left'] = str(self.GetPosition()[0])\r
+        self.gui.config['main top'] = str(self.GetPosition()[1])\r
+        self.gui.config['main width'] = str(self.GetSize().GetWidth())\r
+        # push changes back to Hooke.config?\r
+        self._c['manager'].UnInit()\r
+        del self._c['manager']\r
+        self.Destroy()\r
+\r
+\r
+    # Command handling\r
+\r
     def _command_by_name(self, name):\r
         cs = [c for c in self.commands if c.name == name]\r
         if len(cs) == 0:\r
@@ -286,6 +308,43 @@ class HookeFrame (wx.Frame):
         self.inqueue.put(response)\r
 \r
 \r
+\r
+    # Command-specific postprocessing\r
+\r
+    def _postprocess_get_curve(self, command, results):\r
+        """Update `self` to show the curve.\r
+        """\r
+        if not isinstance(results[-1], Success):\r
+            return  # error executing 'get curve'\r
+        assert len(results) == 2, results\r
+        curve = results[0]\r
+        print curve\r
+\r
+        selected_item = self._c['playlists']._c['tree'].GetSelection()\r
+        if self._c['playlists']._c['tree'].ItemHasChildren(selected_item):\r
+            #GetFirstChild returns a tuple\r
+            #we only need the first element\r
+            next_item = self._c['playlists']._c['tree'].GetFirstChild(selected_item)[0]\r
+        else:\r
+            next_item = self._c['playlists']._c['tree'].GetNextSibling(selected_item)\r
+            if not next_item.IsOk():\r
+                parent_item = self._c['playlists']._c['tree'].GetItemParent(selected_item)\r
+                #GetFirstChild returns a tuple\r
+                #we only need the first element\r
+                next_item = self._c['playlists']._c['tree'].GetFirstChild(parent_item)[0]\r
+        self._c['playlists']._c['tree'].SelectItem(next_item, True)\r
+        if not self._c['playlists']._c['tree'].ItemHasChildren(selected_item):\r
+            playlist = self.GetActivePlaylist()\r
+            if playlist.count > 1:\r
+                playlist.next()\r
+                self._c['status bar'].set_playlist(playlist)\r
+                self.UpdateNote()\r
+                self.UpdatePlot()\r
+\r
+\r
+\r
+    # TODO: cruft\r
+\r
     def _GetActiveFileIndex(self):\r
         lib.playlist.Playlist = self.GetActivePlaylist()\r
         #get the selected item from the tree\r
@@ -311,20 +370,6 @@ class HookeFrame (wx.Frame):
         for option in config[section]:\r
             properties.append([option, config[section][option]])\r
 \r
-    def select_command(self, _class, method, command):\r
-        self.select_plugin(plugin=command.plugin)\r
-        plugin = self.GetItemText(selected_item)\r
-        if plugin != 'core':\r
-            doc_string = eval('plugins.' + plugin + '.' + plugin + 'Commands.__doc__')\r
-        else:\r
-            doc_string = 'The module "core" contains Hooke core functionality'\r
-        if doc_string is not None:\r
-            self.panelAssistant.ChangeValue(doc_string)\r
-        else:\r
-            self.panelAssistant.ChangeValue('')\r
-        panel.propertyeditor.PropertyEditor.Initialize(self.panelProperties, properties)\r
-        self.gui.config['selected command'] = command\r
-\r
     def AddPlaylistFromFiles(self, files=[], name='Untitled'):\r
         if files:\r
             playlist = lib.playlist.Playlist(self, self.drivers)\r
@@ -405,17 +450,6 @@ class HookeFrame (wx.Frame):
                 return plotmanipulator\r
         return None\r
 \r
-    def GetPerspectiveMenuItem(self, name):\r
-        if self._perspectives.has_key(name):\r
-            perspectives_list = [key for key, value in self._perspectives.iteritems()]\r
-            perspectives_list.sort()\r
-            index = perspectives_list.index(name)\r
-            perspective_Id = ID_FirstPerspective + index\r
-            menu_item = self._c['menu bar'].FindItemById(perspective_Id)\r
-            return menu_item\r
-        else:\r
-            return None\r
-\r
     def HasPlotmanipulator(self, name):\r
         '''\r
         returns True if the plotmanipulator 'name' is loaded, False otherwise\r
@@ -425,140 +459,6 @@ class HookeFrame (wx.Frame):
                 return True\r
         return False\r
 \r
-    def _on_about(self, *args):\r
-        dialog = wx.MessageDialog(\r
-            parent=self,\r
-            message=self.gui._splash_text(),\r
-            caption='About Hooke',\r
-            style=wx.OK|wx.ICON_INFORMATION)\r
-        dialog.ShowModal()\r
-        dialog.Destroy()\r
-\r
-    def _on_close(self, *args):\r
-        # apply changes\r
-        self.gui.config['main height'] = str(self.GetSize().GetHeight())\r
-        self.gui.config['main left'] = str(self.GetPosition()[0])\r
-        self.gui.config['main top'] = str(self.GetPosition()[1])\r
-        self.gui.config['main width'] = str(self.GetSize().GetWidth())\r
-        # push changes back to Hooke.config?\r
-        self._c['manager'].UnInit()\r
-        del self._c['manager']\r
-        self.Destroy()\r
-\r
-    def _setup_perspectives(self):\r
-        """Add perspectives to menubar and _perspectives.\r
-        """\r
-        self._perspectives = {\r
-            'Default': self._c['manager'].SavePerspective(),\r
-            }\r
-        path = self.gui.config['perspective path']\r
-        if os.path.isdir(path):\r
-            files = sorted(os.listdir(path))\r
-            for fname in files:\r
-                name, extension = os.path.splitext(fname)\r
-                if extension != self.gui.config['perspective extension']:\r
-                    continue\r
-                fpath = os.path.join(path, fname)\r
-                if not os.path.isfile(fpath):\r
-                    continue\r
-                perspective = None\r
-                with open(fpath, 'rU') as f:\r
-                    perspective = f.readline()\r
-                if perspective:\r
-                    self._perspectives[name] = perspective\r
-\r
-        selected_perspective = self.gui.config['active perspective']\r
-        if not self._perspectives.has_key(selected_perspective):\r
-            self.gui.config['active perspective'] = 'Default'  # TODO: push to engine's Hooke\r
-\r
-        self._restore_perspective(selected_perspective)\r
-        self._update_perspective_menu()\r
-\r
-    def _update_perspective_menu(self):\r
-        self._c['menu bar']._c['perspective'].update(\r
-            sorted(self._perspectives.keys()),\r
-            self.gui.config['active perspective'])\r
-\r
-    def _save_perspective(self, perspective, perspective_dir, name,\r
-                          extension=None):\r
-        path = os.path.join(perspective_dir, name)\r
-        if extension != None:\r
-            path += extension\r
-        if not os.path.isdir(perspective_dir):\r
-            os.makedirs(perspective_dir)\r
-        with open(path, 'w') as f:\r
-            f.write(perspective)\r
-        self._perspectives[name] = perspective\r
-        self._restore_perspective(name)\r
-        self._update_perspective_menu()\r
-\r
-    def _delete_perspectives(self, perspective_dir, names,\r
-                             extension=None):\r
-        print 'pop', names\r
-        for name in names:\r
-            path = os.path.join(perspective_dir, name)\r
-            if extension != None:\r
-                path += extension\r
-            os.remove(path)\r
-            del(self._perspectives[name])\r
-        self._update_perspective_menu()\r
-        if self.gui.config['active perspective'] in names:\r
-            self._restore_perspective('Default')\r
-        # TODO: does this bug still apply?\r
-        # Unfortunately, there is a bug in wxWidgets for win32 (Ticket #3258\r
-        #   http://trac.wxwidgets.org/ticket/3258 \r
-        # ) that makes the radio item indicator in the menu disappear.\r
-        # The code should be fine once this issue is fixed.\r
-\r
-    def _restore_perspective(self, name):\r
-        if name != self.gui.config['active perspective']:\r
-            print 'restoring perspective:', name\r
-            self.gui.config['active perspective'] = name  # TODO: push to engine's Hooke\r
-            self._c['manager'].LoadPerspective(self._perspectives[name])\r
-            self._c['manager'].Update()\r
-            for pane in self._c['manager'].GetAllPanes():\r
-                if pane.name in self._c['menu bar']._c['view']._c.keys():\r
-                    pane.Check(pane.window.IsShown())\r
-\r
-    def _on_save_perspective(self, *args):\r
-        perspective = self._c['manager'].SavePerspective()\r
-        name = self.gui.config['active perspective']\r
-        if name == 'Default':\r
-            name = 'New perspective'\r
-        name = select_save_file(\r
-            directory=self.gui.config['perspective path'],\r
-            name=name,\r
-            extension=self.gui.config['perspective extension'],\r
-            parent=self,\r
-            message='Enter a name for the new perspective:',\r
-            caption='Save perspective')\r
-        if name == None:\r
-            return\r
-        self._save_perspective(\r
-            perspective, self.gui.config['perspective path'], name=name,\r
-            extension=self.gui.config['perspective extension'])\r
-\r
-    def _on_delete_perspective(self, *args, **kwargs):\r
-        options = sorted([p for p in self._perspectives.keys()\r
-                          if p != 'Default'])\r
-        dialog = SelectionDialog(\r
-            options=options,\r
-            message="\nPlease check the perspectives\n\nyou want to delete and click 'Delete'.\n",\r
-            button_id=wx.ID_DELETE,\r
-            selection_style='multiple',\r
-            parent=self,\r
-            title='Delete perspective(s)',\r
-            style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)\r
-        dialog.CenterOnScreen()\r
-        dialog.ShowModal()\r
-        names = [options[i] for i in dialog.selected]\r
-        dialog.Destroy()\r
-        self._delete_perspectives(\r
-            self.gui.config['perspective path'], names=names,\r
-            extension=self.gui.config['perspective extension'])\r
-\r
-    def _on_select_perspective(self, _class, method, name):\r
-        self._restore_perspective(name)\r
 \r
     def _on_dir_ctrl_left_double_click(self, event):\r
         file_path = self.panelFolders.GetPath()\r
@@ -570,54 +470,6 @@ class HookeFrame (wx.Frame):
     def _on_erase_background(self, event):\r
         event.Skip()\r
 \r
-    def _next_curve(self, *args):\r
-        """Call the `next curve` command.\r
-        """\r
-        results = self.execute_command(\r
-            command=self._command_by_name('next curve'))\r
-        if isinstance(results[-1], Success):\r
-            self.execute_command(\r
-                command=self._command_by_name('get curve'))\r
-\r
-    def _previous_curve(self, *args):\r
-        """Call the `previous curve` command.\r
-        """\r
-        self.execute_command(\r
-            command=self._command_by_name('previous curve'))\r
-        if isinstance(results[-1], Success):\r
-            self.execute_command(\r
-                command=self._command_by_name('get curve'))\r
-\r
-    def _postprocess_get_curve(self, command, results):\r
-        """Update `self` to show the curve.\r
-        """\r
-        if not isinstance(results[-1], Success):\r
-            return  # error executing 'get curve'\r
-        assert len(results) == 2, results\r
-        curve = results[0]\r
-        print curve\r
-\r
-        selected_item = self._c['playlists']._c['tree'].GetSelection()\r
-        if self._c['playlists']._c['tree'].ItemHasChildren(selected_item):\r
-            #GetFirstChild returns a tuple\r
-            #we only need the first element\r
-            next_item = self._c['playlists']._c['tree'].GetFirstChild(selected_item)[0]\r
-        else:\r
-            next_item = self._c['playlists']._c['tree'].GetNextSibling(selected_item)\r
-            if not next_item.IsOk():\r
-                parent_item = self._c['playlists']._c['tree'].GetItemParent(selected_item)\r
-                #GetFirstChild returns a tuple\r
-                #we only need the first element\r
-                next_item = self._c['playlists']._c['tree'].GetFirstChild(parent_item)[0]\r
-        self._c['playlists']._c['tree'].SelectItem(next_item, True)\r
-        if not self._c['playlists']._c['tree'].ItemHasChildren(selected_item):\r
-            playlist = self.GetActivePlaylist()\r
-            if playlist.count > 1:\r
-                playlist.next()\r
-                self._c['status bar'].set_playlist(playlist)\r
-                self.UpdateNote()\r
-                self.UpdatePlot()\r
-\r
     def _on_notebook_page_close(self, event):\r
         ctrl = event.GetEventObject()\r
         playlist_name = ctrl.GetPageText(ctrl._curpage)\r
@@ -657,148 +509,6 @@ class HookeFrame (wx.Frame):
         active_file = self.GetActiveFile()\r
         active_file.note = self.panelNote.Editor.GetValue()\r
 \r
-    def _on_panel_visibility(self, _class, method, panel_name, visible):\r
-        pane = self._c['manager'].GetPane(panel_name)\r
-        print visible\r
-        pane.Show(visible)\r
-        #if we don't do the following, the Folders pane does not resize properly on hide/show\r
-        if pane.caption == 'Folders' and pane.IsShown() and pane.IsDocked():\r
-            #folders_size = pane.GetSize()\r
-            self.panelFolders.Fit()\r
-        self._c['manager'].Update()\r
-\r
-    def _clickize(self, xvector, yvector, index):\r
-        '''\r
-        Returns a ClickedPoint() object from an index and vectors of x, y coordinates\r
-        '''\r
-        point = lib.clickedpoint.ClickedPoint()\r
-        point.index = index\r
-        point.absolute_coords = xvector[index], yvector[index]\r
-        point.find_graph_coords(xvector, yvector)\r
-        return point\r
-\r
-    def _delta(self, message='Click 2 points', block=0):\r
-        '''\r
-        Calculates the difference between two clicked points\r
-        '''\r
-        clicked_points = self._measure_N_points(N=2, message=message, block=block)\r
-\r
-        plot = self.GetDisplayedPlotCorrected()\r
-        curve = plot.curves[block]\r
-\r
-        delta = lib.delta.Delta()\r
-        delta.point1.x = clicked_points[0].graph_coords[0]\r
-        delta.point1.y = clicked_points[0].graph_coords[1]\r
-        delta.point2.x = clicked_points[1].graph_coords[0]\r
-        delta.point2.y = clicked_points[1].graph_coords[1]\r
-        delta.units.x = curve.units.x\r
-        delta.units.y = curve.units.y\r
-\r
-        return delta\r
-\r
-    def _measure_N_points(self, N, message='', block=0):\r
-        '''\r
-        General helper function for N-points measurements\r
-        By default, measurements are done on the retraction\r
-        '''\r
-        if message:\r
-            dialog = wx.MessageDialog(None, message, 'Info', wx.OK)\r
-            dialog.ShowModal()\r
-\r
-        figure = self.GetActiveFigure()\r
-\r
-        xvector = self.displayed_plot.curves[block].x\r
-        yvector = self.displayed_plot.curves[block].y\r
-\r
-        clicked_points = figure.ginput(N, timeout=-1, show_clicks=True)\r
-\r
-        points = []\r
-        for clicked_point in clicked_points:\r
-            point = lib.clickedpoint.ClickedPoint()\r
-            point.absolute_coords = clicked_point[0], clicked_point[1]\r
-            point.dest = 0\r
-            #TODO: make this optional?\r
-            #so far, the clicked point is taken, not the corresponding data point\r
-            point.find_graph_coords(xvector, yvector)\r
-            point.is_line_edge = True\r
-            point.is_marker = True\r
-            points.append(point)\r
-        return points\r
-\r
-    def do_copylog(self):\r
-        '''\r
-        Copies all files in the current playlist that have a note to the destination folder.\r
-        destination: select folder where you want the files to be copied\r
-        use_LVDT_folder: when checked, the files will be copied to a folder called 'LVDT' in the destination folder (for MFP-1D files only)\r
-        '''\r
-        playlist = self.GetActivePlaylist()\r
-        if playlist is not None:\r
-            destination = self.GetStringFromConfig('core', 'copylog', 'destination')\r
-            if not os.path.isdir(destination):\r
-                os.makedirs(destination)\r
-            for current_file in playlist.files:\r
-                if current_file.note:\r
-                    shutil.copy(current_file.filename, destination)\r
-                    if current_file.driver.filetype == 'mfp1d':\r
-                        filename = current_file.filename.replace('deflection', 'LVDT', 1)\r
-                        path, name = os.path.split(filename)\r
-                        filename = os.path.join(path, 'lvdt', name)\r
-                        use_LVDT_folder = self.GetBoolFromConfig('core', 'copylog', 'use_LVDT_folder')\r
-                        if use_LVDT_folder:\r
-                            destination = os.path.join(destination, 'LVDT')\r
-                        shutil.copy(filename, destination)\r
-\r
-    def do_plotmanipulators(self):\r
-        '''\r
-        Please select the plotmanipulators you would like to use\r
-        and define the order in which they will be applied to the data.\r
-\r
-        Click 'Execute' to apply your changes.\r
-        '''\r
-        self.UpdatePlot()\r
-\r
-    def do_preferences(self):\r
-        '''\r
-        Please set general preferences for Hooke here.\r
-        hide_curve_extension: hides the extension of the force curve files.\r
-                              not recommended for 'picoforce' files\r
-        '''\r
-        pass\r
-\r
-    def do_test(self):\r
-        '''\r
-        Use this command for testing purposes. You find do_test in hooke.py.\r
-        '''\r
-        pass\r
-\r
-    def do_version(self):\r
-        '''\r
-        VERSION\r
-        ------\r
-        Prints the current version and codename, plus library version. Useful for debugging.\r
-        '''\r
-        self.AppendToOutput('Hooke ' + __version__ + ' (' + __codename__ + ')')\r
-        self.AppendToOutput('Released on: ' + __releasedate__)\r
-        self.AppendToOutput('---')\r
-        self.AppendToOutput('Python version: ' + python_version)\r
-        self.AppendToOutput('WxPython version: ' + wx_version)\r
-        self.AppendToOutput('Matplotlib version: ' + mpl_version)\r
-        self.AppendToOutput('SciPy version: ' + scipy_version)\r
-        self.AppendToOutput('NumPy version: ' + numpy_version)\r
-        self.AppendToOutput('ConfigObj version: ' + configobj_version)\r
-        self.AppendToOutput('wxPropertyGrid version: ' + '.'.join([str(PROPGRID_MAJOR), str(PROPGRID_MINOR), str(PROPGRID_RELEASE)]))\r
-        self.AppendToOutput('---')\r
-        self.AppendToOutput('Platform: ' + str(platform.uname()))\r
-        self.AppendToOutput('******************************')\r
-        self.AppendToOutput('Loaded plugins')\r
-        self.AppendToOutput('---')\r
-\r
-        #sort the plugins into alphabetical order\r
-        plugins_list = [key for key, value in self.plugins.iteritems()]\r
-        plugins_list.sort()\r
-        for plugin in plugins_list:\r
-            self.AppendToOutput(plugin)\r
-\r
     def UpdateNote(self):\r
         #update the note for the active file\r
         active_file = self.GetActiveFile()\r
@@ -930,6 +640,176 @@ class HookeFrame (wx.Frame):
         self.Parent.DeleteFromPlaylists(playlist_name)\r
 \r
 \r
+\r
+    # Command panel interface\r
+\r
+    def select_command(self, _class, method, command):\r
+        self.select_plugin(plugin=command.plugin)\r
+        plugin = self.GetItemText(selected_item)\r
+        if plugin != 'core':\r
+            doc_string = eval('plugins.' + plugin + '.' + plugin + 'Commands.__doc__')\r
+        else:\r
+            doc_string = 'The module "core" contains Hooke core functionality'\r
+        if doc_string is not None:\r
+            self.panelAssistant.ChangeValue(doc_string)\r
+        else:\r
+            self.panelAssistant.ChangeValue('')\r
+        panel.propertyeditor.PropertyEditor.Initialize(self.panelProperties, properties)\r
+        self.gui.config['selected command'] = command\r
+\r
+\r
+\r
+    # Navbar interface\r
+\r
+    def _next_curve(self, *args):\r
+        """Call the `next curve` command.\r
+        """\r
+        results = self.execute_command(\r
+            command=self._command_by_name('next curve'))\r
+        if isinstance(results[-1], Success):\r
+            self.execute_command(\r
+                command=self._command_by_name('get curve'))\r
+\r
+    def _previous_curve(self, *args):\r
+        """Call the `previous curve` command.\r
+        """\r
+        self.execute_command(\r
+            command=self._command_by_name('previous curve'))\r
+        if isinstance(results[-1], Success):\r
+            self.execute_command(\r
+                command=self._command_by_name('get curve'))\r
+\r
+\r
+\r
+    # Panel display handling\r
+\r
+    def _on_panel_visibility(self, _class, method, panel_name, visible):\r
+        pane = self._c['manager'].GetPane(panel_name)\r
+        print visible\r
+        pane.Show(visible)\r
+        #if we don't do the following, the Folders pane does not resize properly on hide/show\r
+        if pane.caption == 'Folders' and pane.IsShown() and pane.IsDocked():\r
+            #folders_size = pane.GetSize()\r
+            self.panelFolders.Fit()\r
+        self._c['manager'].Update()\r
+\r
+    def _setup_perspectives(self):\r
+        """Add perspectives to menubar and _perspectives.\r
+        """\r
+        self._perspectives = {\r
+            'Default': self._c['manager'].SavePerspective(),\r
+            }\r
+        path = self.gui.config['perspective path']\r
+        if os.path.isdir(path):\r
+            files = sorted(os.listdir(path))\r
+            for fname in files:\r
+                name, extension = os.path.splitext(fname)\r
+                if extension != self.gui.config['perspective extension']:\r
+                    continue\r
+                fpath = os.path.join(path, fname)\r
+                if not os.path.isfile(fpath):\r
+                    continue\r
+                perspective = None\r
+                with open(fpath, 'rU') as f:\r
+                    perspective = f.readline()\r
+                if perspective:\r
+                    self._perspectives[name] = perspective\r
+\r
+        selected_perspective = self.gui.config['active perspective']\r
+        if not self._perspectives.has_key(selected_perspective):\r
+            self.gui.config['active perspective'] = 'Default'  # TODO: push to engine's Hooke\r
+\r
+        self._restore_perspective(selected_perspective)\r
+        self._update_perspective_menu()\r
+\r
+    def _update_perspective_menu(self):\r
+        self._c['menu bar']._c['perspective'].update(\r
+            sorted(self._perspectives.keys()),\r
+            self.gui.config['active perspective'])\r
+\r
+    def _save_perspective(self, perspective, perspective_dir, name,\r
+                          extension=None):\r
+        path = os.path.join(perspective_dir, name)\r
+        if extension != None:\r
+            path += extension\r
+        if not os.path.isdir(perspective_dir):\r
+            os.makedirs(perspective_dir)\r
+        with open(path, 'w') as f:\r
+            f.write(perspective)\r
+        self._perspectives[name] = perspective\r
+        self._restore_perspective(name)\r
+        self._update_perspective_menu()\r
+\r
+    def _delete_perspectives(self, perspective_dir, names,\r
+                             extension=None):\r
+        print 'pop', names\r
+        for name in names:\r
+            path = os.path.join(perspective_dir, name)\r
+            if extension != None:\r
+                path += extension\r
+            os.remove(path)\r
+            del(self._perspectives[name])\r
+        self._update_perspective_menu()\r
+        if self.gui.config['active perspective'] in names:\r
+            self._restore_perspective('Default')\r
+        # TODO: does this bug still apply?\r
+        # Unfortunately, there is a bug in wxWidgets for win32 (Ticket #3258\r
+        #   http://trac.wxwidgets.org/ticket/3258 \r
+        # ) that makes the radio item indicator in the menu disappear.\r
+        # The code should be fine once this issue is fixed.\r
+\r
+    def _restore_perspective(self, name):\r
+        if name != self.gui.config['active perspective']:\r
+            print 'restoring perspective:', name\r
+            self.gui.config['active perspective'] = name  # TODO: push to engine's Hooke\r
+            self._c['manager'].LoadPerspective(self._perspectives[name])\r
+            self._c['manager'].Update()\r
+            for pane in self._c['manager'].GetAllPanes():\r
+                if pane.name in self._c['menu bar']._c['view']._c.keys():\r
+                    pane.Check(pane.window.IsShown())\r
+\r
+    def _on_save_perspective(self, *args):\r
+        perspective = self._c['manager'].SavePerspective()\r
+        name = self.gui.config['active perspective']\r
+        if name == 'Default':\r
+            name = 'New perspective'\r
+        name = select_save_file(\r
+            directory=self.gui.config['perspective path'],\r
+            name=name,\r
+            extension=self.gui.config['perspective extension'],\r
+            parent=self,\r
+            message='Enter a name for the new perspective:',\r
+            caption='Save perspective')\r
+        if name == None:\r
+            return\r
+        self._save_perspective(\r
+            perspective, self.gui.config['perspective path'], name=name,\r
+            extension=self.gui.config['perspective extension'])\r
+\r
+    def _on_delete_perspective(self, *args, **kwargs):\r
+        options = sorted([p for p in self._perspectives.keys()\r
+                          if p != 'Default'])\r
+        dialog = SelectionDialog(\r
+            options=options,\r
+            message="\nPlease check the perspectives\n\nyou want to delete and click 'Delete'.\n",\r
+            button_id=wx.ID_DELETE,\r
+            selection_style='multiple',\r
+            parent=self,\r
+            title='Delete perspective(s)',\r
+            style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)\r
+        dialog.CenterOnScreen()\r
+        dialog.ShowModal()\r
+        names = [options[i] for i in dialog.selected]\r
+        dialog.Destroy()\r
+        self._delete_perspectives(\r
+            self.gui.config['perspective path'], names=names,\r
+            extension=self.gui.config['perspective extension'])\r
+\r
+    def _on_select_perspective(self, _class, method, name):\r
+        self._restore_perspective(name)\r
+\r
+\r
+\r
 class HookeApp (wx.App):\r
     """A :class:`wx.App` wrapper around :class:`HookeFrame`.\r
 \r
diff --git a/hooke/ui/gui/dialog/points.py b/hooke/ui/gui/dialog/points.py
new file mode 100644 (file)
index 0000000..1e86ee1
--- /dev/null
@@ -0,0 +1,33 @@
+# Copyright
+
+import wx
+
+
+def measure_N_points(hooke_frame, N, message='', block=0):
+    '''
+    General helper function for N-points measurements
+    By default, measurements are done on the retraction
+    '''
+    if message:
+        dialog = wx.MessageDialog(None, message, 'Info', wx.OK)
+        dialog.ShowModal()
+
+    figure = self.GetActiveFigure()
+
+    xvector = self.displayed_plot.curves[block].x
+    yvector = self.displayed_plot.curves[block].y
+
+    clicked_points = figure.ginput(N, timeout=-1, show_clicks=True)
+
+    points = []
+    for clicked_point in clicked_points:
+        point = lib.clickedpoint.ClickedPoint()
+        point.absolute_coords = clicked_point[0], clicked_point[1]
+        point.dest = 0
+        #TODO: make this optional?
+        #so far, the clicked point is taken, not the corresponding data point
+        point.find_graph_coords(xvector, yvector)
+        point.is_line_edge = True
+        point.is_marker = True
+        points.append(point)
+    return points