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