1/*
2 Copyright 2007 Robert Knight <robertknight@gmail.com>
3 Copyright 2007 Kevin Ottens <ervin@kde.org>
4 Copyright 2008 Marco Martin <notmart@gmail.com>
5
6 This library is free software; you can redistribute it and/or
7 modify it under the terms of the GNU Library General Public
8 License as published by the Free Software Foundation; either
9 version 2 of the License, or (at your option) any later version.
10
11 This library is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 Library General Public License for more details.
15
16 You should have received a copy of the GNU Library General Public License
17 along with this library; see the file COPYING.LIB. If not, write to
18 the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
19 Boston, MA 02110-1301, USA.
20*/
21
22// Own
23#include "delegate.h"
24
25#include <cmath>
26#include <math.h>
27
28// Qt
29#include <QApplication>
30#include <QFontMetrics>
31#include <QIcon>
32#include <QModelIndex>
33#include <QPainter>
34#include <QStyleOptionViewItem>
35
36// KDE
37#include <kcolorutils.h>
38#include <kdebug.h>
39#include <kglobal.h>
40#include <kglobalsettings.h>
41#include <kcolorscheme.h>
42
43// plasma
44#include <plasma/paintutils.h>
45#include <plasma/framesvg.h>
46
47namespace Plasma
48{
49
50class DelegatePrivate
51{
52 public:
53 DelegatePrivate() { }
54
55 ~DelegatePrivate() { }
56
57 QFont fontForSubTitle(const QFont &titleFont) const;
58 QRect titleRect(const QStyleOptionViewItem &option, const QModelIndex &index) const;
59 QRect subTitleRect(const QStyleOptionViewItem &option, const QModelIndex &index) const;
60
61 QMap<int, int> roles;
62
63 static const int ICON_TEXT_MARGIN = 10;
64 static const int TEXT_RIGHT_MARGIN = 5;
65 static const int ACTION_ICON_SIZE = 22;
66
67 static const int ITEM_LEFT_MARGIN = 5;
68 static const int ITEM_RIGHT_MARGIN = 5;
69 static const int ITEM_TOP_MARGIN = 5;
70 static const int ITEM_BOTTOM_MARGIN = 5;
71
72 bool m_showToolTip;
73 FrameSvg *svg;
74};
75
76QFont DelegatePrivate::fontForSubTitle(const QFont &titleFont) const
77{
78 QFont subTitleFont = titleFont;
79 subTitleFont.setPointSize(qMax(subTitleFont.pointSize() - 2,
80 KGlobalSettings::smallestReadableFont().pointSize()));
81 return subTitleFont;
82}
83
84QRect DelegatePrivate::titleRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
85{
86 QFont font(option.font);
87 font.setBold(true);
88 QFontMetrics fm(font);
89
90 Qt::Alignment textAlignment =
91 option.decorationAlignment & Qt::AlignRight ? Qt::AlignRight : Qt::AlignLeft;
92
93 QRect emptyRect;
94 if (option.direction == Qt::LeftToRight) {
95 emptyRect = option.rect.adjusted(
96 option.decorationSize.width() + ICON_TEXT_MARGIN + ITEM_LEFT_MARGIN,
97 ITEM_TOP_MARGIN, -ITEM_RIGHT_MARGIN, -ITEM_BOTTOM_MARGIN);
98 } else {
99 emptyRect = option.rect.adjusted(
100 ITEM_LEFT_MARGIN, ITEM_TOP_MARGIN,
101 -ITEM_RIGHT_MARGIN - option.decorationSize.width() - ICON_TEXT_MARGIN, -ITEM_BOTTOM_MARGIN);
102 }
103
104 if (emptyRect.width() < 0) {
105 emptyRect.setWidth(0);
106 return emptyRect;
107 }
108
109 QRect textRect = QStyle::alignedRect(
110 option.direction,
111 textAlignment,
112 fm.boundingRect(index.data(Qt::DisplayRole).toString()).size(),
113 emptyRect);
114
115 textRect.setWidth(textRect.width() + TEXT_RIGHT_MARGIN);
116 textRect.setHeight(emptyRect.height() / 2);
117 return textRect;
118}
119
120QRect DelegatePrivate::subTitleRect(const QStyleOptionViewItem &option,
121 const QModelIndex &index) const
122{
123 QString subTitle = index.data(roles[Delegate::SubTitleRole]).toString();
124
125 QFontMetrics fm(fontForSubTitle(option.font));
126
127 QRect textRect = titleRect(option, index);
128 int right = textRect.right();
129
130 //if title=subtitle subtitle won't be displayed
131 if (subTitle != index.data(Qt::DisplayRole).toString()) {
132 textRect.setWidth(fm.width(" " + subTitle) + TEXT_RIGHT_MARGIN);
133 } else {
134 textRect.setWidth(0);
135 }
136 textRect.translate(0, textRect.height());
137
138 if (option.direction == Qt::RightToLeft) {
139 textRect.moveRight(right);
140 }
141
142 return textRect;
143}
144
145Delegate::Delegate(QObject *parent)
146 : QAbstractItemDelegate(parent),
147 d(new DelegatePrivate)
148{
149 d->svg = new FrameSvg(this);
150 d->svg->setImagePath("widgets/viewitem");
151 d->svg->setElementPrefix("hover");
152}
153
154Delegate::~Delegate()
155{
156 delete d;
157}
158
159void Delegate::setRoleMapping(SpecificRoles role, int actual)
160{
161 d->roles[role] = actual;
162}
163
164int Delegate::roleMapping(SpecificRoles role) const
165{
166 return d->roles[role];
167}
168
169QRect Delegate::rectAfterTitle(const QStyleOptionViewItem &option, const QModelIndex &index) const
170{
171 QRect textRect = d->titleRect(option, index);
172
173 QRect emptyRect(0, textRect.top(), option.rect.width() - textRect.width() - DelegatePrivate::ITEM_LEFT_MARGIN - DelegatePrivate::ITEM_RIGHT_MARGIN - option.decorationSize.width() - DelegatePrivate::ICON_TEXT_MARGIN, textRect.height());
174
175 if (option.direction == Qt::LeftToRight) {
176 emptyRect.moveLeft(textRect.right());
177 } else {
178 emptyRect.moveRight(textRect.left());
179 }
180
181 if (emptyRect.width() < 0) {
182 emptyRect.setWidth(0);
183 }
184
185 return emptyRect;
186}
187
188QRect Delegate::rectAfterSubTitle(const QStyleOptionViewItem &option, const QModelIndex &index) const
189{
190 QRect textRect = d->subTitleRect(option, index);
191
192 QRect emptyRect(0, textRect.top(), option.rect.width() - textRect.width() - DelegatePrivate::ITEM_LEFT_MARGIN - DelegatePrivate::ITEM_RIGHT_MARGIN - option.decorationSize.width() - DelegatePrivate::ICON_TEXT_MARGIN, textRect.height());
193
194 if (option.direction == Qt::LeftToRight) {
195 emptyRect.moveLeft(textRect.right());
196 } else {
197 emptyRect.moveRight(textRect.left());
198 }
199
200 if (emptyRect.width() < 0) {
201 emptyRect.setWidth(0);
202 }
203
204 return emptyRect;
205}
206
207QRect Delegate::emptyRect(const QStyleOptionViewItem &option, const QModelIndex &index) const
208{
209 QRect afterTitleRect = rectAfterTitle(option, index);
210 QRect afterSubTitleRect = rectAfterSubTitle(option, index);
211
212 afterTitleRect.setHeight(afterTitleRect.height() * 2);
213 afterSubTitleRect.setTop(afterTitleRect.top());
214
215 return afterTitleRect.intersected(afterSubTitleRect);
216}
217
218void Delegate::paint(QPainter *painter, const QStyleOptionViewItem &option,
219 const QModelIndex &index) const
220{
221 const bool hover = option.state & (QStyle::State_MouseOver | QStyle::State_Selected);
222
223 QRect contentRect = option.rect;
224 contentRect.setBottom(contentRect.bottom() - 1);
225
226 QRect decorationRect =
227 QStyle::alignedRect(option.direction,
228 option.decorationPosition == QStyleOptionViewItem::Left ?
229 Qt::AlignLeft : Qt::AlignRight,
230 option.decorationSize,
231 contentRect.adjusted(DelegatePrivate::ITEM_LEFT_MARGIN, DelegatePrivate::ITEM_TOP_MARGIN, -DelegatePrivate::ITEM_RIGHT_MARGIN, -DelegatePrivate::ITEM_BOTTOM_MARGIN));
232 decorationRect.moveTop(contentRect.top() + qMax(0, (contentRect.height() - decorationRect.height())) / 2);
233
234 QString titleText = index.data(Qt::DisplayRole).value<QString>();
235 QString subTitleText = index.data(d->roles[SubTitleRole]).value<QString>();
236 //kDebug() << subTitleText;
237
238 QRect titleRect = d->titleRect(option, index);
239 titleRect.moveTopLeft(titleRect.topLeft()-option.rect.topLeft());
240 QRect subTitleRect = d->subTitleRect(option, index);
241 subTitleRect.moveTopLeft(subTitleRect.topLeft()-option.rect.topLeft());
242
243 if (subTitleText == titleText) {
244 subTitleText.clear();
245 }
246
247 QFont titleFont(option.font);
248
249 // draw icon
250 QIcon decorationIcon = index.data(Qt::DecorationRole).value<QIcon>();
251
252 if (index.data(d->roles[ColumnTypeRole]).toInt() == SecondaryActionColumn) {
253 if (hover) {
254 // Only draw on hover
255 const int delta = floor((qreal)(option.decorationSize.width() - DelegatePrivate::ACTION_ICON_SIZE) / 2.0);
256 decorationRect.adjust(delta, delta-1, -delta-1, -delta);
257 decorationIcon.paint(painter, decorationRect, option.decorationAlignment);
258 }
259 } else {
260 // as default always draw as main column
261 decorationIcon.paint(painter, decorationRect, option.decorationAlignment);
262 }
263
264 QPixmap buffer(option.rect.size());
265 buffer.fill(Qt::transparent);
266 QPainter p(&buffer);
267 // draw title
268 p.setFont(titleFont);
269 if (option.palette.color(QPalette::Base).alpha() > 0) {
270 p.setPen(QPen(KColorScheme(QPalette::Active).foreground(KColorScheme::NormalText), 1));
271 } else {
272 p.setPen(Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor));
273 }
274 p.drawText(titleRect, Qt::AlignLeft|Qt::AlignVCenter, titleText);
275
276 // draw sub-title, BUT only if:
277 // * SubTitleMandatoryRole is defined and model returns 'true'
278 // * SubTitleMandatoryRole is not defined and the adjasent model indexes
279 // have the same contents of the Qt::DisplayRole
280 // * when model doesn't provide a valid data for SubTitleMandatory role
281 // we also show title on mouse hover
282 //
283 // the rationale for this is that subtitle text should in most cases not be
284 // required to understand the item itself and that showing all the subtexts in a
285 // listing makes the information density very high, impacting both the speed at
286 // which one can scan the list visually and the aesthetic qualities of the listing.
287 bool drawSubTitle = !subTitleText.isEmpty();
288
289 if (drawSubTitle && !hover) {
290 // If the model wants to have exact control for subtitles showing
291 // it is expected to return a valid data for SubTitleMandatoryRole.
292 // If it doesn't return a valid data for this role
293 // then by default we well be showing a subtitles for
294 // adjasent items with the same content (see comments below too)
295 QVariant mandatoryRoleData = index.data(d->roles[SubTitleMandatoryRole]);
296 if (mandatoryRoleData.isValid()) {
297 drawSubTitle = mandatoryRoleData.value<bool>();
298 } else {
299 bool uniqueTitle = true;
300 QModelIndex sib = index.sibling(index.row() + 1, index.column());
301 if (sib.isValid()) {
302 uniqueTitle = sib.data(Qt::DisplayRole).value<QString>() != titleText;
303 }
304
305 if (uniqueTitle) {
306 sib = index.sibling(index.row() + -1, index.column());
307 if (sib.isValid()) {
308 uniqueTitle = sib.data(Qt::DisplayRole).value<QString>() != titleText;
309 }
310 }
311
312 drawSubTitle = !uniqueTitle;
313 }
314 }
315
316
317 if (drawSubTitle) {
318 if (option.palette.color(QPalette::Base).alpha() > 0) {
319 p.setPen(QPen(KColorScheme(QPalette::Active).foreground(KColorScheme::InactiveText), 1));
320 } else {
321 QColor textColor = Plasma::Theme::defaultTheme()->color(Plasma::Theme::TextColor);
322 textColor.setAlphaF(0.6);
323 p.setPen(textColor);
324 }
325
326 const QFont subTitleFont = d->fontForSubTitle(option.font);
327 p.setFont(subTitleFont);
328 p.drawText(subTitleRect, Qt::AlignLeft|Qt::AlignVCenter, subTitleText);
329 }
330 p.end();
331
332
333 d->m_showToolTip = false;
334
335 const QColor gradientColor = KColorScheme(QPalette::Active).background(KColorScheme::NormalBackground).color();
336
337 if (option.direction == Qt::LeftToRight) {
338 if (((titleRect.width() + decorationRect.width() + 10) > option.rect.width() ||
339 (subTitleRect.width() + decorationRect.width() + 15) > option.rect.width()) &&
340 (titleRect.width() > 120 || subTitleRect.width() > 120)) {
341 QPainter p(&buffer);
342 p.setCompositionMode(QPainter::CompositionMode_DestinationOut);
343 p.setPen(Qt::NoPen);
344 QLinearGradient gr;
345 QRect gradientRect(option.rect.width() - 60, titleRect.y(),
346 80, titleRect.height() + subTitleRect.height());
347 // draw it on the right side
348 gr.setStart(gradientRect.topLeft());
349 gr.setFinalStop(gradientRect.topRight());
350 gr.setColorAt(0.0, Qt::transparent);
351 gr.setColorAt(0.7, gradientColor);
352 p.setBrush(QBrush(gr));
353 p.drawRect(gradientRect);
354 d->m_showToolTip = true;
355 p.end();
356 }
357
358 } else {
359 if (((titleRect.width() + decorationRect.width() + 10) > option.rect.width() ||
360 (subTitleRect.width() + decorationRect.width() + 15 )> option.rect.width()) &&
361 (titleRect.width() > 120 || subTitleRect.width() > 120)) {
362 buffer.fill(Qt::transparent);
363 QPainter p(&buffer);
364 p.setCompositionMode(QPainter::CompositionMode_DestinationOut);
365 p.setPen(Qt::NoPen);
366 QLinearGradient gr;
367 QRect gradientRect(option.rect.x() - 55, titleRect.y(),
368 60, titleRect.height() + subTitleRect.height());
369 gr.setStart(gradientRect.topRight());
370 gr.setFinalStop(gradientRect.topLeft());
371 gr.setColorAt(0.0, Qt::transparent);
372 gr.setColorAt(0.6, gradientColor);
373 p.setBrush(QBrush(gr));
374 p.drawRect(gradientRect);
375
376 d->m_showToolTip = true;
377 p.end();
378 }
379 }
380
381 painter->drawPixmap(option.rect, buffer, buffer.rect());
382
383 if (hover) {
384 painter->save();
385 painter->setRenderHint(QPainter::Antialiasing);
386
387 const int column = index.column();
388 const int columns = index.model()->columnCount();
389 int roundedRadius = 5;
390 const bool useSvg = option.palette.color(QPalette::Base).alpha() == 0;
391
392 // use a slightly translucent version of the palette's highlight color
393 // for the background
394 QColor backgroundColor = option.palette.color(QPalette::Highlight);
395 backgroundColor.setAlphaF(0.2);
396
397 QColor backgroundColor2 = option.palette.color(QPalette::Highlight);
398 backgroundColor2.setAlphaF(0.5);
399
400 QRect highlightRect = option.rect;
401 if (!useSvg) {
402 highlightRect.adjust(2, 2, -2, -2);
403 }
404
405 QPen outlinePen(backgroundColor, 2);
406
407 if (column == 0) {
408 //clip right (or left for rtl languages) to make the connection with the next column
409 if (columns > 1) {
410 if (useSvg) {
411 roundedRadius = d->svg->marginSize(Plasma::RightMargin);
412 }
413 painter->setClipRect(option.rect);
414 highlightRect.adjust(0, 0, roundedRadius, 0);
415 }
416
417 QLinearGradient gradient(highlightRect.topLeft(), highlightRect.topRight());
418
419 //reverse the gradient
420 if (option.direction == Qt::RightToLeft) {
421 gradient.setStart(highlightRect.topRight());
422 gradient.setFinalStop(highlightRect.topLeft());
423 }
424
425 gradient.setColorAt(0, backgroundColor);
426 gradient.setColorAt(((qreal)titleRect.width()/3.0) / (qreal)highlightRect.width(), backgroundColor2);
427 gradient.setColorAt(0.7, backgroundColor);
428 outlinePen.setBrush(gradient);
429 //last column, clip left (right for rtl)
430 } else if (column == columns-1) {
431 if (useSvg) {
432 roundedRadius = d->svg->marginSize(Plasma::LeftMargin);
433 }
434 painter->setClipRect(option.rect);
435 highlightRect.adjust(-roundedRadius, 0, 0, 0);
436
437 //column < columns-1; clip both ways
438 } else {
439 if (useSvg) {
440 roundedRadius = d->svg->marginSize(Plasma::LeftMargin);
441 }
442 painter->setClipRect(option.rect);
443 highlightRect.adjust(-roundedRadius, 0, +roundedRadius, 0);
444 }
445
446 //if the view is transparent paint as plasma, otherwise paint with kde colors
447 if (useSvg) {
448 d->svg->resizeFrame(highlightRect.size());
449 d->svg->paintFrame(painter, highlightRect.topLeft());
450 } else {
451 painter->setPen(outlinePen);
452 painter->drawPath(PaintUtils::roundedRectangle(highlightRect, roundedRadius));
453 }
454
455 painter->restore();
456 }
457
458
459}
460
461QSize Delegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const
462{
463 Q_UNUSED(index)
464 QSize size = option.rect.size();
465
466 QFontMetrics metrics(option.font);
467
468 QFontMetrics subMetrics(d->fontForSubTitle(option.font));
469 size.setHeight(qMax(option.decorationSize.height(), qMax(size.height(), metrics.height() + subMetrics.ascent()) + 3) + 4);
470// kDebug() << "size hint is" << size << (metrics.height() + subMetrics.ascent());
471
472 const bool useSvg = option.palette.color(QPalette::Base).alpha() == 0;
473
474 if (useSvg) {
475 qreal left, top, right, bottom;
476 d->svg->getMargins(left, top, right, bottom);
477 size += QSize(left+right, top+bottom);
478 } else {
479 size *= 1.1;
480 }
481
482 return size;
483}
484
485bool Delegate::showToolTip() const
486{
487 return d->m_showToolTip;
488}
489
490}
491
492#include "delegate.moc"
493
494