goutput.py 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723
  1. """!
  2. @package gui_core.goutput
  3. @brief Command output widgets
  4. Classes:
  5. - goutput::GConsoleWindow
  6. - goutput::GStc
  7. - goutput::GConsoleFrame
  8. (C) 2007-2012 by the GRASS Development Team
  9. This program is free software under the GNU General Public License
  10. (>=v2). Read the file COPYING that comes with GRASS for details.
  11. @author Michael Barton (Arizona State University)
  12. @author Martin Landa <landa.martin gmail.com>
  13. @author Vaclav Petras <wenzeslaus gmail.com> (refactoring)
  14. @author Anna Kratochvilova <kratochanna gmail.com> (refactoring)
  15. """
  16. import os
  17. import sys
  18. import textwrap
  19. sys.path.append(os.path.join(os.environ['GISBASE'], "etc", "gui", "wxpython"))
  20. import wx
  21. from wx import stc
  22. from core.gcmd import GError, EncodeString
  23. from core.events import gShowNotification
  24. from core.gconsole import GConsole, \
  25. EVT_CMD_OUTPUT, EVT_CMD_PROGRESS, EVT_CMD_RUN, EVT_CMD_DONE, \
  26. EVT_WRITE_LOG, EVT_WRITE_CMD_LOG, EVT_WRITE_WARNING, EVT_WRITE_ERROR
  27. from gui_core.prompt import GPromptSTC, EVT_GPROMPT_RUN_CMD
  28. from core.settings import UserSettings
  29. from gui_core.widgets import SearchModuleWidget, EVT_MODULE_SELECTED
  30. from core.modulesdata import ModulesData
  31. GC_EMPTY = 0
  32. GC_SEARCH = 1
  33. GC_PROMPT = 2
  34. class GConsoleWindow(wx.SplitterWindow):
  35. """!Create and manage output console for commands run by GUI.
  36. """
  37. def __init__(self, parent, gconsole, margin = False,
  38. style = wx.TAB_TRAVERSAL | wx.FULL_REPAINT_ON_RESIZE,
  39. gcstyle = GC_EMPTY,
  40. **kwargs):
  41. """!
  42. @param parent gui parent
  43. @param margin use margin in output pane (GStc)
  44. @param style wx.SplitterWindow style
  45. @param gcstyle GConsole style
  46. (GC_EMPTY, GC_PROMPT to show command prompt,
  47. GC_SEARCH to show search widget)
  48. @param ignoredCmdPattern regular expression specifying commads
  49. to be ignored (e.g. @c '^d\..*' for display commands)
  50. """
  51. wx.SplitterWindow.__init__(self, parent, id = wx.ID_ANY, style = style, **kwargs)
  52. self.SetName("GConsole")
  53. self.panelOutput = wx.Panel(parent = self, id = wx.ID_ANY)
  54. self.panelPrompt = wx.Panel(parent = self, id = wx.ID_ANY)
  55. # initialize variables
  56. self.parent = parent # GMFrame | CmdPanel | ?
  57. self._gconsole = gconsole
  58. self._gcstyle = gcstyle
  59. self.lineWidth = 80
  60. # progress bar
  61. self.progressbar = wx.Gauge(parent = self.panelOutput, id = wx.ID_ANY,
  62. range = 100, pos = (110, 50), size = (-1, 25),
  63. style = wx.GA_HORIZONTAL)
  64. self._gconsole.Bind(EVT_CMD_PROGRESS, self.OnCmdProgress)
  65. self._gconsole.Bind(EVT_CMD_OUTPUT, self.OnCmdOutput)
  66. self._gconsole.Bind(EVT_CMD_RUN, self.OnCmdRun)
  67. self._gconsole.Bind(EVT_CMD_DONE, self.OnCmdDone)
  68. self._gconsole.Bind(EVT_WRITE_LOG,
  69. lambda event:
  70. self.WriteLog(text = event.text,
  71. wrap = event.wrap,
  72. switchPage = event.switchPage,
  73. priority = event.priority))
  74. self._gconsole.Bind(EVT_WRITE_CMD_LOG,
  75. lambda event:
  76. self.WriteCmdLog(line = event.line,
  77. pid = event.pid,
  78. switchPage = event.switchPage))
  79. self._gconsole.Bind(EVT_WRITE_WARNING,
  80. lambda event:
  81. self.WriteWarning(line = event.line))
  82. self._gconsole.Bind(EVT_WRITE_ERROR,
  83. lambda event:
  84. self.WriteError(line = event.line))
  85. # text control for command output
  86. self.cmdOutput = GStc(parent = self.panelOutput, id = wx.ID_ANY, margin = margin,
  87. wrap = None)
  88. # information about available modules
  89. modulesData = ModulesData()
  90. # search & command prompt
  91. # move to the if below
  92. # search depends on cmd prompt
  93. self.cmdPrompt = GPromptSTC(parent = self, modulesData = modulesData)
  94. self.cmdPrompt.Bind(EVT_GPROMPT_RUN_CMD,
  95. lambda event:
  96. self._gconsole.RunCmd(command = event.cmd))
  97. if not self._gcstyle & GC_PROMPT:
  98. self.cmdPrompt.Hide()
  99. if self._gcstyle & GC_SEARCH:
  100. self.infoCollapseLabelExp = _("Click here to show search module engine")
  101. self.infoCollapseLabelCol = _("Click here to hide search module engine")
  102. self.searchPane = wx.CollapsiblePane(parent = self.panelOutput,
  103. label = self.infoCollapseLabelExp,
  104. style = wx.CP_DEFAULT_STYLE |
  105. wx.CP_NO_TLW_RESIZE | wx.EXPAND)
  106. self.MakeSearchPaneContent(self.searchPane.GetPane(), modulesData)
  107. self.searchPane.Collapse(True)
  108. self.Bind(wx.EVT_COLLAPSIBLEPANE_CHANGED, self.OnSearchPaneChanged, self.searchPane)
  109. self.search.Bind(EVT_MODULE_SELECTED,
  110. lambda event:
  111. self.cmdPrompt.SetTextAndFocus(event.name + ' '))
  112. else:
  113. self.search = None
  114. self.outputBox = wx.StaticBox(parent = self.panelOutput, id = wx.ID_ANY,
  115. label = " %s " % _("Output window"))
  116. if self._gcstyle & GC_PROMPT:
  117. cmdLabel = _("Command prompt")
  118. else:
  119. cmdLabel = _("Command")
  120. self.cmdBox = wx.StaticBox(parent = self.panelOutput, id = wx.ID_ANY,
  121. label = " %s " % cmdLabel)
  122. # buttons
  123. self.btnOutputClear = wx.Button(parent = self.panelOutput, id = wx.ID_CLEAR)
  124. self.btnOutputClear.SetToolTipString(_("Clear output window content"))
  125. self.btnCmdClear = wx.Button(parent = self.panelOutput, id = wx.ID_CLEAR)
  126. self.btnCmdClear.SetToolTipString(_("Clear command prompt content"))
  127. self.btnOutputSave = wx.Button(parent = self.panelOutput, id = wx.ID_SAVE)
  128. self.btnOutputSave.SetToolTipString(_("Save output window content to the file"))
  129. self.btnCmdAbort = wx.Button(parent = self.panelOutput, id = wx.ID_STOP)
  130. self.btnCmdAbort.SetToolTipString(_("Abort running command"))
  131. self.btnCmdAbort.Enable(False)
  132. self.btnCmdProtocol = wx.ToggleButton(parent = self.panelOutput, id = wx.ID_ANY,
  133. label = _("&Protocol"),
  134. size = self.btnCmdClear.GetSize())
  135. self.btnCmdProtocol.SetToolTipString(_("Toggle to save list of executed commands into file; "
  136. "content saved when switching off."))
  137. if not self._gcstyle & GC_PROMPT:
  138. self.btnCmdClear.Hide()
  139. self.btnCmdProtocol.Hide()
  140. self.btnCmdClear.Bind(wx.EVT_BUTTON, self.cmdPrompt.OnCmdErase)
  141. self.btnOutputClear.Bind(wx.EVT_BUTTON, self.OnOutputClear)
  142. self.btnOutputSave.Bind(wx.EVT_BUTTON, self.OnOutputSave)
  143. self.btnCmdAbort.Bind(wx.EVT_BUTTON, self._gconsole.OnCmdAbort)
  144. self.btnCmdProtocol.Bind(wx.EVT_TOGGLEBUTTON, self.OnCmdProtocol)
  145. self._layout()
  146. def _layout(self):
  147. """!Do layout"""
  148. outputSizer = wx.BoxSizer(wx.VERTICAL)
  149. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  150. outBtnSizer = wx.StaticBoxSizer(self.outputBox, wx.HORIZONTAL)
  151. cmdBtnSizer = wx.StaticBoxSizer(self.cmdBox, wx.HORIZONTAL)
  152. if self._gcstyle & GC_PROMPT:
  153. promptSizer = wx.BoxSizer(wx.VERTICAL)
  154. promptSizer.Add(item = self.cmdPrompt, proportion = 1,
  155. flag = wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border = 3)
  156. if self._gcstyle & GC_SEARCH:
  157. outputSizer.Add(item = self.searchPane, proportion = 0,
  158. flag = wx.EXPAND | wx.ALL, border = 3)
  159. outputSizer.Add(item = self.cmdOutput, proportion = 1,
  160. flag = wx.EXPAND | wx.ALL, border = 3)
  161. outputSizer.Add(item = self.progressbar, proportion = 0,
  162. flag = wx.EXPAND | wx.LEFT | wx.RIGHT, border = 3)
  163. outBtnSizer.Add(item = self.btnOutputClear, proportion = 1,
  164. flag = wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT, border = 5)
  165. outBtnSizer.Add(item = self.btnOutputSave, proportion = 1,
  166. flag = wx.ALIGN_RIGHT | wx.RIGHT, border = 5)
  167. cmdBtnSizer.Add(item = self.btnCmdProtocol, proportion = 1,
  168. flag = wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT, border = 5)
  169. cmdBtnSizer.Add(item = self.btnCmdClear, proportion = 1,
  170. flag = wx.ALIGN_CENTER | wx.RIGHT, border = 5)
  171. cmdBtnSizer.Add(item = self.btnCmdAbort, proportion = 1,
  172. flag = wx.ALIGN_CENTER | wx.RIGHT, border = 5)
  173. if self._gcstyle & GC_PROMPT:
  174. proportion = (2, 3)
  175. else:
  176. proportion = (1, 1)
  177. btnSizer.Add(item = outBtnSizer, proportion = proportion[0],
  178. flag = wx.ALL | wx.ALIGN_CENTER, border = 5)
  179. btnSizer.Add(item = cmdBtnSizer, proportion = proportion[1],
  180. flag = wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM | wx.RIGHT, border = 5)
  181. outputSizer.Add(item = btnSizer, proportion = 0,
  182. flag = wx.EXPAND)
  183. outputSizer.Fit(self)
  184. outputSizer.SetSizeHints(self)
  185. self.panelOutput.SetSizer(outputSizer)
  186. # eliminate gtk_widget_size_allocate() warnings
  187. outputSizer.SetVirtualSizeHints(self.panelOutput)
  188. if self._gcstyle & GC_PROMPT:
  189. promptSizer.Fit(self)
  190. promptSizer.SetSizeHints(self)
  191. self.panelPrompt.SetSizer(promptSizer)
  192. # split window
  193. if self._gcstyle & GC_PROMPT:
  194. self.SplitHorizontally(self.panelOutput, self.panelPrompt, -50)
  195. else:
  196. self.SplitHorizontally(self.panelOutput, self.panelPrompt, -45)
  197. self.Unsplit()
  198. self.SetMinimumPaneSize(self.btnCmdClear.GetSize()[1] + 25)
  199. self.SetSashGravity(1.0)
  200. # layout
  201. self.SetAutoLayout(True)
  202. self.Layout()
  203. def MakeSearchPaneContent(self, pane, modulesData):
  204. """!Create search pane"""
  205. border = wx.BoxSizer(wx.VERTICAL)
  206. self.search = SearchModuleWidget(parent = pane,
  207. modulesData = modulesData)
  208. border.Add(item = self.search, proportion = 0,
  209. flag = wx.EXPAND | wx.ALL, border = 1)
  210. pane.SetSizer(border)
  211. border.Fit(pane)
  212. def OnSearchPaneChanged(self, event):
  213. """!Collapse search module box"""
  214. if self.searchPane.IsExpanded():
  215. self.searchPane.SetLabel(self.infoCollapseLabelCol)
  216. else:
  217. self.searchPane.SetLabel(self.infoCollapseLabelExp)
  218. self.panelOutput.Layout()
  219. self.panelOutput.SendSizeEvent()
  220. def GetPanel(self, prompt = True):
  221. """!Get panel
  222. @param prompt get prompt / output panel
  223. @return wx.Panel reference
  224. """
  225. if prompt:
  226. return self.panelPrompt
  227. return self.panelOutput
  228. def WriteLog(self, text, style = None, wrap = None,
  229. switchPage = False, priority = 1):
  230. """!Generic method for writing log message in
  231. given style
  232. @param line text line
  233. @param style text style (see GStc)
  234. @param stdout write to stdout or stderr
  235. @param switchPage for backward compatibility
  236. (replace by priority: False=1, True=2)
  237. @param priority priority of this message
  238. (0=no priority, 1=normal, 2=medium, 3=high)
  239. """
  240. self.cmdOutput.SetStyle()
  241. # documenting old behavior/implementation:
  242. # switch notebook if required
  243. # now, let user to bind to the old event
  244. if not style:
  245. style = self.cmdOutput.StyleDefault
  246. # p1 = self.cmdOutput.GetCurrentPos()
  247. p1 = self.cmdOutput.GetEndStyled()
  248. # self.cmdOutput.GotoPos(p1)
  249. self.cmdOutput.DocumentEnd()
  250. for line in text.splitlines():
  251. # fill space
  252. if len(line) < self.lineWidth:
  253. diff = self.lineWidth - len(line)
  254. line += diff * ' '
  255. self.cmdOutput.AddTextWrapped(line, wrap = wrap) # adds '\n'
  256. p2 = self.cmdOutput.GetCurrentPos()
  257. self.cmdOutput.StartStyling(p1, 0xff)
  258. self.cmdOutput.SetStyling(p2 - p1, style)
  259. self.cmdOutput.EnsureCaretVisible()
  260. def WriteCmdLog(self, line, pid = None, switchPage = True):
  261. """!Write message in selected style
  262. @param line message to be printed
  263. @param pid process pid or None
  264. @param switchPage True to switch page
  265. """
  266. if pid:
  267. line = '(' + str(pid) + ') ' + line
  268. self.WriteLog(line, style = self.cmdOutput.StyleCommand, switchPage = switchPage)
  269. def WriteWarning(self, line):
  270. """!Write message in warning style"""
  271. self.WriteLog(line, style = self.cmdOutput.StyleWarning, switchPage = True)
  272. def WriteError(self, line):
  273. """!Write message in error style"""
  274. self.WriteLog(line, style = self.cmdOutput.StyleError, switchPage = True)
  275. def OnOutputClear(self, event):
  276. """!Clear content of output window"""
  277. self.cmdOutput.SetReadOnly(False)
  278. self.cmdOutput.ClearAll()
  279. self.cmdOutput.SetReadOnly(True)
  280. self.progressbar.SetValue(0)
  281. def GetProgressBar(self):
  282. """!Return progress bar widget"""
  283. return self.progressbar
  284. def OnOutputSave(self, event):
  285. """!Save (selected) text from output window to the file"""
  286. text = self.cmdOutput.GetSelectedText()
  287. if not text:
  288. text = self.cmdOutput.GetText()
  289. # add newline if needed
  290. if len(text) > 0 and text[-1] != '\n':
  291. text += '\n'
  292. dlg = wx.FileDialog(self, message = _("Save file as..."),
  293. defaultFile = "grass_cmd_output.txt",
  294. wildcard = _("%(txt)s (*.txt)|*.txt|%(files)s (*)|*") %
  295. {'txt': _("Text files"), 'files': _("Files")},
  296. style = wx.SAVE | wx.FD_OVERWRITE_PROMPT)
  297. # Show the dialog and retrieve the user response. If it is the OK response,
  298. # process the data.
  299. if dlg.ShowModal() == wx.ID_OK:
  300. path = dlg.GetPath()
  301. try:
  302. output = open(path, "w")
  303. output.write(text)
  304. except IOError, e:
  305. GError(_("Unable to write file '%(path)s'.\n\nDetails: %(error)s") % {'path': path, 'error': e})
  306. finally:
  307. output.close()
  308. message = _("Commands output saved into '%s'") % path
  309. wx.PostEvent(self, gShowNotification(self.GetId(), message = message))
  310. dlg.Destroy()
  311. def SetCopyingOfSelectedText(self, copy):
  312. """!Enable or disable copying of selected text in to clipboard.
  313. Effects prompt and output.
  314. @param copy True for enable, False for disable
  315. """
  316. if copy:
  317. self.cmdPrompt.Bind(stc.EVT_STC_PAINTED, self.cmdPrompt.OnTextSelectionChanged)
  318. self.cmdOutput.Bind(stc.EVT_STC_PAINTED, self.cmdOutput.OnTextSelectionChanged)
  319. else:
  320. self.cmdPrompt.Unbind(stc.EVT_STC_PAINTED)
  321. self.cmdOutput.Unbind(stc.EVT_STC_PAINTED)
  322. def OnCmdOutput(self, event):
  323. """!Print command output
  324. Posts event EVT_OUTPUT_TEXT with priority attribute set to 1.
  325. """
  326. message = event.text
  327. type = event.type
  328. self.cmdOutput.AddStyledMessage(message, type)
  329. # documenting old behavior/implementation:
  330. # add elipses if not active
  331. def OnCmdProgress(self, event):
  332. """!Update progress message info"""
  333. self.progressbar.SetValue(event.value)
  334. event.Skip()
  335. def CmdProtocolSave(self):
  336. """Save commands protocol into the file"""
  337. if not hasattr(self, 'cmdFileProtocol'):
  338. return # it should not happen
  339. try:
  340. output = open(self.cmdFileProtocol, "w")
  341. cmds = self.cmdPrompt.GetCommands()
  342. output.write('\n'.join(cmds))
  343. if len(cmds) > 0:
  344. output.write('\n')
  345. except IOError, e:
  346. GError(_("Unable to write file '%(filePath)s'.\n\nDetails: %(error)s") %
  347. {'filePath': self.cmdFileProtocol, 'error': e})
  348. finally:
  349. output.close()
  350. message = _("Commands protocol saved into '%s'") % self.cmdFileProtocol
  351. wx.PostEvent(self, gShowNotification(self.GetId(), message = message))
  352. del self.cmdFileProtocol
  353. def OnCmdProtocol(self, event = None):
  354. """!Save commands into file"""
  355. if not event.IsChecked():
  356. # stop capturing commands, save list of commands to the
  357. # protocol file
  358. self.CmdProtocolSave()
  359. else:
  360. # start capturing commands
  361. self.cmdPrompt.ClearCommands()
  362. # ask for the file
  363. dlg = wx.FileDialog(self, message = _("Save file as..."),
  364. defaultFile = "grass_cmd_protocol.txt",
  365. wildcard = _("%(txt)s (*.txt)|*.txt|%(files)s (*)|*") %
  366. {'txt': _("Text files"), 'files': _("Files")},
  367. style = wx.SAVE | wx.FD_OVERWRITE_PROMPT)
  368. if dlg.ShowModal() == wx.ID_OK:
  369. self.cmdFileProtocol = dlg.GetPath()
  370. else:
  371. wx.CallAfter(self.btnCmdProtocol.SetValue, False)
  372. dlg.Destroy()
  373. event.Skip()
  374. def OnCmdRun(self, event):
  375. """!Run command"""
  376. self.btnCmdAbort.Enable()
  377. event.Skip()
  378. def OnCmdDone(self, event):
  379. """!Command done (or aborted)
  380. """
  381. self.btnCmdAbort.Enable(False)
  382. self.progressbar.SetValue(0) # reset progress bar on '0%'
  383. event.Skip()
  384. def ResetFocus(self):
  385. """!Reset focus"""
  386. self.cmdPrompt.SetFocus()
  387. def GetPrompt(self):
  388. """!Get prompt"""
  389. return self.cmdPrompt
  390. class GStc(stc.StyledTextCtrl):
  391. """!Styled text control for GRASS stdout and stderr.
  392. Based on FrameOutErr.py
  393. Name: FrameOutErr.py
  394. Purpose: Redirecting stdout / stderr
  395. Author: Jean-Michel Fauth, Switzerland
  396. Copyright: (c) 2005-2007 Jean-Michel Fauth
  397. Licence: GPL
  398. """
  399. def __init__(self, parent, id, margin = False, wrap = None):
  400. stc.StyledTextCtrl.__init__(self, parent, id)
  401. self.parent = parent
  402. self.SetUndoCollection(True)
  403. self.SetReadOnly(True)
  404. # remember position of line begining (used for '\r')
  405. self.linePos = -1
  406. #
  407. # styles
  408. #
  409. self.SetStyle()
  410. #
  411. # line margins
  412. #
  413. # TODO print number only from cmdlog
  414. self.SetMarginWidth(1, 0)
  415. self.SetMarginWidth(2, 0)
  416. if margin:
  417. self.SetMarginType(0, stc.STC_MARGIN_NUMBER)
  418. self.SetMarginWidth(0, 30)
  419. else:
  420. self.SetMarginWidth(0, 0)
  421. #
  422. # miscellaneous
  423. #
  424. self.SetViewWhiteSpace(False)
  425. self.SetTabWidth(4)
  426. self.SetUseTabs(False)
  427. self.UsePopUp(True)
  428. self.SetSelBackground(True, "#FFFF00")
  429. self.SetUseHorizontalScrollBar(True)
  430. #
  431. # bindings
  432. #
  433. self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
  434. def OnTextSelectionChanged(self, event):
  435. """!Copy selected text to clipboard and skip event.
  436. The same function is in TextCtrlAutoComplete class (prompt.py).
  437. """
  438. wx.CallAfter(self.Copy)
  439. event.Skip()
  440. def SetStyle(self):
  441. """!Set styles for styled text output windows with type face
  442. and point size selected by user (Courier New 10 is default)"""
  443. typeface = UserSettings.Get(group = 'appearance', key = 'outputfont', subkey = 'type')
  444. if typeface == "":
  445. typeface = "Courier New"
  446. typesize = UserSettings.Get(group = 'appearance', key = 'outputfont', subkey = 'size')
  447. if typesize == None or typesize <= 0:
  448. typesize = 10
  449. typesize = float(typesize)
  450. self.StyleDefault = 0
  451. self.StyleDefaultSpec = "face:%s,size:%d,fore:#000000,back:#FFFFFF" % (typeface, typesize)
  452. self.StyleCommand = 1
  453. self.StyleCommandSpec = "face:%s,size:%d,,fore:#000000,back:#bcbcbc" % (typeface, typesize)
  454. self.StyleOutput = 2
  455. self.StyleOutputSpec = "face:%s,size:%d,,fore:#000000,back:#FFFFFF" % (typeface, typesize)
  456. # fatal error
  457. self.StyleError = 3
  458. self.StyleErrorSpec = "face:%s,size:%d,,fore:#7F0000,back:#FFFFFF" % (typeface, typesize)
  459. # warning
  460. self.StyleWarning = 4
  461. self.StyleWarningSpec = "face:%s,size:%d,,fore:#0000FF,back:#FFFFFF" % (typeface, typesize)
  462. # message
  463. self.StyleMessage = 5
  464. self.StyleMessageSpec = "face:%s,size:%d,,fore:#000000,back:#FFFFFF" % (typeface, typesize)
  465. # unknown
  466. self.StyleUnknown = 6
  467. self.StyleUnknownSpec = "face:%s,size:%d,,fore:#000000,back:#FFFFFF" % (typeface, typesize)
  468. # default and clear => init
  469. self.StyleSetSpec(stc.STC_STYLE_DEFAULT, self.StyleDefaultSpec)
  470. self.StyleClearAll()
  471. self.StyleSetSpec(self.StyleCommand, self.StyleCommandSpec)
  472. self.StyleSetSpec(self.StyleOutput, self.StyleOutputSpec)
  473. self.StyleSetSpec(self.StyleError, self.StyleErrorSpec)
  474. self.StyleSetSpec(self.StyleWarning, self.StyleWarningSpec)
  475. self.StyleSetSpec(self.StyleMessage, self.StyleMessageSpec)
  476. self.StyleSetSpec(self.StyleUnknown, self.StyleUnknownSpec)
  477. def OnDestroy(self, evt):
  478. """!The clipboard contents can be preserved after
  479. the app has exited"""
  480. wx.TheClipboard.Flush()
  481. evt.Skip()
  482. def AddTextWrapped(self, txt, wrap = None):
  483. """!Add string to text area.
  484. String is wrapped and linesep is also added to the end
  485. of the string"""
  486. # allow writing to output window
  487. self.SetReadOnly(False)
  488. if wrap:
  489. txt = textwrap.fill(txt, wrap) + '\n'
  490. else:
  491. if txt[-1] != '\n':
  492. txt += '\n'
  493. if '\r' in txt:
  494. self.linePos = -1
  495. for seg in txt.split('\r'):
  496. if self.linePos > -1:
  497. self.SetCurrentPos(self.linePos)
  498. self.ReplaceSelection(seg)
  499. else:
  500. self.linePos = self.GetCurrentPos()
  501. self.AddText(seg)
  502. else:
  503. self.linePos = self.GetCurrentPos()
  504. try:
  505. self.AddText(txt)
  506. except UnicodeDecodeError:
  507. enc = UserSettings.Get(group = 'atm', key = 'encoding', subkey = 'value')
  508. if enc:
  509. txt = unicode(txt, enc)
  510. elif 'GRASS_DB_ENCODING' in os.environ:
  511. txt = unicode(txt, os.environ['GRASS_DB_ENCODING'])
  512. else:
  513. txt = EncodeString(txt)
  514. self.AddText(txt)
  515. # reset output window to read only
  516. self.SetReadOnly(True)
  517. def AddStyledMessage(self, message, style = None):
  518. """!Add message to text area.
  519. Handles messages with progress percentages.
  520. @param message message to be added
  521. @param style style of message, allowed values: 'message', 'warning', 'error' or None
  522. """
  523. # message prefix
  524. if style == 'warning':
  525. message = 'WARNING: ' + message
  526. elif style == 'error':
  527. message = 'ERROR: ' + message
  528. p1 = self.GetEndStyled()
  529. self.GotoPos(p1)
  530. # is this still needed?
  531. if '\b' in message:
  532. if self.linePos < 0:
  533. self.linePos = p1
  534. last_c = ''
  535. for c in message:
  536. if c == '\b':
  537. self.linePos -= 1
  538. else:
  539. if c == '\r':
  540. pos = self.GetCurLine()[1]
  541. # self.SetCurrentPos(pos)
  542. else:
  543. self.SetCurrentPos(self.linePos)
  544. self.ReplaceSelection(c)
  545. self.linePos = self.GetCurrentPos()
  546. if c != ' ':
  547. last_c = c
  548. if last_c not in ('0123456789'):
  549. self.AddTextWrapped('\n', wrap = None)
  550. self.linePos = -1
  551. else:
  552. self.linePos = -1 # don't force position
  553. if '\n' not in message:
  554. self.AddTextWrapped(message, wrap = 60)
  555. else:
  556. self.AddTextWrapped(message, wrap = None)
  557. p2 = self.GetCurrentPos()
  558. if p2 >= p1:
  559. self.StartStyling(p1, 0xff)
  560. if style == 'error':
  561. self.SetStyling(p2 - p1, self.StyleError)
  562. elif style == 'warning':
  563. self.SetStyling(p2 - p1, self.StyleWarning)
  564. elif style == 'message':
  565. self.SetStyling(p2 - p1, self.StyleMessage)
  566. else: # unknown
  567. self.SetStyling(p2 - p1, self.StyleUnknown)
  568. self.EnsureCaretVisible()
  569. class GConsoleFrame(wx.Frame):
  570. """!Standalone GConsole for testing only"""
  571. def __init__(self, parent, id = wx.ID_ANY, title = "GConsole Test Frame",
  572. style = wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL, **kwargs):
  573. wx.Frame.__init__(self, parent = parent, id = id, title = title)
  574. panel = wx.Panel(self, id = wx.ID_ANY)
  575. self.gconsole = GConsole(guiparent=self)
  576. self.goutput = GConsoleWindow(parent = panel, gconsole = self.gconsole,
  577. gcstyle = GC_SEARCH | GC_PROMPT)
  578. mainSizer = wx.BoxSizer(wx.VERTICAL)
  579. mainSizer.Add(item = self.goutput, proportion = 1, flag = wx.EXPAND, border = 0)
  580. panel.SetSizer(mainSizer)
  581. mainSizer.Fit(panel)
  582. self.SetMinSize((550, 500))
  583. def testGConsole():
  584. app = wx.PySimpleApp()
  585. frame = GConsoleFrame(parent = None)
  586. frame.Show()
  587. app.MainLoop()
  588. if __name__ == '__main__':
  589. testGConsole()