X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/4d886d083ca6cb2d635da2d29efb804b2680b6de..b5432d60372e2dad6d31cd4e6d28e4da5d47a7e4:/src/kfilepreviewgenerator.cpp diff --git a/src/kfilepreviewgenerator.cpp b/src/kfilepreviewgenerator.cpp index e81f66980..f78305d02 100644 --- a/src/kfilepreviewgenerator.cpp +++ b/src/kfilepreviewgenerator.cpp @@ -19,6 +19,7 @@ #include "kfilepreviewgenerator.h" +#include #include #include #include @@ -31,11 +32,19 @@ #include #include #include +#include #include #include +#include #include #include +#ifdef Q_WS_X11 +# include +# include +# include +#endif + /** * If the passed item view is an instance of QListView, expensive * layout operations are blocked in the constructor and are unblocked @@ -80,13 +89,153 @@ private: QListView* m_view; }; -KFilePreviewGenerator::KFilePreviewGenerator(QAbstractItemView* parent, KDirSortFilterProxyModel* model) : - QObject(parent), - m_showPreview(false), +class KFilePreviewGenerator::Private +{ +public: + Private(KFilePreviewGenerator* parent, + QAbstractItemView* view, + KDirSortFilterProxyModel* model); + ~Private(); + + /** + * Generates previews for the items \a items asynchronously. + */ + void generatePreviews(const KFileItemList& items); + + /** + * Adds the preview \a pixmap for the item \a item to the preview + * queue and starts a timer which will dispatch the preview queue + * later. + */ + void addToPreviewQueue(const KFileItem& item, const QPixmap& pixmap); + + /** + * Is invoked when the preview job has been finished and + * removes the job from the m_previewJobs list. + */ + void slotPreviewJobFinished(KJob* job); + + /** Synchronizes the item icon with the clipboard of cut items. */ + void updateCutItems(); + + /** + * Dispatches the preview queue block by block within + * time slices. + */ + void dispatchPreviewQueue(); + + /** + * Pauses all preview jobs and invokes KFilePreviewGenerator::resumePreviews() + * after a short delay. Is invoked as soon as the user has moved + * a scrollbar. + */ + void pausePreviews(); + + /** + * Resumes the previews that have been paused after moving the + * scrollbar. The previews for the current visible area are + * generated first. + */ + void resumePreviews(); + + /** + * Returns true, if the item \a item has been cut into + * the clipboard. + */ + bool isCutItem(const KFileItem& item) const; + + /** Applies an item effect to all cut items. */ + void applyCutItemEffect(); + + /** + * Applies a frame around the icon. False is returned if + * no frame has been added because the icon is too small. + */ + bool applyImageFrame(QPixmap& icon); + + /** + * Resizes the icon to \a maxSize if the icon size does not + * fit into the maximum size. The aspect ratio of the icon + * is kept. + */ + void limitToSize(QPixmap& icon, const QSize& maxSize); + + /** + * Starts a new preview job for the items \a to m_previewJobs + * and triggers the preview timer. + */ + void startPreviewJob(const KFileItemList& items); + + /** Kills all ongoing preview jobs. */ + void killPreviewJobs(); + + /** + * Orders the items \a items in a way that the visible items + * are moved to the front of the list. When passing this + * list to a preview job, the visible items will get generated + * first. + */ + void orderItems(KFileItemList& items); + + /** Remembers the pixmap for an item specified by an URL. */ + struct ItemInfo + { + KUrl url; + QPixmap pixmap; + }; + + bool m_showPreview; + + /** + * True, if m_pendingItems and m_dispatchedItems should be + * cleared when the preview jobs have been finished. + */ + bool m_clearItemQueues; + + /** + * True if a selection has been done which should cut items. + */ + bool m_hasCutSelection; + + int m_pendingVisiblePreviews; + + QAbstractItemView* m_view; + QTimer* m_previewTimer; + QTimer* m_scrollAreaTimer; + QList m_previewJobs; + KDirModel* m_dirModel; + KDirSortFilterProxyModel* m_proxyModel; + + KMimeTypeResolver* m_mimeTypeResolver; + + QList m_cutItemsCache; + QList m_previews; + + /** + * Contains all items where a preview must be generated, but + * where the preview job has not dispatched the items yet. + */ + KFileItemList m_pendingItems; + + /** + * Contains all items, where a preview has already been + * generated by the preview jobs. + */ + KFileItemList m_dispatchedItems; + +private: + KFilePreviewGenerator* const q; + +}; + +KFilePreviewGenerator::Private::Private(KFilePreviewGenerator* parent, + QAbstractItemView* view, + KDirSortFilterProxyModel* model) : + m_showPreview(true), m_clearItemQueues(true), m_hasCutSelection(false), m_pendingVisiblePreviews(0), - m_view(parent), + m_view(view), m_previewTimer(0), m_scrollAreaTimer(0), m_previewJobs(), @@ -96,39 +245,42 @@ KFilePreviewGenerator::KFilePreviewGenerator(QAbstractItemView* parent, KDirSort m_cutItemsCache(), m_previews(), m_pendingItems(), - m_dispatchedItems() + m_dispatchedItems(), + q(parent) { - Q_ASSERT(m_view->iconSize().isValid()); // each view must provide its current icon size + if (!m_view->iconSize().isValid()) { + m_showPreview = false; + } m_dirModel = static_cast(m_proxyModel->sourceModel()); connect(m_dirModel->dirLister(), SIGNAL(newItems(const KFileItemList&)), - this, SLOT(generatePreviews(const KFileItemList&))); + q, SLOT(generatePreviews(const KFileItemList&))); QClipboard* clipboard = QApplication::clipboard(); connect(clipboard, SIGNAL(dataChanged()), - this, SLOT(updateCutItems())); + q, SLOT(updateCutItems())); - m_previewTimer = new QTimer(this); + m_previewTimer = new QTimer(q); m_previewTimer->setSingleShot(true); - connect(m_previewTimer, SIGNAL(timeout()), this, SLOT(dispatchPreviewQueue())); + connect(m_previewTimer, SIGNAL(timeout()), q, SLOT(dispatchPreviewQueue())); // Whenever the scrollbar values have been changed, the pending previews should // be reordered in a way that the previews for the visible items are generated // first. The reordering is done with a small delay, so that during moving the // scrollbars the CPU load is kept low. - m_scrollAreaTimer = new QTimer(this); + m_scrollAreaTimer = new QTimer(q); m_scrollAreaTimer->setSingleShot(true); m_scrollAreaTimer->setInterval(200); connect(m_scrollAreaTimer, SIGNAL(timeout()), - this, SLOT(resumePreviews())); + q, SLOT(resumePreviews())); connect(m_view->horizontalScrollBar(), SIGNAL(valueChanged(int)), - this, SLOT(pausePreviews())); + q, SLOT(pausePreviews())); connect(m_view->verticalScrollBar(), SIGNAL(valueChanged(int)), - this, SLOT(pausePreviews())); + q, SLOT(pausePreviews())); } -KFilePreviewGenerator::~KFilePreviewGenerator() -{ +KFilePreviewGenerator::Private::~Private() +{ killPreviewJobs(); m_pendingItems.clear(); m_dispatchedItems.clear(); @@ -138,60 +290,7 @@ KFilePreviewGenerator::~KFilePreviewGenerator() } } -void KFilePreviewGenerator::setShowPreview(bool show) -{ - if (m_showPreview != show) { - m_showPreview = show; - m_cutItemsCache.clear(); - updateCutItems(); - if (show) { - updatePreviews(); - } - } - - if (show && (m_mimeTypeResolver != 0)) { - // don't resolve the MIME types if the preview is turned on - m_mimeTypeResolver->deleteLater(); - m_mimeTypeResolver = 0; - } else if (!show && (m_mimeTypeResolver == 0)) { - // the preview is turned off: resolve the MIME-types so that - // the icons gets updated - m_mimeTypeResolver = new KMimeTypeResolver(m_view, m_dirModel); - } -} - -void KFilePreviewGenerator::updatePreviews() -{ - if (!m_showPreview) { - return; - } - - killPreviewJobs(); - m_cutItemsCache.clear(); - m_pendingItems.clear(); - m_dispatchedItems.clear(); - - KFileItemList itemList; - const int rowCount = m_dirModel->rowCount(); - for (int row = 0; row < rowCount; ++row) { - const QModelIndex index = m_dirModel->index(row, 0); - KFileItem item = m_dirModel->itemForIndex(index); - itemList.append(item); - } - - generatePreviews(itemList); - updateCutItems(); -} - -void KFilePreviewGenerator::cancelPreviews() -{ - killPreviewJobs(); - m_cutItemsCache.clear(); - m_pendingItems.clear(); - m_dispatchedItems.clear(); -} - -void KFilePreviewGenerator::generatePreviews(const KFileItemList& items) +void KFilePreviewGenerator::Private::generatePreviews(const KFileItemList& items) { applyCutItemEffect(); @@ -209,7 +308,7 @@ void KFilePreviewGenerator::generatePreviews(const KFileItemList& items) startPreviewJob(orderedItems); } -void KFilePreviewGenerator::addToPreviewQueue(const KFileItem& item, const QPixmap& pixmap) +void KFilePreviewGenerator::Private::addToPreviewQueue(const KFileItem& item, const QPixmap& pixmap) { if (!m_showPreview) { // the preview has been canceled in the meantime @@ -270,7 +369,7 @@ void KFilePreviewGenerator::addToPreviewQueue(const KFileItem& item, const QPixm m_dispatchedItems.append(item); } -void KFilePreviewGenerator::slotPreviewJobFinished(KJob* job) +void KFilePreviewGenerator::Private::slotPreviewJobFinished(KJob* job) { const int index = m_previewJobs.indexOf(job); m_previewJobs.removeAt(index); @@ -279,11 +378,11 @@ void KFilePreviewGenerator::slotPreviewJobFinished(KJob* job) m_pendingItems.clear(); m_dispatchedItems.clear(); m_pendingVisiblePreviews = 0; - QMetaObject::invokeMethod(this, "dispatchPreviewQueue", Qt::QueuedConnection); + QMetaObject::invokeMethod(q, "dispatchPreviewQueue", Qt::QueuedConnection); } } -void KFilePreviewGenerator::updateCutItems() +void KFilePreviewGenerator::Private::updateCutItems() { // restore the icons of all previously selected items to the // original state... @@ -299,7 +398,7 @@ void KFilePreviewGenerator::updateCutItems() applyCutItemEffect(); } -void KFilePreviewGenerator::dispatchPreviewQueue() +void KFilePreviewGenerator::Private::dispatchPreviewQueue() { const int previewsCount = m_previews.count(); if (previewsCount > 0) { @@ -331,7 +430,7 @@ void KFilePreviewGenerator::dispatchPreviewQueue() } } -void KFilePreviewGenerator::pausePreviews() +void KFilePreviewGenerator::Private::pausePreviews() { foreach (KJob* job, m_previewJobs) { Q_ASSERT(job != 0); @@ -340,7 +439,7 @@ void KFilePreviewGenerator::pausePreviews() m_scrollAreaTimer->start(); } -void KFilePreviewGenerator::resumePreviews() +void KFilePreviewGenerator::Private::resumePreviews() { // Before creating new preview jobs the m_pendingItems queue must be // cleaned up by removing the already dispatched items. Implementation @@ -377,7 +476,7 @@ void KFilePreviewGenerator::resumePreviews() startPreviewJob(orderedItems); } -bool KFilePreviewGenerator::isCutItem(const KFileItem& item) const +bool KFilePreviewGenerator::Private::isCutItem(const KFileItem& item) const { const QMimeData* mimeData = QApplication::clipboard()->mimeData(); const KUrl::List cutUrls = KUrl::List::fromMimeData(mimeData); @@ -392,7 +491,7 @@ bool KFilePreviewGenerator::isCutItem(const KFileItem& item) const return false; } -void KFilePreviewGenerator::applyCutItemEffect() +void KFilePreviewGenerator::Private::applyCutItemEffect() { const QMimeData* mimeData = QApplication::clipboard()->mimeData(); m_hasCutSelection = KonqMimeData::decodeIsCutSelection(mimeData); @@ -432,7 +531,7 @@ void KFilePreviewGenerator::applyCutItemEffect() } } -bool KFilePreviewGenerator::applyImageFrame(QPixmap& icon) +bool KFilePreviewGenerator::Private::applyImageFrame(QPixmap& icon) { const QSize maxSize = m_view->iconSize(); const bool applyFrame = (maxSize.width() > KIconLoader::SizeSmallMedium) && @@ -482,14 +581,42 @@ bool KFilePreviewGenerator::applyImageFrame(QPixmap& icon) return true; } -void KFilePreviewGenerator::limitToSize(QPixmap& icon, const QSize& maxSize) +void KFilePreviewGenerator::Private::limitToSize(QPixmap& icon, const QSize& maxSize) { if ((icon.width() > maxSize.width()) || (icon.height() > maxSize.height())) { +#ifdef Q_WS_X11 + // Assume that the texture size limit is 2048x2048 + if ((icon.width() <= 2048) && (icon.height() <= 2048)) { + QSize size = icon.size(); + size.scale(maxSize, Qt::KeepAspectRatio); + + const qreal factor = size.width() / qreal(icon.width()); + + XTransform xform = {{ + { XDoubleToFixed(1 / factor), 0, 0 }, + { 0, XDoubleToFixed(1 / factor), 0 }, + { 0, 0, XDoubleToFixed(1) } + }}; + + QPixmap pixmap(size); + pixmap.fill(Qt::transparent); + + Display *dpy = QX11Info::display(); + XRenderSetPictureFilter(dpy, icon.x11PictureHandle(), FilterBilinear, 0, 0); + XRenderSetPictureTransform(dpy, icon.x11PictureHandle(), &xform); + XRenderComposite(dpy, PictOpOver, icon.x11PictureHandle(), None, pixmap.x11PictureHandle(), + 0, 0, 0, 0, 0, 0, pixmap.width(), pixmap.height()); + icon = pixmap; + } else { + icon = icon.scaled(maxSize, Qt::KeepAspectRatio, Qt::FastTransformation); + } +#else icon = icon.scaled(maxSize, Qt::KeepAspectRatio, Qt::FastTransformation); +#endif } } -void KFilePreviewGenerator::startPreviewJob(const KFileItemList& items) +void KFilePreviewGenerator::Private::startPreviewJob(const KFileItemList& items) { if (items.count() == 0) { return; @@ -508,15 +635,15 @@ void KFilePreviewGenerator::startPreviewJob(const KFileItemList& items) const int cacheSize = (size.width() > 128) || (size.height() > 128) ? 256 : 128; KIO::PreviewJob* job = KIO::filePreview(items, cacheSize, cacheSize); connect(job, SIGNAL(gotPreview(const KFileItem&, const QPixmap&)), - this, SLOT(addToPreviewQueue(const KFileItem&, const QPixmap&))); + q, SLOT(addToPreviewQueue(const KFileItem&, const QPixmap&))); connect(job, SIGNAL(finished(KJob*)), - this, SLOT(slotPreviewJobFinished(KJob*))); + q, SLOT(slotPreviewJobFinished(KJob*))); m_previewJobs.append(job); m_previewTimer->start(200); } -void KFilePreviewGenerator::killPreviewJobs() +void KFilePreviewGenerator::Private::killPreviewJobs() { foreach (KJob* job, m_previewJobs) { Q_ASSERT(job != 0); @@ -525,7 +652,7 @@ void KFilePreviewGenerator::killPreviewJobs() m_previewJobs.clear(); } -void KFilePreviewGenerator::orderItems(KFileItemList& items) +void KFilePreviewGenerator::Private::orderItems(KFileItemList& items) { // Order the items in a way that the preview for the visible items // is generated first, as this improves the feeled performance a lot. @@ -593,4 +720,79 @@ void KFilePreviewGenerator::orderItems(KFileItemList& items) } } +KFilePreviewGenerator::KFilePreviewGenerator(QAbstractItemView* parent, KDirSortFilterProxyModel* model) : + QObject(parent), + d(new Private(this, parent, model)) +{ +} + +KFilePreviewGenerator::~KFilePreviewGenerator() +{ + delete d; +} + +void KFilePreviewGenerator::setShowPreview(bool show) +{ + if (show && !d->m_view->iconSize().isValid()) { + // the view must provide an icon size, otherwise the showing + // off previews will get ignored + return; + } + + if (d->m_showPreview != show) { + d->m_showPreview = show; + d->m_cutItemsCache.clear(); + d->updateCutItems(); + if (show) { + updatePreviews(); + } + } + + if (show && (d->m_mimeTypeResolver != 0)) { + // don't resolve the MIME types if the preview is turned on + d->m_mimeTypeResolver->deleteLater(); + d->m_mimeTypeResolver = 0; + } else if (!show && (d->m_mimeTypeResolver == 0)) { + // the preview is turned off: resolve the MIME-types so that + // the icons gets updated + d->m_mimeTypeResolver = new KMimeTypeResolver(d->m_view, d->m_dirModel); + } +} + +bool KFilePreviewGenerator::showPreview() const +{ + return d->m_showPreview; +} + +void KFilePreviewGenerator::updatePreviews() +{ + if (!d->m_showPreview) { + return; + } + + d->killPreviewJobs(); + d->m_cutItemsCache.clear(); + d->m_pendingItems.clear(); + d->m_dispatchedItems.clear(); + + KFileItemList itemList; + const int rowCount = d->m_dirModel->rowCount(); + for (int row = 0; row < rowCount; ++row) { + const QModelIndex index = d->m_dirModel->index(row, 0); + KFileItem item = d->m_dirModel->itemForIndex(index); + itemList.append(item); + } + + d->generatePreviews(itemList); + d->updateCutItems(); +} + +void KFilePreviewGenerator::cancelPreviews() +{ + d->killPreviewJobs(); + d->m_cutItemsCache.clear(); + d->m_pendingItems.clear(); + d->m_dispatchedItems.clear(); +} + #include "kfilepreviewgenerator.moc"