1 | /* |
2 | This file is part of the KDE project |
3 | SPDX-FileCopyrightText: 2024 Felix Ernst <felixernst@kde.org> |
4 | |
5 | SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL |
6 | */ |
7 | |
8 | #include "kcontextualhelpbutton.h" |
9 | |
10 | #include <QLabel> |
11 | #include <QPointer> |
12 | #include <QTextDocumentFragment> |
13 | #include <QWidgetAction> |
14 | |
15 | /** |
16 | * The private class of KContextualHelpButton used for the PIMPL idiom. |
17 | * \internal |
18 | */ |
19 | class KContextualHelpButtonPrivate |
20 | { |
21 | public: |
22 | /** @see KContextualHelpButton::KContextualHelpButton() */ |
23 | explicit KContextualHelpButtonPrivate(KContextualHelpButton *q, const QString &contextualHelpText, const QWidget *heightHintWidget); |
24 | |
25 | /** @see KContextualHelButton::setContextualHelpText() */ |
26 | void setContextualHelpText(const QString &contextualHelpText); |
27 | |
28 | /** @see KContextualHelButton::contextualHelpText() */ |
29 | QString contextualHelpText() const; |
30 | |
31 | /** @see KContextualHelpButton::setHeightHintWidget() */ |
32 | void setHeightHintWidget(const QWidget *heightHintWidget); |
33 | |
34 | /** @see KContextualHelpButton::setHeightHintWidget() */ |
35 | const QWidget *heightHintWidget() const; |
36 | |
37 | /** |
38 | * A helper method called from KContextualHelpButton::sizeHint(). |
39 | * |
40 | * @returns the preferredSize based on m_heightHintWidget and fallbackSize. |
41 | * @param fallbackSize Used as the width. Also used as the height if there is no m_heightHintWidget. |
42 | * |
43 | * @see QWidget::sizeHint() |
44 | * @see KContextualHelpButton::setHeightHintWidget() |
45 | */ |
46 | QSize preferredSize(const QSize &fallbackSize) const; |
47 | |
48 | private: |
49 | KContextualHelpButton *const q_ptr; |
50 | |
51 | /** The popup showing the contextualHelpText. */ |
52 | QLabel * = nullptr; |
53 | |
54 | /** @see KContextualHelpButton::setHeightHintWidget() */ |
55 | QPointer<const QWidget> m_heightHintWidget; |
56 | }; |
57 | |
58 | KContextualHelpButton::KContextualHelpButton(const QString &contextualHelpText, const QWidget *heightHintWidget, QWidget *parent) |
59 | : QToolButton{parent} |
60 | , d_ptr(std::make_unique<KContextualHelpButtonPrivate>(args: this, args: contextualHelpText, args&: heightHintWidget)) |
61 | { |
62 | } |
63 | |
64 | KContextualHelpButton::KContextualHelpButton(QWidget *parent) |
65 | : KContextualHelpButton{QString{}, nullptr, parent} |
66 | { |
67 | } |
68 | |
69 | KContextualHelpButtonPrivate::KContextualHelpButtonPrivate(KContextualHelpButton *q, const QString &contextualHelpText, const QWidget *heightHintWidget) |
70 | : q_ptr{q} |
71 | , m_heightHintWidget{heightHintWidget} |
72 | { |
73 | q_ptr->setIcon(QIcon::fromTheme(QStringLiteral("help-contextual" ))); |
74 | q_ptr->setAutoRaise(true); |
75 | q_ptr->setCursor(Qt::WhatsThisCursor); |
76 | // i18n: This text will only ever be read by assistive technology like screen readers for visually-impaired. It shows up nowhere on the visible user |
77 | // interface. Usually we start button labels with a verb, however the help text contained within this button will automatically be read by the screen |
78 | // reader without requiring additional user input. In this sense the label here is more of an announcement that the text read afterwards is help relevant |
79 | // to the current context. |
80 | q_ptr->setAccessibleName(QObject::tr(s: "Contextual Help" , c: "@action:button accessible name" )); |
81 | q_ptr->setAttribute(Qt::WidgetAttribute::WA_CustomWhatsThis); // Makes sure clicking this button in whatsThisMode triggers it instead of doing nothing. |
82 | |
83 | auto = new QWidgetAction{q_ptr}; |
84 | q_ptr->addAction(action: popup); |
85 | q_ptr->setPopupMode(QToolButton::InstantPopup); |
86 | |
87 | m_popupLabel = new QLabel{q_ptr}; |
88 | m_popupLabel->setWordWrap(true); |
89 | m_popupLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); // Makes the label and links within it keyboard-accessible. |
90 | m_popupLabel->setOpenExternalLinks(true); |
91 | popup->setDefaultWidget(m_popupLabel); |
92 | |
93 | setContextualHelpText(contextualHelpText); |
94 | } |
95 | |
96 | KContextualHelpButton::~KContextualHelpButton() = default; |
97 | |
98 | void KContextualHelpButton::setContextualHelpText(const QString &contextualHelpText) |
99 | { |
100 | d_ptr->setContextualHelpText(contextualHelpText); |
101 | } |
102 | |
103 | void KContextualHelpButtonPrivate::setContextualHelpText(const QString &contextualHelpText) |
104 | { |
105 | if (contextualHelpText == q_ptr->toolTip() && contextualHelpText == m_popupLabel->text()) { |
106 | return; |
107 | } |
108 | q_ptr->setToolTip(contextualHelpText); |
109 | q_ptr->setAccessibleDescription(QTextDocumentFragment::fromHtml(html: contextualHelpText).toPlainText()); // Makes sure html tags aren't read out. |
110 | m_popupLabel->setText(contextualHelpText); |
111 | Q_EMIT q_ptr->contextualHelpTextChanged(newContextualHelpText: contextualHelpText); |
112 | } |
113 | |
114 | QString KContextualHelpButton::contextualHelpText() const |
115 | { |
116 | return d_ptr->contextualHelpText(); |
117 | } |
118 | |
119 | QString KContextualHelpButtonPrivate::contextualHelpText() const |
120 | { |
121 | return m_popupLabel->text(); |
122 | } |
123 | |
124 | void KContextualHelpButton::setHeightHintWidget(const QWidget *heightHintWidget) |
125 | { |
126 | d_ptr->setHeightHintWidget(heightHintWidget); |
127 | } |
128 | |
129 | void KContextualHelpButtonPrivate::setHeightHintWidget(const QWidget *heightHintWidget) |
130 | { |
131 | m_heightHintWidget = heightHintWidget; |
132 | } |
133 | |
134 | const QWidget *KContextualHelpButton::heightHintWidget() const |
135 | { |
136 | return d_ptr->heightHintWidget(); |
137 | } |
138 | |
139 | const QWidget *KContextualHelpButtonPrivate::heightHintWidget() const |
140 | { |
141 | return m_heightHintWidget; |
142 | } |
143 | |
144 | QSize KContextualHelpButton::sizeHint() const |
145 | { |
146 | return d_ptr->preferredSize(fallbackSize: QToolButton::sizeHint()); |
147 | } |
148 | |
149 | QSize KContextualHelpButtonPrivate::preferredSize(const QSize &fallbackSize) const |
150 | { |
151 | return {fallbackSize.width(), m_heightHintWidget ? m_heightHintWidget->sizeHint().height() : fallbackSize.height()}; |
152 | } |
153 | |