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: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@ Thumbs.db
*.wav
*.exe
icon_*.png

# Local user guide
navod.html
51 changes: 39 additions & 12 deletions src/settings_dialog.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,6 @@
QGroupBox,
QHBoxLayout,
QLabel,
QLayout,
QLineEdit,
QListWidget,
QListWidgetItem,
Expand Down Expand Up @@ -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."""

Expand All @@ -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:
Expand All @@ -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):
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)

# ------------------------------------------------------------------
Expand Down Expand Up @@ -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
Expand All @@ -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)
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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,
Expand Down
12 changes: 10 additions & 2 deletions src/theme.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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())
Expand Down
Loading