• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • Contact Us
 

KFile

kfilepreviewgenerator.cpp

Go to the documentation of this file.
00001 /*******************************************************************************
00002  *   Copyright (C) 2008 by Peter Penz <peter.penz@gmx.at>                      *
00003  *                                                                             *
00004  *   This library is free software; you can redistribute it and/or             *
00005  *   modify it under the terms of the GNU Library General Public               *
00006  *   License as published by the Free Software Foundation; either              *
00007  *   version 2 of the License, or (at your option) any later version.          *
00008  *                                                                             *
00009  *   This library is distributed in the hope that it will be useful,           *
00010  *   but WITHOUT ANY WARRANTY; without even the implied warranty of            *
00011  *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU         *
00012  *   Library General Public License for more details.                          *
00013  *                                                                             *
00014  *   You should have received a copy of the GNU Library General Public License *
00015  *   along with this library; see the file COPYING.LIB.  If not, write to      *
00016  *   the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,      *
00017  *   Boston, MA 02110-1301, USA.                                               *
00018  *******************************************************************************/
00019 
00020 #include "kfilepreviewgenerator.h"
00021 
00022 #include "../kio/kio/defaultviewadapter_p.h"
00023 #include "../kio/kio/imagefilter_p.h"
00024 #include <kfileitem.h>
00025 #include <kiconeffect.h>
00026 #include <kio/previewjob.h>
00027 #include <kdirlister.h>
00028 #include <kdirmodel.h>
00029 #include <kmimetyperesolver.h>
00030 
00031 #include <QApplication>
00032 #include <QAbstractItemView>
00033 #include <QAbstractProxyModel>
00034 #include <QClipboard>
00035 #include <QColor>
00036 #include <QList>
00037 #include <QListView>
00038 #include <QPainter>
00039 #include <QPixmap>
00040 #include <QScrollBar>
00041 #include <QIcon>
00042 
00043 #ifdef Q_WS_X11
00044 #  include <QX11Info>
00045 #  include <X11/Xlib.h>
00046 #  include <X11/extensions/Xrender.h>
00047 #endif
00048 
00069 class LayoutBlocker {
00070 public:
00071     LayoutBlocker(QAbstractItemView* view) :
00072         m_uniformSizes(false),
00073         m_view(qobject_cast<QListView*>(view))
00074     {
00075         if (m_view != 0) {
00076             m_uniformSizes = m_view->uniformItemSizes();
00077             m_view->setUniformItemSizes(true);
00078         }
00079     }
00080 
00081     ~LayoutBlocker()
00082     {
00083         if (m_view != 0) {
00084             m_view->setUniformItemSizes(m_uniformSizes);
00085         }
00086     }
00087 
00088 private:
00089     bool m_uniformSizes;
00090     QListView* m_view;
00091 };
00092 
00094 class TileSet
00095 {
00096 public:
00097     enum Tile { TopLeftCorner = 0, TopSide, TopRightCorner, LeftSide, Center,
00098                 RightSide, BottomLeftCorner, BottomSide, BottomRightCorner,
00099                 NumTiles };
00100 
00101     TileSet()
00102     {
00103         QImage image(8 * 3, 8 * 3, QImage::Format_ARGB32_Premultiplied);
00104 
00105         QPainter p(&image);
00106         p.setCompositionMode(QPainter::CompositionMode_Source);
00107         p.fillRect(image.rect(), Qt::transparent);
00108         p.fillRect(image.rect().adjusted(3, 3, -3, -3), Qt::black);
00109         p.end();
00110 
00111         KIO::ImageFilter::shadowBlur(image, 3, Qt::black);
00112 
00113         QPixmap pixmap = QPixmap::fromImage(image);
00114         m_tiles[TopLeftCorner]     = pixmap.copy(0, 0, 8, 8);
00115         m_tiles[TopSide]           = pixmap.copy(8, 0, 8, 8);
00116         m_tiles[TopRightCorner]    = pixmap.copy(16, 0, 8, 8);
00117         m_tiles[LeftSide]          = pixmap.copy(0, 8, 8, 8);
00118         m_tiles[Center]            = pixmap.copy(8, 8, 8, 8);
00119         m_tiles[RightSide]         = pixmap.copy(16, 8, 8, 8);
00120         m_tiles[BottomLeftCorner]  = pixmap.copy(0, 16, 8, 8);
00121         m_tiles[BottomSide]        = pixmap.copy(8, 16, 8, 8);
00122         m_tiles[BottomRightCorner] = pixmap.copy(16, 16, 8, 8);
00123     }
00124 
00125     void paint(QPainter* p, const QRect& r)
00126     {
00127         p->drawPixmap(r.topLeft(), m_tiles[TopLeftCorner]);
00128         if (r.width() - 16 > 0) {
00129             p->drawTiledPixmap(r.x() + 8, r.y(), r.width() - 16, 8, m_tiles[TopSide]);
00130         }
00131         p->drawPixmap(r.right() - 8 + 1, r.y(), m_tiles[TopRightCorner]);
00132         if (r.height() - 16 > 0) {
00133             p->drawTiledPixmap(r.x(), r.y() + 8, 8, r.height() - 16,  m_tiles[LeftSide]);
00134             if (r.width() - 16 > 0) {
00135                 p->drawTiledPixmap(r.x() + 8, r.y() + 8, r.width() - 16, r.height() - 16, m_tiles[Center]);
00136             }
00137             p->drawTiledPixmap(r.right() - 8 + 1, r.y() + 8, 8, r.height() - 16, m_tiles[RightSide]);
00138         }
00139         p->drawPixmap(r.x(), r.bottom() - 8 + 1, m_tiles[BottomLeftCorner]);
00140         if (r.width() - 16 > 0) {
00141             p->drawTiledPixmap(r.x() + 8, r.bottom() - 8 + 1, r.width() - 16, 8, m_tiles[BottomSide]);
00142         }
00143         p->drawPixmap(r.right() - 8 + 1, r.bottom() - 8 + 1, m_tiles[BottomRightCorner]);
00144     }
00145 
00146 private:
00147     QPixmap m_tiles[NumTiles];
00148 };
00149 
00150 class KFilePreviewGenerator::Private
00151 {
00152 public:
00153     Private(KFilePreviewGenerator* parent,
00154             KAbstractViewAdapter* viewAdapter,
00155             QAbstractItemModel* model);
00156     ~Private();
00157 
00161     void generatePreviews(const KFileItemList& items);
00162 
00167     void generatePreviews(const QModelIndex& topLeft, const QModelIndex& bottomRight);
00168 
00174     void addToPreviewQueue(const KFileItem& item, const QPixmap& pixmap);
00175 
00180     void slotPreviewJobFinished(KJob* job);
00181 
00183     void updateCutItems();
00184 
00189     void dispatchPreviewQueue();
00190 
00196     void pausePreviews();
00197 
00203     void resumePreviews();
00204 
00209     bool isCutItem(const KFileItem& item) const;
00210 
00212     void applyCutItemEffect();
00213 
00218     bool applyImageFrame(QPixmap& icon);
00219 
00225     void limitToSize(QPixmap& icon, const QSize& maxSize);
00226 
00231     void startPreviewJob(const KFileItemList& items);
00232 
00234     void killPreviewJobs();
00235 
00242     void orderItems(KFileItemList& items);
00243 
00248     bool decodeIsCutSelection(const QMimeData* mimeData);
00249 
00251     struct ItemInfo
00252     {
00253         KUrl url;
00254         QPixmap pixmap;
00255     };
00256 
00261     class DataChangeObtainer
00262     {
00263     public:
00264         DataChangeObtainer(KFilePreviewGenerator::Private* generator) :
00265             m_gen(generator)  { ++m_gen->m_internalDataChange; }
00266         ~DataChangeObtainer() { --m_gen->m_internalDataChange; }
00267     private:
00268         KFilePreviewGenerator::Private* m_gen;
00269     };
00270 
00271     bool m_previewShown;
00272 
00277     bool m_clearItemQueues;
00278 
00282     bool m_hasCutSelection;
00283 
00289     int m_internalDataChange;
00290 
00291     int m_pendingVisiblePreviews;
00292 
00293     KAbstractViewAdapter* m_viewAdapter;
00294     QAbstractItemView* m_itemView;
00295     QTimer* m_previewTimer;
00296     QTimer* m_scrollAreaTimer;
00297     QList<KJob*> m_previewJobs;
00298     KDirModel* m_dirModel;
00299     QAbstractProxyModel* m_proxyModel;
00300 
00301     KMimeTypeResolver* m_mimeTypeResolver;
00302 
00303     QList<ItemInfo> m_cutItemsCache;
00304     QList<ItemInfo> m_previews;
00305 
00310     KFileItemList m_pendingItems;
00311 
00316     KFileItemList m_dispatchedItems;
00317 
00318     QStringList m_enabledPlugins;
00319 
00320     TileSet* m_tileSet;
00321 
00322 private:
00323     KFilePreviewGenerator* const q;
00324 
00325 };
00326 
00327 KFilePreviewGenerator::Private::Private(KFilePreviewGenerator* parent,
00328                                         KAbstractViewAdapter* viewAdapter,
00329                                         QAbstractItemModel* model) :
00330     m_previewShown(true),
00331     m_clearItemQueues(true),
00332     m_hasCutSelection(false),
00333     m_internalDataChange(0),
00334     m_pendingVisiblePreviews(0),
00335     m_viewAdapter(viewAdapter),
00336     m_itemView(0),
00337     m_previewTimer(0),
00338     m_scrollAreaTimer(0),
00339     m_previewJobs(),
00340     m_dirModel(0),
00341     m_proxyModel(0),
00342     m_mimeTypeResolver(0),
00343     m_cutItemsCache(),
00344     m_previews(),
00345     m_pendingItems(),
00346     m_dispatchedItems(),
00347     m_tileSet(0),
00348     q(parent)
00349 {
00350     if (!m_viewAdapter->iconSize().isValid()) {
00351         m_previewShown = false;
00352     }
00353 
00354     m_proxyModel = qobject_cast<QAbstractProxyModel*>(model);
00355     m_dirModel = (m_proxyModel == 0) ?
00356                  qobject_cast<KDirModel*>(model) :
00357                  qobject_cast<KDirModel*>(m_proxyModel->sourceModel());
00358     if (m_dirModel == 0) {
00359         // previews can only get generated for directory models
00360         m_previewShown = false;
00361     } else {
00362         connect(m_dirModel->dirLister(), SIGNAL(newItems(const KFileItemList&)),
00363                 q, SLOT(generatePreviews(const KFileItemList&)));
00364         connect(m_dirModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
00365                 q, SLOT(generatePreviews(const QModelIndex&, const QModelIndex&)));
00366     }
00367 
00368     QClipboard* clipboard = QApplication::clipboard();
00369     connect(clipboard, SIGNAL(dataChanged()),
00370             q, SLOT(updateCutItems()));
00371 
00372     m_previewTimer = new QTimer(q);
00373     m_previewTimer->setSingleShot(true);
00374     connect(m_previewTimer, SIGNAL(timeout()), q, SLOT(dispatchPreviewQueue()));
00375 
00376     // Whenever the scrollbar values have been changed, the pending previews should
00377     // be reordered in a way that the previews for the visible items are generated
00378     // first. The reordering is done with a small delay, so that during moving the
00379     // scrollbars the CPU load is kept low.
00380     m_scrollAreaTimer = new QTimer(q);
00381     m_scrollAreaTimer->setSingleShot(true);
00382     m_scrollAreaTimer->setInterval(200);
00383     connect(m_scrollAreaTimer, SIGNAL(timeout()),
00384             q, SLOT(resumePreviews()));
00385     m_viewAdapter->connect(KAbstractViewAdapter::ScrollBarValueChanged,
00386                            q, SLOT(pausePreviews()));
00387 }
00388 
00389 KFilePreviewGenerator::Private::~Private()
00390 {
00391     killPreviewJobs();
00392     m_pendingItems.clear();
00393     m_dispatchedItems.clear();
00394     if (m_mimeTypeResolver != 0) {
00395         m_mimeTypeResolver->deleteLater();
00396         m_mimeTypeResolver = 0;
00397     }
00398     delete m_tileSet;
00399 }
00400 
00401 void KFilePreviewGenerator::Private::generatePreviews(const KFileItemList& items)
00402 {
00403     applyCutItemEffect();
00404 
00405     if (!m_previewShown) {
00406         return;
00407     }
00408 
00409     KFileItemList orderedItems = items;
00410     orderItems(orderedItems);
00411 
00412     foreach (const KFileItem& item, orderedItems) {
00413         m_pendingItems.append(item);
00414     }
00415 
00416     startPreviewJob(orderedItems);
00417 }
00418 
00419 void KFilePreviewGenerator::Private::generatePreviews(const QModelIndex& topLeft,
00420                                                       const QModelIndex& bottomRight)
00421 {
00422     if (m_internalDataChange > 0) {
00423         // QAbstractItemModel::setData() has been invoked internally by the KFilePreviewGenerator.
00424         // The signal dataChanged() is connected with this method, but previews only need
00425         // to be generated when an external data change has occured.
00426         return;
00427     }
00428 
00429     KFileItemList itemList;
00430     for (int row = topLeft.row(); row <= bottomRight.row(); ++row) {
00431         const QModelIndex index = m_dirModel->index(row, 0);
00432         const KFileItem item = m_dirModel->itemForIndex(index);
00433         itemList.append(item);
00434     }
00435     generatePreviews(itemList);
00436 }
00437 
00438 void KFilePreviewGenerator::Private::addToPreviewQueue(const KFileItem& item, const QPixmap& pixmap)
00439 {
00440     if (!m_previewShown) {
00441         // the preview has been canceled in the meantime
00442         return;
00443     }
00444     const KUrl url = item.url();
00445 
00446     // check whether the item is part of the directory lister (it is possible
00447     // that a preview from an old directory lister is received)
00448     KDirLister* dirLister = m_dirModel->dirLister();
00449     bool isOldPreview = true;
00450     const KUrl::List dirs = dirLister->directories();
00451     const QString itemDir = url.directory();
00452     foreach (const KUrl& url, dirs) {
00453         if (url.path() == itemDir) {
00454             isOldPreview = false;
00455             break;
00456         }
00457     }
00458     if (isOldPreview) {
00459         return;
00460     }
00461 
00462     QPixmap icon = pixmap;
00463 
00464     const QString mimeType = item.mimetype();
00465     const QString mimeTypeGroup = mimeType.left(mimeType.indexOf('/'));
00466     if ((mimeTypeGroup != "image") || !applyImageFrame(icon)) {
00467         limitToSize(icon, m_viewAdapter->iconSize());
00468     }
00469 
00470     if (m_hasCutSelection && isCutItem(item)) {
00471         // Remember the current icon in the cache for cut items before
00472         // the disabled effect is applied. This makes it possible restoring
00473         // the uncut version again when cutting other items.
00474         QList<ItemInfo>::iterator begin = m_cutItemsCache.begin();
00475         QList<ItemInfo>::iterator end   = m_cutItemsCache.end();
00476         for (QList<ItemInfo>::iterator it = begin; it != end; ++it) {
00477             if ((*it).url == item.url()) {
00478                 (*it).pixmap = icon;
00479                 break;
00480             }
00481         }
00482 
00483         // apply the disabled effect to the icon for marking it as "cut item"
00484         // and apply the icon to the item
00485         KIconEffect iconEffect;
00486         icon = iconEffect.apply(icon, KIconLoader::Desktop, KIconLoader::DisabledState);
00487     }
00488 
00489     // remember the preview and URL, so that it can be applied to the model
00490     // in KFilePreviewGenerator::dispatchPreviewQueue()
00491     ItemInfo preview;
00492     preview.url = url;
00493     preview.pixmap = icon;
00494     m_previews.append(preview);
00495 
00496     m_dispatchedItems.append(item);
00497 }
00498 
00499 void KFilePreviewGenerator::Private::slotPreviewJobFinished(KJob* job)
00500 {
00501     const int index = m_previewJobs.indexOf(job);
00502     m_previewJobs.removeAt(index);
00503 
00504     if ((m_previewJobs.count() == 0) && m_clearItemQueues) {
00505         m_pendingItems.clear();
00506         m_dispatchedItems.clear();
00507         m_pendingVisiblePreviews = 0;
00508         QMetaObject::invokeMethod(q, "dispatchPreviewQueue", Qt::QueuedConnection);
00509     }
00510 }
00511 
00512 void KFilePreviewGenerator::Private::updateCutItems()
00513 {
00514     DataChangeObtainer obt(this);
00515 
00516     // restore the icons of all previously selected items to the
00517     // original state...
00518     foreach (const ItemInfo& cutItem, m_cutItemsCache) {
00519         const QModelIndex index = m_dirModel->indexForUrl(cutItem.url);
00520         if (index.isValid()) {
00521             m_dirModel->setData(index, QIcon(cutItem.pixmap), Qt::DecorationRole);
00522         }
00523     }
00524     m_cutItemsCache.clear();
00525 
00526     // ... and apply an item effect to all currently cut items
00527     applyCutItemEffect();
00528 }
00529 
00530 void KFilePreviewGenerator::Private::dispatchPreviewQueue()
00531 {
00532     const int previewsCount = m_previews.count();
00533     if (previewsCount > 0) {
00534         // Applying the previews to the model must be done step by step
00535         // in larger blocks: Applying a preview immediately when getting the signal
00536         // 'gotPreview()' from the PreviewJob is too expensive, as a relayout
00537         // of the view would be triggered for each single preview.
00538         LayoutBlocker blocker(m_itemView);
00539         DataChangeObtainer obt(this);
00540         for (int i = 0; i < previewsCount; ++i) {
00541             const ItemInfo& preview = m_previews.first();
00542 
00543             const QModelIndex idx = m_dirModel->indexForUrl(preview.url);
00544             if (idx.isValid() && (idx.column() == 0)) {
00545                 m_dirModel->setData(idx, QIcon(preview.pixmap), Qt::DecorationRole);
00546             }
00547 
00548             m_previews.pop_front();
00549             if (m_pendingVisiblePreviews > 0) {
00550                 --m_pendingVisiblePreviews;
00551             }
00552         }
00553     }
00554 
00555     if (m_pendingVisiblePreviews > 0) {
00556         // As long as there are pending previews for visible items, poll
00557         // the preview queue each 200 ms. If there are no pending previews,
00558         // the queue is dispatched in slotPreviewJobFinished().
00559         m_previewTimer->start(200);
00560     }
00561 }
00562 
00563 void KFilePreviewGenerator::Private::pausePreviews()
00564 {
00565     foreach (KJob* job, m_previewJobs) {
00566         Q_ASSERT(job != 0);
00567         job->suspend();
00568     }
00569     m_scrollAreaTimer->start();
00570 }
00571 
00572 void KFilePreviewGenerator::Private::resumePreviews()
00573 {
00574     // Before creating new preview jobs the m_pendingItems queue must be
00575     // cleaned up by removing the already dispatched items. Implementation
00576     // note: The order of the m_dispatchedItems queue and the m_pendingItems
00577     // queue is usually equal. So even when having a lot of elements the
00578     // nested loop is no performance bottle neck, as the inner loop is only
00579     // entered once in most cases.
00580     foreach (const KFileItem& item, m_dispatchedItems) {
00581         KFileItemList::iterator begin = m_pendingItems.begin();
00582         KFileItemList::iterator end   = m_pendingItems.end();
00583         for (KFileItemList::iterator it = begin; it != end; ++it) {
00584             if ((*it).url() == item.url()) {
00585                 m_pendingItems.erase(it);
00586                 break;
00587             }
00588         }
00589     }
00590     m_dispatchedItems.clear();
00591 
00592     m_pendingVisiblePreviews = 0;
00593     dispatchPreviewQueue();
00594 
00595     KFileItemList orderedItems = m_pendingItems;
00596     orderItems(orderedItems);
00597 
00598     // Kill all suspended preview jobs. Usually when a preview job
00599     // has been finished, slotPreviewJobFinished() clears all item queues.
00600     // This is not wanted in this case, as a new job is created afterwards
00601     // for m_pendingItems.
00602     m_clearItemQueues = false;
00603     killPreviewJobs();
00604     m_clearItemQueues = true;
00605 
00606     startPreviewJob(orderedItems);
00607 }
00608 
00609 bool KFilePreviewGenerator::Private::isCutItem(const KFileItem& item) const
00610 {
00611     const QMimeData* mimeData = QApplication::clipboard()->mimeData();
00612     const KUrl::List cutUrls = KUrl::List::fromMimeData(mimeData);
00613 
00614     const KUrl itemUrl = item.url();
00615     foreach (const KUrl& url, cutUrls) {
00616         if (url == itemUrl) {
00617             return true;
00618         }
00619     }
00620 
00621     return false;
00622 }
00623 
00624 void KFilePreviewGenerator::Private::applyCutItemEffect()
00625 {
00626     const QMimeData* mimeData = QApplication::clipboard()->mimeData();
00627     m_hasCutSelection = decodeIsCutSelection(mimeData);
00628     if (!m_hasCutSelection) {
00629         return;
00630     }
00631 
00632     KFileItemList items;
00633     KDirLister* dirLister = m_dirModel->dirLister();
00634     const KUrl::List dirs = dirLister->directories();
00635     foreach (const KUrl& url, dirs) {
00636         items << dirLister->itemsForDir(url);
00637     }
00638 
00639     DataChangeObtainer obt(this);
00640     foreach (const KFileItem& item, items) {
00641         if (isCutItem(item)) {
00642             const QModelIndex index = m_dirModel->indexForItem(item);
00643             const QVariant value = m_dirModel->data(index, Qt::DecorationRole);
00644             if (value.type() == QVariant::Icon) {
00645                 const QIcon icon(qvariant_cast<QIcon>(value));
00646                 const QSize actualSize = icon.actualSize(m_viewAdapter->iconSize());
00647                 QPixmap pixmap = icon.pixmap(actualSize);
00648 
00649                 // remember current pixmap for the item to be able
00650                 // to restore it when other items get cut
00651                 ItemInfo cutItem;
00652                 cutItem.url = item.url();
00653                 cutItem.pixmap = pixmap;
00654                 m_cutItemsCache.append(cutItem);
00655 
00656                 // apply icon effect to the cut item
00657                 KIconEffect iconEffect;
00658                 pixmap = iconEffect.apply(pixmap, KIconLoader::Desktop, KIconLoader::DisabledState);
00659                 m_dirModel->setData(index, QIcon(pixmap), Qt::DecorationRole);
00660             }
00661         }
00662     }
00663 }
00664 
00665 bool KFilePreviewGenerator::Private::applyImageFrame(QPixmap& icon)
00666 {
00667     const QSize maxSize = m_viewAdapter->iconSize();
00668     const bool applyFrame = (maxSize.width()  > KIconLoader::SizeSmallMedium) &&
00669                             (maxSize.height() > KIconLoader::SizeSmallMedium) &&
00670                             ((icon.width()  > KIconLoader::SizeLarge) ||
00671                              (icon.height() > KIconLoader::SizeLarge));
00672     if (!applyFrame) {
00673         // the maximum size or the image itself is too small for a frame
00674         return false;
00675     }
00676 
00677     const int tloffset = 2;
00678     const int broffset = 4;
00679     const int doubleFrame = tloffset + broffset;
00680 
00681     // resize the icon to the maximum size minus the space required for the frame
00682     limitToSize(icon, QSize(maxSize.width() - doubleFrame, maxSize.height() - doubleFrame));
00683 
00684     if (!m_tileSet) {
00685         m_tileSet = new TileSet;
00686     }
00687 
00688     QPixmap framedIcon(icon.size().width() + doubleFrame, icon.size().height() + doubleFrame);
00689     framedIcon.fill(Qt::transparent);
00690 
00691     QPainter painter;
00692     painter.begin(&framedIcon);
00693     painter.setCompositionMode(QPainter::CompositionMode_Source);
00694     m_tileSet->paint(&painter, framedIcon.rect());
00695     painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
00696     painter.drawPixmap(tloffset, tloffset, icon);
00697     painter.end();
00698 
00699     icon = framedIcon;
00700     return true;
00701 }
00702 
00703 void KFilePreviewGenerator::Private::limitToSize(QPixmap& icon, const QSize& maxSize)
00704 {
00705     if ((icon.width() > maxSize.width()) || (icon.height() > maxSize.height())) {
00706 #ifdef Q_WS_X11
00707         // Assume that the texture size limit is 2048x2048
00708         if ((icon.width() <= 2048) && (icon.height() <= 2048) && icon.x11PictureHandle()) {
00709             QSize size = icon.size();
00710             size.scale(maxSize, Qt::KeepAspectRatio);
00711 
00712             const qreal factor = size.width() / qreal(icon.width());
00713 
00714             XTransform xform = {{
00715                 { XDoubleToFixed(1 / factor), 0, 0 },
00716                 { 0, XDoubleToFixed(1 / factor), 0 },
00717                 { 0, 0, XDoubleToFixed(1) }
00718             }};
00719 
00720             QPixmap pixmap(size);
00721             pixmap.fill(Qt::transparent);
00722 
00723             Display* dpy = QX11Info::display();
00724             XRenderSetPictureFilter(dpy, icon.x11PictureHandle(), FilterBilinear, 0, 0);
00725             XRenderSetPictureTransform(dpy, icon.x11PictureHandle(), &xform);
00726             XRenderComposite(dpy, PictOpOver, icon.x11PictureHandle(), None, pixmap.x11PictureHandle(),
00727                              0, 0, 0, 0, 0, 0, pixmap.width(), pixmap.height());
00728             icon = pixmap;
00729         } else {
00730             icon = icon.scaled(maxSize, Qt::KeepAspectRatio, Qt::FastTransformation);
00731         }
00732 #else
00733         icon = icon.scaled(maxSize, Qt::KeepAspectRatio, Qt::FastTransformation);
00734 #endif
00735     }
00736 }
00737 
00738 void KFilePreviewGenerator::Private::startPreviewJob(const KFileItemList& items)
00739 {
00740     if (items.count() == 0) {
00741         return;
00742     }
00743 
00744     const QMimeData* mimeData = QApplication::clipboard()->mimeData();
00745     m_hasCutSelection = decodeIsCutSelection(mimeData);
00746 
00747     const QSize size = m_viewAdapter->iconSize();
00748 
00749     // PreviewJob internally caches items always with the size of
00750     // 128 x 128 pixels or 256 x 256 pixels. A downscaling is done
00751     // by PreviewJob if a smaller size is requested. As the KFilePreviewGenerator must
00752     // do a downscaling anyhow because of the frame, only the provided
00753     // cache sizes are requested.
00754     const int cacheSize = (size.width() > 128) || (size.height() > 128) ? 256 : 128;
00755     KIO::PreviewJob* job = KIO::filePreview(items, cacheSize, cacheSize, 0, 70, true, true, &m_enabledPlugins);
00756     connect(job, SIGNAL(gotPreview(const KFileItem&, const QPixmap&)),
00757             q, SLOT(addToPreviewQueue(const KFileItem&, const QPixmap&)));
00758     connect(job, SIGNAL(finished(KJob*)),
00759             q, SLOT(slotPreviewJobFinished(KJob*)));
00760 
00761     m_previewJobs.append(job);
00762     m_previewTimer->start(200);
00763 }
00764 
00765 void KFilePreviewGenerator::Private::killPreviewJobs()
00766 {
00767     foreach (KJob* job, m_previewJobs) {
00768         Q_ASSERT(job != 0);
00769         job->kill();
00770     }
00771     m_previewJobs.clear();
00772 }
00773 
00774 void KFilePreviewGenerator::Private::orderItems(KFileItemList& items)
00775 {
00776     // Order the items in a way that the preview for the visible items
00777     // is generated first, as this improves the feeled performance a lot.
00778     //
00779     // Implementation note: 2 different algorithms are used for the sorting.
00780     // Algorithm 1 is faster when having a lot of items in comparison
00781     // to the number of rows in the model. Algorithm 2 is faster
00782     // when having quite less items in comparison to the number of rows in
00783     // the model. Choosing the right algorithm is important when having directories
00784     // with several hundreds or thousands of items.
00785 
00786     const bool hasProxy = (m_proxyModel != 0);
00787     const int itemCount = items.count();
00788     const int rowCount = hasProxy ? m_proxyModel->rowCount() : m_dirModel->rowCount();
00789     const QRect visibleArea = m_viewAdapter->visibleArea();
00790 
00791     QModelIndex dirIndex;
00792     QRect itemRect;
00793     int insertPos = 0;
00794     if (itemCount * 10 > rowCount) {
00795         // Algorithm 1: The number of items is > 10 % of the row count. Parse all rows
00796         // and check whether the received row is part of the item list.
00797         for (int row = 0; row < rowCount; ++row) {
00798             if (hasProxy) {
00799                 const QModelIndex proxyIndex = m_proxyModel->index(row, 0);
00800                 itemRect = m_viewAdapter->visualRect(proxyIndex);
00801                 dirIndex = m_proxyModel->mapToSource(proxyIndex);
00802             } else {
00803                 dirIndex = m_dirModel->index(row, 0);
00804                 itemRect = m_viewAdapter->visualRect(dirIndex);
00805             }
00806 
00807             KFileItem item = m_dirModel->itemForIndex(dirIndex);  // O(1)
00808             const KUrl url = item.url();
00809 
00810             // check whether the item is part of the item list 'items'
00811             int index = -1;
00812             for (int i = 0; i < itemCount; ++i) {
00813                 if (items.at(i).url() == url) {
00814                     index = i;
00815                     break;
00816                 }
00817             }
00818 
00819             if ((index > 0) && itemRect.intersects(visibleArea)) {
00820                 // The current item is (at least partly) visible. Move it
00821                 // to the front of the list, so that the preview is
00822                 // generated earlier.
00823                 items.removeAt(index);
00824                 items.insert(insertPos, item);
00825                 ++insertPos;
00826                 ++m_pendingVisiblePreviews;
00827             }
00828         }
00829     } else {
00830         // Algorithm 2: The number of items is <= 10 % of the row count. In this case iterate
00831         // all items and receive the corresponding row from the item.
00832         for (int i = 0; i < itemCount; ++i) {
00833             dirIndex = m_dirModel->indexForItem(items.at(i)); // O(n) (n = number of rows)
00834             if (hasProxy) {
00835                 const QModelIndex proxyIndex = m_proxyModel->mapFromSource(dirIndex);
00836                 itemRect = m_viewAdapter->visualRect(proxyIndex);
00837             } else {
00838                 itemRect = m_viewAdapter->visualRect(dirIndex);
00839             }
00840 
00841             if (itemRect.intersects(visibleArea)) {
00842                 // The current item is (at least partly) visible. Move it
00843                 // to the front of the list, so that the preview is
00844                 // generated earlier.
00845                 items.insert(insertPos, items.at(i));
00846                 items.removeAt(i + 1);
00847                 ++insertPos;
00848                 ++m_pendingVisiblePreviews;
00849             }
00850         }
00851     }
00852 }
00853 
00854 bool KFilePreviewGenerator::Private::decodeIsCutSelection(const QMimeData* mimeData)
00855 {
00856     const QByteArray data = mimeData->data("application/x-kde-cutselection");
00857     if (data.isEmpty()) {
00858         return false;
00859     } else {
00860         return data.at(0) == '1';
00861     }
00862 }
00863 
00864 KFilePreviewGenerator::KFilePreviewGenerator(QAbstractItemView* parent) :
00865     QObject(parent),
00866     d(new Private(this, new KIO::DefaultViewAdapter(parent, this), parent->model()))
00867 {
00868     d->m_itemView = parent;
00869 }
00870 
00871 KFilePreviewGenerator::KFilePreviewGenerator(KAbstractViewAdapter* parent, QAbstractProxyModel* model) :
00872     QObject(parent),
00873     d(new Private(this, parent, model))
00874 {
00875 }
00876 
00877 KFilePreviewGenerator::~KFilePreviewGenerator()
00878 {
00879     delete d;
00880 }
00881 
00882 void KFilePreviewGenerator::setPreviewShown(bool show)
00883 {
00884     if (show && (!d->m_viewAdapter->iconSize().isValid() || (d->m_dirModel == 0))) {
00885         // the view must provide an icon size and a directory model,
00886         // otherwise the showing the previews will get ignored
00887         return;
00888     }
00889 
00890     if (d->m_previewShown != show) {
00891         d->m_previewShown = show;
00892         d->m_cutItemsCache.clear();
00893         d->updateCutItems();
00894         if (show) {
00895             updatePreviews();
00896         }
00897     }
00898 
00899     if (show && (d->m_mimeTypeResolver != 0)) {
00900         // don't resolve the MIME types if the preview is turned on
00901         d->m_mimeTypeResolver->deleteLater();
00902         d->m_mimeTypeResolver = 0;
00903     } else if (!show && (d->m_mimeTypeResolver == 0)) {
00904         // the preview is turned off: resolve the MIME-types so that
00905         // the icons gets updated
00906         d->m_mimeTypeResolver = new KMimeTypeResolver(d->m_viewAdapter);
00907     }
00908 }
00909 
00910 bool KFilePreviewGenerator::isPreviewShown() const
00911 {
00912     return d->m_previewShown;
00913 }
00914 
00915 void KFilePreviewGenerator::updatePreviews()
00916 {
00917     if (!d->m_previewShown) {
00918         return;
00919     }
00920 
00921     d->killPreviewJobs();
00922     d->m_cutItemsCache.clear();
00923     d->m_pendingItems.clear();
00924     d->m_dispatchedItems.clear();
00925 
00926     KFileItemList itemList;
00927     const int rowCount = d->m_dirModel->rowCount();
00928     for (int row = 0; row < rowCount; ++row) {
00929         const QModelIndex index = d->m_dirModel->index(row, 0);
00930         KFileItem item = d->m_dirModel->itemForIndex(index);
00931         itemList.append(item);
00932     }
00933 
00934     d->generatePreviews(itemList);
00935     d->updateCutItems();
00936 }
00937 
00938 void KFilePreviewGenerator::cancelPreviews()
00939 {
00940     d->killPreviewJobs();
00941     d->m_cutItemsCache.clear();
00942     d->m_pendingItems.clear();
00943     d->m_dispatchedItems.clear();
00944 }
00945 
00946 void KFilePreviewGenerator::setEnabledPlugins(const QStringList& plugins)
00947 {
00948     d->m_enabledPlugins = plugins;
00949 }
00950 
00951 QStringList KFilePreviewGenerator::enabledPlugins() const
00952 {
00953     return d->m_enabledPlugins;
00954 }
00955 
00956 #include "kfilepreviewgenerator.moc"

KFile

Skip menu "KFile"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.7
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal