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"
KDE 4.7 API Reference