Moved QueueMessage and subclasses from hooke.ui to the more central hooke.engine.
[hooke.git] / hooke / ui / gui / __init__.py
index c3a9f45a94c3314109b5b43c7609bdbca6cbcb2b..8f1e41bfcf81635bf999539bc221d359dfcbae27 100644 (file)
@@ -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
@@ -291,7 +285,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
 
@@ -313,10 +307,34 @@ class HookeFrame (wx.Frame):
             args = {}
         if ('property editor' in self._c
             and self.gui.config['selected command'] == command):
-            arg_names = [arg.name for arg in command.arguments]
             for name,value in self._c['property editor'].get_values().items():
-                if name in arg_names:
-                    args[name] = value
+                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:
+                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
         self.log.debug('executing %s with %s' % (command.name, args))
         self.inqueue.put(CommandMessage(command, args))
         results = []
@@ -382,6 +400,22 @@ 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]
+        loaded_playlists = []  # TODO
+        if 'playlist' in self._c:
+            for playlist in playlists:
+                if playlist in loaded_playlists:
+                    self._c['playlist'].update_playlist(playlist)
+                else:
+                    self._c['playlist'].add_playlist(playlist)
+
     def _postprocess_load_playlist(self, command, args={}, results=None):
         """Update `self` to show the playlist.
         """
@@ -390,7 +424,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 +432,7 @@ class HookeFrame (wx.Frame):
             return
         assert len(results) == 2, results
         playlist = results[0]
-        self._c['playlist']._c['tree'].update_playlist(playlist)
+        self._c['playlist'].update_playlist(playlist)
 
     def _postprocess_get_curve(self, command, args={}, results=[]):
         """Update `self` to show the curve.
@@ -418,7 +452,7 @@ class HookeFrame (wx.Frame):
         if 'note' in self._c:
             self._c['note'].set_text(curve.info['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)
@@ -650,9 +684,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
@@ -673,11 +706,14 @@ 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
 
@@ -688,7 +724,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})
@@ -708,19 +743,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'),
@@ -736,6 +781,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):
@@ -877,6 +930,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(
@@ -906,10 +961,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
@@ -932,10 +987,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,
@@ -969,15 +1024,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'),
@@ -986,41 +1044,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.'),