goutput.py 37 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997
  1. """!
  2. @package goutput
  3. @brief Command output log widget
  4. Classes:
  5. - GMConsole
  6. - GMStc
  7. - GMStdout
  8. - GMStderr
  9. (C) 2007-2010 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 grass.script as grass
  26. import globalvar
  27. import gcmd
  28. import utils
  29. import preferences
  30. import menuform
  31. import prompt
  32. from debug import Debug
  33. from preferences import globalSettings as UserSettings
  34. wxCmdOutput, EVT_CMD_OUTPUT = NewEvent()
  35. wxCmdProgress, EVT_CMD_PROGRESS = NewEvent()
  36. wxCmdRun, EVT_CMD_RUN = NewEvent()
  37. wxCmdDone, EVT_CMD_DONE = NewEvent()
  38. wxCmdAbort, EVT_CMD_ABORT = NewEvent()
  39. def GrassCmd(cmd, stdout, stderr):
  40. """!Return GRASS command thread"""
  41. return gcmd.CommandThread(cmd,
  42. stdout=stdout, stderr=stderr)
  43. class CmdThread(threading.Thread):
  44. """!Thread for GRASS commands"""
  45. requestId = 0
  46. def __init__(self, parent, requestQ, resultQ, **kwds):
  47. threading.Thread.__init__(self, **kwds)
  48. self.setDaemon(True)
  49. self.parent = parent # GMConsole
  50. self.requestQ = requestQ
  51. self.resultQ = resultQ
  52. self.start()
  53. def RunCmd(self, callable, onDone, *args, **kwds):
  54. CmdThread.requestId += 1
  55. self.requestCmd = None
  56. self.requestQ.put((CmdThread.requestId, callable, onDone, args, kwds))
  57. return CmdThread.requestId
  58. def SetId(self, id):
  59. """!Set starting id"""
  60. CmdThread.requestId = id
  61. def run(self):
  62. while True:
  63. requestId, callable, onDone, args, kwds = self.requestQ.get()
  64. requestTime = time.time()
  65. event = wxCmdRun(cmd=args[0],
  66. pid=requestId)
  67. wx.PostEvent(self.parent, event)
  68. time.sleep(.1)
  69. self.requestCmd = callable(*args, **kwds)
  70. self.resultQ.put((requestId, self.requestCmd.run()))
  71. try:
  72. returncode = self.requestCmd.module.returncode
  73. except AttributeError:
  74. returncode = 0 # being optimistic
  75. try:
  76. aborted = self.requestCmd.aborted
  77. except AttributeError:
  78. aborted = False
  79. time.sleep(.1)
  80. # set default color table for raster data
  81. if UserSettings.Get(group='cmd', key='rasterColorTable', subkey='enabled') and \
  82. args[0][0][:2] == 'r.':
  83. moduleInterface = menuform.GUI().ParseCommand(args[0], show = None)
  84. outputParam = moduleInterface.get_param(value = 'output', raiseError = False)
  85. colorTable = UserSettings.Get(group='cmd', key='rasterColorTable', subkey='selection')
  86. if outputParam and outputParam['prompt'] == 'raster':
  87. argsColor = list(args)
  88. argsColor[0] = [ 'r.colors',
  89. 'map=%s' % outputParam['value'],
  90. 'color=%s' % colorTable ]
  91. self.requestCmdColor = callable(*argsColor, **kwds)
  92. self.resultQ.put((requestId, self.requestCmdColor.run()))
  93. event = wxCmdDone(aborted = aborted,
  94. returncode = returncode,
  95. time = requestTime,
  96. pid = requestId,
  97. onDone = onDone)
  98. # send event
  99. wx.PostEvent(self.parent, event)
  100. def abort(self):
  101. self.requestCmd.abort()
  102. class GMConsole(wx.SplitterWindow):
  103. """!Create and manage output console for commands run by GUI.
  104. """
  105. def __init__(self, parent, id=wx.ID_ANY, margin=False, pageid=0,
  106. notebook = None,
  107. style=wx.TAB_TRAVERSAL | wx.FULL_REPAINT_ON_RESIZE,
  108. **kwargs):
  109. wx.SplitterWindow.__init__(self, parent, id, style = style, *kwargs)
  110. self.SetName("GMConsole")
  111. self.panelOutput = wx.Panel(parent = self, id = wx.ID_ANY)
  112. self.panelPrompt = wx.Panel(parent = self, id = wx.ID_ANY)
  113. # initialize variables
  114. self.Map = None
  115. self.parent = parent # GMFrame | CmdPanel | ?
  116. if notebook:
  117. self._notebook = notebook
  118. else:
  119. self._notebook = self.parent.notebook
  120. self.lineWidth = 80
  121. self.pageid = pageid
  122. # remember position of line begining (used for '\r')
  123. self.linePos = -1
  124. #
  125. # create queues
  126. #
  127. self.requestQ = Queue.Queue()
  128. self.resultQ = Queue.Queue()
  129. #
  130. # progress bar
  131. #
  132. self.console_progressbar = wx.Gauge(parent=self.panelOutput, id=wx.ID_ANY,
  133. range=100, pos=(110, 50), size=(-1, 25),
  134. style=wx.GA_HORIZONTAL)
  135. self.console_progressbar.Bind(EVT_CMD_PROGRESS, self.OnCmdProgress)
  136. #
  137. # text control for command output
  138. #
  139. self.cmd_output = GMStc(parent=self.panelOutput, id=wx.ID_ANY, margin=margin,
  140. wrap=None)
  141. self.cmd_output_timer = wx.Timer(self.cmd_output, id=wx.ID_ANY)
  142. self.cmd_output.Bind(EVT_CMD_OUTPUT, self.OnCmdOutput)
  143. self.cmd_output.Bind(wx.EVT_TIMER, self.OnProcessPendingOutputWindowEvents)
  144. self.Bind(EVT_CMD_RUN, self.OnCmdRun)
  145. self.Bind(EVT_CMD_DONE, self.OnCmdDone)
  146. #
  147. # search & command prompt
  148. #
  149. self.searchBy = wx.Choice(parent = self.panelPrompt, id = wx.ID_ANY,
  150. choices = [_("description"),
  151. _("keywords")])
  152. self.search = wx.TextCtrl(parent = self.panelPrompt, id = wx.ID_ANY,
  153. value = "", size = (-1, 25))
  154. self.cmd_prompt = prompt.GPromptSTC(parent = self)
  155. if self.parent.GetName() != 'LayerManager':
  156. self.searchBy.Hide()
  157. self.search.Hide()
  158. self.cmd_prompt.Hide()
  159. #
  160. # stream redirection
  161. #
  162. self.cmd_stdout = GMStdout(self)
  163. self.cmd_stderr = GMStderr(self)
  164. #
  165. # thread
  166. #
  167. self.cmdThread = CmdThread(self, self.requestQ, self.resultQ)
  168. #
  169. # buttons
  170. #
  171. self.btn_console_clear = wx.Button(parent = self.panelPrompt, id = wx.ID_ANY,
  172. label = _("C&lear output"), size=(125,-1))
  173. self.btn_cmd_clear = wx.Button(parent = self.panelPrompt, id = wx.ID_ANY,
  174. label = _("&Clear command"), size=(125,-1))
  175. if self.parent.GetName() != 'LayerManager':
  176. self.btn_cmd_clear.Hide()
  177. self.btn_console_save = wx.Button(parent = self.panelPrompt, id = wx.ID_ANY,
  178. label = _("&Save output"), size=(125,-1))
  179. # abort
  180. self.btn_abort = wx.Button(parent = self.panelPrompt, id = wx.ID_ANY, label = _("&Abort command"),
  181. size=(125,-1))
  182. self.btn_abort.SetToolTipString(_("Abort the running command"))
  183. self.btn_abort.Enable(False)
  184. self.btn_cmd_clear.Bind(wx.EVT_BUTTON, self.cmd_prompt.OnCmdErase)
  185. self.btn_console_clear.Bind(wx.EVT_BUTTON, self.ClearHistory)
  186. self.btn_console_save.Bind(wx.EVT_BUTTON, self.SaveHistory)
  187. self.btn_abort.Bind(wx.EVT_BUTTON, self.OnCmdAbort)
  188. self.btn_abort.Bind(EVT_CMD_ABORT, self.OnCmdAbort)
  189. self.search.Bind(wx.EVT_TEXT, self.OnSearchModule)
  190. self.__layout()
  191. def __layout(self):
  192. """!Do layout"""
  193. OutputSizer = wx.BoxSizer(wx.VERTICAL)
  194. PromptSizer = wx.BoxSizer(wx.VERTICAL)
  195. SearchSizer = wx.BoxSizer(wx.HORIZONTAL)
  196. ButtonSizer = wx.BoxSizer(wx.HORIZONTAL)
  197. OutputSizer.Add(item=self.cmd_output, proportion=1,
  198. flag=wx.EXPAND | wx.ALL, border=3)
  199. OutputSizer.Add(item=self.console_progressbar, proportion=0,
  200. flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=3)
  201. if self.searchBy.IsShown():
  202. SearchSizer.Add(item = wx.StaticText(parent = self.panelPrompt, id = wx.ID_ANY,
  203. label = _("Find module:")),
  204. proportion = 0, flag = wx.LEFT | wx.ALIGN_CENTER_VERTICAL, border = 3)
  205. SearchSizer.Add(item = self.searchBy,
  206. proportion = 0, flag = wx.LEFT, border = 3)
  207. SearchSizer.Add(item = self.search,
  208. proportion = 1, flag = wx.LEFT | wx.EXPAND, border = 3)
  209. PromptSizer.Add(item=SearchSizer, proportion=0,
  210. flag=wx.EXPAND | wx.ALL, border=1)
  211. PromptSizer.Add(item=self.cmd_prompt, proportion=1,
  212. flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border=3)
  213. ButtonSizer.Add(item=self.btn_console_clear, proportion=0,
  214. flag=wx.ALIGN_CENTER | wx.FIXED_MINSIZE | wx.ALL, border=5)
  215. ButtonSizer.Add(item=self.btn_console_save, proportion=0,
  216. flag=wx.ALIGN_CENTER | wx.FIXED_MINSIZE | wx.ALL, border=5)
  217. ButtonSizer.Add(item=self.btn_cmd_clear, proportion=0,
  218. flag=wx.ALIGN_CENTER | wx.FIXED_MINSIZE | wx.ALL, border=5)
  219. ButtonSizer.Add(item=self.btn_abort, proportion=0,
  220. flag=wx.ALIGN_CENTER | wx.FIXED_MINSIZE | wx.ALL, border=5)
  221. PromptSizer.Add(item=ButtonSizer, proportion=0,
  222. flag=wx.ALIGN_CENTER)
  223. OutputSizer.Fit(self)
  224. OutputSizer.SetSizeHints(self)
  225. PromptSizer.Fit(self)
  226. PromptSizer.SetSizeHints(self)
  227. self.panelOutput.SetSizer(OutputSizer)
  228. self.panelPrompt.SetSizer(PromptSizer)
  229. # split window
  230. if self.parent.GetName() == 'LayerManager':
  231. self.SplitHorizontally(self.panelOutput, self.panelPrompt, -75)
  232. self.SetMinimumPaneSize(self.btn_cmd_clear.GetSize()[1] + 75)
  233. else:
  234. self.SplitHorizontally(self.panelOutput, self.panelPrompt, -45)
  235. self.SetMinimumPaneSize(self.btn_cmd_clear.GetSize()[1] + 10)
  236. self.SetSashGravity(1.0)
  237. self.Fit()
  238. # layout
  239. self.SetAutoLayout(True)
  240. self.Layout()
  241. def GetPanel(self, prompt = True):
  242. """!Get panel
  243. @param prompt get prompt / output panel
  244. @return wx.Panel reference
  245. """
  246. if prompt:
  247. return self.panelPrompt
  248. return self.panelOutput
  249. def Redirect(self):
  250. """!Redirect stderr
  251. @return True redirected
  252. @return False failed
  253. """
  254. if Debug.get_level() == 0:
  255. # don't redirect when debugging is enabled
  256. sys.stdout = self.cmd_stdout
  257. sys.stderr = self.cmd_stderr
  258. return True
  259. return False
  260. def WriteLog(self, text, style = None, wrap = None,
  261. switchPage = False):
  262. """!Generic method for writing log message in
  263. given style
  264. @param line text line
  265. @param style text style (see GMStc)
  266. @param stdout write to stdout or stderr
  267. """
  268. self.cmd_output.SetStyle()
  269. if switchPage and \
  270. self._notebook.GetSelection() != self.parent.goutput.pageid:
  271. self._notebook.SetSelection(self.parent.goutput.pageid)
  272. if not style:
  273. style = self.cmd_output.StyleDefault
  274. # p1 = self.cmd_output.GetCurrentPos()
  275. p1 = self.cmd_output.GetEndStyled()
  276. # self.cmd_output.GotoPos(p1)
  277. self.cmd_output.DocumentEnd()
  278. for line in text.splitlines():
  279. # fill space
  280. if len(line) < self.lineWidth:
  281. diff = self.lineWidth - len(line)
  282. line += diff * ' '
  283. self.cmd_output.AddTextWrapped(line, wrap=wrap) # adds '\n'
  284. p2 = self.cmd_output.GetCurrentPos()
  285. self.cmd_output.StartStyling(p1, 0xff)
  286. self.cmd_output.SetStyling(p2 - p1, style)
  287. self.cmd_output.EnsureCaretVisible()
  288. def WriteCmdLog(self, line, pid=None):
  289. """!Write message in selected style"""
  290. if pid:
  291. line = '(' + str(pid) + ') ' + line
  292. self.WriteLog(line, style=self.cmd_output.StyleCommand, switchPage = True)
  293. def WriteWarning(self, line):
  294. """!Write message in warning style"""
  295. self.WriteLog(line, style=self.cmd_output.StyleWarning, switchPage = True)
  296. def WriteError(self, line):
  297. """!Write message in error style"""
  298. self.WriteLog(line, style=self.cmd_output.StyleError, switchPage = True)
  299. def RunCmd(self, command, compReg = True, switchPage = False,
  300. onDone = None):
  301. """!Run in GUI GRASS (or other) commands typed into console
  302. command text widget, and send stdout output to output text
  303. widget.
  304. Command is transformed into a list for processing.
  305. @todo Display commands (*.d) are captured and processed
  306. separately by mapdisp.py. Display commands are rendered in map
  307. display widget that currently has the focus (as indicted by
  308. mdidx).
  309. @param command command (list)
  310. @param compReg if true use computation region
  311. @param switchPage switch to output page
  312. @param onDone function to be called when command is finished
  313. """
  314. # map display window available ?
  315. try:
  316. curr_disp = self.parent.curr_page.maptree.mapdisplay
  317. self.Map = curr_disp.GetRender()
  318. except:
  319. curr_disp = None
  320. # command given as a string ?
  321. try:
  322. cmdlist = command.strip().split(' ')
  323. except:
  324. cmdlist = command
  325. # update history file
  326. env = grass.gisenv()
  327. fileHistory = open(os.path.join(env['GISDBASE'], env['LOCATION_NAME'], env['MAPSET'],
  328. '.bash_history'), 'a')
  329. cmdString = ' '.join(cmdlist)
  330. try:
  331. fileHistory.write(cmdString + '\n')
  332. finally:
  333. fileHistory.close()
  334. # update history items
  335. if self.parent.GetName() == 'LayerManager':
  336. try:
  337. self.parent.cmdinput.SetHistoryItems()
  338. except AttributeError:
  339. pass
  340. if cmdlist[0] in globalvar.grassCmd['all']:
  341. # send GRASS command without arguments to GUI command interface
  342. # except display commands (they are handled differently)
  343. if self.parent.GetName() == "LayerManager" and cmdlist[0][0:2] == "d.":
  344. #
  345. # display GRASS commands
  346. #
  347. try:
  348. layertype = {'d.rast' : 'raster',
  349. 'd.rgb' : 'rgb',
  350. 'd.his' : 'his',
  351. 'd.shaded' : 'shaded',
  352. 'd.legend' : 'rastleg',
  353. 'd.rast.arrow' : 'rastarrow',
  354. 'd.rast.num' : 'rastnum',
  355. 'd.vect' : 'vector',
  356. 'd.thematic.area': 'thememap',
  357. 'd.vect.chart' : 'themechart',
  358. 'd.grid' : 'grid',
  359. 'd.geodesic' : 'geodesic',
  360. 'd.rhumbline' : 'rhumb',
  361. 'd.labels' : 'labels'}[cmdlist[0]]
  362. except KeyError:
  363. wx.MessageBox(caption = _("Message"),
  364. message=_("Command '%s' not yet implemented in the GUI. "
  365. "Try adding it as a command layer instead.") % cmdlist[0])
  366. return None
  367. # add layer into layer tree
  368. if cmdlist[0] == 'd.rast':
  369. lname = utils.GetLayerNameFromCmd(cmdlist, fullyQualified = True,
  370. layerType = 'raster')
  371. elif cmdlist[0] == 'd.vect':
  372. lname = utils.GetLayerNameFromCmd(cmdlist, fullyQualified = True,
  373. layerType = 'vector')
  374. else:
  375. lname = None
  376. if self.parent.GetName() == "LayerManager":
  377. self.parent.curr_page.maptree.AddLayer(ltype=layertype,
  378. lname=lname,
  379. lcmd=cmdlist)
  380. else:
  381. #
  382. # other GRASS commands (r|v|g|...)
  383. #
  384. # switch to 'Command output'
  385. if switchPage:
  386. if self._notebook.GetSelection() != self.parent.goutput.pageid:
  387. self._notebook.SetSelection(self.parent.goutput.pageid)
  388. self.parent.SetFocus() # -> set focus
  389. self.parent.Raise()
  390. # activate computational region (set with g.region)
  391. # for all non-display commands.
  392. if compReg:
  393. tmpreg = os.getenv("GRASS_REGION")
  394. if os.environ.has_key("GRASS_REGION"):
  395. del os.environ["GRASS_REGION"]
  396. if len(cmdlist) == 1 and cmdlist[0] not in ('v.krige'):
  397. import menuform
  398. # process GRASS command without argument
  399. menuform.GUI().ParseCommand(cmdlist, parentframe=self)
  400. else:
  401. # process GRASS command with argument
  402. self.cmdThread.RunCmd(GrassCmd,
  403. onDone,
  404. cmdlist,
  405. self.cmd_stdout, self.cmd_stderr)
  406. self.btn_abort.Enable()
  407. self.cmd_output_timer.Start(50)
  408. return None
  409. # deactivate computational region and return to display settings
  410. if compReg and tmpreg:
  411. os.environ["GRASS_REGION"] = tmpreg
  412. else:
  413. # Send any other command to the shell. Send output to
  414. # console output window
  415. self.cmdThread.RunCmd(GrassCmd,
  416. onDone,
  417. cmdlist,
  418. self.cmd_stdout, self.cmd_stderr)
  419. self.btn_abort.Enable()
  420. self.cmd_output_timer.Start(50)
  421. return None
  422. def ClearHistory(self, event):
  423. """!Clear history of commands"""
  424. self.cmd_output.SetReadOnly(False)
  425. self.cmd_output.ClearAll()
  426. self.cmd_output.SetReadOnly(True)
  427. self.console_progressbar.SetValue(0)
  428. def SaveHistory(self, event):
  429. """!Save history of commands"""
  430. self.history = self.cmd_output.GetSelectedText()
  431. if self.history == '':
  432. self.history = self.cmd_output.GetText()
  433. # add newline if needed
  434. if len(self.history) > 0 and self.history[-1] != '\n':
  435. self.history += '\n'
  436. wildcard = "Text file (*.txt)|*.txt"
  437. dlg = wx.FileDialog(
  438. self, message=_("Save file as..."), defaultDir=os.getcwd(),
  439. defaultFile="grass_cmd_history.txt", wildcard=wildcard,
  440. style=wx.SAVE|wx.FD_OVERWRITE_PROMPT)
  441. # Show the dialog and retrieve the user response. If it is the OK response,
  442. # process the data.
  443. if dlg.ShowModal() == wx.ID_OK:
  444. path = dlg.GetPath()
  445. output = open(path, "w")
  446. output.write(self.history)
  447. output.close()
  448. dlg.Destroy()
  449. def GetCmd(self):
  450. """!Get running command or None"""
  451. return self.requestQ.get()
  452. def OnSearchModule(self, event):
  453. """!Search module by keywords or description"""
  454. text = event.GetString()
  455. if not text:
  456. self.cmd_prompt.SetFilter(None)
  457. return
  458. modules = dict()
  459. iFound = 0
  460. for module, data in self.cmd_prompt.moduleDesc.iteritems():
  461. found = False
  462. if self.searchBy.GetSelection() == 0: # -> description
  463. if text in data['desc']:
  464. found = True
  465. else: # -> keywords
  466. if self.cmd_prompt.CheckKey(text, data['keywords']):
  467. found = True
  468. if found:
  469. iFound += 1
  470. try:
  471. group, name = module.split('.')
  472. except ValueError:
  473. continue # TODO
  474. if not modules.has_key(group):
  475. modules[group] = list()
  476. modules[group].append(name)
  477. self.parent.statusbar.SetStatusText(_("%d modules found") % iFound)
  478. self.cmd_prompt.SetFilter(modules)
  479. def OnCmdOutput(self, event):
  480. """!Print command output"""
  481. message = event.text
  482. type = event.type
  483. if self._notebook.GetSelection() != self.parent.goutput.pageid:
  484. textP = self._notebook.GetPageText(self.parent.goutput.pageid)
  485. if textP[-1] != ')':
  486. textP += ' (...)'
  487. self._notebook.SetPageText(self.parent.goutput.pageid,
  488. textP)
  489. # message prefix
  490. if type == 'warning':
  491. messege = 'WARNING: ' + message
  492. elif type == 'error':
  493. message = 'ERROR: ' + message
  494. p1 = self.cmd_output.GetEndStyled()
  495. self.cmd_output.GotoPos(p1)
  496. if '\b' in message:
  497. if self.linepos < 0:
  498. self.linepos = p1
  499. last_c = ''
  500. for c in message:
  501. if c == '\b':
  502. self.linepos -= 1
  503. else:
  504. if c == '\r':
  505. pos = self.cmd_output.GetCurLine()[1]
  506. # self.cmd_output.SetCurrentPos(pos)
  507. else:
  508. self.cmd_output.SetCurrentPos(self.linepos)
  509. self.cmd_output.ReplaceSelection(c)
  510. self.linepos = self.cmd_output.GetCurrentPos()
  511. if c != ' ':
  512. last_c = c
  513. if last_c not in ('0123456789'):
  514. self.cmd_output.AddTextWrapped('\n', wrap=None)
  515. self.linepos = -1
  516. else:
  517. self.linepos = -1 # don't force position
  518. if '\n' not in message:
  519. self.cmd_output.AddTextWrapped(message, wrap=60)
  520. else:
  521. self.cmd_output.AddTextWrapped(message, wrap=None)
  522. p2 = self.cmd_output.GetCurrentPos()
  523. if p2 >= p1:
  524. self.cmd_output.StartStyling(p1, 0xff)
  525. if type == 'error':
  526. self.cmd_output.SetStyling(p2 - p1, self.cmd_output.StyleError)
  527. elif type == 'warning':
  528. self.cmd_output.SetStyling(p2 - p1, self.cmd_output.StyleWarning)
  529. elif type == 'message':
  530. self.cmd_output.SetStyling(p2 - p1, self.cmd_output.StyleMessage)
  531. else: # unknown
  532. self.cmd_output.SetStyling(p2 - p1, self.cmd_output.StyleUnknown)
  533. self.cmd_output.EnsureCaretVisible()
  534. def OnCmdProgress(self, event):
  535. """!Update progress message info"""
  536. self.console_progressbar.SetValue(event.value)
  537. def OnCmdAbort(self, event):
  538. """!Abort running command"""
  539. self.cmdThread.abort()
  540. def OnCmdRun(self, event):
  541. """!Run command"""
  542. if self.parent.GetName() == 'Modeler':
  543. self.parent.GetModel().GetActions()[event.pid].Update(running = True)
  544. self.WriteCmdLog('(%s)\n%s' % (str(time.ctime()), ' '.join(event.cmd)))
  545. def OnCmdDone(self, event):
  546. """!Command done (or aborted)"""
  547. if self.parent.GetName() == 'Modeler':
  548. self.parent.GetModel().GetActions()[event.pid].Update(running = False)
  549. if event.aborted:
  550. # Thread aborted (using our convention of None return)
  551. self.WriteLog(_('Please note that the data are left in incosistent stage '
  552. 'and can be corrupted'), self.cmd_output.StyleWarning)
  553. self.WriteCmdLog('(%s) %s (%d sec)' % (str(time.ctime()),
  554. _('Command aborted'),
  555. (time.time() - event.time)))
  556. # pid=self.cmdThread.requestId)
  557. self.btn_abort.Enable(False)
  558. else:
  559. try:
  560. # Process results here
  561. self.WriteCmdLog('(%s) %s (%d sec)' % (str(time.ctime()),
  562. _('Command finished'),
  563. (time.time() - event.time)))
  564. except KeyError:
  565. # stopped deamon
  566. pass
  567. self.btn_abort.Enable(False)
  568. if event.onDone:
  569. event.onDone(returncode = event.returncode)
  570. self.console_progressbar.SetValue(0) # reset progress bar on '0%'
  571. self.cmd_output_timer.Stop()
  572. # set focus on prompt
  573. if self.parent.GetName() == "LayerManager":
  574. self.cmd_prompt.SetFocus()
  575. self.btn_abort.Enable(False)
  576. else:
  577. # updated command dialog
  578. dialog = self.parent.parent
  579. if hasattr(self.parent.parent, "btn_abort"):
  580. dialog.btn_abort.Enable(False)
  581. if hasattr(self.parent.parent, "btn_cancel"):
  582. dialog.btn_cancel.Enable(True)
  583. if hasattr(self.parent.parent, "btn_clipboard"):
  584. dialog.btn_clipboard.Enable(True)
  585. if hasattr(self.parent.parent, "btn_help"):
  586. dialog.btn_help.Enable(True)
  587. if hasattr(self.parent.parent, "btn_run"):
  588. dialog.btn_run.Enable(True)
  589. if event.returncode == 0 and \
  590. not event.aborted and hasattr(dialog, "addbox") and \
  591. dialog.addbox.IsChecked():
  592. # add new map into layer tree
  593. if dialog.outputType in ('raster', 'vector'):
  594. # add layer into layer tree
  595. cmd = dialog.notebookpanel.createCmd(ignoreErrors = True)
  596. name = utils.GetLayerNameFromCmd(cmd, fullyQualified=True, param='output')
  597. winName = self.parent.parent.parent.GetName()
  598. if winName == 'LayerManager':
  599. mapTree = self.parent.parent.parent.curr_page.maptree
  600. else: # GMConsole
  601. mapTree = self.parent.parent.parent.parent.curr_page.maptree
  602. if not mapTree.GetMap().GetListOfLayers(l_name = name):
  603. if dialog.outputType == 'raster':
  604. lcmd = ['d.rast',
  605. 'map=%s' % name]
  606. else:
  607. lcmd = ['d.vect',
  608. 'map=%s' % name]
  609. mapTree.AddLayer(ltype=dialog.outputType,
  610. lcmd=lcmd,
  611. lname=name)
  612. if hasattr(dialog, "get_dcmd") and \
  613. dialog.get_dcmd is None and \
  614. dialog.closebox.IsChecked():
  615. time.sleep(1)
  616. dialog.Close()
  617. event.Skip()
  618. def OnProcessPendingOutputWindowEvents(self, event):
  619. self.ProcessPendingEvents()
  620. class GMStdout:
  621. """!GMConsole standard output
  622. Based on FrameOutErr.py
  623. Name: FrameOutErr.py
  624. Purpose: Redirecting stdout / stderr
  625. Author: Jean-Michel Fauth, Switzerland
  626. Copyright: (c) 2005-2007 Jean-Michel Fauth
  627. Licence: GPL
  628. """
  629. def __init__(self, parent):
  630. self.parent = parent # GMConsole
  631. def write(self, s):
  632. if len(s) == 0 or s == '\n':
  633. return
  634. for line in s.splitlines():
  635. if len(line) == 0:
  636. continue
  637. evt = wxCmdOutput(text=line + '\n',
  638. type='')
  639. wx.PostEvent(self.parent.cmd_output, evt)
  640. class GMStderr:
  641. """!GMConsole standard error output
  642. Based on FrameOutErr.py
  643. Name: FrameOutErr.py
  644. Purpose: Redirecting stdout / stderr
  645. Author: Jean-Michel Fauth, Switzerland
  646. Copyright: (c) 2005-2007 Jean-Michel Fauth
  647. Licence: GPL
  648. """
  649. def __init__(self, parent):
  650. self.parent = parent # GMConsole
  651. self.type = ''
  652. self.message = ''
  653. self.printMessage = False
  654. def write(self, s):
  655. if "GtkPizza" in s:
  656. return
  657. # remove/replace escape sequences '\b' or '\r' from stream
  658. progressValue = -1
  659. for line in s.splitlines():
  660. if len(line) == 0:
  661. continue
  662. if 'GRASS_INFO_PERCENT' in line:
  663. value = int(line.rsplit(':', 1)[1].strip())
  664. if value >= 0 and value < 100:
  665. progressValue = value
  666. else:
  667. progressValue = 0
  668. elif 'GRASS_INFO_MESSAGE' in line:
  669. self.type = 'message'
  670. self.message += line.split(':', 1)[1].strip() + '\n'
  671. elif 'GRASS_INFO_WARNING' in line:
  672. self.type = 'warning'
  673. self.message += line.split(':', 1)[1].strip() + '\n'
  674. elif 'GRASS_INFO_ERROR' in line:
  675. self.type = 'error'
  676. self.message += line.split(':', 1)[1].strip() + '\n'
  677. elif 'GRASS_INFO_END' in line:
  678. self.printMessage = True
  679. elif self.type == '':
  680. if len(line) == 0:
  681. continue
  682. evt = wxCmdOutput(text=line,
  683. type='')
  684. wx.PostEvent(self.parent.cmd_output, evt)
  685. elif len(line) > 0:
  686. self.message += line.strip() + '\n'
  687. if self.printMessage and len(self.message) > 0:
  688. evt = wxCmdOutput(text=self.message,
  689. type=self.type)
  690. wx.PostEvent(self.parent.cmd_output, evt)
  691. self.type = ''
  692. self.message = ''
  693. self.printMessage = False
  694. # update progress message
  695. if progressValue > -1:
  696. # self.gmgauge.SetValue(progressValue)
  697. evt = wxCmdProgress(value=progressValue)
  698. wx.PostEvent(self.parent.console_progressbar, evt)
  699. class GMStc(wx.stc.StyledTextCtrl):
  700. """!Styled GMConsole
  701. Based on FrameOutErr.py
  702. Name: FrameOutErr.py
  703. Purpose: Redirecting stdout / stderr
  704. Author: Jean-Michel Fauth, Switzerland
  705. Copyright: (c) 2005-2007 Jean-Michel Fauth
  706. Licence: GPL
  707. """
  708. def __init__(self, parent, id, margin=False, wrap=None):
  709. wx.stc.StyledTextCtrl.__init__(self, parent, id)
  710. self.parent = parent
  711. self.SetUndoCollection(True)
  712. self.SetReadOnly(True)
  713. #
  714. # styles
  715. #
  716. self.SetStyle()
  717. #
  718. # line margins
  719. #
  720. # TODO print number only from cmdlog
  721. self.SetMarginWidth(1, 0)
  722. self.SetMarginWidth(2, 0)
  723. if margin:
  724. self.SetMarginType(0, wx.stc.STC_MARGIN_NUMBER)
  725. self.SetMarginWidth(0, 30)
  726. else:
  727. self.SetMarginWidth(0, 0)
  728. #
  729. # miscellaneous
  730. #
  731. self.SetViewWhiteSpace(False)
  732. self.SetTabWidth(4)
  733. self.SetUseTabs(False)
  734. self.UsePopUp(True)
  735. self.SetSelBackground(True, "#FFFF00")
  736. self.SetUseHorizontalScrollBar(True)
  737. #
  738. # bindings
  739. #
  740. self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
  741. def SetStyle(self):
  742. """!Set styles for styled text output windows with type face
  743. and point size selected by user (Courier New 10 is default)"""
  744. settings = preferences.Settings()
  745. typeface = settings.Get(group='display', key='outputfont', subkey='type')
  746. if typeface == "": typeface = "Courier New"
  747. typesize = settings.Get(group='display', key='outputfont', subkey='size')
  748. if typesize == None or typesize <= 0: typesize = 10
  749. typesize = float(typesize)
  750. self.StyleDefault = 0
  751. self.StyleDefaultSpec = "face:%s,size:%d,fore:#000000,back:#FFFFFF" % (typeface, typesize)
  752. self.StyleCommand = 1
  753. self.StyleCommandSpec = "face:%s,size:%d,,fore:#000000,back:#bcbcbc" % (typeface, typesize)
  754. self.StyleOutput = 2
  755. self.StyleOutputSpec = "face:%s,size:%d,,fore:#000000,back:#FFFFFF" % (typeface, typesize)
  756. # fatal error
  757. self.StyleError = 3
  758. self.StyleErrorSpec = "face:%s,size:%d,,fore:#7F0000,back:#FFFFFF" % (typeface, typesize)
  759. # warning
  760. self.StyleWarning = 4
  761. self.StyleWarningSpec = "face:%s,size:%d,,fore:#0000FF,back:#FFFFFF" % (typeface, typesize)
  762. # message
  763. self.StyleMessage = 5
  764. self.StyleMessageSpec = "face:%s,size:%d,,fore:#000000,back:#FFFFFF" % (typeface, typesize)
  765. # unknown
  766. self.StyleUnknown = 6
  767. self.StyleUnknownSpec = "face:%s,size:%d,,fore:#000000,back:#FFFFFF" % (typeface, typesize)
  768. # default and clear => init
  769. self.StyleSetSpec(wx.stc.STC_STYLE_DEFAULT, self.StyleDefaultSpec)
  770. self.StyleClearAll()
  771. self.StyleSetSpec(self.StyleCommand, self.StyleCommandSpec)
  772. self.StyleSetSpec(self.StyleOutput, self.StyleOutputSpec)
  773. self.StyleSetSpec(self.StyleError, self.StyleErrorSpec)
  774. self.StyleSetSpec(self.StyleWarning, self.StyleWarningSpec)
  775. self.StyleSetSpec(self.StyleMessage, self.StyleMessageSpec)
  776. self.StyleSetSpec(self.StyleUnknown, self.StyleUnknownSpec)
  777. def OnDestroy(self, evt):
  778. """!The clipboard contents can be preserved after
  779. the app has exited"""
  780. wx.TheClipboard.Flush()
  781. evt.Skip()
  782. def AddTextWrapped(self, txt, wrap=None):
  783. """!Add string to text area.
  784. String is wrapped and linesep is also added to the end
  785. of the string"""
  786. # allow writing to output window
  787. self.SetReadOnly(False)
  788. if wrap:
  789. txt = textwrap.fill(txt, wrap) + '\n'
  790. else:
  791. if txt[-1] != '\n':
  792. txt += '\n'
  793. if '\r' in txt:
  794. self.parent.linePos = -1
  795. for seg in txt.split('\r'):
  796. if self.parent.linePos > -1:
  797. self.SetCurrentPos(self.parent.linePos)
  798. self.ReplaceSelection(seg)
  799. else:
  800. self.parent.linePos = self.GetCurrentPos()
  801. self.AddText(seg)
  802. else:
  803. self.parent.linePos = self.GetCurrentPos()
  804. try:
  805. self.AddText(txt)
  806. except UnicodeDecodeError:
  807. enc = UserSettings.Get(group='atm', key='encoding', subkey='value')
  808. if enc:
  809. txt = unicode(txt, enc)
  810. elif os.environ.has_key('GRASS_DB_ENCODING'):
  811. txt = unicode(txt, os.environ['GRASS_DB_ENCODING'])
  812. else:
  813. txt = _('Unable to encode text. Please set encoding in GUI preferences.') + '\n'
  814. self.AddText(txt)
  815. # reset output window to read only
  816. self.SetReadOnly(True)