From 0cb6035863ee8276f4cd3df1e3edddd45678b8b7 Mon Sep 17 00:00:00 2001 From: thanhnv Date: Tue, 8 Sep 2026 10:08:15 +0900 Subject: [PATCH] fix(jira-knowledge): replace hover tooltip with click-based popup for help button Native Qt tooltips on macOS are unreliable (delayed or invisible). Replace with a QFrame popup that appears on click, positioned below the ? button, and closes when clicking elsewhere (Qt.Popup flag handles ESC and outside-click). - Store help text in self._help_text instead of setToolTip() - Add _toggle_help_popup() method: creates/destroys floating QFrame - Update test to check _help_text attribute instead of toolTip() Co-Authored-By: Claude Opus 5 --- tests/ui/test_jira_knowledge_help_ux.py | 11 +++-- ui/connectors_panel.py | 61 ++++++++++++++++++++----- 2 files changed, 55 insertions(+), 17 deletions(-) diff --git a/tests/ui/test_jira_knowledge_help_ux.py b/tests/ui/test_jira_knowledge_help_ux.py index 6e1bc51..4ed56db 100644 --- a/tests/ui/test_jira_knowledge_help_ux.py +++ b/tests/ui/test_jira_knowledge_help_ux.py @@ -51,15 +51,16 @@ def test_help_icon_exists(dialog): def test_help_icon_has_tooltip(dialog): - """Help icon must have a non-empty tooltip with both Project ID and Jira Key explanations.""" + """Help icon must store help text with both Project ID and Jira Key explanations.""" from PySide6.QtWidgets import QToolButton dlg = dialog help_icons = dlg.findChildren(QToolButton) assert help_icons, "No help button found" - tooltip = help_icons[0].toolTip() - assert tooltip, "Help button has no tooltip" - assert "Project ID" in tooltip, "Tooltip missing Project ID explanation" - assert "Jira Key" in tooltip, "Tooltip missing Jira Key explanation" + # Help text is stored in _help_text for click-based popup + help_text = getattr(dlg, '_help_text', '') or help_icons[0].toolTip() + assert help_text, "Help button has no help content" + assert "Project ID" in help_text, "Help content missing Project ID explanation" + assert "Jira Key" in help_text, "Help content missing Jira Key explanation" # ---- Help text content ---------------------------------------------------- diff --git a/ui/connectors_panel.py b/ui/connectors_panel.py index 009f10c..5350e98 100644 --- a/ui/connectors_panel.py +++ b/ui/connectors_panel.py @@ -11,12 +11,12 @@ setup dialog; OneDrive/SharePoint: neither, they only toggle). """ from __future__ import annotations -from PySide6.QtCore import Qt +from PySide6.QtCore import Qt, QPoint from PySide6.QtGui import QFont from PySide6.QtWidgets import ( QCheckBox, QDialog, QFormLayout, QFrame, QGroupBox, QHBoxLayout, QLabel, - QLineEdit, QMessageBox, QPushButton, QScrollArea, QToolButton, QToolTip, - QVBoxLayout, QWidget, + QLineEdit, QMessageBox, QPushButton, QScrollArea, QToolButton, + QVBoxLayout, QWidget, QApplication, ) from ..core.ext_connectors import CATEGORIES as EXT_CATEGORIES @@ -109,12 +109,6 @@ class JiraConnectDialog(QDialog): mapping_label = QLabel(tr("connectors.jira_kb_mapping_label")) self.help_icon = QToolButton() self.help_icon.setText("?") - self.help_icon.setToolTip( - "" + tr("connectors.jira_kb_project_id_title") + "
" - + tr("connectors.jira_kb_project_id_help") - + "

" + tr("connectors.jira_kb_jira_key_title") + "
" - + tr("connectors.jira_kb_jira_key_help") - ) self.help_icon.setStyleSheet(""" QToolButton { border: 1px solid #5B9BD5; @@ -135,9 +129,15 @@ class JiraConnectDialog(QDialog): color: white; } """) - # Native Qt tooltip: hover to show, no click handler needed. - # AutoRaise=False keeps the button always visible (not faded out). - self.help_icon.setAutoRaise(False) + # Click-based popover: build help content once, show on click + self._help_text = ( + "" + tr("connectors.jira_kb_project_id_title") + "
" + + tr("connectors.jira_kb_project_id_help") + + "

" + tr("connectors.jira_kb_jira_key_title") + "
" + + tr("connectors.jira_kb_jira_key_help") + ) + self._help_popup = None + self.help_icon.clicked.connect(self._toggle_help_popup) label_row = QHBoxLayout() label_row.setSpacing(4) label_row.addWidget(mapping_label) @@ -188,6 +188,43 @@ class JiraConnectDialog(QDialog): self.kb_enabled.toggled.connect(self._update_sync_state) self._update_sync_state(self.kb_enabled.isChecked()) + def _toggle_help_popup(self) -> None: + """Show/hide a floating help popup when the ? button is clicked.""" + if self._help_popup is not None and self._help_popup.isVisible(): + self._help_popup.hide() + self._help_popup.deleteLater() + self._help_popup = None + return + + popup = QFrame(self) + popup.setWindowFlags(Qt.Popup | Qt.FramelessWindowHint) + popup.setAttribute(Qt.WA_DeleteOnClose, False) + popup.setStyleSheet(""" + QFrame { + background: #2D2D2D; + border: 1px solid #5B9BD5; + border-radius: 8px; + padding: 12px; + } + """) + popup_layout = QVBoxLayout(popup) + popup_layout.setContentsMargins(12, 10, 12, 10) + + content = QLabel(self._help_text) + content.setWordWrap(True) + content.setMaximumWidth(380) + content.setStyleSheet("color: #E0E0E0; font-size: 12px; background: transparent; border: none;") + content.setTextFormat(Qt.RichText) + content.setOpenExternalLinks(True) + popup_layout.addWidget(content) + + # Position below the help button + btn_global = self.help_icon.mapToGlobal(QPoint(0, self.help_icon.height())) + popup.move(btn_global) + popup.show() + popup.raise_() + self._help_popup = popup + def _on_paste(self, text: str) -> None: """Auto-fill Base URL from a pasted Jira link.""" import re