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
3 changes: 2 additions & 1 deletion features.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"reverse-geocoding": true,
"map-popout": true
"map-popout": true,
"lua-macros": true
}
1 change: 1 addition & 0 deletions opensak.spec
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ a = Analysis(
"certifi",
"shapely",
"shapely.geometry",
"lupa.lua54",
] + (["win32com.client", "pythoncom"] if sys.platform == "win32" else []),
hookspath=[],
hooksconfig={},
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ dependencies = [
"shapely>=2.0,<3",
"pywin32>=306; platform_system == 'Windows'",
"keyring>=25.0,<26",
# Lua macros (proof of concept, roadmap item 2)
"lupa>=2.0,<3",
]

[project.optional-dependencies]
Expand Down
119 changes: 119 additions & 0 deletions src/opensak/gui/dialogs/macro_dialog.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
"""
src/opensak/gui/dialogs/macro_dialog.py — Lua macro editor/runner (proof of concept).

Non-modal, so the cache list behind it can be watched while a macro changes
the filter. The script runs synchronously on the GUI thread; the runtime's
instruction limit guards against endless loops.
"""

from __future__ import annotations

from pathlib import Path

from PySide6.QtCore import Qt
from PySide6.QtGui import QFontDatabase
from PySide6.QtWidgets import (
QDialog, QFileDialog, QHBoxLayout, QPlainTextEdit, QPushButton,
QSplitter, QVBoxLayout,
)

from opensak.gui.dialogs.widgets import clamp_dialog_height_to_screen
from opensak.lang import tr
from opensak.macro import MacroError, MacroHost, MacroRuntime

EXAMPLE_MACRO = """\
-- OpenSAK macro (Lua) — proof of concept
-- opensak.filter{...}, opensak.filter_profile(name), opensak.clear_filter(),
-- opensak.count(), opensak.profiles(), print(...)

local n = opensak.filter{
type = {"Traditional", "Multi-cache"},
difficulty = {1, 2.5},
found = false,
label = "Easy unfound",
}

if n == 0 then
print("No caches match — filter not applied")
else
print("Selected " .. n .. " caches")
end
"""


class MacroDialog(QDialog):
"""Small editor with Open… / Run and an output pane."""

def __init__(self, host: MacroHost, parent=None):
super().__init__(parent)
self.setWindowTitle(tr("macro_title"))
self.resize(640, 520)
clamp_dialog_height_to_screen(self, parent)
# Non-modal editor window: give it an explicit close (and min/max)
# button — the inherited dialog flags left Windows without a working ✕.
self.setWindowFlags(
Qt.WindowType.Window
| Qt.WindowType.WindowTitleHint
| Qt.WindowType.WindowSystemMenuHint
| Qt.WindowType.WindowMinMaxButtonsHint
| Qt.WindowType.WindowCloseButtonHint
)
self._runtime = MacroRuntime(host, output=self._append_output)
self._chunk_name = "macro"
self._setup_ui()

def _setup_ui(self) -> None:
layout = QVBoxLayout(self)

mono = QFontDatabase.systemFont(QFontDatabase.SystemFont.FixedFont)

self._editor = QPlainTextEdit()
self._editor.setFont(mono)
self._editor.setPlainText(EXAMPLE_MACRO)

self._output = QPlainTextEdit()
self._output.setFont(mono)
self._output.setReadOnly(True)

splitter = QSplitter(Qt.Orientation.Vertical)
splitter.addWidget(self._editor)
splitter.addWidget(self._output)
splitter.setSizes([380, 140])
layout.addWidget(splitter)

buttons = QHBoxLayout()
btn_open = QPushButton(tr("macro_btn_open"))
btn_open.clicked.connect(self._open_file)
buttons.addWidget(btn_open)
buttons.addStretch()
self._btn_run = QPushButton(tr("macro_btn_run"))
self._btn_run.setDefault(True)
self._btn_run.clicked.connect(self._run)
buttons.addWidget(self._btn_run)
btn_close = QPushButton(tr("close"))
btn_close.clicked.connect(self.close)
buttons.addWidget(btn_close)
layout.addLayout(buttons)

def _open_file(self) -> None:
path, _ = QFileDialog.getOpenFileName(
self, tr("macro_open_title"), "", "Lua (*.lua);;* (*)"
)
if not path:
return
self._editor.setPlainText(Path(path).read_text(encoding="utf-8"))
self._chunk_name = Path(path).name

def _append_output(self, text: str) -> None:
self._output.appendPlainText(text)

def _run(self) -> None:
self._output.clear()
self._btn_run.setEnabled(False)
try:
self._runtime.run(self._editor.toPlainText(), chunk_name=self._chunk_name)
self._append_output(tr("macro_done"))
except MacroError as exc:
self._append_output(tr("macro_error", msg=str(exc)))
finally:
self._btn_run.setEnabled(True)
51 changes: 51 additions & 0 deletions src/opensak/gui/mainwindow.py
Original file line number Diff line number Diff line change
Expand Up @@ -547,6 +547,15 @@ def _setup_menu(self) -> None:
act_dist_bearing.triggered.connect(self._open_dist_bearing)
gc_tools_menu.addAction(act_dist_bearing)

# ── Macros (Lua, proof of concept) ────────────────────────────────────
macros_menu = menubar.addMenu(tr("menu_macros"))
# Beta-only until the macro API is settled (#938 step 4).
macros_menu.menuAction().setVisible(flags.lua_macros)

act_run_macro = QAction(tr("action_run_macro"), self)
act_run_macro.triggered.connect(self._open_macro_dialog)
macros_menu.addAction(act_run_macro)

# ── Hjælp ─────────────────────────────────────────────────────────────
help_menu = menubar.addMenu(tr("menu_help"))

Expand Down Expand Up @@ -2600,6 +2609,13 @@ def _on_filter_applied(self, filterset, sort, profile_name: str) -> None:
QTimer.singleShot(0, lambda: self._show_filter_dialog(filterset, profile_name))
return

self._show_filter_result(filterset, profile_name, caches)

def _show_filter_result(self, filterset, profile_name: str, caches: list) -> None:
"""Make *filterset* the active filter and show its (non-empty) *caches*.

Shared by the "Set filter" dialog and Lua macros (opensak.filter).
"""
self._current_filterset = filterset
self._active_filter_name = profile_name
self._save_sort_for_active_db()
Expand Down Expand Up @@ -3023,6 +3039,41 @@ def _open_trip_planner(self) -> None:
def _on_trip_planner_destroyed(self) -> None:
self._trip_planner_win = None

# ── Lua macros (proof of concept) — MacroHost for opensak.macro ─────────

def _open_macro_dialog(self) -> None:
from opensak.gui.dialogs.macro_dialog import MacroDialog
if getattr(self, "_macro_dialog", None) is None:
self._macro_dialog = MacroDialog(host=self, parent=self)
self._macro_dialog.show()
self._macro_dialog.raise_()
self._macro_dialog.activateWindow()

def apply_filter(self, filterset, label: str) -> int:
"""MacroHost: apply *filterset*; like GSAK's MFILTER, an empty result
leaves the current view untouched and returns 0."""
with get_session() as session:
caches = apply_filters_auto(
session, filterset, self._current_sort,
columns=self._visible_table_columns(),
)
if caches:
self._show_filter_result(filterset, label, caches)
return len(caches)

def clear_filter(self) -> None:
"""MacroHost: remove the active filter (the list reloads asynchronously)."""
self._clear_filter()

def cache_count(self) -> int:
"""MacroHost: number of caches matching the active filter.

Asks the database rather than the table: clear_filter() (and any
other _refresh_cache_list()) reloads the table asynchronously, so its
row count would still be stale right after the call."""
with get_session() as session:
return len(apply_filters_auto(session, self._build_active_filterset()))

def _open_found_updater(self) -> None:
if self._trip_planner_active():
self._warn_trip_planner_active()
Expand Down
9 changes: 9 additions & 0 deletions src/opensak/lang/cs.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@
# Tools menu
"action_settings": "&Nastavení…",
"action_found_update": "⟳ Aktualizovat nálezy z referenční databáze…",
# Lua macros (proof of concept)
"action_run_macro": "Spustit makro (Lua)…",
"macro_title": "Makro (Lua) — ověření konceptu",
"macro_btn_open": "Otevřít…",
"macro_btn_run": "▶ Spustit",
"macro_open_title": "Otevřít makro Lua",
"macro_done": "Makro dokončeno.",
"macro_error": "Chyba makra: {msg}",
"action_update_location": "Update waypoint locations…",
"action_download_boundaries": "Download boundary packs…",
"action_check_boundaries": "Check for boundary data updates…",
Expand Down Expand Up @@ -126,6 +134,7 @@

# ── Geocaching Tools menu ─────────────────────────────────────────────────
"menu_gc_tools": "&Nástroje",
"menu_macros": "&Makra",
"action_coord_converter": "⇄ &Převodník souřadnic…",
"action_projection": "📐 &Projekce souřadnic…",
"action_checksum": "🔢 &Ciferný součet…",
Expand Down
9 changes: 9 additions & 0 deletions src/opensak/lang/da.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@
# Funktioner-menu
"action_settings": "&Indstillinger…",
"action_found_update": "⟳ Opdater fund fra reference database…",
# Lua macros (proof of concept)
"action_run_macro": "Kør makro (Lua)…",
"macro_title": "Makro (Lua) — proof of concept",
"macro_btn_open": "Åbn…",
"macro_btn_run": "▶ Kør",
"macro_open_title": "Åbn Lua-makro",
"macro_done": "Makro færdig.",
"macro_error": "Makrofejl: {msg}",
"action_update_location": "Update waypoint locations…",
"action_download_boundaries": "Download grænsefladedata…",
"action_check_boundaries": "Søg efter opdateringer til grænsefladedata…",
Expand Down Expand Up @@ -126,6 +134,7 @@

# ── Geocaching Værktøjer-menu ─────────────────────────────────────────────
"menu_gc_tools": "&Værktøjer",
"menu_macros": "&Makroer",
"action_coord_converter": "⇄ &Koordinatkonverter…",
"action_projection": "📐 &Koordinatprojektion…",
"action_checksum": "🔢 &Tjeksum-beregner…",
Expand Down
9 changes: 9 additions & 0 deletions src/opensak/lang/de.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@
# Tools menu
"action_settings": "&Einstellungen…",
"action_found_update": "⟳ Funde aus Referenz-Datenbank aktualisieren",
# Lua macros (proof of concept)
"action_run_macro": "Makro ausführen (Lua)…",
"macro_title": "Makro (Lua) — Proof of Concept",
"macro_btn_open": "Öffnen…",
"macro_btn_run": "▶ Ausführen",
"macro_open_title": "Lua-Makro öffnen",
"macro_done": "Makro beendet.",
"macro_error": "Makrofehler: {msg}",
"action_update_location": "Standortdaten der Wegpunkte aktualisieren…",
"action_download_boundaries": "Grenzpolygon-Pakete herunterladen…",
"action_check_boundaries": "Nach Grenzpolygon-Aktualisierungen suchen…",
Expand Down Expand Up @@ -126,6 +134,7 @@

# ── Geocaching Tools menu ─────────────────────────────────────────────────
"menu_gc_tools": "&Tools",
"menu_macros": "&Makros",
"action_coord_converter": "⇄ &Koordinaten-Konverter…",
"action_projection": "📐 &Koordinaten-Projektion…",
"action_checksum": "🔢 &Koordinaten-Checksumme…",
Expand Down
9 changes: 9 additions & 0 deletions src/opensak/lang/de_CH.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,14 @@
# Tools menu
"action_settings": "&Einstellungen…",
"action_found_update": "⟳ Funde aus Referenz-Datenbank aktualisieren",
# Lua macros (proof of concept)
"action_run_macro": "Makro ausführen (Lua)…",
"macro_title": "Makro (Lua) — Proof of Concept",
"macro_btn_open": "Öffnen…",
"macro_btn_run": "▶ Ausführen",
"macro_open_title": "Lua-Makro öffnen",
"macro_done": "Makro beendet.",
"macro_error": "Makrofehler: {msg}",
"action_update_location": "Standortdaten der Wegpunkte aktualisieren…",
"action_download_boundaries": "Grenzpolygon-Pakete herunterladen…",
"action_check_boundaries": "Nach Grenzpolygon-Aktualisierungen suchen…",
Expand Down Expand Up @@ -127,6 +135,7 @@

# ── Geocaching Tools menu ─────────────────────────────────────────────────
"menu_gc_tools": "&Tools",
"menu_macros": "&Makros",
"action_coord_converter": "⇄ &Koordinaten-Konverter…",
"action_projection": "📐 &Koordinaten-Projektion…",
"action_checksum": "🔢 &Koordinaten-Checksumme…",
Expand Down
9 changes: 9 additions & 0 deletions src/opensak/lang/en.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@
# Tools menu
"action_settings": "&Settings…",
"action_found_update": "⟳ Update finds from reference database…",
# Lua macros (proof of concept)
"action_run_macro": "Run macro (Lua)…",
"macro_title": "Macro (Lua) — proof of concept",
"macro_btn_open": "Open…",
"macro_btn_run": "▶ Run",
"macro_open_title": "Open Lua macro",
"macro_done": "Macro finished.",
"macro_error": "Macro error: {msg}",
"action_update_location": "🌍 Update waypoint locations…",
"action_download_boundaries": "Download boundary packs…",
"action_check_boundaries": "Check for boundary data updates…",
Expand Down Expand Up @@ -126,6 +134,7 @@

# ── Geocaching Tools menu ─────────────────────────────────────────────────
"menu_gc_tools": "&Tools",
"menu_macros": "&Macros",
"action_coord_converter": "⇄ &Coordinate Converter…",
"action_projection": "📐 &Coordinate Projection…",
"action_checksum": "🔢 &Digit Checksum…",
Expand Down
9 changes: 9 additions & 0 deletions src/opensak/lang/es.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,14 @@
# Tools menu
"action_settings": "&Ajustes…",
"action_found_update": "⟳ Actualizar encontradas desde base de referencia…",
# Lua macros (proof of concept)
"action_run_macro": "Ejecutar macro (Lua)…",
"macro_title": "Macro (Lua) — prueba de concepto",
"macro_btn_open": "Abrir…",
"macro_btn_run": "▶ Ejecutar",
"macro_open_title": "Abrir macro Lua",
"macro_done": "Macro finalizada.",
"macro_error": "Error de macro: {msg}",
"action_update_location": "🌍 Actualizar ubicaciones de waypoints…",
"action_download_boundaries": "Descargar paquetes de límites…",
"action_check_boundaries": "Buscar actualizaciones de datos de límites…",
Expand Down Expand Up @@ -128,6 +136,7 @@

# ── Geocaching Tools menu ─────────────────────────────────────────────────
"menu_gc_tools": "&Herramientas",
"menu_macros": "&Macros",
"action_coord_converter": "⇄ &Convertidor de coordenadas…",
"action_projection": "📐 &Proyección de coordenadas…",
"action_checksum": "🔢 Suma de &dígitos…",
Expand Down
9 changes: 9 additions & 0 deletions src/opensak/lang/fr.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,14 @@
# Tools menu
"action_settings": "&Paramètres…",
"action_found_update": "⟳ Mettre à jour les caches trouvées depuis la base de données de référence…",
# Lua macros (proof of concept)
"action_run_macro": "Exécuter une macro (Lua)…",
"macro_title": "Macro (Lua) — preuve de concept",
"macro_btn_open": "Ouvrir…",
"macro_btn_run": "▶ Exécuter",
"macro_open_title": "Ouvrir une macro Lua",
"macro_done": "Macro terminée.",
"macro_error": "Erreur de macro : {msg}",
"action_update_location": "🌍 Mettre à jour les données de localisation des waypoints…",
"action_download_boundaries": "Télécharger les packs de frontières…",
"action_check_boundaries": "Rechercher des mises à jour des données de frontières…",
Expand Down Expand Up @@ -126,6 +134,7 @@

# ── Menu Outils de géocaching ─────────────────────────────────────────────
"menu_gc_tools": "&Outils géo",
"menu_macros": "&Macros",
"action_coord_converter": "⇄ &Convertisseur de coordonnées…",
"action_projection": "📐 &Projection de coordonnées…",
"action_checksum": "🔢 &Somme des chiffres…",
Expand Down
9 changes: 9 additions & 0 deletions src/opensak/lang/nl.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,14 @@
# Tools menu
"action_settings": "&Instellingen…",
"action_found_update": "⟳ Vondsten bijwerken vanuit referentiedatabase…",
# Lua macros (proof of concept)
"action_run_macro": "Macro uitvoeren (Lua)…",
"macro_title": "Macro (Lua) — proof of concept",
"macro_btn_open": "Openen…",
"macro_btn_run": "▶ Uitvoeren",
"macro_open_title": "Lua-macro openen",
"macro_done": "Macro voltooid.",
"macro_error": "Macrofout: {msg}",
"action_update_location": "🌍 Waypointlocaties bijwerken…",
"action_download_boundaries": "Grenspakketten downloaden…",
"action_check_boundaries": "Zoeken naar updates voor grensgegevens…",
Expand Down Expand Up @@ -129,6 +137,7 @@

# ── Geocaching Tools menu ─────────────────────────────────────────────────
"menu_gc_tools": "&Extra's",
"menu_macros": "&Macro's",
"action_coord_converter": "⇄ &Coördinaatconverter…",
"action_projection": "📐 &Coördinaatprojectie…",
"action_checksum": "🔢 &Cijfersom…",
Expand Down
Loading
Loading