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

KFile

kfilepreviewgenerator.cpp

Go to the documentation of this file.
00001 /*******************************************************************************
00002  *   Copyright (C) 2008-2009 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 <config.h> // for HAVE_XRENDER
00025 #include <kconfiggroup.h>
00026 #include <kfileitem.h>
00027 #include <kiconeffect.h>
00028 #include <kio/previewjob.h>
00029 #include <kdirlister.h>
00030 #include <kdirmodel.h>
00031 #include <ksharedconfig.h>
00032 
00033 #include <QApplication>
00034 #include <QAbstractItemView>
00035 #include <QAbstractProxyModel>
00036 #include <QClipboard>
00037 #include <QColor>
00038 #include <QHash>
00039 #include <QList>
00040 #include <QListView>
00041 #include <QPainter>
00042 #include <QPixmap>
00043 #include <QScrollBar>
00044 #include <QIcon>
00045 
00046 #if defined(Q_WS_X11) && defined(HAVE_XRENDER)
00047 #  include <QX11Info>
00048 #  include <X11/Xlib.h>
00049 #  include <X11/extensions/Xrender.h>
00050 #endif
00051 
00072 class LayoutBlocker
00073 {
00074 public:
00075     LayoutBlocker(QAbstractItemView* view) :
00076         m_uniformSizes(false),
00077         m_view(qobject_cast<QListView*>(view))
00078     {
00079         if (m_view != 0) {
00080             m_uniformSizes = m_view->uniformItemSizes();
00081             m_view->setUniformItemSizes(true);
00082         }
00083     }
00084 
00085     ~LayoutBlocker()
00086     {
00087         if (m_view != 0) {
00088             m_view->setUniformItemSizes(m_uniformSizes);
00089         }
00090     }
00091 
00092 private:
00093     bool m_uniformSizes;
00094     QListView* m_view;
00095 };
00096 
00098 class TileSet
00099 {
00100 public:
00101     enum { LeftMargin = 3, TopMargin = 2, RightMargin = 3, BottomMargin = 4 };
00102 
00103     enum Tile { TopLeftCorner = 0, TopSide, TopRightCorner, LeftSide,
00104                 RightSide, BottomLeftCorner, BottomSide, BottomRightCorner,
00105                 NumTiles };
00106 
00107     TileSet()
00108     {
00109         QImage image(8 * 3, 8 * 3, QImage::Format_ARGB32_Premultiplied);
00110 
00111         QPainter p(&image);
00112         p.setCompositionMode(QPainter::CompositionMode_Source);
00113         p.fillRect(image.rect(), Qt::transparent);
00114         p.fillRect(image.rect().adjusted(3, 3, -3, -3), Qt::black);
00115         p.end();
00116 
00117         KIO::ImageFilter::shadowBlur(image, 3, Qt::black);
00118 
00119         QPixmap pixmap = QPixmap::fromImage(image);
00120         m_tiles[TopLeftCorner]     = pixmap.copy(0, 0, 8, 8);
00121         m_tiles[TopSide]           = pixmap.copy(8, 0, 8, 8);
00122         m_tiles[TopRightCorner]    = pixmap.copy(16, 0, 8, 8);
00123         m_tiles[LeftSide]          = pixmap.copy(0, 8, 8, 8);
00124         m_tiles[RightSide]         = pixmap.copy(16, 8, 8, 8);
00125         m_tiles[BottomLeftCorner]  = pixmap.copy(0, 16, 8, 8);
00126         m_tiles[BottomSide]        = pixmap.copy(8, 16, 8, 8);
00127         m_tiles[BottomRightCorner] = pixmap.copy(16, 16, 8, 8);
00128     }
00129 
00130     void paint(QPainter* p, const QRect& r)
00131     {
00132         p->drawPixmap(r.topLeft(), m_tiles[TopLeftCorner]);
00133         if (r.width() - 16 > 0) {
00134             p->drawTiledPixmap(r.x() + 8, r.y(), r.width() - 16, 8, m_tiles[TopSide]);
00135         }
00136         p->drawPixmap(r.right() - 8 + 1, r.y(), m_tiles[TopRightCorner]);
00137         if (r.height() - 16 > 0) {
00138             p->drawTiledPixmap(r.x(), r.y() + 8, 8, r.height() - 16,  m_tiles[LeftSide]);
00139             p->drawTiledPixmap(r.right() - 8 + 1, r.y() + 8, 8, r.height() - 16, m_tiles[RightSide]);
00140         }
00141         p->drawPixmap(r.x(), r.bottom() - 8 + 1, m_tiles[BottomLeftCorner]);
00142         if (r.width() - 16 > 0) {
00143             p->drawTiledPixmap(r.x() + 8, r.bottom() - 8 + 1, r.width() - 16, 8, m_tiles[BottomSide]);
00144         }
00145         p->drawPixmap(r.right() - 8 + 1, r.bottom() - 8 + 1, m_tiles[BottomRightCorner]);
00146 
00147         const QRect contentRect = r.adjusted(LeftMargin + 1, TopMargin + 1,
00148                                              -(RightMargin + 1), -(BottomMargin + 1));
00149         p->fillRect(contentRect, Qt::transparent);
00150     }
00151 
00152 private:
00153     QPixmap m_tiles[NumTiles];
00154 };
00155 
00156 class KFilePreviewGenerator::Private
00157 {
00158 public:
00159     Private(KFilePreviewGenerator* parent,
00160             KAbstractViewAdapter* viewAdapter,
00161             QAbstractItemModel* model);
00162     ~Private();
00163 
00168     void requestSequenceIcon(const QModelIndex& index, int sequenceIndex);
00169 
00173     void updateIcons(const KFileItemList& items);
00174 
00179     void updateIcons(const QModelIndex& topLeft, const QModelIndex& bottomRight);
00180 
00186     void addToPreviewQueue(const KFileItem& item, const QPixmap& pixmap);
00187 
00192     void slotPreviewJobFinished(KJob* job);
00193 
00195     void updateCutItems();
00196 
00201     void clearCutItemsCache();
00202 
00207     void dispatchIconUpdateQueue();
00208 
00214     void pauseIconUpdates();
00215 
00221     void resumeIconUpdates();
00222 
00227     void startMimeTypeResolving();
00228 
00233     void resolveMimeType();
00234 
00239     bool isCutItem(const KFileItem& item) const;
00240 
00245     void applyCutItemEffect(const KFileItemList& items);
00246 
00251     bool applyImageFrame(QPixmap& icon);
00252 
00258     void limitToSize(QPixmap& icon, const QSize& maxSize);
00259 
00264     void createPreviews(const KFileItemList& items);
00265 
00270     void startPreviewJob(const KFileItemList& items, int width, int height);
00271 
00273     void killPreviewJobs();
00274 
00281     void orderItems(KFileItemList& items);
00282 
00287     bool decodeIsCutSelection(const QMimeData* mimeData);
00288 
00293     void addItemsToList(const QModelIndex& index, KFileItemList& list);
00294 
00299     void delayedIconUpdate();
00300 
00302     struct ItemInfo
00303     {
00304         KUrl url;
00305         QPixmap pixmap;
00306     };
00307 
00312     class DataChangeObtainer
00313     {
00314     public:
00315         DataChangeObtainer(KFilePreviewGenerator::Private* generator) :
00316             m_gen(generator)  { ++m_gen->m_internalDataChange; }
00317         ~DataChangeObtainer() { --m_gen->m_internalDataChange; }
00318     private:
00319         KFilePreviewGenerator::Private* m_gen;
00320     };
00321 
00322     bool m_previewShown;
00323 
00328     bool m_clearItemQueues;
00329 
00333     bool m_hasCutSelection;
00334 
00339     bool m_iconUpdatesPaused;
00340 
00346     int m_internalDataChange;
00347 
00348     int m_pendingVisibleIconUpdates;
00349 
00350     KAbstractViewAdapter* m_viewAdapter;
00351     QAbstractItemView* m_itemView;
00352     QTimer* m_iconUpdateTimer;
00353     QTimer* m_scrollAreaTimer;
00354     QList<KJob*> m_previewJobs;
00355     KDirModel* m_dirModel;
00356     QAbstractProxyModel* m_proxyModel;
00357 
00365     QHash<KUrl, QPixmap> m_cutItemsCache;
00366     QList<ItemInfo> m_previews;
00367     QMap<KUrl, int> m_sequenceIndices;
00368 
00375     QHash<KUrl, bool> m_changedItems;
00376     QTimer* m_changedItemsTimer;
00377 
00382     KFileItemList m_pendingItems;
00383 
00388     KFileItemList m_dispatchedItems;
00389 
00390     KFileItemList m_resolvedMimeTypes;
00391 
00392     QStringList m_enabledPlugins;
00393 
00394     TileSet* m_tileSet;
00395 
00396 private:
00397     KFilePreviewGenerator* const q;
00398 
00399 };
00400 
00401 KFilePreviewGenerator::Private::Private(KFilePreviewGenerator* parent,
00402                                         KAbstractViewAdapter* viewAdapter,
00403                                         QAbstractItemModel* model) :
00404     m_previewShown(true),
00405     m_clearItemQueues(true),
00406     m_hasCutSelection(false),
00407     m_iconUpdatesPaused(false),
00408     m_internalDataChange(0),
00409     m_pendingVisibleIconUpdates(0),
00410     m_viewAdapter(viewAdapter),
00411     m_itemView(0),
00412     m_iconUpdateTimer(0),
00413     m_scrollAreaTimer(0),
00414     m_previewJobs(),
00415     m_dirModel(0),
00416     m_proxyModel(0),
00417     m_cutItemsCache(),
00418     m_previews(),
00419     m_sequenceIndices(),
00420     m_changedItems(),
00421     m_changedItemsTimer(0),
00422     m_pendingItems(),
00423     m_dispatchedItems(),
00424     m_resolvedMimeTypes(),
00425     m_tileSet(0),
00426     q(parent)
00427 {
00428     if (!m_viewAdapter->iconSize().isValid()) {
00429         m_previewShown = false;
00430     }
00431 
00432     m_proxyModel = qobject_cast<QAbstractProxyModel*>(model);
00433     m_dirModel = (m_proxyModel == 0) ?
00434                  qobject_cast<KDirModel*>(model) :
00435                  qobject_cast<KDirModel*>(m_proxyModel->sourceModel());
00436     if (m_dirModel == 0) {
00437         // previews can only get generated for directory models
00438         m_previewShown = false;
00439     } else {
00440         connect(m_dirModel->dirLister(), SIGNAL(newItems(const KFileItemList&)),
00441                 q, SLOT(updateIcons(const KFileItemList&)));
00442         connect(m_dirModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
00443                 q, SLOT(updateIcons(const QModelIndex&, const QModelIndex&)));
00444         connect(m_dirModel, SIGNAL(needSequenceIcon(const QModelIndex&,int)),
00445                q, SLOT(requestSequenceIcon(const QModelIndex&, int)));
00446     }
00447 
00448     QClipboard* clipboard = QApplication::clipboard();
00449     connect(clipboard, SIGNAL(dataChanged()),
00450             q, SLOT(updateCutItems()));
00451 
00452     m_iconUpdateTimer = new QTimer(q);
00453     m_iconUpdateTimer->setSingleShot(true);
00454     connect(m_iconUpdateTimer, SIGNAL(timeout()), q, SLOT(dispatchIconUpdateQueue()));
00455 
00456     // Whenever the scrollbar values have been changed, the pending previews should
00457     // be reordered in a way that the previews for the visible items are generated
00458     // first. The reordering is done with a small delay, so that during moving the
00459     // scrollbars the CPU load is kept low.
00460     m_scrollAreaTimer = new QTimer(q);
00461     m_scrollAreaTimer->setSingleShot(true);
00462     m_scrollAreaTimer->setInterval(200);
00463     connect(m_scrollAreaTimer, SIGNAL(timeout()),
00464             q, SLOT(resumeIconUpdates()));
00465     m_viewAdapter->connect(KAbstractViewAdapter::ScrollBarValueChanged,
00466                            q, SLOT(pauseIconUpdates()));
00467 
00468     m_changedItemsTimer = new QTimer(q);
00469     m_changedItemsTimer->setSingleShot(true);
00470     m_changedItemsTimer->setInterval(5000);
00471     connect(m_changedItemsTimer, SIGNAL(timeout()),
00472             q, SLOT(delayedIconUpdate()));
00473 }
00474 
00475 KFilePreviewGenerator::Private::~Private()
00476 {
00477     killPreviewJobs();
00478     m_pendingItems.clear();
00479     m_dispatchedItems.clear();
00480     delete m_tileSet;
00481 }
00482 
00483 void KFilePreviewGenerator::Private::requestSequenceIcon(const QModelIndex& index,
00484                                                          int sequenceIndex)
00485 {
00486     if (m_pendingItems.isEmpty() || (sequenceIndex == 0)) {
00487         KFileItem item = m_dirModel->itemForIndex(index);
00488         if (sequenceIndex == 0) {
00489            m_sequenceIndices.remove(item.url());
00490         } else {
00491            m_sequenceIndices.insert(item.url(), sequenceIndex);
00492         }
00493 
00495         updateIcons(KFileItemList() << item);
00496     }
00497 }
00498 
00499 void KFilePreviewGenerator::Private::updateIcons(const KFileItemList& items)
00500 {
00501     if (items.count() <= 0) {
00502         return;
00503     }
00504 
00505     applyCutItemEffect(items);
00506 
00507     KFileItemList orderedItems = items;
00508     orderItems(orderedItems);
00509 
00510     foreach (const KFileItem& item, orderedItems) {
00511         m_pendingItems.append(item);
00512     }
00513 
00514     if (m_previewShown) {
00515         createPreviews(orderedItems);
00516     } else {
00517         startMimeTypeResolving();
00518     }
00519 }
00520 
00521 void KFilePreviewGenerator::Private::updateIcons(const QModelIndex& topLeft,
00522                                                  const QModelIndex& bottomRight)
00523 {
00524     if (m_internalDataChange > 0) {
00525         // QAbstractItemModel::setData() has been invoked internally by the KFilePreviewGenerator.
00526         // The signal dataChanged() is connected with this method, but previews only need
00527         // to be generated when an external data change has occured.
00528         return;
00529     }
00530     // dataChanged emitted for the root dir (e.g. permission changes)
00531     if (!topLeft.isValid() || !bottomRight.isValid()) {
00532         return;
00533     }
00534 
00535     KFileItemList itemList;
00536     for (int row = topLeft.row(); row <= bottomRight.row(); ++row) {
00537         const QModelIndex index = m_dirModel->index(row, 0);
00538         const KFileItem item = m_dirModel->itemForIndex(index);
00539 
00540         if (m_previewShown) {
00541             const KUrl url = item.url();
00542             const bool hasChanged = m_changedItems.contains(url); // O(1)
00543             m_changedItems.insert(url, hasChanged);
00544             if (!hasChanged) {
00545                 // only update the icon if it has not been already updated within
00546                 // the last 5 seconds (the other icons will be updated later with
00547                 // the help of m_changedItemsTimer)
00548                 itemList.append(item);
00549             }
00550         } else {
00551             itemList.append(item);
00552         }
00553     }
00554 
00555     updateIcons(itemList);
00556     m_changedItemsTimer->start();
00557 }
00558 
00559 void KFilePreviewGenerator::Private::addToPreviewQueue(const KFileItem& item, const QPixmap& pixmap)
00560 {
00561     KIO::PreviewJob* senderJob = qobject_cast<KIO::PreviewJob*>(q->sender());
00562     Q_ASSERT(senderJob != 0);
00563     if (senderJob != 0) {
00564         QMap<KUrl, int>::iterator it = m_sequenceIndices.find(item.url());
00565         if (senderJob->sequenceIndex() && (it == m_sequenceIndices.end() || *it != senderJob->sequenceIndex())) {
00566             return; // the sequence index does not match the one we want
00567         }
00568         if (!senderJob->sequenceIndex() && it != m_sequenceIndices.end()) {
00569             return; // the sequence index does not match the one we want
00570         }
00571 
00572         m_sequenceIndices.erase(it);
00573     }
00574 
00575     if (!m_previewShown) {
00576         // the preview has been canceled in the meantime
00577         return;
00578     }
00579 
00580     // check whether the item is part of the directory lister (it is possible
00581     // that a preview from an old directory lister is received)
00582     const KUrl url = item.url();
00583     KDirLister* dirLister = m_dirModel->dirLister();
00584     bool isOldPreview = true;
00585     const KUrl::List dirs = dirLister->directories();
00586     const QString itemDir = url.directory();
00587     foreach (const KUrl& url, dirs) {
00588         if (url.path() == itemDir) {
00589             isOldPreview = false;
00590             break;
00591         }
00592     }
00593     if (isOldPreview) {
00594         return;
00595     }
00596 
00597     QPixmap icon = pixmap;
00598 
00599     const QString mimeType = item.mimetype();
00600     const QString mimeTypeGroup = mimeType.left(mimeType.indexOf('/'));
00601     if ((mimeTypeGroup != "image") || !applyImageFrame(icon)) {
00602         limitToSize(icon, m_viewAdapter->iconSize());
00603     }
00604 
00605     if (m_hasCutSelection && isCutItem(item)) {
00606         // apply the disabled effect to the icon for marking it as "cut item"
00607         // and apply the icon to the item
00608         KIconEffect iconEffect;
00609         icon = iconEffect.apply(icon, KIconLoader::Desktop, KIconLoader::DisabledState);
00610     }
00611 
00612     // remember the preview and URL, so that it can be applied to the model
00613     // in KFilePreviewGenerator::dispatchIconUpdateQueue()
00614     ItemInfo preview;
00615     preview.url = url;
00616     preview.pixmap = icon;
00617     m_previews.append(preview);
00618 
00619     m_dispatchedItems.append(item);
00620 }
00621 
00622 void KFilePreviewGenerator::Private::slotPreviewJobFinished(KJob* job)
00623 {
00624     const int index = m_previewJobs.indexOf(job);
00625     m_previewJobs.removeAt(index);
00626 
00627     if (m_previewJobs.isEmpty()) {
00628         if (m_clearItemQueues) {
00629             m_pendingItems.clear();
00630             m_dispatchedItems.clear();
00631             m_pendingVisibleIconUpdates = 0;
00632             QMetaObject::invokeMethod(q, "dispatchIconUpdateQueue", Qt::QueuedConnection);
00633         }
00634         m_sequenceIndices.clear(); // just to be sure that we don't leak anything
00635     }
00636 }
00637 
00638 void KFilePreviewGenerator::Private::updateCutItems()
00639 {
00640     DataChangeObtainer obt(this);
00641     clearCutItemsCache();
00642 
00643     KFileItemList items;
00644     KDirLister* dirLister = m_dirModel->dirLister();
00645     const KUrl::List dirs = dirLister->directories();
00646     foreach (const KUrl& url, dirs) {
00647         items << dirLister->itemsForDir(url);
00648     }
00649     applyCutItemEffect(items);
00650 }
00651 
00652 void KFilePreviewGenerator::Private::clearCutItemsCache()
00653 {
00654     DataChangeObtainer obt(this);
00655     KFileItemList previews;
00656     // Reset the icons of all items that are stored in the cache
00657     // to use their default MIME type icon.
00658     foreach (const KUrl& url, m_cutItemsCache.keys()) {
00659         const QModelIndex index = m_dirModel->indexForUrl(url);
00660         if (index.isValid()) {
00661             m_dirModel->setData(index, QIcon(), Qt::DecorationRole);
00662             if (m_previewShown) {
00663                 previews.append(m_dirModel->itemForIndex(index));
00664             }
00665         }
00666     }
00667     m_cutItemsCache.clear();
00668 
00669     if (previews.size() > 0) {
00670         // assure that the previews gets restored
00671         Q_ASSERT(m_previewShown);
00672         orderItems(previews);
00673         updateIcons(previews);
00674     }
00675 }
00676 
00677 void KFilePreviewGenerator::Private::dispatchIconUpdateQueue()
00678 {
00679     const int count = m_previewShown ? m_previews.count()
00680                                      : m_resolvedMimeTypes.count();
00681     if (count > 0) {
00682         LayoutBlocker blocker(m_itemView);
00683         DataChangeObtainer obt(this);
00684 
00685         if (m_previewShown) {
00686             // dispatch preview queue
00687             foreach (const ItemInfo& preview, m_previews) {
00688                 const QModelIndex idx = m_dirModel->indexForUrl(preview.url);
00689                 if (idx.isValid() && (idx.column() == 0)) {
00690                     m_dirModel->setData(idx, QIcon(preview.pixmap), Qt::DecorationRole);
00691                 }
00692             }
00693             m_previews.clear();
00694         } else {
00695             // dispatch mime type queue
00696             foreach (const KFileItem& item, m_resolvedMimeTypes) {
00697                 const QModelIndex idx = m_dirModel->indexForItem(item);
00698                 m_dirModel->itemChanged(idx);
00699             }
00700             m_resolvedMimeTypes.clear();
00701         }
00702 
00703         m_pendingVisibleIconUpdates -= count;
00704         if (m_pendingVisibleIconUpdates < 0) {
00705             m_pendingVisibleIconUpdates = 0;
00706         }
00707     }
00708 
00709     if (m_pendingVisibleIconUpdates > 0) {
00710         // As long as there are pending previews for visible items, poll
00711         // the preview queue each 200 ms. If there are no pending previews,
00712         // the queue is dispatched in slotPreviewJobFinished().
00713         m_iconUpdateTimer->start(200);
00714     }
00715 }
00716 
00717 void KFilePreviewGenerator::Private::pauseIconUpdates()
00718 {
00719     m_iconUpdatesPaused = true;
00720     foreach (KJob* job, m_previewJobs) {
00721         Q_ASSERT(job != 0);
00722         job->suspend();
00723     }
00724     m_scrollAreaTimer->start();
00725 }
00726 
00727 void KFilePreviewGenerator::Private::resumeIconUpdates()
00728 {
00729     m_iconUpdatesPaused = false;
00730 
00731     // Before creating new preview jobs the m_pendingItems queue must be
00732     // cleaned up by removing the already dispatched items. Implementation
00733     // note: The order of the m_dispatchedItems queue and the m_pendingItems
00734     // queue is usually equal. So even when having a lot of elements the
00735     // nested loop is no performance bottle neck, as the inner loop is only
00736     // entered once in most cases.
00737     foreach (const KFileItem& item, m_dispatchedItems) {
00738         KFileItemList::iterator begin = m_pendingItems.begin();
00739         KFileItemList::iterator end   = m_pendingItems.end();
00740         for (KFileItemList::iterator it = begin; it != end; ++it) {
00741             if ((*it).url() == item.url()) {
00742                 m_pendingItems.erase(it);
00743                 break;
00744             }
00745         }
00746     }
00747     m_dispatchedItems.clear();
00748 
00749     m_pendingVisibleIconUpdates = 0;
00750     dispatchIconUpdateQueue();
00751 
00752 
00753     if (m_previewShown) {
00754         KFileItemList orderedItems = m_pendingItems;
00755         orderItems(orderedItems);
00756 
00757         // Kill all suspended preview jobs. Usually when a preview job
00758         // has been finished, slotPreviewJobFinished() clears all item queues.
00759         // This is not wanted in this case, as a new job is created afterwards
00760         // for m_pendingItems.
00761         m_clearItemQueues = false;
00762         killPreviewJobs();
00763         m_clearItemQueues = true;
00764 
00765         createPreviews(orderedItems);
00766     } else {
00767         orderItems(m_pendingItems);
00768         startMimeTypeResolving();
00769     }
00770 }
00771 
00772 void KFilePreviewGenerator::Private::startMimeTypeResolving()
00773 {
00774     resolveMimeType();
00775     m_iconUpdateTimer->start(200);
00776 }
00777 
00778 void KFilePreviewGenerator::Private::resolveMimeType()
00779 {
00780     if (m_pendingItems.isEmpty()) {
00781         return;
00782     }
00783 
00784     // resolve at least one MIME type
00785     bool resolved = false;
00786     do {
00787         KFileItem item = m_pendingItems.takeFirst();
00788         if (item.isMimeTypeKnown()) {
00789             if (m_pendingVisibleIconUpdates > 0) {
00790                 // The item is visible and the MIME type already known.
00791                 // Decrease the update counter for dispatchIconUpdateQueue():
00792                 --m_pendingVisibleIconUpdates;
00793             }
00794         } else {
00795             // The MIME type is unknown and must get resolved. The
00796             // directory model is not informed yet, as a single update
00797             // would be very expensive. Instead the item is remembered in
00798             // m_resolvedMimeTypes and will be dispatched later
00799             // by dispatchIconUpdateQueue().
00800             item.determineMimeType();
00801             m_resolvedMimeTypes.append(item);
00802             resolved = true;
00803         }
00804     } while (!resolved && !m_pendingItems.isEmpty());
00805 
00806     if (m_pendingItems.isEmpty()) {
00807         // All MIME types have been resolved now. Assure
00808         // that the directory model gets informed about
00809         // this, so that an update of the icons is done.
00810         dispatchIconUpdateQueue();
00811     } else if (!m_iconUpdatesPaused) {
00812         // assure that the MIME type of the next
00813         // item will be resolved asynchronously
00814         QMetaObject::invokeMethod(q, "resolveMimeType", Qt::QueuedConnection);
00815     }
00816 }
00817 
00818 bool KFilePreviewGenerator::Private::isCutItem(const KFileItem& item) const
00819 {
00820     const QMimeData* mimeData = QApplication::clipboard()->mimeData();
00821     const KUrl::List cutUrls = KUrl::List::fromMimeData(mimeData);
00822     return cutUrls.contains(item.url());
00823 }
00824 
00825 void KFilePreviewGenerator::Private::applyCutItemEffect(const KFileItemList& items)
00826 {
00827     const QMimeData* mimeData = QApplication::clipboard()->mimeData();
00828     m_hasCutSelection = decodeIsCutSelection(mimeData);
00829     if (!m_hasCutSelection) {
00830         return;
00831     }
00832 
00833     const QSet<KUrl> cutUrls = KUrl::List::fromMimeData(mimeData).toSet();
00834 
00835     DataChangeObtainer obt(this);
00836     KIconEffect iconEffect;
00837     foreach (const KFileItem& item, items) {
00838         if (cutUrls.contains(item.url())) {
00839             const QModelIndex index = m_dirModel->indexForItem(item);
00840             const QVariant value = m_dirModel->data(index, Qt::DecorationRole);
00841             if (value.type() == QVariant::Icon) {
00842                 const QIcon icon(qvariant_cast<QIcon>(value));
00843                 const QSize actualSize = icon.actualSize(m_viewAdapter->iconSize());
00844                 QPixmap pixmap = icon.pixmap(actualSize);
00845                 
00846                 QHash< KUrl, QPixmap >::iterator cacheIt = m_cutItemsCache.find(item.url());
00847                 if(cacheIt != m_cutItemsCache.end() && cacheIt->cacheKey() == pixmap.cacheKey())
00848                   continue; //Effect already applied to this pixmap
00849                 
00850                 pixmap = iconEffect.apply(pixmap, KIconLoader::Desktop, KIconLoader::DisabledState);
00851                 m_dirModel->setData(index, QIcon(pixmap), Qt::DecorationRole);
00852                 
00853                 m_cutItemsCache.insert(item.url(), pixmap);
00854             }
00855         }
00856     }
00857 }
00858 
00859 bool KFilePreviewGenerator::Private::applyImageFrame(QPixmap& icon)
00860 {
00861     const QSize maxSize = m_viewAdapter->iconSize();
00862     const bool applyFrame = (maxSize.width()  > KIconLoader::SizeSmallMedium) &&
00863                             (maxSize.height() > KIconLoader::SizeSmallMedium) &&
00864                             ((icon.width()  > KIconLoader::SizeLarge) ||
00865                              (icon.height() > KIconLoader::SizeLarge));
00866     if (!applyFrame) {
00867         // the maximum size or the image itself is too small for a frame
00868         return false;
00869     }
00870 
00871     // resize the icon to the maximum size minus the space required for the frame
00872     const QSize size(maxSize.width() - TileSet::LeftMargin - TileSet::RightMargin,
00873                      maxSize.height() - TileSet::TopMargin - TileSet::BottomMargin);
00874     limitToSize(icon, size);
00875 
00876     if (m_tileSet == 0) {
00877         m_tileSet = new TileSet();
00878     }
00879 
00880     QPixmap framedIcon(icon.size().width() + TileSet::LeftMargin + TileSet::RightMargin,
00881                        icon.size().height() + TileSet::TopMargin + TileSet::BottomMargin);
00882     framedIcon.fill(Qt::transparent);
00883 
00884     QPainter painter;
00885     painter.begin(&framedIcon);
00886     painter.setCompositionMode(QPainter::CompositionMode_Source);
00887     m_tileSet->paint(&painter, framedIcon.rect());
00888     painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
00889     painter.drawPixmap(TileSet::LeftMargin, TileSet::TopMargin, icon);
00890     painter.end();
00891 
00892     icon = framedIcon;
00893     return true;
00894 }
00895 
00896 void KFilePreviewGenerator::Private::limitToSize(QPixmap& icon, const QSize& maxSize)
00897 {
00898     if ((icon.width() > maxSize.width()) || (icon.height() > maxSize.height())) {
00899 #if defined(Q_WS_X11) && defined(HAVE_XRENDER)
00900         // Assume that the texture size limit is 2048x2048
00901         if ((icon.width() <= 2048) && (icon.height() <= 2048) && icon.x11PictureHandle()) {
00902             QSize size = icon.size();
00903             size.scale(maxSize, Qt::KeepAspectRatio);
00904 
00905             const qreal factor = size.width() / qreal(icon.width());
00906 
00907             XTransform xform = {{
00908                 { XDoubleToFixed(1 / factor), 0, 0 },
00909                 { 0, XDoubleToFixed(1 / factor), 0 },
00910                 { 0, 0, XDoubleToFixed(1) }
00911             }};
00912 
00913             QPixmap pixmap(size);
00914             pixmap.fill(Qt::transparent);
00915 
00916             Display* dpy = QX11Info::display();
00917             XRenderSetPictureFilter(dpy, icon.x11PictureHandle(), FilterBilinear, 0, 0);
00918             XRenderSetPictureTransform(dpy, icon.x11PictureHandle(), &xform);
00919             XRenderComposite(dpy, PictOpOver, icon.x11PictureHandle(), None, pixmap.x11PictureHandle(),
00920                              0, 0, 0, 0, 0, 0, pixmap.width(), pixmap.height());
00921             icon = pixmap;
00922         } else {
00923             icon = icon.scaled(maxSize, Qt::KeepAspectRatio, Qt::FastTransformation);
00924         }
00925 #else
00926         icon = icon.scaled(maxSize, Qt::KeepAspectRatio, Qt::FastTransformation);
00927 #endif
00928     }
00929 }
00930 
00931 void KFilePreviewGenerator::Private::createPreviews(const KFileItemList& items)
00932 {
00933     if (items.count() == 0) {
00934         return;
00935     }
00936 
00937     const QMimeData* mimeData = QApplication::clipboard()->mimeData();
00938     m_hasCutSelection = decodeIsCutSelection(mimeData);
00939 
00940     // PreviewJob internally caches items always with the size of
00941     // 128 x 128 pixels or 256 x 256 pixels. A downscaling is done
00942     // by PreviewJob if a smaller size is requested. For images KFilePreviewGenerator must
00943     // do a downscaling anyhow because of the frame, so in this case only the provided
00944     // cache sizes are requested.
00945     KFileItemList imageItems;
00946     KFileItemList otherItems;
00947     QString mimeType;
00948     QString mimeTypeGroup;
00949     foreach (const KFileItem& item, items) {
00950         mimeType = item.mimetype();
00951         mimeTypeGroup = mimeType.left(mimeType.indexOf('/'));
00952         if (mimeTypeGroup == "image") {
00953             imageItems.append(item);
00954         } else {
00955             otherItems.append(item);
00956         }
00957     }
00958     const QSize size = m_viewAdapter->iconSize();
00959     startPreviewJob(otherItems, size.width(), size.height());
00960 
00961     const int cacheSize = (size.width() > 128) || (size.height() > 128) ? 256 : 128;
00962     startPreviewJob(imageItems, cacheSize, cacheSize);
00963 
00964     m_iconUpdateTimer->start(200);
00965 }
00966 
00967 void KFilePreviewGenerator::Private::startPreviewJob(const KFileItemList& items, int width, int height)
00968 {
00969     if (items.count() > 0) {
00970         if (m_enabledPlugins.isEmpty()) {
00971             const KConfigGroup globalConfig(KGlobal::config(), "PreviewSettings");
00972             m_enabledPlugins = globalConfig.readEntry("Plugins", QStringList()
00973                                                                  << "directorythumbnail"
00974                                                                  << "imagethumbnail"
00975                                                                  << "jpegthumbnail");
00976         }
00977 
00978         KIO::PreviewJob* job = KIO::filePreview(items, width, height, 0, 70, true, true, &m_enabledPlugins);
00979 
00980         // Set the sequence index to the target. We only need to check if items.count() == 1,
00981         // because requestSequenceIcon(..) creates exactly such a request.
00982         if (!m_sequenceIndices.isEmpty() && (items.count() == 1)) {
00983             QMap<KUrl, int>::iterator it = m_sequenceIndices.find(items[0].url());
00984             if (it != m_sequenceIndices.end()) {
00985                 job->setSequenceIndex(*it);
00986             }
00987         }
00988 
00989         connect(job, SIGNAL(gotPreview(const KFileItem&, const QPixmap&)),
00990                 q, SLOT(addToPreviewQueue(const KFileItem&, const QPixmap&)));
00991         connect(job, SIGNAL(finished(KJob*)),
00992                 q, SLOT(slotPreviewJobFinished(KJob*)));
00993         m_previewJobs.append(job);
00994     }
00995 }
00996 
00997 void KFilePreviewGenerator::Private::killPreviewJobs()
00998 {
00999     foreach (KJob* job, m_previewJobs) {
01000         Q_ASSERT(job != 0);
01001         job->kill();
01002     }
01003     m_previewJobs.clear();
01004     m_sequenceIndices.clear();
01005     
01006     m_iconUpdateTimer->stop();
01007     m_scrollAreaTimer->stop();
01008     m_changedItemsTimer->stop();
01009 }
01010 
01011 void KFilePreviewGenerator::Private::orderItems(KFileItemList& items)
01012 {
01013     // Order the items in a way that the preview for the visible items
01014     // is generated first, as this improves the feeled performance a lot.
01015     const bool hasProxy = (m_proxyModel != 0);
01016     const int itemCount = items.count();
01017     const QRect visibleArea = m_viewAdapter->visibleArea();
01018 
01019     QModelIndex dirIndex;
01020     QRect itemRect;
01021     int insertPos = 0;
01022     for (int i = 0; i < itemCount; ++i) {
01023         dirIndex = m_dirModel->indexForItem(items.at(i)); // O(n) (n = number of rows)
01024         if (hasProxy) {
01025             const QModelIndex proxyIndex = m_proxyModel->mapFromSource(dirIndex);
01026             itemRect = m_viewAdapter->visualRect(proxyIndex);
01027         } else {
01028             itemRect = m_viewAdapter->visualRect(dirIndex);
01029         }
01030 
01031         if (itemRect.intersects(visibleArea)) {
01032             // The current item is (at least partly) visible. Move it
01033             // to the front of the list, so that the preview is
01034             // generated earlier.
01035             items.insert(insertPos, items.at(i));
01036             items.removeAt(i + 1);
01037             ++insertPos;
01038             ++m_pendingVisibleIconUpdates;
01039         }
01040     }
01041 }
01042 
01043 bool KFilePreviewGenerator::Private::decodeIsCutSelection(const QMimeData* mimeData)
01044 {
01045     const QByteArray data = mimeData->data("application/x-kde-cutselection");
01046     if (data.isEmpty()) {
01047         return false;
01048     } else {
01049         return data.at(0) == '1';
01050     }
01051 }
01052 
01053 void KFilePreviewGenerator::Private::addItemsToList(const QModelIndex& index, KFileItemList& list)
01054 {
01055     const int rowCount = m_dirModel->rowCount(index);
01056     for (int row = 0; row < rowCount; ++row) {
01057         const QModelIndex subIndex = m_dirModel->index(row, 0, index);
01058         KFileItem item = m_dirModel->itemForIndex(subIndex);
01059         list.append(item);
01060 
01061         if (m_dirModel->rowCount(subIndex) > 0) {
01062             // the model is hierarchical (treeview)
01063             addItemsToList(subIndex, list);
01064         }
01065     }
01066 }
01067 
01068 void KFilePreviewGenerator::Private::delayedIconUpdate()
01069 {
01070     // Precondition: No items have been changed within the last
01071     // 5 seconds. This means that items that have been changed constantly
01072     // due to a copy operation should be updated now.
01073 
01074     KFileItemList itemList;
01075 
01076     QHash<KUrl, bool>::const_iterator it = m_changedItems.constBegin();
01077     while (it != m_changedItems.constEnd()) {
01078         const bool hasChanged = it.value();
01079         if (hasChanged) {
01080             const QModelIndex index = m_dirModel->indexForUrl(it.key());
01081             const KFileItem item = m_dirModel->itemForIndex(index);
01082             itemList.append(item);
01083         }
01084         ++it;
01085     }    
01086     m_changedItems.clear();
01087 
01088     updateIcons(itemList);
01089 }
01090 
01091 KFilePreviewGenerator::KFilePreviewGenerator(QAbstractItemView* parent) :
01092     QObject(parent),
01093     d(new Private(this, new KIO::DefaultViewAdapter(parent, this), parent->model()))
01094 {
01095     d->m_itemView = parent;
01096 }
01097 
01098 KFilePreviewGenerator::KFilePreviewGenerator(KAbstractViewAdapter* parent, QAbstractProxyModel* model) :
01099     QObject(parent),
01100     d(new Private(this, parent, model))
01101 {
01102 }
01103 
01104 KFilePreviewGenerator::~KFilePreviewGenerator()
01105 {
01106     delete d;
01107 }
01108 
01109 void KFilePreviewGenerator::setPreviewShown(bool show)
01110 {
01111     if (show && (!d->m_viewAdapter->iconSize().isValid() || (d->m_dirModel == 0))) {
01112         // the view must provide an icon size and a directory model,
01113         // otherwise the showing the previews will get ignored
01114         return;
01115     }
01116 
01117     if (d->m_previewShown != show) {
01118         d->m_previewShown = show;
01119         updateIcons();
01120     }
01121 }
01122 
01123 bool KFilePreviewGenerator::isPreviewShown() const
01124 {
01125     return d->m_previewShown;
01126 }
01127 
01128 // deprecated (use updateIcons() instead)
01129 void KFilePreviewGenerator::updatePreviews()
01130 {
01131     updateIcons();
01132 }
01133 
01134 void KFilePreviewGenerator::updateIcons()
01135 {
01136     d->killPreviewJobs();
01137 
01138     d->clearCutItemsCache();
01139     d->m_pendingItems.clear();
01140     d->m_dispatchedItems.clear();
01141 
01142     KFileItemList itemList;
01143     d->addItemsToList(QModelIndex(), itemList);
01144 
01145     d->updateIcons(itemList);
01146 }
01147 
01148 void KFilePreviewGenerator::cancelPreviews()
01149 {
01150     d->killPreviewJobs();
01151     d->m_pendingItems.clear();
01152     d->m_dispatchedItems.clear();
01153     updateIcons();
01154 }
01155 
01156 void KFilePreviewGenerator::setEnabledPlugins(const QStringList& plugins)
01157 {
01158     d->m_enabledPlugins = plugins;
01159 }
01160 
01161 QStringList KFilePreviewGenerator::enabledPlugins() const
01162 {
01163     return d->m_enabledPlugins;
01164 }
01165 
01166 #include "kfilepreviewgenerator.moc"

KFile

Skip menu "KFile"
  • Main Page
  • 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
  • KPty
  • Kross
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.6.1
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