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