#include <stdlib.h>

#include <qfileinfo.h>
#include <qlayout.h>
#include <qobjectlist.h>
#include <qscrollview.h>
#include <qscrollbar.h>
#include <qtimer.h>
#include <qpainter.h>
#include <qprinter.h>
#include <qprintdialog.h>


#include <kaccel.h>
#include <kaction.h>
#include <kapplication.h>
#include <kconfig.h>
#include <kdebug.h>
#include <kfilterbase.h>
#include <kfilterdev.h>
#include <kglobal.h>
#include <kinstance.h>
#include <kio/job.h>
#include <klocale.h>
#include <kiconloader.h>
#include <kmessagebox.h>
#include <kmimetype.h>
#include <kprogress.h>
#include <kstandarddirs.h>
#include <kstdaction.h>
#include <ktempfile.h>


#include <stdlib.h>
#include <unistd.h>
#include <math.h>

#include "marklist.h"
#include "scrollbox.h"
#include "gotodialog.h"
#include "kpagetest.h"
#include "kviewpart.moc"
#include "pageSize.h"
#include "pageSizeDialog.h"

extern "C"
{
  void *init_kviewerpart()
  {
    return new KViewPartFactory;
  }
}


KInstance *KViewPartFactory::s_instance = 0L;


KViewPartFactory::KViewPartFactory()
{
}


KViewPartFactory::~KViewPartFactory()
{
  if (s_instance)
    delete s_instance;

  s_instance = 0;
}


KParts::Part *KViewPartFactory::createPartObject( QWidget *parentWidget, const char *widgetName, QObject *parent, const char *name, const char *, const QStringList &args )
{
  QString partname = "";
  if (args.count() >= 1)
    partname = args[0];
  KViewPart *obj = new KViewPart(partname, parentWidget, widgetName, parent, name);
  if (!obj->isValid())
  {
     delete obj;
     return 0;
  }
  return obj;
}


KInstance *KViewPartFactory::instance()
{
  if (!s_instance)
    s_instance = new KInstance("kviewerpart");
  return s_instance;
}


KViewPart::KViewPart(QString partname, QWidget *parentWidget, const char *widgetName, QObject *parent, const char *name)
  : KViewPart_Iface(parent, name), _partname(partname), multiPage(0), _numberOfPages(0),
  _currentPage(0)
{
  tmpUnzipped = 0L;

  documentHasRequestedPaperSize = false;

  pageChangeIsConnected = false;
  setInstance(KViewPartFactory::instance());

  watch = KDirWatch::self();
  connect(watch, SIGNAL(dirty(const QString&)), this, SLOT(fileChanged(const QString&)));
  watch->startScan();

  connect(&previewPaintInitiator, SIGNAL(timeout()), this, SLOT(doRepaintScrollBoxBackground()) );

  mainWidget = new QWidget(parentWidget, widgetName);
  mainWidget->setFocusPolicy(QWidget::StrongFocus);
  setWidget(mainWidget);

  QHBoxLayout *hbox = new QHBoxLayout(mainWidget, 0, 0);
  QVBoxLayout *vbox = new QVBoxLayout(hbox);

  scrollBox = new ScrollBox(mainWidget);
  scrollBox->setFixedWidth(75);
  scrollBox->setMinimumHeight(75);
  vbox->addWidget(scrollBox);

  connect(scrollBox, SIGNAL(valueChanged(QPoint)), this, SLOT(scrollBoxChanged(QPoint)));

  markList = new MarkList(mainWidget);
  markList->setAutoUpdate(true);
  vbox->addWidget(markList);
  vbox->setStretchFactor(markList, 1);

  connect(markList, SIGNAL(selected(int)), this, SLOT(pageSelected(int)));

  // create the displaying part

  // Try to load
  KLibFactory *factory;
  factory = KLibLoader::self()->factory(QString("k%1part").arg(_partname).utf8());
  // Support old style parts (as libs)
  if (!factory)
    factory = KLibLoader::self()->factory(QString("libk%1").arg(_partname).utf8());
  if (factory) {
    multiPage = (KMultiPage*) factory->create(mainWidget, QString("k%1").arg(_partname).utf8(), "KPart");
  } else {
    KMessageBox::error(parentWidget, i18n("No module (%1) found").arg(_partname));
  }
  if (!multiPage)
     return; // Abort
  
  hbox->addWidget(multiPage->widget());

  // connect to the multi page view
  connect(this, SIGNAL(scrollbarStatusChanged(bool)), multiPage, SLOT(slotShowScrollbars(bool)));
  connect(multiPage, SIGNAL(numberOfPages(int)), this, SLOT(numberOfPages(int)));
  connect(multiPage, SIGNAL(pageInfo(int, int)), this, SLOT(pageInfo(int, int)) );
  connect(multiPage, SIGNAL(askingToCheckActions()), this, SLOT(checkActions()) );
  connect(multiPage->scrollView(), SIGNAL(contentsMoving(int,int)), this, SLOT(contentsMoving(int,int)));
  multiPage->scrollView()->installEventFilter(this);
  connect( multiPage, SIGNAL( started( KIO::Job * ) ), this, SIGNAL( started( KIO::Job * ) ) );
  connect( multiPage, SIGNAL( completed() ), this, SIGNAL( completed() ) );
  connect( multiPage, SIGNAL( canceled( const QString & ) ), this, SIGNAL( canceled( const QString & ) ) );
  connect( multiPage, SIGNAL( previewChanged(bool)), this, SLOT(updatePreview(bool)));
  connect( multiPage, SIGNAL( setStatusBarText( const QString& ) ), this, SLOT( setStatusBarTextFromMultiPage( const QString& ) ) );
  connect( multiPage, SIGNAL( documentSpecifiedPageSize(const pageSize&) ), this, SLOT( slotPageSizeRequests(const pageSize&) ) );
  connect( multiPage->scrollView(), SIGNAL( wheelEventReceived(QWheelEvent *)), this,   SLOT(wheelEvent(QWheelEvent *)));
  // settings menu
  showmarklist = new KToggleAction (i18n("Show &Sidebar"), 0, this, SLOT(slotShowMarkList()), actionCollection(), "show_page_list");
  showPreview = new KToggleAction (i18n("Show P&review"), 0, this, SLOT(slotPreview()), actionCollection(), "show_preview");
  watchAct = new KToggleAction(i18n("&Watch File"), 0, 0, 0, actionCollection(), "watch_file");
  scrollbarHandling = new KToggleAction (i18n("Show Scrollbars"), 0, 0, 0, actionCollection(), "scrollbarHandling");
  connect(scrollbarHandling, SIGNAL(toggled(bool)),  multiPage, SLOT(slotShowScrollbars(bool)));

  // Orientation menu
  QStringList orientations;
  orientations.append(i18n("Portrait"));
  orientations.append(i18n("Landscape"));
  orientation = new KSelectAction (i18n("Preferred &Orientation"), 0, 0, 0, actionCollection(), "view_orientation");
  orientation->setItems(orientations);
  connect(orientation, SIGNAL(activated (int)), &userRequestedPaperSize, SLOT(setOrientation(int)));

  // Zoom Menu
  zoom_action = new KSelectAction (i18n("&Zoom"), 0, 0, 0, actionCollection(), "view_zoom");
  zoom_action->setComboWidth(80);
  zoom_action->setEditable(true);
  zoom_action->setItems(_zoomVal.zoomNames());
  _zoomVal.setZoomValue(1.0); // should not be necessary @@@@
  connect (&_zoomVal, SIGNAL(zoomNamesChanged(const QStringList &)), zoom_action, SLOT(setItems(const QStringList &)));
  connect (&_zoomVal, SIGNAL(valNoChanged(int)), zoom_action, SLOT(setCurrentItem(int)));
  connect (zoom_action, SIGNAL(activated(const QString &)), this, SLOT(setZoomValue(const QString &)));

  // Paper Size Menu
  media = new KSelectAction (i18n("Preferred Paper &Size"), 0, 0, 0, actionCollection(), "view_media");
  QStringList items = _paperSize.pageSizeNames();
  items.prepend(i18n("Custom Size..."));
  media->setItems(items);
  connect (media, SIGNAL(activated(int)), this, SLOT(slotMedia(int)));

  useDocumentSpecifiedSize = new KToggleAction(i18n("&Use Document Specified Paper Size"), 0, this, SLOT(slotShowMarkList()), 
					       actionCollection(), "view_use_document_specified_size");
  connect(useDocumentSpecifiedSize, SIGNAL(toggled(bool)),  this, SLOT(slotUseDocumentSpecifiedSize(bool)));

  // Zoom Actions
  zoomInAct = KStdAction::zoomIn (this, SLOT(zoomIn()), actionCollection());
  zoomOutAct = KStdAction::zoomOut(this, SLOT(zoomOut()), actionCollection());
  fitAct = KStdAction::actualSize(this, SLOT(fitSize()), actionCollection());
  fitPageAct = KStdAction::fitToPage(this, SLOT(fitToPage()), actionCollection());
  fitWidthAct = KStdAction::fitToWidth(this, SLOT(fitToWidth()), actionCollection());
  fitHeightAct = KStdAction::fitToHeight(this, SLOT(fitToHeight()), actionCollection());

  // go menu
  backAct = KStdAction::prior(this, SLOT(prevPage()), actionCollection());
  forwardAct = KStdAction::next(this, SLOT(nextPage()), actionCollection());
  startAct = KStdAction::firstPage(this, SLOT(firstPage()), actionCollection());
  endAct = KStdAction::lastPage(this, SLOT(lastPage()), actionCollection());
  gotoAct = KStdAction::gotoPage(this, SLOT(goToPage()), actionCollection());

  readUpAct = new KAction(i18n("Read Up Document"), "previous", SHIFT+Key_Space, this, SLOT(readUp() ), actionCollection(), "go_read_up");
  readDownAct = new KAction(i18n("Read Down Document"), "next", Key_Space, this, SLOT(readDown() ), actionCollection(), "go_read_down");

  printAction = KStdAction::print(this, SLOT(slotPrint()), actionCollection());
  if (multiPage->isReadWrite() )
    saveAction = KStdAction::save(multiPage, SLOT(slotSave_defaultFilename()), actionCollection());
  saveAsAction = KStdAction::saveAs(multiPage, SLOT(slotSave()), actionCollection());

  // keyboard accelerators
  accel = new KAccel(mainWidget);
  accel->insert(I18N_NOOP("Scroll Up"), Key_Up, this, SLOT(scrollUp()));
  accel->insert(I18N_NOOP("Scroll Down"), Key_Down, this, SLOT(scrollDown()));
  accel->insert(I18N_NOOP("Scroll Left"), Key_Left, this, SLOT(scrollLeft()));
  accel->insert(I18N_NOOP("Scroll Right"), Key_Right, this, SLOT(scrollRight()));

  accel->insert(I18N_NOOP("Scroll Up Page"), SHIFT+Key_Up, this, SLOT(scrollUpPage()));
  accel->insert(I18N_NOOP("Scroll Down Page"), SHIFT+Key_Down, this, SLOT(scrollDownPage()));
  accel->insert(I18N_NOOP("Scroll Left Page"), SHIFT+Key_Left, this, SLOT(scrollLeftPage()));
  accel->insert(I18N_NOOP("Scroll Right Page"), SHIFT+Key_Right, this, SLOT(scrollRightPage()));

  accel->readSettings();

  setXMLFile("kviewerpart.rc");

  m_extension = new KViewPartExtension(this);

  // create the goto dialog
  _gotoDialog = new GotoDialog(mainWidget);
  _gotoDialog->hide();
  connect(_gotoDialog, SIGNAL(gotoPage(const QString&)), this, SLOT(slotGotoDialog(const QString&)));

  // The page size dialog is constructed on first usage -- saves some
  // memory when not used.
  _pageSizeDialog = 0;

  numberOfPages(0);
  checkActions();

  // allow parts to have a GUI, too :-)
  // (will be merged automatically)
  insertChildClient( multiPage );

  // Connect _paperSize to the slot that updated the GUI whenever the
  // paper size changes
  connect(&_paperSize, SIGNAL(sizeChanged(float, float)), this, SLOT(setPaperSize(float, float)));

  // This will update the menu entries, and modify _paperSize whenever
  // the user requests a new paper size
  connect(&userRequestedPaperSize, SIGNAL(sizeChanged(float, float)), this, SLOT(slotUserPreferredSizeChanged(float, float)));

  readSettings();

  // watch mouse events in the viewport
  QWidget *vp = multiPage->scrollView()->viewport();
  vp->installEventFilter(this);
  // we also have to set an filter on the child
  // TODO: solve this more elegantly
  if (vp->children()) {
    QWidget *w = (QWidget*)((QObjectList*)(vp->children()))->first();
    w->installEventFilter(this);
  }
}

KViewPart::~KViewPart()
{
  if (multiPage)
    writeSettings();
  delete multiPage;
  delete tmpUnzipped;
}


QString KViewPart::pageSizeDescription(void)
{
  // Display the current paper size in the status bar. If the format
  // is one of the known sizes, then it makes sense to say if the
  // orientation is "portrait" or "landscape". We give this
  // information also.
  QString size = " ";

  if (_paperSize.formatNumber() == -1) {
    if (KGlobal::locale()-> measureSystem() == KLocale::Metric)
      size += QString("%1x%2 mm").arg(_paperSize.width_in_mm(), 0, 'f', 0).arg(_paperSize.height_in_mm(), 0, 'f', 0);
    else 
      size += QString("%1x%2 in").arg(_paperSize.width_in_mm()/25.4, 0, 'g', 2).arg(_paperSize.height_in_mm()/25.4, 0, 'g', 2);
  } else {
    size += _paperSize.formatName() + "/";
    if (_paperSize.getOrientation() == 0)
      size += i18n("portrait");
    else
      size += i18n("landscape");
  }
  return size+" ";
}


void KViewPart::setPaperSize(float width_in_mm, float height_in_mm)
{
  emit sizeChanged(pageSizeDescription());
  
  // And.... well, yes, also set the paper size in the scrollbox and
  // in the multipage
  float w = width_in_mm/10.0;
  float h = height_in_mm/10.0;

  // reflect in scrollbox
  scrollBox->setMinimumHeight( int(75.0*h/w+0.5));

  // forward
  multiPage->setPaperSize(w, h);

  updateScrollBox();
}


QStringList KViewPart::fileFormats()
{
  // The multiPage returns a list of endings such as "*.dvi". But the
  // kviewshell is also able to read compressed files and to
  // uncompress them on the fly. Thus, we modify the list of supported
  // file formats which we obtain from the multipage to include
  // compressed files like "*.dvi.gz". We add "*.dvi.bz2" if support
  // for bzip2 is compiled into KDE.

  // Check if this version of KDE supports bzip2 
  bool bzip2Available = (KFilterBase::findFilterByMimeType( "application/x-bzip2" ) != 0L);

  QStringList FF = multiPage->fileFormats();
  for(QStringList::Iterator it = FF.begin(); it != FF.end(); ++it ) {
    // Check if we have a mimetype-filter. If so, we don't touch it
    // and go on.
    int pos = (*it).find('/');
    if (pos > 0 && (*it)[pos - 1] != '\\') 
      break;
    
    // Otherwise parse the string and add endings
    QStringList endings  = QStringList::split( " ", (*it).section( '|', 0, 0 ) ) ;
    QString descriptions = (*it).section( '|', 1 );
    QString newFormat;
    
    for(QStringList::Iterator it2 = endings.begin(); it2 != endings.end(); ++it2 ) {
      *it2 = (*it2).stripWhiteSpace();
      newFormat += *it2 + " ";
      if ((*it2).find(".gz", -3) == -1) // Paranoia safety check
	newFormat += (*it2).stripWhiteSpace() + ".gz ";
      if ((bzip2Available) && ((*it2).find(".bz2", -4) == -1))
	newFormat += (*it2).stripWhiteSpace() + ".bz2 ";
    }
    if (!descriptions.isEmpty())
      newFormat += "|" + descriptions;

    *it = newFormat;
  }
  return FF;
}


void KViewPart::slotSetFullPage(bool fullpage)
{
  if (multiPage)
    multiPage->slotSetFullPage(fullpage);
  else
    kdError() << "KViewPart::slotSetFullPage() called without existing multipage" << endl;

  if (fullpage == true) {
    markList->hide();
    scrollBox->hide();
  } else 
    slotShowMarkList();
}


void KViewPart::slotPageSizeRequests(const pageSize &size) 
{
  documentHasRequestedPaperSize = true;
  documentRequestedPaperSize = size;
  
  // Use the document specified paper size, if allowed. The GUI will
  // be updated automatically by the signal/slot mechanism
  if (useDocumentSpecifiedSize->isChecked())
    _paperSize = size;
}


void KViewPart::slotShowMarkList()
{
  if (showmarklist->isChecked()) {
    markList->show();
    scrollBox->show();
  } else {
    markList->hide();
    scrollBox->hide();
  }
}


bool KViewPart::openFile()
{
  KURL tmpFileURL;

  // We try to be error-tolerant about filenames. If the user calls us
  // with something like "test", and we are using the DVI-part, we'll
  // also look for "testdvi" and "test.dvi".
  QFileInfo fi(m_file);
  m_file = fi.absFilePath();

  if (fi.exists() == false) {
    // First produce a list of possible endings we try to append to
    // the filename. We decompose the format strings like "*.dvi
    // *.DVI|DVI File" coming from the multipage part.
    QStringList endings;
    QStringList formats = fileFormats();
    for ( QStringList::Iterator it = formats.begin(); it != formats.end(); ++it ) {
      QString ending = (*it).simplifyWhiteSpace();
      int bar = ending.find('|');
      if (bar != -1)
	ending = ending.left(bar);

      QStringList localendings = QStringList::split(" ",ending);
      for ( QStringList::Iterator it2 = localendings.begin(); it2 != localendings.end(); ++it2 )
	endings += (*it2).mid(2); // remove "*." of "*.dvi"
    }

    // Now try to append the endings with and without dots, and see if
    // that gives a file.
    for ( QStringList::Iterator it = endings.begin(); it != endings.end(); ++it ) {
      fi.setFile(m_file+(*it));
      if (fi.exists() == true) {
	m_file = m_file+(*it);
	break;
      } else {
	fi.setFile(m_file+"."+(*it));
	if (fi.exists() == true) {
	  m_file = m_file+"."+(*it);
	  break;
	}
      }
    }
    m_url.setPath(QFileInfo(m_file).absFilePath());
  }

  // Set the window caption now, before we do any uncompression and generation of temporary files.
  tmpFileURL.setPath(m_file);
  emit setWindowCaption( tmpFileURL.prettyURL() ); // set Window caption WITHOUT the reference part!
  
  // Check if the file is compressed
  KMimeType::Ptr mimetype = KMimeType::findByPath( m_file );

  if (( mimetype->name() == "application/x-gzip" ) || ( mimetype->name() == "application/x-bzip2" ) ||
      ( mimetype->parentMimeType() == "application/x-gzip" ) || ( mimetype->parentMimeType() == "application/x-bzip2" )) {
    // The file is compressed. Make a temporary file, and store an uncompressed version there...
    if (tmpUnzipped != 0L)  // Delete old temporary file
      delete tmpUnzipped;

    tmpUnzipped = new KTempFile;
    if (tmpUnzipped == 0L) {
      KMessageBox::error( mainWidget, i18n( "<qt><strong>File Error!</strong> Could not create temporary file.</qt>") );
      emit setWindowCaption( QString::null );
      return false;
    }
    tmpUnzipped->setAutoDelete(true);
    if( tmpUnzipped->status() != 0 ) {
      KMessageBox::error( mainWidget, i18n( "<qt><strong>File Error!</strong> Could not create temporary file "
					    "<nobr><strong>%1</strong></nobr>.</qt>").arg( strerror( tmpUnzipped->status() ) ) );
      emit setWindowCaption( QString::null );
      return false;
    }

    QIODevice *filterDev;
    if (( mimetype->parentMimeType() == "application/x-gzip" ) || ( mimetype->parentMimeType() == "application/x-bzip2" )) 
      filterDev = KFilterDev::deviceForFile( m_file, mimetype->parentMimeType() );
    else
      filterDev = KFilterDev::deviceForFile( m_file );
    if (filterDev == 0L) {
      emit setWindowCaption( QString::null );
      return false;
    }
    if( !filterDev->open( IO_ReadOnly ) ) {
      KMessageBox::detailedError( mainWidget, i18n( "<qt><strong>File Error!</strong> Could not open the file "
						    "<nobr><strong>%1</strong></nobr> for uncompression. "
						    "The file will not be loaded.</qt>").arg(m_file),
				  i18n("<qt>This error typically occurs if you do not have enough permissions to read the file. "
				       "You can check ownership and permissions if you right-click on the file in the Konqueror "
				       "file manager and then choose the 'Properties' menu.</qt>"));
      emit setWindowCaption( QString::null );
      delete filterDev;
      return false;
    }
    
    KProgressDialog *prog = new KProgressDialog(0L, "uncompress-progress", 
						i18n("Uncompressing..."), 
						i18n("<qt>Uncompressing the file <nobr><strong>%1</strong></nobr>. Please wait.</qt>").arg(m_file));
    prog->progressBar()->setTotalSteps((int) fi.size()/1024 );
    prog->progressBar()->setProgress(0);
    prog->setMinimumDuration(250);

    QByteArray buf( 1024 );
    int read = 0, wrtn = 0;
    while( ( read = filterDev->readBlock( buf.data(), buf.size() ) ) > 0 ) {
      kapp->processEvents();
      if (prog->wasCancelled()) 
	break;
      prog->progressBar()->advance( 1 );
      
      wrtn = tmpUnzipped->file()->writeBlock( buf.data(), read );
      if( read != wrtn )
	break;
    }
    delete filterDev;
    delete prog;
    
    if (prog->wasCancelled())
      return false;
    if( (read != 0)||(tmpUnzipped->file()->size() == 0) ) {
      KMessageBox::detailedError( mainWidget, i18n( "<qt><strong>File Error!</strong> Could not uncompress the file "
						    "<nobr><strong>%1</strong></nobr>. The file will not be loaded.</qt>").arg( m_file ),
				  i18n("<qt>This error typically occurs if the file is corrupt. "
				       "If you want to be sure, try to decompress the file manually using command-line tools.</qt>"));
      emit setWindowCaption( QString::null );
      return false;
    }
    tmpUnzipped->close();
    
    m_file = tmpUnzipped->name();
  }
  
  
  // Now call the openURL-method of the multipage and give it an URL
  // pointing to the downloaded file.
  tmpFileURL.setPath(m_file);
  tmpFileURL.setRef(m_url.ref()); // Pass the reference part of the URL through to the multipage
  
  documentHasRequestedPaperSize = false;
  bool r = multiPage->openURL(tmpFileURL);
  
  updateScrollBox();
  markList->select(0);
  
  if (r) {
    // If the document did not specify a paper size, re-set the paper
    // size to normal, if necessary
    if (documentHasRequestedPaperSize == false) 
      _paperSize = userRequestedPaperSize;
    
    // start viewing horizontally centered
    QScrollView *sv = multiPage->scrollView();
    if (sv)
      sv->center(sv->contentsWidth()/2, 0);
    
    // Add the file to the watchlist
    watch->addFile( m_file );
  } else {
    m_url = "";
    emit setWindowCaption( "" );
  }
  
  checkActions();
  scrollBox->setMinimumHeight( int(75.0*_paperSize.height_in_mm()/_paperSize.width_in_mm()+0.5));

  return r;
}


void KViewPart::reload(void)
{
  multiPage->reload();
}


void KViewPart::fileChanged(const QString &file)
{
  if (file == m_file && watchAct->isChecked())
    multiPage->reload();
}


bool KViewPart::closeURL()
{
  if( watch && !m_file.isEmpty() )
    watch->removeFile( m_file );

  KParts::ReadOnlyPart::closeURL();
  
  multiPage->closeURL();
  
  m_url = "";
  
  numberOfPages(0);
  checkActions();
  
  emit setWindowCaption("");
  
  return true;
}


void KViewPart::slotMedia(int id)
{
  // If the user has chosen one of the 'known' paper sizes, set the
  // user requested paper size to that value. Via signals and slots,
  // this will update the menus, and also the GUI, if necessary.
  if (id > 1) {
    userRequestedPaperSize.setPageSize(media->currentText());
    return;
  }
  
  // If the user has chosen "Custom paper size..", show the paper size
  // dialog. Construct it, if necessary. The paper size dialog will
  // know the address of userRequestedPaperSize and change this
  // member, if the user clicks ok/accept. The signal/slot mechanism
  // will then make sure that the necessary updates in the GUI are
  // done.
  if (_pageSizeDialog == 0) {
    _pageSizeDialog = new pageSizeDialog(mainWidget, &userRequestedPaperSize);
    if (_pageSizeDialog == 0) {
      kdError() << "Could not construct the page size dialog!" << endl;
      return;
    }
  }
  
  // Reset the "preferred paper size" menu. We don't want to have the
  // "custom paper size" check if the user aborts the dialog.
  checkActions();
  
  // Set or update the paper size dialog to show the currently
  // selected value.
  _pageSizeDialog->setPageSize(_paperSize.serialize());
  _pageSizeDialog->show();
}


void KViewPart::slotUserPreferredSizeChanged(float, float)
{
  checkActions();
  
  // Set the paper size that is currently displayed. The GUI is
  // updated automatically using the signal/slot mechanism.
  if (!documentHasRequestedPaperSize || !useDocumentSpecifiedSize->isChecked()) 
    _paperSize = userRequestedPaperSize;
}


void KViewPart::slotUseDocumentSpecifiedSize(bool doUse)
{
#ifdef DEBUG_KVIEWPART
  kdDebug() << "KViewPart::slotUseDocumentSpecifiedSize() called" << endl;
#endif

  // Set the paper size that is currently displayed. The GUI is
  // updated automatically using the signal/slot mechanism.
  if (doUse && documentHasRequestedPaperSize)
    _paperSize = documentRequestedPaperSize;
  else
    _paperSize = userRequestedPaperSize;
  checkActions();
}


void KViewPart::doRepaintScrollBoxBackground(void)
{
  QPixmap pixmap(scrollBox->width(), scrollBox->height());
  QPainter p(&pixmap);
  
  if (multiPage->preview(&p, scrollBox->width(), scrollBox->height()))
    scrollBox->setBackgroundPixmap(pixmap);
}


void KViewPart::numberOfPages(int nr)
{
  _numberOfPages = nr;

  markList->clear();

  if (nr == 0) {
    // clear scroll box
    scrollBox->setPageSize(QSize(0,0));
    scrollBox->setViewSize(QSize(0,0));
    _currentPage = 0;
    return;
  }

  for (int i=0; i<nr; i++)
    markList->insertItem(QString("%1").arg(i+1), i);

  setPage(0);
}


void KViewPart::pageInfo(int numpages, int currentpage)
{
  _numberOfPages = numpages;

  markList->clear();

  if (numpages == 0) {
    // clear scroll box
    scrollBox->setPageSize(QSize(0,0));
    scrollBox->setViewSize(QSize(0,0));
    emit pageChanged(QString::null);
    _currentPage = 0;
    return;
  }

  for (int i=0; i<numpages; i++)
    markList->insertItem(QString("%1").arg(i+1), i);

  _currentPage = currentpage;
  // ATTN: The string here must be the same as in setPage() below
  QString pageString = i18n("Page %1 of %2").arg(_currentPage+1).arg(_numberOfPages);
  if (pageChangeIsConnected)
    emit pageChanged(pageString);
  else
    emit setStatusBarText(pageString);

  markList->select(currentpage);
  checkActions();
  updateScrollBox();
}


void KViewPart::setPage(int page)
{
  _currentPage = page;

  QString pageString;
  if (_numberOfPages == 0)
    pageString = QString::null;
  else
    // ATTN: The string here must be the same as in pageInfo(int,int) above
    pageString = i18n("Page %1 of %2").arg(_currentPage+1).arg(_numberOfPages);
  if (pageChangeIsConnected)
    emit pageChanged(pageString);
  else
    emit setStatusBarText(pageString);

  if (!multiPage->gotoPage(page))
    return;

  markList->select(page);

  checkActions();
  updateScrollBox();
}


void KViewPart::prevPage()
{
  if (page() > 0)
    setPage(page()-1);
}


void KViewPart::nextPage()
{
  if (page()+1 < pages())
    setPage(page()+1);
}


void KViewPart::firstPage()
{
  setPage(0);
}


void KViewPart::lastPage()
{
  setPage(pages()-1);
}


void KViewPart::goToPage()
{
  _gotoDialog->show();
}


void KViewPart::slotGotoDialog(const QString &page)
{
  bool ok;
  int p = page.toInt(&ok)-1;
  if (ok && p >= 0 && p < pages())
    setPage(p);
}


void KViewPart::pageSelected(int nr)
{
  // Check beforehand if there is really a new page to display. Often
  // an item in the markList is selected AFTER the page has been set,
  // and then we don't want the mulipage to render twice.
  if (_currentPage == nr)
    return;

  if ((nr >= 0) && (nr<pages()))
    setPage(nr);
}


void KViewPart::zoomIn()
{
  float oldVal = _zoomVal.value();
  float newVal = _zoomVal.zoomIn();

  if (oldVal != newVal) {
    _zoomVal.setZoomValue(multiPage->setZoom(_zoomVal.zoomIn()));
    updateScrollBox();
  }
}


void KViewPart::zoomOut()
{
  float oldVal = _zoomVal.value();
  float newVal = _zoomVal.zoomOut();

  if (oldVal != newVal) {
    _zoomVal.setZoomValue(multiPage->setZoom(_zoomVal.zoomOut()));
    updateScrollBox();
  }
}


void KViewPart::fitToPage()
{
  double w, h;
  float _zoom;

  w = multiPage->zoomForWidth(sizeOfPage().width());
  h = multiPage->zoomForHeight(sizeOfPage().height());

  if (w < h)
    _zoom = w;
  else
    _zoom = h;
  if (_zoom < ZoomLimits::MinZoom/1000.0)
    _zoom = ZoomLimits::MinZoom/1000.0;
  if (_zoom > ZoomLimits::MaxZoom/1000.0)
    _zoom = ZoomLimits::MaxZoom/1000.0;
  _zoomVal.setZoomValue(multiPage->setZoom(_zoom));
  updateScrollBox();
}


void KViewPart::fitSize()
{
  _zoomVal.setZoomValue(multiPage->setZoom(1.0));
  updateScrollBox();
}


void KViewPart::fitToHeight()
{
  // See below, in the documentation of the method "fitToWidth" for an
  // explanation of the complicated calculation we are doing here.
  int targetHeight = multiPage->scrollView()->viewportSize(0,0).height()-1;
  int targetWidth  = (int)(targetHeight * _paperSize.width_in_mm()/_paperSize.height_in_mm() +0.5);
  targetHeight = multiPage->scrollView()->viewportSize(targetWidth, targetHeight).height()-1;

  float _zoom = multiPage->zoomForHeight(targetHeight);
  if (_zoom < ZoomLimits::MinZoom/1000.0)
    _zoom = ZoomLimits::MinZoom/1000.0;
  if (_zoom > ZoomLimits::MaxZoom/1000.0)
    _zoom = ZoomLimits::MaxZoom/1000.0;
  _zoomVal.setZoomValue(multiPage->setZoom(_zoom));
  updateScrollBox();
}


void KViewPart::fitToWidth()
{
  // 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.

  // Caluculate the width and height of the view, disregarding the
  // possible complications with scrollbars, e.g. assuming the maximal
  // space is available.
  int targetWidth  = multiPage->scrollView()->viewportSize(0,0).width()-1;
  int targetHeight = (int)(targetWidth * _paperSize.height_in_mm()/_paperSize.width_in_mm() +0.5);
  // 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).
  targetWidth      = multiPage->scrollView()->viewportSize(targetWidth, targetHeight).width()-1;

  float _zoom = multiPage->zoomForWidth(targetWidth);
  if (_zoom < ZoomLimits::MinZoom/1000.0)
    _zoom = ZoomLimits::MinZoom/1000.0;
  if (_zoom > ZoomLimits::MaxZoom/1000.0)
    _zoom = ZoomLimits::MaxZoom/1000.0;
  _zoomVal.setZoomValue(multiPage->setZoom(_zoom));
  updateScrollBox();
}


void KViewPart::setZoomValue(const QString &sval)
{
  float fval = _zoomVal.value();
  _zoomVal.setZoomValue(sval);
  if (fval != _zoomVal.value())
    _zoomVal.setZoomValue(multiPage->setZoom(_zoomVal.value()));
  updateScrollBox();
}


QSize KViewPart::sizeOfPage()
{
  QRect r = multiPage->widget()->childrenRect();
  return QSize(r.width(), r.height());
}


void KViewPart::updateScrollBox()
{
  QScrollView *sv = multiPage->scrollView();
  scrollBox->setPageSize(QSize(sv->contentsWidth(), sv->contentsHeight()));
  scrollBox->setViewSize(QSize(sv->visibleWidth(), sv->visibleHeight()));
  scrollBox->setViewPos(QPoint(sv->contentsX(), sv->contentsY()));
  emit zoomChanged(QString("%1%").arg((int)(_zoomVal.value()*100.0+0.5)));
}


void KViewPart::checkActions()
{
  bool doc = !url().isEmpty();

  backAct->setEnabled(doc && page() > 0);
  forwardAct->setEnabled(doc && page()+1 < pages());
  startAct->setEnabled(doc && page() > 0);
  endAct->setEnabled(doc && page()+1 < pages());
  gotoAct->setEnabled(doc && pages()>1);
  readDownAct->setEnabled(doc);

  zoomInAct->setEnabled(doc);
  zoomOutAct->setEnabled(doc);

  fitAct->setEnabled(doc);
  fitPageAct->setEnabled(doc);
  fitHeightAct->setEnabled(doc);
  fitWidthAct->setEnabled(doc);

  media->setEnabled(doc);
  orientation->setEnabled(doc);

  printAction->setEnabled(doc);
  if (multiPage->isReadWrite())
    saveAction->setEnabled(multiPage->isModified());
  saveAsAction->setEnabled(doc);

  if (documentHasRequestedPaperSize && useDocumentSpecifiedSize->isChecked()) {
    media->setEnabled(false);
    orientation->setEnabled(false);
  } else {
    if (userRequestedPaperSize.formatNumber() != -1) {
      orientation->setCurrentItem(userRequestedPaperSize.getOrientation());
      orientation->setEnabled(true);
      media->setCurrentItem(userRequestedPaperSize.formatNumber()+1);
    } else {
      orientation->setEnabled(false);
      media->setCurrentItem(userRequestedPaperSize.formatNumber()-1);
    }
  }
}


void KViewPart::contentsMoving(int x, int y)
{
  QScrollView *sv = multiPage->scrollView();
  scrollBox->setPageSize(QSize(sv->contentsWidth(), sv->contentsHeight()));
  scrollBox->setViewSize(QSize(sv->visibleWidth(), sv->visibleHeight()));
  scrollBox->setViewPos(QPoint(x,y));
}


void KViewPart::scrollBoxChanged(QPoint np)
{
  multiPage->scrollView()->setContentsPos(np.x(), np.y());
}


bool KViewPart::eventFilter(QObject *obj, QEvent *ev)
{
  if (obj == this && ev->type() == QEvent::Resize)
    QTimer::singleShot(0, this, SLOT(updateScrollBox()));

  if (obj != this) {
    if (ev->type() == QEvent::MouseButtonPress) {
      mousePos = ((QMouseEvent*)ev)->globalPos();
      multiPage->scrollView()->setCursor(Qt::sizeAllCursor);
    }

    if (ev->type() == QEvent::MouseMove) {
      QPoint newPos = ((QMouseEvent*)ev)->globalPos();
      if ( ((QMouseEvent*)ev)->state() == LeftButton ) {
	QPoint delta = mousePos - newPos;
	multiPage->scrollView()->scrollBy(delta.x(), delta.y());
      }
      mousePos = newPos;
    }

    if (ev->type() == QEvent::MouseButtonRelease)
      multiPage->scrollView()->setCursor(Qt::arrowCursor);
  }

  return false;
}


void KViewPart::wheelEvent(QWheelEvent *e)
{
  QScrollBar *sb = multiPage->scrollView()->verticalScrollBar();
  if (sb == 0)
    return;
  
  Q_INT32 pxl = -(e->delta()*sb->lineStep())/60;
  if (pxl == 0) 
    if (e->delta() > 0)
      pxl = -1;
    else
      pxl = 1;
  scroll(pxl);
}

void KViewPart::scrollUp()
{
  QScrollBar *sb = multiPage->scrollView()->verticalScrollBar();
  if (sb == 0)
    return;

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


void KViewPart::scrollDown()
{
  QScrollBar *sb = multiPage->scrollView()->verticalScrollBar();
  if (sb == 0)
    return;

  scroll(sb->lineStep());
}


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

  if (deltaInPixel < 0) {
    if (sb->value() == sb->minValue()) {
      if ( (page() == 0) || (changePageDelayTimer.isActive()) )
	return;
      
      changePageDelayTimer.stop();
      prevPage();
      QScrollView *sv = multiPage->scrollView();
      scrollTo(sv->contentsX(), sb->maxValue());
      return;
    }
  }
  
  if (deltaInPixel > 0) {
    if (sb->value() == sb->maxValue()) {
      if ( (page()+1 == pages()) || (changePageDelayTimer.isActive()) )
	return;
      
      changePageDelayTimer.stop();
      nextPage();
      QScrollView *sv = multiPage->scrollView();
      scrollTo(sv->contentsX(), 0);
      return;
    }
  }
  
  sb->setValue(sb->value() + deltaInPixel);

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


void KViewPart::scrollLeft()
{
  QScrollBar *sb = multiPage->scrollView()->horizontalScrollBar();
  if (sb)
    sb->subtractLine();
  updateScrollBox();
}


void KViewPart::scrollRight()
{
  QScrollBar *sb = multiPage->scrollView()->horizontalScrollBar();
  if (sb)
    sb->addLine();
  updateScrollBox();
}


void KViewPart::scrollUpPage()
{
  QScrollBar *sb = multiPage->scrollView()->verticalScrollBar();
  if (sb)
    sb->subtractPage();
  updateScrollBox();
}


void KViewPart::scrollDownPage()
{
  QScrollBar *sb = multiPage->scrollView()->verticalScrollBar();
  if (sb)
    sb->addPage();
  updateScrollBox();
}


void KViewPart::scrollLeftPage()
{
  QScrollBar *sb = multiPage->scrollView()->horizontalScrollBar();
  if (sb)
    sb->subtractPage();
  updateScrollBox();
}


void KViewPart::scrollRightPage()
{
  QScrollBar *sb = multiPage->scrollView()->horizontalScrollBar();
  if (sb)
    sb->addPage();
  updateScrollBox();
}


void KViewPart::scrollTo(int x, int y)
{
  QScrollView *sv = multiPage->scrollView();
  sv->setContentsPos(x, y);
  updateScrollBox();
}


void KViewPart::updatePreview(bool avail)
{
  if (showPreview->isChecked() && showmarklist->isChecked() && avail)
    // Rather than calling the method doRepaintScrollBoxBackground()
    // here (which turns to the multipage to have a pixmap for the
    // preview drawn), we use a timer here whose timeout() signal is
    // connected to doRepaintScrollBoxBackground(). Giving the
    // interval is 0ms will, according to the manual of QT, make the
    // timer time out only after all the events in the window system's
    // event queue have been processed.

    // The reason: shrinking the pixmap to create the preview often
    // takes a while. Doing that after all other events are processed
    // makes sure that the user interface is not blocked. That is
    // particularly important when the user uses repeat keystrokes to
    // read through the document.
    previewPaintInitiator.start(0, true);
  else
    scrollBox->setBackgroundMode(QFrame::PaletteMid);
}


void KViewPart::slotPreview()
{
  updatePreview(true);
}


void KViewPart::slotPrint()
{
  QStringList pages;
  QValueList<int> selectedPageNo = markList->markList();

  QValueList<int>::iterator it;
  for ( it = selectedPageNo.begin(); it != selectedPageNo.end(); ++it )
    pages.append(QString::number((*it)-1));

  multiPage->print(pages, page());
}


void KViewPart::readSettings()
{
  KConfig *config = instance()->config();

  config->setGroup("GUI");

  showmarklist->setChecked(config->readBoolEntry("PageMarks", true));
  slotShowMarkList();

  watchAct->setChecked(config->readBoolEntry("WatchFile", true));
  showPreview->setChecked(config->readBoolEntry("ShowPreview", false));
  float _zoom = config->readDoubleNumEntry("Zoom", 1.0);
  if ( (_zoom < ZoomLimits::MinZoom/1000.0) || (_zoom > ZoomLimits::MaxZoom/1000.0))
    _zoom = 1.0;
  _zoomVal.setZoomValue(multiPage->setZoom(_zoom));

  // Read Paper Size. and orientation. The GUI is updated
  // automatically by the signals/slots mechanism whenever
  // userRequestedPaperSize is changed.
  userRequestedPaperSize.setPageSize(config->readEntry("PaperFormat", QString::null));

  // Check if scrollbars should be shown
  bool sbstatus = config->readBoolEntry("Scrollbars", true);
  scrollbarHandling->setChecked(sbstatus);
  emit scrollbarStatusChanged(sbstatus);

  // Check if document specified paper sizes should be shown. We do
  // not need to take any action here, because this method is called
  // only in the constructor of the KViewPart when no document is
  // loaded
  useDocumentSpecifiedSize->setChecked(config->readBoolEntry("useDocumentSpecifiedSize", true));
}


void KViewPart::writeSettings()
{
  KConfig *config = instance()->config();

  config->setGroup("GUI");

  config->writeEntry("PageMarks", showmarklist->isChecked());
  config->writeEntry("WatchFile", watchAct->isChecked());
  config->writeEntry("ShowPreview", showPreview->isChecked());
  config->writeEntry("Zoom", _zoomVal.value());
  config->writeEntry("PaperFormat", userRequestedPaperSize.serialize() );
  config->writeEntry("Scrollbars", scrollbarHandling->isChecked() );

  config->sync();
}


void KViewPart::readDown()
{
  QScrollBar *sb = multiPage->scrollView()->verticalScrollBar();
  if (sb) {
    if (sb->value() == sb->maxValue()) {
      if (page()+1 == pages())
	return;

      nextPage();
      QScrollView *sv = multiPage->scrollView();
      scrollTo(sv->contentsX(), 0);
    } else
      sb->addPage();
  }
}


void KViewPart::readUp()
{
  QScrollBar *sb = multiPage->scrollView()->verticalScrollBar();
  if (sb) {
    if (sb->value() == sb->minValue()) {
      if (page() == 0)
	return;

      prevPage();
      QScrollView *sv = multiPage->scrollView();
      scrollTo(sv->contentsX(), sb->maxValue());
    } else
      sb->subtractPage();
  }
}


void KViewPart::connectNotify ( const char *sig )
{
  if (QString(sig).contains("pageChanged"))
    pageChangeIsConnected = true;
}


void KViewPart::setStatusBarTextFromMultiPage( const QString &msg )
{
  if (msg.isEmpty()) {
    if (pageChangeIsConnected)
      emit setStatusBarText(QString::null);
    else
      emit setStatusBarText(i18n("Page %1 of %2").arg(_currentPage+1).arg(_numberOfPages));
  }
  else
    emit setStatusBarText(msg);
}

void KViewPart::partActivateEvent( KParts::PartActivateEvent *ev )
{
    QApplication::sendEvent( multiPage, ev );
}

void KViewPart::guiActivateEvent( KParts::GUIActivateEvent *ev )
{
    QApplication::sendEvent( multiPage, ev );
}


KViewPartExtension::KViewPartExtension(KViewPart *parent)
  : KParts::BrowserExtension( parent, "KViewPartExtension")
{
}
