GRASS Programmer's Manual  6.4.2(2012)
goutput.py
Go to the documentation of this file.
00001 """!
00002 @package goutput
00003 
00004 @brief Command output log widget
00005 
00006 Classes:
00007  - GMConsole
00008  - GMStc
00009  - GMStdout
00010  - GMStderr
00011 
00012 (C) 2007-2011 by the GRASS Development Team
00013 This program is free software under the GNU General Public
00014 License (>=v2). Read the file COPYING that comes with GRASS
00015 for details.
00016 
00017 @author Michael Barton (Arizona State University)
00018 @author Martin Landa <landa.martin gmail.com>
00019 @author Vaclav Petras <wenzeslaus gmail.com> (copy&paste customization)
00020 """
00021 
00022 import os
00023 import sys
00024 import textwrap
00025 import time
00026 import threading
00027 import Queue
00028 import codecs
00029 import locale
00030 
00031 import wx
00032 import wx.stc
00033 from wx.lib.newevent import NewEvent
00034 
00035 import grass.script as grass
00036 from   grass.script import task as gtask
00037 
00038 import globalvar
00039 import gcmd
00040 import utils
00041 import preferences
00042 import menuform
00043 import prompt
00044 
00045 from debug       import Debug
00046 from preferences import globalSettings as UserSettings
00047 from ghelp       import SearchModuleWindow
00048 
00049 wxCmdOutput,   EVT_CMD_OUTPUT   = NewEvent()
00050 wxCmdProgress, EVT_CMD_PROGRESS = NewEvent()
00051 wxCmdRun,      EVT_CMD_RUN      = NewEvent()
00052 wxCmdDone,     EVT_CMD_DONE     = NewEvent()
00053 wxCmdAbort,    EVT_CMD_ABORT    = NewEvent()
00054 
00055 def GrassCmd(cmd, stdout = None, stderr = None):
00056     """!Return GRASS command thread"""
00057     return gcmd.CommandThread(cmd,
00058                               stdout = stdout, stderr = stderr)
00059 
00060 class CmdThread(threading.Thread):
00061     """!Thread for GRASS commands"""
00062     requestId = 0
00063     def __init__(self, parent, requestQ, resultQ, **kwds):
00064         threading.Thread.__init__(self, **kwds)
00065 
00066         self.setDaemon(True)
00067 
00068         self.parent = parent # GMConsole
00069         self._want_abort_all = False
00070         
00071         self.requestQ = requestQ
00072         self.resultQ = resultQ
00073 
00074         self.start()
00075 
00076     def RunCmd(self, *args, **kwds):
00077         CmdThread.requestId += 1
00078 
00079         self.requestCmd = None
00080         self.requestQ.put((CmdThread.requestId, args, kwds))
00081         
00082         return CmdThread.requestId
00083 
00084     def SetId(self, id):
00085         """!Set starting id"""
00086         CmdThread.requestId = id
00087         
00088     def run(self):
00089         os.environ['GRASS_MESSAGE_FORMAT'] = 'gui'
00090         while True:
00091             requestId, args, kwds = self.requestQ.get()
00092             for key in ('callable', 'onDone', 'userData'):
00093                 if key in kwds:
00094                     vars()[key] = kwds[key]
00095                     del kwds[key]
00096                 else:
00097                     vars()[key] = None
00098             
00099             if not vars()['callable']:
00100                 vars()['callable'] = GrassCmd
00101             
00102             requestTime = time.time()
00103             event = wxCmdRun(cmd = args[0],
00104                              pid = requestId)
00105             wx.PostEvent(self.parent, event)
00106             
00107             time.sleep(.1)
00108             self.requestCmd = vars()['callable'](*args, **kwds)
00109             if self._want_abort_all:
00110                 self.requestCmd.abort()
00111                 if self.requestQ.empty():
00112                     self._want_abort_all = False
00113             
00114             self.resultQ.put((requestId, self.requestCmd.run()))
00115             
00116             try:
00117                 returncode = self.requestCmd.module.returncode
00118             except AttributeError:
00119                 returncode = 0 # being optimistic
00120             
00121             try:
00122                 aborted = self.requestCmd.aborted
00123             except AttributeError:
00124                 aborted = False
00125             
00126             time.sleep(.1)
00127 
00128             # set default color table for raster data
00129             if UserSettings.Get(group='cmd', key='rasterColorTable', subkey='enabled') and \
00130                     args[0][0][:2] == 'r.':
00131                 colorTable = UserSettings.Get(group='cmd', key='rasterColorTable', subkey='selection')
00132                 mapName = None
00133                 if args[0][0] == 'r.mapcalc':
00134                     try:
00135                         mapName = args[0][1].split('=', 1)[0].strip()
00136                     except KeyError:
00137                         pass
00138                 else:
00139                     moduleInterface = menuform.GUI(show = None).ParseCommand(args[0])
00140                     outputParam = moduleInterface.get_param(value = 'output', raiseError = False)
00141                     if outputParam and outputParam['prompt'] == 'raster':
00142                         mapName = outputParam['value']
00143                 
00144                 if mapName:
00145                     argsColor = list(args)
00146                     argsColor[0] = [ 'r.colors',
00147                                      'map=%s' % mapName,
00148                                      'color=%s' % colorTable ]
00149                     self.requestCmdColor = vars()['callable'](*argsColor, **kwds)
00150                     self.resultQ.put((requestId, self.requestCmdColor.run()))
00151             
00152             event = wxCmdDone(cmd = args[0],
00153                               aborted = aborted,
00154                               returncode = returncode,
00155                               time = requestTime,
00156                               pid = requestId,
00157                               onDone = vars()['onDone'],
00158                               userData = vars()['userData'])
00159             
00160             # send event
00161             wx.PostEvent(self.parent, event)
00162                 
00163     def abort(self, abortall = True):
00164         """!Abort command(s)"""
00165         if abortall:
00166             self._want_abort_all = True
00167         self.requestCmd.abort()
00168         if self.requestQ.empty():
00169             self._want_abort_all = False
00170         
00171 class GMConsole(wx.SplitterWindow):
00172     """!Create and manage output console for commands run by GUI.
00173     """
00174     def __init__(self, parent, id = wx.ID_ANY, margin = False,
00175                  notebook = None,
00176                  style = wx.TAB_TRAVERSAL | wx.FULL_REPAINT_ON_RESIZE,
00177                  **kwargs):
00178         wx.SplitterWindow.__init__(self, parent, id, style = style, *kwargs)
00179         self.SetName("GMConsole")
00180         
00181         self.panelOutput = wx.Panel(parent = self, id = wx.ID_ANY)
00182         self.panelPrompt = wx.Panel(parent = self, id = wx.ID_ANY)
00183         
00184         # initialize variables
00185         self.parent          = parent # GMFrame | CmdPanel | ?
00186         if notebook:
00187             self._notebook = notebook
00188         else:
00189             self._notebook = self.parent.notebook
00190         self.lineWidth       = 80
00191         
00192         # remember position of line begining (used for '\r')
00193         self.linePos         = -1
00194         
00195         #
00196         # create queues
00197         #
00198         self.requestQ = Queue.Queue()
00199         self.resultQ = Queue.Queue()
00200         
00201         #
00202         # progress bar
00203         #
00204         self.console_progressbar = wx.Gauge(parent=self.panelOutput, id=wx.ID_ANY,
00205                                             range=100, pos=(110, 50), size=(-1, 25),
00206                                             style=wx.GA_HORIZONTAL)
00207         self.console_progressbar.Bind(EVT_CMD_PROGRESS, self.OnCmdProgress)
00208         
00209         #
00210         # text control for command output
00211         #
00212         self.cmd_output = GMStc(parent=self.panelOutput, id=wx.ID_ANY, margin=margin,
00213                                 wrap=None) 
00214         self.cmd_output_timer = wx.Timer(self.cmd_output, id=wx.ID_ANY)
00215         self.cmd_output.Bind(EVT_CMD_OUTPUT, self.OnCmdOutput)
00216         self.cmd_output.Bind(wx.EVT_TIMER, self.OnProcessPendingOutputWindowEvents)
00217         self.Bind(EVT_CMD_RUN, self.OnCmdRun)
00218         self.Bind(EVT_CMD_DONE, self.OnCmdDone)
00219         
00220         # search & command prompt
00221         self.cmd_prompt = prompt.GPromptSTC(parent = self)
00222         
00223         if self.parent.GetName() != 'LayerManager':
00224             self.search = None
00225             self.cmd_prompt.Hide()
00226         else:
00227             self.infoCollapseLabelExp = _("Click here to show search module engine")
00228             self.infoCollapseLabelCol = _("Click here to hide search module engine")
00229             self.searchPane = wx.CollapsiblePane(parent = self.panelOutput,
00230                                                  label = self.infoCollapseLabelExp,
00231                                                  style = wx.CP_DEFAULT_STYLE |
00232                                                  wx.CP_NO_TLW_RESIZE | wx.EXPAND)
00233             self.MakeSearchPaneContent(self.searchPane.GetPane())
00234             self.searchPane.Collapse(True)
00235             self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.OnSearchPaneChanged, self.searchPane) 
00236             self.search.Bind(wx.EVT_TEXT,             self.OnUpdateStatusBar)
00237         
00238         #
00239         # stream redirection
00240         #
00241         self.cmd_stdout = GMStdout(self)
00242         self.cmd_stderr = GMStderr(self)
00243         
00244         #
00245         # thread
00246         #
00247         self.cmdThread = CmdThread(self, self.requestQ, self.resultQ)
00248         
00249         #
00250         # buttons
00251         #
00252         self.btn_console_clear = wx.Button(parent = self.panelPrompt, id = wx.ID_ANY,
00253                                            label = _("&Clear output"), size=(100,-1))
00254         self.btn_cmd_clear = wx.Button(parent = self.panelPrompt, id = wx.ID_ANY,
00255                                        label = _("C&lear cmd"), size=(100,-1))
00256         if self.parent.GetName() != 'LayerManager':
00257             self.btn_cmd_clear.Hide()
00258         self.btn_console_save  = wx.Button(parent = self.panelPrompt, id = wx.ID_ANY,
00259                                            label = _("&Save output"), size=(100,-1))
00260         # abort
00261         self.btn_abort = wx.Button(parent = self.panelPrompt, id = wx.ID_ANY, label = _("&Abort cmd"),
00262                                    size=(100,-1))
00263         self.btn_abort.SetToolTipString(_("Abort the running command"))
00264         self.btn_abort.Enable(False)
00265         
00266         self.btn_cmd_clear.Bind(wx.EVT_BUTTON,     self.cmd_prompt.OnCmdErase)
00267         self.btn_console_clear.Bind(wx.EVT_BUTTON, self.ClearHistory)
00268         self.btn_console_save.Bind(wx.EVT_BUTTON,  self.SaveHistory)
00269         self.btn_abort.Bind(wx.EVT_BUTTON,         self.OnCmdAbort)
00270         self.btn_abort.Bind(EVT_CMD_ABORT,         self.OnCmdAbort)
00271         
00272         self.__layout()
00273         
00274     def __layout(self):
00275         """!Do layout"""
00276         OutputSizer = wx.BoxSizer(wx.VERTICAL)
00277         PromptSizer = wx.BoxSizer(wx.VERTICAL)
00278         ButtonSizer = wx.BoxSizer(wx.HORIZONTAL)
00279 
00280         if self.search and self.search.IsShown():
00281             OutputSizer.Add(item=self.searchPane, proportion=0,
00282                             flag=wx.EXPAND | wx.ALL, border=3)
00283         OutputSizer.Add(item=self.cmd_output, proportion=1,
00284                         flag=wx.EXPAND | wx.ALL, border=3)
00285         OutputSizer.Add(item=self.console_progressbar, proportion=0,
00286                         flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=3)
00287         
00288         PromptSizer.Add(item=self.cmd_prompt, proportion=1,
00289                         flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border=3)
00290         
00291         ButtonSizer.Add(item=self.btn_console_clear, proportion=0,
00292                         flag=wx.ALIGN_CENTER | wx.FIXED_MINSIZE | wx.ALL, border=5)
00293         ButtonSizer.Add(item=self.btn_console_save, proportion=0,
00294                         flag=wx.ALIGN_CENTER | wx.FIXED_MINSIZE | wx.ALL, border=5)
00295         ButtonSizer.Add(item=self.btn_cmd_clear, proportion=0,
00296                         flag=wx.ALIGN_CENTER | wx.FIXED_MINSIZE | wx.ALL, border=5)
00297         ButtonSizer.Add(item=self.btn_abort, proportion=0,
00298                         flag=wx.ALIGN_CENTER | wx.FIXED_MINSIZE | wx.ALL, border=5)
00299         PromptSizer.Add(item=ButtonSizer, proportion=0,
00300                         flag=wx.ALIGN_CENTER)
00301         
00302         OutputSizer.Fit(self)
00303         OutputSizer.SetSizeHints(self)
00304         
00305         PromptSizer.Fit(self)
00306         PromptSizer.SetSizeHints(self)
00307         
00308         self.panelOutput.SetSizer(OutputSizer)
00309         self.panelPrompt.SetSizer(PromptSizer)
00310         
00311         # split window
00312         if self.parent.GetName() == 'LayerManager':
00313             self.SplitHorizontally(self.panelOutput, self.panelPrompt, -50)
00314             self.SetMinimumPaneSize(self.btn_cmd_clear.GetSize()[1] + 50)
00315         else:
00316             self.SplitHorizontally(self.panelOutput, self.panelPrompt, -45)
00317             self.SetMinimumPaneSize(self.btn_cmd_clear.GetSize()[1] + 10)
00318         
00319         self.SetSashGravity(1.0)
00320         
00321         # layout
00322         self.SetAutoLayout(True)
00323         self.Layout()
00324 
00325     def MakeSearchPaneContent(self, pane):
00326         """!Create search pane"""
00327         border = wx.BoxSizer(wx.VERTICAL)
00328         
00329         self.search = SearchModuleWindow(parent = pane, cmdPrompt = self.cmd_prompt)
00330         
00331         border.Add(item = self.search, proportion = 0,
00332                    flag = wx.EXPAND | wx.ALL, border = 1)
00333         
00334         pane.SetSizer(border)
00335         border.Fit(pane)
00336         
00337     def OnSearchPaneChanged(self, event):
00338         """!Collapse search module box"""
00339         if self.searchPane.IsExpanded():
00340             self.searchPane.SetLabel(self.infoCollapseLabelCol)
00341         else:
00342             self.searchPane.SetLabel(self.infoCollapseLabelExp)
00343         
00344         self.panelOutput.Layout()
00345         self.panelOutput.SendSizeEvent()
00346         
00347     def GetPanel(self, prompt = True):
00348         """!Get panel
00349 
00350         @param prompt get prompt / output panel
00351 
00352         @return wx.Panel reference
00353         """
00354         if prompt:
00355             return self.panelPrompt
00356 
00357         return self.panelOutput
00358     
00359     def Redirect(self):
00360         """!Redirect stdout/stderr
00361         """
00362         if Debug.GetLevel() == 0 and int(grass.gisenv().get('DEBUG', 0)) == 0:
00363             # don't redirect when debugging is enabled
00364             sys.stdout = self.cmd_stdout
00365             sys.stderr = self.cmd_stderr
00366         else:
00367             enc = locale.getdefaultlocale()[1]
00368             if enc:
00369                 sys.stdout = codecs.getwriter(enc)(sys.__stdout__)
00370                 sys.stderr = codecs.getwriter(enc)(sys.__stderr__)
00371             else:
00372                 sys.stdout = sys.__stdout__
00373                 sys.stderr = sys.__stderr__
00374         
00375     def WriteLog(self, text, style = None, wrap = None,
00376                  switchPage = False):
00377         """!Generic method for writing log message in 
00378         given style
00379 
00380         @param line text line
00381         @param style text style (see GMStc)
00382         @param stdout write to stdout or stderr
00383         """
00384 
00385         self.cmd_output.SetStyle()
00386 
00387         if switchPage:
00388             self._notebook.SetSelectionByName('output')
00389         
00390         if not style:
00391             style = self.cmd_output.StyleDefault
00392         
00393         # p1 = self.cmd_output.GetCurrentPos()
00394         p1 = self.cmd_output.GetEndStyled()
00395 #        self.cmd_output.GotoPos(p1)
00396         self.cmd_output.DocumentEnd()
00397         
00398         for line in text.splitlines():
00399             # fill space
00400             if len(line) < self.lineWidth:
00401                 diff = self.lineWidth - len(line) 
00402                 line += diff * ' '
00403             
00404             self.cmd_output.AddTextWrapped(line, wrap=wrap) # adds '\n'
00405             
00406             p2 = self.cmd_output.GetCurrentPos()
00407             
00408             self.cmd_output.StartStyling(p1, 0xff)
00409             self.cmd_output.SetStyling(p2 - p1, style)
00410         
00411         self.cmd_output.EnsureCaretVisible()
00412         
00413     def WriteCmdLog(self, line, pid = None, switchPage = True):
00414         """!Write message in selected style"""
00415         if pid:
00416             line = '(' + str(pid) + ') ' + line
00417         self.WriteLog(line, style=self.cmd_output.StyleCommand, switchPage = switchPage)
00418 
00419     def WriteWarning(self, line):
00420         """!Write message in warning style"""
00421         self.WriteLog(line, style=self.cmd_output.StyleWarning, switchPage = True)
00422 
00423     def WriteError(self, line):
00424         """!Write message in error style"""
00425         self.WriteLog(line, style = self.cmd_output.StyleError, switchPage = True)
00426 
00427     def RunCmd(self, command, compReg = True, switchPage = False,
00428                onDone = None):
00429         """!Run command typed into console command prompt (GPrompt).
00430         
00431         @todo Display commands (*.d) are captured and processed
00432         separately by mapdisp.py. Display commands are rendered in map
00433         display widget that currently has the focus (as indicted by
00434         mdidx).
00435         
00436         @param command command given as a list (produced e.g. by utils.split())
00437         @param compReg True use computation region
00438         @param switchPage switch to output page
00439         @param onDone function to be called when command is finished
00440         """
00441         if len(command) == 0:
00442             Debug.msg(2, "GPrompt:RunCmd(): empty command")
00443             return
00444         
00445         # update history file
00446         env = grass.gisenv()
00447         try:
00448             fileHistory = codecs.open(os.path.join(env['GISDBASE'],
00449                                                    env['LOCATION_NAME'],
00450                                                    env['MAPSET'],
00451                                                    '.bash_history'),
00452                                       encoding = 'utf-8', mode = 'a')
00453         except IOError, e:
00454             self.WriteError(e)
00455             fileHistory = None
00456         
00457         if fileHistory:
00458             try:
00459                 fileHistory.write(' '.join(command) + os.linesep)
00460             finally:
00461                 fileHistory.close()
00462         
00463         # update history items
00464         if self.parent.GetName() == 'LayerManager':
00465             try:
00466                 self.parent.cmdinput.SetHistoryItems()
00467             except AttributeError:
00468                 pass
00469         
00470         if command[0] in globalvar.grassCmd['all']:
00471             # send GRASS command without arguments to GUI command interface
00472             # except display commands (they are handled differently)
00473             if self.parent.GetName() == "LayerManager" and \
00474                     command[0][0:2] == "d." and \
00475                     'help' not in ' '.join(command[1:]):
00476                 # display GRASS commands
00477                 try:
00478                     layertype = {'d.rast'         : 'raster',
00479                                  'd.rast3d'       : '3d-raster',
00480                                  'd.rgb'          : 'rgb',
00481                                  'd.his'          : 'his',
00482                                  'd.shaded'       : 'shaded',
00483                                  'd.legend'       : 'rastleg',
00484                                  'd.rast.arrow'   : 'rastarrow',
00485                                  'd.rast.num'     : 'rastnum',
00486                                  'd.vect'         : 'vector',
00487                                  'd.vect.thematic': 'thememap',
00488                                  'd.vect.chart'   : 'themechart',
00489                                  'd.grid'         : 'grid',
00490                                  'd.geodesic'     : 'geodesic',
00491                                  'd.rhumbline'    : 'rhumb',
00492                                  'd.labels'       : 'labels',
00493                                  'd.barscale'     : 'barscale',
00494                                  'd.redraw'       : 'redraw'}[command[0]]
00495                 except KeyError:
00496                     gcmd.GMessage(parent = self.parent,
00497                                   message = _("Command '%s' not yet implemented in the WxGUI. "
00498                                               "Try adding it as a command layer instead.") % command[0])
00499                     return None
00500                 
00501                 if layertype == 'barscale':
00502                     self.parent.curr_page.maptree.GetMapDisplay().OnAddBarscale(None)
00503                 elif layertype == 'rastleg':
00504                     self.parent.curr_page.maptree.GetMapDisplay().OnAddLegend(None)
00505                 elif layertype == 'redraw':
00506                     self.parent.curr_page.maptree.GetMapDisplay().OnRender(None)
00507                 else:
00508                     # add layer into layer tree
00509                     lname, found = utils.GetLayerNameFromCmd(command, fullyQualified = True,
00510                                                              layerType = layertype)
00511                     if self.parent.GetName() == "LayerManager":
00512                         self.parent.curr_page.maptree.AddLayer(ltype = layertype,
00513                                                                lname = lname,
00514                                                                lcmd = command)
00515             
00516             else:
00517                 # other GRASS commands (r|v|g|...)
00518                 if sys.platform == 'win32':
00519                     if command[0] in globalvar.grassCmd['script']:
00520                         command[0] += globalvar.EXT_SCT
00521                 hasParams = False
00522                 if command[0] != 'r.mapcalc':
00523                     task = menuform.GUI(show = None).ParseCommand(command)
00524                     if task:
00525                         options = task.get_options()
00526                         hasParams = options['params'] and options['flags']
00527                         # check for <input>=-
00528                         for p in options['params']:
00529                             if p.get('prompt', '') == 'input' and \
00530                                     p.get('element', '') == 'file' and \
00531                                     p.get('age', 'new') == 'old_file' and \
00532                                     p.get('value', '') == '-':
00533                                 gcmd.GError(parent = self,
00534                                             message = _("Unable to run command:\n%(cmd)s\n\n"
00535                                                         "Option <%(opt)s>: read from standard input is not "
00536                                                         "supported by wxGUI") % { 'cmd': ' '.join(command),
00537                                                                                   'opt': p.get('name', '') })
00538                                 return None
00539                 else:
00540                     task = None
00541                 
00542                 if len(command) == 1 and hasParams:
00543                     # no arguments given
00544                     try:
00545                         menuform.GUI(parent = self).ParseCommand(command)
00546                     except gcmd.GException, e:
00547                         print >> sys.stderr, e
00548                     return 0
00549                 
00550                 # switch to 'Command output' if required
00551                 if switchPage:
00552                     self._notebook.SetSelectionByName('output')
00553                     
00554                     self.parent.SetFocus()
00555                     self.parent.Raise()
00556                 
00557                 # activate computational region (set with g.region)
00558                 # for all non-display commands.
00559                 if compReg:
00560                     tmpreg = os.getenv("GRASS_REGION")
00561                     if "GRASS_REGION" in os.environ:
00562                         del os.environ["GRASS_REGION"]
00563                 
00564                 # process GRASS command with argument
00565                 self.cmdThread.RunCmd(command, stdout = self.cmd_stdout, stderr = self.cmd_stderr,
00566                                       onDone = onDone)
00567                 self.cmd_output_timer.Start(50)
00568                 
00569                 # deactivate computational region and return to display settings
00570                 if compReg and tmpreg:
00571                     os.environ["GRASS_REGION"] = tmpreg
00572         else:
00573             # Send any other command to the shell. Send output to
00574             # console output window
00575             if len(command) == 1:
00576                 try:
00577                     task = gtask.parse_interface(command[0])
00578                 except:
00579                     task = None
00580             else:
00581                 task = None
00582                 
00583             if task:
00584                 # process GRASS command without argument
00585                 menuform.GUI(parent = self).ParseCommand(command)
00586             else:
00587                 self.cmdThread.RunCmd(command, stdout = self.cmd_stdout, stderr = self.cmd_stderr,
00588                                       onDone = onDone)
00589             self.cmd_output_timer.Start(50)
00590         
00591         return None
00592 
00593     def ClearHistory(self, event):
00594         """!Clear history of commands"""
00595         self.cmd_output.SetReadOnly(False)
00596         self.cmd_output.ClearAll()
00597         self.cmd_output.SetReadOnly(True)
00598         self.console_progressbar.SetValue(0)
00599 
00600     def GetProgressBar(self):
00601         """!Return progress bar widget"""
00602         return self.console_progressbar
00603     
00604     def GetLog(self, err = False):
00605         """!Get widget used for logging
00606 
00607         @param err True to get stderr widget
00608         """
00609         if err:
00610             return self.cmd_stderr
00611         
00612         return self.cmd_stdout
00613     
00614     def SaveHistory(self, event):
00615         """!Save history of commands"""
00616         self.history = self.cmd_output.GetSelectedText()
00617         if self.history == '':
00618             self.history = self.cmd_output.GetText()
00619 
00620         # add newline if needed
00621         if len(self.history) > 0 and self.history[-1] != '\n':
00622             self.history += '\n'
00623 
00624         wildcard = "Text file (*.txt)|*.txt"
00625         dlg = wx.FileDialog(self, message = _("Save file as..."), defaultDir = os.getcwd(),
00626             defaultFile = "grass_cmd_history.txt", wildcard = wildcard,
00627             style = wx.SAVE | wx.FD_OVERWRITE_PROMPT)
00628 
00629         # Show the dialog and retrieve the user response. If it is the OK response,
00630         # process the data.
00631         if dlg.ShowModal() == wx.ID_OK:
00632             path = dlg.GetPath()
00633 
00634             output = open(path, "w")
00635             output.write(self.history)
00636             output.close()
00637 
00638         dlg.Destroy()
00639 
00640     def GetCmd(self):
00641         """!Get running command or None"""
00642         return self.requestQ.get()
00643     
00644     def SetCopyingOfSelectedText(self, copy):
00645         """!Enable or disable copying of selected text in to clipboard.
00646         Effects prompt and output.
00647         
00648         @param copy True for enable, False for disable
00649         """
00650         if copy:
00651             self.cmd_prompt.Bind(wx.stc.EVT_STC_PAINTED, self.cmd_prompt.OnTextSelectionChanged)
00652             self.cmd_output.Bind(wx.stc.EVT_STC_PAINTED, self.cmd_output.OnTextSelectionChanged)
00653         else:
00654             self.cmd_prompt.Unbind(wx.stc.EVT_STC_PAINTED)
00655             self.cmd_output.Unbind(wx.stc.EVT_STC_PAINTED)
00656         
00657     def OnUpdateStatusBar(self, event):
00658         """!Update statusbar text"""
00659         if event.GetString():
00660             nItems = len(self.cmd_prompt.GetCommandItems())
00661             self.parent.SetStatusText(_('%d modules match') % nItems, 0)
00662         else:
00663             self.parent.SetStatusText('', 0)
00664         
00665         event.Skip()
00666 
00667     def OnCmdOutput(self, event):
00668         """!Print command output"""
00669         message = event.text
00670         type  = event.type
00671         if self._notebook.GetSelection() != self._notebook.GetPageIndexByName('output'):
00672             page = self._notebook.GetPageIndexByName('output')
00673             textP = self._notebook.GetPageText(page)
00674             if textP[-1] != ')':
00675                 textP += ' (...)'
00676             self._notebook.SetPageText(page, textP)
00677         
00678         # message prefix
00679         if type == 'warning':
00680             messege = 'WARNING: ' + message
00681         elif type == 'error':
00682             message = 'ERROR: ' + message
00683         
00684         p1 = self.cmd_output.GetEndStyled()
00685         self.cmd_output.GotoPos(p1)
00686         
00687         if '\b' in message:
00688             if self.linepos < 0:
00689                 self.linepos = p1
00690             last_c = ''
00691             for c in message:
00692                 if c == '\b':
00693                    self.linepos -= 1
00694                 else:
00695                     if c == '\r':
00696                         pos = self.cmd_output.GetCurLine()[1]
00697                         # self.cmd_output.SetCurrentPos(pos)
00698                     else:
00699                         self.cmd_output.SetCurrentPos(self.linepos)
00700                     self.cmd_output.ReplaceSelection(c)
00701                     self.linepos = self.cmd_output.GetCurrentPos()
00702                     if c != ' ':
00703                         last_c = c
00704             if last_c not in ('0123456789'):
00705                 self.cmd_output.AddTextWrapped('\n', wrap=None)
00706                 self.linepos = -1
00707         else:
00708             self.linepos = -1 # don't force position
00709             if '\n' not in message:
00710                 self.cmd_output.AddTextWrapped(message, wrap=60)
00711             else:
00712                 self.cmd_output.AddTextWrapped(message, wrap=None)
00713 
00714         p2 = self.cmd_output.GetCurrentPos()
00715         
00716         if p2 >= p1:
00717             self.cmd_output.StartStyling(p1, 0xff)
00718         
00719             if type == 'error':
00720                 self.cmd_output.SetStyling(p2 - p1, self.cmd_output.StyleError)
00721             elif type == 'warning':
00722                 self.cmd_output.SetStyling(p2 - p1, self.cmd_output.StyleWarning)
00723             elif type == 'message':
00724                 self.cmd_output.SetStyling(p2 - p1, self.cmd_output.StyleMessage)
00725             else: # unknown
00726                 self.cmd_output.SetStyling(p2 - p1, self.cmd_output.StyleUnknown)
00727         
00728         self.cmd_output.EnsureCaretVisible()
00729         
00730     def OnCmdProgress(self, event):
00731         """!Update progress message info"""
00732         self.console_progressbar.SetValue(event.value)
00733 
00734     def OnCmdAbort(self, event):
00735         """!Abort running command"""
00736         self.cmdThread.abort()
00737         
00738     def OnCmdRun(self, event):
00739         """!Run command"""
00740         if self.parent.GetName() == 'Modeler':
00741             self.parent.OnCmdRun(event)
00742         
00743         self.WriteCmdLog('(%s)\n%s' % (str(time.ctime()), ' '.join(event.cmd)))
00744         self.btn_abort.Enable()
00745 
00746     def OnCmdDone(self, event):
00747         """!Command done (or aborted)"""
00748         if self.parent.GetName() == 'Modeler':
00749             self.parent.OnCmdDone(event)
00750         
00751         if event.aborted:
00752             # Thread aborted (using our convention of None return)
00753             self.WriteLog(_('Please note that the data are left in inconsistent state '
00754                             'and may be corrupted'), self.cmd_output.StyleWarning)
00755             self.WriteCmdLog('(%s) %s (%d sec)' % (str(time.ctime()),
00756                                                    _('Command aborted'),
00757                                                    (time.time() - event.time)))
00758             # pid=self.cmdThread.requestId)
00759             self.btn_abort.Enable(False)
00760         else:
00761             try:
00762                 # Process results here
00763                 self.WriteCmdLog('(%s) %s (%d sec)' % (str(time.ctime()),
00764                                                        _('Command finished'),
00765                                                        (time.time() - event.time)))
00766             except KeyError:
00767                 # stopped deamon
00768                 pass
00769 
00770             self.btn_abort.Enable(False)
00771         
00772         if event.onDone:
00773             event.onDone(cmd = event.cmd, returncode = event.returncode)
00774         
00775         self.console_progressbar.SetValue(0) # reset progress bar on '0%'
00776 
00777         self.cmd_output_timer.Stop()
00778 
00779         if event.cmd[0] == 'g.gisenv':
00780             Debug.SetLevel()
00781             self.Redirect()
00782         
00783         if self.parent.GetName() == "LayerManager":
00784             self.btn_abort.Enable(False)
00785             if event.cmd[0] not in globalvar.grassCmd['all'] or \
00786                     event.cmd[0] == 'r.mapcalc':
00787                 return
00788             
00789             display = self.parent.GetLayerTree().GetMapDisplay()
00790             if not display or not display.IsAutoRendered():
00791                 return
00792             mapLayers = map(lambda x: x.GetName(),
00793                             display.GetRender().GetListOfLayers(l_type = 'raster') +
00794                             display.GetRender().GetListOfLayers(l_type = 'vector'))
00795             
00796             try:
00797                 task = menuform.GUI(show = None).ParseCommand(event.cmd)
00798             except gcmd.GException, e:
00799                 print >> sys.stderr, e
00800                 task = None
00801                 return
00802             
00803             for p in task.get_options()['params']:
00804                 if p.get('prompt', '') not in ('raster', 'vector'):
00805                     continue
00806                 mapName = p.get('value', '')
00807                 if '@' not in mapName:
00808                     mapName = mapName + '@' + grass.gisenv()['MAPSET']
00809                 if mapName in mapLayers:
00810                     display.GetWindow().UpdateMap(render = True)
00811                     return
00812         elif self.parent.GetName() == 'Modeler':
00813             pass
00814         else: # standalone dialogs
00815             dialog = self.parent.parent
00816             if hasattr(self.parent.parent, "btn_abort"):
00817                 dialog.btn_abort.Enable(False)
00818             
00819             if hasattr(self.parent.parent, "btn_cancel"):
00820                 dialog.btn_cancel.Enable(True)
00821             
00822             if hasattr(self.parent.parent, "btn_clipboard"):
00823                 dialog.btn_clipboard.Enable(True)
00824             
00825             if hasattr(self.parent.parent, "btn_help"):
00826                 dialog.btn_help.Enable(True)
00827             
00828             if hasattr(self.parent.parent, "btn_run"):
00829                 dialog.btn_run.Enable(True)
00830             
00831             if event.returncode == 0 and not event.aborted:
00832                 try:
00833                     winName = self.parent.parent.parent.GetName()
00834                 except AttributeError:
00835                     winName = ''
00836                 
00837                 if winName == 'LayerManager':
00838                     mapTree = self.parent.parent.parent.GetLayerTree()
00839                 elif winName == 'LayerTree':
00840                     mapTree = self.parent.parent.parent
00841                 elif winName: # GMConsole
00842                     mapTree = self.parent.parent.parent.parent.GetLayerTree()
00843                 else:
00844                     mapTree = None
00845                 
00846                 cmd = dialog.notebookpanel.createCmd(ignoreErrors = True)
00847                 if hasattr(dialog, "addbox") and dialog.addbox.IsChecked():
00848                     # add created maps into layer tree
00849                     for p in dialog.task.get_options()['params']:
00850                         prompt = p.get('prompt', '')
00851                         if prompt in ('raster', 'vector', '3d-raster') and \
00852                                 p.get('age', 'old') == 'new' and \
00853                                 p.get('value', None):
00854                             name, found = utils.GetLayerNameFromCmd(cmd, fullyQualified = True,
00855                                                                     param = p.get('name', ''))
00856                             
00857                             if mapTree.GetMap().GetListOfLayers(l_name = name):
00858                                 continue
00859                             
00860                             if prompt == 'raster':
00861                                 lcmd = ['d.rast',
00862                                         'map=%s' % name]
00863                             else:
00864                                 lcmd = ['d.vect',
00865                                         'map=%s' % name]
00866                             mapTree.AddLayer(ltype = prompt,
00867                                              lcmd = lcmd,
00868                                              lname = name)
00869             
00870             if hasattr(dialog, "get_dcmd") and \
00871                     dialog.get_dcmd is None and \
00872                     hasattr(dialog, "closebox") and \
00873                     dialog.closebox.IsChecked() and \
00874                     (event.returncode == 0 or event.aborted):
00875                 self.cmd_output.Update()
00876                 time.sleep(2)
00877                 dialog.Close()
00878         
00879     def OnProcessPendingOutputWindowEvents(self, event):
00880         self.ProcessPendingEvents()
00881 
00882 class GMStdout:
00883     """!GMConsole standard output
00884 
00885     Based on FrameOutErr.py
00886 
00887     Name:      FrameOutErr.py
00888     Purpose:   Redirecting stdout / stderr
00889     Author:    Jean-Michel Fauth, Switzerland
00890     Copyright: (c) 2005-2007 Jean-Michel Fauth
00891     Licence:   GPL
00892     """
00893     def __init__(self, parent):
00894         self.parent = parent # GMConsole
00895 
00896     def write(self, s):
00897         if len(s) == 0 or s == '\n':
00898             return
00899 
00900         for line in s.splitlines():
00901             if len(line) == 0:
00902                 continue
00903             
00904             evt = wxCmdOutput(text=line + '\n',
00905                               type='')
00906             wx.PostEvent(self.parent.cmd_output, evt)
00907         
00908 class GMStderr:
00909     """!GMConsole standard error output
00910 
00911     Based on FrameOutErr.py
00912 
00913     Name:      FrameOutErr.py
00914     Purpose:   Redirecting stdout / stderr
00915     Author:    Jean-Michel Fauth, Switzerland
00916     Copyright: (c) 2005-2007 Jean-Michel Fauth
00917     Licence:   GPL
00918     """
00919     def __init__(self, parent):
00920         self.parent = parent # GMConsole
00921  
00922         self.type = ''
00923         self.message = ''
00924         self.printMessage = False
00925         
00926     def flush(self):
00927         pass
00928     
00929     def write(self, s):
00930         if "GtkPizza" in s:
00931             return
00932         
00933         # remove/replace escape sequences '\b' or '\r' from stream
00934         progressValue = -1
00935         
00936         for line in s.splitlines():
00937             if len(line) == 0:
00938                 continue
00939             
00940             if 'GRASS_INFO_PERCENT' in line:
00941                 value = int(line.rsplit(':', 1)[1].strip())
00942                 if value >= 0 and value < 100:
00943                     progressValue = value
00944                 else:
00945                     progressValue = 0
00946             elif 'GRASS_INFO_MESSAGE' in line:
00947                 self.type = 'message'
00948                 self.message += line.split(':', 1)[1].strip() + '\n'
00949             elif 'GRASS_INFO_WARNING' in line:
00950                 self.type = 'warning'
00951                 self.message += line.split(':', 1)[1].strip() + '\n'
00952             elif 'GRASS_INFO_ERROR' in line:
00953                 self.type = 'error'
00954                 self.message += line.split(':', 1)[1].strip() + '\n'
00955             elif 'GRASS_INFO_END' in line:
00956                 self.printMessage = True
00957             elif self.type == '':
00958                 if len(line) == 0:
00959                     continue
00960                 evt = wxCmdOutput(text=line,
00961                                   type='')
00962                 wx.PostEvent(self.parent.cmd_output, evt)
00963             elif len(line) > 0:
00964                 self.message += line.strip() + '\n'
00965 
00966             if self.printMessage and len(self.message) > 0:
00967                 evt = wxCmdOutput(text=self.message,
00968                                   type=self.type)
00969                 wx.PostEvent(self.parent.cmd_output, evt)
00970 
00971                 self.type = ''
00972                 self.message = ''
00973                 self.printMessage = False
00974 
00975         # update progress message
00976         if progressValue > -1:
00977             # self.gmgauge.SetValue(progressValue)
00978             evt = wxCmdProgress(value=progressValue)
00979             wx.PostEvent(self.parent.console_progressbar, evt)
00980             
00981 class GMStc(wx.stc.StyledTextCtrl):
00982     """!Styled GMConsole
00983 
00984     Based on FrameOutErr.py
00985 
00986     Name:      FrameOutErr.py
00987     Purpose:   Redirecting stdout / stderr
00988     Author:    Jean-Michel Fauth, Switzerland
00989     Copyright: (c) 2005-2007 Jean-Michel Fauth
00990     Licence:   GPL
00991     """    
00992     def __init__(self, parent, id, margin=False, wrap=None):
00993         wx.stc.StyledTextCtrl.__init__(self, parent, id)
00994         self.parent = parent
00995         self.SetUndoCollection(True)
00996         self.SetReadOnly(True)
00997 
00998         #
00999         # styles
01000         #                
01001         self.SetStyle()
01002         
01003         #
01004         # line margins
01005         #
01006         # TODO print number only from cmdlog
01007         self.SetMarginWidth(1, 0)
01008         self.SetMarginWidth(2, 0)
01009         if margin:
01010             self.SetMarginType(0, wx.stc.STC_MARGIN_NUMBER)
01011             self.SetMarginWidth(0, 30)
01012         else:
01013             self.SetMarginWidth(0, 0)
01014 
01015         #
01016         # miscellaneous
01017         #
01018         self.SetViewWhiteSpace(False)
01019         self.SetTabWidth(4)
01020         self.SetUseTabs(False)
01021         self.UsePopUp(True)
01022         self.SetSelBackground(True, "#FFFF00")
01023         self.SetUseHorizontalScrollBar(True)
01024 
01025         #
01026         # bindings
01027         #
01028         self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
01029         
01030     def OnTextSelectionChanged(self, event):
01031         """!Copy selected text to clipboard and skip event.
01032         The same function is in TextCtrlAutoComplete class (prompt.py).
01033         """
01034         self.Copy()
01035         event.Skip()
01036         
01037     def SetStyle(self):
01038         """!Set styles for styled text output windows with type face 
01039         and point size selected by user (Courier New 10 is default)"""
01040 
01041         settings = preferences.Settings()
01042         
01043         typeface = settings.Get(group = 'appearance', key = 'outputfont', subkey = 'type')   
01044         if typeface == "":
01045             typeface = "Courier New"
01046         
01047         typesize = settings.Get(group = 'appearance', key = 'outputfont', subkey = 'size')
01048         if typesize == None or typesize <= 0:
01049             typesize = 10
01050         typesize = float(typesize)
01051         
01052         self.StyleDefault     = 0
01053         self.StyleDefaultSpec = "face:%s,size:%d,fore:#000000,back:#FFFFFF" % (typeface, typesize)
01054         self.StyleCommand     = 1
01055         self.StyleCommandSpec = "face:%s,size:%d,,fore:#000000,back:#bcbcbc" % (typeface, typesize)
01056         self.StyleOutput      = 2
01057         self.StyleOutputSpec  = "face:%s,size:%d,,fore:#000000,back:#FFFFFF" % (typeface, typesize)
01058         # fatal error
01059         self.StyleError       = 3
01060         self.StyleErrorSpec   = "face:%s,size:%d,,fore:#7F0000,back:#FFFFFF" % (typeface, typesize)
01061         # warning
01062         self.StyleWarning     = 4
01063         self.StyleWarningSpec = "face:%s,size:%d,,fore:#0000FF,back:#FFFFFF" % (typeface, typesize)
01064         # message
01065         self.StyleMessage     = 5
01066         self.StyleMessageSpec = "face:%s,size:%d,,fore:#000000,back:#FFFFFF" % (typeface, typesize)
01067         # unknown
01068         self.StyleUnknown     = 6
01069         self.StyleUnknownSpec = "face:%s,size:%d,,fore:#000000,back:#FFFFFF" % (typeface, typesize)
01070         
01071         # default and clear => init
01072         self.StyleSetSpec(wx.stc.STC_STYLE_DEFAULT, self.StyleDefaultSpec)
01073         self.StyleClearAll()
01074         self.StyleSetSpec(self.StyleCommand, self.StyleCommandSpec)
01075         self.StyleSetSpec(self.StyleOutput,  self.StyleOutputSpec)
01076         self.StyleSetSpec(self.StyleError,   self.StyleErrorSpec)
01077         self.StyleSetSpec(self.StyleWarning, self.StyleWarningSpec)
01078         self.StyleSetSpec(self.StyleMessage, self.StyleMessageSpec)
01079         self.StyleSetSpec(self.StyleUnknown, self.StyleUnknownSpec)        
01080 
01081     def OnDestroy(self, evt):
01082         """!The clipboard contents can be preserved after
01083         the app has exited"""
01084         
01085         wx.TheClipboard.Flush()
01086         evt.Skip()
01087 
01088     def AddTextWrapped(self, txt, wrap=None):
01089         """!Add string to text area.
01090 
01091         String is wrapped and linesep is also added to the end
01092         of the string"""
01093         # allow writing to output window
01094         self.SetReadOnly(False)
01095         
01096         if wrap:
01097             txt = textwrap.fill(txt, wrap) + '\n'
01098         else:
01099             if txt[-1] != '\n':
01100                 txt += '\n'
01101         
01102         if '\r' in txt:
01103             self.parent.linePos = -1
01104             for seg in txt.split('\r'):
01105                 if self.parent.linePos > -1:
01106                     self.SetCurrentPos(self.parent.linePos)
01107                     self.ReplaceSelection(seg)
01108                 else:
01109                     self.parent.linePos = self.GetCurrentPos()
01110                     self.AddText(seg)
01111         else:
01112             self.parent.linePos = self.GetCurrentPos()
01113             try:
01114                 self.AddText(txt)
01115             except UnicodeDecodeError:
01116                 enc = UserSettings.Get(group='atm', key='encoding', subkey='value')
01117                 if enc:
01118                     txt = unicode(txt, enc)
01119                 elif 'GRASS_DB_ENCODING' in os.environ:
01120                     txt = unicode(txt, os.environ['GRASS_DB_ENCODING'])
01121                 else:
01122                     txt = utils.EncodeString(txt)
01123                 
01124                 self.AddText(txt)
01125         
01126         # reset output window to read only
01127         self.SetReadOnly(True)
01128     
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Defines