aboutsummaryrefslogtreecommitdiffstats
path: root/sb_builder/packages.py
diff options
context:
space:
mode:
Diffstat (limited to 'sb_builder/packages.py')
-rw-r--r--sb_builder/packages.py108
1 files changed, 46 insertions, 62 deletions
diff --git a/sb_builder/packages.py b/sb_builder/packages.py
index 64032a2..30215e0 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 (QCheckBox, QDialog, QDialogButtonBox, QFormLayout, QHBoxLayout,
- QLabel, QLineEdit, QMessageBox, QPushButton, QScrollArea, QSplitter,
- QTableView, QVBoxLayout, QWidget)
+from PyQt6.QtWidgets import (QHBoxLayout, QLabel, QLineEdit, QMessageBox, QPushButton, 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
OVERLAYS = ("personal", "pentesting")
STATUSES = ("ok", "failed", "never", "outdated")
@@ -230,9 +230,8 @@ class PackagesTab(QWidget):
chips.addWidget(self.chips[name])
chips.addSpacing(16)
chips.addStretch()
- self.refresh_btn = QPushButton("Refresh")
- self.refresh_btn.clicked.connect(self.reload)
- chips.addWidget(self.refresh_btn)
+ self.count = QLabel()
+ chips.addWidget(self.count)
self.table = QTableView(sortingEnabled=True)
self.table.setModel(self.proxy)
@@ -248,18 +247,14 @@ class PackagesTab(QWidget):
alignment=Qt.AlignmentFlag.AlignTop)
self.detail.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
self.detail.setContentsMargins(12, 8, 12, 8)
- self.new_hint_btn = QPushButton("New hint")
- self.new_hint_btn.clicked.connect(self.new_hint)
- self.open_hint_btn = QPushButton("Open in Hints")
- self.open_hint_btn.clicked.connect(lambda: self.hints_requested.emit(self.current()["name"]))
- detail_bar = QHBoxLayout()
- detail_bar.addWidget(self.new_hint_btn)
- detail_bar.addWidget(self.open_hint_btn)
- detail_bar.addStretch()
+ # navigation, not an action: a link in the pane (UI guidelines)
+ self.open_hint_btn = QLabel('<a href="#">Open in Hints</a>')
+ self.open_hint_btn.setContentsMargins(12, 0, 12, 8)
+ self.open_hint_btn.linkActivated.connect(lambda _l: self.hints_requested.emit(self.current()["name"]))
pane = QWidget()
pane_layout = QVBoxLayout(pane)
pane_layout.addWidget(self.detail)
- pane_layout.addLayout(detail_bar)
+ pane_layout.addWidget(self.open_hint_btn)
pane_layout.addStretch()
scroll = QScrollArea(widgetResizable=True)
scroll.setWidget(pane)
@@ -269,37 +264,38 @@ class PackagesTab(QWidget):
split.addWidget(scroll)
split.setSizes([700, 400])
- self.count = QLabel()
- bar = QHBoxLayout()
- bar.addWidget(self.count)
- self.keys = []
- for label, slot, key in (("Select visible", self.select_visible, "Ctrl+A"),
- ("Clear", self.clear, "Ctrl+Shift+A")):
- b = QPushButton(label, toolTip=key)
- b.clicked.connect(slot)
- bar.addWidget(b)
- # in the search field Ctrl+A still selects its text: QLineEdit overrides it
- self.keys.append(QShortcut(QKeySequence(key), self, slot,
- context=Qt.ShortcutContext.WidgetWithChildrenShortcut))
- bar.addStretch()
- for action in ("build", "update", "rebuild", "remove"):
- b = QPushButton(action.capitalize())
- b.clicked.connect(lambda _c, a=action: self.act(a))
- bar.addWidget(b)
- for label, slot in (("Update all", self.update_all), ("Publish", self.publish)):
- b = QPushButton(label)
- b.clicked.connect(slot)
- bar.addWidget(b)
+ self.act_build = {
+ a: action(self, a.capitalize(), icon, fb, lambda a=a: self.act(a))
+ for a, icon, fb in (("build", "run-build", SP.SP_MediaPlay),
+ ("update", "system-software-update", SP.SP_BrowserReload),
+ ("rebuild", "view-refresh", SP.SP_BrowserReload),
+ ("remove", "edit-delete", SP.SP_TrashIcon))}
+ self.act_new_hint = action(self, "New hint", "document-new", SP.SP_FileIcon, self.new_hint)
+ self.actions = [*self.act_build.values(),
+ action(self, "Update all", "system-upgrade", SP.SP_ArrowUp, self.update_all),
+ self.act_new_hint,
+ action(self, "Publish", "document-send", SP.SP_DialogSaveButton, self.publish)]
+ self.view_actions = [
+ action(self, "Select visible", "edit-select-all", SP.SP_DialogYesButton,
+ self.select_visible, shortcut="Ctrl+A"),
+ action(self, "Clear", "edit-clear", SP.SP_DialogResetButton, self.clear,
+ shortcut="Ctrl+Shift+A"),
+ action(self, "Refresh", "view-refresh", SP.SP_BrowserReload, self.reload)]
+ # in the search field Ctrl+A still selects its text: QLineEdit overrides it
+ self.keys = [QShortcut(QKeySequence(key), self, slot,
+ context=Qt.ShortcutContext.WidgetWithChildrenShortcut)
+ for key, slot in (("Ctrl+A", self.select_visible), ("Ctrl+Shift+A", self.clear))]
+ self.loaded = False
layout = QVBoxLayout(self)
layout.addWidget(self.query)
layout.addLayout(chips)
- layout.addWidget(split)
- layout.addLayout(bar)
+ layout.addWidget(split, 1)
self.model.dataChanged.connect(lambda *_: self.update_count())
self.update_count()
def reload(self):
+ self.loaded = True
self.count.setText("loading…")
self.remote.call(self._loaded, "inventory")
@@ -317,6 +313,15 @@ class PackagesTab(QWidget):
def update_count(self):
self.count.setText(f"{self.proxy.rowCount():,} shown · {len(self.model.checked)} selected")
+ for a in self.act_build.values():
+ enable(a, None if self.model.checked else "tick packages first")
+ enable(self.act_new_hint, None if self.without_hint() else
+ "none of the ticked packages is without a hint")
+
+ def without_hint(self):
+ """Ticked packages that have no hint, sorted."""
+ return sorted(r["name"] for r in self.model.rows
+ if r["name"] in self.model.checked and not r["hint_path"])
def _toggle(self, index):
"""A click anywhere on a row ticks or unticks it."""
@@ -360,30 +365,9 @@ class PackagesTab(QWidget):
def show_detail(self, current, _previous):
row = current.data(Qt.ItemDataRole.UserRole) if current.isValid() else None
self.detail.setText(detail_html(row) if row else "")
- self.new_hint_btn.setVisible(bool(row) and not row["hint_path"])
self.open_hint_btn.setVisible(bool(row) and bool(row["hint_path"]))
- def ask_new_hint(self, name):
- """(version or "", no download) for a new hint, None on cancel."""
- d = QDialog(self, windowTitle=f"New hint: {name}")
- version = QLineEdit(placeholderText="the .info version")
- no_dl = QCheckBox('no download (DOWNLOAD="no")')
- buttons = QDialogButtonBox(QDialogButtonBox.StandardButton.Ok
- | QDialogButtonBox.StandardButton.Cancel)
- buttons.accepted.connect(d.accept)
- buttons.rejected.connect(d.reject)
- form = QFormLayout(d)
- form.addRow("version", version)
- form.addRow(no_dl)
- form.addRow(buttons)
- if d.exec() != QDialog.DialogCode.Accepted:
- return None
- return version.text().strip(), no_dl.isChecked()
-
def new_hint(self):
- row = self.current()
- answer = row and self.ask_new_hint(row["name"])
- if answer:
- version, no_dl = answer
- self.start_requested.emit(["hint-new", row["name"], *([version] if version else []),
- *(["--no-dl"] if no_dl else [])])
+ names = self.without_hint()
+ if names:
+ self.start_requested.emit(["hint-new", *names])