
#include <kaction.h>
#include <kapplication.h>
#include <kdebug.h>
#include <keditcl.h>
#include <kfiledialog.h>
#include <kiconloader.h>
#include <kio/job.h>
#include <klocale.h>
#include <kmessagebox.h>
#include <kstdaction.h>
#include <qobject.h>
#include <qlayout.h>
#include <qprogressdialog.h>
#include <qsplitter.h>
#include <qurl.h>

#include "marklist.h"
#include "documentRenderer.h"
#include "documentWidget.h"
#include "pageNumber.h"
#include "zoomlimits.h"

#include "kvsprefs.h"

#include "kmultipage.moc"

KMultiPage::KMultiPage(QWidget *parentWidget, const char *widgetName, QObject *parent, const char *name)
  : DCOPObject("kmultipage"), KParts::ReadOnlyPart(parent, name)
{
  // For reasons which I don't understand, the initialization of the
  // DCOPObject above does not work properly, the name is ignored. It
  // works fine if we repeat the name here. -- Stefan Kebekus
  // This is because of the virtual inheritance. Get rid of it (but it's BC, and this is a lib...) -- DF
  setObjId("kmultipage");

  findDialog = 0;
  findNextAction         = 0;
  findPrevAction         = 0;
  lastCurrentPage = 0;

  mainWidget = new QSplitter(parentWidget, widgetName);
  mainWidget->setOpaqueResize(false);
  setWidget(mainWidget);

  // Create MarkList
  _markList = new MarkList(mainWidget, "marklist");
  _markList->setMinimumWidth(80);
  _markList->setMaximumWidth(200);

  mainWidget->setResizeMode(_markList, QSplitter::KeepSize);

  connect(_markList, SIGNAL(selected(PageNumber)), this, SLOT(gotoPage(PageNumber)));

  _scrollView = new CenteringScrollview(mainWidget, widgetName);

  connect(_scrollView, SIGNAL(contentsMoving(int, int)), this, SLOT(calculateCurrentPageNumber()));
  connect(_scrollView, SIGNAL(viewSizeChanged(QSize)), this, SLOT(calculateCurrentPageNumber()));
  connect(this, SIGNAL(zoomChanged()), this, SLOT(calculateCurrentPageNumber()));

  connect(this, SIGNAL(numberOfPages(int)), this, SLOT(setNumberOfPages(int)));

  mainWidget->setCollapsible(_markList, false);
  mainWidget->setSizes(KVSPrefs::guiLayout());

  findTextAction = KStdAction::find(this, SLOT(showFindTextDialog()), actionCollection(), "find");
  findNextAction = KStdAction::findNext(this, SLOT(findNextText()), actionCollection(), "findnext");
  findNextAction->setEnabled(false);
  findPrevAction = KStdAction::findPrev(this, SLOT(findPrevText()), actionCollection(), "findprev");
  findPrevAction->setEnabled(false);

  copyTextAction     = KStdAction::copy(&userSelection, SLOT(copyText()), actionCollection(), "copy_text");
  copyTextAction->setEnabled(!userSelection.isEmpty());
  connect(&userSelection, SIGNAL(selectionIsNotEmpty(bool)), copyTextAction, SLOT(setEnabled(bool)));

  selectAllAction    = KStdAction::selectAll(this, SLOT(doSelectAll()), actionCollection(), "edit_select_all");
  deselectAction     = KStdAction::deselect(&userSelection, SLOT(clear()), actionCollection(), "edit_deselect_all");
  deselectAction->setEnabled(!userSelection.isEmpty());
  connect(&userSelection, SIGNAL(selectionIsNotEmpty(bool)), deselectAction, SLOT(setEnabled(bool)));
}

KMultiPage::~KMultiPage()
{
  KVSPrefs::setGuiLayout(mainWidget->sizes());
  KVSPrefs::writeConfig();
}

QString KMultiPage::name_of_current_file()
{
  return m_file;
}

bool KMultiPage::is_file_loaded(QString filename)
{
  return (filename == m_file);
}

void KMultiPage::slotSave_defaultFilename()
{
  slotSave();
}

void KMultiPage::slotSave()
{
  // Try to guess the proper ending...
  QString formats;
  QString ending;
  int rindex = m_file.findRev(".");
  if (rindex == -1) {
    ending = QString::null;
    formats = QString::null;
  } else {
    ending = m_file.mid(rindex); // e.g. ".dvi"
    formats = fileFormats().grep(ending).join("\n");
  }

  QString fileName = KFileDialog::getSaveFileName(QString::null, formats, 0, i18n("Save File As"));

  if (fileName.isEmpty())
    return;

  // Add the ending to the filename. I hope the user likes it that
  // way.
  if (!ending.isEmpty() && fileName.find(ending) == -1)
    fileName = fileName+ending;

  if (QFile(fileName).exists()) {
    int r = KMessageBox::warningYesNo (0, i18n("The file %1\nexists. Shall I overwrite that file?").arg(fileName),
				       i18n("Overwrite File"));
    if (r == KMessageBox::No)
      return;
  }

  KIO::Job *job = KIO::file_copy( KURL( m_file ), KURL( fileName ), 0600, true, false, true );
  connect( job, SIGNAL( result( KIO::Job * ) ), this, SLOT( slotIOJobFinished ( KIO::Job * ) ) );

  return;
}

bool KMultiPage::closeURL()
{
  // Clear navigation history.
  document_history.clear();

  // Close the file. Resize the widget to 0x0.
  renderer->setFile(QString::null);

  // Delete Page Widgets.
  widgetList.setAutoDelete(true);
  widgetList.resize(0);
  widgetList.setAutoDelete(false);

  // Update ScrollView.
  scrollView()->centerContents();
  enableActions(false);

  // Clear Thumbnail List.
  markList()->clear();

  // Clear Status Bar
  // TODO
  return true;
}

void KMultiPage::slotIOJobFinished ( KIO::Job *job )
{
  if ( job->error() )
    job->showErrorDialog( 0L );
}

void KMultiPage::slotShowScrollbars(bool status)
{
  _scrollView->slotShowScrollbars(status);
}

void KMultiPage::slotShowSidebar(bool show)
{
  if (show)
    markList()->show();
  else
    markList()->hide();
}

void KMultiPage::slotShowThumbnails(bool show)
{
  markList()->slotShowThumbnails(show);
}

void KMultiPage::slotSetFullPage(bool fullpage)
{
  _scrollView->setFullScreenMode(fullpage);
  if (fullpage)
    slotShowSidebar(false);
}

void KMultiPage::setViewMode(int mode)
{
  // Save the current page number because when we are changing the columns
  // and rows in the scrollview the currently shown Page probably out of view.
  PageNumber currentPage = currentPageNumber();

  // Save viewMode for future uses of KViewShell
  switch (mode) 
  {
    case KVS_SinglePage:
      KVSPrefs::setViewMode(KVS_SinglePage);
      scrollView()->setNrColumns(1);
      scrollView()->setNrRows(1);
      scrollView()->setContinuousViewMode(false);
      break;
    case KVS_ContinuousFacing:
      KVSPrefs::setViewMode(KVS_ContinuousFacing);
      scrollView()->setNrColumns(2);
      scrollView()->setNrRows(1);
      scrollView()->setContinuousViewMode(true);
      break;
    case KVS_Overview:
      KVSPrefs::setViewMode(KVS_Overview);
      scrollView()->setNrColumns(KVSPrefs::overviewModeColumns());
      scrollView()->setNrRows(KVSPrefs::overviewModeRows());
      scrollView()->setContinuousViewMode(false);
      break;
    default:  //KVS_Continuous
      KVSPrefs::setViewMode(KVS_Continuous);
      scrollView()->setNrColumns(1);
      scrollView()->setNrRows(1);
      scrollView()->setContinuousViewMode(true);
  }
  generateDocumentWidgets(currentPage);
  KVSPrefs::writeConfig();
  emit viewModeChanged();
}


documentWidget* KMultiPage::createDocumentWidget()
{
  // TODO: handle different sizes per page.
  documentWidget* _documentWidget = new documentWidget(scrollView()->viewport(), scrollView(),
                                                      pageCache.sizeOfPageInPixel(/*page+*/1), &pageCache, // FIXME
                                                      &userSelection, "singlePageWidget" );
  return _documentWidget;
}


void KMultiPage::generateDocumentWidgets(PageNumber startPage)
{
  // This function is only called with an invalid pagenumber, when
  // the file has been loaded or reloaded.
  bool reload = startPage.isInvalid();

  if (reload)
  {
    // Find the number of the current page, for later use.
    startPage = currentPageNumber();
  }

  // Make sure that startPage is in the permissible range.
  if (startPage < 1)
    startPage = 1;
  if (startPage > numberOfPages())
    startPage = numberOfPages();

  unsigned int tableauStartPage = startPage;

  // Find out how many widgets are needed, and resize the widgetList accordingly.
  widgetList.setAutoDelete(true);
  Q_UINT16 oldwidgetListSize = widgetList.size();
  if (renderer->totalPages() == 0)
    widgetList.resize(0);
  else
  {
    switch (KVSPrefs::viewMode())
    {
      case KVS_SinglePage:
        widgetList.resize(1);
        break;
      case KVS_Overview:
      {
        // Calculate the number of pages shown in overview mode.
        unsigned int visiblePages = KVSPrefs::overviewModeColumns() * KVSPrefs::overviewModeRows();
        // Calculate the number of the first page in the tableau.
        tableauStartPage = startPage - ((startPage - 1) % visiblePages);
        // We cannot have more widgets then pages in the document.
        visiblePages = QMIN(visiblePages, numberOfPages() - tableauStartPage + 1);
        if (widgetList.size() != visiblePages)
          widgetList.resize(visiblePages);
        break;
      }
      default:
        // In KVS_Continuous and KVS_ContinuousFacing all pages in the document are shown.
        widgetList.resize(numberOfPages());
    }
  }
  bool isWidgetListResized = (widgetList.size() != oldwidgetListSize);
  widgetList.setAutoDelete(false);

  // If the widgetList is empty, there is nothing left to do.
  if (widgetList.size() == 0) {
    scrollView()->addChild(&widgetList);
    return;
  }

  // Allocate DocumentWidget structures so that all entries of
  // widgetList point to a valid DocumentWidget.
  documentWidget *_documentWidget;
  for(Q_UINT16 i=0; i<widgetList.size(); i++) {
    _documentWidget = (documentWidget *)(widgetList[i]);
    if (_documentWidget == 0) {
      _documentWidget = createDocumentWidget();

      widgetList.insert(i, _documentWidget);
      _documentWidget->show();

      connect(_documentWidget, SIGNAL(localLink(const QString &)), this, SLOT(handleLocalLink(const QString &)));
      connect(_documentWidget, SIGNAL(setStatusBarText(const QString&)), this, SIGNAL(setStatusBarText(const QString&)) );
    }
  }

  // Set the page numbers for the newly allocated widgets. How this is
  // done depends on the viewMode.
  if (KVSPrefs::viewMode() == KVS_SinglePage) {
    // In KVS_SinglePage mode, any number between 1 and the maximum
    // number of pages is acceptable. If an acceptable value is found,
    // nothing is done, and otherwise '1' is set as a default.
    _documentWidget = (documentWidget *)(widgetList[0]);
    if (_documentWidget != 0) { // Paranoia safety check
      _documentWidget->setPageNumber(startPage);
      _documentWidget->update();
    } else
      kdError(4300) << "Zero-Pointer in widgetList in KMultiPage::generateDocumentWidgets()" << endl;
  } else {
    // In all other modes, the widgets will be numbered continuously,
    // starting from firstShownPage.
    for(Q_UINT16 i=0; i<widgetList.size(); i++) {
      _documentWidget = (documentWidget *)(widgetList[i]);
      if (_documentWidget != 0) // Paranoia safety check
        if (KVSPrefs::viewMode() == KVS_Overview)
          _documentWidget->setPageNumber(i+tableauStartPage);
      else
        _documentWidget->setPageNumber(i+1);
      else
        kdError(4300) << "Zero-Pointer in widgetList in KMultiPage::generateDocumentWidgets()" << endl;
    }
  }

  // Make the changes in the widgetList known to the scrollview. so
  // that the scrollview may update its contents.
  scrollView()->addChild(&widgetList);

  // If the number of widgets has changed, or the viewmode has been changed the widget 
  // that displays the current page may not be visible anymore. Bring it back into focus.
  if (isWidgetListResized || !reload)
    gotoPage(startPage);

  // The current page might have changed.
  calculateCurrentPageNumber();
}


bool KMultiPage::gotoPage(PageNumber page)
{
  if (widgetList.size() == 0) {
    kdError(4300) << "KMultiPage::gotoPage(" << page << ") called, but widgetList is empty" << endl;
    return false;
  }

  if (!page.isValid())
  {
    kdDebug(1223) << "KMultiPage::gotoPage(" << page << ") invalid pageNumber." << endl;
    return false;
  }

  document_history.add(page, 0);

  // If we are in overview viewmode
  if (KVSPrefs::viewMode() == KVS_Overview)
  {
    unsigned int visiblePages = KVSPrefs::overviewModeColumns() * KVSPrefs::overviewModeRows();
    // Pagenumber of the first page in the current tableau.
    unsigned int firstPage = ((documentWidget*)widgetList[0])->getPageNumber();
    // Pagenumber of the first page in the new tableau.
    unsigned int tableauStartPage = page - ((page - 1) % visiblePages);
    // If these numbers arn't equal "page" is not in the current tableu.
    if (firstPage != tableauStartPage) // widgets need to be updated
    {
      if ( (numberOfPages() - tableauStartPage < visiblePages) ||
            (widgetList.size() < visiblePages) )
      {
        // resize widgetList
        // the pages are also set correctly by "generateDocumentWidgets"
        generateDocumentWidgets(tableauStartPage);
      }
      else
      {
        // "page" is not shown in the scrollview, so we have to switch widgets.
        // Here we don't need to resize the widgetList.
        for (unsigned int i = 0; i < widgetList.size(); i++)
        {
          documentWidget* ptr = (documentWidget*)(widgetList[i]);
          if (ptr != 0)
          {
            ptr->setPageNumber(tableauStartPage + i);
          }
        }
      }
    }
    // move scrollview to "page".

    // Make the widget &ptr visible in the scrollview.
    documentWidget* ptr = (documentWidget*)(widgetList[page % visiblePages - 1]);
    scrollView()->moveViewportToWidget(ptr);

    // Set current page number.
    markList()->setCurrentPageNumber(page);
    emit pageInfo(numberOfPages(), currentPageNumber());
    return true;
  }

  if (widgetList.size() == 1) {
    // If the widget list contains only a single element, then either
    // the document contains only one page, or we are in "single page"
    // view mode. In either case, we set the page number of the single
    // widget to 'page'
    documentWidget* ptr = (documentWidget*)(widgetList[0]);

    // Paranoia security check
    if (ptr == 0) {
      kdError(4300) << "KMultiPage::gotoPage() called with widgetList.size() == 1, but widgetList[0] == 0" << endl;
      return false;
    }
    ptr->setPageNumber(page);

    // Make the widget &ptr visible in the scrollview.
    scrollView()->moveViewportToWidget(ptr);

  } else {
    // There are multiple widgets, then we are either in the
    // "Continuous" or in the "Continouous-Facing" view mode. In that
    // case, we find the widget which is supposed to display page
    // 'page' and move the scrollview to make it visible

    // Paranoia security checks
    if (widgetList.size() < page) {
      kdError(4300) << "KMultiPage::gotoPage(page) called with widgetList.size()=" << widgetList.size() 
                    << ", and page=" << page << endl;
      return false;
    }
    documentWidget* ptr = (documentWidget*)(widgetList[page-1]);
    if (ptr == 0) {
      kdError(4300) << "KMultiPage::gotoPage() called with widgetList.size() > 1, but widgetList[page] == 0"
                    << endl;
      return false;
    }

    // Make the widget &ptr visible in the scrollview.
    scrollView()->moveViewportToWidget(ptr);
  }

  // Set current page number.
  markList()->setCurrentPageNumber(page);
  emit pageInfo(numberOfPages(), currentPageNumber());
  return true;
}


void KMultiPage::goto_page(PageNumber page, int y, bool isLink)
{
  if (widgetList.size() == 0) {
    kdError(4300) << "KMultiPage::goto_Page(" << page << ", y) called, but widgetList is empty" << endl;
    return;
  }

  if (isLink)
    document_history.add(page, y);

  documentWidget *ptr;

  // If we are in overview viewmode
  if (KVSPrefs::viewMode() == KVS_Overview)
  {
    unsigned int visiblePages = KVSPrefs::overviewModeColumns() * KVSPrefs::overviewModeRows();
    // Pagenumber of the first visibile Page
    unsigned int firstPage = ((documentWidget*)widgetList[0])->getPageNumber();
    unsigned int tableauStartPage = page + 1 - (page % visiblePages);
    if (firstPage != tableauStartPage) // widgets need to be updated
    {
      if ( (renderer->totalPages() - tableauStartPage + 1 < visiblePages) ||
           (widgetList.size() < visiblePages) )
      {
        // resize widgetList
        // the pages are also set correctly by "generateDocumentWidgets"
        generateDocumentWidgets(tableauStartPage);
      }
      else
      {
        // "page" is not shown in the scrollview, so we have to switch widgets.
        // Here we don't need to resize the widgetList.
        for (unsigned int i = 0; i < widgetList.size(); i++)
        {
          ptr = (documentWidget*)(widgetList[i]);
          if (ptr != 0)
            ptr->setPageNumber(tableauStartPage + i);
        }
      }
    }
    // move scrollview to "page".
    // Make the widget &ptr visible in the scrollview. Somehow this
    // doesn't seem to trigger the signal contentsMoved in the
    // QScrollview, so that we better call setCurrentPage() ourselves.
    ptr = (documentWidget*)(widgetList[page % visiblePages]);
    // FIXME: This doesn't calculate the right position in all cases.
    scrollView()->setContentsPos(scrollView()->childX(ptr) - scrollView()->distanceBetweenPages(), 
    scrollView()->childY(ptr) + y);

    // Set current page number.
    markList()->setCurrentPageNumber(page);
    emit pageInfo(numberOfPages(), currentPageNumber());
  }
  else if (widgetList.size() == 1)
  {
    // If the widget list contains only a single element, then either
    // the document contains only one page, or we are in "single page"
    // view mode. In either case, we set the page number of the single
    // widget to 'page'
    ptr = (documentWidget *)(widgetList[0]);

    // Paranoia security check
    if (ptr == 0) {
      kdError(4300) << "KMultiPage::goto_Page() called with widgetList.size() == 1, but widgetList[0] == 0" << endl;
      return;
    }
    ptr->setPageNumber(page+1);

    scrollView()->ensureVisible(0, scrollView()->childY(ptr)+y);
  } else {
    // There are multiple widgets, then we are either in the
    // "Continuous" or in the "Continouous-Facing" view mode. In that
    // case, we find the widget which is supposed to display page
    // 'page' and move the scrollview to make it visible

    // Paranoia security checks
    if (widgetList.size() < page) {
      kdError(4300) << "KMultiPage::goto_Page(page,y ) called with widgetList.size()=" << widgetList.size() << ", and page=" << page << endl;
      return;
    }
    ptr = (documentWidget *)(widgetList[page]);
    if (ptr == 0) {
      kdError(4300) << "KMultiPage::goto_Page() called with widgetList.size() > 1, but widgetList[page] == 0" << endl;
      return;
    }

    // Make the widget &ptr visible in the scrollview. We try to do
    // that intelligently, so that the user gets to see most of the
    // widget
    if (ptr->height() < scrollView()->visibleHeight()) {
      // If the widget is less tall then the visible portion of the
      // viewPort, try to center the widget in the viewport
      scrollView()->setContentsPos(0, scrollView()->childY(ptr) - (scrollView()->visibleHeight()-ptr->height())/2);
    }
    else
      scrollView()->ensureVisible(0, scrollView()->childY(ptr)+y);

    // Set current page number.
    markList()->setCurrentPageNumber(page);
    emit pageInfo(numberOfPages(), currentPageNumber());
    ptr->update();
  }
  if (isLink)
    ptr->flash(y);
}


void KMultiPage::handleLocalLink(const QString &linkText)
{	  
#ifdef DEBUG_SPECIAL
  kdDebug(4300) << "hit: local link to " << linkText << endl;
#endif

  if (renderer == 0) {
    kdError(4300) << "KMultiPage::handleLocalLink( " << linkText << " ) called, but renderer==0" << endl;
    return;
  }

  QString locallink;
  if (linkText[0] == '#' )
    locallink = linkText.mid(1); // Drop the '#' at the beginning
  else
    locallink = linkText;

  anchor anch = renderer->findAnchor(locallink);

  if (anch.isValid())
    goto_page(anch.page-1, (int)(anch.distance_from_top_in_inch*pageCache.getResolution() + 0.5));
  else {
    if (linkText[0] != '#' ) {
      // We could in principle use KIO::Netaccess::run() here, but
      // it is perhaps not a very good idea to allow a DVI-file to
      // specify arbitrary commands, such as "rm -rvf /". Using
      // the kfmclient seems to be MUCH safer.
      QUrl DVI_Url(m_file);
      QUrl Link_Url(DVI_Url, linkText, TRUE );

      QStringList args;
      args << "openURL";
      args << Link_Url.toString();
      kapp->kdeinitExec("kfmclient", args);
    }
  }
}


void KMultiPage::calculateCurrentPageNumber()
{
  if (widgetList.size() == 1)
    return;

  // Check if the current page is still visible. If yes, don't
  // change anything. We call that 'lazy page number display': the
  // displayed page number remains unchanged as long as the
  // appropriate widget is still visible.
  if (widgetList.size() > currentPageNumber())
  {
    documentWidget* _documentWidget = (documentWidget*)(widgetList[currentPageNumber()]);
    if (_documentWidget != 0)
    {
      // in the overview mode these two must not match. TODO: find a better way to do this.
      if (_documentWidget->getPageNumber() == currentPageNumber())
      {
        // Found the widget. Now check if it is visible
        if ((scrollView()->childY(_documentWidget)<(scrollView()->contentsY()+scrollView()->visibleHeight())) &&
            (scrollView()->childY(_documentWidget)+_documentWidget->height()) > scrollView()->contentsY())
        {
          // Nothing to do
          return;
        }
      }
    }
  }

  // Otherwise, find the first widget that is visible, and return
  // the page number of that.
  for(Q_UINT16 i = 0; i < widgetList.size(); i++)
  {
    documentWidget* _documentWidget = (documentWidget*)(widgetList[i]);
    if (_documentWidget == 0)
      continue;

    int Y = scrollView()->childY(_documentWidget) + _documentWidget->height();
    if (Y > scrollView()->contentsY()) 
    {
      int pageNumber = _documentWidget->getPageNumber();

      markList()->setCurrentPageNumber(pageNumber);
      emit pageInfo(numberOfPages(), currentPageNumber());

      return;
    }
  }
}

PageNumber KMultiPage::currentPageNumber()
{
  return markList()->currentPageNumber();
}

void KMultiPage::doGoBack(void)
{
  HistoryItem *it = document_history.back();
  if (it != 0)
    goto_page(it->page, it->ypos, false); // Do not add a history item.
  else
    kdDebug(4300) << "Faulty return -- bad history buffer" << endl;
  return;
}


void KMultiPage::doGoForward(void)
{
  HistoryItem *it = document_history.forward();
  if (it != 0)
    goto_page(it->page, it->ypos, false); // Do not add a history item.
  else
    kdDebug(4300) << "Faulty return -- bad history buffer" << endl;
  return;
}


void KMultiPage::repaintAllVisibleWidgets(void)
{
  // Clear the page cache
  pageCache.clear();

  bool everResized = false;

  // Go through the list of widgets and resize them, if necessary
  for(Q_UINT16 i=0; i<widgetList.size(); i++) 
  {
    documentWidget* _documentWidget = (documentWidget*)(widgetList[i]);
    if (_documentWidget == 0)
      continue;

    // Resize, if necessary
    QSize pageSize = pageCache.sizeOfPageInPixel(i+1);
    if (pageSize != _documentWidget->pageSize()) 
    {
      _documentWidget->setPageSize(pageSize);
      everResized = true;
    }
  }

  // If at least one widget was resized, all widgets should be
  // re-aligned. This will automatically update all necessary
  // widgets. If no widgets were resized, go through the list of
  // widgets again, and update those that are visible.
  if (everResized == true)
    scrollView()->centerContents(true);
  else 
  {
    QRect visiblRect(scrollView()->contentsX(), scrollView()->contentsY(),
                     scrollView()->visibleWidth(), scrollView()->visibleHeight());
    for(Q_UINT16 i=0; i<widgetList.size(); i++) 
    {
      documentWidget *_documentWidget = (documentWidget *)(widgetList[i]);
      if (_documentWidget == 0)
        continue;

      // Check visibility, and update
      QRect widgetRect(scrollView()->childX(_documentWidget), scrollView()->childY(_documentWidget),
                       _documentWidget->width(), _documentWidget->height());
      if (widgetRect.intersects(visiblRect))
        _documentWidget->update();
    }
  }
}


double KMultiPage::setZoom(double zoom)
{
  if (zoom < ZoomLimits::MinZoom/1000.0)
    zoom = ZoomLimits::MinZoom/1000.0;
  if (zoom > ZoomLimits::MaxZoom/1000.0)
    zoom = ZoomLimits::MaxZoom/1000.0;

  pageCache.setResolution(QPaintDevice::x11AppDpiX()*zoom);
  emit zoomChanged();
  return zoom;
}


void KMultiPage::setRenderer(documentRenderer* _renderer)
{
  renderer = _renderer;

  // Initialize documentPageCache.
  pageCache.setRenderer(renderer);

  _markList->setPageCache(&pageCache);

  // Clear widget list.
  widgetList.resize(0);

  // Relay signals.
  connect(renderer, SIGNAL(setStatusBarText(const QString&)), this, SIGNAL(setStatusBarText(const QString&)));
  connect(&pageCache, SIGNAL(paperSizeChanged()), this, SLOT(repaintAllVisibleWidgets()));
  connect(&pageCache, SIGNAL(paperSizeChanged()), markList(), SLOT(repaintThumbnails()));
  connect(renderer, SIGNAL(needsRepainting()), this, SLOT(repaintAllVisibleWidgets()));
  connect(this, SIGNAL(zoomChanged()), this, SLOT(repaintAllVisibleWidgets()));
}

void KMultiPage::setNumberOfPages(int numberOfPages)
{
  markList()->clear();
  markList()->setNumberOfPages(numberOfPages, KVSPrefs::showThumbnails());
}

Q_UINT16 KMultiPage::numberOfPages()
{
  if (renderer == 0)
    return 0;

  return renderer->totalPages();
}

QValueList<int> KMultiPage::selectedPages()
{
  return markList()->selectedPages();
}

double KMultiPage::calculateFitToHeightZoomValue()
{
  // See below, in the documentation of the method "calculatefitToWidthZoomLevel"
  // for an explanation of the complicated calculation we are doing here.
  int columns = scrollView()->getNrColumns();
  int rows = scrollView()->getNrRows();
  int continuousViewmode = scrollView()->isContinuous();
  bool fullScreenMode = scrollView()->fullScreenMode();

  int pageDistance = scrollView()->distanceBetweenPages();
  if (columns == 1 && rows == 1 && !continuousViewmode && fullScreenMode)
  {
    // In Single Page Fullscreen Mode we want to fit the page to the
    // window without a margin around it.
    pageDistance = 0;
  }

  int targetViewportHeight = scrollView()->viewportSize(0,0).height();
  int targetPageHeight = (targetViewportHeight - (rows+1)*pageDistance)/rows;
  int targetPageWidth  = (int)(targetPageHeight * pageCache.sizeOfPage().aspectRatio() );
  int targetViewportWidth = targetPageWidth * columns + (columns+1)*pageDistance;
  targetViewportHeight = scrollView()->viewportSize(targetViewportWidth, targetViewportHeight).height();
  targetPageHeight = (targetViewportHeight - (rows+1)*pageDistance)/rows;

  return pageCache.sizeOfPage().zoomForHeight(targetPageHeight);
}


double KMultiPage::calculateFitToWidthZoomValue()
{
  int columns = scrollView()->getNrColumns();
  int rows = scrollView()->getNrRows();
  int continuousViewmode = scrollView()->isContinuous();
  bool fullScreenMode = scrollView()->fullScreenMode();
  // rows should be 1 for Single Page Viewmode,
  // the number of Pages in Continuous Viewmode
  // and number of Pages/2 in Continuous-Facing Viewmode
  if (continuousViewmode)
    rows = (int)(ceil(renderer->totalPages() / (double)columns));

  int pageDistance = scrollView()->distanceBetweenPages();
  if (columns == 1 && rows == 1 && !continuousViewmode && fullScreenMode)
  {
    // In Single Page Fullscreen Mode we want to fit the page to the
    // window without a margin around it.
    pageDistance = 0;
  }
  // There is a slight complication here... if we just take the width
  // of the viewport and scale the contents by a factor x so that it
  // fits the viewport exactly, then, depending on chosen papersize
  // (landscape, etc.), the contents may be higher than the viewport
  // and the QScrollview may or may not insert a scrollbar at the
  // right. If the scrollbar appears, then the usable width of the
  // viewport becomes smaller, and scaling by x does not really fit
  // the (now smaller page) anymore.

  // Calculate the width and height of the view, disregarding the
  // possible complications with scrollbars, e.g. assuming the maximal
  // space is available.

  // width of the widget excluding possible scrollbars
  int targetViewportWidth  = scrollView()->viewportSize(0,0).width();

  // maximal width of a single page
  int targetPageWidth = (targetViewportWidth - (columns+1) * pageDistance) / columns;

  // maximal height of a single page
  int targetPageHeight = (int)(targetPageWidth/pageCache.sizeOfPage().aspectRatio());
  int targetViewportHeight = rows * targetPageHeight + (rows+1) * pageDistance;
  // Think again, this time use only the area which is really
  // acessible (which, in case that targetWidth targetHeight don't fit
  // the viewport, is really smaller because of the scrollbars).
  targetViewportWidth = scrollView()->viewportSize(targetViewportWidth, targetViewportHeight).width();

  // maximal width of a single page (now the scrollbars are taken into account)
  targetPageWidth = (targetViewportWidth - (columns+1) * pageDistance) / columns;

  return pageCache.sizeOfPage().zoomForWidth(targetPageWidth);
}


void KMultiPage::prevPage()
{
  Q_UINT8 cols = scrollView()->getNrColumns();
  Q_UINT8 rows = scrollView()->getNrRows();

  Q_UINT16 np = 0;
  if (cols*rows < currentPageNumber())
    np = currentPageNumber() - cols*rows;

  gotoPage(np);
}


void KMultiPage::nextPage()
{
  Q_UINT8 cols = scrollView()->getNrColumns();
  Q_UINT8 rows = scrollView()->getNrRows();

  Q_UINT16 np = QMIN(currentPageNumber() + cols*rows, numberOfPages());

  gotoPage(np);
}


void KMultiPage::firstPage()
{
  gotoPage(1);
}


void KMultiPage::lastPage()
{
  gotoPage(numberOfPages());
}


void KMultiPage::scroll(Q_INT32 deltaInPixel)
{
  QScrollBar* scrollBar = scrollView()->verticalScrollBar();
  if (scrollBar == 0) {
    kdError(4300) << "KMultiPage::scroll called without scrollBar" << endl;
    return;
  }

  if (deltaInPixel < 0) {
    if (scrollBar->value() == scrollBar->minValue()) {
      if ( (currentPageNumber() == 0) || (changePageDelayTimer.isActive()) )
        return;

      if (scrollView()->isContinuous())
        return;

      changePageDelayTimer.stop();

      prevPage();
      scrollView()->setContentsPos(scrollView()->contentsX(), scrollBar->maxValue());
      return;
    }
  }

  if (deltaInPixel > 0) {
    if (scrollBar->value() == scrollBar->maxValue()) {
      if ( (currentPageNumber() + 1 == numberOfPages()) || (changePageDelayTimer.isActive()) )
        return;

      if (scrollView()->isContinuous())
        return;

      changePageDelayTimer.stop();
      nextPage();

      scrollView()->setContentsPos(scrollView()->contentsX(), 0);
      return;
    }
  }

  scrollBar->setValue(scrollBar->value() + deltaInPixel);

  if ( (scrollBar->value() == scrollBar->maxValue()) || (scrollBar->value() == scrollBar->minValue()) )
    changePageDelayTimer.start(200,true);
  else
    changePageDelayTimer.stop();
}


void KMultiPage::scrollUp()
{
  QScrollBar* scrollBar = scrollView()->verticalScrollBar();
  if (scrollBar == 0)
    return;

  scroll(-scrollBar->lineStep());
}


void KMultiPage::scrollDown()
{
  QScrollBar* scrollBar = scrollView()->verticalScrollBar();
  if (scrollBar == 0)
    return;

  scroll(scrollBar->lineStep());
}

void KMultiPage::scrollLeft()
{
  QScrollBar* scrollBar = scrollView()->horizontalScrollBar();
  if (scrollBar)
    scrollBar->subtractLine();
}


void KMultiPage::scrollRight()
{
  QScrollBar* scrollBar = scrollView()->horizontalScrollBar();
  if (scrollBar)
    scrollBar->addLine();
}


void KMultiPage::scrollUpPage()
{
  QScrollBar* scrollBar = scrollView()->verticalScrollBar();
  if (scrollBar)
    scrollBar->subtractPage();
}


void KMultiPage::scrollDownPage()
{
  QScrollBar* scrollBar = scrollView()->verticalScrollBar();
  if (scrollBar)
    scrollBar->addPage();
}


void KMultiPage::scrollLeftPage()
{
  QScrollBar* scrollBar = scrollView()->horizontalScrollBar();
  if (scrollBar)
    scrollBar->subtractPage();
}


void KMultiPage::scrollRightPage()
{
  QScrollBar* scrollBar = scrollView()->horizontalScrollBar();
  if (scrollBar)
    scrollBar->addPage();
}


void KMultiPage::readDown()
{
  CenteringScrollview* sv = scrollView();

  if (sv->atBottom())
  {
    if (sv->isContinuous())
      return;

    if (currentPageNumber() == numberOfPages())
      return;

    nextPage();
    sv->setContentsPos(sv->contentsX(), 0);
  }
  else
    sv->readDown();
}


void KMultiPage::readUp()
{
  CenteringScrollview* sv = scrollView();

  if (sv->atTop())
  {
    if (sv->isContinuous())
      return;

    if (currentPageNumber() == 1)
      return;

    prevPage();
    sv->setContentsPos(sv->contentsX(),  sv->contentsHeight());
  }
  else
    sv->readUp();
}


void KMultiPage::jumpToReference(QString reference)
{
  if (renderer == 0)
    return;
  
  gotoPage(renderer->parseReference(reference));
}


void KMultiPage::gotoPage(const anchor &a)
{
  if (a.page.isInvalid() || (renderer == 0))
    return;

  goto_page(a.page-1, (int)(a.distance_from_top_in_inch * pageCache.getResolution() + 0.5));
}


void KMultiPage::gotoPage(int pageNr, int beginSelection, int endSelection )
{
#ifdef KDVI_MULTIPAGE_DEBUG
  kdDebug(4300) << "KMultiPage::gotoPage( pageNr=" << pageNr << ", beginSelection=" << beginSelection <<", endSelection=" << endSelection <<" )" << endl;
#endif

  if (pageNr == 0) {
    kdError(4300) << "KMultiPage::gotoPage(...) called with pageNr=0" << endl;
    return;
  }

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

  QString selectedText("");
  for(int i = beginSelection; i < endSelection; i++) {
    selectedText += pageData->textLinkList[i].linkText;
    selectedText += "\n";
  }
  userSelection.set(pageNr, beginSelection, endSelection, selectedText);


  Q_UINT16 y = pageData->textLinkList[beginSelection].box.bottom();
  goto_page(pageNr-1, y);
}


void KMultiPage::doSelectAll(void)
{
  switch( widgetList.size() ) {
  case 0:
    kdError(4300) << "KMultiPage::doSelectAll(void) while widgetList is empty" << endl;
    break;
  case 1:
    ((documentWidget *)widgetList[0])->selectAll();
    break;
  default:
    if (widgetList.size() < currentPageNumber())
      kdError(4300) << "KMultiPage::doSelectAll(void) while widgetList.size()=" << widgetList.size() << "and currentPageNumber()=" << currentPageNumber() << endl;
    else
      ((documentWidget *)widgetList[currentPageNumber()-1])->selectAll();
  }
}


void  KMultiPage::showFindTextDialog(void)
{
  if ((renderer == 0) || (renderer->supportsTextSearch() == false))
    return;

  if (findDialog == 0) {
    // WARNING: This text appears several times in the code. Change
    // everywhere, or nowhere!
    if (KMessageBox::warningContinueCancel( scrollView(), 
					    i18n("<qt>This function searches the DVI file for plain text. Unfortunately, this version of "
						 "KDVI treats only plain ASCII characters properly. Symbols, ligatures, mathematical "
						 "formulae, accented characters, and non-english text, such as Russian or Korean, will "
						 "most likely be messed up completely. Continue anyway?</qt>"),
					    i18n("Function May Not Work as Expected"),
					    KStdGuiItem::cont(),
					    "warning_search_text_may_not_work") == KMessageBox::Cancel)
      return;
    findDialog = new KEdFind( scrollView(), "Text find dialog", true);
    if (findDialog == 0) {
      kdError(4300) << "Could not open text search dialog" << endl;
      return;
    }
    findDialog->setName("text search dialog");
    connect(findDialog, SIGNAL(search()), this, SLOT(findText()));
  }
  findDialog->show();
}


void KMultiPage::findText(void)
{
#ifdef KDVI_MULTIPAGE_DEBUG
  kdDebug(4300) << "KMultiPage::findText(void) called" << endl;
#endif

  // Paranoid security checks
  if (findDialog == 0) {
    kdError(4300) << "KMultiPage::findText(void) called but findDialog == 0" << endl;
    return;
  }

  QString searchText  = findDialog->getText();
  if (searchText.isEmpty())
    return;

  if (findNextAction != 0)
    findNextAction->setEnabled(!searchText.isEmpty());
  if (findPrevAction != 0)
    findPrevAction->setEnabled(!searchText.isEmpty());

  if (findDialog->get_direction())
    findPrevText();
  else
    findNextText();
}


void KMultiPage::findNextText(void)
{
#ifdef KDVI_MULTIPAGE_DEBUG
  kdDebug(4300) << "KMultiPage::findNextText() called" << endl;
#endif

  // Paranoia safety checks
  if (findDialog == 0) {
    kdError(4300) << "KMultiPage::findNextText(void) called when findDialog == 0" << endl;
    return;
  }
  QString searchText = findDialog->getText();

  if (searchText.isEmpty()) {
    kdError(4300) << "KMultiPage::findNextText(void) called when search text was empty" << endl;
    return;
  }

  bool case_sensitive = findDialog->case_sensitive();
  bool oneTimeRound   = false;


  QProgressDialog progress(i18n("Searching for '%1'...").arg(searchText), i18n("Abort"),
                           renderer->totalPages(), scrollView(), "searchForwardTextProgress", true);
  progress.setMinimumDuration ( 1000 );

  documentPagePixmap dummyPage;
  dummyPage.resize(1,1);

  // Find the page and text position on the page where the search will
  // start. If nothing is selected, we start at the beginning of the
  // current page. Otherwise, start after the selected text.  TODO:
  // Optimize this to get a better 'user feeling'
  Q_UINT16 startingPage;
  Q_UINT16 startingTextItem;
  if (userSelection.getPageNumber() == 0) {
    startingPage     = currentPageNumber();
    startingTextItem = 0;
  } else {
    startingPage     = userSelection.getPageNumber();
    startingTextItem = userSelection.getSelectedTextEnd()+1;
  }

  for(Q_UINT16 pageNumber = startingPage;; pageNumber++) {
    // If we reach the end of the last page, start from the beginning
    // of the document, but ask the user first.
    if (pageNumber > renderer->totalPages()) {
      progress.hide();
      if (oneTimeRound == true)
        break;
      oneTimeRound = true;

      // Do not ask the user if the search really started from the
      // beginning of the first page
      if ( (startingPage == 1)&&(startingTextItem == 0) )
        return;

      int answ = KMessageBox::questionYesNo(scrollView(),
                 i18n("<qt>The search string <strong>%1</strong> could not be found by the "
                      "end of the document. Should the search be restarted from the beginning "
                      "of the document?</qt>").arg(searchText),
                 i18n("Text Not Found"));
      if (answ == KMessageBox::Yes)
        pageNumber = 1;
      else
        return;
    }

    if (pageNumber > startingPage)
      progress.setProgress( pageNumber - startingPage );
    else
      progress.setProgress( pageNumber + renderer->totalPages() - startingPage );

    qApp->processEvents();
    if ( progress.wasCancelled() )
      break;

    dummyPage.setPageNumber(pageNumber);
    renderer->drawPage(100.0, &dummyPage); // We don't really care for errors in draw_page(), no error handling here.
    if (dummyPage.textLinkList.size() == 0)
      continue;

    // Go trough the text of the page and search for the string. How
    // much of the page we actually search depends on the page: we
    // start the search on the current page *after* the selected text,
    // if there is any, then search the other pages entirely, and then
    // the rest of the current page.
    if (pageNumber != startingPage) {
      // On pages which are not our current page, search through
      // everything
      for(unsigned int i=0; i<dummyPage.textLinkList.size(); i++) {
        if (dummyPage.textLinkList[i].linkText.find(searchText, 0, case_sensitive) >= 0) {
          gotoPage(pageNumber, i, i );
          return;
        }
      }
    } else {
      if (oneTimeRound == false) {
        // The first time we search on the current page, search
        // everything from the end of the selected text to the end of
        // the page
        for(unsigned int i=startingTextItem; i<dummyPage.textLinkList.size(); i++) {
          if (dummyPage.textLinkList[i].linkText.find(searchText, 0, case_sensitive) >= 0) {
            gotoPage(pageNumber, i, i );
            return;
          }
        }
      } else {
        // The second time we come to the current page, search
        // everything from the beginning of the page till the
        // beginning of the selected test, then end the search.
        for(unsigned int i=0; i<startingTextItem; i++) {
          if (dummyPage.textLinkList[i].linkText.find(searchText, 0, case_sensitive) >= 0) {
            gotoPage(pageNumber, i, i );
            return;
          }
        }
        KMessageBox::sorry( scrollView(), i18n("<qt>The search string <strong>%1</strong> could not be found.</qt>").arg(searchText) );
        return;
      }
    }
  } // of while (all pages)
}


void KMultiPage::findPrevText(void)
{
#ifdef KDVI_MULTIPAGE_DEBUG
  kdDebug(4300) << "KMultiPage::findPrevText() called" << endl;
#endif

  // Paranoia safety checks
  if (findDialog == 0) {
    kdError(4300) << "KMultiPage::findPrevText(void) called when findDialog == 0" << endl;
    return;
  }
  QString searchText = findDialog->getText();

  if (searchText.isEmpty()) {
    kdError(4300) << "KMultiPage::findPrevText(void) called when search text was empty" << endl;
    return;
  }

  bool case_sensitive = findDialog->case_sensitive();
  bool oneTimeRound    = false;


  QProgressDialog progress( i18n("Searching for '%1'...").arg(searchText), i18n("Abort"),
                            renderer->totalPages(), scrollView(), "searchForwardTextProgress", true );
  progress.setMinimumDuration ( 1000 );

  // Find the page and text position on the page where the search will
  // start. If nothing is selected, we start at the end of the current
  // page. Otherwise, start before the selected text.  TODO: Optimize
  // this to get a better 'user feeling'
  Q_UINT16 startingPage;
  Q_UINT16 startingTextItem;
  documentPagePixmap dummyPage;
  dummyPage.resize(1,1); 
  if (userSelection.getPageNumber() == 0) {
    startingPage     = currentPageNumber();
    dummyPage.setPageNumber(startingPage);
    renderer->drawPage(100.0, &dummyPage);
    startingTextItem = dummyPage.textLinkList.size();
  } else {
    startingPage     = userSelection.getPageNumber();
    startingTextItem = userSelection.getSelectedTextStart();
  }


  for(Q_UINT16 pageNumber = startingPage;; pageNumber--) {
    // If we reach the beginning of the last page, start from the end
    // of the document, but ask the user first.
    if (pageNumber <= 0) {
      progress.hide();
      if (oneTimeRound == true)
        break;
      oneTimeRound = true;

      int answ = KMessageBox::questionYesNo(scrollView(),
                 i18n("<qt>The search string <strong>%1</strong> could not be found by the "
                      "beginning of the document. Should the search be restarted from the end "
                      "of the document?</qt>").arg(searchText),
                 i18n("Text Not Found"));
      if (answ == KMessageBox::Yes)
        pageNumber = renderer->totalPages();
      else
        return;
    }

    if (pageNumber < startingPage)
      progress.setProgress( startingPage - pageNumber );
    else
      progress.setProgress( startingPage + renderer->totalPages() - pageNumber );

    qApp->processEvents();
    if ( progress.wasCancelled() )
      break;

    dummyPage.setPageNumber(pageNumber);
    renderer->drawPage(100.0, &dummyPage);
    if (dummyPage.textLinkList.size() == 0)
      continue;


    // Go trough the text of the page and search for the string. How
    // much of the page we actually search depends on the page: we
    // start the search on the current page *before* the selected
    // text, if there is any, then search the other pages entirely,
    // and then the rest of the current page.
    if (pageNumber != startingPage) {
      // On pages which are not our current page, search through
      // everything
      for(int i=dummyPage.textLinkList.size()-1; i>=0; i--) {
        if (dummyPage.textLinkList[i].linkText.find(searchText, 0, case_sensitive) >= 0) {
          gotoPage(pageNumber, i, i );
          return;
        }
      }
    } else {
      if (oneTimeRound == false) {
        // The first time we search on the current page, search
        // everything from the beginning of the selected text to the
        // beginning of the page
        for(int i = startingTextItem-1; i>=0; i--) {
          if (dummyPage.textLinkList[i].linkText.find(searchText, 0, case_sensitive) >= 0) {
            gotoPage(pageNumber, i, i );
            return;
          }
        }
      } else {
        // The second time we come to the current page, search
        // everything from the end of the page till the end of the
        // selected test, then end the search.
        for(int i=dummyPage.textLinkList.size()-1; i>startingTextItem; i--) {
          if (dummyPage.textLinkList[i].linkText.find(searchText, 0, case_sensitive) >= 0) {
            gotoPage(pageNumber, i, i );
            return;
          }
        }
        KMessageBox::sorry( scrollView(), i18n("<qt>The search string <strong>%1</strong> could not be found.</qt>").arg(searchText) );
        return;
      }
    }
  } // of while (all pages)
}


void KMultiPage::enableActions(bool fileLoaded)
{
  bool textSearch = false;
  if (fileLoaded && (getRenderer() != 0) && getRenderer()->supportsTextSearch())
    textSearch = true;

  findTextAction->setEnabled(textSearch);
  selectAllAction->setEnabled(textSearch);
}
