v0.06: replace QMenu with custom BookmarkPanel widget
QMenu has z-order and event-grab issues under Openbox/X11 — the menu was being created successfully but either appeared behind Chrome or was dismissed instantly by the mouse-release event. Replace with BookmarkPanel: a QWidget using Qt.Tool | WindowStaysOnTopHint | FramelessWindowHint (same flags as the toolbar itself). Since the toolbar already stays above Chrome, the panel does too. Features: - Scrollable list of all bookmarks with folder headers - Closes when clicking anywhere outside - Manage Bookmarks entry at the bottom - Toggle open/close on hamburger button
This commit is contained in:
+118
-41
@@ -353,6 +353,117 @@ class ManagerDialog(QDialog):
|
||||
self.tree.setCurrentItem(sel); self._commit()
|
||||
|
||||
|
||||
# ── Bookmark dropdown panel ───────────────────────────────────────
|
||||
class BookmarkPanel(QWidget):
|
||||
"""
|
||||
Custom dropdown that replaces QMenu for bookmarks.
|
||||
Uses the same Qt.Tool | WindowStaysOnTopHint flags as the toolbar so it
|
||||
always appears above Chrome, unlike QMenu which has z-order issues.
|
||||
"""
|
||||
def __init__(self, cdp, overlay):
|
||||
super().__init__()
|
||||
self.cdp = cdp
|
||||
self.overlay = overlay
|
||||
self.setWindowFlags(
|
||||
Qt.Tool | Qt.WindowStaysOnTopHint | Qt.FramelessWindowHint)
|
||||
self.setAttribute(Qt.WA_ShowWithoutActivating)
|
||||
self.setStyleSheet(f"""
|
||||
QWidget {{ background: #1e3a5c; }}
|
||||
QPushButton {{ background: transparent; color: white; border: none;
|
||||
text-align: left; padding: 8px 20px;
|
||||
font-size: 17px; border-radius: 6px; }}
|
||||
QPushButton:hover {{ background: #3a6da4; }}
|
||||
QPushButton:pressed {{ background: #222; }}
|
||||
QLabel {{ color: #aac8e8; padding: 6px 20px 2px 20px;
|
||||
font-size: 14px; font-weight: bold; }}
|
||||
""")
|
||||
QApplication.instance().installEventFilter(self)
|
||||
|
||||
def rebuild(self, items):
|
||||
# Remove old layout
|
||||
old = self.layout()
|
||||
if old:
|
||||
while old.count():
|
||||
w = old.takeAt(0).widget()
|
||||
if w:
|
||||
w.deleteLater()
|
||||
QWidget().setLayout(old)
|
||||
|
||||
scroll_content = QWidget()
|
||||
scroll_content.setStyleSheet("background: #1e3a5c;")
|
||||
vbox = QVBoxLayout(scroll_content)
|
||||
vbox.setContentsMargins(6, 6, 6, 6)
|
||||
vbox.setSpacing(1)
|
||||
|
||||
self._add_items(vbox, items, depth=0)
|
||||
|
||||
sep = QFrame()
|
||||
sep.setFrameShape(QFrame.HLine)
|
||||
sep.setStyleSheet("background: #3a6da4; margin: 4px 8px;")
|
||||
sep.setFixedHeight(1)
|
||||
vbox.addWidget(sep)
|
||||
|
||||
mgr = QPushButton("⚙ Manage Bookmarks…")
|
||||
mgr.setFont(QFont("Arial", 16))
|
||||
mgr.setMinimumHeight(48)
|
||||
mgr.setCursor(Qt.PointingHandCursor)
|
||||
mgr.clicked.connect(self._on_manage)
|
||||
vbox.addWidget(mgr)
|
||||
|
||||
scroll = QScrollArea()
|
||||
scroll.setWidgetResizable(True)
|
||||
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
scroll.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||
scroll.setStyleSheet("background: transparent; border: none;")
|
||||
scroll.setWidget(scroll_content)
|
||||
|
||||
outer = QVBoxLayout(self)
|
||||
outer.setContentsMargins(2, 2, 2, 2)
|
||||
outer.setSpacing(0)
|
||||
outer.addWidget(scroll)
|
||||
|
||||
# Size: cap height at 80 % of screen below toolbar
|
||||
scroll_content.adjustSize()
|
||||
ph = scroll_content.sizeHint().height() + 16
|
||||
pw = max(scroll_content.sizeHint().width() + 16, 340)
|
||||
max_h = SCREEN_H - TOOLBAR_H - 20
|
||||
self.resize(pw, min(ph, max_h))
|
||||
self.move(SCREEN_W - pw - 4, TOOLBAR_H)
|
||||
|
||||
def _add_items(self, vbox, items, depth):
|
||||
indent = " " * depth
|
||||
for item in items:
|
||||
if is_folder(item):
|
||||
lbl = QLabel(f"{indent}▸ {item['name']}")
|
||||
lbl.setFont(QFont("Arial", 14, QFont.Bold))
|
||||
vbox.addWidget(lbl)
|
||||
self._add_items(vbox, item["children"], depth + 1)
|
||||
else:
|
||||
btn = QPushButton(f"{indent} {item['name']}")
|
||||
btn.setFont(QFont("Arial", 17))
|
||||
btn.setMinimumHeight(46)
|
||||
btn.setCursor(Qt.PointingHandCursor)
|
||||
url = item["url"]
|
||||
btn.clicked.connect(lambda _, u=url: self._nav(u))
|
||||
vbox.addWidget(btn)
|
||||
|
||||
def _nav(self, url):
|
||||
self.hide()
|
||||
self.cdp.navigate(url)
|
||||
|
||||
def _on_manage(self):
|
||||
self.hide()
|
||||
self.overlay.open_manager()
|
||||
|
||||
def eventFilter(self, obj, event):
|
||||
from PyQt5.QtCore import QEvent
|
||||
if event.type() == QEvent.MouseButtonPress and self.isVisible():
|
||||
gp = event.globalPos()
|
||||
if not self.geometry().contains(gp):
|
||||
self.hide()
|
||||
return False
|
||||
|
||||
|
||||
# ── Floating toolbar overlay ──────────────────────────────────────
|
||||
class KioskOverlay(QWidget):
|
||||
def __init__(self, cdp):
|
||||
@@ -371,6 +482,7 @@ class KioskOverlay(QWidget):
|
||||
vbox = QVBoxLayout(self)
|
||||
vbox.setContentsMargins(0, 0, 0, 0); vbox.setSpacing(0)
|
||||
vbox.addWidget(self._build_toolbar())
|
||||
self.bm_panel = BookmarkPanel(cdp, self)
|
||||
self.show()
|
||||
|
||||
def _build_toolbar(self):
|
||||
@@ -413,47 +525,12 @@ class KioskOverlay(QWidget):
|
||||
return bar
|
||||
|
||||
def _show_bookmarks_menu(self):
|
||||
try:
|
||||
menu = self._build_menu(load_bm())
|
||||
menu.addSeparator()
|
||||
mgr_act = QAction(" ⚙ Manage Bookmarks…", menu)
|
||||
mgr_act.triggered.connect(self.open_manager)
|
||||
menu.addAction(mgr_act)
|
||||
|
||||
# The toolbar has WA_ShowWithoutActivating, so it never steals
|
||||
# focus from Chrome. QMenu.exec_() needs the window to be active
|
||||
# to capture mouse/keyboard events; without this the menu flashes
|
||||
# and vanishes immediately.
|
||||
self.activateWindow()
|
||||
self.raise_()
|
||||
QApplication.instance().processEvents()
|
||||
|
||||
# Position right-aligned just below the toolbar.
|
||||
mw = menu.sizeHint().width()
|
||||
if mw < 100:
|
||||
mw = 360
|
||||
x = max(0, SCREEN_W - mw - 6)
|
||||
menu.exec_(QPoint(x, TOOLBAR_H))
|
||||
except Exception as e:
|
||||
print(f"Bookmarks menu error: {e}", flush=True)
|
||||
|
||||
def _build_menu(self, items):
|
||||
menu = QMenu()
|
||||
menu.setFont(QFont("Arial", 17))
|
||||
menu.setStyleSheet(MENU_STYLE)
|
||||
for item in items:
|
||||
if is_folder(item):
|
||||
sub = self._build_menu(item["children"])
|
||||
sub.setTitle(f" ▸ {item['name']}")
|
||||
sub.setFont(QFont("Arial", 17))
|
||||
sub.setStyleSheet(MENU_STYLE)
|
||||
menu.addMenu(sub)
|
||||
else:
|
||||
act = QAction(f" {item['name']}", menu)
|
||||
url = item["url"]
|
||||
act.triggered.connect(lambda _, u=url: self.cdp.navigate(u))
|
||||
menu.addAction(act)
|
||||
return menu
|
||||
if self.bm_panel.isVisible():
|
||||
self.bm_panel.hide()
|
||||
else:
|
||||
self.bm_panel.rebuild(load_bm())
|
||||
self.bm_panel.show()
|
||||
self.bm_panel.raise_()
|
||||
|
||||
def open_manager(self):
|
||||
d = ManagerDialog(self)
|
||||
|
||||
Reference in New Issue
Block a user