summaryrefslogtreecommitdiffstats
path: root/src/gvcore/imageview.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/gvcore/imageview.cpp')
-rw-r--r--src/gvcore/imageview.cpp1469
1 files changed, 1469 insertions, 0 deletions
diff --git a/src/gvcore/imageview.cpp b/src/gvcore/imageview.cpp
new file mode 100644
index 0000000..f22b171
--- /dev/null
+++ b/src/gvcore/imageview.cpp
@@ -0,0 +1,1469 @@
+// vim:set tabstop=4 shiftwidth=4 noexpandtab:
+/*
+Gwenview - A simple image viewer for KDE
+Copyright 2000-2004 Aur�ien G�eau
+
+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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+
+*/
+
+#include "config.h"
+
+#include "imageview.moc"
+
+#include <assert.h>
+#include <math.h>
+
+// Qt
+#include <qcolor.h>
+#include <qcombobox.h>
+#include <qcursor.h>
+#include <qdatetime.h>
+#include <qevent.h>
+#include <qpainter.h>
+#include <qpixmap.h>
+#include <qlabel.h>
+#include <qtimer.h>
+#include <qvaluevector.h>
+
+// KDE
+#include <kaction.h>
+#include <kconfig.h>
+#include <kdebug.h>
+#include <kdeversion.h>
+#include <klocale.h>
+#include <kstandarddirs.h>
+#include <kstdaction.h>
+#include <kurldrag.h>
+#include <kapplication.h>
+
+// Local
+#include "document.h"
+#include "imageutils/imageutils.h"
+#include "bcgdialog.h"
+#include "busylevelmanager.h"
+#include "imageviewtools.h"
+#include "imageutils/croppedqimage.h"
+#include "imageviewconfig.h"
+
+namespace Gwenview {
+
+/*
+
+Coordinates:
+
+The image can be zoomed, can have a position offset, and additionally there is
+QScrollView's viewport. This means there are several coordinate systems.
+
+
+
+Let's start from simple things. Viewport ignored, zoom ignored:
+
+ A-----------------------------------
+ | |
+ | |
+ | B--------------------- |
+ | | | |
+ | | | |
+ | | | |
+ | | | |
+ | | | |
+ | | | |
+ | ---------------------C |
+ | |
+ | |
+ ------------------------------------
+
+
+The inner rectangle is the image, outer rectangle is the widget.
+A = [ 0, 0 ]
+B = [ mXOffset, mYOffset ]
+C = B + [ mDocument->width(), mDocument->height() ]
+
+
+
+The same, additionally the image is zoomed.
+
+A = [ 0, 0 ]
+B = [ mXOffset, mYOffset ]
+C = [ mZoom * mDocument->width(), mZoom * mDocument->height()) ]
+
+The groups of functions imageToWidget() and widgetToImage() do conversions
+between the image and widget coordinates, i.e. imageToWidget() accepts coordinates
+in the image (original,not zoomed,image's topleft corner is [0,0]) and returns
+coordinates in the picture above, widgetToImage() works the other way around.
+
+There's no bounds checking, so widgetToImage( A ) in the example above would
+return image coordinate with negative x,y.
+
+The widgetToImage() functions round the values (in order to have the conversion
+as approximate as possible). However when converting from widget to image and back
+this can result in the final rectangle being smaller than the original.
+The widgetToImageBounding() function converts from widget to image coordinates
+in a way which makes sure the reverse conversion will be at least as large
+as the original geometry.
+
+There are no conversion functions for only width/height, as their conversion
+depends on the position (because of the rounding etc.). For similar reasons
+conversions should not be done with the bottomright corner of a rectangle,
+but with the point next to it.
+
+
+
+For conversions from/to QScrollView's viewport, usually QScrollView methods should
+be used: contentsX(), contentsY(), contentsWidth(), contentsHeight(), visibleWidth(),
+visibleHeight(), contentsToViewport() and viewportToContents().
+
+*/
+
+const double MAX_ZOOM=16.0; // Same value as GIMP
+
+const int DEFAULT_MAX_REPAINT_SIZE = 10000;
+const int LIMIT_MAX_REPAINT_SIZE = 10000000;
+
+#ifndef HAVE_LROUND
+inline
+long int lround( double x ) {
+ return static_cast< long int >( x >= 0 ? x + 0.5 : x - 0.5 );
+}
+#endif
+
+
+struct ImageView::Private {
+ Document* mDocument;
+
+ Tools mTools;
+
+ ToolID mToolID;
+
+ // Offset to center images
+ int mXOffset, mYOffset;
+
+ // Zoom info
+ ZoomMode mZoomMode;
+ double mZoom;
+
+ // Gamma, brightness, contrast - multiplied by 100
+ int mGamma, mBrightness, mContrast;
+
+ // Our actions
+ QComboBox* mZoomCombo;
+ // We do not use KSelectAction because it's not possible to set the combo text
+ KWidgetAction* mZoomComboAction;
+ KToggleAction* mZoomToFit;
+ KToggleAction* mZoomToWidth;
+ KToggleAction* mZoomToHeight;
+ QValueVector<KToggleAction*> mZoomComboActions;
+ KAction* mZoomIn;
+ KAction* mZoomOut;
+ KAction* mResetZoom;
+ KToggleAction* mLockZoom;
+ KAction* mAdjustBCG;
+ KAction* mIncreaseGamma;
+ KAction* mDecreaseGamma;
+ KAction* mIncreaseBrightness;
+ KAction* mDecreaseBrightness;
+ KAction* mIncreaseContrast;
+ KAction* mDecreaseContrast;
+ KActionCollection* mActionCollection;
+ BCGDialog* mBCGDialog;
+
+ // Fullscreen stuff
+ bool mFullScreen;
+
+ // Object state info
+ bool mOperaLikePrevious; // Flag to avoid showing the popup menu on Opera like previous
+ double mZoomBeforeAuto;
+ int mXCenterBeforeAuto, mYCenterBeforeAuto;
+
+ QMap< long long, PendingPaint > mPendingPaints;
+ QRegion mPendingNormalRegion;
+ QRegion mPendingSmoothRegion;
+ int mPendingOperations;
+ QTimer mPendingPaintTimer;
+ bool mSmoothingSuspended;
+ QRegion mValidImageArea;
+
+ int imageToWidgetX( int x ) const {
+ if( mZoom == 1.0 ) return x + mXOffset;
+ return lround( x * mZoom ) + mXOffset;
+ }
+
+ int imageToWidgetY( int y ) const {
+ if( mZoom == 1.0 ) return y + mYOffset;
+ return lround( y * mZoom ) + mYOffset;
+ }
+
+ QPoint imageToWidget( const QPoint& p ) const {
+ return QPoint( imageToWidgetX( p.x()), imageToWidgetY( p.y()));
+ }
+
+ QRect imageToWidget( const QRect& r ) const {
+ return QRect( imageToWidget( r.topLeft()),
+ // don't use bottomright corner for conversion, but the one next to it
+ imageToWidget( r.bottomRight() + QPoint( 1, 1 )) - QPoint( 1, 1 ));
+ }
+
+ int widgetToImageX( int x ) const {
+ if( mZoom == 1.0 ) return x - mXOffset;
+ return lround( ( x - mXOffset ) / mZoom );
+ }
+
+ int widgetToImageY( int y ) const {
+ if( mZoom == 1.0 ) return y - mYOffset;
+ return lround( ( y - mYOffset ) / mZoom );
+ }
+
+ QPoint widgetToImage( const QPoint& p ) const {
+ return QPoint( widgetToImageX( p.x()), widgetToImageY( p.y()));
+ }
+
+ QRect widgetToImage( const QRect& r ) const {
+ return QRect( widgetToImage( r.topLeft()),
+ // don't use bottomright corner for conversion, but the one next to it
+ widgetToImage( r.bottomRight() + QPoint( 1, 1 )) - QPoint( 1, 1 ));
+ }
+
+ QRect widgetToImageBounding( const QRect& r, int extra ) const {
+ QRect ret = widgetToImage( r );
+ // make sure converting to image and back always returns QRect at least as large as 'r'
+ extra += mZoom == 1.0 ? 0 : int( ceil( 1 / mZoom ));
+ ret.addCoords( -extra, -extra, extra, extra );
+ return ret;
+ }
+
+ void initZoomCombo() {
+ mZoomCombo->clear();
+ for (QValueVector<KToggleAction*>::iterator it=mZoomComboActions.begin();
+ it!=mZoomComboActions.end();
+ ++it)
+ {
+ QString txt=(*it)->plainText();
+ mZoomCombo->insertItem(txt);
+ }
+
+ const double zoomValues[] = { 0.5, 1, 2 };
+ int nbValues=sizeof(zoomValues) / sizeof(double);
+ for (int pos=0; pos<nbValues; ++pos) {
+ QString txt=QString("%1%").arg( int(zoomValues[pos]*100) );
+ mZoomCombo->insertItem(txt);
+ }
+ }
+};
+
+
+inline bool doDelayedSmoothing() {
+ return ImageViewConfig::delayedSmoothing()
+ && ImageViewConfig::smoothAlgorithm()!=ImageUtils::SMOOTH_NONE;
+}
+
+
+class ImageView::EventFilter : public QObject {
+public:
+ EventFilter(ImageView* parent)
+ : QObject(parent) {}
+
+ bool eventFilter(QObject*, QEvent* event) {
+ switch (event->type()) {
+ case QEvent::KeyPress:
+ case QEvent::KeyRelease:
+ case QEvent::AccelOverride:
+ return static_cast< ImageView* >( parent())
+ ->viewportKeyEvent(static_cast<QKeyEvent*>(event));
+ default:
+ break;
+ }
+ return false;
+ }
+};
+
+
+
+ImageView::ImageView(QWidget* parent,Document* document, KActionCollection* actionCollection)
+: QScrollView(parent,0L,WResizeNoErase|WRepaintNoErase|WPaintClever)
+{
+ d=new Private;
+ d->mDocument=document;
+ d->mToolID=SCROLL;
+ d->mXOffset=0;
+ d->mYOffset=0;
+ d->mZoomMode=static_cast<ZoomMode>( ImageViewConfig::zoomMode() );
+ d->mZoom=1;
+ d->mActionCollection=actionCollection;
+ d->mFullScreen=false;
+ d->mOperaLikePrevious=false;
+ d->mZoomBeforeAuto=1;
+ d->mPendingOperations= 0 ;
+ d->mSmoothingSuspended= false ;
+ d->mGamma = 100;
+ d->mBrightness = 0;
+ d->mContrast = 100;
+ d->mBCGDialog = 0;
+
+ viewport()->setFocusPolicy(WheelFocus);
+ setFrameStyle(NoFrame);
+ setAcceptDrops( true );
+ viewport()->setAcceptDrops( true );
+
+ updateScrollBarMode();
+ viewport()->setBackgroundColor(ImageViewConfig::backgroundColor() );
+
+ d->mTools[SCROLL]=new ScrollTool(this);
+ d->mTools[ZOOM]=new ZoomTool(this);
+ d->mTools[d->mToolID]->updateCursor();
+
+ // Create actions
+ d->mZoomToFit=new KToggleAction(i18n("Fit to &Window"),"viewmagfit",0,d->mActionCollection,"view_zoom_to_fit");
+ connect(d->mZoomToFit,SIGNAL(toggled(bool)),
+ this,SLOT(setZoomToFit(bool)) );
+ d->mZoomToWidth=new KToggleAction(i18n("Fit to &Width"),0,0,d->mActionCollection,"view_zoom_to_width");
+ connect(d->mZoomToWidth,SIGNAL(toggled(bool)),
+ this,SLOT(setZoomToWidth(bool)) );
+ d->mZoomToHeight=new KToggleAction(i18n("Fit to &Height"),0,0,d->mActionCollection,"view_zoom_to_height");
+ connect(d->mZoomToHeight,SIGNAL(toggled(bool)),
+ this,SLOT(setZoomToHeight(bool)) );
+
+ d->mZoomIn=KStdAction::zoomIn(this,SLOT(slotZoomIn()),d->mActionCollection);
+
+ d->mZoomOut=KStdAction::zoomOut(this,SLOT(slotZoomOut()),d->mActionCollection);
+
+ d->mResetZoom=KStdAction::actualSize(this,SLOT(slotResetZoom()),d->mActionCollection);
+ d->mResetZoom->setIcon("viewmag1");
+
+ d->mLockZoom=new KToggleAction(i18n("&Lock Zoom"),"lock",0,d->mActionCollection,"view_zoom_lock");
+ d->mLockZoom->setChecked(ImageViewConfig::lockZoom());
+ connect(d->mLockZoom,SIGNAL(toggled(bool)),
+ this,SLOT(setLockZoom(bool)) );
+
+ d->mZoomCombo=new QComboBox(true);
+ // Avoid stealing focus
+ d->mZoomCombo->setFocusPolicy(ClickFocus);
+ connect(d->mZoomCombo, SIGNAL(activated(int)),
+ this, SLOT(slotSelectZoom()) );
+
+ d->mZoomComboAction=new KWidgetAction(d->mZoomCombo, i18n("Zoom"), 0, 0, 0, d->mActionCollection, "view_zoom_to");
+
+ d->mZoomComboActions.append(d->mZoomToFit);
+ d->mZoomComboActions.append(d->mZoomToWidth);
+ d->mZoomComboActions.append(d->mZoomToHeight);
+ if (d->mZoomMode!=ZOOM_FREE) {
+ d->mZoomComboActions[d->mZoomMode]->setChecked(true);
+ }
+ d->initZoomCombo();
+
+ d->mAdjustBCG=new KAction(i18n("Adjust Brightness/Contrast/Gamma"), "colorize", 0,
+ this, SLOT(showBCGDialog()), d->mActionCollection, "adjust_bcg");
+ d->mIncreaseGamma=new KAction(i18n("Increase Gamma"),0,CTRL+Key_G,
+ this,SLOT(increaseGamma()),d->mActionCollection,"increase_gamma");
+ d->mDecreaseGamma=new KAction(i18n("Decrease Gamma"),0,SHIFT+CTRL+Key_G,
+ this,SLOT(decreaseGamma()),d->mActionCollection,"decrease_gamma");
+ d->mIncreaseBrightness=new KAction(i18n("Increase Brightness" ),0,CTRL+Key_B,
+ this,SLOT(increaseBrightness()),d->mActionCollection,"increase_brightness");
+ d->mDecreaseBrightness=new KAction(i18n("Decrease Brightness" ),0,SHIFT+CTRL+Key_B,
+ this,SLOT(decreaseBrightness()),d->mActionCollection,"decrease_brightness");
+ d->mIncreaseContrast=new KAction(i18n("Increase Contrast" ),0,CTRL+Key_C,
+ this,SLOT(increaseContrast()),d->mActionCollection,"increase_contrast");
+ d->mDecreaseContrast=new KAction(i18n("Decrease Contrast" ),0,SHIFT+CTRL+Key_C,
+ this,SLOT(decreaseContrast()),d->mActionCollection,"decrease_contrast");
+
+ // Connect to some interesting signals
+ connect(d->mDocument,SIGNAL(loaded(const KURL&)),
+ this,SLOT(slotLoaded()) );
+
+ connect(d->mDocument,SIGNAL(loading()),
+ this,SLOT( loadingStarted()) );
+
+ connect(d->mDocument,SIGNAL(modified()),
+ this,SLOT(slotModified()) );
+
+ connect(d->mDocument, SIGNAL(sizeUpdated()),
+ this, SLOT(slotImageSizeUpdated()) );
+
+ connect(d->mDocument, SIGNAL(rectUpdated(const QRect&)),
+ this, SLOT(slotImageRectUpdated(const QRect&)) );
+
+ connect(&d->mPendingPaintTimer,SIGNAL(timeout()),
+ this,SLOT(checkPendingOperations()) );
+
+ connect(BusyLevelManager::instance(),SIGNAL(busyLevelChanged(BusyLevel)),
+ this,SLOT(slotBusyLevelChanged(BusyLevel) ));
+
+ // This event filter is here to make sure the pixmap view is aware of the changes
+ // in the keyboard modifiers, even if it isn't focused. However, making this widget
+ // itself the filter would lead to doubled paint events, because QScrollView
+ // installs an event filter on its viewport, and doesn't filter out the paint
+ // events -> it'd get it twice, first from app filter, second from viewport filter.
+ EventFilter* filter=new EventFilter(this);
+ kapp->installEventFilter(filter);
+}
+
+
+ImageView::~ImageView() {
+ ImageViewConfig::setZoomMode(d->mZoomMode);
+ ImageViewConfig::setLockZoom(d->mLockZoom->isChecked());
+ ImageViewConfig::self()->writeConfig();
+ delete d->mTools[SCROLL];
+ delete d->mTools[ZOOM];
+ delete d;
+}
+
+
+void ImageView::slotLoaded() {
+ if (d->mDocument->isNull()) {
+ resizeContents(0,0);
+ viewport()->repaint(false);
+ return;
+ }
+
+ if (doDelayedSmoothing()) scheduleOperation( SMOOTH_PASS );
+}
+
+
+void ImageView::slotModified() {
+ if (d->mZoomMode!=ZOOM_FREE) {
+ updateZoom(d->mZoomMode);
+ } else {
+ updateContentSize();
+ updateImageOffset();
+ updateZoomActions();
+ fullRepaint();
+ }
+}
+
+
+void ImageView::loadingStarted() {
+ cancelPending();
+ d->mSmoothingSuspended = true;
+ d->mValidImageArea = QRegion();
+ d->mGamma = 100;
+ d->mBrightness = 0;
+ d->mContrast = 100;
+
+ if (!d->mLockZoom->isChecked()) {
+ d->mZoomBeforeAuto = 1.;
+ }
+}
+
+//------------------------------------------------------------------------
+//
+// Properties
+//
+//------------------------------------------------------------------------
+double ImageView::zoom() const {
+ return d->mZoom;
+}
+
+
+bool ImageView::fullScreen() const {
+ return d->mFullScreen;
+}
+
+
+QPoint ImageView::offset() const {
+ return QPoint(d->mXOffset, d->mYOffset);
+}
+
+
+bool ImageView::canZoom(bool in) const {
+ KAction* zoomAction=in ? d->mZoomIn : d->mZoomOut;
+ return zoomAction->isEnabled();
+}
+
+
+KToggleAction* ImageView::zoomToFit() const {
+ return d->mZoomToFit;
+}
+
+
+void ImageView::updateFromSettings() {
+ // Reset, so that next repaint doesn't possibly take longer because of
+ // smoothing
+ ImageViewConfig::setMaxRepaintSize(DEFAULT_MAX_REPAINT_SIZE);
+ ImageViewConfig::setMaxScaleRepaintSize(DEFAULT_MAX_REPAINT_SIZE);
+ ImageViewConfig::setMaxSmoothRepaintSize(DEFAULT_MAX_REPAINT_SIZE);
+
+ if( doDelayedSmoothing() ) {
+ scheduleOperation( SMOOTH_PASS );
+ } else {
+ fullRepaint();
+ }
+
+ // If enlargeSmallImage changed
+ if (d->mZoomMode!=ZOOM_FREE) {
+ updateZoom(d->mZoomMode);
+ }
+
+ updateScrollBarMode();
+
+ if (!d->mFullScreen) {
+ viewport()->setBackgroundColor(ImageViewConfig::backgroundColor() );
+ }
+}
+
+
+void ImageView::setZoom(double zoom, int centerX, int centerY) {
+ updateZoom(ZOOM_FREE, zoom, centerX, centerY);
+}
+
+
+void ImageView::updateZoom(ZoomMode zoomMode, double value, int centerX, int centerY) {
+ ZoomMode oldZoomMode = d->mZoomMode;
+ double oldZoom=d->mZoom;
+ d->mZoomMode=zoomMode;
+ KAction* checkedZoomAction=0;
+
+ viewport()->setUpdatesEnabled(false);
+
+ if (zoomMode==ZOOM_FREE) {
+ Q_ASSERT(value!=0);
+ d->mZoom=value;
+ } else {
+ if (oldZoomMode == ZOOM_FREE) {
+ // Only store zoom before auto if we were in ZOOM_FREE mode, otherwise
+ // we will store the computed auto zoom value (Bug 134590)
+ d->mZoomBeforeAuto = d->mZoom;
+ }
+ d->mXCenterBeforeAuto=width()/2 + contentsX() + d->mXOffset;
+ d->mYCenterBeforeAuto=height()/2 + contentsY() + d->mYOffset;
+
+ if (zoomMode==ZOOM_FIT) {
+ d->mZoom=computeZoomToFit();
+ checkedZoomAction=d->mZoomToFit;
+
+ } else if (zoomMode==ZOOM_FIT_WIDTH) {
+ d->mZoom=computeZoomToWidth();
+ checkedZoomAction=d->mZoomToWidth;
+
+ } else {
+ d->mZoom=computeZoomToHeight();
+ checkedZoomAction=d->mZoomToHeight;
+ }
+ }
+
+ // Make sure only one zoom action is toggled on
+ d->mZoomToFit->setChecked( checkedZoomAction==d->mZoomToFit);
+ d->mZoomToWidth->setChecked( checkedZoomAction==d->mZoomToWidth);
+ d->mZoomToHeight->setChecked(checkedZoomAction==d->mZoomToHeight);
+
+ updateContentSize();
+
+ // Find the coordinate of the center of the image
+ // and center the view on it
+ if (centerX==-1) {
+ centerX=int( ((visibleWidth()/2+contentsX()-d->mXOffset)/oldZoom)*d->mZoom );
+ }
+ if (centerY==-1) {
+ centerY=int( ((visibleHeight()/2+contentsY()-d->mYOffset)/oldZoom)*d->mZoom );
+ }
+ center(centerX,centerY);
+
+ updateScrollBarMode();
+ updateImageOffset();
+ updateZoomActions();
+
+ viewport()->setUpdatesEnabled(true);
+ fullRepaint();
+}
+
+
+void ImageView::setFullScreen(bool fullScreen) {
+ d->mFullScreen=fullScreen;
+
+ if (d->mFullScreen) {
+ viewport()->setBackgroundColor(black);
+ } else {
+ viewport()->setBackgroundColor(ImageViewConfig::backgroundColor() );
+ }
+}
+
+
+//------------------------------------------------------------------------
+//
+// Overloaded methods
+//
+//------------------------------------------------------------------------
+void ImageView::resizeEvent(QResizeEvent* event) {
+ QScrollView::resizeEvent(event);
+ if (d->mZoomMode!=ZOOM_FREE) {
+ updateZoom(d->mZoomMode);
+ } else {
+ updateContentSize();
+ updateImageOffset();
+ }
+}
+
+
+inline void composite(uint* rgba,uint value) {
+ uint alpha=(*rgba) >> 24;
+ if (alpha<255) {
+ uint alphaValue=(255-alpha)*value;
+
+ uint c1=( ( (*rgba & 0xFF0000) >> 16 ) * alpha + alphaValue ) >> 8;
+ uint c2=( ( (*rgba & 0x00FF00) >> 8 ) * alpha + alphaValue ) >> 8;
+ uint c3=( ( (*rgba & 0x0000FF) >> 0 ) * alpha + alphaValue ) >> 8;
+ *rgba=0xFF000000 + (c1<<16) + (c2<<8) + c3;
+ }
+}
+
+void ImageView::drawContents(QPainter* painter,int clipx,int clipy,int clipw,int cliph) {
+ // Erase borders
+ QRect imageRect(0, 0, d->mDocument->width(), d->mDocument->height());
+ imageRect = d->imageToWidget(imageRect);
+
+ QRect widgetRect = QRect(0, 0, visibleWidth(), visibleHeight());
+
+ QRegion region = QRegion(widgetRect) - imageRect;
+ QMemArray<QRect> rects = region.rects();
+ for(unsigned int pos = 0; pos < rects.count(); ++pos ) {
+ painter->eraseRect(rects[pos]);
+ }
+
+ // Repaint
+ if( !d->mValidImageArea.isEmpty()) {
+ addPendingPaint( false, QRect( clipx, clipy, clipw, cliph ));
+ }
+}
+
+// How this pending stuff works:
+// There's a queue of areas to paint (each with bool saying whether it's smooth pass).
+// Also, there's a bitfield of pending operations, operations are handled only after
+// there's nothing more to paint (so that smooth pass is started).
+void ImageView::addPendingPaint( bool smooth, QRect rect ) {
+ if( d->mSmoothingSuspended && smooth ) return;
+
+ // try to avoid scheduling already scheduled areas
+ QRegion& region = smooth ? d->mPendingSmoothRegion : d->mPendingNormalRegion;
+ if( region.intersect( rect ) == QRegion( rect ))
+ return; // whole rect has already pending paints
+ // at least try to remove the part that's already scheduled
+ rect = ( QRegion( rect ) - region ).boundingRect();
+ region += rect;
+ if( rect.isEmpty())
+ return;
+ addPendingPaintInternal( smooth, rect );
+}
+
+void ImageView::addPendingPaintInternal( bool smooth, QRect rect ) {
+ const long long MAX_DIM = 1000000; // if monitors get larger than this, we're in trouble :)
+ // QMap will ensure ordering (non-smooth first, top-to-bottom, left-to-right)
+ long long key = ( smooth ? MAX_DIM * MAX_DIM : 0 ) + rect.y() * MAX_DIM + rect.x();
+ // handle the case of two different paints at the same position (just in case)
+ key *= 100;
+ bool insert = true;
+ while( d->mPendingPaints.contains( key )) {
+ if( d->mPendingPaints[ key ].rect.contains( rect )) {
+ insert = false;
+ break;
+ }
+ if( rect.contains( d->mPendingPaints[ key ].rect )) {
+ break;
+ }
+ ++key;
+ }
+ if( insert ) {
+ d->mPendingPaints[ key ] = PendingPaint( smooth, rect );
+ }
+ scheduleOperation( CHECK_OPERATIONS );
+}
+
+void ImageView::checkPendingOperations() {
+ checkPendingOperationsInternal();
+ if( d->mPendingPaints.isEmpty() && d->mPendingOperations == 0 ) {
+ d->mPendingPaintTimer.stop();
+ }
+ updateBusyLevels();
+}
+
+void ImageView::limitPaintSize( PendingPaint& paint ) {
+ // The only thing that makes time spent in performPaint() vary
+ // is whether there will be scaling and whether there will be smoothing.
+ // So there are three max sizes for each mode.
+ int maxSize = ImageViewConfig::maxRepaintSize();
+ if( d->mZoom != 1.0 ) {
+ if( paint.smooth || !doDelayedSmoothing() ) {
+ maxSize = ImageViewConfig::maxSmoothRepaintSize();
+ } else {
+ maxSize = ImageViewConfig::maxScaleRepaintSize();
+ }
+ }
+ // don't paint more than max_size pixels at a time
+ int maxHeight = ( maxSize + paint.rect.width() - 1 ) / paint.rect.width(); // round up
+ maxHeight = QMAX( maxHeight, 5 ); // at least 5 lines together
+ // can't repaint whole paint at once, adjust height and schedule the rest
+ if( maxHeight < paint.rect.height()) {
+ QRect remaining = paint.rect;
+ remaining.setTop( remaining.top() + maxHeight );
+ addPendingPaintInternal( paint.smooth, remaining );
+ paint.rect.setHeight( maxHeight );
+ }
+}
+
+
+void ImageView::checkPendingOperationsInternal() {
+ if( !d->mPendingPaintTimer.isActive()) // suspended
+ return;
+ while( !d->mPendingPaints.isEmpty()) {
+ PendingPaint paint = *d->mPendingPaints.begin();
+ d->mPendingPaints.remove( d->mPendingPaints.begin());
+ limitPaintSize( paint ); // modifies paint.rect if necessary
+ QRegion& region = paint.smooth ? d->mPendingSmoothRegion : d->mPendingNormalRegion;
+ region -= paint.rect;
+ QRect visibleRect( contentsX(), contentsY(), visibleWidth(), visibleHeight());
+ QRect paintRect = paint.rect.intersect( visibleRect );
+ if( !paintRect.isEmpty()) {
+ QPainter painter( viewport());
+ painter.translate( -contentsX(), -contentsY());
+ performPaint( &painter, paintRect.x(), paintRect.y(),
+ paintRect.width(), paintRect.height(), paint.smooth );
+ return;
+ }
+ }
+ if( d->mPendingOperations & SMOOTH_PASS ) {
+ d->mSmoothingSuspended = false;
+ if( doDelayedSmoothing() ) {
+ QRect visibleRect( contentsX(), contentsY(), visibleWidth(), visibleHeight());
+ addPendingPaint( true, visibleRect );
+ }
+ d->mPendingOperations &= ~SMOOTH_PASS;
+ return;
+ }
+}
+
+void ImageView::scheduleOperation( Operation operation )
+{
+ d->mPendingOperations |= operation;
+ slotBusyLevelChanged( BusyLevelManager::instance()->busyLevel());
+ updateBusyLevels();
+}
+
+void ImageView::updateBusyLevels() {
+ if( !d->mPendingPaintTimer.isActive()) {
+ BusyLevelManager::instance()->setBusyLevel( this, BUSY_NONE );
+ } else if( !d->mPendingPaints.isEmpty() && !(*d->mPendingPaints.begin()).smooth ) {
+ BusyLevelManager::instance()->setBusyLevel( this, BUSY_PAINTING );
+ } else if(( d->mPendingOperations & SMOOTH_PASS )
+ || ( !d->mPendingPaints.isEmpty() && (*d->mPendingPaints.begin()).smooth )) {
+ BusyLevelManager::instance()->setBusyLevel( this, BUSY_SMOOTHING );
+ } else {
+ assert( false );
+ }
+}
+
+void ImageView::slotBusyLevelChanged( BusyLevel level ) {
+ bool resume = false;
+ if( level <= BUSY_PAINTING
+ && !d->mPendingPaints.isEmpty() && !(*d->mPendingPaints.begin()).smooth ) {
+ resume = true;
+ } else if( level <= BUSY_SMOOTHING
+ && (( d->mPendingOperations & SMOOTH_PASS )
+ || ( !d->mPendingPaints.isEmpty() && (*d->mPendingPaints.begin()).smooth ))) {
+ resume = true;
+ }
+ if( resume ) {
+ d->mPendingPaintTimer.start( 0 );
+ } else {
+ d->mPendingPaintTimer.stop();
+ }
+}
+
+// How to do painting:
+// When something needs to be erased: QPainter on viewport and eraseRect()
+// When whole picture needs to be repainted: fullRepaint()
+// When a part of the picture needs to be updated: viewport()->repaint(area,false)
+// All other paints will be changed to progressive painting.
+void ImageView::fullRepaint() {
+ if( !viewport()->isUpdatesEnabled()) return;
+ cancelPending();
+ viewport()->repaint(false);
+}
+
+void ImageView::cancelPending() {
+ d->mPendingPaints.clear();
+ d->mPendingNormalRegion = QRegion();
+ d->mPendingSmoothRegion = QRegion();
+ d->mPendingPaintTimer.stop();
+ d->mPendingOperations = 0;
+ updateBusyLevels();
+}
+
+//#define DEBUG_RECTS
+
+// do the actual painting
+void ImageView::performPaint( QPainter* painter, int clipx, int clipy, int clipw, int cliph, bool secondPass ) {
+ #ifdef DEBUG_RECTS
+ static QColor colors[4]={QColor(255,0,0),QColor(0,255,0),QColor(0,0,255),QColor(255,255,0) };
+ static int numColor=0;
+ #endif
+
+ QTime t;
+ t.start();
+
+ if (d->mDocument->isNull()) {
+ painter->eraseRect(clipx,clipy,clipw,cliph);
+ return;
+ }
+
+ // True if another pass will follow
+ bool fastpass = doDelayedSmoothing() && zoom() != 1.0 && !secondPass;
+
+ ImageUtils::SmoothAlgorithm smoothAlgo = ImageUtils::SMOOTH_NONE;
+ if( zoom() != 1.0 ) {
+ if (doDelayedSmoothing() && !secondPass) {
+ // Add a second, smoothing pass
+ addPendingPaint( true, QRect( clipx, clipy, clipw, cliph ));
+ } else {
+ // We need to smooth now
+ smoothAlgo = static_cast<ImageUtils::SmoothAlgorithm>( ImageViewConfig::smoothAlgorithm() );
+ }
+ }
+
+ int extraPixels = ImageUtils::extraScalePixels( smoothAlgo, zoom());
+ QRect imageRect = d->widgetToImageBounding( QRect(clipx,clipy,clipw,cliph), extraPixels );
+ imageRect = imageRect.intersect( QRect( 0, 0, d->mDocument->width(), d->mDocument->height()));
+ QMemArray< QRect > rects = d->mValidImageArea.intersect( imageRect ).rects();
+ for( unsigned int i = 1; i < rects.count(); ++i ) {
+ addPendingPaint( secondPass, d->imageToWidget( rects[ i ] ));
+ }
+ imageRect = rects.count() > 0 ? rects[ 0 ] : QRect();
+ if (imageRect.isEmpty()) {
+ painter->eraseRect(clipx,clipy,clipw,cliph);
+ return;
+ }
+ QRect widgetRect = d->imageToWidget( imageRect );
+ if (widgetRect.isEmpty() || imageRect.isEmpty()) {
+ painter->eraseRect(clipx,clipy,clipw,cliph);
+ return;
+ }
+
+// With very large images, just getting a subimage using QImage::copy( QRect ) takes a significant
+// portion of time here (even though it's just copying of data - probably because it's a lot of data).
+// So don't do any subimage copying but instead use CroppedQImage which just manipulates scanline
+// pointers. Note however that it's a bit hackish and there may be trouble if any code accesses
+// the image data directly as a whole. See CroppedQImage for details.
+
+// QImage image = d->mDocument->image().copy( imageRect );
+ ImageUtils::CroppedQImage image( d->mDocument->image(), imageRect );
+
+ if( zoom() != 1.0 ) {
+ image=ImageUtils::scale(image,widgetRect.width(),widgetRect.height(), smoothAlgo );
+ }
+
+ if( d->mBrightness != 0 ) {
+ image.normalize(); // needed, it will be modified
+ image = ImageUtils::changeBrightness( image, d->mBrightness );
+ }
+
+ if( d->mContrast != 100 ) { // != 1.0
+ image.normalize(); // needed, it will be modified
+ image = ImageUtils::changeContrast( image, d->mContrast );
+ }
+
+ if( d->mGamma != 100 ) { // != 1.0
+ image.normalize(); // needed, it will be modified
+ image = ImageUtils::changeGamma( image, d->mGamma );
+ }
+
+// Calling normalize() here would make image to be a proper QImage without modified scanlines,
+// so that even calling QImage::copy() would work. However, it seems it's not necessary to call
+// it here. The code above checks that QImage::copy() or similar doesn't occur (that zoom() != 1.0
+// is there primarily to avoid that). If any kind of redraw trouble occurs, try uncommenting this
+// line below first.
+// image.normalize(); // make it use its own data, if needed
+
+ if (image.hasAlphaBuffer()) {
+ image.normalize(); // needed, it will be modified
+ if (image.depth()!=32) {
+ image=image.convertDepth(32);
+ }
+
+ bool light;
+
+ int imageXOffset=widgetRect.x()-d->mXOffset;
+ int imageYOffset=widgetRect.y()-d->mYOffset;
+ int imageWidth=image.width();
+ int imageHeight=image.height();
+ for (int y=0;y<imageHeight;++y) {
+ uint* rgba=(uint*)(image.scanLine(y));
+ for(int x=0;x<imageWidth;x++) {
+ light= ((x+imageXOffset) & 16) ^ ((y+imageYOffset) & 16);
+ composite(rgba,light?192:128);
+ rgba++;
+ }
+ }
+ image.setAlphaBuffer(false);
+ }
+
+ QRect paintRect( clipx, clipy, clipw, cliph );
+ QPixmap buffer( paintRect.size());
+ {
+ QPainter bufferPainter(&buffer);
+ bufferPainter.setBackgroundColor(painter->backgroundColor());
+ bufferPainter.eraseRect(0,0,paintRect.width(),paintRect.height());
+ bufferPainter.drawImage(widgetRect.topLeft()-paintRect.topLeft(),image,
+ fastpass?ThresholdDither:0);
+ }
+ painter->drawPixmap(paintRect.topLeft(),buffer);
+
+ if( paintRect.width() * paintRect.height() >= 10000 ) { // ignore small repaints
+ // try to do one step in 0.1sec
+ int size = paintRect.width() * paintRect.height() * 100 / QMAX( t.elapsed(), 1 );
+
+ int maxRepaintSize;
+ if (zoom() == 1.0) {
+ maxRepaintSize=ImageViewConfig::maxRepaintSize();
+ } else {
+ if (smoothAlgo!=ImageUtils::SMOOTH_NONE) {
+ maxRepaintSize=ImageViewConfig::maxSmoothRepaintSize();
+ } else {
+ maxRepaintSize=ImageViewConfig::maxScaleRepaintSize();
+ }
+ }
+
+ maxRepaintSize = KCLAMP(
+ ( size + maxRepaintSize ) / 2,
+ 10000, LIMIT_MAX_REPAINT_SIZE);
+
+ if (zoom() == 1.0) {
+ ImageViewConfig::setMaxRepaintSize(maxRepaintSize);
+ } else {
+ if (smoothAlgo!=ImageUtils::SMOOTH_NONE) {
+ ImageViewConfig::setMaxSmoothRepaintSize(maxRepaintSize);
+ } else {
+ ImageViewConfig::setMaxScaleRepaintSize(maxRepaintSize);
+ }
+ }
+ }
+
+ #ifdef DEBUG_RECTS
+ painter->setPen(colors[numColor]);
+ numColor=(numColor+1)%4;
+ painter->drawRect(paintRect);
+ #endif
+
+ QApplication::flushX();
+}
+
+
+void ImageView::viewportMousePressEvent(QMouseEvent* event) {
+ viewport()->setFocus();
+ switch (event->button()) {
+ case Qt::LeftButton:
+ d->mTools[d->mToolID]->leftButtonPressEvent(event);
+ break;
+ case Qt::RightButton:
+ d->mTools[d->mToolID]->rightButtonPressEvent(event);
+ break;
+ default: // Avoid compiler complain
+ break;
+ }
+}
+
+
+void ImageView::viewportMouseMoveEvent(QMouseEvent* event) {
+ selectTool(event->state(), true);
+ d->mTools[d->mToolID]->mouseMoveEvent(event);
+}
+
+
+void ImageView::viewportMouseReleaseEvent(QMouseEvent* event) {
+ switch (event->button()) {
+ case Qt::LeftButton:
+ if (event->stateAfter() & Qt::RightButton) {
+ d->mOperaLikePrevious=true;
+ emit selectPrevious();
+ return;
+ }
+ d->mTools[d->mToolID]->leftButtonReleaseEvent(event);
+ break;
+
+ case Qt::MidButton:
+ d->mTools[d->mToolID]->midButtonReleaseEvent(event);
+ break;
+
+ case Qt::RightButton:
+ if (event->stateAfter() & Qt::LeftButton) {
+ emit selectNext();
+ return;
+ }
+
+ if (d->mOperaLikePrevious) { // Avoid showing the popup menu after Opera like previous
+ d->mOperaLikePrevious=false;
+ } else {
+ d->mTools[d->mToolID]->rightButtonReleaseEvent(event);
+ }
+ break;
+
+ default: // Avoid compiler complain
+ break;
+ }
+}
+
+
+bool ImageView::eventFilter(QObject* obj, QEvent* event) {
+ switch (event->type()) {
+ case QEvent::KeyPress:
+ case QEvent::KeyRelease:
+ case QEvent::AccelOverride:
+ return viewportKeyEvent(static_cast<QKeyEvent*>(event));
+
+ case QEvent::MouseButtonDblClick:
+ if (d->mToolID==ZOOM) return false;
+ emit doubleClicked();
+ return true;
+
+ // Getting/loosing focus causes repaints, but repainting here is expensive,
+ // and there's no need to repaint on focus changes, as the focus is not
+ // indicated.
+ case QEvent::FocusIn:
+ case QEvent::FocusOut:
+ return true;
+
+ case QEvent::Enter:
+ selectTool( kapp->keyboardMouseState(), true );
+ emitRequestHintDisplay();
+ break;
+
+ default:
+ break;
+ }
+ return QScrollView::eventFilter(obj,event);
+}
+
+
+bool ImageView::viewportKeyEvent(QKeyEvent* event) {
+ selectTool(event->stateAfter(), false);
+ return false;
+}
+
+
+void ImageView::contentsDragEnterEvent(QDragEnterEvent* event) {
+ event->accept( QUriDrag::canDecode( event ));
+}
+
+void ImageView::contentsDropEvent(QDropEvent* event) {
+ KURL::List list;
+ if( KURLDrag::decode( event, list )) {
+ d->mDocument->setURL( list.first());
+ }
+}
+
+void ImageView::keyPressEvent( QKeyEvent *event ) {
+ QScrollView::keyPressEvent( event );
+ int deltaX, deltaY;
+
+ if (event->state() != Qt::NoButton) {
+ return;
+ }
+ switch (event->key()) {
+ case Key_Up:
+ deltaX = 0;
+ deltaY = -1;
+ break;
+ case Key_Down:
+ deltaX = 0;
+ deltaY = 1;
+ break;
+ case Key_Left:
+ deltaX = -1;
+ deltaY = 0;
+ break;
+ case Key_Right:
+ deltaX = 1;
+ deltaY = 0;
+ break;
+ default:
+ return;
+ }
+ deltaX *= width() / 2;
+ deltaY *= height() / 2;
+ scrollBy (deltaX, deltaY);
+}
+
+/**
+ * If force is set, the cursor will be updated even if the tool is not
+ * different from the current one.
+ */
+void ImageView::selectTool(ButtonState state, bool force) {
+ ToolID oldToolID=d->mToolID;
+ if (state & ControlButton) {
+ d->mToolID=ZOOM;
+ if (d->mToolID!=oldToolID) {
+ emitRequestHintDisplay();
+ }
+ } else {
+ d->mToolID=SCROLL;
+ }
+
+ if (d->mToolID!=oldToolID || force) {
+ d->mTools[d->mToolID]->updateCursor();
+ }
+}
+
+
+void ImageView::wheelEvent(QWheelEvent* event) {
+ d->mTools[d->mToolID]->wheelEvent(event);
+}
+
+
+//------------------------------------------------------------------------
+//
+// Slots
+//
+//------------------------------------------------------------------------
+void ImageView::slotZoomIn() {
+ updateZoom(ZOOM_FREE, computeZoom(true));
+}
+
+
+void ImageView::slotZoomOut() {
+ updateZoom(ZOOM_FREE, computeZoom(false));
+}
+
+
+void ImageView::slotResetZoom() {
+ updateZoom(ZOOM_FREE, 1.0);
+}
+
+
+void ImageView::slotSelectZoom() {
+ int currentItem=d->mZoomCombo->currentItem();
+
+ if (currentItem>=int(d->mZoomComboActions.count()) ) {
+ QString txt=d->mZoomCombo->currentText();
+ txt=txt.left(txt.find('%'));
+ double value=KGlobal::locale()->readNumber(txt) / 100.0;
+ updateZoom(ZOOM_FREE, value);
+ } else {
+ d->mZoomComboActions[currentItem]->activate();
+ }
+}
+
+
+void ImageView::setZoomToFit(bool on) {
+ if (on) {
+ updateZoom(ZOOM_FIT);
+ } else {
+ updateZoom(ZOOM_FREE, d->mZoomBeforeAuto, d->mXCenterBeforeAuto, d->mYCenterBeforeAuto);
+ }
+}
+
+
+void ImageView::setZoomToWidth(bool on) {
+ if (on) {
+ updateZoom(ZOOM_FIT_WIDTH);
+ } else {
+ updateZoom(ZOOM_FREE, d->mZoomBeforeAuto, d->mXCenterBeforeAuto, d->mYCenterBeforeAuto);
+ }
+}
+
+
+void ImageView::setZoomToHeight(bool on) {
+ if (on) {
+ updateZoom(ZOOM_FIT_HEIGHT);
+ } else {
+ updateZoom(ZOOM_FREE, d->mZoomBeforeAuto, d->mXCenterBeforeAuto, d->mYCenterBeforeAuto);
+ }
+}
+
+
+void ImageView::setLockZoom(bool value) {
+ if( value ) {
+ d->mZoomToFit->setChecked( false );
+ d->mZoomToWidth->setChecked( false );
+ d->mZoomToHeight->setChecked( false );
+ }
+ // don't change zoom here, keep it even if it was from some auto zoom mode
+}
+
+
+void ImageView::showBCGDialog() {
+ if (!d->mBCGDialog) {
+ d->mBCGDialog=new BCGDialog(this);
+ }
+ d->mBCGDialog->show();
+}
+
+
+int ImageView::brightness() const {
+ return d->mBrightness;
+}
+
+
+void ImageView::setBrightness(int value) {
+ d->mBrightness=value;
+ fullRepaint();
+}
+
+void ImageView::increaseBrightness() {
+ d->mBrightness = KCLAMP( d->mBrightness + 5, -100, 100 );
+ emit bcgChanged();
+ fullRepaint();
+}
+
+void ImageView::decreaseBrightness() {
+ d->mBrightness = KCLAMP( d->mBrightness - 5, -100, 100 );
+ emit bcgChanged();
+ fullRepaint();
+}
+
+
+
+int ImageView::contrast() const {
+ return d->mContrast - 100;
+}
+
+void ImageView::setContrast(int value) {
+ d->mContrast=value + 100;
+ fullRepaint();
+}
+
+void ImageView::increaseContrast() {
+ d->mContrast = KCLAMP( d->mContrast + 10, 0, 500 );
+ emit bcgChanged();
+ fullRepaint();
+}
+
+void ImageView::decreaseContrast() {
+ d->mContrast = KCLAMP( d->mContrast - 10, 0, 500 );
+ emit bcgChanged();
+ fullRepaint();
+}
+
+
+
+int ImageView::gamma() const {
+ return d->mGamma - 100;
+}
+
+
+void ImageView::setGamma(int value) {
+ d->mGamma=value + 100;
+ fullRepaint();
+}
+
+void ImageView::increaseGamma() {
+ d->mGamma = KCLAMP( d->mGamma + 10, 10, 500 );
+ emit bcgChanged();
+ fullRepaint();
+}
+
+void ImageView::decreaseGamma() {
+ d->mGamma = KCLAMP( d->mGamma - 10, 10, 500 );
+ emit bcgChanged();
+ fullRepaint();
+}
+//------------------------------------------------------------------------
+//
+// Private
+//
+//------------------------------------------------------------------------
+void ImageView::emitRequestHintDisplay() {
+ if (d->mDocument->isNull()) return;
+
+ emit requestHintDisplay( d->mTools[d->mToolID]->hint() );
+}
+
+
+void ImageView::slotImageSizeUpdated() {
+ d->mXOffset=0;
+ d->mYOffset=0;
+
+ d->mValidImageArea = QRegion();
+ if (d->mZoomMode!=ZOOM_FREE) {
+ d->mXCenterBeforeAuto=0;
+ d->mYCenterBeforeAuto=0;
+ } else {
+ horizontalScrollBar()->setValue(0);
+ verticalScrollBar()->setValue(0);
+ }
+ if (d->mZoomMode!=ZOOM_FREE) {
+ updateZoom(d->mZoomMode);
+ } else {
+ if( !d->mLockZoom->isChecked()) {
+ setZoom( 1.0 );
+ }
+ }
+
+ updateZoomActions();
+ d->mAdjustBCG->setEnabled(!d->mDocument->isNull());
+ d->mIncreaseGamma->setEnabled(!d->mDocument->isNull());
+ d->mDecreaseGamma->setEnabled(!d->mDocument->isNull());
+ d->mIncreaseBrightness->setEnabled(!d->mDocument->isNull());
+ d->mDecreaseBrightness->setEnabled(!d->mDocument->isNull());
+ d->mIncreaseContrast->setEnabled(!d->mDocument->isNull());
+ d->mDecreaseContrast->setEnabled(!d->mDocument->isNull());
+
+ updateContentSize();
+ updateImageOffset();
+ updateScrollBarMode();
+ fullRepaint();
+}
+
+void ImageView::slotImageRectUpdated(const QRect& imageRect) {
+ d->mValidImageArea += imageRect;
+ viewport()->repaint( d->imageToWidget( imageRect ), false );
+}
+
+
+void ImageView::updateScrollBarMode() {
+ if (d->mZoomMode==ZOOM_FIT || !ImageViewConfig::showScrollBars()) {
+ setVScrollBarMode(AlwaysOff);
+ setHScrollBarMode(AlwaysOff);
+ } else {
+ setVScrollBarMode(Auto);
+ setHScrollBarMode(Auto);
+ }
+}
+
+
+void ImageView::updateContentSize() {
+ resizeContents(
+ int(d->mDocument->width()*d->mZoom),
+ int(d->mDocument->height()*d->mZoom) );
+}
+
+double ImageView::computeZoomToFit() const {
+ if (d->mDocument->isNull()) {
+ return 1.0;
+ }
+ QSize size=d->mDocument->image().size();
+ size.scale(width(),height(),QSize::ScaleMin);
+
+ double zoom=double(size.width())/d->mDocument->width();
+ if (zoom>1.0 && !ImageViewConfig::enlargeSmallImages()) return 1.0;
+ return zoom;
+}
+
+double ImageView::computeZoomToWidth() const {
+ if (d->mDocument->isNull()) {
+ return 1.0;
+ }
+ int sw = verticalScrollBar()->sizeHint().width(); // geometry is not valid before first show()
+ int w = width();
+ int dw = d->mDocument->width();
+ switch( vScrollBarMode()) {
+ case AlwaysOff:
+ return double(w)/dw;
+ case AlwaysOn:
+ return double(w-sw)/dw;
+ case Auto:
+ default:
+ // there will be a vertical scrollbar if the image's height will be too large
+ if( d->mDocument->height() * (double(w)/dw) > height()) return double(w-sw)/dw;
+ return double(w)/dw;
+ }
+}
+
+double ImageView::computeZoomToHeight() const {
+ if (d->mDocument->isNull()) {
+ return 1.0;
+ }
+ int sh = horizontalScrollBar()->sizeHint().height();
+ int h = height();
+ int dh = d->mDocument->height();
+ switch( vScrollBarMode()) {
+ case AlwaysOff:
+ return double(h)/dh;
+ case AlwaysOn:
+ return double(h-sh)/dh;
+ case Auto:
+ default:
+ if( d->mDocument->width() * (double(h)/dh) > width()) return double(h-sh)/dh;
+ return double(h)/dh;
+ }
+}
+
+double ImageView::computeZoom(bool in) const {
+ const double F = 0.5; // change in 0.5 steps
+ double zoomtofit = computeZoomToFit();
+ double zoomtowidth = computeZoomToWidth();
+ double zoomtoheight = computeZoomToHeight();
+ if (in) {
+ double newzoom;
+ if (d->mZoom>=1.0) {
+ newzoom = (floor(d->mZoom/F)+1.0)*F;
+ } else {
+ newzoom = 1/(( ceil(1/d->mZoom/F)-1.0 )*F);
+ }
+ if( d->mZoom < zoomtofit && zoomtofit < newzoom ) newzoom = zoomtofit;
+ if( d->mZoom < zoomtowidth && zoomtowidth < newzoom ) newzoom = zoomtowidth;
+ if( d->mZoom < zoomtoheight && zoomtoheight < newzoom ) newzoom = zoomtoheight;
+ return newzoom;
+ } else {
+ double newzoom;
+ if (d->mZoom>1.0) {
+ newzoom = (ceil(d->mZoom/F)-1.0)*F;
+ } else {
+ newzoom = 1/(( floor(1/d->mZoom/F)+1.0 )*F);
+ }
+ if( d->mZoom > zoomtofit && zoomtofit > newzoom ) newzoom = zoomtofit;
+ if( d->mZoom > zoomtowidth && zoomtowidth > newzoom ) newzoom = zoomtowidth;
+ if( d->mZoom > zoomtoheight && zoomtoheight > newzoom ) newzoom = zoomtoheight;
+ return newzoom;
+ }
+}
+
+void ImageView::updateImageOffset() {
+ int viewWidth=width();
+ int viewHeight=height();
+
+ // Compute d->mXOffset and d->mYOffset in case the image does not fit
+ // the view width or height
+ int zpixWidth=int(d->mDocument->width() * d->mZoom);
+ int zpixHeight=int(d->mDocument->height() * d->mZoom);
+
+ if (zpixWidth>viewWidth && hScrollBarMode()!=AlwaysOff) {
+ // use sizeHint() - geometry is not valid before first show()
+ viewHeight-=horizontalScrollBar()->sizeHint().height();
+ }
+ if (zpixHeight>viewHeight && vScrollBarMode()!=AlwaysOff) {
+ viewWidth-=verticalScrollBar()->sizeHint().width();
+ }
+
+ d->mXOffset=QMAX(0,(viewWidth-zpixWidth)/2);
+ d->mYOffset=QMAX(0,(viewHeight-zpixHeight)/2);
+}
+
+
+void ImageView::updateZoomActions() {
+ // Disable most actions if there's no image
+ if (d->mDocument->isNull()) {
+ d->mZoomComboAction->setEnabled(false);
+ d->mZoomIn->setEnabled(false);
+ d->mZoomOut->setEnabled(false);
+ d->mResetZoom->setEnabled(false);
+ return;
+ }
+
+ d->mZoomComboAction->setEnabled(true);
+ d->mZoomToFit->setEnabled(true);
+ d->mZoomToWidth->setEnabled(true);
+ d->mZoomToHeight->setEnabled(true);
+ d->mResetZoom->setEnabled(true);
+
+
+ if (d->mZoomMode==ZOOM_FREE) {
+ d->mZoomIn->setEnabled(d->mZoom<MAX_ZOOM);
+ d->mZoomOut->setEnabled(d->mZoom>1/MAX_ZOOM);
+ QString zoomText=QString("%1%").arg(int(d->mZoom*100));
+ d->mZoomCombo->setCurrentText(zoomText);
+ } else {
+ d->mZoomIn->setEnabled(true);
+ d->mZoomOut->setEnabled(true);
+ d->mZoomCombo->setCurrentItem(d->mZoomMode);
+ }
+}
+
+} // namespace