Пример #1
0
class PropertyImportDelegate(QStyledItemDelegate):
    """
    Derived delegate class for drawing the UnitConstructionModel
    """
    def __init__(self, only_numbers: bool = False, *args, **kwargs):
        """
        Initialize the object
        :param only_numbers: show only number rows and don't display type column
        :param args: arguments for initialization of the base class
        :param kwargs: arguments for initialization of the base class
        """
        super().__init__(*args, **kwargs)
        self.__only_numbers = only_numbers
        self.logger = QGISLogHandler(self.__class__.__name__)

    def createEditor(self, parent: QWidget, option: QStyleOptionViewItem,
                     index: QModelIndex) -> QWidget:
        """
        Creates an editor widget for the given index. Derived function.
        :param parent: parent QWidget
        :param option: QStyleOptionViewItem
        :param index: model index for editor creation
        :return: QWidget which represents the editor for the given model index
        """
        if index.isValid():
            if index.column() == 1 and not self.__only_numbers:
                combobox = QComboBox(parent)
                combobox.addItems(["Integer", "Float", "String"])
                combobox.setFocusPolicy(Qt.StrongFocus)
                return combobox
        return super().createEditor(parent, option, index)

    def setEditorData(self, editor: QComboBox, index: QModelIndex) -> None:
        """
        sets the data to the given editor widget based on the model index. Derived function.
        :param editor: editor widget for which the data has to be set (only type; column == 1)
        :param index: model index from which the editor data has to be set
        :return: Nothing
        """
        if index.isValid() and index.column() == 1 and not self.__only_numbers:
            if index.data() != "":
                editor.setCurrentText(index.data())
            else:
                editor.setCurrentText("String")
            return
        super().setEditorData(editor, index)

    def setModelData(self, editor: QComboBox, model: QAbstractTableModel,
                     index: QModelIndex) -> None:
        """
        Update the model data at the given index from the editor value. Derived function.
        :param editor: data provider
        :param model: data storage
        :param index: index where data has to be updated
        :return: Nothing
        """
        self.logger.debug("Updating model data for index [{}, {}]: {}".format(
            index.column(), index.row(), editor.currentText()))
        if index.isValid() and index.column(
        ) == 1 and not self.__only_numbers:  # only type can is editable
            model.setData(index, editor.currentText())
            return
        super().setModelData(editor, model, index)

    def updateEditorGeometry(self, editor: QComboBox,
                             option: QStyleOptionViewItem,
                             index: QModelIndex) -> None:
        """
        update the editor geometry. Derived function.
        :param editor:
        :param option:
        :param index:
        :return: Nothing
        """
        # if index.isValid():
        #     if isinstance(editor, QCheckBox):
        #         pos_x = int(option.rect.x() + option.rect.width() / 2 - editor.sizeHint().width() / 2)
        #         pos_y = int(option.rect.y() + option.rect.height() / 2 - editor.sizeHint().height() / 2)
        #         editor.setGeometry(QRect(pos_x, pos_y, editor.sizeHint().width(), editor.sizeHint().height()))
        #         return
        #     if index.column() == 4:
        #         editor.setGeometry(option.rect)
        super().updateEditorGeometry(editor, option, index)

    def paint(self, painter: QPainter, option: QStyleOptionViewItem,
              index: QModelIndex) -> None:
        """
        paint event for drawing the model data
        :param painter: QPainter for the drawing
        :param option: QStyleOptionViewItem
        :param index: model index to be drawn
        :return: Nothing
        """
        index_data = index.data()

        # set the distance label
        if isinstance(index_data, int):
            text = "{:,} m".format(index_data).replace(',', ' ')
            rect = QRectF(option.rect)
            rect.setWidth(rect.width() - 5)
            painter.drawText(rect, Qt.AlignRight | Qt.AlignVCenter, text)

        else:
            super().paint(painter, option, index)

    def sizeHint(self, option: QStyleOptionViewItem,
                 index: QModelIndex) -> QSize:
        """
        Returns a size hint for the object at the given index
        :param option: QStyleOptionViewItem
        :param index: model index for the requested size hint
        :return: a QSize object with given hint
        """
        # if isinstance(index.data(), bool):
        #     return self.__checkbox_size
        # if isinstance(index.data(), QColor):
        #     return self.__color_size
        # if isinstance(index.data(), int):
        #     self.__label_tmp.setText("{:,} m".format(index.data()).replace(',', ' '))
        #     size = self.__label_tmp.sizeHint()
        #     size.setWidth(size.width() + 5)
        #     return size
        return super().sizeHint(option, index)
Пример #2
0
class PropertyImportModel(QAbstractTableModel):
    """
    Derived Table Model for the storage of UnitConstructionData
    """
    def __init__(self,
                 only_numbers: bool = False,
                 parent: QWidget = None,
                 *args) -> None:
        """
        Initialize the object
        :param only_numbers: show only number rows and don't display type column
        """
        # noinspection PyArgumentList
        QAbstractTableModel.__init__(self, parent, *args)
        self.__only_numbers = only_numbers
        self.__data_list: List[PropertyImportData] = list()
        self.__header_labels = ["Property"
                                ] if only_numbers else ["Property", "Type"]
        self.logger = QGISLogHandler(self.__class__.__name__)

    # noinspection PyMethodOverriding
    def add(self, data: PropertyImportData) -> bool:
        """
        adds a new row at the end of the model.
        :param row: row index where to insert the new row
        :param data: data to be insert
        :return: True, if the insert was performed successfully, else False
        """
        self.beginInsertRows(QModelIndex(),
                             self.rowCount() - 1,
                             self.rowCount() - 1)
        self.__data_list.append(data)
        self.endInsertRows()
        return True

    def clear(self) -> None:
        """
        Removes all rows.
        :return: Nothing
        """
        self.beginRemoveRows(QModelIndex(), 0, self.rowCount() - 1)
        self.__data_list = list()
        self.endRemoveRows()

    def columnCount(self, parent: QModelIndex = ...) -> int:
        """
        returns the current column count of the table model
        :param parent: redundant parameter as this derived class isn't a tree model
        :return: returns the current column count of the table model
        """
        return len(self.__header_labels)

    # noinspection PyMethodOverriding
    def data(self, index: QModelIndex, role):
        """
        returns the data at the given index and the given role. Derived function.
        :param index: index of the requested data
        :param role: role of the requested data
        :return: returns the data at the given index and the given role
        """
        if not index.isValid():
            return QVariant()
        elif role in (Qt.DisplayRole, Qt.EditRole):
            return QVariant(self.__data_list[index.row()][index.column()])
        elif index.column() == 0 and role == Qt.TextAlignmentRole:
            return Qt.AlignLeft
        elif index.column(
        ) == 1 and role == Qt.TextAlignmentRole and not self.__only_numbers:
            return Qt.AlignRight
        return QVariant()

    # noinspection PyTypeChecker
    def flags(self, index: QModelIndex) -> Qt.ItemFlags:
        """
        Set the editable flag for the given model index. Derived function
        :param index: model index for which the flags are requested
        :return: Qt.ItemFlags
        """
        if not index.isValid():
            return Qt.ItemIsEnabled

        if index.column() == 1 and not self.__only_numbers:
            return Qt.ItemIsEditable | super(QAbstractTableModel,
                                             self).flags(index)

        return super(QAbstractTableModel, self).flags(index)

    def headerData(self,
                   section: int,
                   orientation: Qt.Orientation,
                   role: int = Qt.DisplayRole) -> str:
        """
        Derived functions which returns the header data for the given section, orientation and role
        :param section: section of the requested header data
        :param orientation: orientation of the requested header data
        :param role: role of the requested header data
        :return: returns the header data for the given section, orientation and role
        """
        if role == Qt.DisplayRole and orientation == Qt.Horizontal:
            if -1 < section < len(self.__header_labels):
                return self.__header_labels[section]
        return super(QAbstractTableModel,
                     self).headerData(section, orientation, role)

    # noinspection PyMethodOverriding
    def insertRow(self, row: int, data: PropertyImportData) -> bool:
        """
        inserts a new row into the model. Derived and adapted function.
        :param row: row index where to insert the new row
        :param data: data to be insert
        :return: True, if the insert was performed successfully, else False
        """
        self.beginInsertRows(QModelIndex(), row, row)
        if row < 0:
            self.endInsertRows()
            return False
        self.__data_list.insert(row, data)
        self.endInsertRows()
        return True

    # noinspection PyMethodOverriding
    def removeRow(self, row: int) -> bool:
        """
        Removes the row at the given index "row".
        :param row: index of the row to be removed
        :return: True, if the row was removed successfully, else False
        """
        self.beginRemoveRows(QModelIndex(), row, row)
        if 0 <= row < self.rowCount():
            del self.__data_list[row]
            self.endRemoveRows()
            return True
        self.endRemoveRows()
        return False

    def row(self, index: int) -> PropertyImportData or None:
        """
        returns PropertyImportData-item at given index
        :param index: index of requested PropertyImportData-item
        :return: returns the item at given index
        """
        if 0 <= index < self.rowCount():
            return self.__data_list[index]
        return None

    def rowCount(self, parent: QModelIndex = ...) -> int:
        """
        returns the current row count of the table model
        :param parent: redundant parameter as this derived class isn't a tree model
        :return: returns the current row count of the table model
        """
        return len(self.__data_list)

    def setData(self,
                index: QModelIndex,
                value: str,
                role: int = Qt.EditRole) -> bool:
        """
        Sets the current data at the given model index and role to value
        :param index: model index to be changed (only Type is editable, column 2)
        :param value: new value to be set
        :param role: role of data
        :return: True, if the data was set successfully, else False
        """
        if not index.isValid():
            return False
        if role == Qt.EditRole and index.column(
        ) == 1 and not self.__only_numbers:
            if str(value).lower() == "integer":
                self.__data_list[index.row()].property_type = PropertyTypes.INT
            elif str(value).lower() == "float":
                self.__data_list[
                    index.row()].property_type = PropertyTypes.FLOAT
            else:
                self.__data_list[
                    index.row()].property_type = PropertyTypes.STRING

            self.logger.debug("Setting data for index [{}, {}]: {}".format(
                index.column(), index.row(),
                self.__data_list[index.row()].property_type))
            # noinspection PyUnresolvedReferences
            self.dataChanged.emit(index, index, [Qt.EditRole])
        super().setData(index, value, role)
        return True
    def check_required_modules():
        """
        Check all module requirements
        :return: True is all modules with required versions were found, else false
        """
        logger = QGISLogHandler("ModuleService")

        # don't display logging to QGIS
        safed_iface = logger.qgis_iface
        logger.qgis_iface = None

        logger.info("Checking required modules")

        python = ModuleService.get_python()

        cmd = [python, "-m", "pip", "list", "--format", "json", "--user"]

        try:
            logger.info("Checking package versions")
            logger.info("CMD: {}".format(" ".join(cmd)))

            result = run(cmd, stdout=PIPE, stderr=STDOUT, check=True)
            logger.info("run pip info successful")
            packages = json.loads(result.stdout.decode())

        except CalledProcessError as e:
            # restore QGIS logging
            logger.qgis_iface = safed_iface
            logger.error("pip info request failed!")
            logger.error("RETURN-CODE: {} - CMD: {}".format(e.returncode, e.cmd))
            logger.error("OUTPUT: {}".format(e.output))
            return False

        ModuleService.modules = list()
        for module in module_list:
            module_found = False
            for package in packages:
                if package["name"] != module:
                    continue
                module_found = True

                logger.debug("found module {} [{}]".format(package["name"], package["version"]))

                v1 = version.parse(package["version"])
                v2 = version.parse(module_list[module])
                if v1 < v2:
                    logger.warn("Module version [{}] differs from required version [{}]".format(v1, v2))
                    ModuleService.modules.append("{}=={}".format(module, module_list[module]))

            if not module_found:
                logger.debug("Module not found, adding {}=={} to install list".format(module, module_list[module]))
                ModuleService.modules.append("{}=={}".format(module, module_list[module]))

        if len(ModuleService.modules) > 0:
            # restore QGIS logging
            logger.qgis_iface = safed_iface
            logger.info("Missing packages found: {}".format(ModuleService.modules))
            return False

        logger.info("All packages up to date")

        # restore QGIS logging
        logger.qgis_iface = safed_iface
        return True
Пример #4
0
class DatabaseController(QObject):
    """
    Controller class for database interaction
    """
    def __init__(self, settings: SettingsDialog = None) -> None:
        """
        Constructor
        :param settings: settings dialog
        :return: nothing
        """
        QObject.__init__(self)

        self.logger = QGISLogHandler(DatabaseController.__name__)

        self.__db_service = DatabaseService.get_instance()
        self.__last_db_settings = dict()

        self.__settings = None
        self.__config = ConfigHandler()
        self.settings = settings

        db_type = self.__config.get("General", "db_type")
        if db_type != "" and db_type in ["SQLite", "PostgreSQL"]:
            self.settings.DB_type.setCurrentText(db_type)
        else:
            db_type = "SQLite"
            self.__config.set("General", "db_type", db_type)
            self.settings.DB_type.setCurrentText(db_type)

        self.__on_db_type_changed(db_type)

    #
    # slots
    #

    def __on_db_type_changed(self, db_type: str):
        """
        Slot called, when the database type was changed to show / hide specific elements
        :param db_type: selected database type
        :return: nothing
        :raises ValueError: if type is unknown
        """
        self.logger.debug("Selecting new database type: {}".format(db_type))
        if db_type == "SQLite":
            self.settings.create_DB_button.show()
            self.settings.select_DB_button.show()
            self.settings.password_label.hide()
            self.settings.password.hide()
            self.settings.username_label.hide()
            self.settings.username.hide()
            self.settings.save_password.hide()

            tempdir = "/tmp" if platform.system(
            ) == "Darwin" else tempfile.gettempdir()
            filename = "geology.sqlite"
            self.settings.database_connection.setPlaceholderText(
                os.path.join(tempdir, filename))

        elif db_type == "PostgreSQL":
            self.settings.create_DB_button.hide()
            self.settings.select_DB_button.hide()
            self.settings.password_label.show()
            self.settings.password.show()
            self.settings.username_label.show()
            self.settings.username.show()
            if found_keyring:
                self.settings.save_password.show()
            else:
                self.settings.save_password.hide()

            self.settings.database_connection.setPlaceholderText(
                "localhost:5432/geology")
            self.settings.username.setPlaceholderText("postgres")
            self.settings.password.setPlaceholderText("")
        else:
            self.settings.database_connection.setText("")
            self.settings.username.setText("")
            self.settings.password.setText("")
            self.settings.database_connection.setPlaceholderText("")
            self.settings.username.setPlaceholderText("")
            self.settings.password.setPlaceholderText("")
            raise ValueError("Unknown DB Format: {}".format(db_type))

        if self.__config.has_section(db_type):
            self.settings.database_connection.setText(
                self.__config.get(db_type, "connection"))
            self.settings.username.setText(
                self.__config.get(db_type, "username"))
            self.settings.password.setText("")

        else:
            self.settings.database_connection.setText("")
            self.settings.username.setText("")
            self.settings.password.setText("")

        self.__update_db_service()

    def __on_create_db_clicked(self) -> None:
        """
        slot for creating a new database
        :return: Nothing
        """

        self.__validate()
        # noinspection PyCallByClass,PyArgumentList
        filename = get_file_name(
            QFileDialog.getSaveFileName(
                parent=self.settings,
                caption="Select database file",
                directory="",
                filter="Databases(*.db *.sqlite *.data);;Any File Type (*)"))

        if filename != "":
            # noinspection PyTypeChecker
            if os.path.splitext(filename)[-1].lower().lstrip('.') not in [
                    "db", "data", "sqlite"
            ]:
                filename += ".data"
            self.settings.database_connection.setText(filename)

    def __on_select_db(self) -> None:
        """
        slot for selecting a sqlite database file and set the result to the related lineedit
        :return: Nothing
        """

        self.__validate()
        # noinspection PyCallByClass,PyArgumentList
        filename = get_file_name(
            QFileDialog.getOpenFileName(
                self.settings, "Select database file", "",
                "Databases(*.db *.sqlite *.data);;Any File Type (*)"))

        if filename != "":
            # noinspection PyTypeChecker
            if os.path.splitext(filename)[-1].lower().lstrip('.') not in [
                    "db", "data", "sqlite"
            ]:
                filename += ".data"
            self.settings.database_connection.setText(filename)

    def __on_check_connection(self):
        """
        Check the requested database connection
        :return: if the connection check was successful
        :raises ValueError: if database type is unknown
        """
        result = self.__db_service.check_connection()

        if result == "":
            self.logger.info("Connection test successful")
            return True

        else:
            self.logger.error("connection test failed", result)
            return False

    def __on_save(self):
        self.__last_db_settings = self.__get_db_settings()
        self.__update_db_service()
        if self.__on_check_connection():
            self.__update_config()
            self.settings.accept()
        else:
            self.__restore_db_settings(self.__last_db_settings)

    def __on_cancel(self):
        self.__on_db_type_changed(self.settings.DB_type.currentText())
        self.settings.reject()

    #
    # private functions
    #

    def __update_db_service(self, _: object = None) -> None:
        """
        Update the database service, if a GUI input element changed
        :param _: temporary parameter for QLineEdit update
        :return: Nothing
        """

        self.__db_service.db_type = self.settings.DB_type.currentText()
        self.__db_service.connection = self.settings.database_connection.text()
        self.__db_service.username = self.settings.username.text()
        self.__db_service.password = self.settings.password.text()

        if self.__db_service.connection == "":
            self.__db_service.connection = self.settings.database_connection.placeholderText(
            )
        if self.__db_service.username == "":
            self.__db_service.username = self.settings.username.placeholderText(
            )
        if self.__db_service.password == "" and found_keyring:
            # empty password ? try request from system keystore
            self.__db_service.password = keyring.get_password(
                "Postgres {}".format(self.__db_service.connection),
                self.__db_service.username)

        # self.logger.debug("Connection settings:\ndatabase:\t{}\nconnection:\t{}\nusername:\t{}\npassword:\t{}".format(
        #    self.__db_service.db_type, self.__db_service.connection,
        #    self.__db_service.username, self.__db_service.password))

    def __get_db_settings(self) -> Dict:
        """
        Returns the current database connection settings as dictionary
        :return: current database connection settings
        """
        return {
            "db": self.__db_service.db_type,
            "connection": self.__db_service.connection,
            "username": self.__db_service.username,
            "password": self.__db_service.password
        }

    def __restore_db_settings(self, values: Dict) -> None:
        """
        Restores the database connection settings
        :param values: dictionary with connection settings
        :return: Nothing
        """
        try:
            self.__db_service.db_type = values["db"]
            self.__db_service.connection = values["connection"]
            self.__db_service.username = values["username"]
            self.__db_service.password = values["password"]
        except KeyError as e:
            self.logger.error("Can't restore database settings: {}", str(e))

    def __update_config(self):
        db_type = self.settings.DB_type.currentText()

        if db_type == "PostgreSQL":
            self.__config.set("PostgreSQL", "connection",
                              self.__db_service.connection)
            self.__config.set("PostgreSQL", "username",
                              self.__db_service.username)

            if self.settings.save_password.isChecked() and found_keyring:
                keyring.set_password(
                    "Postgres {}".format(self.__db_service.connection),
                    self.__db_service.username, self.__db_service.password)

        elif db_type == "SQLite":
            self.__config.set("SQLite", "connection",
                              self.__db_service.connection)

        if db_type in ["PostgreSQL", "SQLite"]:
            self.__config.set("General", "db_type", db_type)
            self.__on_db_type_changed(self.__config.get("General", "db_type"))

    def __validate(self):
        """
        Validates, if the service can be executed
        :return: Nothing
        :raises
        """

        if self.settings is None:
            raise AttributeError("No settings dialog is set")

        if self.__config is None:
            raise AttributeError("No config is set")

    #
    # setter and getter
    #

    @property
    def settings(self) -> SettingsDialog:
        """
        Returns the currently active settings dialog
        :return: returns the currently active settings dialog
        """
        return self.__settings

    @settings.setter
    def settings(self, value: SettingsDialog) -> None:
        """
        Sets the currently active settings dialog
        :return: returns the currently active settings dialog
        :raises TypeError: if value is not of type SettingsDialog
        """

        if isinstance(value, SettingsDialog):
            if self.__settings is not None:
                self.__settings.create_DB_button.clicked.disconnect(
                    self.__on_create_db_clicked)
                self.__settings.select_DB_button.clicked.disconnect(
                    self.__on_select_db)
                self.__settings.DB_type.currentIndexChanged[str].disconnect(
                    self.__on_db_type_changed)
                self.__settings.save_button.clicked.disconnect(self.__on_save)
                self.__settings.cancel_button.clicked.disconnect(
                    self.__on_cancel)

            self.__settings = value

            self.__settings.create_DB_button.clicked.connect(
                self.__on_create_db_clicked)
            self.__settings.select_DB_button.clicked.connect(
                self.__on_select_db)
            self.__settings.DB_type.currentIndexChanged[str].connect(
                self.__on_db_type_changed)
            self.__settings.save_button.clicked.connect(self.__on_save)
            self.__settings.cancel_button.clicked.connect(self.__on_cancel)

        else:
            raise TypeError(
                "committed parameter is not of type SettingsDialog")
Пример #5
0
class ImportViewInterface(QObject):
    """
    interface class defining signals and slots for all import views
    """

    def __init__(self, dock_widget: GeologicalDataProcessingDockWidget) -> None:
        """
        Initialize the view
        :param dock_widget: current GeologicalDataProcessingDockWidget instance
        """

        self.logger = QGISLogHandler(self.__class__.__name__)
        self.__combos = dict()
        self._dwg = dock_widget

        # initialize user interface
        self._import_service = ImportService.get_instance()
        self._import_service.reset_import.connect(self.reset_import)
        self._import_service.import_columns_changed.connect(self._on_import_columns_changed)

        self._table_view: QTableView or None = None
        self._only_number_in_table_view: bool = False
        self._table_model: PropertyImportModel = PropertyImportModel()
        self._dwg.start_import_button.clicked.connect(self._on_start_import)
        self._controller_thread: ImportControllersInterface or None = None

        super().__init__()

    def _connect_combo_listener(self):
        """
        connects all combobox elements to the on_selection_changed slot
        :return: Nothing
        """
        [combo.currentIndexChanged.connect(self.on_selection_changed) for combo in self.__combos]

    @property
    def combobox_names(self) -> Dict:
        """
        Returns a dictionary of comboboxes for the current view
        :return: returns a dictionary of comboboxes for the current view
        """
        return self.__combos

    @combobox_names.setter
    def combobox_names(self, combo_dict: Dict) -> None:
        """
        Sets a new dictionary to the combobox list. Disconnects the old ones and connects the new to the
        on_selection_changed slot
        :param combo_dict: dictionary with new combobox elements
        :return: Nothing
        :raises TypeError: if a dictionary value is not an instance of QComboBox or a key is not a str. Sets an empty
        dictionary instead
        """

        self._disconnect_selection_changed()

        for key in combo_dict:
            if not isinstance(key, str):
                self.__combos = dict()
                raise TypeError("{} is not a string".format(str(key)))
            if not isinstance(combo_dict[key], QComboBox):
                self.__combos = dict()
                raise TypeError("{} is not an instance of QComboBox".format(str(key)))

        self.__combos = combo_dict
        self._connect_selection_changed()

    @property
    def table_view(self) -> QTableView or None:
        return self._table_view

    @table_view.setter
    def table_view(self, widget: QTableView) -> None:
        if not isinstance(widget, QTableView):
            raise TypeError("submitted object is not of type QTableView: {}", str(widget))

        self._table_model.clear()
        self._table_view = widget
        self._table_view.setModel(self._table_model)

        if self._only_number_in_table_view:
            self._table_view.setItemDelegate(LogImportDelegate())
        else:
            self._table_view.setItemDelegate(PropertyImportDelegate())
        # self._table_view.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
        # self._table_view.horizontalHeader().setSectionResizeMode(0, QHeaderView.Stretch)
        self._table_view.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

    @property
    def dockwidget(self) -> GeologicalDataProcessingDockWidget:
        """
        Returns the current dockwidget
        :return: the current dockwidget
        """
        return self._dwg

    #
    # signals
    #

    selection_changed = pyqtSignal(list)
    """data changed signal which gives the index or name of the changed column and the newly selected text"""

    start_import = pyqtSignal()
    """signal to start the data import through an ImportController Thread"""

    #
    # slots
    #

    def on_selection_changed(self, _=None) -> None:
        """
        Emits the combobox_changed signal with a list of changed text
        :return: Nothing
        """
        selection_list = [self.combobox_names[key].currentText() for key in self.combobox_names]
        self.selection_changed.emit(selection_list)

        if self._table_view is None:
            self.logger.debug("No list widget specified for additional columns")
            return

        if self._only_number_in_table_view:
            cols = diff(self._import_service.number_columns, selection_list)
        else:
            cols = diff(self._import_service.selectable_columns, selection_list)

        self.logger.debug("selectable_columns: " + str(self._import_service.selectable_columns))
        self.logger.debug("selected_cols: " + str(selection_list))
        self.logger.debug("additional cols: " + str(cols))

        if self._table_view is not None:
            self._table_view.show()
            self._table_view.setEnabled(True)
            self._table_view.clearSelection()

        self._table_model.clear()
        for col in cols:
            property_type = PropertyTypes.FLOAT if col in self._import_service.number_columns else PropertyTypes.STRING
            self._table_model.add(PropertyImportData(name=col[0], unit=col[1], property_type=property_type))

    def _on_import_columns_changed(self) -> None:
        """
        change the import columns
        :return: Nothing
        """
        self.logger.debug("(Interface) _on_import_columns_changed")
        self._connect_selection_changed()
        self.on_selection_changed()

    def _on_start_import(self) -> None:
        self.logger.debug("(Interface) _on_start_import")
        self._update_progress_bar(0)
        self._dwg.progress_bar_layout.setVisible(True)

    def _on_import_failed(self, msg: str) -> None:
        self.logger.debug("(Interface) _on_import_failed")
        self.__import_finished()
        self.logger.error("Import failed", msg, to_messagebar=True)

    def _on_import_finished_with_warnings(self, msg: str) -> None:
        self.logger.debug("(Interface) _on_import_finished_with_warnings")
        self.logger.warn("Import finished with warnings", msg, to_messagebar=True)
        self.__import_finished()

    def _on_import_successful(self):
        self.logger.debug("(Interface) _on_import_successful")
        self.logger.info("Import successful", to_messagebar=True)
        self.__import_finished()

    def _on_cancel_import(self):
        self._controller_thread.cancel_import("Import canceled by user")

    def __import_finished(self):
        self._dwg.progress_bar_layout.setVisible(False)
        self._disconnect_thread()

        self._controller_thread.wait(2000)
        self._controller_thread = None

    #
    # public functions
    #

    def combobox_data(self, index: int or str) -> str:
        """
        Returns the currently selected item of the gui element with the given index
        :param index: index of the requested gui element as integer or string
        :return: Returns the data at the given index
        :raises IndexError: if index is not part in the available list
        """

        if isinstance(index, int):
            index = [self.combobox_names.keys()][index]
        else:
            index = str(index)

        if index not in self.combobox_names:
            raise IndexError("{} is not available".format(index))

        return self.combobox_names[index].currentText()

    def get_name(self, index: int) -> str or None:
        """
        Returns the name of the combobox with the given index
        :param index: index of the requested combobox
        :return: Returns the name of the combobox with the given index
        :raises IndexError: if the requested index is not in the list
        :raises ValueError: if the index is not convertible to an integer
        """
        index = int(index)

        if 0 <= index < len(self.combobox_names.keys()):
            return list(self.combobox_names.keys())[0]

    def get_names(self):
        """
        Returns a list of the combobox names
        :return: Returns a list of the combobox names
        """
        return list(self.combobox_names.keys())

    def set_combobox_data(self, index: int or str, values: List[str], default_index: int = 0) -> None:
        """
        Sets the committed values list to the gui combobox elements for the given index
        :param index: index of the requested gui element as integer or string
        :param values: new values for the combo boxes as a list of strings
        :param default_index: default selected index. If no default value is given, or the index is not part of the
                              list, the first entry will be selected by default
        :return: Returns, if the data setting was successful
        :raises IndexError: if index is not part in the available list
        :raises TypeError: if default_index is not an instance of int
        """

        if isinstance(index, int):
            index = [self.combobox_names.keys()][index]
        else:
            index = str(index)

        if index not in self.combobox_names:
            raise IndexError("{} is not available".format(index))

        if not isinstance(default_index, int):
            raise TypeError("default_index({}) is not an instance of int!".format(default_index))

        self.combobox_names[index].clear()
        for item in values:
            self.combobox_names[index].addItem(str(item))

        if not (0 <= default_index <= len(values)):
            default_index = 0
        self.combobox_names[index].setCurrentIndex(default_index)

    def reset_import(self) -> None:
        """
        Clears all import combo boxes, in case of a failure
        :return: Nothing
        """
        [self.set_combobox_data(name, []) for name in self.get_names()]

    def get_property_columns(self) -> List[PropertyImportData]:
        selection = set([x.row() for x in self._table_view.selectedIndexes()])
        self.logger.debug("selected rows indices: {}".format(selection))
        erg = [self._table_model.row(x) for x in selection]
        self.logger.debug("Selection:")
        [self.logger.debug("\t{}".format(x)) for x in erg]
        return erg

    #
    # protected functions
    #

    def _update_progress_bar(self, value):
        """
        slot to set the current progress bar value
        :param value: value in percent
        :return: nothing
        """
        self.logger.debug("Update progressbar with value {} called".format(value))
        if value < 0:
            self.dockwidget.progress_bar.setValue(0)
        elif value > 100:
            self.dockwidget.progress_bar.setValue(100)
        else:
            self.dockwidget.progress_bar.setValue(int(value))

    def _connect_selection_changed(self):
        self.logger.debug("_connect_selection_changed")
        [self.__combos[key].currentTextChanged.connect(self.on_selection_changed) for key in self.__combos]

    def _disconnect_selection_changed(self):
        for key in self.__combos:
            try:
                self.__combos[key].currentTextChanged.disconnect(self.on_selection_changed)
            except TypeError:
                # not connected
                pass

    def _connect_thread(self):
        self._controller_thread.import_finished.connect(self._on_import_successful)
        self._controller_thread.import_failed.connect(self._on_import_failed)
        self._controller_thread.import_finished_with_warnings.connect(self._on_import_finished_with_warnings)
        self._controller_thread.update_progress.connect(self._update_progress_bar)
        self._dwg.cancel_import.clicked.connect(self._on_cancel_import)

    def _disconnect_thread(self):
        self._controller_thread.import_finished.disconnect(self._on_import_successful)
        self._controller_thread.import_failed.disconnect(self._on_import_failed)
        self._controller_thread.import_finished_with_warnings.disconnect(self._on_import_finished_with_warnings)
        self._controller_thread.update_progress.disconnect(self._update_progress_bar)
        self._dwg.cancel_import.clicked.disconnect(self._on_cancel_import)
Пример #6
0
class ImportControllersInterface(QThread):
    """
    Basic interface for all import_tests controller
    """

    def __init__(self, data: Dict, selection: Dict, properties: List[PropertyImportData]) -> None:
        """
        :param data: import data parsed from the file to import
        :param selection: dictionary of selected columns
        """
        super().__init__()

        self._logger = QGISLogHandler(self.__class__.__name__)
        self._data: Dict = data
        self._selection: Dict = selection
        self._properties: List[PropertyImportData] = properties
        self._mutex = QMutex()
        self._cancel = False
        self._message = ""

    def run(self) -> None:
        """
        Thread execution function to import data
        :return: Nothing
        """
        pass

    #
    # signals
    #

    update_progress = pyqtSignal(int)
    """update progress bar signal. Committed value has to be between 0 and 100"""

    import_finished = pyqtSignal()
    """signal emitted, when the import process has finished"""

    import_finished_with_warnings = pyqtSignal(str)
    """signal emitted, when the import process has finished with warnings"""

    import_failed = pyqtSignal(str)
    """signal emitted, when the import process was canceled or failed through a call of the cancel_import slot"""

    #
    # slots
    #

    def cancel_import(self, msg: str = "") -> None:
        """
        slot for canceling the import process. A trigger variable will hint the importer to stop at the next
        possibility. This should ensure the finalization of all started write processes and therefore the integrity of
        all database objects.

        The :func:`~GeologicalDataProcessing.controller.import_controller.ImportControllersInterface.import_cancelled`
        signal will be sent, if the import process was successfully cancelled.
        :return: Nothing
        """
        self._cancel = True
        if msg == "":
            self._message = "Import canceled"
        else:
            self._message = msg

    def _import_done(self, future: Future = None) -> None:
        """
        function called, when import is done or canceled
        :param future: import executing future object
        :return: nothing
        """
        self._logger.debug("import done")

        self._view.dockwidget.progress_bar_layout.setVisible(False)
        self._view.dockwidget.cancel_import.clicked.disconnect(self._stop_import)
        if (future is not None) and future.cancelled():
            self._logger.warn("future run finished, import cancelled!")
        elif future is not None:
            self._logger.info("future run finished, import successful")

        self._logger.info("QThread finished")
        if self.__thread is not None:
            self._logger.debug("waiting for the end...")
            self.__thread.wait()
            self._logger.debug("at the end...")
            self.__thread = None
    def run(self) -> None:
        """Run method that loads and starts the plugin"""

        if not self.pluginIsActive:
            self.pluginIsActive = True

            try:
                # initialize logger
                logger = QGISLogHandler()
                logger.qgis_iface = self.iface
                logger.save_to_file = True

                if packages_found == "NO_PACKAGES" or not ModuleService.check_required_modules():
                    logger.info("installing or updating packages")
                    if not ModuleService.install_packages():
                        logger.error("package installation failed, please restart QGIS to try again.")
                    else:
                        logger.info("package installation successful, please restart QGIS")
                        msg = QMessageBox()
                        msg.setIcon(QMessageBox.Information)
                        msg.setText("packages installation successful")
                        msg.setInformativeText("Please restart QGIS to use the GeologicalDataProcessing extension")

                        msg.setWindowTitle("package update")
                        msg.exec_()
                        return

                    return
                else:
                    logger.debug("all required packages up2date")

                # dockwidget may not exist if:
                #    first run of plugin
                #    removed on close (see self.onClosePlugin method)
                if self.dockwidget is None:
                    # Create the dockwidget (after translation) and keep reference
                    self.dockwidget = GeologicalDataProcessingDockWidget()

                if self.settings_dialog is None:
                    self.settings_dialog = SettingsDialog(parent=self.dockwidget)
                    self.settings_dialog.setModal(True)
                    self.dockwidget.settings_button.clicked.connect(self.settings_dialog.exec)

                # connect to provide cleanup on closing of dockwidget
                self.dockwidget.closingPlugin.connect(self.onClosePlugin)

                # show the dockwidget
                # TODO: fix to allow choice of dock location
                self.iface.addDockWidget(Qt.RightDockWidgetArea, self.dockwidget)
                self.dockwidget.show()

                from GeologicalDataProcessing.controller.database_controller import DatabaseController
                from GeologicalDataProcessing.services.import_service import ImportService
                from GeologicalDataProcessing.views.import_views import LineImportView, PointImportView, \
                    WellImportView, PropertyImportView, WellLogImportView

                ImportService.get_instance(self.dockwidget)

                # initialize the gui and connect signals and slots
                self.dockwidget.import_type.currentChanged.connect(self.on_import_type_changed_event)

                # start tests button
                # -> only visible and active when the debug flag is True
                if config.debug:
                    self.dockwidget.start_tests_button.clicked.connect(self.on_start_tests)
                else:
                    self.dockwidget.start_tests_button.setVisible(False)
                    self.dockwidget.start_tests_separator.setVisible(False)

                self.dockwidget.progress_bar_layout.setVisible(False)

                self.__views["import_points"] = PointImportView(self.dockwidget)
                self.__views["import_lines"] = LineImportView(self.dockwidget)
                self.__views["import_wells"] = WellImportView(self.dockwidget)
                self.__views["import_properties"] = PropertyImportView(self.dockwidget)
                self.__views["import_well_logs"] = WellLogImportView(self.dockwidget)

                self.__db_controller = DatabaseController(self.settings_dialog)

                if config.debug:
                    self.dockwidget.import_file.setText(
                        "/Users/stephan/Library/Application Support/QGIS/QGIS3/profiles/" +
                        "default/python/plugins/GeologicalDataProcessing/tests/test_data/point_data.txt")

            except Exception as e:
                ExceptionHandler(e).log()