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