diff --git a/app/browsemainpage.cpp b/app/browsemainpage.cpp index 0f3137fa..39c28b3e 100644 --- a/app/browsemainpage.cpp +++ b/app/browsemainpage.cpp @@ -1,404 +1,405 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2008 Aurélien Gâteau This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. */ // Self #include "browsemainpage.h" // Qt #include #include #include // KDE #include #include #include #include #include #include #include #include #include #include // Local #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Gwenview { inline Sorting::Enum sortingFromSortAction(const QAction* action) { Q_ASSERT(action); return Sorting::Enum(action->data().toInt()); } struct BrowseMainPagePrivate : public Ui_BrowseMainPage { BrowseMainPage* q; GvCore* mGvCore; KFilePlacesModel* mFilePlacesModel; KUrlNavigator* mUrlNavigator; SortedDirModel* mDirModel; int mDocumentCount; KActionCollection* mActionCollection; FilterController* mFilterController; KSelectAction* mSortAction; QActionGroup* mThumbnailDetailsActionGroup; PreviewItemDelegate* mDelegate; void setupWidgets() { setupUi(q); q->layout()->setMargin(0); // mThumbnailView mThumbnailView->setModel(mDirModel); mDelegate = new PreviewItemDelegate(mThumbnailView); mThumbnailView->setItemDelegate(mDelegate); mThumbnailView->setSelectionMode(QAbstractItemView::ExtendedSelection); // mUrlNavigator (use stupid layouting code because KUrlNavigator ctor // can't be used directly from Designer) mFilePlacesModel = new KFilePlacesModel(q); mUrlNavigator = new KUrlNavigator(mFilePlacesModel, QUrl(), mUrlNavigatorContainer); mUrlNavigatorContainer->setAutoFillBackground(true); QVBoxLayout* layout = new QVBoxLayout(mUrlNavigatorContainer); layout->setMargin(0); layout->addWidget(mUrlNavigator); QObject::connect(mUrlNavigator, SIGNAL(urlsDropped(QUrl,QDropEvent*)), q, SLOT(slotUrlsDropped(QUrl,QDropEvent*))); updateUrlNavigatorBackgroundColor(); // Thumbnail slider QObject::connect(mThumbnailSlider, SIGNAL(valueChanged(int)), mThumbnailView, SLOT(setThumbnailWidth(int))); QObject::connect(mThumbnailView, SIGNAL(thumbnailWidthChanged(int)), mThumbnailSlider, SLOT(setValue(int))); } QAction *thumbnailDetailAction(const QString &text, PreviewItemDelegate::ThumbnailDetail detail) { QAction *action = new QAction(q); action->setText(text); action->setCheckable(true); action->setChecked(GwenviewConfig::thumbnailDetails() & detail); action->setData(QVariant(detail)); mThumbnailDetailsActionGroup->addAction(action); QObject::connect(action, SIGNAL(triggered(bool)), q, SLOT(updateThumbnailDetails())); return action; } void setupActions(KActionCollection* actionCollection) { KActionCategory* view = new KActionCategory(i18nc("@title actions category - means actions changing smth in interface", "View"), actionCollection); QAction * action = view->addAction("edit_location", q, SLOT(editLocation())); action->setText(i18nc("@action:inmenu Navigation Bar", "Edit Location")); actionCollection->setDefaultShortcut(action, Qt::Key_F6); mSortAction = view->add("sort_order"); mSortAction->setText(i18nc("@action:inmenu", "Sort By")); action = mSortAction->addAction(i18nc("@addAction:inmenu", "Name")); action->setData(QVariant(Sorting::Name)); action = mSortAction->addAction(i18nc("@addAction:inmenu", "Date")); action->setData(QVariant(Sorting::Date)); action = mSortAction->addAction(i18nc("@addAction:inmenu", "Size")); action->setData(QVariant(Sorting::Size)); QObject::connect(mSortAction, SIGNAL(triggered(QAction*)), q, SLOT(updateSortOrder())); mThumbnailDetailsActionGroup = new QActionGroup(q); mThumbnailDetailsActionGroup->setExclusive(false); KActionMenu* thumbnailDetailsAction = view->add("thumbnail_details"); thumbnailDetailsAction->setText(i18nc("@action:inmenu", "Thumbnail Details")); thumbnailDetailsAction->addAction(thumbnailDetailAction(i18nc("@action:inmenu", "Filename"), PreviewItemDelegate::FileNameDetail)); thumbnailDetailsAction->addAction(thumbnailDetailAction(i18nc("@action:inmenu", "Date"), PreviewItemDelegate::DateDetail)); thumbnailDetailsAction->addAction(thumbnailDetailAction(i18nc("@action:inmenu", "Image Size"), PreviewItemDelegate::ImageSizeDetail)); thumbnailDetailsAction->addAction(thumbnailDetailAction(i18nc("@action:inmenu", "File Size"), PreviewItemDelegate::FileSizeDetail)); #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE thumbnailDetailsAction->addAction(thumbnailDetailAction(i18nc("@action:inmenu", "Rating"), PreviewItemDelegate::RatingDetail)); #endif KActionCategory* file = new KActionCategory(i18nc("@title actions category", "File"), actionCollection); action = file->addAction("add_folder_to_places", q, SLOT(addFolderToPlaces())); action->setText(i18nc("@action:inmenu", "Add Folder to Places")); } void setupFilterController() { QMenu* menu = new QMenu(mAddFilterButton); mFilterController = new FilterController(mFilterFrame, mDirModel); Q_FOREACH(QAction * action, mFilterController->actionList()) { menu->addAction(action); } mAddFilterButton->setMenu(menu); } void setupFullScreenToolBar() { mFullScreenToolBar->setIconDimensions(KIconLoader::SizeMedium); mFullScreenToolBar->setToolButtonStyle(Qt::ToolButtonIconOnly); mFullScreenToolBar->addAction(mActionCollection->action("browse")); mFullScreenToolBar->addAction(mActionCollection->action("view")); mFullScreenToolBar2->setIconDimensions(KIconLoader::SizeMedium); mFullScreenToolBar2->setToolButtonStyle(Qt::ToolButtonIconOnly); mFullScreenToolBar2->addAction(mActionCollection->action("leave_fullscreen")); } void updateDocumentCountLabel() { QString text = i18ncp("@label", "%1 document", "%1 documents", mDocumentCount); mDocumentCountLabel->setText(text); } void setupDocumentCountConnections() { QObject::connect(mDirModel, SIGNAL(rowsInserted(QModelIndex,int,int)), q, SLOT(slotDirModelRowsInserted(QModelIndex,int,int))); QObject::connect(mDirModel, SIGNAL(rowsAboutToBeRemoved(QModelIndex,int,int)), q, SLOT(slotDirModelRowsAboutToBeRemoved(QModelIndex,int,int))); QObject::connect(mDirModel, SIGNAL(modelReset()), q, SLOT(slotDirModelReset())); } int documentCountInIndexRange(const QModelIndex& parent, int start, int end) { int count = 0; for (int row = start; row <= end; ++row) { QModelIndex index = mDirModel->index(row, 0, parent); KFileItem item = mDirModel->itemForIndex(index); if (!ArchiveUtils::fileItemIsDirOrArchive(item)) { ++count; } } return count; } void updateUrlNavigatorBackgroundColor() { QPalette pal(q->palette()); pal.setColor(QPalette::Window, pal.color(QPalette::Window).dark(110)); mUrlNavigatorContainer->setPalette(pal); } }; BrowseMainPage::BrowseMainPage(QWidget* parent, KActionCollection* actionCollection, GvCore* gvCore) : QWidget(parent) , d(new BrowseMainPagePrivate) { d->q = this; d->mGvCore = gvCore; d->mDirModel = gvCore->sortedDirModel(); d->mDocumentCount = 0; d->mActionCollection = actionCollection; d->setupWidgets(); d->setupActions(actionCollection); d->setupFilterController(); d->setupDocumentCountConnections(); loadConfig(); updateSortOrder(); updateThumbnailDetails(); } BrowseMainPage::~BrowseMainPage() { delete d; } void BrowseMainPage::loadConfig() { setPalette(d->mGvCore->palette(GvCore::NormalPalette)); d->mThumbnailView->setPalette(d->mGvCore->palette(GvCore::NormalViewPalette)); d->mUrlNavigator->setUrlEditable(GwenviewConfig::urlNavigatorIsEditable()); d->mUrlNavigator->setShowFullPath(GwenviewConfig::urlNavigatorShowFullPath()); d->mThumbnailSlider->setValue(GwenviewConfig::thumbnailSize()); d->mThumbnailSlider->updateToolTip(); // If GwenviewConfig::thumbnailSize() returns the current value of // mThumbnailSlider, it won't emit valueChanged() and the thumbnail view // won't be updated. That's why we do it ourself. d->mThumbnailView->setThumbnailAspectRatio(GwenviewConfig::thumbnailAspectRatio()); d->mThumbnailView->setThumbnailWidth(GwenviewConfig::thumbnailSize()); + d->mThumbnailView->setThumbnailDevicePixelRatio(qApp->devicePixelRatio()); Q_FOREACH(QAction * action, d->mSortAction->actions()) { if (sortingFromSortAction(action) == GwenviewConfig::sorting()) { d->mSortAction->setCurrentAction(action); break; } } } void BrowseMainPage::saveConfig() const { GwenviewConfig::setUrlNavigatorIsEditable(d->mUrlNavigator->isUrlEditable()); GwenviewConfig::setUrlNavigatorShowFullPath(d->mUrlNavigator->showFullPath()); GwenviewConfig::setThumbnailSize(d->mThumbnailSlider->value()); GwenviewConfig::setSorting(sortingFromSortAction(d->mSortAction->currentAction())); GwenviewConfig::setThumbnailDetails(d->mDelegate->thumbnailDetails()); } ThumbnailView* BrowseMainPage::thumbnailView() const { return d->mThumbnailView; } KUrlNavigator* BrowseMainPage::urlNavigator() const { return d->mUrlNavigator; } void BrowseMainPage::reload() { QModelIndexList list = d->mThumbnailView->selectionModel()->selectedIndexes(); Q_FOREACH(const QModelIndex & index, list) { d->mThumbnailView->reloadThumbnail(index); } d->mDirModel->reload(); } void BrowseMainPage::editLocation() { d->mUrlNavigator->setUrlEditable(true); d->mUrlNavigator->setFocus(); } void BrowseMainPage::addFolderToPlaces() { QUrl url = d->mUrlNavigator->locationUrl(); QString text = url.fileName(); if (text.isEmpty()) { text = url.toDisplayString(); } d->mFilePlacesModel->addPlace(text, url); } void BrowseMainPage::slotDirModelRowsInserted(const QModelIndex& parent, int start, int end) { int count = d->documentCountInIndexRange(parent, start, end); if (count > 0) { d->mDocumentCount += count; d->updateDocumentCountLabel(); } } void BrowseMainPage::slotDirModelRowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) { int count = d->documentCountInIndexRange(parent, start, end); if (count > 0) { d->mDocumentCount -= count; d->updateDocumentCountLabel(); } } void BrowseMainPage::slotDirModelReset() { d->mDocumentCount = 0; d->updateDocumentCountLabel(); } void BrowseMainPage::updateSortOrder() { const QAction* action = d->mSortAction->currentAction(); GV_RETURN_IF_FAIL(action); // This works because for now Sorting::Enum maps to KDirModel::ModelColumns d->mDirModel->sort(sortingFromSortAction(action)); } void BrowseMainPage::updateThumbnailDetails() { PreviewItemDelegate::ThumbnailDetails details = 0; Q_FOREACH(const QAction * action, d->mThumbnailDetailsActionGroup->actions()) { if (action->isChecked()) { details |= PreviewItemDelegate::ThumbnailDetail(action->data().toInt()); } } d->mDelegate->setThumbnailDetails(details); } void BrowseMainPage::setFullScreenMode(bool fullScreen) { setPalette(d->mGvCore->palette(fullScreen ? GvCore::FullScreenPalette : GvCore::NormalPalette)); d->mThumbnailView->setPalette(d->mGvCore->palette(fullScreen ? GvCore::FullScreenViewPalette : GvCore::NormalViewPalette)); d->updateUrlNavigatorBackgroundColor(); d->mUrlNavigatorContainer->setContentsMargins( fullScreen ? 6 : 0, 0, 0, 0); PreviewItemDelegate::ContextBarActions actions = PreviewItemDelegate::SelectionAction | PreviewItemDelegate::RotateAction; if (!fullScreen) { actions |= PreviewItemDelegate::FullScreenAction; } d->mDelegate->setContextBarActions(actions); d->mFullScreenToolBar->setVisible(fullScreen); d->mFullScreenToolBar2->setVisible(fullScreen); if (fullScreen && d->mFullScreenToolBar->actions().isEmpty()) { d->setupFullScreenToolBar(); } } void BrowseMainPage::setStatusBarVisible(bool visible) { d->mStatusBarContainer->setVisible(visible); } void BrowseMainPage::slotUrlsDropped(const QUrl &destUrl, QDropEvent* event) { const QList urlList = KUrlMimeData::urlsFromMimeData(event->mimeData()); if (urlList.isEmpty()) { return; } event->acceptProposedAction(); // We can't call FileOperations::showMenuForDroppedUrls() directly because // we need the slot to return so that the drop event is accepted. Otherwise // the drop cursor is still visible when the menu is shown. QMetaObject::invokeMethod(this, "showMenuForDroppedUrls", Qt::QueuedConnection, Q_ARG(QList, urlList), Q_ARG(QUrl, destUrl)); } void BrowseMainPage::showMenuForDroppedUrls(const QList& urlList, const QUrl &destUrl) { FileOperations::showMenuForDroppedUrls(d->mUrlNavigator, urlList, destUrl); } QToolButton* BrowseMainPage::toggleSideBarButton() const { return d->mToggleSideBarButton; } } // namespace diff --git a/app/startmainpage.cpp b/app/startmainpage.cpp index a7087dae..a30a7af0 100644 --- a/app/startmainpage.cpp +++ b/app/startmainpage.cpp @@ -1,313 +1,313 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2008 Aurélien Gâteau This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. */ // Self #include "startmainpage.h" #include // Qt #include #include #ifdef GTK_WORKAROUND_BROKE_IN_KF5_PORT #include #endif #include #include // KDE #include #include // Local #include #include #include #include #include #include #include #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE #include #endif namespace Gwenview { class HistoryThumbnailViewHelper : public AbstractThumbnailViewHelper { public: HistoryThumbnailViewHelper(QObject* parent) : AbstractThumbnailViewHelper(parent) {} void showContextMenu(QWidget*) Q_DECL_OVERRIDE { } void showMenuForUrlDroppedOnViewport(QWidget*, const QList&) Q_DECL_OVERRIDE { } void showMenuForUrlDroppedOnDir(QWidget*, const QList&, const QUrl&) Q_DECL_OVERRIDE { } }; struct StartMainPagePrivate : public Ui_StartMainPage { StartMainPage* q; GvCore* mGvCore; KFilePlacesModel* mBookmarksModel; ThumbnailProvider *mRecentFilesThumbnailProvider; bool mSearchUiInitialized; void setupSearchUi() { #ifdef GWENVIEW_SEMANTICINFO_BACKEND_BALOO mTagView->setModel(TagModel::createAllTagsModel(mTagView, mGvCore->semanticInfoBackEnd())); mTagView->show(); mTagLabel->hide(); #else mTagView->hide(); mTagLabel->hide(); #endif } void updateHistoryTab() { mHistoryWidget->setVisible(GwenviewConfig::historyEnabled()); mHistoryDisabledLabel->setVisible(!GwenviewConfig::historyEnabled()); } void setupHistoryView(ThumbnailView *view) { view->setThumbnailViewHelper(new HistoryThumbnailViewHelper(view)); PreviewItemDelegate* delegate = new PreviewItemDelegate(view); delegate->setContextBarActions(PreviewItemDelegate::NoAction); delegate->setTextElideMode(Qt::ElideLeft); view->setItemDelegate(delegate); - view->setThumbnailWidth(128); + view->setThumbnailWidth(128 * qApp->devicePixelRatio()); view->setCreateThumbnailsForRemoteUrls(false); QModelIndex index = view->model()->index(0, 0); if (index.isValid()) { view->setCurrentIndex(index); } } }; static void initViewPalette(QAbstractItemView* view, const QColor& fgColor) { QWidget* viewport = view->viewport(); QPalette palette = viewport->palette(); palette.setColor(viewport->backgroundRole(), Qt::transparent); palette.setColor(QPalette::WindowText, fgColor); palette.setColor(QPalette::Text, fgColor); // QListView uses QStyledItemDelegate, which uses the view palette for // foreground color, while KFilePlacesView uses the viewport palette. viewport->setPalette(palette); view->setPalette(palette); } static bool styleIsGtkBased() { const char* name = QApplication::style()->metaObject()->className(); return qstrcmp(name, "QGtkStyle") == 0; } StartMainPage::StartMainPage(QWidget* parent, GvCore* gvCore) : QFrame(parent) , d(new StartMainPagePrivate) { d->mRecentFilesThumbnailProvider = 0; d->q = this; d->mGvCore = gvCore; d->mSearchUiInitialized = false; d->setupUi(this); if (styleIsGtkBased()) { #ifdef GTK_WORKAROUND_BROKE_IN_KF5_PORT // Gtk-based styles do not apply the correct background color on tabs. // As a workaround, use the Plastique style instead. QStyle* fix = new QPlastiqueStyle(); fix->setParent(this); d->mHistoryWidget->tabBar()->setStyle(fix); d->mPlacesTagsWidget->tabBar()->setStyle(fix); #endif } setFrameStyle(QFrame::NoFrame); // Bookmark view d->mBookmarksModel = new KFilePlacesModel(this); d->mBookmarksView->setModel(d->mBookmarksModel); d->mBookmarksView->setAutoResizeItemsEnabled(false); connect(d->mBookmarksView, &KFilePlacesView::urlChanged, this, &StartMainPage::urlSelected); // Tag view connect(d->mTagView, &QListView::clicked, this, &StartMainPage::slotTagViewClicked); // Recent folder view connect(d->mRecentFoldersView, &Gwenview::ThumbnailView::indexActivated, this, &StartMainPage::slotListViewActivated); connect(d->mRecentFoldersView, &Gwenview::ThumbnailView::customContextMenuRequested, this, &StartMainPage::showRecentFoldersViewContextMenu); connect(d->mRecentFilesView, &Gwenview::ThumbnailView::indexActivated, this, &StartMainPage::slotListViewActivated); d->updateHistoryTab(); connect(GwenviewConfig::self(), &GwenviewConfig::configChanged, this, &StartMainPage::loadConfig); d->mRecentFoldersView->setFocus(); } StartMainPage::~StartMainPage() { delete d->mRecentFilesThumbnailProvider; delete d; } void StartMainPage::slotTagViewClicked(const QModelIndex& index) { #ifdef GWENVIEW_SEMANTICINFO_BACKEND_BALOO if (!index.isValid()) { return; } // FIXME: Check label encoding const QString tag = index.data().toString(); emit urlSelected(QUrl("tags:/" + tag)); #endif } void StartMainPage::applyPalette(const QPalette& newPalette) { QColor fgColor = newPalette.text().color(); QPalette pal = palette(); pal.setBrush(backgroundRole(), newPalette.base()); pal.setBrush(QPalette::Button, newPalette.base()); pal.setBrush(QPalette::WindowText, fgColor); pal.setBrush(QPalette::ButtonText, fgColor); pal.setBrush(QPalette::Text, fgColor); setPalette(pal); initViewPalette(d->mBookmarksView, fgColor); initViewPalette(d->mTagView, fgColor); initViewPalette(d->mRecentFoldersView, fgColor); initViewPalette(d->mRecentFilesView, fgColor); } void StartMainPage::slotListViewActivated(const QModelIndex& index) { if (!index.isValid()) { return; } QVariant data = index.data(KFilePlacesModel::UrlRole); QUrl url = data.toUrl(); // Prevent dir lister error if (!url.isValid()) { qCritical() << "Tried to open an invalid url"; return; } emit urlSelected(url); } void StartMainPage::showEvent(QShowEvent* event) { if (GwenviewConfig::historyEnabled()) { if (!d->mRecentFoldersView->model()) { d->mRecentFoldersView->setModel(d->mGvCore->recentFoldersModel()); d->setupHistoryView(d->mRecentFoldersView); } if (!d->mRecentFilesView->model()) { d->mRecentFilesView->setModel(d->mGvCore->recentFilesModel()); d->mRecentFilesThumbnailProvider = new ThumbnailProvider(); d->mRecentFilesView->setThumbnailProvider(d->mRecentFilesThumbnailProvider); d->setupHistoryView(d->mRecentFilesView); } } if (!d->mSearchUiInitialized) { d->mSearchUiInitialized = true; d->setupSearchUi(); } QFrame::showEvent(event); } void StartMainPage::showRecentFoldersViewContextMenu(const QPoint& pos) { QAbstractItemView* view = qobject_cast(sender()); QUrl url; QModelIndex index = view->indexAt(pos); if (index.isValid()) { QVariant data = index.data(KFilePlacesModel::UrlRole); url = data.toUrl(); } // Create menu QMenu menu(this); QAction* addToPlacesAction = menu.addAction(QIcon::fromTheme("bookmark-new"), i18n("Add to Places")); QAction* removeAction = menu.addAction(QIcon::fromTheme("edit-delete"), i18n("Forget this Folder")); menu.addSeparator(); QAction* clearAction = menu.addAction(QIcon::fromTheme("edit-delete-all"), i18n("Forget All")); if (!index.isValid()) { if (addToPlacesAction) { addToPlacesAction->setEnabled(false); } removeAction->setEnabled(false); } // Handle menu QAction* action = menu.exec(view->mapToGlobal(pos)); if (!action) { return; } if (action == addToPlacesAction) { QString text = url.fileName(); if (text.isEmpty()) { text = url.toDisplayString(); } d->mBookmarksModel->addPlace(text, url); } else if (action == removeAction) { view->model()->removeRow(index.row()); } else if (action == clearAction) { view->model()->removeRows(0, view->model()->rowCount()); } } void StartMainPage::loadConfig() { d->updateHistoryTab(); applyPalette(d->mGvCore->palette(GvCore::NormalViewPalette)); } ThumbnailView* StartMainPage::recentFoldersView() const { return d->mRecentFoldersView; } } // namespace diff --git a/importer/thumbnailpage.cpp b/importer/thumbnailpage.cpp index 25a11b2e..3e295e79 100644 --- a/importer/thumbnailpage.cpp +++ b/importer/thumbnailpage.cpp @@ -1,458 +1,459 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2009 Aurélien Gâteau This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. */ // Self #include "thumbnailpage.h" // Qt #include #include #include #include #include // KDE #include #include #include #include #include #include #include // Local #include #include #include #include #include #include #include #include #include #include #include #include namespace Gwenview { static const int DEFAULT_THUMBNAIL_SIZE = 128; static const qreal DEFAULT_THUMBNAIL_ASPECT_RATIO = 3. / 2.; static const char* URL_FOR_BASE_URL_GROUP = "UrlForBaseUrl"; class ImporterThumbnailViewHelper : public AbstractThumbnailViewHelper { public: ImporterThumbnailViewHelper(QObject* parent) : AbstractThumbnailViewHelper(parent) {} void showContextMenu(QWidget*) Q_DECL_OVERRIDE {} void showMenuForUrlDroppedOnViewport(QWidget*, const QList&) Q_DECL_OVERRIDE {} void showMenuForUrlDroppedOnDir(QWidget*, const QList&, const QUrl&) Q_DECL_OVERRIDE {} }; inline KFileItem itemForIndex(const QModelIndex& index) { return index.data(KDirModel::FileItemRole).value(); } struct ThumbnailPagePrivate : public Ui_ThumbnailPage { ThumbnailPage* q; SerializedUrlMap mUrlMap; QIcon mSrcBaseIcon; QString mSrcBaseName; QUrl mSrcBaseUrl; QUrl mSrcUrl; KModelIndexProxyMapper* mSrcUrlModelProxyMapper; RecursiveDirModel* mRecursiveDirModel; QAbstractItemModel* mFinalModel; ThumbnailProvider mThumbnailProvider; QPushButton* mImportSelectedButton; QPushButton* mImportAllButton; QList mUrlList; void setupDirModel() { mRecursiveDirModel = new RecursiveDirModel(q); KindProxyModel* kindProxyModel = new KindProxyModel(q); kindProxyModel->setKindFilter( MimeTypeUtils::KIND_RASTER_IMAGE | MimeTypeUtils::KIND_SVG_IMAGE | MimeTypeUtils::KIND_VIDEO); kindProxyModel->setSourceModel(mRecursiveDirModel); QSortFilterProxyModel *sortModel = new QSortFilterProxyModel(q); sortModel->setDynamicSortFilter(true); sortModel->setSourceModel(kindProxyModel); sortModel->sort(0); mFinalModel = sortModel; QObject::connect( mFinalModel, SIGNAL(rowsInserted(QModelIndex,int,int)), q, SLOT(updateImportButtons())); QObject::connect( mFinalModel, SIGNAL(rowsRemoved(QModelIndex,int,int)), q, SLOT(updateImportButtons())); QObject::connect( mFinalModel, SIGNAL(modelReset()), q, SLOT(updateImportButtons())); } void setupIcons() { const KIconLoader::Group group = KIconLoader::NoGroup; const int size = KIconLoader::SizeHuge; mSrcIconLabel->setPixmap(KIconLoader::global()->loadIcon("camera-photo", group, size)); mDstIconLabel->setPixmap(KIconLoader::global()->loadIcon("computer", group, size)); } void setupSrcUrlWidgets() { mSrcUrlModelProxyMapper = 0; QObject::connect(mSrcUrlButton, SIGNAL(clicked()), q, SLOT(setupSrcUrlTreeView())); QObject::connect(mSrcUrlButton, SIGNAL(clicked()), q, SLOT(toggleSrcUrlTreeView())); mSrcUrlTreeView->hide(); KAcceleratorManager::setNoAccel(mSrcUrlButton); } void setupDstUrlRequester() { mDstUrlRequester->setMode(KFile::Directory | KFile::LocalOnly); } void setupThumbnailView() { mThumbnailView->setModel(mFinalModel); mThumbnailView->setSelectionMode(QAbstractItemView::ExtendedSelection); mThumbnailView->setThumbnailViewHelper(new ImporterThumbnailViewHelper(q)); PreviewItemDelegate* delegate = new PreviewItemDelegate(mThumbnailView); delegate->setThumbnailDetails(PreviewItemDelegate::FileNameDetail); delegate->setContextBarActions(PreviewItemDelegate::SelectionAction); mThumbnailView->setItemDelegate(delegate); // Colors int value = GwenviewConfig::viewBackgroundValue(); QColor bgColor = QColor::fromHsv(0, 0, value); QColor fgColor = value > 128 ? Qt::black : Qt::white; QPalette pal = mThumbnailView->palette(); pal.setColor(QPalette::Base, bgColor); pal.setColor(QPalette::Text, fgColor); mThumbnailView->setPalette(pal); QObject::connect(mSlider, SIGNAL(valueChanged(int)), mThumbnailView, SLOT(setThumbnailWidth(int))); QObject::connect(mThumbnailView, SIGNAL(thumbnailWidthChanged(int)), mSlider, SLOT(setValue(int))); int thumbnailSize = DEFAULT_THUMBNAIL_SIZE; mSlider->setValue(thumbnailSize); mSlider->updateToolTip(); mThumbnailView->setThumbnailAspectRatio(DEFAULT_THUMBNAIL_ASPECT_RATIO); mThumbnailView->setThumbnailWidth(thumbnailSize); + mThumbnailView->setThumbnailDevicePixelRatio(qApp->devicePixelRatio()); mThumbnailView->setThumbnailProvider(&mThumbnailProvider); QObject::connect( mThumbnailView->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), q, SLOT(updateImportButtons())); } void setupButtonBox() { QObject::connect(mConfigureButton, SIGNAL(clicked()), q, SLOT(showConfigDialog())); mImportSelectedButton = mButtonBox->addButton(i18n("Import Selected"), QDialogButtonBox::AcceptRole); QObject::connect(mImportSelectedButton, SIGNAL(clicked(bool)), q, SLOT(slotImportSelected())); mImportAllButton = mButtonBox->addButton(i18n("Import All"), QDialogButtonBox::AcceptRole); QObject::connect(mImportAllButton, SIGNAL(clicked(bool)), q, SLOT(slotImportAll())); QObject::connect( mButtonBox, SIGNAL(rejected()), q, SIGNAL(rejected())); } QUrl urlForBaseUrl() const { QUrl url = mUrlMap.value(mSrcBaseUrl); if (!url.isValid()) { return QUrl(); } KIO::StatJob *job = KIO::stat(url); KJobWidgets::setWindow(job, q); if (!job->exec()) { return QUrl(); } KFileItem item(job->statResult(), url, true /* delayedMimeTypes */); return item.isDir() ? url : QUrl(); } void rememberUrl(const QUrl& url) { mUrlMap.insert(mSrcBaseUrl, url); } }; ThumbnailPage::ThumbnailPage() : d(new ThumbnailPagePrivate) { d->q = this; d->mUrlMap.setConfigGroup(KConfigGroup(KSharedConfig::openConfig(), URL_FOR_BASE_URL_GROUP)); d->setupUi(this); d->setupIcons(); d->setupDirModel(); d->setupSrcUrlWidgets(); d->setupDstUrlRequester(); d->setupThumbnailView(); d->setupButtonBox(); updateImportButtons(); } ThumbnailPage::~ThumbnailPage() { delete d; } void ThumbnailPage::setSourceUrl(const QUrl& srcBaseUrl, const QString& iconName, const QString& name) { d->mSrcBaseIcon = QIcon::fromTheme(iconName); d->mSrcBaseName = name; const int size = KIconLoader::SizeHuge; d->mSrcIconLabel->setPixmap(d->mSrcBaseIcon.pixmap(size)); d->mSrcBaseUrl = srcBaseUrl; if (!d->mSrcBaseUrl.path().endsWith('/')) { d->mSrcBaseUrl.setPath(d->mSrcBaseUrl.path() + '/'); } QUrl url = d->urlForBaseUrl(); if (url.isValid()) { openUrl(url); } else { DocumentDirFinder* finder = new DocumentDirFinder(srcBaseUrl); connect(finder, SIGNAL(done(QUrl,DocumentDirFinder::Status)), SLOT(slotDocumentDirFinderDone(QUrl,DocumentDirFinder::Status))); finder->start(); } } void ThumbnailPage::slotDocumentDirFinderDone(const QUrl& url, DocumentDirFinder::Status /*status*/) { d->rememberUrl(url); openUrl(url); } void ThumbnailPage::openUrl(const QUrl& url) { d->mSrcUrl = url; QString path = QDir(d->mSrcBaseUrl.path()).relativeFilePath(d->mSrcUrl.path()); QString text; if (path.isEmpty() || path == ".") { text = d->mSrcBaseName; } else { path = QUrl::fromPercentEncoding(path.toUtf8()); path.replace("/", QString::fromUtf8(" › ")); text = QString::fromUtf8("%1 › %2").arg(d->mSrcBaseName).arg(path); } d->mSrcUrlButton->setText(text); d->mRecursiveDirModel->setUrl(url); } QList ThumbnailPage::urlList() const { return d->mUrlList; } void ThumbnailPage::setDestinationUrl(const QUrl& url) { d->mDstUrlRequester->setUrl(url); } QUrl ThumbnailPage::destinationUrl() const { return d->mDstUrlRequester->url(); } void ThumbnailPage::slotImportSelected() { importList(d->mThumbnailView->selectionModel()->selectedIndexes()); } void ThumbnailPage::slotImportAll() { QModelIndexList list; QAbstractItemModel* model = d->mThumbnailView->model(); for (int row = model->rowCount() - 1; row >= 0; --row) { list << model->index(row, 0); } importList(list); } void ThumbnailPage::importList(const QModelIndexList& list) { d->mUrlList.clear(); Q_FOREACH(const QModelIndex & index, list) { KFileItem item = itemForIndex(index); if (!ArchiveUtils::fileItemIsDirOrArchive(item)) { d->mUrlList << item.url(); } // FIXME: Handle dirs (do we want to import recursively?) } emit importRequested(); } void ThumbnailPage::updateImportButtons() { d->mImportSelectedButton->setEnabled(d->mThumbnailView->selectionModel()->hasSelection()); d->mImportAllButton->setEnabled(d->mThumbnailView->model()->rowCount(QModelIndex()) > 0); } void ThumbnailPage::showConfigDialog() { ImporterConfigDialog dialog(this); dialog.exec(); } /** * This model allows only the url passed in the constructor to appear at the root * level. This makes it possible to select the url, but not its siblings. * It also provides custom role values for the root item. */ class OnlyBaseUrlProxyModel : public QSortFilterProxyModel { public: OnlyBaseUrlProxyModel(const QUrl& url, const QIcon& icon, const QString& name, QObject* parent) : QSortFilterProxyModel(parent) , mUrl(url) , mIcon(icon) , mName(name) {} bool filterAcceptsRow(int sourceRow, const QModelIndex& sourceParent) const // reimp Q_DECL_OVERRIDE { if (sourceParent.isValid()) { return true; } QModelIndex index = sourceModel()->index(sourceRow, 0); KFileItem item = itemForIndex(index); return item.url().matches(mUrl, QUrl::StripTrailingSlash); } QVariant data(const QModelIndex& index, int role) const // reimp Q_DECL_OVERRIDE { if (index.parent().isValid()) { return QSortFilterProxyModel::data(index, role); } switch (role) { case Qt::DisplayRole: return mName; case Qt::DecorationRole: return mIcon; case Qt::ToolTipRole: return mUrl.toDisplayString(QUrl::PreferLocalFile); default: return QSortFilterProxyModel::data(index, role); } } private: QUrl mUrl; QIcon mIcon; QString mName; }; void ThumbnailPage::setupSrcUrlTreeView() { if (d->mSrcUrlTreeView->model()) { // Already initialized return; } KDirModel* dirModel = new KDirModel(this); dirModel->dirLister()->setDirOnlyMode(true); dirModel->dirLister()->openUrl(KIO::upUrl(d->mSrcBaseUrl)); OnlyBaseUrlProxyModel* onlyBaseUrlModel = new OnlyBaseUrlProxyModel(d->mSrcBaseUrl, d->mSrcBaseIcon, d->mSrcBaseName, this); onlyBaseUrlModel->setSourceModel(dirModel); QSortFilterProxyModel* sortModel = new QSortFilterProxyModel(this); sortModel->setDynamicSortFilter(true); sortModel->setSourceModel(onlyBaseUrlModel); sortModel->sort(0); d->mSrcUrlModelProxyMapper = new KModelIndexProxyMapper(dirModel, sortModel, this); d->mSrcUrlTreeView->setModel(sortModel); for(int i = 1; i < dirModel->columnCount(); ++i) { d->mSrcUrlTreeView->hideColumn(i); } connect(d->mSrcUrlTreeView, SIGNAL(activated(QModelIndex)), SLOT(openUrlFromIndex(QModelIndex))); connect(d->mSrcUrlTreeView, SIGNAL(clicked(QModelIndex)), SLOT(openUrlFromIndex(QModelIndex))); dirModel->expandToUrl(d->mSrcUrl); connect(dirModel, SIGNAL(expand(QModelIndex)), SLOT(slotSrcUrlModelExpand(QModelIndex))); } void ThumbnailPage::slotSrcUrlModelExpand(const QModelIndex& index) { QModelIndex viewIndex = d->mSrcUrlModelProxyMapper->mapLeftToRight(index); d->mSrcUrlTreeView->expand(viewIndex); KFileItem item = itemForIndex(index); if (item.url() == d->mSrcUrl) { d->mSrcUrlTreeView->selectionModel()->select(viewIndex, QItemSelectionModel::ClearAndSelect); } } void ThumbnailPage::toggleSrcUrlTreeView() { d->mSrcUrlTreeView->setVisible(!d->mSrcUrlTreeView->isVisible()); } void ThumbnailPage::openUrlFromIndex(const QModelIndex& index) { KFileItem item = itemForIndex(index); if (item.isNull()) { return; } QUrl url = item.url(); d->rememberUrl(url); openUrl(url); } } // namespace diff --git a/lib/thumbnailview/previewitemdelegate.cpp b/lib/thumbnailview/previewitemdelegate.cpp index b2ade959..1177a263 100644 --- a/lib/thumbnailview/previewitemdelegate.cpp +++ b/lib/thumbnailview/previewitemdelegate.cpp @@ -1,956 +1,957 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2008 Aurélien Gâteau This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. */ // Self #include "previewitemdelegate.h" #include // Qt #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE #include #include #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE #include #endif // Local #include "archiveutils.h" #include "itemeditor.h" #include "paintutils.h" #include "thumbnailview.h" #include "timeutils.h" #include "tooltipwidget.h" #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE #include "../semanticinfo/semanticinfodirmodel.h" #endif // Define this to be able to fine tune the rendering of the selection // background through a config file //#define FINETUNE_SELECTION_BACKGROUND #ifdef FINETUNE_SELECTION_BACKGROUND #include #include #endif //#define DEBUG_DRAW_BORDER //#define DEBUG_DRAW_CURRENT namespace Gwenview { /** * Space between the item outer rect and the content, and between the * thumbnail and the caption */ const int ITEM_MARGIN = 5; /** How darker is the border line around selection */ const int SELECTION_BORDER_DARKNESS = 140; /** Radius of the selection rounded corners, in pixels */ const int SELECTION_RADIUS = 5; /** Space between the item outer rect and the context bar */ const int CONTEXTBAR_MARGIN = 1; /** How dark is the shadow, 0 is invisible, 255 is as dark as possible */ const int SHADOW_STRENGTH = 128; /** How many pixels around the thumbnail are shadowed */ const int SHADOW_SIZE = 4; static KFileItem fileItemForIndex(const QModelIndex& index) { Q_ASSERT(index.isValid()); QVariant data = index.data(KDirModel::FileItemRole); return qvariant_cast(data); } static QUrl urlForIndex(const QModelIndex& index) { KFileItem item = fileItemForIndex(index); return item.url(); } struct PreviewItemDelegatePrivate { /** * Maps full text to elided text. */ mutable QHash mElidedTextCache; // Key is height * 1000 + width typedef QHash ShadowCache; mutable ShadowCache mShadowCache; PreviewItemDelegate* q; ThumbnailView* mView; QWidget* mContextBar; QToolButton* mSaveButton; QPixmap mSaveButtonPixmap; QToolButton* mToggleSelectionButton; QToolButton* mFullScreenButton; QToolButton* mRotateLeftButton; QToolButton* mRotateRightButton; #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE KRatingPainter mRatingPainter; #endif QPersistentModelIndex mIndexUnderCursor; QSize mThumbnailSize; PreviewItemDelegate::ThumbnailDetails mDetails; PreviewItemDelegate::ContextBarActions mContextBarActions; Qt::TextElideMode mTextElideMode; QPointer mToolTip; QScopedPointer mToolTipAnimation; void initSaveButtonPixmap() { if (!mSaveButtonPixmap.isNull()) { return; } // Necessary otherwise we won't see the save button itself mSaveButton->adjustSize(); mSaveButtonPixmap = QPixmap(mSaveButton->sizeHint()); mSaveButtonPixmap.fill(Qt::transparent); mSaveButton->render(&mSaveButtonPixmap, QPoint(), QRegion(), QWidget::DrawChildren); } void showContextBar(const QRect& rect, const QPixmap& thumbnailPix) { if (mContextBarActions == PreviewItemDelegate::NoAction) { return; } mContextBar->adjustSize(); // Center bar, except if only showing SelectionAction. const int posX = mContextBarActions == PreviewItemDelegate::SelectionAction ? 0 : (rect.width() - mContextBar->width()) / 2; const int posY = qMax(CONTEXTBAR_MARGIN, mThumbnailSize.height() - thumbnailPix.height() - mContextBar->height()); mContextBar->move(rect.topLeft() + QPoint(posX, posY)); mContextBar->show(); } void initToolTip() { mToolTip = new ToolTipWidget(mView->viewport()); mToolTip->setOpacity(0); mToolTip->show(); } bool hoverEventFilter(QHoverEvent* event) { QModelIndex index = mView->indexAt(event->pos()); if (index != mIndexUnderCursor) { updateHoverUi(index); } else { // Same index, nothing to do, but repaint anyway in case we are // over the rating row mView->update(mIndexUnderCursor); } return false; } void updateHoverUi(const QModelIndex& index) { QModelIndex oldIndex = mIndexUnderCursor; mIndexUnderCursor = index; mView->update(oldIndex); if (QApplication::style()->styleHint(QStyle::SH_ItemView_ActivateItemOnSingleClick, 0, mView)) { mView->setCursor(mIndexUnderCursor.isValid() ? Qt::PointingHandCursor : Qt::ArrowCursor); } if (mIndexUnderCursor.isValid()) { updateToggleSelectionButton(); updateImageButtons(); const QRect rect = mView->visualRect(mIndexUnderCursor); const QPixmap thumbnailPix = mView->thumbnailForIndex(index); + qDebug() << "THUMB thumbnailPix dpr" << thumbnailPix.devicePixelRatio(); showContextBar(rect, thumbnailPix); if (mView->isModified(mIndexUnderCursor)) { showSaveButton(rect); } else { mSaveButton->hide(); } showToolTip(index); mView->update(mIndexUnderCursor); } else { mContextBar->hide(); mSaveButton->hide(); hideToolTip(); } } QRect ratingRectFromIndexRect(const QRect& rect) const { return QRect( rect.left(), rect.bottom() - ratingRowHeight() - ITEM_MARGIN, rect.width(), ratingRowHeight()); } #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE int ratingFromCursorPosition(const QRect& ratingRect) const { const QPoint pos = mView->viewport()->mapFromGlobal(QCursor::pos()); return mRatingPainter.ratingFromPosition(ratingRect, pos); } #endif bool mouseButtonEventFilter(QEvent::Type type) { #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE const QRect rect = ratingRectFromIndexRect(mView->visualRect(mIndexUnderCursor)); const int rating = ratingFromCursorPosition(rect); if (rating == -1) { return false; } if (type == QEvent::MouseButtonRelease) { q->setDocumentRatingRequested(urlForIndex(mIndexUnderCursor) , rating); } return true; #else return false; #endif } QPoint saveButtonPosition(const QRect& itemRect) const { QSize buttonSize = mSaveButton->sizeHint(); int posX = itemRect.right() - buttonSize.width(); int posY = itemRect.top() + mThumbnailSize.height() + 2 * ITEM_MARGIN - buttonSize.height(); return QPoint(posX, posY); } void showSaveButton(const QRect& itemRect) const { mSaveButton->move(saveButtonPosition(itemRect)); mSaveButton->show(); } void drawBackground(QPainter* painter, const QRect& rect, const QColor& bgColor, const QColor& borderColor) const { int bgH, bgS, bgV; int borderH, borderS, borderV, borderMargin; #ifdef FINETUNE_SELECTION_BACKGROUND QSettings settings(QDir::homePath() + "/colors.ini", QSettings::IniFormat); bgH = settings.value("bg/h").toInt(); bgS = settings.value("bg/s").toInt(); bgV = settings.value("bg/v").toInt(); borderH = settings.value("border/h").toInt(); borderS = settings.value("border/s").toInt(); borderV = settings.value("border/v").toInt(); borderMargin = settings.value("border/margin").toInt(); #else bgH = 0; bgS = -20; bgV = 43; borderH = 0; borderS = -100; borderV = 60; borderMargin = 1; #endif painter->setRenderHint(QPainter::Antialiasing); QRectF rectF = QRectF(rect).adjusted(0.5, 0.5, -0.5, -0.5); QPainterPath path = PaintUtils::roundedRectangle(rectF, SELECTION_RADIUS); QLinearGradient gradient(rectF.topLeft(), rectF.bottomLeft()); gradient.setColorAt(0, PaintUtils::adjustedHsv(bgColor, bgH, bgS, bgV)); gradient.setColorAt(1, bgColor); painter->fillPath(path, gradient); painter->setPen(borderColor); painter->drawPath(path); painter->setPen(PaintUtils::adjustedHsv(borderColor, borderH, borderS, borderV)); rectF = rectF.adjusted(borderMargin, borderMargin, -borderMargin, -borderMargin); path = PaintUtils::roundedRectangle(rectF, SELECTION_RADIUS); painter->drawPath(path); } void drawShadow(QPainter* painter, const QRect& rect) const { const QPoint shadowOffset(-SHADOW_SIZE, -SHADOW_SIZE + 1); int key = rect.height() * 1000 + rect.width(); ShadowCache::Iterator it = mShadowCache.find(key); if (it == mShadowCache.end()) { QSize size = QSize(rect.width() + 2 * SHADOW_SIZE, rect.height() + 2 * SHADOW_SIZE); QColor color(0, 0, 0, SHADOW_STRENGTH); QPixmap shadow = PaintUtils::generateFuzzyRect(size, color, SHADOW_SIZE); it = mShadowCache.insert(key, shadow); } painter->drawPixmap(rect.topLeft() + shadowOffset, it.value()); } void drawText(QPainter* painter, const QRect& rect, const QColor& fgColor, const QString& fullText) const { QFontMetrics fm = mView->fontMetrics(); // Elide text QString text; QHash::const_iterator it = mElidedTextCache.constFind(fullText); if (it == mElidedTextCache.constEnd()) { text = fm.elidedText(fullText, mTextElideMode, rect.width()); mElidedTextCache[fullText] = text; } else { text = it.value(); } // Compute x pos int posX; if (text.length() == fullText.length()) { // Not elided, center text posX = (rect.width() - fm.width(text)) / 2; } else { // Elided, left align posX = 0; } // Draw text painter->setPen(fgColor); painter->drawText(rect.left() + posX, rect.top() + fm.ascent(), text); } void drawRating(QPainter* painter, const QRect& rect, const QVariant& value) { #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE const int rating = value.toInt(); const QRect ratingRect = ratingRectFromIndexRect(rect); const int hoverRating = ratingFromCursorPosition(ratingRect); mRatingPainter.paint(painter, ratingRect, rating, hoverRating); #endif } bool isTextElided(const QString& text) const { QHash::const_iterator it = mElidedTextCache.constFind(text); if (it == mElidedTextCache.constEnd()) { return false; } return it.value().length() < text.length(); } /** * Show a tooltip only if the item has been elided. * This function places the tooltip over the item text. */ void showToolTip(const QModelIndex& index) { if (mDetails == 0 || mDetails == PreviewItemDelegate::RatingDetail) { // No text to display return; } // Gather tip text QStringList textList; bool elided = false; if (mDetails & PreviewItemDelegate::FileNameDetail) { const QString text = index.data().toString(); elided |= isTextElided(text); textList << text; } // FIXME: Duplicated from drawText const KFileItem fileItem = fileItemForIndex(index); const bool isDirOrArchive = ArchiveUtils::fileItemIsDirOrArchive(fileItem); if (mDetails & PreviewItemDelegate::DateDetail) { if (!ArchiveUtils::fileItemIsDirOrArchive(fileItem)) { const QDateTime dt = TimeUtils::dateTimeForFileItem(fileItem); const QString text = QLocale().toString(dt, QLocale::ShortFormat); elided |= isTextElided(text); textList << text; } } if (!isDirOrArchive && (mDetails & PreviewItemDelegate::ImageSizeDetail)) { QSize fullSize; QPixmap thumbnailPix = mView->thumbnailForIndex(index, &fullSize); if (fullSize.isValid()) { const QString text = QString("%1x%2").arg(fullSize.width()).arg(fullSize.height()); elided |= isTextElided(text); textList << text; } } if (!isDirOrArchive && (mDetails & PreviewItemDelegate::FileSizeDetail)) { const KIO::filesize_t size = fileItem.size(); if (size > 0) { const QString text = KIO::convertSize(size); elided |= isTextElided(text); textList << text; } } if (!elided) { hideToolTip(); return; } bool newTipLabel = !mToolTip; if (!mToolTip) { initToolTip(); } mToolTip->setText(textList.join("\n")); QSize tipSize = mToolTip->sizeHint(); // Compute tip position QRect rect = mView->visualRect(index); const int textY = ITEM_MARGIN + mThumbnailSize.height() + ITEM_MARGIN; const int spacing = 1; QRect geometry( QPoint(rect.topLeft() + QPoint((rect.width() - tipSize.width()) / 2, textY + spacing)), tipSize ); if (geometry.left() < 0) { geometry.moveLeft(0); } else if (geometry.right() > mView->viewport()->width()) { geometry.moveRight(mView->viewport()->width()); } // Show tip QParallelAnimationGroup* anim = new QParallelAnimationGroup(); QPropertyAnimation* fadeIn = new QPropertyAnimation(mToolTip, "opacity"); fadeIn->setStartValue(mToolTip->opacity()); fadeIn->setEndValue(1.); anim->addAnimation(fadeIn); if (newTipLabel) { mToolTip->setGeometry(geometry); } else { QPropertyAnimation* move = new QPropertyAnimation(mToolTip, "geometry"); move->setStartValue(mToolTip->geometry()); move->setEndValue(geometry); anim->addAnimation(move); } mToolTipAnimation.reset(anim); mToolTipAnimation->start(); } void hideToolTip() { if (!mToolTip) { return; } QSequentialAnimationGroup* anim = new QSequentialAnimationGroup(); anim->addPause(500); QPropertyAnimation* fadeOut = new QPropertyAnimation(mToolTip, "opacity"); fadeOut->setStartValue(mToolTip->opacity()); fadeOut->setEndValue(0.); anim->addAnimation(fadeOut); mToolTipAnimation.reset(anim); mToolTipAnimation->start(); QObject::connect(anim, &QSequentialAnimationGroup::finished, mToolTip.data(), &ToolTipWidget::deleteLater); } int itemWidth() const { return mThumbnailSize.width() + 2 * ITEM_MARGIN; } int ratingRowHeight() const { #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE return qMax(mView->fontMetrics().ascent(), int(KIconLoader::SizeSmall)); #endif return 0; } int itemHeight() const { const int lineHeight = mView->fontMetrics().height(); int textHeight = 0; if (mDetails & PreviewItemDelegate::FileNameDetail) { textHeight += lineHeight; } if (mDetails & PreviewItemDelegate::DateDetail) { textHeight += lineHeight; } if (mDetails & PreviewItemDelegate::ImageSizeDetail) { textHeight += lineHeight; } if (mDetails & PreviewItemDelegate::FileSizeDetail) { textHeight += lineHeight; } if (mDetails & PreviewItemDelegate::RatingDetail) { textHeight += ratingRowHeight(); } if (textHeight == 0) { // Keep at least one row of text, so that we can show folder names textHeight = lineHeight; } return mThumbnailSize.height() + textHeight + 3 * ITEM_MARGIN; } void selectIndexUnderCursorIfNoMultiSelection() { if (mView->selectionModel()->selectedIndexes().size() <= 1) { mView->setCurrentIndex(mIndexUnderCursor); } } void updateToggleSelectionButton() { mToggleSelectionButton->setIcon(QIcon::fromTheme( mView->selectionModel()->isSelected(mIndexUnderCursor) ? "list-remove" : "list-add" )); } void updateImageButtons() { const KFileItem item = fileItemForIndex(mIndexUnderCursor); const bool isImage = !ArchiveUtils::fileItemIsDirOrArchive(item); mFullScreenButton->setEnabled(isImage); mRotateLeftButton->setEnabled(isImage); mRotateRightButton->setEnabled(isImage); } void updateContextBar() { if (mContextBarActions == PreviewItemDelegate::NoAction) { mContextBar->hide(); return; } const int width = itemWidth(); const int buttonWidth = mRotateRightButton->sizeHint().width(); mFullScreenButton->setVisible(mContextBarActions & PreviewItemDelegate::FullScreenAction); bool rotate = mContextBarActions & PreviewItemDelegate::RotateAction; mRotateLeftButton->setVisible(rotate && width >= 3 * buttonWidth); mRotateRightButton->setVisible(rotate && width >= 4 * buttonWidth); mContextBar->adjustSize(); } void updateViewGridSize() { mView->setGridSize(QSize(itemWidth(), itemHeight())); } }; PreviewItemDelegate::PreviewItemDelegate(ThumbnailView* view) : QItemDelegate(view) , d(new PreviewItemDelegatePrivate) { d->q = this; d->mView = view; view->viewport()->installEventFilter(this); // Set this attribute so that the viewport receives QEvent::HoverMove and // QEvent::HoverLeave events. We use these events in the event filter // installed on the viewport. // Some styles set this attribute themselves (Oxygen and Skulpture do) but // others do not (Plastique, Cleanlooks...) view->viewport()->setAttribute(Qt::WA_Hover); d->mThumbnailSize = view->thumbnailSize(); d->mDetails = FileNameDetail; d->mContextBarActions = SelectionAction | FullScreenAction | RotateAction; d->mTextElideMode = Qt::ElideRight; connect(view, SIGNAL(rowsRemovedSignal(QModelIndex,int,int)), SLOT(slotRowsChanged())); connect(view, SIGNAL(rowsInsertedSignal(QModelIndex,int,int)), SLOT(slotRowsChanged())); #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE d->mRatingPainter.setAlignment(Qt::AlignHCenter | Qt::AlignBottom); d->mRatingPainter.setLayoutDirection(view->layoutDirection()); d->mRatingPainter.setMaxRating(10); #endif connect(view, SIGNAL(thumbnailSizeChanged(QSize)), SLOT(setThumbnailSize(QSize))); // Button frame d->mContextBar = new QWidget(d->mView->viewport()); d->mContextBar->hide(); d->mToggleSelectionButton = new QToolButton; d->mToggleSelectionButton->setIcon(QIcon::fromTheme("list-add")); connect(d->mToggleSelectionButton, &QToolButton::clicked, this, &PreviewItemDelegate::slotToggleSelectionClicked); d->mFullScreenButton = new QToolButton; d->mFullScreenButton->setIcon(QIcon::fromTheme("view-fullscreen")); connect(d->mFullScreenButton, &QToolButton::clicked, this, &PreviewItemDelegate::slotFullScreenClicked); d->mRotateLeftButton = new QToolButton; d->mRotateLeftButton->setIcon(QIcon::fromTheme("object-rotate-left")); connect(d->mRotateLeftButton, &QToolButton::clicked, this, &PreviewItemDelegate::slotRotateLeftClicked); d->mRotateRightButton = new QToolButton; d->mRotateRightButton->setIcon(QIcon::fromTheme("object-rotate-right")); connect(d->mRotateRightButton, &QToolButton::clicked, this, &PreviewItemDelegate::slotRotateRightClicked); QHBoxLayout* layout = new QHBoxLayout(d->mContextBar); layout->setMargin(2); layout->setSpacing(2); layout->addWidget(d->mToggleSelectionButton); layout->addWidget(d->mFullScreenButton); layout->addWidget(d->mRotateLeftButton); layout->addWidget(d->mRotateRightButton); // Save button d->mSaveButton = new QToolButton(d->mView->viewport()); d->mSaveButton->setIcon(QIcon::fromTheme("document-save")); d->mSaveButton->hide(); connect(d->mSaveButton, &QToolButton::clicked, this, &PreviewItemDelegate::slotSaveClicked); } PreviewItemDelegate::~PreviewItemDelegate() { delete d; } QSize PreviewItemDelegate::sizeHint(const QStyleOptionViewItem & /*option*/, const QModelIndex & /*index*/) const { return d->mView->gridSize(); } bool PreviewItemDelegate::eventFilter(QObject* object, QEvent* event) { if (object == d->mView->viewport()) { switch (event->type()) { case QEvent::ToolTip: return true; case QEvent::HoverMove: case QEvent::HoverLeave: return d->hoverEventFilter(static_cast(event)); case QEvent::MouseButtonPress: case QEvent::MouseButtonRelease: return d->mouseButtonEventFilter(event->type()); default: return false; } } else { // Necessary for the item editor to work correctly (especially closing // the editor with the Escape key) return QItemDelegate::eventFilter(object, event); } } void PreviewItemDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const { int thumbnailHeight = d->mThumbnailSize.height(); QSize fullSize; QPixmap thumbnailPix = d->mView->thumbnailForIndex(index, &fullSize); const KFileItem fileItem = fileItemForIndex(index); const bool opaque = !thumbnailPix.hasAlphaChannel(); const bool isDirOrArchive = ArchiveUtils::fileItemIsDirOrArchive(fileItem); QRect rect = option.rect; const bool selected = option.state & QStyle::State_Selected; const bool underMouse = option.state & QStyle::State_MouseOver; const QWidget* viewport = d->mView->viewport(); #ifdef DEBUG_DRAW_BORDER painter->setPen(Qt::red); painter->setBrush(Qt::NoBrush); painter->drawRect(rect); #endif // Select color group QPalette::ColorGroup cg; if ((option.state & QStyle::State_Enabled) && (option.state & QStyle::State_Active)) { cg = QPalette::Normal; } else if ((option.state & QStyle::State_Enabled)) { cg = QPalette::Inactive; } else { cg = QPalette::Disabled; } // Select colors QColor bgColor, borderColor, fgColor; if (selected || underMouse) { bgColor = option.palette.color(cg, QPalette::Highlight); borderColor = bgColor.dark(SELECTION_BORDER_DARKNESS); } else { bgColor = viewport->palette().color(viewport->backgroundRole()); borderColor = bgColor.light(200); } fgColor = viewport->palette().color(viewport->foregroundRole()); // Compute thumbnailRect QRect thumbnailRect = QRect( rect.left() + (rect.width() - thumbnailPix.width()) / 2, rect.top() + (thumbnailHeight - thumbnailPix.height()) + ITEM_MARGIN, thumbnailPix.width(), thumbnailPix.height()); // Draw background const QRect backgroundRect = thumbnailRect.adjusted(-ITEM_MARGIN, -ITEM_MARGIN, ITEM_MARGIN, ITEM_MARGIN); if (selected) { d->drawBackground(painter, backgroundRect, bgColor, borderColor); } else if (underMouse) { painter->setOpacity(0.2); d->drawBackground(painter, backgroundRect, bgColor, borderColor); painter->setOpacity(1.); } else if (opaque) { d->drawShadow(painter, thumbnailRect); } // Draw thumbnail if (opaque) { painter->setPen(borderColor); painter->setRenderHint(QPainter::Antialiasing, false); QRect borderRect = thumbnailRect.adjusted(-1, -1, 0, 0); painter->drawRect(borderRect); } painter->drawPixmap(thumbnailRect.left(), thumbnailRect.top(), thumbnailPix); // Draw modified indicator bool isModified = d->mView->isModified(index); if (isModified) { // Draws a pixmap of the save button frame, as an indicator that // the image has been modified QPoint framePosition = d->saveButtonPosition(rect); d->initSaveButtonPixmap(); painter->drawPixmap(framePosition, d->mSaveButtonPixmap); } // Draw busy indicator if (d->mView->isBusy(index)) { QPixmap pix = d->mView->busySequenceCurrentPixmap(); painter->drawPixmap( thumbnailRect.left() + (thumbnailRect.width() - pix.width()) / 2, thumbnailRect.top() + (thumbnailRect.height() - pix.height()) / 2, pix); } if (index == d->mIndexUnderCursor) { // Show bar again: if the thumbnail has changed, we may need to update // its position. Don't do it if we are over rotate buttons, though: it // would not be nice to move the button now, the user may want to // rotate the image one more time. // The button will get moved when the mouse leaves. if (!d->mRotateLeftButton->underMouse() && !d->mRotateRightButton->underMouse()) { d->showContextBar(rect, thumbnailPix); } if (isModified) { // If we just rotated the image with the buttons from the // button frame, we need to show the save button frame right now. d->showSaveButton(rect); } else { d->mSaveButton->hide(); } } QRect textRect( rect.left() + ITEM_MARGIN, rect.top() + 2 * ITEM_MARGIN + thumbnailHeight, rect.width() - 2 * ITEM_MARGIN, d->mView->fontMetrics().height()); if (isDirOrArchive || (d->mDetails & PreviewItemDelegate::FileNameDetail)) { d->drawText(painter, textRect, fgColor, index.data().toString()); textRect.moveTop(textRect.bottom()); } if (!isDirOrArchive && (d->mDetails & PreviewItemDelegate::DateDetail)) { const QDateTime dt = TimeUtils::dateTimeForFileItem(fileItem); d->drawText(painter, textRect, fgColor, QLocale().toString(dt, QLocale::ShortFormat)); textRect.moveTop(textRect.bottom()); } if (!isDirOrArchive && (d->mDetails & PreviewItemDelegate::ImageSizeDetail)) { if (fullSize.isValid()) { const QString text = QString("%1x%2").arg(fullSize.width()).arg(fullSize.height()); d->drawText(painter, textRect, fgColor, text); textRect.moveTop(textRect.bottom()); } } if (!isDirOrArchive && (d->mDetails & PreviewItemDelegate::FileSizeDetail)) { const KIO::filesize_t size = fileItem.size(); if (size > 0) { const QString st = KIO::convertSize(size); d->drawText(painter, textRect, fgColor, st); textRect.moveTop(textRect.bottom()); } } if (!isDirOrArchive && (d->mDetails & PreviewItemDelegate::RatingDetail)) { #ifndef GWENVIEW_SEMANTICINFO_BACKEND_NONE d->drawRating(painter, rect, index.data(SemanticInfoDirModel::RatingRole)); #endif } #ifdef DEBUG_DRAW_CURRENT if (d->mView->currentIndex() == index) { painter->fillRect(rect.left(), rect.top(), 12, 12, Qt::red); } #endif } void PreviewItemDelegate::setThumbnailSize(const QSize& value) { d->mThumbnailSize = value; d->updateViewGridSize(); d->updateContextBar(); d->mElidedTextCache.clear(); } void PreviewItemDelegate::slotSaveClicked() { saveDocumentRequested(urlForIndex(d->mIndexUnderCursor)); } void PreviewItemDelegate::slotRotateLeftClicked() { d->selectIndexUnderCursorIfNoMultiSelection(); rotateDocumentLeftRequested(urlForIndex(d->mIndexUnderCursor)); } void PreviewItemDelegate::slotRotateRightClicked() { d->selectIndexUnderCursorIfNoMultiSelection(); rotateDocumentRightRequested(urlForIndex(d->mIndexUnderCursor)); } void PreviewItemDelegate::slotFullScreenClicked() { showDocumentInFullScreenRequested(urlForIndex(d->mIndexUnderCursor)); } void PreviewItemDelegate::slotToggleSelectionClicked() { d->mView->selectionModel()->select(d->mIndexUnderCursor, QItemSelectionModel::Toggle); d->updateToggleSelectionButton(); } PreviewItemDelegate::ThumbnailDetails PreviewItemDelegate::thumbnailDetails() const { return d->mDetails; } void PreviewItemDelegate::setThumbnailDetails(PreviewItemDelegate::ThumbnailDetails details) { d->mDetails = details; d->updateViewGridSize(); d->mView->scheduleDelayedItemsLayout(); } PreviewItemDelegate::ContextBarActions PreviewItemDelegate::contextBarActions() const { return d->mContextBarActions; } void PreviewItemDelegate::setContextBarActions(PreviewItemDelegate::ContextBarActions actions) { d->mContextBarActions = actions; d->updateContextBar(); } Qt::TextElideMode PreviewItemDelegate::textElideMode() const { return d->mTextElideMode; } void PreviewItemDelegate::setTextElideMode(Qt::TextElideMode mode) { if (d->mTextElideMode == mode) { return; } d->mTextElideMode = mode; d->mElidedTextCache.clear(); d->mView->viewport()->update(); } void PreviewItemDelegate::slotRowsChanged() { // We need to update hover ui because the current index may have // disappeared: for example if the current image is removed with "del". QPoint pos = d->mView->viewport()->mapFromGlobal(QCursor::pos()); QModelIndex index = d->mView->indexAt(pos); d->updateHoverUi(index); } QWidget * PreviewItemDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/) const { return new ItemEditor(parent); } void PreviewItemDelegate::setEditorData(QWidget* widget, const QModelIndex& index) const { ItemEditor* edit = qobject_cast(widget); if (!edit) { return; } edit->setText(index.data().toString()); } void PreviewItemDelegate::updateEditorGeometry(QWidget* widget, const QStyleOptionViewItem& option, const QModelIndex& index) const { ItemEditor* edit = qobject_cast(widget); if (!edit) { return; } QString text = index.data().toString(); int textWidth = edit->fontMetrics().width(" " + text + " "); QRect textRect( option.rect.left() + (option.rect.width() - textWidth) / 2, option.rect.top() + 2 * ITEM_MARGIN + d->mThumbnailSize.height(), textWidth, edit->sizeHint().height()); edit->setGeometry(textRect); } void PreviewItemDelegate::setModelData(QWidget* widget, QAbstractItemModel* model, const QModelIndex& index) const { ItemEditor* edit = qobject_cast(widget); if (!edit) { return; } if (index.data().toString() != edit->text()) { model->setData(index, edit->text(), Qt::EditRole); } } } // namespace diff --git a/lib/thumbnailview/thumbnailbarview.cpp b/lib/thumbnailview/thumbnailbarview.cpp index a53bcb49..6c649b69 100644 --- a/lib/thumbnailview/thumbnailbarview.cpp +++ b/lib/thumbnailview/thumbnailbarview.cpp @@ -1,543 +1,553 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2008 Aurélien Gâteau Copyright 2008 Ilya Konkov This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Cambridge, MA 02110-1301, USA. */ // Self #include "thumbnailbarview.h" // Qt #include #include #include #include #include #include #include #include #include #ifdef WINDOWS_PROXY_STYLE #include #endif // KDE #include // Local #include "lib/hud/hudtheme.h" #include "lib/paintutils.h" #include "lib/thumbnailview/abstractthumbnailviewhelper.h" namespace Gwenview { /** * Duration in ms of the smooth scroll */ const int SMOOTH_SCROLL_DURATION = 250; /** * Space between the item outer rect and the content, and between the * thumbnail and the caption */ const int ITEM_MARGIN = 5; /** How dark is the shadow, 0 is invisible, 255 is as dark as possible */ const int SHADOW_STRENGTH = 127; /** How many pixels around the thumbnail are shadowed */ const int SHADOW_SIZE = 4; struct ThumbnailBarItemDelegatePrivate { // Key is height * 1000 + width typedef QMap ShadowCache; mutable ShadowCache mShadowCache; ThumbnailBarItemDelegate* q; ThumbnailView* mView; QToolButton* mToggleSelectionButton; QColor mBorderColor; QPersistentModelIndex mIndexUnderCursor; void setupToggleSelectionButton() { mToggleSelectionButton = new QToolButton(mView->viewport()); mToggleSelectionButton->setIcon(QIcon::fromTheme("list-add")); mToggleSelectionButton->hide(); QObject::connect(mToggleSelectionButton, &QToolButton::clicked, q, &ThumbnailBarItemDelegate::toggleSelection); } void showToolTip(QHelpEvent* helpEvent) { QModelIndex index = mView->indexAt(helpEvent->pos()); if (!index.isValid()) { return; } QString fullText = index.data().toString(); QPoint pos = QCursor::pos(); QToolTip::showText(pos, fullText, mView); } void drawShadow(QPainter* painter, const QRect& rect) const { const QPoint shadowOffset(-SHADOW_SIZE, -SHADOW_SIZE + 1); int key = rect.height() * 1000 + rect.width(); ShadowCache::Iterator it = mShadowCache.find(key); if (it == mShadowCache.end()) { QSize size = QSize(rect.width() + 2 * SHADOW_SIZE, rect.height() + 2 * SHADOW_SIZE); QColor color(0, 0, 0, SHADOW_STRENGTH); QPixmap shadow = PaintUtils::generateFuzzyRect(size, color, SHADOW_SIZE); it = mShadowCache.insert(key, shadow); } painter->drawPixmap(rect.topLeft() + shadowOffset, it.value()); } bool hoverEventFilter(QHoverEvent* event) { QModelIndex index = mView->indexAt(event->pos()); if (index != mIndexUnderCursor) { updateHoverUi(index); } return false; } void updateHoverUi(const QModelIndex& index) { mIndexUnderCursor = index; if (mIndexUnderCursor.isValid()) { updateToggleSelectionButton(); const QRect rect = mView->visualRect(mIndexUnderCursor); mToggleSelectionButton->move(rect.topLeft() + QPoint(2, 2)); mToggleSelectionButton->show(); } else { mToggleSelectionButton->hide(); } } void updateToggleSelectionButton() { bool isSelected = mView->selectionModel()->isSelected(mIndexUnderCursor); mToggleSelectionButton->setIcon(QIcon::fromTheme(isSelected ? "list-remove" : "list-add")); } }; ThumbnailBarItemDelegate::ThumbnailBarItemDelegate(ThumbnailView* view) : QAbstractItemDelegate(view) , d(new ThumbnailBarItemDelegatePrivate) { d->q = this; d->mView = view; d->setupToggleSelectionButton(); view->viewport()->installEventFilter(this); // Set this attribute so that the viewport receives QEvent::HoverMove and // QEvent::HoverLeave events. We use these events in the event filter // installed on the viewport. // Some styles set this attribute themselves (Oxygen and Skulpture do) but // others do not (Plastique, Cleanlooks...) view->viewport()->setAttribute(Qt::WA_Hover); d->mBorderColor = PaintUtils::alphaAdjustedF(QColor(Qt::white), 0.65); } QSize ThumbnailBarItemDelegate::sizeHint(const QStyleOptionViewItem & /*option*/, const QModelIndex & index) const { QSize size; if (d->mView->thumbnailScaleMode() == ThumbnailView::ScaleToFit) { size = d->mView->gridSize(); } else { QPixmap thumbnailPix = d->mView->thumbnailForIndex(index); size = thumbnailPix.size(); size.rwidth() += ITEM_MARGIN * 2; size.rheight() += ITEM_MARGIN * 2; } return size; } bool ThumbnailBarItemDelegate::eventFilter(QObject*, QEvent* event) { switch (event->type()) { case QEvent::ToolTip: d->showToolTip(static_cast(event)); return true; case QEvent::HoverMove: case QEvent::HoverLeave: return d->hoverEventFilter(static_cast(event)); default: break; } return false; } void ThumbnailBarItemDelegate::paint(QPainter * painter, const QStyleOptionViewItem & option, const QModelIndex & index) const { + static int ct = 0; + ct++; + bool isSelected = option.state & QStyle::State_Selected; bool isCurrent = d->mView->selectionModel()->currentIndex() == index; QPixmap thumbnailPix = d->mView->thumbnailForIndex(index); + qDebug() << "THUMB " << ct; + qDebug() << "THUMB ThumbnailBarItemDelegate::paint dpr" << thumbnailPix.devicePixelRatio(); + thumbnailPix.save("/tmp/gv/thumb_" + QString::number(index.row()) + "_" + QString::number(index.column()) + "_" + QString::number(ct) + ".png"); QRect rect = option.rect; QStyleOptionViewItem opt = option; const QWidget* widget = opt.widget; QStyle* style = widget ? widget->style() : QApplication::style(); if (isSelected && !isCurrent) { // Draw selected but not current item backgrounds with some transparency // so that the current item stands out. painter->setOpacity(.33); } style->drawControl(QStyle::CE_ItemViewItem, &opt, painter, widget); painter->setOpacity(1); // Draw thumbnail if (!thumbnailPix.isNull()) { QRect thumbnailRect = QRect( rect.left() + (rect.width() - thumbnailPix.width()) / 2, rect.top() + (rect.height() - thumbnailPix.height()) / 2 - 1, - thumbnailPix.width(), - thumbnailPix.height()); + thumbnailPix.width() * thumbnailPix.devicePixelRatio(), + thumbnailPix.height() * thumbnailPix.devicePixelRatio()); if (!thumbnailPix.hasAlphaChannel()) { d->drawShadow(painter, thumbnailRect); painter->setPen(d->mBorderColor); painter->setRenderHint(QPainter::Antialiasing, false); QRect borderRect = thumbnailRect.adjusted(-1, -1, 0, 0); painter->drawRect(borderRect); } painter->drawPixmap(thumbnailRect.left(), thumbnailRect.top(), thumbnailPix); // Draw busy indicator if (d->mView->isBusy(index)) { QPixmap pix = d->mView->busySequenceCurrentPixmap(); painter->drawPixmap( thumbnailRect.left() + (thumbnailRect.width() - pix.width()) / 2, thumbnailRect.top() + (thumbnailRect.height() - pix.height()) / 2, pix); } } } void ThumbnailBarItemDelegate::toggleSelection() { d->mView->selectionModel()->select(d->mIndexUnderCursor, QItemSelectionModel::Toggle); d->updateToggleSelectionButton(); } ThumbnailBarItemDelegate::~ThumbnailBarItemDelegate() { delete d; } //this is disabled by David Edmundson as I can't figure out how to port it //I hope with breeze being the default we don't want to start making our own styles anyway #ifdef WINDOWS_PROXY_STYLE /** * This proxy style makes it possible to override the value returned by * styleHint() which leads to not-so-nice results with some styles. * * We cannot use QProxyStyle because it takes ownership of the base style, * which causes crash when user change styles. */ class ProxyStyle : public QWindowsStyle { public: ProxyStyle() : QWindowsStyle() { } void drawPrimitive(PrimitiveElement pe, const QStyleOption *opt, QPainter *p, const QWidget *w = 0) const { QApplication::style()->drawPrimitive(pe, opt, p, w); } void drawControl(ControlElement element, const QStyleOption *opt, QPainter *p, const QWidget *w = 0) const { QApplication::style()->drawControl(element, opt, p, w); } void drawComplexControl(ComplexControl cc, const QStyleOptionComplex *opt, QPainter *p, const QWidget *w = 0) const { QApplication::style()->drawComplexControl(cc, opt, p, w); } int styleHint(StyleHint sh, const QStyleOption *opt = 0, const QWidget *w = 0, QStyleHintReturn *shret = 0) const { switch (sh) { case SH_ItemView_ShowDecorationSelected: // We want the highlight to cover our thumbnail return true; case SH_ScrollView_FrameOnlyAroundContents: // Ensure the frame does not include the scrollbar. This ensure the // scrollbar touches the edge of the window and thus can touch the // edge of the screen when maximized return false; default: return QApplication::style()->styleHint(sh, opt, w, shret); } } void polish(QApplication* application) { QApplication::style()->polish(application); } void polish(QPalette& palette) { QApplication::style()->polish(palette); } void polish(QWidget* widget) { QApplication::style()->polish(widget); } void unpolish(QWidget* widget) { QApplication::style()->unpolish(widget); } void unpolish(QApplication* application) { QApplication::style()->unpolish(application); } int pixelMetric(PixelMetric pm, const QStyleOption* opt, const QWidget* widget) const { switch (pm) { case PM_MaximumDragDistance: // Ensure the fullscreen thumbnailbar does not go away while // dragging the scrollbar if the mouse cursor is too far away from // the widget return -1; default: return QApplication::style()->pixelMetric(pm, opt, widget); } } }; #endif// WINDOWS_PROXY_STYLE typedef int (QSize::*QSizeDimension)() const; struct ThumbnailBarViewPrivate { ThumbnailBarView* q; QStyle* mStyle; QTimeLine* mTimeLine; Qt::Orientation mOrientation; int mRowCount; QScrollBar* scrollBar() const { return mOrientation == Qt::Horizontal ? q->horizontalScrollBar() : q->verticalScrollBar(); } QSizeDimension mainDimension() const { return mOrientation == Qt::Horizontal ? &QSize::width : &QSize::height; } QSizeDimension oppositeDimension() const { return mOrientation == Qt::Horizontal ? &QSize::height : &QSize::width; } void smoothScrollTo(const QModelIndex& index) { if (!index.isValid()) { return; } const QRect rect = q->visualRect(index); int oldValue = scrollBar()->value(); int newValue = scrollToValue(rect); if (mTimeLine->state() == QTimeLine::Running) { mTimeLine->stop(); } mTimeLine->setFrameRange(oldValue, newValue); mTimeLine->start(); } int scrollToValue(const QRect& rect) { // This code is a much simplified version of // QListViewPrivate::horizontalScrollToValue() const QRect area = q->viewport()->rect(); int value = scrollBar()->value(); if (mOrientation == Qt::Horizontal) { if (q->isRightToLeft()) { value += (area.width() - rect.width()) / 2 - rect.left(); } else { value += rect.left() - (area.width() - rect.width()) / 2; } } else { value += rect.top() - (area.height() - rect.height()) / 2; } return value; } void updateMinMaxSizes() { QSizeDimension dimension = oppositeDimension(); int scrollBarSize = (scrollBar()->sizeHint().*dimension)(); QSize minSize(0, mRowCount * 48 + scrollBarSize); QSize maxSize(QWIDGETSIZE_MAX, mRowCount * 256 + scrollBarSize); if (mOrientation == Qt::Vertical) { minSize.transpose(); maxSize.transpose(); } q->setMinimumSize(minSize); q->setMaximumSize(maxSize); } void updateThumbnailSize() { QSizeDimension dimension = oppositeDimension(); int scrollBarSize = (scrollBar()->sizeHint().*dimension)(); int widgetSize = (q->size().*dimension)(); if (mRowCount > 1) { // Decrease widgetSize because otherwise the view sometimes wraps at // mRowCount-1 instead of mRowCount. Probably because gridSize * // mRowCount is too close to widgetSize. --widgetSize; } int gridWidth, gridHeight; if (mOrientation == Qt::Horizontal) { gridHeight = (widgetSize - scrollBarSize - 2 * q->frameWidth()) / mRowCount; gridWidth = qRound(gridHeight * q->thumbnailAspectRatio()); } else { gridWidth = (widgetSize - scrollBarSize - 2 * q->frameWidth()) / mRowCount; gridHeight = qRound(gridWidth / q->thumbnailAspectRatio()); } if (q->thumbnailScaleMode() == ThumbnailView::ScaleToFit) { q->setGridSize(QSize(gridWidth, gridHeight)); } + qDebug() << "widgetSize" << widgetSize; + qDebug() << "setThumbnailWidth" << gridHeight; + qDebug() << "setThumbnailheight" << (gridWidth - ITEM_MARGIN * 2); q->setThumbnailWidth(gridWidth - ITEM_MARGIN * 2); + q->setThumbnailDevicePixelRatio(qApp->devicePixelRatio()); } }; ThumbnailBarView::ThumbnailBarView(QWidget* parent) : ThumbnailView(parent) , d(new ThumbnailBarViewPrivate) { d->q = this; d->mTimeLine = new QTimeLine(SMOOTH_SCROLL_DURATION, this); connect(d->mTimeLine, &QTimeLine::frameChanged, this, &ThumbnailBarView::slotFrameChanged); d->mRowCount = 1; d->mOrientation = Qt::Vertical; // To pass value-has-changed check in setOrientation() setOrientation(Qt::Horizontal); setObjectName(QLatin1String("thumbnailBarView")); setWrapping(true); #ifdef WINDOWS_PROXY_STYLE d->mStyle = new ProxyStyle; setStyle(d->mStyle); #endif } ThumbnailBarView::~ThumbnailBarView() { #ifdef WINDOWS_PROXY_STYLE delete d->mStyle; #endif delete d; } Qt::Orientation ThumbnailBarView::orientation() const { return d->mOrientation; } void ThumbnailBarView::setOrientation(Qt::Orientation orientation) { if (d->mOrientation == orientation) { return; } d->mOrientation = orientation; if (d->mOrientation == Qt::Vertical) { setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn); setFlow(LeftToRight); } else { setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn); setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); setFlow(TopToBottom); } d->updateMinMaxSizes(); } void ThumbnailBarView::slotFrameChanged(int value) { d->scrollBar()->setValue(value); } void ThumbnailBarView::resizeEvent(QResizeEvent *event) { ThumbnailView::resizeEvent(event); d->updateThumbnailSize(); } void ThumbnailBarView::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { QListView::selectionChanged(selected, deselected); QModelIndexList oldList = deselected.indexes(); QModelIndexList newList = selected.indexes(); // Only scroll the list if the user went from one image to another. If the // user just unselected one image from a set of two, he might want to // reselect it again, scrolling the thumbnails would prevent him from // reselecting it by clicking again without moving the mouse. if (oldList.count() == 1 && newList.count() == 1 && isVisible()) { d->smoothScrollTo(newList.first()); } } void ThumbnailBarView::wheelEvent(QWheelEvent* event) { d->scrollBar()->setValue(d->scrollBar()->value() - event->delta()); } int ThumbnailBarView::rowCount() const { return d->mRowCount; } void ThumbnailBarView::setRowCount(int rowCount) { Q_ASSERT(rowCount > 0); d->mRowCount = rowCount; d->updateMinMaxSizes(); d->updateThumbnailSize(); } } // namespace diff --git a/lib/thumbnailview/thumbnailview.cpp b/lib/thumbnailview/thumbnailview.cpp index ec24be8a..9840497b 100644 --- a/lib/thumbnailview/thumbnailview.cpp +++ b/lib/thumbnailview/thumbnailview.cpp @@ -1,963 +1,981 @@ /* Gwenview: an image viewer Copyright 2007 Aurélien Gâteau This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "thumbnailview.h" // Std #include // Qt #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE #include #include #include #include // Local #include "abstractdocumentinfoprovider.h" #include "abstractthumbnailviewhelper.h" #include "archiveutils.h" #include "dragpixmapgenerator.h" #include "mimetypeutils.h" #include "urlutils.h" #include #include namespace Gwenview { #undef ENABLE_LOG #undef LOG //#define ENABLE_LOG #ifdef ENABLE_LOG #define LOG(x) //qDebug() << x #else #define LOG(x) ; #endif /** How many msec to wait before starting to smooth thumbnails */ const int SMOOTH_DELAY = 500; const int WHEEL_ZOOM_MULTIPLIER = 4; static KFileItem fileItemForIndex(const QModelIndex& index) { if (!index.isValid()) { LOG("Invalid index"); return KFileItem(); } QVariant data = index.data(KDirModel::FileItemRole); return qvariant_cast(data); } static QUrl urlForIndex(const QModelIndex& index) { KFileItem item = fileItemForIndex(index); return item.isNull() ? QUrl() : item.url(); } struct Thumbnail { Thumbnail(const QPersistentModelIndex& index_, const QDateTime& mtime) : mIndex(index_) , mModificationTime(mtime) , mFileSize(0) , mRough(true) , mWaitingForThumbnail(true) {} Thumbnail() : mFileSize(0) , mRough(true) , mWaitingForThumbnail(true) {} /** * Init the thumbnail based on a icon */ void initAsIcon(const QPixmap& pix) { mGroupPix = pix; int largeGroupSize = ThumbnailGroup::pixelSize(ThumbnailGroup::Large); mFullSize = QSize(largeGroupSize, largeGroupSize); } bool isGroupPixAdaptedForSize(int size) const { if (mWaitingForThumbnail) { return false; } if (mGroupPix.isNull()) { return false; } const int groupSize = qMax(mGroupPix.width(), mGroupPix.height()); if (groupSize >= size) { return true; } // groupSize is less than size, but this may be because the full image // is the same size as groupSize return groupSize == qMax(mFullSize.width(), mFullSize.height()); } void prepareForRefresh(const QDateTime& mtime) { mModificationTime = mtime; mFileSize = 0; mGroupPix = QPixmap(); mAdjustedPix = QPixmap(); mFullSize = QSize(); mRealFullSize = QSize(); mRough = true; mWaitingForThumbnail = true; } QPersistentModelIndex mIndex; QDateTime mModificationTime; /// The pix loaded from .thumbnails/{large,normal} QPixmap mGroupPix; /// Scaled version of mGroupPix, adjusted to ThumbnailView::thumbnailSize QPixmap mAdjustedPix; /// Size of the full image QSize mFullSize; /// Real size of the full image, invalid unless the thumbnail /// represents a raster image (not an icon) QSize mRealFullSize; /// File size of the full image KIO::filesize_t mFileSize; /// Whether mAdjustedPix represents has been scaled using fast or smooth /// transformation bool mRough; /// Set to true if mGroupPix should be replaced with a real thumbnail bool mWaitingForThumbnail; }; typedef QHash ThumbnailForUrl; typedef QQueue UrlQueue; typedef QSet PersistentModelIndexSet; struct ThumbnailViewPrivate { ThumbnailView* q; ThumbnailView::ThumbnailScaleMode mScaleMode; QSize mThumbnailSize; qreal mThumbnailAspectRatio; + qreal mThumbnailDevicePixelRatio; AbstractDocumentInfoProvider* mDocumentInfoProvider; AbstractThumbnailViewHelper* mThumbnailViewHelper; ThumbnailForUrl mThumbnailForUrl; QTimer mScheduledThumbnailGenerationTimer; UrlQueue mSmoothThumbnailQueue; QTimer mSmoothThumbnailTimer; QPixmap mWaitingThumbnail; QPointer mThumbnailProvider; PersistentModelIndexSet mBusyIndexSet; KPixmapSequence mBusySequence; QTimeLine* mBusyAnimationTimeLine; bool mCreateThumbnailsForRemoteUrls; void setupBusyAnimation() { mBusySequence = KIconLoader::global()->loadPixmapSequence(QStringLiteral("process-working"), 22); mBusyAnimationTimeLine = new QTimeLine(100 * mBusySequence.frameCount(), q); mBusyAnimationTimeLine->setCurveShape(QTimeLine::LinearCurve); mBusyAnimationTimeLine->setEndFrame(mBusySequence.frameCount() - 1); mBusyAnimationTimeLine->setLoopCount(0); QObject::connect(mBusyAnimationTimeLine, &QTimeLine::frameChanged, q, &ThumbnailView::updateBusyIndexes); } void scheduleThumbnailGeneration() { if (mThumbnailProvider) { mThumbnailProvider->removePendingItems(); } mSmoothThumbnailQueue.clear(); mScheduledThumbnailGenerationTimer.start(); } void updateThumbnailForModifiedDocument(const QModelIndex& index) { Q_ASSERT(mDocumentInfoProvider); KFileItem item = fileItemForIndex(index); QUrl url = item.url(); - ThumbnailGroup::Enum group = ThumbnailGroup::fromPixelSize(mThumbnailSize.width()); + ThumbnailGroup::Enum group = ThumbnailGroup::fromPixelSize(mThumbnailSize.width() * mThumbnailDevicePixelRatio); QPixmap pix; + pix.setDevicePixelRatio(mThumbnailDevicePixelRatio); QSize fullSize; mDocumentInfoProvider->thumbnailForDocument(url, group, &pix, &fullSize); mThumbnailForUrl[url] = Thumbnail(QPersistentModelIndex(index), QDateTime::currentDateTime()); q->setThumbnail(item, pix, fullSize, 0); } void appendItemsToThumbnailProvider(const KFileItemList& list) { if (mThumbnailProvider) { - ThumbnailGroup::Enum group = ThumbnailGroup::fromPixelSize(mThumbnailSize.width()); + ThumbnailGroup::Enum group = ThumbnailGroup::fromPixelSize(mThumbnailSize.width() * mThumbnailDevicePixelRatio); mThumbnailProvider->setThumbnailGroup(group); mThumbnailProvider->appendItems(list); } } void roughAdjustThumbnail(Thumbnail* thumbnail) { const QPixmap& mGroupPix = thumbnail->mGroupPix; const int groupSize = qMax(mGroupPix.width(), mGroupPix.height()); const int fullSize = qMax(thumbnail->mFullSize.width(), thumbnail->mFullSize.height()); - if (fullSize == groupSize && mGroupPix.height() <= mThumbnailSize.height() && mGroupPix.width() <= mThumbnailSize.width()) { + if (fullSize == groupSize && mGroupPix.height() <= (mThumbnailSize.height() * mThumbnailDevicePixelRatio) && mGroupPix.width() <= (mThumbnailSize.width() * mThumbnailDevicePixelRatio)) { thumbnail->mAdjustedPix = mGroupPix; + thumbnail->mAdjustedPix.setDevicePixelRatio(mThumbnailDevicePixelRatio); thumbnail->mRough = false; } else { thumbnail->mAdjustedPix = scale(mGroupPix, Qt::FastTransformation); + thumbnail->mAdjustedPix.setDevicePixelRatio(mThumbnailDevicePixelRatio); thumbnail->mRough = true; } } void initDragPixmap(QDrag* drag, const QModelIndexList& indexes) { const int thumbCount = qMin(indexes.count(), int(DragPixmapGenerator::MaxCount)); QList lst; for (int row = 0; row < thumbCount; ++row) { const QUrl url = urlForIndex(indexes[row]); lst << mThumbnailForUrl.value(url).mAdjustedPix; } DragPixmapGenerator::DragPixmap dragPixmap = DragPixmapGenerator::generate(lst, indexes.count()); drag->setPixmap(dragPixmap.pix); drag->setHotSpot(dragPixmap.hotSpot); } QPixmap scale(const QPixmap& pix, Qt::TransformationMode transformationMode) { + QPixmap scaledPix = QPixmap(); switch (mScaleMode) { case ThumbnailView::ScaleToFit: - return pix.scaled(mThumbnailSize.width(), mThumbnailSize.height(), Qt::KeepAspectRatio, transformationMode); + scaledPix = pix.scaled(mThumbnailSize.width() * mThumbnailDevicePixelRatio, mThumbnailSize.height() * mThumbnailDevicePixelRatio, Qt::KeepAspectRatio, transformationMode); break; case ThumbnailView::ScaleToSquare: { int minSize = qMin(pix.width(), pix.height()); QPixmap pix2 = pix.copy((pix.width() - minSize) / 2, (pix.height() - minSize) / 2, minSize, minSize); - return pix2.scaled(mThumbnailSize.width(), mThumbnailSize.height(), Qt::KeepAspectRatio, transformationMode); + scaledPix = pix2.scaled(mThumbnailSize.width() * mThumbnailDevicePixelRatio, mThumbnailSize.height() * mThumbnailDevicePixelRatio, Qt::KeepAspectRatio, transformationMode); + break; } case ThumbnailView::ScaleToHeight: - return pix.scaledToHeight(mThumbnailSize.height(), transformationMode); + scaledPix = pix.scaledToHeight(mThumbnailSize.height() * mThumbnailDevicePixelRatio, transformationMode); break; case ThumbnailView::ScaleToWidth: - return pix.scaledToWidth(mThumbnailSize.width(), transformationMode); + scaledPix = pix.scaledToWidth(mThumbnailSize.width() * mThumbnailDevicePixelRatio, transformationMode); break; } - // Keep compiler happy - Q_ASSERT(0); - return QPixmap(); + scaledPix.setDevicePixelRatio(mThumbnailDevicePixelRatio); + return scaledPix; } }; ThumbnailView::ThumbnailView(QWidget* parent) : QListView(parent) , d(new ThumbnailViewPrivate) { d->q = this; d->mScaleMode = ScaleToFit; d->mThumbnailViewHelper = 0; d->mDocumentInfoProvider = 0; d->mThumbnailProvider = 0; // Init to some stupid value so that the first call to setThumbnailSize() // is not ignored (do not use 0 in case someone try to divide by // mThumbnailSize...) d->mThumbnailSize = QSize(1, 1); d->mThumbnailAspectRatio = 1; + d->mThumbnailDevicePixelRatio = 1; d->mCreateThumbnailsForRemoteUrls = true; setFrameShape(QFrame::NoFrame); setViewMode(QListView::IconMode); setResizeMode(QListView::Adjust); setDragEnabled(true); setAcceptDrops(true); setDropIndicatorShown(true); setUniformItemSizes(true); setEditTriggers(QAbstractItemView::EditKeyPressed); d->setupBusyAnimation(); setVerticalScrollMode(ScrollPerPixel); setHorizontalScrollMode(ScrollPerPixel); d->mScheduledThumbnailGenerationTimer.setSingleShot(true); d->mScheduledThumbnailGenerationTimer.setInterval(500); connect(&d->mScheduledThumbnailGenerationTimer, &QTimer::timeout, this, &ThumbnailView::generateThumbnailsForItems); d->mSmoothThumbnailTimer.setSingleShot(true); connect(&d->mSmoothThumbnailTimer, &QTimer::timeout, this, &ThumbnailView::smoothNextThumbnail); setContextMenuPolicy(Qt::CustomContextMenu); connect(this, &ThumbnailView::customContextMenuRequested, this, &ThumbnailView::showContextMenu); connect(this, &ThumbnailView::activated, this, &ThumbnailView::emitIndexActivatedIfNoModifiers); } ThumbnailView::~ThumbnailView() { delete d; } ThumbnailView::ThumbnailScaleMode ThumbnailView::thumbnailScaleMode() const { return d->mScaleMode; } void ThumbnailView::setThumbnailScaleMode(ThumbnailScaleMode mode) { d->mScaleMode = mode; setUniformItemSizes(mode == ScaleToFit || mode == ScaleToSquare); } void ThumbnailView::setModel(QAbstractItemModel* newModel) { if (model()) { disconnect(model(), 0, this, 0); } QListView::setModel(newModel); connect(model(), SIGNAL(rowsRemoved(QModelIndex,int,int)), SIGNAL(rowsRemovedSignal(QModelIndex,int,int))); } void ThumbnailView::setThumbnailProvider(ThumbnailProvider* thumbnailProvider) { GV_RETURN_IF_FAIL(d->mThumbnailProvider != thumbnailProvider); if (thumbnailProvider) { connect(thumbnailProvider, SIGNAL(thumbnailLoaded(KFileItem,QPixmap,QSize,qulonglong)), SLOT(setThumbnail(KFileItem,QPixmap,QSize,qulonglong))); connect(thumbnailProvider, SIGNAL(thumbnailLoadingFailed(KFileItem)), SLOT(setBrokenThumbnail(KFileItem))); } else { disconnect(d->mThumbnailProvider, 0 , this, 0); } d->mThumbnailProvider = thumbnailProvider; } void ThumbnailView::updateThumbnailSize() { QSize value = d->mThumbnailSize; // mWaitingThumbnail int waitingThumbnailSize; if (value.width() > 64) { waitingThumbnailSize = 48; } else { waitingThumbnailSize = 32; } QPixmap icon = DesktopIcon("chronometer", waitingThumbnailSize); QPixmap pix(value); + pix.setDevicePixelRatio(d->mThumbnailDevicePixelRatio); pix.fill(Qt::transparent); QPainter painter(&pix); painter.setOpacity(0.5); painter.drawPixmap((value.width() - icon.width()) / 2, (value.height() - icon.height()) / 2, icon); painter.end(); d->mWaitingThumbnail = pix; // Stop smoothing d->mSmoothThumbnailTimer.stop(); d->mSmoothThumbnailQueue.clear(); // Clear adjustedPixes ThumbnailForUrl::iterator it = d->mThumbnailForUrl.begin(), end = d->mThumbnailForUrl.end(); for (; it != end; ++it) { it.value().mAdjustedPix = QPixmap(); } thumbnailSizeChanged(value); thumbnailWidthChanged(value.width()); if (d->mScaleMode != ScaleToFit) { scheduleDelayedItemsLayout(); } d->scheduleThumbnailGeneration(); } void ThumbnailView::setThumbnailWidth(int width) { if(d->mThumbnailSize.width() == width) { return; } int height = round((qreal)width / d->mThumbnailAspectRatio); d->mThumbnailSize = QSize(width, height); updateThumbnailSize(); } void ThumbnailView::setThumbnailAspectRatio(qreal ratio) { if(d->mThumbnailAspectRatio == ratio) { return; } d->mThumbnailAspectRatio = ratio; int width = d->mThumbnailSize.width(); int height = round((qreal)width / d->mThumbnailAspectRatio); d->mThumbnailSize = QSize(width, height); updateThumbnailSize(); } +void ThumbnailView::setThumbnailDevicePixelRatio(qreal dpr) +{ + d->mThumbnailDevicePixelRatio = dpr; + updateThumbnailSize(); +} + qreal ThumbnailView::thumbnailAspectRatio() const { return d->mThumbnailAspectRatio; } QSize ThumbnailView::thumbnailSize() const { return d->mThumbnailSize; } void ThumbnailView::setThumbnailViewHelper(AbstractThumbnailViewHelper* helper) { d->mThumbnailViewHelper = helper; } AbstractThumbnailViewHelper* ThumbnailView::thumbnailViewHelper() const { return d->mThumbnailViewHelper; } void ThumbnailView::setDocumentInfoProvider(AbstractDocumentInfoProvider* provider) { d->mDocumentInfoProvider = provider; if (provider) { connect(provider, SIGNAL(busyStateChanged(QModelIndex,bool)), SLOT(updateThumbnailBusyState(QModelIndex,bool))); connect(provider, SIGNAL(documentChanged(QModelIndex)), SLOT(updateThumbnail(QModelIndex))); } } AbstractDocumentInfoProvider* ThumbnailView::documentInfoProvider() const { return d->mDocumentInfoProvider; } void ThumbnailView::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) { QListView::rowsAboutToBeRemoved(parent, start, end); // Remove references to removed items KFileItemList itemList; for (int pos = start; pos <= end; ++pos) { QModelIndex index = model()->index(pos, 0, parent); KFileItem item = fileItemForIndex(index); if (item.isNull()) { //qDebug() << "Skipping invalid item!" << index.data().toString(); continue; } QUrl url = item.url(); d->mThumbnailForUrl.remove(url); d->mSmoothThumbnailQueue.removeAll(url); itemList.append(item); } if (d->mThumbnailProvider) { d->mThumbnailProvider->removeItems(itemList); } // Removing rows might make new images visible, make sure their thumbnail // is generated d->mScheduledThumbnailGenerationTimer.start(); } void ThumbnailView::rowsInserted(const QModelIndex& parent, int start, int end) { QListView::rowsInserted(parent, start, end); d->mScheduledThumbnailGenerationTimer.start(); rowsInsertedSignal(parent, start, end); } void ThumbnailView::dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector &roles) { QListView::dataChanged(topLeft, bottomRight, roles); bool thumbnailsNeedRefresh = false; for (int row = topLeft.row(); row <= bottomRight.row(); ++row) { QModelIndex index = model()->index(row, 0); KFileItem item = fileItemForIndex(index); if (item.isNull()) { qWarning() << "Invalid item for index" << index << ". This should not happen!"; GV_FATAL_FAILS; continue; } ThumbnailForUrl::Iterator it = d->mThumbnailForUrl.find(item.url()); if (it != d->mThumbnailForUrl.end()) { // All thumbnail views are connected to the model, so // ThumbnailView::dataChanged() is called for all of them. As a // result this method will also be called for views which are not // currently visible, and do not yet have a thumbnail for the // modified url. QDateTime mtime = item.time(KFileItem::ModificationTime); if (it->mModificationTime != mtime || it->mFileSize != item.size()) { // dataChanged() is called when the file changes but also when // the model fetched additional data such as semantic info. To // avoid needless refreshes, we only trigger a refresh if the // modification time changes. thumbnailsNeedRefresh = true; it->prepareForRefresh(mtime); } } } if (thumbnailsNeedRefresh) { d->mScheduledThumbnailGenerationTimer.start(); } } void ThumbnailView::showContextMenu() { d->mThumbnailViewHelper->showContextMenu(this); } void ThumbnailView::emitIndexActivatedIfNoModifiers(const QModelIndex& index) { if (QApplication::keyboardModifiers() == Qt::NoModifier) { emit indexActivated(index); } } void ThumbnailView::setThumbnail(const KFileItem& item, const QPixmap& pixmap, const QSize& size, qulonglong fileSize) { ThumbnailForUrl::iterator it = d->mThumbnailForUrl.find(item.url()); if (it == d->mThumbnailForUrl.end()) { return; } Thumbnail& thumbnail = it.value(); thumbnail.mGroupPix = pixmap; thumbnail.mAdjustedPix = QPixmap(); int largeGroupSize = ThumbnailGroup::pixelSize(ThumbnailGroup::Large); thumbnail.mFullSize = size.isValid() ? size : QSize(largeGroupSize, largeGroupSize); thumbnail.mRealFullSize = size; thumbnail.mWaitingForThumbnail = false; thumbnail.mFileSize = fileSize; update(thumbnail.mIndex); if (d->mScaleMode != ScaleToFit) { scheduleDelayedItemsLayout(); } } void ThumbnailView::setBrokenThumbnail(const KFileItem& item) { ThumbnailForUrl::iterator it = d->mThumbnailForUrl.find(item.url()); if (it == d->mThumbnailForUrl.end()) { return; } Thumbnail& thumbnail = it.value(); MimeTypeUtils::Kind kind = MimeTypeUtils::fileItemKind(item); if (kind == MimeTypeUtils::KIND_VIDEO) { // Special case for videos because our kde install may come without // support for video thumbnails so we show the mimetype icon instead of // a broken image icon QPixmap pix = KIconLoader::global()->loadMimeTypeIcon(item.iconName(), KIconLoader::Desktop, d->mThumbnailSize.height()); thumbnail.initAsIcon(pix); } else if (kind == MimeTypeUtils::KIND_DIR) { // Special case for folders because ThumbnailProvider does not return a // thumbnail if there is no images thumbnail.mWaitingForThumbnail = false; return; } else { thumbnail.initAsIcon(DesktopIcon("image-missing", 48)); thumbnail.mFullSize = thumbnail.mGroupPix.size(); } update(thumbnail.mIndex); } QPixmap ThumbnailView::thumbnailForIndex(const QModelIndex& index, QSize* fullSize) { KFileItem item = fileItemForIndex(index); if (item.isNull()) { LOG("Invalid item"); if (fullSize) { *fullSize = QSize(); } return QPixmap(); } QUrl url = item.url(); // Find or create Thumbnail instance ThumbnailForUrl::Iterator it = d->mThumbnailForUrl.find(url); if (it == d->mThumbnailForUrl.end()) { Thumbnail thumbnail = Thumbnail(QPersistentModelIndex(index), item.time(KFileItem::ModificationTime)); it = d->mThumbnailForUrl.insert(url, thumbnail); } Thumbnail& thumbnail = it.value(); // If dir or archive, generate a thumbnail from fileitem pixmap MimeTypeUtils::Kind kind = MimeTypeUtils::fileItemKind(item); if (kind == MimeTypeUtils::KIND_ARCHIVE || kind == MimeTypeUtils::KIND_DIR) { - int groupSize = ThumbnailGroup::pixelSize(ThumbnailGroup::fromPixelSize(d->mThumbnailSize.height())); + int groupSize = ThumbnailGroup::pixelSize(ThumbnailGroup::fromPixelSize(d->mThumbnailSize.height() * d->mThumbnailDevicePixelRatio)); if (thumbnail.mGroupPix.isNull() || thumbnail.mGroupPix.height() < groupSize) { - QPixmap pix = KIconLoader::global()->loadMimeTypeIcon(item.iconName(), KIconLoader::Desktop, d->mThumbnailSize.height()); + QPixmap pix = KIconLoader::global()->loadMimeTypeIcon(item.iconName(), KIconLoader::Desktop, d->mThumbnailSize.height() * d->mThumbnailDevicePixelRatio); + pix.setDevicePixelRatio(d->mThumbnailDevicePixelRatio); thumbnail.initAsIcon(pix); if (kind == MimeTypeUtils::KIND_ARCHIVE) { // No thumbnails for archives thumbnail.mWaitingForThumbnail = false; } else if (!d->mCreateThumbnailsForRemoteUrls && !UrlUtils::urlIsFastLocalFile(url)) { // If we don't want thumbnails for remote urls, use // "folder-remote" icon for remote folders, so that they do // not look like regular folders thumbnail.mWaitingForThumbnail = false; thumbnail.initAsIcon(DesktopIcon("folder-remote", groupSize)); } else { // set mWaitingForThumbnail to true (necessary in the case // 'thumbnail' already existed before, but with a too small // mGroupPix) thumbnail.mWaitingForThumbnail = true; } } } if (thumbnail.mGroupPix.isNull()) { if (fullSize) { *fullSize = QSize(); } + qDebug() << "waiting thumbnail"; return d->mWaitingThumbnail; } // Adjust thumbnail if (thumbnail.mAdjustedPix.isNull()) { + qDebug() << "roughAdjustThumbnail"; d->roughAdjustThumbnail(&thumbnail); } if (thumbnail.mRough && !d->mSmoothThumbnailQueue.contains(url)) { + qDebug() << "mSmoothThumbnailQueue.enqueue"; d->mSmoothThumbnailQueue.enqueue(url); if (!d->mSmoothThumbnailTimer.isActive()) { d->mSmoothThumbnailTimer.start(SMOOTH_DELAY); } } if (fullSize) { *fullSize = thumbnail.mRealFullSize; } return thumbnail.mAdjustedPix; } bool ThumbnailView::isModified(const QModelIndex& index) const { if (!d->mDocumentInfoProvider) { return false; } QUrl url = urlForIndex(index); return d->mDocumentInfoProvider->isModified(url); } bool ThumbnailView::isBusy(const QModelIndex& index) const { if (!d->mDocumentInfoProvider) { return false; } QUrl url = urlForIndex(index); return d->mDocumentInfoProvider->isBusy(url); } void ThumbnailView::startDrag(Qt::DropActions supportedActions) { QModelIndexList indexes = selectionModel()->selectedIndexes(); if (indexes.isEmpty()) { return; } QDrag* drag = new QDrag(this); drag->setMimeData(model()->mimeData(indexes)); d->initDragPixmap(drag, indexes); drag->exec(supportedActions, Qt::CopyAction); } void ThumbnailView::dragEnterEvent(QDragEnterEvent* event) { QAbstractItemView::dragEnterEvent(event); if (event->mimeData()->hasUrls()) { event->acceptProposedAction(); } } void ThumbnailView::dragMoveEvent(QDragMoveEvent* event) { // Necessary, otherwise we don't reach dropEvent() QAbstractItemView::dragMoveEvent(event); event->acceptProposedAction(); } void ThumbnailView::dropEvent(QDropEvent* event) { const QList urlList = KUrlMimeData::urlsFromMimeData(event->mimeData()); if (urlList.isEmpty()) { return; } QModelIndex destIndex = indexAt(event->pos()); if (destIndex.isValid()) { KFileItem item = fileItemForIndex(destIndex); if (item.isDir()) { QUrl destUrl = item.url(); d->mThumbnailViewHelper->showMenuForUrlDroppedOnDir(this, urlList, destUrl); return; } } d->mThumbnailViewHelper->showMenuForUrlDroppedOnViewport(this, urlList); event->acceptProposedAction(); } void ThumbnailView::keyPressEvent(QKeyEvent* event) { QListView::keyPressEvent(event); if (event->key() == Qt::Key_Return) { const QModelIndex index = selectionModel()->currentIndex(); if (index.isValid() && selectionModel()->selectedIndexes().count() == 1) { emit indexActivated(index); } } } void ThumbnailView::resizeEvent(QResizeEvent* event) { QListView::resizeEvent(event); d->scheduleThumbnailGeneration(); } void ThumbnailView::showEvent(QShowEvent* event) { QListView::showEvent(event); d->scheduleThumbnailGeneration(); QTimer::singleShot(0, this, SLOT(scrollToSelectedIndex())); } void ThumbnailView::wheelEvent(QWheelEvent* event) { // If we don't adjust the single step, the wheel scroll exactly one item up // and down, giving the impression that the items do not move but only // their label changes. // For some reason it is necessary to set the step here: setting it in // setThumbnailSize() does not work //verticalScrollBar()->setSingleStep(d->mThumbnailSize / 5); if (event->modifiers() == Qt::ControlModifier) { int width = d->mThumbnailSize.width() + (event->delta() > 0 ? 1 : -1) * WHEEL_ZOOM_MULTIPLIER; width = qMax(int(MinThumbnailSize), qMin(width, int(MaxThumbnailSize))); setThumbnailWidth(width); } else { QListView::wheelEvent(event); } } void ThumbnailView::scrollToSelectedIndex() { QModelIndexList list = selectedIndexes(); if (list.count() >= 1) { scrollTo(list.first(), PositionAtCenter); } } void ThumbnailView::selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) { QListView::selectionChanged(selected, deselected); emit selectionChangedSignal(selected, deselected); } void ThumbnailView::scrollContentsBy(int dx, int dy) { QListView::scrollContentsBy(dx, dy); d->scheduleThumbnailGeneration(); } void ThumbnailView::generateThumbnailsForItems() { if (!isVisible() || !model()) { return; } const QRect visibleRect = viewport()->rect(); const int visibleSurface = visibleRect.width() * visibleRect.height(); const QPoint origin = visibleRect.center(); // distance => item QMultiMap itemMap; for (int row = 0; row < model()->rowCount(); ++row) { QModelIndex index = model()->index(row, 0); KFileItem item = fileItemForIndex(index); QUrl url = item.url(); // Filter out remote items if necessary if (!d->mCreateThumbnailsForRemoteUrls && !url.isLocalFile()) { continue; } // Filter out archives MimeTypeUtils::Kind kind = MimeTypeUtils::fileItemKind(item); if (kind == MimeTypeUtils::KIND_ARCHIVE) { continue; } // Immediately update modified items if (d->mDocumentInfoProvider && d->mDocumentInfoProvider->isModified(url)) { d->updateThumbnailForModifiedDocument(index); continue; } // Filter out items which already have a thumbnail ThumbnailForUrl::ConstIterator it = d->mThumbnailForUrl.constFind(url); if (it != d->mThumbnailForUrl.constEnd() && it.value().isGroupPixAdaptedForSize(d->mThumbnailSize.height())) { continue; } // Compute distance int distance; const QRect itemRect = visualRect(index); const qreal itemSurface = itemRect.width() * itemRect.height(); const QRect visibleItemRect = visibleRect.intersected(itemRect); qreal visibleItemFract = 0; if (itemSurface > 0) { visibleItemFract = visibleItemRect.width() * visibleItemRect.height() / itemSurface; } if (visibleItemFract > 0.7) { // Item is visible, order thumbnails from left to right, top to bottom // Distance is computed so that it is between 0 and visibleSurface distance = itemRect.top() * visibleRect.width() + itemRect.left(); // Make sure directory thumbnails are generated after image thumbnails: // Distance is between visibleSurface and 2 * visibleSurface if (kind == MimeTypeUtils::KIND_DIR) { distance = distance + visibleSurface; } } else { // Item is not visible, order thumbnails according to distance // Start at 2 * visibleSurface to ensure invisible thumbnails are // generated *after* visible thumbnails distance = 2 * visibleSurface + (itemRect.center() - origin).manhattanLength(); } // Add the item to our map itemMap.insert(distance, item); // Insert the thumbnail in mThumbnailForUrl, so that // setThumbnail() can find the item to update if (it == d->mThumbnailForUrl.constEnd()) { Thumbnail thumbnail = Thumbnail(QPersistentModelIndex(index), item.time(KFileItem::ModificationTime)); d->mThumbnailForUrl.insert(url, thumbnail); } } if (!itemMap.isEmpty()) { d->appendItemsToThumbnailProvider(itemMap.values()); } } void ThumbnailView::updateThumbnail(const QModelIndex& index) { KFileItem item = fileItemForIndex(index); QUrl url = item.url(); if (d->mDocumentInfoProvider && d->mDocumentInfoProvider->isModified(url)) { d->updateThumbnailForModifiedDocument(index); } else { KFileItemList list; list << item; d->appendItemsToThumbnailProvider(list); } } void ThumbnailView::updateThumbnailBusyState(const QModelIndex& _index, bool busy) { QPersistentModelIndex index(_index); if (busy && !d->mBusyIndexSet.contains(index)) { d->mBusyIndexSet << index; update(index); if (d->mBusyAnimationTimeLine->state() != QTimeLine::Running) { d->mBusyAnimationTimeLine->start(); } } else if (!busy && d->mBusyIndexSet.remove(index)) { update(index); if (d->mBusyIndexSet.isEmpty()) { d->mBusyAnimationTimeLine->stop(); } } } void ThumbnailView::updateBusyIndexes() { Q_FOREACH(const QPersistentModelIndex & index, d->mBusyIndexSet) { update(index); } } QPixmap ThumbnailView::busySequenceCurrentPixmap() const { return d->mBusySequence.frameAt(d->mBusyAnimationTimeLine->currentFrame()); } void ThumbnailView::smoothNextThumbnail() { if (d->mSmoothThumbnailQueue.isEmpty()) { return; } if (d->mThumbnailProvider && d->mThumbnailProvider->isRunning()) { // give mThumbnailProvider priority over smoothing d->mSmoothThumbnailTimer.start(SMOOTH_DELAY); return; } QUrl url = d->mSmoothThumbnailQueue.dequeue(); ThumbnailForUrl::Iterator it = d->mThumbnailForUrl.find(url); GV_RETURN_IF_FAIL2(it != d->mThumbnailForUrl.end(), url << "not in mThumbnailForUrl."); Thumbnail& thumbnail = it.value(); + qDebug() << "scale to " << d->mThumbnailSize.width() << "*" << d->mThumbnailDevicePixelRatio; thumbnail.mAdjustedPix = d->scale(thumbnail.mGroupPix, Qt::SmoothTransformation); thumbnail.mRough = false; GV_RETURN_IF_FAIL2(thumbnail.mIndex.isValid(), "index for" << url << "is invalid."); update(thumbnail.mIndex); if (!d->mSmoothThumbnailQueue.isEmpty()) { d->mSmoothThumbnailTimer.start(0); } } void ThumbnailView::reloadThumbnail(const QModelIndex& index) { QUrl url = urlForIndex(index); if (!url.isValid()) { qWarning() << "Invalid url for index" << index; return; } ThumbnailProvider::deleteImageThumbnail(url); ThumbnailForUrl::Iterator it = d->mThumbnailForUrl.find(url); if (it == d->mThumbnailForUrl.end()) { return; } d->mThumbnailForUrl.erase(it); generateThumbnailsForItems(); } void ThumbnailView::setCreateThumbnailsForRemoteUrls(bool createRemoteThumbs) { d->mCreateThumbnailsForRemoteUrls = createRemoteThumbs; } } // namespace diff --git a/lib/thumbnailview/thumbnailview.h b/lib/thumbnailview/thumbnailview.h index 7abc9f1b..96b451cd 100644 --- a/lib/thumbnailview/thumbnailview.h +++ b/lib/thumbnailview/thumbnailview.h @@ -1,216 +1,221 @@ /* Gwenview: an image viewer Copyright 2007 Aurélien Gâteau This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef THUMBNAILVIEW_H #define THUMBNAILVIEW_H #include // Qt #include // KDE #include class KFileItem; class QDragEnterEvent; class QDragMoveEvent; class QDropEvent; class QPixmap; namespace Gwenview { class AbstractDocumentInfoProvider; class AbstractThumbnailViewHelper; class ThumbnailProvider; struct ThumbnailViewPrivate; class GWENVIEWLIB_EXPORT ThumbnailView : public QListView { Q_OBJECT public: enum { MinThumbnailSize = 48, MaxThumbnailSize = 256 }; enum ThumbnailScaleMode { ScaleToSquare, ScaleToHeight, ScaleToWidth, ScaleToFit }; ThumbnailView(QWidget* parent); ~ThumbnailView(); void setThumbnailViewHelper(AbstractThumbnailViewHelper* helper); AbstractThumbnailViewHelper* thumbnailViewHelper() const; void setDocumentInfoProvider(AbstractDocumentInfoProvider* provider); AbstractDocumentInfoProvider* documentInfoProvider() const; ThumbnailScaleMode thumbnailScaleMode() const; void setThumbnailScaleMode(ThumbnailScaleMode); /** * Returns the thumbnail size. */ QSize thumbnailSize() const; /** * Returns the aspect ratio of the thumbnail. */ qreal thumbnailAspectRatio() const; QPixmap thumbnailForIndex(const QModelIndex&, QSize* fullSize = 0); /** * Returns true if the document pointed by the index has been modified * inside Gwenview. */ bool isModified(const QModelIndex&) const; /** * Returns true if the document pointed by the index is currently busy * (loading, saving, rotating...) */ bool isBusy(const QModelIndex& index) const; virtual void setModel(QAbstractItemModel* model) Q_DECL_OVERRIDE; void setThumbnailProvider(ThumbnailProvider* thumbnailProvider); /** * Publish this method so that delegates can call it. */ using QListView::scheduleDelayedItemsLayout; /** * Returns the current pixmap to paint when drawing a busy index. */ QPixmap busySequenceCurrentPixmap() const; void reloadThumbnail(const QModelIndex&); void updateThumbnailSize(); void setCreateThumbnailsForRemoteUrls(bool createRemoteThumbs); Q_SIGNALS: /** * It seems we can't use the 'activated()' signal for now because it does * not know about KDE single vs doubleclick settings. The indexActivated() * signal replaces it. */ void indexActivated(const QModelIndex&); void urlListDropped(const QList& lst, const QUrl &destination); void thumbnailSizeChanged(const QSize&); void thumbnailWidthChanged(int); /** * Emitted whenever selectionChanged() is called. * This signal is suffixed with "Signal" because * QAbstractItemView::selectionChanged() is a slot. */ void selectionChangedSignal(const QItemSelection&, const QItemSelection&); /** * Forward some signals from model, so that the delegate can use them */ void rowsRemovedSignal(const QModelIndex& parent, int start, int end); void rowsInsertedSignal(const QModelIndex& parent, int start, int end); public Q_SLOTS: /** * Sets the thumbnail's width, in pixels. Keeps aspect ratio unchanged. */ void setThumbnailWidth(int width); /** * Sets the thumbnail's aspect ratio. Keeps width unchanged. */ void setThumbnailAspectRatio(qreal ratio); + /** + * Sets the device pixel ratio for HiDPI rendering. + */ + void setThumbnailDevicePixelRatio(qreal dpr); + void scrollToSelectedIndex(); void generateThumbnailsForItems(); protected: virtual void dragEnterEvent(QDragEnterEvent*) Q_DECL_OVERRIDE; virtual void dragMoveEvent(QDragMoveEvent*) Q_DECL_OVERRIDE; virtual void dropEvent(QDropEvent*) Q_DECL_OVERRIDE; virtual void keyPressEvent(QKeyEvent*) Q_DECL_OVERRIDE; virtual void resizeEvent(QResizeEvent*) Q_DECL_OVERRIDE; virtual void scrollContentsBy(int dx, int dy) Q_DECL_OVERRIDE; virtual void showEvent(QShowEvent*) Q_DECL_OVERRIDE; virtual void wheelEvent(QWheelEvent*) Q_DECL_OVERRIDE; virtual void startDrag(Qt::DropActions) Q_DECL_OVERRIDE; protected Q_SLOTS: virtual void rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) Q_DECL_OVERRIDE; virtual void rowsInserted(const QModelIndex& parent, int start, int end) Q_DECL_OVERRIDE; virtual void selectionChanged(const QItemSelection& selected, const QItemSelection& deselected) Q_DECL_OVERRIDE; virtual void dataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight, const QVector &roles = QVector()) Q_DECL_OVERRIDE; private Q_SLOTS: void showContextMenu(); void emitIndexActivatedIfNoModifiers(const QModelIndex&); void setThumbnail(const KFileItem&, const QPixmap&, const QSize&, qulonglong fileSize); void setBrokenThumbnail(const KFileItem&); /** * Generate thumbnail for @a index. */ void updateThumbnail(const QModelIndex& index); /** * Tells the view the busy state of the document pointed by the index has changed. */ void updateThumbnailBusyState(const QModelIndex& index, bool); /* * Cause a repaint of all busy indexes */ void updateBusyIndexes(); void smoothNextThumbnail(); private: friend struct ThumbnailViewPrivate; ThumbnailViewPrivate * const d; }; } // namespace #endif /* THUMBNAILVIEW_H */