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 ¤t, 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> ¤t, const QList<QByteArray> &previous) 0285 { 0286 KStandardItemListView::onVisibleRolesChanged(current, previous); 0287 applyRolesToModel(); 0288 } 0289 0290 void KFileItemListView::onStyleOptionChanged(const KItemListStyleOption ¤t, 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 ¤t, 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"