|
GRASS Programmer's Manual
6.5.svn(2012)-r51648
|
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