]> cloud.milkyroute.net Git - dolphin.git/blob - src/kitemviews/kfileitemlistview.cpp
Fix warnings about scaling pixmaps
[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 if (!icon.isNull()) {
172 pixmap = icon.pixmap(size, size);
173 } else {
174 pixmap = QPixmap(size, size);
175 pixmap.fill(Qt::transparent);
176 }
177 } else {
178 KPixmapModifier::scale(pixmap, QSize(size, size) * dpr);
179 }
180
181 painter.drawPixmap(x, y, pixmap);
182
183 x += size + 1;
184 if (x >= dragPixmap.width()) {
185 x = 0;
186 y += size + 1;
187 }
188
189 if (y >= dragPixmap.height()) {
190 break;
191 }
192 }
193
194 return dragPixmap;
195 }
196
197 KItemListWidgetCreatorBase* KFileItemListView::defaultWidgetCreator() const
198 {
199 return new KItemListWidgetCreator<KFileItemListWidget>();
200 }
201
202 void KFileItemListView::initializeItemListWidget(KItemListWidget* item)
203 {
204 KStandardItemListView::initializeItemListWidget(item);
205
206 // Make sure that the item has an icon.
207 QHash<QByteArray, QVariant> data = item->data();
208 if (!data.contains("iconName") && data["iconPixmap"].value<QPixmap>().isNull()) {
209 Q_ASSERT(qobject_cast<KFileItemModel*>(model()));
210 KFileItemModel* fileItemModel = static_cast<KFileItemModel*>(model());
211
212 const KFileItem fileItem = fileItemModel->fileItem(item->index());
213 data.insert("iconName", fileItem.iconName());
214 item->setData(data, {"iconName"});
215 }
216 }
217
218 void KFileItemListView::onPreviewsShownChanged(bool shown)
219 {
220 Q_UNUSED(shown)
221 }
222
223 void KFileItemListView::onItemLayoutChanged(ItemLayout current, ItemLayout previous)
224 {
225 KStandardItemListView::onItemLayoutChanged(current, previous);
226 triggerVisibleIndexRangeUpdate();
227 }
228
229 void KFileItemListView::onModelChanged(KItemModelBase* current, KItemModelBase* previous)
230 {
231 Q_ASSERT(qobject_cast<KFileItemModel*>(current));
232 KStandardItemListView::onModelChanged(current, previous);
233
234 delete m_modelRolesUpdater;
235 m_modelRolesUpdater = nullptr;
236
237 if (current) {
238 m_modelRolesUpdater = new KFileItemModelRolesUpdater(static_cast<KFileItemModel*>(current), this);
239 m_modelRolesUpdater->setIconSize(availableIconSize());
240
241 applyRolesToModel();
242 }
243 }
244
245 void KFileItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous)
246 {
247 KStandardItemListView::onScrollOrientationChanged(current, previous);
248 triggerVisibleIndexRangeUpdate();
249 }
250
251 void KFileItemListView::onItemSizeChanged(const QSizeF& current, const QSizeF& previous)
252 {
253 Q_UNUSED(current)
254 Q_UNUSED(previous)
255 triggerVisibleIndexRangeUpdate();
256 }
257
258 void KFileItemListView::onScrollOffsetChanged(qreal current, qreal previous)
259 {
260 KStandardItemListView::onScrollOffsetChanged(current, previous);
261 triggerVisibleIndexRangeUpdate();
262 }
263
264 void KFileItemListView::onVisibleRolesChanged(const QList<QByteArray>& current, const QList<QByteArray>& previous)
265 {
266 KStandardItemListView::onVisibleRolesChanged(current, previous);
267 applyRolesToModel();
268 }
269
270 void KFileItemListView::onStyleOptionChanged(const KItemListStyleOption& current, const KItemListStyleOption& previous)
271 {
272 KStandardItemListView::onStyleOptionChanged(current, previous);
273 triggerIconSizeUpdate();
274 }
275
276 void KFileItemListView::onSupportsItemExpandingChanged(bool supportsExpanding)
277 {
278 applyRolesToModel();
279 KStandardItemListView::onSupportsItemExpandingChanged(supportsExpanding);
280 triggerVisibleIndexRangeUpdate();
281 }
282
283 void KFileItemListView::onTransactionBegin()
284 {
285 if (m_modelRolesUpdater) {
286 m_modelRolesUpdater->setPaused(true);
287 }
288 }
289
290 void KFileItemListView::onTransactionEnd()
291 {
292 if (!m_modelRolesUpdater) {
293 return;
294 }
295
296 // Only unpause the model-roles-updater if no timer is active. If one
297 // timer is still active the model-roles-updater will be unpaused later as
298 // soon as the timer has been exceeded.
299 const bool timerActive = m_updateVisibleIndexRangeTimer->isActive() ||
300 m_updateIconSizeTimer->isActive();
301 if (!timerActive) {
302 m_modelRolesUpdater->setPaused(false);
303 }
304 }
305
306 void KFileItemListView::resizeEvent(QGraphicsSceneResizeEvent* event)
307 {
308 KStandardItemListView::resizeEvent(event);
309 triggerVisibleIndexRangeUpdate();
310 }
311
312 void KFileItemListView::slotItemsRemoved(const KItemRangeList& itemRanges)
313 {
314 KStandardItemListView::slotItemsRemoved(itemRanges);
315 }
316
317 void KFileItemListView::slotSortRoleChanged(const QByteArray& current, const QByteArray& previous)
318 {
319 const QByteArray sortRole = model()->sortRole();
320 if (!visibleRoles().contains(sortRole)) {
321 applyRolesToModel();
322 }
323
324 KStandardItemListView::slotSortRoleChanged(current, previous);
325 }
326
327 void KFileItemListView::triggerVisibleIndexRangeUpdate()
328 {
329 if (!model()) {
330 return;
331 }
332 m_modelRolesUpdater->setPaused(true);
333
334 // If the icon size has been changed recently, wait until
335 // m_updateIconSizeTimer expires.
336 if (!m_updateIconSizeTimer->isActive()) {
337 m_updateVisibleIndexRangeTimer->start();
338 }
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->setMaximumVisibleItems(maximumVisibleItems());
350 m_modelRolesUpdater->setVisibleIndexRange(index, count);
351 m_modelRolesUpdater->setPaused(isTransactionActive());
352 }
353
354 void KFileItemListView::triggerIconSizeUpdate()
355 {
356 if (!model()) {
357 return;
358 }
359 m_modelRolesUpdater->setPaused(true);
360 m_updateIconSizeTimer->start();
361
362 // The visible index range will be updated when m_updateIconSizeTimer expires.
363 // Stop m_updateVisibleIndexRangeTimer to prevent an expensive re-generation
364 // of all previews (note that the user might change the icon size again soon).
365 m_updateVisibleIndexRangeTimer->stop();
366 }
367
368 void KFileItemListView::updateIconSize()
369 {
370 if (!m_modelRolesUpdater) {
371 return;
372 }
373
374 m_modelRolesUpdater->setIconSize(availableIconSize());
375
376 // Update the visible index range (which has most likely changed after the
377 // icon size change) before unpausing m_modelRolesUpdater.
378 const int index = firstVisibleIndex();
379 const int count = lastVisibleIndex() - index + 1;
380 m_modelRolesUpdater->setVisibleIndexRange(index, count);
381
382 m_modelRolesUpdater->setPaused(isTransactionActive());
383 }
384
385 void KFileItemListView::applyRolesToModel()
386 {
387 if (!model()) {
388 return;
389 }
390
391 Q_ASSERT(qobject_cast<KFileItemModel*>(model()));
392 KFileItemModel* fileItemModel = static_cast<KFileItemModel*>(model());
393
394 // KFileItemModel does not distinct between "visible" and "invisible" roles.
395 // Add all roles that are mandatory for having a working KFileItemListView:
396 const auto visibleRoles = this->visibleRoles();
397 auto roles = QSet<QByteArray>(visibleRoles.constBegin(), visibleRoles.constEnd());
398 roles.insert("iconPixmap");
399 roles.insert("iconName");
400 roles.insert("text");
401 roles.insert("isDir");
402 roles.insert("isLink");
403 roles.insert("isHidden");
404 if (supportsItemExpanding()) {
405 roles.insert("isExpanded");
406 roles.insert("isExpandable");
407 roles.insert("expandedParentsCount");
408 }
409
410 // Assure that the role that is used for sorting will be determined
411 roles.insert(fileItemModel->sortRole());
412
413 fileItemModel->setRoles(roles);
414 m_modelRolesUpdater->setRoles(roles);
415 }
416
417 QSize KFileItemListView::availableIconSize() const
418 {
419 const KItemListStyleOption& option = styleOption();
420 const int iconSize = option.iconSize;
421 if (itemLayout() == IconsLayout) {
422 const int maxIconWidth = itemSize().width() - 2 * option.padding;
423 return QSize(maxIconWidth, iconSize);
424 }
425
426 return QSize(iconSize, iconSize);
427 }
428