gconsole.py 27 KB

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