diff options
| author | Danilo M. <danix@danix.xyz> | 2026-10-20 12:23:50 +0200 |
|---|---|---|
| committer | Danilo M. <danix@danix.xyz> | 2026-10-20 12:23:50 +0200 |
| commit | ecf11290bda5a670ff520f62a96d85645ad95d82 (patch) | |
| tree | 1e6390a28297e3fb5a9ba8c2fd5c8ee58de37a41 /sb_builder | |
| parent | aa4d36c541e603ed4d60ee1185806c51818eccc9 (diff) | |
| download | sb-builder-ecf11290bda5a670ff520f62a96d85645ad95d82.tar.gz sb-builder-ecf11290bda5a670ff520f62a96d85645ad95d82.zip | |
Filter bars: chips read as buttons, status dots, group labels
Chips get a button background and border, a dot in the color the table
uses for that status, and a muted label per group (overlay, status,
state, result). Search fields get a search icon and a clear button.
Phase 2 spec: why the signing key locks after the GUI quits.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'sb_builder')
| -rw-r--r-- | sb_builder/hints_tab.py | 15 | ||||
| -rw-r--r-- | sb_builder/packages.py | 22 | ||||
| -rw-r--r-- | sb_builder/results.py | 6 | ||||
| -rw-r--r-- | sb_builder/ui.py | 46 |
4 files changed, 62 insertions, 27 deletions
diff --git a/sb_builder/hints_tab.py b/sb_builder/hints_tab.py index feba94d..071e304 100644 --- a/sb_builder/hints_tab.py +++ b/sb_builder/hints_tab.py @@ -21,14 +21,14 @@ import os from PyQt6.QtCore import Qt, pyqtSignal from PyQt6.QtGui import QColor, QFontDatabase, QKeySequence, QShortcut, QSyntaxHighlighter -from PyQt6.QtWidgets import (QDialog, QHBoxLayout, QLabel, QLineEdit, QListWidget, - QListWidgetItem, QMessageBox, QPlainTextEdit, QPushButton, QSplitter, - QTableWidget, QTableWidgetItem, QVBoxLayout, QWidget) +from PyQt6.QtWidgets import (QDialog, QHBoxLayout, QLabel, QListWidget, QListWidgetItem, + QMessageBox, QPlainTextEdit, QPushButton, QSplitter, QTableWidget, + QTableWidgetItem, QVBoxLayout, QWidget) from . import upstream from .dashboard import STATUS -from .packages import chip, load_rows -from .ui import SP, action, enable +from .packages import load_rows +from .ui import SP, action, chip, enable, group_label, search_field STATES = ("outdated", "current", "no stanza", "not checked", "nvchecker error") STATE_COLOR = {"outdated": STATUS["aborted"], "current": STATUS["ok"], @@ -51,12 +51,13 @@ class HintsTab(QWidget): self.checker = upstream.Checker(self) self.checker.done.connect(self._checked) - self.query = QLineEdit(placeholderText="search package") + self.query = search_field("search package") self.query.textChanged.connect(self.refilter) self.chips = {} chips = QHBoxLayout() + chips.addWidget(group_label("state")) for name in STATES: - self.chips[name] = chip(name) + self.chips[name] = chip(name, STATE_COLOR.get(name)) self.chips[name].toggled.connect(self.refilter) chips.addWidget(self.chips[name]) chips.addStretch() diff --git a/sb_builder/packages.py b/sb_builder/packages.py index 30215e0..a4d1f1b 100644 --- a/sb_builder/packages.py +++ b/sb_builder/packages.py @@ -20,13 +20,13 @@ import html from PyQt6.QtCore import QAbstractTableModel, QModelIndex, QSortFilterProxyModel, Qt, pyqtSignal from PyQt6.QtGui import QColor, QKeySequence, QShortcut -from PyQt6.QtWidgets import (QHBoxLayout, QLabel, QLineEdit, QMessageBox, QPushButton, QScrollArea, - QSplitter, QTableView, QVBoxLayout, QWidget) +from PyQt6.QtWidgets import (QHBoxLayout, QLabel, QMessageBox, QScrollArea, QSplitter, QTableView, + QVBoxLayout, QWidget) from . import hints as hintlib from .dashboard import STATUS from .fmt import NA, fmt_time -from .ui import SP, action, enable +from .ui import SP, action, chip, enable, group_label, search_field OVERLAYS = ("personal", "pentesting") STATUSES = ("ok", "failed", "never", "outdated") @@ -159,15 +159,6 @@ class Filter(QSortFilterProxyModel): return matches(row, self.terms, self.overlays, self.statuses) -def chip(text): - b = QPushButton(text, checkable=True) - b.setStyleSheet("QPushButton { border: 1px solid palette(mid); border-radius: 10px;" - " padding: 2px 10px; }" - "QPushButton:checked { background: palette(highlight);" - " color: palette(highlighted-text); }") - return b - - def detail_html(row): """The detail pane for one package: facts, dependencies, then its hints.""" view = hintlib.interpret(row["hints"], row["name"], row["info_version"], row["requires"]) @@ -219,13 +210,14 @@ class PackagesTab(QWidget): self.proxy = Filter() self.proxy.setSourceModel(self.model) - self.query = QLineEdit(placeholderText="search (cat: maint: req: status:)") + self.query = search_field("search (cat: maint: req: status:)") self.query.textChanged.connect(self.refilter) self.chips = {} chips = QHBoxLayout() - for group in (("personal", "pentesting", "upstream"), STATUSES): + for label, group in (("overlay", ("personal", "pentesting", "upstream")), ("status", STATUSES)): + chips.addWidget(group_label(label)) for name in group: - self.chips[name] = chip(name) + self.chips[name] = chip(name, STATUS_COLOR.get(name) if group is STATUSES else None) self.chips[name].toggled.connect(self.refilter) chips.addWidget(self.chips[name]) chips.addSpacing(16) diff --git a/sb_builder/results.py b/sb_builder/results.py index 21fdcc3..3c9df82 100644 --- a/sb_builder/results.py +++ b/sb_builder/results.py @@ -26,8 +26,7 @@ from PyQt6.QtWidgets import (QDialog, QHBoxLayout, QLabel, QPlainTextEdit, QSpli from .dashboard import STATUS from .fmt import NA, fmt_time -from .packages import chip -from .ui import SP, action, enable +from .ui import SP, action, chip, enable, group_label KINDS = ("ok", "failed", "bad", "aborted", "removed") ANSI = re.compile(r"\x1b\[[0-9;?]*[A-Za-z]") @@ -68,8 +67,9 @@ class ResultsTab(QWidget): self.remote = remote self.rows = [] self.viewers = [] - self.chips = {k: chip(k) for k in KINDS} + self.chips = {k: chip(k, STATUS.get(k)) for k in KINDS} bar = QHBoxLayout() + bar.addWidget(group_label("result")) for c in self.chips.values(): c.toggled.connect(self.refilter) bar.addWidget(c) diff --git a/sb_builder/ui.py b/sb_builder/ui.py index 087d1a7..82ddc6d 100644 --- a/sb_builder/ui.py +++ b/sb_builder/ui.py @@ -21,8 +21,9 @@ does, in workflow order) and `view_actions` for the small one (selection and view controls). The main window shows the current tab's lists. """ -from PyQt6.QtGui import QAction, QIcon -from PyQt6.QtWidgets import QStyle +from PyQt6.QtCore import Qt +from PyQt6.QtGui import QAction, QColor, QIcon, QPainter, QPalette, QPixmap +from PyQt6.QtWidgets import QApplication, QLabel, QLineEdit, QPushButton, QStyle def action(parent, text, icon, fallback, slot=None, checkable=False, shortcut=None): @@ -44,3 +45,44 @@ def enable(a, reason): SP = QStyle.StandardPixmap + + +# ── filter bars: search field, group labels, chips ────────────────────────── + +CHIP_STYLE = ("QPushButton { background: palette(button); border: 1px solid palette(mid);" + " border-radius: 11px; padding: 3px 12px; }" + "QPushButton:hover { border-color: palette(highlight); }" + "QPushButton:checked { background: palette(highlight);" + " color: palette(highlighted-text); border-color: palette(highlight); }") + + +def chip(text, color=None): + """A filter toggle that reads as a button; color: a dot, the one the table uses.""" + b = QPushButton(text, checkable=True) + b.setStyleSheet(CHIP_STYLE) + if color: + pix = QPixmap(10, 10) + pix.fill(Qt.GlobalColor.transparent) + p = QPainter(pix) + p.setRenderHint(QPainter.RenderHint.Antialiasing) + p.setPen(Qt.PenStyle.NoPen) + p.setBrush(QColor(color)) + p.drawEllipse(0, 0, 10, 10) + p.end() + b.setIcon(QIcon(pix)) + return b + + +def group_label(text): + """The muted name in front of a group of chips.""" + label = QLabel(text + ":") + label.setForegroundRole(QPalette.ColorRole.PlaceholderText) + label.setContentsMargins(4, 0, 6, 0) + return label + + +def search_field(placeholder): + q = QLineEdit(placeholderText=placeholder, clearButtonEnabled=True) + icon = QIcon.fromTheme("edit-find", QApplication.style().standardIcon(SP.SP_FileDialogContentsView)) + q.addAction(icon, QLineEdit.ActionPosition.LeadingPosition) + return q |
