43b7510a076384579caacdd6f41132087fd3fc51
[hooke.git] / hooke.py
1 #!/usr/bin/env python\r
2 \r
3 '''\r
4 HOOKE - A force spectroscopy review & analysis tool\r
5 \r
6 Copyright 2008 by Massimo Sandal (University of Bologna, Italy)\r
7 Copyright 2010 by Rolf Schmidt (Concordia University, Canada)\r
8 \r
9 This program is released under the GNU General Public License version 2.\r
10 '''\r
11 \r
12 import lib.libhooke as lh\r
13 import wxversion\r
14 wxversion.select(lh.WX_GOOD)\r
15 \r
16 from configobj import ConfigObj\r
17 import copy\r
18 import os.path\r
19 import platform\r
20 import shutil\r
21 import time\r
22 \r
23 import wx.html\r
24 import wx.lib.agw.aui as aui\r
25 import wx.lib.evtmgr as evtmgr\r
26 import wx.propgrid as wxpg\r
27 \r
28 from matplotlib.ticker import FuncFormatter\r
29 \r
30 from configobj import __version__ as configobj_version\r
31 from matplotlib import __version__ as mpl_version\r
32 from numpy import __version__ as numpy_version\r
33 from scipy import __version__ as scipy_version\r
34 from sys import version as python_version\r
35 from wx import __version__ as wx_version\r
36 from wx.propgrid import PROPGRID_MAJOR\r
37 from wx.propgrid import PROPGRID_MINOR\r
38 from wx.propgrid import PROPGRID_RELEASE\r
39 \r
40 try:\r
41     from agw import cubecolourdialog as CCD\r
42 except ImportError: # if it's not there locally, try the wxPython lib.\r
43     import wx.lib.agw.cubecolourdialog as CCD\r
44 \r
45 #set the Hooke directory\r
46 lh.hookeDir = os.path.abspath(os.path.dirname(__file__))\r
47 from config.config import config\r
48 import drivers\r
49 import lib.clickedpoint\r
50 import lib.curve\r
51 import lib.delta\r
52 import lib.playlist\r
53 import lib.plotmanipulator\r
54 import lib.prettyformat\r
55 import panels.commands\r
56 import panels.note\r
57 import panels.perspectives\r
58 import panels.playlist\r
59 import panels.plot\r
60 import panels.propertyeditor\r
61 import panels.results\r
62 import plugins\r
63 \r
64 global __version__\r
65 global __codename__\r
66 global __releasedate__\r
67 __version__ = lh.HOOKE_VERSION[0]\r
68 __codename__ = lh.HOOKE_VERSION[1]\r
69 __releasedate__ = lh.HOOKE_VERSION[2]\r
70 __release_name__ = lh.HOOKE_VERSION[1]\r
71 \r
72 ID_About = wx.NewId()\r
73 ID_Next = wx.NewId()\r
74 ID_Previous = wx.NewId()\r
75 \r
76 ID_ViewAssistant = wx.NewId()\r
77 ID_ViewCommands = wx.NewId()\r
78 ID_ViewFolders = wx.NewId()\r
79 ID_ViewNote = wx.NewId()\r
80 ID_ViewOutput = wx.NewId()\r
81 ID_ViewPlaylists = wx.NewId()\r
82 ID_ViewProperties = wx.NewId()\r
83 ID_ViewResults = wx.NewId()\r
84 \r
85 ID_DeletePerspective = wx.NewId()\r
86 ID_SavePerspective = wx.NewId()\r
87 \r
88 ID_FirstPerspective = ID_SavePerspective + 1000\r
89 #I hope we'll never have more than 1000 perspectives\r
90 ID_FirstPlot = ID_SavePerspective + 2000\r
91 \r
92 class Hooke(wx.App):\r
93 \r
94     def OnInit(self):\r
95         self.SetAppName('Hooke')\r
96         self.SetVendorName('')\r
97 \r
98         window_height = config['main']['height']\r
99         window_left= config['main']['left']\r
100         window_top = config['main']['top']\r
101         window_width = config['main']['width']\r
102 \r
103         #sometimes, the ini file gets confused and sets 'left'\r
104         #and 'top' to large negative numbers\r
105         #let's catch and fix this\r
106         #keep small negative numbers, the user might want those\r
107         if window_left < -window_width:\r
108             window_left = 0\r
109         if window_top < -window_height:\r
110             window_top = 0\r
111         window_position = (window_left, window_top)\r
112         window_size = (window_width, window_height)\r
113 \r
114         #setup the splashscreen\r
115         if config['splashscreen']['show']:\r
116             filename = lh.get_file_path('hooke.jpg', ['resources'])\r
117             if os.path.isfile(filename):\r
118                 bitmap = wx.Image(filename).ConvertToBitmap()\r
119                 splashStyle = wx.SPLASH_CENTRE_ON_SCREEN|wx.SPLASH_TIMEOUT\r
120                 splashDuration = config['splashscreen']['duration']\r
121                 wx.SplashScreen(bitmap, splashStyle, splashDuration, None, -1)\r
122                 wx.Yield()\r
123                 '''\r
124                 we need for the splash screen to disappear\r
125                 for whatever reason splashDuration and sleep do not correspond to each other\r
126                 at least not on Windows\r
127                 maybe it's because duration is in milliseconds and sleep in seconds\r
128                 thus we need to increase the sleep time a bit\r
129                 a factor of 1.2 seems to work quite well\r
130                 '''\r
131                 sleepFactor = 1.2\r
132                 time.sleep(sleepFactor * splashDuration / 1000)\r
133 \r
134         plugin_objects = []\r
135         for plugin in config['plugins']:\r
136             if config['plugins'][plugin]:\r
137                 filename = ''.join([plugin, '.py'])\r
138                 path = lh.get_file_path(filename, ['plugins'])\r
139                 if os.path.isfile(path):\r
140                     #get the corresponding filename and path\r
141                     plugin_name = ''.join(['plugins.', plugin])\r
142                     #import the module\r
143                     __import__(plugin_name)\r
144                     #get the file that contains the plugin\r
145                     class_file = getattr(plugins, plugin)\r
146                     #get the class that contains the commands\r
147                     class_object = getattr(class_file, plugin + 'Commands')\r
148                     plugin_objects.append(class_object)\r
149 \r
150         def make_command_class(*bases):\r
151             #create metaclass with plugins and plotmanipulators\r
152             return type(HookeFrame)("HookeFramePlugged", bases + (HookeFrame,), {})\r
153         frame = make_command_class(*plugin_objects)(parent=None, id=wx.ID_ANY, title='Hooke', pos=window_position, size=window_size)\r
154         frame.Show(True)\r
155         self.SetTopWindow(frame)\r
156 \r
157         return True\r
158 \r
159     def OnExit(self):\r
160         return True\r
161 \r
162 \r
163 class HookeFrame(wx.Frame):\r
164 \r
165     def __init__(self, parent, id=-1, title='', pos=wx.DefaultPosition, size=wx.DefaultSize, style=wx.DEFAULT_FRAME_STYLE|wx.SUNKEN_BORDER|wx.CLIP_CHILDREN):\r
166         #call parent constructor\r
167         wx.Frame.__init__(self, parent, id, title, pos, size, style)\r
168         self.config = config\r
169         self.CreateApplicationIcon()\r
170         #self.configs contains: {the name of the Commands file: corresponding ConfigObj}\r
171         self.configs = {}\r
172         #self.displayed_plot holds the currently displayed plot\r
173         self.displayed_plot = None\r
174         #self.playlists contains: {the name of the playlist: [playlist, tabIndex, plotID]}\r
175         self.playlists = {}\r
176         #list of all plotmanipulators\r
177         self.plotmanipulators = []\r
178         #self.plugins contains: {the name of the plugin: [caption, function]}\r
179         self.plugins = {}\r
180         #self.results_str contains the type of results we want to display\r
181         self.results_str = 'wlc'\r
182 \r
183         #tell FrameManager to manage this frame\r
184         self._mgr = aui.AuiManager()\r
185         self._mgr.SetManagedWindow(self)\r
186         #set the gradient style\r
187         self._mgr.GetArtProvider().SetMetric(aui.AUI_DOCKART_GRADIENT_TYPE, aui.AUI_GRADIENT_NONE)\r
188         #set transparent drag\r
189         self._mgr.SetFlags(self._mgr.GetFlags() ^ aui.AUI_MGR_TRANSPARENT_DRAG)\r
190 \r
191         # set up default notebook style\r
192         self._notebook_style = aui.AUI_NB_DEFAULT_STYLE | aui.AUI_NB_TAB_EXTERNAL_MOVE | wx.NO_BORDER\r
193         self._notebook_theme = 0\r
194 \r
195         #holds the perspectives: {name, perspective_str}\r
196         self._perspectives = {}\r
197 \r
198         # min size for the frame itself isn't completely done.\r
199         # see the end up FrameManager::Update() for the test\r
200         # code. For now, just hard code a frame minimum size\r
201         self.SetMinSize(wx.Size(500, 500))\r
202         #define the list of active drivers\r
203         self.drivers = []\r
204         for driver in self.config['drivers']:\r
205             if self.config['drivers'][driver]:\r
206                 #get the corresponding filename and path\r
207                 filename = ''.join([driver, '.py'])\r
208                 path = lh.get_file_path(filename, ['drivers'])\r
209                 #the driver is active for driver[1] == 1\r
210                 if os.path.isfile(path):\r
211                     #driver files are located in the 'drivers' subfolder\r
212                     driver_name = ''.join(['drivers.', driver])\r
213                     __import__(driver_name)\r
214                     class_file = getattr(drivers, driver)\r
215                     for command in dir(class_file):\r
216                         if command.endswith('Driver'):\r
217                             self.drivers.append(getattr(class_file, command))\r
218         #import all active plugins and plotmanips\r
219         #add 'core.ini' to self.configs (this is not a plugin and thus must be imported separately)\r
220         ini_path = lh.get_file_path('core.ini', ['plugins'])\r
221         plugin_config = ConfigObj(ini_path)\r
222         #self.config.merge(plugin_config)\r
223         self.configs['core'] = plugin_config\r
224         #existing_commands contains: {command: plugin}\r
225         existing_commands = {}\r
226         #make sure we execute _plug_init() for every command line plugin we import\r
227         for plugin in self.config['plugins']:\r
228             if self.config['plugins'][plugin]:\r
229                 filename = ''.join([plugin, '.py'])\r
230                 path = lh.get_file_path(filename, ['plugins'])\r
231                 if os.path.isfile(path):\r
232                     #get the corresponding filename and path\r
233                     plugin_name = ''.join(['plugins.', plugin])\r
234                     try:\r
235                         #import the module\r
236                         module = __import__(plugin_name)\r
237                         #prepare the ini file for inclusion\r
238                         ini_path = path.replace('.py', '.ini')\r
239                         #include ini file\r
240                         plugin_config = ConfigObj(ini_path)\r
241                         #self.config.merge(plugin_config)\r
242                         self.configs[plugin] = plugin_config\r
243                         #add to plugins\r
244                         commands = eval('dir(module.' + plugin+ '.' + plugin + 'Commands)')\r
245                         #keep only commands (ie names that start with 'do_')\r
246                         commands = [command for command in commands if command.startswith('do_')]\r
247                         if commands:\r
248                             for command in commands:\r
249                                 if existing_commands.has_key(command):\r
250                                     message_str = 'Adding "' + command + '" in plugin "' + plugin + '".\n\n'\r
251                                     message_str += '"' + command + '" already exists in "' + str(existing_commands[command]) + '".\n\n'\r
252                                     message_str += 'Only "' + command + '" in "' + str(existing_commands[command]) + '" will work.\n\n'\r
253                                     message_str += 'Please rename one of the commands in the source code and restart Hooke or disable one of the plugins.'\r
254                                     dialog = wx.MessageDialog(self, message_str, 'Warning', wx.OK|wx.ICON_WARNING|wx.CENTER)\r
255                                     dialog.ShowModal()\r
256                                     dialog.Destroy()\r
257                                 existing_commands[command] = plugin\r
258                             self.plugins[plugin] = commands\r
259                         try:\r
260                             #initialize the plugin\r
261                             eval('module.' + plugin+ '.' + plugin + 'Commands._plug_init(self)')\r
262                         except AttributeError:\r
263                             pass\r
264                     except ImportError:\r
265                         pass\r
266         #add commands from hooke.py i.e. 'core' commands\r
267         commands = dir(HookeFrame)\r
268         commands = [command for command in commands if command.startswith('do_')]\r
269         if commands:\r
270             self.plugins['core'] = commands\r
271         #create panels here\r
272         self.panelAssistant = self.CreatePanelAssistant()\r
273         self.panelCommands = self.CreatePanelCommands()\r
274         self.panelFolders = self.CreatePanelFolders()\r
275         self.panelPlaylists = self.CreatePanelPlaylists()\r
276         self.panelProperties = self.CreatePanelProperties()\r
277         self.panelNote = self.CreatePanelNote()\r
278         self.panelOutput = self.CreatePanelOutput()\r
279         self.panelResults = self.CreatePanelResults()\r
280         self.plotNotebook = self.CreateNotebook()\r
281 \r
282         # add panes\r
283         self._mgr.AddPane(self.panelFolders, aui.AuiPaneInfo().Name('Folders').Caption('Folders').Left().CloseButton(True).MaximizeButton(False))\r
284         self._mgr.AddPane(self.panelPlaylists, aui.AuiPaneInfo().Name('Playlists').Caption('Playlists').Left().CloseButton(True).MaximizeButton(False))\r
285         self._mgr.AddPane(self.panelNote, aui.AuiPaneInfo().Name('Note').Caption('Note').Left().CloseButton(True).MaximizeButton(False))\r
286         self._mgr.AddPane(self.plotNotebook, aui.AuiPaneInfo().Name('Plots').CenterPane().PaneBorder(False))\r
287         self._mgr.AddPane(self.panelCommands, aui.AuiPaneInfo().Name('Commands').Caption('Settings and commands').Right().CloseButton(True).MaximizeButton(False))\r
288         self._mgr.AddPane(self.panelProperties, aui.AuiPaneInfo().Name('Properties').Caption('Properties').Right().CloseButton(True).MaximizeButton(False))\r
289         self._mgr.AddPane(self.panelAssistant, aui.AuiPaneInfo().Name('Assistant').Caption('Assistant').Right().CloseButton(True).MaximizeButton(False))\r
290         self._mgr.AddPane(self.panelOutput, aui.AuiPaneInfo().Name('Output').Caption('Output').Bottom().CloseButton(True).MaximizeButton(False))\r
291         self._mgr.AddPane(self.panelResults, aui.AuiPaneInfo().Name('Results').Caption('Results').Bottom().CloseButton(True).MaximizeButton(False))\r
292         #self._mgr.AddPane(self.textCtrlCommandLine, aui.AuiPaneInfo().Name('CommandLine').CaptionVisible(False).Fixed().Bottom().Layer(2).CloseButton(False).MaximizeButton(False))\r
293         #self._mgr.AddPane(panelBottom, aui.AuiPaneInfo().Name("panelCommandLine").Bottom().Position(1).CloseButton(False).MaximizeButton(False))\r
294 \r
295         # add the toolbars to the manager\r
296         #self.toolbar=self.CreateToolBar()\r
297         self.toolbarNavigation=self.CreateToolBarNavigation()\r
298         #self._mgr.AddPane(self.toolbar, aui.AuiPaneInfo().Name('toolbar').Caption('Toolbar').ToolbarPane().Top().Layer(1).Row(1).LeftDockable(False).RightDockable(False))\r
299         self._mgr.AddPane(self.toolbarNavigation, aui.AuiPaneInfo().Name('toolbarNavigation').Caption('Navigation').ToolbarPane().Top().Layer(1).Row(1).LeftDockable(False).RightDockable(False))\r
300         # "commit" all changes made to FrameManager\r
301         self._mgr.Update()\r
302         #create the menubar after the panes so that the default perspective\r
303         #is created with all panes open\r
304         self.CreateMenuBar()\r
305         self.statusbar = self.CreateStatusbar()\r
306         self._BindEvents()\r
307 \r
308         name = self.config['perspectives']['active']\r
309         menu_item = self.GetPerspectiveMenuItem(name)\r
310         if menu_item is not None:\r
311             self.OnRestorePerspective(menu_item)\r
312             #TODO: config setting to remember playlists from last session\r
313         self.playlists = self.panelPlaylists.Playlists\r
314         #initialize the commands tree\r
315         self.panelCommands.Initialize(self.plugins)\r
316         for command in dir(self):\r
317             if command.startswith('plotmanip_'):\r
318                 self.plotmanipulators.append(lib.plotmanipulator.Plotmanipulator(method=getattr(self, command), command=command))\r
319 \r
320         #load default list, if possible\r
321         self.do_loadlist(self.GetStringFromConfig('core', 'preferences', 'playlist'))\r
322 \r
323     def _BindEvents(self):\r
324         #TODO: figure out if we can use the eventManager for menu ranges\r
325         #and events of 'self' without raising an assertion fail error\r
326         self.Bind(wx.EVT_ERASE_BACKGROUND, self.OnEraseBackground)\r
327         self.Bind(wx.EVT_SIZE, self.OnSize)\r
328         self.Bind(wx.EVT_CLOSE, self.OnClose)\r
329         # Show How To Use The Closing Panes Event\r
330         self.Bind(aui.EVT_AUI_PANE_CLOSE, self.OnPaneClose)\r
331         self.Bind(aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self.OnNotebookPageClose)\r
332         #menu\r
333         evtmgr.eventManager.Register(self.OnAbout, wx.EVT_MENU, win=self, id=wx.ID_ABOUT)\r
334         evtmgr.eventManager.Register(self.OnClose, wx.EVT_MENU, win=self, id=wx.ID_EXIT)\r
335         #view\r
336         self.Bind(wx.EVT_MENU_RANGE, self.OnView, id=ID_ViewAssistant, id2=ID_ViewResults)\r
337         #perspectives\r
338         self.Bind(wx.EVT_MENU, self.OnDeletePerspective, id=ID_DeletePerspective)\r
339         self.Bind(wx.EVT_MENU, self.OnSavePerspective, id=ID_SavePerspective)\r
340         self.Bind(wx.EVT_MENU_RANGE, self.OnRestorePerspective, id=ID_FirstPerspective, id2=ID_FirstPerspective+1000)\r
341         #toolbar\r
342         evtmgr.eventManager.Register(self.OnNext, wx.EVT_TOOL, win=self, id=ID_Next)\r
343         evtmgr.eventManager.Register(self.OnPrevious, wx.EVT_TOOL, win=self, id=ID_Previous)\r
344         #self.Bind(.EVT_AUITOOLBAR_TOOL_DROPDOWN, self.OnDropDownToolbarItem, id=ID_DropDownToolbarItem)\r
345         #dir control\r
346         treeCtrl = self.panelFolders.GetTreeCtrl()\r
347         #tree.Bind(wx.EVT_LEFT_UP, self.OnDirCtrl1LeftUp)\r
348         #tree.Bind(wx.EVT_LEFT_DOWN, self.OnGenericDirCtrl1LeftDown)\r
349         treeCtrl.Bind(wx.EVT_LEFT_DCLICK, self.OnDirCtrlLeftDclick)\r
350         #playlist tree\r
351         self.panelPlaylists.PlaylistsTree.Bind(wx.EVT_LEFT_DOWN, self.OnPlaylistsLeftDown)\r
352         self.panelPlaylists.PlaylistsTree.Bind(wx.EVT_LEFT_DCLICK, self.OnPlaylistsLeftDclick)\r
353         #commands tree\r
354         evtmgr.eventManager.Register(self.OnExecute, wx.EVT_BUTTON, self.panelCommands.ExecuteButton)\r
355         evtmgr.eventManager.Register(self.OnTreeCtrlCommandsSelectionChanged, wx.EVT_TREE_SEL_CHANGED, self.panelCommands.CommandsTree)\r
356         evtmgr.eventManager.Register(self.OnTreeCtrlItemActivated, wx.EVT_TREE_ITEM_ACTIVATED, self.panelCommands.CommandsTree)\r
357         evtmgr.eventManager.Register(self.OnUpdateNote, wx.EVT_BUTTON, self.panelNote.UpdateButton)\r
358         #property editor\r
359         self.panelProperties.pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChanged)\r
360         #results panel\r
361         self.panelResults.results_list.OnCheckItem = self.OnResultsCheck\r
362 \r
363     def _GetActiveFileIndex(self):\r
364         lib.playlist.Playlist = self.GetActivePlaylist()\r
365         #get the selected item from the tree\r
366         selected_item = self.panelPlaylists.PlaylistsTree.GetSelection()\r
367         #test if a playlist or a curve was double-clicked\r
368         if self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item):\r
369             return -1\r
370         else:\r
371             count = 0\r
372             selected_item = self.panelPlaylists.PlaylistsTree.GetPrevSibling(selected_item)\r
373             while selected_item.IsOk():\r
374                 count += 1\r
375                 selected_item = self.panelPlaylists.PlaylistsTree.GetPrevSibling(selected_item)\r
376             return count\r
377 \r
378     def _GetPlaylistTab(self, name):\r
379         for index, page in enumerate(self.plotNotebook._tabs._pages):\r
380             if page.caption == name:\r
381                 return index\r
382         return -1\r
383 \r
384     def _GetUniquePlaylistName(self, name):\r
385         playlist_name = name\r
386         count = 1\r
387         while playlist_name in self.playlists:\r
388             playlist_name = ''.join([name, str(count)])\r
389             count += 1\r
390         return playlist_name\r
391 \r
392     def _RestorePerspective(self, name):\r
393         self._mgr.LoadPerspective(self._perspectives[name])\r
394         self.config['perspectives']['active'] = name\r
395         self._mgr.Update()\r
396         all_panes = self._mgr.GetAllPanes()\r
397         for pane in all_panes:\r
398             if not pane.name.startswith('toolbar'):\r
399                 if pane.name == 'Assistant':\r
400                     self.MenuBar.FindItemById(ID_ViewAssistant).Check(pane.window.IsShown())\r
401                 if pane.name == 'Folders':\r
402                     self.MenuBar.FindItemById(ID_ViewFolders).Check(pane.window.IsShown())\r
403                 if pane.name == 'Playlists':\r
404                     self.MenuBar.FindItemById(ID_ViewPlaylists).Check(pane.window.IsShown())\r
405                 if pane.name == 'Commands':\r
406                     self.MenuBar.FindItemById(ID_ViewCommands).Check(pane.window.IsShown())\r
407                 if pane.name == 'Note':\r
408                     self.MenuBar.FindItemById(ID_ViewNote).Check(pane.window.IsShown())\r
409                 if pane.name == 'Properties':\r
410                     self.MenuBar.FindItemById(ID_ViewProperties).Check(pane.window.IsShown())\r
411                 if pane.name == 'Output':\r
412                     self.MenuBar.FindItemById(ID_ViewOutput).Check(pane.window.IsShown())\r
413                 if pane.name == 'Results':\r
414                     self.MenuBar.FindItemById(ID_ViewResults).Check(pane.window.IsShown())\r
415 \r
416     def _SavePerspectiveToFile(self, name, perspective):\r
417         filename = ''.join([name, '.txt'])\r
418         filename = lh.get_file_path(filename, ['perspectives'])\r
419         perspectivesFile = open(filename, 'w')\r
420         perspectivesFile.write(perspective)\r
421         perspectivesFile.close()\r
422 \r
423     def _UnbindEvents(self):\r
424         #menu\r
425         evtmgr.eventManager.DeregisterListener(self.OnAbout)\r
426         evtmgr.eventManager.DeregisterListener(self.OnClose)\r
427         #toolbar\r
428         evtmgr.eventManager.DeregisterListener(self.OnNext)\r
429         evtmgr.eventManager.DeregisterListener(self.OnPrevious)\r
430         #commands tree\r
431         evtmgr.eventManager.DeregisterListener(self.OnExecute)\r
432         evtmgr.eventManager.DeregisterListener(self.OnTreeCtrlCommandsSelectionChanged)\r
433         evtmgr.eventManager.DeregisterListener(self.OnTreeCtrlItemActivated)\r
434         evtmgr.eventManager.DeregisterListener(self.OnUpdateNote)\r
435 \r
436     def AddPlaylist(self, playlist=None, name='Untitled'):\r
437         if playlist and playlist.count > 0:\r
438             playlist.name = self._GetUniquePlaylistName(name)\r
439             playlist.reset()\r
440             self.AddToPlaylists(playlist)\r
441 \r
442     def AddPlaylistFromFiles(self, files=[], name='Untitled'):\r
443         if files:\r
444             playlist = lib.playlist.Playlist(self, self.drivers)\r
445             for item in files:\r
446                 playlist.add_curve(item)\r
447         if playlist.count > 0:\r
448             playlist.name = self._GetUniquePlaylistName(name)\r
449             playlist.reset()\r
450             self.AddTayliss(playlist)\r
451 \r
452     def AddToPlaylists(self, playlist):\r
453         if playlist.count > 0:\r
454             #setup the playlist in the Playlist tree\r
455             tree_root = self.panelPlaylists.PlaylistsTree.GetRootItem()\r
456             playlist_root = self.panelPlaylists.PlaylistsTree.AppendItem(tree_root, playlist.name, 0)\r
457             #add all files to the Playlist tree\r
458 #            files = {}\r
459             hide_curve_extension = self.GetBoolFromConfig('core', 'preferences', 'hide_curve_extension')\r
460             for index, file_to_add in enumerate(playlist.files):\r
461                 #optionally remove the extension from the name of the curve\r
462                 if hide_curve_extension:\r
463                     file_to_add.name = lh.remove_extension(file_to_add.name)\r
464                 file_ID = self.panelPlaylists.PlaylistsTree.AppendItem(playlist_root, file_to_add.name, 1)\r
465                 if index == playlist.index:\r
466                     self.panelPlaylists.PlaylistsTree.SelectItem(file_ID)\r
467             playlist.reset()\r
468             #create the plot tab and add playlist to the dictionary\r
469             plotPanel = panels.plot.PlotPanel(self, ID_FirstPlot + len(self.playlists))\r
470             notebook_tab = self.plotNotebook.AddPage(plotPanel, playlist.name, True)\r
471             #tab_index = self.plotNotebook.GetSelection()\r
472             playlist.figure = plotPanel.get_figure()\r
473             self.playlists[playlist.name] = playlist\r
474             #self.playlists[playlist.name] = [playlist, figure]\r
475             self.panelPlaylists.PlaylistsTree.Expand(playlist_root)\r
476             self.statusbar.SetStatusText(playlist.get_status_string(), 0)\r
477             self.UpdateNote()\r
478             self.UpdatePlot()\r
479 \r
480     def AppendToOutput(self, text):\r
481         self.panelOutput.AppendText(''.join([text, '\n']))\r
482 \r
483     def AppliesPlotmanipulator(self, name):\r
484         '''\r
485         Returns True if the plotmanipulator 'name' is applied, False otherwise\r
486         name does not contain 'plotmanip_', just the name of the plotmanipulator (e.g. 'flatten')\r
487         '''\r
488         return self.GetBoolFromConfig('core', 'plotmanipulators', name)\r
489 \r
490     def ApplyPlotmanipulators(self, plot, plot_file):\r
491         '''\r
492         Apply all active plotmanipulators.\r
493         '''\r
494         if plot is not None and plot_file is not None:\r
495             manipulated_plot = copy.deepcopy(plot)\r
496             for plotmanipulator in self.plotmanipulators:\r
497                 if self.GetBoolFromConfig('core', 'plotmanipulators', plotmanipulator.name):\r
498                     manipulated_plot = plotmanipulator.method(manipulated_plot, plot_file)\r
499             return manipulated_plot\r
500 \r
501     def CreateApplicationIcon(self):\r
502         iconFile = 'resources' + os.sep + 'microscope.ico'\r
503         icon = wx.Icon(iconFile, wx.BITMAP_TYPE_ICO)\r
504         self.SetIcon(icon)\r
505 \r
506     def CreateCommandLine(self):\r
507         return wx.TextCtrl(self, -1, '', style=wx.NO_BORDER|wx.EXPAND)\r
508 \r
509     def CreatePanelAssistant(self):\r
510         panel = wx.TextCtrl(self, -1, '', wx.Point(0, 0), wx.Size(150, 90), wx.NO_BORDER|wx.TE_MULTILINE)\r
511         panel.SetEditable(False)\r
512         return panel\r
513 \r
514     def CreatePanelCommands(self):\r
515         return panels.commands.Commands(self)\r
516 \r
517     def CreatePanelFolders(self):\r
518         #set file filters\r
519         filters = self.config['folders']['filters']\r
520         index = self.config['folders'].as_int('filterindex')\r
521         #set initial directory\r
522         folder = self.GetStringFromConfig('core', 'preferences', 'workdir')\r
523         return wx.GenericDirCtrl(self, -1, dir=folder, size=(200, 250), style=wx.DIRCTRL_SHOW_FILTERS, filter=filters, defaultFilter=index)\r
524 \r
525     def CreatePanelNote(self):\r
526         return panels.note.Note(self)\r
527 \r
528     def CreatePanelOutput(self):\r
529         return wx.TextCtrl(self, -1, '', wx.Point(0, 0), wx.Size(150, 90), wx.NO_BORDER|wx.TE_MULTILINE)\r
530 \r
531     def CreatePanelPlaylists(self):\r
532         return panels.playlist.Playlists(self)\r
533 \r
534     def CreatePanelProperties(self):\r
535         return panels.propertyeditor.PropertyEditor(self)\r
536 \r
537     def CreatePanelResults(self):\r
538         return panels.results.Results(self)\r
539 \r
540     def CreatePanelWelcome(self):\r
541         #TODO: move into panels.welcome\r
542         ctrl = wx.html.HtmlWindow(self, -1, wx.DefaultPosition, wx.Size(400, 300))\r
543         introStr = '<h1>Welcome to Hooke</h1>' + \\r
544                  '<h3>Features</h3>' + \\r
545                  '<ul>' + \\r
546                  '<li>View, annotate, measure force files</li>' + \\r
547                  '<li>Worm-like chain fit of force peaks</li>' + \\r
548                  '<li>Automatic convolution-based filtering of empty files</li>' + \\r
549                  '<li>Automatic fit and measurement of multiple force peaks</li>' + \\r
550                  '<li>Handles force-clamp force experiments (experimental)</li>' + \\r
551                  '<li>It is extensible by users by means of plugins and drivers</li>' + \\r
552                  '</ul>' + \\r
553                  '<p>See the <a href="http://code.google.com/p/hooke/wiki/DocumentationIndex">DocumentationIndex</a> for more information</p>'\r
554         ctrl.SetPage(introStr)\r
555         return ctrl\r
556 \r
557     def CreateMenuBar(self):\r
558         menu_bar = wx.MenuBar()\r
559         self.SetMenuBar(menu_bar)\r
560         #file\r
561         file_menu = wx.Menu()\r
562         file_menu.Append(wx.ID_EXIT, 'Exit\tCtrl-Q')\r
563 #        edit_menu.AppendSeparator();\r
564 #        edit_menu.Append(ID_Config, 'Preferences')\r
565         #view\r
566         view_menu = wx.Menu()\r
567         view_menu.AppendCheckItem(ID_ViewFolders, 'Folders\tF5')\r
568         view_menu.AppendCheckItem(ID_ViewPlaylists, 'Playlists\tF6')\r
569         view_menu.AppendCheckItem(ID_ViewCommands, 'Commands\tF7')\r
570         view_menu.AppendCheckItem(ID_ViewProperties, 'Properties\tF8')\r
571         view_menu.AppendCheckItem(ID_ViewAssistant, 'Assistant\tF9')\r
572         view_menu.AppendCheckItem(ID_ViewResults, 'Results\tF10')\r
573         view_menu.AppendCheckItem(ID_ViewOutput, 'Output\tF11')\r
574         view_menu.AppendCheckItem(ID_ViewNote, 'Note\tF12')\r
575         #perspectives\r
576         perspectives_menu = wx.Menu()\r
577 \r
578         #help\r
579         help_menu = wx.Menu()\r
580         help_menu.Append(wx.ID_ABOUT, 'About Hooke')\r
581         #put it all together\r
582         menu_bar.Append(file_menu, 'File')\r
583         menu_bar.Append(view_menu, 'View')\r
584         menu_bar.Append(perspectives_menu, "Perspectives")\r
585         self.UpdatePerspectivesMenu()\r
586         menu_bar.Append(help_menu, 'Help')\r
587 \r
588     def CreateNotebook(self):\r
589         # create the notebook off-window to avoid flicker\r
590         client_size = self.GetClientSize()\r
591         ctrl = aui.AuiNotebook(self, -1, wx.Point(client_size.x, client_size.y), wx.Size(430, 200), self._notebook_style)\r
592         arts = [aui.AuiDefaultTabArt, aui.AuiSimpleTabArt, aui.VC71TabArt, aui.FF2TabArt, aui.VC8TabArt, aui.ChromeTabArt]\r
593         art = arts[self._notebook_theme]()\r
594         ctrl.SetArtProvider(art)\r
595         #uncomment if we find a nice icon\r
596         #page_bmp = wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, wx.Size(16, 16))\r
597         ctrl.AddPage(self.CreatePanelWelcome(), "Welcome", False)\r
598         return ctrl\r
599 \r
600     def CreateStatusbar(self):\r
601         statusbar = self.CreateStatusBar(2, wx.ST_SIZEGRIP)\r
602         statusbar.SetStatusWidths([-2, -3])\r
603         statusbar.SetStatusText('Ready', 0)\r
604         welcomeString=u'Welcome to Hooke (version '+__version__+', '+__release_name__+')!'\r
605         statusbar.SetStatusText(welcomeString, 1)\r
606         return statusbar\r
607 \r
608     def CreateToolBarNavigation(self):\r
609         toolbar = wx.ToolBar(self, -1, wx.DefaultPosition, wx.DefaultSize, wx.TB_FLAT | wx.TB_NODIVIDER)\r
610         toolbar.SetToolBitmapSize(wx.Size(16,16))\r
611         toolbar_bmpBack = wx.ArtProvider_GetBitmap(wx.ART_GO_BACK, wx.ART_OTHER, wx.Size(16, 16))\r
612         toolbar_bmpForward = wx.ArtProvider_GetBitmap(wx.ART_GO_FORWARD, wx.ART_OTHER, wx.Size(16, 16))\r
613         toolbar.AddLabelTool(ID_Previous, 'Previous', toolbar_bmpBack, shortHelp='Previous curve')\r
614         toolbar.AddLabelTool(ID_Next, 'Next', toolbar_bmpForward, shortHelp='Next curve')\r
615         toolbar.Realize()\r
616         return toolbar\r
617 \r
618     def DeleteFromPlaylists(self, name):\r
619         if name in self.playlists:\r
620             del self.playlists[name]\r
621         tree_root = self.panelPlaylists.PlaylistsTree.GetRootItem()\r
622         item, cookie = self.panelPlaylists.PlaylistsTree.GetFirstChild(tree_root)\r
623         while item.IsOk():\r
624             playlist_name = self.panelPlaylists.PlaylistsTree.GetItemText(item)\r
625             if playlist_name == name:\r
626                 try:\r
627                     self.panelPlaylists.PlaylistsTree.Delete(item)\r
628                 except:\r
629                     pass\r
630             item = self.panelPlaylists.PlaylistsTree.GetNextSibling(item)\r
631 \r
632     def GetActiveFigure(self):\r
633         playlist_name = self.GetActivePlaylistName()\r
634         figure = self.playlists[playlist_name].figure\r
635         if figure is not None:\r
636             return figure\r
637         return None\r
638 \r
639     def GetActiveFile(self):\r
640         playlist = self.GetActivePlaylist()\r
641         if playlist is not None:\r
642             return playlist.get_active_file()\r
643         return None\r
644 \r
645     def GetActivePlaylist(self):\r
646         playlist_name = self.GetActivePlaylistName()\r
647         if playlist_name in self.playlists:\r
648             return self.playlists[playlist_name]\r
649         return None\r
650 \r
651     def GetActivePlaylistName(self):\r
652         #get the selected item from the tree\r
653         selected_item = self.panelPlaylists.PlaylistsTree.GetSelection()\r
654         #test if a playlist or a curve was double-clicked\r
655         if self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item):\r
656             playlist_item = selected_item\r
657         else:\r
658             #get the name of the playlist\r
659             playlist_item = self.panelPlaylists.PlaylistsTree.GetItemParent(selected_item)\r
660         #now we have a playlist\r
661         return self.panelPlaylists.PlaylistsTree.GetItemText(playlist_item)\r
662 \r
663     def GetActivePlot(self):\r
664         playlist = self.GetActivePlaylist()\r
665         if playlist is not None:\r
666             return playlist.get_active_file().plot\r
667         return None\r
668 \r
669     def GetDisplayedPlot(self):\r
670         plot = copy.deepcopy(self.displayed_plot)\r
671         #plot.curves = []\r
672         #plot.curves = copy.deepcopy(plot.curves)\r
673         return plot\r
674 \r
675     def GetDisplayedPlotCorrected(self):\r
676         plot = copy.deepcopy(self.displayed_plot)\r
677         plot.curves = []\r
678         plot.curves = copy.deepcopy(plot.corrected_curves)\r
679         return plot\r
680 \r
681     def GetDisplayedPlotRaw(self):\r
682         plot = copy.deepcopy(self.displayed_plot)\r
683         plot.curves = []\r
684         plot.curves = copy.deepcopy(plot.raw_curves)\r
685         return plot\r
686 \r
687     def GetDockArt(self):\r
688         return self._mgr.GetArtProvider()\r
689 \r
690     def GetBoolFromConfig(self, *args):\r
691         if len(args) == 2:\r
692             plugin = args[0]\r
693             section = args[0]\r
694             key = args[1]\r
695         elif len(args) == 3:\r
696             plugin = args[0]\r
697             section = args[1]\r
698             key = args[2]\r
699         if self.configs.has_key(plugin):\r
700             config = self.configs[plugin]\r
701             return config[section][key].as_bool('value')\r
702         return None\r
703 \r
704     def GetColorFromConfig(self, *args):\r
705         if len(args) == 2:\r
706             plugin = args[0]\r
707             section = args[0]\r
708             key = args[1]\r
709         elif len(args) == 3:\r
710             plugin = args[0]\r
711             section = args[1]\r
712             key = args[2]\r
713         if self.configs.has_key(plugin):\r
714             config = self.configs[plugin]\r
715             color_tuple = eval(config[section][key]['value'])\r
716             color = [value / 255.0 for value in color_tuple]\r
717             return color\r
718         return None\r
719 \r
720     def GetFloatFromConfig(self, *args):\r
721         if len(args) == 2:\r
722             plugin = args[0]\r
723             section = args[0]\r
724             key = args[1]\r
725         elif len(args) == 3:\r
726             plugin = args[0]\r
727             section = args[1]\r
728             key = args[2]\r
729         if self.configs.has_key(plugin):\r
730             config = self.configs[plugin]\r
731             return config[section][key].as_float('value')\r
732         return None\r
733 \r
734     def GetIntFromConfig(self, *args):\r
735         if len(args) == 2:\r
736             plugin = args[0]\r
737             section = args[0]\r
738             key = args[1]\r
739         elif len(args) == 3:\r
740             plugin = args[0]\r
741             section = args[1]\r
742             key = args[2]\r
743         if self.configs.has_key(plugin):\r
744             config = self.configs[plugin]\r
745             return config[section][key].as_int('value')\r
746         return None\r
747 \r
748     def GetStringFromConfig(self, *args):\r
749         if len(args) == 2:\r
750             plugin = args[0]\r
751             section = args[0]\r
752             key = args[1]\r
753         elif len(args) == 3:\r
754             plugin = args[0]\r
755             section = args[1]\r
756             key = args[2]\r
757         if self.configs.has_key(plugin):\r
758             config = self.configs[plugin]\r
759             return config[section][key]['value']\r
760         return None\r
761 \r
762     def GetPlotmanipulator(self, name):\r
763         '''\r
764         Returns a plot manipulator function from its name\r
765         '''\r
766         for plotmanipulator in self.plotmanipulators:\r
767             if plotmanipulator.name == name:\r
768                 return plotmanipulator\r
769         return None\r
770 \r
771     def GetPerspectiveMenuItem(self, name):\r
772         if self._perspectives.has_key(name):\r
773             perspectives_list = [key for key, value in self._perspectives.iteritems()]\r
774             perspectives_list.sort()\r
775             index = perspectives_list.index(name)\r
776             perspective_Id = ID_FirstPerspective + index\r
777             menu_item = self.MenuBar.FindItemById(perspective_Id)\r
778             return menu_item\r
779         else:\r
780             return None\r
781 \r
782     def HasPlotmanipulator(self, name):\r
783         '''\r
784         returns True if the plotmanipulator 'name' is loaded, False otherwise\r
785         '''\r
786         for plotmanipulator in self.plotmanipulators:\r
787             if plotmanipulator.command == name:\r
788                 return True\r
789         return False\r
790 \r
791     def OnAbout(self, event):\r
792         message = 'Hooke\n\n'+\\r
793             'A free, open source data analysis platform\n\n'+\\r
794             'Copyright 2006-2008 by Massimo Sandal\n'+\\r
795             'Copyright 2010 by Dr. Rolf Schmidt\n\n'+\\r
796             'Hooke is released under the GNU General Public License version 2.'\r
797         dialog = wx.MessageDialog(self, message, 'About Hooke', wx.OK | wx.ICON_INFORMATION)\r
798         dialog.ShowModal()\r
799         dialog.Destroy()\r
800 \r
801     def OnClose(self, event):\r
802         #apply changes\r
803         self.config['main']['height'] = str(self.GetSize().GetHeight())\r
804         self.config['main']['left'] = str(self.GetPosition()[0])\r
805         self.config['main']['top'] = str(self.GetPosition()[1])\r
806         self.config['main']['width'] = str(self.GetSize().GetWidth())\r
807         #save the configuration file to 'config/hooke.ini'\r
808         self.config.write()\r
809         #save all plugin config files\r
810         for config in self.configs:\r
811             plugin_config = self.configs[config]\r
812             plugin_config.write()\r
813         self._UnbindEvents()\r
814         self._mgr.UnInit()\r
815         del self._mgr\r
816         self.Destroy()\r
817 \r
818     def OnDeletePerspective(self, event):\r
819         dialog = panels.perspectives.Perspectives(self, -1, 'Delete perspective(s)')\r
820         dialog.CenterOnScreen()\r
821         dialog.ShowModal()\r
822         dialog.Destroy()\r
823         self.UpdatePerspectivesMenu()\r
824         #unfortunately, there is a bug in wxWidgets (Ticket #3258) that\r
825         #makes the radio item indicator in the menu disappear\r
826         #the code should be fine once this issue is fixed\r
827 \r
828     def OnDirCtrlLeftDclick(self, event):\r
829         file_path = self.panelFolders.GetPath()\r
830         if os.path.isfile(file_path):\r
831             if file_path.endswith('.hkp'):\r
832                 self.do_loadlist(file_path)\r
833         event.Skip()\r
834 \r
835     def OnEraseBackground(self, event):\r
836         event.Skip()\r
837 \r
838     def OnExecute(self, event):\r
839         item = self.panelCommands.CommandsTree.GetSelection()\r
840         if item.IsOk():\r
841             if not self.panelCommands.CommandsTree.ItemHasChildren(item):\r
842                 item_text = self.panelCommands.CommandsTree.GetItemText(item)\r
843                 command = ''.join(['self.do_', item_text, '()'])\r
844                 #self.AppendToOutput(command + '\n')\r
845                 exec(command)\r
846 \r
847     def OnExit(self, event):\r
848         self.Close()\r
849 \r
850     def OnNext(self, event):\r
851         '''\r
852         NEXT\r
853         Go to the next curve in the playlist.\r
854         If we are at the last curve, we come back to the first.\r
855         -----\r
856         Syntax: next, n\r
857         '''\r
858         selected_item = self.panelPlaylists.PlaylistsTree.GetSelection()\r
859         if self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item):\r
860             #GetFirstChild returns a tuple\r
861             #we only need the first element\r
862             next_item = self.panelPlaylists.PlaylistsTree.GetFirstChild(selected_item)[0]\r
863         else:\r
864             next_item = self.panelPlaylists.PlaylistsTree.GetNextSibling(selected_item)\r
865             if not next_item.IsOk():\r
866                 parent_item = self.panelPlaylists.PlaylistsTree.GetItemParent(selected_item)\r
867                 #GetFirstChild returns a tuple\r
868                 #we only need the first element\r
869                 next_item = self.panelPlaylists.PlaylistsTree.GetFirstChild(parent_item)[0]\r
870         self.panelPlaylists.PlaylistsTree.SelectItem(next_item, True)\r
871         if not self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item):\r
872             playlist = self.GetActivePlaylist()\r
873             if playlist.count > 1:\r
874                 playlist.next()\r
875                 self.statusbar.SetStatusText(playlist.get_status_string(), 0)\r
876                 self.UpdateNote()\r
877                 self.UpdatePlot()\r
878 \r
879     def OnNotebookPageClose(self, event):\r
880         ctrl = event.GetEventObject()\r
881         playlist_name = ctrl.GetPageText(ctrl._curpage)\r
882         self.DeleteFromPlaylists(playlist_name)\r
883 \r
884     def OnPaneClose(self, event):\r
885         event.Skip()\r
886 \r
887     def OnPlaylistsLeftDclick(self, event):\r
888         if self.panelPlaylists.PlaylistsTree.Count > 0:\r
889             playlist_name = self.GetActivePlaylistName()\r
890             #if that playlist already exists\r
891             #we check if it is the active playlist (ie selected in panelPlaylists)\r
892             #and switch to it if necessary\r
893             if playlist_name in self.playlists:\r
894                 index = self.plotNotebook.GetSelection()\r
895                 current_playlist = self.plotNotebook.GetPageText(index)\r
896                 if current_playlist != playlist_name:\r
897                     index = self._GetPlaylistTab(playlist_name)\r
898                     self.plotNotebook.SetSelection(index)\r
899                 #if a curve was double-clicked\r
900                 item = self.panelPlaylists.PlaylistsTree.GetSelection()\r
901                 if not self.panelPlaylists.PlaylistsTree.ItemHasChildren(item):\r
902                     index = self._GetActiveFileIndex()\r
903                 else:\r
904                     index = 0\r
905                 if index >= 0:\r
906                     playlist = self.GetActivePlaylist()\r
907                     playlist.index = index\r
908                     self.statusbar.SetStatusText(playlist.get_status_string(), 0)\r
909                     self.UpdateNote()\r
910                     self.UpdatePlot()\r
911             #if you uncomment the following line, the tree will collapse/expand as well\r
912             #event.Skip()\r
913 \r
914     def OnPlaylistsLeftDown(self, event):\r
915         hit_item, hit_flags = self.panelPlaylists.PlaylistsTree.HitTest(event.GetPosition())\r
916         if (hit_flags & wx.TREE_HITTEST_ONITEM) != 0:\r
917             self.panelPlaylists.PlaylistsTree.SelectItem(hit_item)\r
918             playlist_name = self.GetActivePlaylistName()\r
919             playlist = self.GetActivePlaylist()\r
920             #if a curve was clicked\r
921             item = self.panelPlaylists.PlaylistsTree.GetSelection()\r
922             if not self.panelPlaylists.PlaylistsTree.ItemHasChildren(item):\r
923                 index = self._GetActiveFileIndex()\r
924                 if index >= 0:\r
925                     playlist.index = index\r
926             self.playlists[playlist_name] = playlist\r
927         event.Skip()\r
928 \r
929     def OnPrevious(self, event):\r
930         '''\r
931         PREVIOUS\r
932         Go to the previous curve in the playlist.\r
933         If we are at the first curve, we jump to the last.\r
934         -------\r
935         Syntax: previous, p\r
936         '''\r
937         #playlist = self.playlists[self.GetActivePlaylistName()][0]\r
938         #select the previous curve and tell the user if we wrapped around\r
939         #self.AppendToOutput(playlist.previous())\r
940         selected_item = self.panelPlaylists.PlaylistsTree.GetSelection()\r
941         if self.panelPlaylists.PlaylistsTree.ItemHasChildren(selected_item):\r
942             previous_item = self.panelPlaylists.PlaylistsTree.GetLastChild(selected_item)\r
943         else:\r
944             previous_item = self.panelPlaylists.PlaylistsTree.GetPrevSibling(selected_item)\r
945             if not previous_item.IsOk():\r
946                 parent_item = self.panelPlaylists.PlaylistsTree.GetItemParent(selected_item)\r
947                 previous_item = self.panelPlaylists.PlaylistsTree.GetLastChild(parent_item)\r
948         self.panelPlaylists.PlaylistsTree.SelectItem(previous_item, True)\r
949         playlist = self.GetActivePlaylist()\r
950         if playlist.count > 1:\r
951             playlist.previous()\r
952             self.statusbar.SetStatusText(playlist.get_status_string(), 0)\r
953             self.UpdateNote()\r
954             self.UpdatePlot()\r
955 \r
956     def OnPropGridChanged (self, event):\r
957         prop = event.GetProperty()\r
958         if prop:\r
959             item_section = self.panelProperties.SelectedTreeItem\r
960             item_plugin = self.panelCommands.CommandsTree.GetItemParent(item_section)\r
961             plugin = self.panelCommands.CommandsTree.GetItemText(item_plugin)\r
962             config = self.configs[plugin]\r
963             property_section = self.panelCommands.CommandsTree.GetItemText(item_section)\r
964             property_key = prop.GetName()\r
965             property_value = prop.GetDisplayedString()\r
966 \r
967             config[property_section][property_key]['value'] = property_value\r
968 \r
969     def OnRestorePerspective(self, event):\r
970         name = self.MenuBar.FindItemById(event.GetId()).GetLabel()\r
971         self._RestorePerspective(name)\r
972 \r
973     def OnResultsCheck(self, index, flag):\r
974         results = self.GetActivePlot().results\r
975         if results.has_key(self.results_str):\r
976             results[self.results_str].results[index].visible = flag\r
977             results[self.results_str].update()\r
978             self.UpdatePlot()\r
979 \r
980     def OnSavePerspective(self, event):\r
981 \r
982         def nameExists(name):\r
983             menu_position = self.MenuBar.FindMenu('Perspectives')\r
984             menu = self.MenuBar.GetMenu(menu_position)\r
985             for item in menu.GetMenuItems():\r
986                 if item.GetText() == name:\r
987                     return True\r
988             return False\r
989 \r
990         done = False\r
991         while not done:\r
992             dialog = wx.TextEntryDialog(self, 'Enter a name for the new perspective:', 'Save perspective')\r
993             dialog.SetValue('New perspective')\r
994             if dialog.ShowModal() != wx.ID_OK:\r
995                 return\r
996             else:\r
997                 name = dialog.GetValue()\r
998 \r
999             if nameExists(name):\r
1000                 dialogConfirm = wx.MessageDialog(self, 'A file with this name already exists.\n\nDo you want to replace it?', 'Confirm', wx.YES_NO|wx.ICON_QUESTION|wx.CENTER)\r
1001                 if dialogConfirm.ShowModal() == wx.ID_YES:\r
1002                     done = True\r
1003             else:\r
1004                 done = True\r
1005 \r
1006         perspective = self._mgr.SavePerspective()\r
1007         self._SavePerspectiveToFile(name, perspective)\r
1008         self.config['perspectives']['active'] = name\r
1009         self.UpdatePerspectivesMenu()\r
1010 #        if nameExists(name):\r
1011 #            #check the corresponding menu item\r
1012 #            menu_item = self.GetPerspectiveMenuItem(name)\r
1013 #            #replace the perspectiveStr in _pespectives\r
1014 #            self._perspectives[name] = perspective\r
1015 #        else:\r
1016 #            #because we deal with radio items, we need to do some extra work\r
1017 #            #delete all menu items from the perspectives menu\r
1018 #            for item in self._perspectives_menu.GetMenuItems():\r
1019 #                self._perspectives_menu.DeleteItem(item)\r
1020 #            #recreate the perspectives menu\r
1021 #            self._perspectives_menu.Append(ID_SavePerspective, 'Save Perspective')\r
1022 #            self._perspectives_menu.Append(ID_DeletePerspective, 'Delete Perspective')\r
1023 #            self._perspectives_menu.AppendSeparator()\r
1024 #            #convert the perspectives dictionary into a list\r
1025 #            # the list contains:\r
1026 #            #[0]: name of the perspective\r
1027 #            #[1]: perspective\r
1028 #            perspectives_list = [key for key, value in self._perspectives.iteritems()]\r
1029 #            perspectives_list.append(name)\r
1030 #            perspectives_list.sort()\r
1031 #            #add all previous perspectives\r
1032 #            for index, item in enumerate(perspectives_list):\r
1033 #                menu_item = self._perspectives_menu.AppendRadioItem(ID_FirstPerspective + index, item)\r
1034 #                if item == name:\r
1035 #                    menu_item.Check()\r
1036 #            #add the new perspective to _perspectives\r
1037 #            self._perspectives[name] = perspective\r
1038 \r
1039     def OnSize(self, event):\r
1040         event.Skip()\r
1041 \r
1042     def OnTreeCtrlCommandsSelectionChanged(self, event):\r
1043         selected_item = event.GetItem()\r
1044         if selected_item is not None:\r
1045             plugin = ''\r
1046             section = ''\r
1047             #deregister/register the listener to avoid infinite loop\r
1048             evtmgr.eventManager.DeregisterListener(self.OnTreeCtrlCommandsSelectionChanged)\r
1049             self.panelCommands.CommandsTree.SelectItem(selected_item)\r
1050             evtmgr.eventManager.Register(self.OnTreeCtrlCommandsSelectionChanged, wx.EVT_TREE_SEL_CHANGED, self.panelCommands.CommandsTree)\r
1051             self.panelProperties.SelectedTreeItem = selected_item\r
1052             #if a command was clicked\r
1053             properties = []\r
1054             if not self.panelCommands.CommandsTree.ItemHasChildren(selected_item):\r
1055                 item_plugin = self.panelCommands.CommandsTree.GetItemParent(selected_item)\r
1056                 plugin = self.panelCommands.CommandsTree.GetItemText(item_plugin)\r
1057                 if self.configs.has_key(plugin):\r
1058                     #config = self.panelCommands.CommandsTree.GetPyData(item_plugin)\r
1059                     config = self.configs[plugin]\r
1060                     section = self.panelCommands.CommandsTree.GetItemText(selected_item)\r
1061                     #display docstring in help window\r
1062                     doc_string = eval('self.do_' + section + '.__doc__')\r
1063                     if section in config:\r
1064                         for option in config[section]:\r
1065                             properties.append([option, config[section][option]])\r
1066             else:\r
1067                 plugin = self.panelCommands.CommandsTree.GetItemText(selected_item)\r
1068                 if plugin != 'core':\r
1069                     doc_string = eval('plugins.' + plugin + '.' + plugin + 'Commands.__doc__')\r
1070                 else:\r
1071                     doc_string = 'The module "core" contains Hooke core functionality'\r
1072             if doc_string is not None:\r
1073                 self.panelAssistant.ChangeValue(doc_string)\r
1074             else:\r
1075                 self.panelAssistant.ChangeValue('')\r
1076             panels.propertyeditor.PropertyEditor.Initialize(self.panelProperties, properties)\r
1077             #save the currently selected command/plugin to the config file\r
1078             self.config['command']['command'] = section\r
1079             self.config['command']['plugin'] = plugin\r
1080 \r
1081     def OnTreeCtrlItemActivated(self, event):\r
1082         self.OnExecute(event)\r
1083 \r
1084     def OnUpdateNote(self, event):\r
1085         '''\r
1086         Saves the note to the active file.\r
1087         '''\r
1088         active_file = self.GetActiveFile()\r
1089         active_file.note = self.panelNote.Editor.GetValue()\r
1090 \r
1091     def OnView(self, event):\r
1092         menu_id = event.GetId()\r
1093         menu_item = self.MenuBar.FindItemById(menu_id)\r
1094         menu_label = menu_item.GetLabel()\r
1095 \r
1096         pane = self._mgr.GetPane(menu_label)\r
1097         pane.Show(not pane.IsShown())\r
1098         #if we don't do the following, the Folders pane does not resize properly on hide/show\r
1099         if pane.caption == 'Folders' and pane.IsShown() and pane.IsDocked():\r
1100             #folders_size = pane.GetSize()\r
1101             self.panelFolders.Fit()\r
1102         self._mgr.Update()\r
1103 \r
1104     def _clickize(self, xvector, yvector, index):\r
1105         '''\r
1106         Returns a ClickedPoint() object from an index and vectors of x, y coordinates\r
1107         '''\r
1108         point = lib.clickedpoint.ClickedPoint()\r
1109         point.index = index\r
1110         point.absolute_coords = xvector[index], yvector[index]\r
1111         point.find_graph_coords(xvector, yvector)\r
1112         return point\r
1113 \r
1114     def _delta(self, message='Click 2 points', whatset=lh.RETRACTION):\r
1115         '''\r
1116         Calculates the difference between two clicked points\r
1117         '''\r
1118         clicked_points = self._measure_N_points(N=2, message=message, whatset=whatset)\r
1119 \r
1120         plot = self.GetDisplayedPlotCorrected()\r
1121         curve = plot.curves[whatset]\r
1122 \r
1123         delta = lib.delta.Delta()\r
1124         delta.point1.x = clicked_points[0].graph_coords[0]\r
1125         delta.point1.y = clicked_points[0].graph_coords[1]\r
1126         delta.point2.x = clicked_points[1].graph_coords[0]\r
1127         delta.point2.y = clicked_points[1].graph_coords[1]\r
1128         delta.units.x = curve.units.x\r
1129         delta.units.y = curve.units.y\r
1130 \r
1131         return delta\r
1132 \r
1133     def _measure_N_points(self, N, message='', whatset=lh.RETRACTION):\r
1134         '''\r
1135         General helper function for N-points measurements\r
1136         By default, measurements are done on the retraction\r
1137         '''\r
1138         if message:\r
1139             dialog = wx.MessageDialog(None, message, 'Info', wx.OK)\r
1140             dialog.ShowModal()\r
1141 \r
1142         figure = self.GetActiveFigure()\r
1143 \r
1144         xvector = self.displayed_plot.curves[whatset].x\r
1145         yvector = self.displayed_plot.curves[whatset].y\r
1146 \r
1147         clicked_points = figure.ginput(N, timeout=-1, show_clicks=True)\r
1148 \r
1149         points = []\r
1150         for clicked_point in clicked_points:\r
1151             point = lib.clickedpoint.ClickedPoint()\r
1152             point.absolute_coords = clicked_point[0], clicked_point[1]\r
1153             point.dest = 0\r
1154             #TODO: make this optional?\r
1155             #so far, the clicked point is taken, not the corresponding data point\r
1156             point.find_graph_coords(xvector, yvector)\r
1157             point.is_line_edge = True\r
1158             point.is_marker = True\r
1159             points.append(point)\r
1160         return points\r
1161 \r
1162     def do_copylog(self):\r
1163         '''\r
1164         Copies all files in the current playlist that have a note to the destination folder.\r
1165         destination: select folder where you want the files to be copied\r
1166         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
1167         '''\r
1168         playlist = self.GetActivePlaylist()\r
1169         if playlist is not None:\r
1170             destination = self.GetStringFromConfig('core', 'copylog', 'destination')\r
1171             if not os.path.isdir(destination):\r
1172                 os.makedirs(destination)\r
1173             for current_file in playlist.files:\r
1174                 if current_file.note:\r
1175                     shutil.copy(current_file.filename, destination)\r
1176                     if current_file.driver.filetype == 'mfp1d':\r
1177                         filename = current_file.filename.replace('deflection', 'LVDT', 1)\r
1178                         path, name = os.path.split(filename)\r
1179                         filename = os.path.join(path, 'lvdt', name)\r
1180                         use_LVDT_folder = self.GetBoolFromConfig('core', 'copylog', 'use_LVDT_folder')\r
1181                         if use_LVDT_folder:\r
1182                             destination = os.path.join(destination, 'LVDT')\r
1183                         shutil.copy(filename, destination)\r
1184 \r
1185     def do_plotmanipulators(self):\r
1186         '''\r
1187         Please select the plotmanipulators you would like to use\r
1188         and define the order in which they will be applied to the data.\r
1189 \r
1190         Click 'Execute' to apply your changes.\r
1191         '''\r
1192         self.UpdatePlot()\r
1193 \r
1194     def do_preferences(self):\r
1195         '''\r
1196         Please set general preferences for Hooke here.\r
1197         hide_curve_extension: hides the extension of the force curve files.\r
1198                               not recommended for 'picoforce' files\r
1199         '''\r
1200         pass\r
1201 \r
1202     def do_test(self):\r
1203         '''\r
1204         Use this command for testing purposes. You find do_test in hooke.py.\r
1205         '''\r
1206         pass\r
1207 \r
1208     def do_version(self):\r
1209         '''\r
1210         VERSION\r
1211         ------\r
1212         Prints the current version and codename, plus library version. Useful for debugging.\r
1213         '''\r
1214         self.AppendToOutput('Hooke ' + __version__ + ' (' + __codename__ + ')')\r
1215         self.AppendToOutput('Released on: ' + __releasedate__)\r
1216         self.AppendToOutput('---')\r
1217         self.AppendToOutput('Python version: ' + python_version)\r
1218         self.AppendToOutput('WxPython version: ' + wx_version)\r
1219         self.AppendToOutput('Matplotlib version: ' + mpl_version)\r
1220         self.AppendToOutput('SciPy version: ' + scipy_version)\r
1221         self.AppendToOutput('NumPy version: ' + numpy_version)\r
1222         self.AppendToOutput('ConfigObj version: ' + configobj_version)\r
1223         self.AppendToOutput('wxPropertyGrid version: ' + '.'.join([str(PROPGRID_MAJOR), str(PROPGRID_MINOR), str(PROPGRID_RELEASE)]))\r
1224         self.AppendToOutput('---')\r
1225         self.AppendToOutput('Platform: ' + str(platform.uname()))\r
1226         self.AppendToOutput('******************************')\r
1227         self.AppendToOutput('Loaded plugins')\r
1228         self.AppendToOutput('---')\r
1229 \r
1230         #sort the plugins into alphabetical order\r
1231         plugins_list = [key for key, value in self.plugins.iteritems()]\r
1232         plugins_list.sort()\r
1233         for plugin in plugins_list:\r
1234             self.AppendToOutput(plugin)\r
1235 \r
1236     def UpdateNote(self):\r
1237         #update the note for the active file\r
1238         active_file = self.GetActiveFile()\r
1239         if active_file is not None:\r
1240             self.panelNote.Editor.SetValue(active_file.note)\r
1241 \r
1242     def UpdatePerspectivesMenu(self):\r
1243         #add perspectives to menubar and _perspectives\r
1244         perspectivesDirectory = os.path.join(lh.hookeDir, 'perspectives')\r
1245         self._perspectives = {}\r
1246         if os.path.isdir(perspectivesDirectory):\r
1247             perspectiveFileNames = os.listdir(perspectivesDirectory)\r
1248             for perspectiveFilename in perspectiveFileNames:\r
1249                 filename = lh.get_file_path(perspectiveFilename, ['perspectives'])\r
1250                 if os.path.isfile(filename):\r
1251                     perspectiveFile = open(filename, 'rU')\r
1252                     perspective = perspectiveFile.readline()\r
1253                     perspectiveFile.close()\r
1254                     if perspective:\r
1255                         name, extension = os.path.splitext(perspectiveFilename)\r
1256                         if extension == '.txt':\r
1257                             self._perspectives[name] = perspective\r
1258 \r
1259         #in case there are no perspectives\r
1260         if not self._perspectives:\r
1261             perspective = self._mgr.SavePerspective()\r
1262             self._perspectives['Default'] = perspective\r
1263             self._SavePerspectiveToFile('Default', perspective)\r
1264 \r
1265         selected_perspective = self.config['perspectives']['active']\r
1266         if not self._perspectives.has_key(selected_perspective):\r
1267             self.config['perspectives']['active'] = 'Default'\r
1268             selected_perspective = 'Default'\r
1269 \r
1270         perspectives_list = [key for key, value in self._perspectives.iteritems()]\r
1271         perspectives_list.sort()\r
1272 \r
1273         #get the Perspectives menu\r
1274         menu_position = self.MenuBar.FindMenu('Perspectives')\r
1275         menu = self.MenuBar.GetMenu(menu_position)\r
1276         #delete all menu items\r
1277         for item in menu.GetMenuItems():\r
1278             menu.DeleteItem(item)\r
1279         #rebuild the menu by adding the standard menu items\r
1280         menu.Append(ID_SavePerspective, 'Save Perspective')\r
1281         menu.Append(ID_DeletePerspective, 'Delete Perspective')\r
1282         menu.AppendSeparator()\r
1283         #add all previous perspectives\r
1284         for index, label in enumerate(perspectives_list):\r
1285             menu_item = menu.AppendRadioItem(ID_FirstPerspective + index, label)\r
1286             if label == selected_perspective:\r
1287                 self._RestorePerspective(label)\r
1288                 menu_item.Check(True)\r
1289 \r
1290     def UpdatePlaylistsTreeSelection(self):\r
1291         playlist = self.GetActivePlaylist()\r
1292         if playlist is not None:\r
1293             if playlist.index >= 0:\r
1294                 self.statusbar.SetStatusText(playlist.get_status_string(), 0)\r
1295                 self.UpdateNote()\r
1296                 self.UpdatePlot()\r
1297 \r
1298     def UpdatePlot(self, plot=None):\r
1299 \r
1300         def add_to_plot(curve, set_scale=True):\r
1301             if curve.visible and curve.x and curve.y:\r
1302                 #get the index of the subplot to use as destination\r
1303                 destination = (curve.destination.column - 1) * number_of_rows + curve.destination.row - 1\r
1304                 #set all parameters for the plot\r
1305                 axes_list[destination].set_title(curve.title)\r
1306                 if set_scale:\r
1307                     axes_list[destination].set_xlabel(curve.prefix.x + curve.units.x)\r
1308                     axes_list[destination].set_ylabel(curve.prefix.y + curve.units.y)\r
1309                     #set the formatting details for the scale\r
1310                     formatter_x = lib.curve.PrefixFormatter(curve.decimals.x, curve.prefix.x, use_zero)\r
1311                     formatter_y = lib.curve.PrefixFormatter(curve.decimals.y, curve.prefix.y, use_zero)\r
1312                     axes_list[destination].xaxis.set_major_formatter(formatter_x)\r
1313                     axes_list[destination].yaxis.set_major_formatter(formatter_y)\r
1314                 if curve.style == 'plot':\r
1315                     axes_list[destination].plot(curve.x, curve.y, color=curve.color, label=curve.label, lw=curve.linewidth, zorder=1)\r
1316                 if curve.style == 'scatter':\r
1317                     axes_list[destination].scatter(curve.x, curve.y, color=curve.color, label=curve.label, s=curve.size, zorder=2)\r
1318                 #add the legend if necessary\r
1319                 if curve.legend:\r
1320                     axes_list[destination].legend()\r
1321 \r
1322         if plot is None:\r
1323             active_file = self.GetActiveFile()\r
1324             if not active_file.driver:\r
1325                 #the first time we identify a file, the following need to be set\r
1326                 active_file.identify(self.drivers)\r
1327                 for curve in active_file.plot.curves:\r
1328                     curve.decimals.x = self.GetIntFromConfig('core', 'preferences', 'x_decimals')\r
1329                     curve.decimals.y = self.GetIntFromConfig('core', 'preferences', 'y_decimals')\r
1330                     curve.legend = self.GetBoolFromConfig('core', 'preferences', 'legend')\r
1331                     curve.prefix.x = self.GetStringFromConfig('core', 'preferences', 'x_prefix')\r
1332                     curve.prefix.y = self.GetStringFromConfig('core', 'preferences', 'y_prefix')\r
1333             if active_file.driver is None:\r
1334                 self.AppendToOutput('Invalid file: ' + active_file.filename)\r
1335                 return\r
1336             self.displayed_plot = copy.deepcopy(active_file.plot)\r
1337             #add raw curves to plot\r
1338             self.displayed_plot.raw_curves = copy.deepcopy(self.displayed_plot.curves)\r
1339             #apply all active plotmanipulators\r
1340             self.displayed_plot = self.ApplyPlotmanipulators(self.displayed_plot, active_file)\r
1341             #add corrected curves to plot\r
1342             self.displayed_plot.corrected_curves = copy.deepcopy(self.displayed_plot.curves)\r
1343         else:\r
1344             active_file = None\r
1345             self.displayed_plot = copy.deepcopy(plot)\r
1346 \r
1347         figure = self.GetActiveFigure()\r
1348         figure.clear()\r
1349 \r
1350         #use '0' instead of e.g. '0.00' for scales\r
1351         use_zero = self.GetBoolFromConfig('core', 'preferences', 'use_zero')\r
1352         #optionally remove the extension from the title of the plot\r
1353         hide_curve_extension = self.GetBoolFromConfig('core', 'preferences', 'hide_curve_extension')\r
1354         if hide_curve_extension:\r
1355             title = lh.remove_extension(self.displayed_plot.title)\r
1356         else:\r
1357             title = self.displayed_plot.title\r
1358         figure.suptitle(title, fontsize=14)\r
1359         #create the list of all axes necessary (rows and columns)\r
1360         axes_list =[]\r
1361         number_of_columns = max([curve.destination.column for curve in self.displayed_plot.curves])\r
1362         number_of_rows = max([curve.destination.row for curve in self.displayed_plot.curves])\r
1363         for index in range(number_of_rows * number_of_columns):\r
1364             axes_list.append(figure.add_subplot(number_of_rows, number_of_columns, index + 1))\r
1365 \r
1366         #add all curves to the corresponding plots\r
1367         for curve in self.displayed_plot.curves:\r
1368             add_to_plot(curve)\r
1369 \r
1370         #make sure the titles of 'subplots' do not overlap with the axis labels of the 'main plot'\r
1371         figure.subplots_adjust(hspace=0.3)\r
1372 \r
1373         #display results\r
1374         self.panelResults.ClearResults()\r
1375         if self.displayed_plot.results.has_key(self.results_str):\r
1376             for curve in self.displayed_plot.results[self.results_str].results:\r
1377                 add_to_plot(curve, set_scale=False)\r
1378             self.panelResults.DisplayResults(self.displayed_plot.results[self.results_str])\r
1379         else:\r
1380             self.panelResults.ClearResults()\r
1381         #refresh the plot\r
1382         figure.canvas.draw()\r
1383 \r
1384 if __name__ == '__main__':\r
1385 \r
1386     ## now, silence a deprecation warning for py2.3\r
1387     import warnings\r
1388     warnings.filterwarnings("ignore", "integer", DeprecationWarning, "wxPython.gdi")\r
1389 \r
1390     redirect = True\r
1391     if __debug__:\r
1392         redirect=False\r
1393 \r
1394     app = Hooke(redirect=redirect)\r
1395 \r
1396     app.MainLoop()\r