]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemlistview.cpp
c5c4448750f55c4fdd4cefd3134b51d302539035
[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 "kfileitemlistgroupheader.h"
23 #include "kfileitemmodelrolesupdater.h"
24 #include "kfileitemlistwidget.h"
25 #include "kfileitemmodel.h"
26 #include <KLocale>
27 #include <KStringHandler>
28 #include "private/kpixmapmodifier.h"
29
30 #include <KDebug>
31 #include <KIcon>
32 #include <KTextEdit>
33
34 #include <QPainter>
35 #include <QTextLine>
36 #include <QTimer>
37
38 // #define KFILEITEMLISTVIEW_DEBUG
39
40 namespace {
41 const int ShortInterval = 50;
42 const int LongInterval = 300;
43 }
44
45 KFileItemListView::KFileItemListView(QGraphicsWidget* parent) :
46 KItemListView(parent),
47 m_itemLayout(IconsLayout),
48 m_modelRolesUpdater(0),
49 m_updateVisibleIndexRangeTimer(0),
50 m_updateIconSizeTimer(0)
51 {
52 setAcceptDrops(true);
53
54 setScrollOrientation(Qt::Vertical);
55 setWidgetCreator(new KItemListWidgetCreator<KFileItemListWidget>());
56 setGroupHeaderCreator(new KItemListGroupHeaderCreator<KFileItemListGroupHeader>());
57
58 m_updateVisibleIndexRangeTimer = new QTimer(this);
59 m_updateVisibleIndexRangeTimer->setSingleShot(true);
60 m_updateVisibleIndexRangeTimer->setInterval(ShortInterval);
61 connect(m_updateVisibleIndexRangeTimer, SIGNAL(timeout()), this, SLOT(updateVisibleIndexRange()));
62
63 m_updateIconSizeTimer = new QTimer(this);
64 m_updateIconSizeTimer->setSingleShot(true);
65 m_updateIconSizeTimer->setInterval(ShortInterval);
66 connect(m_updateIconSizeTimer, SIGNAL(timeout()), this, SLOT(updateIconSize()));
67
68 setVisibleRoles(QList<QByteArray>() << "name");
69 }
70
71 KFileItemListView::~KFileItemListView()
72 {
73 // The group headers are children of the widgets created by
74 // widgetCreator(). So it is mandatory to delete the group headers
75 // first.
76 delete groupHeaderCreator();
77 delete widgetCreator();
78
79 delete m_modelRolesUpdater;
80 m_modelRolesUpdater = 0;
81 }
82
83 void KFileItemListView::setPreviewsShown(bool show)
84 {
85 if (m_modelRolesUpdater) {
86 m_modelRolesUpdater->setPreviewsShown(show);
87 }
88 }
89
90 bool KFileItemListView::previewsShown() const
91 {
92 return m_modelRolesUpdater ? m_modelRolesUpdater->previewsShown() : false;
93 }
94
95 void KFileItemListView::setEnlargeSmallPreviews(bool enlarge)
96 {
97 if (m_modelRolesUpdater) {
98 m_modelRolesUpdater->setEnlargeSmallPreviews(enlarge);
99 }
100 }
101
102 bool KFileItemListView::enlargeSmallPreviews() const
103 {
104 return m_modelRolesUpdater ? m_modelRolesUpdater->enlargeSmallPreviews() : false;
105 }
106
107 void KFileItemListView::setItemLayout(Layout layout)
108 {
109 if (m_itemLayout != layout) {
110 const bool updateRoles = (m_itemLayout == DetailsLayout || layout == DetailsLayout);
111 m_itemLayout = layout;
112 if (updateRoles) {
113 // The details-layout requires some invisible roles that
114 // must be added to the model if the new layout is "details".
115 // If the old layout was "details" the roles will get removed.
116 applyRolesToModel();
117 }
118 updateLayoutOfVisibleItems();
119 }
120 }
121
122 KFileItemListView::Layout KFileItemListView::itemLayout() const
123 {
124 return m_itemLayout;
125 }
126
127 void KFileItemListView::setEnabledPlugins(const QStringList& list)
128 {
129 if (m_modelRolesUpdater) {
130 m_modelRolesUpdater->setEnabledPlugins(list);
131 }
132 }
133
134 QStringList KFileItemListView::enabledPlugins() const
135 {
136 return m_modelRolesUpdater ? m_modelRolesUpdater->enabledPlugins() : QStringList();
137 }
138
139 QPixmap KFileItemListView::createDragPixmap(const QSet<int>& indexes) const
140 {
141 if (!model()) {
142 return QPixmap();
143 }
144
145 const int itemCount = indexes.count();
146 Q_ASSERT(itemCount > 0);
147
148 // If more than one item is dragged, align the items inside a
149 // rectangular grid. The maximum grid size is limited to 5 x 5 items.
150 int xCount;
151 int size;
152 if (itemCount > 16) {
153 xCount = 5;
154 size = KIconLoader::SizeSmall;
155 } else if (itemCount > 9) {
156 xCount = 4;
157 size = KIconLoader::SizeSmallMedium;
158 } else {
159 xCount = 3;
160 size = KIconLoader::SizeMedium;
161 }
162
163 if (itemCount < xCount) {
164 xCount = itemCount;
165 }
166
167 int yCount = itemCount / xCount;
168 if (itemCount % xCount != 0) {
169 ++yCount;
170 }
171 if (yCount > xCount) {
172 yCount = xCount;
173 }
174
175 // Draw the selected items into the grid cells.
176 QPixmap dragPixmap(xCount * size + xCount, yCount * size + yCount);
177 dragPixmap.fill(Qt::transparent);
178
179 QPainter painter(&dragPixmap);
180 int x = 0;
181 int y = 0;
182 QSetIterator<int> it(indexes);
183 while (it.hasNext()) {
184 const int index = it.next();
185
186 QPixmap pixmap = model()->data(index).value("iconPixmap").value<QPixmap>();
187 if (pixmap.isNull()) {
188 KIcon icon(model()->data(index).value("iconName").toString());
189 pixmap = icon.pixmap(size, size);
190 } else {
191 KPixmapModifier::scale(pixmap, QSize(size, size));
192 }
193
194 painter.drawPixmap(x, y, pixmap);
195
196 x += size + 1;
197 if (x >= dragPixmap.width()) {
198 x = 0;
199 y += size + 1;
200 }
201
202 if (y >= dragPixmap.height()) {
203 break;
204 }
205 }
206
207 return dragPixmap;
208 }
209
210 void KFileItemListView::initializeItemListWidget(KItemListWidget* item)
211 {
212 KFileItemListWidget* fileItemListWidget = static_cast<KFileItemListWidget*>(item);
213
214 switch (m_itemLayout) {
215 case IconsLayout: fileItemListWidget->setLayout(KFileItemListWidget::IconsLayout); break;
216 case CompactLayout: fileItemListWidget->setLayout(KFileItemListWidget::CompactLayout); break;
217 case DetailsLayout: fileItemListWidget->setLayout(KFileItemListWidget::DetailsLayout); break;
218 default: Q_ASSERT(false); break;
219 }
220
221 fileItemListWidget->setSupportsItemExpanding(supportsItemExpanding());
222 }
223
224 bool KFileItemListView::itemSizeHintUpdateRequired(const QSet<QByteArray>& changedRoles) const
225 {
226 // Even if the icons have a different size they are always aligned within
227 // the area defined by KItemStyleOption.iconSize and hence result in no
228 // change of the item-size.
229 const bool containsIconName = changedRoles.contains("iconName");
230 const bool containsIconPixmap = changedRoles.contains("iconPixmap");
231 const int count = changedRoles.count();
232
233 const bool iconChanged = (containsIconName && containsIconPixmap && count == 2) ||
234 (containsIconName && count == 1) ||
235 (containsIconPixmap && count == 1);
236 return !iconChanged;
237 }
238
239 void KFileItemListView::onModelChanged(KItemModelBase* current, KItemModelBase* previous)
240 {
241 Q_UNUSED(previous);
242 Q_ASSERT(qobject_cast<KFileItemModel*>(current));
243
244 delete m_modelRolesUpdater;
245 m_modelRolesUpdater = new KFileItemModelRolesUpdater(static_cast<KFileItemModel*>(current), this);
246 m_modelRolesUpdater->setIconSize(availableIconSize());
247
248 applyRolesToModel();
249 }
250
251 void KFileItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous)
252 {
253 Q_UNUSED(current);
254 Q_UNUSED(previous);
255 updateLayoutOfVisibleItems();
256 }
257
258 void KFileItemListView::onItemSizeChanged(const QSizeF& current, const QSizeF& previous)
259 {
260 Q_UNUSED(current);
261 Q_UNUSED(previous);
262 triggerVisibleIndexRangeUpdate();
263 }
264
265 void KFileItemListView::onScrollOffsetChanged(qreal current, qreal previous)
266 {
267 Q_UNUSED(current);
268 Q_UNUSED(previous);
269 triggerVisibleIndexRangeUpdate();
270 }
271
272 void KFileItemListView::onVisibleRolesChanged(const QList<QByteArray>& current, const QList<QByteArray>& previous)
273 {
274 Q_UNUSED(current);
275 Q_UNUSED(previous);
276 applyRolesToModel();
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::onSupportsItemExpandingChanged(bool supportsExpanding)
287 {
288 Q_UNUSED(supportsExpanding);
289 applyRolesToModel();
290 updateLayoutOfVisibleItems();
291 }
292
293 void KFileItemListView::onTransactionBegin()
294 {
295 m_modelRolesUpdater->setPaused(true);
296 }
297
298 void KFileItemListView::onTransactionEnd()
299 {
300 // Only unpause the model-roles-updater if no timer is active. If one
301 // timer is still active the model-roles-updater will be unpaused later as
302 // soon as the timer has been exceeded.
303 const bool timerActive = m_updateVisibleIndexRangeTimer->isActive() ||
304 m_updateIconSizeTimer->isActive();
305 if (!timerActive) {
306 m_modelRolesUpdater->setPaused(false);
307 }
308 }
309
310 void KFileItemListView::resizeEvent(QGraphicsSceneResizeEvent* event)
311 {
312 KItemListView::resizeEvent(event);
313 triggerVisibleIndexRangeUpdate();
314 }
315
316 void KFileItemListView::slotItemsRemoved(const KItemRangeList& itemRanges)
317 {
318 KItemListView::slotItemsRemoved(itemRanges);
319 updateTimersInterval();
320 }
321
322 void KFileItemListView::slotSortRoleChanged(const QByteArray& current, const QByteArray& previous)
323 {
324 const QByteArray sortRole = model()->sortRole();
325 if (!visibleRoles().contains(sortRole)) {
326 applyRolesToModel();
327 }
328
329 KItemListView::slotSortRoleChanged(current, previous);
330 }
331
332 void KFileItemListView::triggerVisibleIndexRangeUpdate()
333 {
334 if (!model()) {
335 return;
336 }
337 m_modelRolesUpdater->setPaused(true);
338 m_updateVisibleIndexRangeTimer->start();
339 }
340
341 void KFileItemListView::updateVisibleIndexRange()
342 {
343 if (!m_modelRolesUpdater) {
344 return;
345 }
346
347 const int index = firstVisibleIndex();
348 const int count = lastVisibleIndex() - index + 1;
349 m_modelRolesUpdater->setVisibleIndexRange(index, count);
350
351 if (m_updateIconSizeTimer->isActive()) {
352 // If the icon-size update is pending do an immediate update
353 // of the icon-size before unpausing m_modelRolesUpdater. This prevents
354 // an unnecessary expensive recreation of all previews afterwards.
355 m_updateIconSizeTimer->stop();
356 m_modelRolesUpdater->setIconSize(availableIconSize());
357 }
358
359 m_modelRolesUpdater->setPaused(isTransactionActive());
360 updateTimersInterval();
361 }
362
363 void KFileItemListView::triggerIconSizeUpdate()
364 {
365 if (!model()) {
366 return;
367 }
368 m_modelRolesUpdater->setPaused(true);
369 m_updateIconSizeTimer->start();
370 }
371
372 void KFileItemListView::updateIconSize()
373 {
374 if (!m_modelRolesUpdater) {
375 return;
376 }
377
378 m_modelRolesUpdater->setIconSize(availableIconSize());
379
380 if (m_updateVisibleIndexRangeTimer->isActive()) {
381 // If the visibility-index-range update is pending do an immediate update
382 // of the range before unpausing m_modelRolesUpdater. This prevents
383 // an unnecessary expensive recreation of all previews afterwards.
384 m_updateVisibleIndexRangeTimer->stop();
385 const int index = firstVisibleIndex();
386 const int count = lastVisibleIndex() - index + 1;
387 m_modelRolesUpdater->setVisibleIndexRange(index, count);
388 }
389
390 m_modelRolesUpdater->setPaused(isTransactionActive());
391 updateTimersInterval();
392 }
393
394 void KFileItemListView::updateLayoutOfVisibleItems()
395 {
396 if (!model()) {
397 return;
398 }
399
400 foreach (KItemListWidget* widget, visibleItemListWidgets()) {
401 initializeItemListWidget(widget);
402 }
403 triggerVisibleIndexRangeUpdate();
404 }
405
406 void KFileItemListView::updateTimersInterval()
407 {
408 if (!model()) {
409 return;
410 }
411
412 // The ShortInterval is used for cases like switching the directory: If the
413 // model is empty and filled later the creation of the previews should be done
414 // as soon as possible. The LongInterval is used when the model already contains
415 // items and assures that operations like zooming don't result in too many temporary
416 // recreations of the previews.
417
418 const int interval = (model()->count() <= 0) ? ShortInterval : LongInterval;
419 m_updateVisibleIndexRangeTimer->setInterval(interval);
420 m_updateIconSizeTimer->setInterval(interval);
421 }
422
423 void KFileItemListView::applyRolesToModel()
424 {
425 if (!model()) {
426 return;
427 }
428
429 Q_ASSERT(qobject_cast<KFileItemModel*>(model()));
430 KFileItemModel* fileItemModel = static_cast<KFileItemModel*>(model());
431
432 // KFileItemModel does not distinct between "visible" and "invisible" roles.
433 // Add all roles that are mandatory for having a working KFileItemListView:
434 QSet<QByteArray> roles = visibleRoles().toSet();
435 roles.insert("iconPixmap");
436 roles.insert("iconName");
437 roles.insert("name");
438 roles.insert("isDir");
439 if (supportsItemExpanding()) {
440 roles.insert("isExpanded");
441 roles.insert("isExpandable");
442 roles.insert("expandedParentsCount");
443 }
444
445 // Assure that the role that is used for sorting will be determined
446 roles.insert(fileItemModel->sortRole());
447
448 fileItemModel->setRoles(roles);
449 m_modelRolesUpdater->setRoles(roles);
450 }
451
452 QSize KFileItemListView::availableIconSize() const
453 {
454 const KItemListStyleOption& option = styleOption();
455 const int iconSize = option.iconSize;
456 if (m_itemLayout == IconsLayout) {
457 const int maxIconWidth = itemSize().width() - 2 * option.padding;
458 return QSize(maxIconWidth, iconSize);
459 }
460
461 return QSize(iconSize, iconSize);
462 }
463
464 #include "kfileitemlistview.moc"