X-Git-Url: https://cloud.milkyroute.net/gitweb/dolphin.git/blobdiff_plain/4d886d083ca6cb2d635da2d29efb804b2680b6de..73f095cc5ebe64aeec6ec1ffaa9878f5ebc1fff4:/src/kfilepreviewgenerator.cpp diff --git a/src/kfilepreviewgenerator.cpp b/src/kfilepreviewgenerator.cpp index e81f66980..cb93a7f07 100644 --- a/src/kfilepreviewgenerator.cpp +++ b/src/kfilepreviewgenerator.cpp @@ -19,23 +19,91 @@ #include "kfilepreviewgenerator.h" +#include +#include #include #include #include #include -#include #include #include #include #include +#include #include #include +#include #include #include +#include #include #include +#ifdef Q_WS_X11 +# include +# include +# include +#endif + +/** + * Implementation of the view adapter for the default case when + * an instance of QAbstractItemView is used as view. + */ +class DefaultViewAdapter : public KAbstractViewAdapter +{ +public: + DefaultViewAdapter(QAbstractItemView* view, QObject* parent); + virtual QObject* createMimeTypeResolver(KDirModel* model) const; + virtual QSize iconSize() const; + virtual QPalette palette() const; + virtual QRect visibleArea() const; + virtual QRect visualRect(const QModelIndex& index) const; + virtual void connect(Signal signal, QObject* receiver, const char* slot); + +private: + QAbstractItemView* m_view; +}; + +DefaultViewAdapter::DefaultViewAdapter(QAbstractItemView* view, QObject* parent) : + KAbstractViewAdapter(parent), + m_view(view) +{ +} + +QObject* DefaultViewAdapter::createMimeTypeResolver(KDirModel* model) const +{ + return new KMimeTypeResolver(m_view, model); +} + +QSize DefaultViewAdapter::iconSize() const +{ + return m_view->iconSize(); +} + +QPalette DefaultViewAdapter::palette() const +{ + return m_view->palette(); +} + +QRect DefaultViewAdapter::visibleArea() const +{ + return m_view->viewport()->rect(); +} + +QRect DefaultViewAdapter::visualRect(const QModelIndex& index) const +{ + return m_view->visualRect(index); +} + +void DefaultViewAdapter::connect(Signal signal, QObject* receiver, const char* slot) +{ + if (signal == ScrollBarValueChanged) { + QObject::connect(m_view->horizontalScrollBar(), SIGNAL(valueChanged(int)), receiver, slot); + QObject::connect(m_view->verticalScrollBar(), SIGNAL(valueChanged(int)), receiver, slot); + } +} + /** * If the passed item view is an instance of QListView, expensive * layout operations are blocked in the constructor and are unblocked @@ -80,13 +148,155 @@ private: QListView* m_view; }; -KFilePreviewGenerator::KFilePreviewGenerator(QAbstractItemView* parent, KDirSortFilterProxyModel* model) : - QObject(parent), - m_showPreview(false), +class KFilePreviewGenerator::Private +{ +public: + Private(KFilePreviewGenerator* parent, + KAbstractViewAdapter* viewAdapter, + QAbstractProxyModel* 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; + + KAbstractViewAdapter* m_viewAdapter; + QAbstractItemView* m_itemView; + QTimer* m_previewTimer; + QTimer* m_scrollAreaTimer; + QList m_previewJobs; + KDirModel* m_dirModel; + QAbstractProxyModel* m_proxyModel; + + QObject* 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, + KAbstractViewAdapter* viewAdapter, + QAbstractProxyModel* model) : + m_showPreview(true), m_clearItemQueues(true), m_hasCutSelection(false), m_pendingVisiblePreviews(0), - m_view(parent), + m_viewAdapter(viewAdapter), + m_itemView(0), m_previewTimer(0), m_scrollAreaTimer(0), m_previewJobs(), @@ -96,39 +306,40 @@ 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_viewAdapter->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())); - connect(m_view->horizontalScrollBar(), SIGNAL(valueChanged(int)), - this, SLOT(pausePreviews())); - connect(m_view->verticalScrollBar(), SIGNAL(valueChanged(int)), - this, SLOT(pausePreviews())); + q, SLOT(resumePreviews())); + m_viewAdapter->connect(KAbstractViewAdapter::ScrollBarValueChanged, + q, SLOT(pausePreviews())); } -KFilePreviewGenerator::~KFilePreviewGenerator() -{ +KFilePreviewGenerator::Private::~Private() +{ killPreviewJobs(); m_pendingItems.clear(); m_dispatchedItems.clear(); @@ -138,60 +349,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 +367,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 @@ -238,7 +396,7 @@ void KFilePreviewGenerator::addToPreviewQueue(const KFileItem& item, const QPixm const QString mimeType = item.mimetype(); const QString mimeTypeGroup = mimeType.left(mimeType.indexOf('/')); if ((mimeTypeGroup != "image") || !applyImageFrame(icon)) { - limitToSize(icon, m_view->iconSize()); + limitToSize(icon, m_viewAdapter->iconSize()); } if (m_hasCutSelection && isCutItem(item)) { @@ -270,7 +428,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 +437,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 +457,7 @@ void KFilePreviewGenerator::updateCutItems() applyCutItemEffect(); } -void KFilePreviewGenerator::dispatchPreviewQueue() +void KFilePreviewGenerator::Private::dispatchPreviewQueue() { const int previewsCount = m_previews.count(); if (previewsCount > 0) { @@ -307,7 +465,7 @@ void KFilePreviewGenerator::dispatchPreviewQueue() // in larger blocks: Applying a preview immediately when getting the signal // 'gotPreview()' from the PreviewJob is too expensive, as a relayout // of the view would be triggered for each single preview. - LayoutBlocker blocker(m_view); + LayoutBlocker blocker(m_itemView); for (int i = 0; i < previewsCount; ++i) { const ItemInfo& preview = m_previews.first(); @@ -331,7 +489,7 @@ void KFilePreviewGenerator::dispatchPreviewQueue() } } -void KFilePreviewGenerator::pausePreviews() +void KFilePreviewGenerator::Private::pausePreviews() { foreach (KJob* job, m_previewJobs) { Q_ASSERT(job != 0); @@ -340,7 +498,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 +535,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 +550,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); @@ -413,7 +571,7 @@ void KFilePreviewGenerator::applyCutItemEffect() const QVariant value = m_dirModel->data(index, Qt::DecorationRole); if (value.type() == QVariant::Icon) { const QIcon icon(qvariant_cast(value)); - const QSize actualSize = icon.actualSize(m_view->iconSize()); + const QSize actualSize = icon.actualSize(m_viewAdapter->iconSize()); QPixmap pixmap = icon.pixmap(actualSize); // remember current pixmap for the item to be able @@ -432,9 +590,9 @@ void KFilePreviewGenerator::applyCutItemEffect() } } -bool KFilePreviewGenerator::applyImageFrame(QPixmap& icon) +bool KFilePreviewGenerator::Private::applyImageFrame(QPixmap& icon) { - const QSize maxSize = m_view->iconSize(); + const QSize maxSize = m_viewAdapter->iconSize(); const bool applyFrame = (maxSize.width() > KIconLoader::SizeSmallMedium) && (maxSize.height() > KIconLoader::SizeSmallMedium) && ((icon.width() > KIconLoader::SizeLarge) || @@ -451,7 +609,7 @@ bool KFilePreviewGenerator::applyImageFrame(QPixmap& icon) limitToSize(icon, QSize(maxSize.width() - doubleFrame, maxSize.height() - doubleFrame)); QPainter painter; - const QPalette palette = m_view->palette(); + const QPalette palette = m_viewAdapter->palette(); QPixmap framedIcon(icon.size().width() + doubleFrame, icon.size().height() + doubleFrame); framedIcon.fill(palette.color(QPalette::Normal, QPalette::Base)); const int width = framedIcon.width() - 1; @@ -482,14 +640,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; @@ -498,7 +684,7 @@ void KFilePreviewGenerator::startPreviewJob(const KFileItemList& items) const QMimeData* mimeData = QApplication::clipboard()->mimeData(); m_hasCutSelection = KonqMimeData::decodeIsCutSelection(mimeData); - const QSize size = m_view->iconSize(); + const QSize size = m_viewAdapter->iconSize(); // PreviewJob internally caches items always with the size of // 128 x 128 pixels or 256 x 256 pixels. A downscaling is done @@ -508,15 +694,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 +711,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. @@ -539,7 +725,7 @@ void KFilePreviewGenerator::orderItems(KFileItemList& items) const int itemCount = items.count(); const int rowCount = m_proxyModel->rowCount(); - const QRect visibleArea = m_view->viewport()->rect(); + const QRect visibleArea = m_viewAdapter->visibleArea(); int insertPos = 0; if (itemCount * 10 > rowCount) { @@ -547,7 +733,7 @@ void KFilePreviewGenerator::orderItems(KFileItemList& items) // and check whether the received row is part of the item list. for (int row = 0; row < rowCount; ++row) { const QModelIndex proxyIndex = m_proxyModel->index(row, 0); - const QRect itemRect = m_view->visualRect(proxyIndex); + const QRect itemRect = m_viewAdapter->visualRect(proxyIndex); const QModelIndex dirIndex = m_proxyModel->mapToSource(proxyIndex); KFileItem item = m_dirModel->itemForIndex(dirIndex); // O(1) @@ -578,7 +764,7 @@ void KFilePreviewGenerator::orderItems(KFileItemList& items) for (int i = 0; i < itemCount; ++i) { const QModelIndex dirIndex = m_dirModel->indexForItem(items.at(i)); // O(n) (n = number of rows) const QModelIndex proxyIndex = m_proxyModel->mapFromSource(dirIndex); - const QRect itemRect = m_view->visualRect(proxyIndex); + const QRect itemRect = m_viewAdapter->visualRect(proxyIndex); if (itemRect.intersects(visibleArea)) { // The current item is (at least partly) visible. Move it @@ -593,4 +779,86 @@ void KFilePreviewGenerator::orderItems(KFileItemList& items) } } +KFilePreviewGenerator::KFilePreviewGenerator(QAbstractItemView* parent, QAbstractProxyModel* model) : + QObject(parent), + d(new Private(this, new DefaultViewAdapter(parent, this), model)) +{ + d->m_itemView = parent; +} + +KFilePreviewGenerator::KFilePreviewGenerator(KAbstractViewAdapter* parent, QAbstractProxyModel* model) : + QObject(parent), + d(new Private(this, parent, model)) +{ +} + +KFilePreviewGenerator::~KFilePreviewGenerator() +{ + delete d; +} + +void KFilePreviewGenerator::setShowPreview(bool show) +{ + if (show && !d->m_viewAdapter->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 = d->m_viewAdapter->createMimeTypeResolver(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"