hooke.ui.gui was getting complicated, so I stripped it down for a moment.
[hooke.git] / hooke / ui / gui / __init__.py
index 69f5a61588a7c698ba9bdbbb45f3a43942b722ae..f83936d0a1c09959f042a6ccecbca58e5af4c95d 100644 (file)
@@ -1,6 +1,6 @@
 # Copyright\r
 \r
-"""Defines :class:`GUI` providing a wxWindows interface to Hooke.\r
+"""Defines :class:`GUI` providing a wxWidgets interface to Hooke.\r
 """\r
 \r
 WX_GOOD=['2.8']\r
@@ -26,8 +26,7 @@ import wx.lib.evtmgr as evtmgr
 \r
 from matplotlib.ticker import FuncFormatter\r
 \r
-from ... import version\r
-from ...command import CommandExit, Exit, Command, Argument, StoreValue\r
+from ...command import CommandExit, Exit, Success, Failure, Command, Argument\r
 from ...config import Setting\r
 from ...interaction import Request, BooleanRequest, ReloadUserInterfaceConfig\r
 from ...ui import UserInterface, CommandMessage\r
@@ -75,14 +74,18 @@ class HookeFrame (wx.Frame):
 \r
         # Create the menubar after the panes so that the default\r
         # perspective is created with all panes open\r
-        self._c['menu bar'] = menu.MenuBar(\r
-            callbacks={},\r
-            )\r
+        self._c['menu bar'] = menu.HookeMenuBar(\r
+            parent=self,\r
+            callbacks={\r
+                'close': self._on_close,\r
+                'about': self._on_about,\r
+                })\r
         self.SetMenuBar(self._c['menu bar'])\r
 \r
-        self._c['status bar'] = statubar.StatusBar(\r
+        self._c['status bar'] = statusbar.StatusBar(\r
             parent=self,\r
             style=wx.ST_SIZEGRIP)\r
+        self.SetStatusBar(self._c['status bar'])\r
 \r
         self._update_perspectives()\r
         self._bind_events()\r
@@ -101,28 +104,31 @@ class HookeFrame (wx.Frame):
     def _setup_panels(self):\r
         client_size = self.GetClientSize()\r
         for label,p,style in [\r
-            ('folders', wx.GenericDirCtrl(\r
-                    parent=self,\r
-                    dir=self.gui.config['folders-workdir'],\r
-                    size=(200, 250),\r
-                    style=wx.DIRCTRL_SHOW_FILTERS,\r
-                    filter=self.gui.config['folders-filters'],\r
-                    defaultFilter=int(self.gui.config['folders-filter-index'])), 'left'),  #HACK: config should convert\r
-            ('playlists', panel.playlist.Playlist(\r
-                    config=self.gui.config,\r
-                    callbacks={},\r
-                    parent=self,\r
-                    style=wx.WANTS_CHARS|wx.NO_BORDER,\r
-                    # WANTS_CHARS so the panel doesn't eat the Return key.\r
-                    size=(160, 200)), 'left'),\r
-            ('note', panel.note.Note(self), 'left'),\r
-            ('notebook', Notebook(\r
-                    parent=self,\r
-                    pos=wx.Point(client_size.x, client_size.y),\r
-                    size=wx.Size(430, 200),\r
-                    style=aui.AUI_NB_DEFAULT_STYLE\r
-                    | aui.AUI_NB_TAB_EXTERNAL_MOVE | wx.NO_BORDER), 'center'),\r
-            ('commands', panel.commands.Commands(\r
+#            ('folders', wx.GenericDirCtrl(\r
+#                    parent=self,\r
+#                    dir=self.gui.config['folders-workdir'],\r
+#                    size=(200, 250),\r
+#                    style=wx.DIRCTRL_SHOW_FILTERS,\r
+#                    filter=self.gui.config['folders-filters'],\r
+#                    defaultFilter=int(self.gui.config['folders-filter-index'])), 'left'),  #HACK: config should convert\r
+#            ('playlists', panel.PANELS['playlist'](\r
+#                    callbacks={},\r
+#                    config=self.gui.config,\r
+#                    parent=self,\r
+#                    style=wx.WANTS_CHARS|wx.NO_BORDER,\r
+#                    # WANTS_CHARS so the panel doesn't eat the Return key.\r
+#                    size=(160, 200)), 'left'),\r
+#            ('note', panel.note.Note(\r
+#                    parent=self\r
+#                    style=wx.WANTS_CHARS|wx.NO_BORDER,\r
+#                    size=(160, 200)), 'left'),\r
+#            ('notebook', Notebook(\r
+#                    parent=self,\r
+#                    pos=wx.Point(client_size.x, client_size.y),\r
+#                    size=wx.Size(430, 200),\r
+#                    style=aui.AUI_NB_DEFAULT_STYLE\r
+#                    | aui.AUI_NB_TAB_EXTERNAL_MOVE | wx.NO_BORDER), 'center'),\r
+            ('commands', panel.PANELS['commands'](\r
                     commands=self.commands,\r
                     selected=self.gui.config['selected command'],\r
                     callbacks={\r
@@ -134,49 +140,52 @@ class HookeFrame (wx.Frame):
                     parent=self,\r
                     style=wx.WANTS_CHARS|wx.NO_BORDER,\r
                     # WANTS_CHARS so the panel doesn't eat the Return key.\r
-                    size=(160, 200)), 'right'),\r
+#                    size=(160, 200)\r
+                    ), 'center'),\r
             #('properties', panel.propertyeditor.PropertyEditor(self),'right'),\r
-            ('assistant', wx.TextCtrl(\r
-                    parent=self,\r
-                    pos=wx.Point(0, 0),\r
-                    size=wx.Size(150, 90),\r
-                    style=wx.NO_BORDER|wx.TE_MULTILINE), 'right'),\r
-            ('output', wx.TextCtrl(\r
-                    parent=self,\r
-                    pos=wx.Point(0, 0),\r
-                    size=wx.Size(150, 90),\r
-                    style=wx.NO_BORDER|wx.TE_MULTILINE), 'bottom'),\r
-            ('results', panel.results.Results(self), 'bottom'),\r
+#            ('assistant', wx.TextCtrl(\r
+#                    parent=self,\r
+#                    pos=wx.Point(0, 0),\r
+#                    size=wx.Size(150, 90),\r
+#                    style=wx.NO_BORDER|wx.TE_MULTILINE), 'right'),\r
+#            ('output', wx.TextCtrl(\r
+#                    parent=self,\r
+#                    pos=wx.Point(0, 0),\r
+#                    size=wx.Size(150, 90),\r
+#                    style=wx.NO_BORDER|wx.TE_MULTILINE), 'bottom'),\r
+#            ('results', panel.results.Results(self), 'bottom'),\r
             ]:\r
             self._add_panel(label, p, style)\r
-        self._c['assistant'].SetEditable(False)\r
+        #self._c['assistant'].SetEditable(False)\r
 \r
     def _add_panel(self, label, panel, style):\r
         self._c[label] = panel\r
         cap_label = label.capitalize()\r
         info = aui.AuiPaneInfo().Name(cap_label).Caption(cap_label)\r
-        if style == 'left':\r
-            info.Left().CloseButton(True).MaximizeButton(False)\r
+        info.PaneBorder(False).CloseButton(True).MaximizeButton(False)\r
+        if style == 'top':\r
+            info.Top()\r
         elif style == 'center':\r
-            info.CenterPane().PaneBorder(False)\r
+            info.CenterPane()\r
+        elif style == 'left':\r
+            info.Left()\r
         elif style == 'right':\r
-            info.Right().CloseButton(True).MaximizeButton(False)\r
+            info.Right()\r
         else:\r
             assert style == 'bottom', style\r
-            info.Bottom().CloseButton(True).MaximizeButton(False)\r
+            info.Bottom()\r
         self._c['manager'].AddPane(panel, info)\r
 \r
     def _setup_toolbars(self):\r
-        self._c['navbar'] = navbar.NavBar(\r
+        self._c['navigation bar'] = navbar.NavBar(\r
             callbacks={\r
                 'next': self._next_curve,\r
                 'previous': self._previous_curve,\r
                 },\r
             parent=self,\r
             style=wx.TB_FLAT | wx.TB_NODIVIDER)\r
-\r
         self._c['manager'].AddPane(\r
-            self._c['navbar'],\r
+            self._c['navigation bar'],\r
             aui.AuiPaneInfo().Name('Navigation').Caption('Navigation'\r
                 ).ToolbarPane().Top().Layer(1).Row(1).LeftDockable(False\r
                 ).RightDockable(False))\r
@@ -188,11 +197,10 @@ class HookeFrame (wx.Frame):
         self.Bind(wx.EVT_ERASE_BACKGROUND, self._on_erase_background)\r
         self.Bind(wx.EVT_SIZE, self._on_size)\r
         self.Bind(wx.EVT_CLOSE, self._on_close)\r
-        self.Bind(wx.EVT_MENU, self._on_close, id=wx.ID_EXIT)\r
-        self.Bind(wx.EVT_MENU, self._on_about, id=wx.ID_ABOUT)\r
         self.Bind(aui.EVT_AUI_PANE_CLOSE, self.OnPaneClose)\r
         self.Bind(aui.EVT_AUINOTEBOOK_PAGE_CLOSE, self._on_notebook_page_close)\r
 \r
+        return # TODO: cleanup\r
         for value in self._c['menu bar']._c['view']._c.values():\r
             self.Bind(wx.EVT_MENU_RANGE, self._on_view, value)\r
 \r
@@ -212,6 +220,65 @@ class HookeFrame (wx.Frame):
         #results panel\r
         self.panelResults.results_list.OnCheckItem = self.OnResultsCheck\r
 \r
+    def _command_by_name(self, name):\r
+        cs = [c for c in self.commands if c.name == name]\r
+        if len(cs) == 0:\r
+            raise KeyError(name)\r
+        elif len(cs) > 1:\r
+            raise Exception('Multiple commands named "%s"' % name)\r
+        return cs[0]\r
+\r
+    def execute_command(self, _class=None, method=None,\r
+                        command=None, args=None):\r
+        self.inqueue.put(CommandMessage(command, args))\r
+        results = []\r
+        while True:\r
+            msg = self.outqueue.get()\r
+            results.append(msg)\r
+            print type(msg), msg\r
+            if isinstance(msg, Exit):\r
+                self._on_close()\r
+                break\r
+            elif isinstance(msg, CommandExit):\r
+                # TODO: display command complete\r
+                break\r
+            elif isinstance(msg, ReloadUserInterfaceConfig):\r
+                self.gui.reload_config(msg.config)\r
+                continue\r
+            elif isinstance(msg, Request):\r
+                h = handler.HANDLERS[msg.type]\r
+                h.run(self, msg)  # TODO: pause for response?\r
+                continue\r
+        pp = getattr(\r
+            self, '_postprocess_%s' % command.name.replace(' ', '_'), None)\r
+        if pp != None:\r
+            pp(command=command, results=results)\r
+        return results\r
+\r
+    def _handle_request(self, msg):\r
+        """Repeatedly try to get a response to `msg`.\r
+        """\r
+        if prompt == None:\r
+            raise NotImplementedError('_%s_request_prompt' % msg.type)\r
+        prompt_string = prompt(msg)\r
+        parser = getattr(self, '_%s_request_parser' % msg.type, None)\r
+        if parser == None:\r
+            raise NotImplementedError('_%s_request_parser' % msg.type)\r
+        error = None\r
+        while True:\r
+            if error != None:\r
+                self.cmd.stdout.write(''.join([\r
+                        error.__class__.__name__, ': ', str(error), '\n']))\r
+            self.cmd.stdout.write(prompt_string)\r
+            value = parser(msg, self.cmd.stdin.readline())\r
+            try:\r
+                response = msg.response(value)\r
+                break\r
+            except ValueError, error:\r
+                continue\r
+        self.inqueue.put(response)\r
+\r
+\r
     def _GetActiveFileIndex(self):\r
         lib.playlist.Playlist = self.GetActivePlaylist()\r
         #get the selected item from the tree\r
@@ -249,34 +316,12 @@ class HookeFrame (wx.Frame):
         perspectivesFile.write(perspective)\r
         perspectivesFile.close()\r
 \r
-    def execute_command(self, _class, method, command, args):\r
-        self.cmd.inqueue.put(CommandMessage(command, args))\r
-        while True:\r
-            msg = self.cmd.outqueue.get()\r
-            if isinstance(msg, Exit):\r
-                return True\r
-            elif isinstance(msg, CommandExit):\r
-                self.cmd.stdout.write(msg.__class__.__name__+'\n')\r
-                self.cmd.stdout.write(str(msg).rstrip()+'\n')\r
-                break\r
-            elif isinstance(msg, ReloadUserInterfaceConfig):\r
-                self.cmd.ui.reload_config(msg.config)\r
-                continue\r
-            elif isinstance(msg, Request):\r
-                self._handle_request(msg)\r
-                continue\r
-            self.cmd.stdout.write(str(msg).rstrip()+'\n')\r
-                #TODO: run the command\r
-                #command = ''.join(['self.do_', item_text, '()'])\r
-                #self.AppendToOutput(command + '\n')\r
-                #exec(command)\r
-\r
-    def select_plugin(self, _class, method, plugin):\r
+    def select_plugin(self, _class=None, method=None, plugin=None):\r
         for option in config[section]:\r
             properties.append([option, config[section][option]])\r
 \r
     def select_command(self, _class, method, command):\r
-        self.select_plugin(command.plugin)\r
+        self.select_plugin(plugin=command.plugin)\r
         plugin = self.GetItemText(selected_item)\r
         if plugin != 'core':\r
             doc_string = eval('plugins.' + plugin + '.' + plugin + 'Commands.__doc__')\r
@@ -375,7 +420,7 @@ class HookeFrame (wx.Frame):
             perspectives_list.sort()\r
             index = perspectives_list.index(name)\r
             perspective_Id = ID_FirstPerspective + index\r
-            menu_item = self.MenuBar.FindItemById(perspective_Id)\r
+            menu_item = self._c['menu bar'].FindItemById(perspective_Id)\r
             return menu_item\r
         else:\r
             return None\r
@@ -389,17 +434,16 @@ class HookeFrame (wx.Frame):
                 return True\r
         return False\r
 \r
-    def _on_about(self, event):\r
-        message = 'Hooke\n\n'+\\r
-            'A free, open source data analysis platform\n\n'+\\r
-            'Copyright 2006-2008 by Massimo Sandal\n'+\\r
-            'Copyright 2010 by Dr. Rolf Schmidt\n\n'+\\r
-            'Hooke is released under the GNU General Public License version 2.'\r
-        dialog = wx.MessageDialog(self, message, 'About Hooke', wx.OK | wx.ICON_INFORMATION)\r
+    def _on_about(self, *args):\r
+        dialog = wx.MessageDialog(\r
+            parent=self,\r
+            message=self.gui._splash_text(),\r
+            caption='About Hooke',\r
+            style=wx.OK|wx.ICON_INFORMATION)\r
         dialog.ShowModal()\r
         dialog.Destroy()\r
 \r
-    def _on_close(self, event):\r
+    def _on_close(self, *args):\r
         # apply changes\r
         self.gui.config['main height'] = str(self.GetSize().GetHeight())\r
         self.gui.config['main left'] = str(self.GetPosition()[0])\r
@@ -446,13 +490,13 @@ class HookeFrame (wx.Frame):
             self._on_restore_perspective)\r
 \r
     def _on_restore_perspective(self, event):\r
-        name = self.MenuBar.FindItemById(event.GetId()).GetLabel()\r
+        name = self._c['menu bar'].FindItemById(event.GetId()).GetLabel()\r
         self._restore_perspective(name)\r
 \r
     def _on_save_perspective(self, event):\r
         def nameExists(name):\r
-            menu_position = self.MenuBar.FindMenu('Perspective')\r
-            menu = self.MenuBar.GetMenu(menu_position)\r
+            menu_position = self._c['menu bar'].FindMenu('Perspective')\r
+            menu = self._c['menu bar'].GetMenu(menu_position)\r
             for item in menu.GetMenuItems():\r
                 if item.GetText() == name:\r
                     return True\r
@@ -512,7 +556,8 @@ class HookeFrame (wx.Frame):
             options=sorted(os.listdir(self.gui.config['perspective path'])),\r
             message="\nPlease check the perspectives\n\nyou want to delete and click 'Delete'.\n",\r
             button_id=wx.ID_DELETE,\r
-            button_callback=self._on_delete_perspective,\r
+            callbacks={'button': self._on_delete_perspective},\r
+            selection_style='multiple',\r
             parent=self,\r
             label='Delete perspective(s)',\r
             style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER)\r
@@ -525,13 +570,13 @@ class HookeFrame (wx.Frame):
         # ) that makes the radio item indicator in the menu disappear.\r
         # The code should be fine once this issue is fixed.\r
 \r
-    def _on_delete_perspective(self, event, items, selected_items):\r
-        for item in selected_items:\r
-            self._perspectives.remove(item)\r
-            if item == self.gui.config['active perspective']:\r
+    def _on_delete_perspective(self, _class, method, options, selected):\r
+        for p in selected:\r
+            self._perspectives.remove(p)\r
+            if p == self.gui.config['active perspective']:\r
                 self.gui.config['active perspective'] = 'Default'\r
             path = os.path.join(self.gui.config['perspective path'],\r
-                                item+'.txt')\r
+                                p+'.txt')\r
             remove(path)\r
         self._update_perspective_menu()\r
 \r
@@ -546,13 +591,32 @@ class HookeFrame (wx.Frame):
         event.Skip()\r
 \r
     def _next_curve(self, *args):\r
-        '''\r
-        NEXT\r
-        Go to the next curve in the playlist.\r
-        If we are at the last curve, we come back to the first.\r
-        -----\r
-        Syntax: next, n\r
-        '''\r
+        """Call the `next curve` command.\r
+        """\r
+        results = self.execute_command(\r
+            command=self._command_by_name('next curve'))\r
+        if isinstance(results[-1], Success):\r
+            self.execute_command(\r
+                command=self._command_by_name('get curve'))\r
+\r
+    def _previous_curve(self, *args):\r
+        """Call the `previous curve` command.\r
+        """\r
+        self.execute_command(\r
+            command=self._command_by_name('previous curve'))\r
+        if isinstance(results[-1], Success):\r
+            self.execute_command(\r
+                command=self._command_by_name('get curve'))\r
+\r
+    def _postprocess_get_curve(self, command, results):\r
+        """Update `self` to show the curve.\r
+        """\r
+        if not isinstance(results[-1], Success):\r
+            return  # error executing 'get curve'\r
+        assert len(results) == 2, results\r
+        curve = results[0]\r
+        print curve\r
+\r
         selected_item = self._c['playlists']._c['tree'].GetSelection()\r
         if self._c['playlists']._c['tree'].ItemHasChildren(selected_item):\r
             #GetFirstChild returns a tuple\r
@@ -574,33 +638,6 @@ class HookeFrame (wx.Frame):
                 self.UpdateNote()\r
                 self.UpdatePlot()\r
 \r
-    def _previous_curve(self, *args):\r
-        '''\r
-        PREVIOUS\r
-        Go to the previous curve in the playlist.\r
-        If we are at the first curve, we jump to the last.\r
-        -------\r
-        Syntax: previous, p\r
-        '''\r
-        #playlist = self.playlists[self.GetActivePlaylistName()][0]\r
-        #select the previous curve and tell the user if we wrapped around\r
-        #self.AppendToOutput(playlist.previous())\r
-        selected_item = self._c['playlists']._c['tree'].GetSelection()\r
-        if self._c['playlists']._c['tree'].ItemHasChildren(selected_item):\r
-            previous_item = self._c['playlists']._c['tree'].GetLastChild(selected_item)\r
-        else:\r
-            previous_item = self._c['playlists']._c['tree'].GetPrevSibling(selected_item)\r
-            if not previous_item.IsOk():\r
-                parent_item = self._c['playlists']._c['tree'].GetItemParent(selected_item)\r
-                previous_item = self._c['playlists']._c['tree'].GetLastChild(parent_item)\r
-        self._c['playlists']._c['tree'].SelectItem(previous_item, True)\r
-        playlist = self.GetActivePlaylist()\r
-        if playlist.count > 1:\r
-            playlist.previous()\r
-            self._c['status bar'].set_playlist(playlist)\r
-            self.UpdateNote()\r
-            self.UpdatePlot()\r
-\r
     def _on_notebook_page_close(self, event):\r
         ctrl = event.GetEventObject()\r
         playlist_name = ctrl.GetPageText(ctrl._curpage)\r
@@ -642,7 +679,7 @@ class HookeFrame (wx.Frame):
 \r
     def _on_view(self, event):\r
         menu_id = event.GetId()\r
-        menu_item = self.MenuBar.FindItemById(menu_id)\r
+        menu_item = self._c['menu bar'].FindItemById(menu_id)\r
         menu_label = menu_item.GetLabel()\r
 \r
         pane = self._c['manager'].GetPane(menu_label)\r