1# -*- coding: utf-8 -*- 2 3""" 4*************************************************************************** 5 ConfigDialog.py 6 --------------------- 7 Date : August 2012 8 Copyright : (C) 2012 by Victor Olaya 9 Email : volayaf at gmail dot com 10*************************************************************************** 11* * 12* This program is free software; you can redistribute it and/or modify * 13* it under the terms of the GNU General Public License as published by * 14* the Free Software Foundation; either version 2 of the License, or * 15* (at your option) any later version. * 16* * 17*************************************************************************** 18""" 19 20__author__ = 'Victor Olaya' 21__date__ = 'August 2012' 22__copyright__ = '(C) 2012, Victor Olaya' 23 24import os 25import warnings 26 27from qgis.PyQt import uic 28from qgis.PyQt.QtCore import Qt, QEvent 29from qgis.PyQt.QtWidgets import (QFileDialog, 30 QStyle, 31 QMessageBox, 32 QStyledItemDelegate, 33 QLineEdit, 34 QWidget, 35 QToolButton, 36 QHBoxLayout, 37 QComboBox, 38 QPushButton, 39 QApplication) 40from qgis.PyQt.QtGui import (QIcon, 41 QStandardItemModel, 42 QStandardItem, 43 QCursor) 44 45from qgis.gui import (QgsDoubleSpinBox, 46 QgsSpinBox, 47 QgsOptionsPageWidget, 48 QgsOptionsDialogHighlightWidget) 49from qgis.core import NULL, QgsApplication, QgsSettings 50from qgis.utils import OverrideCursor 51 52from processing.core.ProcessingConfig import (ProcessingConfig, 53 settingsWatcher, 54 Setting) 55from processing.core.Processing import Processing 56from processing.gui.DirectorySelectorDialog import DirectorySelectorDialog 57from processing.gui.menus import defaultMenuEntries, menusSettingsGroup 58 59pluginPath = os.path.split(os.path.dirname(__file__))[0] 60with warnings.catch_warnings(): 61 warnings.filterwarnings("ignore", category=DeprecationWarning) 62 WIDGET, BASE = uic.loadUiType( 63 os.path.join(pluginPath, 'ui', 'DlgConfig.ui')) 64 65 66class ConfigOptionsPage(QgsOptionsPageWidget): 67 68 def __init__(self, parent): 69 super(ConfigOptionsPage, self).__init__(parent) 70 self.config_widget = ConfigDialog(False) 71 layout = QHBoxLayout() 72 layout.setContentsMargins(0, 0, 0, 0) 73 layout.setMargin(0) 74 self.setLayout(layout) 75 layout.addWidget(self.config_widget) 76 self.setObjectName('processingOptions') 77 self.highlightWidget = ProcessingTreeHighlight(self.config_widget) 78 self.registerHighlightWidget(self.highlightWidget) 79 80 def apply(self): 81 self.config_widget.accept() 82 83 def helpKey(self): 84 return 'processing/index.html' 85 86 87class ProcessingTreeHighlight(QgsOptionsDialogHighlightWidget): 88 89 def __init__(self, config_dialog): 90 super(ProcessingTreeHighlight, self).__init__(config_dialog.tree) 91 self.config_dialog = config_dialog 92 93 def highlightText(self, text): 94 return self.config_dialog.textChanged(text) 95 96 def searchText(self, text): 97 return self.config_dialog.textChanged(text) 98 99 def reset(self): 100 self.config_dialog.textChanged('') 101 102 103class ConfigDialog(BASE, WIDGET): 104 105 def __init__(self, showSearch=True): 106 super(ConfigDialog, self).__init__(None) 107 self.setupUi(self) 108 109 self.groupIcon = QgsApplication.getThemeIcon('mIconFolder.svg') 110 111 self.model = QStandardItemModel() 112 self.tree.setModel(self.model) 113 114 self.delegate = SettingDelegate() 115 self.tree.setItemDelegateForColumn(1, self.delegate) 116 117 if showSearch: 118 if hasattr(self.searchBox, 'setPlaceholderText'): 119 self.searchBox.setPlaceholderText(QApplication.translate('ConfigDialog', 'Search…')) 120 self.searchBox.textChanged.connect(self.textChanged) 121 else: 122 self.searchBox.hide() 123 124 self.fillTree() 125 126 self.saveMenus = False 127 self.tree.expanded.connect(self.itemExpanded) 128 self.auto_adjust_columns = True 129 130 def textChanged(self, text=None): 131 if text is not None: 132 text = str(text.lower()) 133 else: 134 text = str(self.searchBox.text().lower()) 135 found = self._filterItem(self.model.invisibleRootItem(), text) 136 137 self.auto_adjust_columns = False 138 if text: 139 self.tree.expandAll() 140 else: 141 self.tree.collapseAll() 142 143 self.adjustColumns() 144 self.auto_adjust_columns = True 145 146 if text: 147 return found 148 else: 149 self.tree.collapseAll() 150 return False 151 152 def _filterItem(self, item, text, forceShow=False): 153 if item.hasChildren(): 154 show = forceShow or isinstance(item, QStandardItem) and bool(text) and (text in item.text().lower()) 155 for i in range(item.rowCount()): 156 child = item.child(i) 157 show = self._filterItem(child, text, forceShow) or show 158 self.tree.setRowHidden(item.row(), item.index().parent(), not show) 159 return show 160 161 elif isinstance(item, QStandardItem): 162 show = forceShow or bool(text) and (text in item.text().lower()) 163 self.tree.setRowHidden(item.row(), item.index().parent(), not show) 164 return show 165 166 def fillTree(self): 167 self.fillTreeUsingProviders() 168 169 def fillTreeUsingProviders(self): 170 self.items = {} 171 self.model.clear() 172 self.model.setHorizontalHeaderLabels([self.tr('Setting'), 173 self.tr('Value')]) 174 175 settings = ProcessingConfig.getSettings() 176 177 rootItem = self.model.invisibleRootItem() 178 179 """ 180 Filter 'General', 'Models' and 'Scripts' items 181 """ 182 priorityKeys = [self.tr('General'), self.tr('Models'), self.tr('Scripts')] 183 for group in priorityKeys: 184 groupItem = QStandardItem(group) 185 icon = ProcessingConfig.getGroupIcon(group) 186 groupItem.setIcon(icon) 187 groupItem.setEditable(False) 188 emptyItem = QStandardItem() 189 emptyItem.setEditable(False) 190 191 rootItem.insertRow(0, [groupItem, emptyItem]) 192 if group not in settings: 193 continue 194 195 # add menu item only if it has any search matches 196 for setting in settings[group]: 197 if setting.hidden or setting.name.startswith("MENU_"): 198 continue 199 200 labelItem = QStandardItem(setting.description) 201 labelItem.setIcon(icon) 202 labelItem.setEditable(False) 203 self.items[setting] = SettingItem(setting) 204 groupItem.insertRow(0, [labelItem, self.items[setting]]) 205 206 """ 207 Filter 'Providers' items 208 """ 209 providersItem = QStandardItem(self.tr('Providers')) 210 icon = QgsApplication.getThemeIcon("/processingAlgorithm.svg") 211 providersItem.setIcon(icon) 212 providersItem.setEditable(False) 213 emptyItem = QStandardItem() 214 emptyItem.setEditable(False) 215 216 rootItem.insertRow(0, [providersItem, emptyItem]) 217 for group in list(settings.keys()): 218 if group in priorityKeys or group == menusSettingsGroup: 219 continue 220 221 groupItem = QStandardItem(group) 222 icon = ProcessingConfig.getGroupIcon(group) 223 groupItem.setIcon(icon) 224 groupItem.setEditable(False) 225 226 for setting in settings[group]: 227 if setting.hidden: 228 continue 229 230 labelItem = QStandardItem(setting.description) 231 labelItem.setIcon(icon) 232 labelItem.setEditable(False) 233 self.items[setting] = SettingItem(setting) 234 groupItem.insertRow(0, [labelItem, self.items[setting]]) 235 236 emptyItem = QStandardItem() 237 emptyItem.setEditable(False) 238 providersItem.appendRow([groupItem, emptyItem]) 239 240 """ 241 Filter 'Menus' items 242 """ 243 self.menusItem = QStandardItem(self.tr('Menus')) 244 icon = QIcon(os.path.join(pluginPath, 'images', 'menu.png')) 245 self.menusItem.setIcon(icon) 246 self.menusItem.setEditable(False) 247 emptyItem = QStandardItem() 248 emptyItem.setEditable(False) 249 250 rootItem.insertRow(0, [self.menusItem, emptyItem]) 251 252 button = QPushButton(self.tr('Reset to defaults')) 253 button.clicked.connect(self.resetMenusToDefaults) 254 layout = QHBoxLayout() 255 layout.setContentsMargins(0, 0, 0, 0) 256 layout.addWidget(button) 257 layout.addStretch() 258 widget = QWidget() 259 widget.setLayout(layout) 260 self.tree.setIndexWidget(emptyItem.index(), widget) 261 262 for provider in QgsApplication.processingRegistry().providers(): 263 providerDescription = provider.name() 264 groupItem = QStandardItem(providerDescription) 265 icon = provider.icon() 266 groupItem.setIcon(icon) 267 groupItem.setEditable(False) 268 269 for alg in provider.algorithms(): 270 algItem = QStandardItem(alg.displayName()) 271 algItem.setIcon(icon) 272 algItem.setEditable(False) 273 try: 274 settingMenu = ProcessingConfig.settings["MENU_" + alg.id()] 275 settingButton = ProcessingConfig.settings["BUTTON_" + alg.id()] 276 settingIcon = ProcessingConfig.settings["ICON_" + alg.id()] 277 except: 278 continue 279 self.items[settingMenu] = SettingItem(settingMenu) 280 self.items[settingButton] = SettingItem(settingButton) 281 self.items[settingIcon] = SettingItem(settingIcon) 282 menuLabelItem = QStandardItem("Menu path") 283 menuLabelItem.setEditable(False) 284 buttonLabelItem = QStandardItem("Add button in toolbar") 285 buttonLabelItem.setEditable(False) 286 iconLabelItem = QStandardItem("Icon") 287 iconLabelItem.setEditable(False) 288 emptyItem = QStandardItem() 289 emptyItem.setEditable(False) 290 algItem.insertRow(0, [menuLabelItem, self.items[settingMenu]]) 291 algItem.insertRow(0, [buttonLabelItem, self.items[settingButton]]) 292 algItem.insertRow(0, [iconLabelItem, self.items[settingIcon]]) 293 groupItem.insertRow(0, [algItem, emptyItem]) 294 295 emptyItem = QStandardItem() 296 emptyItem.setEditable(False) 297 298 self.menusItem.appendRow([groupItem, emptyItem]) 299 300 self.tree.sortByColumn(0, Qt.AscendingOrder) 301 self.adjustColumns() 302 303 def resetMenusToDefaults(self): 304 for provider in QgsApplication.processingRegistry().providers(): 305 for alg in provider.algorithms(): 306 d = defaultMenuEntries.get(alg.id(), "") 307 setting = ProcessingConfig.settings["MENU_" + alg.id()] 308 item = self.items[setting] 309 item.setData(d, Qt.EditRole) 310 self.saveMenus = True 311 312 def accept(self): 313 qsettings = QgsSettings() 314 for setting in list(self.items.keys()): 315 if setting.group != menusSettingsGroup or self.saveMenus: 316 if isinstance(setting.value, bool): 317 setting.setValue(self.items[setting].checkState() == Qt.Checked) 318 else: 319 try: 320 setting.setValue(str(self.items[setting].text())) 321 except ValueError as e: 322 QMessageBox.warning(self, self.tr('Wrong value'), 323 self.tr('Wrong value for parameter "{0}":\n\n{1}').format(setting.description, str(e))) 324 return 325 setting.save(qsettings) 326 327 with OverrideCursor(Qt.WaitCursor): 328 for p in QgsApplication.processingRegistry().providers(): 329 p.refreshAlgorithms() 330 331 settingsWatcher.settingsChanged.emit() 332 333 def itemExpanded(self, idx): 334 if idx == self.menusItem.index(): 335 self.saveMenus = True 336 if self.auto_adjust_columns: 337 self.adjustColumns() 338 339 def adjustColumns(self): 340 self.tree.resizeColumnToContents(0) 341 self.tree.resizeColumnToContents(1) 342 343 344class SettingItem(QStandardItem): 345 346 def __init__(self, setting): 347 QStandardItem.__init__(self) 348 self.setting = setting 349 self.setData(setting, Qt.UserRole) 350 if isinstance(setting.value, bool): 351 self.setCheckable(True) 352 self.setEditable(False) 353 if setting.value: 354 self.setCheckState(Qt.Checked) 355 else: 356 self.setCheckState(Qt.Unchecked) 357 else: 358 self.setData(setting.value, Qt.EditRole) 359 360 361class SettingDelegate(QStyledItemDelegate): 362 363 def __init__(self, parent=None): 364 QStyledItemDelegate.__init__(self, parent) 365 366 def createEditor(self, parent, options, index): 367 setting = index.model().data(index, Qt.UserRole) 368 if setting.valuetype == Setting.FOLDER: 369 return FileDirectorySelector(parent, placeholder=setting.placeholder) 370 elif setting.valuetype == Setting.FILE: 371 return FileDirectorySelector(parent, True, setting.placeholder) 372 elif setting.valuetype == Setting.SELECTION: 373 combo = QComboBox(parent) 374 combo.addItems(setting.options) 375 return combo 376 elif setting.valuetype == Setting.MULTIPLE_FOLDERS: 377 return MultipleDirectorySelector(parent, setting.placeholder) 378 else: 379 value = self.convertValue(index.model().data(index, Qt.EditRole)) 380 if isinstance(value, int): 381 spnBox = QgsSpinBox(parent) 382 spnBox.setRange(-999999999, 999999999) 383 return spnBox 384 elif isinstance(value, float): 385 spnBox = QgsDoubleSpinBox(parent) 386 spnBox.setRange(-999999999.999999, 999999999.999999) 387 spnBox.setDecimals(6) 388 return spnBox 389 elif isinstance(value, str): 390 lineEdit = QLineEdit(parent) 391 lineEdit.setPlaceholderText(setting.placeholder) 392 return lineEdit 393 394 def setEditorData(self, editor, index): 395 value = self.convertValue(index.model().data(index, Qt.EditRole)) 396 setting = index.model().data(index, Qt.UserRole) 397 if setting.valuetype == Setting.SELECTION: 398 editor.setCurrentIndex(editor.findText(value)) 399 elif setting.valuetype in (Setting.FLOAT, Setting.INT): 400 editor.setValue(value) 401 else: 402 editor.setText(value) 403 404 def setModelData(self, editor, model, index): 405 value = self.convertValue(index.model().data(index, Qt.EditRole)) 406 setting = index.model().data(index, Qt.UserRole) 407 if setting.valuetype == Setting.SELECTION: 408 model.setData(index, editor.currentText(), Qt.EditRole) 409 else: 410 if isinstance(value, str): 411 model.setData(index, editor.text(), Qt.EditRole) 412 else: 413 model.setData(index, editor.value(), Qt.EditRole) 414 415 def sizeHint(self, option, index): 416 return QgsSpinBox().sizeHint() 417 418 def eventFilter(self, editor, event): 419 if event.type() == QEvent.FocusOut and hasattr(editor, 'canFocusOut'): 420 if not editor.canFocusOut: 421 return False 422 return QStyledItemDelegate.eventFilter(self, editor, event) 423 424 def convertValue(self, value): 425 if value is None or value == NULL: 426 return "" 427 try: 428 return int(value) 429 except: 430 try: 431 return float(value) 432 except: 433 return str(value) 434 435 436class FileDirectorySelector(QWidget): 437 438 def __init__(self, parent=None, selectFile=False, placeholder=""): 439 QWidget.__init__(self, parent) 440 441 # create gui 442 self.btnSelect = QToolButton() 443 self.btnSelect.setText('…') 444 self.lineEdit = QLineEdit() 445 self.lineEdit.setPlaceholderText(placeholder) 446 self.hbl = QHBoxLayout() 447 self.hbl.setMargin(0) 448 self.hbl.setSpacing(0) 449 self.hbl.addWidget(self.lineEdit) 450 self.hbl.addWidget(self.btnSelect) 451 452 self.setLayout(self.hbl) 453 454 self.canFocusOut = False 455 self.selectFile = selectFile 456 457 self.setFocusPolicy(Qt.StrongFocus) 458 self.btnSelect.clicked.connect(self.select) 459 460 def select(self): 461 lastDir = '' 462 if not self.selectFile: 463 selectedPath = QFileDialog.getExistingDirectory(None, 464 self.tr('Select directory'), lastDir, 465 QFileDialog.ShowDirsOnly) 466 else: 467 selectedPath, selected_filter = QFileDialog.getOpenFileName(None, 468 self.tr('Select file'), lastDir, self.tr('All files (*)') 469 ) 470 471 if not selectedPath: 472 return 473 474 self.lineEdit.setText(selectedPath) 475 self.canFocusOut = True 476 477 def text(self): 478 return self.lineEdit.text() 479 480 def setText(self, value): 481 self.lineEdit.setText(value) 482 483 484class MultipleDirectorySelector(QWidget): 485 486 def __init__(self, parent=None, placeholder=""): 487 QWidget.__init__(self, parent) 488 489 # create gui 490 self.btnSelect = QToolButton() 491 self.btnSelect.setText('…') 492 self.lineEdit = QLineEdit() 493 self.lineEdit.setPlaceholderText(placeholder) 494 self.hbl = QHBoxLayout() 495 self.hbl.setMargin(0) 496 self.hbl.setSpacing(0) 497 self.hbl.addWidget(self.lineEdit) 498 self.hbl.addWidget(self.btnSelect) 499 500 self.setLayout(self.hbl) 501 502 self.canFocusOut = False 503 504 self.setFocusPolicy(Qt.StrongFocus) 505 self.btnSelect.clicked.connect(self.select) 506 507 def select(self): 508 text = self.lineEdit.text() 509 if text != '': 510 items = text.split(';') 511 else: 512 items = [] 513 514 dlg = DirectorySelectorDialog(None, items) 515 if dlg.exec_(): 516 text = dlg.value() 517 self.lineEdit.setText(text) 518 519 self.canFocusOut = True 520 521 def text(self): 522 return self.lineEdit.text() 523 524 def setText(self, value): 525 self.lineEdit.setText(value) 526