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