class GwDocument(QObject):
    doc_added = pyqtSignal()

    def __init__(self, single_tool=True):
        """ Class to control action 'Add document' of toolbar 'edit' """

        QObject.__init__(self)
        # parameter to set if the document manager is working as
        # single tool or integrated in another tool
        self.single_tool_mode = single_tool
        self.previous_dialog = None
        self.iface = global_vars.iface
        self.canvas = global_vars.canvas
        self.schema_name = global_vars.schema_name
        self.files_path = []


    def get_document(self, tablename=None, qtable=None, item_id=None, feature=None, feature_type=None, row=None):
        """ Button 34: Add document """

        self.rubber_band = tools_gw.create_rubberband(self.canvas)
        # Create the dialog and signals
        self.dlg_add_doc = GwDocUi()
        tools_gw.load_settings(self.dlg_add_doc)
        self.doc_id = None
        self.files_path = []

        # Capture the current layer to return it at the end of the operation
        cur_active_layer = self.iface.activeLayer()

        widget_list = self.dlg_add_doc.findChildren(QTableView)
        for widget in widget_list:
            tools_qt.set_tableview_config(widget)

        # Get layers of every feature_type

        # Setting lists
        self.ids = []
        self.list_ids = {'arc': [], 'node': [], 'connec': [], 'gully': [], 'element': []}

        # Setting layers
        self.layers = {'arc': [], 'node': [], 'connec': [], 'element': []}
        self.layers['arc'] = tools_gw.get_layers_from_feature_type('arc')
        self.layers['node'] = tools_gw.get_layers_from_feature_type('node')
        self.layers['connec'] = tools_gw.get_layers_from_feature_type('connec')
        self.layers['element'] = tools_gw.get_layers_from_feature_type('element')

        # Remove 'gully' for 'WS'
        self.project_type = tools_gw.get_project_type()
        if self.project_type == 'ws':
            tools_qt.remove_tab(self.dlg_add_doc.tab_feature, 'tab_gully')

        else:
            self.layers['gully'] = tools_gw.get_layers_from_feature_type('gully')

        # Remove all previous selections
        if self.single_tool_mode:
            self.layers = tools_gw.remove_selection(True, layers=self.layers)

        if feature is not None:
            layer = self.layers[feature_type][0]
            layer.selectByIds([feature.id()])

        # Set icons
        tools_gw.add_icon(self.dlg_add_doc.btn_insert, "111", sub_folder="24x24")
        tools_gw.add_icon(self.dlg_add_doc.btn_delete, "112", sub_folder="24x24")
        tools_gw.add_icon(self.dlg_add_doc.btn_snapping, "137")
        self.dlg_add_doc.tabWidget.setTabEnabled(1, False)

        # Fill combo boxes
        self._fill_combo_doc_type(self.dlg_add_doc.doc_type)

        # Set current/selected date and link
        if row:
            tools_qt.set_calendar(self.dlg_add_doc, 'date', row.value('date'))
            tools_qt.set_widget_text(self.dlg_add_doc, 'path', row.value('path'))
            self.files_path.append(row.value('path'))
        else:
            tools_qt.set_calendar(self.dlg_add_doc, 'date', None)

        # Adding auto-completion to a QLineEdit
        table_object = "doc"
        tools_gw.set_completer_object(self.dlg_add_doc, table_object)

        # Adding auto-completion to a QLineEdit for default feature
        if feature_type is None:
            feature_type = "arc"
        viewname = f"v_edit_{feature_type}"
        tools_gw.set_completer_widget(viewname, self.dlg_add_doc.feature_id, str(feature_type) + "_id")

        # Set signals
        self.excluded_layers = ["v_edit_arc", "v_edit_node", "v_edit_connec", "v_edit_element", "v_edit_gully",
                           "v_edit_element"]
        layers_visibility = tools_gw.get_parent_layers_visibility()
        self.dlg_add_doc.rejected.connect(lambda: tools_gw.reset_rubberband(self.rubber_band))
        self.dlg_add_doc.rejected.connect(partial(tools_gw.restore_parent_layers_visibility, layers_visibility))
        self.dlg_add_doc.doc_type.currentIndexChanged.connect(self._activate_relations)
        self.dlg_add_doc.btn_path_url.clicked.connect(partial(self._open_web_browser, self.dlg_add_doc, "path"))
        self.dlg_add_doc.btn_path_doc.clicked.connect(
            lambda: setattr(self, 'files_path', self._get_file_dialog(self.dlg_add_doc, "path")))
        self.dlg_add_doc.btn_accept.clicked.connect(
            partial(self._manage_document_accept, table_object, tablename, qtable, item_id))
        self.dlg_add_doc.btn_cancel.clicked.connect(lambda: setattr(self, 'layers', tools_gw.manage_close(
            self.dlg_add_doc, table_object, cur_active_layer, self.single_tool_mode, self.layers)))
        self.dlg_add_doc.rejected.connect(lambda: setattr(self, 'layers', tools_gw.manage_close(
            self.dlg_add_doc, table_object, cur_active_layer, self.single_tool_mode, self.layers)))
        self.dlg_add_doc.tab_feature.currentChanged.connect(
            partial(tools_gw.get_signal_change_tab, self.dlg_add_doc, self.excluded_layers))

        self.dlg_add_doc.doc_id.textChanged.connect(
            partial(self._fill_dialog_document, self.dlg_add_doc, table_object, None))
        self.dlg_add_doc.btn_insert.clicked.connect(
            partial(tools_gw.insert_feature, self, self.dlg_add_doc, table_object, False, False, None, None))
        self.dlg_add_doc.btn_delete.clicked.connect(
            partial(tools_gw.delete_records, self, self.dlg_add_doc, table_object, False, None, None))
        self.dlg_add_doc.btn_snapping.clicked.connect(
            partial(tools_gw.selection_init, self, self.dlg_add_doc, table_object, False))

        self.dlg_add_doc.tbl_doc_x_arc.clicked.connect(partial(tools_qgis.hilight_feature_by_id,
             self.dlg_add_doc.tbl_doc_x_arc, "v_edit_arc", "arc_id", self.rubber_band, 5))
        self.dlg_add_doc.tbl_doc_x_node.clicked.connect(partial(tools_qgis.hilight_feature_by_id,
            self.dlg_add_doc.tbl_doc_x_node, "v_edit_node", "node_id", self.rubber_band, 10))
        self.dlg_add_doc.tbl_doc_x_connec.clicked.connect(partial(tools_qgis.hilight_feature_by_id,
            self.dlg_add_doc.tbl_doc_x_connec, "v_edit_connec", "connec_id", self.rubber_band, 10))
        self.dlg_add_doc.tbl_doc_x_gully.clicked.connect(partial(tools_qgis.hilight_feature_by_id,
            self.dlg_add_doc.tbl_doc_x_gully, "v_edit_gully", "gully_id", self.rubber_band, 10))

        if feature:
            self.dlg_add_doc.tabWidget.currentChanged.connect(
                partial(self._fill_table_doc, self.dlg_add_doc, feature_type, feature[feature_type + "_id"]))

        # Set default tab 'arc'
        self.dlg_add_doc.tab_feature.setCurrentIndex(0)
        self.feature_type = "arc"
        tools_gw.get_signal_change_tab(self.dlg_add_doc, self.excluded_layers)

        # Open the dialog
        tools_gw.open_dialog(self.dlg_add_doc, dlg_name='doc')

        return self.dlg_add_doc


    def manage_documents(self):
        """ Button 66: Edit document """

        # Create the dialog
        self.dlg_man = GwDocManagerUi()
        tools_gw.load_settings(self.dlg_man)
        self.dlg_man.tbl_document.setSelectionBehavior(QAbstractItemView.SelectRows)
        tools_qt.set_tableview_config(self.dlg_man.tbl_document)

        # Adding auto-completion to a QLineEdit
        table_object = "doc"
        tools_gw.set_completer_object(self.dlg_man, table_object)

        # Set a model with selected filter. Attach that model to selected table
        message = tools_qt.fill_table(self.dlg_man.tbl_document, f"{self.schema_name}.{table_object}")
        if message:
            tools_qgis.show_warning(message)
        tools_gw.set_tablemodel_config(self.dlg_man, self.dlg_man.tbl_document, table_object)

        # Set dignals
        self.dlg_man.doc_id.textChanged.connect(
            partial(tools_qt.filter_by_id, self.dlg_man, self.dlg_man.tbl_document, self.dlg_man.doc_id, table_object))
        self.dlg_man.tbl_document.doubleClicked.connect(
            partial(self._open_selected_object_document, self.dlg_man, self.dlg_man.tbl_document, table_object))
        self.dlg_man.btn_cancel.clicked.connect(partial(tools_gw.close_dialog, self.dlg_man))
        self.dlg_man.rejected.connect(partial(tools_gw.close_dialog, self.dlg_man))
        self.dlg_man.btn_delete.clicked.connect(
            partial(tools_gw.delete_selected_rows, self.dlg_man.tbl_document, table_object))

        # Open form
        tools_gw.open_dialog(self.dlg_man, dlg_name='doc_manager')


    # region private functions


    def _fill_combo_doc_type(self, widget):
        """ Executes query and fill combo box """

        sql = (f"SELECT id, id"
               f" FROM doc_type"
               f" ORDER BY id;")
        rows = tools_db.get_rows(sql)
        tools_qt.fill_combo_values(widget, rows, 1, add_empty=True)
        doctype_vdefault = tools_gw.get_config_value('edit_doctype_vdefault')
        if doctype_vdefault:
            tools_qt.set_combo_value(widget, doctype_vdefault[0], 0)
            self._activate_relations()



    def _activate_relations(self):
        """ Force user to set doc_id and doc_type """

        doc_type = tools_qt.get_combo_value(self.dlg_add_doc, self.dlg_add_doc.doc_type)

        if doc_type in (None, '', 'null'):
            self.dlg_add_doc.tabWidget.setTabEnabled(1, False)
        else:
            self.dlg_add_doc.tabWidget.setTabEnabled(1, True)


    def _fill_table_doc(self, dialog, feature_type, feature_id):

        widget = "tbl_doc_x_" + feature_type
        widget = dialog.findChild(QTableView, widget)
        widget.setSelectionBehavior(QAbstractItemView.SelectRows)
        expr_filter = f"{feature_type}_id = '{feature_id}'"

        # Set model of selected widget
        table_name = f"{self.schema_name}.v_edit_{feature_type}"
        message = tools_qt.fill_table(widget, table_name, expr_filter)
        if message:
            tools_qgis.show_warning(message)


    def _manage_document_accept(self, table_object, tablename=None, qtable=None, item_id=None):
        """ Insert or update table 'document'. Add document to selected feature """

        # Get values from dialog
        doc_id = tools_qt.get_text(self.dlg_add_doc, "doc_id", False, False)
        doc_type = tools_qt.get_combo_value(self.dlg_add_doc, self.dlg_add_doc.doc_type)
        date = tools_qt.get_calendar_date(self.dlg_add_doc, "date", datetime_format="yyyy/MM/dd")
        path = tools_qt.get_text(self.dlg_add_doc, "path", return_string_null=False)
        observ = tools_qt.get_text(self.dlg_add_doc, "observ", False, False)

        if doc_type in (None, '', -1):
            message = "You need to insert doc_type"
            tools_qgis.show_warning(message)
            return

        # Check if this document already exists
        sql = (f"SELECT DISTINCT(id)"
               f" FROM {table_object}"
               f" WHERE id = '{doc_id}'")
        row = tools_db.get_row(sql, log_info=False)

        # If document not exists perform an INSERT
        if row is None:
            if len(self.files_path) <= 1:
                if doc_id in (None, ''):
                    sql, doc_id = self._insert_doc_sql(doc_type, observ, date, path)
                else:
                    sql = (f"INSERT INTO doc (id, doc_type, path, observ, date)"
                           f" VALUES ('{doc_id}', '{doc_type}', '{path}', '{observ}', '{date}');")
                self._update_doc_tables(sql, doc_id, table_object, tablename, item_id, qtable)
                self.doc_added.emit()
            else:
                # Ask question before executing
                msg = ("You have selected multiple documents. In this case, doc_id will be a sequencial number for "
                       "all selected documents and your doc_id won't be used.")
                answer = tools_qt.show_question(msg, tools_qt.tr("Add document"))
                if answer:
                    for file in self.files_path:
                        sql, doc_id = self._insert_doc_sql(doc_type, observ, date, file)
                        self._update_doc_tables(sql, doc_id, table_object, tablename, item_id, qtable)
                        self.doc_added.emit()
        # If document exists perform an UPDATE
        else:
            message = "Are you sure you want to update the data?"
            answer = tools_qt.show_question(message)
            if not answer:
                return
            if len(self.files_path) <= 1:
                sql = self._update_doc_sql(doc_type, observ, date, doc_id, path)
                self._update_doc_tables(sql, doc_id, table_object, tablename, item_id, qtable)
            else:
                # If document have more than 1 file perform an INSERT
                # Ask question before executing
                msg = ("You have selected multiple documents. In this case, doc_id will be a sequencial number for "
                       "all selected documents and your doc_id won't be used.")
                answer = tools_qt.show_question(msg, tools_qt.tr("Add document"))
                if answer:
                    for cont, file in enumerate(self.files_path):
                        if cont == 0:
                            sql = self._update_doc_sql(doc_type, observ, date, doc_id, file)
                            self._update_doc_tables(sql, doc_id, table_object, tablename, item_id, qtable)
                        else:
                            sql, doc_id = self._insert_doc_sql(doc_type, observ, date, file)
                            self._update_doc_tables(sql, doc_id, table_object, tablename, item_id, qtable)


    def _insert_doc_sql(self, doc_type, observ, date, path):

        sql = (f"INSERT INTO doc (doc_type, path, observ, date)"
               f" VALUES ('{doc_type}', '{path}', '{observ}', '{date}') RETURNING id;")
        new_doc_id = tools_db.execute_returning(sql)
        sql = ""
        doc_id = str(new_doc_id[0])
        return sql, doc_id


    def _update_doc_sql(self, doc_type, observ, date, doc_id, path):
        sql = (f"UPDATE doc "
               f" SET doc_type = '{doc_type}', observ = '{observ}', path = '{path}', date = '{date}'"
               f" WHERE id = '{doc_id}';")
        return sql


    def _update_doc_tables(self, sql, doc_id, table_object, tablename, item_id, qtable):

        # Manage records in tables @table_object_x_@feature_type
        sql += (f"\nDELETE FROM doc_x_node"
                f" WHERE doc_id = '{doc_id}';")
        sql += (f"\nDELETE FROM doc_x_arc"
                f" WHERE doc_id = '{doc_id}';")
        sql += (f"\nDELETE FROM doc_x_connec"
                f" WHERE doc_id = '{doc_id}';")
        if self.project_type == 'ud':
            sql += (f"\nDELETE FROM doc_x_gully"
                    f" WHERE doc_id = '{doc_id}';")

        if self.list_ids['arc']:
            for feature_id in self.list_ids['arc']:
                sql += (f"\nINSERT INTO doc_x_arc (doc_id, arc_id)"
                        f" VALUES ('{doc_id}', '{feature_id}');")
        if self.list_ids['node']:
            for feature_id in self.list_ids['node']:
                sql += (f"\nINSERT INTO doc_x_node (doc_id, node_id)"
                        f" VALUES ('{doc_id}', '{feature_id}');")
        if self.list_ids['connec']:
            for feature_id in self.list_ids['connec']:
                sql += (f"\nINSERT INTO doc_x_connec (doc_id, connec_id)"
                        f" VALUES ('{doc_id}', '{feature_id}');")
        if self.project_type == 'ud' and self.list_ids['gully']:
            for feature_id in self.list_ids['gully']:
                sql += (f"\nINSERT INTO doc_x_gully (doc_id, gully_id)"
                        f" VALUES ('{doc_id}', '{feature_id}');")

        status = tools_db.execute_sql(sql)
        if status:
            self.doc_id = doc_id
            tools_gw.manage_close(self.dlg_add_doc, table_object, None, self.single_tool_mode, self.layers)

        if tablename is None:
            return
        else:
            sql = (f"INSERT INTO doc_x_{tablename} (doc_id, {tablename}_id) "
                   f" VALUES('{doc_id}', '{item_id}')")
            tools_db.execute_sql(sql)
            expr = f"{tablename}_id = '{item_id}'"
            message = tools_qt.fill_table(qtable, f"{self.schema_name}.v_ui_doc_x_{tablename}", expr)
            if message:
                tools_qgis.show_warning(message)


    def _open_selected_object_document(self, dialog, widget, table_object):

        selected_list = widget.selectionModel().selectedRows()
        if len(selected_list) == 0:
            message = "Any record selected"
            tools_qgis.show_warning(message)
            return

        row = selected_list[0].row()

        # Get object_id from selected row
        field_object_id = "id"
        widget_id = table_object + "_id"
        selected_object_id = widget.model().record(row).value(field_object_id)

        # Close this dialog and open selected object
        keep_open_form = tools_gw.get_config_parser('dialogs_actions', 'doc_manager_keep_open', "user", "init", prefix=True)
        if tools_os.set_boolean(keep_open_form, False) is not True:
            dialog.close()

        self.get_document(row=widget.model().record(row))
        tools_qt.set_widget_text(self.dlg_add_doc, widget_id, selected_object_id)


    def _open_web_browser(self, dialog, widget=None):
        """ Display url using the default browser """

        if widget is not None:
            url = tools_qt.get_text(dialog, widget)
            if url == 'null':
                url = 'http://www.giswater.org'
        else:
            url = 'http://www.giswater.org'

        webbrowser.open(url)


    def _get_file_dialog(self, dialog, widget):
        """ Get file dialog """

        # Check if selected file exists. Set default value if necessary
        file_path = tools_qt.get_text(dialog, widget)
        if file_path is None or file_path == 'null' or not os.path.exists(str(file_path)):
            folder_path = global_vars.plugin_dir
        else:
            folder_path = os.path.dirname(file_path)
        # Open dialog to select file
        os.chdir(folder_path)
        file_dialog = QFileDialog()
        file_dialog.setFileMode(QFileDialog.AnyFile)
        message = "Select file"
        files_path, filter_ = file_dialog.getOpenFileNames(parent=None, caption=tools_qt.tr(message))

        file_text = ""
        if len(files_path) == 1:
            file_text += f"{files_path[0]}"
        else:
            for file in files_path:
                file_text += f"{file}\n\n"
        if files_path:
            tools_qt.set_widget_text(dialog, widget, str(file_text))
        return files_path


    def _fill_dialog_document(self, dialog, table_object, single_tool_mode=None):

        # Reset list of selected records
        self.ids, self.list_ids = tools_gw.reset_feature_list()

        list_feature_type = ['arc', 'node', 'connec', 'element']
        if global_vars.project_type == 'ud':
            list_feature_type.append('gully')

        object_id = tools_qt.get_text(dialog, table_object + "_id")

        # Check if we already have data with selected object_id
        sql = (f"SELECT * "
               f" FROM {table_object}"
               f" WHERE id = '{object_id}'")
        row = tools_db.get_row(sql, log_info=False)

        # If object_id not found: Clear data
        if not row:
            # Reset widgets
            widgets = ["doc_type", "observ", "path"]
            if widgets:
                for widget_name in widgets:
                    tools_qt.set_widget_text(dialog, widget_name, "")

            if single_tool_mode is not None:
                self.layers = tools_gw.remove_selection(single_tool_mode, self.layers)
            else:
                self.layers = tools_gw.remove_selection(True, self.layers)

            for feature_type in list_feature_type:
                tools_qt.reset_model(dialog, table_object, feature_type)

            return

        # Fill input widgets with data of the @row
        tools_qt.set_widget_text(dialog, "doc_type", row["doc_type"])
        tools_qt.set_widget_text(dialog, "observ", row["observ"])
        tools_qt.set_widget_text(dialog, "path", row["path"])

        # Check related @feature_type
        for feature_type in list_feature_type:
            tools_gw.get_rows_by_feature_type(self, dialog, table_object, feature_type)

    # endregion class GwOmVisit(GwTable):
    """ Class to serialize table 'om_visit' """

    id = GwGenericDescriptor(-1)
    visitcat_id = GwGenericDescriptor(None)
    ext_code = GwGenericDescriptor(None)
    startdate = GwGenericDescriptor(None)
    enddate = GwGenericDescriptor(None)
    user_name = GwGenericDescriptor(None)
    webclient_id = GwGenericDescriptor(None)
    expl_id = GwGenericDescriptor(None)
    the_geom = GwGenericDescriptor(None)
    descript = GwGenericDescriptor(None)
    status = GwGenericDescriptor(None)

    def __init__(self):

        GwTable.__init__(self, 'om_visit', 'id') class GwOmVisitEvent(GwTable):
    """ Class to serialize table 'om_visit_event' """

    id = GwGenericDescriptor(-1)
    event_code = GwGenericDescriptor(None)
    visit_id = GwGenericDescriptor(None)
    position_id = GwGenericDescriptor(None)
    position_value = GwGenericDescriptor(None)
    parameter_id = GwGenericDescriptor(None)
    value = GwGenericDescriptor(None)
    value1 = GwGenericDescriptor(None)
    value2 = GwGenericDescriptor(None)
    geom1 = GwGenericDescriptor(None)
    geom2 = GwGenericDescriptor(None)
    geom3 = GwGenericDescriptor(None)
    xcoord = GwGenericDescriptor(None)
    ycoord = GwGenericDescriptor(None)
    compass = GwGenericDescriptor(None)
    tstamp = GwGenericDescriptor(None)
    text = GwGenericDescriptor(None)
    index_val = GwGenericDescriptor(None)
    is_last = GwGenericDescriptor(None)

    def __init__(self):

        GwTable.__init__(self, 'om_visit_event', 'id') class GwOmVisitXArc(GwTable):
    """ Class to serialize table 'om_visit_x_arc' """

    id = GwGenericDescriptor(-1)
    visit_id = GwGenericDescriptor(None)
    arc_id = GwGenericDescriptor(None)
    is_last = GwGenericDescriptor(None)

    def __init__(self):

        GwTable.__init__(self, 'om_visit_x_arc', 'id') class GwOmVisitXConnec(GwTable):
    """ Class to serialize table 'om_visit_x_connec' """

    id = GwGenericDescriptor(-1)
    visit_id = GwGenericDescriptor(None)
    connec_id = GwGenericDescriptor(None)
    is_last = GwGenericDescriptor(None)

    def __init__(self):

        GwTable.__init__(self, 'om_visit_x_connec', 'id') class GwOmVisitXNode(GwTable):
    """ Class to serialize table 'om_visit_x_node' """

    id = GwGenericDescriptor(-1)
    visit_id = GwGenericDescriptor(None)
    node_id = GwGenericDescriptor(None)
    is_last = GwGenericDescriptor(None)

    def __init__(self):

        GwTable.__init__(self, 'om_visit_x_node', 'id') class GwOmVisitXGully(GwTable):
    """ Class to serialize table 'om_visit_x_gully' """

    id = GwGenericDescriptor(-1)
    visit_id = GwGenericDescriptor(None)
    gully_id = GwGenericDescriptor(None)
    is_last = GwGenericDescriptor(None)

    def __init__(self):

        GwTable.__init__(self, 'om_visit_x_gully', 'id') class GwConfigVisitParameter(GwTable):
    """ Class to serialize table 'config_visit_parameter' """

    id = GwGenericDescriptor(None)
    code = GwGenericDescriptor(None)
    parameter_type = GwGenericDescriptor(None)
    feature_type = GwGenericDescriptor(None)
    data_type = GwGenericDescriptor(None)
    criticity = GwGenericDescriptor(None)
    descript = GwGenericDescriptor(None)
    form_type = GwGenericDescriptor(None)
    vdefault = GwGenericDescriptor(None)

    def __init__(self):

        GwTable.__init__(self, 'config_visit_parameter', 'id') class GwVisitUi(GwDialog, FORM_CLASS):
    pass class GwVisitEventUi(GwDialog, FORM_CLASS):
    pass class GwVisitEventRehabUi(GwDialog, FORM_CLASS):
    pass class GwVisitManagerUi(GwDialog, FORM_CLASS):
    pass def load_settings(dialog):
def save_settings(dialog):
def initialize_parsers():
def get_config_parser(section: str, parameter: str, config_type, file_name, prefix=True, get_comment=False,
                      chk_user_params=True, get_none=False, force_reload=False) -> str:
def set_config_parser(section: str, parameter: str, value: str = None, config_type="user", file_name="session",
                      comment=None, prefix=True, chk_user_params=True):
def save_current_tab(dialog, tab_widget, selector_name):
def open_dialog(dlg, dlg_name=None, stay_on_top=True, title=None, hide_config_widgets=False):
def close_dialog(dlg, delete_dlg=True):
def connect_signal(obj, pfunc, section, signal_name):
def disconnect_signal(section, signal_name=None, pop=True):
def reconnect_signal(section, signal_name):
def create_body(form='', feature='', filter_fields='', extras=None):
def refresh_legend():
def get_cursor_multiple_selection():
def hide_parent_layers(excluded_layers=[]):
def draw_by_json(complet_result, rubber_band, margin=None, reset_rb=True, color=QColor(255, 0, 0, 100), width=3):
def enable_feature_type(dialog, widget_name='tbl_relation', ids=None):
def reset_feature_list():
def get_signal_change_tab(dialog, excluded_layers=[]):
def set_completer_feature_id(widget, feature_type, viewname):
def add_layer_database(tablename=None, the_geom="the_geom", field_id="id", group="GW Layers", sub_group=None, style_id="-1", alias=None):
def add_layer_temp(dialog, data, layer_name, force_tab=True, reset_text=True, tab_idx=1, del_old_layers=True,
                   group='GW Temporal Layers', call_set_tabs_enabled=True, close=True):
def config_layer_attributes(json_result, layer, layer_name, thread=None):
def load_missing_layers(filter, group="GW Layers", sub_group=None):
def fill_tab_log(dialog, data, force_tab=True, reset_text=True, tab_idx=1, call_set_tabs_enabled=True, close=True):
def disable_tab_log(dialog):
def fill_layer_temp(virtual_layer, data, layer_type, counter, group='GW Temporal Layers'):
def enable_widgets(dialog, result, enable):
def enable_all(dialog, result):
def set_stylesheet(field, widget, wtype='label'):
def delete_selected_rows(widget, table_object):
def set_tabs_enabled(dialog):
def set_style_mapzones():
def manage_feature_cat():
def build_dialog_info(dialog, result, my_json=None):
def build_dialog_options(dialog, row, pos, _json, temp_layers_added=None, module=sys.modules[__name__]):
def check_parameters(field):
def add_widget(dialog, field, lbl, widget):
def get_dialog_changed_values(dialog, chk, widget, field, list, value=None):
def add_button(dialog, field, temp_layers_added=None, module=sys.modules[__name__]):
def add_spinbox(field):
def get_values(dialog, widget, _json=None, ignore_editability=False):
def add_checkbox(field):
def add_textarea(field):
def add_hyperlink(field):
def add_calendar(dialog, field):
def set_typeahead(field, dialog, widget, completer):
def fill_typeahead(completer, model, field, dialog, widget):
def set_data_type(field, widget):
def set_widget_size(widget, field):
def add_lineedit(field):
def add_tableview(complet_result, field, module=sys.modules[__name__]):
def add_frame(field, x=None):
def add_combo(field):
def fill_combo(widget, field):
def fill_combo_child(dialog, combo_child):
def manage_combo_child(dialog, combo_parent, combo_child):
def fill_child(dialog, widget, action, feature_type=''):
def get_expression_filter(feature_type, list_ids=None, layers=None):
def get_actions_from_json(json_result, sql):
def exec_pg_function(function_name, parameters=None, commit=True, schema_name=None, log_sql=False, rubber_band=None,
        aux_conn=None, is_thread=False, check_function=True):
def execute_procedure(function_name, parameters=None, schema_name=None, commit=True, log_sql=False, rubber_band=None,
        aux_conn=None, is_thread=False, check_function=True):
def manage_json_geometry(json_result):
def manage_json_response(complet_result, sql=None, rubber_band=None):
def manage_json_exception(json_result, sql=None, stack_level=2, stack_level_increase=0, is_thread=False):
def manage_json_return(json_result, sql, rubber_band=None):
def get_rows_by_feature_type(class_object, dialog, table_object, feature_type):
def get_project_type(schemaname=None):
def get_project_info(schemaname=None):
def get_layers_from_feature_type(feature_type):
def get_role_permissions(qgis_project_role):
def get_config_value(parameter='', columns='value', table='config_param_user', sql_added=None, log_info=True):
def manage_layer_manager(json_result, sql=None):
def selection_init(class_object, dialog, table_object, query=False):
def selection_changed(class_object, dialog, table_object, query=False, lazy_widget=None, lazy_init_function=None):
def insert_feature(class_object, dialog, table_object, query=False, remove_ids=True, lazy_widget=None,
                   lazy_init_function=None):
def remove_selection(remove_groups=True, layers=None):
def connect_signal_selection_changed(class_object, dialog, table_object, query=False):
def docker_dialog(dialog):
def init_docker(docker_param='qgis_info_docker'):
def close_docker(option_name='position'):
def manage_docker_options(option_name='position'):
def set_tablemodel_config(dialog, widget, table_name, sort_order=0, isQStandardItemModel=False, schema_name=None):
def add_icon(widget, icon, sub_folder="20x20"):
def add_tableview_header(widget, field):
def fill_tableview_rows(widget, field):
def set_calendar_from_user_param(dialog, widget, table_name, value, parameter):
def load_tablename(dialog, table_object, feature_type, expr_filter):
def load_tableview_psector(dialog, feature_type):
def set_completer_object(dialog, tablename, field_id="id"):
def set_completer_widget(tablename, widget, field_id):
def set_dates_from_to(widget_from, widget_to, table_name, field_from, field_to):
def manage_close(dialog, table_object, cur_active_layer=None, single_tool_mode=None, layers=None):
def delete_records(class_object, dialog, table_object, query=False, lazy_widget=None, lazy_init_function=None):
def get_parent_layers_visibility():
def restore_parent_layers_visibility(layers):
def create_rubberband(canvas, geometry_type=1):
def reset_rubberband(rb, geometry_type=None):
def set_epsg():
def refresh_selectors(tab_name=None):
def open_dlg_help():
def manage_current_selections_docker(result, open=False):
def create_sqlite_conn(file_name):
def user_params_to_userconfig():
def remove_deprecated_config_vars():
def hide_widgets_form(dialog, dlg_name):
def get_project_version(schemaname=None):
def export_layers_to_gpkg(layers, path):
def set_snapping_type(layer_settings, value):
def get_segment_flag(default_value):
def get_vertex_flag(default_value):
def create_giswater_menu(project_loaded=False):
def unset_giswater_menu():
def _insert_feature_psector(dialog, feature_type, ids=None):
def _delete_feature_psector(dialog, feature_type, list_id):
def _check_user_params(section, parameter, file_name, prefix=False):
def _get_parser_from_filename(filename):
                R = random.randint(0, 255)
                G = random.randint(0, 255)
                B = random.randint(0, 255)
                        R = id['stylesheet']['color'][0]
                        G = id['stylesheet']['color'][1]
                        B = id['stylesheet']['color'][2]
                        R = random.randint(0, 255)
                        G = random.randint(0, 255)
                        B = random.randint(0, 255)
                    R = random.randint(0, 255)
                    G = random.randint(0, 255)
                    B = random.randint(0, 255) class GwSnapManager(object):

    def __init__(self, iface):

        self.iface = iface
        self.canvas = self.iface.mapCanvas()
        self.previous_snapping = None
        self.is_valid = False
        self.point_xy = {"x": None, "y": None}

        # Snapper
        self.snapping_config = self.get_snapping_options()
        self.snapping_config.setEnabled(True)
        self.snapper = self.get_snapper()
        proj = QgsProject.instance()
        proj.writeEntry('Digitizing', 'SnappingMode', 'advanced')

        # Set default vertex marker
        color = QColor(255, 100, 255)
        self.vertex_marker = QgsVertexMarker(self.canvas)
        self.vertex_marker.setIconType(QgsVertexMarker.ICON_CROSS)
        self.vertex_marker.setColor(color)
        self.vertex_marker.setIconSize(15)
        self.vertex_marker.setPenWidth(3)
        global_vars.snappers.append(self)


    def set_snapping_layers(self):
        """ Set main snapping layers """

        self.layer_arc = tools_qgis.get_layer_by_tablename('v_edit_arc')
        self.layer_connec = tools_qgis.get_layer_by_tablename('v_edit_connec')
        self.layer_gully = tools_qgis.get_layer_by_tablename('v_edit_gully')
        self.layer_node = tools_qgis.get_layer_by_tablename('v_edit_node')


    def get_snapping_options(self):
        """ Function that collects all the snapping options """

        global_snapping_config = QgsProject.instance().snappingConfig()
        return global_snapping_config


    def store_snapping_options(self):
        """ Store the project user snapping configuration """

        # Get an array containing the snapping options for all the layers
        self.snapping_config = self.get_snapping_options()
        self.snapping_config.setEnabled(True)
        self.previous_snapping = self.snapping_config


    def set_snapping_status(self, enable=False):
        """ Enable/Disable snapping of all layers """

        QgsProject.instance().blockSignals(True)

        layers = tools_qgis.get_project_layers()
        # Loop through all the layers in the project
        for layer in layers:
            if type(layer) != QgsVectorLayer:
                continue
            layer_settings = self.snapping_config.individualLayerSettings(layer)
            layer_settings.setEnabled(enable)
            self.snapping_config.setIndividualLayerSettings(layer, layer_settings)

        QgsProject.instance().blockSignals(False)
        QgsProject.instance().snappingConfigChanged.emit(self.snapping_config)


    def set_snap_mode(self, mode=3):
        """ Defines on which layer the snapping is performed
        :param mode: 1 = ActiveLayer, 2=AllLayers, 3=AdvancedConfiguration (int or SnappingMode)
        """

        snapping_options = self.get_snapping_options()
        if snapping_options:
            QgsProject.instance().blockSignals(True)
            snapping_options.setMode(mode)
            QgsProject.instance().setSnappingConfig(snapping_options)
            QgsProject.instance().blockSignals(False)
            QgsProject.instance().snappingConfigChanged.emit(snapping_options)


    def config_snap_to_arc(self):
        """ Set snapping to 'arc' """

        QgsProject.instance().blockSignals(True)
        self.set_snapping_layers()
        segment_flag = tools_gw.get_segment_flag(2)
        layer_settings = self.config_snap_to_layer(self.layer_arc, QgsPointLocator.All, True)
        if layer_settings:
            tools_gw.set_snapping_type(layer_settings, segment_flag)
            layer_settings.setTolerance(15)
            layer_settings.setEnabled(True)
        else:
            layer_settings = QgsSnappingConfig.IndividualLayerSettings(True, segment_flag, 15, 1)
        self.snapping_config.setIndividualLayerSettings(self.layer_arc, layer_settings)
        self.restore_snap_options(self.snapping_config)


    def config_snap_to_node(self):
        """ Set snapping to 'node' """

        QgsProject.instance().blockSignals(True)
        vertex_flag = tools_gw.get_vertex_flag(1)
        layer_settings = self.config_snap_to_layer(self.layer_node, QgsPointLocator.Vertex, True)
        if layer_settings:
            tools_gw.set_snapping_type(layer_settings, vertex_flag)
            layer_settings.setTolerance(15)
            layer_settings.setEnabled(True)
        else:
            layer_settings = QgsSnappingConfig.IndividualLayerSettings(True, vertex_flag, 15, 1)
        self.snapping_config.setIndividualLayerSettings(self.layer_node, layer_settings)
        self.restore_snap_options(self.snapping_config)


    def config_snap_to_connec(self):
        """ Set snapping to 'connec' """

        QgsProject.instance().blockSignals(True)
        snapping_config = self.get_snapping_options()
        vertex_flag = tools_gw.get_vertex_flag(1)
        layer_settings = self.config_snap_to_layer(tools_qgis.get_layer_by_tablename('v_edit_connec'),
            QgsPointLocator.Vertex, True)
        if layer_settings:
            tools_gw.set_snapping_type(layer_settings, vertex_flag)
            layer_settings.setTolerance(15)
            layer_settings.setEnabled(True)
        else:
            layer_settings = QgsSnappingConfig.IndividualLayerSettings(True, vertex_flag, 15, 1)
        snapping_config.setIndividualLayerSettings(tools_qgis.get_layer_by_tablename('v_edit_connec'), layer_settings)
        self.restore_snap_options(self.snapping_config)


    def config_snap_to_gully(self):
        """ Set snapping to 'gully' """

        QgsProject.instance().blockSignals(True)
        snapping_config = self.get_snapping_options()
        vertex_flag = tools_gw.get_vertex_flag(1)
        layer_settings = self.config_snap_to_layer(tools_qgis.get_layer_by_tablename('v_edit_gully'),
            QgsPointLocator.Vertex, True)
        if layer_settings:
            tools_gw.set_snapping_type(layer_settings, vertex_flag)
            layer_settings.setTolerance(15)
            layer_settings.setEnabled(True)
        else:
            layer_settings = QgsSnappingConfig.IndividualLayerSettings(True, vertex_flag, 15, 1)
        snapping_config.setIndividualLayerSettings(tools_qgis.get_layer_by_tablename('v_edit_gully'), layer_settings)
        self.restore_snap_options(self.snapping_config)


    def config_snap_to_layer(self, layer, point_locator=QgsPointLocator.All, set_settings=False):
        """ Set snapping to @layer """

        if layer is None:
            return

        QgsSnappingUtils.LayerConfig(layer, point_locator, 15, QgsTolerance.Pixels)
        if set_settings:
            layer_settings = self.snapping_config.individualLayerSettings(layer)
            layer_settings.setEnabled(True)
            self.snapping_config.setIndividualLayerSettings(layer, layer_settings)
            return layer_settings


    def restore_snap_options(self, snappings_options):
        """ Function that applies selected snapping configuration """

        QgsProject.instance().blockSignals(True)
        if snappings_options is None and self.snapping_config:
            snappings_options = self.snapping_config
        QgsProject.instance().setSnappingConfig(snappings_options)
        QgsProject.instance().blockSignals(False)
        QgsProject.instance().snappingConfigChanged.emit(self.snapping_config)


    def recover_snapping_options(self):
        """ Function to restore the previous snapping configuration """

        self.restore_snap_options(self.previous_snapping)


    def get_snapper(self):
        """ Return snapper """

        snapper = QgsMapCanvas.snappingUtils(self.canvas)
        return snapper


    def snap_to_current_layer(self, event_point, vertex_marker=None):

        self.is_valid = False
        if event_point is None:
            return None, None

        result = self.snapper.snapToCurrentLayer(event_point, QgsPointLocator.All)
        if vertex_marker:
            if result.isValid():
                # Get the point and add marker on it
                point = QgsPointXY(result.point())
                vertex_marker.setCenter(point)
                vertex_marker.show()

        self.is_valid = result.isValid()
        return result


    def snap_to_project_config_layers(self, event_point, vertex_marker=None):

        self.is_valid = False
        if event_point is None:
            return None, None

        result = self.snapper.snapToMap(event_point)
        if vertex_marker:
            if result.isValid():
                # Get the point and add marker on it
                point = QgsPointXY(result.point())
                vertex_marker.setCenter(point)
                vertex_marker.show()

        self.is_valid = result.isValid()
        return result


    def add_marker(self, result, vertex_marker=None, icon_type=None):

        if not result.isValid():
            return None

        if vertex_marker is None:
            vertex_marker = self.vertex_marker

        point = result.point()
        if icon_type:
            vertex_marker.setIconType(icon_type)
        vertex_marker.setCenter(point)
        vertex_marker.show()

        return point


    def remove_marker(self, vertex_marker=None):

        if vertex_marker is None:
            vertex_marker = self.vertex_marker

        vertex_marker.hide()


    def get_event_point(self, event=None, point=None):
        """ Get point """

        event_point = None
        x = None
        y = None
        try:
            if event:
                x = event.pos().x()
                y = event.pos().y()
            if point:
                map_point = self.canvas.getCoordinateTransform().transform(point)
                x = map_point.x()
                y = map_point.y()
            event_point = QPoint(int(x), int(y))
        except Exception:
            pass
        finally:
            return event_point


    def get_snapped_layer(self, result):

        layer = None
        if result.isValid():
            layer = result.layer()

        return layer


    def get_snapped_point(self, result):

        point = None
        if result.isValid():
            point = QgsPointXY(result.point())

        return point


    def get_snapped_feature_id(self, result):

        feature_id = None
        if result.isValid():
            feature_id = result.featureId()

        return feature_id


    def get_snapped_feature(self, result, select_feature=False):

        if not result.isValid():
            return None

        snapped_feat = None
        try:
            layer = result.layer()
            feature_id = result.featureId()
            feature_request = QgsFeatureRequest().setFilterFid(feature_id)
            snapped_feat = next(layer.getFeatures(feature_request))
            if select_feature and snapped_feat:
                self._select_snapped_feature(result, feature_id)
        except Exception:
            pass
        finally:
            return snapped_feat


    def result_is_valid(self):
        return self.is_valid


    def add_point(self, vertex_marker):
        """ Create the appropriate map tool and connect to the corresponding signal """

        active_layer = global_vars.iface.activeLayer()
        if active_layer is None:
            active_layer = tools_qgis.get_layer_by_tablename('sys_version')
            global_vars.iface.setActiveLayer(active_layer)

        # Snapper
        tools_gw.disconnect_signal('snap_managers', f'{hash(self)}_ep_canvasClicked_get_xy')
        emit_point = QgsMapToolEmitPoint(global_vars.canvas)
        global_vars.canvas.setMapTool(emit_point)
        tools_gw.connect_signal(global_vars.canvas.xyCoordinates, partial(self._get_mouse_move, vertex_marker),
                                'snap_managers', f'{hash(self)}_xyCoordinates_get_mouse_move')
        tools_gw.connect_signal(emit_point.canvasClicked, partial(self._get_xy, vertex_marker, emit_point),
                                'snap_managers', f'{hash(self)}_ep_canvasClicked_get_xy')


    def set_vertex_marker(self, vertex_marker, icon_type=1, color_type=0, icon_size=15, pen_width=3):

        # Vertex marker
        icons = {0: QgsVertexMarker.ICON_NONE, 1: QgsVertexMarker.ICON_CROSS, 2: QgsVertexMarker.ICON_X,
                 3: QgsVertexMarker.ICON_BOX, 4: QgsVertexMarker.ICON_CIRCLE}
        colors = {0: QColor(255, 100, 255), 1: QColor(0, 255, 0), 2: QColor(0, 255, 0),
                  3: QColor(255, 0, 0), 4: QColor(0, 0, 255)}

        vertex_marker.setIconType(icons[icon_type])
        vertex_marker.setColor(colors[color_type])
        vertex_marker.setIconSize(icon_size)
        vertex_marker.setPenWidth(pen_width)

    # region private functions


    def _get_mouse_move(self, vertex_marker, point):

        # Hide marker and get coordinates
        vertex_marker.hide()
        event_point = self.get_event_point(point=point)

        # Snapping
        result = self.snap_to_project_config_layers(event_point)
        if result.isValid():
            self.add_marker(result, vertex_marker)
        else:
            vertex_marker.hide()


    def _get_xy(self, vertex_marker, emit_point, point):
        """ Get coordinates of selected point """

        event_point = self.get_event_point(point=point)
        result = self.snap_to_project_config_layers(event_point)
        point = self.get_snapped_point(result)
        # Setting x, y coordinates from snapped point
        self.point_xy['x'] = point.x()
        self.point_xy['y'] = point.y()

        message = "Geometry has been added!"
        tools_qgis.show_info(message)
        tools_gw.disconnect_signal('snap_managers', f'{hash(self)}_ep_canvasClicked_get_xy')
        tools_gw.disconnect_signal('snap_managers', f'{hash(self)}_xyCoordinates_get_mouse_move')
        global_vars.iface.mapCanvas().refreshAllLayers()
        vertex_marker.hide()


    def _select_snapped_feature(self, result, feature_id):

        if not result.isValid():
            return

        layer = result.layer()
        layer.select([feature_id])


    # endregion
import os
from datetime import datetime
from functools import partial
from qgis.PyQt.QtCore import Qt, QDate, QStringListModel, pyqtSignal, QDateTime, QObject
from qgis.PyQt.QtGui import QStandardItemModel, QStandardItem
from qgis.PyQt.QtSql import QSqlTableModel
from qgis.PyQt.QtWidgets import QAbstractItemView, QDialogButtonBox, QCompleter, QLineEdit, QFileDialog, QTableView, \
    QTextEdit, QPushButton, QComboBox, QTabWidget, QDateEdit, QDateTimeEdit
from .document import GwDocument
from ..models.om_visit import GwOmVisit
from ..models.om_visit_event import GwOmVisitEvent
from ..models.om_visit_x_arc import GwOmVisitXArc
from ..models.om_visit_x_connec import GwOmVisitXConnec
from ..models.om_visit_x_node import GwOmVisitXNode
from ..models.om_visit_x_gully import GwOmVisitXGully
from ..models.config_visit_parameter import GwConfigVisitParameter
from ..ui.ui_manager import GwVisitUi, GwVisitEventUi, GwVisitEventRehabUi, GwVisitManagerUi
from ..utils import tools_gw
from ..utils.snap_manager import GwSnapManager
from ... import global_vars
from ...lib import tools_qgis, tools_qt, tools_log, tools_db, tools_os
        # Get file types from catalog and populate QFileDialog filter
        sql = "SELECT filetype, fextension FROM config_file"
        rows = tools_db.get_rows(sql)
        f_types = rows
        file_types = ""
        for row in rows:
            file_types += f"{row[0]} (*.{row[1]});;"
        file_types += "All (*.*)"
        new_files, filter_ = QFileDialog.getOpenFileNames(None, "Save file path", "", file_types)

        # Add files to QtableView
        if new_files:
            for path in new_files:
                item = []
                if path not in self.files_all and path not in self.files_added:
                    self.files_all.append(path)
                    self.files_added.append(path)
                    filename, file_extension = os.path.splitext(path)
                    file_extension = file_extension.replace('.', '')

                    # Set default file_type = extension, but look for matches with the catalog
                    file_type = file_extension
                    for _types in f_types:
                        if _types[1] == file_extension:
                            file_type = _types[0]
                            break

                    item.append(path)
                    item.append(file_type)
                    item.append(file_extension)
                    row = []
                    for value in item:
                        row.append(QStandardItem(str(value)))
                    if len(row) > 0:
                        self.dlg_event.tbl_docs_x_event.model().appendRow(row)
            if save:
                self._save_files_added(visit_id, event_id)


    def _save_files_added(self, visit_id, event_id):
        """ Save new files into DataBase """

        if self.files_added:
            sql = "SELECT filetype, fextension FROM config_file"
            f_types = tools_db.get_rows(sql)
            sql = ""
            for path in self.files_added:
                filename, file_extension = os.path.splitext(path)
                # Set default file_type = extension, but look for matches with the catalog
                file_extension = file_extension.replace('.', '')
                file_type = file_extension
                for _types in f_types:
                    if _types[1] == file_extension:
                        file_type = _types[0]
                        break

                sql += (f"INSERT INTO om_visit_event_photo "
                        f"(visit_id, event_id, value, filetype, fextension) "
                        f" VALUES('{visit_id}', '{event_id}', '{path}', "
                        f"'{file_type}', ' {file_extension}'); \n")
            tools_db.execute_sql(sql)


    def _manage_events_changed(self):
        """ Action when at a Event model is changed.
        A) if some record is available => enable OK button of VisitDialog """

        state = (self.tbl_event.model().rowCount() > 0)
        self.button_box.button(QDialogButtonBox.Ok).setEnabled(state)


    def _event_update(self):
        """ Update selected event. """

        # Parameter to save all selected files associated to events
        self.files_added = []
        self.files_all = []
        if not self.tbl_event.selectionModel().hasSelection():
            message = "Any record selected"
            tools_qt.show_info_box(message)
            return

        # Get selected rows
        # TODO: use tbl_event.model().fieldIndex(event.pk()) to be pk name independent
        # 0 is the column of the pk 0 'id'
        selected_list = self.tbl_event.selectionModel().selectedRows(0)
        if selected_list == 0:
            message = "Any record selected"
            tools_qt.show_info_box(message)
            return

        elif len(selected_list) > 1:
            message = "More then one event selected. Select just one"
            tools_qgis.show_warning(message)
            return

        # fetch the record
        event = GwOmVisitEvent()
        event.id = selected_list[0].data()

        if not event.fetch():
            return

        # get parameter_id code to select the widget useful to edit the event
        om_event_parameter = GwConfigVisitParameter()
        om_event_parameter.id = event.parameter_id
        parameter_id = event.parameter_id

        if not om_event_parameter.fetch():
            return

        dlg_name = None
        row = selected_list[0].row()
        if om_event_parameter.form_type in ('event_ud_arc_standard', 'event_standard'):

            event_code = self.dlg_add_visit.tbl_event.model().record(row).value('event_code')
            _value = self.dlg_add_visit.tbl_event.model().record(row).value('value')
            position_value = self.dlg_add_visit.tbl_event.model().record(row).value('position_value')
            text = self.dlg_add_visit.tbl_event.model().record(row).value('text')
            self.dlg_event = GwVisitEventUi()

            tools_gw.load_settings(self.dlg_event)