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