1 /***************************************************************************
2 * Copyright (C) 2011 by Peter Penz <peter.penz19@gmail.com> *
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. *
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. *
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 ***************************************************************************/
20 #include "kfileitemlistview.h"
22 #include "kitemlistgroupheader.h"
23 #include "kfileitemmodelrolesupdater.h"
24 #include "kfileitemlistwidget.h"
25 #include "kfileitemmodel.h"
27 #include <KStringHandler>
34 #define KFILEITEMLISTVIEW_DEBUG
37 const int ShortInterval
= 50;
38 const int LongInterval
= 300;
41 KFileItemListView::KFileItemListView(QGraphicsWidget
* parent
) :
42 KItemListView(parent
),
43 m_itemLayout(IconsLayout
),
44 m_modelRolesUpdater(0),
45 m_updateVisibleIndexRangeTimer(0),
46 m_updateIconSizeTimer(0),
47 m_minimumRolesWidths()
49 setScrollOrientation(Qt::Vertical
);
50 setWidgetCreator(new KItemListWidgetCreator
<KFileItemListWidget
>());
51 setGroupHeaderCreator(new KItemListGroupHeaderCreator
<KItemListGroupHeader
>());
53 m_updateVisibleIndexRangeTimer
= new QTimer(this);
54 m_updateVisibleIndexRangeTimer
->setSingleShot(true);
55 m_updateVisibleIndexRangeTimer
->setInterval(ShortInterval
);
56 connect(m_updateVisibleIndexRangeTimer
, SIGNAL(timeout()), this, SLOT(updateVisibleIndexRange()));
58 m_updateIconSizeTimer
= new QTimer(this);
59 m_updateIconSizeTimer
->setSingleShot(true);
60 m_updateIconSizeTimer
->setInterval(ShortInterval
);
61 connect(m_updateIconSizeTimer
, SIGNAL(timeout()), this, SLOT(updateIconSize()));
63 updateMinimumRolesWidths();
66 KFileItemListView::~KFileItemListView()
68 delete widgetCreator();
69 delete groupHeaderCreator();
71 delete m_modelRolesUpdater
;
72 m_modelRolesUpdater
= 0;
75 void KFileItemListView::setPreviewsShown(bool show
)
77 if (m_modelRolesUpdater
) {
78 m_modelRolesUpdater
->setPreviewShown(show
);
82 bool KFileItemListView::previewsShown() const
84 return m_modelRolesUpdater
->isPreviewShown();
87 void KFileItemListView::setItemLayout(Layout layout
)
89 if (m_itemLayout
!= layout
) {
90 m_itemLayout
= layout
;
91 updateLayoutOfVisibleItems();
95 KFileItemListView::Layout
KFileItemListView::itemLayout() const
100 QSizeF
KFileItemListView::itemSizeHint(int index
) const
102 const QHash
<QByteArray
, QVariant
> values
= model()->data(index
);
103 const KItemListStyleOption
& option
= styleOption();
104 const int additionalRolesCount
= qMax(visibleRoles().count() - 1, 0);
106 switch (m_itemLayout
) {
108 const QString text
= KStringHandler::preProcessWrap(values
["name"].toString());
110 const qreal maxWidth
= itemSize().width() - 2 * option
.margin
;
111 int textLinesCount
= 0;
114 // Calculate the number of lines required for wrapping the name
115 QTextOption
textOption(Qt::AlignHCenter
);
116 textOption
.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere
);
118 QTextLayout
layout(text
, option
.font
);
119 layout
.setTextOption(textOption
);
120 layout
.beginLayout();
121 while ((line
= layout
.createLine()).isValid()) {
122 line
.setLineWidth(maxWidth
);
123 line
.naturalTextWidth();
128 // Add one line for each additional information
129 textLinesCount
+= additionalRolesCount
;
131 const qreal height
= textLinesCount
* option
.fontMetrics
.height() +
134 return QSizeF(itemSize().width(), height
);
137 case CompactLayout
: {
138 // For each row exactly one role is shown. Calculate the maximum required width that is necessary
139 // to show all roles without horizontal clipping.
140 qreal maximumRequiredWidth
= 0.0;
141 QHashIterator
<QByteArray
, int> it(visibleRoles());
142 while (it
.hasNext()) {
144 const QByteArray
& role
= it
.key();
145 const QString text
= values
[role
].toString();
146 const qreal requiredWidth
= option
.fontMetrics
.width(text
);
147 maximumRequiredWidth
= qMax(maximumRequiredWidth
, requiredWidth
);
150 const qreal width
= option
.margin
* 4 + option
.iconSize
+ maximumRequiredWidth
;
151 const qreal height
= option
.margin
* 2 + qMax(option
.iconSize
, (1 + additionalRolesCount
) * option
.fontMetrics
.height());
152 return QSizeF(width
, height
);
155 case DetailsLayout
: {
156 // The width will be determined dynamically by KFileItemListView::visibleRoleSizes()
157 const qreal height
= option
.margin
* 2 + qMax(option
.iconSize
, option
.fontMetrics
.height());
158 return QSizeF(-1, height
);
169 QHash
<QByteArray
, QSizeF
> KFileItemListView::visibleRoleSizes() const
174 QHash
<QByteArray
, QSizeF
> sizes
;
176 const int itemCount
= model()->count();
177 for (int i
= 0; i
< itemCount
; ++i
) {
178 QHashIterator
<QByteArray
, int> it(visibleRoles());
179 while (it
.hasNext()) {
181 const QByteArray
& visibleRole
= it
.key();
183 QSizeF maxSize
= sizes
.value(visibleRole
, QSizeF(0, 0));
185 const QSizeF itemSize
= visibleRoleSizeHint(i
, visibleRole
);
186 maxSize
= maxSize
.expandedTo(itemSize
);
187 sizes
.insert(visibleRole
, maxSize
);
190 if (i
> 100 && timer
.elapsed() > 200) {
191 // When having several thousands of items calculating the sizes can get
192 // very expensive. We accept a possibly too small role-size in favour
193 // of having no blocking user interface.
194 #ifdef KFILEITEMLISTVIEW_DEBUG
195 kDebug() << "Timer exceeded, stopped after" << i
<< "items";
201 #ifdef KFILEITEMLISTVIEW_DEBUG
202 kDebug() << "[TIME] Calculated dynamic item size for " << itemCount
<< "items:" << timer
.elapsed();
207 void KFileItemListView::initializeItemListWidget(KItemListWidget
* item
)
209 KFileItemListWidget
* fileItemListWidget
= static_cast<KFileItemListWidget
*>(item
);
211 switch (m_itemLayout
) {
212 case IconsLayout
: fileItemListWidget
->setLayout(KFileItemListWidget::IconsLayout
); break;
213 case CompactLayout
: fileItemListWidget
->setLayout(KFileItemListWidget::CompactLayout
); break;
214 case DetailsLayout
: fileItemListWidget
->setLayout(KFileItemListWidget::DetailsLayout
); break;
215 default: Q_ASSERT(false); break;
219 void KFileItemListView::onModelChanged(KItemModelBase
* current
, KItemModelBase
* previous
)
222 Q_ASSERT(qobject_cast
<KFileItemModel
*>(current
));
224 if (m_modelRolesUpdater
) {
225 delete m_modelRolesUpdater
;
228 m_modelRolesUpdater
= new KFileItemModelRolesUpdater(static_cast<KFileItemModel
*>(current
), this);
229 const int size
= styleOption().iconSize
;
230 m_modelRolesUpdater
->setIconSize(QSize(size
, size
));
233 void KFileItemListView::onScrollOrientationChanged(Qt::Orientation current
, Qt::Orientation previous
)
237 updateLayoutOfVisibleItems();
240 void KFileItemListView::onItemSizeChanged(const QSizeF
& current
, const QSizeF
& previous
)
244 triggerVisibleIndexRangeUpdate();
247 void KFileItemListView::onOffsetChanged(qreal current
, qreal previous
)
251 triggerVisibleIndexRangeUpdate();
254 void KFileItemListView::onVisibleRolesChanged(const QHash
<QByteArray
, int>& current
, const QHash
<QByteArray
, int>& previous
)
258 Q_ASSERT(qobject_cast
<KFileItemModel
*>(model()));
259 KFileItemModel
* fileItemModel
= static_cast<KFileItemModel
*>(model());
261 // KFileItemModel does not distinct between "visible" and "invisible" roles.
262 // Add all roles that are mandatory for having a working KFileItemListView:
263 QSet
<QByteArray
> keys
= current
.keys().toSet();
264 QSet
<QByteArray
> roles
= keys
;
265 roles
.insert("iconPixmap");
266 roles
.insert("iconName");
267 roles
.insert("name"); // TODO: just don't allow to disable it
268 roles
.insert("isDir");
269 if (m_itemLayout
== DetailsLayout
) {
270 roles
.insert("isExpanded");
271 roles
.insert("expansionLevel");
274 fileItemModel
->setRoles(roles
);
276 m_modelRolesUpdater
->setRoles(keys
);
279 void KFileItemListView::onStyleOptionChanged(const KItemListStyleOption
& current
, const KItemListStyleOption
& previous
)
283 triggerIconSizeUpdate();
286 void KFileItemListView::onTransactionBegin()
288 m_modelRolesUpdater
->setPaused(true);
291 void KFileItemListView::onTransactionEnd()
293 // Only unpause the model-roles-updater if no timer is active. If one
294 // timer is still active the model-roles-updater will be unpaused later as
295 // soon as the timer has been exceeded.
296 const bool timerActive
= m_updateVisibleIndexRangeTimer
->isActive() ||
297 m_updateIconSizeTimer
->isActive();
299 m_modelRolesUpdater
->setPaused(false);
303 void KFileItemListView::resizeEvent(QGraphicsSceneResizeEvent
* event
)
305 KItemListView::resizeEvent(event
);
306 triggerVisibleIndexRangeUpdate();
309 void KFileItemListView::slotItemsRemoved(const KItemRangeList
& itemRanges
)
311 KItemListView::slotItemsRemoved(itemRanges
);
312 updateTimersInterval();
315 void KFileItemListView::triggerVisibleIndexRangeUpdate()
317 m_modelRolesUpdater
->setPaused(true);
318 m_updateVisibleIndexRangeTimer
->start();
321 void KFileItemListView::updateVisibleIndexRange()
323 if (!m_modelRolesUpdater
) {
327 const int index
= firstVisibleIndex();
328 const int count
= lastVisibleIndex() - index
+ 1;
329 m_modelRolesUpdater
->setVisibleIndexRange(index
, count
);
331 if (m_updateIconSizeTimer
->isActive()) {
332 // If the icon-size update is pending do an immediate update
333 // of the icon-size before unpausing m_modelRolesUpdater. This prevents
334 // an unnecessary expensive recreation of all previews afterwards.
335 m_updateIconSizeTimer
->stop();
336 const KItemListStyleOption
& option
= styleOption();
337 m_modelRolesUpdater
->setIconSize(QSize(option
.iconSize
, option
.iconSize
));
340 m_modelRolesUpdater
->setPaused(isTransactionActive());
341 updateTimersInterval();
344 void KFileItemListView::triggerIconSizeUpdate()
346 m_modelRolesUpdater
->setPaused(true);
347 m_updateIconSizeTimer
->start();
350 void KFileItemListView::updateIconSize()
352 if (!m_modelRolesUpdater
) {
356 const KItemListStyleOption
& option
= styleOption();
357 m_modelRolesUpdater
->setIconSize(QSize(option
.iconSize
, option
.iconSize
));
359 if (m_updateVisibleIndexRangeTimer
->isActive()) {
360 // If the visibility-index-range update is pending do an immediate update
361 // of the range before unpausing m_modelRolesUpdater. This prevents
362 // an unnecessary expensive recreation of all previews afterwards.
363 m_updateVisibleIndexRangeTimer
->stop();
364 const int index
= firstVisibleIndex();
365 const int count
= lastVisibleIndex() - index
+ 1;
366 m_modelRolesUpdater
->setVisibleIndexRange(index
, count
);
369 m_modelRolesUpdater
->setPaused(isTransactionActive());
370 updateTimersInterval();
373 QSizeF
KFileItemListView::visibleRoleSizeHint(int index
, const QByteArray
& role
) const
375 const KItemListStyleOption
& option
= styleOption();
377 qreal width
= m_minimumRolesWidths
.value(role
, 0);
378 const qreal height
= option
.margin
* 2 + option
.fontMetrics
.height();
380 const QVariant value
= model()->data(index
).value(role
);
381 const QString text
= value
.toString();
382 if (!text
.isEmpty()) {
383 width
= qMax(width
, qreal(option
.margin
* 2 + option
.fontMetrics
.width(text
)));
386 return QSizeF(width
, height
);
389 void KFileItemListView::updateLayoutOfVisibleItems()
391 foreach (KItemListWidget
* widget
, visibleItemListWidgets()) {
392 initializeItemListWidget(widget
);
394 triggerVisibleIndexRangeUpdate();
397 void KFileItemListView::updateTimersInterval()
403 // The ShortInterval is used for cases like switching the directory: If the
404 // model is empty and filled later the creation of the previews should be done
405 // as soon as possible. The LongInterval is used when the model already contains
406 // items and assures that operations like zooming don't result in too many temporary
407 // recreations of the previews.
409 const int interval
= (model()->count() <= 0) ? ShortInterval
: LongInterval
;
410 m_updateVisibleIndexRangeTimer
->setInterval(interval
);
411 m_updateIconSizeTimer
->setInterval(interval
);
414 void KFileItemListView::updateMinimumRolesWidths()
416 m_minimumRolesWidths
.clear();
418 const KItemListStyleOption
& option
= styleOption();
419 const QString sizeText
= QLatin1String("888888") + i18nc("@item:intable", "items");
420 m_minimumRolesWidths
.insert("size", option
.fontMetrics
.width(sizeText
));
423 #include "kfileitemlistview.moc"