GRASS Programmer's Manual  6.4.2(2012)
 All Data Structures Namespaces Files Functions Variables Typedefs Enumerations Enumerator Macros Pages
goutput.py
Go to the documentation of this file.
1 """!
2 @package goutput
3 
4 @brief Command output log widget
5 
6 Classes:
7  - GMConsole
8  - GMStc
9  - GMStdout
10  - GMStderr
11 
12 (C) 2007-2011 by the GRASS Development Team
13 This program is free software under the GNU General Public
14 License (>=v2). Read the file COPYING that comes with GRASS
15 for details.
16 
17 @author Michael Barton (Arizona State University)
18 @author Martin Landa <landa.martin gmail.com>
19 @author Vaclav Petras <wenzeslaus gmail.com> (copy&paste customization)
20 """
21 
22 import os
23 import sys
24 import textwrap
25 import time
26 import threading
27 import Queue
28 import codecs
29 import locale
30 
31 import wx
32 import wx.stc
33 from wx.lib.newevent import NewEvent
34 
35 import grass.script as grass
36 from grass.script import task as gtask
37 
38 import globalvar
39 import gcmd
40 import utils
41 import preferences
42 import menuform
43 import prompt
44 
45 from debug import Debug
46 from preferences import globalSettings as UserSettings
47 from ghelp import SearchModuleWindow
48 
49 wxCmdOutput, EVT_CMD_OUTPUT = NewEvent()
50 wxCmdProgress, EVT_CMD_PROGRESS = NewEvent()
51 wxCmdRun, EVT_CMD_RUN = NewEvent()
52 wxCmdDone, EVT_CMD_DONE = NewEvent()
53 wxCmdAbort, EVT_CMD_ABORT = NewEvent()
54 
55 def GrassCmd(cmd, stdout = None, stderr = None):
56  """!Return GRASS command thread"""
57  return gcmd.CommandThread(cmd,
58  stdout = stdout, stderr = stderr)
59 
60 class CmdThread(threading.Thread):
61  """!Thread for GRASS commands"""
62  requestId = 0
63  def __init__(self, parent, requestQ, resultQ, **kwds):
64  threading.Thread.__init__(self, **kwds)
65 
66  self.setDaemon(True)
67 
68  self.parent = parent # GMConsole
69  self._want_abort_all = False
70 
71  self.requestQ = requestQ
72  self.resultQ = resultQ
73 
74  self.start()
75 
76  def RunCmd(self, *args, **kwds):
77  CmdThread.requestId += 1
78 
79  self.requestCmd = None
80  self.requestQ.put((CmdThread.requestId, args, kwds))
81 
82  return CmdThread.requestId
83 
84  def SetId(self, id):
85  """!Set starting id"""
86  CmdThread.requestId = id
87 
88  def run(self):
89  os.environ['GRASS_MESSAGE_FORMAT'] = 'gui'
90  while True:
91  requestId, args, kwds = self.requestQ.get()
92  for key in ('callable', 'onDone', 'userData'):
93  if key in kwds:
94  vars()[key] = kwds[key]
95  del kwds[key]
96  else:
97  vars()[key] = None
98 
99  if not vars()['callable']:
100  vars()['callable'] = GrassCmd
101 
102  requestTime = time.time()
103  event = wxCmdRun(cmd = args[0],
104  pid = requestId)
105  wx.PostEvent(self.parent, event)
106 
107  time.sleep(.1)
108  self.requestCmd = vars()['callable'](*args, **kwds)
109  if self._want_abort_all:
110  self.requestCmd.abort()
111  if self.requestQ.empty():
112  self._want_abort_all = False
113 
114  self.resultQ.put((requestId, self.requestCmd.run()))
115 
116  try:
117  returncode = self.requestCmd.module.returncode
118  except AttributeError:
119  returncode = 0 # being optimistic
120 
121  try:
122  aborted = self.requestCmd.aborted
123  except AttributeError:
124  aborted = False
125 
126  time.sleep(.1)
127 
128  # set default color table for raster data
129  if UserSettings.Get(group='cmd', key='rasterColorTable', subkey='enabled') and \
130  args[0][0][:2] == 'r.':
131  colorTable = UserSettings.Get(group='cmd', key='rasterColorTable', subkey='selection')
132  mapName = None
133  if args[0][0] == 'r.mapcalc':
134  try:
135  mapName = args[0][1].split('=', 1)[0].strip()
136  except KeyError:
137  pass
138  else:
139  moduleInterface = menuform.GUI(show = None).ParseCommand(args[0])
140  outputParam = moduleInterface.get_param(value = 'output', raiseError = False)
141  if outputParam and outputParam['prompt'] == 'raster':
142  mapName = outputParam['value']
143 
144  if mapName:
145  argsColor = list(args)
146  argsColor[0] = [ 'r.colors',
147  'map=%s' % mapName,
148  'color=%s' % colorTable ]
149  self.requestCmdColor = vars()['callable'](*argsColor, **kwds)
150  self.resultQ.put((requestId, self.requestCmdColor.run()))
151 
152  event = wxCmdDone(cmd = args[0],
153  aborted = aborted,
154  returncode = returncode,
155  time = requestTime,
156  pid = requestId,
157  onDone = vars()['onDone'],
158  userData = vars()['userData'])
159 
160  # send event
161  wx.PostEvent(self.parent, event)
162 
163  def abort(self, abortall = True):
164  """!Abort command(s)"""
165  if abortall:
166  self._want_abort_all = True
167  self.requestCmd.abort()
168  if self.requestQ.empty():
169  self._want_abort_all = False
170 
171 class GMConsole(wx.SplitterWindow):
172  """!Create and manage output console for commands run by GUI.
173  """
174  def __init__(self, parent, id = wx.ID_ANY, margin = False,
175  notebook = None,
176  style = wx.TAB_TRAVERSAL | wx.FULL_REPAINT_ON_RESIZE,
177  **kwargs):
178  wx.SplitterWindow.__init__(self, parent, id, style = style, *kwargs)
179  self.SetName("GMConsole")
180 
181  self.panelOutput = wx.Panel(parent = self, id = wx.ID_ANY)
182  self.panelPrompt = wx.Panel(parent = self, id = wx.ID_ANY)
183 
184  # initialize variables
185  self.parent = parent # GMFrame | CmdPanel | ?
186  if notebook:
187  self._notebook = notebook
188  else:
189  self._notebook = self.parent.notebook
190  self.lineWidth = 80
191 
192  # remember position of line begining (used for '\r')
193  self.linePos = -1
194 
195  #
196  # create queues
197  #
198  self.requestQ = Queue.Queue()
199  self.resultQ = Queue.Queue()
200 
201  #
202  # progress bar
203  #
204  self.console_progressbar = wx.Gauge(parent=self.panelOutput, id=wx.ID_ANY,
205  range=100, pos=(110, 50), size=(-1, 25),
206  style=wx.GA_HORIZONTAL)
207  self.console_progressbar.Bind(EVT_CMD_PROGRESS, self.OnCmdProgress)
208 
209  #
210  # text control for command output
211  #
212  self.cmd_output = GMStc(parent=self.panelOutput, id=wx.ID_ANY, margin=margin,
213  wrap=None)
214  self.cmd_output_timer = wx.Timer(self.cmd_output, id=wx.ID_ANY)
215  self.cmd_output.Bind(EVT_CMD_OUTPUT, self.OnCmdOutput)
216  self.cmd_output.Bind(wx.EVT_TIMER, self.OnProcessPendingOutputWindowEvents)
217  self.Bind(EVT_CMD_RUN, self.OnCmdRun)
218  self.Bind(EVT_CMD_DONE, self.OnCmdDone)
219 
220  # search & command prompt
221  self.cmd_prompt = prompt.GPromptSTC(parent = self)
222 
223  if self.parent.GetName() != 'LayerManager':
224  self.search = None
225  self.cmd_prompt.Hide()
226  else:
227  self.infoCollapseLabelExp = _("Click here to show search module engine")
228  self.infoCollapseLabelCol = _("Click here to hide search module engine")
229  self.searchPane = wx.CollapsiblePane(parent = self.panelOutput,
230  label = self.infoCollapseLabelExp,
231  style = wx.CP_DEFAULT_STYLE |
232  wx.CP_NO_TLW_RESIZE | wx.EXPAND)
233  self.MakeSearchPaneContent(self.searchPane.GetPane())
234  self.searchPane.Collapse(True)
235  self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.OnSearchPaneChanged, self.searchPane)
236  self.search.Bind(wx.EVT_TEXT, self.OnUpdateStatusBar)
237 
238  #
239  # stream redirection
240  #
241  self.cmd_stdout = GMStdout(self)
242  self.cmd_stderr = GMStderr(self)
243 
244  #
245  # thread
246  #
247  self.cmdThread = CmdThread(self, self.requestQ, self.resultQ)
248 
249  #
250  # buttons
251  #
252  self.btn_console_clear = wx.Button(parent = self.panelPrompt, id = wx.ID_ANY,
253  label = _("&Clear output"), size=(100,-1))
254  self.btn_cmd_clear = wx.Button(parent = self.panelPrompt, id = wx.ID_ANY,
255  label = _("C&lear cmd"), size=(100,-1))
256  if self.parent.GetName() != 'LayerManager':
257  self.btn_cmd_clear.Hide()
258  self.btn_console_save = wx.Button(parent = self.panelPrompt, id = wx.ID_ANY,
259  label = _("&Save output"), size=(100,-1))
260  # abort
261  self.btn_abort = wx.Button(parent = self.panelPrompt, id = wx.ID_ANY, label = _("&Abort cmd"),
262  size=(100,-1))
263  self.btn_abort.SetToolTipString(_("Abort the running command"))
264  self.btn_abort.Enable(False)
265 
266  self.btn_cmd_clear.Bind(wx.EVT_BUTTON, self.cmd_prompt.OnCmdErase)
267  self.btn_console_clear.Bind(wx.EVT_BUTTON, self.ClearHistory)
268  self.btn_console_save.Bind(wx.EVT_BUTTON, self.SaveHistory)
269  self.btn_abort.Bind(wx.EVT_BUTTON, self.OnCmdAbort)
270  self.btn_abort.Bind(EVT_CMD_ABORT, self.OnCmdAbort)
271 
272  self.__layout()
273 
274  def __layout(self):
275  """!Do layout"""
276  OutputSizer = wx.BoxSizer(wx.VERTICAL)
277  PromptSizer = wx.BoxSizer(wx.VERTICAL)
278  ButtonSizer = wx.BoxSizer(wx.HORIZONTAL)
279 
280  if self.search and self.search.IsShown():
281  OutputSizer.Add(item=self.searchPane, proportion=0,
282  flag=wx.EXPAND | wx.ALL, border=3)
283  OutputSizer.Add(item=self.cmd_output, proportion=1,
284  flag=wx.EXPAND | wx.ALL, border=3)
285  OutputSizer.Add(item=self.console_progressbar, proportion=0,
286  flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=3)
287 
288  PromptSizer.Add(item=self.cmd_prompt, proportion=1,
289  flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border=3)
290 
291  ButtonSizer.Add(item=self.btn_console_clear, proportion=0,
292  flag=wx.ALIGN_CENTER | wx.FIXED_MINSIZE | wx.ALL, border=5)
293  ButtonSizer.Add(item=self.btn_console_save, proportion=0,
294  flag=wx.ALIGN_CENTER | wx.FIXED_MINSIZE | wx.ALL, border=5)
295  ButtonSizer.Add(item=self.btn_cmd_clear, proportion=0,
296  flag=wx.ALIGN_CENTER | wx.FIXED_MINSIZE | wx.ALL, border=5)
297  ButtonSizer.Add(item=self.btn_abort, proportion=0,
298  flag=wx.ALIGN_CENTER | wx.FIXED_MINSIZE | wx.ALL, border=5)
299  PromptSizer.Add(item=ButtonSizer, proportion=0,
300  flag=wx.ALIGN_CENTER)
301 
302  OutputSizer.Fit(self)
303  OutputSizer.SetSizeHints(self)
304 
305  PromptSizer.Fit(self)
306  PromptSizer.SetSizeHints(self)
307 
308  self.panelOutput.SetSizer(OutputSizer)
309  self.panelPrompt.SetSizer(PromptSizer)
310 
311  # split window
312  if self.parent.GetName() == 'LayerManager':
313  self.SplitHorizontally(self.panelOutput, self.panelPrompt, -50)
314  self.SetMinimumPaneSize(self.btn_cmd_clear.GetSize()[1] + 50)
315  else:
316  self.SplitHorizontally(self.panelOutput, self.panelPrompt, -45)
317  self.SetMinimumPaneSize(self.btn_cmd_clear.GetSize()[1] + 10)
318 
319  self.SetSashGravity(1.0)
320 
321  # layout
322  self.SetAutoLayout(True)
323  self.Layout()
324 
325  def MakeSearchPaneContent(self, pane):
326  """!Create search pane"""
327  border = wx.BoxSizer(wx.VERTICAL)
328 
329  self.search = SearchModuleWindow(parent = pane, cmdPrompt = self.cmd_prompt)
330 
331  border.Add(item = self.search, proportion = 0,
332  flag = wx.EXPAND | wx.ALL, border = 1)
333 
334  pane.SetSizer(border)
335  border.Fit(pane)
336 
337  def OnSearchPaneChanged(self, event):
338  """!Collapse search module box"""
339  if self.searchPane.IsExpanded():
340  self.searchPane.SetLabel(self.infoCollapseLabelCol)
341  else:
342  self.searchPane.SetLabel(self.infoCollapseLabelExp)
343 
344  self.panelOutput.Layout()
345  self.panelOutput.SendSizeEvent()
346 
347  def GetPanel(self, prompt = True):
348  """!Get panel
349 
350  @param prompt get prompt / output panel
351 
352  @return wx.Panel reference
353  """
354  if prompt:
355  return self.panelPrompt
356 
357  return self.panelOutput
358 
359  def Redirect(self):
360  """!Redirect stdout/stderr
361  """
362  if Debug.GetLevel() == 0 and int(grass.gisenv().get('DEBUG', 0)) == 0:
363  # don't redirect when debugging is enabled
364  sys.stdout = self.cmd_stdout
365  sys.stderr = self.cmd_stderr
366  else:
367  enc = locale.getdefaultlocale()[1]
368  if enc:
369  sys.stdout = codecs.getwriter(enc)(sys.__stdout__)
370  sys.stderr = codecs.getwriter(enc)(sys.__stderr__)
371  else:
372  sys.stdout = sys.__stdout__
373  sys.stderr = sys.__stderr__
374 
375  def WriteLog(self, text, style = None, wrap = None,
376  switchPage = False):
377  """!Generic method for writing log message in
378  given style
379 
380  @param line text line
381  @param style text style (see GMStc)
382  @param stdout write to stdout or stderr
383  """
384 
385  self.cmd_output.SetStyle()
386 
387  if switchPage:
388  self._notebook.SetSelectionByName('output')
389 
390  if not style:
391  style = self.cmd_output.StyleDefault
392 
393  # p1 = self.cmd_output.GetCurrentPos()
394  p1 = self.cmd_output.GetEndStyled()
395 # self.cmd_output.GotoPos(p1)
396  self.cmd_output.DocumentEnd()
397 
398  for line in text.splitlines():
399  # fill space
400  if len(line) < self.lineWidth:
401  diff = self.lineWidth - len(line)
402  line += diff * ' '
403 
404  self.cmd_output.AddTextWrapped(line, wrap=wrap) # adds '\n'
405 
406  p2 = self.cmd_output.GetCurrentPos()
407 
408  self.cmd_output.StartStyling(p1, 0xff)
409  self.cmd_output.SetStyling(p2 - p1, style)
410 
411  self.cmd_output.EnsureCaretVisible()
412 
413  def WriteCmdLog(self, line, pid = None, switchPage = True):
414  """!Write message in selected style"""
415  if pid:
416  line = '(' + str(pid) + ') ' + line
417  self.WriteLog(line, style=self.cmd_output.StyleCommand, switchPage = switchPage)
418 
419  def WriteWarning(self, line):
420  """!Write message in warning style"""
421  self.WriteLog(line, style=self.cmd_output.StyleWarning, switchPage = True)
422 
423  def WriteError(self, line):
424  """!Write message in error style"""
425  self.WriteLog(line, style = self.cmd_output.StyleError, switchPage = True)
426 
427  def RunCmd(self, command, compReg = True, switchPage = False,
428  onDone = None):
429  """!Run command typed into console command prompt (GPrompt).
430 
431  @todo Display commands (*.d) are captured and processed
432  separately by mapdisp.py. Display commands are rendered in map
433  display widget that currently has the focus (as indicted by
434  mdidx).
435 
436  @param command command given as a list (produced e.g. by utils.split())
437  @param compReg True use computation region
438  @param switchPage switch to output page
439  @param onDone function to be called when command is finished
440  """
441  if len(command) == 0:
442  Debug.msg(2, "GPrompt:RunCmd(): empty command")
443  return
444 
445  # update history file
446  env = grass.gisenv()
447  try:
448  fileHistory = codecs.open(os.path.join(env['GISDBASE'],
449  env['LOCATION_NAME'],
450  env['MAPSET'],
451  '.bash_history'),
452  encoding = 'utf-8', mode = 'a')
453  except IOError, e:
454  self.WriteError(e)
455  fileHistory = None
456 
457  if fileHistory:
458  try:
459  fileHistory.write(' '.join(command) + os.linesep)
460  finally:
461  fileHistory.close()
462 
463  # update history items
464  if self.parent.GetName() == 'LayerManager':
465  try:
466  self.parent.cmdinput.SetHistoryItems()
467  except AttributeError:
468  pass
469 
470  if command[0] in globalvar.grassCmd['all']:
471  # send GRASS command without arguments to GUI command interface
472  # except display commands (they are handled differently)
473  if self.parent.GetName() == "LayerManager" and \
474  command[0][0:2] == "d." and \
475  'help' not in ' '.join(command[1:]):
476  # display GRASS commands
477  try:
478  layertype = {'d.rast' : 'raster',
479  'd.rast3d' : '3d-raster',
480  'd.rgb' : 'rgb',
481  'd.his' : 'his',
482  'd.shaded' : 'shaded',
483  'd.legend' : 'rastleg',
484  'd.rast.arrow' : 'rastarrow',
485  'd.rast.num' : 'rastnum',
486  'd.vect' : 'vector',
487  'd.vect.thematic': 'thememap',
488  'd.vect.chart' : 'themechart',
489  'd.grid' : 'grid',
490  'd.geodesic' : 'geodesic',
491  'd.rhumbline' : 'rhumb',
492  'd.labels' : 'labels',
493  'd.barscale' : 'barscale',
494  'd.redraw' : 'redraw'}[command[0]]
495  except KeyError:
496  gcmd.GMessage(parent = self.parent,
497  message = _("Command '%s' not yet implemented in the WxGUI. "
498  "Try adding it as a command layer instead.") % command[0])
499  return None
500 
501  if layertype == 'barscale':
502  self.parent.curr_page.maptree.GetMapDisplay().OnAddBarscale(None)
503  elif layertype == 'rastleg':
504  self.parent.curr_page.maptree.GetMapDisplay().OnAddLegend(None)
505  elif layertype == 'redraw':
506  self.parent.curr_page.maptree.GetMapDisplay().OnRender(None)
507  else:
508  # add layer into layer tree
509  lname, found = utils.GetLayerNameFromCmd(command, fullyQualified = True,
510  layerType = layertype)
511  if self.parent.GetName() == "LayerManager":
512  self.parent.curr_page.maptree.AddLayer(ltype = layertype,
513  lname = lname,
514  lcmd = command)
515 
516  else:
517  # other GRASS commands (r|v|g|...)
518  if sys.platform == 'win32':
519  if command[0] in globalvar.grassCmd['script']:
520  command[0] += globalvar.EXT_SCT
521  hasParams = False
522  if command[0] != 'r.mapcalc':
523  task = menuform.GUI(show = None).ParseCommand(command)
524  if task:
525  options = task.get_options()
526  hasParams = options['params'] and options['flags']
527  # check for <input>=-
528  for p in options['params']:
529  if p.get('prompt', '') == 'input' and \
530  p.get('element', '') == 'file' and \
531  p.get('age', 'new') == 'old_file' and \
532  p.get('value', '') == '-':
533  gcmd.GError(parent = self,
534  message = _("Unable to run command:\n%(cmd)s\n\n"
535  "Option <%(opt)s>: read from standard input is not "
536  "supported by wxGUI") % { 'cmd': ' '.join(command),
537  'opt': p.get('name', '') })
538  return None
539  else:
540  task = None
541 
542  if len(command) == 1 and hasParams:
543  # no arguments given
544  try:
545  menuform.GUI(parent = self).ParseCommand(command)
546  except gcmd.GException, e:
547  print >> sys.stderr, e
548  return 0
549 
550  # switch to 'Command output' if required
551  if switchPage:
552  self._notebook.SetSelectionByName('output')
553 
554  self.parent.SetFocus()
555  self.parent.Raise()
556 
557  # activate computational region (set with g.region)
558  # for all non-display commands.
559  if compReg:
560  tmpreg = os.getenv("GRASS_REGION")
561  if "GRASS_REGION" in os.environ:
562  del os.environ["GRASS_REGION"]
563 
564  # process GRASS command with argument
565  self.cmdThread.RunCmd(command, stdout = self.cmd_stdout, stderr = self.cmd_stderr,
566  onDone = onDone)
567  self.cmd_output_timer.Start(50)
568 
569  # deactivate computational region and return to display settings
570  if compReg and tmpreg:
571  os.environ["GRASS_REGION"] = tmpreg
572  else:
573  # Send any other command to the shell. Send output to
574  # console output window
575  if len(command) == 1:
576  try:
577  task = gtask.parse_interface(command[0])
578  except:
579  task = None
580  else:
581  task = None
582 
583  if task:
584  # process GRASS command without argument
585  menuform.GUI(parent = self).ParseCommand(command)
586  else:
587  self.cmdThread.RunCmd(command, stdout = self.cmd_stdout, stderr = self.cmd_stderr,
588  onDone = onDone)
589  self.cmd_output_timer.Start(50)
590 
591  return None
592 
593  def ClearHistory(self, event):
594  """!Clear history of commands"""
595  self.cmd_output.SetReadOnly(False)
596  self.cmd_output.ClearAll()
597  self.cmd_output.SetReadOnly(True)
598  self.console_progressbar.SetValue(0)
599 
600  def GetProgressBar(self):
601  """!Return progress bar widget"""
602  return self.console_progressbar
603 
604  def GetLog(self, err = False):
605  """!Get widget used for logging
606 
607  @param err True to get stderr widget
608  """
609  if err:
610  return self.cmd_stderr
611 
612  return self.cmd_stdout
613 
614  def SaveHistory(self, event):
615  """!Save history of commands"""
616  self.history = self.cmd_output.GetSelectedText()
617  if self.history == '':
618  self.history = self.cmd_output.GetText()
619 
620  # add newline if needed
621  if len(self.history) > 0 and self.history[-1] != '\n':
622  self.history += '\n'
623 
624  wildcard = "Text file (*.txt)|*.txt"
625  dlg = wx.FileDialog(self, message = _("Save file as..."), defaultDir = os.getcwd(),
626  defaultFile = "grass_cmd_history.txt", wildcard = wildcard,
627  style = wx.SAVE | wx.FD_OVERWRITE_PROMPT)
628 
629  # Show the dialog and retrieve the user response. If it is the OK response,
630  # process the data.
631  if dlg.ShowModal() == wx.ID_OK:
632  path = dlg.GetPath()
633 
634  output = open(path, "w")
635  output.write(self.history)
636  output.close()
637 
638  dlg.Destroy()
639 
640  def GetCmd(self):
641  """!Get running command or None"""
642  return self.requestQ.get()
643 
644  def SetCopyingOfSelectedText(self, copy):
645  """!Enable or disable copying of selected text in to clipboard.
646  Effects prompt and output.
647 
648  @param copy True for enable, False for disable
649  """
650  if copy:
651  self.cmd_prompt.Bind(wx.stc.EVT_STC_PAINTED, self.cmd_prompt.OnTextSelectionChanged)
652  self.cmd_output.Bind(wx.stc.EVT_STC_PAINTED, self.cmd_output.OnTextSelectionChanged)
653  else:
654  self.cmd_prompt.Unbind(wx.stc.EVT_STC_PAINTED)
655  self.cmd_output.Unbind(wx.stc.EVT_STC_PAINTED)
656 
657  def OnUpdateStatusBar(self, event):
658  """!Update statusbar text"""
659  if event.GetString():
660  nItems = len(self.cmd_prompt.GetCommandItems())
661  self.parent.SetStatusText(_('%d modules match') % nItems, 0)
662  else:
663  self.parent.SetStatusText('', 0)
664 
665  event.Skip()
666 
667  def OnCmdOutput(self, event):
668  """!Print command output"""
669  message = event.text
670  type = event.type
671  if self._notebook.GetSelection() != self._notebook.GetPageIndexByName('output'):
672  page = self._notebook.GetPageIndexByName('output')
673  textP = self._notebook.GetPageText(page)
674  if textP[-1] != ')':
675  textP += ' (...)'
676  self._notebook.SetPageText(page, textP)
677 
678  # message prefix
679  if type == 'warning':
680  messege = 'WARNING: ' + message
681  elif type == 'error':
682  message = 'ERROR: ' + message
683 
684  p1 = self.cmd_output.GetEndStyled()
685  self.cmd_output.GotoPos(p1)
686 
687  if '\b' in message:
688  if self.linepos < 0:
689  self.linepos = p1
690  last_c = ''
691  for c in message:
692  if c == '\b':
693  self.linepos -= 1
694  else:
695  if c == '\r':
696  pos = self.cmd_output.GetCurLine()[1]
697  # self.cmd_output.SetCurrentPos(pos)
698  else:
699  self.cmd_output.SetCurrentPos(self.linepos)
700  self.cmd_output.ReplaceSelection(c)
701  self.linepos = self.cmd_output.GetCurrentPos()
702  if c != ' ':
703  last_c = c
704  if last_c not in ('0123456789'):
705  self.cmd_output.AddTextWrapped('\n', wrap=None)
706  self.linepos = -1
707  else:
708  self.linepos = -1 # don't force position
709  if '\n' not in message:
710  self.cmd_output.AddTextWrapped(message, wrap=60)
711  else:
712  self.cmd_output.AddTextWrapped(message, wrap=None)
713 
714  p2 = self.cmd_output.GetCurrentPos()
715 
716  if p2 >= p1:
717  self.cmd_output.StartStyling(p1, 0xff)
718 
719  if type == 'error':
720  self.cmd_output.SetStyling(p2 - p1, self.cmd_output.StyleError)
721  elif type == 'warning':
722  self.cmd_output.SetStyling(p2 - p1, self.cmd_output.StyleWarning)
723  elif type == 'message':
724  self.cmd_output.SetStyling(p2 - p1, self.cmd_output.StyleMessage)
725  else: # unknown
726  self.cmd_output.SetStyling(p2 - p1, self.cmd_output.StyleUnknown)
727 
728  self.cmd_output.EnsureCaretVisible()
729 
730  def OnCmdProgress(self, event):
731  """!Update progress message info"""
732  self.console_progressbar.SetValue(event.value)
733 
734  def OnCmdAbort(self, event):
735  """!Abort running command"""
736  self.cmdThread.abort()
737 
738  def OnCmdRun(self, event):
739  """!Run command"""
740  if self.parent.GetName() == 'Modeler':
741  self.parent.OnCmdRun(event)
742 
743  self.WriteCmdLog('(%s)\n%s' % (str(time.ctime()), ' '.join(event.cmd)))
744  self.btn_abort.Enable()
745 
746  def OnCmdDone(self, event):
747  """!Command done (or aborted)"""
748  if self.parent.GetName() == 'Modeler':
749  self.parent.OnCmdDone(event)
750 
751  if event.aborted:
752  # Thread aborted (using our convention of None return)
753  self.WriteLog(_('Please note that the data are left in inconsistent state '
754  'and may be corrupted'), self.cmd_output.StyleWarning)
755  self.WriteCmdLog('(%s) %s (%d sec)' % (str(time.ctime()),
756  _('Command aborted'),
757  (time.time() - event.time)))
758  # pid=self.cmdThread.requestId)
759  self.btn_abort.Enable(False)
760  else:
761  try:
762  # Process results here
763  self.WriteCmdLog('(%s) %s (%d sec)' % (str(time.ctime()),
764  _('Command finished'),
765  (time.time() - event.time)))
766  except KeyError:
767  # stopped deamon
768  pass
769 
770  self.btn_abort.Enable(False)
771 
772  if event.onDone:
773  event.onDone(cmd = event.cmd, returncode = event.returncode)
774 
775  self.console_progressbar.SetValue(0) # reset progress bar on '0%'
776 
777  self.cmd_output_timer.Stop()
778 
779  if event.cmd[0] == 'g.gisenv':
780  Debug.SetLevel()
781  self.Redirect()
782 
783  if self.parent.GetName() == "LayerManager":
784  self.btn_abort.Enable(False)
785  if event.cmd[0] not in globalvar.grassCmd['all'] or \
786  event.cmd[0] == 'r.mapcalc':
787  return
788 
789  display = self.parent.GetLayerTree().GetMapDisplay()
790  if not display or not display.IsAutoRendered():
791  return
792  mapLayers = map(lambda x: x.GetName(),
793  display.GetRender().GetListOfLayers(l_type = 'raster') +
794  display.GetRender().GetListOfLayers(l_type = 'vector'))
795 
796  try:
797  task = menuform.GUI(show = None).ParseCommand(event.cmd)
798  except gcmd.GException, e:
799  print >> sys.stderr, e
800  task = None
801  return
802 
803  for p in task.get_options()['params']:
804  if p.get('prompt', '') not in ('raster', 'vector'):
805  continue
806  mapName = p.get('value', '')
807  if '@' not in mapName:
808  mapName = mapName + '@' + grass.gisenv()['MAPSET']
809  if mapName in mapLayers:
810  display.GetWindow().UpdateMap(render = True)
811  return
812  elif self.parent.GetName() == 'Modeler':
813  pass
814  else: # standalone dialogs
815  dialog = self.parent.parent
816  if hasattr(self.parent.parent, "btn_abort"):
817  dialog.btn_abort.Enable(False)
818 
819  if hasattr(self.parent.parent, "btn_cancel"):
820  dialog.btn_cancel.Enable(True)
821 
822  if hasattr(self.parent.parent, "btn_clipboard"):
823  dialog.btn_clipboard.Enable(True)
824 
825  if hasattr(self.parent.parent, "btn_help"):
826  dialog.btn_help.Enable(True)
827 
828  if hasattr(self.parent.parent, "btn_run"):
829  dialog.btn_run.Enable(True)
830 
831  if event.returncode == 0 and not event.aborted:
832  try:
833  winName = self.parent.parent.parent.GetName()
834  except AttributeError:
835  winName = ''
836 
837  if winName == 'LayerManager':
838  mapTree = self.parent.parent.parent.GetLayerTree()
839  elif winName == 'LayerTree':
840  mapTree = self.parent.parent.parent
841  elif winName: # GMConsole
842  mapTree = self.parent.parent.parent.parent.GetLayerTree()
843  else:
844  mapTree = None
845 
846  cmd = dialog.notebookpanel.createCmd(ignoreErrors = True)
847  if hasattr(dialog, "addbox") and dialog.addbox.IsChecked():
848  # add created maps into layer tree
849  for p in dialog.task.get_options()['params']:
850  prompt = p.get('prompt', '')
851  if prompt in ('raster', 'vector', '3d-raster') and \
852  p.get('age', 'old') == 'new' and \
853  p.get('value', None):
854  name, found = utils.GetLayerNameFromCmd(cmd, fullyQualified = True,
855  param = p.get('name', ''))
856 
857  if mapTree.GetMap().GetListOfLayers(l_name = name):
858  continue
859 
860  if prompt == 'raster':
861  lcmd = ['d.rast',
862  'map=%s' % name]
863  else:
864  lcmd = ['d.vect',
865  'map=%s' % name]
866  mapTree.AddLayer(ltype = prompt,
867  lcmd = lcmd,
868  lname = name)
869 
870  if hasattr(dialog, "get_dcmd") and \
871  dialog.get_dcmd is None and \
872  hasattr(dialog, "closebox") and \
873  dialog.closebox.IsChecked() and \
874  (event.returncode == 0 or event.aborted):
875  self.cmd_output.Update()
876  time.sleep(2)
877  dialog.Close()
878 
880  self.ProcessPendingEvents()
881 
882 class GMStdout:
883  """!GMConsole standard output
884 
885  Based on FrameOutErr.py
886 
887  Name: FrameOutErr.py
888  Purpose: Redirecting stdout / stderr
889  Author: Jean-Michel Fauth, Switzerland
890  Copyright: (c) 2005-2007 Jean-Michel Fauth
891  Licence: GPL
892  """
893  def __init__(self, parent):
894  self.parent = parent # GMConsole
895 
896  def write(self, s):
897  if len(s) == 0 or s == '\n':
898  return
899 
900  for line in s.splitlines():
901  if len(line) == 0:
902  continue
903 
904  evt = wxCmdOutput(text=line + '\n',
905  type='')
906  wx.PostEvent(self.parent.cmd_output, evt)
907 
908 class GMStderr:
909  """!GMConsole standard error output
910 
911  Based on FrameOutErr.py
912 
913  Name: FrameOutErr.py
914  Purpose: Redirecting stdout / stderr
915  Author: Jean-Michel Fauth, Switzerland
916  Copyright: (c) 2005-2007 Jean-Michel Fauth
917  Licence: GPL
918  """
919  def __init__(self, parent):
920  self.parent = parent # GMConsole
921 
922  self.type = ''
923  self.message = ''
924  self.printMessage = False
925 
926  def flush(self):
927  pass
928 
929  def write(self, s):
930  if "GtkPizza" in s:
931  return
932 
933  # remove/replace escape sequences '\b' or '\r' from stream
934  progressValue = -1
935 
936  for line in s.splitlines():
937  if len(line) == 0:
938  continue
939 
940  if 'GRASS_INFO_PERCENT' in line:
941  value = int(line.rsplit(':', 1)[1].strip())
942  if value >= 0 and value < 100:
943  progressValue = value
944  else:
945  progressValue = 0
946  elif 'GRASS_INFO_MESSAGE' in line:
947  self.type = 'message'
948  self.message += line.split(':', 1)[1].strip() + '\n'
949  elif 'GRASS_INFO_WARNING' in line:
950  self.type = 'warning'
951  self.message += line.split(':', 1)[1].strip() + '\n'
952  elif 'GRASS_INFO_ERROR' in line:
953  self.type = 'error'
954  self.message += line.split(':', 1)[1].strip() + '\n'
955  elif 'GRASS_INFO_END' in line:
956  self.printMessage = True
957  elif self.type == '':
958  if len(line) == 0:
959  continue
960  evt = wxCmdOutput(text=line,
961  type='')
962  wx.PostEvent(self.parent.cmd_output, evt)
963  elif len(line) > 0:
964  self.message += line.strip() + '\n'
965 
966  if self.printMessage and len(self.message) > 0:
967  evt = wxCmdOutput(text=self.message,
968  type=self.type)
969  wx.PostEvent(self.parent.cmd_output, evt)
970 
971  self.type = ''
972  self.message = ''
973  self.printMessage = False
974 
975  # update progress message
976  if progressValue > -1:
977  # self.gmgauge.SetValue(progressValue)
978  evt = wxCmdProgress(value=progressValue)
979  wx.PostEvent(self.parent.console_progressbar, evt)
980 
981 class GMStc(wx.stc.StyledTextCtrl):
982  """!Styled GMConsole
983 
984  Based on FrameOutErr.py
985 
986  Name: FrameOutErr.py
987  Purpose: Redirecting stdout / stderr
988  Author: Jean-Michel Fauth, Switzerland
989  Copyright: (c) 2005-2007 Jean-Michel Fauth
990  Licence: GPL
991  """
992  def __init__(self, parent, id, margin=False, wrap=None):
993  wx.stc.StyledTextCtrl.__init__(self, parent, id)
994  self.parent = parent
995  self.SetUndoCollection(True)
996  self.SetReadOnly(True)
997 
998  #
999  # styles
1000  #
1001  self.SetStyle()
1002 
1003  #
1004  # line margins
1005  #
1006  # TODO print number only from cmdlog
1007  self.SetMarginWidth(1, 0)
1008  self.SetMarginWidth(2, 0)
1009  if margin:
1010  self.SetMarginType(0, wx.stc.STC_MARGIN_NUMBER)
1011  self.SetMarginWidth(0, 30)
1012  else:
1013  self.SetMarginWidth(0, 0)
1014 
1015  #
1016  # miscellaneous
1017  #
1018  self.SetViewWhiteSpace(False)
1019  self.SetTabWidth(4)
1020  self.SetUseTabs(False)
1021  self.UsePopUp(True)
1022  self.SetSelBackground(True, "#FFFF00")
1023  self.SetUseHorizontalScrollBar(True)
1024 
1025  #
1026  # bindings
1027  #
1028  self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
1029 
1030  def OnTextSelectionChanged(self, event):
1031  """!Copy selected text to clipboard and skip event.
1032  The same function is in TextCtrlAutoComplete class (prompt.py).
1033  """
1034  self.Copy()
1035  event.Skip()
1036 
1037  def SetStyle(self):
1038  """!Set styles for styled text output windows with type face
1039  and point size selected by user (Courier New 10 is default)"""
1040 
1041  settings = preferences.Settings()
1042 
1043  typeface = settings.Get(group = 'appearance', key = 'outputfont', subkey = 'type')
1044  if typeface == "":
1045  typeface = "Courier New"
1046 
1047  typesize = settings.Get(group = 'appearance', key = 'outputfont', subkey = 'size')
1048  if typesize == None or typesize <= 0:
1049  typesize = 10
1050  typesize = float(typesize)
1051 
1052  self.StyleDefault = 0
1053  self.StyleDefaultSpec = "face:%s,size:%d,fore:#000000,back:#FFFFFF" % (typeface, typesize)
1054  self.StyleCommand = 1
1055  self.StyleCommandSpec = "face:%s,size:%d,,fore:#000000,back:#bcbcbc" % (typeface, typesize)
1056  self.StyleOutput = 2
1057  self.StyleOutputSpec = "face:%s,size:%d,,fore:#000000,back:#FFFFFF" % (typeface, typesize)
1058  # fatal error
1059  self.StyleError = 3
1060  self.StyleErrorSpec = "face:%s,size:%d,,fore:#7F0000,back:#FFFFFF" % (typeface, typesize)
1061  # warning
1062  self.StyleWarning = 4
1063  self.StyleWarningSpec = "face:%s,size:%d,,fore:#0000FF,back:#FFFFFF" % (typeface, typesize)
1064  # message
1065  self.StyleMessage = 5
1066  self.StyleMessageSpec = "face:%s,size:%d,,fore:#000000,back:#FFFFFF" % (typeface, typesize)
1067  # unknown
1068  self.StyleUnknown = 6
1069  self.StyleUnknownSpec = "face:%s,size:%d,,fore:#000000,back:#FFFFFF" % (typeface, typesize)
1070 
1071  # default and clear => init
1072  self.StyleSetSpec(wx.stc.STC_STYLE_DEFAULT, self.StyleDefaultSpec)
1073  self.StyleClearAll()
1074  self.StyleSetSpec(self.StyleCommand, self.StyleCommandSpec)
1075  self.StyleSetSpec(self.StyleOutput, self.StyleOutputSpec)
1076  self.StyleSetSpec(self.StyleError, self.StyleErrorSpec)
1077  self.StyleSetSpec(self.StyleWarning, self.StyleWarningSpec)
1078  self.StyleSetSpec(self.StyleMessage, self.StyleMessageSpec)
1079  self.StyleSetSpec(self.StyleUnknown, self.StyleUnknownSpec)
1080 
1081  def OnDestroy(self, evt):
1082  """!The clipboard contents can be preserved after
1083  the app has exited"""
1084 
1085  wx.TheClipboard.Flush()
1086  evt.Skip()
1087 
1088  def AddTextWrapped(self, txt, wrap=None):
1089  """!Add string to text area.
1090 
1091  String is wrapped and linesep is also added to the end
1092  of the string"""
1093  # allow writing to output window
1094  self.SetReadOnly(False)
1095 
1096  if wrap:
1097  txt = textwrap.fill(txt, wrap) + '\n'
1098  else:
1099  if txt[-1] != '\n':
1100  txt += '\n'
1101 
1102  if '\r' in txt:
1103  self.parent.linePos = -1
1104  for seg in txt.split('\r'):
1105  if self.parent.linePos > -1:
1106  self.SetCurrentPos(self.parent.linePos)
1107  self.ReplaceSelection(seg)
1108  else:
1109  self.parent.linePos = self.GetCurrentPos()
1110  self.AddText(seg)
1111  else:
1112  self.parent.linePos = self.GetCurrentPos()
1113  try:
1114  self.AddText(txt)
1115  except UnicodeDecodeError:
1116  enc = UserSettings.Get(group='atm', key='encoding', subkey='value')
1117  if enc:
1118  txt = unicode(txt, enc)
1119  elif 'GRASS_DB_ENCODING' in os.environ:
1120  txt = unicode(txt, os.environ['GRASS_DB_ENCODING'])
1121  else:
1122  txt = utils.EncodeString(txt)
1123 
1124  self.AddText(txt)
1125 
1126  # reset output window to read only
1127  self.SetReadOnly(True)
1128