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