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