File indexing completed on 2024-05-12 16:39:55
0001 /* This file is part of the KDE project 0002 Copyright (C) 2011 Nokia Corporation and/or its subsidiary(-ies). 0003 Copyright (C) 2011 Jarosław Staniek <staniek@kde.org> 0004 0005 This library is free software; you can redistribute it and/or 0006 modify it under the terms of the GNU Library General Public 0007 License as published by the Free Software Foundation; either 0008 version 2 of the License, or (at your option) any later version. 0009 0010 This library is distributed in the hope that it will be useful, 0011 but WITHOUT ANY WARRANTY; without even the implied warranty of 0012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0013 Library General Public License for more details. 0014 0015 You should have received a copy of the GNU Library General Public License 0016 along with this library; see the file COPYING.LIB. If not, write to 0017 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, 0018 * Boston, MA 02110-1301, USA. 0019 */ 0020 0021 #include "KexiCommandLinkButton.h" 0022 #include <QStylePainter> 0023 #include <QStyleOption> 0024 #include <QTextLayout> 0025 #include <QColor> 0026 #include <QFont> 0027 #include <qmath.h> 0028 0029 /*! 0030 \class KexiCommandLinkButton 0031 \brief The KexiCommandLinkButton widget provides a Vista style command link button. 0032 0033 The command link is a new control that was introduced by Windows Vista. It's 0034 intended use is similar to that of a radio button in that it is used to choose 0035 between a set of mutually exclusive options. Command link buttons should not 0036 be used by themselves but rather as an alternative to radio buttons in 0037 Wizards and dialogs and makes pressing the "next" button redundant. 0038 The appearance is generally similar to that of a flat pushbutton, but 0039 it allows for a descriptive text in addition to the normal button text. 0040 By default it will also carry an arrow icon, indicating that pressing the 0041 control will open another window or page. 0042 0043 \sa QPushButton QRadioButton 0044 */ 0045 0046 /*! 0047 \property KexiCommandLinkButton::description 0048 \brief A descriptive label to complement the button text 0049 0050 Setting this property will set a descriptive text on the 0051 button, complementing the text label. This will usually 0052 be displayed in a smaller font than the primary text. 0053 */ 0054 0055 /*! 0056 \property KexiCommandLinkButton::flat 0057 \brief This property determines whether the button is displayed as a flat 0058 panel or with a border. 0059 0060 By default, this property is set to false. 0061 0062 \sa QPushButton::flat 0063 */ 0064 0065 class KexiCommandLinkButtonPrivate 0066 { 0067 public: 0068 explicit KexiCommandLinkButtonPrivate(KexiCommandLinkButton *qq) 0069 : isArrowVisible(false), q(qq) {} 0070 0071 void init(); 0072 qreal titleSize() const; 0073 bool usingVistaStyle() const; 0074 0075 QFont titleFont() const; 0076 QFont descriptionFont() const; 0077 0078 QRect titleRect() const; 0079 QRect descriptionRect() const; 0080 0081 int textOffset() const; 0082 int descriptionOffset() const; 0083 int descriptionHeight(int width) const; 0084 QColor mergedColors(const QColor &a, const QColor &b, int value) const; 0085 0086 int topMargin() const { return 10; } 0087 int leftMargin() const { return 7; } 0088 int rightMargin() const { return 4; } 0089 int bottomMargin() const { return 10; } 0090 0091 QString description; 0092 QColor currentColor; 0093 bool isArrowVisible; 0094 KexiCommandLinkButton *q; 0095 }; 0096 0097 // Mix colors a and b with a ratio in the range [0-255] 0098 QColor KexiCommandLinkButtonPrivate::mergedColors(const QColor &a, const QColor &b, int value = 50) const 0099 { 0100 Q_ASSERT(value >= 0); 0101 Q_ASSERT(value <= 255); 0102 QColor tmp = a; 0103 tmp.setRed((tmp.red() * value) / 255 + (b.red() * (255 - value)) / 255); 0104 tmp.setGreen((tmp.green() * value) / 255 + (b.green() * (255 - value)) / 255); 0105 tmp.setBlue((tmp.blue() * value) / 255 + (b.blue() * (255 - value)) / 255); 0106 return tmp; 0107 } 0108 0109 QFont KexiCommandLinkButtonPrivate::titleFont() const 0110 { 0111 QFont font = q->font(); 0112 if (usingVistaStyle()) { 0113 font.setPointSizeF(font.pointSizeF() + 1); 0114 } else { 0115 font.setBold(true); 0116 } 0117 0118 // Note the font will be resolved against 0119 // QPainters font, so we need to restore the mask 0120 int resolve_mask = font.resolve(); 0121 QFont modifiedFont = q->font().resolve(font); 0122 modifiedFont.resolve(resolve_mask); 0123 return modifiedFont; 0124 } 0125 0126 QFont KexiCommandLinkButtonPrivate::descriptionFont() const 0127 { 0128 return q->font(); 0129 } 0130 0131 QRect KexiCommandLinkButtonPrivate::titleRect() const 0132 { 0133 QRect r = q->rect().adjusted(textOffset(), topMargin(), -rightMargin(), 0); 0134 QFontMetrics fm(titleFont()); 0135 r.setHeight(fm.height()); 0136 if (description.isEmpty()) 0137 { 0138 r.setTop(r.top() + qMax(0, (q->icon().actualSize(q->iconSize()).height() 0139 - fm.height()) / 2)); 0140 } 0141 0142 return r; 0143 } 0144 0145 QRect KexiCommandLinkButtonPrivate::descriptionRect() const 0146 { 0147 return q->rect().adjusted(textOffset(), descriptionOffset(), 0148 -rightMargin(), -bottomMargin()); 0149 } 0150 0151 int KexiCommandLinkButtonPrivate::textOffset() const 0152 { 0153 return q->icon().actualSize(q->iconSize()).width() + leftMargin() + 6; 0154 } 0155 0156 int KexiCommandLinkButtonPrivate::descriptionOffset() const 0157 { 0158 QFontMetrics fm(descriptionFont()); 0159 return topMargin() + fm.height(); 0160 } 0161 0162 bool KexiCommandLinkButtonPrivate::usingVistaStyle() const 0163 { 0164 //### This is a hack to detect if we are indeed running Vista style themed and not in classic 0165 // When we add api to query for this, we should change this implementation to use it. 0166 return q->style()->inherits("QWindowsVistaStyle") 0167 && !q->style()->pixelMetric(QStyle::PM_ButtonShiftHorizontal); 0168 } 0169 0170 void KexiCommandLinkButtonPrivate::init() 0171 { 0172 q->setAttribute(Qt::WA_Hover); 0173 0174 QSizePolicy policy(QSizePolicy::Preferred, QSizePolicy::Preferred, QSizePolicy::PushButton); 0175 policy.setHeightForWidth(true); 0176 q->setSizePolicy(policy); 0177 0178 q->setIconSize(QSize(20, 20)); 0179 QStyleOptionButton opt; 0180 q->initStyleOption(&opt); 0181 q->setIcon(q->style()->standardIcon(QStyle::SP_CommandLink, &opt)); 0182 } 0183 0184 // Calculates the height of the description text based on widget width 0185 int KexiCommandLinkButtonPrivate::descriptionHeight(int widgetWidth) const 0186 { 0187 // Calc width of actual paragraph 0188 int lineWidth = widgetWidth - textOffset() - rightMargin(); 0189 0190 qreal descriptionheight = 0; 0191 if (!description.isEmpty()) { 0192 QTextLayout layout(description); 0193 layout.setFont(descriptionFont()); 0194 layout.beginLayout(); 0195 while (true) { 0196 QTextLine line = layout.createLine(); 0197 if (!line.isValid()) 0198 break; 0199 line.setLineWidth(lineWidth); 0200 line.setPosition(QPointF(0, descriptionheight)); 0201 descriptionheight += line.height(); 0202 } 0203 layout.endLayout(); 0204 } 0205 return qCeil(descriptionheight); 0206 } 0207 0208 /*! 0209 \reimp 0210 */ 0211 QSize KexiCommandLinkButton::minimumSizeHint() const 0212 { 0213 QSize size = sizeHint(); 0214 int minimumHeight = qMax(d->descriptionOffset() + d->bottomMargin(), 0215 icon().actualSize(iconSize()).height() + d->topMargin()); 0216 size.setHeight(minimumHeight); 0217 return size; 0218 } 0219 0220 /*! 0221 Constructs a command link with no text and a \a parent. 0222 */ 0223 0224 KexiCommandLinkButton::KexiCommandLinkButton(QWidget *parent) 0225 : KexiPushButton(parent), d(new KexiCommandLinkButtonPrivate(this)) 0226 { 0227 d->init(); 0228 } 0229 0230 /*! 0231 Constructs a command link with the parent \a parent and the text \a 0232 text. 0233 */ 0234 0235 KexiCommandLinkButton::KexiCommandLinkButton(const QString &text, QWidget *parent) 0236 : KexiPushButton(parent), d(new KexiCommandLinkButtonPrivate(this)) 0237 { 0238 setText(text); 0239 d->init(); 0240 } 0241 0242 /*! 0243 Constructs a command link with a \a text, a \a description, and a \a parent. 0244 */ 0245 KexiCommandLinkButton::KexiCommandLinkButton(const QString &text, const QString &description, QWidget *parent) 0246 : KexiPushButton(parent), d(new KexiCommandLinkButtonPrivate(this)) 0247 { 0248 setText(text); 0249 setDescription(description); 0250 d->init(); 0251 } 0252 0253 KexiCommandLinkButton::~KexiCommandLinkButton() 0254 { 0255 delete d; 0256 } 0257 0258 /*! \reimp */ 0259 bool KexiCommandLinkButton::event(QEvent *e) 0260 { 0261 return QPushButton::event(e); 0262 } 0263 0264 /*! \reimp */ 0265 QSize KexiCommandLinkButton::sizeHint() const 0266 { 0267 // Standard size hints from UI specs 0268 // Without note: 135, 41 0269 // With note: 135, 60 0270 0271 QSize size = QPushButton::sizeHint(); 0272 QFontMetrics fm(d->titleFont()); 0273 int textWidth = qMax(fm.width(text()), 135); 0274 int buttonWidth = textWidth + d->textOffset() + d->rightMargin(); 0275 int heightWithoutDescription = d->descriptionOffset() + d->bottomMargin(); 0276 0277 size.setWidth(qMax(size.width(), buttonWidth)); 0278 size.setHeight(qMax(d->description.isEmpty() ? 41 : 60, 0279 heightWithoutDescription + d->descriptionHeight(buttonWidth))); 0280 return size; 0281 } 0282 0283 /*! \reimp */ 0284 int KexiCommandLinkButton::heightForWidth(int width) const 0285 { 0286 int heightWithoutDescription = d->descriptionOffset() + d->bottomMargin(); 0287 // find the width available for the description area 0288 return qMax(heightWithoutDescription + d->descriptionHeight(width), 0289 icon().actualSize(iconSize()).height() + d->topMargin() + 0290 d->bottomMargin()); 0291 } 0292 0293 /*! \reimp */ 0294 void KexiCommandLinkButton::paintEvent(QPaintEvent *) 0295 { 0296 QStylePainter p(this); 0297 p.save(); 0298 0299 QStyleOptionButton option; 0300 initStyleOption(&option); 0301 0302 //Enable command link appearance on Vista 0303 option.features |= QStyleOptionButton::CommandLinkButton; 0304 option.text.clear(); 0305 option.icon = QIcon(); //we draw this ourselves 0306 QSize pixmapSize = icon().actualSize(iconSize()); 0307 0308 int vOffset = isDown() ? style()->pixelMetric(QStyle::PM_ButtonShiftVertical) : 0; 0309 int hOffset = isDown() ? style()->pixelMetric(QStyle::PM_ButtonShiftHorizontal) : 0; 0310 0311 //Draw icon 0312 p.drawControl(QStyle::CE_PushButton, option); 0313 if (!icon().isNull()) 0314 p.drawPixmap(d->leftMargin() + hOffset, d->topMargin() + vOffset, 0315 icon().pixmap(pixmapSize, isEnabled() ? QIcon::Normal : QIcon::Disabled, 0316 isChecked() ? QIcon::On : QIcon::Off)); 0317 0318 //Draw title 0319 QColor textColor = palette().buttonText().color(); 0320 if (isEnabled() && d->usingVistaStyle()) { 0321 textColor = QColor(21, 28, 85); 0322 if (underMouse() && !isDown()) 0323 textColor = QColor(7, 64, 229); 0324 //A simple text color transition 0325 d->currentColor = d->mergedColors(textColor, d->currentColor, 60); 0326 option.palette.setColor(QPalette::ButtonText, d->currentColor); 0327 } 0328 int arrowWidth = d->isArrowVisible ? 12 : 0; 0329 int textflags = Qt::TextShowMnemonic | Qt::AlignTop; 0330 if (!style()->styleHint(QStyle::SH_UnderlineShortcut, &option, this)) 0331 textflags |= Qt::TextHideMnemonic; 0332 0333 p.setFont(d->titleFont()); 0334 p.drawItemText(d->titleRect().adjusted(0, 0, -arrowWidth, 0).translated(hOffset, vOffset), 0335 textflags, option.palette, isEnabled(), text(), QPalette::ButtonText); 0336 0337 //Draw description 0338 textflags = Qt::TextWordWrap | Qt::ElideRight | Qt::AlignTop; 0339 p.setFont(d->descriptionFont()); 0340 p.drawItemText(d->descriptionRect().adjusted(0, 0, -arrowWidth, 0).translated(hOffset, vOffset), textflags, 0341 option.palette, isEnabled(), description(), QPalette::ButtonText); 0342 0343 //Optional arrow 0344 if (d->isArrowVisible) { 0345 const int margin = style()->pixelMetric(QStyle::PM_ButtonMargin, &option, this); 0346 option.rect.setX(option.rect.width() - margin * 2 - 10); 0347 style()->drawPrimitive(QStyle::PE_IndicatorArrowRight, &option, &p, this); 0348 } 0349 p.restore(); 0350 } 0351 0352 void KexiCommandLinkButton::setDescription(const QString &description) 0353 { 0354 d->description = description; 0355 updateGeometry(); 0356 update(); 0357 } 0358 0359 QString KexiCommandLinkButton::description() const 0360 { 0361 return d->description; 0362 } 0363 0364 bool KexiCommandLinkButton::isArrowVisible() const 0365 { 0366 return d->isArrowVisible; 0367 } 0368 0369 void KexiCommandLinkButton::setArrowVisible(bool visible) 0370 { 0371 if (d->isArrowVisible == visible) { 0372 return; 0373 } 0374 d->isArrowVisible = visible; 0375 update(); 0376 }