]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemlistwidget.cpp
KItemListView interface and implementation simplification
[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_textPos(),
59 m_text(),
60 m_textRect(),
61 m_sortedVisibleRoles(),
62 m_expansionArea(),
63 m_customTextColor(),
64 m_additionalInfoTextColor(),
65 m_overlay()
66 {
67 for (int i = 0; i < TextIdCount; ++i) {
68 m_text[i].setTextFormat(Qt::PlainText);
69 m_text[i].setPerformanceHint(QStaticText::AggressiveCaching);
70 }
71 }
72
73 KFileItemListWidget::~KFileItemListWidget()
74 {
75 }
76
77 void KFileItemListWidget::setLayout(Layout layout)
78 {
79 if (m_layout != layout) {
80 m_layout = layout;
81 m_dirtyLayout = true;
82 updateAdditionalInfoTextColor();
83 update();
84 }
85 }
86
87 KFileItemListWidget::Layout KFileItemListWidget::layout() const
88 {
89 return m_layout;
90 }
91
92 void KFileItemListWidget::setSupportsItemExpanding(bool supportsItemExpanding)
93 {
94 if (m_supportsItemExpanding != supportsItemExpanding) {
95 m_supportsItemExpanding = supportsItemExpanding;
96 m_dirtyLayout = true;
97 update();
98 }
99 }
100
101 bool KFileItemListWidget::supportsItemExpanding() const
102 {
103 return m_supportsItemExpanding;
104 }
105
106 void KFileItemListWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
107 {
108 const_cast<KFileItemListWidget*>(this)->triggerCacheRefreshing();
109
110 KItemListWidget::paint(painter, option, widget);
111
112 if (!m_expansionArea.isEmpty()) {
113 drawSiblingsInformation(painter);
114 }
115
116 const KItemListStyleOption& itemListStyleOption = styleOption();
117 if (isHovered()) {
118 // Blend the unhovered and hovered pixmap if the hovering
119 // animation is ongoing
120 if (hoverOpacity() < 1.0) {
121 drawPixmap(painter, m_pixmap);
122 }
123
124 const qreal opacity = painter->opacity();
125 painter->setOpacity(hoverOpacity() * opacity);
126 drawPixmap(painter, m_hoverPixmap);
127 painter->setOpacity(opacity);
128 } else {
129 drawPixmap(painter, m_pixmap);
130 }
131
132 painter->setFont(itemListStyleOption.font);
133 painter->setPen(textColor());
134 painter->drawStaticText(m_textPos[Name], m_text[Name]);
135
136 bool clipAdditionalInfoBounds = false;
137 if (m_supportsItemExpanding) {
138 // Prevent a possible overlapping of the additional-information texts
139 // with the icon. This can happen if the user has minimized the width
140 // of the name-column to a very small value.
141 const qreal minX = m_pixmapPos.x() + m_pixmap.width() + 4 * itemListStyleOption.padding;
142 if (m_textPos[Name].x() + columnWidth("name") > minX) {
143 clipAdditionalInfoBounds = true;
144 painter->save();
145 painter->setClipRect(minX, 0, size().width() - minX, size().height(), Qt::IntersectClip);
146 }
147 }
148
149 painter->setPen(m_additionalInfoTextColor);
150 painter->setFont(itemListStyleOption.font);
151 for (int i = Name + 1; i < TextIdCount; ++i) {
152 painter->drawStaticText(m_textPos[i], m_text[i]);
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 scaledIconSize = static_cast<int>(m_textPos[Name].y() - 2 * padding);
583 } else {
584 const int textRowsCount = (m_layout == CompactLayout) ? visibleRoles().count() : 1;
585 const qreal requiredTextHeight = textRowsCount * option.fontMetrics.height();
586 scaledIconSize = (requiredTextHeight < maxIconHeight) ?
587 widgetSize.height() - 2 * padding : maxIconHeight;
588 }
589
590 const int maxScaledIconWidth = iconOnTop ? widgetSize.width() - 2 * padding : scaledIconSize;
591 const int maxScaledIconHeight = scaledIconSize;
592
593 m_scaledPixmapSize = m_pixmap.size();
594 m_scaledPixmapSize.scale(maxScaledIconWidth, maxScaledIconHeight, Qt::KeepAspectRatio);
595
596 if (iconOnTop) {
597 // Center horizontally and align on bottom within the icon-area
598 m_pixmapPos.setX((widgetSize.width() - m_scaledPixmapSize.width()) / 2);
599 m_pixmapPos.setY(padding + scaledIconSize - m_scaledPixmapSize.height());
600 } else {
601 // Center horizontally and vertically within the icon-area
602 m_pixmapPos.setX(m_textPos[Name].x() - 2 * padding
603 - (scaledIconSize + m_scaledPixmapSize.width()) / 2);
604 m_pixmapPos.setY(padding
605 + (scaledIconSize - m_scaledPixmapSize.height()) / 2);
606 }
607
608 m_iconRect = QRectF(m_pixmapPos, QSizeF(m_scaledPixmapSize));
609
610 // Prepare the pixmap that is used when the item gets hovered
611 if (isHovered()) {
612 m_hoverPixmap = m_pixmap;
613 KIconEffect* effect = KIconLoader::global()->iconEffect();
614 // In the KIconLoader terminology, active = hover.
615 if (effect->hasEffect(KIconLoader::Desktop, KIconLoader::ActiveState)) {
616 m_hoverPixmap = effect->apply(m_pixmap, KIconLoader::Desktop, KIconLoader::ActiveState);
617 } else {
618 m_hoverPixmap = m_pixmap;
619 }
620 } else if (hoverOpacity() <= 0.0) {
621 // No hover animation is ongoing. Clear m_hoverPixmap to save memory.
622 m_hoverPixmap = QPixmap();
623 }
624 }
625
626 void KFileItemListWidget::updateTextsCache()
627 {
628 QTextOption textOption;
629 switch (m_layout) {
630 case IconsLayout:
631 textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
632 textOption.setAlignment(Qt::AlignHCenter);
633 break;
634 case CompactLayout:
635 case DetailsLayout:
636 textOption.setAlignment(Qt::AlignLeft);
637 textOption.setWrapMode(QTextOption::NoWrap);
638 break;
639 default:
640 Q_ASSERT(false);
641 break;
642 }
643
644 for (int i = 0; i < TextIdCount; ++i) {
645 m_text[i].setText(QString());
646 m_text[i].setTextOption(textOption);
647 }
648
649 switch (m_layout) {
650 case IconsLayout: updateIconsLayoutTextCache(); break;
651 case CompactLayout: updateCompactLayoutTextCache(); break;
652 case DetailsLayout: updateDetailsLayoutTextCache(); break;
653 default: Q_ASSERT(false); break;
654 }
655 }
656
657 void KFileItemListWidget::updateIconsLayoutTextCache()
658 {
659 // +------+
660 // | Icon |
661 // +------+
662 //
663 // Name role that
664 // might get wrapped above
665 // several lines.
666 // Additional role 1
667 // Additional role 2
668
669 const QHash<QByteArray, QVariant> values = data();
670
671 const KItemListStyleOption& option = styleOption();
672 const qreal padding = option.padding;
673 const qreal maxWidth = size().width() - 2 * padding;
674 const qreal widgetHeight = size().height();
675 const qreal fontHeight = option.fontMetrics.height();
676
677 // Initialize properties for the "name" role. It will be used as anchor
678 // for initializing the position of the other roles.
679 m_text[Name].setText(KStringHandler::preProcessWrap(values["name"].toString()));
680
681 // Calculate the number of lines required for the name and the required width
682 int textLinesCountForName = 0;
683 qreal requiredWidthForName = 0;
684 QTextLine line;
685
686 QTextLayout layout(m_text[Name].text(), option.font);
687 layout.setTextOption(m_text[Name].textOption());
688 layout.beginLayout();
689 while ((line = layout.createLine()).isValid()) {
690 line.setLineWidth(maxWidth);
691 requiredWidthForName = qMax(requiredWidthForName, line.naturalTextWidth());
692 ++textLinesCountForName;
693 }
694 layout.endLayout();
695
696 // Use one line for each additional information
697 int textLinesCount = textLinesCountForName;
698 const int additionalRolesCount = qMax(visibleRoles().count() - 1, 0);
699 textLinesCount += additionalRolesCount;
700
701 m_text[Name].setTextWidth(maxWidth);
702 m_textPos[Name] = QPointF(padding, widgetHeight - textLinesCount * fontHeight - padding);
703 m_textRect = QRectF(padding + (maxWidth - requiredWidthForName) / 2,
704 m_textPos[Name].y(),
705 requiredWidthForName,
706 textLinesCountForName * fontHeight);
707
708 // Calculate the position for each additional information
709 qreal y = m_textPos[Name].y() + textLinesCountForName * fontHeight;
710 foreach (const QByteArray& role, m_sortedVisibleRoles) {
711 const TextId textId = roleTextId(role);
712 if (textId == Name) {
713 continue;
714 }
715
716 const QString text = roleText(role, values);
717 m_text[textId].setText(text);
718
719 qreal requiredWidth = 0;
720
721 QTextLayout layout(text, option.font);
722 layout.setTextOption(m_text[textId].textOption());
723 layout.beginLayout();
724 QTextLine textLine = layout.createLine();
725 if (textLine.isValid()) {
726 textLine.setLineWidth(maxWidth);
727 requiredWidth = textLine.naturalTextWidth();
728 if (textLine.textLength() < text.length()) {
729 // TODO: QFontMetrics::elidedText() works different regarding the given width
730 // in comparison to QTextLine::setLineWidth(). It might happen that the text does
731 // not get elided although it does not fit into the given width. As workaround
732 // the padding is substracted.
733 const QString elidedText = option.fontMetrics.elidedText(text, Qt::ElideRight, maxWidth - padding);
734 m_text[textId].setText(elidedText);
735 }
736 }
737 layout.endLayout();
738
739 m_textPos[textId] = QPointF(padding, y);
740 m_text[textId].setTextWidth(maxWidth);
741
742 const QRectF textRect(padding + (maxWidth - requiredWidth) / 2, y, requiredWidth, fontHeight);
743 m_textRect |= textRect;
744
745 y += fontHeight;
746 }
747
748 // Add a padding to the text rectangle
749 m_textRect.adjust(-padding, -padding, padding, padding);
750 }
751
752 void KFileItemListWidget::updateCompactLayoutTextCache()
753 {
754 // +------+ Name role
755 // | Icon | Additional role 1
756 // +------+ Additional role 2
757
758 const QHash<QByteArray, QVariant> values = data();
759
760 const KItemListStyleOption& option = styleOption();
761 const qreal widgetHeight = size().height();
762 const qreal fontHeight = option.fontMetrics.height();
763 const qreal textLinesHeight = qMax(visibleRoles().count(), 1) * fontHeight;
764 const int scaledIconSize = (textLinesHeight < option.iconSize) ? widgetHeight - 2 * option.padding : option.iconSize;
765
766 qreal maximumRequiredTextWidth = 0;
767 const qreal x = option.padding * 3 + scaledIconSize;
768 qreal y = (widgetHeight - textLinesHeight) / 2;
769 const qreal maxWidth = size().width() - x - option.padding;
770 foreach (const QByteArray& role, m_sortedVisibleRoles) {
771 const TextId textId = roleTextId(role);
772
773 const QString text = roleText(role, values);
774 m_text[textId].setText(text);
775
776 qreal requiredWidth = option.fontMetrics.width(text);
777 if (requiredWidth > maxWidth) {
778 requiredWidth = maxWidth;
779 const QString elidedText = option.fontMetrics.elidedText(text, Qt::ElideRight, maxWidth);
780 m_text[textId].setText(elidedText);
781 }
782
783 m_textPos[textId] = QPointF(x, y);
784 m_text[textId].setTextWidth(maxWidth);
785
786 maximumRequiredTextWidth = qMax(maximumRequiredTextWidth, requiredWidth);
787
788 y += fontHeight;
789 }
790
791 m_textRect = QRectF(x - option.padding, 0, maximumRequiredTextWidth + 2 * option.padding, widgetHeight);
792 }
793
794 void KFileItemListWidget::updateDetailsLayoutTextCache()
795 {
796 // Precondition: Requires already updated m_expansionArea
797 // to determine the left position.
798
799 // +------+
800 // | Icon | Name role Additional role 1 Additional role 2
801 // +------+
802 m_textRect = QRectF();
803
804 const KItemListStyleOption& option = styleOption();
805 const QHash<QByteArray, QVariant> values = data();
806
807 const qreal widgetHeight = size().height();
808 const int scaledIconSize = widgetHeight - 2 * option.padding;
809 const int fontHeight = option.fontMetrics.height();
810
811 const qreal columnPadding = option.padding * 3;
812 qreal firstColumnInc = scaledIconSize;
813 if (m_supportsItemExpanding) {
814 firstColumnInc += (m_expansionArea.left() + m_expansionArea.right() + widgetHeight) / 2;
815 } else {
816 firstColumnInc += option.padding;
817 }
818
819 qreal x = firstColumnInc;
820 const qreal y = qMax(qreal(option.padding), (widgetHeight - fontHeight) / 2);
821
822 foreach (const QByteArray& role, m_sortedVisibleRoles) {
823 const TextId textId = roleTextId(role);
824
825 QString text = roleText(role, values);
826
827 // Elide the text in case it does not fit into the available column-width
828 qreal requiredWidth = option.fontMetrics.width(text);
829 const qreal roleWidth = columnWidth(role);
830 qreal availableTextWidth = roleWidth - 2 * columnPadding;
831 if (textId == Name) {
832 availableTextWidth -= firstColumnInc;
833 }
834
835 if (requiredWidth > availableTextWidth) {
836 text = option.fontMetrics.elidedText(text, Qt::ElideRight, availableTextWidth);
837 requiredWidth = option.fontMetrics.width(text);
838 }
839
840 m_text[textId].setText(text);
841 m_textPos[textId] = QPointF(x + columnPadding, y);
842 x += roleWidth;
843
844 switch (textId) {
845 case Name: {
846 const qreal textWidth = option.extendedSelectionRegion
847 ? size().width() - m_textPos[textId].x()
848 : requiredWidth + 2 * option.padding;
849 m_textRect = QRectF(m_textPos[textId].x() - option.padding, 0,
850 textWidth, size().height());
851
852 // The column after the name should always be aligned on the same x-position independent
853 // from the expansion-level shown in the name column
854 x -= firstColumnInc;
855 break;
856 }
857 case Size:
858 // The values for the size should be right aligned
859 m_textPos[textId].rx() += roleWidth - requiredWidth - 2 * columnPadding;
860 break;
861
862 default:
863 break;
864 }
865 }
866 }
867
868 void KFileItemListWidget::updateAdditionalInfoTextColor()
869 {
870 QColor c1;
871 if (m_customTextColor.isValid()) {
872 c1 = m_customTextColor;
873 } else if (isSelected() && m_layout != DetailsLayout) {
874 c1 = styleOption().palette.highlightedText().color();
875 } else {
876 c1 = styleOption().palette.text().color();
877 }
878
879 // For the color of the additional info the inactive text color
880 // is not used as this might lead to unreadable text for some color schemes. Instead
881 // the text color c1 is slightly mixed with the background color.
882 const QColor c2 = styleOption().palette.base().color();
883 const int p1 = 70;
884 const int p2 = 100 - p1;
885 m_additionalInfoTextColor = QColor((c1.red() * p1 + c2.red() * p2) / 100,
886 (c1.green() * p1 + c2.green() * p2) / 100,
887 (c1.blue() * p1 + c2.blue() * p2) / 100);
888 }
889
890 void KFileItemListWidget::drawPixmap(QPainter* painter, const QPixmap& pixmap)
891 {
892 if (m_scaledPixmapSize != pixmap.size()) {
893 QPixmap scaledPixmap = pixmap;
894 KPixmapModifier::scale(scaledPixmap, m_scaledPixmapSize);
895 painter->drawPixmap(m_pixmapPos, scaledPixmap);
896
897 #ifdef KFILEITEMLISTWIDGET_DEBUG
898 painter->setPen(Qt::blue);
899 painter->drawRect(QRectF(m_pixmapPos, QSizeF(m_scaledPixmapSize)));
900 #endif
901 } else {
902 painter->drawPixmap(m_pixmapPos, pixmap);
903 }
904 }
905
906 void KFileItemListWidget::drawSiblingsInformation(QPainter* painter)
907 {
908 const int siblingSize = size().height();
909 const int x = (m_expansionArea.left() + m_expansionArea.right() - siblingSize) / 2;
910 QRect siblingRect(x, 0, siblingSize, siblingSize);
911
912 QStyleOption option;
913 bool isItemSibling = true;
914
915 const QBitArray siblings = siblingsInformation();
916 for (int i = siblings.count() - 1; i >= 0; --i) {
917 option.rect = siblingRect;
918 option.state = siblings.at(i) ? QStyle::State_Sibling : QStyle::State_None;
919
920 if (isItemSibling) {
921 option.state |= QStyle::State_Item;
922 if (m_isExpandable) {
923 option.state |= QStyle::State_Children;
924 }
925 if (data()["isExpanded"].toBool()) {
926 option.state |= QStyle::State_Open;
927 }
928 isItemSibling = false;
929 }
930
931 style()->drawPrimitive(QStyle::PE_IndicatorBranch, &option, painter);
932
933 siblingRect.translate(-siblingRect.width(), 0);
934 }
935 }
936
937 QPixmap KFileItemListWidget::pixmapForIcon(const QString& name, int size)
938 {
939 const KIcon icon(name);
940
941 int requestedSize;
942 if (size <= KIconLoader::SizeSmall) {
943 requestedSize = KIconLoader::SizeSmall;
944 } else if (size <= KIconLoader::SizeSmallMedium) {
945 requestedSize = KIconLoader::SizeSmallMedium;
946 } else if (size <= KIconLoader::SizeMedium) {
947 requestedSize = KIconLoader::SizeMedium;
948 } else if (size <= KIconLoader::SizeLarge) {
949 requestedSize = KIconLoader::SizeLarge;
950 } else if (size <= KIconLoader::SizeHuge) {
951 requestedSize = KIconLoader::SizeHuge;
952 } else if (size <= KIconLoader::SizeEnormous) {
953 requestedSize = KIconLoader::SizeEnormous;
954 } else if (size <= KIconLoader::SizeEnormous * 2) {
955 requestedSize = KIconLoader::SizeEnormous * 2;
956 } else {
957 requestedSize = size;
958 }
959
960 QPixmap pixmap = icon.pixmap(requestedSize, requestedSize);
961 if (requestedSize != size) {
962 KPixmapModifier::scale(pixmap, QSize(size, size));
963 }
964
965 return pixmap;
966 }
967
968 void KFileItemListWidget::applyCutEffect(QPixmap& pixmap)
969 {
970 KIconEffect* effect = KIconLoader::global()->iconEffect();
971 pixmap = effect->apply(pixmap, KIconLoader::Desktop, KIconLoader::DisabledState);
972 }
973
974 void KFileItemListWidget::applyHiddenEffect(QPixmap& pixmap)
975 {
976 KIconEffect::semiTransparent(pixmap);
977 }
978
979 KFileItemListWidget::TextId KFileItemListWidget::roleTextId(const QByteArray& role)
980 {
981 static QHash<QByteArray, TextId> rolesHash;
982 if (rolesHash.isEmpty()) {
983 rolesHash.insert("name", Name);
984 rolesHash.insert("size", Size);
985 rolesHash.insert("date", Date);
986 rolesHash.insert("permissions", Permissions);
987 rolesHash.insert("owner", Owner);
988 rolesHash.insert("group", Group);
989 rolesHash.insert("type", Type);
990 rolesHash.insert("destination", Destination);
991 rolesHash.insert("path", Path);
992 }
993
994 return rolesHash.value(role);
995 }
996
997 QString KFileItemListWidget::roleText(const QByteArray& role, const QHash<QByteArray, QVariant>& values)
998 {
999 QString text;
1000 const QVariant roleValue = values.value(role);
1001
1002 switch (roleTextId(role)) {
1003 case Name:
1004 case Permissions:
1005 case Owner:
1006 case Group:
1007 case Type:
1008 case Destination:
1009 case Path:
1010 text = roleValue.toString();
1011 break;
1012
1013 case Size: {
1014 if (values.value("isDir").toBool()) {
1015 // The item represents a directory. Show the number of sub directories
1016 // instead of the file size of the directory.
1017 if (!roleValue.isNull()) {
1018 const int count = roleValue.toInt();
1019 if (count < 0) {
1020 text = i18nc("@item:intable", "Unknown");
1021 } else {
1022 text = i18ncp("@item:intable", "%1 item", "%1 items", count);
1023 }
1024 }
1025 } else {
1026 // Show the size in kilobytes (always round up)
1027 const KLocale* locale = KGlobal::locale();
1028 const int roundInc = (locale->binaryUnitDialect() == KLocale::MetricBinaryDialect) ? 499 : 511;
1029 const KIO::filesize_t size = roleValue.value<KIO::filesize_t>() + roundInc;
1030 text = locale->formatByteSize(size, 0, KLocale::DefaultBinaryDialect, KLocale::UnitKiloByte);
1031 }
1032 break;
1033 }
1034
1035 case Date: {
1036 const QDateTime dateTime = roleValue.toDateTime();
1037 text = KGlobal::locale()->formatDateTime(dateTime);
1038 break;
1039 }
1040
1041 default:
1042 Q_ASSERT(false);
1043 break;
1044 }
1045
1046 return text;
1047 }
1048 #include "kfileitemlistwidget.moc"