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