goutput.py 28 KB

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