From 4076b081cee53c690b444a217c64f6d563ac8ad0 Mon Sep 17 00:00:00 2001 From: DavidHruby1 Date: Fri, 16 Oct 2026 22:09:21 +0200 Subject: [PATCH 1/2] chore: ignore local user guide Co-Authored-By: Claude Opus 5.5 --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 60288db..0eca530 100644 --- a/.gitignore +++ b/.gitignore @@ -31,3 +31,6 @@ Thumbs.db *.wav *.exe icon_*.png + +# Local user guide +navod.html From edf3cc09ad4a190f5ad7e8a579f12e44867ef0f3 Mon Sep 17 00:00:00 2001 From: DavidHruby1 Date: Fri, 16 Oct 2026 22:27:59 +0200 Subject: [PATCH 2/2] fix: tighten Hush settings dialog and tray menu polish - Settings dialog is a fixed size ~20% below its natural size; long pages scroll. Import from .env / Reset to Defaults move to the sidebar bottom so the window can narrow. - Pages are pinned to the top so QFormLayout no longer spreads spare height between rows, and hidden damage-repair rows collapse (setRowVisible). - Tray submenu chevrons end where the separators end. - App-wide window icon (Hush mark) instead of the default title bar icon. Co-Authored-By: Claude Opus 5.5 --- src/settings_dialog.py | 51 ++++++++++++++++++++++++++++++++---------- src/theme.py | 12 ++++++++-- 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/src/settings_dialog.py b/src/settings_dialog.py index 12f72c7..3fc8e7c 100644 --- a/src/settings_dialog.py +++ b/src/settings_dialog.py @@ -49,7 +49,6 @@ QGroupBox, QHBoxLayout, QLabel, - QLayout, QLineEdit, QListWidget, QListWidgetItem, @@ -252,6 +251,9 @@ def paintEvent(self, event) -> None: } +_SIZE_FACTOR = 0.8 + + class _PageScroll(QScrollArea): """Scrolls a settings page; its size hint follows the content up to the screen.""" @@ -262,9 +264,16 @@ def __init__(self, content: QWidget) -> None: self.setWidgetResizable(True) self.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff) self.viewport().setAutoFillBackground(False) - content.setObjectName("hushPageBody") - self.setWidget(content) - content.installEventFilter(self) + # Pin the page to the top: a QFormLayout stretched to a taller viewport + # spreads the spare height between its rows instead of leaving it below. + body = QWidget() + body.setObjectName("hushPageBody") + column = QVBoxLayout(body) + column.setContentsMargins(0, 0, 0, 0) + column.addWidget(content) + column.addStretch(1) + self.setWidget(body) + body.installEventFilter(self) def eventFilter(self, watched, event) -> bool: if watched is self.widget() and event.type() == QEvent.Type.LayoutRequest: @@ -282,7 +291,8 @@ def sizeHint(self) -> QSize: return QSize(width + bar + 4, max(320, min(max(height, hint.height()), cap))) def minimumSizeHint(self) -> QSize: - return self.sizeHint() + bar = self.verticalScrollBar().sizeHint().width() + return QSize(self.widget().minimumSizeHint().width() + bar + 4, 200) class _SidebarTabs(QObject): @@ -324,7 +334,7 @@ def addTab(self, content: QWidget, text: str) -> int: layout.addSpacing(6) layout.addWidget(sub) layout.addSpacing(22) - content.setMinimumWidth(560) + content.setMinimumWidth(420) content.setContentsMargins(0, 0, 12, 16) layout.addWidget(_PageScroll(content), 1) self.stack.addWidget(page) @@ -495,7 +505,13 @@ def __init__( self._rewrite_error: str | None = None self._build_ui() - self.layout().setSizeConstraint(QLayout.SetFixedSize) + # A fixed size a fifth below the tallest page's natural size; pages that + # do not fit scroll instead of growing the dialog. + natural = self.sizeHint() + self.setFixedSize( + max(self.minimumSizeHint().width(), round(natural.width() * _SIZE_FACTOR)), + round(natural.height() * _SIZE_FACTOR), + ) self._populate(self._working) # ------------------------------------------------------------------ @@ -554,6 +570,9 @@ def _build_ui(self) -> None: btn_import.clicked.connect(self._on_import_env) btn_reset = theme.set_role(QPushButton("Reset to Defaults"), "ghost") btn_reset.clicked.connect(self._on_reset) + for button in (btn_import, btn_reset): + button.setStyleSheet("text-align: left;") + side.addWidget(button) self._button_box = QDialogButtonBox( QDialogButtonBox.StandardButton.Ok @@ -570,9 +589,6 @@ def _build_ui(self) -> None: footer.setObjectName("hushFooter") bottom = QHBoxLayout(footer) bottom.setContentsMargins(32, 14, 24, 14) - bottom.setSpacing(4) - bottom.addWidget(btn_import) - bottom.addWidget(btn_reset) bottom.addStretch() bottom.addWidget(self._button_box) main.addWidget(footer) @@ -995,7 +1011,9 @@ def _build_profiles_tab(self) -> None: self._rewrite_replace_btn = QPushButton("Replace damaged catalog…") self._rewrite_reset_btn.clicked.connect(lambda: self._on_catalog_repair("Reset")) self._rewrite_replace_btn.clicked.connect(lambda: self._on_catalog_repair("Replace")) - form.addRow(_button_row(self._rewrite_reset_btn, self._rewrite_replace_btn)) + self._rewrite_repair_row = _button_row(self._rewrite_reset_btn, self._rewrite_replace_btn) + form.addRow(self._rewrite_repair_row) + self._rewrite_form = form self._rewrite_mode_combo = QComboBox() self._rewrite_mode_combo.addItem("Manual", "manual") self._rewrite_mode_combo.addItem("Automatic (application mappings)", "automatic") @@ -1301,6 +1319,9 @@ def _update_catalog_damage_ui(self) -> None: damaged = self._working._rewrite_catalog_corrupt_original is not None for widget in (self._rewrite_warning, self._rewrite_reset_btn, self._rewrite_replace_btn): widget.setVisible(damaged) + # Collapse the rows too; hidden widgets alone still leave form spacing behind. + for row in (self._rewrite_warning, self._rewrite_repair_row): + self._rewrite_form.setRowVisible(row, damaged) self._tabs.setTabText(5, "Profiles ⚠" if damaged else "Profiles") for widget in ( self._rewrite_mode_combo, @@ -1448,7 +1469,11 @@ def _build_vocabulary_tab(self) -> None: self._vocabulary_reset_btn.clicked.connect(lambda: self._on_vocabulary_repair("Reset")) self._vocabulary_replace_btn = QPushButton("Replace damaged vocabulary…") self._vocabulary_replace_btn.clicked.connect(lambda: self._on_vocabulary_repair("Replace")) - form.addRow(_button_row(self._vocabulary_reset_btn, self._vocabulary_replace_btn)) + self._vocabulary_repair_row = _button_row( + self._vocabulary_reset_btn, self._vocabulary_replace_btn + ) + form.addRow(self._vocabulary_repair_row) + self._vocabulary_form = form _section(form, "Terms") self._vocabulary_list = QListWidget() self._vocabulary_list.setMaximumHeight(200) @@ -1483,6 +1508,8 @@ def _update_vocabulary_damage_ui(self) -> None: self._vocabulary_warning.setVisible(damaged) self._vocabulary_reset_btn.setVisible(damaged) self._vocabulary_replace_btn.setVisible(damaged) + for row in (self._vocabulary_warning, self._vocabulary_repair_row): + self._vocabulary_form.setRowVisible(row, damaged) self._tabs.setTabText(4, "Vocabulary ⚠" if damaged else "Vocabulary") for widget in ( self._vocabulary_list, diff --git a/src/theme.py b/src/theme.py index e8429ba..cd87f6f 100644 --- a/src/theme.py +++ b/src/theme.py @@ -18,12 +18,15 @@ QFont, QFontDatabase, QGuiApplication, + QIcon, QPainter, QPainterPath, QPalette, ) from PySide6.QtWidgets import QAbstractSpinBox, QApplication, QMenu, QWidget +from src.icons import TrayState, get_icon_pixmap + log = logging.getLogger(__name__) ASSETS_DIR = Path(__file__).resolve().parent / "assets" @@ -337,7 +340,10 @@ def _palette() -> QPalette: QMenu::item:selected { background: $hover; } QMenu::item:disabled { color: $text_3; } QMenu::separator { height: 1px; background: $line; margin: 5px 8px; } -QMenu::right-arrow { image: url("$chevron_right"); width: 12px; height: 12px; } +/* Pull the chevron in so its tip ends where the separators end (8px inset). */ +QMenu::right-arrow { + image: url("$chevron_right"); width: 12px; height: 12px; position: relative; right: 4px; +} QMenu QCheckBox, QMenu QRadioButton { padding: 7px 12px; border-radius: 7px; background: transparent; } @@ -466,12 +472,14 @@ def eventFilter(self, watched, event) -> bool: def apply_theme(app: QApplication) -> None: - """Install fonts, palette, stylesheet and native polish on *app* (idempotent).""" + """Install fonts, palette, stylesheet, window icon and native polish on *app* (idempotent).""" global _polisher if not isinstance(app, QApplication): return # e.g. a test double standing in for the application load_fonts() app.setStyle("Fusion") + # Title bars of every window (settings, recovery, message boxes) show the mark. + app.setWindowIcon(QIcon(get_icon_pixmap(TrayState.IDLE))) app.setFont(ui_font(13)) app.setPalette(_palette()) app.setStyleSheet(stylesheet())