1# -*- coding: utf-8 -*-
2
3"""
4***************************************************************************
5    MultipleInputDialog.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
26from pathlib import Path
27
28from qgis.core import (QgsSettings,
29                       QgsProcessing,
30                       QgsVectorFileWriter,
31                       QgsProviderRegistry,
32                       QgsProcessingModelChildParameterSource)
33from qgis.PyQt import uic
34from qgis.PyQt.QtCore import Qt, QByteArray, QCoreApplication
35from qgis.PyQt.QtWidgets import QDialog, QAbstractItemView, QPushButton, QDialogButtonBox, QFileDialog
36from qgis.PyQt.QtGui import QStandardItemModel, QStandardItem
37
38pluginPath = os.path.split(os.path.dirname(__file__))[0]
39with warnings.catch_warnings():
40    warnings.filterwarnings("ignore", category=DeprecationWarning)
41    WIDGET, BASE = uic.loadUiType(
42        os.path.join(pluginPath, 'ui', 'DlgMultipleSelection.ui'))
43
44
45class MultipleInputDialog(BASE, WIDGET):
46
47    def __init__(self, options, selectedoptions=None, datatype=None):
48        super(MultipleInputDialog, self).__init__(None)
49        self.setupUi(self)
50        self.datatype = datatype
51        self.model = None
52
53        self.options = []
54        for i, option in enumerate(options):
55            if option is None or isinstance(option, str):
56                self.options.append((i, option))
57            else:
58                self.options.append((option[0], option[1]))
59
60        self.selectedoptions = selectedoptions or []
61
62        # Additional buttons
63        self.btnSelectAll = QPushButton(self.tr('Select All'))
64        self.buttonBox.addButton(self.btnSelectAll,
65                                 QDialogButtonBox.ActionRole)
66        self.btnClearSelection = QPushButton(self.tr('Clear Selection'))
67        self.buttonBox.addButton(self.btnClearSelection,
68                                 QDialogButtonBox.ActionRole)
69        self.btnToggleSelection = QPushButton(self.tr('Toggle Selection'))
70        self.buttonBox.addButton(self.btnToggleSelection,
71                                 QDialogButtonBox.ActionRole)
72        if self.datatype is not None:
73            btnAddFile = QPushButton(QCoreApplication.translate("MultipleInputDialog", 'Add File(s)…'))
74            btnAddFile.clicked.connect(self.addFiles)
75            self.buttonBox.addButton(btnAddFile,
76                                     QDialogButtonBox.ActionRole)
77
78            btnAddDir = QPushButton(QCoreApplication.translate("MultipleInputDialog", 'Add Directory…'))
79            btnAddDir.clicked.connect(self.addDirectory)
80            self.buttonBox.addButton(btnAddDir,
81                                     QDialogButtonBox.ActionRole)
82
83        self.btnSelectAll.clicked.connect(lambda: self.selectAll(True))
84        self.btnClearSelection.clicked.connect(lambda: self.selectAll(False))
85        self.btnToggleSelection.clicked.connect(self.toggleSelection)
86
87        self.settings = QgsSettings()
88        self.restoreGeometry(self.settings.value("/Processing/multipleInputDialogGeometry", QByteArray()))
89
90        self.lstLayers.setSelectionMode(QAbstractItemView.ExtendedSelection)
91        self.lstLayers.setDragDropMode(QAbstractItemView.InternalMove)
92
93        self.populateList()
94        self.finished.connect(self.saveWindowGeometry)
95
96    def saveWindowGeometry(self):
97        self.settings.setValue("/Processing/multipleInputDialogGeometry", self.saveGeometry())
98
99    def populateList(self):
100        self.model = QStandardItemModel()
101        for value, text in self.options:
102            item = QStandardItem(text)
103            item.setData(value, Qt.UserRole)
104            item.setCheckState(Qt.Checked if value in self.selectedoptions else Qt.Unchecked)
105            item.setCheckable(True)
106            item.setDropEnabled(False)
107            self.model.appendRow(item)
108
109        # add extra options (e.g. manually added layers)
110        for t in [o for o in self.selectedoptions if not isinstance(o, int)]:
111            if isinstance(t, QgsProcessingModelChildParameterSource):
112                item = QStandardItem(t.staticValue())
113            else:
114                item = QStandardItem(t)
115            item.setData(item.text(), Qt.UserRole)
116            item.setCheckState(Qt.Checked)
117            item.setCheckable(True)
118            item.setDropEnabled(False)
119            self.model.appendRow(item)
120
121        self.lstLayers.setModel(self.model)
122
123    def accept(self):
124        self.selectedoptions = []
125        model = self.lstLayers.model()
126        for i in range(model.rowCount()):
127            item = model.item(i)
128            if item.checkState() == Qt.Checked:
129                self.selectedoptions.append(item.data(Qt.UserRole))
130        QDialog.accept(self)
131
132    def reject(self):
133        self.selectedoptions = None
134        QDialog.reject(self)
135
136    def getItemsToModify(self):
137        items = []
138        if len(self.lstLayers.selectedIndexes()) > 1:
139            for i in self.lstLayers.selectedIndexes():
140                items.append(self.model.itemFromIndex(i))
141        else:
142            for i in range(self.model.rowCount()):
143                items.append(self.model.item(i))
144        return items
145
146    def selectAll(self, value):
147        for item in self.getItemsToModify():
148            item.setCheckState(Qt.Checked if value else Qt.Unchecked)
149
150    def toggleSelection(self):
151        for item in self.getItemsToModify():
152            checked = item.checkState() == Qt.Checked
153            item.setCheckState(Qt.Unchecked if checked else Qt.Checked)
154
155    def getFileFilter(self, datatype):
156        """
157        Returns a suitable file filter pattern for the specified parameter definition
158        :param param:
159        :return:
160        """
161        if datatype == QgsProcessing.TypeRaster:
162            return QgsProviderRegistry.instance().fileRasterFilters()
163        elif datatype == QgsProcessing.TypeFile:
164            return self.tr('All files (*.*)')
165        else:
166            exts = QgsVectorFileWriter.supportedFormatExtensions()
167            for i in range(len(exts)):
168                exts[i] = self.tr('{0} files (*.{1})').format(exts[i].upper(), exts[i].lower())
169            return self.tr('All files (*.*)') + ';;' + ';;'.join(exts)
170
171    def addFiles(self):
172        filter = self.getFileFilter(self.datatype)
173
174        settings = QgsSettings()
175        path = str(settings.value('/Processing/LastInputPath'))
176
177        ret, selected_filter = QFileDialog.getOpenFileNames(self, self.tr('Select File(s)'),
178                                                            path, filter)
179        if ret:
180            files = list(ret)
181            settings.setValue('/Processing/LastInputPath',
182                              os.path.dirname(str(files[0])))
183            for filename in files:
184                item = QStandardItem(filename)
185                item.setData(filename, Qt.UserRole)
186                item.setCheckState(Qt.Checked)
187                item.setCheckable(True)
188                item.setDropEnabled(False)
189                self.model.appendRow(item)
190
191    def addDirectory(self):
192        settings = QgsSettings()
193        path = str(settings.value('/Processing/LastInputPath'))
194
195        ret = QFileDialog.getExistingDirectory(self, self.tr('Select File(s)'), path)
196        if ret:
197            exts = []
198
199            if self.datatype == QgsProcessing.TypeVector:
200                exts = QgsVectorFileWriter.supportedFormatExtensions()
201            elif self.datatype == QgsProcessing.TypeRaster:
202                for t in QgsProviderRegistry.instance().fileRasterFilters().split(';;')[1:]:
203                    for e in t[t.index('(') + 1:-1].split(' '):
204                        if e != "*.*" and e.startswith("*."):
205                            exts.append(e[2:])
206
207            files = []
208            for pp in Path(ret).rglob("*"):
209                if not pp.is_file():
210                    continue
211
212                if exts and pp.suffix[1:] not in exts:
213                    continue
214
215                p = pp.as_posix()
216
217                files.append(p)
218
219            settings.setValue('/Processing/LastInputPath', ret)
220
221            for filename in files:
222                item = QStandardItem(filename)
223                item.setData(filename, Qt.UserRole)
224                item.setCheckState(Qt.Checked)
225                item.setCheckable(True)
226                item.setDropEnabled(False)
227                self.model.appendRow(item)
228