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