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