summaryrefslogtreecommitdiffstats
path: root/kviewshell/kmultipage.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'kviewshell/kmultipage.cpp')
-rw-r--r--kviewshell/kmultipage.cpp1976
1 files changed, 1976 insertions, 0 deletions
diff --git a/kviewshell/kmultipage.cpp b/kviewshell/kmultipage.cpp
new file mode 100644
index 00000000..b747ce0f
--- /dev/null
+++ b/kviewshell/kmultipage.cpp
@@ -0,0 +1,1976 @@
+#include <config.h>
+
+#include <kaction.h>
+#include <kapplication.h>
+#include <kdebug.h>
+#include <kfiledialog.h>
+#include <kiconloader.h>
+#include <kio/job.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kprinter.h>
+#include <kstdaction.h>
+#include <qobject.h>
+#include <qlayout.h>
+#include <qpaintdevicemetrics.h>
+#include <qprogressdialog.h>
+#include <qsplitter.h>
+#include <qurl.h>
+#include <qtoolbox.h>
+#include <qvbox.h>
+
+#include "documentWidget.h"
+#include "marklist.h"
+#include "tableOfContents.h"
+#include "kprintDialogPage_pageoptions.h"
+#include "kvsprefs.h"
+#include "kmultipage.h"
+#include "pageNumber.h"
+#include "renderedDocumentPagePrinter.h"
+#include "searchWidget.h"
+#include "textBox.h"
+#include "zoomlimits.h"
+
+
+//#define DEBUG_KMULTIPAGE
+
+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");
+
+ parentWdg = parentWidget;
+ lastCurrentPage = 0;
+ timer_id = -1;
+ searchInProgress = false;
+
+ QVBox* verticalBox = new QVBox(parentWidget);
+ verticalBox->setFocusPolicy(QWidget::StrongFocus);
+ setWidget(verticalBox);
+
+ splitterWidget = new QSplitter(verticalBox, widgetName);
+ splitterWidget->setOpaqueResize(false);
+ splitterWidget->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
+
+ // Create SideBar
+ sideBar = new QToolBox(splitterWidget, "sidebar");
+
+ // Create ContentsList
+ tableOfContents = new TableOfContents(sideBar);
+ sideBar->addItem(tableOfContents, QIconSet(SmallIcon("contents")), i18n("Contents"));
+
+ connect(tableOfContents, SIGNAL(gotoPage(const Anchor&)), this, SLOT(gotoPage(const Anchor&)));
+
+ // Create MarkList
+ _markList = new MarkList(sideBar, "marklist");
+ sideBar->addItem(_markList, QIconSet(SmallIcon("thumbnail")), i18n("Thumbnails"));
+
+ // Restore state of the sidebar
+ sideBar->setCurrentItem(sideBar->item(KVSPrefs::sideBarItem()));
+
+ splitterWidget->setResizeMode(sideBar, QSplitter::KeepSize);
+
+ connect(_markList, SIGNAL(selected(const PageNumber&)), this, SLOT(gotoPage(const PageNumber&)));
+
+ _scrollView = new PageView(splitterWidget, widgetName);
+
+ // Create Search Panel
+ searchWidget = new SearchWidget(verticalBox);
+ searchWidget->hide();
+ connect(searchWidget, SIGNAL(findNextText()), this, SLOT(findNextText()));
+ connect(searchWidget, SIGNAL(findPrevText()), this, SLOT(findPrevText()));
+
+ sideBar->setMinimumWidth(80);
+ sideBar->setMaximumWidth(300);
+
+ connect(_scrollView, SIGNAL(currentPageChanged(const PageNumber&)), this, SLOT(setCurrentPageNumber(const PageNumber&)));
+ connect(_scrollView, SIGNAL(viewSizeChanged(const QSize&)), scrollView(), SLOT(calculateCurrentPageNumber()));
+ connect(_scrollView, SIGNAL(wheelEventReceived(QWheelEvent *)), this, SLOT(wheelEvent(QWheelEvent*)));
+
+ connect(this, SIGNAL(enableMoveTool(bool)), _scrollView, SLOT(slotEnableMoveTool(bool)));
+
+ splitterWidget->setCollapsible(sideBar, false);
+ splitterWidget->setSizes(KVSPrefs::guiLayout());
+
+ connect(searchWidget, SIGNAL(searchEnabled(bool)), this, SIGNAL(searchEnabled(bool)));
+ connect(searchWidget, SIGNAL(stopSearch()), this, SLOT(stopSearch()));
+}
+
+
+KMultiPage::~KMultiPage()
+{
+ writeSettings();
+
+ if (timer_id != -1)
+ killTimer(timer_id);
+
+ delete pageCache;
+}
+
+void KMultiPage::readSettings()
+{
+}
+
+void KMultiPage::writeSettings()
+{
+ // Save TOC layout
+ tableOfContents->writeSettings();
+
+ KVSPrefs::setGuiLayout(splitterWidget->sizes());
+ // Save state of the sidebar
+ KVSPrefs::setSideBarItem(sideBar->indexOf(sideBar->currentItem()));
+ KVSPrefs::writeConfig();
+}
+
+QString KMultiPage::name_of_current_file()
+{
+ return m_file;
+}
+
+bool KMultiPage::is_file_loaded(const 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::warningContinueCancel (0, i18n("The file %1\nexists. Shall I overwrite that file?").arg(fileName),
+ i18n("Overwrite File"), i18n("Overwrite"));
+ if (r == KMessageBox::Cancel)
+ 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;
+}
+
+
+void KMultiPage::setFile(bool)
+{
+ return;
+}
+
+
+bool KMultiPage::closeURL()
+{
+#ifdef DEBUG_KMULTIPAGE
+ kdDebug(1233) << "KMultiPage::closeURL()" << endl;
+#endif
+
+ if (renderer.isNull())
+ return false;
+
+ // Clear navigation history.
+ document_history.clear();
+
+ // Close the file.
+ renderer->setFile(QString::null, KURL());
+ renderer->clear();
+
+ // Delete Page Widgets.
+ widgetList.setAutoDelete(true);
+ widgetList.resize(0);
+ widgetList.setAutoDelete(false);
+
+ // Update ScrollView.
+ scrollView()->layoutPages();
+ enableActions(false);
+
+ // Clear Thumbnail List.
+ markList()->clear();
+
+ // Clear Table of Contents
+ tableOfContents->clear();
+
+ // Clear Status Bar
+ emit setStatusBarText(QString::null);
+
+ 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)
+ sideBar->show();
+ else
+ sideBar->hide();
+}
+
+void KMultiPage::slotShowThumbnails(bool show)
+{
+ markList()->slotShowThumbnails(show);
+}
+
+void KMultiPage::slotSetFullPage(bool fullpage)
+{
+ _scrollView->setFullScreenMode(fullpage);
+ if (fullpage)
+ slotShowSidebar(false);
+}
+
+void KMultiPage::preferencesChanged()
+{
+ // We need to read the config options otherwise the KVSPrefs-object would
+ // not be syncronized between the kviewpart and the kmultipage.
+ KVSPrefs::self()->readConfig();
+
+ slotShowThumbnails(KVSPrefs::showThumbnails());
+
+ // if we are in overviewmode and the number of columns or rows has changed
+ if (scrollView()->overviewMode() &&
+ (scrollView()->getNrColumns() != KVSPrefs::overviewModeColumns() ||
+ scrollView()->getNrRows() != KVSPrefs::overviewModeRows()))
+ {
+ setViewMode(KVSPrefs::EnumViewMode::Overview);
+ }
+
+ if (KVSPrefs::changeColors() && KVSPrefs::renderMode() == KVSPrefs::EnumRenderMode::Paper)
+ renderer->setAccessibleBackground(true, KVSPrefs::paperColor());
+ else
+ renderer->setAccessibleBackground(false);
+
+ renderModeChanged();
+}
+
+void KMultiPage::setViewMode(int mode)
+{
+#ifdef DEBUG_KMULTIPAGE
+ kdDebug(1233) << "KMultiPage::setViewMode(" << mode << ")" << endl;
+#endif
+ // 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 KVSPrefs::EnumViewMode::SinglePage:
+ KVSPrefs::setViewMode(KVSPrefs::EnumViewMode::SinglePage);
+
+ // Don't do anything if the view mode is already set
+ if ((scrollView()->getNrColumns() == 1) && (scrollView()->getNrRows() == 1) && (scrollView()->isContinuous() == false))
+ return;
+
+ scrollView()->setNrColumns(1);
+ scrollView()->setNrRows(1);
+ scrollView()->setContinuousViewMode(false);
+ // We scroll the view to the top, so that top and not the bottom
+ // of the visible page is shown.
+ scrollView()->scrollTop();
+ break;
+ case KVSPrefs::EnumViewMode::ContinuousFacing:
+ KVSPrefs::setViewMode(KVSPrefs::EnumViewMode::ContinuousFacing);
+
+ // Don't do anything if the view mode is already set
+ if ((scrollView()->getNrColumns() == 2) && (scrollView()->getNrRows() == 1) && (scrollView()->isContinuous() == true))
+ return;
+
+ scrollView()->setNrColumns(2);
+ scrollView()->setNrRows(1);
+ scrollView()->setContinuousViewMode(true);
+ break;
+ case KVSPrefs::EnumViewMode::Overview:
+ KVSPrefs::setViewMode(KVSPrefs::EnumViewMode::Overview);
+
+ // Don't do anything if the view mode is already set
+ if ((scrollView()->getNrColumns() == KVSPrefs::overviewModeColumns()) && (scrollView()->getNrRows() == KVSPrefs::overviewModeRows()) && (scrollView()->isContinuous() == false))
+ return;
+
+ scrollView()->setNrColumns(KVSPrefs::overviewModeColumns());
+ scrollView()->setNrRows(KVSPrefs::overviewModeRows());
+ scrollView()->setContinuousViewMode(false);
+ // We scroll the view to the top, so that top and not the bottom
+ // of the visible tableau is shown.
+ scrollView()->scrollTop();
+ break;
+ default: //KVSPrefs::EnumViewMode::Continuous
+ KVSPrefs::setViewMode(KVSPrefs::EnumViewMode::Continuous);
+
+ // Don't do anything if the view mode is already set
+ if ((scrollView()->getNrColumns() == 1) && (scrollView()->getNrRows() == 1) && (scrollView()->isContinuous() == true))
+ return;
+
+ scrollView()->setNrColumns(1);
+ scrollView()->setNrRows(1);
+ scrollView()->setContinuousViewMode(true);
+ }
+ generateDocumentWidgets(currentPage);
+ KVSPrefs::writeConfig();
+ emit viewModeChanged();
+}
+
+void KMultiPage::initializePageCache()
+{
+ pageCache = new DocumentPageCache();
+}
+
+DocumentWidget* KMultiPage::createDocumentWidget()
+{
+ DocumentWidget* documentWidget = new DocumentWidget(scrollView()->viewport(), scrollView(), pageCache, "singlePageWidget");
+ connect(documentWidget, SIGNAL(clearSelection()), this, SLOT(clearSelection()));
+ connect(this, SIGNAL(enableMoveTool(bool)), documentWidget, SLOT(slotEnableMoveTool(bool)));
+ return documentWidget;
+}
+
+
+void KMultiPage::generateDocumentWidgets(const PageNumber& _startPage)
+{
+ PageNumber startPage = _startPage;
+#ifdef DEBUG_KMULTIPAGE
+ kdDebug(1233) << "KMultiPage::generateDocumentWidgets(" << startPage << ")" << endl;
+#endif
+
+ // Do nothing if no document is loaded.
+ if (getRenderer().isNull() || getRenderer()->isEmpty())
+ return;
+
+ // This function is only called with an invalid pagenumber, when
+ // the file has been loaded or reloaded.
+ bool reload = !startPage.isValid();
+
+ 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 (numberOfPages() == 0)
+ widgetList.resize(0);
+ else
+ {
+ switch (KVSPrefs::viewMode())
+ {
+ case KVSPrefs::EnumViewMode::SinglePage:
+ widgetList.resize(1);
+ break;
+ case KVSPrefs::EnumViewMode::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 = 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() == KVSPrefs::EnumViewMode::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 = 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 = widgetList[i];
+ if (documentWidget != 0) // Paranoia safety check
+ {
+ if (KVSPrefs::viewMode() == KVSPrefs::EnumViewMode::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);
+}
+
+
+bool KMultiPage::gotoPage(const PageNumber& page)
+{
+ return gotoPage(page, 0, true);
+}
+
+
+bool KMultiPage::gotoPage(const PageNumber& page, int y, bool isLink)
+{
+#ifdef DEBUG_KMULTIPAGE
+ kdDebug(1233) << "KMultiPage::gotoPage()" << endl;
+#endif
+
+ if (widgetList.size() == 0) {
+ kdError(4300) << "KMultiPage::gotoPage(" << page << ", y) called, but widgetList is empty" << endl;
+ return false;
+ }
+
+ if (!page.isValid())
+ {
+ kdDebug(1223) << "KMultiPage::gotoPage(" << page << ") invalid pageNumber." << endl;
+ return false;
+ }
+
+ if (isLink)
+ document_history.add(page, y);
+
+ DocumentWidget* pageWidget;
+
+ // If we are in overview viewmode
+ if (KVSPrefs::viewMode() == KVSPrefs::EnumViewMode::Overview)
+ {
+ unsigned int visiblePages = KVSPrefs::overviewModeColumns() * KVSPrefs::overviewModeRows();
+ // Pagenumber of the first visibile 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 + 1 - (page % 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 + 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++)
+ {
+ pageWidget = (DocumentWidget*)(widgetList[i]);
+ if (pageWidget != 0)
+ pageWidget->setPageNumber(tableauStartPage + i);
+ }
+ scrollView()->layoutPages();
+ }
+ }
+ // move scrollview to "page".
+ // Make the widget pageWidget visible in the scrollview. Somehow this
+ // doesn't seem to trigger the signal contentsMoved in the
+ // QScrollview, so that we better call setCurrentPage() ourselves.
+ pageWidget = (DocumentWidget*)(widgetList[page % visiblePages]);
+
+ scrollView()->moveViewportToWidget(pageWidget, y);
+
+ // Set current page number.
+ setCurrentPageNumber(page);
+
+ return true;
+ }
+ 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'
+ pageWidget = (DocumentWidget*)(widgetList[0]);
+
+ // Paranoia security check
+ if (pageWidget == 0) {
+ kdError(4300) << "KMultiPage::goto_Page() called with widgetList.size() == 1, but widgetList[0] == 0" << endl;
+ return false;
+ }
+
+ if (pageCache->sizeOfPageInPixel(currentPageNumber()) == pageCache->sizeOfPageInPixel(page))
+ {
+ // We are rendering the page before we switch the widget to the new page.
+ // To make a smooth transition. We only do this if the size of the current and new page are equal,
+ // otherwise we would have to render the page twice, if autozoom is enabled.
+ pageCache->getPage(page);
+ }
+
+ pageWidget->setPageNumber(page);
+ scrollView()->layoutPages();
+ scrollView()->moveViewportToWidget(pageWidget, 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 false;
+ }
+ pageWidget = (DocumentWidget*)(widgetList[page-1]);
+ if (pageWidget == 0) {
+ kdError(4300) << "KMultiPage::goto_Page() called with widgetList.size() > 1, but widgetList[page] == 0" << endl;
+ return false;
+ }
+
+ scrollView()->moveViewportToWidget(pageWidget, y);
+ }
+
+ if (isLink && y != 0)
+ pageWidget->flash(y);
+
+ // Set current page number.
+ setCurrentPageNumber(page);
+ return true;
+}
+
+
+void KMultiPage::handleLocalLink(const QString &linkText)
+{
+#ifdef DEBUG_SPECIAL
+ kdDebug(4300) << "hit: local link to " << linkText << endl;
+#endif
+
+ if (renderer.isNull()) {
+ 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())
+ gotoPage(anch);
+ 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::setCurrentPageNumber(const PageNumber& page)
+{
+#ifdef DEBUG_KMULTIPAGE
+ kdDebug(1233) << "KMultiPage::setCurrentPageNumber()" << endl;
+#endif
+
+ if (page != currentPageNumber())
+ {
+ markList()->setCurrentPageNumber(page);
+ emit pageInfo(numberOfPages(), currentPageNumber());
+ }
+}
+
+PageNumber KMultiPage::currentPageNumber()
+{
+ return markList()->currentPageNumber();
+}
+
+void KMultiPage::doGoBack()
+{
+ HistoryItem *it = document_history.back();
+ if (it != 0)
+ gotoPage(it->page, it->ypos, false); // Do not add a history item.
+ else
+ kdDebug(4300) << "Faulty return -- bad history buffer" << endl;
+ return;
+}
+
+
+void KMultiPage::doGoForward()
+{
+ HistoryItem *it = document_history.forward();
+ if (it != 0)
+ gotoPage(it->page, it->ypos, false); // Do not add a history item.
+ else
+ kdDebug(4300) << "Faulty return -- bad history buffer" << endl;
+ return;
+}
+
+
+void KMultiPage::renderModeChanged()
+{
+ pageCache->clear();
+
+ generateDocumentWidgets();
+ scrollView()->layoutPages();
+
+ for (Q_UINT16 i=0; i < widgetList.size(); i++)
+ {
+ DocumentWidget* documentWidget = widgetList[i];
+ if (documentWidget == 0)
+ continue;
+
+ documentWidget->update();
+ }
+
+ markList()->repaintThumbnails();
+}
+
+
+void KMultiPage::repaintAllVisibleWidgets()
+{
+#ifdef DEBUG_KMULTIPAGE
+ kdDebug(1233) << "KMultiPage::repaintAllVisibleWidgets()" << endl;
+#endif
+
+ 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 = widgetList[i];
+ if (documentWidget == 0)
+ continue;
+
+ // Resize, if necessary
+ QSize pageSize = pageCache->sizeOfPageInPixel(documentWidget->getPageNumber());
+ 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 (everResized == true)
+ scrollView()->layoutPages(true);
+}
+
+
+double KMultiPage::setZoom(double zoom)
+{
+#ifdef DEBUG_KMULTIPAGE
+ kdDebug(1233) << "KMultiPage::setZoom(" << zoom << ")" << endl;
+#endif
+
+ 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::print()
+{
+ // Paranoid safety checks
+ if (renderer.isNull())
+ return;
+ if (renderer->isEmpty())
+ return;
+
+ // Allocate the printer structure
+ KPrinter *printer = getPrinter();
+ if (printer == 0)
+ return;
+
+ // initialize the printer using the print dialog
+ if ( printer->setup(parentWdg, i18n("Print %1").arg(m_file.section('/', -1))) ) {
+ // Now do the printing.
+ QValueList<int> pageList = printer->pageList();
+ if (pageList.isEmpty())
+ printer->abort();
+ else {
+ printer->setCreator("kviewshell");
+ printer->setDocName(m_file);
+ RenderedDocumentPagePrinter rdpp(printer);
+
+ // Obtain papersize information that is required to perform
+ // the resizing and centering, if this is wanted by the user.
+ Length paperWidth, paperHeight;
+ QPaintDeviceMetrics pdm(printer);
+ paperWidth.setLength_in_mm(pdm.widthMM());
+ paperHeight.setLength_in_mm(pdm.heightMM());
+
+ QValueList<int>::ConstIterator it = pageList.begin();
+ while (true) {
+ SimplePageSize paper_s(paperWidth, paperHeight);
+
+ // Printing usually takes a while. This is to keep the GUI
+ // updated.
+ qApp->processEvents();
+
+ QPainter *paint = rdpp.getPainter();
+ if (paint != 0) {
+ // Before drawing the page, we figure out the zoom-value,
+ // taking the "page sizes and placement" options from the
+ // printer dialog into account
+ double factual_zoom = 1.0;
+
+ // Obtain pagesize information that is required to perform the
+ // resizing and centering, if this is wanted by the user.
+ SimplePageSize page_s = sizeOfPage(*it);
+
+ paint->save();
+
+ // Rotate the page, if appropriate. By default, page
+ // rotation is enabled. This is also hardcoded into
+ // KPrintDialogPage_PageOptions.cpp
+ if ((page_s.isPortrait() != paper_s.isPortrait()) && (printer->option( "kde-kviewshell-rotatepage" ) != "false")) {
+ paint->rotate(-90);
+ paint->translate(-printer->resolution()*paperHeight.getLength_in_inch(), 0.0);
+ paper_s = paper_s.rotate90();
+ }
+
+ double suggested_zoom = page_s.zoomToFitInto(paper_s);
+
+ // By default, "shrink page" and "expand page" are off. This
+ // is also hardcoded into KPrintDialogPage_PageOptions.cpp
+ if ((suggested_zoom < 1.0) && (printer->option( "kde-kviewshell-shrinkpage" ) == "true"))
+ factual_zoom = suggested_zoom;
+ if ((suggested_zoom > 1.0) && (printer->option( "kde-kviewshell-expandpage" ) == "true"))
+ factual_zoom = suggested_zoom;
+
+ Length delX, delY;
+ // By default, "center page" is on. This is also hardcoded
+ // into KPrintDialogPage_PageOptions.cpp
+ if (printer->option( "kde-kviewshell-centerpage" ) != "false") {
+ delX = (paper_s.width() - page_s.width()*factual_zoom)/2.0;
+ delY = (paper_s.height() - page_s.height()*factual_zoom)/2.0;
+ }
+
+ // Now draw the page.
+ rdpp.setPageNumber(*it);
+
+ double resolution = factual_zoom*printer->resolution();
+
+ paint->translate(resolution*delX.getLength_in_inch(), resolution*delY.getLength_in_inch());
+ renderer->drawPage(resolution, &rdpp);
+ paint->restore();
+ }
+ ++it;
+ if ((it == pageList.end()) || (printer->aborted() == true))
+ break;
+
+ printer->newPage();
+ }
+ // At this point the rdpp is destructed. The last page is then
+ // printed.
+ }
+ }
+ delete printer;
+}
+
+
+void KMultiPage::setRenderer(DocumentRenderer* _renderer)
+{
+ renderer = _renderer;
+
+ // Initialize documentPageCache.
+ initializePageCache();
+ 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(renderModeChanged()));
+ connect(pageCache, SIGNAL(textSelected(bool)), this, SIGNAL(textSelected(bool)));
+ connect(renderer, SIGNAL(documentIsChanged()), this, SLOT(renderModeChanged()));
+ connect(this, SIGNAL(zoomChanged()), this, SLOT(repaintAllVisibleWidgets()));
+}
+
+
+void KMultiPage::updateWidgetSize(const PageNumber& pageNumber)
+{
+ for(Q_UINT16 i=0; i<widgetList.size(); i++)
+ {
+ DocumentWidget* documentWidget = widgetList[i];
+ if (documentWidget == 0)
+ continue;
+
+ if (documentWidget->getPageNumber() == pageNumber)
+ {
+ // Resize, if necessary
+ QSize pageSize = pageCache->sizeOfPageInPixel(documentWidget->getPageNumber());
+ if (pageSize != documentWidget->pageSize())
+ {
+ documentWidget->setPageSize(pageSize);
+ scrollView()->layoutPages();
+ }
+ // We have just one widget per page.
+ break;
+ }
+ }
+
+ // Update marklist
+ markList()->updateWidgetSize(pageNumber);
+}
+
+
+PageNumber KMultiPage::widestPage() const
+{
+ Length maxWidth;
+ PageNumber pageNumber = 1;
+
+ for (int i = 1; i <= numberOfPages(); i++)
+ {
+ Length width = pageCache->sizeOfPage(i).width();
+
+ if (width > maxWidth)
+ {
+ maxWidth = width;
+ pageNumber = i;
+ }
+ }
+
+ return pageNumber;
+}
+
+double KMultiPage::zoomForWidthColumns(unsigned int viewportWidth) const
+{
+ Length maxLeftColumnWidth;
+ Length maxRightColumnWidth;
+ Length maxWidth;
+
+ PageNumber widestPageLeft;
+ PageNumber widestPageRight;
+
+ for (int i = 1; i <= numberOfPages(); i++)
+ {
+ Length width = pageCache->sizeOfPage(i).width();
+
+ if ( i % 2 == 0) // page is in left column
+ {
+ if (width > maxLeftColumnWidth)
+ {
+ maxLeftColumnWidth = width;
+ widestPageLeft = i;
+ }
+ }
+
+ if ( i % 2 == 1) // page is in right column
+ {
+ if (width > maxRightColumnWidth)
+ maxRightColumnWidth = width;
+ widestPageRight = i;
+ }
+ }
+
+ double ratio = maxLeftColumnWidth / (maxLeftColumnWidth + maxRightColumnWidth);
+
+ // This number is the amount of space the left column should occupy in the viewport.
+ unsigned int leftTargetWidth = (unsigned int)(ratio * viewportWidth);
+
+ return pageCache->sizeOfPage(widestPageLeft).zoomForWidth(leftTargetWidth);
+}
+
+double KMultiPage::calculateFitToHeightZoomValue()
+{
+ PageNumber pageNumber = 1;
+
+ // 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();
+
+ if (columns == 1 && rows == 1 && !continuousViewmode) // single page mode
+ {
+ pageNumber = currentPageNumber();
+ if (!pageNumber.isValid())
+ pageNumber = 1;
+ }
+
+ 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*pageDistance)/rows;
+ int targetPageWidth = (int)(targetPageHeight * pageCache->sizeOfPage(pageNumber).aspectRatio() );
+ int targetViewportWidth = targetPageWidth * columns + (columns+1)*pageDistance;
+ targetViewportHeight = scrollView()->viewportSize(targetViewportWidth, targetViewportHeight).height();
+ targetPageHeight = (targetViewportHeight - rows*pageDistance)/rows;
+
+ return pageCache->sizeOfPage(pageNumber).zoomForHeight(targetPageHeight);
+}
+
+
+double KMultiPage::calculateFitToWidthZoomValue()
+{
+ PageNumber pageNumber = 1;
+
+ int columns = scrollView()->getNrColumns();
+ int rows = scrollView()->getNrRows();
+ int continuousViewmode = scrollView()->isContinuous();
+ bool fullScreenMode = scrollView()->fullScreenMode();
+
+ if (columns == 1 && rows == 1 && !continuousViewmode) // single page mode
+ {
+ // To calculate the zoom level in single page mode we need the size
+ // of the current page. When a new document is opened this function
+ // is called while the currentPageNumber is invalid. We use the size
+ // of the first page of the document in this case.
+ pageNumber = currentPageNumber();
+ if (!pageNumber.isValid())
+ pageNumber = 1;
+ }
+
+ if (columns == 1 && rows == 1 && continuousViewmode) // continuous viewmode
+ {
+ pageNumber = widestPage();
+ if (!pageNumber.isValid())
+ pageNumber = 1;
+ }
+
+ // 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(numberOfPages() / (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(pageNumber).aspectRatio());
+ // FIXME: this is only correct if all pages in the document have the same height
+ 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();
+
+ if (columns == 2 && continuousViewmode) // continuous facing
+ {
+ // TODO Generalize this for more than 2 columns
+ return zoomForWidthColumns(targetViewportWidth - (columns+1) * pageDistance);
+ }
+
+ // maximal width of a single page (now the scrollbars are taken into account)
+ targetPageWidth = (targetViewportWidth - (columns+1) * pageDistance) / columns;
+
+ return pageCache->sizeOfPage(pageNumber).zoomForWidth(targetPageWidth);
+}
+
+
+void KMultiPage::prevPage()
+{
+ Q_UINT8 cols = scrollView()->getNrColumns();
+ Q_UINT8 rows = scrollView()->getNrRows();
+
+ PageNumber np = 1;
+ if (cols*rows < currentPageNumber())
+ {
+ np = currentPageNumber() - cols*rows;
+ }
+
+ gotoPage(np);
+}
+
+
+void KMultiPage::nextPage()
+{
+ Q_UINT8 cols = scrollView()->getNrColumns();
+ Q_UINT8 rows = scrollView()->getNrRows();
+
+ PageNumber np = QMIN(currentPageNumber() + cols*rows, (Q_UINT16)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() == 1) || (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() == 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()
+{
+ PageView* 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()
+{
+ PageView* 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(const QString& reference)
+{
+ if (renderer.isNull())
+ return;
+
+ gotoPage(renderer->parseReference(reference));
+}
+
+
+void KMultiPage::gotoPage(const Anchor &a)
+{
+ if (!a.page.isValid() || (renderer.isNull()))
+ return;
+
+ gotoPage(a.page, (int)(a.distance_from_top.getLength_in_inch()*pageCache->getResolution() + 0.5), true);
+}
+
+
+void KMultiPage::gotoPage(const TextSelection& selection)
+{
+ if (selection.isEmpty())
+ {
+ kdError(4300) << "KMultiPage::gotoPage(...) called with empty TextSelection." << endl;
+ return;
+ }
+
+ RenderedDocumentPage* pageData = pageCache->getPage(selection.getPageNumber());
+
+ if (pageData == 0) {
+#ifdef DEBUG_DOCUMENTWIDGET
+ kdDebug(4300) << "DocumentWidget::paintEvent: no documentPage generated" << endl;
+#endif
+ return;
+ }
+
+ switch (widgetList.size())
+ {
+ case 0:
+ kdError(4300) << "KMultiPage::select() while widgetList is empty" << endl;
+ break;
+ case 1:
+ ((DocumentWidget*)widgetList[0])->select(selection);
+ break;
+ default:
+ if (widgetList.size() < currentPageNumber())
+ kdError(4300) << "KMultiPage::select() while widgetList.size()=" << widgetList.size() << "and currentPageNumber()=" << currentPageNumber() << endl;
+ else
+ ((DocumentWidget*)widgetList[selection.getPageNumber() - 1])->select(selection);
+ }
+
+ unsigned int y = pageData->textBoxList[selection.getSelectedTextStart()].box.top();
+ gotoPage(selection.getPageNumber(), y, false);
+}
+
+
+void KMultiPage::doSelectAll()
+{
+ switch( widgetList.size() ) {
+ case 0:
+ kdError(4300) << "KMultiPage::doSelectAll() while widgetList is empty" << endl;
+ break;
+ case 1:
+ ((DocumentWidget *)widgetList[0])->selectAll();
+ break;
+ default:
+ if (widgetList.size() < currentPageNumber())
+ kdError(4300) << "KMultiPage::doSelectAll() while widgetList.size()=" << widgetList.size() << "and currentPageNumber()=" << currentPageNumber() << endl;
+ else
+ ((DocumentWidget *)widgetList[currentPageNumber()-1])->selectAll();
+ }
+}
+
+
+
+void KMultiPage::showFindTextDialog()
+{
+ if ((renderer.isNull()) || (renderer->supportsTextSearch() == false))
+ return;
+
+ searchWidget->show();
+ searchWidget->setFocus();
+}
+
+void KMultiPage::stopSearch()
+{
+ if (searchInProgress)
+ {
+ // stop the search
+ searchInProgress = false;
+ }
+ else
+ searchWidget->hide();
+}
+
+void KMultiPage::findNextText()
+{
+#ifdef KDVI_MULTIPAGE_DEBUG
+ kdDebug(4300) << "KMultiPage::findNextText() called" << endl;
+#endif
+
+ searchInProgress = true;
+
+ // Used to remember if the documentPage we use is from the cache.
+ // If not we need to delete it manually to avoid a memory leak.
+ bool cachedPage = false;
+
+ QString searchText = searchWidget->getText();
+
+ if (searchText.isEmpty())
+ {
+ kdError(4300) << "KMultiPage::findNextText() called when search text was empty" << endl;
+ return;
+ }
+
+ bool case_sensitive = searchWidget->caseSensitive();
+
+ // 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;
+
+ TextSelection userSelection = pageCache->selectedText();
+ if (userSelection.isEmpty())
+ {
+ startingPage = currentPageNumber();
+ startingTextItem = 0;
+ }
+ else
+ {
+ startingPage = userSelection.getPageNumber();
+ startingTextItem = userSelection.getSelectedTextEnd()+1;
+ }
+
+ TextSelection foundSelection;
+
+ RenderedDocumentPagePixmap* searchPage = 0;
+
+ for(unsigned int i = 0; i < numberOfPages(); i++)
+ {
+ unsigned int pageNumber = (i + startingPage - 1) % numberOfPages() + 1;
+
+ if (!searchInProgress)
+ {
+ // Interrupt the search
+ setStatusBarText(i18n("Search interrupted"));
+ if (!cachedPage)
+ delete searchPage;
+ return;
+ }
+
+ if (i != 0)
+ {
+ setStatusBarText(i18n("Search page %1 of %2").arg(pageNumber).arg(numberOfPages()));
+ kapp->processEvents();
+ }
+
+ // Check if we already have a rendered version of the page in the cache. As we are only interested in the
+ // text we don't care about the page size.
+ if (pageCache->isPageCached(pageNumber))
+ {
+ // If the last search page used was created locally, we need to destroy it
+ if (!cachedPage)
+ delete searchPage;
+
+ searchPage = pageCache->getPage(pageNumber);
+ cachedPage = true;
+ }
+ else
+ {
+ // If the page is not in the cache we draw a small version of it, since this is faster.
+
+ // We only create a new searchPage if we need to, otherwise reuse the existing one.
+ if (!searchPage || cachedPage)
+ searchPage = new RenderedDocumentPagePixmap();
+
+ cachedPage = false;
+
+ searchPage->resize(1,1);
+ searchPage->setPageNumber(pageNumber);
+ renderer->getText(searchPage);
+ }
+
+ // If there is no text in the current page, try the next one.
+ if (searchPage->textBoxList.size() == 0)
+ continue;
+
+ foundSelection = searchPage->find(searchText, startingTextItem, case_sensitive);
+
+ if (foundSelection.isEmpty())
+ {
+ // In the next page, start search again at the beginning.
+ startingTextItem = 0;
+ clearSelection();
+
+ if (pageNumber == numberOfPages())
+ {
+ 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"), KStdGuiItem::cont(), KStdGuiItem::cancel());
+
+ if (answ != KMessageBox::Yes)
+ {
+ setStatusBarText(QString::null);
+ searchInProgress = false;
+ if (!cachedPage)
+ delete searchPage;
+ return;
+ }
+ }
+ }
+ else
+ {
+ pageCache->selectText(foundSelection);
+ gotoPage(pageCache->selectedText());
+ setStatusBarText(QString::null);
+ searchInProgress = false;
+ if (!cachedPage)
+ delete searchPage;
+ return;
+ }
+ }
+
+ KMessageBox::sorry(scrollView(), i18n("<qt>The search string <strong>%1</strong> could not be found.</qt>").arg(searchText));
+ setStatusBarText(QString::null);
+ searchInProgress = false;
+ if (!cachedPage)
+ delete searchPage;
+}
+
+
+void KMultiPage::findPrevText()
+{
+#ifdef KDVI_MULTIPAGE_DEBUG
+ kdDebug(4300) << "KMultiPage::findPrevText() called" << endl;
+#endif
+
+ searchInProgress = true;
+
+ // Used to remember if the documentPage we use is from the cache.
+ // If not we need to delete it manually to avoid a memory leak.
+ bool cachedPage = false;
+
+ QString searchText = searchWidget->getText();
+
+ if (searchText.isEmpty())
+ {
+ kdError(4300) << "KMultiPage::findPrevText() called when search text was empty" << endl;
+ return;
+ }
+
+ bool case_sensitive = searchWidget->caseSensitive();
+
+ // 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'
+ unsigned int startingPage;
+ int startingTextItem;
+
+ TextSelection userSelection = pageCache->selectedText();
+ if (userSelection.isEmpty())
+ {
+ startingPage = currentPageNumber();
+ startingTextItem = -1;
+ }
+ else
+ {
+ startingPage = userSelection.getPageNumber();
+ startingTextItem = userSelection.getSelectedTextStart()-1;
+ }
+
+ TextSelection foundSelection;
+
+ RenderedDocumentPagePixmap* searchPage = 0;
+
+ for(unsigned int i = 0; i < numberOfPages(); i++)
+ {
+ int pageNumber = startingPage - i;
+ if (pageNumber <= 0)
+ pageNumber += numberOfPages();
+
+ if (!searchInProgress)
+ {
+ // Interrupt the search
+ setStatusBarText(i18n("Search interrupted"));
+ if (!cachedPage)
+ delete searchPage;
+ return;
+ }
+
+ if (i != 0)
+ {
+ setStatusBarText(i18n("Search page %1 of %2").arg(pageNumber).arg(numberOfPages()));
+ kapp->processEvents();
+ }
+
+ // Check if we already have a rendered version of the page in the cache. As we are only interested in the
+ // text we don't care about the page size.
+ if (pageCache->isPageCached(pageNumber))
+ {
+ // If the last search page used was created locally, we need to destroy it
+ if (!cachedPage)
+ delete searchPage;
+
+ searchPage = pageCache->getPage(pageNumber);
+ cachedPage = true;
+ }
+ else
+ {
+ // If the page is not in the cache we draw a small version of it, since this is faster.
+
+ // We only create a new searchPage if we need to, otherwise reuse the existing one.
+ if (!searchPage || cachedPage)
+ searchPage = new RenderedDocumentPagePixmap();
+
+ cachedPage = false;
+
+ searchPage->resize(1,1);
+ searchPage->setPageNumber(pageNumber);
+ renderer->getText(searchPage);
+ }
+
+ // If there is no text in the current page, try the next one.
+ if (searchPage->textBoxList.size() == 0)
+ continue;
+
+ foundSelection = searchPage->findRev(searchText, startingTextItem, case_sensitive);
+
+ if (foundSelection.isEmpty())
+ {
+ // In the next page, start search again at the beginning.
+ startingTextItem = -1;
+ clearSelection();
+
+ if (pageNumber == 1)
+ {
+ 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"), KStdGuiItem::cont(), KStdGuiItem::cancel());
+
+ if (answ != KMessageBox::Yes)
+ {
+ setStatusBarText(QString::null);
+ searchInProgress = false;
+ if (!cachedPage)
+ delete searchPage;
+ return;
+ }
+ }
+ }
+ else
+ {
+ pageCache->selectText(foundSelection);
+ gotoPage(pageCache->selectedText());
+ setStatusBarText(QString::null);
+ searchInProgress = false;
+ if (!cachedPage)
+ delete searchPage;
+ return;
+ }
+ }
+
+ KMessageBox::sorry(scrollView(), i18n("<qt>The search string <strong>%1</strong> could not be found.</qt>").arg(searchText));
+ setStatusBarText(QString::null);
+ searchInProgress = false;
+ if (!cachedPage)
+ delete searchPage;
+}
+
+
+void KMultiPage::clearSelection()
+{
+ PageNumber page = pageCache->selectedText().getPageNumber();
+
+ if (!page.isValid())
+ return;
+
+ // Clear selection
+ pageCache->deselectText();
+
+ // Now we need to update the widget which contained the selection
+ switch(widgetList.size())
+ {
+ case 0:
+ kdError(4300) << "KMultiPage::clearSelection() while widgetList is empty" << endl;
+ break;
+ case 1:
+ widgetList[0]->update();
+ break;
+ default:
+ for (unsigned int i = 0; i < widgetList.size(); i++)
+ {
+ DocumentWidget* pageWidget = (DocumentWidget*)widgetList[i];
+ if (pageWidget->getPageNumber() == page)
+ {
+ pageWidget->update();
+ break;
+ }
+ }
+ }
+}
+
+void KMultiPage::copyText()
+{
+ pageCache->selectedText().copyText();
+}
+
+void KMultiPage::timerEvent( QTimerEvent * )
+{
+#ifdef KMULTIPAGE_DEBUG
+ kdDebug(4300) << "Timer Event " << endl;
+#endif
+ reload();
+}
+
+
+void KMultiPage::reload()
+{
+#ifdef KMULTIPAGE_DEBUG
+ kdDebug(4300) << "Reload file " << m_file << endl;
+#endif
+
+ if (renderer.isNull()) {
+ kdError() << "KMultiPage::reload() called, but no renderer was set" << endl;
+ return;
+ }
+
+ if (renderer->isValidFile(m_file)) {
+ pageCache->clear();
+ pageCache->deselectText();
+ document_history.clear();
+ emit setStatusBarText(i18n("Reloading file %1").arg(m_file));
+ Q_INT32 pg = currentPageNumber();
+
+ killTimer(timer_id);
+ timer_id = -1;
+ bool r = renderer->setFile(m_file, m_url);
+
+ generateDocumentWidgets();
+
+ // Set Table of Contents
+ tableOfContents->setContents(renderer->getBookmarks());
+
+ // Adjust number of widgets in the thumbnail sidebar
+ markList()->clear();
+ markList()->setNumberOfPages(numberOfPages(), KVSPrefs::showThumbnails());
+
+ setCurrentPageNumber(pg);
+ setFile(r);
+ emit setStatusBarText(QString::null);
+ } else {
+ if (timer_id == -1)
+ timer_id = startTimer(1000);
+ }
+}
+
+
+bool KMultiPage::openFile()
+{
+ if (renderer.isNull()) {
+ kdError(4300) << "KMultiPage::openFile() called when no renderer was set" << endl;
+ return false;
+ }
+
+ pageCache->deselectText();
+ document_history.clear();
+ pageCache->clear();
+ emit setStatusBarText(i18n("Loading file %1").arg(m_file));
+
+ bool r = renderer->setFile(m_file, m_url);
+
+ if (r) {
+ setCurrentPageNumber(1);
+ generateDocumentWidgets();
+
+ // Set number of widgets in the thumbnail sidebar
+ markList()->clear();
+ markList()->setNumberOfPages(numberOfPages(), KVSPrefs::showThumbnails());
+
+ QString reference = url().ref();
+ if (!reference.isEmpty())
+ gotoPage(renderer->parseReference(reference));
+
+ // Set Table of Contents
+ tableOfContents->setContents(renderer->getBookmarks());
+ } else
+ m_file = QString::null;
+
+
+ setFile(r);
+
+ // Clear Statusbar
+ emit setStatusBarText(QString::null);
+ return r;
+}
+
+
+bool KMultiPage::openURL(const QString &filename, const KURL &base_url)
+{
+ m_file = filename;
+ m_url = base_url;
+
+ bool success = openFile();
+ if (success)
+ setCurrentPageNumber(1);
+
+ return success;
+}
+
+
+void KMultiPage::enableActions(bool fileLoaded)
+{
+ Q_UNUSED(fileLoaded);
+}
+
+void KMultiPage::wheelEvent(QWheelEvent *e)
+{
+ QScrollBar *sb = scrollView()->verticalScrollBar();
+ if (sb == 0)
+ return;
+
+ // Zoom in/out
+ if (e->state() & ControlButton)
+ {
+ if (e->delta() < 0)
+ emit zoomOut();
+ else
+ emit zoomIn();
+ return;
+ }
+
+ Q_INT32 pxl = -(e->delta()*sb->lineStep())/60;
+ if (pxl == 0)
+ {
+ if (e->delta() > 0)
+ pxl = -1;
+ else
+ pxl = 1;
+ }
+
+ // Faster scrolling
+ if (e->state() & ShiftButton)
+ pxl *= 10;
+
+ scroll(pxl);
+}
+
+
+KPrinter *KMultiPage::getPrinter(bool enablePageSizeFeatures)
+{
+ // Allocate a new KPrinter structure, if necessary
+ KPrinter *printer = new KPrinter(true);
+ if (printer == 0) {
+ kdError(1223) << "KMultiPage::getPrinter(..): Cannot allocate new KPrinter structure" << endl;
+ return 0;
+ }
+
+ // Allocate a new KPrintDialogPage structure and add it to the
+ // printer, if the kmultipage implementation requests that
+ if (enablePageSizeFeatures == true) {
+ KPrintDialogPage_PageOptions *pageOptions = new KPrintDialogPage_PageOptions();
+ if (pageOptions == 0) {
+ kdError(1223) << "KMultiPage::getPrinter(..): Cannot allocate new KPrintDialogPage_PageOptions structure" << endl;
+ delete printer;
+ return 0;
+ }
+ printer->addDialogPage( pageOptions );
+ }
+
+ // Feed the printer with useful defaults and information.
+ printer->setPageSelection( KPrinter::ApplicationSide );
+ printer->setCurrentPage( currentPageNumber() );
+ printer->setMinMax( 1, numberOfPages() );
+ printer->setFullPage( true );
+
+ // If pages are marked, give a list of marked pages to the
+ // printer. We try to be smart and optimize the list by using ranges
+ // ("5-11") wherever possible. The user will be tankful for
+ // that. Complicated? Yeah, but that's life.
+ QValueList<int> selectedPageNo = selectedPages();
+ if (selectedPageNo.isEmpty() == true)
+ printer->setOption( "kde-range", "" );
+ else {
+ int commaflag = 0;
+ QString range;
+ QValueList<int>::ConstIterator it = selectedPageNo.begin();
+ do{
+ int val = *it;
+ if (commaflag == 1)
+ range += QString(", ");
+ else
+ commaflag = 1;
+ int endval = val;
+ if (it != selectedPageNo.end()) {
+ QValueList<int>::ConstIterator jt = it;
+ jt++;
+ do{
+ int val2 = *jt;
+ if (val2 == endval+1)
+ endval++;
+ else
+ break;
+ jt++;
+ } while( jt != selectedPageNo.end() );
+ it = jt;
+ } else
+ it++;
+ if (endval == val)
+ range += QString("%1").arg(val);
+ else
+ range += QString("%1-%2").arg(val).arg(endval);
+ } while (it != selectedPageNo.end() );
+ printer->setOption( "kde-range", range );
+ }
+
+ return printer;
+}
+
+void KMultiPage::doExportText()
+{
+ // Generate a suggestion for a reasonable file name
+ QString suggestedName = url().filename();
+ suggestedName = suggestedName.left(suggestedName.find(".")) + ".txt";
+
+ QString fileName = KFileDialog::getSaveFileName(suggestedName, i18n("*.txt|Plain Text (Latin 1) (*.txt)"), scrollView(), i18n("Export File As"));
+
+ if (fileName.isEmpty())
+ return;
+
+ QFileInfo finfo(fileName);
+ if (finfo.exists())
+ {
+ int r = KMessageBox::warningContinueCancel (scrollView(),
+ i18n("The file %1\nexists. Do you want to overwrite that file?").arg(fileName),
+ i18n("Overwrite File"), i18n("Overwrite"));
+
+ if (r == KMessageBox::Cancel)
+ return;
+ }
+
+ QFile textFile(fileName);
+ textFile.open(IO_WriteOnly);
+ QTextStream stream(&textFile);
+
+ QProgressDialog progress(i18n("Exporting to text..."), i18n("Abort"), renderer->totalPages(),
+ scrollView(), "export_text_progress", true);
+ progress.setMinimumDuration(300);
+
+ RenderedDocumentPagePixmap dummyPage;
+ dummyPage.resize(1, 1);
+
+ for(unsigned int page = 1; page <= renderer->totalPages(); page++)
+ {
+ progress.setProgress(page);
+ qApp->processEvents();
+
+ if (progress.wasCancelled())
+ break;
+
+ dummyPage.setPageNumber(page);
+ // We gracefully ignore any errors (bad file, etc.)
+ renderer->getText(&dummyPage);
+
+ for(unsigned int i = 0; i < dummyPage.textBoxList.size(); i++)
+ {
+ // We try to detect newlines
+ if (i > 0)
+ {
+ // Like all our textalgorithmns this currently assumes left to right text.
+ // TODO: make this more generic. But we first would need to guess the corrent
+ // orientation.
+ if (dummyPage.textBoxList[i].box.top() > dummyPage.textBoxList[i-1].box.bottom() &&
+ dummyPage.textBoxList[i].box.x() < dummyPage.textBoxList[i-1].box.x())
+ {
+ stream << "\n";
+ }
+ }
+ stream << dummyPage.textBoxList[i].text;
+ }
+
+ // Send newline after each page.
+ stream << "\n";
+ }
+
+ // Switch off the progress dialog, etc.
+ progress.setProgress(renderer->totalPages());
+ return;
+}
+
+void KMultiPage::slotEnableMoveTool(bool enable)
+{
+ emit enableMoveTool(enable);
+}
+
+#include "kmultipage.moc"