goutput.py 28 KB

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