]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemlistwidget.cpp
Use correct selection color for inactive windows
[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 "kfileitemmodel.h"
23 #include "kitemlistview.h"
24 #include "kpixmapmodifier_p.h"
25
26 #include <KIcon>
27 #include <KIconEffect>
28 #include <KIconLoader>
29 #include <KLocale>
30 #include <KStringHandler>
31 #include <KDebug>
32
33 #include <QFontMetricsF>
34 #include <QGraphicsSceneResizeEvent>
35 #include <QPainter>
36 #include <QStyleOption>
37 #include <QTextLayout>
38 #include <QTextLine>
39
40 //#define KFILEITEMLISTWIDGET_DEBUG
41
42 KFileItemListWidget::KFileItemListWidget(QGraphicsItem* parent) :
43 KItemListWidget(parent),
44 m_isDir(false),
45 m_dirtyLayout(true),
46 m_dirtyContent(true),
47 m_dirtyContentRoles(),
48 m_layout(IconsLayout),
49 m_pixmapPos(),
50 m_pixmap(),
51 m_scaledPixmapSize(),
52 m_hoverPixmapRect(),
53 m_hoverPixmap(),
54 m_textPos(),
55 m_text(),
56 m_textRect(),
57 m_sortedVisibleRoles(),
58 m_expansionArea(),
59 m_customTextColor(),
60 m_additionalInfoTextColor(),
61 m_overlay()
62 {
63 for (int i = 0; i < TextIdCount; ++i) {
64 m_text[i].setTextFormat(Qt::PlainText);
65 m_text[i].setPerformanceHint(QStaticText::AggressiveCaching);
66 }
67 }
68
69 KFileItemListWidget::~KFileItemListWidget()
70 {
71 }
72
73 void KFileItemListWidget::setLayout(Layout layout)
74 {
75 if (m_layout != layout) {
76 m_layout = layout;
77 m_dirtyLayout = true;
78 update();
79 }
80 }
81
82 KFileItemListWidget::Layout KFileItemListWidget::layout() const
83 {
84 return m_layout;
85 }
86
87 void KFileItemListWidget::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
88 {
89 KItemListWidget::paint(painter, option, widget);
90
91 const_cast<KFileItemListWidget*>(this)->triggerCacheRefreshing();
92
93 // Draw expansion toggle '>' or 'V'
94 if (m_isDir && !m_expansionArea.isEmpty()) {
95 QStyleOption arrowOption;
96 arrowOption.rect = m_expansionArea.toRect();
97 const QStyle::PrimitiveElement arrow = data()["isExpanded"].toBool()
98 ? QStyle::PE_IndicatorArrowDown : QStyle::PE_IndicatorArrowRight;
99 style()->drawPrimitive(arrow, &arrowOption, painter);
100 }
101
102 const KItemListStyleOption& itemListStyleOption = styleOption();
103 if (isHovered()) {
104 // Blend the unhovered and hovered pixmap if the hovering
105 // animation is ongoing
106 if (hoverOpacity() < 1.0) {
107 drawPixmap(painter, m_pixmap);
108 }
109
110 const qreal opacity = painter->opacity();
111 painter->setOpacity(hoverOpacity() * opacity);
112 drawPixmap(painter, m_hoverPixmap);
113 painter->setOpacity(opacity);
114 } else {
115 drawPixmap(painter, m_pixmap);
116 }
117
118 painter->setFont(itemListStyleOption.font);
119 painter->setPen(textColor());
120 painter->drawStaticText(m_textPos[Name], m_text[Name]);
121
122 bool clipAdditionalInfoBounds = false;
123 if (m_layout == DetailsLayout) {
124 // Prevent a possible overlapping of the additional-information texts
125 // with the icon. This can happen if the user has minimized the width
126 // of the name-column to a very small value.
127 const qreal minX = m_pixmapPos.x() + m_pixmap.width() + 4 * itemListStyleOption.margin;
128 if (m_textPos[Name + 1].x() < minX) {
129 clipAdditionalInfoBounds = true;
130 painter->save();
131 painter->setClipRect(minX, 0, size().width() - minX, size().height(), Qt::IntersectClip);
132 }
133 }
134
135 painter->setPen(m_additionalInfoTextColor);
136 painter->setFont(itemListStyleOption.font);
137 for (int i = Name + 1; i < TextIdCount; ++i) {
138 painter->drawStaticText(m_textPos[i], m_text[i]);
139 }
140
141 if (clipAdditionalInfoBounds) {
142 painter->restore();
143 }
144
145 #ifdef KFILEITEMLISTWIDGET_DEBUG
146 painter->setPen(Qt::red);
147 painter->setBrush(Qt::NoBrush);
148 painter->drawText(QPointF(0, itemListStyleOption.fontMetrics.height()), QString::number(index()));
149 painter->drawRect(rect());
150 #endif
151 }
152
153 QRectF KFileItemListWidget::iconRect() const
154 {
155 const_cast<KFileItemListWidget*>(this)->triggerCacheRefreshing();
156
157 QRectF bounds = m_hoverPixmapRect;
158 const qreal margin = styleOption().margin;
159 bounds.adjust(-margin, -margin, margin, margin);
160 return bounds;
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_isDir ? 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()) {
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::triggerCacheRefreshing()
366 {
367 if ((!m_dirtyContent && !m_dirtyLayout) || index() < 0) {
368 return;
369 }
370
371 refreshCache();
372
373 m_isDir = data()["isDir"].toBool();
374
375 updateExpansionArea();
376 updateTextsCache();
377 updatePixmapCache();
378
379 m_dirtyLayout = false;
380 m_dirtyContent = false;
381 m_dirtyContentRoles.clear();
382 }
383
384 void KFileItemListWidget::updateExpansionArea()
385 {
386 if (m_layout == DetailsLayout) {
387 const QHash<QByteArray, QVariant> values = data();
388 Q_ASSERT(values.contains("expansionLevel"));
389 const KItemListStyleOption& option = styleOption();
390 const int expansionLevel = values.value("expansionLevel", 0).toInt();
391
392 const qreal widgetHeight = size().height();
393 const qreal expansionLevelSize = KIconLoader::SizeSmall;
394 const qreal x = option.margin + expansionLevel * widgetHeight;
395 const qreal y = (widgetHeight - expansionLevelSize) / 2;
396 m_expansionArea = QRectF(x, y, expansionLevelSize, expansionLevelSize);
397 } else {
398 m_expansionArea = QRectF();
399 }
400 }
401
402 void KFileItemListWidget::updatePixmapCache()
403 {
404 // Precondition: Requires already updated m_textPos values to calculate
405 // the remaining height when the alignment is vertical.
406
407 const bool iconOnTop = (m_layout == IconsLayout);
408 const KItemListStyleOption& option = styleOption();
409 const int iconHeight = option.iconSize;
410
411 const QHash<QByteArray, QVariant> values = data();
412 const QSizeF widgetSize = size();
413
414 int scaledIconHeight = 0;
415 if (iconOnTop) {
416 scaledIconHeight = static_cast<int>(m_textPos[Name].y() - 3 * option.margin);
417 } else {
418 const int textRowsCount = (m_layout == CompactLayout) ? visibleRoles().count() : 1;
419 const qreal requiredTextHeight = textRowsCount * option.fontMetrics.height();
420 scaledIconHeight = (requiredTextHeight < iconHeight) ? widgetSize.height() - 2 * option.margin : iconHeight;
421 }
422
423 bool updatePixmap = (iconHeight != m_pixmap.height());
424 if (!updatePixmap && m_dirtyContent) {
425 updatePixmap = m_dirtyContentRoles.isEmpty()
426 || m_dirtyContentRoles.contains("iconPixmap")
427 || m_dirtyContentRoles.contains("iconName");
428 }
429
430 if (updatePixmap) {
431 m_pixmap = values["iconPixmap"].value<QPixmap>();
432 if (m_pixmap.isNull()) {
433 // Use the icon that fits to the MIME-type
434 QString iconName = values["iconName"].toString();
435 if (iconName.isEmpty()) {
436 // The icon-name has not been not resolved by KFileItemModelRolesUpdater,
437 // use a generic icon as fallback
438 iconName = QLatin1String("unknown");
439 }
440 m_pixmap = pixmapForIcon(iconName, iconHeight);
441 m_hoverPixmapRect.setSize(m_pixmap.size());
442 } else if (m_pixmap.size() != QSize(iconHeight, iconHeight)) {
443 // A custom pixmap has been applied. Assure that the pixmap
444 // is scaled to the available size.
445 const bool scale = m_pixmap.width() > iconHeight || m_pixmap.height() > iconHeight ||
446 (m_pixmap.width() < iconHeight && m_pixmap.height() < iconHeight);
447 if (scale) {
448 KPixmapModifier::scale(m_pixmap, QSize(iconHeight, iconHeight));
449 }
450 m_hoverPixmapRect.setSize(m_pixmap.size());
451
452 // To simplify the handling of scaling the original pixmap
453 // will be embedded into a square pixmap.
454 QPixmap squarePixmap(iconHeight, iconHeight);
455 squarePixmap.fill(Qt::transparent);
456
457 QPainter painter(&squarePixmap);
458 int x, y;
459 if (iconOnTop) {
460 x = (iconHeight - m_pixmap.width()) / 2; // Center horizontally
461 y = iconHeight - m_pixmap.height(); // Align on bottom
462 painter.drawPixmap(x, y, m_pixmap);
463 } else {
464 x = iconHeight - m_pixmap.width(); // Align right
465 y = (iconHeight - m_pixmap.height()) / 2; // Center vertically
466 painter.drawPixmap(x, y, m_pixmap);
467 }
468
469 m_pixmap = squarePixmap;
470 } else {
471 m_hoverPixmapRect.setSize(m_pixmap.size());
472 }
473
474 Q_ASSERT(m_pixmap.height() == iconHeight);
475 }
476 if (!m_overlay.isNull()) {
477 QPainter painter(&m_pixmap);
478 painter.drawPixmap(0, m_pixmap.height() - m_overlay.height(), m_overlay);
479 }
480
481 m_scaledPixmapSize = QSize(scaledIconHeight, scaledIconHeight);
482
483 if (iconOnTop) {
484 m_pixmapPos.setX((widgetSize.width() - m_scaledPixmapSize.width()) / 2);
485 } else {
486 m_pixmapPos.setX(m_textPos[Name].x() - 2 * option.margin - scaledIconHeight);
487 }
488 m_pixmapPos.setY(option.margin);
489
490 // Center the hover rectangle horizontally and align it on bottom
491 const qreal x = m_pixmapPos.x() + (m_scaledPixmapSize.width() - m_hoverPixmapRect.width()) / 2.0;
492 const qreal y = m_pixmapPos.y() + m_scaledPixmapSize.height() - m_hoverPixmapRect.height();
493 m_hoverPixmapRect.moveTopLeft(QPointF(x, y));
494
495 // Prepare the pixmap that is used when the item gets hovered
496 if (isHovered()) {
497 m_hoverPixmap = m_pixmap;
498 KIconEffect* effect = KIconLoader::global()->iconEffect();
499 // In the KIconLoader terminology, active = hover.
500 if (effect->hasEffect(KIconLoader::Desktop, KIconLoader::ActiveState)) {
501 m_hoverPixmap = effect->apply(m_pixmap, KIconLoader::Desktop, KIconLoader::ActiveState);
502 } else {
503 m_hoverPixmap = m_pixmap;
504 }
505 } else if (hoverOpacity() <= 0.0) {
506 // No hover animation is ongoing. Clear m_hoverPixmap to save memory.
507 m_hoverPixmap = QPixmap();
508 }
509 }
510
511 void KFileItemListWidget::updateTextsCache()
512 {
513 QTextOption textOption;
514 switch (m_layout) {
515 case IconsLayout:
516 textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
517 textOption.setAlignment(Qt::AlignHCenter);
518 break;
519 case CompactLayout:
520 case DetailsLayout:
521 textOption.setAlignment(Qt::AlignLeft);
522 textOption.setWrapMode(QTextOption::NoWrap);
523 break;
524 default:
525 Q_ASSERT(false);
526 break;
527 }
528
529 for (int i = 0; i < TextIdCount; ++i) {
530 m_text[i].setText(QString());
531 m_text[i].setTextOption(textOption);
532 }
533
534 switch (m_layout) {
535 case IconsLayout: updateIconsLayoutTextCache(); break;
536 case CompactLayout: updateCompactLayoutTextCache(); break;
537 case DetailsLayout: updateDetailsLayoutTextCache(); break;
538 default: Q_ASSERT(false); break;
539 }
540 }
541
542 void KFileItemListWidget::updateIconsLayoutTextCache()
543 {
544 // +------+
545 // | Icon |
546 // +------+
547 //
548 // Name role that
549 // might get wrapped above
550 // several lines.
551 // Additional role 1
552 // Additional role 2
553
554 const QHash<QByteArray, QVariant> values = data();
555
556 const KItemListStyleOption& option = styleOption();
557 const qreal maxWidth = size().width() - 2 * option.margin;
558 const qreal widgetHeight = size().height();
559 const qreal fontHeight = option.fontMetrics.height();
560
561 // Initialize properties for the "name" role. It will be used as anchor
562 // for initializing the position of the other roles.
563 m_text[Name].setText(KStringHandler::preProcessWrap(values["name"].toString()));
564
565 // Calculate the number of lines required for the name and the required width
566 int textLinesCountForName = 0;
567 qreal requiredWidthForName = 0;
568 QTextLine line;
569
570 QTextLayout layout(m_text[Name].text(), option.font);
571 layout.setTextOption(m_text[Name].textOption());
572 layout.beginLayout();
573 while ((line = layout.createLine()).isValid()) {
574 line.setLineWidth(maxWidth);
575 requiredWidthForName = qMax(requiredWidthForName, line.naturalTextWidth());
576 ++textLinesCountForName;
577 }
578 layout.endLayout();
579
580 // Use one line for each additional information
581 int textLinesCount = textLinesCountForName;
582 const int additionalRolesCount = qMax(visibleRoles().count() - 1, 0);
583 textLinesCount += additionalRolesCount;
584
585 m_text[Name].setTextWidth(maxWidth);
586 m_textPos[Name] = QPointF(option.margin, widgetHeight - textLinesCount * fontHeight - option.margin);
587 m_textRect = QRectF(option.margin + (maxWidth - requiredWidthForName) / 2,
588 m_textPos[Name].y(),
589 requiredWidthForName,
590 m_text[Name].size().height());
591
592 // Calculate the position for each additional information
593 qreal y = m_textPos[Name].y() + textLinesCountForName * fontHeight;
594 foreach (const QByteArray& role, m_sortedVisibleRoles) {
595 const TextId textId = roleTextId(role);
596 if (textId == Name) {
597 continue;
598 }
599
600 const QString text = roleText(role, values);
601 m_text[textId].setText(text);
602
603 qreal requiredWidth = 0;
604
605 QTextLayout layout(text, option.font);
606 layout.setTextOption(m_text[textId].textOption());
607 layout.beginLayout();
608 QTextLine textLine = layout.createLine();
609 if (textLine.isValid()) {
610 textLine.setLineWidth(maxWidth);
611 requiredWidth = textLine.naturalTextWidth();
612 if (textLine.textLength() < text.length()) {
613 // TODO: QFontMetrics::elidedText() works different regarding the given width
614 // in comparison to QTextLine::setLineWidth(). It might happen that the text does
615 // not get elided although it does not fit into the given width. As workaround
616 // the margin is substracted.
617 const QString elidedText = option.fontMetrics.elidedText(text, Qt::ElideRight, maxWidth - option.margin);
618 m_text[textId].setText(elidedText);
619 }
620 }
621 layout.endLayout();
622
623 m_textPos[textId] = QPointF(option.margin, y);
624 m_text[textId].setTextWidth(maxWidth);
625
626 const QRectF textRect(option.margin + (maxWidth - requiredWidth) / 2, y, requiredWidth, fontHeight);
627 m_textRect |= textRect;
628
629 y += fontHeight;
630 }
631
632 // Add a margin to the text rectangle
633 const qreal margin = option.margin;
634 m_textRect.adjust(-margin, -margin, margin, margin);
635 }
636
637 void KFileItemListWidget::updateCompactLayoutTextCache()
638 {
639 // +------+ Name role
640 // | Icon | Additional role 1
641 // +------+ Additional role 2
642
643 const QHash<QByteArray, QVariant> values = data();
644
645 const KItemListStyleOption& option = styleOption();
646 const qreal widgetHeight = size().height();
647 const qreal fontHeight = option.fontMetrics.height();
648 const qreal textLinesHeight = qMax(visibleRoles().count(), 1) * fontHeight;
649 const int scaledIconSize = (textLinesHeight < option.iconSize) ? widgetHeight - 2 * option.margin : option.iconSize;
650
651 qreal maximumRequiredTextWidth = 0;
652 const qreal x = option.margin * 3 + scaledIconSize;
653 qreal y = (widgetHeight - textLinesHeight) / 2;
654 const qreal maxWidth = size().width() - x - option.margin;
655 foreach (const QByteArray& role, m_sortedVisibleRoles) {
656 const TextId textId = roleTextId(role);
657
658 const QString text = roleText(role, values);
659 m_text[textId].setText(text);
660
661 qreal requiredWidth = option.fontMetrics.width(text);
662 if (requiredWidth > maxWidth) {
663 requiredWidth = maxWidth;
664 const QString elidedText = option.fontMetrics.elidedText(text, Qt::ElideRight, maxWidth);
665 m_text[textId].setText(elidedText);
666 }
667
668 m_textPos[textId] = QPointF(x, y);
669 m_text[textId].setTextWidth(maxWidth);
670
671 maximumRequiredTextWidth = qMax(maximumRequiredTextWidth, requiredWidth);
672
673 y += fontHeight;
674 }
675
676 m_textRect = QRectF(x - option.margin, 0, maximumRequiredTextWidth + 2 * option.margin, widgetHeight);
677 }
678
679 void KFileItemListWidget::updateDetailsLayoutTextCache()
680 {
681 // Precondition: Requires already updated m_expansionArea
682 // to determine the left position.
683
684 // +------+
685 // | Icon | Name role Additional role 1 Additional role 2
686 // +------+
687 m_textRect = QRectF();
688
689 const KItemListStyleOption& option = styleOption();
690 const QHash<QByteArray, QVariant> values = data();
691
692 const qreal widgetHeight = size().height();
693 const int scaledIconSize = widgetHeight - 2 * option.margin;
694 const int fontHeight = option.fontMetrics.height();
695
696 const qreal columnMargin = option.margin * 3;
697 const qreal firstColumnInc = m_expansionArea.right() + option.margin * 2 + scaledIconSize;
698 qreal x = firstColumnInc;
699 const qreal y = qMax(qreal(option.margin), (widgetHeight - fontHeight) / 2);
700
701 foreach (const QByteArray& role, m_sortedVisibleRoles) {
702 const TextId textId = roleTextId(role);
703
704 QString text = roleText(role, values);
705
706 // Elide the text in case it does not fit into the available column-width
707 qreal requiredWidth = option.fontMetrics.width(text);
708 const qreal columnWidth = visibleRolesSizes().value(role, QSizeF(0, 0)).width();
709 qreal availableTextWidth = columnWidth - 2 * columnMargin;
710 if (textId == Name) {
711 availableTextWidth -= firstColumnInc;
712 }
713
714 if (requiredWidth > availableTextWidth) {
715 text = option.fontMetrics.elidedText(text, Qt::ElideRight, availableTextWidth);
716 requiredWidth = option.fontMetrics.width(text);
717 }
718
719 m_text[textId].setText(text);
720 m_textPos[textId] = QPointF(x + columnMargin, y);
721 x += columnWidth;
722
723 switch (textId) {
724 case Name: {
725 m_textRect = QRectF(m_textPos[textId].x() - option.margin, 0,
726 requiredWidth + 2 * option.margin, size().height());
727
728 // The column after the name should always be aligned on the same x-position independent
729 // from the expansion-level shown in the name column
730 x -= firstColumnInc;
731 break;
732 }
733 case Size:
734 // The values for the size should be right aligned
735 m_textPos[textId].rx() += columnWidth - requiredWidth - 2 * columnMargin;
736 break;
737
738 default:
739 break;
740 }
741 }
742 }
743
744 void KFileItemListWidget::updateAdditionalInfoTextColor()
745 {
746 QColor c1;
747 if (m_customTextColor.isValid()) {
748 c1 = m_customTextColor;
749 } else if (isSelected() && m_layout != DetailsLayout) {
750 c1 = styleOption().palette.highlightedText().color();
751 } else {
752 c1 = styleOption().palette.text().color();
753 }
754
755 // For the color of the additional info the inactive text color
756 // is not used as this might lead to unreadable text for some color schemes. Instead
757 // the text color c1 is slightly mixed with the background color.
758 const QColor c2 = styleOption().palette.base().color();
759 const int p1 = 70;
760 const int p2 = 100 - p1;
761 m_additionalInfoTextColor = QColor((c1.red() * p1 + c2.red() * p2) / 100,
762 (c1.green() * p1 + c2.green() * p2) / 100,
763 (c1.blue() * p1 + c2.blue() * p2) / 100);
764 }
765
766 void KFileItemListWidget::drawPixmap(QPainter* painter, const QPixmap& pixmap)
767 {
768 const bool isHiddenItem = m_text[Name].text().startsWith(QLatin1Char('.'));
769 qreal opacity;
770 if (isHiddenItem) {
771 opacity = painter->opacity();
772 painter->setOpacity(opacity * 0.3);
773 }
774
775 if (m_scaledPixmapSize != pixmap.size()) {
776 QPixmap scaledPixmap = pixmap;
777 KPixmapModifier::scale(scaledPixmap, m_scaledPixmapSize);
778 painter->drawPixmap(m_pixmapPos, scaledPixmap);
779
780 #ifdef KFILEITEMLISTWIDGET_DEBUG
781 painter->setPen(Qt::green);
782 painter->drawRect(QRectF(m_pixmapPos, QSizeF(scaledPixmap.size())));
783 #endif
784 } else {
785 painter->drawPixmap(m_pixmapPos, pixmap);
786 }
787
788 if (isHiddenItem) {
789 painter->setOpacity(opacity);
790 }
791 }
792
793 QPixmap KFileItemListWidget::pixmapForIcon(const QString& name, int size)
794 {
795 const KIcon icon(name);
796
797 int requestedSize;
798 if (size <= KIconLoader::SizeSmall) {
799 requestedSize = KIconLoader::SizeSmall;
800 } else if (size <= KIconLoader::SizeSmallMedium) {
801 requestedSize = KIconLoader::SizeSmallMedium;
802 } else if (size <= KIconLoader::SizeMedium) {
803 requestedSize = KIconLoader::SizeMedium;
804 } else if (size <= KIconLoader::SizeLarge) {
805 requestedSize = KIconLoader::SizeLarge;
806 } else if (size <= KIconLoader::SizeHuge) {
807 requestedSize = KIconLoader::SizeHuge;
808 } else if (size <= KIconLoader::SizeEnormous) {
809 requestedSize = KIconLoader::SizeEnormous;
810 } else if (size <= KIconLoader::SizeEnormous * 2) {
811 requestedSize = KIconLoader::SizeEnormous * 2;
812 } else {
813 requestedSize = size;
814 }
815
816 QPixmap pixmap = icon.pixmap(requestedSize, requestedSize);
817 if (requestedSize != size) {
818 KPixmapModifier::scale(pixmap, QSize(size, size));
819 }
820
821 return pixmap;
822 }
823
824 KFileItemListWidget::TextId KFileItemListWidget::roleTextId(const QByteArray& role)
825 {
826 static QHash<QByteArray, TextId> rolesHash;
827 if (rolesHash.isEmpty()) {
828 rolesHash.insert("name", Name);
829 rolesHash.insert("size", Size);
830 rolesHash.insert("date", Date);
831 rolesHash.insert("permissions", Permissions);
832 rolesHash.insert("owner", Owner);
833 rolesHash.insert("group", Group);
834 rolesHash.insert("type", Type);
835 rolesHash.insert("destination", Destination);
836 rolesHash.insert("path", Path);
837 }
838
839 return rolesHash.value(role);
840 }
841
842 #include "kfileitemlistwidget.moc"