GRASS Programmer's Manual  6.5.svn(2012)-r51648
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Defines
goutput.py
Go to the documentation of this file.
00001 """!
00002 @package gui_core.goutput
00003 
00004 @brief Command output widgets
00005 
00006 Classes:
00007  - goutput::CmdThread
00008  - goutput::GMConsole
00009  - goutput::GMStdout
00010  - goutput::GMStderr
00011  - goutput::GMStc
00012  - goutput::PyStc
00013 
00014 (C) 2007-2012 by the GRASS Development Team
00015 
00016 This program is free software under the GNU General Public License
00017 (>=v2). Read the file COPYING that comes with GRASS for details.
00018 
00019 @author Michael Barton (Arizona State University)
00020 @author Martin Landa <landa.martin gmail.com>
00021 @author Vaclav Petras <wenzeslaus gmail.com> (copy&paste customization)
00022 """
00023 
00024 import os
00025 import sys
00026 import textwrap
00027 import time
00028 import threading
00029 import Queue
00030 import codecs
00031 import locale
00032 import keyword
00033 
00034 import wx
00035 from   wx import stc
00036 from wx.lib.newevent import NewEvent
00037 
00038 import grass.script as grass
00039 from   grass.script import task as gtask
00040 
00041 from core            import globalvar
00042 from core            import utils
00043 from core.gcmd       import CommandThread, GMessage, GError, GException, EncodeString
00044 from gui_core.forms  import GUI
00045 from gui_core.prompt import GPromptSTC
00046 from core.debug      import Debug
00047 from core.settings   import UserSettings, Settings
00048 from gui_core.ghelp  import SearchModuleWindow
00049 
00050 wxCmdOutput,   EVT_CMD_OUTPUT   = NewEvent()
00051 wxCmdProgress, EVT_CMD_PROGRESS = NewEvent()
00052 wxCmdRun,      EVT_CMD_RUN      = NewEvent()
00053 wxCmdDone,     EVT_CMD_DONE     = NewEvent()
00054 wxCmdAbort,    EVT_CMD_ABORT    = NewEvent()
00055 wxCmdPrepare,  EVT_CMD_PREPARE  = NewEvent()
00056 
00057 def GrassCmd(cmd, env = None, stdout = None, stderr = None):
00058     """!Return GRASS command thread"""
00059     return CommandThread(cmd, env = env,
00060                          stdout = stdout, stderr = stderr)
00061 
00062 class CmdThread(threading.Thread):
00063     """!Thread for GRASS commands"""
00064     requestId = 0
00065     def __init__(self, parent, requestQ, resultQ, **kwds):
00066         threading.Thread.__init__(self, **kwds)
00067 
00068         self.setDaemon(True)
00069 
00070         self.parent = parent # GMConsole
00071         self._want_abort_all = False
00072         
00073         self.requestQ = requestQ
00074         self.resultQ = resultQ
00075 
00076         self.start()
00077 
00078     def RunCmd(self, *args, **kwds):
00079         CmdThread.requestId += 1
00080 
00081         self.requestCmd = None
00082         self.requestQ.put((CmdThread.requestId, args, kwds))
00083         
00084         return CmdThread.requestId
00085 
00086     def SetId(self, id):
00087         """!Set starting id"""
00088         CmdThread.requestId = id
00089         
00090     def run(self):
00091         os.environ['GRASS_MESSAGE_FORMAT'] = 'gui'
00092         while True:
00093             requestId, args, kwds = self.requestQ.get()
00094             for key in ('callable', 'onDone', 'onPrepare', 'userData'):
00095                 if key in kwds:
00096                     vars()[key] = kwds[key]
00097                     del kwds[key]
00098                 else:
00099                     vars()[key] = None
00100             
00101             if not vars()['callable']:
00102                 vars()['callable'] = GrassCmd
00103             
00104             requestTime = time.time()
00105             
00106             # prepare
00107             event = wxCmdPrepare(cmd = args[0],
00108                                  time = requestTime,
00109                                  pid = requestId,
00110                                  onPrepare = vars()['onPrepare'],
00111                                  userData = vars()['userData'])
00112             wx.PostEvent(self.parent, event)
00113             
00114             # run command
00115             event = wxCmdRun(cmd = args[0],
00116                              pid = requestId)
00117             wx.PostEvent(self.parent, event)
00118             
00119             time.sleep(.1)
00120             self.requestCmd = vars()['callable'](*args, **kwds)
00121             if self._want_abort_all:
00122                 self.requestCmd.abort()
00123                 if self.requestQ.empty():
00124                     self._want_abort_all = False
00125             
00126             self.resultQ.put((requestId, self.requestCmd.run()))
00127             
00128             try:
00129                 returncode = self.requestCmd.module.returncode
00130             except AttributeError:
00131                 returncode = 0 # being optimistic
00132             
00133             try:
00134                 aborted = self.requestCmd.aborted
00135             except AttributeError:
00136                 aborted = False
00137             
00138             time.sleep(.1)
00139 
00140             # set default color table for raster data
00141             if UserSettings.Get(group = 'cmd', key = 'rasterColorTable', subkey = 'enabled') and \
00142                     args[0][0][:2] == 'r.':
00143                 colorTable = UserSettings.Get(group = 'cmd', key = 'rasterColorTable', subkey = 'selection')
00144                 mapName = None
00145                 if args[0][0] == 'r.mapcalc':
00146                     try:
00147                         mapName = args[0][1].split('=', 1)[0].strip()
00148                     except KeyError:
00149                         pass
00150                 else:
00151                     moduleInterface = GUI(show = None).ParseCommand(args[0])
00152                     outputParam = moduleInterface.get_param(value = 'output', raiseError = False)
00153                     if outputParam and outputParam['prompt'] == 'raster':
00154                         mapName = outputParam['value']
00155                 
00156                 if mapName:
00157                     argsColor = list(args)
00158                     argsColor[0] = [ 'r.colors',
00159                                      'map=%s' % mapName,
00160                                      'color=%s' % colorTable ]
00161                     self.requestCmdColor = vars()['callable'](*argsColor, **kwds)
00162                     self.resultQ.put((requestId, self.requestCmdColor.run()))
00163             
00164             event = wxCmdDone(cmd = args[0],
00165                               aborted = aborted,
00166                               returncode = returncode,
00167                               time = requestTime,
00168                               pid = requestId,
00169                               onDone = vars()['onDone'],
00170                               userData = vars()['userData'])
00171             
00172             # send event
00173             wx.PostEvent(self.parent, event)
00174             
00175     def abort(self, abortall = True):
00176         """!Abort command(s)"""
00177         if abortall:
00178             self._want_abort_all = True
00179         self.requestCmd.abort()
00180         if self.requestQ.empty():
00181             self._want_abort_all = False
00182         
00183 class GMConsole(wx.SplitterWindow):
00184     """!Create and manage output console for commands run by GUI.
00185     """
00186     def __init__(self, parent, id = wx.ID_ANY, margin = False,
00187                  notebook = None,
00188                  style = wx.TAB_TRAVERSAL | wx.FULL_REPAINT_ON_RESIZE,
00189                  **kwargs):
00190         wx.SplitterWindow.__init__(self, parent, id, style = style, *kwargs)
00191         self.SetName("GMConsole")
00192         
00193         self.panelOutput = wx.Panel(parent = self, id = wx.ID_ANY)
00194         self.panelPrompt = wx.Panel(parent = self, id = wx.ID_ANY)
00195         
00196         # initialize variables
00197         self.parent          = parent # GMFrame | CmdPanel | ?
00198         if notebook:
00199             self._notebook = notebook
00200         else:
00201             self._notebook = self.parent.notebook
00202         self.lineWidth       = 80
00203         
00204         # remember position of line begining (used for '\r')
00205         self.linePos         = -1
00206         
00207         # create queues
00208         self.requestQ = Queue.Queue()
00209         self.resultQ = Queue.Queue()
00210         
00211         # progress bar
00212         self.progressbar = wx.Gauge(parent = self.panelOutput, id = wx.ID_ANY,
00213                                     range = 100, pos = (110, 50), size = (-1, 25),
00214                                     style = wx.GA_HORIZONTAL)
00215         self.progressbar.Bind(EVT_CMD_PROGRESS, self.OnCmdProgress)
00216         
00217         # text control for command output
00218         self.cmdOutput = GMStc(parent = self.panelOutput, id = wx.ID_ANY, margin = margin,
00219                                wrap = None) 
00220         self.cmdOutputTimer = wx.Timer(self.cmdOutput, id = wx.ID_ANY)
00221         self.cmdOutput.Bind(EVT_CMD_OUTPUT, self.OnCmdOutput)
00222         self.cmdOutput.Bind(wx.EVT_TIMER, self.OnProcessPendingOutputWindowEvents)
00223         self.Bind(EVT_CMD_RUN,     self.OnCmdRun)
00224         self.Bind(EVT_CMD_DONE,    self.OnCmdDone)
00225         self.Bind(EVT_CMD_PREPARE, self.OnCmdPrepare)
00226         
00227         # search & command prompt
00228         self.cmdPrompt = GPromptSTC(parent = self)
00229         
00230         if self.parent.GetName() != 'LayerManager':
00231             self.search = None
00232             self.cmdPrompt.Hide()
00233         else:
00234             self.infoCollapseLabelExp = _("Click here to show search module engine")
00235             self.infoCollapseLabelCol = _("Click here to hide search module engine")
00236             self.searchPane = wx.CollapsiblePane(parent = self.panelOutput,
00237                                                  label = self.infoCollapseLabelExp,
00238                                                  style = wx.CP_DEFAULT_STYLE |
00239                                                  wx.CP_NO_TLW_RESIZE | wx.EXPAND)
00240             self.MakeSearchPaneContent(self.searchPane.GetPane())
00241             self.searchPane.Collapse(True)
00242             self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.OnSearchPaneChanged, self.searchPane) 
00243             self.search.Bind(wx.EVT_TEXT,             self.OnUpdateStatusBar)
00244         
00245         # stream redirection
00246         self.cmdStdOut = GMStdout(self)
00247         self.cmdStdErr = GMStderr(self)
00248         
00249         # thread
00250         self.cmdThread = CmdThread(self, self.requestQ, self.resultQ)
00251         
00252         self.outputBox = wx.StaticBox(parent = self.panelOutput, id = wx.ID_ANY,
00253                                       label = " %s " % _("Output window"))
00254         self.cmdBox = wx.StaticBox(parent = self.panelOutput, id = wx.ID_ANY,
00255                                    label = " %s " % _("Command prompt"))
00256         
00257         # buttons
00258         self.btnOutputClear = wx.Button(parent = self.panelOutput, id = wx.ID_CLEAR)
00259         self.btnOutputClear.SetToolTipString(_("Clear output window content"))
00260         self.btnCmdClear = wx.Button(parent = self.panelOutput, id = wx.ID_CLEAR)
00261         self.btnCmdClear.SetToolTipString(_("Clear command prompt content"))
00262         if self.parent.GetName() != 'LayerManager':
00263             self.btnCmdClear.Hide()
00264         self.btnOutputSave  = wx.Button(parent = self.panelOutput, id = wx.ID_SAVE)
00265         self.btnOutputSave.SetToolTipString(_("Save output window content to the file"))
00266         # abort
00267         self.btnCmdAbort = wx.Button(parent = self.panelOutput, id = wx.ID_STOP)
00268         self.btnCmdAbort.SetToolTipString(_("Abort running command"))
00269         self.btnCmdAbort.Enable(False)
00270         
00271         self.btnCmdClear.Bind(wx.EVT_BUTTON,     self.cmdPrompt.OnCmdErase)
00272         self.btnOutputClear.Bind(wx.EVT_BUTTON,  self.ClearHistory)
00273         self.btnOutputSave.Bind(wx.EVT_BUTTON,   self.SaveHistory)
00274         self.btnCmdAbort.Bind(wx.EVT_BUTTON,     self.OnCmdAbort)
00275         self.btnCmdAbort.Bind(EVT_CMD_ABORT,     self.OnCmdAbort)
00276         
00277         self._layout()
00278         
00279     def _layout(self):
00280         """!Do layout"""
00281         outputSizer = wx.BoxSizer(wx.VERTICAL)
00282         btnSizer = wx.BoxSizer(wx.HORIZONTAL)
00283         outBtnSizer = wx.StaticBoxSizer(self.outputBox, wx.HORIZONTAL)
00284         cmdBtnSizer = wx.StaticBoxSizer(self.cmdBox, wx.HORIZONTAL)
00285         
00286         if self.cmdPrompt.IsShown():
00287             promptSizer = wx.BoxSizer(wx.VERTICAL)
00288             promptSizer.Add(item = self.cmdPrompt, proportion = 1,
00289                         flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border = 3)
00290         
00291         if self.search and self.search.IsShown():
00292             outputSizer.Add(item = self.searchPane, proportion = 0,
00293                             flag = wx.EXPAND | wx.ALL, border = 3)
00294         outputSizer.Add(item = self.cmdOutput, proportion = 1,
00295                         flag = wx.EXPAND | wx.ALL, border = 3)
00296         outputSizer.Add(item = self.progressbar, proportion = 0,
00297                         flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = 3)
00298         outBtnSizer.Add(item = self.btnOutputClear, proportion = 1,
00299                         flag = wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT, border = 5)
00300         outBtnSizer.Add(item = self.btnOutputSave, proportion = 1,
00301                         flag = wx.ALIGN_RIGHT | wx.RIGHT, border = 5)
00302         
00303         cmdBtnSizer.Add(item = self.btnCmdClear, proportion = 1,
00304                         flag = wx.ALIGN_CENTER | wx.LEFT | wx.RIGHT, border = 5)
00305         cmdBtnSizer.Add(item = self.btnCmdAbort, proportion = 1,
00306                         flag = wx.ALIGN_CENTER | wx.RIGHT, border = 5)
00307         
00308         btnSizer.Add(item = outBtnSizer, proportion = 1,
00309                      flag = wx.ALL | wx.ALIGN_CENTER, border = 5)
00310         btnSizer.Add(item = cmdBtnSizer, proportion = 1,
00311                      flag = wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM | wx.RIGHT, border = 5)
00312         outputSizer.Add(item = btnSizer, proportion = 0,
00313                         flag = wx.EXPAND)
00314         
00315         outputSizer.Fit(self)
00316         outputSizer.SetSizeHints(self)
00317         self.panelOutput.SetSizer(outputSizer)
00318         
00319         if self.cmdPrompt.IsShown():
00320             promptSizer.Fit(self)
00321             promptSizer.SetSizeHints(self)
00322             self.panelPrompt.SetSizer(promptSizer)
00323         
00324         # split window
00325         if self.cmdPrompt.IsShown():
00326             self.SplitHorizontally(self.panelOutput, self.panelPrompt, -50)
00327         else:
00328             self.SplitHorizontally(self.panelOutput, self.panelPrompt, -45)
00329             self.Unsplit()
00330         self.SetMinimumPaneSize(self.btnCmdClear.GetSize()[1] + 25)
00331         
00332         self.SetSashGravity(1.0)
00333         
00334         # layout
00335         self.SetAutoLayout(True)
00336         self.Layout()
00337 
00338     def MakeSearchPaneContent(self, pane):
00339         """!Create search pane"""
00340         border = wx.BoxSizer(wx.VERTICAL)
00341         
00342         self.search = SearchModuleWindow(parent = pane, cmdPrompt = self.cmdPrompt)
00343         
00344         border.Add(item = self.search, proportion = 0,
00345                    flag = wx.EXPAND | wx.ALL, border = 1)
00346         
00347         pane.SetSizer(border)
00348         border.Fit(pane)
00349         
00350     def OnSearchPaneChanged(self, event):
00351         """!Collapse search module box"""
00352         if self.searchPane.IsExpanded():
00353             self.searchPane.SetLabel(self.infoCollapseLabelCol)
00354         else:
00355             self.searchPane.SetLabel(self.infoCollapseLabelExp)
00356         
00357         self.panelOutput.Layout()
00358         self.panelOutput.SendSizeEvent()
00359         
00360     def GetPanel(self, prompt = True):
00361         """!Get panel
00362 
00363         @param prompt get prompt / output panel
00364 
00365         @return wx.Panel reference
00366         """
00367         if prompt:
00368             return self.panelPrompt
00369 
00370         return self.panelOutput
00371     
00372     def Redirect(self):
00373         """!Redirect stdout/stderr
00374         """
00375         if Debug.GetLevel() == 0 and int(grass.gisenv().get('DEBUG', 0)) == 0:
00376             # don't redirect when debugging is enabled
00377             sys.stdout = self.cmdStdOut
00378             sys.stderr = self.cmdStdErr
00379         else:
00380             enc = locale.getdefaultlocale()[1]
00381             if enc:
00382                 sys.stdout = codecs.getwriter(enc)(sys.__stdout__)
00383                 sys.stderr = codecs.getwriter(enc)(sys.__stderr__)
00384             else:
00385                 sys.stdout = sys.__stdout__
00386                 sys.stderr = sys.__stderr__
00387         
00388     def WriteLog(self, text, style = None, wrap = None,
00389                  switchPage = False):
00390         """!Generic method for writing log message in 
00391         given style
00392 
00393         @param line text line
00394         @param style text style (see GMStc)
00395         @param stdout write to stdout or stderr
00396         """
00397 
00398         self.cmdOutput.SetStyle()
00399 
00400         if switchPage:
00401             self._notebook.SetSelectionByName('output')
00402         
00403         if not style:
00404             style = self.cmdOutput.StyleDefault
00405         
00406         # p1 = self.cmdOutput.GetCurrentPos()
00407         p1 = self.cmdOutput.GetEndStyled()
00408         # self.cmdOutput.GotoPos(p1)
00409         self.cmdOutput.DocumentEnd()
00410         
00411         for line in text.splitlines():
00412             # fill space
00413             if len(line) < self.lineWidth:
00414                 diff = self.lineWidth - len(line) 
00415                 line += diff * ' '
00416             
00417             self.cmdOutput.AddTextWrapped(line, wrap = wrap) # adds '\n'
00418             
00419             p2 = self.cmdOutput.GetCurrentPos()
00420             
00421             self.cmdOutput.StartStyling(p1, 0xff)
00422             self.cmdOutput.SetStyling(p2 - p1, style)
00423         
00424         self.cmdOutput.EnsureCaretVisible()
00425         
00426     def WriteCmdLog(self, line, pid = None, switchPage = True):
00427         """!Write message in selected style"""
00428         if pid:
00429             line = '(' + str(pid) + ') ' + line
00430         
00431         self.WriteLog(line, style = self.cmdOutput.StyleCommand, switchPage = switchPage)
00432         
00433     def WriteWarning(self, line):
00434         """!Write message in warning style"""
00435         self.WriteLog(line, style = self.cmdOutput.StyleWarning, switchPage = True)
00436 
00437     def WriteError(self, line):
00438         """!Write message in error style"""
00439         self.WriteLog(line, style = self.cmdOutput.StyleError, switchPage = True)
00440 
00441     def RunCmd(self, command, compReg = True, switchPage = False, skipInterface = False,
00442                onDone = None, onPrepare = None, userData = None):
00443         """!Run command typed into console command prompt (GPrompt).
00444         
00445         @todo Display commands (*.d) are captured and processed
00446         separately by mapdisp.py. Display commands are rendered in map
00447         display widget that currently has the focus (as indicted by
00448         mdidx).
00449         
00450         @param command command given as a list (produced e.g. by utils.split())
00451         @param compReg True use computation region
00452         @param switchPage switch to output page
00453         @param skipInterface True to do not launch GRASS interface
00454         parser when command has no arguments given
00455         @param onDone function to be called when command is finished
00456         @param onPrepare function to be called before command is launched
00457         @param userData data defined for the command
00458         """
00459         if len(command) == 0:
00460             Debug.msg(2, "GPrompt:RunCmd(): empty command")
00461             return
00462         
00463         # update history file
00464         env = grass.gisenv()
00465         try:
00466             fileHistory = codecs.open(os.path.join(env['GISDBASE'],
00467                                                    env['LOCATION_NAME'],
00468                                                    env['MAPSET'],
00469                                                    '.bash_history'),
00470                                       encoding = 'utf-8', mode = 'a')
00471         except IOError, e:
00472             self.WriteError(e)
00473             fileHistory = None
00474         
00475         if fileHistory:
00476             try:
00477                 fileHistory.write(' '.join(command) + os.linesep)
00478             finally:
00479                 fileHistory.close()
00480         
00481         if command[0] in globalvar.grassCmd:
00482             # send GRASS command without arguments to GUI command interface
00483             # except display commands (they are handled differently)
00484             if self.parent.GetName() == "LayerManager" and \
00485                     command[0][0:2] == "d." and \
00486                     'help' not in ' '.join(command[1:]):
00487                 # display GRASS commands
00488                 try:
00489                     layertype = {'d.rast'         : 'raster',
00490                                  'd.rast3d'       : '3d-raster',
00491                                  'd.rgb'          : 'rgb',
00492                                  'd.his'          : 'his',
00493                                  'd.shaded'       : 'shaded',
00494                                  'd.legend'       : 'rastleg',
00495                                  'd.rast.arrow'   : 'rastarrow',
00496                                  'd.rast.num'     : 'rastnum',
00497                                  'd.vect'         : 'vector',
00498                                  'd.vect.thematic': 'thememap',
00499                                  'd.vect.chart'   : 'themechart',
00500                                  'd.grid'         : 'grid',
00501                                  'd.geodesic'     : 'geodesic',
00502                                  'd.rhumbline'    : 'rhumb',
00503                                  'd.labels'       : 'labels',
00504                                  'd.barscale'     : 'barscale',
00505                                  'd.redraw'       : 'redraw'}[command[0]]
00506                 except KeyError:
00507                     GMessage(parent = self.parent,
00508                              message = _("Command '%s' not yet implemented in the WxGUI. "
00509                                          "Try adding it as a command layer instead.") % command[0])
00510                     return
00511                 
00512                 if layertype == 'barscale':
00513                     self.parent.curr_page.maptree.GetMapDisplay().OnAddBarscale(None)
00514                 elif layertype == 'rastleg':
00515                     self.parent.curr_page.maptree.GetMapDisplay().OnAddLegend(None)
00516                 elif layertype == 'redraw':
00517                     self.parent.curr_page.maptree.GetMapDisplay().OnRender(None)
00518                 else:
00519                     # add layer into layer tree
00520                     lname, found = utils.GetLayerNameFromCmd(command, fullyQualified = True,
00521                                                              layerType = layertype)
00522                     if self.parent.GetName() == "LayerManager":
00523                         self.parent.curr_page.maptree.AddLayer(ltype = layertype,
00524                                                                lname = lname,
00525                                                                lcmd = command)
00526             
00527             else:
00528                 # other GRASS commands (r|v|g|...)
00529                 hasParams = False
00530                 if command[0] != 'r.mapcalc':
00531                     try:
00532                         task = GUI(show = None).ParseCommand(command)
00533                     except GException, e:
00534                         GError(parent = self,
00535                                message = unicode(e),
00536                                showTraceback = False)
00537                         return
00538                     
00539                     if task:
00540                         options = task.get_options()
00541                         hasParams = options['params'] and options['flags']
00542                         # check for <input>=-
00543                         for p in options['params']:
00544                             if p.get('prompt', '') == 'input' and \
00545                                     p.get('element', '') == 'file' and \
00546                                     p.get('age', 'new') == 'old' and \
00547                                     p.get('value', '') == '-':
00548                                 GError(parent = self,
00549                                        message = _("Unable to run command:\n%(cmd)s\n\n"
00550                                                    "Option <%(opt)s>: read from standard input is not "
00551                                                    "supported by wxGUI") % { 'cmd': ' '.join(command),
00552                                                                              'opt': p.get('name', '') })
00553                                 return
00554                 else:
00555                     task = None
00556                 
00557                 if len(command) == 1 and hasParams and \
00558                         command[0] != 'v.krige.py':
00559                     # no arguments given
00560                     try:
00561                         GUI(parent = self).ParseCommand(command)
00562                     except GException, e:
00563                         print >> sys.stderr, e
00564                     return
00565                 
00566                 # switch to 'Command output' if required
00567                 if switchPage:
00568                     self._notebook.SetSelectionByName('output')
00569                     
00570                     self.parent.SetFocus()
00571                     self.parent.Raise()
00572                 
00573                 # activate computational region (set with g.region)
00574                 # for all non-display commands.
00575                 if compReg:
00576                     tmpreg = os.getenv("GRASS_REGION")
00577                     if "GRASS_REGION" in os.environ:
00578                         del os.environ["GRASS_REGION"]
00579                 
00580                 # process GRASS command with argument
00581                 self.cmdThread.RunCmd(command, stdout = self.cmdStdOut, stderr = self.cmdStdErr,
00582                                       onDone = onDone, onPrepare = onPrepare, userData = userData,
00583                                       env = os.environ.copy())
00584                 self.cmdOutputTimer.Start(50)
00585                 
00586                 # deactivate computational region and return to display settings
00587                 if compReg and tmpreg:
00588                     os.environ["GRASS_REGION"] = tmpreg
00589         else:
00590             # Send any other command to the shell. Send output to
00591             # console output window
00592             if len(command) == 1 and not skipInterface:
00593                 try:
00594                     task = gtask.parse_interface(command[0])
00595                 except:
00596                     task = None
00597             else:
00598                 task = None
00599                 
00600             if task:
00601                 # process GRASS command without argument
00602                 GUI(parent = self).ParseCommand(command)
00603             else:
00604                 self.cmdThread.RunCmd(command, stdout = self.cmdStdOut, stderr = self.cmdStdErr,
00605                                       onDone = onDone, onPrepare = onPrepare, userData = userData)
00606             self.cmdOutputTimer.Start(50)
00607         
00608     def ClearHistory(self, event):
00609         """!Clear history of commands"""
00610         self.cmdOutput.SetReadOnly(False)
00611         self.cmdOutput.ClearAll()
00612         self.cmdOutput.SetReadOnly(True)
00613         self.progressbar.SetValue(0)
00614 
00615     def GetProgressBar(self):
00616         """!Return progress bar widget"""
00617         return self.progressbar
00618     
00619     def GetLog(self, err = False):
00620         """!Get widget used for logging
00621 
00622         @param err True to get stderr widget
00623         """
00624         if err:
00625             return self.cmdStdErr
00626         
00627         return self.cmdStdOut
00628     
00629     def SaveHistory(self, event):
00630         """!Save history of commands"""
00631         self.history = self.cmdOutput.GetSelectedText()
00632         if self.history == '':
00633             self.history = self.cmdOutput.GetText()
00634 
00635         # add newline if needed
00636         if len(self.history) > 0 and self.history[-1] != '\n':
00637             self.history += '\n'
00638 
00639         wildcard = "Text file (*.txt)|*.txt"
00640         dlg = wx.FileDialog(self, message = _("Save file as..."), defaultDir = os.getcwd(),
00641                             defaultFile = "grass_cmd_history.txt", wildcard = wildcard,
00642                             style = wx.SAVE | wx.FD_OVERWRITE_PROMPT)
00643 
00644         # Show the dialog and retrieve the user response. If it is the OK response,
00645         # process the data.
00646         if dlg.ShowModal() == wx.ID_OK:
00647             path = dlg.GetPath()
00648 
00649             output = open(path, "w")
00650             output.write(self.history)
00651             output.close()
00652 
00653         dlg.Destroy()
00654 
00655     def GetCmd(self):
00656         """!Get running command or None"""
00657         return self.requestQ.get()
00658     
00659     def SetCopyingOfSelectedText(self, copy):
00660         """!Enable or disable copying of selected text in to clipboard.
00661         Effects prompt and output.
00662         
00663         @param copy True for enable, False for disable
00664         """
00665         if copy:
00666             self.cmdPrompt.Bind(stc.EVT_STC_PAINTED, self.cmdPrompt.OnTextSelectionChanged)
00667             self.cmdOutput.Bind(stc.EVT_STC_PAINTED, self.cmdOutput.OnTextSelectionChanged)
00668         else:
00669             self.cmdPrompt.Unbind(stc.EVT_STC_PAINTED)
00670             self.cmdOutput.Unbind(stc.EVT_STC_PAINTED)
00671         
00672     def OnUpdateStatusBar(self, event):
00673         """!Update statusbar text"""
00674         if event.GetString():
00675             nItems = len(self.cmdPrompt.GetCommandItems())
00676             self.parent.SetStatusText(_('%d modules match') % nItems, 0)
00677         else:
00678             self.parent.SetStatusText('', 0)
00679         
00680         event.Skip()
00681 
00682     def OnCmdOutput(self, event):
00683         """!Print command output"""
00684         message = event.text
00685         type  = event.type
00686         if self._notebook.GetSelection() != self._notebook.GetPageIndexByName('output'):
00687             page = self._notebook.GetPageIndexByName('output')
00688             textP = self._notebook.GetPageText(page)
00689             if textP[-1] != ')':
00690                 textP += ' (...)'
00691             self._notebook.SetPageText(page, textP)
00692         
00693         # message prefix
00694         if type == 'warning':
00695             messege = 'WARNING: ' + message
00696         elif type == 'error':
00697             message = 'ERROR: ' + message
00698         
00699         p1 = self.cmdOutput.GetEndStyled()
00700         self.cmdOutput.GotoPos(p1)
00701         
00702         if '\b' in message:
00703             if self.linepos < 0:
00704                 self.linepos = p1
00705             last_c = ''
00706             for c in message:
00707                 if c == '\b':
00708                     self.linepos -= 1
00709                 else:
00710                     if c == '\r':
00711                         pos = self.cmdOutput.GetCurLine()[1]
00712                         # self.cmdOutput.SetCurrentPos(pos)
00713                     else:
00714                         self.cmdOutput.SetCurrentPos(self.linepos)
00715                     self.cmdOutput.ReplaceSelection(c)
00716                     self.linepos = self.cmdOutput.GetCurrentPos()
00717                     if c != ' ':
00718                         last_c = c
00719             if last_c not in ('0123456789'):
00720                 self.cmdOutput.AddTextWrapped('\n', wrap = None)
00721                 self.linepos = -1
00722         else:
00723             self.linepos = -1 # don't force position
00724             if '\n' not in message:
00725                 self.cmdOutput.AddTextWrapped(message, wrap = 60)
00726             else:
00727                 self.cmdOutput.AddTextWrapped(message, wrap = None)
00728 
00729         p2 = self.cmdOutput.GetCurrentPos()
00730         
00731         if p2 >= p1:
00732             self.cmdOutput.StartStyling(p1, 0xff)
00733         
00734             if type == 'error':
00735                 self.cmdOutput.SetStyling(p2 - p1, self.cmdOutput.StyleError)
00736             elif type == 'warning':
00737                 self.cmdOutput.SetStyling(p2 - p1, self.cmdOutput.StyleWarning)
00738             elif type == 'message':
00739                 self.cmdOutput.SetStyling(p2 - p1, self.cmdOutput.StyleMessage)
00740             else: # unknown
00741                 self.cmdOutput.SetStyling(p2 - p1, self.cmdOutput.StyleUnknown)
00742         
00743         self.cmdOutput.EnsureCaretVisible()
00744         
00745     def OnCmdProgress(self, event):
00746         """!Update progress message info"""
00747         self.progressbar.SetValue(event.value)
00748 
00749     def OnCmdAbort(self, event):
00750         """!Abort running command"""
00751         self.cmdThread.abort()
00752         
00753     def OnCmdRun(self, event):
00754         """!Run command"""
00755         if self.parent.GetName() == 'Modeler':
00756             self.parent.OnCmdRun(event)
00757         
00758         self.WriteCmdLog('(%s)\n%s' % (str(time.ctime()), ' '.join(event.cmd)))
00759         self.btnCmdAbort.Enable()
00760 
00761     def OnCmdPrepare(self, event):
00762         """!Prepare for running command"""
00763         if self.parent.GetName() == 'Modeler':
00764             self.parent.OnCmdPrepare(event)
00765         
00766         event.Skip()
00767         
00768     def OnCmdDone(self, event):
00769         """!Command done (or aborted)"""
00770         if self.parent.GetName() == 'Modeler':
00771             self.parent.OnCmdDone(event)
00772             
00773         # Process results here
00774         try:
00775             ctime = time.time() - event.time
00776             if ctime < 60:
00777                 stime = _("%d sec") % int(ctime)
00778             else:
00779                 mtime = int(ctime / 60)
00780                 stime = _("%(min)d min %(sec)d sec") %  { 'min' : mtime, 
00781                                                           'sec' : int(ctime - (mtime * 60)) }
00782         except KeyError:
00783             # stopped deamon
00784             stime = _("unknown")
00785         
00786         if event.aborted:
00787             # Thread aborted (using our convention of None return)
00788             self.WriteLog(_('Please note that the data are left in inconsistent state '
00789                             'and may be corrupted'), self.cmdOutput.StyleWarning)
00790             msg = _('Command aborted')
00791         else:
00792             msg = _('Command finished')
00793             
00794         self.WriteCmdLog('(%s) %s (%s)' % (str(time.ctime()), msg, stime))
00795         self.btnCmdAbort.Enable(False)
00796         
00797         if event.onDone:
00798             event.onDone(cmd = event.cmd, returncode = event.returncode)
00799         
00800         self.progressbar.SetValue(0) # reset progress bar on '0%'
00801 
00802         self.cmdOutputTimer.Stop()
00803 
00804         if event.cmd[0] == 'g.gisenv':
00805             Debug.SetLevel()
00806             self.Redirect()
00807         
00808         if self.parent.GetName() == "LayerManager":
00809             self.btnCmdAbort.Enable(False)
00810             if event.cmd[0] not in globalvar.grassCmd or \
00811                     event.cmd[0] == 'r.mapcalc':
00812                 return
00813             
00814             display = self.parent.GetLayerTree().GetMapDisplay()
00815             if not display or not display.IsAutoRendered():
00816                 return
00817             mapLayers = map(lambda x: x.GetName(),
00818                             display.GetMap().GetListOfLayers(l_type = 'raster') +
00819                             display.GetMap().GetListOfLayers(l_type = 'vector'))
00820             
00821             try:
00822                 task = GUI(show = None).ParseCommand(event.cmd)
00823             except GException, e:
00824                 print >> sys.stderr, e
00825                 task = None
00826                 return
00827             
00828             for p in task.get_options()['params']:
00829                 if p.get('prompt', '') not in ('raster', 'vector'):
00830                     continue
00831                 mapName = p.get('value', '')
00832                 if '@' not in mapName:
00833                     mapName = mapName + '@' + grass.gisenv()['MAPSET']
00834                 if mapName in mapLayers:
00835                     display.GetWindow().UpdateMap(render = True)
00836                     return
00837         elif self.parent.GetName() == 'Modeler':
00838             pass
00839         else: # standalone dialogs
00840             dialog = self.parent.parent
00841             if hasattr(self.parent.parent, "btn_abort"):
00842                 dialog.btn_abort.Enable(False)
00843             
00844             if hasattr(self.parent.parent, "btn_cancel"):
00845                 dialog.btn_cancel.Enable(True)
00846             
00847             if hasattr(self.parent.parent, "btn_clipboard"):
00848                 dialog.btn_clipboard.Enable(True)
00849             
00850             if hasattr(self.parent.parent, "btn_help"):
00851                 dialog.btn_help.Enable(True)
00852             
00853             if hasattr(self.parent.parent, "btn_run"):
00854                 dialog.btn_run.Enable(True)
00855             
00856             if event.returncode == 0 and not event.aborted:
00857                 try:
00858                     winName = self.parent.parent.parent.GetName()
00859                 except AttributeError:
00860                     winName = ''
00861                 
00862                 if winName == 'LayerManager':
00863                     mapTree = self.parent.parent.parent.GetLayerTree()
00864                 elif winName == 'LayerTree':
00865                     mapTree = self.parent.parent.parent
00866                 elif winName: # GMConsole
00867                     mapTree = self.parent.parent.parent.parent.GetLayerTree()
00868                 else:
00869                     mapTree = None
00870                 
00871                 cmd = dialog.notebookpanel.createCmd(ignoreErrors = True)
00872                 if hasattr(dialog, "addbox") and dialog.addbox.IsChecked():
00873                     # add created maps into layer tree
00874                     for p in dialog.task.get_options()['params']:
00875                         prompt = p.get('prompt', '')
00876                         if prompt in ('raster', 'vector', '3d-raster') and \
00877                                 p.get('age', 'old') == 'new' and \
00878                                 p.get('value', None):
00879                             name, found = utils.GetLayerNameFromCmd(cmd, fullyQualified = True,
00880                                                                     param = p.get('name', ''))
00881                             
00882                             if mapTree.GetMap().GetListOfLayers(l_name = name):
00883                                 continue
00884                             
00885                             if prompt == 'raster':
00886                                 lcmd = ['d.rast',
00887                                         'map=%s' % name]
00888                             else:
00889                                 lcmd = ['d.vect',
00890                                         'map=%s' % name]
00891                             mapTree.AddLayer(ltype = prompt,
00892                                              lcmd = lcmd,
00893                                              lname = name)
00894             
00895             if hasattr(dialog, "get_dcmd") and \
00896                     dialog.get_dcmd is None and \
00897                     hasattr(dialog, "closebox") and \
00898                     dialog.closebox.IsChecked() and \
00899                     (event.returncode == 0 or event.aborted):
00900                 self.cmdOutput.Update()
00901                 time.sleep(2)
00902                 dialog.Close()
00903         
00904     def OnProcessPendingOutputWindowEvents(self, event):
00905         self.ProcessPendingEvents()
00906 
00907     def ResetFocus(self):
00908         """!Reset focus"""
00909         self.cmdPrompt.SetFocus()
00910         
00911 class GMStdout:
00912     """!GMConsole standard output
00913 
00914     Based on FrameOutErr.py
00915 
00916     Name:      FrameOutErr.py
00917     Purpose:   Redirecting stdout / stderr
00918     Author:    Jean-Michel Fauth, Switzerland
00919     Copyright: (c) 2005-2007 Jean-Michel Fauth
00920     Licence:   GPL
00921     """
00922     def __init__(self, parent):
00923         self.parent = parent # GMConsole
00924 
00925     def write(self, s):
00926         if len(s) == 0 or s == '\n':
00927             return
00928 
00929         for line in s.splitlines():
00930             if len(line) == 0:
00931                 continue
00932             
00933             evt = wxCmdOutput(text = line + '\n',
00934                               type = '')
00935             wx.PostEvent(self.parent.cmdOutput, evt)
00936         
00937 class GMStderr:
00938     """!GMConsole standard error output
00939 
00940     Based on FrameOutErr.py
00941 
00942     Name:      FrameOutErr.py
00943     Purpose:   Redirecting stdout / stderr
00944     Author:    Jean-Michel Fauth, Switzerland
00945     Copyright: (c) 2005-2007 Jean-Michel Fauth
00946     Licence:   GPL
00947     """
00948     def __init__(self, parent):
00949         self.parent = parent # GMConsole
00950         
00951         self.type = ''
00952         self.message = ''
00953         self.printMessage = False
00954         
00955     def flush(self):
00956         pass
00957     
00958     def write(self, s):
00959         if "GtkPizza" in s:
00960             return
00961         
00962         # remove/replace escape sequences '\b' or '\r' from stream
00963         progressValue = -1
00964         
00965         for line in s.splitlines():
00966             if len(line) == 0:
00967                 continue
00968             
00969             if 'GRASS_INFO_PERCENT' in line:
00970                 value = int(line.rsplit(':', 1)[1].strip())
00971                 if value >= 0 and value < 100:
00972                     progressValue = value
00973                 else:
00974                     progressValue = 0
00975             elif 'GRASS_INFO_MESSAGE' in line:
00976                 self.type = 'message'
00977                 self.message += line.split(':', 1)[1].strip() + '\n'
00978             elif 'GRASS_INFO_WARNING' in line:
00979                 self.type = 'warning'
00980                 self.message += line.split(':', 1)[1].strip() + '\n'
00981             elif 'GRASS_INFO_ERROR' in line:
00982                 self.type = 'error'
00983                 self.message += line.split(':', 1)[1].strip() + '\n'
00984             elif 'GRASS_INFO_END' in line:
00985                 self.printMessage = True
00986             elif self.type == '':
00987                 if len(line) == 0:
00988                     continue
00989                 evt = wxCmdOutput(text = line,
00990                                   type = '')
00991                 wx.PostEvent(self.parent.cmdOutput, evt)
00992             elif len(line) > 0:
00993                 self.message += line.strip() + '\n'
00994 
00995             if self.printMessage and len(self.message) > 0:
00996                 evt = wxCmdOutput(text = self.message,
00997                                   type = self.type)
00998                 wx.PostEvent(self.parent.cmdOutput, evt)
00999 
01000                 self.type = ''
01001                 self.message = ''
01002                 self.printMessage = False
01003 
01004         # update progress message
01005         if progressValue > -1:
01006             # self.gmgauge.SetValue(progressValue)
01007             evt = wxCmdProgress(value = progressValue)
01008             wx.PostEvent(self.parent.progressbar, evt)
01009             
01010 class GMStc(stc.StyledTextCtrl):
01011     """!Styled GMConsole
01012 
01013     Based on FrameOutErr.py
01014 
01015     Name:      FrameOutErr.py
01016     Purpose:   Redirecting stdout / stderr
01017     Author:    Jean-Michel Fauth, Switzerland
01018     Copyright: (c) 2005-2007 Jean-Michel Fauth
01019     Licence:   GPL
01020     """    
01021     def __init__(self, parent, id, margin = False, wrap = None):
01022         stc.StyledTextCtrl.__init__(self, parent, id)
01023         self.parent = parent
01024         self.SetUndoCollection(True)
01025         self.SetReadOnly(True)
01026 
01027         #
01028         # styles
01029         #                
01030         self.SetStyle()
01031         
01032         #
01033         # line margins
01034         #
01035         # TODO print number only from cmdlog
01036         self.SetMarginWidth(1, 0)
01037         self.SetMarginWidth(2, 0)
01038         if margin:
01039             self.SetMarginType(0, stc.STC_MARGIN_NUMBER)
01040             self.SetMarginWidth(0, 30)
01041         else:
01042             self.SetMarginWidth(0, 0)
01043 
01044         #
01045         # miscellaneous
01046         #
01047         self.SetViewWhiteSpace(False)
01048         self.SetTabWidth(4)
01049         self.SetUseTabs(False)
01050         self.UsePopUp(True)
01051         self.SetSelBackground(True, "#FFFF00")
01052         self.SetUseHorizontalScrollBar(True)
01053 
01054         #
01055         # bindings
01056         #
01057         self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
01058         
01059     def OnTextSelectionChanged(self, event):
01060         """!Copy selected text to clipboard and skip event.
01061         The same function is in TextCtrlAutoComplete class (prompt.py).
01062         """
01063         self.Copy()
01064         event.Skip()
01065         
01066     def SetStyle(self):
01067         """!Set styles for styled text output windows with type face 
01068         and point size selected by user (Courier New 10 is default)"""
01069 
01070         settings = Settings()
01071         
01072         typeface = settings.Get(group = 'appearance', key = 'outputfont', subkey = 'type')   
01073         if typeface == "":
01074             typeface = "Courier New"
01075         
01076         typesize = settings.Get(group = 'appearance', key = 'outputfont', subkey = 'size')
01077         if typesize == None or typesize <= 0:
01078             typesize = 10
01079         typesize = float(typesize)
01080         
01081         self.StyleDefault     = 0
01082         self.StyleDefaultSpec = "face:%s,size:%d,fore:#000000,back:#FFFFFF" % (typeface, typesize)
01083         self.StyleCommand     = 1
01084         self.StyleCommandSpec = "face:%s,size:%d,,fore:#000000,back:#bcbcbc" % (typeface, typesize)
01085         self.StyleOutput      = 2
01086         self.StyleOutputSpec  = "face:%s,size:%d,,fore:#000000,back:#FFFFFF" % (typeface, typesize)
01087         # fatal error
01088         self.StyleError       = 3
01089         self.StyleErrorSpec   = "face:%s,size:%d,,fore:#7F0000,back:#FFFFFF" % (typeface, typesize)
01090         # warning
01091         self.StyleWarning     = 4
01092         self.StyleWarningSpec = "face:%s,size:%d,,fore:#0000FF,back:#FFFFFF" % (typeface, typesize)
01093         # message
01094         self.StyleMessage     = 5
01095         self.StyleMessageSpec = "face:%s,size:%d,,fore:#000000,back:#FFFFFF" % (typeface, typesize)
01096         # unknown
01097         self.StyleUnknown     = 6
01098         self.StyleUnknownSpec = "face:%s,size:%d,,fore:#000000,back:#FFFFFF" % (typeface, typesize)
01099         
01100         # default and clear => init
01101         self.StyleSetSpec(stc.STC_STYLE_DEFAULT, self.StyleDefaultSpec)
01102         self.StyleClearAll()
01103         self.StyleSetSpec(self.StyleCommand, self.StyleCommandSpec)
01104         self.StyleSetSpec(self.StyleOutput,  self.StyleOutputSpec)
01105         self.StyleSetSpec(self.StyleError,   self.StyleErrorSpec)
01106         self.StyleSetSpec(self.StyleWarning, self.StyleWarningSpec)
01107         self.StyleSetSpec(self.StyleMessage, self.StyleMessageSpec)
01108         self.StyleSetSpec(self.StyleUnknown, self.StyleUnknownSpec)        
01109 
01110     def OnDestroy(self, evt):
01111         """!The clipboard contents can be preserved after
01112         the app has exited"""
01113         
01114         wx.TheClipboard.Flush()
01115         evt.Skip()
01116 
01117     def AddTextWrapped(self, txt, wrap = None):
01118         """!Add string to text area.
01119 
01120         String is wrapped and linesep is also added to the end
01121         of the string"""
01122         # allow writing to output window
01123         self.SetReadOnly(False)
01124         
01125         if wrap:
01126             txt = textwrap.fill(txt, wrap) + '\n'
01127         else:
01128             if txt[-1] != '\n':
01129                 txt += '\n'
01130         
01131         if '\r' in txt:
01132             self.parent.linePos = -1
01133             for seg in txt.split('\r'):
01134                 if self.parent.linePos > -1:
01135                     self.SetCurrentPos(self.parent.linePos)
01136                     self.ReplaceSelection(seg)
01137                 else:
01138                     self.parent.linePos = self.GetCurrentPos()
01139                     self.AddText(seg)
01140         else:
01141             self.parent.linePos = self.GetCurrentPos()
01142             try:
01143                 self.AddText(txt)
01144             except UnicodeDecodeError:
01145                 enc = UserSettings.Get(group = 'atm', key = 'encoding', subkey = 'value')
01146                 if enc:
01147                     txt = unicode(txt, enc)
01148                 elif 'GRASS_DB_ENCODING' in os.environ:
01149                     txt = unicode(txt, os.environ['GRASS_DB_ENCODING'])
01150                 else:
01151                     txt = EncodeString(txt)
01152                 
01153                 self.AddText(txt)
01154         
01155         # reset output window to read only
01156         self.SetReadOnly(True)
01157     
01158 class PyStc(stc.StyledTextCtrl):
01159     """!Styled Python output (see gmodeler::frame::PythonPanel for
01160     usage)
01161 
01162     Based on StyledTextCtrl_2 from wxPython demo
01163     """    
01164     def __init__(self, parent, id = wx.ID_ANY, statusbar = None):
01165         stc.StyledTextCtrl.__init__(self, parent, id)
01166         
01167         self.parent = parent
01168         self.statusbar = statusbar
01169         
01170         self.modified = False # content modified ?
01171         
01172         self.faces = { 'times': 'Times New Roman',
01173                        'mono' : 'Courier New',
01174                        'helv' : 'Arial',
01175                        'other': 'Comic Sans MS',
01176                        'size' : 10,
01177                        'size2': 8,
01178                        }
01179         
01180         self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
01181         self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
01182         
01183         self.SetLexer(stc.STC_LEX_PYTHON)
01184         self.SetKeyWords(0, " ".join(keyword.kwlist))
01185         
01186         self.SetProperty("fold", "1")
01187         self.SetProperty("tab.timmy.whinge.level", "1")
01188         self.SetMargins(0, 0)
01189         self.SetTabWidth(4)
01190         self.SetUseTabs(False)
01191         
01192         self.SetEdgeMode(stc.STC_EDGE_BACKGROUND)
01193         self.SetEdgeColumn(78)
01194         
01195         # setup a margin to hold fold markers
01196         self.SetMarginType(2, stc.STC_MARGIN_SYMBOL)
01197         self.SetMarginMask(2, stc.STC_MASK_FOLDERS)
01198         self.SetMarginSensitive(2, True)
01199         self.SetMarginWidth(2, 12)
01200         
01201         # like a flattened tree control using square headers
01202         self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN,    stc.STC_MARK_BOXMINUS,          "white", "#808080")
01203         self.MarkerDefine(stc.STC_MARKNUM_FOLDER,        stc.STC_MARK_BOXPLUS,           "white", "#808080")
01204         self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB,     stc.STC_MARK_VLINE,             "white", "#808080")
01205         self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL,    stc.STC_MARK_LCORNER,           "white", "#808080")
01206         self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND,     stc.STC_MARK_BOXPLUSCONNECTED,  "white", "#808080")
01207         self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080")
01208         self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNER,           "white", "#808080")
01209         
01210         self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI)
01211         self.Bind(stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
01212         self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)
01213         
01214         # Make some styles, the lexer defines what each style is used
01215         # for, we just have to define what each style looks like.
01216         # This set is adapted from Scintilla sample property files.
01217         
01218         # global default styles for all languages
01219         self.StyleSetSpec(stc.STC_STYLE_DEFAULT,     "face:%(helv)s,size:%(size)d" % self.faces)
01220         self.StyleClearAll()  # reset all to be like the default
01221         
01222         # global default styles for all languages
01223         self.StyleSetSpec(stc.STC_STYLE_DEFAULT,     "face:%(helv)s,size:%(size)d" % self.faces)
01224         self.StyleSetSpec(stc.STC_STYLE_LINENUMBER,  "back:#C0C0C0,face:%(helv)s,size:%(size2)d" % self.faces)
01225         self.StyleSetSpec(stc.STC_STYLE_CONTROLCHAR, "face:%(other)s" % self.faces)
01226         self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT,  "fore:#FFFFFF,back:#0000FF,bold")
01227         self.StyleSetSpec(stc.STC_STYLE_BRACEBAD,    "fore:#000000,back:#FF0000,bold")
01228         
01229         # Python styles
01230         # Default 
01231         self.StyleSetSpec(stc.STC_P_DEFAULT, "fore:#000000,face:%(helv)s,size:%(size)d" % self.faces)
01232         # Comments
01233         self.StyleSetSpec(stc.STC_P_COMMENTLINE, "fore:#007F00,face:%(other)s,size:%(size)d" % self.faces)
01234         # Number
01235         self.StyleSetSpec(stc.STC_P_NUMBER, "fore:#007F7F,size:%(size)d" % self.faces)
01236         # String
01237         self.StyleSetSpec(stc.STC_P_STRING, "fore:#7F007F,face:%(helv)s,size:%(size)d" % self.faces)
01238         # Single quoted string
01239         self.StyleSetSpec(stc.STC_P_CHARACTER, "fore:#7F007F,face:%(helv)s,size:%(size)d" % self.faces)
01240         # Keyword
01241         self.StyleSetSpec(stc.STC_P_WORD, "fore:#00007F,bold,size:%(size)d" % self.faces)
01242         # Triple quotes
01243         self.StyleSetSpec(stc.STC_P_TRIPLE, "fore:#7F0000,size:%(size)d" % self.faces)
01244         # Triple double quotes
01245         self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, "fore:#7F0000,size:%(size)d" % self.faces)
01246         # Class name definition
01247         self.StyleSetSpec(stc.STC_P_CLASSNAME, "fore:#0000FF,bold,underline,size:%(size)d" % self.faces)
01248         # Function or method name definition
01249         self.StyleSetSpec(stc.STC_P_DEFNAME, "fore:#007F7F,bold,size:%(size)d" % self.faces)
01250         # Operators
01251         self.StyleSetSpec(stc.STC_P_OPERATOR, "bold,size:%(size)d" % self.faces)
01252         # Identifiers
01253         self.StyleSetSpec(stc.STC_P_IDENTIFIER, "fore:#000000,face:%(helv)s,size:%(size)d" % self.faces)
01254         # Comment-blocks
01255         self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, "fore:#7F7F7F,size:%(size)d" % self.faces)
01256         # End of line where string is not closed
01257         self.StyleSetSpec(stc.STC_P_STRINGEOL, "fore:#000000,face:%(mono)s,back:#E0C0E0,eol,size:%(size)d" % self.faces)
01258         
01259         self.SetCaretForeground("BLUE")
01260         
01261     def OnKeyPressed(self, event):
01262         """!Key pressed
01263         
01264         @todo implement code completion (see wxPython demo)
01265         """
01266         if not self.modified:
01267             self.modified = True
01268             if self.statusbar:
01269                 self.statusbar.SetStatusText(_('Python script contains local modifications'), 0)
01270         
01271         event.Skip()
01272         
01273     def OnUpdateUI(self, evt):
01274         # check for matching braces
01275         braceAtCaret = -1
01276         braceOpposite = -1
01277         charBefore = None
01278         caretPos = self.GetCurrentPos()
01279         
01280         if caretPos > 0:
01281             charBefore  = self.GetCharAt(caretPos - 1)
01282             styleBefore = self.GetStyleAt(caretPos - 1)
01283         
01284         # check before
01285         if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
01286             braceAtCaret = caretPos - 1
01287         
01288         # check after
01289         if braceAtCaret < 0:
01290             charAfter = self.GetCharAt(caretPos)
01291             styleAfter = self.GetStyleAt(caretPos)
01292             
01293             if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
01294                 braceAtCaret = caretPos
01295         
01296         if braceAtCaret >= 0:
01297             braceOpposite = self.BraceMatch(braceAtCaret)
01298         
01299         if braceAtCaret != -1  and braceOpposite == -1:
01300             self.BraceBadLight(braceAtCaret)
01301         else:
01302             self.BraceHighlight(braceAtCaret, braceOpposite)
01303         
01304     def OnMarginClick(self, evt):
01305         # fold and unfold as needed
01306         if evt.GetMargin() == 2:
01307             if evt.GetShift() and evt.GetControl():
01308                 self.FoldAll()
01309             else:
01310                 lineClicked = self.LineFromPosition(evt.GetPosition())
01311                 
01312                 if self.GetFoldLevel(lineClicked) & stc.STC_FOLDLEVELHEADERFLAG:
01313                     if evt.GetShift():
01314                         self.SetFoldExpanded(lineClicked, True)
01315                         self.Expand(lineClicked, True, True, 1)
01316                     elif evt.GetControl():
01317                         if self.GetFoldExpanded(lineClicked):
01318                             self.SetFoldExpanded(lineClicked, False)
01319                             self.Expand(lineClicked, False, True, 0)
01320                         else:
01321                             self.SetFoldExpanded(lineClicked, True)
01322                             self.Expand(lineClicked, True, True, 100)
01323                     else:
01324                         self.ToggleFold(lineClicked)
01325         
01326     def FoldAll(self):
01327         lineCount = self.GetLineCount()
01328         expanding = True
01329         
01330         # find out if we are folding or unfolding
01331         for lineNum in range(lineCount):
01332             if self.GetFoldLevel(lineNum) & stc.STC_FOLDLEVELHEADERFLAG:
01333                 expanding = not self.GetFoldExpanded(lineNum)
01334                 break
01335         
01336         lineNum = 0
01337         while lineNum < lineCount:
01338             level = self.GetFoldLevel(lineNum)
01339             if level & stc.STC_FOLDLEVELHEADERFLAG and \
01340                (level & stc.STC_FOLDLEVELNUMBERMASK) == stc.STC_FOLDLEVELBASE:
01341                 
01342                 if expanding:
01343                     self.SetFoldExpanded(lineNum, True)
01344                     lineNum = self.Expand(lineNum, True)
01345                     lineNum = lineNum - 1
01346                 else:
01347                     lastChild = self.GetLastChild(lineNum, -1)
01348                     self.SetFoldExpanded(lineNum, False)
01349                     
01350                     if lastChild > lineNum:
01351                         self.HideLines(lineNum+1, lastChild)
01352             
01353             lineNum = lineNum + 1
01354         
01355     def Expand(self, line, doExpand, force=False, visLevels=0, level=-1):
01356         lastChild = self.GetLastChild(line, level)
01357         line = line + 1
01358         
01359         while line <= lastChild:
01360             if force:
01361                 if visLevels > 0:
01362                     self.ShowLines(line, line)
01363                 else:
01364                     self.HideLines(line, line)
01365             else:
01366                 if doExpand:
01367                     self.ShowLines(line, line)
01368             
01369             if level == -1:
01370                 level = self.GetFoldLevel(line)
01371             
01372             if level & stc.STC_FOLDLEVELHEADERFLAG:
01373                 if force:
01374                     if visLevels > 1:
01375                         self.SetFoldExpanded(line, True)
01376                     else:
01377                         self.SetFoldExpanded(line, False)
01378                     
01379                     line = self.Expand(line, doExpand, force, visLevels-1)
01380                 else:
01381                     if doExpand and self.GetFoldExpanded(line):
01382                         line = self.Expand(line, True, force, visLevels-1)
01383                     else:
01384                         line = self.Expand(line, False, force, visLevels-1)
01385             else:
01386                 line = line + 1
01387         
01388         return line
01389