]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemlistwidget.cpp
Fix icon alignment issue in the details view
[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_dirtyLayout(true),
49 m_dirtyContent(true),
50 m_dirtyContentRoles(),
51 m_layout(IconsLayout),
52 m_pixmapPos(),
53 m_pixmap(),
54 m_scaledPixmapSize(),
55 m_originalPixmapSize(),
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 update();
83 }
84 }
85
86 KFileItemListWidget::Layout KFileItemListWidget::layout() const
87 {
88 return m_layout;
89 }
90
91 void KFileItemListWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
92 {
93 const_cast<KFileItemListWidget*>(this)->triggerCacheRefreshing();
94
95 KItemListWidget::paint(painter, option, widget);
96
97 // Draw expansion toggle '>' or 'V'
98 if (m_isExpandable && !m_expansionArea.isEmpty()) {
99 QStyleOption arrowOption;
100 arrowOption.rect = m_expansionArea.toRect();
101 const QStyle::PrimitiveElement arrow = data()["isExpanded"].toBool()
102 ? QStyle::PE_IndicatorArrowDown : QStyle::PE_IndicatorArrowRight;
103 style()->drawPrimitive(arrow, &arrowOption, painter);
104 }
105
106 const KItemListStyleOption& itemListStyleOption = styleOption();
107 if (isHovered()) {
108 // Blend the unhovered and hovered pixmap if the hovering
109 // animation is ongoing
110 if (hoverOpacity() < 1.0) {
111 drawPixmap(painter, m_pixmap);
112 }
113
114 const qreal opacity = painter->opacity();
115 painter->setOpacity(hoverOpacity() * opacity);
116 drawPixmap(painter, m_hoverPixmap);
117 painter->setOpacity(opacity);
118 } else {
119 drawPixmap(painter, m_pixmap);
120 }
121
122 painter->setFont(itemListStyleOption.font);
123 painter->setPen(textColor());
124 painter->drawStaticText(m_textPos[Name], m_text[Name]);
125
126 bool clipAdditionalInfoBounds = false;
127 if (m_layout == DetailsLayout) {
128 // Prevent a possible overlapping of the additional-information texts
129 // with the icon. This can happen if the user has minimized the width
130 // of the name-column to a very small value.
131 const qreal minX = m_pixmapPos.x() + m_pixmap.width() + 4 * itemListStyleOption.margin;
132 if (m_textPos[Name + 1].x() < minX) {
133 clipAdditionalInfoBounds = true;
134 painter->save();
135 painter->setClipRect(minX, 0, size().width() - minX, size().height(), Qt::IntersectClip);
136 }
137 }
138
139 painter->setPen(m_additionalInfoTextColor);
140 painter->setFont(itemListStyleOption.font);
141 for (int i = Name + 1; i < TextIdCount; ++i) {
142 painter->drawStaticText(m_textPos[i], m_text[i]);
143 }
144
145 if (clipAdditionalInfoBounds) {
146 painter->restore();
147 }
148
149 #ifdef KFILEITEMLISTWIDGET_DEBUG
150 painter->setBrush(Qt::NoBrush);
151 painter->setPen(Qt::green);
152 painter->drawRect(m_iconRect);
153
154 painter->setPen(Qt::red);
155 painter->drawText(QPointF(0, itemListStyleOption.fontMetrics.height()), QString::number(index()));
156 painter->drawRect(rect());
157 #endif
158 }
159
160 QRectF KFileItemListWidget::iconRect() const
161 {
162 const_cast<KFileItemListWidget*>(this)->triggerCacheRefreshing();
163 return m_iconRect;
164 }
165
166 QRectF KFileItemListWidget::textRect() const
167 {
168 const_cast<KFileItemListWidget*>(this)->triggerCacheRefreshing();
169 return m_textRect;
170 }
171
172 QRectF KFileItemListWidget::expansionToggleRect() const
173 {
174 const_cast<KFileItemListWidget*>(this)->triggerCacheRefreshing();
175 return m_isExpandable ? m_expansionArea : QRectF();
176 }
177
178 QRectF KFileItemListWidget::selectionToggleRect() const
179 {
180 const_cast<KFileItemListWidget*>(this)->triggerCacheRefreshing();
181
182 const int iconHeight = m_pixmap.height();
183
184 int toggleSize = KIconLoader::SizeSmall;
185 if (iconHeight >= KIconLoader::SizeEnormous) {
186 toggleSize = KIconLoader::SizeMedium;
187 } else if (iconHeight >= KIconLoader::SizeLarge) {
188 toggleSize = KIconLoader::SizeSmallMedium;
189 }
190
191 QPointF pos = iconRect().topLeft();
192
193 // If the selection toggle has a very small distance to the
194 // widget borders, the size of the selection toggle will get
195 // increased to prevent an accidental clicking of the item
196 // when trying to hit the toggle.
197 const int widgetHeight = size().height();
198 const int widgetWidth = size().width();
199 const int minMargin = 2;
200
201 if (toggleSize + minMargin * 2 >= widgetHeight) {
202 toggleSize = widgetHeight;
203 pos.setY(0);
204 }
205 if (toggleSize + minMargin * 2 >= widgetWidth) {
206 toggleSize = widgetWidth;
207 pos.setX(0);
208 }
209
210 return QRectF(pos, QSizeF(toggleSize, toggleSize));
211 }
212
213 QString KFileItemListWidget::roleText(const QByteArray& role, const QHash<QByteArray, QVariant>& values)
214 {
215 QString text;
216 const QVariant roleValue = values.value(role);
217
218 switch (roleTextId(role)) {
219 case Name:
220 case Permissions:
221 case Owner:
222 case Group:
223 case Type:
224 case Destination:
225 case Path:
226 text = roleValue.toString();
227 break;
228
229 case Size: {
230 if (values.value("isDir").toBool()) {
231 // The item represents a directory. Show the number of sub directories
232 // instead of the file size of the directory.
233 if (roleValue.isNull()) {
234 text = i18nc("@item:intable", "Unknown");
235 } else {
236 const KIO::filesize_t size = roleValue.value<KIO::filesize_t>();
237 text = i18ncp("@item:intable", "%1 item", "%1 items", size);
238 }
239 } else {
240 // Show the size in kilobytes (always round up)
241 const KLocale* locale = KGlobal::locale();
242 const int roundInc = (locale->binaryUnitDialect() == KLocale::MetricBinaryDialect) ? 499 : 511;
243 const KIO::filesize_t size = roleValue.value<KIO::filesize_t>() + roundInc;
244 text = locale->formatByteSize(size, 0, KLocale::DefaultBinaryDialect, KLocale::UnitKiloByte);
245 }
246 break;
247 }
248
249 case Date: {
250 const QDateTime dateTime = roleValue.toDateTime();
251 text = KGlobal::locale()->formatDateTime(dateTime);
252 break;
253 }
254
255 default:
256 Q_ASSERT(false);
257 break;
258 }
259
260 return text;
261 }
262
263 void KFileItemListWidget::invalidateCache()
264 {
265 m_dirtyLayout = true;
266 m_dirtyContent = true;
267 }
268
269 void KFileItemListWidget::refreshCache()
270 {
271 }
272
273 void KFileItemListWidget::setTextColor(const QColor& color)
274 {
275 if (color != m_customTextColor) {
276 m_customTextColor = color;
277 updateAdditionalInfoTextColor();
278 update();
279 }
280 }
281
282 QColor KFileItemListWidget::textColor() const
283 {
284 if (m_customTextColor.isValid() && !isSelected()) {
285 return m_customTextColor;
286 }
287
288 const QPalette::ColorGroup group = isActiveWindow() ? QPalette::Active : QPalette::Inactive;
289 const QPalette::ColorRole role = isSelected() ? QPalette::HighlightedText : QPalette::Text;
290 return styleOption().palette.brush(group, role).color();
291 }
292
293 void KFileItemListWidget::setOverlay(const QPixmap& overlay)
294 {
295 m_overlay = overlay;
296 m_dirtyContent = true;
297 update();
298 }
299
300 QPixmap KFileItemListWidget::overlay() const
301 {
302 return m_overlay;
303 }
304
305 void KFileItemListWidget::dataChanged(const QHash<QByteArray, QVariant>& current,
306 const QSet<QByteArray>& roles)
307 {
308 KItemListWidget::dataChanged(current, roles);
309 m_dirtyContent = true;
310
311 QSet<QByteArray> dirtyRoles;
312 if (roles.isEmpty()) {
313 dirtyRoles = visibleRoles().toSet();
314 dirtyRoles.insert("iconPixmap");
315 dirtyRoles.insert("iconName");
316 } else {
317 dirtyRoles = roles;
318 }
319
320 QSetIterator<QByteArray> it(dirtyRoles);
321 while (it.hasNext()) {
322 const QByteArray& role = it.next();
323 m_dirtyContentRoles.insert(role);
324 }
325 }
326
327 void KFileItemListWidget::visibleRolesChanged(const QList<QByteArray>& current,
328 const QList<QByteArray>& previous)
329 {
330 KItemListWidget::visibleRolesChanged(current, previous);
331 m_sortedVisibleRoles = current;
332 m_dirtyLayout = true;
333 }
334
335 void KFileItemListWidget::visibleRolesSizesChanged(const QHash<QByteArray, QSizeF>& current,
336 const QHash<QByteArray, QSizeF>& previous)
337 {
338 KItemListWidget::visibleRolesSizesChanged(current, previous);
339 m_dirtyLayout = true;
340 }
341
342 void KFileItemListWidget::styleOptionChanged(const KItemListStyleOption& current,
343 const KItemListStyleOption& previous)
344 {
345 KItemListWidget::styleOptionChanged(current, previous);
346 updateAdditionalInfoTextColor();
347 m_dirtyLayout = true;
348 }
349
350 void KFileItemListWidget::hoveredChanged(bool hovered)
351 {
352 Q_UNUSED(hovered);
353 m_dirtyLayout = true;
354 }
355
356 void KFileItemListWidget::selectedChanged(bool selected)
357 {
358 Q_UNUSED(selected);
359 updateAdditionalInfoTextColor();
360 }
361
362 void KFileItemListWidget::resizeEvent(QGraphicsSceneResizeEvent* event)
363 {
364 KItemListWidget::resizeEvent(event);
365 m_dirtyLayout = true;
366 }
367
368 void KFileItemListWidget::showEvent(QShowEvent* event)
369 {
370 KItemListWidget::showEvent(event);
371
372 // Listen to changes of the clipboard to mark the item as cut/uncut
373 KFileItemClipboard* clipboard = KFileItemClipboard::instance();
374
375 const KUrl itemUrl = data().value("url").value<KUrl>();
376 m_isCut = clipboard->isCut(itemUrl);
377
378 connect(clipboard, SIGNAL(cutItemsChanged()),
379 this, SLOT(slotCutItemsChanged()));
380 }
381
382 void KFileItemListWidget::hideEvent(QHideEvent* event)
383 {
384 disconnect(KFileItemClipboard::instance(), SIGNAL(cutItemsChanged()),
385 this, SLOT(slotCutItemsChanged()));
386
387 KItemListWidget::hideEvent(event);
388 }
389
390 void KFileItemListWidget::slotCutItemsChanged()
391 {
392 const KUrl itemUrl = data().value("url").value<KUrl>();
393 const bool isCut = KFileItemClipboard::instance()->isCut(itemUrl);
394 if (m_isCut != isCut) {
395 m_isCut = isCut;
396 m_pixmap = QPixmap();
397 m_dirtyContent = true;
398 update();
399 }
400 }
401
402 void KFileItemListWidget::triggerCacheRefreshing()
403 {
404 if ((!m_dirtyContent && !m_dirtyLayout) || index() < 0) {
405 return;
406 }
407
408 refreshCache();
409
410 const QHash<QByteArray, QVariant> values = data();
411 m_isExpandable = values["isExpandable"].toBool();
412 m_isHidden = values["name"].toString().startsWith(QLatin1Char('.'));
413
414 updateExpansionArea();
415 updateTextsCache();
416 updatePixmapCache();
417
418 m_dirtyLayout = false;
419 m_dirtyContent = false;
420 m_dirtyContentRoles.clear();
421 }
422
423 void KFileItemListWidget::updateExpansionArea()
424 {
425 if (m_layout == DetailsLayout) {
426 const QHash<QByteArray, QVariant> values = data();
427 Q_ASSERT(values.contains("expansionLevel"));
428 const KItemListStyleOption& option = styleOption();
429 const int expansionLevel = values.value("expansionLevel", 0).toInt();
430 if (expansionLevel >= 0) {
431 const qreal widgetHeight = size().height();
432 const qreal expansionLevelSize = KIconLoader::SizeSmall;
433 const qreal x = option.margin + expansionLevel * widgetHeight;
434 const qreal y = (widgetHeight - expansionLevelSize) / 2;
435 m_expansionArea = QRectF(x, y, expansionLevelSize, expansionLevelSize);
436 return;
437 }
438 }
439
440 m_expansionArea = QRectF();
441 }
442
443 void KFileItemListWidget::updatePixmapCache()
444 {
445 // Precondition: Requires already updated m_textPos values to calculate
446 // the remaining height when the alignment is vertical.
447
448 const bool iconOnTop = (m_layout == IconsLayout);
449 const KItemListStyleOption& option = styleOption();
450 const int iconHeight = option.iconSize;
451
452 const QHash<QByteArray, QVariant> values = data();
453 const QSizeF widgetSize = size();
454
455 int scaledIconHeight = 0;
456 if (iconOnTop) {
457 scaledIconHeight = static_cast<int>(m_textPos[Name].y() - 3 * option.margin);
458 } else {
459 const int textRowsCount = (m_layout == CompactLayout) ? visibleRoles().count() : 1;
460 const qreal requiredTextHeight = textRowsCount * option.fontMetrics.height();
461 scaledIconHeight = (requiredTextHeight < iconHeight) ? widgetSize.height() - 2 * option.margin : iconHeight;
462 }
463
464 bool updatePixmap = (iconHeight != m_pixmap.height());
465 if (!updatePixmap && m_dirtyContent) {
466 updatePixmap = m_dirtyContentRoles.isEmpty()
467 || m_dirtyContentRoles.contains("iconPixmap")
468 || m_dirtyContentRoles.contains("iconName")
469 || m_dirtyContentRoles.contains("iconOverlays");
470 }
471
472 if (updatePixmap) {
473 m_pixmap = values["iconPixmap"].value<QPixmap>();
474 if (m_pixmap.isNull()) {
475 // Use the icon that fits to the MIME-type
476 QString iconName = values["iconName"].toString();
477 if (iconName.isEmpty()) {
478 // The icon-name has not been not resolved by KFileItemModelRolesUpdater,
479 // use a generic icon as fallback
480 iconName = QLatin1String("unknown");
481 }
482 m_pixmap = pixmapForIcon(iconName, iconHeight);
483 m_originalPixmapSize = m_pixmap.size();
484 } else if (m_pixmap.size() != QSize(iconHeight, iconHeight)) {
485 // A custom pixmap has been applied. Assure that the pixmap
486 // is scaled to the available size.
487 const bool scale = m_pixmap.width() > iconHeight || m_pixmap.height() > iconHeight ||
488 (m_pixmap.width() < iconHeight && m_pixmap.height() < iconHeight);
489 if (scale) {
490 KPixmapModifier::scale(m_pixmap, QSize(iconHeight, iconHeight));
491 }
492 m_originalPixmapSize = m_pixmap.size();
493
494 // To simplify the handling of scaling the original pixmap
495 // will be embedded into a square pixmap.
496 QPixmap squarePixmap(iconHeight, iconHeight);
497 squarePixmap.fill(Qt::transparent);
498
499 QPainter painter(&squarePixmap);
500 const int x = (iconHeight - m_pixmap.width()) / 2; // Center horizontally
501 int y = iconHeight - m_pixmap.height(); // Move to bottom
502 if (!iconOnTop) {
503 y /= 2.0; // Center vertically
504 }
505 painter.drawPixmap(x, y, m_pixmap);
506
507 m_pixmap = squarePixmap;
508 } else {
509 m_originalPixmapSize = m_pixmap.size();
510 }
511
512 const QStringList overlays = values["iconOverlays"].toStringList();
513
514 // Strangely KFileItem::overlays() returns empty string-values, so
515 // we need to check first whether an overlay must be drawn at all.
516 // It is more efficient to do it here, as KIconLoader::drawOverlays()
517 // assumes that an overlay will be drawn and has some additional
518 // setup time.
519 foreach (const QString& overlay, overlays) {
520 if (!overlay.isEmpty()) {
521 // There is at least one overlay, draw all overlays above m_pixmap
522 // and cancel the check
523 KIconLoader::global()->drawOverlays(overlays, m_pixmap, KIconLoader::Desktop);
524 break;
525 }
526 }
527
528 if (m_isCut) {
529 applyCutEffect(m_pixmap);
530 }
531
532 if (m_isHidden) {
533 applyHiddenEffect(m_pixmap);
534 }
535
536 Q_ASSERT(m_pixmap.height() == iconHeight);
537 }
538 if (!m_overlay.isNull()) {
539 QPainter painter(&m_pixmap);
540 painter.drawPixmap(0, m_pixmap.height() - m_overlay.height(), m_overlay);
541 }
542
543 m_scaledPixmapSize = QSize(scaledIconHeight, scaledIconHeight);
544
545 if (iconOnTop) {
546 m_pixmapPos.setX((widgetSize.width() - m_scaledPixmapSize.width()) / 2);
547 } else {
548 m_pixmapPos.setX(m_textPos[Name].x() - 2 * option.margin - scaledIconHeight);
549 }
550 m_pixmapPos.setY(option.margin);
551
552 // Center the hover rectangle horizontally and align it on bottom
553 qreal hoverWidth = m_originalPixmapSize.width();
554 qreal hoverHeight = m_originalPixmapSize.height();
555 if (scaledIconHeight != m_pixmap.height()) {
556 const qreal scaleFactor = qreal(scaledIconHeight) / qreal(m_pixmap.height());
557 hoverWidth *= scaleFactor;
558 hoverHeight *= scaleFactor;
559 }
560 const qreal hoverX = m_pixmapPos.x() + (m_scaledPixmapSize.width() - hoverWidth) / 2.0;
561 qreal hoverY = m_scaledPixmapSize.height() - hoverHeight;
562 if (!iconOnTop) {
563 hoverY /= 2.0;
564 }
565 hoverY += m_pixmapPos.y();
566
567 m_iconRect = QRectF(hoverX, hoverY, hoverWidth, hoverHeight);
568 const qreal margin = option.margin;
569 m_iconRect.adjust(-margin, -margin, margin, margin);
570
571 // Prepare the pixmap that is used when the item gets hovered
572 if (isHovered()) {
573 m_hoverPixmap = m_pixmap;
574 KIconEffect* effect = KIconLoader::global()->iconEffect();
575 // In the KIconLoader terminology, active = hover.
576 if (effect->hasEffect(KIconLoader::Desktop, KIconLoader::ActiveState)) {
577 m_hoverPixmap = effect->apply(m_pixmap, KIconLoader::Desktop, KIconLoader::ActiveState);
578 } else {
579 m_hoverPixmap = m_pixmap;
580 }
581 } else if (hoverOpacity() <= 0.0) {
582 // No hover animation is ongoing. Clear m_hoverPixmap to save memory.
583 m_hoverPixmap = QPixmap();
584 }
585 }
586
587 void KFileItemListWidget::updateTextsCache()
588 {
589 QTextOption textOption;
590 switch (m_layout) {
591 case IconsLayout:
592 textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
593 textOption.setAlignment(Qt::AlignHCenter);
594 break;
595 case CompactLayout:
596 case DetailsLayout:
597 textOption.setAlignment(Qt::AlignLeft);
598 textOption.setWrapMode(QTextOption::NoWrap);
599 break;
600 default:
601 Q_ASSERT(false);
602 break;
603 }
604
605 for (int i = 0; i < TextIdCount; ++i) {
606 m_text[i].setText(QString());
607 m_text[i].setTextOption(textOption);
608 }
609
610 switch (m_layout) {
611 case IconsLayout: updateIconsLayoutTextCache(); break;
612 case CompactLayout: updateCompactLayoutTextCache(); break;
613 case DetailsLayout: updateDetailsLayoutTextCache(); break;
614 default: Q_ASSERT(false); break;
615 }
616 }
617
618 void KFileItemListWidget::updateIconsLayoutTextCache()
619 {
620 // +------+
621 // | Icon |
622 // +------+
623 //
624 // Name role that
625 // might get wrapped above
626 // several lines.
627 // Additional role 1
628 // Additional role 2
629
630 const QHash<QByteArray, QVariant> values = data();
631
632 const KItemListStyleOption& option = styleOption();
633 const qreal maxWidth = size().width() - 2 * option.margin;
634 const qreal widgetHeight = size().height();
635 const qreal fontHeight = option.fontMetrics.height();
636
637 // Initialize properties for the "name" role. It will be used as anchor
638 // for initializing the position of the other roles.
639 m_text[Name].setText(KStringHandler::preProcessWrap(values["name"].toString()));
640
641 // Calculate the number of lines required for the name and the required width
642 int textLinesCountForName = 0;
643 qreal requiredWidthForName = 0;
644 QTextLine line;
645
646 QTextLayout layout(m_text[Name].text(), option.font);
647 layout.setTextOption(m_text[Name].textOption());
648 layout.beginLayout();
649 while ((line = layout.createLine()).isValid()) {
650 line.setLineWidth(maxWidth);
651 requiredWidthForName = qMax(requiredWidthForName, line.naturalTextWidth());
652 ++textLinesCountForName;
653 }
654 layout.endLayout();
655
656 // Use one line for each additional information
657 int textLinesCount = textLinesCountForName;
658 const int additionalRolesCount = qMax(visibleRoles().count() - 1, 0);
659 textLinesCount += additionalRolesCount;
660
661 m_text[Name].setTextWidth(maxWidth);
662 m_textPos[Name] = QPointF(option.margin, widgetHeight - textLinesCount * fontHeight - option.margin);
663 m_textRect = QRectF(option.margin + (maxWidth - requiredWidthForName) / 2,
664 m_textPos[Name].y(),
665 requiredWidthForName,
666 textLinesCountForName * fontHeight);
667
668 // Calculate the position for each additional information
669 qreal y = m_textPos[Name].y() + textLinesCountForName * fontHeight;
670 foreach (const QByteArray& role, m_sortedVisibleRoles) {
671 const TextId textId = roleTextId(role);
672 if (textId == Name) {
673 continue;
674 }
675
676 const QString text = roleText(role, values);
677 m_text[textId].setText(text);
678
679 qreal requiredWidth = 0;
680
681 QTextLayout layout(text, option.font);
682 layout.setTextOption(m_text[textId].textOption());
683 layout.beginLayout();
684 QTextLine textLine = layout.createLine();
685 if (textLine.isValid()) {
686 textLine.setLineWidth(maxWidth);
687 requiredWidth = textLine.naturalTextWidth();
688 if (textLine.textLength() < text.length()) {
689 // TODO: QFontMetrics::elidedText() works different regarding the given width
690 // in comparison to QTextLine::setLineWidth(). It might happen that the text does
691 // not get elided although it does not fit into the given width. As workaround
692 // the margin is substracted.
693 const QString elidedText = option.fontMetrics.elidedText(text, Qt::ElideRight, maxWidth - option.margin);
694 m_text[textId].setText(elidedText);
695 }
696 }
697 layout.endLayout();
698
699 m_textPos[textId] = QPointF(option.margin, y);
700 m_text[textId].setTextWidth(maxWidth);
701
702 const QRectF textRect(option.margin + (maxWidth - requiredWidth) / 2, y, requiredWidth, fontHeight);
703 m_textRect |= textRect;
704
705 y += fontHeight;
706 }
707
708 // Add a margin to the text rectangle
709 const qreal margin = option.margin;
710 m_textRect.adjust(-margin, -margin, margin, margin);
711 }
712
713 void KFileItemListWidget::updateCompactLayoutTextCache()
714 {
715 // +------+ Name role
716 // | Icon | Additional role 1
717 // +------+ Additional role 2
718
719 const QHash<QByteArray, QVariant> values = data();
720
721 const KItemListStyleOption& option = styleOption();
722 const qreal widgetHeight = size().height();
723 const qreal fontHeight = option.fontMetrics.height();
724 const qreal textLinesHeight = qMax(visibleRoles().count(), 1) * fontHeight;
725 const int scaledIconSize = (textLinesHeight < option.iconSize) ? widgetHeight - 2 * option.margin : option.iconSize;
726
727 qreal maximumRequiredTextWidth = 0;
728 const qreal x = option.margin * 3 + scaledIconSize;
729 qreal y = (widgetHeight - textLinesHeight) / 2;
730 const qreal maxWidth = size().width() - x - option.margin;
731 foreach (const QByteArray& role, m_sortedVisibleRoles) {
732 const TextId textId = roleTextId(role);
733
734 const QString text = roleText(role, values);
735 m_text[textId].setText(text);
736
737 qreal requiredWidth = option.fontMetrics.width(text);
738 if (requiredWidth > maxWidth) {
739 requiredWidth = maxWidth;
740 const QString elidedText = option.fontMetrics.elidedText(text, Qt::ElideRight, maxWidth);
741 m_text[textId].setText(elidedText);
742 }
743
744 m_textPos[textId] = QPointF(x, y);
745 m_text[textId].setTextWidth(maxWidth);
746
747 maximumRequiredTextWidth = qMax(maximumRequiredTextWidth, requiredWidth);
748
749 y += fontHeight;
750 }
751
752 m_textRect = QRectF(x - option.margin, 0, maximumRequiredTextWidth + 2 * option.margin, widgetHeight);
753 }
754
755 void KFileItemListWidget::updateDetailsLayoutTextCache()
756 {
757 // Precondition: Requires already updated m_expansionArea
758 // to determine the left position.
759
760 // +------+
761 // | Icon | Name role Additional role 1 Additional role 2
762 // +------+
763 m_textRect = QRectF();
764
765 const KItemListStyleOption& option = styleOption();
766 const QHash<QByteArray, QVariant> values = data();
767
768 const qreal widgetHeight = size().height();
769 const int scaledIconSize = widgetHeight - 2 * option.margin;
770 const int fontHeight = option.fontMetrics.height();
771
772 const qreal columnMargin = option.margin * 3;
773 const qreal firstColumnInc = m_expansionArea.right() + option.margin * 2 + scaledIconSize;
774 qreal x = firstColumnInc;
775 const qreal y = qMax(qreal(option.margin), (widgetHeight - fontHeight) / 2);
776
777 foreach (const QByteArray& role, m_sortedVisibleRoles) {
778 const TextId textId = roleTextId(role);
779
780 QString text = roleText(role, values);
781
782 // Elide the text in case it does not fit into the available column-width
783 qreal requiredWidth = option.fontMetrics.width(text);
784 const qreal columnWidth = visibleRolesSizes().value(role, QSizeF(0, 0)).width();
785 qreal availableTextWidth = columnWidth - 2 * columnMargin;
786 if (textId == Name) {
787 availableTextWidth -= firstColumnInc;
788 }
789
790 if (requiredWidth > availableTextWidth) {
791 text = option.fontMetrics.elidedText(text, Qt::ElideRight, availableTextWidth);
792 requiredWidth = option.fontMetrics.width(text);
793 }
794
795 m_text[textId].setText(text);
796 m_textPos[textId] = QPointF(x + columnMargin, y);
797 x += columnWidth;
798
799 switch (textId) {
800 case Name: {
801 m_textRect = QRectF(m_textPos[textId].x() - option.margin, 0,
802 requiredWidth + 2 * option.margin, size().height());
803
804 // The column after the name should always be aligned on the same x-position independent
805 // from the expansion-level shown in the name column
806 x -= firstColumnInc;
807 break;
808 }
809 case Size:
810 // The values for the size should be right aligned
811 m_textPos[textId].rx() += columnWidth - requiredWidth - 2 * columnMargin;
812 break;
813
814 default:
815 break;
816 }
817 }
818 }
819
820 void KFileItemListWidget::updateAdditionalInfoTextColor()
821 {
822 QColor c1;
823 if (m_customTextColor.isValid()) {
824 c1 = m_customTextColor;
825 } else if (isSelected() && m_layout != DetailsLayout) {
826 c1 = styleOption().palette.highlightedText().color();
827 } else {
828 c1 = styleOption().palette.text().color();
829 }
830
831 // For the color of the additional info the inactive text color
832 // is not used as this might lead to unreadable text for some color schemes. Instead
833 // the text color c1 is slightly mixed with the background color.
834 const QColor c2 = styleOption().palette.base().color();
835 const int p1 = 70;
836 const int p2 = 100 - p1;
837 m_additionalInfoTextColor = QColor((c1.red() * p1 + c2.red() * p2) / 100,
838 (c1.green() * p1 + c2.green() * p2) / 100,
839 (c1.blue() * p1 + c2.blue() * p2) / 100);
840 }
841
842 void KFileItemListWidget::drawPixmap(QPainter* painter, const QPixmap& pixmap)
843 {
844 if (m_scaledPixmapSize != pixmap.size()) {
845 QPixmap scaledPixmap = pixmap;
846 KPixmapModifier::scale(scaledPixmap, m_scaledPixmapSize);
847 painter->drawPixmap(m_pixmapPos, scaledPixmap);
848
849 #ifdef KFILEITEMLISTWIDGET_DEBUG
850 painter->setPen(Qt::blue);
851 painter->drawRect(QRectF(m_pixmapPos, QSizeF(scaledPixmap.size())));
852 #endif
853 } else {
854 painter->drawPixmap(m_pixmapPos, pixmap);
855 }
856 }
857
858 QPixmap KFileItemListWidget::pixmapForIcon(const QString& name, int size)
859 {
860 const KIcon icon(name);
861
862 int requestedSize;
863 if (size <= KIconLoader::SizeSmall) {
864 requestedSize = KIconLoader::SizeSmall;
865 } else if (size <= KIconLoader::SizeSmallMedium) {
866 requestedSize = KIconLoader::SizeSmallMedium;
867 } else if (size <= KIconLoader::SizeMedium) {
868 requestedSize = KIconLoader::SizeMedium;
869 } else if (size <= KIconLoader::SizeLarge) {
870 requestedSize = KIconLoader::SizeLarge;
871 } else if (size <= KIconLoader::SizeHuge) {
872 requestedSize = KIconLoader::SizeHuge;
873 } else if (size <= KIconLoader::SizeEnormous) {
874 requestedSize = KIconLoader::SizeEnormous;
875 } else if (size <= KIconLoader::SizeEnormous * 2) {
876 requestedSize = KIconLoader::SizeEnormous * 2;
877 } else {
878 requestedSize = size;
879 }
880
881 QPixmap pixmap = icon.pixmap(requestedSize, requestedSize);
882 if (requestedSize != size) {
883 KPixmapModifier::scale(pixmap, QSize(size, size));
884 }
885
886 return pixmap;
887 }
888
889 KFileItemListWidget::TextId KFileItemListWidget::roleTextId(const QByteArray& role)
890 {
891 static QHash<QByteArray, TextId> rolesHash;
892 if (rolesHash.isEmpty()) {
893 rolesHash.insert("name", Name);
894 rolesHash.insert("size", Size);
895 rolesHash.insert("date", Date);
896 rolesHash.insert("permissions", Permissions);
897 rolesHash.insert("owner", Owner);
898 rolesHash.insert("group", Group);
899 rolesHash.insert("type", Type);
900 rolesHash.insert("destination", Destination);
901 rolesHash.insert("path", Path);
902 }
903
904 return rolesHash.value(role);
905 }
906
907 void KFileItemListWidget::applyCutEffect(QPixmap& pixmap)
908 {
909 KIconEffect* effect = KIconLoader::global()->iconEffect();
910 pixmap = effect->apply(pixmap, KIconLoader::Desktop, KIconLoader::DisabledState);
911 }
912
913 void KFileItemListWidget::applyHiddenEffect(QPixmap& pixmap)
914 {
915 KIconEffect::semiTransparent(pixmap);
916 }
917
918 #include "kfileitemlistwidget.moc"