]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemlistview.cpp
Provide basic rubberband functionality
[dolphin.git] / src / kitemviews / kfileitemlistview.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 "kfileitemlistview.h"
21
22 #include "kitemlistgroupheader.h"
23 #include "kfileitemmodelrolesupdater.h"
24 #include "kfileitemlistwidget.h"
25 #include "kfileitemmodel.h"
26 #include <KLocale>
27 #include <KStringHandler>
28
29 #include <KDebug>
30
31 #include <QTextLine>
32 #include <QTimer>
33
34 #define KFILEITEMLISTVIEW_DEBUG
35
36 namespace {
37 const int ShortInterval = 50;
38 const int LongInterval = 300;
39 }
40
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()
48 {
49 setScrollOrientation(Qt::Vertical);
50 setWidgetCreator(new KItemListWidgetCreator<KFileItemListWidget>());
51 setGroupHeaderCreator(new KItemListGroupHeaderCreator<KItemListGroupHeader>());
52
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()));
57
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()));
62
63 updateMinimumRolesWidths();
64 }
65
66 KFileItemListView::~KFileItemListView()
67 {
68 delete widgetCreator();
69 delete groupHeaderCreator();
70
71 delete m_modelRolesUpdater;
72 m_modelRolesUpdater = 0;
73 }
74
75 void KFileItemListView::setPreviewsShown(bool show)
76 {
77 if (m_modelRolesUpdater) {
78 m_modelRolesUpdater->setPreviewShown(show);
79 }
80 }
81
82 bool KFileItemListView::previewsShown() const
83 {
84 return m_modelRolesUpdater->isPreviewShown();
85 }
86
87 void KFileItemListView::setItemLayout(Layout layout)
88 {
89 if (m_itemLayout != layout) {
90 m_itemLayout = layout;
91 updateLayoutOfVisibleItems();
92 }
93 }
94
95 KFileItemListView::Layout KFileItemListView::itemLayout() const
96 {
97 return m_itemLayout;
98 }
99
100 QSizeF KFileItemListView::itemSizeHint(int index) const
101 {
102 const QHash<QByteArray, QVariant> values = model()->data(index);
103 const KItemListStyleOption& option = styleOption();
104 const int additionalRolesCount = qMax(visibleRoles().count() - 1, 0);
105
106 switch (m_itemLayout) {
107 case IconsLayout: {
108 const QString text = KStringHandler::preProcessWrap(values["name"].toString());
109
110 const qreal maxWidth = itemSize().width() - 2 * option.margin;
111 int textLinesCount = 0;
112 QTextLine line;
113
114 // Calculate the number of lines required for wrapping the name
115 QTextOption textOption(Qt::AlignHCenter);
116 textOption.setWrapMode(QTextOption::WrapAtWordBoundaryOrAnywhere);
117
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();
124 ++textLinesCount;
125 }
126 layout.endLayout();
127
128 // Add one line for each additional information
129 textLinesCount += additionalRolesCount;
130
131 const qreal height = textLinesCount * option.fontMetrics.height() +
132 option.iconSize +
133 option.margin * 4;
134 return QSizeF(itemSize().width(), height);
135 }
136
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()) {
143 it.next();
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);
148 }
149
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);
153 }
154
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);
159 }
160
161 default:
162 Q_ASSERT(false);
163 break;
164 }
165
166 return QSize();
167 }
168
169 QHash<QByteArray, QSizeF> KFileItemListView::visibleRoleSizes() const
170 {
171 QElapsedTimer timer;
172 timer.start();
173
174 QHash<QByteArray, QSizeF> sizes;
175
176 const int itemCount = model()->count();
177 for (int i = 0; i < itemCount; ++i) {
178 QHashIterator<QByteArray, int> it(visibleRoles());
179 while (it.hasNext()) {
180 it.next();
181 const QByteArray& visibleRole = it.key();
182
183 QSizeF maxSize = sizes.value(visibleRole, QSizeF(0, 0));
184
185 const QSizeF itemSize = visibleRoleSizeHint(i, visibleRole);
186 maxSize = maxSize.expandedTo(itemSize);
187 sizes.insert(visibleRole, maxSize);
188 }
189
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";
196 #endif
197 break;
198 }
199 }
200
201 #ifdef KFILEITEMLISTVIEW_DEBUG
202 kDebug() << "[TIME] Calculated dynamic item size for " << itemCount << "items:" << timer.elapsed();
203 #endif
204 return sizes;
205 }
206
207 void KFileItemListView::initializeItemListWidget(KItemListWidget* item)
208 {
209 KFileItemListWidget* fileItemListWidget = static_cast<KFileItemListWidget*>(item);
210
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;
216 }
217 }
218
219 void KFileItemListView::onModelChanged(KItemModelBase* current, KItemModelBase* previous)
220 {
221 Q_UNUSED(previous);
222 Q_ASSERT(qobject_cast<KFileItemModel*>(current));
223
224 if (m_modelRolesUpdater) {
225 delete m_modelRolesUpdater;
226 }
227
228 m_modelRolesUpdater = new KFileItemModelRolesUpdater(static_cast<KFileItemModel*>(current), this);
229 const int size = styleOption().iconSize;
230 m_modelRolesUpdater->setIconSize(QSize(size, size));
231 }
232
233 void KFileItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous)
234 {
235 Q_UNUSED(current);
236 Q_UNUSED(previous);
237 updateLayoutOfVisibleItems();
238 }
239
240 void KFileItemListView::onItemSizeChanged(const QSizeF& current, const QSizeF& previous)
241 {
242 Q_UNUSED(current);
243 Q_UNUSED(previous);
244 triggerVisibleIndexRangeUpdate();
245 }
246
247 void KFileItemListView::onOffsetChanged(qreal current, qreal previous)
248 {
249 Q_UNUSED(current);
250 Q_UNUSED(previous);
251 triggerVisibleIndexRangeUpdate();
252 }
253
254 void KFileItemListView::onVisibleRolesChanged(const QHash<QByteArray, int>& current, const QHash<QByteArray, int>& previous)
255 {
256 Q_UNUSED(previous);
257
258 Q_ASSERT(qobject_cast<KFileItemModel*>(model()));
259 KFileItemModel* fileItemModel = static_cast<KFileItemModel*>(model());
260
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");
272 }
273
274 fileItemModel->setRoles(roles);
275
276 m_modelRolesUpdater->setRoles(keys);
277 }
278
279 void KFileItemListView::onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous)
280 {
281 Q_UNUSED(current);
282 Q_UNUSED(previous);
283 triggerIconSizeUpdate();
284 }
285
286 void KFileItemListView::onTransactionBegin()
287 {
288 m_modelRolesUpdater->setPaused(true);
289 }
290
291 void KFileItemListView::onTransactionEnd()
292 {
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();
298 if (!timerActive) {
299 m_modelRolesUpdater->setPaused(false);
300 }
301 }
302
303 void KFileItemListView::resizeEvent(QGraphicsSceneResizeEvent* event)
304 {
305 KItemListView::resizeEvent(event);
306 triggerVisibleIndexRangeUpdate();
307 }
308
309 void KFileItemListView::slotItemsRemoved(const KItemRangeList& itemRanges)
310 {
311 KItemListView::slotItemsRemoved(itemRanges);
312 updateTimersInterval();
313 }
314
315 void KFileItemListView::triggerVisibleIndexRangeUpdate()
316 {
317 m_modelRolesUpdater->setPaused(true);
318 m_updateVisibleIndexRangeTimer->start();
319 }
320
321 void KFileItemListView::updateVisibleIndexRange()
322 {
323 if (!m_modelRolesUpdater) {
324 return;
325 }
326
327 const int index = firstVisibleIndex();
328 const int count = lastVisibleIndex() - index + 1;
329 m_modelRolesUpdater->setVisibleIndexRange(index, count);
330
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));
338 }
339
340 m_modelRolesUpdater->setPaused(isTransactionActive());
341 updateTimersInterval();
342 }
343
344 void KFileItemListView::triggerIconSizeUpdate()
345 {
346 m_modelRolesUpdater->setPaused(true);
347 m_updateIconSizeTimer->start();
348 }
349
350 void KFileItemListView::updateIconSize()
351 {
352 if (!m_modelRolesUpdater) {
353 return;
354 }
355
356 const KItemListStyleOption& option = styleOption();
357 m_modelRolesUpdater->setIconSize(QSize(option.iconSize, option.iconSize));
358
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);
367 }
368
369 m_modelRolesUpdater->setPaused(isTransactionActive());
370 updateTimersInterval();
371 }
372
373 QSizeF KFileItemListView::visibleRoleSizeHint(int index, const QByteArray& role) const
374 {
375 const KItemListStyleOption& option = styleOption();
376
377 qreal width = m_minimumRolesWidths.value(role, 0);
378 const qreal height = option.margin * 2 + option.fontMetrics.height();
379
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)));
384 }
385
386 return QSizeF(width, height);
387 }
388
389 void KFileItemListView::updateLayoutOfVisibleItems()
390 {
391 foreach (KItemListWidget* widget, visibleItemListWidgets()) {
392 initializeItemListWidget(widget);
393 }
394 triggerVisibleIndexRangeUpdate();
395 }
396
397 void KFileItemListView::updateTimersInterval()
398 {
399 if (!model()) {
400 return;
401 }
402
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.
408
409 const int interval = (model()->count() <= 0) ? ShortInterval : LongInterval;
410 m_updateVisibleIndexRangeTimer->setInterval(interval);
411 m_updateIconSizeTimer->setInterval(interval);
412 }
413
414 void KFileItemListView::updateMinimumRolesWidths()
415 {
416 m_minimumRolesWidths.clear();
417
418 const KItemListStyleOption& option = styleOption();
419 const QString sizeText = QLatin1String("888888") + i18nc("@item:intable", "items");
420 m_minimumRolesWidths.insert("size", option.fontMetrics.width(sizeText));
421 }
422
423 #include "kfileitemlistview.moc"