goutput.py 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801
  1. """
  2. @package goutput
  3. @brief Command output log widget
  4. Classes:
  5. - GMConsole
  6. - GMStc
  7. - GMStdout
  8. - GMStderr
  9. (C) 2007-2008 by the GRASS Development Team
  10. This program is free software under the GNU General Public
  11. License (>=v2). Read the file COPYING that comes with GRASS
  12. for details.
  13. @author Michael Barton (Arizona State University)
  14. @author Martin Landa <landa.martin gmail.com>
  15. """
  16. import os
  17. import sys
  18. import textwrap
  19. import time
  20. import threading
  21. import Queue
  22. import wx
  23. import wx.stc
  24. from wx.lib.newevent import NewEvent
  25. import globalvar
  26. import gcmd
  27. import utils
  28. from debug import Debug as Debug
  29. from preferences import globalSettings as UserSettings
  30. wxCmdOutput, EVT_CMD_OUTPUT = NewEvent()
  31. wxCmdProgress, EVT_CMD_PROGRESS = NewEvent()
  32. wxCmdRun, EVT_CMD_RUN = NewEvent()
  33. wxCmdDone, EVT_CMD_DONE = NewEvent()
  34. wxCmdAbort, EVT_CMD_ABORT = NewEvent()
  35. def GrassCmd(cmd, stdout, stderr):
  36. """!Return GRASS command thread"""
  37. return gcmd.CommandThread(cmd,
  38. stdout=stdout, stderr=stderr)
  39. class CmdThread(threading.Thread):
  40. """!Thread for GRASS commands"""
  41. requestId = 0
  42. def __init__(self, parent, requestQ, resultQ, **kwds):
  43. threading.Thread.__init__(self, **kwds)
  44. self.setDaemon(True)
  45. self.parent = parent # GMConsole
  46. self.requestQ = requestQ
  47. self.resultQ = resultQ
  48. self.start()
  49. def RunCmd(self, callable, onDone, *args, **kwds):
  50. CmdThread.requestId += 1
  51. self.requestCmd = None
  52. self.requestQ.put((CmdThread.requestId, callable, onDone, args, kwds))
  53. return CmdThread.requestId
  54. def run(self):
  55. while True:
  56. requestId, callable, onDone, args, kwds = self.requestQ.get()
  57. requestTime = time.time()
  58. event = wxCmdRun(cmd=args[0],
  59. pid=requestId)
  60. wx.PostEvent(self.parent, event)
  61. time.sleep(.1)
  62. self.requestCmd = callable(*args, **kwds)
  63. self.resultQ.put((requestId, self.requestCmd.run()))
  64. try:
  65. returncode = self.requestCmd.module.returncode
  66. except AttributeError:
  67. returncode = 0 # being optimistic
  68. try:
  69. aborted = self.requestCmd.aborted
  70. except AttributeError:
  71. aborted = False
  72. time.sleep(.1)
  73. event = wxCmdDone(aborted = aborted,
  74. returncode = returncode,
  75. time = requestTime,
  76. pid = requestId,
  77. onDone = onDone)
  78. # send event
  79. wx.PostEvent(self.parent, event)
  80. def abort(self):
  81. self.requestCmd.abort()
  82. class GMConsole(wx.Panel):
  83. """
  84. Create and manage output console for commands entered on the
  85. GIS Manager command line.
  86. """
  87. def __init__(self, parent, id=wx.ID_ANY, margin=False, pageid=0,
  88. pos=wx.DefaultPosition, size=wx.DefaultSize,
  89. style=wx.TAB_TRAVERSAL | wx.FULL_REPAINT_ON_RESIZE):
  90. wx.Panel.__init__(self, parent, id, pos, size, style)
  91. self.SetName("GMConsole")
  92. # initialize variables
  93. self.Map = None
  94. self.parent = parent # GMFrame | CmdPanel
  95. self.lineWidth = 80
  96. self.pageid = pageid
  97. # remember position of line begining (used for '\r')
  98. self.linePos = -1
  99. #
  100. # create queues
  101. #
  102. self.requestQ = Queue.Queue()
  103. self.resultQ = Queue.Queue()
  104. #
  105. # progress bar
  106. #
  107. self.console_progressbar = wx.Gauge(parent=self, id=wx.ID_ANY,
  108. range=100, pos=(110, 50), size=(-1, 25),
  109. style=wx.GA_HORIZONTAL)
  110. self.console_progressbar.Bind(EVT_CMD_PROGRESS, self.OnCmdProgress)
  111. # abort
  112. self.btn_abort = wx.Button(parent=self, id=wx.ID_STOP)
  113. self.btn_abort.SetToolTipString(_("Abort the running command"))
  114. self.btn_abort.Bind(wx.EVT_BUTTON, self.OnCmdAbort)
  115. self.btn_abort.Enable(False)
  116. #
  117. # text control for command output
  118. #
  119. self.cmd_output = GMStc(parent=self, id=wx.ID_ANY, margin=margin,
  120. wrap=None)
  121. self.cmd_output_timer = wx.Timer(self.cmd_output, id=wx.ID_ANY)
  122. self.cmd_output.Bind(EVT_CMD_OUTPUT, self.OnCmdOutput)
  123. self.cmd_output.Bind(wx.EVT_TIMER, self.OnProcessPendingOutputWindowEvents)
  124. self.Bind(EVT_CMD_RUN, self.OnCmdRun)
  125. self.Bind(EVT_CMD_DONE, self.OnCmdDone)
  126. #
  127. # stream redirection
  128. #
  129. self.cmd_stdout = GMStdout(self)
  130. self.cmd_stderr = GMStderr(self)
  131. #
  132. # thread
  133. #
  134. self.cmdThread = CmdThread(self, self.requestQ, self.resultQ)
  135. #
  136. # buttons
  137. #
  138. self.console_clear = wx.Button(parent=self, id=wx.ID_CLEAR)
  139. self.console_save = wx.Button(parent=self, id=wx.ID_SAVE)
  140. self.Bind(wx.EVT_BUTTON, self.ClearHistory, self.console_clear)
  141. self.Bind(wx.EVT_BUTTON, self.SaveHistory, self.console_save)
  142. self.Bind(EVT_CMD_ABORT, self.OnCmdAbort)
  143. self.__layout()
  144. def __layout(self):
  145. """!Do layout"""
  146. boxsizer1 = wx.BoxSizer(wx.VERTICAL)
  147. gridsizer1 = wx.GridSizer(rows=1, cols=2, vgap=0, hgap=0)
  148. boxsizer1.Add(item=self.cmd_output, proportion=1,
  149. flag=wx.EXPAND | wx.ADJUST_MINSIZE, border=0)
  150. gridsizer1.Add(item=self.console_clear, proportion=0,
  151. flag=wx.ALIGN_CENTER_HORIZONTAL | wx.ADJUST_MINSIZE, border=0)
  152. gridsizer1.Add(item=self.console_save, proportion=0,
  153. flag=wx.ALIGN_CENTER_HORIZONTAL | wx.ADJUST_MINSIZE, border=0)
  154. boxsizer1.Add(item=gridsizer1, proportion=0,
  155. flag=wx.EXPAND | wx.ALIGN_CENTRE_VERTICAL | wx.TOP | wx.BOTTOM,
  156. border=5)
  157. boxsizer2 = wx.BoxSizer(wx.HORIZONTAL)
  158. boxsizer2.Add(item=self.console_progressbar, proportion=1,
  159. flag=wx.EXPAND | wx.ALIGN_CENTRE_VERTICAL)
  160. boxsizer2.Add(item=self.btn_abort, proportion=0,
  161. flag=wx.ALIGN_CENTRE_VERTICAL | wx.LEFT,
  162. border = 5)
  163. boxsizer1.Add(item=boxsizer2, proportion=0,
  164. flag=wx.EXPAND | wx.ALIGN_CENTRE_VERTICAL | wx.ALL,
  165. border=5)
  166. boxsizer1.Fit(self)
  167. boxsizer1.SetSizeHints(self)
  168. # layout
  169. self.SetAutoLayout(True)
  170. self.SetSizer(boxsizer1)
  171. def Redirect(self):
  172. """!Redirect stderr
  173. @return True redirected
  174. @return False failed
  175. """
  176. if Debug.get_level() == 0:
  177. # don't redirect when debugging is enabled
  178. sys.stdout = self.cmd_stdout
  179. sys.stderr = self.cmd_stderr
  180. return True
  181. return False
  182. def WriteLog(self, text, style = None, wrap = None,
  183. switchPage = False):
  184. """!Generic method for writing log message in
  185. given style
  186. @param line text line
  187. @param style text style (see GMStc)
  188. @param stdout write to stdout or stderr
  189. """
  190. if switchPage and \
  191. self.parent.notebook.GetSelection() != self.parent.goutput.pageid:
  192. self.parent.notebook.SetSelection(self.parent.goutput.pageid)
  193. if not style:
  194. style = self.cmd_output.StyleDefault
  195. # p1 = self.cmd_output.GetCurrentPos()
  196. p1 = self.cmd_output.GetEndStyled()
  197. self.cmd_output.GotoPos(p1)
  198. for line in text.splitlines():
  199. # fill space
  200. if len(line) < self.lineWidth:
  201. diff = self.lineWidth - len(line)
  202. line += diff * ' '
  203. self.cmd_output.AddTextWrapped(line, wrap=wrap) # adds '\n'
  204. p2 = self.cmd_output.GetCurrentPos()
  205. self.cmd_output.StartStyling(p1, 0xff)
  206. self.cmd_output.SetStyling(p2 - p1, style)
  207. self.cmd_output.EnsureCaretVisible()
  208. def WriteCmdLog(self, line, pid=None):
  209. """!Write message in selected style"""
  210. if pid:
  211. line = '(' + str(pid) + ') ' + line
  212. self.WriteLog(line, style=self.cmd_output.StyleCommand, switchPage = True)
  213. def WriteWarning(self, line):
  214. """!Write message in warning style"""
  215. self.WriteLog(line, style=self.cmd_output.StyleWarning, switchPage = True)
  216. def WriteError(self, line):
  217. """!Write message in error style"""
  218. self.WriteLog(line, style=self.cmd_output.StyleError, switchPage = True)
  219. def RunCmd(self, command, compReg=True, switchPage=False,
  220. onDone = None):
  221. """
  222. Run in GUI GRASS (or other) commands typed into
  223. console command text widget, and send stdout output to output
  224. text widget.
  225. Command is transformed into a list for processing.
  226. TODO: Display commands (*.d) are captured and
  227. processed separately by mapdisp.py. Display commands are
  228. rendered in map display widget that currently has
  229. the focus (as indicted by mdidx).
  230. @param command command (list)
  231. @param compReg if true use computation region
  232. @param switchPage switch to output page
  233. @param onDone function to be called when command is finished
  234. """
  235. # map display window available ?
  236. try:
  237. curr_disp = self.parent.curr_page.maptree.mapdisplay
  238. self.Map = curr_disp.GetRender()
  239. except:
  240. curr_disp = None
  241. # command given as a string ?
  242. try:
  243. cmdlist = command.strip().split(' ')
  244. except:
  245. cmdlist = command
  246. if cmdlist[0] in globalvar.grassCmd['all']:
  247. # send GRASS command without arguments to GUI command interface
  248. # except display commands (they are handled differently)
  249. if cmdlist[0][0:2] == "d.":
  250. #
  251. # display GRASS commands
  252. #
  253. try:
  254. layertype = {'d.rast' : 'raster',
  255. 'd.rgb' : 'rgb',
  256. 'd.his' : 'his',
  257. 'd.shaded' : 'shaded',
  258. 'd.legend' : 'rastleg',
  259. 'd.rast.arrow' : 'rastarrow',
  260. 'd.rast.num' : 'rastnum',
  261. 'd.vect' : 'vector',
  262. 'd.vect.thematic': 'thememap',
  263. 'd.vect.chart' : 'themechart',
  264. 'd.grid' : 'grid',
  265. 'd.geodesic' : 'geodesic',
  266. 'd.rhumbline' : 'rhumb',
  267. 'd.labels' : 'labels'}[cmdlist[0]]
  268. except KeyError:
  269. wx.MessageBox(message=_("Command '%s' not yet implemented.") % cmdlist[0])
  270. return None
  271. # add layer into layer tree
  272. self.parent.curr_page.maptree.AddLayer(ltype=layertype,
  273. lcmd=cmdlist)
  274. else:
  275. #
  276. # other GRASS commands (r|v|g|...)
  277. #
  278. # switch to 'Command output'
  279. if switchPage:
  280. if self.parent.notebook.GetSelection() != self.parent.goutput.pageid:
  281. self.parent.notebook.SetSelection(self.parent.goutput.pageid)
  282. self.parent.SetFocus() # -> set focus
  283. self.parent.Raise()
  284. # activate computational region (set with g.region)
  285. # for all non-display commands.
  286. if compReg:
  287. tmpreg = os.getenv("GRASS_REGION")
  288. if os.environ.has_key("GRASS_REGION"):
  289. del os.environ["GRASS_REGION"]
  290. if len(cmdlist) == 1:
  291. import menuform
  292. # process GRASS command without argument
  293. menuform.GUI().ParseCommand(cmdlist, parentframe=self)
  294. else:
  295. # process GRASS command with argument
  296. self.cmdThread.RunCmd(GrassCmd,
  297. onDone,
  298. cmdlist,
  299. self.cmd_stdout, self.cmd_stderr)
  300. self.btn_abort.Enable()
  301. self.cmd_output_timer.Start(50)
  302. return None
  303. # deactivate computational region and return to display settings
  304. if compReg and tmpreg:
  305. os.environ["GRASS_REGION"] = tmpreg
  306. else:
  307. # Send any other command to the shell. Send output to
  308. # console output window
  309. # if command is not a GRASS command, treat it like a shell command
  310. try:
  311. gcmd.Command(cmdlist,
  312. stdout=self.cmd_stdout,
  313. stderr=self.cmd_stderr)
  314. except gcmd.CmdError, e:
  315. print >> sys.stderr, e
  316. return None
  317. def ClearHistory(self, event):
  318. """!Clear history of commands"""
  319. self.cmd_output.ClearAll()
  320. self.console_progressbar.SetValue(0)
  321. def SaveHistory(self, event):
  322. """!Save history of commands"""
  323. self.history = self.cmd_output.GetSelectedText()
  324. if self.history == '':
  325. self.history = self.cmd_output.GetText()
  326. # add newline if needed
  327. if len(self.history) > 0 and self.history[-1] != '\n':
  328. self.history += '\n'
  329. wildcard = "Text file (*.txt)|*.txt"
  330. dlg = wx.FileDialog(
  331. self, message=_("Save file as..."), defaultDir=os.getcwd(),
  332. defaultFile="grass_cmd_history.txt", wildcard=wildcard,
  333. style=wx.SAVE|wx.FD_OVERWRITE_PROMPT)
  334. # Show the dialog and retrieve the user response. If it is the OK response,
  335. # process the data.
  336. if dlg.ShowModal() == wx.ID_OK:
  337. path = dlg.GetPath()
  338. output = open(path, "w")
  339. output.write(self.history)
  340. output.close()
  341. dlg.Destroy()
  342. def GetCmd(self):
  343. """!Get running command or None"""
  344. return self.requestQ.get()
  345. def OnCmdOutput(self, event):
  346. """!Print command output"""
  347. message = event.text
  348. type = event.type
  349. if self.parent.notebook.GetSelection() != self.parent.goutput.pageid:
  350. textP = self.parent.notebook.GetPageText(self.parent.goutput.pageid)
  351. if textP[-1] != ')':
  352. textP += ' (...)'
  353. self.parent.notebook.SetPageText(self.parent.goutput.pageid,
  354. textP)
  355. # message prefix
  356. if type == 'warning':
  357. messege = 'WARNING: ' + message
  358. elif type == 'error':
  359. message = 'ERROR: ' + message
  360. p1 = self.cmd_output.GetEndStyled()
  361. self.cmd_output.GotoPos(p1)
  362. if '\b' in message:
  363. if self.linepos < 0:
  364. self.linepos = p1
  365. last_c = ''
  366. for c in message:
  367. if c == '\b':
  368. self.linepos -= 1
  369. else:
  370. if c == '\r':
  371. pos = self.cmd_output.GetCurLine()[1]
  372. # self.cmd_output.SetCurrentPos(pos)
  373. else:
  374. self.cmd_output.SetCurrentPos(self.linepos)
  375. self.cmd_output.ReplaceSelection(c)
  376. self.linepos = self.cmd_output.GetCurrentPos()
  377. if c != ' ':
  378. last_c = c
  379. if last_c not in ('0123456789'):
  380. self.cmd_output.AddTextWrapped('\n', wrap=None)
  381. self.linepos = -1
  382. else:
  383. self.linepos = -1 # don't force position
  384. if '\n' not in message:
  385. self.cmd_output.AddTextWrapped(message, wrap=60)
  386. else:
  387. self.cmd_output.AddTextWrapped(message, wrap=None)
  388. p2 = self.cmd_output.GetCurrentPos()
  389. if p2 >= p1:
  390. self.cmd_output.StartStyling(p1, 0xff)
  391. if type == 'error':
  392. self.cmd_output.SetStyling(p2 - p1, self.cmd_output.StyleError)
  393. elif type == 'warning':
  394. self.cmd_output.SetStyling(p2 - p1, self.cmd_output.StyleWarning)
  395. elif type == 'message':
  396. self.cmd_output.SetStyling(p2 - p1, self.cmd_output.StyleMessage)
  397. else: # unknown
  398. self.cmd_output.SetStyling(p2 - p1, self.cmd_output.StyleUnknown)
  399. self.cmd_output.EnsureCaretVisible()
  400. def OnCmdProgress(self, event):
  401. """!Update progress message info"""
  402. self.console_progressbar.SetValue(event.value)
  403. def OnCmdAbort(self, event):
  404. """!Abort running command"""
  405. self.cmdThread.abort()
  406. def OnCmdRun(self, event):
  407. """!Run command"""
  408. self.WriteCmdLog('(%s)\n%s' % (str(time.ctime()), ' '.join(event.cmd)))
  409. def OnCmdDone(self, event):
  410. """!Command done (or aborted)"""
  411. if event.aborted:
  412. # Thread aborted (using our convention of None return)
  413. self.WriteLog(_('Please note that the data are left in incosistent stage '
  414. 'and can be corrupted'), self.cmd_output.StyleWarning)
  415. self.WriteCmdLog('(%s) %s (%d sec)' % (str(time.ctime()),
  416. _('Command aborted'),
  417. (time.time() - event.time)))
  418. # pid=self.cmdThread.requestId)
  419. else:
  420. try:
  421. # Process results here
  422. self.WriteCmdLog('(%s) %s (%d sec)' % (str(time.ctime()),
  423. _('Command finished'),
  424. (time.time() - event.time)))
  425. # pid=event.pid)
  426. except KeyError:
  427. # stopped deamon
  428. pass
  429. if event.onDone:
  430. event.onDone(returncode = event.returncode)
  431. self.console_progressbar.SetValue(0) # reset progress bar on '0%'
  432. self.cmd_output_timer.Stop()
  433. # set focus on prompt
  434. if self.parent.GetName() == "LayerManager":
  435. self.parent.cmdinput.SetFocus()
  436. self.btn_abort.Enable(False)
  437. else:
  438. # updated command dialog
  439. dialog = self.parent.parent
  440. if hasattr(self.parent.parent, "btn_abort"):
  441. dialog.btn_abort.Enable(False)
  442. if hasattr(self.parent.parent, "btn_cancel"):
  443. dialog.btn_cancel.Enable(True)
  444. if hasattr(self.parent.parent, "btn_clipboard"):
  445. dialog.btn_clipboard.Enable(True)
  446. if hasattr(self.parent.parent, "btn_help"):
  447. dialog.btn_help.Enable(True)
  448. dialog.btn_run.Enable(True)
  449. if event.returncode == 0 and \
  450. not event.aborted and hasattr(dialog, "addbox") and \
  451. dialog.addbox.IsChecked():
  452. # add new map into layer tree
  453. if dialog.outputType in ('raster', 'vector'):
  454. # add layer into layer tree
  455. cmd = dialog.notebookpanel.createCmd(ignoreErrors = True)
  456. name = utils.GetLayerNameFromCmd(cmd, fullyQualified=True, param='output')
  457. winName = self.parent.parent.parent.GetName()
  458. if winName == 'LayerManager':
  459. mapTree = self.parent.parent.parent.curr_page.maptree
  460. else: # GMConsole
  461. mapTree = self.parent.parent.parent.parent.curr_page.maptree
  462. if dialog.outputType == 'raster':
  463. lcmd = ['d.rast',
  464. 'map=%s' % name]
  465. else:
  466. lcmd = ['d.vect',
  467. 'map=%s' % name]
  468. mapTree.AddLayer(ltype=dialog.outputType,
  469. lcmd=lcmd,
  470. lname=name)
  471. if dialog.get_dcmd is None and \
  472. dialog.closebox.IsChecked():
  473. time.sleep(1)
  474. dialog.Close()
  475. event.Skip()
  476. def OnProcessPendingOutputWindowEvents(self, event):
  477. self.ProcessPendingEvents()
  478. class GMStdout:
  479. """!GMConsole standard output
  480. Based on FrameOutErr.py
  481. Name: FrameOutErr.py
  482. Purpose: Redirecting stdout / stderr
  483. Author: Jean-Michel Fauth, Switzerland
  484. Copyright: (c) 2005-2007 Jean-Michel Fauth
  485. Licence: GPL
  486. """
  487. def __init__(self, parent):
  488. self.parent = parent # GMConsole
  489. def write(self, s):
  490. if len(s) == 0 or s == '\n':
  491. return
  492. for line in s.splitlines():
  493. if len(line) == 0:
  494. continue
  495. evt = wxCmdOutput(text=line + '\n',
  496. type='')
  497. wx.PostEvent(self.parent.cmd_output, evt)
  498. class GMStderr:
  499. """!GMConsole standard error output
  500. Based on FrameOutErr.py
  501. Name: FrameOutErr.py
  502. Purpose: Redirecting stdout / stderr
  503. Author: Jean-Michel Fauth, Switzerland
  504. Copyright: (c) 2005-2007 Jean-Michel Fauth
  505. Licence: GPL
  506. """
  507. def __init__(self, parent):
  508. self.parent = parent # GMConsole
  509. self.type = ''
  510. self.message = ''
  511. self.printMessage = False
  512. def write(self, s):
  513. if "GtkPizza" in s:
  514. return
  515. # remove/replace escape sequences '\b' or '\r' from stream
  516. progressValue = -1
  517. for line in s.splitlines():
  518. if len(line) == 0:
  519. continue
  520. if 'GRASS_INFO_PERCENT' in line:
  521. value = int(line.rsplit(':', 1)[1].strip())
  522. if value >= 0 and value < 100:
  523. progressValue = value
  524. else:
  525. progressValue = 0
  526. elif 'GRASS_INFO_MESSAGE' in line:
  527. self.type = 'message'
  528. self.message += line.split(':', 1)[1].strip() + '\n'
  529. elif 'GRASS_INFO_WARNING' in line:
  530. self.type = 'warning'
  531. self.message += line.split(':', 1)[1].strip() + '\n'
  532. elif 'GRASS_INFO_ERROR' in line:
  533. self.type = 'error'
  534. self.message += line.split(':', 1)[1].strip() + '\n'
  535. elif 'GRASS_INFO_END' in line:
  536. self.printMessage = True
  537. elif self.type == '':
  538. if len(line) == 0:
  539. continue
  540. evt = wxCmdOutput(text=line,
  541. type='')
  542. wx.PostEvent(self.parent.cmd_output, evt)
  543. elif len(line) > 0:
  544. self.message += line.strip() + '\n'
  545. if self.printMessage and len(self.message) > 0:
  546. evt = wxCmdOutput(text=self.message,
  547. type=self.type)
  548. wx.PostEvent(self.parent.cmd_output, evt)
  549. self.type = ''
  550. self.message = ''
  551. self.printMessage = False
  552. # update progress message
  553. if progressValue > -1:
  554. # self.gmgauge.SetValue(progressValue)
  555. evt = wxCmdProgress(value=progressValue)
  556. wx.PostEvent(self.parent.console_progressbar, evt)
  557. class GMStc(wx.stc.StyledTextCtrl):
  558. """!Styled GMConsole
  559. Based on FrameOutErr.py
  560. Name: FrameOutErr.py
  561. Purpose: Redirecting stdout / stderr
  562. Author: Jean-Michel Fauth, Switzerland
  563. Copyright: (c) 2005-2007 Jean-Michel Fauth
  564. Licence: GPL
  565. """
  566. def __init__(self, parent, id, margin=False, wrap=None):
  567. wx.stc.StyledTextCtrl.__init__(self, parent, id)
  568. self.parent = parent
  569. #
  570. # styles
  571. #
  572. self.StyleDefault = 0
  573. self.StyleDefaultSpec = "face:Courier New,size:10,fore:#000000,back:#FFFFFF"
  574. self.StyleCommand = 1
  575. self.StyleCommandSpec = "face:Courier New,size:10,fore:#000000,back:#bcbcbc"
  576. self.StyleOutput = 2
  577. self.StyleOutputSpec = "face:Courier New,size:10,fore:#000000,back:#FFFFFF"
  578. # fatal error
  579. self.StyleError = 3
  580. self.StyleErrorSpec = "face:Courier New,size:10,fore:#7F0000,back:#FFFFFF"
  581. # warning
  582. self.StyleWarning = 4
  583. self.StyleWarningSpec = "face:Courier New,size:10,fore:#0000FF,back:#FFFFFF"
  584. # message
  585. self.StyleMessage = 5
  586. self.StyleMessageSpec = "face:Courier New,size:10,fore:#000000,back:#FFFFFF"
  587. # unknown
  588. self.StyleUnknown = 6
  589. self.StyleUnknownSpec = "face:Courier New,size:10,fore:#000000,back:#FFFFFF"
  590. # default and clear => init
  591. self.StyleSetSpec(wx.stc.STC_STYLE_DEFAULT, self.StyleDefaultSpec)
  592. self.StyleClearAll()
  593. self.StyleSetSpec(self.StyleCommand, self.StyleCommandSpec)
  594. self.StyleSetSpec(self.StyleOutput, self.StyleOutputSpec)
  595. self.StyleSetSpec(self.StyleError, self.StyleErrorSpec)
  596. self.StyleSetSpec(self.StyleWarning, self.StyleWarningSpec)
  597. self.StyleSetSpec(self.StyleMessage, self.StyleMessageSpec)
  598. self.StyleSetSpec(self.StyleUnknown, self.StyleUnknownSpec)
  599. #
  600. # line margins
  601. #
  602. # TODO print number only from cmdlog
  603. self.SetMarginWidth(1, 0)
  604. self.SetMarginWidth(2, 0)
  605. if margin:
  606. self.SetMarginType(0, wx.stc.STC_MARGIN_NUMBER)
  607. self.SetMarginWidth(0, 30)
  608. else:
  609. self.SetMarginWidth(0, 0)
  610. #
  611. # miscellaneous
  612. #
  613. self.SetViewWhiteSpace(False)
  614. self.SetTabWidth(4)
  615. self.SetUseTabs(False)
  616. self.UsePopUp(True)
  617. self.SetSelBackground(True, "#FFFF00")
  618. self.SetUseHorizontalScrollBar(True)
  619. #
  620. # bindins
  621. #
  622. self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
  623. def OnDestroy(self, evt):
  624. """!The clipboard contents can be preserved after
  625. the app has exited"""
  626. wx.TheClipboard.Flush()
  627. evt.Skip()
  628. def AddTextWrapped(self, txt, wrap=None):
  629. """!Add string to text area.
  630. String is wrapped and linesep is also added to the end
  631. of the string"""
  632. if wrap:
  633. txt = textwrap.fill(txt, wrap) + '\n'
  634. else:
  635. if txt[-1] != '\n':
  636. txt += '\n'
  637. if '\r' in txt:
  638. self.parent.linePos = -1
  639. for seg in txt.split('\r'):
  640. if self.parent.linePos > -1:
  641. self.SetCurrentPos(self.parent.linePos)
  642. self.ReplaceSelection(seg)
  643. else:
  644. self.parent.linePos = self.GetCurrentPos()
  645. self.AddText(seg)
  646. else:
  647. self.parent.linePos = self.GetCurrentPos()
  648. try:
  649. self.AddText(txt)
  650. except UnicodeDecodeError:
  651. enc = UserSettings.Get(group='atm', key='encoding', subkey='value')
  652. if enc:
  653. txt = unicode(txt, enc)
  654. elif os.environ.has_key('GRASS_DB_ENCODING'):
  655. txt = unicode(txt, os.environ['GRASS_DB_ENCODING'])
  656. else:
  657. txt = _('Unable to encode text. Please set encoding in GUI preferences.') + '\n'
  658. self.AddText(txt)