/*=========================================================================

  Program:   CMB
  Module:    qtCMBTreeWidget.cxx

Copyright (c) 1998-2005 Kitware Inc. 28 Corporate Drive, Suite 204,
Clifton Park, NY, 12065, USA.

All rights reserved. No part of this software may be reproduced,
distributed,
or modified, in any form or by any means, without permission in writing from
Kitware Inc.

IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY FOR
DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY DERIVATIVES THEREOF,
EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES,
INCLUDING,
BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
PARTICULAR PURPOSE, AND NON-INFRINGEMENT.  THIS SOFTWARE IS PROVIDED ON AN
"AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE NO OBLIGATION TO
PROVIDE
MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.

=========================================================================*/

#include "qtCMBTreeWidget.h"

#include <QApplication>
#include <QDropEvent>
#include <QPainter>
#include <QStyle>
#include <QTimer>
#include <QHeaderView>
#include <QLayout>
#include <QDragEnterEvent>
#include <QDragMoveEvent>
#include <QMouseEvent>
#include <QPersistentModelIndex>
#include <QPoint>

// enum for different pixmap types
enum qtCMBTreeWidgetPixmap
{
  pqCheck               = 0,
  pqPartialCheck        = 1,
  pqUnCheck             = 2,

  // All active states in lower half
  pqCheck_Active        = 3,
  pqPartialCheck_Active = 4,
  pqUnCheck_Active      = 5,

  pqMaxCheck            = 6
};

//-----------------------------------------------------------------------------
// array of style corresponding with the qtCMBTreeWidgetPixmap enum
static const QStyle::State qtCMBTreeWidgetPixmapStyle[] =
{
  QStyle::State_On | QStyle::State_Enabled,
  QStyle::State_NoChange | QStyle::State_Enabled,
  QStyle::State_Off | QStyle::State_Enabled,
  QStyle::State_On | QStyle::State_Enabled | QStyle::State_Active,
  QStyle::State_NoChange | QStyle::State_Enabled | QStyle::State_Active,
  QStyle::State_Off | QStyle::State_Enabled | QStyle::State_Active
};

//-----------------------------------------------------------------------------
QPixmap qtCMBTreeWidget::pixmap(Qt::CheckState cs, bool active)
{
  int offset = active ? pqMaxCheck/2 : 0;
  switch(cs)
    {
  case Qt::Checked:
    return *this->CheckPixmaps[offset + pqCheck];
  case Qt::Unchecked:
    return *this->CheckPixmaps[offset + pqUnCheck];
  case Qt::PartiallyChecked:
    return *this->CheckPixmaps[offset + pqPartialCheck];
    }
  return QPixmap();
}

//-----------------------------------------------------------------------------
qtCMBTreeWidget::qtCMBTreeWidget(QWidget* p)
  : QTreeWidget(p)
{

  QStyleOptionButton option;
  QRect r = this->style()->subElementRect(QStyle::SE_CheckBoxIndicator,
                                          &option, this);
  option.rect = QRect(QPoint(0,0), r.size());

  this->CheckPixmaps = new QPixmap*[6];
  for(int i=0; i<pqMaxCheck; i++)
    {
    this->CheckPixmaps[i] = new QPixmap(r.size());
    this->CheckPixmaps[i]->fill(QColor(0,0,0,0));
    QPainter painter(this->CheckPixmaps[i]);
    option.state = qtCMBTreeWidgetPixmapStyle[i];

    this->style()->drawPrimitive(QStyle::PE_IndicatorCheckBox, &option,
                         &painter, this);
    }

/*
  QObject::connect(this->header(), SIGNAL(sectionClicked(int)),
                   this, SLOT(doToggle(int)),
                   Qt::QueuedConnection);

  this->header()->setClickable(true);

  QObject::connect(this->model(), SIGNAL(dataChanged(QModelIndex, QModelIndex)),
                   this, SLOT(updateCheckState()));
  QObject::connect(this->model(), SIGNAL(rowsInserted(QModelIndex, int, int)),
                   this, SLOT(updateCheckState()));

  QObject::connect(this->model(), SIGNAL(rowsInserted(QModelIndex, int, int)),
                   this, SLOT(invalidateLayout()));
  QObject::connect(this->model(), SIGNAL(rowsRemoved(QModelIndex, int, int)),
                   this, SLOT(invalidateLayout()));
  QObject::connect(this->model(), SIGNAL(modelReset()),
                   this, SLOT(invalidateLayout()));
*/
  this->Timer = new QTimer(this);
  this->Timer->setSingleShot(true);
  this->Timer->setInterval(10);
  //QObject::connect(this->Timer, SIGNAL(timeout()),
  //  this, SLOT(updateCheckStateInternal()));

}

//-----------------------------------------------------------------------------
qtCMBTreeWidget::~qtCMBTreeWidget()
{
  delete this->Timer;
  for(int i=0; i<pqMaxCheck; i++)
    {
    delete this->CheckPixmaps[i];
    }
  delete [] this->CheckPixmaps;
}

//-----------------------------------------------------------------------------
bool qtCMBTreeWidget::event(QEvent* e)
{
/*
  if(e->type() == QEvent::FocusIn ||
     e->type() == QEvent::FocusOut)
    {
    bool convert = false;
    int cs = this->headerItem()->data(0, Qt::CheckStateRole).toInt(&convert);
    if(convert)
      {
      bool active = e->type() == QEvent::FocusIn;
      this->headerItem()->setData(0, Qt::DecorationRole,
                            pixmap(static_cast<Qt::CheckState>(cs), active));
      }
    }
*/
  return Superclass::event(e);
}

//-----------------------------------------------------------------------------
void qtCMBTreeWidget::updateCheckState()
{
  this->Timer->start();
  // updateCheckStateInternal needs to call rowIndex() which forces the tree to
  // sort. Hence when multiple items are being added/updated the tree is sorted
  // after every insert. To avoid that we use this timer.
}

//-----------------------------------------------------------------------------
void qtCMBTreeWidget::updateCheckStateInternal()
{
  Qt::CheckState newState = Qt::Checked;
  int numChecked = 0;
  int numPartial = 0;
  int numUnchecked = 0;
  QAbstractItemModel* m = this->model();
  int numRows = m->rowCount(QModelIndex());
  for(int i=0; i<numRows; i++)
    {
    QModelIndex idx = m->index(i, 0);
    bool convert = 0;
    int v = m->data(idx, Qt::CheckStateRole).toInt(&convert);
    if(convert)
      {
      if(v == Qt::Checked)
        {
        numChecked++;
        }
      else if (v == Qt::PartiallyChecked)
        {
        numPartial++;
        }
      else
        {
        numUnchecked++;
        }
      }
    }

  // if there are no check boxes at all
  if(0 == (numUnchecked + numPartial + numChecked))
    {
    return;
    }

  if(numChecked != numRows)
    {
      newState = ((numChecked==0) && (numPartial==0))
        ? Qt::Unchecked : Qt::PartiallyChecked;
    }

  this->headerItem()->setCheckState(0, newState);
  this->headerItem()->setData(0, Qt::DecorationRole,
                              pixmap(newState, this->hasFocus()));
}

//-----------------------------------------------------------------------------
void qtCMBTreeWidget::allOn()
{
  QTreeWidgetItem* item;
  int i, end = this->topLevelItemCount();
  for(i=0; i<end; i++)
    {
    item = this->topLevelItem(i);
    item->setCheckState(0, Qt::Checked);
    }
}

//-----------------------------------------------------------------------------
void qtCMBTreeWidget::allOff()
{
  QTreeWidgetItem* item;
  int i, end = this->topLevelItemCount();
  for(i=0; i<end; i++)
    {
    item = this->topLevelItem(i);
    item->setCheckState(0, Qt::Unchecked);
    }
}


//-----------------------------------------------------------------------------
void qtCMBTreeWidget::doToggle(int column)
{
  if(column == 0)
    {
    bool convert = false;
    int cs = this->headerItem()->data(0, Qt::CheckStateRole).toInt(&convert);
    if(convert)
      {
      if(cs == Qt::Checked)
        {
        this->allOff();
        }
      else
        {
        // both unchecked and partial checked go here
        this->allOn();
        }
      }
    }
}

//-----------------------------------------------------------------------------
QSize qtCMBTreeWidget::sizeHint() const
{
  // lets show X items before we get a scrollbar
  // probably want to make this a member variable
  // that a caller has access to
  int maxItemHint = 10;
  // for no items, let's give a space of X pixels
  int minItemHeight = 20;

  int num = this->topLevelItemCount() + 1; /* extra room for scroll bar */
  num = qMin(num, maxItemHint);

  int pix = minItemHeight;

  if(num)
    {
    pix = qMax(pix, this->sizeHintForRow(0) * num);
    }

  int margin[4];
  this->getContentsMargins(margin, margin+1, margin+2, margin+3);
  int h = pix + margin[1] + margin[3] + this->header()->frameSize().height();
  return QSize(156, h);
}

//-----------------------------------------------------------------------------
QSize qtCMBTreeWidget::minimumSizeHint() const
{
  return this->sizeHint();
}

//-----------------------------------------------------------------------------
QModelIndex qtCMBTreeWidget::getItemIndex(QTreeWidgetItem* item)
{
  if(item)
    {
    return this->indexFromItem(item);
    }
  return QModelIndex();
}

//-----------------------------------------------------------------------------
void qtCMBTreeWidget::invalidateLayout()
{
  // sizeHint is dynamic, so we need to invalidate parent layouts
  // when items are added or removed
  for(QWidget* w = this->parentWidget();
      w && w->layout();
      w = w->parentWidget())
    {
    w->layout()->invalidate();
    }
}

//-----------------------------------------------------------------------------
void qtCMBTreeWidget::dropEvent(QDropEvent* dEvent)
{
/*
  QModelIndex topIndex;
  int col = -1;
  int row = -1;
  if (this->items->dropOn(event, &row, &col, &topIndex)) {
      QList<QModelIndex> idxs = selectedIndexes();
      QList<QPersistentModelIndex> indexes;
      for (int i = 0; i < idxs.count(); i++)
          indexes.append(idxs.at(i));

      if (indexes.contains(topIndex))
          return;

      // When removing items the drop location could shift
      QPersistentModelIndex dropRow = model()->index(row, col, topIndex);
*/
  if ( dEvent->proposedAction() == Qt::MoveAction )
    {
    //move events break the way we handle drops, convert it to a copy
    dEvent->setDropAction( Qt::CopyAction );
    }

  QTreeWidgetItem *item = this->itemAt(dEvent->pos());

  if(item == NULL)
    {
    emit this->itemsDroppedOnItem(this->invisibleRootItem(), dEvent);
    }
  else
    {
    emit this->itemsDroppedOnItem(item, dEvent);
    }
  dEvent->accept();
}
//-----------------------------------------------------------------------------
Qt::DropActions qtCMBTreeWidget::supportedDropActions () const
{
  // returns what actions are supported when dropping
  return Qt::CopyAction;
}

//-----------------------------------------------------------------------------
void qtCMBTreeWidget::startDrag ( Qt::DropActions supportedActions )
{
  emit this->dragStarted(this);
  this->Superclass::startDrag(supportedActions);
}

//-----------------------------------------------------------------------------
void qtCMBTreeWidget::dragEnterEvent ( QDragEnterEvent * event )
{
  QStringList mType = this->mimeTypes();
  const QMimeData* mData = event->mimeData();
  foreach(QString type, mType)
    {
    if ( mData->hasFormat( type ) )
      {
      event->accept();
      return;
      }
    }
}

//-----------------------------------------------------------------------------
void qtCMBTreeWidget::dragMoveEvent( QDragMoveEvent * event )
{
  if ( event->proposedAction() & this->supportedDropActions() )
    {
    event->accept();
    }
}

//-----------------------------------------------------------------------------
QModelIndex qtCMBTreeWidget::moveCursor(CursorAction cursorAction,
                                      Qt::KeyboardModifiers modifiers)
{
  QModelIndex suggestedIndex = this->Superclass::moveCursor(cursorAction, modifiers);

  int max_rows = this->topLevelItemCount();
  int max_colums = this->columnCount();
  QTreeWidgetItem* curItem = this->currentItem();
  int cur_col = this->currentColumn();
  if (!curItem || cur_col < 0 || cur_col >= max_colums)
    {
    return suggestedIndex;
    }

  int cur_row = this->indexOfTopLevelItem(curItem);
  if (cursorAction == QAbstractItemView::MoveNext && modifiers ==  Qt::NoModifier)
    {
    int next_column = cur_col+1;
    while (next_column < max_colums && this->isColumnHidden(next_column))
      {
      // skip hidden columns.
      next_column++;
      }
    if (next_column < max_colums)
      {
      return this->indexFromItem(curItem, next_column);
      }
    else if ((cur_row +1) == max_rows)
      {
      // User is at last row, we need to add a new row before moving to that
      // row.
      emit this->navigatedPastEnd();
      // if the table grows, the index may change.
      suggestedIndex = this->Superclass::moveCursor(cursorAction, modifiers);
      }
    // otherwise default behaviour takes it to the first column in the next
    // row, which is what is expected.
    }
  else if (cursorAction == QAbstractItemView::MovePrevious && modifiers == Qt::NoModifier)
    {
    int prev_column = cur_col-1;
    while (prev_column >=0 && this->isColumnHidden(prev_column))
      {
      // skip hidden columns.
      prev_column--;
      }
    if (prev_column >= 0)
      {
      return this->indexFromItem(curItem, prev_column);
      }
    else
      {
      // we need to go to the last column in the previous row.
      if (cur_row > 0)
        {
        prev_column = max_colums-1;
        while (prev_column >=0 && this->isColumnHidden(prev_column))
          {
          // skip hidden columns.
          prev_column--;
          }
        if (prev_column >= 0)
          {
          return this->indexFromItem(
            this->topLevelItem(cur_row-1), max_colums-1);
          }
        }
      }
    }

  return suggestedIndex;
}
//-----------------------------------------------------------------------------
void qtCMBTreeWidget::mouseReleaseEvent( QMouseEvent *event )
{
  QPoint pos = event->pos();
  QPersistentModelIndex index = indexAt(pos);

  if (index.isValid())
    {
    if ( event->button() & Qt::LeftButton )
      {
      emit this->itemLeftButtonClicked(this->itemAt(pos),index.column());
      }
    else if ( event->button() & Qt::RightButton )
      {
      emit this->itemRightButtonClicked(this->itemAt(pos),index.column());
      }
    else if ( event->button() & Qt::MidButton )
      {
      emit this->itemMiddleButtonClicked(this->itemAt(pos),index.column());
      }
    }
  this->Superclass::mouseReleaseEvent( event );
}
