Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qcommandlinkbutton.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:default
4
7#include "qstyleoption.h"
9#include "qtextlayout.h"
10#include "qcolor.h"
11#include "qfont.h"
12#include <qmath.h>
13
14#include "private/qpushbutton_p.h"
15#include "private/qstylesheetstyle_p.h"
16
18
19/*!
20 \class QCommandLinkButton
21 \since 4.4
22 \brief The QCommandLinkButton widget provides a Vista style command link button.
23
24 \ingroup basicwidgets
25 \inmodule QtWidgets
26
27 The command link is a new control that was introduced by Windows Vista. Its
28 intended use is similar to that of a radio button in that it is used to choose
29 between a set of mutually exclusive options. Command link buttons should not
30 be used by themselves but rather as an alternative to radio buttons in
31 Wizards and dialogs and makes pressing the "next" button redundant.
32 The appearance is generally similar to that of a flat pushbutton, but
33 it allows for a descriptive text in addition to the normal button text.
34 By default it will also carry an arrow icon, indicating that pressing the
35 control will open another window or page.
36
37 \sa QPushButton, QRadioButton
38*/
39
40/*!
41 \property QCommandLinkButton::description
42 \brief A descriptive label to complement the button text
43
44 Setting this property will set a descriptive text on the
45 button, complementing the text label. This will usually
46 be displayed in a smaller font than the primary text.
47*/
48
49/*!
50 \property QCommandLinkButton::flat
51 \brief This property determines whether the button is displayed as a flat
52 panel or with a border.
53
54 By default, this property is set to false.
55
56 \sa QPushButton::flat
57*/
58
60{
61 Q_DECLARE_PUBLIC(QCommandLinkButton)
62
63public:
66
67 void init();
69 bool usingVistaStyle() const;
70
71 QFont titleFont() const;
72 QFont descriptionFont() const;
73
74 QRect titleRect() const;
75 QRect descriptionRect() const;
76
77 int textOffset() const;
78 int descriptionOffset() const;
79 int descriptionHeight(int width) const;
80 QColor mergedColors(const QColor &a, const QColor &b, int value) const;
81
82 int topMargin() const { return 10; }
83 int leftMargin() const { return 7; }
84 int rightMargin() const { return 4; }
85 int bottomMargin() const { return 10; }
86
89};
90
91// Mix colors a and b with a ratio in the range [0-255]
92QColor QCommandLinkButtonPrivate::mergedColors(const QColor &a, const QColor &b, int value = 50) const
93{
94 Q_ASSERT(value >= 0);
95 Q_ASSERT(value <= 255);
96 QColor tmp = a;
97 tmp.setRed((tmp.red() * value) / 255 + (b.red() * (255 - value)) / 255);
98 tmp.setGreen((tmp.green() * value) / 255 + (b.green() * (255 - value)) / 255);
99 tmp.setBlue((tmp.blue() * value) / 255 + (b.blue() * (255 - value)) / 255);
100 return tmp;
101}
102
104{
105 Q_Q(const QCommandLinkButton);
106 QFont font = q->font();
107 if (usingVistaStyle()) {
108 font.setPointSizeF(12.0);
109 } else {
110 font.setBold(true);
111 font.setPointSizeF(9.0);
112 }
113
114 // Note the font will be resolved against
115 // QPainters font, so we need to restore the mask
116 int resolve_mask = font.resolve_mask;
117 QFont modifiedFont = q->font().resolve(font);
118 modifiedFont.detach();
119 modifiedFont.resolve_mask = resolve_mask;
120 return modifiedFont;
121}
122
124{
125 Q_Q(const QCommandLinkButton);
126 QFont font = q->font();
127 font.setPointSizeF(9.0);
128
129 // Note the font will be resolved against
130 // QPainters font, so we need to restore the mask
131 int resolve_mask = font.resolve_mask;
132 QFont modifiedFont = q->font().resolve(font);
133 modifiedFont.detach();
134 modifiedFont.resolve_mask = resolve_mask;
135 return modifiedFont;
136}
137
139{
140 Q_Q(const QCommandLinkButton);
141 QRect r = q->rect().adjusted(textOffset(), topMargin(), -rightMargin(), 0);
142 if (description.isEmpty())
143 {
144 QFontMetrics fm(titleFont());
145 r.setTop(r.top() + qMax(0, (q->icon().actualSize(q->iconSize()).height()
146 - fm.height()) / 2));
147 }
148
149 return r;
150}
151
153{
154 Q_Q(const QCommandLinkButton);
155 return q->rect().adjusted(textOffset(), descriptionOffset(),
156 -rightMargin(), -bottomMargin());
157}
158
160{
161 Q_Q(const QCommandLinkButton);
162 return q->icon().actualSize(q->iconSize()).width() + leftMargin() + 6;
163}
164
166{
167 QFontMetrics fm(titleFont());
168 return topMargin() + fm.height();
169}
170
172{
173 Q_Q(const QCommandLinkButton);
174 //### This is a hack to detect if we are indeed running Vista style themed and not in classic
175 // When we add api to query for this, we should change this implementation to use it.
176 return q->property("_qt_usingVistaStyle").toBool()
177 && q->style()->pixelMetric(QStyle::PM_ButtonShiftHorizontal, nullptr, q) == 0;
178}
179
181{
182 Q_Q(QCommandLinkButton);
183 QPushButtonPrivate::init();
184 q->setAttribute(Qt::WA_Hover);
185 q->setAttribute(Qt::WA_MacShowFocusRect, false);
186
187 QSizePolicy policy(QSizePolicy::Preferred, QSizePolicy::Preferred, QSizePolicy::PushButton);
188 policy.setHeightForWidth(true);
189 q->setSizePolicy(policy);
190
191 q->setIconSize(QSize(20, 20));
193 q->initStyleOption(&opt);
194 q->setIcon(q->style()->standardIcon(QStyle::SP_CommandLink, &opt, q));
195}
196
197// Calculates the height of the description text based on widget width
199{
200 // Calc width of actual paragraph
201 int lineWidth = widgetWidth - textOffset() - rightMargin();
202
203 qreal descriptionheight = 0;
204 if (!description.isEmpty()) {
205 QTextLayout layout(description);
206 layout.setFont(descriptionFont());
207 layout.beginLayout();
208 while (true) {
209 QTextLine line = layout.createLine();
210 if (!line.isValid())
211 break;
212 line.setLineWidth(lineWidth);
213 line.setPosition(QPointF(0, descriptionheight));
214 descriptionheight += line.height();
215 }
216 layout.endLayout();
217 }
218 return qCeil(descriptionheight);
219}
220
221/*!
222 \reimp
223 */
224QSize QCommandLinkButton::minimumSizeHint() const
225{
226 Q_D(const QCommandLinkButton);
227 QSize size = sizeHint();
228 int minimumHeight = qMax(d->descriptionOffset() + d->bottomMargin(),
229 icon().actualSize(iconSize()).height() + d->topMargin());
230 size.setHeight(minimumHeight);
231 return size;
232}
233
234void QCommandLinkButton::initStyleOption(QStyleOptionButton *option) const
235{
236 QPushButton::initStyleOption(option);
237 option->features |= QStyleOptionButton::CommandLinkButton;
238}
239
240/*!
241 Constructs a command link with no text and a \a parent.
242*/
243
244QCommandLinkButton::QCommandLinkButton(QWidget *parent)
245: QPushButton(*new QCommandLinkButtonPrivate, parent)
246{
247 Q_D(QCommandLinkButton);
248 d->init();
249}
250
251/*!
252 Constructs a command link with the parent \a parent and the text \a
253 text.
254*/
255
256QCommandLinkButton::QCommandLinkButton(const QString &text, QWidget *parent)
257 : QCommandLinkButton(parent)
258{
259 setText(text);
260}
261
262/*!
263 Constructs a command link with a \a text, a \a description, and a \a parent.
264*/
265QCommandLinkButton::QCommandLinkButton(const QString &text, const QString &description, QWidget *parent)
266 : QCommandLinkButton(text, parent)
267{
268 setDescription(description);
269}
270
271/*!
272 Destructor.
273*/
274QCommandLinkButton::~QCommandLinkButton()
275{
276}
277
278/*! \reimp */
279bool QCommandLinkButton::event(QEvent *e)
280{
281 if (e->type() == QEvent::StyleChange) {
282 // If the new style is a QStyleSheetStyle, don't reset the icon, because:
283 // - either it has been explicitly set, in which case we want to keep it.
284 // - or it has been initialised by the previous style, which is now the base style,
285 // in which case we want to keep it as well.
286 // - or it has been set in the style sheet, in which case we don't want to override it here.
287 // When a style sheet with an icon is replaced by one without an icon, the old icon
288 // will be reset, when baseStyle()->repolish() is called.
289 if (!qobject_cast<QStyleSheetStyle *>(style())) {
290 QStyleOptionButton opt;
291 initStyleOption(&opt);
292 setIcon(style()->standardIcon(QStyle::SP_CommandLink, &opt, this));
293 }
294 }
295
296 return QPushButton::event(e);
297}
298
299/*! \reimp */
300QSize QCommandLinkButton::sizeHint() const
301{
302// Standard size hints from UI specs
303// Without note: 135, 41
304// With note: 135, 60
305 Q_D(const QCommandLinkButton);
306
307 QSize size = QPushButton::sizeHint();
308 QFontMetrics fm(d->titleFont());
309 int textWidth = qMax(fm.horizontalAdvance(text()), 135);
310 int buttonWidth = textWidth + d->textOffset() + d->rightMargin();
311 int heightWithoutDescription = d->descriptionOffset() + d->bottomMargin();
312
313 size.setWidth(qMax(size.width(), buttonWidth));
314 size.setHeight(qMax(d->description.isEmpty() ? 41 : 60,
315 heightWithoutDescription + d->descriptionHeight(buttonWidth)));
316 return size;
317}
318
319/*! \reimp */
320int QCommandLinkButton::heightForWidth(int width) const
321{
322 Q_D(const QCommandLinkButton);
323 int heightWithoutDescription = d->descriptionOffset() + d->bottomMargin();
324 // find the width available for the description area
325 return qMax(heightWithoutDescription + d->descriptionHeight(width),
326 icon().actualSize(iconSize()).height() + d->topMargin() +
327 d->bottomMargin());
328}
329
330/*! \reimp */
331void QCommandLinkButton::paintEvent(QPaintEvent *)
332{
333 Q_D(QCommandLinkButton);
334 QStylePainter p(this);
335
336 QStyleOptionButton option;
337 initStyleOption(&option);
338
339 option.text = QString();
340 option.icon = QIcon(); //we draw this ourselves
341
342 const int vOffset = isDown()
343 ? style()->pixelMetric(QStyle::PM_ButtonShiftVertical, &option, this) : 0;
344 const int hOffset = isDown()
345 ? style()->pixelMetric(QStyle::PM_ButtonShiftHorizontal, &option, this) : 0;
346
347 //Draw icon
348 p.drawControl(QStyle::CE_PushButton, option);
349 if (!icon().isNull()) {
350 const auto size = icon().actualSize(iconSize());
351 const auto mode = isEnabled() ? QIcon::Normal : QIcon::Disabled;
352 const auto state = isChecked() ? QIcon::On : QIcon::Off;
353 const auto rect = QRect(d->leftMargin() + hOffset, d->topMargin() + vOffset,
354 size.width(), size.height());
355 icon().paint(&p, rect, Qt::AlignCenter, mode, state);
356 }
357
358 //Draw title
359 QColor textColor = palette().buttonText().color();
360 if (isEnabled() && d->usingVistaStyle()) {
361 textColor = option.palette.buttonText().color();
362 if (underMouse() && !isDown())
363 textColor = option.palette.brightText().color();
364 //A simple text color transition
365 d->currentColor = d->mergedColors(textColor, d->currentColor, 60);
366 option.palette.setColor(QPalette::ButtonText, d->currentColor);
367 }
368
369 int textflags = Qt::TextShowMnemonic;
370 if (!style()->styleHint(QStyle::SH_UnderlineShortcut, &option, this))
371 textflags |= Qt::TextHideMnemonic;
372
373 p.setFont(d->titleFont());
374 p.drawItemText(d->titleRect().translated(hOffset, vOffset),
375 textflags, option.palette, isEnabled(), text(), QPalette::ButtonText);
376
377 //Draw description
378 textflags |= Qt::TextWordWrap | Qt::ElideRight;
379 p.setFont(d->descriptionFont());
380 p.drawItemText(d->descriptionRect().translated(hOffset, vOffset), textflags,
381 option.palette, isEnabled(), description(), QPalette::ButtonText);
382}
383
384void QCommandLinkButton::setDescription(const QString &description)
385{
386 Q_D(QCommandLinkButton);
387 d->description = description;
388 updateGeometry();
389 update();
390}
391
392QString QCommandLinkButton::description() const
393{
394 Q_D(const QCommandLinkButton);
395 return d->description;
396}
397
398QT_END_NAMESPACE
399
400#include "moc_qcommandlinkbutton.cpp"
int descriptionHeight(int width) const
QColor mergedColors(const QColor &a, const QColor &b, int value) const
\variable QStyleOptionHeaderV2::textElideMode
\reentrant
Definition qtextlayout.h:70