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