]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemlistview.cpp
e2a27a5ea018bb0ec4d834ab2cd6da357caa9383
[dolphin.git] / src / kitemviews / kfileitemlistview.cpp
1 /*
2 * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com>
3 *
4 * SPDX-License-Identifier: GPL-2.0-or-later
5 */
6
7 #include "kfileitemlistview.h"
8
9 #include "kfileitemlistwidget.h"
10 #include "kfileitemmodel.h"
11 #include "kfileitemmodelrolesupdater.h"
12 #include "private/kpixmapmodifier.h"
13
14 #include <KIconLoader>
15
16 #include <QGraphicsScene>
17 #include <QGraphicsView>
18 #include <QIcon>
19 #include <QMimeDatabase>
20 #include <QPainter>
21 #include <QTimer>
22
23 // #define KFILEITEMLISTVIEW_DEBUG
24
25 namespace
26 {
27 // If the visible index range changes, KFileItemModelRolesUpdater is not
28 // informed immediately, but with a short delay. This ensures that scrolling
29 // always feels smooth and is not interrupted by icon loading (which can be
30 // quite expensive if a disk access is required to determine the final icon).
31 const int ShortInterval = 50;
32
33 // If the icon size changes, a longer delay is used. This prevents that
34 // the expensive re-generation of all previews is triggered repeatedly when
35 // changing the zoom level.
36 const int LongInterval = 300;
37 }
38
39 KFileItemListView::KFileItemListView(QGraphicsWidget *parent)
40 : KStandardItemListView(parent)
41 , m_modelRolesUpdater(nullptr)
42 , m_updateVisibleIndexRangeTimer(nullptr)
43 , m_updateIconSizeTimer(nullptr)
44 , m_scanDirectories(true)
45 {
46 setAcceptDrops(true);
47
48 setScrollOrientation(Qt::Vertical);
49
50 m_updateVisibleIndexRangeTimer = new QTimer(this);
51 m_updateVisibleIndexRangeTimer->setSingleShot(true);
52 m_updateVisibleIndexRangeTimer->setInterval(ShortInterval);
53 connect(m_updateVisibleIndexRangeTimer, &QTimer::timeout, this, &KFileItemListView::updateVisibleIndexRange);
54
55 m_updateIconSizeTimer = new QTimer(this);
56 m_updateIconSizeTimer->setSingleShot(true);
57 m_updateIconSizeTimer->setInterval(LongInterval);
58 connect(m_updateIconSizeTimer, &QTimer::timeout, this, &KFileItemListView::updateIconSize);
59
60 setVisibleRoles({"text"});
61 }
62
63 KFileItemListView::~KFileItemListView()
64 {
65 }
66
67 void KFileItemListView::setPreviewsShown(bool show)
68 {
69 if (!m_modelRolesUpdater) {
70 return;
71 }
72
73 if (m_modelRolesUpdater->previewsShown() != show) {
74 beginTransaction();
75 m_modelRolesUpdater->setPreviewsShown(show);
76 onPreviewsShownChanged(show);
77 endTransaction();
78 }
79 }
80
81 bool KFileItemListView::previewsShown() const
82 {
83 return m_modelRolesUpdater ? m_modelRolesUpdater->previewsShown() : false;
84 }
85
86 void KFileItemListView::setEnlargeSmallPreviews(bool enlarge)
87 {
88 if (m_modelRolesUpdater) {
89 m_modelRolesUpdater->setEnlargeSmallPreviews(enlarge);
90 }
91 }
92
93 bool KFileItemListView::enlargeSmallPreviews() const
94 {
95 return m_modelRolesUpdater ? m_modelRolesUpdater->enlargeSmallPreviews() : false;
96 }
97
98 void KFileItemListView::setEnabledPlugins(const QStringList &list)
99 {
100 if (m_modelRolesUpdater) {
101 m_modelRolesUpdater->setEnabledPlugins(list);
102 }
103 }
104
105 QStringList KFileItemListView::enabledPlugins() const
106 {
107 return m_modelRolesUpdater ? m_modelRolesUpdater->enabledPlugins() : QStringList();
108 }
109
110 void KFileItemListView::setLocalFileSizePreviewLimit(const qlonglong size)
111 {
112 if (m_modelRolesUpdater) {
113 m_modelRolesUpdater->setLocalFileSizePreviewLimit(size);
114 }
115 }
116
117 qlonglong KFileItemListView::localFileSizePreviewLimit() const
118 {
119 return m_modelRolesUpdater ? m_modelRolesUpdater->localFileSizePreviewLimit() : 0;
120 }
121
122 void KFileItemListView::setScanDirectories(bool enabled)
123 {
124 m_scanDirectories = enabled;
125 if (m_modelRolesUpdater) {
126 m_modelRolesUpdater->setScanDirectories(m_scanDirectories);
127 }
128 }
129
130 bool KFileItemListView::scanDirectories()
131 {
132 return m_scanDirectories;
133 }
134
135 QPixmap KFileItemListView::createDragPixmap(const KItemSet &indexes) const
136 {
137 if (!model()) {
138 return QPixmap();
139 }
140
141 const int itemCount = indexes.count();
142 Q_ASSERT(itemCount > 0);
143 if (itemCount == 1) {
144 return KItemListView::createDragPixmap(indexes);
145 }
146
147 // If more than one item is dragged, align the items inside a
148 // rectangular grid. The maximum grid size is limited to 5 x 5 items.
149 int xCount;
150 int size;
151 if (itemCount > 16) {
152 xCount = 5;
153 size = KIconLoader::SizeSmall;
154 } else if (itemCount > 9) {
155 xCount = 4;
156 size = KIconLoader::SizeSmallMedium;
157 } else {
158 xCount = 3;
159 size = KIconLoader::SizeMedium;
160 }
161
162 if (itemCount < xCount) {
163 xCount = itemCount;
164 }
165
166 int yCount = itemCount / xCount;
167 if (itemCount % xCount != 0) {
168 ++yCount;
169 }
170 if (yCount > xCount) {
171 yCount = xCount;
172 }
173
174 const qreal dpr = scene()->views()[0]->devicePixelRatio();
175 // Draw the selected items into the grid cells.
176 QPixmap dragPixmap(QSize(xCount * size + xCount, yCount * size + yCount) * dpr);
177 dragPixmap.setDevicePixelRatio(dpr);
178 dragPixmap.fill(Qt::transparent);
179
180 QPainter painter(&dragPixmap);
181 int x = 0;
182 int y = 0;
183
184 for (int index : indexes) {
185 QPixmap pixmap = model()->data(index).value("iconPixmap").value<QPixmap>();
186 if (pixmap.isNull()) {
187 QIcon icon = QIcon::fromTheme(model()->data(index).value("iconName").toString());
188 if (icon.isNull()) {
189 icon = QIcon::fromTheme(QStringLiteral("unknown"));
190 }
191 if (!icon.isNull()) {
192 pixmap = icon.pixmap(size, size);
193 } else {
194 pixmap = QPixmap(size, size);
195 pixmap.fill(Qt::transparent);
196 }
197 } else {
198 KPixmapModifier::scale(pixmap, QSize(size, size) * dpr);
199 }
200
201 painter.drawPixmap(x, y, pixmap);
202
203 x += size + 1;
204 if (x >= dragPixmap.width()) {
205 x = 0;
206 y += size + 1;
207 }
208
209 if (y >= dragPixmap.height()) {
210 break;
211 }
212 }
213
214 return dragPixmap;
215 }
216
217 void KFileItemListView::setHoverSequenceState(const QUrl &itemUrl, int seqIdx)
218 {
219 if (m_modelRolesUpdater) {
220 m_modelRolesUpdater->setHoverSequenceState(itemUrl, seqIdx);
221 }
222 }
223
224 KItemListWidgetCreatorBase *KFileItemListView::defaultWidgetCreator() const
225 {
226 return new KItemListWidgetCreator<KFileItemListWidget>();
227 }
228
229 void KFileItemListView::initializeItemListWidget(KItemListWidget *item)
230 {
231 KStandardItemListView::initializeItemListWidget(item);
232
233 // Make sure that the item has an icon.
234 QHash<QByteArray, QVariant> data = item->data();
235 if (!data.contains("iconName") && data["iconPixmap"].value<QPixmap>().isNull()) {
236 Q_ASSERT(qobject_cast<KFileItemModel *>(model()));
237 KFileItemModel *fileItemModel = static_cast<KFileItemModel *>(model());
238
239 const KFileItem fileItem = fileItemModel->fileItem(item->index());
240 QString iconName = fileItem.iconName();
241 if (!QIcon::hasThemeIcon(iconName)) {
242 QMimeDatabase mimeDb;
243 iconName = mimeDb.mimeTypeForName(fileItem.mimetype()).genericIconName();
244 }
245 data.insert("iconName", iconName);
246 item->setData(data, {"iconName"});
247 }
248 }
249
250 void KFileItemListView::onPreviewsShownChanged(bool shown)
251 {
252 Q_UNUSED(shown)
253 }
254
255 void KFileItemListView::onItemLayoutChanged(ItemLayout current, ItemLayout previous)
256 {
257 KStandardItemListView::onItemLayoutChanged(current, previous);
258 triggerVisibleIndexRangeUpdate();
259 }
260
261 void KFileItemListView::onModelChanged(KItemModelBase *current, KItemModelBase *previous)
262 {
263 Q_ASSERT(qobject_cast<KFileItemModel *>(current));
264 KStandardItemListView::onModelChanged(current, previous);
265
266 delete m_modelRolesUpdater;
267 m_modelRolesUpdater = nullptr;
268
269 if (current) {
270 m_modelRolesUpdater = new KFileItemModelRolesUpdater(static_cast<KFileItemModel *>(current), this);
271 m_modelRolesUpdater->setIconSize(availableIconSize());
272 m_modelRolesUpdater->setScanDirectories(scanDirectories());
273
274 applyRolesToModel();
275 }
276 }
277
278 void KFileItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous)
279 {
280 KStandardItemListView::onScrollOrientationChanged(current, previous);
281 triggerVisibleIndexRangeUpdate();
282 }
283
284 void KFileItemListView::onItemSizeChanged(const QSizeF &current, const QSizeF &previous)
285 {
286 Q_UNUSED(current)
287 Q_UNUSED(previous)
288 triggerVisibleIndexRangeUpdate();
289 }
290
291 void KFileItemListView::onScrollOffsetChanged(qreal current, qreal previous)
292 {
293 KStandardItemListView::onScrollOffsetChanged(current, previous);
294 triggerVisibleIndexRangeUpdate();
295 }
296
297 void KFileItemListView::onVisibleRolesChanged(const QList<QByteArray> &current, const QList<QByteArray> &previous)
298 {
299 KStandardItemListView::onVisibleRolesChanged(current, previous);
300 applyRolesToModel();
301 }
302
303 void KFileItemListView::onStyleOptionChanged(const KItemListStyleOption &current, const KItemListStyleOption &previous)
304 {
305 KStandardItemListView::onStyleOptionChanged(current, previous);
306 triggerIconSizeUpdate();
307 }
308
309 void KFileItemListView::onSupportsItemExpandingChanged(bool supportsExpanding)
310 {
311 applyRolesToModel();
312 KStandardItemListView::onSupportsItemExpandingChanged(supportsExpanding);
313 triggerVisibleIndexRangeUpdate();
314 }
315
316 void KFileItemListView::onTransactionBegin()
317 {
318 if (m_modelRolesUpdater) {
319 m_modelRolesUpdater->setPaused(true);
320 }
321 }
322
323 void KFileItemListView::onTransactionEnd()
324 {
325 if (!m_modelRolesUpdater) {
326 return;
327 }
328
329 // Only unpause the model-roles-updater if no timer is active. If one
330 // timer is still active the model-roles-updater will be unpaused later as
331 // soon as the timer has been exceeded.
332 const bool timerActive = m_updateVisibleIndexRangeTimer->isActive() || m_updateIconSizeTimer->isActive();
333 if (!timerActive) {
334 m_modelRolesUpdater->setPaused(false);
335 }
336 }
337
338 void KFileItemListView::resizeEvent(QGraphicsSceneResizeEvent *event)
339 {
340 KStandardItemListView::resizeEvent(event);
341 triggerVisibleIndexRangeUpdate();
342 }
343
344 void KFileItemListView::slotItemsRemoved(const KItemRangeList &itemRanges)
345 {
346 KStandardItemListView::slotItemsRemoved(itemRanges);
347 }
348
349 void KFileItemListView::slotSortRoleChanged(const QByteArray &current, const QByteArray &previous)
350 {
351 const QByteArray sortRole = model()->sortRole();
352 if (!visibleRoles().contains(sortRole)) {
353 applyRolesToModel();
354 }
355
356 KStandardItemListView::slotSortRoleChanged(current, previous);
357 }
358
359 void KFileItemListView::triggerVisibleIndexRangeUpdate()
360 {
361 if (!model()) {
362 return;
363 }
364 m_modelRolesUpdater->setPaused(true);
365
366 // If the icon size has been changed recently, wait until
367 // m_updateIconSizeTimer expires.
368 if (!m_updateIconSizeTimer->isActive()) {
369 m_updateVisibleIndexRangeTimer->start();
370 }
371 }
372
373 void KFileItemListView::updateVisibleIndexRange()
374 {
375 if (!m_modelRolesUpdater) {
376 return;
377 }
378
379 const int index = firstVisibleIndex();
380 const int count = lastVisibleIndex() - index + 1;
381 m_modelRolesUpdater->setMaximumVisibleItems(maximumVisibleItems());
382 m_modelRolesUpdater->setVisibleIndexRange(index, count);
383 m_modelRolesUpdater->setPaused(isTransactionActive());
384 }
385
386 void KFileItemListView::triggerIconSizeUpdate()
387 {
388 if (!model()) {
389 return;
390 }
391 m_modelRolesUpdater->setPaused(true);
392 m_updateIconSizeTimer->start();
393
394 // The visible index range will be updated when m_updateIconSizeTimer expires.
395 // Stop m_updateVisibleIndexRangeTimer to prevent an expensive re-generation
396 // of all previews (note that the user might change the icon size again soon).
397 m_updateVisibleIndexRangeTimer->stop();
398 }
399
400 void KFileItemListView::updateIconSize()
401 {
402 if (!m_modelRolesUpdater) {
403 return;
404 }
405
406 m_modelRolesUpdater->setIconSize(availableIconSize());
407
408 // Update the visible index range (which has most likely changed after the
409 // icon size change) before unpausing m_modelRolesUpdater.
410 const int index = firstVisibleIndex();
411 const int count = lastVisibleIndex() - index + 1;
412 m_modelRolesUpdater->setVisibleIndexRange(index, count);
413
414 m_modelRolesUpdater->setPaused(isTransactionActive());
415 }
416
417 void KFileItemListView::applyRolesToModel()
418 {
419 if (!model()) {
420 return;
421 }
422
423 Q_ASSERT(qobject_cast<KFileItemModel *>(model()));
424 KFileItemModel *fileItemModel = static_cast<KFileItemModel *>(model());
425
426 // KFileItemModel does not distinct between "visible" and "invisible" roles.
427 // Add all roles that are mandatory for having a working KFileItemListView:
428 const auto visibleRoles = this->visibleRoles();
429 auto roles = QSet<QByteArray>(visibleRoles.constBegin(), visibleRoles.constEnd());
430 roles.insert("iconPixmap");
431 roles.insert("iconName");
432 roles.insert("text");
433 roles.insert("isDir");
434 roles.insert("isLink");
435 roles.insert("isHidden");
436 if (supportsItemExpanding()) {
437 roles.insert("isExpanded");
438 roles.insert("isExpandable");
439 roles.insert("expandedParentsCount");
440 }
441
442 // Assure that the role that is used for sorting will be determined
443 roles.insert(fileItemModel->sortRole());
444
445 fileItemModel->setRoles(roles);
446 m_modelRolesUpdater->setRoles(roles);
447 }
448
449 QSize KFileItemListView::availableIconSize() const
450 {
451 const KItemListStyleOption &option = styleOption();
452 const int iconSize = option.iconSize;
453 if (itemLayout() == IconsLayout) {
454 const int maxIconWidth = itemSize().width() - 2 * option.padding;
455 return QSize(maxIconWidth, iconSize);
456 }
457
458 return QSize(iconSize, iconSize);
459 }