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()
|
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 ──────────────────────────────────────
|
# ── Floating toolbar overlay ──────────────────────────────────────
|
||||||
class KioskOverlay(QWidget):
|
class KioskOverlay(QWidget):
|
||||||
def __init__(self, cdp):
|
def __init__(self, cdp):
|
||||||
@@ -371,6 +482,7 @@ class KioskOverlay(QWidget):
|
|||||||
vbox = QVBoxLayout(self)
|
vbox = QVBoxLayout(self)
|
||||||
vbox.setContentsMargins(0, 0, 0, 0); vbox.setSpacing(0)
|
vbox.setContentsMargins(0, 0, 0, 0); vbox.setSpacing(0)
|
||||||
vbox.addWidget(self._build_toolbar())
|
vbox.addWidget(self._build_toolbar())
|
||||||
|
self.bm_panel = BookmarkPanel(cdp, self)
|
||||||
self.show()
|
self.show()
|
||||||
|
|
||||||
def _build_toolbar(self):
|
def _build_toolbar(self):
|
||||||
@@ -413,47 +525,12 @@ class KioskOverlay(QWidget):
|
|||||||
return bar
|
return bar
|
||||||
|
|
||||||
def _show_bookmarks_menu(self):
|
def _show_bookmarks_menu(self):
|
||||||
try:
|
if self.bm_panel.isVisible():
|
||||||
menu = self._build_menu(load_bm())
|
self.bm_panel.hide()
|
||||||
menu.addSeparator()
|
else:
|
||||||
mgr_act = QAction(" ⚙ Manage Bookmarks…", menu)
|
self.bm_panel.rebuild(load_bm())
|
||||||
mgr_act.triggered.connect(self.open_manager)
|
self.bm_panel.show()
|
||||||
menu.addAction(mgr_act)
|
self.bm_panel.raise_()
|
||||||
|
|
||||||
# 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
|
|
||||||
|
|
||||||
def open_manager(self):
|
def open_manager(self):
|
||||||
d = ManagerDialog(self)
|
d = ManagerDialog(self)
|
||||||
|
|||||||
Reference in New Issue
Block a user