X-Git-Url: http://git.tremily.us/?a=blobdiff_plain;f=hooke%2Fui%2Fgui%2F__init__.py;h=fd502c0bde33dfe8cd475d1c36c98a8102bba3da;hb=d78bdc0e1d7796a1ec950350984fc984b130450c;hp=9059b8c85088c1d3b8390c99ad068b2f01d365df;hpb=565f9d7b69d2e4a9ea447d7a50f8f835c3e08642;p=hooke.git diff --git a/hooke/ui/gui/__init__.py b/hooke/ui/gui/__init__.py index 9059b8c..fd502c0 100644 --- a/hooke/ui/gui/__init__.py +++ b/hooke/ui/gui/__init__.py @@ -46,14 +46,15 @@ import wx.lib.evtmgr as evtmgr from ...command import CommandExit, Exit, Success, Failure, Command, Argument from ...config import Setting +from ...engine import CommandMessage from ...interaction import Request, BooleanRequest, ReloadUserInterfaceConfig -from ...ui import UserInterface, CommandMessage +from ...ui import UserInterface from .dialog.selection import Selection as SelectionDialog from .dialog.save_file import select_save_file from . import menu as menu from . import navbar as navbar from . import panel as panel -from .panel.propertyeditor import prop_from_argument, prop_from_setting +from .panel.propertyeditor import props_from_argument, props_from_setting from . import statusbar as statusbar @@ -114,13 +115,7 @@ class HookeFrame (wx.Frame): self._setup_perspectives() self._bind_events() - - self.execute_command( - command=self._command_by_name('load playlist'), - args={'input':'test/data/vclamp_picoforce/playlist'}, - ) return # TODO: cleanup - self.playlists = self._c['playlist'].Playlists self._displayed_plot = None #load default list, if possible self.do_loadlist(self.GetStringFromConfig('core', 'preferences', 'playlists')) @@ -137,7 +132,7 @@ class HookeFrame (wx.Frame): # size=(200, 250), # style=wx.DIRCTRL_SHOW_FILTERS, # filter=self.gui.config['folders-filters'], -# defaultFilter=int(self.gui.config['folders-filter-index'])), 'left'), #HACK: config should convert +# defaultFilter=self.gui.config['folders-filter-index']), 'left'), (panel.PANELS['playlist']( callbacks={ 'delete_playlist':self._on_user_delete_playlist, @@ -186,13 +181,9 @@ class HookeFrame (wx.Frame): style=wx.WANTS_CHARS, # WANTS_CHARS so the panel doesn't eat the Return key. ), 'center'), -# ('assistant', wx.TextCtrl( -# parent=self, -# pos=wx.Point(0, 0), -# size=wx.Size(150, 90), -# style=wx.NO_BORDER|wx.TE_MULTILINE), 'right'), (panel.PANELS['plot']( callbacks={ + '_set_status_text': self._on_plot_status_text, }, parent=self, style=wx.WANTS_CHARS|wx.NO_BORDER, @@ -208,7 +199,10 @@ class HookeFrame (wx.Frame): # ('results', panel.results.Results(self), 'bottom'), ]: self._add_panel(p, style) - #self._c['assistant'].SetEditable(False) + self.execute_command( # setup already loaded playlists + command=self._command_by_name('playlists')) + self.execute_command( # setup already loaded curve + command=self._command_by_name('get curve')) def _add_panel(self, panel, style): self._c[panel.name] = panel @@ -249,7 +243,7 @@ class HookeFrame (wx.Frame): self.Bind(wx.EVT_ERASE_BACKGROUND, self._on_erase_background) self.Bind(wx.EVT_SIZE, self._on_size) self.Bind(wx.EVT_CLOSE, self._on_close) - self.Bind(aui.EVT_AUI_PANE_CLOSE, self.OnPaneClose) + self.Bind(aui.EVT_AUI_PANE_CLOSE, self._on_pane_close) self.Bind(aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self._on_notebook_page_close) return # TODO: cleanup @@ -275,11 +269,10 @@ class HookeFrame (wx.Frame): def _on_close(self, *args): self.log.info('closing GUI framework') # apply changes - self.gui.config['main height'] = str(self.GetSize().GetHeight()) - self.gui.config['main left'] = str(self.GetPosition()[0]) - self.gui.config['main top'] = str(self.GetPosition()[1]) - self.gui.config['main width'] = str(self.GetSize().GetWidth()) - # push changes back to Hooke.config? + self._set_config('main height', self.GetSize().GetHeight()) + self._set_config('main left', self.GetPosition()[0]) + self._set_config('main top', self.GetPosition()[1]) + self._set_config('main width', self.GetSize().GetWidth()) self._c['manager'].UnInit() del self._c['manager'] self.Destroy() @@ -291,7 +284,7 @@ class HookeFrame (wx.Frame): def _file_name(self, name): """Cleanup names according to configured preferences. """ - if self.gui.config['hide extensions'] == 'True': # HACK: config should decode + if self.gui.config['hide extensions'] == True: name,ext = os.path.splitext(name) return name @@ -312,13 +305,42 @@ class HookeFrame (wx.Frame): if args == None: args = {} if ('property editor' in self._c - and self.gui.config['selected command'] == command): - arg_names = [arg.name for arg in command.arguments] + and self.gui.config['selected command'] == command.name): for name,value in self._c['property editor'].get_values().items(): - if name in arg_names: - args[name] = value - self.log.debug('executing %s with %s' % (command.name, args)) - self.inqueue.put(CommandMessage(command, args)) + arg = self._c['property editor']._argument_from_label.get( + name, None) + if arg == None: + continue + elif arg.count == 1: + args[arg.name] = value + continue + # deal with counted arguments + if arg.name not in args: + args[arg.name] = {} + index = int(name[len(arg.name):]) + args[arg.name][index] = value + for arg in command.arguments: + if arg.name not in args: + continue # undisplayed argument, e.g. 'driver' types. + count = arg.count + if hasattr(arg, '_display_count'): # support HACK in props_from_argument() + count = arg._display_count + if count != 1 and arg.name in args: + keys = sorted(args[arg.name].keys()) + assert keys == range(count), keys + args[arg.name] = [args[arg.name][i] + for i in range(count)] + if arg.count == -1: + while (len(args[arg.name]) > 0 + and args[arg.name][-1] == None): + args[arg.name].pop() + if len(args[arg.name]) == 0: + args[arg.name] = arg.default + cm = CommandMessage(command.name, args) + self.gui._submit_command(cm, self.inqueue) + return self._handle_response(command_message=cm) + + def _handle_response(self, command_message): results = [] while True: msg = self.outqueue.get() @@ -337,9 +359,11 @@ class HookeFrame (wx.Frame): h.run(self, msg) # TODO: pause for response? continue pp = getattr( - self, '_postprocess_%s' % command.name.replace(' ', '_'), - self._postprocess_text) - pp(command=command, args=args, results=results) + self, '_postprocess_%s' % command_message.command.replace(' ', '_'), + self._postprocess_text) + pp(command=command_message.command, + args=command_message.arguments, + results=results) return results def _handle_request(self, msg): @@ -365,6 +389,10 @@ class HookeFrame (wx.Frame): continue self.inqueue.put(response) + def _set_config(self, option, value, section=None): + self.gui._set_config(section=section, option=option, value=value, + ui_to_command_queue=self.inqueue, + response_handler=self._handle_response) # Command-specific postprocessing @@ -382,6 +410,34 @@ class HookeFrame (wx.Frame): self._c['output'].write(result.__class__.__name__+'\n') self._c['output'].write(str(result).rstrip()+'\n') + def _postprocess_playlists(self, command, args={}, results=None): + """Update `self` to show the playlists. + """ + if not isinstance(results[-1], Success): + self._postprocess_text(command, results=results) + return + assert len(results) == 2, results + playlists = results[0] + if 'playlist' in self._c: + for playlist in playlists: + if self._c['playlist'].is_playlist_loaded(playlist): + self._c['playlist'].update_playlist(playlist) + else: + self._c['playlist'].add_playlist(playlist) + + def _postprocess_new_playlist(self, command, args={}, results=None): + """Update `self` to show the new playlist. + """ + if not isinstance(results[-1], Success): + self._postprocess_text(command, results=results) + return + assert len(results) == 2, results + playlist = results[0] + if 'playlist' in self._c: + loaded = self._c['playlist'].is_playlist_loaded(playlist) + assert loaded == False, loaded + self._c['playlist'].add_playlist(playlist) + def _postprocess_load_playlist(self, command, args={}, results=None): """Update `self` to show the playlist. """ @@ -390,7 +446,7 @@ class HookeFrame (wx.Frame): return assert len(results) == 2, results playlist = results[0] - self._c['playlist']._c['tree'].add_playlist(playlist) + self._c['playlist'].add_playlist(playlist) def _postprocess_get_playlist(self, command, args={}, results=[]): if not isinstance(results[-1], Success): @@ -398,7 +454,10 @@ class HookeFrame (wx.Frame): return assert len(results) == 2, results playlist = results[0] - self._c['playlist']._c['tree'].update_playlist(playlist) + if 'playlist' in self._c: + loaded = self._c['playlist'].is_playlist_loaded(playlist) + assert loaded == True, loaded + self._c['playlist'].update_playlist(playlist) def _postprocess_get_curve(self, command, args={}, results=[]): """Update `self` to show the curve. @@ -416,10 +475,9 @@ class HookeFrame (wx.Frame): else: raise NotImplementedError() if 'note' in self._c: - print sorted(curve.info.keys()) - self._c['note'].set_text(curve.info['note']) + self._c['note'].set_text(curve.info.get('note', '')) if 'playlist' in self._c: - self._c['playlist']._c['tree'].set_selected_curve( + self._c['playlist'].set_selected_curve( playlist, curve) if 'plot' in self._c: self._c['plot'].set_curve(curve, config=self.gui.config) @@ -434,6 +492,25 @@ class HookeFrame (wx.Frame): """ pass + def _postprocess_glob_curves_to_playlist( + self, command, args={}, results=[]): + """Update `self` to show new curves. + """ + if not isinstance(results[-1], Success): + self._postprocess_text(command, results=results) + return + if 'playlist' in self._c: + if args.get('playlist', None) != None: + playlist = args['playlist'] + pname = playlist.name + loaded = self._c['playlist'].is_playlist_name_loaded(pname) + assert loaded == True, loaded + for curve in results[:-1]: + self._c['playlist']._add_curve(pname, curve) + else: + self.execute_command( + command=self._command_by_name('get playlist')) + def _postprocess_zero_block_surface_contact_point( self, command, args={}, results=[]): """Update the curve, since the available columns may have changed. @@ -580,9 +657,6 @@ class HookeFrame (wx.Frame): playlist_name = ctrl.GetPageText(ctrl._curpage) self.DeleteFromPlaylists(playlist_name) - def OnPaneClose(self, event): - event.Skip() - def OnPropGridChanged (self, event): prop = event.GetProperty() if prop: @@ -651,9 +725,8 @@ class HookeFrame (wx.Frame): def select_command(self, _class, method, command): #self.select_plugin(plugin=command.plugin) - if 'assistant' in self._c: - self._c['assitant'].ChangeValue(command.help) self._c['property editor'].clear() + self._c['property editor']._argument_from_label = {} for argument in command.arguments: if argument.name == 'help': continue @@ -674,13 +747,16 @@ class HookeFrame (wx.Frame): else: curves = results[0] - p = prop_from_argument( + ret = props_from_argument( argument, curves=curves, playlists=playlists) - if p == None: + if ret == None: continue # property intentionally not handled (yet) - self._c['property editor'].append_property(p) + for label,p in ret: + self._c['property editor'].append_property(p) + self._c['property editor']._argument_from_label[label] = ( + argument) - self.gui.config['selected command'] = command # TODO: push to engine + self._set_config('selected command', command.name) @@ -689,7 +765,6 @@ class HookeFrame (wx.Frame): def _on_update_note(self, _class, method, text): """Sets the note for the active curve. """ - # TODO: note list interface in NotePanel. self.execute_command( command=self._command_by_name('set note'), args={'note':text}) @@ -709,19 +784,29 @@ class HookeFrame (wx.Frame): pass def _on_delete_curve(self, _class, method, playlist, curve): + # TODO: execute_command 'remove curve from playlist' os.remove(curve.path) def _on_set_selected_playlist(self, _class, method, playlist): - """TODO: playlists plugin with `jump to playlist`. + """Call the `jump to playlist` command. """ - pass + results = self.execute_command( + command=self._command_by_name('playlists')) + if not isinstance(results[-1], Success): + return + assert len(results) == 2, results + playlists = results[0] + matching = [p for p in playlists if p.name == playlist.name] + assert len(matching) == 1, matching + index = playlists.index(matching[0]) + results = self.execute_command( + command=self._command_by_name('jump to playlist'), + args={'index':index}) def _on_set_selected_curve(self, _class, method, playlist, curve): """Call the `jump to curve` command. - - TODO: playlists plugin. """ - # TODO: jump to playlist, get playlist + self._on_set_selected_playlist(_class, method, playlist) index = playlist.index(curve) results = self.execute_command( command=self._command_by_name('jump to curve'), @@ -737,6 +822,14 @@ class HookeFrame (wx.Frame): + # Plot panel interface + + def _on_plot_status_text(self, _class, method, text): + if 'status bar' in self._c: + self._c['status bar'].set_plot_text(text) + + + # Navbar interface def _next_curve(self, *args): @@ -761,6 +854,13 @@ class HookeFrame (wx.Frame): # Panel display handling + def _on_pane_close(self, event): + pane = event.pane + view = self._c['menu bar']._c['view'] + if pane.name in view._c.keys(): + view._c[pane.name].Check(False) + event.Skip() + def _on_panel_visibility(self, _class, method, panel_name, visible): pane = self._c['manager'].GetPane(panel_name) pane.Show(visible) @@ -794,7 +894,7 @@ class HookeFrame (wx.Frame): selected_perspective = self.gui.config['active perspective'] if not self._perspectives.has_key(selected_perspective): - self.gui.config['active perspective'] = 'Default' # TODO: push to engine's Hooke + self._set_config('active perspective', 'Default') self._restore_perspective(selected_perspective, force=True) self._update_perspective_menu() @@ -839,7 +939,7 @@ class HookeFrame (wx.Frame): def _restore_perspective(self, name, force=False): if name != self.gui.config['active perspective'] or force == True: self.log.debug('restore perspective %s' % name) - self.gui.config['active perspective'] = name # TODO: push to engine's Hooke + self._set_config('active perspective', name) self._c['manager'].LoadPerspective(self._perspectives[name]) self._c['manager'].Update() for pane in self._c['manager'].GetAllPanes(): @@ -878,6 +978,8 @@ class HookeFrame (wx.Frame): style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER) dialog.CenterOnScreen() dialog.ShowModal() + if dialog.canceled == True: + return names = [options[i] for i in dialog.selected] dialog.Destroy() self._delete_perspectives( @@ -907,10 +1009,10 @@ class HookeApp (wx.App): self.SetVendorName('') self._setup_splash_screen() - height = int(self.gui.config['main height']) # HACK: config should convert - width = int(self.gui.config['main width']) - top = int(self.gui.config['main top']) - left = int(self.gui.config['main left']) + height = self.gui.config['main height'] + width = self.gui.config['main width'] + top = self.gui.config['main top'] + left = self.gui.config['main left'] # Sometimes, the ini file gets confused and sets 'left' and # 'top' to large negative numbers. Here we catch and fix @@ -933,10 +1035,10 @@ class HookeApp (wx.App): return True def _setup_splash_screen(self): - if self.gui.config['show splash screen'] == 'True': # HACK: config should decode + if self.gui.config['show splash screen'] == True: path = self.gui.config['splash screen image'] if os.path.isfile(path): - duration = int(self.gui.config['splash screen duration']) # HACK: config should decode types + duration = self.gui.config['splash screen duration'] wx.SplashScreen( bitmap=wx.Image(path).ConvertToBitmap(), splashStyle=wx.SPLASH_CENTRE_ON_SCREEN|wx.SPLASH_TIMEOUT, @@ -970,15 +1072,18 @@ class GUI (UserInterface): Setting(section=self.setting_section, help=self.__doc__), Setting(section=self.setting_section, option='icon image', value=os.path.join('doc', 'img', 'microscope.ico'), + type='file', help='Path to the hooke icon image.'), Setting(section=self.setting_section, option='show splash screen', - value=True, + value=True, type='bool', help='Enable/disable the splash screen'), Setting(section=self.setting_section, option='splash screen image', value=os.path.join('doc', 'img', 'hooke.jpg'), + type='file', help='Path to the Hooke splash screen image.'), - Setting(section=self.setting_section, option='splash screen duration', - value=1000, + Setting(section=self.setting_section, + option='splash screen duration', + value=1000, type='int', help='Duration of the splash screen in milliseconds.'), Setting(section=self.setting_section, option='perspective path', value=os.path.join('resources', 'gui', 'perspective'), @@ -987,41 +1092,42 @@ class GUI (UserInterface): value='.txt', help='Extension for perspective files.'), Setting(section=self.setting_section, option='hide extensions', - value=False, + value=False, type='bool', help='Hide file extensions when displaying names.'), Setting(section=self.setting_section, option='plot legend', - value=True, + value=True, type='bool', help='Enable/disable the plot legend.'), Setting(section=self.setting_section, option='plot SI format', - value='True', + value='True', type='bool', help='Enable/disable SI plot axes numbering.'), Setting(section=self.setting_section, option='plot decimals', - value=2, + value=2, type='int', help='Number of decimal places to show if "plot SI format" is enabled.'), Setting(section=self.setting_section, option='folders-workdir', - value='.', + value='.', type='path', help='This should probably go...'), Setting(section=self.setting_section, option='folders-filters', - value='.', + value='.', type='path', help='This should probably go...'), Setting(section=self.setting_section, option='active perspective', value='Default', help='Name of active perspective file (or "Default").'), - Setting(section=self.setting_section, option='folders-filter-index', - value='0', + Setting(section=self.setting_section, + option='folders-filter-index', + value=0, type='int', help='This should probably go...'), Setting(section=self.setting_section, option='main height', - value=450, + value=450, type='int', help='Height of main window in pixels.'), Setting(section=self.setting_section, option='main width', - value=800, + value=800, type='int', help='Width of main window in pixels.'), Setting(section=self.setting_section, option='main top', - value=0, + value=0, type='int', help='Pixels from screen top to top of main window.'), Setting(section=self.setting_section, option='main left', - value=0, - help='Pixels from screen left to left of main window.'), + value=0, type='int', + help='Pixels from screen left to left of main window.'), Setting(section=self.setting_section, option='selected command', value='load playlist', help='Name of the initially selected command.'),