File indexing completed on 2024-04-28 05:45:03

0001 /*
0002  * SPDX-FileCopyrightText: 2011 Peter Penz <peter.penz19@gmail.com>
0003  *
0004  * SPDX-License-Identifier: GPL-2.0-or-later
0005  */
0006 
0007 #include "kfileitemlistview.h"
0008 
0009 #include "kfileitemlistwidget.h"
0010 #include "kfileitemmodel.h"
0011 #include "kfileitemmodelrolesupdater.h"
0012 #include "private/kitemviewsutils.h"
0013 #include "private/kpixmapmodifier.h"
0014 
0015 #include <KIconLoader>
0016 
0017 #include <QGraphicsScene>
0018 #include <QGraphicsView>
0019 #include <QIcon>
0020 #include <QMimeDatabase>
0021 #include <QPainter>
0022 #include <QTimer>
0023 
0024 // #define KFILEITEMLISTVIEW_DEBUG
0025 
0026 namespace
0027 {
0028 // If the visible index range changes, KFileItemModelRolesUpdater is not
0029 // informed immediately, but with a short delay. This ensures that scrolling
0030 // always feels smooth and is not interrupted by icon loading (which can be
0031 // quite expensive if a disk access is required to determine the final icon).
0032 const int ShortInterval = 50;
0033 
0034 // If the icon size changes, a longer delay is used. This prevents that
0035 // the expensive re-generation of all previews is triggered repeatedly when
0036 // changing the zoom level.
0037 const int LongInterval = 300;
0038 }
0039 
0040 KFileItemListView::KFileItemListView(QGraphicsWidget *parent)
0041     : KStandardItemListView(parent)
0042     , m_modelRolesUpdater(nullptr)
0043     , m_updateVisibleIndexRangeTimer(nullptr)
0044     , m_updateIconSizeTimer(nullptr)
0045 {
0046     setAcceptDrops(true);
0047 
0048     setScrollOrientation(Qt::Vertical);
0049 
0050     m_updateVisibleIndexRangeTimer = new QTimer(this);
0051     m_updateVisibleIndexRangeTimer->setSingleShot(true);
0052     m_updateVisibleIndexRangeTimer->setInterval(ShortInterval);
0053     connect(m_updateVisibleIndexRangeTimer, &QTimer::timeout, this, &KFileItemListView::updateVisibleIndexRange);
0054 
0055     m_updateIconSizeTimer = new QTimer(this);
0056     m_updateIconSizeTimer->setSingleShot(true);
0057     m_updateIconSizeTimer->setInterval(LongInterval);
0058     connect(m_updateIconSizeTimer, &QTimer::timeout, this, &KFileItemListView::updateIconSize);
0059 
0060     setVisibleRoles({"text"});
0061 }
0062 
0063 KFileItemListView::~KFileItemListView()
0064 {
0065 }
0066 
0067 void KFileItemListView::setPreviewsShown(bool show)
0068 {
0069     if (!m_modelRolesUpdater) {
0070         return;
0071     }
0072 
0073     if (m_modelRolesUpdater->previewsShown() != show) {
0074         beginTransaction();
0075         m_modelRolesUpdater->setPreviewsShown(show);
0076         onPreviewsShownChanged(show);
0077         endTransaction();
0078     }
0079 }
0080 
0081 bool KFileItemListView::previewsShown() const
0082 {
0083     return m_modelRolesUpdater ? m_modelRolesUpdater->previewsShown() : false;
0084 }
0085 
0086 void KFileItemListView::setEnlargeSmallPreviews(bool enlarge)
0087 {
0088     if (m_modelRolesUpdater) {
0089         m_modelRolesUpdater->setEnlargeSmallPreviews(enlarge);
0090     }
0091 }
0092 
0093 bool KFileItemListView::enlargeSmallPreviews() const
0094 {
0095     return m_modelRolesUpdater ? m_modelRolesUpdater->enlargeSmallPreviews() : false;
0096 }
0097 
0098 void KFileItemListView::setEnabledPlugins(const QStringList &list)
0099 {
0100     if (m_modelRolesUpdater) {
0101         m_modelRolesUpdater->setEnabledPlugins(list);
0102     }
0103 }
0104 
0105 QStringList KFileItemListView::enabledPlugins() const
0106 {
0107     return m_modelRolesUpdater ? m_modelRolesUpdater->enabledPlugins() : QStringList();
0108 }
0109 
0110 void KFileItemListView::setLocalFileSizePreviewLimit(const qlonglong size)
0111 {
0112     if (m_modelRolesUpdater) {
0113         m_modelRolesUpdater->setLocalFileSizePreviewLimit(size);
0114     }
0115 }
0116 
0117 qlonglong KFileItemListView::localFileSizePreviewLimit() const
0118 {
0119     return m_modelRolesUpdater ? m_modelRolesUpdater->localFileSizePreviewLimit() : 0;
0120 }
0121 
0122 QPixmap KFileItemListView::createDragPixmap(const KItemSet &indexes) const
0123 {
0124     if (!model()) {
0125         return QPixmap();
0126     }
0127 
0128     const int itemCount = indexes.count();
0129     Q_ASSERT(itemCount > 0);
0130     if (itemCount == 1) {
0131         return KItemListView::createDragPixmap(indexes);
0132     }
0133 
0134     // If more than one item is dragged, align the items inside a
0135     // rectangular grid. The maximum grid size is limited to 5 x 5 items.
0136     int xCount;
0137     int size;
0138     if (itemCount > 16) {
0139         xCount = 5;
0140         size = KIconLoader::SizeSmall;
0141     } else if (itemCount > 9) {
0142         xCount = 4;
0143         size = KIconLoader::SizeSmallMedium;
0144     } else {
0145         xCount = 3;
0146         size = KIconLoader::SizeMedium;
0147     }
0148 
0149     if (itemCount < xCount) {
0150         xCount = itemCount;
0151     }
0152 
0153     int yCount = itemCount / xCount;
0154     if (itemCount % xCount != 0) {
0155         ++yCount;
0156     }
0157     if (yCount > xCount) {
0158         yCount = xCount;
0159     }
0160 
0161     const qreal dpr = KItemViewsUtils::devicePixelRatio(this);
0162     // Draw the selected items into the grid cells.
0163     QPixmap dragPixmap(QSize(xCount * size + xCount, yCount * size + yCount) * dpr);
0164     dragPixmap.setDevicePixelRatio(dpr);
0165     dragPixmap.fill(Qt::transparent);
0166 
0167     QPainter painter(&dragPixmap);
0168     int x = 0;
0169     int y = 0;
0170 
0171     for (int index : indexes) {
0172         QPixmap pixmap = model()->data(index).value("iconPixmap").value<QPixmap>();
0173         if (pixmap.isNull()) {
0174             QIcon icon = QIcon::fromTheme(model()->data(index).value("iconName").toString());
0175             if (icon.isNull()) {
0176                 icon = QIcon::fromTheme(QStringLiteral("unknown"));
0177             }
0178             if (!icon.isNull()) {
0179                 pixmap = icon.pixmap(size, size);
0180             } else {
0181                 pixmap = QPixmap(size, size);
0182                 pixmap.fill(Qt::transparent);
0183             }
0184         } else {
0185             KPixmapModifier::scale(pixmap, QSize(size, size) * dpr);
0186         }
0187 
0188         painter.drawPixmap(x, y, pixmap);
0189 
0190         x += size + 1;
0191         if (x >= dragPixmap.width()) {
0192             x = 0;
0193             y += size + 1;
0194         }
0195 
0196         if (y >= dragPixmap.height()) {
0197             break;
0198         }
0199     }
0200 
0201     return dragPixmap;
0202 }
0203 
0204 void KFileItemListView::setHoverSequenceState(const QUrl &itemUrl, int seqIdx)
0205 {
0206     if (m_modelRolesUpdater) {
0207         m_modelRolesUpdater->setHoverSequenceState(itemUrl, seqIdx);
0208     }
0209 }
0210 
0211 KItemListWidgetCreatorBase *KFileItemListView::defaultWidgetCreator() const
0212 {
0213     return new KItemListWidgetCreator<KFileItemListWidget>();
0214 }
0215 
0216 void KFileItemListView::initializeItemListWidget(KItemListWidget *item)
0217 {
0218     KStandardItemListView::initializeItemListWidget(item);
0219 
0220     // Make sure that the item has an icon.
0221     QHash<QByteArray, QVariant> data = item->data();
0222     if (!data.contains("iconName") && data["iconPixmap"].value<QPixmap>().isNull()) {
0223         Q_ASSERT(qobject_cast<KFileItemModel *>(model()));
0224         KFileItemModel *fileItemModel = static_cast<KFileItemModel *>(model());
0225 
0226         const KFileItem fileItem = fileItemModel->fileItem(item->index());
0227         QString iconName = fileItem.iconName();
0228         if (!QIcon::hasThemeIcon(iconName)) {
0229             QMimeDatabase mimeDb;
0230             iconName = mimeDb.mimeTypeForName(fileItem.mimetype()).genericIconName();
0231         }
0232         data.insert("iconName", iconName);
0233         item->setData(data, {"iconName"});
0234     }
0235 }
0236 
0237 void KFileItemListView::onPreviewsShownChanged(bool shown)
0238 {
0239     Q_UNUSED(shown)
0240 }
0241 
0242 void KFileItemListView::onItemLayoutChanged(ItemLayout current, ItemLayout previous)
0243 {
0244     KStandardItemListView::onItemLayoutChanged(current, previous);
0245     triggerVisibleIndexRangeUpdate();
0246 }
0247 
0248 void KFileItemListView::onModelChanged(KItemModelBase *current, KItemModelBase *previous)
0249 {
0250     Q_ASSERT(qobject_cast<KFileItemModel *>(current));
0251     KStandardItemListView::onModelChanged(current, previous);
0252 
0253     delete m_modelRolesUpdater;
0254     m_modelRolesUpdater = nullptr;
0255 
0256     if (current) {
0257         m_modelRolesUpdater = new KFileItemModelRolesUpdater(static_cast<KFileItemModel *>(current), this);
0258         m_modelRolesUpdater->setIconSize(availableIconSize());
0259         m_modelRolesUpdater->setDevicePixelRatio(KItemViewsUtils::devicePixelRatio(this));
0260 
0261         applyRolesToModel();
0262     }
0263 }
0264 
0265 void KFileItemListView::onScrollOrientationChanged(Qt::Orientation current, Qt::Orientation previous)
0266 {
0267     KStandardItemListView::onScrollOrientationChanged(current, previous);
0268     triggerVisibleIndexRangeUpdate();
0269 }
0270 
0271 void KFileItemListView::onItemSizeChanged(const QSizeF &current, const QSizeF &previous)
0272 {
0273     Q_UNUSED(current)
0274     Q_UNUSED(previous)
0275     triggerVisibleIndexRangeUpdate();
0276 }
0277 
0278 void KFileItemListView::onScrollOffsetChanged(qreal current, qreal previous)
0279 {
0280     KStandardItemListView::onScrollOffsetChanged(current, previous);
0281     triggerVisibleIndexRangeUpdate();
0282 }
0283 
0284 void KFileItemListView::onVisibleRolesChanged(const QList<QByteArray> &current, const QList<QByteArray> &previous)
0285 {
0286     KStandardItemListView::onVisibleRolesChanged(current, previous);
0287     applyRolesToModel();
0288 }
0289 
0290 void KFileItemListView::onStyleOptionChanged(const KItemListStyleOption &current, const KItemListStyleOption &previous)
0291 {
0292     KStandardItemListView::onStyleOptionChanged(current, previous);
0293     triggerIconSizeUpdate();
0294 }
0295 
0296 void KFileItemListView::onSupportsItemExpandingChanged(bool supportsExpanding)
0297 {
0298     applyRolesToModel();
0299     KStandardItemListView::onSupportsItemExpandingChanged(supportsExpanding);
0300     triggerVisibleIndexRangeUpdate();
0301 }
0302 
0303 void KFileItemListView::onTransactionBegin()
0304 {
0305     if (m_modelRolesUpdater) {
0306         m_modelRolesUpdater->setPaused(true);
0307     }
0308 }
0309 
0310 void KFileItemListView::onTransactionEnd()
0311 {
0312     if (!m_modelRolesUpdater) {
0313         return;
0314     }
0315 
0316     // Only unpause the model-roles-updater if no timer is active. If one
0317     // timer is still active the model-roles-updater will be unpaused later as
0318     // soon as the timer has been exceeded.
0319     const bool timerActive = m_updateVisibleIndexRangeTimer->isActive() || m_updateIconSizeTimer->isActive();
0320     if (!timerActive) {
0321         m_modelRolesUpdater->setPaused(false);
0322     }
0323 }
0324 
0325 void KFileItemListView::resizeEvent(QGraphicsSceneResizeEvent *event)
0326 {
0327     KStandardItemListView::resizeEvent(event);
0328     triggerVisibleIndexRangeUpdate();
0329 }
0330 
0331 void KFileItemListView::focusInEvent(QFocusEvent *event)
0332 {
0333     Q_UNUSED(event)
0334     updateSelectedWidgets();
0335 }
0336 
0337 void KFileItemListView::focusOutEvent(QFocusEvent *event)
0338 {
0339     Q_UNUSED(event)
0340     updateSelectedWidgets();
0341 }
0342 
0343 void KFileItemListView::updateSelectedWidgets()
0344 {
0345     const auto visibleWidgets = visibleItemListWidgets();
0346     for (KItemListWidget *widget : visibleWidgets) {
0347         if (widget->isSelected()) {
0348             auto w = qobject_cast<KFileItemListWidget *>(widget);
0349             if (w) {
0350                 w->forceUpdate();
0351             }
0352         }
0353     }
0354 }
0355 
0356 void KFileItemListView::slotItemsRemoved(const KItemRangeList &itemRanges)
0357 {
0358     KStandardItemListView::slotItemsRemoved(itemRanges);
0359 }
0360 
0361 void KFileItemListView::slotSortRoleChanged(const QByteArray &current, const QByteArray &previous)
0362 {
0363     const QByteArray sortRole = model()->sortRole();
0364     if (!visibleRoles().contains(sortRole)) {
0365         applyRolesToModel();
0366     }
0367 
0368     KStandardItemListView::slotSortRoleChanged(current, previous);
0369 }
0370 
0371 void KFileItemListView::triggerVisibleIndexRangeUpdate()
0372 {
0373     if (!model()) {
0374         return;
0375     }
0376     m_modelRolesUpdater->setPaused(true);
0377 
0378     // If the icon size has been changed recently, wait until
0379     // m_updateIconSizeTimer expires.
0380     if (!m_updateIconSizeTimer->isActive()) {
0381         m_updateVisibleIndexRangeTimer->start();
0382     }
0383 }
0384 
0385 void KFileItemListView::updateVisibleIndexRange()
0386 {
0387     if (!m_modelRolesUpdater) {
0388         return;
0389     }
0390 
0391     const int index = firstVisibleIndex();
0392     const int count = lastVisibleIndex() - index + 1;
0393     m_modelRolesUpdater->setMaximumVisibleItems(maximumVisibleItems());
0394     m_modelRolesUpdater->setVisibleIndexRange(index, count);
0395     m_modelRolesUpdater->setPaused(isTransactionActive());
0396 }
0397 
0398 void KFileItemListView::triggerIconSizeUpdate()
0399 {
0400     if (!model()) {
0401         return;
0402     }
0403     m_modelRolesUpdater->setPaused(true);
0404     m_updateIconSizeTimer->start();
0405 
0406     // The visible index range will be updated when m_updateIconSizeTimer expires.
0407     // Stop m_updateVisibleIndexRangeTimer to prevent an expensive re-generation
0408     // of all previews (note that the user might change the icon size again soon).
0409     m_updateVisibleIndexRangeTimer->stop();
0410 }
0411 
0412 void KFileItemListView::updateIconSize()
0413 {
0414     if (!m_modelRolesUpdater) {
0415         return;
0416     }
0417 
0418     m_modelRolesUpdater->setIconSize(availableIconSize());
0419     m_modelRolesUpdater->setDevicePixelRatio(KItemViewsUtils::devicePixelRatio(this));
0420 
0421     // Update the visible index range (which has most likely changed after the
0422     // icon size change) before unpausing m_modelRolesUpdater.
0423     const int index = firstVisibleIndex();
0424     const int count = lastVisibleIndex() - index + 1;
0425     m_modelRolesUpdater->setVisibleIndexRange(index, count);
0426 
0427     m_modelRolesUpdater->setPaused(isTransactionActive());
0428 }
0429 
0430 void KFileItemListView::applyRolesToModel()
0431 {
0432     if (!model()) {
0433         return;
0434     }
0435 
0436     Q_ASSERT(qobject_cast<KFileItemModel *>(model()));
0437     KFileItemModel *fileItemModel = static_cast<KFileItemModel *>(model());
0438 
0439     // KFileItemModel does not distinct between "visible" and "invisible" roles.
0440     // Add all roles that are mandatory for having a working KFileItemListView:
0441     const auto visibleRoles = this->visibleRoles();
0442     auto roles = QSet<QByteArray>(visibleRoles.constBegin(), visibleRoles.constEnd());
0443     roles.insert("iconPixmap");
0444     roles.insert("iconName");
0445     roles.insert("text");
0446     roles.insert("isDir");
0447     roles.insert("isLink");
0448     roles.insert("isHidden");
0449     if (supportsItemExpanding()) {
0450         roles.insert("isExpanded");
0451         roles.insert("isExpandable");
0452         roles.insert("expandedParentsCount");
0453     }
0454 
0455     // Assure that the role that is used for sorting will be determined
0456     roles.insert(fileItemModel->sortRole());
0457 
0458     fileItemModel->setRoles(roles);
0459     m_modelRolesUpdater->setRoles(roles);
0460 }
0461 
0462 QSize KFileItemListView::availableIconSize() const
0463 {
0464     const KItemListStyleOption &option = styleOption();
0465     const int iconSize = option.iconSize;
0466     if (itemLayout() == IconsLayout) {
0467         const int maxIconWidth = itemSize().width() - 2 * option.padding;
0468         return QSize(maxIconWidth, iconSize);
0469     }
0470 
0471     return QSize(iconSize, iconSize);
0472 }
0473 
0474 #include "moc_kfileitemlistview.cpp"