Esempio n. 1
0
class WizardMessagesManager:
    def __init__(self, wizard_tool_name, editing_layer_name):
        self.__WIZARD_TOOL_NAME = wizard_tool_name
        self.__logger = Logger()
        self.__editing_layer_name = editing_layer_name

    def show_wizard_closed_msg(self):
        message = QCoreApplication.translate(
            "WizardTranslations",
            "'{}' tool has been closed.").format(self.__WIZARD_TOOL_NAME)
        self.__logger.info_msg(__name__, message)

    def show_form_closed_msg(self):
        message = QCoreApplication.translate(
            "WizardTranslations", "'{}' tool has been closed because you just closed the form.")\
            .format(self.__WIZARD_TOOL_NAME)
        self.__logger.info_msg(__name__, message)

    def show_map_tool_changed_msg(self):
        message = QCoreApplication.translate(
            "WizardTranslations", "'{}' tool has been closed because the map tool change.")\
            .format(self.__WIZARD_TOOL_NAME)
        self.__logger.info_msg(__name__, message)

    def show_layer_removed_msg(self):
        message = QCoreApplication.translate(
            "WizardTranslations", "'{}' tool has been closed because you just removed a required layer.")\
            .format(self.__WIZARD_TOOL_NAME)
        self.__logger.info_msg(__name__, message)

    def show_feature_successfully_created_msg(self, feature_name, feature_id):
        message = QCoreApplication.translate(
                "WizardTranslations", "The new {} (t_id={}) was successfully created ")\
                .format(feature_name, feature_id)

        self.__logger.info_msg(__name__, message)

    def show_feature_not_found_in_layer_msg(self):
        message = QCoreApplication.translate(
            "WizardTranslations",
            "'{}' tool has been closed. Feature not found in layer {}... It's not possible create it.") \
            .format(self.__WIZARD_TOOL_NAME, self.__editing_layer_name)

        self.__logger.info_msg(__name__, message)

    def show_feature_not_found_in_layer_warning(self):
        self.__logger.warning(
            __name__, "Feature not found in layer {} ...".format(
                self.__editing_layer_name))

    def show_select_a_source_layer_warning(self):
        message = QCoreApplication.translate(
            "WizardTranslations", "Select a source layer to set the field mapping to '{}'.") \
            .format(self.__editing_layer_name)
        self.__logger.warning_msg(__name__, message)
class BaseConfigureReceiversPanelWidget(QgsPanelWidget, WIDGET_UI):
    clear_message_bar_requested = pyqtSignal()

    def __init__(self, parent, controller):
        QgsPanelWidget.__init__(self, parent)
        self.setupUi(self)
        self.parent = parent
        self.logger = Logger()
        self._controller = controller

        self.setDockMode(True)
        self.setPanelTitle(
            QCoreApplication.translate("BaseConfigureReceiversPanelWidget",
                                       "Configure receivers"))
        self.panelAccepted.connect(self.panel_accepted)
        self.tbl_receivers.itemSelectionChanged.connect(self.selection_changed)
        self.btn_save.clicked.connect(self.save_receiver)
        self.btn_delete.clicked.connect(self.delete_receiver)

        self.btn_delete.setEnabled(False)
        self.fill_data()
        self.tbl_receivers.resizeColumnsToContents()

    def panel_accepted(self):
        self.clear_message_bar_requested.emit()

    def fill_data(self):
        self.tbl_receivers.clearContents()
        receivers_data = self._controller.get_receivers_data()

        self.tbl_receivers.setRowCount(len(receivers_data))
        self.tbl_receivers.setSortingEnabled(False)

        for row, data in enumerate(receivers_data.items()):
            receiver_name, receiver_docid = data[1]
            self.fill_row(data[0], receiver_name, receiver_docid, row)

        self.tbl_receivers.setSortingEnabled(True)
        self.tbl_receivers.resizeColumnsToContents()

    def fill_row(self, receiver_t_id, receiver_name, receiver_docid, row):
        item = QTableWidgetItem(receiver_name)
        item.setData(Qt.UserRole, receiver_t_id)
        self.tbl_receivers.setItem(row, 0, item)

        item2 = QTableWidgetItem(receiver_docid)
        item2.setData(Qt.UserRole, receiver_t_id)
        self.tbl_receivers.setItem(row, 1, item2)

    def selection_changed(self):
        self.btn_delete.setEnabled(bool(self.tbl_receivers.selectedItems()))

    def save_receiver(self):
        if self.txt_first_name.text().strip(
        ) and self.txt_first_last_name.text().strip(
        ) and self.txt_document_id.text().strip():
            try:
                int(self.txt_document_id.text().strip())
            except ValueError as e:
                self.logger.warning_msg(
                    __name__,
                    QCoreApplication.translate(
                        "BaseConfigureReceiversPanelWidget",
                        "Invalid value for document id. Only digits are accepted."
                    ))
                return

            basket_t_id, msg = self._controller.get_basket_id_for_new_receiver(
            )
            if basket_t_id is None:
                self.logger.warning_msg(__name__, msg)
                return

            names = self._controller.db().names
            receiver_data = {
                names.FDC_USER_T_DOCUMENT_TYPE_F:
                self._controller.receiver_type,
                names.FDC_USER_T_DOCUMENT_ID_F:
                self.txt_document_id.text().strip(),
                names.FDC_USER_T_FIRST_NAME_F:
                self.txt_first_name.text().strip(),
                names.FDC_USER_T_SECOND_NAME_F:
                self.txt_second_name.text().strip(),
                names.FDC_USER_T_FIRST_LAST_NAME_F:
                self.txt_first_last_name.text().strip(),
                names.FDC_USER_T_SECOND_LAST_NAME_F:
                self.txt_second_last_name.text().strip(),
                names.T_ILI_TID_F:
                str(uuid.uuid4()),
                names.T_BASKET_F:
                basket_t_id
            }
            res = self._controller.save_receiver(receiver_data)
            if res:
                self.logger.success_msg(
                    __name__,
                    QCoreApplication.translate(
                        "BaseConfigureReceiversPanelWidget",
                        "Receiver saved!"))
                self.fill_data()
                self.initialize_input_controls()
            else:
                self.logger.warning_msg(
                    __name__,
                    QCoreApplication.translate(
                        "BaseConfigureReceiversPanelWidget",
                        "There was an error saving the receiver."))
        else:
            self.logger.warning_msg(
                __name__,
                QCoreApplication.translate(
                    "BaseConfigureReceiversPanelWidget",
                    "First name, last name and document id are mandatory."))

    def delete_receiver(self):
        selected_receiver_id = [
            item.data(Qt.UserRole)
            for item in self.tbl_receivers.selectedItems()
        ]
        if selected_receiver_id:
            res, msg = self._controller.delete_receiver(
                selected_receiver_id[0])
            if res:
                self.logger.success_msg(
                    __name__,
                    QCoreApplication.translate(
                        "BaseConfigureReceiversPanelWidget",
                        "Receiver deleted!"))
                self.fill_data()
            else:
                if not msg:
                    msg = QCoreApplication.translate(
                        "BaseConfigureReceiversPanelWidget",
                        "There was an error deleting the receiver.")
                self.logger.warning_msg(__name__, msg)

    def initialize_input_controls(self):
        self.txt_document_id.setText('')
        self.txt_first_name.setText('')
        self.txt_second_name.setText('')
        self.txt_first_last_name.setText('')
        self.txt_second_last_name.setText('')
Esempio n. 3
0
class ReportGenerator(QObject):
    LOG_TAB = 'LADM-COL Reports'

    enable_action_requested = pyqtSignal(str, bool)

    def __init__(self, ladm_data):
        QObject.__init__(self)
        self.ladm_data = ladm_data
        self.logger = Logger()
        self.app = AppInterface()
        self.java_dependency = JavaDependency()
        self.java_dependency.download_dependency_completed.connect(
            self.download_java_complete)

        self.report_dependency = ReportDependency()
        self.report_dependency.download_dependency_completed.connect(
            self.download_report_complete)

        self.encoding = locale.getlocale()[1]
        # This might be unset
        if not self.encoding:
            self.encoding = 'UTF8'

        self._downloading = False

    def stderr_ready(self, proc):
        text = bytes(proc.readAllStandardError()).decode(self.encoding)
        self.logger.critical(__name__, text, tab=self.LOG_TAB)

    def stdout_ready(self, proc):
        text = bytes(proc.readAllStandardOutput()).decode(self.encoding)
        self.logger.info(__name__, text, tab=self.LOG_TAB)

    def update_yaml_config(self, db, config_path):
        text = ''
        qgs_uri = QgsDataSourceUri(db.uri)

        with open(os.path.join(config_path, 'config_template.yaml')) as f:
            text = f.read()
            text = text.format('{}',
                               DB_USER=qgs_uri.username(),
                               DB_PASSWORD=qgs_uri.password(),
                               DB_HOST=qgs_uri.host(),
                               DB_PORT=qgs_uri.port(),
                               DB_NAME=qgs_uri.database())
        new_file_path = os.path.join(
            config_path, self.get_tmp_filename('yaml_config', 'yaml'))

        with open(new_file_path, 'w') as new_yaml:
            new_yaml.write(text)

        return new_file_path

    def get_layer_geojson(self, db, layer_name, plot_id, report_type):
        if report_type == ANNEX_17_REPORT:
            if layer_name == 'terreno':
                return db.get_annex17_plot_data(plot_id, 'only_id')
            elif layer_name == 'terrenos':
                return db.get_annex17_plot_data(plot_id, 'all_but_id')
            elif layer_name == 'terrenos_all':
                return db.get_annex17_plot_data(plot_id, 'all')
            elif layer_name == 'construcciones':
                return db.get_annex17_building_data()
            else:
                return db.get_annex17_point_data(plot_id)
        else:  #report_type == ANT_MAP_REPORT:
            if layer_name == 'terreno':
                return db.get_ant_map_plot_data(plot_id, 'only_id')
            elif layer_name == 'terrenos':
                return db.get_ant_map_plot_data(plot_id, 'all_but_id')
            elif layer_name == 'terrenos_all':
                return db.get_annex17_plot_data(plot_id, 'all')
            elif layer_name == 'construcciones':
                return db.get_annex17_building_data()
            elif layer_name == 'puntoLindero':
                return db.get_annex17_point_data(plot_id)
            else:  #layer_name == 'cambio_colindancia':
                return db.get_ant_map_neighbouring_change_data(plot_id)

    def update_json_data(self, db, json_spec_file, plot_id, tmp_dir,
                         report_type):
        json_data = dict()
        with open(json_spec_file) as f:
            json_data = json.load(f)

        json_data['attributes']['id'] = plot_id
        json_data['attributes']['datasetName'] = db.schema
        layers = json_data['attributes']['map']['layers']
        for layer in layers:
            layer['geoJson'] = self.get_layer_geojson(db, layer['name'],
                                                      plot_id, report_type)

        overview_layers = json_data['attributes']['overviewMap']['layers']
        for layer in overview_layers:
            layer['geoJson'] = self.get_layer_geojson(db, layer['name'],
                                                      plot_id, report_type)

        new_json_file_path = os.path.join(
            tmp_dir,
            self.get_tmp_filename('json_data_{}'.format(plot_id), 'json'))
        with open(new_json_file_path, 'w') as new_json:
            new_json.write(json.dumps(json_data))

        return new_json_file_path

    def get_tmp_dir(self, create_random=True):
        if create_random:
            return tempfile.mkdtemp()

        return tempfile.gettempdir()

    def get_tmp_filename(self, basename, extension='gpkg'):
        return "{}_{}.{}".format(basename,
                                 str(time.time()).replace(".", ""), extension)

    def generate_report(self, db, report_type):
        # Check if mapfish and Jasper are installed, otherwise show where to
        # download them from and return
        if not self.report_dependency.check_if_dependency_is_valid():
            self.report_dependency.download_dependency(URL_REPORTS_LIBRARIES)
            return

        java_home_set = self.java_dependency.set_java_home()
        if not java_home_set:
            self.java_dependency.get_java_on_demand()
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ReportGenerator",
                    "Java is a prerequisite. Since it was not found, it is being configured..."
                ))
            return

        plot_layer = self.app.core.get_layer(db, db.names.LC_PLOT_T, load=True)
        if not plot_layer:
            return

        selected_plots = plot_layer.selectedFeatures()
        if not selected_plots:
            self.logger.warning_msg(
                __name__,
                QCoreApplication.translate(
                    "ReportGenerator",
                    "To generate reports, first select at least a plot!"))
            return

        # Where to store the reports?
        previous_folder = QSettings().value(
            "Asistente-LADM-COL/reports/save_into_dir", ".")
        save_into_folder = QFileDialog.getExistingDirectory(
            None,
            QCoreApplication.translate(
                "ReportGenerator",
                "Select a folder to save the reports to be generated"),
            previous_folder)
        if not save_into_folder:
            self.logger.warning_msg(
                __name__,
                QCoreApplication.translate(
                    "ReportGenerator",
                    "You need to select a folder where to save the reports before continuing."
                ))
            return
        QSettings().setValue("Asistente-LADM-COL/reports/save_into_dir",
                             save_into_folder)

        config_path = os.path.join(DEPENDENCY_REPORTS_DIR_NAME, report_type)
        json_spec_file = os.path.join(config_path, 'spec_json_file.json')

        script_name = ''
        if os.name == 'posix':
            script_name = 'print'
        elif os.name == 'nt':
            script_name = 'print.bat'

        script_path = os.path.join(DEPENDENCY_REPORTS_DIR_NAME, 'bin',
                                   script_name)
        if not os.path.isfile(script_path):
            self.logger.warning(
                __name__,
                "Script file for reports wasn't found! {}".format(script_path))
            return

        self.enable_action_requested.emit(report_type, False)

        # Update config file
        yaml_config_path = self.update_yaml_config(db, config_path)
        self.logger.debug(
            __name__, "Config file for reports: {}".format(yaml_config_path))

        total = len(selected_plots)
        step = 0
        count = 0
        tmp_dir = self.get_tmp_dir()

        # Progress bar setup
        progress = QProgressBar()
        if total == 1:
            progress.setRange(0, 0)
        else:
            progress.setRange(0, 100)
        progress.setAlignment(Qt.AlignLeft | Qt.AlignVCenter)
        self.app.gui.create_progress_message_bar(
            QCoreApplication.translate("ReportGenerator",
                                       "Generating {} report{}...").format(
                                           total, '' if total == 1 else 's'),
            progress)

        polygons_with_holes = []
        multi_polygons = []

        for selected_plot in selected_plots:
            plot_id = selected_plot[db.names.T_ID_F]

            geometry = selected_plot.geometry()
            abstract_geometry = geometry.get()
            if abstract_geometry.ringCount() > 1:
                polygons_with_holes.append(str(plot_id))
                self.logger.warning(
                    __name__,
                    QCoreApplication.translate(
                        "ReportGenerator",
                        "Skipping Annex 17 for plot with {}={} because it has holes. The reporter module does not support such polygons."
                    ).format(db.names.T_ID_F, plot_id))
                continue
            if abstract_geometry.numGeometries() > 1:
                multi_polygons.append(str(plot_id))
                self.logger.warning(
                    __name__,
                    QCoreApplication.translate(
                        "ReportGenerator",
                        "Skipping Annex 17 for plot with {}={} because it is a multi-polygon. The reporter module does not support such polygons."
                    ).format(db.names.T_ID_F, plot_id))
                continue

            # Generate data file
            json_file = self.update_json_data(db, json_spec_file, plot_id,
                                              tmp_dir, report_type)
            self.logger.debug(__name__,
                              "JSON file for reports: {}".format(json_file))

            # Run sh/bat passing config and data files
            proc = QProcess()
            proc.readyReadStandardError.connect(
                functools.partial(self.stderr_ready, proc=proc))
            proc.readyReadStandardOutput.connect(
                functools.partial(self.stdout_ready, proc=proc))

            parcel_number = self.ladm_data.get_parcels_related_to_plots(
                db, [plot_id], db.names.LC_PARCEL_T_PARCEL_NUMBER_F) or ['']
            file_name = '{}_{}_{}.pdf'.format(report_type, plot_id,
                                              parcel_number[0])

            current_report_path = os.path.join(save_into_folder, file_name)
            proc.start(script_path, [
                '-config', yaml_config_path, '-spec', json_file, '-output',
                current_report_path
            ])

            if not proc.waitForStarted():
                # Grant execution permissions
                os.chmod(
                    script_path, stat.S_IXOTH | stat.S_IXGRP | stat.S_IXUSR
                    | stat.S_IRUSR | stat.S_IRGRP)
                proc.start(script_path, [
                    '-config', yaml_config_path, '-spec', json_file, '-output',
                    current_report_path
                ])

            if not proc.waitForStarted():
                proc = None
                self.logger.warning(
                    __name__, "Couldn't execute script to generate report...")
            else:
                loop = QEventLoop()
                proc.finished.connect(loop.exit)
                loop.exec()

                self.logger.debug(__name__,
                                  "{}:{}".format(plot_id, proc.exitCode()))
                if proc.exitCode() == 0:
                    count += 1

                step += 1
                progress.setValue(step * 100 / total)

        os.remove(yaml_config_path)

        self.enable_action_requested.emit(report_type, True)
        self.logger.clear_message_bar()

        if total == count:
            if total == 1:
                msg = QCoreApplication.translate(
                    "ReportGenerator",
                    "The report <a href='file:///{}'>{}</a> was successfully generated!"
                ).format(normalize_local_url(save_into_folder), file_name)
            else:
                msg = QCoreApplication.translate(
                    "ReportGenerator",
                    "All reports were successfully generated in folder <a href='file:///{path}'>{path}</a>!"
                ).format(path=normalize_local_url(save_into_folder))

            self.logger.success_msg(__name__, msg)
        else:
            details_msg = ''
            if polygons_with_holes:
                details_msg += QCoreApplication.translate(
                    "ReportGenerator",
                    " The following polygons were skipped because they have holes and are not supported: {}."
                ).format(", ".join(polygons_with_holes))
            if multi_polygons:
                details_msg += QCoreApplication.translate(
                    "ReportGenerator",
                    " The following polygons were skipped because they are multi-polygons and are not supported: {}."
                ).format(", ".join(multi_polygons))

            if total == 1:
                msg = QCoreApplication.translate(
                    "ReportGenerator",
                    "The report for plot {} couldn't be generated!{} See QGIS log (tab '{}') for details."
                ).format(plot_id, details_msg, self.LOG_TAB)
            else:
                if count == 0:
                    msg = QCoreApplication.translate(
                        "ReportGenerator",
                        "No report could be generated!{} See QGIS log (tab '{}') for details."
                    ).format(details_msg, self.LOG_TAB)
                else:
                    msg = QCoreApplication.translate(
                        "ReportGenerator",
                        "At least one report couldn't be generated!{details_msg} See QGIS log (tab '{log_tab}') for details. Go to <a href='file:///{path}'>{path}</a> to see the reports that were generated."
                    ).format(details_msg=details_msg,
                             path=normalize_local_url(save_into_folder),
                             log_tab=self.LOG_TAB)

            self.logger.warning_msg(__name__, msg)

    def download_java_complete(self):
        if self.java_dependency.fetcher_task and not self.java_dependency.fetcher_task.isCanceled(
        ):
            if self.java_dependency.check_if_dependency_is_valid():
                self.logger.info_msg(
                    __name__,
                    QCoreApplication.translate(
                        "ReportGenerator",
                        "Java was successfully configured!"), 5)
        else:
            self.logger.warning_msg(
                __name__,
                QCoreApplication.translate(
                    "ReportGenerator",
                    "You have just canceled the Java dependency download."), 5)

    def download_report_complete(self):
        if self.report_dependency.fetcher_task and not self.report_dependency.fetcher_task.isCanceled(
        ):
            if self.report_dependency.check_if_dependency_is_valid():
                self.logger.info_msg(
                    __name__,
                    QCoreApplication.translate(
                        "ReportGenerator",
                        "Report dependency was successfully configured!"), 5)
        else:
            self.logger.warning_msg(
                __name__,
                QCoreApplication.translate(
                    "ReportGenerator",
                    "You have just canceled the report dependency download."),
                5)
Esempio n. 4
0
class TaskPanelWidget(QgsPanelWidget, WIDGET_UI):
    trigger_action_emitted = pyqtSignal(str)  # action tag

    def __init__(self, task_id, parent):
        QgsPanelWidget.__init__(self, parent)
        self.setupUi(self)
        self.session = STSession()
        self._task = self.session.task_manager.get_task(task_id)
        self.parent = parent
        self.logger = Logger()
        self.st_config = TransitionalSystemConfig()

        self.setDockMode(True)
        self.setPanelTitle(
            QCoreApplication.translate("TaskPanelWidget", "Task details"))

        self.trw_task_steps.itemDoubleClicked.connect(self.trigger_action)
        self.trw_task_steps.itemChanged.connect(self.update_step_controls)
        self.session.task_manager.task_started.connect(self.update_task)
        self.session.task_manager.task_canceled.connect(self.acceptPanel)
        self.session.task_manager.task_closed.connect(self.acceptPanel)

        self.btn_start_task.clicked.connect(self.start_task)
        self.btn_cancel_task.clicked.connect(self.cancel_task)
        self.btn_close_task.clicked.connect(self.close_task)

        self.initialize_gui()

    def initialize_gui(self):
        self.show_task_description()
        self.show_task_steps()
        self.update_controls()

    def show_task_description(self):
        if self._task is not None:
            self.logger.debug(__name__,
                              "Setting task description in Task Panel...")
            self.lbl_name.setText(self._task.get_name())
            self.lbl_description.setText(self._task.get_description())
            self.lbl_created_at.setText(
                QCoreApplication.translate("TaskPanelWidget",
                                           "Created at: {}").format(
                                               self._task.get_creation_date()))
            if self._task.get_status() == EnumSTTaskStatus.STARTED.value:
                self.lbl_started_at.setVisible(True)
                self.lbl_started_at.setText(
                    QCoreApplication.translate(
                        "TaskPanelWidget", "Started at: {}").format(
                            self._task.get_started_date()))
            else:
                self.lbl_started_at.setVisible(False)
            self.lbl_deadline.setText(
                QCoreApplication.translate("TaskPanelWidget",
                                           "Deadline: {}").format(
                                               self._task.get_deadline()))
            self.lbl_status.setText(self._task.get_status())

            # Styles
            self.lbl_name.setStyleSheet(self.st_config.TASK_TITLE_BIG_TEXT_CSS)
            if self._task.get_status() == EnumSTTaskStatus.ASSIGNED.value:
                self.lbl_status.setStyleSheet(
                    self.st_config.TASK_ASSIGNED_STATUS_BIG_TEXT_CSS)
            elif self._task.get_status() == EnumSTTaskStatus.STARTED.value:
                self.lbl_status.setStyleSheet(
                    self.st_config.TASK_STARTED_STATUS_BIG_TEXT_CSS)

    def show_task_steps(self):
        self.trw_task_steps.clear()
        steps = self._task.get_steps()
        self.logger.debug(
            __name__,
            "Showing task steps in Task Panel. {} task steps found: {}.".
            format(len(steps), ", ".join([s.get_name() for s in steps])))

        for i, step in enumerate(steps):
            children = []
            step_item = QTreeWidgetItem([
                QCoreApplication.translate("TaskPanelWidget",
                                           "Step {}").format(i + 1)
            ])
            step_item.setData(0, Qt.BackgroundRole, QBrush(GRAY_COLOR))
            step_item.setToolTip(0, step.get_name())
            step_item.setCheckState(
                0, Qt.Checked if step.get_status() else Qt.Unchecked)

            action_item = QTreeWidgetItem([step.get_name()])
            action_item.setData(0, Qt.UserRole, step.get_id())
            action_item.setIcon(
                0, QIcon(":/Asistente-LADM-COL/resources/images/process.svg"))
            action_item.setToolTip(0, step.get_description())
            step_item.setFlags(Qt.ItemIsEnabled | Qt.ItemIsSelectable)
            children.append(action_item)

            step_item.addChildren(children)
            self.trw_task_steps.addTopLevelItem(step_item)

        for i in range(self.trw_task_steps.topLevelItemCount()):
            self.trw_task_steps.topLevelItem(
                i).setFlags(Qt.ItemIsEnabled | Qt.ItemIsUserCheckable
                            | Qt.ItemIsSelectable)
            self.trw_task_steps.topLevelItem(i).setExpanded(True)

    def trigger_action(self, item, column):
        """
        Triggers an action linked to the task step. Action config comes from TaskStepsConfig, and it can be either a
        default action slot or a customized action slot (even passing a task context).

        :param item: QTreeWidgetItem that is linked to an action, (i.e., a child item)
        :param column: 0
        """
        # Make sure we have a child item and only trigger if parent is not checked yet (i.e., step is not done yet)
        if not item.childCount() and item.parent().checkState(
                column) == Qt.Unchecked:
            step_id = item.data(column, Qt.UserRole)
            step = self._task.get_step(step_id)
            if step:
                slot = step.get_custom_action_slot()
                if slot:  # Custom action call
                    self.logger.info(
                        __name__,
                        "Executing step action with custom parameters...")
                    if SLOT_CONTEXT in slot and slot[SLOT_CONTEXT]:
                        slot[SLOT_CONTEXT].set_slot_on_result(
                            partial(self.set_item_enabled, item.parent()))
                        slot[SLOT_NAME](
                            slot[SLOT_CONTEXT],
                            **slot[SLOT_PARAMS])  # Call passing task context
                    else:
                        slot[SLOT_NAME](**slot[SLOT_PARAMS])
                else:  # Default action call
                    self.logger.info(__name__, "Executing default action...")
                    self.trigger_action_emitted.emit(step.get_action_tag())

    def set_item_enabled(self, item, enable):
        """
        Slot to react upon getting the result of an action (dialog) linked to a task step

        :param item: Checkable QTreeWidgetItem (i.e., it's the parent, not the child that triggers the action)
        :param enable: Whether the task step was successfully run or not
        """
        item.setCheckState(0, Qt.Checked if enable else Qt.Unchecked)

    def set_item_style(self, item, column):
        color = CHECKED_COLOR if item.checkState(
            column) == Qt.Checked else UNCHECKED_COLOR
        item.setData(column, Qt.BackgroundRole, QBrush(color))

        for index in range(item.childCount()):
            color = GRAY_COLOR if item.checkState(
                column) == Qt.Checked else Qt.black
            item.child(index).setData(column, Qt.ForegroundRole, QBrush(color))

    def update_step_controls(self, item, column):
        if item.childCount():  # Only do this for parents
            self.trw_task_steps.blockSignals(True)
            self.set_item_style(item, column)
            self.save_task_steps_status(column)
            self.trw_task_steps.blockSignals(False)

        self.update_close_control()

    def update_controls(self):
        # Steps panel
        self.trw_task_steps.setEnabled(
            self._task.get_status() == EnumSTTaskStatus.STARTED.value)

        # Start task button
        self.btn_start_task.setEnabled(
            self._task.get_status() == EnumSTTaskStatus.ASSIGNED.value)

        # Cancel task button
        self.btn_cancel_task.setEnabled(
            self._task.get_status() == EnumSTTaskStatus.STARTED.value)

        self.update_close_control()

    def update_close_control(self):
        # Can we close the task?
        self.btn_close_task.setEnabled(
            self._task.get_status() == EnumSTTaskStatus.STARTED.value
            and self.steps_complete())
        if self._task.get_status(
        ) == EnumSTTaskStatus.STARTED.value and self.steps_complete():
            self.btn_close_task.setToolTip("")
        elif self._task.get_status(
        ) != EnumSTTaskStatus.STARTED.value and self.steps_complete():
            self.btn_close_task.setToolTip(
                QCoreApplication.translate(
                    "TaskPanelWidget",
                    "The task is not started yet, hence, it cannot be closed.")
            )
        else:  # The remaining 2 cases: steps incomplete (whether the task is started or not)
            self.btn_close_task.setToolTip(
                QCoreApplication.translate(
                    "TaskPanelWidget",
                    "You should complete the steps before closing the task."))

    def steps_complete(self):
        """
        :return: boolean --> Can we close the task
        """
        return self._task.steps_complete()

    def save_task_steps_status(self, column):
        steps_status = dict()
        for i in range(self.trw_task_steps.topLevelItemCount()):
            steps_status[i + 1] = self.trw_task_steps.topLevelItem(
                i).checkState(column) == Qt.Checked

        # Don't save if not necessary
        status = QSettings().value(
            "Asistente-LADM-COL/transitional_system/tasks/{}/step_status".
            format(self._task.get_id()), "{}")
        if status != json.dumps(steps_status):
            self._task.save_steps_status(steps_status)

    def start_task(self):
        self.session.task_manager.start_task(self.session.get_logged_st_user(),
                                             self._task.get_id())
        self.update_controls()

    def cancel_task(self):
        reply = QMessageBox.question(
            self, QCoreApplication.translate("TaskPanelWidget", "Confirm"),
            QCoreApplication.translate(
                "TaskPanelWidget",
                "Are you sure you want to cancel the task '{}'?").format(
                    self._task.get_name()), QMessageBox.Yes, QMessageBox.No)
        if reply == QMessageBox.Yes:
            dlg = STCancelTaskDialog(self.parent)
            res = dlg.exec_()
            if res == QDialog.Accepted:
                if dlg.comments:
                    self.session.task_manager.cancel_task(
                        self.session.get_logged_st_user(), self._task.get_id(),
                        dlg.comments)
            else:
                self.logger.warning_msg(
                    __name__,
                    QCoreApplication.translate("TaskPanelWidget",
                                               "The task was not canceled."))

    def close_task(self):
        reply = QMessageBox.question(
            self, QCoreApplication.translate("TaskPanelWidget", "Confirm"),
            QCoreApplication.translate(
                "TaskPanelWidget",
                "Are you sure you want to close the task '{}'?").format(
                    self._task.get_name()), QMessageBox.Yes, QMessageBox.No)
        if reply == QMessageBox.Yes:
            self.session.task_manager.close_task(
                self.session.get_logged_st_user(), self._task.get_id())

    def update_task(self, task_id):
        """A task changed in the Task Manager, so, update the base task for the panel and update the panel itself"""
        self._task = self.session.task_manager.get_task(task_id)
        self.initialize_gui()
Esempio n. 5
0
class AbsWizardFactory(QWizard):
    update_wizard_is_open_flag = pyqtSignal(bool)
    set_finalize_geometry_creation_enabled_emitted = pyqtSignal(bool)

    def __init__(self, iface, db, qgis_utils, wizard_settings):
        super(AbsWizardFactory, self).__init__()
        self.iface = iface
        self._db = db
        self.qgis_utils = qgis_utils
        self.wizard_config = wizard_settings
        self.logger = Logger()
        self.names = self._db.names
        self.help_strings = HelpStrings()
        self.translatable_config_strings = TranslatableConfigStrings()
        load_ui(self.wizard_config[WIZARD_UI], self)

        self.WIZARD_FEATURE_NAME = self.wizard_config[WIZARD_FEATURE_NAME]
        self.WIZARD_TOOL_NAME = self.wizard_config[WIZARD_TOOL_NAME]
        self.EDITING_LAYER_NAME = self.wizard_config[WIZARD_EDITING_LAYER_NAME]
        self._layers = self.wizard_config[WIZARD_LAYERS]
        self.set_ready_only_field()

        self.init_gui()

    def init_gui(self):
        raise NotImplementedError

    def adjust_page_1_controls(self):
        raise NotImplementedError

    def finished_dialog(self):
        raise NotImplementedError

    def prepare_feature_creation(self):
        result = self.prepare_feature_creation_layers()
        if result:
            self.edit_feature()
        else:
            self.close_wizard(show_message=False)

    def prepare_feature_creation_layers(self):
        raise NotImplementedError

    def close_wizard(self, message=None, show_message=True):
        raise NotImplementedError

    def rollback_in_layers_with_empty_editing_buffer(self):
        for layer_name in self._layers:
            if self._layers[layer_name][
                    LAYER] is not None:  # If the layer was removed, this becomes None
                if self._layers[layer_name][LAYER].isEditable():
                    if not self._layers[layer_name][LAYER].editBuffer(
                    ).isModified():
                        self._layers[layer_name][LAYER].rollBack()

    def disconnect_signals(self):
        raise NotImplementedError

    def edit_feature(self):
        raise NotImplementedError

    def finish_feature_creation(self, layerId, features):
        message = self.post_save(features)

        self._layers[
            self.EDITING_LAYER_NAME][LAYER].committedFeaturesAdded.disconnect(
                self.finish_feature_creation)
        self.logger.info(
            __name__, "{} committedFeaturesAdded SIGNAL disconnected".format(
                self.WIZARD_FEATURE_NAME))
        self.close_wizard(message)

    def post_save(self, features):
        raise NotImplementedError

    def open_form(self, layer):
        raise NotImplementedError

    def exec_form(self, layer):
        feature = self.get_feature_exec_form(layer)
        dialog = self.iface.getFeatureForm(layer, feature)
        dialog.rejected.connect(self.form_rejected)
        dialog.setModal(True)

        if dialog.exec_():
            self.exec_form_advanced(layer)
            saved = layer.commitChanges()

            if not saved:
                layer.rollBack()
                self.logger.warning_msg(
                    __name__,
                    QCoreApplication.translate(
                        "WizardTranslations",
                        "Error while saving changes. {} could not be created."
                    ).format(self.WIZARD_FEATURE_NAME))
                for e in layer.commitErrors():
                    self.logger.warning(__name__, "Commit error: {}".format(e))
        else:
            layer.rollBack()
        self.iface.mapCanvas().refresh()

    def get_feature_exec_form(self, layer):
        raise NotImplementedError

    def exec_form_advanced(self, layer):
        raise NotImplementedError

    def form_rejected(self):
        message = QCoreApplication.translate(
            "WizardTranslations",
            "'{}' tool has been closed because you just closed the form."
        ).format(self.WIZARD_TOOL_NAME)
        self.close_wizard(message)

    def save_settings(self):
        settings = QSettings()
        settings.setValue(
            self.wizard_config[WIZARD_QSETTINGS]
            [WIZARD_QSETTINGS_LOAD_DATA_TYPE], 'create_manually'
            if self.rad_create_manually.isChecked() else 'refactor')

    def restore_settings(self):
        settings = QSettings()

        load_data_type = settings.value(
            self.wizard_config[WIZARD_QSETTINGS]
            [WIZARD_QSETTINGS_LOAD_DATA_TYPE]) or 'create_manually'
        if load_data_type == 'refactor':
            self.rad_refactor.setChecked(True)
        else:
            self.rad_create_manually.setChecked(True)

    def show_help(self):
        self.qgis_utils.show_help(self.wizard_config[WIZARD_HELP])

    def set_ready_only_field(self, read_only=True):
        if self._layers[self.EDITING_LAYER_NAME][LAYER] is not None:
            for field in self.wizard_config[WIZARD_READ_ONLY_FIELDS]:
                # Not validate field that are read only
                QGISUtils.set_read_only_field(
                    self._layers[self.EDITING_LAYER_NAME][LAYER], field,
                    read_only)
class JavaUtils(QObject):
    download_java_completed = pyqtSignal()
    download_java_progress_changed = pyqtSignal(int)  # progress

    JAVA_NAME = 'java.exe' if platform.system() == 'Windows' else 'java'

    def __init__(self):
        QObject.__init__(self)
        self.logger = Logger()
        self._downloading = False
        self._show_cursor = True

    def get_java_on_demand(self):
        # Create required directories
        Path(DEPENDENCIES_BASE_PATH).mkdir(parents=True, exist_ok=True)

        # Remove previous Java if any
        custom_java_dir_path = os.path.join(
            DEPENDENCIES_BASE_PATH, DICT_JAVA_DIR_NAME[KEY_JAVA_OS_VERSION])
        if os.path.exists(custom_java_dir_path):
            shutil.rmtree(custom_java_dir_path, ignore_errors=True)

        url_java = DICT_JAVA_DOWNLOAD_URL[KEY_JAVA_OS_VERSION]
        self.download_java_dependency(url_java)

    def download_java_dependency(self, uri):
        self.logger.clear_message_bar()

        if not self._downloading:  # Already downloading report dependency?
            if QGISUtils.is_connected(TEST_SERVER):
                self._downloading = True
                fetcher_task = QgsNetworkContentFetcherTask(QUrl(uri))
                fetcher_task.begun.connect(self.task_begun)
                fetcher_task.progressChanged.connect(
                    self.task_progress_changed)
                fetcher_task.fetched.connect(
                    functools.partial(self.save_java_dependency_file,
                                      fetcher_task))
                fetcher_task.taskCompleted.connect(self.task_completed)
                QgsApplication.taskManager().addTask(fetcher_task)
            else:
                self.logger.warning_msg(
                    __name__,
                    QCoreApplication.translate(
                        "JavaUtils",
                        "There was a problem connecting to Internet."))
                self._downloading = False

    def task_begun(self):
        if self._show_cursor:
            QApplication.setOverrideCursor(Qt.WaitCursor)

    def task_progress_changed(self, progress):
        self.download_java_progress_changed.emit(progress)

    def task_completed(self):
        if self._show_cursor:
            QApplication.restoreOverrideCursor()
        self.download_java_completed.emit()

    def save_java_dependency_file(self, fetcher_task):
        if fetcher_task.reply() is not None:
            # Write response to tmp file
            tmp_file = tempfile.mktemp()
            out_file = QFile(tmp_file)
            out_file.open(QIODevice.WriteOnly)
            out_file.write(fetcher_task.reply().readAll())
            out_file.close()

            if not os.path.exists(DEPENDENCIES_BASE_PATH):
                os.makedirs(DEPENDENCIES_BASE_PATH)

            if md5sum(tmp_file) == DICT_JAVA_MD5SUM[KEY_JAVA_OS_VERSION]:

                try:
                    tar = tarfile.open(tmp_file)
                    tar.extractall(DEPENDENCIES_BASE_PATH)
                    tar.close()
                except tarfile.ReadError as e:
                    self.logger.warning_msg(
                        __name__,
                        QCoreApplication.translate(
                            "JavaUtils",
                            "There was an error with the download. The downloaded file is invalid."
                        ))
                except PermissionError as e:
                    self.logger.warning_msg(
                        __name__,
                        QCoreApplication.translate(
                            "JavaUtils",
                            "Java couldn't be installed. Check if it is possible to write into this folder: <a href='file:///{path}'>{path}</a>"
                        ).format(path=normalize_local_url(
                            os.path.join(DEPENDENCIES_BASE_PATH),
                            DICT_JAVA_DIR_NAME[KEY_JAVA_OS_VERSION])))

            else:
                self.logger.warning_msg(
                    __name__,
                    QCoreApplication.translate(
                        "JavaUtils",
                        "There was an error with the download. The downloaded file is invalid."
                    ))
            try:
                os.remove(tmp_file)
            except:
                pass

        self._downloading = False

    def set_java_home(self):
        """
        Attempt to set a valid JAVA_HOME only for the current session, which is used by reports (MapFish).
        First try with the system JAVA_HOME, if not present, try with the Java configured in Model Baker.
        If not, return False.
        :return: Whether a proper Java could be set or not in the current session's JAVA_HOME
        """
        java_name = 'java.exe' if sys.platform == 'win32' else 'java'
        pattern_java = "bin{}java".format(os.sep)

        # Get JAVA_HOME environment variable
        if 'JAVA_HOME' in os.environ:
            java_home = os.environ['JAVA_HOME']

            java_exe = os.path.join(java_home, 'bin', java_name)
            is_valid, java_message = JavaUtils.java_path_is_valid(java_exe)

            if not is_valid:
                # Another try: does JAVA_HOME include bin dir?
                java_exe = os.path.join(java_home, java_name)
                is_valid, java_message = JavaUtils.java_path_is_valid(java_exe)

                if is_valid:
                    os.environ['JAVA_HOME'] = java_exe.split(pattern_java)[0]
                    return True
            else:
                os.environ['JAVA_HOME'] = java_exe.split(pattern_java)[0]
                # JAVA_HOME is valid, we'll use it as it is!
                return True
        else:
            java_exe = which(java_name)
            if java_exe:
                # verifies that the java configured in the system is valid.
                is_valid, java_message = JavaUtils.java_path_is_valid(java_exe)
                if is_valid:
                    os.environ['JAVA_HOME'] = java_exe.split(pattern_java)[0]
                    return True

        # If JAVA_HOME environment variable doesn't exist
        # We use the value defined in QgisModelBaker
        java_exe = JavaUtils.get_java_path_from_qgis_model_baker()

        if java_exe:
            is_valid, java_message = JavaUtils.java_path_is_valid(java_exe)
            if is_valid:
                os.environ['JAVA_HOME'] = java_exe.split(pattern_java)[0]
                return True

        # If JAVA_HOME environment variable doesn't exist
        # If JAVA_HOME is no defined in QgisModelBaker

        # Check if Java was installed previously by this plugin
        full_path_java = os.path.join(DEPENDENCIES_BASE_PATH,
                                      DICT_JAVA_DIR_NAME[KEY_JAVA_OS_VERSION],
                                      'bin', JavaUtils.JAVA_NAME)
        is_valid, java_message = JavaUtils.java_path_is_valid(full_path_java)

        if is_valid:
            # Java was downloaded previously, so use it!
            os.environ['JAVA_HOME'] = full_path_java.split(pattern_java)[0]
            return True

        return False

    @staticmethod
    def get_full_java_exe_path():
        java_name = 'java.exe' if sys.platform == 'win32' else 'java'
        full_java_exe_path = ""

        # Get JAVA_HOME environment variable
        if 'JAVA_HOME' in os.environ:
            java_home = os.environ['JAVA_HOME']

            full_java_exe_path = os.path.join(java_home, 'bin', java_name)
            is_valid, java_message = JavaUtils.java_path_is_valid(
                full_java_exe_path)

            if not is_valid:
                # Another try: does JAVA_HOME include bin dir?
                full_java_exe_path = os.path.join(java_home, java_name)
                is_valid, java_message = JavaUtils.java_path_is_valid(
                    full_java_exe_path)
                if not is_valid:
                    full_java_exe_path = ""
        else:
            full_java_exe_path = which(java_name)
            # verifies that the java configured in the system is valid.
            is_valid, java_message = JavaUtils.java_path_is_valid(
                full_java_exe_path)
            if not is_valid:
                full_java_exe_path = ""

        return full_java_exe_path

    @staticmethod
    def java_path_is_valid(java_path):
        """
        Check if java path exists
        :param java_path: (str) java path to validate
        :return: (bool, str)  True if java Path is valid, False otherwise
        """
        try:
            if os.name == 'nt':
                java_path = JavaUtils.normalize_java_path(java_path)

            procs_message = subprocess.check_output(
                [java_path, '-version'],
                stderr=subprocess.STDOUT).decode('utf8').lower()
            types_java = ['jre', 'java', 'jdk']

            if procs_message:
                if any(type_java in procs_message for type_java in types_java):
                    pattern = '\"(\d+\.\d+).*\"'
                    java_version = re.search(pattern,
                                             procs_message).groups()[0]

                    if java_version:
                        if float(java_version) == JAVA_REQUIRED_VERSION:
                            return (
                                True,
                                QCoreApplication.translate(
                                    "JavaPath",
                                    "Java path has been configured correctly.")
                            )
                        else:
                            return (
                                False,
                                QCoreApplication.translate(
                                    "JavaPath",
                                    "Java version is not valid. Current version is {}, but must be {}."
                                ).format(java_version, JAVA_REQUIRED_VERSION))

                    return (
                        False,
                        QCoreApplication.translate(
                            "JavaPath",
                            "Java exists but it is not possible to know and validate its version."
                        ))
                else:
                    return (
                        False,
                        QCoreApplication.translate(
                            "JavaPath",
                            "Java path is not valid, please select a valid path..."
                        ))
            else:
                return (
                    False,
                    QCoreApplication.translate(
                        "JavaPath",
                        "Java path is not valid, please select a valid path..."
                    ))
        except Exception as e:
            return (
                False,
                QCoreApplication.translate(
                    "JavaPath",
                    "Java path is not valid, please select a valid path..."))

    @staticmethod
    def normalize_java_path(java_path):
        escape_characters = [('\a', '\\a'), ('\b', '\\b'), ('\f', '\\f'),
                             ('\n', '\\n'), ('\r', '\\r'), ('\t', '\\t'),
                             ('\v', '\\v')]
        for escape_character in escape_characters:
            java_path = java_path.replace(escape_character[0],
                                          escape_character[1])
        return java_path

    @staticmethod
    def get_java_path_from_qgis_model_baker():
        return QSettings().value('QgisModelBaker/ili2db/JavaPath', '', str)
Esempio n. 7
0
class ChangeDetectionSettingsDialog(QDialog, DIALOG_UI):
    CHANGE_DETECTIONS_MODE_SUPPLIES_MODEL = "CHANGE_DETECTIONS_MODE_SUPPLIES_MODEL"
    CHANGE_DETECTIONS_MODES = {CHANGE_DETECTIONS_MODE_SUPPLIES_MODEL: QCoreApplication.translate("ChangeDetectionSettingsDialog", "Change detection supplies model")}

    def __init__(self, parent=None, qgis_utils=None, conn_manager=None):
        QDialog.__init__(self, parent)
        self.setupUi(self)
        self.logger = Logger()
        self.help_strings = HelpStrings()
        self.txt_help_page.setHtml(self.help_strings.CHANGE_DETECTION_SETTING_DIALOG_HELP)

        self.conn_manager = conn_manager
        self.qgis_utils = qgis_utils

        # we will use a unique instance of setting dialog
        self.settings_dialog = SettingsDialog(qgis_utils=self.qgis_utils, conn_manager=self.conn_manager)
        # The database configuration is saved if it becomes necessary
        # to restore the configuration when the user rejects the dialog
        self.init_db_collected = None
        self.init_db_supplies = None
        self.set_init_db_config()  # Always call after the settings_dialog variable is set

        self._db_collected = self.conn_manager.get_db_connector_from_source()
        self._db_supplies = self.conn_manager.get_db_connector_from_source(SUPPLIES_DB_SOURCE)

        # There may be 1 case where we need to emit a db_connection_changed from the change detection settings dialog:
        #   1) Connection Settings was opened and the DB conn was changed.
        self._db_collected_was_changed = False  # To postpone calling refresh gui until we close this dialog instead of settings
        self._db_supplies_was_changed = False

        # Similarly, we could call a refresh on layers and relations cache in 1 case:
        #   1) If the change detection settings dialog was called for the COLLECTED source: opening Connection Settings
        #      and changing the DB connection.
        self._schedule_layers_and_relations_refresh = False

        for mode, label_mode in self.CHANGE_DETECTIONS_MODES.items():
            self.cbo_change_detection_modes.addItem(label_mode, mode)

        self.radio_button_other_db.setChecked(True)  # Default option
        self.radio_button_same_db.setEnabled(True)
        if not self._db_collected.supplies_model_exists():
            self.radio_button_same_db.setEnabled(False)

        self.radio_button_same_db.toggled.connect(self.update_supplies_db_options)
        self.update_supplies_db_options()

        self.btn_collected_db.clicked.connect(self.show_settings_collected_db)
        self.btn_supplies_db.clicked.connect(self.show_settings_supplies_db)

        # Default color error labels
        self.lbl_msg_collected.setStyleSheet('color: orange')
        self.lbl_msg_supplies.setStyleSheet('color: orange')

        # Set connections
        self.buttonBox.accepted.disconnect()
        self.buttonBox.accepted.connect(self.accepted)
        self.buttonBox.helpRequested.connect(self.show_help)

        self.bar = QgsMessageBar()
        self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        self.layout().addWidget(self.bar, 0, 0, Qt.AlignTop)

        self.update_connection_info()

        self.logger.clear_message_bar()  # Close any existing message in QGIS message bar

    def set_init_db_config(self):
        """
         A copy of the initial connections to the database is made,
         User can change the initial connections and then cancel the changes.
         Initial connections need to be re-established
        """
        self.init_db_collected = self.conn_manager.get_db_connector_from_source(COLLECTED_DB_SOURCE)
        self.init_db_supplies = self.conn_manager.get_db_connector_from_source(SUPPLIES_DB_SOURCE)

    def update_supplies_db_options(self):
        if self.radio_button_same_db.isChecked():
            self.btn_supplies_db.setEnabled(False)
        else:
            self.btn_supplies_db.setEnabled(True)
        self.update_connection_info()

    def show_settings_collected_db(self):
        self.settings_dialog.set_db_source(COLLECTED_DB_SOURCE)
        self.settings_dialog.set_tab_pages_list([SETTINGS_CONNECTION_TAB_INDEX])
        self.settings_dialog.set_required_models([LADMNames.OPERATION_MODEL_PREFIX])
        self.settings_dialog.db_connection_changed.connect(self.db_connection_changed)

        if self.settings_dialog.exec_():
            self._db_collected = self.settings_dialog.get_db_connection()
            self.update_connection_info()
        self.settings_dialog.db_connection_changed.disconnect(self.db_connection_changed)

    def show_settings_supplies_db(self):
        self.settings_dialog.set_db_source(SUPPLIES_DB_SOURCE)
        self.settings_dialog.set_tab_pages_list([SETTINGS_CONNECTION_TAB_INDEX])
        self.settings_dialog.set_required_models([LADMNames.SUPPLIES_MODEL_PREFIX])
        self.settings_dialog.db_connection_changed.connect(self.db_connection_changed)

        if self.settings_dialog.exec_():
            self._db_supplies = self.settings_dialog.get_db_connection()
            self.update_connection_info()
        self.settings_dialog.db_connection_changed.disconnect(self.db_connection_changed)

    def db_connection_changed(self, db, ladm_col_db, db_source):
        # We dismiss parameters here, after all, we already have the db, and the ladm_col_db
        # may change from this moment until we close the import schema dialog
        if db_source == COLLECTED_DB_SOURCE:
            self._db_collected_was_changed = True
            self._schedule_layers_and_relations_refresh = True
        else:
            self._db_supplies_was_changed = True

    def update_connection_info(self):
        # Validate db connections
        self.lbl_msg_collected.setText("")
        self.lbl_msg_supplies.setText("")
        self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(False)

        # First, update status of same_db button according to collected db connection
        res_collected, code_collected, msg_collected = self._db_collected.test_connection(required_models=[LADMNames.OPERATION_MODEL_PREFIX])
        res_supplies, code_supplies, msg_supplies = self._db_collected.test_connection(required_models=[LADMNames.SUPPLIES_MODEL_PREFIX])

        if res_supplies:
            self.radio_button_same_db.setEnabled(True)
        else:
            self.radio_button_same_db.setChecked(False)  # signal update the label

        if not self.radio_button_same_db.isChecked():
            res_supplies, code_supplies, msg_supplies = self._db_supplies.test_connection(required_models=[LADMNames.SUPPLIES_MODEL_PREFIX])

        # Update collected db connection label
        db_description = self._db_collected.get_description_conn_string()
        if db_description:
            self.db_collected_connect_label.setText(db_description)
            self.db_collected_connect_label.setToolTip(self._db_collected.get_display_conn_string())
        else:
            self.db_collected_connect_label.setText(QCoreApplication.translate("ChangeDetectionSettingsDialog", "The database is not defined!"))
            self.db_collected_connect_label.setToolTip('')

        # Update supplies db connection label
        if self.radio_button_same_db.isChecked():
            self.db_supplies_connect_label.setText(self.db_collected_connect_label.text())
            self.db_supplies_connect_label.setToolTip(self.db_collected_connect_label.toolTip())
        else:
            db_description = self._db_supplies.get_description_conn_string()
            if db_description:
                self.db_supplies_connect_label.setText(db_description)
                self.db_supplies_connect_label.setToolTip(self._db_supplies.get_display_conn_string())
            else:
                self.db_supplies_connect_label.setText(QCoreApplication.translate("ChangeDetectionSettingsDialog", "The database is not defined!"))
                self.db_supplies_connect_label.setToolTip('')

        # Update error message labels
        if not res_collected:
            self.lbl_msg_collected.setText(QCoreApplication.translate("ChangeDetectionSettingsDialog", "Warning: DB connection is not valid"))
            self.lbl_msg_collected.setToolTip(msg_collected)

        if not res_supplies:
            self.lbl_msg_supplies.setText(QCoreApplication.translate("ChangeDetectionSettingsDialog", "Warning: DB connection is not valid"))
            self.lbl_msg_supplies.setToolTip(msg_supplies)

        if res_collected and res_supplies:
            self.buttonBox.button(QDialogButtonBox.Ok).setEnabled(True)

    def accepted(self):
        """
        Confirm changes in db connections.
        If user select collected db as supplies db we update supplies db connection with collected db connection.

        If there are layers loaded in canvas from a previous connection that changed, we ask users
        if they want to clean the canvas or preserve the layers.

        If none of the connections changed, the dialog is closed without asking anything to users, and an info message
        is displayed.
        """
        if self.radio_button_same_db.isChecked():
            # Set supplies db connector from collected db connector
            self.conn_manager.save_parameters_conn(self._db_collected, SUPPLIES_DB_SOURCE)
            self._db_supplies = self._db_collected
            self.conn_manager.db_connection_changed.emit(self._db_supplies, self._db_supplies.test_connection()[0], SUPPLIES_DB_SOURCE)

        # Show messages when closing dialog
        if self._db_collected_was_changed or self._db_supplies_was_changed:
            if list(QgsProject.instance().mapLayers().values()):
                message = ""
                if self._db_collected_was_changed and self._db_supplies_was_changed:
                    message = "The connection of the collected and supplies databases has changed,"
                elif self._db_collected_was_changed:
                    message = "The collected database connection has changed,"
                elif self._db_supplies_was_changed:
                    message = "The supplies database connection has changed,"

                message += " do you want to remove the layers that are currently loaded in QGIS?"
                self.show_message_clean_layers_panel(message)
                self.show_message_change_detection_settings_status()  # Show information message indicating if setting is OK
            else:
                self.close_dialog(QDialog.Accepted)
        else:
            # Connections have not changed
            self.close_dialog(QDialog.Accepted)

    def show_message_change_detection_settings_status(self):
        if not self.collected_db_is_valid() and not self.supplies_db_is_valid():
            message = QCoreApplication.translate("ChangeDetectionSettingsDialog", "Neither Collected nor Supplies database connections is valid, you should first configure them before proceeding to detect changes.")
            self.logger.warning_msg(__name__, message, 5)
        elif not self.collected_db_is_valid() or not self.supplies_db_is_valid():

            if not self.collected_db_is_valid():
                message = QCoreApplication.translate("ChangeDetectionSettingsDialog", "Collected database connection is not valid, you should first configure it before proceeding to detect changes.")
                self.logger.warning_msg(__name__, message, 5)

            if not self.supplies_db_is_valid():
                message = QCoreApplication.translate("ChangeDetectionSettingsDialog", "Supplies database connection is not valid, you should first configure it before proceeding to detect changes.")
                self.logger.warning_msg(__name__, message, 5)
        else:
            message = QCoreApplication.translate("ChangeDetectionSettingsDialog", "Both Collected and Supplies database connections are valid, now you can proceed to detect changes.")
            self.logger.message_with_buttons_change_detection_all_and_per_parcel_emitted.emit(message)

    def show_message_clean_layers_panel(self, message):
        msg = QMessageBox(self)
        msg.setIcon(QMessageBox.Question)
        msg.setText(message)
        msg.setWindowTitle(QCoreApplication.translate("ChangeDetectionSettingsDialog", "Remove layers?"))
        msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
        msg.button(QMessageBox.Yes).setText(QCoreApplication.translate("ChangeDetectionSettingsDialog", "Yes, remove layers"))
        msg.button(QMessageBox.No).setText(QCoreApplication.translate("ChangeDetectionSettingsDialog", "No, don't remove"))
        reply = msg.exec_()

        if reply == QMessageBox.Yes:
            QgsProject.instance().layerTreeRoot().removeAllChildren()
            self.close_dialog(QDialog.Accepted)
        elif reply == QMessageBox.No:
            self.close_dialog(QDialog.Accepted)
        elif reply == QMessageBox.Cancel:
            pass  # Continue config db connections

    def collected_db_is_valid(self):
        res, foo, bar = self._db_collected.test_connection(required_models=[LADMNames.OPERATION_MODEL_PREFIX])
        return res

    def supplies_db_is_valid(self):
        res, foo, bar = self._db_supplies.test_connection(required_models=[LADMNames.SUPPLIES_MODEL_PREFIX])
        return res

    def reject(self):
        self.close_dialog(QDialog.Rejected)

    def close_dialog(self, result):
        """
        We use this slot to be safe when emitting the db_connection_changed (should be done at the end), otherwise we
        could trigger slots that unload the plugin, destroying dialogs and thus, leading to crashes.
        """
        if result == QDialog.Accepted:
            if self._schedule_layers_and_relations_refresh:
                self.conn_manager.db_connection_changed.connect(self.qgis_utils.cache_layers_and_relations)

            if self._db_collected_was_changed:
                self.conn_manager.db_connection_changed.emit(self._db_collected,
                                                             self._db_collected.test_connection()[0],
                                                             COLLECTED_DB_SOURCE)

            if self._db_supplies_was_changed:
                self.conn_manager.db_connection_changed.emit(self._db_supplies,
                                                             self._db_supplies.test_connection()[0],
                                                             SUPPLIES_DB_SOURCE)

            if self._schedule_layers_and_relations_refresh:
                self.conn_manager.db_connection_changed.disconnect(self.qgis_utils.cache_layers_and_relations)

        elif result == QDialog.Rejected:
            # Go back to initial connections and don't emit db_connection_changed
            if self._db_collected_was_changed:
                self.conn_manager.save_parameters_conn(self.init_db_collected, COLLECTED_DB_SOURCE)

            if self._db_supplies_was_changed:
                self.conn_manager.save_parameters_conn(self.init_db_supplies, SUPPLIES_DB_SOURCE)

        self.show_message_change_detection_settings_status()  # Show information message indicating whether setting is OK
        self.logger.info(__name__, "Dialog closed.")
        self.done(result)

    def show_help(self):
        self.qgis_utils.show_help("change_detection_settings")
Esempio n. 8
0
class STTaskManager(QObject):
    """
    Retrieve tasks for a user from the Transitional System's Task Service and store them during the session.
    """
    task_started = pyqtSignal(int)  # task_id
    task_canceled = pyqtSignal(int)  # task_id
    task_closed = pyqtSignal(int)  # task_id

    def __init__(self):
        QObject.__init__(self)
        self.logger = Logger()
        self.__registered_tasks = dict()
        self.st_config = TransitionalSystemConfig()

    @_with_override_cursor
    def __retrieve_tasks(self, st_user, task_type=None, task_status=None):
        headers = {
            'Authorization': "Bearer {}".format(st_user.get_token()),
            # 'User-Agent': "PostmanRuntime/7.20.1",
            'Accept': "*/*",
            'Cache-Control': "no-cache",
            # 'Postman-Token': "987c7fbf-af4d-42e8-adee-687f35f4a4a0,0547120a-6f8e-42a8-b97f-f052602cc7ff",
            # 'Host': "st.local:8090",
            'Accept-Encoding': "gzip, deflate",
            'Connection': "keep-alive",
            'cache-control': "no-cache"
        }

        try:
            self.logger.debug(__name__, "Retrieving tasks from server...")
            response = requests.request("GET", self.st_config.ST_GET_TASKS_SERVICE_URL, headers=headers)
        except requests.ConnectionError as e:
            msg = self.st_config.ST_CONNECTION_ERROR_MSG.format(e)
            self.logger.warning(__name__, msg)
            return False, msg

        status_OK = response.status_code == 200
        if status_OK:
            # Parse, create and register tasks
            response_data = json.loads(response.text)
            for task_data in response_data:
                task = STTask(task_data)
                if task.is_valid():
                    self.__register_task(task)
        else:
             if response.status_code == 500:
                 self.logger.warning(__name__, self.st_config.ST_STATUS_500_MSG)
             elif response.status_code > 500 and response.status_code < 600:
                 self.logger.warning(__name__, self.st_config.ST_STATUS_GT_500_MSG)
             elif response.status_code == 401:
                 self.logger.warning(__name__, self.st_config.ST_STATUS_401_MSG)

    def get_tasks(self, st_user, task_type=None, task_status=None):
        """
        Go to server for current tasks per user
        :param st_user:
        :param task_type: To filter task types. Still unused.
        :param task_status: To filter task statuses. Still unused.
        :return: dict of task ids with the corresponding task object
        """
        # Each call refreshes the registered tasks.
        self.unregister_tasks()
        self.__retrieve_tasks(st_user, task_type, task_status)

        return self.__registered_tasks

    def get_task(self, task_id):
        task = self.__registered_tasks[task_id] if task_id in self.__registered_tasks else None
        if task is None:
            self.logger.warning(__name__, "Task {} not found!!!".format(task_id))
        else:
            self.logger.info(__name__, "Task {} found!!!".format(task_id))
        return task

    def __register_task(self, task):
        self.logger.debug(__name__, "Task {} registered!".format(task.get_id()))
        self.__registered_tasks[task.get_id()] = task

    def __unregister_task(self, task_id):
        self.logger.debug(__name__, "Task {} unregistered!".format(task_id))
        self.__registered_tasks[task_id] = None
        del self.__registered_tasks[task_id]

    def unregister_tasks(self):
        for k,v in self.__registered_tasks.items():
            self.__registered_tasks[k] = None

        self.__registered_tasks = dict()
        self.logger.info(__name__, "All tasks have been unregistered!")

    @_with_override_cursor
    def start_task(self, st_user, task_id):
        payload = {}
        headers = {
            'Authorization': "Bearer {}".format(st_user.get_token()),
        }

        try:
            self.logger.debug(__name__, "Telling the server to start a task...")
            response = requests.request("PUT", self.st_config.ST_START_TASK_SERVICE_URL.format(task_id), headers=headers, data=payload)
        except requests.ConnectionError as e:
            msg = self.st_config.ST_CONNECTION_ERROR_MSG.format(e)
            self.logger.warning(__name__, msg)
            return False, msg

        status_OK = response.status_code == 200
        if status_OK:
            # Parse response
            response_data = json.loads(response.text)
            self.logger.info(__name__, "Task id '{}' started in server!...".format(task_id))
            self.logger.info_msg(__name__, QCoreApplication.translate("TaskManager",
                                                                      "The task '{}' was successfully started!".format(
                                                                          self.get_task(task_id).get_name())))
            self.update_task_info(task_id, response_data)
            self.task_started.emit(task_id)
        else:
            if response.status_code == 500:
                self.logger.warning(__name__, self.st_config.ST_STATUS_500_MSG)
            elif response.status_code > 500 and response.status_code < 600:
                self.logger.warning(__name__, self.st_config.ST_STATUS_GT_500_MSG)
            elif response.status_code == 401:
                self.logger.warning(__name__, self.st_config.ST_STATUS_401_MSG)
            else:
                self.logger.warning(__name__, "Status code not handled: {}".format(response.status_code))

    @_with_override_cursor
    def cancel_task(self, st_user, task_id, reason):
        payload = json.dumps({"reason": reason})
        headers = {
            'Authorization': "Bearer {}".format(st_user.get_token()),
            'Content-Type': 'application/json'
        }

        try:
            self.logger.debug(__name__, "Telling the server to cancel a task...")
            response = requests.request("PUT", self.st_config.ST_CANCEL_TASK_SERVICE_URL.format(task_id), headers=headers, data=payload)
        except requests.ConnectionError as e:
            msg = self.st_config.ST_CONNECTION_ERROR_MSG.format(e)
            self.logger.warning(__name__, msg)
            return False, msg

        status_OK = response.status_code == 200
        if status_OK:
            # No need to parse response this time, we'll ask tasks from server again anyways
            self.logger.info(__name__, "Task id '{}' canceled in server!".format(task_id))
            self.logger.info_msg(__name__, QCoreApplication.translate("TaskManager", "The task '{}' was successfully canceled!".format(self.get_task(task_id).get_name())))
            self.task_canceled.emit(task_id)
        else:
            if response.status_code == 500:
                self.logger.warning(__name__, self.st_config.ST_STATUS_500_MSG)
            elif response.status_code > 500 and response.status_code < 600:
                self.logger.warning(__name__, self.st_config.ST_STATUS_GT_500_MSG)
            elif response.status_code == 401:
                self.logger.warning(__name__, self.st_config.ST_STATUS_401_MSG)
            else:
                self.logger.warning(__name__, "Status code not handled: {}, payload: {}".format(response.status_code, payload))

    @_with_override_cursor
    def close_task(self, st_user, task_id):
        payload = {}
        headers = {
            'Authorization': "Bearer {}".format(st_user.get_token()),
        }

        try:
            self.logger.debug(__name__, "Telling the server to close a task...")
            response = requests.request("PUT", self.st_config.ST_CLOSE_TASK_SERVICE_URL.format(task_id), headers=headers, data=payload)
        except requests.ConnectionError as e:
            msg = self.st_config.ST_CONNECTION_ERROR_MSG.format(e)
            self.logger.warning(__name__, msg)
            return False, msg

        status_OK = response.status_code == 200
        if status_OK:
            # No need to parse response this time, we'll ask tasks from server again anyways
            self.logger.success(__name__, "Task id '{}' closed in server!".format(task_id))
            self.logger.success_msg(__name__, QCoreApplication.translate("TaskManager",
                                                                      "The task '{}' was successfully closed!".format(
                                                                          self.get_task(task_id).get_name())))
            self.task_closed.emit(task_id)
        else:
            if response.status_code == 500:
                self.logger.warning(__name__, self.st_config.ST_STATUS_500_MSG)
            elif response.status_code > 500 and response.status_code < 600:
                self.logger.warning(__name__, self.st_config.ST_STATUS_GT_500_MSG)
            elif response.status_code == 401:
                self.logger.warning(__name__, self.st_config.ST_STATUS_401_MSG)
            elif response.status_code == 422:
                response_data = json.loads(response.text)
                msg = QCoreApplication.translate("STSession", QCoreApplication.translate("TaskManager",
                    "Task not closed! Details: {}").format(response_data['message'] if 'message' in response_data else "Unreadable response from server."))
                self.logger.warning_msg(__name__, msg)
            else:
                self.logger.warning(__name__, "Status code not handled: {}".format(response.status_code))

    def update_task_info(self, task_id, task_data):
        task = STTask(task_data)
        if task.is_valid():
            self.__unregister_task(task_id)
            self.__register_task(task)
Esempio n. 9
0
class ToolBar(QObject):
    def __init__(self, iface, qgis_utils):
        QObject.__init__(self)
        self.iface = iface
        self.qgis_utils = qgis_utils
        self.logger = Logger()
        self.geometry = GeometryUtils()

    def build_boundary(self, db):
        QgsProject.instance().setAutoTransaction(False)
        layer = self.qgis_utils.get_layer_from_layer_tree(
            db, db.names.OP_BOUNDARY_T)
        use_selection = True

        if layer is None:
            self.logger.message_with_button_load_layer_emitted.emit(
                QCoreApplication.translate(
                    "ToolBar", "First load the layer {} into QGIS!").format(
                        db.names.OP_BOUNDARY_T),
                QCoreApplication.translate("ToolBar",
                                           "Load layer {} now").format(
                                               db.names.OP_BOUNDARY_T),
                db.names.OP_BOUNDARY_T, Qgis.Warning)
            return
        else:
            if layer.selectedFeatureCount() == 0:

                reply = QMessageBox.question(
                    None, QCoreApplication.translate("ToolBar", "Continue?"),
                    QCoreApplication.translate(
                        "ToolBar",
                        "There are no selected boundaries. Do you want to use all the {} boundaries in the database?"
                    ).format(layer.featureCount()),
                    QMessageBox.Yes | QMessageBox.Cancel, QMessageBox.Cancel)
                if reply == QMessageBox.Yes:
                    use_selection = False
                elif reply == QMessageBox.Cancel:
                    self.logger.warning_msg(
                        __name__,
                        QCoreApplication.translate(
                            "ToolBar", "First select at least one boundary!"))
                    return

        if use_selection:
            new_boundary_geoms, boundaries_to_del_ids = self.geometry.fix_selected_boundaries(
                db.names, layer, db.names.T_ID_F)
            num_boundaries = layer.selectedFeatureCount()
        else:
            new_boundary_geoms, boundaries_to_del_ids = self.geometry.fix_boundaries(
                layer, db.names.T_ID_F)
            num_boundaries = layer.featureCount()

        if len(new_boundary_geoms) > 0:
            layer.startEditing(
            )  # Safe, even if layer is already on editing state

            # the boundaries that are to be replaced are removed
            layer.deleteFeatures(boundaries_to_del_ids)

            # Create features based on segment geometries
            new_fix_boundary_features = list()
            for boundary_geom in new_boundary_geoms:
                feature = QgsVectorLayerUtils().createFeature(
                    layer, boundary_geom)

                # TODO: Remove when local id and working space are defined
                feature.setAttribute(db.names.OID_T_LOCAL_ID_F, 1)
                feature.setAttribute(db.names.OID_T_NAMESPACE_F,
                                     db.names.OP_BOUNDARY_T)

                new_fix_boundary_features.append(feature)

            layer.addFeatures(new_fix_boundary_features)
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "{} feature(s) was(were) analyzed generating {} boundary(ies)!"
                ).format(num_boundaries, len(new_fix_boundary_features)))
            self.iface.mapCanvas().refresh()
        else:
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar", "There are no boundaries to build."))

    def fill_topology_table_pointbfs(self, db, use_selection=True):
        layers = {
            db.names.OP_BOUNDARY_T: {
                'name': db.names.OP_BOUNDARY_T,
                'geometry': None,
                LAYER: None
            },
            db.names.POINT_BFS_T: {
                'name': db.names.POINT_BFS_T,
                'geometry': None,
                LAYER: None
            },
            db.names.OP_BOUNDARY_POINT_T: {
                'name': db.names.OP_BOUNDARY_POINT_T,
                'geometry': None,
                LAYER: None
            }
        }

        self.qgis_utils.get_layers(db, layers, load=True)
        if not layers:
            return None

        if use_selection:
            if layers[
                    db.names.OP_BOUNDARY_T][LAYER].selectedFeatureCount() == 0:
                if self.qgis_utils.get_layer_from_layer_tree(
                        db, db.names.OP_BOUNDARY_T) is None:
                    self.logger.message_with_button_load_layer_emitted.emit(
                        QCoreApplication.translate(
                            "ToolBar",
                            "First load the layer {} into QGIS and select at least one boundary!"
                        ).format(db.names.OP_BOUNDARY_T),
                        QCoreApplication.translate("ToolBar",
                                                   "Load layer {} now").format(
                                                       db.names.OP_BOUNDARY_T),
                        db.names.OP_BOUNDARY_T, Qgis.Warning)
                else:
                    reply = QMessageBox.question(
                        None,
                        QCoreApplication.translate("ToolBar", "Continue?"),
                        QCoreApplication.translate(
                            "ToolBar",
                            "There are no selected boundaries. Do you want to fill the '{}' table for all the {} boundaries in the database?"
                        ).format(
                            db.names.POINT_BFS_T, layers[
                                db.names.OP_BOUNDARY_T][LAYER].featureCount()),
                        QMessageBox.Yes | QMessageBox.Cancel,
                        QMessageBox.Cancel)
                    if reply == QMessageBox.Yes:
                        use_selection = False
                    elif reply == QMessageBox.Cancel:
                        self.logger.warning_msg(
                            __name__,
                            QCoreApplication.translate(
                                "ToolBar",
                                "First select at least one boundary!"))
                        return
            else:
                reply = QMessageBox.question(
                    None, QCoreApplication.translate("ToolBar", "Continue?"),
                    QCoreApplication.translate(
                        "ToolBar",
                        "There are {selected} boundaries selected. Do you want to fill the '{table}' table just for the selected boundaries?\n\nIf you say 'No', the '{table}' table will be filled for all boundaries in the database."
                    ).format(selected=layers[db.names.OP_BOUNDARY_T]
                             [LAYER].selectedFeatureCount(),
                             table=db.names.POINT_BFS_T),
                    QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel,
                    QMessageBox.Cancel)
                if reply == QMessageBox.Yes:
                    use_selection = True
                elif reply == QMessageBox.No:
                    use_selection = False
                elif reply == QMessageBox.Cancel:
                    return

        bfs_features = layers[db.names.POINT_BFS_T][LAYER].getFeatures()

        # Get unique pairs id_boundary-id_boundary_point
        existing_pairs = [
            (bfs_feature[db.names.POINT_BFS_T_OP_BOUNDARY_F],
             bfs_feature[db.names.POINT_BFS_T_OP_BOUNDARY_POINT_F])
            for bfs_feature in bfs_features
        ]
        existing_pairs = set(existing_pairs)

        id_pairs = self.geometry.get_pair_boundary_boundary_point(
            layers[db.names.OP_BOUNDARY_T][LAYER],
            layers[db.names.OP_BOUNDARY_POINT_T][LAYER],
            db.names.T_ID_F,
            use_selection=use_selection)

        if id_pairs:
            layers[db.names.POINT_BFS_T][LAYER].startEditing()
            features = list()
            for id_pair in id_pairs:
                if not id_pair in existing_pairs:  # Avoid duplicated pairs in the DB
                    # Create feature
                    feature = QgsVectorLayerUtils().createFeature(
                        layers[db.names.POINT_BFS_T][LAYER])
                    feature.setAttribute(db.names.POINT_BFS_T_OP_BOUNDARY_F,
                                         id_pair[0])
                    feature.setAttribute(
                        db.names.POINT_BFS_T_OP_BOUNDARY_POINT_F, id_pair[1])
                    features.append(feature)
            layers[db.names.POINT_BFS_T][LAYER].addFeatures(features)
            layers[db.names.POINT_BFS_T][LAYER].commitChanges()
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "{} out of {} records were saved into {}! {} out of {} records already existed in the database."
                ).format(len(features), len(id_pairs), db.names.POINT_BFS_T,
                         len(id_pairs) - len(features), len(id_pairs)))
        else:
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "No pairs id_boundary-id_boundary_point found."))

    def fill_topology_tables_morebfs_less(self, db, use_selection=True):
        layers = {
            db.names.OP_PLOT_T: {
                'name': db.names.OP_PLOT_T,
                'geometry': QgsWkbTypes.PolygonGeometry,
                LAYER: None
            },
            db.names.MORE_BFS_T: {
                'name': db.names.MORE_BFS_T,
                'geometry': None,
                LAYER: None
            },
            db.names.LESS_BFS_T: {
                'name': db.names.LESS_BFS_T,
                'geometry': None,
                LAYER: None
            },
            db.names.OP_BOUNDARY_T: {
                'name': db.names.OP_BOUNDARY_T,
                'geometry': None,
                LAYER: None
            }
        }

        self.qgis_utils.get_layers(db, layers, load=True)
        if not layers:
            return None

        if use_selection:
            if layers[db.names.OP_PLOT_T][LAYER].selectedFeatureCount() == 0:
                if self.qgis_utils.get_layer_from_layer_tree(
                        db,
                        db.names.OP_PLOT_T,
                        geometry_type=QgsWkbTypes.PolygonGeometry) is None:
                    self.logger.message_with_button_load_layer_emitted.emit(
                        QCoreApplication.translate(
                            "ToolBar",
                            "First load the layer {} into QGIS and select at least one plot!"
                        ).format(db.names.OP_PLOT_T),
                        QCoreApplication.translate("ToolBar",
                                                   "Load layer {} now").format(
                                                       db.names.OP_PLOT_T),
                        db.names.OP_PLOT_T, Qgis.Warning)
                else:
                    reply = QMessageBox.question(
                        None,
                        QCoreApplication.translate("ToolBar", "Continue?"),
                        QCoreApplication.translate(
                            "ToolBar",
                            "There are no selected plots. Do you want to fill the '{more}' and '{less}' tables for all the {all} plots in the database?"
                        ).format(more=db.names.MORE_BFS_T,
                                 less=db.names.LESS_BFS_T,
                                 all=layers[db.names.OP_PLOT_T]
                                 [LAYER].featureCount()),
                        QMessageBox.Yes | QMessageBox.Cancel,
                        QMessageBox.Cancel)
                    if reply == QMessageBox.Yes:
                        use_selection = False
                    elif reply == QMessageBox.Cancel:
                        self.logger.warning_msg(
                            __name__,
                            QCoreApplication.translate(
                                "ToolBar", "First select at least one plot!"))
                        return
            else:
                reply = QMessageBox.question(
                    None, QCoreApplication.translate("ToolBar", "Continue?"),
                    QCoreApplication.translate(
                        "ToolBar",
                        "There are {selected} plots selected. Do you want to fill the '{more}' and '{less}' tables just for the selected plots?\n\nIf you say 'No', the '{more}' and '{less}' tables will be filled for all plots in the database."
                    ).format(selected=layers[db.names.OP_PLOT_T]
                             [LAYER].selectedFeatureCount(),
                             more=db.names.MORE_BFS_T,
                             less=db.names.LESS_BFS_T),
                    QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel,
                    QMessageBox.Cancel)
                if reply == QMessageBox.Yes:
                    use_selection = True
                elif reply == QMessageBox.No:
                    use_selection = False
                elif reply == QMessageBox.Cancel:
                    return

        more_bfs_features = layers[db.names.MORE_BFS_T][LAYER].getFeatures()
        less_features = layers[db.names.LESS_BFS_T][LAYER].getFeatures()

        # Get unique pairs id_boundary-id_plot in both tables
        existing_more_pairs = [
            (more_bfs_feature[db.names.MORE_BFS_T_OP_PLOT_F],
             more_bfs_feature[db.names.MORE_BFS_T_OP_BOUNDARY_F])
            for more_bfs_feature in more_bfs_features
        ]
        existing_more_pairs = set(existing_more_pairs)
        # Todo: Update when ili2db issue is solved.
        # Todo: When an abstract class only implements a concrete class, the name of the attribute is different if two or more classes are implemented.
        existing_less_pairs = [
            (less_feature[db.names.LESS_BFS_T_OP_PLOT_F],
             less_feature[db.names.LESS_BFS_T_OP_BOUNDARY_F])
            for less_feature in less_features
        ]
        existing_less_pairs = set(existing_less_pairs)

        id_more_pairs, id_less_pairs = self.geometry.get_pair_boundary_plot(
            layers[db.names.OP_BOUNDARY_T][LAYER],
            layers[db.names.OP_PLOT_T][LAYER],
            db.names.T_ID_F,
            use_selection=use_selection)
        if id_less_pairs:
            layers[db.names.LESS_BFS_T][LAYER].startEditing()
            features = list()
            for id_pair in id_less_pairs:
                if not id_pair in existing_less_pairs:  # Avoid duplicated pairs in the DB
                    # Create feature
                    feature = QgsVectorLayerUtils().createFeature(
                        layers[db.names.LESS_BFS_T][LAYER])
                    feature.setAttribute(db.names.LESS_BFS_T_OP_PLOT_F,
                                         id_pair[0])
                    # Todo: Update LESS_BFS_T_OP_BOUNDARY_F by LESS_BFS_T_OP_BOUNDARY_F.
                    # Todo: When an abstract class only implements a concrete class, the name of the attribute is different if two or more classes are implemented.
                    feature.setAttribute(db.names.LESS_BFS_T_OP_BOUNDARY_F,
                                         id_pair[1])
                    features.append(feature)
            layers[db.names.LESS_BFS_T][LAYER].addFeatures(features)
            layers[db.names.LESS_BFS_T][LAYER].commitChanges()
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "{} out of {} records were saved into '{}'! {} out of {} records already existed in the database."
                ).format(len(features), len(id_less_pairs),
                         db.names.LESS_BFS_T,
                         len(id_less_pairs) - len(features),
                         len(id_less_pairs)))
        else:
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "No pairs id_boundary-id_plot found for '{}' table.").
                format(db.names.LESS_BFS_T))

        if id_more_pairs:
            layers[db.names.MORE_BFS_T][LAYER].startEditing()
            features = list()
            for id_pair in id_more_pairs:
                if not id_pair in existing_more_pairs:  # Avoid duplicated pairs in the DB
                    # Create feature
                    feature = QgsVectorLayerUtils().createFeature(
                        layers[db.names.MORE_BFS_T][LAYER])
                    feature.setAttribute(db.names.MORE_BFS_T_OP_PLOT_F,
                                         id_pair[0])
                    feature.setAttribute(db.names.MORE_BFS_T_OP_BOUNDARY_F,
                                         id_pair[1])
                    features.append(feature)
            layers[db.names.MORE_BFS_T][LAYER].addFeatures(features)
            layers[db.names.MORE_BFS_T][LAYER].commitChanges()
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "{} out of {} records were saved into '{}'! {} out of {} records already existed in the database."
                ).format(len(features), len(id_more_pairs),
                         db.names.MORE_BFS_T,
                         len(id_more_pairs) - len(features),
                         len(id_more_pairs)))
        else:
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "No pairs id_boundary-id_plot found for '{}' table.").
                format(db.names.MORE_BFS_T))
Esempio n. 10
0
class BaseSplitDataForReceiversPanelWidget(QgsPanelWidget, WIDGET_UI):
    refresh_parcel_data_clear_selection_requested = pyqtSignal()

    def __init__(self, parent, controller):
        QgsPanelWidget.__init__(self, parent)
        self.setupUi(self)
        self.parent = parent
        self._controller = controller

        self.logger = Logger()
        self.app = AppInterface()

        self.setDockMode(True)
        self.setPanelTitle(QCoreApplication.translate("BaseSplitDataForReceiversPanelWidget", "Convert to offline"))
        self.parent.setWindowTitle(QCoreApplication.translate("BaseSplitDataForReceiversPanelWidget", "Allocate parcels"))

        self.mQgsFileWidget.lineEdit().setPlaceholderText(QCoreApplication.translate("BaseSplitDataForReceiversPanelWidget", "Choose the output folder..."))
        self.mQgsFileWidget.setDefaultRoot(self.app.settings.export_dir_field_data)

        self.panelAccepted.connect(self.panel_accepted)
        self._controller.export_field_data_progress.connect(self.update_progress)
        self.btn_split_data.clicked.connect(self.export_field_data)

        self.fill_data()

    def panel_accepted(self):
        self.refresh_parcel_data_clear_selection_requested.emit()

    def fill_data(self):
        summary_data = self._controller.get_summary_data()
        row = 1
        for row_data in summary_data:
            receiver_name, parcel_count = row_data
            self.fill_row(receiver_name, parcel_count, row)
            row += 2  # v_spacer + label

        # After the real data, we add a new spacer that expands itself to shrink content upwards
        v_spacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
        self.grb_summary.layout().addItem(v_spacer, row, 0)

        # Show/hide warning depending on if there are not allocated parcels
        not_allocated_parcels = self._controller.get_count_of_not_allocated_parcels()
        self.lbl_warning.setVisible(not_allocated_parcels)
        self.lbl_not_allocated_parcels.setVisible(not_allocated_parcels)
        self.lbl_not_allocated_parcels.setText(QCoreApplication.translate("BaseSplitDataForReceiversPanelWidget",
                                               "{} parcels have not been yet allocated!").format(not_allocated_parcels))

    def fill_row(self, receiver_name, parcel_count, row):
        # First add a spacer between previoud data row (could be the title) and the next row data
        v_spacer = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Preferred)
        self.grb_summary.layout().addItem(v_spacer, row, 0)

        # Now, let's add a row of data
        w = QLabel(receiver_name)
        self.grb_summary.layout().addWidget(w, row+1, 0)
        w = QLabel(str(parcel_count))
        w.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        self.grb_summary.layout().addWidget(w, row+1, 2)

    def export_field_data(self):
        self.logger.clear_message_bar()
        export_dir = self.mQgsFileWidget.filePath()

        if export_dir and os.path.isdir(export_dir):
            self.app.settings.export_dir_field_data = export_dir
            self.prb_export_field_data.setRange(0, 100)
            self.prb_export_field_data.setValue(0)

            res, msg = self._controller.export_field_data(export_dir)

            self.logger.success_warning(__name__, res, msg, EnumLogHandler.MESSAGE_BAR)
        else:
            self.logger.warning_msg(__name__, QCoreApplication.translate("BaseSplitDataForReceiversPanelWidget", "The output folder is invalid. Choose a valid folder."))

    def update_progress(self, progress):
        self.prb_export_field_data.setValue(progress)
class AboutDialog(QDialog, DIALOG_UI):
    message_with_button_open_about_emitted = pyqtSignal(str)

    def __init__(self, qgis_utils):
        QDialog.__init__(self)
        self.setupUi(self)
        self.qgis_utils = qgis_utils
        self.logger = Logger()
        self.check_local_help()

        self.tb_changelog.setOpenExternalLinks(True)

        if QGIS_LANG == 'en':
            file = QFile(
                ":/Asistente-LADM_COL/resources/html/Changelog_en.html")
        else:
            file = QFile(":/Asistente-LADM_COL/resources/html/Changelog.html")

        if not file.open(QIODevice.ReadOnly | QIODevice.Text):
            raise Exception(file.errorString())

        stream = QTextStream(file)
        stream.setCodec("UTF-8")

        self.tb_changelog.setHtml(stream.readAll())

    def check_local_help(self):
        try:
            self.btn_download_help.clicked.disconnect(self.show_help)
        except TypeError as e:
            pass
        try:
            self.btn_download_help.clicked.disconnect(self.download_help)
        except TypeError as e:
            pass

        if os.path.exists(
                os.path.join(PLUGIN_DIR, HELP_DIR_NAME, QGIS_LANG,
                             'index.html')):
            self.btn_download_help.setText(
                QCoreApplication.translate("AboutDialog",
                                           "Open help from local folder"))
            self.btn_download_help.clicked.connect(self.show_help)
        else:
            self.btn_download_help.setText(
                QCoreApplication.translate("AboutDialog",
                                           "Download help for offline access"))
            self.btn_download_help.clicked.connect(self.download_help)

    def save_file(self, fetcher_task):
        if fetcher_task.reply() is not None:
            tmpFile = tempfile.mktemp()
            tmpFold = tempfile.mktemp()
            outFile = QFile(tmpFile)
            outFile.open(QIODevice.WriteOnly)
            outFile.write(fetcher_task.reply().readAll())
            outFile.close()

            try:
                with zipfile.ZipFile(tmpFile, "r") as zip_ref:
                    zip_ref.extractall(tmpFold)
                    languages = glob.glob(
                        os.path.join(tmpFold, 'asistente_ladm_col_docs/*'))

                    for language in languages:
                        shutil.move(
                            language,
                            os.path.join(PLUGIN_DIR, HELP_DIR_NAME,
                                         language[-2:]))

            except zipfile.BadZipFile as e:
                self.logger.warning_msg(
                    __name__,
                    QCoreApplication.translate(
                        "AboutDialog",
                        "There was an error with the download. The downloaded file is invalid."
                    ))
            else:
                self.message_with_button_open_about_emitted.emit(
                    QCoreApplication.translate(
                        "AboutDialog",
                        "Help files were successfully downloaded and can be accessed offline from the About dialog!"
                    ))

            try:
                os.remove(tmpFile)
                os.remove(tmpFold)
            except:
                pass

        self.check_local_help()

    def download_help(self):
        if self.qgis_utils.is_connected(TEST_SERVER):
            self.btn_download_help.setEnabled(False)
            url = '/'.join([
                HELP_DOWNLOAD, PLUGIN_VERSION,
                'asistente_ladm_col_docs_{lang}.zip'.format(lang=QGIS_LANG)
            ])
            fetcher_task = QgsNetworkContentFetcherTask(QUrl(url))
            fetcher_task.taskCompleted.connect(self.enable_download_button)
            fetcher_task.fetched.connect(partial(self.save_file, fetcher_task))
            QgsApplication.taskManager().addTask(fetcher_task)
        else:
            self.logger.warning_msg(
                __name__,
                QCoreApplication.translate(
                    "AboutDialog",
                    "There was a problem connecting to Internet."))

    def enable_download_button(self):
        self.btn_download_help.setEnabled(True)
        self.check_local_help()

    def show_help(self):
        self.qgis_utils.show_help('', offline=True)
Esempio n. 12
0
class ManualFeatureCreator(QObject, metaclass=AbstractQObjectMeta):
    finish_feature_creation = pyqtSignal(str, list)
    form_rejected = pyqtSignal()
    exec_form_advanced = pyqtSignal(FeatureFormArgs)
    form_feature_showing = pyqtSignal(FeatureFormArgs)

    def __init__(self, iface, layer, feature_name):
        QObject.__init__(self)
        self._iface = iface
        self._app = AppInterface()
        self._layer = layer

        self._logger = Logger()
        self._feature_name = feature_name

    def create(self):
        layer = self._get_editing_layer()
        self.__prepare_layer(layer)

        self._add_feature(layer)

    def disconnect_signals(self):
        try:
            self._layer.committedFeaturesAdded.disconnect(
                self.__finish_feature_creation)
        except:
            # TODO specify what type of exception is caught
            pass

    @abstractmethod
    def _add_feature(self, layer):
        pass

    @abstractmethod
    def _get_editing_layer(self):
        pass

    def __prepare_layer(self, layer):
        self._iface.layerTreeView().setCurrentLayer(layer)
        # The original layer. It is not the editing layer
        self._layer.committedFeaturesAdded.connect(
            self.__finish_feature_creation)

        if not layer.isEditable():
            layer.startEditing()

    def _exec_form(self, layer, feature):
        form_feature_showing_args = FeatureFormArgs(layer, feature)
        self.form_feature_showing.emit(form_feature_showing_args)
        dialog = self._iface.getFeatureForm(layer, feature)

        dialog.rejected.connect(self.form_rejected)
        dialog.setModal(True)

        if dialog.exec_():
            args = FeatureFormArgs(layer, feature)
            self.exec_form_advanced.emit(args)
            saved = layer.commitChanges()

            if not saved:
                layer.rollBack()
                self._logger.warning_msg(
                    __name__,
                    QCoreApplication.translate(
                        "WizardTranslations",
                        "Error while saving changes. {} could not be created."
                    ).format(self._feature_name))

                for e in layer.commitErrors():
                    self._logger.warning(__name__,
                                         "Commit error: {}".format(e))
        else:
            layer.rollBack()
        self._iface.mapCanvas().refresh()
        dialog.rejected.disconnect(self.form_rejected)

    def __finish_feature_creation(self, layerId, features):
        self._layer.committedFeaturesAdded.disconnect(
            self.__finish_feature_creation)
        self._logger.info(
            __name__, "{} committedFeaturesAdded SIGNAL disconnected".format(
                self._feature_name))
        self.finish_feature_creation.emit(layerId, features)
Esempio n. 13
0
class QualityRulesInitialPanelWidget(QgsPanelWidget, WIDGET_UI):

    def __init__(self, controller, parent=None):
        QgsPanelWidget.__init__(self, None)
        self.setupUi(self)
        self.__controller = controller
        self.parent = parent
        self.app = AppInterface()
        self.logger = Logger()
        self.__mode = EnumQualityRulePanelMode.VALIDATE

        self.__selected_items_list = list()
        self.__icon_names = ['schema.png', 'points.png', 'lines.png', 'polygons.png', 'relationships.svg']

        self.txt_search.addAction(QIcon(":/Asistente-LADM-COL/resources/images/search.png"), QLineEdit.LeadingPosition)

        self.setDockMode(True)
        self.setPanelTitle(QCoreApplication.translate("QualityRulesInitialPanelWidget", "Quality Rules"))

        self.tab.setTabEnabled(TAB_READ_INDEX, False)  # TODO: Remove when implemented
        self.__restore_settings()

        # TODO: uncomment the following block when implemented
        self.lbl_presets.setEnabled(False)
        self.cbo_presets.setEnabled(False)
        self.btn_save_selection.setEnabled(False)
        self.btn_delete_selection.setEnabled(False)

        self.txt_search.textChanged.connect(self.__search_text_changed)
        self.btn_validate.clicked.connect(self.__validate_clicked)
        self.btn_read.clicked.connect(self.__read_clicked)
        self.btn_help.clicked.connect(self.__show_help)
        self.trw_qrs.itemSelectionChanged.connect(self.__selection_changed)
        self.tab.currentChanged.connect(self.__tab_changed)

        dir_selector = make_folder_selector(self.txt_dir_path,
                                            title=QCoreApplication.translate("QualityRulesInitialPanelWidget",
                                                                             "Select a folder to store quality errors"),
                                            parent=None,
                                            setting_property="qr_results_dir_path")
        self.btn_dir_path.clicked.connect(dir_selector)

        db_file_selector = make_file_selector(self.txt_db_path,
                                              title=QCoreApplication.translate("QualityRulesInitialPanelWidget",
                                                                               "Open GeoPackage database file with quality errors"),
                                              file_filter=QCoreApplication.translate("QualityRulesInitialPanelWidget",
                                                                                     "GeoPackage Database (*.gpkg)"),
                                              setting_property="qr_db_file_path")

        self.btn_db_path.clicked.connect(db_file_selector)

        self.__tab_changed(self.tab.currentIndex())  # Direct call to initialize controls

    def __tab_changed(self, index):
        self.__activate_mode(
            EnumQualityRulePanelMode.VALIDATE if index == TAB_VALIDATE_INDEX else EnumQualityRulePanelMode.READ)

    def __activate_mode(self, mode):
        self.__mode = mode
        validate_mode = mode == EnumQualityRulePanelMode.VALIDATE

        # First reset both search and selection
        self.txt_search.setText('')
        self.trw_qrs.clearSelection()
        self.__selected_items_list = list()

        self.trw_qrs.setEnabled(validate_mode)

        self.btn_validate.setVisible(validate_mode)
        self.lbl_selected_count.setVisible(validate_mode)
        self.btn_read.setVisible(not validate_mode)

        self.lbl_presets.setVisible(validate_mode)
        self.cbo_presets.setVisible(validate_mode)
        self.btn_save_selection.setVisible(validate_mode)
        self.btn_delete_selection.setVisible(validate_mode)

        # Load available rules for current role and current db models
        self.__load_available_rules()

    def __load_available_rules(self):
        self.__controller.load_tree_data(self.__mode)
        self.__update_available_rules()

    def __update_available_rules(self):
        self.trw_qrs.setUpdatesEnabled(False)  # Don't render until we're ready

        # Grab some context data
        top_level_items_expanded_info = dict()
        for i in range(self.trw_qrs.topLevelItemCount()):
            top_level_items_expanded_info[self.trw_qrs.topLevelItem(i).text(0)] = self.trw_qrs.topLevelItem(i).isExpanded()

        # Save selection
        self.__update_selected_items()

        # Iterate qr types adding children
        self.trw_qrs.blockSignals(True)  # We don't want to get itemSelectionChanged here
        self.trw_qrs.clear()
        self.trw_qrs.blockSignals(False)

        bold_font = QFont()
        bold_font.setBold(True)

        sorted_types = sorted(self.__controller.get_qrs_tree_data().keys())
        for type_enum in sorted_types:
            children = []
            type_item = QTreeWidgetItem([self.__controller.get_tr_string(type_enum)])

            # Filter by search text
            list_qrs = self.__filter_by_search_text(type_enum, self.txt_search.text())

            for qr in list_qrs:
                qr_item = QTreeWidgetItem([qr.name()])
                qr_item.setData(0, Qt.UserRole, qr.id())
                qr_item.setData(0, Qt.ToolTipRole, "{}\n{}".format(qr.name(), qr.id()))
                children.append(qr_item)

            if children:
                icon_name = self.__icon_names[type_enum.value]
                icon = QIcon(":/Asistente-LADM-COL/resources/images/{}".format(icon_name))
                type_item.setData(0, Qt.DecorationRole, icon)
                type_item.setData(0, Qt.FontRole, bold_font)
                type_item.addChildren(children)
                self.trw_qrs.addTopLevelItem(type_item)

        # Set selection
        iterator = QTreeWidgetItemIterator(self.trw_qrs, QTreeWidgetItemIterator.Selectable)
        self.trw_qrs.blockSignals(True)  # We don't want to get itemSelectionChanged here
        while iterator.value():
            item = iterator.value()
            if item.data(0, Qt.UserRole) in self.__selected_items_list:
                item.setSelected(True)

            iterator += 1

        self.trw_qrs.blockSignals(False)

        # Make type items non selectable
        # Set expand taking previous states into account
        for i in range(self.trw_qrs.topLevelItemCount()):
            self.trw_qrs.topLevelItem(i).setFlags(Qt.ItemIsEnabled)  # Not selectable
            self.trw_qrs.topLevelItem(i).setExpanded(top_level_items_expanded_info.get(self.trw_qrs.topLevelItem(i).text(0), True))

        self.trw_qrs.setUpdatesEnabled(True)  # Now render!

    def __filter_by_search_text(self, type, search_text):
        res = list(self.__controller.get_qrs_tree_data()[type].values())

        search_text = search_text.lower().strip()
        if len(search_text) > 1:
            to_delete = list()
            for qr_obj in res:
                # First check in QR id
                found = search_text in qr_obj.id().lower()

                if not found:
                    # Now search in QR tags
                    for tag in qr_obj.tags():
                        if search_text in tag:
                            found = True
                            continue

                    if not found:
                        to_delete.append(qr_obj)

            for qr_obj in to_delete:
                res.remove(qr_obj)

        return res

    def __update_selected_items(self):
        iterator = QTreeWidgetItemIterator(self.trw_qrs, QTreeWidgetItemIterator.Selectable)
        while iterator.value():
            item = iterator.value()
            qr_key = item.data(0, Qt.UserRole)

            if item.isSelected():
                if qr_key not in self.__selected_items_list:
                    self.__selected_items_list.append(qr_key)
            else:
                if qr_key in self.__selected_items_list:
                    # It was selected before, but not anymore
                    self.__selected_items_list.remove(qr_key)

            iterator += 1

        self.__update_controls_after_selection_update()

    def __search_text_changed(self, text):
        self.__update_available_rules()

    def __validate_clicked(self):
        self.__save_settings()
        self.__update_selected_items() # Take latest selection into account

        dir_path = self.txt_dir_path.text().strip()
        if not os.path.isdir(dir_path):
            self.logger.warning_msg(__name__, QCoreApplication.translate("QualityRulesInitialPanelWidget",
                                                                         "The dir '{}' does not exist! Select a valid dir to store quality validation results.").format(
                dir_path), 15)
            return

        if len(self.__selected_items_list):
            self.__controller.set_qr_dir_path(dir_path)
            self.__controller.set_selected_qrs(self.__selected_items_list.copy())
            self.__selected_items_list = list()  # Reset
            self.show_general_results_panel()

    def __read_clicked(self):
        self.__save_settings()
        self.show_general_results_panel()

    def __save_settings(self):
        self.app.settings.qr_results_dir_path = self.txt_dir_path.text().strip()
        self.app.settings.qr_db_file_path = self.txt_db_path.text().strip()

    def __restore_settings(self):
        self.txt_dir_path.setText(self.app.settings.qr_results_dir_path)
        self.txt_db_path.setText(self.app.settings.qr_db_file_path)

    def __select_predefined_changed(self, index):
        pass

    def __update_selected_count_label(self):
        selected_count = len(self.__selected_items_list)
        if selected_count == 0:
            text = QCoreApplication.translate("QualityRules", "There are no selected rules to validate")
            btn_text = QCoreApplication.translate("QualityRules", "Validate")
        elif selected_count == 1:
            text = QCoreApplication.translate("QualityRules", "There is 1 selected rule to validate")
            btn_text = QCoreApplication.translate("QualityRules", "Validate 1 rule")
        else:
            text = QCoreApplication.translate("QualityRules",
                                              "There are {} selected rules to validate").format(selected_count)
            btn_text = QCoreApplication.translate("QualityRules", "Validate {} rules").format(selected_count)

        self.lbl_selected_count.setText(text)
        self.btn_validate.setText(btn_text)

    def __selection_changed(self):
        # Update internal dict and dialog label
        self.__update_selected_items()

    def __update_controls_after_selection_update(self):
        # Custom slot to refresh labels and button state
        self.btn_validate.setEnabled(len(self.__selected_items_list))
        self.__update_selected_count_label()

    def show_general_results_panel(self):
        self.parent.show_general_results_panel(self.__mode)

    def __show_help(self):
        show_plugin_help("quality_rules")
Esempio n. 14
0
class AppGUIInterface(QObject):
    add_indicators_requested = pyqtSignal(
        str, QgsLayerTreeNode.NodeType)  # node name, node type

    def __init__(self, iface):
        QObject.__init__(self)
        self.iface = iface

        self.logger = Logger()

    def trigger_add_feature(self):
        self.iface.actionAddFeature().trigger()

    def trigger_vertex_tool(self):
        self.iface.actionVertexTool().trigger()

    def create_progress_message_bar(self, text, progress):
        progressMessageBar = self.iface.messageBar().createMessage(
            PLUGIN_NAME, text)
        progressMessageBar.layout().addWidget(progress)
        self.iface.messageBar().pushWidget(progressMessageBar, Qgis.Info)

    def refresh_layer_symbology(self, layer_id):
        self.iface.layerTreeView().refreshLayerSymbology(layer_id)

    def refresh_map(self):
        self.iface.mapCanvas().refresh()

    def redraw_all_layers(self):
        self.iface.mapCanvas().redrawAllLayers()

    def freeze_map(self, frozen):
        self.iface.mapCanvas().freeze(frozen)

    def activate_layer(self, layer):
        self.iface.layerTreeView().setCurrentLayer(layer)

    def set_node_visibility(self, node, visible=True):
        # Modes may eventually be layer_id, group_name, layer, group
        if node is not None:
            node.setItemVisibilityChecked(visible)

    def remove_error_group(self):
        group = self.get_error_layers_group()
        parent = group.parent()
        parent.removeChildNode(group)

    def clear_status_bar(self):
        self.iface.statusBarIface().clearMessage()

    def add_error_layer(self, db, error_layer):
        group = self.get_error_layers_group()

        # Check if layer is loaded and remove it
        layers = group.findLayers()
        for layer in layers:
            if layer.name() == error_layer.name():
                group.removeLayer(layer.layer())
                break

        added_layer = QgsProject.instance().addMapLayer(error_layer, False)
        index = QgisModelBakerUtils().get_suggested_index_for_layer(
            added_layer, group)
        added_layer = group.insertLayer(index, added_layer).layer()
        if added_layer.isSpatial():
            # db connection is none because we are using a memory layer
            SymbologyUtils().set_layer_style_from_qml(db,
                                                      added_layer,
                                                      is_error_layer=True)

            if isinstance(added_layer.renderer(),
                          QgsCategorizedSymbolRenderer):
                # Remove empty style categories as they just make difficult to understand validation errors
                unique_values = added_layer.uniqueValues(
                    added_layer.fields().indexOf(
                        QCoreApplication.translate("QualityRule",
                                                   "codigo_error")))
                renderer = added_layer.renderer()
                for cat in reversed(renderer.categories()
                                    ):  # To be safe while removing categories
                    if cat.value() not in unique_values:
                        renderer.deleteCategory(
                            renderer.categoryIndexForValue(cat.value()))

                added_layer.setRenderer(added_layer.renderer().clone())

        return added_layer

    def get_error_layers_group(self):
        """
        Get the topology errors group. If it exists but is placed in another
        position rather than the top, it moves the group to the top.
        """
        root = QgsProject.instance().layerTreeRoot()
        translated_strings = TranslatableConfigStrings.get_translatable_config_strings(
        )
        group = root.findGroup(translated_strings[ERROR_LAYER_GROUP])
        if group is None:
            group = root.insertGroup(0, translated_strings[ERROR_LAYER_GROUP])
            self.add_indicators_requested.emit(
                translated_strings[ERROR_LAYER_GROUP],
                QgsLayerTreeNode.NodeGroup)
        elif not self.iface.layerTreeView().layerTreeModel().node2index(
                group).row() == 0 or type(group.parent()) is QgsLayerTreeGroup:
            group_clone = group.clone()
            root.insertChildNode(0, group_clone)
            parent = group.parent()
            parent.removeChildNode(group)
            group = group_clone
        return group

    def add_indicators(self, node_name, node_type, payload):
        """
        Adds all indicators for a node in layer tree. It searches for the proper node and its config.

        :param node_name: Key to get the config and possibly, the node (see payload)
        :param node_type: QgsLayerTreeNode.NodeType
        :param payload: If the node is a LADM layer, we need the layer object, as the name is not enough to disambiguate
                        between layers from different connections
        """
        # First get the node
        node = None
        root = QgsProject.instance().layerTreeRoot()
        if node_type == QgsLayerTreeNode.NodeGroup:
            node = root.findGroup(node_name)
        elif node_type == QgsLayerTreeNode.NodeLayer:
            if payload:
                node = root.findLayer(payload)  # Search by QgsMapLayer
            else:  # Get the first layer matching the node name
                layers = QgsProject.instance().mapLayersByName(node_name)
                if layers:
                    node = root.findLayer(layers[0])

        if not node:
            self.logger.warning(
                __name__,
                "Node not found for adding indicators! ({}, {})".format(
                    node_name, node_type))
            return  # No node, no party

        # Then, get the config
        indicators_config = LayerTreeIndicatorConfig().get_indicators_config(
            node_name, node_type)
        if not indicators_config:
            self.logger.warning(
                __name__,
                "Configuration for indicators not found for node '{}'!".format(
                    node_name))

        # And finally...
        for config in indicators_config:
            self.add_indicator(node, config)

    def add_indicator(self, node, config):
        """
        Adds a single indicator for the node, based on a config dict

        :param node: Layer tree node
        :param config: Dictionary with required data to set the indicator
        """
        indicator = QgsLayerTreeViewIndicator(self.iface.layerTreeView())
        indicator.setToolTip(config[INDICATOR_TOOLTIP])
        indicator.setIcon(config[INDICATOR_ICON])
        indicator.clicked.connect(config[INDICATOR_SLOT])
        self.iface.layerTreeView().addIndicator(node, indicator)

    def export_error_group(self):
        """Exports the error group to GeoPackage"""
        group = self.get_error_layers_group()
        if group:
            layers = group.findLayerIds()
            if not layers:
                self.logger.warning_msg(
                    __name__,
                    QCoreApplication.translate(
                        "AppGUIInterface",
                        "There are no error layers to export!"))
                return

            filename, matched_filter = QFileDialog.getSaveFileName(
                self.iface.mainWindow(),
                QCoreApplication.translate(
                    "AppGUIInterface",
                    "Where do you want to save your GeoPackage?"), ".",
                QCoreApplication.translate("AppGUIInterface",
                                           "GeoPackage (*.gpkg)"))

            if filename:
                if not filename.endswith(".gpkg") and filename:
                    filename = filename + ".gpkg"

                feedback = CustomFeedbackWithErrors()
                try:
                    msg = QCoreApplication.translate(
                        "AppGUIInterface",
                        "Exporting quality errors to GeoPackage...")
                    with ProcessWithStatus(msg):
                        processing.run("native:package", {
                            'LAYERS': layers,
                            'OUTPUT': filename,
                            'OVERWRITE': False,
                            'SAVE_STYLES': True
                        },
                                       feedback=feedback)
                except QgsProcessingException as e:
                    self.logger.warning_msg(
                        __name__,
                        QCoreApplication.translate(
                            "AppGUIInterface",
                            "The quality errors could not be exported. Details: {}"
                            .format(feedback.msg)))
                    return

                self.logger.success_msg(
                    __name__,
                    QCoreApplication.translate(
                        "AppGUIInterface",
                        "The quality errors have been exported to GeoPackage!")
                )
            else:
                self.logger.warning_msg(
                    __name__,
                    QCoreApplication.translate(
                        "AppGUIInterface",
                        "Export to GeoPackage was cancelled. No output file was selected."
                    ), 5)
        else:
            self.logger.warning_msg(
                __name__,
                QCoreApplication.translate(
                    "AppGUIInterface",
                    "There is no quality error group to export!"), 5)

    def set_error_group_visibility(self, visible):
        self.set_node_visibility(self.get_error_layers_group(), visible)

    def set_layer_visibility(self, layer, visible):
        node = QgsProject.instance().layerTreeRoot().findLayer(layer.id())
        self.set_node_visibility(node, visible)

    def error_group_exists(self):
        root = QgsProject.instance().layerTreeRoot()
        translated_strings = TranslatableConfigStrings.get_translatable_config_strings(
        )
        return root.findGroup(
            translated_strings[ERROR_LAYER_GROUP]) is not None

    @pyqtSlot()
    def clear_message_bar(self):
        self.iface.messageBar().clearWidgets()

    def zoom_full(self):
        self.iface.zoomFull()

    def zoom_to_active_layer(self):
        self.iface.zoomToActiveLayer()

    def zoom_to_selected(self):
        self.iface.actionZoomToSelected().trigger()

    def show_message(self, msg, level, duration=5):
        self.clear_message_bar(
        )  # Remove previous messages before showing a new one
        self.iface.messageBar().pushMessage("Asistente LADM-COL", msg, level,
                                            duration)

    def show_status_bar_message(self, msg, duration):
        self.iface.statusBarIface().showMessage(msg, duration)

    def add_tabified_dock_widget(self, area, dock_widget):
        """
        Adds the dock_widget to the given area, making sure it is tabified if other dock widgets exist.
        :param area: Value of the Qt.DockWidgetArea enum
        :param dock_widget: QDockWidget object
        """
        if Qgis.QGIS_VERSION_INT >= 31300:  # Use native addTabifiedDockWidget
            self.iface.addTabifiedDockWidget(area, dock_widget, raiseTab=True)
        else:  # Use plugin's addTabifiedDockWidget, which does not raise the new tab
            dock_widgets = list()
            for dw in self.iface.mainWindow().findChildren(QDockWidget):
                if dw.isVisible() and self.iface.mainWindow().dockWidgetArea(
                        dw) == area:
                    dock_widgets.append(dw)

            self.iface.mainWindow().addDockWidget(
                area,
                dock_widget)  # We add the dock widget, then attempt to tabify
            if dock_widgets:
                self.logger.debug(
                    __name__, "Tabifying dock widget {}...".format(
                        dock_widget.windowTitle()))
                self.iface.mainWindow().tabifyDockWidget(
                    dock_widgets[0],
                    dock_widget)  # No way to prefer one Dock Widget
Esempio n. 15
0
class ToolBar(QObject):
    def __init__(self, iface):
        QObject.__init__(self)
        self.iface = iface
        self.logger = Logger()
        self.app = AppInterface()
        self.geometry = GeometryUtils()

    def build_boundary(self, db):
        QgsProject.instance().setAutoTransaction(False)
        layer = self.app.core.get_ladm_layer_from_qgis(
            db, db.names.LC_BOUNDARY_T, EnumLayerRegistryType.IN_LAYER_TREE)
        use_selection = True

        if layer is None:
            self.logger.message_with_button_load_layer_emitted.emit(
                QCoreApplication.translate(
                    "ToolBar", "First load the layer {} into QGIS!").format(
                        db.names.LC_BOUNDARY_T),
                QCoreApplication.translate("ToolBar",
                                           "Load layer {} now").format(
                                               db.names.LC_BOUNDARY_T),
                db.names.LC_BOUNDARY_T, Qgis.Warning)
            return
        else:
            if layer.selectedFeatureCount() == 0:

                reply = QMessageBox.question(
                    None, QCoreApplication.translate("ToolBar", "Continue?"),
                    QCoreApplication.translate(
                        "ToolBar",
                        "There are no selected boundaries. Do you want to use all the {} boundaries in the database?"
                    ).format(layer.featureCount()),
                    QMessageBox.Yes | QMessageBox.Cancel, QMessageBox.Cancel)
                if reply == QMessageBox.Yes:
                    use_selection = False
                elif reply == QMessageBox.Cancel:
                    self.logger.warning_msg(
                        __name__,
                        QCoreApplication.translate(
                            "ToolBar", "First select at least one boundary!"))
                    return

        if use_selection:
            new_boundary_geoms, boundaries_to_del_ids = self.geometry.fix_selected_boundaries(
                db.names, layer, db.names.T_ID_F)
            num_boundaries = layer.selectedFeatureCount()
        else:
            new_boundary_geoms, boundaries_to_del_ids = self.geometry.fix_boundaries(
                layer, db.names.T_ID_F)
            num_boundaries = layer.featureCount()

        if len(new_boundary_geoms) > 0:
            layer.startEditing(
            )  # Safe, even if layer is already on editing state

            # the boundaries that are to be replaced are removed
            layer.deleteFeatures(boundaries_to_del_ids)

            # Create features based on segment geometries
            new_fix_boundary_features = list()
            for boundary_geom in new_boundary_geoms:
                feature = QgsVectorLayerUtils().createFeature(
                    layer, boundary_geom)

                # TODO: Remove when local id and namespace are defined
                feature.setAttribute(db.names.OID_T_LOCAL_ID_F, 1)
                feature.setAttribute(db.names.OID_T_NAMESPACE_F,
                                     db.names.LC_BOUNDARY_T)

                new_fix_boundary_features.append(feature)

            layer.addFeatures(new_fix_boundary_features)
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "{} feature(s) was(were) analyzed generating {} boundary(ies)!"
                ).format(num_boundaries, len(new_fix_boundary_features)))
            self.iface.mapCanvas().refresh()
        else:
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar", "There are no boundaries to build."))

    def fill_topology_table_pointbfs(self, db, use_selection=True):
        layers = {
            db.names.LC_BOUNDARY_T: None,
            db.names.POINT_BFS_T: None,
            db.names.LC_BOUNDARY_POINT_T: None
        }

        self.app.core.get_layers(db, layers, load=True)
        if not layers:
            return None

        if use_selection:
            if layers[db.names.LC_BOUNDARY_T].selectedFeatureCount() == 0:
                if self.app.core.get_ladm_layer_from_qgis(
                        db, db.names.LC_BOUNDARY_T,
                        EnumLayerRegistryType.IN_LAYER_TREE) is None:
                    self.logger.message_with_button_load_layer_emitted.emit(
                        QCoreApplication.translate(
                            "ToolBar",
                            "First load the layer {} into QGIS and select at least one boundary!"
                        ).format(db.names.LC_BOUNDARY_T),
                        QCoreApplication.translate("ToolBar",
                                                   "Load layer {} now").format(
                                                       db.names.LC_BOUNDARY_T),
                        db.names.LC_BOUNDARY_T, Qgis.Warning)
                else:
                    reply = QMessageBox.question(
                        None,
                        QCoreApplication.translate("ToolBar", "Continue?"),
                        QCoreApplication.translate(
                            "ToolBar",
                            "There are no selected boundaries. Do you want to fill the '{}' table for all the {} boundaries in the database?"
                        ).format(
                            db.names.POINT_BFS_T,
                            layers[db.names.LC_BOUNDARY_T].featureCount()),
                        QMessageBox.Yes | QMessageBox.Cancel,
                        QMessageBox.Cancel)
                    if reply == QMessageBox.Yes:
                        use_selection = False
                    elif reply == QMessageBox.Cancel:
                        self.logger.warning_msg(
                            __name__,
                            QCoreApplication.translate(
                                "ToolBar",
                                "First select at least one boundary!"))
                        return
            else:
                reply = QMessageBox.question(
                    None, QCoreApplication.translate("ToolBar", "Continue?"),
                    QCoreApplication.translate(
                        "ToolBar",
                        "There are {selected} boundaries selected. Do you want to fill the '{table}' table just for the selected boundaries?\n\nIf you say 'No', the '{table}' table will be filled for all boundaries in the database."
                    ).format(selected=layers[
                        db.names.LC_BOUNDARY_T].selectedFeatureCount(),
                             table=db.names.POINT_BFS_T),
                    QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel,
                    QMessageBox.Cancel)
                if reply == QMessageBox.Yes:
                    use_selection = True
                elif reply == QMessageBox.No:
                    use_selection = False
                elif reply == QMessageBox.Cancel:
                    return

        bfs_features = layers[db.names.POINT_BFS_T].getFeatures()

        # Get unique pairs id_boundary-id_boundary_point
        existing_pairs = [
            (bfs_feature[db.names.POINT_BFS_T_LC_BOUNDARY_F],
             bfs_feature[db.names.POINT_BFS_T_LC_BOUNDARY_POINT_F])
            for bfs_feature in bfs_features
        ]
        existing_pairs = set(existing_pairs)

        tolerance = self.app.settings.tolerance
        id_pairs = self.geometry.get_pair_boundary_boundary_point(
            layers[db.names.LC_BOUNDARY_T],
            layers[db.names.LC_BOUNDARY_POINT_T],
            db.names.T_ID_F,
            use_selection=use_selection,
            tolerance=tolerance)

        if id_pairs:
            layers[db.names.POINT_BFS_T].startEditing()
            features = list()
            for id_pair in id_pairs:
                if not id_pair in existing_pairs:  # Avoid duplicated pairs in the DB
                    # Create feature
                    feature = QgsVectorLayerUtils().createFeature(
                        layers[db.names.POINT_BFS_T])
                    feature.setAttribute(db.names.POINT_BFS_T_LC_BOUNDARY_F,
                                         id_pair[0])
                    feature.setAttribute(
                        db.names.POINT_BFS_T_LC_BOUNDARY_POINT_F, id_pair[1])
                    features.append(feature)
            layers[db.names.POINT_BFS_T].addFeatures(features)
            layers[db.names.POINT_BFS_T].commitChanges()
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "{} out of {} records were saved into {}! {} out of {} records already existed in the database."
                ).format(len(features), len(id_pairs), db.names.POINT_BFS_T,
                         len(id_pairs) - len(features), len(id_pairs)))
        else:
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "No pairs id_boundary-id_boundary_point found."))

    def fill_topology_tables_morebfs_less(self, db, use_selection=True):
        layers = {
            db.names.LC_PLOT_T: None,
            db.names.MORE_BFS_T: None,
            db.names.LESS_BFS_T: None,
            db.names.LC_BOUNDARY_T: None
        }

        self.app.core.get_layers(db, layers, load=True)
        if not layers:
            return None

        if use_selection:
            if layers[db.names.LC_PLOT_T].selectedFeatureCount() == 0:
                if self.app.core.get_ladm_layer_from_qgis(
                        db, db.names.LC_PLOT_T,
                        EnumLayerRegistryType.IN_LAYER_TREE) is None:
                    self.logger.message_with_button_load_layer_emitted.emit(
                        QCoreApplication.translate(
                            "ToolBar",
                            "First load the layer {} into QGIS and select at least one plot!"
                        ).format(db.names.LC_PLOT_T),
                        QCoreApplication.translate("ToolBar",
                                                   "Load layer {} now").format(
                                                       db.names.LC_PLOT_T),
                        db.names.LC_PLOT_T, Qgis.Warning)
                else:
                    reply = QMessageBox.question(
                        None,
                        QCoreApplication.translate("ToolBar", "Continue?"),
                        QCoreApplication.translate(
                            "ToolBar",
                            "There are no selected plots. Do you want to fill the '{more}' and '{less}' tables for all the {all} plots in the database?"
                        ).format(
                            more=db.names.MORE_BFS_T,
                            less=db.names.LESS_BFS_T,
                            all=layers[db.names.LC_PLOT_T].featureCount()),
                        QMessageBox.Yes | QMessageBox.Cancel,
                        QMessageBox.Cancel)
                    if reply == QMessageBox.Yes:
                        use_selection = False
                    elif reply == QMessageBox.Cancel:
                        self.logger.warning_msg(
                            __name__,
                            QCoreApplication.translate(
                                "ToolBar", "First select at least one plot!"))
                        return
            else:
                reply = QMessageBox.question(
                    None, QCoreApplication.translate("ToolBar", "Continue?"),
                    QCoreApplication.translate(
                        "ToolBar",
                        "There are {selected} plots selected. Do you want to fill the '{more}' and '{less}' tables just for the selected plots?\n\nIf you say 'No', the '{more}' and '{less}' tables will be filled for all plots in the database."
                    ).format(selected=layers[
                        db.names.LC_PLOT_T].selectedFeatureCount(),
                             more=db.names.MORE_BFS_T,
                             less=db.names.LESS_BFS_T),
                    QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel,
                    QMessageBox.Cancel)
                if reply == QMessageBox.Yes:
                    use_selection = True
                elif reply == QMessageBox.No:
                    use_selection = False
                elif reply == QMessageBox.Cancel:
                    return

        tolerance = self.app.settings.tolerance
        if tolerance:
            # We need to adjust input layers to take tolerance into account
            # Use the same configuration we use in quality rule 3004 (Plots should be covered by boundaries).
            layers[db.names.LC_PLOT_T] = self.app.core.adjust_layer(
                layers[db.names.LC_PLOT_T], layers[db.names.LC_PLOT_T],
                tolerance, True, use_selection)
            layers[db.names.LC_BOUNDARY_T] = self.app.core.adjust_layer(
                layers[db.names.LC_BOUNDARY_T], layers[db.names.LC_PLOT_T],
                tolerance, True)
            if use_selection:
                layers[db.names.LC_PLOT_T].selectAll(
                )  # Because this layer is already filtered by selected features

        # Get unique pairs id_boundary-id_plot in both tables
        existing_more_pairs = set([
            (more_bfs_feature[db.names.MORE_BFS_T_LC_PLOT_F],
             more_bfs_feature[db.names.MORE_BFS_T_LC_BOUNDARY_F])
            for more_bfs_feature in layers[db.names.MORE_BFS_T].getFeatures()
        ])
        existing_less_pairs = set([
            (less_feature[db.names.LESS_BFS_T_LC_PLOT_F],
             less_feature[db.names.LESS_BFS_T_LC_BOUNDARY_F])
            for less_feature in layers[db.names.LESS_BFS_T].getFeatures()
        ])

        id_more_pairs, id_less_pairs = self.geometry.get_pair_boundary_plot(
            layers[db.names.LC_BOUNDARY_T],
            layers[db.names.LC_PLOT_T],
            db.names.T_ID_F,
            use_selection=use_selection)
        if id_less_pairs:
            layers[db.names.LESS_BFS_T].startEditing()
            features = list()
            for id_pair in id_less_pairs:
                if not id_pair in existing_less_pairs:  # Avoid duplicated pairs in the DB
                    # Create feature
                    feature = QgsVectorLayerUtils().createFeature(
                        layers[db.names.LESS_BFS_T])
                    feature.setAttribute(db.names.LESS_BFS_T_LC_PLOT_F,
                                         id_pair[0])
                    feature.setAttribute(db.names.LESS_BFS_T_LC_BOUNDARY_F,
                                         id_pair[1])
                    features.append(feature)
            layers[db.names.LESS_BFS_T].addFeatures(features)
            layers[db.names.LESS_BFS_T].commitChanges()
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "{} out of {} records were saved into '{}'! {} out of {} records already existed in the database."
                ).format(len(features), len(id_less_pairs),
                         db.names.LESS_BFS_T,
                         len(id_less_pairs) - len(features),
                         len(id_less_pairs)))
        else:
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "No pairs id_boundary-id_plot found for '{}' table.").
                format(db.names.LESS_BFS_T))

        if id_more_pairs:
            layers[db.names.MORE_BFS_T].startEditing()
            features = list()
            for id_pair in id_more_pairs:
                if not id_pair in existing_more_pairs:  # Avoid duplicated pairs in the DB
                    # Create feature
                    feature = QgsVectorLayerUtils().createFeature(
                        layers[db.names.MORE_BFS_T])
                    feature.setAttribute(db.names.MORE_BFS_T_LC_PLOT_F,
                                         id_pair[0])
                    feature.setAttribute(db.names.MORE_BFS_T_LC_BOUNDARY_F,
                                         id_pair[1])
                    features.append(feature)
            layers[db.names.MORE_BFS_T].addFeatures(features)
            layers[db.names.MORE_BFS_T].commitChanges()
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "{} out of {} records were saved into '{}'! {} out of {} records already existed in the database."
                ).format(len(features), len(id_more_pairs),
                         db.names.MORE_BFS_T,
                         len(id_more_pairs) - len(features),
                         len(id_more_pairs)))
        else:
            self.logger.info_msg(
                __name__,
                QCoreApplication.translate(
                    "ToolBar",
                    "No pairs id_boundary-id_plot found for '{}' table.").
                format(db.names.MORE_BFS_T))
class BaseAllocateParcelsToReceiverPanelWidget(QgsPanelWidget, WIDGET_UI):
    refresh_parcel_data_requested = pyqtSignal()

    def __init__(self, parent, controller, parcels_to_be_allocated):
        QgsPanelWidget.__init__(self, parent)
        self.setupUi(self)
        self.parent = parent
        self._controller = controller
        self.logger = Logger()

        # Main dicts to store parcels that are not yet allocated but were selected
        # from the previous panel and parcels that are already allocated in the DB
        self.__parcels_to_be_allocated = parcels_to_be_allocated  # {parcel_fid: parcel_number}
        self.__parcels_already_allocated = dict(
        )  # {parcel_fid: parcel_number}

        self.setDockMode(True)
        self.setPanelTitle(
            QCoreApplication.translate(
                "BaseAllocateParcelsToReceiverPanelWidget",
                "Allocate parcels to receiver"))

        self.panelAccepted.connect(self.panel_accepted)
        self.btn_save_allocation.clicked.connect(self.save_allocation)
        self.btn_discard_parcels.clicked.connect(self.discard_parcels)
        self.cbo_receiver.currentIndexChanged.connect(self.receiver_changed)

        self.__txt_already_allocated = QCoreApplication.translate(
            "BaseAllocateParcelsToReceiverPanelWidget", "{} already allocated")
        self.__txt_to_be_allocated = QCoreApplication.translate(
            "BaseAllocateParcelsToReceiverPanelWidget", "{} to be allocated")

        self.fill_table()
        self.fill_receivers()
        self.tbl_parcels.resizeColumnsToContents()

    def panel_accepted(self):
        self.refresh_parcel_data_requested.emit()

    def fill_table(self, refresh_allocated_parcels=True):
        self.tbl_parcels.clearContents()

        if refresh_allocated_parcels:
            self.__parcels_already_allocated = self._controller.get_already_allocated_parcels_for_receiver(
                self.cbo_receiver.currentData())

        number_of_rows = len(self.__parcels_to_be_allocated) + len(
            self.__parcels_already_allocated)
        self.tbl_parcels.setRowCount(number_of_rows)
        self.tbl_parcels.setSortingEnabled(False)

        # Fill parcels to be allocated
        for row, (parcel_fid, parcel_number) in enumerate(
                self.__parcels_to_be_allocated.items()):
            self.fill_row(parcel_fid, parcel_number, False, row)

        # Fill already allocated parcels
        for row, (parcel_fid, parcel_number) in enumerate(
                self.__parcels_already_allocated.items()):
            self.fill_row(parcel_fid, parcel_number, True,
                          row + len(self.__parcels_to_be_allocated))

        self.tbl_parcels.setSortingEnabled(True)
        self.update_count_labels()

    def fill_row(self, parcel_fid, parcel_number, allocated, row):
        item = QTableWidgetItem(parcel_number)
        item.setData(Qt.UserRole, parcel_fid)
        self.tbl_parcels.setItem(row, 0, item)

        text = QCoreApplication.translate(
            "BaseAllocateParcelsToReceiverPanelWidget",
            "Already allocated") if allocated else QCoreApplication.translate(
                "BaseAllocateParcelsToReceiverPanelWidget", "To be allocated")
        item2 = QTableWidgetItem(text)
        item2.setData(Qt.UserRole, allocated)
        if not allocated:
            item2.setBackground(QBrush(NOT_ALLOCATED_PARCEL_COLOR))
        self.tbl_parcels.setItem(row, 1, item2)

    def fill_receivers(self):
        self.cbo_receiver.clear()
        for receiver_id, receiver_data in sorted(
                self._controller.get_receivers_data().items(),
                key=lambda x: locale.strxfrm(str(x[1][0]))):
            # receiver_id: either t_id (surveyor) or t_basket (coordinator)
            # receiver_data: (name, doc id)
            self.cbo_receiver.addItem(receiver_data[0], receiver_id)

    def receiver_changed(self, index):
        self.fill_table()

    def update_count_labels(self):
        self.lbl_already_allocated.setText(
            self.__txt_already_allocated.format(
                len(self.__parcels_already_allocated)))
        self.lbl_to_be_allocated.setText(
            self.__txt_to_be_allocated.format(
                len(self.__parcels_to_be_allocated)))

    def discard_parcels(self):
        # Take 2 cases into account:
        # 1) an already allocated parcel is being discarded --> fill_table()
        # 2) a 'to be allocated' parcel is being discarded --> fill_table(refresh_allocated_parcels=False)
        already_allocated = list()
        to_be_allocated = list()

        selected_gui_items = [
            item.data(Qt.UserRole)
            for item in self.tbl_parcels.selectedItems()
        ]
        for row in range(self.tbl_parcels.rowCount()):
            item = self.tbl_parcels.item(row, 0)
            fid = item.data(Qt.UserRole)
            if fid in selected_gui_items:
                item2 = self.tbl_parcels.item(row, 1)
                if item2.data(Qt.UserRole):  # Allocated?
                    already_allocated.append(fid)
                else:
                    to_be_allocated.append(fid)

        if not already_allocated and not to_be_allocated:
            self.logger.warning_msg(
                __name__,
                QCoreApplication.translate(
                    "BaseAllocateParcelsToReceiverPanelWidget",
                    "First select some parcels in the list."))
            return

        reply = QMessageBox.question(
            self,
            QCoreApplication.translate(
                "BaseAllocateParcelsToReceiverPanelWidget", "Do you confirm?"),
            QCoreApplication.translate(
                "BaseAllocateParcelsToReceiverPanelWidget",
                "Are you sure you want to remove the allocation of selected parcels in the list?"
            ), QMessageBox.Yes, QMessageBox.No)

        if reply == QMessageBox.Yes:
            res = False
            # 1)
            if already_allocated:
                res = self._controller.discard_parcel_allocation(
                    already_allocated)

            # 2)
            for parcel_fid in to_be_allocated:
                del self.__parcels_to_be_allocated[parcel_fid]
                res = True

            if res:
                self.logger.success_msg(
                    __name__,
                    QCoreApplication.translate(
                        "BaseAllocateParcelsToReceiverPanelWidget",
                        "Selected parcels were successfully discarded!"))
            else:
                self.logger.warning_msg(
                    __name__,
                    QCoreApplication.translate(
                        "BaseAllocateParcelsToReceiverPanelWidget",
                        "There were troubles discarding parcels!"))

            # Finally, reload the table, refreshing from data source only when already-allocated parcels were discarded
            # For safety, we reload even if not res, just to make sure our data is totally in sync
            self.fill_table(bool(already_allocated))

    def save_allocation(self):
        if not self.__parcels_to_be_allocated:
            self.logger.warning_msg(
                __name__,
                QCoreApplication.translate(
                    "BaseAllocateParcelsToReceiverPanelWidget",
                    "There are no parcels to be allocated! Go back and select some parcels first."
                ))
            return

        parcel_ids_to_allocate = list(self.__parcels_to_be_allocated.keys())

        res = self._controller.save_allocation_for_receiver(
            parcel_ids_to_allocate, self.cbo_receiver.currentData())
        if res:
            self.logger.success_msg(
                __name__,
                QCoreApplication.translate(
                    "BaseAllocateParcelsToReceiverPanelWidget",
                    "{} parcels were allocated to user {}!").format(
                        len(parcel_ids_to_allocate),
                        self.cbo_receiver.currentText()))
            self.__parcels_to_be_allocated = dict()
            self.fill_table()
        else:
            self.logger.warning_msg(
                __name__,
                QCoreApplication.translate(
                    "BaseAllocateParcelsToReceiverPanelWidget",
                    "There was an error allocating the parcels!"))
Esempio n. 17
0
class RightOfWay(QObject):
    def __init__(self):
        QObject.__init__(self)
        self.logger = Logger()
        self.app = AppInterface()

        self._right_of_way_line_layer = None
        self.addedFeatures = None

    def fill_right_of_way_relations(self, db):
        layers = {
            db.names.LC_ADMINISTRATIVE_SOURCE_T: None,
            db.names.LC_PARCEL_T: None,
            db.names.LC_PLOT_T: None,
            db.names.LC_RESTRICTION_T: None,
            db.names.LC_RESTRICTION_TYPE_D: None,
            db.names.LC_RIGHT_OF_WAY_T: None,
            db.names.COL_RRR_SOURCE_T: None,
            db.names.LC_SURVEY_POINT_T: None,
            db.names.COL_UE_BAUNIT_T: None
        }

        # Load layers
        self.app.core.get_layers(db, layers, load=True)
        if not layers:
            return None

        exp = "\"{}\" = '{}'".format(
            db.names.ILICODE_F,
            LADMNames.RESTRICTION_TYPE_D_RIGHT_OF_WAY_ILICODE_VALUE)
        restriction_right_of_way_t_id = [
            feature for feature in layers[
                db.names.LC_RESTRICTION_TYPE_D].getFeatures(exp)
        ][0][db.names.T_ID_F]

        if layers[db.names.LC_PLOT_T].selectedFeatureCount() == 0 or layers[
                db.names.LC_RIGHT_OF_WAY_T].selectedFeatureCount(
                ) == 0 or layers[
                    db.names.LC_ADMINISTRATIVE_SOURCE_T].selectedFeatureCount(
                    ) == 0:
            if self.app.core.get_ladm_layer_from_qgis(
                    db, db.names.LC_PLOT_T,
                    EnumLayerRegistryType.IN_LAYER_TREE) is None:
                self.logger.message_with_button_load_layer_emitted.emit(
                    QCoreApplication.translate(
                        "RightOfWay",
                        "First load the layer {} into QGIS and select at least one plot!"
                    ).format(db.names.LC_PLOT_T),
                    QCoreApplication.translate("RightOfWay",
                                               "Load layer {} now").format(
                                                   db.names.LC_PLOT_T),
                    db.names.LC_PLOT_T, Qgis.Warning)
            else:
                self.logger.warning_msg(
                    __name__,
                    QCoreApplication.translate(
                        "RightOfWay",
                        "Select at least one benefited plot, one right of way and at least one administrative source to create relations!"
                    ))
                return
        else:
            ue_baunit_features = layers[db.names.COL_UE_BAUNIT_T].getFeatures()
            # Get unique pairs id_right_of_way-id_parcel
            existing_pairs = [
                (ue_baunit_feature[db.names.COL_UE_BAUNIT_T_PARCEL_F],
                 ue_baunit_feature[db.names.COL_UE_BAUNIT_T_LC_RIGHT_OF_WAY_F])
                for ue_baunit_feature in ue_baunit_features
            ]
            existing_pairs = set(existing_pairs)

            plot_ids = [
                f[db.names.T_ID_F]
                for f in layers[db.names.LC_PLOT_T].selectedFeatures()
            ]

            right_of_way_id = layers[
                db.names.LC_RIGHT_OF_WAY_T].selectedFeatures()[0].attribute(
                    db.names.T_ID_F)
            id_pairs = list()
            for plot in plot_ids:
                exp = "\"{uebaunit}\" = {plot}".format(
                    uebaunit=db.names.COL_UE_BAUNIT_T_LC_PLOT_F, plot=plot)
                parcels = layers[db.names.COL_UE_BAUNIT_T].getFeatures(exp)
                for parcel in parcels:
                    id_pair = (parcel.attribute(
                        db.names.COL_UE_BAUNIT_T_PARCEL_F), right_of_way_id)
                    id_pairs.append(id_pair)

            if len(id_pairs) < len(plot_ids):
                # If any relationship plot-parcel is not found, we don't need to continue
                self.logger.warning_msg(
                    __name__,
                    QCoreApplication.translate(
                        "RightOfWay",
                        "One or more pairs id_plot-id_parcel weren't found, this is needed to create benefited and restriction relations."
                    ))
                return

            if id_pairs:
                new_features = list()
                for id_pair in id_pairs:
                    if not id_pair in existing_pairs:
                        #Create feature
                        new_feature = QgsVectorLayerUtils().createFeature(
                            layers[db.names.COL_UE_BAUNIT_T])
                        new_feature.setAttribute(
                            db.names.COL_UE_BAUNIT_T_PARCEL_F, id_pair[0])
                        new_feature.setAttribute(
                            db.names.COL_UE_BAUNIT_T_LC_RIGHT_OF_WAY_F,
                            id_pair[1])
                        self.logger.info(
                            __name__, "Saving RightOfWay-Parcel: {}-{}".format(
                                id_pair[1], id_pair[0]))
                        new_features.append(new_feature)

                layers[db.names.COL_UE_BAUNIT_T].dataProvider().addFeatures(
                    new_features)
                self.logger.info_msg(
                    __name__,
                    QCoreApplication.translate(
                        "RightOfWay",
                        "{} out of {} records were saved into {}! {} out of {} records already existed in the database."
                    ).format(len(new_features), len(id_pairs),
                             db.names.COL_UE_BAUNIT_T,
                             len(id_pairs) - len(new_features), len(id_pairs)))

            spatial_join_layer = processing.run(
                "qgis:joinattributesbylocation", {
                    'INPUT':
                    layers[db.names.LC_PLOT_T],
                    'JOIN':
                    QgsProcessingFeatureSourceDefinition(
                        layers[db.names.LC_RIGHT_OF_WAY_T].id(), True),
                    'PREDICATE': [0],
                    'JOIN_FIELDS': [db.names.T_ID_F],
                    'METHOD':
                    0,
                    'DISCARD_NONMATCHING':
                    True,
                    'PREFIX':
                    '',
                    'OUTPUT':
                    'memory:'
                })['OUTPUT']

            restriction_features = layers[
                db.names.LC_RESTRICTION_T].getFeatures()
            existing_restriction_pairs = [
                (restriction_feature[db.names.COL_BAUNIT_RRR_T_UNIT_F],
                 restriction_feature[db.names.COL_RRR_T_DESCRIPTION_F])
                for restriction_feature in restriction_features
            ]
            existing_restriction_pairs = set(existing_restriction_pairs)
            id_pairs_restriction = list()
            plot_ids = spatial_join_layer.getFeatures()

            for plot in plot_ids:
                exp = "\"uebaunit\" = {plot}".format(
                    uebaunit=db.names.COL_UE_BAUNIT_T_LC_PLOT_F,
                    plot=plot.attribute(db.names.T_ID_F))
                parcels = layers[db.names.COL_UE_BAUNIT_T].getFeatures(exp)
                for parcel in parcels:
                    id_pair_restriction = (parcel.attribute(
                        db.names.COL_UE_BAUNIT_T_PARCEL_F),
                                           QCoreApplication.translate(
                                               "RightOfWay", "Right of way"))
                    id_pairs_restriction.append(id_pair_restriction)

            new_restriction_features = list()
            if id_pairs_restriction:
                for id_pair in id_pairs_restriction:
                    if not id_pair in existing_restriction_pairs:
                        #Create feature
                        new_feature = QgsVectorLayerUtils().createFeature(
                            layers[db.names.LC_RESTRICTION_T])
                        new_feature.setAttribute(
                            db.names.COL_BAUNIT_RRR_T_UNIT_F, id_pair[0])
                        new_feature.setAttribute(
                            db.names.COL_RRR_T_DESCRIPTION_F, id_pair[1])
                        new_feature.setAttribute(
                            db.names.LC_RESTRICTION_T_TYPE_F,
                            restriction_right_of_way_t_id)
                        self.logger.info(
                            __name__, "Saving RightOfWay-Parcel: {}-{}".format(
                                id_pair[1], id_pair[0]))
                        new_restriction_features.append(new_feature)

                layers[db.names.LC_RESTRICTION_T].dataProvider().addFeatures(
                    new_restriction_features)
                self.logger.info_msg(
                    __name__,
                    QCoreApplication.translate(
                        "RightOfWay",
                        "{} out of {} records were saved into {}! {} out of {} records already existed in the database."
                    ).format(
                        len(new_restriction_features),
                        len(id_pairs_restriction), db.names.LC_RESTRICTION_T,
                        len(id_pairs_restriction) -
                        len(new_restriction_features),
                        len(id_pairs_restriction)))

            administrative_source_ids = [
                f[db.names.T_ID_F] for f in layers[
                    db.names.LC_ADMINISTRATIVE_SOURCE_T].selectedFeatures()
            ]

            source_relation_features = layers[
                db.names.COL_RRR_SOURCE_T].getFeatures()

            existing_source_pairs = [
                (source_relation_feature[db.names.COL_RRR_SOURCE_T_SOURCE_F],
                 source_relation_feature[
                     db.names.COL_RRR_SOURCE_T_LC_RESTRICTION_F])
                for source_relation_feature in source_relation_features
            ]
            existing_source_pairs = set(existing_source_pairs)

            rrr_source_relation_pairs = list()

            for administrative_source_id in administrative_source_ids:
                for restriction_feature in new_restriction_features:
                    rrr_source_relation_pair = (administrative_source_id,
                                                restriction_feature.attribute(
                                                    db.names.T_ID_F))
                    rrr_source_relation_pairs.append(rrr_source_relation_pair)

            new_rrr_source_relation_features = list()
            if rrr_source_relation_pairs:
                for id_pair in rrr_source_relation_pairs:
                    if not id_pair in existing_source_pairs:
                        new_feature = QgsVectorLayerUtils().createFeature(
                            layers[db.names.COL_RRR_SOURCE_T])
                        new_feature.setAttribute(
                            db.names.COL_RRR_SOURCE_T_SOURCE_F, id_pair[0])
                        new_feature.setAttribute(
                            db.names.COL_RRR_SOURCE_T_LC_RESTRICTION_F,
                            id_pair[1])
                        self.logger.info(
                            __name__,
                            "Saving Restriction-Source: {}-{}".format(
                                id_pair[1], id_pair[0]))
                        new_rrr_source_relation_features.append(new_feature)

                layers[db.names.COL_RRR_SOURCE_T].dataProvider().addFeatures(
                    new_rrr_source_relation_features)
                self.logger.info_msg(
                    __name__,
                    QCoreApplication.translate(
                        "RightOfWay",
                        "{} out of {} records were saved into {}! {} out of {} records already existed in the database."
                    ).format(
                        len(new_rrr_source_relation_features),
                        len(rrr_source_relation_pairs),
                        db.names.COL_RRR_SOURCE_T,
                        len(rrr_source_relation_pairs) -
                        len(new_rrr_source_relation_features),
                        len(rrr_source_relation_pairs)))
Esempio n. 18
0
class Dependency(QObject):
    download_dependency_completed = pyqtSignal()
    download_dependency_progress_changed = pyqtSignal(int)  # progress

    def __init__(self):
        QObject.__init__(self)
        self.logger = Logger()
        self._downloading = False
        self._show_cursor = True
        self.dependency_name = ""
        self.fetcher_task = None

    def download_dependency(self, uri):
        if not uri:
            self.logger.warning_msg(
                __name__,
                QCoreApplication.translate(
                    "Dependency", "Invalid URL to download dependency."))

        self.logger.clear_message_bar()
        is_valid = self.check_if_dependency_is_valid()

        if is_valid:
            self.logger.debug(
                __name__,
                QCoreApplication.translate(
                    "Dependency",
                    "The {} dependency is already valid, so it won't be downloaded! (Dev, why did you asked to download it :P?)"
                    .format(self.dependency_name)))
            return

        if not self._downloading:  # Already downloading dependency?
            if is_connected(TEST_SERVER):
                self._downloading = True
                self.logger.clear_message_bar()
                self.logger.info_msg(
                    __name__,
                    QCoreApplication.translate(
                        "Dependency",
                        "A {} dependency will be downloaded...".format(
                            self.dependency_name)))
                self.fetcher_task = QgsNetworkContentFetcherTask(QUrl(uri))
                self.fetcher_task.begun.connect(self._task_begun)
                self.fetcher_task.progressChanged.connect(
                    self._task_progress_changed)
                self.fetcher_task.fetched.connect(
                    partial(self._save_dependency_file, self.fetcher_task))
                self.fetcher_task.taskCompleted.connect(self._task_completed)
                QgsApplication.taskManager().addTask(self.fetcher_task)
            else:
                self.logger.clear_message_bar()
                self.logger.warning_msg(
                    __name__,
                    QCoreApplication.translate(
                        "Dependency",
                        "There was a problem connecting to Internet."))
                self._downloading = False

    def check_if_dependency_is_valid(self):
        raise NotImplementedError

    def _task_begun(self):
        if self._show_cursor:
            QApplication.setOverrideCursor(Qt.WaitCursor)

    def _task_progress_changed(self, progress):
        self.download_dependency_progress_changed.emit(progress)

    def _save_dependency_file(self, fetcher_task):
        raise NotImplementedError

    def _task_completed(self):
        if self._show_cursor:
            QApplication.restoreOverrideCursor()
        self.download_dependency_completed.emit()
Esempio n. 19
0
class QualityRulesGeneralResultsPanelWidget(QgsPanelWidget, WIDGET_UI):
    def __init__(self, controller, mode, parent=None):
        QgsPanelWidget.__init__(self, None)
        self.setupUi(self)
        self.__controller = controller
        self.__mode = mode
        self.parent = parent
        self.logger = Logger()

        self.__selected_item = None  # qr_key
        self.__icon_names = [
            'schema.png', 'points.png', 'lines.png', 'polygons.png',
            'relationships.svg'
        ]
        self.__block_control_updates = False

        self.txt_search.addAction(
            QIcon(":/Asistente-LADM-COL/resources/images/search.png"),
            QLineEdit.LeadingPosition)

        self.setDockMode(True)
        self.setPanelTitle(
            QCoreApplication.translate("QualityRulesGeneralResultsPanelWidget",
                                       "Validation results"))
        self.trw_qrs.header().setStretchLastSection(False)
        self.trw_qrs.setColumnWidth(RESULT_COLUMN, 50)
        self.trw_qrs.header().setSectionResizeMode(0, QHeaderView.Stretch)
        self.pbr_total_progress.setValue(0)

        self.txt_search.textChanged.connect(self.__search_text_changed)
        self.btn_open_report.clicked.connect(self.__controller.open_report)
        self.btn_view_error_results.clicked.connect(
            self.__view_error_results_clicked)
        self.btn_help.clicked.connect(self.__show_help)
        self.trw_qrs.itemSelectionChanged.connect(self.__selection_changed)
        self.panelAccepted.connect(self.__reset)

        # To keep track of the connections to 'partial' slots, because we need to delete them when the panel is closed
        self.__partial_connections = list()

        # Load available rules for current role and current db models
        self.__load_available_rules()

        self.__enable_panel_controls(
            False
        )  # Panel controls should be enabled after all rules have validation results

    def __reset(self, panel=None):
        # Reset connections to "partial" slots
        for pair in self.__partial_connections:
            pair[0].disconnect(pair[1])

        self.__partial_connections = list()

        self.logger.clear_message_bar()

    def __load_available_rules(self):
        self.__controller.load_general_results_tree_data()
        self.__update_available_rules()

    def __update_available_rules(self):
        self.trw_qrs.setUpdatesEnabled(False)  # Don't render until we're ready

        # Grab some context data
        top_level_items_expanded_info = dict()
        for i in range(self.trw_qrs.topLevelItemCount()):
            top_level_items_expanded_info[self.trw_qrs.topLevelItem(i).text(
                QR_COLUMN)] = self.trw_qrs.topLevelItem(i).isExpanded()

        # Save selection before clearing tree to restate it later (if needed)
        self.__update_selected_item()

        # Iterate qr types adding children
        self.trw_qrs.blockSignals(
            True)  # We don't want to get itemSelectionChanged here
        self.trw_qrs.clear()
        self.trw_qrs.blockSignals(False)

        bold_font = QFont()
        bold_font.setBold(True)

        sorted_types = sorted(
            self.__controller.get_general_results_tree_data().keys())
        for type_enum in sorted_types:
            children = []
            type_item = QTreeWidgetItem(
                [self.__controller.get_tr_string(type_enum)])

            # Filter by search text
            list_qrs = self.__filter_by_search_text(type_enum,
                                                    self.txt_search.text())

            for qr in list_qrs:
                qr_item = QTreeWidgetItem([qr.name(), ''])
                qr_item.setData(QR_COLUMN, Qt.UserRole, qr.id())
                qr_item.setData(QR_COLUMN, Qt.ToolTipRole,
                                "{}\n{}".format(qr.name(), qr.id()))

                # Let's listen some QR's relevant signals to update our view when needed
                self.__partial_connections.append([
                    qr,
                    qr.progress_changed.connect(
                        partial(self.__set_qr_progress, qr.id()))
                ])
                self.__partial_connections.append([
                    qr,
                    qr.validation_finished.connect(
                        partial(self.__set_qr_validation_result, qr))
                ])

                children.append(qr_item)

            if children:
                icon_name = self.__icon_names[type_enum.value]
                icon = QIcon(":/Asistente-LADM-COL/resources/images/{}".format(
                    icon_name))
                type_item.setData(0, Qt.DecorationRole, icon)
                type_item.setData(0, Qt.FontRole, bold_font)
                type_item.addChildren(children)
                self.trw_qrs.addTopLevelItem(type_item)

                # After we've set the children, we can set custom item widgets
                self.__set_children_custom_widget(type_enum, type_item,
                                                  list_qrs)
            else:
                type_item = None

        # Set selection
        self.trw_qrs.blockSignals(
            True)  # We don't want to get itemSelectionChanged here
        item = self.__get_item_by_qr_key(self.__selected_item)
        if item:
            item.setSelected(True)
        self.trw_qrs.blockSignals(False)

        # Make type items non selectable
        # Set expand taking previous states into account
        for i in range(self.trw_qrs.topLevelItemCount()):
            self.trw_qrs.topLevelItem(i).setFlags(
                Qt.ItemIsEnabled)  # Not selectable
            self.trw_qrs.topLevelItem(i).setExpanded(
                top_level_items_expanded_info.get(
                    self.trw_qrs.topLevelItem(i).text(QR_COLUMN), True))

        self.trw_qrs.setUpdatesEnabled(True)  # Now render!

    def __set_children_custom_widget(self, type_enum, type_item, filtered_qrs):
        dict_filtered_qrs = {qr.id(): qr for qr in filtered_qrs}
        for i in range(type_item.childCount()):
            item = type_item.child(i)
            qr_key = item.data(QR_COLUMN, Qt.UserRole)
            qr_result = self.__controller.get_general_results_tree_data(
            )[type_enum][dict_filtered_qrs[qr_key]]
            self.__set_style_for_validation_result(item, qr_result)

    def __filter_by_search_text(self, type, search_text):
        res = list(self.__controller.get_general_results_tree_data()
                   [type].keys())  # qr_objs

        search_text = search_text.lower().strip()
        if len(search_text) > 1:
            to_delete = list()
            for qr_obj in res:
                # First check in QR id
                found = search_text in qr_obj.id().lower()

                if not found:
                    # Now search in QR tags
                    for tag in qr_obj.tags():
                        if search_text in tag:
                            found = True
                            continue

                    if not found:
                        # Finally, if we search for 'errores', we should find failing rules
                        if search_text in 'errores':
                            qr_res = self.__controller.get_general_results_tree_data(
                            )[type][qr_obj]
                            if qr_res and qr_res.level == EnumQualityRuleResult.ERRORS:
                                found = True

                        if not found:
                            to_delete.append(qr_obj)

            for qr_obj in to_delete:
                res.remove(qr_obj)

        return res

    def __get_item_by_qr_key(self, qr_key):
        iterator = QTreeWidgetItemIterator(self.trw_qrs,
                                           QTreeWidgetItemIterator.Selectable)
        while iterator.value():
            item = iterator.value()
            if item.data(QR_COLUMN, Qt.UserRole) == qr_key:
                return item

            iterator += 1

        return None

    def __update_selected_item(self):
        self.__selected_item = None
        iterator = QTreeWidgetItemIterator(self.trw_qrs,
                                           QTreeWidgetItemIterator.Selectable)
        while iterator.value():
            item = iterator.value()
            qr_key = item.data(QR_COLUMN, Qt.UserRole)

            if item.isSelected():
                self.__selected_item = qr_key
                break

            iterator += 1

    def __search_text_changed(self, text):
        self.__update_available_rules()

    def __view_error_results_clicked(self):
        self.__save_settings()

        if self.trw_qrs.selectedItems():
            qr_key = self.__get_selected_qr_key()
            res = self.__controller.set_selected_qr(qr_key)
            if res:
                self.show_error_results_panel()
            else:
                self.logger.warning_msg(
                    __name__,
                    QCoreApplication.translate(
                        "QualityRulesGeneralResultsPanelWidget",
                        "The quality rule '{}' couldn't be found! We cannot continue exploring errors."
                    ).format(qr_key))

    def __get_selected_qr_key(self):
        # Only call it if you know that bool(self.trw_qrs.selectedItems()) is True.
        # Since only 1 selected item is allowed, we can do this:
        return self.trw_qrs.selectedItems()[0].data(QR_COLUMN, Qt.UserRole)

    def __save_settings(self):
        pass

    def __restore_settings(self):
        pass

    def __selection_changed(self):
        # Custom slot to refresh labels and button state
        self.__update_view_error_button_state()

    def __enable_panel_controls(self, enable):
        # Enable/disable panel controls
        self.blockSignals(not enable)
        self.__block_control_updates = not enable

        self.txt_search.setEnabled(enable)
        self.btn_open_report.setEnabled(enable)

        # Enable/disable view error results button, which depends on a selection as well
        self.__update_view_error_button_state()

    def __update_view_error_button_state(self):
        enable = False
        if not self.__block_control_updates:
            if self.trw_qrs.selectedItems():
                # Only enable the next panel for QRs that have errors
                qr_key = self.__get_selected_qr_key()
                qr_result = self.__controller.get_qr_result(qr_key)
                if qr_result:
                    enable = qr_result.level == EnumQualityRuleResult.ERRORS

        self.btn_view_error_results.setEnabled(enable)

    def __set_qr_progress(self, qr_key, value):
        item = self.__get_item_by_qr_key(qr_key)
        if item:
            self.trw_qrs.itemWidget(item,
                                    RESULT_COLUMN).setText("{}%".format(value))
            QCoreApplication.processEvents()

    def __set_qr_validation_result(self, qr, qr_result):
        self.__controller.set_qr_validation_result(qr, qr_result)
        item = self.__get_item_by_qr_key(qr.id())
        if item:
            self.__set_style_for_validation_result(item, qr_result)

    def __set_style_for_validation_result(self, item, qr_result):
        # Note this method considers when qr_result hasn't been set
        if qr_result:
            text = qr_result.msg
            if qr_result.record_count:
                text = "{} records generated in the error database.\n({})".format(
                    qr_result.record_count, text)
            item.setData(RESULT_COLUMN, Qt.ToolTipRole, text)

        self.trw_qrs.setItemWidget(
            item, RESULT_COLUMN,
            self.__get_custom_widget_item_for_result(qr_result))

    def __get_custom_widget_item_for_result(self, qr_result):
        label = QLabel()

        if not qr_result:
            style = WIDGET_STYLE_QUALITY_RULE_INITIAL_STATE
            label.setText("0%")
        elif qr_result.level == EnumQualityRuleResult.SUCCESS:
            style = WIDGET_STYLE_QUALITY_RULE_SUCCESS
            icon = QIcon(
                ":/Asistente-LADM-COL/resources/images/qr_validation.svg")
            label.setPixmap(icon.pixmap(QSize(16, 16)))
        elif qr_result.level == EnumQualityRuleResult.ERRORS:
            style = WIDGET_STYLE_QUALITY_RULE_ERRORS
            label.setText(str(qr_result.record_count))
        elif qr_result.level == EnumQualityRuleResult.UNDEFINED:
            style = WIDGET_STYLE_QUALITY_RULE_UNDEFINED
        else:  # EnumQualityRuleResult.CRITICAL
            style = WIDGET_STYLE_QUALITY_RULE_CRITICAL

        label.setAlignment(Qt.AlignHCenter | Qt.AlignVCenter)
        label.setStyleSheet("QLabel{}".format(style))

        return label

    def update_total_progress(self, value):
        self.pbr_total_progress.setValue(value)
        if value == 100:
            self.__enable_panel_controls(True)

    def unblock_panel(self):
        """
        Since the panel is blocked while getting QR results,
        we need a way to let external classes call "unblock_panel",
        because getting QR results might trigger errors and then they
        would need to go back and close/accept this panel.
        """
        self.blockSignals(False)
        self.__block_control_updates = False

    def show_error_results_panel(self):
        """
        Slot called to show the task panel based on a selected task.

        :param task_id: Id of the task that will be used to show the task panel.
        """
        self.parent.show_error_results_panel()

    def __show_help(self):
        show_plugin_help("quality_rules")
class DockWidgetQualityRules(QgsDockWidget, DOCKWIDGET_UI):
    """
    Main UI for the Quality Rules module. It holds other panels.
    """
    trigger_action_emitted = pyqtSignal(str)  # action tag

    def __init__(self, controller, parent):
        super(DockWidgetQualityRules, self).__init__(parent)
        self.setupUi(self)
        self.setAllowedAreas(Qt.LeftDockWidgetArea | Qt.RightDockWidgetArea)

        self.__controller = controller
        self.__controller.quality_rule_layer_removed.connect(self.__layer_removed)

        self.logger = Logger()

        # Configure panels
        self.__general_results_panel = None
        self.__error_results_panel = None

        self.__main_panel = QualityRulesInitialPanelWidget(controller, self)
        self.widget.setMainPanel(self.__main_panel)

    def __layer_removed(self):
        self.logger.info_msg(__name__, QCoreApplication.translate("DockWidgetQualityRules",
                                                                  "'Quality rules panel' has been initialized because you just removed a required layer."))

        # Go back to initial panel when the user removes QR DB layers
        # Note this closes open signals on panels, reset controller variables
        # and even removes the whole QR DB group!
        self.widget.acceptAllPanels()

    def closeEvent(self, event):
        # Note this closes open signals on panels, reset controller variables
        # and even removes the whole QR DB group!
        self.widget.acceptAllPanels()

        self.close_dock_widget()

    def update_db_connection(self, db, ladm_col_db, db_source):
        self.close_dock_widget()  # The user needs to use the menus again, which will start everything from scratch

    def close_dock_widget(self):
        self.close()  # The user needs to use the menus again, which will start everything from scratch

    def show_general_results_panel(self, mode):
        """
        :params mode: EnumQualityRulePanelMode
        """
        with OverrideCursor(Qt.WaitCursor):
            self.__delete_general_result_panel()

            self.__general_results_panel = QualityRulesGeneralResultsPanelWidget(self.__controller, mode, self)
            self.__controller.total_progress_changed.connect(self.__general_results_panel.update_total_progress)
            self.__general_results_panel.panelAccepted.connect(self.__controller.reset_vars_for_general_results_panel)
            self.widget.showPanel(self.__general_results_panel)

            if mode == EnumQualityRulePanelMode.VALIDATE:
                self.logger.clear_message_bar()
                res, msg, db_qr = self.__controller.validate_qrs()
                if not res:
                    self.__general_results_panel.unblock_panel()
                    self.widget.acceptAllPanels()  # Go back to initial panel
                    self.logger.warning_msg(__name__, msg)

    def __delete_general_result_panel(self):
        if self.__general_results_panel is not None:
            try:
                self.widget.closePanel(self.__general_results_panel)
            except RuntimeError as e:  # Panel in C++ could be already closed...
                pass

            self.__general_results_panel = None

    def show_error_results_panel(self):
        with OverrideCursor(Qt.WaitCursor):
            if self.__error_results_panel is not None:
                try:
                    self.widget.closePanel(self.__error_results_panel)
                except RuntimeError as e:  # Panel in C++ could be already closed...
                    pass

                self.__error_results_panel = None

            self.__error_results_panel = QualityRulesErrorResultsPanelWidget(self.__controller, self)
            self.__error_results_panel.panelAccepted.connect(self.__controller.reset_vars_for_error_results_panel)
            self.widget.showPanel(self.__error_results_panel)
class CreatePointsSurveyWizard(QWizard, WIZARD_UI):
    WIZARD_NAME = "CreatePointsSurveyWizard"
    WIZARD_TOOL_NAME = QCoreApplication.translate(WIZARD_NAME, "Create Point")

    def __init__(self, iface, db):
        QWizard.__init__(self)
        self.setupUi(self)
        self.iface = iface
        self._db = db
        self.logger = Logger()
        self.app = AppInterface()

        self.names = self._db.names
        self.help_strings = HelpStrings()

        self._layers = {
            self.names.LC_BOUNDARY_POINT_T: None,
            self.names.LC_SURVEY_POINT_T: None,
            self.names.LC_CONTROL_POINT_T: None
        }

        self.target_layer = None

        # Auxiliary data to set nonlinear next pages
        self.pages = [self.wizardPage1, self.wizardPage2, self.wizardPage3]
        self.dict_pages_ids = {self.pages[idx] : pid for idx, pid in enumerate(self.pageIds())}

        self.mMapLayerComboBox.setFilters(QgsMapLayerProxyModel.PointLayer)

        # Set connections
        self.btn_browse_file.clicked.connect(
            make_file_selector(self.txt_file_path,
                               file_filter=QCoreApplication.translate("WizardTranslations",'CSV File (*.csv *.txt)')))
        self.txt_file_path.textChanged.connect(self.file_path_changed)
        self.crsSelector.crsChanged.connect(self.crs_changed)
        self.crs = ""  # SRS auth id
        self.txt_delimiter.textChanged.connect(self.fill_long_lat_combos)
        self.mMapLayerComboBox.layerChanged.connect(self.import_layer_changed)

        self.known_delimiters = [
            {'name': ';', 'value': ';'},
            {'name': ',', 'value': ','},
            {'name': 'tab', 'value': '\t'},
            {'name': 'space', 'value': ' '},
            {'name': '|', 'value': '|'},
            {'name': '~', 'value': '~'},
            {'name': 'Other', 'value': ''}
        ]
        self.cbo_delimiter.addItems([ item['name'] for item in self.known_delimiters ])
        self.cbo_delimiter.currentTextChanged.connect(self.separator_changed)

        self.restore_settings()

        self.txt_file_path.textChanged.emit(self.txt_file_path.text())

        self.rad_boundary_point.toggled.connect(self.point_option_changed)
        self.rad_control_point.toggled.connect(self.point_option_changed)
        self.rad_csv.toggled.connect(self.adjust_page_2_controls)
        self.point_option_changed() # Initialize it
        self.button(QWizard.FinishButton).clicked.connect(self.finished_dialog)
        self.currentIdChanged.connect(self.current_page_changed)

        self.txt_help_page_2.setHtml(self.help_strings.WIZ_ADD_POINTS_SURVEY_PAGE_2_OPTION_CSV)

        self.wizardPage2.setButtonText(QWizard.FinishButton,
                                       QCoreApplication.translate("WizardTranslations",
                                            "Import"))
        self.txt_help_page_3.setHtml(self.help_strings.WIZ_ADD_POINTS_SURVEY_PAGE_3_OPTION_CSV)
        self.txt_help_page_3.anchorClicked.connect(self.save_template)
        self.button(QWizard.HelpButton).clicked.connect(self.show_help)
        self.rejected.connect(self.close_wizard)

        # Set MessageBar for QWizard
        self.bar = QgsMessageBar()
        self.bar.setSizePolicy(QSizePolicy.Minimum, QSizePolicy.Fixed)
        self.setLayout(QGridLayout())
        self.layout().addWidget(self.bar, 0, 0, Qt.AlignTop)

    def nextId(self):
        """
        Set navigation order. Should return an integer. -1 is Finish.
        """
        if self.currentId() == self.dict_pages_ids[self.wizardPage1]:
            return self.dict_pages_ids[self.wizardPage2]
        elif self.currentId() == self.dict_pages_ids[self.wizardPage2]:
            if self.rad_csv.isChecked():
                return self.dict_pages_ids[self.wizardPage3]
            elif self.rad_refactor.isChecked():
                return -1
        elif self.currentId() == self.dict_pages_ids[self.wizardPage3]:
            return -1
        else:
            return -1

    def current_page_changed(self, id):
        """
        Reset the Next button. Needed because Next might have been disabled by a
        condition in a another SLOT.
        """
        enable_next_wizard(self)

        if id == self.dict_pages_ids[self.wizardPage2]:
            self.adjust_page_2_controls()
        elif id == self.dict_pages_ids[self.wizardPage3]:
            self.set_buttons_visible(False)
            self.set_buttons_enabled(False)

            QCoreApplication.processEvents()
            self.check_z_in_geometry()
            QCoreApplication.processEvents()
            self.fill_long_lat_combos("")
            QCoreApplication.processEvents()

            self.set_buttons_visible(True)
            self.set_buttons_enabled(True)

    def set_buttons_visible(self, visible):
        self.button(self.BackButton).setVisible(visible)
        self.button(self.FinishButton).setVisible(visible)
        self.button(self.CancelButton).setVisible(visible)

    def set_buttons_enabled(self, enabled):
        self.wizardPage3.setEnabled(enabled)
        self.button(self.BackButton).setEnabled(enabled)
        self.button(self.FinishButton).setEnabled(enabled)
        self.button(self.CancelButton).setEnabled(enabled)

    def check_z_in_geometry(self):
        self.target_layer = self.app.core.get_layer(self._db, self.current_point_name(), load=True)
        if not self.target_layer:
            return

        if not QgsWkbTypes().hasZ(self.target_layer.wkbType()):
            self.labelZ.setEnabled(False)
            self.cbo_elevation.setEnabled(False)
            msg = QCoreApplication.translate("WizardTranslations",
                                             "The current model does not support 3D geometries")
            self.cbo_elevation.setToolTip(msg)
            self.labelZ.setToolTip(msg)
        else:
            self.labelZ.setEnabled(True)
            self.cbo_elevation.setEnabled(True)
            self.labelZ.setToolTip("")
            self.cbo_elevation.setToolTip("")

    def adjust_page_2_controls(self):
        self.cbo_mapping.clear()
        self.cbo_mapping.addItem("")
        self.cbo_mapping.addItems(self.app.core.get_field_mappings_file_names(self.current_point_name()))

        if self.rad_refactor.isChecked():
            self.lbl_refactor_source.setEnabled(True)
            self.mMapLayerComboBox.setEnabled(True)
            self.lbl_field_mapping.setEnabled(True)
            self.cbo_mapping.setEnabled(True)
            self.import_layer_changed(self.mMapLayerComboBox.currentLayer())

            disable_next_wizard(self)
            self.wizardPage2.setFinalPage(True)
            self.txt_help_page_2.setHtml(self.help_strings.get_refactor_help_string(self._db, self._layers[self.current_point_name()]))

        elif self.rad_csv.isChecked():
            self.lbl_refactor_source.setEnabled(False)
            self.mMapLayerComboBox.setEnabled(False)
            self.lbl_field_mapping.setEnabled(False)
            self.cbo_mapping.setEnabled(False)
            self.lbl_refactor_source.setStyleSheet('')

            enable_next_wizard(self)
            self.wizardPage2.setFinalPage(False)
            self.txt_help_page_2.setHtml(self.help_strings.WIZ_ADD_POINTS_SURVEY_PAGE_2_OPTION_CSV)

    def point_option_changed(self):
        if self.rad_boundary_point.isChecked():
            self.gbx_page_2.setTitle(QCoreApplication.translate("WizardTranslations", "Load data to Boundary Points..."))
            self.gbx_page_3.setTitle(QCoreApplication.translate("WizardTranslations", "Configure CSV data source for Boundary Points..."))
            self.txt_help_page_1.setHtml(self.help_strings.WIZ_ADD_POINTS_SURVEY_PAGE_1_OPTION_BP)
        elif self.rad_survey_point.isChecked(): # self.rad_survey_point is checked
            self.gbx_page_2.setTitle(QCoreApplication.translate("WizardTranslations", "Load data to Survey Points..."))
            self.gbx_page_3.setTitle(QCoreApplication.translate("WizardTranslations", "Configure CSV data source for Survey Points..."))
            self.txt_help_page_1.setHtml(self.help_strings.WIZ_ADD_POINTS_SURVEY_PAGE_1_OPTION_SP)
        else: # self.rad_control_point is checked
            self.gbx_page_2.setTitle(QCoreApplication.translate("WizardTranslations", "Load data to Control Points..."))
            self.gbx_page_3.setTitle(QCoreApplication.translate("WizardTranslations", "Configure CSV data source for Control Points..."))
            self.txt_help_page_1.setHtml(self.help_strings.WIZ_ADD_POINTS_SURVEY_PAGE_1_OPTION_CP)

    def finished_dialog(self):
        self.save_settings()

        if self.rad_refactor.isChecked():
            output_layer_name = self.current_point_name()

            if self.mMapLayerComboBox.currentLayer() is not None:
                field_mapping = self.cbo_mapping.currentText()
                res_etl_model = self.app.core.show_etl_model(self._db,
                                                               self.mMapLayerComboBox.currentLayer(),
                                                               output_layer_name,
                                                               field_mapping=field_mapping)

                if res_etl_model:
                    self.app.gui.redraw_all_layers()  # Redraw all layers to show imported data

                    # If the result of the etl_model is successful and we used a stored recent mapping, we delete the
                    # previous mapping used (we give preference to the latest used mapping)
                    if field_mapping:
                        self.app.core.delete_old_field_mapping(field_mapping)

                    self.app.core.save_field_mapping(output_layer_name)

            else:
                self.logger.warning_msg(__name__, QCoreApplication.translate("WizardTranslations",
                    "Select a source layer to set the field mapping to '{}'.").format(output_layer_name))

            self.close_wizard()

        elif self.rad_csv.isChecked():
            self.prepare_copy_csv_points_to_db()

    def close_wizard(self, message=None, show_message=True):
        if message is None:
            message = QCoreApplication.translate("WizardTranslations", "'{}' tool has been closed.").format(self.WIZARD_TOOL_NAME)
        if show_message:
            self.logger.info_msg(__name__, message)
        self.close()

    def current_point_name(self):
        if self.rad_boundary_point.isChecked():
            return self.names.LC_BOUNDARY_POINT_T
        elif self.rad_survey_point.isChecked():
            return self.names.LC_SURVEY_POINT_T
        else:
            return self.names.LC_CONTROL_POINT_T

    def prepare_copy_csv_points_to_db(self):
        csv_path = self.txt_file_path.text().strip()

        if not csv_path or not os.path.exists(csv_path):
            self.logger.warning_msg(__name__, QCoreApplication.translate("WizardTranslations",
                                                                         "No CSV file given or file doesn't exist."))
            return

        target_layer_name = self.current_point_name()

        with OverrideCursor(Qt.WaitCursor):
            csv_layer = self.app.core.csv_to_layer(csv_path,
                                                   self.txt_delimiter.text(),
                                                   self.cbo_longitude.currentText(),
                                                   self.cbo_latitude.currentText(),
                                                   self.crs,
                                                   self.cbo_elevation.currentText() or None,
                                                   self.detect_decimal_point(csv_path))

            self.app.core.copy_csv_to_db(csv_layer, self._db, target_layer_name)

    def required_layers_are_available(self):
        layers_are_available = self.app.core.required_layers_are_available(self._db, self._layers, self.WIZARD_TOOL_NAME)
        return layers_are_available

    def file_path_changed(self):
        self.autodetect_separator()
        self.fill_long_lat_combos("")
        self.cbo_delimiter.currentTextChanged.connect(self.separator_changed)

    def detect_decimal_point(self, csv_path):
        if os.path.exists(csv_path):
            with open(csv_path) as file:
                file.readline() # headers
                data = file.readline().strip() # 1st line with data

            if data:
                fields = self.get_fields_from_csv_file(csv_path)
                if self.cbo_latitude.currentText() in fields:
                    num_col = data.split(self.cbo_delimiter.currentText())[fields.index(self.cbo_latitude.currentText())]
                    for decimal_point in ['.', ',']:
                        if decimal_point in num_col:
                            return decimal_point

        return '.' # just use the default one

    def autodetect_separator(self):
        csv_path = self.txt_file_path.text().strip()
        if os.path.exists(csv_path):
            with open(csv_path) as file:
                first_line = file.readline()
                for delimiter in self.known_delimiters:
                    if delimiter['value'] == '':
                        continue
                    # if separator works like a column separator in header
                    # number of cols is greater than 1
                    if len(first_line.split(delimiter['value'])) > 1:
                        self.cbo_delimiter.setCurrentText(delimiter['name'])
                        return

    def update_crs_info(self):
        self.crsSelector.setCrs(QgsCoordinateReferenceSystem(self.crs))

    def crs_changed(self):
        self.crs = get_crs_authid(self.crsSelector.crs())
        if self.crs != DEFAULT_SRS_AUTHID:
            self.lbl_crs.setStyleSheet('color: orange')
            self.lbl_crs.setToolTip(QCoreApplication.translate("WizardTranslations",
                "Your CSV data will be reprojected for you to '{}' (Colombian National Origin),<br>before attempting to import it into LADM-COL.").format(DEFAULT_SRS_AUTHID))
        else:
            self.lbl_crs.setStyleSheet('')
            self.lbl_crs.setToolTip(QCoreApplication.translate("WizardTranslations", "Coordinate Reference System"))

    def fill_long_lat_combos(self, text):
        csv_path = self.txt_file_path.text().strip()
        self.cbo_longitude.clear()
        self.cbo_latitude.clear()
        self.cbo_elevation.clear()
        if os.path.exists(csv_path):
            self.button(QWizard.FinishButton).setEnabled(True)

            fields = self.get_fields_from_csv_file(csv_path)
            fields_dict = {field: field.lower() for field in fields}
            if not fields:
                self.button(QWizard.FinishButton).setEnabled(False)
                return

            self.cbo_longitude.addItems(fields)
            self.cbo_latitude.addItems(fields)
            self.cbo_elevation.addItems([""] + fields)

            # Heuristics to suggest values for x, y and z
            x_potential_names = ['x', 'lon', 'long', 'longitud', 'longitude', 'este', 'east', 'oeste', 'west']
            y_potential_names = ['y', 'lat', 'latitud', 'latitude', 'norte', 'north']
            z_potential_names = ['z', 'altura', 'elevacion', 'elevation', 'elevación', 'height']
            for x_potential_name in x_potential_names:
                for k,v in fields_dict.items():
                    if x_potential_name == v:
                        self.cbo_longitude.setCurrentText(k)
                        break
            for y_potential_name in y_potential_names:
                for k, v in fields_dict.items():
                    if y_potential_name == v:
                        self.cbo_latitude.setCurrentText(k)
                        break
            if self.cbo_elevation.isEnabled():
                for z_potential_name in z_potential_names:
                    for k, v in fields_dict.items():
                        if z_potential_name == v:
                            self.cbo_elevation.setCurrentText(k)
                            break

        else:
            self.button(QWizard.FinishButton).setEnabled(False)

    def get_fields_from_csv_file(self, csv_path):
        if not self.txt_delimiter.text():
            return []

        error_reading = False
        try:
            reader  = open(csv_path, "r")
        except IOError:
            error_reading = True
        line = reader.readline().replace("\n", "")
        reader.close()
        if not line:
            error_reading = True
        else:
            return line.split(self.txt_delimiter.text())

        if error_reading:
            self.logger.warning_msg(__name__, QCoreApplication.translate("WizardTranslations",
                "It was not possible to read field names from the CSV. Check the file and try again."))
        return []

    def separator_changed(self, text):
        # first ocurrence
        value = next((x['value'] for x in self.known_delimiters if x['name'] == text), '')
        self.txt_delimiter.setText(value)
        if value == '':
            self.txt_delimiter.setEnabled(True)
        else:
            self.txt_delimiter.setEnabled(False)

    def save_template(self, url):
        link = url.url()
        if self.rad_boundary_point.isChecked():
            if link == '#template':
                self.download_csv_file('template_boundary_points.csv')
            elif link == '#data':
                self.download_csv_file('sample_boundary_points.csv')
        elif self.rad_survey_point.isChecked():
            if link == '#template':
                self.download_csv_file('template_survey_points.csv')
            elif link == '#data':
                self.download_csv_file('sample_survey_points.csv')
        elif self.rad_control_point.isChecked():
            if link == '#template':
                self.download_csv_file('template_control_points.csv')
            elif link == '#data':
                self.download_csv_file('sample_control_points.csv')

    def download_csv_file(self, filename):
        settings = QSettings()
        settings.setValue('Asistente-LADM-COL/wizards/points_csv_file_delimiter', self.txt_delimiter.text().strip())

        new_filename, filter = QFileDialog.getSaveFileName(self,
                                   QCoreApplication.translate("WizardTranslations",
                                                              "Save File"),
                                   os.path.join(settings.value('Asistente-LADM-COL/wizards/points_download_csv_path', '.'), filename),
                                   QCoreApplication.translate("WizardTranslations",
                                                              "CSV File (*.csv *.txt)"))

        if new_filename:
            settings.setValue('Asistente-LADM-COL/wizards/points_download_csv_path', os.path.dirname(new_filename))
            template_file = QFile(":/Asistente-LADM-COL/resources/csv/" + filename)

            if not template_file.exists():
                self.logger.critical(__name__, "CSV doesn't exist! Probably due to a missing 'make' execution to generate resources...")
                msg = QCoreApplication.translate("WizardTranslations", "CSV file not found. Update your plugin. For details see log.")
                self.show_message(msg, Qgis.Warning)
                return

            if os.path.isfile(new_filename):
                self.logger.info(__name__, 'Removing existing file {}...'.format(new_filename))
                os.chmod(new_filename, 0o777)
                os.remove(new_filename)

            if template_file.copy(new_filename):
                os.chmod(new_filename, stat.S_IRUSR | stat.S_IWUSR | stat.S_IRGRP | stat.S_IROTH)
                msg = QCoreApplication.translate("WizardTranslations", """The file <a href="file:///{}">{}</a> was successfully saved!""").format(normalize_local_url(new_filename), os.path.basename(new_filename))
                self.show_message(msg, Qgis.Info)
            else:
                self.logger.warning(__name__, 'There was an error copying the CSV file {}!'.format(new_filename))
                msg = QCoreApplication.translate("WizardTranslations", "The file couldn\'t be saved.")
                self.show_message(msg, Qgis.Warning)

    def import_layer_changed(self, layer):
        if layer:
            crs = get_crs_authid(layer.crs())
            if crs != DEFAULT_SRS_AUTHID:
                self.lbl_refactor_source.setStyleSheet('color: orange')
                self.lbl_refactor_source.setToolTip(QCoreApplication.translate("WizardTranslations",
                                                                   "This layer will be reprojected for you to '{}' (Colombian National Origin),<br>before attempting to import it into LADM-COL.").format(
                    DEFAULT_SRS_AUTHID))
            else:
                self.lbl_refactor_source.setStyleSheet('')
                self.lbl_refactor_source.setToolTip('')

    def show_message(self, message, level):
        self.bar.clearWidgets()  # Remove previous messages before showing a new one
        self.bar.pushMessage(message, level, 10)

    def save_settings(self):
        settings = QSettings()
        point_type = None
        if self.rad_boundary_point.isChecked():
            point_type = 'boundary_point'
        elif self.rad_survey_point.isChecked():
            point_type = 'survey_point'
        else:
            point_type = 'control_point'

        settings.setValue('Asistente-LADM-COL/wizards/points_add_points_type', point_type)
        settings.setValue('Asistente-LADM-COL/wizards/points_load_data_type', 'csv' if self.rad_csv.isChecked() else 'refactor')
        settings.setValue('Asistente-LADM-COL/wizards/points_add_points_csv_file', self.txt_file_path.text().strip())
        settings.setValue('Asistente-LADM-COL/wizards/points_csv_file_delimiter', self.txt_delimiter.text().strip())
        settings.setValue('Asistente-LADM-COL/wizards/points_csv_crs', self.crs)

    def restore_settings(self):
        settings = QSettings()
        point_type = settings.value('Asistente-LADM-COL/wizards/points_add_points_type') or 'boundary_point'
        if point_type == 'boundary_point':
            self.rad_boundary_point.setChecked(True)
        elif point_type == 'survey_point':
            self.rad_survey_point.setChecked(True)
        else: # 'control_point'
            self.rad_control_point.setChecked(True)

        load_data_type = settings.value('Asistente-LADM-COL/wizards/points_load_data_type') or 'csv'
        if load_data_type == 'refactor':
            self.rad_refactor.setChecked(True)
        else:
            self.rad_csv.setChecked(True)

        self.txt_file_path.setText(settings.value('Asistente-LADM-COL/wizards/points_add_points_csv_file'))
        self.txt_delimiter.setText(settings.value('Asistente-LADM-COL/wizards/points_csv_file_delimiter'))

        self.crs = settings.value('Asistente-LADM-COL/wizards/points_csv_crs', DEFAULT_SRS_AUTHID, str)
        self.update_crs_info()

    def show_help(self):
        show_plugin_help("create_points")
class BaseAllocateParcelsInitialPanelWidget(QgsPanelWidget, WIDGET_UI):
    allocate_parcels_to_receiver_panel_requested = pyqtSignal(dict)  # {parcel_fid: parcel_number}
    configure_receivers_panel_requested = pyqtSignal()
    split_data_for_receivers_panel_requested = pyqtSignal()

    STATUS_COL = 1

    def __init__(self, parent, controller):
        QgsPanelWidget.__init__(self, parent)
        self.setupUi(self)
        self.parent = parent
        self._controller = controller
        self.logger = Logger()
        self.app = AppInterface()

        self.setDockMode(True)
        self.setPanelTitle(QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget", "Allocate parcels"))
        self.parent.setWindowTitle(QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget", "Allocate parcels"))

        self.tbl_parcels.resizeColumnsToContents()

        self.txt_search.valueChanged.connect(self.search_value_changed)
        self.tbl_parcels.itemSelectionChanged.connect(self.selection_changed)
        self.btn_allocate.clicked.connect(self.call_allocate_parcels_to_receiver_panel)
        self.btn_configure_receivers.clicked.connect(self.configure_receivers_panel_requested)
        self.btn_show_summary.clicked.connect(self.split_data_for_receivers_panel_requested)
        self.chk_show_only_not_allocated.stateChanged.connect(self.chk_check_state_changed)
        self.btn_reallocate.clicked.connect(self.reallocate_clicked)

        self.connect_to_plot_selection(True)

        self.__parcel_data = dict()  # {parcel_fid: (parcel_number, surveyor_name)}
        self.__selected_items = dict()  # {parcel_fid: parcel_number}

    def _parcel_data(self, refresh_parcel_data=False):
        if not self.__parcel_data or refresh_parcel_data:
            self.__parcel_data = self._controller.get_parcel_receiver_data()

        return self.__parcel_data

    def fill_data(self, refresh_parcel_data=False):
        self.update_selected_items()  # Save selection

        self.tbl_parcels.blockSignals(True)  # We don't want to get itemSelectionChanged here
        self.tbl_parcels.clearContents()
        self.tbl_parcels.blockSignals(False)  # We don't want to get itemSelectionChanged here

        # Build the parcel_data dict taking configuration (search string, chk filter) into account
        parcel_data = self._parcel_data(refresh_parcel_data).copy()
        if self.chk_show_only_not_allocated.isChecked():
            parcel_data = {k:v for k,v in parcel_data.items() if not v[1]}  # v: (parcel_number, surveyor)
        parcel_data = self.filter_data_by_search_string(parcel_data)

        self.tbl_parcels.setRowCount(len(parcel_data))
        self.tbl_parcels.setSortingEnabled(False)

        self.tbl_parcels.blockSignals(True)  # We don't want to get itemSelectionChanged here
        for row, data in enumerate(parcel_data.items()):
            parcel_number, receiver = data[1]
            self.fill_row(data[0], parcel_number, receiver, row)
        self.tbl_parcels.blockSignals(False)  # We don't want to get itemSelectionChanged here

        self.tbl_parcels.setSortingEnabled(True)
        self.tbl_parcels.resizeColumnsToContents()

    def fill_row(self, parcel_fid, parcel_number, receiver, row):
        item = QTableWidgetItem(parcel_number)
        item.setData(Qt.UserRole, parcel_fid)
        self.tbl_parcels.setItem(row, 0, item)

        item2 = QTableWidgetItem(receiver or '')
        if not receiver:
            item2.setBackground(QBrush(NOT_ALLOCATED_PARCEL_COLOR))
        self.tbl_parcels.setItem(row, self.STATUS_COL, item2)

        if parcel_fid in self.__selected_items:
            item.setSelected(True)
            item2.setSelected(True)

    def filter_data_by_search_string(self, parcel_data):
        value = self.txt_search.value().strip()
        if value and len(value) > 1:
            parcel_data = {k:v for k,v in parcel_data.items() if value in v[0]}

        return parcel_data

    def search_value_changed(self, value):
        self.fill_data()

    def chk_check_state_changed(self, state):
        self.fill_data()

    def update_selected_items(self):
        """Update the internal selected_items dict"""
        selected_gui_items = [item.data(Qt.UserRole) for item in self.tbl_parcels.selectedItems()]
        for row in range(self.tbl_parcels.rowCount()):
            item = self.tbl_parcels.item(row, 0)
            fid = item.data(Qt.UserRole)
            if fid in selected_gui_items:
                self.__selected_items[fid] = item.text()
            else:
                if fid in self.__selected_items:
                    # It was selected before, but not anymore
                    del self.__selected_items[fid]

    def selection_changed(self):
        """React upon manual selection in the table widget"""
        self.update_selected_items()

        self.connect_to_plot_selection(False)  # This plot selection should not trigger a table view selection refresh
        self._controller.update_plot_selection(list(self.__selected_items.keys()))
        self.connect_to_plot_selection(True)

    def update_parcel_selection(self, selected, deselected, clear_and_select):
        """React upon a plot selection"""
        self.tbl_parcels.blockSignals(True)  # We don't want to get itemSelectionChanged here
        self.tbl_parcels.clearSelection()  # Reset GUI selection
        self.__selected_items = dict()  # Reset internal selection dict
        parcel_ids = self._controller.get_parcel_numbers_from_selected_plots()

        for parcel_id in parcel_ids:
            if parcel_id in self._parcel_data():
                parcel_number = self._parcel_data()[parcel_id][0]
                items = self.tbl_parcels.findItems(parcel_number, Qt.MatchExactly)
                if items:
                    items[0].setSelected(True)  # Select item in column 0
                    self.tbl_parcels.item(items[0].row(), self.STATUS_COL).setSelected(True)  # Select item in column 1
                else:  # parcel is not currently shown, so select it in internal dict
                    if parcel_id in self._parcel_data():
                        self.__selected_items[parcel_id] = parcel_number

        self.tbl_parcels.blockSignals(False)
        self.update_selected_items()  # Update the internal selection dict

    def connect_to_plot_selection(self, connect):
        if connect:
            self._controller.plot_layer().selectionChanged.connect(self.update_parcel_selection)
        else:
            try:
                self._controller.plot_layer().selectionChanged.disconnect(self.update_parcel_selection)
            except (TypeError, RuntimeError):  # Layer in C++ could be already deleted...
                pass

    def close_panel(self):
        # Disconnect signals
        self.connect_to_plot_selection(False)

    def panel_accepted_clear_message_bar(self):
        self.logger.clear_message_bar()

    def panel_accepted_refresh_parcel_data(self):
        """Slot for refreshing parcel data when it has changed in other panels"""
        self.panel_accepted_clear_message_bar()
        self.fill_data(True)

    def panel_accepted_refresh_and_clear_selection(self):
        self.panel_accepted_refresh_parcel_data()  # Refresh data in table widget, as it might be out of sync with newly added layers
        self.tbl_parcels.clearSelection()  # Selection might be remembered from the status before converting to offline

    def call_allocate_parcels_to_receiver_panel(self):
        # Make sure that all selected items are not yet allocated, otherwise, allow users to deallocate selected
        already_allocated = list()  # [parcel_fid1, ...]
        for parcel_fid, parcel_number in self.__selected_items.items():
            if parcel_fid in self._parcel_data():
                if self._parcel_data()[parcel_fid][1]:  # surveyor_name
                    already_allocated.append(parcel_fid)

        if already_allocated:
            msg = QMessageBox(self)
            msg.setIcon(QMessageBox.Question)
            msg.setText(QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget",
                                                   "Some selected parcels are already allocated!\n\nWhat would you like to do with selected parcels that are already allocated?"))
            msg.setWindowTitle(QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget", "Warning"))
            msg.setStandardButtons(QMessageBox.Yes | QMessageBox.No | QMessageBox.Cancel)
            msg.button(QMessageBox.Yes).setText(
                QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget", "Deselect them and continue"))
            msg.button(QMessageBox.No).setText(
                QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget", "Reallocate and continue"))
            reply = msg.exec_()

            if reply == QMessageBox.Yes:  # Ignore
                # Remove selection of allocated parcels, reload table widget data and continue
                for allocated_parcel_id in already_allocated:
                    if allocated_parcel_id in self._parcel_data():
                        items = self.tbl_parcels.findItems(self._parcel_data()[allocated_parcel_id][0], Qt.MatchExactly)
                        if items:  # Item is currently shown, so deselect it in GUI
                            items[0].setSelected(False)  # Deselect item in column 0
                            self.tbl_parcels.item(items[0].row(), self.STATUS_COL).setSelected(False)  # Deselect item in column 1
                        else:  # Item is not currently shown, deselected in internal selection dict
                            if allocated_parcel_id in self.__selected_items:
                                del self.__selected_items[allocated_parcel_id]

                self.fill_data()

                if not self.__selected_items:
                    self.logger.warning_msg(__name__, QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget",
                                                                       "Ignoring selected parcels, there are none to be allocated! First select some!"), 10)
                    return

            elif reply == QMessageBox.No:  # Reallocate
                # Preserve the selected_items dict, but remove allocation before continuing
                if not self.discard_parcel_allocation(already_allocated):
                    return
            else:  # QMessageBox.Cancel
                return

        if self.__selected_items:
            self.allocate_parcels_to_receiver_panel_requested.emit(self.__selected_items)
        else:
            self.logger.warning_msg(__name__, QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget", "First select some parcels to be allocated."), 5)

    def discard_parcel_allocation(self, parcel_fids):
        res = self._controller.discard_parcel_allocation(parcel_fids)
        if res:
            self.fill_data(True)  # Refresh parcel data
            self.logger.success_msg(__name__, QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget",
                                                                         "Selected parcels are now not allocated!"))
        else:
            self.logger.warning_msg(__name__, QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget",
                                                                         "There were troubles reallocating parcels!"))
        return res

    def reallocate_clicked(self):
        if not self.__selected_items:
            self.logger.warning_msg(__name__, QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget",
                                                                         "First select some parcels."), 5)
            return

        # Get selected parcels that are already allocated
        already_allocated = list()  # [parcel_fid1, ...]
        for parcel_fid, parcel_number in self.__selected_items.items():
            if parcel_fid in self._parcel_data():
                if self._parcel_data()[parcel_fid][1]:  # surveyor_name
                    already_allocated.append(parcel_fid)

        if already_allocated:
            # Ask for confirmation
            reply = QMessageBox.question(self,
                                         QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget", "Do you confirm?"),
                                         QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget",
                                                                    "Are you sure you want to remove the allocation of selected parcels?"),
                                         QMessageBox.Yes, QMessageBox.No)
            if reply == QMessageBox.Yes:
                self.discard_parcel_allocation(already_allocated)
        else:
            self.logger.info_msg(__name__, QCoreApplication.translate("AllocateParcelsFieldDataCapturePanelWidget", "Selected parcels are not yet allocated, so we cannot reallocate them."))