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