feat: settings dialog for update config (gear entry)

A low-key "⚙ 配置" button at the right end of the flow-tab row opens a settings
dialog to edit update_source / update_user / update_pass, with a test-connection
button that distinguishes "reachable & up to date" from "cannot connect".

- settings_dialog.py: pure-UI QDialog; password mask + show; test runs off the
  UI thread via load_manifest and reports the result
- update_service: add load_manifest() (raises on failure, unlike check_for_update)
- main_window: gear button, _open_settings persists via save_config and re-runs
  the update check; _update_found connected once to avoid duplicate banners
- tests: +2 for load_manifest

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-18 11:09:29 +08:00
co-authored by Claude Opus 4.8
parent 295dc803c6
commit 2d2c6c839d
5 changed files with 222 additions and 11 deletions
+43 -2
View File
@@ -6,6 +6,7 @@ from pathlib import Path
from PySide6.QtCore import Qt, QUrl, Signal
from PySide6.QtGui import QDesktopServices
from PySide6.QtWidgets import (
QDialog,
QHBoxLayout,
QLabel,
QMainWindow,
@@ -26,6 +27,7 @@ from app.widgets.export_panel import ExportPanel
from app.widgets.image_canvas import ImageCanvas
from app.widgets.image_list_panel import ImageListPanel
from app.widgets.queue_panel import QueuePanel
from app.widgets.settings_dialog import SettingsDialog
from app.widgets.template_panel import TemplatePanel
from app.widgets.transform_panel import TransformPanel
@@ -108,8 +110,34 @@ class MainWindow(QMainWindow):
layout.addWidget(self._tab_bar)
layout.addStretch()
# Right-aligned utility entry — not a numbered workflow step (docs/07 §4.3)
self._settings_btn = QPushButton("⚙ 配置")
self._settings_btn.setObjectName("settingsBtn")
self._settings_btn.setCursor(Qt.PointingHandCursor)
self._settings_btn.clicked.connect(self._open_settings)
layout.addWidget(self._settings_btn)
return container
def _open_settings(self):
"""Open the settings dialog; persist + re-check on save (docs/07 §4.4)."""
dlg = SettingsDialog(
self,
update_source=self._config.get("update_source", ""),
update_user=self._config.get("update_user", ""),
update_pass=self._config.get("update_pass", ""),
current_version=APP_VERSION,
)
if dlg.exec() == QDialog.Accepted:
self._config.update(dlg.values())
save_config(self._config)
self._refresh_update_check()
def _refresh_update_check(self):
"""Re-run the update check after the source/credentials changed."""
self._update_banner.setVisible(False)
self._start_update_check()
# ------------------------------------------------------------------
# Update notification (stage 2: notify-only, see docs/10-lan-update.md)
# ------------------------------------------------------------------
@@ -117,6 +145,7 @@ class MainWindow(QMainWindow):
def _create_update_banner(self):
"""A thin info bar shown when a newer version is found. Hidden by default."""
self._update_info = None
self._update_found.connect(self._on_update_found) # connect once
bar = QWidget()
bar.setObjectName("updateBanner")
@@ -149,13 +178,16 @@ class MainWindow(QMainWindow):
return bar
def _start_update_check(self):
"""Check the configured update source for a newer version, off the UI thread."""
"""Check the configured update source for a newer version, off the UI thread.
Safe to call again after the settings change; the result signal is
connected once in _create_update_banner, not here.
"""
source = self._config.get("update_source", "")
if not source:
return
user = self._config.get("update_user", "")
password = self._config.get("update_pass", "")
self._update_found.connect(self._on_update_found)
def worker():
try:
@@ -636,6 +668,15 @@ class MainWindow(QMainWindow):
QTabBar#flowTabBar::tab:disabled {
color: #bbbbbb;
}
#settingsBtn {
font-family: "Microsoft YaHei", "Segoe UI", sans-serif;
font-size: 12px;
color: #666666;
background: transparent;
border: none;
padding: 4px 14px;
}
#settingsBtn:hover { color: #0078d4; background: #e8f0fb; }
/* Work splitter */
QSplitter#workSplitter::handle {