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