/*
Gwenview - A simple image viewer for TDE
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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

*/

#include "config.h"

#include "imageview.moc"

#include <assert.h>
#include <math.h>

// TQt 
#include <tqcolor.h>
#include <tqcombobox.h>
#include <tqcursor.h>
#include <tqdatetime.h>
#include <tqevent.h>
#include <tqpainter.h>
#include <tqpixmap.h>
#include <tqlabel.h>
#include <tqtimer.h>
#include <tqvaluevector.h>

// KDE 
#include <tdeaction.h>
#include <tdeconfig.h>
#include <kdebug.h>
#include <tdeversion.h>
#include <tdelocale.h>
#include <kstandarddirs.h>
#include <kstdaction.h>
#include <kurldrag.h>
#include <tdeapplication.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
TQScrollView'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 TQScrollView's viewport, usually TQScrollView 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
	TQComboBox* mZoomCombo;
	// We do not use TDESelectAction because it's not possible to set the combo text
	KWidgetAction* mZoomComboAction;
	TDEToggleAction* mZoomToFit;
	TDEToggleAction* mZoomToWidth;
	TDEToggleAction* mZoomToHeight;
	TQValueVector<TDEToggleAction*> mZoomComboActions;
	TDEAction* mZoomIn;
	TDEAction* mZoomOut;
	TDEAction* mResetZoom;
	TDEToggleAction* mLockZoom;
	TDEAction* mAdjustBCG;
	TDEAction* mIncreaseGamma;
	TDEAction* mDecreaseGamma;
	TDEAction* mIncreaseBrightness;
	TDEAction* mDecreaseBrightness;
	TDEAction* mIncreaseContrast;
	TDEAction* mDecreaseContrast;
	TDEActionCollection* 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;
	
	TQMap< long long, PendingPaint > mPendingPaints;
	TQRegion mPendingNormalRegion;
	TQRegion mPendingSmoothRegion;
	int mPendingOperations;
	TQTimer mPendingPaintTimer;
	bool mSmoothingSuspended;
	TQRegion 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;
	}

	TQPoint imageToWidget( const TQPoint& p ) const {
		return TQPoint( imageToWidgetX( p.x()), imageToWidgetY( p.y()));
	}

	TQRect imageToWidget( const TQRect& r ) const {
		return TQRect( imageToWidget( r.topLeft()),
			// don't use bottomright corner for conversion, but the one next to it
			imageToWidget( r.bottomRight() + TQPoint( 1, 1 )) - TQPoint( 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 );
	}

	TQPoint widgetToImage( const TQPoint& p ) const {
		return TQPoint( widgetToImageX( p.x()), widgetToImageY( p.y()));
	}

	TQRect widgetToImage( const TQRect& r ) const {
		return TQRect( widgetToImage( r.topLeft()),
			// don't use bottomright corner for conversion, but the one next to it
			widgetToImage( r.bottomRight() + TQPoint( 1, 1 )) - TQPoint( 1, 1 ));
	}

	TQRect widgetToImageBounding( const TQRect& r, int extra ) const {
		TQRect ret = widgetToImage( r );
		// make sure converting to image and back always returns TQRect 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 (TQValueVector<TDEToggleAction*>::iterator it=mZoomComboActions.begin();
			it!=mZoomComboActions.end();
			++it)
		{
			TQString 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) {
			TQString txt=TQString("%1%").arg( int(zoomValues[pos]*100) );
			mZoomCombo->insertItem(txt);
		}
	}
};


inline bool doDelayedSmoothing() {
	return ImageViewConfig::delayedSmoothing()
		&& ImageViewConfig::smoothAlgorithm()!=ImageUtils::SMOOTH_NONE;
}


class ImageView::EventFilter : public TQObject {
public:
	EventFilter(ImageView* parent)
	: TQObject(parent) {}
		
	bool eventFilter(TQObject*, TQEvent* event) {
		switch (event->type()) {
		case TQEvent::KeyPress:
		case TQEvent::KeyRelease:
		case TQEvent::AccelOverride:
			return static_cast< ImageView* >(parent())->viewportKeyEvent(static_cast<TQKeyEvent*>(event));
		default:
			break;
		}
		return false;
	}
};



ImageView::ImageView(TQWidget* parent,Document* document, TDEActionCollection* actionCollection)
: TQScrollView(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(TQWidget::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 TDEToggleAction(i18n("Fit to &Window"),"zoom-fit-best",0,d->mActionCollection,"view_zoom_to_fit");
	connect(d->mZoomToFit,TQ_SIGNAL(toggled(bool)),
		this, TQ_SLOT(setZoomToFit(bool)) );
	d->mZoomToWidth=new TDEToggleAction(i18n("Fit to &Width"),0,0,d->mActionCollection,"view_zoom_to_width");
	connect(d->mZoomToWidth,TQ_SIGNAL(toggled(bool)),
		this, TQ_SLOT(setZoomToWidth(bool)) );
	d->mZoomToHeight=new TDEToggleAction(i18n("Fit to &Height"),0,0,d->mActionCollection,"view_zoom_to_height");
	connect(d->mZoomToHeight,TQ_SIGNAL(toggled(bool)),
		this, TQ_SLOT(setZoomToHeight(bool)) );

	d->mZoomIn=KStdAction::zoomIn(this,TQ_SLOT(slotZoomIn()),d->mActionCollection);

	d->mZoomOut=KStdAction::zoomOut(this,TQ_SLOT(slotZoomOut()),d->mActionCollection);

	d->mResetZoom=KStdAction::actualSize(this,TQ_SLOT(slotResetZoom()),d->mActionCollection);
	d->mResetZoom->setIcon("zoom-original");

	d->mLockZoom=new TDEToggleAction(i18n("&Lock Zoom"),"system-lock-screen",0,d->mActionCollection,"view_zoom_lock");
	d->mLockZoom->setChecked(ImageViewConfig::lockZoom());
	connect(d->mLockZoom,TQ_SIGNAL(toggled(bool)),
		this, TQ_SLOT(setLockZoom(bool)) );

	d->mZoomCombo=new TQComboBox(true);
	// Avoid stealing focus
	d->mZoomCombo->setFocusPolicy(TQWidget::ClickFocus);
	connect(d->mZoomCombo, TQ_SIGNAL(activated(int)),
		this, TQ_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 TDEAction(i18n("Adjust Brightness/Contrast/Gamma"), "colorize", 0,
		this, TQ_SLOT(showBCGDialog()), d->mActionCollection, "adjust_bcg");
	d->mIncreaseGamma=new TDEAction(i18n("Increase Gamma"),0,CTRL+Key_G,
		this, TQ_SLOT(increaseGamma()),d->mActionCollection,"increase_gamma");
	d->mDecreaseGamma=new TDEAction(i18n("Decrease Gamma"),0,SHIFT+CTRL+Key_G,
		this, TQ_SLOT(decreaseGamma()),d->mActionCollection,"decrease_gamma");
	d->mIncreaseBrightness=new TDEAction(i18n("Increase Brightness" ),0,CTRL+Key_B,
		this, TQ_SLOT(increaseBrightness()),d->mActionCollection,"increase_brightness");
	d->mDecreaseBrightness=new TDEAction(i18n("Decrease Brightness" ),0,SHIFT+CTRL+Key_B,
		this, TQ_SLOT(decreaseBrightness()),d->mActionCollection,"decrease_brightness");
	d->mIncreaseContrast=new TDEAction(i18n("Increase Contrast" ),0,CTRL+Key_C,
		this, TQ_SLOT(increaseContrast()),d->mActionCollection,"increase_contrast");
	d->mDecreaseContrast=new TDEAction(i18n("Decrease Contrast" ),0,SHIFT+CTRL+Key_C,
		this, TQ_SLOT(decreaseContrast()),d->mActionCollection,"decrease_contrast");

	// Connect to some interesting signals
	connect(d->mDocument,TQ_SIGNAL(loaded(const KURL&)),
		this, TQ_SLOT(slotLoaded()) );

	connect(d->mDocument,TQ_SIGNAL(loading()),
		this, TQ_SLOT( loadingStarted()) );

	connect(d->mDocument,TQ_SIGNAL(modified()),
		this, TQ_SLOT(slotModified()) );

	connect(d->mDocument, TQ_SIGNAL(sizeUpdated()),
		this, TQ_SLOT(slotImageSizeUpdated()) );

	connect(d->mDocument, TQ_SIGNAL(rectUpdated(const TQRect&)),
		this, TQ_SLOT(slotImageRectUpdated(const TQRect&)) );

	connect(&d->mPendingPaintTimer,TQ_SIGNAL(timeout()),
		this, TQ_SLOT(checkPendingOperations()) );

	connect(BusyLevelManager::instance(),TQ_SIGNAL(busyLevelChanged(BusyLevel)),
		this, TQ_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 TQScrollView
	// 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 = TQRegion();
	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;
}


TQPoint ImageView::offset() const {
	return TQPoint(d->mXOffset, d->mYOffset);
}
	

bool ImageView::canZoom(bool in) const {
	TDEAction* zoomAction=in ? d->mZoomIn : d->mZoomOut;
	return zoomAction->isEnabled();
}


TDEToggleAction* 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;
	TDEAction* 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(TQResizeEvent* event) {
	TQScrollView::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(TQPainter* painter,int clipx,int clipy,int clipw,int cliph) {
	// Erase borders
	TQRect imageRect(0, 0, d->mDocument->width(), d->mDocument->height());
	imageRect = d->imageToWidget(imageRect);
	
	TQRect widgetRect = TQRect(0, 0, visibleWidth(), visibleHeight());

	TQRegion region = TQRegion(widgetRect) - imageRect;
	TQMemArray<TQRect> rects = region.rects();
	for(unsigned int pos = 0; pos < rects.count(); ++pos ) {
		painter->eraseRect(rects[pos]);
	}
	
	// Repaint
	if( !d->mValidImageArea.isEmpty()) {
		addPendingPaint( false, TQRect( 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, TQRect rect ) {
	if( d->mSmoothingSuspended && smooth ) return;

	// try to avoid scheduling already scheduled areas
	TQRegion& region = smooth ? d->mPendingSmoothRegion : d->mPendingNormalRegion;
	if( region.intersect( rect ) == TQRegion( rect ))
		return; // whole rect has already pending paints
	// at least try to remove the part that's already scheduled
	rect = ( TQRegion( rect ) - region ).boundingRect();
	region += rect;
	if( rect.isEmpty())
		return;
	addPendingPaintInternal( smooth, rect );
}

void ImageView::addPendingPaintInternal( bool smooth, TQRect rect ) {
	const long long MAX_DIM = 1000000; // if monitors get larger than this, we're in trouble :)
	// TQMap 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 = TQMAX( 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()) {
		TQRect 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
		TQRegion& region = paint.smooth ? d->mPendingSmoothRegion : d->mPendingNormalRegion;
		region -= paint.rect;
		TQRect visibleRect( contentsX(), contentsY(), visibleWidth(), visibleHeight());
		TQRect paintRect = paint.rect.intersect( visibleRect );
		if( !paintRect.isEmpty()) {
			TQPainter 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() ) {
			TQRect 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: TQPainter 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 = TQRegion();
	d->mPendingSmoothRegion = TQRegion();
	d->mPendingPaintTimer.stop();
	d->mPendingOperations = 0;
	updateBusyLevels();
}

//#define DEBUG_RECTS

// do the actual painting
void ImageView::performPaint( TQPainter* painter, int clipx, int clipy, int clipw, int cliph, bool secondPass ) {
	#ifdef DEBUG_RECTS
	static TQColor colors[4]={TQColor(255,0,0),TQColor(0,255,0),TQColor(0,0,255),TQColor(255,255,0) };
	static int numColor=0;
	#endif

	TQTime 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, TQRect( clipx, clipy, clipw, cliph ));
		} else {
			// We need to smooth now
			smoothAlgo = static_cast<ImageUtils::SmoothAlgorithm>( ImageViewConfig::smoothAlgorithm() );
		}
	}

	int extraPixels = ImageUtils::extraScalePixels( smoothAlgo, zoom());
	TQRect imageRect = d->widgetToImageBounding( TQRect(clipx,clipy,clipw,cliph), extraPixels );
	imageRect = imageRect.intersect( TQRect( 0, 0, d->mDocument->width(), d->mDocument->height()));
	TQMemArray< TQRect > rects = TQRegion(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 ] : TQRect();
	if (imageRect.isEmpty()) {
		painter->eraseRect(clipx,clipy,clipw,cliph);
		return;
	}
	TQRect 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 TQImage::copy( TQRect ) 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 CroppedTQImage 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 CroppedTQImage for details.

//	TQImage image = d->mDocument->image().copy( imageRect );
	ImageUtils::CroppedTQImage 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 TQImage without modified scanlines,
// so that even calling TQImage::copy() would work. However, it seems it's not necessary to call
// it here. The code above checks that TQImage::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);
	}

	TQRect paintRect( clipx, clipy, clipw, cliph );
	TQPixmap buffer( paintRect.size());
	{
		TQPainter 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 / TQMAX( 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

	TQApplication::flushX();
}


void ImageView::viewportMousePressEvent(TQMouseEvent* event) {
	viewport()->setFocus();
	switch (event->button()) {
	case TQt::LeftButton:
		d->mTools[d->mToolID]->leftButtonPressEvent(event);
		break;
	case TQt::RightButton:
		d->mTools[d->mToolID]->rightButtonPressEvent(event);
		break;
	default: // Avoid compiler complain
		break;
	}
}


void ImageView::viewportMouseMoveEvent(TQMouseEvent* event) {
	selectTool(event->state(), true);
	d->mTools[d->mToolID]->mouseMoveEvent(event);
}


void ImageView::viewportMouseReleaseEvent(TQMouseEvent* event) {
	switch (event->button()) {
	case TQt::LeftButton:
		if (event->stateAfter() & TQt::RightButton) {
			d->mOperaLikePrevious=true;
			emit selectPrevious();
			return;
		}
		d->mTools[d->mToolID]->leftButtonReleaseEvent(event);
		break;

	case TQt::MidButton:
		d->mTools[d->mToolID]->midButtonReleaseEvent(event);
		break;

	case TQt::RightButton:
		if (event->stateAfter() & TQt::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(TQObject* obj, TQEvent* event) {
	switch (event->type()) {
	case TQEvent::KeyPress:
	case TQEvent::KeyRelease:
	case TQEvent::AccelOverride:
		return viewportKeyEvent(static_cast<TQKeyEvent*>(event));

	case TQEvent::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 TQEvent::FocusIn:
	case TQEvent::FocusOut:
		return true;

	case TQEvent::Enter:
		selectTool( kapp->keyboardMouseState(), true );
		emitRequestHintDisplay();
		break;

	default:
		break;
	}
	return TQScrollView::eventFilter(obj,event);
}


bool ImageView::viewportKeyEvent(TQKeyEvent* event) {
	selectTool(event->stateAfter(), false);
	return false;
}


void ImageView::contentsDragEnterEvent(TQDragEnterEvent* event) {
	event->accept( TQUriDrag::canDecode( event ));
}

void ImageView::contentsDropEvent(TQDropEvent* event) {
	KURL::List list;
	if( KURLDrag::decode( event, list )) {
		d->mDocument->setURL( list.first());
	}
}

void ImageView::keyPressEvent( TQKeyEvent *event ) {
	TQScrollView::keyPressEvent( event );
	int deltaX, deltaY;

	if (event->state() != TQt::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(TQWheelEvent* 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()) ) {
		TQString txt=d->mZoomCombo->currentText();
		txt=txt.left(txt.find('%'));
		double value=TDEGlobal::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 = TQRegion();
	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 TQRect& 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;
	}
	TQSize size=d->mDocument->image().size();
	size.scale(width(),height(),TQSize::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=TQMAX(0,(viewWidth-zpixWidth)/2);
	d->mYOffset=TQMAX(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);
		TQString zoomText=TQString("%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
