KFile
kfilepreviewgenerator.cpp
Go to the documentation of this file.
00001 /******************************************************************************* 00002 * Copyright (C) 2008-2009 by Peter Penz <peter.penz@gmx.at> * 00003 * * 00004 * This library is free software; you can redistribute it and/or * 00005 * modify it under the terms of the GNU Library General Public * 00006 * License as published by the Free Software Foundation; either * 00007 * version 2 of the License, or (at your option) any later version. * 00008 * * 00009 * This library is distributed in the hope that it will be useful, * 00010 * but WITHOUT ANY WARRANTY; without even the implied warranty of * 00011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * 00012 * Library General Public License for more details. * 00013 * * 00014 * You should have received a copy of the GNU Library General Public License * 00015 * along with this library; see the file COPYING.LIB. If not, write to * 00016 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * 00017 * Boston, MA 02110-1301, USA. * 00018 *******************************************************************************/ 00019 00020 #include "kfilepreviewgenerator.h" 00021 00022 #include "../kio/kio/defaultviewadapter_p.h" 00023 #include "../kio/kio/imagefilter_p.h" 00024 #include <config.h> // for HAVE_XRENDER 00025 #include <kconfiggroup.h> 00026 #include <kfileitem.h> 00027 #include <kiconeffect.h> 00028 #include <kio/previewjob.h> 00029 #include <kdirlister.h> 00030 #include <kdirmodel.h> 00031 #include <ksharedconfig.h> 00032 00033 #include <QApplication> 00034 #include <QAbstractItemView> 00035 #include <QAbstractProxyModel> 00036 #include <QClipboard> 00037 #include <QColor> 00038 #include <QHash> 00039 #include <QList> 00040 #include <QListView> 00041 #include <QPainter> 00042 #include <QPixmap> 00043 #include <QScrollBar> 00044 #include <QIcon> 00045 00046 #if defined(Q_WS_X11) && defined(HAVE_XRENDER) 00047 # include <QX11Info> 00048 # include <X11/Xlib.h> 00049 # include <X11/extensions/Xrender.h> 00050 #endif 00051 00072 class KFilePreviewGenerator::LayoutBlocker 00073 { 00074 public: 00075 LayoutBlocker(QAbstractItemView* view) : 00076 m_uniformSizes(false), 00077 m_view(qobject_cast<QListView*>(view)) 00078 { 00079 if (m_view != 0) { 00080 m_uniformSizes = m_view->uniformItemSizes(); 00081 m_view->setUniformItemSizes(true); 00082 } 00083 } 00084 00085 ~LayoutBlocker() 00086 { 00087 if (m_view != 0) { 00088 m_view->setUniformItemSizes(m_uniformSizes); 00089 } 00090 } 00091 00092 private: 00093 bool m_uniformSizes; 00094 QListView* m_view; 00095 }; 00096 00098 class KFilePreviewGenerator::TileSet 00099 { 00100 public: 00101 enum { LeftMargin = 3, TopMargin = 2, RightMargin = 3, BottomMargin = 4 }; 00102 00103 enum Tile { TopLeftCorner = 0, TopSide, TopRightCorner, LeftSide, 00104 RightSide, BottomLeftCorner, BottomSide, BottomRightCorner, 00105 NumTiles }; 00106 00107 TileSet() 00108 { 00109 QImage image(8 * 3, 8 * 3, QImage::Format_ARGB32_Premultiplied); 00110 00111 QPainter p(&image); 00112 p.setCompositionMode(QPainter::CompositionMode_Source); 00113 p.fillRect(image.rect(), Qt::transparent); 00114 p.fillRect(image.rect().adjusted(3, 3, -3, -3), Qt::black); 00115 p.end(); 00116 00117 KIO::ImageFilter::shadowBlur(image, 3, Qt::black); 00118 00119 QPixmap pixmap = QPixmap::fromImage(image); 00120 m_tiles[TopLeftCorner] = pixmap.copy(0, 0, 8, 8); 00121 m_tiles[TopSide] = pixmap.copy(8, 0, 8, 8); 00122 m_tiles[TopRightCorner] = pixmap.copy(16, 0, 8, 8); 00123 m_tiles[LeftSide] = pixmap.copy(0, 8, 8, 8); 00124 m_tiles[RightSide] = pixmap.copy(16, 8, 8, 8); 00125 m_tiles[BottomLeftCorner] = pixmap.copy(0, 16, 8, 8); 00126 m_tiles[BottomSide] = pixmap.copy(8, 16, 8, 8); 00127 m_tiles[BottomRightCorner] = pixmap.copy(16, 16, 8, 8); 00128 } 00129 00130 void paint(QPainter* p, const QRect& r) 00131 { 00132 p->drawPixmap(r.topLeft(), m_tiles[TopLeftCorner]); 00133 if (r.width() - 16 > 0) { 00134 p->drawTiledPixmap(r.x() + 8, r.y(), r.width() - 16, 8, m_tiles[TopSide]); 00135 } 00136 p->drawPixmap(r.right() - 8 + 1, r.y(), m_tiles[TopRightCorner]); 00137 if (r.height() - 16 > 0) { 00138 p->drawTiledPixmap(r.x(), r.y() + 8, 8, r.height() - 16, m_tiles[LeftSide]); 00139 p->drawTiledPixmap(r.right() - 8 + 1, r.y() + 8, 8, r.height() - 16, m_tiles[RightSide]); 00140 } 00141 p->drawPixmap(r.x(), r.bottom() - 8 + 1, m_tiles[BottomLeftCorner]); 00142 if (r.width() - 16 > 0) { 00143 p->drawTiledPixmap(r.x() + 8, r.bottom() - 8 + 1, r.width() - 16, 8, m_tiles[BottomSide]); 00144 } 00145 p->drawPixmap(r.right() - 8 + 1, r.bottom() - 8 + 1, m_tiles[BottomRightCorner]); 00146 00147 const QRect contentRect = r.adjusted(LeftMargin + 1, TopMargin + 1, 00148 -(RightMargin + 1), -(BottomMargin + 1)); 00149 p->fillRect(contentRect, Qt::transparent); 00150 } 00151 00152 private: 00153 QPixmap m_tiles[NumTiles]; 00154 }; 00155 00156 class KFilePreviewGenerator::Private 00157 { 00158 public: 00159 Private(KFilePreviewGenerator* parent, 00160 KAbstractViewAdapter* viewAdapter, 00161 QAbstractItemModel* model); 00162 ~Private(); 00163 00168 void requestSequenceIcon(const QModelIndex& index, int sequenceIndex); 00169 00173 void updateIcons(const KFileItemList& items); 00174 00179 void updateIcons(const QModelIndex& topLeft, const QModelIndex& bottomRight); 00180 00186 void addToPreviewQueue(const KFileItem& item, const QPixmap& pixmap); 00187 00192 void slotPreviewJobFinished(KJob* job); 00193 00195 void updateCutItems(); 00196 00201 void clearCutItemsCache(); 00202 00207 void dispatchIconUpdateQueue(); 00208 00214 void pauseIconUpdates(); 00215 00221 void resumeIconUpdates(); 00222 00227 void startMimeTypeResolving(); 00228 00233 void resolveMimeType(); 00234 00239 bool isCutItem(const KFileItem& item) const; 00240 00245 void applyCutItemEffect(const KFileItemList& items); 00246 00251 bool applyImageFrame(QPixmap& icon); 00252 00258 void limitToSize(QPixmap& icon, const QSize& maxSize); 00259 00264 void createPreviews(const KFileItemList& items); 00265 00270 void startPreviewJob(const KFileItemList& items, int width, int height); 00271 00273 void killPreviewJobs(); 00274 00281 void orderItems(KFileItemList& items); 00282 00287 bool decodeIsCutSelection(const QMimeData* mimeData); 00288 00293 void addItemsToList(const QModelIndex& index, KFileItemList& list); 00294 00299 void delayedIconUpdate(); 00300 00304 void rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end); 00305 00307 struct ItemInfo 00308 { 00309 KUrl url; 00310 QPixmap pixmap; 00311 }; 00312 00317 class DataChangeObtainer 00318 { 00319 public: 00320 DataChangeObtainer(KFilePreviewGenerator::Private* generator) : 00321 m_gen(generator) { ++m_gen->m_internalDataChange; } 00322 ~DataChangeObtainer() { --m_gen->m_internalDataChange; } 00323 private: 00324 KFilePreviewGenerator::Private* m_gen; 00325 }; 00326 00327 bool m_previewShown; 00328 00333 bool m_clearItemQueues; 00334 00338 bool m_hasCutSelection; 00339 00344 bool m_iconUpdatesPaused; 00345 00351 int m_internalDataChange; 00352 00353 int m_pendingVisibleIconUpdates; 00354 00355 KAbstractViewAdapter* m_viewAdapter; 00356 QAbstractItemView* m_itemView; 00357 QTimer* m_iconUpdateTimer; 00358 QTimer* m_scrollAreaTimer; 00359 QList<KJob*> m_previewJobs; 00360 QWeakPointer<KDirModel> m_dirModel; 00361 QAbstractProxyModel* m_proxyModel; 00362 00370 QHash<KUrl, QPixmap> m_cutItemsCache; 00371 QList<ItemInfo> m_previews; 00372 QMap<KUrl, int> m_sequenceIndices; 00373 00380 QHash<KUrl, bool> m_changedItems; 00381 QTimer* m_changedItemsTimer; 00382 00387 KFileItemList m_pendingItems; 00388 00393 KFileItemList m_dispatchedItems; 00394 00395 KFileItemList m_resolvedMimeTypes; 00396 00397 QStringList m_enabledPlugins; 00398 00399 TileSet* m_tileSet; 00400 00401 private: 00402 KFilePreviewGenerator* const q; 00403 00404 }; 00405 00406 KFilePreviewGenerator::Private::Private(KFilePreviewGenerator* parent, 00407 KAbstractViewAdapter* viewAdapter, 00408 QAbstractItemModel* model) : 00409 m_previewShown(true), 00410 m_clearItemQueues(true), 00411 m_hasCutSelection(false), 00412 m_iconUpdatesPaused(false), 00413 m_internalDataChange(0), 00414 m_pendingVisibleIconUpdates(0), 00415 m_viewAdapter(viewAdapter), 00416 m_itemView(0), 00417 m_iconUpdateTimer(0), 00418 m_scrollAreaTimer(0), 00419 m_previewJobs(), 00420 m_proxyModel(0), 00421 m_cutItemsCache(), 00422 m_previews(), 00423 m_sequenceIndices(), 00424 m_changedItems(), 00425 m_changedItemsTimer(0), 00426 m_pendingItems(), 00427 m_dispatchedItems(), 00428 m_resolvedMimeTypes(), 00429 m_tileSet(0), 00430 q(parent) 00431 { 00432 if (!m_viewAdapter->iconSize().isValid()) { 00433 m_previewShown = false; 00434 } 00435 00436 m_proxyModel = qobject_cast<QAbstractProxyModel*>(model); 00437 m_dirModel = (m_proxyModel == 0) ? 00438 qobject_cast<KDirModel*>(model) : 00439 qobject_cast<KDirModel*>(m_proxyModel->sourceModel()); 00440 if (!m_dirModel) { 00441 // previews can only get generated for directory models 00442 m_previewShown = false; 00443 } else { 00444 KDirModel* dirModel = m_dirModel.data(); 00445 connect(dirModel->dirLister(), SIGNAL(newItems(const KFileItemList&)), 00446 q, SLOT(updateIcons(const KFileItemList&))); 00447 connect(dirModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), 00448 q, SLOT(updateIcons(const QModelIndex&, const QModelIndex&))); 00449 connect(dirModel, SIGNAL(needSequenceIcon(const QModelIndex&,int)), 00450 q, SLOT(requestSequenceIcon(const QModelIndex&, int))); 00451 connect(dirModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), 00452 q, SLOT(rowsAboutToBeRemoved(const QModelIndex&, int, int))); 00453 } 00454 00455 QClipboard* clipboard = QApplication::clipboard(); 00456 connect(clipboard, SIGNAL(dataChanged()), 00457 q, SLOT(updateCutItems())); 00458 00459 m_iconUpdateTimer = new QTimer(q); 00460 m_iconUpdateTimer->setSingleShot(true); 00461 m_iconUpdateTimer->setInterval(200); 00462 connect(m_iconUpdateTimer, SIGNAL(timeout()), q, SLOT(dispatchIconUpdateQueue())); 00463 00464 // Whenever the scrollbar values have been changed, the pending previews should 00465 // be reordered in a way that the previews for the visible items are generated 00466 // first. The reordering is done with a small delay, so that during moving the 00467 // scrollbars the CPU load is kept low. 00468 m_scrollAreaTimer = new QTimer(q); 00469 m_scrollAreaTimer->setSingleShot(true); 00470 m_scrollAreaTimer->setInterval(200); 00471 connect(m_scrollAreaTimer, SIGNAL(timeout()), 00472 q, SLOT(resumeIconUpdates())); 00473 m_viewAdapter->connect(KAbstractViewAdapter::ScrollBarValueChanged, 00474 q, SLOT(pauseIconUpdates())); 00475 00476 m_changedItemsTimer = new QTimer(q); 00477 m_changedItemsTimer->setSingleShot(true); 00478 m_changedItemsTimer->setInterval(5000); 00479 connect(m_changedItemsTimer, SIGNAL(timeout()), 00480 q, SLOT(delayedIconUpdate())); 00481 } 00482 00483 KFilePreviewGenerator::Private::~Private() 00484 { 00485 killPreviewJobs(); 00486 m_pendingItems.clear(); 00487 m_dispatchedItems.clear(); 00488 delete m_tileSet; 00489 } 00490 00491 void KFilePreviewGenerator::Private::requestSequenceIcon(const QModelIndex& index, 00492 int sequenceIndex) 00493 { 00494 if (m_pendingItems.isEmpty() || (sequenceIndex == 0)) { 00495 KDirModel* dirModel = m_dirModel.data(); 00496 if (!dirModel) { 00497 return; 00498 } 00499 00500 KFileItem item = dirModel->itemForIndex(index); 00501 if (sequenceIndex == 0) { 00502 m_sequenceIndices.remove(item.url()); 00503 } else { 00504 m_sequenceIndices.insert(item.url(), sequenceIndex); 00505 } 00506 00508 updateIcons(KFileItemList() << item); 00509 } 00510 } 00511 00512 void KFilePreviewGenerator::Private::updateIcons(const KFileItemList& items) 00513 { 00514 if (items.count() <= 0) { 00515 return; 00516 } 00517 00518 applyCutItemEffect(items); 00519 00520 KFileItemList orderedItems = items; 00521 orderItems(orderedItems); 00522 00523 foreach (const KFileItem& item, orderedItems) { 00524 m_pendingItems.append(item); 00525 } 00526 00527 if (m_previewShown) { 00528 createPreviews(orderedItems); 00529 } else { 00530 startMimeTypeResolving(); 00531 } 00532 } 00533 00534 void KFilePreviewGenerator::Private::updateIcons(const QModelIndex& topLeft, 00535 const QModelIndex& bottomRight) 00536 { 00537 if (m_internalDataChange > 0) { 00538 // QAbstractItemModel::setData() has been invoked internally by the KFilePreviewGenerator. 00539 // The signal dataChanged() is connected with this method, but previews only need 00540 // to be generated when an external data change has occurred. 00541 return; 00542 } 00543 00544 // dataChanged emitted for the root dir (e.g. permission changes) 00545 if (!topLeft.isValid() || !bottomRight.isValid()) { 00546 return; 00547 } 00548 00549 KDirModel* dirModel = m_dirModel.data(); 00550 if (!dirModel) { 00551 return; 00552 } 00553 00554 KFileItemList itemList; 00555 for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { 00556 const QModelIndex index = dirModel->index(row, 0); 00557 if (!index.isValid()) { 00558 continue; 00559 } 00560 const KFileItem item = dirModel->itemForIndex(index); 00561 Q_ASSERT(!item.isNull()); 00562 00563 if (m_previewShown) { 00564 const KUrl url = item.url(); 00565 const bool hasChanged = m_changedItems.contains(url); // O(1) 00566 m_changedItems.insert(url, hasChanged); 00567 if (!hasChanged) { 00568 // only update the icon if it has not been already updated within 00569 // the last 5 seconds (the other icons will be updated later with 00570 // the help of m_changedItemsTimer) 00571 itemList.append(item); 00572 } 00573 } else { 00574 itemList.append(item); 00575 } 00576 } 00577 00578 updateIcons(itemList); 00579 m_changedItemsTimer->start(); 00580 } 00581 00582 void KFilePreviewGenerator::Private::addToPreviewQueue(const KFileItem& item, const QPixmap& pixmap) 00583 { 00584 KIO::PreviewJob* senderJob = qobject_cast<KIO::PreviewJob*>(q->sender()); 00585 Q_ASSERT(senderJob != 0); 00586 if (senderJob != 0) { 00587 QMap<KUrl, int>::iterator it = m_sequenceIndices.find(item.url()); 00588 if (senderJob->sequenceIndex() && (it == m_sequenceIndices.end() || *it != senderJob->sequenceIndex())) { 00589 return; // the sequence index does not match the one we want 00590 } 00591 if (!senderJob->sequenceIndex() && it != m_sequenceIndices.end()) { 00592 return; // the sequence index does not match the one we want 00593 } 00594 00595 m_sequenceIndices.erase(it); 00596 } 00597 00598 if (!m_previewShown) { 00599 // the preview has been canceled in the meantime 00600 return; 00601 } 00602 00603 KDirModel* dirModel = m_dirModel.data(); 00604 if (!dirModel) { 00605 return; 00606 } 00607 00608 // check whether the item is part of the directory lister (it is possible 00609 // that a preview from an old directory lister is received) 00610 bool isOldPreview = true; 00611 00612 KUrl itemParentDir(item.url()); 00613 itemParentDir.setPath(itemParentDir.directory()); 00614 00615 foreach (const KUrl& dir, dirModel->dirLister()->directories()) { 00616 if (dir == itemParentDir || !dir.hasPath()) { 00617 isOldPreview = false; 00618 break; 00619 } 00620 } 00621 if (isOldPreview) { 00622 return; 00623 } 00624 00625 QPixmap icon = pixmap; 00626 00627 const QString mimeType = item.mimetype(); 00628 const int slashIndex = mimeType.indexOf(QLatin1Char('/')); 00629 const QString mimeTypeGroup = mimeType.left(slashIndex); 00630 if ((mimeTypeGroup != QLatin1String("image")) || !applyImageFrame(icon)) { 00631 limitToSize(icon, m_viewAdapter->iconSize()); 00632 } 00633 00634 if (m_hasCutSelection && isCutItem(item)) { 00635 // apply the disabled effect to the icon for marking it as "cut item" 00636 // and apply the icon to the item 00637 KIconEffect *iconEffect = KIconLoader::global()->iconEffect(); 00638 icon = iconEffect->apply(icon, KIconLoader::Desktop, KIconLoader::DisabledState); 00639 } 00640 00641 // remember the preview and URL, so that it can be applied to the model 00642 // in KFilePreviewGenerator::dispatchIconUpdateQueue() 00643 ItemInfo preview; 00644 preview.url = item.url(); 00645 preview.pixmap = icon; 00646 m_previews.append(preview); 00647 00648 m_dispatchedItems.append(item); 00649 } 00650 00651 void KFilePreviewGenerator::Private::slotPreviewJobFinished(KJob* job) 00652 { 00653 const int index = m_previewJobs.indexOf(job); 00654 m_previewJobs.removeAt(index); 00655 00656 if (m_previewJobs.isEmpty()) { 00657 if (m_clearItemQueues) { 00658 m_pendingItems.clear(); 00659 m_dispatchedItems.clear(); 00660 m_pendingVisibleIconUpdates = 0; 00661 QMetaObject::invokeMethod(q, "dispatchIconUpdateQueue", Qt::QueuedConnection); 00662 } 00663 m_sequenceIndices.clear(); // just to be sure that we don't leak anything 00664 } 00665 } 00666 00667 void KFilePreviewGenerator::Private::updateCutItems() 00668 { 00669 KDirModel* dirModel = m_dirModel.data(); 00670 if (!dirModel) { 00671 return; 00672 } 00673 00674 DataChangeObtainer obt(this); 00675 clearCutItemsCache(); 00676 00677 KFileItemList items; 00678 KDirLister* dirLister = dirModel->dirLister(); 00679 const KUrl::List dirs = dirLister->directories(); 00680 foreach (const KUrl& url, dirs) { 00681 items << dirLister->itemsForDir(url); 00682 } 00683 applyCutItemEffect(items); 00684 } 00685 00686 void KFilePreviewGenerator::Private::clearCutItemsCache() 00687 { 00688 KDirModel* dirModel = m_dirModel.data(); 00689 if (!dirModel) { 00690 return; 00691 } 00692 00693 DataChangeObtainer obt(this); 00694 KFileItemList previews; 00695 // Reset the icons of all items that are stored in the cache 00696 // to use their default MIME type icon. 00697 foreach (const KUrl& url, m_cutItemsCache.keys()) { 00698 const QModelIndex index = dirModel->indexForUrl(url); 00699 if (index.isValid()) { 00700 dirModel->setData(index, QIcon(), Qt::DecorationRole); 00701 if (m_previewShown) { 00702 previews.append(dirModel->itemForIndex(index)); 00703 } 00704 } 00705 } 00706 m_cutItemsCache.clear(); 00707 00708 if (previews.size() > 0) { 00709 // assure that the previews gets restored 00710 Q_ASSERT(m_previewShown); 00711 orderItems(previews); 00712 updateIcons(previews); 00713 } 00714 } 00715 00716 void KFilePreviewGenerator::Private::dispatchIconUpdateQueue() 00717 { 00718 KDirModel* dirModel = m_dirModel.data(); 00719 if (!dirModel) { 00720 return; 00721 } 00722 00723 const int count = m_previewShown ? m_previews.count() 00724 : m_resolvedMimeTypes.count(); 00725 if (count > 0) { 00726 LayoutBlocker blocker(m_itemView); 00727 DataChangeObtainer obt(this); 00728 00729 if (m_previewShown) { 00730 // dispatch preview queue 00731 foreach (const ItemInfo& preview, m_previews) { 00732 const QModelIndex idx = dirModel->indexForUrl(preview.url); 00733 if (idx.isValid() && (idx.column() == 0)) { 00734 dirModel->setData(idx, QIcon(preview.pixmap), Qt::DecorationRole); 00735 } 00736 } 00737 m_previews.clear(); 00738 } else { 00739 // dispatch mime type queue 00740 foreach (const KFileItem& item, m_resolvedMimeTypes) { 00741 const QModelIndex idx = dirModel->indexForItem(item); 00742 dirModel->itemChanged(idx); 00743 } 00744 m_resolvedMimeTypes.clear(); 00745 } 00746 00747 m_pendingVisibleIconUpdates -= count; 00748 if (m_pendingVisibleIconUpdates < 0) { 00749 m_pendingVisibleIconUpdates = 0; 00750 } 00751 } 00752 00753 if (m_pendingVisibleIconUpdates > 0) { 00754 // As long as there are pending previews for visible items, poll 00755 // the preview queue periodically. If there are no pending previews, 00756 // the queue is dispatched in slotPreviewJobFinished(). 00757 m_iconUpdateTimer->start(); 00758 } 00759 } 00760 00761 void KFilePreviewGenerator::Private::pauseIconUpdates() 00762 { 00763 m_iconUpdatesPaused = true; 00764 foreach (KJob* job, m_previewJobs) { 00765 Q_ASSERT(job != 0); 00766 job->suspend(); 00767 } 00768 m_scrollAreaTimer->start(); 00769 } 00770 00771 void KFilePreviewGenerator::Private::resumeIconUpdates() 00772 { 00773 m_iconUpdatesPaused = false; 00774 00775 // Before creating new preview jobs the m_pendingItems queue must be 00776 // cleaned up by removing the already dispatched items. Implementation 00777 // note: The order of the m_dispatchedItems queue and the m_pendingItems 00778 // queue is usually equal. So even when having a lot of elements the 00779 // nested loop is no performance bottle neck, as the inner loop is only 00780 // entered once in most cases. 00781 foreach (const KFileItem& item, m_dispatchedItems) { 00782 KFileItemList::iterator begin = m_pendingItems.begin(); 00783 KFileItemList::iterator end = m_pendingItems.end(); 00784 for (KFileItemList::iterator it = begin; it != end; ++it) { 00785 if ((*it).url() == item.url()) { 00786 m_pendingItems.erase(it); 00787 break; 00788 } 00789 } 00790 } 00791 m_dispatchedItems.clear(); 00792 00793 m_pendingVisibleIconUpdates = 0; 00794 dispatchIconUpdateQueue(); 00795 00796 00797 if (m_previewShown) { 00798 KFileItemList orderedItems = m_pendingItems; 00799 orderItems(orderedItems); 00800 00801 // Kill all suspended preview jobs. Usually when a preview job 00802 // has been finished, slotPreviewJobFinished() clears all item queues. 00803 // This is not wanted in this case, as a new job is created afterwards 00804 // for m_pendingItems. 00805 m_clearItemQueues = false; 00806 killPreviewJobs(); 00807 m_clearItemQueues = true; 00808 00809 createPreviews(orderedItems); 00810 } else { 00811 orderItems(m_pendingItems); 00812 startMimeTypeResolving(); 00813 } 00814 } 00815 00816 void KFilePreviewGenerator::Private::startMimeTypeResolving() 00817 { 00818 resolveMimeType(); 00819 m_iconUpdateTimer->start(); 00820 } 00821 00822 void KFilePreviewGenerator::Private::resolveMimeType() 00823 { 00824 if (m_pendingItems.isEmpty()) { 00825 return; 00826 } 00827 00828 // resolve at least one MIME type 00829 bool resolved = false; 00830 do { 00831 KFileItem item = m_pendingItems.takeFirst(); 00832 if (item.isMimeTypeKnown()) { 00833 if (m_pendingVisibleIconUpdates > 0) { 00834 // The item is visible and the MIME type already known. 00835 // Decrease the update counter for dispatchIconUpdateQueue(): 00836 --m_pendingVisibleIconUpdates; 00837 } 00838 } else { 00839 // The MIME type is unknown and must get resolved. The 00840 // directory model is not informed yet, as a single update 00841 // would be very expensive. Instead the item is remembered in 00842 // m_resolvedMimeTypes and will be dispatched later 00843 // by dispatchIconUpdateQueue(). 00844 item.determineMimeType(); 00845 m_resolvedMimeTypes.append(item); 00846 resolved = true; 00847 } 00848 } while (!resolved && !m_pendingItems.isEmpty()); 00849 00850 if (m_pendingItems.isEmpty()) { 00851 // All MIME types have been resolved now. Assure 00852 // that the directory model gets informed about 00853 // this, so that an update of the icons is done. 00854 dispatchIconUpdateQueue(); 00855 } else if (!m_iconUpdatesPaused) { 00856 // assure that the MIME type of the next 00857 // item will be resolved asynchronously 00858 QMetaObject::invokeMethod(q, "resolveMimeType", Qt::QueuedConnection); 00859 } 00860 } 00861 00862 bool KFilePreviewGenerator::Private::isCutItem(const KFileItem& item) const 00863 { 00864 const QMimeData* mimeData = QApplication::clipboard()->mimeData(); 00865 const KUrl::List cutUrls = KUrl::List::fromMimeData(mimeData); 00866 return cutUrls.contains(item.url()); 00867 } 00868 00869 void KFilePreviewGenerator::Private::applyCutItemEffect(const KFileItemList& items) 00870 { 00871 const QMimeData* mimeData = QApplication::clipboard()->mimeData(); 00872 m_hasCutSelection = decodeIsCutSelection(mimeData); 00873 if (!m_hasCutSelection) { 00874 return; 00875 } 00876 00877 KDirModel* dirModel = m_dirModel.data(); 00878 if (!dirModel) { 00879 return; 00880 } 00881 00882 const QSet<KUrl> cutUrls = KUrl::List::fromMimeData(mimeData).toSet(); 00883 00884 DataChangeObtainer obt(this); 00885 KIconEffect *iconEffect = KIconLoader::global()->iconEffect(); 00886 foreach (const KFileItem& item, items) { 00887 if (cutUrls.contains(item.url())) { 00888 const QModelIndex index = dirModel->indexForItem(item); 00889 const QVariant value = dirModel->data(index, Qt::DecorationRole); 00890 if (value.type() == QVariant::Icon) { 00891 const QIcon icon(qvariant_cast<QIcon>(value)); 00892 const QSize actualSize = icon.actualSize(m_viewAdapter->iconSize()); 00893 QPixmap pixmap = icon.pixmap(actualSize); 00894 00895 const QHash<KUrl, QPixmap>::const_iterator cacheIt = m_cutItemsCache.constFind(item.url()); 00896 if ((cacheIt == m_cutItemsCache.constEnd()) || (cacheIt->cacheKey() != pixmap.cacheKey())) { 00897 pixmap = iconEffect->apply(pixmap, KIconLoader::Desktop, KIconLoader::DisabledState); 00898 dirModel->setData(index, QIcon(pixmap), Qt::DecorationRole); 00899 00900 m_cutItemsCache.insert(item.url(), pixmap); 00901 } 00902 } 00903 } 00904 } 00905 } 00906 00907 bool KFilePreviewGenerator::Private::applyImageFrame(QPixmap& icon) 00908 { 00909 const QSize maxSize = m_viewAdapter->iconSize(); 00910 00911 // The original size of an image is not exported by the thumbnail mechanism. 00912 // Still it would be helpful to not apply an image frame for e. g. icons that 00913 // fit into the given boundaries: 00914 const bool isIconCandidate = (icon.width() == icon.height()) && 00915 ((icon.width() & 0x7) == 0); 00916 00917 const bool applyFrame = (maxSize.width() > KIconLoader::SizeSmallMedium) && 00918 (maxSize.height() > KIconLoader::SizeSmallMedium) && 00919 !isIconCandidate; 00920 if (!applyFrame) { 00921 // the maximum size or the image itself is too small for a frame 00922 return false; 00923 } 00924 00925 // resize the icon to the maximum size minus the space required for the frame 00926 const QSize size(maxSize.width() - TileSet::LeftMargin - TileSet::RightMargin, 00927 maxSize.height() - TileSet::TopMargin - TileSet::BottomMargin); 00928 limitToSize(icon, size); 00929 00930 if (m_tileSet == 0) { 00931 m_tileSet = new TileSet(); 00932 } 00933 00934 QPixmap framedIcon(icon.size().width() + TileSet::LeftMargin + TileSet::RightMargin, 00935 icon.size().height() + TileSet::TopMargin + TileSet::BottomMargin); 00936 framedIcon.fill(Qt::transparent); 00937 00938 QPainter painter; 00939 painter.begin(&framedIcon); 00940 painter.setCompositionMode(QPainter::CompositionMode_Source); 00941 m_tileSet->paint(&painter, framedIcon.rect()); 00942 painter.setCompositionMode(QPainter::CompositionMode_SourceOver); 00943 painter.drawPixmap(TileSet::LeftMargin, TileSet::TopMargin, icon); 00944 painter.end(); 00945 00946 icon = framedIcon; 00947 return true; 00948 } 00949 00950 void KFilePreviewGenerator::Private::limitToSize(QPixmap& icon, const QSize& maxSize) 00951 { 00952 if ((icon.width() > maxSize.width()) || (icon.height() > maxSize.height())) { 00953 #if defined(Q_WS_X11) && defined(HAVE_XRENDER) 00954 // Assume that the texture size limit is 2048x2048 00955 if ((icon.width() <= 2048) && (icon.height() <= 2048) && icon.x11PictureHandle()) { 00956 QSize size = icon.size(); 00957 size.scale(maxSize, Qt::KeepAspectRatio); 00958 00959 const qreal factor = size.width() / qreal(icon.width()); 00960 00961 XTransform xform = {{ 00962 { XDoubleToFixed(1 / factor), 0, 0 }, 00963 { 0, XDoubleToFixed(1 / factor), 0 }, 00964 { 0, 0, XDoubleToFixed(1) } 00965 }}; 00966 00967 QPixmap pixmap(size); 00968 pixmap.fill(Qt::transparent); 00969 00970 Display* dpy = QX11Info::display(); 00971 00972 XRenderPictureAttributes attr; 00973 attr.repeat = RepeatPad; 00974 XRenderChangePicture(dpy, icon.x11PictureHandle(), CPRepeat, &attr); 00975 00976 XRenderSetPictureFilter(dpy, icon.x11PictureHandle(), FilterBilinear, 0, 0); 00977 XRenderSetPictureTransform(dpy, icon.x11PictureHandle(), &xform); 00978 XRenderComposite(dpy, PictOpOver, icon.x11PictureHandle(), None, pixmap.x11PictureHandle(), 00979 0, 0, 0, 0, 0, 0, pixmap.width(), pixmap.height()); 00980 icon = pixmap; 00981 } else { 00982 icon = icon.scaled(maxSize, Qt::KeepAspectRatio, Qt::FastTransformation); 00983 } 00984 #else 00985 icon = icon.scaled(maxSize, Qt::KeepAspectRatio, Qt::FastTransformation); 00986 #endif 00987 } 00988 } 00989 00990 void KFilePreviewGenerator::Private::createPreviews(const KFileItemList& items) 00991 { 00992 if (items.count() == 0) { 00993 return; 00994 } 00995 00996 const QMimeData* mimeData = QApplication::clipboard()->mimeData(); 00997 m_hasCutSelection = decodeIsCutSelection(mimeData); 00998 00999 // PreviewJob internally caches items always with the size of 01000 // 128 x 128 pixels or 256 x 256 pixels. A downscaling is done 01001 // by PreviewJob if a smaller size is requested. For images KFilePreviewGenerator must 01002 // do a downscaling anyhow because of the frame, so in this case only the provided 01003 // cache sizes are requested. 01004 KFileItemList imageItems; 01005 KFileItemList otherItems; 01006 QString mimeType; 01007 QString mimeTypeGroup; 01008 foreach (const KFileItem& item, items) { 01009 mimeType = item.mimetype(); 01010 const int slashIndex = mimeType.indexOf(QLatin1Char('/')); 01011 mimeTypeGroup = mimeType.left(slashIndex); 01012 if (mimeTypeGroup == QLatin1String("image")) { 01013 imageItems.append(item); 01014 } else { 01015 otherItems.append(item); 01016 } 01017 } 01018 const QSize size = m_viewAdapter->iconSize(); 01019 startPreviewJob(otherItems, size.width(), size.height()); 01020 01021 const int cacheSize = (size.width() > 128) || (size.height() > 128) ? 256 : 128; 01022 startPreviewJob(imageItems, cacheSize, cacheSize); 01023 01024 m_iconUpdateTimer->start(); 01025 } 01026 01027 void KFilePreviewGenerator::Private::startPreviewJob(const KFileItemList& items, int width, int height) 01028 { 01029 if (items.count() > 0) { 01030 if (m_enabledPlugins.isEmpty()) { 01031 const KConfigGroup globalConfig(KGlobal::config(), "PreviewSettings"); 01032 m_enabledPlugins = globalConfig.readEntry("Plugins", QStringList() 01033 << "directorythumbnail" 01034 << "imagethumbnail" 01035 << "jpegthumbnail"); 01036 } 01037 01038 KIO::PreviewJob* job = KIO::filePreview(items, width, height, 0, 70, true, true, &m_enabledPlugins); 01039 01040 // Set the sequence index to the target. We only need to check if items.count() == 1, 01041 // because requestSequenceIcon(..) creates exactly such a request. 01042 if (!m_sequenceIndices.isEmpty() && (items.count() == 1)) { 01043 QMap<KUrl, int>::iterator it = m_sequenceIndices.find(items[0].url()); 01044 if (it != m_sequenceIndices.end()) { 01045 job->setSequenceIndex(*it); 01046 } 01047 } 01048 01049 connect(job, SIGNAL(gotPreview(const KFileItem&, const QPixmap&)), 01050 q, SLOT(addToPreviewQueue(const KFileItem&, const QPixmap&))); 01051 connect(job, SIGNAL(finished(KJob*)), 01052 q, SLOT(slotPreviewJobFinished(KJob*))); 01053 m_previewJobs.append(job); 01054 } 01055 } 01056 01057 void KFilePreviewGenerator::Private::killPreviewJobs() 01058 { 01059 foreach (KJob* job, m_previewJobs) { 01060 Q_ASSERT(job != 0); 01061 job->kill(); 01062 } 01063 m_previewJobs.clear(); 01064 m_sequenceIndices.clear(); 01065 01066 m_iconUpdateTimer->stop(); 01067 m_scrollAreaTimer->stop(); 01068 m_changedItemsTimer->stop(); 01069 } 01070 01071 void KFilePreviewGenerator::Private::orderItems(KFileItemList& items) 01072 { 01073 KDirModel* dirModel = m_dirModel.data(); 01074 if (!dirModel) { 01075 return; 01076 } 01077 01078 // Order the items in a way that the preview for the visible items 01079 // is generated first, as this improves the feeled performance a lot. 01080 const bool hasProxy = (m_proxyModel != 0); 01081 const int itemCount = items.count(); 01082 const QRect visibleArea = m_viewAdapter->visibleArea(); 01083 01084 QModelIndex dirIndex; 01085 QRect itemRect; 01086 int insertPos = 0; 01087 for (int i = 0; i < itemCount; ++i) { 01088 dirIndex = dirModel->indexForItem(items.at(i)); // O(n) (n = number of rows) 01089 if (hasProxy) { 01090 const QModelIndex proxyIndex = m_proxyModel->mapFromSource(dirIndex); 01091 itemRect = m_viewAdapter->visualRect(proxyIndex); 01092 } else { 01093 itemRect = m_viewAdapter->visualRect(dirIndex); 01094 } 01095 01096 if (itemRect.intersects(visibleArea)) { 01097 // The current item is (at least partly) visible. Move it 01098 // to the front of the list, so that the preview is 01099 // generated earlier. 01100 items.insert(insertPos, items.at(i)); 01101 items.removeAt(i + 1); 01102 ++insertPos; 01103 ++m_pendingVisibleIconUpdates; 01104 } 01105 } 01106 } 01107 01108 bool KFilePreviewGenerator::Private::decodeIsCutSelection(const QMimeData* mimeData) 01109 { 01110 const QByteArray data = mimeData->data("application/x-kde-cutselection"); 01111 if (data.isEmpty()) { 01112 return false; 01113 } else { 01114 return data.at(0) == QLatin1Char('1'); 01115 } 01116 } 01117 01118 void KFilePreviewGenerator::Private::addItemsToList(const QModelIndex& index, KFileItemList& list) 01119 { 01120 KDirModel* dirModel = m_dirModel.data(); 01121 if (!dirModel) { 01122 return; 01123 } 01124 01125 const int rowCount = dirModel->rowCount(index); 01126 for (int row = 0; row < rowCount; ++row) { 01127 const QModelIndex subIndex = dirModel->index(row, 0, index); 01128 KFileItem item = dirModel->itemForIndex(subIndex); 01129 list.append(item); 01130 01131 if (dirModel->rowCount(subIndex) > 0) { 01132 // the model is hierarchical (treeview) 01133 addItemsToList(subIndex, list); 01134 } 01135 } 01136 } 01137 01138 void KFilePreviewGenerator::Private::delayedIconUpdate() 01139 { 01140 KDirModel* dirModel = m_dirModel.data(); 01141 if (!dirModel) { 01142 return; 01143 } 01144 01145 // Precondition: No items have been changed within the last 01146 // 5 seconds. This means that items that have been changed constantly 01147 // due to a copy operation should be updated now. 01148 01149 KFileItemList itemList; 01150 01151 QHash<KUrl, bool>::const_iterator it = m_changedItems.constBegin(); 01152 while (it != m_changedItems.constEnd()) { 01153 const bool hasChanged = it.value(); 01154 if (hasChanged) { 01155 const QModelIndex index = dirModel->indexForUrl(it.key()); 01156 const KFileItem item = dirModel->itemForIndex(index); 01157 itemList.append(item); 01158 } 01159 ++it; 01160 } 01161 m_changedItems.clear(); 01162 01163 updateIcons(itemList); 01164 } 01165 01166 void KFilePreviewGenerator::Private::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) 01167 { 01168 if (m_changedItems.isEmpty()) { 01169 return; 01170 } 01171 01172 KDirModel* dirModel = m_dirModel.data(); 01173 if (!dirModel) { 01174 return; 01175 } 01176 01177 for (int row = start; row <= end; row++) { 01178 const QModelIndex index = dirModel->index(row, 0, parent); 01179 01180 const KFileItem item = dirModel->itemForIndex(index); 01181 if (!item.isNull()) { 01182 m_changedItems.remove(item.url()); 01183 } 01184 01185 if (dirModel->hasChildren(index)) { 01186 rowsAboutToBeRemoved(index, 0, dirModel->rowCount(index) - 1); 01187 } 01188 } 01189 } 01190 01191 KFilePreviewGenerator::KFilePreviewGenerator(QAbstractItemView* parent) : 01192 QObject(parent), 01193 d(new Private(this, new KIO::DefaultViewAdapter(parent, this), parent->model())) 01194 { 01195 d->m_itemView = parent; 01196 } 01197 01198 KFilePreviewGenerator::KFilePreviewGenerator(KAbstractViewAdapter* parent, QAbstractProxyModel* model) : 01199 QObject(parent), 01200 d(new Private(this, parent, model)) 01201 { 01202 } 01203 01204 KFilePreviewGenerator::~KFilePreviewGenerator() 01205 { 01206 delete d; 01207 } 01208 01209 void KFilePreviewGenerator::setPreviewShown(bool show) 01210 { 01211 KDirModel* dirModel = d->m_dirModel.data(); 01212 if (show && (!d->m_viewAdapter->iconSize().isValid() || !dirModel)) { 01213 // the view must provide an icon size and a directory model, 01214 // otherwise the showing the previews will get ignored 01215 return; 01216 } 01217 01218 if (d->m_previewShown != show) { 01219 d->m_previewShown = show; 01220 if (!show) { 01221 // When turning off the previews, the directory must be refreshed 01222 // so that the previews get be replaced again by the MIME type icons. 01223 KDirLister* dirLister = dirModel->dirLister(); 01224 const KUrl url = dirLister->url(); 01225 if (url.isValid()) { 01226 dirLister->openUrl(url, KDirLister::NoFlags); 01227 } 01228 } 01229 updateIcons(); 01230 } 01231 } 01232 01233 bool KFilePreviewGenerator::isPreviewShown() const 01234 { 01235 return d->m_previewShown; 01236 } 01237 01238 // deprecated (use updateIcons() instead) 01239 void KFilePreviewGenerator::updatePreviews() 01240 { 01241 updateIcons(); 01242 } 01243 01244 void KFilePreviewGenerator::updateIcons() 01245 { 01246 d->killPreviewJobs(); 01247 01248 d->clearCutItemsCache(); 01249 d->m_pendingItems.clear(); 01250 d->m_dispatchedItems.clear(); 01251 01252 KFileItemList itemList; 01253 d->addItemsToList(QModelIndex(), itemList); 01254 01255 d->updateIcons(itemList); 01256 } 01257 01258 void KFilePreviewGenerator::cancelPreviews() 01259 { 01260 d->killPreviewJobs(); 01261 d->m_pendingItems.clear(); 01262 d->m_dispatchedItems.clear(); 01263 updateIcons(); 01264 } 01265 01266 void KFilePreviewGenerator::setEnabledPlugins(const QStringList& plugins) 01267 { 01268 d->m_enabledPlugins = plugins; 01269 } 01270 01271 QStringList KFilePreviewGenerator::enabledPlugins() const 01272 { 01273 return d->m_enabledPlugins; 01274 } 01275 01276 #include "kfilepreviewgenerator.moc"
KDE 4.6 API Reference