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