class DBManager(QMainWindow): def __init__(self, iface, parent=None): QMainWindow.__init__(self, parent) self.setAttribute(Qt.WA_DeleteOnClose) self.setupUi() self.iface = iface # restore the window state settings = QgsSettings() self.restoreGeometry(settings.value("/DB_Manager/mainWindow/geometry", QByteArray(), type=QByteArray)) self.restoreState(settings.value("/DB_Manager/mainWindow/windowState", QByteArray(), type=QByteArray)) self.tabs.currentChanged.connect(self.tabChanged) self.tree.selectedItemChanged.connect(self.itemChanged) self.itemChanged(None) def closeEvent(self, e): self.unregisterAllActions() # clear preview, this will delete the layer in preview tab self.preview.loadPreview(None) # save the window state settings = QgsSettings() settings.setValue("/DB_Manager/mainWindow/windowState", self.saveState()) settings.setValue("/DB_Manager/mainWindow/geometry", self.saveGeometry()) QMainWindow.closeEvent(self, e) def refreshItem(self, item=None): with OverrideCursor(Qt.WaitCursor): try: if item is None: item = self.tree.currentItem() self.tree.refreshItem(item) # refresh item children in the db tree except BaseError as e: DlgDbError.showError(e, self) def itemChanged(self, item): with OverrideCursor(Qt.WaitCursor): try: self.reloadButtons() # clear preview, this will delete the layer in preview tab self.preview.loadPreview(None) self.refreshTabs() except BaseError as e: DlgDbError.showError(e, self) def reloadButtons(self): db = self.tree.currentDatabase() if not hasattr(self, '_lastDb'): self._lastDb = db elif db == self._lastDb: return # remove old actions if self._lastDb is not None: self.unregisterAllActions() # add actions of the selected database self._lastDb = db if self._lastDb is not None: self._lastDb.registerAllActions(self) def tabChanged(self, index): with OverrideCursor(Qt.WaitCursor): try: self.refreshTabs() except BaseError as e: DlgDbError.showError(e, self) def refreshTabs(self): index = self.tabs.currentIndex() item = self.tree.currentItem() table = self.tree.currentTable() # enable/disable tabs self.tabs.setTabEnabled(self.tabs.indexOf(self.table), table is not None) self.tabs.setTabEnabled(self.tabs.indexOf(self.preview), table is not None and table.type in [table.VectorType, table.RasterType] and table.geomColumn is not None) # show the info tab if the current tab is disabled if not self.tabs.isTabEnabled(index): self.tabs.setCurrentWidget(self.info) current_tab = self.tabs.currentWidget() if current_tab == self.info: self.info.showInfo(item) elif current_tab == self.table: self.table.loadData(item) elif current_tab == self.preview: self.preview.loadPreview(item) def refreshActionSlot(self): self.info.setDirty() self.table.setDirty() self.preview.setDirty() self.refreshItem() def importActionSlot(self): db = self.tree.currentDatabase() if db is None: self.infoBar.pushMessage(self.tr("No database selected or you are not connected to it."), QgsMessageBar.INFO, self.iface.messageTimeout()) return outUri = db.uri() schema = self.tree.currentSchema() if schema: outUri.setDataSource(schema.name, "", "", "") from .dlg_import_vector import DlgImportVector dlg = DlgImportVector(None, db, outUri, self) dlg.exec_() def exportActionSlot(self): table = self.tree.currentTable() if table is None: self.infoBar.pushMessage(self.tr("Select the table you want export to file."), QgsMessageBar.INFO, self.iface.messageTimeout()) return inLayer = table.toMapLayer() if inLayer.type() != QgsMapLayer.VectorLayer: self.infoBar.pushMessage( self.tr("Select a vector or a tabular layer you want export."), QgsMessageBar.WARNING, self.iface.messageTimeout()) return from .dlg_export_vector import DlgExportVector dlg = DlgExportVector(inLayer, table.database(), self) dlg.exec_() inLayer.deleteLater() def runSqlWindow(self): db = self.tree.currentDatabase() if db is None: self.infoBar.pushMessage(self.tr("No database selected or you are not connected to it."), QgsMessageBar.INFO, self.iface.messageTimeout()) # force displaying of the message, it appears on the first tab (i.e. Info) self.tabs.setCurrentIndex(0) return from .dlg_sql_window import DlgSqlWindow query = DlgSqlWindow(self.iface, db, self) dbname = db.connection().connectionName() tabname = self.tr("Query ({0})").format(dbname) index = self.tabs.addTab(query, tabname) self.tabs.setTabIcon(index, db.connection().icon()) self.tabs.setCurrentIndex(index) query.nameChanged.connect(functools.partial(self.update_query_tab_name, index, dbname)) def runSqlLayerWindow(self, layer): from .dlg_sql_layer_window import DlgSqlLayerWindow query = DlgSqlLayerWindow(self.iface, layer, self) lname = layer.name() tabname = self.tr("Layer ({0})").format(lname) index = self.tabs.addTab(query, tabname) # self.tabs.setTabIcon(index, db.connection().icon()) self.tabs.setCurrentIndex(index) def update_query_tab_name(self, index, dbname, queryname): if not queryname: queryname = self.tr("Query") tabname = u"%s (%s)" % (queryname, dbname) self.tabs.setTabText(index, tabname) def showSystemTables(self): self.tree.showSystemTables(self.actionShowSystemTables.isChecked()) def registerAction(self, action, menuName, callback=None): """ register an action to the manager's main menu """ if not hasattr(self, '_registeredDbActions'): self._registeredDbActions = {} if callback is not None: def invoke_callback(x): return self.invokeCallback(callback) if menuName is None or menuName == "": self.addAction(action) if menuName not in self._registeredDbActions: self._registeredDbActions[menuName] = list() self._registeredDbActions[menuName].append(action) if callback is not None: action.triggered.connect(invoke_callback) return True # search for the menu actionMenu = None helpMenuAction = None for a in self.menuBar.actions(): if not a.menu() or a.menu().title() != menuName: continue if a.menu() != self.menuHelp: helpMenuAction = a actionMenu = a break # not found, add a new menu before the help menu if actionMenu is None: menu = QMenu(menuName, self) if helpMenuAction is not None: actionMenu = self.menuBar.insertMenu(helpMenuAction, menu) else: actionMenu = self.menuBar.addMenu(menu) menu = actionMenu.menu() menuActions = menu.actions() # get the placeholder's position to insert before it pos = 0 for pos in range(len(menuActions)): if menuActions[pos].isSeparator() and menuActions[pos].objectName().endswith("_placeholder"): menuActions[pos].setVisible(True) break if pos < len(menuActions): before = menuActions[pos] menu.insertAction(before, action) else: menu.addAction(action) actionMenu.setVisible(True) # show the menu if menuName not in self._registeredDbActions: self._registeredDbActions[menuName] = list() self._registeredDbActions[menuName].append(action) if callback is not None: action.triggered.connect(invoke_callback) return True def invokeCallback(self, callback, *params): """ Call a method passing the selected item in the database tree, the sender (usually a QAction), the plugin mainWindow and optionally additional parameters. This method takes care to override and restore the cursor, but also catches exceptions and displays the error dialog. """ with OverrideCursor(Qt.WaitCursor): try: callback(self.tree.currentItem(), self.sender(), self, *params) except BaseError as e: # catch database errors and display the error dialog DlgDbError.showError(e, self) def unregisterAction(self, action, menuName): if not hasattr(self, '_registeredDbActions'): return if menuName is None or menuName == "": self.removeAction(action) if menuName in self._registeredDbActions: if self._registeredDbActions[menuName].count(action) > 0: self._registeredDbActions[menuName].remove(action) action.deleteLater() return True for a in self.menuBar.actions(): if not a.menu() or a.menu().title() != menuName: continue menu = a.menu() menuActions = menu.actions() menu.removeAction(action) if menu.isEmpty(): # hide the menu a.setVisible(False) if menuName in self._registeredDbActions: if self._registeredDbActions[menuName].count(action) > 0: self._registeredDbActions[menuName].remove(action) # hide the placeholder if there're no other registered actions if len(self._registeredDbActions[menuName]) <= 0: for i in range(len(menuActions)): if menuActions[i].isSeparator() and menuActions[i].objectName().endswith("_placeholder"): menuActions[i].setVisible(False) break action.deleteLater() return True return False def unregisterAllActions(self): if not hasattr(self, '_registeredDbActions'): return for menuName in self._registeredDbActions: for action in list(self._registeredDbActions[menuName]): self.unregisterAction(action, menuName) del self._registeredDbActions def close_tab(self, index): widget = self.tabs.widget(index) if widget not in [self.info, self.table, self.preview]: self.tabs.removeTab(index) widget.deleteLater() def setupUi(self): self.setWindowTitle(self.tr("DB Manager")) self.setWindowIcon(QIcon(":/db_manager/icon")) self.resize(QSize(700, 500).expandedTo(self.minimumSizeHint())) # create central tab widget and add the first 3 tabs: info, table and preview self.tabs = QTabWidget() self.info = InfoViewer(self) self.tabs.addTab(self.info, self.tr("Info")) self.table = TableViewer(self) self.tabs.addTab(self.table, self.tr("Table")) self.preview = LayerPreview(self) self.tabs.addTab(self.preview, self.tr("Preview")) self.setCentralWidget(self.tabs) # display close button for all tabs but the first 3 ones, i.e. # HACK: just hide the close button where not needed (GS) self.tabs.setTabsClosable(True) self.tabs.tabCloseRequested.connect(self.close_tab) tabbar = self.tabs.tabBar() for i in range(3): btn = tabbar.tabButton(i, QTabBar.RightSide) if tabbar.tabButton(i, QTabBar.RightSide) else tabbar.tabButton(i, QTabBar.LeftSide) btn.resize(0, 0) btn.hide() # Creates layout for message bar self.layout = QGridLayout(self.info) self.layout.setContentsMargins(0, 0, 0, 0) spacerItem = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) self.layout.addItem(spacerItem, 1, 0, 1, 1) # init messageBar instance self.infoBar = QgsMessageBar(self.info) sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.infoBar.setSizePolicy(sizePolicy) self.layout.addWidget(self.infoBar, 0, 0, 1, 1) # create database tree self.dock = QDockWidget("Tree", self) self.dock.setObjectName("DB_Manager_DBView") self.dock.setFeatures(QDockWidget.DockWidgetMovable) self.tree = DBTree(self) self.dock.setWidget(self.tree) self.addDockWidget(Qt.LeftDockWidgetArea, self.dock) # create status bar self.statusBar = QStatusBar(self) self.setStatusBar(self.statusBar) # create menus self.menuBar = QMenuBar(self) self.menuDb = QMenu(self.tr("&Database"), self) self.menuBar.addMenu(self.menuDb) self.menuSchema = QMenu(self.tr("&Schema"), self) actionMenuSchema = self.menuBar.addMenu(self.menuSchema) self.menuTable = QMenu(self.tr("&Table"), self) actionMenuTable = self.menuBar.addMenu(self.menuTable) self.menuHelp = None # QMenu(self.tr("&Help"), self) # actionMenuHelp = self.menuBar.addMenu(self.menuHelp) self.setMenuBar(self.menuBar) # create toolbar self.toolBar = QToolBar("Default", self) self.toolBar.setObjectName("DB_Manager_ToolBar") self.addToolBar(self.toolBar) # create menus' actions # menu DATABASE sep = self.menuDb.addSeparator() sep.setObjectName("DB_Manager_DbMenu_placeholder") sep.setVisible(False) self.actionRefresh = self.menuDb.addAction(QIcon(":/db_manager/actions/refresh"), self.tr("&Refresh"), self.refreshActionSlot, QKeySequence("F5")) self.actionSqlWindow = self.menuDb.addAction(QIcon(":/db_manager/actions/sql_window"), self.tr("&SQL window"), self.runSqlWindow, QKeySequence("F2")) self.menuDb.addSeparator() self.actionClose = self.menuDb.addAction(QIcon(), self.tr("&Exit"), self.close, QKeySequence("CTRL+Q")) # menu SCHEMA sep = self.menuSchema.addSeparator() sep.setObjectName("DB_Manager_SchemaMenu_placeholder") sep.setVisible(False) actionMenuSchema.setVisible(False) # menu TABLE sep = self.menuTable.addSeparator() sep.setObjectName("DB_Manager_TableMenu_placeholder") sep.setVisible(False) self.actionImport = self.menuTable.addAction(QIcon(":/db_manager/actions/import"), self.tr("&Import layer/file"), self.importActionSlot) self.actionExport = self.menuTable.addAction(QIcon(":/db_manager/actions/export"), self.tr("&Export to file"), self.exportActionSlot) self.menuTable.addSeparator() #self.actionShowSystemTables = self.menuTable.addAction(self.tr("Show system tables/views"), self.showSystemTables) #self.actionShowSystemTables.setCheckable(True) #self.actionShowSystemTables.setChecked(True) actionMenuTable.setVisible(False) # add actions to the toolbar self.toolBar.addAction(self.actionRefresh) self.toolBar.addAction(self.actionSqlWindow) self.toolBar.addAction(self.actionImport) self.toolBar.addAction(self.actionExport)
class DebuggerWidget(QMainWindow): def __init__(self, parent=None): QMainWindow.__init__(self, parent) self.setWindowTitle("First Aid - Debugger") self.text_edits = {} # fully expanded path of the file -> associated SourceWidget self.toolbar = self.addToolBar("General") self.toolbar.setObjectName("ToolbarGeneral") self.tab_widget = QTabWidget() self.tab_widget.setTabsClosable(True) self.tab_widget.tabCloseRequested.connect(self.on_tab_close_requested) self.tab_widget.currentChanged.connect(self.on_pos_changed) self.setCentralWidget(self.tab_widget) _icon = lambda x: QIcon(os.path.join(os.path.dirname(__file__), "icons", x + ".svg")) self.action_load = self.toolbar.addAction(_icon("folder-outline"), "Load Python file (Ctrl+O)", self.on_load) self.action_load.setShortcut("Ctrl+O") self.action_run = self.toolbar.addAction(_icon("run"), "Run Python file (Ctrl+R)", self.on_run) self.action_run.setShortcut("Ctrl+R") self.action_bp = self.toolbar.addAction(_icon("record"), "Toggle breakpoint (F9)", self.on_toggle_breakpoint) self.action_bp.setShortcut("F9") self.toolbar.addSeparator() self.action_continue = self.toolbar.addAction(_icon("play"), "Continue (F5)", self.on_continue) self.action_continue.setShortcut("F5") self.action_step_into = self.toolbar.addAction(_icon("debug-step-into"), "Step into (F11)", self.on_step_into) self.action_step_into.setShortcut("F11") self.action_step_over = self.toolbar.addAction(_icon("debug-step-over"), "Step over (F10)", self.on_step_over) self.action_step_over.setShortcut("F10") self.action_step_out = self.toolbar.addAction(_icon("debug-step-out"), "Step out (Shift+F11)", self.on_step_out) self.action_step_out.setShortcut("Shift+F11") self.action_run_to_cursor = self.toolbar.addAction(_icon("cursor-default-outline"), "Run to cursor (Ctrl+F10)", self.on_run_to_cursor) self.action_run_to_cursor.setShortcut("Ctrl+F10") self.vars_view = VariablesView() self.frames_view = FramesView() self.dock_frames = QDockWidget("Frames", self) self.dock_frames.setObjectName("DockFrames") self.dock_frames.setWidget(self.frames_view) self.addDockWidget(Qt.BottomDockWidgetArea, self.dock_frames) self.dock_vars = QDockWidget("Variables", self) self.dock_vars.setObjectName("DockVariables") self.dock_vars.setWidget(self.vars_view) self.addDockWidget(Qt.BottomDockWidgetArea, self.dock_vars) self.resize(800, 800) self.debugger = Debugger(self) self.update_buttons() settings = QSettings() self.restoreGeometry(settings.value("/plugins/firstaid/debugger-geometry", b'')) self.restoreState(settings.value("/plugins/firstaid/debugger-windowstate", b'')) filenames = settings.value("/plugins/firstaid/debugger-files", []) if filenames is None: filenames = [] # load files from previous session for filename in filenames: self.load_file(filename) if self.tab_widget.count() > 1: self.tab_widget.setCurrentIndex(0) # start tracing self.start_tracing() def start_tracing(self): """ called from constructor or when the debugger window is opened again """ sys.settrace(self.debugger.trace_function) def closeEvent(self, event): # disable tracing sys.settrace(None) settings = QSettings() settings.setValue("/plugins/firstaid/debugger-geometry", self.saveGeometry()) settings.setValue("/plugins/firstaid/debugger-windowstate", self.saveState()) filenames = list(self.text_edits.keys()) settings.setValue("/plugins/firstaid/debugger-files", filenames) QMainWindow.closeEvent(self, event) def load_file(self, filename): filename = os.path.normpath(os.path.realpath(filename)) if filename in self.text_edits: self.switch_to_file(filename) return # already there... try: self.text_edits[filename] = SourceWidget(filename) except IOError: # TODO: display warning we failed to read the file return tab_text = os.path.basename(filename) self.tab_widget.addTab(self.text_edits[filename], tab_text) self.tab_widget.setTabToolTip(self.tab_widget.count() - 1, filename) self.tab_widget.setCurrentWidget(self.text_edits[filename]) self.text_edits[filename].cursorPositionChanged.connect(self.on_pos_changed) self.on_pos_changed() def switch_to_file(self, filename): if filename in self.text_edits: self.tab_widget.setCurrentWidget(self.text_edits[filename]) def unload_file(self, filename): for index in range(self.tab_widget.count()): if self.text_edits[filename] == self.tab_widget.widget(index): self.tab_widget.removeTab(index) del self.text_edits[filename] break def get_file_name(self, args): if isinstance(args, tuple): return args[0] elif isinstance(args, str): return args return "" def on_load(self): settings = QSettings() folder = settings.value("firstaid/lastFolder", '') args = QFileDialog.getOpenFileName(self, "Load", folder, "Python files (*.py)") filename = self.get_file_name(args) if not filename: return settings.setValue("firstaid/lastFolder", os.path.dirname(filename)) self.load_file(filename) def on_tab_close_requested(self, index): self.unload_file(self.tab_widget.widget(index).filename) def on_pos_changed(self): if not self.current_text_edit(): self.statusBar().showMessage("[no file]") return c = self.current_text_edit().textCursor() line = c.blockNumber() + 1 col = c.positionInBlock() + 1 self.statusBar().showMessage("%d:%d" % (line, col)) def on_run(self): globals = None locals = None if globals is None: import __main__ globals = __main__.__dict__ if locals is None: locals = globals execfile(self.tab_widget.currentWidget().filename, globals, locals) def current_text_edit(self): return self.tab_widget.currentWidget() def on_toggle_breakpoint(self): if self.current_text_edit(): self.current_text_edit().toggle_breakpoint() def update_buttons(self): active = self.debugger.stopped self.action_step_into.setEnabled(active) self.action_step_over.setEnabled(active) self.action_step_out.setEnabled(active) self.action_run_to_cursor.setEnabled(active) self.action_continue.setEnabled(active) def on_step_into(self): self.debugger.stepping = True self.debugger.next_step = None self.debugger.ev_loop.exit(0) def on_step_over(self): self.debugger.stepping = True self.debugger.next_step = ( 'over', self.debugger.current_frame.f_code.co_filename, self.debugger.current_frame.f_lineno) self.debugger.ev_loop.exit(0) def on_step_out(self): self.debugger.stepping = True self.debugger.next_step = ('out', frame_depth(self.debugger.current_frame)) self.debugger.ev_loop.exit(0) def on_run_to_cursor(self): self.debugger.stepping = True filename = self.tab_widget.currentWidget().filename line_no = self.tab_widget.currentWidget().textCursor().blockNumber() + 1 self.debugger.next_step = ('at', filename, line_no) self.debugger.ev_loop.exit(0) def on_continue(self): self.debugger.stepping = False self.current_text_edit().debug_line = -1 self.current_text_edit().update_highlight() self.vars_view.setVariables({}) self.frames_view.setTraceback(None) self.debugger.ev_loop.exit(0)
class DBManager(QMainWindow): def __init__(self, iface, parent=None): QMainWindow.__init__(self, parent) self.setAttribute(Qt.WA_DeleteOnClose) self.setupUi() self.iface = iface # restore the window state settings = QgsSettings() self.restoreGeometry(settings.value("/DB_Manager/mainWindow/geometry", QByteArray(), type=QByteArray)) self.restoreState(settings.value("/DB_Manager/mainWindow/windowState", QByteArray(), type=QByteArray)) self.tabs.currentChanged.connect(self.tabChanged) self.tree.selectedItemChanged.connect(self.itemChanged) self.tree.model().dataChanged.connect(self.iface.reloadConnections) self.itemChanged(None) def closeEvent(self, e): self.unregisterAllActions() # clear preview, this will delete the layer in preview tab self.preview.loadPreview(None) # save the window state settings = QgsSettings() settings.setValue("/DB_Manager/mainWindow/windowState", self.saveState()) settings.setValue("/DB_Manager/mainWindow/geometry", self.saveGeometry()) QMainWindow.closeEvent(self, e) def refreshItem(self, item=None): with OverrideCursor(Qt.WaitCursor): try: if item is None: item = self.tree.currentItem() self.tree.refreshItem(item) # refresh item children in the db tree except BaseError as e: DlgDbError.showError(e, self) def itemChanged(self, item): with OverrideCursor(Qt.WaitCursor): try: self.reloadButtons() # clear preview, this will delete the layer in preview tab self.preview.loadPreview(None) self.refreshTabs() except BaseError as e: DlgDbError.showError(e, self) def reloadButtons(self): db = self.tree.currentDatabase() if not hasattr(self, '_lastDb'): self._lastDb = db elif db == self._lastDb: return # remove old actions if self._lastDb is not None: self.unregisterAllActions() # add actions of the selected database self._lastDb = db if self._lastDb is not None: self._lastDb.registerAllActions(self) def tabChanged(self, index): with OverrideCursor(Qt.WaitCursor): try: self.refreshTabs() except BaseError as e: DlgDbError.showError(e, self) def refreshTabs(self): index = self.tabs.currentIndex() item = self.tree.currentItem() table = self.tree.currentTable() # enable/disable tabs self.tabs.setTabEnabled(self.tabs.indexOf(self.table), table is not None) self.tabs.setTabEnabled(self.tabs.indexOf(self.preview), table is not None and table.type in [table.VectorType, table.RasterType] and table.geomColumn is not None) # show the info tab if the current tab is disabled if not self.tabs.isTabEnabled(index): self.tabs.setCurrentWidget(self.info) current_tab = self.tabs.currentWidget() if current_tab == self.info: self.info.showInfo(item) elif current_tab == self.table: self.table.loadData(item) elif current_tab == self.preview: self.preview.loadPreview(item) def refreshActionSlot(self): self.info.setDirty() self.table.setDirty() self.preview.setDirty() self.refreshItem() def importActionSlot(self): db = self.tree.currentDatabase() if db is None: self.infoBar.pushMessage(self.tr("No database selected or you are not connected to it."), Qgis.Info, self.iface.messageTimeout()) return outUri = db.uri() schema = self.tree.currentSchema() if schema: outUri.setDataSource(schema.name, "", "", "") from .dlg_import_vector import DlgImportVector dlg = DlgImportVector(None, db, outUri, self) dlg.exec_() def exportActionSlot(self): table = self.tree.currentTable() if table is None: self.infoBar.pushMessage(self.tr("Select the table you want export to file."), Qgis.Info, self.iface.messageTimeout()) return inLayer = table.toMapLayer() if inLayer.type() != QgsMapLayer.VectorLayer: self.infoBar.pushMessage( self.tr("Select a vector or a tabular layer you want export."), Qgis.Warning, self.iface.messageTimeout()) return from .dlg_export_vector import DlgExportVector dlg = DlgExportVector(inLayer, table.database(), self) dlg.exec_() inLayer.deleteLater() def runSqlWindow(self): db = self.tree.currentDatabase() if db is None: self.infoBar.pushMessage(self.tr("No database selected or you are not connected to it."), Qgis.Info, self.iface.messageTimeout()) # force displaying of the message, it appears on the first tab (i.e. Info) self.tabs.setCurrentIndex(0) return from .dlg_sql_window import DlgSqlWindow query = DlgSqlWindow(self.iface, db, self) dbname = db.connection().connectionName() tabname = self.tr("Query ({0})").format(dbname) index = self.tabs.addTab(query, tabname) self.tabs.setTabIcon(index, db.connection().icon()) self.tabs.setCurrentIndex(index) query.nameChanged.connect(functools.partial(self.update_query_tab_name, index, dbname)) def runSqlLayerWindow(self, layer): from .dlg_sql_layer_window import DlgSqlLayerWindow query = DlgSqlLayerWindow(self.iface, layer, self) lname = layer.name() tabname = self.tr("Layer ({0})").format(lname) index = self.tabs.addTab(query, tabname) # self.tabs.setTabIcon(index, db.connection().icon()) self.tabs.setCurrentIndex(index) def update_query_tab_name(self, index, dbname, queryname): if not queryname: queryname = self.tr("Query") tabname = u"%s (%s)" % (queryname, dbname) self.tabs.setTabText(index, tabname) def showSystemTables(self): self.tree.showSystemTables(self.actionShowSystemTables.isChecked()) def registerAction(self, action, menuName, callback=None): """ register an action to the manager's main menu """ if not hasattr(self, '_registeredDbActions'): self._registeredDbActions = {} if callback is not None: def invoke_callback(x): return self.invokeCallback(callback) if menuName is None or menuName == "": self.addAction(action) if menuName not in self._registeredDbActions: self._registeredDbActions[menuName] = list() self._registeredDbActions[menuName].append(action) if callback is not None: action.triggered.connect(invoke_callback) return True # search for the menu actionMenu = None helpMenuAction = None for a in self.menuBar.actions(): if not a.menu() or a.menu().title() != menuName: continue if a.menu() != self.menuHelp: helpMenuAction = a actionMenu = a break # not found, add a new menu before the help menu if actionMenu is None: menu = QMenu(menuName, self) if helpMenuAction is not None: actionMenu = self.menuBar.insertMenu(helpMenuAction, menu) else: actionMenu = self.menuBar.addMenu(menu) menu = actionMenu.menu() menuActions = menu.actions() # get the placeholder's position to insert before it pos = 0 for pos in range(len(menuActions)): if menuActions[pos].isSeparator() and menuActions[pos].objectName().endswith("_placeholder"): menuActions[pos].setVisible(True) break if pos < len(menuActions): before = menuActions[pos] menu.insertAction(before, action) else: menu.addAction(action) actionMenu.setVisible(True) # show the menu if menuName not in self._registeredDbActions: self._registeredDbActions[menuName] = list() self._registeredDbActions[menuName].append(action) if callback is not None: action.triggered.connect(invoke_callback) return True def invokeCallback(self, callback, *params): """ Call a method passing the selected item in the database tree, the sender (usually a QAction), the plugin mainWindow and optionally additional parameters. This method takes care to override and restore the cursor, but also catches exceptions and displays the error dialog. """ with OverrideCursor(Qt.WaitCursor): try: callback(self.tree.currentItem(), self.sender(), self, *params) except BaseError as e: # catch database errors and display the error dialog DlgDbError.showError(e, self) def unregisterAction(self, action, menuName): if not hasattr(self, '_registeredDbActions'): return if menuName is None or menuName == "": self.removeAction(action) if menuName in self._registeredDbActions: if self._registeredDbActions[menuName].count(action) > 0: self._registeredDbActions[menuName].remove(action) action.deleteLater() return True for a in self.menuBar.actions(): if not a.menu() or a.menu().title() != menuName: continue menu = a.menu() menuActions = menu.actions() menu.removeAction(action) if menu.isEmpty(): # hide the menu a.setVisible(False) if menuName in self._registeredDbActions: if self._registeredDbActions[menuName].count(action) > 0: self._registeredDbActions[menuName].remove(action) # hide the placeholder if there're no other registered actions if len(self._registeredDbActions[menuName]) <= 0: for i in range(len(menuActions)): if menuActions[i].isSeparator() and menuActions[i].objectName().endswith("_placeholder"): menuActions[i].setVisible(False) break action.deleteLater() return True return False def unregisterAllActions(self): if not hasattr(self, '_registeredDbActions'): return for menuName in self._registeredDbActions: for action in list(self._registeredDbActions[menuName]): self.unregisterAction(action, menuName) del self._registeredDbActions def close_tab(self, index): widget = self.tabs.widget(index) if widget not in [self.info, self.table, self.preview]: self.tabs.removeTab(index) widget.deleteLater() def setupUi(self): self.setWindowTitle(self.tr("DB Manager")) self.setWindowIcon(QIcon(":/db_manager/icon")) self.resize(QSize(700, 500).expandedTo(self.minimumSizeHint())) # create central tab widget and add the first 3 tabs: info, table and preview self.tabs = QTabWidget() self.info = InfoViewer(self) self.tabs.addTab(self.info, self.tr("Info")) self.table = TableViewer(self) self.tabs.addTab(self.table, self.tr("Table")) self.preview = LayerPreview(self) self.tabs.addTab(self.preview, self.tr("Preview")) self.setCentralWidget(self.tabs) # display close button for all tabs but the first 3 ones, i.e. # HACK: just hide the close button where not needed (GS) self.tabs.setTabsClosable(True) self.tabs.tabCloseRequested.connect(self.close_tab) tabbar = self.tabs.tabBar() for i in range(3): btn = tabbar.tabButton(i, QTabBar.RightSide) if tabbar.tabButton(i, QTabBar.RightSide) else tabbar.tabButton(i, QTabBar.LeftSide) btn.resize(0, 0) btn.hide() # Creates layout for message bar self.layout = QGridLayout(self.info) self.layout.setContentsMargins(0, 0, 0, 0) spacerItem = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding) self.layout.addItem(spacerItem, 1, 0, 1, 1) # init messageBar instance self.infoBar = QgsMessageBar(self.info) sizePolicy = QSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed) self.infoBar.setSizePolicy(sizePolicy) self.layout.addWidget(self.infoBar, 0, 0, 1, 1) # create database tree self.dock = QDockWidget("Tree", self) self.dock.setObjectName("DB_Manager_DBView") self.dock.setFeatures(QDockWidget.DockWidgetMovable) self.tree = DBTree(self) self.dock.setWidget(self.tree) self.addDockWidget(Qt.LeftDockWidgetArea, self.dock) # create status bar self.statusBar = QStatusBar(self) self.setStatusBar(self.statusBar) # create menus self.menuBar = QMenuBar(self) self.menuDb = QMenu(self.tr("&Database"), self) self.menuBar.addMenu(self.menuDb) self.menuSchema = QMenu(self.tr("&Schema"), self) actionMenuSchema = self.menuBar.addMenu(self.menuSchema) self.menuTable = QMenu(self.tr("&Table"), self) actionMenuTable = self.menuBar.addMenu(self.menuTable) self.menuHelp = None # QMenu(self.tr("&Help"), self) # actionMenuHelp = self.menuBar.addMenu(self.menuHelp) self.setMenuBar(self.menuBar) # create toolbar self.toolBar = QToolBar("Default", self) self.toolBar.setObjectName("DB_Manager_ToolBar") self.addToolBar(self.toolBar) # create menus' actions # menu DATABASE sep = self.menuDb.addSeparator() sep.setObjectName("DB_Manager_DbMenu_placeholder") sep.setVisible(False) self.actionRefresh = self.menuDb.addAction(QIcon(":/db_manager/actions/refresh"), self.tr("&Refresh"), self.refreshActionSlot, QKeySequence("F5")) self.actionSqlWindow = self.menuDb.addAction(QIcon(":/db_manager/actions/sql_window"), self.tr("&SQL window"), self.runSqlWindow, QKeySequence("F2")) self.menuDb.addSeparator() self.actionClose = self.menuDb.addAction(QIcon(), self.tr("&Exit"), self.close, QKeySequence("CTRL+Q")) # menu SCHEMA sep = self.menuSchema.addSeparator() sep.setObjectName("DB_Manager_SchemaMenu_placeholder") sep.setVisible(False) actionMenuSchema.setVisible(False) # menu TABLE sep = self.menuTable.addSeparator() sep.setObjectName("DB_Manager_TableMenu_placeholder") sep.setVisible(False) self.actionImport = self.menuTable.addAction(QIcon(":/db_manager/actions/import"), self.tr("&Import layer/file"), self.importActionSlot) self.actionExport = self.menuTable.addAction(QIcon(":/db_manager/actions/export"), self.tr("&Export to file"), self.exportActionSlot) self.menuTable.addSeparator() #self.actionShowSystemTables = self.menuTable.addAction(self.tr("Show system tables/views"), self.showSystemTables) #self.actionShowSystemTables.setCheckable(True) #self.actionShowSystemTables.setChecked(True) actionMenuTable.setVisible(False) # add actions to the toolbar self.toolBar.addAction(self.actionRefresh) self.toolBar.addAction(self.actionSqlWindow) self.toolBar.addAction(self.actionImport) self.toolBar.addAction(self.actionExport)
class AttributesTable(QWidget): def __init__(self, iface): QWidget.__init__(self) self.setWindowTitle(self.tr('Search results')) self.resize(480,320) self.setMinimumSize(320,240) self.center() # Results export button self.btn_saveTab = QAction(QIcon(':/plugins/qgeric/resources/icon_save.png'), self.tr('Save this tab\'s results'), self) self.btn_saveTab.triggered.connect(lambda : self.saveAttributes(True)) self.btn_saveAllTabs = QAction(QIcon(':/plugins/qgeric/resources/icon_saveAll.png'), self.tr('Save all results'), self) self.btn_saveAllTabs.triggered.connect(lambda : self.saveAttributes(False)) self.btn_export = QAction(QIcon(':/plugins/qgeric/resources/icon_export.png'), self.tr('Export the selection as a memory layer'), self) self.btn_export.triggered.connect(self.exportLayer) self.btn_zoom = QAction(QIcon(':/plugins/qgeric/resources/icon_Zoom.png'), self.tr('Zoom to selected attributes'), self) self.btn_zoom.triggered.connect(self.zoomToFeature) self.btn_selectGeom = QAction(QIcon(':/plugins/qgeric/resources/icon_HlG.png'), self.tr('Highlight feature\'s geometry'), self) self.btn_selectGeom.triggered.connect(self.selectGeomChanged) self.btn_rename = QAction(QIcon(':/plugins/qgeric/resources/icon_Settings.png'), self.tr('Settings'), self) self.btn_rename.triggered.connect(self.renameWindow) self.tabWidget = QTabWidget() # Tab container self.tabWidget.setTabsClosable(True) self.tabWidget.currentChanged.connect(self.highlight_features) self.tabWidget.tabCloseRequested.connect(self.closeTab) self.loadingWindow = QProgressDialog() self.loadingWindow.setWindowTitle(self.tr('Loading...')) self.loadingWindow.setRange(0,100) self.loadingWindow.setAutoClose(False) self.loadingWindow.setCancelButton(None) self.canvas = iface.mapCanvas() self.canvas.extentsChanged.connect(self.highlight_features) self.highlight = [] self.highlight_rows = [] toolbar = QToolBar() toolbar.addAction(self.btn_saveTab) toolbar.addAction(self.btn_saveAllTabs) toolbar.addAction(self.btn_export) toolbar.addSeparator() toolbar.addAction(self.btn_zoom) toolbar.addSeparator() toolbar.addAction(self.btn_selectGeom) toolbar.addAction(self.btn_rename) vbox = QVBoxLayout() vbox.setContentsMargins(0,0,0,0) vbox.addWidget(toolbar) vbox.addWidget(self.tabWidget) self.setLayout(vbox) self.mb = iface.messageBar() self.selectGeom = False # False for point, True for geometry def renameWindow(self): title, ok = QInputDialog.getText(self, self.tr('Rename window'), self.tr('Enter a new title:')) if ok: self.setWindowTitle(title) def closeTab(self, index): self.tabWidget.widget(index).deleteLater() self.tabWidget.removeTab(index) def selectGeomChanged(self): if self.selectGeom: self.selectGeom = False self.btn_selectGeom.setText(self.tr('Highlight feature\'s geometry')) self.btn_selectGeom.setIcon(QIcon(':/plugins/qgeric/resources/icon_HlG.png')) else: self.selectGeom = True self.btn_selectGeom.setText(self.tr('Highlight feature\'s centroid')) self.btn_selectGeom.setIcon(QIcon(':/plugins/qgeric/resources/icon_HlC.png')) self.highlight_features() def exportLayer(self): if self.tabWidget.count() != 0: index = self.tabWidget.currentIndex() table = self.tabWidget.widget(index).findChildren(QTableWidget)[0] items = table.selectedItems() if len(items) > 0: type = '' if items[0].feature.geometry().type() == QgsWkbTypes.PointGeometry: type = 'Point' elif items[0].feature.geometry().type() == QgsWkbTypes.LineGeometry: type = 'LineString' else: type = 'Polygon' features = [] for item in items: if item.feature not in features: features.append(item.feature) name = '' ok = True while not name.strip() and ok == True: name, ok = QInputDialog.getText(self, self.tr('Layer name'), self.tr('Give a name to the layer:')) if ok: layer = QgsVectorLayer(type+"?crs="+table.crs.authid(),name,"memory") layer.startEditing() layer.dataProvider().addAttributes(features[0].fields().toList()) layer.dataProvider().addFeatures(features) layer.commitChanges() QgsProject.instance().addMapLayer(layer) else: self.mb.pushWarning(self.tr('Warning'), self.tr('There is no selected feature !')) def highlight_features(self): for item in self.highlight: self.canvas.scene().removeItem(item) del self.highlight[:] del self.highlight_rows[:] index = self.tabWidget.currentIndex() tab = self.tabWidget.widget(index) if self.tabWidget.count() != 0: table = self.tabWidget.widget(index).findChildren(QTableWidget)[0] nb = 0 area = 0 length = 0 items = table.selectedItems() for item in items: if item.row() not in self.highlight_rows: if self.selectGeom: highlight = QgsHighlight(self.canvas, item.feature.geometry(), self.tabWidget.widget(index).layer) else: highlight = QgsHighlight(self.canvas, item.feature.geometry().centroid(), self.tabWidget.widget(index).layer) highlight.setColor(QColor(255,0,0)) self.highlight.append(highlight) self.highlight_rows.append(item.row()) g = QgsGeometry(item.feature.geometry()) g.transform(QgsCoordinateTransform(tab.layer.crs(), QgsCoordinateReferenceSystem(2154), QgsProject.instance())) # geometry reprojection to get meters nb += 1 area += g.area() length += g.length() if tab.layer.geometryType()==QgsWkbTypes.PolygonGeometry: tab.sb.showMessage(self.tr('Selected features')+': '+str(nb)+' '+self.tr('Area')+': '+"%.2f"%area+' m'+u'²') elif tab.layer.geometryType()==QgsWkbTypes.LineGeometry: tab.sb.showMessage(self.tr('Selected features')+': '+str(nb)+' '+self.tr('Length')+': '+"%.2f"%length+' m') else: tab.sb.showMessage(self.tr('Selected features')+': '+str(nb)) def tr(self, message): return QCoreApplication.translate('Qgeric', message) def zoomToFeature(self): index = self.tabWidget.currentIndex() table = self.tabWidget.widget(index).findChildren(QTableWidget)[0] items = table.selectedItems() feat_id = [] for item in items: feat_id.append(item.feature.id()) if len(feat_id) >= 1: if len(feat_id) == 1: self.canvas.setExtent(items[0].feature.geometry().buffer(5, 0).boundingBox()) # in case of a single point, it will still zoom to it else: self.canvas.zoomToFeatureIds(self.tabWidget.widget(self.tabWidget.currentIndex()).layer, feat_id) self.canvas.refresh() # Add a new tab def addLayer(self, layer, headers, types, features): tab = QWidget() tab.layer = layer p1_vertical = QVBoxLayout(tab) p1_vertical.setContentsMargins(0,0,0,0) table = QTableWidget() table.itemSelectionChanged.connect(self.highlight_features) table.title = layer.name() table.crs = layer.crs() table.setColumnCount(len(headers)) if len(features) > 0: table.setRowCount(len(features)) nbrow = len(features) self.loadingWindow.show() self.loadingWindow.setLabelText(table.title) self.loadingWindow.activateWindow() self.loadingWindow.showNormal() # Table population m = 0 for feature in features: n = 0 for cell in feature.attributes(): item = QTableWidgetItem() item.setData(Qt.DisplayRole, cell) item.setFlags(item.flags() ^ Qt.ItemIsEditable) item.feature = feature table.setItem(m, n, item) n += 1 m += 1 self.loadingWindow.setValue(int((float(m)/nbrow)*100)) QApplication.processEvents() else: table.setRowCount(0) table.setHorizontalHeaderLabels(headers) table.horizontalHeader().setSectionsMovable(True) table.types = types table.filter_op = [] table.filters = [] for i in range(0, len(headers)): table.filters.append('') table.filter_op.append(0) header = table.horizontalHeader() header.setContextMenuPolicy(Qt.CustomContextMenu) header.customContextMenuRequested.connect(partial(self.filterMenu, table)) table.setSortingEnabled(True) p1_vertical.addWidget(table) # Status bar to display informations (ie: area) tab.sb = QStatusBar() p1_vertical.addWidget(tab.sb) title = table.title # We reduce the title's length to 20 characters if len(title)>20: title = title[:20]+'...' # We add the number of elements to the tab's title. title += ' ('+str(len(features))+')' self.tabWidget.addTab(tab, title) # Add the tab to the conatiner self.tabWidget.setTabToolTip(self.tabWidget.indexOf(tab), table.title) # Display a tooltip with the layer's full name def filterMenu(self, table, pos): index = table.columnAt(pos.x()) menu = QMenu() filter_operation = QComboBox() if table.types[index] in [10]: filter_operation.addItems([self.tr('Contains'),self.tr('Equals')]) else: filter_operation.addItems(['=','>','<']) filter_operation.setCurrentIndex(table.filter_op[index]) action_filter_operation = QWidgetAction(self) action_filter_operation.setDefaultWidget(filter_operation) if table.types[index] in [14]: if not isinstance(table.filters[index], QDate): filter_value = QDateEdit() else: filter_value = QDateEdit(table.filters[index]) elif table.types[index] in [15]: if not isinstance(table.filters[index], QTime): filter_value = QTimeEdit() else: filter_value = QTimeEdit(table.filters[index]) elif table.types[index] in [16]: if not isinstance(table.filters[index], QDateTime): filter_value = QDateTimeEdit() else: filter_value = QDateTimeEdit(table.filters[index]) else: filter_value = QLineEdit(table.filters[index]) action_filter_value = QWidgetAction(self) action_filter_value.setDefaultWidget(filter_value) menu.addAction(action_filter_operation) menu.addAction(action_filter_value) action_filter_apply = QAction(self.tr('Apply'), self) action_filter_apply.triggered.connect(partial(self.applyFilter, table, index, filter_value, filter_operation)) action_filter_cancel = QAction(self.tr('Cancel'), self) action_filter_cancel.triggered.connect(partial(self.applyFilter, table, index, None, filter_operation)) menu.addAction(action_filter_apply) menu.addAction(action_filter_cancel) menu.exec_(QtGui.QCursor.pos()) def applyFilter(self, table, index, filter_value, filter_operation): if filter_value == None: table.filters[index] = None else: if isinstance(filter_value, QDateEdit): table.filters[index] = filter_value.date() elif isinstance(filter_value, QTimeEdit): table.filters[index] = filter_value.time() elif isinstance(filter_value, QDateTimeEdit): table.filters[index] = filter_value.dateTime() else: table.filters[index] = filter_value.text() table.filter_op[index] = filter_operation.currentIndex() nb_elts = 0 for i in range(0, table.rowCount()): table.setRowHidden(i, False) nb_elts += 1 hidden_rows = [] for nb_col in range(0, table.columnCount()): filtered = False header = table.horizontalHeaderItem(nb_col).text() valid = False if table.filters[nb_col] is not None: if type(table.filters[nb_col]) in [QDate, QTime, QDateTime]: valid = True else: if table.filters[nb_col].strip(): valid = True if valid: filtered = True items = None if table.types[nb_col] in [10]:# If it's a string filter_type = None if table.filter_op[nb_col] == 0: # Contain filter_type = Qt.MatchContains if table.filter_op[nb_col] == 1: # Equal filter_type = Qt.MatchFixedString items = table.findItems(table.filters[nb_col], filter_type) elif table.types[nb_col] in [14, 15, 16]: # If it's a date/time items = [] for nb_row in range(0, table.rowCount()): item = table.item(nb_row, nb_col) if table.filter_op[nb_col] == 0: # = if item.data(QTableWidgetItem.Type) == table.filters[nb_col]: items.append(item) if table.filter_op[nb_col] == 1: # > if item.data(QTableWidgetItem.Type) > table.filters[nb_col]: items.append(item) if table.filter_op[nb_col] == 2: # < if item.data(QTableWidgetItem.Type) < table.filters[nb_col]: items.append(item) else: # If it's a number items = [] for nb_row in range(0, table.rowCount()): item = table.item(nb_row, nb_col) if item.text().strip(): if table.filter_op[nb_col] == 0: # = if float(item.text()) == float(table.filters[nb_col]): items.append(item) if table.filter_op[nb_col] == 1: # > if float(item.text()) > float(table.filters[nb_col]): items.append(item) if table.filter_op[nb_col] == 2: # < if float(item.text()) < float(table.filters[nb_col]): items.append(item) rows = [] for item in items: if item.column() == nb_col: rows.append(item.row()) for i in range(0, table.rowCount()): if i not in rows: if i not in hidden_rows: nb_elts -= 1 table.setRowHidden(i, True) hidden_rows.append(i) if filtered: if header[len(header)-1] != '*': table.setHorizontalHeaderItem(nb_col, QTableWidgetItem(header+'*')) else: if header[len(header)-1] == '*': header = header[:-1] table.setHorizontalHeaderItem(nb_col, QTableWidgetItem(header)) title = self.tabWidget.tabText(self.tabWidget.currentIndex()) for i in reversed(range(len(title))): if title[i] == ' ': break title = title[:-1] title += '('+str(nb_elts)+')' self.tabWidget.setTabText(self.tabWidget.currentIndex(), title) # Save tables in OpenDocument format # Use odswriter library def saveAttributes(self, active): file = QFileDialog.getSaveFileName(self, self.tr('Save in...'),'', self.tr('OpenDocument Spreadsheet (*.ods)')) if file[0]: try: with ods.writer(open(file[0],"wb")) as odsfile: tabs = None if active: tabs = self.tabWidget.currentWidget().findChildren(QTableWidget) else: tabs = self.tabWidget.findChildren(QTableWidget) for table in reversed(tabs): sheet = odsfile.new_sheet(table.title[:20]+'...') # For each tab in the container, a new sheet is created sheet.writerow([table.title]) # As the tab's title's lenght is limited, the full name of the layer is written in the first row nb_row = table.rowCount() nb_col = table.columnCount() # Fetching and writing of the table's header header = [] for i in range(0,nb_col): header.append(table.horizontalHeaderItem(i).text()) sheet.writerow(header) # Fetching and writing of the table's items for i in range(0,nb_row): row = [] for j in range(0,nb_col): row.append(table.item(i,j).text()) if not table.isRowHidden(i): sheet.writerow(row) return True except IOError: QMessageBox.critical(self, self.tr('Error'), self.tr('The file can\'t be written.')+'\n'+self.tr('Maybe you don\'t have the rights or are trying to overwrite an opened file.')) return False def center(self): screen = QDesktopWidget().screenGeometry() size = self.geometry() self.move((screen.width()-size.width())/2, (screen.height()-size.height())/2) def clear(self): self.tabWidget.clear() for table in self.tabWidget.findChildren(QTableWidget): table.setParent(None) def closeEvent(self, e): result = QMessageBox.question(self, self.tr("Saving ?"), self.tr("Would you like to save results before exit ?"), buttons = QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel) if result == QMessageBox.Yes: if self.saveAttributes(False): self.clear() e.accept() else: e.ignore() elif result == QMessageBox.No: self.clear() e.accept() else: e.ignore()