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
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
27 from matplotlib.ticker import FuncFormatter
\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
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
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
50 '<h1>Welcome to Hooke</h1>',
\r
51 '<h3>Features</h3>',
\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
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
64 ctrl.SetPage('\n'.join(lines))
\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
73 'previous': self.AddLabelTool(
\r
74 id=wx.ID_PREVIEW_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
82 bitmap=wx.ArtProvider_GetBitmap(
\r
83 wx.ART_GO_FORWARD, wx.ART_OTHER, wx.Size(16, 16)),
\r
84 shortHelp='Next curve'),
\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
95 class ViewMenu (wx.Menu):
\r
96 def __init__(self, *args, **kwargs):
\r
97 super(ViewMenu, self).__init__(*args, **kwargs)
\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
112 for item in self._c.values():
\r
116 class PerspectiveMenu (wx.Menu):
\r
117 def __init__(self, *args, **kwargs):
\r
118 super(PerspectiveMenu, self).__init__(*args, **kwargs)
\r
121 def update(self, perspectives, selected, callback):
\r
122 """Rebuild the perspectives menu.
\r
124 for item in self.GetMenuItems():
\r
126 self.DeleteItem(item)
\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
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
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
145 class MenuBar (wx.MenuBar):
\r
146 def __init__(self, *args, **kwargs):
\r
147 super(MenuBar, self).__init__(*args, **kwargs)
\r
149 'file': FileMenu(),
\r
150 'view': ViewMenu(),
\r
151 'perspective': PerspectiveMenu(),
\r
152 'help': HelpMenu(),
\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
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
168 class HookeFrame (wx.Frame):
\r
169 def __init__(self, gui, commands, *args, **kwargs):
\r
170 super(HookeFrame, self).__init__(*args, **kwargs)
\r
172 self.commands = commands
\r
173 self._perspectives = {} # {name: perspective_str}
\r
176 self.SetIcon(wx.Icon(self.gui.config['icon image'], wx.BITMAP_TYPE_ICO))
\r
178 # setup frame manager
\r
179 self._c['manager'] = aui.AuiManager()
\r
180 self._c['manager'].SetManagedWindow(self)
\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
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
193 self._setup_panels()
\r
194 self._setup_toolbars()
\r
195 self._c['manager'].Update() # commit pending changes
\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
201 self.SetMenuBar(self._c['menu bar'])
\r
203 self._c['status bar'] = StatusBar(self, style=wx.ST_SIZEGRIP)
\r
205 self._update_perspectives()
\r
206 self._bind_events()
\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
219 def _setup_panels(self):
\r
220 client_size = self.GetClientSize()
\r
221 for label,p,style in [
\r
222 ('folders', wx.GenericDirCtrl(
\r
224 dir=self.gui.config['folders-workdir'],
\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
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
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
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
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
257 self._add_panel(label, p, style)
\r
258 self._c['assistant'].SetEditable(False)
\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
271 assert style == 'bottom', style
\r
272 info.Bottom().CloseButton(True).MaximizeButton(False)
\r
273 self._c['manager'].AddPane(panel, info)
\r
275 def _setup_toolbars(self):
\r
276 self._c['navbar'] = NavBar(self, style=wx.TB_FLAT | wx.TB_NODIVIDER)
\r
278 self._c['manager'].AddPane(
\r
280 aui.AuiPaneInfo().Name('Navigation').Caption('Navigation'
\r
281 ).ToolbarPane().Top().Layer(1).Row(1).LeftDockable(False
\r
282 ).RightDockable(False))
\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
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
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
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
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
307 treeCtrl = self._c['folders'].GetTreeCtrl()
\r
308 treeCtrl.Bind(wx.EVT_LEFT_DCLICK, self._on_dir_ctrl_left_double_click)
\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
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
319 self.panelProperties.pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChanged)
\r
321 self.panelResults.results_list.OnCheckItem = self.OnResultsCheck
\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
332 selected_item = self._c['playlists'].PlaylistsTree.GetPrevSibling(selected_item)
\r
333 while selected_item.IsOk():
\r
335 selected_item = self._c['playlists'].PlaylistsTree.GetPrevSibling(selected_item)
\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
344 def _GetUniquePlaylistName(self, name):
\r
345 playlist_name = name
\r
347 while playlist_name in self.playlists:
\r
348 playlist_name = ''.join([name, str(count)])
\r
350 return playlist_name
\r
352 def _restore_perspective(self, name):
\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
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
368 def AddPlaylist(self, playlist=None, name='Untitled'):
\r
369 if playlist and playlist.count > 0:
\r
370 playlist.name = self._GetUniquePlaylistName(name)
\r
372 self.AddToPlaylists(playlist)
\r
374 def AddPlaylistFromFiles(self, files=[], name='Untitled'):
\r
376 playlist = lib.playlist.Playlist(self, self.drivers)
\r
378 playlist.add_curve(item)
\r
379 if playlist.count > 0:
\r
380 playlist.name = self._GetUniquePlaylistName(name)
\r
382 self.AddTayliss(playlist)
\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
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
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
412 def AppendToOutput(self, text):
\r
413 self.panelOutput.AppendText(''.join([text, '\n']))
\r
415 def AppliesPlotmanipulator(self, name):
\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
420 return self.GetBoolFromConfig('core', 'plotmanipulators', name)
\r
422 def ApplyPlotmanipulators(self, plot, plot_file):
\r
424 Apply all active plotmanipulators.
\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
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
439 playlist_name = self._c['playlists'].PlaylistsTree.GetItemText(item)
\r
440 if playlist_name == name:
\r
442 self._c['playlists'].PlaylistsTree.Delete(item)
\r
445 item = self._c['playlists'].PlaylistsTree.GetNextSibling(item)
\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
454 def GetActiveFile(self):
\r
455 playlist = self.GetActivePlaylist()
\r
456 if playlist is not None:
\r
457 return playlist.get_active_file()
\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
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
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
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
484 def GetDisplayedPlot(self):
\r
485 plot = copy.deepcopy(self.displayed_plot)
\r
487 #plot.curves = copy.deepcopy(plot.curves)
\r
490 def GetDisplayedPlotCorrected(self):
\r
491 plot = copy.deepcopy(self.displayed_plot)
\r
493 plot.curves = copy.deepcopy(plot.corrected_curves)
\r
496 def GetDisplayedPlotRaw(self):
\r
497 plot = copy.deepcopy(self.displayed_plot)
\r
499 plot.curves = copy.deepcopy(plot.raw_curves)
\r
502 def GetDockArt(self):
\r
503 return self._c['manager'].GetArtProvider()
\r
505 def GetPlotmanipulator(self, name):
\r
507 Returns a plot manipulator function from its name
\r
509 for plotmanipulator in self.plotmanipulators:
\r
510 if plotmanipulator.name == name:
\r
511 return plotmanipulator
\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
525 def HasPlotmanipulator(self, name):
\r
527 returns True if the plotmanipulator 'name' is loaded, False otherwise
\r
529 for plotmanipulator in self.plotmanipulators:
\r
530 if plotmanipulator.command == name:
\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
544 def _on_close(self, event):
\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
555 def _update_perspectives(self):
\r
556 """Add perspectives to menubar and _perspectives.
\r
558 self._perspectives = {
\r
559 'Default': self._c['manager'].SavePerspective(),
\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
568 fpath = os.path.join(path, fpath)
\r
569 if not os.path.isfile(fpath):
\r
572 with open(fpath, 'rU') as f:
\r
573 perspective = f.readline()
\r
575 self._perspectives[name] = perspective
\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
581 self._update_perspective_menu()
\r
582 self._restore_perspective(selected_perspective)
\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
590 def _on_restore_perspective(self, event):
\r
591 name = self.MenuBar.FindItemById(event.GetId()).GetLabel()
\r
592 self._restore_perspective(name)
\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
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
610 name = dialog.GetValue()
\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
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
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
648 # menu_item.Check()
\r
649 # #add the new perspective to _perspectives
\r
650 # self._perspectives[name] = perspective
\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
659 label='Delete perspective(s)',
\r
660 style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
\r
661 dialog.CenterOnScreen()
\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
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
678 self._update_perspective_menu()
\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
687 def _on_erase_background(self, event):
\r
690 def OnExecute(self, event):
\r
691 item = self._c['commands']._c['tree'].GetSelection()
\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
699 def OnExit(self, event):
\r
702 def _on_next(self, event):
\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
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
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
727 self.statusbar.SetStatusText(playlist.get_status_string(), 0)
\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
736 def OnPaneClose(self, event):
\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
758 playlist = self.GetActivePlaylist()
\r
759 playlist.index = index
\r
760 self.statusbar.SetStatusText(playlist.get_status_string(), 0)
\r
763 #if you uncomment the following line, the tree will collapse/expand as well
\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
777 playlist.index = index
\r
778 self.playlists[playlist_name] = playlist
\r
781 def _on_previous(self, event):
\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
787 Syntax: previous, p
\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
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
808 def OnPropGridChanged (self, event):
\r
809 prop = event.GetProperty()
\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
819 config[property_section][property_key]['value'] = property_value
\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
829 def _on_size(self, event):
\r
832 def OnTreeCtrlCommandsSelectionChanged(self, event):
\r
833 selected_item = event.GetItem()
\r
834 if selected_item is not None:
\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
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
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
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
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
871 def OnTreeCtrlItemActivated(self, event):
\r
872 self.OnExecute(event)
\r
874 def OnUpdateNote(self, event):
\r
876 Saves the note to the active file.
\r
878 active_file = self.GetActiveFile()
\r
879 active_file.note = self.panelNote.Editor.GetValue()
\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
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
894 def _clickize(self, xvector, yvector, index):
\r
896 Returns a ClickedPoint() object from an index and vectors of x, y coordinates
\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
904 def _delta(self, message='Click 2 points', block=0):
\r
906 Calculates the difference between two clicked points
\r
908 clicked_points = self._measure_N_points(N=2, message=message, block=block)
\r
910 plot = self.GetDisplayedPlotCorrected()
\r
911 curve = plot.curves[block]
\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
923 def _measure_N_points(self, N, message='', block=0):
\r
925 General helper function for N-points measurements
\r
926 By default, measurements are done on the retraction
\r
929 dialog = wx.MessageDialog(None, message, 'Info', wx.OK)
\r
932 figure = self.GetActiveFigure()
\r
934 xvector = self.displayed_plot.curves[block].x
\r
935 yvector = self.displayed_plot.curves[block].y
\r
937 clicked_points = figure.ginput(N, timeout=-1, show_clicks=True)
\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
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
952 def do_copylog(self):
\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
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
975 def do_plotmanipulators(self):
\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
980 Click 'Execute' to apply your changes.
\r
984 def do_preferences(self):
\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
994 Use this command for testing purposes. You find do_test in hooke.py.
\r
998 def do_version(self):
\r
1002 Prints the current version and codename, plus library version. Useful for debugging.
\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
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
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
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
1040 def UpdatePlot(self, plot=None):
\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
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
1062 axes_list[destination].legend()
\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
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
1086 active_file = None
\r
1087 self.displayed_plot = copy.deepcopy(plot)
\r
1089 figure = self.GetActiveFigure()
\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
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
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
1108 #add all curves to the corresponding plots
\r
1109 for curve in self.displayed_plot.curves:
\r
1110 add_to_plot(curve)
\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
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
1122 self.panelResults.ClearResults()
\r
1124 figure.canvas.draw()
\r
1128 class HookeApp (wx.App):
\r
1129 def __init__(self, gui, commands, inqueue, outqueue, *args, **kwargs):
\r
1131 self.commands = commands
\r
1132 self.inqueue = inqueue
\r
1133 self.outqueue = outqueue
\r
1134 super(HookeApp, self).__init__(*args, **kwargs)
\r
1137 self.SetAppName('Hooke')
\r
1138 self.SetVendorName('')
\r
1139 self._setup_splash_screen()
\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
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
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
1161 self._c['frame'].Show(True)
\r
1162 self.SetTopWindow(self._c['frame'])
\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
1171 bitmap=wx.Image(path).ConvertToBitmap(),
\r
1172 splashStyle=wx.SPLASH_CENTRE_ON_SCREEN|wx.SPLASH_TIMEOUT,
\r
1173 milliseconds=duration,
\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
1182 time.sleep(sleepFactor * duration / 1000)
\r
1188 class GUI (UserInterface):
\r
1189 """wxWindows graphical user interface.
\r
1191 def __init__(self):
\r
1192 super(GUI, self).__init__(name='gui')
\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
1198 The suggested section setting is::
\r
1200 Setting(section=self.setting_section, help=self.__doc__)
\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
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
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
1221 help='This should probably go...'),
\r
1222 Setting(section=self.setting_section, option='folders-filters',
\r
1224 help='This should probably go...'),
\r
1225 Setting(section=self.setting_section, option='active perspective',
\r
1227 help='Name of active perspective file (or "Default").'),
\r
1228 Setting(section=self.setting_section, option='folders-filter-index',
\r
1230 help='This should probably go...'),
\r
1231 Setting(section=self.setting_section, option='main height',
\r
1233 help='Height of main window in pixels.'),
\r
1234 Setting(section=self.setting_section, option='main width',
\r
1236 help='Width of main window in pixels.'),
\r
1237 Setting(section=self.setting_section, option='main top',
\r
1239 help='Pixels from screen top to top of main window.'),
\r
1240 Setting(section=self.setting_section, option='main left',
\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
1248 def _app(self, commands, ui_to_command_queue, command_to_ui_queue):
\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
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