浏览代码

cmbarton: Advanced grass command console. Try again with repaired subversion
installation (merge https://trac.osgeo.org/grass/changeset/40068 from devbr6)
martinl: Various fixes (merge https://trac.osgeo.org/grass/changeset/40071, https://trac.osgeo.org/grass/changeset/40072, https://trac.osgeo.org/grass/changeset/40073, https://trac.osgeo.org/grass/changeset/40074, https://trac.osgeo.org/grass/changeset/40077
from devbr6)


git-svn-id: https://svn.osgeo.org/grass/grass/trunk@40079 15284696-431f-4ddb-bdfa-cd5b030d7da7

Martin Landa 15 年之前
父节点
当前提交
e7f15480f7
共有 3 个文件被更改,包括 548 次插入81 次删除
  1. 100 57
      gui/wxpython/gui_modules/goutput.py
  2. 442 6
      gui/wxpython/gui_modules/prompt.py
  3. 6 18
      gui/wxpython/wxgui.py

+ 100 - 57
gui/wxpython/gui_modules/goutput.py

@@ -1,4 +1,4 @@
-"""
+"""!
 @package goutput
 @package goutput
 
 
 @brief Command output log widget
 @brief Command output log widget
@@ -9,7 +9,7 @@ Classes:
  - GMStdout
  - GMStdout
  - GMStderr
  - GMStderr
 
 
-(C) 2007-2008 by the GRASS Development Team
+(C) 2007-2009 by the GRASS Development Team
 This program is free software under the GNU General Public
 This program is free software under the GNU General Public
 License (>=v2). Read the file COPYING that comes with GRASS
 License (>=v2). Read the file COPYING that comes with GRASS
 for details.
 for details.
@@ -36,6 +36,8 @@ import gcmd
 import utils
 import utils
 import preferences
 import preferences
 import menuform
 import menuform
+import prompt
+
 from debug import Debug as Debug
 from debug import Debug as Debug
 from preferences import globalSettings as UserSettings
 from preferences import globalSettings as UserSettings
 
 
@@ -127,15 +129,17 @@ class CmdThread(threading.Thread):
     def abort(self):
     def abort(self):
         self.requestCmd.abort()
         self.requestCmd.abort()
     
     
-class GMConsole(wx.Panel):
+class GMConsole(wx.SplitterWindow):
     """!Create and manage output console for commands run by GUI.
     """!Create and manage output console for commands run by GUI.
     """
     """
     def __init__(self, parent, id=wx.ID_ANY, margin=False, pageid=0,
     def __init__(self, parent, id=wx.ID_ANY, margin=False, pageid=0,
                  notebook = None,
                  notebook = None,
                  style=wx.TAB_TRAVERSAL | wx.FULL_REPAINT_ON_RESIZE,
                  style=wx.TAB_TRAVERSAL | wx.FULL_REPAINT_ON_RESIZE,
                  **kwargs):
                  **kwargs):
-        wx.Panel.__init__(self, parent, id, style = style, *kwargs)
+        wx.SplitterWindow.__init__(self, parent, id, style = style, *kwargs)
         self.SetName("GMConsole")
         self.SetName("GMConsole")
+
+        self.panel = wx.Panel(parent = self, id = wx.ID_ANY)
         
         
         # initialize variables
         # initialize variables
         self.Map             = None
         self.Map             = None
@@ -146,6 +150,7 @@ class GMConsole(wx.Panel):
             self._notebook = self.parent.notebook
             self._notebook = self.parent.notebook
         self.lineWidth       = 80
         self.lineWidth       = 80
         self.pageid          = pageid
         self.pageid          = pageid
+                        
         # remember position of line begining (used for '\r')
         # remember position of line begining (used for '\r')
         self.linePos         = -1
         self.linePos         = -1
         
         
@@ -158,15 +163,10 @@ class GMConsole(wx.Panel):
         #
         #
         # progress bar
         # progress bar
         #
         #
-        self.console_progressbar = wx.Gauge(parent=self, id=wx.ID_ANY,
+        self.console_progressbar = wx.Gauge(parent=self.panel, id=wx.ID_ANY,
                                             range=100, pos=(110, 50), size=(-1, 25),
                                             range=100, pos=(110, 50), size=(-1, 25),
                                             style=wx.GA_HORIZONTAL)
                                             style=wx.GA_HORIZONTAL)
         self.console_progressbar.Bind(EVT_CMD_PROGRESS, self.OnCmdProgress)
         self.console_progressbar.Bind(EVT_CMD_PROGRESS, self.OnCmdProgress)
-        # abort
-        self.btn_abort = wx.Button(parent=self, id=wx.ID_STOP)
-        self.btn_abort.SetToolTipString(_("Abort the running command"))
-        self.btn_abort.Bind(wx.EVT_BUTTON, self.OnCmdAbort)
-        self.btn_abort.Enable(False)
         
         
         #
         #
         # text control for command output
         # text control for command output
@@ -178,6 +178,14 @@ class GMConsole(wx.Panel):
         self.cmd_output.Bind(wx.EVT_TIMER, self.OnProcessPendingOutputWindowEvents)
         self.cmd_output.Bind(wx.EVT_TIMER, self.OnProcessPendingOutputWindowEvents)
         self.Bind(EVT_CMD_RUN, self.OnCmdRun)
         self.Bind(EVT_CMD_RUN, self.OnCmdRun)
         self.Bind(EVT_CMD_DONE, self.OnCmdDone)
         self.Bind(EVT_CMD_DONE, self.OnCmdDone)
+
+        #
+        # command prompt
+        #
+        self.cmd_prompt = prompt.GPromptSTC(parent = self.panel, id=wx.ID_ANY,
+                                            onRun = self.RunCmd)
+        if self.parent.GetName() != 'LayerManager':
+            self.cmd_prompt.Hide()
         
         
         #
         #
         # stream redirection
         # stream redirection
@@ -193,46 +201,67 @@ class GMConsole(wx.Panel):
         #
         #
         # buttons
         # buttons
         #
         #
-        self.console_clear = wx.Button(parent=self, id=wx.ID_CLEAR)
-        self.console_save  = wx.Button(parent=self, id=wx.ID_SAVE)
-        self.Bind(wx.EVT_BUTTON, self.ClearHistory, self.console_clear)
-        self.Bind(wx.EVT_BUTTON, self.SaveHistory,  self.console_save)
+        self.btn_console_clear = wx.Button(parent = self.panel, id = wx.ID_ANY,
+                                           label = _("C&lear output"), size=(125,-1))
+        self.btn_cmd_clear = wx.Button(parent = self.panel, id = wx.ID_ANY,
+                                       label = _("Cl&ear command"), size=(125,-1))
+        if self.parent.GetName() != 'LayerManager':
+            self.btn_cmd_clear.Hide()
+        self.btn_console_save  = wx.Button(parent = self.panel, id = wx.ID_ANY,
+                                           label = _("&Save output"), size=(125,-1))
+        # abort
+        self.btn_abort = wx.Button(parent = self.panel, id = wx.ID_ANY, label = _("&Abort command"),
+                                   size=(125,-1))
+        self.btn_abort.SetToolTipString(_("Abort the running command"))
+        self.btn_abort.Enable(False)
 
 
-        self.Bind(EVT_CMD_ABORT, self.OnCmdAbort)
+        self.btn_cmd_clear.Bind(wx.EVT_BUTTON,     self.cmd_prompt.OnCmdErase)
+        self.btn_console_clear.Bind(wx.EVT_BUTTON, self.ClearHistory)
+        self.btn_console_save.Bind(wx.EVT_BUTTON,  self.SaveHistory)
+        self.btn_abort.Bind(wx.EVT_BUTTON,         self.OnCmdAbort)
+        self.btn_abort.Bind(EVT_CMD_ABORT,         self.OnCmdAbort)
         
         
         self.__layout()
         self.__layout()
 
 
     def __layout(self):
     def __layout(self):
         """!Do layout"""
         """!Do layout"""
-        boxsizer1 = wx.BoxSizer(wx.VERTICAL)
-        gridsizer1 = wx.GridSizer(rows=1, cols=2, vgap=0, hgap=0)
-        boxsizer1.Add(item=self.cmd_output, proportion=1,
-                      flag=wx.EXPAND | wx.ADJUST_MINSIZE, border=0)
-        gridsizer1.Add(item=self.console_clear, proportion=0,
-                       flag=wx.ALIGN_CENTER_HORIZONTAL | wx.ADJUST_MINSIZE, border=0)
-        gridsizer1.Add(item=self.console_save, proportion=0,
-                       flag=wx.ALIGN_CENTER_HORIZONTAL | wx.ADJUST_MINSIZE, border=0)
-
-
-        boxsizer1.Add(item=gridsizer1, proportion=0,
-                      flag=wx.EXPAND | wx.ALIGN_CENTRE_VERTICAL | wx.TOP | wx.BOTTOM,
-                      border=5)
-        boxsizer2 = wx.BoxSizer(wx.HORIZONTAL)
-        boxsizer2.Add(item=self.console_progressbar, proportion=1,
-                      flag=wx.EXPAND | wx.ALIGN_CENTRE_VERTICAL)
-        boxsizer2.Add(item=self.btn_abort, proportion=0,
-                      flag=wx.ALIGN_CENTRE_VERTICAL | wx.LEFT,
-                      border = 5)
-        boxsizer1.Add(item=boxsizer2, proportion=0,
-                      flag=wx.EXPAND | wx.ALIGN_CENTRE_VERTICAL | wx.ALL,
-                      border=5)
-        
-        boxsizer1.Fit(self)
-        boxsizer1.SetSizeHints(self)
+        boxsizer = wx.BoxSizer(wx.VERTICAL)
+        buttonsizer = wx.BoxSizer(wx.HORIZONTAL)
+        
+        boxsizer.Add(item=self.cmd_prompt, proportion=1,
+                         flag=wx.EXPAND | wx.ALL, border=1)
+        
+        buttonsizer.Add(item=self.btn_console_clear, proportion=0,
+                        flag=wx.ALIGN_CENTER | wx.FIXED_MINSIZE | wx.ALL, border=5)
+        buttonsizer.Add(item=self.btn_console_save, proportion=0,
+                        flag=wx.ALIGN_CENTER | wx.FIXED_MINSIZE | wx.ALL, border=5)
+        buttonsizer.Add(item=self.btn_cmd_clear, proportion=0,
+                        flag=wx.ALIGN_CENTER | wx.FIXED_MINSIZE | wx.ALL, border=5)
+        buttonsizer.Add(item=self.btn_abort, proportion=0,
+                        flag=wx.ALIGN_CENTER | wx.FIXED_MINSIZE | wx.ALL, border=5)
+        boxsizer.Add(item=buttonsizer, proportion=0,
+                      flag=wx.ALIGN_CENTER)
+        
+        boxsizer.Add(item=self.console_progressbar, proportion=1,
+                      flag=wx.EXPAND | wx.ALIGN_CENTRE_VERTICAL | wx.LEFT | wx.RIGHT | wx.BOTTOM, border=3)
+        
+        boxsizer.Fit(self)
+        boxsizer.SetSizeHints(self)
 
 
+        self.panel.SetSizer(boxsizer)
+        
+        # split window
+        if self.parent.GetName() == 'LayerManager':
+            self.SplitHorizontally(self.cmd_output, self.panel, -75)
+            self.SetMinimumPaneSize(100)
+        else:
+            self.SplitHorizontally(self.cmd_output, self.panel, -10)
+            self.SetMinimumPaneSize(65)
+        self.Fit()
+        
         # layout
         # layout
         self.SetAutoLayout(True)
         self.SetAutoLayout(True)
-        self.SetSizer(boxsizer1)
+        self.Layout()
 
 
     def Redirect(self):
     def Redirect(self):
         """!Redirect stderr
         """!Redirect stderr
@@ -270,7 +299,8 @@ class GMConsole(wx.Panel):
         
         
         # p1 = self.cmd_output.GetCurrentPos()
         # p1 = self.cmd_output.GetCurrentPos()
         p1 = self.cmd_output.GetEndStyled()
         p1 = self.cmd_output.GetEndStyled()
-        self.cmd_output.GotoPos(p1)
+#        self.cmd_output.GotoPos(p1)
+        self.cmd_output.DocumentEnd()
         
         
         for line in text.splitlines():
         for line in text.splitlines():
             # fill space
             # fill space
@@ -350,7 +380,10 @@ class GMConsole(wx.Panel):
                 self.parent.cmdinput.SetHistoryItems()
                 self.parent.cmdinput.SetHistoryItems()
             except AttributeError:
             except AttributeError:
                 pass
                 pass
-        
+
+        # allow writing to output window
+        self.cmd_output.SetReadOnly(False)
+                
         if cmdlist[0] in globalvar.grassCmd['all']:
         if cmdlist[0] in globalvar.grassCmd['all']:
             # send GRASS command without arguments to GUI command interface
             # send GRASS command without arguments to GUI command interface
             # except display commands (they are handled differently)
             # except display commands (they are handled differently)
@@ -411,7 +444,7 @@ class GMConsole(wx.Panel):
                     if os.environ.has_key("GRASS_REGION"):
                     if os.environ.has_key("GRASS_REGION"):
                         del os.environ["GRASS_REGION"]
                         del os.environ["GRASS_REGION"]
                     
                     
-                if len(cmdlist) == 1 and cmdlist[0] not in ('v.krige'):
+                if len(cmdlist) == 1:
                     import menuform
                     import menuform
                     # process GRASS command without argument
                     # process GRASS command without argument
                     menuform.GUI().ParseCommand(cmdlist, parentframe=self)
                     menuform.GUI().ParseCommand(cmdlist, parentframe=self)
@@ -432,13 +465,23 @@ class GMConsole(wx.Panel):
         else:
         else:
             # Send any other command to the shell. Send output to
             # Send any other command to the shell. Send output to
             # console output window
             # console output window
-            
-            self.cmdThread.RunCmd(GrassCmd,
-                                  onDone,
-                                  cmdlist,
-                                  self.cmd_stdout, self.cmd_stderr)                                          
-            self.btn_abort.Enable()
-            self.cmd_output_timer.Start(50)
+
+            # if command is not a GRASS command, treat it like a shell command
+            try:
+#                gcmd.Command(cmdlist,
+#                         stdout=self.cmd_stdout,
+#                         stderr=self.cmd_stderr)
+                self.cmdThread.RunCmd(GrassCmd,
+                                      onDone,
+                                      cmdlist,
+                                      self.cmd_stdout, self.cmd_stderr)
+                self.btn_abort.Enable()
+                self.cmd_output_timer.Start(50)
+            except gcmd.CmdError, e:
+                print >> sys.stderr, e
+        
+        # reset output window to read only
+        self.cmd_output.SetReadOnly(True)
         
         
         return None
         return None
 
 
@@ -524,7 +567,7 @@ class GMConsole(wx.Panel):
                 self.cmd_output.AddTextWrapped(message, wrap=60)
                 self.cmd_output.AddTextWrapped(message, wrap=60)
             else:
             else:
                 self.cmd_output.AddTextWrapped(message, wrap=None)
                 self.cmd_output.AddTextWrapped(message, wrap=None)
-	    
+
         p2 = self.cmd_output.GetCurrentPos()
         p2 = self.cmd_output.GetCurrentPos()
         
         
         if p2 >= p1:
         if p2 >= p1:
@@ -584,7 +627,7 @@ class GMConsole(wx.Panel):
 
 
         # set focus on prompt
         # set focus on prompt
         if self.parent.GetName() == "LayerManager":
         if self.parent.GetName() == "LayerManager":
-            self.parent.cmdinput.SetFocus()
+            self.cmd_prompt.SetFocus()
             self.btn_abort.Enable(False)
             self.btn_abort.Enable(False)
         else:
         else:
             # updated command dialog
             # updated command dialog
@@ -634,7 +677,7 @@ class GMConsole(wx.Panel):
                     dialog.closebox.IsChecked():
                     dialog.closebox.IsChecked():
                 time.sleep(1)
                 time.sleep(1)
                 dialog.Close()
                 dialog.Close()
-        
+
         event.Skip()
         event.Skip()
         
         
     def OnProcessPendingOutputWindowEvents(self, event):
     def OnProcessPendingOutputWindowEvents(self, event):
@@ -750,13 +793,14 @@ class GMStc(wx.stc.StyledTextCtrl):
     def __init__(self, parent, id, margin=False, wrap=None):
     def __init__(self, parent, id, margin=False, wrap=None):
         wx.stc.StyledTextCtrl.__init__(self, parent, id)
         wx.stc.StyledTextCtrl.__init__(self, parent, id)
         self.parent = parent
         self.parent = parent
+        self.SetUndoCollection(True)
+        self.SetReadOnly(True)
 
 
         #
         #
         # styles
         # styles
         #                
         #                
         self.SetStyle()
         self.SetStyle()
         
         
-
         #
         #
         # line margins
         # line margins
         #
         #
@@ -780,7 +824,7 @@ class GMStc(wx.stc.StyledTextCtrl):
         self.SetUseHorizontalScrollBar(True)
         self.SetUseHorizontalScrollBar(True)
 
 
         #
         #
-        # bindins
+        # bindings
         #
         #
         self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
         self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
         
         
@@ -867,4 +911,3 @@ class GMStc(wx.stc.StyledTextCtrl):
                     txt = _('Unable to encode text. Please set encoding in GUI preferences.') + '\n'
                     txt = _('Unable to encode text. Please set encoding in GUI preferences.') + '\n'
                     
                     
                 self.AddText(txt) 
                 self.AddText(txt) 
-    

+ 442 - 6
gui/wxpython/gui_modules/prompt.py

@@ -4,12 +4,12 @@
 @brief wxGUI prompt
 @brief wxGUI prompt
 
 
 Classes:
 Classes:
- - GPrompt
+ - GPromptPopUp
  - PromptListCtrl
  - PromptListCtrl
  - TextCtrlAutoComplete
  - TextCtrlAutoComplete
+ - GPromptSTC
 
 
-@todo: fix TextCtrlAutoComplete to work also on Macs (missing
-wx.PopupWindow())
+@todo: reduce size of STC prompt to about 3 lines
 
 
 (C) 2009 by the GRASS Development Team
 (C) 2009 by the GRASS Development Team
 This program is free software under the GNU General Public
 This program is free software under the GNU General Public
@@ -17,6 +17,7 @@ License (>=v2). Read the file COPYING that comes with GRASS
 for details.
 for details.
 
 
 @author Martin Landa <landa.martin gmail.com>
 @author Martin Landa <landa.martin gmail.com>
+@author Michael Barton <michael.barton@asu.edu>
 """
 """
 
 
 import os
 import os
@@ -24,16 +25,16 @@ import sys
 import shlex
 import shlex
 
 
 import wx
 import wx
+import wx.stc
 import wx.lib.mixins.listctrl as listmix
 import wx.lib.mixins.listctrl as listmix
 
 
 from grass.script import core as grass
 from grass.script import core as grass
 
 
 import globalvar
 import globalvar
-import utils
-import menuform
 import menudata
 import menudata
+import gcmd
 
 
-class GPrompt:
+class GPromptPopUp:
     """!Interactive GRASS prompt"""
     """!Interactive GRASS prompt"""
     def __init__(self, parent):
     def __init__(self, parent):
         self.parent = parent # GMFrame
         self.parent = parent # GMFrame
@@ -664,3 +665,438 @@ class TextCtrlAutoComplete(wx.ComboBox, listmix.ColumnSorterMixin):
             self._showDropDown(False)
             self._showDropDown(False)
         
         
         event.Skip()
         event.Skip()
+
+class GPromptSTC(wx.stc.StyledTextCtrl):
+    """!Styled GRASS prompt with autocomplete and calltips"""    
+    def __init__(self, parent, id, onRun, margin=False, wrap=None):
+        wx.stc.StyledTextCtrl.__init__(self, parent, id)
+        self.parent = parent
+        self.SetUndoCollection(True)        
+        self.RunCmd = onRun
+        
+        #
+        # styles
+        #                
+        self.SetWrapMode(True)
+        
+        #
+        # create command and map lists for autocompletion
+        #
+        self.AutoCompSetIgnoreCase(False) 
+        
+        self.rastlist = []
+        self.vectlist = []
+        self.imglist = []
+        self.r3list = []
+        self.dblist = []
+        self.genlist = []
+        self.displist = []
+        
+        #
+        # Get available GRASS commands and parse into lists by command type for autocomplete
+        #
+        for item in globalvar.grassCmd['all']:
+            if len(item.split('.')) > 1:
+                start,end = item.split('.',1)
+                if start == 'r': self.rastlist.append(end)
+                elif start == 'v': self.vectlist.append(end)
+                elif start == 'i': self.imglist.append(end)
+                elif start == 'r3': self.r3list.append(end)
+                elif start == 'db': self.dblist.append(end)
+                elif start == 'g': self.genlist.append(end)
+                elif start == 'd': self.displist.append(end)
+
+        self.rastlist.sort()
+        self.vectlist.sort()
+        self.imglist.sort()
+        self.r3list.sort()
+        self.dblist.sort()
+        self.genlist.sort()
+        self.displist.sort()
+                        
+        #
+        # Create lists of element types and possible arguments for autocomplete
+        #
+        self.datatypes = []
+        self.maplists = {}
+        self.maptype = ''
+        self.datatypes = ['rast',
+                        'rast3d',
+                        'vect',
+                        'oldvect',
+                        'asciivect',
+                        'labels',
+                        'region',
+                        'region3d',
+                        'group',
+                        '3dview']
+
+        self.drastcmd = ['d.rast',
+                        'd.rgb',
+                        'd.his',
+                        'd.rast.arrow',
+                        'd.rast.num']
+                    
+        self.dvectcmd = ['d.vect',
+                        'd.vect.chart'
+                        'd.thematic.area',
+                        'd.vect.thematic']
+        
+        self.rastargs = ['map',
+                        'input',
+                        'rast',
+                        'raster',
+                        'red',
+                        'green',
+                        'blue',
+                        'h_map',
+                        'i_map',
+                        's_map',
+                        'hue_input',
+                        'intensity_input',
+                        'saturation_input',
+                        'red_input',
+                        'green_input',
+                        'blue_input']
+                        
+        self.__getfiles()
+
+        #
+        # command history buffer
+        #
+        self.cmdbuffer = []
+        self.cmdindex = 0
+
+        #
+        # line margins
+        #
+        # TODO print number only from cmdlog
+        self.SetMarginWidth(1, 0)
+        self.SetMarginWidth(2, 0)
+        if margin:
+            self.SetMarginType(0, wx.stc.STC_MARGIN_NUMBER)
+            self.SetMarginWidth(0, 30)
+        else:
+            self.SetMarginWidth(0, 0)
+
+        #
+        # miscellaneous
+        #
+        self.SetViewWhiteSpace(False)
+#        self.SetTabWidth(4)
+        self.SetUseTabs(False)
+        self.UsePopUp(True)
+        self.SetSelBackground(True, "#FFFF00")
+        self.SetUseHorizontalScrollBar(True)
+
+        #
+        # bindings
+        #
+        self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
+        self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)
+ 
+    def __getfiles(self):   
+        """!Get accessible files for autocomplete"""
+        for item in self.datatypes:
+            mlist = grass.read_command("g.mlist", "m", type=item).splitlines()
+            mlist.sort()
+            self.maplists[item] = mlist
+            
+    def OnKeyPressed(self, event):
+        """!Key press capture for autocompletion, calltips, and command history"""
+        
+        #keycodes used: "." = 46, "=" = 61, "," = 44 
+        line = ''
+        entry = ''
+        usage = ''
+        cmdtype = ''
+        cmdname = ''
+        cmd = ''
+                            
+        # CAN CHANGE: event.ControlDown() for manual autocomplete
+        
+        if event.GetKeyCode() == 46 and not event.ShiftDown():
+            #GRASS command autocomplete when "." is pressed after r,v,i,g,db, or d
+            listcmds = []
+            pos = self.GetCurrentPos()
+            self.InsertText(pos,'.')
+            self.CharRight()
+            
+            entry = self.GetTextLeft()
+            if entry not in ['r.','v.','i.','g.','db.','d.']:
+                return
+
+            if entry == 'r.': listcmds = self.rastlist
+            elif entry == 'v.': listcmds = self.vectlist
+            elif entry == 'i.': listcmds = self.imglist
+            elif entry == 'r3.': listcmds = self.r3list
+            elif entry == 'db.': listcmds = self.dblist
+            elif entry == 'g.': listcmds = self.genlist
+            elif entry == 'd.': listcmds = self.displist
+
+            if listcmds == []:
+                return
+            else:
+                self.AutoCompShow(0, " ".join(listcmds))                    
+            
+        elif event.GetKeyCode() == wx.WXK_TAB:
+            #GRASS command calltips
+                        
+            #Must be a command to the left somewhere
+            pos = self.GetCurrentPos()
+            entry = self.GetTextLeft()
+            cmd = entry.split()[0].strip()
+            if cmd not in globalvar.grassCmd['all']:
+                return
+            
+            usage, description = self.GetCommandUsage(cmd)
+                                        
+            self.CallTipSetBackground("PALE GREEN")
+            self.CallTipSetForeground("BLACK")
+            self.CallTipShow(pos, usage+'\n\n'+description)
+            
+        elif (event.GetKeyCode() == wx.WXK_SPACE and event.ControlDown()) or \
+            event.GetKeyCode() == 61 or event.GetKeyCode() == 44:
+            #Autocompletion for map/data file name entry after '=', ',', or manually
+            
+            pos = self.GetCurrentPos()
+            entry = self.GetTextLeft()
+            if event.GetKeyCode() != 44:
+                self.maptype = ''
+            arg = ''
+            cmdtype = ''
+            cmdname = ''
+            cmd = ''
+
+            if entry.strip()[0:2] in ['r.','v.','i.','g.','db.','d.']:
+                cmdtype =  entry.strip()[0]
+                cmd = entry.split()[0].strip()
+                if cmd in globalvar.grassCmd['all']:
+                    cmdname = cmd.split('.')[1]
+                else:
+                    #No complete GRASS command found
+                    cmd = ''
+                    cmdname = ''
+            elif entry.strip()[0:4] == 'nviz':
+                cmdtype = ''
+                cmdname = cmd = 'nviz'
+            else:
+                #No partial or complete GRASS command found
+                return
+
+            cmdargs = entry.strip('=')
+            try:
+                arg = cmdargs.rsplit(' ',1)[1]
+            except:
+                arg = ''
+                
+            if event.GetKeyCode() == 61:
+                # autocompletion after '='
+                # insert the '=' and move to after the '=', ready for a map name
+                self.InsertText(pos,'=')
+                self.CharRight()
+
+                maplist = []
+                self.maptype = ''
+
+                # what kind of map/data type is desired?
+                if (((cmdtype in ['r', 'i'] or cmd in self.drastcmd) and arg in self.rastargs) or
+                  ((cmd=='nviz' or cmdtype=='r3') and arg in ['elevation','color']) or
+                  arg in ['rast', 'raster']):
+                    self.maptype = 'rast'
+                elif (((cmdtype=='v' or cmd in self.dvectcmd) and arg in ['map', 'input']) or
+                  (cmdtype=='r3' and arg=='input') or
+                  arg in ['vect', 'vector', 'points']):
+                    self.maptype = 'vect'
+                elif ((cmdtype=='r3' and arg in ['map', 'input']) or
+                  (cmdtype=='nviz' and arg=='volume') or arg=='rast3d'):
+                    self.maptype = 'rast3d'
+                elif arg=='labels':
+                    self.maptype ='labels'
+                elif arg=='region':
+                    self.maptype ='region'
+                elif arg=='region3d':
+                    self.maptype ='region3d'
+                elif arg=='group':
+                    self.maptype ='group'
+                elif arg=='3dview':
+                    self.maptype ='3dview'
+                
+            elif event.GetKeyCode() == 44:
+                # autocompletion after ','
+                # if comma is pressed, use the same maptype as previous for multiple map entries
+                
+                # insert the comma and move to after the comma ready for a map name
+                self.InsertText(pos,',')
+                self.CharRight()
+                
+                #must apply to an entry where '=[string]' has already been entered
+                if '=' not in arg:
+                    return
+
+            elif event.GetKeyCode() == wx.WXK_SPACE and event.ControlDown():
+                # manual autocompletion
+                # map entries without arguments (as in r.info [mapname]) use ctrl-shift
+                
+                maplist = []
+                if cmdtype=='r' or cmdtype=='i':
+                    self.maptype = 'rast'
+                elif cmdtype=='v':
+                    self.maptype = 'vect'
+                elif cmdtype=='r3':
+                    self.maptype = 'rast3d'
+                    
+            if self.maptype == '': 
+                return
+            else:
+                maplist = self.maplists[self.maptype]
+                self.AutoCompShow(0, " ".join(maplist))
+                        
+        elif event.GetKeyCode() in [wx.WXK_UP,wx.WXK_DOWN] and event.ControlDown():
+            # Command history using ctrl-up and ctrl-down   
+            
+            if self.cmdbuffer == []: return
+            txt = ''
+
+            self.DocumentEnd()
+            
+            # move through command history list index values
+            if event.GetKeyCode() == wx.WXK_UP:
+                self.cmdindex = self.cmdindex - 1
+            if event.GetKeyCode() == wx.WXK_DOWN:
+                self.cmdindex = self.cmdindex + 1
+            if self.cmdindex < 0:
+                self.cmdindex = 0
+            if self.cmdindex > len(self.cmdbuffer) - 1:
+                self.cmdindex = len(self.cmdbuffer) - 1
+            
+            try:
+                txt = self.cmdbuffer[self.cmdindex]
+            except:
+                pass
+                
+            # clear current line and insert command history    
+            self.DelLineLeft()
+            self.DelLineRight()
+            pos = self.GetCurrentPos()            
+            self.InsertText(pos,txt)
+            self.LineEnd()
+            
+        elif event.GetKeyCode() == wx.WXK_RETURN and self.AutoCompActive() == False:
+            # Run command on line when <return> is pressed    
+            
+            # find the command to run
+            line = str(self.GetCurLine()[0]).strip()
+            if len(line) == 0:
+                return
+            
+            # parse command into list
+            # TODO: shell commands should probably be passed as string           
+            cmd = shlex.split(str(line))
+            
+            #send the command list to the processor 
+            self.RunCmd(cmd)
+                            
+            #add command to history    
+            self.cmdbuffer.append(line)
+            
+            #keep command history to a managable size
+            if len(self.cmdbuffer) > 200:
+                del self.cmdbuffer[0]
+            self.cmdindex = len(self.cmdbuffer)
+
+        else:
+            event.Skip()
+
+    def GetTextLeft(self):
+        """!Returns all text left of the caret"""
+        entry = ''
+        pos = self.GetCurrentPos()
+        self.HomeExtend()
+        entry = self.GetSelectedText().strip()
+        self.SetCurrentPos(pos)
+        
+        return entry
+
+    def GetCommandUsage(self, command):
+        """!Returns command syntax by running command help"""
+        usage = ''
+        description = ''
+
+        ret, out  = gcmd.RunCommand(command, 'help', getErrorMsg = True)
+               
+        if ret == 0:
+            cmdhelp = out.splitlines()
+            addline = False
+            helplist = []
+            description = ''
+            for line in cmdhelp:
+                if "Usage:" in line:
+                    addline = True
+                    continue
+                elif "Flags:" in line:
+                    addline = False
+                    break
+                elif addline == True:
+                    line = line.strip()
+                    helplist.append(line)
+
+            for line in cmdhelp:
+                if "Description:" in line:
+                    addline = True
+                    continue
+                elif "Keywords:" in line:
+                    addline = False
+                    break
+                elif addline == True:
+                    description += (line + ' ')
+                
+            description = description.strip()
+
+            for line in helplist:
+                usage += line + '\n'
+
+            return usage.strip(), description
+        else:
+            return ''   
+
+    def OnDestroy(self, evt):
+        """!The clipboard contents can be preserved after
+        the app has exited"""
+        
+        wx.TheClipboard.Flush()
+        evt.Skip()
+    
+    def OnCmdErase(self, event):
+        """!Erase command prompt"""
+        self.Home()
+        self.DelLineRight()
+        
+    def OnRunCmd(self, event):
+        """!Run command"""
+        cmdString = event.GetString()
+        
+        if self.parent.GetName() != "LayerManager":
+            return
+        
+        if cmdString[:2] == 'd.' and not self.parent.curr_page:
+            self.parent.NewDisplay(show=True)
+        
+        cmd = shlex.split(str(cmdString))
+        if len(cmd) > 1:
+            self.parent.goutput.RunCmd(cmd, switchPage = True)
+        else:
+            self.parent.goutput.RunCmd(cmd, switchPage = False)
+        
+        self.OnUpdateStatusBar(None)
+        
+    def OnUpdateStatusBar(self, event):
+        """!Update Layer Manager status bar"""
+        if self.parent.GetName() != "LayerManager":
+            return
+        
+        if event is None:
+            self.parent.statusbar.SetStatusText("")
+        else:
+            self.parent.statusbar.SetStatusText(_("Type GRASS command and run by pressing ENTER"))
+            event.Skip()

+ 6 - 18
gui/wxpython/wxgui.py

@@ -132,7 +132,6 @@ class GMFrame(wx.Frame):
         self.notebook  = self.__createNoteBook()
         self.notebook  = self.__createNoteBook()
         self.menubar, self.menudata = self.__createMenuBar()
         self.menubar, self.menudata = self.__createMenuBar()
         self.statusbar = self.CreateStatusBar(number=1)
         self.statusbar = self.CreateStatusBar(number=1)
-        self.cmdprompt, self.cmdinput = self.__createCommandPrompt()
         self.toolbar   = self.__createToolBar()
         self.toolbar   = self.__createToolBar()
         
         
         # bindings
         # bindings
@@ -146,16 +145,11 @@ class GMFrame(wx.Frame):
         self._auimgr.AddPane(self.notebook, wx.aui.AuiPaneInfo().
         self._auimgr.AddPane(self.notebook, wx.aui.AuiPaneInfo().
                              Left().CentrePane().BestSize((-1,-1)).Dockable(False).
                              Left().CentrePane().BestSize((-1,-1)).Dockable(False).
                              CloseButton(False).DestroyOnClose(True).Row(1).Layer(0))
                              CloseButton(False).DestroyOnClose(True).Row(1).Layer(0))
-        self._auimgr.AddPane(self.cmdprompt, wx.aui.AuiPaneInfo().
-                             Bottom().BestSize((-1, -1)).Dockable(False).
-                             CloseButton(False).DestroyOnClose(True).
-                             PaneBorder(False).Row(1).Layer(0).Position(0).
-                             CaptionVisible(False))
 
 
         self._auimgr.Update()
         self._auimgr.Update()
 
 
         wx.CallAfter(self.notebook.SetSelection, 0)
         wx.CallAfter(self.notebook.SetSelection, 0)
-        wx.CallAfter(self.cmdinput.SetFocus)
+        wx.CallAfter(self.goutput.cmd_prompt.SetFocus)
         
         
         # use default window layout ?
         # use default window layout ?
         if UserSettings.Get(group='general', key='defWindowPos', subkey='enabled') is True:
         if UserSettings.Get(group='general', key='defWindowPos', subkey='enabled') is True:
@@ -192,13 +186,7 @@ class GMFrame(wx.Frame):
         # start with layer manager on top
         # start with layer manager on top
         self.curr_page.maptree.mapdisplay.Raise()
         self.curr_page.maptree.mapdisplay.Raise()
         self.Raise()
         self.Raise()
-        
-    def __createCommandPrompt(self):
-        """!Creates command-line input area"""
-        p = prompt.GPrompt(self)
-
-        return p.GetPanel(), p.GetInput()
-    
+            
     def __createMenuBar(self):
     def __createMenuBar(self):
         """!Creates menubar"""
         """!Creates menubar"""
 
 
@@ -276,7 +264,7 @@ class GMFrame(wx.Frame):
 
 
         # create command output text area and add it to main notebook page
         # create command output text area and add it to main notebook page
         self.goutput = goutput.GMConsole(self, pageid=1)
         self.goutput = goutput.GMConsole(self, pageid=1)
-        self.outpage = self.notebook.AddPage(self.goutput, text=_("Command output"))
+        self.outpage = self.notebook.AddPage(self.goutput, text=_("Command console"))
 
 
         # bindings
         # bindings
         self.gm_cb.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnCBPageChanged)
         self.gm_cb.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnCBPageChanged)
@@ -367,7 +355,7 @@ class GMFrame(wx.Frame):
         page = event.GetSelection()
         page = event.GetSelection()
         if page == self.goutput.pageid:
         if page == self.goutput.pageid:
             # remove '(...)'
             # remove '(...)'
-            self.notebook.SetPageText(page, _("Command output"))
+            self.notebook.SetPageText(page, _("Command console"))
         
         
         event.Skip()
         event.Skip()
 
 
@@ -454,7 +442,7 @@ class GMFrame(wx.Frame):
         if event:
         if event:
             cmd = self.GetMenuCmd(event)
             cmd = self.GetMenuCmd(event)
         self.goutput.RunCmd(cmd, switchPage=True)
         self.goutput.RunCmd(cmd, switchPage=True)
-        
+
     def OnMenuCmd(self, event, cmd = ''):
     def OnMenuCmd(self, event, cmd = ''):
         """!Parse command selected from menu"""
         """!Parse command selected from menu"""
         if event:
         if event:
@@ -1491,7 +1479,7 @@ class GMFrame(wx.Frame):
         """!Quit GRASS session (wxGUI and shell)"""
         """!Quit GRASS session (wxGUI and shell)"""
         # quit wxGUI session
         # quit wxGUI session
         self.OnCloseWindow(event)
         self.OnCloseWindow(event)
-        
+
         # quit GRASS shell
         # quit GRASS shell
         try:
         try:
             pid = int(os.environ['GIS_LOCK'])
             pid = int(os.environ['GIS_LOCK'])