#include "kfilepreviewgenerator.h"
+#include <kabstractviewadapter_p.h>
+#include <kfileitem.h>
#include <kiconeffect.h>
#include <kio/previewjob.h>
#include <kdirlister.h>
#include <kdirmodel.h>
-#include <kdirsortfilterproxymodel.h>
#include <kmimetyperesolver.h>
#include <konqmimedata.h>
#include <QApplication>
#include <QAbstractItemView>
+#include <QAbstractProxyModel>
#include <QClipboard>
#include <QColor>
+#include <QList>
#include <QListView>
#include <QPainter>
+#include <QPixmap>
#include <QScrollBar>
#include <QIcon>
+#ifdef Q_WS_X11
+# include <QX11Info>
+# include <X11/Xlib.h>
+# include <X11/extensions/Xrender.h>
+#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
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<KJob*> m_previewJobs;
+ KDirModel* m_dirModel;
+ QAbstractProxyModel* m_proxyModel;
+
+ QObject* m_mimeTypeResolver;
+
+ QList<ItemInfo> m_cutItemsCache;
+ QList<ItemInfo> 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(),
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<KDirModel*>(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();
}
}
-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();
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
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)) {
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);
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...
applyCutItemEffect();
}
-void KFilePreviewGenerator::dispatchPreviewQueue()
+void KFilePreviewGenerator::Private::dispatchPreviewQueue()
{
const int previewsCount = m_previews.count();
if (previewsCount > 0) {
// 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();
}
}
-void KFilePreviewGenerator::pausePreviews()
+void KFilePreviewGenerator::Private::pausePreviews()
{
foreach (KJob* job, m_previewJobs) {
Q_ASSERT(job != 0);
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
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);
return false;
}
-void KFilePreviewGenerator::applyCutItemEffect()
+void KFilePreviewGenerator::Private::applyCutItemEffect()
{
const QMimeData* mimeData = QApplication::clipboard()->mimeData();
m_hasCutSelection = KonqMimeData::decodeIsCutSelection(mimeData);
const QVariant value = m_dirModel->data(index, Qt::DecorationRole);
if (value.type() == QVariant::Icon) {
const QIcon icon(qvariant_cast<QIcon>(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
}
}
-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) ||
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;
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;
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
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);
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.
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) {
// 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)
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
}
}
+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"