1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtWidgets module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#include "qcommandlinkbutton.h"
41#include "qstylepainter.h"
42#include "qstyleoption.h"
43#include "qtextdocument.h"
44#include "qtextlayout.h"
45#include "qcolor.h"
46#include "qfont.h"
47#include <qmath.h>
48
49#include "private/qpushbutton_p.h"
50
51QT_BEGIN_NAMESPACE
52
53/*!
54 \class QCommandLinkButton
55 \since 4.4
56 \brief The QCommandLinkButton widget provides a Vista style command link button.
57
58 \ingroup basicwidgets
59 \inmodule QtWidgets
60
61 The command link is a new control that was introduced by Windows Vista. Its
62 intended use is similar to that of a radio button in that it is used to choose
63 between a set of mutually exclusive options. Command link buttons should not
64 be used by themselves but rather as an alternative to radio buttons in
65 Wizards and dialogs and makes pressing the "next" button redundant.
66 The appearance is generally similar to that of a flat pushbutton, but
67 it allows for a descriptive text in addition to the normal button text.
68 By default it will also carry an arrow icon, indicating that pressing the
69 control will open another window or page.
70
71 \sa QPushButton, QRadioButton
72*/
73
74/*!
75 \property QCommandLinkButton::description
76 \brief A descriptive label to complement the button text
77
78 Setting this property will set a descriptive text on the
79 button, complementing the text label. This will usually
80 be displayed in a smaller font than the primary text.
81*/
82
83/*!
84 \property QCommandLinkButton::flat
85 \brief This property determines whether the button is displayed as a flat
86 panel or with a border.
87
88 By default, this property is set to false.
89
90 \sa QPushButton::flat
91*/
92
93class QCommandLinkButtonPrivate : public QPushButtonPrivate
94{
95 Q_DECLARE_PUBLIC(QCommandLinkButton)
96
97public:
98 QCommandLinkButtonPrivate()
99 : QPushButtonPrivate(){}
100
101 void init();
102 qreal titleSize() const;
103 bool usingVistaStyle() const;
104
105 QFont titleFont() const;
106 QFont descriptionFont() const;
107
108 QRect titleRect() const;
109 QRect descriptionRect() const;
110
111 int textOffset() const;
112 int descriptionOffset() const;
113 int descriptionHeight(int width) const;
114 QColor mergedColors(const QColor &a, const QColor &b, int value) const;
115
116 int topMargin() const { return 10; }
117 int leftMargin() const { return 7; }
118 int rightMargin() const { return 4; }
119 int bottomMargin() const { return 10; }
120
121 QString description;
122 QColor currentColor;
123};
124
125// Mix colors a and b with a ratio in the range [0-255]
126QColor QCommandLinkButtonPrivate::mergedColors(const QColor &a, const QColor &b, int value = 50) const
127{
128 Q_ASSERT(value >= 0);
129 Q_ASSERT(value <= 255);
130 QColor tmp = a;
131 tmp.setRed((tmp.red() * value) / 255 + (b.red() * (255 - value)) / 255);
132 tmp.setGreen((tmp.green() * value) / 255 + (b.green() * (255 - value)) / 255);
133 tmp.setBlue((tmp.blue() * value) / 255 + (b.blue() * (255 - value)) / 255);
134 return tmp;
135}
136
137QFont QCommandLinkButtonPrivate::titleFont() const
138{
139 Q_Q(const QCommandLinkButton);
140 QFont font = q->font();
141 if (usingVistaStyle()) {
142 font.setPointSizeF(12.0);
143 } else {
144 font.setBold(true);
145 font.setPointSizeF(9.0);
146 }
147
148 // Note the font will be resolved against
149 // QPainters font, so we need to restore the mask
150 int resolve_mask = font.resolve_mask;
151 QFont modifiedFont = q->font().resolve(font);
152 modifiedFont.detach();
153 modifiedFont.resolve_mask = resolve_mask;
154 return modifiedFont;
155}
156
157QFont QCommandLinkButtonPrivate::descriptionFont() const
158{
159 Q_Q(const QCommandLinkButton);
160 QFont font = q->font();
161 font.setPointSizeF(9.0);
162
163 // Note the font will be resolved against
164 // QPainters font, so we need to restore the mask
165 int resolve_mask = font.resolve_mask;
166 QFont modifiedFont = q->font().resolve(font);
167 modifiedFont.detach();
168 modifiedFont.resolve_mask = resolve_mask;
169 return modifiedFont;
170}
171
172QRect QCommandLinkButtonPrivate::titleRect() const
173{
174 Q_Q(const QCommandLinkButton);
175 QRect r = q->rect().adjusted(xp1: textOffset(), yp1: topMargin(), xp2: -rightMargin(), yp2: 0);
176 if (description.isEmpty())
177 {
178 QFontMetrics fm(titleFont());
179 r.setTop(r.top() + qMax(a: 0, b: (q->icon().actualSize(size: q->iconSize()).height()
180 - fm.height()) / 2));
181 }
182
183 return r;
184}
185
186QRect QCommandLinkButtonPrivate::descriptionRect() const
187{
188 Q_Q(const QCommandLinkButton);
189 return q->rect().adjusted(xp1: textOffset(), yp1: descriptionOffset(),
190 xp2: -rightMargin(), yp2: -bottomMargin());
191}
192
193int QCommandLinkButtonPrivate::textOffset() const
194{
195 Q_Q(const QCommandLinkButton);
196 return q->icon().actualSize(size: q->iconSize()).width() + leftMargin() + 6;
197}
198
199int QCommandLinkButtonPrivate::descriptionOffset() const
200{
201 QFontMetrics fm(titleFont());
202 return topMargin() + fm.height();
203}
204
205bool QCommandLinkButtonPrivate::usingVistaStyle() const
206{
207 Q_Q(const QCommandLinkButton);
208 //### This is a hack to detect if we are indeed running Vista style themed and not in classic
209 // When we add api to query for this, we should change this implementation to use it.
210 return q->style()->inherits(classname: "QWindowsVistaStyle")
211 && q->style()->pixelMetric(metric: QStyle::PM_ButtonShiftHorizontal, option: nullptr) == 0;
212}
213
214void QCommandLinkButtonPrivate::init()
215{
216 Q_Q(QCommandLinkButton);
217 QPushButtonPrivate::init();
218 q->setAttribute(Qt::WA_Hover);
219 q->setAttribute(Qt::WA_MacShowFocusRect, on: false);
220
221 QSizePolicy policy(QSizePolicy::Preferred, QSizePolicy::Preferred, QSizePolicy::PushButton);
222 policy.setHeightForWidth(true);
223 q->setSizePolicy(policy);
224
225 q->setIconSize(QSize(20, 20));
226 QStyleOptionButton opt;
227 q->initStyleOption(option: &opt);
228 q->setIcon(q->style()->standardIcon(standardIcon: QStyle::SP_CommandLink, option: &opt));
229}
230
231// Calculates the height of the description text based on widget width
232int QCommandLinkButtonPrivate::descriptionHeight(int widgetWidth) const
233{
234 // Calc width of actual paragraph
235 int lineWidth = widgetWidth - textOffset() - rightMargin();
236
237 qreal descriptionheight = 0;
238 if (!description.isEmpty()) {
239 QTextLayout layout(description);
240 layout.setFont(descriptionFont());
241 layout.beginLayout();
242 while (true) {
243 QTextLine line = layout.createLine();
244 if (!line.isValid())
245 break;
246 line.setLineWidth(lineWidth);
247 line.setPosition(QPointF(0, descriptionheight));
248 descriptionheight += line.height();
249 }
250 layout.endLayout();
251 }
252 return qCeil(v: descriptionheight);
253}
254
255/*!
256 \reimp
257 */
258QSize QCommandLinkButton::minimumSizeHint() const
259{
260 Q_D(const QCommandLinkButton);
261 QSize size = sizeHint();
262 int minimumHeight = qMax(a: d->descriptionOffset() + d->bottomMargin(),
263 b: icon().actualSize(size: iconSize()).height() + d->topMargin());
264 size.setHeight(minimumHeight);
265 return size;
266}
267
268/*!
269 Constructs a command link with no text and a \a parent.
270*/
271
272QCommandLinkButton::QCommandLinkButton(QWidget *parent)
273: QPushButton(*new QCommandLinkButtonPrivate, parent)
274{
275 Q_D(QCommandLinkButton);
276 d->commandLink = true;
277 d->init();
278}
279
280/*!
281 Constructs a command link with the parent \a parent and the text \a
282 text.
283*/
284
285QCommandLinkButton::QCommandLinkButton(const QString &text, QWidget *parent)
286 : QCommandLinkButton(parent)
287{
288 setText(text);
289}
290
291/*!
292 Constructs a command link with a \a text, a \a description, and a \a parent.
293*/
294QCommandLinkButton::QCommandLinkButton(const QString &text, const QString &description, QWidget *parent)
295 : QCommandLinkButton(text, parent)
296{
297 setDescription(description);
298}
299
300/*!
301 Destructor.
302*/
303QCommandLinkButton::~QCommandLinkButton()
304{
305}
306
307/*! \reimp */
308bool QCommandLinkButton::event(QEvent *e)
309{
310 return QPushButton::event(e);
311}
312
313/*! \reimp */
314QSize QCommandLinkButton::sizeHint() const
315{
316// Standard size hints from UI specs
317// Without note: 135, 41
318// With note: 135, 60
319 Q_D(const QCommandLinkButton);
320
321 QSize size = QPushButton::sizeHint();
322 QFontMetrics fm(d->titleFont());
323 int textWidth = qMax(a: fm.horizontalAdvance(text()), b: 135);
324 int buttonWidth = textWidth + d->textOffset() + d->rightMargin();
325 int heightWithoutDescription = d->descriptionOffset() + d->bottomMargin();
326
327 size.setWidth(qMax(a: size.width(), b: buttonWidth));
328 size.setHeight(qMax(a: d->description.isEmpty() ? 41 : 60,
329 b: heightWithoutDescription + d->descriptionHeight(widgetWidth: buttonWidth)));
330 return size;
331}
332
333/*! \reimp */
334int QCommandLinkButton::heightForWidth(int width) const
335{
336 Q_D(const QCommandLinkButton);
337 int heightWithoutDescription = d->descriptionOffset() + d->bottomMargin();
338 // find the width available for the description area
339 return qMax(a: heightWithoutDescription + d->descriptionHeight(widgetWidth: width),
340 b: icon().actualSize(size: iconSize()).height() + d->topMargin() +
341 d->bottomMargin());
342}
343
344/*! \reimp */
345void QCommandLinkButton::paintEvent(QPaintEvent *)
346{
347 Q_D(QCommandLinkButton);
348 QStylePainter p(this);
349 p.save();
350
351 QStyleOptionButton option;
352 initStyleOption(option: &option);
353
354 //Enable command link appearance on Vista
355 option.features |= QStyleOptionButton::CommandLinkButton;
356 option.text = QString();
357 option.icon = QIcon(); //we draw this ourselves
358 QSize pixmapSize = icon().actualSize(size: iconSize());
359
360 const int vOffset = isDown()
361 ? style()->pixelMetric(metric: QStyle::PM_ButtonShiftVertical, option: &option) : 0;
362 const int hOffset = isDown()
363 ? style()->pixelMetric(metric: QStyle::PM_ButtonShiftHorizontal, option: &option) : 0;
364
365 //Draw icon
366 p.drawControl(ce: QStyle::CE_PushButton, opt: option);
367 if (!icon().isNull())
368 p.drawPixmap(x: d->leftMargin() + hOffset, y: d->topMargin() + vOffset,
369 pm: icon().pixmap(size: pixmapSize, mode: isEnabled() ? QIcon::Normal : QIcon::Disabled,
370 state: isChecked() ? QIcon::On : QIcon::Off));
371
372 //Draw title
373 QColor textColor = palette().buttonText().color();
374 if (isEnabled() && d->usingVistaStyle()) {
375 textColor = QColor(21, 28, 85);
376 if (underMouse() && !isDown())
377 textColor = QColor(7, 64, 229);
378 //A simple text color transition
379 d->currentColor = d->mergedColors(a: textColor, b: d->currentColor, value: 60);
380 option.palette.setColor(acr: QPalette::ButtonText, acolor: d->currentColor);
381 }
382
383 int textflags = Qt::TextShowMnemonic;
384 if (!style()->styleHint(stylehint: QStyle::SH_UnderlineShortcut, opt: &option, widget: this))
385 textflags |= Qt::TextHideMnemonic;
386
387 p.setFont(d->titleFont());
388 p.drawItemText(r: d->titleRect().translated(dx: hOffset, dy: vOffset),
389 flags: textflags, pal: option.palette, enabled: isEnabled(), text: text(), textRole: QPalette::ButtonText);
390
391 //Draw description
392 textflags |= Qt::TextWordWrap | Qt::ElideRight;
393 p.setFont(d->descriptionFont());
394 p.drawItemText(r: d->descriptionRect().translated(dx: hOffset, dy: vOffset), flags: textflags,
395 pal: option.palette, enabled: isEnabled(), text: description(), textRole: QPalette::ButtonText);
396 p.restore();
397}
398
399void QCommandLinkButton::setDescription(const QString &description)
400{
401 Q_D(QCommandLinkButton);
402 d->description = description;
403 updateGeometry();
404 update();
405}
406
407QString QCommandLinkButton::description() const
408{
409 Q_D(const QCommandLinkButton);
410 return d->description;
411}
412
413QT_END_NAMESPACE
414
415#include "moc_qcommandlinkbutton.cpp"
416

source code of qtbase/src/widgets/widgets/qcommandlinkbutton.cpp