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(
\r
230 config=self.gui.config,
\r
233 style=wx.WANTS_CHARS|wx.NO_BORDER,
\r
234 # WANTS_CHARS so the panel doesn't eat the Return key.
\r
235 size=(160, 200)), 'left'),
\r
236 ('note', panel.note.Note(self), 'left'),
\r
237 ('notebook', Notebook(
\r
239 pos=wx.Point(client_size.x, client_size.y),
\r
240 size=wx.Size(430, 200),
\r
241 style=aui.AUI_NB_DEFAULT_STYLE
\r
242 | aui.AUI_NB_TAB_EXTERNAL_MOVE | wx.NO_BORDER), 'center'),
\r
243 ('commands', panel.commands.Commands(
\r
244 commands=self.commands,
\r
245 selected=self.gui.config['selected command'],
\r
247 style=wx.WANTS_CHARS|wx.NO_BORDER,
\r
248 # WANTS_CHARS so the panel doesn't eat the Return key.
\r
249 size=(160, 200)), 'right'),
\r
250 #('properties', panel.propertyeditor.PropertyEditor(self),'right'),
\r
251 ('assistant', wx.TextCtrl(
\r
253 pos=wx.Point(0, 0),
\r
254 size=wx.Size(150, 90),
\r
255 style=wx.NO_BORDER|wx.TE_MULTILINE), 'right'),
\r
256 ('output', wx.TextCtrl(
\r
258 pos=wx.Point(0, 0),
\r
259 size=wx.Size(150, 90),
\r
260 style=wx.NO_BORDER|wx.TE_MULTILINE), 'bottom'),
\r
261 ('results', panel.results.Results(self), 'bottom'),
\r
263 self._add_panel(label, p, style)
\r
264 self._c['assistant'].SetEditable(False)
\r
266 def _add_panel(self, label, panel, style):
\r
267 self._c[label] = panel
\r
268 cap_label = label.capitalize()
\r
269 info = aui.AuiPaneInfo().Name(cap_label).Caption(cap_label)
\r
270 if style == 'left':
\r
271 info.Left().CloseButton(True).MaximizeButton(False)
\r
272 elif style == 'center':
\r
273 info.CenterPane().PaneBorder(False)
\r
274 elif style == 'right':
\r
275 info.Right().CloseButton(True).MaximizeButton(False)
\r
277 assert style == 'bottom', style
\r
278 info.Bottom().CloseButton(True).MaximizeButton(False)
\r
279 self._c['manager'].AddPane(panel, info)
\r
281 def _setup_toolbars(self):
\r
282 self._c['navbar'] = NavBar(self, style=wx.TB_FLAT | wx.TB_NODIVIDER)
\r
284 self._c['manager'].AddPane(
\r
286 aui.AuiPaneInfo().Name('Navigation').Caption('Navigation'
\r
287 ).ToolbarPane().Top().Layer(1).Row(1).LeftDockable(False
\r
288 ).RightDockable(False))
\r
290 def _bind_events(self):
\r
291 # TODO: figure out if we can use the eventManager for menu
\r
292 # ranges and events of 'self' without raising an assertion
\r
294 self.Bind(wx.EVT_ERASE_BACKGROUND, self._on_erase_background)
\r
295 self.Bind(wx.EVT_SIZE, self._on_size)
\r
296 self.Bind(wx.EVT_CLOSE, self._on_close)
\r
297 self.Bind(wx.EVT_MENU, self._on_close, id=wx.ID_EXIT)
\r
298 self.Bind(wx.EVT_MENU, self._on_about, id=wx.ID_ABOUT)
\r
299 self.Bind(aui.EVT_AUI_PANE_CLOSE, self.OnPaneClose)
\r
300 self.Bind(aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self._on_notebook_page_close)
\r
302 for value in self._c['menu bar']._c['view']._c.values():
\r
303 self.Bind(wx.EVT_MENU_RANGE, self._on_view, value)
\r
305 self.Bind(wx.EVT_MENU, self._on_save_perspective,
\r
306 self._c['menu bar']._c['perspective']._c['save'])
\r
307 self.Bind(wx.EVT_MENU, self._on_delete_perspective,
\r
308 self._c['menu bar']._c['perspective']._c['delete'])
\r
310 self.Bind(wx.EVT_TOOL, self._on_next, self._c['navbar']._c['next'])
\r
311 self.Bind(wx.EVT_TOOL, self._on_previous,self._c['navbar']._c['previous'])
\r
313 treeCtrl = self._c['folders'].GetTreeCtrl()
\r
314 treeCtrl.Bind(wx.EVT_LEFT_DCLICK, self._on_dir_ctrl_left_double_click)
\r
316 # TODO: playlist callbacks
\r
317 return # TODO: cleanup
\r
319 evtmgr.eventManager.Register(self.OnExecute, wx.EVT_BUTTON, self._c['commands'].ExecuteButton)
\r
320 evtmgr.eventManager.Register(self.OnTreeCtrlCommandsSelectionChanged, wx.EVT_TREE_SEL_CHANGED, self._c['commands']._c['tree'])
\r
321 evtmgr.eventManager.Register(self.OnTreeCtrlItemActivated, wx.EVT_TREE_ITEM_ACTIVATED, self._c['commands']._c['tree'])
\r
322 evtmgr.eventManager.Register(self.OnUpdateNote, wx.EVT_BUTTON, self.panelNote.UpdateButton)
\r
324 self.panelProperties.pg.Bind(wxpg.EVT_PG_CHANGED, self.OnPropGridChanged)
\r
326 self.panelResults.results_list.OnCheckItem = self.OnResultsCheck
\r
328 def _GetActiveFileIndex(self):
\r
329 lib.playlist.Playlist = self.GetActivePlaylist()
\r
330 #get the selected item from the tree
\r
331 selected_item = self._c['playlists']._c['tree'].GetSelection()
\r
332 #test if a playlist or a curve was double-clicked
\r
333 if self._c['playlists']._c['tree'].ItemHasChildren(selected_item):
\r
337 selected_item = self._c['playlists']._c['tree'].GetPrevSibling(selected_item)
\r
338 while selected_item.IsOk():
\r
340 selected_item = self._c['playlists']._c['tree'].GetPrevSibling(selected_item)
\r
343 def _GetPlaylistTab(self, name):
\r
344 for index, page in enumerate(self._c['notebook']._tabs._pages):
\r
345 if page.caption == name:
\r
349 def _restore_perspective(self, name):
\r
351 self.gui.config['active perspective'] = name # TODO: push to engine's Hooke
\r
352 self._c['manager'].LoadPerspective(self._perspectives[name])
\r
353 self._c['manager'].Update()
\r
354 for pane in self._c['manager'].GetAllPanes():
\r
355 if pane.name in self._c['menu bar']._c['view']._c.keys():
\r
356 pane.Check(pane.window.IsShown())
\r
358 def _SavePerspectiveToFile(self, name, perspective):
\r
359 filename = ''.join([name, '.txt'])
\r
360 filename = lh.get_file_path(filename, ['perspective'])
\r
361 perspectivesFile = open(filename, 'w')
\r
362 perspectivesFile.write(perspective)
\r
363 perspectivesFile.close()
\r
365 def AddPlaylistFromFiles(self, files=[], name='Untitled'):
\r
367 playlist = lib.playlist.Playlist(self, self.drivers)
\r
369 playlist.add_curve(item)
\r
370 if playlist.count > 0:
\r
371 playlist.name = self._GetUniquePlaylistName(name)
\r
373 self.AddTayliss(playlist)
\r
375 def AppendToOutput(self, text):
\r
376 self.panelOutput.AppendText(''.join([text, '\n']))
\r
378 def AppliesPlotmanipulator(self, name):
\r
380 Returns True if the plotmanipulator 'name' is applied, False otherwise
\r
381 name does not contain 'plotmanip_', just the name of the plotmanipulator (e.g. 'flatten')
\r
383 return self.GetBoolFromConfig('core', 'plotmanipulators', name)
\r
385 def ApplyPlotmanipulators(self, plot, plot_file):
\r
387 Apply all active plotmanipulators.
\r
389 if plot is not None and plot_file is not None:
\r
390 manipulated_plot = copy.deepcopy(plot)
\r
391 for plotmanipulator in self.plotmanipulators:
\r
392 if self.GetBoolFromConfig('core', 'plotmanipulators', plotmanipulator.name):
\r
393 manipulated_plot = plotmanipulator.method(manipulated_plot, plot_file)
\r
394 return manipulated_plot
\r
396 def GetActiveFigure(self):
\r
397 playlist_name = self.GetActivePlaylistName()
\r
398 figure = self.playlists[playlist_name].figure
\r
399 if figure is not None:
\r
403 def GetActiveFile(self):
\r
404 playlist = self.GetActivePlaylist()
\r
405 if playlist is not None:
\r
406 return playlist.get_active_file()
\r
409 def GetActivePlot(self):
\r
410 playlist = self.GetActivePlaylist()
\r
411 if playlist is not None:
\r
412 return playlist.get_active_file().plot
\r
415 def GetDisplayedPlot(self):
\r
416 plot = copy.deepcopy(self.displayed_plot)
\r
418 #plot.curves = copy.deepcopy(plot.curves)
\r
421 def GetDisplayedPlotCorrected(self):
\r
422 plot = copy.deepcopy(self.displayed_plot)
\r
424 plot.curves = copy.deepcopy(plot.corrected_curves)
\r
427 def GetDisplayedPlotRaw(self):
\r
428 plot = copy.deepcopy(self.displayed_plot)
\r
430 plot.curves = copy.deepcopy(plot.raw_curves)
\r
433 def GetDockArt(self):
\r
434 return self._c['manager'].GetArtProvider()
\r
436 def GetPlotmanipulator(self, name):
\r
438 Returns a plot manipulator function from its name
\r
440 for plotmanipulator in self.plotmanipulators:
\r
441 if plotmanipulator.name == name:
\r
442 return plotmanipulator
\r
445 def GetPerspectiveMenuItem(self, name):
\r
446 if self._perspectives.has_key(name):
\r
447 perspectives_list = [key for key, value in self._perspectives.iteritems()]
\r
448 perspectives_list.sort()
\r
449 index = perspectives_list.index(name)
\r
450 perspective_Id = ID_FirstPerspective + index
\r
451 menu_item = self.MenuBar.FindItemById(perspective_Id)
\r
456 def HasPlotmanipulator(self, name):
\r
458 returns True if the plotmanipulator 'name' is loaded, False otherwise
\r
460 for plotmanipulator in self.plotmanipulators:
\r
461 if plotmanipulator.command == name:
\r
465 def _on_about(self, event):
\r
466 message = 'Hooke\n\n'+\
\r
467 'A free, open source data analysis platform\n\n'+\
\r
468 'Copyright 2006-2008 by Massimo Sandal\n'+\
\r
469 'Copyright 2010 by Dr. Rolf Schmidt\n\n'+\
\r
470 'Hooke is released under the GNU General Public License version 2.'
\r
471 dialog = wx.MessageDialog(self, message, 'About Hooke', wx.OK | wx.ICON_INFORMATION)
\r
475 def _on_close(self, event):
\r
477 self.gui.config['main height'] = str(self.GetSize().GetHeight())
\r
478 self.gui.config['main left'] = str(self.GetPosition()[0])
\r
479 self.gui.config['main top'] = str(self.GetPosition()[1])
\r
480 self.gui.config['main width'] = str(self.GetSize().GetWidth())
\r
481 # push changes back to Hooke.config?
\r
482 self._c['manager'].UnInit()
\r
483 del self._c['manager']
\r
486 def _update_perspectives(self):
\r
487 """Add perspectives to menubar and _perspectives.
\r
489 self._perspectives = {
\r
490 'Default': self._c['manager'].SavePerspective(),
\r
492 path = self.gui.config['perspective path']
\r
493 if os.path.isdir(path):
\r
494 files = sorted(os.listdir(path))
\r
495 for fname in files:
\r
496 name, extension = os.path.splitext(fname)
\r
497 if extension != '.txt':
\r
499 fpath = os.path.join(path, fpath)
\r
500 if not os.path.isfile(fpath):
\r
503 with open(fpath, 'rU') as f:
\r
504 perspective = f.readline()
\r
506 self._perspectives[name] = perspective
\r
508 selected_perspective = self.gui.config['active perspective']
\r
509 if not self._perspectives.has_key(selected_perspective):
\r
510 self.gui.config['active perspective'] = 'Default' # TODO: push to engine's Hooke
\r
512 self._update_perspective_menu()
\r
513 self._restore_perspective(selected_perspective)
\r
515 def _update_perspective_menu(self):
\r
516 self._c['menu bar']._c['perspective'].update(
\r
517 sorted(self._perspectives.keys()),
\r
518 self.gui.config['active perspective'],
\r
519 self._on_restore_perspective)
\r
521 def _on_restore_perspective(self, event):
\r
522 name = self.MenuBar.FindItemById(event.GetId()).GetLabel()
\r
523 self._restore_perspective(name)
\r
525 def _on_save_perspective(self, event):
\r
526 def nameExists(name):
\r
527 menu_position = self.MenuBar.FindMenu('Perspective')
\r
528 menu = self.MenuBar.GetMenu(menu_position)
\r
529 for item in menu.GetMenuItems():
\r
530 if item.GetText() == name:
\r
536 dialog = wx.TextEntryDialog(self, 'Enter a name for the new perspective:', 'Save perspective')
\r
537 dialog.SetValue('New perspective')
\r
538 if dialog.ShowModal() != wx.ID_OK:
\r
541 name = dialog.GetValue()
\r
543 if nameExists(name):
\r
544 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
545 if dialogConfirm.ShowModal() == wx.ID_YES:
\r
550 perspective = self._c['manager'].SavePerspective()
\r
551 self._SavePerspectiveToFile(name, perspective)
\r
552 self.gui.config['active perspectives'] = name
\r
553 self._update_perspective_menu()
\r
554 # if nameExists(name):
\r
555 # #check the corresponding menu item
\r
556 # menu_item = self.GetPerspectiveMenuItem(name)
\r
557 # #replace the perspectiveStr in _pespectives
\r
558 # self._perspectives[name] = perspective
\r
560 # #because we deal with radio items, we need to do some extra work
\r
561 # #delete all menu items from the perspectives menu
\r
562 # for item in self._perspectives_menu.GetMenuItems():
\r
563 # self._perspectives_menu.DeleteItem(item)
\r
564 # #recreate the perspectives menu
\r
565 # self._perspectives_menu.Append(ID_SavePerspective, 'Save Perspective')
\r
566 # self._perspectives_menu.Append(ID_DeletePerspective, 'Delete Perspective')
\r
567 # self._perspectives_menu.AppendSeparator()
\r
568 # #convert the perspectives dictionary into a list
\r
569 # # the list contains:
\r
570 # #[0]: name of the perspective
\r
571 # #[1]: perspective
\r
572 # perspectives_list = [key for key, value in self._perspectives.iteritems()]
\r
573 # perspectives_list.append(name)
\r
574 # perspectives_list.sort()
\r
575 # #add all previous perspectives
\r
576 # for index, item in enumerate(perspectives_list):
\r
577 # menu_item = self._perspectives_menu.AppendRadioItem(ID_FirstPerspective + index, item)
\r
579 # menu_item.Check()
\r
580 # #add the new perspective to _perspectives
\r
581 # self._perspectives[name] = perspective
\r
583 def _on_delete_perspective(self, event):
\r
584 dialog = panel.selection.Selection(
\r
585 options=sorted(os.listdir(self.gui.config['perspective path'])),
\r
586 message="\nPlease check the perspectives\n\nyou want to delete and click 'Delete'.\n",
\r
587 button_id=wx.ID_DELETE,
\r
588 button_callback=self._on_delete_perspective,
\r
590 label='Delete perspective(s)',
\r
591 style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)
\r
592 dialog.CenterOnScreen()
\r
595 self._update_perspective_menu()
\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
609 self._update_perspective_menu()
\r
611 def _on_dir_ctrl_left_double_click(self, event):
\r
612 file_path = self.panelFolders.GetPath()
\r
613 if os.path.isfile(file_path):
\r
614 if file_path.endswith('.hkp'):
\r
615 self.do_loadlist(file_path)
\r
618 def _on_erase_background(self, event):
\r
621 def OnExecute(self, event):
\r
622 item = self._c['commands']._c['tree'].GetSelection()
\r
624 if not self._c['commands']._c['tree'].ItemHasChildren(item):
\r
625 item_text = self._c['commands']._c['tree'].GetItemText(item)
\r
626 command = ''.join(['self.do_', item_text, '()'])
\r
627 #self.AppendToOutput(command + '\n')
\r
630 def OnExit(self, event):
\r
633 def _on_next(self, event):
\r
636 Go to the next curve in the playlist.
\r
637 If we are at the last curve, we come back to the first.
\r
641 selected_item = self._c['playlists']._c['tree'].GetSelection()
\r
642 if self._c['playlists']._c['tree'].ItemHasChildren(selected_item):
\r
643 #GetFirstChild returns a tuple
\r
644 #we only need the first element
\r
645 next_item = self._c['playlists']._c['tree'].GetFirstChild(selected_item)[0]
\r
647 next_item = self._c['playlists']._c['tree'].GetNextSibling(selected_item)
\r
648 if not next_item.IsOk():
\r
649 parent_item = self._c['playlists']._c['tree'].GetItemParent(selected_item)
\r
650 #GetFirstChild returns a tuple
\r
651 #we only need the first element
\r
652 next_item = self._c['playlists']._c['tree'].GetFirstChild(parent_item)[0]
\r
653 self._c['playlists']._c['tree'].SelectItem(next_item, True)
\r
654 if not self._c['playlists']._c['tree'].ItemHasChildren(selected_item):
\r
655 playlist = self.GetActivePlaylist()
\r
656 if playlist.count > 1:
\r
658 self.statusbar.SetStatusText(playlist.get_status_string(), 0)
\r
662 def _on_notebook_page_close(self, event):
\r
663 ctrl = event.GetEventObject()
\r
664 playlist_name = ctrl.GetPageText(ctrl._curpage)
\r
665 self.DeleteFromPlaylists(playlist_name)
\r
667 def OnPaneClose(self, event):
\r
670 def _on_previous(self, event):
\r
673 Go to the previous curve in the playlist.
\r
674 If we are at the first curve, we jump to the last.
\r
676 Syntax: previous, p
\r
678 #playlist = self.playlists[self.GetActivePlaylistName()][0]
\r
679 #select the previous curve and tell the user if we wrapped around
\r
680 #self.AppendToOutput(playlist.previous())
\r
681 selected_item = self._c['playlists']._c['tree'].GetSelection()
\r
682 if self._c['playlists']._c['tree'].ItemHasChildren(selected_item):
\r
683 previous_item = self._c['playlists']._c['tree'].GetLastChild(selected_item)
\r
685 previous_item = self._c['playlists']._c['tree'].GetPrevSibling(selected_item)
\r
686 if not previous_item.IsOk():
\r
687 parent_item = self._c['playlists']._c['tree'].GetItemParent(selected_item)
\r
688 previous_item = self._c['playlists']._c['tree'].GetLastChild(parent_item)
\r
689 self._c['playlists']._c['tree'].SelectItem(previous_item, True)
\r
690 playlist = self.GetActivePlaylist()
\r
691 if playlist.count > 1:
\r
692 playlist.previous()
\r
693 self.statusbar.SetStatusText(playlist.get_status_string(), 0)
\r
697 def OnPropGridChanged (self, event):
\r
698 prop = event.GetProperty()
\r
700 item_section = self.panelProperties.SelectedTreeItem
\r
701 item_plugin = self._c['commands']._c['tree'].GetItemParent(item_section)
\r
702 plugin = self._c['commands']._c['tree'].GetItemText(item_plugin)
\r
703 config = self.gui.config[plugin]
\r
704 property_section = self._c['commands']._c['tree'].GetItemText(item_section)
\r
705 property_key = prop.GetName()
\r
706 property_value = prop.GetDisplayedString()
\r
708 config[property_section][property_key]['value'] = property_value
\r
710 def OnResultsCheck(self, index, flag):
\r
711 results = self.GetActivePlot().results
\r
712 if results.has_key(self.results_str):
\r
713 results[self.results_str].results[index].visible = flag
\r
714 results[self.results_str].update()
\r
718 def _on_size(self, event):
\r
721 def OnTreeCtrlCommandsSelectionChanged(self, event):
\r
722 selected_item = event.GetItem()
\r
723 if selected_item is not None:
\r
726 #deregister/register the listener to avoid infinite loop
\r
727 evtmgr.eventManager.DeregisterListener(self.OnTreeCtrlCommandsSelectionChanged)
\r
728 self._c['commands']._c['tree'].SelectItem(selected_item)
\r
729 evtmgr.eventManager.Register(self.OnTreeCtrlCommandsSelectionChanged, wx.EVT_TREE_SEL_CHANGED, self._c['commands']._c['tree'])
\r
730 self.panelProperties.SelectedTreeItem = selected_item
\r
731 #if a command was clicked
\r
733 if not self._c['commands']._c['tree'].ItemHasChildren(selected_item):
\r
734 item_plugin = self._c['commands']._c['tree'].GetItemParent(selected_item)
\r
735 plugin = self._c['commands']._c['tree'].GetItemText(item_plugin)
\r
736 if self.gui.config.has_key(plugin):
\r
737 #config = self._c['commands']._c['tree'].GetPyData(item_plugin)
\r
738 config = self.gui.config[plugin]
\r
739 section = self._c['commands']._c['tree'].GetItemText(selected_item)
\r
740 #display docstring in help window
\r
741 doc_string = eval('self.do_' + section + '.__doc__')
\r
742 if section in config:
\r
743 for option in config[section]:
\r
744 properties.append([option, config[section][option]])
\r
746 plugin = self._c['commands']._c['tree'].GetItemText(selected_item)
\r
747 if plugin != 'core':
\r
748 doc_string = eval('plugins.' + plugin + '.' + plugin + 'Commands.__doc__')
\r
750 doc_string = 'The module "core" contains Hooke core functionality'
\r
751 if doc_string is not None:
\r
752 self.panelAssistant.ChangeValue(doc_string)
\r
754 self.panelAssistant.ChangeValue('')
\r
755 panel.propertyeditor.PropertyEditor.Initialize(self.panelProperties, properties)
\r
756 #save the currently selected command/plugin to the config file
\r
757 self.gui.config['command']['command'] = section
\r
758 self.gui.config['command']['plugin'] = plugin
\r
760 def OnTreeCtrlItemActivated(self, event):
\r
761 self.OnExecute(event)
\r
763 def OnUpdateNote(self, event):
\r
765 Saves the note to the active file.
\r
767 active_file = self.GetActiveFile()
\r
768 active_file.note = self.panelNote.Editor.GetValue()
\r
770 def _on_view(self, event):
\r
771 menu_id = event.GetId()
\r
772 menu_item = self.MenuBar.FindItemById(menu_id)
\r
773 menu_label = menu_item.GetLabel()
\r
775 pane = self._c['manager'].GetPane(menu_label)
\r
776 pane.Show(not pane.IsShown())
\r
777 #if we don't do the following, the Folders pane does not resize properly on hide/show
\r
778 if pane.caption == 'Folders' and pane.IsShown() and pane.IsDocked():
\r
779 #folders_size = pane.GetSize()
\r
780 self.panelFolders.Fit()
\r
781 self._c['manager'].Update()
\r
783 def _clickize(self, xvector, yvector, index):
\r
785 Returns a ClickedPoint() object from an index and vectors of x, y coordinates
\r
787 point = lib.clickedpoint.ClickedPoint()
\r
788 point.index = index
\r
789 point.absolute_coords = xvector[index], yvector[index]
\r
790 point.find_graph_coords(xvector, yvector)
\r
793 def _delta(self, message='Click 2 points', block=0):
\r
795 Calculates the difference between two clicked points
\r
797 clicked_points = self._measure_N_points(N=2, message=message, block=block)
\r
799 plot = self.GetDisplayedPlotCorrected()
\r
800 curve = plot.curves[block]
\r
802 delta = lib.delta.Delta()
\r
803 delta.point1.x = clicked_points[0].graph_coords[0]
\r
804 delta.point1.y = clicked_points[0].graph_coords[1]
\r
805 delta.point2.x = clicked_points[1].graph_coords[0]
\r
806 delta.point2.y = clicked_points[1].graph_coords[1]
\r
807 delta.units.x = curve.units.x
\r
808 delta.units.y = curve.units.y
\r
812 def _measure_N_points(self, N, message='', block=0):
\r
814 General helper function for N-points measurements
\r
815 By default, measurements are done on the retraction
\r
818 dialog = wx.MessageDialog(None, message, 'Info', wx.OK)
\r
821 figure = self.GetActiveFigure()
\r
823 xvector = self.displayed_plot.curves[block].x
\r
824 yvector = self.displayed_plot.curves[block].y
\r
826 clicked_points = figure.ginput(N, timeout=-1, show_clicks=True)
\r
829 for clicked_point in clicked_points:
\r
830 point = lib.clickedpoint.ClickedPoint()
\r
831 point.absolute_coords = clicked_point[0], clicked_point[1]
\r
833 #TODO: make this optional?
\r
834 #so far, the clicked point is taken, not the corresponding data point
\r
835 point.find_graph_coords(xvector, yvector)
\r
836 point.is_line_edge = True
\r
837 point.is_marker = True
\r
838 points.append(point)
\r
841 def do_copylog(self):
\r
843 Copies all files in the current playlist that have a note to the destination folder.
\r
844 destination: select folder where you want the files to be copied
\r
845 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
847 playlist = self.GetActivePlaylist()
\r
848 if playlist is not None:
\r
849 destination = self.GetStringFromConfig('core', 'copylog', 'destination')
\r
850 if not os.path.isdir(destination):
\r
851 os.makedirs(destination)
\r
852 for current_file in playlist.files:
\r
853 if current_file.note:
\r
854 shutil.copy(current_file.filename, destination)
\r
855 if current_file.driver.filetype == 'mfp1d':
\r
856 filename = current_file.filename.replace('deflection', 'LVDT', 1)
\r
857 path, name = os.path.split(filename)
\r
858 filename = os.path.join(path, 'lvdt', name)
\r
859 use_LVDT_folder = self.GetBoolFromConfig('core', 'copylog', 'use_LVDT_folder')
\r
860 if use_LVDT_folder:
\r
861 destination = os.path.join(destination, 'LVDT')
\r
862 shutil.copy(filename, destination)
\r
864 def do_plotmanipulators(self):
\r
866 Please select the plotmanipulators you would like to use
\r
867 and define the order in which they will be applied to the data.
\r
869 Click 'Execute' to apply your changes.
\r
873 def do_preferences(self):
\r
875 Please set general preferences for Hooke here.
\r
876 hide_curve_extension: hides the extension of the force curve files.
\r
877 not recommended for 'picoforce' files
\r
883 Use this command for testing purposes. You find do_test in hooke.py.
\r
887 def do_version(self):
\r
891 Prints the current version and codename, plus library version. Useful for debugging.
\r
893 self.AppendToOutput('Hooke ' + __version__ + ' (' + __codename__ + ')')
\r
894 self.AppendToOutput('Released on: ' + __releasedate__)
\r
895 self.AppendToOutput('---')
\r
896 self.AppendToOutput('Python version: ' + python_version)
\r
897 self.AppendToOutput('WxPython version: ' + wx_version)
\r
898 self.AppendToOutput('Matplotlib version: ' + mpl_version)
\r
899 self.AppendToOutput('SciPy version: ' + scipy_version)
\r
900 self.AppendToOutput('NumPy version: ' + numpy_version)
\r
901 self.AppendToOutput('ConfigObj version: ' + configobj_version)
\r
902 self.AppendToOutput('wxPropertyGrid version: ' + '.'.join([str(PROPGRID_MAJOR), str(PROPGRID_MINOR), str(PROPGRID_RELEASE)]))
\r
903 self.AppendToOutput('---')
\r
904 self.AppendToOutput('Platform: ' + str(platform.uname()))
\r
905 self.AppendToOutput('******************************')
\r
906 self.AppendToOutput('Loaded plugins')
\r
907 self.AppendToOutput('---')
\r
909 #sort the plugins into alphabetical order
\r
910 plugins_list = [key for key, value in self.plugins.iteritems()]
\r
911 plugins_list.sort()
\r
912 for plugin in plugins_list:
\r
913 self.AppendToOutput(plugin)
\r
915 def UpdateNote(self):
\r
916 #update the note for the active file
\r
917 active_file = self.GetActiveFile()
\r
918 if active_file is not None:
\r
919 self.panelNote.Editor.SetValue(active_file.note)
\r
921 def UpdatePlaylistsTreeSelection(self):
\r
922 playlist = self.GetActivePlaylist()
\r
923 if playlist is not None:
\r
924 if playlist.index >= 0:
\r
925 self.statusbar.SetStatusText(playlist.get_status_string(), 0)
\r
929 def UpdatePlot(self, plot=None):
\r
931 def add_to_plot(curve, set_scale=True):
\r
932 if curve.visible and curve.x and curve.y:
\r
933 #get the index of the subplot to use as destination
\r
934 destination = (curve.destination.column - 1) * number_of_rows + curve.destination.row - 1
\r
935 #set all parameters for the plot
\r
936 axes_list[destination].set_title(curve.title)
\r
938 axes_list[destination].set_xlabel(curve.prefix.x + curve.units.x)
\r
939 axes_list[destination].set_ylabel(curve.prefix.y + curve.units.y)
\r
940 #set the formatting details for the scale
\r
941 formatter_x = lib.curve.PrefixFormatter(curve.decimals.x, curve.prefix.x, use_zero)
\r
942 formatter_y = lib.curve.PrefixFormatter(curve.decimals.y, curve.prefix.y, use_zero)
\r
943 axes_list[destination].xaxis.set_major_formatter(formatter_x)
\r
944 axes_list[destination].yaxis.set_major_formatter(formatter_y)
\r
945 if curve.style == 'plot':
\r
946 axes_list[destination].plot(curve.x, curve.y, color=curve.color, label=curve.label, lw=curve.linewidth, zorder=1)
\r
947 if curve.style == 'scatter':
\r
948 axes_list[destination].scatter(curve.x, curve.y, color=curve.color, label=curve.label, s=curve.size, zorder=2)
\r
949 #add the legend if necessary
\r
951 axes_list[destination].legend()
\r
954 active_file = self.GetActiveFile()
\r
955 if not active_file.driver:
\r
956 #the first time we identify a file, the following need to be set
\r
957 active_file.identify(self.drivers)
\r
958 for curve in active_file.plot.curves:
\r
959 curve.decimals.x = self.GetIntFromConfig('core', 'preferences', 'x_decimals')
\r
960 curve.decimals.y = self.GetIntFromConfig('core', 'preferences', 'y_decimals')
\r
961 curve.legend = self.GetBoolFromConfig('core', 'preferences', 'legend')
\r
962 curve.prefix.x = self.GetStringFromConfig('core', 'preferences', 'x_prefix')
\r
963 curve.prefix.y = self.GetStringFromConfig('core', 'preferences', 'y_prefix')
\r
964 if active_file.driver is None:
\r
965 self.AppendToOutput('Invalid file: ' + active_file.filename)
\r
967 self.displayed_plot = copy.deepcopy(active_file.plot)
\r
968 #add raw curves to plot
\r
969 self.displayed_plot.raw_curves = copy.deepcopy(self.displayed_plot.curves)
\r
970 #apply all active plotmanipulators
\r
971 self.displayed_plot = self.ApplyPlotmanipulators(self.displayed_plot, active_file)
\r
972 #add corrected curves to plot
\r
973 self.displayed_plot.corrected_curves = copy.deepcopy(self.displayed_plot.curves)
\r
976 self.displayed_plot = copy.deepcopy(plot)
\r
978 figure = self.GetActiveFigure()
\r
981 #use '0' instead of e.g. '0.00' for scales
\r
982 use_zero = self.GetBoolFromConfig('core', 'preferences', 'use_zero')
\r
983 #optionally remove the extension from the title of the plot
\r
984 hide_curve_extension = self.GetBoolFromConfig('core', 'preferences', 'hide_curve_extension')
\r
985 if hide_curve_extension:
\r
986 title = lh.remove_extension(self.displayed_plot.title)
\r
988 title = self.displayed_plot.title
\r
989 figure.suptitle(title, fontsize=14)
\r
990 #create the list of all axes necessary (rows and columns)
\r
992 number_of_columns = max([curve.destination.column for curve in self.displayed_plot.curves])
\r
993 number_of_rows = max([curve.destination.row for curve in self.displayed_plot.curves])
\r
994 for index in range(number_of_rows * number_of_columns):
\r
995 axes_list.append(figure.add_subplot(number_of_rows, number_of_columns, index + 1))
\r
997 #add all curves to the corresponding plots
\r
998 for curve in self.displayed_plot.curves:
\r
1001 #make sure the titles of 'subplots' do not overlap with the axis labels of the 'main plot'
\r
1002 figure.subplots_adjust(hspace=0.3)
\r
1005 self.panelResults.ClearResults()
\r
1006 if self.displayed_plot.results.has_key(self.results_str):
\r
1007 for curve in self.displayed_plot.results[self.results_str].results:
\r
1008 add_to_plot(curve, set_scale=False)
\r
1009 self.panelResults.DisplayResults(self.displayed_plot.results[self.results_str])
\r
1011 self.panelResults.ClearResults()
\r
1013 figure.canvas.draw()
\r
1015 def _on_curve_select(self, playlist, curve):
\r
1016 #create the plot tab and add playlist to the dictionary
\r
1017 plotPanel = panel.plot.PlotPanel(self, ID_FirstPlot + len(self.playlists))
\r
1018 notebook_tab = self._c['notebook'].AddPage(plotPanel, playlist.name, True)
\r
1019 #tab_index = self._c['notebook'].GetSelection()
\r
1020 playlist.figure = plotPanel.get_figure()
\r
1021 self.playlists[playlist.name] = playlist
\r
1022 #self.playlists[playlist.name] = [playlist, figure]
\r
1023 self.statusbar.SetStatusText(playlist.get_status_string(), 0)
\r
1028 def _on_playlist_left_doubleclick(self):
\r
1029 index = self._c['notebook'].GetSelection()
\r
1030 current_playlist = self._c['notebook'].GetPageText(index)
\r
1031 if current_playlist != playlist_name:
\r
1032 index = self._GetPlaylistTab(playlist_name)
\r
1033 self._c['notebook'].SetSelection(index)
\r
1034 self.statusbar.SetStatusText(playlist.get_status_string(), 0)
\r
1038 def _on_playlist_delete(self, playlist):
\r
1039 notebook = self.Parent.plotNotebook
\r
1040 index = self.Parent._GetPlaylistTab(playlist.name)
\r
1041 notebook.SetSelection(index)
\r
1042 notebook.DeletePage(notebook.GetSelection())
\r
1043 self.Parent.DeleteFromPlaylists(playlist_name)
\r
1046 class HookeApp (wx.App):
\r
1047 def __init__(self, gui, commands, inqueue, outqueue, *args, **kwargs):
\r
1049 self.commands = commands
\r
1050 self.inqueue = inqueue
\r
1051 self.outqueue = outqueue
\r
1052 super(HookeApp, self).__init__(*args, **kwargs)
\r
1055 self.SetAppName('Hooke')
\r
1056 self.SetVendorName('')
\r
1057 self._setup_splash_screen()
\r
1059 height = int(self.gui.config['main height']) # HACK: config should convert
\r
1060 width = int(self.gui.config['main width'])
\r
1061 top = int(self.gui.config['main top'])
\r
1062 left = int(self.gui.config['main left'])
\r
1064 # Sometimes, the ini file gets confused and sets 'left' and
\r
1065 # 'top' to large negative numbers. Here we catch and fix
\r
1066 # this. Keep small negative numbers, the user might want
\r
1074 'frame': HookeFrame(
\r
1075 self.gui, self.commands, parent=None, title='Hooke',
\r
1076 pos=(left, top), size=(width, height),
\r
1077 style=wx.DEFAULT_FRAME_STYLE|wx.SUNKEN_BORDER|wx.CLIP_CHILDREN),
\r
1079 self._c['frame'].Show(True)
\r
1080 self.SetTopWindow(self._c['frame'])
\r
1083 def _setup_splash_screen(self):
\r
1084 if self.gui.config['show splash screen']:
\r
1085 path = self.gui.config['splash screen image']
\r
1086 if os.path.isfile(path):
\r
1087 duration = int(self.gui.config['splash screen duration']) # HACK: config should decode types
\r
1089 bitmap=wx.Image(path).ConvertToBitmap(),
\r
1090 splashStyle=wx.SPLASH_CENTRE_ON_SCREEN|wx.SPLASH_TIMEOUT,
\r
1091 milliseconds=duration,
\r
1094 # For some reason splashDuration and sleep do not
\r
1095 # correspond to each other at least not on Windows.
\r
1096 # Maybe it's because duration is in milliseconds and
\r
1097 # sleep in seconds. Thus we need to increase the
\r
1098 # sleep time a bit. A factor of 1.2 seems to work.
\r
1100 time.sleep(sleepFactor * duration / 1000)
\r
1106 class GUI (UserInterface):
\r
1107 """wxWindows graphical user interface.
\r
1109 def __init__(self):
\r
1110 super(GUI, self).__init__(name='gui')
\r
1112 def default_settings(self):
\r
1113 """Return a list of :class:`hooke.config.Setting`\s for any
\r
1114 configurable UI settings.
\r
1116 The suggested section setting is::
\r
1118 Setting(section=self.setting_section, help=self.__doc__)
\r
1121 Setting(section=self.setting_section, help=self.__doc__),
\r
1122 Setting(section=self.setting_section, option='icon image',
\r
1123 value=os.path.join('doc', 'img', 'microscope.ico'),
\r
1124 help='Path to the hooke icon image.'),
\r
1125 Setting(section=self.setting_section, option='show splash screen',
\r
1127 help='Enable/disable the splash screen'),
\r
1128 Setting(section=self.setting_section, option='splash screen image',
\r
1129 value=os.path.join('doc', 'img', 'hooke.jpg'),
\r
1130 help='Path to the Hooke splash screen image.'),
\r
1131 Setting(section=self.setting_section, option='splash screen duration',
\r
1133 help='Duration of the splash screen in milliseconds.'),
\r
1134 Setting(section=self.setting_section, option='perspective path',
\r
1135 value=os.path.join('resources', 'gui', 'perspective'),
\r
1136 help='Directory containing perspective files.'), # TODO: allow colon separated list, like $PATH.
\r
1137 Setting(section=self.setting_section, option='hide extensions',
\r
1139 help='Hide file extensions when displaying names.'),
\r
1140 Setting(section=self.setting_section, option='folders-workdir',
\r
1142 help='This should probably go...'),
\r
1143 Setting(section=self.setting_section, option='folders-filters',
\r
1145 help='This should probably go...'),
\r
1146 Setting(section=self.setting_section, option='active perspective',
\r
1148 help='Name of active perspective file (or "Default").'),
\r
1149 Setting(section=self.setting_section, option='folders-filter-index',
\r
1151 help='This should probably go...'),
\r
1152 Setting(section=self.setting_section, option='main height',
\r
1154 help='Height of main window in pixels.'),
\r
1155 Setting(section=self.setting_section, option='main width',
\r
1157 help='Width of main window in pixels.'),
\r
1158 Setting(section=self.setting_section, option='main top',
\r
1160 help='Pixels from screen top to top of main window.'),
\r
1161 Setting(section=self.setting_section, option='main left',
\r
1163 help='Pixels from screen left to left of main window.'),
\r
1164 Setting(section=self.setting_section, option='selected command',
\r
1165 value='load playlist',
\r
1166 help='Name of the initially selected command.'),
\r
1169 def _app(self, commands, ui_to_command_queue, command_to_ui_queue):
\r
1173 app = HookeApp(gui=self,
\r
1174 commands=commands,
\r
1175 inqueue=ui_to_command_queue,
\r
1176 outqueue=command_to_ui_queue,
\r
1177 redirect=redirect)
\r
1180 def run(self, commands, ui_to_command_queue, command_to_ui_queue):
\r
1181 app = self._app(commands, ui_to_command_queue, command_to_ui_queue)
\r