gconsole.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731
  1. """
  2. @package core.gconsole
  3. @brief Command output widgets
  4. Classes:
  5. - goutput::CmdThread
  6. - goutput::GStdout
  7. - goutput::GStderr
  8. - goutput::GConsole
  9. (C) 2007-2015 by the GRASS Development Team
  10. This program is free software under the GNU General Public License
  11. (>=v2). Read the file COPYING that comes with GRASS for details.
  12. @author Michael Barton (Arizona State University)
  13. @author Martin Landa <landa.martin gmail.com>
  14. @author Vaclav Petras <wenzeslaus gmail.com> (refactoring)
  15. @author Anna Kratochvilova <kratochanna gmail.com> (refactoring)
  16. """
  17. import os
  18. import sys
  19. import re
  20. import time
  21. import threading
  22. import Queue
  23. import codecs
  24. import locale
  25. import wx
  26. from wx.lib.newevent import NewEvent
  27. import grass.script as grass
  28. from grass.script import task as gtask
  29. from grass.pydispatch.signal import Signal
  30. from core import globalvar
  31. from core.gcmd import CommandThread, GError, GException
  32. from core.utils import _
  33. from gui_core.forms import GUI
  34. from core.debug import Debug
  35. from core.settings import UserSettings
  36. from core.giface import Notification
  37. wxCmdOutput, EVT_CMD_OUTPUT = NewEvent()
  38. wxCmdProgress, EVT_CMD_PROGRESS = NewEvent()
  39. wxCmdRun, EVT_CMD_RUN = NewEvent()
  40. wxCmdDone, EVT_CMD_DONE = NewEvent()
  41. wxCmdAbort, EVT_CMD_ABORT = NewEvent()
  42. wxCmdPrepare, EVT_CMD_PREPARE = NewEvent()
  43. def GrassCmd(cmd, env=None, stdout=None, stderr=None):
  44. """Return GRASS command thread"""
  45. return CommandThread(cmd, env=env,
  46. stdout=stdout, stderr=stderr)
  47. class CmdThread(threading.Thread):
  48. """Thread for GRASS commands"""
  49. requestId = 0
  50. def __init__(self, receiver, requestQ=None, resultQ=None, **kwds):
  51. """
  52. :param receiver: event receiver (used in PostEvent)
  53. """
  54. threading.Thread.__init__(self, **kwds)
  55. if requestQ is None:
  56. self.requestQ = Queue.Queue()
  57. else:
  58. self.requestQ = requestQ
  59. if resultQ is None:
  60. self.resultQ = Queue.Queue()
  61. else:
  62. self.resultQ = resultQ
  63. self.setDaemon(True)
  64. self.requestCmd = None
  65. self.receiver = receiver
  66. self._want_abort_all = False
  67. self.start()
  68. def RunCmd(self, *args, **kwds):
  69. """Run command in queue
  70. :param args: unnamed command arguments
  71. :param kwds: named command arguments
  72. :return: request id in queue
  73. """
  74. CmdThread.requestId += 1
  75. self.requestCmd = None
  76. self.requestQ.put((CmdThread.requestId, args, kwds))
  77. return CmdThread.requestId
  78. def GetId(self):
  79. """Get id for next command"""
  80. return CmdThread.requestId + 1
  81. def SetId(self, id):
  82. """Set starting id"""
  83. CmdThread.requestId = id
  84. def run(self):
  85. os.environ['GRASS_MESSAGE_FORMAT'] = 'gui'
  86. while True:
  87. requestId, args, kwds = self.requestQ.get()
  88. for key in ('callable', 'onDone', 'onPrepare', 'userData', 'addLayer', 'notification'):
  89. if key in kwds:
  90. vars()[key] = kwds[key]
  91. del kwds[key]
  92. else:
  93. vars()[key] = None
  94. if not vars()['callable']:
  95. vars()['callable'] = GrassCmd
  96. requestTime = time.time()
  97. # prepare
  98. if self.receiver:
  99. event = wxCmdPrepare(cmd=args[0],
  100. time=requestTime,
  101. pid=requestId,
  102. onPrepare=vars()['onPrepare'],
  103. userData=vars()['userData'])
  104. wx.PostEvent(self.receiver, event)
  105. # run command
  106. event = wxCmdRun(cmd=args[0],
  107. pid=requestId,
  108. notification=vars()['notification'])
  109. wx.PostEvent(self.receiver, event)
  110. time.sleep(.1)
  111. self.requestCmd = vars()['callable'](*args, **kwds)
  112. if self._want_abort_all and self.requestCmd is not None:
  113. self.requestCmd.abort()
  114. if self.requestQ.empty():
  115. self._want_abort_all = False
  116. self.resultQ.put((requestId, self.requestCmd.run()))
  117. try:
  118. returncode = self.requestCmd.module.returncode
  119. except AttributeError:
  120. returncode = 0 # being optimistic
  121. try:
  122. aborted = self.requestCmd.aborted
  123. except AttributeError:
  124. aborted = False
  125. time.sleep(.1)
  126. # set default color table for raster data
  127. if UserSettings.Get(group='rasterLayer',
  128. key='colorTable', subkey='enabled') and \
  129. args[0][0][:2] == 'r.':
  130. colorTable = UserSettings.Get(group='rasterLayer',
  131. key='colorTable',
  132. subkey='selection')
  133. mapName = None
  134. if args[0][0] == 'r.mapcalc':
  135. try:
  136. mapName = args[0][1].split('=', 1)[0].strip()
  137. except KeyError:
  138. pass
  139. else:
  140. moduleInterface = GUI(show=None).ParseCommand(args[0])
  141. outputParam = moduleInterface.get_param(value='output',
  142. raiseError=False)
  143. if outputParam and outputParam['prompt'] == 'raster':
  144. mapName = outputParam['value']
  145. if mapName:
  146. argsColor = list(args)
  147. argsColor[0] = ['r.colors',
  148. 'map=%s' % mapName,
  149. 'color=%s' % colorTable]
  150. self.requestCmdColor = vars()['callable'](*argsColor, **kwds)
  151. self.resultQ.put((requestId, self.requestCmdColor.run()))
  152. if self.receiver:
  153. event = wxCmdDone(cmd=args[0],
  154. aborted=aborted,
  155. returncode=returncode,
  156. time=requestTime,
  157. pid=requestId,
  158. onDone=vars()['onDone'],
  159. userData=vars()['userData'],
  160. addLayer=vars()['addLayer'],
  161. notification=vars()['notification'])
  162. # send event
  163. wx.PostEvent(self.receiver, event)
  164. def abort(self, abortall=True):
  165. """Abort command(s)"""
  166. if abortall:
  167. self._want_abort_all = True
  168. if self.requestCmd is not None:
  169. self.requestCmd.abort()
  170. if self.requestQ.empty():
  171. self._want_abort_all = False
  172. class GStdout:
  173. """GConsole standard output
  174. Based on FrameOutErr.py
  175. Name: FrameOutErr.py
  176. Purpose: Redirecting stdout / stderr
  177. Author: Jean-Michel Fauth, Switzerland
  178. Copyright: (c) 2005-2007 Jean-Michel Fauth
  179. Licence: GPL
  180. """
  181. def __init__(self, receiver):
  182. """
  183. :param receiver: event receiver (used in PostEvent)
  184. """
  185. self.receiver = receiver
  186. def flush(self):
  187. pass
  188. def write(self, s):
  189. if len(s) == 0 or s == '\n':
  190. return
  191. for line in s.splitlines():
  192. if len(line) == 0:
  193. continue
  194. evt = wxCmdOutput(text=line + '\n',
  195. type='')
  196. wx.PostEvent(self.receiver, evt)
  197. class GStderr:
  198. """GConsole standard error output
  199. Based on FrameOutErr.py
  200. Name: FrameOutErr.py
  201. Purpose: Redirecting stdout / stderr
  202. Author: Jean-Michel Fauth, Switzerland
  203. Copyright: (c) 2005-2007 Jean-Michel Fauth
  204. Licence: GPL
  205. """
  206. def __init__(self, receiver):
  207. """
  208. :param receiver: event receiver (used in PostEvent)
  209. """
  210. self.receiver = receiver
  211. self.type = ''
  212. self.message = ''
  213. self.printMessage = False
  214. def flush(self):
  215. pass
  216. def write(self, s):
  217. if "GtkPizza" in s:
  218. return
  219. # remove/replace escape sequences '\b' or '\r' from stream
  220. progressValue = -1
  221. for line in s.splitlines():
  222. if len(line) == 0:
  223. continue
  224. if 'GRASS_INFO_PERCENT' in line:
  225. value = int(line.rsplit(':', 1)[1].strip())
  226. if value >= 0 and value < 100:
  227. progressValue = value
  228. else:
  229. progressValue = 0
  230. elif 'GRASS_INFO_MESSAGE' in line:
  231. self.type = 'message'
  232. self.message += line.split(':', 1)[1].strip() + '\n'
  233. elif 'GRASS_INFO_WARNING' in line:
  234. self.type = 'warning'
  235. self.message += line.split(':', 1)[1].strip() + '\n'
  236. elif 'GRASS_INFO_ERROR' in line:
  237. self.type = 'error'
  238. self.message += line.split(':', 1)[1].strip() + '\n'
  239. elif 'GRASS_INFO_END' in line:
  240. self.printMessage = True
  241. elif self.type == '':
  242. if len(line) == 0:
  243. continue
  244. evt = wxCmdOutput(text=line,
  245. type='')
  246. wx.PostEvent(self.receiver, evt)
  247. elif len(line) > 0:
  248. self.message += line.strip() + '\n'
  249. if self.printMessage and len(self.message) > 0:
  250. evt = wxCmdOutput(text=self.message,
  251. type=self.type)
  252. wx.PostEvent(self.receiver, evt)
  253. self.type = ''
  254. self.message = ''
  255. self.printMessage = False
  256. # update progress message
  257. if progressValue > -1:
  258. # self.gmgauge.SetValue(progressValue)
  259. evt = wxCmdProgress(value=progressValue)
  260. wx.PostEvent(self.receiver, evt)
  261. # Occurs when an ignored command is called.
  262. # Attribute cmd contains command (as a list).
  263. gIgnoredCmdRun, EVT_IGNORED_CMD_RUN = NewEvent()
  264. class GConsole(wx.EvtHandler):
  265. """
  266. """
  267. def __init__(self, guiparent=None, giface=None, ignoredCmdPattern=None):
  268. """
  269. :param guiparent: parent window for created GUI objects
  270. :param lmgr: layer manager window (TODO: replace by giface)
  271. :param ignoredCmdPattern: regular expression specifying commads
  272. to be ignored (e.g. @c '^d\..*' for
  273. display commands)
  274. """
  275. wx.EvtHandler.__init__(self)
  276. # Signal when some map is created or updated by a module.
  277. # attributes: name: map name, ltype: map type,
  278. self.mapCreated = Signal('GConsole.mapCreated')
  279. # emitted when map display should be re-render
  280. self.updateMap = Signal('GConsole.updateMap')
  281. # emitted when log message should be written
  282. self.writeLog = Signal('GConsole.writeLog')
  283. # emitted when command log message should be written
  284. self.writeCmdLog = Signal('GConsole.writeCmdLog')
  285. # emitted when warning message should be written
  286. self.writeWarning = Signal('GConsole.writeWarning')
  287. # emitted when error message should be written
  288. self.writeError = Signal('GConsole.writeError')
  289. self._guiparent = guiparent
  290. self._giface = giface
  291. self._ignoredCmdPattern = ignoredCmdPattern
  292. # create queues
  293. self.requestQ = Queue.Queue()
  294. self.resultQ = Queue.Queue()
  295. self.cmdOutputTimer = wx.Timer(self)
  296. self.Bind(wx.EVT_TIMER, self.OnProcessPendingOutputWindowEvents)
  297. self.Bind(EVT_CMD_RUN, self.OnCmdRun)
  298. self.Bind(EVT_CMD_DONE, self.OnCmdDone)
  299. self.Bind(EVT_CMD_ABORT, self.OnCmdAbort)
  300. # stream redirection
  301. self.cmdStdOut = GStdout(receiver=self)
  302. self.cmdStdErr = GStderr(receiver=self)
  303. # thread
  304. self.cmdThread = CmdThread(self, self.requestQ, self.resultQ)
  305. def Redirect(self):
  306. """Redirect stdout/stderr
  307. """
  308. if Debug.GetLevel() == 0 and grass.debug_level(force=True) == 0:
  309. # don't redirect when debugging is enabled
  310. sys.stdout = self.cmdStdOut
  311. sys.stderr = self.cmdStdErr
  312. else:
  313. enc = locale.getdefaultlocale()[1]
  314. if enc:
  315. sys.stdout = codecs.getwriter(enc)(sys.__stdout__)
  316. sys.stderr = codecs.getwriter(enc)(sys.__stderr__)
  317. else:
  318. sys.stdout = sys.__stdout__
  319. sys.stderr = sys.__stderr__
  320. def WriteLog(self, text, style=None, wrap=None,
  321. notification=Notification.HIGHLIGHT):
  322. """Generic method for writing log message in
  323. given style
  324. :param text: text line
  325. :param notification: form of notification
  326. """
  327. self.writeLog.emit(text=text, wrap=wrap,
  328. notification=notification)
  329. def WriteCmdLog(self, text, pid=None, notification=Notification.MAKE_VISIBLE):
  330. """Write message in selected style
  331. :param text: message to be printed
  332. :param pid: process pid or None
  333. :param notification: form of notification
  334. """
  335. self.writeCmdLog.emit(text=text, pid=pid,
  336. notification=notification)
  337. def WriteWarning(self, text):
  338. """Write message in warning style"""
  339. self.writeWarning.emit(text=text)
  340. def WriteError(self, text):
  341. """Write message in error style"""
  342. self.writeError.emit(text=text)
  343. def RunCmd(self, command, compReg=True, skipInterface=False,
  344. onDone=None, onPrepare=None, userData=None, addLayer=None,
  345. notification=Notification.MAKE_VISIBLE):
  346. """Run command typed into console command prompt (GPrompt).
  347. .. todo::
  348. Document the other event.
  349. .. todo::
  350. Solve problem with the other event (now uses gOutputText
  351. event but there is no text, use onPrepare handler instead?)
  352. Posts event EVT_IGNORED_CMD_RUN when command which should be ignored
  353. (according to ignoredCmdPattern) is run.
  354. For example, see layer manager which handles d.* on its own.
  355. :param command: command given as a list (produced e.g. by utils.split())
  356. :param compReg: True use computation region
  357. :param notification: form of notification
  358. :param bool skipInterface: True to do not launch GRASS interface
  359. parser when command has no arguments
  360. given
  361. :param onDone: function to be called when command is finished
  362. :param onPrepare: function to be called before command is launched
  363. :param addLayer: to be passed in the mapCreated signal
  364. :param userData: data defined for the command
  365. """
  366. if len(command) == 0:
  367. Debug.msg(2, "GPrompt:RunCmd(): empty command")
  368. return
  369. # update history file
  370. self.UpdateHistoryFile(' '.join(command))
  371. if command[0] in globalvar.grassCmd:
  372. # send GRASS command without arguments to GUI command interface
  373. # except ignored commands (event is emitted)
  374. if self._ignoredCmdPattern and \
  375. re.compile(self._ignoredCmdPattern).search(' '.join(command)) and \
  376. '--help' not in command and '--ui' not in command:
  377. event = gIgnoredCmdRun(cmd=command)
  378. wx.PostEvent(self, event)
  379. return
  380. else:
  381. # other GRASS commands (r|v|g|...)
  382. try:
  383. task = GUI(show=None).ParseCommand(command)
  384. except GException as e:
  385. GError(parent=self._guiparent,
  386. message=unicode(e),
  387. showTraceback=False)
  388. return
  389. hasParams = False
  390. if task:
  391. options = task.get_options()
  392. hasParams = options['params'] and options['flags']
  393. # check for <input>=-
  394. for p in options['params']:
  395. if p.get('prompt', '') == 'input' and \
  396. p.get('element', '') == 'file' and \
  397. p.get('age', 'new') == 'old' and \
  398. p.get('value', '') == '-':
  399. GError(parent=self._guiparent,
  400. message=_("Unable to run command:\n%(cmd)s\n\n"
  401. "Option <%(opt)s>: read from standard input is not "
  402. "supported by wxGUI") % {'cmd': ' '.join(command),
  403. 'opt': p.get('name', '')})
  404. return
  405. if len(command) == 1:
  406. if command[0].startswith('g.gui.'):
  407. import imp
  408. import inspect
  409. pyFile = command[0]
  410. if sys.platform == 'win32':
  411. pyFile += '.py'
  412. pyPath = os.path.join(os.environ['GISBASE'], 'scripts', pyFile)
  413. if not os.path.exists(pyPath):
  414. pyPath = os.path.join(os.environ['GRASS_ADDON_BASE'], 'scripts', pyFile)
  415. if not os.path.exists(pyPath):
  416. GError(parent=self._guiparent,
  417. message=_("Module <%s> not found.") % command[0])
  418. pymodule = imp.load_source(command[0].replace('.', '_'), pyPath)
  419. pymain = inspect.getargspec(pymodule.main)
  420. if pymain and 'giface' in pymain.args:
  421. pymodule.main(self._giface)
  422. return
  423. if hasParams and command[0] != 'v.krige':
  424. # no arguments given
  425. try:
  426. GUI(parent=self._guiparent, giface=self._giface).ParseCommand(command)
  427. except GException as e:
  428. print >> sys.stderr, e
  429. return
  430. # activate computational region (set with g.region)
  431. # for all non-display commands.
  432. if compReg:
  433. tmpreg = os.getenv("GRASS_REGION")
  434. if "GRASS_REGION" in os.environ:
  435. del os.environ["GRASS_REGION"]
  436. # process GRASS command with argument
  437. self.cmdThread.RunCmd(command,
  438. stdout=self.cmdStdOut,
  439. stderr=self.cmdStdErr,
  440. onDone=onDone, onPrepare=onPrepare,
  441. userData=userData, addLayer=addLayer,
  442. env=os.environ.copy(),
  443. notification=notification)
  444. self.cmdOutputTimer.Start(50)
  445. # deactivate computational region and return to display settings
  446. if compReg and tmpreg:
  447. os.environ["GRASS_REGION"] = tmpreg
  448. else:
  449. # Send any other command to the shell. Send output to
  450. # console output window
  451. #
  452. # Check if the script has an interface (avoid double-launching
  453. # of the script)
  454. # check if we ignore the command (similar to grass commands part)
  455. if self._ignoredCmdPattern and \
  456. re.compile(self._ignoredCmdPattern).search(' '.join(command)):
  457. event = gIgnoredCmdRun(cmd=command)
  458. wx.PostEvent(self, event)
  459. return
  460. skipInterface = True
  461. if os.path.splitext(command[0])[1] in ('.py', '.sh'):
  462. try:
  463. sfile = open(command[0], "r")
  464. for line in sfile.readlines():
  465. if len(line) < 2:
  466. continue
  467. if line[0] is '#' and line[1] is '%':
  468. skipInterface = False
  469. break
  470. sfile.close()
  471. except IOError:
  472. pass
  473. if len(command) == 1 and not skipInterface:
  474. try:
  475. task = gtask.parse_interface(command[0])
  476. except:
  477. task = None
  478. else:
  479. task = None
  480. if task:
  481. # process GRASS command without argument
  482. GUI(parent=self._guiparent, giface=self._giface).ParseCommand(command)
  483. else:
  484. self.cmdThread.RunCmd(command,
  485. stdout=self.cmdStdOut,
  486. stderr=self.cmdStdErr,
  487. onDone=onDone, onPrepare=onPrepare,
  488. userData=userData, addLayer=addLayer,
  489. notification=notification)
  490. self.cmdOutputTimer.Start(50)
  491. def GetLog(self, err=False):
  492. """Get widget used for logging
  493. .. todo::
  494. what's this?
  495. :param bool err: True to get stderr widget
  496. """
  497. if err:
  498. return self.cmdStdErr
  499. return self.cmdStdOut
  500. def GetCmd(self):
  501. """Get running command or None"""
  502. return self.requestQ.get()
  503. def OnCmdAbort(self, event):
  504. """Abort running command"""
  505. self.cmdThread.abort()
  506. event.Skip()
  507. def OnCmdRun(self, event):
  508. """Run command"""
  509. self.WriteCmdLog('(%s)\n%s' % (str(time.ctime()), ' '.join(event.cmd)),
  510. notification=event.notification)
  511. event.Skip()
  512. def OnCmdDone(self, event):
  513. """Command done (or aborted)
  514. Sends signal mapCreated if map is recognized in output
  515. parameters or for specific modules (as r.colors).
  516. """
  517. # Process results here
  518. try:
  519. ctime = time.time() - event.time
  520. if ctime < 60:
  521. stime = _("%d sec") % int(ctime)
  522. else:
  523. mtime = int(ctime / 60)
  524. stime = _("%(min)d min %(sec)d sec") % {'min': mtime,
  525. 'sec': int(ctime - (mtime * 60))}
  526. except KeyError:
  527. # stopped deamon
  528. stime = _("unknown")
  529. if event.aborted:
  530. # Thread aborted (using our convention of None return)
  531. self.WriteWarning(_('Please note that the data are left in'
  532. ' inconsistent state and may be corrupted'))
  533. msg = _('Command aborted')
  534. else:
  535. msg = _('Command finished')
  536. self.WriteCmdLog('(%s) %s (%s)' % (str(time.ctime()), msg, stime),
  537. notification=event.notification)
  538. if event.onDone:
  539. event.onDone(event)
  540. self.cmdOutputTimer.Stop()
  541. if event.cmd[0] == 'g.gisenv':
  542. Debug.SetLevel()
  543. self.Redirect()
  544. # do nothing when no map added
  545. if event.returncode != 0 or event.aborted:
  546. event.Skip()
  547. return
  548. if event.cmd[0] not in globalvar.grassCmd:
  549. return
  550. # find which maps were created
  551. try:
  552. task = GUI(show=None).ParseCommand(event.cmd)
  553. except GException as e:
  554. print >> sys.stderr, e
  555. task = None
  556. return
  557. name = task.get_name()
  558. for p in task.get_options()['params']:
  559. prompt = p.get('prompt', '')
  560. if prompt in ('raster', 'vector', '3d-raster') and p.get('value', None):
  561. if p.get('age', 'old') == 'new' or \
  562. name in ('r.colors', 'r3.colors', 'v.colors', 'v.proj', 'r.proj'):
  563. # if multiple maps (e.g. r.series.interp), we need add each
  564. if p.get('multiple', False):
  565. lnames = p.get('value').split(',')
  566. # in case multiple input (old) maps in r.colors
  567. # we don't want to rerender it multiple times! just once
  568. if p.get('age', 'old') == 'old':
  569. lnames = lnames[0:1]
  570. else:
  571. lnames = [p.get('value')]
  572. for lname in lnames:
  573. if '@' not in lname:
  574. lname += '@' + grass.gisenv()['MAPSET']
  575. if grass.find_file(lname, element=p.get('element'))['fullname']:
  576. self.mapCreated.emit(name=lname, ltype=prompt, add=event.addLayer)
  577. if name == 'r.mask':
  578. self.updateMap.emit()
  579. event.Skip()
  580. def OnProcessPendingOutputWindowEvents(self, event):
  581. wx.GetApp().ProcessPendingEvents()
  582. def UpdateHistoryFile(self, command):
  583. """Update history file
  584. :param command: the command given as a string
  585. """
  586. env = grass.gisenv()
  587. try:
  588. filePath = os.path.join(env['GISDBASE'],
  589. env['LOCATION_NAME'],
  590. env['MAPSET'],
  591. '.bash_history')
  592. fileHistory = codecs.open(filePath, encoding='utf-8', mode='a')
  593. except IOError as e:
  594. GError(_("Unable to write file '%(filePath)s'.\n\nDetails: %(error)s") %
  595. {'filePath': filePath, 'error': e},
  596. parent=self._guiparent)
  597. return
  598. try:
  599. fileHistory.write(command + os.linesep)
  600. finally:
  601. fileHistory.close()
  602. # update wxGUI prompt
  603. if self._giface:
  604. self._giface.UpdateCmdHistory(command)