Hooke(GUI)
[hooke.git] / hooke.py
index 6a28f12dc944509515fcaf2fc0c5408e8ed86e73..43b7510a076384579caacdd6f41132087fd3fc51 100644 (file)
--- a/hooke.py
+++ b/hooke.py
@@ -3,32 +3,39 @@
 '''\r
 HOOKE - A force spectroscopy review & analysis tool\r
 \r
-Copyright 2008 by Massimo Sandal (University of Bologna, Italy).\r
-Copyright 2010 by Rolf Schmidt (Concordia University, Canada).\r
+Copyright 2008 by Massimo Sandal (University of Bologna, Italy)\r
+Copyright 2010 by Rolf Schmidt (Concordia University, Canada)\r
 \r
 This program is released under the GNU General Public License version 2.\r
 '''\r
 \r
-import wxversion\r
 import lib.libhooke as lh\r
+import wxversion\r
 wxversion.select(lh.WX_GOOD)\r
 \r
 from configobj import ConfigObj\r
 import copy\r
 import os.path\r
 import platform\r
+import shutil\r
 import time\r
-#import wx\r
+\r
 import wx.html\r
 import wx.lib.agw.aui as aui\r
 import wx.lib.evtmgr as evtmgr\r
 import wx.propgrid as wxpg\r
 \r
+from matplotlib.ticker import FuncFormatter\r
+\r
+from configobj import __version__ as configobj_version\r
 from matplotlib import __version__ as mpl_version\r
 from numpy import __version__ as numpy_version\r
 from scipy import __version__ as scipy_version\r
 from sys import version as python_version\r
 from wx import __version__ as wx_version\r
+from wx.propgrid import PROPGRID_MAJOR\r
+from wx.propgrid import PROPGRID_MINOR\r
+from wx.propgrid import PROPGRID_RELEASE\r
 \r
 try:\r
     from agw import cubecolourdialog as CCD\r
@@ -39,9 +46,14 @@ except ImportError: # if it's not there locally, try the wxPython lib.
 lh.hookeDir = os.path.abspath(os.path.dirname(__file__))\r
 from config.config import config\r
 import drivers\r
+import lib.clickedpoint\r
+import lib.curve\r
+import lib.delta\r
 import lib.playlist\r
 import lib.plotmanipulator\r
+import lib.prettyformat\r
 import panels.commands\r
+import panels.note\r
 import panels.perspectives\r
 import panels.playlist\r
 import panels.plot\r
@@ -57,9 +69,6 @@ __codename__ = lh.HOOKE_VERSION[1]
 __releasedate__ = lh.HOOKE_VERSION[2]\r
 __release_name__ = lh.HOOKE_VERSION[1]\r
 \r
-#TODO: add general preferences to Hooke\r
-#this might be useful\r
-#ID_Config = wx.NewId()\r
 ID_About = wx.NewId()\r
 ID_Next = wx.NewId()\r
 ID_Previous = wx.NewId()\r
@@ -67,6 +76,7 @@ ID_Previous = wx.NewId()
 ID_ViewAssistant = wx.NewId()\r
 ID_ViewCommands = wx.NewId()\r
 ID_ViewFolders = wx.NewId()\r
+ID_ViewNote = wx.NewId()\r
 ID_ViewOutput = wx.NewId()\r
 ID_ViewPlaylists = wx.NewId()\r
 ID_ViewProperties = wx.NewId()\r
@@ -85,8 +95,21 @@ class Hooke(wx.App):
         self.SetAppName('Hooke')\r
         self.SetVendorName('')\r
 \r
-        windowPosition = (config['main']['left'], config['main']['top'])\r
-        windowSize = (config['main']['width'], config['main']['height'])\r
+        window_height = config['main']['height']\r
+        window_left= config['main']['left']\r
+        window_top = config['main']['top']\r
+        window_width = config['main']['width']\r
+\r
+        #sometimes, the ini file gets confused and sets 'left'\r
+        #and 'top' to large negative numbers\r
+        #let's catch and fix this\r
+        #keep small negative numbers, the user might want those\r
+        if window_left < -window_width:\r
+            window_left = 0\r
+        if window_top < -window_height:\r
+            window_top = 0\r
+        window_position = (window_left, window_top)\r
+        window_size = (window_width, window_height)\r
 \r
         #setup the splashscreen\r
         if config['splashscreen']['show']:\r
@@ -127,7 +150,7 @@ class Hooke(wx.App):
         def make_command_class(*bases):\r
             #create metaclass with plugins and plotmanipulators\r
             return type(HookeFrame)("HookeFramePlugged", bases + (HookeFrame,), {})\r
-        frame = make_command_class(*plugin_objects)(parent=None, id=wx.ID_ANY, title='Hooke', pos=windowPosition, size=windowSize)\r
+        frame = make_command_class(*plugin_objects)(parent=None, id=wx.ID_ANY, title='Hooke', pos=window_position, size=window_size)\r
         frame.Show(True)\r
         self.SetTopWindow(frame)\r
 \r
@@ -154,6 +177,8 @@ class HookeFrame(wx.Frame):
         self.plotmanipulators = []\r
         #self.plugins contains: {the name of the plugin: [caption, function]}\r
         self.plugins = {}\r
+        #self.results_str contains the type of results we want to display\r
+        self.results_str = 'wlc'\r
 \r
         #tell FrameManager to manage this frame\r
         self._mgr = aui.AuiManager()\r
@@ -174,48 +199,6 @@ class HookeFrame(wx.Frame):
         # see the end up FrameManager::Update() for the test\r
         # code. For now, just hard code a frame minimum size\r
         self.SetMinSize(wx.Size(500, 500))\r
-        #create panels here\r
-        self.panelAssistant = self.CreatePanelAssistant()\r
-        self.panelCommands = self.CreatePanelCommands()\r
-        self.panelFolders = self.CreatePanelFolders()\r
-        self.panelPlaylists = self.CreatePanelPlaylists()\r
-        self.panelProperties = self.CreatePanelProperties()\r
-        self.panelOutput = self.CreatePanelOutput()\r
-        self.panelResults = self.CreatePanelResults()\r
-        self.plotNotebook = self.CreateNotebook()\r
-        #self.textCtrlCommandLine=self.CreateCommandLine()\r
-\r
-        # add panes\r
-        self._mgr.AddPane(self.panelFolders, aui.AuiPaneInfo().Name('Folders').Caption('Folders').Left().CloseButton(True).MaximizeButton(False))\r
-        self._mgr.AddPane(self.panelPlaylists, aui.AuiPaneInfo().Name('Playlists').Caption('Playlists').Left().CloseButton(True).MaximizeButton(False))\r
-        self._mgr.AddPane(self.plotNotebook, aui.AuiPaneInfo().Name('Plots').CenterPane().PaneBorder(False))\r
-        self._mgr.AddPane(self.panelCommands, aui.AuiPaneInfo().Name('Commands').Caption('Settings and commands').Right().CloseButton(True).MaximizeButton(False))\r
-        self._mgr.AddPane(self.panelProperties, aui.AuiPaneInfo().Name('Properties').Caption('Properties').Right().CloseButton(True).MaximizeButton(False))\r
-        self._mgr.AddPane(self.panelAssistant, aui.AuiPaneInfo().Name('Assistant').Caption('Assistant').Right().CloseButton(True).MaximizeButton(False))\r
-        self._mgr.AddPane(self.panelOutput, aui.AuiPaneInfo().Name('Output').Caption('Output').Bottom().CloseButton(True).MaximizeButton(False))\r
-        self._mgr.AddPane(self.panelResults, aui.AuiPaneInfo().Name('Results').Caption('Results').Bottom().CloseButton(True).MaximizeButton(False))\r
-        #self._mgr.AddPane(self.textCtrlCommandLine, aui.AuiPaneInfo().Name('CommandLine').CaptionVisible(False).Fixed().Bottom().Layer(2).CloseButton(False).MaximizeButton(False))\r
-        #self._mgr.AddPane(panelBottom, aui.AuiPaneInfo().Name("panelCommandLine").Bottom().Position(1).CloseButton(False).MaximizeButton(False))\r
-\r
-        # add the toolbars to the manager\r
-        #self.toolbar=self.CreateToolBar()\r
-        self.toolbarNavigation=self.CreateToolBarNavigation()\r
-        #self._mgr.AddPane(self.toolbar, aui.AuiPaneInfo().Name('toolbar').Caption('Toolbar').ToolbarPane().Top().Layer(1).Row(1).LeftDockable(False).RightDockable(False))\r
-        self._mgr.AddPane(self.toolbarNavigation, aui.AuiPaneInfo().Name('toolbarNavigation').Caption('Navigation').ToolbarPane().Top().Layer(1).Row(1).LeftDockable(False).RightDockable(False))\r
-        # "commit" all changes made to FrameManager\r
-        self._mgr.Update()\r
-        #create the menubar after the panes so that the default perspective\r
-        #is created with all panes open\r
-        self.CreateMenuBar()\r
-        self.statusbar = self.CreateStatusbar()\r
-        self._BindEvents()\r
-\r
-        name = self.config['perspectives']['active']\r
-        menu_item = self.GetPerspectiveMenuItem(name)\r
-        if menu_item is not None:\r
-            self.OnRestorePerspective(menu_item)\r
-            #TODO: config setting to remember playlists from last session\r
-        self.playlists = self.panelPlaylists.Playlists\r
         #define the list of active drivers\r
         self.drivers = []\r
         for driver in self.config['drivers']:\r
@@ -238,6 +221,8 @@ class HookeFrame(wx.Frame):
         plugin_config = ConfigObj(ini_path)\r
         #self.config.merge(plugin_config)\r
         self.configs['core'] = plugin_config\r
+        #existing_commands contains: {command: plugin}\r
+        existing_commands = {}\r
         #make sure we execute _plug_init() for every command line plugin we import\r
         for plugin in self.config['plugins']:\r
             if self.config['plugins'][plugin]:\r
@@ -258,9 +243,18 @@ class HookeFrame(wx.Frame):
                         #add to plugins\r
                         commands = eval('dir(module.' + plugin+ '.' + plugin + 'Commands)')\r
                         #keep only commands (ie names that start with 'do_')\r
-                        #TODO: check for existing commands and warn the user!\r
                         commands = [command for command in commands if command.startswith('do_')]\r
                         if commands:\r
+                            for command in commands:\r
+                                if existing_commands.has_key(command):\r
+                                    message_str = 'Adding "' + command + '" in plugin "' + plugin + '".\n\n'\r
+                                    message_str += '"' + command + '" already exists in "' + str(existing_commands[command]) + '".\n\n'\r
+                                    message_str += 'Only "' + command + '" in "' + str(existing_commands[command]) + '" will work.\n\n'\r
+                                    message_str += 'Please rename one of the commands in the source code and restart Hooke or disable one of the plugins.'\r
+                                    dialog = wx.MessageDialog(self, message_str, 'Warning', wx.OK|wx.ICON_WARNING|wx.CENTER)\r
+                                    dialog.ShowModal()\r
+                                    dialog.Destroy()\r
+                                existing_commands[command] = plugin\r
                             self.plugins[plugin] = commands\r
                         try:\r
                             #initialize the plugin\r
@@ -269,19 +263,62 @@ class HookeFrame(wx.Frame):
                             pass\r
                     except ImportError:\r
                         pass\r
-        #initialize the commands tree\r
+        #add commands from hooke.py i.e. 'core' commands\r
         commands = dir(HookeFrame)\r
         commands = [command for command in commands if command.startswith('do_')]\r
         if commands:\r
             self.plugins['core'] = commands\r
+        #create panels here\r
+        self.panelAssistant = self.CreatePanelAssistant()\r
+        self.panelCommands = self.CreatePanelCommands()\r
+        self.panelFolders = self.CreatePanelFolders()\r
+        self.panelPlaylists = self.CreatePanelPlaylists()\r
+        self.panelProperties = self.CreatePanelProperties()\r
+        self.panelNote = self.CreatePanelNote()\r
+        self.panelOutput = self.CreatePanelOutput()\r
+        self.panelResults = self.CreatePanelResults()\r
+        self.plotNotebook = self.CreateNotebook()\r
+\r
+        # add panes\r
+        self._mgr.AddPane(self.panelFolders, aui.AuiPaneInfo().Name('Folders').Caption('Folders').Left().CloseButton(True).MaximizeButton(False))\r
+        self._mgr.AddPane(self.panelPlaylists, aui.AuiPaneInfo().Name('Playlists').Caption('Playlists').Left().CloseButton(True).MaximizeButton(False))\r
+        self._mgr.AddPane(self.panelNote, aui.AuiPaneInfo().Name('Note').Caption('Note').Left().CloseButton(True).MaximizeButton(False))\r
+        self._mgr.AddPane(self.plotNotebook, aui.AuiPaneInfo().Name('Plots').CenterPane().PaneBorder(False))\r
+        self._mgr.AddPane(self.panelCommands, aui.AuiPaneInfo().Name('Commands').Caption('Settings and commands').Right().CloseButton(True).MaximizeButton(False))\r
+        self._mgr.AddPane(self.panelProperties, aui.AuiPaneInfo().Name('Properties').Caption('Properties').Right().CloseButton(True).MaximizeButton(False))\r
+        self._mgr.AddPane(self.panelAssistant, aui.AuiPaneInfo().Name('Assistant').Caption('Assistant').Right().CloseButton(True).MaximizeButton(False))\r
+        self._mgr.AddPane(self.panelOutput, aui.AuiPaneInfo().Name('Output').Caption('Output').Bottom().CloseButton(True).MaximizeButton(False))\r
+        self._mgr.AddPane(self.panelResults, aui.AuiPaneInfo().Name('Results').Caption('Results').Bottom().CloseButton(True).MaximizeButton(False))\r
+        #self._mgr.AddPane(self.textCtrlCommandLine, aui.AuiPaneInfo().Name('CommandLine').CaptionVisible(False).Fixed().Bottom().Layer(2).CloseButton(False).MaximizeButton(False))\r
+        #self._mgr.AddPane(panelBottom, aui.AuiPaneInfo().Name("panelCommandLine").Bottom().Position(1).CloseButton(False).MaximizeButton(False))\r
+\r
+        # add the toolbars to the manager\r
+        #self.toolbar=self.CreateToolBar()\r
+        self.toolbarNavigation=self.CreateToolBarNavigation()\r
+        #self._mgr.AddPane(self.toolbar, aui.AuiPaneInfo().Name('toolbar').Caption('Toolbar').ToolbarPane().Top().Layer(1).Row(1).LeftDockable(False).RightDockable(False))\r
+        self._mgr.AddPane(self.toolbarNavigation, aui.AuiPaneInfo().Name('toolbarNavigation').Caption('Navigation').ToolbarPane().Top().Layer(1).Row(1).LeftDockable(False).RightDockable(False))\r
+        # "commit" all changes made to FrameManager\r
+        self._mgr.Update()\r
+        #create the menubar after the panes so that the default perspective\r
+        #is created with all panes open\r
+        self.CreateMenuBar()\r
+        self.statusbar = self.CreateStatusbar()\r
+        self._BindEvents()\r
+\r
+        name = self.config['perspectives']['active']\r
+        menu_item = self.GetPerspectiveMenuItem(name)\r
+        if menu_item is not None:\r
+            self.OnRestorePerspective(menu_item)\r
+            #TODO: config setting to remember playlists from last session\r
+        self.playlists = self.panelPlaylists.Playlists\r
+        #initialize the commands tree\r
         self.panelCommands.Initialize(self.plugins)\r
         for command in dir(self):\r
             if command.startswith('plotmanip_'):\r
                 self.plotmanipulators.append(lib.plotmanipulator.Plotmanipulator(method=getattr(self, command), command=command))\r
 \r
         #load default list, if possible\r
-        self.do_loadlist(self.config['core']['list'])\r
-        #self.do_loadlist()\r
+        self.do_loadlist(self.GetStringFromConfig('core', 'preferences', 'playlist'))\r
 \r
     def _BindEvents(self):\r
         #TODO: figure out if we can use the eventManager for menu ranges\r
@@ -317,6 +354,7 @@ class HookeFrame(wx.Frame):
         evtmgr.eventManager.Register(self.OnExecute, wx.EVT_BUTTON, self.panelCommands.ExecuteButton)\r
         evtmgr.eventManager.Register(self.OnTreeCtrlCommandsSelectionChanged, wx.EVT_TREE_SEL_CHANGED, self.panelCommands.CommandsTree)\r
         evtmgr.eventManager.Register(self.OnTreeCtrlItemActivated, wx.EVT_TREE_ITEM_ACTIVATED, self.panelCommands.CommandsTree)\r
+        evtmgr.eventManager.Register(self.OnUpdateNote, wx.EVT_BUTTON, self.panelNote.UpdateButton)\r
         #property editor\r
         self.panelProperties.pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChanged)\r
         #results panel\r
@@ -366,6 +404,8 @@ class HookeFrame(wx.Frame):
                     self.MenuBar.FindItemById(ID_ViewPlaylists).Check(pane.window.IsShown())\r
                 if pane.name == 'Commands':\r
                     self.MenuBar.FindItemById(ID_ViewCommands).Check(pane.window.IsShown())\r
+                if pane.name == 'Note':\r
+                    self.MenuBar.FindItemById(ID_ViewNote).Check(pane.window.IsShown())\r
                 if pane.name == 'Properties':\r
                     self.MenuBar.FindItemById(ID_ViewProperties).Check(pane.window.IsShown())\r
                 if pane.name == 'Output':\r
@@ -390,6 +430,8 @@ class HookeFrame(wx.Frame):
         #commands tree\r
         evtmgr.eventManager.DeregisterListener(self.OnExecute)\r
         evtmgr.eventManager.DeregisterListener(self.OnTreeCtrlCommandsSelectionChanged)\r
+        evtmgr.eventManager.DeregisterListener(self.OnTreeCtrlItemActivated)\r
+        evtmgr.eventManager.DeregisterListener(self.OnUpdateNote)\r
 \r
     def AddPlaylist(self, playlist=None, name='Untitled'):\r
         if playlist and playlist.count > 0:\r
@@ -414,10 +456,11 @@ class HookeFrame(wx.Frame):
             playlist_root = self.panelPlaylists.PlaylistsTree.AppendItem(tree_root, playlist.name, 0)\r
             #add all files to the Playlist tree\r
 #            files = {}\r
+            hide_curve_extension = self.GetBoolFromConfig('core', 'preferences', 'hide_curve_extension')\r
             for index, file_to_add in enumerate(playlist.files):\r
-                #TODO: optionally remove the extension from the name of the curve\r
-                #item_text, extension = os.path.splitext(curve.name)\r
-                #curve_ID = self.panelPlaylists.PlaylistsTree.AppendItem(playlist_root, item_text, 1)\r
+                #optionally remove the extension from the name of the curve\r
+                if hide_curve_extension:\r
+                    file_to_add.name = lh.remove_extension(file_to_add.name)\r
                 file_ID = self.panelPlaylists.PlaylistsTree.AppendItem(playlist_root, file_to_add.name, 1)\r
                 if index == playlist.index:\r
                     self.panelPlaylists.PlaylistsTree.SelectItem(file_ID)\r
@@ -431,6 +474,7 @@ class HookeFrame(wx.Frame):
             #self.playlists[playlist.name] = [playlist, figure]\r
             self.panelPlaylists.PlaylistsTree.Expand(playlist_root)\r
             self.statusbar.SetStatusText(playlist.get_status_string(), 0)\r
+            self.UpdateNote()\r
             self.UpdatePlot()\r
 \r
     def AppendToOutput(self, text):\r
@@ -438,11 +482,22 @@ class HookeFrame(wx.Frame):
 \r
     def AppliesPlotmanipulator(self, name):\r
         '''\r
-        returns True if the plotmanipulator 'name' is applied, False otherwise\r
+        Returns True if the plotmanipulator 'name' is applied, False otherwise\r
         name does not contain 'plotmanip_', just the name of the plotmanipulator (e.g. 'flatten')\r
         '''\r
         return self.GetBoolFromConfig('core', 'plotmanipulators', name)\r
 \r
+    def ApplyPlotmanipulators(self, plot, plot_file):\r
+        '''\r
+        Apply all active plotmanipulators.\r
+        '''\r
+        if plot is not None and plot_file is not None:\r
+            manipulated_plot = copy.deepcopy(plot)\r
+            for plotmanipulator in self.plotmanipulators:\r
+                if self.GetBoolFromConfig('core', 'plotmanipulators', plotmanipulator.name):\r
+                    manipulated_plot = plotmanipulator.method(manipulated_plot, plot_file)\r
+            return manipulated_plot\r
+\r
     def CreateApplicationIcon(self):\r
         iconFile = 'resources' + os.sep + 'microscope.ico'\r
         icon = wx.Icon(iconFile, wx.BITMAP_TYPE_ICO)\r
@@ -464,9 +519,12 @@ class HookeFrame(wx.Frame):
         filters = self.config['folders']['filters']\r
         index = self.config['folders'].as_int('filterindex')\r
         #set initial directory\r
-        folder = self.config['core']['workdir']\r
+        folder = self.GetStringFromConfig('core', 'preferences', 'workdir')\r
         return wx.GenericDirCtrl(self, -1, dir=folder, size=(200, 250), style=wx.DIRCTRL_SHOW_FILTERS, filter=filters, defaultFilter=index)\r
 \r
+    def CreatePanelNote(self):\r
+        return panels.note.Note(self)\r
+\r
     def CreatePanelOutput(self):\r
         return wx.TextCtrl(self, -1, '', wx.Point(0, 0), wx.Size(150, 90), wx.NO_BORDER|wx.TE_MULTILINE)\r
 \r
@@ -513,8 +571,8 @@ class HookeFrame(wx.Frame):
         view_menu.AppendCheckItem(ID_ViewAssistant, 'Assistant\tF9')\r
         view_menu.AppendCheckItem(ID_ViewResults, 'Results\tF10')\r
         view_menu.AppendCheckItem(ID_ViewOutput, 'Output\tF11')\r
+        view_menu.AppendCheckItem(ID_ViewNote, 'Note\tF12')\r
         #perspectives\r
-#        perspectives_menu = self.CreatePerspectivesMenu()\r
         perspectives_menu = wx.Menu()\r
 \r
         #help\r
@@ -522,7 +580,6 @@ class HookeFrame(wx.Frame):
         help_menu.Append(wx.ID_ABOUT, 'About Hooke')\r
         #put it all together\r
         menu_bar.Append(file_menu, 'File')\r
-#        menu_bar.Append(edit_menu, 'Edit')\r
         menu_bar.Append(view_menu, 'View')\r
         menu_bar.Append(perspectives_menu, "Perspectives")\r
         self.UpdatePerspectivesMenu()\r
@@ -611,8 +668,8 @@ class HookeFrame(wx.Frame):
 \r
     def GetDisplayedPlot(self):\r
         plot = copy.deepcopy(self.displayed_plot)\r
-        plot.curves = []\r
-        plot.curves = copy.deepcopy(plot.curves)\r
+        #plot.curves = []\r
+        #plot.curves = copy.deepcopy(plot.curves)\r
         return plot\r
 \r
     def GetDisplayedPlotCorrected(self):\r
@@ -702,6 +759,15 @@ class HookeFrame(wx.Frame):
             return config[section][key]['value']\r
         return None\r
 \r
+    def GetPlotmanipulator(self, name):\r
+        '''\r
+        Returns a plot manipulator function from its name\r
+        '''\r
+        for plotmanipulator in self.plotmanipulators:\r
+            if plotmanipulator.name == name:\r
+                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
@@ -807,6 +873,7 @@ class HookeFrame(wx.Frame):
             if playlist.count > 1:\r
                 playlist.next()\r
                 self.statusbar.SetStatusText(playlist.get_status_string(), 0)\r
+                self.UpdateNote()\r
                 self.UpdatePlot()\r
 \r
     def OnNotebookPageClose(self, event):\r
@@ -839,6 +906,7 @@ class HookeFrame(wx.Frame):
                     playlist = self.GetActivePlaylist()\r
                     playlist.index = index\r
                     self.statusbar.SetStatusText(playlist.get_status_string(), 0)\r
+                    self.UpdateNote()\r
                     self.UpdatePlot()\r
             #if you uncomment the following line, the tree will collapse/expand as well\r
             #event.Skip()\r
@@ -882,6 +950,7 @@ class HookeFrame(wx.Frame):
         if playlist.count > 1:\r
             playlist.previous()\r
             self.statusbar.SetStatusText(playlist.get_status_string(), 0)\r
+            self.UpdateNote()\r
             self.UpdatePlot()\r
 \r
     def OnPropGridChanged (self, event):\r
@@ -900,38 +969,18 @@ class HookeFrame(wx.Frame):
     def OnRestorePerspective(self, event):\r
         name = self.MenuBar.FindItemById(event.GetId()).GetLabel()\r
         self._RestorePerspective(name)\r
-#        self._mgr.LoadPerspective(self._perspectives[name])\r
-#        self.config['perspectives']['active'] = name\r
-#        self._mgr.Update()\r
-#        all_panes = self._mgr.GetAllPanes()\r
-#        for pane in all_panes:\r
-#            if not pane.name.startswith('toolbar'):\r
-#                if pane.name == 'Assistant':\r
-#                    self.MenuBar.FindItemById(ID_ViewAssistant).Check(pane.window.IsShown())\r
-#                if pane.name == 'Folders':\r
-#                    self.MenuBar.FindItemById(ID_ViewFolders).Check(pane.window.IsShown())\r
-#                if pane.name == 'Playlists':\r
-#                    self.MenuBar.FindItemById(ID_ViewPlaylists).Check(pane.window.IsShown())\r
-#                if pane.name == 'Commands':\r
-#                    self.MenuBar.FindItemById(ID_ViewCommands).Check(pane.window.IsShown())\r
-#                if pane.name == 'Properties':\r
-#                    self.MenuBar.FindItemById(ID_ViewProperties).Check(pane.window.IsShown())\r
-#                if pane.name == 'Output':\r
-#                    self.MenuBar.FindItemById(ID_ViewOutput).Check(pane.window.IsShown())\r
-#                if pane.name == 'Results':\r
-#                    self.MenuBar.FindItemById(ID_ViewResults).Check(pane.window.IsShown())\r
 \r
     def OnResultsCheck(self, index, flag):\r
-        #TODO: fix for multiple results\r
         results = self.GetActivePlot().results\r
-        fit_function_str = self.GetStringFromConfig('results', 'show_results', 'fit_function')\r
-        results[fit_function_str].results[index].visible = flag\r
-        self.UpdatePlot()\r
+        if results.has_key(self.results_str):\r
+            results[self.results_str].results[index].visible = flag\r
+            results[self.results_str].update()\r
+            self.UpdatePlot()\r
 \r
     def OnSavePerspective(self, event):\r
 \r
         def nameExists(name):\r
-            menu_position = self.MenuBar.FindMenu('Perspectives') \r
+            menu_position = self.MenuBar.FindMenu('Perspectives')\r
             menu = self.MenuBar.GetMenu(menu_position)\r
             for item in menu.GetMenuItems():\r
                 if item.GetText() == name:\r
@@ -1032,6 +1081,13 @@ class HookeFrame(wx.Frame):
     def OnTreeCtrlItemActivated(self, event):\r
         self.OnExecute(event)\r
 \r
+    def OnUpdateNote(self, event):\r
+        '''\r
+        Saves the note to the active file.\r
+        '''\r
+        active_file = self.GetActiveFile()\r
+        active_file.note = self.panelNote.Editor.GetValue()\r
+\r
     def OnView(self, event):\r
         menu_id = event.GetId()\r
         menu_item = self.MenuBar.FindItemById(menu_id)\r
@@ -1045,12 +1101,41 @@ class HookeFrame(wx.Frame):
             self.panelFolders.Fit()\r
         self._mgr.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', whatset=lh.RETRACTION):\r
+        '''\r
+        Calculates the difference between two clicked points\r
+        '''\r
+        clicked_points = self._measure_N_points(N=2, message=message, whatset=whatset)\r
+\r
+        plot = self.GetDisplayedPlotCorrected()\r
+        curve = plot.curves[whatset]\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='', whatset=lh.RETRACTION):\r
         '''\r
         General helper function for N-points measurements\r
         By default, measurements are done on the retraction\r
         '''\r
-        if message != '':\r
+        if message:\r
             dialog = wx.MessageDialog(None, message, 'Info', wx.OK)\r
             dialog.ShowModal()\r
 \r
@@ -1063,7 +1148,7 @@ class HookeFrame(wx.Frame):
 \r
         points = []\r
         for clicked_point in clicked_points:\r
-            point = lh.ClickedPoint()\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
@@ -1074,45 +1159,28 @@ class HookeFrame(wx.Frame):
             points.append(point)\r
         return points\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 = lh.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, color='black', message='Click 2 points', show=True, whatset=1):\r
+    def do_copylog(self):\r
         '''\r
-        calculates the difference between two clicked points\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
-        clicked_points = self._measure_N_points(N=2, message=message, whatset=whatset)\r
-        dx = abs(clicked_points[0].graph_coords[0] - clicked_points[1].graph_coords[0])\r
-        dy = abs(clicked_points[0].graph_coords[1] - clicked_points[1].graph_coords[1])\r
-\r
-        plot = self.GetDisplayedPlotCorrected()\r
-\r
-        curve = plot.curves[whatset]\r
-        unitx = curve.units.x\r
-        unity = curve.units.y\r
-\r
-        #TODO: move this to clicked_points?\r
-        if show:\r
-            for point in clicked_points:\r
-                points = copy.deepcopy(curve)\r
-                points.x = point.graph_coords[0]\r
-                points.y = point.graph_coords[1]\r
-\r
-                points.color = color\r
-                points.size = 20\r
-                points.style = 'scatter'\r
-                plot.curves.append(points)\r
-\r
-        self.UpdatePlot(plot)\r
-\r
-        return dx, unitx, dy, unity\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
@@ -1123,8 +1191,18 @@ class HookeFrame(wx.Frame):
         '''\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
-        self.AppendToOutput(self.config['perspectives']['active'])\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
@@ -1141,11 +1219,25 @@ class HookeFrame(wx.Frame):
         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
-        #TODO: adapt to 'new' config\r
-        #self.AppendToOutput('---')\r
-        #self.AppendToOutput('Loaded plugins:', self.config['loaded_plugins'])\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
+        if active_file is not None:\r
+            self.panelNote.Editor.SetValue(active_file.note)\r
 \r
     def UpdatePerspectivesMenu(self):\r
         #add perspectives to menubar and _perspectives\r
@@ -1159,7 +1251,7 @@ class HookeFrame(wx.Frame):
                     perspectiveFile = open(filename, 'rU')\r
                     perspective = perspectiveFile.readline()\r
                     perspectiveFile.close()\r
-                    if perspective != '':\r
+                    if perspective:\r
                         name, extension = os.path.splitext(perspectiveFilename)\r
                         if extension == '.txt':\r
                             self._perspectives[name] = perspective\r
@@ -1179,7 +1271,7 @@ class HookeFrame(wx.Frame):
         perspectives_list.sort()\r
 \r
         #get the Perspectives menu\r
-        menu_position = self.MenuBar.FindMenu('Perspectives') \r
+        menu_position = self.MenuBar.FindMenu('Perspectives')\r
         menu = self.MenuBar.GetMenu(menu_position)\r
         #delete all menu items\r
         for item in menu.GetMenuItems():\r
@@ -1200,32 +1292,52 @@ class HookeFrame(wx.Frame):
         if playlist is not None:\r
             if playlist.index >= 0:\r
                 self.statusbar.SetStatusText(playlist.get_status_string(), 0)\r
+                self.UpdateNote()\r
                 self.UpdatePlot()\r
 \r
     def UpdatePlot(self, plot=None):\r
 \r
-        def add_to_plot(curve):\r
+        def add_to_plot(curve, set_scale=True):\r
             if curve.visible and curve.x and curve.y:\r
+                #get the index of the subplot to use as destination\r
                 destination = (curve.destination.column - 1) * number_of_rows + curve.destination.row - 1\r
+                #set all parameters for the plot\r
                 axes_list[destination].set_title(curve.title)\r
-                axes_list[destination].set_xlabel(curve.units.x)\r
-                axes_list[destination].set_ylabel(curve.units.y)\r
+                if set_scale:\r
+                    axes_list[destination].set_xlabel(curve.prefix.x + curve.units.x)\r
+                    axes_list[destination].set_ylabel(curve.prefix.y + curve.units.y)\r
+                    #set the formatting details for the scale\r
+                    formatter_x = lib.curve.PrefixFormatter(curve.decimals.x, curve.prefix.x, use_zero)\r
+                    formatter_y = lib.curve.PrefixFormatter(curve.decimals.y, curve.prefix.y, use_zero)\r
+                    axes_list[destination].xaxis.set_major_formatter(formatter_x)\r
+                    axes_list[destination].yaxis.set_major_formatter(formatter_y)\r
                 if curve.style == 'plot':\r
-                    axes_list[destination].plot(curve.x, curve.y, color=curve.color, label=curve.label, zorder=1)\r
+                    axes_list[destination].plot(curve.x, curve.y, color=curve.color, label=curve.label, lw=curve.linewidth, zorder=1)\r
                 if curve.style == 'scatter':\r
                     axes_list[destination].scatter(curve.x, curve.y, color=curve.color, label=curve.label, s=curve.size, zorder=2)\r
+                #add the legend if necessary\r
+                if curve.legend:\r
+                    axes_list[destination].legend()\r
 \r
         if plot is None:\r
             active_file = self.GetActiveFile()\r
             if not active_file.driver:\r
+                #the first time we identify a file, the following need to be set\r
                 active_file.identify(self.drivers)\r
+                for curve in active_file.plot.curves:\r
+                    curve.decimals.x = self.GetIntFromConfig('core', 'preferences', 'x_decimals')\r
+                    curve.decimals.y = self.GetIntFromConfig('core', 'preferences', 'y_decimals')\r
+                    curve.legend = self.GetBoolFromConfig('core', 'preferences', 'legend')\r
+                    curve.prefix.x = self.GetStringFromConfig('core', 'preferences', 'x_prefix')\r
+                    curve.prefix.y = self.GetStringFromConfig('core', 'preferences', 'y_prefix')\r
+            if active_file.driver is None:\r
+                self.AppendToOutput('Invalid file: ' + active_file.filename)\r
+                return\r
             self.displayed_plot = copy.deepcopy(active_file.plot)\r
             #add raw curves to plot\r
             self.displayed_plot.raw_curves = copy.deepcopy(self.displayed_plot.curves)\r
-            #apply all active plotmanipulators and add the 'manipulated' data\r
-            for plotmanipulator in self.plotmanipulators:\r
-                if self.GetBoolFromConfig('core', 'plotmanipulators', plotmanipulator.name):\r
-                    self.displayed_plot = plotmanipulator.method(self.displayed_plot, active_file)\r
+            #apply all active plotmanipulators\r
+            self.displayed_plot = self.ApplyPlotmanipulators(self.displayed_plot, active_file)\r
             #add corrected curves to plot\r
             self.displayed_plot.corrected_curves = copy.deepcopy(self.displayed_plot.curves)\r
         else:\r
@@ -1233,45 +1345,42 @@ class HookeFrame(wx.Frame):
             self.displayed_plot = copy.deepcopy(plot)\r
 \r
         figure = self.GetActiveFigure()\r
-\r
         figure.clear()\r
-        figure.suptitle(self.displayed_plot.title, fontsize=14)\r
 \r
+        #use '0' instead of e.g. '0.00' for scales\r
+        use_zero = self.GetBoolFromConfig('core', 'preferences', 'use_zero')\r
+        #optionally remove the extension from the title of the plot\r
+        hide_curve_extension = self.GetBoolFromConfig('core', 'preferences', 'hide_curve_extension')\r
+        if hide_curve_extension:\r
+            title = lh.remove_extension(self.displayed_plot.title)\r
+        else:\r
+            title = self.displayed_plot.title\r
+        figure.suptitle(title, fontsize=14)\r
+        #create the list of all axes necessary (rows and columns)\r
         axes_list =[]\r
-\r
         number_of_columns = max([curve.destination.column for curve in self.displayed_plot.curves])\r
         number_of_rows = max([curve.destination.row for curve in self.displayed_plot.curves])\r
-\r
         for index in range(number_of_rows * number_of_columns):\r
             axes_list.append(figure.add_subplot(number_of_rows, number_of_columns, index + 1))\r
 \r
+        #add all curves to the corresponding plots\r
         for curve in self.displayed_plot.curves:\r
             add_to_plot(curve)\r
 \r
         #make sure the titles of 'subplots' do not overlap with the axis labels of the 'main plot'\r
         figure.subplots_adjust(hspace=0.3)\r
 \r
-        #TODO: add multiple results support to fit in curve.results:\r
-        #get the fit_function results to display\r
-        fit_function_str = self.GetStringFromConfig('results', 'show_results', 'fit_function')\r
+        #display results\r
         self.panelResults.ClearResults()\r
-        plot = self.GetActivePlot()\r
-        if plot is not None:\r
-            if plot.results.has_key(fit_function_str):\r
-                for curve in plot.results[fit_function_str].results:\r
-                    add_to_plot(curve)\r
-                self.panelResults.DisplayResults(plot.results[fit_function_str])\r
-            else:\r
-                self.panelResults.ClearResults()\r
-\r
+        if self.displayed_plot.results.has_key(self.results_str):\r
+            for curve in self.displayed_plot.results[self.results_str].results:\r
+                add_to_plot(curve, set_scale=False)\r
+            self.panelResults.DisplayResults(self.displayed_plot.results[self.results_str])\r
+        else:\r
+            self.panelResults.ClearResults()\r
+        #refresh the plot\r
         figure.canvas.draw()\r
 \r
-        for axes in axes_list:\r
-            #TODO: add legend as global option or per graph option\r
-            #axes.legend()\r
-            axes.figure.canvas.draw()\r
-\r
-\r
 if __name__ == '__main__':\r
 \r
     ## now, silence a deprecation warning for py2.3\r
@@ -1285,5 +1394,3 @@ if __name__ == '__main__':
     app = Hooke(redirect=redirect)\r
 \r
     app.MainLoop()\r
-\r
-\r