gconsole.py 27 KB

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