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
51 QT_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
93 class QCommandLinkButtonPrivate : public QPushButtonPrivate
94 {
95 Q_DECLARE_PUBLIC(QCommandLinkButton)
96
97 public:
QCommandLinkButtonPrivate()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
topMargin() const116 int topMargin() const { return 10; }
leftMargin() const117 int leftMargin() const { return 7; }
rightMargin() const118 int rightMargin() const { return 4; }
bottomMargin() const119 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]
mergedColors(const QColor & a,const QColor & b,int value=50) const126 QColor 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
titleFont() const137 QFont 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
descriptionFont() const157 QFont 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
titleRect() const172 QRect QCommandLinkButtonPrivate::titleRect() const
173 {
174 Q_Q(const QCommandLinkButton);
175 QRect r = q->rect().adjusted(textOffset(), topMargin(), -rightMargin(), 0);
176 if (description.isEmpty())
177 {
178 QFontMetrics fm(titleFont());
179 r.setTop(r.top() + qMax(0, (q->icon().actualSize(q->iconSize()).height()
180 - fm.height()) / 2));
181 }
182
183 return r;
184 }
185
descriptionRect() const186 QRect QCommandLinkButtonPrivate::descriptionRect() const
187 {
188 Q_Q(const QCommandLinkButton);
189 return q->rect().adjusted(textOffset(), descriptionOffset(),
190 -rightMargin(), -bottomMargin());
191 }
192
textOffset() const193 int QCommandLinkButtonPrivate::textOffset() const
194 {
195 Q_Q(const QCommandLinkButton);
196 return q->icon().actualSize(q->iconSize()).width() + leftMargin() + 6;
197 }
198
descriptionOffset() const199 int QCommandLinkButtonPrivate::descriptionOffset() const
200 {
201 QFontMetrics fm(titleFont());
202 return topMargin() + fm.height();
203 }
204
usingVistaStyle() const205 bool 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("QWindowsVistaStyle")
211 && q->style()->pixelMetric(QStyle::PM_ButtonShiftHorizontal, nullptr) == 0;
212 }
213
init()214 void QCommandLinkButtonPrivate::init()
215 {
216 Q_Q(QCommandLinkButton);
217 QPushButtonPrivate::init();
218 q->setAttribute(Qt::WA_Hover);
219
220 QSizePolicy policy(QSizePolicy::Preferred, QSizePolicy::Preferred, QSizePolicy::PushButton);
221 policy.setHeightForWidth(true);
222 q->setSizePolicy(policy);
223
224 q->setIconSize(QSize(20, 20));
225 QStyleOptionButton opt;
226 q->initStyleOption(&opt);
227 q->setIcon(q->style()->standardIcon(QStyle::SP_CommandLink, &opt));
228 }
229
230 // Calculates the height of the description text based on widget width
descriptionHeight(int widgetWidth) const231 int QCommandLinkButtonPrivate::descriptionHeight(int widgetWidth) const
232 {
233 // Calc width of actual paragraph
234 int lineWidth = widgetWidth - textOffset() - rightMargin();
235
236 qreal descriptionheight = 0;
237 if (!description.isEmpty()) {
238 QTextLayout layout(description);
239 layout.setFont(descriptionFont());
240 layout.beginLayout();
241 while (true) {
242 QTextLine line = layout.createLine();
243 if (!line.isValid())
244 break;
245 line.setLineWidth(lineWidth);
246 line.setPosition(QPointF(0, descriptionheight));
247 descriptionheight += line.height();
248 }
249 layout.endLayout();
250 }
251 return qCeil(descriptionheight);
252 }
253
254 /*!
255 \reimp
256 */
minimumSizeHint() const257 QSize QCommandLinkButton::minimumSizeHint() const
258 {
259 Q_D(const QCommandLinkButton);
260 QSize size = sizeHint();
261 int minimumHeight = qMax(d->descriptionOffset() + d->bottomMargin(),
262 icon().actualSize(iconSize()).height() + d->topMargin());
263 size.setHeight(minimumHeight);
264 return size;
265 }
266
267 /*!
268 Constructs a command link with no text and a \a parent.
269 */
270
QCommandLinkButton(QWidget * parent)271 QCommandLinkButton::QCommandLinkButton(QWidget *parent)
272 : QPushButton(*new QCommandLinkButtonPrivate, parent)
273 {
274 Q_D(QCommandLinkButton);
275 d->init();
276 }
277
278 /*!
279 Constructs a command link with the parent \a parent and the text \a
280 text.
281 */
282
QCommandLinkButton(const QString & text,QWidget * parent)283 QCommandLinkButton::QCommandLinkButton(const QString &text, QWidget *parent)
284 : QCommandLinkButton(parent)
285 {
286 setText(text);
287 }
288
289 /*!
290 Constructs a command link with a \a text, a \a description, and a \a parent.
291 */
QCommandLinkButton(const QString & text,const QString & description,QWidget * parent)292 QCommandLinkButton::QCommandLinkButton(const QString &text, const QString &description, QWidget *parent)
293 : QCommandLinkButton(text, parent)
294 {
295 setDescription(description);
296 }
297
298 /*!
299 Destructor.
300 */
~QCommandLinkButton()301 QCommandLinkButton::~QCommandLinkButton()
302 {
303 }
304
305 /*! \reimp */
event(QEvent * e)306 bool QCommandLinkButton::event(QEvent *e)
307 {
308 return QPushButton::event(e);
309 }
310
311 /*! \reimp */
sizeHint() const312 QSize QCommandLinkButton::sizeHint() const
313 {
314 // Standard size hints from UI specs
315 // Without note: 135, 41
316 // With note: 135, 60
317 Q_D(const QCommandLinkButton);
318
319 QSize size = QPushButton::sizeHint();
320 QFontMetrics fm(d->titleFont());
321 int textWidth = qMax(fm.horizontalAdvance(text()), 135);
322 int buttonWidth = textWidth + d->textOffset() + d->rightMargin();
323 int heightWithoutDescription = d->descriptionOffset() + d->bottomMargin();
324
325 size.setWidth(qMax(size.width(), buttonWidth));
326 size.setHeight(qMax(d->description.isEmpty() ? 41 : 60,
327 heightWithoutDescription + d->descriptionHeight(buttonWidth)));
328 return size;
329 }
330
331 /*! \reimp */
heightForWidth(int width) const332 int QCommandLinkButton::heightForWidth(int width) const
333 {
334 Q_D(const QCommandLinkButton);
335 int heightWithoutDescription = d->descriptionOffset() + d->bottomMargin();
336 // find the width available for the description area
337 return qMax(heightWithoutDescription + d->descriptionHeight(width),
338 icon().actualSize(iconSize()).height() + d->topMargin() +
339 d->bottomMargin());
340 }
341
342 /*! \reimp */
paintEvent(QPaintEvent *)343 void QCommandLinkButton::paintEvent(QPaintEvent *)
344 {
345 Q_D(QCommandLinkButton);
346 QStylePainter p(this);
347 p.save();
348
349 QStyleOptionButton option;
350 initStyleOption(&option);
351
352 //Enable command link appearance on Vista
353 option.features |= QStyleOptionButton::CommandLinkButton;
354 option.text = QString();
355 option.icon = QIcon(); //we draw this ourselves
356 QSize pixmapSize = icon().actualSize(iconSize());
357
358 const int vOffset = isDown()
359 ? style()->pixelMetric(QStyle::PM_ButtonShiftVertical, &option) : 0;
360 const int hOffset = isDown()
361 ? style()->pixelMetric(QStyle::PM_ButtonShiftHorizontal, &option) : 0;
362
363 //Draw icon
364 p.drawControl(QStyle::CE_PushButton, option);
365 if (!icon().isNull())
366 p.drawPixmap(d->leftMargin() + hOffset, d->topMargin() + vOffset,
367 icon().pixmap(pixmapSize, isEnabled() ? QIcon::Normal : QIcon::Disabled,
368 isChecked() ? QIcon::On : QIcon::Off));
369
370 //Draw title
371 QColor textColor = palette().buttonText().color();
372 if (isEnabled() && d->usingVistaStyle()) {
373 textColor = QColor(21, 28, 85);
374 if (underMouse() && !isDown())
375 textColor = QColor(7, 64, 229);
376 //A simple text color transition
377 d->currentColor = d->mergedColors(textColor, d->currentColor, 60);
378 option.palette.setColor(QPalette::ButtonText, d->currentColor);
379 }
380
381 int textflags = Qt::TextShowMnemonic;
382 if (!style()->styleHint(QStyle::SH_UnderlineShortcut, &option, this))
383 textflags |= Qt::TextHideMnemonic;
384
385 p.setFont(d->titleFont());
386 p.drawItemText(d->titleRect().translated(hOffset, vOffset),
387 textflags, option.palette, isEnabled(), text(), QPalette::ButtonText);
388
389 //Draw description
390 textflags |= Qt::TextWordWrap | Qt::ElideRight;
391 p.setFont(d->descriptionFont());
392 p.drawItemText(d->descriptionRect().translated(hOffset, vOffset), textflags,
393 option.palette, isEnabled(), description(), QPalette::ButtonText);
394 p.restore();
395 }
396
setDescription(const QString & description)397 void QCommandLinkButton::setDescription(const QString &description)
398 {
399 Q_D(QCommandLinkButton);
400 d->description = description;
401 updateGeometry();
402 update();
403 }
404
description() const405 QString QCommandLinkButton::description() const
406 {
407 Q_D(const QCommandLinkButton);
408 return d->description;
409 }
410
411 QT_END_NAMESPACE
412
413 #include "moc_qcommandlinkbutton.cpp"
414