]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemlistwidget.cpp
5c865d1ca3a0f7872b6eaa92a6631f5417c9d380
[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 "kfileitemmodel.h"
24 #include "kitemlistview.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 QString KFileItemListWidget::roleText(const QByteArray& role, const QHash<QByteArray, QVariant>& values)
226 {
227 QString text;
228 const QVariant roleValue = values.value(role);
229
230 switch (roleTextId(role)) {
231 case Name:
232 case Permissions:
233 case Owner:
234 case Group:
235 case Type:
236 case Destination:
237 case Path:
238 text = roleValue.toString();
239 break;
240
241 case Size: {
242 if (values.value("isDir").toBool()) {
243 // The item represents a directory. Show the number of sub directories
244 // instead of the file size of the directory.
245 if (!roleValue.isNull()) {
246 const int count = roleValue.toInt();
247 if (count < 0) {
248 text = i18nc("@item:intable", "Unknown");
249 } else {
250 text = i18ncp("@item:intable", "%1 item", "%1 items", count);
251 }
252 }
253 } else {
254 // Show the size in kilobytes (always round up)
255 const KLocale* locale = KGlobal::locale();
256 const int roundInc = (locale->binaryUnitDialect() == KLocale::MetricBinaryDialect) ? 499 : 511;
257 const KIO::filesize_t size = roleValue.value<KIO::filesize_t>() + roundInc;
258 text = locale->formatByteSize(size, 0, KLocale::DefaultBinaryDialect, KLocale::UnitKiloByte);
259 }
260 break;
261 }
262
263 case Date: {
264 const QDateTime dateTime = roleValue.toDateTime();
265 text = KGlobal::locale()->formatDateTime(dateTime);
266 break;
267 }
268
269 default:
270 Q_ASSERT(false);
271 break;
272 }
273
274 return text;
275 }
276
277 void KFileItemListWidget::invalidateCache()
278 {
279 m_dirtyLayout = true;
280 m_dirtyContent = true;
281 }
282
283 void KFileItemListWidget::refreshCache()
284 {
285 }
286
287 void KFileItemListWidget::setTextColor(const QColor& color)
288 {
289 if (color != m_customTextColor) {
290 m_customTextColor = color;
291 updateAdditionalInfoTextColor();
292 update();
293 }
294 }
295
296 QColor KFileItemListWidget::textColor() const
297 {
298 if (m_customTextColor.isValid() && !isSelected()) {
299 return m_customTextColor;
300 }
301
302 const QPalette::ColorGroup group = isActiveWindow() ? QPalette::Active : QPalette::Inactive;
303 const QPalette::ColorRole role = isSelected() ? QPalette::HighlightedText : QPalette::Text;
304 return styleOption().palette.brush(group, role).color();
305 }
306
307 void KFileItemListWidget::setOverlay(const QPixmap& overlay)
308 {
309 m_overlay = overlay;
310 m_dirtyContent = true;
311 update();
312 }
313
314 QPixmap KFileItemListWidget::overlay() const
315 {
316 return m_overlay;
317 }
318
319 void KFileItemListWidget::dataChanged(const QHash<QByteArray, QVariant>& current,
320 const QSet<QByteArray>& roles)
321 {
322 Q_UNUSED(current);
323
324 m_dirtyContent = true;
325
326 QSet<QByteArray> dirtyRoles;
327 if (roles.isEmpty()) {
328 dirtyRoles = visibleRoles().toSet();
329 dirtyRoles.insert("iconPixmap");
330 dirtyRoles.insert("iconName");
331 } else {
332 dirtyRoles = roles;
333 }
334
335 QSetIterator<QByteArray> it(dirtyRoles);
336 while (it.hasNext()) {
337 const QByteArray& role = it.next();
338 m_dirtyContentRoles.insert(role);
339 }
340 }
341
342 void KFileItemListWidget::visibleRolesChanged(const QList<QByteArray>& current,
343 const QList<QByteArray>& previous)
344 {
345 Q_UNUSED(previous);
346 m_sortedVisibleRoles = current;
347 m_dirtyLayout = true;
348 }
349
350 void KFileItemListWidget::columnWidthChanged(const QByteArray& role,
351 qreal current,
352 qreal previous)
353 {
354 Q_UNUSED(role);
355 Q_UNUSED(current);
356 Q_UNUSED(previous);
357 m_dirtyLayout = true;
358 }
359
360 void KFileItemListWidget::styleOptionChanged(const KItemListStyleOption& current,
361 const KItemListStyleOption& previous)
362 {
363 Q_UNUSED(current);
364 Q_UNUSED(previous);
365 updateAdditionalInfoTextColor();
366 m_dirtyLayout = true;
367 }
368
369 void KFileItemListWidget::hoveredChanged(bool hovered)
370 {
371 Q_UNUSED(hovered);
372 m_dirtyLayout = true;
373 }
374
375 void KFileItemListWidget::selectedChanged(bool selected)
376 {
377 Q_UNUSED(selected);
378 updateAdditionalInfoTextColor();
379 }
380
381 void KFileItemListWidget::siblingsInformationChanged(const QBitArray& current, const QBitArray& previous)
382 {
383 Q_UNUSED(current);
384 Q_UNUSED(previous);
385 m_dirtyLayout = true;
386 }
387
388
389 void KFileItemListWidget::resizeEvent(QGraphicsSceneResizeEvent* event)
390 {
391 KItemListWidget::resizeEvent(event);
392 m_dirtyLayout = true;
393 }
394
395 void KFileItemListWidget::showEvent(QShowEvent* event)
396 {
397 KItemListWidget::showEvent(event);
398
399 // Listen to changes of the clipboard to mark the item as cut/uncut
400 KFileItemClipboard* clipboard = KFileItemClipboard::instance();
401
402 const KUrl itemUrl = data().value("url").value<KUrl>();
403 m_isCut = clipboard->isCut(itemUrl);
404
405 connect(clipboard, SIGNAL(cutItemsChanged()),
406 this, SLOT(slotCutItemsChanged()));
407 }
408
409 void KFileItemListWidget::hideEvent(QHideEvent* event)
410 {
411 disconnect(KFileItemClipboard::instance(), SIGNAL(cutItemsChanged()),
412 this, SLOT(slotCutItemsChanged()));
413
414 KItemListWidget::hideEvent(event);
415 }
416
417 void KFileItemListWidget::slotCutItemsChanged()
418 {
419 const KUrl itemUrl = data().value("url").value<KUrl>();
420 const bool isCut = KFileItemClipboard::instance()->isCut(itemUrl);
421 if (m_isCut != isCut) {
422 m_isCut = isCut;
423 m_pixmap = QPixmap();
424 m_dirtyContent = true;
425 update();
426 }
427 }
428
429 void KFileItemListWidget::triggerCacheRefreshing()
430 {
431 if ((!m_dirtyContent && !m_dirtyLayout) || index() < 0) {
432 return;
433 }
434
435 refreshCache();
436
437 const QHash<QByteArray, QVariant> values = data();
438 m_isExpandable = m_supportsItemExpanding && values["isExpandable"].toBool();
439 m_isHidden = values["name"].toString().startsWith(QLatin1Char('.'));
440
441 updateExpansionArea();
442 updateTextsCache();
443 updatePixmapCache();
444
445 m_dirtyLayout = false;
446 m_dirtyContent = false;
447 m_dirtyContentRoles.clear();
448 }
449
450 void KFileItemListWidget::updateExpansionArea()
451 {
452 if (m_supportsItemExpanding) {
453 const QHash<QByteArray, QVariant> values = data();
454 Q_ASSERT(values.contains("expandedParentsCount"));
455 const int expandedParentsCount = values.value("expandedParentsCount", 0).toInt();
456 if (expandedParentsCount >= 0) {
457 const qreal widgetHeight = size().height();
458 const qreal inc = (widgetHeight - KIconLoader::SizeSmall) / 2;
459 const qreal x = expandedParentsCount * widgetHeight + inc;
460 const qreal y = inc;
461 m_expansionArea = QRectF(x, y, KIconLoader::SizeSmall, KIconLoader::SizeSmall);
462 return;
463 }
464 }
465
466 m_expansionArea = QRectF();
467 }
468
469 void KFileItemListWidget::updatePixmapCache()
470 {
471 // Precondition: Requires already updated m_textPos values to calculate
472 // the remaining height when the alignment is vertical.
473
474 const QSizeF widgetSize = size();
475 const bool iconOnTop = (m_layout == IconsLayout);
476 const KItemListStyleOption& option = styleOption();
477 const qreal padding = option.padding;
478
479 const int maxIconWidth = iconOnTop ? widgetSize.width() - 2 * padding : option.iconSize;
480 const int maxIconHeight = option.iconSize;
481
482 const QHash<QByteArray, QVariant> values = data();
483
484 bool updatePixmap = (m_pixmap.width() != maxIconWidth || m_pixmap.height() != maxIconHeight);
485 if (!updatePixmap && m_dirtyContent) {
486 updatePixmap = m_dirtyContentRoles.isEmpty()
487 || m_dirtyContentRoles.contains("iconPixmap")
488 || m_dirtyContentRoles.contains("iconName")
489 || m_dirtyContentRoles.contains("iconOverlays");
490 }
491
492 if (updatePixmap) {
493 m_pixmap = values["iconPixmap"].value<QPixmap>();
494 if (m_pixmap.isNull()) {
495 // Use the icon that fits to the MIME-type
496 QString iconName = values["iconName"].toString();
497 if (iconName.isEmpty()) {
498 // The icon-name has not been not resolved by KFileItemModelRolesUpdater,
499 // use a generic icon as fallback
500 iconName = QLatin1String("unknown");
501 }
502 m_pixmap = pixmapForIcon(iconName, maxIconHeight);
503 } else if (m_pixmap.width() != maxIconWidth || m_pixmap.height() != maxIconHeight) {
504 // A custom pixmap has been applied. Assure that the pixmap
505 // is scaled to the maximum available size.
506 KPixmapModifier::scale(m_pixmap, QSize(maxIconWidth, maxIconHeight));
507 }
508
509 const QStringList overlays = values["iconOverlays"].toStringList();
510
511 // Strangely KFileItem::overlays() returns empty string-values, so
512 // we need to check first whether an overlay must be drawn at all.
513 // It is more efficient to do it here, as KIconLoader::drawOverlays()
514 // assumes that an overlay will be drawn and has some additional
515 // setup time.
516 foreach (const QString& overlay, overlays) {
517 if (!overlay.isEmpty()) {
518 // There is at least one overlay, draw all overlays above m_pixmap
519 // and cancel the check
520 KIconLoader::global()->drawOverlays(overlays, m_pixmap, KIconLoader::Desktop);
521 break;
522 }
523 }
524
525 if (m_isCut) {
526 applyCutEffect(m_pixmap);
527 }
528
529 if (m_isHidden) {
530 applyHiddenEffect(m_pixmap);
531 }
532 }
533
534 if (!m_overlay.isNull()) {
535 QPainter painter(&m_pixmap);
536 painter.drawPixmap(0, m_pixmap.height() - m_overlay.height(), m_overlay);
537 }
538
539 int scaledIconSize = 0;
540 if (iconOnTop) {
541 scaledIconSize = static_cast<int>(m_textPos[Name].y() - 2 * padding);
542 } else {
543 const int textRowsCount = (m_layout == CompactLayout) ? visibleRoles().count() : 1;
544 const qreal requiredTextHeight = textRowsCount * option.fontMetrics.height();
545 scaledIconSize = (requiredTextHeight < maxIconHeight) ?
546 widgetSize.height() - 2 * padding : maxIconHeight;
547 }
548
549 const int maxScaledIconWidth = iconOnTop ? widgetSize.width() - 2 * padding : scaledIconSize;
550 const int maxScaledIconHeight = scaledIconSize;
551
552 m_scaledPixmapSize = m_pixmap.size();
553 m_scaledPixmapSize.scale(maxScaledIconWidth, maxScaledIconHeight, Qt::KeepAspectRatio);
554
555 if (iconOnTop) {
556 // Center horizontally and align on bottom within the icon-area
557 m_pixmapPos.setX((widgetSize.width() - m_scaledPixmapSize.width()) / 2);
558 m_pixmapPos.setY(padding + scaledIconSize - m_scaledPixmapSize.height());
559 } else {
560 // Center horizontally and vertically within the icon-area
561 m_pixmapPos.setX(m_textPos[Name].x() - 2 * padding
562 - (scaledIconSize + m_scaledPixmapSize.width()) / 2);
563 m_pixmapPos.setY(padding
564 + (scaledIconSize - m_scaledPixmapSize.height()) / 2);
565 }
566
567 m_iconRect = QRectF(m_pixmapPos, QSizeF(m_scaledPixmapSize));
568
569 // Prepare the pixmap that is used when the item gets hovered
570 if (isHovered()) {
571 m_hoverPixmap = m_pixmap;
572 KIconEffect* effect = KIconLoader::global()->iconEffect();
573 // In the KIconLoader terminology, active = hover.
574 if (effect->hasEffect(KIconLoader::Desktop, KIconLoader::ActiveState)) {
575 m_hoverPixmap = effect->apply(m_pixmap, KIconLoader::Desktop, KIconLoader::ActiveState);
576 } else {
577 m_hoverPixmap = m_pixmap;
578 }
579 } else if (hoverOpacity() <= 0.0) {
580 // No hover animation is ongoing. Clear m_hoverPixmap to save memory.
581 m_hoverPixmap = QPixmap();
582 }
583 }
584
585 void KFileItemListWidget::updateTextsCache()
586 {
587 QTextOption textOption;
588 switch (m_layout) {
589 case IconsLayout:
590 textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
591 textOption.setAlignment(Qt::AlignHCenter);
592 break;
593 case CompactLayout:
594 case DetailsLayout:
595 textOption.setAlignment(Qt::AlignLeft);
596 textOption.setWrapMode(QTextOption::NoWrap);
597 break;
598 default:
599 Q_ASSERT(false);
600 break;
601 }
602
603 for (int i = 0; i < TextIdCount; ++i) {
604 m_text[i].setText(QString());
605 m_text[i].setTextOption(textOption);
606 }
607
608 switch (m_layout) {
609 case IconsLayout: updateIconsLayoutTextCache(); break;
610 case CompactLayout: updateCompactLayoutTextCache(); break;
611 case DetailsLayout: updateDetailsLayoutTextCache(); break;
612 default: Q_ASSERT(false); break;
613 }
614 }
615
616 void KFileItemListWidget::updateIconsLayoutTextCache()
617 {
618 // +------+
619 // | Icon |
620 // +------+
621 //
622 // Name role that
623 // might get wrapped above
624 // several lines.
625 // Additional role 1
626 // Additional role 2
627
628 const QHash<QByteArray, QVariant> values = data();
629
630 const KItemListStyleOption& option = styleOption();
631 const qreal padding = option.padding;
632 const qreal maxWidth = size().width() - 2 * padding;
633 const qreal widgetHeight = size().height();
634 const qreal fontHeight = option.fontMetrics.height();
635
636 // Initialize properties for the "name" role. It will be used as anchor
637 // for initializing the position of the other roles.
638 m_text[Name].setText(KStringHandler::preProcessWrap(values["name"].toString()));
639
640 // Calculate the number of lines required for the name and the required width
641 int textLinesCountForName = 0;
642 qreal requiredWidthForName = 0;
643 QTextLine line;
644
645 QTextLayout layout(m_text[Name].text(), option.font);
646 layout.setTextOption(m_text[Name].textOption());
647 layout.beginLayout();
648 while ((line = layout.createLine()).isValid()) {
649 line.setLineWidth(maxWidth);
650 requiredWidthForName = qMax(requiredWidthForName, line.naturalTextWidth());
651 ++textLinesCountForName;
652 }
653 layout.endLayout();
654
655 // Use one line for each additional information
656 int textLinesCount = textLinesCountForName;
657 const int additionalRolesCount = qMax(visibleRoles().count() - 1, 0);
658 textLinesCount += additionalRolesCount;
659
660 m_text[Name].setTextWidth(maxWidth);
661 m_textPos[Name] = QPointF(padding, widgetHeight - textLinesCount * fontHeight - padding);
662 m_textRect = QRectF(padding + (maxWidth - requiredWidthForName) / 2,
663 m_textPos[Name].y(),
664 requiredWidthForName,
665 textLinesCountForName * fontHeight);
666
667 // Calculate the position for each additional information
668 qreal y = m_textPos[Name].y() + textLinesCountForName * fontHeight;
669 foreach (const QByteArray& role, m_sortedVisibleRoles) {
670 const TextId textId = roleTextId(role);
671 if (textId == Name) {
672 continue;
673 }
674
675 const QString text = roleText(role, values);
676 m_text[textId].setText(text);
677
678 qreal requiredWidth = 0;
679
680 QTextLayout layout(text, option.font);
681 layout.setTextOption(m_text[textId].textOption());
682 layout.beginLayout();
683 QTextLine textLine = layout.createLine();
684 if (textLine.isValid()) {
685 textLine.setLineWidth(maxWidth);
686 requiredWidth = textLine.naturalTextWidth();
687 if (textLine.textLength() < text.length()) {
688 // TODO: QFontMetrics::elidedText() works different regarding the given width
689 // in comparison to QTextLine::setLineWidth(). It might happen that the text does
690 // not get elided although it does not fit into the given width. As workaround
691 // the padding is substracted.
692 const QString elidedText = option.fontMetrics.elidedText(text, Qt::ElideRight, maxWidth - padding);
693 m_text[textId].setText(elidedText);
694 }
695 }
696 layout.endLayout();
697
698 m_textPos[textId] = QPointF(padding, y);
699 m_text[textId].setTextWidth(maxWidth);
700
701 const QRectF textRect(padding + (maxWidth - requiredWidth) / 2, y, requiredWidth, fontHeight);
702 m_textRect |= textRect;
703
704 y += fontHeight;
705 }
706
707 // Add a padding to the text rectangle
708 m_textRect.adjust(-padding, -padding, padding, padding);
709 }
710
711 void KFileItemListWidget::updateCompactLayoutTextCache()
712 {
713 // +------+ Name role
714 // | Icon | Additional role 1
715 // +------+ Additional role 2
716
717 const QHash<QByteArray, QVariant> values = data();
718
719 const KItemListStyleOption& option = styleOption();
720 const qreal widgetHeight = size().height();
721 const qreal fontHeight = option.fontMetrics.height();
722 const qreal textLinesHeight = qMax(visibleRoles().count(), 1) * fontHeight;
723 const int scaledIconSize = (textLinesHeight < option.iconSize) ? widgetHeight - 2 * option.padding : option.iconSize;
724
725 qreal maximumRequiredTextWidth = 0;
726 const qreal x = option.padding * 3 + scaledIconSize;
727 qreal y = (widgetHeight - textLinesHeight) / 2;
728 const qreal maxWidth = size().width() - x - option.padding;
729 foreach (const QByteArray& role, m_sortedVisibleRoles) {
730 const TextId textId = roleTextId(role);
731
732 const QString text = roleText(role, values);
733 m_text[textId].setText(text);
734
735 qreal requiredWidth = option.fontMetrics.width(text);
736 if (requiredWidth > maxWidth) {
737 requiredWidth = maxWidth;
738 const QString elidedText = option.fontMetrics.elidedText(text, Qt::ElideRight, maxWidth);
739 m_text[textId].setText(elidedText);
740 }
741
742 m_textPos[textId] = QPointF(x, y);
743 m_text[textId].setTextWidth(maxWidth);
744
745 maximumRequiredTextWidth = qMax(maximumRequiredTextWidth, requiredWidth);
746
747 y += fontHeight;
748 }
749
750 m_textRect = QRectF(x - option.padding, 0, maximumRequiredTextWidth + 2 * option.padding, widgetHeight);
751 }
752
753 void KFileItemListWidget::updateDetailsLayoutTextCache()
754 {
755 // Precondition: Requires already updated m_expansionArea
756 // to determine the left position.
757
758 // +------+
759 // | Icon | Name role Additional role 1 Additional role 2
760 // +------+
761 m_textRect = QRectF();
762
763 const KItemListStyleOption& option = styleOption();
764 const QHash<QByteArray, QVariant> values = data();
765
766 const qreal widgetHeight = size().height();
767 const int scaledIconSize = widgetHeight - 2 * option.padding;
768 const int fontHeight = option.fontMetrics.height();
769
770 const qreal columnPadding = option.padding * 3;
771 qreal firstColumnInc = scaledIconSize;
772 if (m_supportsItemExpanding) {
773 firstColumnInc += (m_expansionArea.left() + m_expansionArea.right() + widgetHeight) / 2;
774 } else {
775 firstColumnInc += option.padding;
776 }
777
778 qreal x = firstColumnInc;
779 const qreal y = qMax(qreal(option.padding), (widgetHeight - fontHeight) / 2);
780
781 foreach (const QByteArray& role, m_sortedVisibleRoles) {
782 const TextId textId = roleTextId(role);
783
784 QString text = roleText(role, values);
785
786 // Elide the text in case it does not fit into the available column-width
787 qreal requiredWidth = option.fontMetrics.width(text);
788 const qreal roleWidth = columnWidth(role);
789 qreal availableTextWidth = roleWidth - 2 * columnPadding;
790 if (textId == Name) {
791 availableTextWidth -= firstColumnInc;
792 }
793
794 if (requiredWidth > availableTextWidth) {
795 text = option.fontMetrics.elidedText(text, Qt::ElideRight, availableTextWidth);
796 requiredWidth = option.fontMetrics.width(text);
797 }
798
799 m_text[textId].setText(text);
800 m_textPos[textId] = QPointF(x + columnPadding, y);
801 x += roleWidth;
802
803 switch (textId) {
804 case Name: {
805 const qreal textWidth = option.extendedSelectionRegion
806 ? size().width() - m_textPos[textId].x()
807 : requiredWidth + 2 * option.padding;
808 m_textRect = QRectF(m_textPos[textId].x() - option.padding, 0,
809 textWidth, size().height());
810
811 // The column after the name should always be aligned on the same x-position independent
812 // from the expansion-level shown in the name column
813 x -= firstColumnInc;
814 break;
815 }
816 case Size:
817 // The values for the size should be right aligned
818 m_textPos[textId].rx() += roleWidth - requiredWidth - 2 * columnPadding;
819 break;
820
821 default:
822 break;
823 }
824 }
825 }
826
827 void KFileItemListWidget::updateAdditionalInfoTextColor()
828 {
829 QColor c1;
830 if (m_customTextColor.isValid()) {
831 c1 = m_customTextColor;
832 } else if (isSelected() && m_layout != DetailsLayout) {
833 c1 = styleOption().palette.highlightedText().color();
834 } else {
835 c1 = styleOption().palette.text().color();
836 }
837
838 // For the color of the additional info the inactive text color
839 // is not used as this might lead to unreadable text for some color schemes. Instead
840 // the text color c1 is slightly mixed with the background color.
841 const QColor c2 = styleOption().palette.base().color();
842 const int p1 = 70;
843 const int p2 = 100 - p1;
844 m_additionalInfoTextColor = QColor((c1.red() * p1 + c2.red() * p2) / 100,
845 (c1.green() * p1 + c2.green() * p2) / 100,
846 (c1.blue() * p1 + c2.blue() * p2) / 100);
847 }
848
849 void KFileItemListWidget::drawPixmap(QPainter* painter, const QPixmap& pixmap)
850 {
851 if (m_scaledPixmapSize != pixmap.size()) {
852 QPixmap scaledPixmap = pixmap;
853 KPixmapModifier::scale(scaledPixmap, m_scaledPixmapSize);
854 painter->drawPixmap(m_pixmapPos, scaledPixmap);
855
856 #ifdef KFILEITEMLISTWIDGET_DEBUG
857 painter->setPen(Qt::blue);
858 painter->drawRect(QRectF(m_pixmapPos, QSizeF(m_scaledPixmapSize)));
859 #endif
860 } else {
861 painter->drawPixmap(m_pixmapPos, pixmap);
862 }
863 }
864
865 void KFileItemListWidget::drawSiblingsInformation(QPainter* painter)
866 {
867 const int siblingSize = size().height();
868 const int x = (m_expansionArea.left() + m_expansionArea.right() - siblingSize) / 2;
869 QRect siblingRect(x, 0, siblingSize, siblingSize);
870
871 QStyleOption option;
872 bool isItemSibling = true;
873
874 const QBitArray siblings = siblingsInformation();
875 for (int i = siblings.count() - 1; i >= 0; --i) {
876 option.rect = siblingRect;
877 option.state = siblings.at(i) ? QStyle::State_Sibling : QStyle::State_None;
878
879 if (isItemSibling) {
880 option.state |= QStyle::State_Item;
881 if (m_isExpandable) {
882 option.state |= QStyle::State_Children;
883 }
884 if (data()["isExpanded"].toBool()) {
885 option.state |= QStyle::State_Open;
886 }
887 isItemSibling = false;
888 }
889
890 style()->drawPrimitive(QStyle::PE_IndicatorBranch, &option, painter);
891
892 siblingRect.translate(-siblingRect.width(), 0);
893 }
894 }
895
896 QPixmap KFileItemListWidget::pixmapForIcon(const QString& name, int size)
897 {
898 const KIcon icon(name);
899
900 int requestedSize;
901 if (size <= KIconLoader::SizeSmall) {
902 requestedSize = KIconLoader::SizeSmall;
903 } else if (size <= KIconLoader::SizeSmallMedium) {
904 requestedSize = KIconLoader::SizeSmallMedium;
905 } else if (size <= KIconLoader::SizeMedium) {
906 requestedSize = KIconLoader::SizeMedium;
907 } else if (size <= KIconLoader::SizeLarge) {
908 requestedSize = KIconLoader::SizeLarge;
909 } else if (size <= KIconLoader::SizeHuge) {
910 requestedSize = KIconLoader::SizeHuge;
911 } else if (size <= KIconLoader::SizeEnormous) {
912 requestedSize = KIconLoader::SizeEnormous;
913 } else if (size <= KIconLoader::SizeEnormous * 2) {
914 requestedSize = KIconLoader::SizeEnormous * 2;
915 } else {
916 requestedSize = size;
917 }
918
919 QPixmap pixmap = icon.pixmap(requestedSize, requestedSize);
920 if (requestedSize != size) {
921 KPixmapModifier::scale(pixmap, QSize(size, size));
922 }
923
924 return pixmap;
925 }
926
927 KFileItemListWidget::TextId KFileItemListWidget::roleTextId(const QByteArray& role)
928 {
929 static QHash<QByteArray, TextId> rolesHash;
930 if (rolesHash.isEmpty()) {
931 rolesHash.insert("name", Name);
932 rolesHash.insert("size", Size);
933 rolesHash.insert("date", Date);
934 rolesHash.insert("permissions", Permissions);
935 rolesHash.insert("owner", Owner);
936 rolesHash.insert("group", Group);
937 rolesHash.insert("type", Type);
938 rolesHash.insert("destination", Destination);
939 rolesHash.insert("path", Path);
940 }
941
942 return rolesHash.value(role);
943 }
944
945 void KFileItemListWidget::applyCutEffect(QPixmap& pixmap)
946 {
947 KIconEffect* effect = KIconLoader::global()->iconEffect();
948 pixmap = effect->apply(pixmap, KIconLoader::Desktop, KIconLoader::DisabledState);
949 }
950
951 void KFileItemListWidget::applyHiddenEffect(QPixmap& pixmap)
952 {
953 KIconEffect::semiTransparent(pixmap);
954 }
955
956 #include "kfileitemlistwidget.moc"