/****************************************************************************
**
** Copyright (C) 1992-2008 Trolltech ASA.  All rights reserved.
**
** This file is part of an example program for Qt.  This example
** program may be used, distributed and modified without limitation.
**
*****************************************************************************/

#include "qdragapp.h"
#include "qptrlist.h"
#include "qintdict.h"
#include "qpopupmenu.h"
#include "qguardedptr.h"
#include "qcolor.h"
#include "qwidget.h"
#include "qfontmetrics.h"
#include "qcursor.h"
#include "qobjectlist.h"

QWidget *cursorWidget( QPoint * = 0 );

class QDragger;


class DropWindow : public QWidget
{
    Q_OBJECT
public:
    void paintEvent( QPaintEvent * );
    void closeEvent( QCloseEvent * );

    QDragger *master;
};


struct DropInfo {
    DropInfo()	{ w=0; }
   ~DropInfo()  { delete w; }
    DropWindow *w;
    bool userOpened;
};

struct DraggedInfo {
    QWidget *w;
    QWidget *mother;
    QPoint   pos;
};


class QDragger : public QObject
{
    Q_OBJECT
public:
    QDragger();
    ~QDragger();

    bool notify( QObject *, QEvent * ); // event filter
    void closeDropWindow( DropWindow * );
public slots:
    void openDropWindow();
    void killDropWindow();
    void killAllDropWindows();
    void sendChildHome();
    void sendAllChildrenHome();
private:
    bool isParentToDragged( QWidget * );
    bool noWidgets( QWidget * );
    void killDropWindow( DropInfo * );
    void killAllDropWindows( bool );
    void sendChildHome( DraggedInfo * );
    void sendAllChildrenHome( QWidget * );
    QWidget *openDropWindow( const QRect&, bool );

    bool startGrab();
    void grabFinished();
    bool dragEvent( QWidget *, QMouseEvent * );
    bool killDropEvent( QMouseEvent * );
    bool sendChildEvent( QMouseEvent * );

    bool		   killingDrop;
    bool		   sendingChild;
    QWidget		  *clickedWidget;
    QGuardedPtr<QWidget>   hostWidget;
    QCursor		   cursor;

    QPopupMenu*		   menu;
    QPoint		   clickOffset;
    QColor		   dragBackground;
    QColor		   dragForeground;
    DraggedInfo		   dragInfo;
    QIntDict<DraggedInfo>  draggedDict;
    QIntDict<DropInfo>	   dropDict;
};


QDragApplication::QDragApplication( int &argc, char **argv )
    : QApplication( argc, argv ), dragger( 0 )
{
    dragger = new QDragger;
}

QDragApplication::~QDragApplication()
{
    delete dragger;
}

bool QDragApplication::notify( QObject *o, QEvent *e )
{
    if ( dragger && !dragger->notify( o, e ) )
	return QApplication::notify( o, e );
    else
	return FALSE;
}

void DropWindow::paintEvent( QPaintEvent * )
{
    const char *msg    = "Drag widgets and drop them here or anywhere!";
    int		startX = ( width() - fontMetrics().width( msg ) )/2;
    startX	       = startX < 0 ? 0 : startX;

    drawText( startX, height()/2, msg );
}

void DropWindow::closeEvent( QCloseEvent *e )
{
    master->closeDropWindow( this );
    e->ignore();
}

QDragger::QDragger()
{
    dragInfo.w	 = 0;
    killingDrop	 = FALSE;
    sendingChild = FALSE;
    draggedDict.setAutoDelete( TRUE );
    dropDict   .setAutoDelete( TRUE );

    menu = new QPopupMenu;
    menu->insertItem( "Open drop window", 1 );
    menu->insertItem( "Kill drop window", 2 );
    menu->insertItem( "Kill all drop windows", 3 );
    menu->insertSeparator();
//    menu->insertItem( "Send child home", 4 );
    menu->insertItem( "Send all children home", 5 );

    menu->connectItem( 1, this, SLOT(openDropWindow()) );
    menu->connectItem( 2, this, SLOT(killDropWindow()) );
    menu->connectItem( 3, this, SLOT(killAllDropWindows()) );
//    menu->connectItem( 4, this, SLOT(sendChildHome()) );
    menu->connectItem( 5, this, SLOT(sendAllChildrenHome()) );
}

QDragger::~QDragger()
{
    delete menu;
}


bool QDragger::notify( QObject *o, QEvent *e )
{
    if ( !o->isWidgetType() || o == menu )
	return FALSE;
    switch( e->type() ) {
	case QEvent::MouseMove:
	     {
		 QMouseEvent *tmp = (QMouseEvent*) e;
		 if ( killingDrop )
		     return killDropEvent( tmp );
		 if ( sendingChild )
		     return sendChildEvent( tmp );
		 if ( tmp->state() & QMouseEvent::RightButton )
		     return dragEvent( (QWidget*) o, tmp );
		 break;
	     }
	case QEvent::MouseButtonPress:
	case QEvent::MouseButtonRelease:
	case QEvent::MouseButtonDblClick:
	     {
		 QMouseEvent *tmp = (QMouseEvent*) e;
		 if ( killingDrop )
		     return killDropEvent( tmp );
		 if ( sendingChild )
		     return sendChildEvent( tmp );
		 if ( tmp->button() == QMouseEvent::RightButton )
		     return dragEvent( (QWidget*) o, tmp );
	     }
	     break;
	default:
	     break;
    }
    return FALSE;
}

bool QDragger::isParentToDragged( QWidget *w )
{
    QIntDictIterator<DraggedInfo> iter( draggedDict );

    DraggedInfo *tmp;
    while( (tmp = iter.current()) ) {
	++iter;
	if ( tmp->mother == w )
	    return TRUE;
    }
    return FALSE;
}

bool QDragger::noWidgets( QWidget *w )
{
    const QObjectList *l = w->children();
    if ( !l )
	return TRUE;
    QObjectListIt iter( *l );
    QObject *tmp;
    while( (tmp = iter.current()) ) {
	++iter;
	if ( tmp->isWidgetType() )
	    return FALSE;
    }
    return TRUE;
}

void QDragger::sendAllChildrenHome( QWidget *w )
{
    const QObjectList *l = w->children();
    if ( !l )
	return;
    QObjectListIt iter( *l );
    QObject *tmp;
    while( (tmp = iter.current()) ) {
	++iter;
	if ( tmp->isWidgetType() ) {
	    sendAllChildrenHome( (QWidget*) tmp );
	    DraggedInfo *di = draggedDict.find( (long) tmp );
	    if ( di )
		sendChildHome( di );
	}
    }
}

bool QDragger::dragEvent( QWidget *w, QMouseEvent *e )
{
    switch( e->type() ) {
	case QEvent::MouseButtonDblClick:
	case QEvent::MouseButtonPress: {
	    if ( !noWidgets( w ) || // has widget children
		 isParentToDragged( w )	|| // has had widget children
		 w->parentWidget() == 0 ) {       // is top level window
		hostWidget = w;
		menu->popup( w->mapToGlobal( e->pos() ) );
		return TRUE;
	    }
	    if ( !draggedDict.find( (long) w ) ) {
		DraggedInfo *tmp = new DraggedInfo;
		tmp->w	      = w;
		tmp->mother      = w->parentWidget();
		tmp->pos	      = w->frameGeometry().topLeft();
		draggedDict.insert( (long) w, tmp );
	    }
	    dragBackground	 = w->backgroundColor();
	    dragForeground	 = w->foregroundColor();
	    dragInfo.w	 = w;
	    dragInfo.mother = w->parentWidget();
	    dragInfo.pos	 = w->frameGeometry().topLeft();
	    clickOffset = e->pos();
	    dragInfo.w  = w;
	    QPoint p    = w->mapToGlobal(QPoint(0,0));
	    w->reparent( 0, WType_Popup, p, TRUE );

	    return TRUE;
	}
	case QEvent::MouseButtonRelease:
	case QEvent::MouseMove: {
	    if ( dragInfo.w != 0 ) {
		QPoint p = QCursor::pos() - clickOffset;
		dragInfo.w->move( p );
		if ( e->type() == QEvent::MouseMove )
		    return TRUE;
	    } else {
		return FALSE;
	    }
	    if ( !dragInfo.w )
		return FALSE;
	    if ( w != dragInfo.w )
		w = dragInfo.w;
	    dragInfo.w = 0;
	    w->hide();
	    QPoint pos;
	    QWidget *target = cursorWidget( &pos );
	    pos = pos - clickOffset;
	    QPoint p;
	    if ( !target ) {
		target = openDropWindow( QRect( pos, w->size() ),
					 FALSE);
		p = QPoint( 0, 0 );
	    }
	    else
		p = target->mapFromGlobal( pos );
	    w->reparent( target, 0, p, TRUE );
	    DropInfo *tmp = dropDict.find( (long) dragInfo.mother );
	    if ( tmp ) {
		if ( !tmp->userOpened && noWidgets( tmp->w ) )
		    dropDict.remove( (long) tmp->w );
	    }
	    if ( !target->isVisible() )
		target->show();
	  }
	  return TRUE;
	default:
	  return FALSE;
    }
}

bool QDragger::killDropEvent( QMouseEvent *e )
{
    switch( e->type() ) {
	case QEvent::MouseButtonDblClick:
	case QEvent::MouseButtonPress:
	    clickedWidget = cursorWidget();
	    return TRUE;
	case QEvent::MouseButtonRelease:
	    hostWidget->releaseMouse();
	    if ( clickedWidget ) {
		DropInfo *tmp = dropDict.find( (long) clickedWidget );
		if( tmp ) {
		    killDropWindow( tmp );
		    dropDict.remove( (long) tmp->w );
		}
	    }
	    grabFinished();
	    return TRUE;
	case QEvent::MouseMove:
	    return TRUE;
	default:
	    break;
    }
    return FALSE;
}

bool QDragger::sendChildEvent( QMouseEvent *e )
{
    switch( e->type() ) {
	case QEvent::MouseButtonDblClick:
	case QEvent::MouseButtonPress:
	    clickedWidget = cursorWidget();
	    return TRUE;
	case QEvent::MouseButtonRelease:
	    hostWidget->releaseMouse();
	    if ( clickedWidget ) {
		DraggedInfo *tmp = draggedDict.find((long) clickedWidget);
		if( tmp ) {
		    QWidget *parent = tmp->w->parentWidget();
		    sendChildHome( tmp );
		    DropInfo *dri = dropDict.find( (long) parent );
		    if ( dri && noWidgets(dri->w) && !dri->userOpened ) {
			killDropWindow( dri );
			dropDict.remove( (long) dri );
		    }
		}
		grabFinished();
	    }
	    return TRUE;
	case QEvent::MouseMove:
	    return TRUE;
	default:
	    break;
    }
    return FALSE;
}

bool QDragger::startGrab()
{
    if ( !hostWidget )
	return FALSE;
    clickedWidget = 0;
    cursor	  = hostWidget->cursor();
    hostWidget->grabMouse();
    hostWidget->setCursor( QCursor( CrossCursor ) );
    return TRUE;
}

void QDragger::grabFinished()
{
    killingDrop	 = FALSE;
    sendingChild = FALSE;
    if(hostWidget)
	hostWidget->setCursor( cursor );
}

void QDragger::closeDropWindow( DropWindow *w )
{
    DropInfo *tmp = dropDict.find( (long) w);
    if( tmp )
	killDropWindow( tmp );
}

void QDragger::openDropWindow()
{
    QWidget *tmp = openDropWindow( QRect(100, 100, 300, 200), TRUE );
    tmp->show();
}

QWidget *QDragger::openDropWindow( const QRect &r, bool user )
{
    DropInfo *tmp = new DropInfo;
    DropWindow *w = new DropWindow;
    if ( user ) {
	tmp->userOpened = TRUE;
	w->setCaption( "Drop window" );
    } else {
	tmp->userOpened = FALSE;
	w->setCaption( "Auto drop window" );
    }
    tmp->w = w;
    w->master = this;
    w->setGeometry( r );
    dropDict.insert( (long) w, tmp );
    w->show();
    return w;
}

void QDragger::killDropWindow()
{
    if ( startGrab() )
	killingDrop   = TRUE;
}

void QDragger::killDropWindow( DropInfo *di )
{
    const QObjectList *l = di->w->children();
    if ( !l )
	return;
    QObjectListIt iter( *l );
    QObject *tmp;
    while( (tmp = iter.current()) ) {
	++iter;
	if ( tmp->isWidgetType() ) {
	    DraggedInfo *dri = draggedDict.find( (long) tmp );
	    if ( dri ) {
		sendChildHome( dri );
		draggedDict.remove( (long) tmp );
	    }
	}
    }
    di->w->hide();
}

void QDragger::killAllDropWindows()
{
    killAllDropWindows( FALSE );
}

void QDragger::killAllDropWindows( bool autoOnly )
{
    QIntDictIterator<DropInfo> iter( dropDict );

    DropInfo *tmp;
    while( (tmp = iter.current()) ) {
	++iter;
	if( !autoOnly || !tmp->userOpened ) {
	    killDropWindow( tmp );
	    dropDict.remove( (long) tmp->w );
	}
    }
}

void QDragger::sendChildHome( DraggedInfo *i )
{
    i->w->reparent( i->mother, 0, i->pos, TRUE );
}

void QDragger::sendChildHome()
{
    if ( startGrab() )
	sendingChild  = TRUE;
}

void QDragger::sendAllChildrenHome()
{
    QIntDictIterator<DraggedInfo> iter( draggedDict );

    DraggedInfo *tmp;
    while( (tmp = iter.current()) ) {
	++iter;
	sendChildHome( tmp );
	draggedDict.remove( (long) tmp->w );
    }
    killAllDropWindows( TRUE );
    draggedDict.clear();
}


QWidget *cursorWidget( QPoint *p )
{
    QPoint curpos = QCursor::pos();
    if ( p )
	*p = curpos;
    return QApplication::widgetAt( curpos );
}


#include "qdragapp.moc"
