[GRASS-SVN] r32364 - grass/trunk/gui/wxpython/gui_modules

svn_grass at osgeo.org svn_grass at osgeo.org
Tue Jul 29 19:31:59 EDT 2008


Author: martinl
Date: 2008-07-29 19:31:59 -0400 (Tue, 29 Jul 2008)
New Revision: 32364

Modified:
   grass/trunk/gui/wxpython/gui_modules/gcmd.py
   grass/trunk/gui/wxpython/gui_modules/goutput.py
   grass/trunk/gui/wxpython/gui_modules/menuform.py
Log:
wxGUI: general goutput module reconstruction, event-based (fix problems with the last wxPython versions) -- experimental (TODO: abort, clean up gcmd module, use 
grass module)


Modified: grass/trunk/gui/wxpython/gui_modules/gcmd.py
===================================================================
--- grass/trunk/gui/wxpython/gui_modules/gcmd.py	2008-07-29 13:44:13 UTC (rev 32363)
+++ grass/trunk/gui/wxpython/gui_modules/gcmd.py	2008-07-29 23:31:59 UTC (rev 32364)
@@ -491,8 +491,8 @@
     @param stdout  redirect standard output or None
     @param stderr  redirect standard error output or None
     """
-    def __init__ (self, cmd, stdin=None,
-                  stdout=None, stderr=sys.stderr):
+    def __init__ (self, cmd,
+                  stdin=None, stdout=None, stderr=sys.stderr):
 
         Thread.__init__(self)
         
@@ -517,6 +517,7 @@
             return
         
         self.startTime = time.time()
+        
         # TODO: wx.Exectute/wx.Process (?)
         try:
             self.module = Popen(self.cmd,
@@ -526,8 +527,7 @@
         except OSError, e:
             self.rerr = str(e)
             return
-            # raise CmdError(self.cmd[0], str(e))
-
+        
         if self.stdin: # read stdin if requested ...
             self.module.stdin.write(self.stdin)
             self.module.stdin.close()
@@ -536,21 +536,6 @@
         if self.stdout or self.stderr:
             self.__redirect_stream()
 
-    def __read_all(self, fd):
-        out = ""
-        while True:
-            try:
-                bytes = fd.read(4096)
-            except IOError, e:
-                if e[0] != errno.EAGAIN:
-                    raise
-                break
-            if not bytes:
-                break
-            out += bytes
-
-        return out
-
     def __redirect_stream(self):
         """Redirect stream"""
         if self.stdout:
@@ -569,31 +554,27 @@
                 
         # wait for the process to end, sucking in stuff until it does end
         while self.module.poll() is None:
-            time.sleep(.1)
             if self._want_abort: # abort running process
                 self.module.kill()
                 self.aborted = True
                 if hasattr(self.stderr, "gmstc"):
                     # -> GMConsole
                     wx.PostEvent(self.stderr.gmstc.parent, ResultEvent(self))
+                    pass
                 return 
             if self.stdout:
-                # line = self.__read_all(self.module.stdout)
                 line = recv_some(self.module, e=0, stderr=0)
                 self.stdout.write(line)
             if self.stderr:
-                # line = self.__read_all(self.module.stderr)
                 line = recv_some(self.module, e=0, stderr=1)
                 self.stderr.write(line)
                 self.rerr = line
 
         # get the last output
         if self.stdout:
-            # line = self.__read_all(self.module.stdout)
             line = recv_some(self.module, e=0, stderr=0)
             self.stdout.write(line)
         if self.stderr:
-            # line = self.__read_all(self.module.stderr)
             line = recv_some(self.module, e=0, stderr=1)
             self.stderr.write(line)
             if len(line) > 0:
@@ -602,11 +583,94 @@
         if hasattr(self.stderr, "gmstc"):
             # -> GMConsole
             wx.PostEvent(self.stderr.gmstc.parent, ResultEvent(self))
+            pass
+        
+    def abort(self):
+        """Abort running process, used by main thread to signal an abort"""
+        self._want_abort = True
 
+class RunCommand:
+    """Run command in separate thread
+
+    @param cmd GRASS command (given as list)
+    @param stdin standard input stream 
+    @param stdout  redirect standard output or None
+    @param stderr  redirect standard error output or None
+    """
+    def __init__ (self, cmd, stdin=None,
+                  stdout=sys.stdout, stderr=sys.stderr):
+
+        self.cmd    = cmd
+        self.stdin  = stdin
+        self.stdout = stdout
+        self.stderr = stderr
+        
+        self._want_abort = False
+        self.aborted = False
+        
+        self.startTime = time.time()
+        
+        try:
+            self.module = Popen(self.cmd,
+                                stdin=subprocess.PIPE,
+                                stdout=subprocess.PIPE,
+                                stderr=subprocess.PIPE)
+        except OSError, e:
+            self.rerr = str(e)
+            return 1
+        
+        if self.stdin: # read stdin if requested ...
+            self.module.stdin.write(self.stdin)
+            self.module.stdin.close()
+
+        # redirect standard outputs...
+        if self.stdout or self.stderr:
+            self.__redirect_stream()
+
+    def __redirect_stream(self):
+        """Redirect stream"""
+        if self.stdout:
+            # make module stdout/stderr non-blocking
+            out_fileno = self.module.stdout.fileno()
+	    if not subprocess.mswindows:
+                flags = fcntl.fcntl(out_fileno, fcntl.F_GETFL)
+                fcntl.fcntl(out_fileno, fcntl.F_SETFL, flags| os.O_NONBLOCK)
+                
+        if self.stderr:
+            # make module stdout/stderr non-blocking
+            out_fileno = self.module.stderr.fileno()
+	    if not subprocess.mswindows:
+                flags = fcntl.fcntl(out_fileno, fcntl.F_GETFL)
+                fcntl.fcntl(out_fileno, fcntl.F_SETFL, flags| os.O_NONBLOCK)
+                
+        # wait for the process to end, sucking in stuff until it does end
+        while self.module.poll() is None:
+            if self._want_abort: # abort running process
+                self.module.kill()
+                return
+            if self.stdout:
+                line = recv_some(self.module, e=0, stderr=0)
+                self.stdout.write(line)
+            if self.stderr:
+                line = recv_some(self.module, e=0, stderr=1)
+                self.stderr.write(line)
+                self.rerr = line
+
+        # get the last output
+        if self.stdout:
+            line = recv_some(self.module, e=0, stderr=0)
+            self.stdout.write(line)
+        if self.stderr:
+            line = recv_some(self.module, e=0, stderr=1)
+            self.stderr.write(line)
+            if len(line) > 0:
+                self.rerr = line
+
     def abort(self):
         """Abort running process, used by main thread to signal an abort"""
+        print 'a'
         self._want_abort = True
-
+    
 # testing ...
 if __name__ == "__main__":
     SEP = "-----------------------------------------------------------------------------"

Modified: grass/trunk/gui/wxpython/gui_modules/goutput.py
===================================================================
--- grass/trunk/gui/wxpython/gui_modules/goutput.py	2008-07-29 13:44:13 UTC (rev 32363)
+++ grass/trunk/gui/wxpython/gui_modules/goutput.py	2008-07-29 23:31:59 UTC (rev 32364)
@@ -15,21 +15,72 @@
 for details.
 
 @author Michael Barton (Arizona State University)
-Martin Landa <landa.martin gmail.com>
+ at author Martin Landa <landa.martin gmail.com>
 """
 
 import os
 import sys
 import textwrap
 import time
+import threading
+import Queue
 
 import wx
 import wx.stc
+from wx.lib.newevent import NewEvent
 
 import globalvar
 import gcmd
 from debug import Debug as Debug
 
+wxCmdOutput,   EVT_CMD_OUTPUT   = NewEvent()
+wxCmdProgress, EVT_CMD_PROGRESS = NewEvent()
+wxCmdDone,     EVT_CMD_DONE     = NewEvent()
+wxCmdAbort,    EVT_CMD_ABORT    = NewEvent()
+
+def GrassCmd(cmd, stdout, stderr):
+    """Run GRASS command on background"""
+    return gcmd.RunCommand(cmd=cmd,
+                           stdout=stdout, stderr=stderr)
+
+class CmdThread(threading.Thread):
+    """Thread for GRASS commands"""
+    requestId = 0
+    def __init__(self, parent, requestQ, resultQ, **kwds):
+        threading.Thread.__init__(self, **kwds)
+
+        self.setDaemon(True)
+
+        self.parent = parent # GMConsole
+        
+        self.requestQ = requestQ
+        self.resultQ = resultQ
+
+        self.start()
+
+    def RunCmd(self, callable, *args, **kwds):
+        CmdThread.requestId += 1
+
+        self.requestTime = time.time()
+
+        self.requestQ.put((CmdThread.requestId, callable, args, kwds))
+        
+        return CmdThread.requestId
+
+    def run(self):
+        while True:
+            requestId, callable, args, kwds = self.requestQ.get()
+            self.resultQ.put((requestId, callable(*args, **kwds)))
+
+            event = wxCmdDone(aborted=False,
+                              time=self.requestTime,
+                              pid=requestId)
+            wx.PostEvent(self.parent, event)
+
+    def abort(self):
+        # TODO
+        print self.resultQ.get_()
+        
 class GMConsole(wx.Panel):
     """
     Create and manage output console for commands entered on the
@@ -43,33 +94,58 @@
         # initialize variables
         self.Map             = None
         self.parent          = parent # GMFrame
-        self.cmdThreads      = {}     # cmdThread : cmdPID
         self.lineWidth       = 80
         self.pageid          = pageid
 
+        #
+        # create queues
+        #
+        self.requestQ = Queue.Queue()
+        self.resultQ = Queue.Queue()
+
+        #
         # progress bar
+        #
         self.console_progressbar = wx.Gauge(parent=self, id=wx.ID_ANY,
                                             range=100, pos=(110, 50), size=(-1, 25),
                                             style=wx.GA_HORIZONTAL)
-
+        self.console_progressbar.Bind(EVT_CMD_PROGRESS, self.OnCmdProgress)
+        
+        #
         # text control for command output
+        #
         self.cmd_output = GMStc(parent=self, id=wx.ID_ANY, margin=margin,
                                 wrap=None) 
+        self.cmd_output_timer = wx.Timer(self.cmd_output, id=wx.ID_ANY)
+        self.cmd_output.Bind(EVT_CMD_OUTPUT, self.OnCmdOutput)
+        self.cmd_output.Bind(wx.EVT_TIMER, self.OnProcessPendingOutputWindowEvents)
+        self.Bind(EVT_CMD_DONE, self.OnCmdDone)
         
-        # redirect
-        self.cmd_stdout = GMStdout(self.cmd_output)
-        self.cmd_stderr = GMStderr(self.cmd_output,
-                                   self.console_progressbar,
-                                   self.parent.notebook,
-                                   pageid)
+        #
+        # stream redirection
+        #
+        self.cmd_stdout = GMStdout(self)
+        self.cmd_stderr = GMStderr(self)
 
+        #
+        # thread
+        #
+        self.cmdThread = CmdThread(self, self.requestQ, self.resultQ)
+
+        #
         # 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)
 
-        # output control layout
+        self.Bind(EVT_CMD_ABORT, self.OnCmdDone)
+        
+        self.__layout()
+
+    def __layout(self):
+        """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,
@@ -89,9 +165,6 @@
         boxsizer1.Fit(self)
         boxsizer1.SetSizeHints(self)
 
-        # set up event handler for any command thread results
-        gcmd.EVT_RESULT(self, self.OnResult)
-        
         # layout
         self.SetAutoLayout(True)
         self.SetSizer(boxsizer1)
@@ -104,8 +177,9 @@
         """
         if Debug.get_level() == 0:
             # don't redirect when debugging is enabled
-            sys.stdout = self.cmd_stdout
-            sys.stderr = self.cmd_stderr
+            #sys.stdout = self.cmd_stdout
+            #sys.stderr = self.cmd_stderr
+            pass
 
             return True
 
@@ -122,7 +196,7 @@
         if not style:
             style = self.cmd_output.StyleDefault
 
-        self.cmd_output.GotoPos(self.cmd_output.GetEndStyled())
+        self.cmd_output.GotoPos(self.cmd_output.GetLength())
         p1 = self.cmd_output.GetCurrentPos()
         
         # fill space
@@ -131,16 +205,17 @@
             line += diff * ' '
 
         self.cmd_output.AddTextWrapped(line, wrap=wrap) # adds os.linesep
-        self.cmd_output.EnsureCaretVisible()
+
         p2 = self.cmd_output.GetCurrentPos()
         self.cmd_output.StartStyling(p1, 0xff)
         self.cmd_output.SetStyling(p2 - p1, style)
 
+        self.cmd_output.EnsureCaretVisible()
+        
     def WriteCmdLog(self, line, pid=None):
         """Write out line in selected style"""
         if pid:
             line = '(' + str(pid) + ') ' + line
-
         self.WriteLog(line, self.cmd_output.StyleCommand)
 
     def RunCmd(self, command):
@@ -164,7 +239,7 @@
         except:
             curr_disp = None
 
-        if len(self.GetListOfCmdThreads()) > 0:
+        if not self.requestQ.empty():
             # only one running command enabled (per GMConsole instance)
             busy = wx.BusyInfo(message=_("Unable to run the command, another command is running..."),
                                parent=self)
@@ -228,16 +303,15 @@
                     menuform.GUI().ParseCommand(cmdlist, parentframe=self)
                 else:
                     # process GRASS command with argument
-                    cmdPID = len(self.cmdThreads.keys())+1
+                    cmdPID = self.cmdThread.requestId + 1
                     self.WriteCmdLog('%s' % ' '.join(cmdlist), pid=cmdPID)
+
+                    self.cmdThread.RunCmd(GrassCmd,
+                                          cmdlist, self.cmd_stdout, self.cmd_stderr)
                     
-                    grassCmd = gcmd.Command(cmdlist, wait=False,
-                                            stdout=self.cmd_stdout,
-                                            stderr=self.cmd_stderr)
-                    
-                    self.cmdThreads[grassCmd.cmdThread] = { 'cmdPID' : cmdPID }
-                    
-                    return grassCmd
+                    self.cmd_output_timer.Start(50)
+
+                    return None
                 # deactivate computational region and return to display settings
                 if tmpreg:
                     os.environ["GRASS_REGION"] = tmpreg
@@ -291,38 +365,68 @@
 
         dlg.Destroy()
 
-    def GetListOfCmdThreads(self, onlyAlive=True):
-        """Return list of command threads)"""
-        list = []
-        for t in self.cmdThreads.keys():
-            Debug.msg (4, "GMConsole.GetListOfCmdThreads(): name=%s, alive=%s" %
-                       (t.getName(), t.isAlive()))
-            if onlyAlive and not t.isAlive():
-                continue
-            list.append(t)
+    def GetCmd(self):
+        """Get running command or None"""
+        return self.requestQ.get()
+        
+    def OnCmdOutput(self, event):
+        """Print command output"""
+        message = event.text
+        type  = event.type
 
-        return list
+        # message prefix
+        if type == 'warning':
+            messege = 'WARNING: ' + message
+        elif type == 'error':
+            message = 'ERROR: ' + message
+            
+        p1 = self.cmd_output.GetCurrentPos()
 
-    def OnResult(self, event):
-        """Show result status"""
+        if os.linesep not in message:
+            self.cmd_output.AddTextWrapped(message, wrap=60)
+        else:
+            self.cmd_output.AppendText(message)
+
+        p2 = self.cmd_output.GetCurrentPos()
+        self.cmd_output.StartStyling(p1, 0xff)
+        if type == 'error':
+            self.cmd_output.SetStyling(p2 - p1 + 1, self.cmd_output.StyleError)
+        elif type == 'warning':
+            self.cmd_output.SetStyling(p2 - p1 + 1, self.cmd_output.StyleWarning)
+        elif type == 'message':
+            self.cmd_output.SetStyling(p2 - p1 + 1, self.cmd_output.StyleMessage)
+        else: # unknown
+            self.cmd_output.SetStyling(p2 - p1 + 1, self.cmd_output.StyleUnknown)
+            
+        self.cmd_output.EnsureCaretVisible()
         
-        if event.cmdThread.aborted:
+    def OnCmdProgress(self, event):
+        """Update progress message info"""
+        self.console_progressbar.SetValue(event.value)
+
+    def OnCmdDone(self, event):
+        """Command done (or aborted)"""
+        if event.aborted:
+            self.cmdThread.abort()
+            
             # Thread aborted (using our convention of None return)
             self.WriteLog(_('Please note that the data are left in incosistent stage '
                             'and can be corrupted'), self.cmd_output.StyleWarning)
             self.WriteCmdLog(_('Command aborted'),
-                             pid=self.cmdThreads[event.cmdThread]['cmdPID'])
+                             pid=self.cmdThread.requestId)
         else:
             try:
                 # Process results here
-                self.WriteCmdLog(_('Command finished (%d sec)') % (time.time() - event.cmdThread.startTime),
-                                 pid=self.cmdThreads[event.cmdThread]['cmdPID'])
+                self.WriteCmdLog(_('Command finished (%d sec)') % (time.time() - event.time),
+                                 pid=event.pid)
             except KeyError:
                 # stopped deamon
                 pass
-                
+        
         self.console_progressbar.SetValue(0) # reset progress bar on '0%'
 
+        self.cmd_output_timer.Stop()
+
         # updated command dialog
         if hasattr(self.parent.parent, "btn_run"):
             dialog = self.parent.parent
@@ -340,12 +444,17 @@
                 dialog.btn_help.Enable(True)
 
             dialog.btn_run.Enable(True)
-            
+
             if dialog.get_dcmd is None and \
                    dialog.closebox.IsChecked():
                 time.sleep(1)
                 dialog.Close()
 
+        event.Skip()
+        
+    def OnProcessPendingOutputWindowEvents(self, event):
+        self.ProcessPendingEvents()
+
 class GMStdout:
     """GMConsole standard output
 
@@ -357,22 +466,20 @@
     Copyright: (c) 2005-2007 Jean-Michel Fauth
     Licence:   GPL
     """
-    def __init__(self, gmstc):
-        self.gmstc  = gmstc
+    def __init__(self, parent):
+        self.parent = parent # GMConsole
 
     def write(self, s):
         if len(s) == 0 or s == '\n':
             return
         s = s.replace('\n', os.linesep)
+        
         for line in s.split(os.linesep):
-            p1 = self.gmstc.GetCurrentPos() # get caret position
-            self.gmstc.AddTextWrapped(line, wrap=None) # no wrapping && adds os.linesep
-            self.gmstc.EnsureCaretVisible()
-            p2 = self.gmstc.GetCurrentPos()
-            self.gmstc.StartStyling(p1, 0xff)
-            self.gmstc.SetStyling(p2 - p1 + 1, self.gmstc.StyleOutput)
-
-class GMStderr(object):
+            evt = wxCmdOutput(text=line + os.linesep,
+                              type='')
+            wx.PostEvent(self.parent.cmd_output, evt)
+        
+class GMStderr:
     """GMConsole standard error output
 
     Based on FrameOutErr.py
@@ -383,37 +490,29 @@
     Copyright: (c) 2005-2007 Jean-Michel Fauth
     Licence:   GPL
     """
-    def __init__(self, gmstc, gmgauge, notebook, pageid):
-        self.gmstc    = gmstc
-        self.gmgauge  = gmgauge
-        self.notebook = notebook
-        self.pageid   = pageid
-
+    def __init__(self, parent):
+        self.parent = parent # GMConsole
+ 
         self.type = ''
         self.message = ''
         self.printMessage = False
-        
+
     def write(self, s):
-        if self.pageid > -1:
-            # swith notebook page to 'command output'
-            if self.notebook.GetSelection() != self.pageid: 
-                self.notebook.SetSelection(self.pageid)
-
         s = s.replace('\n', os.linesep)
         # remove/replace escape sequences '\b' or '\r' from stream
         s = s.replace('\b', '').replace('\r', '%s' % os.linesep)
-
+        progressValue = -1
+        
         for line in s.split(os.linesep):
             if len(line) == 0:
                 continue
-            
+
             if 'GRASS_INFO_PERCENT' in line:
-                # 'GRASS_INFO_PERCENT: 10' -> value=10
                 value = int(line.rsplit(':', 1)[1].strip())
                 if value >= 0 and value < 100:
-                    self.gmgauge.SetValue(value)
+                    progressValue = value
                 else:
-                    self.gmgauge.SetValue(0) # reset progress bar on '0%'
+                    progressValue = 0
             elif 'GRASS_INFO_MESSAGE' in line:
                 self.type = 'message'
                 self.message = line.split(':', 1)[1].strip()
@@ -427,39 +526,28 @@
                 self.printMessage = True
             elif not self.type:
                 if len(line) > 0:
-                    p1 = self.gmstc.GetCurrentPos()
-                    self.gmstc.AddTextWrapped(line, wrap=60) # wrap && add os.linesep
-                    self.gmstc.EnsureCaretVisible()
-                    p2 = self.gmstc.GetCurrentPos()
-                    self.gmstc.StartStyling(p1, 0xff)
-                    self.gmstc.SetStyling(p2 - p1 + 1, self.gmstc.StyleUnknown)
+                    continue
+                evt = wxCmdOutput(text=line,
+                                  type='')
+                wx.PostEvent(self.parent.cmd_output, evt)
             elif len(line) > 0:
                 self.message += line.strip() + os.linesep
 
             if self.printMessage and len(self.message) > 0:
-                p1 = self.gmstc.GetCurrentPos()
-                if self.type == 'warning':
-                    self.message = 'WARNING: ' + self.message
-                elif self.type == 'error':
-                    self.message = 'ERROR: ' + self.message
-                if os.linesep not in self.message:
-                    self.gmstc.AddTextWrapped(self.message, wrap=60) #wrap && add os.linesep
-                else:
-                    self.gmstc.AddText(self.message)
-                self.gmstc.EnsureCaretVisible()
-                p2 = self.gmstc.GetCurrentPos()
-                self.gmstc.StartStyling(p1, 0xff)
-                if self.type == 'error':
-                    self.gmstc.SetStyling(p2 - p1 + 1, self.gmstc.StyleError)
-                elif self.type == 'warning':
-                    self.gmstc.SetStyling(p2 - p1 + 1, self.gmstc.StyleWarning)
-                elif self.type == 'self.message':
-                    self.gmstc.SetStyling(p2 - p1 + 1, self.gmstc.StyleSelf.Message)
+                evt = wxCmdOutput(text=self.message,
+                                  type=self.type)
+                wx.PostEvent(self.parent.cmd_output, evt)
 
                 self.type = ''
                 self.message = ''
                 self.printMessage = False
 
+        # update progress message
+        if progressValue > -1:
+            # self.gmgauge.SetValue(progressValue)
+            evt = wxCmdProgress(value=progressValue)
+            wx.PostEvent(self.parent.console_progressbar, evt)
+            
 class GMStc(wx.stc.StyledTextCtrl):
     """Styled GMConsole
 
@@ -542,7 +630,7 @@
         wx.TheClipboard.Flush()
         evt.Skip()
 
-    def AddTextWrapped(self, str, wrap=None):
+    def AddTextWrapped(self, txt, wrap=None):
         """Add string to text area.
 
         String is wrapped and linesep is also added to the end
@@ -551,12 +639,13 @@
             wrap = self.wrap
 
         if wrap is not None:
-            str = textwrap.fill(str, wrap) + os.linesep
+            txt = textwrap.fill(txt, wrap) + os.linesep
         else:
-            str += os.linesep
+            txt += os.linesep
 
-        self.AddText(str)
+        self.AddText(txt)
 
+
     def SetWrap(self, wrap):
         """Set wrapping value
 

Modified: grass/trunk/gui/wxpython/gui_modules/menuform.py
===================================================================
--- grass/trunk/gui/wxpython/gui_modules/menuform.py	2008-07-29 13:44:13 UTC (rev 32363)
+++ grass/trunk/gui/wxpython/gui_modules/menuform.py	2008-07-29 23:31:59 UTC (rev 32364)
@@ -773,7 +773,7 @@
 
     def OnRun(self, event):
         """Run the command"""
-        if len(self.goutput.GetListOfCmdThreads()) > 0:
+        if not self.goutput.resultQ.empty():
             return
 
         cmd = self.createCmd()
@@ -807,10 +807,8 @@
 
     def OnAbort(self, event):
         """Abort running command"""
-        try:
-            self.goutput.GetListOfCmdThreads()[0].abort()
-        except IndexError:
-            pass
+        event = goutput.wxCmdAbort(aborted=True)
+        wx.PostEvent(self.goutput, event)
 
     def OnCopy(self, event):
         """Copy the command"""



More information about the grass-commit mailing list