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