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