]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemlistwidget.cpp
Allow showing Nepomuk metadata inside views
[dolphin.git] / src / kitemviews / kfileitemlistwidget.cpp
1 /***************************************************************************
2 * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
3 * *
4 * This program is free software; you can redistribute it and/or modify *
5 * it under the terms of the GNU General Public License as published by *
6 * the Free Software Foundation; either version 2 of the License, or *
7 * (at your option) any later version. *
8 * *
9 * This program is distributed in the hope that it will be useful, *
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
12 * GNU General Public License for more details. *
13 * *
14 * You should have received a copy of the GNU General Public License *
15 * along with this program; if not, write to the *
16 * Free Software Foundation, Inc., *
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA *
18 ***************************************************************************/
19
20 #include "kfileitemlistwidget.h"
21
22 #include "kfileitemclipboard_p.h"
23 #include "kfileitemlistview.h"
24 #include "kfileitemmodel.h"
25 #include "kpixmapmodifier_p.h"
26
27 #include <KIcon>
28 #include <KIconEffect>
29 #include <KIconLoader>
30 #include <KLocale>
31 #include <KStringHandler>
32 #include <KDebug>
33
34 #include <QFontMetricsF>
35 #include <QGraphicsSceneResizeEvent>
36 #include <QPainter>
37 #include <QStyleOption>
38 #include <QTextLayout>
39 #include <QTextLine>
40
41 // #define KFILEITEMLISTWIDGET_DEBUG
42
43 KFileItemListWidget::KFileItemListWidget(QGraphicsItem* parent) :
44 KItemListWidget(parent),
45 m_isCut(false),
46 m_isHidden(false),
47 m_isExpandable(false),
48 m_supportsItemExpanding(false),
49 m_dirtyLayout(true),
50 m_dirtyContent(true),
51 m_dirtyContentRoles(),
52 m_layout(IconsLayout),
53 m_pixmapPos(),
54 m_pixmap(),
55 m_scaledPixmapSize(),
56 m_iconRect(),
57 m_hoverPixmap(),
58 m_textInfo(),
59 m_textRect(),
60 m_sortedVisibleRoles(),
61 m_expansionArea(),
62 m_customTextColor(),
63 m_additionalInfoTextColor(),
64 m_overlay()
65 {
66 }
67
68 KFileItemListWidget::~KFileItemListWidget()
69 {
70 qDeleteAll(m_textInfo);
71 m_textInfo.clear();
72 }
73
74 void KFileItemListWidget::setLayout(Layout layout)
75 {
76 if (m_layout != layout) {
77 m_layout = layout;
78 m_dirtyLayout = true;
79 updateAdditionalInfoTextColor();
80 update();
81 }
82 }
83
84 KFileItemListWidget::Layout KFileItemListWidget::layout() const
85 {
86 return m_layout;
87 }
88
89 void KFileItemListWidget::setSupportsItemExpanding(bool supportsItemExpanding)
90 {
91 if (m_supportsItemExpanding != supportsItemExpanding) {
92 m_supportsItemExpanding = supportsItemExpanding;
93 m_dirtyLayout = true;
94 update();
95 }
96 }
97
98 bool KFileItemListWidget::supportsItemExpanding() const
99 {
100 return m_supportsItemExpanding;
101 }
102
103 void KFileItemListWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
104 {
105 const_cast<KFileItemListWidget*>(this)->triggerCacheRefreshing();
106
107 KItemListWidget::paint(painter, option, widget);
108
109 if (!m_expansionArea.isEmpty()) {
110 drawSiblingsInformation(painter);
111 }
112
113 const KItemListStyleOption& itemListStyleOption = styleOption();
114 if (isHovered()) {
115 // Blend the unhovered and hovered pixmap if the hovering
116 // animation is ongoing
117 if (hoverOpacity() < 1.0) {
118 drawPixmap(painter, m_pixmap);
119 }
120
121 const qreal opacity = painter->opacity();
122 painter->setOpacity(hoverOpacity() * opacity);
123 drawPixmap(painter, m_hoverPixmap);
124 painter->setOpacity(opacity);
125 } else {
126 drawPixmap(painter, m_pixmap);
127 }
128
129 painter->setFont(itemListStyleOption.font);
130 painter->setPen(textColor());
131 const TextInfo* textInfo = m_textInfo.value("name");
132 painter->drawStaticText(textInfo->pos, textInfo->staticText);
133
134 bool clipAdditionalInfoBounds = false;
135 if (m_supportsItemExpanding) {
136 // Prevent a possible overlapping of the additional-information texts
137 // with the icon. This can happen if the user has minimized the width
138 // of the name-column to a very small value.
139 const qreal minX = m_pixmapPos.x() + m_pixmap.width() + 4 * itemListStyleOption.padding;
140 if (textInfo->pos.x() + columnWidth("name") > minX) {
141 clipAdditionalInfoBounds = true;
142 painter->save();
143 painter->setClipRect(minX, 0, size().width() - minX, size().height(), Qt::IntersectClip);
144 }
145 }
146
147 painter->setPen(m_additionalInfoTextColor);
148 painter->setFont(itemListStyleOption.font);
149
150 for (int i = 1; i < m_sortedVisibleRoles.count(); ++i) {
151 const TextInfo* textInfo = m_textInfo.value(m_sortedVisibleRoles[i]);
152 painter->drawStaticText(textInfo->pos, textInfo->staticText);
153 }
154
155 if (clipAdditionalInfoBounds) {
156 painter->restore();
157 }
158
159 #ifdef KFILEITEMLISTWIDGET_DEBUG
160 painter->setBrush(Qt::NoBrush);
161 painter->setPen(Qt::green);
162 painter->drawRect(m_iconRect);
163
164 painter->setPen(Qt::red);
165 painter->drawText(QPointF(0, itemListStyleOption.fontMetrics.height()), QString::number(index()));
166 painter->drawRect(rect());
167 #endif
168 }
169
170 QRectF KFileItemListWidget::iconRect() const
171 {
172 const_cast<KFileItemListWidget*>(this)->triggerCacheRefreshing();
173 return m_iconRect;
174 }
175
176 QRectF KFileItemListWidget::textRect() const
177 {
178 const_cast<KFileItemListWidget*>(this)->triggerCacheRefreshing();
179 return m_textRect;
180 }
181
182 QRectF KFileItemListWidget::expansionToggleRect() const
183 {
184 const_cast<KFileItemListWidget*>(this)->triggerCacheRefreshing();
185 return m_isExpandable ? m_expansionArea : QRectF();
186 }
187
188 QRectF KFileItemListWidget::selectionToggleRect() const
189 {
190 const_cast<KFileItemListWidget*>(this)->triggerCacheRefreshing();
191
192 const int iconHeight = styleOption().iconSize;
193
194 int toggleSize = KIconLoader::SizeSmall;
195 if (iconHeight >= KIconLoader::SizeEnormous) {
196 toggleSize = KIconLoader::SizeMedium;
197 } else if (iconHeight >= KIconLoader::SizeLarge) {
198 toggleSize = KIconLoader::SizeSmallMedium;
199 }
200
201 QPointF pos = iconRect().topLeft();
202
203 // If the selection toggle has a very small distance to the
204 // widget borders, the size of the selection toggle will get
205 // increased to prevent an accidental clicking of the item
206 // when trying to hit the toggle.
207 const int widgetHeight = size().height();
208 const int widgetWidth = size().width();
209 const int minMargin = 2;
210
211 if (toggleSize + minMargin * 2 >= widgetHeight) {
212 pos.rx() -= (widgetHeight - toggleSize) / 2;
213 toggleSize = widgetHeight;
214 pos.setY(0);
215 }
216 if (toggleSize + minMargin * 2 >= widgetWidth) {
217 pos.ry() -= (widgetWidth - toggleSize) / 2;
218 toggleSize = widgetWidth;
219 pos.setX(0);
220 }
221
222 return QRectF(pos, QSizeF(toggleSize, toggleSize));
223 }
224
225 QSizeF KFileItemListWidget::itemSizeHint(int index, const KItemListView* view)
226 {
227 const QHash<QByteArray, QVariant> values = view->model()->data(index);
228 const KItemListStyleOption& option = view->styleOption();
229 const int additionalRolesCount = qMax(view->visibleRoles().count() - 1, 0);
230
231 switch (static_cast<const KFileItemListView*>(view)->itemLayout()) {
232 case IconsLayout: {
233 const QString text = KStringHandler::preProcessWrap(values["name"].toString());
234
235 const qreal maxWidth = view->itemSize().width() - 2 * option.padding;
236 int textLinesCount = 0;
237 QTextLine line;
238
239 // Calculate the number of lines required for wrapping the name
240 QTextOption textOption(Qt::AlignHCenter);
241 textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
242
243 QTextLayout layout(text, option.font);
244 layout.setTextOption(textOption);
245 layout.beginLayout();
246 while ((line = layout.createLine()).isValid()) {
247 line.setLineWidth(maxWidth);
248 line.naturalTextWidth();
249 ++textLinesCount;
250 }
251 layout.endLayout();
252
253 // Add one line for each additional information
254 textLinesCount += additionalRolesCount;
255
256 const qreal height = textLinesCount * option.fontMetrics.height() +
257 option.iconSize +
258 option.padding * 3;
259 return QSizeF(view->itemSize().width(), height);
260 }
261
262 case CompactLayout: {
263 // For each row exactly one role is shown. Calculate the maximum required width that is necessary
264 // to show all roles without horizontal clipping.
265 qreal maximumRequiredWidth = 0.0;
266
267 foreach (const QByteArray& role, view->visibleRoles()) {
268 const QString text = KFileItemListWidget::roleText(role, values);
269 const qreal requiredWidth = option.fontMetrics.width(text);
270 maximumRequiredWidth = qMax(maximumRequiredWidth, requiredWidth);
271 }
272
273 const qreal width = option.padding * 4 + option.iconSize + maximumRequiredWidth;
274 const qreal height = option.padding * 2 + qMax(option.iconSize, (1 + additionalRolesCount) * option.fontMetrics.height());
275 return QSizeF(width, height);
276 }
277
278 case DetailsLayout: {
279 const qreal height = option.padding * 2 + qMax(option.iconSize, option.fontMetrics.height());
280 return QSizeF(-1, height);
281 }
282
283 default:
284 Q_ASSERT(false);
285 break;
286 }
287
288 return QSize();
289 }
290
291 qreal KFileItemListWidget::preferredRoleColumnWidth(const QByteArray& role,
292 int index,
293 const KItemListView* view)
294 {
295 qreal width = 0;
296
297 const QHash<QByteArray, QVariant> values = view->model()->data(index);
298 const KItemListStyleOption& option = view->styleOption();
299
300 const QString text = KFileItemListWidget::roleText(role, values);
301 if (!text.isEmpty()) {
302 const qreal columnPadding = option.padding * 3;
303 width = qMax(width, qreal(2 * columnPadding + option.fontMetrics.width(text)));
304 }
305
306 if (role == "name") {
307 // Increase the width by the expansion-toggle and the current expansion level
308 const int expandedParentsCount = values.value("expandedParentsCount", 0).toInt();
309 width += option.padding + (expandedParentsCount + 1) * view->itemSize().height() + KIconLoader::SizeSmall;
310
311 // Increase the width by the required space for the icon
312 width += option.padding * 2 + option.iconSize;
313 }
314
315 return width;
316 }
317
318 void KFileItemListWidget::invalidateCache()
319 {
320 m_dirtyLayout = true;
321 m_dirtyContent = true;
322 }
323
324 void KFileItemListWidget::refreshCache()
325 {
326 }
327
328 void KFileItemListWidget::setTextColor(const QColor& color)
329 {
330 if (color != m_customTextColor) {
331 m_customTextColor = color;
332 updateAdditionalInfoTextColor();
333 update();
334 }
335 }
336
337 QColor KFileItemListWidget::textColor() const
338 {
339 if (m_customTextColor.isValid() && !isSelected()) {
340 return m_customTextColor;
341 }
342
343 const QPalette::ColorGroup group = isActiveWindow() ? QPalette::Active : QPalette::Inactive;
344 const QPalette::ColorRole role = isSelected() ? QPalette::HighlightedText : QPalette::Text;
345 return styleOption().palette.brush(group, role).color();
346 }
347
348 void KFileItemListWidget::setOverlay(const QPixmap& overlay)
349 {
350 m_overlay = overlay;
351 m_dirtyContent = true;
352 update();
353 }
354
355 QPixmap KFileItemListWidget::overlay() const
356 {
357 return m_overlay;
358 }
359
360 void KFileItemListWidget::dataChanged(const QHash<QByteArray, QVariant>& current,
361 const QSet<QByteArray>& roles)
362 {
363 Q_UNUSED(current);
364
365 m_dirtyContent = true;
366
367 QSet<QByteArray> dirtyRoles;
368 if (roles.isEmpty()) {
369 dirtyRoles = visibleRoles().toSet();
370 dirtyRoles.insert("iconPixmap");
371 dirtyRoles.insert("iconName");
372 } else {
373 dirtyRoles = roles;
374 }
375
376 QSetIterator<QByteArray> it(dirtyRoles);
377 while (it.hasNext()) {
378 const QByteArray& role = it.next();
379 m_dirtyContentRoles.insert(role);
380 }
381 }
382
383 void KFileItemListWidget::visibleRolesChanged(const QList<QByteArray>& current,
384 const QList<QByteArray>& previous)
385 {
386 Q_UNUSED(previous);
387 m_sortedVisibleRoles = current;
388 m_dirtyLayout = true;
389 }
390
391 void KFileItemListWidget::columnWidthChanged(const QByteArray& role,
392 qreal current,
393 qreal previous)
394 {
395 Q_UNUSED(role);
396 Q_UNUSED(current);
397 Q_UNUSED(previous);
398 m_dirtyLayout = true;
399 }
400
401 void KFileItemListWidget::styleOptionChanged(const KItemListStyleOption& current,
402 const KItemListStyleOption& previous)
403 {
404 Q_UNUSED(current);
405 Q_UNUSED(previous);
406 updateAdditionalInfoTextColor();
407 m_dirtyLayout = true;
408 }
409
410 void KFileItemListWidget::hoveredChanged(bool hovered)
411 {
412 Q_UNUSED(hovered);
413 m_dirtyLayout = true;
414 }
415
416 void KFileItemListWidget::selectedChanged(bool selected)
417 {
418 Q_UNUSED(selected);
419 updateAdditionalInfoTextColor();
420 }
421
422 void KFileItemListWidget::siblingsInformationChanged(const QBitArray& current, const QBitArray& previous)
423 {
424 Q_UNUSED(current);
425 Q_UNUSED(previous);
426 m_dirtyLayout = true;
427 }
428
429
430 void KFileItemListWidget::resizeEvent(QGraphicsSceneResizeEvent* event)
431 {
432 KItemListWidget::resizeEvent(event);
433 m_dirtyLayout = true;
434 }
435
436 void KFileItemListWidget::showEvent(QShowEvent* event)
437 {
438 KItemListWidget::showEvent(event);
439
440 // Listen to changes of the clipboard to mark the item as cut/uncut
441 KFileItemClipboard* clipboard = KFileItemClipboard::instance();
442
443 const KUrl itemUrl = data().value("url").value<KUrl>();
444 m_isCut = clipboard->isCut(itemUrl);
445
446 connect(clipboard, SIGNAL(cutItemsChanged()),
447 this, SLOT(slotCutItemsChanged()));
448 }
449
450 void KFileItemListWidget::hideEvent(QHideEvent* event)
451 {
452 disconnect(KFileItemClipboard::instance(), SIGNAL(cutItemsChanged()),
453 this, SLOT(slotCutItemsChanged()));
454
455 KItemListWidget::hideEvent(event);
456 }
457
458 void KFileItemListWidget::slotCutItemsChanged()
459 {
460 const KUrl itemUrl = data().value("url").value<KUrl>();
461 const bool isCut = KFileItemClipboard::instance()->isCut(itemUrl);
462 if (m_isCut != isCut) {
463 m_isCut = isCut;
464 m_pixmap = QPixmap();
465 m_dirtyContent = true;
466 update();
467 }
468 }
469
470 void KFileItemListWidget::triggerCacheRefreshing()
471 {
472 if ((!m_dirtyContent && !m_dirtyLayout) || index() < 0) {
473 return;
474 }
475
476 refreshCache();
477
478 const QHash<QByteArray, QVariant> values = data();
479 m_isExpandable = m_supportsItemExpanding && values["isExpandable"].toBool();
480 m_isHidden = values["name"].toString().startsWith(QLatin1Char('.'));
481
482 updateExpansionArea();
483 updateTextsCache();
484 updatePixmapCache();
485
486 m_dirtyLayout = false;
487 m_dirtyContent = false;
488 m_dirtyContentRoles.clear();
489 }
490
491 void KFileItemListWidget::updateExpansionArea()
492 {
493 if (m_supportsItemExpanding) {
494 const QHash<QByteArray, QVariant> values = data();
495 Q_ASSERT(values.contains("expandedParentsCount"));
496 const int expandedParentsCount = values.value("expandedParentsCount", 0).toInt();
497 if (expandedParentsCount >= 0) {
498 const qreal widgetHeight = size().height();
499 const qreal inc = (widgetHeight - KIconLoader::SizeSmall) / 2;
500 const qreal x = expandedParentsCount * widgetHeight + inc;
501 const qreal y = inc;
502 m_expansionArea = QRectF(x, y, KIconLoader::SizeSmall, KIconLoader::SizeSmall);
503 return;
504 }
505 }
506
507 m_expansionArea = QRectF();
508 }
509
510 void KFileItemListWidget::updatePixmapCache()
511 {
512 // Precondition: Requires already updated m_textPos values to calculate
513 // the remaining height when the alignment is vertical.
514
515 const QSizeF widgetSize = size();
516 const bool iconOnTop = (m_layout == IconsLayout);
517 const KItemListStyleOption& option = styleOption();
518 const qreal padding = option.padding;
519
520 const int maxIconWidth = iconOnTop ? widgetSize.width() - 2 * padding : option.iconSize;
521 const int maxIconHeight = option.iconSize;
522
523 const QHash<QByteArray, QVariant> values = data();
524
525 bool updatePixmap = (m_pixmap.width() != maxIconWidth || m_pixmap.height() != maxIconHeight);
526 if (!updatePixmap && m_dirtyContent) {
527 updatePixmap = m_dirtyContentRoles.isEmpty()
528 || m_dirtyContentRoles.contains("iconPixmap")
529 || m_dirtyContentRoles.contains("iconName")
530 || m_dirtyContentRoles.contains("iconOverlays");
531 }
532
533 if (updatePixmap) {
534 m_pixmap = values["iconPixmap"].value<QPixmap>();
535 if (m_pixmap.isNull()) {
536 // Use the icon that fits to the MIME-type
537 QString iconName = values["iconName"].toString();
538 if (iconName.isEmpty()) {
539 // The icon-name has not been not resolved by KFileItemModelRolesUpdater,
540 // use a generic icon as fallback
541 iconName = QLatin1String("unknown");
542 }
543 m_pixmap = pixmapForIcon(iconName, maxIconHeight);
544 } else if (m_pixmap.width() != maxIconWidth || m_pixmap.height() != maxIconHeight) {
545 // A custom pixmap has been applied. Assure that the pixmap
546 // is scaled to the maximum available size.
547 KPixmapModifier::scale(m_pixmap, QSize(maxIconWidth, maxIconHeight));
548 }
549
550 const QStringList overlays = values["iconOverlays"].toStringList();
551
552 // Strangely KFileItem::overlays() returns empty string-values, so
553 // we need to check first whether an overlay must be drawn at all.
554 // It is more efficient to do it here, as KIconLoader::drawOverlays()
555 // assumes that an overlay will be drawn and has some additional
556 // setup time.
557 foreach (const QString& overlay, overlays) {
558 if (!overlay.isEmpty()) {
559 // There is at least one overlay, draw all overlays above m_pixmap
560 // and cancel the check
561 KIconLoader::global()->drawOverlays(overlays, m_pixmap, KIconLoader::Desktop);
562 break;
563 }
564 }
565
566 if (m_isCut) {
567 applyCutEffect(m_pixmap);
568 }
569
570 if (m_isHidden) {
571 applyHiddenEffect(m_pixmap);
572 }
573 }
574
575 if (!m_overlay.isNull()) {
576 QPainter painter(&m_pixmap);
577 painter.drawPixmap(0, m_pixmap.height() - m_overlay.height(), m_overlay);
578 }
579
580 int scaledIconSize = 0;
581 if (iconOnTop) {
582 const TextInfo* textInfo = m_textInfo.value("name");
583 scaledIconSize = static_cast<int>(textInfo->pos.y() - 2 * padding);
584 } else {
585 const int textRowsCount = (m_layout == CompactLayout) ? visibleRoles().count() : 1;
586 const qreal requiredTextHeight = textRowsCount * option.fontMetrics.height();
587 scaledIconSize = (requiredTextHeight < maxIconHeight) ?
588 widgetSize.height() - 2 * padding : maxIconHeight;
589 }
590
591 const int maxScaledIconWidth = iconOnTop ? widgetSize.width() - 2 * padding : scaledIconSize;
592 const int maxScaledIconHeight = scaledIconSize;
593
594 m_scaledPixmapSize = m_pixmap.size();
595 m_scaledPixmapSize.scale(maxScaledIconWidth, maxScaledIconHeight, Qt::KeepAspectRatio);
596
597 if (iconOnTop) {
598 // Center horizontally and align on bottom within the icon-area
599 m_pixmapPos.setX((widgetSize.width() - m_scaledPixmapSize.width()) / 2);
600 m_pixmapPos.setY(padding + scaledIconSize - m_scaledPixmapSize.height());
601 } else {
602 // Center horizontally and vertically within the icon-area
603 const TextInfo* textInfo = m_textInfo.value("name");
604 m_pixmapPos.setX(textInfo->pos.x() - 2 * padding
605 - (scaledIconSize + m_scaledPixmapSize.width()) / 2);
606 m_pixmapPos.setY(padding
607 + (scaledIconSize - m_scaledPixmapSize.height()) / 2);
608 }
609
610 m_iconRect = QRectF(m_pixmapPos, QSizeF(m_scaledPixmapSize));
611
612 // Prepare the pixmap that is used when the item gets hovered
613 if (isHovered()) {
614 m_hoverPixmap = m_pixmap;
615 KIconEffect* effect = KIconLoader::global()->iconEffect();
616 // In the KIconLoader terminology, active = hover.
617 if (effect->hasEffect(KIconLoader::Desktop, KIconLoader::ActiveState)) {
618 m_hoverPixmap = effect->apply(m_pixmap, KIconLoader::Desktop, KIconLoader::ActiveState);
619 } else {
620 m_hoverPixmap = m_pixmap;
621 }
622 } else if (hoverOpacity() <= 0.0) {
623 // No hover animation is ongoing. Clear m_hoverPixmap to save memory.
624 m_hoverPixmap = QPixmap();
625 }
626 }
627
628 void KFileItemListWidget::updateTextsCache()
629 {
630 QTextOption textOption;
631 switch (m_layout) {
632 case IconsLayout:
633 textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
634 textOption.setAlignment(Qt::AlignHCenter);
635 break;
636 case CompactLayout:
637 case DetailsLayout:
638 textOption.setAlignment(Qt::AlignLeft);
639 textOption.setWrapMode(QTextOption::NoWrap);
640 break;
641 default:
642 Q_ASSERT(false);
643 break;
644 }
645
646 qDeleteAll(m_textInfo);
647 m_textInfo.clear();
648 for (int i = 0; i < m_sortedVisibleRoles.count(); ++i) {
649 TextInfo* textInfo = new TextInfo();
650 textInfo->staticText.setTextFormat(Qt::PlainText);
651 textInfo->staticText.setPerformanceHint(QStaticText::AggressiveCaching);
652 textInfo->staticText.setTextOption(textOption);
653 m_textInfo.insert(m_sortedVisibleRoles[i], textInfo);
654 }
655
656 switch (m_layout) {
657 case IconsLayout: updateIconsLayoutTextCache(); break;
658 case CompactLayout: updateCompactLayoutTextCache(); break;
659 case DetailsLayout: updateDetailsLayoutTextCache(); break;
660 default: Q_ASSERT(false); break;
661 }
662 }
663
664 void KFileItemListWidget::updateIconsLayoutTextCache()
665 {
666 // +------+
667 // | Icon |
668 // +------+
669 //
670 // Name role that
671 // might get wrapped above
672 // several lines.
673 // Additional role 1
674 // Additional role 2
675
676 const QHash<QByteArray, QVariant> values = data();
677
678 const KItemListStyleOption& option = styleOption();
679 const qreal padding = option.padding;
680 const qreal maxWidth = size().width() - 2 * padding;
681 const qreal widgetHeight = size().height();
682 const qreal fontHeight = option.fontMetrics.height();
683
684 // Initialize properties for the "name" role. It will be used as anchor
685 // for initializing the position of the other roles.
686 TextInfo* nameTextInfo = m_textInfo.value("name");
687 nameTextInfo->staticText.setText(KStringHandler::preProcessWrap(values["name"].toString()));
688
689 // Calculate the number of lines required for the name and the required width
690 int textLinesCountForName = 0;
691 qreal requiredWidthForName = 0;
692 QTextLine line;
693
694 QTextLayout layout(nameTextInfo->staticText.text(), option.font);
695 layout.setTextOption(nameTextInfo->staticText.textOption());
696 layout.beginLayout();
697 while ((line = layout.createLine()).isValid()) {
698 line.setLineWidth(maxWidth);
699 requiredWidthForName = qMax(requiredWidthForName, line.naturalTextWidth());
700 ++textLinesCountForName;
701 }
702 layout.endLayout();
703
704 // Use one line for each additional information
705 int textLinesCount = textLinesCountForName;
706 const int additionalRolesCount = qMax(visibleRoles().count() - 1, 0);
707 textLinesCount += additionalRolesCount;
708
709 nameTextInfo->staticText.setTextWidth(maxWidth);
710 nameTextInfo->pos = QPointF(padding, widgetHeight - textLinesCount * fontHeight - padding);
711 m_textRect = QRectF(padding + (maxWidth - requiredWidthForName) / 2,
712 nameTextInfo->pos.y(),
713 requiredWidthForName,
714 textLinesCountForName * fontHeight);
715
716 // Calculate the position for each additional information
717 qreal y = nameTextInfo->pos.y() + textLinesCountForName * fontHeight;
718 foreach (const QByteArray& role, m_sortedVisibleRoles) {
719 if (role == "name") {
720 continue;
721 }
722
723 const QString text = roleText(role, values);
724 TextInfo* textInfo = m_textInfo.value(role);
725 textInfo->staticText.setText(text);
726
727 qreal requiredWidth = 0;
728
729 QTextLayout layout(text, option.font);
730 layout.setTextOption(textInfo->staticText.textOption());
731 layout.beginLayout();
732 QTextLine textLine = layout.createLine();
733 if (textLine.isValid()) {
734 textLine.setLineWidth(maxWidth);
735 requiredWidth = textLine.naturalTextWidth();
736 if (textLine.textLength() < text.length()) {
737 // TODO: QFontMetrics::elidedText() works different regarding the given width
738 // in comparison to QTextLine::setLineWidth(). It might happen that the text does
739 // not get elided although it does not fit into the given width. As workaround
740 // the padding is substracted.
741 const QString elidedText = option.fontMetrics.elidedText(text, Qt::ElideRight, maxWidth - padding);
742 textInfo->staticText.setText(elidedText);
743 }
744 }
745 layout.endLayout();
746
747 textInfo->pos = QPointF(padding, y);
748 textInfo->staticText.setTextWidth(maxWidth);
749
750 const QRectF textRect(padding + (maxWidth - requiredWidth) / 2, y, requiredWidth, fontHeight);
751 m_textRect |= textRect;
752
753 y += fontHeight;
754 }
755
756 // Add a padding to the text rectangle
757 m_textRect.adjust(-padding, -padding, padding, padding);
758 }
759
760 void KFileItemListWidget::updateCompactLayoutTextCache()
761 {
762 // +------+ Name role
763 // | Icon | Additional role 1
764 // +------+ Additional role 2
765
766 const QHash<QByteArray, QVariant> values = data();
767
768 const KItemListStyleOption& option = styleOption();
769 const qreal widgetHeight = size().height();
770 const qreal fontHeight = option.fontMetrics.height();
771 const qreal textLinesHeight = qMax(visibleRoles().count(), 1) * fontHeight;
772 const int scaledIconSize = (textLinesHeight < option.iconSize) ? widgetHeight - 2 * option.padding : option.iconSize;
773
774 qreal maximumRequiredTextWidth = 0;
775 const qreal x = option.padding * 3 + scaledIconSize;
776 qreal y = (widgetHeight - textLinesHeight) / 2;
777 const qreal maxWidth = size().width() - x - option.padding;
778 foreach (const QByteArray& role, m_sortedVisibleRoles) {
779 const QString text = roleText(role, values);
780 TextInfo* textInfo = m_textInfo.value(role);
781 textInfo->staticText.setText(text);
782
783 qreal requiredWidth = option.fontMetrics.width(text);
784 if (requiredWidth > maxWidth) {
785 requiredWidth = maxWidth;
786 const QString elidedText = option.fontMetrics.elidedText(text, Qt::ElideRight, maxWidth);
787 textInfo->staticText.setText(elidedText);
788 }
789
790 textInfo->pos = QPointF(x, y);
791 textInfo->staticText.setTextWidth(maxWidth);
792
793 maximumRequiredTextWidth = qMax(maximumRequiredTextWidth, requiredWidth);
794
795 y += fontHeight;
796 }
797
798 m_textRect = QRectF(x - option.padding, 0, maximumRequiredTextWidth + 2 * option.padding, widgetHeight);
799 }
800
801 void KFileItemListWidget::updateDetailsLayoutTextCache()
802 {
803 // Precondition: Requires already updated m_expansionArea
804 // to determine the left position.
805
806 // +------+
807 // | Icon | Name role Additional role 1 Additional role 2
808 // +------+
809 m_textRect = QRectF();
810
811 const KItemListStyleOption& option = styleOption();
812 const QHash<QByteArray, QVariant> values = data();
813
814 const qreal widgetHeight = size().height();
815 const int scaledIconSize = widgetHeight - 2 * option.padding;
816 const int fontHeight = option.fontMetrics.height();
817
818 const qreal columnPadding = option.padding * 3;
819 qreal firstColumnInc = scaledIconSize;
820 if (m_supportsItemExpanding) {
821 firstColumnInc += (m_expansionArea.left() + m_expansionArea.right() + widgetHeight) / 2;
822 } else {
823 firstColumnInc += option.padding;
824 }
825
826 qreal x = firstColumnInc;
827 const qreal y = qMax(qreal(option.padding), (widgetHeight - fontHeight) / 2);
828
829 foreach (const QByteArray& role, m_sortedVisibleRoles) {
830 const RoleType type = roleType(role);
831
832 QString text = roleText(role, values);
833
834 // Elide the text in case it does not fit into the available column-width
835 qreal requiredWidth = option.fontMetrics.width(text);
836 const qreal roleWidth = columnWidth(role);
837 qreal availableTextWidth = roleWidth - 2 * columnPadding;
838 if (type == Name) {
839 availableTextWidth -= firstColumnInc;
840 }
841
842 if (requiredWidth > availableTextWidth) {
843 text = option.fontMetrics.elidedText(text, Qt::ElideRight, availableTextWidth);
844 requiredWidth = option.fontMetrics.width(text);
845 }
846
847 TextInfo* textInfo = m_textInfo.value(role);
848 textInfo->staticText.setText(text);
849 textInfo->pos = QPointF(x + columnPadding, y);
850 x += roleWidth;
851
852 switch (type) {
853 case Name: {
854 const qreal textWidth = option.extendedSelectionRegion
855 ? size().width() - textInfo->pos.x()
856 : requiredWidth + 2 * option.padding;
857 m_textRect = QRectF(textInfo->pos.x() - option.padding, 0,
858 textWidth, size().height());
859
860 // The column after the name should always be aligned on the same x-position independent
861 // from the expansion-level shown in the name column
862 x -= firstColumnInc;
863 break;
864 }
865 case Size:
866 // The values for the size should be right aligned
867 textInfo->pos.rx() += roleWidth - requiredWidth - 2 * columnPadding;
868 break;
869
870 default:
871 break;
872 }
873 }
874 }
875
876 void KFileItemListWidget::updateAdditionalInfoTextColor()
877 {
878 QColor c1;
879 if (m_customTextColor.isValid()) {
880 c1 = m_customTextColor;
881 } else if (isSelected() && m_layout != DetailsLayout) {
882 c1 = styleOption().palette.highlightedText().color();
883 } else {
884 c1 = styleOption().palette.text().color();
885 }
886
887 // For the color of the additional info the inactive text color
888 // is not used as this might lead to unreadable text for some color schemes. Instead
889 // the text color c1 is slightly mixed with the background color.
890 const QColor c2 = styleOption().palette.base().color();
891 const int p1 = 70;
892 const int p2 = 100 - p1;
893 m_additionalInfoTextColor = QColor((c1.red() * p1 + c2.red() * p2) / 100,
894 (c1.green() * p1 + c2.green() * p2) / 100,
895 (c1.blue() * p1 + c2.blue() * p2) / 100);
896 }
897
898 void KFileItemListWidget::drawPixmap(QPainter* painter, const QPixmap& pixmap)
899 {
900 if (m_scaledPixmapSize != pixmap.size()) {
901 QPixmap scaledPixmap = pixmap;
902 KPixmapModifier::scale(scaledPixmap, m_scaledPixmapSize);
903 painter->drawPixmap(m_pixmapPos, scaledPixmap);
904
905 #ifdef KFILEITEMLISTWIDGET_DEBUG
906 painter->setPen(Qt::blue);
907 painter->drawRect(QRectF(m_pixmapPos, QSizeF(m_scaledPixmapSize)));
908 #endif
909 } else {
910 painter->drawPixmap(m_pixmapPos, pixmap);
911 }
912 }
913
914 void KFileItemListWidget::drawSiblingsInformation(QPainter* painter)
915 {
916 const int siblingSize = size().height();
917 const int x = (m_expansionArea.left() + m_expansionArea.right() - siblingSize) / 2;
918 QRect siblingRect(x, 0, siblingSize, siblingSize);
919
920 QStyleOption option;
921 bool isItemSibling = true;
922
923 const QBitArray siblings = siblingsInformation();
924 for (int i = siblings.count() - 1; i >= 0; --i) {
925 option.rect = siblingRect;
926 option.state = siblings.at(i) ? QStyle::State_Sibling : QStyle::State_None;
927
928 if (isItemSibling) {
929 option.state |= QStyle::State_Item;
930 if (m_isExpandable) {
931 option.state |= QStyle::State_Children;
932 }
933 if (data()["isExpanded"].toBool()) {
934 option.state |= QStyle::State_Open;
935 }
936 isItemSibling = false;
937 }
938
939 style()->drawPrimitive(QStyle::PE_IndicatorBranch, &option, painter);
940
941 siblingRect.translate(-siblingRect.width(), 0);
942 }
943 }
944
945 QPixmap KFileItemListWidget::pixmapForIcon(const QString& name, int size)
946 {
947 const KIcon icon(name);
948
949 int requestedSize;
950 if (size <= KIconLoader::SizeSmall) {
951 requestedSize = KIconLoader::SizeSmall;
952 } else if (size <= KIconLoader::SizeSmallMedium) {
953 requestedSize = KIconLoader::SizeSmallMedium;
954 } else if (size <= KIconLoader::SizeMedium) {
955 requestedSize = KIconLoader::SizeMedium;
956 } else if (size <= KIconLoader::SizeLarge) {
957 requestedSize = KIconLoader::SizeLarge;
958 } else if (size <= KIconLoader::SizeHuge) {
959 requestedSize = KIconLoader::SizeHuge;
960 } else if (size <= KIconLoader::SizeEnormous) {
961 requestedSize = KIconLoader::SizeEnormous;
962 } else if (size <= KIconLoader::SizeEnormous * 2) {
963 requestedSize = KIconLoader::SizeEnormous * 2;
964 } else {
965 requestedSize = size;
966 }
967
968 QPixmap pixmap = icon.pixmap(requestedSize, requestedSize);
969 if (requestedSize != size) {
970 KPixmapModifier::scale(pixmap, QSize(size, size));
971 }
972
973 return pixmap;
974 }
975
976 void KFileItemListWidget::applyCutEffect(QPixmap& pixmap)
977 {
978 KIconEffect* effect = KIconLoader::global()->iconEffect();
979 pixmap = effect->apply(pixmap, KIconLoader::Desktop, KIconLoader::DisabledState);
980 }
981
982 void KFileItemListWidget::applyHiddenEffect(QPixmap& pixmap)
983 {
984 KIconEffect::semiTransparent(pixmap);
985 }
986
987 KFileItemListWidget::RoleType KFileItemListWidget::roleType(const QByteArray& role)
988 {
989 static QHash<QByteArray, RoleType> rolesHash;
990 if (rolesHash.isEmpty()) {
991 rolesHash.insert("name", Name);
992 rolesHash.insert("size", Size);
993 rolesHash.insert("date", Date);
994 }
995
996 return rolesHash.value(role, Generic);
997 }
998
999 QString KFileItemListWidget::roleText(const QByteArray& role, const QHash<QByteArray, QVariant>& values)
1000 {
1001 QString text;
1002 const QVariant roleValue = values.value(role);
1003
1004 switch (roleType(role)) {
1005 case Size: {
1006 if (values.value("isDir").toBool()) {
1007 // The item represents a directory. Show the number of sub directories
1008 // instead of the file size of the directory.
1009 if (!roleValue.isNull()) {
1010 const int count = roleValue.toInt();
1011 if (count < 0) {
1012 text = i18nc("@item:intable", "Unknown");
1013 } else {
1014 text = i18ncp("@item:intable", "%1 item", "%1 items", count);
1015 }
1016 }
1017 } else {
1018 // Show the size in kilobytes (always round up)
1019 const KLocale* locale = KGlobal::locale();
1020 const int roundInc = (locale->binaryUnitDialect() == KLocale::MetricBinaryDialect) ? 499 : 511;
1021 const KIO::filesize_t size = roleValue.value<KIO::filesize_t>() + roundInc;
1022 text = locale->formatByteSize(size, 0, KLocale::DefaultBinaryDialect, KLocale::UnitKiloByte);
1023 }
1024 break;
1025 }
1026
1027 case Date: {
1028 const QDateTime dateTime = roleValue.toDateTime();
1029 text = KGlobal::locale()->formatDateTime(dateTime);
1030 break;
1031 }
1032
1033 case Name:
1034 case Generic:
1035 text = roleValue.toString();
1036 break;
1037
1038 default:
1039 Q_ASSERT(false);
1040 break;
1041 }
1042
1043 return text;
1044 }
1045 #include "kfileitemlistwidget.moc"