/* This file is part of the KDE project
   Copyright (C) 2001 Wilco Greven <greven@kde.org>
   Copyright (C) 2002-2004 Stefan Kebekus <kebekus@kde.org>
   Copyright (C) 2004-2005 Wilfried Huss <Wilfried.Huss@gmx.at>

   This library is free software; you can redistribute it and/or
   modify it under the terms of the GNU Library General Public
   License as published by the Free Software Foundation; either
   version 2 of the License, or (at your option) any later version.

   This library is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   Library General Public License for more details.

   You should have received a copy of the GNU Library General Public License
   along with this library; see the file COPYING.LIB.  If not, write to
   the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
   Boston, MA 02111-1307, USA.
*/

#include <kdebug.h>
#include <qapplication.h>
#include <qcursor.h>
#include <qimage.h>
#include <qpainter.h>
#include <qrect.h>
#include <math.h>

#include "centeringScrollview.h"

CenteringScrollview::CenteringScrollview( QWidget* parent, const char* name )
  : QScrollView( parent, name, WStaticContents | WNoAutoErase)
{
  widgetList = 0;
  setFocusPolicy( QWidget::StrongFocus );
  viewport()->setFocusPolicy( QWidget::WheelFocus );

  setResizePolicy(QScrollView::Manual);

  setVScrollBarMode(QScrollView::Auto);
  setHScrollBarMode(QScrollView::Auto);

  viewport()->setBackgroundMode(Qt::NoBackground);

  setResizePolicy(Manual);
  setAcceptDrops(true);
  setDragAutoScroll(false);

  enableClipper(true);
  nrCols = 2;
  nrRows = 1;
  continuousViewmode = true;
  fullScreen = false;
}


void CenteringScrollview::addChild( QPtrVector<QWidget> *wdgList )
{
  if( wdgList == 0 ) {
    kdError(4300) << "CenteringScrollview::addChild(...) called with invalid arguments" << endl;
    return;
  }

  widgetList = wdgList;

  for(Q_UINT16 i=0; i<widgetList->size(); i++) 
    connect(widgetList->at(i), SIGNAL(showMe(QWidget *, Q_UINT16)), this, SLOT(slotShowWidget(QWidget *, Q_UINT16)) );

  centerContents();
}


void CenteringScrollview::addChild( QWidget *widget )
{
  widgetListIfOldAddChildIsUsed.resize(1);
  widgetListIfOldAddChildIsUsed.insert(0, widget);
  addChild(&widgetListIfOldAddChildIsUsed);
}


bool CenteringScrollview::atTop() const
{
  return verticalScrollBar()->value() == verticalScrollBar()->minValue();
}


bool CenteringScrollview::atBottom() const 
{
  return verticalScrollBar()->value() == verticalScrollBar()->maxValue();
}


bool CenteringScrollview::readUp()
{
  if( atTop() )
    return false;
  else {
    int newValue = QMAX( verticalScrollBar()->value() - (int)(height() * 0.95),
			 verticalScrollBar()->minValue() );
    verticalScrollBar()->setValue( newValue );
    return true;
  }
}


bool CenteringScrollview::readDown()
{   
  if( atBottom() )
    return false;
  else {
    int newValue = QMIN( verticalScrollBar()->value() + (int)(height() * 0.95),
			 verticalScrollBar()->maxValue() );
    verticalScrollBar()->setValue( newValue );
    return true;
  }
}


void CenteringScrollview::scrollRight()
{
    horizontalScrollBar()->addLine();
}

void CenteringScrollview::scrollLeft()
{
    horizontalScrollBar()->subtractLine();
}

void CenteringScrollview::scrollDown()
{
    verticalScrollBar()->addLine();
}

void CenteringScrollview::scrollUp()
{
    verticalScrollBar()->subtractLine();
}

void CenteringScrollview::scrollBottom()
{
    verticalScrollBar()->setValue( verticalScrollBar()->maxValue() );
}

void CenteringScrollview::scrollTop()
{
    verticalScrollBar()->setValue( verticalScrollBar()->minValue() );
}

void CenteringScrollview::keyPressEvent( QKeyEvent* e )
{   
    switch ( e->key() ) {
    case Key_Up:
	scrollUp();
	break;
    case Key_Down:
	scrollDown();
	break;
    case Key_Left:
	scrollLeft();
	break;
    case Key_Right:
	scrollRight();
	break;
    default:
	e->ignore();
	return;
    }
    e->accept();
}

void CenteringScrollview::contentsMousePressEvent( QMouseEvent* e )
{
  if (e->button() == LeftButton)
  {
    setCursor(Qt::SizeAllCursor);
    dragGrabPos = e->globalPos();
  }
  else
  {
    setCursor(Qt::arrowCursor);
  }
}

void CenteringScrollview::contentsMouseReleaseEvent( QMouseEvent* )
{
  setCursor(Qt::arrowCursor);
}

void CenteringScrollview::contentsMouseMoveEvent( QMouseEvent* e )
{
  QPoint newPos = e->globalPos();

  if (e->state() == LeftButton)
  {
    QPoint delta = dragGrabPos - newPos;
    scrollBy(delta.x(), delta.y());
  }
  dragGrabPos = newPos;
}

void CenteringScrollview::viewportResizeEvent( QResizeEvent* e )
{
  QScrollView::viewportResizeEvent( e );
  emit viewSizeChanged( viewport()->size() );
  centerContents();
}

void CenteringScrollview::setNrColumns( Q_UINT8 cols )
{
  nrCols = cols;
  centerContents();
}

void CenteringScrollview::setNrRows( Q_UINT8 rows )
{
  nrRows = rows;
}

void CenteringScrollview::setContinuousViewMode(bool continuous)
{
  continuousViewmode = continuous;
}

bool CenteringScrollview::singlePageFullScreenMode()
{
  return (nrCols == 1 && nrRows == 1 && !continuousViewmode && fullScreen);
}

void CenteringScrollview::slotShowScrollbars(bool status)
{
  if (status == true) {
    setVScrollBarMode(QScrollView::Auto);
    setHScrollBarMode(QScrollView::Auto);
  } else {
    setVScrollBarMode(QScrollView::AlwaysOff);
    setHScrollBarMode(QScrollView::AlwaysOff);
  }

  // Remember the state of the scrollbars so that we can restore
  // scrollbar visibility when we switch back from fullscreen mode to
  // normal mode
  showScrollBars = status;
}

void CenteringScrollview::setFullScreenMode(bool fullScreen)
{ 
  this -> fullScreen = fullScreen; 
  if (fullScreen == true) 
  {
    setVScrollBarMode(QScrollView::AlwaysOff);
    setHScrollBarMode(QScrollView::AlwaysOff);
    oldFrameStyle = frameStyle();
    setFrameStyle(QFrame::NoFrame);
    backgroundColor = viewport()->paletteBackgroundColor();
    if (singlePageFullScreenMode())
    {
      viewport()->setPaletteBackgroundColor( Qt::black ) ;
    }
  }
  else
  {
    viewport()->setPaletteBackgroundColor( backgroundColor ) ;
    slotShowScrollbars(showScrollBars);
    setFrameStyle(oldFrameStyle);
  }
}

void CenteringScrollview::centerContents(bool zoomChanged)
{
  // Paranoid safety check
  if (widgetList == 0)
    return;

  // If there are no widgets, e.g. because the last widget has been
  // removed, the matter is easy: set the contents size to 0. If there
  // are no widgets because previously existing widgets were removed
  if (widgetList->isEmpty()) {
    if ((contentsWidth() != 0) || (contentsHeight() != 0)) {
      QScrollView::resizeContents(0,0);
    }
    return;
  }

  // Ok, now we are in a situation where we do have some widgets that
  // shall be centered.
  int distance = distanceBetweenWidgets;
  if (singlePageFullScreenMode())
  {
    // In single page fullscreen mode we don't want a margin around the pages
    distance = 0;
  }

  QMemArray<Q_UINT32> colWidth(nrCols);
  for(Q_UINT8 i=0; i<colWidth.size(); i++)
    colWidth[i] = 0;

  Q_UINT16 numRows;
  if(nrCols <= 2)
  {
    numRows = (widgetList->size()+2*nrCols-2) / nrCols;
  }
  else
  {
    numRows = (Q_INT16)ceil(((double)widgetList->size()) / nrCols);
  }

  QMemArray<Q_UINT32> rowHeight(numRows);
  for(Q_UINT16 i=0; i<rowHeight.size(); i++)
    rowHeight[i] = 0;

  // Now find the widths and heights of the columns
  for(Q_UINT16 i=0; i<widgetList->size(); i++) 
  {
    Q_UINT8 col;
    Q_UINT16 row;

    if (nrCols == 2) {
      // In two-column display, start with the right column
      col = (i+1+nrCols) % nrCols;
      row = (i+1+nrCols) / nrCols - 1;
    } else {
      col = (i+nrCols) % nrCols;
      row = (i+nrCols) / nrCols - 1;
    }

    // Substract 4 pixels from the widgets width and height to take the page shadow into account.
    // It would be much cleaner to just call documentWidget::pageSize(), but then we would have to
    // link documentWidget etc. to the kviewpart. (This has already been changed in HEAD)
    colWidth[col] = QMAX(colWidth[col], (Q_UINT32)widgetList->at(i)->width()-4);
    rowHeight[row] = QMAX(rowHeight[row], (Q_UINT32)widgetList->at(i)->height()-4);
  }

  // Calculate the total width and height of the display
  Q_UINT32 totalHeight = 0;
  for(Q_UINT16 i=0; i<rowHeight.size(); i++)
    totalHeight += rowHeight[i];

  totalHeight += (numRows+1)*distance;
  Q_UINT32 totalWidth = 0;
  for(Q_UINT8 i=0; i<colWidth.size(); i++)
    totalWidth += colWidth[i];

  totalWidth += (nrCols+1)*distance;
  QSize newViewportSize = viewportSize( totalWidth, totalHeight );
  Q_UINT32 centeringLeft = 0;
  if( (Q_UINT32)newViewportSize.width() > totalWidth )
    centeringLeft = ( newViewportSize.width() - totalWidth )/2;
  Q_UINT32 centeringTop = 0;
  if( (Q_UINT32)newViewportSize.height() > totalHeight )
    centeringTop = ( newViewportSize.height() - totalHeight)/2;

  // Finally, calculate the left and top coordinates of each row and
  // column, respectively
  QMemArray<Q_UINT32> colLeft(nrCols);
  colLeft[0] = distance;
  for(Q_UINT8 i=1; i<colLeft.size(); i++)
    colLeft[i] = colLeft[i-1]+colWidth[i-1]+distance;

  QMemArray<Q_UINT32> rowTop(numRows);
  rowTop[0] = distance;
  for(Q_UINT16 i=1; i<rowTop.size(); i++)
    rowTop[i] = rowTop[i-1]+rowHeight[i-1]+distance;

  for(Q_UINT16 i=0; i<widgetList->size(); i++) 
  {
    Q_UINT8 col;
    Q_UINT16 row;
    if (nrCols == 2)
    {
      // In two column-mode start with the right column.
      col = (i+nrCols-1) % nrCols;
      row = (i+nrCols-1) / nrCols;
    }
    else
    {
      col = (i+nrCols) % nrCols;
      row = i / nrCols;
    }
    moveChild( widgetList->at(i), centeringLeft + colLeft[col], centeringTop+rowTop[row] );
  }

  // Re-draw the viewport background so that old shadows are removed
  if (((Q_UINT32)contentsWidth() != totalWidth) || ((Q_UINT32)contentsHeight() != totalHeight))
  {
    // Calculate the point in the coordinates of the contents which is currently at the center of the viewport.
    QPoint midPoint = QPoint(visibleWidth() / 2 + contentsX(), visibleHeight() / 2 + contentsY()); 
    double midPointRatioX = (double)(midPoint.x()) / contentsWidth();
    double midPointRatioY = (double)(midPoint.y()) / contentsHeight();

    resizeContents(totalWidth,totalHeight);

    // If the zoom changed recenter the former midPoint
    if (zoomChanged)
      center((int)(contentsWidth() * midPointRatioX), (int)(contentsHeight() * midPointRatioY));

  }
}


void CenteringScrollview::slotShowWidget(QWidget *wdg, Q_UINT16 y)
{
  if (wdg == 0)
    return;

  ensureVisible(childY(wdg)+wdg->width()/2, childY(wdg)+y, 50, 100);
}


void CenteringScrollview::contentsWheelEvent ( QWheelEvent * e )
{
  emit(wheelEventReceived(e));
}


void CenteringScrollview::moveViewportToWidget(QWidget* widget)
{
  setContentsPos(childX(widget) - distanceBetweenWidgets, 
                 childY(widget) - distanceBetweenWidgets);
}


void CenteringScrollview::viewportPaintEvent(QPaintEvent* e)
{
  // Region from which rectangles occupied by child widgets will by substracted.
  QRegion backgroundArea(e->rect());

  if (widgetList != 0)
  {
    for (unsigned int i = 0; i < widgetList->count(); i++)
    {
      QWidget* item = widgetList->at(i);

      // Check if the Widget needs to be updated.
      if (!item->geometry().intersects(e->rect()))
        continue;

      QRect widgetGeometry = item->geometry();

      // Draw the widget.
      if (e->rect().intersects(widgetGeometry))
      {
        QRect widgetRect = e->rect().intersect(widgetGeometry);
        widgetRect.moveBy(-widgetGeometry.left(), -widgetGeometry.top());

        item->update(widgetRect);
      }

      // Substract the painted area.
      backgroundArea -= widgetGeometry.intersect(e->rect());
    }
  }

  // Paint the background.
  QPainter p(viewport());

  QMemArray<QRect> backgroundRects = backgroundArea.rects();

  for (unsigned int i = 0; i < backgroundRects.count(); i++)
    p.fillRect(backgroundRects[i], colorGroup().mid());
}

#include "centeringScrollview.moc"
