class GConsoleWindow(wx.SplitterWindow): """Create and manage output console for commands run by GUI.""" def __init__( self, parent, giface, gconsole, menuModel=None, margin=False, style=wx.TAB_TRAVERSAL | wx.FULL_REPAINT_ON_RESIZE, gcstyle=GC_EMPTY, **kwargs, ): """ :param parent: gui parent :param gconsole: console logic :param menuModel: tree model of modules (from menu) :param margin: use margin in output pane (GStc) :param style: wx.SplitterWindow style :param gcstyle: GConsole style (GC_EMPTY, GC_PROMPT to show command prompt) """ wx.SplitterWindow.__init__(self, parent, id=wx.ID_ANY, style=style, **kwargs) self.SetName("GConsole") self.panelOutput = wx.Panel(parent=self, id=wx.ID_ANY) self.panelProgress = wx.Panel( parent=self.panelOutput, id=wx.ID_ANY, name="progressPanel" ) self.panelPrompt = wx.Panel(parent=self, id=wx.ID_ANY) # initialize variables self.parent = parent # GMFrame | CmdPanel | ? self._gconsole = gconsole self._menuModel = menuModel self._gcstyle = gcstyle self.lineWidth = 80 # signal which requests showing of a notification self.showNotification = Signal("GConsoleWindow.showNotification") # signal emitted when text appears in the console # parameter 'notification' suggests form of notification (according to # core.giface.Notification) self.contentChanged = Signal("GConsoleWindow.contentChanged") # progress bar self.progressbar = wx.Gauge( parent=self.panelProgress, id=wx.ID_ANY, range=100, pos=(110, 50), size=(-1, 25), style=wx.GA_HORIZONTAL, ) self._gconsole.Bind(EVT_CMD_PROGRESS, self.OnCmdProgress) self._gconsole.Bind(EVT_CMD_OUTPUT, self.OnCmdOutput) self._gconsole.Bind(EVT_CMD_RUN, self.OnCmdRun) self._gconsole.Bind(EVT_CMD_DONE, self.OnCmdDone) self._gconsole.writeLog.connect(self.WriteLog) self._gconsole.writeCmdLog.connect(self.WriteCmdLog) self._gconsole.writeWarning.connect(self.WriteWarning) self._gconsole.writeError.connect(self.WriteError) # text control for command output self.cmdOutput = GStc( parent=self.panelOutput, id=wx.ID_ANY, margin=margin, wrap=None ) # command prompt # move to the if below # search depends on cmd prompt self.cmdPrompt = GPromptSTC( parent=self, giface=giface, menuModel=self._menuModel ) self.cmdPrompt.promptRunCmd.connect( lambda cmd: self._gconsole.RunCmd(command=cmd) ) self.cmdPrompt.showNotification.connect(self.showNotification) if not self._gcstyle & GC_PROMPT: self.cmdPrompt.Hide() if self._gcstyle & GC_PROMPT: cmdLabel = _("Command prompt") self.outputBox = StaticBox( parent=self.panelOutput, id=wx.ID_ANY, label=" %s " % _("Output window") ) self.cmdBox = StaticBox( parent=self.panelOutput, id=wx.ID_ANY, label=" %s " % cmdLabel ) # buttons self.btnOutputClear = ClearButton(parent=self.panelOutput) self.btnOutputClear.SetToolTip(_("Clear output window content")) self.btnCmdClear = ClearButton(parent=self.panelOutput) self.btnCmdClear.SetToolTip(_("Clear command prompt content")) self.btnOutputSave = Button(parent=self.panelOutput, id=wx.ID_SAVE) self.btnOutputSave.SetToolTip(_("Save output window content to the file")) self.btnCmdAbort = Button(parent=self.panelProgress, id=wx.ID_STOP) self.btnCmdAbort.SetToolTip(_("Abort running command")) self.btnCmdProtocol = ToggleButton( parent=self.panelOutput, id=wx.ID_ANY, label=_("&Log file"), size=self.btnCmdClear.GetSize(), ) self.btnCmdProtocol.SetToolTip( _( "Toggle to save list of executed commands into " "a file; content saved when switching off." ) ) self.cmdFileProtocol = None if not self._gcstyle & GC_PROMPT: self.btnCmdClear.Hide() self.btnCmdProtocol.Hide() self.btnCmdClear.Bind(wx.EVT_BUTTON, self.cmdPrompt.OnCmdErase) self.btnOutputClear.Bind(wx.EVT_BUTTON, self.OnOutputClear) self.btnOutputSave.Bind(wx.EVT_BUTTON, self.OnOutputSave) self.btnCmdAbort.Bind(wx.EVT_BUTTON, self._gconsole.OnCmdAbort) self.btnCmdProtocol.Bind(wx.EVT_TOGGLEBUTTON, self.OnCmdProtocol) self._layout() def _layout(self): """Do layout""" self.outputSizer = wx.BoxSizer(wx.VERTICAL) progressSizer = wx.BoxSizer(wx.HORIZONTAL) btnSizer = wx.BoxSizer(wx.HORIZONTAL) if self._gcstyle & GC_PROMPT: outBtnSizer = wx.StaticBoxSizer(self.outputBox, wx.HORIZONTAL) cmdBtnSizer = wx.StaticBoxSizer(self.cmdBox, wx.HORIZONTAL) else: outBtnSizer = wx.BoxSizer(wx.HORIZONTAL) cmdBtnSizer = wx.BoxSizer(wx.HORIZONTAL) if self._gcstyle & GC_PROMPT: promptSizer = wx.BoxSizer(wx.VERTICAL) promptSizer.Add( self.cmdPrompt, proportion=1, flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border=3, ) helpText = StaticText( self.panelPrompt, id=wx.ID_ANY, label="Press Tab to display command help, Ctrl+Space to autocomplete", ) helpText.SetForegroundColour( wx.SystemSettings.GetColour(wx.SYS_COLOUR_GRAYTEXT) ) promptSizer.Add(helpText, proportion=0, flag=wx.EXPAND | wx.LEFT, border=5) self.outputSizer.Add( self.cmdOutput, proportion=1, flag=wx.EXPAND | wx.ALL, border=3 ) if self._gcstyle & GC_PROMPT: proportion = 1 else: proportion = 0 outBtnSizer.AddStretchSpacer() outBtnSizer.Add( self.btnOutputClear, proportion=proportion, flag=wx.ALIGN_LEFT | wx.LEFT | wx.RIGHT | wx.BOTTOM, border=5, ) outBtnSizer.Add( self.btnOutputSave, proportion=proportion, flag=wx.RIGHT | wx.BOTTOM, border=5, ) cmdBtnSizer.Add( self.btnCmdProtocol, proportion=1, flag=wx.ALIGN_CENTER | wx.ALIGN_CENTER_VERTICAL | wx.LEFT | wx.RIGHT | wx.BOTTOM, border=5, ) cmdBtnSizer.Add( self.btnCmdClear, proportion=1, flag=wx.ALIGN_CENTER | wx.RIGHT | wx.BOTTOM, border=5, ) progressSizer.Add( self.btnCmdAbort, proportion=0, flag=wx.ALL | wx.ALIGN_CENTER, border=5 ) progressSizer.Add( self.progressbar, proportion=1, flag=wx.ALIGN_CENTER | wx.RIGHT | wx.TOP | wx.BOTTOM, border=5, ) self.panelProgress.SetSizer(progressSizer) progressSizer.Fit(self.panelProgress) btnSizer.Add(outBtnSizer, proportion=1, flag=wx.ALL | wx.ALIGN_CENTER, border=5) btnSizer.Add( cmdBtnSizer, proportion=1, flag=wx.ALIGN_CENTER | wx.TOP | wx.BOTTOM | wx.RIGHT, border=5, ) self.outputSizer.Add(self.panelProgress, proportion=0, flag=wx.EXPAND) self.outputSizer.Add(btnSizer, proportion=0, flag=wx.EXPAND) self.outputSizer.Fit(self) self.outputSizer.SetSizeHints(self) self.panelOutput.SetSizer(self.outputSizer) self.outputSizer.FitInside(self.panelOutput) if self._gcstyle & GC_PROMPT: promptSizer.Fit(self) promptSizer.SetSizeHints(self) self.panelPrompt.SetSizer(promptSizer) # split window if self._gcstyle & GC_PROMPT: self.SplitHorizontally(self.panelOutput, self.panelPrompt, -50) else: self.SplitHorizontally(self.panelOutput, self.panelPrompt, -45) self.Unsplit() self.SetMinimumPaneSize(self.btnCmdClear.GetSize()[1] + 25) self.SetSashGravity(1.0) self.outputSizer.Hide(self.panelProgress) # layout self.SetAutoLayout(True) self.Layout() def GetPanel(self, prompt=True): """Get panel :param prompt: get prompt / output panel :return: wx.Panel reference """ if prompt: return self.panelPrompt return self.panelOutput def WriteLog( self, text, style=None, wrap=None, notification=Notification.HIGHLIGHT ): """Generic method for writing log message in given style. Emits contentChanged signal. :param line: text line :param style: text style (see GStc) :param stdout: write to stdout or stderr :param notification: form of notification """ self.cmdOutput.SetStyle() # documenting old behavior/implementation: # switch notebook if required # now, let user to bind to the old event if not style: style = self.cmdOutput.StyleDefault # p1 = self.cmdOutput.GetCurrentPos() p1 = self.cmdOutput.GetEndStyled() # self.cmdOutput.GotoPos(p1) self.cmdOutput.DocumentEnd() for line in text.splitlines(): # fill space if len(line) < self.lineWidth: diff = self.lineWidth - len(line) line += diff * " " self.cmdOutput.AddTextWrapped(line, wrap=wrap) # adds '\n' p2 = self.cmdOutput.GetCurrentPos() # between wxWidgets 3.0 and 3.1 they dropped mask param try: self.cmdOutput.StartStyling(p1) except TypeError: self.cmdOutput.StartStyling(p1, 0xFF) self.cmdOutput.SetStyling(p2 - p1, style) self.cmdOutput.EnsureCaretVisible() self.contentChanged.emit(notification=notification) def WriteCmdLog(self, text, pid=None, notification=Notification.MAKE_VISIBLE): """Write message in selected style :param text: message to be printed :param pid: process pid or None :param switchPage: True to switch page """ if pid: text = "(" + str(pid) + ") " + text self.WriteLog( text, style=self.cmdOutput.StyleCommand, notification=notification ) def WriteWarning(self, text): """Write message in warning style""" self.WriteLog( text, style=self.cmdOutput.StyleWarning, notification=Notification.MAKE_VISIBLE, ) def WriteError(self, text): """Write message in error style""" self.WriteLog( text, style=self.cmdOutput.StyleError, notification=Notification.MAKE_VISIBLE, ) def OnOutputClear(self, event): """Clear content of output window""" self.cmdOutput.SetReadOnly(False) self.cmdOutput.ClearAll() self.cmdOutput.SetReadOnly(True) self.progressbar.SetValue(0) def GetProgressBar(self): """Return progress bar widget""" return self.progressbar def OnOutputSave(self, event): """Save (selected) text from output window to the file""" text = self.cmdOutput.GetSelectedText() if not text: text = self.cmdOutput.GetText() # add newline if needed if len(text) > 0 and text[-1] != "\n": text += "\n" dlg = wx.FileDialog( self, message=_("Save file as..."), defaultFile="grass_cmd_output.txt", wildcard=_("%(txt)s (*.txt)|*.txt|%(files)s (*)|*") % {"txt": _("Text files"), "files": _("Files")}, style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT, ) # Show the dialog and retrieve the user response. If it is the OK response, # process the data. if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() try: output = open(path, "w") output.write(text) except IOError as e: GError( _("Unable to write file '%(path)s'.\n\nDetails: %(error)s") % {"path": path, "error": e} ) finally: output.close() message = _("Command output saved into '%s'") % path self.showNotification.emit(message=message) dlg.Destroy() def SetCopyingOfSelectedText(self, copy): """Enable or disable copying of selected text in to clipboard. Effects prompt and output. :param bool copy: True for enable, False for disable """ if copy: self.cmdPrompt.Bind( stc.EVT_STC_PAINTED, self.cmdPrompt.OnTextSelectionChanged ) self.cmdOutput.Bind( stc.EVT_STC_PAINTED, self.cmdOutput.OnTextSelectionChanged ) else: self.cmdPrompt.Unbind(stc.EVT_STC_PAINTED) self.cmdOutput.Unbind(stc.EVT_STC_PAINTED) def OnCmdOutput(self, event): """Prints command output. Emits contentChanged signal. """ message = event.text type = event.type self.cmdOutput.AddStyledMessage(message, type) if event.type in ("warning", "error"): self.contentChanged.emit(notification=Notification.MAKE_VISIBLE) else: self.contentChanged.emit(notification=Notification.HIGHLIGHT) def OnCmdProgress(self, event): """Update progress message info""" self.progressbar.SetValue(event.value) event.Skip() def CmdProtocolSave(self): """Save list of manually entered commands into a text log file""" if self.cmdFileProtocol is None: return # it should not happen try: with open(self.cmdFileProtocol, "a") as output: cmds = self.cmdPrompt.GetCommands() output.write("\n".join(cmds)) if len(cmds) > 0: output.write("\n") except IOError as e: GError( _("Unable to write file '{filePath}'.\n\nDetails: {error}").format( filePath=self.cmdFileProtocol, error=e ) ) self.showNotification.emit( message=_("Command log saved to '{}'".format(self.cmdFileProtocol)) ) self.cmdFileProtocol = None def OnCmdProtocol(self, event=None): """Save commands into file""" if not event.IsChecked(): # stop capturing commands, save list of commands to the # protocol file self.CmdProtocolSave() else: # start capturing commands self.cmdPrompt.ClearCommands() # ask for the file dlg = wx.FileDialog( self, message=_("Save file as..."), defaultFile="grass_cmd_log.txt", wildcard=_("%(txt)s (*.txt)|*.txt|%(files)s (*)|*") % {"txt": _("Text files"), "files": _("Files")}, style=wx.FD_SAVE, ) if dlg.ShowModal() == wx.ID_OK: self.cmdFileProtocol = dlg.GetPath() else: wx.CallAfter(self.btnCmdProtocol.SetValue, False) dlg.Destroy() event.Skip() def OnCmdRun(self, event): """Run command""" self.outputSizer.Show(self.panelProgress) self.outputSizer.Layout() event.Skip() def OnCmdDone(self, event): """Command done (or aborted)""" self.progressbar.SetValue(0) # reset progress bar on '0%' wx.CallLater(100, self._hideProgress) event.Skip() def _hideProgress(self): self.outputSizer.Hide(self.panelProgress) self.outputSizer.Layout() def ResetFocus(self): """Reset focus""" self.cmdPrompt.SetFocus() def GetPrompt(self): """Get prompt""" return self.cmdPrompt
class ModelSearchDialog(wx.Dialog): def __init__(self, parent, title=_("Add GRASS command to the model"), style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs): """Graphical modeler module search window :param parent: parent window :param id: window id :param title: window title :param kwargs: wx.Dialogs' arguments """ self.parent = parent wx.Dialog.__init__( self, parent=parent, id=wx.ID_ANY, title=title, **kwargs) self.SetName("ModelerDialog") self.SetIcon( wx.Icon( os.path.join( globalvar.ICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO)) self._command = None self.panel = wx.Panel(parent=self, id=wx.ID_ANY) self.cmdBox = StaticBox(parent=self.panel, id=wx.ID_ANY, label=" %s " % _("Command")) self.labelBox = StaticBox(parent=self.panel, id=wx.ID_ANY, label=" %s " % _("Label and comment")) # menu data for search widget and prompt menuModel = LayerManagerMenuData() self.cmd_prompt = GPromptSTC( parent=self, menuModel=menuModel.GetModel()) self.cmd_prompt.promptRunCmd.connect(self.OnCommand) self.cmd_prompt.commandSelected.connect( lambda command: self.label.SetValue(command)) self.search = SearchModuleWidget(parent=self.panel, model=menuModel.GetModel(), showTip=True) self.search.moduleSelected.connect( lambda name: self.cmd_prompt.SetTextAndFocus(name + ' ')) wx.CallAfter(self.cmd_prompt.SetFocus) self.label = TextCtrl(parent=self.panel, id=wx.ID_ANY) self.comment = TextCtrl( parent=self.panel, id=wx.ID_ANY, style=wx.TE_MULTILINE) self.btnCancel = Button(self.panel, wx.ID_CANCEL) self.btnOk = Button(self.panel, wx.ID_OK) self.btnOk.SetDefault() self.Bind(wx.EVT_BUTTON, self.OnOk, self.btnOk) self.Bind(wx.EVT_BUTTON, self.OnCancel, self.btnCancel) self._layout() self.SetSize((500, -1)) def _layout(self): cmdSizer = wx.StaticBoxSizer(self.cmdBox, wx.VERTICAL) cmdSizer.Add(self.cmd_prompt, proportion=1, flag=wx.EXPAND) labelSizer = wx.StaticBoxSizer(self.labelBox, wx.VERTICAL) gridSizer = wx.GridBagSizer(hgap=5, vgap=5) gridSizer.Add(StaticText(parent=self.panel, id=wx.ID_ANY, label=_("Label:")), flag=wx.ALIGN_CENTER_VERTICAL, pos=(0, 0)) gridSizer.Add(self.label, pos=(0, 1), flag=wx.EXPAND) gridSizer.Add(StaticText(parent=self.panel, id=wx.ID_ANY, label=_("Comment:")), flag=wx.ALIGN_CENTER_VERTICAL, pos=(1, 0)) gridSizer.Add(self.comment, pos=(1, 1), flag=wx.EXPAND) gridSizer.AddGrowableRow(1) gridSizer.AddGrowableCol(1) labelSizer.Add(gridSizer, proportion=1, flag=wx.EXPAND) btnSizer = wx.StdDialogButtonSizer() btnSizer.AddButton(self.btnCancel) btnSizer.AddButton(self.btnOk) btnSizer.Realize() mainSizer = wx.BoxSizer(wx.VERTICAL) mainSizer.Add(self.search, proportion=0, flag=wx.EXPAND | wx.ALL, border=3) mainSizer.Add(cmdSizer, proportion=1, flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border=3) mainSizer.Add(labelSizer, proportion=1, flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border=3) mainSizer.Add(btnSizer, proportion=0, flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5) self.panel.SetSizer(mainSizer) mainSizer.Fit(self) self.Layout() def GetPanel(self): """Get dialog panel""" return self.panel def _getCmd(self): line = self.cmd_prompt.GetCurLine()[0].strip() if len(line) == 0: cmd = list() else: try: cmd = utils.split(str(line)) except UnicodeError: cmd = utils.split(EncodeString((line))) return cmd def GetCmd(self): """Get command""" return self._command def GetLabel(self): """Get label and comment""" return self.label.GetValue(), self.comment.GetValue() def ValidateCmd(self, cmd): if len(cmd) < 1: GError(parent=self, message=_("Command not defined.\n\n" "Unable to add new action to the model.")) return False if cmd[0] not in globalvar.grassCmd: GError( parent=self, message=_( "'%s' is not a GRASS module.\n\n" "Unable to add new action to the model.") % cmd[0]) return False return True def OnCommand(self, cmd): """Command in prompt confirmed""" if self.ValidateCmd(cmd): self._command = cmd self.EndModal(wx.ID_OK) def OnOk(self, event): """Button 'OK' pressed""" cmd = self._getCmd() if self.ValidateCmd(cmd): self._command = cmd self.EndModal(wx.ID_OK) def OnCancel(self, event): """Cancel pressed, close window""" self.Hide() def Reset(self): """Reset dialog""" self.search.Reset() self.label.SetValue('') self.comment.SetValue('') self.cmd_prompt.OnCmdErase(None) self.cmd_prompt.SetFocus()
class ModelSearchDialog(wx.Dialog): def __init__(self, parent, id=wx.ID_ANY, title=_("Add new GRASS module to the model"), style=wx.DEFAULT_DIALOG_STYLE | wx.RESIZE_BORDER, **kwargs): """!Graphical modeler module search window @param parent parent window @param id window id @param title window title @param kwargs wx.Dialogs' arguments """ self.parent = parent wx.Dialog.__init__(self, parent=parent, id=id, title=title, **kwargs) self.SetName("ModelerDialog") self.SetIcon( wx.Icon(os.path.join(globalvar.ETCICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO)) self.panel = wx.Panel(parent=self, id=wx.ID_ANY) self.cmdBox = wx.StaticBox(parent=self.panel, id=wx.ID_ANY, label=" %s " % _("Command")) self.cmd_prompt = GPromptSTC(parent=self) self.search = SearchModuleWindow(parent=self.panel, cmdPrompt=self.cmd_prompt, showTip=True) wx.CallAfter(self.cmd_prompt.SetFocus) # get commands items = self.cmd_prompt.GetCommandItems() self.btnCancel = wx.Button(self.panel, wx.ID_CANCEL) self.btnOk = wx.Button(self.panel, wx.ID_OK) self.btnOk.SetDefault() self.btnOk.Enable(False) self.cmd_prompt.Bind(wx.EVT_KEY_UP, self.OnText) self.search.searchChoice.Bind(wx.EVT_CHOICE, self.OnText) self.Bind(wx.EVT_BUTTON, self.OnOk, self.btnOk) self.Bind(wx.EVT_BUTTON, self.OnCancel, self.btnCancel) self._layout() self.SetSize((500, 275)) def _layout(self): cmdSizer = wx.StaticBoxSizer(self.cmdBox, wx.VERTICAL) cmdSizer.Add(item=self.cmd_prompt, proportion=1, flag=wx.EXPAND) btnSizer = wx.StdDialogButtonSizer() btnSizer.AddButton(self.btnCancel) btnSizer.AddButton(self.btnOk) btnSizer.Realize() mainSizer = wx.BoxSizer(wx.VERTICAL) mainSizer.Add(item=self.search, proportion=0, flag=wx.EXPAND | wx.ALL, border=3) mainSizer.Add(item=cmdSizer, proportion=1, flag=wx.EXPAND | wx.LEFT | wx.RIGHT | wx.TOP, border=3) mainSizer.Add(item=btnSizer, proportion=0, flag=wx.EXPAND | wx.ALL | wx.ALIGN_CENTER, border=5) self.panel.SetSizer(mainSizer) mainSizer.Fit(self.panel) self.Layout() def GetPanel(self): """!Get dialog panel""" return self.panel def GetCmd(self): """!Get command""" line = self.cmd_prompt.GetCurLine()[0].strip() if len(line) == 0: list() try: cmd = utils.split(str(line)) except UnicodeError: cmd = utils.split(utils.EncodeString((line))) return cmd def OnOk(self, event): """!Button 'OK' pressed""" # hide autocomplete if self.cmd_prompt.AutoCompActive(): self.cmd_prompt.AutoCompCancel() self.btnOk.SetFocus() cmd = self.GetCmd() if len(cmd) < 1: GError(parent=self, message=_("Command not defined.\n\n" "Unable to add new action to the model.")) return if cmd[0] not in globalvar.grassCmd: GError(parent=self, message=_("'%s' is not a GRASS module.\n\n" "Unable to add new action to the model.") % cmd[0]) return self.EndModal(wx.ID_OK) def OnCancel(self, event): """Cancel pressed, close window""" # hide autocomplete if self.cmd_prompt.AutoCompActive(): self.cmd_prompt.AutoCompCancel() self.Hide() def OnText(self, event): """!Text in prompt changed""" if self.cmd_prompt.AutoCompActive(): event.Skip() return if isinstance(event, wx.KeyEvent): entry = self.cmd_prompt.GetTextLeft() elif isinstance(event, wx.stc.StyledTextEvent): entry = event.GetText() else: entry = event.GetString() if entry: self.btnOk.Enable() else: self.btnOk.Enable(False) event.Skip() def Reset(self): """!Reset dialog""" self.search.Reset() self.cmd_prompt.OnCmdErase(None) self.btnOk.Enable(False) self.cmd_prompt.SetFocus()