/*
 * KSnapshot
 *
 * (c) Richard J. Moore 1997-2002
 * (c) Matthias Ettrich 2000
 * (c) Aaron J. Seigo 2002
 * (c) Nadeem Hasan 2003
 * This adaptation: (c) Boudewijn Rempt 2005 
 *
 * Released under the GPL see file LICENSE for details.
 */

#include <kapplication.h>
#include <klocale.h>
#include <kimageio.h>
#include <kfiledialog.h>
#include <kimagefilepreview.h>
#include <kmessagebox.h>
#include <kdebug.h>
#include <kprinter.h>
#include <kio/netaccess.h>
#include <ksavefile.h>
#include <ktempfile.h>

#include <qbitmap.h>
#include <qdragobject.h>
#include <qimage.h>
#include <qclipboard.h>
#include <qvbox.h>

#include <kaccel.h>
#include <knotifyclient.h>
#include <khelpmenu.h>
#include <kpopupmenu.h>
#include <kpushbutton.h>
#include <kstartupinfo.h>

#include <qcursor.h>
#include <qregexp.h>
#include <qpainter.h>
#include <qpaintdevicemetrics.h>
#include <qwhatsthis.h>

#include <stdlib.h>

#include "ksnapshot.h"
#include "regiongrabber.h"
#include "ksnapshotwidget.h"

#include <X11/Xlib.h>
#include <X11/Xatom.h>

#include <config.h>

#ifdef HAVE_X11_EXTENSIONS_SHAPE_H
#include <X11/extensions/shape.h>
#endif

#include <kglobal.h>

KSnapshot::KSnapshot(QWidget *parent, const char *name)
	: super(parent, name, false, QString::null, Ok|Cancel)
{
	grabber = new QWidget( 0, 0, WStyle_Customize | WX11BypassWM );
	Q_CHECK_PTR(grabber);
	grabber->move( -1000, -1000 );
	grabber->installEventFilter( this );

#ifdef HAVE_X11_EXTENSIONS_SHAPE_H
	int tmp1, tmp2;
	//Check whether the extension is available
	haveXShape = XShapeQueryExtension( qt_xdisplay(), &tmp1, &tmp2 );
#endif

	QVBox *vbox = makeVBoxMainWidget();
	mainWidget = new KSnapshotWidget( vbox, "mainWidget" );
	Q_CHECK_PTR(mainWidget);

	connect(mainWidget, SIGNAL(startImageDrag()), SLOT(slotDragSnapshot()));

	connect( mainWidget, SIGNAL( newClicked() ), SLOT( slotGrab() ) );
	connect( mainWidget, SIGNAL( printClicked() ), SLOT( slotPrint() ) );

	grabber->show();
	grabber->grabMouse( waitCursor );
	
	snapshot = QPixmap::grabWindow( qt_xrootwin() );
	updatePreview();
	grabber->releaseMouse();
	grabber->hide();

	KConfig *conf=KGlobal::config();
	conf->setGroup("GENERAL");
	mainWidget->setDelay(conf->readNumEntry("delay",0));
	mainWidget->setMode( conf->readNumEntry( "mode", 0 ) );
	mainWidget->setIncludeDecorations(conf->readBoolEntry("includeDecorations",true));

	connect( &grabTimer, SIGNAL( timeout() ), this, SLOT(  grabTimerDone() ) );

	KAccel* accel = new KAccel(this);
	Q_CHECK_PTR(accel);
	accel->insert(KStdAccel::Print, this, SLOT(slotPrint()));
	accel->insert(KStdAccel::New, this, SLOT(slotGrab()));

	accel->insert( "Print2", Key_P, this, SLOT(slotPrint()));
	accel->insert( "New2", Key_N, this, SLOT(slotGrab()));
	accel->insert( "New3", Key_Space, this, SLOT(slotGrab()));

	mainWidget->btnNew->setFocus();
}

KSnapshot::~KSnapshot()
{
}

bool KSnapshot::save( const QString &filename )
{
	return save( KURL::fromPathOrURL( filename ));
}

bool KSnapshot::save( const KURL& url )
{
	QString type( KImageIO::type(url.path()) );
	if ( type.isNull() )
		type = "PNG";

	bool ok = false;

	if ( url.isLocalFile() ) {
		KSaveFile saveFile( url.path() );
		if ( saveFile.status() == 0 ) {
			if ( snapshot.save( saveFile.file(), type.latin1() ) )
				ok = saveFile.close();
		}
	}
	else {
		KTempFile tmpFile;
		tmpFile.setAutoDelete( true );
		if ( tmpFile.status() == 0 ) {
			if ( snapshot.save( tmpFile.file(), type.latin1() ) ) {
				if ( tmpFile.close() )
					ok = KIO::NetAccess::upload( tmpFile.name(), url, this );
			}
		}
	}

	QApplication::restoreOverrideCursor();
	if ( !ok ) {
		kdWarning() << "KSnapshot was unable to save the snapshot" << endl;

		QString caption = i18n("Unable to save image");
		QString text = i18n("KSnapshot was unable to save the image to\n%1.")
			.arg(url.prettyURL());
		KMessageBox::error(this, text, caption);
	}

	return ok;
}

void KSnapshot::slotCopy()
{
	QClipboard *cb = QApplication::clipboard();
	cb->setPixmap( snapshot );
}

void KSnapshot::slotDragSnapshot()
{
	QDragObject *drobj = new QImageDrag(snapshot.convertToImage(), this);
	Q_CHECK_PTR(drobj);
	drobj->setPixmap(mainWidget->preview());
	drobj->dragCopy();
}

void KSnapshot::slotGrab()
{
	hide();
	if ( mainWidget->mode() == Region )
	{
		rgnGrab = new RegionGrabber();
		Q_CHECK_PTR(rgnGrab);
		connect( rgnGrab, SIGNAL( regionGrabbed( const QPixmap & ) ),
			 SLOT( slotRegionGrabbed( const QPixmap & ) ) );
	}
	else
	{
		if ( mainWidget->delay() )
			grabTimer.start( mainWidget->delay() * 1000, true );
		else {
			grabber->show();
			grabber->grabMouse( crossCursor );
		}
	}
}

void KSnapshot::slotPrint()
{
	KPrinter printer;
	if (snapshot.width() > snapshot.height())
		printer.setOrientation(KPrinter::Landscape);
	else
		printer.setOrientation(KPrinter::Portrait);

	qApp->processEvents();

	if (printer.setup(this, i18n("Print Screenshot")))
	{
		qApp->processEvents();

		QPainter painter(&printer);
		QPaintDeviceMetrics metrics(painter.device());

		float w = snapshot.width();
		float dw = w - metrics.width();
		float h = snapshot.height();
		float dh = h - metrics.height();
		bool scale = false;

		if ( (dw > 0.0) || (dh > 0.0) )
			scale = true;

		if ( scale ) {

			QImage img = snapshot.convertToImage();
			qApp->processEvents();

			float newh, neww;
			if ( dw > dh ) {
				neww = w-dw;
				newh = neww/w*h;
			}
			else {
				newh = h-dh;
				neww = newh/h*w;
			}

			img = img.smoothScale( int(neww), int(newh), QImage::ScaleMin );
			qApp->processEvents();

			int x = (metrics.width()-img.width())/2;
			int y = (metrics.height()-img.height())/2;

			painter.drawImage( x, y, img);
		}
		else {
			int x = (metrics.width()-snapshot.width())/2;
			int y = (metrics.height()-snapshot.height())/2;
			painter.drawPixmap( x, y, snapshot );
		}
	}

	qApp->processEvents();
}

void KSnapshot::slotRegionGrabbed( const QPixmap &pix )
{
	if ( !pix.isNull() )
	{
		snapshot = pix;
		updatePreview();
		modified = true;
	}

	delete rgnGrab;
	QApplication::restoreOverrideCursor();
	show();
}

bool KSnapshot::eventFilter( QObject* o, QEvent* e)
{
	if ( o == grabber && e->type() == QEvent::MouseButtonPress ) {
		QMouseEvent* me = (QMouseEvent*) e;
		if ( QWidget::mouseGrabber() != grabber )
			return false;
		if ( me->button() == LeftButton )
			performGrab();
	}
	return false;
}

void KSnapshot::updatePreview()
{
	QImage img = snapshot.convertToImage();
	double r1 = ((double) snapshot.height() ) / snapshot.width();
	if ( r1 * mainWidget->previewWidth()  < mainWidget->previewHeight() )
		img = img.smoothScale( mainWidget->previewWidth(),
				       int( mainWidget->previewWidth() * r1 ));
	else
		img = img.smoothScale( (int) (((double)mainWidget->previewHeight()) / r1),
				       (mainWidget->previewHeight() ) );

	QPixmap pm;
	pm.convertFromImage( img );
	mainWidget->setPreview( pm );
}

void KSnapshot::grabTimerDone()
{
	performGrab();
	KNotifyClient::beep(i18n("The screen has been successfully grabbed."));
}

static 
Window findRealWindow( Window w, int depth = 0 )
{
	if( depth > 5 )
		return None;
	static Atom wm_state = XInternAtom( qt_xdisplay(), "WM_STATE", False );
	Atom type;
	int format;
	unsigned long nitems, after;
	unsigned char* prop;
	if( XGetWindowProperty( qt_xdisplay(), w, wm_state, 0, 0, False, AnyPropertyType,
				&type, &format, &nitems, &after, &prop ) == Success ) {
		if( prop != NULL )
			XFree( prop );
		if( type != None )
			return w;
	}
	Window root, parent;
	Window* children;
	unsigned int nchildren;
	Window ret = None;
	if( XQueryTree( qt_xdisplay(), w, &root, &parent, &children, &nchildren ) != 0 ) {
		for( unsigned int i = 0;
		     i < nchildren && ret == None;
		     ++i )
			ret = findRealWindow( children[ i ], depth + 1 );
		if( children != NULL )
			XFree( children );
	}
	return ret;
}

void KSnapshot::performGrab()
{
	grabber->releaseMouse();
	grabber->hide();
	grabTimer.stop();
	XGrabServer( qt_xdisplay());
	if ( mainWidget->mode() == WindowUnderCursor ) {
		Window root;
		Window child;
		uint mask;
		int rootX, rootY, winX, winY;
		XQueryPointer( qt_xdisplay(), qt_xrootwin(), &root, &child,
			       &rootX, &rootY, &winX, &winY,
			       &mask);
		if( child == None )
			child = qt_xrootwin();
		if( !mainWidget->includeDecorations()) {
			Window real_child = findRealWindow( child );
			if( real_child != None ) // test just in case
				child = real_child;
		}
		int x, y;
		unsigned int w, h;
		unsigned int border;
		unsigned int depth;
		XGetGeometry( qt_xdisplay(), child, &root, &x, &y,
			      &w, &h, &border, &depth );
		w += 2 * border;
		h += 2 * border;

		Window parent;
		Window* children;
		unsigned int nchildren;
		if( XQueryTree( qt_xdisplay(), child, &root, &parent,
				&children, &nchildren ) != 0 ) {
			if( children != NULL )
				XFree( children );
			int newx, newy;
			Window dummy;
			if( XTranslateCoordinates( qt_xdisplay(), parent, qt_xrootwin(),
						   x, y, &newx, &newy, &dummy )) {
				x = newx;
				y = newy;
			}
		}

		snapshot = QPixmap::grabWindow( qt_xrootwin(), x, y, w, h );

#ifdef HAVE_X11_EXTENSIONS_SHAPE_H
		//No XShape - no work.
		if (haveXShape) {
			QBitmap mask(w, h);
			//As the first step, get the mask from XShape.
			int count, order;
			XRectangle* rects = XShapeGetRectangles( qt_xdisplay(), child,
								 ShapeBounding, &count, &order);
			//The ShapeBounding region is the outermost shape of the window;
			//ShapeBounding - ShapeClipping is defined to be the border.
			//Since the border area is part of the window, we use bounding
			// to limit our work region
			if (rects) {
				//Create a QRegion from the rectangles describing the bounding mask.
				QRegion contents;
				for (int pos = 0; pos < count; pos++)
					contents += QRegion(rects[pos].x, rects[pos].y,
							    rects[pos].width, rects[pos].height);
				XFree(rects);

				//Create the bounding box.
				QRegion bbox(0, 0, snapshot.width(), snapshot.height());
                
				if( border > 0 ) {
					contents.translate( border, border );
					contents += QRegion( 0, 0, border, h );
					contents += QRegion( 0, 0, w, border );
					contents += QRegion( 0, h - border, w, border );
					contents += QRegion( w - border, 0, border, h );
				}
                
				//Get the masked away area.
				QRegion maskedAway = bbox - contents;
				QMemArray<QRect> maskedAwayRects = maskedAway.rects();

				//Construct a bitmap mask from the rectangles
				QPainter p(&mask);
				p.fillRect(0, 0, w, h, Qt::color1);
				for (uint pos = 0; pos < maskedAwayRects.count(); pos++)
					p.fillRect(maskedAwayRects[pos], Qt::color0);
				p.end();

				snapshot.setMask(mask);
			}
		}
#endif
	}
	else {
		snapshot = QPixmap::grabWindow( qt_xrootwin() );
	}
	XUngrabServer( qt_xdisplay());
	updatePreview();
	QApplication::restoreOverrideCursor();
	modified = true;
	show();
}

void KSnapshot::setTime(int newTime)
{
	mainWidget->setDelay(newTime);
}

void KSnapshot::setURL( const QString &url )
{
	KURL newURL = KURL::fromPathOrURL( url );
	if ( newURL == filename )
		return;

	filename = newURL;
}

void KSnapshot::setGrabMode( int m )
{
	mainWidget->setMode( m );
}

void KSnapshot::slotMovePointer(int x, int y)
{
	QCursor::setPos( x, y );
}

void KSnapshot::exit()
{
	kdDebug() << "exit: reject\n";

	KConfig *conf=KGlobal::config();
	conf->setGroup("GENERAL");
	conf->writeEntry("delay",mainWidget->delay());
	conf->writeEntry("mode",mainWidget->mode());
	conf->writeEntry("includeDecorations",mainWidget->includeDecorations());
	KURL url = filename;
	url.setPass( QString::null );
	conf->writePathEntry("filename",url.url());

	reject();
}

void KSnapshot::slotOk()
{
	kdDebug() << "Accept\n";

	KConfig *conf=KGlobal::config();
	conf->setGroup("GENERAL");
	conf->writeEntry("delay",mainWidget->delay());
	conf->writeEntry("mode",mainWidget->mode());
	conf->writeEntry("includeDecorations",mainWidget->includeDecorations());
	KURL url = filename;
	url.setPass( QString::null );
	conf->writePathEntry("filename",url.url());

	emit screenGrabbed();

	accept();
}

#include "ksnapshot.moc"
