Bound events down through folders. To go: commands, propgrid, results
[hooke.git] / hooke / ui / gui / __init__.py
1 # Copyright\r
2 \r
3 """Defines :class:`GUI` providing a wxWindows interface to Hooke.\r
4 """\r
5 \r
6 WX_GOOD=['2.8']\r
7 \r
8 import wxversion\r
9 wxversion.select(WX_GOOD)\r
10 \r
11 import copy\r
12 import os.path\r
13 import platform\r
14 import shutil\r
15 import time\r
16 \r
17 import wx.html\r
18 import wx.aui as aui\r
19 import wx.lib.evtmgr as evtmgr\r
20 \r
21 \r
22 # wxPropertyGrid included in wxPython >= 2.9.1, until then, see\r
23 #   http://wxpropgrid.sourceforge.net/cgi-bin/index?page=download\r
24 # until then, we'll avoid it because of the *nix build problems.\r
25 #import wx.propgrid as wxpg\r
26 \r
27 from matplotlib.ticker import FuncFormatter\r
28 \r
29 from ... import version\r
30 from ...command import CommandExit, Exit, Command, Argument, StoreValue\r
31 from ...config import Setting\r
32 from ...interaction import Request, BooleanRequest, ReloadUserInterfaceConfig\r
33 from ...ui import UserInterface, CommandMessage\r
34 from . import panel as panel\r
35 from . import prettyformat as prettyformat\r
36 \r
37 \r
38 class Notebook (aui.AuiNotebook):\r
39     def __init__(self, *args, **kwargs):\r
40         super(Notebook, self).__init__(*args, **kwargs)\r
41         self.SetArtProvider(aui.AuiDefaultTabArt())\r
42         #uncomment if we find a nice icon\r
43         #page_bmp = wx.ArtProvider.GetBitmap(wx.ART_NORMAL_FILE, wx.ART_OTHER, wx.Size(16, 16))\r
44         self.AddPage(self._welcome_window(), 'Welcome')\r
45 \r
46     def _welcome_window(self):\r
47         #TODO: move into panel.welcome\r
48         ctrl = wx.html.HtmlWindow(parent=self, size=wx.Size(400, 300))\r
49         lines = [\r
50             '<h1>Welcome to Hooke</h1>',\r
51             '<h3>Features</h3>',\r
52             '<ul>',\r
53             '<li>View, annotate, measure force files</li>',\r
54             '<li>Worm-like chain fit of force peaks</li>',\r
55             '<li>Automatic convolution-based filtering of empty files</li>',\r
56             '<li>Automatic fit and measurement of multiple force peaks</li>',\r
57             '<li>Handles force-clamp force experiments (experimental)</li>',\r
58             '<li>It is extensible through plugins and drivers</li>',\r
59             '</ul>',\r
60             '<p>See the <a href="%s">DocumentationIndex</a>'\r
61             % 'http://code.google.com/p/hooke/wiki/DocumentationIndex',\r
62             'for more information</p>',\r
63             ]\r
64         ctrl.SetPage('\n'.join(lines))\r
65         return ctrl\r
66 \r
67 \r
68 class NavBar (wx.ToolBar):\r
69     def __init__(self, *args, **kwargs):\r
70         super(NavBar, self).__init__(*args, **kwargs)\r
71         self.SetToolBitmapSize(wx.Size(16,16))\r
72         self._c = {\r
73             'previous': self.AddLabelTool(\r
74                 id=wx.ID_PREVIEW_PREVIOUS,\r
75                 label='Previous',\r
76                 bitmap=wx.ArtProvider_GetBitmap(\r
77                     wx.ART_GO_BACK, wx.ART_OTHER, wx.Size(16, 16)),\r
78                 shortHelp='Previous curve'),\r
79             'next': self.AddLabelTool(\r
80                 id=wx.ID_PREVIEW_NEXT,\r
81                 label='Next',\r
82                 bitmap=wx.ArtProvider_GetBitmap(\r
83                     wx.ART_GO_FORWARD, wx.ART_OTHER, wx.Size(16, 16)),\r
84                 shortHelp='Next curve'),\r
85             }\r
86         self.Realize()\r
87 \r
88 \r
89 class FileMenu (wx.Menu):\r
90     def __init__(self, *args, **kwargs):\r
91         super(FileMenu, self).__init__(*args, **kwargs)\r
92         self._c = {'exit': self.Append(wx.ID_EXIT)}\r
93 \r
94 \r
95 class ViewMenu (wx.Menu):\r
96     def __init__(self, *args, **kwargs):\r
97         super(ViewMenu, self).__init__(*args, **kwargs)\r
98         self._c = {\r
99             'folders': self.AppendCheckItem(id=wx.ID_ANY, text='Folders\tF5'),\r
100             'playlist': self.AppendCheckItem(\r
101                 id=wx.ID_ANY, text='Playlists\tF6'),\r
102             'commands': self.AppendCheckItem(\r
103                 id=wx.ID_ANY, text='Commands\tF7'),\r
104             'assistant': self.AppendCheckItem(\r
105                 id=wx.ID_ANY, text='Assistant\tF9'),\r
106             'properties': self.AppendCheckItem(\r
107                 id=wx.ID_ANY, text='Properties\tF8'),\r
108             'results': self.AppendCheckItem(id=wx.ID_ANY, text='Results\tF10'),\r
109             'output': self.AppendCheckItem(id=wx.ID_ANY, text='Output\tF11'),\r
110             'note': self.AppendCheckItem(id=wx.ID_ANY, text='Note\tF12'),\r
111             }\r
112         for item in self._c.values():\r
113             item.Check()\r
114 \r
115 \r
116 class PerspectiveMenu (wx.Menu):\r
117     def __init__(self, *args, **kwargs):\r
118         super(PerspectiveMenu, self).__init__(*args, **kwargs)\r
119         self._c = {}\r
120 \r
121     def update(self, perspectives, selected, callback):\r
122         """Rebuild the perspectives menu.\r
123         """\r
124         for item in self.GetMenuItems():\r
125             self.UnBind(item)\r
126             self.DeleteItem(item)\r
127         self._c = {\r
128             'save': self.Append(id=wx.ID_ANY, text='Save Perspective'),\r
129             'delete': self.Append(id=wx.ID_ANY, text='Delete Perspective'),\r
130             }\r
131         self.AppendSeparator()\r
132         for label in perspectives:\r
133             self._c[label] = self.AppendRadioItem(id=wx.ID_ANY, text=label)\r
134             self.Bind(wx.EVT_MENU, callback, self._c[label])\r
135             if label == selected:\r
136                 self._c[label].Check(True)\r
137             \r
138 \r
139 class HelpMenu (wx.Menu):\r
140     def __init__(self, *args, **kwargs):\r
141         super(HelpMenu, self).__init__(*args, **kwargs)\r
142         self._c = {'about':self.Append(id=wx.ID_ABOUT)}\r
143 \r
144 \r
145 class MenuBar (wx.MenuBar):\r
146     def __init__(self, *args, **kwargs):\r
147         super(MenuBar, self).__init__(*args, **kwargs)\r
148         self._c = {\r
149             'file': FileMenu(),\r
150             'view': ViewMenu(),\r
151             'perspective': PerspectiveMenu(),\r
152             'help': HelpMenu(),\r
153             }\r
154         self.Append(self._c['file'], 'File')\r
155         self.Append(self._c['view'], 'View')\r
156         self.Append(self._c['perspective'], 'Perspective')\r
157         self.Append(self._c['help'], 'Help')\r
158 \r
159 \r
160 class StatusBar (wx.StatusBar):\r
161     def __init__(self, *args, **kwargs):\r
162         super(StatusBar, self).__init__(*args, **kwargs)\r
163         self.SetStatusWidths([-2, -3])\r
164         self.SetStatusText('Ready', 0)\r
165         self.SetStatusText(u'Welcome to Hooke (version %s)' % version(), 1)\r
166 \r
167 \r
168 class HookeFrame (wx.Frame):\r
169     def __init__(self, gui, commands, *args, **kwargs):\r
170         super(HookeFrame, self).__init__(*args, **kwargs)\r
171         self.gui = gui\r
172         self.commands = commands\r
173         self._perspectives = {}  # {name: perspective_str}\r
174         self._c = {}\r
175 \r
176         self.SetIcon(wx.Icon(self.gui.config['icon image'], wx.BITMAP_TYPE_ICO))\r
177 \r
178         # setup frame manager\r
179         self._c['manager'] = aui.AuiManager()\r
180         self._c['manager'].SetManagedWindow(self)\r
181 \r
182         # set the gradient and drag styles\r
183         self._c['manager'].GetArtProvider().SetMetric(\r
184             aui.AUI_DOCKART_GRADIENT_TYPE, aui.AUI_GRADIENT_NONE)\r
185         self._c['manager'].SetFlags(\r
186             self._c['manager'].GetFlags() ^ aui.AUI_MGR_TRANSPARENT_DRAG)\r
187 \r
188         # Min size for the frame itself isn't completely done.  See\r
189         # the end of FrameManager::Update() for the test code. For\r
190         # now, just hard code a frame minimum size.\r
191         self.SetMinSize(wx.Size(500, 500))\r
192 \r
193         self._setup_panels()\r
194         self._setup_toolbars()\r
195         self._c['manager'].Update()  # commit pending changes\r
196 \r
197         # Create the menubar after the panes so that the default\r
198         # perspective is created with all panes open\r
199         self._c['menu bar'] = MenuBar(\r
200             )\r
201         self.SetMenuBar(self._c['menu bar'])\r
202 \r
203         self._c['status bar'] = StatusBar(self, style=wx.ST_SIZEGRIP)\r
204 \r
205         self._update_perspectives()\r
206         self._bind_events()\r
207 \r
208         name = self.gui.config['active perspective']\r
209         return # TODO: cleanup\r
210         menu_item = self.GetPerspectiveMenuItem(name)\r
211         if menu_item is not None:\r
212             self._on_restore_perspective(menu_item)\r
213             #TODO: config setting to remember playlists from last session\r
214         self.playlists = self._c['playlists'].Playlists\r
215         self._displayed_plot = None\r
216         #load default list, if possible\r
217         self.do_loadlist(self.GetStringFromConfig('core', 'preferences', 'playlist'))\r
218 \r
219     def _setup_panels(self):\r
220         client_size = self.GetClientSize()\r
221         for label,p,style in [\r
222             ('folders', wx.GenericDirCtrl(\r
223                     parent=self,\r
224                     dir=self.gui.config['folders-workdir'],\r
225                     size=(200, 250),\r
226                     style=wx.DIRCTRL_SHOW_FILTERS,\r
227                     filter=self.gui.config['folders-filters'],\r
228                     defaultFilter=int(self.gui.config['folders-filter-index'])), 'left'),  #HACK: config should convert\r
229             ('playlists', panel.playlist.Playlist(self), 'left'),\r
230             ('note', panel.note.Note(self), 'left'),\r
231             ('notebook', Notebook(\r
232                     parent=self,\r
233                     pos=wx.Point(client_size.x, client_size.y),\r
234                     size=wx.Size(430, 200),\r
235                     style=aui.AUI_NB_DEFAULT_STYLE\r
236                     | aui.AUI_NB_TAB_EXTERNAL_MOVE | wx.NO_BORDER), 'center'),\r
237             ('commands', panel.commands.Commands(\r
238                     commands=self.commands,\r
239                     selected=self.gui.config['selected command'],\r
240                     parent=self,\r
241                     style=wx.WANTS_CHARS|wx.NO_BORDER,\r
242                     # WANTS_CHARS so the panel doesn't eat the Return key.\r
243                     size=(160, 200)), 'right'),\r
244             #('properties', panel.propertyeditor.PropertyEditor(self),'right'),\r
245             ('assistant', wx.TextCtrl(\r
246                     parent=self,\r
247                     pos=wx.Point(0, 0),\r
248                     size=wx.Size(150, 90),\r
249                     style=wx.NO_BORDER|wx.TE_MULTILINE), 'right'),\r
250             ('output', wx.TextCtrl(\r
251                     parent=self,\r
252                     pos=wx.Point(0, 0),\r
253                     size=wx.Size(150, 90),\r
254                     style=wx.NO_BORDER|wx.TE_MULTILINE), 'bottom'),\r
255             ('results', panel.results.Results(self), 'bottom'),\r
256             ]:\r
257             self._add_panel(label, p, style)\r
258         self._c['assistant'].SetEditable(False)\r
259 \r
260     def _add_panel(self, label, panel, style):\r
261         self._c[label] = panel\r
262         cap_label = label.capitalize()\r
263         info = aui.AuiPaneInfo().Name(cap_label).Caption(cap_label)\r
264         if style == 'left':\r
265             info.Left().CloseButton(True).MaximizeButton(False)\r
266         elif style == 'center':\r
267             info.CenterPane().PaneBorder(False)\r
268         elif style == 'right':\r
269             info.Right().CloseButton(True).MaximizeButton(False)\r
270         else:\r
271             assert style == 'bottom', style\r
272             info.Bottom().CloseButton(True).MaximizeButton(False)\r
273         self._c['manager'].AddPane(panel, info)\r
274 \r
275     def _setup_toolbars(self):\r
276         self._c['navbar'] = NavBar(self, style=wx.TB_FLAT | wx.TB_NODIVIDER)\r
277 \r
278         self._c['manager'].AddPane(\r
279             self._c['navbar'],\r
280             aui.AuiPaneInfo().Name('Navigation').Caption('Navigation'\r
281                 ).ToolbarPane().Top().Layer(1).Row(1).LeftDockable(False\r
282                 ).RightDockable(False))\r
283 \r
284     def _bind_events(self):\r
285         # TODO: figure out if we can use the eventManager for menu\r
286         # ranges and events of 'self' without raising an assertion\r
287         # fail error.\r
288         self.Bind(wx.EVT_ERASE_BACKGROUND, self._on_erase_background)\r
289         self.Bind(wx.EVT_SIZE, self._on_size)\r
290         self.Bind(wx.EVT_CLOSE, self._on_close)\r
291         self.Bind(wx.EVT_MENU, self._on_close, id=wx.ID_EXIT)\r
292         self.Bind(wx.EVT_MENU, self._on_about, id=wx.ID_ABOUT)\r
293         self.Bind(aui.EVT_AUI_PANE_CLOSE, self.OnPaneClose)\r
294         self.Bind(aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self._on_notebook_page_close)\r
295 \r
296         for value in self._c['menu bar']._c['view']._c.values():\r
297             self.Bind(wx.EVT_MENU_RANGE, self._on_view, value)\r
298 \r
299         self.Bind(wx.EVT_MENU, self._on_save_perspective,\r
300                   self._c['menu bar']._c['perspective']._c['save'])\r
301         self.Bind(wx.EVT_MENU, self._on_delete_perspective,\r
302                   self._c['menu bar']._c['perspective']._c['delete'])\r
303 \r
304         self.Bind(wx.EVT_TOOL, self._on_next, self._c['navbar']._c['next'])\r
305         self.Bind(wx.EVT_TOOL, self._on_previous,self._c['navbar']._c['previous'])\r
306 \r
307         treeCtrl = self._c['folders'].GetTreeCtrl()\r
308         treeCtrl.Bind(wx.EVT_LEFT_DCLICK, self._on_dir_ctrl_left_double_click)\r
309         \r
310         return # TODO: cleanup\r
311         self._c['playlists'].PlaylistsTree.Bind(wx.EVT_LEFT_DOWN, self.OnPlaylistsLeftDown)\r
312         self._c['playlists'].PlaylistsTree.Bind(wx.EVT_LEFT_DCLICK, self.OnPlaylistsLeftDclick)\r
313         #commands tree\r
314         evtmgr.eventManager.Register(self.OnExecute, wx.EVT_BUTTON, self._c['commands'].ExecuteButton)\r
315         evtmgr.eventManager.Register(self.OnTreeCtrlCommandsSelectionChanged, wx.EVT_TREE_SEL_CHANGED, self._c['commands']._c['tree'])\r
316         evtmgr.eventManager.Register(self.OnTreeCtrlItemActivated, wx.EVT_TREE_ITEM_ACTIVATED, self._c['commands']._c['tree'])\r
317         evtmgr.eventManager.Register(self.OnUpdateNote, wx.EVT_BUTTON, self.panelNote.UpdateButton)\r
318         #property editor\r
319         self.panelProperties.pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChanged)\r
320         #results panel\r
321         self.panelResults.results_list.OnCheckItem = self.OnResultsCheck\r
322 \r
323     def _GetActiveFileIndex(self):\r
324         lib.playlist.Playlist = self.GetActivePlaylist()\r
325         #get the selected item from the tree\r
326         selected_item = self._c['playlists'].PlaylistsTree.GetSelection()\r
327         #test if a playlist or a curve was double-clicked\r
328         if self._c['playlists'].PlaylistsTree.ItemHasChildren(selected_item):\r
329             return -1\r
330         else:\r
331             count = 0\r
332             selected_item = self._c['playlists'].PlaylistsTree.GetPrevSibling(selected_item)\r
333             while selected_item.IsOk():\r
334                 count += 1\r
335                 selected_item = self._c['playlists'].PlaylistsTree.GetPrevSibling(selected_item)\r
336             return count\r
337 \r
338     def _GetPlaylistTab(self, name):\r
339         for index, page in enumerate(self._c['notebook']._tabs._pages):\r
340             if page.caption == name:\r
341                 return index\r
342         return -1\r
343 \r
344     def _GetUniquePlaylistName(self, name):\r
345         playlist_name = name\r
346         count = 1\r
347         while playlist_name in self.playlists:\r
348             playlist_name = ''.join([name, str(count)])\r
349             count += 1\r
350         return playlist_name\r
351 \r
352     def _restore_perspective(self, name):\r
353         # TODO: cleanup\r
354         self.gui.config['active perspective'] = name  # TODO: push to engine's Hooke\r
355         self._c['manager'].LoadPerspective(self._perspectives[name])\r
356         self._c['manager'].Update()\r
357         for pane in self._c['manager'].GetAllPanes():\r
358             if pane.name in self._c['menu bar']._c['view']._c.keys():\r
359                 pane.Check(pane.window.IsShown())\r
360 \r
361     def _SavePerspectiveToFile(self, name, perspective):\r
362         filename = ''.join([name, '.txt'])\r
363         filename = lh.get_file_path(filename, ['perspective'])\r
364         perspectivesFile = open(filename, 'w')\r
365         perspectivesFile.write(perspective)\r
366         perspectivesFile.close()\r
367 \r
368     def AddPlaylist(self, playlist=None, name='Untitled'):\r
369         if playlist and playlist.count > 0:\r
370             playlist.name = self._GetUniquePlaylistName(name)\r
371             playlist.reset()\r
372             self.AddToPlaylists(playlist)\r
373 \r
374     def AddPlaylistFromFiles(self, files=[], name='Untitled'):\r
375         if files:\r
376             playlist = lib.playlist.Playlist(self, self.drivers)\r
377             for item in files:\r
378                 playlist.add_curve(item)\r
379         if playlist.count > 0:\r
380             playlist.name = self._GetUniquePlaylistName(name)\r
381             playlist.reset()\r
382             self.AddTayliss(playlist)\r
383 \r
384     def AddToPlaylists(self, playlist):\r
385         if playlist.count > 0:\r
386             #setup the playlist in the Playlist tree\r
387             tree_root = self._c['playlists'].PlaylistsTree.GetRootItem()\r
388             playlist_root = self._c['playlists'].PlaylistsTree.AppendItem(tree_root, playlist.name, 0)\r
389             #add all files to the Playlist tree\r
390 #            files = {}\r
391             hide_curve_extension = self.GetBoolFromConfig('core', 'preferences', 'hide_curve_extension')\r
392             for index, file_to_add in enumerate(playlist.files):\r
393                 #optionally remove the extension from the name of the curve\r
394                 if hide_curve_extension:\r
395                     file_to_add.name = lh.remove_extension(file_to_add.name)\r
396                 file_ID = self._c['playlists'].PlaylistsTree.AppendItem(playlist_root, file_to_add.name, 1)\r
397                 if index == playlist.index:\r
398                     self._c['playlists'].PlaylistsTree.SelectItem(file_ID)\r
399             playlist.reset()\r
400             #create the plot tab and add playlist to the dictionary\r
401             plotPanel = panel.plot.PlotPanel(self, ID_FirstPlot + len(self.playlists))\r
402             notebook_tab = self._c['notebook'].AddPage(plotPanel, playlist.name, True)\r
403             #tab_index = self._c['notebook'].GetSelection()\r
404             playlist.figure = plotPanel.get_figure()\r
405             self.playlists[playlist.name] = playlist\r
406             #self.playlists[playlist.name] = [playlist, figure]\r
407             self._c['playlists'].PlaylistsTree.Expand(playlist_root)\r
408             self.statusbar.SetStatusText(playlist.get_status_string(), 0)\r
409             self.UpdateNote()\r
410             self.UpdatePlot()\r
411 \r
412     def AppendToOutput(self, text):\r
413         self.panelOutput.AppendText(''.join([text, '\n']))\r
414 \r
415     def AppliesPlotmanipulator(self, name):\r
416         '''\r
417         Returns True if the plotmanipulator 'name' is applied, False otherwise\r
418         name does not contain 'plotmanip_', just the name of the plotmanipulator (e.g. 'flatten')\r
419         '''\r
420         return self.GetBoolFromConfig('core', 'plotmanipulators', name)\r
421 \r
422     def ApplyPlotmanipulators(self, plot, plot_file):\r
423         '''\r
424         Apply all active plotmanipulators.\r
425         '''\r
426         if plot is not None and plot_file is not None:\r
427             manipulated_plot = copy.deepcopy(plot)\r
428             for plotmanipulator in self.plotmanipulators:\r
429                 if self.GetBoolFromConfig('core', 'plotmanipulators', plotmanipulator.name):\r
430                     manipulated_plot = plotmanipulator.method(manipulated_plot, plot_file)\r
431             return manipulated_plot\r
432 \r
433     def DeleteFromPlaylists(self, name):\r
434         if name in self.playlists:\r
435             del self.playlists[name]\r
436         tree_root = self._c['playlists'].PlaylistsTree.GetRootItem()\r
437         item, cookie = self._c['playlists'].PlaylistsTree.GetFirstChild(tree_root)\r
438         while item.IsOk():\r
439             playlist_name = self._c['playlists'].PlaylistsTree.GetItemText(item)\r
440             if playlist_name == name:\r
441                 try:\r
442                     self._c['playlists'].PlaylistsTree.Delete(item)\r
443                 except:\r
444                     pass\r
445             item = self._c['playlists'].PlaylistsTree.GetNextSibling(item)\r
446 \r
447     def GetActiveFigure(self):\r
448         playlist_name = self.GetActivePlaylistName()\r
449         figure = self.playlists[playlist_name].figure\r
450         if figure is not None:\r
451             return figure\r
452         return None\r
453 \r
454     def GetActiveFile(self):\r
455         playlist = self.GetActivePlaylist()\r
456         if playlist is not None:\r
457             return playlist.get_active_file()\r
458         return None\r
459 \r
460     def GetActivePlaylist(self):\r
461         playlist_name = self.GetActivePlaylistName()\r
462         if playlist_name in self.playlists:\r
463             return self.playlists[playlist_name]\r
464         return None\r
465 \r
466     def GetActivePlaylistName(self):\r
467         #get the selected item from the tree\r
468         selected_item = self._c['playlists'].PlaylistsTree.GetSelection()\r
469         #test if a playlist or a curve was double-clicked\r
470         if self._c['playlists'].PlaylistsTree.ItemHasChildren(selected_item):\r
471             playlist_item = selected_item\r
472         else:\r
473             #get the name of the playlist\r
474             playlist_item = self._c['playlists'].PlaylistsTree.GetItemParent(selected_item)\r
475         #now we have a playlist\r
476         return self._c['playlists'].PlaylistsTree.GetItemText(playlist_item)\r
477 \r
478     def GetActivePlot(self):\r
479         playlist = self.GetActivePlaylist()\r
480         if playlist is not None:\r
481             return playlist.get_active_file().plot\r
482         return None\r
483 \r
484     def GetDisplayedPlot(self):\r
485         plot = copy.deepcopy(self.displayed_plot)\r
486         #plot.curves = []\r
487         #plot.curves = copy.deepcopy(plot.curves)\r
488         return plot\r
489 \r
490     def GetDisplayedPlotCorrected(self):\r
491         plot = copy.deepcopy(self.displayed_plot)\r
492         plot.curves = []\r
493         plot.curves = copy.deepcopy(plot.corrected_curves)\r
494         return plot\r
495 \r
496     def GetDisplayedPlotRaw(self):\r
497         plot = copy.deepcopy(self.displayed_plot)\r
498         plot.curves = []\r
499         plot.curves = copy.deepcopy(plot.raw_curves)\r
500         return plot\r
501 \r
502     def GetDockArt(self):\r
503         return self._c['manager'].GetArtProvider()\r
504 \r
505     def GetPlotmanipulator(self, name):\r
506         '''\r
507         Returns a plot manipulator function from its name\r
508         '''\r
509         for plotmanipulator in self.plotmanipulators:\r
510             if plotmanipulator.name == name:\r
511                 return plotmanipulator\r
512         return None\r
513 \r
514     def GetPerspectiveMenuItem(self, name):\r
515         if self._perspectives.has_key(name):\r
516             perspectives_list = [key for key, value in self._perspectives.iteritems()]\r
517             perspectives_list.sort()\r
518             index = perspectives_list.index(name)\r
519             perspective_Id = ID_FirstPerspective + index\r
520             menu_item = self.MenuBar.FindItemById(perspective_Id)\r
521             return menu_item\r
522         else:\r
523             return None\r
524 \r
525     def HasPlotmanipulator(self, name):\r
526         '''\r
527         returns True if the plotmanipulator 'name' is loaded, False otherwise\r
528         '''\r
529         for plotmanipulator in self.plotmanipulators:\r
530             if plotmanipulator.command == name:\r
531                 return True\r
532         return False\r
533 \r
534     def _on_about(self, event):\r
535         message = 'Hooke\n\n'+\\r
536             'A free, open source data analysis platform\n\n'+\\r
537             'Copyright 2006-2008 by Massimo Sandal\n'+\\r
538             'Copyright 2010 by Dr. Rolf Schmidt\n\n'+\\r
539             'Hooke is released under the GNU General Public License version 2.'\r
540         dialog = wx.MessageDialog(self, message, 'About Hooke', wx.OK | wx.ICON_INFORMATION)\r
541         dialog.ShowModal()\r
542         dialog.Destroy()\r
543 \r
544     def _on_close(self, event):\r
545         # apply changes\r
546         self.gui.config['main height'] = str(self.GetSize().GetHeight())\r
547         self.gui.config['main left'] = str(self.GetPosition()[0])\r
548         self.gui.config['main top'] = str(self.GetPosition()[1])\r
549         self.gui.config['main width'] = str(self.GetSize().GetWidth())\r
550         # push changes back to Hooke.config?\r
551         self._c['manager'].UnInit()\r
552         del self._c['manager']\r
553         self.Destroy()\r
554 \r
555     def _update_perspectives(self):\r
556         """Add perspectives to menubar and _perspectives.\r
557         """\r
558         self._perspectives = {\r
559             'Default': self._c['manager'].SavePerspective(),\r
560             }\r
561         path = self.gui.config['perspective path']\r
562         if os.path.isdir(path):\r
563             files = sorted(os.listdir(path))\r
564             for fname in files:\r
565                 name, extension = os.path.splitext(fname)\r
566                 if extension != '.txt':\r
567                     continue\r
568                 fpath = os.path.join(path, fpath)\r
569                 if not os.path.isfile(fpath):\r
570                     continue\r
571                 perspective = None\r
572                 with open(fpath, 'rU') as f:\r
573                     perspective = f.readline()\r
574                 if perspective:\r
575                     self._perspectives[name] = perspective\r
576 \r
577         selected_perspective = self.gui.config['active perspective']\r
578         if not self._perspectives.has_key(selected_perspective):\r
579             self.gui.config['active perspective'] = 'Default'  # TODO: push to engine's Hooke\r
580 \r
581         self._update_perspective_menu()\r
582         self._restore_perspective(selected_perspective)\r
583 \r
584     def _update_perspective_menu(self):\r
585         self._c['menu bar']._c['perspective'].update(\r
586             sorted(self._perspectives.keys()),\r
587             self.gui.config['active perspective'],\r
588             self._on_restore_perspective)\r
589 \r
590     def _on_restore_perspective(self, event):\r
591         name = self.MenuBar.FindItemById(event.GetId()).GetLabel()\r
592         self._restore_perspective(name)\r
593 \r
594     def _on_save_perspective(self, event):\r
595         def nameExists(name):\r
596             menu_position = self.MenuBar.FindMenu('Perspective')\r
597             menu = self.MenuBar.GetMenu(menu_position)\r
598             for item in menu.GetMenuItems():\r
599                 if item.GetText() == name:\r
600                     return True\r
601             return False\r
602 \r
603         done = False\r
604         while not done:\r
605             dialog = wx.TextEntryDialog(self, 'Enter a name for the new perspective:', 'Save perspective')\r
606             dialog.SetValue('New perspective')\r
607             if dialog.ShowModal() != wx.ID_OK:\r
608                 return\r
609             else:\r
610                 name = dialog.GetValue()\r
611 \r
612             if nameExists(name):\r
613                 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
614                 if dialogConfirm.ShowModal() == wx.ID_YES:\r
615                     done = True\r
616             else:\r
617                 done = True\r
618 \r
619         perspective = self._c['manager'].SavePerspective()\r
620         self._SavePerspectiveToFile(name, perspective)\r
621         self.gui.config['active perspectives'] = name\r
622         self._update_perspective_menu()\r
623 #        if nameExists(name):\r
624 #            #check the corresponding menu item\r
625 #            menu_item = self.GetPerspectiveMenuItem(name)\r
626 #            #replace the perspectiveStr in _pespectives\r
627 #            self._perspectives[name] = perspective\r
628 #        else:\r
629 #            #because we deal with radio items, we need to do some extra work\r
630 #            #delete all menu items from the perspectives menu\r
631 #            for item in self._perspectives_menu.GetMenuItems():\r
632 #                self._perspectives_menu.DeleteItem(item)\r
633 #            #recreate the perspectives menu\r
634 #            self._perspectives_menu.Append(ID_SavePerspective, 'Save Perspective')\r
635 #            self._perspectives_menu.Append(ID_DeletePerspective, 'Delete Perspective')\r
636 #            self._perspectives_menu.AppendSeparator()\r
637 #            #convert the perspectives dictionary into a list\r
638 #            # the list contains:\r
639 #            #[0]: name of the perspective\r
640 #            #[1]: perspective\r
641 #            perspectives_list = [key for key, value in self._perspectives.iteritems()]\r
642 #            perspectives_list.append(name)\r
643 #            perspectives_list.sort()\r
644 #            #add all previous perspectives\r
645 #            for index, item in enumerate(perspectives_list):\r
646 #                menu_item = self._perspectives_menu.AppendRadioItem(ID_FirstPerspective + index, item)\r
647 #                if item == name:\r
648 #                    menu_item.Check()\r
649 #            #add the new perspective to _perspectives\r
650 #            self._perspectives[name] = perspective\r
651 \r
652     def _on_delete_perspective(self, event):\r
653         dialog = panel.selection.Selection(\r
654             options=sorted(os.listdir(self.gui.config['perspective path'])),\r
655             message="\nPlease check the perspectives\n\nyou want to delete and click 'Delete'.\n",\r
656             button_id=wx.ID_DELETE,\r
657             button_callback=self._on_delete_perspective,\r
658             parent=self,\r
659             label='Delete perspective(s)',\r
660             style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)\r
661         dialog.CenterOnScreen()\r
662         dialog.ShowModal()\r
663         dialog.Destroy()\r
664         self._update_perspective_menu()\r
665         # Unfortunately, there is a bug in wxWidgets for win32 (Ticket #3258\r
666         #   http://trac.wxwidgets.org/ticket/3258 \r
667         # ) that makes the radio item indicator in the menu disappear.\r
668         # The code should be fine once this issue is fixed.\r
669 \r
670     def _on_delete_perspective(self, event, items, selected_items):\r
671         for item in selected_items:\r
672             self._perspectives.remove(item)\r
673             if item == self.gui.config['active perspective']:\r
674                 self.gui.config['active perspective'] = 'Default'\r
675             path = os.path.join(self.gui.config['perspective path'],\r
676                                 item+'.txt')\r
677             remove(path)\r
678         self._update_perspective_menu()\r
679 \r
680     def _on_dir_ctrl_left_double_click(self, event):\r
681         file_path = self.panelFolders.GetPath()\r
682         if os.path.isfile(file_path):\r
683             if file_path.endswith('.hkp'):\r
684                 self.do_loadlist(file_path)\r
685         event.Skip()\r
686 \r
687     def _on_erase_background(self, event):\r
688         event.Skip()\r
689 \r
690     def OnExecute(self, event):\r
691         item = self._c['commands']._c['tree'].GetSelection()\r
692         if item.IsOk():\r
693             if not self._c['commands']._c['tree'].ItemHasChildren(item):\r
694                 item_text = self._c['commands']._c['tree'].GetItemText(item)\r
695                 command = ''.join(['self.do_', item_text, '()'])\r
696                 #self.AppendToOutput(command + '\n')\r
697                 exec(command)\r
698 \r
699     def OnExit(self, event):\r
700         self.Close()\r
701 \r
702     def _on_next(self, event):\r
703         '''\r
704         NEXT\r
705         Go to the next curve in the playlist.\r
706         If we are at the last curve, we come back to the first.\r
707         -----\r
708         Syntax: next, n\r
709         '''\r
710         selected_item = self._c['playlists'].PlaylistsTree.GetSelection()\r
711         if self._c['playlists'].PlaylistsTree.ItemHasChildren(selected_item):\r
712             #GetFirstChild returns a tuple\r
713             #we only need the first element\r
714             next_item = self._c['playlists'].PlaylistsTree.GetFirstChild(selected_item)[0]\r
715         else:\r
716             next_item = self._c['playlists'].PlaylistsTree.GetNextSibling(selected_item)\r
717             if not next_item.IsOk():\r
718                 parent_item = self._c['playlists'].PlaylistsTree.GetItemParent(selected_item)\r
719                 #GetFirstChild returns a tuple\r
720                 #we only need the first element\r
721                 next_item = self._c['playlists'].PlaylistsTree.GetFirstChild(parent_item)[0]\r
722         self._c['playlists'].PlaylistsTree.SelectItem(next_item, True)\r
723         if not self._c['playlists'].PlaylistsTree.ItemHasChildren(selected_item):\r
724             playlist = self.GetActivePlaylist()\r
725             if playlist.count > 1:\r
726                 playlist.next()\r
727                 self.statusbar.SetStatusText(playlist.get_status_string(), 0)\r
728                 self.UpdateNote()\r
729                 self.UpdatePlot()\r
730 \r
731     def _on_notebook_page_close(self, event):\r
732         ctrl = event.GetEventObject()\r
733         playlist_name = ctrl.GetPageText(ctrl._curpage)\r
734         self.DeleteFromPlaylists(playlist_name)\r
735 \r
736     def OnPaneClose(self, event):\r
737         event.Skip()\r
738 \r
739     def OnPlaylistsLeftDclick(self, event):\r
740         if self._c['playlists'].PlaylistsTree.Count > 0:\r
741             playlist_name = self.GetActivePlaylistName()\r
742             #if that playlist already exists\r
743             #we check if it is the active playlist (ie selected in panelPlaylists)\r
744             #and switch to it if necessary\r
745             if playlist_name in self.playlists:\r
746                 index = self._c['notebook'].GetSelection()\r
747                 current_playlist = self._c['notebook'].GetPageText(index)\r
748                 if current_playlist != playlist_name:\r
749                     index = self._GetPlaylistTab(playlist_name)\r
750                     self._c['notebook'].SetSelection(index)\r
751                 #if a curve was double-clicked\r
752                 item = self._c['playlists'].PlaylistsTree.GetSelection()\r
753                 if not self._c['playlists'].PlaylistsTree.ItemHasChildren(item):\r
754                     index = self._GetActiveFileIndex()\r
755                 else:\r
756                     index = 0\r
757                 if index >= 0:\r
758                     playlist = self.GetActivePlaylist()\r
759                     playlist.index = index\r
760                     self.statusbar.SetStatusText(playlist.get_status_string(), 0)\r
761                     self.UpdateNote()\r
762                     self.UpdatePlot()\r
763             #if you uncomment the following line, the tree will collapse/expand as well\r
764             #event.Skip()\r
765 \r
766     def OnPlaylistsLeftDown(self, event):\r
767         hit_item, hit_flags = self._c['playlists'].PlaylistsTree.HitTest(event.GetPosition())\r
768         if (hit_flags & wx.TREE_HITTEST_ONITEM) != 0:\r
769             self._c['playlists'].PlaylistsTree.SelectItem(hit_item)\r
770             playlist_name = self.GetActivePlaylistName()\r
771             playlist = self.GetActivePlaylist()\r
772             #if a curve was clicked\r
773             item = self._c['playlists'].PlaylistsTree.GetSelection()\r
774             if not self._c['playlists'].PlaylistsTree.ItemHasChildren(item):\r
775                 index = self._GetActiveFileIndex()\r
776                 if index >= 0:\r
777                     playlist.index = index\r
778             self.playlists[playlist_name] = playlist\r
779         event.Skip()\r
780 \r
781     def _on_previous(self, event):\r
782         '''\r
783         PREVIOUS\r
784         Go to the previous curve in the playlist.\r
785         If we are at the first curve, we jump to the last.\r
786         -------\r
787         Syntax: previous, p\r
788         '''\r
789         #playlist = self.playlists[self.GetActivePlaylistName()][0]\r
790         #select the previous curve and tell the user if we wrapped around\r
791         #self.AppendToOutput(playlist.previous())\r
792         selected_item = self._c['playlists'].PlaylistsTree.GetSelection()\r
793         if self._c['playlists'].PlaylistsTree.ItemHasChildren(selected_item):\r
794             previous_item = self._c['playlists'].PlaylistsTree.GetLastChild(selected_item)\r
795         else:\r
796             previous_item = self._c['playlists'].PlaylistsTree.GetPrevSibling(selected_item)\r
797             if not previous_item.IsOk():\r
798                 parent_item = self._c['playlists'].PlaylistsTree.GetItemParent(selected_item)\r
799                 previous_item = self._c['playlists'].PlaylistsTree.GetLastChild(parent_item)\r
800         self._c['playlists'].PlaylistsTree.SelectItem(previous_item, True)\r
801         playlist = self.GetActivePlaylist()\r
802         if playlist.count > 1:\r
803             playlist.previous()\r
804             self.statusbar.SetStatusText(playlist.get_status_string(), 0)\r
805             self.UpdateNote()\r
806             self.UpdatePlot()\r
807 \r
808     def OnPropGridChanged (self, event):\r
809         prop = event.GetProperty()\r
810         if prop:\r
811             item_section = self.panelProperties.SelectedTreeItem\r
812             item_plugin = self._c['commands']._c['tree'].GetItemParent(item_section)\r
813             plugin = self._c['commands']._c['tree'].GetItemText(item_plugin)\r
814             config = self.gui.config[plugin]\r
815             property_section = self._c['commands']._c['tree'].GetItemText(item_section)\r
816             property_key = prop.GetName()\r
817             property_value = prop.GetDisplayedString()\r
818 \r
819             config[property_section][property_key]['value'] = property_value\r
820 \r
821     def OnResultsCheck(self, index, flag):\r
822         results = self.GetActivePlot().results\r
823         if results.has_key(self.results_str):\r
824             results[self.results_str].results[index].visible = flag\r
825             results[self.results_str].update()\r
826             self.UpdatePlot()\r
827 \r
828 \r
829     def _on_size(self, event):\r
830         event.Skip()\r
831 \r
832     def OnTreeCtrlCommandsSelectionChanged(self, event):\r
833         selected_item = event.GetItem()\r
834         if selected_item is not None:\r
835             plugin = ''\r
836             section = ''\r
837             #deregister/register the listener to avoid infinite loop\r
838             evtmgr.eventManager.DeregisterListener(self.OnTreeCtrlCommandsSelectionChanged)\r
839             self._c['commands']._c['tree'].SelectItem(selected_item)\r
840             evtmgr.eventManager.Register(self.OnTreeCtrlCommandsSelectionChanged, wx.EVT_TREE_SEL_CHANGED, self._c['commands']._c['tree'])\r
841             self.panelProperties.SelectedTreeItem = selected_item\r
842             #if a command was clicked\r
843             properties = []\r
844             if not self._c['commands']._c['tree'].ItemHasChildren(selected_item):\r
845                 item_plugin = self._c['commands']._c['tree'].GetItemParent(selected_item)\r
846                 plugin = self._c['commands']._c['tree'].GetItemText(item_plugin)\r
847                 if self.gui.config.has_key(plugin):\r
848                     #config = self._c['commands']._c['tree'].GetPyData(item_plugin)\r
849                     config = self.gui.config[plugin]\r
850                     section = self._c['commands']._c['tree'].GetItemText(selected_item)\r
851                     #display docstring in help window\r
852                     doc_string = eval('self.do_' + section + '.__doc__')\r
853                     if section in config:\r
854                         for option in config[section]:\r
855                             properties.append([option, config[section][option]])\r
856             else:\r
857                 plugin = self._c['commands']._c['tree'].GetItemText(selected_item)\r
858                 if plugin != 'core':\r
859                     doc_string = eval('plugins.' + plugin + '.' + plugin + 'Commands.__doc__')\r
860                 else:\r
861                     doc_string = 'The module "core" contains Hooke core functionality'\r
862             if doc_string is not None:\r
863                 self.panelAssistant.ChangeValue(doc_string)\r
864             else:\r
865                 self.panelAssistant.ChangeValue('')\r
866             panel.propertyeditor.PropertyEditor.Initialize(self.panelProperties, properties)\r
867             #save the currently selected command/plugin to the config file\r
868             self.gui.config['command']['command'] = section\r
869             self.gui.config['command']['plugin'] = plugin\r
870 \r
871     def OnTreeCtrlItemActivated(self, event):\r
872         self.OnExecute(event)\r
873 \r
874     def OnUpdateNote(self, event):\r
875         '''\r
876         Saves the note to the active file.\r
877         '''\r
878         active_file = self.GetActiveFile()\r
879         active_file.note = self.panelNote.Editor.GetValue()\r
880 \r
881     def _on_view(self, event):\r
882         menu_id = event.GetId()\r
883         menu_item = self.MenuBar.FindItemById(menu_id)\r
884         menu_label = menu_item.GetLabel()\r
885 \r
886         pane = self._c['manager'].GetPane(menu_label)\r
887         pane.Show(not pane.IsShown())\r
888         #if we don't do the following, the Folders pane does not resize properly on hide/show\r
889         if pane.caption == 'Folders' and pane.IsShown() and pane.IsDocked():\r
890             #folders_size = pane.GetSize()\r
891             self.panelFolders.Fit()\r
892         self._c['manager'].Update()\r
893 \r
894     def _clickize(self, xvector, yvector, index):\r
895         '''\r
896         Returns a ClickedPoint() object from an index and vectors of x, y coordinates\r
897         '''\r
898         point = lib.clickedpoint.ClickedPoint()\r
899         point.index = index\r
900         point.absolute_coords = xvector[index], yvector[index]\r
901         point.find_graph_coords(xvector, yvector)\r
902         return point\r
903 \r
904     def _delta(self, message='Click 2 points', block=0):\r
905         '''\r
906         Calculates the difference between two clicked points\r
907         '''\r
908         clicked_points = self._measure_N_points(N=2, message=message, block=block)\r
909 \r
910         plot = self.GetDisplayedPlotCorrected()\r
911         curve = plot.curves[block]\r
912 \r
913         delta = lib.delta.Delta()\r
914         delta.point1.x = clicked_points[0].graph_coords[0]\r
915         delta.point1.y = clicked_points[0].graph_coords[1]\r
916         delta.point2.x = clicked_points[1].graph_coords[0]\r
917         delta.point2.y = clicked_points[1].graph_coords[1]\r
918         delta.units.x = curve.units.x\r
919         delta.units.y = curve.units.y\r
920 \r
921         return delta\r
922 \r
923     def _measure_N_points(self, N, message='', block=0):\r
924         '''\r
925         General helper function for N-points measurements\r
926         By default, measurements are done on the retraction\r
927         '''\r
928         if message:\r
929             dialog = wx.MessageDialog(None, message, 'Info', wx.OK)\r
930             dialog.ShowModal()\r
931 \r
932         figure = self.GetActiveFigure()\r
933 \r
934         xvector = self.displayed_plot.curves[block].x\r
935         yvector = self.displayed_plot.curves[block].y\r
936 \r
937         clicked_points = figure.ginput(N, timeout=-1, show_clicks=True)\r
938 \r
939         points = []\r
940         for clicked_point in clicked_points:\r
941             point = lib.clickedpoint.ClickedPoint()\r
942             point.absolute_coords = clicked_point[0], clicked_point[1]\r
943             point.dest = 0\r
944             #TODO: make this optional?\r
945             #so far, the clicked point is taken, not the corresponding data point\r
946             point.find_graph_coords(xvector, yvector)\r
947             point.is_line_edge = True\r
948             point.is_marker = True\r
949             points.append(point)\r
950         return points\r
951 \r
952     def do_copylog(self):\r
953         '''\r
954         Copies all files in the current playlist that have a note to the destination folder.\r
955         destination: select folder where you want the files to be copied\r
956         use_LVDT_folder: when checked, the files will be copied to a folder called 'LVDT' in the destination folder (for MFP-1D files only)\r
957         '''\r
958         playlist = self.GetActivePlaylist()\r
959         if playlist is not None:\r
960             destination = self.GetStringFromConfig('core', 'copylog', 'destination')\r
961             if not os.path.isdir(destination):\r
962                 os.makedirs(destination)\r
963             for current_file in playlist.files:\r
964                 if current_file.note:\r
965                     shutil.copy(current_file.filename, destination)\r
966                     if current_file.driver.filetype == 'mfp1d':\r
967                         filename = current_file.filename.replace('deflection', 'LVDT', 1)\r
968                         path, name = os.path.split(filename)\r
969                         filename = os.path.join(path, 'lvdt', name)\r
970                         use_LVDT_folder = self.GetBoolFromConfig('core', 'copylog', 'use_LVDT_folder')\r
971                         if use_LVDT_folder:\r
972                             destination = os.path.join(destination, 'LVDT')\r
973                         shutil.copy(filename, destination)\r
974 \r
975     def do_plotmanipulators(self):\r
976         '''\r
977         Please select the plotmanipulators you would like to use\r
978         and define the order in which they will be applied to the data.\r
979 \r
980         Click 'Execute' to apply your changes.\r
981         '''\r
982         self.UpdatePlot()\r
983 \r
984     def do_preferences(self):\r
985         '''\r
986         Please set general preferences for Hooke here.\r
987         hide_curve_extension: hides the extension of the force curve files.\r
988                               not recommended for 'picoforce' files\r
989         '''\r
990         pass\r
991 \r
992     def do_test(self):\r
993         '''\r
994         Use this command for testing purposes. You find do_test in hooke.py.\r
995         '''\r
996         pass\r
997 \r
998     def do_version(self):\r
999         '''\r
1000         VERSION\r
1001         ------\r
1002         Prints the current version and codename, plus library version. Useful for debugging.\r
1003         '''\r
1004         self.AppendToOutput('Hooke ' + __version__ + ' (' + __codename__ + ')')\r
1005         self.AppendToOutput('Released on: ' + __releasedate__)\r
1006         self.AppendToOutput('---')\r
1007         self.AppendToOutput('Python version: ' + python_version)\r
1008         self.AppendToOutput('WxPython version: ' + wx_version)\r
1009         self.AppendToOutput('Matplotlib version: ' + mpl_version)\r
1010         self.AppendToOutput('SciPy version: ' + scipy_version)\r
1011         self.AppendToOutput('NumPy version: ' + numpy_version)\r
1012         self.AppendToOutput('ConfigObj version: ' + configobj_version)\r
1013         self.AppendToOutput('wxPropertyGrid version: ' + '.'.join([str(PROPGRID_MAJOR), str(PROPGRID_MINOR), str(PROPGRID_RELEASE)]))\r
1014         self.AppendToOutput('---')\r
1015         self.AppendToOutput('Platform: ' + str(platform.uname()))\r
1016         self.AppendToOutput('******************************')\r
1017         self.AppendToOutput('Loaded plugins')\r
1018         self.AppendToOutput('---')\r
1019 \r
1020         #sort the plugins into alphabetical order\r
1021         plugins_list = [key for key, value in self.plugins.iteritems()]\r
1022         plugins_list.sort()\r
1023         for plugin in plugins_list:\r
1024             self.AppendToOutput(plugin)\r
1025 \r
1026     def UpdateNote(self):\r
1027         #update the note for the active file\r
1028         active_file = self.GetActiveFile()\r
1029         if active_file is not None:\r
1030             self.panelNote.Editor.SetValue(active_file.note)\r
1031 \r
1032     def UpdatePlaylistsTreeSelection(self):\r
1033         playlist = self.GetActivePlaylist()\r
1034         if playlist is not None:\r
1035             if playlist.index >= 0:\r
1036                 self.statusbar.SetStatusText(playlist.get_status_string(), 0)\r
1037                 self.UpdateNote()\r
1038                 self.UpdatePlot()\r
1039 \r
1040     def UpdatePlot(self, plot=None):\r
1041 \r
1042         def add_to_plot(curve, set_scale=True):\r
1043             if curve.visible and curve.x and curve.y:\r
1044                 #get the index of the subplot to use as destination\r
1045                 destination = (curve.destination.column - 1) * number_of_rows + curve.destination.row - 1\r
1046                 #set all parameters for the plot\r
1047                 axes_list[destination].set_title(curve.title)\r
1048                 if set_scale:\r
1049                     axes_list[destination].set_xlabel(curve.prefix.x + curve.units.x)\r
1050                     axes_list[destination].set_ylabel(curve.prefix.y + curve.units.y)\r
1051                     #set the formatting details for the scale\r
1052                     formatter_x = lib.curve.PrefixFormatter(curve.decimals.x, curve.prefix.x, use_zero)\r
1053                     formatter_y = lib.curve.PrefixFormatter(curve.decimals.y, curve.prefix.y, use_zero)\r
1054                     axes_list[destination].xaxis.set_major_formatter(formatter_x)\r
1055                     axes_list[destination].yaxis.set_major_formatter(formatter_y)\r
1056                 if curve.style == 'plot':\r
1057                     axes_list[destination].plot(curve.x, curve.y, color=curve.color, label=curve.label, lw=curve.linewidth, zorder=1)\r
1058                 if curve.style == 'scatter':\r
1059                     axes_list[destination].scatter(curve.x, curve.y, color=curve.color, label=curve.label, s=curve.size, zorder=2)\r
1060                 #add the legend if necessary\r
1061                 if curve.legend:\r
1062                     axes_list[destination].legend()\r
1063 \r
1064         if plot is None:\r
1065             active_file = self.GetActiveFile()\r
1066             if not active_file.driver:\r
1067                 #the first time we identify a file, the following need to be set\r
1068                 active_file.identify(self.drivers)\r
1069                 for curve in active_file.plot.curves:\r
1070                     curve.decimals.x = self.GetIntFromConfig('core', 'preferences', 'x_decimals')\r
1071                     curve.decimals.y = self.GetIntFromConfig('core', 'preferences', 'y_decimals')\r
1072                     curve.legend = self.GetBoolFromConfig('core', 'preferences', 'legend')\r
1073                     curve.prefix.x = self.GetStringFromConfig('core', 'preferences', 'x_prefix')\r
1074                     curve.prefix.y = self.GetStringFromConfig('core', 'preferences', 'y_prefix')\r
1075             if active_file.driver is None:\r
1076                 self.AppendToOutput('Invalid file: ' + active_file.filename)\r
1077                 return\r
1078             self.displayed_plot = copy.deepcopy(active_file.plot)\r
1079             #add raw curves to plot\r
1080             self.displayed_plot.raw_curves = copy.deepcopy(self.displayed_plot.curves)\r
1081             #apply all active plotmanipulators\r
1082             self.displayed_plot = self.ApplyPlotmanipulators(self.displayed_plot, active_file)\r
1083             #add corrected curves to plot\r
1084             self.displayed_plot.corrected_curves = copy.deepcopy(self.displayed_plot.curves)\r
1085         else:\r
1086             active_file = None\r
1087             self.displayed_plot = copy.deepcopy(plot)\r
1088 \r
1089         figure = self.GetActiveFigure()\r
1090         figure.clear()\r
1091 \r
1092         #use '0' instead of e.g. '0.00' for scales\r
1093         use_zero = self.GetBoolFromConfig('core', 'preferences', 'use_zero')\r
1094         #optionally remove the extension from the title of the plot\r
1095         hide_curve_extension = self.GetBoolFromConfig('core', 'preferences', 'hide_curve_extension')\r
1096         if hide_curve_extension:\r
1097             title = lh.remove_extension(self.displayed_plot.title)\r
1098         else:\r
1099             title = self.displayed_plot.title\r
1100         figure.suptitle(title, fontsize=14)\r
1101         #create the list of all axes necessary (rows and columns)\r
1102         axes_list =[]\r
1103         number_of_columns = max([curve.destination.column for curve in self.displayed_plot.curves])\r
1104         number_of_rows = max([curve.destination.row for curve in self.displayed_plot.curves])\r
1105         for index in range(number_of_rows * number_of_columns):\r
1106             axes_list.append(figure.add_subplot(number_of_rows, number_of_columns, index + 1))\r
1107 \r
1108         #add all curves to the corresponding plots\r
1109         for curve in self.displayed_plot.curves:\r
1110             add_to_plot(curve)\r
1111 \r
1112         #make sure the titles of 'subplots' do not overlap with the axis labels of the 'main plot'\r
1113         figure.subplots_adjust(hspace=0.3)\r
1114 \r
1115         #display results\r
1116         self.panelResults.ClearResults()\r
1117         if self.displayed_plot.results.has_key(self.results_str):\r
1118             for curve in self.displayed_plot.results[self.results_str].results:\r
1119                 add_to_plot(curve, set_scale=False)\r
1120             self.panelResults.DisplayResults(self.displayed_plot.results[self.results_str])\r
1121         else:\r
1122             self.panelResults.ClearResults()\r
1123         #refresh the plot\r
1124         figure.canvas.draw()\r
1125 \r
1126 \r
1127 \r
1128 class HookeApp (wx.App):\r
1129     def __init__(self, gui, commands, inqueue, outqueue, *args, **kwargs):\r
1130         self.gui = gui\r
1131         self.commands = commands\r
1132         self.inqueue = inqueue\r
1133         self.outqueue = outqueue\r
1134         super(HookeApp, self).__init__(*args, **kwargs)\r
1135 \r
1136     def OnInit(self):\r
1137         self.SetAppName('Hooke')\r
1138         self.SetVendorName('')\r
1139         self._setup_splash_screen()\r
1140 \r
1141         height = int(self.gui.config['main height']) # HACK: config should convert\r
1142         width = int(self.gui.config['main width'])\r
1143         top = int(self.gui.config['main top'])\r
1144         left = int(self.gui.config['main left'])\r
1145 \r
1146         # Sometimes, the ini file gets confused and sets 'left' and\r
1147         # 'top' to large negative numbers.  Here we catch and fix\r
1148         # this.  Keep small negative numbers, the user might want\r
1149         # those.\r
1150         if left < -width:\r
1151             left = 0\r
1152         if top < -height:\r
1153             top = 0\r
1154 \r
1155         self._c = {\r
1156             'frame': HookeFrame(\r
1157                 self.gui, self.commands, parent=None, title='Hooke',\r
1158                 pos=(left, top), size=(width, height),\r
1159                 style=wx.DEFAULT_FRAME_STYLE|wx.SUNKEN_BORDER|wx.CLIP_CHILDREN),\r
1160             }\r
1161         self._c['frame'].Show(True)\r
1162         self.SetTopWindow(self._c['frame'])\r
1163         return True\r
1164 \r
1165     def _setup_splash_screen(self):\r
1166         if self.gui.config['show splash screen']:\r
1167             path = self.gui.config['splash screen image']\r
1168             if os.path.isfile(path):\r
1169                 duration = int(self.gui.config['splash screen duration'])  # HACK: config should decode types\r
1170                 wx.SplashScreen(\r
1171                     bitmap=wx.Image(path).ConvertToBitmap(),\r
1172                     splashStyle=wx.SPLASH_CENTRE_ON_SCREEN|wx.SPLASH_TIMEOUT,\r
1173                     milliseconds=duration,\r
1174                     parent=None)\r
1175                 wx.Yield()\r
1176                 # For some reason splashDuration and sleep do not\r
1177                 # correspond to each other at least not on Windows.\r
1178                 # Maybe it's because duration is in milliseconds and\r
1179                 # sleep in seconds.  Thus we need to increase the\r
1180                 # sleep time a bit. A factor of 1.2 seems to work.\r
1181                 sleepFactor = 1.2\r
1182                 time.sleep(sleepFactor * duration / 1000)\r
1183 \r
1184     def OnExit(self):\r
1185         return True\r
1186 \r
1187 \r
1188 class GUI (UserInterface):\r
1189     """wxWindows graphical user interface.\r
1190     """\r
1191     def __init__(self):\r
1192         super(GUI, self).__init__(name='gui')\r
1193 \r
1194     def default_settings(self):\r
1195         """Return a list of :class:`hooke.config.Setting`\s for any\r
1196         configurable UI settings.\r
1197 \r
1198         The suggested section setting is::\r
1199 \r
1200             Setting(section=self.setting_section, help=self.__doc__)\r
1201         """\r
1202         return [\r
1203             Setting(section=self.setting_section, help=self.__doc__),\r
1204             Setting(section=self.setting_section, option='icon image',\r
1205                     value=os.path.join('doc', 'img', 'microscope.ico'),\r
1206                     help='Path to the hooke icon image.'),\r
1207             Setting(section=self.setting_section, option='show splash screen',\r
1208                     value=True,\r
1209                     help='Enable/disable the splash screen'),\r
1210             Setting(section=self.setting_section, option='splash screen image',\r
1211                     value=os.path.join('doc', 'img', 'hooke.jpg'),\r
1212                     help='Path to the Hooke splash screen image.'),\r
1213             Setting(section=self.setting_section, option='splash screen duration',\r
1214                     value=1000,\r
1215                     help='Duration of the splash screen in milliseconds.'),\r
1216             Setting(section=self.setting_section, option='perspective path',\r
1217                     value=os.path.join('resources', 'gui', 'perspective'),\r
1218                     help='Directory containing perspective files.'), # TODO: allow colon separated list, like $PATH.\r
1219             Setting(section=self.setting_section, option='folders-workdir',\r
1220                     value='.',\r
1221                     help='This should probably go...'),\r
1222             Setting(section=self.setting_section, option='folders-filters',\r
1223                     value='.',\r
1224                     help='This should probably go...'),\r
1225             Setting(section=self.setting_section, option='active perspective',\r
1226                     value='Default',\r
1227                     help='Name of active perspective file (or "Default").'),\r
1228             Setting(section=self.setting_section, option='folders-filter-index',\r
1229                     value='0',\r
1230                     help='This should probably go...'),\r
1231             Setting(section=self.setting_section, option='main height',\r
1232                     value=500,\r
1233                     help='Height of main window in pixels.'),\r
1234             Setting(section=self.setting_section, option='main width',\r
1235                     value=500,\r
1236                     help='Width of main window in pixels.'),\r
1237             Setting(section=self.setting_section, option='main top',\r
1238                     value=0,\r
1239                     help='Pixels from screen top to top of main window.'),\r
1240             Setting(section=self.setting_section, option='main left',\r
1241                     value=0,\r
1242                     help='Pixels from screen left to left of main window.'),            \r
1243             Setting(section=self.setting_section, option='selected command',\r
1244                     value='load playlist',\r
1245                     help='Name of the initially selected command.'),\r
1246             ]\r
1247 \r
1248     def _app(self, commands, ui_to_command_queue, command_to_ui_queue):\r
1249         redirect = True\r
1250         if __debug__:\r
1251             redirect=False\r
1252         app = HookeApp(gui=self,\r
1253                        commands=commands,\r
1254                        inqueue=ui_to_command_queue,\r
1255                        outqueue=command_to_ui_queue,\r
1256                        redirect=redirect)\r
1257         return app\r
1258 \r
1259     def run(self, commands, ui_to_command_queue, command_to_ui_queue):\r
1260         app = self._app(commands, ui_to_command_queue, command_to_ui_queue)\r
1261         app.MainLoop()\r