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

KFile

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

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • 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.5
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