Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions src/opensak/db/corrected_coords.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""
src/opensak/db/corrected_coords.py — Skriv korrigerede koordinater for en cache.

Fælles DB-skrivning for alle steder der gemmer korrigerede koordinater
med det samme (detaljepanel, cache-tabellens højreklik-menu, kortets
højreklik-menu), så UserNote-rækken altid opdateres ens.
"""

from __future__ import annotations
from typing import Optional

from opensak.db.database import get_session
from opensak.db.models import Cache, UserNote
from opensak.utils.types import GcCode


def set_corrected_coords(
gc_code: GcCode, lat: Optional[float], lon: Optional[float]
) -> bool:
"""Sæt (eller ryd, med lat/lon = None) korrigerede koordinater for gc_code.

Opretter UserNote-rækken hvis den mangler. is_corrected sættes kun når
både lat og lon er angivet. Returnerer False hvis cachen ikke findes.
"""
with get_session() as session:
cache_row = session.query(Cache).filter_by(gc_code=gc_code).first()
if cache_row is None:
return False
note = cache_row.user_note
if note is None:
note = UserNote(cache_id=cache_row.id)
session.add(note)
note.corrected_lat = lat
note.corrected_lon = lon
note.is_corrected = lat is not None and lon is not None
return True
26 changes: 9 additions & 17 deletions src/opensak/gui/cache_detail.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,8 @@ def _edit_corrected_coords(self) -> None:
from opensak.gui.dialogs.corrected_coords_dialog import CorrectedCoordsDialog
dlg = CorrectedCoordsDialog(
gc_code=self._current_gc_code,
orig_lat=self._current_lat,
orig_lon=self._current_lon,
corrected_lat=self._corrected_lat,
corrected_lon=self._corrected_lon,
parent=self,
Expand All @@ -462,27 +464,17 @@ def _clear_corrected_coords(self) -> None:
self._save_corrected_coords(None, None)

def _save_corrected_coords(self, lat, lon) -> None:
from opensak.db.database import get_session
from opensak.db.models import Cache as CacheModel, UserNote
with get_session() as session:
cache_row = session.query(CacheModel).filter_by(
gc_code=self._current_gc_code
).first()
if not cache_row:
return
note = cache_row.user_note
if note is None:
note = UserNote(cache_id=cache_row.id)
session.add(note)
note.corrected_lat = lat
note.corrected_lon = lon
note.is_corrected = (lat is not None and lon is not None)
gc_code = self._current_gc_code
if not gc_code:
return
from opensak.db.corrected_coords import set_corrected_coords
if not set_corrected_coords(gc_code, lat, lon):
return

self._corrected_lat = lat
self._corrected_lon = lon
self._update_corrected_ui()
if self._current_gc_code:
self.corrected_coords_changed.emit(self._current_gc_code)
self.corrected_coords_changed.emit(gc_code)

def eventFilter(self, obj, event) -> bool:
note_editor = getattr(self, "_note_editor", None)
Expand Down
49 changes: 6 additions & 43 deletions src/opensak/gui/cache_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -1848,49 +1848,12 @@ def _clear_corrected(self, cache: Cache) -> None:
self._save_corrected(cache, None, None)

def _save_corrected(self, cache: Cache, lat, lon) -> None:
from opensak.db.database import get_session
from opensak.db.models import UserNote, Cache as CacheModel
from sqlalchemy.orm import joinedload, selectinload
with get_session() as session:
cache_row = session.query(CacheModel).options(
joinedload(CacheModel.user_note)
).filter_by(gc_code=cache.gc_code).first()
if not cache_row:
return
note = cache_row.user_note
if note is None:
note = UserNote(cache_id=cache_row.id)
session.add(note)
session.flush()
note.corrected_lat = lat
note.corrected_lon = lon
note.is_corrected = (lat is not None and lon is not None)

# Reload det fulde cache-objekt fra DB med user_note eager-loaded,
# og erstat det detachede objekt i modellen direkte.
# Det undgår alle problemer med at skrive til detached ORM-relationer.
with get_session() as session:
fresh = session.query(CacheModel).options(
joinedload(CacheModel.user_note),
selectinload(CacheModel.waypoints),
selectinload(CacheModel.attributes),
selectinload(CacheModel.trackables),
).filter_by(gc_code=cache.gc_code).first()
if fresh is None:
return

# Erstat objektet i listen — find det via gc_code
caches = self._model._caches
for i, c in enumerate(caches):
if c.gc_code == cache.gc_code:
caches[i] = fresh
break

self._reset_model_preserving_selection()
# Issue #474: map wasn't refreshed when corrected coords were set/cleared
# via this (context menu) path — only the "Add corrected coordinates..."
# button in the cache detail panel emitted a change signal. Emit here too
# so mainwindow can update the map pin the same way for both entry points.
from opensak.db.corrected_coords import set_corrected_coords
if not set_corrected_coords(cache.gc_code, lat, lon):
return
# Issue #474: mainwindow refreshes the table row (refresh_cache_row),
# map pin and detail panel from this signal — the same handler for
# every entry point, so no row reload here.
self.corrected_coords_changed.emit(cache.gc_code)

def _open_converter(self, lat: float, lon: float) -> None:
Expand Down
58 changes: 56 additions & 2 deletions src/opensak/gui/dialogs/corrected_coords_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@

from opensak.lang import tr
from opensak.coords import format_coords, parse_coords
from opensak.filters.engine import (
DIRECTIONS, _bearing_deg, bearing_direction, distance_km,
)
from opensak.gui.dialogs.distance_bearing_dialog import _format_distance
from opensak.gui.settings import get_settings
from opensak.gui.theme import hint_style
from opensak.utils.types import GcCode, CoordFormat
Expand Down Expand Up @@ -164,6 +168,13 @@ def _setup_ui(
self._corrected_panel_widget: Optional[QFrame] = None
layout.addLayout(self._corrected_panel_container)

# ── Afstand og retning fra originale koordinater ──────────────────────
self._offset_lbl = QLabel("")
self._offset_lbl.setStyleSheet("font-weight: bold;")
self._offset_lbl.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
self._offset_lbl.setVisible(False)
layout.addWidget(self._offset_lbl)

# ── Knapper ───────────────────────────────────────────────────────────
btn_box = QDialogButtonBox(
QDialogButtonBox.StandardButton.Ok |
Expand All @@ -173,6 +184,17 @@ def _setup_ui(
btn_box.rejected.connect(self.reject)
self._ok_btn = btn_box.button(QDialogButtonBox.StandardButton.Ok)
self._ok_btn.setEnabled(False)

# Fjern-knap — kun når cachen allerede har korrigerede koordinater.
# Accepterer dialogen med (None, None), som alle kaldere gemmer som "ryd".
self._remove_btn = btn_box.addButton(
tr("ctx_clear_corrected"), QDialogButtonBox.ButtonRole.ResetRole
)
self._remove_btn.setToolTip(tr("detail_corrected_clear_tooltip"))
self._remove_btn.clicked.connect(self._on_remove)
self._remove_btn.setVisible(
current_lat is not None and current_lon is not None
)
layout.addWidget(btn_box)

self._input.textChanged.connect(self._on_input_changed)
Expand Down Expand Up @@ -221,15 +243,47 @@ def _set_corrected_panel(self, lat: Optional[float], lon: Optional[float]) -> No
self._corrected_lbl.setVisible(True)
self._corrected_panel_widget = self._make_coords_panel(lat, lon)
self._corrected_panel_container.addWidget(self._corrected_panel_widget)
self.adjustSize()
else:
self._corrected_lbl.setVisible(False)
self.adjustSize()
self._update_offset(lat, lon)
self.adjustSize()

def _update_offset(self, lat: Optional[float], lon: Optional[float]) -> None:
"""Vis afstand og retning fra originale til korrigerede koordinater.

Skjules når originale koordinater mangler eller input er ugyldigt.
Fanger tastefejl (forkert minut-ciffer, forkert halvkugle) — mystery
final-koordinater ligger typisk inden for 2 mi / 3.2 km af de originale.
"""
if (lat is None or lon is None
or self._orig_lat is None or self._orig_lon is None):
self._offset_lbl.setVisible(False)
return
metres = distance_km(self._orig_lat, self._orig_lon, lat, lon) * 1000.0
if metres < 0.5:
bearing = "—"
else:
deg = _bearing_deg(self._orig_lat, self._orig_lon, lat, lon)
dirs = dict(zip(DIRECTIONS, tr("bearing_dirs").split()))
code = bearing_direction(deg)
bearing = f"{deg:.0f}° {dirs.get(code, code)}"
self._offset_lbl.setText(tr(
"corrected_dialog_offset",
distance=_format_distance(metres, get_settings().use_miles),
bearing=bearing,
))
self._offset_lbl.setVisible(True)

def _on_accept(self) -> None:
if self._lat is not None and self._lon is not None:
self.accept()

def _on_remove(self) -> None:
"""Ryd korrigerede koordinater: accepter med (None, None)."""
self._lat = None
self._lon = None
self.accept()

def get_coords(self) -> Tuple[Optional[float], Optional[float]]:
"""Returner de parsede koordinater."""
return self._lat, self._lon
26 changes: 8 additions & 18 deletions src/opensak/gui/mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -1627,26 +1627,12 @@ def _on_map_cache_selected(self, gc_code: GcCode) -> None:

def _on_set_corrected_from_map(self, gc_code: GcCode, lat: float, lon: float) -> None:
"""Sæt korrigerede koordinater på en cache via højreklik på kortet."""
from opensak.db.database import get_session
from opensak.db.models import UserNote
from opensak.db.corrected_coords import set_corrected_coords
from opensak.coords import format_coords
from opensak.gui.settings import get_settings
from sqlalchemy.orm import joinedload

with get_session() as session:
cache = session.query(Cache).options(
joinedload(Cache.user_note)
).filter_by(gc_code=gc_code).first()
if not cache:
return
note = cache.user_note
if note is None:
note = UserNote(cache_id=cache.id)
session.add(note)
note.corrected_lat = lat
note.corrected_lon = lon
note.is_corrected = True
session.commit()
if not set_corrected_coords(gc_code, lat, lon):
return

coords = format_coords(lat, lon, get_settings().coord_format)
self._statusbar.showMessage(
Expand All @@ -1655,11 +1641,15 @@ def _on_set_corrected_from_map(self, gc_code: GcCode, lat: float, lon: float) ->
self._on_corrected_coords_changed(gc_code)

def _on_corrected_coords_changed(self, gc_code: GcCode) -> None:
"""Update the map pin and table row after corrected coordinates change."""
"""Update the map pin, table row and detail panel (if this cache is
currently shown) after corrected coordinates change — whichever entry
point (detail panel, table context menu, map context menu) made it."""
self._cache_table.refresh_cache_row(gc_code)
full = self._load_full_cache(gc_code)
if full:
self._map_widget.update_cache(full)
if getattr(self._detail_panel, "_current_gc_code", None) == gc_code:
self._detail_panel.show_cache(full)

def _on_found_status_changed(self, gc_code: GcCode) -> None:
"""Issue #649: refresh table row, map pin, detail panel (if this
Expand Down
1 change: 1 addition & 0 deletions src/opensak/lang/cs.py
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,7 @@
"corrected_dialog_original": "Původní souřadnice:",
"corrected_dialog_corrected": "Opravené souřadnice:",
"corrected_dialog_copy_tooltip": "Kopírovat do schránky",
"corrected_dialog_offset": "Vzdálenost od originálu: {distance} · Azimut: {bearing}",

# ── Distance & bearing dialog ─────────────────────────────────────────────
"dist_title": "Vzdálenost & Azimut",
Expand Down
1 change: 1 addition & 0 deletions src/opensak/lang/da.py
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,7 @@
"corrected_dialog_original": "Originale koordinater:",
"corrected_dialog_corrected": "Korrigerede koordinater:",
"corrected_dialog_copy_tooltip": "Kopiér til udklipsholder",
"corrected_dialog_offset": "Afstand fra original: {distance} · Retning: {bearing}",

# ── Afstand & retning dialog ──────────────────────────────────────────────
"dist_title": "Afstand & Retning",
Expand Down
1 change: 1 addition & 0 deletions src/opensak/lang/de.py
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,7 @@
"corrected_dialog_original": "Originalkoordinaten:",
"corrected_dialog_corrected": "Korrigierte Koordinaten:",
"corrected_dialog_copy_tooltip": "In die Zwischenablage kopieren",
"corrected_dialog_offset": "Entfernung vom Original: {distance} · Richtung: {bearing}",

# ── Distance & bearing dialog ─────────────────────────────────────────────
"dist_title": "Entfernung & Peilung",
Expand Down
1 change: 1 addition & 0 deletions src/opensak/lang/de_CH.py
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,7 @@
"corrected_dialog_original": "Originalkoordinaten:",
"corrected_dialog_corrected": "Korrigierte Koordinaten:",
"corrected_dialog_copy_tooltip": "In die Zwischenablage kopieren",
"corrected_dialog_offset": "Entfernung vom Original: {distance} · Richtung: {bearing}",

# ── Distance & bearing dialog ─────────────────────────────────────────────
"dist_title": "Entfernung & Peilung",
Expand Down
1 change: 1 addition & 0 deletions src/opensak/lang/en.py
Original file line number Diff line number Diff line change
Expand Up @@ -1000,6 +1000,7 @@
"corrected_dialog_original": "Original coordinates:",
"corrected_dialog_corrected": "Corrected coordinates:",
"corrected_dialog_copy_tooltip": "Copy to clipboard",
"corrected_dialog_offset": "Distance from original: {distance} · Bearing: {bearing}",

# ── Distance & bearing dialog ─────────────────────────────────────────────
"dist_title": "Distance & Bearing",
Expand Down
1 change: 1 addition & 0 deletions src/opensak/lang/es.py
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,7 @@
"corrected_dialog_original": "Coordenadas originales:",
"corrected_dialog_corrected": "Coordenadas corregidas:",
"corrected_dialog_copy_tooltip": "Copiar al portapapeles",
"corrected_dialog_offset": "Distancia desde el original: {distance} · Rumbo: {bearing}",

# ── Distance & bearing dialog ─────────────────────────────────────────────
"dist_title": "Distancia y rumbo",
Expand Down
1 change: 1 addition & 0 deletions src/opensak/lang/fr.py
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,7 @@
"corrected_dialog_original": "Coordonnées d'origine :",
"corrected_dialog_corrected": "Coordonnées corrigées :",
"corrected_dialog_copy_tooltip": "Copier dans le presse-papiers",
"corrected_dialog_offset": "Distance depuis l'original : {distance} · Cap : {bearing}",

# ── Distance & cap ────────────────────────────────────────────────────────
"dist_title": "Distance & Cap",
Expand Down
1 change: 1 addition & 0 deletions src/opensak/lang/nl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1003,6 +1003,7 @@
"corrected_dialog_original": "Originele coördinaten:",
"corrected_dialog_corrected": "Gecorrigeerde coördinaten:",
"corrected_dialog_copy_tooltip": "Kopiëren naar klembord",
"corrected_dialog_offset": "Afstand tot origineel: {distance} · Richting: {bearing}",

# ── Distance & bearing dialog ─────────────────────────────────────────────
"dist_title": "Afstand & Richting",
Expand Down
1 change: 1 addition & 0 deletions src/opensak/lang/pl.py
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,7 @@
"corrected_dialog_original": "Oryginalne współrzędne:",
"corrected_dialog_corrected": "Poprawione współrzędne:",
"corrected_dialog_copy_tooltip": "Kopiuj do schowka",
"corrected_dialog_offset": "Odległość od oryginału: {distance} · Azymut: {bearing}",

# ── Distance & bearing dialog ─────────────────────────────────────────────
"dist_title": "Odległość i azymut",
Expand Down
1 change: 1 addition & 0 deletions src/opensak/lang/pt.py
Original file line number Diff line number Diff line change
Expand Up @@ -1002,6 +1002,7 @@
"corrected_dialog_original": "Coordenadas originais:",
"corrected_dialog_corrected": "Coordenadas corrigidas:",
"corrected_dialog_copy_tooltip": "Copiar para a área de transferência",
"corrected_dialog_offset": "Distância do original: {distance} · Rumo: {bearing}",

# ── Distance & bearing dialog ─────────────────────────────────────────────
"dist_title": "Distância e Azimute",
Expand Down
1 change: 1 addition & 0 deletions src/opensak/lang/se.py
Original file line number Diff line number Diff line change
Expand Up @@ -1001,6 +1001,7 @@
"corrected_dialog_original": "Ursprungliga koordinater:",
"corrected_dialog_corrected": "Korrigerade koordinater:",
"corrected_dialog_copy_tooltip": "Kopiera till urklipp",
"corrected_dialog_offset": "Avstånd från original: {distance} · Riktning: {bearing}",

# ── Distance & bearing dialog ─────────────────────────────────────────────
"dist_title": "Avstånd & Bäring",
Expand Down
Loading
Loading