00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
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>
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
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
00464
00465
00466
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
00533
00534
00535 return;
00536 }
00537
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);
00550 m_changedItems.insert(url, hasChanged);
00551 if (!hasChanged) {
00552
00553
00554
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;
00574 }
00575 if (!senderJob->sequenceIndex() && it != m_sequenceIndices.end()) {
00576 return;
00577 }
00578
00579 m_sequenceIndices.erase(it);
00580 }
00581
00582 if (!m_previewShown) {
00583
00584 return;
00585 }
00586
00587
00588
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
00614
00615 KIconEffect iconEffect;
00616 icon = iconEffect.apply(icon, KIconLoader::Desktop, KIconLoader::DisabledState);
00617 }
00618
00619
00620
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();
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
00664
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
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
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
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
00718
00719
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
00739
00740
00741
00742
00743
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
00765
00766
00767
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
00792 bool resolved = false;
00793 do {
00794 KFileItem item = m_pendingItems.takeFirst();
00795 if (item.isMimeTypeKnown()) {
00796 if (m_pendingVisibleIconUpdates > 0) {
00797
00798
00799 --m_pendingVisibleIconUpdates;
00800 }
00801 } else {
00802
00803
00804
00805
00806
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
00815
00816
00817 dispatchIconUpdateQueue();
00818 } else if (!m_iconUpdatesPaused) {
00819
00820
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;
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
00875 return false;
00876 }
00877
00878
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
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
00948
00949
00950
00951
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
00988
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
01021
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));
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
01040
01041
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
01070 addItemsToList(subIndex, list);
01071 }
01072 }
01073 }
01074
01075 void KFilePreviewGenerator::Private::delayedIconUpdate()
01076 {
01077
01078
01079
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
01140
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
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"