GRASS Programmer's Manual  6.4.4(2014)-r
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros Pages
goutput.py
Go to the documentation of this file.
1 """!
2 @package gui_core.goutput
3 
4 @brief Command output widgets
5 
6 Classes:
7  - goutput::CmdThread
8  - goutput::GMConsole
9  - goutput::GMStdout
10  - goutput::GMStderr
11  - goutput::GMStc
12  - goutput::PyStc
13 
14 (C) 2007-2012 by the GRASS Development Team
15 
16 This program is free software under the GNU General Public License
17 (>=v2). Read the file COPYING that comes with GRASS for details.
18 
19 @author Michael Barton (Arizona State University)
20 @author Martin Landa <landa.martin gmail.com>
21 @author Vaclav Petras <wenzeslaus gmail.com> (copy&paste customization)
22 """
23 
24 import os
25 import sys
26 import textwrap
27 import time
28 import threading
29 import Queue
30 import codecs
31 import locale
32 import keyword
33 
34 import wx
35 from wx import stc
36 from wx.lib.newevent import NewEvent
37 
38 import grass.script as grass
39 from grass.script import task as gtask
40 
41 from core import globalvar
42 from core import utils
43 from core.gcmd import CommandThread, GMessage, GError, GException, EncodeString
44 from gui_core.forms import GUI
45 from gui_core.prompt import GPromptSTC
46 from core.debug import Debug
47 from core.settings import UserSettings
48 from gui_core.ghelp import SearchModuleWindow
49 
50 wxCmdOutput, EVT_CMD_OUTPUT = NewEvent()
51 wxCmdProgress, EVT_CMD_PROGRESS = NewEvent()
52 wxCmdRun, EVT_CMD_RUN = NewEvent()
53 wxCmdDone, EVT_CMD_DONE = NewEvent()
54 wxCmdAbort, EVT_CMD_ABORT = NewEvent()
55 wxCmdPrepare, EVT_CMD_PREPARE = NewEvent()
56 
57 def GrassCmd(cmd, env = None, stdout = None, stderr = None):
58  """!Return GRASS command thread"""
59  return CommandThread(cmd, env = env,
60  stdout = stdout, stderr = stderr)
61 
62 class CmdThread(threading.Thread):
63  """!Thread for GRASS commands"""
64  requestId = 0
65  def __init__(self, parent, requestQ, resultQ, **kwds):
66  threading.Thread.__init__(self, **kwds)
67 
68  self.setDaemon(True)
69 
70  self.parent = parent # GMConsole
71  self._want_abort_all = False
72 
73  self.requestQ = requestQ
74  self.resultQ = resultQ
75 
76  self.start()
77 
78  def RunCmd(self, *args, **kwds):
79  CmdThread.requestId += 1
80 
81  self.requestCmd = None
82  self.requestQ.put((CmdThread.requestId, args, kwds))
83 
84  return CmdThread.requestId
85 
86  def SetId(self, id):
87  """!Set starting id"""
88  CmdThread.requestId = id
89 
90  def run(self):
91  os.environ['GRASS_MESSAGE_FORMAT'] = 'gui'
92  while True:
93  requestId, args, kwds = self.requestQ.get()
94  for key in ('callable', 'onDone', 'onPrepare', 'userData'):
95  if key in kwds:
96  vars()[key] = kwds[key]
97  del kwds[key]
98  else:
99  vars()[key] = None
100 
101  if not vars()['callable']:
102  vars()['callable'] = GrassCmd
103 
104  requestTime = time.time()
105 
106  # prepare
107  event = wxCmdPrepare(cmd = args[0],
108  time = requestTime,
109  pid = requestId,
110  onPrepare = vars()['onPrepare'],
111  userData = vars()['userData'])
112  wx.PostEvent(self.parent, event)
113 
114  # run command
115  event = wxCmdRun(cmd = args[0],
116  pid = requestId)
117  wx.PostEvent(self.parent, event)
118 
119  time.sleep(.1)
120  self.requestCmd = vars()['callable'](*args, **kwds)
121  if self._want_abort_all:
122  self.requestCmd.abort()
123  if self.requestQ.empty():
124  self._want_abort_all = False
125 
126  self.resultQ.put((requestId, self.requestCmd.run()))
127 
128  try:
129  returncode = self.requestCmd.module.returncode
130  except AttributeError:
131  returncode = 0 # being optimistic
132 
133  try:
134  aborted = self.requestCmd.aborted
135  except AttributeError:
136  aborted = False
137 
138  time.sleep(.1)
139 
140  # set default color table for raster data
141  if UserSettings.Get(group = 'cmd', key = 'rasterColorTable', subkey = 'enabled') and \
142  args[0][0][:2] == 'r.':
143  colorTable = UserSettings.Get(group = 'cmd', key = 'rasterColorTable', subkey = 'selection')
144  mapName = None
145  if args[0][0] == 'r.mapcalc':
146  try:
147  mapName = args[0][1].split('=', 1)[0].strip()
148  except KeyError:
149  pass
150  else:
151  moduleInterface = GUI(show = None).ParseCommand(args[0])
152  outputParam = moduleInterface.get_param(value = 'output', raiseError = False)
153  if outputParam and outputParam['prompt'] == 'raster':
154  mapName = outputParam['value']
155 
156  if mapName:
157  argsColor = list(args)
158  argsColor[0] = [ 'r.colors',
159  'map=%s' % mapName,
160  'color=%s' % colorTable ]
161  self.requestCmdColor = vars()['callable'](*argsColor, **kwds)
162  self.resultQ.put((requestId, self.requestCmdColor.run()))
163 
164  event = wxCmdDone(cmd = args[0],
165  aborted = aborted,
166  returncode = returncode,
167  time = requestTime,
168  pid = requestId,
169  onDone = vars()['onDone'],
170  userData = vars()['userData'])
171 
172  # send event
173  wx.PostEvent(self.parent, event)
174 
175  def abort(self, abortall = True):
176  """!Abort command(s)"""
177  if abortall:
178  self._want_abort_all = True
179  self.requestCmd.abort()
180  if self.requestQ.empty():
181  self._want_abort_all = False
182 
183 class GMConsole(wx.SplitterWindow):
184  """!Create and manage output console for commands run by GUI.
185  """
186  def __init__(self, parent, id = wx.ID_ANY, margin = False,
187  notebook = None,
188  style = wx.TAB_TRAVERSAL | wx.FULL_REPAINT_ON_RESIZE,
189  **kwargs):
190  wx.SplitterWindow.__init__(self, parent, id, style = style, *kwargs)
191  self.SetName("GMConsole")
192 
193  self.panelOutput = wx.Panel(parent = self, id = wx.ID_ANY)
194  self.panelPrompt = wx.Panel(parent = self, id = wx.ID_ANY)
195 
196  # initialize variables
197  self.parent = parent # GMFrame | CmdPanel | ?
198  if notebook:
199  self._notebook = notebook
200  else:
201  self._notebook = self.parent.notebook
202  self.lineWidth = 80
203 
204  # remember position of line begining (used for '\r')
205  self.linePos = -1
206 
207  # create queues
208  self.requestQ = Queue.Queue()
209  self.resultQ = Queue.Queue()
210 
211  # progress bar
212  self.progressbar = wx.Gauge(parent = self.panelOutput, id = wx.ID_ANY,
213  range = 100, pos = (110, 50), size = (-1, 25),
214  style = wx.GA_HORIZONTAL)
215  self.progressbar.Bind(EVT_CMD_PROGRESS, self.OnCmdProgress)
216 
217  # text control for command output
218  self.cmdOutput = GMStc(parent = self.panelOutput, id = wx.ID_ANY, margin = margin,
219  wrap = None)
220  self.cmdOutputTimer = wx.Timer(self.cmdOutput, id = wx.ID_ANY)
221  self.cmdOutput.Bind(EVT_CMD_OUTPUT, self.OnCmdOutput)
222  self.cmdOutput.Bind(wx.EVT_TIMER, self.OnProcessPendingOutputWindowEvents)
223  self.Bind(EVT_CMD_RUN, self.OnCmdRun)
224  self.Bind(EVT_CMD_DONE, self.OnCmdDone)
225  self.Bind(EVT_CMD_PREPARE, self.OnCmdPrepare)
226 
227  # search & command prompt
228  self.cmdPrompt = GPromptSTC(parent = self)
229 
230  if self.parent.GetName() != 'LayerManager':
231  self.search = None
232  self.cmdPrompt.Hide()
233  else:
234  self.infoCollapseLabelExp = _("Click here to show search module engine")
235  self.infoCollapseLabelCol = _("Click here to hide search module engine")
236  self.searchPane = wx.CollapsiblePane(parent = self.panelOutput,
237  label = self.infoCollapseLabelExp,
238  style = wx.CP_DEFAULT_STYLE |
239  wx.CP_NO_TLW_RESIZE | wx.EXPAND)
240  self.MakeSearchPaneContent(self.searchPane.GetPane())
241  self.searchPane.Collapse(True)
242  self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.OnSearchPaneChanged, self.searchPane)
243  self.search.Bind(wx.EVT_TEXT, self.OnUpdateStatusBar)
244 
245  # stream redirection
246  self.cmdStdOut = GMStdout(self)
247  self.cmdStdErr = GMStderr(self)
248 
249  # thread
250  self.cmdThread = CmdThread(self, self.requestQ, self.resultQ)
251 
252  self.outputBox = wx.StaticBox(parent = self.panelOutput, id = wx.ID_ANY,
253  label = " %s " % _("Output window"))
254  self.cmdBox = wx.StaticBox(parent = self.panelOutput, id = wx.ID_ANY,
255  label = " %s " % _("Command prompt"))
256 
257  # buttons
258  self.btnOutputClear = wx.Button(parent = self.panelOutput, id = wx.ID_CLEAR)
259  self.btnOutputClear.SetToolTipString(_("Clear output window content"))
260  self.btnCmdClear = wx.Button(parent = self.panelOutput, id = wx.ID_CLEAR)
261  self.btnCmdClear.SetToolTipString(_("Clear command prompt content"))
262  self.btnOutputSave = wx.Button(parent = self.panelOutput, id = wx.ID_SAVE)
263  self.btnOutputSave.SetToolTipString(_("Save output window content to the file"))
264  self.btnCmdAbort = wx.Button(parent = self.panelOutput, id = wx.ID_STOP)
265  self.btnCmdAbort.SetToolTipString(_("Abort running command"))
266  self.btnCmdAbort.Enable(False)
267  self.btnCmdProtocol = wx.ToggleButton(parent = self.panelOutput, id = wx.ID_ANY,
268  label = _("&Log file"),
269  size = self.btnCmdClear.GetSize())
270  self.btnCmdProtocol.SetToolTipString(_("Toggle to save list of executed commands into "
271  "a file; content saved when switching off."))
272 
273  if self.parent.GetName() != 'LayerManager':
274  self.btnCmdClear.Hide()
275  self.btnCmdProtocol.Hide()
276 
277  self.btnCmdClear.Bind(wx.EVT_BUTTON, self.cmdPrompt.OnCmdErase)
278  self.btnOutputClear.Bind(wx.EVT_BUTTON, self.OnOutputClear)
279  self.btnOutputSave.Bind(wx.EVT_BUTTON, self.OnOutputSave)
280  self.btnCmdAbort.Bind(wx.EVT_BUTTON, self.OnCmdAbort)
281  self.Bind(EVT_CMD_ABORT, self.OnCmdAbort)
282  self.btnCmdProtocol.Bind(wx.EVT_TOGGLEBUTTON, self.OnCmdProtocol)
283 
284  self._layout()
285 
286  def _layout(self):
287  """!Do layout"""
288  outputSizer = wx.BoxSizer(wx.VERTICAL)
289  btnSizer = wx.BoxSizer(wx.HORIZONTAL)
290  outBtnSizer = wx.StaticBoxSizer(self.outputBox, wx.HORIZONTAL)
291  cmdBtnSizer = wx.StaticBoxSizer(self.cmdBox, wx.HORIZONTAL)
292 
293  if self.cmdPrompt.IsShown():
294  promptSizer = wx.BoxSizer(wx.VERTICAL)
295  promptSizer.Add(item = self.cmdPrompt, proportion = 1,
296  flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border = 3)
297 
298  if self.search and self.search.IsShown():
299  outputSizer.Add(item = self.searchPane, proportion = 0,
300  flag = wx.EXPAND | wx.ALL, border = 3)
301  outputSizer.Add(item = self.cmdOutput, proportion = 1,
302  flag = wx.EXPAND | wx.ALL, border = 3)
303  outputSizer.Add(item = self.progressbar, proportion = 0,
304  flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = 3)
305  outBtnSizer.Add(item = self.btnOutputClear, proportion = 1,
306  flag = wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT, border = 5)
307  outBtnSizer.Add(item = self.btnOutputSave, proportion = 1,
308  flag = wx.ALIGN_RIGHT | wx.RIGHT, border = 5)
309 
310  cmdBtnSizer.Add(item = self.btnCmdProtocol, proportion = 1,
311  flag = wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT, border = 5)
312  cmdBtnSizer.Add(item = self.btnCmdClear, proportion = 1,
313  flag = wx.ALIGN_CENTER | wx.RIGHT, border = 5)
314  cmdBtnSizer.Add(item = self.btnCmdAbort, proportion = 1,
315  flag = wx.ALIGN_CENTER | wx.RIGHT, border = 5)
316 
317  if self.parent.GetName() != 'LayerManager':
318  proportion = (1, 1)
319  else:
320  proportion = (2, 3)
321 
322  btnSizer.Add(item = outBtnSizer, proportion = proportion[0],
323  flag = wx.ALL | wx.ALIGN_CENTER, border = 5)
324  btnSizer.Add(item = cmdBtnSizer, proportion = proportion[1],
325  flag = wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM | wx.RIGHT, border = 5)
326  outputSizer.Add(item = btnSizer, proportion = 0,
327  flag = wx.EXPAND)
328 
329  outputSizer.Fit(self)
330  outputSizer.SetSizeHints(self)
331  self.panelOutput.SetSizer(outputSizer)
332 
333  if self.cmdPrompt.IsShown():
334  promptSizer.Fit(self)
335  promptSizer.SetSizeHints(self)
336  self.panelPrompt.SetSizer(promptSizer)
337 
338  # split window
339  if self.cmdPrompt.IsShown():
340  self.SplitHorizontally(self.panelOutput, self.panelPrompt, -50)
341  else:
342  self.SplitHorizontally(self.panelOutput, self.panelPrompt, -45)
343  self.Unsplit()
344  self.SetMinimumPaneSize(self.btnCmdClear.GetSize()[1] + 25)
345 
346  self.SetSashGravity(1.0)
347 
348  # layout
349  self.SetAutoLayout(True)
350  self.Layout()
351 
352  def MakeSearchPaneContent(self, pane):
353  """!Create search pane"""
354  border = wx.BoxSizer(wx.VERTICAL)
355 
356  self.search = SearchModuleWindow(parent = pane, cmdPrompt = self.cmdPrompt)
357 
358  border.Add(item = self.search, proportion = 0,
359  flag = wx.EXPAND | wx.ALL, border = 1)
360 
361  pane.SetSizer(border)
362  border.Fit(pane)
363 
364  def OnSearchPaneChanged(self, event):
365  """!Collapse search module box"""
366  if self.searchPane.IsExpanded():
367  self.searchPane.SetLabel(self.infoCollapseLabelCol)
368  else:
369  self.searchPane.SetLabel(self.infoCollapseLabelExp)
370 
371  self.panelOutput.Layout()
372  self.panelOutput.SendSizeEvent()
373 
374  def GetPanel(self, prompt = True):
375  """!Get panel
376 
377  @param prompt get prompt / output panel
378 
379  @return wx.Panel reference
380  """
381  if prompt:
382  return self.panelPrompt
383 
384  return self.panelOutput
385 
386  def Redirect(self):
387  """!Redirect stdout/stderr
388  """
389  if Debug.GetLevel() == 0 and int(grass.gisenv().get('DEBUG', 0)) == 0:
390  # don't redirect when debugging is enabled
391  sys.stdout = self.cmdStdOut
392  sys.stderr = self.cmdStdErr
393  else:
394  enc = locale.getdefaultlocale()[1]
395  if enc:
396  sys.stdout = codecs.getwriter(enc)(sys.__stdout__)
397  sys.stderr = codecs.getwriter(enc)(sys.__stderr__)
398  else:
399  sys.stdout = sys.__stdout__
400  sys.stderr = sys.__stderr__
401 
402  def WriteLog(self, text, style = None, wrap = None,
403  switchPage = False):
404  """!Generic method for writing log message in
405  given style
406 
407  @param line text line
408  @param style text style (see GMStc)
409  @param stdout write to stdout or stderr
410  """
411 
412  self.cmdOutput.SetStyle()
413 
414  if switchPage:
415  self._notebook.SetSelectionByName('output')
416 
417  if not style:
418  style = self.cmdOutput.StyleDefault
419 
420  # p1 = self.cmdOutput.GetCurrentPos()
421  p1 = self.cmdOutput.GetEndStyled()
422  # self.cmdOutput.GotoPos(p1)
423  self.cmdOutput.DocumentEnd()
424 
425  for line in text.splitlines():
426  # fill space
427  if len(line) < self.lineWidth:
428  diff = self.lineWidth - len(line)
429  line += diff * ' '
430 
431  self.cmdOutput.AddTextWrapped(line, wrap = wrap) # adds '\n'
432 
433  p2 = self.cmdOutput.GetCurrentPos()
434 
435  self.cmdOutput.StartStyling(p1, 0xff)
436  self.cmdOutput.SetStyling(p2 - p1, style)
437 
438  self.cmdOutput.EnsureCaretVisible()
439 
440  def WriteCmdLog(self, line, pid = None, switchPage = True):
441  """!Write message in selected style"""
442  if pid:
443  line = '(' + str(pid) + ') ' + line
444 
445  self.WriteLog(line, style = self.cmdOutput.StyleCommand, switchPage = switchPage)
446 
447  def WriteWarning(self, line):
448  """!Write message in warning style"""
449  self.WriteLog(line, style = self.cmdOutput.StyleWarning, switchPage = True)
450 
451  def WriteError(self, line):
452  """!Write message in error style"""
453  self.WriteLog(line, style = self.cmdOutput.StyleError, switchPage = True)
454 
455  def RunCmd(self, command, compReg = True, switchPage = False, skipInterface = False,
456  onDone = None, onPrepare = None, userData = None):
457  """!Run command typed into console command prompt (GPrompt).
458 
459  @todo Display commands (*.d) are captured and processed
460  separately by mapdisp.py. Display commands are rendered in map
461  display widget that currently has the focus (as indicted by
462  mdidx).
463 
464  @param command command given as a list (produced e.g. by utils.split())
465  @param compReg True use computation region
466  @param switchPage switch to output page
467  @param skipInterface True to do not launch GRASS interface
468  parser when command has no arguments given
469  @param onDone function to be called when command is finished
470  @param onPrepare function to be called before command is launched
471  @param userData data defined for the command
472  """
473  if len(command) == 0:
474  Debug.msg(2, "GPrompt:RunCmd(): empty command")
475  return
476 
477  # update history file
478  env = grass.gisenv()
479  try:
480  filePath = os.path.join(env['GISDBASE'],
481  env['LOCATION_NAME'],
482  env['MAPSET'],
483  '.bash_history')
484  fileHistory = codecs.open(filePath, encoding = 'utf-8', mode = 'a')
485  except IOError, e:
486  GError(_("Unable to write file '%(filePath)s'.\n\nDetails: %(error)s") %
487  {'filePath': filePath, 'error' : e },
488  parent = self.parent)
489  fileHistory = None
490 
491  if fileHistory:
492  try:
493  fileHistory.write(' '.join(command) + os.linesep)
494  finally:
495  fileHistory.close()
496 
497  if command[0] in globalvar.grassCmd:
498  # send GRASS command without arguments to GUI command interface
499  # except display commands (they are handled differently)
500  if self.parent.GetName() == "LayerManager" and \
501  command[0][0:2] == "d." and \
502  'help' not in ' '.join(command[1:]):
503  # display GRASS commands
504  try:
505  layertype = {'d.rast' : 'raster',
506  'd.rast3d' : '3d-raster',
507  'd.rgb' : 'rgb',
508  'd.his' : 'his',
509  'd.shadedmap' : 'shaded',
510  'd.legend' : 'rastleg',
511  'd.rast.arrow' : 'rastarrow',
512  'd.rast.num' : 'rastnum',
513  'd.vect' : 'vector',
514  'd.vect.thematic': 'thememap',
515  'd.vect.chart' : 'themechart',
516  'd.grid' : 'grid',
517  'd.geodesic' : 'geodesic',
518  'd.rhumbline' : 'rhumb',
519  'd.labels' : 'labels',
520  'd.barscale' : 'barscale',
521  'd.redraw' : 'redraw'}[command[0]]
522  except KeyError:
523  GMessage(parent = self.parent,
524  message = _("Command '%s' not yet implemented in the WxGUI. "
525  "Try adding it as a command layer instead.") % command[0])
526  return
527 
528  if layertype == 'barscale':
529  if len(command) > 1:
530  self.parent.curr_page.maptree.GetMapDisplay().AddBarscale(cmd = command, showDialog = False)
531  else:
532  self.parent.curr_page.maptree.GetMapDisplay().AddBarscale(showDialog = True)
533  elif layertype == 'rastleg':
534  if len(command) > 1:
535  self.parent.curr_page.maptree.GetMapDisplay().AddLegend(cmd = command, showDialog = False)
536  else:
537  self.parent.curr_page.maptree.GetMapDisplay().AddLegend(showDialog = True)
538  elif layertype == 'redraw':
539  self.parent.curr_page.maptree.GetMapDisplay().OnRender(None)
540  else:
541  # add layer into layer tree
542  lname, found = utils.GetLayerNameFromCmd(command, fullyQualified = True,
543  layerType = layertype)
544  if self.parent.GetName() == "LayerManager":
545  self.parent.curr_page.maptree.AddLayer(ltype = layertype,
546  lname = lname,
547  lcmd = command)
548 
549  else:
550  # other GRASS commands (r|v|g|...)
551  hasParams = False
552  if command[0] not in ('r.mapcalc', 'r3.mapcalc'):
553  try:
554  task = GUI(show = None).ParseCommand(command)
555  except GException, e:
556  GError(parent = self,
557  message = unicode(e),
558  showTraceback = False)
559  return
560 
561  if task:
562  options = task.get_options()
563  hasParams = options['params'] and options['flags']
564  # check for <input>=-
565  for p in options['params']:
566  if p.get('prompt', '') == 'input' and \
567  p.get('element', '') == 'file' and \
568  p.get('age', 'new') == 'old' and \
569  p.get('value', '') == '-':
570  GError(parent = self,
571  message = _("Unable to run command:\n%(cmd)s\n\n"
572  "Option <%(opt)s>: read from standard input is not "
573  "supported by wxGUI") % { 'cmd': ' '.join(command),
574  'opt': p.get('name', '') })
575  return
576  else:
577  task = None
578 
579  if len(command) == 1 and hasParams and \
580  command[0] != 'v.krige.py':
581  # no arguments given
582  try:
583  GUI(parent = self).ParseCommand(command)
584  except GException, e:
585  print >> sys.stderr, e
586  return
587 
588  # switch to 'Command output' if required
589  if switchPage:
590  self._notebook.SetSelectionByName('output')
591 
592  self.parent.SetFocus()
593  self.parent.Raise()
594 
595  # activate computational region (set with g.region)
596  # for all non-display commands.
597  if compReg:
598  tmpreg = os.getenv("GRASS_REGION")
599  if "GRASS_REGION" in os.environ:
600  del os.environ["GRASS_REGION"]
601 
602  # process GRASS command with argument
603  self.cmdThread.RunCmd(command, stdout = self.cmdStdOut, stderr = self.cmdStdErr,
604  onDone = onDone, onPrepare = onPrepare, userData = userData,
605  env = os.environ.copy())
606  self.cmdOutputTimer.Start(50)
607 
608  # deactivate computational region and return to display settings
609  if compReg and tmpreg:
610  os.environ["GRASS_REGION"] = tmpreg
611  else:
612  # Send any other command to the shell. Send output to
613  # console output window
614  if len(command) == 1 and not skipInterface:
615  try:
616  task = gtask.parse_interface(command[0])
617  except:
618  task = None
619  else:
620  task = None
621 
622  if task:
623  # process GRASS command without argument
624  GUI(parent = self).ParseCommand(command)
625  else:
626  self.cmdThread.RunCmd(command, stdout = self.cmdStdOut, stderr = self.cmdStdErr,
627  onDone = onDone, onPrepare = onPrepare, userData = userData)
628  self.cmdOutputTimer.Start(50)
629 
630  def OnOutputClear(self, event):
631  """!Clear content of output window"""
632  self.cmdOutput.SetReadOnly(False)
633  self.cmdOutput.ClearAll()
634  self.cmdOutput.SetReadOnly(True)
635  self.progressbar.SetValue(0)
636 
637  def GetProgressBar(self):
638  """!Return progress bar widget"""
639  return self.progressbar
640 
641  def GetLog(self, err = False):
642  """!Get widget used for logging
643 
644  @param err True to get stderr widget
645  """
646  if err:
647  return self.cmdStdErr
648 
649  return self.cmdStdOut
650 
651  def OnOutputSave(self, event):
652  """!Save (selected) text from output window to the file"""
653  text = self.cmdOutput.GetSelectedText()
654  if not text:
655  text = self.cmdOutput.GetText()
656 
657  # add newline if needed
658  if len(text) > 0 and text[-1] != '\n':
659  text += '\n'
660 
661  dlg = wx.FileDialog(self, message = _("Save file as..."),
662  defaultFile = "grass_cmd_output.txt",
663  wildcard = _("%(txt)s (*.txt)|*.txt|%(files)s (*)|*") %
664  {'txt': _("Text files"), 'files': _("Files")},
665  style = wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
666 
667  # Show the dialog and retrieve the user response. If it is the OK response,
668  # process the data.
669  if dlg.ShowModal() == wx.ID_OK:
670  path = dlg.GetPath()
671 
672  try:
673  output = open(path, "w")
674  output.write(text)
675  except IOError, e:
676  GError(_("Unable to write file '%(path)s'.\n\nDetails: %(error)s") % {'path': path, 'error': e})
677  finally:
678  output.close()
679  message = _("Command output saved into '%s'") % path
680  if hasattr(self.parent, 'SetStatusText'):
681  self.parent.SetStatusText(message)
682  else:
683  self.parent.parent.SetStatusText(message)
684 
685  dlg.Destroy()
686 
687  def GetCmd(self):
688  """!Get running command or None"""
689  return self.requestQ.get()
690 
691  def SetCopyingOfSelectedText(self, copy):
692  """!Enable or disable copying of selected text in to clipboard.
693  Effects prompt and output.
694 
695  @param copy True for enable, False for disable
696  """
697  if copy:
698  self.cmdPrompt.Bind(stc.EVT_STC_PAINTED, self.cmdPrompt.OnTextSelectionChanged)
699  self.cmdOutput.Bind(stc.EVT_STC_PAINTED, self.cmdOutput.OnTextSelectionChanged)
700  else:
701  self.cmdPrompt.Unbind(stc.EVT_STC_PAINTED)
702  self.cmdOutput.Unbind(stc.EVT_STC_PAINTED)
703 
704  def OnUpdateStatusBar(self, event):
705  """!Update statusbar text"""
706  if event.GetString():
707  nItems = len(self.cmdPrompt.GetCommandItems())
708  self.parent.SetStatusText(_('%d modules match') % nItems, 0)
709  else:
710  self.parent.SetStatusText('', 0)
711 
712  event.Skip()
713 
714  def OnCmdOutput(self, event):
715  """!Print command output"""
716  message = event.text
717  type = event.type
718  if self._notebook.GetSelection() != self._notebook.GetPageIndexByName('output'):
719  page = self._notebook.GetPageIndexByName('output')
720  textP = self._notebook.GetPageText(page)
721  if textP[-1] != ')':
722  textP += ' (...)'
723  self._notebook.SetPageText(page, textP)
724 
725  # message prefix
726  if type == 'warning':
727  message = 'WARNING: ' + message
728  elif type == 'error':
729  message = 'ERROR: ' + message
730 
731  p1 = self.cmdOutput.GetEndStyled()
732  self.cmdOutput.GotoPos(p1)
733 
734  if '\b' in message:
735  if self.linePos < 0:
736  self.linePos = p1
737  last_c = ''
738  for c in message:
739  if c == '\b':
740  self.linePos -= 1
741  else:
742  if c == '\r':
743  pos = self.cmdOutput.GetCurLine()[1]
744  # self.cmdOutput.SetCurrentPos(pos)
745  else:
746  self.cmdOutput.SetCurrentPos(self.linePos)
747  self.cmdOutput.ReplaceSelection(c)
748  self.linePos = self.cmdOutput.GetCurrentPos()
749  if c != ' ':
750  last_c = c
751  if last_c not in ('0123456789'):
752  self.cmdOutput.AddTextWrapped('\n', wrap = None)
753  self.linePos = -1
754  else:
755  self.linePos = -1 # don't force position
756  if '\n' not in message:
757  self.cmdOutput.AddTextWrapped(message, wrap = 60)
758  else:
759  self.cmdOutput.AddTextWrapped(message, wrap = None)
760 
761  p2 = self.cmdOutput.GetCurrentPos()
762 
763  if p2 >= p1:
764  self.cmdOutput.StartStyling(p1, 0xff)
765 
766  if type == 'error':
767  self.cmdOutput.SetStyling(p2 - p1, self.cmdOutput.StyleError)
768  elif type == 'warning':
769  self.cmdOutput.SetStyling(p2 - p1, self.cmdOutput.StyleWarning)
770  elif type == 'message':
771  self.cmdOutput.SetStyling(p2 - p1, self.cmdOutput.StyleMessage)
772  else: # unknown
773  self.cmdOutput.SetStyling(p2 - p1, self.cmdOutput.StyleUnknown)
774 
775  self.cmdOutput.EnsureCaretVisible()
776 
777  def OnCmdProgress(self, event):
778  """!Update progress message info"""
779  self.progressbar.SetValue(event.value)
780 
781  def CmdProtocolSave(self):
782  """Save list of manually entered commands into a text log file"""
783  if not hasattr(self, 'cmdFileProtocol'):
784  return # it should not happen
785 
786  try:
787  output = open(self.cmdFileProtocol, "a")
788  cmds = self.cmdPrompt.GetCommands()
789  output.write('\n'.join(cmds))
790  if len(cmds) > 0:
791  output.write('\n')
792  except IOError, e:
793  GError(_("Unable to write file '%(filePath)s'.\n\nDetails: %(error)s") %
794  {'filePath': self.cmdFileProtocol, 'error': e})
795  finally:
796  output.close()
797 
798  self.parent.SetStatusText(_("Command log saved to '%s'") % self.cmdFileProtocol)
799  del self.cmdFileProtocol
800 
801  def OnCmdProtocol(self, event = None):
802  """!Save commands into file"""
803  if not event.IsChecked():
804  # stop capturing commands, save list of commands to the
805  # protocol file
806  self.CmdProtocolSave()
807  else:
808  # start capturing commands
809  self.cmdPrompt.ClearCommands()
810  # ask for the file
811  dlg = wx.FileDialog(self, message = _("Save file as..."),
812  defaultFile = "grass_cmd_log.txt",
813  wildcard = _("%(txt)s (*.txt)|*.txt|%(files)s (*)|*") %
814  {'txt': _("Text files"), 'files': _("Files")},
815  style = wx.FD_SAVE)
816  if dlg.ShowModal() == wx.ID_OK:
817  self.cmdFileProtocol = dlg.GetPath()
818  else:
819  wx.CallAfter(self.btnCmdProtocol.SetValue, False)
820 
821  dlg.Destroy()
822 
823  event.Skip()
824 
825  def OnCmdAbort(self, event):
826  """!Abort running command"""
827  self.cmdThread.abort()
828 
829  def OnCmdRun(self, event):
830  """!Run command"""
831  if self.parent.GetName() == 'Modeler':
832  self.parent.OnCmdRun(event)
833 
834  self.WriteCmdLog('(%s)\n%s' % (str(time.ctime()), ' '.join(event.cmd)))
835  self.btnCmdAbort.Enable()
836 
837  def OnCmdPrepare(self, event):
838  """!Prepare for running command"""
839  if self.parent.GetName() == 'Modeler':
840  self.parent.OnCmdPrepare(event)
841 
842  event.Skip()
843 
844  def OnCmdDone(self, event):
845  """!Command done (or aborted)"""
846  if self.parent.GetName() == 'Modeler':
847  self.parent.OnCmdDone(event)
848 
849  # Process results here
850  try:
851  ctime = time.time() - event.time
852  if ctime < 60:
853  stime = _("%d sec") % int(ctime)
854  else:
855  mtime = int(ctime / 60)
856  stime = _("%(min)d min %(sec)d sec") % { 'min' : mtime,
857  'sec' : int(ctime - (mtime * 60)) }
858  except KeyError:
859  # stopped deamon
860  stime = _("unknown")
861 
862  if event.aborted:
863  # Thread aborted (using our convention of None return)
864  self.WriteLog(_('Please note that the data are left in inconsistent state '
865  'and may be corrupted'), self.cmdOutput.StyleWarning)
866  msg = _('Command aborted')
867  else:
868  msg = _('Command finished')
869 
870  self.WriteCmdLog('(%s) %s (%s)' % (str(time.ctime()), msg, stime))
871  self.btnCmdAbort.Enable(False)
872 
873  if event.onDone:
874  event.onDone(cmd = event.cmd, returncode = event.returncode)
875 
876  self.progressbar.SetValue(0) # reset progress bar on '0%'
877 
878  self.cmdOutputTimer.Stop()
879 
880  if event.cmd[0] == 'g.gisenv':
881  Debug.SetLevel()
882  self.Redirect()
883 
884  if self.parent.GetName() == "LayerManager":
885  self.btnCmdAbort.Enable(False)
886  if event.cmd[0] not in globalvar.grassCmd or \
887  event.cmd[0] in ('r.mapcalc', 'r3.mapcalc'):
888  return
889 
890  tree = self.parent.GetLayerTree()
891  display = None
892  if tree:
893  display = tree.GetMapDisplay()
894  if not display or not display.IsAutoRendered():
895  return
896  mapLayers = map(lambda x: x.GetName(),
897  display.GetMap().GetListOfLayers(l_type = 'raster') +
898  display.GetMap().GetListOfLayers(l_type = 'vector'))
899 
900  try:
901  task = GUI(show = None).ParseCommand(event.cmd)
902  except GException, e:
903  print >> sys.stderr, e
904  task = None
905  return
906 
907  for p in task.get_options()['params']:
908  if p.get('prompt', '') not in ('raster', 'vector'):
909  continue
910  mapName = p.get('value', '')
911  if '@' not in mapName:
912  mapName = mapName + '@' + grass.gisenv()['MAPSET']
913  if mapName in mapLayers:
914  display.GetWindow().UpdateMap(render = True)
915  return
916  elif self.parent.GetName() == 'Modeler':
917  pass
918  else: # standalone dialogs
919  dialog = self.parent.parent
920  if hasattr(self.parent.parent, "btn_abort"):
921  dialog.btn_abort.Enable(False)
922 
923  if hasattr(self.parent.parent, "btn_cancel"):
924  dialog.btn_cancel.Enable(True)
925 
926  if hasattr(self.parent.parent, "btn_clipboard"):
927  dialog.btn_clipboard.Enable(True)
928 
929  if hasattr(self.parent.parent, "btn_help"):
930  dialog.btn_help.Enable(True)
931 
932  if hasattr(self.parent.parent, "btn_run"):
933  dialog.btn_run.Enable(True)
934 
935  if event.returncode == 0 and not event.aborted:
936  try:
937  winName = self.parent.parent.parent.GetName()
938  except AttributeError:
939  winName = ''
940 
941  if winName == 'LayerManager':
942  mapTree = self.parent.parent.parent.GetLayerTree()
943  elif winName == 'LayerTree':
944  mapTree = self.parent.parent.parent
945  elif winName: # GMConsole
946  mapTree = self.parent.parent.parent.parent.GetLayerTree()
947  else:
948  mapTree = None
949 
950  cmd = dialog.notebookpanel.createCmd(ignoreErrors = True)
951  if mapTree and hasattr(dialog, "addbox") and dialog.addbox.IsChecked():
952  # add created maps into layer tree
953  for p in dialog.task.get_options()['params']:
954  prompt = p.get('prompt', '')
955  if prompt in ('raster', 'vector', '3d-raster') and \
956  p.get('age', 'old') == 'new' and \
957  p.get('value', None):
958  name, found = utils.GetLayerNameFromCmd(cmd, fullyQualified = True,
959  param = p.get('name', ''))
960 
961  if mapTree.GetMap().GetListOfLayers(l_name = name):
962  display = mapTree.GetMapDisplay()
963  if display and display.IsAutoRendered():
964  display.GetWindow().UpdateMap(render = True)
965  continue
966 
967  if prompt == 'raster':
968  lcmd = ['d.rast',
969  'map=%s' % name]
970  elif prompt == '3d-raster':
971  lcmd = ['d.rast3d',
972  'map=%s' % name]
973  else:
974  lcmd = ['d.vect',
975  'map=%s' % name]
976  mapTree.AddLayer(ltype = prompt,
977  lcmd = lcmd,
978  lname = name)
979 
980  if hasattr(dialog, "get_dcmd") and \
981  dialog.get_dcmd is None and \
982  hasattr(dialog, "closebox") and \
983  dialog.closebox.IsChecked() and \
984  (event.returncode == 0 or event.aborted):
985  self.cmdOutput.Update()
986  time.sleep(2)
987  dialog.Close()
988 
990  wx.GetApp().ProcessPendingEvents()
991 
992  def ResetFocus(self):
993  """!Reset focus"""
994  self.cmdPrompt.SetFocus()
995 
996  def GetPrompt(self):
997  """!Get prompt"""
998  return self.cmdPrompt
999 
1000 class GMStdout:
1001  """!GMConsole standard output
1002 
1003  Based on FrameOutErr.py
1004 
1005  Name: FrameOutErr.py
1006  Purpose: Redirecting stdout / stderr
1007  Author: Jean-Michel Fauth, Switzerland
1008  Copyright: (c) 2005-2007 Jean-Michel Fauth
1009  Licence: GPL
1010  """
1011  def __init__(self, parent):
1012  self.parent = parent # GMConsole
1013 
1014  def write(self, s):
1015  if len(s) == 0 or s == '\n':
1016  return
1017 
1018  for line in s.splitlines():
1019  if len(line) == 0:
1020  continue
1021 
1022  evt = wxCmdOutput(text = line + '\n',
1023  type = '')
1024  wx.PostEvent(self.parent.cmdOutput, evt)
1025 
1026 class GMStderr:
1027  """!GMConsole standard error output
1028 
1029  Based on FrameOutErr.py
1030 
1031  Name: FrameOutErr.py
1032  Purpose: Redirecting stdout / stderr
1033  Author: Jean-Michel Fauth, Switzerland
1034  Copyright: (c) 2005-2007 Jean-Michel Fauth
1035  Licence: GPL
1036  """
1037  def __init__(self, parent):
1038  self.parent = parent # GMConsole
1039 
1040  self.type = ''
1041  self.message = ''
1042  self.printMessage = False
1043 
1044  def flush(self):
1045  pass
1046 
1047  def write(self, s):
1048  if "GtkPizza" in s:
1049  return
1050 
1051  # remove/replace escape sequences '\b' or '\r' from stream
1052  progressValue = -1
1053 
1054  for line in s.splitlines():
1055  if len(line) == 0:
1056  continue
1057 
1058  if 'GRASS_INFO_PERCENT' in line:
1059  value = int(line.rsplit(':', 1)[1].strip())
1060  if value >= 0 and value < 100:
1061  progressValue = value
1062  else:
1063  progressValue = 0
1064  elif 'GRASS_INFO_MESSAGE' in line:
1065  self.type = 'message'
1066  self.message += line.split(':', 1)[1].strip() + '\n'
1067  elif 'GRASS_INFO_WARNING' in line:
1068  self.type = 'warning'
1069  self.message += line.split(':', 1)[1].strip() + '\n'
1070  elif 'GRASS_INFO_ERROR' in line:
1071  self.type = 'error'
1072  self.message += line.split(':', 1)[1].strip() + '\n'
1073  elif 'GRASS_INFO_END' in line:
1074  self.printMessage = True
1075  elif self.type == '':
1076  if len(line) == 0:
1077  continue
1078  evt = wxCmdOutput(text = line,
1079  type = '')
1080  wx.PostEvent(self.parent.cmdOutput, evt)
1081  elif len(line) > 0:
1082  self.message += line.strip() + '\n'
1083 
1084  if self.printMessage and len(self.message) > 0:
1085  evt = wxCmdOutput(text = self.message,
1086  type = self.type)
1087  wx.PostEvent(self.parent.cmdOutput, evt)
1088 
1089  self.type = ''
1090  self.message = ''
1091  self.printMessage = False
1092 
1093  # update progress message
1094  if progressValue > -1:
1095  # self.gmgauge.SetValue(progressValue)
1096  evt = wxCmdProgress(value = progressValue)
1097  wx.PostEvent(self.parent.progressbar, evt)
1098 
1099 class GMStc(stc.StyledTextCtrl):
1100  """!Styled GMConsole
1101 
1102  Based on FrameOutErr.py
1103 
1104  Name: FrameOutErr.py
1105  Purpose: Redirecting stdout / stderr
1106  Author: Jean-Michel Fauth, Switzerland
1107  Copyright: (c) 2005-2007 Jean-Michel Fauth
1108  Licence: GPL
1109  """
1110  def __init__(self, parent, id, margin = False, wrap = None):
1111  stc.StyledTextCtrl.__init__(self, parent, id)
1112  self.parent = parent
1113  self.SetUndoCollection(True)
1114  self.SetReadOnly(True)
1115 
1116  #
1117  # styles
1118  #
1119  self.SetStyle()
1120 
1121  #
1122  # line margins
1123  #
1124  # TODO print number only from cmdlog
1125  self.SetMarginWidth(1, 0)
1126  self.SetMarginWidth(2, 0)
1127  if margin:
1128  self.SetMarginType(0, stc.STC_MARGIN_NUMBER)
1129  self.SetMarginWidth(0, 30)
1130  else:
1131  self.SetMarginWidth(0, 0)
1132 
1133  #
1134  # miscellaneous
1135  #
1136  self.SetViewWhiteSpace(False)
1137  self.SetTabWidth(4)
1138  self.SetUseTabs(False)
1139  self.UsePopUp(True)
1140  self.SetSelBackground(True, "#FFFF00")
1141  self.SetUseHorizontalScrollBar(True)
1142 
1143  #
1144  # bindings
1145  #
1146  self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
1147 
1148  def OnTextSelectionChanged(self, event):
1149  """!Copy selected text to clipboard and skip event.
1150  The same function is in TextCtrlAutoComplete class (prompt.py).
1151  """
1152  self.Copy()
1153  event.Skip()
1154 
1155  def SetStyle(self):
1156  """!Set styles for styled text output windows with type face
1157  and point size selected by user (Courier New 10 is default)"""
1158 
1159  typeface = UserSettings.Get(group = 'appearance', key = 'outputfont', subkey = 'type')
1160  if typeface == "":
1161  typeface = "Courier New"
1162 
1163  typesize = UserSettings.Get(group = 'appearance', key = 'outputfont', subkey = 'size')
1164  if typesize == None or typesize <= 0:
1165  typesize = 10
1166  typesize = float(typesize)
1167 
1168  self.StyleDefault = 0
1169  self.StyleDefaultSpec = "face:%s,size:%d,fore:#000000,back:#FFFFFF" % (typeface, typesize)
1170  self.StyleCommand = 1
1171  self.StyleCommandSpec = "face:%s,size:%d,,fore:#000000,back:#bcbcbc" % (typeface, typesize)
1172  self.StyleOutput = 2
1173  self.StyleOutputSpec = "face:%s,size:%d,,fore:#000000,back:#FFFFFF" % (typeface, typesize)
1174  # fatal error
1175  self.StyleError = 3
1176  self.StyleErrorSpec = "face:%s,size:%d,,fore:#7F0000,back:#FFFFFF" % (typeface, typesize)
1177  # warning
1178  self.StyleWarning = 4
1179  self.StyleWarningSpec = "face:%s,size:%d,,fore:#0000FF,back:#FFFFFF" % (typeface, typesize)
1180  # message
1181  self.StyleMessage = 5
1182  self.StyleMessageSpec = "face:%s,size:%d,,fore:#000000,back:#FFFFFF" % (typeface, typesize)
1183  # unknown
1184  self.StyleUnknown = 6
1185  self.StyleUnknownSpec = "face:%s,size:%d,,fore:#000000,back:#FFFFFF" % (typeface, typesize)
1186 
1187  # default and clear => init
1188  self.StyleSetSpec(stc.STC_STYLE_DEFAULT, self.StyleDefaultSpec)
1189  self.StyleClearAll()
1190  self.StyleSetSpec(self.StyleCommand, self.StyleCommandSpec)
1191  self.StyleSetSpec(self.StyleOutput, self.StyleOutputSpec)
1192  self.StyleSetSpec(self.StyleError, self.StyleErrorSpec)
1193  self.StyleSetSpec(self.StyleWarning, self.StyleWarningSpec)
1194  self.StyleSetSpec(self.StyleMessage, self.StyleMessageSpec)
1195  self.StyleSetSpec(self.StyleUnknown, self.StyleUnknownSpec)
1196 
1197  def OnDestroy(self, evt):
1198  """!The clipboard contents can be preserved after
1199  the app has exited"""
1200 
1201  wx.TheClipboard.Flush()
1202  evt.Skip()
1203 
1204  def AddTextWrapped(self, txt, wrap = None):
1205  """!Add string to text area.
1206 
1207  String is wrapped and linesep is also added to the end
1208  of the string"""
1209  # allow writing to output window
1210  self.SetReadOnly(False)
1211 
1212  if wrap:
1213  txt = textwrap.fill(txt, wrap) + '\n'
1214  else:
1215  if txt[-1] != '\n':
1216  txt += '\n'
1217 
1218  if '\r' in txt:
1219  self.parent.linePos = -1
1220  for seg in txt.split('\r'):
1221  if self.parent.linePos > -1:
1222  self.SetCurrentPos(self.parent.linePos)
1223  self.ReplaceSelection(seg)
1224  else:
1225  self.parent.linePos = self.GetCurrentPos()
1226  self.AddText(seg)
1227  else:
1228  self.parent.linePos = self.GetCurrentPos()
1229  try:
1230  self.AddText(txt)
1231  except UnicodeDecodeError:
1232  enc = UserSettings.Get(group = 'atm', key = 'encoding', subkey = 'value')
1233  if enc:
1234  txt = unicode(txt, enc)
1235  elif 'GRASS_DB_ENCODING' in os.environ:
1236  txt = unicode(txt, os.environ['GRASS_DB_ENCODING'])
1237  else:
1238  txt = EncodeString(txt)
1239 
1240  self.AddText(txt)
1241 
1242  # reset output window to read only
1243  self.SetReadOnly(True)
1244 
1245 class PyStc(stc.StyledTextCtrl):
1246  """!Styled Python output (see gmodeler::frame::PythonPanel for
1247  usage)
1248 
1249  Based on StyledTextCtrl_2 from wxPython demo
1250  """
1251  def __init__(self, parent, id = wx.ID_ANY, statusbar = None):
1252  stc.StyledTextCtrl.__init__(self, parent, id)
1253 
1254  self.parent = parent
1255  self.statusbar = statusbar
1256 
1257  self.modified = False # content modified ?
1258 
1259  self.faces = { 'times': 'Times New Roman',
1260  'mono' : 'Courier New',
1261  'helv' : 'Arial',
1262  'other': 'Comic Sans MS',
1263  'size' : 10,
1264  'size2': 8,
1265  }
1266 
1267  self.CmdKeyAssign(ord('B'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMIN)
1268  self.CmdKeyAssign(ord('N'), stc.STC_SCMOD_CTRL, stc.STC_CMD_ZOOMOUT)
1269 
1270  self.SetLexer(stc.STC_LEX_PYTHON)
1271  self.SetKeyWords(0, " ".join(keyword.kwlist))
1272 
1273  self.SetProperty("fold", "1")
1274  self.SetProperty("tab.timmy.whinge.level", "1")
1275  self.SetMargins(0, 0)
1276  self.SetTabWidth(4)
1277  self.SetUseTabs(False)
1278 
1279  self.SetEdgeMode(stc.STC_EDGE_BACKGROUND)
1280  self.SetEdgeColumn(78)
1281 
1282  # setup a margin to hold fold markers
1283  self.SetMarginType(2, stc.STC_MARGIN_SYMBOL)
1284  self.SetMarginMask(2, stc.STC_MASK_FOLDERS)
1285  self.SetMarginSensitive(2, True)
1286  self.SetMarginWidth(2, 12)
1287 
1288  # like a flattened tree control using square headers
1289  self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPEN, stc.STC_MARK_BOXMINUS, "white", "#808080")
1290  self.MarkerDefine(stc.STC_MARKNUM_FOLDER, stc.STC_MARK_BOXPLUS, "white", "#808080")
1291  self.MarkerDefine(stc.STC_MARKNUM_FOLDERSUB, stc.STC_MARK_VLINE, "white", "#808080")
1292  self.MarkerDefine(stc.STC_MARKNUM_FOLDERTAIL, stc.STC_MARK_LCORNER, "white", "#808080")
1293  self.MarkerDefine(stc.STC_MARKNUM_FOLDEREND, stc.STC_MARK_BOXPLUSCONNECTED, "white", "#808080")
1294  self.MarkerDefine(stc.STC_MARKNUM_FOLDEROPENMID, stc.STC_MARK_BOXMINUSCONNECTED, "white", "#808080")
1295  self.MarkerDefine(stc.STC_MARKNUM_FOLDERMIDTAIL, stc.STC_MARK_TCORNER, "white", "#808080")
1296 
1297  self.Bind(stc.EVT_STC_UPDATEUI, self.OnUpdateUI)
1298  self.Bind(stc.EVT_STC_MARGINCLICK, self.OnMarginClick)
1299  self.Bind(wx.EVT_KEY_DOWN, self.OnKeyPressed)
1300 
1301  # Make some styles, the lexer defines what each style is used
1302  # for, we just have to define what each style looks like.
1303  # This set is adapted from Scintilla sample property files.
1304 
1305  # global default styles for all languages
1306  self.StyleSetSpec(stc.STC_STYLE_DEFAULT, "face:%(helv)s,size:%(size)d" % self.faces)
1307  self.StyleClearAll() # reset all to be like the default
1308 
1309  # global default styles for all languages
1310  self.StyleSetSpec(stc.STC_STYLE_DEFAULT, "face:%(helv)s,size:%(size)d" % self.faces)
1311  self.StyleSetSpec(stc.STC_STYLE_LINENUMBER, "back:#C0C0C0,face:%(helv)s,size:%(size2)d" % self.faces)
1312  self.StyleSetSpec(stc.STC_STYLE_CONTROLCHAR, "face:%(other)s" % self.faces)
1313  self.StyleSetSpec(stc.STC_STYLE_BRACELIGHT, "fore:#FFFFFF,back:#0000FF,bold")
1314  self.StyleSetSpec(stc.STC_STYLE_BRACEBAD, "fore:#000000,back:#FF0000,bold")
1315 
1316  # Python styles
1317  # Default
1318  self.StyleSetSpec(stc.STC_P_DEFAULT, "fore:#000000,face:%(helv)s,size:%(size)d" % self.faces)
1319  # Comments
1320  self.StyleSetSpec(stc.STC_P_COMMENTLINE, "fore:#007F00,face:%(other)s,size:%(size)d" % self.faces)
1321  # Number
1322  self.StyleSetSpec(stc.STC_P_NUMBER, "fore:#007F7F,size:%(size)d" % self.faces)
1323  # String
1324  self.StyleSetSpec(stc.STC_P_STRING, "fore:#7F007F,face:%(helv)s,size:%(size)d" % self.faces)
1325  # Single quoted string
1326  self.StyleSetSpec(stc.STC_P_CHARACTER, "fore:#7F007F,face:%(helv)s,size:%(size)d" % self.faces)
1327  # Keyword
1328  self.StyleSetSpec(stc.STC_P_WORD, "fore:#00007F,bold,size:%(size)d" % self.faces)
1329  # Triple quotes
1330  self.StyleSetSpec(stc.STC_P_TRIPLE, "fore:#7F0000,size:%(size)d" % self.faces)
1331  # Triple double quotes
1332  self.StyleSetSpec(stc.STC_P_TRIPLEDOUBLE, "fore:#7F0000,size:%(size)d" % self.faces)
1333  # Class name definition
1334  self.StyleSetSpec(stc.STC_P_CLASSNAME, "fore:#0000FF,bold,underline,size:%(size)d" % self.faces)
1335  # Function or method name definition
1336  self.StyleSetSpec(stc.STC_P_DEFNAME, "fore:#007F7F,bold,size:%(size)d" % self.faces)
1337  # Operators
1338  self.StyleSetSpec(stc.STC_P_OPERATOR, "bold,size:%(size)d" % self.faces)
1339  # Identifiers
1340  self.StyleSetSpec(stc.STC_P_IDENTIFIER, "fore:#000000,face:%(helv)s,size:%(size)d" % self.faces)
1341  # Comment-blocks
1342  self.StyleSetSpec(stc.STC_P_COMMENTBLOCK, "fore:#7F7F7F,size:%(size)d" % self.faces)
1343  # End of line where string is not closed
1344  self.StyleSetSpec(stc.STC_P_STRINGEOL, "fore:#000000,face:%(mono)s,back:#E0C0E0,eol,size:%(size)d" % self.faces)
1345 
1346  self.SetCaretForeground("BLUE")
1347 
1348  def OnKeyPressed(self, event):
1349  """!Key pressed
1350 
1351  @todo implement code completion (see wxPython demo)
1352  """
1353  if not self.modified:
1354  self.modified = True
1355  if self.statusbar:
1356  self.statusbar.SetStatusText(_('Python script contains local modifications'), 0)
1357 
1358  event.Skip()
1359 
1360  def OnUpdateUI(self, evt):
1361  # check for matching braces
1362  braceAtCaret = -1
1363  braceOpposite = -1
1364  charBefore = None
1365  caretPos = self.GetCurrentPos()
1366 
1367  if caretPos > 0:
1368  charBefore = self.GetCharAt(caretPos - 1)
1369  styleBefore = self.GetStyleAt(caretPos - 1)
1370 
1371  # check before
1372  if charBefore and chr(charBefore) in "[]{}()" and styleBefore == stc.STC_P_OPERATOR:
1373  braceAtCaret = caretPos - 1
1374 
1375  # check after
1376  if braceAtCaret < 0:
1377  charAfter = self.GetCharAt(caretPos)
1378  styleAfter = self.GetStyleAt(caretPos)
1379 
1380  if charAfter and chr(charAfter) in "[]{}()" and styleAfter == stc.STC_P_OPERATOR:
1381  braceAtCaret = caretPos
1382 
1383  if braceAtCaret >= 0:
1384  braceOpposite = self.BraceMatch(braceAtCaret)
1385 
1386  if braceAtCaret != -1 and braceOpposite == -1:
1387  self.BraceBadLight(braceAtCaret)
1388  else:
1389  self.BraceHighlight(braceAtCaret, braceOpposite)
1390 
1391  def OnMarginClick(self, evt):
1392  # fold and unfold as needed
1393  if evt.GetMargin() == 2:
1394  if evt.GetShift() and evt.GetControl():
1395  self.FoldAll()
1396  else:
1397  lineClicked = self.LineFromPosition(evt.GetPosition())
1398 
1399  if self.GetFoldLevel(lineClicked) & stc.STC_FOLDLEVELHEADERFLAG:
1400  if evt.GetShift():
1401  self.SetFoldExpanded(lineClicked, True)
1402  self.Expand(lineClicked, True, True, 1)
1403  elif evt.GetControl():
1404  if self.GetFoldExpanded(lineClicked):
1405  self.SetFoldExpanded(lineClicked, False)
1406  self.Expand(lineClicked, False, True, 0)
1407  else:
1408  self.SetFoldExpanded(lineClicked, True)
1409  self.Expand(lineClicked, True, True, 100)
1410  else:
1411  self.ToggleFold(lineClicked)
1412 
1413  def FoldAll(self):
1414  lineCount = self.GetLineCount()
1415  expanding = True
1416 
1417  # find out if we are folding or unfolding
1418  for lineNum in range(lineCount):
1419  if self.GetFoldLevel(lineNum) & stc.STC_FOLDLEVELHEADERFLAG:
1420  expanding = not self.GetFoldExpanded(lineNum)
1421  break
1422 
1423  lineNum = 0
1424  while lineNum < lineCount:
1425  level = self.GetFoldLevel(lineNum)
1426  if level & stc.STC_FOLDLEVELHEADERFLAG and \
1427  (level & stc.STC_FOLDLEVELNUMBERMASK) == stc.STC_FOLDLEVELBASE:
1428 
1429  if expanding:
1430  self.SetFoldExpanded(lineNum, True)
1431  lineNum = self.Expand(lineNum, True)
1432  lineNum = lineNum - 1
1433  else:
1434  lastChild = self.GetLastChild(lineNum, -1)
1435  self.SetFoldExpanded(lineNum, False)
1436 
1437  if lastChild > lineNum:
1438  self.HideLines(lineNum+1, lastChild)
1439 
1440  lineNum = lineNum + 1
1441 
1442  def Expand(self, line, doExpand, force=False, visLevels=0, level=-1):
1443  lastChild = self.GetLastChild(line, level)
1444  line = line + 1
1445 
1446  while line <= lastChild:
1447  if force:
1448  if visLevels > 0:
1449  self.ShowLines(line, line)
1450  else:
1451  self.HideLines(line, line)
1452  else:
1453  if doExpand:
1454  self.ShowLines(line, line)
1455 
1456  if level == -1:
1457  level = self.GetFoldLevel(line)
1458 
1459  if level & stc.STC_FOLDLEVELHEADERFLAG:
1460  if force:
1461  if visLevels > 1:
1462  self.SetFoldExpanded(line, True)
1463  else:
1464  self.SetFoldExpanded(line, False)
1465 
1466  line = self.Expand(line, doExpand, force, visLevels-1)
1467  else:
1468  if doExpand and self.GetFoldExpanded(line):
1469  line = self.Expand(line, True, force, visLevels-1)
1470  else:
1471  line = self.Expand(line, False, force, visLevels-1)
1472  else:
1473  line = line + 1
1474 
1475  return line
1476 
Styled GMConsole.
Definition: goutput.py:1099
def _layout
Do layout.
Definition: goutput.py:286
def EncodeString
Return encoded string using system locales.
Definition: gcmd.py:91
wxGUI command interface
def AddTextWrapped
Add string to text area.
Definition: goutput.py:1204
def abort
Abort command(s)
Definition: goutput.py:175
def WriteCmdLog
Write message in selected style.
Definition: goutput.py:440
def OnOutputSave
Save (selected) text from output window to the file.
Definition: goutput.py:651
def OnTextSelectionChanged
Copy selected text to clipboard and skip event.
Definition: goutput.py:1148
def OnMarginClick
Definition: goutput.py:1391
def SetCopyingOfSelectedText
Enable or disable copying of selected text in to clipboard.
Definition: goutput.py:691
def WriteWarning
Write message in warning style.
Definition: goutput.py:447
def OnCmdOutput
Print command output.
Definition: goutput.py:714
def SetId
Set starting id.
Definition: goutput.py:86
wxGUI debugging
def OnUpdateUI
Definition: goutput.py:1360
def GetPanel
Get panel.
Definition: goutput.py:374
def GetProgressBar
Return progress bar widget.
Definition: goutput.py:637
Create and manage output console for commands run by GUI.
Definition: goutput.py:183
def OnSearchPaneChanged
Collapse search module box.
Definition: goutput.py:364
def GetCmd
Get running command or None.
Definition: goutput.py:687
def GetLog
Get widget used for logging.
Definition: goutput.py:641
def OnCmdRun
Run command.
Definition: goutput.py:829
GMConsole standard output.
Definition: goutput.py:1000
wxGUI command prompt
def Redirect
Redirect stdout/stderr.
Definition: goutput.py:386
def OnCmdProgress
Update progress message info.
Definition: goutput.py:777
def RunCmd
Run command typed into console command prompt (GPrompt).
Definition: goutput.py:456
def split
Platform spefic shlex.split.
Definition: core/utils.py:37
def MakeSearchPaneContent
Create search pane.
Definition: goutput.py:352
def GetLayerNameFromCmd
Get map name from GRASS command.
Definition: core/utils.py:73
def SetStyle
Set styles for styled text output windows with type face and point size selected by user (Courier New...
Definition: goutput.py:1155
def WriteLog
Generic method for writing log message in given style.
Definition: goutput.py:403
def OnDestroy
The clipboard contents can be preserved after the app has exited.
Definition: goutput.py:1197
Styled Python output (see gmodeler::frame::PythonPanel for usage)
Definition: goutput.py:1245
def OnOutputClear
Clear content of output window.
Definition: goutput.py:630
def GetPrompt
Get prompt.
Definition: goutput.py:996
def WriteError
Write message in error style.
Definition: goutput.py:451
def OnKeyPressed
Key pressed.
Definition: goutput.py:1348
Help window.
def GrassCmd
Return GRASS command thread.
Definition: goutput.py:57
def OnUpdateStatusBar
Update statusbar text.
Definition: goutput.py:704
def OnCmdPrepare
Prepare for running command.
Definition: goutput.py:837
def ResetFocus
Reset focus.
Definition: goutput.py:992
Default GUI settings.
def OnCmdAbort
Abort running command.
Definition: goutput.py:825
Thread for GRASS commands.
Definition: goutput.py:62
def OnProcessPendingOutputWindowEvents
Definition: goutput.py:989
tuple range
Definition: tools.py:1406
def OnCmdDone
Command done (or aborted)
Definition: goutput.py:844
def OnCmdProtocol
Save commands into file.
Definition: goutput.py:801
GMConsole standard error output.
Definition: goutput.py:1026