diff --git a/app/documentpanel.cpp b/app/documentpanel.cpp index a6ea2aa7..18bb3b89 100644 --- a/app/documentpanel.cpp +++ b/app/documentpanel.cpp @@ -1,702 +1,702 @@ /* 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 "documentpanel.moc" // Qt #include #include #include #include #include #include // KDE #include #include #include #include #include #include #include // Local #include "fileoperations.h" #include "splitter.h" #include #include #include #include #include #include #include #include #include #include #include #include #include namespace Gwenview { #undef ENABLE_LOG #undef LOG //#define ENABLE_LOG #ifdef ENABLE_LOG #define LOG(x) kDebug() << x #else #define LOG(x) ; #endif const int DocumentPanel::MaxViewCount = 6; static QString rgba(const QColor &color) { return QString::fromAscii("rgba(%1, %2, %3, %4)") .arg(color.red()) .arg(color.green()) .arg(color.blue()) .arg(color.alpha()); } static QString gradient(Qt::Orientation orientation, const QColor &color, int value) { int x2, y2; if (orientation == Qt::Horizontal) { x2 = 0; y2 = 1; } else { x2 = 1; y2 = 0; } QString grad = "qlineargradient(x1:0, y1:0, x2:%1, y2:%2," "stop:0 %3, stop: 1 %4)"; return grad .arg(x2) .arg(y2) .arg(rgba(PaintUtils::adjustedHsv(color, 0, 0, qMin(255 - color.value(), value/2)))) .arg(rgba(PaintUtils::adjustedHsv(color, 0, 0, -qMin(color.value(), value/2)))) ; } /* * Layout of mThumbnailSplitter is: * * +-mThumbnailSplitter--------------------------------+ * |+-mAdapterContainer-------------------------------+| * ||+-mDocumentViewContainer------------------------+|| * |||+-DocumentView--------++-DocumentView---------+||| * |||| || |||| * |||| || |||| * |||| || |||| * |||| || |||| * |||| || |||| * |||| || |||| * |||+---------------------++----------------------+||| * ||+-----------------------------------------------+|| * ||+-mStatusBarContainer---------------------------+|| * |||[mToggleThumbnailBarButton] [mZoomWidget]||| * ||+-----------------------------------------------+|| * |+-------------------------------------------------+| * |===================================================| * |+-mThumbnailBar-----------------------------------+| * || || * || || * |+-------------------------------------------------+| * +---------------------------------------------------+ */ struct DocumentPanelPrivate { DocumentPanel* that; SlideShow* mSlideShow; KActionCollection* mActionCollection; QSplitter *mThumbnailSplitter; QWidget* mAdapterContainer; DocumentViewController* mDocumentViewController; QList mDocumentViews; DocumentViewSynchronizer* mSynchronizer; QToolButton* mToggleThumbnailBarButton; DocumentViewContainer* mDocumentViewContainer; QWidget* mStatusBarContainer; ThumbnailBarView* mThumbnailBar; KToggleAction* mToggleThumbnailBarAction; KToggleAction* mSynchronizeAction; QCheckBox* mSynchronizeCheckBox; bool mFullScreenMode; QPalette mNormalPalette; QPalette mFullScreenPalette; bool mCompareMode; bool mThumbnailBarVisibleBeforeFullScreen; void setupThumbnailBar() { mThumbnailBar = new ThumbnailBarView; ThumbnailBarItemDelegate* delegate = new ThumbnailBarItemDelegate(mThumbnailBar); mThumbnailBar->setItemDelegate(delegate); mThumbnailBar->setVisible(GwenviewConfig::thumbnailBarIsVisible()); mThumbnailBar->setSelectionMode(QAbstractItemView::ExtendedSelection); } void setupThumbnailBarStyleSheet() { Qt::Orientation orientation = mThumbnailBar->orientation(); QColor bgColor = mNormalPalette.color(QPalette::Normal, QPalette::Window); QColor bgSelColor = mNormalPalette.color(QPalette::Normal, QPalette::Highlight); // Avoid dark and bright colors bgColor.setHsv(bgColor.hue(), bgColor.saturation(), (127 + 3 * bgColor.value()) / 4); QColor leftBorderColor = PaintUtils::adjustedHsv(bgColor, 0, 0, qMin(20, 255 - bgColor.value())); QColor rightBorderColor = PaintUtils::adjustedHsv(bgColor, 0, 0, -qMin(40, bgColor.value())); QColor borderSelColor = PaintUtils::adjustedHsv(bgSelColor, 0, 0, -qMin(60, bgSelColor.value())); QString viewCss = "#thumbnailBarView {" " background-color: rgba(0, 0, 0, 10%);" "}"; QString itemCss = "QListView::item {" " background-color: %1;" " border-left: 1px solid %2;" " border-right: 1px solid %3;" "}"; itemCss = itemCss.arg( gradient(orientation, bgColor, 46), gradient(orientation, leftBorderColor, 36), gradient(orientation, rightBorderColor, 26)); QString itemSelCss = "QListView::item:selected {" " background-color: %1;" " border-left: 1px solid %2;" " border-right: 1px solid %2;" "}"; itemSelCss = itemSelCss.arg( gradient(orientation, bgSelColor, 56), rgba(borderSelColor)); QString css = viewCss + itemCss + itemSelCss; if (orientation == Qt::Vertical) { css.replace("left", "top").replace("right", "bottom"); } mThumbnailBar->setStyleSheet(css); } void setupAdapterContainer() { mAdapterContainer = new QWidget; QVBoxLayout* layout = new QVBoxLayout(mAdapterContainer); layout->setMargin(0); layout->setSpacing(0); mDocumentViewContainer = new DocumentViewContainer; mDocumentViewContainer->setAutoFillBackground(true); mDocumentViewContainer->setBackgroundRole(QPalette::Base); layout->addWidget(mDocumentViewContainer); layout->addWidget(mStatusBarContainer); } void setupDocumentViewController() { mDocumentViewController = new DocumentViewController(mActionCollection, that); ZoomWidget* zoomWidget = new ZoomWidget(that); mDocumentViewController->setZoomWidget(zoomWidget); mSynchronizer = new DocumentViewSynchronizer(that); } DocumentView* createDocumentView() { DocumentView* view = mDocumentViewContainer->createView(); // Connect context menu QObject::connect(view, SIGNAL(contextMenuRequested()), that, SLOT(showContextMenu()) ); QObject::connect(view, SIGNAL(completed()), that, SIGNAL(completed()) ); QObject::connect(view, SIGNAL(previousImageRequested()), that, SIGNAL(previousImageRequested()) ); QObject::connect(view, SIGNAL(nextImageRequested()), that, SIGNAL(nextImageRequested()) ); QObject::connect(view, SIGNAL(captionUpdateRequested(QString)), that, SIGNAL(captionUpdateRequested(QString)) ); QObject::connect(view, SIGNAL(toggleFullScreenRequested()), that, SIGNAL(toggleFullScreenRequested()) ); QObject::connect(view, SIGNAL(focused(DocumentView*)), that, SLOT(slotViewFocused(DocumentView*)) ); QObject::connect(view, SIGNAL(hudTrashClicked(DocumentView*)), that, SLOT(trashView(DocumentView*)) ); QObject::connect(view, SIGNAL(hudDeselectClicked(DocumentView*)), that, SLOT(deselectView(DocumentView*)) ); QObject::connect(view, SIGNAL(videoFinished()), mSlideShow, SLOT(resumeAndGoToNextUrl())); mDocumentViews << view; return view; } void removeDocumentView(DocumentView* view) { mDocumentViewContainer->removeView(view); mDocumentViews.removeOne(view); } void setupStatusBar() { mStatusBarContainer = new QWidget; mToggleThumbnailBarButton = new StatusBarToolButton; mSynchronizeCheckBox = new QCheckBox(i18n("Synchronize")); mSynchronizeCheckBox->hide(); QHBoxLayout* layout = new QHBoxLayout(mStatusBarContainer); layout->setMargin(0); layout->setSpacing(0); layout->addWidget(mToggleThumbnailBarButton); layout->addStretch(); layout->addWidget(mSynchronizeCheckBox); layout->addStretch(); layout->addWidget(mDocumentViewController->zoomWidget()); } void setupSplitter() { Qt::Orientation orientation = GwenviewConfig::thumbnailBarOrientation(); mThumbnailSplitter = new Splitter(orientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal, that); mThumbnailBar->setOrientation(orientation); mThumbnailBar->setRowCount(GwenviewConfig::thumbnailBarRowCount()); mThumbnailSplitter->addWidget(mAdapterContainer); mThumbnailSplitter->addWidget(mThumbnailBar); mThumbnailSplitter->setSizes(GwenviewConfig::thumbnailSplitterSizes()); QVBoxLayout* layout = new QVBoxLayout(that); layout->setMargin(0); layout->addWidget(mThumbnailSplitter); } void applyPalette() { QPalette palette = mFullScreenMode ? mFullScreenPalette : mNormalPalette; mDocumentViewContainer->setPalette(palette); } void saveSplitterConfig() { if (mThumbnailBar->isVisible()) { GwenviewConfig::setThumbnailSplitterSizes(mThumbnailSplitter->sizes()); } } DocumentView* currentView() const { return mDocumentViewController->view(); } void setCurrentView(DocumentView* view) { DocumentView* oldView = currentView(); if (view == oldView) { return; } if (oldView) { oldView->setCurrent(false); } view->setCurrent(true); mDocumentViewController->setView(view); mSynchronizer->setCurrentView(view); QModelIndex index = indexForView(view); if (!index.isValid()) { kWarning() << "No index found for current view"; return; } mThumbnailBar->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Current); } QModelIndex indexForView(DocumentView* view) const { KUrl url = view->url(); if (!url.isValid()) { kWarning() << "View does not display any document!"; return QModelIndex(); } // FIXME: Ugly coupling! SortedDirModel* model = static_cast(mThumbnailBar->model()); return model->indexForUrl(url); } }; DocumentPanel::DocumentPanel(QWidget* parent, SlideShow* slideShow, KActionCollection* actionCollection) : QWidget(parent) , d(new DocumentPanelPrivate) { d->that = this; d->mSlideShow = slideShow; d->mActionCollection = actionCollection; d->mFullScreenMode = false; d->mCompareMode = false; d->mThumbnailBarVisibleBeforeFullScreen = false; d->mFullScreenPalette = QPalette(palette()); d->mFullScreenPalette.setColor(QPalette::Base, Qt::black); d->mFullScreenPalette.setColor(QPalette::Text, Qt::white); QShortcut* toggleFullScreenShortcut = new QShortcut(this); toggleFullScreenShortcut->setKey(Qt::Key_Return); connect(toggleFullScreenShortcut, SIGNAL(activated()), SIGNAL(toggleFullScreenRequested()) ); d->setupDocumentViewController(); d->setupStatusBar(); d->setupAdapterContainer(); d->setupThumbnailBar(); d->setupSplitter(); KActionCategory* view = new KActionCategory(i18nc("@title actions category - means actions changing smth in interface","View"), actionCollection); d->mToggleThumbnailBarAction = view->add(QString("toggle_thumbnailbar")); d->mToggleThumbnailBarAction->setText(i18n("Thumbnail Bar")); d->mToggleThumbnailBarAction->setIcon(KIcon("folder-image")); d->mToggleThumbnailBarAction->setShortcut(Qt::CTRL | Qt::Key_B); d->mToggleThumbnailBarAction->setChecked(GwenviewConfig::thumbnailBarIsVisible()); connect(d->mToggleThumbnailBarAction, SIGNAL(triggered(bool)), this, SLOT(setThumbnailBarVisibility(bool)) ); d->mToggleThumbnailBarButton->setDefaultAction(d->mToggleThumbnailBarAction); d->mSynchronizeAction = view->add("synchronize_views"); d->mSynchronizeAction->setText(i18n("Synchronize")); d->mSynchronizeAction->setShortcut(Qt::CTRL | Qt::Key_Y); connect(d->mSynchronizeAction, SIGNAL(toggled(bool)), d->mSynchronizer, SLOT(setActive(bool))); // Ensure mSynchronizeAction and mSynchronizeCheckBox are in sync connect(d->mSynchronizeAction, SIGNAL(toggled(bool)), d->mSynchronizeCheckBox, SLOT(setChecked(bool))); connect(d->mSynchronizeCheckBox, SIGNAL(toggled(bool)), d->mSynchronizeAction, SLOT(setChecked(bool))); } DocumentPanel::~DocumentPanel() { delete d; } void DocumentPanel::loadConfig() { // FIXME: Not symetric with saveConfig(). Check if it matters. Q_FOREACH(DocumentView* view, d->mDocumentViews) { view->loadAdapterConfig(); } Qt::Orientation orientation = GwenviewConfig::thumbnailBarOrientation(); d->mThumbnailSplitter->setOrientation(orientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal); d->mThumbnailBar->setOrientation(orientation); d->setupThumbnailBarStyleSheet(); int oldRowCount = d->mThumbnailBar->rowCount(); int newRowCount = GwenviewConfig::thumbnailBarRowCount(); if (oldRowCount != newRowCount) { d->mThumbnailBar->setUpdatesEnabled(false); int gridSize = d->mThumbnailBar->gridSize().width(); d->mThumbnailBar->setRowCount(newRowCount); // Adjust splitter to ensure thumbnail size remains the same int delta = (newRowCount - oldRowCount) * gridSize; QList sizes = d->mThumbnailSplitter->sizes(); Q_ASSERT(sizes.count() == 2); sizes[0] -= delta; sizes[1] += delta; d->mThumbnailSplitter->setSizes(sizes); d->mThumbnailBar->setUpdatesEnabled(true); } } void DocumentPanel::saveConfig() { d->saveSplitterConfig(); GwenviewConfig::setThumbnailBarIsVisible(d->mToggleThumbnailBarAction->isChecked()); } void DocumentPanel::setThumbnailBarVisibility(bool visible) { d->saveSplitterConfig(); d->mThumbnailBar->setVisible(visible); } int DocumentPanel::statusBarHeight() const { return d->mStatusBarContainer->height(); } void DocumentPanel::setFullScreenMode(bool fullScreenMode) { d->mFullScreenMode = fullScreenMode; d->mStatusBarContainer->setVisible(!fullScreenMode); d->applyPalette(); if (fullScreenMode) { d->mThumbnailBarVisibleBeforeFullScreen = d->mToggleThumbnailBarAction->isChecked(); if (d->mThumbnailBarVisibleBeforeFullScreen) { d->mToggleThumbnailBarAction->trigger(); } } else if (d->mThumbnailBarVisibleBeforeFullScreen) { d->mToggleThumbnailBarAction->trigger(); } d->mToggleThumbnailBarAction->setEnabled(!fullScreenMode); } bool DocumentPanel::isFullScreenMode() const { return d->mFullScreenMode; } ThumbnailBarView* DocumentPanel::thumbnailBar() const { return d->mThumbnailBar; } inline void addActionToMenu(KMenu* menu, KActionCollection* actionCollection, const char* name) { QAction* action = actionCollection->action(name); if (action) { menu->addAction(action); } } void DocumentPanel::showContextMenu() { KMenu menu(this); addActionToMenu(&menu, d->mActionCollection, "fullscreen"); menu.addSeparator(); addActionToMenu(&menu, d->mActionCollection, "go_previous"); addActionToMenu(&menu, d->mActionCollection, "go_next"); if (d->currentView()->canZoom()) { menu.addSeparator(); addActionToMenu(&menu, d->mActionCollection, "view_actual_size"); addActionToMenu(&menu, d->mActionCollection, "view_zoom_to_fit"); addActionToMenu(&menu, d->mActionCollection, "view_zoom_in"); addActionToMenu(&menu, d->mActionCollection, "view_zoom_out"); } if (d->mCompareMode) { menu.addSeparator(); addActionToMenu(&menu, d->mActionCollection, "synchronize_views"); } menu.addSeparator(); addActionToMenu(&menu, d->mActionCollection, "file_copy_to"); addActionToMenu(&menu, d->mActionCollection, "file_move_to"); addActionToMenu(&menu, d->mActionCollection, "file_link_to"); menu.addSeparator(); addActionToMenu(&menu, d->mActionCollection, "file_open_with"); menu.exec(QCursor::pos()); } QSize DocumentPanel::sizeHint() const { return QSize(400, 300); } KUrl DocumentPanel::url() const { if (!d->currentView()) { LOG("!d->documentView()"); return KUrl(); } return d->currentView()->url(); } Document::Ptr DocumentPanel::currentDocument() const { if (!d->currentView()) { LOG("!d->documentView()"); return Document::Ptr(); } return d->currentView()->document(); } bool DocumentPanel::isEmpty() const { if (!d->currentView()) { return true; } return d->currentView()->isEmpty(); } -ImageView* DocumentPanel::imageView() const { +RasterImageView* DocumentPanel::imageView() const { if (!d->currentView()) { return 0; } return d->currentView()->imageView(); } DocumentView* DocumentPanel::documentView() const { return d->currentView(); } void DocumentPanel::setNormalPalette(const QPalette& palette) { d->mNormalPalette = palette; d->applyPalette(); d->setupThumbnailBarStyleSheet(); } void DocumentPanel::openUrl(const KUrl& url) { openUrls(KUrl::List() << url, url); } void DocumentPanel::openUrls(const KUrl::List& _urls, const KUrl& currentUrl) { QSet urls = _urls.toSet(); d->mCompareMode = urls.count() > 1; // Destroy views which show urls we don't care about, remove from "urls" the // urls which already have a view. Q_FOREACH(DocumentView* view, d->mDocumentViews) { KUrl url = view->url(); if (urls.contains(url)) { // view displays an url we must display, keep it urls.remove(url); } else { // view url is not interesting, drop it d->removeDocumentView(view); } } // Create view for remaining urls typedef QPair ViewUrlPair; QList urlForViewList; Q_FOREACH(const KUrl& url, urls) { if (d->mDocumentViews.count() >= MaxViewCount) { kWarning() << "Too many documents to show"; break; } DocumentView* view = d->createDocumentView(); urlForViewList << qMakePair(view, url); } d->mDocumentViewContainer->updateLayout(); // Load urls for new views. Do it only now because the view must have the // correct size before it starts loading its url. Do not do it later because // view->url() needs to be set for the next loop. Q_FOREACH(const ViewUrlPair& pair, urlForViewList) { pair.first->openUrl(pair.second); } // Init views Q_FOREACH(DocumentView* view, d->mDocumentViews) { view->setCompareMode(d->mCompareMode); if (view->url() == currentUrl) { d->setCurrentView(view); } else { view->setCurrent(false); } } d->mSynchronizeCheckBox->setVisible(d->mCompareMode); if (d->mCompareMode) { d->mSynchronizer->setDocumentViews(d->mDocumentViews); d->mSynchronizer->setActive(d->mSynchronizeCheckBox->isChecked()); } else { d->mSynchronizer->setDocumentViews(QList()); d->mSynchronizer->setActive(false); } } void DocumentPanel::reload() { Document::Ptr doc = d->currentView()->document(); if (!doc) { kWarning() << "!doc"; return; } if (doc->isModified()) { KGuiItem cont = KStandardGuiItem::cont(); cont.setText(i18nc("@action:button", "Discard Changes and Reload")); int answer = KMessageBox::warningContinueCancel(this, i18nc("@info", "This image has been modified. Reloading it will discard all your changes."), QString() /* caption */, cont); if (answer != KMessageBox::Continue) { return; } } doc->reload(); // Call openUrl again because DocumentView may need to switch to a new // adapter (for example because document was broken and it is not anymore) d->currentView()->openUrl(doc->url()); } void DocumentPanel::reset() { // FIXME: Should probably delete views instead of resetting and hiding them Q_FOREACH(DocumentView* view, d->mDocumentViews) { view->reset(); view->hide(); } } void DocumentPanel::slotViewFocused(DocumentView* view) { d->setCurrentView(view); } void DocumentPanel::trashView(DocumentView* view) { KUrl url = view->url(); deselectView(view); FileOperations::trash(KUrl::List() << url, this); } void DocumentPanel::deselectView(DocumentView* view) { DocumentView* newCurrentView = 0; if (view == d->currentView()) { // We need to find a new view to set as current int idx = d->mDocumentViews.indexOf(view); // Look for the next visible view after the current one for (int newIdx = idx + 1; newIdx < d->mDocumentViews.count(); ++newIdx) { newCurrentView = d->mDocumentViews.at(newIdx); break; } if (!newCurrentView) { // No visible view found after the current one, look before for (int newIdx = idx - 1; newIdx >= 0; --newIdx) { newCurrentView = d->mDocumentViews.at(newIdx); break; } } if (!newCurrentView) { kWarning() << "No view found to set as current, this should not happen!"; } } QModelIndex index = d->indexForView(view); QItemSelectionModel* selectionModel = d->mThumbnailBar->selectionModel(); selectionModel->select(index, QItemSelectionModel::Deselect); if (newCurrentView) { d->setCurrentView(newCurrentView); } } } // namespace diff --git a/app/documentpanel.h b/app/documentpanel.h index 94a82aa6..efc541a2 100644 --- a/app/documentpanel.h +++ b/app/documentpanel.h @@ -1,137 +1,137 @@ /* 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 DOCUMENTPANEL_H #define DOCUMENTPANEL_H // Local #include // KDE #include // Qt #include class QPalette; class KActionCollection; namespace Gwenview { class DocumentView; -class ImageView; +class RasterImageView; class SlideShow; class ThumbnailBarView; struct DocumentPanelPrivate; /** * Holds the active document view and associated widgetry. */ class DocumentPanel : public QWidget { Q_OBJECT public: static const int MaxViewCount; DocumentPanel(QWidget* parent, SlideShow*, KActionCollection*); ~DocumentPanel(); ThumbnailBarView* thumbnailBar() const; void loadConfig(); void saveConfig(); /** * Reset the view */ void reset(); void setFullScreenMode(bool fullScreen); bool isFullScreenMode() const; void setNormalPalette(const QPalette&); int statusBarHeight() const; virtual QSize sizeHint() const; /** * Returns the url of the current document, or an invalid url if unknown */ KUrl url() const; void openUrl(const KUrl& url); /** * Opens up to MaxViewCount urls, and set currentUrl as the current one */ void openUrls(const KUrl::List& urls, const KUrl& currentUrl); void reload(); Document::Ptr currentDocument() const; bool isEmpty() const; /** * Returns the image view, if the current adapter has one. */ - ImageView* imageView() const; + RasterImageView* imageView() const; /** * Returns the document view */ DocumentView* documentView() const; Q_SIGNALS: /** * Emitted when the part has finished loading */ void completed(); void previousImageRequested(); void nextImageRequested(); void toggleFullScreenRequested(); void captionUpdateRequested(const QString&); private Q_SLOTS: void setThumbnailBarVisibility(bool visible); void showContextMenu(); void slotViewFocused(DocumentView*); void trashView(DocumentView*); void deselectView(DocumentView*); private: friend struct DocumentPanelPrivate; DocumentPanelPrivate* const d; }; } // namespace #endif /* DOCUMENTPANEL_H */ diff --git a/app/imageopscontextmanageritem.cpp b/app/imageopscontextmanageritem.cpp index 819533cf..8bcd262e 100644 --- a/app/imageopscontextmanageritem.cpp +++ b/app/imageopscontextmanageritem.cpp @@ -1,295 +1,298 @@ // vim: set tabstop=4 shiftwidth=4 noexpandtab: /* 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. */ // Self #include "imageopscontextmanageritem.moc" // Qt // KDE #include #include #include #include #include #include #include // Local #include "contextmanager.h" #include "documentpanel.h" #include "gvcore.h" #include "mainwindow.h" #include "sidebar.h" #include #include +#include #include #include #include -#include #include #include #include namespace Gwenview { #undef ENABLE_LOG #undef LOG //#define ENABLE_LOG #ifdef ENABLE_LOG #define LOG(x) kDebug() << x #else #define LOG(x) ; #endif struct ImageOpsContextManagerItem::Private { ImageOpsContextManagerItem* that; MainWindow* mMainWindow; SideBarGroup* mGroup; KAction* mRotateLeftAction; KAction* mRotateRightAction; KAction* mMirrorAction; KAction* mFlipAction; KAction* mResizeAction; KAction* mCropAction; KAction* mRedEyeReductionAction; QList mActionList; void setupActions() { KActionCollection* actionCollection = mMainWindow->actionCollection(); KActionCategory* edit=new KActionCategory(i18nc("@title actions category - means actions changing image","Edit"), actionCollection); mRotateLeftAction = edit->addAction("rotate_left",that, SLOT(rotateLeft())); mRotateLeftAction->setText(i18n("Rotate Left")); mRotateLeftAction->setIcon(KIcon("object-rotate-left")); mRotateLeftAction->setShortcut(Qt::CTRL + Qt::Key_L); mRotateRightAction = edit->addAction("rotate_right",that, SLOT(rotateRight())); mRotateRightAction->setText(i18n("Rotate Right")); mRotateRightAction->setIcon(KIcon("object-rotate-right")); mRotateRightAction->setShortcut(Qt::CTRL + Qt::Key_R); mMirrorAction = edit->addAction("mirror",that, SLOT(mirror())); mMirrorAction->setText(i18n("Mirror")); mMirrorAction->setIcon(KIcon("object-flip-horizontal")); mFlipAction = edit->addAction("flip",that, SLOT(flip())); mFlipAction->setText(i18n("Flip")); mFlipAction->setIcon(KIcon("object-flip-vertical")); mResizeAction = edit->addAction("resize",that, SLOT(resizeImage()) ); mResizeAction->setText(i18n("Resize")); mResizeAction->setIcon(KIcon("transform-scale")); mResizeAction->setShortcut(Qt::SHIFT + Qt::Key_R); mCropAction = edit->addAction("crop",that, SLOT(crop())); mCropAction->setText(i18n("Crop")); mCropAction->setIcon(KIcon("transform-crop-and-resize")); mCropAction->setShortcut(Qt::SHIFT + Qt::Key_C); mRedEyeReductionAction = edit->addAction("red_eye_reduction",that, SLOT(startRedEyeReduction()) ); mRedEyeReductionAction->setText(i18n("Red Eye Reduction")); //mRedEyeReductionAction->setIcon(KIcon("transform-crop-and-resize")); mActionList << mRotateLeftAction << mRotateRightAction << mMirrorAction << mFlipAction << mResizeAction << mCropAction << mRedEyeReductionAction ; } bool ensureEditable() { KUrl url = that->contextManager()->currentUrl(); return GvCore::ensureDocumentIsEditable(url); } }; ImageOpsContextManagerItem::ImageOpsContextManagerItem(ContextManager* manager, MainWindow* mainWindow) : AbstractContextManagerItem(manager) , d(new Private) { d->that = this; d->mMainWindow = mainWindow; d->mGroup = new SideBarGroup(i18n("Image Operations")); setWidget(d->mGroup); EventWatcher::install(d->mGroup, QEvent::Show, this, SLOT(updateSideBarContent())); d->setupActions(); updateActions(); connect(contextManager(), SIGNAL(selectionChanged()), SLOT(updateActions()) ); connect(mainWindow, SIGNAL(viewModeChanged()), SLOT(updateActions()) ); connect(mainWindow->documentPanel(), SIGNAL(completed()), SLOT(updateActions()) ); } ImageOpsContextManagerItem::~ImageOpsContextManagerItem() { delete d; } void ImageOpsContextManagerItem::updateSideBarContent() { if (!d->mGroup->isVisible()) { return; } d->mGroup->clear(); Q_FOREACH(KAction* action, d->mActionList) { if (action->isEnabled()) { d->mGroup->addAction(action); } } } void ImageOpsContextManagerItem::updateActions() { bool canModify = d->mMainWindow->currentDocumentIsRasterImage(); bool documentPanelIsVisible = d->mMainWindow->documentPanel()->isVisible(); if (!documentPanelIsVisible) { // Since we only support image operations on one image for now, // disable actions if several images are selected and the document // view is not visible. if (contextManager()->selectedFileItemList().count() != 1) { canModify = false; } } d->mRotateLeftAction->setEnabled(canModify); d->mRotateRightAction->setEnabled(canModify); d->mMirrorAction->setEnabled(canModify); d->mFlipAction->setEnabled(canModify); d->mResizeAction->setEnabled(canModify); d->mCropAction->setEnabled(canModify && documentPanelIsVisible); d->mRedEyeReductionAction->setEnabled(canModify && documentPanelIsVisible); updateSideBarContent(); } void ImageOpsContextManagerItem::rotateLeft() { TransformImageOperation* op = new TransformImageOperation(ROT_270); applyImageOperation(op); } void ImageOpsContextManagerItem::rotateRight() { TransformImageOperation* op = new TransformImageOperation(ROT_90); applyImageOperation(op); } void ImageOpsContextManagerItem::mirror() { TransformImageOperation* op = new TransformImageOperation(HFLIP); applyImageOperation(op); } void ImageOpsContextManagerItem::flip() { TransformImageOperation* op = new TransformImageOperation(VFLIP); applyImageOperation(op); } void ImageOpsContextManagerItem::resizeImage() { if (!d->ensureEditable()) { return; } Document::Ptr doc = DocumentFactory::instance()->load(contextManager()->currentUrl()); doc->startLoadingFullImage(); ResizeImageDialog dialog(d->mMainWindow); dialog.setOriginalSize(doc->size()); if (!dialog.exec()) { return; } ResizeImageOperation* op = new ResizeImageOperation(dialog.size()); applyImageOperation(op); } void ImageOpsContextManagerItem::crop() { if (!d->ensureEditable()) { return; } +// FIXME: QGV +#if 0 ImageView* imageView = d->mMainWindow->documentPanel()->imageView(); if (!imageView) { kError() << "No ImageView available!"; return; } CropTool* tool = new CropTool(imageView); connect(tool, SIGNAL(imageOperationRequested(AbstractImageOperation*)), SLOT(applyImageOperation(AbstractImageOperation*)) ); connect(tool, SIGNAL(done()), SLOT(restoreDefaultImageViewTool()) ); d->mMainWindow->setDistractionFreeMode(true); imageView->setCurrentTool(tool); +#endif } void ImageOpsContextManagerItem::startRedEyeReduction() { if (!d->ensureEditable()) { return; } - ImageView* imageView = d->mMainWindow->documentPanel()->imageView(); - if (!imageView) { - kError() << "No ImageView available!"; + RasterImageView* view = d->mMainWindow->documentPanel()->imageView(); + if (!view) { + kError() << "No RasterImageView available!"; return; } - RedEyeReductionTool* tool = new RedEyeReductionTool(imageView); + RedEyeReductionTool* tool = new RedEyeReductionTool(view); connect(tool, SIGNAL(imageOperationRequested(AbstractImageOperation*)), SLOT(applyImageOperation(AbstractImageOperation*)) ); connect(tool, SIGNAL(done()), SLOT(restoreDefaultImageViewTool()) ); d->mMainWindow->setDistractionFreeMode(true); - imageView->setCurrentTool(tool); + view->setCurrentTool(tool); } void ImageOpsContextManagerItem::applyImageOperation(AbstractImageOperation* op) { // For now, we only support operations on one image KUrl url = contextManager()->currentUrl(); Document::Ptr doc = DocumentFactory::instance()->load(url); op->applyToDocument(doc); } void ImageOpsContextManagerItem::restoreDefaultImageViewTool() { - ImageView* imageView = d->mMainWindow->documentPanel()->imageView(); + RasterImageView* imageView = d->mMainWindow->documentPanel()->imageView(); if (!imageView) { - kError() << "No ImageView available!"; + kError() << "No RasterImageView available!"; return; } - AbstractImageViewTool* tool = imageView->currentTool(); + AbstractRasterImageViewTool* tool = imageView->currentTool(); imageView->setCurrentTool(0); tool->deleteLater(); d->mMainWindow->setDistractionFreeMode(false); } } // namespace diff --git a/lib/documentview/abstractimageview.cpp b/lib/documentview/abstractimageview.cpp index e956090b..1b291337 100644 --- a/lib/documentview/abstractimageview.cpp +++ b/lib/documentview/abstractimageview.cpp @@ -1,310 +1,325 @@ // vim: set tabstop=4 shiftwidth=4 noexpandtab: /* Gwenview: an image viewer Copyright 2011 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 "abstractimageview.moc" // Local // KDE #include #include // Qt #include #include namespace Gwenview { static const int UNIT_STEP = 16; struct AbstractImageViewPrivate { enum Verbosity { Silent, Notify }; AbstractImageView* q; Document::Ptr mDocument; qreal mZoom; bool mZoomToFit; QPointF mImageOffset; QPointF mScrollPos; QPointF mLastDragPos; void adjustImageOffset(Verbosity verbosity = Notify) { QSizeF zoomedDocSize = q->documentSize() * mZoom; QSizeF viewSize = q->boundingRect().size(); QPointF offset( qMax((viewSize.width() - zoomedDocSize.width()) / 2, 0.), qMax((viewSize.height() - zoomedDocSize.height()) / 2, 0.) ); if (offset != mImageOffset) { mImageOffset = offset; if (verbosity == Notify) { q->onImageOffsetChanged(); } } } void adjustScrollPos(Verbosity verbosity = Notify) { setScrollPos(mScrollPos, verbosity); } void setScrollPos(const QPointF& _newPos, Verbosity verbosity = Notify) { QSizeF zoomedDocSize = q->documentSize() * mZoom; QSizeF viewSize = q->boundingRect().size(); QPointF newPos( qBound(0., _newPos.x(), zoomedDocSize.width() - viewSize.width()), qBound(0., _newPos.y(), zoomedDocSize.height() - viewSize.height()) ); if (newPos != mScrollPos) { QPointF oldPos = mScrollPos; mScrollPos = newPos; if (verbosity == Notify) { q->onScrollPosChanged(oldPos); } } } }; AbstractImageView::AbstractImageView(QGraphicsItem* parent) : QGraphicsWidget(parent) , d(new AbstractImageViewPrivate) { d->q = this; d->mZoom = 1; d->mZoomToFit = true; d->mImageOffset = QPointF(0, 0); d->mScrollPos = QPointF(0, 0); setCursor(Qt::OpenHandCursor); setFocusPolicy(Qt::WheelFocus); setFlag(ItemIsSelectable); } AbstractImageView::~AbstractImageView() { delete d; } Document::Ptr AbstractImageView::document() const { return d->mDocument; } void AbstractImageView::setDocument(Document::Ptr doc) { d->mDocument = doc; loadFromDocument(); if (d->mZoomToFit) { setZoom(computeZoomToFit()); } } QSizeF AbstractImageView::documentSize() const { return d->mDocument ? d->mDocument->size() : QSizeF(); } qreal AbstractImageView::zoom() const { return d->mZoom; } void AbstractImageView::setZoom(qreal zoom, const QPointF& _center) { qreal oldZoom = d->mZoom; if (qFuzzyCompare(zoom, oldZoom)) { return; } d->mZoom = zoom; QPointF center; if (_center == QPointF(-1, -1)) { center = boundingRect().center(); } else { center = _center; } /* We want to keep the point at viewport coordinates "center" at the same position after zooming. The coordinates of this point in image coordinates can be expressed like this: oldScroll + center imagePointAtOldZoom = ------------------ oldZoom scroll + center imagePointAtZoom = --------------- zoom So we want: imagePointAtOldZoom = imagePointAtZoom oldScroll + center scroll + center <=> ------------------ = --------------- oldZoom zoom zoom <=> scroll = ------- (oldScroll + center) - center oldZoom */ /* Compute oldScroll It's useless to take the new offset in consideration because if a direction of the new offset is not 0, we won't be able to center on a specific point in that direction. */ QPointF oldScroll = scrollPos() - imageOffset(); QPointF scroll = (zoom / oldZoom) * (oldScroll + center) - center; d->adjustImageOffset(AbstractImageViewPrivate::Silent); d->setScrollPos(scroll, AbstractImageViewPrivate::Silent); onZoomChanged(); zoomChanged(d->mZoom); } bool AbstractImageView::zoomToFit() const { return d->mZoomToFit; } void AbstractImageView::setZoomToFit(bool on) { d->mZoomToFit = on; if (on) { setZoom(computeZoomToFit()); } // We do not set zoom to 1 if zoomToFit is off, this is up to the code // calling us. It may went to zoom to some other level and/or to zoom on // a particular position zoomToFitChanged(d->mZoomToFit); } void AbstractImageView::resizeEvent(QGraphicsSceneResizeEvent* event) { QGraphicsWidget::resizeEvent(event); if (d->mZoomToFit) { setZoom(computeZoomToFit()); } else { d->adjustImageOffset(); d->adjustScrollPos(); } } qreal AbstractImageView::computeZoomToFit() const { QSizeF docSize = documentSize(); if (docSize.isEmpty()) { return 1; } QSizeF viewSize = boundingRect().size(); qreal fitWidth = viewSize.width() / docSize.width(); qreal fitHeight = viewSize.height() / docSize.height(); return qMin(fitWidth, fitHeight); } void AbstractImageView::mousePressEvent(QGraphicsSceneMouseEvent* event) { QGraphicsItem::mousePressEvent(event); setCursor(Qt::ClosedHandCursor); d->mLastDragPos = event->pos(); } void AbstractImageView::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { QGraphicsItem::mouseMoveEvent(event); QPointF mousePos = event->pos(); QPointF newScrollPos = d->mScrollPos + d->mLastDragPos - mousePos; // Wrap mouse pos qreal maxWidth = boundingRect().width(); qreal maxHeight = boundingRect().height(); // We need a margin because if the window is maximized, the mouse may not // be able to go past the bounding rect. // The mouse get placed 1 pixel before/after the margin to avoid getting // considered as needing to wrap the other way in next mouseMoveEvent // (because we don't check the move vector) const int margin = 5; if (mousePos.x() <= margin) { mousePos.setX(maxWidth - margin - 1); } else if (mousePos.x() >= maxWidth - margin) { mousePos.setX(margin + 1); } if (mousePos.y() <= margin) { mousePos.setY(maxHeight - margin - 1); } else if (mousePos.y() >= maxHeight - margin) { mousePos.setY(margin + 1); } // Set mouse pos (Hackish translation to screen coords!) QPointF screenDelta = event->screenPos() - event->pos(); QCursor::setPos((mousePos + screenDelta).toPoint()); d->mLastDragPos = mousePos; d->setScrollPos(newScrollPos); } void AbstractImageView::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { QGraphicsItem::mouseReleaseEvent(event); setCursor(Qt::OpenHandCursor); } void AbstractImageView::keyPressEvent(QKeyEvent* event) { QPointF delta(0, 0); qreal pageStep = boundingRect().height(); qreal unitStep; if (event->modifiers() & Qt::ShiftModifier) { unitStep = pageStep / 2; } else { unitStep = UNIT_STEP; } switch (event->key()) { case Qt::Key_Left: delta.setX(-unitStep); break; case Qt::Key_Right: delta.setX(unitStep); break; case Qt::Key_Up: delta.setY(-unitStep); break; case Qt::Key_Down: delta.setY(unitStep); break; case Qt::Key_PageUp: delta.setY(-pageStep); break; case Qt::Key_PageDown: delta.setY(pageStep); break; case Qt::Key_Home: d->setScrollPos(QPointF(d->mScrollPos.x(), 0)); return; case Qt::Key_End: d->setScrollPos(QPointF(d->mScrollPos.x(), documentSize().height() * zoom())); return; default: return; } d->setScrollPos(d->mScrollPos + delta); } QPointF AbstractImageView::imageOffset() const { return d->mImageOffset; } QPointF AbstractImageView::scrollPos() const { return d->mScrollPos; } +QPointF AbstractImageView::mapToView(const QPointF& imagePos) const { + return imagePos * d->mZoom + d->mImageOffset - d->mScrollPos; +} + +QRectF AbstractImageView::mapToView(const QRectF& imageRect) const { + return QRectF( + mapToView(imageRect.topLeft()), + imageRect.size() * zoom() + ); +} + +QPointF AbstractImageView::mapToImage(const QPointF& viewPos) const { + return (viewPos - d->mImageOffset + d->mScrollPos) / d->mZoom; +} + } // namespace diff --git a/lib/documentview/abstractimageview.h b/lib/documentview/abstractimageview.h index b12f1ad6..21a973db 100644 --- a/lib/documentview/abstractimageview.h +++ b/lib/documentview/abstractimageview.h @@ -1,111 +1,115 @@ // vim: set tabstop=4 shiftwidth=4 noexpandtab: /* Gwenview: an image viewer Copyright 2011 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. */ #ifndef ABSTRACTIMAGEVIEW_H #define ABSTRACTIMAGEVIEW_H // Local #include // KDE // Qt #include namespace Gwenview { class AbstractImageViewPrivate; /** * */ class AbstractImageView : public QGraphicsWidget { Q_OBJECT public: AbstractImageView(QGraphicsItem* parent); ~AbstractImageView(); qreal zoom() const; virtual void setZoom(qreal zoom, const QPointF& center = QPointF(-1, -1)); bool zoomToFit() const; virtual void setZoomToFit(bool value); virtual void setDocument(Document::Ptr doc); Document::Ptr document() const; qreal computeZoomToFit() const; virtual QSizeF documentSize() const; QSizeF visibleImageSize() const; /** * If the image is smaller than the view, imageOffset is the distance from * the topleft corner of the view to the topleft corner of the image. * Neither x nor y can be negative. */ QPointF imageOffset() const; /** * The scroll position, in zoomed image coordinates. * x and y are always between 0 and (docsize * zoom - viewsize) */ QPointF scrollPos() const; + QPointF mapToView(const QPointF& viewPos) const; + QRectF mapToView(const QRectF& imageRect) const; + QPointF mapToImage(const QPointF& viewPos) const; + Q_SIGNALS: void zoomToFitChanged(bool); void zoomChanged(qreal); protected: void setChildItem(QGraphicsItem*); virtual void loadFromDocument() = 0; virtual void onZoomChanged() = 0; /** * Called when the offset changes. * Note: to avoid multiple adjustments, this is not called if zoom changes! */ virtual void onImageOffsetChanged() = 0; /** * Called when the scrollPos changes. * Note: to avoid multiple adjustments, this is not called if zoom changes! */ virtual void onScrollPosChanged(const QPointF& oldPos) = 0; void resizeEvent(QGraphicsSceneResizeEvent* event); void keyPressEvent(QKeyEvent* event); void mousePressEvent(QGraphicsSceneMouseEvent* event); void mouseMoveEvent(QGraphicsSceneMouseEvent* event); void mouseReleaseEvent(QGraphicsSceneMouseEvent* event); private: friend class AbstractImageViewPrivate; AbstractImageViewPrivate* const d; }; } // namespace #endif /* ABSTRACTIMAGEVIEW_H */ diff --git a/lib/documentview/documentview.cpp b/lib/documentview/documentview.cpp index ab50ada3..08740bee 100644 --- a/lib/documentview/documentview.cpp +++ b/lib/documentview/documentview.cpp @@ -1,743 +1,743 @@ // vim: set tabstop=4 shiftwidth=4 noexpandtab: /* 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 "documentview.moc" // Qt #include #include #include #include #include #include #include #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 { #undef ENABLE_LOG #undef LOG //#define ENABLE_LOG #ifdef ENABLE_LOG #define LOG(x) kDebug() << x #else #define LOG(x) ; #endif static const qreal REAL_DELTA = 0.001; static const qreal MAXIMUM_ZOOM_VALUE = qreal(DocumentView::MaximumZoom); static const int COMPARE_MARGIN = 4; struct DocumentViewPrivate { DocumentView* that; QGraphicsProxyWidget* mHud; KModifierKeyInfo* mModifierKeyInfo; QCursor mZoomCursor; QCursor mPreviousCursor; QWeakPointer mMoveAnimation; QGraphicsProxyWidget* mLoadingIndicator; QScopedPointer mAdapter; QList mZoomSnapValues; Document::Ptr mDocument; bool mCurrent; bool mCompareMode; void setCurrentAdapter(AbstractDocumentViewAdapter* adapter) { Q_ASSERT(adapter); mAdapter.reset(adapter); adapter->widget()->setParentItem(that); resizeAdapterWidget(); if (adapter->canZoom()) { QObject::connect(adapter, SIGNAL(zoomChanged(qreal)), that, SLOT(slotZoomChanged(qreal)) ); QObject::connect(adapter, SIGNAL(zoomToFitChanged(bool)), that, SIGNAL(zoomToFitChanged(bool)) ); } // FIXME QGV /* QAbstractScrollArea* area = qobject_cast(adapter->widget()); if (area) { QObject::connect(area->horizontalScrollBar(), SIGNAL(valueChanged(int)), that, SIGNAL(positionChanged())); QObject::connect(area->verticalScrollBar(), SIGNAL(valueChanged(int)), that, SIGNAL(positionChanged())); } */ adapter->widget()->installSceneEventFilter(that); if (mCurrent) { adapter->widget()->setFocus(); } that->adapterChanged(); that->positionChanged(); if (adapter->canZoom()) { that->zoomToFitChanged(adapter->zoomToFit()); } } void setupZoomCursor() { QString path = KStandardDirs::locate("appdata", "cursors/zoom.png"); QPixmap cursorPixmap = QPixmap(path); mZoomCursor = QCursor(cursorPixmap); } void setZoomCursor() { QCursor currentCursor = mAdapter.data()->cursor(); if (currentCursor.pixmap().cacheKey() == mZoomCursor.pixmap().cacheKey()) { return; } mPreviousCursor = currentCursor; mAdapter.data()->setCursor(mZoomCursor); } void restoreCursor() { mAdapter.data()->setCursor(mPreviousCursor); } void setupLoadingIndicator() { KPixmapSequence sequence("process-working", 22); KPixmapSequenceWidget* widget = new KPixmapSequenceWidget; widget->setSequence(sequence); widget->setInterval(100); mLoadingIndicator = new QGraphicsProxyWidget(that); mLoadingIndicator->setWidget(widget); GraphicsWidgetFloater* floater = new GraphicsWidgetFloater(that); floater->setChildWidget(mLoadingIndicator); } QToolButton* createHudButton(const QString& text, const char* iconName, bool showText) { QToolButton* button = new QToolButton; if (showText) { button->setToolButtonStyle(Qt::ToolButtonTextBesideIcon); button->setText(text); } else { button->setToolTip(text); } button->setIcon(SmallIcon(iconName)); return button; } void setupHud() { QToolButton* trashButton = createHudButton(i18n("Trash"), "user-trash", false); QToolButton* deselectButton = createHudButton(i18n("Deselect"), "list-remove", true); QWidget* content = new QWidget; QHBoxLayout* layout = new QHBoxLayout(content); layout->setMargin(0); layout->setSpacing(4); layout->addWidget(trashButton); layout->addWidget(deselectButton); HudWidget* hud = new HudWidget; hud->init(content, HudWidget::OptionNone); mHud = new QGraphicsProxyWidget(that); mHud->setWidget(hud); GraphicsWidgetFloater* floater = new GraphicsWidgetFloater(that); floater->setChildWidget(mHud); floater->setAlignment(Qt::AlignBottom | Qt::AlignHCenter); QObject::connect(trashButton, SIGNAL(clicked()), that, SLOT(emitHudTrashClicked())); QObject::connect(deselectButton, SIGNAL(clicked()), that, SLOT(emitHudDeselectClicked())); mHud->hide(); } void updateCaption() { QString caption; Document::Ptr doc = mAdapter->document(); if (!doc) { emit that->captionUpdateRequested(caption); return; } caption = doc->url().fileName(); QSize size = doc->size(); if (size.isValid()) { caption += QString(" - %1x%2") .arg(size.width()) .arg(size.height()); if (mAdapter->canZoom()) { int intZoom = qRound(mAdapter->zoom() * 100); caption += QString(" - %1%") .arg(intZoom); } } emit that->captionUpdateRequested(caption); } void uncheckZoomToFit() { if (mAdapter->zoomToFit()) { mAdapter->setZoomToFit(false); } } void setZoom(qreal zoom, const QPointF& center = QPointF(-1, -1)) { uncheckZoomToFit(); zoom = qBound(that->minimumZoom(), zoom, MAXIMUM_ZOOM_VALUE); mAdapter->setZoom(zoom, center); } void updateZoomSnapValues() { qreal min = that->minimumZoom(); mZoomSnapValues.clear(); if (min < 1.) { mZoomSnapValues << min; for (qreal invZoom = 16.; invZoom > 1.; invZoom /= 2.) { qreal zoom = 1. / invZoom; if (zoom > min) { mZoomSnapValues << zoom; } } } for (qreal zoom = 1; zoom <= MAXIMUM_ZOOM_VALUE ; zoom += 1.) { mZoomSnapValues << zoom; } that->minimumZoomChanged(min); } void showLoadingIndicator() { if (!mLoadingIndicator) { setupLoadingIndicator(); } mLoadingIndicator->show(); mLoadingIndicator->setZValue(1); } void hideLoadingIndicator() { if (!mLoadingIndicator) { return; } mLoadingIndicator->hide(); } void animate(QPropertyAnimation* anim) { QObject::connect(anim, SIGNAL(finished()), that, SLOT(slotAnimationFinished())); anim->setDuration(500); anim->start(QAbstractAnimation::DeleteWhenStopped); } void resizeAdapterWidget() { QRectF rect = QRectF(QPointF(0, 0), that->boundingRect().size()); if (mCompareMode) { rect.adjust(COMPARE_MARGIN, COMPARE_MARGIN, -COMPARE_MARGIN, -COMPARE_MARGIN); } mAdapter->widget()->setGeometry(rect); } void adapterMousePressEvent(QGraphicsSceneMouseEvent* event) { if (mAdapter->canZoom()) { if (event->modifiers() == Qt::ControlModifier) { // Ctrl + Left or right button => zoom in or out if (event->button() == Qt::LeftButton) { that->zoomIn(event->pos()); } else if (event->button() == Qt::RightButton) { that->zoomOut(event->pos()); } } else if (event->button() == Qt::MidButton) { // Middle click => toggle zoom to fit that->setZoomToFit(!mAdapter->zoomToFit()); } } QMetaObject::invokeMethod(that, "emitFocused", Qt::QueuedConnection); } }; DocumentView::DocumentView(QGraphicsScene* scene) : d(new DocumentViewPrivate) { setFlag(ItemIsFocusable); setFlag(ItemIsSelectable); setFlag(ItemClipsChildrenToShape); d->that = this; d->mLoadingIndicator = 0; d->mCurrent = false; d->mCompareMode = false; d->mModifierKeyInfo = new KModifierKeyInfo(this); connect(d->mModifierKeyInfo, SIGNAL(keyPressed(Qt::Key,bool)), SLOT(slotKeyPressed(Qt::Key,bool))); scene->addItem(this); d->setupZoomCursor(); d->setupHud(); d->setCurrentAdapter(new MessageViewAdapter); } DocumentView::~DocumentView() { delete d; } void DocumentView::createAdapterForDocument() { const MimeTypeUtils::Kind documentKind = d->mDocument->kind(); if (d->mAdapter && documentKind == d->mAdapter->kind() && documentKind != MimeTypeUtils::KIND_UNKNOWN) { // Do not reuse for KIND_UNKNOWN: we may need to change the message LOG("Reusing current adapter"); return; } AbstractDocumentViewAdapter* adapter = 0; switch (documentKind) { case MimeTypeUtils::KIND_RASTER_IMAGE: adapter = new ImageViewAdapter; break; case MimeTypeUtils::KIND_SVG_IMAGE: adapter = new SvgViewAdapter; break; case MimeTypeUtils::KIND_VIDEO: adapter = new VideoViewAdapter; connect(adapter, SIGNAL(videoFinished()), SIGNAL(videoFinished())); break; case MimeTypeUtils::KIND_UNKNOWN: adapter = new MessageViewAdapter; static_cast(adapter)->setErrorMessage(i18n("Gwenview does not know how to display this kind of document")); break; default: kWarning() << "should not be called for documentKind=" << documentKind; adapter = new MessageViewAdapter; break; } d->setCurrentAdapter(adapter); } void DocumentView::openUrl(const KUrl& url) { if (d->mDocument) { disconnect(d->mDocument.data(), 0, this, 0); } d->mDocument = DocumentFactory::instance()->load(url); connect(d->mDocument.data(), SIGNAL(busyChanged(KUrl,bool)), SLOT(slotBusyChanged(KUrl,bool))); if (d->mDocument->loadingState() < Document::KindDetermined) { MessageViewAdapter* messageViewAdapter = qobject_cast(d->mAdapter.data()); if (messageViewAdapter) { messageViewAdapter->setInfoMessage(QString()); } d->showLoadingIndicator(); connect(d->mDocument.data(), SIGNAL(kindDetermined(KUrl)), SLOT(finishOpenUrl())); } else { finishOpenUrl(); } } void DocumentView::finishOpenUrl() { disconnect(d->mDocument.data(), SIGNAL(kindDetermined(KUrl)), this, SLOT(finishOpenUrl())); if (d->mDocument->loadingState() < Document::KindDetermined) { kWarning() << "d->mDocument->loadingState() < Document::KindDetermined, this should not happen!"; return; } if (d->mDocument->loadingState() == Document::LoadingFailed) { slotLoadingFailed(); return; } createAdapterForDocument(); connect(d->mDocument.data(), SIGNAL(downSampledImageReady()), SLOT(slotLoaded()) ); connect(d->mDocument.data(), SIGNAL(loaded(KUrl)), SLOT(slotLoaded()) ); connect(d->mDocument.data(), SIGNAL(loadingFailed(KUrl)), SLOT(slotLoadingFailed()) ); d->mAdapter->setDocument(d->mDocument); d->updateCaption(); if (d->mDocument->loadingState() == Document::Loaded) { slotLoaded(); } } void DocumentView::reset() { d->hideLoadingIndicator(); if (d->mDocument) { disconnect(d->mDocument.data(), 0, this, 0); d->mDocument = 0; } d->setCurrentAdapter(new EmptyAdapter); } bool DocumentView::isEmpty() const { return qobject_cast(d->mAdapter.data()); } void DocumentView::loadAdapterConfig() { d->mAdapter->loadConfig(); } -ImageView* DocumentView::imageView() const { - return d->mAdapter->imageView(); +RasterImageView* DocumentView::imageView() const { + return d->mAdapter->rasterImageView(); } void DocumentView::slotLoaded() { d->hideLoadingIndicator(); d->updateCaption(); d->updateZoomSnapValues(); if (!d->mAdapter->zoomToFit()) { qreal min = minimumZoom(); if (d->mAdapter->zoom() < min) { d->mAdapter->setZoom(min); } } emit completed(); } void DocumentView::slotLoadingFailed() { d->hideLoadingIndicator(); MessageViewAdapter* adapter = new MessageViewAdapter; adapter->setDocument(d->mDocument); QString message = i18n("Loading %1 failed", d->mDocument->url().fileName()); adapter->setErrorMessage(message, d->mDocument->errorString()); d->setCurrentAdapter(adapter); emit completed(); } bool DocumentView::canZoom() const { return d->mAdapter->canZoom(); } void DocumentView::setZoomToFit(bool on) { if (on == d->mAdapter->zoomToFit()) { return; } d->mAdapter->setZoomToFit(on); if (!on) { d->mAdapter->setZoom(1.); } } bool DocumentView::zoomToFit() const { return d->mAdapter->zoomToFit(); } void DocumentView::zoomActualSize() { d->uncheckZoomToFit(); d->mAdapter->setZoom(1.); } void DocumentView::zoomIn(const QPointF& center) { qreal currentZoom = d->mAdapter->zoom(); Q_FOREACH(qreal zoom, d->mZoomSnapValues) { if (zoom > currentZoom + REAL_DELTA) { d->setZoom(zoom, center); return; } } } void DocumentView::zoomOut(const QPointF& center) { qreal currentZoom = d->mAdapter->zoom(); QListIterator it(d->mZoomSnapValues); it.toBack(); while (it.hasPrevious()) { qreal zoom = it.previous(); if (zoom < currentZoom - REAL_DELTA) { d->setZoom(zoom, center); return; } } } void DocumentView::slotZoomChanged(qreal zoom) { d->updateCaption(); zoomChanged(zoom); } void DocumentView::setZoom(qreal zoom) { d->setZoom(zoom); } qreal DocumentView::zoom() const { return d->mAdapter->zoom(); } void DocumentView::mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event) { if (event->modifiers() == Qt::NoModifier) { toggleFullScreenRequested(); } } void DocumentView::wheelEvent(QGraphicsSceneWheelEvent* event) { if (d->mAdapter->canZoom() && event->modifiers() & Qt::ControlModifier) { // Ctrl + wheel => zoom in or out if (event->delta() > 0) { zoomIn(event->pos()); } else { zoomOut(event->pos()); } return; } if (event->modifiers() == Qt::NoModifier && GwenviewConfig::mouseWheelBehavior() == MouseWheelBehavior::Browse ) { // Browse with mouse wheel if (event->delta() > 0) { previousImageRequested(); } else { nextImageRequested(); } } } void DocumentView::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { // Filter out context menu if Ctrl is down to avoid showing it when // zooming out with Ctrl + Right button if (event->modifiers() != Qt::ControlModifier) { contextMenuRequested(); } } void DocumentView::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/) { if (!d->mCompareMode) { return; } if (d->mCurrent) { painter->save(); painter->setBrush(Qt::NoBrush); painter->setPen(QPen(palette().highlight().color(), 2)); painter->setRenderHint(QPainter::Antialiasing); QRectF selectionRect = boundingRect().adjusted(2, 2, -2, -2); painter->drawRoundedRect(selectionRect, 3, 3); painter->restore(); } } void DocumentView::slotBusyChanged(const KUrl&, bool busy) { if (busy) { d->showLoadingIndicator(); } else { d->hideLoadingIndicator(); } } qreal DocumentView::minimumZoom() const { // There is no point zooming out less than zoomToFit, but make sure it does // not get too small either return qMax(0.001, qMin(double(d->mAdapter->computeZoomToFit()), 1.)); } void DocumentView::setCompareMode(bool compare) { d->mCompareMode = compare; d->resizeAdapterWidget(); if (compare) { d->mHud->show(); d->mHud->setZValue(1); } else { d->mHud->hide(); } } void DocumentView::setCurrent(bool value) { d->mCurrent = value; if (value) { d->mAdapter->widget()->setFocus(); } update(); } bool DocumentView::isCurrent() const { return d->mCurrent; } QPoint DocumentView::position() const { // FIXME: QGV /* QAbstractScrollArea* area = qobject_cast(d->mAdapter->widget()); if (!area) { return QPoint(); } return QPoint( area->horizontalScrollBar()->value(), area->verticalScrollBar()->value() ); */ return QPoint(); } void DocumentView::setPosition(const QPoint& pos) { QAbstractScrollArea* area = qobject_cast(d->mAdapter->widget()); if (!area) { return; } area->horizontalScrollBar()->setValue(pos.x()); area->verticalScrollBar()->setValue(pos.y()); } void DocumentView::slotKeyPressed(Qt::Key key, bool pressed) { if (key == Qt::Key_Control) { if (pressed) { d->setZoomCursor(); } else { d->restoreCursor(); } } } Document::Ptr DocumentView::document() const { return d->mDocument; } KUrl DocumentView::url() const { Document::Ptr doc = d->mDocument; return doc ? doc->url() : KUrl(); } void DocumentView::emitHudDeselectClicked() { hudDeselectClicked(this); } void DocumentView::emitHudTrashClicked() { hudTrashClicked(this); } void DocumentView::emitFocused() { focused(this); } void DocumentView::setGeometry(const QRectF& rect) { QGraphicsWidget::setGeometry(rect); d->resizeAdapterWidget(); } void DocumentView::moveTo(const QRect& rect) { if (d->mMoveAnimation) { d->mMoveAnimation.data()->setEndValue(rect); } else { setGeometry(rect); } } void DocumentView::moveToAnimated(const QRect& rect) { QPropertyAnimation* anim = new QPropertyAnimation(this, "geometry"); anim->setStartValue(geometry()); anim->setEndValue(rect); d->animate(anim); d->mMoveAnimation = anim; } void DocumentView::fadeIn() { setOpacity(0); show(); QPropertyAnimation* anim = new QPropertyAnimation(this, "opacity"); anim->setStartValue(0.); anim->setEndValue(1.); d->animate(anim); } void DocumentView::fadeOut() { QPropertyAnimation* anim = new QPropertyAnimation(this, "opacity"); anim->setStartValue(1.); anim->setEndValue(0.); d->animate(anim); } void DocumentView::slotAnimationFinished() { animationFinished(this); } bool DocumentView::sceneEventFilter(QGraphicsItem*, QEvent* event) { if (event->type() == QEvent::GraphicsSceneMousePress) { QGraphicsSceneMouseEvent* mouseEvent = static_cast(event); d->adapterMousePressEvent(mouseEvent); } return false; } } // namespace diff --git a/lib/documentview/documentview.h b/lib/documentview/documentview.h index e64edc06..65abc38a 100644 --- a/lib/documentview/documentview.h +++ b/lib/documentview/documentview.h @@ -1,199 +1,199 @@ // vim: set tabstop=4 shiftwidth=4 noexpandtab: /* 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. */ #ifndef DOCUMENTVIEW_H #define DOCUMENTVIEW_H #include // Qt #include // KDE #include // Local #include class KUrl; namespace Gwenview { -class ImageView; +class RasterImageView; class SlideShow; class ZoomWidget; struct DocumentViewPrivate; /** * This widget can display various documents, using an instance of * AbstractDocumentViewAdapter */ class GWENVIEWLIB_EXPORT DocumentView : public QGraphicsWidget { Q_OBJECT Q_PROPERTY(qreal zoom READ zoom WRITE setZoom NOTIFY zoomChanged) Q_PROPERTY(bool zoomToFit READ zoomToFit WRITE setZoomToFit NOTIFY zoomToFitChanged) Q_PROPERTY(QPoint position READ position WRITE setPosition NOTIFY positionChanged) public: enum { MaximumZoom = 16 }; /** * Create a new view attached to scene. We need the scene to be able to * install scene event filters. */ DocumentView(QGraphicsScene* scene); ~DocumentView(); Document::Ptr document() const; KUrl url() const; void openUrl(const KUrl&); /** * Tells the current adapter to load its config. Used when the user changed * the config while the view was visible. */ void loadAdapterConfig(); /** * Unload the current adapter, if any */ void reset(); /** * Returns true if an adapter is loaded (note: adapters are also used to * display error messages!) */ bool isEmpty() const; bool canZoom() const; qreal minimumZoom() const; qreal zoom() const; bool isCurrent() const; void setCurrent(bool); void setCompareMode(bool); bool zoomToFit() const; QPoint position() const; /** - * Returns the ImageView of the current adapter, if it has one + * Returns the RasterImageView of the current adapter, if it has one */ - ImageView* imageView() const; + RasterImageView* imageView() const; void moveTo(const QRect&); void moveToAnimated(const QRect&); void fadeIn(); void fadeOut(); void setGeometry(const QRectF& rect); // reimp public Q_SLOTS: void setZoom(qreal); void setZoomToFit(bool); void setPosition(const QPoint&); Q_SIGNALS: /** * Emitted when the part has finished loading */ void completed(); void previousImageRequested(); void nextImageRequested(); void captionUpdateRequested(const QString&); void toggleFullScreenRequested(); void videoFinished(); void minimumZoomChanged(qreal); void zoomChanged(qreal); void adapterChanged(); void focused(DocumentView*); void zoomToFitChanged(bool); void positionChanged(); void hudTrashClicked(DocumentView*); void hudDeselectClicked(DocumentView*); void animationFinished(DocumentView*); void contextMenuRequested(); protected: void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = 0); void mouseDoubleClickEvent(QGraphicsSceneMouseEvent* event); void wheelEvent(QGraphicsSceneWheelEvent* event); void contextMenuEvent(QGraphicsSceneContextMenuEvent* event); bool sceneEventFilter(QGraphicsItem*, QEvent*); private Q_SLOTS: void finishOpenUrl(); void slotLoaded(); void slotLoadingFailed(); void zoomActualSize(); void zoomIn(const QPointF& center = QPointF(-1,-1)); void zoomOut(const QPointF& center = QPointF(-1,-1)); void slotZoomChanged(qreal); void slotBusyChanged(const KUrl&, bool); void slotKeyPressed(Qt::Key key, bool pressed); void emitHudTrashClicked(); void emitHudDeselectClicked(); void emitFocused(); void slotAnimationFinished(); private: friend struct DocumentViewPrivate; DocumentViewPrivate* const d; void createAdapterForDocument(); }; } // namespace #endif /* DOCUMENTVIEW_H */ diff --git a/lib/documentview/rasterimageview.cpp b/lib/documentview/rasterimageview.cpp index 11fd177f..804a4baa 100644 --- a/lib/documentview/rasterimageview.cpp +++ b/lib/documentview/rasterimageview.cpp @@ -1,379 +1,398 @@ // vim: set tabstop=4 shiftwidth=4 noexpandtab: /* Gwenview: an image viewer Copyright 2011 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 "rasterimageview.moc" // Local #include #include // KDE #include // Qt +#include #include #include #include namespace Gwenview { struct RasterImageViewPrivate { RasterImageView* q; ImageScaler* mScaler; bool mBufferIsEmpty; QPixmap mCurrentBuffer; // The alternate buffer is useful when scrolling: existing content is copied // to mAlternateBuffer and buffers are swapped. This avoids allocating a new // QPixmap everytime the image is scrolled. QPixmap mAlternateBuffer; QTimer* mUpdateTimer; QWeakPointer mTool; void setupUpdateTimer() { mUpdateTimer = new QTimer(q); mUpdateTimer->setInterval(500); mUpdateTimer->setSingleShot(true); QObject::connect(mUpdateTimer, SIGNAL(timeout()), q, SLOT(updateBuffer())); } void startAnimationIfNecessary() { } QSizeF visibleImageSize() const { if (!q->document()) { return QSizeF(); } qreal zoom; if (q->zoomToFit()) { zoom = q->computeZoomToFit(); } else { zoom = q->zoom(); } QSizeF size = q->documentSize() * zoom; return size.boundedTo(q->boundingRect().size()); } QRectF mapViewportToZoomedImage(const QRectF& viewportRect) const { return QRectF( viewportRect.topLeft() - q->imageOffset() + q->scrollPos(), viewportRect.size() ); } void setScalerRegionToVisibleRect() { QRectF rect = mapViewportToZoomedImage(q->boundingRect()); mScaler->setDestinationRegion(QRegion(rect.toRect())); } void createBuffer() { QSize size = visibleImageSize().toSize(); if (size == mCurrentBuffer.size()) { return; } if (!size.isValid()) { mAlternateBuffer = QPixmap(); mCurrentBuffer = QPixmap(); return; } mAlternateBuffer = QPixmap(size); mAlternateBuffer.fill(Qt::transparent); { QPainter painter(&mAlternateBuffer); painter.drawPixmap(0, 0, mCurrentBuffer); } qSwap(mAlternateBuffer, mCurrentBuffer); mAlternateBuffer = QPixmap(); } }; RasterImageView::RasterImageView(QGraphicsItem* parent) : AbstractImageView(parent) , d(new RasterImageViewPrivate) { d->q = this; d->mBufferIsEmpty = true; d->mScaler = new ImageScaler(this); connect(d->mScaler, SIGNAL(scaledRect(int,int,QImage)), SLOT(updateFromScaler(int,int,QImage)) ); d->setupUpdateTimer(); } RasterImageView::~RasterImageView() { delete d; } void RasterImageView::loadFromDocument() { Document::Ptr doc = document(); connect(doc.data(), SIGNAL(metaInfoLoaded(KUrl)), SLOT(slotDocumentMetaInfoLoaded()) ); connect(doc.data(), SIGNAL(isAnimatedUpdated()), SLOT(slotDocumentIsAnimatedUpdated()) ); const Document::LoadingState state = doc->loadingState(); if (state == Document::MetaInfoLoaded || state == Document::Loaded) { slotDocumentMetaInfoLoaded(); } } void RasterImageView::slotDocumentMetaInfoLoaded() { if (document()->size().isValid()) { finishSetDocument(); } else { // Could not retrieve image size from meta info, we need to load the // full image now. connect(document().data(), SIGNAL(loaded(KUrl)), SLOT(finishSetDocument()) ); document()->startLoadingFullImage(); } } void RasterImageView::finishSetDocument() { if (!document()->size().isValid()) { kError() << "No valid image size available, this should not happen!"; return; } d->createBuffer(); d->mScaler->setDocument(document()); connect(document().data(), SIGNAL(imageRectUpdated(QRect)), SLOT(updateImageRect(QRect)) ); if (zoomToFit()) { setZoom(computeZoomToFit()); } else { QRect rect(QPoint(0, 0), document()->size()); updateImageRect(rect); } d->startAnimationIfNecessary(); update(); } void RasterImageView::updateImageRect(const QRect& /*imageRect*/) { // FIXME: QGV /* QRect viewportRect = mapToViewport(imageRect); viewportRect = viewportRect.intersected(d->mViewport->rect()); if (viewportRect.isEmpty()) { return; } */ if (zoomToFit()) { setZoom(computeZoomToFit()); } d->setScalerRegionToVisibleRect(); update(); } void RasterImageView::slotDocumentIsAnimatedUpdated() { d->startAnimationIfNecessary(); } void RasterImageView::updateFromScaler(int zoomedImageLeft, int zoomedImageTop, const QImage& image) { int viewportLeft = zoomedImageLeft - scrollPos().x(); int viewportTop = zoomedImageTop - scrollPos().y(); d->mBufferIsEmpty = false; { QPainter painter(&d->mCurrentBuffer); /* if (d->mDocument->hasAlphaChannel()) { d->drawAlphaBackground( &painter, QRect(viewportLeft, viewportTop, image.width(), image.height()), QPoint(zoomedImageLeft, zoomedImageTop) ); } else { painter.setCompositionMode(QPainter::CompositionMode_Source); } painter.drawImage(viewportLeft, viewportTop, image); */ painter.setCompositionMode(QPainter::CompositionMode_Source); painter.drawImage(viewportLeft, viewportTop, image); } update(); } void RasterImageView::onZoomChanged() { // If we zoom more than twice, then assume the user wants to see the real // pixels, for example to fine tune a crop operation if (zoom() < 2.) { d->mScaler->setTransformationMode(Qt::SmoothTransformation); } else { d->mScaler->setTransformationMode(Qt::FastTransformation); } if (!d->mUpdateTimer->isActive()) { updateBuffer(); } } void RasterImageView::onImageOffsetChanged() { update(); } void RasterImageView::onScrollPosChanged(const QPointF& oldPos) { QPointF delta = scrollPos() - oldPos; // Scroll existing { if (d->mAlternateBuffer.size() != d->mCurrentBuffer.size()) { d->mAlternateBuffer = QPixmap(d->mCurrentBuffer.size()); } QPainter painter(&d->mAlternateBuffer); painter.drawPixmap(-delta, d->mCurrentBuffer); } qSwap(d->mCurrentBuffer, d->mAlternateBuffer); // Scale missing parts QRegion bufferRegion = QRegion(d->mCurrentBuffer.rect().translated(scrollPos().toPoint())); QRegion updateRegion = bufferRegion - bufferRegion.translated(-delta.toPoint()); updateBuffer(updateRegion); update(); } void RasterImageView::paint(QPainter* painter, const QStyleOptionGraphicsItem* /*option*/, QWidget* /*widget*/) { QSize bufferSize = d->mCurrentBuffer.size(); QSizeF paintSize; if (zoomToFit()) { paintSize = documentSize() * computeZoomToFit(); } else { paintSize = bufferSize; } painter->drawPixmap( (boundingRect().width() - paintSize.width()) / 2, (boundingRect().height() - paintSize.height()) / 2, paintSize.width(), paintSize.height(), d->mCurrentBuffer); if (d->mTool) { d->mTool.data()->paint(painter); } // Debug #if 0 QPointF topLeft = imageOffset(); QSizeF visibleSize = documentSize() * zoom(); painter->setPen(Qt::red); painter->drawRect(topLeft.x(), topLeft.y(), visibleSize.width() - 1, visibleSize.height() - 1); painter->setPen(Qt::blue); painter->drawRect(topLeft.x(), topLeft.y(), d->mCurrentBuffer.width() - 1, d->mCurrentBuffer.height() - 1); #endif } void RasterImageView::resizeEvent(QGraphicsSceneResizeEvent* event) { // If we are in zoomToFit mode and have something in our buffer, delay the // update: paint() will paint a scaled version of the buffer until resizing // is done. This is much faster than rescaling the whole image for each // resize event we receive. // mUpdateTimer must be start before calling AbstractImageView::resizeEvent() // because AbstractImageView::resizeEvent() will call onZoomChanged(), which // will trigger an immediate update unless the mUpdateTimer is active. if (zoomToFit() && !d->mBufferIsEmpty) { d->mUpdateTimer->start(); } AbstractImageView::resizeEvent(event); if (!zoomToFit()) { // Only update buffer if we are not in zoomToFit mode: if we are // onZoomChanged() will have already updated the buffer. updateBuffer(); } } void RasterImageView::updateBuffer(const QRegion& region) { d->mUpdateTimer->stop(); d->createBuffer(); d->mScaler->setZoom(zoom()); if (region.isEmpty()) { d->setScalerRegionToVisibleRect(); } else { d->mScaler->setDestinationRegion(region); } } void RasterImageView::setCurrentTool(AbstractRasterImageViewTool* tool) { if (d->mTool) { d->mTool.data()->toolDeactivated(); } d->mTool = tool; if (d->mTool) { d->mTool.data()->toolActivated(); } update(); } AbstractRasterImageViewTool* RasterImageView::currentTool() const { return d->mTool.data(); } void RasterImageView::mousePressEvent(QGraphicsSceneMouseEvent* event) { if (d->mTool) { d->mTool.data()->mousePressEvent(event); + if (event->isAccepted()) { + return; + } } AbstractImageView::mousePressEvent(event); } void RasterImageView::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { if (d->mTool) { d->mTool.data()->mouseMoveEvent(event); + if (event->isAccepted()) { + return; + } } AbstractImageView::mouseMoveEvent(event); } void RasterImageView::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { if (d->mTool) { d->mTool.data()->mouseReleaseEvent(event); + if (event->isAccepted()) { + return; + } } AbstractImageView::mouseReleaseEvent(event); } void RasterImageView::wheelEvent(QGraphicsSceneWheelEvent* event) { if (d->mTool) { d->mTool.data()->wheelEvent(event); + if (event->isAccepted()) { + return; + } } AbstractImageView::wheelEvent(event); } void RasterImageView::keyPressEvent(QKeyEvent* event) { if (d->mTool) { d->mTool.data()->keyPressEvent(event); + if (event->isAccepted()) { + return; + } } AbstractImageView::keyPressEvent(event); } void RasterImageView::keyReleaseEvent(QKeyEvent* event) { if (d->mTool) { d->mTool.data()->keyReleaseEvent(event); + if (event->isAccepted()) { + return; + } } AbstractImageView::keyReleaseEvent(event); } } // namespace diff --git a/lib/redeyereduction/redeyereductiontool.cpp b/lib/redeyereduction/redeyereductiontool.cpp index ca5e3cd8..55012ddb 100644 --- a/lib/redeyereduction/redeyereductiontool.cpp +++ b/lib/redeyereduction/redeyereductiontool.cpp @@ -1,212 +1,217 @@ // vim: set tabstop=4 shiftwidth=4 noexpandtab: /* 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. */ // Self #include "redeyereductiontool.moc" // Qt +#include +#include #include #include #include #include #include // KDE #include #include // Local +#include +#include #include #include "gwenviewconfig.h" -#include "imageview.h" #include "paintutils.h" #include "redeyereductionimageoperation.h" #include "ui_redeyereductionhud.h" -#include "widgetfloater.h" namespace Gwenview { struct RedEyeReductionHud : public QWidget, public Ui_RedEyeReductionHud { RedEyeReductionHud() { setupUi(this); setCursor(Qt::ArrowCursor); } }; struct RedEyeReductionToolPrivate { RedEyeReductionTool* mRedEyeReductionTool; RedEyeReductionTool::Status mStatus; QPointF mCenter; int mDiameter; - RedEyeReductionHud* mHud; - HudWidget* mHudWidget; - WidgetFloater* mFloater; + QGraphicsProxyWidget* mHudWidget; + GraphicsWidgetFloater* mFloater; void showNotSetHudWidget() { - mHud->deleteLater(); - mHud = 0; QLabel* label = new QLabel(i18n("Click on the red eye you want to fix.")); label->show(); label->adjustSize(); createHudWidgetForWidget(label); } void showAdjustingHudWidget() { - mHud = new RedEyeReductionHud(); + RedEyeReductionHud* hud = new RedEyeReductionHud(); - mHud->diameterSpinBox->setValue(mDiameter); - QObject::connect(mHud->applyButton, SIGNAL(clicked()), + hud->diameterSpinBox->setValue(mDiameter); + QObject::connect(hud->applyButton, SIGNAL(clicked()), mRedEyeReductionTool, SLOT(slotApplyClicked())); - QObject::connect(mHud->diameterSpinBox, SIGNAL(valueChanged(int)), + QObject::connect(hud->diameterSpinBox, SIGNAL(valueChanged(int)), mRedEyeReductionTool, SLOT(setDiameter(int))); - createHudWidgetForWidget(mHud); + createHudWidgetForWidget(hud); } void createHudWidgetForWidget(QWidget* widget) { mHudWidget->deleteLater(); - mHudWidget = new HudWidget(); - mHudWidget->init(widget, HudWidget::OptionCloseButton); - mHudWidget->adjustSize(); - QObject::connect(mHudWidget, SIGNAL(closed()), + HudWidget* hud = new HudWidget(); + hud->init(widget, HudWidget::OptionCloseButton); + hud->adjustSize(); + QObject::connect(hud, SIGNAL(closed()), mRedEyeReductionTool, SIGNAL(done()) ); + mHudWidget = new QGraphicsProxyWidget(mRedEyeReductionTool->imageView()); + mHudWidget->setWidget(hud); mFloater->setChildWidget(mHudWidget); } void hideHud() { mHudWidget->hide(); } QRectF rectF() const { if (mStatus == RedEyeReductionTool::NotSet) { return QRectF(); } return QRectF(mCenter.x() - mDiameter / 2, mCenter.y() - mDiameter / 2, mDiameter, mDiameter); } }; -RedEyeReductionTool::RedEyeReductionTool(ImageView* view) -: AbstractImageViewTool(view) +RedEyeReductionTool::RedEyeReductionTool(RasterImageView* view) +: AbstractRasterImageViewTool(view) , d(new RedEyeReductionToolPrivate) { d->mRedEyeReductionTool = this; d->mDiameter = GwenviewConfig::redEyeReductionDiameter(); d->mStatus = NotSet; - d->mHud = 0; d->mHudWidget = 0; - d->mFloater = new WidgetFloater(imageView()); + d->mFloater = new GraphicsWidgetFloater(imageView()); d->mFloater->setAlignment(Qt::AlignHCenter | Qt::AlignBottom); d->mFloater->setVerticalMargin( KDialog::marginHint() + imageView()->style()->pixelMetric(QStyle::PM_ScrollBarExtent) ); d->showNotSetHudWidget(); view->document()->startLoadingFullImage(); } RedEyeReductionTool::~RedEyeReductionTool() { GwenviewConfig::setRedEyeReductionDiameter(d->mDiameter); delete d; } void RedEyeReductionTool::paint(QPainter* painter) { if (d->mStatus == NotSet) { return; } QRectF docRectF = d->rectF(); imageView()->document()->waitUntilLoaded(); QRect docRect = PaintUtils::containingRect(docRectF); QImage img = imageView()->document()->image().copy(docRect); QRectF imgRectF( docRectF.left() - docRect.left(), docRectF.top() - docRect.top(), docRectF.width(), docRectF.height() ); RedEyeReductionImageOperation::apply(&img, imgRectF); - const QRectF viewRectF = imageView()->mapToViewportF(docRectF); + const QRectF viewRectF = imageView()->mapToView(docRectF); painter->drawImage(viewRectF, img, imgRectF); } -void RedEyeReductionTool::mousePressEvent(QMouseEvent* event) { +void RedEyeReductionTool::mousePressEvent(QGraphicsSceneMouseEvent* event) { + event->accept(); if (d->mStatus == NotSet) { d->showAdjustingHudWidget(); d->mStatus = Adjusting; } - d->mCenter = imageView()->mapToImageF(event->pos()); - imageView()->viewport()->update(); + d->mCenter = imageView()->mapToImage(event->pos()); + imageView()->update(); } - -void RedEyeReductionTool::mouseMoveEvent(QMouseEvent* event) { +void RedEyeReductionTool::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { + event->accept(); if (event->buttons() == Qt::NoButton) { return; } - d->mCenter = imageView()->mapToImageF(event->pos()); - imageView()->viewport()->update(); + d->mCenter = imageView()->mapToImage(event->pos()); + imageView()->update(); } +void RedEyeReductionTool::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { + // Just prevent the event from reaching the image view + event->accept(); +} void RedEyeReductionTool::toolActivated() { - imageView()->viewport()->setCursor(Qt::CrossCursor); + imageView()->setCursor(Qt::CrossCursor); } void RedEyeReductionTool::toolDeactivated() { d->mHudWidget->deleteLater(); } void RedEyeReductionTool::slotApplyClicked() { QRectF docRectF = d->rectF(); if (!docRectF.isValid()) { kWarning() << "invalid rect"; return; } RedEyeReductionImageOperation* op = new RedEyeReductionImageOperation(docRectF); emit imageOperationRequested(op); d->mStatus = NotSet; d->showNotSetHudWidget(); } void RedEyeReductionTool::setDiameter(int value) { d->mDiameter = value; - imageView()->viewport()->update(); + imageView()->update(); } } // namespace diff --git a/lib/redeyereduction/redeyereductiontool.h b/lib/redeyereduction/redeyereductiontool.h index 77db60c6..771f234f 100644 --- a/lib/redeyereduction/redeyereductiontool.h +++ b/lib/redeyereduction/redeyereductiontool.h @@ -1,73 +1,74 @@ // vim: set tabstop=4 shiftwidth=4 noexpandtab: /* 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 REDEYEREDUCTIONTOOL_H #define REDEYEREDUCTIONTOOL_H #include // Qt // KDE // Local -#include +#include namespace Gwenview { class AbstractImageOperation; -class ImageView; +class RasterImageView; struct RedEyeReductionToolPrivate; -class GWENVIEWLIB_EXPORT RedEyeReductionTool : public AbstractImageViewTool { +class GWENVIEWLIB_EXPORT RedEyeReductionTool : public AbstractRasterImageViewTool { Q_OBJECT public: enum Status { NotSet, Adjusting }; - RedEyeReductionTool(ImageView* parent); + RedEyeReductionTool(RasterImageView* parent); ~RedEyeReductionTool(); virtual void paint(QPainter*); - virtual void mousePressEvent(QMouseEvent*); - virtual void mouseMoveEvent(QMouseEvent*); + virtual void mousePressEvent(QGraphicsSceneMouseEvent*); + virtual void mouseMoveEvent(QGraphicsSceneMouseEvent*); + virtual void mouseReleaseEvent(QGraphicsSceneMouseEvent*); virtual void toolActivated(); virtual void toolDeactivated(); Q_SIGNALS: void done(); void imageOperationRequested(AbstractImageOperation*); private Q_SLOTS: void setDiameter(int); void slotApplyClicked(); private: RedEyeReductionToolPrivate* const d; }; } // namespace #endif /* REDEYEREDUCTIONTOOL_H */