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 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 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 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()