aboutsummaryrefslogtreecommitdiffstats
path: root/sb_builder
diff options
context:
space:
mode:
authorDanilo M. <danix@danix.xyz>2026-10-20 12:23:50 +0200
committerDanilo M. <danix@danix.xyz>2026-10-20 12:23:50 +0200
commitecf11290bda5a670ff520f62a96d85645ad95d82 (patch)
tree1e6390a28297e3fb5a9ba8c2fd5c8ee58de37a41 /sb_builder
parentaa4d36c541e603ed4d60ee1185806c51818eccc9 (diff)
downloadsb-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.py15
-rw-r--r--sb_builder/packages.py22
-rw-r--r--sb_builder/results.py6
-rw-r--r--sb_builder/ui.py46
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