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