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

#include "qdragapp.h"
#include "ntqptrlist.h"
#include "ntqintdict.h"
#include "ntqpopupmenu.h"
#include "ntqguardedptr.h"
#include "ntqcolor.h"
#include "ntqwidget.h"
#include "ntqfontmetrics.h"
#include "ntqcursor.h"
#include "ntqobjectlist.h"

TQWidget *cursorWidget( TQPoint * = 0 );

class TQDragger;


class DropWindow : public TQWidget
{
    TQ_OBJECT
public:
    void paintEvent( TQPaintEvent * );
    void closeEvent( TQCloseEvent * );

    TQDragger *master;
};


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

struct DraggedInfo {
    TQWidget *w;
    TQWidget *mother;
    TQPoint   pos;
};


class TQDragger : public TQObject
{
    TQ_OBJECT
public:
    TQDragger();
    ~TQDragger();

    bool notify( TQObject *, TQEvent * ); // event filter
    void closeDropWindow( DropWindow * );
public slots:
    void openDropWindow();
    void killDropWindow();
    void killAllDropWindows();
    void sendChildHome();
    void sendAllChildrenHome();
private:
    bool isParentToDragged( TQWidget * );
    bool noWidgets( TQWidget * );
    void killDropWindow( DropInfo * );
    void killAllDropWindows( bool );
    void sendChildHome( DraggedInfo * );
    void sendAllChildrenHome( TQWidget * );
    TQWidget *openDropWindow( const TQRect&, bool );

    bool startGrab();
    void grabFinished();
    bool dragEvent( TQWidget *, TQMouseEvent * );
    bool killDropEvent( TQMouseEvent * );
    bool sendChildEvent( TQMouseEvent * );

    bool		   killingDrop;
    bool		   sendingChild;
    TQWidget		  *clickedWidget;
    TQGuardedPtr<TQWidget>   hostWidget;
    TQCursor		   cursor;

    TQPopupMenu*		   menu;
    TQPoint		   clickOffset;
    TQColor		   dragBackground;
    TQColor		   dragForeground;
    DraggedInfo		   dragInfo;
    TQIntDict<DraggedInfo>  draggedDict;
    TQIntDict<DropInfo>	   dropDict;
};


TQDragApplication::TQDragApplication( int &argc, char **argv )
    : TQApplication( argc, argv ), dragger( 0 )
{
    dragger = new TQDragger;
}

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

bool TQDragApplication::notify( TQObject *o, TQEvent *e )
{
    if ( dragger && !dragger->notify( o, e ) )
	return TQApplication::notify( o, e );
    else
	return FALSE;
}

void DropWindow::paintEvent( TQPaintEvent * )
{
    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( TQCloseEvent *e )
{
    master->closeDropWindow( this );
    e->ignore();
}

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

    menu = new TQPopupMenu;
    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()) );
}

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


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

bool TQDragger::isParentToDragged( TQWidget *w )
{
    TQIntDictIterator<DraggedInfo> iter( draggedDict );

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

bool TQDragger::noWidgets( TQWidget *w )
{
    const TQObjectList *l = w->children();
    if ( !l )
	return TRUE;
    TQObjectListIt iter( *l );
    TQObject *tmp;
    while( (tmp = iter.current()) ) {
	++iter;
	if ( tmp->isWidgetType() )
	    return FALSE;
    }
    return TRUE;
}

void TQDragger::sendAllChildrenHome( TQWidget *w )
{
    const TQObjectList *l = w->children();
    if ( !l )
	return;
    TQObjectListIt iter( *l );
    TQObject *tmp;
    while( (tmp = iter.current()) ) {
	++iter;
	if ( tmp->isWidgetType() ) {
	    sendAllChildrenHome( (TQWidget*) tmp );
	    DraggedInfo *di = draggedDict.find( (long) tmp );
	    if ( di )
		sendChildHome( di );
	}
    }
}

bool TQDragger::dragEvent( TQWidget *w, TQMouseEvent *e )
{
    switch( e->type() ) {
	case TQEvent::MouseButtonDblClick:
	case TQEvent::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;
	    TQPoint p    = w->mapToGlobal(TQPoint(0,0));
	    w->reparent( 0, WType_Popup, p, TRUE );

	    return TRUE;
	}
	case TQEvent::MouseButtonRelease:
	case TQEvent::MouseMove: {
	    if ( dragInfo.w != 0 ) {
		TQPoint p = TQCursor::pos() - clickOffset;
		dragInfo.w->move( p );
		if ( e->type() == TQEvent::MouseMove )
		    return TRUE;
	    } else {
		return FALSE;
	    }
	    if ( !dragInfo.w )
		return FALSE;
	    if ( w != dragInfo.w )
		w = dragInfo.w;
	    dragInfo.w = 0;
	    w->hide();
	    TQPoint pos;
	    TQWidget *target = cursorWidget( &pos );
	    pos = pos - clickOffset;
	    TQPoint p;
	    if ( !target ) {
		target = openDropWindow( TQRect( pos, w->size() ),
					 FALSE);
		p = TQPoint( 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 TQDragger::killDropEvent( TQMouseEvent *e )
{
    switch( e->type() ) {
	case TQEvent::MouseButtonDblClick:
	case TQEvent::MouseButtonPress:
	    clickedWidget = cursorWidget();
	    return TRUE;
	case TQEvent::MouseButtonRelease:
	    hostWidget->releaseMouse();
	    if ( clickedWidget ) {
		DropInfo *tmp = dropDict.find( (long) clickedWidget );
		if( tmp ) {
		    killDropWindow( tmp );
		    dropDict.remove( (long) tmp->w );
		}
	    }
	    grabFinished();
	    return TRUE;
	case TQEvent::MouseMove:
	    return TRUE;
	default:
	    break;
    }
    return FALSE;
}

bool TQDragger::sendChildEvent( TQMouseEvent *e )
{
    switch( e->type() ) {
	case TQEvent::MouseButtonDblClick:
	case TQEvent::MouseButtonPress:
	    clickedWidget = cursorWidget();
	    return TRUE;
	case TQEvent::MouseButtonRelease:
	    hostWidget->releaseMouse();
	    if ( clickedWidget ) {
		DraggedInfo *tmp = draggedDict.find((long) clickedWidget);
		if( tmp ) {
		    TQWidget *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 TQEvent::MouseMove:
	    return TRUE;
	default:
	    break;
    }
    return FALSE;
}

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

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

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

void TQDragger::openDropWindow()
{
    TQWidget *tmp = openDropWindow( TQRect(100, 100, 300, 200), TRUE );
    tmp->show();
}

TQWidget *TQDragger::openDropWindow( const TQRect &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 TQDragger::killDropWindow()
{
    if ( startGrab() )
	killingDrop   = TRUE;
}

void TQDragger::killDropWindow( DropInfo *di )
{
    const TQObjectList *l = di->w->children();
    if ( !l )
	return;
    TQObjectListIt iter( *l );
    TQObject *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 TQDragger::killAllDropWindows()
{
    killAllDropWindows( FALSE );
}

void TQDragger::killAllDropWindows( bool autoOnly )
{
    TQIntDictIterator<DropInfo> iter( dropDict );

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

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

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

void TQDragger::sendAllChildrenHome()
{
    TQIntDictIterator<DraggedInfo> iter( draggedDict );

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


TQWidget *cursorWidget( TQPoint *p )
{
    TQPoint curpos = TQCursor::pos();
    if ( p )
	*p = curpos;
    return TQApplication::widgetAt( curpos );
}


#include "qdragapp.moc"