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