class ProcessingPlugin: def __init__(self, iface): self.iface = iface self.options_factory = None self.drop_handler = None self.item_provider = None self.locator_filter = None self.edit_features_locator_filter = None self.initialized = False self.initProcessing() def initProcessing(self): if not self.initialized: self.initialized = True Processing.initialize() def initGui(self): self.options_factory = ProcessingOptionsFactory() self.options_factory.setTitle(self.tr('Processing')) iface.registerOptionsWidgetFactory(self.options_factory) self.drop_handler = ProcessingDropHandler() iface.registerCustomDropHandler(self.drop_handler) self.item_provider = ProcessingDataItemProvider() QgsApplication.dataItemProviderRegistry().addProvider(self.item_provider) self.locator_filter = AlgorithmLocatorFilter() iface.registerLocatorFilter(self.locator_filter) # Invalidate the locator filter for in-place when active layer changes iface.currentLayerChanged.connect(lambda _: self.iface.invalidateLocatorResults()) self.edit_features_locator_filter = InPlaceAlgorithmLocatorFilter() iface.registerLocatorFilter(self.edit_features_locator_filter) self.toolbox = ProcessingToolbox() self.iface.addDockWidget(Qt.RightDockWidgetArea, self.toolbox) self.toolbox.hide() self.toolbox.visibilityChanged.connect(self.toolboxVisibilityChanged) self.resultsDock = ResultsDock() self.iface.addDockWidget(Qt.RightDockWidgetArea, self.resultsDock) self.resultsDock.hide() self.menu = QMenu(self.iface.mainWindow().menuBar()) self.menu.setObjectName('processing') self.menu.setTitle(self.tr('Pro&cessing')) self.toolboxAction = QAction(self.tr('&Toolbox'), self.iface.mainWindow()) self.toolboxAction.setCheckable(True) self.toolboxAction.setObjectName('toolboxAction') self.toolboxAction.setIcon( QgsApplication.getThemeIcon("/processingAlgorithm.svg")) self.iface.registerMainWindowAction(self.toolboxAction, QKeySequence('Ctrl+Alt+T').toString(QKeySequence.NativeText)) self.toolboxAction.toggled.connect(self.openToolbox) self.iface.attributesToolBar().insertAction(self.iface.actionOpenStatisticalSummary(), self.toolboxAction) self.menu.addAction(self.toolboxAction) self.modelerAction = QAction( QgsApplication.getThemeIcon("/processingModel.svg"), QCoreApplication.translate('ProcessingPlugin', 'Graphical &Modeler…'), self.iface.mainWindow()) self.modelerAction.setObjectName('modelerAction') self.modelerAction.triggered.connect(self.openModeler) self.iface.registerMainWindowAction(self.modelerAction, QKeySequence('Ctrl+Alt+M').toString(QKeySequence.NativeText)) self.menu.addAction(self.modelerAction) self.historyAction = QAction( QgsApplication.getThemeIcon("/mIconHistory.svg"), QCoreApplication.translate('ProcessingPlugin', '&History…'), self.iface.mainWindow()) self.historyAction.setObjectName('historyAction') self.historyAction.triggered.connect(self.openHistory) self.iface.registerMainWindowAction(self.historyAction, QKeySequence('Ctrl+Alt+H').toString(QKeySequence.NativeText)) self.menu.addAction(self.historyAction) self.toolbox.processingToolbar.addAction(self.historyAction) self.resultsAction = QAction( QgsApplication.getThemeIcon("/processingResult.svg"), self.tr('&Results Viewer'), self.iface.mainWindow()) self.resultsAction.setCheckable(True) self.iface.registerMainWindowAction(self.resultsAction, QKeySequence('Ctrl+Alt+R').toString(QKeySequence.NativeText)) self.menu.addAction(self.resultsAction) self.toolbox.processingToolbar.addAction(self.resultsAction) self.resultsDock.visibilityChanged.connect(self.resultsAction.setChecked) self.resultsAction.toggled.connect(self.resultsDock.setUserVisible) self.toolbox.processingToolbar.addSeparator() self.editInPlaceAction = QAction( QgsApplication.getThemeIcon("/mActionProcessSelected.svg"), self.tr('Edit Features In-Place'), self.iface.mainWindow()) self.editInPlaceAction.setObjectName('editInPlaceFeatures') self.editInPlaceAction.setCheckable(True) self.editInPlaceAction.toggled.connect(self.editSelected) self.menu.addAction(self.editInPlaceAction) self.toolbox.processingToolbar.addAction(self.editInPlaceAction) self.toolbox.processingToolbar.addSeparator() self.optionsAction = QAction( QgsApplication.getThemeIcon("/mActionOptions.svg"), self.tr('Options'), self.iface.mainWindow()) self.optionsAction.setObjectName('optionsAction') self.optionsAction.triggered.connect(self.openProcessingOptions) self.toolbox.processingToolbar.addAction(self.optionsAction) menuBar = self.iface.mainWindow().menuBar() menuBar.insertMenu( self.iface.firstRightStandardMenu().menuAction(), self.menu) self.menu.addSeparator() initializeMenus() createMenus() # In-place editing button state sync self.iface.currentLayerChanged.connect(self.sync_in_place_button_state) self.iface.mapCanvas().selectionChanged.connect(self.sync_in_place_button_state) self.iface.actionToggleEditing().triggered.connect(partial(self.sync_in_place_button_state, None)) self.sync_in_place_button_state() def sync_in_place_button_state(self, layer=None): """Synchronise the button state with layer state""" if layer is None: layer = self.iface.activeLayer() old_enabled_state = self.editInPlaceAction.isEnabled() new_enabled_state = layer is not None and layer.type() == QgsMapLayerType.VectorLayer self.editInPlaceAction.setEnabled(new_enabled_state) if new_enabled_state != old_enabled_state: self.toolbox.set_in_place_edit_mode(new_enabled_state and self.editInPlaceAction.isChecked()) def openProcessingOptions(self): self.iface.showOptionsDialog(self.iface.mainWindow(), currentPage='processingOptions') def unload(self): self.toolbox.setVisible(False) self.iface.removeDockWidget(self.toolbox) self.iface.attributesToolBar().removeAction(self.toolboxAction) self.resultsDock.setVisible(False) self.iface.removeDockWidget(self.resultsDock) self.toolbox.deleteLater() self.menu.deleteLater() # delete temporary output files folder = QgsProcessingUtils.tempFolder() if QDir(folder).exists(): shutil.rmtree(folder, True) # also delete temporary help files folder = tempHelpFolder() if QDir(folder).exists(): shutil.rmtree(folder, True) self.iface.unregisterMainWindowAction(self.toolboxAction) self.iface.unregisterMainWindowAction(self.modelerAction) self.iface.unregisterMainWindowAction(self.historyAction) self.iface.unregisterMainWindowAction(self.resultsAction) self.iface.unregisterOptionsWidgetFactory(self.options_factory) self.iface.deregisterLocatorFilter(self.locator_filter) self.iface.deregisterLocatorFilter(self.edit_features_locator_filter) self.iface.unregisterCustomDropHandler(self.drop_handler) QgsApplication.dataItemProviderRegistry().removeProvider(self.item_provider) removeMenus() Processing.deinitialize() def openToolbox(self, show): self.toolbox.setUserVisible(show) def toolboxVisibilityChanged(self, visible): self.toolboxAction.setChecked(visible) def openModeler(self): dlg = ModelerDialog() dlg.update_model.connect(self.updateModel) dlg.show() def updateModel(self): model_provider = QgsApplication.processingRegistry().providerById('model') model_provider.refreshAlgorithms() def openResults(self): if self.resultsDock.isVisible(): self.resultsDock.hide() else: self.resultsDock.show() def openHistory(self): dlg = HistoryDialog() dlg.exec_() def tr(self, message): return QCoreApplication.translate('ProcessingPlugin', message) def editSelected(self, enabled): self.toolbox.set_in_place_edit_mode(enabled)
class ProcessingPlugin: def __init__(self, iface): self.iface = iface self.options_factory = ProcessingOptionsFactory() self.options_factory.setTitle(self.tr('Processing')) iface.registerOptionsWidgetFactory(self.options_factory) self.drop_handler = ProcessingDropHandler() iface.registerCustomDropHandler(self.drop_handler) self.item_provider = ProcessingDataItemProvider() QgsApplication.dataItemProviderRegistry().addProvider( self.item_provider) self.locator_filter = AlgorithmLocatorFilter() iface.registerLocatorFilter(self.locator_filter) # Invalidate the locator filter for in-place when active layer changes iface.currentLayerChanged.connect( lambda _: self.iface.invalidateLocatorResults()) self.edit_features_locator_filter = InPlaceAlgorithmLocatorFilter() iface.registerLocatorFilter(self.edit_features_locator_filter) Processing.initialize() def initGui(self): self.toolbox = ProcessingToolbox() self.iface.addDockWidget(Qt.RightDockWidgetArea, self.toolbox) self.toolbox.hide() self.toolbox.visibilityChanged.connect(self.toolboxVisibilityChanged) self.resultsDock = ResultsDock() self.iface.addDockWidget(Qt.RightDockWidgetArea, self.resultsDock) self.resultsDock.hide() self.menu = QMenu(self.iface.mainWindow().menuBar()) self.menu.setObjectName('processing') self.menu.setTitle(self.tr('Pro&cessing')) self.toolboxAction = QAction(self.tr('&Toolbox'), self.iface.mainWindow()) self.toolboxAction.setCheckable(True) self.toolboxAction.setObjectName('toolboxAction') self.toolboxAction.setIcon( QgsApplication.getThemeIcon("/processingAlgorithm.svg")) self.iface.registerMainWindowAction( self.toolboxAction, QKeySequence('Ctrl+Alt+T').toString(QKeySequence.NativeText)) self.toolboxAction.toggled.connect(self.openToolbox) self.iface.attributesToolBar().insertAction( self.iface.actionOpenStatisticalSummary(), self.toolboxAction) self.menu.addAction(self.toolboxAction) self.modelerAction = QAction( QgsApplication.getThemeIcon("/processingModel.svg"), QCoreApplication.translate('ProcessingPlugin', 'Graphical &Modeler…'), self.iface.mainWindow()) self.modelerAction.setObjectName('modelerAction') self.modelerAction.triggered.connect(self.openModeler) self.iface.registerMainWindowAction( self.modelerAction, QKeySequence('Ctrl+Alt+M').toString(QKeySequence.NativeText)) self.menu.addAction(self.modelerAction) self.historyAction = QAction( QgsApplication.getThemeIcon("/mIconHistory.svg"), QCoreApplication.translate('ProcessingPlugin', '&History…'), self.iface.mainWindow()) self.historyAction.setObjectName('historyAction') self.historyAction.triggered.connect(self.openHistory) self.iface.registerMainWindowAction( self.historyAction, QKeySequence('Ctrl+Alt+H').toString(QKeySequence.NativeText)) self.menu.addAction(self.historyAction) self.toolbox.processingToolbar.addAction(self.historyAction) self.resultsAction = QAction( QgsApplication.getThemeIcon("/processingResult.svg"), self.tr('&Results Viewer'), self.iface.mainWindow()) self.resultsAction.setCheckable(True) self.iface.registerMainWindowAction( self.resultsAction, QKeySequence('Ctrl+Alt+R').toString(QKeySequence.NativeText)) self.menu.addAction(self.resultsAction) self.toolbox.processingToolbar.addAction(self.resultsAction) self.resultsDock.visibilityChanged.connect( self.resultsAction.setChecked) self.resultsAction.toggled.connect(self.resultsDock.setUserVisible) self.toolbox.processingToolbar.addSeparator() self.editInPlaceAction = QAction( QgsApplication.getThemeIcon("/mActionProcessSelected.svg"), self.tr('Edit Features In-Place'), self.iface.mainWindow()) self.editInPlaceAction.setObjectName('editInPlaceFeatures') self.editInPlaceAction.setCheckable(True) self.editInPlaceAction.toggled.connect(self.editSelected) self.menu.addAction(self.editInPlaceAction) self.toolbox.processingToolbar.addAction(self.editInPlaceAction) self.toolbox.processingToolbar.addSeparator() self.optionsAction = QAction( QgsApplication.getThemeIcon("/mActionOptions.svg"), self.tr('Options'), self.iface.mainWindow()) self.optionsAction.setObjectName('optionsAction') self.optionsAction.triggered.connect(self.openProcessingOptions) self.toolbox.processingToolbar.addAction(self.optionsAction) menuBar = self.iface.mainWindow().menuBar() menuBar.insertMenu(self.iface.firstRightStandardMenu().menuAction(), self.menu) self.menu.addSeparator() initializeMenus() createMenus() # In-place editing button state sync self.iface.currentLayerChanged.connect(self.sync_in_place_button_state) self.iface.mapCanvas().selectionChanged.connect( self.sync_in_place_button_state) self.iface.actionToggleEditing().triggered.connect( partial(self.sync_in_place_button_state, None)) self.sync_in_place_button_state() def sync_in_place_button_state(self, layer=None): """Synchronise the button state with layer state""" if layer is None: layer = self.iface.activeLayer() old_enabled_state = self.editInPlaceAction.isEnabled() new_enabled_state = layer is not None and layer.type( ) == QgsMapLayer.VectorLayer self.editInPlaceAction.setEnabled(new_enabled_state) if new_enabled_state != old_enabled_state: self.toolbox.set_in_place_edit_mode( new_enabled_state and self.editInPlaceAction.isChecked()) def openProcessingOptions(self): self.iface.showOptionsDialog(self.iface.mainWindow(), currentPage='processingOptions') def unload(self): self.toolbox.setVisible(False) self.iface.removeDockWidget(self.toolbox) self.iface.attributesToolBar().removeAction(self.toolboxAction) self.resultsDock.setVisible(False) self.iface.removeDockWidget(self.resultsDock) self.toolbox.deleteLater() self.menu.deleteLater() # delete temporary output files folder = QgsProcessingUtils.tempFolder() if QDir(folder).exists(): shutil.rmtree(folder, True) # also delete temporary help files folder = tempHelpFolder() if QDir(folder).exists(): shutil.rmtree(folder, True) self.iface.unregisterMainWindowAction(self.toolboxAction) self.iface.unregisterMainWindowAction(self.modelerAction) self.iface.unregisterMainWindowAction(self.historyAction) self.iface.unregisterMainWindowAction(self.resultsAction) self.iface.unregisterOptionsWidgetFactory(self.options_factory) self.iface.deregisterLocatorFilter(self.locator_filter) self.iface.deregisterLocatorFilter(self.edit_features_locator_filter) self.iface.unregisterCustomDropHandler(self.drop_handler) QgsApplication.dataItemProviderRegistry().removeProvider( self.item_provider) removeMenus() Processing.deinitialize() def openToolbox(self, show): self.toolbox.setUserVisible(show) def toolboxVisibilityChanged(self, visible): self.toolboxAction.setChecked(visible) def openModeler(self): dlg = ModelerDialog() dlg.update_model.connect(self.updateModel) dlg.show() def updateModel(self): model_provider = QgsApplication.processingRegistry().providerById( 'model') model_provider.refreshAlgorithms() def openResults(self): if self.resultsDock.isVisible(): self.resultsDock.hide() else: self.resultsDock.show() def openHistory(self): dlg = HistoryDialog() dlg.exec_() def tr(self, message): return QCoreApplication.translate('ProcessingPlugin', message) def editSelected(self, enabled): self.toolbox.set_in_place_edit_mode(enabled)
class ProcessingPlugin(QObject): def __init__(self, iface): super().__init__() self.iface = iface self.options_factory = None self.drop_handler = None self.item_provider = None self.locator_filter = None self.edit_features_locator_filter = None self.initialized = False self.initProcessing() def initProcessing(self): if not self.initialized: self.initialized = True Processing.initialize() def initGui(self): # port old log, ONCE ONLY! settings = QgsSettings() if not settings.value("/Processing/hasPortedOldLog", False, bool): processing_history_provider = QgsGui.historyProviderRegistry().providerById('processing') if processing_history_provider: processing_history_provider.portOldLog() settings.setValue("/Processing/hasPortedOldLog", True) self.options_factory = ProcessingOptionsFactory() self.options_factory.setTitle(self.tr('Processing')) iface.registerOptionsWidgetFactory(self.options_factory) self.drop_handler = ProcessingDropHandler() iface.registerCustomDropHandler(self.drop_handler) self.item_provider = ProcessingDataItemProvider() QgsApplication.dataItemProviderRegistry().addProvider(self.item_provider) self.locator_filter = AlgorithmLocatorFilter() iface.registerLocatorFilter(self.locator_filter) # Invalidate the locator filter for in-place when active layer changes iface.currentLayerChanged.connect(lambda _: self.iface.invalidateLocatorResults()) self.edit_features_locator_filter = InPlaceAlgorithmLocatorFilter() iface.registerLocatorFilter(self.edit_features_locator_filter) self.toolbox = ProcessingToolbox() self.iface.addDockWidget(Qt.RightDockWidgetArea, self.toolbox) self.toolbox.hide() self.toolbox.visibilityChanged.connect(self.toolboxVisibilityChanged) self.toolbox.executeWithGui.connect(self.executeAlgorithm) self.resultsDock = ResultsDock() self.iface.addDockWidget(Qt.RightDockWidgetArea, self.resultsDock) self.resultsDock.hide() self.menu = QMenu(self.iface.mainWindow().menuBar()) self.menu.setObjectName('processing') self.menu.setTitle(self.tr('Pro&cessing')) self.toolboxAction = QAction(self.tr('&Toolbox'), self.iface.mainWindow()) self.toolboxAction.setCheckable(True) self.toolboxAction.setObjectName('toolboxAction') self.toolboxAction.setIcon( QgsApplication.getThemeIcon("/processingAlgorithm.svg")) self.iface.registerMainWindowAction(self.toolboxAction, QKeySequence('Ctrl+Alt+T').toString(QKeySequence.NativeText)) self.toolboxAction.toggled.connect(self.openToolbox) self.iface.attributesToolBar().insertAction(self.iface.actionOpenStatisticalSummary(), self.toolboxAction) self.menu.addAction(self.toolboxAction) self.modelerAction = QAction( QgsApplication.getThemeIcon("/processingModel.svg"), QCoreApplication.translate('ProcessingPlugin', '&Graphical Modeler…'), self.iface.mainWindow()) self.modelerAction.setObjectName('modelerAction') self.modelerAction.triggered.connect(self.openModeler) self.iface.registerMainWindowAction(self.modelerAction, QKeySequence('Ctrl+Alt+G').toString(QKeySequence.NativeText)) self.menu.addAction(self.modelerAction) self.historyAction = QAction( QgsApplication.getThemeIcon("/mIconHistory.svg"), QCoreApplication.translate('ProcessingPlugin', '&History…'), self.iface.mainWindow()) self.historyAction.setObjectName('historyAction') self.historyAction.triggered.connect(self.openHistory) self.iface.registerMainWindowAction(self.historyAction, QKeySequence('Ctrl+Alt+H').toString(QKeySequence.NativeText)) self.menu.addAction(self.historyAction) self.toolbox.processingToolbar.addAction(self.historyAction) self.resultsAction = QAction( QgsApplication.getThemeIcon("/processingResult.svg"), self.tr('&Results Viewer'), self.iface.mainWindow()) self.resultsAction.setObjectName('resultsViewer') self.resultsAction.setCheckable(True) self.iface.registerMainWindowAction(self.resultsAction, QKeySequence('Ctrl+Alt+R').toString(QKeySequence.NativeText)) self.menu.addAction(self.resultsAction) self.toolbox.processingToolbar.addAction(self.resultsAction) self.resultsDock.visibilityChanged.connect(self.resultsAction.setChecked) self.resultsAction.toggled.connect(self.resultsDock.setUserVisible) self.toolbox.processingToolbar.addSeparator() self.editInPlaceAction = QAction( QgsApplication.getThemeIcon("/mActionProcessSelected.svg"), self.tr('Edit Features In-Place'), self.iface.mainWindow()) self.editInPlaceAction.setObjectName('editInPlaceFeatures') self.editInPlaceAction.setCheckable(True) self.editInPlaceAction.toggled.connect(self.editSelected) self.menu.addAction(self.editInPlaceAction) self.toolbox.processingToolbar.addAction(self.editInPlaceAction) self.toolbox.processingToolbar.addSeparator() self.optionsAction = QAction( QgsApplication.getThemeIcon("/mActionOptions.svg"), self.tr('Options'), self.iface.mainWindow()) self.optionsAction.setObjectName('optionsAction') self.optionsAction.triggered.connect(self.openProcessingOptions) self.toolbox.processingToolbar.addAction(self.optionsAction) menuBar = self.iface.mainWindow().menuBar() menuBar.insertMenu( self.iface.firstRightStandardMenu().menuAction(), self.menu) self.menu.addSeparator() initializeMenus() createMenus() createButtons() # In-place editing button state sync self.iface.currentLayerChanged.connect(self.sync_in_place_button_state) self.iface.mapCanvas().selectionChanged.connect(self.sync_in_place_button_state) self.iface.actionToggleEditing().triggered.connect(partial(self.sync_in_place_button_state, None)) self.sync_in_place_button_state() # Sync project models self.projectModelsMenu = None self.projectMenuAction = None self.projectMenuSeparator = None self.projectProvider = QgsApplication.instance().processingRegistry().providerById("project") self.projectProvider.algorithmsLoaded.connect(self.updateProjectModelMenu) def updateProjectModelMenu(self): """Add projects models to menu""" if self.projectMenuAction is None: self.projectModelsMenu = QMenu(self.tr("Models")) self.projectMenuAction = self.iface.projectMenu().insertMenu(self.iface.projectMenu().children()[-1], self.projectModelsMenu) self.projectMenuAction.setParent(self.projectModelsMenu) self.iface.projectMenu().insertSeparator(self.projectMenuAction) self.projectModelsMenu.clear() for model in self.projectProvider.algorithms(): modelSubMenu = self.projectModelsMenu.addMenu(model.name()) modelSubMenu.setParent(self.projectModelsMenu) action = QAction(self.tr("Execute…"), modelSubMenu) action.triggered.connect(partial(self.executeAlgorithm, model.id(), self.projectModelsMenu, self.toolbox.in_place_mode)) modelSubMenu.addAction(action) if model.flags() & QgsProcessingAlgorithm.FlagSupportsBatch: action = QAction(self.tr("Execute as Batch Process…"), modelSubMenu) modelSubMenu.addAction(action) action.triggered.connect(partial(self.executeAlgorithm, model.id(), self.projectModelsMenu, self.toolbox.in_place_mode, True)) @pyqtSlot(str, QWidget, bool, bool) def executeAlgorithm(self, alg_id, parent, in_place=False, as_batch=False): """Executes a project model with GUI interaction if needed. :param alg_id: algorithm id :type alg_id: string :param parent: parent widget :type parent: QWidget :param in_place: in place flag, defaults to False :type in_place: bool, optional :param as_batch: execute as batch flag, defaults to False :type as_batch: bool, optional """ config = {} if in_place: config['IN_PLACE'] = True alg = QgsApplication.instance().processingRegistry().createAlgorithmById(alg_id, config) if alg is not None: ok, message = alg.canExecute() if not ok: dlg = MessageDialog() dlg.setTitle(self.tr('Error executing algorithm')) dlg.setMessage( self.tr('<h3>This algorithm cannot ' 'be run :-( </h3>\n{0}').format(message)) dlg.exec_() return if as_batch: dlg = BatchAlgorithmDialog(alg, iface.mainWindow()) dlg.show() dlg.exec_() else: in_place_input_parameter_name = 'INPUT' if hasattr(alg, 'inputParameterName'): in_place_input_parameter_name = alg.inputParameterName() if in_place and not [d for d in alg.parameterDefinitions() if d.name() not in (in_place_input_parameter_name, 'OUTPUT')]: parameters = {} feedback = MessageBarProgress(algname=alg.displayName()) ok, results = execute_in_place(alg, parameters, feedback=feedback) if ok: iface.messageBar().pushSuccess('', self.tr('{algname} completed. %n feature(s) processed.', n=results['__count']).format(algname=alg.displayName())) feedback.close() # MessageBarProgress handles errors return if alg.countVisibleParameters() > 0: dlg = alg.createCustomParametersWidget(parent) if not dlg: dlg = AlgorithmDialog(alg, in_place, iface.mainWindow()) canvas = iface.mapCanvas() prevMapTool = canvas.mapTool() dlg.show() dlg.exec_() if canvas.mapTool() != prevMapTool: try: canvas.mapTool().reset() except Exception: pass canvas.setMapTool(prevMapTool) else: feedback = MessageBarProgress(algname=alg.displayName()) context = dataobjects.createContext(feedback) parameters = {} ret, results = execute(alg, parameters, context, feedback) handleAlgorithmResults(alg, context, feedback) feedback.close() def sync_in_place_button_state(self, layer=None): """Synchronise the button state with layer state""" if layer is None: layer = self.iface.activeLayer() old_enabled_state = self.editInPlaceAction.isEnabled() new_enabled_state = layer is not None and layer.type() == QgsMapLayerType.VectorLayer self.editInPlaceAction.setEnabled(new_enabled_state) if new_enabled_state != old_enabled_state: self.toolbox.set_in_place_edit_mode(new_enabled_state and self.editInPlaceAction.isChecked()) def openProcessingOptions(self): self.iface.showOptionsDialog(self.iface.mainWindow(), currentPage='processingOptions') def unload(self): self.toolbox.setVisible(False) self.iface.removeDockWidget(self.toolbox) self.iface.attributesToolBar().removeAction(self.toolboxAction) self.resultsDock.setVisible(False) self.iface.removeDockWidget(self.resultsDock) self.toolbox.deleteLater() self.menu.deleteLater() # also delete temporary help files folder = tempHelpFolder() if QDir(folder).exists(): shutil.rmtree(folder, True) self.iface.unregisterMainWindowAction(self.toolboxAction) self.iface.unregisterMainWindowAction(self.modelerAction) self.iface.unregisterMainWindowAction(self.historyAction) self.iface.unregisterMainWindowAction(self.resultsAction) self.iface.unregisterOptionsWidgetFactory(self.options_factory) self.iface.deregisterLocatorFilter(self.locator_filter) self.iface.deregisterLocatorFilter(self.edit_features_locator_filter) self.iface.unregisterCustomDropHandler(self.drop_handler) QgsApplication.dataItemProviderRegistry().removeProvider(self.item_provider) removeButtons() removeMenus() if self.projectMenuAction is not None: self.iface.projectMenu().removeAction(self.projectMenuAction) self.projectMenuAction = None if self.projectMenuSeparator is not None: self.iface.projectMenu().removeAction(self.projectMenuSeparator) self.projectMenuSeparator = None Processing.deinitialize() def openToolbox(self, show): self.toolbox.setUserVisible(show) def toolboxVisibilityChanged(self, visible): self.toolboxAction.setChecked(visible) def openModeler(self): dlg = ModelerDialog.create() dlg.update_model.connect(self.updateModel) dlg.show() def updateModel(self): model_provider = QgsApplication.processingRegistry().providerById('model') model_provider.refreshAlgorithms() def openResults(self): if self.resultsDock.isVisible(): self.resultsDock.hide() else: self.resultsDock.show() def openHistory(self): dlg = HistoryDialog() dlg.exec_() def tr(self, message, disambiguation=None, n=-1): return QCoreApplication.translate('ProcessingPlugin', message, disambiguation=disambiguation, n=n) def editSelected(self, enabled): self.toolbox.set_in_place_edit_mode(enabled)
class MessageViewer(QtWebKitWidgets.QWebView): """A simple message queue.""" static_message_count = 0 # noinspection PyOldStyleClasses def __init__(self, the_parent): _ = the_parent # NOQA needed for promoted Qt widget in designer super(MessageViewer, self).__init__() self.setWindowTitle('Message Viewer') # We use this var to keep track of the last allocated div id # in cases where we are assigning divs ids so we can scroll to them self.last_id = 0 # whether to show or not dev only options self.dev_mode = setting('developer_mode', False, expected_type=bool) if self.dev_mode: self.settings().globalSettings().setAttribute( QtWebKit.QWebSettings.DeveloperExtrasEnabled, True) # Always gets replaced when a new message is passed self.static_message = None # Always get appended until the next static message is called, # then cleared self.dynamic_messages = [] self.dynamic_messages_log = [] # self.show() self.action_show_log = QAction(self.tr('Show log'), None) self.action_show_log.setEnabled(False) # noinspection PyUnresolvedReferences self.action_show_log.triggered.connect(self.show_log) self.action_show_report = QAction(self.tr('Show report'), None) self.action_show_report.setEnabled(False) # noinspection PyUnresolvedReferences self.action_show_report.triggered.connect(self.show_report) self.action_print_to_pdf = QAction(self.tr('Save as PDF'), None) self.action_print_to_pdf.setEnabled(True) self.action_print_to_pdf.triggered.connect(self.generate_pdf) self.log_path = None self.report_path = None self._impact_path = None self._html_loaded_flag = False # noinspection PyUnresolvedReferences self.loadFinished.connect(self.html_loaded_slot) @property def impact_path(self): """Getter to impact path.""" return self._impact_path @impact_path.setter def impact_path(self, value): """Setter to impact path. :param value: The impact path. :type value: str """ self._impact_path = value if value is None: self.action_show_report.setEnabled(False) self.action_show_log.setEnabled(False) self.report_path = None self.log_path = None else: self.action_show_report.setEnabled(True) self.action_show_log.setEnabled(True) self.log_path = '%s.log.html' % self.impact_path self.report_path = '%s.report.html' % self.impact_path self.save_report_to_html() self.save_log_to_html() self.show_report() def contextMenuEvent(self, event): """Slot automatically called by Qt on right click on the WebView. :param event: the event that caused the context menu to be called. """ context_menu = QMenu(self) # add select all action_select_all = self.page().action( QtWebKitWidgets.QWebPage.SelectAll) action_select_all.setEnabled(not self.page_to_text() == '') context_menu.addAction(action_select_all) # add copy action_copy = self.page().action(QtWebKitWidgets.QWebPage.Copy) if qt_at_least('4.8.0'): action_copy.setEnabled(not self.selectedHtml() == '') else: action_copy.setEnabled(not self.selectedText() == '') context_menu.addAction(action_copy) # add show in browser action_page_to_html_file = QAction(self.tr('Open in web browser'), None) # noinspection PyUnresolvedReferences action_page_to_html_file.triggered.connect( self.open_current_in_browser) context_menu.addAction(action_page_to_html_file) # Add the PDF export menu context_menu.addAction(self.action_print_to_pdf) # add load report context_menu.addAction(self.action_show_report) # add load log context_menu.addAction(self.action_show_log) # add view source if in dev mode if self.dev_mode: action_copy = self.page().action( QtWebKitWidgets.QWebPage.InspectElement) action_copy.setEnabled(True) context_menu.addAction(action_copy) # add view to_text if in dev mode action_page_to_stdout = QAction(self.tr('log pageToText'), None) # noinspection PyUnresolvedReferences action_page_to_stdout.triggered.connect(self.page_to_stdout) context_menu.addAction(action_page_to_stdout) # show the menu context_menu.setVisible(True) context_menu.exec_(event.globalPos()) def static_message_event(self, sender, message): """Static message event handler - set message state based on event. Static message events will clear the message buffer before displaying themselves. :param sender: Unused - the object that sent the message. :type sender: Object, None :param message: A message to show in the viewer. :type message: safe.messaging.message.Message """ self.static_message_count += 1 if message == self.static_message: return # LOGGER.debug('Static message event %i' % self.static_message_count) _ = sender # NOQA self.dynamic_messages = [] self.static_message = message self.show_messages() def error_message_event(self, sender, message): """Error message event handler - set message state based on event. Error messages are treated as dynamic messages - they don't clear the message buffer. :param sender: The object that sent the message. :type sender: Object, None :param message: A message to show in the viewer. :type message: safe.messaging.Message """ # LOGGER.debug('Error message event') self.dynamic_message_event(sender, message) def dynamic_message_event(self, sender, message): """Dynamic event handler - set message state based on event. Dynamic messages don't clear the message buffer. :param sender: Unused - the object that sent the message. :type sender: Object, None :param message: A message to show in the viewer. :type message: safe.messaging.Message """ # LOGGER.debug('Dynamic message event') _ = sender # NOQA self.dynamic_messages.append(message) self.dynamic_messages_log.append(message) # Old way (works but causes full page refresh) self.show_messages() return # New way add html snippet to end of page, not currently working # self.last_id += 1 # message.element_id = str(self.last_id) # # TODO probably we should do some escaping of quotes etc in message # html = message.to_html(in_div_flag=True) # html = html.replace('\'', '\\\'') # # We could run into side effect still if messages contain single # # quotes # LOGGER.debug('HTML: %s' % html) # js = 'document.body.innerHTML += \'%s\'' % html # LOGGER.debug('JAVASCRIPT: %s' % js) # self.page().mainFrame().evaluateJavaScript(js) # self.scrollToDiv() def clear_dynamic_messages_log(self): """Clear dynamic message log.""" self.dynamic_messages_log = [] def show_messages(self): """Show all messages.""" if isinstance(self.static_message, MessageElement): # Handle sent Message instance string = html_header() if self.static_message is not None: string += self.static_message.to_html() # Keep track of the last ID we had so we can scroll to it self.last_id = 0 for message in self.dynamic_messages: if message.element_id is None: self.last_id += 1 message.element_id = str(self.last_id) html = message.to_html(in_div_flag=True) if html is not None: string += html string += html_footer() elif (isinstance(self.static_message, str)): # Handle sent text directly string = self.static_message elif self.static_message is not None: string = str(self.static_message) elif not self.static_message: # handle dynamic message # Handle sent Message instance string = html_header() # Keep track of the last ID we had so we can scroll to it self.last_id = 0 for message in self.dynamic_messages: if message.element_id is None: self.last_id += 1 message.element_id = str(self.last_id) html = message.to_html(in_div_flag=True) if html is not None: string += html string += html_footer() # Set HTML self.load_html(HTML_STR_MODE, string) def to_message(self): """Collate all message elements to a single message.""" my_message = m.Message() if self.static_message is not None: my_message.add(self.static_message) for myDynamic in self.dynamic_messages: my_message.add(myDynamic) return my_message def page_to_text(self): """Return the current page contents as plain text.""" my_message = self.to_message() return my_message.to_text() def page_to_html(self): """Return the current page contents as html.""" my_message = self.to_message() return my_message.to_html() def page_to_stdout(self): """Print to console the current page contents as plain text.""" print((self.page_to_text())) def save_report_to_html(self): """Save report in the dock to html.""" html = self.page().mainFrame().toHtml() if self.report_path is not None: html_to_file(html, self.report_path) else: msg = self.tr('report_path is not set') raise InvalidParameterError(msg) def save_log_to_html(self): """Helper to write the log out as an html file.""" html = html_header() html += ('<img src="file:///%s/img/logos/inasafe-logo-url.png" ' 'title="InaSAFE Logo" alt="InaSAFE Logo" />' % resources_path()) html += ('<h5 class="info"><i class="icon-info-sign icon-white"></i> ' '%s</h5>' % self.tr('Analysis log')) for item in self.dynamic_messages_log: html += "%s\n" % item.to_html() html += html_footer() if self.log_path is not None: html_to_file(html, self.log_path) else: msg = self.tr('log_path is not set') raise InvalidParameterError(msg) def show_report(self): """Show report.""" self.action_show_report.setEnabled(False) self.action_show_log.setEnabled(True) self.load_html_file(self.report_path) def show_log(self): """Show log.""" self.action_show_report.setEnabled(True) self.action_show_log.setEnabled(False) self.load_html_file(self.log_path) def open_current_in_browser(self): """Open current selected impact report in browser.""" if self.impact_path is None: html = self.page().mainFrame().toHtml() html_to_file(html, open_browser=True) else: if self.action_show_report.isEnabled(): # if show report is enable, we are looking at a log open_in_browser(self.log_path) else: open_in_browser(self.report_path) def generate_pdf(self): """Generate a PDF from the displayed content.""" printer = QtGui.QPrinter(QtGui.QPrinter.HighResolution) printer.setPageSize(QtGui.QPrinter.A4) printer.setColorMode(QtGui.QPrinter.Color) printer.setOutputFormat(QtGui.QPrinter.PdfFormat) report_path = unique_filename(suffix='.pdf') printer.setOutputFileName(report_path) self.print_(printer) url = QtCore.QUrl.fromLocalFile(report_path) # noinspection PyTypeChecker,PyCallByClass,PyArgumentList QtGui.QDesktopServices.openUrl(url) def load_html_file(self, file_path): """Load html file into webkit. :param file_path: The path of the html file :type file_path: str """ self.load_html(HTML_FILE_MODE, file_path) def load_html(self, mode, html): """Load HTML to this class with the mode specified. There are two modes that can be used: * HTML_FILE_MODE: Directly from a local HTML file. * HTML_STR_MODE: From a valid HTML string. :param mode: The mode. :type mode: int :param html: The html that will be loaded. If the mode is a file, then it should be a path to the htm lfile. If the mode is a string, then it should be a valid HTML string. :type html: str """ # noinspection PyCallByClass,PyTypeChecker,PyArgumentList self._html_loaded_flag = False if mode == HTML_FILE_MODE: self.setUrl(QtCore.QUrl.fromLocalFile(html)) elif mode == HTML_STR_MODE: self.setHtml(html) else: raise InvalidParameterError('The mode is not supported.') counter = 0 sleep_period = 0.1 # sec timeout = 20 # it's generous enough! while not self._html_loaded_flag and counter < timeout: # Block until the event loop is done counter += sleep_period time.sleep(sleep_period) # noinspection PyArgumentList QgsApplication.processEvents() def html_loaded_slot(self, ok): """Slot called when the page is loaded. :param ok: Flag indicating if the html is loaded. :type ok: bool """ self._html_loaded_flag = ok
class VertexComparePlugin(QObject): """QGIS Plugin Implementation.""" def __init__(self, iface: QgisInterface): """Constructor. :param iface: An interface instance that will be passed to this class which provides the hook by which you can manipulate the QGIS application at run time. :type iface: QgsInterface """ super().__init__() # Save reference to the QGIS interface self.iface = iface # initialize plugin directory self.plugin_dir = os.path.dirname(__file__) # initialize locale locale = QgsApplication.locale() locale_path = os.path.join(self.plugin_dir, 'i18n', '{}.qm'.format(locale)) if os.path.exists(locale_path): self.translator = QTranslator() self.translator.load(locale_path) QCoreApplication.installTranslator(self.translator) self.toolbar = None self.layer_combo = None self.actions = [] self.dock = None self.vertex_highlighter = VertexHighlighterManager() self.selection_handler = SelectionHandler(self) self.show_vertices_action = None self.show_topology_action = None self.show_dock_action = None @staticmethod def tr(message): """Get the translation for a string using Qt translation API. We implement this ourselves since we do not inherit QObject. :param message: String for translation. :type message: str, QString :returns: Translated version of message. :rtype: QString """ # noinspection PyTypeChecker,PyArgumentList,PyCallByClass return QCoreApplication.translate('VertexCompare', message) def initProcessing(self): """Create the Processing provider""" def initGui(self): """Creates application GUI widgets""" self.initProcessing() self.dock = VertexDockWidget(self.iface.mapCanvas()) self.iface.addDockWidget(Qt.RightDockWidgetArea, self.dock) self.dock.setUserVisible(False) self.toolbar = QToolBar(self.tr('Vertex Compare Toolbar')) self.toolbar.setObjectName('vertexCompareToolbar') self.iface.addToolBar(self.toolbar) self.layer_combo = QgsMapLayerComboBox() self.layer_combo.setAllowEmptyLayer(True, self.tr('Disabled')) self.layer_combo.setFilters(QgsMapLayerProxyModel.PolygonLayer | QgsMapLayerProxyModel.LineLayer) self.layer_combo.setMinimumWidth( QFontMetrics(self.layer_combo.font()).width('x') * 40) self.layer_combo.setCurrentIndex(0) self.layer_combo.layerChanged.connect(self._set_layer) self.toolbar.addWidget(self.layer_combo) self.show_vertices_action = QAction(self.tr("Show Vertex Numbers"), self) self.show_vertices_action.setIcon( GuiUtils.get_icon('show_vertex_numbers.svg')) self.show_vertices_action.setCheckable(True) self.show_vertices_action.setEnabled(False) self.actions.append(self.show_vertices_action) self.toolbar.addAction(self.show_vertices_action) self.show_vertices_action.toggled.connect( self.vertex_highlighter.set_visible) self.show_topology_action = QAction(self.tr("Compare Vertices"), self) self.show_topology_action.setIcon(GuiUtils.get_icon('topology.svg')) self.show_topology_action.setCheckable(True) self.actions.append(self.show_topology_action) self.toolbar.addAction(self.show_topology_action) self.show_topology_action.toggled.connect( self.vertex_highlighter.set_topological) self.show_dock_action = QAction(self.tr('Show Vertices'), parent=self.toolbar) self.show_dock_action.setIcon(GuiUtils.get_icon('vertex_table.svg')) self.toolbar.addAction(self.show_dock_action) self.actions.append(self.show_dock_action) self.dock.setToggleVisibilityAction(self.show_dock_action) self.selection_handler.selection_changed.connect( self._selection_changed) self.dock.label_filter_changed.connect(self.vertex_highlighter.redraw) self.dock.vertex_symbol_changed.connect(self.vertex_highlighter.redraw) self.dock.vertex_text_format_changed.connect( self.vertex_highlighter.redraw) self.dock.selected_vertex_changed.connect( self.vertex_highlighter.set_selected_vertex) def unload(self): """Removes the plugin menu item and icon from QGIS GUI.""" for a in self.actions: a.deleteLater() self.actions = [] if self.toolbar is not None: self.toolbar.deleteLater() self.toolbar = None if self.dock is not None: self.dock.deleteLater() self.dock = None def _set_layer(self, layer: Optional[QgsVectorLayer]): """ Triggered when the selected layer is changed """ self.selection_handler.set_layer(layer) self.vertex_highlighter.set_layer(layer) self.show_vertices_action.setEnabled(layer is not None) if not self.show_vertices_action.isEnabled(): self.show_vertices_action.setChecked(False) else: self.show_vertices_action.setChecked(True) self.dock.set_selection( layer, layer.selectedFeatureIds() if layer is not None else []) def _selection_changed(self, layer: Optional[QgsVectorLayer], selection: List[int]): """ Triggered when the watched layer's selection is changed """ self.dock.set_selection(layer, selection)