class GwCatalog:

    def __init__(self):
        """ Class to control toolbar 'om_ws' """
        pass


    def open_catalog(self, previous_dialog, widget_name, feature_type, child_type):
        """ Main function of catalog """

        # Manage if feature_type is gully and set grate
        if feature_type == 'gully':
            feature_type = 'grate'

        form_name = 'upsert_catalog_' + feature_type + ''
        form = f'"formName":"{form_name}", "tabName":"data", "editable":"TRUE"'
        feature = f'"feature_type":"{child_type}"'
        body = tools_gw.create_body(form, feature)
        json_result = tools_gw.execute_procedure('gw_fct_getcatalog', body, log_sql=True)
        if json_result is None:
            return

        group_box_1 = QGroupBox("Filter")
        self.filter_form = QGridLayout()

        self.dlg_catalog = GwInfoCatalogUi()
        tools_gw.load_settings(self.dlg_catalog)
        self.dlg_catalog.btn_cancel.clicked.connect(partial(tools_gw.close_dialog, self.dlg_catalog))
        self.dlg_catalog.btn_accept.clicked.connect(partial(self._fill_geomcat_id, previous_dialog, widget_name))

        main_layout = self.dlg_catalog.widget.findChild(QGridLayout, 'main_layout')
        result = json_result['body']['data']
        for field in result['fields']:
            label = QLabel()
            label.setObjectName('lbl_' + field['label'])
            label.setText(field['label'].capitalize())
            widget = None
            if field['widgettype'] == 'combo':
                widget = self._add_combobox(field)
            if field['layoutname'] == 'lyt_data_1':
                self.filter_form.addWidget(label, field['layoutorder'], 0)
                self.filter_form.addWidget(widget, field['layoutorder'], 1)

        group_box_1.setLayout(self.filter_form)
        main_layout.addWidget(group_box_1)
        vertical_spacer1 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
        main_layout.addItem(vertical_spacer1)

        matcat_id = self.dlg_catalog.findChild(QComboBox, 'matcat_id')

        pnom = None
        dnom = None
        if tools_gw.get_project_type() == 'ws':
            pnom = self.dlg_catalog.findChild(QComboBox, 'pnom')
            dnom = self.dlg_catalog.findChild(QComboBox, 'dnom')
        elif tools_gw.get_project_type() == 'ud':
            pnom = self.dlg_catalog.findChild(QComboBox, 'shape')
            dnom = self.dlg_catalog.findChild(QComboBox, 'geom1')

        id = self.dlg_catalog.findChild(QComboBox, 'id')

        # Call _get_catalog first time
        self._get_catalog(matcat_id, pnom, dnom, id, feature_type, child_type)

        # Set Listeners
        if matcat_id:
            matcat_id.currentIndexChanged.connect(partial(self._populate_pn_dn, matcat_id, pnom, dnom, feature_type, child_type))
        if pnom:
            pnom.currentIndexChanged.connect(partial(self._get_catalog, matcat_id, pnom, dnom, id, feature_type, child_type))
        if dnom:
            dnom.currentIndexChanged.connect(partial(self._get_catalog, matcat_id, pnom, dnom, id, feature_type, child_type))

        # Set shortcut keys
        self.dlg_catalog.key_escape.connect(partial(tools_gw.close_dialog, self.dlg_catalog))

        # Open form
        tools_gw.open_dialog(self.dlg_catalog, dlg_name='info_catalog')


    # region private functions

    def _get_catalog(self, matcat_id, pnom, dnom, id, feature_type, child_type):
        """ Execute gw_fct_getcatalog """

        matcat_id_value = tools_qt.get_combo_value(self.dlg_catalog, matcat_id)
        pn_value = tools_qt.get_combo_value(self.dlg_catalog, pnom)
        dn_value = tools_qt.get_combo_value(self.dlg_catalog, dnom)

        form_name = 'upsert_catalog_' + feature_type + ''
        form = f'"formName":"{form_name}", "tabName":"data", "editable":"TRUE"'
        feature = f'"feature_type":"{child_type}"'
        extras = None
        if tools_gw.get_project_type() == 'ws':
            extras = f'"fields":{{"matcat_id":"{matcat_id_value}", "pnom":"{pn_value}", "dnom":"{dn_value}"}}'
        elif tools_gw.get_project_type() == 'ud':
            extras = f'"fields":{{"matcat_id":"{matcat_id_value}", "shape":"{pn_value}", "geom1":"{dn_value}"}}'

        body = tools_gw.create_body(form=form, feature=feature, extras=extras)
        json_result = tools_gw.execute_procedure('gw_fct_getcatalog', body, log_sql=True)
        if json_result is None:
            return

        if json_result['status'] == "Failed":
            tools_log.log_warning(json_result)
            return False
        if json_result['status'] == "Accepted":
            result = json_result['body']['data']
            for field in result['fields']:
                if field['columnname'] == 'id':
                    self._fill_combo(id, field)


    def _populate_pn_dn(self, matcat_id, pnom, dnom, feature_type, child_type):
        """ Execute gw_fct_getcatalog and fill combos """

        matcat_id_value = tools_qt.get_combo_value(self.dlg_catalog, matcat_id)

        form_name = f'upsert_catalog_' + feature_type + ''
        form = f'"formName":"{form_name}", "tabName":"data", "editable":"TRUE"'
        feature = f'"feature_type":"{child_type}"'
        extras = f'"fields":{{"matcat_id":"{matcat_id_value}"}}'
        body = tools_gw.create_body(form=form, feature=feature, extras=extras)
        json_result = tools_gw.execute_procedure('gw_fct_getcatalog', body, log_sql=True)
        if json_result is None:
            return

        for field in json_result['body']['data']['fields']:
            if field['columnname'] in ('pnom', 'shape'):
                self._fill_combo(pnom, field)
            elif field['columnname'] in ('dnom', 'geom1'):
                self._fill_combo(dnom, field)


    def _populate_catalog_id(self, feature_type):
        """ Execute gw_api_get_catalog_id and fill combo id """

        # Get widgets
        widget_metcat_id = self.dlg_catalog.findChild(QComboBox, 'matcat_id')
        widget_pn = self.dlg_catalog.findChild(QComboBox, 'pnom')
        widget_dn = self.dlg_catalog.findChild(QComboBox, 'dnom')
        widget_id = self.dlg_catalog.findChild(QComboBox, 'id')

        # Get values from combo parents
        metcat_value = tools_qt.get_text(self.dlg_catalog, widget_metcat_id)
        pn_value = tools_qt.get_text(self.dlg_catalog, widget_pn)
        dn_value = tools_qt.get_text(self.dlg_catalog, widget_dn)

        exists = tools_db.check_function('gw_api_get_catalog_id')
        if exists:
            sql = f"SELECT gw_api_get_catalog_id('{metcat_value}', '{pn_value}', '{dn_value}', '{feature_type}', 9)"
            row = tools_db.get_row(sql)
            self._fill_combo(widget_id, row[0]['catalog_id'][0])


    def _add_combobox(self, field):
        """ Add QComboBox to dialog """

        widget = QComboBox()
        widget.setObjectName(field['columnname'])
        widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
        self._fill_combo(widget, field)
        if 'selectedId' in field:
            tools_qt.set_combo_value(widget, field['selectedId'], 0)

        return widget


    def _fill_combo(self, widget, field):
        """
        Fill QComboBox
            :param widget: combobox destination (QComboBox)
            :param field: json where find values (Json)
        """

        # Generate list of items to add into combo
        widget.blockSignals(True)
        widget.clear()
        widget.blockSignals(False)
        combolist = []
        if 'comboIds' in field:
            for i in range(0, len(field['comboIds'])):
                if field['comboIds'][i] is not None and field['comboNames'][i] is not None:
                    elem = [field['comboIds'][i], field['comboNames'][i]]
                    combolist.append(elem)
            records_sorted = sorted(combolist, key=operator.itemgetter(1))
            # Populate combo
            if widget.objectName() != 'id':
                records_sorted.insert(0, ['', ''])
            for record in records_sorted:
                widget.addItem(str(record[1]), record)


    def _fill_geomcat_id(self, previous_dialog, widget_name):
        """ Fill the widget of the previous dialogue """

        widget_id = self.dlg_catalog.findChild(QComboBox, 'id')
        catalog_id = tools_qt.get_text(self.dlg_catalog, widget_id)
        widget = previous_dialog.findChild(QWidget, widget_name)

        if type(widget) is QLineEdit:
            widget.setText(catalog_id)
            widget.setFocus()
        elif type(widget) is QComboBox:
            tools_qt.set_widget_text(previous_dialog, widget, catalog_id)
            widget.setFocus()
        else:
            message = "Widget not found"
            tools_qgis.show_message(message, 2, parameter=str(widget_name))

        tools_gw.close_dialog(self.dlg_catalog)

    # endregion class GwDimensioning:

    def __init__(self):

        self.iface = global_vars.iface
        self.settings = global_vars.giswater_settings
        self.plugin_dir = global_vars.plugin_dir
        self.canvas = global_vars.canvas
        self.points = None
        self.snapper_manager = GwSnapManager(self.iface)
        self.vertex_marker = self.snapper_manager.vertex_marker
        self.vertex_marker.setIconType(QgsVertexMarker.ICON_CIRCLE)


    def open_dimensioning_form(self, qgis_feature=None, layer=None, db_return=None, fid=None, rubber_band=None):

        self.dlg_dim = GwDimensioningUi()
        tools_gw.load_settings(self.dlg_dim)

        self.user_current_layer = self.iface.activeLayer()
        # Set layers dimensions, node and connec
        self.layer_dimensions = tools_qgis.get_layer_by_tablename("v_edit_dimensions")
        self.layer_node = tools_qgis.get_layer_by_tablename("v_edit_node")
        self.layer_connec = tools_qgis.get_layer_by_tablename("v_edit_connec")

        feature = None
        if qgis_feature is None:
            features = self.layer_dimensions.getFeatures()
            for feature in features:
                if feature['id'] == fid:
                    return feature
            qgis_feature = feature

        # qgis_feature = self.get_feature_by_id(self.layer_dimensions, fid, 'id')

        # when funcion is called from new feature
        if db_return is None:
            rubber_band = tools_gw.create_rubberband(self.canvas, 0)
            extras = f'"coordinates":{{{self.points}}}'
            body = tools_gw.create_body(extras=extras)
            json_result = tools_gw.execute_procedure('gw_fct_getdimensioning', body)
            if json_result is None or json_result['status'] == 'Failed':
                return False
            db_return = json_result

            # get id from db response
            self.fid = int(db_return['body']['feature']['id']) + 1
        else:
            self.fid = int(db_return['body']['feature']['id'])
        # ACTION SIGNALS
        action_snapping = self.dlg_dim.findChild(QAction, "actionSnapping")
        action_snapping.triggered.connect(partial(self._snapping, action_snapping))
        tools_gw.add_icon(action_snapping, "103")

        action_orientation = self.dlg_dim.findChild(QAction, "actionOrientation")
        action_orientation.triggered.connect(partial(self._orientation, action_orientation))
        tools_gw.add_icon(action_orientation, "133")

        # LAYER SIGNALS
        tools_gw.connect_signal(self.layer_dimensions.editingStarted,
                                partial(tools_gw.enable_all, self.dlg_dim, db_return['body']['data']),
                                'dimensioning', 'open_dimensioning_form_layer_dimensions_editingStarted_enable_all')
        tools_gw.connect_signal(self.layer_dimensions.editingStopped,
                                partial(tools_gw.enable_widgets, self.dlg_dim, db_return['body']['data'], False),
                                'dimensioning', 'open_dimensioning_form_layer_dimensions_editingStopped_enable_widgets')

        # WIDGETS SIGNALS
        self.dlg_dim.btn_accept.clicked.connect(
            partial(self._save_dimensioning, qgis_feature, layer))
        self.dlg_dim.btn_cancel.clicked.connect(partial(self._cancel_dimensioning, action_snapping, action_orientation))
        self.dlg_dim.key_escape.connect(partial(tools_gw.close_dialog, self.dlg_dim))
        self.dlg_dim.dlg_closed.connect(partial(self._cancel_dimensioning, action_snapping, action_orientation))
        self.dlg_dim.dlg_closed.connect(partial(tools_gw.save_settings, self.dlg_dim))
        self.dlg_dim.dlg_closed.connect(rubber_band.reset)
        self.dlg_dim.dlg_closed.connect(self.layer_node.removeSelection)
        self.dlg_dim.dlg_closed.connect(self.layer_connec.removeSelection)

        self._create_map_tips()

        layout_list = []
        for field in db_return['body']['data']['fields']:
            if 'hidden' in field and field['hidden']:
                continue

            label, widget = self._set_widgets(self.dlg_dim, db_return, field)

            if widget.objectName() == 'id':
                tools_qt.set_widget_text(self.dlg_dim, widget, self.fid)
            layout = self.dlg_dim.findChild(QGridLayout, field['layoutname'])

            # Profilactic issue to prevent missed layouts againts db response and form
            if layout is not None:
                # Take the QGridLayout with the intention of adding a QSpacerItem later
                if layout not in layout_list and layout.objectName() not in ('lyt_top_1', 'lyt_bot_1', 'lyt_bot_2'):
                    layout_list.append(layout)
                    # Add widgets into layout
                    layout.addWidget(label, 0, field['layoutorder'])
                    layout.addWidget(widget, 1, field['layoutorder'])

                # If field is on top or bottom layout the position is horitzontal no vertical
                if field['layoutname'] in ('lyt_top_1', 'lyt_bot_1', 'lyt_bot_2'):
                    layout.addWidget(label, 0, field['layoutorder'])
                    layout.addWidget(widget, 1, field['layoutorder'])
                else:
                    tools_gw.add_widget(self.dlg_dim, field, label, widget)

        # Add a QSpacerItem into each QGridLayout of the list
        for layout in layout_list:
            vertical_spacer1 = QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)
            layout.addItem(vertical_spacer1)

        if self.layer_dimensions:
            self.iface.setActiveLayer(self.layer_dimensions)
            if self.layer_dimensions.isEditable():
                tools_gw.enable_all(self.dlg_dim, db_return['body']['data'])
            else:
                tools_gw.enable_widgets(self.dlg_dim, db_return['body']['data'], False)

        tools_qt.hide_void_groupbox(self.dlg_dim)
        self.iface.actionPan().trigger()
        
        title = f"DIMENSIONING - {self.fid}"
        tools_gw.open_dialog(self.dlg_dim, dlg_name='dimensioning', title=title)
        return False, False


    # region private functions


    def _cancel_dimensioning(self, action_snapping, action_orientation):

        self.iface.actionRollbackEdits().trigger()
        if action_snapping.isChecked():
            action_snapping.trigger()
        if action_orientation.isChecked():
            action_orientation.trigger()
        tools_qgis.restore_user_layer('v_edit_node', self.user_current_layer)
        tools_gw.disconnect_signal('dimensioning')
        tools_gw.close_dialog(self.dlg_dim)


    def _save_dimensioning(self, qgis_feature, layer):

        # Upsert feature into db
        layer.updateFeature(qgis_feature)
        layer.commitChanges()

        # Create body
        fields = ''
        list_widgets = self.dlg_dim.findChildren(QLineEdit)
        for widget in list_widgets:
            widget_name = widget.property('columnname')
            widget_value = tools_qt.get_text(self.dlg_dim, widget)
            if widget_value == 'null':
                continue
            fields += f'"{widget_name}":"{widget_value}", '

        list_widgets = self.dlg_dim.findChildren(QCheckBox)
        for widget in list_widgets:
            widget_name = widget.property('columnname')
            widget_value = f'"{tools_qt.is_checked(self.dlg_dim, widget)}"'
            if widget_value == 'null':
                continue
            fields += f'"{widget_name}":{widget_value},'


        list_widgets = self.dlg_dim.findChildren(QComboBox)
        for widget in list_widgets:
            widget_name = widget.property('columnname')
            widget_value = f'"{tools_qt.get_combo_value(self.dlg_dim, widget)}"'
            if widget_value == 'null':
                continue
            fields += f'"{widget_name}":{widget_value},'

        # remove last character (,) from fields
        fields = fields[:-1]

        feature = '"tableName":"v_edit_dimensions", '
        feature += f'"id":"{self.fid}"'
        extras = f'"fields":{{{fields}}}'
        body = tools_gw.create_body(feature=feature, extras=extras)
        tools_gw.execute_procedure('gw_fct_setdimensioning', body)

        # Close dialog
        tools_gw.close_dialog(self.dlg_dim)


    def _deactivate_signals(self, action, emit_point=None):

        self.vertex_marker.hide()
        try:
            self.canvas.xyCoordinates.disconnect()
        except TypeError:
            pass

        try:
            emit_point.canvasClicked.disconnect()
        except TypeError:
            pass

        if not action.isChecked():
            action.setChecked(False)
            return True

        return False


    def _snapping(self, action):

        # Set active layer and set signals
        tools_gw.disconnect_signal('dimensioning', 'snapping_ep_canvasClicked_click_button_snapping')
        tools_gw.disconnect_signal('dimensioning', 'snapping_xyCoordinates_mouse_move')
        emit_point = QgsMapToolEmitPoint(self.canvas)
        self.canvas.setMapTool(emit_point)
        if self._deactivate_signals(action, emit_point):
            return

        self.snapper_manager.remove_marker(self.vertex_marker)
        self.previous_snapping = self.snapper_manager.get_snapping_options()
        self.snapper_manager.set_snapping_status()
        self.snapper_manager.set_snapping_layers()

        self.snapper_manager.config_snap_to_node()
        self.snapper_manager.config_snap_to_connec()
        self.snapper_manager.config_snap_to_gully()
        self.snapper_manager.set_snap_mode()

        self.dlg_dim.actionOrientation.setChecked(False)
        self.iface.setActiveLayer(self.layer_node)
        tools_gw.connect_signal(self.canvas.xyCoordinates, self._mouse_move,
                                'dimensioning', 'snapping_xyCoordinates_mouse_move')
        tools_gw.connect_signal(emit_point.canvasClicked, partial(self._click_button_snapping, action, emit_point),
                                'dimensioning', 'snapping_ep_canvasClicked_click_button_snapping')


    def _mouse_move(self, point):

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

        # Snapping
        result = self.snapper_manager.snap_to_project_config_layers(event_point)
        if result.isValid():
            layer = self.snapper_manager.get_snapped_layer(result)
            # Check feature
            if layer == self.layer_node or layer == self.layer_connec:
                self.snapper_manager.add_marker(result, self.vertex_marker)


    def _click_button_snapping(self, action, emit_point, point, btn):

        if not self.layer_dimensions:
            return

        if btn == Qt.RightButton:
            if btn == Qt.RightButton:
                action.setChecked(False)
                tools_gw.disconnect_signal('dimensioning', 'snapping_ep_canvasClicked_click_button_snapping')
                tools_gw.disconnect_signal('dimensioning', 'snapping_xyCoordinates_mouse_move')
                self._deactivate_signals(action, emit_point)
                return

        layer = self.layer_dimensions
        self.iface.setActiveLayer(layer)

        # Get coordinates
        event_point = self.snapper_manager.get_event_point(point=point)

        # Snapping
        result = self.snapper_manager.snap_to_project_config_layers(event_point)
        if result.isValid():

            layer = self.snapper_manager.get_snapped_layer(result)
            # Check feature
            if layer == self.layer_node:
                feat_type = 'node'
            elif layer == self.layer_connec:
                feat_type = 'connec'
            else:
                return

            # Get the point
            snapped_feat = self.snapper_manager.get_snapped_feature(result)
            feature_id = self.snapper_manager.get_snapped_feature_id(result)
            element_id = snapped_feat.attribute(feat_type + '_id')

            # Leave selection
            layer.select([feature_id])

            # Get depth of the feature
            fieldname = None
            self.project_type = tools_gw.get_project_type()
            if self.project_type == 'ws':
                fieldname = "depth"
            elif self.project_type == 'ud' and feat_type == 'node':
                fieldname = "ymax"
            elif self.project_type == 'ud' and feat_type == 'connec':
                fieldname = "connec_depth"

            if fieldname is None:
                return

            depth = snapped_feat.attribute(fieldname)
            if depth in ('', None, 0, '0', 'NULL'):
                tools_qt.set_widget_text(self.dlg_dim, "depth", None)
            else:
                tools_qt.set_widget_text(self.dlg_dim, "depth", depth)
            tools_qt.set_widget_text(self.dlg_dim, "feature_id", element_id)
            tools_qt.set_widget_text(self.dlg_dim, "feature_type", feat_type.upper())

            self.snapper_manager.restore_snap_options(self.previous_snapping)
            tools_gw.disconnect_signal('dimensioning', 'snapping_ep_canvasClicked_click_button_snapping')
            tools_gw.disconnect_signal('dimensioning', 'snapping_xyCoordinates_mouse_move')
            self._deactivate_signals(action, emit_point)
            action.setChecked(False)


    def _orientation(self, action):

        tools_gw.disconnect_signal('dimensioning', 'orientation_ep_canvasClicked_click_button_orientation')
        tools_gw.disconnect_signal('dimensioning', 'orientation_xyCoordinates_canvas_move_event')
        emit_point = QgsMapToolEmitPoint(self.canvas)
        self.canvas.setMapTool(emit_point)
        if self._deactivate_signals(action, emit_point):
            return

        self.snapper_manager.remove_marker(self.vertex_marker)
        self.previous_snapping = self.snapper_manager.get_snapping_options()
        self.snapper_manager.set_snapping_status()
        self.snapper_manager.set_snapping_layers()

        self.snapper_manager.config_snap_to_node()
        self.snapper_manager.config_snap_to_connec()
        self.snapper_manager.config_snap_to_gully()
        self.snapper_manager.set_snap_mode()

        self.dlg_dim.actionSnapping.setChecked(False)
        tools_gw.connect_signal(self.canvas.xyCoordinates, self._canvas_move_event,
                                'dimensioning', 'orientation_xyCoordinates_canvas_move_event')
        tools_gw.connect_signal(emit_point.canvasClicked, partial(self._click_button_orientation, action, emit_point),
                                'dimensioning', 'orientation_ep_canvasClicked_click_button_orientation')


    def _canvas_move_event(self, point):

        # Get clicked point
        self.vertex_marker.hide()
        event_point = self.snapper_manager.get_event_point(point=point)
        result = self.snapper_manager.snap_to_project_config_layers(event_point)
        if self.snapper_manager.result_is_valid():
            self.snapper_manager.add_marker(result, self.vertex_marker)


    def _click_button_orientation(self, action, emit_point, point, btn):

        if not self.layer_dimensions:
            return

        if btn == Qt.RightButton:
            action.setChecked(False)
            tools_gw.disconnect_signal('dimensioning', 'orientation_ep_canvasClicked_click_button_orientation')
            tools_gw.disconnect_signal('dimensioning', 'orientation_xyCoordinates_canvas_move_event')
            self._deactivate_signals(action, emit_point)
            return

        self.x_symbol = self.dlg_dim.findChild(QLineEdit, "x_symbol")
        self.x_symbol.setText(str(int(point.x())))
        self.y_symbol = self.dlg_dim.findChild(QLineEdit, "y_symbol")
        self.y_symbol.setText(str(int(point.y())))

        self.snapper_manager.restore_snap_options(self.previous_snapping)
        tools_gw.disconnect_signal('dimensioning', 'orientation_ep_canvasClicked_click_button_orientation')
        tools_gw.disconnect_signal('dimensioning', 'orientation_xyCoordinates_canvas_move_event')
        self._deactivate_signals(action, emit_point)
        action.setChecked(False)


    def _create_map_tips(self):
        """ Create MapTips on the map """

        row = tools_gw.get_config_value('qgis_dim_tooltip')
        if not row or row[0].lower() != 'true':
            return

        tools_gw.disconnect_signal('dimensioning', 'create_map_tips_timer_map_tips_timeout_show_map_tip')
        self.timer_map_tips = QTimer(self.canvas)
        self.map_tip_node = QgsMapTip()
        self.map_tip_connec = QgsMapTip()

        tools_gw.connect_signal(self.canvas.xyCoordinates, self._map_tip_changed,
                                'dimensioning', 'create_map_tips_xyCoordinates_map_tip_changed')
        tools_gw.connect_signal(self.timer_map_tips.timeout, self._show_map_tip,
                                'dimensioning', 'create_map_tips_timer_map_tips_timeout_show_map_tip')

        tools_gw.disconnect_signal('dimensioning', 'create_map_tips_timer_map_tips_clear_timeout_clear_map_tip')
        self.timer_map_tips_clear = QTimer(self.canvas)
        tools_gw.connect_signal(self.timer_map_tips_clear.timeout, self._clear_map_tip,
                                'dimensioning', 'create_map_tips_timer_map_tips_clear_timeout_clear_map_tip')


    def _map_tip_changed(self, point):
        """ SLOT. Initialize the Timer to show MapTips on the map """

        if self.canvas.underMouse():
            self.last_map_position = QgsPointXY(point.x(), point.y())
            self.map_tip_node.clear(self.canvas)
            self.map_tip_connec.clear(self.canvas)
            self.timer_map_tips.start(100)


    def _show_map_tip(self):
        """ Show MapTips on the map """

        self.timer_map_tips.stop()
        if self.canvas.underMouse():
            point_qgs = self.last_map_position
            point_qt = self.canvas.mouseLastXY()
            if self.layer_node:
                self.map_tip_node.showMapTip(self.layer_node, point_qgs, point_qt, self.canvas)
            if self.layer_connec:
                self.map_tip_connec.showMapTip(self.layer_connec, point_qgs, point_qt, self.canvas)
            self.timer_map_tips_clear.start(1000)


    def _clear_map_tip(self):
        """ Clear MapTips """

        self.timer_map_tips_clear.stop()
        self.map_tip_node.clear(self.canvas)
        self.map_tip_connec.clear(self.canvas)


    def _set_widgets(self, dialog, db_return, field):

        widget = None
        label = None
        if field['label']:
            label = QLabel()
            label.setObjectName('lbl_' + field['widgetname'])
            label.setText(field['label'].capitalize())
            if 'stylesheet' in field and field['stylesheet'] is not None and 'label' in field['stylesheet']:
                label = tools_gw.set_stylesheet(field, label)
            if 'tooltip' in field:
                label.setToolTip(field['tooltip'])
            else:
                label.setToolTip(field['label'].capitalize())
        if field['widgettype'] == 'text' or field['widgettype'] == 'typeahead':
            completer = QCompleter()
            widget = tools_gw.add_lineedit(field)
            widget = tools_gw.set_widget_size(widget, field)
            widget = tools_gw.set_data_type(field, widget)
            if field['widgettype'] == 'typeahead':
                widget = tools_gw.set_typeahead(field, dialog, widget, completer)
        elif field['widgettype'] == 'combo':
            widget = tools_gw.add_combo(field)
            widget = tools_gw.set_widget_size(widget, field)
        elif field['widgettype'] == 'check':
            widget = tools_gw.add_checkbox(field)
        elif field['widgettype'] == 'datetime':
            widget = tools_gw.add_calendar(dialog, field)
        elif field['widgettype'] == 'button':
            widget = tools_gw.add_button(dialog, field)
            widget = tools_gw.set_widget_size(widget, field)
        elif field['widgettype'] == 'hyperlink':
            widget = tools_gw.add_hyperlink(field)
            widget = tools_gw.set_widget_size(widget, field)
        elif field['widgettype'] == 'hspacer':
            widget = tools_qt.add_horizontal_spacer()
        elif field['widgettype'] == 'vspacer':
            widget = tools_qt.add_verticalspacer()
        elif field['widgettype'] == 'textarea':
            widget = tools_gw.add_textarea(field)
        elif field['widgettype'] in 'spinbox':
            widget = tools_gw.add_spinbox(field)
        elif field['widgettype'] == 'tableview':
            widget = tools_gw.add_tableview(db_return, field)
            widget = tools_gw.add_tableview_header(widget, field)
            widget = tools_gw.fill_tableview_rows(widget, field)
            widget = tools_gw.set_tablemodel_config(dialog, widget, field['widgetname'], sort_order=1,
                                                    isQStandardItemModel=True)
            tools_qt.set_tableview_config(widget)
        widget.setObjectName(widget.property('columnname'))

        return label, widget

    # endregion 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 GwElement:

    def __init__(self):
        """ Class to control 'Add element' of toolbar 'edit' """

        self.iface = global_vars.iface
        self.schema_name = global_vars.schema_name
        self.canvas = global_vars.canvas
        self.snapper_manager = GwSnapManager(self.iface)
        self.vertex_marker = self.snapper_manager.vertex_marker


    def get_element(self, new_element_id=True, feature=None, feature_type=None, selected_object_id=None):
        """ Button 33: Add element """

        self.rubber_band = tools_gw.create_rubberband(self.canvas)
        self.new_element_id = new_element_id

        # Create the dialog and signals
        self.dlg_add_element = GwElementUi()
        tools_gw.load_settings(self.dlg_add_element)
        self.element_id = None

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

        widget_list = self.dlg_add_element.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 = {}
        self.list_ids['arc'] = []
        self.list_ids['node'] = []
        self.list_ids['connec'] = []
        self.list_ids['gully'] = []
        self.list_ids['element'] = []

        # Setting layers
        self.layers = {}
        self.layers['arc'] = []
        self.layers['node'] = []
        self.layers['connec'] = []
        self.layers['gully'] = []
        self.layers['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')
        self.point_xy = {"x": None, "y": None}

        # Remove 'gully' for 'WS'
        self.project_type = tools_gw.get_project_type()
        if self.project_type == 'ws':
            tools_qt.remove_tab(self.dlg_add_element.tab_feature, 'tab_gully')
        else:
            self.layers['gully'] = tools_gw.get_layers_from_feature_type('gully')

        # Set icons
        tools_gw.add_icon(self.dlg_add_element.btn_add_geom, "133")
        tools_gw.add_icon(self.dlg_add_element.btn_insert, "111", sub_folder="24x24")
        tools_gw.add_icon(self.dlg_add_element.btn_delete, "112", sub_folder="24x24")
        tools_gw.add_icon(self.dlg_add_element.btn_snapping, "137")

        # Remove all previous selections
        self.layers = tools_gw.remove_selection(True, layers=self.layers)
        if feature:
            layer = self.layers[feature_type][0]
            layer.selectByIds([feature.id()])

        self._check_date(self.dlg_add_element.builtdate, self.dlg_add_element.btn_accept, 1)
        self._check_date(self.dlg_add_element.enddate, self.dlg_add_element.btn_accept, 1)

        # Get layer element and save if is visible or not for restore when finish process
        layer_element = tools_qgis.get_layer_by_tablename("v_edit_element")
        layer_is_visible = False
        if layer_element:
            layer_is_visible = tools_qgis.is_layer_visible(layer_element)

        recursive = False
        if layer_is_visible:
            recursive = True

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

        # Set signals
        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_element.rejected.connect(partial(tools_gw.restore_parent_layers_visibility, layers_visibility))
        self.dlg_add_element.btn_accept.clicked.connect(partial(self._manage_element_accept, table_object))
        self.dlg_add_element.btn_accept.clicked.connect(
            partial(tools_qgis.set_layer_visible, layer_element, recursive, layer_is_visible))
        self.dlg_add_element.btn_cancel.clicked.connect(lambda: setattr(self, 'layers', tools_gw.manage_close(
            self.dlg_add_element, table_object, cur_active_layer, layers=self.layers)))
        self.dlg_add_element.btn_cancel.clicked.connect(
            partial(tools_qgis.set_layer_visible, layer_element, recursive, layer_is_visible))
        self.dlg_add_element.rejected.connect(lambda: setattr(self, 'layers', tools_gw.manage_close(
            self.dlg_add_element, table_object, cur_active_layer, layers=self.layers)))
        self.dlg_add_element.rejected.connect(
            partial(tools_qgis.set_layer_visible, layer_element, recursive, layer_is_visible))
        self.dlg_add_element.tab_feature.currentChanged.connect(
            partial(tools_gw.get_signal_change_tab, self.dlg_add_element, excluded_layers))
        self.dlg_add_element.rejected.connect(lambda: tools_gw.reset_rubberband(self.rubber_band))

        self.dlg_add_element.element_id.textChanged.connect(
            partial(self._fill_dialog_element, self.dlg_add_element, table_object, None))
        self.dlg_add_element.btn_insert.clicked.connect(
            partial(tools_gw.insert_feature, self, self.dlg_add_element, table_object, False, False, None, None))
        self.dlg_add_element.btn_delete.clicked.connect(
            partial(tools_gw.delete_records, self, self.dlg_add_element, table_object, False, None, None))
        self.dlg_add_element.btn_snapping.clicked.connect(
            partial(tools_gw.selection_init, self, self.dlg_add_element, table_object, False))

        self.dlg_add_element.btn_add_geom.clicked.connect(self._get_point_xy)
        self.dlg_add_element.state.currentIndexChanged.connect(partial(self._filter_state_type))

        self.dlg_add_element.tbl_element_x_arc.clicked.connect(partial(tools_qgis.hilight_feature_by_id,
            self.dlg_add_element.tbl_element_x_arc, "v_edit_arc", "arc_id", self.rubber_band, 5))
        self.dlg_add_element.tbl_element_x_node.clicked.connect(partial(tools_qgis.hilight_feature_by_id,
            self.dlg_add_element.tbl_element_x_node, "v_edit_node", "node_id", self.rubber_band, 10))
        self.dlg_add_element.tbl_element_x_connec.clicked.connect(partial(tools_qgis.hilight_feature_by_id,
            self.dlg_add_element.tbl_element_x_connec, "v_edit_connec", "connec_id", self.rubber_band, 10))
        self.dlg_add_element.tbl_element_x_gully.clicked.connect(partial(tools_qgis.hilight_feature_by_id,
            self.dlg_add_element.tbl_element_x_gully, "v_edit_gully", "gully_id", self.rubber_band, 10))

        # Fill combo boxes of the form and related events
        self.dlg_add_element.element_type.currentIndexChanged.connect(partial(self._filter_elementcat_id))
        self.dlg_add_element.element_type.currentIndexChanged.connect(partial(self._update_location_cmb))

        # TODO maybe all this values can be in one Json query
        # Fill combo boxes
        sql = "SELECT DISTINCT(elementtype_id), elementtype_id FROM cat_element ORDER BY elementtype_id"
        rows = tools_db.get_rows(sql)
        tools_qt.fill_combo_values(self.dlg_add_element.element_type, rows, 1)

        sql = "SELECT expl_id, name FROM exploitation WHERE expl_id != '0' ORDER BY name"
        rows = tools_db.get_rows(sql)
        tools_qt.fill_combo_values(self.dlg_add_element.expl_id, rows, 1)

        # Set explotation vdefault for combo expl_id
        sql = "SELECT value FROM config_param_user WHERE parameter='edit_exploitation_vdefault' AND cur_user=current_user"
        row = tools_db.get_row(sql)
        if row is not None:
            tools_qt.set_combo_value(self.dlg_add_element.expl_id, row[0], 0)

        sql = "SELECT DISTINCT(id), name FROM value_state"
        rows = tools_db.get_rows(sql)
        tools_qt.fill_combo_values(self.dlg_add_element.state, rows, 1)

        self._filter_state_type()

        sql = ("SELECT location_type, location_type FROM man_type_location"
               " WHERE feature_type = 'ELEMENT' "
               " ORDER BY location_type")
        rows = tools_db.get_rows(sql)
        tools_qt.fill_combo_values(self.dlg_add_element.location_type, rows, 1)
        if rows:
            tools_qt.set_combo_value(self.dlg_add_element.location_type, rows[0][0], 0)

        sql = "SELECT DISTINCT(id), id FROM cat_owner"
        rows = tools_db.get_rows(sql)
        tools_qt.fill_combo_values(self.dlg_add_element.ownercat_id, rows, 1, add_empty=True)

        sql = "SELECT DISTINCT(id), id FROM cat_builder"
        rows = tools_db.get_rows(sql)
        tools_qt.fill_combo_values(self.dlg_add_element.buildercat_id, rows, 1, add_empty=True)

        sql = "SELECT DISTINCT(id), id FROM cat_work"
        rows = tools_db.get_rows(sql)
        tools_qt.fill_combo_values(self.dlg_add_element.workcat_id, rows, 1, add_empty=True)
        self.dlg_add_element.workcat_id.currentIndexChanged.connect(partial(
            tools_qt.set_stylesheet, self.dlg_add_element.workcat_id, None))

        sql = "SELECT DISTINCT(id), id FROM cat_work"
        rows = tools_db.get_rows(sql)
        tools_qt.fill_combo_values(self.dlg_add_element.workcat_id_end, rows, 1, add_empty=True)

        sql = "SELECT id, idval FROM edit_typevalue WHERE typevalue = 'value_verified'"
        rows = tools_db.get_rows(sql)
        tools_qt.fill_combo_values(self.dlg_add_element.verified, rows, 1, add_empty=True)
        self._filter_elementcat_id()

        if self.new_element_id:
            self._set_default_values()

        # Adding auto-completion to a QLineEdit for default feature
        tools_gw.set_completer_widget("v_edit_arc", self.dlg_add_element.feature_id, "arc_id", )

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

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

        # Force layer v_edit_element set active True
        if layer_element:
            tools_qgis.set_layer_visible(layer_element)

        # If is a new element dont need set enddate
        if self.new_element_id is True:
            tools_qt.set_widget_text(self.dlg_add_element, 'num_elements', '1')

        self._update_location_cmb()
        if not self.new_element_id:
            tools_qt.set_widget_text(self.dlg_add_element, 'element_id', selected_object_id)
            self._fill_dialog_element(self.dlg_add_element, 'element', None)

        # Open the dialog
        tools_gw.open_dialog(self.dlg_add_element, dlg_name='element', hide_config_widgets=True)
        return self.dlg_add_element


    def _update_location_cmb(self):

        element_type = tools_qt.get_text(self.dlg_add_element, self.dlg_add_element.element_type)
        sql = (f"SELECT location_type, location_type FROM man_type_location"
               f" WHERE feature_type = 'ELEMENT' "
               f" AND ('{element_type}' = ANY(featurecat_id::text[]) OR featurecat_id is null)"
               f" ORDER BY location_type")
        rows = tools_db.get_rows(sql)
        tools_qt.fill_combo_values(self.dlg_add_element.location_type, rows, add_empty=True)
        if rows:
            tools_qt.set_combo_value(self.dlg_add_element.location_type, rows[0][0], 0)


    def manage_elements(self):
        """ Button 67: Edit element """

        # Create the dialog
        self.dlg_man = GwElementManagerUi()
        tools_gw.load_settings(self.dlg_man)
        self.dlg_man.tbl_element.setSelectionBehavior(QAbstractItemView.SelectRows)

        # Adding auto-completion to a QLineEdit
        table_object = "element"
        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_element, 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_element, table_object)

        # Set signals
        self.dlg_man.element_id.textChanged.connect(partial(
            tools_qt.filter_by_id, self.dlg_man, self.dlg_man.tbl_element, self.dlg_man.element_id, table_object))
        self.dlg_man.tbl_element.doubleClicked.connect(partial(
            self._open_selected_object_element, self.dlg_man, self.dlg_man.tbl_element, 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_element, table_object))

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


    # region private functions

    def _get_point_xy(self):

        self.snapper_manager.add_point(self.vertex_marker)
        self.point_xy = self.snapper_manager.point_xy


    def _set_default_values(self):
        """ Set default values """

        row = tools_gw.get_config_value("edit_elementcat_vdefault")
        if row is not None and row[0]:
            sql = f"SELECT elementtype_id, elementtype_id FROM cat_element WHERE id = '{row[0]}'"
            element_type = tools_db.get_row(sql)
            tools_qt.set_combo_value(self.dlg_add_element.element_type, element_type[0], 0)

        self._manage_combo(self.dlg_add_element.elementcat_id, 'edit_elementcat_vdefault')
        self._manage_combo(self.dlg_add_element.state, 'edit_state_vdefault')
        self._manage_combo(self.dlg_add_element.state_type, 'edit_statetype_1_vdefault')
        self._manage_combo(self.dlg_add_element.ownercat_id, 'edit_ownercat_vdefault')
        self._manage_combo(self.dlg_add_element.workcat_id, 'edit_workcat_vdefault')
        self._manage_combo(self.dlg_add_element.workcat_id_end, 'edit_workcat_id_end_vdefault')
        self._manage_combo(self.dlg_add_element.verified, 'edit_verified_vdefault')

        builtdate_vdef = tools_gw.get_config_value('edit_builtdate_vdefault')
        enddate_vdef = tools_gw.get_config_value('edit_enddate_vdefault')
        if builtdate_vdef:
            self.dlg_add_element.builtdate.setText(builtdate_vdef[0].replace('/', '-'))
        if enddate_vdef:
            self.dlg_add_element.enddate.setText(enddate_vdef[0].replace('/', '-'))


    def _manage_combo(self, combo, parameter):

        row = tools_gw.get_config_value(parameter)
        if row:
            tools_qt.set_combo_value(combo, row[0], 0)


    def _filter_state_type(self):

        state = tools_qt.get_combo_value(self.dlg_add_element, self.dlg_add_element.state, 0)
        sql = (f"SELECT DISTINCT(id), name FROM value_state_type "
               f"WHERE state = {state}")
        rows = tools_db.get_rows(sql)
        tools_qt.fill_combo_values(self.dlg_add_element.state_type, rows, 1)


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

        widget = "tbl_element_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)

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


    def _manage_element_accept(self, table_object):
        """ Insert or update table 'element'. Add element to selected feature """

        # Get values from dialog
        element_id = tools_qt.get_text(self.dlg_add_element, "element_id", return_string_null=False)
        code = tools_qt.get_text(self.dlg_add_element, "code", return_string_null=False)
        elementcat_id = tools_qt.get_combo_value(self.dlg_add_element, self.dlg_add_element.elementcat_id)
        ownercat_id = tools_qt.get_combo_value(self.dlg_add_element, self.dlg_add_element.ownercat_id)
        location_type = tools_qt.get_combo_value(self.dlg_add_element, self.dlg_add_element.location_type)
        buildercat_id = tools_qt.get_combo_value(self.dlg_add_element, self.dlg_add_element.buildercat_id)
        builtdate = tools_qt.get_text(self.dlg_add_element, "builtdate", return_string_null=False)
        enddate = tools_qt.get_text(self.dlg_add_element, "enddate", return_string_null=False)
        workcat_id = tools_qt.get_combo_value(self.dlg_add_element, self.dlg_add_element.workcat_id)
        workcat_id_end = tools_qt.get_combo_value(self.dlg_add_element, self.dlg_add_element.workcat_id_end)
        comment = tools_qt.get_text(self.dlg_add_element, "comment", return_string_null=False)
        observ = tools_qt.get_text(self.dlg_add_element, "observ", return_string_null=False)
        link = tools_qt.get_text(self.dlg_add_element, "link", return_string_null=False)
        verified = tools_qt.get_combo_value(self.dlg_add_element, self.dlg_add_element.verified)
        rotation = tools_qt.get_text(self.dlg_add_element, "rotation")
        if rotation == 0 or rotation is None or rotation == 'null':
            rotation = '0'
        undelete = self.dlg_add_element.undelete.isChecked()

        # Check mandatory fields
        message = "You need to insert value for field"
        if elementcat_id == '':
            tools_qgis.show_warning(message, parameter="elementcat_id")
            return
        num_elements = tools_qt.get_text(self.dlg_add_element, "num_elements", return_string_null=False)
        if num_elements == '':
            tools_qgis.show_warning(message, parameter="num_elements")
            return
        state = tools_qt.get_combo_value(self.dlg_add_element, self.dlg_add_element.state)
        if state == '':
            tools_qgis.show_warning(message, parameter="state_id")
            return

        state_type = tools_qt.get_combo_value(self.dlg_add_element, self.dlg_add_element.state_type)
        expl_id = tools_qt.get_combo_value(self.dlg_add_element, self.dlg_add_element.expl_id)

        # Get SRID
        srid = global_vars.data_epsg

        # Check if this element already exists
        sql = (f"SELECT DISTINCT(element_id)"
               f" FROM {table_object}"
               f" WHERE element_id = '{element_id}'")
        row = tools_db.get_row(sql, log_info=False)
        if row is None:
            # If object not exist perform an INSERT
            if element_id == '':
                sql = ("INSERT INTO v_edit_element (elementcat_id,  num_elements, state, state_type"
                       ", expl_id, rotation, comment, observ, link, undelete, builtdate, enddate, ownercat_id"
                       ", location_type, buildercat_id, workcat_id, workcat_id_end, verified, the_geom, code)")
                sql_values = (f" VALUES ('{elementcat_id}', '{num_elements}', '{state}', '{state_type}', "
                              f"'{expl_id}', '{rotation}', $${comment}$$, $${observ}$$, "
                              f"$${link}$$, '{undelete}'")
            else:
                sql = ("INSERT INTO v_edit_element (element_id, elementcat_id, num_elements, state, state_type"
                       ", expl_id, rotation, comment, observ, link, undelete, builtdate, enddate, ownercat_id"
                       ", location_type, buildercat_id, workcat_id, workcat_id_end, verified, the_geom, code)")

                sql_values = (f" VALUES ('{element_id}', '{elementcat_id}', '{num_elements}',  '{state}', "
                              f"'{state_type}', '{expl_id}', '{rotation}', $${comment}$$, $${observ}$$, $${link}$$, "
                              f"'{undelete}'")
            if builtdate:
                sql_values += f", '{builtdate}'"
            else:
                sql_values += ", null"
            if enddate:
                sql_values += f", '{enddate}'"
            else:
                sql_values += ", null"
            if ownercat_id:
                sql_values += f", '{ownercat_id}'"
            else:
                sql_values += ", null"
            if location_type:
                sql_values += f", '{location_type}'"
            else:
                sql_values += ", null"
            if buildercat_id:
                sql_values += f", '{buildercat_id}'"
            else:
                sql_values += ", null"
            if workcat_id:
                sql_values += f", '{workcat_id}'"
            else:
                sql_values += ", null"
            if workcat_id_end:
                sql_values += f", '{workcat_id_end}'"
            else:
                sql_values += ", null"
            if verified:
                sql_values += f", '{verified}'"
            else:
                sql_values += ", null"
            if str(self.point_xy['x']) not in ("", None, "None"):
                sql_values += f", ST_SetSRID(ST_MakePoint({self.point_xy['x']},{self.point_xy['y']}), {srid})"
                self.point_xy['x'] = ""
            else:
                sql_values += ", null"
            if code:
                sql_values += f", $${code}$$"
            else:
                sql_values += ", null"
            if element_id == '':
                sql += sql_values + ") RETURNING element_id;"
                new_elem_id = tools_db.execute_returning(sql)
                sql_values = ""
                sql = ""
                element_id = str(new_elem_id[0])
            else:
                sql_values += ");\n"
            sql += sql_values
        # If object already exist perform an UPDATE
        else:
            message = "Are you sure you want to update the data?"
            answer = tools_qt.show_question(message)
            if not answer:
                return
            sql = (f"UPDATE element"
                   f" SET elementcat_id = '{elementcat_id}', num_elements = '{num_elements}', state = '{state}'"
                   f", state_type = '{state_type}', expl_id = '{expl_id}', rotation = '{rotation}'"
                   f", comment = $${comment}$$, observ = $${observ}$$"
                   f", link = $${link}$$, undelete = '{undelete}'")
            if builtdate:
                sql += f", builtdate = '{builtdate}'"
            else:
                sql += ", builtdate = null"
            if enddate:
                sql += f", enddate = '{enddate}'"
            else:
                sql += ", enddate = null"
            if ownercat_id:
                sql += f", ownercat_id = '{ownercat_id}'"
            else:
                sql += ", ownercat_id = null"
            if location_type:
                sql += f", location_type = '{location_type}'"
            else:
                sql += ", location_type = null"
            if buildercat_id:
                sql += f", buildercat_id = '{buildercat_id}'"
            else:
                sql += ", buildercat_id = null"
            if workcat_id:
                sql += f", workcat_id = '{workcat_id}'"
            else:
                sql += ", workcat_id = null"
            if code:
                sql += f", code = $${code}$$"
            else:
                sql += ", code = null"
            if workcat_id_end:
                sql += f", workcat_id_end = '{workcat_id_end}'"
            else:
                sql += ", workcat_id_end = null"
            if verified:
                sql += f", verified = '{verified}'"
            else:
                sql += ", verified = null"
            if str(self.point_xy['x']) not in ("", None, "None"):
                sql += f", the_geom = ST_SetSRID(ST_MakePoint({self.point_xy['x']},{self.point_xy['y']}), {srid})"

            sql += f" WHERE element_id = '{element_id}';"
        # Manage records in tables @table_object_x_@feature_type
        sql += (f"\nDELETE FROM element_x_node"
                f" WHERE element_id = '{element_id}';")
        sql += (f"\nDELETE FROM element_x_arc"
                f" WHERE element_id = '{element_id}';")
        sql += (f"\nDELETE FROM element_x_connec"
                f" WHERE element_id = '{element_id}';")

        if self.list_ids['arc']:
            for feature_id in self.list_ids['arc']:
                sql += (f"\nINSERT INTO element_x_arc (element_id, arc_id)"
                        f" VALUES ('{element_id}', '{feature_id}');")
        if self.list_ids['node']:
            for feature_id in self.list_ids['node']:
                sql += (f"\nINSERT INTO element_x_node (element_id, node_id)"
                        f" VALUES ('{element_id}', '{feature_id}');")
        if self.list_ids['connec']:
            for feature_id in self.list_ids['connec']:
                sql += (f"\nINSERT INTO element_x_connec (element_id, connec_id)"
                        f" VALUES ('{element_id}', '{feature_id}');")

        status = tools_db.execute_sql(sql)
        if status:
            self.element_id = element_id
            self.layers = tools_gw.manage_close(self.dlg_add_element, table_object, layers=self.layers)


    def _filter_elementcat_id(self):
        """ Filter QComboBox @elementcat_id according QComboBox @elementtype_id """

        element_type = tools_qt.get_combo_value(self.dlg_add_element, self.dlg_add_element.element_type, 1)
        sql = (f"SELECT DISTINCT(id), id FROM cat_element"
               f" WHERE elementtype_id = '{element_type}'"
               f" ORDER BY id")
        rows = tools_db.get_rows(sql)
        tools_qt.fill_combo_values(self.dlg_add_element.elementcat_id, rows, 1)


    def _open_selected_object_element(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 = 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', 'element_manager_keep_open', "user", "init", prefix=True)
        if tools_os.set_boolean(keep_open_form, False) is not True:
            dialog.close()

        self.get_element(new_element_id=False, selected_object_id=selected_object_id)


    def _check_date(self, widget, button=None, regex_type=1):
        """ Set QRegExpression in order to validate QLineEdit(widget) field type date.
        Also allow to enable or disable a QPushButton(button), like typical accept button
        @Type=1 (yyy-mm-dd), @Type=2 (dd-mm-yyyy)
        """

        reg_exp = ""
        placeholder = "yyyy-mm-dd"
        if regex_type == 1:
            widget.setPlaceholderText("yyyy-mm-dd")
            placeholder = "yyyy-mm-dd"
            reg_exp = QRegExp("(((\d{4})([-])(0[13578]|10|12)([-])(0[1-9]|[12][0-9]|3[01]))|"
                              "((\d{4})([-])(0[469]|11)([-])([0][1-9]|[12][0-9]|30))|"
                              "((\d{4})([-])(02)([-])(0[1-9]|1[0-9]|2[0-8]))|"
                              "(([02468][048]00)([-])(02)([-])(29))|"
                              "(([13579][26]00)([-])(02)([-])(29))|"
                              "(([0-9][0-9][0][48])([-])(02)([-])(29))|"
                              "(([0-9][0-9][2468][048])([-])(02)([-])(29))|"
                              "(([0-9][0-9][13579][26])([-])(02)([-])(29)))")
        elif regex_type == 2:
            widget.setPlaceholderText("dd-mm-yyyy")
            placeholder = "dd-mm-yyyy"
            reg_exp = QRegExp("(((0[1-9]|[12][0-9]|3[01])([-])(0[13578]|10|12)([-])(\d{4}))|"
                              "(([0][1-9]|[12][0-9]|30)([-])(0[469]|11)([-])(\d{4}))|"
                              "((0[1-9]|1[0-9]|2[0-8])([-])(02)([-])(\d{4}))|"
                              "((29)(-)(02)([-])([02468][048]00))|"
                              "((29)([-])(02)([-])([13579][26]00))|"
                              "((29)([-])(02)([-])([0-9][0-9][0][48]))|"
                              "((29)([-])(02)([-])([0-9][0-9][2468][048]))|"
                              "((29)([-])(02)([-])([0-9][0-9][13579][26])))")
        elif regex_type == 3:
            widget.setPlaceholderText("yyyy/mm/dd")
            placeholder = "yyyy/mm/dd"
            reg_exp = QRegExp("(((\d{4})([/])(0[13578]|10|12)([/])(0[1-9]|[12][0-9]|3[01]))|"
                              "((\d{4})([/])(0[469]|11)([/])([0][1-9]|[12][0-9]|30))|"
                              "((\d{4})([/])(02)([/])(0[1-9]|1[0-9]|2[0-8]))|"
                              "(([02468][048]00)([/])(02)([/])(29))|"
                              "(([13579][26]00)([/])(02)([/])(29))|"
                              "(([0-9][0-9][0][48])([/])(02)([/])(29))|"
                              "(([0-9][0-9][2468][048])([/])(02)([/])(29))|"
                              "(([0-9][0-9][13579][26])([/])(02)([/])(29)))")
        elif regex_type == 4:
            widget.setPlaceholderText("dd/mm/yyyy")
            placeholder = "dd/mm/yyyy"
            reg_exp = QRegExp("(((0[1-9]|[12][0-9]|3[01])([/])(0[13578]|10|12)([/])(\d{4}))|"
                              "(([0][1-9]|[12][0-9]|30)([/])(0[469]|11)([/])(\d{4}))|"
                              "((0[1-9]|1[0-9]|2[0-8])([/])(02)([/])(\d{4}))|"
                              "((29)(-)(02)([/])([02468][048]00))|"
                              "((29)([/])(02)([/])([13579][26]00))|"
                              "((29)([/])(02)([/])([0-9][0-9][0][48]))|"
                              "((29)([/])(02)([/])([0-9][0-9][2468][048]))|"
                              "((29)([/])(02)([/])([0-9][0-9][13579][26])))")

        widget.setValidator(QRegExpValidator(reg_exp))
        widget.textChanged.connect(partial(self._check_regex, widget, reg_exp, button, placeholder))


    def _check_regex(self, widget, reg_exp, button, placeholder, text):

        is_valid = False
        if reg_exp.exactMatch(text) is True:
            widget.setStyleSheet(None)
            is_valid = True
        elif str(text) == '':
            widget.setStyleSheet(None)
            widget.setPlaceholderText(placeholder)
            is_valid = True
        elif reg_exp.exactMatch(text) is False:
            widget.setStyleSheet("border: 1px solid red")
            is_valid = False

        if button is not None and type(button) == QPushButton:
            if is_valid is False:
                button.setEnabled(False)
            else:
                button.setEnabled(True)


    def _fill_dialog_element(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 {table_object}_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 = ["elementcat_id", "state", "expl_id", "ownercat_id", "location_type", "buildercat_id",
                       "workcat_id", "workcat_id_end", "comment", "observ", "path", "rotation", "verified",
                       "num_elements"]
            for widget_name in widgets:
                tools_qt.set_widget_text(dialog, widget_name, "")

            self._set_combo_from_param_user(dialog, 'state', 'value_state', 'edit_state_vdefault', field_name='name')
            self._set_combo_from_param_user(dialog, 'expl_id', 'exploitation', 'edit_exploitation_vdefault',
                                           field_id='expl_id', field_name='name')

            builtdate = tools_gw.get_config_value('edit_builtdate_vdefault')
            if builtdate: self.dlg_add_element.builtdate.setText(builtdate[0].replace('/', '-'))
            enddate = tools_gw.get_config_value('edit_enddate_vdefault')
            if enddate: self.dlg_add_element.enddate.setText(enddate[0].replace('/', '-'))
            self._set_combo_from_param_user(dialog, 'workcat_id', 'cat_work', 'edit_workcat_vdefault',
                                           field_id='id', field_name='id')
            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

        tools_qt.set_widget_text(dialog, "code", row['code'])
        sql = (f"SELECT elementtype_id FROM cat_element"
               f" WHERE id = '{row['elementcat_id']}'")
        row_type = tools_db.get_row(sql)
        if row_type:
            tools_qt.set_widget_text(dialog, "element_type", f"{row_type[0]}")

        # Fill input widgets with data of the @row
        cmb_widgets = ["elementcat_id", "state", "state_type", "expl_id", "ownercat_id", "location_type", "buildercat_id", "workcat_id",
                   "workcat_id_end", "verified"]
        widgets = ["comment", "observ", "link", "rotation", "num_elements"]
        for widget_name in cmb_widgets:
            widget = dialog.findChild(QComboBox, widget_name)
            tools_qt.set_combo_value(widget, f"{row[widget_name]}", 0)
        for widget_name in widgets:
            tools_qt.set_widget_text(dialog, widget_name, f"{row[widget_name]}")

        tools_qt.set_widget_text(dialog, "builtdate", row['builtdate'])
        tools_qt.set_widget_text(dialog, "enddate", row['enddate'])
        if str(row['undelete']) == 'True':
            dialog.undelete.setChecked(True)

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


    def _set_combo_from_param_user(self, dialog, widget, table_name, parameter, field_id='id', field_name='id'):
        """ Executes query and set combo box """

        sql = (f"SELECT t1.{field_name} FROM {table_name} as t1"
               f" INNER JOIN config_param_user as t2 ON t1.{field_id}::text = t2.value::text"
               f" WHERE parameter = '{parameter}' AND cur_user = current_user")
        row = tools_db.get_row(sql)
        if row:
            tools_qt.set_widget_text(dialog, widget, row[0])


    # endregion class GwVisitGallery:

    def __init__(self):
        """ Class to control 'Add element' of toolbar 'edit' """

        pass


    def manage_gallery(self):

        # Create the dialog and signals
        self.dlg_gallery = GwGalleryUi()
        tools_gw.load_settings(self.dlg_gallery)


    def fill_gallery(self, visit_id, event_id):

        self.img_path_list = []
        self.img_path_list1D = []

        # Get all pictures for event_id | visit_id
        sql = (f"SELECT value FROM om_visit_event_photo"
               f" WHERE event_id = '{event_id}' AND visit_id = '{visit_id}'")
        rows = tools_db.get_rows(sql)
        num = len(rows)
        for m in range(0, num):
            self.img_path_list1D.append(rows[m][0])

        # Add picture to gallery
        # Fill one-dimensional array till the end with "0"
        self.num_events = len(self.img_path_list1D)

        # Fill the rest of the fields in gallery with 0
        limit = self.num_events % 9
        limit = 9 - limit
        for k in range(0, limit):  # @UnusedVariable
            self.img_path_list1D.append(0)

        # Inicialization of two-dimensional array
        columns = 9
        rows = self.num_events
        # noinspection PyUnusedLocal
        self.img_path_list = [[0 for x in range(columns)] for x in range(rows)]

        # Convert one-dimensional array to two-dimensional array
        # Fill self.img_path_list with values from self.img_path_list1D
        idx = 0
        if rows == 1:
            for br in range(0, len(self.img_path_list1D)):
                self.img_path_list[0][br] = self.img_path_list1D[br]
        else:
            for h in range(0, rows):
                for r in range(0, columns - 1):
                    if idx >= columns:
                        break
                    self.img_path_list[h][r] = self.img_path_list1D[idx]
                    idx = idx + 1

        # List of pointers(in memory) of clicableLabels
        self.list_widget = []
        self.list_labels = []

        # Fill first slide of gallery
        for i in range(0, 9):
            widget_name = "img_" + str(i)
            widget = self.dlg_gallery.findChild(QLabel, widget_name)
            if widget:
                # Set image to QLabel

                # Parse a URL into components
                url = urllib.parse.urlsplit(str(self.img_path_list[0][i]))

                # Check if path is URL
                if url.scheme == "http" or url.scheme == "https":
                    url = str(self.img_path_list[0][i])
                    data = urllib.request.urlopen(url).read()
                    pixmap = QPixmap()
                    pixmap.loadFromData(data)
                else:
                    pixmap = QPixmap(str(self.img_path_list[0][i]))

                pixmap = pixmap.scaled(171, 151, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
                widget_extended = GwExtendedQLabel(widget)
                widget_extended.setPixmap(pixmap)
                widget_extended.clicked.connect(partial(self._zoom_img, i, visit_id, event_id))
                self.list_widget.append(widget_extended)
                self.list_labels.append(widget)

        txt_visit_id = self.dlg_gallery.findChild(QLineEdit, 'visit_id')
        txt_visit_id.setText(str(visit_id))
        txt_event_id = self.dlg_gallery.findChild(QLineEdit, 'event_id')
        txt_event_id.setText(str(event_id))

        self.start_indx = 0
        self.btn_next = self.dlg_gallery.findChild(QPushButton, "btn_next")
        self.btn_next.clicked.connect(self._next_gallery)
        self.btn_previous = self.dlg_gallery.findChild(QPushButton, "btn_previous")
        self.btn_previous.clicked.connect(self._previous_gallery)
        tools_gw.add_icon(self.btn_previous, "109", sub_folder="24x24")
        tools_gw.add_icon(self.btn_next, "108", sub_folder="24x24")
        self.btn_close = self.dlg_gallery.findChild(QPushButton, "btn_close")
        self.btn_close.clicked.connect(partial(tools_gw.close_dialog, self.dlg_gallery))

        # If all images set in one page, disable button next
        if num <= 9:
            self.btn_next.setDisabled(True)

        # Open dialog
        tools_gw.open_dialog(self.dlg_gallery, dlg_name='visit_gallery')


    # region private functions


    def _next_gallery(self):

        self.start_indx = self.start_indx + 1

        # Clear previous
        for i in self.list_widget:
            i.clear()

        # Add new 9 images
        for i in range(0, 9):

            # Parse a URL into components
            url = urllib.parse.urlsplit(str(self.img_path_list[self.start_indx][i]))

            # Check if path is URL
            if url.scheme == "http" or url.scheme == "https":
                url = str(self.img_path_list[self.start_indx][i])
                data = urllib.request.urlopen(url).read()
                pixmap = QPixmap()
                pixmap.loadFromData(data)
            else:
                pixmap = QPixmap(str(self.img_path_list[self.start_indx][i]))

            pixmap = pixmap.scaled(171, 151, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
            self.list_widget[i].setPixmap(pixmap)

        # Control sliding buttons
        if self.start_indx > 0:
            self.btn_previous.setEnabled(True)
        else:
            self.btn_previous.setEnabled(False)

        control = len(self.img_path_list1D) / 9
        if self.start_indx == (control - 1):
            self.btn_next.setEnabled(False)


    def _previous_gallery(self):

        self.start_indx = self.start_indx - 1

        # First clear previous
        for i in self.list_widget:
            i.clear()

        # Add new 9 images
        for i in range(0, 9):

            # Parse a URL into components
            url = urllib.parse.urlsplit(str(self.img_path_list[self.start_indx][i]))

            # Check if path is URL
            if url.scheme == "http" or url.scheme == "https":
                url = str(self.img_path_list[self.start_indx][i])
                data = urllib.request.urlopen(url).read()
                pixmap = QPixmap()
                pixmap.loadFromData(data)
            else:
                pixmap = QPixmap(str(self.img_path_list[self.start_indx][i]))

            pixmap = pixmap.scaled(171, 151, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)
            self.list_widget[i].setPixmap(pixmap)

        # Control sliding buttons
        if self.start_indx == 0:
            self.btn_previous.setEnabled(False)

        control = len(self.img_path_list1D) / 9
        if self.start_indx < (control - 1):
            self.btn_next.setEnabled(True)


    def _zoom_img(self, i, visit_id, event_id):

        handeler_index = i

        self.dlg_gallery_zoom = GwGalleryZoomUi()
        tools_gw.load_settings(self.dlg_gallery_zoom)
        self.lbl_img = self.dlg_gallery_zoom.findChild(QLabel, "lbl_img_zoom")

        # Parse a URL into components
        url = urllib.parse.urlsplit(str(self.img_path_list[self.start_indx][i]))

        # Check if path is URL
        if url.scheme == "http" or url.scheme == "https":
            url = str(self.img_path_list[self.start_indx][i])
            data = urllib.request.urlopen(url).read()
            pixmap = QPixmap()
            pixmap.loadFromData(data)
        else:
            pixmap = QPixmap(str(self.img_path_list[self.start_indx][i]))

        self.lbl_img.setPixmap(pixmap)

        # lbl_img.show()
        zoom_visit_id = self.dlg_gallery_zoom.findChild(QLineEdit, "visit_id")
        zoom_event_id = self.dlg_gallery_zoom.findChild(QLineEdit, "event_id")

        zoom_visit_id.setText(str(visit_id))
        zoom_event_id.setText(str(event_id))

        self.btn_slidePrevious = self.dlg_gallery_zoom.findChild(QPushButton, "btn_slidePrevious")
        self.btn_slideNext = self.dlg_gallery_zoom.findChild(QPushButton, "btn_slideNext")
        tools_gw.add_icon(self.btn_slidePrevious, "109", sub_folder="24x24")
        tools_gw.add_icon(self.btn_slideNext, "108", sub_folder="24x24")

        self.dlg_gallery_zoom.rejected.connect(partial(tools_gw.close_dialog, self.dlg_gallery_zoom))

        self.i = i
        self.btn_slidePrevious.clicked.connect(self._slide_previous)
        self.btn_slideNext.clicked.connect(self._slide_next)

        # Open dialog
        tools_gw.open_dialog(self.dlg_gallery_zoom, dlg_name='visit_gallery_zoom')

        # Controling start index
        if handeler_index != i:
            self.start_indx = self.start_indx + 1


    def _slide_previous(self):

        indx = (self.start_indx * 9) + self.i - 1

        # Parse a URL into components
        url = urllib.parse.urlsplit(str(self.img_path_list1D[indx]))

        # Check if path is URL
        if url.scheme == "http" or url.scheme == "https":
            url = str(self.img_path_list1D[indx])
            data = urllib.request.urlopen(url).read()
            pixmap = QPixmap()
            pixmap.loadFromData(data)
        else:
            pixmap = QPixmap(str(self.img_path_list1D[indx]))

        self.lbl_img.setPixmap(pixmap)
        self.i = self.i - 1

        # Control sliding buttons
        if indx == 0:
            self.btn_slidePrevious.setEnabled(False)

        if indx < (self.num_events - 1):
            self.btn_slideNext.setEnabled(True)


    def _slide_next(self):

        indx = (self.start_indx * 9) + self.i + 1

        # Parse a URL into components
        url = urllib.parse.urlsplit(str(self.img_path_list1D[indx]))

        # Check if path is URL
        if url.scheme == "http" or url.scheme == "https":
            url = str(self.img_path_list1D[indx])
            data = urllib.request.urlopen(url).read()
            pixmap = QPixmap()
            pixmap.loadFromData(data)

        else:
            pixmap = QPixmap(str(self.img_path_list1D[indx]))

        self.lbl_img.setPixmap(pixmap)
        self.i = self.i + 1

        # Control sliding buttons
        if indx > 0:
            self.btn_slidePrevious.setEnabled(True)

        if indx == (self.num_events - 1):
            self.btn_slideNext.setEnabled(False)

    # endregion class GwVisit(QObject):

    # event emitted when a new Visit is added when GUI is closed/accepted
    visit_added = pyqtSignal(int)

    def __init__(self):
        """ Class to control 'Add visit' of toolbar 'edit' """

        QObject.__init__(self)
        self.canvas = global_vars.canvas
        self.schema_name = global_vars.schema_name
        self.iface = global_vars.iface
        self.feature_type = None
        self.layers = None
        self.event_parameter_id = None
        self.event_feature_type = None
        self.lazy_widget = None
        self.lazy_init_function = None
        self.snapper_manager = GwSnapManager(self.iface)
        self.vertex_marker = self.snapper_manager.vertex_marker


    def get_visit(self, visit_id=None, feature_type=None, feature_id=None, single_tool=True, expl_id=None, tag=None,
                  open_dlg=True, is_new_from_cf=False):

        """ Button 64. Add visit.
        if visit_id => load record related to the visit_id
        if feature_type => lock feature_type in relations tab
        if feature_id => load related feature basing on feature_type in relation
        if single_tool notify that the tool is used called from another dialog."""

        self.rubber_band = tools_gw.create_rubberband(self.canvas)

        # parameter to set if the dialog is working as single tool or integrated in another tool
        global_vars.single_tool_mode = single_tool

        # bool to distinguish if we entered to edit an exisiting Visit or creating a new one
        self.it_is_new_visit = (not visit_id)
        self.visit_id_value = visit_id

        # Remove previous selection
        for layer in self.canvas.layers():
            if layer.type() == layer.VectorLayer:
                layer.removeSelection()

        self.iface.mapCanvas().refresh()
        # set vars to manage if GUI have to lock the relation
        self.locked_feature_type = feature_type
        self.locked_feature_id = feature_id

        # Create the dialog and signals and related ORM Visit class
        self.current_visit = GwOmVisit()
        self.dlg_add_visit = GwVisitUi(tag)
        tools_gw.load_settings(self.dlg_add_visit)
        # Get layer visibility to restore when dialog is closed
        layers_visibility = {}
        for layer_name in ["v_edit_arc", "v_edit_node", "v_edit_connec", "v_edit_element", "v_edit_gully"]:
            layer = tools_qgis.get_layer_by_tablename(layer_name)
            if layer:
                layers_visibility[layer] = tools_qgis.is_layer_visible(layer)

        self.dlg_add_visit.rejected.connect(partial(tools_gw.restore_parent_layers_visibility, layers_visibility))
        self.dlg_add_visit.rejected.connect(tools_gw.remove_selection)
        self.dlg_add_visit.accepted.connect(partial(tools_gw.restore_parent_layers_visibility, layers_visibility))

        # Get expl_id from previus dialog
        self.expl_id = expl_id

        # Get layers of every feature_type

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

        # Setting layers
        self.layers = {}
        self.layers['gully'] = []
        self.layers['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')
        if tools_gw.get_project_type() == 'ud':
            self.layers['gully'] = tools_gw.get_layers_from_feature_type('gully')

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

        # Feature type of selected parameter
        self.feature_type_parameter = None

        # Reset geometry
        self.point_xy = {"x": None, "y": None}


        # Set icons
        tools_gw.add_icon(self.dlg_add_visit.btn_feature_insert, "111", sub_folder="24x24")
        tools_gw.add_icon(self.dlg_add_visit.btn_feature_delete, "112", sub_folder="24x24")
        tools_gw.add_icon(self.dlg_add_visit.btn_feature_snapping, "137")
        tools_gw.add_icon(self.dlg_add_visit.btn_doc_insert, "111", sub_folder="24x24")
        tools_gw.add_icon(self.dlg_add_visit.btn_doc_delete, "112", sub_folder="24x24")
        tools_gw.add_icon(self.dlg_add_visit.btn_doc_new, "134")
        tools_gw.add_icon(self.dlg_add_visit.btn_open_doc, "170")
        tools_gw.add_icon(self.dlg_add_visit.btn_add_geom, "133")

        # tab events
        self.tabs = self.dlg_add_visit.findChild(QTabWidget, 'tab_widget')
        self.button_box = self.dlg_add_visit.findChild(QDialogButtonBox, 'button_box')
        if visit_id is None:
            self.button_box.button(QDialogButtonBox.Ok).setEnabled(False)

        # Tab 'Data'/'Visit'
        self.visit_id = self.dlg_add_visit.findChild(QLineEdit, "visit_id")
        self.user_name = self.dlg_add_visit.findChild(QLineEdit, "user_name")
        self.ext_code = self.dlg_add_visit.findChild(QLineEdit, "ext_code")
        self.visitcat_id = self.dlg_add_visit.findChild(QComboBox, "visitcat_id")
        self.exploitation = self.dlg_add_visit.findChild(QComboBox, "expl_id")

        # tab 'Event'
        self.tbl_event = self.dlg_add_visit.findChild(QTableView, "tbl_event")
        self.parameter_type_id = self.dlg_add_visit.findChild(QComboBox, "parameter_type_id")
        self.parameter_id = self.dlg_add_visit.findChild(QComboBox, "parameter_id")
        self.cmb_feature_type = self.dlg_add_visit.findChild(QComboBox, "feature_type")

        # tab 'Document'
        self.doc_id = self.dlg_add_visit.findChild(QLineEdit, "doc_id")
        self.btn_doc_insert = self.dlg_add_visit.findChild(QPushButton, "btn_doc_insert")
        self.btn_doc_delete = self.dlg_add_visit.findChild(QPushButton, "btn_doc_delete")
        self.btn_doc_new = self.dlg_add_visit.findChild(QPushButton, "btn_doc_new")
        self.btn_open_doc = self.dlg_add_visit.findChild(QPushButton, "btn_open_doc")
        self.tbl_document = self.dlg_add_visit.findChild(QTableView, "tbl_document")
        self.tbl_document.setSelectionBehavior(QAbstractItemView.SelectRows)

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

        # Check the default dates, if it does not exist force today
        _date = QDate.currentDate()
        date_string = tools_gw.get_config_value('om_visit_startdate_vdefault')
        if date_string:
            _date = datetime.strptime(date_string[0], '%Y/%m/%d')
        self.dlg_add_visit.startdate.setDate(_date)

        # Check the default dates, if it does not exist force today
        _date = QDate.currentDate()
        date_string = tools_gw.get_config_value('om_visit_startdate_vdefault')
        if date_string:
            _date = datetime.strptime(date_string[0], '%Y/%m/%d')
        self.dlg_add_visit.startdate.setDate(_date)

        date_string = tools_gw.get_config_value('om_visit_enddate_vdefault')
        if date_string is not None:
            _date = datetime.strptime(date_string[0], '%Y/%m/%d')
        self.dlg_add_visit.enddate.setDate(_date)

        # set User name get from login
        if global_vars.current_user and self.user_name:
            self.user_name.setText(str(global_vars.current_user))

        # set the start tab to be shown (e.g. tab_visit)
        self.current_tab_index = self._tab_index('tab_visit')
        self.tabs.setCurrentIndex(self.current_tab_index)

        # Set signals
        self._set_signals()

        # Set autocompleters of the form
        self._set_completers()

        # Show id of visit. If not set, infer a new value
        self._fill_combos(visit_id=visit_id)
        if not visit_id:
            visit_id = self.current_visit.nextval()

        self.visit_id.setText(str(visit_id))

        if tools_gw.get_project_type() == 'ud':
            self._event_feature_type_selected(self.dlg_add_visit, "gully")
        self._event_feature_type_selected(self.dlg_add_visit, "node")
        self._event_feature_type_selected(self.dlg_add_visit, "connec")
        self._event_feature_type_selected(self.dlg_add_visit, "arc")

        # Force _visit_tab_feature_changed
        excluded_layers = ["v_edit_arc", "v_edit_node", "v_edit_connec", "v_edit_element", "v_edit_gully",
                           "v_edit_element"]
        self._visit_tab_feature_changed(self.dlg_add_visit, 'visit', excluded_layers=excluded_layers)

        # Manage relation locking
        if self.locked_feature_type:
            self._set_locked_relation()


        if self.it_is_new_visit is False:
            # Disable widgets when the visit is not new
            # self.dlg_add_visit.btn_feature_insert.setEnabled(False)
            # self.dlg_add_visit.btn_feature_delete.setEnabled(False)
            # self.dlg_add_visit.btn_feature_snapping.setEnabled(False)
            # self.dlg_add_visit.tab_feature.setEnabled(False)
            self.dlg_add_visit.tab_feature.tabBar().setEnabled(False)

            # Zoom to selected geometry or relations
            visit_layer = tools_qgis.get_layer_by_tablename('v_edit_om_visit')
            if visit_layer:
                visit_layer.selectByExpression(f'"id"={visit_id}')
                box = visit_layer.boundingBoxOfSelected()
                visit_layer.removeSelection()
                if not box.isNull():
                    self._zoom_box(box)
                else:
                    for layer in self.layers[self.feature_type]:
                        box = layer.boundingBoxOfSelected()
                        self._zoom_box(box)

        # Open the dialog
        if open_dlg:
            # If the new visit dont come from info emit signal
            if is_new_from_cf is False:
                self.cmb_feature_type.currentIndexChanged.emit(0)
            tools_gw.open_dialog(self.dlg_add_visit, dlg_name="visit")


    def manage_visits(self, feature_type=None, feature_id=None):
        """ Button 65: manage visits """

        # Create the dialog
        self.dlg_visit_manager = GwVisitManagerUi()
        tools_gw.load_settings(self.dlg_visit_manager)
        self.dlg_visit_manager.tbl_visit.setSelectionBehavior(QAbstractItemView.SelectRows)

        if feature_type is None:
            # Set a model with selected filter. Attach that model to selected table
            tools_qt.set_widget_text(self.dlg_visit_manager, self.dlg_visit_manager.lbl_filter, 'Filter by ext_code')
            filed_to_filter = "ext_code"
            table_object = "v_ui_om_visit"
            expr_filter = ""
            message = tools_qt.fill_table(self.dlg_visit_manager.tbl_visit, table_object)
            if message:
                tools_qgis.show_warning(message)
            tools_gw.set_tablemodel_config(self.dlg_visit_manager, self.dlg_visit_manager.tbl_visit, table_object)
        else:
            # Set a model with selected filter. Attach that model to selected table
            tools_qt.set_widget_text(self.dlg_visit_manager, self.dlg_visit_manager.lbl_filter, 'Filter by code')
            filed_to_filter = "code"
            table_object = "v_ui_om_visitman_x_" + str(feature_type)
            expr_filter = f"{feature_type}_id = '{feature_id}'"
            # Refresh model with selected filter
            message = tools_qt.fill_table(self.dlg_visit_manager.tbl_visit, table_object, expr_filter)
            if message:
                tools_qgis.show_warning(message)
            tools_gw.set_tablemodel_config(self.dlg_visit_manager, self.dlg_visit_manager.tbl_visit, table_object)

        # manage save and rollback when closing the dialog
        self.dlg_visit_manager.rejected.connect(partial(tools_gw.close_dialog, self.dlg_visit_manager))
        self.dlg_visit_manager.btn_close.clicked.connect(partial(tools_gw.close_dialog, self.dlg_visit_manager))

        # Set signals
        self.dlg_visit_manager.tbl_visit.doubleClicked.connect(
            partial(self._open_selected_object_visit, self.dlg_visit_manager, self.dlg_visit_manager.tbl_visit, table_object))
        self.dlg_visit_manager.btn_open.clicked.connect(
            partial(self._open_selected_object_visit, self.dlg_visit_manager, self.dlg_visit_manager.tbl_visit, table_object))
        self.dlg_visit_manager.btn_delete.clicked.connect(
            partial(tools_gw.delete_selected_rows, self.dlg_visit_manager.tbl_visit, table_object))
        self.dlg_visit_manager.txt_filter.textChanged.connect(partial(self._filter_visit, self.dlg_visit_manager,
            self.dlg_visit_manager.tbl_visit, self.dlg_visit_manager.txt_filter, table_object, expr_filter, filed_to_filter))

        self.dlg_visit_manager.txt_filter.textChanged.connect(partial(self._filter_visit, self.dlg_visit_manager,
            self.dlg_visit_manager.tbl_visit, self.dlg_visit_manager.txt_filter, table_object, expr_filter, filed_to_filter))

        # set timeStart and timeEnd as the min/max dave values get from model
        tools_gw.set_dates_from_to(self.dlg_visit_manager.date_event_from, self.dlg_visit_manager.date_event_to,
                                   'om_visit', 'startdate', 'enddate')

        # set date events
        self.dlg_visit_manager.date_event_from.dateChanged.connect(partial(self._filter_visit, self.dlg_visit_manager,
            self.dlg_visit_manager.tbl_visit, self.dlg_visit_manager.txt_filter, table_object, expr_filter, filed_to_filter))
        self.dlg_visit_manager.date_event_to.dateChanged.connect(partial(self._filter_visit, self.dlg_visit_manager,
            self.dlg_visit_manager.tbl_visit, self.dlg_visit_manager.txt_filter, table_object, expr_filter, filed_to_filter))

        # Open form
        tools_gw.open_dialog(self.dlg_visit_manager, dlg_name="lot_visitmanager")


    def get_visit_dialog(self):
        return self.dlg_add_visit


    # region private functions

    def _update_table_visit(self, widget, table_name, expr_filter=None, cmb_visitclass=None, id=None):

        """ Set a model with selected filter.
                Attach that model to selected table """
        if self.schema_name not in table_name:
            table_name = self.schema_name + "." + table_name

        # Set model
        model = QSqlTableModel()
        model.setTable(table_name)
        model.setEditStrategy(QSqlTableModel.OnManualSubmit)
        if expr_filter is not None:
            model.setFilter(expr_filter)
        model.select()

        # Check for errors
        if model.lastError().isValid():
            tools_qgis.show_warning(model.lastError().text())

            # Attach model to table view
        if widget:
            widget.setModel(model)
        else:
            tools_log.log_info("set_model_to_table: widget not found")
        current_visit_class = tools_qt.get_combo_value(self.dlg_add_visit, self.dlg_add_visit.visitclass_id, 0)
        feature_key = tools_qgis.get_primary_key()
        feature_type = 'node'
        if feature_key == 'node_id':
            feature_type = 'node'
        elif feature_key == 'connec_id':
            feature_type = 'connec'
        elif feature_key == 'arc_id':
            feature_type = 'arc'
        elif feature_key == 'gully_id':
            feature_type = 'gully'
        # Fill ComboBox cmb_visit_class
        sql = ("SELECT DISTINCT(class_id), om_visit_class.idval"
               " FROM " + self.schema_name + ".v_ui_om_visit_x_" + feature_type + ""
               " JOIN " + self.schema_name + ".om_visit_class ON om_visit_class.id = v_ui_om_visit_x_" + feature_type + ".class_id"
               " WHERE " + str(feature_key) + " IS NOT NULL AND " + str(feature_key) + " = '" + str(id) + "'")
        rows = tools_db.get_rows(sql)
        tools_qt.set_combo_value(cmb_visitclass, rows, 1)
        tools_qt.fill_combo_values(cmb_visitclass, str(current_visit_class), 0)


    def _delete_files(self, qtable, visit_id, event_id):
        """ Delete rows from table om_visit_event_photo, NOT DELETE FILES FROM DISC """

        # Get selected rows
        selected_list = qtable.selectionModel().selectedRows()
        if len(selected_list) == 0:
            message = "Any record selected"
            tools_qt.show_info_box(message)
            return

        list_values = ""

        for x in range(0, len(selected_list)):
            # Get index of row and row[index]
            row_index = selected_list[x]
            row = row_index.row()
            col_index = tools_qt.get_col_index_by_col_name(qtable, 'value')
            value = row_index.sibling(row, col_index).data()
            if value in self.files_added:
                self.files_added.remove(value)
            if value in self.files_all:
                self.files_all.remove(value)
            list_values += "'" + str(value) + "',\n"
        list_values = list_values[:-2]

        message = "Are you sure you want to delete these records?"
        title = "Delete records"
        answer = tools_qt.show_question(message, title, list_values)
        if answer:
            sql = (f"DELETE FROM om_visit_event_photo "
                   f"WHERE visit_id='{visit_id}' "
                   f"AND event_id='{event_id}' "
                   f"AND value IN ({list_values})")
            tools_db.execute_sql(sql)
            self._populate_tbl_docs_x_event(event_id)
        else:
            return


    def _zoom_box(self, box):
        """
        :param box: (QgsRectangle)
        """
        # When it is a point, and only one, it must be converted into a rectangle to be able to zoom
        if not box.isNull():
            if box.xMinimum() == box.xMaximum() and box.yMinimum() == box.yMaximum():
                box.setXMaximum(box.xMaximum() + 0.0001)
                box.setYMaximum(box.yMaximum() + 0.0001)
                box.setXMaximum(box.xMinimum() + 0.0001)
                box.setYMaximum(box.yMinimum() + 0.0001)
            box.set(box.xMinimum() - 10, box.yMinimum() - 10, box.xMaximum() + 10, box.yMaximum() + 10)
            self.iface.mapCanvas().setExtent(box)
            self.iface.mapCanvas().refresh()


    def _open_selected_object_visit(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"
        if "v_ui_om_visitman_x_" in table_object:
            field_object_id = "visit_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', 'visit_manager_keep_open', "user", "init", prefix=True)
        if tools_os.set_boolean(keep_open_form, False) is not True:
            dialog.close()

        if table_object == "v_ui_om_visit" or "v_ui_om_visitman_x_" in table_object:
            self.get_visit(visit_id=selected_object_id)

        # Disconnect open visit from table_visit once there opened
        self.dlg_visit_manager.tbl_visit.doubleClicked.disconnect()
        self.dlg_visit_manager.btn_open.clicked.disconnect()
        self.dlg_add_visit.rejected.connect(partial(self._reconnect_table_signals, table_object))
        self.dlg_add_visit.accepted.connect(partial(self._reconnect_table_signals, table_object))


    def _reconnect_table_signals(self, table_object):
        try:
            self.dlg_visit_manager.tbl_visit.doubleClicked.connect(
                partial(self._open_selected_object_visit, self.dlg_visit_manager, self.dlg_visit_manager.tbl_visit,
                        table_object))
        except RuntimeError:
            pass
        try:
            self.dlg_visit_manager.btn_open.clicked.connect(
                partial(self._open_selected_object_visit, self.dlg_visit_manager, self.dlg_visit_manager.tbl_visit,
                        table_object))
        except RuntimeError:
            pass


    def _set_signals(self):

        self.dlg_add_visit.rejected.connect(self._manage_rejected)
        self.dlg_add_visit.rejected.connect(partial(tools_gw.close_dialog, self.dlg_add_visit))
        self.dlg_add_visit.rejected.connect(lambda: tools_gw.reset_rubberband(self.rubber_band))
        self.dlg_add_visit.accepted.connect(partial(self._update_relations, self.dlg_add_visit))
        self.dlg_add_visit.accepted.connect(partial(self._manage_accepted))

        self.dlg_add_visit.btn_event_insert.clicked.connect(self._event_insert)
        self.dlg_add_visit.btn_event_delete.clicked.connect(self._event_delete)
        self.dlg_add_visit.btn_event_update.clicked.connect(self._event_update)
        self.tabs.currentChanged.connect(partial(self._manage_tab_changed, self.dlg_add_visit))
        self.visit_id.textChanged.connect(partial(self._manage_visit_id_change, self.dlg_add_visit))
        self.dlg_add_visit.btn_doc_insert.clicked.connect(self._document_insert)
        self.dlg_add_visit.btn_doc_delete.clicked.connect(partial(tools_qt.delete_rows_tableview, self.tbl_document))
        self.dlg_add_visit.btn_doc_new.clicked.connect(self._manage_document)
        self.dlg_add_visit.btn_open_doc.clicked.connect(partial(tools_qt.document_open, self.tbl_document, 'path'))
        self.tbl_document.doubleClicked.connect(partial(tools_qt.document_open, self.tbl_document, 'path'))
        self.dlg_add_visit.btn_add_geom.clicked.connect(self._add_feature_clicked)

        # Fill combo boxes of the form and related events
        self.parameter_type_id.currentIndexChanged.connect(partial(self._set_parameter_id_combo, self.dlg_add_visit))
        self.cmb_feature_type.currentIndexChanged.connect(
            partial(self._event_feature_type_selected, self.dlg_add_visit, None))
        self.cmb_feature_type.currentIndexChanged.connect(partial(self._manage_tabs_enabled, True))
        self.parameter_id.currentIndexChanged.connect(self._get_feature_type_of_parameter)
        self.dlg_add_visit.tbl_visit_x_arc.clicked.connect(partial(tools_qgis.hilight_feature_by_id,
            self.dlg_add_visit.tbl_visit_x_arc, "v_edit_arc", "arc_id", self.rubber_band, 5))
        self.dlg_add_visit.tbl_visit_x_node.clicked.connect(partial(tools_qgis.hilight_feature_by_id,
            self.dlg_add_visit.tbl_visit_x_node, "v_edit_node", "node_id", self.rubber_band, 10))
        self.dlg_add_visit.tbl_visit_x_connec.clicked.connect(partial(tools_qgis.hilight_feature_by_id,
            self.dlg_add_visit.tbl_visit_x_connec, "v_edit_connec", "connec_id", self.rubber_band, 10))
        self.dlg_add_visit.tbl_visit_x_gully.clicked.connect(partial(tools_qgis.hilight_feature_by_id,
            self.dlg_add_visit.tbl_visit_x_gully, "v_edit_gully", "gully_id", self.rubber_band, 10))


    def _add_feature_clicked(self):

        self.previous_map_tool = global_vars.canvas.mapTool()
        self.snapper_manager.add_point(self.vertex_marker)
        self.point_xy = self.snapper_manager.point_xy


    def _set_locked_relation(self):
        """ Set feature_type and listed feature_id in @table_name to lock it """

        # Enable tab
        index = self._tab_index('tab_relations')
        self.tabs.setTabEnabled(index, True)

        # set geometry_type
        feature_type_index = self.cmb_feature_type.findText(self.locked_feature_type.upper())
        if feature_type_index < 0:
            return

        # set default combo box value = trigger model and selection of related features
        if self.cmb_feature_type.currentIndex() != feature_type_index:
            self.cmb_feature_type.setCurrentIndex(feature_type_index)
        else:
            self.cmb_feature_type.currentIndexChanged.emit(feature_type_index)

        # Load feature if in @table_name. Select list of related features
        # Set 'expr_filter' with features that are in the list
        if self.locked_feature_id:
            expr_filter = f'"{self.feature_type}_id" IN (\'{self.locked_feature_id}\')'
            (is_valid, expr) = tools_qt.check_expression_filter(expr_filter)
            if not is_valid:
                return

            # do selection allowing @table_name to be linked to canvas selectionChanged
            widget_name = f'tbl_visit_x_{self.feature_type}'
            widget_table = tools_qt.get_widget(self.dlg_add_visit, widget_name)
            tools_qgis.disconnect_signal_selection_changed()
            tools_gw.disconnect_signal('visit')
            tools_gw.connect_signal(global_vars.canvas.selectionChanged,
                                    partial(tools_gw.selection_changed, self, self.dlg_add_visit, widget_table, False),
                                    'visit', 'set_locked_relation_canvas_selectionChanged')
            tools_qgis.select_features_by_ids(self.feature_type, expr, self.layers)
            tools_qgis.disconnect_signal_selection_changed()
            tools_gw.disconnect_signal('visit')


    def _manage_accepted(self):
        """ Do all action when closed the dialog with Ok.
        e.g. all necessary commits and cleanings.
        A) Trigger SELECT gw_fct_om_visit_multiplier (visit_id, feature_type)
        for multiple visits management. """

        # tab Visit
        if self.current_tab_index == self._tab_index('tab_visit'):
            self._manage_leave_visit_tab()

        expl_value = tools_qt.get_combo_value(self.dlg_add_visit, self.exploitation)
        if expl_value not in (None, 'None', ''):
            tools_gw.set_config_parser('btn_add_visit', 'expl_id', expl_value)
        # Remove all previous selections
        tools_qgis.disconnect_signal_selection_changed()
        tools_gw.disconnect_signal('visit')
        self.layers = tools_gw.remove_selection(layers=self.layers)

        # Update geometry field (if user have selected a point)
        if self.point_xy['x'] is not None:
            self._update_geom()

        layer = tools_qgis.get_layer_by_tablename('v_edit_om_visit')
        if layer:
            layer.dataProvider().reloadData()

        # If new visit, execute PG function
        if self.it_is_new_visit:
            self._execute_pgfunction()

        # notify that a new visit has been added
        self.visit_added.emit(self.current_visit.id)

        layer = tools_qgis.get_layer_by_tablename('v_edit_om_visit')
        if layer:
            layer.dataProvider().reloadData()
        tools_qgis.refresh_map_canvas()


    def _execute_pgfunction(self):
        """ Execute function 'gw_fct_om_visit_multiplier' """

        feature = f'"id":"{self.current_visit.id}"'
        body = tools_gw.create_body(feature=feature)
        complet_result = tools_gw.execute_procedure('gw_fct_om_visit_multiplier', body)
        if complet_result is None:
            return
        tools_log.log_info(f"execute_pgfunction: {complet_result}")


    def _update_geom(self):
        """ Update geometry field """

        srid = global_vars.data_epsg
        sql = (f"UPDATE om_visit"
               f" SET the_geom = ST_SetSRID(ST_MakePoint({self.point_xy['x']},{self.point_xy['y']}), {srid})"
               f" WHERE id = {self.current_visit.id}")
        tools_db.execute_sql(sql)


    def _manage_rejected(self):
        """ Do all action when closed the dialog with Cancel or X.
        e.g. all necessary rollbacks and cleanings. """

        try:
            self.canvas.setMapTool(self.previous_map_tool)
            # removed current working visit. This should cascade removing of all related records
            if hasattr(self, 'it_is_new_visit') and self.it_is_new_visit:
                self.current_visit.delete()

            # Remove all previous selections
            tools_qgis.disconnect_signal_selection_changed()
            tools_gw.disconnect_signal('visit')
            self.layers = tools_gw.remove_selection(layers=self.layers)
        except Exception as e:
            tools_log.log_info(f"manage_rejected: {e}")


    def _tab_index(self, tab_name):
        """ Get the index of a tab basing on objectName. """

        for idx in range(self.tabs.count()):
            if self.tabs.widget(idx).objectName() == tab_name:
                return idx
        return -1


    def _manage_visit_id_change(self, dialog, text):
        """ Manage action when the visit id is changed.
        A) Update current Visit record
        B) Fill the GUI values of the current visit
        C) load all related events in the relative table
        D) load all related documents in the relative table. """

        self.event_parameter_id = None
        self.event_feature_type = None
        tools_qt.set_widget_enabled(self.dlg_add_visit, 'parameter_id', True)

        # A) Update current Visit record
        self.current_visit.id = int(text)
        exist = self.current_visit.fetch()
        if exist:
            # B) Fill the GUI values of the current visit
            self._fill_widget_with_fields(self.dlg_add_visit, self.current_visit, self.current_visit.field_names())
            # Get parameter_id and feature_type from his event
            self.event_parameter_id, self.event_feature_type = self._get_data_from_event(self.visit_id_value)
            tools_qt.set_combo_value(self.dlg_add_visit.parameter_id, self.event_parameter_id, 0)

        # C) load all related events in the relative table
        self.filter = f"visit_id = '{text}'"
        table_name = "v_ui_om_event"
        message = tools_qt.fill_table(self.tbl_event, table_name, self.filter)
        if message:
            tools_qgis.show_warning(message)
        tools_gw.set_tablemodel_config(dialog, self.tbl_event, table_name)
        self._manage_events_changed()

        # D) load all related documents in the relative table
        table_name = "v_ui_doc_x_visit"
        message = tools_qt.fill_table(self.tbl_document, table_name, self.filter)
        if message:
            tools_qgis.show_warning(message)
        tools_gw.set_tablemodel_config(dialog, self.tbl_document, table_name)

        # E) load all related Relations in the relative table
        self._set_feature_type_by_visit_id()


    def _set_feature_type_by_visit_id(self):
        """ Set the feature_type in Relation tab basing on visit_id.
        The steps to follow are:
        1) check geometry type looking what table contain records related with visit_id
        2) set gemetry type. """

        selected_feature_type = None
        feature_type_index = None
        for index in range(self.cmb_feature_type.count()):
            # feture_type combobox is filled before the visit_id is changed
            # it will contain all the geometry type allows basing on project type
            feature_type = self.cmb_feature_type.itemText(index).lower()
            if feature_type != '' and feature_type != 'all':
                table_name = f'om_visit_x_{feature_type}'
                sql = f"SELECT id FROM {table_name} WHERE visit_id = '{self.current_visit.id}'"
                rows = tools_db.get_rows(sql, log_info=False)
                if not rows or not rows[0]:
                    continue

                selected_feature_type = feature_type
                feature_type_index = index
                break

        # if no related records found do nothing
        if not selected_feature_type:
            return

        # set default combo box value = trigger model and selection
        # of related features
        if self.cmb_feature_type.currentIndex() != feature_type_index:
            self.cmb_feature_type.setCurrentIndex(feature_type_index)
        else:
            self.cmb_feature_type.currentIndexChanged.emit(feature_type_index)


    def _manage_leave_visit_tab(self):
        """ Manage all the action when leaving the tab_visit
        A) Manage sync between GUI values and Visit record in DB. """

        # A) fill Visit basing on GUI values
        self.current_visit.id = int(self.visit_id.text())
        self.current_visit.startdate = tools_qt.get_calendar_date(self.dlg_add_visit, 'startdate')
        self.current_visit.enddate = tools_qt.get_calendar_date(self.dlg_add_visit, 'enddate')
        self.current_visit.user_name = self.user_name.text()
        self.current_visit.ext_code = self.ext_code.text()
        self.current_visit.visitcat_id = tools_qt.get_combo_value(self.dlg_add_visit, 'visitcat_id', 0)
        self.current_visit.descript = tools_qt.get_text(self.dlg_add_visit, 'descript', False, False)
        self.current_visit.status = tools_qt.get_combo_value(self.dlg_add_visit, 'status', 0)
        if self.expl_id is None:
            self.expl_id = tools_qt.get_combo_value(self.dlg_add_visit, self.exploitation)
        if self.expl_id:
            self.current_visit.expl_id = self.expl_id

        # update or insert but without closing the transaction
        self.current_visit.upsert()


    def _update_relations(self, dialog, delete_old_relations=True):
        """ Save current selected features in every table of feature_type """

        if delete_old_relations:
            for index in range(self.cmb_feature_type.count()):
                # Remove all old relations related with current visit_id and @feature_type
                feature_type = self.cmb_feature_type.itemText(index).lower()
                self._delete_relations_feature_type(feature_type)

        feature_type = tools_qt.get_text(self.dlg_add_visit, self.cmb_feature_type).lower()
        # Save new relations listed in every table of feature_type
        if feature_type == 'all':
            self._update_relations_feature_type("arc")
            self._update_relations_feature_type("node")
            self._update_relations_feature_type("connec")
            if tools_gw.get_project_type() == 'ud':
                self._update_relations_feature_type("gully")
        else:
            self._update_relations_feature_type(self.feature_type)

        widget_name = f"tbl_visit_x_{self.feature_type}"
        tools_gw.enable_feature_type(dialog, widget_name, ids=self.ids)


    def _delete_relations_feature_type(self, feature_type):
        """ Remove all old relations related with current visit_id and @feature_type """

        if feature_type == '' or feature_type.lower() == 'all':
            return

        db_record = None
        if feature_type == 'arc':
            db_record = GwOmVisitXArc()
        elif feature_type == 'node':
            db_record = GwOmVisitXNode()
        elif feature_type == 'connec':
            db_record = GwOmVisitXConnec()
        elif feature_type == 'gully':
            db_record = GwOmVisitXGully()

        # remove all actual saved records related with visit_id
        where_clause = f"visit_id = '{self.visit_id.text()}'"

        if db_record:
            db_record.delete(where_clause=where_clause)


    def _update_relations_feature_type(self, feature_type):
        """ Update relations of specific @feature_type """

        if feature_type == '' or feature_type.lower() == 'all':
            return

        # for each showed element of a specific feature_type create an db entry
        column_name = f"{feature_type}_id"
        widget = tools_qt.get_widget(self.dlg_add_visit, f"tbl_visit_x_{feature_type}")
        if not widget:
            message = "Widget not found"
            tools_log.log_info(message, parameter=f"tbl_visit_x_{feature_type}")
            return None

        # do nothing if model is None or no element is present
        if not widget.model():  # or not widget.rowCount():
            tools_log.log_info(f"Widget model is none: tbl_visit_x_{feature_type}")
            return

        db_record = None
        if feature_type == 'arc':
            db_record = GwOmVisitXArc()
        elif feature_type == 'node':
            db_record = GwOmVisitXNode()
        elif feature_type == 'connec':
            db_record = GwOmVisitXConnec()
        elif feature_type == 'gully':
            db_record = GwOmVisitXGully()

        if db_record:
            for row in range(widget.model().rowCount()):
                # get modelIndex to get data
                index = widget.model().index(row, 0)

                # set common fields
                db_record.id = db_record.max_pk() + 1
                db_record.visit_id = int(self.visit_id.text())

                # set value for column <feature_type>_id
                # db_record.column_name = index.data()
                setattr(db_record, column_name, index.data())

                # than save the showed records
                db_record.upsert()


    def _manage_tab_changed(self, dialog, index):
        """ Do actions when tab is exit and entered. Actions depend on tab index """

        # manage leaving tab
        # tab Visit
        if self.current_tab_index == self._tab_index('tab_visit'):
            self._manage_leave_visit_tab()
            # need to create the relation record that is done only
            # changing tab
            if self.locked_feature_type:
                self._update_relations(dialog)

        # manage arriving tab
        self.current_tab_index = index

        # Set user devault parameter
        parameter_id = tools_gw.get_config_value('om_visit_parameter_vdefault')
        if parameter_id:
            tools_qt.set_combo_value(self.dlg_add_visit.parameter_id, parameter_id[0], 0)


    def _set_parameter_id_combo(self, dialog):
        """ Set parameter_id combo basing on current selections """

        dialog.parameter_id.clear()
        sql = (f"SELECT id, descript "
               f"FROM config_visit_parameter "
               f"WHERE UPPER(parameter_type) = '{self.parameter_type_id.currentText().upper()}' ")
        if self.cmb_feature_type.currentText() != '':
            sql += f"AND UPPER(feature_type) = '{self.cmb_feature_type.currentText().upper()}' "
        sql += f"ORDER BY id"
        rows = tools_db.get_rows(sql)
        if rows:
            tools_qt.fill_combo_values(dialog.parameter_id, rows, 1)

        # Set user devault parameter
        parameter_id = tools_gw.get_config_value('om_visit_parameter_vdefault')
        if parameter_id:
            tools_qt.set_combo_value(self.dlg_add_visit.parameter_id, parameter_id[0], 0)


    def _get_feature_type_of_parameter(self):
        """ Get feature type of selected parameter """

        sql = (f"SELECT feature_type "
               f"FROM config_visit_parameter "
               f"WHERE descript = '{self.parameter_id.currentText()}'")
        row = tools_db.get_row(sql)
        if row:
            self.feature_type_parameter = row[0]
            if self.it_is_new_visit:
                self.feature_type = self.feature_type_parameter.lower()
            self._manage_tabs_enabled(True)


    def _connect_signal_tab_feature_signal(self, connect=True, excluded_layers=[]):

        try:
            if connect:
                self.dlg_add_visit.tab_feature.currentChanged.connect(partial(
                    self._visit_tab_feature_changed, self.dlg_add_visit, 'visit', excluded_layers))
            else:
                self.dlg_add_visit.tab_feature.currentChanged.disconnect()
        except Exception as e:
            tools_log.log_info(f"connect_signal_tab_feature_signal error: {e}")


    def _manage_tabs_enabled(self, enable_tabs=False):
        """ Enable/Disable tabs depending feature_type """

        excluded_layers = ["v_edit_arc", "v_edit_node", "v_edit_connec", "v_edit_element", "v_edit_gully",
                          "v_edit_element"]
        if self.feature_type is None:
            return

        self._connect_signal_tab_feature_signal(False, excluded_layers)

        # If feature_type = 'all': enable all tabs
        if self.feature_type == 'all':
            for i in range(self.dlg_add_visit.tab_feature.count()):
                self.dlg_add_visit.tab_feature.setTabEnabled(i, True)
            self._connect_signal_tab_feature_signal(True, excluded_layers)
            return

        # Disable all tabs
        if enable_tabs:
            for i in range(self.dlg_add_visit.tab_feature.count()):
                self.dlg_add_visit.tab_feature.setTabEnabled(i, False)

        self._manage_feature_type_selected()


    def _manage_feature_type_selected(self):

        tab_index = 0
        if self.feature_type == 'arc':
            tab_index = 0
        elif self.feature_type == 'node':
            tab_index = 1
        elif self.feature_type == 'connec':
            tab_index = 2
        elif self.feature_type == 'gully':
            tab_index = 3

        # Enable only tab of this geometry type
        self.dlg_add_visit.tab_feature.setTabEnabled(tab_index, True)
        self.dlg_add_visit.tab_feature.setCurrentIndex(tab_index)

        self._connect_signal_tab_feature_signal(True)

        # tools_gw.hide_parent_layers(excluded_layers=excluded_layers)
        widget_name = f"tbl_visit_x_{self.feature_type}"
        viewname = f"v_edit_{self.feature_type}"
        widget_table = tools_qt.get_widget(self.dlg_add_visit, widget_name)

        try:
            self.dlg_add_visit.btn_feature_insert.clicked.disconnect()
            self.dlg_add_visit.btn_feature_delete.clicked.disconnect()
            self.dlg_add_visit.btn_feature_snapping.clicked.disconnect()
        except Exception as e:
            tools_log.log_info(f"manage_feature_type_selected exception: {e}")
        finally:
            self.dlg_add_visit.btn_feature_insert.clicked.connect(partial(tools_gw.insert_feature, self,
                 self.dlg_add_visit, widget_table, False, False, None, None))
            self.dlg_add_visit.btn_feature_delete.clicked.connect(partial(tools_gw.delete_records, self,
                 self.dlg_add_visit, widget_table, False, self.lazy_widget, self.lazy_init_function))
            self.dlg_add_visit.btn_feature_snapping.clicked.connect(
                partial(self._feature_snapping_clicked, self.dlg_add_visit, 'visit'))

        # Adding auto-completion to a QLineEdit
        tools_gw.set_completer_widget(viewname, self.dlg_add_visit.feature_id, str(self.feature_type) + "_id")


    def _config_relation_table(self, dialog):
        """ Set all actions related to the table, model and selectionModel.
        It's necessary a centralised call because base class can create a None model
        where all callbacks are lost ance can't be registered. """

        if self.feature_type == '':
            return

        # configure model visibility
        tools_gw.set_tablemodel_config(dialog, "tbl_visit_x_arc", "v_edit_arc")
        tools_gw.set_tablemodel_config(dialog, "tbl_visit_x_node", "v_edit_node")
        tools_gw.set_tablemodel_config(dialog, "tbl_visit_x_connec", "v_edit_connec")
        tools_gw.set_tablemodel_config(dialog, "tbl_visit_x_gully", "v_edit_gully")


    def _event_feature_type_selected(self, dialog, feature_type=None):
        """ Manage selection change in feature_type combo box.
        THis means that have to set completer for feature_id QTextLine and
        setup model for features to select table """
        # 1) set the model linked to selecte features
        # 2) check if there are features related to the current visit
        # 3) if so, select them => would appear in the table associated to the model
        if feature_type is None:
            feature_type = self.cmb_feature_type.currentText().lower()

        self.feature_type = feature_type
        if feature_type == '':
            return

        # Fill combo parameter_id depending feature_type
        self._fill_combo_parameter_id()

        if self.event_parameter_id:
            tools_qt.set_combo_value(self.dlg_add_visit.parameter_id, self.event_parameter_id, 0)

        if feature_type.lower() == 'all':
            return

        viewname = f"v_edit_{feature_type}"
        tools_gw.set_completer_widget(viewname, dialog.feature_id, str(feature_type) + "_id")

        # set table model and completer
        widget_name = f'tbl_visit_x_{feature_type}'
        self._get_features_visit_feature_type(self.current_visit.id, feature_type)

        # set the callback to setup all events later
        # its not possible to setup listener in this moment beacouse set_table_model without
        # a valid expression parameter return a None model => no events can be triggered
        widget_table = tools_qt.get_widget(dialog, widget_name)
        self.lazy_widget, self.lazy_init_function = self._lazy_configuration(widget_table, self._config_relation_table)

        # check if there are features related to the current visit
        if not self.visit_id.text():
            return

        self._get_features_visit_feature_type(self.visit_id.text(), feature_type, widget_table)


    def _lazy_configuration(self, widget, init_function):
        """ set the init_function where all necessary events are set.
        This is necessary to allow a lazy setup of the events because set_table_events
        can create a table with a None model loosing any event connection. """

        lazy_widget = widget
        lazy_init_function = init_function

        return lazy_widget, lazy_init_function


    def _get_features_visit_feature_type(self, visit_id, feature_type, widget_table=None):
        """ Get features from table om_visit_x@feature_type of selected @visit_id
        Select them in canvas and automatically load them into @widget_table """

        table_name = f'om_visit_x_{feature_type}'
        sql = f"SELECT {feature_type}_id FROM {table_name} WHERE visit_id = '{visit_id}'"
        rows = tools_db.get_rows(sql, log_info=False)
        if not rows or not rows[0]:
            return

        ids = [f"'{x[0]}'" for x in rows]

        # Select list of related features
        # Set 'expr_filter' with features that are in the list
        expr_filter = f"{feature_type}_id IN ({','.join(ids)})"
        (is_valid, expr) = tools_qt.check_expression_filter(expr_filter)  # @UnusedVariable
        if not is_valid:
            return

        if widget_table is None:
            widget_name = f'tbl_visit_x_{feature_type}'
            widget_table = tools_qt.get_widget(self.dlg_add_visit, widget_name)

        # Do selection allowing @widget_table to be linked to canvas selectionChanged
        tools_qgis.disconnect_signal_selection_changed()
        tools_gw.disconnect_signal('visit')
        tools_gw.connect_signal(global_vars.canvas.selectionChanged,
                                partial(tools_gw.selection_changed, self, self.dlg_add_visit, widget_table, False),
                                'visit', 'get_features_visit_feature_type_canvas_selectionChanged')
        tools_qgis.select_features_by_ids(feature_type, expr, self.layers)
        tools_qgis.disconnect_signal_selection_changed()
        tools_gw.disconnect_signal('visit')


    def _filter_visit(self, dialog, widget_table, widget_txt, table_object, expr_filter, filed_to_filter):
        """ Filter om_visit in self.dlg_visit_manager.tbl_visit based on (id AND text AND between dates) """

        object_id = tools_qt.get_text(dialog, widget_txt)
        visit_start = dialog.date_event_from.date()
        visit_end = dialog.date_event_to.date()
        if visit_start > visit_end:
            message = "Selected date interval is not valid"
            tools_qgis.show_warning(message)
            return

        # Create interval dates
        format_low = 'yyyy-MM-dd 00:00:00.000'
        format_high = 'yyyy-MM-dd 23:59:59.999'
        interval = f"'{visit_start.toString(format_low)}'::timestamp AND '{visit_end.toString(format_high)}'::timestamp"

        if table_object == "v_ui_om_visit":
            expr_filter += f"(startdate BETWEEN {interval}) AND (enddate BETWEEN {interval})"
            if object_id != 'null':
                expr_filter += f" AND {filed_to_filter}::TEXT ILIKE '%{object_id}%'"
        else:
            expr_filter += f"AND (visit_start BETWEEN {interval}) AND (visit_end BETWEEN {interval})"
            if object_id != 'null':
                expr_filter += f" AND {filed_to_filter}::TEXT ILIKE '%{object_id}%'"

        # Refresh model with selected filter
        widget_table.model().setFilter(expr_filter)
        widget_table.model().select()


    def _fill_combos(self, visit_id=None):
        """ Fill combo boxes of the form """

        # Visit tab
        # Fill ComboBox visitcat_id
        # save result in self.visitcat_ids to get id depending on selected combo
        sql = ("SELECT id, name"
               " FROM om_visit_cat"
               " WHERE active is true"
               " ORDER BY name")
        self.visitcat_ids = tools_db.get_rows(sql)

        if self.visitcat_ids:
            tools_qt.fill_combo_values(self.dlg_add_visit.visitcat_id, self.visitcat_ids, 1)
            # now get default value to be show in visitcat_id
            row = tools_gw.get_config_value('om_visit_cat_vdefault')
            if row:
                # if int then look for default row ans set it
                try:
                    tools_qt.set_combo_value(self.dlg_add_visit.visitcat_id, row[0], 0)
                    for i in range(0, self.dlg_add_visit.visitcat_id.count()):
                        elem = self.dlg_add_visit.visitcat_id.itemData(i)
                        if str(row[0]) == str(elem[0]):
                            tools_qt.set_widget_text(self.dlg_add_visit.visitcat_id, (elem[1]))
                except TypeError:
                    pass
                except ValueError:
                    pass
            elif visit_id is not None:
                sql = (f"SELECT visitcat_id"
                       f" FROM om_visit"
                       f" WHERE id = '{visit_id}' ")
                id_visitcat = tools_db.get_row(sql)
                sql = (f"SELECT id, name"
                       f" FROM om_visit_cat"
                       f" WHERE active is true AND id = '{id_visitcat[0]}' "
                       f" ORDER BY name")
                row = tools_db.get_row(sql)
                tools_qt.set_combo_value(self.dlg_add_visit.visitcat_id, str(row[1]), 1)

        # Fill ComboBox status
        rows = self._get_values_from_catalog('om_typevalue', 'visit_status')
        if rows:
            tools_qt.fill_combo_values(self.dlg_add_visit.status, rows, 1, sort_combo=True)
            status = tools_gw.get_config_value('om_visit_status_vdefault')
            if status:
                tools_qt.set_combo_value(self.dlg_add_visit.status, str(status[0]), 0)

            if visit_id is not None:
                sql = (f"SELECT status "
                       f"FROM om_visit "
                       f"WHERE id = '{visit_id}'")
                status = tools_db.get_row(sql)
                tools_qt.set_combo_value(self.dlg_add_visit.status, str(status[0]), 0)

        # Fill ComboBox exploitation
        sql = "SELECT exploitation.expl_id, name FROM selector_expl JOIN exploitation USING (expl_id) " \
              "WHERE cur_user=current_user"
        rows = tools_db.get_rows(sql)
        if rows:
            tools_qt.fill_combo_values(self.exploitation, rows, 1, sort_by=0)
            if visit_id is None:
                if self.expl_id is not None:
                    tools_qt.set_combo_value(self.exploitation, self.expl_id, 0, add_new=False)
                else:
                    default_expl = tools_gw.get_config_parser('btn_add_visit', 'expl_id', "user", "session")
                    if default_expl not in (None, 'None', ''):
                        tools_qt.set_combo_value(self.exploitation, default_expl, 0, add_new=False)
            else:
                sql = (f"SELECT expl_id "
                       f"FROM om_visit "
                       f"WHERE id = '{visit_id}'")
                row = tools_db.get_row(sql)
                tools_qt.set_combo_value(self.exploitation, row[0], 0)

        # Relations tab
        # fill feature_type
        sql = ("SELECT 'ALL' as id "
               "UNION SELECT id "
               "FROM sys_feature_type "
               "WHERE classlevel = 1 OR classlevel = 2"
               "ORDER BY id")
        rows = tools_db.get_rows(sql)
        tools_qt.fill_combo_box(self.dlg_add_visit, "feature_type", rows, False)

        # Event tab
        # Fill ComboBox parameter_type_id
        sql = "SELECT id, idval FROM om_typevalue WHERE typevalue = 'visit_param_type' ORDER by idval"
        parameter_type_ids = tools_db.get_rows(sql)
        tools_qt.fill_combo_values(self.dlg_add_visit.parameter_type_id, parameter_type_ids, 1)

        # now get default value to be show in parameter_type_id
        row = tools_gw.get_config_value('om_param_type_vdefault', log_info=False)
        if row:
            tools_qt.set_combo_value(self.dlg_add_visit.parameter_type_id, row[0], 0)


    def _fill_combo_parameter_id(self):
        """ Fill combo parameter_id depending feature_type """

        sql = (f"SELECT id, descript "
               f"FROM config_visit_parameter ")
        where = None
        parameter_type_id = tools_qt.get_text(self.dlg_add_visit, "parameter_type_id")
        if parameter_type_id:
            where = f"WHERE parameter_type = '{parameter_type_id}' "
        if self.feature_type:
            if where is None:
                where = f"WHERE UPPER(feature_type) IN ('{self.feature_type.upper()}', 'ALL') "
            else:
                where += f"AND UPPER(feature_type) IN ('{self.feature_type.upper()}', 'ALL') "

        sql += where
        sql += f"ORDER BY id"
        rows = tools_db.get_rows(sql)
        tools_qt.fill_combo_values(self.dlg_add_visit.parameter_id, rows, 1)


    def _set_completers(self):
        """ Set autocompleters of the form """

        # Adding auto-completion to a QLineEdit - visit_id
        self.completer = QCompleter()
        self.dlg_add_visit.visit_id.setCompleter(self.completer)
        model = QStringListModel()

        sql = "SELECT DISTINCT(id) FROM om_visit"
        rows = tools_db.get_rows(sql)
        values = []
        if rows:
            for row in rows:
                values.append(str(row[0]))

        model.setStringList(values)
        self.completer.setModel(model)

        # Adding auto-completion to a QLineEdit - document_id
        self.completer = QCompleter()
        self.dlg_add_visit.doc_id.setCompleter(self.completer)
        model = QStringListModel()

        sql = "SELECT DISTINCT(id) FROM v_ui_document"
        rows = tools_db.get_rows(sql)
        values = []
        if rows:
            for row in rows:
                values.append(str(row[0]))

        model.setStringList(values)
        self.completer.setModel(model)


    def _manage_document(self):
        """ Access GUI to manage documents e.g Execute action of button 34 """

        visit_id = tools_qt.get_text(self.dlg_add_visit, self.dlg_add_visit.visit_id)
        manage_document = GwDocument(single_tool=False)
        dlg_docman = manage_document.get_document(
            tablename='visit', qtable=self.dlg_add_visit.tbl_document, item_id=visit_id)
        tools_qt.remove_tab(dlg_docman.tabWidget, 'tab_rel')
        dlg_docman.btn_accept.clicked.connect(partial(tools_gw.set_completer_object, dlg_docman, 'doc'))


    def _event_insert(self):
        """ Add and event basing on form associated to the selected parameter_id. """

        # Parameter to save all selected files associated to events
        self.files_added = []
        self.files_all = []

        # check a parameter_id is selected (can be that no value is available)
        parameter_id = tools_qt.get_combo_value(self.dlg_add_visit, self.dlg_add_visit.parameter_id, 0)
        parameter_text = tools_qt.get_combo_value(self.dlg_add_visit, self.dlg_add_visit.parameter_id, 1)

        if not parameter_id or parameter_id == -1:
            message = "You need to select a valid parameter id"
            tools_qt.show_info_box(message)
            return

        # get form associated
        sql = (f"SELECT form_type"
               f" FROM config_visit_parameter"
               f" WHERE id = '{parameter_id}'")
        row = tools_db.get_row(sql)
        form_type = str(row[0])
        if form_type in ('event_ud_arc_standard', 'event_standard'):
            self.dlg_event = GwVisitEventUi()
            tools_gw.load_settings(self.dlg_event)
            self._populate_position_id()
            dlg_name = 'visit_event'
        elif form_type == 'event_ud_arc_rehabit':
            self.dlg_event = GwVisitEventRehabUi()
            tools_gw.load_settings(self.dlg_event)
            self._populate_position_id()
            dlg_name = 'visit_event_rehab'
            self.dlg_event.position_id.setEnabled(True)
            self.dlg_event.position_value.setEnabled(True)
        else:
            message = "Unrecognised form type"
            tools_qt.show_info_box(message, parameter=form_type)
            return

        # form_type event_ud_arc_rehabit dont have widget value
        if form_type != 'event_ud_arc_rehabit':
            val = tools_gw.get_config_value('om_visit_paramvalue_vdefault')
            if val:
                tools_qt.set_widget_text(self.dlg_event, self.dlg_event.value, val[0])

        # Manage QTableView docx_x_event
        tools_qt.set_tableview_config(self.dlg_event.tbl_docs_x_event)
        self.dlg_event.tbl_docs_x_event.doubleClicked.connect(self._open_file)
        self._populate_tbl_docs_x_event()

        # set fixed values
        self.dlg_event.parameter_id.setText(parameter_text)

        # create an empty Event
        event = GwOmVisitEvent()
        event.id = event.max_pk() + 1
        event.parameter_id = parameter_id
        event.visit_id = int(self.visit_id.text())

        self.dlg_event.btn_add_file.clicked.connect(partial(self._get_added_files, event.visit_id, event.id, save=False))
        self.dlg_event.btn_delete_file.clicked.connect(
            partial(self._delete_files, self.dlg_event.tbl_docs_x_event, event.visit_id, event.id))

        self.dlg_event.setWindowFlags(Qt.WindowStaysOnTopHint)
        tools_qt.manage_translation(dlg_name, self.dlg_event)
        ret = self.dlg_event.exec_()

        # check return
        if not ret:
            # clicked cancel
            return

        for field_name in event.field_names():
            value = None
            if not hasattr(self.dlg_event, field_name):
                continue
            if type(getattr(self.dlg_event, field_name)) is QLineEdit:
                if field_name == 'parameter_id':
                    value = parameter_id
                else:
                    value = getattr(self.dlg_event, field_name).text()
            if type(getattr(self.dlg_event, field_name)) is QTextEdit:
                value = getattr(self.dlg_event, field_name).toPlainText()
            if type(getattr(self.dlg_event, field_name)) is QComboBox:
                value = tools_qt.get_combo_value(self.dlg_event, getattr(self.dlg_event, field_name), index=0)

            if value:
                setattr(event, field_name, value)

        # save new event
        event.upsert()
        self._save_files_added(event.visit_id, event.id)

        # update Table
        self.tbl_event.model().select()
        self._manage_events_changed()


    def _open_file(self):

        # Get row index
        index = self.dlg_event.tbl_docs_x_event.selectionModel().selectedRows()[0]
        column_index = tools_qt.get_col_index_by_col_name(self.dlg_event.tbl_docs_x_event, 'value')
        path = index.sibling(index.row(), column_index).data()
        status, message = tools_os.open_file(path)
        if status is False and message is not None:
            tools_qgis.show_warning(message, parameter=path)


    def _populate_tbl_docs_x_event(self, event_id=0):

        # Create and set model
        model = QStandardItemModel()
        self.dlg_event.tbl_docs_x_event.setModel(model)
        self.dlg_event.tbl_docs_x_event.horizontalHeader().setStretchLastSection(True)
        self.dlg_event.tbl_docs_x_event.horizontalHeader().setSectionResizeMode(3)

        # Get columns name and set headers of model with that
        columns_name = tools_db.get_columns_list('om_visit_event_photo')
        headers = []
        for x in columns_name:
            headers.append(x[0])
        headers = ['value', 'filetype', 'fextension']
        model.setHorizontalHeaderLabels(headers)

        # Get values in order to populate model
        visit_id = tools_qt.get_text(self.dlg_add_visit, self.dlg_add_visit.visit_id)
        sql = (f"SELECT value, filetype, fextension FROM om_visit_event_photo "
               f"WHERE visit_id='{visit_id}' AND event_id='{event_id}'")
        rows = tools_db.get_rows(sql)
        if rows is None:
            return

        for row in rows:
            item = []
            if row[0] not in self.files_all:
                self.files_all.append(str(row[0]))
            for _file in row:
                if _file is not None:
                    if type(_file) != str:
                        item.append(QStandardItem(str(_file)))
                    else:
                        item.append(QStandardItem(_file))
                else:
                    item.append(QStandardItem(None))

            if len(row) > 0:
                model.appendRow(item)


    def _get_added_files(self, visit_id, event_id, save):
        """ Get path of new files """

        file_dialog = QFileDialog()
        file_dialog.setFileMode(QFileDialog.Directory)
        # 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)
            self._populate_position_id()
            dlg_name = 'visit_event'
            # set fixed values
            if event_code not in ('NULL', None):
                tools_qt.set_widget_text(self.dlg_event, self.dlg_event.event_code, event_code)
            if _value not in ('NULL', None):
                tools_qt.set_widget_text(self.dlg_event, self.dlg_event.value, _value)
            if position_value not in ('NULL', None):
                tools_qt.set_widget_text(self.dlg_event, self.dlg_event.position_value, position_value)
            if text not in ('NULL', None):
                tools_qt.set_widget_text(self.dlg_event, self.dlg_event.text, text)
            self.dlg_event.position_id.setEnabled(False)
            self.dlg_event.position_value.setEnabled(False)

        elif om_event_parameter.form_type == 'event_ud_arc_rehabit':

            position_value = self.dlg_add_visit.tbl_event.model().record(row).value('position_value')
            value1 = self.dlg_add_visit.tbl_event.model().record(row).value('value1')
            value2 = self.dlg_add_visit.tbl_event.model().record(row).value('value2')
            geom1 = self.dlg_add_visit.tbl_event.model().record(row).value('geom1')
            geom2 = self.dlg_add_visit.tbl_event.model().record(row).value('geom2')
            geom3 = self.dlg_add_visit.tbl_event.model().record(row).value('geom3')
            text = self.dlg_add_visit.tbl_event.model().record(row).value('text')
            self.dlg_event = GwVisitEventRehabUi()
            tools_gw.load_settings(self.dlg_event)
            self._populate_position_id()
            dlg_name = 'visit_event_rehab'
            self.dlg_event.position_value.setText(str(position_value))
            self.dlg_event.value1.setText(str(value1))
            self.dlg_event.value2.setText(str(value2))
            self.dlg_event.geom1.setText(str(geom1))
            self.dlg_event.geom2.setText(str(geom2))
            self.dlg_event.geom3.setText(str(geom3))
            tools_qt.set_widget_text(self.dlg_event, self.dlg_event.text, text)
            # disable position_x fields because not allowed in multiple view
            self.dlg_event.position_id.setEnabled(True)
            self.dlg_event.position_value.setEnabled(True)

        elif om_event_parameter.form_type == 'event_standard':

            index = selected_list[0]
            row = index.row()
            column_index = tools_qt.get_col_index_by_col_name(self.dlg_add_visit.tbl_event, 'parameter_id')
            parameter_id = index.sibling(row, column_index).data()
            column_index = tools_qt.get_col_index_by_col_name(self.dlg_add_visit.tbl_event, 'event_code')
            event_code = index.sibling(row, column_index).data()
            column_index = tools_qt.get_col_index_by_col_name(self.dlg_add_visit.tbl_event, 'value')
            _value = index.sibling(row, column_index).data()
            column_index = tools_qt.get_col_index_by_col_name(self.dlg_add_visit.tbl_event, 'text')
            text = index.sibling(row, column_index).data()

            self.dlg_event = GwVisitEventUi()
            tools_gw.load_settings(self.dlg_event)
            if event_code not in ('NULL', None):
                tools_qt.set_widget_text(self.dlg_event, self.dlg_event.event_code, event_code)
            if _value not in ('NULL', None):
                tools_qt.set_widget_text(self.dlg_event, self.dlg_event.value, _value)
            if text not in ('NULL', None):
                tools_qt.set_widget_text(self.dlg_event, self.dlg_event.text, text)

        # Manage QTableView docx_x_event
        tools_qt.set_tableview_config(self.dlg_event.tbl_docs_x_event)
        self.dlg_event.tbl_docs_x_event.doubleClicked.connect(self._open_file)
        self.dlg_event.btn_add_file.clicked.connect(partial(self._get_added_files, event.visit_id, event.id, save=True))
        self.dlg_event.btn_delete_file.clicked.connect(
            partial(self._delete_files, self.dlg_event.tbl_docs_x_event, event.visit_id, event.id))
        self._populate_tbl_docs_x_event(event.id)

        # fill widget values if the values are present
        for field_name in event.field_names():
            if not hasattr(self.dlg_event, field_name):
                continue
            value = None
            if type(getattr(self.dlg_event, field_name)) is QLineEdit:
                value = getattr(self.dlg_event, field_name).text()
            elif type(getattr(self.dlg_event, field_name)) is QTextEdit:
                value = getattr(self.dlg_event, field_name).toPlainText()
            if type(getattr(self.dlg_event, field_name)) is QComboBox:
                value = tools_qt.get_combo_value(self.dlg_event, getattr(self.dlg_event, field_name), index=0)
            setattr(event, field_name, value)

        # set fixed values
        self.dlg_event.parameter_id.setText(parameter_id)

        self.dlg_event.setWindowFlags(Qt.WindowStaysOnTopHint)
        tools_qt.manage_translation(dlg_name, self.dlg_event)
        if self.dlg_event.exec_():

            # set record values basing on widget
            for field_name in event.field_names():
                if not hasattr(self.dlg_event, field_name):
                    continue
                value = None
                if type(getattr(self.dlg_event, field_name)) is QLineEdit:
                    value = getattr(self.dlg_event, field_name).text()
                elif type(getattr(self.dlg_event, field_name)) is QTextEdit:
                    value = getattr(self.dlg_event, field_name).toPlainText()
                elif type(getattr(self.dlg_event, field_name)) is QComboBox:
                    value = tools_qt.get_combo_value(self.dlg_event, getattr(self.dlg_event, field_name), index=0)
                setattr(event, field_name, value)

            # update the record
            event.upsert()

        self._save_files_added(event.visit_id, event.id)

        # update Table
        self.tbl_event.model().select()
        self.tbl_event.setModel(self.tbl_event.model())
        self._manage_events_changed()


    def _event_delete(self):
        """ Delete a selected event. """

        if not self.tbl_event.selectionModel().hasSelection():
            message = "Any record selected"
            tools_qt.show_info_box(message)
            return

        # a fake event to get some ancyllary data
        event = GwOmVisitEvent()

        # 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)
        selected_id = []
        list_id = ""
        any_docs = False
        for index in selected_list:
            selected_id.append(str(index.data()))
            list_id += "Event_id: " + str(index.data())
            sql = (f"SELECT value FROM om_visit_event_photo "
                   f"WHERE event_id='{index.data()}'")
            rows = tools_db.get_rows(sql)
            if rows:
                any_docs = True
                list_id += "(Docs associated)"
            list_id += "\n"

        # ask for deletion
        message = "Are you sure you want to delete these records?"
        if any_docs:
            message += "\nSome events have documents"
        title = "Delete records"
        answer = tools_qt.show_question(message, title, list_id)
        if not answer:
            return

        # do the action
        if not event.delete(pks=selected_id):
            message = "Error deleting records"
            tools_qgis.show_warning(message)
            return

        message = "Records deleted"
        tools_qgis.show_info(message)

        # update Table
        self.tbl_event.model().select()
        self._manage_events_changed()


    def _document_insert(self):
        """ Insert a document related to the current visit. """

        doc_id = self.doc_id.text()
        visit_id = self.visit_id.text()
        if not doc_id:
            message = "You need to insert doc_id"
            tools_qgis.show_warning(message)
            return
        if not visit_id:
            message = "You need to insert visit_id"
            tools_qgis.show_warning(message)
            return

        # Insert into new table
        sql = (f"INSERT INTO doc_x_visit (doc_id, visit_id)"
               f" VALUES ('{doc_id}', {visit_id})")
        status = tools_db.execute_sql(sql)
        if status:
            message = "Document inserted successfully"
            tools_qgis.show_info(message)

        self.dlg_add_visit.tbl_document.model().select()


    def _populate_position_id(self):

        self.dlg_event.position_id.setEnabled(self.feature_type == 'arc')
        self.dlg_event.position_value.setEnabled(self.feature_type == 'arc')
        node_list = []
        if self.feature_type != 'all':
            widget_name = f"tbl_visit_x_{self.feature_type}"
            widget_table = tools_qt.get_widget(self.dlg_add_visit, widget_name)
            node_1 = widget_table.model().record(0).value('node_1')
            node_2 = widget_table.model().record(0).value('node_2')
        else:
            node_1 = None
            node_2 = None

        node_list.append([node_1, f"node 1: {node_1}"])
        node_list.append([node_2, f"node 2: {node_2}"])
        tools_qt.fill_combo_values(self.dlg_event.position_id, node_list, 1, True, False)


    def _visit_tab_feature_changed(self, dialog, table_object='visit', excluded_layers=[]):
        """ Set feature_type and layer depending selected tab """

        # Get selected tab to set geometry type
        tab_idx = dialog.tab_feature.currentIndex()
        if dialog.tab_feature.widget(tab_idx).objectName() == 'tab_arc':
            self.feature_type = "arc"
        elif dialog.tab_feature.widget(tab_idx).objectName() == 'tab_node':
            self.feature_type = "node"
        elif dialog.tab_feature.widget(tab_idx).objectName() == 'tab_connec':
            self.feature_type = "connec"
        elif dialog.tab_feature.widget(tab_idx).objectName() == 'tab_gully':
            self.feature_type = "gully"
        if self.feature_type == '':
            return

        tools_gw.hide_parent_layers(excluded_layers=excluded_layers)
        widget_name = f"tbl_{table_object}_x_{self.feature_type}"
        viewname = f"v_edit_{self.feature_type}"
        widget_table = tools_qt.get_widget(dialog, widget_name)

        try:
            self.dlg_add_visit.btn_feature_insert.clicked.disconnect()
            self.dlg_add_visit.btn_feature_delete.clicked.disconnect()
            self.dlg_add_visit.btn_feature_snapping.clicked.disconnect()
        except Exception as e:
            tools_log.log_info(f"visit_tab_feature_changed exception: {e}")
        finally:

            self.dlg_add_visit.btn_feature_insert.clicked.connect(
                partial(tools_gw.insert_feature, self, self.dlg_add_visit, widget_table, False, False))

            self.dlg_add_visit.btn_feature_delete.clicked.connect(partial(tools_gw.delete_records, self,
                 self.dlg_add_visit, widget_table, False, self.lazy_widget, self.lazy_init_function))

            self.dlg_add_visit.btn_feature_snapping.clicked.connect(
                partial(self._feature_snapping_clicked, self.dlg_add_visit, 'visit'))

        # Adding auto-completion to a QLineEdit
        tools_gw.set_completer_widget(viewname, dialog.feature_id, str(self.feature_type) + "_id")
        tools_gw.selection_changed(self, dialog, widget_table, False, self.lazy_widget, self.lazy_init_function)

        try:
            self.iface.actionPan().trigger()
        except Exception:
            pass


    def _feature_snapping_clicked(self, dialog, table_object):

        self.previous_map_tool = global_vars.canvas.mapTool()
        tools_gw.selection_init(self, dialog, table_object, False)


    def _get_data_from_event(self, visit_id):
        """ Get parameter_id and feature_type from event of @visit_id """

        parameter_id = None
        feature_type = None
        sql = (f"SELECT visit_id, parameter_id, feature_type "
               f"FROM om_visit_event "
               f"INNER JOIN config_visit_parameter ON parameter_id = config_visit_parameter.id "
               f"WHERE visit_id = {visit_id}")
        row = tools_db.get_row(sql)
        if row:
            parameter_id = row["parameter_id"]
            feature_type = row["feature_type"].lower()

        return parameter_id, feature_type


    def _fill_widget_with_fields(self, dialog, data_object, field_names):
        """ Fill the Widget with value get from data_object limited to
        the list of field_names. """

        for field_name in field_names:
            value = getattr(data_object, field_name)
            if not hasattr(dialog, field_name):
                continue

            widget = getattr(dialog, field_name)
            if type(widget) == QDateEdit:
                widget.setDate(value if value else QDate.currentDate())
            elif type(widget) == QDateTimeEdit:
                widget.setDateTime(value if value else QDateTime.currentDateTime())

            if type(widget) in [QLineEdit, QTextEdit]:
                if value:
                    widget.setText(value)
                else:
                    widget.clear()
            if type(widget) in [QComboBox]:
                if not value:
                    widget.setCurrentIndex(0)
                    continue
                # look the value in item text
                index = widget.findText(str(value))
                if index >= 0:
                    widget.setCurrentIndex(index)
                    continue
                # look the value in itemData
                index = widget.findData(value)
                if index >= 0:
                    widget.setCurrentIndex(index)
                    continue


    def _get_values_from_catalog(self, table_name, typevalue, order_by='id'):

        sql = (f"SELECT id, idval"
               f" FROM {table_name}"
               f" WHERE typevalue = '{typevalue}'"
               f" ORDER BY {order_by}")
        rows = tools_db.get_rows(sql)
        return rows

    # endregion class GwToggleValveTask(GwTask):

    task_finished = pyqtSignal(list)

    def __init__(self, description='', params=None):

        super().__init__(description)
        self.params = params
        self.result = None
        self.json_result = None


    def run(self):

        super().run()

        body = self.params['body']
        self.json_result = tools_gw.execute_procedure('gw_fct_setfields', body, is_thread=True, aux_conn=self.aux_conn)
        if not self.json_result:
            tools_log.log_info("Function gw_fct_setfields failed")
            return False
        tools_qgis.refresh_map_canvas()
        if self.json_result and self.json_result['status'] != 'Failed':
            self.json_result = tools_gw.execute_procedure('gw_fct_setmapzonestrigger', body, is_thread=True, aux_conn=self.aux_conn)
            if not self.json_result:
                tools_log.log_info("Function gw_fct_setmapzonestrigger failed")
                return False
            tools_log.log_info("Function gw_fct_setmapzonestrigger finished successfully")

        return True


    def finished(self, result):

        super().finished(result)

        if self.isCanceled():
            self.setProgress(100)
            return

        # Handle exception
        if self.exception is not None:
            msg = f"<b>Key: </b>{self.exception}<br>"
            msg += f"<b>key container: </b>'body/data/ <br>"
            msg += f"<b>Python file: </b>{__name__} <br>"
            msg += f"<b>Python function:</b> {self.__class__.__name__} <br>"
            tools_qt.show_exception_message("Key on returned json from ddbb is missed.", msg)
            return

        if self.json_result:
            if self.json_result['level'] in (1, 2):
                tools_qt.show_info_box(self.json_result['text'])
            if self.json_result['level'] == 0:
                tools_qgis.show_info(self.json_result['text'])

        self.setProgress(100) 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 def _get_ui_class(ui_file_name, subfolder='shared'):
    def __init__(self):
    def __init__(self):
    def show_pass(self):
    def __init__(self):
    def show_pass(self):
FORM_CLASS = _get_ui_class('info_crossect.ui', 'basic')
FORM_CLASS = _get_ui_class('info_feature.ui', 'basic')
FORM_CLASS = _get_ui_class('search.ui', 'basic')
FORM_CLASS = _get_ui_class('search_workcat.ui', 'basic')
FORM_CLASS = _get_ui_class('visit_event_full.ui')
FORM_CLASS = _get_ui_class('visit_gallery.ui')
FORM_CLASS = _get_ui_class('visit_gallery_zoom.ui')
FORM_CLASS = _get_ui_class('mincut.ui', 'om')
FORM_CLASS = _get_ui_class('mincut_connec.ui', 'om')
FORM_CLASS = _get_ui_class('mincut_hydrometer.ui', 'om')
FORM_CLASS = _get_ui_class('mincut_composer.ui', 'om')
FORM_CLASS = _get_ui_class('mincut_manager.ui', 'om')
FORM_CLASS = _get_ui_class('mincut_end.ui', 'om')
FORM_CLASS = _get_ui_class('profile.ui', 'om')
FORM_CLASS = _get_ui_class('profile_list.ui', 'om')
FORM_CLASS = _get_ui_class('selector_date.ui', 'om')
FORM_CLASS = _get_ui_class('arc_fusion.ui', 'edit')
FORM_CLASS = _get_ui_class('auxcircle.ui', 'edit')
FORM_CLASS = _get_ui_class('auxpoint.ui', 'edit')
FORM_CLASS = _get_ui_class('dimensioning.ui', 'edit')
FORM_CLASS = _get_ui_class('doc.ui', 'edit')
FORM_CLASS = _get_ui_class('doc_manager.ui', 'edit')
FORM_CLASS = _get_ui_class('element.ui', 'edit')
FORM_CLASS = _get_ui_class('element_manager.ui', 'edit')
FORM_CLASS = _get_ui_class('feature_delete.ui', 'edit')
FORM_CLASS = _get_ui_class('feature_end.ui', 'edit')
FORM_CLASS = _get_ui_class('feature_end_connec.ui', 'edit')
FORM_CLASS = _get_ui_class('feature_replace.ui', 'edit')
FORM_CLASS = _get_ui_class('featuretype_change.ui', 'edit')
FORM_CLASS = _get_ui_class('go2epa.ui', 'epa')
FORM_CLASS = _get_ui_class('go2epa_selector.ui', 'epa')
FORM_CLASS = _get_ui_class('go2epa_manager.ui', 'epa')
FORM_CLASS = _get_ui_class('go2epa_options.ui', 'epa')
FROM_CLASS = _get_ui_class('dscenario_manager.ui', 'epa')
FROM_CLASS = _get_ui_class('dscenario.ui')
FORM_CLASS = _get_ui_class('psector.ui', 'plan')
FORM_CLASS = _get_ui_class('price_manager.ui', 'plan')
FORM_CLASS = _get_ui_class('psector_duplicate.ui', 'plan')
FORM_CLASS = _get_ui_class('psector_manager.ui', 'plan')
FORM_CLASS = _get_ui_class('psector_rapport.ui', 'plan')
FORM_CLASS = _get_ui_class('replace_arc.ui', 'plan')
FORM_CLASS = _get_ui_class('config.ui', 'utilities')
FORM_CLASS = _get_ui_class('csv.ui', 'utilities')
FORM_CLASS = _get_ui_class('print.ui', 'utilities')
FORM_CLASS = _get_ui_class('toolbox_reports.ui', 'utilities')
FORM_CLASS = _get_ui_class('workspace_manager.ui', 'utilities')
FORM_CLASS = _get_ui_class('workspace_create.ui', 'utilities')
FORM_CLASS = _get_ui_class('admin_addfields.ui', 'admin')
FORM_CLASS = _get_ui_class('admin_credentials.ui', 'admin')
FORM_CLASS = _get_ui_class('admin_dbproject.ui', 'admin')
FORM_CLASS = _get_ui_class('admin_gisproject.ui', 'admin')
FORM_CLASS = _get_ui_class('admin_importinp.ui', 'admin')
FORM_CLASS = _get_ui_class('admin_projectinfo.ui', 'admin')
FORM_CLASS = _get_ui_class('admin_renameproj.ui', 'admin')
FORM_CLASS = _get_ui_class('admin_sysfields.ui', 'admin')
FORM_CLASS = _get_ui_class('admin_ui.ui', 'admin')
FORM_CLASS = _get_ui_class('admin_visitclass.ui', 'admin')
FORM_CLASS = _get_ui_class('admin_visitparam.ui', 'admin')
FORM_CLASS = _get_ui_class('toolbox.ui', 'utilities')
FORM_CLASS = _get_ui_class('toolbox_tool.ui', 'utilities')
FORM_CLASS = _get_ui_class('project_check.ui', 'utilities')
FORM_CLASS = _get_ui_class('load_menu.ui', 'menu')
FORM_CLASS = _get_ui_class('dialog_text.ui')
FORM_CLASS = _get_ui_class('info_catalog.ui')
FORM_CLASS = _get_ui_class('info_generic.ui')
FORM_CLASS = _get_ui_class('info_workcat.ui')
FORM_CLASS = _get_ui_class('admin_translation.ui', 'admin')
FORM_CLASS = _get_ui_class('selector.ui')
FORM_CLASS = _get_ui_class('visit.ui')
FORM_CLASS = _get_ui_class('visit_document.ui')
FORM_CLASS = _get_ui_class('visit_event_rehab.ui')
FORM_CLASS = _get_ui_class('visit_event.ui')
FORM_CLASS = _get_ui_class('visit_manager.ui')
FORM_CLASS = _get_ui_class('interpolate.ui')
FORM_CLASS = _get_ui_class('replace_in_file.ui')
class GwInfoCrossectUi(GwDialog, FORM_CLASS):
class GwInfoFeatureUi(GwMainWindow, FORM_CLASS):
class GwSearchUi(GwDocker, FORM_CLASS):
class GwSearchWorkcatUi(GwDialog, FORM_CLASS):
class GwVisitEventFullUi(GwDialog, FORM_CLASS):
class GwGalleryUi(GwDialog, FORM_CLASS):
class GwGalleryZoomUi(GwDialog, FORM_CLASS):
class GwMincutUi(GwMainWindow, FORM_CLASS):
class GwMincutConnecUi(GwDialog, FORM_CLASS):
class GwMincutHydrometerUi(GwDialog, FORM_CLASS):
class GwMincutComposerUi(GwDialog, FORM_CLASS):
class GwMincutManagerUi(GwDialog, FORM_CLASS):
class GwMincutEndUi(GwDialog, FORM_CLASS):
class GwProfileUi(GwMainWindow, FORM_CLASS):
class GwProfilesListUi(GwDialog, FORM_CLASS):
class GwSelectorDateUi(GwDialog, FORM_CLASS):
class GwArcFusionUi(GwDialog, FORM_CLASS):
class GwAuxCircleUi(GwDialog, FORM_CLASS):
class GwAuxPointUi(GwDialog, FORM_CLASS):
class GwDimensioningUi(GwMainWindow, FORM_CLASS):
class GwDocUi(GwDialog, FORM_CLASS):
class GwDocManagerUi(GwDialog, FORM_CLASS):
class GwElementUi(GwDialog, FORM_CLASS):
class GwElementManagerUi(GwDialog, FORM_CLASS):
class GwFeatureDeleteUi(GwDialog, FORM_CLASS):
class GwFeatureEndUi(GwDialog, FORM_CLASS):
class GwFeatureEndConnecUi(GwDialog, FORM_CLASS):
class GwFeatureReplaceUi(GwDialog, FORM_CLASS):
class GwFeatureTypeChangeUi(GwDialog, FORM_CLASS):
class GwGo2EpaUI(GwDialog, FORM_CLASS):
class GwGo2EpaSelectorUi(GwDialog, FORM_CLASS):
class GwEpaManagerUi(GwDialog, FORM_CLASS):
class GwGo2EpaOptionsUi(GwDialog, FORM_CLASS):
class GwDscenarioManagerUi(GwDialog, FROM_CLASS):
class GwDscenarioUi(GwDialog, FROM_CLASS):
class GwPsectorUi(GwDialog, FORM_CLASS):
class GwPriceManagerUi(GwDialog, FORM_CLASS):
class GwPsectorDuplicateUi(GwDialog, FORM_CLASS):
class GwPsectorManagerUi(GwDialog, FORM_CLASS):
class GwPsectorRapportUi(GwDialog, FORM_CLASS):
class GwReplaceArc(GwDialog, FORM_CLASS):
class GwConfigUi(GwMainWindow, FORM_CLASS):
class GwCsvUi(GwDialog, FORM_CLASS):
class GwPrintUi(GwDialog, FORM_CLASS):
class GwToolboxReportsUi(GwDialog, FORM_CLASS):
class GwWorkspaceManagerUi(GwDialog, FORM_CLASS):
class GwCreateWorkspaceUi(GwDialog, FORM_CLASS):
class GwAdminFieldsUi(GwDialog, FORM_CLASS):
class GwCredentialsUi(GwDialog, FORM_CLASS):
class GwAdminDbProjectUi(GwMainWindow, FORM_CLASS):
class GwAdminGisProjectUi(GwMainWindow, FORM_CLASS):
class GwAdminImportUi(GwDialog, FORM_CLASS):
class GwAdminProjectInfoUi(GwMainWindow, FORM_CLASS):
class GwAdminRenameProjUi(GwMainWindow, FORM_CLASS):
class GwAdminSysFieldsUi(GwDialog, FORM_CLASS):
class GwAdminUi(GwMainWindow, FORM_CLASS):
class GwAdminVisitClassUi(GwDialog, FORM_CLASS):
class GwAdminVisitParamUi(GwDialog, FORM_CLASS):
class GwToolboxUi(GwDialog, FORM_CLASS):
class GwToolboxManagerUi(GwDialog, FORM_CLASS):
class GwProjectCheckUi(GwDialog, FORM_CLASS):
class GwLoadMenuUi(GwDialog, FORM_CLASS):
class GwDialogTextUi(GwDialog, FORM_CLASS):
class GwInfoCatalogUi(GwMainWindow, FORM_CLASS):
class GwInfoGenericUi(GwDialog, FORM_CLASS):
class GwInfoWorkcatUi(GwDialog, FORM_CLASS):
class GwAdminTranslationUi(GwDialog, FORM_CLASS):
class GwSelectorUi(GwDialog, FORM_CLASS):
class GwVisitUi(GwDialog, FORM_CLASS):
class GwVisitDocumentUi(GwDialog, FORM_CLASS):
class GwVisitEventRehabUi(GwDialog, FORM_CLASS):
class GwVisitEventUi(GwDialog, FORM_CLASS):
class GwVisitManagerUi(GwDialog, FORM_CLASS):
class GwInterpolate(GwDialog, FORM_CLASS):
class GwReplaceInFileUi(GwDialog, FORM_CLASS):
import json
import os
import re
import urllib.parse as parse
import webbrowser
from functools import partial
from sip import isdeleted
from qgis.PyQt.QtCore import pyqtSignal, QDate, QObject, QRegExp, QStringListModel, Qt, QSettings
from qgis.PyQt.QtGui import QColor, QRegExpValidator, QStandardItem, QStandardItemModel
from qgis.PyQt.QtSql import QSqlTableModel
from qgis.PyQt.QtWidgets import QAction, QAbstractItemView, QCheckBox, QComboBox, QCompleter, QDoubleSpinBox, \
    QDateEdit, QGridLayout, QLabel, QLineEdit, QListWidget, QListWidgetItem, QPushButton, QSizePolicy, \
    QSpinBox, QSpacerItem, QTableView, QTabWidget, QWidget, QTextEdit, QRadioButton
from qgis.core import QgsApplication, QgsMapToPixel, QgsVectorLayer, QgsExpression, QgsFeatureRequest, QgsPointXY, QgsProject
from qgis.gui import QgsDateTimeEdit, QgsMapToolEmitPoint
from .catalog import GwCatalog
from .dimensioning import GwDimensioning
from .document import GwDocument
from .element import GwElement
from .visit_gallery import GwVisitGallery
from .visit import GwVisit
from ..threads.toggle_valve_state import GwToggleValveTask
from ..utils import tools_gw
from ..utils.snap_manager import GwSnapManager
from ..ui.ui_manager import GwInfoGenericUi, GwInfoFeatureUi, GwVisitEventFullUi, GwMainWindow, GwVisitDocumentUi, GwInfoCrossectUi, \
    GwInterpolate
from ... import global_vars
from ...lib import tools_qgis, tools_qt, tools_log, tools_db, tools_os
        # Get selected dates
        visit_start = self.date_event_from.date()
        visit_end = self.date_event_to.date()
        date_from = visit_start.toString('yyyyMMdd 00:00:00')
        date_to = visit_end.toString('yyyyMMdd 23:59:59')
        if date_from > date_to:
            message = "Selected date interval is not valid"
            tools_qgis.show_warning(message)
            return

        format_low = 'yyyy-MM-dd 00:00:00.000'
        format_high = 'yyyy-MM-dd 23:59:59.999'
        interval = f"'{visit_start.toString(format_low)}'::timestamp AND '{visit_end.toString(format_high)}'::timestamp"

        # Set filter to model
        expr = f"{self.field_id} = '{self.feature_id}'"
        # Set filter
        expr += f" AND visit_start BETWEEN {interval}"

        # Get selected values in Comboboxes
        event_type_value = tools_qt.get_combo_value(self.dlg_cf, self.dlg_cf.event_type, 0)
        if event_type_value != 'null':
            expr += f" AND parameter_type ILIKE '%{event_type_value}%'"
        event_id = tools_qt.get_combo_value(self.dlg_cf, self.dlg_cf.event_id, 0)
        if event_id != 'null':
            expr += f" AND parameter_id ILIKE '%{event_id}%'"

        # Refresh model with selected filter
        widget.model().setFilter(expr)
        widget.model().select()


    def _open_visit(self):
        """ Call button 65: om_visit_management """

        manage_visit = GwVisit()
        manage_visit.visit_added.connect(self._update_visit_table)
        manage_visit.manage_visits(self.feature_type, self.feature_id)


    def _update_visit_table(self):
        """ Convenience fuction set as slot to update table after a Visit GUI close. """
        table_name = "v_ui_event_x_" + self.feature_type
        tools_gw.set_dates_from_to(self.date_event_from, self.date_event_to, table_name, 'visit_start', 'visit_end')
        self.tbl_event_cf.model().select()


    def _new_visit(self):
        """ Call button 64: om_add_visit """

        # Get expl_id to save it on om_visit and show the geometry of visit
        expl_id = tools_qt.get_combo_value(self.dlg_cf, self.tab_type + '_expl_id', 0)
        if expl_id == -1:
            msg = "Widget expl_id not found"
            tools_qgis.show_warning(msg)
            return

        manage_visit = GwVisit()
        manage_visit.visit_added.connect(self._update_visit_table)
        # TODO: the following query fix a (for me) misterious bug
        # the DB connection is not available during manage_visit.manage_visit first call
        # so the workaroud is to do a unuseful query to have the dao active
        sql = "SELECT id FROM om_visit LIMIT 1;"
        tools_db.get_rows(sql)
        manage_visit.get_visit(feature_type=self.feature_type, feature_id=self.feature_id, expl_id=expl_id,
                               is_new_from_cf=True)


    def _open_gallery(self):
        """ Open gallery of selected record of the table """

        # Open Gallery
        gal = GwVisitGallery()
        gal.manage_gallery()
        gal.fill_gallery(self.visit_id, self.event_id)


    def _open_visit_doc(self):
        """ Open document of selected record of the table """

        # Get all documents for one visit
        sql = (f"SELECT doc_id FROM doc_x_visit"
               f" WHERE visit_id = '{self.visit_id}'")
        rows = tools_db.get_rows(sql)
        if not rows:
            return

        num_doc = len(rows)
        if num_doc == 1:
            # If just one document is attached directly open

            # Get path of selected document
            sql = (f"SELECT path"
                   f" FROM v_ui_doc"
                   f" WHERE id = '{rows[0][0]}'")
            row = tools_db.get_row(sql)
            if not row:
                return

            path = str(row[0])

            # Parse a URL into components
            url = parse.urlsplit(path)

            # Open selected document
            # Check if path is URL
            if url.scheme == "http" or url.scheme == "https":
                webbrowser.open(path)
            else:

                if not os.path.exists(path):
                    message = "File not found"
                    tools_qgis.show_warning(message, parameter=path)
                else:
                    status, message = tools_os.open_file(path)
                    if status is False and message is not None:
                        tools_qgis.show_warning(message, parameter=path)

        else:
            # If more then one document is attached open dialog with list of documents

            self.dlg_load_doc = GwVisitDocumentUi()