/*
**************************************************************************
                                 description
                             --------------------
    copyright            : (C) 2000-2003 by Andreas Zehender
    email                : zehender@kde.org
**************************************************************************

**************************************************************************
*                                                                        *
*  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.                                   *
*                                                                        *
**************************************************************************/


#include <stdlib.h>

#include <qlistview.h>
#include <qheader.h>
#include <qlayout.h>
#include <qpopupmenu.h>
#include <qcursor.h>

#include <klocale.h>
#include <kmessagebox.h>
#include <kglobalsettings.h>
#include <kiconloader.h>
#include <kxmlguifactory.h>

#include "pmtreeview.h"
#include "pmtreeviewitem.h"
#include "pmcommand.h"
#include "pmpart.h"
#include "pmscene.h"
#include "pmobjectdrag.h"


PMTreeViewWidget::PMTreeViewWidget( PMPart* part, QWidget* parent /*= 0*/,
                                    const char* name /*=0*/ )
      : PMViewBase( parent, name )
{
   QHBoxLayout* hl = new QHBoxLayout( this );
   PMTreeView* tv = new PMTreeView( part, this );
   hl->addWidget( tv );
}

QString PMTreeViewWidget::description( ) const
{
   return i18n( "Object Tree" );
}

PMTreeView::PMTreeView( PMPart* part, QWidget* parent /*= 0*/,
                        const char* name /*= 0*/ )
      : QListView( parent, name )
{
   addColumn( i18n( "Objects" ) );
   header( )->hide( );
   setRootIsDecorated( true );
   setSorting( -1 );
   setSelectionMode( Multi );
   m_pPart = part;

   m_itemSelected = false;
   m_itemDeselected = false;
   m_selectionCleared = false;
   m_pLastSelected = 0;
   m_event = false;
   m_pressed = false;
   m_pDragOverItem = 0;
   m_acceptSelect = false;
   m_pressedItem = 0;

   viewport( )->setAcceptDrops( true );
   viewport( )->setMouseTracking( true );
   viewport( )->setFocusPolicy( QWidget::WheelFocus );
   setFocusPolicy( QWidget::WheelFocus );
   setAcceptDrops( true );

   connect( part, SIGNAL( refresh( ) ), SLOT( slotRefresh( ) ) );
   connect( part, SIGNAL( objectChanged( PMObject*, const int, QObject* ) ),
                  SLOT( slotObjectChanged( PMObject*, const int, QObject* ) ) );
   connect( part, SIGNAL( clear( ) ), SLOT( slotClear( ) ) );
   connect( this, SIGNAL( objectChanged( PMObject*, const int, QObject* ) ),
            part, SLOT( slotObjectChanged( PMObject*, const int, QObject* ) ) );

   slotRefresh( );
}

PMTreeView::~PMTreeView( )
{
   emit destroyed( this );
}

void PMTreeView::slotObjectChanged( PMObject* obj, const int mode,
                                    QObject* sender )
{
   PMTreeViewItem* pTreeItem = 0;
   bool as = m_acceptSelect;
   m_acceptSelect = true;

   if( sender != this )
   {
      if( ( mode & PMCAdd ) && !( mode & PMCInsertError ) )
      {
         // object was added
         if( !obj->parent( ) )
         {
            // object has no parent, append it as top level item
            pTreeItem = new PMTreeViewItem( obj, this );
         }
         else
         {
            // find the parent in the listview
            QListViewItem* pParentTreeItem = findObject( obj->parent( ) );
            if( pParentTreeItem )
            {
               PMObject* hObj = obj->prevSibling( );
               QListViewItem* pSibling = 0;
               bool found = false;

               if( hObj )
               {
                  // find the previous sibling
                  pSibling = pParentTreeItem->firstChild( );
                  while( pSibling && !found )
                  {
                     if( ( ( PMTreeViewItem* ) pSibling )->object( ) == hObj )
                        found = true;
                     else
                        pSibling = pSibling->nextSibling( );
                  }
               }
               if( found )
               {
                  // object has sibling
                  pTreeItem = new PMTreeViewItem( obj, pParentTreeItem, pSibling );
               }
               else
               {
                  // object has no sibling
                  pTreeItem = new PMTreeViewItem( obj, pParentTreeItem );
               }
            }
         }

         if( pTreeItem )
         {
            // add child items if necessary
            if( obj->countChildren( ) > 0 )
               addChildItems( pTreeItem );
         }
      }
      if( mode & PMCDescription )
      {
         if( !pTreeItem )
            pTreeItem = findObject( obj );

         if( pTreeItem )
            pTreeItem->setDescriptions( );
      }
      if( mode & PMCChildren )
      {
         if( !pTreeItem )
            pTreeItem = findObject( obj );

         if( pTreeItem )
         {
            // delete old items
            while( pTreeItem->firstChild( ) )
               delete pTreeItem->firstChild( );
            // create new
            addChildItems( pTreeItem );
            pTreeItem->setOpen( true );
         }
      }
      if( mode & PMCNewSelection )
      {
         clearSelection( );

         if( obj )
         {
            if( !pTreeItem )
               pTreeItem = findObject( obj );

            if( pTreeItem )
            {
               PMTreeViewItem* p;
               for( p = pTreeItem->parent( ); p; p = p->parent( ) )
                  p->setOpen( true );
               pTreeItem->setSelected( true );
               setCurrentItem( pTreeItem );
            }
         }
      }
      if( mode & PMCDeselected )
      {
         if( !pTreeItem )
            pTreeItem = findObject( obj );
         pTreeItem->setSelected( false );
      }
      if( mode & PMCSelected )
      {
         if( !pTreeItem )
            pTreeItem = findObject( obj );
         pTreeItem->setSelected( true );
      }
      if( mode & PMCRemove )
      {
         // object was removed, remove the listview item
         if( !pTreeItem )
            pTreeItem = findObject( obj );
         delete( pTreeItem );
      }
      if( mode & PMCData )
      {
         // special case for texture maps
         if( obj )
         {
            if( obj->isA( "TextureMapBase" ) )
            {
               if( !pTreeItem )
                  pTreeItem = findObject( obj );
               if( pTreeItem )
               {
                  PMTreeViewItem* it = ( PMTreeViewItem* ) pTreeItem->firstChild( );
                  for( ; it; it = ( PMTreeViewItem* ) it->nextSibling( ) )
                     it->setDescriptions( );
               }
            }
         }
      }
   }
   m_acceptSelect = as;
}


PMTreeViewItem* PMTreeView::findObject( const PMObject* obj )
{
   PMTreeViewItem* pTreeItem = 0;

   if( !obj->parent( ) )
   {
      // top level object
      pTreeItem = ( PMTreeViewItem* ) firstChild( );
      for( ; pTreeItem; pTreeItem = ( PMTreeViewItem* ) pTreeItem->nextSibling( ) )
         if( pTreeItem->object( ) == obj )
            return pTreeItem;
   }
   else
   {
      pTreeItem = findObject( obj->parent( ) );
      if( pTreeItem )
      {
         pTreeItem = ( PMTreeViewItem* ) pTreeItem->firstChild( );
         for( ; pTreeItem; pTreeItem = ( PMTreeViewItem* ) pTreeItem->nextSibling( ) )
            if( pTreeItem->object( ) == obj )
               return pTreeItem;
      }
   }
   return 0;
}


void PMTreeView::selectItem( QListViewItem* /*sitem*/ )
{
/*   QListViewItem* pItem = 0;
   bool emitSig;
   emitSig = ( m_pSelectedObject != ( ( PMTreeViewItem* ) sitem )->object( ) );

   m_pSelectedObject = ( ( PMTreeViewItem* ) sitem )->object( );

   for( pItem = sitem->parent( ); pItem; pItem = pItem->parent( ) )
      pItem->setOpen( true );
   ensureItemVisible( sitem );
   setCurrentItem( sitem );
   setSelected( sitem, true );
   if( emitSig )
      emit objectSelected( m_pSelectedObject );
*/
}

void PMTreeView::addChildItems( PMTreeViewItem* item )
{
   PMObject* obj = 0;
   PMTreeViewItem* listItem = 0;

   for( obj = item->object( )->firstChild( ); obj; obj = obj->nextSibling( ) )
   {
      // insert all child objects
      if( listItem )
         listItem = new PMTreeViewItem( obj, item, listItem );
      else
         // first child
         listItem = new PMTreeViewItem( obj, item );
      // recursive call, if child has children
      if( obj->countChildren( ) > 0 )
         addChildItems( listItem );
   }
}

void PMTreeView::slotRefresh( )
{
   PMTreeViewItem* item;
   slotClear( );
   // insert the top level items
   if( m_pPart->scene( ) )
   {
      item = new PMTreeViewItem( m_pPart->scene( ), this );
      addChildItems( item );
      item->setOpen( true );
//   item = new PMTreeViewItem( m_pPart->insertErrors( ), this );
//   addChildItems( item );
//   item->setOpen( true );
   }
}

void PMTreeView::slotClear( )
{
   clear( );
   m_pLastSelected = 0;
   m_pDragOverItem = 0;
   m_pressedItem = 0;
}

void PMTreeView::itemSelected( PMTreeViewItem* item, bool selected )
{
   repaintItem( item );

   if( m_event )
   {
      m_pLastSelected = item;

      if( selected )
         m_itemSelected = true;
      else
      {
         if( m_itemDeselected )
            m_selectionCleared = true;
         else
            m_itemDeselected = true;
      }
   }
}

void PMTreeView::contentsMousePressEvent( QMouseEvent * e )
{
   m_itemSelected = false;
   m_itemDeselected = false;
   m_pLastSelected = 0;
   m_selectionCleared = false;
   m_selectOnReleaseEvent = false;
   bool specialAction = false;

   QListViewItem* oldCurrent = currentItem( );

   m_event = true;
   m_acceptSelect = true;
   QListView::contentsMousePressEvent( e );
   m_event = false;
   m_acceptSelect = true;

   if( m_selectionCleared )
   {
      emit objectChanged( 0, PMCNewSelection, this );
      specialAction = true;
   }
   else if( m_itemSelected || m_itemDeselected )
   {
      if( !( e->state( ) & ( ShiftButton | ControlButton ) ) )
      {
         specialAction = true;
         // simple click, deselect all selected item
         // m_pLastSelected is the new selection

         if( m_itemSelected )
         {
            clearSelection( );
            m_pLastSelected->setSelected( true );

            emit objectChanged( m_pLastSelected->object( ), PMCNewSelection,
                                this );
         }
         else
         {
            m_selectOnReleaseEvent = true;
            m_pLastSelected->setSelected( true );
         }
      }
      else if( ( e->state( ) & ShiftButton ) && oldCurrent && m_pLastSelected )
      {
         if( ( oldCurrent != m_pLastSelected ) &&
             ( oldCurrent->parent( ) == m_pLastSelected->parent( ) ) )
         {
            specialAction = true;

            // shift click, old current item has the same parent
            // as the new selection. Select all items between the two
            // items
            if( m_pLastSelected->object( )->isSelectable( ) )
            {
               bool down = oldCurrent->itemPos( ) < m_pLastSelected->itemPos( );
               QListViewItem* tmp;

               if( down )
               {
                  for( tmp = oldCurrent; tmp; tmp = tmp->nextSibling( ) )
                  {
                     tmp->setSelected( true );
                     emit objectChanged( (( PMTreeViewItem* ) tmp)->object( ),
                                         PMCSelected, this );
                     if( tmp == m_pLastSelected )
                        break;
                  }
               }
               else
               {
                  for( tmp = m_pLastSelected; tmp; tmp = tmp->nextSibling( ) )
                  {
                     tmp->setSelected( true );
                     emit objectChanged( (( PMTreeViewItem* ) tmp)->object( ),
                                         PMCSelected, this );
                     if( tmp == oldCurrent )
                        break;
                  }
               }
            }
            else
               m_pLastSelected->setSelected( false );
         }
      }
   }
   if( !specialAction )
   {
      // no special action
      // object is selected or deselected, no other objects are changed
      if( m_itemSelected )
      {
         if( m_pLastSelected->object( )->isSelectable( ) )
            emit objectChanged( m_pLastSelected->object( ), PMCSelected, this );
         else
            m_pLastSelected->setSelected( false );
      }
      else if( m_itemDeselected )
         emit objectChanged( m_pLastSelected->object( ), PMCDeselected, this );
   }
   m_acceptSelect = false;
}

void PMTreeView::contentsMouseMoveEvent( QMouseEvent * e )
{
   m_itemSelected = false;
   m_itemDeselected = false;
   m_pLastSelected = 0;
   m_selectionCleared = false;

   m_event = true;
   QListView::contentsMouseMoveEvent( e );
   m_event = false;

   // ignore all selections/deselections
   if( m_itemSelected || m_itemDeselected )
      m_pLastSelected->setSelected( m_pLastSelected->object( )->isSelected( ) );
}

void PMTreeView::viewportMousePressEvent( QMouseEvent* e )
{
   m_acceptSelect = true;
   QListView::viewportMousePressEvent( e );
   m_acceptSelect = false;

   m_pressed = false;

   QPoint p = e->pos( );

   if( e->button( ) & RightButton )
   {
      if( m_pPart->factory( ) ) 
      {
         QPopupMenu* m =
            ( QPopupMenu* ) m_pPart->factory( )->container( "treeViewPopup", m_pPart );
         if( m )
            m->exec( QCursor::pos( ) );
      }
      return;
   }

   PMTreeViewItem *item = ( PMTreeViewItem* )itemAt( p );
   if( item )
   {
      // check if the root decoration was clicked
      if( !( p.x( ) > header( )->cellPos( header( )->mapToActual( 0 ) ) +
             treeStepSize( ) * ( item->depth( ) + ( rootIsDecorated( ) ? 1 : 0 ) )
             + itemMargin( ) ||
             p.x( ) < header( )->cellPos( header( )->mapToActual( 0 ) ) ) )
         item = 0; // p is on the root decoration
   }

   if( item )
   {
      if( e->button( ) == LeftButton || e->button( ) == MidButton )
      {
         m_pressed = true;
         m_pressedPos = e->pos( );
         m_pressedItem = item;
         return;
      }
   }
}

void PMTreeView::viewportMouseReleaseEvent( QMouseEvent* e )
{
   QListView::viewportMouseReleaseEvent( e );

   if( !m_pressed )
      return;

   m_pressed = false;
   m_pressedItem = 0L;

   if( m_selectOnReleaseEvent )
   {
      if( m_pLastSelected )
      {
         m_acceptSelect = true;
         clearSelection( );
         m_pLastSelected->setSelected( true );
         m_acceptSelect = false;

         emit objectChanged( m_pLastSelected->object( ), PMCNewSelection, this );
      }
   }
}

void PMTreeView::viewportMouseMoveEvent( QMouseEvent *e )
{
   QListView::viewportMouseMoveEvent( e );

   if( m_pressed && m_pressedItem )
   {
      int x = e->pos( ).x( );
      int y = e->pos( ).y( );

      //Is it time to start a drag?
      if( abs( x - m_pressedPos.x( ) ) > KGlobalSettings::dndEventDelay( ) ||
           abs( y - m_pressedPos.y( ) ) > KGlobalSettings::dndEventDelay( ) )
      {
         m_selectOnReleaseEvent = false;

         // Calculate hotspot
         QPoint hotspot;
         PMObjectList sortedList = m_pPart->selectedObjects( );

         // Do not handle more mouse move or mouse release events
         m_pressed = false;

         if( sortedList.count( ) > 0 )
         {
            PMObjectDrag* d = new PMObjectDrag( m_pPart, sortedList, viewport( ) );

            hotspot.setX( m_pressedItem->pixmap( 0 )->width( ) / 2 );
            hotspot.setY( m_pressedItem->pixmap( 0 )->height( ) / 2 );
            if( sortedList.count( ) == 1 )
               d->setPixmap( SmallIcon(
                  sortedList.first( )->pixmap( ) ), hotspot );
            else
               d->setPixmap( SmallIcon( "pmdrag" ) );

            if( d->drag( ) )
            {
               kdDebug( PMArea ) << "Drag returned true\n";
               if( !targetDisplaysPart( d->target( ) ) )
                  m_pPart->dragMoveSelectionTo( 0 );
            }
         }
      }
   }
}

void PMTreeView::viewportDragMoveEvent( QDragMoveEvent *e )
{
   bool accept = false;

   if( m_pPart->isReadWrite( ) )
   {
      if( PMObjectDrag::canDecode( e, m_pPart ) )
      {
         PMTreeViewItem *item = ( PMTreeViewItem* ) itemAt( e->pos( ) );
         PMObject* obj = 0;

         if( !item )
         {
            accept = false;
            /*
            if( e->source( ) == viewport( ) )
            {
               if( m_pPart->scene( )->isSelected( ) )
                  accept = false;
               else
                  accept = true;
            }
            else
               accept = true;
            obj = m_pPart->scene( );
            */

            m_pDragOverItem = 0L;
            obj = 0;
         }
         else
         {
            obj = item->object( );
            if( ( obj->isSelectable( ) &&
                   !obj->isSelected( ) ) || ( e->source( ) != viewport( ) ) )
            {
               accept = true;
               setCurrentItem( item );
               m_pDragOverItem = item;
            }
            else
            {
               accept = false;
               m_pDragOverItem = 0L;
            }
         }

         if( accept )
         {
            accept = false;
            if( !obj->isReadOnly( ) )
               accept = true;
            if( obj->parent( ) )
               if( !obj->parent( )->isReadOnly( ) )
                  accept = true;
         }
      }
      else
         accept = false;
   }
   else
      accept = false;

   if( accept )
      e->acceptAction( );
   else
      e->ignore( );
}

void PMTreeView::viewportDragEnterEvent( QDragEnterEvent *e )
{
   m_pDragOverItem = 0L;

   if( m_pPart->isReadWrite( ) )
      e->accept( PMObjectDrag::canDecode( e, m_pPart ) );
   else
      e->ignore( );
}

void PMTreeView::viewportDragLeaveEvent( QDragLeaveEvent* )
{
   m_pDragOverItem = 0L;
}

void PMTreeView::viewportDropEvent( QDropEvent* e )
{
   PMObject* obj;

   if( m_pPart->isReadWrite( ) )
   {
      if( m_pDragOverItem )
         obj = m_pDragOverItem->object( );
      else
         obj = m_pPart->scene( );

      if( PMObjectDrag::canDecode( e, m_pPart ) )
      {
         if( targetDisplaysPart( e->source( ) ) &&
             ( e->action( ) == QDropEvent::Move ) )
         {
            if( m_pPart->dragMoveSelectionTo( obj ) )
               e->acceptAction( );
            else
               e->ignore( );
         }
         else
         {
            if( m_pPart->drop( obj, e ) )
               e->acceptAction( );
            else
               e->ignore( );
         }
      }
      else
         e->ignore( );
   }
   else
      e->ignore( );

   m_pDragOverItem = 0L;
}

void PMTreeView::focusOutEvent( QFocusEvent* e )
{
   QWidget::focusOutEvent( e );
   m_pressed = false;
   m_pressedItem = 0;
}

void PMTreeView::focusInEvent( QFocusEvent* e )
{
   QWidget::focusInEvent( e );
   m_pressed = false;
   m_pressedItem = 0;
}

void PMTreeView::keyPressEvent( QKeyEvent* e )
{
   QListViewItem* current = currentItem( );
   QListViewItem* newSelection = 0;
   bool accept = false;
   bool deleteItem = false;
   bool pasteItem = false;

   if( current )
   {
      switch( e->key( ) )
      {
         case Qt::Key_Up:
            newSelection = current->itemAbove( );
            accept = true;
            break;
         case Qt::Key_Down:
            newSelection = current->itemBelow( );
            accept = true;
            break;
         case Qt::Key_Left:
            newSelection = current->parent( );
            accept = true;
            break;
         case Qt::Key_Right:
            newSelection = current->firstChild( );
            accept = true;
            break;
         case Qt::Key_Plus:
            current->setOpen( true );
            accept = true;
            break;
         case Qt::Key_Minus:
            current->setOpen( false );
            accept = true;
         case Qt::Key_Delete:
            deleteItem = true;
            accept = true;
            break;
         case Qt::CTRL+Qt::Key_V:
         case Qt::SHIFT+Qt::Key_Insert:
            pasteItem = true;
            accept = true;
            break;
      }
   }

   if( newSelection )
   {
      m_acceptSelect = true;
      clearSelection( );
      newSelection->setSelected( true );
      setCurrentItem( newSelection );
      ensureItemVisible( newSelection );
      m_acceptSelect = false;

      emit objectChanged( ( ( PMTreeViewItem* ) newSelection )->object( ),
                          PMCNewSelection, this );
   }

   if( deleteItem && m_pPart->isReadWrite( ) )
   {
      m_pPart->slotEditDelete( );
      m_pPart->setModified( true );
   }
   
   if( pasteItem && m_pPart->isReadWrite( ) )
   {
      m_pPart->slotEditPaste( );
      m_pPart->setModified( true );
   }

   if( accept )
      e->accept( );
   else
      e->ignore( );
   QWidget::keyPressEvent( e );
}

bool PMTreeView::targetDisplaysPart( QWidget* target )
{
   bool result = false;
   if( !target ) // another application
      result = false;
   else if( target == viewport( ) ) // self
      result = true;
   else
   {
      // Widget may be a view port
      // find the tree view
      QWidget* t = target;
      while( t && !t->isA( "PMTreeView" ) )
         t = t->parentWidget( );
      if( t )
         if( ( ( PMTreeView* ) t )->part( ) == m_pPart )
            result = true;
   }
   return result;
}

QString PMTreeViewFactory::description( ) const
{
   return i18n( "Object Tree" );
}

#include "pmtreeview.moc"
