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 | |
47 | namespace Plasma |
48 | { |
49 | |
50 | class 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 | |
76 | QFont 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 | |
84 | QRect 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 | |
120 | QRect 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 | |
145 | Delegate::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 | |
154 | Delegate::~Delegate() |
155 | { |
156 | delete d; |
157 | } |
158 | |
159 | void Delegate::setRoleMapping(SpecificRoles role, int actual) |
160 | { |
161 | d->roles[role] = actual; |
162 | } |
163 | |
164 | int Delegate::roleMapping(SpecificRoles role) const |
165 | { |
166 | return d->roles[role]; |
167 | } |
168 | |
169 | QRect 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 | |
188 | QRect 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 | |
207 | QRect 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 | |
218 | void 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 | |
461 | QSize 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 | |
485 | bool Delegate::showToolTip() const |
486 | { |
487 | return d->m_showToolTip; |
488 | } |
489 | |
490 | } |
491 | |
492 | #include "delegate.moc" |
493 | |
494 | |