Exemplo n.º 1
0
class BigList(Ui_BigList, QWidget):
    itemselected = pyqtSignal(QModelIndex)
    closewidget = pyqtSignal()
    savewidget = pyqtSignal()

    def __init__(self, parent=None, centeronparent=False, showsave=True):
        super(BigList, self).__init__(parent)
        self.setupUi(self)
        self.centeronparent = centeronparent
        self.listView.clicked.connect(self.selected)
        self.saveButton.pressed.connect(self.savewidget.emit)
        self.closebutton.pressed.connect(self.closewidget.emit)
        self._index = None
        self.search.textEdited.connect(self.set_filter)
        self.filtermodel = QSortFilterProxyModel()
        self.filtermodel.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self.listView.setModel(self.filtermodel)
        self.listView.setWordWrap(True)

        self.saveButton.setVisible(showsave)

        utils.install_touch_scroll(self.listView)

    def set_filter(self, text):
        self.filtermodel.setFilterRegExp(text + ".*")

    def selected(self, index):
        self._index = index
        self._index = self.filtermodel.mapToSource(index)
        self.itemselected.emit(self._index)

    def setmodel(self, model):
        self.filtermodel.setSourceModel(model)

    def setlabel(self, fieldname):
        self.fieldnameLabel.setText(fieldname)

    def currentindex(self):
        return self._index

    def setcurrentindex(self, index):
        if index is None:
            index = QModelIndex()
        if isinstance(index, int):
            index = self.listView.model().index(index, 0)
        self.listView.setCurrentIndex(index)

    def show(self):
        super(BigList, self).show()

        if self.centeronparent:
            width = self.parent().width()
            height = self.parent().height()
            self.move(width / 4, 0)
            self.resize(QSize(width / 2, height))
Exemplo n.º 2
0
class ExtendedComboBox(QComboBox):
    def __init__(self, parent=None):
        super(ExtendedComboBox, self).__init__(parent)

        self.setFocusPolicy(Qt.StrongFocus)
        self.setEditable(True)
        self.pFilterModel = QSortFilterProxyModel(self)
        self.pFilterModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self.pFilterModel.setSourceModel(self.model())
        self.completer = QCompleter(self.pFilterModel, self)
        self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion)
        self.completer.popup().setStyleSheet("min-height: 150px")
        self.completer.popup().setAlternatingRowColors(True)
        self.setCompleter(self.completer)
        self.lineEdit().textEdited[str].connect(self.pFilterModel.setFilterFixedString)
Exemplo n.º 3
0
class ExtendedComboBox(QComboBox):
    def __init__(self, parent=None):
        super(ExtendedComboBox, self).__init__(parent)

        self.setFocusPolicy(Qt.StrongFocus)
        self.setEditable(True)
        self.pFilterModel = QSortFilterProxyModel(self)
        self.pFilterModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self.pFilterModel.setSourceModel(self.model())
        self.completer = QCompleter(self.pFilterModel, self)
        self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion)
        self.completer.popup().setStyleSheet('min-height: 150px')
        self.completer.popup().setAlternatingRowColors(True)
        self.setCompleter(self.completer)
        self.lineEdit().textEdited[unicode].connect(
            self.pFilterModel.setFilterFixedString)
Exemplo n.º 4
0
class ExtendedComboBox(QComboBox):
    """Extended class of QComboBox so we can perform a filtering of items.
    """
    def __init__(self, parent=None):
        super(ExtendedComboBox, self).__init__(parent)

        self.setFocusPolicy(Qt.StrongFocus)
        self.setEditable(True)

        # add a filter model to filter matching items
        self.pFilterModel = QSortFilterProxyModel(self)
        self.pFilterModel.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self.pFilterModel.setSourceModel(self.model())

        # add a completer, which uses the filter model
        self.completer = QCompleter(self.pFilterModel, self)
        # always show all (filtered) completions
        self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion)
        self.setCompleter(self.completer)

        # connect signals
        self.lineEdit().textEdited[str].connect(
            self.pFilterModel.setFilterFixedString)
        self.completer.activated.connect(self.on_completer_activated)

    # on selection of an item from the completer,
    # select the corresponding item from combobox
    def on_completer_activated(self, text):
        if text:
            index = self.findText(text)
            self.setCurrentIndex(index)

    # on model change, update the models of the filter and completer as well
    def setModel(self, model):
        super(ExtendedComboBox, self).setModel(model)
        self.pFilterModel.setSourceModel(model)
        self.completer.setModel(self.pFilterModel)

    # on model column change, update the model column of
    # the filter and completer as well
    def setModelColumn(self, column):
        self.completer.setCompletionColumn(column)
        self.pFilterModel.setFilterKeyColumn(column)
        super(ExtendedComboBox, self).setModelColumn(column)
Exemplo n.º 5
0
class FilteredComboBox(QComboBox):
    """Custom QComboBox with filtering option."""
    def __init__(self, parent=None):
        super(FilteredComboBox, self).__init__(parent)

        self.setFocusPolicy(Qt.StrongFocus)
        self.setSizeAdjustPolicy(QComboBox.AdjustToMinimumContentsLength)
        self.setEditable(True)
        self.filter_proxy_model = QSortFilterProxyModel(self)
        self.filter_proxy_model.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self.filter_proxy_model.setSortCaseSensitivity(Qt.CaseInsensitive)
        self.filter_proxy_model.setSourceModel(self.model())
        self.completer = QCompleter(self.filter_proxy_model, self)
        self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion)
        self.setCompleter(self.completer)
        self.setMinimumSize(150, 25)
        self.setFont(QFont("Segoe UI", 10))
        self.setStyleSheet("QComboBox {background-color: white;}")
        self.setMaxVisibleItems(10)
        self.completer.activated.connect(self.on_completer_activated)
        self.lineEdit().textEdited.connect(
            self.filter_proxy_model.setFilterFixedString)

    def on_completer_activated(self, text):
        """Set active combobox item when a completer item is picked."""
        if not text:
            return
        idx = self.findText(text)
        self.setCurrentIndex(idx)
        self.activated[str].emit(self.itemText(idx))

    def setModel(self, model):
        """Set completer model after the combobox model."""
        super(FilteredComboBox, self).setModel(model)
        self.filter_proxy_model.setSourceModel(model)
        self.completer.setModel(self.filter_proxy_model)

    def setModelColumn(self, column_idx):
        """Set the correct column for completer and combobox model using column index."""
        self.completer.setCompletionColumn(column_idx)
        self.filter_proxy_model.setFilterKeyColumn(column_idx)
        super(FilteredComboBox, self).setModelColumn(column_idx)
Exemplo n.º 6
0
class ExtendedCombobox(QComboBox):
    """
    Overwrite combobox to provide text filtering of
    combobox list.
    """

    def __init__(self, parent):
        """
        Initialise  ExtendedCombobox

        :param parent: Parent of combobox
        :type parent: PyQt5.QtWidgets.QWidget
        """

        super().__init__(parent)

        self.setFocusPolicy(Qt.StrongFocus)
        self.setEditable(True)
        self.completer = QCompleter(self)

        # always show all completions
        self.completer.setCompletionMode(QCompleter.UnfilteredPopupCompletion)
        self.p_filter_model = QSortFilterProxyModel(self)
        self.p_filter_model.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self.completer.setPopup(self.view())
        self.setCompleter(self.completer)
        self.lineEdit().textEdited.connect(self.p_filter_model.setFilterFixedString)
        self.completer.activated.connect(self.setTextIfCompleterIsClicked)

    def setModel(self, model):  # pylint:disable=invalid-name
        """
        Set the model to use the Filter model

        :param model: The model to be used by the combobox
        :type model: PyQt5.QtGui.QStandardItemModel
        """

        super().setModel(model)
        self.p_filter_model.setSourceModel(model)
        self.completer.setModel(self.p_filter_model)

    def setModelColumn(self, column):  # pylint:disable=invalid-name
        """
        :param model: The model to be used by the combobox
        :type model: PyQt5.QtGui.QStandardItemModel
        """

        self.completer.setCompletionColumn(column)
        self.p_filter_model.setFilterKeyColumn(column)
        super().setModelColumn(column)

    def view(self):
        """
        A QListView of items stored in the model

        :return: items stored in the model
        :rtype: PyQt5.QtWidgets.QListView
        """

        return self.completer.popup()

    def index(self):
        """
        Index of the current item in the combobox.

        :return: index of the current item
        :rtype: int
        """

        return self.currentIndex()

    def setTextIfCompleterIsClicked(self, text):  # pylint:disable=invalid-name
        """
        :param text: The current text of the qlineedit
        :type text: str

        If the combobx lineedit is clicked, set the lineedits
        current item as the combobox's current item
        """

        if text:
            index = self.findText(text)
            self.setCurrentIndex(index)
class ThreeDiResultSelectionWidget(QWidget, FORM_CLASS):
    """Dialog for selecting model (spatialite and result files netCDFs)

    TODO: actually, it is two dialogs in one: a selector of results and a
    separate downloader of results. They are not really connected. So
    splitting them makes the code simpler. And, more importantly, it makes the
    UI clearer.

    """

    closingDialog = pyqtSignal()

    def __init__(
        self,
        parent=None,
        iface=None,
        ts_datasources=None,
        downloadable_results=None,
        tool=None,
    ):
        """Constructor

        :parent: Qt parent Widget
        :iface: QGiS interface
        :ts_datasources: TimeseriesDatasourceModel instance
        :downloadable_results: DownloadableResultModel instance
        :tool: the tool class which instantiated this widget. Is used
             here for storing volatile information
        """
        super().__init__(parent)

        self.tool = tool
        self.iface = iface
        self.setupUi(self)

        # login administration
        self.login_dialog = LoginDialog()
        # NOTE: autoDefault was set on ``log_in_button`` (via Qt Designer),
        # which makes pressing Enter work for logging in.
        self.login_dialog.log_in_button.clicked.connect(self.handle_log_in)

        # set models on table views and update view columns
        self.ts_datasources = ts_datasources
        self.resultTableView.setModel(self.ts_datasources)
        self.ts_datasources.set_column_sizes_on_view(self.resultTableView)

        self.downloadable_results = downloadable_results
        self.download_proxy_model = QSortFilterProxyModel()
        self.download_proxy_model.setSourceModel(self.downloadable_results)
        self.download_proxy_model.setFilterCaseSensitivity(Qt.CaseInsensitive)
        self.filterLineEdit.textChanged.connect(
            self.download_proxy_model.setFilterFixedString)
        self.downloadResultTableView.setModel(self.download_proxy_model)

        self.toggle_login_interface()

        # connect signals
        self.selectTsDatasourceButton.clicked.connect(
            self.select_ts_datasource)
        self.closeButton.clicked.connect(self.close)
        self.removeTsDatasourceButton.clicked.connect(
            self.remove_selected_ts_datasource)
        self.selectModelSpatialiteButton.clicked.connect(
            self.select_model_spatialite_file)
        self.loginButton.clicked.connect(self.on_login_button_clicked)

        # set combobox list
        combo_list = [
            datasource for datasource in self.get_3di_spatialites_legendlist()
        ]

        if (self.ts_datasources.model_spatialite_filepath
                and self.ts_datasources.model_spatialite_filepath
                not in combo_list):
            combo_list.append(self.ts_datasources.model_spatialite_filepath)

        if not self.ts_datasources.model_spatialite_filepath:
            combo_list.append("")

        self.modelSpatialiteComboBox.addItems(combo_list)

        if self.ts_datasources.model_spatialite_filepath:
            current_index = self.modelSpatialiteComboBox.findText(
                self.ts_datasources.model_spatialite_filepath)

            self.modelSpatialiteComboBox.setCurrentIndex(current_index)
        else:
            current_index = self.modelSpatialiteComboBox.findText("")
            self.modelSpatialiteComboBox.setCurrentIndex(current_index)

        self.modelSpatialiteComboBox.currentIndexChanged.connect(
            self.model_spatialite_change)

        self.thread = None

    def on_close(self):
        """
        Clean object on close
        """
        self.selectTsDatasourceButton.clicked.disconnect(
            self.select_ts_datasource)
        self.closeButton.clicked.disconnect(self.close)
        self.removeTsDatasourceButton.clicked.disconnect(
            self.remove_selected_ts_datasource)
        self.selectModelSpatialiteButton.clicked.disconnect(
            self.select_model_spatialite_file)
        self.loginButton.clicked.disconnect(self.on_login_button_clicked)

        # stop the thread when we close the widget
        if self.thread:
            self.thread.output.disconnect(self.update_download_result_model)
            self.thread.stop()

    def closeEvent(self, event):
        """
        Close widget, called by Qt on close
        :param event: QEvent, close event
        """
        self.closingDialog.emit()
        self.on_close()
        event.accept()

    def keyPressEvent(self, event):
        """Handle key press events on the widget."""
        # Close window if the Escape key is pressed
        if event.key() == Qt.Key_Escape:
            self.close()

    def select_ts_datasource(self):
        """
        Open File dialog for selecting netCDF result files, triggered by button
        :return: boolean, if file is selected
        """

        settings = QSettings("3di", "qgisplugin")

        try:
            init_path = settings.value("last_used_datasource_path", type=str)
        except TypeError:
            logger.debug(
                "Last used datasource path is no string, setting it to our home dir."
            )
            init_path = os.path.expanduser("~")

        filename, __ = QFileDialog.getOpenFileName(
            self,
            "Open resultaten file",
            init_path,
            "NetCDF (subgrid_map.nc results_3di.nc)",
        )

        if filename:
            # Little test for checking if there is an id mapping file available
            # If not we check if an .h5 file is available
            # If not we're not going to proceed

            datasource_type = detect_netcdf_version(filename)
            logger.info("Netcdf result file selected: %s, type is %s",
                        filename, datasource_type)
            if datasource_type == "netcdf-groundwater":
                try:
                    find_h5_file(filename)
                except FileNotFoundError:
                    logger.warning(
                        "Groundwater h5 not found (%s), warning the user.",
                        filename)
                    pop_up_info(
                        "You selected a netcdf that was created "
                        "(after May 2018) with a 3Di calculation"
                        "core that is able to include groundwater"
                        " calculations. The ThreeDiToolbox reads "
                        "this netcdf together with an .h5 file, we "
                        "could however not find this .h5 file. Please "
                        "add this file next to the netcdf and try "
                        "again",
                        title="Error",
                    )
                    return False
            elif datasource_type == "netcdf":
                logger.warning(
                    "Result file (%s) version is too old. Warning the user.",
                    filename)
                pop_up_info(
                    "The selected result data is too old and no longer "
                    "supported in this version of ThreediToolbox. Please "
                    "recalculate the results with a newer version of the "
                    "threedicore or use the ThreediToolbox plugin for QGIS 2",
                    title="Error",
                )
                # TODO: the below "return False" was previously missing. Check
                # if Reinout was right in adding it :-)
                return False

            items = [{
                "type": datasource_type,
                "name": os.path.basename(filename).lower().rstrip(".nc"),
                "file_path": filename,
            }]
            self.ts_datasources.insertRows(items)
            settings.setValue("last_used_datasource_path",
                              os.path.dirname(filename))
            return True
        return False

    def remove_selected_ts_datasource(self):
        """
        Remove selected result files from model, called by 'remove' button
        """

        selection_model = self.resultTableView.selectionModel()
        # get unique rows in selected fields
        rows = set(
            [index.row() for index in selection_model.selectedIndexes()])
        for row in reversed(sorted(rows)):
            self.ts_datasources.removeRows(row, 1)

    def get_3di_spatialites_legendlist(self):
        """
        Get list of spatialite data sources currently active in canvas
        :return: list of strings, unique spatialite paths
        """

        tdi_spatialites = []
        for layer in self.iface.layerTreeView().selectedLayers():
            if (layer.name() in list(LAYER_QH_TYPE_MAPPING.keys())
                    and layer.dataProvider().name() == "spatialite"):
                source = layer.dataProvider().dataSourceUri().split("'")[1]
                if source not in tdi_spatialites:
                    tdi_spatialites.append(source)

        return tdi_spatialites

    def model_spatialite_change(self, nr):
        """
        Change active modelsource. Called by combobox when selected
        spatialite changed
        :param nr: integer, nr of item selected in combobox
        """
        filepath = self.modelSpatialiteComboBox.currentText()
        logger.info("Different spatialite 3di model selected: %s", filepath)
        self.ts_datasources.model_spatialite_filepath = filepath
        # Just emitting some dummy model indices cuz what else can we do, there
        # is no corresponding rows/columns that's been changed
        self.ts_datasources.dataChanged.emit(QModelIndex(), QModelIndex())

    def select_model_spatialite_file(self):
        """
        Open file dialog on click on button 'load model'
        :return: Boolean, if file is selected
        """

        settings = QSettings("3di", "qgisplugin")

        try:
            init_path = settings.value("last_used_spatialite_path", type=str)
        except TypeError:
            logger.debug(
                "Last used datasource path is no string, setting it to our home dir."
            )
            init_path = os.path.expanduser("~")

        filepath, __ = QFileDialog.getOpenFileName(
            self, "Open 3Di model spatialite file", init_path,
            "Spatialite (*.sqlite)")

        if filepath == "":
            return False

        self.ts_datasources.spatialite_filepath = filepath
        index_nr = self.modelSpatialiteComboBox.findText(filepath)
        if index_nr < 0:
            self.modelSpatialiteComboBox.addItem(filepath)
            index_nr = self.modelSpatialiteComboBox.findText(filepath)

        self.modelSpatialiteComboBox.setCurrentIndex(index_nr)

        add_spatialite_connection(filepath, self.iface)
        settings.setValue("last_used_spatialite_path",
                          os.path.dirname(filepath))
        return True

    def on_login_button_clicked(self):
        """Handle log in and out."""
        if self.logged_in:
            self.handle_log_out()
        else:
            self.login_dialog.user_name_input.setFocus()
            self.login_dialog.show()

    def handle_log_out(self):
        self.set_logged_out_status()
        if self.thread:
            self.thread.stop()
        num_rows = len(self.downloadable_results.rows)
        self.downloadable_results.removeRows(0, num_rows)
        self.toggle_login_interface()

    def toggle_login_interface(self):
        """Enable/disable aspects of the interface based on login status."""
        # TODO: better to use signals maybe?
        if self.logged_in:
            self.loginButton.setText("Log out")
            self.downloadResultTableView.setEnabled(True)
            self.downloadResultButton.setEnabled(True)
        else:
            self.loginButton.setText("Log in")
            self.downloadResultTableView.setEnabled(False)
            self.downloadResultButton.setEnabled(False)

    def handle_log_in(self):
        """Handle logging in and populating DownloadableResultModel."""
        # Get the username and password
        username = self.login_dialog.user_name_input.text()
        password = self.login_dialog.user_password_input.text()

        if username == "" or password == "":
            pop_up_info("Username or password cannot be empty.")
            return

        try:
            scenarios_endpoint = Endpoint(username=username,
                                          password=password,
                                          endpoint="scenarios")
            endpoint = scenarios_endpoint.get_paginated(page_size=10)
        except HTTPError as e:
            logger.exception("Error trying to log in")
            if e.code == 401:
                pop_up_info("Incorrect username and/or password.")
            else:
                pop_up_info(str(e))
            return

        self.set_logged_in_status(username, password)
        self.toggle_login_interface()
        # don't persist info in the dialog: useful when logged out
        self.login_dialog.user_name_input.clear()
        self.login_dialog.user_password_input.clear()

        # start thread
        self.thread = DownloadWorker(endpoint=endpoint,
                                     username=username,
                                     password=password)
        self.thread.connection_failure.connect(self.handle_connection_failure)
        self.thread.output.connect(self.update_download_result_model)
        self.thread.start()

        # return to widget
        self.login_dialog.close()

    def update_download_result_model(self, items):
        self.downloadable_results.insertRows(items)

    def handle_connection_failure(self, status, reason):
        pop_up_info("Something went wrong trying to connect to "
                    "lizard: {0} {1}".format(status, reason))
        self.handle_log_out()

    @property
    def username(self):
        return self.tool.username

    @username.setter
    def username(self, username):
        self.tool.username = username

    @property
    def password(self):
        return self.tool.password

    @password.setter
    def password(self, password):
        self.tool.password = password

    @property
    def logged_in(self):
        """Return the logged in status."""
        return self.tool.logged_in

    def set_logged_in_status(self, username, password):
        """Set logged in status to True."""
        # TODO: it doesn't really do that. I *suspect* that the parent class
        # has a logged_in() method that checks if username and password are
        # set there. It's all quite indirect.
        self.username = username
        self.password = password

    def set_logged_out_status(self):
        """Set logged in status to False."""
        self.username = None
        self.password = None