ConfigDialog.py 19 KB

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