diff --git a/app/configdialog.cpp b/app/configdialog.cpp index a582695a..8027a0f3 100644 --- a/app/configdialog.cpp +++ b/app/configdialog.cpp @@ -1,132 +1,139 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* 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 "configdialog.h" // Qt // KDE #include // Local #include "ui_generalconfigpage.h" #include "ui_imageviewconfigpage.h" #include "ui_advancedconfigpage.h" #include #include namespace Gwenview { struct ConfigDialogPrivate { InvisibleButtonGroup* mAlphaBackgroundModeGroup; InvisibleButtonGroup* mWheelBehaviorGroup; InvisibleButtonGroup* mAnimationMethodGroup; + InvisibleButtonGroup* mZoomModeGroup; InvisibleButtonGroup* mThumbnailBarOrientationGroup; Ui_GeneralConfigPage mGeneralConfigPage; Ui_ImageViewConfigPage mImageViewConfigPage; Ui_AdvancedConfigPage mAdvancedConfigPage; }; template QWidget* setupPage(Ui& ui) { QWidget* widget = new QWidget; ui.setupUi(widget); widget->layout()->setMargin(0); return widget; } ConfigDialog::ConfigDialog(QWidget* parent) : KConfigDialog(parent, "Settings", GwenviewConfig::self()) , d(new ConfigDialogPrivate) { setFaceType(KPageDialog::List); setButtons(KDialog::Ok | KDialog::Cancel | KDialog::Apply | KDialog::Default); showButtonSeparator(true); QWidget* widget; KPageWidgetItem* pageItem; // General widget = setupPage(d->mGeneralConfigPage); pageItem = addPage(widget, i18n("General")); pageItem->setIcon(KIcon("gwenview")); connect(d->mGeneralConfigPage.kcfg_ViewBackgroundValue, SIGNAL(valueChanged(int)), SLOT(updateViewBackgroundFrame())); // Image View widget = setupPage(d->mImageViewConfigPage); d->mAlphaBackgroundModeGroup = new InvisibleButtonGroup(widget); d->mAlphaBackgroundModeGroup->setObjectName(QLatin1String("kcfg_AlphaBackgroundMode")); d->mAlphaBackgroundModeGroup->addButton(d->mImageViewConfigPage.checkBoardRadioButton, int(RasterImageView::AlphaBackgroundCheckBoard)); d->mAlphaBackgroundModeGroup->addButton(d->mImageViewConfigPage.solidColorRadioButton, int(RasterImageView::AlphaBackgroundSolid)); d->mWheelBehaviorGroup = new InvisibleButtonGroup(widget); d->mWheelBehaviorGroup->setObjectName(QLatin1String("kcfg_MouseWheelBehavior")); d->mWheelBehaviorGroup->addButton(d->mImageViewConfigPage.mouseWheelScrollRadioButton, int(MouseWheelBehavior::Scroll)); d->mWheelBehaviorGroup->addButton(d->mImageViewConfigPage.mouseWheelBrowseRadioButton, int(MouseWheelBehavior::Browse)); d->mAnimationMethodGroup = new InvisibleButtonGroup(widget); d->mAnimationMethodGroup->setObjectName(QLatin1String("kcfg_AnimationMethod")); d->mAnimationMethodGroup->addButton(d->mImageViewConfigPage.glAnimationRadioButton, int(DocumentView::GLAnimation)); d->mAnimationMethodGroup->addButton(d->mImageViewConfigPage.softwareAnimationRadioButton, int(DocumentView::SoftwareAnimation)); d->mAnimationMethodGroup->addButton(d->mImageViewConfigPage.noAnimationRadioButton, int(DocumentView::NoAnimation)); + d->mZoomModeGroup = new InvisibleButtonGroup(widget); + d->mZoomModeGroup->setObjectName(QLatin1String("kcfg_ZoomMode")); + d->mZoomModeGroup->addButton(d->mImageViewConfigPage.autofitZoomModeRadioButton, int(ZoomMode::Autofit)); + d->mZoomModeGroup->addButton(d->mImageViewConfigPage.keepSameZoomModeRadioButton, int(ZoomMode::KeepSame)); + d->mZoomModeGroup->addButton(d->mImageViewConfigPage.individualZoomModeRadioButton, int(ZoomMode::Individual)); + d->mThumbnailBarOrientationGroup = new InvisibleButtonGroup(widget); d->mThumbnailBarOrientationGroup->setObjectName(QLatin1String("kcfg_ThumbnailBarOrientation")); d->mThumbnailBarOrientationGroup->addButton(d->mImageViewConfigPage.horizontalRadioButton, int(Qt::Horizontal)); d->mThumbnailBarOrientationGroup->addButton(d->mImageViewConfigPage.verticalRadioButton, int(Qt::Vertical)); pageItem = addPage(widget, i18n("Image View")); pageItem->setIcon(KIcon("view-preview")); // Advanced widget = setupPage(d->mAdvancedConfigPage); pageItem = addPage(widget, i18n("Advanced")); pageItem->setIcon(KIcon("preferences-other")); d->mAdvancedConfigPage.cacheHelpLabel->setFont(KGlobalSettings::smallestReadableFont()); updateViewBackgroundFrame(); } ConfigDialog::~ConfigDialog() { delete d; } void ConfigDialog::updateViewBackgroundFrame() { QColor color = QColor::fromHsv(0, 0, d->mGeneralConfigPage.kcfg_ViewBackgroundValue->value()); QString css = QString( "background-color: %1;" "border-radius: 5px;" "border: 1px solid %1;") .arg(color.name()); // When using Oxygen, setting the background color via palette causes the // pixels outside the frame to be painted with the new background color as // well. Using CSS works more like expected. d->mGeneralConfigPage.backgroundValueFrame->setStyleSheet(css); } } // namespace diff --git a/app/imageviewconfigpage.ui b/app/imageviewconfigpage.ui index f5e1e5a2..5a95cc1d 100644 --- a/app/imageviewconfigpage.ui +++ b/app/imageviewconfigpage.ui @@ -1,492 +1,628 @@ ImageViewConfigPage 0 0 - 400 - 385 + 500 + 600 - + Transparent background: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter checkBoardRadioButton - + 6 0 &Check board true - + Qt::Horizontal 40 20 - + 6 0 &Solid color: false - + Qt::Horizontal 20 20 - + Qt::Vertical QSizePolicy::Fixed 207 17 - + Mouse wheel behavior: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter mouseWheelScrollRadioButton - + Scroll true - + Qt::Horizontal 40 20 - + Browse - + Qt::Horizontal 40 20 - + Qt::Vertical QSizePolicy::Fixed 207 17 + + + + Zoom mode: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + autofitZoomModeRadioButton + + + - + + + + + Autofit each image + + + true + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Keep same zoom and position + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Per image zoom and position + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 207 + 17 + + + + + + Enlarge smaller images - + Qt::Horizontal 40 20 - + Qt::Vertical QSizePolicy::Fixed 204 17 - - + + Animations: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter glAnimationRadioButton - + OpenGL true - + Qt::Horizontal 40 20 - - + + Software - + Qt::Horizontal 40 20 - - + + None - + Qt::Horizontal 40 20 - - + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 204 + 17 + + + + + + <b>Thumbnail Bar</b> - - + + + + Qt::Vertical + + + QSizePolicy::Fixed + + + + 204 + 12 + + + + + + Orientation: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter horizontalRadioButton - - + + Horizontal - + Qt::Horizontal 40 20 - - + + Vertical - + Qt::Horizontal 40 20 - - + + Row count: Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter kcfg_ThumbnailBarRowCount - - + + 1 10 - + Qt::Horizontal 40 20 - - + + Qt::Vertical 20 - 62 + 60 KColorButton QPushButton
kcolorbutton.h
KIntSpinBox QSpinBox
knuminput.h
checkBoardRadioButton solidColorRadioButton kcfg_AlphaBackgroundColor mouseWheelScrollRadioButton mouseWheelBrowseRadioButton kcfg_EnlargeSmallerImages glAnimationRadioButton softwareAnimationRadioButton noAnimationRadioButton horizontalRadioButton verticalRadioButton kcfg_ThumbnailBarRowCount solidColorRadioButton toggled(bool) kcfg_AlphaBackgroundColor setEnabled(bool) 195 49 292 56
diff --git a/app/kipiinterface.h b/app/kipiinterface.h index 45fb03ba..d2a9e236 100644 --- a/app/kipiinterface.h +++ b/app/kipiinterface.h @@ -1,119 +1,118 @@ // vim: set tabstop=4 shiftwidth=4 expandtab: /* Gwenview: an image viewer Copyright 2000-2008 Aurélien Gâteau Copyright 2008 Angelo Naselli 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 KIPIINTERFACE_H #define KIPIINTERFACE_H // KIPI #include #include #include class QAction; namespace Gwenview { struct KIPIInterfacePrivate; class MainWindow; class KIPIInterface : public KIPI::Interface { Q_OBJECT public: KIPIInterface(MainWindow*); virtual ~KIPIInterface(); KIPI::ImageCollection currentAlbum(); KIPI::ImageCollection currentSelection(); QList allAlbums(); KIPI::ImageInfo info(const KUrl& url); int features() const; virtual bool addImage(const KUrl&, QString& err); virtual void delImage(const KUrl&); virtual void refreshImages(const KUrl::List& urls); virtual KIPI::ImageCollectionSelector* imageCollectionSelector(QWidget *parent); virtual KIPI::UploadWidget* uploadWidget(QWidget *parent); QList pluginActions(KIPI::Category) const; bool isLoadingFinished() const; Q_SIGNALS: void loadingFinished(); public Q_SLOTS: void loadPlugins(); private Q_SLOTS: void slotSelectionChanged(); void slotDirectoryChanged(); void init(); void loadOnePlugin(); private: KIPIInterfacePrivate* const d; }; class ImageCollection : public KIPI::ImageCollectionShared { public: ImageCollection(KUrl dirURL, const QString& name, const KUrl::List& images) : KIPI::ImageCollectionShared() , mDirURL(dirURL) , mName(name) , mImages(images) {} QString name() { return mName; } QString comment() { return QString(); } KUrl::List images() { return mImages; } KUrl uploadRoot() { return KUrl("/"); } KUrl uploadPath() { return mDirURL; } QString uploadRootName() { return "/"; } bool isDirectory() { return true; } private: KUrl mDirURL; QString mName; KUrl::List mImages; }; } // namespace #endif /* KIPIINTERFACE_H */ - diff --git a/app/viewmainpage.cpp b/app/viewmainpage.cpp index 7caf0992..439ccd6a 100644 --- a/app/viewmainpage.cpp +++ b/app/viewmainpage.cpp @@ -1,793 +1,788 @@ /* 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 "viewmainpage.moc" // Qt #include #include #include #include #include #include #include // KDE #include #include #include #include #include #include #include #include #include // Local #include "fileoperations.h" #include #include "splitter.h" #include #include #include #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 ViewMainPage::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-----------------+||| * |||| || |||| * |||| || |||| * |||| || |||| * |||| || |||| * |||| || |||| * |||| || |||| * |||+-----------------------------++------------------------------+||| * ||+---------------------------------------------------------------+|| * ||+-mToolContainer------------------------------------------------+|| * ||| ||| * ||+---------------------------------------------------------------+|| * ||+-mStatusBarContainer-------------------------------------------+|| * |||[mToggleSideBarButton][mToggleThumbnailBarButton] [mZoomWidget]||| * ||+---------------------------------------------------------------+|| * |+-----------------------------------------------------------------+| * |===================================================================| * |+-mThumbnailBar---------------------------------------------------+| * || || * || || * |+-----------------------------------------------------------------+| * +-------------------------------------------------------------------+ */ struct ViewMainPagePrivate { ViewMainPage* q; SlideShow* mSlideShow; KActionCollection* mActionCollection; GvCore* mGvCore; KModelIndexProxyMapper* mDirModelToBarModelProxyMapper; QSplitter *mThumbnailSplitter; QWidget* mAdapterContainer; DocumentViewController* mDocumentViewController; QList mDocumentViews; DocumentViewSynchronizer* mSynchronizer; QToolButton* mToggleSideBarButton; QToolButton* mToggleThumbnailBarButton; ZoomWidget* mZoomWidget; DocumentViewContainer* mDocumentViewContainer; SlideContainer* mToolContainer; QWidget* mStatusBarContainer; ThumbnailBarView* mThumbnailBar; KToggleAction* mToggleThumbnailBarAction; KToggleAction* mSynchronizeAction; QCheckBox* mSynchronizeCheckBox; // Activity Resource events reporting needs to be above KPart, // in the shell itself, to avoid problems with other MDI applications // that use this KPart QHash mActivityResources; bool mFullScreenMode; bool mCompareMode; bool mThumbnailBarVisibleBeforeFullScreen; + ZoomMode::Enum mZoomMode; void setupThumbnailBar() { mThumbnailBar = new ThumbnailBarView; ThumbnailBarItemDelegate* delegate = new ThumbnailBarItemDelegate(mThumbnailBar); mThumbnailBar->setItemDelegate(delegate); mThumbnailBar->setVisible(GwenviewConfig::thumbnailBarIsVisible()); mThumbnailBar->setSelectionMode(QAbstractItemView::ExtendedSelection); } void setupThumbnailBarStyleSheet() { QPalette pal = mGvCore->palette(GvCore::NormalViewPalette); mThumbnailBar->setPalette(pal); Qt::Orientation orientation = mThumbnailBar->orientation(); QColor bgColor = pal.color(QPalette::Normal, QPalette::Base); QColor bgSelColor = pal.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 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 = 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(mToolContainer); layout->addWidget(mStatusBarContainer); } void setupDocumentViewController() { mDocumentViewController = new DocumentViewController(mActionCollection, q); mDocumentViewController->setZoomWidget(mZoomWidget); mDocumentViewController->setToolContainer(mToolContainer); mSynchronizer = new DocumentViewSynchronizer(&mDocumentViews, q); } DocumentView* createDocumentView() { DocumentView* view = mDocumentViewContainer->createView(); // Connect context menu // If you need to connect another view signal, make sure it is disconnected in deleteDocumentView QObject::connect(view, SIGNAL(contextMenuRequested()), q, SLOT(showContextMenu())); QObject::connect(view, SIGNAL(completed()), q, SIGNAL(completed())); QObject::connect(view, SIGNAL(previousImageRequested()), q, SIGNAL(previousImageRequested())); QObject::connect(view, SIGNAL(nextImageRequested()), q, SIGNAL(nextImageRequested())); QObject::connect(view, SIGNAL(captionUpdateRequested(QString)), q, SIGNAL(captionUpdateRequested(QString))); QObject::connect(view, SIGNAL(toggleFullScreenRequested()), q, SIGNAL(toggleFullScreenRequested())); QObject::connect(view, SIGNAL(focused(DocumentView*)), q, SLOT(slotViewFocused(DocumentView*))); QObject::connect(view, SIGNAL(hudTrashClicked(DocumentView*)), q, SLOT(trashView(DocumentView*))); QObject::connect(view, SIGNAL(hudDeselectClicked(DocumentView*)), q, SLOT(deselectView(DocumentView*))); QObject::connect(view, SIGNAL(videoFinished()), mSlideShow, SLOT(resumeAndGoToNextUrl())); mDocumentViews << view; mActivityResources.insert(view, new KActivities::ResourceInstance(q->window()->winId(), view)); return view; } void deleteDocumentView(DocumentView* view) { if (mDocumentViewController->view() == view) { mDocumentViewController->setView(0); } // Make sure we do not get notified about this view while it is going away. // mDocumentViewController->deleteView() animates the view deletion so // the view still exists for a short while when we come back to the // event loop) QObject::disconnect(view, 0, q, 0); QObject::disconnect(view, 0, mSlideShow, 0); mDocumentViews.removeOne(view); mActivityResources.remove(view); mDocumentViewContainer->deleteView(view); } void setupToolContainer() { mToolContainer = new SlideContainer; } void setupStatusBar() { mStatusBarContainer = new QWidget; mToggleSideBarButton = new StatusBarToolButton; mToggleThumbnailBarButton = new StatusBarToolButton; mZoomWidget = new ZoomWidget; mSynchronizeCheckBox = new QCheckBox(i18n("Synchronize")); mSynchronizeCheckBox->hide(); QHBoxLayout* layout = new QHBoxLayout(mStatusBarContainer); layout->setMargin(0); layout->setSpacing(0); layout->addWidget(mToggleSideBarButton); layout->addWidget(mToggleThumbnailBarButton); layout->addStretch(); layout->addWidget(mSynchronizeCheckBox); layout->addStretch(); layout->addWidget(mZoomWidget); } void setupSplitter() { Qt::Orientation orientation = GwenviewConfig::thumbnailBarOrientation(); mThumbnailSplitter = new Splitter(orientation == Qt::Horizontal ? Qt::Vertical : Qt::Horizontal, q); mThumbnailBar->setOrientation(orientation); mThumbnailBar->setThumbnailAspectRatio(GwenviewConfig::thumbnailAspectRatio()); mThumbnailBar->setRowCount(GwenviewConfig::thumbnailBarRowCount()); mThumbnailSplitter->addWidget(mAdapterContainer); mThumbnailSplitter->addWidget(mThumbnailBar); mThumbnailSplitter->setSizes(GwenviewConfig::thumbnailSplitterSizes()); QVBoxLayout* layout = new QVBoxLayout(q); layout->setMargin(0); layout->addWidget(mThumbnailSplitter); } 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); Q_ASSERT(mActivityResources.contains(oldView)); mActivityResources.value(oldView)->notifyFocusedOut(); } view->setCurrent(true); mDocumentViewController->setView(view); mSynchronizer->setCurrentView(view); QModelIndex index = indexForView(view); if (index.isValid()) { // Index may be invalid when Gwenview is started as // `gwenview /foo/image.png` because in this situation it loads image.png // *before* listing /foo (because it matters less to the user) mThumbnailBar->selectionModel()->setCurrentIndex(index, QItemSelectionModel::Current); } Q_ASSERT(mActivityResources.contains(view)); mActivityResources.value(view)->notifyFocusedIn(); } QModelIndex indexForView(DocumentView* view) const { KUrl url = view->url(); if (!url.isValid()) { kWarning() << "View does not display any document!"; return QModelIndex(); } SortedDirModel* dirModel = mGvCore->sortedDirModel(); QModelIndex srcIndex = dirModel->indexForUrl(url); if (!mDirModelToBarModelProxyMapper) { // Delay the initialization of the mapper to its first use because // mThumbnailBar->model() is not set after ViewMainPage ctor is // done. const_cast(this)->mDirModelToBarModelProxyMapper = new KModelIndexProxyMapper(dirModel, mThumbnailBar->model(), q); } QModelIndex index = mDirModelToBarModelProxyMapper->mapLeftToRight(srcIndex); return index; } void applyPalette(bool fullScreenMode) { mDocumentViewContainer->setPalette(mGvCore->palette(fullScreenMode ? GvCore::FullScreenViewPalette : GvCore::NormalViewPalette)); setupThumbnailBarStyleSheet(); } }; ViewMainPage::ViewMainPage(QWidget* parent, SlideShow* slideShow, KActionCollection* actionCollection, GvCore* gvCore) : QWidget(parent) , d(new ViewMainPagePrivate) { d->q = this; d->mDirModelToBarModelProxyMapper = 0; // Initialized later d->mSlideShow = slideShow; d->mActionCollection = actionCollection; d->mGvCore = gvCore; d->mFullScreenMode = false; d->mCompareMode = false; d->mThumbnailBarVisibleBeforeFullScreen = false; QShortcut* goToBrowseModeShortcut = new QShortcut(this); goToBrowseModeShortcut->setKey(Qt::Key_Return); connect(goToBrowseModeShortcut, SIGNAL(activated()), SIGNAL(goToBrowseModeRequested())); d->setupToolContainer(); d->setupStatusBar(); d->setupAdapterContainer(); d->setupThumbnailBar(); d->setupSplitter(); d->setupDocumentViewController(); 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))); } ViewMainPage::~ViewMainPage() { delete d; } void ViewMainPage::loadConfig() { d->applyPalette(false /* fullScreenMode */); // 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); } - if (GwenviewConfig::showLockZoomButton()) { - d->mZoomWidget->setZoomLocked(GwenviewConfig::lockZoom()); - } else { - d->mZoomWidget->setLockZoomButtonVisible(false); - } + d->mZoomMode = GwenviewConfig::zoomMode(); } void ViewMainPage::saveConfig() { d->saveSplitterConfig(); GwenviewConfig::setThumbnailBarIsVisible(d->mToggleThumbnailBarAction->isChecked()); - if (GwenviewConfig::showLockZoomButton()) { - GwenviewConfig::setLockZoom(d->mZoomWidget->isZoomLocked()); - } } void ViewMainPage::setThumbnailBarVisibility(bool visible) { d->saveSplitterConfig(); d->mThumbnailBar->setVisible(visible); } int ViewMainPage::statusBarHeight() const { return d->mStatusBarContainer->height(); } void ViewMainPage::setFullScreenMode(bool fullScreenMode) { d->mFullScreenMode = fullScreenMode; d->mStatusBarContainer->setVisible(!fullScreenMode); if (fullScreenMode) { d->mThumbnailBarVisibleBeforeFullScreen = d->mToggleThumbnailBarAction->isChecked(); if (d->mThumbnailBarVisibleBeforeFullScreen) { d->mToggleThumbnailBarAction->trigger(); } } else { if (d->mThumbnailBarVisibleBeforeFullScreen) { d->mToggleThumbnailBarAction->trigger(); } } d->applyPalette(fullScreenMode); d->mToggleThumbnailBarAction->setEnabled(!fullScreenMode); } bool ViewMainPage::isFullScreenMode() const { return d->mFullScreenMode; } ThumbnailBarView* ViewMainPage::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 ViewMainPage::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 ViewMainPage::sizeHint() const { return QSize(400, 300); } KUrl ViewMainPage::url() const { GV_RETURN_VALUE_IF_FAIL(d->currentView(), KUrl()); return d->currentView()->url(); } Document::Ptr ViewMainPage::currentDocument() const { if (!d->currentView()) { LOG("!d->documentView()"); return Document::Ptr(); } return d->currentView()->document(); } bool ViewMainPage::isEmpty() const { return !currentDocument(); } RasterImageView* ViewMainPage::imageView() const { if (!d->currentView()) { return 0; } return d->currentView()->imageView(); } DocumentView* ViewMainPage::documentView() const { return d->currentView(); } void ViewMainPage::openUrl(const KUrl& url) { openUrls(KUrl::List() << url, url); } void ViewMainPage::openUrls(const KUrl::List& allUrls, const KUrl& currentUrl) { DocumentView::Setup setup; QSet urls = allUrls.toSet(); d->mCompareMode = urls.count() > 1; typedef QMap ViewForUrlMap; ViewForUrlMap viewForUrlMap; if (!d->mDocumentViews.isEmpty()) { d->mDocumentViewContainer->updateSetup(d->mDocumentViews.last()); } - if (!d->mDocumentViews.isEmpty() && d->mZoomWidget->isZoomLocked()) { - setup = d->mDocumentViews.last()->setup(); - } else { + if (d->mDocumentViews.isEmpty() || d->mZoomMode == ZoomMode::Autofit) { setup.valid = true; setup.zoomToFit = true; + } else { + setup = d->mDocumentViews.last()->setup(); } // 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); viewForUrlMap.insert(url, view); } else { // view url is not interesting, drop it d->deleteDocumentView(view); } } // Create view for remaining urls Q_FOREACH(const KUrl & url, urls) { if (d->mDocumentViews.count() >= MaxViewCount) { kWarning() << "Too many documents to show"; break; } DocumentView* view = d->createDocumentView(); viewForUrlMap.insert(url, view); } // Set sortKey to match url order int sortKey = 0; Q_FOREACH(const KUrl& url, allUrls) { viewForUrlMap[url]->setSortKey(sortKey); ++sortKey; } 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. ViewForUrlMap::ConstIterator it = viewForUrlMap.constBegin(), end = viewForUrlMap.constEnd(); for (; it != end; ++it) { KUrl url = it.key(); DocumentView* view = it.value(); DocumentView::Setup savedSetup = d->mDocumentViewContainer->savedSetup(url); - view->openUrl(url, savedSetup.valid ? savedSetup : setup); + view->openUrl(url, d->mZoomMode == ZoomMode::Individual && savedSetup.valid ? savedSetup : setup); d->mActivityResources.value(view)->setUri(url); } // 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->setActive(d->mSynchronizeCheckBox->isChecked()); } else { d->mSynchronizer->setActive(false); } } void ViewMainPage::reload() { DocumentView *view = d->currentView(); if (!view) { return; } Document::Ptr doc = view->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(), d->currentView()->setup()); } void ViewMainPage::reset() { d->mDocumentViewController->setView(0); d->mDocumentViewContainer->reset(); d->mDocumentViews.clear(); } void ViewMainPage::slotViewFocused(DocumentView* view) { d->setCurrentView(view); } void ViewMainPage::trashView(DocumentView* view) { KUrl url = view->url(); deselectView(view); FileOperations::trash(KUrl::List() << url, this); } void ViewMainPage::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); if (idx + 1 < d->mDocumentViews.count()) { newCurrentView = d->mDocumentViews.at(idx + 1); } else if (idx > 0) { newCurrentView = d->mDocumentViews.at(idx - 1); } else { GV_WARN_AND_RETURN("No view found to set as current"); } } QModelIndex index = d->indexForView(view); QItemSelectionModel* selectionModel = d->mThumbnailBar->selectionModel(); selectionModel->select(index, QItemSelectionModel::Deselect); if (newCurrentView) { d->setCurrentView(newCurrentView); } } QToolButton* ViewMainPage::toggleSideBarButton() const { return d->mToggleSideBarButton; } void ViewMainPage::showMessageWidget(QGraphicsWidget* widget, Qt::Alignment align) { d->mDocumentViewContainer->showMessageWidget(widget, align); } } // namespace diff --git a/cmake/FindLCMS2.cmake b/cmake/FindLCMS2.cmake index cc17e9f8..53231ed5 100644 --- a/cmake/FindLCMS2.cmake +++ b/cmake/FindLCMS2.cmake @@ -1,72 +1,71 @@ # - Find LCMS2 # Find the LCMS2 includes and library # This module defines # LCMS2_INCLUDE_DIR, where to find lcms.h # LCMS2_LIBRARIES, the libraries needed to use LCMS2. # LCMS2_VERSION, The value of LCMS_VERSION defined in lcms.h # LCMS2_FOUND, If false, do not try to use LCMS2. # Copyright (c) 2008, Adrian Page, # Copyright (c) 2009, Cyrille Berger, # # Redistribution and use is allowed according to the terms of the BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # use pkg-config to get the directories and then use these values # in the FIND_PATH() and FIND_LIBRARY() calls if(NOT WIN32) find_package(PkgConfig) pkg_check_modules(PC_LCMS2 lcms2) set(LCMS2_DEFINITIONS ${PC_LCMS2_CFLAGS_OTHER}) endif() find_path(LCMS2_INCLUDE_DIR lcms2.h PATHS ${PC_LCMS2_INCLUDEDIR} ${PC_LCMS2_INCLUDE_DIRS} PATH_SUFFIXES lcms2 liblcms2 ) find_library(LCMS2_LIBRARIES NAMES lcms2 liblcms2 lcms-2 liblcms-2 - PATHS + PATHS ${PC_LCMS2_LIBDIR} ${PC_LCMS2_LIBRARY_DIRS} PATH_SUFFIXES lcms2 ) if(LCMS2_INCLUDE_DIR AND LCMS2_LIBRARIES) set(LCMS2_FOUND TRUE) else() set(LCMS2_FOUND FALSE) endif() if(LCMS2_FOUND) file(READ ${LCMS2_INCLUDE_DIR}/lcms2.h LCMS2_VERSION_CONTENT) string(REGEX MATCH "#define LCMS_VERSION[ ]*[0-9]*\n" LCMS2_VERSION_MATCH ${LCMS2_VERSION_CONTENT}) if(LCMS2_VERSION_MATCH) string(REGEX REPLACE "#define LCMS_VERSION[ ]*([0-9]*)\n" "\\1" LCMS2_VERSION ${LCMS2_VERSION_MATCH}) if(NOT LCMS2_FIND_QUIETLY) string(SUBSTRING ${LCMS2_VERSION} 0 1 LCMS2_MAJOR_VERSION) string(SUBSTRING ${LCMS2_VERSION} 1 2 LCMS2_MINOR_VERSION) message(STATUS "Found lcms version ${LCMS2_MAJOR_VERSION}.${LCMS2_MINOR_VERSION}, ${LCMS2_LIBRARIES}") endif() else() if(NOT LCMS2_FIND_QUIETLY) message(STATUS "Found lcms2 but failed to find version ${LCMS2_LIBRARIES}") endif() set(LCMS2_VERSION NOTFOUND) endif() else() if(NOT LCMS2_FIND_QUIETLY) if(LCMS2_FIND_REQUIRED) message(FATAL_ERROR "Required package lcms2 NOT found") else() message(STATUS "lcms2 NOT found") endif() endif() endif() mark_as_advanced(LCMS2_INCLUDE_DIR LCMS2_LIBRARIES LCMS2_VERSION) - diff --git a/devdoc/CONTRIBUTING.md b/devdoc/CONTRIBUTING.md index ffdb7f7e..2efc8f27 100644 --- a/devdoc/CONTRIBUTING.md +++ b/devdoc/CONTRIBUTING.md @@ -1,46 +1,45 @@ # Intro Great to hear you want to contribute to Gwenview! Patches are always welcome. # Mailing list If you want to discuss development of Gwenview, you can subscribe to gwenview-devel mailing list: . # Bug tracker Gwenview bugs are tracked on KDE Bugzilla (). They are assigned to a fake user by default: `gwenview-bugs-null@kde.org`. To get notified when new bugs are filed, add this user to the list of users you follow. You can do so from Bugzilla by editing your user preferences, then go in the "Email Preferences" tab () # Code review Patches should be sent for review on . You will get faster answers by posting them there rather than attaching them to a Bugzilla bug report. # Commits for stable branch Commits to stable branch should be made to the stable branch first, then merged back to master. Here is an example work-flow. First fix something in KDE/4.x: git checkout KDE/4.x # Fix something, get it reviewed git commit git push Now merge the commit in master. Note the use of `--no-ff` in `git merge`. This is required to make it easy to rollback the merge if need be. git checkout master git merge --no-ff origin/KDE/4.x # Check merge is correct git push - diff --git a/doc/index.docbook b/doc/index.docbook index d3266b8d..4509f67f 100644 --- a/doc/index.docbook +++ b/doc/index.docbook @@ -1,713 +1,713 @@ Gwenview"> ]> Gwenview User Manual Aurélien Gâteau
agateau@kde.org
ChristopherMartin
chrsmrtn@debian.org
Henry de Valence
hdevalence@gmail.com
2005 Aurélien Gâteau 2008 Henry de Valence &FDLNotice; 2014-03-06 4.13 (&kde; 4.13) &kappname; is an image viewer for &kde;. KDE image viewer artist photo picture
- Introduction What is &kappname; &kappname; is a fast and easy to use image and video viewer for KDE. - -Gwenview features two main modes: Browse and View. Both modes can be used + +Gwenview features two main modes: Browse and View. Both modes can be used in a normal application window and Full Screen. Browse Mode lets you navigate through your computer showing thumbnails of your images, View Mode lets you view images one at a time, and Full Screen lets you make quick slideshows. There is also a start screen that displays a list of recently opened folders and &URL;s as well as your places and tags. Image loading is handled by the Qt library, so &kappname; supports all image formats your Qt installation supports. &kappname; correctly displays images with an alpha channel (transparency) as well as animations. &kappname; supports the displaying and editing of EXIF comments in JPEG images. Lossless JPEG transforms such as rotations and mirroring are also supported. &kappname; can read embedded color profiles from PNG and JPEG files. -It can use the image color profile together with the display color profile +It can use the image color profile together with the display color profile to output correct colors on the screen. The Interface Start Page The start page lists recently opened folders and &URL;s on the left side, and your places and tags on the right side. Start Page Screenshot Image Operations &kappname; has a few features which are available in both Browse, View, and Full Screen view. &kappname; has the capability to do basic alteration of your images. Rotate: A rotate operation will rotate the image either to the left or to the right, depending on whether you do &Ctrl;R Edit Rotate Right or &Ctrl;L Edit Rotate Left Edit Mirror : This operation will reflect the image along the vertical axis, as if you were seeing it in a mirror. Edit Flip : This operation will reflect the image upside-down (a reflection along the horizontal axis) &Shift;R Edit Resize : This operation will allow you to shrink or expand the image. Note that if you increase the size of an image, it may appear blurry and/or pixelated. These actions are also available on the Operations tab of the sidebar. If you have edited one or more images a bar with additional actions is displayed at the top of the image. You can undo or redo your changes, go to the previous or next modified image and there are three options to save the changed images. Actions bar for modified images If you have installed the Kipi Plugins, a Plugins menu will be available that will allow you to perform many additional operations on your images. For more information, see the Kipi Plugins documentation. Browse Mode When in Browse Mode, you can easily navigate through your files and folders. The preview window shows thumbnails of the images in the current folder, as well as subfolders. Browse Mode Screenshot Moving the mouse over an image shows buttons to select or rotate the image as well as -a button to enter Fullscreen Mode. +a button to enter Fullscreen Mode. Modified images are indicated by an icon down right, click it to save the changed image. Clicking on an image takes you into View Mode. You may select multiple images and switch to View Mode to view them side-by-side. -The slider at the bottom right allows you to change the size of the images. +The slider at the bottom right allows you to change the size of the images. You can also filter the images by filename, date, tag or rating using the box on the lower left. The toolbar appears in both Browse mode as well as View mode and contains the most commonly used actions. Start Page: Open the start page. Browse: Switches to Browse Mode. View: Switches to View Mode. Full Screen: Switches to Full Screen Mode. Previous: Clicking this icon will go to the previous image in the folder. Next: Clicking this button will go to the next image in the folder. Rotate Left/Right: Same as discussed in Image Operations View Mode View Mode displays full-size images. The same sidebar available in Browse Mode is displayed on the left. At the bottom, there is the Thumbnail Bar, which allows you to scroll through the images in the current folder. The Thumbnail Bar can be minimized by clicking on the Thumbnail Bar button. Clicking again will restore it. To change the size of the thumbnails move the splitter with the &LMB;. View Mode supports viewing multiple images side-by-side. You may select multiple images in Browse Mode before switching to View Mode, or you may click the + button that appears when hovering over images in the Thumbnail Bar to add a pane displaying that image. A - will then appear that will permit you to remove its pane. When multiple images are displayed, a small toolbar appears below each image that permits you to delete the image or remove its pane. You may perform zoom operations independently for each image, or synchronize them. Toggle this by checking the Synchronize to the left of the zoom slider or by pressing &Ctrl;Y. You can switch images by clicking on their pane, or using your keyboard. To switch to the image on the right, press . To switch to the image on the left, press &Shift; . View Mode Screenshot The slider at the bottom right controls the zoom of the image. The Fit button and the 100% button are next to the zoom slider and are two preset zoom levels. The Fit button zooms the current image to fit the size of the window, and the 100% button zooms the image to the actual pixel size. The shortcut F toggles between both view modes. -When an image is in zoom-to-fit mode, you can go to the previous and next -image with the arrow keys. When you zoom in, arrow keys are used to scroll the image. +When an image is in zoom-to-fit mode, you can go to the previous and next +image with the arrow keys. When you zoom in, arrow keys are used to scroll the image. This is very similar to the behavior provided by phones or digital cameras. -When an image is zoomed in, a bird-eye view appears and lets you scroll -the image using the mouse and the arrow keys. The bird-eye view automatically hides +When an image is zoomed in, a bird-eye view appears and lets you scroll +the image using the mouse and the arrow keys. The bird-eye view automatically hides itself after a short delay, showing back only while zooming or scrolling. You can start directly in View mode by starting &kappname; from a context menu like Open With in another program or by launching it from the command line with an image as an argument. The following additional image operations are available only in View Mode: &Shift;C Edit Crop : This operation lets you discard parts of the image you don't want. You can access the advanced cropping parameters by ticking Advanced settings check box on the bottom popup pane. Use the corresponding fields to tune up the cropping operation. It is also possible to adjust the cropped area by dragging the gray square handles on the borders of the image. You can move the cropped area by clicking and holding the &LMB; and drag it with the mouse pointer. Press the Crop button to see the results when you are ready. Use the upper popup pane to save the results or undo/redo the operation. Edit Red Eye Reduction : This operation reduces the "red eye" effect commonly found in photographs taken with a flash camera. Full Screen Modes Access Full Screen by pressing the Full Screen button on the toolbar, or by &Ctrl;&Shift;F View Full Screen Mode . To leave this mode press the &Esc; key. Browse Mode Full Screen -In Browse Mode you can switch to fullscreen also by clicking on the button that +In Browse Mode you can switch to fullscreen also by clicking on the button that appears when you move the mouse over the thumbnails. Full Screen View Mode Screenshot -Going fullscreen while browsing gives you a more immersive experience while -you go through your pictures. It is quite nice on your regular computer, but makes -even more sense when you connect your laptop to the big TV in the living room to show +Going fullscreen while browsing gives you a more immersive experience while +you go through your pictures. It is quite nice on your regular computer, but makes +even more sense when you connect your laptop to the big TV in the living room to show pictures to your guests. View Mode Full Screen The full screen View Mode shows a slideshow of your images. Access Full Screen Mode by clicking on the button that appears when you move the mouse over the thumbnails in Browse Mode, by pressing the Full Screen button on the taskbar. Full Screen Browse Mode Screenshot The top bar will hide itself automatically; to show it simply move the mouse to the top of the screen. If the mouse cursor is over the top bar, it will not autohide. Most of the buttons on the bar are the same as the ones on the toolbar in Browse or View Modes, except for the Exit Full Screen Mode button which returns you to the &kappname; window, the Start/Stop Slideshow button, and the Configure Full Screen Mode button which shows a small settings dialog that allows you to easily and quickly configure the slideshow. The slideshow controls there are: The Interval slider controls how long &kappname; will show an image before it move to the next one. If the Loop check box is checked, when the end of the slideshow is reached, it will continue from the beginning instead of stopping. If the Random check box is checked, instead of progressing through the folder alphabetically, images will be shown in random order. Select Image Information to Display allows you to define what metadata is displayed under the buttons on the toolbar. If the Show thumbnails check box is checked, thumbnails for all images in the current folder will be displayed to the right of the toolbar. The Height slider changes the size of the thumbnails displayed. If enabled, an area that shows you the other images in the current folder will be shown on the top bar. Clicking on one will display it. Sidebar The sidebar on the left is available in the Browse and View modes, but does not appear by default in Browse Mode. Its appearance can be toggled using F4 View Sidebar -or using the ▮← / ▮→ button at the left side -of the statusbar. When clicked it collapses or expands the sidebar. +or using the ▮← / ▮→ button at the left side +of the statusbar. When clicked it collapses or expands the sidebar. The sidebar contains several tabs: Folders Displays a list of all folders on your system permitting you to switch between them. In Browse Mode thumbnails from the folder will be displayed, while in View Mode the first image in the folder will appear, from which you can browse through the folder using the Previous and Next buttons or shortcuts. Information Displays Meta Information like the filename and size. The More... link permits you to view all available metadata and select which data appear in the sidebar. Operations This permits you to perform the previously described global image operations as well as those specific to View Mode. It also permits common file operations like copying, renaming, deleting, and creating new folders. &kappname; Importer Introduction The &kappname; Importer allows you to import images from a digital camera or removable media. To launch it, select Download Photos with &kappname; in the &kde; Device Notifier after connecting a supported device. When you plug in a device the &kappname; importer recursively lists all images and videos. -This is not always what you expect, ⪚ plugging a smartphone you do not want to list all medias +This is not always what you expect, ⪚ plugging a smartphone you do not want to list all medias of the device; but only the pictures you took, which are usually in a special subfolder. Root Folder Picking -It is possible to select the root folder to list, and &kappname; Importer will remember the -last root folder for each device. This way, next time you plug a device in, only the relevant +It is possible to select the root folder to list, and &kappname; Importer will remember the +last root folder for each device. This way, next time you plug a device in, only the relevant pictures and videos should be listed. Importing Images &kappname; Importer Screenshot If you wish, you may select the images you want to import under Select the documents to import by pressing the + button that appears when hovering over an image. You may also select the folder that images are imported to in the text box at the bottom of the window. When you are done, click the Import Selected button to import only the images you have selected, or click Import All to import all images found on the device. Automatic Renaming &kappname; Importer can rename your files according to a specified pattern. To configure this, select the Settings in the lower left corner. You may turn this feature on or off using the check box at the top. The Rename Format supports several special parameters, which will be replaced by metadata such as the date the image was created or its filename. They are listed below the text box. You may either click on the parameter name to enter it into the text box or type one manually. Tips Using the mouse Panning with the mouse Holding down the left mouse button on an image allows you to scroll the image. The mouse wheel will scroll the image up and down. Zooming with the mouse Clicking the middle mouse button will toggle the auto zoom on/off. Hold down the &Ctrl; key, then either use the mouse wheel to zoom in and out or left click to zoom in and right click to zoom out. The mouse wheel, used while holding down the &Alt; key, will scroll the image horizontally. Browsing with the mouse When in Browse mode, clicking an image switches into View mode and shows that image. When in Browse mode, scrolling the mouse wheel will scroll up or down the thumbnail view area. If the Mouse Behavior option in SettingsConfigure &kappname; is set to Browse, scrolling the mouse wheel while in View Mode will move you through the images in the folder. Key bindings &kappname; comes with a range of keyboard shortcuts, all of which can be viewed and remapped by selecting SettingsConfigure Shortcuts.... Note that in the Files and Folders windows, all the normal KDE shortcuts are functional, unless otherwise remapped. A few of the most useful default bindings are: Space: Displays the next image in the directory. &Backspace;: Displays the previous image in the directory. &Alt;Up: Moves to the parent folder of the current folder. &Ctrl;&Shift;F: Switches into Full Screen Mode. &Esc;: Switches back to Browse Mode. &Ctrl;M: Show or hide the menubar. &Ctrl;B: Show or hide the Thumbnail bar. F4: Show or hide the Sidebar. F6: Make the Location bar editable so that you can directly type in a file path. You can return to the standard Location Bar by pressing the arrow at the right. &Ctrl;R: Rotate the current image to the right. &Ctrl;L: Rotate the current image to the left. &Shift;R: Resize the current image. &Shift;C: Crop the current image. &Ctrl;Y: When multiple images are displayed in View Mode, this synchronizes their views. : When multiple images are displayed in View Mode, this switched to the image to the right of the currently selected image. &Shift; : When multiple images are displayed in View Mode, this switched to the image to the left of the currently selected image. &Ctrl;S: Save any changes made to the image. Del: Move the current image to the trash. &Shift;Del: Permanently delete the image from the disk. Note that this operation is irreversible and cannot be undone. &Ctrl;P: Print the current image. &Ctrl;O: Open an image using the standard file selection dialog. F: Pressing this shortcut toggles zoom-to-fit on and off. P: Viewing a video this shortcut toggles playback on and off. &Ctrl;T: Edit tags. &Ctrl;F2: Rename an image inline. Del: Move an image to the trash. &Shift;Del: Delete an image. &Ctrl;F7: Copy an image. &Ctrl;F8: Move an image. &Ctrl;F9: Link an image. Advanced Configuration Options Some notes on hidden &kappname; options can be found on this page. The options described on the above-mentioned page may help you tune &kappname; for specific needs, but please keep in mind there is no warranty they will continue working from one version to another. Credits and Copyright &kappname; is currently maintained by Aurélien Gâteau This document was written by Christopher Martin This document was updated for &kde; 4 by Henry de Valence &underFDL; &underGPL;
diff --git a/lib/archiveutils.h b/lib/archiveutils.h index db797712..b1b047f4 100644 --- a/lib/archiveutils.h +++ b/lib/archiveutils.h @@ -1,61 +1,60 @@ // vim: set tabstop=4 shiftwidth=4 expandtab /* Gwenview - A simple image viewer for KDE Copyright 2000-2004 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 ARCHIVEUTILS_H #define ARCHIVEUTILS_H #include // Qt #include class KFileItem; namespace Gwenview { /** * Helper functions to deal with archives */ namespace ArchiveUtils { /** * Returns true if @p item is an archive */ GWENVIEWLIB_EXPORT bool fileItemIsArchive(const KFileItem& item); /** * Returns true if @p item is a dir or an archive */ GWENVIEWLIB_EXPORT bool fileItemIsDirOrArchive(const KFileItem& item); /** * Returns the protocol for an archive mime type. Similar to * KProtocolManager::protocolForArchiveMimetype(), except it tries parent * mimetypes if it can't find anything (useful for .cbz and co) */ GWENVIEWLIB_EXPORT QString protocolForMimeType(const QString& mimeType); } // namespace ArchiveUtils } // namespace Gwenview #endif - diff --git a/lib/cms/iccjpeg.c b/lib/cms/iccjpeg.c index 1f6c4b10..7cbec5e0 100644 --- a/lib/cms/iccjpeg.c +++ b/lib/cms/iccjpeg.c @@ -1,270 +1,270 @@ /* * Little cms * Copyright (C) 1998-2004 Marti Maria * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be included * in all copies or substantial portions of the Software. - * + * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. * * iccprofile.c * * This file provides code to read and write International Color Consortium * (ICC) device profiles embedded in JFIF JPEG image files. The ICC has * defined a standard format for including such data in JPEG "APP2" markers. * The code given here does not know anything about the internal structure * of the ICC profile data; it just knows how to put the profile data into * a JPEG file being written, or get it back out when reading. * * This code depends on new features added to the IJG JPEG library as of * IJG release 6b; it will not compile or work with older IJG versions. * * NOTE: this code would need surgery to work on 16-bit-int machines * with ICC profiles exceeding 64K bytes in size. If you need to do that, * change all the "unsigned int" variables to "INT32". You'll also need * to find a malloc() replacement that can allocate more than 64K. */ #include "iccjpeg.h" #include /* define malloc() */ /* * Since an ICC profile can be larger than the maximum size of a JPEG marker * (64K), we need provisions to split it into multiple markers. The format * defined by the ICC specifies one or more APP2 markers containing the * following data: * Identifying string ASCII "ICC_PROFILE\0" (12 bytes) * Marker sequence number 1 for first APP2, 2 for next, etc (1 byte) * Number of markers Total number of APP2's used (1 byte) * Profile data (remainder of APP2 data) * Decoders should use the marker sequence numbers to reassemble the profile, * rather than assuming that the APP2 markers appear in the correct sequence. */ #define ICC_MARKER (JPEG_APP0 + 2) /* JPEG marker code for ICC */ #define ICC_OVERHEAD_LEN 14 /* size of non-profile data in APP2 */ #define MAX_BYTES_IN_MARKER 65533 /* maximum data len of a JPEG marker */ #define MAX_DATA_BYTES_IN_MARKER (MAX_BYTES_IN_MARKER - ICC_OVERHEAD_LEN) /* * This routine writes the given ICC profile data into a JPEG file. * It *must* be called AFTER calling jpeg_start_compress() and BEFORE * the first call to jpeg_write_scanlines(). * (This ordering ensures that the APP2 marker(s) will appear after the * SOI and JFIF or Adobe markers, but before all else.) */ void write_icc_profile (j_compress_ptr cinfo, const JOCTET *icc_data_ptr, unsigned int icc_data_len) { unsigned int num_markers; /* total number of markers we'll write */ int cur_marker = 1; /* per spec, counting starts at 1 */ unsigned int length; /* number of bytes to write in this marker */ /* Calculate the number of markers we'll need, rounding up of course */ num_markers = icc_data_len / MAX_DATA_BYTES_IN_MARKER; if (num_markers * MAX_DATA_BYTES_IN_MARKER != icc_data_len) num_markers++; while (icc_data_len > 0) { /* length of profile to put in this marker */ length = icc_data_len; if (length > MAX_DATA_BYTES_IN_MARKER) length = MAX_DATA_BYTES_IN_MARKER; icc_data_len -= length; /* Write the JPEG marker header (APP2 code and marker length) */ jpeg_write_m_header(cinfo, ICC_MARKER, (unsigned int) (length + ICC_OVERHEAD_LEN)); /* Write the marker identifying string "ICC_PROFILE" (null-terminated). * We code it in this less-than-transparent way so that the code works * even if the local character set is not ASCII. */ jpeg_write_m_byte(cinfo, 0x49); jpeg_write_m_byte(cinfo, 0x43); jpeg_write_m_byte(cinfo, 0x43); jpeg_write_m_byte(cinfo, 0x5F); jpeg_write_m_byte(cinfo, 0x50); jpeg_write_m_byte(cinfo, 0x52); jpeg_write_m_byte(cinfo, 0x4F); jpeg_write_m_byte(cinfo, 0x46); jpeg_write_m_byte(cinfo, 0x49); jpeg_write_m_byte(cinfo, 0x4C); jpeg_write_m_byte(cinfo, 0x45); jpeg_write_m_byte(cinfo, 0x0); /* Add the sequencing info */ jpeg_write_m_byte(cinfo, cur_marker); jpeg_write_m_byte(cinfo, (int) num_markers); /* Add the profile data */ while (length--) { jpeg_write_m_byte(cinfo, *icc_data_ptr); icc_data_ptr++; } cur_marker++; } } /* * Prepare for reading an ICC profile */ void setup_read_icc_profile (j_decompress_ptr cinfo) { /* Tell the library to keep any APP2 data it may find */ jpeg_save_markers(cinfo, ICC_MARKER, 0xFFFF); } /* * Handy subroutine to test whether a saved marker is an ICC profile marker. */ static boolean marker_is_icc (jpeg_saved_marker_ptr marker) { return marker->marker == ICC_MARKER && marker->data_length >= ICC_OVERHEAD_LEN && /* verify the identifying string */ GETJOCTET(marker->data[0]) == 0x49 && GETJOCTET(marker->data[1]) == 0x43 && GETJOCTET(marker->data[2]) == 0x43 && GETJOCTET(marker->data[3]) == 0x5F && GETJOCTET(marker->data[4]) == 0x50 && GETJOCTET(marker->data[5]) == 0x52 && GETJOCTET(marker->data[6]) == 0x4F && GETJOCTET(marker->data[7]) == 0x46 && GETJOCTET(marker->data[8]) == 0x49 && GETJOCTET(marker->data[9]) == 0x4C && GETJOCTET(marker->data[10]) == 0x45 && GETJOCTET(marker->data[11]) == 0x0; } /* * See if there was an ICC profile in the JPEG file being read; * if so, reassemble and return the profile data. * * TRUE is returned if an ICC profile was found, FALSE if not. * If TRUE is returned, *icc_data_ptr is set to point to the * returned data, and *icc_data_len is set to its length. * * IMPORTANT: the data at **icc_data_ptr has been allocated with malloc() * and must be freed by the caller with free() when the caller no longer * needs it. (Alternatively, we could write this routine to use the * IJG library's memory allocator, so that the data would be freed implicitly * at jpeg_finish_decompress() time. But it seems likely that many apps * will prefer to have the data stick around after decompression finishes.) * * NOTE: if the file contains invalid ICC APP2 markers, we just silently * return FALSE. You might want to issue an error message instead. */ boolean read_icc_profile (j_decompress_ptr cinfo, JOCTET **icc_data_ptr, unsigned int *icc_data_len) { jpeg_saved_marker_ptr marker; int num_markers = 0; int seq_no; JOCTET *icc_data; unsigned int total_length; #define MAX_SEQ_NO 255 /* sufficient since marker numbers are bytes */ char marker_present[MAX_SEQ_NO+1]; /* 1 if marker found */ unsigned int data_length[MAX_SEQ_NO+1]; /* size of profile data in marker */ unsigned int data_offset[MAX_SEQ_NO+1]; /* offset for data in marker */ *icc_data_ptr = NULL; /* avoid confusion if FALSE return */ *icc_data_len = 0; /* This first pass over the saved markers discovers whether there are * any ICC markers and verifies the consistency of the marker numbering. */ for (seq_no = 1; seq_no <= MAX_SEQ_NO; seq_no++) marker_present[seq_no] = 0; for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) { if (marker_is_icc(marker)) { if (num_markers == 0) num_markers = GETJOCTET(marker->data[13]); else if (num_markers != GETJOCTET(marker->data[13])) return FALSE; /* inconsistent num_markers fields */ seq_no = GETJOCTET(marker->data[12]); if (seq_no <= 0 || seq_no > num_markers) return FALSE; /* bogus sequence number */ if (marker_present[seq_no]) return FALSE; /* duplicate sequence numbers */ marker_present[seq_no] = 1; data_length[seq_no] = marker->data_length - ICC_OVERHEAD_LEN; } } if (num_markers == 0) return FALSE; /* Check for missing markers, count total space needed, * compute offset of each marker's part of the data. */ total_length = 0; for (seq_no = 1; seq_no <= num_markers; seq_no++) { if (marker_present[seq_no] == 0) return FALSE; /* missing sequence number */ data_offset[seq_no] = total_length; total_length += data_length[seq_no]; } if (total_length <= 0) return FALSE; /* found only empty markers? */ /* Allocate space for assembled data */ icc_data = (JOCTET *) malloc(total_length * sizeof(JOCTET)); if (icc_data == NULL) return FALSE; /* oops, out of memory */ /* and fill it in */ for (marker = cinfo->marker_list; marker != NULL; marker = marker->next) { if (marker_is_icc(marker)) { JOCTET FAR *src_ptr; JOCTET *dst_ptr; unsigned int length; seq_no = GETJOCTET(marker->data[12]); dst_ptr = icc_data + data_offset[seq_no]; src_ptr = marker->data + ICC_OVERHEAD_LEN; length = data_length[seq_no]; while (length--) { *dst_ptr++ = *src_ptr++; } } } *icc_data_ptr = icc_data; *icc_data_len = total_length; return TRUE; } diff --git a/lib/documentview/documentview.cpp b/lib/documentview/documentview.cpp index 1ab8bbb1..0b8d2013 100644 --- a/lib/documentview/documentview.cpp +++ b/lib/documentview/documentview.cpp @@ -1,773 +1,777 @@ // 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 "documentview.moc" // Qt #include #include #include #include #include #include #include #include #include #include #include #include #include // KDE #include #include #include #include // Local #include #include #include #include #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 static const qreal REAL_DELTA = 0.001; static const qreal MAXIMUM_ZOOM_VALUE = qreal(DocumentView::MaximumZoom); static const int COMPARE_MARGIN = 4; const int DocumentView::MaximumZoom = 16; const int DocumentView::AnimDuration = 250; struct DocumentViewPrivate { DocumentView* q; int mSortKey; // Used to sort views when displayed in compare mode HudWidget* mHud; BirdEyeView* mBirdEyeView; QWeakPointer mMoveAnimation; QWeakPointer mFadeAnimation; LoadingIndicator* mLoadingIndicator; QScopedPointer mAdapter; QList mZoomSnapValues; Document::Ptr mDocument; DocumentView::Setup mSetup; bool mCurrent; bool mCompareMode; bool mEraseBorders; void setCurrentAdapter(AbstractDocumentViewAdapter* adapter) { Q_ASSERT(adapter); mAdapter.reset(adapter); adapter->widget()->setParentItem(q); resizeAdapterWidget(); if (adapter->canZoom()) { QObject::connect(adapter, SIGNAL(zoomChanged(qreal)), q, SLOT(slotZoomChanged(qreal))); QObject::connect(adapter, SIGNAL(zoomInRequested(QPointF)), q, SLOT(zoomIn(QPointF))); QObject::connect(adapter, SIGNAL(zoomOutRequested(QPointF)), q, SLOT(zoomOut(QPointF))); QObject::connect(adapter, SIGNAL(zoomToFitChanged(bool)), q, SIGNAL(zoomToFitChanged(bool))); } QObject::connect(adapter, SIGNAL(scrollPosChanged()), q, SIGNAL(positionChanged())); QObject::connect(adapter, SIGNAL(previousImageRequested()), q, SIGNAL(previousImageRequested())); QObject::connect(adapter, SIGNAL(nextImageRequested()), q, SIGNAL(nextImageRequested())); QObject::connect(adapter, SIGNAL(toggleFullScreenRequested()), q, SIGNAL(toggleFullScreenRequested())); QObject::connect(adapter, SIGNAL(completed()), q, SLOT(slotCompleted())); adapter->loadConfig(); adapter->widget()->installSceneEventFilter(q); if (mCurrent) { adapter->widget()->setFocus(); } if (mSetup.valid && adapter->canZoom()) { adapter->setZoomToFit(mSetup.zoomToFit); if (!mSetup.zoomToFit) { adapter->setZoom(mSetup.zoom); adapter->setScrollPos(mSetup.position); } } q->adapterChanged(); q->positionChanged(); if (adapter->canZoom()) { - q->zoomToFitChanged(adapter->zoomToFit()); + if (adapter->zoomToFit()) { + q->zoomToFitChanged(true); + } else { + q->zoomChanged(adapter->zoom()); + } } if (adapter->rasterImageView()) { QObject::connect(adapter->rasterImageView(), SIGNAL(currentToolChanged(AbstractRasterImageViewTool*)), q, SIGNAL(currentToolChanged(AbstractRasterImageViewTool*))); } } void setupLoadingIndicator() { mLoadingIndicator = new LoadingIndicator(q); GraphicsWidgetFloater* floater = new GraphicsWidgetFloater(q); floater->setChildWidget(mLoadingIndicator); } HudButton* createHudButton(const QString& text, const char* iconName, bool showText) { HudButton* button = new HudButton; if (showText) { button->setText(text); } else { button->setToolTip(text); } button->setIcon(KIcon(iconName)); return button; } void setupHud() { HudButton* trashButton = createHudButton(i18nc("@info:tooltip", "Trash"), "user-trash", false); HudButton* deselectButton = createHudButton(i18nc("@action:button", "Deselect"), "list-remove", true); QGraphicsWidget* content = new QGraphicsWidget; QGraphicsLinearLayout* layout = new QGraphicsLinearLayout(content); layout->addItem(trashButton); layout->addItem(deselectButton); mHud = new HudWidget(q); mHud->init(content, HudWidget::OptionNone); GraphicsWidgetFloater* floater = new GraphicsWidgetFloater(q); floater->setChildWidget(mHud); floater->setAlignment(Qt::AlignBottom | Qt::AlignHCenter); QObject::connect(trashButton, SIGNAL(clicked()), q, SLOT(emitHudTrashClicked())); QObject::connect(deselectButton, SIGNAL(clicked()), q, SLOT(emitHudDeselectClicked())); mHud->hide(); } void setupBirdEyeView() { if (mBirdEyeView) { delete mBirdEyeView; } mBirdEyeView = new BirdEyeView(q); mBirdEyeView->setZValue(1); } void updateCaption() { QString caption; Document::Ptr doc = mAdapter->document(); if (!doc) { emit q->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 q->captionUpdateRequested(caption); } void uncheckZoomToFit() { if (mAdapter->zoomToFit()) { mAdapter->setZoomToFit(false); } } void setZoom(qreal zoom, const QPointF& center = QPointF(-1, -1)) { uncheckZoomToFit(); zoom = qBound(q->minimumZoom(), zoom, MAXIMUM_ZOOM_VALUE); mAdapter->setZoom(zoom, center); } void updateZoomSnapValues() { qreal min = q->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; } q->minimumZoomChanged(min); } void showLoadingIndicator() { if (!mLoadingIndicator) { setupLoadingIndicator(); } mLoadingIndicator->show(); mLoadingIndicator->setZValue(1); } void hideLoadingIndicator() { if (!mLoadingIndicator) { return; } mLoadingIndicator->hide(); } void resizeAdapterWidget() { QRectF rect = QRectF(QPointF(0, 0), q->boundingRect().size()); if (mCompareMode) { rect.adjust(COMPARE_MARGIN, COMPARE_MARGIN, -COMPARE_MARGIN, -COMPARE_MARGIN); } mAdapter->widget()->setGeometry(rect); } void fadeTo(qreal value) { if (mFadeAnimation.data()) { qreal endValue = mFadeAnimation.data()->endValue().toReal(); if (qFuzzyCompare(value, endValue)) { // Same end value, don't change the actual animation return; } } // Create a new fade animation QPropertyAnimation* anim = new QPropertyAnimation(q, "opacity"); anim->setStartValue(q->opacity()); anim->setEndValue(value); if (qFuzzyCompare(value, 1)) { QObject::connect(anim, SIGNAL(finished()), q, SLOT(slotFadeInFinished())); } QObject::connect(anim, SIGNAL(finished()), q, SIGNAL(isAnimatedChanged())); anim->setDuration(DocumentView::AnimDuration); mFadeAnimation = anim; q->isAnimatedChanged(); anim->start(QAbstractAnimation::DeleteWhenStopped); } }; DocumentView::DocumentView(QGraphicsScene* scene) : d(new DocumentViewPrivate) { setFlag(ItemIsFocusable); setFlag(ItemIsSelectable); setFlag(ItemClipsChildrenToShape); d->q = this; d->mLoadingIndicator = 0; d->mBirdEyeView = 0; d->mCurrent = false; d->mCompareMode = false; d->mEraseBorders = false; setOpacity(0); scene->addItem(this); d->setupHud(); d->setCurrentAdapter(new EmptyAdapter); } 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 RasterImageViewAdapter; 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, const DocumentView::Setup& setup) { if (d->mDocument) { if (url == d->mDocument->url()) { return; } disconnect(d->mDocument.data(), 0, this, 0); } d->mSetup = setup; 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 { QMetaObject::invokeMethod(this, "finishOpenUrl", Qt::QueuedConnection); } d->setupBirdEyeView(); } void DocumentView::finishOpenUrl() { disconnect(d->mDocument.data(), SIGNAL(kindDetermined(KUrl)), this, SLOT(finishOpenUrl())); GV_RETURN_IF_FAIL(d->mDocument->loadingState() >= Document::KindDetermined); if (d->mDocument->loadingState() == Document::LoadingFailed) { slotLoadingFailed(); return; } createAdapterForDocument(); connect(d->mDocument.data(), SIGNAL(loadingFailed(KUrl)), SLOT(slotLoadingFailed())); d->mAdapter->setDocument(d->mDocument); d->updateCaption(); } void DocumentView::loadAdapterConfig() { d->mAdapter->loadConfig(); } RasterImageView* DocumentView::imageView() const { return d->mAdapter->rasterImageView(); } void DocumentView::slotCompleted() { d->hideLoadingIndicator(); d->updateCaption(); d->updateZoomSnapValues(); if (!d->mAdapter->zoomToFit()) { qreal min = minimumZoom(); if (d->mAdapter->zoom() < min) { d->mAdapter->setZoom(min); } } emit completed(); } DocumentView::Setup DocumentView::setup() const { Setup setup; if (d->mAdapter->canZoom()) { setup.valid = true; setup.zoomToFit = zoomToFit(); if (!setup.zoomToFit) { setup.zoom = zoom(); setup.position = position(); } } return setup; } 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::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 (GwenviewConfig::mouseWheelBehavior() == MouseWheelBehavior::Browse && event->modifiers() == Qt::NoModifier) { // Browse with mouse wheel if (event->delta() > 0) { previousImageRequested(); } else { nextImageRequested(); } return; } // Scroll qreal dx = 0; // 16 = pixels for one line // 120: see QWheelEvent::delta() doc qreal dy = -qApp->wheelScrollLines() * 16 * event->delta() / 120; if (event->orientation() == Qt::Horizontal) { qSwap(dx, dy); } d->mAdapter->setScrollPos(d->mAdapter->scrollPos() + QPointF(dx, dy)); } 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*/) { QRectF visibleRect = mapRectFromItem(d->mAdapter->widget(), d->mAdapter->visibleDocumentRect()); if (d->mEraseBorders) { QRegion borders = QRegion(boundingRect().toRect()) - QRegion(visibleRect.toRect()); Q_FOREACH(const QRect& rect, borders.rects()) { painter->eraseRect(rect); } } if (d->mCompareMode && d->mCurrent) { painter->save(); painter->setBrush(Qt::NoBrush); painter->setPen(QPen(palette().highlight().color(), 2)); painter->setRenderHint(QPainter::Antialiasing); QRectF selectionRect = visibleRect.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 qBound(qreal(0.001), d->mAdapter->computeZoomToFit(), qreal(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 { return d->mAdapter->scrollPos().toPoint(); } void DocumentView::setPosition(const QPoint& pos) { d->mAdapter->setScrollPos(pos); } 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(); if (d->mBirdEyeView) { d->mBirdEyeView->slotZoomOrSizeChanged(); } } 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); anim->setDuration(DocumentView::AnimDuration); connect(anim, SIGNAL(finished()), SIGNAL(isAnimatedChanged())); d->mMoveAnimation = anim; isAnimatedChanged(); anim->start(QAbstractAnimation::DeleteWhenStopped); } QPropertyAnimation* DocumentView::fadeIn() { d->fadeTo(1); return d->mFadeAnimation.data(); } void DocumentView::fadeOut() { d->fadeTo(0); } void DocumentView::slotFadeInFinished() { fadeInFinished(this); } bool DocumentView::isAnimated() const { return d->mMoveAnimation || d->mFadeAnimation; } bool DocumentView::sceneEventFilter(QGraphicsItem*, QEvent* event) { if (event->type() == QEvent::GraphicsSceneMousePress) { QMetaObject::invokeMethod(this, "emitFocused", Qt::QueuedConnection); } else if (event->type() == QEvent::GraphicsSceneHoverMove) { if (d->mBirdEyeView) { d->mBirdEyeView->onMouseMoved(); } } return false; } AbstractRasterImageViewTool* DocumentView::currentTool() const { return imageView() ? imageView()->currentTool() : 0; } int DocumentView::sortKey() const { return d->mSortKey; } void DocumentView::setSortKey(int sortKey) { d->mSortKey = sortKey; } void DocumentView::setEraseBorders(bool value) { d->mEraseBorders = value; } void DocumentView::hideAndDeleteLater() { hide(); deleteLater(); } } // namespace diff --git a/lib/gwenviewconfig.kcfg b/lib/gwenviewconfig.kcfg index 92d39f77..96925df2 100644 --- a/lib/gwenviewconfig.kcfg +++ b/lib/gwenviewconfig.kcfg @@ -1,254 +1,264 @@ lib/sorting.h + lib/zoommode.h lib/mousewheelbehavior.h lib/documentview/documentview.h lib/documentview/rasterimageview.h lib/print/printoptionspage.h General.Name,General.ImageSize,Exif.Photo.ExposureTime,Exif.Photo.Flash 100 true 0.5 The percentage of memory used by Gwenview before it warns the user and suggest saving changes. new A list of filename extensions Gwenview should not try to load. We exclude *.new as well because this is the extension used for temporary files by KSaveFile. false Horizontal 1 false false information General.Name,Exif.Image.DateTime 75 true RasterImageView::AlphaBackgroundCheckBoard #ffffff MouseWheelBehavior::Scroll false true 350, 100 DocumentView::SoftwareAnimation - - false - - - - false - Defines what happens when going to image B after having zoomed an area of image A. - When true: zoom and position is kept. When false: image B is zoomed out to fit the screen. + + + + + + + ZoomMode::Autofit + Defines what happens when going to image B after + having zoomed in on an area of image A. If set to Autofit, + image B is zoomed out to fit the screen. If set to KeepSame, + all images share the same zoom and position: image B is set + to the same zoom parameters as image A (and if these are + changed, image A will then be displayed with the updated zoom + and position). If set to Individual, all images remember + their own zoom and position: image B is initially set to the + same zoom parameters as image A, but will then remember its + own zoom and position (if these are changed, image A will NOT + be displayed with the updated zoom and position). 128 3./2. false Sorting::Name 1 true Qt::AlignHCenter | Qt::AlignVCenter PrintOptionsPage::ScaleToPage false 15.0 10.0 PrintOptionsPage::Centimeters true false true false false 5.0 24 false - diff --git a/lib/zoommode.h b/lib/zoommode.h new file mode 100644 index 00000000..ed9cbba7 --- /dev/null +++ b/lib/zoommode.h @@ -0,0 +1,49 @@ +// vim: set tabstop=4 shiftwidth=4 expandtab: +/* +Gwenview: an image viewer +Copyright 2008 Aurélien Gâteau +Copyright 2014 John Zaitseff + +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 ZOOMMODE_H +#define ZOOMMODE_H + +// Qt + +// KDE + +// Local + +namespace Gwenview +{ + +namespace ZoomMode +{ +/** + * This enum represents the different zoom modes. + */ +enum Enum { + Autofit, + KeepSame, + Individual +}; + +} // namespace ZoomMode + +} // namespace Gwenview + +#endif /* ZOOMMODE_H */ diff --git a/lib/zoomwidget.cpp b/lib/zoomwidget.cpp index da66a56f..7ca8aa65 100644 --- a/lib/zoomwidget.cpp +++ b/lib/zoomwidget.cpp @@ -1,213 +1,184 @@ // 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 "zoomwidget.moc" // stdc++ #include // Qt #include #include #include #include #include // KDE #include #include // Local #include "zoomslider.h" #include "signalblocker.h" #include "statusbartoolbutton.h" namespace Gwenview { static const qreal MAGIC_K = 1.04; static const qreal MAGIC_OFFSET = 16.; static const qreal PRECISION = 100.; inline int sliderValueForZoom(qreal zoom) { return int(PRECISION * (log(zoom) / log(MAGIC_K) + MAGIC_OFFSET)); } inline qreal zoomForSliderValue(int sliderValue) { return pow(MAGIC_K, sliderValue / PRECISION - MAGIC_OFFSET); } struct ZoomWidgetPrivate { ZoomWidget* q; StatusBarToolButton* mZoomToFitButton; StatusBarToolButton* mActualSizeButton; QLabel* mZoomLabel; ZoomSlider* mZoomSlider; QAction* mZoomToFitAction; QAction* mActualSizeAction; - QToolButton* mLockZoomButton; - bool mZoomUpdatedBySlider; void emitZoomChanged() { // Use QSlider::sliderPosition(), not QSlider::value() because when we are // called from slotZoomSliderActionTriggered(), QSlider::value() has not // been updated yet. qreal zoom = zoomForSliderValue(mZoomSlider->slider()->sliderPosition()); mZoomUpdatedBySlider = true; emit q->zoomChanged(zoom); mZoomUpdatedBySlider = false; } }; ZoomWidget::ZoomWidget(QWidget* parent) : QFrame(parent) , d(new ZoomWidgetPrivate) { d->q = this; d->mZoomUpdatedBySlider = false; setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Minimum); d->mZoomToFitButton = new StatusBarToolButton; d->mActualSizeButton = new StatusBarToolButton; if (QApplication::isLeftToRight()) { d->mZoomToFitButton->setGroupPosition(StatusBarToolButton::GroupLeft); d->mActualSizeButton->setGroupPosition(StatusBarToolButton::GroupRight); } else { d->mActualSizeButton->setGroupPosition(StatusBarToolButton::GroupLeft); d->mZoomToFitButton->setGroupPosition(StatusBarToolButton::GroupRight); } d->mZoomLabel = new QLabel; d->mZoomLabel->setFixedWidth(d->mZoomLabel->fontMetrics().width(" 1000% ")); d->mZoomLabel->setAlignment(Qt::AlignCenter); d->mZoomSlider = new ZoomSlider; d->mZoomSlider->setMinimumWidth(150); d->mZoomSlider->slider()->setSingleStep(int(PRECISION)); d->mZoomSlider->slider()->setPageStep(3 * int(PRECISION)); connect(d->mZoomSlider->slider(), SIGNAL(actionTriggered(int)), SLOT(slotZoomSliderActionTriggered())); - d->mLockZoomButton = new QToolButton; - d->mLockZoomButton->setAutoRaise(true); - d->mLockZoomButton->setCheckable(true); - updateLockZoomButton(); - connect(d->mLockZoomButton, SIGNAL(toggled(bool)), SLOT(updateLockZoomButton())); - // Layout QHBoxLayout* layout = new QHBoxLayout(this); layout->setMargin(0); layout->setSpacing(0); layout->addWidget(d->mZoomToFitButton); layout->addWidget(d->mActualSizeButton); layout->addWidget(d->mZoomSlider); layout->addWidget(d->mZoomLabel); - layout->addWidget(d->mLockZoomButton); } ZoomWidget::~ZoomWidget() { delete d; } void ZoomWidget::setActions(QAction* zoomToFitAction, QAction* actualSizeAction, QAction* zoomInAction, QAction* zoomOutAction) { d->mZoomToFitAction = zoomToFitAction; d->mActualSizeAction = actualSizeAction; d->mZoomToFitButton->setDefaultAction(zoomToFitAction); d->mActualSizeButton->setDefaultAction(actualSizeAction); d->mZoomSlider->setZoomInAction(zoomInAction); d->mZoomSlider->setZoomOutAction(zoomOutAction); // Adjust sizes int width = qMax(d->mZoomToFitButton->sizeHint().width(), d->mActualSizeButton->sizeHint().width()); d->mZoomToFitButton->setFixedWidth(width); d->mActualSizeButton->setFixedWidth(width); } void ZoomWidget::slotZoomSliderActionTriggered() { // The slider value changed because of the user (not because of range // changes). In this case disable zoom and apply slider value. d->emitZoomChanged(); } void ZoomWidget::setZoom(qreal zoom) { int intZoom = qRound(zoom * 100); d->mZoomLabel->setText(QString("%1%").arg(intZoom)); // Don't change slider value if we come here because the slider change, // avoids choppy sliding scroll. if (!d->mZoomUpdatedBySlider) { QSlider* slider = d->mZoomSlider->slider(); SignalBlocker blocker(slider); int value = sliderValueForZoom(zoom); if (value < slider->minimum()) { // It is possible that we are called *before* setMinimumZoom() as // been called. In this case, define the minimum ourself. d->mZoomSlider->setMinimum(value); } d->mZoomSlider->setValue(value); } } void ZoomWidget::setMinimumZoom(qreal minimumZoom) { d->mZoomSlider->setMinimum(sliderValueForZoom(minimumZoom)); } void ZoomWidget::setMaximumZoom(qreal zoom) { d->mZoomSlider->setMaximum(sliderValueForZoom(zoom)); } -bool ZoomWidget::isZoomLocked() const -{ - return d->mLockZoomButton->isVisible() && d->mLockZoomButton->isChecked(); -} - -void ZoomWidget::setZoomLocked(bool locked) -{ - d->mLockZoomButton->setChecked(locked); -} - -void ZoomWidget::setLockZoomButtonVisible(bool visible) -{ - d->mLockZoomButton->setVisible(visible); -} - -void ZoomWidget::updateLockZoomButton() -{ - d->mLockZoomButton->setIcon(KIcon(d->mLockZoomButton->isChecked() ? "object-locked" : "object-unlocked")); -} - } // namespace diff --git a/lib/zoomwidget.h b/lib/zoomwidget.h index b181f5ac..d5de70b9 100644 --- a/lib/zoomwidget.h +++ b/lib/zoomwidget.h @@ -1,73 +1,67 @@ // 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. */ #ifndef ZOOMWIDGET_H #define ZOOMWIDGET_H #include // Qt #include // KDE // Local class QAction; namespace Gwenview { struct ZoomWidgetPrivate; class GWENVIEWLIB_EXPORT ZoomWidget : public QFrame { Q_OBJECT public: ZoomWidget(QWidget* parent = 0); ~ZoomWidget(); void setActions(QAction* zoomToFitAction, QAction* actualSizeAction, QAction* zoomInAction, QAction* zoomOutAction); - bool isZoomLocked() const; - void setZoomLocked(bool); - - void setLockZoomButtonVisible(bool); - public Q_SLOTS: void setZoom(qreal zoom); void setMinimumZoom(qreal zoom); void setMaximumZoom(qreal zoom); Q_SIGNALS: void zoomChanged(qreal); private Q_SLOTS: void slotZoomSliderActionTriggered(); - void updateLockZoomButton(); private: friend struct ZoomWidgetPrivate; ZoomWidgetPrivate* const d; }; } // namespace #endif /* ZOOMWIDGET_H */ diff --git a/part/gvpart.rc b/part/gvpart.rc index 35703a79..7af31d68 100644 --- a/part/gvpart.rc +++ b/part/gvpart.rc @@ -1,22 +1,21 @@ &File &View Main Toolbar -