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