//
// Class: DocumentWidget
//
// Widget for displaying TeX DVI files.
// Part of KDVI- A previewer for TeX DVI files.
//
// (C) 2001 Stefan Kebekus
// Distributed under the GPL
//

#include <kaction.h>
#include <kdebug.h>
#include <kglobalsettings.h>
#include <klocale.h>
#include <qapplication.h>
#include <qclipboard.h>
#include <qcursor.h>
#include <qpainter.h>
#include <qimage.h>

#include "centeringScrollview.h"
#include "documentPagePixmap.h"
#include "documentPageCache.h"
#include "documentWidget.h"
#include "selection.h"

#include "kvsprefs.h"

//#define DEBUG_DOCUMENTWIDGET

const int documentWidget::bottom_right_corner[16] =
  { 61, 71, 85, 95,
    71, 78, 89, 96,
    85, 89, 95, 98,
    95, 97, 98, 99 };

const int documentWidget::bottom_left_corner[16] =
  { 95, 85, 71, 61,
    97, 89, 78, 71,
    98, 95, 89, 85,
    99, 98, 96, 95 };

const int documentWidget::shadow_strip[4] =
  { 56, 67, 83, 94 };

QPixmap documentWidget::URShadow;
QPixmap documentWidget::BRShadow;
QPixmap documentWidget::BLShadow;

QColor documentWidget::backgroundColorForCorners;


documentWidget::documentWidget(QWidget *parent, CenteringScrollview *sv, QSize size, documentPageCache *cache, 
			       textSelection *documentSelection, const char *name )
  : QWidget( parent, name ), indexOfUnderlinedLink(-1)
{
  // Variables used in animation.
  animationCounter = 0;
  timerIdent       = 0;
  documentCache    = cache;
  selection     = documentSelection;
  scrollView       = sv;

  setMouseTracking(true);
  setPageSize(size);

  connect(&clearStatusBarTimer, SIGNAL(timeout()), this, SLOT(clearStatusBar()));
  setBackgroundMode(Qt::NoBackground);

  // Initialize shadow pixmaps
  if(URShadow.size() == QSize(0,0))
  {
    URShadow.resize(4,4);
    BRShadow.resize(4,4);
    BLShadow.resize(4,4);
  }
}


void documentWidget::setPageNumber(Q_UINT16 nr)
{
  pageNr = nr;
  update();
}


void documentWidget::timerEvent( QTimerEvent *e )
{
  animationCounter++;
  if (animationCounter >= 10) {
    killTimer(e->timerId());
    timerIdent       = 0;
    animationCounter = 0;
  }

  emit showMe(this, flashOffset-1);
  repaint(0, flashOffset-1, width(), height()/19+1, false);
}


void documentWidget::flash(int fo)
{
  animationCounter = 0;
  if (timerIdent != 0)
    killTimer(timerIdent);
  flashOffset      = fo;
  timerIdent       = startTimer(50); // Start the animation. The animation proceeds in 1/10s intervals
}


void documentWidget::paintEvent(QPaintEvent *e)
{
#ifdef DEBUG_DOCUMENTWIDGET
  kdDebug(4300) << "documentWidget::paintEvent() called" << endl;
#endif
  
  // Check if this widget is really visible to the user. If not, there
  // is nothing to do. Remark: if we don't do this, then under QT
  // 3.2.3 the following happens: when the user changes the zoom
  // value, all those widgets are updated which the user has EVER
  // seen, not just those that are visible at the moment. If the
  // document contains several thousand pages, it is easily possible
  // that this means that a few hundred of these are re-painted (which
  // takes substantial time) although perhaps only three widgets are
  // visible and *should* be updated. I believe this is some error in
  // QT, but I am not positive about that ---Stefan Kebekus.
  QRect visiblRect(scrollView->contentsX(), scrollView->contentsY(), scrollView->visibleWidth(), scrollView->visibleHeight());
  QRect widgetRect(scrollView->childX(this), scrollView->childY(this), width(), height() );
  if (!widgetRect.intersects(visiblRect))
    return;
  
  QPainter p(this);
  p.setClipRect(e->rect());
  
  // Paint page shadow
  QColor backgroundColor = colorGroup().mid();
  
  // (Re-)generate the Pixmaps for the shadow corners, if necessary
  if (backgroundColor != backgroundColorForCorners)
    {
    backgroundColorForCorners = backgroundColor;
    QImage tmp(4, 4, 32);
    for(int x=0; x<4; x++)
      for(int y=0; y<4; y++)
        tmp.setPixel(x, y, backgroundColor.light(bottom_right_corner[x+4*y]).rgb() );
    BRShadow.convertFromImage(tmp);

    for(int x=0; x<4; x++)
      for(int y=0; y<4; y++)
        tmp.setPixel(x, y, backgroundColor.light(bottom_left_corner[x+4*y]).rgb() );
    BLShadow.convertFromImage(tmp);

    URShadow.convertFromImage(tmp.mirror(true, true));
  }

  // Draw right and bottom shadows
  for(int i=0; i<4; i++)
  {
    p.setPen(backgroundColor.light(shadow_strip[i]));
    // Right shadow
    p.drawLine(pageSize().width()+i, 8, pageSize().width()+i, pageSize().height());
    // Bottom shadow
    p.drawLine(8, pageSize().height()+i, pageSize().width(), pageSize().height()+i);
  }
  // Draw shadow corners
  p.drawPixmap(pageSize().width(), pageSize().height(), BRShadow);
  p.drawPixmap(4, pageSize().height(), BLShadow);
  p.drawPixmap(pageSize().width(), 4, URShadow);

  // Draw corners
  p.fillRect(0, pageSize().height(), 4, 4, backgroundColor);
  p.fillRect(pageSize().width(), 0, 4, 4, backgroundColor);

  // Paint the page where it intersects with the damaged area.
  QRect destRect = e->rect().intersect(pageRect());

  documentPagePixmap *pageData = documentCache->getPage(pageNr);
  if (pageData == 0) {
#ifdef DEBUG_DOCUMENTWIDGET
    kdDebug(4300) << "documentWidget::paintEvent: no documentPage generated" << endl;
#endif
    return;
  }

  // Paint widget contents
  bitBlt ( this, destRect.topLeft(), pageData, destRect, CopyROP);

  // Underline hyperlinks
  if (KVSPrefs::underlineLinks() == UL_Enabled || KVSPrefs::underlineLinks() == UL_OnlyOnHover)
  {
    int h = 2; // Height of line.
    for(int i = 0; i < (int)pageData->hyperLinkList.size(); i++) 
    {
      if (KVSPrefs::underlineLinks() == UL_OnlyOnHover && i != indexOfUnderlinedLink)
        continue;
      int x = pageData->hyperLinkList[i].box.left();
      int w = pageData->hyperLinkList[i].box.width();
      int y = pageData->hyperLinkList[i].baseline;

      QRect hyperLinkRect(x, y, w, h);
      if (hyperLinkRect.intersects(e->rect()))
      {
#ifdef DEBUG_DOCUMENTWIDGET
      kdDebug(1223) << "Underline hyperlink \"" << pageData->hyperLinkList[i].linkText << "\"" << endl;
#endif
        p.fillRect(x, y, w, h, KGlobalSettings::linkColor());
      }
    }
  }

  // Paint flashing frame, if appropriate
  if (animationCounter > 0 && animationCounter < 10) {
    int wdt = width()/(10-animationCounter);
    int hgt = height()/((10-animationCounter)*20);
    p.setPen(QPen(QColor(150,0,0), 3, DashLine));
    p.drawRect((width()-wdt)/2, flashOffset, wdt, hgt);
  }

  // Mark selected text.
  if ((selection->getPageNumber() != 0)&&(selection->getPageNumber() == pageNr)) {
    // @@@ Add paranoid safety check about beginning and end 
    if (selection->getSelectedTextStart() != -1)
      for(int i = selection->getSelectedTextStart(); (i <= selection->getSelectedTextEnd()) && (i < (int)pageData->textLinkList.size()); i++) {
        p.setPen( NoPen );
        p.setBrush( white );
        p.setRasterOp( Qt::XorROP );
        p.drawRect(pageData->textLinkList[i].box);
      }
  }
}


void documentWidget::selectAll(void)
{
  // pageNr == 0 indicated an invalid page (e.g. page number not yet
  // set)
  if (pageNr == 0)
    return;

  // Get a pointer to the page contents
  documentPage *pageData = documentCache->getPage(pageNr);
  if (pageData == 0) {
    kdDebug(4300) << "documentWidget::selectAll() pageData for page #" << pageNr << " is empty" << endl;
    return;
  }
  
  // mark everything as selected
  QString selectedText("");
  for(unsigned int i = 0; i < pageData->textLinkList.size(); i++) {
    selectedText += pageData->textLinkList[i].linkText;
    selectedText += "\n";
  }
  Q_UINT16 oldPageNr = selection->getPageNumber();
  selection->set(pageNr, 0, pageData->textLinkList.size()-1, selectedText);
  if (oldPageNr != pageNr)
    connect(selection, SIGNAL(pageChanged(void)), this, SLOT(selectionPageChanged(void)));

  // Re-paint
  update();
}


void documentWidget::mousePressEvent ( QMouseEvent * e )
{
#ifdef DEBUG_DOCUMENTWIDGET
  kdDebug(4300) << "documentWidget::mousePressEvent(...) called" << endl;
#endif
  
  // Make sure the event is passed on to the higher-level widget;
  // otherwise QT gets the coordinated in the mouse move events wrong
  e->ignore();

  // pageNr == 0 indicated an invalid page (e.g. page number not yet
  // set)
  if (pageNr == 0)
    return;

  // Get a pointer to the page contents
  documentPage *pageData = documentCache->getPage(pageNr);
  if (pageData == 0) {
    kdDebug(4300) << "documentWidget::selectAll() pageData for page #" << pageNr << " is empty" << endl;
    return;
  }

  // Check if the mouse is pressed on a regular hyperlink
  if (e->button() == LeftButton) {
    if (pageData->hyperLinkList.size() > 0)
      for(unsigned int i = 0; i < pageData->hyperLinkList.size(); i++) {
        if (pageData->hyperLinkList[i].box.contains(e->pos())) {
          emit(localLink(pageData->hyperLinkList[i].linkText));
          e->accept();
          return;
        }
      }
    setCursor(Qt::SizeAllCursor);
  }

  if (e->button() == RightButton)
  {
    setCursor(Qt::IbeamCursor);
    selection->clear();
  }
}


void documentWidget::mouseReleaseEvent ( QMouseEvent *e )
{
  // Make sure the event is passed on to the higher-level widget;
  // otherwise the mouse cursor in the centeringScrollview is wrong
  e->ignore();

  unsetCursor();
  selectedRectangle.setRect(0,0,0,0);
}


void documentWidget::mouseMoveEvent ( QMouseEvent * e )
{
#ifdef DEBUG_DOCUMENTWIDGET
  kdDebug(4300) << "documentWidget::mouseMoveEvent(...) called" << endl;
#endif


  // pageNr == 0 indicated an invalid page (e.g. page number not yet
  // set)
  if (pageNr == 0)
    return;

  // Get a pointer to the page contents
  documentPage *pageData = documentCache->getPage(pageNr);
  if (pageData == 0) {
    kdDebug(4300) << "documentWidget::selectAll() pageData for page #" << pageNr << " is empty" << endl;
    return;
  }

  // If no mouse button pressed
  if ( e->state() == 0 ) {
    // Remember the index of the underlined link.
    int lastUnderlinedLink = indexOfUnderlinedLink;
    // go through hyperlinks
    for(unsigned int i=0; i<pageData->hyperLinkList.size(); i++) {
      if (pageData->hyperLinkList[i].box.contains(e->pos())) {
        clearStatusBarTimer.stop();
        setCursor(pointingHandCursor);
        QString link = pageData->hyperLinkList[i].linkText;
        if ( link.startsWith("#") )
          link = link.remove(0,1);
        emit setStatusBarText( i18n("Link to %1").arg(link) );

        indexOfUnderlinedLink = i;
        if (KVSPrefs::underlineLinks() == UL_OnlyOnHover && indexOfUnderlinedLink != lastUnderlinedLink)
        {
          QRect newUnderline = pageData->hyperLinkList[i].box;
          // Increase Rectangle so that the whole line really lines in it.
          newUnderline.addCoords(0, 0, 0, 2);
          // Redraw widget
          update(newUnderline);

          if (lastUnderlinedLink != -1)
          {
            // Erase old underline
            QRect oldUnderline = pageData->hyperLinkList[lastUnderlinedLink].box;
            oldUnderline.addCoords(0, 0, 0, 2);
            update(oldUnderline);
          }
        }
        return;
      }
    }
    // Whenever we reach this the mouse hovers no link.
    indexOfUnderlinedLink = -1;
    if (KVSPrefs::underlineLinks() == UL_OnlyOnHover && lastUnderlinedLink != -1)
    {
      // Erase old underline
      QRect oldUnderline = pageData->hyperLinkList[lastUnderlinedLink].box;
      // Increase Rectangle so that the whole line really lines in it.
      oldUnderline.addCoords(0, 0, 0, 2);
      // Redraw widget
      update(oldUnderline);
    }
    // Cursor not over hyperlink? Then let the cursor be the usual arrow
    setCursor(arrowCursor);
  }

  if (!clearStatusBarTimer.isActive())
    clearStatusBarTimer.start( 200, TRUE ); // clear the statusbar after 200 msec.
  
  // Left mouse button pressed -> Text scroll function
  if ((e->state() & LeftButton) != 0) {
    // Pass the mouse event on to the owner of this widget ---under
    // normal circumstances that is the centeringScrollView which will
    // then scroll the scrollview contents
    e->ignore();
  }
  
  // Right mouse button pressed -> Text copy function
  if ((e->state() & RightButton) != 0) {
    if (selectedRectangle.isEmpty()) {
      firstSelectedPoint = e->pos();
      selectedRectangle.setRect(e->pos().x(),e->pos().y(),1,1);
    } else {
      int lx = e->pos().x() < firstSelectedPoint.x() ? e->pos().x() : firstSelectedPoint.x();
      int rx = e->pos().x() > firstSelectedPoint.x() ? e->pos().x() : firstSelectedPoint.x();
      int ty = e->pos().y() < firstSelectedPoint.y() ? e->pos().y() : firstSelectedPoint.y();
      int by = e->pos().y() > firstSelectedPoint.y() ? e->pos().y() : firstSelectedPoint.y();
      selectedRectangle.setCoords(lx,ty,rx,by);
    }
    
    // Now that we know the rectangle, we have to find out which words
    // intersect it!
    Q_INT32 selectedTextStart = -1;
    Q_INT32 selectedTextEnd   = -1;
    
    for(unsigned int i=0; i<pageData->textLinkList.size(); i++) {
      if ( selectedRectangle.intersects(pageData->textLinkList[i].box) ) {
        if (selectedTextStart == -1)
          selectedTextStart = i;
        selectedTextEnd = i;
      }
    }
    
    QString selectedText("");


    if (selectedTextStart != -1) {
      for(int i = selectedTextStart; (i <= selectedTextEnd) && (i < (int)pageData->textLinkList.size()); i++) {
        selectedText += pageData->textLinkList[i].linkText;
        selectedText += "\n";
      }
    }

    if ((selectedTextStart != selection->getSelectedTextStart()) || (selectedTextEnd != selection->getSelectedTextEnd())) {
      if (selectedTextEnd == -1) {
        selection->clear();
        update();
      } else {
        // Find the rectangle that needs to be updated (reduces
        // flicker)
        int a = selection->getSelectedTextStart();
        int b = selection->getSelectedTextEnd()+1;
        int c = selectedTextStart;
        int d = selectedTextEnd+1;

        int i1 = kMin(a,c);
        int i2 = kMin(kMax(a,c),kMin(b,d));
        int i3 = kMax(kMax(a,c),kMin(b,d));
        int i4 = kMax(b,d);

        QRect box;
        int i=i1;
        while(i<i2) {
          if (i != -1)
            box = box.unite(pageData->textLinkList[i].box);
          i++;
        }

        for(int i=i3; i<i4; i++)
          if (i != -1)
            box = box.unite(pageData->textLinkList[i].box);

        Q_UINT16 oldPageNr = selection->getPageNumber();
        selection->set(pageNr, selectedTextStart, selectedTextEnd, selectedText);
        if (oldPageNr != pageNr)
          connect(selection, SIGNAL(pageChanged(void)), this, SLOT(selectionPageChanged(void)));

        update(box);
      }
    }
  }
}


void documentWidget::selectionPageChanged(void)
{
  update();
  disconnect(selection);
}


void documentWidget::clearStatusBar()
{
  emit setStatusBarText( QString::null );
}


QSize documentWidget::pageSize()
{
  // Substract size of the shadow.
  return size() - QSize(4, 4);
}


QRect documentWidget::pageRect()
{
  QRect boundingRect = rect();
  // Substract the shadow.
  boundingRect.addCoords(0, 0, -4, -4);
  return boundingRect;
}


void documentWidget::setPageSize(const QSize& pageSize)
{
  // Add size of the shadow.
  resize(pageSize + QSize(4, 4));
}


void documentWidget::setPageSize(int width, int height)
{
  setPageSize(QSize(width, height));
}

#include "documentWidget.moc"
