"""sn_verfahrensgebiet/ui/tabs/eigentuemer_tab.py – Tab „Eigentümer“ (#56). Zwei Bereiche: - **Flurstückssuche:** Gemarkung (Name oder Nummer) und Flurstücksnummer eingeben, „Suchen“ wählt die Treffer im Layer „Flurstücke“ aus und zoomt hin. Logik: :mod:`sn_verfahrensgebiet.functions.flurstueck_suche`. - **Eigentümersuche:** Eigentümerdatei (CSV-Layer) wählen, Name und/oder Vorname eingeben, „Suchen“ öffnet die Trefferliste. Logik: :mod:`sn_verfahrensgebiet.functions.eigentuemer_suche`. """ from sn_basis.functions.qt_wrapper import ( QWidget, QVBoxLayout, QHBoxLayout, QGridLayout, QLabel, QLineEdit, QPushButton, QComboBox, Qt, ) from sn_basis.functions.qgisui_wrapper import iface from sn_basis.functions.dialog_wrapper import show_info_dialog from sn_basis.functions.message_wrapper import success, info, warning from sn_verfahrensgebiet.functions.flurstueck_suche import ( flurstueck_suchen, FlurstueckSucheFehler, KeinFlurstueckslayer, ) from sn_verfahrensgebiet.functions.eigentuemer_suche import ( eigentuemerlayer_im_projekt, hole_layer, suche_eigentuemer, EigentuemerSucheFehler, ) from sn_verfahrensgebiet.ui.eigentuemer_treffer import EigentuemerTrefferFenster _TITEL_SUCHE = "Flurstückssuche" _TITEL_EIGENTUEMER = "Eigentümersuche" class EigentuemerLayerAuswahl(QComboBox): """Auswahl der Eigentümerdatei; zeigt nur Layer mit den Spalten Nachname und Vorname. Die Liste wird beim Aufklappen neu aufgebaut, damit eine gerade geladene CSV ohne Projektsignale erscheint. """ def aktualisieren(self) -> None: """Baut die Liste neu auf und behält die bisherige Auswahl, solange es den Layer noch gibt.""" bisher = self.currentData() self.clear() for layer in eigentuemerlayer_im_projekt(): self.addItem(layer.name(), layer.id()) index = self.findData(bisher) if bisher else -1 self.setCurrentIndex(index if index >= 0 else 0) def showPopup(self) -> None: self.aktualisieren() super().showPopup() class EigentuemerTab(QWidget): """Tab für Flurstückssuche und Eigentümersuche. Attributes: tab_title: Anzeigetitel des Tabs ("Eigentümer") """ tab_title = "Eigentümer" def __init__(self, parent=None): super().__init__(parent) layout = QVBoxLayout() layout.setAlignment(Qt.AlignmentFlag.AlignTop) layout.addWidget(QLabel("Flurstückssuche")) raster = QGridLayout() raster.addWidget(QLabel("Gemarkung:"), 0, 0) self.gemarkung_edit = QLineEdit() self.gemarkung_edit.setPlaceholderText("Name oder Nummer, z. B. 0268") raster.addWidget(self.gemarkung_edit, 0, 1) raster.addWidget(QLabel("Flurstück:"), 1, 0) self.flurstueck_edit = QLineEdit() self.flurstueck_edit.setPlaceholderText("z. B. 562 oder 12/3") raster.addWidget(self.flurstueck_edit, 1, 1) layout.addLayout(raster) zeile_suchen = QHBoxLayout() zeile_suchen.addStretch() self.btn_flurstueck_suchen = QPushButton("Suchen") self.btn_flurstueck_suchen.setToolTip( "Sucht im Layer „Flurstücke“. Ohne Nenner werden alle Flurstücke " "mit diesem Zähler gefunden." ) zeile_suchen.addWidget(self.btn_flurstueck_suchen) layout.addLayout(zeile_suchen) self.btn_flurstueck_suchen.clicked.connect(self._on_flurstueck_suchen) self.gemarkung_edit.returnPressed.connect(self._on_flurstueck_suchen) self.flurstueck_edit.returnPressed.connect(self._on_flurstueck_suchen) titel_eigentuemer = QLabel("Eigentümersuche") titel_eigentuemer.setStyleSheet("margin-top: 12px;") layout.addWidget(titel_eigentuemer) raster_eig = QGridLayout() raster_eig.addWidget(QLabel("Eigentümerdatei:"), 0, 0) self.eigentuemer_layer_combo = EigentuemerLayerAuswahl() self.eigentuemer_layer_combo.setToolTip( "CSV-Layer mit den Spalten Nachname und Vorname (Serienbriefexport). " "Die CSV von Hand ins Projekt laden; die Liste wird beim Aufklappen aktualisiert." ) raster_eig.addWidget(self.eigentuemer_layer_combo, 0, 1) raster_eig.addWidget(QLabel("Name:"), 1, 0) self.nachname_edit = QLineEdit() raster_eig.addWidget(self.nachname_edit, 1, 1) raster_eig.addWidget(QLabel("Vorname:"), 2, 0) self.vorname_edit = QLineEdit() raster_eig.addWidget(self.vorname_edit, 2, 1) layout.addLayout(raster_eig) zeile_eig_suchen = QHBoxLayout() zeile_eig_suchen.addStretch() self.btn_eigentuemer_suchen = QPushButton("Suchen") self.btn_eigentuemer_suchen.setToolTip( "Findet Einträge, deren Name und Vorname die Eingaben enthalten. " "Gleiche Personen mit gleicher Anschrift werden zusammengefasst." ) zeile_eig_suchen.addWidget(self.btn_eigentuemer_suchen) layout.addLayout(zeile_eig_suchen) self.btn_eigentuemer_suchen.clicked.connect(self._on_eigentuemer_suchen) self.nachname_edit.returnPressed.connect(self._on_eigentuemer_suchen) self.vorname_edit.returnPressed.connect(self._on_eigentuemer_suchen) self._trefferfenster = None self.eigentuemer_layer_combo.aktualisieren() self.setLayout(layout) def _on_flurstueck_suchen(self) -> None: """Sucht das eingegebene Flurstück, wählt die Treffer aus und zoomt hin.""" gemarkung = self.gemarkung_edit.text().strip() nummer = self.flurstueck_edit.text().strip() try: ergebnis = flurstueck_suchen(gemarkung, nummer) except KeinFlurstueckslayer as exc: show_info_dialog(_TITEL_SUCHE, str(exc), self) return except FlurstueckSucheFehler as exc: warning(_TITEL_SUCHE, str(exc)) return if not ergebnis.feature_ids: info(_TITEL_SUCHE, f"Flurstück {nummer} in Gemarkung {gemarkung} nicht gefunden.") return ergebnis.layer.selectByIds(ergebnis.feature_ids) self._zoome_auf_auswahl(ergebnis.layer, ergebnis.feature_ids) anzahl = len(ergebnis.feature_ids) text = "1 Flurstück" if anzahl == 1 else f"{anzahl} Flurstücke" success(_TITEL_SUCHE, f"{text} im Layer „{ergebnis.layer.name()}“ ausgewählt.") @staticmethod def _zoome_auf_auswahl(layer, feature_ids) -> bool: """Zoomt auf die Auswahl und lässt die Treffer kurz aufblinken. ``zoomToSelected`` rechnet selbst in das KBS der Kartenansicht um. Gibt ``False`` zurück, wenn keine Kartenansicht verfügbar ist. """ try: canvas = iface.mapCanvas() canvas.zoomToSelected(layer) canvas.flashFeatureIds(layer, feature_ids) return True except Exception: return False def _on_eigentuemer_suchen(self) -> None: """Sucht in der gewählten Eigentümerdatei und zeigt die Trefferliste. Meldungen enthalten bewusst keine Namen (Datenschutz). """ layer = hole_layer(self.eigentuemer_layer_combo.currentData()) if layer is None: self.eigentuemer_layer_combo.aktualisieren() layer = hole_layer(self.eigentuemer_layer_combo.currentData()) if layer is None: warning( _TITEL_EIGENTUEMER, "Keine Eigentümerdatei gewählt. Bitte den Serienbriefexport (CSV) " "als Layer laden und hier auswählen.", ) return try: treffer = suche_eigentuemer(layer, self.nachname_edit.text(), self.vorname_edit.text()) except EigentuemerSucheFehler as exc: warning(_TITEL_EIGENTUEMER, str(exc)) return if not treffer: info(_TITEL_EIGENTUEMER, "Keine passenden Einträge gefunden.") return self._zeige_treffer(treffer) def _zeige_treffer(self, treffer) -> None: """Ersetzt eine offene Trefferliste durch die neue.""" alt = getattr(self, "_trefferfenster", None) if alt is not None: alt.close() self._trefferfenster = EigentuemerTrefferFenster(treffer, self) self._trefferfenster.show()