goutput.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779
  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 textwrap
  17. import wx
  18. from wx import stc
  19. from grass.pydispatch.signal import Signal
  20. # needed just for testing
  21. if __name__ == '__main__':
  22. from grass.script.setup import set_gui_path
  23. set_gui_path()
  24. from core.gcmd import GError
  25. from core.gconsole import GConsole, \
  26. EVT_CMD_OUTPUT, EVT_CMD_PROGRESS, EVT_CMD_RUN, EVT_CMD_DONE, \
  27. Notification
  28. from core.globalvar import CheckWxVersion, wxPythonPhoenix
  29. from gui_core.prompt import GPromptSTC
  30. from gui_core.wrap import Button, ClearButton, ToggleButton, StaticText, \
  31. StaticBox
  32. from core.settings import UserSettings
  33. GC_EMPTY = 0
  34. GC_PROMPT = 1
  35. class GConsoleWindow(wx.SplitterWindow):
  36. """Create and manage output console for commands run by GUI.
  37. """
  38. def __init__(self, parent, giface, gconsole, menuModel=None, margin=False,
  39. style=wx.TAB_TRAVERSAL | wx.FULL_REPAINT_ON_RESIZE,
  40. gcstyle=GC_EMPTY,
  41. **kwargs):
  42. """
  43. :param parent: gui parent
  44. :param gconsole: console logic
  45. :param menuModel: tree model of modules (from menu)
  46. :param margin: use margin in output pane (GStc)
  47. :param style: wx.SplitterWindow style
  48. :param gcstyle: GConsole style
  49. (GC_EMPTY, GC_PROMPT to show command prompt)
  50. """
  51. wx.SplitterWindow.__init__(
  52. self, parent, id=wx.ID_ANY, style=style, **kwargs)
  53. self.SetName("GConsole")
  54. self.panelOutput = wx.Panel(parent=self, id=wx.ID_ANY)
  55. self.panelProgress = wx.Panel(
  56. parent=self.panelOutput,
  57. id=wx.ID_ANY,
  58. name='progressPanel')
  59. self.panelPrompt = wx.Panel(parent=self, id=wx.ID_ANY)
  60. # initialize variables
  61. self.parent = parent # GMFrame | CmdPanel | ?
  62. self._gconsole = gconsole
  63. self._menuModel = menuModel
  64. self._gcstyle = gcstyle
  65. self.lineWidth = 80
  66. # signal which requests showing of a notification
  67. self.showNotification = Signal("GConsoleWindow.showNotification")
  68. # signal emitted when text appears in the console
  69. # parameter 'notification' suggests form of notification (according to
  70. # core.giface.Notification)
  71. self.contentChanged = Signal("GConsoleWindow.contentChanged")
  72. # progress bar
  73. self.progressbar = wx.Gauge(parent=self.panelProgress, id=wx.ID_ANY,
  74. range=100, pos=(110, 50), size=(-1, 25),
  75. style=wx.GA_HORIZONTAL)
  76. self._gconsole.Bind(EVT_CMD_PROGRESS, self.OnCmdProgress)
  77. self._gconsole.Bind(EVT_CMD_OUTPUT, self.OnCmdOutput)
  78. self._gconsole.Bind(EVT_CMD_RUN, self.OnCmdRun)
  79. self._gconsole.Bind(EVT_CMD_DONE, self.OnCmdDone)
  80. self._gconsole.writeLog.connect(self.WriteLog)
  81. self._gconsole.writeCmdLog.connect(self.WriteCmdLog)
  82. self._gconsole.writeWarning.connect(self.WriteWarning)
  83. self._gconsole.writeError.connect(self.WriteError)
  84. # text control for command output
  85. self.cmdOutput = GStc(
  86. parent=self.panelOutput,
  87. id=wx.ID_ANY,
  88. margin=margin,
  89. wrap=None)
  90. # command prompt
  91. # move to the if below
  92. # search depends on cmd prompt
  93. self.cmdPrompt = GPromptSTC(
  94. parent=self, giface=giface, menuModel=self._menuModel
  95. )
  96. self.cmdPrompt.promptRunCmd.connect(lambda cmd:
  97. self._gconsole.RunCmd(command=cmd))
  98. self.cmdPrompt.showNotification.connect(self.showNotification)
  99. if not self._gcstyle & GC_PROMPT:
  100. self.cmdPrompt.Hide()
  101. if self._gcstyle & GC_PROMPT:
  102. cmdLabel = _("Command prompt")
  103. self.outputBox = StaticBox(
  104. parent=self.panelOutput,
  105. id=wx.ID_ANY,
  106. label=" %s " %
  107. _("Output window"))
  108. self.cmdBox = StaticBox(parent=self.panelOutput, id=wx.ID_ANY,
  109. label=" %s " % cmdLabel)
  110. # buttons
  111. self.btnOutputClear = ClearButton(parent=self.panelOutput)
  112. self.btnOutputClear.SetToolTip(_("Clear output window content"))
  113. self.btnCmdClear = ClearButton(parent=self.panelOutput)
  114. self.btnCmdClear.SetToolTip(_("Clear command prompt content"))
  115. self.btnOutputSave = Button(parent=self.panelOutput, id=wx.ID_SAVE)
  116. self.btnOutputSave.SetToolTip(
  117. _("Save output window content to the file"))
  118. self.btnCmdAbort = Button(parent=self.panelProgress, id=wx.ID_STOP)
  119. self.btnCmdAbort.SetToolTip(_("Abort running command"))
  120. self.btnCmdProtocol = ToggleButton(
  121. parent=self.panelOutput,
  122. id=wx.ID_ANY,
  123. label=_("&Log file"),
  124. size=self.btnCmdClear.GetSize())
  125. self.btnCmdProtocol.SetToolTip(_("Toggle to save list of executed commands into "
  126. "a file; content saved when switching off."))
  127. self.cmdFileProtocol = None
  128. if not self._gcstyle & GC_PROMPT:
  129. self.btnCmdClear.Hide()
  130. self.btnCmdProtocol.Hide()
  131. self.btnCmdClear.Bind(wx.EVT_BUTTON, self.cmdPrompt.OnCmdErase)
  132. self.btnOutputClear.Bind(wx.EVT_BUTTON, self.OnOutputClear)
  133. self.btnOutputSave.Bind(wx.EVT_BUTTON, self.OnOutputSave)
  134. self.btnCmdAbort.Bind(wx.EVT_BUTTON, self._gconsole.OnCmdAbort)
  135. self.btnCmdProtocol.Bind(wx.EVT_TOGGLEBUTTON, self.OnCmdProtocol)
  136. self._layout()
  137. def _layout(self):
  138. """Do layout"""
  139. self.outputSizer = wx.BoxSizer(wx.VERTICAL)
  140. progressSizer = wx.BoxSizer(wx.HORIZONTAL)
  141. btnSizer = wx.BoxSizer(wx.HORIZONTAL)
  142. if self._gcstyle & GC_PROMPT:
  143. outBtnSizer = wx.StaticBoxSizer(self.outputBox, wx.HORIZONTAL)
  144. cmdBtnSizer = wx.StaticBoxSizer(self.cmdBox, wx.HORIZONTAL)
  145. else:
  146. outBtnSizer = wx.BoxSizer(wx.HORIZONTAL)
  147. cmdBtnSizer = wx.BoxSizer(wx.HORIZONTAL)
  148. if self._gcstyle & GC_PROMPT:
  149. promptSizer = wx.BoxSizer(wx.VERTICAL)
  150. promptSizer.Add(self.cmdPrompt, proportion=1,
  151. flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP,
  152. border=3)
  153. helpText = StaticText(
  154. self.panelPrompt, id=wx.ID_ANY,
  155. label="Press Tab to display command help, Ctrl+Space to autocomplete")
  156. helpText.SetForegroundColour(
  157. wx.SystemSettings.GetColour(
  158. wx.SYS_COLOUR_GRAYTEXT))
  159. promptSizer.Add(helpText,
  160. proportion=0, flag=wx.EXPAND | wx.LEFT, border=5)
  161. self.outputSizer.Add(self.cmdOutput, proportion=1,
  162. flag=wx.EXPAND | wx.ALL, border=3)
  163. if self._gcstyle & GC_PROMPT:
  164. proportion = 1
  165. else:
  166. proportion = 0
  167. outBtnSizer.AddStretchSpacer()
  168. outBtnSizer.Add(
  169. self.btnOutputClear,
  170. proportion=proportion,
  171. flag=wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.BOTTOM,
  172. border=5)
  173. outBtnSizer.Add(self.btnOutputSave, proportion=proportion,
  174. flag=wx.RIGHT | wx.BOTTOM, border=5)
  175. cmdBtnSizer.Add(
  176. self.btnCmdProtocol,
  177. proportion=1,
  178. flag=wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT | wx.BOTTOM,
  179. border=5)
  180. cmdBtnSizer.Add(self.btnCmdClear, proportion=1,
  181. flag=wx.ALIGN_CENTER | wx.RIGHT | wx.BOTTOM, border=5)
  182. progressSizer.Add(self.btnCmdAbort, proportion=0,
  183. flag=wx.ALL | wx.ALIGN_CENTER, border=5)
  184. progressSizer.Add(
  185. self.progressbar,
  186. proportion=1,
  187. flag=wx.ALIGN_CENTER | wx.RIGHT | wx.TOP | wx.BOTTOM,
  188. border=5)
  189. self.panelProgress.SetSizer(progressSizer)
  190. progressSizer.Fit(self.panelProgress)
  191. btnSizer.Add(outBtnSizer, proportion=1,
  192. flag=wx.ALL | wx.ALIGN_CENTER, border=5)
  193. btnSizer.Add(
  194. cmdBtnSizer,
  195. proportion=1,
  196. flag=wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM | wx.RIGHT,
  197. border=5)
  198. self.outputSizer.Add(self.panelProgress, proportion=0,
  199. flag=wx.EXPAND)
  200. self.outputSizer.Add(btnSizer, proportion=0,
  201. flag=wx.EXPAND)
  202. self.outputSizer.Fit(self)
  203. self.outputSizer.SetSizeHints(self)
  204. self.panelOutput.SetSizer(self.outputSizer)
  205. self.outputSizer.FitInside(self.panelOutput)
  206. if self._gcstyle & GC_PROMPT:
  207. promptSizer.Fit(self)
  208. promptSizer.SetSizeHints(self)
  209. self.panelPrompt.SetSizer(promptSizer)
  210. # split window
  211. if self._gcstyle & GC_PROMPT:
  212. self.SplitHorizontally(self.panelOutput, self.panelPrompt, -50)
  213. else:
  214. self.SplitHorizontally(self.panelOutput, self.panelPrompt, -45)
  215. self.Unsplit()
  216. self.SetMinimumPaneSize(self.btnCmdClear.GetSize()[1] + 25)
  217. self.SetSashGravity(1.0)
  218. self.outputSizer.Hide(self.panelProgress)
  219. # layout
  220. self.SetAutoLayout(True)
  221. self.Layout()
  222. def GetPanel(self, prompt=True):
  223. """Get panel
  224. :param prompt: get prompt / output panel
  225. :return: wx.Panel reference
  226. """
  227. if prompt:
  228. return self.panelPrompt
  229. return self.panelOutput
  230. def WriteLog(self, text, style=None, wrap=None,
  231. notification=Notification.HIGHLIGHT):
  232. """Generic method for writing log message in
  233. given style.
  234. Emits contentChanged signal.
  235. :param line: text line
  236. :param style: text style (see GStc)
  237. :param stdout: write to stdout or stderr
  238. :param notification: form of notification
  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. # between wxWidgets 3.0 and 3.1 they dropped mask param
  258. try:
  259. self.cmdOutput.StartStyling(p1)
  260. except TypeError:
  261. self.cmdOutput.StartStyling(p1, 0xff)
  262. self.cmdOutput.SetStyling(p2 - p1, style)
  263. self.cmdOutput.EnsureCaretVisible()
  264. self.contentChanged.emit(notification=notification)
  265. def WriteCmdLog(self, text, pid=None,
  266. notification=Notification.MAKE_VISIBLE):
  267. """Write message in selected style
  268. :param text: message to be printed
  269. :param pid: process pid or None
  270. :param switchPage: True to switch page
  271. """
  272. if pid:
  273. text = '(' + str(pid) + ') ' + text
  274. self.WriteLog(
  275. text,
  276. style=self.cmdOutput.StyleCommand,
  277. notification=notification)
  278. def WriteWarning(self, text):
  279. """Write message in warning style"""
  280. self.WriteLog(text, style=self.cmdOutput.StyleWarning,
  281. notification=Notification.MAKE_VISIBLE)
  282. def WriteError(self, text):
  283. """Write message in error style"""
  284. self.WriteLog(text, style=self.cmdOutput.StyleError,
  285. notification=Notification.MAKE_VISIBLE)
  286. def OnOutputClear(self, event):
  287. """Clear content of output window"""
  288. self.cmdOutput.SetReadOnly(False)
  289. self.cmdOutput.ClearAll()
  290. self.cmdOutput.SetReadOnly(True)
  291. self.progressbar.SetValue(0)
  292. def GetProgressBar(self):
  293. """Return progress bar widget"""
  294. return self.progressbar
  295. def OnOutputSave(self, event):
  296. """Save (selected) text from output window to the file"""
  297. text = self.cmdOutput.GetSelectedText()
  298. if not text:
  299. text = self.cmdOutput.GetText()
  300. # add newline if needed
  301. if len(text) > 0 and text[-1] != '\n':
  302. text += '\n'
  303. dlg = wx.FileDialog(
  304. self, message=_("Save file as..."),
  305. defaultFile="grass_cmd_output.txt",
  306. wildcard=_("%(txt)s (*.txt)|*.txt|%(files)s (*)|*") %
  307. {'txt': _("Text files"),
  308. 'files': _("Files")},
  309. style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT)
  310. # Show the dialog and retrieve the user response. If it is the OK response,
  311. # process the data.
  312. if dlg.ShowModal() == wx.ID_OK:
  313. path = dlg.GetPath()
  314. try:
  315. output = open(path, "w")
  316. output.write(text)
  317. except IOError as e:
  318. GError(
  319. _("Unable to write file '%(path)s'.\n\nDetails: %(error)s") % {
  320. 'path': path,
  321. 'error': e})
  322. finally:
  323. output.close()
  324. message = _("Command output saved into '%s'") % path
  325. self.showNotification.emit(message=message)
  326. dlg.Destroy()
  327. def SetCopyingOfSelectedText(self, copy):
  328. """Enable or disable copying of selected text in to clipboard.
  329. Effects prompt and output.
  330. :param bool copy: True for enable, False for disable
  331. """
  332. if copy:
  333. self.cmdPrompt.Bind(
  334. stc.EVT_STC_PAINTED,
  335. self.cmdPrompt.OnTextSelectionChanged)
  336. self.cmdOutput.Bind(
  337. stc.EVT_STC_PAINTED,
  338. self.cmdOutput.OnTextSelectionChanged)
  339. else:
  340. self.cmdPrompt.Unbind(stc.EVT_STC_PAINTED)
  341. self.cmdOutput.Unbind(stc.EVT_STC_PAINTED)
  342. def OnCmdOutput(self, event):
  343. """Prints command output.
  344. Emits contentChanged signal.
  345. """
  346. message = event.text
  347. type = event.type
  348. self.cmdOutput.AddStyledMessage(message, type)
  349. if event.type in ('warning', 'error'):
  350. self.contentChanged.emit(notification=Notification.MAKE_VISIBLE)
  351. else:
  352. self.contentChanged.emit(notification=Notification.HIGHLIGHT)
  353. def OnCmdProgress(self, event):
  354. """Update progress message info"""
  355. self.progressbar.SetValue(event.value)
  356. event.Skip()
  357. def CmdProtocolSave(self):
  358. """Save list of manually entered commands into a text log file"""
  359. if self.cmdFileProtocol is None:
  360. return # it should not happen
  361. try:
  362. with open(self.cmdFileProtocol, "a") as output:
  363. cmds = self.cmdPrompt.GetCommands()
  364. output.write('\n'.join(cmds))
  365. if len(cmds) > 0:
  366. output.write('\n')
  367. except IOError as e:
  368. GError(_("Unable to write file '{filePath}'.\n\nDetails: {error}").format(
  369. filePath=self.cmdFileProtocol, error=e))
  370. self.showNotification.emit(
  371. message=_("Command log saved to '{}'".format(self.cmdFileProtocol))
  372. )
  373. self.cmdFileProtocol = None
  374. def OnCmdProtocol(self, event=None):
  375. """Save commands into file"""
  376. if not event.IsChecked():
  377. # stop capturing commands, save list of commands to the
  378. # protocol file
  379. self.CmdProtocolSave()
  380. else:
  381. # start capturing commands
  382. self.cmdPrompt.ClearCommands()
  383. # ask for the file
  384. dlg = wx.FileDialog(
  385. self, message=_("Save file as..."),
  386. defaultFile="grass_cmd_log.txt",
  387. wildcard=_("%(txt)s (*.txt)|*.txt|%(files)s (*)|*") %
  388. {'txt': _("Text files"),
  389. 'files': _("Files")},
  390. style=wx.FD_SAVE)
  391. if dlg.ShowModal() == wx.ID_OK:
  392. self.cmdFileProtocol = dlg.GetPath()
  393. else:
  394. wx.CallAfter(self.btnCmdProtocol.SetValue, False)
  395. dlg.Destroy()
  396. event.Skip()
  397. def OnCmdRun(self, event):
  398. """Run command"""
  399. self.outputSizer.Show(self.panelProgress)
  400. self.outputSizer.Layout()
  401. event.Skip()
  402. def OnCmdDone(self, event):
  403. """Command done (or aborted)
  404. """
  405. self.progressbar.SetValue(0) # reset progress bar on '0%'
  406. wx.CallLater(100, self._hideProgress)
  407. event.Skip()
  408. def _hideProgress(self):
  409. self.outputSizer.Hide(self.panelProgress)
  410. self.outputSizer.Layout()
  411. def ResetFocus(self):
  412. """Reset focus"""
  413. self.cmdPrompt.SetFocus()
  414. def GetPrompt(self):
  415. """Get prompt"""
  416. return self.cmdPrompt
  417. class GStc(stc.StyledTextCtrl):
  418. """Styled text control for GRASS stdout and stderr.
  419. Based on FrameOutErr.py
  420. Name: FrameOutErr.py
  421. Purpose: Redirecting stdout / stderr
  422. Author: Jean-Michel Fauth, Switzerland
  423. Copyright: (c) 2005-2007 Jean-Michel Fauth
  424. Licence: GPL
  425. """
  426. def __init__(self, parent, id, margin=False, wrap=None):
  427. stc.StyledTextCtrl.__init__(self, parent, id)
  428. self.parent = parent
  429. self.SetUndoCollection(True)
  430. self.SetReadOnly(True)
  431. # remember position of line beginning (used for '\r')
  432. self.linePos = -1
  433. #
  434. # styles
  435. #
  436. self.SetStyle()
  437. #
  438. # line margins
  439. #
  440. # TODO print number only from cmdlog
  441. self.SetMarginWidth(1, 0)
  442. self.SetMarginWidth(2, 0)
  443. if margin:
  444. self.SetMarginType(0, stc.STC_MARGIN_NUMBER)
  445. self.SetMarginWidth(0, 30)
  446. else:
  447. self.SetMarginWidth(0, 0)
  448. #
  449. # miscellaneous
  450. #
  451. self.SetViewWhiteSpace(False)
  452. self.SetTabWidth(4)
  453. self.SetUseTabs(False)
  454. self.UsePopUp(True)
  455. self.SetSelBackground(True,
  456. wx.SystemSettings.GetColour(wx.SYS_COLOUR_HIGHLIGHT))
  457. self.SetUseHorizontalScrollBar(True)
  458. #
  459. # bindings
  460. #
  461. self.Bind(wx.EVT_WINDOW_DESTROY, self.OnDestroy)
  462. def OnTextSelectionChanged(self, event):
  463. """Copy selected text to clipboard and skip event.
  464. The same function is in TextCtrlAutoComplete class (prompt.py).
  465. """
  466. wx.CallAfter(self.Copy)
  467. event.Skip()
  468. def SetStyle(self):
  469. """Set styles for styled text output windows with type face
  470. and point size selected by user (Courier New 10 is default)"""
  471. typeface = UserSettings.Get(
  472. group='appearance',
  473. key='outputfont',
  474. subkey='type')
  475. if typeface == "":
  476. typeface = "Courier New"
  477. typesize = UserSettings.Get(
  478. group='appearance',
  479. key='outputfont',
  480. subkey='size')
  481. if typesize is None or int(typesize) <= 0:
  482. typesize = 10
  483. typesize = float(typesize)
  484. fontInfo = wx.FontInfo(typesize)
  485. fontInfo.FaceName(typeface)
  486. fontInfo.Family(wx.FONTFAMILY_TELETYPE)
  487. defaultFont = wx.Font(fontInfo)
  488. self.StyleClearAll()
  489. isDarkMode = False
  490. if wxPythonPhoenix and CheckWxVersion([4, 1, 0]):
  491. isDarkMode = wx.SystemSettings.GetAppearance().IsDark()
  492. defaultBackgroundColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)
  493. defaultTextColour = wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOWTEXT)
  494. self.StyleDefault = 0
  495. self.StyleSetFont(stc.STC_STYLE_DEFAULT, defaultFont)
  496. self.StyleSetBackground(stc.STC_STYLE_DEFAULT, defaultBackgroundColour)
  497. self.StyleSetForeground(stc.STC_STYLE_DEFAULT, defaultTextColour)
  498. self.StyleCommand = 1
  499. self.StyleSetBackground(self.StyleCommand, wx.Colour(154, 154, 154, 255))
  500. self.StyleSetForeground(self.StyleCommand, defaultTextColour)
  501. self.StyleOutput = 2
  502. self.StyleSetBackground(self.StyleOutput, defaultBackgroundColour)
  503. self.StyleSetForeground(self.StyleOutput, defaultTextColour)
  504. # fatal error
  505. self.StyleError = 3
  506. errorColour = wx.Colour(127, 0, 0)
  507. if isDarkMode:
  508. errorColour = wx.Colour(230, 0, 0)
  509. self.StyleSetBackground(self.StyleError, defaultBackgroundColour)
  510. self.StyleSetForeground(self.StyleError, errorColour)
  511. # warning
  512. self.StyleWarning = 4
  513. warningColour = wx.Colour(0, 0, 255)
  514. if isDarkMode:
  515. warningColour = wx.Colour(0, 102, 255)
  516. self.StyleSetBackground(self.StyleWarning, defaultBackgroundColour)
  517. self.StyleSetForeground(self.StyleWarning, warningColour)
  518. # message
  519. self.StyleMessage = 5
  520. self.StyleSetBackground(self.StyleMessage, defaultBackgroundColour)
  521. self.StyleSetForeground(self.StyleMessage, defaultTextColour)
  522. # unknown
  523. self.StyleUnknown = 6
  524. self.StyleSetBackground(self.StyleUnknown, defaultBackgroundColour)
  525. self.StyleSetForeground(self.StyleUnknown, defaultTextColour)
  526. def OnDestroy(self, evt):
  527. """The clipboard contents can be preserved after
  528. the app has exited"""
  529. if wx.TheClipboard.IsOpened():
  530. wx.TheClipboard.Flush()
  531. evt.Skip()
  532. def AddTextWrapped(self, txt, wrap=None):
  533. """Add string to text area.
  534. String is wrapped and linesep is also added to the end
  535. of the string"""
  536. # allow writing to output window
  537. self.SetReadOnly(False)
  538. if wrap:
  539. txt = textwrap.fill(txt, wrap) + '\n'
  540. else:
  541. if txt[-1] != '\n':
  542. txt += '\n'
  543. if '\r' in txt:
  544. self.linePos = -1
  545. for seg in txt.split('\r'):
  546. if self.linePos > -1:
  547. self.SetCurrentPos(self.linePos)
  548. self.ReplaceSelection(seg)
  549. else:
  550. self.linePos = self.GetCurrentPos()
  551. self.AddText(seg)
  552. else:
  553. self.linePos = self.GetCurrentPos()
  554. self.AddText(txt)
  555. # reset output window to read only
  556. self.SetReadOnly(True)
  557. def AddStyledMessage(self, message, style=None):
  558. """Add message to text area.
  559. Handles messages with progress percentages.
  560. :param message: message to be added
  561. :param style: style of message, allowed values: 'message',
  562. 'warning', 'error' or None
  563. """
  564. # message prefix
  565. if style == 'warning':
  566. message = 'WARNING: ' + message
  567. elif style == 'error':
  568. message = 'ERROR: ' + message
  569. p1 = self.GetEndStyled()
  570. self.GotoPos(p1)
  571. # is this still needed?
  572. if '\b' in message:
  573. if self.linePos < 0:
  574. self.linePos = p1
  575. last_c = ''
  576. for c in message:
  577. if c == '\b':
  578. self.linePos -= 1
  579. else:
  580. if c == '\r':
  581. pos = self.GetCurLine()[1]
  582. # self.SetCurrentPos(pos)
  583. else:
  584. self.SetCurrentPos(self.linePos)
  585. self.ReplaceSelection(c)
  586. self.linePos = self.GetCurrentPos()
  587. if c != ' ':
  588. last_c = c
  589. if last_c not in ('0123456789'):
  590. self.AddTextWrapped('\n', wrap=None)
  591. self.linePos = -1
  592. else:
  593. self.linePos = -1 # don't force position
  594. if '\n' not in message:
  595. self.AddTextWrapped(message, wrap=60)
  596. else:
  597. self.AddTextWrapped(message, wrap=None)
  598. p2 = self.GetCurrentPos()
  599. if p2 >= p1:
  600. try:
  601. self.StartStyling(p1)
  602. except TypeError:
  603. self.StartStyling(p1, 0xff)
  604. if style == 'error':
  605. self.SetStyling(p2 - p1, self.StyleError)
  606. elif style == 'warning':
  607. self.SetStyling(p2 - p1, self.StyleWarning)
  608. elif style == 'message':
  609. self.SetStyling(p2 - p1, self.StyleMessage)
  610. else: # unknown
  611. self.SetStyling(p2 - p1, self.StyleUnknown)
  612. self.EnsureCaretVisible()
  613. class GConsoleFrame(wx.Frame):
  614. """Standalone GConsole for testing only"""
  615. def __init__(self, parent, id=wx.ID_ANY, title="GConsole Test Frame",
  616. style=wx.DEFAULT_FRAME_STYLE | wx.TAB_TRAVERSAL, **kwargs):
  617. wx.Frame.__init__(self, parent=parent, id=id, title=title, style=style)
  618. panel = wx.Panel(self, id=wx.ID_ANY)
  619. from lmgr.menudata import LayerManagerMenuData
  620. menuTreeBuilder = LayerManagerMenuData()
  621. self.gconsole = GConsole(guiparent=self)
  622. self.goutput = GConsoleWindow(parent=panel, gconsole=self.gconsole,
  623. menuModel=menuTreeBuilder.GetModel(),
  624. gcstyle=GC_PROMPT)
  625. mainSizer = wx.BoxSizer(wx.VERTICAL)
  626. mainSizer.Add(
  627. self.goutput,
  628. proportion=1,
  629. flag=wx.EXPAND,
  630. border=0)
  631. panel.SetSizer(mainSizer)
  632. mainSizer.Fit(panel)
  633. self.SetMinSize((550, 500))
  634. def testGConsole():
  635. app = wx.App()
  636. frame = GConsoleFrame(parent=None)
  637. frame.Show()
  638. app.MainLoop()
  639. if __name__ == '__main__':
  640. testGConsole()