Kleinigkeiten

This commit is contained in:
2026-06-29 15:04:02 +02:00
parent a8b6fa2fa6
commit 21d9936138
5 changed files with 356 additions and 15 deletions
+96 -15
View File
@@ -11,6 +11,7 @@ import asyncio
import base64
import json
import tkinter as tk
import tkinter.font as tkfont
from tkinter import ttk, filedialog, messagebox, scrolledtext
import threading
import os
@@ -26,6 +27,9 @@ except ImportError:
HAS_ASYNCSSH = False
SSH_CREDENTIALS_JSON = "ssh_zugangsdaten.json"
# ---------------------------------------------------------------------------
# Tooltip
# ---------------------------------------------------------------------------
@@ -215,6 +219,7 @@ class AstrometryGUI:
self.v_port = tk.IntVar(value=22)
self.v_user = tk.StringVar(value="eskimue")
self.v_pass = tk.StringVar(value="Preside")
self._load_startup_credentials()
self.v_local_file = tk.StringVar()
self.v_local_file.trace_add("write", self._on_local_file_changed)
@@ -275,6 +280,27 @@ class AstrometryGUI:
self.v_extra = tk.StringVar()
self.results_files: dict[str, dict] = {}
def _load_startup_credentials(self):
path = os.path.join(os.getcwd(), SSH_CREDENTIALS_JSON)
if not os.path.isfile(path):
return
try:
self._load_credentials_from_file(path)
except Exception:
pass
def _load_credentials_from_file(self, path: str):
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
if "host" in data:
self.v_host.set(str(data["host"]))
if "port" in data:
self.v_port.set(int(data["port"]))
if "user" in data:
self.v_user.set(str(data["user"]))
if "password" in data:
self.v_pass.set(str(data["password"]))
# -----------------------------------------------------------------------
# UI aufbauen
# -----------------------------------------------------------------------
@@ -345,6 +371,8 @@ class AstrometryGUI:
menubar = tk.Menu(self.root)
profile_menu = tk.Menu(menubar, tearoff=False)
profile_menu.add_command(label="Verbindung laden…", command=self._menu_load_credentials)
profile_menu.add_separator()
profile_menu.add_command(label="Profil speichern…", command=self._save_profile)
profile_menu.add_command(label="Profil laden…", command=self._load_profile)
menubar.add_cascade(label="Profil", menu=profile_menu)
@@ -399,6 +427,9 @@ class AstrometryGUI:
"Öffnet einen Datei-Dialog um die lokale Astro-Bild-Datei auszuwählen."
).grid(row=0, column=2, padx=4)
self.local_image_size_label = ttk.Label(uf, text="Größe: —", foreground="gray")
self.local_image_size_label.grid(row=0, column=3, sticky=tk.W, padx=4, pady=3)
ttk.Label(uf, text="Remote-Verzeichnis:").grid(row=1, column=0, sticky=tk.W, padx=4, pady=3)
tip(ttk.Entry(uf, textvariable=self.v_remote_dir),
"Verzeichnis auf dem Server wohin die Datei hochgeladen wird.\n"
@@ -772,6 +803,9 @@ class AstrometryGUI:
tip(ttk.Button(head, text="Aktualisieren", command=self._refresh_results_tab),
"Liest den Inhalt des aktuell eingestellten Remote-Verzeichnisses erneut vom Server ein."
).pack(side=tk.RIGHT, padx=4)
tip(ttk.Button(head, text="Remote-Inhalt löschen", command=self._delete_remote_dir),
"Löscht den Inhalt des aktuell eingestellten Remote-Verzeichnisses auf dem Server."
).pack(side=tk.RIGHT, padx=4)
tip(ttk.Button(head, text="Herunterladen", command=self._download_selected_result),
"Lädt die ausgewählte Remote-Datei per SFTP herunter."
).pack(side=tk.RIGHT, padx=4)
@@ -780,7 +814,7 @@ class AstrometryGUI:
self.results_info.pack(anchor=tk.W, pady=(0, 4))
cols = ("name", "size", "remote")
self.results_tree = ttk.Treeview(parent, columns=cols, show="headings", height=16)
self.results_tree = ttk.Treeview(parent, columns=cols, show="headings", height=16, selectmode="extended")
self.results_tree.heading("name", text="Datei")
self.results_tree.heading("size", text="Größe")
self.results_tree.heading("remote", text="Remote-Pfad")
@@ -1060,6 +1094,20 @@ class AstrometryGUI:
entry["remote"],
),
)
self._autosize_results_columns()
def _autosize_results_columns(self):
body_font = tkfont.nametofont("TkDefaultFont")
head_font = tkfont.nametofont("TkHeadingFont")
padding = 24
for col in ("name", "size", "remote"):
header = self.results_tree.heading(col, "text") or col
width = head_font.measure(header) + padding
for item_id in self.results_tree.get_children():
value = self.results_tree.set(item_id, col)
width = max(width, body_font.measure(str(value)) + padding)
self.results_tree.column(col, width=width)
def _clear_wait_cursor(self):
self.root.config(cursor="")
@@ -1084,13 +1132,16 @@ class AstrometryGUI:
def _download_selected_result(self):
selection = self.results_tree.selection()
if not selection:
messagebox.showwarning("Keine Datei ausgewählt", "Bitte zuerst eine Datei im Ergebnis-Tab auswählen.")
messagebox.showwarning("Keine Datei ausgewählt", "Bitte zuerst eine oder mehrere Dateien im Ergebnis-Tab auswählen.")
return
entry = self.results_files.get(selection[0])
if not entry:
messagebox.showwarning("Datei fehlt", "Die ausgewählte Datei konnte nicht gefunden werden.")
entries = []
for item_id in selection:
entry = self.results_files.get(item_id)
if entry:
entries.append(entry)
if not entries:
messagebox.showwarning("Datei fehlt", "Die ausgewählten Dateien konnten nicht gefunden werden.")
return
suggested = os.path.basename(entry["remote"])
source_dir = os.path.dirname(self.v_local_file.get().strip()) if self.v_local_file.get().strip() else ""
initial_dir = source_dir if source_dir and os.path.isdir(source_dir) else os.getcwd()
local_dir = filedialog.askdirectory(
@@ -1099,20 +1150,25 @@ class AstrometryGUI:
)
if not local_dir:
return
local_path = os.path.normpath(os.path.join(local_dir, suggested))
self._submit(self._async_download_result(entry["remote"], local_path))
downloads = []
for entry in entries:
suggested = os.path.basename(entry["remote"])
local_path = os.path.normpath(os.path.join(local_dir, suggested))
downloads.append((entry["remote"], local_path))
self._submit(self._async_download_results(downloads))
async def _async_download_result(self, remote_path: str, local_path: str):
self._log(f"[INFO] Lade Ergebnis herunter: {remote_path} → {local_path}\n", "info")
async def _async_download_results(self, downloads: list[tuple[str, str]]):
try:
os.makedirs(os.path.dirname(local_path), exist_ok=True)
ssh = self._make_ssh()
await ssh.connect()
await ssh.download(remote_path, local_path)
for remote_path, local_path in downloads:
self._log(f"[INFO] Lade Ergebnis herunter: {remote_path} → {local_path}\n", "info")
os.makedirs(os.path.dirname(local_path), exist_ok=True)
await ssh.download(remote_path, local_path)
if not os.path.isfile(local_path):
raise FileNotFoundError(f"Datei nach Download nicht gefunden: {local_path}")
self._log(f"[OK] Download abgeschlossen: {local_path}\n", "ok")
await ssh.close()
if not os.path.isfile(local_path):
raise FileNotFoundError(f"Datei nach Download nicht gefunden: {local_path}")
self._log(f"[OK] Download abgeschlossen: {local_path}\n", "ok")
except Exception as exc:
self._log(f"[FEHLER] Download: {exc}\n", "error")
@@ -1159,6 +1215,15 @@ class AstrometryGUI:
self.v_new_fits.set(auto_path)
def _on_local_file_changed(self, *_):
path = self.v_local_file.get().strip()
if not path or not os.path.isfile(path):
self.local_image_size_label.config(text="Größe: —")
else:
try:
width, height = self._read_image_size(path)
self.local_image_size_label.config(text=f"Größe: {width} × {height}")
except Exception:
self.local_image_size_label.config(text="Größe: unbekannt")
if self.v_plot_scale_auto.get():
self._recalculate_plot_scale()
@@ -1285,6 +1350,8 @@ class AstrometryGUI:
)
if path:
self.v_local_file.set(path)
if messagebox.askyesno("Datei hochladen", "Soll die ausgewählte Datei jetzt direkt auf den Server hochgeladen werden?"):
self._upload()
def _refresh_cmd(self):
cmd = self._build_command()
@@ -1385,6 +1452,20 @@ class AstrometryGUI:
except Exception as exc:
messagebox.showerror("Profil laden", f"Laden fehlgeschlagen:\n{exc}")
def _menu_load_credentials(self):
path = filedialog.askopenfilename(
title="Zugangsdaten laden",
initialdir=os.getcwd(),
filetypes=[("JSON-Dateien", "*.json"), ("Alle Dateien", "*.*")],
)
if not path:
return
try:
self._load_credentials_from_file(path)
self._log(f"[OK] Zugangsdaten geladen: {path}\n", "ok")
except Exception as exc:
messagebox.showerror("Zugangsdaten laden", f"Laden fehlgeschlagen:\n{exc}")
# -----------------------------------------------------------------------
# Log (thread-sicher via Queue)
# -----------------------------------------------------------------------