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 PyShellWindow(wx.Panel): """Python Shell Window""" def __init__(self, parent, giface, id=wx.ID_ANY, simpleEditorHandler=None, **kwargs): self.parent = parent self.giface = giface wx.Panel.__init__(self, parent=parent, id=id, **kwargs) self.intro = _("Welcome to wxGUI Interactive Python Shell %s") % VERSION + "\n\n" + \ _("Type %s for more GRASS scripting related information.") % "\"help(gs)\"" + "\n" + \ _("Type %s to add raster or vector to the layer tree.") % "\"AddLayer()\"" + "\n\n" shellargs = dict( parent=self, id=wx.ID_ANY, introText=self.intro, locals={"gs": grass, "AddLayer": self.AddLayer}, ) # useStockId (available since wxPython 4.0.2) should be False on macOS if sys.platform == "darwin" and CheckWxVersion([4, 0, 2]): shellargs["useStockId"] = False self.shell = PyShell(**shellargs) if IsDark(): SetDarkMode(self.shell) sys.displayhook = self._displayhook self.btnClear = ClearButton(self) self.btnClear.Bind(wx.EVT_BUTTON, self.OnClear) self.btnClear.SetToolTip(_("Delete all text from the shell")) self.simpleEditorHandler = simpleEditorHandler if simpleEditorHandler: self.btnSimpleEditor = Button( self, id=wx.ID_ANY, label=_("Simple &editor")) self.btnSimpleEditor.Bind(wx.EVT_BUTTON, simpleEditorHandler) self.btnSimpleEditor.SetToolTip( _("Open a simple Python code editor")) self._layout() def _displayhook(self, value): print(value) # do not modify __builtin__._ def _layout(self): sizer = wx.BoxSizer(wx.VERTICAL) sizer.Add(self.shell, proportion=1, flag=wx.EXPAND) btnSizer = wx.BoxSizer(wx.HORIZONTAL) if self.simpleEditorHandler: btnSizer.Add(self.btnSimpleEditor, proportion=0, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=5) btnSizer.AddStretchSpacer() btnSizer.Add(self.btnClear, proportion=0, flag=wx.EXPAND, border=5) sizer.Add(btnSizer, proportion=0, flag=wx.ALL | wx.EXPAND, border=5) sizer.Fit(self) sizer.SetSizeHints(self) self.SetSizer(sizer) self.Fit() self.SetAutoLayout(True) self.Layout() def AddLayer(self, name, ltype='auto'): """Add selected map to the layer tree :param name: name of raster/vector map to be added :param type: map type ('raster', 'vector', 'auto' for autodetection) """ fname = None if ltype == 'raster' or ltype != 'vector': # check for raster fname = grass.find_file(name, element='cell')['fullname'] if fname: ltype = 'raster' lcmd = 'd.rast' if not fname and (ltype == 'vector' or ltype != 'raster'): # if not found check for vector fname = grass.find_file(name, element='vector')['fullname'] if fname: ltype = 'vector' lcmd = 'd.vect' if not fname: return _("Raster or vector map <%s> not found") % (name) self.giface.GetLayerTree().AddLayer(ltype=ltype, lname=fname, lchecked=True, lcmd=[lcmd, 'map=%s' % fname]) if ltype == 'raster': return _('Raster map <%s> added') % fname return _('Vector map <%s> added') % fname def OnClear(self, event): """Delete all text from the shell """ self.shell.clear() self.shell.showIntro(self.intro) self.shell.prompt()
class MapCalcFrame(wx.Frame): """Mapcalc Frame class. Calculator-style window to create and run r(3).mapcalc statements. """ def __init__(self, parent, giface, cmd, id=wx.ID_ANY, style=wx.DEFAULT_FRAME_STYLE | wx.RESIZE_BORDER, **kwargs): self.parent = parent self._giface = giface if self.parent: self.log = self.parent.GetLogWindow() else: self.log = None # grass command self.cmd = cmd if self.cmd == 'r.mapcalc': self.rast3d = False title = _('GRASS GIS Raster Map Calculator') if self.cmd == 'r3.mapcalc': self.rast3d = True title = _('GRASS GIS 3D Raster Map Calculator') wx.Frame.__init__(self, parent, id=id, title=title, **kwargs) self.SetIcon( wx.Icon(os.path.join(globalvar.ICONDIR, 'grass.ico'), wx.BITMAP_TYPE_ICO)) self.panel = wx.Panel(parent=self, id=wx.ID_ANY) self.CreateStatusBar() # # variables # self.heading = _('mapcalc statement') self.funct_dict = { 'abs(x)': 'abs()', 'acos(x)': 'acos()', 'asin(x)': 'asin()', 'atan(x)': 'atan()', 'atan(x,y)': 'atan( , )', 'cos(x)': 'cos()', 'double(x)': 'double()', 'eval([x,y,...,]z)': 'eval()', 'exp(x)': 'exp()', 'exp(x,y)': 'exp( , )', 'float(x)': 'float()', 'graph(x,x1,y1[x2,y2..])': 'graph( , , )', 'if(x)': 'if()', 'if(x,a)': 'if( , )', 'if(x,a,b)': 'if( , , )', 'if(x,a,b,c)': 'if( , , , )', 'int(x)': 'int()', 'isnull(x)': 'isnull()', 'log(x)': 'log(', 'log(x,b)': 'log( , )', 'max(x,y[,z...])': 'max( , )', 'median(x,y[,z...])': 'median( , )', 'min(x,y[,z...])': 'min( , )', 'mode(x,y[,z...])': 'mode( , )', 'nmax(x,y[,z...])': 'nmax( , )', 'nmedian(x,y[,z...])': 'nmedian( , )', 'nmin(x,y[,z...])': 'nmin( , )', 'nmode(x,y[,z...])': 'nmode( , )', 'not(x)': 'not()', 'pow(x,y)': 'pow( , )', 'rand(a,b)': 'rand( , )', 'round(x)': 'round()', 'round(x,y)': 'round( , )', 'round(x,y,z)': 'round( , , )', 'sin(x)': 'sin()', 'sqrt(x)': 'sqrt()', 'tan(x)': 'tan()', 'xor(x,y)': 'xor( , )', 'row()': 'row()', 'col()': 'col()', 'nrows()': 'nrows()', 'ncols()': 'ncols()', 'x()': 'x()', 'y()': 'y()', 'ewres()': 'ewres()', 'nsres()': 'nsres()', 'area()': 'area()', 'null()': 'null()' } if self.rast3d: self.funct_dict['z()'] = 'z()' self.funct_dict['tbres()'] = 'tbres()' element = 'raster_3d' else: element = 'cell' # characters which can be in raster map name but the map name must be # then quoted self.charactersToQuote = '+-&!<>%~?^|' # stores last typed map name in Select widget to distinguish typing # from selection self.lastMapName = '' self.operatorBox = StaticBox(parent=self.panel, id=wx.ID_ANY, label=" %s " % _('Operators')) self.outputBox = StaticBox(parent=self.panel, id=wx.ID_ANY, label=" %s " % _('Output')) self.operandBox = StaticBox(parent=self.panel, id=wx.ID_ANY, label=" %s " % _('Operands')) self.expressBox = StaticBox(parent=self.panel, id=wx.ID_ANY, label=" %s " % _('Expression')) # # Buttons # self.btn_clear = ClearButton(parent=self.panel) self.btn_help = Button(parent=self.panel, id=wx.ID_HELP) self.btn_run = Button(parent=self.panel, id=wx.ID_ANY, label=_("&Run")) self.btn_run.SetDefault() self.btn_close = CloseButton(parent=self.panel) self.btn_save = Button(parent=self.panel, id=wx.ID_SAVE) self.btn_save.SetToolTip(_('Save expression to file')) self.btn_load = Button(parent=self.panel, id=wx.ID_ANY, label=_("&Load")) self.btn_load.SetToolTip(_('Load expression from file')) self.btn_copy = Button(parent=self.panel, id=wx.ID_ANY, label=_("Copy")) self.btn_copy.SetToolTip( _("Copy the current command string to the clipboard")) self.btn = dict() self.btn['pow'] = Button(parent=self.panel, id=wx.ID_ANY, label="^") self.btn['pow'].SetToolTip(_('exponent')) self.btn['div'] = Button(parent=self.panel, id=wx.ID_ANY, label="/") self.btn['div'].SetToolTip(_('divide')) self.btn['add'] = Button(parent=self.panel, id=wx.ID_ANY, label="+") self.btn['add'].SetToolTip(_('add')) self.btn['minus'] = Button(parent=self.panel, id=wx.ID_ANY, label="-") self.btn['minus'].SetToolTip(_('subtract')) self.btn['mod'] = Button(parent=self.panel, id=wx.ID_ANY, label="%") self.btn['mod'].SetToolTip(_('modulus')) self.btn['mult'] = Button(parent=self.panel, id=wx.ID_ANY, label="*") self.btn['mult'].SetToolTip(_('multiply')) self.btn['parenl'] = Button(parent=self.panel, id=wx.ID_ANY, label="(") self.btn['parenr'] = Button(parent=self.panel, id=wx.ID_ANY, label=")") self.btn['lshift'] = Button(parent=self.panel, id=wx.ID_ANY, label="<<") self.btn['lshift'].SetToolTip(_('left shift')) self.btn['rshift'] = Button(parent=self.panel, id=wx.ID_ANY, label=">>") self.btn['rshift'].SetToolTip(_('right shift')) self.btn['rshiftu'] = Button(parent=self.panel, id=wx.ID_ANY, label=">>>") self.btn['rshiftu'].SetToolTip(_('right shift (unsigned)')) self.btn['gt'] = Button(parent=self.panel, id=wx.ID_ANY, label=">") self.btn['gt'].SetToolTip(_('greater than')) self.btn['gteq'] = Button(parent=self.panel, id=wx.ID_ANY, label=">=") self.btn['gteq'].SetToolTip(_('greater than or equal to')) self.btn['lt'] = Button(parent=self.panel, id=wx.ID_ANY, label="<") self.btn['lt'].SetToolTip(_('less than')) self.btn['lteq'] = Button(parent=self.panel, id=wx.ID_ANY, label="<=") self.btn['lteq'].SetToolTip(_('less than or equal to')) self.btn['eq'] = Button(parent=self.panel, id=wx.ID_ANY, label="==") self.btn['eq'].SetToolTip(_('equal to')) self.btn['noteq'] = Button(parent=self.panel, id=wx.ID_ANY, label="!=") self.btn['noteq'].SetToolTip(_('not equal to')) self.btn['compl'] = Button(parent=self.panel, id=wx.ID_ANY, label="~") self.btn['compl'].SetToolTip(_('one\'s complement')) self.btn['not'] = Button(parent=self.panel, id=wx.ID_ANY, label="!") self.btn['not'].SetToolTip(_('NOT')) self.btn['andbit'] = Button(parent=self.panel, id=wx.ID_ANY, label='&&') self.btn['andbit'].SetToolTip(_('bitwise AND')) self.btn['orbit'] = Button(parent=self.panel, id=wx.ID_ANY, label="|") self.btn['orbit'].SetToolTip(_('bitwise OR')) self.btn['and'] = Button(parent=self.panel, id=wx.ID_ANY, label="&&&&") self.btn['and'].SetToolTip(_('logical AND')) self.btn['andnull'] = Button(parent=self.panel, id=wx.ID_ANY, label="&&&&&&") self.btn['andnull'].SetToolTip(_('logical AND (ignores NULLs)')) self.btn['or'] = Button(parent=self.panel, id=wx.ID_ANY, label="||") self.btn['or'].SetToolTip(_('logical OR')) self.btn['ornull'] = Button(parent=self.panel, id=wx.ID_ANY, label="|||") self.btn['ornull'].SetToolTip(_('logical OR (ignores NULLs)')) self.btn['cond'] = Button(parent=self.panel, id=wx.ID_ANY, label="a ? b : c") self.btn['cond'].SetToolTip(_('conditional')) # # Text area # self.text_mcalc = TextCtrl(parent=self.panel, id=wx.ID_ANY, size=(-1, 100), style=wx.TE_MULTILINE) wx.CallAfter(self.text_mcalc.SetFocus) # # Map and function insertion text and ComboBoxes self.newmaplabel = StaticText(parent=self.panel, id=wx.ID_ANY) if self.rast3d: self.newmaplabel.SetLabel( _('Name for new 3D raster map to create')) else: self.newmaplabel.SetLabel(_('Name for new raster map to create')) # As we can write only to current mapset, names should not be fully qualified # to not confuse end user about writing in other mapset self.newmaptxt = Select(parent=self.panel, id=wx.ID_ANY, size=(250, -1), type=element, multiple=False, fullyQualified=False) self.mapsellabel = StaticText(parent=self.panel, id=wx.ID_ANY) if self.rast3d: self.mapsellabel.SetLabel(_('Insert existing 3D raster map')) else: self.mapsellabel.SetLabel(_('Insert existing raster map')) self.mapselect = Select(parent=self.panel, id=wx.ID_ANY, size=(250, -1), type=element, multiple=False) self.functlabel = StaticText(parent=self.panel, id=wx.ID_ANY, label=_('Insert mapcalc function')) self.function = wx.ComboBox(parent=self.panel, id=wx.ID_ANY, size=(250, -1), choices=sorted(self.funct_dict.keys()), style=wx.CB_DROPDOWN | wx.CB_READONLY | wx.TE_PROCESS_ENTER) self.overwrite = wx.CheckBox( parent=self.panel, id=wx.ID_ANY, label=_("Allow output files to overwrite existing files")) self.overwrite.SetValue( UserSettings.Get(group='cmd', key='overwrite', subkey='enabled')) self.randomSeed = wx.CheckBox( parent=self.panel, label=_("Generate random seed for rand()")) self.randomSeedStaticText = StaticText(parent=self.panel, label=_("Seed:")) self.randomSeedText = TextCtrl(parent=self.panel, size=(100, -1), validator=IntegerValidator()) self.randomSeedText.SetToolTip(_("Integer seed for rand() function")) self.randomSeed.SetValue(True) self.randomSeedStaticText.Disable() self.randomSeedText.Disable() self.addbox = wx.CheckBox( parent=self.panel, label=_('Add created raster map into layer tree'), style=wx.NO_BORDER) self.addbox.SetValue( UserSettings.Get(group='cmd', key='addNewLayer', subkey='enabled')) if not self.parent or self.parent.GetName() != 'LayerManager': self.addbox.Hide() # # Bindings # for btn in self.btn.keys(): self.btn[btn].Bind(wx.EVT_BUTTON, self.AddMark) self.btn_close.Bind(wx.EVT_BUTTON, self.OnClose) self.btn_clear.Bind(wx.EVT_BUTTON, self.OnClear) self.btn_run.Bind(wx.EVT_BUTTON, self.OnMCalcRun) self.btn_help.Bind(wx.EVT_BUTTON, self.OnHelp) self.btn_save.Bind(wx.EVT_BUTTON, self.OnSaveExpression) self.btn_load.Bind(wx.EVT_BUTTON, self.OnLoadExpression) self.btn_copy.Bind(wx.EVT_BUTTON, self.OnCopyCommand) self.mapselect.Bind(wx.EVT_TEXT, self.OnSelect) self.function.Bind(wx.EVT_COMBOBOX, self._return_funct) self.function.Bind(wx.EVT_TEXT_ENTER, self.OnSelect) self.newmaptxt.Bind(wx.EVT_TEXT, self.OnUpdateStatusBar) self.text_mcalc.Bind(wx.EVT_TEXT, self.OnUpdateStatusBar) self.overwrite.Bind(wx.EVT_CHECKBOX, self.OnUpdateStatusBar) self.randomSeed.Bind(wx.EVT_CHECKBOX, self.OnUpdateStatusBar) self.randomSeed.Bind(wx.EVT_CHECKBOX, self.OnSeedFlag) self.randomSeedText.Bind(wx.EVT_TEXT, self.OnUpdateStatusBar) # bind closing to ESC self.Bind(wx.EVT_MENU, self.OnClose, id=wx.ID_CANCEL) accelTableList = [(wx.ACCEL_NORMAL, wx.WXK_ESCAPE, wx.ID_CANCEL)] accelTable = wx.AcceleratorTable(accelTableList) self.SetAcceleratorTable(accelTable) self._layout() self.SetMinSize(self.panel.GetBestSize()) # workaround for http://trac.wxwidgets.org/ticket/13628 self.SetSize(self.panel.GetBestSize()) def _return_funct(self, event): i = event.GetString() self._addSomething(self.funct_dict[i]) # reset win = self.FindWindowById(event.GetId()) win.SetValue('') def _layout(self): sizer = wx.BoxSizer(wx.VERTICAL) controlSizer = wx.BoxSizer(wx.HORIZONTAL) operatorSizer = wx.StaticBoxSizer(self.operatorBox, wx.HORIZONTAL) outOpeSizer = wx.BoxSizer(wx.VERTICAL) buttonSizer1 = wx.GridBagSizer(5, 1) buttonSizer1.Add(self.btn['add'], pos=(0, 0)) buttonSizer1.Add(self.btn['minus'], pos=(0, 1)) buttonSizer1.Add(self.btn['mod'], pos=(5, 0)) buttonSizer1.Add(self.btn['mult'], pos=(1, 0)) buttonSizer1.Add(self.btn['div'], pos=(1, 1)) buttonSizer1.Add(self.btn['pow'], pos=(5, 1)) buttonSizer1.Add(self.btn['gt'], pos=(2, 0)) buttonSizer1.Add(self.btn['gteq'], pos=(2, 1)) buttonSizer1.Add(self.btn['eq'], pos=(4, 0)) buttonSizer1.Add(self.btn['lt'], pos=(3, 0)) buttonSizer1.Add(self.btn['lteq'], pos=(3, 1)) buttonSizer1.Add(self.btn['noteq'], pos=(4, 1)) buttonSizer2 = wx.GridBagSizer(5, 1) buttonSizer2.Add(self.btn['and'], pos=(0, 0)) buttonSizer2.Add(self.btn['andbit'], pos=(1, 0)) buttonSizer2.Add(self.btn['andnull'], pos=(2, 0)) buttonSizer2.Add(self.btn['or'], pos=(0, 1)) buttonSizer2.Add(self.btn['orbit'], pos=(1, 1)) buttonSizer2.Add(self.btn['ornull'], pos=(2, 1)) buttonSizer2.Add(self.btn['lshift'], pos=(3, 0)) buttonSizer2.Add(self.btn['rshift'], pos=(3, 1)) buttonSizer2.Add(self.btn['rshiftu'], pos=(4, 0)) buttonSizer2.Add(self.btn['cond'], pos=(5, 0)) buttonSizer2.Add(self.btn['compl'], pos=(5, 1)) buttonSizer2.Add(self.btn['not'], pos=(4, 1)) outputSizer = wx.StaticBoxSizer(self.outputBox, wx.VERTICAL) outputSizer.Add(self.newmaplabel, flag=wx.ALIGN_CENTER | wx.TOP, border=5) outputSizer.Add(self.newmaptxt, flag=wx.EXPAND | wx.ALL, border=5) operandSizer = wx.StaticBoxSizer(self.operandBox, wx.HORIZONTAL) buttonSizer3 = wx.GridBagSizer(7, 1) buttonSizer3.Add(self.functlabel, pos=(0, 0), span=(1, 2), flag=wx.ALIGN_CENTER | wx.EXPAND) buttonSizer3.Add(self.function, pos=(1, 0), span=(1, 2)) buttonSizer3.Add(self.mapsellabel, pos=(2, 0), span=(1, 2), flag=wx.ALIGN_CENTER) buttonSizer3.Add(self.mapselect, pos=(3, 0), span=(1, 2)) threebutton = wx.GridBagSizer(1, 2) threebutton.Add(self.btn['parenl'], pos=(0, 0), span=(1, 1), flag=wx.ALIGN_LEFT) threebutton.Add(self.btn['parenr'], pos=(0, 1), span=(1, 1), flag=wx.ALIGN_CENTER) threebutton.Add(self.btn_clear, pos=(0, 2), span=(1, 1), flag=wx.ALIGN_RIGHT) buttonSizer3.Add(threebutton, pos=(4, 0), span=(1, 1), flag=wx.ALIGN_CENTER) buttonSizer4 = wx.BoxSizer(wx.HORIZONTAL) buttonSizer4.Add(self.btn_load, flag=wx.ALL, border=5) buttonSizer4.Add(self.btn_save, flag=wx.ALL, border=5) buttonSizer4.Add(self.btn_copy, flag=wx.ALL, border=5) buttonSizer4.AddSpacer(30) buttonSizer4.Add(self.btn_help, flag=wx.ALL, border=5) buttonSizer4.Add(self.btn_run, flag=wx.ALL, border=5) buttonSizer4.Add(self.btn_close, flag=wx.ALL, border=5) operatorSizer.Add(buttonSizer1, proportion=0, flag=wx.ALL | wx.EXPAND, border=5) operatorSizer.Add(buttonSizer2, proportion=0, flag=wx.TOP | wx.BOTTOM | wx.RIGHT | wx.EXPAND, border=5) operandSizer.Add(buttonSizer3, proportion=0, flag=wx.ALL, border=5) controlSizer.Add(operatorSizer, proportion=1, flag=wx.RIGHT | wx.EXPAND, border=5) outOpeSizer.Add(outputSizer, proportion=0, flag=wx.EXPAND) outOpeSizer.Add(operandSizer, proportion=1, flag=wx.EXPAND | wx.TOP, border=5) controlSizer.Add(outOpeSizer, proportion=0, flag=wx.EXPAND) expressSizer = wx.StaticBoxSizer(self.expressBox, wx.HORIZONTAL) expressSizer.Add(self.text_mcalc, proportion=1, flag=wx.EXPAND) sizer.Add(controlSizer, proportion=0, flag=wx.EXPAND | wx.ALL, border=5) sizer.Add(expressSizer, proportion=1, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=5) sizer.Add(buttonSizer4, proportion=0, flag=wx.ALIGN_RIGHT | wx.ALL, border=3) randomSizer = wx.BoxSizer(wx.HORIZONTAL) randomSizer.Add(self.randomSeed, proportion=0, flag=wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, border=20) randomSizer.Add(self.randomSeedStaticText, proportion=0, flag=wx.RIGHT | wx.ALIGN_CENTER_VERTICAL, border=5) randomSizer.Add(self.randomSeedText, proportion=0) sizer.Add(randomSizer, proportion=0, flag=wx.LEFT | wx.RIGHT, border=5) sizer.Add(self.overwrite, proportion=0, flag=wx.LEFT | wx.RIGHT, border=5) if self.addbox.IsShown(): sizer.Add(self.addbox, proportion=0, flag=wx.LEFT | wx.RIGHT, border=5) self.panel.SetAutoLayout(True) self.panel.SetSizer(sizer) sizer.Fit(self.panel) self.Layout() def AddMark(self, event): """Sends operators to insertion method """ if event.GetId() == self.btn['compl'].GetId(): mark = "~" elif event.GetId() == self.btn['not'].GetId(): mark = "!" elif event.GetId() == self.btn['pow'].GetId(): mark = "^" elif event.GetId() == self.btn['div'].GetId(): mark = "/" elif event.GetId() == self.btn['add'].GetId(): mark = "+" elif event.GetId() == self.btn['minus'].GetId(): mark = "-" elif event.GetId() == self.btn['mod'].GetId(): mark = "%" elif event.GetId() == self.btn['mult'].GetId(): mark = "*" elif event.GetId() == self.btn['lshift'].GetId(): mark = "<<" elif event.GetId() == self.btn['rshift'].GetId(): mark = ">>" elif event.GetId() == self.btn['rshiftu'].GetId(): mark = ">>>" elif event.GetId() == self.btn['gt'].GetId(): mark = ">" elif event.GetId() == self.btn['gteq'].GetId(): mark = ">=" elif event.GetId() == self.btn['lt'].GetId(): mark = "<" elif event.GetId() == self.btn['lteq'].GetId(): mark = "<=" elif event.GetId() == self.btn['eq'].GetId(): mark = "==" elif event.GetId() == self.btn['noteq'].GetId(): mark = "!=" elif event.GetId() == self.btn['andbit'].GetId(): mark = "&" elif event.GetId() == self.btn['orbit'].GetId(): mark = "|" elif event.GetId() == self.btn['or'].GetId(): mark = "||" elif event.GetId() == self.btn['ornull'].GetId(): mark = "|||" elif event.GetId() == self.btn['and'].GetId(): mark = "&&" elif event.GetId() == self.btn['andnull'].GetId(): mark = "&&&" elif event.GetId() == self.btn['cond'].GetId(): mark = " ? : " elif event.GetId() == self.btn['parenl'].GetId(): mark = "(" elif event.GetId() == self.btn['parenr'].GetId(): mark = ")" self._addSomething(mark) # unused # def OnSelectTextEvt(self, event): # """Checks if user is typing or the event was emited by map selection. # Prevents from changing focus. # """ # item = self.mapselect.GetValue().strip() # if not (abs(len(item) - len(self.lastMapName)) == 1 and \ # self.lastMapName in item or item in self.lastMapName): # self.OnSelect(event) # self.lastMapName = item def OnSelect(self, event): """Gets raster map or function selection and send it to insertion method. Checks for characters which can be in raster map name but the raster map name must be then quoted. """ win = self.FindWindowById(event.GetId()) item = win.GetValue().strip() if any((char in item) for char in self.charactersToQuote): item = '"' + item + '"' self._addSomething(item) win.ChangeValue('') # reset # Map selector likes to keep focus. Set it back to expression input area wx.CallAfter(self.text_mcalc.SetFocus) def OnUpdateStatusBar(self, event): """Update statusbar text""" command = self._getCommand() self.SetStatusText(command) event.Skip() def OnSeedFlag(self, event): checked = self.randomSeed.IsChecked() self.randomSeedText.Enable(not checked) self.randomSeedStaticText.Enable(not checked) event.Skip() def _getCommand(self): """Returns entire command as string.""" expr = self.text_mcalc.GetValue().strip().replace("\n", " ") cmd = 'r.mapcalc' if self.rast3d: cmd = 'r3.mapcalc' overwrite = '' if self.overwrite.IsChecked(): overwrite = ' --overwrite' seed_flag = seed = '' if re.search(pattern="rand *\(.+\)", string=expr): if self.randomSeed.IsChecked(): seed_flag = ' -s' else: seed = " seed={val}".format( val=self.randomSeedText.GetValue().strip()) return ('{cmd} expression="{new} = {expr}"{seed}{seed_flag}{overwrite}' .format(cmd=cmd, expr=expr, new=self.newmaptxt.GetValue(), seed_flag=seed_flag, seed=seed, overwrite=overwrite)) def _addSomething(self, what): """Inserts operators, map names, and functions into text area """ mcalcstr = self.text_mcalc.GetValue() position = self.text_mcalc.GetInsertionPoint() newmcalcstr = mcalcstr[:position] position_offset = 0 try: if newmcalcstr[-1] != ' ': newmcalcstr += ' ' position_offset += 1 except: pass newmcalcstr += what # Do not add extra space if there is already one try: if newmcalcstr[-1] != ' ' and mcalcstr[position] != ' ': newmcalcstr += ' ' except: newmcalcstr += ' ' newmcalcstr += mcalcstr[position:] self.text_mcalc.SetValue(newmcalcstr) if len(what) > 0: match = re.search(pattern="\(.*\)", string=what) if match: position_offset += match.start() + 1 else: position_offset += len(what) try: if newmcalcstr[position + position_offset] == ' ': position_offset += 1 except: pass self.text_mcalc.SetInsertionPoint(position + position_offset) self.text_mcalc.Update() self.text_mcalc.SetFocus() def OnMCalcRun(self, event): """Builds and runs r.mapcalc statement """ name = self.newmaptxt.GetValue().strip() if not name: GError(parent=self, message=_("You must enter the name of " "a new raster map to create.")) return if not (name[0] == '"' and name[-1] == '"') and any( (char in name) for char in self.charactersToQuote): name = '"' + name + '"' expr = self.text_mcalc.GetValue().strip().replace("\n", " ") if not expr: GError(parent=self, message=_("You must enter an expression " "to create a new raster map.")) return seed_flag = seed = None if re.search(pattern="rand *\(.+\)", string=expr): if self.randomSeed.IsChecked(): seed_flag = '-s' else: seed = self.randomSeedText.GetValue().strip() if self.log: cmd = [self.cmd] if seed_flag: cmd.append('-s') if seed: cmd.append("seed={val}".format(val=seed)) if self.overwrite.IsChecked(): cmd.append('--overwrite') cmd.append(str('expression=%s = %s' % (name, expr))) self.log.RunCmd(cmd, onDone=self.OnDone) self.parent.Raise() else: if self.overwrite.IsChecked(): overwrite = True else: overwrite = False params = dict(expression="%s=%s" % (name, expr), overwrite=overwrite) if seed_flag: params['flags'] = 's' if seed: params['seed'] = seed RunCommand(self.cmd, **params) def OnDone(self, event): """Add create map to the layer tree Sends the mapCreated signal from the grass interface. """ if event.returncode != 0: return name = self.newmaptxt.GetValue().strip( ' "') + '@' + grass.gisenv()['MAPSET'] ltype = 'raster' if self.rast3d: ltype = 'raster_3d' self._giface.mapCreated.emit(name=name, ltype=ltype, add=self.addbox.IsChecked()) gisenv = grass.gisenv() self._giface.grassdbChanged.emit(grassdb=gisenv['GISDBASE'], location=gisenv['LOCATION_NAME'], mapset=gisenv['MAPSET'], action='new', map=name.split('@')[0], element=ltype) def OnSaveExpression(self, event): """Saves expression to file """ mctxt = self.newmaptxt.GetValue() + ' = ' + self.text_mcalc.GetValue( ) + os.linesep # dialog dlg = wx.FileDialog( parent=self, message=_("Choose a file name to save the expression"), wildcard=_("Expression file (*)|*"), style=wx.FD_SAVE | wx.FD_OVERWRITE_PROMPT) if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() if not path: dlg.Destroy() return try: fobj = open(path, 'w') fobj.write(mctxt) finally: fobj.close() dlg.Destroy() def OnLoadExpression(self, event): """Load expression from file """ dlg = wx.FileDialog( parent=self, message=_("Choose a file name to load the expression"), wildcard=_("Expression file (*)|*"), style=wx.FD_OPEN) if dlg.ShowModal() == wx.ID_OK: path = dlg.GetPath() if not path: dlg.Destroy() return try: fobj = open(path, 'r') mctxt = fobj.read() finally: fobj.close() try: result, exp = mctxt.split('=', 1) except ValueError: result = '' exp = mctxt self.newmaptxt.SetValue(result.strip()) self.text_mcalc.SetValue(exp.strip()) self.text_mcalc.SetFocus() self.text_mcalc.SetInsertionPointEnd() dlg.Destroy() def OnCopyCommand(self, event): command = self._getCommand() cmddata = wx.TextDataObject() cmddata.SetText(command) if wx.TheClipboard.Open(): wx.TheClipboard.SetData(cmddata) wx.TheClipboard.Close() self.SetStatusText( _("'{cmd}' copied to clipboard").format(cmd=command)) def OnClear(self, event): """Clears text area """ self.text_mcalc.SetValue('') def OnHelp(self, event): """Launches r.mapcalc help """ RunCommand('g.manual', parent=self, entry=self.cmd) def OnClose(self, event): """Close window""" self.Destroy()
class AttributeManager(wx.Frame, DbMgrBase): def __init__(self, parent, id=wx.ID_ANY, title=None, vectorName=None, item=None, log=None, selection=None, **kwargs): """GRASS Attribute Table Manager window :param parent: parent window :param id: window id :param title: window title or None for default title :param vectorName: name of vector map :param item: item from Layer Tree :param log: log window :param selection: name of page to be selected :param kwagrs: other wx.Frame's arguments """ self.parent = parent try: mapdisplay = self.parent.GetMapDisplay() except: mapdisplay = None DbMgrBase.__init__(self, id=id, mapdisplay=mapdisplay, vectorName=vectorName, item=item, log=log, statusbar=self, **kwargs) wx.Frame.__init__(self, parent, id, *kwargs) # title if not title: title = "%s" % _("GRASS GIS Attribute Table Manager - ") if not self.dbMgrData['editable']: title += _("READONLY - ") title += "<%s>" % (self.dbMgrData['vectName']) self.SetTitle(title) # icon self.SetIcon( wx.Icon(os.path.join(globalvar.ICONDIR, 'grass_sql.ico'), wx.BITMAP_TYPE_ICO)) self.panel = wx.Panel(parent=self, id=wx.ID_ANY) if len(self.dbMgrData['mapDBInfo'].layers.keys()) == 0: GMessage(parent=self.parent, message=_("Database connection for vector map <%s> " "is not defined in DB file. " "You can define new connection in " "'Manage layers' tab.") % self.dbMgrData['vectName']) busy = wx.BusyInfo(_("Please wait, loading attribute data..."), parent=self.parent) wx.SafeYield() self.CreateStatusBar(number=1) self.notebook = GNotebook(self.panel, style=globalvar.FNPageDStyle) self.CreateDbMgrPage(parent=self, pageName='browse') self.notebook.AddPage(page=self.pages['browse'], text=_("Browse data"), name='browse') self.pages['browse'].SetTabAreaColour(globalvar.FNPageColor) self.CreateDbMgrPage(parent=self, pageName='manageTable') self.notebook.AddPage(page=self.pages['manageTable'], text=_("Manage tables"), name='table') self.pages['manageTable'].SetTabAreaColour(globalvar.FNPageColor) self.CreateDbMgrPage(parent=self, pageName='manageLayer') self.notebook.AddPage(page=self.pages['manageLayer'], text=_("Manage layers"), name='layers') del busy if selection: wx.CallAfter(self.notebook.SetSelectionByName, selection) else: wx.CallAfter(self.notebook.SetSelection, 0) # select browse tab # buttons self.btnClose = CloseButton(parent=self.panel) self.btnClose.SetToolTip(_("Close Attribute Table Manager")) self.btnReload = Button(parent=self.panel, id=wx.ID_REFRESH) self.btnReload.SetToolTip( _("Reload currently selected attribute data")) self.btnReset = ClearButton(parent=self.panel) self.btnReset.SetToolTip( _("Reload all attribute data (drop current selection)")) # bind closing to ESC self.Bind(wx.EVT_MENU, self.OnCloseWindow, id=wx.ID_CANCEL) accelTableList = [(wx.ACCEL_NORMAL, wx.WXK_ESCAPE, wx.ID_CANCEL)] accelTable = wx.AcceleratorTable(accelTableList) self.SetAcceleratorTable(accelTable) # events self.btnClose.Bind(wx.EVT_BUTTON, self.OnCloseWindow) self.btnReload.Bind(wx.EVT_BUTTON, self.OnReloadData) self.btnReset.Bind(wx.EVT_BUTTON, self.OnReloadDataAll) self.notebook.Bind(FN.EVT_FLATNOTEBOOK_PAGE_CHANGED, self.OnPageChanged) self.Bind(wx.EVT_CLOSE, self.OnCloseWindow) # do layout self._layout() # self.SetMinSize(self.GetBestSize()) self.SetSize((700, 550)) # FIXME hard-coded size self.SetMinSize(self.GetSize()) def _layout(self): """Do layout""" # frame body mainSizer = wx.BoxSizer(wx.VERTICAL) # buttons btnSizer = wx.BoxSizer(wx.HORIZONTAL) btnSizer.Add(self.btnReset, proportion=1, flag=wx.ALL, border=5) btnSizer.Add(self.btnReload, proportion=1, flag=wx.ALL, border=5) btnSizer.Add(self.btnClose, proportion=1, flag=wx.ALL, border=5) mainSizer.Add(self.notebook, proportion=1, flag=wx.EXPAND) mainSizer.Add(btnSizer, flag=wx.ALIGN_RIGHT | wx.ALL, border=5) self.panel.SetAutoLayout(True) self.panel.SetSizer(mainSizer) mainSizer.Fit(self.panel) self.Layout() def OnCloseWindow(self, event): """Cancel button pressed""" if self.parent and self.parent.GetName() == 'LayerManager': # deregister ATM self.parent.dialogs['atm'].remove(self) if not isinstance(event, wx.CloseEvent): self.Destroy() event.Skip() def OnReloadData(self, event): """Reload data""" if self.pages['browse']: self.pages['browse'].OnDataReload(event) # TODO replace by signal def OnReloadDataAll(self, event): """Reload all data""" if self.pages['browse']: self.pages['browse'].ResetPage() def OnPageChanged(self, event): """On page in ATM is changed""" try: if self.pages["browse"]: selPage = self.pages["browse"].selLayer id = self.pages["browse"].layerPage[selPage]['data'] else: id = None except KeyError: id = None if event.GetSelection() == self.notebook.GetPageIndexByName( 'browse') and id: win = self.FindWindowById(id) if win: self.log.write( _("Number of loaded records: %d") % win.GetItemCount()) else: self.log.write("") self.btnReload.Enable() self.btnReset.Enable() else: self.log.write("") self.btnReload.Enable(False) self.btnReset.Enable(False) event.Skip() def OnTextEnter(self, event): pass def UpdateDialog(self, layer): """Updates dialog layout for given layer""" DbMgrBase.UpdateDialog(self, layer=layer) # set current page selection self.notebook.SetSelectionByName('layers')
class SQLBuilder(wx.Frame): """SQLBuider class Base class for classes, which builds SQL statements. """ def __init__(self, parent, title, vectmap, modeChoices=[], id=wx.ID_ANY, layer=1): wx.Frame.__init__(self, parent, id, title) self.SetIcon( wx.Icon(os.path.join(globalvar.ICONDIR, "grass_sql.ico"), wx.BITMAP_TYPE_ICO)) self.parent = parent # variables self.vectmap = vectmap # fullname if "@" not in self.vectmap: self.vectmap = grass.find_file(self.vectmap, element="vector")["fullname"] if not self.vectmap: grass.fatal(_("Vector map <%s> not found") % vectmap) self.mapname, self.mapset = self.vectmap.split("@", 1) # db info self.layer = layer self.dbInfo = VectorDBInfo(self.vectmap) self.tablename = self.dbInfo.GetTable(self.layer) self.driver, self.database = self.dbInfo.GetDbSettings(self.layer) self.colvalues = [] # array with unique values in selected column self.panel = wx.Panel(parent=self, id=wx.ID_ANY) # statusbar self.statusbar = self.CreateStatusBar(number=1) self._doLayout(modeChoices) self.panel.SetAutoLayout(True) self.panel.SetSizer(self.pagesizer) self.pagesizer.Fit(self.panel) self.SetMinSize((400, 600)) self.SetClientSize(self.panel.GetSize()) self.CenterOnParent() def _doLayout(self, modeChoices, showDbInfo=False): """Do dialog layout""" self.pagesizer = wx.BoxSizer(wx.VERTICAL) # dbInfo if showDbInfo: databasebox = StaticBox(parent=self.panel, id=wx.ID_ANY, label=" %s " % _("Database connection")) databaseboxsizer = wx.StaticBoxSizer(databasebox, wx.VERTICAL) databaseboxsizer.Add( CreateDbInfoDesc(self.panel, self.dbInfo, layer=self.layer), proportion=1, flag=wx.EXPAND | wx.ALL, border=3, ) # # text areas # # sql box sqlbox = StaticBox(parent=self.panel, id=wx.ID_ANY, label=" %s " % _("Query")) sqlboxsizer = wx.StaticBoxSizer(sqlbox, wx.VERTICAL) self.text_sql = TextCtrl( parent=self.panel, id=wx.ID_ANY, value="", size=(-1, 50), style=wx.TE_MULTILINE, ) self.text_sql.SetInsertionPointEnd() wx.CallAfter(self.text_sql.SetFocus) sqlboxsizer.Add(self.text_sql, flag=wx.EXPAND) # # buttons # self.btn_clear = ClearButton(parent=self.panel) self.btn_clear.SetToolTip(_("Set SQL statement to default")) self.btn_apply = ApplyButton(parent=self.panel) self.btn_apply.SetToolTip(_("Apply SQL statement")) self.btn_close = CloseButton(parent=self.panel) self.btn_close.SetToolTip(_("Close the dialog")) self.btn_logic = { "is": [ "=", ], "isnot": [ "!=", ], "like": [ "LIKE", ], "gt": [ ">", ], "ge": [ ">=", ], "lt": [ "<", ], "le": [ "<=", ], "or": [ "OR", ], "not": [ "NOT", ], "and": [ "AND", ], "brac": [ "()", ], "prc": [ "%", ], } self.btn_logicpanel = wx.Panel(parent=self.panel, id=wx.ID_ANY) for key, value in six.iteritems(self.btn_logic): btn = Button(parent=self.btn_logicpanel, id=wx.ID_ANY, label=value[0]) self.btn_logic[key].append(btn.GetId()) self.buttonsizer = wx.FlexGridSizer(cols=4, hgap=5, vgap=5) self.buttonsizer.Add(self.btn_clear) self.buttonsizer.Add(self.btn_apply) self.buttonsizer.Add(self.btn_close) btn_logicsizer = wx.GridBagSizer(5, 5) btn_logicsizer.Add(self.FindWindowById(self.btn_logic["is"][1]), pos=(0, 0)) btn_logicsizer.Add(self.FindWindowById(self.btn_logic["isnot"][1]), pos=(1, 0)) btn_logicsizer.Add(self.FindWindowById(self.btn_logic["like"][1]), pos=(2, 0)) btn_logicsizer.Add(self.FindWindowById(self.btn_logic["gt"][1]), pos=(0, 1)) btn_logicsizer.Add(self.FindWindowById(self.btn_logic["ge"][1]), pos=(1, 1)) btn_logicsizer.Add(self.FindWindowById(self.btn_logic["or"][1]), pos=(2, 1)) btn_logicsizer.Add(self.FindWindowById(self.btn_logic["lt"][1]), pos=(0, 2)) btn_logicsizer.Add(self.FindWindowById(self.btn_logic["le"][1]), pos=(1, 2)) btn_logicsizer.Add(self.FindWindowById(self.btn_logic["not"][1]), pos=(2, 2)) btn_logicsizer.Add(self.FindWindowById(self.btn_logic["brac"][1]), pos=(0, 3)) btn_logicsizer.Add(self.FindWindowById(self.btn_logic["prc"][1]), pos=(1, 3)) btn_logicsizer.Add(self.FindWindowById(self.btn_logic["and"][1]), pos=(2, 3)) self.btn_logicpanel.SetSizer(btn_logicsizer) # # list boxes (columns, values) # self.hsizer = wx.BoxSizer(wx.HORIZONTAL) columnsbox = StaticBox(parent=self.panel, id=wx.ID_ANY, label=" %s " % _("Columns")) columnsizer = wx.StaticBoxSizer(columnsbox, wx.VERTICAL) self.list_columns = wx.ListBox( parent=self.panel, id=wx.ID_ANY, choices=self.dbInfo.GetColumns(self.tablename), style=wx.LB_MULTIPLE, ) columnsizer.Add(self.list_columns, proportion=1, flag=wx.EXPAND) if modeChoices: modesizer = wx.BoxSizer(wx.VERTICAL) self.mode = wx.RadioBox( parent=self.panel, id=wx.ID_ANY, label=" %s " % _("Interactive insertion"), choices=modeChoices, style=wx.RA_SPECIFY_COLS, majorDimension=1, ) self.mode.SetSelection(1) # default 'values' modesizer.Add(self.mode, proportion=1, flag=wx.EXPAND, border=5) # self.list_columns.SetMinSize((-1,130)) # self.list_values.SetMinSize((-1,100)) self.valuespanel = wx.Panel(parent=self.panel, id=wx.ID_ANY) valuesbox = StaticBox(parent=self.valuespanel, id=wx.ID_ANY, label=" %s " % _("Values")) valuesizer = wx.StaticBoxSizer(valuesbox, wx.VERTICAL) self.list_values = wx.ListBox( parent=self.valuespanel, id=wx.ID_ANY, choices=self.colvalues, style=wx.LB_MULTIPLE, ) valuesizer.Add(self.list_values, proportion=1, flag=wx.EXPAND) self.valuespanel.SetSizer(valuesizer) self.btn_unique = Button(parent=self.valuespanel, id=wx.ID_ANY, label=_("Get all values")) self.btn_unique.Enable(False) self.btn_uniquesample = Button(parent=self.valuespanel, id=wx.ID_ANY, label=_("Get sample")) self.btn_uniquesample.SetToolTip( _("Get first 256 unique values as sample")) self.btn_uniquesample.Enable(False) buttonsizer3 = wx.BoxSizer(wx.HORIZONTAL) buttonsizer3.Add(self.btn_uniquesample, proportion=0, flag=wx.RIGHT, border=5) buttonsizer3.Add(self.btn_unique, proportion=0) valuesizer.Add(buttonsizer3, proportion=0, flag=wx.TOP, border=5) # go to gotosizer = wx.BoxSizer(wx.HORIZONTAL) self.goto = TextCtrl(parent=self.valuespanel, id=wx.ID_ANY, style=wx.TE_PROCESS_ENTER) gotosizer.Add( StaticText(parent=self.valuespanel, id=wx.ID_ANY, label=_("Go to:")), proportion=0, flag=wx.ALIGN_CENTER_VERTICAL | wx.RIGHT, border=5, ) gotosizer.Add(self.goto, proportion=1, flag=wx.EXPAND) valuesizer.Add(gotosizer, proportion=0, flag=wx.ALL | wx.EXPAND, border=5) self.hsizer.Add(columnsizer, proportion=1, flag=wx.EXPAND) self.hsizer.Add(self.valuespanel, proportion=1, flag=wx.EXPAND) self.close_onapply = wx.CheckBox(parent=self.panel, id=wx.ID_ANY, label=_("Close dialog on apply")) self.close_onapply.SetValue(True) if showDbInfo: self.pagesizer.Add(databaseboxsizer, flag=wx.ALL | wx.EXPAND, border=5) if modeChoices: self.pagesizer.Add( modesizer, proportion=0, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border=5, ) self.pagesizer.Add( self.hsizer, proportion=1, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border=5, ) # self.pagesizer.Add(self.btn_uniqe,0,wx.ALIGN_LEFT|wx.TOP,border=5) # self.pagesizer.Add(self.btn_uniqesample,0,wx.ALIGN_LEFT|wx.TOP,border=5) self.pagesizer.Add(self.btn_logicpanel, proportion=0, flag=wx.ALIGN_CENTER_HORIZONTAL) self.pagesizer.Add(sqlboxsizer, proportion=0, flag=wx.EXPAND | wx.LEFT | wx.RIGHT, border=5) self.pagesizer.Add(self.buttonsizer, proportion=0, flag=wx.ALIGN_RIGHT | wx.ALL, border=5) self.pagesizer.Add( self.close_onapply, proportion=0, flag=wx.LEFT | wx.RIGHT | wx.BOTTOM | wx.EXPAND, border=5, ) # # bindings # if modeChoices: self.mode.Bind(wx.EVT_RADIOBOX, self.OnMode) # self.text_sql.Bind(wx.EVT_ACTIVATE, self.OnTextSqlActivate)TODO self.btn_unique.Bind(wx.EVT_BUTTON, self.OnUniqueValues) self.btn_uniquesample.Bind(wx.EVT_BUTTON, self.OnSampleValues) for key, value in six.iteritems(self.btn_logic): self.FindWindowById(value[1]).Bind(wx.EVT_BUTTON, self.OnAddMark) self.btn_close.Bind(wx.EVT_BUTTON, self.OnClose) self.btn_clear.Bind(wx.EVT_BUTTON, self.OnClear) self.btn_apply.Bind(wx.EVT_BUTTON, self.OnApply) self.list_columns.Bind(wx.EVT_LISTBOX, self.OnAddColumn) self.list_values.Bind(wx.EVT_LISTBOX, self.OnAddValue) self.goto.Bind(wx.EVT_TEXT, self.OnGoTo) self.goto.Bind(wx.EVT_TEXT_ENTER, self.OnAddValue) def OnUniqueValues(self, event, justsample=False): """Get unique values""" vals = [] try: idx = self.list_columns.GetSelections()[0] column = self.list_columns.GetString(idx) except: self.list_values.Clear() return self.list_values.Clear() sql = "SELECT DISTINCT {column} FROM {table} ORDER BY {column}".format( column=column, table=self.tablename) if justsample: sql += " LIMIT {}".format(255) data = grass.db_select(sql=sql, database=self.database, driver=self.driver, sep="{_sep_}") if not data: return desc = self.dbInfo.GetTableDesc(self.dbInfo.GetTable( self.layer))[column] i = 0 items = [] for item in data: # sorted(set(map(lambda x: desc['ctype'](x[0]), data))): if desc["type"] not in ("character", "text"): items.append(str(item[0])) else: items.append("'{}'".format(GetUnicodeValue(item[0]))) i += 1 self.list_values.AppendItems(items) def OnSampleValues(self, event): """Get sample values""" self.OnUniqueValues(None, True) def OnAddColumn(self, event): """Add column name to the query""" idx = self.list_columns.GetSelections() for i in idx: column = self.list_columns.GetString(i) self._add(element="column", value=column) if not self.btn_uniquesample.IsEnabled(): self.btn_uniquesample.Enable(True) self.btn_unique.Enable(True) def OnAddValue(self, event): """Add value""" selection = self.list_values.GetSelections() if not selection: event.Skip() return idx = selection[0] value = self.list_values.GetString(idx) idx = self.list_columns.GetSelections()[0] column = self.list_columns.GetString(idx) ctype = self.dbInfo.GetTableDesc(self.dbInfo.GetTable( self.layer))[column]["type"] self._add(element="value", value=value) def OnGoTo(self, event): # clear all previous selections for item in self.list_values.GetSelections(): self.list_values.Deselect(item) gotoText = event.GetString() lenLimit = len(gotoText) found = idx = 0 string = False for item in self.list_values.GetItems(): if idx == 0 and item.startswith("'"): string = True if string: item = item[1:-1] # strip "'" if item[:lenLimit] == gotoText: found = idx break idx += 1 if found > 0: self.list_values.SetSelection(found) def OnAddMark(self, event): """Add mark""" mark = None if self.btn_logicpanel and self.btn_logicpanel.IsShown(): btns = self.btn_logic elif self.btn_arithmeticpanel and self.btn_arithmeticpanel.IsShown(): btns = self.btn_arithmetic for key, value in six.iteritems(btns): if event.GetId() == value[1]: mark = value[0] break self._add(element="mark", value=mark) def GetSQLStatement(self): """Return SQL statement""" return self.text_sql.GetValue().strip().replace("\n", " ") def OnClose(self, event): self.Destroy() event.Skip()