""" *************************************************************************** ConfigDialog.py --------------------- Date : August 2012 Copyright : (C) 2012 by Victor Olaya Email : volayaf at gmail dot com *************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * *************************************************************************** """ __author__ = "Victor Olaya" __date__ = "August 2012" __copyright__ = "(C) 2012, Victor Olaya" import os import warnings from qgis.PyQt import uic from qgis.PyQt.QtCore import Qt, QEvent from qgis.PyQt.QtWidgets import ( QFileDialog, QStyle, QMessageBox, QStyledItemDelegate, QLineEdit, QWidget, QToolButton, QHBoxLayout, QComboBox, QPushButton, QApplication, ) from qgis.PyQt.QtGui import QIcon, QStandardItemModel, QStandardItem, QCursor from qgis.gui import ( QgsDoubleSpinBox, QgsSpinBox, QgsOptionsPageWidget, QgsOptionsDialogHighlightWidget, ) from qgis.core import NULL, QgsApplication, QgsSettings from qgis.utils import OverrideCursor from processing.core.ProcessingConfig import ProcessingConfig, settingsWatcher, Setting from processing.core.Processing import Processing from processing.gui.DirectorySelectorDialog import DirectorySelectorDialog from processing.gui.menus import defaultMenuEntries, menusSettingsGroup pluginPath = os.path.split(os.path.dirname(__file__))[0] with warnings.catch_warnings(): warnings.filterwarnings("ignore", category=DeprecationWarning) WIDGET, BASE = uic.loadUiType(os.path.join(pluginPath, "ui", "DlgConfig.ui")) class ConfigOptionsPage(QgsOptionsPageWidget): def __init__(self, parent): super().__init__(parent) self.config_widget = ConfigDialog(False) layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.setMargin(0) self.setLayout(layout) layout.addWidget(self.config_widget) self.setObjectName("processingOptions") self.highlightWidget = ProcessingTreeHighlight(self.config_widget) self.registerHighlightWidget(self.highlightWidget) def apply(self): self.config_widget.accept() def helpKey(self): return "processing/index.html" class ProcessingTreeHighlight(QgsOptionsDialogHighlightWidget): def __init__(self, config_dialog): super().__init__(config_dialog.tree) self.config_dialog = config_dialog def highlightText(self, text): return self.config_dialog.textChanged(text) def searchText(self, text): return self.config_dialog.textChanged(text) def reset(self): self.config_dialog.textChanged("") class ConfigDialog(BASE, WIDGET): def __init__(self, showSearch=True): super().__init__(None) self.setupUi(self) self.groupIcon = QgsApplication.getThemeIcon("mIconFolder.svg") self.model = QStandardItemModel() self.tree.setModel(self.model) self.delegate = SettingDelegate() self.tree.setItemDelegateForColumn(1, self.delegate) if showSearch: if hasattr(self.searchBox, "setPlaceholderText"): self.searchBox.setPlaceholderText( QApplication.translate("ConfigDialog", "Search…") ) self.searchBox.textChanged.connect(self.textChanged) else: self.searchBox.hide() self.fillTree() self.saveMenus = False self.tree.expanded.connect(self.itemExpanded) self.auto_adjust_columns = True def textChanged(self, text=None): if text is not None: text = str(text.lower()) else: text = str(self.searchBox.text().lower()) found = self._filterItem( self.model.invisibleRootItem(), text, False if text else True ) self.auto_adjust_columns = False if text: self.tree.expandAll() else: self.tree.collapseAll() self.adjustColumns() self.auto_adjust_columns = True if text: return found else: self.tree.collapseAll() return False def _filterItem(self, item, text, forceShow=False): if item.hasChildren(): show = ( forceShow or isinstance(item, QStandardItem) and bool(text) and (text in item.text().lower()) ) for i in range(item.rowCount()): child = item.child(i) show = self._filterItem(child, text, forceShow) or show self.tree.setRowHidden(item.row(), item.index().parent(), not show) return show elif isinstance(item, QStandardItem): show = forceShow or bool(text) and (text in item.text().lower()) self.tree.setRowHidden(item.row(), item.index().parent(), not show) return show def fillTree(self): self.fillTreeUsingProviders() def fillTreeUsingProviders(self): self.items = {} self.model.clear() self.model.setHorizontalHeaderLabels([self.tr("Setting"), self.tr("Value")]) settings = ProcessingConfig.getSettings() rootItem = self.model.invisibleRootItem() """ Filter 'General', 'Models' and 'Scripts' items """ priorityKeys = [self.tr("General"), self.tr("Models"), self.tr("Scripts")] for group in priorityKeys: groupItem = QStandardItem(group) icon = ProcessingConfig.getGroupIcon(group) groupItem.setIcon(icon) groupItem.setEditable(False) emptyItem = QStandardItem() emptyItem.setEditable(False) rootItem.insertRow(0, [groupItem, emptyItem]) if group not in settings: continue # add menu item only if it has any search matches for setting in settings[group]: if setting.hidden or setting.name.startswith("MENU_"): continue labelItem = QStandardItem(setting.description) labelItem.setIcon(icon) labelItem.setEditable(False) self.items[setting] = SettingItem(setting) groupItem.insertRow(0, [labelItem, self.items[setting]]) """ Filter 'Providers' items """ providersItem = QStandardItem(self.tr("Providers")) icon = QgsApplication.getThemeIcon("/processingAlgorithm.svg") providersItem.setIcon(icon) providersItem.setEditable(False) emptyItem = QStandardItem() emptyItem.setEditable(False) rootItem.insertRow(0, [providersItem, emptyItem]) for group in list(settings.keys()): if group in priorityKeys or group == menusSettingsGroup: continue groupItem = QStandardItem(group) icon = ProcessingConfig.getGroupIcon(group) groupItem.setIcon(icon) groupItem.setEditable(False) for setting in settings[group]: if setting.hidden: continue labelItem = QStandardItem(setting.description) labelItem.setIcon(icon) labelItem.setEditable(False) self.items[setting] = SettingItem(setting) groupItem.insertRow(0, [labelItem, self.items[setting]]) emptyItem = QStandardItem() emptyItem.setEditable(False) providersItem.appendRow([groupItem, emptyItem]) """ Filter 'Menus' items """ self.menusItem = QStandardItem(self.tr("Menus")) icon = QIcon(os.path.join(pluginPath, "images", "menu.png")) self.menusItem.setIcon(icon) self.menusItem.setEditable(False) emptyItem = QStandardItem() emptyItem.setEditable(False) rootItem.insertRow(0, [self.menusItem, emptyItem]) button = QPushButton(self.tr("Reset to defaults")) button.clicked.connect(self.resetMenusToDefaults) layout = QHBoxLayout() layout.setContentsMargins(0, 0, 0, 0) layout.addWidget(button) layout.addStretch() widget = QWidget() widget.setLayout(layout) self.tree.setIndexWidget(emptyItem.index(), widget) for provider in QgsApplication.processingRegistry().providers(): providerDescription = provider.name() groupItem = QStandardItem(providerDescription) icon = provider.icon() groupItem.setIcon(icon) groupItem.setEditable(False) for alg in provider.algorithms(): algItem = QStandardItem(alg.displayName()) algItem.setIcon(icon) algItem.setEditable(False) try: settingMenu = ProcessingConfig.settings["MENU_" + alg.id()] settingButton = ProcessingConfig.settings["BUTTON_" + alg.id()] settingIcon = ProcessingConfig.settings["ICON_" + alg.id()] except: continue self.items[settingMenu] = SettingItem(settingMenu) self.items[settingButton] = SettingItem(settingButton) self.items[settingIcon] = SettingItem(settingIcon) menuLabelItem = QStandardItem("Menu path") menuLabelItem.setEditable(False) buttonLabelItem = QStandardItem("Add button in toolbar") buttonLabelItem.setEditable(False) iconLabelItem = QStandardItem("Icon") iconLabelItem.setEditable(False) emptyItem = QStandardItem() emptyItem.setEditable(False) algItem.insertRow(0, [menuLabelItem, self.items[settingMenu]]) algItem.insertRow(0, [buttonLabelItem, self.items[settingButton]]) algItem.insertRow(0, [iconLabelItem, self.items[settingIcon]]) groupItem.insertRow(0, [algItem, emptyItem]) emptyItem = QStandardItem() emptyItem.setEditable(False) self.menusItem.appendRow([groupItem, emptyItem]) self.tree.sortByColumn(0, Qt.SortOrder.AscendingOrder) self.adjustColumns() def resetMenusToDefaults(self): for provider in QgsApplication.processingRegistry().providers(): for alg in provider.algorithms(): d = defaultMenuEntries.get(alg.id(), "") setting = ProcessingConfig.settings["MENU_" + alg.id()] item = self.items[setting] item.setData(d, Qt.ItemDataRole.EditRole) self.saveMenus = True def accept(self): qsettings = QgsSettings() for setting in list(self.items.keys()): if setting.group != menusSettingsGroup or self.saveMenus: if isinstance(setting.value, bool): setting.setValue( self.items[setting].checkState() == Qt.CheckState.Checked ) else: try: setting.setValue(str(self.items[setting].text())) except ValueError as e: QMessageBox.warning( self, self.tr("Wrong value"), self.tr('Wrong value for parameter "{0}":\n\n{1}').format( setting.description, str(e) ), ) return setting.save(qsettings) with OverrideCursor(Qt.CursorShape.WaitCursor): for p in QgsApplication.processingRegistry().providers(): p.refreshAlgorithms() settingsWatcher.settingsChanged.emit() def itemExpanded(self, idx): if idx == self.menusItem.index(): self.saveMenus = True if self.auto_adjust_columns: self.adjustColumns() def adjustColumns(self): self.tree.resizeColumnToContents(0) self.tree.resizeColumnToContents(1) class SettingItem(QStandardItem): def __init__(self, setting): QStandardItem.__init__(self) self.setting = setting self.setData(setting, Qt.ItemDataRole.UserRole) if isinstance(setting.value, bool): self.setCheckable(True) self.setEditable(False) if setting.value: self.setCheckState(Qt.CheckState.Checked) else: self.setCheckState(Qt.CheckState.Unchecked) else: self.setData(setting.value, Qt.ItemDataRole.EditRole) class SettingDelegate(QStyledItemDelegate): def __init__(self, parent=None): QStyledItemDelegate.__init__(self, parent) def createEditor(self, parent, options, index): setting = index.model().data(index, Qt.ItemDataRole.UserRole) if setting.valuetype == Setting.FOLDER: return FileDirectorySelector(parent, placeholder=setting.placeholder) elif setting.valuetype == Setting.FILE: return FileDirectorySelector(parent, True, setting.placeholder) elif setting.valuetype == Setting.SELECTION: combo = QComboBox(parent) combo.addItems(setting.options) return combo elif setting.valuetype == Setting.SELECTION_STORE_STRING: combo = QComboBox(parent) for option in setting.options: combo.addItem(option, option) return combo elif setting.valuetype == Setting.MULTIPLE_FOLDERS: return MultipleDirectorySelector(parent, setting.placeholder) else: value = self.convertValue( index.model().data(index, Qt.ItemDataRole.EditRole) ) if isinstance(value, int): spnBox = QgsSpinBox(parent) spnBox.setRange(-999999999, 999999999) return spnBox elif isinstance(value, float): spnBox = QgsDoubleSpinBox(parent) spnBox.setRange(-999999999.999999, 999999999.999999) spnBox.setDecimals(6) return spnBox elif isinstance(value, str): lineEdit = QLineEdit(parent) lineEdit.setPlaceholderText(setting.placeholder) return lineEdit def setEditorData(self, editor, index): value = self.convertValue(index.model().data(index, Qt.ItemDataRole.EditRole)) setting = index.model().data(index, Qt.ItemDataRole.UserRole) if setting.valuetype == Setting.SELECTION: editor.setCurrentIndex(editor.findText(value)) elif setting.valuetype == Setting.SELECTION_STORE_STRING: editor.setCurrentIndex(editor.findData(value)) elif setting.valuetype in (Setting.FLOAT, Setting.INT): editor.setValue(value) else: editor.setText(value) def setModelData(self, editor, model, index): value = self.convertValue(index.model().data(index, Qt.ItemDataRole.EditRole)) setting = index.model().data(index, Qt.ItemDataRole.UserRole) if setting.valuetype == Setting.SELECTION: model.setData(index, editor.currentText(), Qt.ItemDataRole.EditRole) elif setting.valuetype == Setting.SELECTION_STORE_STRING: model.setData(index, editor.currentData(), Qt.ItemDataRole.EditRole) else: if isinstance(value, str): model.setData(index, editor.text(), Qt.ItemDataRole.EditRole) else: model.setData(index, editor.value(), Qt.ItemDataRole.EditRole) def sizeHint(self, option, index): return QgsSpinBox().sizeHint() def eventFilter(self, editor, event): if event.type() == QEvent.Type.FocusOut and hasattr(editor, "canFocusOut"): if not editor.canFocusOut: return False return QStyledItemDelegate.eventFilter(self, editor, event) def convertValue(self, value): if value is None or value == NULL: return "" try: return int(value) except: try: return float(value) except: return str(value) class FileDirectorySelector(QWidget): def __init__(self, parent=None, selectFile=False, placeholder=""): QWidget.__init__(self, parent) # create gui self.btnSelect = QToolButton() self.btnSelect.setText("…") self.lineEdit = QLineEdit() self.lineEdit.setPlaceholderText(placeholder) self.hbl = QHBoxLayout() self.hbl.setMargin(0) self.hbl.setSpacing(0) self.hbl.addWidget(self.lineEdit) self.hbl.addWidget(self.btnSelect) self.setLayout(self.hbl) self.canFocusOut = False self.selectFile = selectFile self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.btnSelect.clicked.connect(self.select) def select(self): lastDir = "" if not self.selectFile: selectedPath = QFileDialog.getExistingDirectory( None, self.tr("Select directory"), lastDir, QFileDialog.Option.ShowDirsOnly, ) else: selectedPath, selected_filter = QFileDialog.getOpenFileName( None, self.tr("Select file"), lastDir, self.tr("All files (*)") ) if not selectedPath: return self.lineEdit.setText(selectedPath) self.canFocusOut = True def text(self): return self.lineEdit.text() def setText(self, value): self.lineEdit.setText(value) class MultipleDirectorySelector(QWidget): def __init__(self, parent=None, placeholder=""): QWidget.__init__(self, parent) # create gui self.btnSelect = QToolButton() self.btnSelect.setText("…") self.lineEdit = QLineEdit() self.lineEdit.setPlaceholderText(placeholder) self.hbl = QHBoxLayout() self.hbl.setMargin(0) self.hbl.setSpacing(0) self.hbl.addWidget(self.lineEdit) self.hbl.addWidget(self.btnSelect) self.setLayout(self.hbl) self.canFocusOut = False self.setFocusPolicy(Qt.FocusPolicy.StrongFocus) self.btnSelect.clicked.connect(self.select) def select(self): text = self.lineEdit.text() if text != "": items = text.split(";") else: items = [] dlg = DirectorySelectorDialog(None, items) if dlg.exec(): text = dlg.value() self.lineEdit.setText(text) self.canFocusOut = True def text(self): return self.lineEdit.text() def setText(self, value): self.lineEdit.setText(value)