From 47d455dd55be855e4cc691c32f687f723d9247ee Mon Sep 17 00:00:00 2001 From: toma Date: Wed, 25 Nov 2009 17:56:58 +0000 Subject: Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features. BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdegraphics@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- kviewshell/DESIGN | 7 + kviewshell/Mainpage.dox | 92 + kviewshell/Makefile.am | 67 + kviewshell/TODO | 94 + kviewshell/anchor.h | 61 + kviewshell/bookmark.h | 107 + kviewshell/documentPageCache.cpp | 337 +++ kviewshell/documentPageCache.h | 143 + kviewshell/documentRenderer.cpp | 133 + kviewshell/documentRenderer.h | 478 ++++ kviewshell/documentWidget.cpp | 764 ++++++ kviewshell/documentWidget.h | 193 ++ kviewshell/emptyRenderer.cpp | 29 + kviewshell/emptyRenderer.h | 41 + kviewshell/empty_multipage.cpp | 48 + kviewshell/empty_multipage.h | 51 + kviewshell/emptymultipage.desktop | 29 + kviewshell/history.cpp | 91 + kviewshell/history.h | 58 + kviewshell/hyperlink.h | 78 + kviewshell/kmultipage.cpp | 1976 ++++++++++++++ kviewshell/kmultipage.desktop | 41 + kviewshell/kmultipage.h | 650 +++++ kviewshell/kmultipageInterface.h | 18 + kviewshell/kprintDialogPage_pageoptions.cpp | 166 ++ kviewshell/kprintDialogPage_pageoptions.h | 43 + kviewshell/kviewerpart.rc | 91 + kviewshell/kviewpart.cpp | 1632 +++++++++++ kviewshell/kviewpart.h | 252 ++ kviewshell/kviewpart_iface.cpp | 4 + kviewshell/kviewpart_iface.h | 35 + kviewshell/kviewshell.cpp | 384 +++ kviewshell/kviewshell.h | 89 + kviewshell/kviewshell.kcfg | 115 + kviewshell/kviewshell.rc | 37 + kviewshell/kvsprefs.kcfgc | 5 + kviewshell/length.h | 173 ++ kviewshell/main.cpp | 184 ++ kviewshell/marklist.cpp | 616 +++++ kviewshell/marklist.h | 186 ++ kviewshell/optionDialogAccessibilityWidget.ui | 528 ++++ kviewshell/optionDialogGUIWidget_base.ui | 148 + kviewshell/pageNumber.h | 65 + kviewshell/pageSize.cpp | 343 +++ kviewshell/pageSize.h | 280 ++ kviewshell/pageSizeDialog.cpp | 63 + kviewshell/pageSizeDialog.h | 52 + kviewshell/pageSizeWidget.cpp | 148 + kviewshell/pageSizeWidget.h | 52 + kviewshell/pageSizeWidget_base.ui | 260 ++ kviewshell/pageView.cpp | 593 ++++ kviewshell/pageView.h | 175 ++ kviewshell/pics/Makefile.am | 3 + kviewshell/pics/cr16-app-kviewshell.png | Bin 0 -> 945 bytes kviewshell/pics/cr32-app-kviewshell.png | Bin 0 -> 2224 bytes kviewshell/pics/cr48-app-kviewshell.png | Bin 0 -> 3949 bytes kviewshell/pics/icons/Makefile.am | 3 + kviewshell/pics/icons/hi16-action-movetool.png | Bin 0 -> 433 bytes .../pics/icons/hi16-action-selectiontool.png | Bin 0 -> 483 bytes kviewshell/pics/icons/hi22-action-movetool.png | Bin 0 -> 826 bytes .../pics/icons/hi22-action-selectiontool.png | Bin 0 -> 404 bytes kviewshell/pics/icons/hi32-action-movetool.png | Bin 0 -> 1554 bytes kviewshell/pics/icons/hi48-action-movetool.png | Bin 0 -> 2874 bytes kviewshell/plugins/Makefile.am | 1 + kviewshell/plugins/djvu/Makefile.am | 33 + kviewshell/plugins/djvu/djvumultipage.cpp | 357 +++ kviewshell/plugins/djvu/djvumultipage.desktop | 57 + kviewshell/plugins/djvu/djvumultipage.h | 149 ++ kviewshell/plugins/djvu/djvumultipage.kcfg | 18 + kviewshell/plugins/djvu/djvumultipage.rc | 15 + kviewshell/plugins/djvu/djvurenderer.cpp | 719 +++++ kviewshell/plugins/djvu/djvurenderer.h | 146 + .../kprintDialogPage_DJVUconversionoptions.cpp | 118 + .../djvu/kprintDialogPage_DJVUconversionoptions.h | 50 + ...tDialogPage_DJVUconversionoptions_basewidget.ui | 145 + .../djvu/kprintDialogPage_DJVUpageoptions.cpp | 119 + .../djvu/kprintDialogPage_DJVUpageoptions.h | 42 + kviewshell/plugins/djvu/libdjvu/Arrays.cpp | 305 +++ kviewshell/plugins/djvu/libdjvu/Arrays.h | 997 +++++++ kviewshell/plugins/djvu/libdjvu/BSByteStream.cpp | 465 ++++ kviewshell/plugins/djvu/libdjvu/BSByteStream.h | 275 ++ .../plugins/djvu/libdjvu/BSEncodeByteStream.cpp | 1010 +++++++ kviewshell/plugins/djvu/libdjvu/ByteStream.cpp | 1445 ++++++++++ kviewshell/plugins/djvu/libdjvu/ByteStream.h | 416 +++ kviewshell/plugins/djvu/libdjvu/DataPool.cpp | 1837 +++++++++++++ kviewshell/plugins/djvu/libdjvu/DataPool.h | 627 +++++ kviewshell/plugins/djvu/libdjvu/DjVmDir.cpp | 839 ++++++ kviewshell/plugins/djvu/libdjvu/DjVmDir.h | 451 ++++ kviewshell/plugins/djvu/libdjvu/DjVmDir0.cpp | 169 ++ kviewshell/plugins/djvu/libdjvu/DjVmDir0.h | 217 ++ kviewshell/plugins/djvu/libdjvu/DjVmDoc.cpp | 663 +++++ kviewshell/plugins/djvu/libdjvu/DjVmDoc.h | 274 ++ kviewshell/plugins/djvu/libdjvu/DjVmNav.cpp | 338 +++ kviewshell/plugins/djvu/libdjvu/DjVmNav.h | 146 + kviewshell/plugins/djvu/libdjvu/DjVuAnno.cpp | 1514 +++++++++++ kviewshell/plugins/djvu/libdjvu/DjVuAnno.h | 295 ++ kviewshell/plugins/djvu/libdjvu/DjVuDocEditor.cpp | 2193 +++++++++++++++ kviewshell/plugins/djvu/libdjvu/DjVuDocEditor.h | 460 ++++ kviewshell/plugins/djvu/libdjvu/DjVuDocument.cpp | 1845 +++++++++++++ kviewshell/plugins/djvu/libdjvu/DjVuDocument.h | 1071 ++++++++ kviewshell/plugins/djvu/libdjvu/DjVuDumpHelper.cpp | 353 +++ kviewshell/plugins/djvu/libdjvu/DjVuDumpHelper.h | 126 + kviewshell/plugins/djvu/libdjvu/DjVuErrorList.cpp | 174 ++ kviewshell/plugins/djvu/libdjvu/DjVuErrorList.h | 194 ++ kviewshell/plugins/djvu/libdjvu/DjVuFile.cpp | 2831 ++++++++++++++++++++ kviewshell/plugins/djvu/libdjvu/DjVuFile.h | 848 ++++++ kviewshell/plugins/djvu/libdjvu/DjVuFileCache.cpp | 272 ++ kviewshell/plugins/djvu/libdjvu/DjVuFileCache.h | 292 ++ kviewshell/plugins/djvu/libdjvu/DjVuGlobal.cpp | 255 ++ kviewshell/plugins/djvu/libdjvu/DjVuGlobal.h | 398 +++ .../plugins/djvu/libdjvu/DjVuGlobalMemory.cpp | 306 +++ kviewshell/plugins/djvu/libdjvu/DjVuImage.cpp | 1486 ++++++++++ kviewshell/plugins/djvu/libdjvu/DjVuImage.h | 449 ++++ kviewshell/plugins/djvu/libdjvu/DjVuInfo.cpp | 205 ++ kviewshell/plugins/djvu/libdjvu/DjVuInfo.h | 193 ++ kviewshell/plugins/djvu/libdjvu/DjVuMessage.cpp | 647 +++++ kviewshell/plugins/djvu/libdjvu/DjVuMessage.h | 135 + .../plugins/djvu/libdjvu/DjVuMessageLite.cpp | 478 ++++ kviewshell/plugins/djvu/libdjvu/DjVuMessageLite.h | 227 ++ kviewshell/plugins/djvu/libdjvu/DjVuNavDir.cpp | 237 ++ kviewshell/plugins/djvu/libdjvu/DjVuNavDir.h | 192 ++ kviewshell/plugins/djvu/libdjvu/DjVuPalette.cpp | 590 ++++ kviewshell/plugins/djvu/libdjvu/DjVuPalette.h | 339 +++ kviewshell/plugins/djvu/libdjvu/DjVuPort.cpp | 710 +++++ kviewshell/plugins/djvu/libdjvu/DjVuPort.h | 518 ++++ kviewshell/plugins/djvu/libdjvu/DjVuText.cpp | 971 +++++++ kviewshell/plugins/djvu/libdjvu/DjVuText.h | 281 ++ kviewshell/plugins/djvu/libdjvu/DjVuToPS.cpp | 2582 ++++++++++++++++++ kviewshell/plugins/djvu/libdjvu/DjVuToPS.h | 425 +++ kviewshell/plugins/djvu/libdjvu/GBitmap.cpp | 1658 ++++++++++++ kviewshell/plugins/djvu/libdjvu/GBitmap.h | 673 +++++ kviewshell/plugins/djvu/libdjvu/GContainer.cpp | 802 ++++++ kviewshell/plugins/djvu/libdjvu/GContainer.h | 1366 ++++++++++ kviewshell/plugins/djvu/libdjvu/GException.cpp | 284 ++ kviewshell/plugins/djvu/libdjvu/GException.h | 356 +++ kviewshell/plugins/djvu/libdjvu/GIFFManager.cpp | 663 +++++ kviewshell/plugins/djvu/libdjvu/GIFFManager.h | 394 +++ kviewshell/plugins/djvu/libdjvu/GMapAreas.cpp | 1066 ++++++++ kviewshell/plugins/djvu/libdjvu/GMapAreas.h | 565 ++++ kviewshell/plugins/djvu/libdjvu/GOS.cpp | 370 +++ kviewshell/plugins/djvu/libdjvu/GOS.h | 161 ++ kviewshell/plugins/djvu/libdjvu/GPixmap.cpp | 1676 ++++++++++++ kviewshell/plugins/djvu/libdjvu/GPixmap.h | 531 ++++ kviewshell/plugins/djvu/libdjvu/GRect.cpp | 458 ++++ kviewshell/plugins/djvu/libdjvu/GRect.h | 407 +++ kviewshell/plugins/djvu/libdjvu/GScaler.cpp | 706 +++++ kviewshell/plugins/djvu/libdjvu/GScaler.h | 321 +++ kviewshell/plugins/djvu/libdjvu/GSmartPointer.cpp | 256 ++ kviewshell/plugins/djvu/libdjvu/GSmartPointer.h | 489 ++++ kviewshell/plugins/djvu/libdjvu/GString.cpp | 2811 +++++++++++++++++++ kviewshell/plugins/djvu/libdjvu/GString.h | 1676 ++++++++++++ kviewshell/plugins/djvu/libdjvu/GThreads.cpp | 1887 +++++++++++++ kviewshell/plugins/djvu/libdjvu/GThreads.h | 639 +++++ kviewshell/plugins/djvu/libdjvu/GURL.cpp | 1965 ++++++++++++++ kviewshell/plugins/djvu/libdjvu/GURL.h | 446 +++ kviewshell/plugins/djvu/libdjvu/GUnicode.cpp | 790 ++++++ kviewshell/plugins/djvu/libdjvu/IFFByteStream.cpp | 558 ++++ kviewshell/plugins/djvu/libdjvu/IFFByteStream.h | 312 +++ .../plugins/djvu/libdjvu/IW44EncodeCodec.cpp | 1797 +++++++++++++ kviewshell/plugins/djvu/libdjvu/IW44Image.cpp | 1935 +++++++++++++ kviewshell/plugins/djvu/libdjvu/IW44Image.h | 761 ++++++ kviewshell/plugins/djvu/libdjvu/JB2EncodeCodec.cpp | 564 ++++ kviewshell/plugins/djvu/libdjvu/JB2Image.cpp | 1427 ++++++++++ kviewshell/plugins/djvu/libdjvu/JB2Image.h | 805 ++++++ kviewshell/plugins/djvu/libdjvu/JPEGDecoder.cpp | 413 +++ kviewshell/plugins/djvu/libdjvu/JPEGDecoder.h | 133 + kviewshell/plugins/djvu/libdjvu/MMRDecoder.cpp | 961 +++++++ kviewshell/plugins/djvu/libdjvu/MMRDecoder.h | 238 ++ kviewshell/plugins/djvu/libdjvu/MMX.cpp | 209 ++ kviewshell/plugins/djvu/libdjvu/MMX.h | 194 ++ kviewshell/plugins/djvu/libdjvu/Makefile.am | 18 + kviewshell/plugins/djvu/libdjvu/README.djvulibre | 85 + kviewshell/plugins/djvu/libdjvu/Template.h | 258 ++ .../plugins/djvu/libdjvu/UnicodeByteStream.cpp | 368 +++ .../plugins/djvu/libdjvu/UnicodeByteStream.h | 199 ++ kviewshell/plugins/djvu/libdjvu/XMLParser.cpp | 1128 ++++++++ kviewshell/plugins/djvu/libdjvu/XMLParser.h | 123 + kviewshell/plugins/djvu/libdjvu/XMLTags.cpp | 417 +++ kviewshell/plugins/djvu/libdjvu/XMLTags.h | 242 ++ kviewshell/plugins/djvu/libdjvu/ZPCodec.cpp | 1292 +++++++++ kviewshell/plugins/djvu/libdjvu/ZPCodec.h | 747 ++++++ kviewshell/plugins/djvu/libdjvu/configure.in.in | 674 +++++ kviewshell/plugins/djvu/libdjvu/debug.cpp | 299 +++ kviewshell/plugins/djvu/libdjvu/debug.h | 304 +++ kviewshell/plugins/djvu/pageRangeWidget.cpp | 68 + kviewshell/plugins/djvu/pageRangeWidget.h | 45 + kviewshell/plugins/djvu/pageRangeWidget_base.ui | 75 + kviewshell/plugins/djvu/prefs.kcfgc | 5 + kviewshell/renderedDocumentPage.cpp | 375 +++ kviewshell/renderedDocumentPage.h | 241 ++ kviewshell/renderedDocumentPagePixmap.cpp | 122 + kviewshell/renderedDocumentPagePixmap.h | 59 + kviewshell/renderedDocumentPagePrinter.cpp | 53 + kviewshell/renderedDocumentPagePrinter.h | 46 + kviewshell/searchWidget.cpp | 141 + kviewshell/searchWidget.h | 73 + kviewshell/selection.cpp | 66 + kviewshell/selection.h | 86 + kviewshell/simplePageSize.cpp | 49 + kviewshell/simplePageSize.h | 164 ++ kviewshell/sizePreview.cpp | 134 + kviewshell/sizePreview.h | 47 + kviewshell/tableOfContents.cpp | 119 + kviewshell/tableOfContents.h | 69 + kviewshell/textBox.h | 65 + kviewshell/units.cpp | 83 + kviewshell/units.h | 34 + kviewshell/zoom.cpp | 156 ++ kviewshell/zoom.h | 55 + kviewshell/zoomlimits.h | 19 + 210 files changed, 91172 insertions(+) create mode 100644 kviewshell/DESIGN create mode 100644 kviewshell/Mainpage.dox create mode 100644 kviewshell/Makefile.am create mode 100644 kviewshell/TODO create mode 100644 kviewshell/anchor.h create mode 100644 kviewshell/bookmark.h create mode 100644 kviewshell/documentPageCache.cpp create mode 100644 kviewshell/documentPageCache.h create mode 100644 kviewshell/documentRenderer.cpp create mode 100644 kviewshell/documentRenderer.h create mode 100644 kviewshell/documentWidget.cpp create mode 100644 kviewshell/documentWidget.h create mode 100644 kviewshell/emptyRenderer.cpp create mode 100644 kviewshell/emptyRenderer.h create mode 100644 kviewshell/empty_multipage.cpp create mode 100644 kviewshell/empty_multipage.h create mode 100644 kviewshell/emptymultipage.desktop create mode 100644 kviewshell/history.cpp create mode 100644 kviewshell/history.h create mode 100644 kviewshell/hyperlink.h create mode 100644 kviewshell/kmultipage.cpp create mode 100644 kviewshell/kmultipage.desktop create mode 100644 kviewshell/kmultipage.h create mode 100644 kviewshell/kmultipageInterface.h create mode 100644 kviewshell/kprintDialogPage_pageoptions.cpp create mode 100644 kviewshell/kprintDialogPage_pageoptions.h create mode 100644 kviewshell/kviewerpart.rc create mode 100644 kviewshell/kviewpart.cpp create mode 100644 kviewshell/kviewpart.h create mode 100644 kviewshell/kviewpart_iface.cpp create mode 100644 kviewshell/kviewpart_iface.h create mode 100644 kviewshell/kviewshell.cpp create mode 100644 kviewshell/kviewshell.h create mode 100644 kviewshell/kviewshell.kcfg create mode 100644 kviewshell/kviewshell.rc create mode 100644 kviewshell/kvsprefs.kcfgc create mode 100644 kviewshell/length.h create mode 100644 kviewshell/main.cpp create mode 100644 kviewshell/marklist.cpp create mode 100644 kviewshell/marklist.h create mode 100644 kviewshell/optionDialogAccessibilityWidget.ui create mode 100644 kviewshell/optionDialogGUIWidget_base.ui create mode 100644 kviewshell/pageNumber.h create mode 100644 kviewshell/pageSize.cpp create mode 100644 kviewshell/pageSize.h create mode 100644 kviewshell/pageSizeDialog.cpp create mode 100644 kviewshell/pageSizeDialog.h create mode 100644 kviewshell/pageSizeWidget.cpp create mode 100644 kviewshell/pageSizeWidget.h create mode 100644 kviewshell/pageSizeWidget_base.ui create mode 100644 kviewshell/pageView.cpp create mode 100644 kviewshell/pageView.h create mode 100644 kviewshell/pics/Makefile.am create mode 100644 kviewshell/pics/cr16-app-kviewshell.png create mode 100644 kviewshell/pics/cr32-app-kviewshell.png create mode 100644 kviewshell/pics/cr48-app-kviewshell.png create mode 100644 kviewshell/pics/icons/Makefile.am create mode 100644 kviewshell/pics/icons/hi16-action-movetool.png create mode 100644 kviewshell/pics/icons/hi16-action-selectiontool.png create mode 100644 kviewshell/pics/icons/hi22-action-movetool.png create mode 100644 kviewshell/pics/icons/hi22-action-selectiontool.png create mode 100644 kviewshell/pics/icons/hi32-action-movetool.png create mode 100644 kviewshell/pics/icons/hi48-action-movetool.png create mode 100644 kviewshell/plugins/Makefile.am create mode 100644 kviewshell/plugins/djvu/Makefile.am create mode 100644 kviewshell/plugins/djvu/djvumultipage.cpp create mode 100644 kviewshell/plugins/djvu/djvumultipage.desktop create mode 100644 kviewshell/plugins/djvu/djvumultipage.h create mode 100644 kviewshell/plugins/djvu/djvumultipage.kcfg create mode 100644 kviewshell/plugins/djvu/djvumultipage.rc create mode 100644 kviewshell/plugins/djvu/djvurenderer.cpp create mode 100644 kviewshell/plugins/djvu/djvurenderer.h create mode 100644 kviewshell/plugins/djvu/kprintDialogPage_DJVUconversionoptions.cpp create mode 100644 kviewshell/plugins/djvu/kprintDialogPage_DJVUconversionoptions.h create mode 100644 kviewshell/plugins/djvu/kprintDialogPage_DJVUconversionoptions_basewidget.ui create mode 100644 kviewshell/plugins/djvu/kprintDialogPage_DJVUpageoptions.cpp create mode 100644 kviewshell/plugins/djvu/kprintDialogPage_DJVUpageoptions.h create mode 100644 kviewshell/plugins/djvu/libdjvu/Arrays.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/Arrays.h create mode 100644 kviewshell/plugins/djvu/libdjvu/BSByteStream.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/BSByteStream.h create mode 100644 kviewshell/plugins/djvu/libdjvu/BSEncodeByteStream.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/ByteStream.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/ByteStream.h create mode 100644 kviewshell/plugins/djvu/libdjvu/DataPool.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/DataPool.h create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVmDir.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVmDir.h create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVmDir0.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVmDir0.h create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVmDoc.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVmDoc.h create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVmNav.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVmNav.h create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuAnno.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuAnno.h create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuDocEditor.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuDocEditor.h create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuDocument.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuDocument.h create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuDumpHelper.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuDumpHelper.h create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuErrorList.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuErrorList.h create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuFile.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuFile.h create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuFileCache.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuFileCache.h create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuGlobal.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuGlobal.h create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuGlobalMemory.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuImage.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuImage.h create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuInfo.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuInfo.h create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuMessage.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuMessage.h create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuMessageLite.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuMessageLite.h create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuNavDir.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuNavDir.h create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuPalette.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuPalette.h create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuPort.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuPort.h create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuText.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuText.h create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuToPS.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/DjVuToPS.h create mode 100644 kviewshell/plugins/djvu/libdjvu/GBitmap.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/GBitmap.h create mode 100644 kviewshell/plugins/djvu/libdjvu/GContainer.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/GContainer.h create mode 100644 kviewshell/plugins/djvu/libdjvu/GException.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/GException.h create mode 100644 kviewshell/plugins/djvu/libdjvu/GIFFManager.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/GIFFManager.h create mode 100644 kviewshell/plugins/djvu/libdjvu/GMapAreas.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/GMapAreas.h create mode 100644 kviewshell/plugins/djvu/libdjvu/GOS.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/GOS.h create mode 100644 kviewshell/plugins/djvu/libdjvu/GPixmap.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/GPixmap.h create mode 100644 kviewshell/plugins/djvu/libdjvu/GRect.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/GRect.h create mode 100644 kviewshell/plugins/djvu/libdjvu/GScaler.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/GScaler.h create mode 100644 kviewshell/plugins/djvu/libdjvu/GSmartPointer.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/GSmartPointer.h create mode 100644 kviewshell/plugins/djvu/libdjvu/GString.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/GString.h create mode 100644 kviewshell/plugins/djvu/libdjvu/GThreads.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/GThreads.h create mode 100644 kviewshell/plugins/djvu/libdjvu/GURL.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/GURL.h create mode 100644 kviewshell/plugins/djvu/libdjvu/GUnicode.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/IFFByteStream.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/IFFByteStream.h create mode 100644 kviewshell/plugins/djvu/libdjvu/IW44EncodeCodec.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/IW44Image.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/IW44Image.h create mode 100644 kviewshell/plugins/djvu/libdjvu/JB2EncodeCodec.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/JB2Image.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/JB2Image.h create mode 100644 kviewshell/plugins/djvu/libdjvu/JPEGDecoder.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/JPEGDecoder.h create mode 100644 kviewshell/plugins/djvu/libdjvu/MMRDecoder.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/MMRDecoder.h create mode 100644 kviewshell/plugins/djvu/libdjvu/MMX.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/MMX.h create mode 100644 kviewshell/plugins/djvu/libdjvu/Makefile.am create mode 100644 kviewshell/plugins/djvu/libdjvu/README.djvulibre create mode 100644 kviewshell/plugins/djvu/libdjvu/Template.h create mode 100644 kviewshell/plugins/djvu/libdjvu/UnicodeByteStream.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/UnicodeByteStream.h create mode 100644 kviewshell/plugins/djvu/libdjvu/XMLParser.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/XMLParser.h create mode 100644 kviewshell/plugins/djvu/libdjvu/XMLTags.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/XMLTags.h create mode 100644 kviewshell/plugins/djvu/libdjvu/ZPCodec.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/ZPCodec.h create mode 100644 kviewshell/plugins/djvu/libdjvu/configure.in.in create mode 100644 kviewshell/plugins/djvu/libdjvu/debug.cpp create mode 100644 kviewshell/plugins/djvu/libdjvu/debug.h create mode 100644 kviewshell/plugins/djvu/pageRangeWidget.cpp create mode 100644 kviewshell/plugins/djvu/pageRangeWidget.h create mode 100644 kviewshell/plugins/djvu/pageRangeWidget_base.ui create mode 100644 kviewshell/plugins/djvu/prefs.kcfgc create mode 100644 kviewshell/renderedDocumentPage.cpp create mode 100644 kviewshell/renderedDocumentPage.h create mode 100644 kviewshell/renderedDocumentPagePixmap.cpp create mode 100644 kviewshell/renderedDocumentPagePixmap.h create mode 100644 kviewshell/renderedDocumentPagePrinter.cpp create mode 100644 kviewshell/renderedDocumentPagePrinter.h create mode 100644 kviewshell/searchWidget.cpp create mode 100644 kviewshell/searchWidget.h create mode 100644 kviewshell/selection.cpp create mode 100644 kviewshell/selection.h create mode 100644 kviewshell/simplePageSize.cpp create mode 100644 kviewshell/simplePageSize.h create mode 100644 kviewshell/sizePreview.cpp create mode 100644 kviewshell/sizePreview.h create mode 100644 kviewshell/tableOfContents.cpp create mode 100644 kviewshell/tableOfContents.h create mode 100644 kviewshell/textBox.h create mode 100644 kviewshell/units.cpp create mode 100644 kviewshell/units.h create mode 100644 kviewshell/zoom.cpp create mode 100644 kviewshell/zoom.h create mode 100644 kviewshell/zoomlimits.h (limited to 'kviewshell') diff --git a/kviewshell/DESIGN b/kviewshell/DESIGN new file mode 100644 index 00000000..58c47f07 --- /dev/null +++ b/kviewshell/DESIGN @@ -0,0 +1,7 @@ +KViewShell, a KParts::MainWindow +| Embeds a dlopened KViewPart_Iface, which is a KParts::ReadOnlyPart. +| KViewPart is such a part. +| | KViewPart embeds a dlopened KMultiPage, which is a KParts::ReadOnlyPart. +| | (The widget is made a child widget, and the part is made a child XMLGUI client) +| | KDVI provides a KMultiPage implementation, KDVIMultiPage. + diff --git a/kviewshell/Mainpage.dox b/kviewshell/Mainpage.dox new file mode 100644 index 00000000..160f34e2 --- /dev/null +++ b/kviewshell/Mainpage.dox @@ -0,0 +1,92 @@ +/** +@mainpage The KDE Graphics API Reference + +The kviewshell is a document viewing application contained in the +kdegraphics package. It cannot load documents itself, but relies on +plugins that support various document formats. Currently, there are +plugins for TeX DVI, for FAX, and for AT&T's DJVU format. Some of the +plugins come with clones of the kviewshell programm (kdvi, kfaxview) +to maintain compatibility with older shell scripts. + +This document contains the kviewshell library API documentation. It is +primarily aimed at programmers who wish to write plugins to the +kviewshell application. It documents the few classes that the +programmer will have to deal with. + + +@section refimpl kviewshell Example Plugins and Reference Implementation + +Writing KDE libraries and the associated makefiles can be rather +complicated. We have therefore provided a well-documented reference +implementation, the FAX plugin. This is a fine example for a simple +kviewshell plugin, and a very good starting point for your own +implementation. It can be found in the kdegraphics source tree under +kfaxview. If +you are looking for a plugin with more functionality, you might +consider the DJVU plugin, contained in kviewshell/plugins/kdjview. +The DVI plugin is, for historical reasons, rather involved and cannot +be recommended as an example. + +More information about the KDE architecture in form of tutorials, +HOWTOs, and FAQs can be found at the KDE Developer's corner. + + +@section req Plugin Author's TODO List + +For a basic kviewshell plugin, you must as a minimum do the following: + +- Produce a KDE library that contains as a minimum implementations of +two core classes, the KMultiPage and the DocumentRenderer classes. The +DocumentRenderer class loads and renderes files, while the KMultiPage +class provides plugin-specific GUI elements. The reference +implementation contains a good example for a library that you can +tailor to suit your needs. + +- If your file type is not known to KDE, then you need to provide a +.desktop file for the mime type you wish to support. Search the KDE +control center for "mime type" to get a list of known types. Since the +list of mime types that are contained in the kdelibs package is quite +long, it is quite likely that your file type is already known. + +- Provide one or more .desktop files that relate mime-types with your +library. After the file is installed, the file dialog of the +kviewshell application will show files that match the mime-types. The +kviewshell will then use your library to load and display these +files. Again, the reference implementation contains examples. + +With these data provided, you can produce a perfectly working +plugin. To support more advanced features of your document format, you +can optionally also do the following: + +- You can add GUI elements. This is most often done by providing an +.rc-file file, and by calling setXMLFile() in the +constructor. Re-implement KMultiPage::setFile() in order to update the +GUI after a file was loaded or closed. + +- You can add pages to the preferences dialog by re-implementing +KMultiPage::addConfigDialogs() + +- If your plugin offers functionality to modify the document +(e.g. remove or add pages), you should re-implement +KMultiPage::isModified() and KMultiPage::isModified(). + +- If you have good code to convert your document to PostScript, you +can improve printing performance tremendously if you re-implement +KMultiPage::print(). + +@section help Further information + +This document describes only the most basic methods of the most +important classes used in kviewshell, which we expect to be of +interest for authors of kviewshell plugins. If you need more advanced +classes or functionalities, there is some chance that they are already +implemented or can easily be provided. Thus, please do not hesitate to +contact Stefan Kebekus or Wilfried Huss +. No-one profits if you spend hours duplicating +existing code. + +*/ + diff --git a/kviewshell/Makefile.am b/kviewshell/Makefile.am new file mode 100644 index 00000000..ccde5b59 --- /dev/null +++ b/kviewshell/Makefile.am @@ -0,0 +1,67 @@ +SUBDIRS = . pics plugins + +INCLUDES= $(all_includes) +include_HEADERS = kmultipageInterface.h + +####### Files + +bin_PROGRAMS = kviewshell +lib_LTLIBRARIES = libkmultipage.la +kde_module_LTLIBRARIES = kviewerpart.la emptymultipagepart.la +noinst_LTLIBRARIES = libifaces.la libkviewshell.la + +libifaces_la_SOURCES = kviewpart_iface.cpp + +kviewerpart_la_SOURCES = kviewpart.cpp pageSizeWidget_base.ui \ + pageSizeWidget.cpp pageSizeDialog.cpp pageSize.cpp sizePreview.cpp \ + zoom.cpp units.cpp kvsprefs.kcfgc \ + optionDialogGUIWidget_base.ui optionDialogAccessibilityWidget.ui simplePageSize.cpp + +kviewerpart_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) -module +kviewerpart_la_LIBADD = ./libifaces.la $(LIB_KFILE) -lkparts + +libkmultipage_la_SOURCES = kmultipage.cpp pageView.cpp marklist.cpp kmultipageInterface.skel \ + units.cpp pageSize.cpp simplePageSize.cpp renderedDocumentPage.cpp renderedDocumentPagePixmap.cpp \ + renderedDocumentPagePrinter.cpp documentPageCache.cpp documentWidget.cpp searchWidget.cpp \ + selection.cpp documentRenderer.cpp history.cpp kvsprefs.kcfgc tableOfContents.cpp \ + kprintDialogPage_pageoptions.cpp + + +kde_kcfg_DATA = kviewshell.kcfg + +libkmultipage_la_LDFLAGS = $(all_libraries) +libkmultipage_la_LIBADD = $(LIB_KFILE) -lkdeprint -lkparts + +emptymultipagepart_la_SOURCES = empty_multipage.cpp emptyRenderer.cpp +emptymultipagepart_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) -module +emptymultipagepart_la_LIBADD = -lkparts libkmultipage.la + +libkviewshell_la_SOURCES = kviewshell.cpp + +kviewshell_SOURCES = main.cpp +kviewshell_LDFLAGS = $(all_libraries) $(KDE_RPATH) +kviewshell_LDADD = libkviewshell.la ./libifaces.la -lkparts + +kviewshellinclude_HEADERS = anchor.h bookmark.h pageView.h documentPageCache.h documentRenderer.h documentWidget.h \ + history.h hyperlink.h kmultipage.h length.h pageNumber.h pageSize.h renderedDocumentPage.h renderedDocumentPagePixmap.h \ + renderedDocumentPagePrinter.h selection.h simplePageSize.h textBox.h zoom.h zoomlimits.h +kviewshellincludedir = $(includedir)/kviewshell + + +METASOURCES = AUTO + +rc_DATA = kviewshell.rc +rcdir = $(kde_datadir)/kviewshell +partrc_DATA = kviewerpart.rc +partrcdir = $(kde_datadir)/kviewerpart + +servicetype_DATA = kmultipage.desktop +servicetypedir = $(kde_servicetypesdir) + +kde_services_DATA = emptymultipage.desktop + +messages: rc.cpp + $(XGETTEXT) *.cpp *.h -o $(podir)/kviewshell.pot + + +include ../admin/Doxyfile.am diff --git a/kviewshell/TODO b/kviewshell/TODO new file mode 100644 index 00000000..840653c7 --- /dev/null +++ b/kviewshell/TODO @@ -0,0 +1,94 @@ +API +=== + +If we want developers of other applications to move over to +kviewshell, we definitvely need a stable and mature API. Here are some +things that I feel need to be implemented, at least in API (it is +probably ok if we don't implement all of them right now). + +* documentRenderer. This class needs documentation. We need to specify +what the documentRenderer actually does at what time, and what signals +it emits at what time and what they mean. Things that come to my mind: + +- specify what drawPage actually does. In kdvi, drawPage is a highly +asynchronous method, that either does nothing (e.g. if font are still +being generated), or really draws a page. The behaviour must be +specified, and I believe that for printing (see below) we would also +need to provide for a synchronous way of rendering pages. + +- some document formats include ready-made thumbnails. We should have +have a virtual function that makes thumbnails out of fully rendered +pages, which can be overloaden if the author of a specific file format +supports 'embedded thumbnails' + +- in the future we probably want to use threading to render pages in +memory to improve the perceived speed of applications. Thus, we will +have to specify what methods need to be implemented in a reentrant or +thread-save manner. Probably we should provide infrastructure for +that. + +- support for reading and writing hirachical bookmarks for document +that support that + +- some document formats, such as dvi, allow to READ the position of +characters, which allows for full-text search, copying of text, +etc. Other formats, such djvu allow also WRITING of such +information. The idea is that you produce a djvu file by scanning a +certain document, and than later add the position and +character-information by using e.g. OCR software. We should at least +have support for that in the API, even if we don't implement an OCR +interface right now. + +- dvi has the special feature that it supports source information and +uses that to implement inverse and forward search functionality. We +must somehow make sure that even these unusual features fit well into +our framework without too much of an effort. + +* kmultipage + +- kviewshell should provide a print dialog (with the extra option that +certain document formats can insert their own configuration pages). I +find the print dialog of Acroread 6.0 in OSX very well done. Then we +should have a default printing implementation that uses KPrinter, etc, +to print the pages (this will be used e.g. by kfax), and we would need +infrastructure for implementation which have their own way of +producing PS file (such as dvi, where dvips is the preferred method to +print). + +This may rise some important questions in documentRenderer API. For +printing we would need a SYNCHRONOUS way of rendering pages to a +qpainter object. + + + +DOCUMENTATION +============= + +If we want developers of other applications to move over to +kviewshell, we definitvely need a well-documented API, and some +applications that demonstrate how everything works. In particular, we +should have + +* lots of documentation, including API documentation and a tutorial on +"How to implement a kmultipage" + +* an exteremely well-defined reference implementation for a trivial +file format, either one that is actually useful (kfax, perhaps), or +one that serves only demonstration purposes and that goes into the +kdesdk module + + + + + +KVIEWSHELL APPLICATION +====================== + +* improve command line interface. The somewhat antique method of +giving the library name in the command line should be replaced by a +more modern method where kviewshell uses some kde wizardy +(e.g. 'services') so that installed kmultipage implementations can +announce what mime type they are supposed to handle, and kvieshell +then loads them automatically + + diff --git a/kviewshell/anchor.h b/kviewshell/anchor.h new file mode 100644 index 00000000..f3faf13e --- /dev/null +++ b/kviewshell/anchor.h @@ -0,0 +1,61 @@ +// -*- C++ -*- +// +// anchor.h +// +// Part of KVIEWSHELL - A framework for multipage text/gfx viewers +// +// (C) 2004-2005 Stefan Kebekus +// Distributed under the GPL + + +#ifndef ANCHOR_H +#define ANCHOR_H + +#include "length.h" +#include "pageNumber.h" + + +/** \brief Page number and vertical position in physical coordinates + +This very simple class contains a page number and a vertical position +in physical coordiantes. The vertical position is given by the +distance from the top of the page. Anchors are completely independent +of documents, there is no need for a document to exists that contains +the given page, nor does the page number need to be valid. + +@author Stefan Kebekus +@version 1.0 0 +*/ + +class Anchor { + public: + /** \brief Constructs an anchor that points to an invalid page */ + Anchor() {page = 0;} + + /** \brief Constructs an snchor that points to a given position on a + given page + + The class contains no code to make sure in any way that the page + number pg exists, and that page pg, if it exists, is taller than + distance_from_top + + @param pg number of the page + @param _distance_from_top distance from the top of the page + */ + Anchor(const PageNumber& pg, const Length& _distance_from_top): page(pg), distance_from_top(_distance_from_top) {} + + /** \brief quick validity check for anchors + + @returns true if the page number is valid, and 0mm <= distance_from_top <= 2m + */ + bool isValid() const {return page.isValid() && (0.0 <= distance_from_top.getLength_in_mm()) && (distance_from_top.getLength_in_mm() <= 2000.0);} + + /** \brief Page number that this anchor point to */ + PageNumber page; + + /** \brief Distance from the top of the page in inch */ + Length distance_from_top; +}; + + +#endif diff --git a/kviewshell/bookmark.h b/kviewshell/bookmark.h new file mode 100644 index 00000000..1df245fb --- /dev/null +++ b/kviewshell/bookmark.h @@ -0,0 +1,107 @@ +// -*- C++ -*- +/*************************************************************************** + * Copyright (C) 2005 by Stefan Kebekus * + * kebekus@kde.org * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _bookmarks_h_ +#define _bookmarks_h_ + +#include "anchor.h" + +#include +#include + + +/** \brief Bookmark + +This class represents a bookmark. It contains the bookmark text, an +anchor that represents the target of the bookmark, and a list of +subordinate bookmarks. + +@warning The list of subordinate bookmarks owns its entries, +i.e. autoDelete() is set to true. Thus, extra care must be taken when +copying bookmarks. Otherwise, a segfault could result. +*/ + +class Bookmark { + public: + /** Default constructor. + + Constructs an invalid bookmark with an empty bookMarkText and an + invalid position + */ + Bookmark() {subordinateBookmarks.setAutoDelete(true);} + + /** Constructs a bookmark with given a text and anchor + + @param text name of the bookmark, as it appears in the GUI + + @param pos an Anchor that specifies the position of the bookmark + */ + Bookmark(const QString &text, const Anchor &pos) { + Bookmark(); + bookmarkText = text; + position = pos; + } + + /** Convenience funtion, behaves as Bookmark(const QString &text, const Anchor &pos) + + @param text name of the bookmark, as it appears in the GUI + + @param page number of the where the bookmark lives. Recall the + convention that '0' is an invalid page, the first page is '1' + + @param distance_from_top position on the page, distance from + top of the page + */ + Bookmark(const QString& text, const PageNumber& page, const Length& distance_from_top) { + Bookmark(); + bookmarkText = text; + position.page = page; + position.distance_from_top = distance_from_top; + } + + /** Label of the bookmark as it will appear in the GUI to the user, + such as "Section 1", "Theorem 12", etc. */ + QString bookmarkText; + + /** The anchor that specifies the position of the bookmark */ + Anchor position; + + /** List of subordinate bookmarks + + Bookmarks generally come in trees, and some bookmarks can have + subordinate bookmarks. For instance, the bookmark that + respresents a chapter heading would have headings of subsections + as subordinate bookmarks. These are contained in this list. + + @warning This list owns its entries, e.g. autoDelete() is set to + true. Thus, extra care must be taken when copying this + list. Otherwise, a segfault could result. + */ + QPtrList subordinateBookmarks; + + // Returns true if the bookmark is valid, false otherwise. A + // bookmark is valid if the text is not empty and the anchor is + // valid. + bool isValid() const {return (!bookmarkText.isEmpty()) && position.isValid();} +}; + + +#endif diff --git a/kviewshell/documentPageCache.cpp b/kviewshell/documentPageCache.cpp new file mode 100644 index 00000000..2c9a46a5 --- /dev/null +++ b/kviewshell/documentPageCache.cpp @@ -0,0 +1,337 @@ +// +// Class: documentPageCache +// +// Cache that holds a number of pages in a document. +// Part of KDVI- A previewer for TeX DVI files. +// +// (C) 2004 Stefan Kebekus. Distributed under the GPL. + +#include + +#include +#include + +#include "documentPageCache.h" +#include "documentRenderer.h" +#include "kvsprefs.h" +#include "renderedDocumentPagePixmap.h" + + +//#define documentPageCache_DEBUG + + +DocumentPageCache::DocumentPageCache() + : maxMemory(2*16777216), LRUCache(maxMemory, 200) +{ + LRUCache.setAutoDelete(true); + + resolutionInDPI = 0.0; + Length w,h; + w.setLength_in_mm(200); + h.setLength_in_mm(300); + userPreferredSize.setPageSize(w,h); + useDocumentSpecifiedSize = true; +} + + +DocumentPageCache::~DocumentPageCache() +{ +} + + +void DocumentPageCache::setRenderer(DocumentRenderer *_renderer) +{ + clear(); + renderer = _renderer; +} + + +SimplePageSize DocumentPageCache::sizeOfPage(const PageNumber& page) const +{ + // Paranoid safety checks + if (!page.isValid()) { + kdError(1223) << "DocumentPageCache::sizeOfPage( " << page << ") called with invalid page number." << endl; + return SimplePageSize(); + } + if (renderer.isNull()) { + kdError(1223) << "DocumentPageCache::sizeOfPage( " << page << ") called when no renderer was set." << endl; + return SimplePageSize(); + } + + SimplePageSize s = renderer->sizeOfPage(page); + if (!useDocumentSpecifiedSize) + s = userPreferredSize; + + if (!s.isValid()) + { + // If the size is invalid use the size of the first Page in the document + // as an estimate. + s = renderer->sizeOfPage(1); + if (!s.isValid()) + s = userPreferredSize; + } + + return s; +} + +void DocumentPageCache::setResolution(double res) +{ + resolutionInDPI = res; +} + +QSize DocumentPageCache::sizeOfPageInPixel(const PageNumber& pg) const +{ + // Paranoid safety checks + if (renderer.isNull()) { + kdError(1223) << "DocumentPageCache::sizeOfPageInPixel( " << pg << " ) called but no renderer was set" << endl; + return QSize(); + } + if (!pg.isValid()) { + kdError(1223) << "DocumentPageCache::sizeOfPageInPixel( " << pg << " ) called with invalid argument" << endl; + return QSize(); + } + + SimplePageSize ps = sizeOfPage(pg); + if (ps.isValid()) + return ps.sizeInPixel(resolutionInDPI); + return userPreferredSize.sizeInPixel(resolutionInDPI); +} + + +bool DocumentPageCache::isPageCached(const PageNumber& pageNumber, const QSize& size) +{ + // Paranoid checks + if (renderer.isNull()) { + kdError(1223) << "DocumentPageCache::isPageCached(..) called but no renderer was set" << endl; + return false; + } + if (!pageNumber.isValid()) { + kdError(1223) << "DocumentPageCache::isPageCached( " << pageNumber << " ) called, with invalid argument." << endl; + return false; + } + if (renderer->totalPages() < pageNumber) { + kdError(1223) << "DocumentPageCache::isPageCached( " << pageNumber + << " ) called but document contains only " << renderer->totalPages() << " pages." << endl; + return false; + } + + QString key = createKey(pageNumber, size); + + // Check if the page that we are looking for is in the cache. + // We are not accessing the page, so we don't want it to be moved into the front. + RenderedDocumentPagePixmap* page = LRUCache.find(key, false); + + if (page) + return true; + else + return false; +} + +QString DocumentPageCache::createKey(const PageNumber& pageNumber, const QSize& size) +{ + QString key; + + key = QString::number(pageNumber) + ":" + + QString::number(size.width()) + ":" + QString::number(size.height()); + + return key; +} + +QString DocumentPageCache::createKey(const PageNumber& pageNumber) +{ + QSize pageSize = sizeOfPageInPixel(pageNumber); + + QString key; + + key = QString::number(pageNumber) + ":" + + QString::number(pageSize.width()) + ":" + QString::number(pageSize.height()); + + return key; +} + +bool DocumentPageCache::isPageCached(const PageNumber& pageNumber) +{ + // Paranoid checks + if (renderer.isNull()) { + kdError(1223) << "DocumentPageCache::isPageCached(..) called but no renderer was set" << endl; + return false; + } + if (!pageNumber.isValid()) { + kdError(1223) << "DocumentPageCache::isPageCached( " << pageNumber << " ) called, with invalid argument." << endl; + return false; + } + if (renderer->totalPages() < pageNumber) { + kdError(1223) << "DocumentPageCache::isPageCached( " << pageNumber + << " ) called but document contains only " << renderer->totalPages() << " pages." << endl; + return false; + } + + return isPageCached(pageNumber, sizeOfPageInPixel(pageNumber)); +} + +RenderedDocumentPagePixmap* DocumentPageCache::getPage(const PageNumber& pageNr) +{ +#ifdef DocumentPageCache_DEBUG + kdDebug(1223) << "DocumentPageCache::getPage( pageNr=" << pageNr << " )" << endl; +#endif + + // Paranoid checks + if (renderer.isNull()) { + kdError(1223) << "DocumentPageCache::getPage(..) called but no renderer was set" << endl; + return 0; + } + if (!pageNr.isValid()) { + kdError(1223) << "DocumentPageCache::getPage( " << pageNr << " ) called, with invalid argument." << endl; + return 0; + } + if (renderer->totalPages() < pageNr) { + kdError(1223) << "DocumentPageCache::getPage( " << pageNr << " ) called but document contains only " << renderer->totalPages() << " pages." << endl; + return 0; + } + + // First check if the page that we are looking for is in the cache + RenderedDocumentPagePixmap* page; + page = LRUCache.find(createKey(pageNr)); + + if (page) + return page; + + // The page was not found in the cache, so we have to make a new + // page and add this to the cache. + page = createDocumentPagePixmap(); + + // If that failed, issue an error message and quit. + if (page == 0) { + kdError(1223) << "DocumentPageCache::getPage(..) cannot allocate DocumentPage structure" << endl; + return 0; + } + + // Now 'page' contains a point to a page structure that we can + // use. Add the page to the cache, and apply the renderer to the page. + page->setPageNumber(pageNr); + if (!renderer.isNull()) + { + if (resolutionInDPI > 0.0) + { + page->resize(sizeOfPageInPixel(pageNr)); + QApplication::setOverrideCursor( waitCursor ); + renderer->drawPage(resolutionInDPI, page); + QApplication::restoreOverrideCursor(); + + // We always set the cache capacity to be at least n times the cost of the page we want to insert. + // Where n is the number of pages that can be visible at the same time at very high zoomlevels. + // n depends on the layout mode. + // If these pages are not all in the cache, scrolling the view becomes very slow, because for each + // paint event the pages need to be rerendered. + // We set n for each viewmode differently so that the user is able to reduce memory consuption by + // switching to a simpler viewmode like Single Page. + int n = 4; + switch (KVSPrefs::viewMode()) + { + case KVSPrefs::EnumViewMode::SinglePage: + n = 1; + break; + case KVSPrefs::EnumViewMode::Continuous: + n = 2; + break; + default: + n = 4; + } + LRUCache.setMaxCost(QMAX(page->memory() * n, maxMemory)); + + if (!LRUCache.insert(createKey(pageNr), page, page->memory())) + { + kdError() << "DocumentPageCache::getPage(): inserting pagestructure into the cache failed.\n This should never happen. If you see this message, something is very wrong." << endl; + } + } + else + kdError(1223) << "DocumentPageCache::getPage() called, but no resolution or negative resolution was set" << endl; + } + + return page; +} + + +RenderedDocumentPagePixmap* DocumentPageCache::createDocumentPagePixmap() const +{ + return new RenderedDocumentPagePixmap(); +} + + +void DocumentPageCache::clear() +{ + LRUCache.clear(); +} + + +void DocumentPageCache::setUserPreferredSize(const SimplePageSize& s) +{ + bool sizeChanged = !userPreferredSize.isNearlyEqual(s); + userPreferredSize = s; + + if (sizeChanged) + emit(paperSizeChanged()); +} + + +void DocumentPageCache::setUseDocumentSpecifiedSize(bool b) +{ + bool valChanged = (useDocumentSpecifiedSize == b); + + useDocumentSpecifiedSize = b; + if (valChanged) + emit(paperSizeChanged()); +} + + +QPixmap DocumentPageCache::createThumbnail(const PageNumber& pageNr, int width) +{ + // Paranoid checks + if (renderer.isNull()) { + kdError(1223) << "DocumentPageCache::createThumbnail(..) called but no renderer was set" << endl; + thumbnailPage.resize(0,0); + return thumbnailPage; + } + if (renderer->totalPages() < pageNr) { + kdError(1223) << "DocumentPageCache::createThumbnail( " << pageNr << ", width ) called but document contains only " << renderer->totalPages() << " pages." << endl; + thumbnailPage.resize(0,0); + return thumbnailPage; + } + if (!pageNr.isValid()) { + kdError(1223) << "DocumentPageCache::createThumbnail(..) called for page with invalid page specification" << endl; + thumbnailPage.resize(0,0); + return thumbnailPage; + } + if (!sizeOfPage().isValid()) { + kdError(1223) << "DocumentPageCache::createThumbnail(..) called for page with invalid size" << endl; + thumbnailPage.resize(0,0); + return thumbnailPage; + } + + thumbnailPage.setPageNumber(pageNr); + thumbnailPage.resize(width, (int)(width/sizeOfPage(pageNr).aspectRatio() + 0.5 ) ); + renderer->drawThumbnail((double)(width)/sizeOfPage(pageNr).width().getLength_in_inch(), &thumbnailPage); + + if (KVSPrefs::changeColors() && KVSPrefs::renderMode() != KVSPrefs::EnumRenderMode::Paper) + { + return thumbnailPage.accessiblePixmap(); + } + else + { + return thumbnailPage; + } +} + +void DocumentPageCache::deselectText() +{ + userSelection.clear(); + emit textSelected(false); +} + +void DocumentPageCache::selectText(const TextSelection& selection) +{ + userSelection = selection; + emit textSelected(!userSelection.isEmpty()); +} + +#include "documentPageCache.moc" diff --git a/kviewshell/documentPageCache.h b/kviewshell/documentPageCache.h new file mode 100644 index 00000000..c885123a --- /dev/null +++ b/kviewshell/documentPageCache.h @@ -0,0 +1,143 @@ +// -*- C++ -*- +// +// Class: documentPageCache +// +// Cache that holds a number of pages in a document. +// Part of KDVI- A previewer for TeX DVI files. +// +// (C) 2004 Stefan Kebekus. Distributed under the GPL. + +#ifndef _documentpagecache_h_ +#define _documentpagecache_h_ + +#include "renderedDocumentPagePixmap.h" +#include "pageNumber.h" +#include "pageSize.h" +#include "selection.h" + +#include +#include +#include + +class DocumentRenderer; +class QPixmap; +class RenderedDocumentPage; + + +class DocumentPageCache: public QObject +{ + Q_OBJECT + + public: + DocumentPageCache(); + virtual ~DocumentPageCache(); + + /** This method is used to name the DocumentRenderer, that the + documentPageCache uses to render the page. The renderer can be + used any time (e.g., triggered by an internal timer event), and + must not be deleted before either the DocumentRenderer is + deleted, or another renderer has been set. */ + void setRenderer(DocumentRenderer *_renderer); + + void setResolution(double res); + double getResolution() const {return resolutionInDPI;} + + const TextSelection& selectedText() const { return userSelection; } + + void deselectText(); + void selectText(const TextSelection& selection); + + /** Returns the size of page 'page'. If the document does not + specify a size (which happens, e.g., for some DVI-files), then + the userPreferredSize is returned. */ + SimplePageSize sizeOfPage(const PageNumber& page = 1) const; + + /** Returns the size of page 'page', in pixels, using the resolution + set with setResolution(). If the document does not specify a + size (which happens, e.g., for some DVI-files), the + userPreferredSize is used. */ + QSize sizeOfPageInPixel(const PageNumber& page) const; + + /** Returns a pointer to a documentPage structure, or 0, if the + documentPage could not be generated for some reason (e.g., + because no renderer has been set). */ + RenderedDocumentPagePixmap* getPage(const PageNumber& pageNr); + + /** Checks if the given page is already in the cache. */ + bool isPageCached(const PageNumber& pageNumber, const QSize& size); + + /** Checks if the given page is already in the cache. Here we don't care about the size + of the page. */ + bool isPageCached(const PageNumber& pageNumber); + + /** Returns a "width" pixels width thumbnail of the given page. This + method returns an empty QPixmap if one of the arguments is + invalid, or if the page cannot be rendered for any reason. */ + QPixmap createThumbnail(const PageNumber& pageNr, int width); + + signals: + /** This signal is emitted when setUserPreferredSize() or + setUseUserPreferredSize() is called, and the page size + changes accordingly. */ + void paperSizeChanged(); + + /** This signal is emitted when the text selection of the current + document changes. The argument is false if no text is selected, true + otherwise. */ + void textSelected(bool); + + public slots: + /** Clears the contents of the cache. */ + void clear(); + + /** Sets the userPreferredSize, which is explained below */ + void setUserPreferredSize(const SimplePageSize& t); + void setUseDocumentSpecifiedSize(bool); + + protected: + /** This function creates new RenderedDocumentPagePixmap objects. + If a multipage implementation needs additional functionality overwrite + this function to create objects of a suitable subclass of RenderedDocumentPagePixmap. + */ + virtual RenderedDocumentPagePixmap* createDocumentPagePixmap() const; + + /** Creates the hashing key for the cache. */ + QString createKey(const PageNumber& pageNumber, const QSize& size); + + /** Creates the hashing function. The size of the page is calculated + based on the current resolution. */ + QString createKey(const PageNumber& pageNumber); + + QGuardedPtr renderer; + + private: + /** The maximum of memory used by the cache. (32MB) + TODO: make this configurable, or detact an appropriate value at startup. */ + Q_UINT32 maxMemory; + + /** This field contains resolution of the display device. In + principle. In fact, kviewshell implements zooming by calling the + setResolution()-method with values that are not exactly the + resolution of the display, but multiplied with the zoom + factor. Bottom line: the documentRenderer should act as if this + field indeed contains resolution of the display device. Whene a + documentRenderer is constructed, this field is set to the actual + resolution to give a reasonable default value. */ + double resolutionInDPI; + + SimplePageSize userPreferredSize; + bool useDocumentSpecifiedSize; + + TextSelection userSelection; + + + /** This list holds the cache. */ + QCache LRUCache; + + /** This is a dummy documentPage structure which is used internally + by the 'createThumbnail' method. */ + RenderedDocumentPagePixmap thumbnailPage; +}; + + +#endif diff --git a/kviewshell/documentRenderer.cpp b/kviewshell/documentRenderer.cpp new file mode 100644 index 00000000..f9837fa2 --- /dev/null +++ b/kviewshell/documentRenderer.cpp @@ -0,0 +1,133 @@ +// +// Class: documentRenderer +// +// Abstract Widget for displaying document types +// Needs to be implemented from the actual parts +// using kviewshell +// Part of KViewshell - A generic interface for document viewers. +// +// (C) 2004-2005 Wilfried Huss, Stefan Kebekus. Distributed under the GPL. + +#include + +#include + +#include "documentRenderer.h" +#include "renderedDocumentPage.h" + + +DocumentRenderer::DocumentRenderer(QWidget* par) + : mutex(true), parentWidget(par), accessibilityBackground(false), + accessibilityBackgroundColor(QColor(255,255,255)) +{ + numPages = 0; + _isModified = false; +} + + +DocumentRenderer::~DocumentRenderer() +{ + // Wait for all access to this DocumentRenderer to finish + QMutexLocker lock(&mutex); + + clear(); +} + + +void DocumentRenderer::clear() +{ + // Wait for all access to this DocumentRenderer to finish + QMutexLocker lock(&mutex); + + numPages = 0; + pageSizes.clear(); + anchorList.clear(); + bookmarks.clear(); + _isModified = false; +} + +void DocumentRenderer::setAccessibleBackground(bool accessibleMode, const QColor& background) +{ + // Wait for all access to this DocumentRenderer to finish + QMutexLocker lock(&mutex); + + accessibilityBackground = accessibleMode; + accessibilityBackgroundColor = background; +} + +SimplePageSize DocumentRenderer::sizeOfPage(const PageNumber& page) +{ + // Wait for all access to this DocumentRenderer to finish + QMutexLocker locker(&mutex); + + if (!page.isValid()) + return SimplePageSize(); + if (page > totalPages()) + return SimplePageSize(); + if (page > pageSizes.size()) + return SimplePageSize(); + + return pageSizes[page-1]; +} + + +void DocumentRenderer::drawThumbnail(double resolution, RenderedDocumentPage* page) +{ + // Wait for all access to this DocumentRenderer to finish + QMutexLocker locker(&mutex); + + drawPage(resolution, page); +} + +void DocumentRenderer::getText(RenderedDocumentPage* page) +{ + // We are only interrested in the textual data, so we can use a dummy value for the resolution. + drawPage(100.0, page); +} + +bool DocumentRenderer::isValidFile(const QString&) const +{ + return true; +} + + +Anchor DocumentRenderer::parseReference(const QString &reference) +{ + // Wait for all access to this documentRenderer to finish + QMutexLocker locker(&mutex); + + if (isEmpty()) + return Anchor(); + + // If the reference is a number, which we'll interpret as a + // page number. + bool ok; + int page = reference.toInt(&ok); + if (ok == true) + { + if (page < 1) + page = 1; + if (page > totalPages()) + page = totalPages(); + + return Anchor(page, Length()); + } + + // If the reference is not a number, return an empty anchor. + return Anchor(); +} + +Anchor DocumentRenderer::findAnchor(const QString &locallink) +{ + // Wait for all access to this DocumentRenderer to finish + QMutexLocker locker(&mutex); + + QMap::Iterator it = anchorList.find(locallink); + if (it != anchorList.end()) + return *it; + else + return Anchor(); +} + + +#include "documentRenderer.moc" diff --git a/kviewshell/documentRenderer.h b/kviewshell/documentRenderer.h new file mode 100644 index 00000000..a8a87410 --- /dev/null +++ b/kviewshell/documentRenderer.h @@ -0,0 +1,478 @@ +// -*- C++ -*- +// +// Class: documentRenderer +// +// Abstract class for rendering document types. Needs to be +// subclassed by the actual parts using kviewshell. Part of +// KViewshell - A generic interface for document viewers. +// +// (C) 2004-2005 Wilfried Huss, Stefan Kebekus. Distributed under the GPL. +// + +#ifndef DOCUMENTRENDERER_H +#define DOCUMENTRENDERER_H + +#include "bookmark.h" +#include "pageNumber.h" +#include "pageSize.h" + +#include +#include +#include +#include +#include + +class Anchor; +class KURL; +class RenderedDocumentPage; + + +/** \brief loads and renders documents + +This abstract class is one of the two core classes that must be +implemented by all authors who write plugins for the kviewshell +programm. It is responsible for document loading and rendering. As a +minimum, the setFile() and drawPage() must be reimplemented. + +This documentation mentiones only the methods and members that are +important for authors of plugins. For full documentation, consult the +header file documentRenderer.h. + +@warning Future versions of kviewshell will use threading to keep the +GUI responsive while pages are rendered. As a result, IT IS ABSOLUTELY +NECESSARY that your implementation is THREAD-SAFE, if not, this can +result in infrequent and very hard-to-find crashes of your +programm. Use the member mutex to make your implemetation +thread-safe. + +@author Wilfried Huss, Stefan Kebekus +*/ + +class DocumentRenderer : public QObject +{ + Q_OBJECT + +public: + /** \brief default constructor */ + DocumentRenderer(QWidget* parent); + + virtual ~DocumentRenderer(); + + /** \brief loading of files + + This is a purely virtual method that must be re-implemented. It is + called when a file should be loaded. The implementation must then do + the following + + - initialize internal data structures so the document pointed to by + 'fname' can be rendered quickly. It is not necessary actually load + the file; if the implementation choses to load only parts of a large + file and leave the rest on the disc, this is perfectly fine. + + - return 'true' on success and 'false' on failure. Before 'false' is + returned, the method clear() should be called + + When the method returns 'true', it is expected that + + - the member _isModified is set to 'false' + + - the member 'numPages' is either set to 0 if the document is empty, + or else to the number of page in the document + + - the vector pageSizes *must* either be empty (e.g. if your file + format does not specify a page size), or must be of size + totalPages(), and contain the sizes of all pages in the document. + + - the anchorList is filled with data; it is perfectly fine to leave + the anchorList empty, if your file format does not support anchors, + or if your document doesn't contain any. + + - the list 'bookmarks' is filled with data; it is perfectly fine to + leave this list empty, if your file format does not support + bookmarks or if your document doesn't contain any. + + - the method drawPage() works + + @note It is perfectly possible that setFile() is called several + times in a row, with the same or with different filenames. + + @warning The signal documentIsChanged() must not be emitted while + the method runs. + + @warning Future versions of kviewshell will use threading to keep + the GUI responsive while pages are rendered. As a result, IT IS + ABSOLUTELY NECESSARY that your implementation is THREAD-SAFE, if + not, this can result in infrequent and very hard-to-find crashes of + your programm. Use the member mutex to make your implemetation + thread-safe. + + @param fname name of the file to open. It is not guaranteed that the + file exists, that it is a file, or that it is readable. + + @param base original URL of the file that was opened. + + If the program that uses this documentRenderer was asked to open + http://www.kde.org/test.dvi.bz2, then the program would download the + file to a temporary file and decompress it, generating + e.g. /tmp/tmp.dvi. In that case, base would be + http://www.kde.org/test.dvi.bz2, and fname=/tmp/tmp.dvi. The base + can be used by the documentRenderer to handle relative URLs that + might be contained in a file. Otherwise, it can safely be ignored. + + @returns 'true' on success and 'false' on failure. Even after this + method returns 'false' the class must act reasonably, i.e. by + clear()ing the document + */ + virtual bool setFile(const QString &fname, const KURL &base) = 0; + + + /** \brief clearing the document renderer + + This method clears the renderer, so that it represents an empty + document. The standard implementation doe the following: + + - sets 'numPages' to zero + + - clears the pageSizes and the anchorList + + - sets _isModified to 'false' + + Most authors of kviewshell-plugins will probably want to + re-implement this to clear internal data structures of their + implementations. + + @warning Future versions of kviewshell will use threading to keep + the GUI responsive while pages are rendered. As a result, IT IS + ABSOLUTELY NECESSARY that your implementation is THREAD-SAFE, if + not, this can result in infrequent and very hard-to-find crashes of + your programm. Use the member mutex to make your implemetation + thread-safe. + */ + virtual void clear(); + + + /* Returns true if the current document contains 0 pages. */ + bool isEmpty() const {return numPages == 0;} + + /* Tells if the document was modified after is was loaded. */ + bool isModified() const {return _isModified; } + + /* Returns the number of pages in the document. This method can well + return 0, e.g. if no document has been loaded yet, or if the + current document is empty. */ + PageNumber totalPages() const {return numPages; } + + QPtrList getBookmarks() const { return bookmarks; } + + /* Returns the size of page 'page'. If the document is empty, if the + page specified is not a page of the document or if the document + does not specify a size (which happens, e.g., for some + DVI-files), then an invalid page size is returned. */ + SimplePageSize sizeOfPage(const PageNumber& page); + + /* Returns true if the document specifies page sizes, and false + otherwise. NOTE: the information returned by this method is not + always 100% reliable. Although unlikely, it is theoretically + possible that this method returns 'true', but still some of the + sizes returned by sizeOfPage() are invalid. */ + bool hasSpecifiedPageSizes() const {return !pageSizes.isEmpty();} + + /** rendering of documents + + This purely virtual method is the most important method in the + DocumentRenderer class. It must be re-implemented by authors who + want to write plugins for the kviewshell program. The purpose of + this method is to render a graphical representation into a + documentPage structure. More specifically, the implementation needs + to + + - call the documentPage::clear() on *page + + and the do all of the following, in no particular order + + - obtain the pointer to the QPaintDevice from the documentPage using + the documentPage::getPaintDevice() method and draw a graphical + representation of the page number page->getPageNumber() into the + QPaintDevice, using the given resolution. If the member + accessibilityBackground is true, the accessibilityBackgroundColor + should be used for a background color, if possible. Otherwise, + white should be used, if possible. If you need to compute the size + of the page in pixel, do this as follows: + @code + SimplePageSize psize = pageSizes[page->getPageNumber() - 1]; + if (psize.isValid()) { + int width_in_pixel = resolution * psize.width().getLength_in_inch(); + int height_in_pixel = resolution * psize.height().getLength_in_inch(); + + <...> + } + @endcode + Don't use page->height() or page->width() to calculate the sizes + ---KViewShell uses transformations e.g. to rotate the page, and + sets the argument 'resolution' accordingly; these changes are not + reflected in page->height() and page->width(). Similar problems + occur if KViewShell required a shrunken version of the page, + e.g. to print multiple pages on one sheet of paper. + + - if your document contains hyperlinks, fill the + documentPage::hyperLinkList with HyperLinks, using pixel + coordinates for the coordinates in the Hyperlink::box member of + the Hyperlink. The Hyperlink::baseline member of the Hyperlink + can be ignored. The linkText member of the Hyperlink should either + be an absolute URL ("http://www.kde.org"), or be of the form + "#anch", where the string "anch" is contained in the anchorList. + + - if your plugin supports full-text information, fill + documentPage::textLinkList with HyperLinks, using pixel + coordinates for the coordinates in the Hyperlink::box and + Hyperlink::baseline members of the Hyperlink. The entries in the + documentPage::textLinkList should have a natural ordering, "first + text first" (left-to-right, up-to-down for western languages, + right-to-left for hebrew, etc.). This is important so that text + selection with the mouse works properly, and only continuous + blocks of text can be selected. + + @note This method is often called in a paintEvent, so that care must + be taken to return as soon as possible. No user interaction should + be done during the execution. + + @note If your plugin supports full-text information, you probably + want to re-implement the method supportsTextSearch() below. + + @warning As mentioned above, it may be tempting to compute the image + size in pixel, using page->height() and page->width(). DON'T DO + THAT. KViewShell uses transformations e.g. to rotate the page, and + sets the argument 'resolution' accordingly; these changes are not + reflected in page->height() and page->width(). Similar problems + occur if KViewShell required a shrunken version of the page, e.g. to + print multiple pages on one sheet of paper. + + @warning The signal documentIsChanged() must not be emitted while the + method runs. + + @warning Future versions of kviewshell will use threading to keep + the GUI responsive while pages are rendered. As a result, IT IS + ABSOLUTELY NECESSARY that your implementation is THREAD-SAFE, if + not, this can result in infrequent and very hard-to-find crashes of + your programm. Use the member mutex to make your implemetation + thread-safe. + + @param resolution this argument contains the resolution of the + display device. In principle. In fact, kviewshell implements zooming + by using values that are not exactly the resolution of the display, + but multiplied with the zoom factor. Bottom line: the + DocumentRenderer should act as if this field indeed contains + resolution of the display device. + + @param page pointer to a documentPage structure that this method + rendered into. + + */ + virtual void drawPage(double resolution, RenderedDocumentPage* page) = 0; + + /** rendering of documents at thumbnail size + + This method is used to draw thumbnails. The standared + implementations just calls 'drawPage' to do the job. Reimplement + this if the used fileformat has embedded thumbnails. + + @warning Future versions of kviewshell will use threading to keep + the GUI responsive while pages are rendered. As a result, IT IS + ABSOLUTELY NECESSARY that your implementation is THREAD-SAFE, if + not, this can result in infrequent and very hard-to-find crashes of + your programm. Use the member mutex to make your implemetation + thread-safe. + */ + virtual void drawThumbnail(double resolution, RenderedDocumentPage* page); + + /** quick extraction of text information + + This method returns the textinformation of the current page if available. + It is only called when the page pixmap is not of interest, so it is possible + to implement it much more efficiently then the drawPage() method. + + The default implementation just calls drawPage(). + + @warning Future versions of kviewshell will use threading to keep + the GUI responsive while pages are rendered. As a result, IT IS + ABSOLUTELY NECESSARY that your implementation is THREAD-SAFE, if + not, this can result in infrequent and very hard-to-find crashes of + your programm. Use the member mutex to make your implemetation + thread-safe. + + @param page pointer to a documentPage structure that this method rendered into. + */ + virtual void getText(RenderedDocumentPage* page); + + /** Flag to indicate if full text is supported + + If your implementation of the drawPage() method supports full-text + information and writes to the documentPage::textLinkList, this + method should be re-implemented to return 'true'. The text-search + and text-selection utilities will then be enabled in the GUI. + + The default implementation returns 'false'. + */ + virtual bool supportsTextSearch() const {return false;} + + /* This method will try to parse the reference part of the DVI + file's URL, (either a number, which is supposed to be a page + number, or src:(line)(filename)) and see if a corresponding + section of the DVI file can be found. If so, it returns an anchor + to that section. If not, it returns an invalid anchor. + */ + virtual Anchor parseReference(const QString &reference); + + /* Looks up a anchor in the "anchorList". Returns the anchor found, + or an invalid anchor otherwise. + */ + Anchor findAnchor(const QString &); + + /* Quick file validity check + + This method is used internally, to check if a file is valid before + it is re-loaded. This is used e.g. by kdvi: when a the user TeXes a + file, the file changes immediately. If the 'watch file' option is + set, kdvi is informed immediately. At that time, however, the TeX + typesetting program still writes to the dvi file, and reloading must + be postphoned till TeX finishes, and the dvi file becomes vaild. If + such considerations are not an issue for you, this method does not + need to be re-implemented. + + @warning Future versions of kviewshell will use threading to keep + the GUI responsive while pages are rendered. As a result, IT IS + ABSOLUTELY NECESSARY that your implementation is THREAD-SAFE, if + not, this can result in infrequent and very hard-to-find crashes of + your programm. Use the member mutex to make your implemetation + thread-safe. + + @param fileName name of the file that should be checked for validity + + @returns 'false' if the file 'fileName' is obviously invalid, and + true otherwise. The default implementation always returns + 'true'. + */ + virtual bool isValidFile(const QString& fileName) const; + + void setAccessibleBackground(bool accessibleMode, const QColor& background = QColor(255, 255, 255)); + +signals: + /** signals that the document is changed + + This signal can be emitted if the document or status of this class + changed internally so that all associated widgets should be + repainted. This could be emitted, e.g. if pages are removed from a + document, or if some preferences change that have some direct + influence on the way the document is rendered. + + When this signal is emitted, the whole GUI setup is re-computed, and + all widgets are re-drawn. This can take considerable time. + */ + void documentIsChanged(); + + + /** sets text in the statusbar + + This signal is emitted when the renderer needs to inform the user + via the status bar. Since the status bar is not always visible, and + since the duration that the message is shown is not quite specified, + this should not be used for important information. */ + void setStatusBarText( const QString& ); + +protected: + /** mutex used to make method thread-safe + + This is a recursive mutex that must be used to make the public + methods of this class thread-safe. Future versions of kviewshell + will use threading to keep the GUI responsive while pages are + rendered. As a result, IT IS ABSOLUTELY NECESSARY that your + implementation is THREAD-SAFE, if not, this can result in infrequent + and very hard-to-find crashes of your programm. + */ + QMutex mutex; + + + /** number of pages in the document + + This member is set by the implementations of the setFile() + method. It is set to zero by the constructor and in clear(). + + @warning Only the constructor and the methods setFile() and clear() + may write to this member. + */ + Q_UINT16 numPages; + + /** page sizes + + This vector contains the size of every page in the document. To + accomodate for file format that do not specify a page size, it is + explicitly allowed that this vector is empty, or that entries are + invalid page sizes. The values in this vector are set by the + setFile() method. + + @note if the document does not specify page sizes, this vector + should --for performance reasons-- be empty, and not set to a large + number of invalid page sizes. + + @warning Only the constructor and the methods setFile() and clear() + may write to this member. + */ + QValueVector pageSizes; + + /** bookmarks + + This (ordered!) list contains the bookmarks that are contained in + the document. The values in this vector are set by the setFile() + method, and cleared by the constructor and the clear() method. + + @warning Only the constructor and the methods setFile() and clear() + may write to this member. + */ + QPtrList bookmarks; + + /** map of anchors in a document. + + This map contains the anchors that are contained in the + document. The values in this map are set by the setFile() method, + and cleared by the constructor and the clear() method. + + @warning Only the constructor and the methods setFile() and clear() + may write to this member. + */ + QMap anchorList; + + /** pointer to the parent widget + + This pointer can be used by implementations e.g. to display error + messages. This pointer can well be zero. + */ + QGuardedPtr parentWidget; + + /** specifies if accessibilityBackgroundColor should be used + + If true, the drawPage() and drawThumbnail() methods should use + accessibilityBackgroundColor as the backgroundcolor of the + pages. + */ + bool accessibilityBackground; + + /** background color, to be used for visibly impaired users + + If accessibilityBackground is true, the drawPage() and + drawThumbnail() methods should use this color as the backgroundcolor + of the pages. + */ + QColor accessibilityBackgroundColor; + + /** Flag if document is modified + + This flag indicates if the document was modified after it was + loaded. It is set to 'false' in the constructor, in the clear() and + setFile() method. It can be set to 'true' be methods that modify the + document (e.g. the deletePages() method of the djvu implementation + of this class). + */ + bool _isModified; +}; + +#endif diff --git a/kviewshell/documentWidget.cpp b/kviewshell/documentWidget.cpp new file mode 100644 index 00000000..95b6f771 --- /dev/null +++ b/kviewshell/documentWidget.cpp @@ -0,0 +1,764 @@ +// +// Class: DocumentWidget +// +// Widget for displaying TeX DVI files. +// Part of KDVI- A previewer for TeX DVI files. +// +// (C) 2001 Stefan Kebekus +// Copyright (C) 2004-2005 Wilfried Huss +// Distributed under the GPL +// + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "documentWidget.h" +#include "pageView.h" +#include "documentPageCache.h" +#include "hyperlink.h" +#include "renderedDocumentPagePixmap.h" +#include "textBox.h" + +#include "kvsprefs.h" + +//#define DEBUG_DOCUMENTWIDGET + +const int DocumentWidget::bottom_right_corner[16] = + { 61, 71, 85, 95, + 71, 78, 89, 96, + 85, 89, 95, 98, + 95, 97, 98, 99 }; + +const int DocumentWidget::bottom_left_corner[16] = + { 95, 85, 71, 61, + 97, 89, 78, 71, + 98, 95, 89, 85, + 99, 98, 96, 95 }; + +const int DocumentWidget::shadow_strip[4] = + { 56, 67, 83, 94 }; + +QColor DocumentWidget::backgroundColorForCorners; + +namespace { + +/** Holds the icon used as a overlay on pages which are not drawn yet. */ +QPixmap* busyIcon = 0; + +/** Internal storages used in the shadow drawing routines in the + drawContents method.*/ +QPixmap* URShadow = 0; +QPixmap* BRShadow = 0; +QPixmap* BLShadow = 0; + +} // namespace anon + + +DocumentWidget::DocumentWidget(QWidget *parent, PageView *sv, DocumentPageCache *cache, const char *name ) + : QWidget( parent, name ), indexOfUnderlinedLink(-1) +{ + moveTool = true; + + selectionNeedsUpdating = false; + + // Variables used in animation. + animationCounter = 0; + timerIdent = 0; + documentCache = cache; + scrollView = sv; + + pixmapRequested = false; + + scrollGuide = -1; + + setMouseTracking(true); + setFocusPolicy(QWidget::ClickFocus); + + connect(&clearStatusBarTimer, SIGNAL(timeout()), this, SLOT(clearStatusBar())); + setBackgroundMode(Qt::NoBackground); + + if (!busyIcon) + { + busyIcon = new QPixmap(KGlobal::iconLoader()->loadIcon("gear", KIcon::NoGroup, KIcon::SizeMedium)); + + URShadow = new QPixmap(); + BRShadow = new QPixmap(); + BLShadow = new QPixmap(); + + URShadow->resize(4,4); + BRShadow->resize(4,4); + BLShadow->resize(4,4); + } +} + + +void DocumentWidget::setPageNumber(Q_UINT16 nr) +{ + pageNr = nr; + + selectionNeedsUpdating = true; + + // We have to reset this, because otherwise we might crash in the mouseMoveEvent + // After switching pages in SinglePageMode or OverviewMode. + indexOfUnderlinedLink = -1; + + // Resize Widget if the size of the new page is different than the size of the old page. + QSize _pageSize = documentCache->sizeOfPageInPixel(pageNr); + if (_pageSize != pageSize()) + { + setPageSize(_pageSize); + } + update(); +} + +QRect DocumentWidget::linkFlashRect() +{ + int width = pageSize().width()/(11 - animationCounter); + int height = pageSize().height()/(60 - 4 * animationCounter); + return QRect((pageSize().width()-width)/2, flashOffset - height/2, width, height); +} + +void DocumentWidget::timerEvent( QTimerEvent *e ) +{ + if (animationCounter == 0) { + killTimer(e->timerId()); + timerIdent = startTimer(50); // Proceed with the animation in 1/10s intervals + } + + animationCounter++; + + QRect flashRect = linkFlashRect(); + flashRect.addCoords(-1, -1, 1, 1); + + if (animationCounter >= 10) { + killTimer(e->timerId()); + timerIdent = 0; + animationCounter = 0; + } + + repaint(flashRect, false); +} + + +void DocumentWidget::flash(int fo) +{ + if (timerIdent != 0) + { + killTimer(timerIdent); + // Delete old flash rectangle + animationCounter = 10; + QRect flashRect = linkFlashRect(); + flashRect.addCoords(-1, -1, 1, 1); + repaint(flashRect, false); + } + animationCounter = 0; + flashOffset = fo; + timerIdent = startTimer(50); // Start the animation. The animation proceeds in 1/10s intervals +} + + +void DocumentWidget::paintEvent(QPaintEvent *e) +{ +#ifdef DEBUG_DOCUMENTWIDGET + kdDebug(1223) << "DocumentWidget::paintEvent() called" << endl; +#endif + + // Check if this widget is really visible to the user. If not, there + // is nothing to do. Remark: if we don't do this, then under QT + // 3.2.3 the following happens: when the user changes the zoom + // value, all those widgets are updated which the user has EVER + // seen, not just those that are visible at the moment. If the + // document contains several thousand pages, it is easily possible + // that this means that a few hundred of these are re-painted (which + // takes substantial time) although perhaps only three widgets are + // visible and *should* be updated. I believe this is some error in + // QT, but I am not positive about that ---Stefan Kebekus. + if (!isVisible()) + { + //kdDebug() << "widget of page " << pageNr << " is not visible. Abort rendering" << endl; + kapp->processEvents(); + return; + } + + QPainter p(this); + p.setClipRegion(e->region()); + + // Paint a black border around the widget + p.setRasterOp(Qt::CopyROP); + p.setBrush(NoBrush); + p.setPen(Qt::black); + QRect outlineRect = pageRect(); + outlineRect.addCoords(-1, -1, 1, 1); + p.drawRect(outlineRect); + + // Paint page shadow + QColor backgroundColor = colorGroup().mid(); + + // (Re-)generate the Pixmaps for the shadow corners, if necessary + if (backgroundColor != backgroundColorForCorners) + { + backgroundColorForCorners = backgroundColor; + QImage tmp(4, 4, 32); + for(int x=0; x<4; x++) + for(int y=0; y<4; y++) + tmp.setPixel(x, y, backgroundColor.light(bottom_right_corner[x+4*y]).rgb() ); + BRShadow->convertFromImage(tmp); + + for(int x=0; x<4; x++) + for(int y=0; y<4; y++) + tmp.setPixel(x, y, backgroundColor.light(bottom_left_corner[x+4*y]).rgb() ); + BLShadow->convertFromImage(tmp); + + URShadow->convertFromImage(tmp.mirror(true, true)); + } + + // Draw right and bottom shadows + for(int i=0; i<4; i++) + { + p.setPen(backgroundColor.light(shadow_strip[i])); + // Right shadow + p.drawLine(pageSize().width()+i+2, 8, pageSize().width()+i+2, pageSize().height()+2); + // Bottom shadow + p.drawLine(8, pageSize().height()+i+2, pageSize().width()+2, pageSize().height()+i+2); + } + // Draw shadow corners + p.drawPixmap(pageSize().width()+2, pageSize().height()+2, *BRShadow); + p.drawPixmap(4, pageSize().height()+2, *BLShadow); + p.drawPixmap(pageSize().width()+2, 4, *URShadow); + + // Draw corners + p.fillRect(0, pageSize().height()+2, 4, 4, backgroundColor); + p.fillRect(pageSize().width()+2, 0, 4, 4, backgroundColor); + + if (!documentCache->isPageCached(pageNr, pageSize())) + { + QRect destRect = e->rect().intersect(pageRect()); + if (KVSPrefs::changeColors() && KVSPrefs::renderMode() == KVSPrefs::EnumRenderMode::Paper) + p.fillRect(destRect, KVSPrefs::paperColor()); + else + p.fillRect(destRect, Qt::white); + + // Draw busy indicator. + // Im not really sure if this is a good idea. + // While it is nice to see an indication that something is happening for pages which + // take long to redraw, it gets quite annoing for fast redraws. + // TODO: Disable or find something less distractiong. + p.drawPixmap(10, 10, *busyIcon); + + if (!pixmapRequested) + { + // Request page pixmap. + pixmapRequested = true; + QTimer::singleShot(50, this, SLOT(delayedRequestPage())); + } + return; + } + + RenderedDocumentPagePixmap *pageData = documentCache->getPage(pageNr); + if (pageData == 0) { +#ifdef DEBUG_DOCUMENTWIDGET + kdDebug(1223) << "DocumentWidget::paintEvent: no documentPage generated" << endl; +#endif + return; + } + + QMemArray damagedRects = e->region().rects(); + for (unsigned int i = 0; i < damagedRects.count(); i++) + { + // Paint the page where it intersects with the damaged area. + QRect destRect = damagedRects[i].intersect(pageRect()); + + // The actual page starts at point (1,1) because of the outline. + // Therefore we need to shift the destination rectangle. + QRect pixmapRect = destRect; + pixmapRect.moveBy(-1,-1); + + if (KVSPrefs::changeColors() && KVSPrefs::renderMode() != KVSPrefs::EnumRenderMode::Paper) + { + // Paint widget contents with accessibility changes. + bitBlt ( this, destRect.topLeft(), &pageData->accessiblePixmap(), pixmapRect, CopyROP); + } + else + { + // Paint widget contents + bitBlt ( this, destRect.topLeft(), pageData, pixmapRect, CopyROP); + } + } + // Underline hyperlinks + if (KVSPrefs::underlineLinks() == KVSPrefs::EnumUnderlineLinks::Enabled || + KVSPrefs::underlineLinks() == KVSPrefs::EnumUnderlineLinks::OnlyOnHover) + { + int h = 2; // Height of line. + for(int i = 0; i < (int)pageData->hyperLinkList.size(); i++) + { + if (KVSPrefs::underlineLinks() == KVSPrefs::EnumUnderlineLinks::OnlyOnHover && + i != indexOfUnderlinedLink) + continue; + int x = pageData->hyperLinkList[i].box.left(); + int w = pageData->hyperLinkList[i].box.width(); + int y = pageData->hyperLinkList[i].baseline; + + QRect hyperLinkRect(x, y, w, h); + if (hyperLinkRect.intersects(e->rect())) + { +#ifdef DEBUG_DOCUMENTWIDGET + kdDebug(1223) << "Underline hyperlink \"" << pageData->hyperLinkList[i].linkText << "\"" << endl; +#endif + p.fillRect(x, y, w, h, KGlobalSettings::linkColor()); + } + } + } + + // Paint flashing frame, if appropriate + if (animationCounter > 0 && animationCounter < 10) + { + int gbChannel = 255 - (9-animationCounter)*28; + p.setPen(QPen(QColor(255, gbChannel, gbChannel), 3)); + p.drawRect(linkFlashRect()); + } + + // Mark selected text. + TextSelection selection = documentCache->selectedText(); + if ((selection.getPageNumber() != 0) && (selection.getPageNumber() == pageNr)) + { + if (selectionNeedsUpdating) + { + //The zoom value has changed, therefore we need to recalculate + //the selected region. + selectedRegion = pageData->selectedRegion(selection); + selectionNeedsUpdating = false; + } + + p.setPen(NoPen); + p.setBrush(white); + p.setRasterOp(Qt::XorROP); + + QMemArray selectionRects = selectedRegion.rects(); + + for (unsigned int i = 0; i < selectionRects.count(); i++) + p.drawRect(selectionRects[i]); + } + + // Draw scroll Guide + if (scrollGuide >= 0) + { + // Don't draw over the page shadow + p.setClipRegion(e->region().intersect(pageRect())); + p.setRasterOp(Qt::CopyROP); + p.setPen(Qt::red); + p.drawLine(1, scrollGuide, pageSize().width(), scrollGuide); + } +} + +void DocumentWidget::drawScrollGuide(int ycoord) +{ + //kdDebug() << "draw scroll guide for page " << pageNr << " at y = " << ycoord << endl; + scrollGuide = ycoord; + update(QRect(1, scrollGuide, pageSize().width(), 1)); + QTimer::singleShot(1000, this, SLOT(clearScrollGuide())); +} + +void DocumentWidget::clearScrollGuide() +{ + //kdDebug() << "clear scroll guide for page " << pageNr << " at y = " << scrollGuide << endl; + int temp = scrollGuide; + scrollGuide = -1; + update(QRect(1, temp, pageSize().width(), 1)); +} + +void DocumentWidget::select(const TextSelection& newSelection) +{ + // Get a pointer to the page contents + RenderedDocumentPage *pageData = documentCache->getPage(pageNr); + if (pageData == 0) { + kdDebug(1223) << "DocumentWidget::select() pageData for page #" << pageNr << " is empty" << endl; + return; + } + + documentCache->selectText(newSelection); + + selectedRegion = pageData->selectedRegion(documentCache->selectedText()); + selectionNeedsUpdating = false; + + update(); +} + +void DocumentWidget::selectAll() +{ + // pageNr == 0 indicated an invalid page (e.g. page number not yet + // set) + if (pageNr == 0) + return; + + // Get a pointer to the page contents + RenderedDocumentPage *pageData = documentCache->getPage(pageNr); + if (pageData == 0) { + kdDebug(1223) << "DocumentWidget::selectAll() pageData for page #" << pageNr << " is empty" << endl; + return; + } + + TextSelection selection; + // mark everything as selected + QString selectedText(""); + for(unsigned int i = 0; i < pageData->textBoxList.size(); i++) { + selectedText += pageData->textBoxList[i].text; + selectedText += "\n"; + } + selection.set(pageNr, 0, pageData->textBoxList.size()-1, selectedText); + + selectedRegion = pageData->selectedRegion(selection); + + documentCache->selectText(selection); + + // Re-paint + update(); +} + + +void DocumentWidget::mousePressEvent ( QMouseEvent * e ) +{ +#ifdef DEBUG_DOCUMENTWIDGET + kdDebug(1223) << "DocumentWidget::mousePressEvent(...) called" << endl; +#endif + + // Make sure the event is passed on to the higher-level widget; + // otherwise QT gets the coordinated in the mouse move events wrong + e->ignore(); + + // pageNr == 0 indicated an invalid page (e.g. page number not yet + // set) + if (pageNr == 0) + return; + + // Get a pointer to the page contents + RenderedDocumentPage *pageData = documentCache->getPage(pageNr); + if (pageData == 0) { + kdDebug(1223) << "DocumentWidget::selectAll() pageData for page #" << pageNr << " is empty" << endl; + return; + } + + // Check if the mouse is pressed on a regular hyperlink + if (e->button() == LeftButton) { + if (pageData->hyperLinkList.size() > 0) + for(unsigned int i = 0; i < pageData->hyperLinkList.size(); i++) { + if (pageData->hyperLinkList[i].box.contains(e->pos())) { + emit(localLink(pageData->hyperLinkList[i].linkText)); + return; + } + } + if (moveTool) + setCursor(Qt::SizeAllCursor); + else + setCursor(Qt::IbeamCursor); + } + + if (e->button() == RightButton || (!moveTool && e->button() == LeftButton)) + { + setCursor(Qt::IbeamCursor); + // If Shift is not pressed clear the current selection, + // otherwise modify the existing selection. + if (!(e->state() & ShiftButton)) + { + firstSelectedPoint = e->pos(); + selectedRectangle = QRect(); + selectedRegion = QRegion(); + emit clearSelection(); + } + } +} + + +void DocumentWidget::mouseReleaseEvent ( QMouseEvent *e ) +{ + // Make sure the event is passed on to the higher-level widget; + // otherwise the mouse cursor in the centeringScrollview is wrong + e->ignore(); + + if (e->button() == RightButton || (!moveTool && e->button() == LeftButton)) + { + // If the selectedRectangle is empty then there was only a single right click. + if (firstSelectedPoint == e->pos()) + { + if (pageNr == 0) + return; + + // Get a pointer to the page contents + RenderedDocumentPage* pageData = documentCache->getPage(pageNr); + if (pageData == 0) + { + kdDebug(1223) << "DocumentWidget::mouseReleaseEvent() pageData for page #" << pageNr << " is empty" << endl; + return; + } + + TextSelection newTextSelection = pageData->select(firstSelectedPoint); + updateSelection(newTextSelection); + } + } + + //Reset the cursor to the usual arrow if we use the move tool, and + // The textselection cursor if we use the selection tool. + setStandardCursor(); +} + + +void DocumentWidget::mouseMoveEvent ( QMouseEvent * e ) +{ +#ifdef DEBUG_DOCUMENTWIDGET + kdDebug(1223) << "DocumentWidget::mouseMoveEvent(...) called" << endl; +#endif + + + // pageNr == 0 indicated an invalid page (e.g. page number not yet + // set) + if (pageNr == 0) + return; + + // Get a pointer to the page contents + RenderedDocumentPage *pageData = documentCache->getPage(pageNr); + if (pageData == 0) { + kdDebug(1223) << "DocumentWidget::selectAll() pageData for page #" << pageNr << " is empty" << endl; + return; + } + + // If no mouse button pressed + if (e->state() == 0) { + // Remember the index of the underlined link. + int lastUnderlinedLink = indexOfUnderlinedLink; + // go through hyperlinks + for(unsigned int i = 0; i < pageData->hyperLinkList.size(); i++) { + if (pageData->hyperLinkList[i].box.contains(e->pos())) { + clearStatusBarTimer.stop(); + setCursor(pointingHandCursor); + QString link = pageData->hyperLinkList[i].linkText; + if ( link.startsWith("#") ) + link = link.remove(0,1); + + emit setStatusBarText( i18n("Link to %1").arg(link) ); + + indexOfUnderlinedLink = i; + if (KVSPrefs::underlineLinks() == KVSPrefs::EnumUnderlineLinks::OnlyOnHover && + indexOfUnderlinedLink != lastUnderlinedLink) + { + QRect newUnderline = pageData->hyperLinkList[i].box; + // Increase Rectangle so that the whole line really lines in it. + newUnderline.addCoords(0, 0, 0, 2); + // Redraw widget + update(newUnderline); + + if (lastUnderlinedLink != -1 && lastUnderlinedLink < pageData->hyperLinkList.size()) + { + // Erase old underline + QRect oldUnderline = pageData->hyperLinkList[lastUnderlinedLink].box; + oldUnderline.addCoords(0, 0, 0, 2); + update(oldUnderline); + } + } + return; + } + } + // Whenever we reach this the mouse hovers no link. + indexOfUnderlinedLink = -1; + if (KVSPrefs::underlineLinks() == KVSPrefs::EnumUnderlineLinks::OnlyOnHover && + lastUnderlinedLink != -1 && lastUnderlinedLink < pageData->hyperLinkList.size()) + { + // Erase old underline + QRect oldUnderline = pageData->hyperLinkList[lastUnderlinedLink].box; + // Increase Rectangle so that the whole line really lines in it. + oldUnderline.addCoords(0, 0, 0, 2); + // Redraw widget + update(oldUnderline); + } + // Cursor not over hyperlink? Then let the cursor be the usual arrow if we use the move tool, and + // The textselection cursor if we use the selection tool. + setStandardCursor(); + } + + if (!clearStatusBarTimer.isActive()) + clearStatusBarTimer.start(200, true); // clear the statusbar after 200 msec. + + // Left mouse button pressed -> Text scroll function + if ((e->state() & LeftButton) != 0 && moveTool) + { + // Pass the mouse event on to the owner of this widget ---under + // normal circumstances that is the centeringScrollView which will + // then scroll the scrollview contents + e->ignore(); + } + + // Right mouse button pressed -> Text copy function + if ((e->state() & RightButton) != 0 || (!moveTool && (e->state() & LeftButton != 0))) + { + if (selectedRectangle.isEmpty()) { + firstSelectedPoint = e->pos(); + selectedRectangle.setRect(e->pos().x(),e->pos().y(),1,1); + } else { + int lx = e->pos().x() < firstSelectedPoint.x() ? e->pos().x() : firstSelectedPoint.x(); + int rx = e->pos().x() > firstSelectedPoint.x() ? e->pos().x() : firstSelectedPoint.x(); + int ty = e->pos().y() < firstSelectedPoint.y() ? e->pos().y() : firstSelectedPoint.y(); + int by = e->pos().y() > firstSelectedPoint.y() ? e->pos().y() : firstSelectedPoint.y(); + selectedRectangle.setCoords(lx,ty,rx,by); + } + + // Now that we know the rectangle, we have to find out which words + // intersect it! + TextSelection newTextSelection = pageData->select(selectedRectangle); + + updateSelection(newTextSelection); + } +} + +void DocumentWidget::updateSelection(const TextSelection& newTextSelection) +{ + if (newTextSelection != documentCache->selectedText()) + { + if (newTextSelection.isEmpty()) + { + // Clear selection + documentCache->deselectText(); + selectedRectangle = QRect(); + selectedRegion = QRegion(); + update(); + } + else + { + if (pageNr == 0) + return; + + // Get a pointer to the page contents + RenderedDocumentPage* pageData = documentCache->getPage(pageNr); + if (pageData == 0) + { + kdDebug(1223) << "DocumentWidget::mouseReleaseEvent() pageData for page #" << pageNr << " is empty" << endl; + return; + } + + documentCache->selectText(newTextSelection); + + QRegion newlySelectedRegion = pageData->selectedRegion(documentCache->selectedText()); + + // Compute the region that needs to be updated + QRegion updateRegion; + if(!selectedRegion.isEmpty()) + { + updateRegion = newlySelectedRegion.eor(selectedRegion); + } + else + { + updateRegion = newlySelectedRegion; + } + + selectedRegion = newlySelectedRegion; + + QMemArray rectangles = updateRegion.rects(); + for (unsigned int i = 0; i < rectangles.count(); i++) + { + repaint(rectangles[i]); + } + } + } +} + + +void DocumentWidget::clearStatusBar() +{ + emit setStatusBarText( QString::null ); +} + + +void DocumentWidget::delayedRequestPage() +{ + if (!isVisible()) + { + // We only want to calculate the page pixmap for widgets that are currently visible. + // When we are fast scrolling thru the document many paint events are created, that + // are often not needed anymore at the time the eventloop executes them. + + //kdDebug() << "delayedRequest: widget of page " << pageNr << " is not visible. Abort rendering" << endl; + pixmapRequested = false; + kapp->processEvents(); + return; + } + + documentCache->getPage(pageNr); + pixmapRequested = false; + update(); + + // If more widgets need updateing at the some time, the next line allows them to be + // displayed one after another. Widthout it all widgets are updated after all the rendering + // is completed. This is especially noticable in overview mode. After the change to a seperate + // rendering thread this will probably not be needed anymore. + kapp->processEvents(); +} + +QSize DocumentWidget::pageSize() const +{ + // Substract size of the shadow. + return size() - QSize(6, 6); +} + +QRect DocumentWidget::pageRect() const +{ + QRect boundingRect = rect(); + // Substract the shadow. + boundingRect.addCoords(1,1,-5,-5); + return boundingRect; +} + +void DocumentWidget::setPageSize(const QSize& pageSize) +{ + // When the page size changes this means either the paper size + // has been changed, or the zoomlevel has been changed. + // If the zoomlevel changes we need to recalculate the selected + // region. We do this always, just to be on the safe side. + selectionNeedsUpdating = true; + + // Add size of the shadow. + resize(pageSize + QSize(6,6)); +} + +void DocumentWidget::setPageSize(int width, int height) +{ + setPageSize(QSize(width, height)); +} + + +void DocumentWidget::slotEnableMoveTool(bool enable) +{ + moveTool = enable; + + setStandardCursor(); +} + + +void DocumentWidget::setStandardCursor() +{ + if (moveTool) + { + setCursor(Qt::arrowCursor); + } + else + { + setCursor(Qt::IbeamCursor); + } +} + + +bool DocumentWidget::isVisible() +{ + QRect visibleRect(scrollView->contentsX(), scrollView->contentsY(), scrollView->visibleWidth(), scrollView->visibleHeight()); + QRect widgetRect(scrollView->childX(this), scrollView->childY(this), width(), height()); + return widgetRect.intersects(visibleRect); +} + +#include "documentWidget.moc" diff --git a/kviewshell/documentWidget.h b/kviewshell/documentWidget.h new file mode 100644 index 00000000..50f2ee44 --- /dev/null +++ b/kviewshell/documentWidget.h @@ -0,0 +1,193 @@ +// -*- C++ -*- +// +// Class: documentWidet +// +// Widget for displaying TeX DVI files. +// Part of KDVI- A previewer for TeX DVI files. +// +// (C) 2004 Stefan Kebekus. +// Copyright (C) 2004-2005 Wilfried Huss +// +// Distributed under the GPL. + +#ifndef _documentwidget_h_ +#define _documentwidget_h_ + +#include "selection.h" + +#include +#include +#include + +class DocumentPageCache; +class PageView; +class QMouseEvent; +class QPaintEvent; + + +/* DocumentWidget */ + +class DocumentWidget : public QWidget +{ + Q_OBJECT + +public: + DocumentWidget(QWidget *parent, PageView *sv, DocumentPageCache *cache, const char *name); + + void setPageNumber(Q_UINT16 pageNr); + Q_UINT16 getPageNumber() const {return pageNr;} + + /** Returns the size of the widget without the page shadow. */ + QSize pageSize() const; + /** Returns the bounding rectangle of the widget without the page shadow. */ + QRect pageRect() const; + + /** Draw a red vertical line at y-coordinate ycoord. The line is removed again + after one second. This is used to make it easier to regain reading focus if + the whole page is scrolled up or down. */ + void drawScrollGuide(int ycoord); + + /** Checks if the page is currently visible in the PageView. */ + bool isVisible(); + +public slots: + void slotEnableMoveTool(bool enable); + + void select(const TextSelection&); + void selectAll(); + void flash(int); + + /** Sets the size of the widget so that the page is of the given size. + The widget gets slightly bigger because of the page shadow. */ + void setPageSize(const QSize&); + void setPageSize(int width, int height); + +signals: + /** Passed through to the top-level kpart. */ + void setStatusBarText( const QString& ); + void localLink( const QString& ); + + /** This signal is emitted when the widget resizes itself */ + void resized(); + + /** This signal is emitted when the selection needs to be cleared. */ + void clearSelection(); + +protected: + virtual void paintEvent (QPaintEvent *); + virtual void mousePressEvent ( QMouseEvent * e ); + virtual void mouseReleaseEvent (QMouseEvent *); + + /** This method is used by the DocumentWidget to find out of the + mouse pointer hovers over a hyperlink, and to update the + statusbar accordingly. Scrolling with the left mouse button + pressed, and the text copy functions are also implemented here. + Re-implementations of this method should do the following: + + 0) Immediately return if pageNr == 0, i.e. if no page number has + been set + + 1) Call the standard implementation using + + DocumentWidget::mouseMoveEvent(e); + + 2) Ignore the QMouseEvent if a mouse button is pressed + + 3) If no mouse button is pressed, analyze the mouse movement and + take appropriate actions. To set statusbar text, do + + clearStatusBarTimer.stop(); + emit setStatusBarText( i18n("Whatever string") ); + + To clear the statusbar, use the following code + + if (!clearStatusBarTimer.isActive()) + clearStatusBarTimer.start(200, true); + + This clears the statusbar after 200 msec and avoids awful + flickering when the mouse is swiftly moved across various + areas in the widget. + + */ + virtual void mouseMoveEvent (QMouseEvent *); + +protected: + void updateSelection(const TextSelection& newTextSelection); + + /** Methods and counters used for the animation to mark the target of + an hyperlink. */ + int timerIdent; + void timerEvent( QTimerEvent *e ); + int animationCounter; + int flashOffset; + + Q_UINT16 pageNr; + + /* This timer is used to delay clearing of the statusbar. Clearing + the statusbar is delayed to avoid awful flickering when the mouse + moves over a block of text that contains source hyperlinks. The + signal timeout() is connected to the method clearStatusBar() of + *this. */ + QTimer clearStatusBarTimer; + + /* Data structures used for marking text with the mouse */ + QPoint firstSelectedPoint; + QRect selectedRectangle; + + /** Pointer to the PageView that contains this + widget. This pointer is used in the re-implementation of the + paintEvent() method ---see the explanation there. */ + PageView *scrollView; + DocumentPageCache *documentCache; + + /** Currently selected Region */ + QRegion selectedRegion; + + /** This is set to the index of the link over which the mouse pointer currently resides, + and -1 if the no link is hovered. + Is used when "Underline Links" is set to "Only on Hover". */ + int indexOfUnderlinedLink; + + /** True if there is already a request for this page to the renderer. */ + bool pixmapRequested; + + /** Sets the cursor to an arrow if the move tool is selected, and to the text selection + cursor if the selection tool is active. */ + virtual void setStandardCursor(); + +private slots: + /** This slot emits the signal setStatusBarText(QString::null) to + clear the status bar. It is connected to the timeout slot of the + clearStatusBarTimer. */ + void clearStatusBar(); + + void delayedRequestPage(); + + /** Hide the scroll guide. This slot is called one second after drawScrollGuide(). */ + void clearScrollGuide(); + +private: + QRect linkFlashRect(); + + /** If this variable is positive draw a vertical line at this y-coordinate. */ + int scrollGuide; + + /** Color used by in the shadow drawing to check if the background color has been changed. */ + static QColor backgroundColorForCorners; + + /** The following tables store grey values for roundish shadow + corners. They were shamelessly stolen from kdelibs/kdefx/kstyle.cpp. */ + static const int bottom_right_corner[16]; + static const int bottom_left_corner[16]; + static const int shadow_strip[4]; + + bool moveTool; + + /** If this is true the zoomlevel has changed and we need to update the + selected region. */ + bool selectionNeedsUpdating; +}; + + + +#endif diff --git a/kviewshell/emptyRenderer.cpp b/kviewshell/emptyRenderer.cpp new file mode 100644 index 00000000..e89ffd6e --- /dev/null +++ b/kviewshell/emptyRenderer.cpp @@ -0,0 +1,29 @@ +// *************************************************************************** +// +// Copyright (C) 2005 by Wilfried Huss +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// *************************************************************************** + +#include + +#include "emptyRenderer.h" + +EmptyRenderer::EmptyRenderer(QWidget* parent) + : DocumentRenderer(parent) +{ +} + +#include "emptyRenderer.moc" diff --git a/kviewshell/emptyRenderer.h b/kviewshell/emptyRenderer.h new file mode 100644 index 00000000..62fd46f7 --- /dev/null +++ b/kviewshell/emptyRenderer.h @@ -0,0 +1,41 @@ +// -*- C++ -*- +// *************************************************************************** +// +// Copyright (C) 2005 by Wilfried Huss +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// *************************************************************************** + +#ifndef _EMPTYRENDERER_H_ +#define _EMPTYRENDERER_H_ + +#include "documentRenderer.h" + +class RenderedDocumentPage; + + +class EmptyRenderer : public DocumentRenderer +{ + Q_OBJECT + +public: + EmptyRenderer(QWidget* parent); + + virtual bool setFile(const QString &, const KURL &) { return false; } + + void drawPage(double res, RenderedDocumentPage* page) { Q_UNUSED(res); Q_UNUSED(page); } +}; + +#endif diff --git a/kviewshell/empty_multipage.cpp b/kviewshell/empty_multipage.cpp new file mode 100644 index 00000000..39de90a3 --- /dev/null +++ b/kviewshell/empty_multipage.cpp @@ -0,0 +1,48 @@ +// *************************************************************************** +// +// Copyright (C) 2005 by Wilfried Huss +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// *************************************************************************** + +#include + +#include +#include + +#include "empty_multipage.h" + +K_EXPORT_COMPONENT_FACTORY(emptymultipagepart, EmptyMultiPageFactory) + +EmptyMultiPage::EmptyMultiPage(QWidget* parentWidget, const char* widgetName, QObject* parent, const char* name, + const QStringList& args) + : KMultiPage(parentWidget, widgetName, parent, name), emptyRenderer(parentWidget) +{ + Q_UNUSED(args); + setInstance(EmptyMultiPageFactory::instance()); + + emptyRenderer.setName("Empty renderer"); + setRenderer(&emptyRenderer); +} + +KAboutData* EmptyMultiPage::createAboutData() +{ + return new KAboutData("emptymultipage", I18N_NOOP("Empty Multipage"), + "1.0", I18N_NOOP(""), + KAboutData::License_GPL, + I18N_NOOP("Copyright (c) 2005 Wilfried Huss")); +} + +#include "empty_multipage.moc" diff --git a/kviewshell/empty_multipage.h b/kviewshell/empty_multipage.h new file mode 100644 index 00000000..92d824b7 --- /dev/null +++ b/kviewshell/empty_multipage.h @@ -0,0 +1,51 @@ +// -*- C++ -*- +// *************************************************************************** +// +// Copyright (C) 2005 by Wilfried Huss +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +// *************************************************************************** + +#ifndef EMPTY_MULTIPAGE_H +#define EMPTY_MULTIPAGE_H + +#include "kmultipage.h" +#include "emptyRenderer.h" + +#include + + +class EmptyMultiPage : public KMultiPage +{ + Q_OBJECT + +public: + EmptyMultiPage(QWidget* parentWidget, const char* widgetName, QObject* parent, const char* name, + const QStringList& args = QStringList()); + + virtual QStringList fileFormats() const { return QString::null; } + + /// opens a file + virtual bool openFile() { return false; } + + static KAboutData* createAboutData(); + +private: + EmptyRenderer emptyRenderer; +}; + +typedef KParts::GenericFactory EmptyMultiPageFactory; + +#endif diff --git a/kviewshell/emptymultipage.desktop b/kviewshell/emptymultipage.desktop new file mode 100644 index 00000000..c2fef0e8 --- /dev/null +++ b/kviewshell/emptymultipage.desktop @@ -0,0 +1,29 @@ +[Desktop Entry] +Type=Service +Comment= +Name=EmptyMultiPage +Name[de]=Leere Mehrfachseite +Name[el]=ΚενήΠολλαπλήΣελίδα +Name[es]=Vaciado multipágina +Name[fi]=TyhjäMoniSivu +Name[fr]=Multi-page vide +Name[gl]=MultiPáxinaBaleira +Name[hu]=ÃœresTöbbOldalas +Name[it]=MultiPaginaVuota +Name[nb]=Tom Flerside +Name[nds]=Leddig Mehrfachsiet +Name[ne]=रिकà¥à¤¤ बहà¥à¤ªà¥ƒà¤·à¥à¤  +Name[nl]=LegeMultiPagina +Name[nn]=Tom fleirside +Name[pl]=Puste wielostronicowe +Name[pt_BR]=MultiPáginas Vazias +Name[ro]=Pagini multiple goale +Name[sk]=EmprtyMultiPage +Name[sv]=Tom flera sidor +Name[ta]=காலியான பலபகà¯à®•à®®à¯ +Name[tr]=BoşÇokluSayfa +Name[zh_HK]=ç©ºçš„å¤šé  +ServiceTypes=KViewShell/MultiPage +X-KDE-Library=emptymultipagepart +X-KDE-EmptyMultiPage=1 +X-KDE-MultiPageVersion=2 diff --git a/kviewshell/history.cpp b/kviewshell/history.cpp new file mode 100644 index 00000000..a8765220 --- /dev/null +++ b/kviewshell/history.cpp @@ -0,0 +1,91 @@ +// history.cpp +// +// (C) 2001 Stefan Kebekus +// Distributed under the GPL + +#include + +#include + +#include "history.h" + +HistoryItem::HistoryItem(Q_UINT32 p, Q_UINT32 y) + : page(p), ypos(y) +{ +} + +bool HistoryItem::operator== (const HistoryItem& item) const +{ + return page == item.page && ypos == item.ypos; +} + +History::History() +{ +} + +void History::add(Q_UINT32 page, Q_UINT32 ypos) +{ + HistoryItem item(page, ypos); + + if (historyList.empty()) + { + currentItem = historyList.append(item); + } + else + { + // Don't add the same item several times in a row + if (item == *currentItem) + return; + + currentItem++; + if (currentItem == historyList.end()) + { + currentItem = historyList.append(item); + } + else + { + currentItem = historyList.insert(currentItem, item); + } + // Delete items starting after currentItem to the end of the list. + QValueList::iterator deleteItemsStart = currentItem; + deleteItemsStart++; + historyList.erase(deleteItemsStart, historyList.end()); + + if (historyList.size() > HISTORYLENGTH) + historyList.pop_front(); + } + emit backItem(currentItem != historyList.begin()); + emit forwardItem(false); +} + +HistoryItem* History::forward() +{ + if (historyList.empty() || currentItem == historyList.fromLast()) + return 0; + + currentItem++; + emit backItem(true); + emit forwardItem(currentItem != historyList.fromLast()); + return &(*currentItem); +} + +HistoryItem* History::back() +{ + if (historyList.empty() || currentItem == historyList.begin()) + return 0; + + currentItem--; + emit backItem(currentItem != historyList.begin()); + emit forwardItem(true); + return &(*currentItem); +} + +void History::clear() +{ + historyList.clear(); + currentItem = historyList.begin(); + emit backItem(false); + emit forwardItem(false); +} + +#include "history.moc" diff --git a/kviewshell/history.h b/kviewshell/history.h new file mode 100644 index 00000000..2c651475 --- /dev/null +++ b/kviewshell/history.h @@ -0,0 +1,58 @@ +// -*- C++ -*- +#ifndef history_h +#define history_h + +// history.h +// +// (C) 2001 Stefan Kebekus +// Distributed under the GPL + +#include +#include + +#define HISTORYLENGTH 10 + +class HistoryItem +{ + public: + HistoryItem(Q_UINT32, Q_UINT32); + HistoryItem() {} + + bool operator== (const HistoryItem& item) const; + + Q_UINT32 page; + Q_UINT32 ypos; +}; + +inline +bool operator!=(const HistoryItem& lhs, const HistoryItem& rhs) +{ + return !(lhs == rhs); +} + + +class History : public QObject +{ + Q_OBJECT + +public: + History(); + + void add(Q_UINT32 page, Q_UINT32 ypos); + void clear(); + + HistoryItem* forward(); + HistoryItem* back(); + +signals: + void backItem(bool); + void forwardItem(bool); + +private: + // List of history items. First item is the oldest. + QValueList historyList; + + QValueList::iterator currentItem; +}; + +#endif diff --git a/kviewshell/hyperlink.h b/kviewshell/hyperlink.h new file mode 100644 index 00000000..bd07fef5 --- /dev/null +++ b/kviewshell/hyperlink.h @@ -0,0 +1,78 @@ +// -*- C++ -*- +// +// Class: hyperlink +// +// Part of KDVI- A previewer for TeX DVI files. +// +// (C) 2004-2005 Stefan Kebekus. Distributed under the GPL. + +#ifndef _hyperlink_h_ +#define _hyperlink_h_ + +#include +#include + + +/** Represents a named, rectangular region in a rendered documentPage + + This trivial class is used in the documentPage class to represent + a hyperlink in a rendered documentPage. + + @author Stefan Kebekus + @version 1.0.0 +*/ + +class Hyperlink +{ +public: + /** \brief Default Constructor + + The default constructor leaves all fields uninitialized. + */ + Hyperlink() {} + + /** \brief Constructor + + Trivial constructor leaves that initialized all members. + + @param bl value for the baseline field + @param re value for the box + @param lT valus for the text field + */ + Hyperlink(Q_UINT32 bl, const QRect& re, const QString& lT): baseline(bl), box(re), linkText(lT) {} + + /** \brief Base line of a hyperlink + + This field specifies the Y-coordinate of the base line of the + bounding box in the same coordinates that were used when the + associated documentPage was rendered by the + documentRenderer.drawPage() method. It is used to underline + hyperlinks in blue. Note that this field does generally differ from + the Y-coordinate of the bottom of the bounding box, e.g. if the text + in the box contains characters with underlengths, such as 'y', 'j' + or 'g'. + */ + Q_UINT32 baseline; + + /** \brief Bounding box of the text or hyperlink + + This rectangle specifies where on the page the hyperlink is + found. It uses the same coordinates that were used when the + associated documentPage was rendered by the + documentRenderer.drawPage() method. The box is used to determine if + the mouse pointer hovers over the link. + */ + QRect box; + + /** \brief Name of the region + + This field contains the name of the target, + e.g. "http://www.kde.org". If the Hyperlink class is used to + represent text, then the text is stored here. + */ + QString linkText; +}; + + + +#endif 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 + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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; ishow(); + + 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; isetPageNumber(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; isizeOfPageInPixel(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 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::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; igetPageNumber() == 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("The search string %1 could not be found by the " + "end of the document. Should the search be restarted from the beginning " + "of the document?").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("The search string %1 could not be found.").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("The search string %1 could not be found by the " + "beginning of the document. Should the search be restarted from the end " + "of the document?").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("The search string %1 could not be found.").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 selectedPageNo = selectedPages(); + if (selectedPageNo.isEmpty() == true) + printer->setOption( "kde-range", "" ); + else { + int commaflag = 0; + QString range; + QValueList::ConstIterator it = selectedPageNo.begin(); + do{ + int val = *it; + if (commaflag == 1) + range += QString(", "); + else + commaflag = 1; + int endval = val; + if (it != selectedPageNo.end()) { + QValueList::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" diff --git a/kviewshell/kmultipage.desktop b/kviewshell/kmultipage.desktop new file mode 100644 index 00000000..2b4ac822 --- /dev/null +++ b/kviewshell/kmultipage.desktop @@ -0,0 +1,41 @@ +[Desktop Entry] +Type=Service +X-KDE-ServiceType=KViewShell/MultiPage +Name=KViewShell MultiPage +Name[da]=KViewShell Multiside +Name[de]=KViewShell Mehrfachseiten +Name[el]=Πολλαπλή σελίδα KViewShell +Name[es]=Multipágina KViewShell +Name[fi]=KViewShell MoniSivu +Name[fr]=Multi-page KViewShell +Name[gl]=KViewShell Multipáxina +Name[hu]=KViewShell többoldalas +Name[it]=MultiPagina KViewShell +Name[kk]=Көпбетті KViewShell +Name[ms]=PelbagaiMuka KViewShell +Name[nb]=KViewShell Flerside +Name[nds]=KViewShell för Mehrfachsieden +Name[ne]=के दृशà¥à¤¯ शेल बहà¥à¤ªà¥ƒà¤·à¥à¤  +Name[nl]=KViewShell MultiPagina +Name[nn]=KViewShell-fleirside +Name[pl]=Wielostronicowe dla KViewShell +Name[pt]=KViewShell MultiPágina +Name[pt_BR]=KViewShell - Múltiplas Páginas +Name[ro]=KViewShell Pagini Multiple +Name[ru]=Компонент MultiPage +Name[sr]=KViewShell вишеÑтрани +Name[sr@Latn]=KViewShell viÅ¡estrani +Name[sv]=Kviewshell flera sidor +Name[ta]=கேகாடà¯à®šà®¿à®“ட௠பலபகà¯à®•à®®à¯ +Name[tr]=KViewShell ÇokluSayfa +Name[zh_CN]=KViewShell 多页 +Name[zh_HK]=KViewShell å¤šé  + +[PropertyDef::X-KDE-MimeTypes] +Type=QString + +[PropertyDef::X-KDE-MultiPageVersion] +Type=int + +[PropertyDef::X-KDE-EmptyMultiPage] +Type=int diff --git a/kviewshell/kmultipage.h b/kviewshell/kmultipage.h new file mode 100644 index 00000000..7f24a809 --- /dev/null +++ b/kviewshell/kmultipage.h @@ -0,0 +1,650 @@ +// -*- C++ -*- +#ifndef _KMULTIPAGE_H +#define _KMULTIPAGE_H + +#include "pageView.h" +#include "documentPageCache.h" +#include "documentRenderer.h" +#include "history.h" +#include "kmultipageInterface.h" +#include "marklist.h" + +#include +#include + +class Anchor; +class DocumentWidget; +class KConfigDialog; +class KPrintDialogPage_PageOptions; +class KPrinter; +class PageView; +class QPainter; +class QSplitter; +class QToolBox; +class simplePageSize; +class TableOfContents; +class PageNumber; +class SearchWidget; + + +/** \brief This class provides plugin-specific GUI elements for kviewshell plugins + +@author Wilfried Huss, Stefan Kebekus + + */ + +// TODO remove virtual inheritance for KDE 4. It's the reason for the strange DCOPObject construction +class KMultiPage : public KParts::ReadOnlyPart, virtual public kmultipageInterface +{ + Q_OBJECT + +public: + KMultiPage(QWidget *parentWidget, const char *widgetName, QObject *parent, const char *name); + virtual ~KMultiPage(); + + /* returns the scrollview used for the display */ + virtual QWidget* mainWidget() {return _scrollView;} + + /* Methods which are associated with the DCOP functionality of the + kmultipage. This method returns the file name (not the URL) of + the currently loaded file. */ + QString name_of_current_file(); + + /* Methods which are associated with the DCOP functionality of the + kmultipage. This method can be implemented by the multipage, + e.g. to jump to a certain location. */ + virtual ASYNC jumpToReference(const QString& /*reference*/); + + /* Methods which are associated with the DCOP functionality of the + kmultipage. This method checks if a given file is loaded. */ + bool is_file_loaded(const QString& filename); + + /* Opens file and sets URL + + This method does the same as openFile, but sets the m_url of the + kmultipage. This can be important, for the following reason: + assume that a DVI is or DJVU-file is located on a web server at + baseURL=http://www.x.x/x.dvi The file may refer to external + graphic files using relative links. + + The file is downloaded by the kviewpart to a temporary file on + the hard disk, say /tmp/t.dvi. The kviewpart would then call this + method with filename=/tmp/t.dvi and baseURL=http://www.x.x/x.dvi, + so the DVI-renderer knows to interpret the link to t.jpg as + http://www.x.x/t.jpg, and will download the file from there. + + @warning This method is virtual only for technical reasons. Do + not re-implement this method + + @returns true on success, false on failure + */ + virtual bool openURL(const QString &filename, const KURL &base_url); + + /** Prints a document + + This method prints a document. The default implementation offers + fairly good printer support, but printing with the default + implementation is usually quite slow as tremendous amounts of data + needs to be transferred to the printer. To limit the data sent to + the printer, this default implementation prints only at low + resolution and produces mediocre quality. This method can (and + should) therefore be re-implemented if you have good code to convert + your document to PostScript. + + Example: If your document consists of a single A4 page that contains + a DJVU image of 30KB, then the default implementation would render + the image in 600dpi, i.e. in about 7000x5000 pixel, and then send + this huge graphics uncompressed to the printer. A smart + implementation, on the other hand, would send the DJVU-file directly + to the printer, together with a DJVU decoder, which is implemented + in PostScript and uses only a few KB of memory, making for less than + 40KB of data sent to the printer. + + If you decide to re-implement this method, you need to decide if + your implementation will support the options offered by the "page + size & placement" tab of the print dialog. If these options are set, + pages that are smaller/larger than the printer's paper size will be + shrunk/enlarged and optionally centered on the paper. + + If your implementation does not support the options, the following + code should be used: + @code + // Obtain a fully initialized KPrinter structure, and disable all + // entries in the "Page Size & Placement" tab of the printer dialog. + KPrinter *printer = getPrinter(false); + // Abort with an error message if no KPrinter could be initialized + if (printer == 0) { + kdError(4300) << "KPrinter not available" << endl; + return; + } + + // Show the printer options dialog. Return immediately if the user + // aborts. + if (!printer->setup(parentWdg, i18n("Print %1").arg(m_file.section('/', -1)) )) { + delete printer; + return; + } + + ... pageList() gives a list of pages to be printed, where "1" + denotes the first page, "2" the second, etc.> ... + + printer->printFiles( QStringList(tmpPSFile), true ); + delete printer; + @endcode + + If your implementation does support the options, code must be + provided to support the KPrinter options + "kde-kviewshell-shrinkpage", "kde-kviewshell-expandpage", + "kde-kviewshell-centerpage" and "kde-kviewshell-rotatepage". It is + important to note that "kde-kviewshell-rotatepage" and + "kde-kviewshell-centerpage" should by default treated as "true", + while the other options should be "false" by default. The following + code sees to that: + @code + // Obtain a fully initialized KPrinter structure, and enable all + // entries in the "Page Size & Placement" tab of the printer dialog. + KPrinter *printer = getPrinter(true); + // Abort with an error message if no KPrinter could be initialized + if (printer == 0) { + kdError(4300) << "KPrinter not available" << endl; + return; + } + + // Show the printer options dialog. Return immediately if the user + // aborts. + if (!printer->setup(parentWdg, i18n("Print %1").arg(m_file.section('/', -1)) )) { + delete printer; + return; + } + + if (printer->option( "kde-kviewshell-shrinkpage" ) == "true") + + if (printer->option( "kde-kviewshell-expandpage" ) == "true") + + if (printer->option( "kde-kviewshell-centerpage" ) != "false") +
+ if (printer->option( "kde-kviewshell-rotatepage" ) != "false") + + + ... pageList() gives a list of pages to be printed, where "1" + denotes the first page, "2" the second, etc.> ... + + printer->printFiles( QStringList(tmpPSFile), true ); + delete printer; + @endcode + + For more information, see the default implementation in the source + file kmultipage.cpp. You might also look at the documentation to + getPrinter(). + */ + virtual void print(); + + + /* Returns true if the document specifies page sizes, and false + otherwise. NOTE: the information returned by this method is not + always 100% reliable. Although unlikely, it is theoretically + possible that this method returns 'true', but still some of the + sizes returned by sizeOfPage() are invalid. */ + virtual bool hasSpecifiedPageSizes() const {return renderer && renderer->hasSpecifiedPageSizes();} + + /* This methos is similar to openFile(). It is used when the "Watch + file" option is activated, and the file has changed on disk. It + differs from openFile() in two aspects: first, the file is + checked for validity with DVIRenderer.isValidFile(m_file) if the + file is invalid, a timer event is used to call the method again + after a brief pause. Secondly, when the GUI is updated, the + implementation does not jump to the first page, but tries to keep + the current page. */ + virtual void reload(); + +// Interface definition start ------------------------------------------------ + + /** list of supported file formats, for saving + + This member must return the list of supported file formats for + saving. These strings returned must be in the format explained in + the documentation to KFileDialog::setFilter(), e.g. ""*.cpp *.cxx + *.c++|C++ Source Files". The use of mimetype-filters is allowed, but + discouraged. Among other penalties, if a multipage gives a + mimetype-filter, e.g. "application/x-dvi", the open file dialog in + kviewshell will not allow the user to see and select compressed + dvi-files. + */ + virtual QStringList fileFormats() const = 0; + + /// closes a file + virtual bool closeURL(); + + /* sets a zoom factor. The multipage implementation might refuse to + use a given zoom factor, even if it falls within the bounds given + by the constants MinZoom and MaxZoom which are defined in + zoomlimits.h. In that case, the multipage implementation chooses a + different zomm factor. The implementation returns the factor which + has actually been used. A default implementation is provided. */ + virtual double setZoom(double z); + + /** reads in settings. Reimplementations must call this. */ + virtual void readSettings(); + + /** writes settings. Reimplementations must call this. */ + virtual void writeSettings(); + + /** Flag to indicate that this implementation has support for textserach and selection */ + virtual bool supportsTextSearch() const { return getRenderer() && getRenderer()->supportsTextSearch(); } + + /** Flag to indicate the document was modified since last saved + + KMultiPage implementations that offer functionality that + modifies the document (e.g. remove or insert pages) must + re-implement this method to return + + @returns 'true' if the document was modified since it was last + saved + */ + virtual bool isModified() const {return false;} + + /* Returns the number of the first (i.e. top-left) page in the + display. Such a number cannot be reasonably assigned + (e.g. because the current document is empty, or because no + document has been loaded yet), the method returns "invalid page", + i.e. 0. */ + virtual PageNumber currentPageNumber(); + + /* Returns the number of pages in the currently loaded document or + 0, if no document is loaded or if the document is empty */ + PageNumber numberOfPages() const {return renderer.isNull() ? (PageNumber)0 : renderer->totalPages();} + + /* List of pages selected in the sidebar + + @returns a list with the numbers of the currently selected + pages. */ + virtual QValueList selectedPages() {return markList()->selectedPages();} + + virtual History* history() { return &document_history; } + + /** Add pages to the KViewshell's central preferences dialog + + This method can be re-implemented to add documenttype specific + configuration pages to the central preferences dialog. The + documentation to KConfigDialog explains how to do that. + + The default implementation does nothing. + + @param configDialog a pointer to the KConfigDialog the dialog to + add pages to + */ + virtual void addConfigDialogs(KConfigDialog* configDialog) { Q_UNUSED(configDialog); } + + + /* These methods calculate the Zoomfactor needed to fit the pages + into the current viewport. Note that the return value need *not* + be within the limits defined in "zoomLimits.h". If they are not, + this indicates that fitting to width or height is currently not + possible (e.g. because no document is loaded). The return value + should then be ignored and any operation that relies on the + return value should be aborted. */ + virtual double calculateFitToWidthZoomValue(); + virtual double calculateFitToHeightZoomValue(); + + /* Returns the number of columns into which the widgets are aligned. */ + virtual Q_UINT8 getNrColumns() const { return _scrollView->getNrColumns(); } + virtual Q_UINT8 getNrRows() const { return _scrollView->getNrRows(); } + + virtual bool overviewMode() const { return _scrollView->overviewMode(); } + + // =========== Interface definition ends + + /* --------------------------------------------------------------------- + The following methods export functions of the DocumentPageCache which + are currently needed by KViewPart. TODO: Clean this up again without + directly linking DocumentPageCache to the KViewPart. */ + + /* Returns the size of page 'page'. If the document does not + specify a size (which happens, e.g., for some DVI-files), then + the userPreferredSize is returned. */ + virtual SimplePageSize sizeOfPage(const PageNumber& page = 1) const { return pageCache->sizeOfPage(page); } + +public slots: + /* Sets the userPreferredSize, which is explained below */ + virtual void setUserPreferredSize(const SimplePageSize& t) { pageCache->setUserPreferredSize(t); } + virtual void setUseDocumentSpecifiedSize(bool b) { pageCache->setUseDocumentSpecifiedSize(b); } + // -------------------------------------------------------------------- + +protected: + /** Update GUI after loading or closing of a file + + This method is called by openFile() when a new file was loaded, + and by closeURL() when a file is closed so that the kmultipage + implementation can update its own GUI, enable/disable actions, + prepare info texts, etc. At the time the method is executed, the + file has already been loaded into the renderer using + renderer.setFile(), or closed using renderer.clear() and the + standard KMultiPage GUI is set up. The filename can be accessed + via m_file. + + The default implementation does nothing. + + @param success the return value of renderer.setFile() which + indicates if the file could be successfully loaded by the + renderer, or not. Note that setFile() is called even if the + renderer returned 'false', so that the implemtation can then + disable menu items, etc. */ + virtual void setFile(bool success); + + /* Creates new instances of DocumentWidget. If you need special + functionality and reimplement the DocumentWidget class, then you + should also reimplement this method to ensure that your new + DocumentWidgets will be used. This function is also the right + place to connect to signals emitted by DocumentWidget. */ + virtual DocumentWidget* createDocumentWidget(); + + /* Used to enable/disable KActions of multiPage implementations. + enableActions(true) should be called whenever a file is + successfully loaded. enableActions(false) is called when the + file is closed. */ + virtual void enableActions(bool); + + /* Initializes all data structures that need to know the renderer. + This function must be called in the constructor of multipage + implementations. */ + void setRenderer(DocumentRenderer*); + + /** This function creates the page cache. If a multipage implementation needs + additional functionaly from the cache overwrite this function to create a + subclass of DocumentPageCache. + @warning This function is called by the constructor, never call it explicitly. + */ + virtual void initializePageCache(); + + /* Returns a pointer to the renderer. */ + virtual QGuardedPtr getRenderer() const { return renderer; } + + PageView* scrollView() { return _scrollView; } + + MarkList* markList() { return _markList; } + + // The next two functions are used for the autozoom feature + // TODO optimize (the calculation of the widest page needs to be done only once + // per document, not everytime calculateFitToWidthZoomValue() is called) + PageNumber widestPage() const; + + // TODO Generalize this for more than 2 columns + double zoomForWidthColumns(unsigned int viewportWidth) const; + +public slots: + virtual void doSelectAll(); + + virtual void clearSelection(); + + virtual void copyText(); + + /** Exports the document to a plain text file. */ + virtual void doExportText(); + + /* Shows the "text search" dialog, if text search is supported by + the renderer. Otherwise, the method returns immediately. */ + virtual void showFindTextDialog(); + + + /* This method may be called after the text search dialog + 'findDialog' has been set up, and the user has entered a search + phrase. The method searches for the next occurence of the text, + starting from the beginning of the current page, or after the + currently selected text, if there is any. */ + virtual void findNextText(); + + /* This method may be called after the text search dialog + 'findDialog' has been set up, and the user has entered a search + phrase. The method searches for the next occurence of the text, + starting from the end of the current page, or before the + currently selected text, if there is any. */ + virtual void findPrevText(); + + /** Opens a file requestor and starts a basic copy KIO-Job. A + multipage implementation that wishes to offer saving in various + formats must re-implement this slot. */ + virtual void slotSave(); + + /* The standard implementation just calls slotSave. */ + virtual void slotSave_defaultFilename(); + + /* Initialize/Update PageWidgets, thumbnail list and bookmark list + + This slot is called after a document was loaded, when the + document was modified (e.g. when pages were deleted), or the + rendering mode is changed (e.g. when a different accessibility + viewmode is selected). The following is done + + - The page cache is cleared + + - all page widgets as well as the thumbnails are updated. + */ + void renderModeChanged(); + + /* Empties the page cache and --as the name suggests-- repaints all + visible widgets. */ + void repaintAllVisibleWidgets(); + + /* Tells the multipage if scrollbars should be used. */ + virtual void slotShowScrollbars(bool); + + /* Show or hide the sidebar widget. */ + virtual void slotShowSidebar(bool); + + /* Show or hide thumbnails. */ + virtual void slotShowThumbnails(bool); + + /* Used internally. */ + void slotIOJobFinished ( KIO::Job *job ); + + /* Switches to fullscreen mode and back. */ + virtual void slotSetFullPage(bool fullpage); + + virtual void setViewMode(int); + + /* Is called if settings are changed in the configuration dialog. + If this method is reimplemented in a child class, it needs to be + called from there. */ + virtual void preferencesChanged(); + + /* Page Navigation. */ + virtual bool gotoPage(const PageNumber& page); + virtual void gotoPage(const Anchor& a); + + virtual void prevPage(); + virtual void nextPage(); + virtual void firstPage(); + virtual void lastPage(); + + virtual void scrollUp(); + virtual void scrollDown(); + virtual void scrollLeft(); + virtual void scrollRight(); + + virtual void scrollUpPage(); + virtual void scrollDownPage(); + virtual void scrollLeftPage(); + virtual void scrollRightPage(); + + virtual void readUp(); + virtual void readDown(); + + virtual void doGoBack(); + virtual void doGoForward(); + + /* Scrolls the main scrollview by deltaInPixel (positive values + scroll DOWN). If the user tries to scroll past the beginning or + the end of a page, then the method either returns without doing + anything (if the current page is the first or last page, resp, or + if the method is called within 200ms after the beg. or end of the + page was reached), or goes the the next/previous page. The delay + makes it a little easier for the user to scroll with the mouse + wheel or the keyboard without involuntarily moving to another + page. */ + virtual void scroll(Q_INT32 deltaInPixel); + + virtual void slotEnableMoveTool(bool enable); + +protected slots: + virtual bool gotoPage(const PageNumber& page, int y, bool isLink = true); + + /* Make the selection visible */ + void gotoPage(const TextSelection&); + +private slots: + void handleLocalLink(const QString &linkText); + +signals: + /** Emitted with argument "true" when the move tool is selected, and + with argument "false" if selection tool is selected. */ + void enableMoveTool(bool enable); + + /* Emitted when a page has been selected in the MarkList. */ + void selected(const PageNumber& pageNumber); + + /* Emitted to indicate the number of pages in the file and the + current page. The receiver will not change or update the display, + nor call the gotoPage()-method. */ + void pageInfo(int nr, int currpg); + + void askingToCheckActions(); + + /* emitted when a new preview is available */ + void previewChanged(bool previewAvailable); + + void viewModeChanged(); + + /* Emitted when the zoom of the pageview changes. */ + void zoomChanged(); + + void zoomOut(); + void zoomIn(); + + /* Emitted it the status of the text selection changes. */ + void textSelected(bool); + + void searchEnabled(bool); + +// Interface definition end -------------------------------------------------- + +public slots: + virtual void generateDocumentWidgets(const PageNumber& startPage = PageNumber::invalidPage); + + protected slots: + /* This is the slot where mouseWheel events are processed that come + from the multipage/scrollview. This method calles scroll, a + delta-value of 120 (i.e. one notch on a standard wheel mouse) + scrolls by two 'lines'. */ + void wheelEvent(QWheelEvent *); + +protected: + /** Allocates and initializes a KPrinter structure + + This method is used in implementations of the print() method. See + the documentation of print() for more information and for example + code. This method allocates and initializes a KPrinter + structure. The list of pages marked in the sidebar is already set in + the "kde-range" option in the KPrinter structure. In this option "1" + means: first page of the document, "2" the second, etc. + + @param enablePageSizeFeatures Enables or diables the entries in the + "Page Size & Placement" tab of the print dialog. + + @returns a pointer to a KPrinter, or 0 if no KPrinter could be + allocated. + + @warning The KPrinter allocated is owned by the caller must be + deleted before the KMultiPage is deleted. Otherwise, a segfault will + occur. + */ + KPrinter *getPrinter(bool enablePageSizeFeatures=true); + + + /** Pointer to the parent widget + + This pointer is automatically set by the constructor. + */ + QGuardedPtr parentWdg; + + QPtrVector widgetList; + + History document_history; + + /* Variable which is used internally by the method + currentPageNumber() to provide 'lazy' page numbers. */ + PageNumber lastCurrentPage; + + /* The pageCache caches already drawn "documentPages" and invokes + the renderer if the needed page is not available in the cache. */ + DocumentPageCache* pageCache; + +private slots: + void setCurrentPageNumber(const PageNumber&); + void updateWidgetSize(const PageNumber&); + + /* Interrupts a search if one is currently performed, otherwise + hide the search panel */ + void stopSearch(); + +private: + /* For internal use by the reload()-method. See the comments in + kmultipage.cpp, right before the timerEvent function. */ + int timer_id; + + /* For internal use the reload()-method. This is a dreadful + hack. The problem we adress with this timer event is the + following: the kviewshell has a KDirWatch object which looks at + the DVI file and calls reload() when the object has changed. That + works very nicely in principle, but in practise, when TeX runs + for several seconds over a complicated file, this does not work + at all. First, most of the time, while TeX is still writing, the + file is invalid. Thus, reload() is very often called when the DVI + file is bad. We solve this problem by checking the file first. If + the file is bad, we do not reload. Second, when the file finally + becomes good, it very often happens that KDirWatch does not + notify us anymore. Whether this is a bug or a side effect of a + feature of KDirWatch, I dare not say. We remedy that problem by + using a timer: when reload() was called on a bad file, we + automatically come back (via the timerEvent() function) every + second and check if the file became good. If so, we stop the + timer. It may well happen that KDirWatch calls us several times + while we are waiting for the file to become good, but that does + not do any harm. */ + void timerEvent( QTimerEvent *e ); + + /* This method opens a file and sets up the GUI when the file is + loaded. It calls setFile() so that implementations of kmultipage + can update their own GUI. DO NOT REIMPLEMENT THIS METHOD. */ + bool openFile(); + + /* Is set by setRenderer. */ + QGuardedPtr renderer; + + PageView *_scrollView; + SearchWidget* searchWidget; + QToolBox* sideBar; + + MarkList* _markList; + TableOfContents* tableOfContents; + + QSplitter* splitterWidget; + + /* This timer is used to implement a brief delay when the user + scrolls past the beginning or the end of the page before a the + program moves to a new page. That way, it is a little easier for + the user to scroll with the mouse wheel or the keyboard without + involuntarily moving to another page. The timer is used in the + scroll() method. */ + QTimer changePageDelayTimer; + + // This is set to true while a text search is performed. + // If set to false the search is interrupted. + bool searchInProgress; +}; + + +#endif diff --git a/kviewshell/kmultipageInterface.h b/kviewshell/kmultipageInterface.h new file mode 100644 index 00000000..0ad6e914 --- /dev/null +++ b/kviewshell/kmultipageInterface.h @@ -0,0 +1,18 @@ +// -*- C++ -*- +#ifndef kmultipageINTERFACE_H +#define kmultipageINTERFACE_H + +#include + +class kmultipageInterface : virtual public DCOPObject +{ + K_DCOP + + k_dcop: + virtual ASYNC jumpToReference(const QString& reference) = 0; + virtual QString name_of_current_file() = 0; + virtual bool is_file_loaded(const QString& filename) = 0; +}; + +#endif + diff --git a/kviewshell/kprintDialogPage_pageoptions.cpp b/kviewshell/kprintDialogPage_pageoptions.cpp new file mode 100644 index 00000000..dbadd759 --- /dev/null +++ b/kviewshell/kprintDialogPage_pageoptions.cpp @@ -0,0 +1,166 @@ +// KPrintDialogPage_PageOptions.cpp +// +// Part of KVIEWSHELL - A framework for multipage text/gfx viewers +// +// (C) 2005 Stefan Kebekus +// Distributed under the GPL + +// Add header files alphabetically + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "kprintDialogPage_pageoptions.h" + + +KPrintDialogPage_PageOptions::KPrintDialogPage_PageOptions( QWidget *parent, const char *name ) + : KPrintDialogPage( parent, name ) +{ + setTitle( i18n("Page Size & Placement") ); + + kprintDialogPage_pageoptions_baseLayout = 0; + checkBox_center = 0; + checkBox_rotate = 0; + checkBox_shrink = 0; + checkBox_expand = 0; + + kprintDialogPage_pageoptions_baseLayout = new QVBoxLayout( this, 11, 6, "kprintDialogPage_pageoptions_baseLayout"); + if (kprintDialogPage_pageoptions_baseLayout == 0) { + kdError(1223) << "KPrintDialogPage_PageOptions::KPrintDialogPage_PageOptions() cannot create layout" << endl; + return; + } + + checkBox_center = new QCheckBox( this, "checkBox_center" ); + if (checkBox_center != 0) { + checkBox_center->setText( i18n( "Center the page on paper" ) ); + QToolTip::add( checkBox_center, i18n( "If this option is enabled, the pages are centered on the paper." ) ); + QWhatsThis::add( checkBox_center, i18n( "

If this option is enabled, the pages will be printed centered on the paper; this makes " + "more visually-appealing printouts.

" + "

If the option is not enabled, all pages will be placed in the top-left corner of the paper.

" ) ); + kprintDialogPage_pageoptions_baseLayout->addWidget( checkBox_center ); + } + + checkBox_rotate = new QCheckBox( this, "checkBox_rotate" ); + if (checkBox_rotate != 0) { + checkBox_rotate->setText( i18n( "Automatically choose landscape or portrait orientation" ) ); + QToolTip::add( checkBox_rotate, i18n( "If this option is enabled, some pages might be rotated to better fit the paper size." ) ); + QWhatsThis::add( checkBox_rotate, i18n( "

If this option is enabled, landscape or portrait orientation are automatically chosen on a " + "page-by-page basis. This makes better use of the paper and gives more visually-" + "appealing printouts.

" + "

Note: This option overrides the Portrait/Landscape option chosen in the printer " + "properties. If this option is enabled, and if the pages in your document have different sizes, " + "then some pages might be rotated while others are not.

" ) ); + kprintDialogPage_pageoptions_baseLayout->addWidget( checkBox_rotate ); + } + + QFrame *line = new QFrame( this, "line1" ); + if (line != 0) { + line->setFrameShape( QFrame::HLine ); + line->setFrameShadow( QFrame::Sunken ); + line->setFrameShape( QFrame::HLine ); + kprintDialogPage_pageoptions_baseLayout->addWidget( line ); + } + + checkBox_shrink = new QCheckBox( this, "checkBox_shrink" ); + if (checkBox_shrink != 0) { + checkBox_shrink->setText( i18n( "Shrink oversized pages to fit paper size" ) ); + QToolTip::add( checkBox_shrink, i18n( "If this option is enabled, large pages that would not fit the printer's paper size will be shrunk." ) ); + QWhatsThis::add( checkBox_shrink, i18n( "

If this option is enabled, large pages that would not fit the printer's paper size will be " + "shrunk so that edges won't be cut off during printing.

" + "

Note: If this option is enabled, and if the pages in your document have different sizes, " + "then different pages might be shrunk by different scaling factors.

" ) ); + kprintDialogPage_pageoptions_baseLayout->addWidget( checkBox_shrink ); + } + + checkBox_expand = new QCheckBox( this, "checkBox_expand" ); + if (checkBox_expand != 0) { + checkBox_expand->setText( i18n( "Expand small pages to fit paper size" ) ); + QToolTip::add( checkBox_expand, i18n( "If this option is enabled, small pages will be enlarged so that they fit the printer's paper size." ) ); + QWhatsThis::add( checkBox_expand, i18n( "

If this option is enabled, small pages will be enlarged so that they fit the printer's " + "paper size.

" + "

Note: If this option is enabled, and if the pages in your document have different sizes, " + "then different pages might be expanded by different scaling factors.

" ) ); + kprintDialogPage_pageoptions_baseLayout->addWidget( checkBox_expand ); + } + + + resize( QSize(319, 166).expandedTo(minimumSizeHint()) ); + clearWState( WState_Polished ); +} + + + +void KPrintDialogPage_PageOptions::getOptions( QMap& opts, bool incldef ) +{ + // Save options, taking default values into consideration: by + // default "center" is checked, "expand" and "shrink" are + // not. Warning: The default values are also coded into setOptions() + // and kmultipage::print(..). + + if (checkBox_center != 0) + if ( incldef || !checkBox_center->isChecked() ) + if (checkBox_center->isChecked()) + opts[ "kde-kviewshell-centerpage" ] = "true"; + else + opts[ "kde-kviewshell-centerpage" ] = "false"; + + if (checkBox_rotate != 0) + if ( incldef || !checkBox_rotate->isChecked() ) + if (checkBox_rotate->isChecked()) + opts[ "kde-kviewshell-rotatepage" ] = "true"; + else + opts[ "kde-kviewshell-rotatepage" ] = "false"; + + if (checkBox_shrink != 0) + if ( incldef || checkBox_shrink->isChecked() ) + if (checkBox_shrink->isChecked()) + opts[ "kde-kviewshell-shrinkpage" ] = "true"; + else + opts[ "kde-kviewshell-shrinkpage" ] = "false"; + + if (checkBox_expand != 0) + if ( incldef || checkBox_expand->isChecked() ) + if (checkBox_expand->isChecked()) + opts[ "kde-kviewshell-expandpage" ] = "true"; + else + opts[ "kde-kviewshell-expandpage" ] = "false"; +} + + +void KPrintDialogPage_PageOptions::setOptions( const QMap& opts ) +{ + // Sets the centering option. By default, this option is + // checked. Warning: All default values are also coded into + // getOptions() and kmultipage::print(..). + QString op = opts[ "kde-kviewshell-centerpage" ]; + if (checkBox_center != 0) + checkBox_center->setChecked( op != "false" ); + + // same for rotation + op = opts[ "kde-kviewshell-rotatepage" ]; + if (checkBox_rotate != 0) + checkBox_rotate->setChecked( op != "false" ); + + // Sets the shrink option. By default, this option is not checked + op = opts[ "kde-kviewshell-shrinkpage" ]; + if (checkBox_shrink != 0) + checkBox_shrink->setChecked( op == "true" ); + + // Sets the expand option. By default, this option is not checked + op = opts[ "kde-kviewshell-expandpage" ]; + if (checkBox_expand != 0) + checkBox_expand->setChecked( op == "true" ); +} + + +bool KPrintDialogPage_PageOptions::isValid( QString& ) +{ + return true; +} diff --git a/kviewshell/kprintDialogPage_pageoptions.h b/kviewshell/kprintDialogPage_pageoptions.h new file mode 100644 index 00000000..0c274248 --- /dev/null +++ b/kviewshell/kprintDialogPage_pageoptions.h @@ -0,0 +1,43 @@ +// -*- C++ -*- +// KPrintDialogPage_PageOptions.h +// +// Part of KVIEWSHELL - A framework for multipage text/gfx viewers +// +// (C) 2005 Stefan Kebekus +// Distributed under the GPL + +// Add header files alphabetically + +#ifndef KPRINTDIALOGPAGE_PAGEOPTIONS_H +#define KPRINTDIALOGPAGE_PAGEOPTIONS_H + +#include + +class QCheckBox; +class QVBoxLayout; + + +// This is a fairly standard KPrintDialogPage that allows the user to +// chose page size & placement options: center page on paper, shrink +// oversized pages, and expand small pages + +class KPrintDialogPage_PageOptions : public KPrintDialogPage +{ + public: + KPrintDialogPage_PageOptions( QWidget *parent = 0, const char *name = 0 ); + + void getOptions( QMap& opts, bool incldef = false ); + void setOptions( const QMap& opts ); + bool isValid( QString& msg ); + + QCheckBox* checkBox_center; + QCheckBox* checkBox_rotate; + QCheckBox* checkBox_shrink; + QCheckBox* checkBox_expand; + + private: + QVBoxLayout* kprintDialogPage_pageoptions_baseLayout; +}; + + +#endif // KPRINTDIALOGPAGE_PAGEOPTIONS_H diff --git a/kviewshell/kviewerpart.rc b/kviewshell/kviewerpart.rc new file mode 100644 index 00000000..856ca4e2 --- /dev/null +++ b/kviewshell/kviewerpart.rc @@ -0,0 +1,91 @@ + + + + &File + + + + + + + Export As + + + + + + &Edit + + + + + + + + + + + + + &View + + + + + + + + + + + + + + + &Go + + + + + + + + + + + + + + + + + + + + + &Help + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/kviewshell/kviewpart.cpp b/kviewshell/kviewpart.cpp new file mode 100644 index 00000000..ef21dca7 --- /dev/null +++ b/kviewshell/kviewpart.cpp @@ -0,0 +1,1632 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "kviewpart.h" +#include "kmultipage.h" +#include "pageSize.h" +#include "pageSizeDialog.h" +#include "zoomlimits.h" + +#include "optionDialogGUIWidget_base.h" +#include "optionDialogAccessibilityWidget.h" + +#include "kvsprefs.h" + +#define MULTIPAGE_VERSION 2 + +typedef KParts::GenericFactory KViewPartFactory; +K_EXPORT_COMPONENT_FACTORY(kviewerpart, KViewPartFactory) + +KViewPart::KViewPart(QWidget *parentWidget, const char *widgetName, QObject *parent, + const char *name, const QStringList& args) + : KViewPart_Iface(parent, name), showSidebar(0), saveAction(0), partManager(0), + multiPageLibrary(QString::null), aboutDialog(0) +{ + KGlobal::locale()->insertCatalogue("kviewshell"); + + tmpUnzipped = 0L; + pageChangeIsConnected = false; + setInstance(KViewPartFactory::instance()); + + watch = KDirWatch::self(); + connect(watch, SIGNAL(dirty(const QString&)), this, SLOT(fileChanged(const QString&))); + watch->startScan(); + + mainWidget = new QHBox(parentWidget, widgetName); + mainWidget->setFocusPolicy(QWidget::StrongFocus); + setWidget(mainWidget); + + // Setup part manager + partManager = new KParts::PartManager(parentWidget, "PartManager for kviewpart"); + setManager(partManager); + // Don't switch to another part when pressing a mouse button + partManager->setActivationButtonMask(0); + // Without this the GUI-items of the KMultiPages are not merged + partManager->setAllowNestedParts(true); + + connect(partManager, SIGNAL(activePartChanged(KParts::Part*)), this, SIGNAL(pluginChanged(KParts::Part*))); + partManager->addPart(this); + + // create the displaying part + + // Search for service + KTrader::OfferList offers; + + if (!args.isEmpty()) + { + // If a default MimeType is specified try to load a MultiPage supporting it. + QString defaultMimeType = args.first(); + offers = KTrader::self()->query( + QString::fromLatin1("KViewShell/MultiPage" ), + QString("([X-KDE-MultiPageVersion] == %1) and " + "([X-KDE-MimeTypes] == '%2')").arg(MULTIPAGE_VERSION).arg(defaultMimeType)); + } + + // If no default MimeType is given or no MultiPage has been found, try to load the Empty MultiPage. + if (offers.isEmpty()) + { + offers = KTrader::self()->query( + QString::fromLatin1("KViewShell/MultiPage" ), + QString("([X-KDE-MultiPageVersion] == %1) and " + "([X-KDE-EmptyMultiPage] == 1)").arg(MULTIPAGE_VERSION)); + } + + // If still no MultiPage has been found, report an error and abort. + if (offers.isEmpty()) + { + KMessageBox::error(parentWidget, i18n("No MultiPage found.")); + // return; + } + + KService::Ptr service = offers.first(); + kdDebug(1223) << service->library() << endl; + + // Try to load the multiPage + int error; + multiPage = static_cast(KParts::ComponentFactory::createInstanceFromService(service, mainWidget, + service->name().utf8(), QStringList(), &error )); + + // If the loading of the MultiPage failed report and error and abort. + if (!multiPage) { + QString reason; + switch(error) { + case KParts::ComponentFactory::ErrNoServiceFound: + reason = i18n("No service implementing the given mimetype and fullfilling the given constraint expression can be found."); + break; + case KParts::ComponentFactory::ErrServiceProvidesNoLibrary: + reason = i18n("The specified service provides no shared library."); + break; + case KParts::ComponentFactory::ErrNoLibrary: + reason = i18n("

The specified library %1 could not be loaded. The error message returned was:

" + "

%2

").arg(service->library()).arg(KLibLoader::self()->lastErrorMessage()); + break; + case KParts::ComponentFactory::ErrNoFactory: + reason = i18n("The library does not export a factory for creating components."); + break; + case KParts::ComponentFactory::ErrNoComponent: + reason = i18n("The factory does not support creating components of the specified type."); + break; + } + + QString text = i18n("

Problem: The document %1 cannot be shown.

" + "

Reason: The software component %2 which is required to " + "display your files could not be initialized. This could point to " + "serious misconfiguration of your KDE system, or to damaged program files.

" + "

What you can do: You could try to re-install the software packages in " + "question. If that does not help, you could file an error report, either to the " + "provider of your software (e.g. the vendor of your Linux distribution), or " + "directly to the authors of the software. The entry Report Bug... in the " + "Help menu helps you to contact the KDE programmers.

").arg(m_file).arg(service->library()); + QString caption = i18n("Error Initializing Software Component"); + KMessageBox::detailedError(mainWidget, text, reason, caption); + emit setStatusBarText(QString::null); + return; + } + // Make the KViewPart the parent of the MultiPage. + // So the Partmanager treats it as a nested KPart. + insertChild(multiPage); + + // Remember the name of the library. + multiPageLibrary = service->library(); + // Add the multipage to the GUI. + partManager->addPart(multiPage); + + exportTextAction = new KAction(i18n("Text..."), 0, this, SLOT(mp_exportText()), actionCollection(), "export_text"); + + // edit menu + findTextAction = KStdAction::find(this, SLOT(mp_showFindTextDialog()), actionCollection(), "find"); + findNextAction = KStdAction::findNext(this, SLOT(mp_findNextText()), actionCollection(), "findnext"); + findNextAction->setEnabled(false); + findPrevAction = KStdAction::findPrev(this, SLOT(mp_findPrevText()), actionCollection(), "findprev"); + findPrevAction->setEnabled(false); + + selectAllAction = KStdAction::selectAll(this, SLOT(mp_doSelectAll()), actionCollection(), "edit_select_all"); + + copyTextAction = KStdAction::copy(this, SLOT(mp_copyText()), actionCollection(), "copy_text"); + copyTextAction->setEnabled(false); + + deselectAction = KStdAction::deselect(this, SLOT(mp_clearSelection()), actionCollection(), "edit_deselect_all"); + deselectAction->setEnabled(false); + + saveAction = KStdAction::save(this, SLOT(mp_slotSave_defaultFilename()), actionCollection()); + + // settings menu + showSidebar = new KToggleAction (i18n("Show &Sidebar"), "show_side_panel", 0, this, + SLOT(slotShowSidebar()), actionCollection(), "show_sidebar"); + showSidebar->setCheckedState(i18n("Hide &Sidebar")); + watchAct = new KToggleAction(i18n("&Watch File"), 0, 0, 0, actionCollection(), "watch_file"); + scrollbarHandling = new KToggleAction (i18n("Show Scrollbars"), 0, 0, 0, actionCollection(), "scrollbarHandling"); + scrollbarHandling->setCheckedState(i18n("Hide Scrollbars")); + + // View modes + QStringList viewModes; + viewModes.append(i18n("Single Page")); + viewModes.append(i18n("Continuous")); + viewModes.append(i18n("Continuous - Facing")); + viewModes.append(i18n("Overview")); + viewModeAction = new KSelectAction (i18n("View Mode"), 0, 0, 0, actionCollection(), "viewmode"); + viewModeAction->setItems(viewModes); + + // 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->setEditable(true); + zoom_action->setItems(_zoomVal.zoomNames()); + + connect (&_zoomVal, SIGNAL(zoomNamesChanged(const QStringList &)), zoom_action, SLOT(setItems(const QStringList &))); + connect (&_zoomVal, SIGNAL(valNoChanged(int)), zoom_action, SLOT(setCurrentItem(int))); + connect (&_zoomVal, SIGNAL(zoomNameChanged(const QString &)), this, SIGNAL(zoomChanged(const QString &)) ); + connect (zoom_action, SIGNAL(activated(const QString &)), this, SLOT(setZoomValue(const QString &))); + _zoomVal.setZoomValue(1.0); // should not be necessary @@@@ + emit(zoomChanged("100%")); + + // Paper Size Menu + media = new KSelectAction (i18n("Preferred Paper &Size"), 0, 0, 0, actionCollection(), "view_media"); + QStringList items = userRequestedPaperSize.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(slotShowSidebar()), + actionCollection(), "view_use_document_specified_size"); + + // Zoom Actions + zoomInAct = KStdAction::zoomIn (this, SLOT(zoomIn()), actionCollection()); + zoomOutAct = KStdAction::zoomOut(this, SLOT(zoomOut()), actionCollection()); + + fitPageAct = new KToggleAction(i18n("&Fit to Page"), "view_fit_window", Key_P, + actionCollection(), "view_fit_to_page"); + fitWidthAct = new KToggleAction(i18n("Fit to Page &Width"), "view_fit_width", Key_W, + actionCollection(), "view_fit_to_width"); + fitHeightAct = new KToggleAction(i18n("Fit to Page &Height"), "view_fit_height", Key_H, + actionCollection(), "view_fit_to_height"); + + fitPageAct -> setExclusiveGroup("view_fit"); + fitWidthAct -> setExclusiveGroup("view_fit"); + fitHeightAct -> setExclusiveGroup("view_fit"); + + connect(fitPageAct, SIGNAL(toggled(bool)), this, SLOT(enableFitToPage(bool))); + connect(fitWidthAct, SIGNAL(toggled(bool)), this, SLOT(enableFitToWidth(bool))); + connect(fitHeightAct, SIGNAL(toggled(bool)), this, SLOT(enableFitToHeight(bool))); + + // go menu + backAct = KStdAction::prior(this, SLOT(mp_prevPage()), actionCollection()); + forwardAct = KStdAction::next(this, SLOT(mp_nextPage()), actionCollection()); + startAct = KStdAction::firstPage(this, SLOT(mp_firstPage()), actionCollection()); + endAct = KStdAction::lastPage(this, SLOT(mp_lastPage()), actionCollection()); + gotoAct = KStdAction::gotoPage(this, SLOT(goToPage()), actionCollection()); + gotoAct->setShortcut("CTRL+G"); + + readUpAct = new KAction(i18n("Read Up Document"), "up", SHIFT+Key_Space, this, SLOT(mp_readUp()), actionCollection(), "go_read_up"); + readDownAct = new KAction(i18n("Read Down Document"), "down", Key_Space, this, SLOT(mp_readDown()), actionCollection(), "go_read_down"); + + printAction = KStdAction::print(this, SLOT(slotPrint()), actionCollection()); + + saveAsAction = KStdAction::saveAs(this, SLOT(mp_slotSave()), actionCollection()); + + // mode action + moveModeAction = new KRadioAction(i18n("&Move Tool"), "movetool", Key_F4, actionCollection(), "move_tool"); + selectionModeAction = new KRadioAction(i18n("&Selection Tool"), "selectiontool", Key_F5, actionCollection(), "selection_tool"); + + moveModeAction->setExclusiveGroup("tools"); + selectionModeAction->setExclusiveGroup("tools"); + + moveModeAction->setChecked(true); + + connect(moveModeAction, SIGNAL(toggled(bool)), this, SLOT(slotEnableMoveTool(bool))); + //connect(selectionModeAction, SIGNAL(toggled(bool)), this, SLOT(slotEnableSelectionTool(bool))); + + // history action + backAction = new KAction(i18n("&Back"), "1leftarrow", 0, + this, SLOT(mp_doGoBack()), actionCollection(), "go_back"); + forwardAction = new KAction(i18n("&Forward"), "1rightarrow", 0, + this, SLOT(mp_doGoForward()), actionCollection(), "go_forward"); + + backAction->setEnabled(false); + forwardAction->setEnabled(false); + + + settingsAction = KStdAction::preferences(this, SLOT(doSettings()), actionCollection()); + + // We only show this menuitem if no default mimetype is set. This usually means kviewshell + // has been started by itself. Otherwise if KDVI or KFaxView has been started show the + // additional about information. + if (!args.isEmpty()) + { + aboutAction = new KAction(i18n("About KViewShell"), "kviewshell", 0, this, + SLOT(aboutKViewShell()), actionCollection(), "help_about_kviewshell"); + } + + // keyboard accelerators + accel = new KAccel(mainWidget); + accel->insert(I18N_NOOP("Scroll Up"), Key_Up, this, SLOT(mp_scrollUp())); + accel->insert(I18N_NOOP("Scroll Down"), Key_Down, this, SLOT(mp_scrollDown())); + accel->insert(I18N_NOOP("Scroll Left"), Key_Left, this, SLOT(mp_scrollLeft())); + accel->insert(I18N_NOOP("Scroll Right"), Key_Right, this, SLOT(mp_scrollRight())); + + accel->insert(I18N_NOOP("Scroll Up Page"), SHIFT+Key_Up, this, SLOT(mp_scrollUpPage())); + accel->insert(I18N_NOOP("Scroll Down Page"), SHIFT+Key_Down, this, SLOT(mp_scrollDownPage())); + accel->insert(I18N_NOOP("Scroll Left Page"), SHIFT+Key_Left, this, SLOT(mp_scrollLeftPage())); + accel->insert(I18N_NOOP("Scroll Right Page"), SHIFT+Key_Right, this, SLOT(mp_scrollRightPage())); + + accel->readSettings(); + readSettings(); + + m_extension = new KViewPartExtension(this); + + setXMLFile("kviewerpart.rc"); + + initializeMultiPage(); + + // The page size dialog is constructed on first usage -- saves some + // memory when not used. + _pageSizeDialog = 0; + + checkActions(); + viewModeAction->setCurrentItem(KVSPrefs::viewMode()); + + // We disconnect because we dont want some FocusEvents to trigger a GUI update, which might mess + // with our menus. + disconnect(partManager, SIGNAL(activePartChanged(KParts::Part*)), this, SIGNAL(pluginChanged(KParts::Part*))); +} + +KViewPart::~KViewPart() +{ + writeSettings(); + + // Without the next two lines, konqueror crashes when it is quit + // while displaying a DVI file. I don't really understand + // why... --Stefan. + if (manager() != 0) + manager()->removePart(this); + + // Delete the partManager; + setManager(0); + delete partManager; + + delete multiPage; + delete tmpUnzipped; +} + + +void KViewPart::initializeMultiPage() +{ + // Paper Size handling + multiPage->setUseDocumentSpecifiedSize(useDocumentSpecifiedSize->isChecked()); + multiPage->setUserPreferredSize(userRequestedPaperSize); + connect(&userRequestedPaperSize, SIGNAL(sizeChanged(const SimplePageSize&)), multiPage, SLOT(setUserPreferredSize(const SimplePageSize&))); + connect(useDocumentSpecifiedSize, SIGNAL(toggled(bool)), multiPage, SLOT(setUseDocumentSpecifiedSize(bool))); + + + connect(scrollbarHandling, SIGNAL(toggled(bool)), multiPage, SLOT(slotShowScrollbars(bool))); + + // connect to the multi page view + connect( this, SIGNAL(scrollbarStatusChanged(bool)), multiPage, SLOT(slotShowScrollbars(bool))); + connect( multiPage, SIGNAL(pageInfo(int, int)), this, SLOT(pageInfo(int, int)) ); + connect( multiPage, SIGNAL(askingToCheckActions()), this, SLOT(checkActions()) ); + 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( setStatusBarText( const QString& ) ), this, SLOT( setStatusBarTextFromMultiPage( const QString& ) ) ); + + connect( multiPage, SIGNAL(zoomIn()), this, SLOT(zoomIn()) ); + connect( multiPage, SIGNAL(zoomOut()), this, SLOT(zoomOut()) ); + + // change the viewmode + connect(viewModeAction, SIGNAL(activated (int)), multiPage, SLOT(setViewMode(int))); + + // Update zoomlevel on viewmode changes + connect(multiPage, SIGNAL(viewModeChanged()), this, SLOT(updateZoomLevel())); + + // navigation history + connect(multiPage->history(), SIGNAL(backItem(bool)), backAction, SLOT(setEnabled(bool))); + connect(multiPage->history(), SIGNAL(forwardItem(bool)), forwardAction, SLOT(setEnabled(bool))); + + // text selection + connect(multiPage, SIGNAL(textSelected(bool)), copyTextAction, SLOT(setEnabled(bool))); + connect(multiPage, SIGNAL(textSelected(bool)), deselectAction, SLOT(setEnabled(bool))); + + // text search + connect(multiPage, SIGNAL(searchEnabled(bool)), findNextAction, SLOT(setEnabled(bool))); + connect(multiPage, SIGNAL(searchEnabled(bool)), findPrevAction, SLOT(setEnabled(bool))); + + // allow parts to have a GUI, too :-) + // (will be merged automatically) + insertChildClient( multiPage ); +} + +void KViewPart::slotStartFitTimer() +{ + fitTimer.start(100, true); +} + + +QString KViewPart::pageSizeDescription() +{ + PageNumber nr = multiPage->currentPageNumber(); + if (!nr.isValid()) + return QString::null; + SimplePageSize ss = multiPage->sizeOfPage(nr); + if (!ss.isValid()) + return QString::null; + pageSize s(ss); + + QString size = " "; + if (s.formatNumber() == -1) { + if (KGlobal::locale()-> measureSystem() == KLocale::Metric) + size += QString("%1x%2 mm").arg(s.width().getLength_in_mm(), 0, 'f', 0).arg(s.height().getLength_in_mm(), 0, 'f', 0); + else + size += QString("%1x%2 in").arg(s.width().getLength_in_inch(), 0, 'g', 2).arg(s.height().getLength_in_inch(), 0, 'g', 2); + } else { + size += s.formatName() + "/"; + if (s.getOrientation() == 0) + size += i18n("portrait"); + else + size += i18n("landscape"); + } + return size+" "; +} + + +void KViewPart::restoreDocument(const KURL &url, int page) +{ + if (openURL(url)) + multiPage->gotoPage(page); +} + + +void KViewPart::saveDocumentRestoreInfo(KConfig* config) +{ + config->writePathEntry("URL", url().url()); + if (multiPage->numberOfPages() > 0) + config->writeEntry("Page", multiPage->currentPageNumber()); +} + + +void KViewPart::slotFileOpen() +{ + if ((!multiPage.isNull()) && (multiPage->isModified() == true)) { + int ans = KMessageBox::warningContinueCancel( 0, + i18n("Your document has been modified. Do you really want to open another document?"), + i18n("Warning - Document Was Modified"),KStdGuiItem::open()); + if (ans == KMessageBox::Cancel) + return; + } + + KURL url = KFileDialog::getOpenURL(QString::null, supportedMimeTypes().join(" ")); + + if (!url.isEmpty()) + openURL(url); +} + +QStringList KViewPart::supportedMimeTypes() +{ + QStringList supportedMimeTypes; + + // Search for service + KTrader::OfferList offers = KTrader::self()->query( + QString::fromLatin1("KViewShell/MultiPage"), + QString("([X-KDE-MultiPageVersion] == %1)").arg(MULTIPAGE_VERSION) + ); + + if (!offers.isEmpty()) + { + KTrader::OfferList::ConstIterator iterator = offers.begin(); + KTrader::OfferList::ConstIterator end = offers.end(); + + for (; iterator != end; ++iterator) + { + KService::Ptr service = *iterator; + QString mimeType = service->property("X-KDE-MimeTypes").toString(); + supportedMimeTypes << mimeType; + + } + } + + // The kviewshell is also able to read compressed files and to + // uncompress them on the fly. + + // Check if this version of KDE supports bzip2 + bool bzip2Available = (KFilterBase::findFilterByMimeType( "application/x-bzip2" ) != 0L); + + supportedMimeTypes << "application/x-gzip"; + + if (bzip2Available) + { + supportedMimeTypes << "application/x-bzip2"; + } + + return supportedMimeTypes; +} + +QStringList KViewPart::fileFormats() const +{ + // Compile a list of the supported filename patterns + + // First we build a list of the mimetypes which are supported by the + // currently installed KMultiPage-Plugins. + QStringList supportedMimeTypes; + QStringList supportedPattern; + + // Search for service + KTrader::OfferList offers = KTrader::self()->query( + QString::fromLatin1("KViewShell/MultiPage"), + QString("([X-KDE-MultiPageVersion] == %1)").arg(MULTIPAGE_VERSION) + ); + + if (!offers.isEmpty()) + { + KTrader::OfferList::ConstIterator iterator = offers.begin(); + KTrader::OfferList::ConstIterator end = offers.end(); + + for (; iterator != end; ++iterator) + { + KService::Ptr service = *iterator; + QString mimeType = service->property("X-KDE-MimeTypes").toString(); + supportedMimeTypes << mimeType; + + QStringList pattern = KMimeType::mimeType(mimeType)->patterns(); + while(!pattern.isEmpty()) + { + supportedPattern.append(pattern.front().stripWhiteSpace()); + pattern.pop_front(); + } + } + } + + // 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 multipages 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 compressedPattern; + + for(QStringList::Iterator it = supportedPattern.begin(); it != supportedPattern.end(); ++it ) + { + if ((*it).find(".gz", -3) == -1) // Paranoia safety check + compressedPattern.append(*it + ".gz"); + + if ((bzip2Available) && ((*it).find(".bz2", -4) == -1)) // Paranoia safety check + compressedPattern.append(*it + ".bz2"); + } + + while (!compressedPattern.isEmpty()) + { + supportedPattern.append(compressedPattern.front()); + compressedPattern.pop_front(); + } + + kdDebug(1223) << "Supported Pattern: " << supportedPattern << endl; + + return supportedPattern; +} + + +void KViewPart::slotSetFullPage(bool fullpage) +{ + if (multiPage) + multiPage->slotSetFullPage(fullpage); + else + kdError(1223) << "KViewPart::slotSetFullPage() called without existing multipage" << endl; + + // Restore normal view + if (fullpage == false) + { + slotShowSidebar(); + multiPage->slotShowScrollbars(scrollbarHandling->isChecked()); + } +} + + +void KViewPart::slotShowSidebar() +{ + bool show = showSidebar->isChecked(); + multiPage->slotShowSidebar(show); +} + + +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()) + { + QStringList supportedPatterns = fileFormats(); + QStringList endings; + + for (QStringList::Iterator it = supportedPatterns.begin(); it != supportedPatterns.end(); ++it) + { + // Only consider patterns starting with "*." + if ((*it).find("*.") == 0) + { + // Remove first Letter from string + endings.append((*it).mid(2).stripWhiteSpace()); + } + } + kdDebug(1223) << "Supported Endings: " << endings << endl; + + // Now try to append the endings with and without "." to the given filename, + // and see if that gives a existing file. + for (QStringList::Iterator it = endings.begin(); it != endings.end(); ++it) + { + fi.setFile(m_file+(*it)); + if (fi.exists()) + { + m_file = m_file+(*it); + break; + } + fi.setFile(m_file+"."+(*it)); + if (fi.exists()) + { + m_file = m_file+"."+(*it); + break; + } + } + + // If we still have not found a file. Show an error message and return. + if (!fi.exists()) + { + KMessageBox::error(mainWidget, i18n("File %1 does not exist.").arg(m_file)); + emit setStatusBarText(QString::null); + return false; + } + 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 setStatusBarText(i18n("Loading '%1'...").arg(tmpFileURL.prettyURL())); + 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("File Error! Could not create " + "temporary file.")); + emit setWindowCaption(QString::null); + emit setStatusBarText(QString::null); + return false; + } + tmpUnzipped->setAutoDelete(true); + if(tmpUnzipped->status() != 0) + { + KMessageBox::error(mainWidget, i18n("File Error! Could not create temporary file " + "%1.").arg(strerror(tmpUnzipped->status()))); + emit setWindowCaption(QString::null); + emit setStatusBarText(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); + emit setStatusBarText(QString::null); + return false; + } + if(!filterDev->open(IO_ReadOnly)) + { + KMessageBox::detailedError(mainWidget, i18n("File Error! Could not open the file " + "%1 for uncompression. " + "The file will not be loaded.").arg(m_file), + i18n("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.")); + emit setWindowCaption(QString::null); + delete filterDev; + emit setStatusBarText(QString::null); + return false; + } + + KProgressDialog* prog = new KProgressDialog(0L, "uncompress-progress", + i18n("Uncompressing..."), + i18n("Uncompressing the file %1. Please wait.").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; + + bool progress_dialog_was_cancelled = false; + while ((read = filterDev->readBlock(buf.data(), buf.size())) > 0) + { + kapp->processEvents(); + progress_dialog_was_cancelled = prog->wasCancelled(); + if (progress_dialog_was_cancelled) + break; + prog->progressBar()->advance(1); + + wrtn = tmpUnzipped->file()->writeBlock(buf.data(), read); + if(read != wrtn) + break; + } + delete filterDev; + delete prog; + tmpUnzipped->sync(); + + if (progress_dialog_was_cancelled) { + emit setStatusBarText(QString::null); + return false; + } + + if ((read != 0) || (tmpUnzipped->file()->size() == 0)) + { + KMessageBox::detailedError(mainWidget, i18n("File Error! Could not uncompress " + "the file %1. The file will not be loaded.").arg( m_file ), + i18n("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.")); + emit setWindowCaption(QString::null); + emit setStatusBarText(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); + // Pass the reference part of the URL through to the multipage + tmpFileURL.setRef(m_url.ref()); + + mimetype = KMimeType::findByURL(tmpFileURL); + + // Search for service + KTrader::OfferList offers = KTrader::self()->query( + QString::fromLatin1("KViewShell/MultiPage" ), + QString("([X-KDE-MultiPageVersion] == %1) and " + "([X-KDE-MimeTypes] == '%2')").arg(MULTIPAGE_VERSION).arg(mimetype->name())); + + if (offers.isEmpty()) { + KMessageBox::detailedError(mainWidget, i18n("The document %1 cannot be shown because " + "its file type is not supported.").arg(m_file), + i18n("The file has mime type %1 which is not supported by " + "any of the installed KViewShell plugins.").arg(mimetype->name())); + emit setWindowCaption(QString::null); + emit setStatusBarText(QString::null); + return false; + } + + KService::Ptr service = offers.first(); + + // The the new multiPage is different then the currently loaded one. + if (service->library() != multiPageLibrary) + { + // We write the settings before we load the new multipage, so + // that the new multipage gets the same settings than the + // currently loaded one. + writeSettings(); + + // Delete old config dialog + KConfigDialog* configDialog = KConfigDialog::exists("kviewshell_config"); + delete configDialog; + + KMultiPage* oldMultiPage = multiPage; + + // Try to load the multiPage + int error; + multiPage = static_cast(KParts::ComponentFactory::createInstanceFromService(service, mainWidget, + service->name().utf8(), QStringList(), &error )); + + if (multiPage.isNull()) { + QString reason; + switch(error) { + case KParts::ComponentFactory::ErrNoServiceFound: + reason = i18n("No service implementing the given mimetype and fullfilling the given constraint expression can be found."); + break; + case KParts::ComponentFactory::ErrServiceProvidesNoLibrary: + reason = i18n("The specified service provides no shared library."); + break; + case KParts::ComponentFactory::ErrNoLibrary: + reason = i18n("

The specified library %1 could not be loaded. The error message returned was:

%2

").arg(service->library()).arg(KLibLoader::self()->lastErrorMessage()); + break; + case KParts::ComponentFactory::ErrNoFactory: + reason = i18n("The library does not export a factory for creating components."); + break; + case KParts::ComponentFactory::ErrNoComponent: + reason = i18n("The factory does not support creating components of the specified type."); + break; + } + + QString text = i18n("

Problem: The document %1 cannot be shown.

" + "

Reason: The software " + "component %2 which is required to display files of type %3 could " + "not be initialized. This could point to serious misconfiguration of your KDE " + "system, or to damaged program files.

" + "

What you can do: You could try to re-install the software packages in " + "question. If that does not help, you could file an error report, either to the " + "provider of your software (e.g. the vendor of your Linux distribution), or " + "directly to the authors of the software. The entry Report Bug... in the " + "Help menu helps you to contact the KDE programmers.

").arg(m_file).arg(service->library()).arg(mimetype->name()); + QString caption = i18n("Error Initializing Software Component"); + if (reason.isEmpty()) + KMessageBox::error(mainWidget, text, caption); + else + KMessageBox::detailedError(mainWidget, text, reason, caption); + emit setStatusBarText(QString::null); + return false; + } + + // Remember the name of the part. So only need to switch if really necessary. + multiPageLibrary = service->library(); + + connect(partManager, SIGNAL(activePartChanged(KParts::Part*)), this, SIGNAL(pluginChanged(KParts::Part*))); + + // Switch to the new multiPage + partManager->replacePart(oldMultiPage, multiPage); + + delete oldMultiPage; + + // The next line makes the plugin switch much more smooth. Without it the new document + // is at first show at a very small zoomlevel before the zoom switches to the right value. + // This makes the plugin switching actually slower. + // TODO: Get rid of this without causing nasty artifacts. + kapp->processEvents(); + initializeMultiPage(); + partManager->setActivePart(this); + + // We disconnect because we dont want some FocusEvents to trigger a GUI update, which might mess + // with our menus. + disconnect(partManager, SIGNAL(activePartChanged(KParts::Part*)), this, SIGNAL(pluginChanged(KParts::Part*))); + + readSettings(); + } + + // Set the multipage to the current viewmode. + multiPage->setViewMode(viewModeAction->currentItem()); + + // Load the URL + bool r = multiPage->openURL(m_file, m_url); + updateZoomLevel(); // @@@@@@@@@@@@@ + + // We disable the selection tool for plugins that dont support text. + // Currently this is only the fax plugin. + if (multiPage->supportsTextSearch()) + { + selectionModeAction->setEnabled(true); + } + else + { + selectionModeAction->setEnabled(false); + moveModeAction->setChecked(true); + } + // Switch the new multipage to the right tool + slotEnableMoveTool(moveModeAction->isChecked()); + + if (r) { + // Add the file to the watchlist + watch->addFile( m_file ); + + // Notify the ViewShell about the newly opened file. + emit fileOpened(); + } else { + m_url = QString::null; + emit setWindowCaption(QString::null); + } + + checkActions(); + emit zoomChanged(QString("%1%").arg((int)(_zoomVal.value()*100.0+0.5))); + emit setStatusBarText(QString::null); + return r; +} + + +void KViewPart::reload() +{ + multiPage->reload(); +} + + +void KViewPart::fileChanged(const QString &file) +{ + if (file == m_file && watchAct->isChecked()) + multiPage->reload(); +} + + +bool KViewPart::closeURL_ask() +{ + if (multiPage.isNull()) + return false; + + if (multiPage->isModified() == true) { + int ans = KMessageBox::warningContinueCancel( 0, + i18n("Your document has been modified. Do you really want to close it?"), + i18n("Document Was Modified"), KStdGuiItem::close()); + if (ans == KMessageBox::Cancel) + return false; + } + + return closeURL(); +} + + +bool KViewPart::closeURL() +{ + if (multiPage.isNull()) + return false; + + if( watch && !m_file.isEmpty() ) + watch->removeFile( m_file ); + + KParts::ReadOnlyPart::closeURL(); + multiPage->closeURL(); + m_url = QString::null; + 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(1223) << "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(userRequestedPaperSize.serialize()); + _pageSizeDialog->show(); +} + + +void KViewPart::pageInfo(int numpages, int currentpage) +{ + updateZoomLevel(); + + // ATTN: The string here must be the same as in setPage() below + QString pageString = i18n("Page %1 of %2").arg(currentpage).arg(numpages); + if (pageChangeIsConnected) { + emit pageChanged(pageString); + emit sizeChanged(pageSizeDescription()); + } else + emit setStatusBarText(pageString); + + checkActions(); +} + + +void KViewPart::goToPage() +{ + bool ok = false; + int p = KInputDialog::getInteger(i18n("Go to Page"), i18n("Page:"), + multiPage->currentPageNumber(), 1, multiPage->numberOfPages(), + 1 /*step*/, &ok, mainWidget, "gotoDialog"); + if (ok) + multiPage->gotoPage(p); +} + + +void KViewPart::disableZoomFit() +{ + if (fitPageAct -> isChecked()) + { + fitPageAct -> setChecked(false); + enableFitToPage(false); + } + else if(fitWidthAct -> isChecked()) + { + fitWidthAct -> setChecked(false); + enableFitToWidth(false); + } + else if (fitHeightAct -> isChecked()) + { + fitHeightAct -> setChecked(false); + enableFitToHeight(false); + } +} + +void KViewPart::zoomIn() +{ + disableZoomFit(); + + float oldVal = _zoomVal.value(); + float newVal = _zoomVal.zoomIn(); + + if (oldVal != newVal) + _zoomVal.setZoomValue(multiPage->setZoom(_zoomVal.zoomIn())); +} + + +void KViewPart::zoomOut() +{ + disableZoomFit(); + + float oldVal = _zoomVal.value(); + float newVal = _zoomVal.zoomOut(); + + if (oldVal != newVal) + _zoomVal.setZoomValue(multiPage->setZoom(_zoomVal.zoomOut())); +} + +void KViewPart::updateZoomLevel() +{ + if (fitPageAct->isChecked()) + { + fitToPage(); + } + else if (fitWidthAct->isChecked()) + { + fitToWidth(); + } + else if (fitHeightAct->isChecked()) + { + fitToHeight(); + } + else + { + // Manuell Zoom + } +} + +void KViewPart::enableFitToPage(bool enable) +{ + if (enable) + { + fitToPage(); + connect(multiPage->mainWidget(), SIGNAL(viewSizeChanged(const QSize&)), + this, SLOT(slotStartFitTimer())); + connect(&fitTimer, SIGNAL(timeout()), SLOT(fitToPage())); + } + else + { + disconnect(multiPage->mainWidget(), SIGNAL(viewSizeChanged(const QSize&)), + this, SLOT(slotStartFitTimer())); + disconnect(&fitTimer, SIGNAL(timeout()), this, SLOT(fitToPage())); + } +} + +void KViewPart::enableFitToWidth(bool enable) +{ + if (enable) + { + fitToWidth(); + connect(multiPage->mainWidget(), SIGNAL(viewSizeChanged(const QSize&)), + this, SLOT(slotStartFitTimer())); + connect(&fitTimer, SIGNAL(timeout()), SLOT(fitToWidth())); + } + else + { + disconnect(multiPage->mainWidget(), SIGNAL(viewSizeChanged(const QSize&)), + this, SLOT(slotStartFitTimer())); + disconnect(&fitTimer, SIGNAL(timeout()), this, SLOT(fitToWidth())); + } +} + +void KViewPart::enableFitToHeight(bool enable) +{ + if (enable) + { + fitToHeight(); + connect(multiPage->mainWidget(), SIGNAL(viewSizeChanged(const QSize&)), + this, SLOT(slotStartFitTimer())); + connect(&fitTimer, SIGNAL(timeout()), SLOT(fitToHeight())); + } + else + { + disconnect(multiPage->mainWidget(), SIGNAL(viewSizeChanged(const QSize&)), + this, SLOT(slotStartFitTimer())); + disconnect(&fitTimer, SIGNAL(timeout()), this, SLOT(fitToHeight())); + } +} + + +void KViewPart::fitToPage() +{ + double z = QMIN(multiPage->calculateFitToHeightZoomValue(), + multiPage->calculateFitToWidthZoomValue()); + + // Check if the methods returned usable values. Values that are not + // within the limits indicate that fitting to width or height is + // currently not possible (e.g. because no document is + // loaded). In that case, we abort. + if ((z < ZoomLimits::MinZoom/1000.0) || (z > ZoomLimits::MaxZoom/1000.0)) + return; + + multiPage->setZoom(z); + _zoomVal.setZoomFitPage(z); +} + + +void KViewPart::fitToHeight() +{ + double z = multiPage->calculateFitToHeightZoomValue(); + + // Check if the method returned a usable value. Values that are not + // within the limits indicate that fitting to height is currently + // not possible (e.g. because no document is loaded). In that case, + // we abort. + if ((z < ZoomLimits::MinZoom/1000.0) || (z > ZoomLimits::MaxZoom/1000.0)) + return; + + multiPage->setZoom(z); + _zoomVal.setZoomFitHeight(z); +} + + +void KViewPart::fitToWidth() +{ + double z = multiPage->calculateFitToWidthZoomValue(); + + // Check if the method returned a usable value. Values that are not + // within the limits indicate that fitting to width is currently not + // possible (e.g. because no document is loaded). In that case, we + // abort. + if ((z < ZoomLimits::MinZoom/1000.0) || (z > ZoomLimits::MaxZoom/1000.0)) + return; + + multiPage->setZoom(z); + _zoomVal.setZoomFitWidth(z); +} + + +void KViewPart::setZoomValue(const QString &sval) +{ + if (sval == i18n("Fit to Page Width")) + { + fitWidthAct -> setChecked(true); + fitToWidth(); + } + else if (sval == i18n("Fit to Page Height")) + { + fitHeightAct -> setChecked(true); + fitToHeight(); + } + else if (sval == i18n("Fit to Page")) + { + fitPageAct -> setChecked(true); + fitToPage(); + } + else + { + disableZoomFit(); + + float fval = _zoomVal.value(); + _zoomVal.setZoomValue(sval); + if (fval != _zoomVal.value()) + _zoomVal.setZoomValue(multiPage->setZoom(_zoomVal.value())); + } + + mainWidget->setFocus(); +} + + +void KViewPart::checkActions() +{ + if (multiPage.isNull()) + return; + + int currentPage = multiPage->currentPageNumber(); + int numberOfPages = multiPage->numberOfPages(); + + bool doc = !url().isEmpty(); + + useDocumentSpecifiedSize->setEnabled(multiPage->hasSpecifiedPageSizes() ); + + if (multiPage->overviewMode()) + { + int visiblePages = multiPage->getNrRows() * + multiPage->getNrColumns(); + + // firstVisiblePage is the smallest currently shown pagenumber. + int firstVisiblePage = currentPage - (currentPage % visiblePages); + + backAct->setEnabled(doc && currentPage >= visiblePages); + forwardAct->setEnabled(doc && firstVisiblePage <= numberOfPages - visiblePages); + + startAct->setEnabled(doc && firstVisiblePage > 1); + endAct->setEnabled(doc && firstVisiblePage + visiblePages < numberOfPages); + } + else + { + backAct->setEnabled(doc && currentPage > 1); + forwardAct->setEnabled(doc && currentPage < numberOfPages); + + startAct->setEnabled(doc && currentPage > 1); + endAct->setEnabled(doc && currentPage < numberOfPages); + } + + gotoAct->setEnabled(doc && numberOfPages > 1); + readDownAct->setEnabled(doc); + readUpAct->setEnabled(doc); + + zoomInAct->setEnabled(doc); + zoomOutAct->setEnabled(doc); + + fitPageAct->setEnabled(doc); + fitHeightAct->setEnabled(doc); + fitWidthAct->setEnabled(doc); + + media->setEnabled(doc); + orientation->setEnabled(doc); + + printAction->setEnabled(doc); + + saveAction->setEnabled(multiPage->isModified()); + + saveAsAction->setEnabled(doc); + + 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); + } + + bool textSearch = false; + if (doc && multiPage->supportsTextSearch()) + textSearch = true; + + exportTextAction->setEnabled(textSearch); + findTextAction->setEnabled(textSearch); + selectAllAction->setEnabled(textSearch); +} + + +void KViewPart::slotPrint() +{ + // TODO: REMOVE THIS METHOD + // @@@@@@@@@@@@@@@ + multiPage->print(); +} + + +void KViewPart::readSettings() +{ + showSidebar->setChecked(KVSPrefs::pageMarks()); + slotShowSidebar(); + + watchAct->setChecked(KVSPrefs::watchFile()); + + // Read zoom value. Even if 'fitToPage' has been set above, there is + // no widget available right now, so setting a good default value + // from the configuration file is perhaps not a bad idea. + float _zoom = KVSPrefs::zoom(); + if ( (_zoom < ZoomLimits::MinZoom/1000.0) || (_zoom > ZoomLimits::MaxZoom/1000.0)) { + kdWarning(1223) << "Illeagal zoom value of " << _zoom*100.0 << "% found in the preferences file. Setting zoom to 100%." << endl; + _zoom = 1.0; + } + _zoomVal.setZoomValue(multiPage->setZoom(_zoom)); + + // The value 'fitToPage' has several meanings: 1 is 'fit to page + // width', 2 is 'fit to page height', 3 is 'fit to page'. Other + // values indicate 'no fit to page'. Note: at the time this code is + // executed, the methods fitToWidth(), etc., do not work well at all + // (perhaps some data is not initialized yet)? For that reason, we + // do not call these methods, and load the last zoom-value from the + // configuration file below. The hope is that this value is not + // terribly wrong. If the user doesn't like it, it suffices to + // resize the window just a bit... + switch(KVSPrefs::fitToPage()) { + case KVSPrefs::EnumFitToPage::FitToPage: + fitPageAct->setChecked(true); + _zoomVal.setZoomFitPage(_zoom); + enableFitToPage(true); + break; + case KVSPrefs::EnumFitToPage::FitToPageWidth: + fitWidthAct->setChecked(true); + _zoomVal.setZoomFitWidth(_zoom); + enableFitToWidth(true); + break; + case KVSPrefs::EnumFitToPage::FitToPageHeight: + fitHeightAct->setChecked(true); + _zoomVal.setZoomFitHeight(_zoom); + enableFitToHeight(true); + break; + } + + // Read Paper Size. and orientation. The GUI is updated + // automatically by the signals/slots mechanism whenever + // userRequestedPaperSize is changed. + userRequestedPaperSize.setPageSize(KVSPrefs::paperFormat()); + + // Check if scrollbars should be shown. + bool sbstatus = KVSPrefs::scrollbars(); + 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(KVSPrefs::useDocumentSpecifiedSize()); + + multiPage->readSettings(); +} + + +void KViewPart::writeSettings() +{ + KVSPrefs::setPageMarks(showSidebar->isChecked()); + KVSPrefs::setWatchFile(watchAct->isChecked()); + KVSPrefs::setZoom(_zoomVal.value()); + KVSPrefs::setPaperFormat(userRequestedPaperSize.serialize()); + KVSPrefs::setScrollbars(scrollbarHandling->isChecked()); + KVSPrefs::setUseDocumentSpecifiedSize(useDocumentSpecifiedSize->isChecked()); + + if (!multiPage.isNull()) + multiPage->writeSettings(); + + if (fitPageAct->isChecked()) + KVSPrefs::setFitToPage(KVSPrefs::EnumFitToPage::FitToPage); + else if(fitWidthAct->isChecked()) + KVSPrefs::setFitToPage(KVSPrefs::EnumFitToPage::FitToPageWidth); + else if (fitHeightAct->isChecked()) + KVSPrefs::setFitToPage(KVSPrefs::EnumFitToPage::FitToPageHeight); + else + KVSPrefs::setFitToPage(KVSPrefs::EnumFitToPage::DontFit); + + KVSPrefs::writeConfig(); +} + + +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 + { + int currentPage = multiPage->currentPageNumber(); + int numberOfPages = multiPage->numberOfPages(); + emit setStatusBarText(i18n("Page %1 of %2").arg(currentPage).arg(numberOfPages)); + } + } + else + emit setStatusBarText(msg); +} + +KAboutData* KViewPart::createAboutData() +{ + return new KAboutData("kviewerpart", I18N_NOOP("Document Viewer Part"), + "0.6", I18N_NOOP(""), + KAboutData::License_GPL, + I18N_NOOP("Copyright (c) 2005 Wilfried Huss")); +} + +void KViewPart::aboutKViewShell() +{ + if (aboutDialog == 0) + { + // Create Dialog + aboutDialog = new KAboutDialog(mainWidget, "about_kviewshell"); + aboutDialog->setTitle(I18N_NOOP("KViewShell")); + aboutDialog->setVersion("0.6"); + aboutDialog->setAuthor("Matthias Hoelzer-Kluepfel", QString::null, QString::null, + I18N_NOOP("Original Author")); + + aboutDialog->addContributor("Matthias Hoelzer-Kluepfel", "mhk@caldera.de", QString::null, + I18N_NOOP("Framework")); + aboutDialog->addContributor("David Sweet", "dsweet@kde.org", "http://www.chaos.umd.edu/~dsweet", + I18N_NOOP("Former KGhostView Maintainer")); + aboutDialog->addContributor("Mark Donohoe", QString::null, QString::null, + I18N_NOOP("KGhostView Author")); + aboutDialog->addContributor("Markku Hihnala", QString::null, QString::null, + I18N_NOOP("Navigation widgets")); + aboutDialog->addContributor("David Faure", QString::null, QString::null, + I18N_NOOP("Basis for shell")); + aboutDialog->addContributor("Daniel Duley", QString::null, QString::null, + I18N_NOOP("Port to KParts")); + aboutDialog->addContributor("Espen Sand", QString::null, QString::null, + I18N_NOOP("Dialog boxes")); + aboutDialog->addContributor("Stefan Kebekus", "kebekus@kde.org", QString::null, + I18N_NOOP("DCOP-Interface, major improvements")); + aboutDialog->addContributor("Wilfried Huss", "Wilfried.Huss@gmx.at", QString::null, + I18N_NOOP("Interface enhancements")); + } + aboutDialog->show(); +} + +void KViewPart::doSettings() +{ + if (KConfigDialog::showDialog("kviewshell_config")) + return; + + KConfigDialog* configDialog = new KConfigDialog(mainWidget, "kviewshell_config", KVSPrefs::self()); + + optionDialogGUIWidget_base* guiWidget = new optionDialogGUIWidget_base(mainWidget); + configDialog->addPage(guiWidget, i18n("User Interface"), "view_choose"); + + optionDialogAccessibilityWidget* accWidget = new optionDialogAccessibilityWidget(mainWidget); + configDialog->addPage(accWidget, i18n("Accessibility"), "access"); + + multiPage->addConfigDialogs(configDialog); + + connect(configDialog, SIGNAL(settingsChanged()), this, SLOT(preferencesChanged())); + configDialog->show(); +} + +void KViewPart::preferencesChanged() +{ + multiPage->preferencesChanged(); +} + +void KViewPart::partActivateEvent( KParts::PartActivateEvent *ev ) +{ + QApplication::sendEvent( multiPage, ev ); +} + + +void KViewPart::guiActivateEvent( KParts::GUIActivateEvent *ev ) +{ + QApplication::sendEvent( multiPage, ev ); +} + + +void KViewPart::slotEnableMoveTool(bool enable) +{ + // Safety Check + if (multiPage.isNull()) + return; + + multiPage->slotEnableMoveTool(enable); +} + + +KViewPartExtension::KViewPartExtension(KViewPart *parent) + : KParts::BrowserExtension( parent, "KViewPartExtension") +{ +} + + +// KMultiPage Interface +void KViewPart::mp_prevPage() +{ + multiPage->prevPage(); +} + +void KViewPart::mp_nextPage() +{ + multiPage->nextPage(); +} + +void KViewPart::mp_firstPage() +{ + multiPage->firstPage(); +} + +void KViewPart::mp_lastPage() +{ + multiPage->lastPage(); +} + + +void KViewPart::mp_readUp() +{ + multiPage->readUp(); +} + +void KViewPart::mp_readDown() +{ + multiPage->readDown(); +} + + +void KViewPart::mp_scrollUp() +{ + multiPage->scrollUp(); +} + +void KViewPart::mp_scrollDown() +{ + multiPage->scrollDown(); +} + +void KViewPart::mp_scrollLeft() +{ + multiPage->scrollLeft(); +} + +void KViewPart::mp_scrollRight() +{ + multiPage->scrollRight(); +} + +void KViewPart::mp_scrollUpPage() +{ + multiPage->scrollUpPage(); +} + +void KViewPart::mp_scrollDownPage() +{ + multiPage->scrollDownPage(); +} + +void KViewPart::mp_scrollLeftPage() +{ + multiPage->scrollLeftPage(); +} + +void KViewPart::mp_scrollRightPage() +{ + multiPage->scrollRightPage(); +} + + +void KViewPart::mp_slotSave() +{ + multiPage->slotSave(); +} + +void KViewPart::mp_slotSave_defaultFilename() +{ + multiPage->slotSave_defaultFilename(); +} + + +void KViewPart::mp_doGoBack() +{ + multiPage->doGoBack(); +} + +void KViewPart::mp_doGoForward() +{ + multiPage->doGoForward(); +} + + +void KViewPart::mp_showFindTextDialog() +{ + multiPage->showFindTextDialog(); +} + +void KViewPart::mp_findNextText() +{ + multiPage->findNextText(); +} + +void KViewPart::mp_findPrevText() +{ + multiPage->findPrevText(); +} + +void KViewPart::mp_doSelectAll() +{ + multiPage->doSelectAll(); +} + +void KViewPart::mp_clearSelection() +{ + multiPage->clearSelection(); +} + +void KViewPart::mp_copyText() +{ + multiPage->copyText(); +} + +void KViewPart::mp_exportText() +{ + multiPage->doExportText(); +} + +#include "kviewpart.moc" + diff --git a/kviewshell/kviewpart.h b/kviewshell/kviewpart.h new file mode 100644 index 00000000..f30b1109 --- /dev/null +++ b/kviewshell/kviewpart.h @@ -0,0 +1,252 @@ +// -*- C++ -*- +#ifndef KVIEWPART_H +#define KVIEWPART_H + +#include "kviewpart_iface.h" +#include "pageSize.h" +#include "zoom.h" + +#include +#include + +class KAboutData; +class KAboutDialog; +class KAccel; +class KAction; +class KConfig; +class KDirWatch; +class KInstance; +class KMultiPage; +class KRadioAction; +class KSelectAction; +class KTempFile; +class KToggleAction; +class KURL; +class KViewPartExtension; +class QHBoxLayout; +class pageSizeDialog; +class QSize; + + +class KViewPart : public KViewPart_Iface +{ + Q_OBJECT + +public: + KViewPart(QWidget *parentWidget, const char *widgetName, QObject *parent, + const char *name, const QStringList& args); + virtual ~KViewPart(); + + static KAboutData* createAboutData(); + + bool isValid() { return multiPage; } + + /* This method calls closeURL(), but asks first ("The document was + modified. Do you really want to close?") if the document has been + modified after it has been loaded. */ + virtual bool closeURL_ask(); + + /* Returns a description of the current page size, for use in the + statusbar of the kviewshell that embeds this KViewPart. */ + QString pageSizeDescription(); + + /** Returns a list of mimetypes supported by the installed KViewShell plugins. */ + virtual QStringList supportedMimeTypes(); + +signals: + void zoomChanged(const QString &); + void pageChanged(const QString &); + void sizeChanged(const QString &); + void scrollbarStatusChanged(bool); + void fileOpened(); + + void pluginChanged(KParts::Part*); + +public slots: + void slotSetFullPage(bool fullpage); + + virtual void slotFileOpen(); + virtual bool closeURL(); + virtual QStringList fileFormats() const; + void setStatusBarTextFromMultiPage(const QString &); + + /** Calling this slot will cause the kmultipage to reload the file */ + void reload(); + + void restoreDocument(const KURL &url, int page); + void saveDocumentRestoreInfo(KConfig* config); + +protected slots: + void slotShowSidebar(); + void slotMedia (int); + + void goToPage(); + + void zoomIn(); + void zoomOut(); + + void disableZoomFit(); + void updateZoomLevel(); + + void enableFitToPage(bool); + void enableFitToHeight(bool); + void enableFitToWidth(bool); + + void fitToPage(); + void fitToHeight(); + void fitToWidth(); + + void slotPrint(); + + void fileChanged(const QString&); + + // Connected to the QLineEdit in the toolbar. + void setZoomValue(const QString &); + + +protected: + KToggleAction *showSidebar, *scrollbarHandling; + KSelectAction *orientation, *media, *zoom_action; + + virtual bool openFile(); + + void connectNotify ( const char * ); + + void partActivateEvent( KParts::PartActivateEvent *ev ); + void guiActivateEvent( KParts::GUIActivateEvent *ev ); + +private slots: + void pageInfo(int numpages, int currentpage); + void checkActions(); + + void slotStartFitTimer(); + + void doSettings(); + void preferencesChanged(); + + void aboutKViewShell(); + + void slotEnableMoveTool(bool enable); + + // Relay signals to the multipage. We cannot connect to the + // slots of the multipage directly because than we would have + // to recreate the whole interface whenever a new multipage is loaded. + void mp_prevPage(); + void mp_nextPage(); + void mp_firstPage(); + void mp_lastPage(); + + void mp_readUp(); + void mp_readDown(); + + void mp_scrollUp(); + void mp_scrollDown(); + void mp_scrollLeft(); + void mp_scrollRight(); + void mp_scrollUpPage(); + void mp_scrollDownPage(); + void mp_scrollLeftPage(); + void mp_scrollRightPage(); + + void mp_slotSave(); + void mp_slotSave_defaultFilename(); + + void mp_doGoBack(); + void mp_doGoForward(); + + void mp_showFindTextDialog(); + void mp_findNextText(); + void mp_findPrevText(); + + void mp_doSelectAll(); + void mp_clearSelection(); + + void mp_copyText(); + + void mp_exportText(); + +private: + void initializeMultiPage(); + + // This method reads the configuration file. It should only be + // called when no document is loaded. + void readSettings(); + void writeSettings(); + + // The method openFile of this kviewpart can be called even if + // m_file points to a compressed file. In that case, the temporary + // file tmpUnzipped will be created and a decompressed copy of the + // file stored there. + KTempFile *tmpUnzipped; + + KDirWatch *watch; + KAccel *accel; + KAction *zoomInAct, *zoomOutAct, *backAct, *forwardAct, + *startAct, *endAct, *gotoAct, + *saveAction, *saveAsAction, *printAction, *readUpAct, *readDownAct; + KAction *backAction; + KAction *forwardAction; + KAction *settingsAction; + KAction* aboutAction; + KToggleAction *watchAct,*useDocumentSpecifiedSize, + *fitPageAct, *fitHeightAct, *fitWidthAct; + KSelectAction* viewModeAction; + + KRadioAction* moveModeAction; + KRadioAction* selectionModeAction; + + KAction* exportTextAction; + + KAction* copyTextAction; + KAction* selectAllAction; + KAction* deselectAction; + KAction* findTextAction; + KAction* findNextTextAction; + KAction* findPrevAction; + KAction* findNextAction; + + KParts::PartManager* partManager; + + QGuardedPtr multiPage; + // Name of the library of the currently loaded multiPage. + // Is used to check if it is really necessary to load a new MultiPage. + QString multiPageLibrary; + + KViewPartExtension *m_extension; + + bool pageChangeIsConnected; + + QWidget *mainWidget; + QHBoxLayout* mainLayout; + + /** This entry stores the paper size that the user has requested in + the preferences dialog. If that paper size is actually used or + not, depends on if the document specifies a paper size of its + own and if the user has chosen the option "use document + specified paper size if available" */ + pageSize userRequestedPaperSize; + + /** stores the current zoom value */ + Zoom _zoomVal; + pageSizeDialog *_pageSizeDialog; + + QTimer fitTimer; + + KAboutDialog* aboutDialog; +}; + + +class KViewPartExtension : public KParts::BrowserExtension +{ + Q_OBJECT + friend class KViewPart; + +public: + + KViewPartExtension(KViewPart *parent); + virtual ~KViewPartExtension() {} + +}; + + +#endif diff --git a/kviewshell/kviewpart_iface.cpp b/kviewshell/kviewpart_iface.cpp new file mode 100644 index 00000000..b55f7ac1 --- /dev/null +++ b/kviewshell/kviewpart_iface.cpp @@ -0,0 +1,4 @@ +#include + +#include "kviewpart_iface.h" +#include "kviewpart_iface.moc" diff --git a/kviewshell/kviewpart_iface.h b/kviewshell/kviewpart_iface.h new file mode 100644 index 00000000..9e488f3e --- /dev/null +++ b/kviewshell/kviewpart_iface.h @@ -0,0 +1,35 @@ +// -*- C++ -*- +#ifndef KVIEWPART_IFACE_H +#define KVIEWPART_IFACE_H + +#include + +class QStringList; + + +class KViewPart_Iface : public KParts::ReadOnlyPart +{ + Q_OBJECT +public: + KViewPart_Iface(QObject *parent, const char *name) + : KParts::ReadOnlyPart(parent, name) {} + + virtual ~KViewPart_Iface() {} + + /* Returns a description of the current page size, for use in the + statusbar of the kviewshell that embeds this KViewPart. */ + virtual QString pageSizeDescription() = 0; + + /* This method calls closeURL(), but asks first ("The document was + modified. Do you really want to close?") if the document has been + modified after it has been loaded. */ + virtual bool closeURL_ask() = 0; + + virtual QStringList supportedMimeTypes() = 0; + +public slots: + virtual void slotSetFullPage(bool fullpage) = 0; + virtual QStringList fileFormats() const = 0; +}; + +#endif diff --git a/kviewshell/kviewshell.cpp b/kviewshell/kviewshell.cpp new file mode 100644 index 00000000..90e1e474 --- /dev/null +++ b/kviewshell/kviewshell.cpp @@ -0,0 +1,384 @@ +/* + * Parts of this file are + * Copyright 2003 Waldo Bastian + * + * These parts are free software; you can redistribute and/or modify + * them under the terms of the GNU General Public License as published by + * the Free Software Foundation; version 2. + */ + + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include "kviewpart_iface.h" +#include +#include +#include "kviewshell.moc" + + +#define StatusBar_ID_PageNr 1 +#define StatusBar_ID_PageSize 2 +#define StatusBar_ID_Zoom 3 + + +KViewShell::KViewShell(const QString& defaultMimeType) + : KParts::MainWindow() +{ + // create the viewer part + + // Try to load + KLibFactory *factory = KLibLoader::self()->factory("kviewerpart"); + if (factory) { + if (defaultMimeType == QString::null) + { + view = (KViewPart_Iface*) factory->create(this, "kviewerpart", "KViewPart"); + } + else + { + QStringList args; + args << defaultMimeType; + view = (KViewPart_Iface*) factory->create(this, "kviewerpart", "KViewPart", args); + } + if (!view) + ::exit(-1); + } else { + KMessageBox::error(this, i18n("No viewing component found")); + ::exit(-1); + } + + setCentralWidget(view->widget()); + + // file menu + KStdAction::open(view, SLOT(slotFileOpen()), actionCollection()); + recent = KStdAction::openRecent (this, SLOT(openURL(const KURL &)), actionCollection()); + reloadAction = new KAction(i18n("Reload"), "reload", CTRL + Key_R, view, SLOT(reload()), actionCollection(), "reload"); + closeAction = KStdAction::close(this, SLOT(slotFileClose()), actionCollection()); + KStdAction::quit (this, SLOT(slotQuit()), actionCollection()); + + connect(view, SIGNAL(fileOpened()), this, SLOT(addRecentFile())); + + // view menu + fullScreenAction = KStdAction::fullScreen(this, SLOT(slotFullScreen()), actionCollection(), this, "fullscreen" ); + + // settings menu + createStandardStatusBarAction(); + + setStandardToolBarMenuEnabled(true); + + KStdAction::keyBindings(this, SLOT(slotConfigureKeys()), actionCollection()); + KStdAction::configureToolbars(this, SLOT(slotEditToolbar()), actionCollection()); + + // statusbar connects + connect( view, SIGNAL( zoomChanged(const QString &) ), this,SLOT( slotChangeZoomText(const QString &) ) ); + connect( view, SIGNAL( pageChanged(const QString &) ), this,SLOT( slotChangePageText(const QString &) ) ); + connect( view, SIGNAL( sizeChanged(const QString &) ), this,SLOT( slotChangeSizeText(const QString &) ) ); + + // Setup session management + connect( this, SIGNAL( restoreDocument(const KURL &, int) ), view, SLOT( restoreDocument(const KURL &, int))); + connect( this, SIGNAL( saveDocumentRestoreInfo(KConfig*) ), view, SLOT( saveDocumentRestoreInfo(KConfig*))); + + setXMLFile( "kviewshell.rc" ); + createGUI(view); + readSettings(); + checkActions(); + setAcceptDrops(true); + + // If kviewshell is started when another instance of kviewshell runs + // in fullscreen mode, the menubar is switched off by default, which + // is a nasty surprise for the user: there is no way to access the + // menus. To avoid such complications, we switch the menubar on + // explicitly. + menuBar()->show(); + + // Add statusbar-widgets for zoom, pagenr and format + statusBar()->insertItem("", StatusBar_ID_PageNr, 0, true); + statusBar()->insertFixedItem("XXXX%", StatusBar_ID_Zoom, true); + statusBar()->changeItem("", StatusBar_ID_Zoom); + statusBar()->insertItem(view->pageSizeDescription(), StatusBar_ID_PageSize, 0, true); + + connect( view, SIGNAL(pluginChanged(KParts::Part*)), this, SLOT(createGUI(KParts::Part*))); +} + + +void KViewShell::checkActions() +{ + bool doc = !view->url().isEmpty(); + + closeAction->setEnabled(doc); + reloadAction->setEnabled(doc); + fullScreenAction->setEnabled(doc); +} + + +KViewShell::~KViewShell() +{ + writeSettings(); + delete view; +} + + +void KViewShell::slotQuit() +{ + // If we are to quit the application while we operate in fullscreen + // mode, we need to restore the visibility properties of the + // statusbar, toolbar and the menus because these properties are + // saved automatically ... and we don't want to start next time + // without having menus. + if (fullScreenAction->isChecked()) { + kdDebug() << "Switching off fullscreen mode before quitting the application" << endl; + showNormal(); + if (isStatusBarShownInNormalMode) + statusBar()->show(); + if (isToolBarShownInNormalMode) + toolBar()->show(); + menuBar()->show(); + view->slotSetFullPage(false); + } + kapp->closeAllWindows(); + kapp->quit(); +} + + +void KViewShell::readSettings() +{ + resize(600, 300); // default size if the config file specifies no size + setAutoSaveSettings( "General" ); // apply mainwindow settings (size, toolbars, etc.) + + KConfig *config = kapp->config(); + config->setGroup("General"); + + recent->loadEntries(config, "Recent Files"); + + // Constant source of annoyance in KDVI < 1.0: the 'recent-files' + // menu contains lots of files which don't exist (any longer). Thus, + // we'll sort out the non-existent files here. + QStringList items = recent->items(); + for ( QStringList::Iterator it = items.begin(); it != items.end(); ++it ) { + KURL url(*it); + if (url.isLocalFile()) { + QFileInfo info(url.path()); + if (!info.exists()) + recent->removeURL(url); + } + } + +} + + +void KViewShell::writeSettings() +{ + KConfig *config = kapp->config(); + config->setGroup( "General" ); + recent->saveEntries(config, "Recent Files"); + + config->sync(); +} + + +void KViewShell::saveProperties(KConfig* config) +{ + // the 'config' object points to the session managed + // config file. anything you write here will be available + // later when this app is restored + emit saveDocumentRestoreInfo(config); +} + + +void KViewShell::readProperties(KConfig* config) +{ + // the 'config' object points to the session managed + // config file. this function is automatically called whenever + // the app is being restored. read in here whatever you wrote + // in 'saveProperties' + if (view) + { + KURL url (config->readPathEntry("URL")); + if (url.isValid()) + emit restoreDocument(url, config->readNumEntry("Page", 1)); + } +} + + +void KViewShell::addRecentFile() +{ + // Get the URL of the opened file from the kviewpart. + KURL actualURL = view->url(); + // To store the URL in the list of recent files, we remove the + // reference part. + actualURL.setRef(QString::null); + recent->addURL(actualURL); + checkActions(); +} + +void KViewShell::openURL(const KURL& url) +{ + view->openURL(url); +} + +void KViewShell::slotFullScreen() +{ + if (fullScreenAction->isChecked()) { + // In fullscreen mode, menu- tool- and statusbar are hidden. Save + // the visibility flags of these objects here, so that they can + // later be properly restored when we switch back to normal mode, + // or before we leave the application in slotQuit() + isStatusBarShownInNormalMode = statusBar()->isShown(); + statusBar()->hide(); + isToolBarShownInNormalMode = toolBar()->isShown(); + toolBar()->hide(); + menuBar()->hide(); + view->slotSetFullPage(true); + + // Go to fullscreen mode + showFullScreen(); + + KMessageBox::information(this, i18n("Use the Escape key to leave the fullscreen mode."), i18n("Entering Fullscreen Mode"), "leavingFullScreen"); + } else { + showNormal(); + if (isStatusBarShownInNormalMode) + statusBar()->show(); + if (isToolBarShownInNormalMode) + toolBar()->show(); + menuBar()->show(); + view->slotSetFullPage(false); + } +} + + +void KViewShell::slotFileClose() +{ + view->closeURL_ask(); + + checkActions(); +} + +void KViewShell::slotConfigureKeys() +{ + KKeyDialog dlg( true, this ); + + dlg.insert( actionCollection() ); + dlg.insert( view->actionCollection() ); + + dlg.configure(); +} + +void KViewShell::slotEditToolbar() +{ + saveMainWindowSettings( KGlobal::config(), autoSaveGroup() ); + KEditToolbar dlg(factory()); + connect( &dlg, SIGNAL( newToolbarConfig() ), SLOT( slotNewToolbarConfig() ) ); + dlg.exec(); +} + + +void KViewShell::slotNewToolbarConfig() +{ + applyMainWindowSettings( KGlobal::config(), autoSaveGroup() ); +} + +void KViewShell::dragEnterEvent(QDragEnterEvent *event) +{ + if (KURLDrag::canDecode(event)) + { + KURL::List urls; + KURLDrag::decode(event, urls); + if (!urls.isEmpty()) + { + KURL url = urls.first(); + + // Always try to open remote files + if (!url.isLocalFile()) + { + event->accept(); + return; + } + + // For local files we only accept a drop, if we have a plugin for its + // particular mimetype + KMimeType::Ptr mimetype = KMimeType::findByURL(url); + kdDebug() << "[dragEnterEvent] Dragged URL is of type " << mimetype->comment() << endl; + + // Safety check + if (view) + { + QStringList mimetypeList = view->supportedMimeTypes(); + kdDebug() << "[dragEnterEvent] Supported mime types: " << mimetypeList << endl; + + for (QStringList::Iterator it = mimetypeList.begin(); it != mimetypeList.end(); ++it) + { + if (mimetype->is(*it)) + { + kdDebug() << "[dragEnterEvent] Found matching mimetype: " << *it << endl; + event->accept(); + return; + } + } + kdDebug() << "[dragEnterEvent] no matching mimetype found" << endl; + } + } + event->ignore(); + } +} + + +void KViewShell::dropEvent(QDropEvent *event) +{ + KURL::List urls; + if (KURLDrag::decode(event, urls) && !urls.isEmpty()) + view->openURL(urls.first()); +} + + +void KViewShell::keyPressEvent(QKeyEvent *event) +{ + // The Escape Key is used to return to normal mode from fullscreen + // mode + if ((event->key() == Qt::Key_Escape) && (fullScreenAction->isChecked())) { + showNormal(); + return; + } + // If we can't use the key event, pass it on + event->ignore(); +} + + +void KViewShell::slotChangePageText(const QString &message) +{ + statusBar()->changeItem(" "+message+" ",StatusBar_ID_PageNr); +} + + +void KViewShell::slotChangeSizeText(const QString &message) +{ + statusBar()->changeItem(" "+message+" ",StatusBar_ID_PageSize); +} + + +void KViewShell::slotChangeZoomText(const QString &message) +{ + statusBar()->changeItem(" "+message+" ",StatusBar_ID_Zoom); +} diff --git a/kviewshell/kviewshell.h b/kviewshell/kviewshell.h new file mode 100644 index 00000000..c6aa0620 --- /dev/null +++ b/kviewshell/kviewshell.h @@ -0,0 +1,89 @@ +// -*- C++ -*- +#ifndef KVIEWSHELL_H +#define KVIEWSHELL_H + +#include + +#include + +class KRecentFilesAction; +class KURL; +class KViewPart_Iface; + + +class QLabel; + +class KViewShell : public KParts::MainWindow +{ + Q_OBJECT + +public: + KStatusBar *statusbar; + KStatusBar *action; + + KViewShell(const QString& defaultMimeType = QString::null); + virtual ~KViewShell(); + +public slots: + void openURL(const KURL&); + void addRecentFile(); + + +protected slots: + void slotFullScreen(); + void slotQuit(); + void slotConfigureKeys(); + void slotEditToolbar(); + void slotFileClose(); + void slotNewToolbarConfig(); + + void slotChangeZoomText(const QString &); + void slotChangePageText(const QString &); + void slotChangeSizeText(const QString &); + +signals: + void restoreDocument(const KURL &url, int page); + void saveDocumentRestoreInfo(KConfig* config); + +protected: + void readSettings(); + void writeSettings(); + + /** + * This method is called when it is time for the app to save its + * properties for session management purposes. + */ + void saveProperties(KConfig*); + + /** + * This method is called when this app is restored. The KConfig + * object points to the session management config file that was saved + * with @ref saveProperties + */ + void readProperties(KConfig*); + + void checkActions(); + + void dragEnterEvent(QDragEnterEvent *event); + void dropEvent(QDropEvent *event); + void keyPressEvent(QKeyEvent * e); + +private: + KViewPart_Iface *view; + + KRecentFilesAction *recent; + QString cwd; + + KAction *closeAction, *reloadAction; + KToggleAction *fullScreenAction; + + // In the attribute, the status of the statusbar (shown of hidden) + // is saved when the kviewshell switches to fullscreen mode. The + // statusbar can then be restored when the application returns to normal mode. + bool isStatusBarShownInNormalMode; + // ditto, for the toolbar + bool isToolBarShownInNormalMode; +}; + + +#endif diff --git a/kviewshell/kviewshell.kcfg b/kviewshell/kviewshell.kcfg new file mode 100644 index 00000000..3114929c --- /dev/null +++ b/kviewshell/kviewshell.kcfg @@ -0,0 +1,115 @@ + + + + + + true + + + true + + + 1.0 + + + + + + true + + + true + + + Continuous + + + + + + + + + DontFit + + + + + + + + + 4 + + + 2 + + + true + + + 200 + + + Enabled + + + + + + + <qt> + Controls how hyperlinks are underlined: + <ul> + <li><b>UL_Enabled</b>: Always underline links</li> + <li><b>UL_Disabled</b>: Never underline links</li> + <li><b>UL_OnlyOnHover</b>: Underline when the mouse is moved over the link</li> + </ul> + </qt> + + + + + 1 + + + + + Qt::white + + + false + + + Inverted + + + + + + + + + + + + 0x600000 + + + 0xF0F0F0 + + + 127 + 2 + 253 + + + 2 + 2 + 6 + + + diff --git a/kviewshell/kviewshell.rc b/kviewshell/kviewshell.rc new file mode 100644 index 00000000..480a868a --- /dev/null +++ b/kviewshell/kviewshell.rc @@ -0,0 +1,37 @@ + + + + &File + + + + + + &Edit + + + &View + + + &Go + + + + &Settings + + + + + + + &Help + + + + + + + + + + diff --git a/kviewshell/kvsprefs.kcfgc b/kviewshell/kvsprefs.kcfgc new file mode 100644 index 00000000..c507ba3d --- /dev/null +++ b/kviewshell/kvsprefs.kcfgc @@ -0,0 +1,5 @@ +# Code generation options for kconfig_compiler +File=kviewshell.kcfg +ClassName=KVSPrefs +Singleton=true +Mutators=true diff --git a/kviewshell/length.h b/kviewshell/length.h new file mode 100644 index 00000000..149c8a10 --- /dev/null +++ b/kviewshell/length.h @@ -0,0 +1,173 @@ +// -*- C++ -*- +// +// Class: length +// +// Part of KVIESHELL +// +// (C) 2005 Stefan Kebekus. Distributed under the GPL. + + +#ifndef _length_h_ +#define _length_h_ + +#define mm_per_cm 10.0 +#define mm_per_m 1000.0 +#define mm_per_inch 25.4 +#define mm_per_TeXPoint (2540.0/7227.0) +#define mm_per_bigPoint (25.4/72.0) +#define mm_per_pica (25.4/6.0) +#define mm_per_didot (25.4*0.0148) +#define mm_per_cicero (25.4*0.178) +#define mm_per_scaledPoint (25.4/(72.27 * 65536.0)) + + +#include + +/** @short Represents a phyical length + + This class is used to represent a physical length. Its main purpose + it to help in the conversion of units, and to avoid confusion + about units. To avoid misunderstandings, there is no default + constructor so that this class needs to be explicitly initialized + with one of the functions below. + + @warning Lengths are stored internally in mm. If you convert to + or from any other unit, expect floating point round-off errors. + + @author Stefan Kebekus + @version 1.0.0 +*/ + +class Length +{ + public: + /** constructs a 'length = 0mm' object */ + Length() {length_in_mm = 0;} + + /** sets the length in millimeters */ + void setLength_in_mm(double l) {length_in_mm = l;} + + /** sets the length in centimeters */ + void setLength_in_cm(double l) {length_in_mm = l*mm_per_cm;} + + /** sets the length in meters */ + void setLength_in_m(double l) {length_in_mm = l*mm_per_m;} + + /** sets the length in inches */ + void setLength_in_inch(double l) {length_in_mm = l*mm_per_inch;} + + /** sets the length in TeX points */ + void setLength_in_TeXPoints(double l) {length_in_mm = l*mm_per_TeXPoint;} + + /** sets the length in big points (1/72 of an inch) */ + void setLength_in_bigPoints(double l) {length_in_mm = l*mm_per_bigPoint;} + + /** sets the length in picas (1/6 of an inch) */ + void setLength_in_pica(double l) {length_in_mm = l*mm_per_pica;} + + /** sets the length in didots (0.0148 inches) */ + void setLength_in_didot(double l) {length_in_mm = l*mm_per_didot;} + + /** sets the length in ciceros (0.178 inches) */ + void setLength_in_cicero(double l) {length_in_mm = l*mm_per_cicero;} + + /** sets the length in scaled points (1 scaled point = 65536 TeX points) */ + void setLength_in_scaledPoints(double l) {length_in_mm = l*mm_per_scaledPoint;} + + + /** returns the length in millimeters */ + double getLength_in_mm() const {return length_in_mm;} + + /** returns the length in centimeters */ + double getLength_in_cm() const {return length_in_mm/mm_per_cm;} + + /** returns the length in meters */ + double getLength_in_m() const {return length_in_mm/mm_per_m;} + + /** returns the length in inches */ + double getLength_in_inch() const {return length_in_mm/mm_per_inch;} + + /** returns the length in TeX points */ + double getLength_in_TeXPoints() const {return length_in_mm/mm_per_TeXPoint;} + + /** returns the length in big points (1/72 of an inch) */ + double getLength_in_bigPoints() const {return length_in_mm/mm_per_bigPoint;} + + /** returns the length in picas (1/6 of an inch) */ + double getLength_in_pica() const {return length_in_mm/mm_per_pica;} + + /** returns the length in didots (0.0148 inches) */ + double getLength_in_didot() const {return length_in_mm/mm_per_didot;} + + /** returns the length in ciceros (0.178 inches) */ + double getLength_in_cicero() const {return length_in_mm/mm_per_cicero;} + + /** returns the length in scaled points (1 scaled point = 65536 TeX points) */ + double getLength_in_scaledPoints() const {return length_in_mm/mm_per_scaledPoint;} + + /** returns true is lengths differ by no more than 2mm */ + bool isNearlyEqual(const Length &o) const {return fabs(length_in_mm-o.getLength_in_mm()) <= 2.0;} + + /** Comparison of two lengthes */ + bool operator > (const Length &o) const {return (length_in_mm > o.getLength_in_mm());} + bool operator < (const Length &o) const {return (length_in_mm < o.getLength_in_mm());} + + /** Comparison of two lengthes */ + bool operator >= (const Length &o) const {return (length_in_mm >= o.getLength_in_mm());} + bool operator <= (const Length &o) const {return (length_in_mm <= o.getLength_in_mm());} + + /** Ratio of two lengthes + + @warning There is no safeguared to prevent you from division by + zero. If the length in the denominator is near 0.0, a floating point + exception may occur. + + @returns the ratio of the two lengthes as a double + */ + double operator / (const Length &o) const {return (length_in_mm/o.getLength_in_mm());} + + /** Sum of two lengthes + + @returns the sum of the lengthes as a Length + */ + Length operator + (const Length &o) const {Length r; r.length_in_mm = length_in_mm + o.length_in_mm; return r; } + + /** Difference of two lengthes + + @returns the difference of the lengthes as a Length + */ + Length operator - (const Length &o) const {Length r; r.length_in_mm = length_in_mm - o.length_in_mm; return r; } + + /** Division of a length + + @warning There is no safeguared to prevent you from division by + zero. If the number in the denominator is near 0.0, a floating point + exception may occur. + + @returns a fraction of the original length as a Length + */ + Length operator / (const double l) const {Length r; r.length_in_mm = length_in_mm/l; return r; } + + /** Multiplication of a length + + @returns a multiplied length as a Length + */ + Length operator * (const double l) const {Length r; r.length_in_mm = length_in_mm*l; return r; } + + private: + /** Length in millimeters */ + double length_in_mm; +}; + +#undef mm_per_cm +#undef mm_per_m +#undef mm_per_inch +#undef mm_per_TeXPoint +#undef mm_per_bigPoint +#undef mm_per_pica +#undef mm_per_didot +#undef mm_per_cicero +#undef mm_per_scaledPoint + + +#endif diff --git a/kviewshell/main.cpp b/kviewshell/main.cpp new file mode 100644 index 00000000..c87cb07e --- /dev/null +++ b/kviewshell/main.cpp @@ -0,0 +1,184 @@ +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "kviewshell.h" + + +static KCmdLineOptions options[] = +{ + { "u", 0, 0}, + { "unique", I18N_NOOP("Check if the file is loaded in another kviewshell.\nIf it is, bring up the other kviewshell. Otherwise, load the file."), 0 }, + { "m", 0, 0}, + { "mimetype ", I18N_NOOP("Loads a plugin which supports files of type ,\nif one is installed."), 0 }, + { "g", 0, 0}, + { "goto ", I18N_NOOP("Navigate to this page"), 0 }, + { "+file(s)", I18N_NOOP("Files to load"), 0 }, + KCmdLineLastOption +}; + + +static const char description[] = I18N_NOOP("Generic framework for viewer applications"); + + +int main(int argc, char **argv) +{ + KAboutData about ("kviewshell", I18N_NOOP("KViewShell"), "0.6", + description, KAboutData::License_GPL, + "(C) 2000, Matthias Hoelzer-Kluepfel\n" + "(C) 2004-2005, Wilfried Huss", + I18N_NOOP("Displays various document formats. " + "Based on original code from KGhostView.")); + about.addAuthor ("Wilfried Huss", I18N_NOOP("Current Maintainer"), + "Wilfried.Huss@gmx.at"); + about.addAuthor ("Matthias Hoelzer-Kluepfel", I18N_NOOP("Framework"), + "mhk@caldera.de"); + about.addAuthor ("David Sweet", + I18N_NOOP("KGhostView Maintainer"), + "dsweet@kde.org", + "http://www.chaos.umd.edu/~dsweet"); + about.addAuthor ("Mark Donohoe", + I18N_NOOP("KGhostView Author")); + about.addAuthor ("Markku Hihnala", + I18N_NOOP("Navigation widgets")); + about.addAuthor ("David Faure", + I18N_NOOP("Basis for shell")); + about.addAuthor ("Daniel Duley", + I18N_NOOP("Port to KParts")); + about.addAuthor ("Espen Sand", + I18N_NOOP("Dialog boxes")); + about.addAuthor ("Stefan Kebekus", + I18N_NOOP("DCOP-Interface, major improvements"), + "kebekus@kde.org"); + + KCmdLineArgs::init(argc, argv, &about); + KCmdLineArgs::addCmdLineOptions( options ); // Add my own options. + KApplication app; + + // see if we are starting with session management + if (app.isRestored()) + { + RESTORE(KViewShell); + } + else + { + KCmdLineArgs *args = KCmdLineArgs::parsedArgs(); + + if (args->isSet("unique")){ + // With --unique, we need one argument. + if (args->count() < 1) { + args->usage(); + exit(-1); + } + + // Find the fully qualified file name of the file we are + // loading. Complain, if we are given a URL which does not point + // to a local file. + KURL url(args->url(0)); + + if (!args->url(0).isValid()) { + kdError(1223) << QString(I18N_NOOP("The URL %1 is not well-formed.")).arg(args->arg(0)) << endl; + return -1; + } + + if (!args->url(0).isLocalFile()) { + kdError(1223) << QString(I18N_NOOP("The URL %1 does not point to a local file. You can only specify local " + "files if you are using the '--unique' option.")).arg(args->arg(0)) << endl; + return -1; + } + + QString qualPath = QFileInfo(args->url(0).path()).absFilePath(); + + app.dcopClient()->attach(); + QCString id = app.dcopClient()->registerAs("unique-kviewshell"); + if (id.isNull()) + kdError(1223) << "There was an error using dcopClient()->registerAs()." << endl; + QCStringList apps = app.dcopClient()->registeredApplications(); + for ( QCStringList::Iterator it = apps.begin(); it != apps.end(); ++it ) + { + if ((*it).find("kviewshell") == 0) + { + QByteArray data, replyData; + QCString replyType; + QDataStream arg(data, IO_WriteOnly); + bool result; + arg << qualPath.stripWhiteSpace(); + if (!app.dcopClient()->call( *it, "kmultipage", "is_file_loaded(QString)", data, replyType, replyData)) + kdError(1223) << "There was an error using DCOP." << endl; + else + { + QDataStream reply(replyData, IO_ReadOnly); + if (replyType == "bool") + { + reply >> result; + if (result == true) + { + if (app.dcopClient()->send(*it, "kmultipage", "jumpToReference(QString)", args->url(0).ref()) == true) + { + app.dcopClient()->detach(); + return 0; + } + } + } + else + { + kdError(1223) << "The DCOP function 'doIt' returned an unexpected type of reply!"; + } + } + } + } + } + app.dcopClient()->registerAs("kviewshell"); + KViewShell* shell; + + if (args->isSet("mimetype")) + { + shell = new KViewShell(args->getOption("mimetype")); + } + else if (args->count() > 0) + { + // If a url is given, we try to load a matching KViewShell plugin, + // so we don't have to load the empty plugin first. + KMimeType::Ptr mimetype = KMimeType::findByURL(args->url(0)); + shell = new KViewShell(mimetype->name()); + } + else + { + // Load the empty plugin + shell = new KViewShell(); + } + + // Show the main window before a file is loaded. This gives visual + // feedback to the user and (hopefully) reduces the perceived + // startup time. + shell->show(); + app.processEvents(); + + if ( args->count() > 0 ) + { + KURL url = args->url(0); + if (!url.hasRef() && args->isSet("goto")) + { + // If the url doesn't already has a reference part, add the + // argument of --goto to the url as reference, to make the + // KViewShell jump to this page. + QString reference = args->getOption("goto"); + url.setHTMLRef(reference); + } + shell->openURL(url); + } + } + + return app.exec(); +} diff --git a/kviewshell/marklist.cpp b/kviewshell/marklist.cpp new file mode 100644 index 00000000..8e39f619 --- /dev/null +++ b/kviewshell/marklist.cpp @@ -0,0 +1,616 @@ +/* This file is part of the KDE project + Copyright (C) 2004 Wilfried Huss + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include "documentPageCache.h" +#include "kvsprefs.h" +#include "marklist.h" + + +#include "marklist.moc" + + +namespace { + +/** Holds the icon used as a overlay on pages which are not drawn yet. */ +QPixmap* waitIcon = 0; + +} // namespace anon + + +/****** ThumbnailWidget ******/ + +ThumbnailWidget::ThumbnailWidget(MarkListWidget* _parent, const PageNumber& _pageNumber, DocumentPageCache* _pageCache) + : QWidget(_parent), pageNumber(_pageNumber), pageCache(_pageCache), parent(_parent) +{ + setBackgroundMode(Qt::NoBackground); + + needsUpdating = true; + + if (!waitIcon) + { + waitIcon = new QPixmap(KGlobal::iconLoader()->loadIcon("gear", KIcon::NoGroup, KIcon::SizeMedium)); + } +} + +void ThumbnailWidget::paintEvent(QPaintEvent* e) +{ + // Only repaint if the widget is really visible. We need to check this because Qt + // sends paintEvents to all widgets that have ever been visible in the Scrollview + // whenever the ScrollView is resized. This also increases the percieved performance + // only thumbnails that are really needed are rendered. + if (!parent->isVisible()) + { + //kdDebug() << "Abort Thumbnail drawing for page " << pageNumber << endl; + return; + } + + QPainter p(this); + p.setClipRect(e->rect()); + + // Paint a black border around the widget + p.setRasterOp(Qt::CopyROP); + p.setBrush(NoBrush); + p.setPen(Qt::black); + p.drawRect(rect()); + + // Remove 1 pixel from all sides of the rectangle, to eliminate overdraw with + // the black border. + QRect thumbRect = rect(); + thumbRect.addCoords(1,1,-1,-1); + + // If the thumbnail is empty or has been marked for updating generate a new thumbnail. + if (thumbnail.isNull() || needsUpdating) + { + if (KVSPrefs::changeColors() && KVSPrefs::renderMode() == KVSPrefs::EnumRenderMode::Paper) + p.fillRect(thumbRect, KVSPrefs::paperColor()); + else + p.fillRect(thumbRect, Qt::white); + + // Draw busy indicator. + // Im not really sure if this is a good idea. + // While it is nice to see an indication that something is happening for pages which + // take long to redraw, it gets quite annoing for fast redraws. + // TODO: Disable or find something less distractiong. + p.drawPixmap(10, 10, *waitIcon); + + QTimer::singleShot(50, this, SLOT(setThumbnail())); + return; + } + + // Safety check + if (thumbnail.isNull()) + { + kdDebug(1223) << "No Thumbnail for page " << pageNumber << " created." << endl; + return; + } + + + // The actual page starts at point (1,1) because of the outline. + // Therefore we need to shift the destination rectangle. + QRect pixmapRect = thumbRect; + pixmapRect.moveBy(-1,-1); + + // Paint widget + bitBlt (this, thumbRect.topLeft(), &thumbnail, pixmapRect, CopyROP); +} + +void ThumbnailWidget::resizeEvent(QResizeEvent*) +{ + thumbnail.resize(width(), height()); + // Generate a new thumbnail in the next paintEvent. + needsUpdating = true; +} + +void ThumbnailWidget::setThumbnail() +{ + if (!parent->isVisible()) + { + // We only want to calculate the thumbnail for widgets that are currently visible. + // When we are fast scrolling thru the document. Many paint events are created, that + // are often not needed anymore at the time the eventloop executes them. + //kdDebug() << "Delayed request Abort Thumbnail drawing for page " << pageNumber << endl; + kapp->processEvents(); + return; + } + + needsUpdating = false; + + // Draw Thumbnail + thumbnail = pageCache->createThumbnail(pageNumber, width() - 2); + + if (thumbnail.height() != height() + 2) + setFixedHeight(thumbnail.height() + 2); + + update(); + kapp->processEvents(); +} + + +/****** MarkListWidget ******/ + + +MarkListWidget::MarkListWidget(QWidget* _parent, MarkList* _markList, const PageNumber& _pageNumber, + DocumentPageCache* _pageCache, bool _showThumbnail) + : QWidget(_parent), showThumbnail(_showThumbnail), pageNumber(_pageNumber), + pageCache(_pageCache), markList(_markList) +{ + QBoxLayout* layout = new QVBoxLayout(this, margin); + + thumbnailWidget = 0; + if (showThumbnail) + { + thumbnailWidget = new ThumbnailWidget(this, pageNumber, pageCache); + layout->addWidget(thumbnailWidget, 1, Qt::AlignTop); + } + + QBoxLayout* bottomLayout = new QHBoxLayout(layout); + + checkBox = new QCheckBox(QString::null, this ); + checkBox->setFocusPolicy(QWidget::NoFocus); + QToolTip::add(checkBox, i18n("Select for printing")); + bottomLayout->addWidget(checkBox, 0, Qt::AlignAuto); + + pageLabel = new QLabel(QString("%1").arg(pageNumber), this); + bottomLayout->addWidget(pageLabel, 1); + + _backgroundColor = KGlobalSettings::baseColor(); + + // Alternate between colors. + if ((pageNumber % 2 == 0) && KGlobalSettings::alternateBackgroundColor().isValid()) + _backgroundColor = KGlobalSettings::alternateBackgroundColor(); + + setPaletteBackgroundColor( _backgroundColor ); + + show(); +} + +bool MarkListWidget::isChecked() const +{ + return checkBox->isChecked(); +} + +void MarkListWidget::toggle() +{ + checkBox->toggle(); +} + +void MarkListWidget::setChecked( bool checked ) +{ + checkBox->setChecked(checked); +} + +void MarkListWidget::setSelected( bool selected ) +{ + if (selected) + setPaletteBackgroundColor( QApplication::palette().active().highlight() ); + else + setPaletteBackgroundColor( _backgroundColor ); +} + +int MarkListWidget::setNewWidth(int width) +{ + int height = QMAX(checkBox->height(), pageLabel->height()) + 2*margin; + if (showThumbnail) + { + // Calculate size of Thumbnail + int thumbnailWidth = QMIN(width, KVSPrefs::maxThumbnailWidth()); + int thumbnailHeight = (int)((thumbnailWidth - 2*margin - 2) / pageCache->sizeOfPage(pageNumber).aspectRatio() + 0.5) + 2; + + // Resize Thumbnail if necessary + if (thumbnailWidget->size() != QSize(thumbnailWidth, thumbnailHeight)) + thumbnailWidget->setFixedSize(thumbnailWidth - 2*margin, thumbnailHeight); + + height += thumbnailHeight + 2*margin; + } + + setFixedSize(width, height); + return height; +} + +bool MarkListWidget::isVisible() +{ + QRect visibleRect(markList->contentsX(), markList->contentsY(), + markList->visibleWidth(), markList->visibleHeight()); + QRect widgetRect(markList->childX(this), markList->childY(this), width(), height()); + + if (widgetRect.intersects(visibleRect)) + return true; + + return false; +} + + +void MarkListWidget::mousePressEvent(QMouseEvent* e) +{ + // Select Page + if (e->button() == LeftButton) + { + emit selected(pageNumber); + } + else if (e->button() == RightButton) + { + emit showPopupMenu(pageNumber, e->globalPos()); + } +} + + +/****** MarkList ******/ + + +MarkList::MarkList(QWidget* parent, const char* name) + : QScrollView(parent, name), clickedThumbnail(0), showThumbnails(true), contextMenu(0) +{ + currentPage = PageNumber::invalidPage; + widgetList.setAutoDelete(true); + setFocusPolicy( QWidget::StrongFocus ); + //viewport()->setFocusPolicy( QWidget::WheelFocus ); + setResizePolicy(QScrollView::Manual); + + setVScrollBarMode(QScrollView::AlwaysOn); + setHScrollBarMode(QScrollView::AlwaysOff); + + setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding); + + viewport()->setBackgroundMode(Qt::PaletteBase); + enableClipper(true); +} + +MarkList::~MarkList() +{ + delete contextMenu; +} + +void MarkList::setPageCache(DocumentPageCache* _pageCache) +{ + pageCache = _pageCache; +} + +QValueList MarkList::selectedPages() const +{ + QValueList list; + MarkListWidget* item; + for(unsigned int i = 0; i < widgetList.count(); i++) + { + item = widgetList[i]; + if (item->isChecked()) + list << (i + 1); + } + return list; +} + +void MarkList::setNumberOfPages(int numberOfPages, bool _showThumbnails) +{ + showThumbnails = _showThumbnails; + + widgetList.resize(numberOfPages); + + int y = 0; + + for (int page = 1; page <= numberOfPages; page++) + { + MarkListWidget* item = new MarkListWidget(viewport(), this, page, pageCache, showThumbnails); + + connect(item, SIGNAL(selected(const PageNumber&)), this, SLOT(thumbnailSelected(const PageNumber&))); + connect(item, SIGNAL(showPopupMenu(const PageNumber&, const QPoint&)), this, SLOT(showPopupMenu(const PageNumber&, const QPoint&))); + + widgetList.insert(page - 1, item); + + int height = item->setNewWidth(visibleWidth()); + addChild(item, 0, y); + + y += height; + } + resizeContents(visibleWidth(), y); + viewport()->update(); +} + +void MarkList::thumbnailSelected(const PageNumber& pageNumber) +{ + // This variable is set to remember that the next call to setCurrentPageNumber + // has been initiated with a left click on the thumbnail of page pageNumber. + clickedThumbnail = pageNumber; + emit selected(pageNumber); +} + +void MarkList::setCurrentPageNumber(const PageNumber& pageNumber) +{ + if (!pageNumber.isValid() || pageNumber > (int)widgetList.count()) + { + clickedThumbnail = 0; + return; + } + + if (currentPage == pageNumber) + return; + + MarkListWidget* item; + + // Clear old selection + if (currentPage.isValid() && currentPage <= (int)widgetList.count()) + { + item = widgetList[currentPage - 1]; + item->setSelected(false); + } + + // Draw new selection + item = widgetList[pageNumber - 1]; + item->setSelected(true); + + // Make selected page visible if the current page has not been set with a mouseclick + // in the thumbnail list. (We use this because it is a bit confusing if the element that + // you have just clicked on, is scrolled away under the mouse cursor) + if (clickedThumbnail != pageNumber) + ensureVisible(childX(item), childY(item), 0, item->height()); + + clickedThumbnail = 0; + + currentPage = pageNumber; +} + +void MarkList::clear() +{ + currentPage = PageNumber::invalidPage; + widgetList.resize(0); +} + +void MarkList::selectAll() +{ + MarkListWidget* item; + for (unsigned int i = 0; i < widgetList.count(); i++) + { + item = widgetList[i]; + item->setChecked(true); + } +} + +void MarkList::selectEven() +{ + MarkListWidget* item; + for (unsigned int i = 1; i < widgetList.count(); i = i + 2) + { + item = widgetList[i]; + item->setChecked(true); + } +} + +void MarkList::selectOdd() +{ + MarkListWidget* item; + for (unsigned int i = 0; i < widgetList.count(); i = i + 2) + { + item = widgetList[i]; + item->setChecked(true); + } +} + +void MarkList::toggleSelection() +{ + MarkListWidget* item; + for (unsigned int i = 0; i < widgetList.count(); i++) + { + item = widgetList[i]; + item->toggle(); + } +} + +void MarkList::removeSelection() +{ + MarkListWidget* item; + for (unsigned int i = 0; i < widgetList.count(); i++) + { + item = widgetList[i]; + item->setChecked(false); + } +} + +void MarkList::viewportResizeEvent(QResizeEvent*) +{ + MarkListWidget* item; + + int yold = contentsHeight(); + int y = 0; + + for (unsigned int i = 0; i < widgetList.count(); i++) + { + item = widgetList[i]; + int height = item->setNewWidth(visibleWidth()); + moveChild(item, 0, y); + + y += height; + } + resizeContents(visibleWidth(), y); + + // If the height of the content has changed + if (yold != contentsHeight()) + { + // Make sure the selected item is still visible. + if (currentPage.isValid() && currentPage <= (int)widgetList.count()) + { + item = widgetList[currentPage-1]; + ensureVisible(childX(item), childY(item), 0, item->height()); + } + } + + viewport()->update(); +} + + +void MarkList::updateWidgetSize(const PageNumber& pageNumber) +{ + // safety checks + if (!pageNumber.isValid() || pageNumber > widgetList.count()) + { + kdError() << "MarkList::updateWidgetSize called with invalid pageNumber " << pageNumber << endl; + return; + } + + MarkListWidget* item; + + // Resize the changed widget + item = widgetList[pageNumber - 1]; + int height = item->setNewWidth(visibleWidth()); + int y = childY(item) + height; + + // Move the rest of the widgets + for (unsigned int i = pageNumber; i < widgetList.count(); i++) + { + item = widgetList[i]; + int height = item->height(); + moveChild(item, 0, y); + + y += height; + } + resizeContents(contentsWidth(), y); + + viewport()->update(); +} + +void MarkList::mousePressEvent(QMouseEvent* e) +{ + if (e->button() == RightButton) + { + // We call showPopupMenu with an invalid pageNumber to indicate that + // the mouse does not point at a thumbnailWidget. + showPopupMenu(PageNumber::invalidPage, e->globalPos()); + } +} + +void MarkList::slotShowThumbnails(bool show) +{ + if (show != showThumbnails) + { + int numOfPages = widgetList.count(); + + if (numOfPages == 0) + return; + + // Save current page. + PageNumber _currentPage = currentPage; + + // Save page selections. + QValueVector selections; + selections.resize(widgetList.count()); + for (unsigned int i = 0; i < widgetList.count(); i++) + selections[i] = widgetList[i]->isChecked(); + + // Rebuild thumbnail widgets. + clear(); + setNumberOfPages(numOfPages, show); + + // Restore current page. + setCurrentPageNumber(_currentPage); + + // Restore page selections + for (unsigned int i = 0; i < widgetList.count(); i++) + widgetList[i]->setChecked(selections[i]); + } +} + + +void MarkList::repaintThumbnails() +{ + bool show = showThumbnails; + int numOfPages = widgetList.count(); + + // Rebuild thumbnail widgets. + clear(); + setNumberOfPages(numOfPages, show); +} + + +void MarkList::showPopupMenu(const PageNumber& pageNumber, const QPoint& position) +{ + if (contextMenu == 0) + { + // Initialize Contextmenu + contextMenu = new KPopupMenu(this, "markListContext"); + + contextMenu->insertItem(i18n("Select &Current Page"), 0); + contextMenu->insertItem(i18n("Select &All Pages"), 1); + contextMenu->insertItem(i18n("Select &Even Pages"), 2); + contextMenu->insertItem(i18n("Select &Odd Pages"), 3); + contextMenu->insertItem(i18n("&Invert Selection"), 4); + contextMenu->insertItem(i18n("&Deselect All Pages"), 5); + } + + if (widgetList.count() == 0) + { + for (int i = 0; i <= 5; i++) + contextMenu->setItemEnabled(i, false); + } + else + { + for (int i = 0; i <= 5; i++) + contextMenu->setItemEnabled(i, true); + } + + // Only allow to select the current page if we got a valid pageNumber. + if (pageNumber.isValid() && pageNumber <= (int)widgetList.count()) + contextMenu->setItemEnabled(0, true); + else + contextMenu->setItemEnabled(0, false); + + // Show Contextmenu + switch(contextMenu->exec(position)) + { + case 0: + widgetList[pageNumber - 1]->toggle(); + break; + + case 1: + selectAll(); + break; + + case 2: + selectEven(); + break; + + case 3: + selectOdd(); + break; + + case 4: + toggleSelection(); + break; + + case 5: + removeSelection(); + break; + } +} + diff --git a/kviewshell/marklist.h b/kviewshell/marklist.h new file mode 100644 index 00000000..01458db3 --- /dev/null +++ b/kviewshell/marklist.h @@ -0,0 +1,186 @@ +// -*- C++ -*- +/* This file is part of the KDE project + Copyright (C) 2004 Wilfried Huss + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef MARKLIST_H +#define MARKLIST_H + +#include "pageNumber.h" + +#include +#include +#include + +class QCheckBox; +class QLabel; +class KPopupMenu; + +class DocumentPageCache; + +class MarkList; +class MarkListWidget; + + +/****** ThumbnailWidget ******/ + + +class ThumbnailWidget : public QWidget +{ + Q_OBJECT + +public: + ThumbnailWidget(MarkListWidget* parent_, const PageNumber& _pageNumber, DocumentPageCache*); + +private: + virtual void paintEvent(QPaintEvent*); + virtual void resizeEvent(QResizeEvent*); + +private slots: + void setThumbnail(); + +private: + PageNumber pageNumber; + + bool needsUpdating; + + DocumentPageCache* pageCache; + + MarkListWidget* parent; + + QPixmap thumbnail; +}; + + +/****** MarkListWidget ******/ + + +class MarkListWidget : public QWidget +{ + Q_OBJECT + +public: + MarkListWidget(QWidget* _parent, MarkList*, const PageNumber& _pageNumber, DocumentPageCache*, bool _showThumbnail = true); + + bool isChecked() const; + + bool isVisible(); + +public slots: + void toggle(); + void setChecked( bool checked ); + + void setSelected( bool selected ); + + int setNewWidth(int width); + +signals: + /** Emitted when the Page is selected in the ThumbnailView. */ + void selected(const PageNumber&); + + /** Emitted on right click. */ + void showPopupMenu(const PageNumber& pageNumber, const QPoint& position); + +protected: + virtual void mousePressEvent(QMouseEvent*); + +private: + + bool showThumbnail; + + ThumbnailWidget* thumbnailWidget; + QCheckBox* checkBox; + QLabel* pageLabel; + QColor _backgroundColor; + + const PageNumber pageNumber; + + DocumentPageCache* pageCache; + + static const int margin = 5; + + MarkList* markList; +}; + + +/****** MarkList ******/ + + +class MarkList: public QScrollView +{ + Q_OBJECT + +public: + MarkList(QWidget* parent = 0, const char* name = 0); + virtual ~MarkList(); + + void setPageCache(DocumentPageCache*); + + QValueList selectedPages() const; + + PageNumber currentPageNumber() { return currentPage; } + + PageNumber numberOfPages() { return widgetList.count(); } + + virtual QSize sizeHint() const { return QSize(); } + +public slots: + void setNumberOfPages(int numberOfPages, bool showThumbnails = true); + + void thumbnailSelected(const PageNumber& pageNumber); + void setCurrentPageNumber(const PageNumber& pageNumber); + + void clear(); + + void slotShowThumbnails(bool); + void repaintThumbnails(); + + void updateWidgetSize(const PageNumber&); + +protected: + virtual void viewportResizeEvent(QResizeEvent*); + + virtual void mousePressEvent(QMouseEvent*); + +signals: + void selected(const PageNumber&); + +private slots: + void showPopupMenu(const PageNumber& pageNumber, const QPoint& position); + + void selectAll(); + void selectEven(); + void selectOdd(); + void toggleSelection(); + void removeSelection(); + +private: + QPtrVector widgetList; + + PageNumber currentPage; + + PageNumber clickedThumbnail; + + DocumentPageCache* pageCache; + + bool showThumbnails; + + KPopupMenu* contextMenu; +}; + +#endif diff --git a/kviewshell/optionDialogAccessibilityWidget.ui b/kviewshell/optionDialogAccessibilityWidget.ui new file mode 100644 index 00000000..43490028 --- /dev/null +++ b/kviewshell/optionDialogAccessibilityWidget.ui @@ -0,0 +1,528 @@ + +optionDialogAccessibilityWidget + + + optionDialogAccessibilityWidget + + + + 0 + 0 + 487 + 384 + + + + + unnamed + + + 0 + + + + kcfg_ChangeColors + + + + 5 + 7 + 0 + 0 + + + + Change &Colors + + + true + + + false + + + + unnamed + + + + warn + + + + 5 + 4 + 0 + 0 + + + + + 80 + 0 + 0 + + + + Warning: these options can badly affect drawing speed. + + + PlainText + + + + + kcfg_RenderMode + + + false + + + + 5 + 4 + 0 + 0 + + + + NoFrame + + + 0 + + + + + + + unnamed + + + 0 + + + + radioInverted + + + &Invert colors + + + true + + + + + radioNormal + + + Change &paper color + + + + + layout5 + + + + unnamed + + + + spacer14_3 + + + Horizontal + + + Fixed + + + + 16 + 20 + + + + + + textLabel1 + + + false + + + Paper color: + + + kcfg_PaperColor + + + + + kcfg_PaperColor + + + false + + + + + + + + spacer7_2 + + + Horizontal + + + Expanding + + + + 30 + 20 + + + + + + + + radioRecolor + + + &Change dark and light colors + + + + + layout2 + + + + unnamed + + + 0 + + + + spacer12_2 + + + Horizontal + + + Expanding + + + + 48 + 21 + + + + + + kcfg_RecolorBackground + + + false + + + + + + + + spacer14_2 + + + Horizontal + + + Fixed + + + + 16 + 20 + + + + + + kcfg_RecolorForeground + + + false + + + + + + + + spacer12 + + + Horizontal + + + Expanding + + + + 48 + 21 + + + + + + textLabel3_2 + + + false + + + Light color: + + + + + spacer14 + + + Horizontal + + + Fixed + + + + 16 + 20 + + + + + + textLabel3 + + + false + + + Dark color: + + + + + + + radioContrast + + + Convert to &black and white + + + + + layout5 + + + + unnamed + + + + textLabel2_2 + + + false + + + Contrast: + + + + + spacer11_2 + + + Horizontal + + + Fixed + + + + 16 + 20 + + + + + + spacer11 + + + Horizontal + + + Fixed + + + + 16 + 20 + + + + + + kcfg_BWContrast + + + false + + + + 7 + 0 + 0 + 0 + + + + 1 + + + Horizontal + + + + + kcfg_BWThreshold + + + false + + + + 7 + 0 + 0 + 0 + + + + 16 + + + Horizontal + + + + + textLabel2 + + + false + + + Threshold: + + + + + + + + + + + + + + + radioRecolor + toggled(bool) + textLabel3 + setEnabled(bool) + + + radioRecolor + toggled(bool) + kcfg_RecolorForeground + setEnabled(bool) + + + radioRecolor + toggled(bool) + textLabel3_2 + setEnabled(bool) + + + radioRecolor + toggled(bool) + kcfg_RecolorBackground + setEnabled(bool) + + + radioContrast + toggled(bool) + textLabel2 + setEnabled(bool) + + + radioContrast + toggled(bool) + kcfg_BWThreshold + setEnabled(bool) + + + radioNormal + toggled(bool) + textLabel1 + setEnabled(bool) + + + radioNormal + toggled(bool) + kcfg_PaperColor + setEnabled(bool) + + + radioContrast + toggled(bool) + textLabel2_2 + setEnabled(bool) + + + radioContrast + toggled(bool) + kcfg_BWContrast + setEnabled(bool) + + + kcfg_ChangeColors + toggled(bool) + kcfg_RenderMode + setEnabled(bool) + + + + kdialog.h + + + + kcolorbutton.h + kcolorbutton.h + kcolorbutton.h + + diff --git a/kviewshell/optionDialogGUIWidget_base.ui b/kviewshell/optionDialogGUIWidget_base.ui new file mode 100644 index 00000000..cd8fba19 --- /dev/null +++ b/kviewshell/optionDialogGUIWidget_base.ui @@ -0,0 +1,148 @@ + +optionDialogGUIWidget_base + + + optionDialogGUIWidget_base + + + + 0 + 0 + 349 + 174 + + + + + unnamed + + + 0 + + + + + Enabled + + + + + Disabled + + + + + Only on Hover + + + + kcfg_UnderlineLinks + + + <qt>Controls how hyperlinks are underlined: +<ul> +<li><b>Enabled</b>: Always underline links</li> +<li><b>Disabled</b>: Never underline links</li> +<li><b>Only on Hover</b>: Underline when the mouse is moved over the link</li> +</ul></qt> + + + + + textLabel1 + + + + 4 + 5 + 0 + 0 + + + + Underline links: + + + + + kcfg_ShowThumbnails + + + Show &thumbnail previews + + + + + groupBox1 + + + Overview Mode + + + + unnamed + + + + rowLabel + + + + 4 + 5 + 0 + 0 + + + + Rows: + + + + + kcfg_OverviewModeColumns + + + 8 + + + 3 + + + + + kcfg_OverviewModeRows + + + 5 + + + 1 + + + + + columnLabel + + + + 4 + 5 + 0 + 0 + + + + Columns: + + + + + + + + kcfg_OverviewModeRows + kcfg_OverviewModeColumns + + + diff --git a/kviewshell/pageNumber.h b/kviewshell/pageNumber.h new file mode 100644 index 00000000..71ba3e50 --- /dev/null +++ b/kviewshell/pageNumber.h @@ -0,0 +1,65 @@ +// -*- C++ -*- +// +// pageNumber.h +// +// Part of KVIEWSHELL - A framework for multipage text/gfx viewers +// +// (C) 2004 Stefan Kebekus +// Distributed under the GPL + +// Add header files alphabetically + +#ifndef PAGENUMBER_H +#define PAGENUMBER_H + +#include + + +/** \brief Class to represent a page number + +The class PageNumber is really nothing but an alias for Q_UINT16, and +can be casted to and from Q_UINT16. It is used in kviewshell to remind +the programmer of the convention that page numbers start at '1' (for +'first page'), and that the value '0' means 'illegal page number' or +'no page number'. Accordingly, the value '0' is also named +PageNumber::invalidPage, and there is a trivial method isInvalid() +that checks if the page number is 0. + +@author Stefan Kebekus +@version 1.0 0 +*/ + +class PageNumber +{ + public: + enum pageNums { + invalidPage = 0 /*! Invalid page number */ + }; + + /** The default constructor sets the page number to 'invalidPage' */ + PageNumber() {pgNum = invalidPage;} + + /** \brief Constructor that sets the page number + + @param num page number that is set initially + */ + PageNumber(Q_UINT16 num) {pgNum = num;} + + /** \brief this method implements typecasts from Q_UINT16 */ + PageNumber &operator=(const Q_UINT16 p) { pgNum = p; return *this; } + + /** \brief This method implements typecasts to Q_UINT16 */ + operator Q_UINT16() const { return pgNum; } + + /** \brief Checks if the page number is invalid + + @returns true, if pgNum != invalidPage, i.e., does not equal 0 + */ + bool isValid() const {return (pgNum != invalidPage);} + + private: + /** \brief Single number that represents the page number */ + Q_UINT16 pgNum; +}; + +#endif diff --git a/kviewshell/pageSize.cpp b/kviewshell/pageSize.cpp new file mode 100644 index 00000000..f52f01a4 --- /dev/null +++ b/kviewshell/pageSize.cpp @@ -0,0 +1,343 @@ +// pageSize.cpp +// +// Part of KVIEWSHELL - A framework for multipage text/gfx viewers +// +// (C) 2002-2003 Stefan Kebekus +// Distributed under the GPL + +// Add header files alphabetically + +#include + +#include +#include +#include +#include +#include + +#include "pageSize.h" +#include "units.h" + +struct pageSizeItem +{ + const char *name; + float width; // in mm + float height; // in mm + const char *preferredUnit; +}; + +#define defaultMetricPaperSize 4 // Default paper size is "DIN A4" +#define defaultImperialPaperSize 8 // Default paper size is "US Letter" + +static pageSizeItem staticList[] = { {"DIN A0", 841.0, 1189.0, "mm"}, + {"DIN A1", 594.0, 841.0, "mm"}, + {"DIN A2", 420.0, 594.0, "mm"}, + {"DIN A3", 297.0, 420.0, "mm"}, + {"DIN A4", 210.0, 297.0, "mm"}, + {"DIN A5", 148.5, 210.0, "mm"}, + {"DIN B4", 250.0, 353.0, "mm"}, + {"DIN B5", 176.0, 250.0, "mm"}, + {"US Letter", 215.9, 279.4, "in"}, + {"US Legal", 215.9, 355.6, "in"}, + {0, 0.0, 0.0, 0} // marks the end of the list. +}; + + +pageSize::pageSize() +{ + currentSize = defaultPageSize(); + pageWidth.setLength_in_mm(staticList[currentSize].width); + pageHeight.setLength_in_mm(staticList[currentSize].height); +} + + +pageSize::pageSize(const SimplePageSize& s) +{ + pageWidth = s.width(); + pageHeight = s.height(); + + rectifySizes(); + reconstructCurrentSize(); +} + + +bool pageSize::setPageSize(const QString& name) +{ + // See if we can recognize the string + QString currentName; + for(int i=0; staticList[i].name != 0; i++) { + currentName = staticList[i].name; + if (currentName == name) { + currentSize = i; + // Set page width/height accordingly + pageWidth.setLength_in_mm(staticList[currentSize].width); + pageHeight.setLength_in_mm(staticList[currentSize].height); + emit(sizeChanged(*this)); + return true; + } + } + + // Check if the string contains 'x'. If yes, we assume it is of type + // "x". If yes, the first number is interpreted as + // the width in mm, the second as the height in mm + if (name.find('x') >= 0) { + bool wok, hok; + float pageWidth_tmp = name.section('x',0,0).toFloat(&wok); + float pageHeight_tmp = name.section('x',1,1).toFloat(&hok); + if (wok && hok) { + pageWidth.setLength_in_mm(pageWidth_tmp); + pageHeight.setLength_in_mm(pageHeight_tmp); + + rectifySizes(); + reconstructCurrentSize(); + emit(sizeChanged(*this)); + return true; + } + } + + // Check if the string contains ','. If yes, we assume it is of type + // ",". The first number is supposed to + // be the width, the second the height. + if (name.find(',') >= 0) { + bool wok, hok; + float pageWidth_tmp = distance::convertToMM(name.section(',',0,0), &wok); + float pageHeight_tmp = distance::convertToMM(name.section(',',1,1), &hok); + if (wok && hok) { + pageWidth.setLength_in_mm(pageWidth_tmp); + pageHeight.setLength_in_mm(pageHeight_tmp); + + rectifySizes(); + reconstructCurrentSize(); + emit(sizeChanged(*this)); + return true; + } + } + + // Last resource. Set the default, in case the string is + // unintelligible to us. + currentSize = defaultPageSize(); + pageWidth.setLength_in_mm(staticList[currentSize].width); + pageHeight.setLength_in_mm(staticList[currentSize].height); + kdError(1223) << "pageSize::setPageSize: could not parse '" << name << "'. Using " << staticList[currentSize].name << " as a default." << endl; + emit(sizeChanged(*this)); + return false; +} + + +void pageSize::setPageSize(double width, double height) +{ + SimplePageSize oldPage = *this; + + pageWidth.setLength_in_mm(width); + pageHeight.setLength_in_mm(height); + + rectifySizes(); + reconstructCurrentSize(); + if ( !isNearlyEqual(oldPage)) + emit(sizeChanged(*this)); +} + + +void pageSize::setPageSize(const QString& width, const QString& _widthUnits, const QString& height, const QString& _heightUnits) +{ + SimplePageSize oldPage = *this; + + double w = width.toFloat(); + double h = height.toFloat(); + + QString widthUnits = _widthUnits; + if ((widthUnits != "cm") && (widthUnits != "mm") && (widthUnits != "in")) { + kdError(1223) << "Unrecognized page width unit '" << widthUnits << "'. Assuming mm" << endl; + widthUnits = "mm"; + } + pageWidth.setLength_in_mm(w); + if (widthUnits == "cm") + pageWidth.setLength_in_cm(w); + if (widthUnits == "in") + pageWidth.setLength_in_inch(w); + + QString heightUnits = _heightUnits; + if ((heightUnits != "cm") && (heightUnits != "mm") && (heightUnits != "in")) { + kdError(1223) << "Unrecognized page height unit '" << widthUnits << "'. Assuming mm" << endl; + heightUnits = "mm"; + } + pageHeight.setLength_in_mm(h); + if (heightUnits == "cm") + pageHeight.setLength_in_cm(h); + if (heightUnits == "in") + pageHeight.setLength_in_inch(h); + + rectifySizes(); + reconstructCurrentSize(); + if ( !isNearlyEqual(oldPage)) + emit(sizeChanged(*this)); +} + + +pageSize &pageSize::operator= (const pageSize &src) +{ + SimplePageSize oldPage = *this; + + currentSize = src.currentSize; + pageWidth = src.pageWidth; + pageHeight = src.pageHeight; + + if ( !isNearlyEqual(oldPage)) + emit(sizeChanged(*this)); + return *this; +} + + +void pageSize::rectifySizes() +{ + // Now do some sanity checks to make sure that values are not + // outrageous. We allow values between 5cm and 50cm. + if (pageWidth.getLength_in_mm() < 50) + pageWidth.setLength_in_mm(50.0); + if (pageWidth.getLength_in_mm() > 1200) + pageWidth.setLength_in_mm(1200); + if (pageHeight.getLength_in_mm() < 50) + pageHeight.setLength_in_mm(50); + if (pageHeight.getLength_in_mm() > 1200) + pageHeight.setLength_in_mm(1200); + return; +} + + +QString pageSize::preferredUnit() const +{ + if (currentSize >= 0) + return staticList[currentSize].preferredUnit; + + // User-defined size. Give a preferred unit depening on the locale. + if (KGlobal::locale()-> measureSystem() == KLocale::Metric) + return "mm"; + else + return "in"; +} + + +QString pageSize::widthString(const QString& unit) const +{ + QString answer = "--"; + + if (unit == "cm") + answer.setNum(pageWidth.getLength_in_cm()); + if (unit == "mm") + answer.setNum(pageWidth.getLength_in_mm()); + if (unit == "in") + answer.setNum(pageWidth.getLength_in_inch()); + + return answer; +} + + +QString pageSize::heightString(const QString& unit) const +{ + QString answer = "--"; + + if (unit == "cm") + answer.setNum(pageHeight.getLength_in_cm()); + if (unit == "mm") + answer.setNum(pageHeight.getLength_in_mm()); + if (unit == "in") + answer.setNum(pageHeight.getLength_in_inch()); + + return answer; +} + + +QStringList pageSize::pageSizeNames() +{ + QStringList names; + + for(int i=0; staticList[i].name != 0; i++) + names << staticList[i].name; + + return names; +} + + +QString pageSize::formatName() const +{ + if (currentSize >= 0) + return staticList[currentSize].name; + else + return QString::null; +} + + +int pageSize::getOrientation() const +{ + if (currentSize == -1) { + kdError(1223) << "pageSize::getOrientation: getOrientation called for page format that does not have a name." << endl; + return 0; + } + + if (pageWidth.getLength_in_mm() == staticList[currentSize].width) + return 0; + else + return 1; +} + + +void pageSize::setOrientation(int orient) +{ + if (currentSize == -1) { + kdError(1223) << "pageSize::setOrientation: setOrientation called for page format that does not have a name." << endl; + return; + } + + if (orient == 1) { + pageWidth.setLength_in_mm(staticList[currentSize].height); + pageHeight.setLength_in_mm(staticList[currentSize].width); + } else { + pageWidth.setLength_in_mm(staticList[currentSize].width); + pageHeight.setLength_in_mm(staticList[currentSize].height); + } + emit(sizeChanged(*this)); +} + + +QString pageSize::serialize() const +{ + if ((currentSize >= 0) && (fabs(staticList[currentSize].height-pageHeight.getLength_in_mm()) <= 0.5)) + return staticList[currentSize].name; + else + return QString("%1x%2").arg(pageWidth.getLength_in_mm()).arg(pageHeight.getLength_in_mm()); +} + + +void pageSize::reconstructCurrentSize() +{ + for(int i=0; staticList[i].name != 0; i++) { + if ((fabs(staticList[i].width - pageWidth.getLength_in_mm()) <= 2) && (fabs(staticList[i].height - pageHeight.getLength_in_mm()) <= 2)) { + currentSize = i; + pageWidth.setLength_in_mm(staticList[currentSize].width); + pageHeight.setLength_in_mm(staticList[currentSize].height); + return; + } + if ((fabs(staticList[i].height - pageWidth.getLength_in_mm()) <= 2) && (fabs(staticList[i].width - pageHeight.getLength_in_mm()) <= 2)) { + currentSize = i; + pageWidth.setLength_in_mm(staticList[currentSize].height); + pageHeight.setLength_in_mm(staticList[currentSize].width); + return; + } + } + currentSize = -1; + return; +} + +int pageSize::defaultPageSize() +{ + // FIXME: static_cast(KGlobal::locale()->pageSize()) + // is the proper solution here. Then you can determine the values + // without using your hardcoded table too! + if (KGlobal::locale()-> measureSystem() == KLocale::Metric) + return defaultMetricPaperSize; + else + return defaultImperialPaperSize; +} + +#include "pageSize.moc" + diff --git a/kviewshell/pageSize.h b/kviewshell/pageSize.h new file mode 100644 index 00000000..5ba0194e --- /dev/null +++ b/kviewshell/pageSize.h @@ -0,0 +1,280 @@ +// -*- C++ -*- +// +// pageSize.h +// +// Part of KVIEWSHELL - A framework for multipage text/gfx viewers +// +// (C) 2002-2005 Stefan Kebekus +// Distributed under the GPL + +// Add header files alphabetically + +#ifndef PAGESIZE_H +#define PAGESIZE_H + +#include "simplePageSize.h" + +#include + +class QString; +class QStringList; + + +/* \brief This class represents physical page sizes. + +The main difference to the SimplePageSize class are the following. + +- This class knows about standard page sizes and accepts page sizes in + various formats, e.g. as a string "DIN A4", or by specifiying the + page width and height. Several units (inch, millimeters, + centimeters) are possible. + +- It is made sure that page width an hight are always in a resonable + range, which is currently set to 5cm .. 50cm + +- The default constructor provides a locale-depending default. + +@author Stefan Kebekus +@version 1.0.0 +*/ + +class pageSize : public QObject, public SimplePageSize +{ +Q_OBJECT + +public: + /** \brief Default constructor, initializes the pageSize with a + reasonable default + + The default chosen depends on the locale. At the moment, A4 size + is chosen for countries with metric measurement system, and US + letter otherwise. + */ + pageSize(); + + /** \brief Initializes the pageSize with a SimplePageSize. */ + pageSize(const SimplePageSize&); + + /** \brief List of standard pageSizes + + This method returns the names of standard pageSizes, + e.g. "A4". These can be used, e.g., by a QComboBox to let the user + choose known sizes. The returned list is also a list of all possible + return values of the formatName() method explained below. If you + call pageSizeNames() more than once, it is guaranteed that the + same list of strings will be returned. + + @returns QStringList that contains + */ + QStringList pageSizeNames(); + + /** \brief Set page size by name. + + Acceptable strings are + + (1) a name from the list retured by pageSizeNames(), such as "DIN + A4" + + (2) a string like "500x300", which describes a page of width 500mm + and height 300mm. + + (3) a string like "3in, 4 cm". A number of different units, + including "in", "mm" and "cm", and a few typographical units are + recognized + + If the name is not of these types, and error message is printed to + stderr using kdError() and a default value, which depends on the + locale, is set. + + In any case, the values will be trimmed so as not to exceed the + minima/maxima of 5cm and 50cm, respectively. If the page size found + matches one of the standard sizes by an error of no more than 2mm, + the standard page size will be set. The signal sizeChanged() will + always be emitted. + + @param name string that represents the page size + + @returns 'True', if the parameter could be parsed, and 'false' + otherwise. + */ + bool setPageSize(const QString& name); + + /** \brief Set page size from width and height strings + + Sets the page size to "width" and "height", given in the associated + units. Currently, "mm", "cm" and "in" are supported. If a unit is + not recognized, "mm" is siliently assumed, and error message is + printed to stderr using kdError(). If the page size set matches one + of the standard sizes by an error of no more than 2mm, the standard + page size will be set. If width or height does not contain a + number, the result is an undefined value. However, it is guaranteed + in any case that both width and height are between the minimal and + maximal possible values, i.e. in the range 5..50 cm. If the newly + set value differs from the old value by more that 2mm for width or + height, the signal sizeChanged() will be emitted + + @param width string that represents the page width as a number, + e.g., " 300 " + + @param widthUnits units for the width string. Currently "mm", "cm" + and "in" are allowed. + + @param height string that represents the page height as a number, + e.g., " 300 " + + @param heightUnits units for the height string. Currently "mm", "cm" + and "in" are allowed. + */ + void setPageSize(const QString& width, const QString& widthUnits, const QString& height, const QString& heightUnits); + + /** \brief Set page size + + Sets the page size to "width" and "height", given in millimeter. If + the page size set matches one of the standard sizes by an error of + no more than 2mm, the standard page size will be set. Values are + trimmed so that both width and height are between the minimal and + maximal possible values, i.e. in the range 5..50 cm. If the newly + set value differs from the old value by more that 2mm for width or + height, the signal sizeChanged() will be emitted + + @param width_in_mm page width in mm + + @param height_in_mm page height in mm + */ + virtual void setPageSize(double width_in_mm, double height_in_mm); + + /** \brief Copy operator. + + This operator will emit the signal sizeChanged() if one of the + dimensions of *this and src differ by more than two millimeters. + */ + pageSize & operator= (const pageSize &src); + + /** \brief Preferred unit for the current page size + + @returns The name of the unit, one of "cm", "mm" or "in", which is + most commonly used with the current page format. For instance, + US Letter and US Legal are best given in inches, to avoid very + odd numbers. If the page format is unknown, returns a guess + based on the current locale. */ + QString preferredUnit() const; + + /** \brief Returns the page width as a string + + @param unit The unit in which the page width shall be returned. This + must be one of "cm", "mm" or "in". + + @returns a string containing a number, e.g. "3.1415", which gives the page + width in the given unit. If the unit is not recognized, the string "--" is returned. + */ + QString widthString(const QString& unit) const; + + /** \brief Returns the page height as a string + + @param unit The unit in height the page width shall be + returned. This must be one of "cm", "mm" or "in". + + @returns a string containing a number which gives the page height in + the given unit. If the unit is not recognized, the string "--" is + returned. + */ + QString heightString(const QString& unit) const; + + /** \brief Returns a name for the page size, if this is a standard + size + + @warning This method does not take care of orientation, e.g. it + will return "DIN A4" if the page size is either 210x297 or + 297x210. + + @returns A name for the current page size, if the format has a + name, or QString::null otherwise. If the result is not + QString::null, it is guaranteed to be one of the strings + returned by the pageSizeNames() method. + */ + QString formatName() const; + + /** \brief Returns an number for the page size, if this is a + standard size + + @warning This method does not take care of orientation, e.g. it + will return the same value if the page size is either 210x297 or + 297x210. + + @returns If the current format is one of the standard sizes, a + non-negative integer is returned, which is an index to the + QStringList returned by the pageSizeNames() method. If the + current format is none of the standard sizes, -1 is returned. + */ + int formatNumber() const {return currentSize;} + + /** \brief Gets orientation for standard sizes + + If the pageSize is one of the standard sizes, i.e. formatNumber() != + -1, this method can be used to get the orientation. If the pageSize + is not a standard size, this method prints an error message stderr + using kdError(). + + @returns 0 for 'portrait', or 1 for 'landscape'. If the size is none + of the standard sizes, an undefined value is returned. + */ + int getOrientation() const; + + /** \brief Returns a string that can be read by setPageSize(QString) + + @returns This method returns a string like "210x297". The numbers + are page width and height in millimeters. The setPageSize(QString) + method will understand this output. + */ + QString serialize() const; + +public slots: + /** \brief Sets orientation + + If the pageSize is one of the standard sizes, i.e. formatNumber() != + -1, this method can be used to set the orientation. If the pageSize + is not a standard size, this method prints an error message stderr + using kdError() and does nothing. + + @param orient 0 sets 'portrait orientation', 1 sets 'landscape' + */ + void setOrientation(int orient); + +signals: + /** \brief Signal emitted when the page sizes changes + + emitted to indicate that the size changed. Not emitted immediately + after construction, when the constructor sets the default + size. + + @param t a pointer to this + */ + void sizeChanged(const SimplePageSize& t); + +private: + /** Makes sure that pageWidth and pageHeight are in the permissible + range and not, e.g., negative. */ + void rectifySizes(); + + /** Tries to find one of the known sizes which matches pageWidth and + pageHeight, with an error margin of 2 millimeters. If found, the + value of 'currentsize' is set to point to the known size, and + pageWidth and pageHeight are set to the correct values for that + size. Otherwise, currentSize is set to -1 to indicate "custom + size". Note: this method does not take care of orientation, + e.g. the method will set 'currentsize' to point to "DIN A4" if + either the page size is 210x297 or 297x210. */ + void reconstructCurrentSize(); + + /** Gives a default value for currentSize, which depends on the + locale. In countries with metric system, this will be "DIN A4", + in countries with the imperial system, "US Letter". */ + int defaultPageSize(); + + /** Permissible range: 0--#Size_of_array staticList, or -1 to + indicate a "user defined setting". Other values may lead to a + segfault. */ + int currentSize; +}; + +#endif diff --git a/kviewshell/pageSizeDialog.cpp b/kviewshell/pageSizeDialog.cpp new file mode 100644 index 00000000..3a64cbe3 --- /dev/null +++ b/kviewshell/pageSizeDialog.cpp @@ -0,0 +1,63 @@ +// pageSizeDialog.cpp +// +// Part of KVIEWSHELL - A framework for multipage text/gfx viewers +// +// (C) 2002-2003 Stefan Kebekus +// Distributed under the GPL + +// Add header files alphabetically + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + + +#include "pageSizeDialog.h" +#include "pageSizeWidget.h" + + +pageSizeDialog::pageSizeDialog( QWidget *parent, pageSize *userPrefdPageSize, const char *name, bool modal) + :KDialogBase( parent, name, modal, i18n("Page Size"), Ok|Apply|Cancel, Ok, + true ) +{ + userPreferredPageSize = userPrefdPageSize; + pageSizeW = new pageSizeWidget(this, "PageSizeWidget"); + pageSizeW->setSizePolicy( QSizePolicy( (QSizePolicy::SizeType)3, (QSizePolicy::SizeType)3, 0, 0, + pageSizeW->sizePolicy().hasHeightForWidth() ) ); + setMainWidget(pageSizeW); +} + + +void pageSizeDialog::slotOk() +{ + if (userPreferredPageSize != 0) + *userPreferredPageSize = pageSizeW->pageSizeData(); + accept(); +} + + +void pageSizeDialog::slotApply() +{ + if (userPreferredPageSize != 0) + *userPreferredPageSize = pageSizeW->pageSizeData(); +} + + +void pageSizeDialog::setPageSize(const QString& name) +{ + if (pageSizeW == 0) + return; + pageSizeW->setPageSize(name); +} + + +#include "pageSizeDialog.moc" diff --git a/kviewshell/pageSizeDialog.h b/kviewshell/pageSizeDialog.h new file mode 100644 index 00000000..17a4a0df --- /dev/null +++ b/kviewshell/pageSizeDialog.h @@ -0,0 +1,52 @@ +// -*- C++ -*- +/* + * pageSizeDialog, for kviewshell + * This file: Copyright (C) 2002-2003 Stefan Kebekus, kebekus@kde.org + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + */ +#ifndef _PAGESIZE_DIALOG_H_ +#define _PAGESIZE_DIALOG_H_ + +#include + +class pageSizeWidget; +class pageSize; + +class pageSizeDialog : public KDialogBase +{ + Q_OBJECT + +public: + // Constructs the page size Dialog. The pointer userPrefdPageSize + // points to a pageSize object which will be set the the chosen + // value whenever the user clicks on 'accept' or 'ok'. Programmers + // can then connect to the signal sizeChanged() of the object to be + // informed about the changes. + pageSizeDialog( QWidget *parent=0, pageSize *userPrefdPageSize=0, const char *name=0, bool modal=true); + void setPageSize(const QString&); + +protected slots: + virtual void slotOk(); + virtual void slotApply(); + + private: + pageSizeWidget *pageSizeW; + pageSize *userPreferredPageSize; +}; + + +#endif diff --git a/kviewshell/pageSizeWidget.cpp b/kviewshell/pageSizeWidget.cpp new file mode 100644 index 00000000..d485f3bb --- /dev/null +++ b/kviewshell/pageSizeWidget.cpp @@ -0,0 +1,148 @@ +// pageSizeWidget.cpp +// +// Part of KVIEWSHELL - A framework for multipage text/gfx viewers +// +// (C) 2002-2003 Stefan Kebekus +// Distributed under the GPL + +// Add header files alphabetically + +#include + +#include +#include +#include +#include +#include + +#include "pageSize.h" +#include "pageSizeWidget.h" +#include "sizePreview.h" + + + +// Constructs a pageSizeWidget_base which is a child of 'parent', with +// the name 'name' and widget flags set to 'f'. +pageSizeWidget::pageSizeWidget( QWidget* parent, const char* name, WFlags fl ) + : pageSizeWidget_base( parent, name, fl ) +{ + connect(&chosenSize, SIGNAL(sizeChanged(const SimplePageSize&)), previewer, SLOT(setSize(const SimplePageSize&))); + + // Set up the formatChoice QComboBox + formatChoice->insertItem(i18n("Custom Size")); + formatChoice->insertStringList(chosenSize.pageSizeNames()); + + // Activate the proper entry in the QComboBox + if (chosenSize.formatName().isNull()) { + orientationChoice->setEnabled(false); + formatChoice->setCurrentItem(0); + } else { + orientationChoice->setEnabled(true); + formatChoice->setCurrentText(chosenSize.formatName()); + } + paperSize(formatChoice->currentItem()); + + connect(formatChoice, SIGNAL(activated(int)), this, SLOT(paperSize(int))); + connect(orientationChoice, SIGNAL(activated(int)), this, SLOT(orientationChanged(int))); + + // Update the text fields when the user switches to a new unit, and + // when the "custom format" is NOT selected. + connect(widthUnits, SIGNAL(activated(int)), this, SLOT(unitsChanged(int))); + connect(heightUnits, SIGNAL(activated(int)), this, SLOT(unitsChanged(int))); + + // Upate the chosen size whenever the user edits the input field. + connect(widthInput, SIGNAL(textChanged(const QString &)), this, SLOT(input(const QString &))); + connect(heightInput, SIGNAL(textChanged(const QString &)), this, SLOT(input(const QString &))); + + // Allow entries between 0 and 1200. More filtering is done by the + // pageSize class, which silently ignores values which are out of + // range. + widthInput->setValidator(new QDoubleValidator(0.0, 1200.0, 1, this, "widthValidator")); + heightInput->setValidator(new QDoubleValidator(0.0, 1200.0, 1, this, "heightValidator")); +} + + +// Receives the output from the formatChoice combobox. A value of +// "zero" means that the "custom paper size" has been chosen. +void pageSizeWidget::paperSize(int index) +{ + widthInput->setEnabled(index == 0); + heightInput->setEnabled(index == 0); + orientationChoice->setEnabled(index != 0); + + if (index != 0) { + chosenSize.setPageSize(formatChoice->currentText()); + chosenSize.setOrientation(orientationChoice->currentItem()); + } + widthUnits->setCurrentText(chosenSize.preferredUnit()); + heightUnits->setCurrentText(chosenSize.preferredUnit()); + + fillTextFields(); +} + + +void pageSizeWidget::setPageSize(const QString& sizeName) +{ + chosenSize.setPageSize(sizeName); + + int index = chosenSize.formatNumber(); + + formatChoice->setCurrentItem(index+1); + widthInput->setEnabled(index == -1); + heightInput->setEnabled(index == -1); + orientationChoice->setEnabled(index != -1); + + widthUnits->setCurrentText(chosenSize.preferredUnit()); + heightUnits->setCurrentText(chosenSize.preferredUnit()); + + fillTextFields(); +} + + +void pageSizeWidget::fillTextFields() +{ + // Warning! It is really necessary here to first generate both + // strings, and then call setText() on the input widgets. Reason? + // Calling setText() implicitly calls the input() method via the + // textChanged()-Signal of the QLineEdit widgets. + QString width = chosenSize.widthString(widthUnits->currentText()); + QString height = chosenSize.heightString(heightUnits->currentText()); + + widthInput->setText(width); + heightInput->setText(height); +} + + +void pageSizeWidget::unitsChanged(int) +{ + // Update the text fields, i.e. show the current size in the proper + // units, but only if the "custom size" is NOT selected. + if (formatChoice->currentItem() != 0) + fillTextFields(); + else + input(QString::null); +} + + +void pageSizeWidget::setOrientation(int ori) +{ + orientationChoice->setCurrentItem(ori); + orientationChanged(); +} + + +void pageSizeWidget::orientationChanged(int orient) +{ + // Update the page preview + chosenSize.setOrientation(orient); +} + + +void pageSizeWidget::input(const QString &) +{ + chosenSize.setPageSize(widthInput->text(), widthUnits->currentText(), heightInput->text(), heightUnits->currentText()); +} + + + +#include "pageSizeWidget.moc" diff --git a/kviewshell/pageSizeWidget.h b/kviewshell/pageSizeWidget.h new file mode 100644 index 00000000..76cc0d02 --- /dev/null +++ b/kviewshell/pageSizeWidget.h @@ -0,0 +1,52 @@ +// -*- C++ -*- +// pageSizeWidget.h +// +// Part of KVIEWSHELL - A framework for multipage text/gfx viewers +// +// (C) 2002 Stefan Kebekus +// Distributed under the GPL + +// Add header files alphabetically + +#ifndef PAGESIZEWIDGET_H +#define PAGESIZEWIDGET_H + +#include "pageSize.h" +#include "pageSizeWidget_base.h" + +class QDoubleValidator; + +class pageSizeWidget : public pageSizeWidget_base +{ + Q_OBJECT + +public: + pageSizeWidget( QWidget* parent = 0, const char* name = 0, WFlags fl = 0 ); + + // Sets the page size. If the dialog is already shown, updates all + // the GUI. Accepts the same strings as input as the setPageSize() + // method of the pageSize() class. + void setPageSize(const QString&); + + void setOrientation(int ori); + + const pageSize &pageSizeData() const {return chosenSize;} + +protected slots: + void paperSize(int); + void fillTextFields(); + + // Dummy function, for convenience. Ignores the argument and calls + // the fillTextFields() slot. + void unitsChanged(int); + void orientationChanged(int = 0); + void input(const QString &); + +private: + pageSize chosenSize; + + QDoubleValidator *widthValidator; + QDoubleValidator *heightValidator; +}; + +#endif // PAGESIZEWIDGET_H diff --git a/kviewshell/pageSizeWidget_base.ui b/kviewshell/pageSizeWidget_base.ui new file mode 100644 index 00000000..25cdc648 --- /dev/null +++ b/kviewshell/pageSizeWidget_base.ui @@ -0,0 +1,260 @@ + +pageSizeWidget_base + + + pageSizeWidget_base + + + + 0 + 0 + 595 + 175 + + + + + 4 + 4 + 0 + 0 + + + + + + + + unnamed + + + 11 + + + 6 + + + + GroupBox5 + + + Box + + + Sunken + + + Page Format + + + + unnamed + + + 11 + + + 6 + + + + TextLabel3 + + + Format: + + + + + TextLabel1 + + + Width: + + + + + TextLabel2 + + + Height: + + + + + widthInput + + + Width of the chosen paper size in portrait orientation + + + + + heightInput + + + Height of the chosen paper size in portrait orientation + + + + + + cm + + + + + mm + + + + + in + + + + heightUnits + + + + + + cm + + + + + mm + + + + + in + + + + widthUnits + + + + + TextLabel4 + + + Orientation: + + + + + formatChoice + + + + + + Portrait + + + + + Landscape + + + + orientationChoice + + + + + + + GroupBox6 + + + + 3 + 7 + 0 + 0 + + + + Box + + + 1 + + + Page Preview + + + + unnamed + + + 11 + + + 6 + + + + previewer + + + + 3 + 3 + 0 + 0 + + + + + 250 + 50 + + + + + + + + + + SizePreview +
sizePreview.h
+ + -1 + -1 + + 0 + + 7 + 7 + 0 + 0 + + image0 +
+
+ + + 89504e470d0a1a0a0000000d4948445200000016000000160806000000c4b46c3b0000041d49444154388d8d95c18b1c4514c67fbb29e5b54ea05b26da1d36b08d09a6c508138cb8c13d647141163de49043bca9f1628eb9294810f24f88c15b728b07617358888785d980427290b4a8d00b19a8821da8228cf42316ac87eed9247ac95c9aeaeef9eaabdffb5ebf85f178ccfcf7f5b79fec07a7280a08a008f25ceb7c29e5da573716e65a0be3f198ef6f5ededff9b9e1b34faf90882092a1ea21428b9220ffbf46054055b19386ba6970ae6665b5e0d2c5eb0b066067bbe1f21757c88f5504eff0c192189081303a5582b16854fccce2fe041f00cdd0a8103b874556405476b62d972ec2c2b90ff3fd8df5cb946549d80b34aea15ccaa94625450e2d0e558b460b7371a734b5e027251a155545678ab62d7eea90b4c504078908c107acb354cb251b1f8f08b1c18631aabbb4d18211404804f22545863561d2f060fb34aa8a18d0086204e72c46a32292e1a796f278c1da7a859d8da9a737506db04d0a24103d8949480b214d3bbee952a05c1d536f9d2684160c744f048311543d459eb1b65ee1754cedae13a64abd5d6127053c9d0281f22d4f75c6a2118a5c09272d7ab740638bf4ef2dd2174032d0d8d04c6fe19c32bedd8bc65e342aa3f785e2784bfd6bc1bd9db24b05505616a4edd67d5a8c1801a03a9962754c504bf34b8986acfb9be91cac7d54315a15541b36b134f74bd2dc53940115a5381e087bc25c6f71be831a8baa234cc1ee3eeb74ed83116beb2b8849d108a3730e8ce27e2bfb82419a7b407ac7c222fd0e6280a8078ce64e47ef54ac9d5be9aaae0162e0de568a44a18d3df90845d919c4f01463406340a44f40efb43a99b371618488e2670d1a2d7ee6f1d3be39342104e937ed85fb86599c33e9ee0be9a004946259d8b858909880d59aa00daa0d3280b5f39e34074c4b3a5434029a76a9e9f5cc0163b5c80024158a1cfc4cb1d3061d5834066cb0a403c008599a70f6bc27b8d03106ea3ae91987278c5b143b015547224ab5e24185cd9b81665273e7b6e7cecd82ddbf8098002de94049737bc09828cf3036f31c3735c8b0e98eb704672f348c7f7c93cd1fb2ae905119ff54e0dff3542b0e9dd983dcea2cc5eea6cf303e74ec687af5c41b6fa38f5e203c7ec8e1238ef85831a214afef21060ea781ec68247b65c6916353cc8b73d1ae79354606af2a930709aa118da1636c274dd7e3774b884a79aa6b5711a538d33cf9c01c1c9b83bc6a1f55c91ccb23b05b1deb43a7decdaffefd2862f61366aa8449c69e33642f012f2bb208fa98eeda1f3346f076c8eeef19475e9be115423364f78f84105a8643d34d90cfbfdcd8cf872589a45dc7f54cf3134a36f4e495c23f9da8db15bccb708d4014f2ca512cb5d4f7c13e84300d5cff6eb39b202bab053bdb9674f0648609427d0f8402ddeac30f4fcd3ced3efc3b422d406c09b340bedc1571e1bfc3d44dc281634c5fe5e758e7395cfbe6d6c130fd17d92372d277c159b70000000049454e44ae426082 + + + + formatChoice + orientationChoice + widthInput + widthUnits + heightInput + + + + kcombobox.h + kcombobox.h + kcombobox.h + kcombobox.h + +
diff --git a/kviewshell/pageView.cpp b/kviewshell/pageView.cpp new file mode 100644 index 00000000..c7813280 --- /dev/null +++ b/kviewshell/pageView.cpp @@ -0,0 +1,593 @@ +/* This file is part of the KDE project + Copyright (C) 2001 Wilco Greven + Copyright (C) 2002-2004 Stefan Kebekus + Copyright (C) 2004-2005 Wilfried Huss + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#include + +#include +#include +#include +#include +#include + +#include "pageView.h" +#include "pageNumber.h" + +PageView::PageView( QWidget* parent, const char* name ) + : QScrollView( parent, name, WStaticContents | WNoAutoErase) +{ + moveTool = true; + + widgetList = 0; + viewport()->setFocusPolicy(QWidget::StrongFocus); + + setResizePolicy(QScrollView::Manual); + + setVScrollBarMode(QScrollView::Auto); + setHScrollBarMode(QScrollView::Auto); + + viewport()->setBackgroundMode(Qt::NoBackground); + + setResizePolicy(Manual); + setDragAutoScroll(false); + + enableClipper(true); + nrCols = 1; + nrRows = 1; + continuousViewmode = true; + fullScreen = false; + + connect(this, SIGNAL(contentsMoving(int, int)), this, SLOT(calculateCurrentPageNumber(int, int))); +} + + +void PageView::addChild( QPtrVector *wdgList ) +{ + if( wdgList == 0 ) { + kdError(1223) << "PageView::addChild(...) called with invalid arguments" << endl; + return; + } + + widgetList = wdgList; + layoutPages(); +} + + +bool PageView::atTop() const +{ + return verticalScrollBar()->value() == verticalScrollBar()->minValue(); +} + + +bool PageView::atBottom() const +{ + return verticalScrollBar()->value() == verticalScrollBar()->maxValue(); +} + + +bool PageView::readUp() +{ + if( atTop() ) + return false; + else { + // Coordinate of the top of the viewport + int top = contentsY(); + + DocumentWidget* widget = 0; + // Find the widget(s) that intersect the top of the viewport + // TODO: It would be better to use a binary search. + for(Q_UINT16 i=0; isize(); i++) + { + widget = widgetList->at(i); + if (childY(widget) < top && childY(widget) + widget->height() > top) + { + // Draw scrollguide + widget->drawScrollGuide(top - childY(widget)); + } + } + + int newValue = QMAX( verticalScrollBar()->value() - (int)(height() * 0.9), + verticalScrollBar()->minValue() ); + verticalScrollBar()->setValue( newValue ); + return true; + } +} + + +bool PageView::readDown() +{ + if( atBottom() ) + return false; + else { + // Coordinate of the bottom of the viewport + int bottom = contentsY() + visibleHeight(); + + DocumentWidget* widget = 0; + // Find the widget(s) that intersect the bottom of the viewport + // TODO: It would be better to use a binary search. + for(Q_UINT16 i=0; isize(); i++) + { + widget = widgetList->at(i); + if (childY(widget) < bottom && childY(widget) + widget->height() > bottom) + { + // Draw scrollguide + widget->drawScrollGuide(bottom - childY(widget)); + } + } + + int newValue = QMIN( verticalScrollBar()->value() + (int)(height() * 0.9), + verticalScrollBar()->maxValue() ); + verticalScrollBar()->setValue( newValue ); + + return true; + } +} + + +void PageView::scrollRight() +{ + horizontalScrollBar()->addLine(); +} + +void PageView::scrollLeft() +{ + horizontalScrollBar()->subtractLine(); +} + +void PageView::scrollDown() +{ + verticalScrollBar()->addLine(); +} + +void PageView::scrollUp() +{ + verticalScrollBar()->subtractLine(); +} + +void PageView::scrollBottom() +{ + verticalScrollBar()->setValue( verticalScrollBar()->maxValue() ); +} + +void PageView::scrollTop() +{ + verticalScrollBar()->setValue( verticalScrollBar()->minValue() ); +} + +void PageView::keyPressEvent( QKeyEvent* e ) +{ + switch ( e->key() ) { + case Key_Up: + scrollUp(); + break; + case Key_Down: + scrollDown(); + break; + case Key_Left: + scrollLeft(); + break; + case Key_Right: + scrollRight(); + break; + default: + e->ignore(); + return; + } + e->accept(); +} + +void PageView::contentsMousePressEvent( QMouseEvent* e ) +{ + if (e->button() == LeftButton) + { + if (moveTool) + { + setCursor(Qt::SizeAllCursor); + dragGrabPos = e->globalPos(); + } + else + { + // we are in selection mode + } + } + else + { + setCursor(Qt::arrowCursor); + } +} + +void PageView::contentsMouseReleaseEvent( QMouseEvent* ) +{ + setCursor(Qt::arrowCursor); +} + +void PageView::contentsMouseMoveEvent( QMouseEvent* e ) +{ + QPoint newPos = e->globalPos(); + + if (e->state() == LeftButton && moveTool) + { + QPoint delta = dragGrabPos - newPos; + scrollBy(delta.x(), delta.y()); + } + dragGrabPos = newPos; +} + +void PageView::viewportResizeEvent( QResizeEvent* e ) +{ + QScrollView::viewportResizeEvent( e ); + + if (!widgetList) + return; + + layoutPages(); + + emit viewSizeChanged( viewport()->size() ); +} + +void PageView::setNrColumns( Q_UINT8 cols ) +{ + nrCols = cols; +} + +void PageView::setNrRows( Q_UINT8 rows ) +{ + nrRows = rows; +} + +void PageView::setContinuousViewMode(bool continuous) +{ + continuousViewmode = continuous; +} + +bool PageView::singlePageFullScreenMode() +{ + return (nrCols == 1 && nrRows == 1 && !continuousViewmode && fullScreen); +} + +void PageView::slotShowScrollbars(bool status) +{ + if (status == true) { + setVScrollBarMode(QScrollView::Auto); + setHScrollBarMode(QScrollView::Auto); + } else { + setVScrollBarMode(QScrollView::AlwaysOff); + setHScrollBarMode(QScrollView::AlwaysOff); + } +} + +void PageView::setFullScreenMode(bool fullScreen) +{ + this -> fullScreen = fullScreen; + if (fullScreen == true) + { + setVScrollBarMode(QScrollView::AlwaysOff); + setHScrollBarMode(QScrollView::AlwaysOff); + oldFrameStyle = frameStyle(); + setFrameStyle(QFrame::NoFrame); + backgroundColor = viewport()->paletteBackgroundColor(); + if (singlePageFullScreenMode()) + { + viewport()->setPaletteBackgroundColor( Qt::black ) ; + } + } + else + { + viewport()->setPaletteBackgroundColor( backgroundColor ) ; + setFrameStyle(oldFrameStyle); + } +} + +void PageView::layoutPages(bool zoomChanged) +{ + // Paranoid safety check + if (widgetList == 0) + return; + + // If there are no widgets, e.g. because the last widget has been + // removed, the matter is easy: set the contents size to 0. If there + // are no widgets because previously existing widgets were removed + // (we detect that by looking at the contentsWidth and -Height). + if (widgetList->isEmpty()) { + if ((contentsWidth() != 0) || (contentsHeight() != 0)) { + QScrollView::resizeContents(0,0); + } + return; + } + + // Ok, now we are in a situation where we do have some widgets that + // shall be centered. + int distance = distanceBetweenWidgets; + if (singlePageFullScreenMode()) + { + // In single page fullscreen mode we don't want a margin around the pages + distance = 0; + } + + QMemArray colWidth(nrCols); + for(Q_UINT8 i=0; isize()+2*nrCols-2) / nrCols; + } + else + { + numRows = (Q_INT16)ceil(((double)widgetList->size()) / nrCols); + } + + QMemArray rowHeight(numRows); + for(Q_UINT16 i=0; isize(); i++) + { + Q_UINT8 col; + Q_UINT16 row; + + if (nrCols == 2) { + // In two-column display, start with the right column + col = (i+1+nrCols) % nrCols; + row = (i+1+nrCols) / nrCols - 1; + } else { + col = (i+nrCols) % nrCols; + row = (i+nrCols) / nrCols - 1; + } + + colWidth[col] = QMAX(colWidth[col], (Q_UINT32)widgetList->at(i)->pageSize().width()); + rowHeight[row] = QMAX(rowHeight[row], (Q_UINT32)widgetList->at(i)->pageSize().height()); + } + + // Calculate the total width and height of the display + Q_UINT32 totalHeight = 0; + for(Q_UINT16 i=0; i totalWidth ) + centeringLeft = ( newViewportSize.width() - totalWidth )/2; + Q_UINT32 centeringTop = 0; + if( (Q_UINT32)newViewportSize.height() > totalHeight ) + centeringTop = ( newViewportSize.height() - totalHeight)/2; + + // Resize the viewport + if (((Q_UINT32)contentsWidth() != totalWidth) || ((Q_UINT32)contentsHeight() != totalHeight)) + { + // Calculate the point in the coordinates of the contents which is currently at the center of the viewport. + QPoint midPoint = QPoint(visibleWidth() / 2 + contentsX(), visibleHeight() / 2 + contentsY()); + double midPointRatioX = (double)(midPoint.x()) / contentsWidth(); + double midPointRatioY = (double)(midPoint.y()) / contentsHeight(); + + resizeContents(totalWidth,totalHeight); + + // If the zoom changed recenter the former midPoint + if (zoomChanged) + center((int)(contentsWidth() * midPointRatioX), (int)(contentsHeight() * midPointRatioY)); + } + + // Finally, calculate the left and top coordinates of each row and + // column, respectively + QMemArray colLeft(nrCols); + colLeft[0] = distance; + for(Q_UINT8 i=1; i rowTop(numRows); + rowTop[0] = distance; + for(Q_UINT16 i=1; isize(); i++) + { + Q_UINT8 col; + Q_UINT16 row; + if (nrCols == 2) + { + // In two column-mode start with the right column. + col = (i+nrCols-1) % nrCols; + row = (i+nrCols-1) / nrCols; + } + else + { + col = (i+nrCols) % nrCols; + row = i / nrCols; + } + if (nrCols == 2) + { + // in 2-column mode right justify the first column, and leftjustify the second column + int width = widgetList->at(i)->width(); + int left; + if (col == 0) + left = centeringLeft + colLeft[col] + colWidth[col]-width + distance/2; + else + left = centeringLeft + colLeft[col]; + moveChild( widgetList->at(i), left, centeringTop+rowTop[row]); + } + else + { + // in single column and overview mode center the widgets + int widgetWidth = widgetList->at(i)->width(); + int left = centeringLeft + colLeft[col] + ((int)colWidth[col]-widgetWidth)/2; + moveChild(widgetList->at(i), left, centeringTop+rowTop[row]); + } + } + calculateCurrentPageNumber(); +} + + +void PageView::contentsWheelEvent ( QWheelEvent * e ) +{ + emit(wheelEventReceived(e)); +} + + +void PageView::moveViewportToWidget(QWidget* widget, int y) +{ + int verticalPos = 0; + int verticalPosTop = 0; + + if (y != 0) + { + verticalPosTop = childY(widget) + y - visibleHeight()/2; + verticalPos = childY(widget) + y; + } + else + { + verticalPos = childY(widget) - distanceBetweenWidgets; + verticalPosTop = verticalPos; + } + + if (nrCols == 1) + { + // In single column viewmodes, we change the vertical position only, to make it + // easier to work with high zoomlevels where not the whole pagewidth is visible. + // TODO: Smarter algorithm also for continuous facing viewmode. + int top = (int)(contentsY() + 0.1 * visibleHeight()); + int bottom = (int)(contentsY() + 0.9 * visibleHeight()); + + // Move the viewport if the target is currently not visible, or lies at the edge + // of the viewport. If y = 0 always move the top of the targetpage to the top edge + // of the viewport. + if (verticalPos < top || verticalPos > bottom || y == 0) + { + setContentsPos(contentsX(), verticalPosTop); + } + } + else + { + setContentsPos(childX(widget) - distanceBetweenWidgets, verticalPosTop); + } +} + + +void PageView::viewportPaintEvent(QPaintEvent* e) +{ + // Region from which rectangles occupied by child widgets will by substracted. + QRegion backgroundArea(e->rect()); + + if (widgetList != 0) + { + for (unsigned int i = 0; i < widgetList->count(); i++) + { + DocumentWidget* item = widgetList->at(i); + + // Check if the Widget needs to be updated. + if (!item->geometry().intersects(e->rect())) + continue; + + QRect widgetGeometry = item->geometry(); + + // Draw the widget. + if (e->rect().intersects(widgetGeometry)) + { + QRect widgetRect = e->rect().intersect(widgetGeometry); + widgetRect.moveBy(-widgetGeometry.left(), -widgetGeometry.top()); + + item->update(widgetRect); + } + + // Substract the painted area. + backgroundArea -= widgetGeometry.intersect(e->rect()); + } + } + + // Paint the background. + QPainter p(viewport()); + + QMemArray backgroundRects = backgroundArea.rects(); + + for (unsigned int i = 0; i < backgroundRects.count(); i++) + p.fillRect(backgroundRects[i], colorGroup().mid()); +} + + +void PageView::calculateCurrentPageNumber(int x, int y) +{ + // Safety check + if (widgetList == 0) + return; + + QRect viewportRect(x, y, visibleWidth(), visibleHeight()); + + //kdDebug() << "viewportRect(" << viewportRect.x() << ", " << viewportRect.y() << ", " + // << viewportRect.width() << ", " << viewportRect.height() << ")" << endl; + + int maxVisiblePixels = 0; + DocumentWidget* _currentWidget = 0; + + for (Q_UINT16 i = 0; i < widgetList->size(); i++) + { + DocumentWidget* documentWidget = widgetList->at(i); + // Safety check + if (documentWidget == 0) + continue; + + // Check if the Widget is visible + int cx = childX(documentWidget); + int cy = childY(documentWidget); + QRect widgetRect(cx, cy, documentWidget->width(), documentWidget->height()); + bool isVisible = widgetRect.intersects(viewportRect); + + if (!isVisible) + continue; + + // Calculate the number of visible pixels of the widget + QRect visibleRect = widgetRect.intersect(viewportRect); + int visiblePixels = visibleRect.width() * visibleRect.height(); + + //kdDebug() << visiblePixels << " pixels are visible of page " << documentWidget->getPageNumber() << endl; + + // If a bigger part of this widget as of the previous widgets is visible make it the current widget. + if (maxVisiblePixels < visiblePixels) + { + maxVisiblePixels = visiblePixels; + _currentWidget = documentWidget; + } + } + + // No page is visible + if (_currentWidget == 0) + return; + + // Return the number of the current page + emit currentPageChanged(_currentWidget->getPageNumber()); +} + +void PageView::calculateCurrentPageNumber() +{ + calculateCurrentPageNumber(contentsX(), contentsY()); +} + +void PageView::slotEnableMoveTool(bool enable) +{ + moveTool = enable; +} + +#include "pageView.moc" diff --git a/kviewshell/pageView.h b/kviewshell/pageView.h new file mode 100644 index 00000000..d87ae0ca --- /dev/null +++ b/kviewshell/pageView.h @@ -0,0 +1,175 @@ +// -*- C++ -*- +/* This file is part of the KDE project + Copyright (C) 2001 Wilco Greven + Copyright (C) 2004-2005 Wilfried Huss + Copyright (C) 2005 Stefan Kebekus + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License as published by the Free Software Foundation; either + version 2 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02110-1301, USA. +*/ + +#ifndef PAGEVIEW_H +#define PAGEVIEW_H + +#include "documentWidget.h" + +#include +#include + +class PageNumber; + +/* + * PageView is a customized QScrollView, which can hold one + * page. This page will be centered on the viewport. + */ +class PageView : public QScrollView +{ + Q_OBJECT + +public: + PageView( QWidget* parent = 0, const char* name = 0 ); + ~PageView() {} + + void addChild( QPtrVector *wdgList ); + + /** Sets the number of columns into which the widgets are + aligned. If nothing is set, '2' is the default. */ + void setNrColumns( Q_UINT8 cols ); + void setNrRows( Q_UINT8 rows ); + + void setContinuousViewMode(bool continuous); + bool fullScreenMode() { return fullScreen; } + bool singlePageFullScreenMode(); + bool overviewMode() { return !continuousViewmode && (nrRows > 1 || nrCols > 1); } + + /** Returns the number of columns into which the widgets are aligned. + + This method returns the number of colums actually used to display + the widgets. + + @warning This method need not return the number columns set in the + setViewMode() method. For instance, if the viewmode + KVSPrefs::EnumViewMode::ContinuousFacing is set, but there is only + one widget, then only one column is used, and the method returns + the number one. + + If there aren't any widgets, the number 1 is returned. + + @returns Number of columns used, or 1 if there aren't any + widgets. The number i returned always satisfies 1 <= i <= nrCols, + where nrCols is the private variable of the same nane. + */ + Q_UINT8 getNrColumns() const { return (widgetList==0) ? 1 : QMIN(nrCols, QMAX(1, widgetList->size())); } + + Q_UINT8 getNrRows() const { return nrRows; } + bool isContinuous() const { return continuousViewmode; } + + /** Return true if the top resp. bottom of the page is visible. */ + bool atTop() const; + bool atBottom() const; + + /** Distance between pages in pixels (this is independent of + the zoom level). */ + int distanceBetweenPages() { return distanceBetweenWidgets; } + + /** Moves the viewport so that the widget is at the top left corner. */ + void moveViewportToWidget(QWidget* widget, int y = 0); + + bool isMoveToolEnabled() const { return moveTool; } + +public slots: + void calculateCurrentPageNumber(); + + bool readUp(); + bool readDown(); + void scrollUp(); + void scrollDown(); + void scrollRight(); + void scrollLeft(); + void scrollBottom(); + void scrollTop(); + + void setFullScreenMode(bool fullScreen); + /** Turn the scrollbars on/off. */ + void slotShowScrollbars(bool); + + /** Set layout of the page widgets according to the current viewmode and zoomlevel. + Set zoomChanged = true if the the layout needs updateing because the zoomlevel has changed. */ + void layoutPages(bool zoomChanged = false); + + void slotEnableMoveTool(bool enable); + +signals: + void viewSizeChanged(const QSize& size); + void pageSizeChanged(const QSize& size); + + void currentPageChanged(const PageNumber&); + + /** This signal is emitted when the scrollView receives a mouse + wheel event. */ + void wheelEventReceived( QWheelEvent * ); + +protected: + void keyPressEvent( QKeyEvent* ); + + /** Reimplemented from QScrollView to make sure that the page is + centered when it fits in the viewport. */ + void viewportResizeEvent( QResizeEvent* ); + void viewportPaintEvent(QPaintEvent*); + + /** Reimplemented from QScrollView, because the kviepart needs to + handle the wheel events itself. The wheel event is passed on by + emitting the singal "wheelEventReceived". Nothing else is done. */ + void contentsWheelEvent ( QWheelEvent * ); + + void contentsMousePressEvent(QMouseEvent*); + void contentsMouseReleaseEvent(QMouseEvent*); + void contentsMouseMoveEvent(QMouseEvent*); + +private slots: + void calculateCurrentPageNumber(int x, int y); + +private: + /** Stores the mouse position between two mouse events. This is used + to implement the "grab and drag the viewport contents" feature. */ + QPoint dragGrabPos; + + QPtrVector* widgetList; + + /** Used internally by the centerContents()-method. Set with the + setNrColumns() method */ + Q_UINT8 nrCols; + Q_UINT8 nrRows; + + bool continuousViewmode; + bool fullScreen; + + /** This int remembers the style of the frame of the centering + scrollview when fullscreen mode is switched on. It is then + restored when it is switched off. */ + int oldFrameStyle; + + /** color of the viewport's background. This is also + stored on entering the fullscreen mode. */ + QColor backgroundColor; + + /** Distance between pages in pixels + (this is independent of the zoom level). */ + static const int distanceBetweenWidgets=10; + + bool moveTool; +}; + +#endif diff --git a/kviewshell/pics/Makefile.am b/kviewshell/pics/Makefile.am new file mode 100644 index 00000000..80777a5d --- /dev/null +++ b/kviewshell/pics/Makefile.am @@ -0,0 +1,3 @@ +SUBDIRS = icons + +KDE_ICON = kviewshell diff --git a/kviewshell/pics/cr16-app-kviewshell.png b/kviewshell/pics/cr16-app-kviewshell.png new file mode 100644 index 00000000..f9b15a18 Binary files /dev/null and b/kviewshell/pics/cr16-app-kviewshell.png differ diff --git a/kviewshell/pics/cr32-app-kviewshell.png b/kviewshell/pics/cr32-app-kviewshell.png new file mode 100644 index 00000000..abb2830e Binary files /dev/null and b/kviewshell/pics/cr32-app-kviewshell.png differ diff --git a/kviewshell/pics/cr48-app-kviewshell.png b/kviewshell/pics/cr48-app-kviewshell.png new file mode 100644 index 00000000..6faaec42 Binary files /dev/null and b/kviewshell/pics/cr48-app-kviewshell.png differ diff --git a/kviewshell/pics/icons/Makefile.am b/kviewshell/pics/icons/Makefile.am new file mode 100644 index 00000000..f1dcd108 --- /dev/null +++ b/kviewshell/pics/icons/Makefile.am @@ -0,0 +1,3 @@ +kviewparticondir = $(kde_datadir)/kviewerpart/icons + +kviewparticon_ICON = AUTO diff --git a/kviewshell/pics/icons/hi16-action-movetool.png b/kviewshell/pics/icons/hi16-action-movetool.png new file mode 100644 index 00000000..11a69bdd Binary files /dev/null and b/kviewshell/pics/icons/hi16-action-movetool.png differ diff --git a/kviewshell/pics/icons/hi16-action-selectiontool.png b/kviewshell/pics/icons/hi16-action-selectiontool.png new file mode 100644 index 00000000..d9a0eba6 Binary files /dev/null and b/kviewshell/pics/icons/hi16-action-selectiontool.png differ diff --git a/kviewshell/pics/icons/hi22-action-movetool.png b/kviewshell/pics/icons/hi22-action-movetool.png new file mode 100644 index 00000000..ccb0823c Binary files /dev/null and b/kviewshell/pics/icons/hi22-action-movetool.png differ diff --git a/kviewshell/pics/icons/hi22-action-selectiontool.png b/kviewshell/pics/icons/hi22-action-selectiontool.png new file mode 100644 index 00000000..6d9a797b Binary files /dev/null and b/kviewshell/pics/icons/hi22-action-selectiontool.png differ diff --git a/kviewshell/pics/icons/hi32-action-movetool.png b/kviewshell/pics/icons/hi32-action-movetool.png new file mode 100644 index 00000000..134c76d2 Binary files /dev/null and b/kviewshell/pics/icons/hi32-action-movetool.png differ diff --git a/kviewshell/pics/icons/hi48-action-movetool.png b/kviewshell/pics/icons/hi48-action-movetool.png new file mode 100644 index 00000000..8d51a696 Binary files /dev/null and b/kviewshell/pics/icons/hi48-action-movetool.png differ diff --git a/kviewshell/plugins/Makefile.am b/kviewshell/plugins/Makefile.am new file mode 100644 index 00000000..c81110a2 --- /dev/null +++ b/kviewshell/plugins/Makefile.am @@ -0,0 +1 @@ +SUBDIRS = djvu diff --git a/kviewshell/plugins/djvu/Makefile.am b/kviewshell/plugins/djvu/Makefile.am new file mode 100644 index 00000000..c0124821 --- /dev/null +++ b/kviewshell/plugins/djvu/Makefile.am @@ -0,0 +1,33 @@ +INCLUDES = -I$(top_srcdir) $(all_includes) \ + -I$(top_srcdir)/kviewshell \ + -I$(top_builddir)/kviewshell \ + -I$(kde_includes)/kviewshell \ + -I$(srcdir)/libdjvu + +SUBDIRS = libdjvu . + +KDE_CXXFLAGS = -Wno-deprecated + +METASOURCES = AUTO + +# this is where the desktop file will go +kde_services_DATA = djvumultipage.desktop + +# this is where the shell's XML-GUI resource file goes +shellrcdir = $(kde_datadir)/kviewshell/plugins/djvu + +kde_module_LTLIBRARIES = djvuviewpart.la +djvuviewpart_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) -module +djvuviewpart_la_LIBADD = -lkdeprint -lkparts $(top_builddir)/kviewshell/libkmultipage.la libdjvu/libdjvu.la +djvuviewpart_la_SOURCES = djvumultipage.cpp djvurenderer.cpp kprintDialogPage_DJVUpageoptions.cpp \ + kprintDialogPage_DJVUconversionoptions.cpp kprintDialogPage_DJVUconversionoptions_basewidget.ui \ + pageRangeWidget_base.ui pageRangeWidget.cpp \ + prefs.kcfgc + +kde_kcfg_DATA = djvumultipage.kcfg + +pluginsdir = $(kde_datadir) +plugins_DATA = djvumultipage.rc + +messages: rc.cpp + $(XGETTEXT) *.cpp -o $(podir)/kdjview.pot diff --git a/kviewshell/plugins/djvu/djvumultipage.cpp b/kviewshell/plugins/djvu/djvumultipage.cpp new file mode 100644 index 00000000..2599cc29 --- /dev/null +++ b/kviewshell/plugins/djvu/djvumultipage.cpp @@ -0,0 +1,357 @@ +/*************************************************************************** + * Copyright (C) 2005 by Stefan Kebekus * + * kebekus@kde.org * + * * + * Copyright (C) 2005 by Wilfried Huss * + * Wilfried.Huss@gmx.at * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include "kvsprefs.h" +#include +#include +#include +#include + +#include "ByteStream.h" +#include "DjVuToPS.h" +#include "kprintDialogPage_DJVUpageoptions.h" +#include "kprintDialogPage_DJVUconversionoptions.h" +#include "djvumultipage.h" +#include "pageRangeWidget.h" +#include "prefs.h" + +#include "kmessagebox.h" + +typedef KParts::GenericFactory DjVuMultiPageFactory; +K_EXPORT_COMPONENT_FACTORY(djvuviewpart, DjVuMultiPageFactory) + + +DjVuMultiPage::DjVuMultiPage(QWidget *parentWidget, const char *widgetName, QObject *parent, + const char *name, const QStringList&) + : KMultiPage(parentWidget, widgetName, parent, name), djvuRenderer(parentWidget) +{ + /* This is kparts wizardry that cannot be understood by man. Simply + change the names to match your implementation. */ + setInstance(DjVuMultiPageFactory::instance()); + djvuRenderer.setName("DjVu renderer"); + + // Render modes + QStringList renderModes; + renderModes.append(i18n("Color")); + renderModes.append(i18n("Black and White")); + renderModes.append(i18n("Show foreground only")); + renderModes.append(i18n("Show background only")); + renderModeAction = new KSelectAction (i18n("Render Mode"), 0, 0, 0, actionCollection(), "render_mode"); + renderModeAction->setItems(renderModes); + + renderModeAction->setCurrentItem(Prefs::renderMode()); + + deletePagesAction = new KAction(i18n("Delete Pages..."), 0, this, SLOT(slotDeletePages()), actionCollection(), "delete_pages"); + + // change the rendermode + connect(renderModeAction, SIGNAL(activated(int)), this, SLOT(setRenderMode(int))); + + /* It is very important that this method is called in the + constructor. Otherwise kmultipage does not know how to render + files, and crashes may result. */ + setRenderer(&djvuRenderer); + + setXMLFile("djvumultipage.rc"); + + enableActions(false); +} + + +DjVuMultiPage::~DjVuMultiPage() +{ + ; +} + + +KAboutData* DjVuMultiPage::createAboutData() +{ + /* You obviously want to change this to match your setup */ + KAboutData* about = new KAboutData("djvumultipage", I18N_NOOP("KDjView"), "0.1", + I18N_NOOP("KViewshell DjVu Plugin."), + KAboutData::License_GPL, + "Wilfried Huss", + I18N_NOOP("This program displays DjVu files.")); + + about->addAuthor ("Stefan Kebekus", + I18N_NOOP("KViewShell plugin"), + "kebekus@kde.org", + "http://www.mi.uni-koeln.de/~kebekus"); + + about->addAuthor ("Wilfried Huss", + I18N_NOOP("DjVu file loading"), + "Wilfried.Huss@gmx.at"); + + return about; +} + +void DjVuMultiPage::enableActions(bool b) +{ + KMultiPage::enableActions(b); + + deletePagesAction->setEnabled(b); +} + +void DjVuMultiPage::setFile(bool r) +{ + enableActions(r); +} + +QStringList DjVuMultiPage::fileFormats() const +{ + /* This list is used in the file selection dialog when the file is + saved */ + QStringList r; + r << i18n("*.djvu|DjVu file (*.djvu)"); + return r; +} + + +void DjVuMultiPage::setRenderMode(int mode) +{ + // Save renderMode for future uses + switch (mode) + { + case Prefs::EnumRenderMode::BlackAndWhite: + Prefs::setRenderMode(Prefs::EnumRenderMode::BlackAndWhite); + + break; + case Prefs::EnumRenderMode::Foreground: + Prefs::setRenderMode(Prefs::EnumRenderMode::Foreground); + + break; + case Prefs::EnumRenderMode::Background: + Prefs::setRenderMode(Prefs::EnumRenderMode::Background); + + break; + default: //Prefs::EnumRenderMode::Color + Prefs::setRenderMode(Prefs::EnumRenderMode::Color); + } + Prefs::writeConfig(); + renderModeChanged(); +} + + +void DjVuMultiPage::slotDeletePages() +{ + if (numberOfPages() == 0) + return; + + KDialogBase dialog( parentWdg, "urldialog", true, i18n("Delete Pages"), KDialogBase::Ok|KDialogBase::Cancel, KDialogBase::Ok, true ); + PageRangeWidget range( 1, numberOfPages(), currentPageNumber(), &dialog, "range widget" ); + QToolTip::add( &range, i18n( "Select the pages you wish to delete." ) ); + dialog.setButtonOK(i18n("Delete Pages")); + dialog.setMainWidget(&range); + if (dialog.exec() != QDialog::Accepted) + return; + + djvuRenderer.deletePages(range.getFrom(), range.getTo()); + + + // ========= + pageCache->deselectText(); + document_history.clear(); + pageCache->clear(); + + generateDocumentWidgets(); + + // Set number of widgets in the thumbnail sidebar + markList()->clear(); + markList()->setNumberOfPages(numberOfPages(), KVSPrefs::showThumbnails()); + + // Set Table of Contents + //@@@@@@@@@@ tableOfContents->setContents(renderer->getBookmarks()); + + // Clear Statusbar + emit setStatusBarText(QString::null); +} + + +void DjVuMultiPage::print() +{ + // Paranoid safety checks + if (djvuRenderer.isEmpty()) + return; + + // Allocate the printer structure + KPrinter *printer = getPrinter(false); + if (printer == 0) + return; + + KPrintDialogPage_DJVUPageOptions *pageOptions = new KPrintDialogPage_DJVUPageOptions(); + if (pageOptions == 0) { + kdError(1223) << "DjVuMultiPage::print(): Cannot allocate new KPrintDialogPage_PageOptions structure" << endl; + delete printer; + return; + } + printer->addDialogPage( pageOptions ); + + KPrintDialogPage_DJVUConversionOptions *conversionOptions = new KPrintDialogPage_DJVUConversionOptions(); + if (pageOptions == 0) { + kdError(1223) << "DjVuMultiPage::print(): Cannot allocate new KPrintDialogPage_ConversionOptions structure" << endl; + delete printer; + return; + } + printer->addDialogPage( conversionOptions ); + + // initialize the printer using the print dialog + if ( printer->setup(parentWdg, i18n("Print %1").arg(m_file.section('/', -1))) ) { + // Now do the printing. + QValueList pageList = printer->pageList(); + if (pageList.isEmpty()) + printer->abort(); + else { + // Printing usually takes a while. This is to keep the GUI + // updated. + qApp->processEvents(); + + // Printing... + DjVuToPS converter; + DjVuToPS::Options &options = converter.options; + + // Set PostScript Language Level, taking 3 as the default + options.set_format(DjVuToPS::Options::PS); + QString op = printer->option( "kde-kdjvu-pslevel" ); + if (op == "1") + options.set_level(1); + else + if (op == "3") + options.set_level(3); + else + options.set_level(2); + + // Set page size orientation + if (printer->option( "kde-kviewshell-rotatepage" ) == "true") + options.set_orientation (DjVuToPS::Options::AUTO); + else + if ( printer->orientation() == KPrinter::Landscape ) + options.set_orientation (DjVuToPS::Options::LANDSCAPE); + else + options.set_orientation (DjVuToPS::Options::PORTRAIT); + + // Set render mode, taking "color" as default + op = printer->option("kde-kdjvu-rendermode"); + if (op == "black-and-white") + options.set_mode(DjVuToPS::Options::BW); + else + if (op == "foreground") + options.set_mode(DjVuToPS::Options::FORE); + else + if (op == "background") + options.set_mode(DjVuToPS::Options::BACK); + else + options.set_mode(DjVuToPS::Options::COLOR); + + // Set Color or Grayscale mode + if (printer->colorMode() == KPrinter::Color) + options.set_color(true); + else + options.set_color(false); + + // Set Zoom + if (printer->option( "kde-kdjvu-fitpage" ) == "true") + options.set_zoom(0); + else + options.set_zoom(100); + + KTempFile tmpPSFile(QString::null, "ps"); + tmpPSFile.close(); + tmpPSFile.setAutoDelete(true); + + if (djvuRenderer.convertToPSFile(converter, tmpPSFile.name(), pageList ) == true) + printer->printFiles( QStringList(tmpPSFile.name()), true ); + else + printer->abort(); + } + delete printer; + } +} + + +bool DjVuMultiPage::isReadWrite() const +{ + return true; +} + + +bool DjVuMultiPage::isModified() const +{ + return djvuRenderer.isModified(); +} + + +void DjVuMultiPage::slotSave() +{ + // Paranoid safety checks + if (djvuRenderer.isEmpty()) + return; + + // 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(parentWdg, i18n("The file %1\nalready exists. Do you want to overwrite it?").arg(fileName), + i18n("Overwrite File"), i18n("Overwrite")); + if (r == KMessageBox::Cancel) + return; + } + + djvuRenderer.save(fileName); + + /* + if (!djvuRenderer.save(fileName) == false) + KMessageBox::error( parentWdg, + i18n("File error. Unable to write to the specified file '%1'. The document is not saved.").arg(fileName), + i18n("File Error")); + */ + + return; +} + + + +#include "djvumultipage.moc" diff --git a/kviewshell/plugins/djvu/djvumultipage.desktop b/kviewshell/plugins/djvu/djvumultipage.desktop new file mode 100644 index 00000000..1de46b47 --- /dev/null +++ b/kviewshell/plugins/djvu/djvumultipage.desktop @@ -0,0 +1,57 @@ +[Desktop Entry] +Name=kdjview +Name[hu]=KDjView +Name[ja]=Kdjview +Name[ne]=केडीजे दृशà¥à¤¯ +Name[sk]=kdjView +Name[sv]=Kdjview +Name[zh_CN]=KDjView +Icon=kdjview +Type=Service +Comment=KViewShell plugin for DjVu files +Comment[bg]=ПриÑтавка за файлове DjVu +Comment[br]=Lugent KViewShell evit ar restroù DjVu +Comment[bs]=KViewShell dodatak za DjVu datoteke +Comment[ca]=Connector pel KViewShell per fitxers DjVu +Comment[cs]=KViewShell modul pro DjVu soubory +Comment[da]=Kviewshell-plugin for DjVu-filer +Comment[de]=Ein KViewShell-Modul für DjVu-Dateien +Comment[el]=ΠÏόσθετο του KViewShell για αÏχεία DjVu +Comment[es]=Extensión KViewShell para archivos DjVu +Comment[et]=KView DjVu-failide plugin +Comment[eu]=DjVu fitxategien KViewShell-en plugina +Comment[fa]=وصلۀ KViewShell برای پرونده‌های DjVu +Comment[fi]=KViewShell sovelma DjVu-tiedostoille +Comment[fr]=Module KViewShell pour les fichiers DjVu +Comment[gl]=Extensión de KViewShell para ficheiros DjVu +Comment[hu]=KViewShell-modul DjVu-fájlokhoz +Comment[is]=KViewShell íforrit fyrir DjVu skrár +Comment[it]=Plugin KViewShell per file DjVu +Comment[ja]=DjVu ファイル用㮠KViewShell プラグイン +Comment[kk]=DjVu файлдарына арналған KViewShell плагин модулі +Comment[km]=កម្មវិធី​ជំនួយ KViewShell សម្រាប់​ឯកសារ DjVu +Comment[lt]=KViewShell priedas, skirtas DjVu byloms +Comment[ms]=Plugin KViewShell untuk fail DjVu +Comment[nb]=KViewShell programtillegg for DjVu-filer +Comment[nds]=En "KViewShell"-Moduul för DjVu-Dateien +Comment[ne]=डिजे भीयू फाइलका लागि केडीई दृशà¥à¤¯ शेल पà¥à¤²à¤—इन +Comment[nl]=KViewShell-plugin voor DjVu-bestanden +Comment[nn]=KViewShell-programtillegg for DjVu-filer +Comment[pl]=Wtyczka KViewShell do plików DjVu +Comment[pt]='Plugin' do KViewShell para ficheiros do DjVu +Comment[pt_BR]=Plugin KViewShell para arquivos DjVu +Comment[ru]=Компонент проÑмотра файлов DjVu +Comment[sk]=KViewShell modul pre DjVu súbory +Comment[sl]=Vstavek za KViewShell za datoteke DjVu +Comment[sr]=KViewShell-ов прикључак за DjVu фајлове +Comment[sr@Latn]=KViewShell-ov prikljuÄak za DjVu fajlove +Comment[sv]=Kviewshell-insticksprogram för DjVu-filer +Comment[tr]=DjVu dosyaları için KViewShell eklentisi +Comment[uk]=Втулок переглÑду файлів DjVu Ð´Ð»Ñ KViewShell +Comment[zh_CN]=DjVu 文件的 KViewShell æ’件 +Comment[zh_HK]=用於 DjVu 檔案的 KViewShell æ’件 +Comment[zh_TW]=DjVu 檔的 KViewShell å¤–æŽ›ç¨‹å¼ +ServiceTypes=KViewShell/MultiPage +X-KDE-MimeTypes=image/x-djvu +X-KDE-Library=djvuviewpart +X-KDE-MultiPageVersion=2 diff --git a/kviewshell/plugins/djvu/djvumultipage.h b/kviewshell/plugins/djvu/djvumultipage.h new file mode 100644 index 00000000..b417144b --- /dev/null +++ b/kviewshell/plugins/djvu/djvumultipage.h @@ -0,0 +1,149 @@ +/*************************************************************************** + * Copyright (C) 2005 by Stefan Kebekus * + * kebekus@kde.org * + * * + * Copyright (C) 2005 by Wilfried Huss * + * Wilfried.Huss@gmx.at * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef __DJVUMULTIPAGE_H +#define __DJVUMULTIPAGE_H + +#include + +#include "kmultipage.h" +#include "djvurenderer.h" + +#include "DjVuToPS.h" + +class KSelectAction; + +/*! \mainpage DjVuMultiPage + +\section intro_sec Introduction + +kvsdemo is a minimal, but well-documented reference implementation of +a kviewshell plugin that can serve as a starting point for a +real-world implementation. + +\section install_sec Usage + +When kvsdemo is installed, the kviewshell program can open C++ source +files, i.e. files of mime type text/x-c++src. When such a file is +loaded, kviewshell shows 10 blank pages of A4 size. + +\section content Content + +Only the two classes that are absolutely necessary for a working +plugin are implemented. The only other file that is installed is a +desktop file, which tells kviewhshell to use the plugin. + +- kvsdemo_multipage, an implementation of a kmultipage. In a real +application, this class would create and manage the GUI elements that +the plugin adds to the GUI of the kviewshell. This implementation adds +nothing, and does only the minimal initialization required.. + +- kvsdemo_renderer, an implementation of a documentRenderer. This +class is responsible for document loading and rendering. + +- kvsdemo.desktop, the desktop entry file that tells KDE that kvsdemo +is a plugin for kviewshell that handles files of type +text/x-c++src. Without this file installed, the file dialog in +kviewshell would not show C++ source files, and the command line +"kvieshell test.cpp" would fail with an error dialog "No plugin for +text/x-c++src files installed". + +*/ + + + + +/*! \brief Well-documented minimal implementation of a KMultiPage + +This class provides a well-documented reference implementation of a +KMultiPage, suitable as a starting point for a real-world +implementation. In a real application, this class would contain the +GUI elements that the plugin adds to the GUI of the kviewshell. Our +implementation adds nothing, and does only the minimal initialization +required. + +*/ + +class DjVuMultiPage : public KMultiPage +{ + Q_OBJECT + +public: + /** Constructor + + The constructor needs to initialize several members of the + kmultipage. Please have a look at the constructor's source code to + see how to adjust this for your implementation. + */ + DjVuMultiPage(QWidget *parentWidget, const char *widgetName, QObject *parent, + const char *name, const QStringList& args = QStringList()); + + /** Destructor + + This destructor does nothing. + */ + virtual ~DjVuMultiPage(); + + virtual void setFile(bool r); + + /** List of file formats for file saving + + This method returns the list of supported file formats for saving + the file. + */ + virtual QStringList fileFormats() const; + + /** Author information + + This member returns a structure that contains information about the + authors of the implementation + */ + static KAboutData* createAboutData(); + + /** Re-implementation of the print method */ + virtual void print(); + + virtual bool isReadWrite() const; + virtual bool isModified() const; + + virtual void slotSave(); + + protected: + virtual void enableActions(bool); + + private slots: + void setRenderMode(int mode); + + /** Opens a dialog to delete pages */ + void slotDeletePages(); + + private: + /** This member holds the renderer which is used by the demo + implementation */ + DjVuRenderer djvuRenderer; + + KSelectAction* renderModeAction; + KAction* deletePagesAction; +}; + +#endif diff --git a/kviewshell/plugins/djvu/djvumultipage.kcfg b/kviewshell/plugins/djvu/djvumultipage.kcfg new file mode 100644 index 00000000..478667b7 --- /dev/null +++ b/kviewshell/plugins/djvu/djvumultipage.kcfg @@ -0,0 +1,18 @@ + + + + + + Color + + + + + + + + + diff --git a/kviewshell/plugins/djvu/djvumultipage.rc b/kviewshell/plugins/djvu/djvumultipage.rc new file mode 100644 index 00000000..c340fe42 --- /dev/null +++ b/kviewshell/plugins/djvu/djvumultipage.rc @@ -0,0 +1,15 @@ + + + + + &Edit + + + + + &View + + + + + diff --git a/kviewshell/plugins/djvu/djvurenderer.cpp b/kviewshell/plugins/djvu/djvurenderer.cpp new file mode 100644 index 00000000..54a96b38 --- /dev/null +++ b/kviewshell/plugins/djvu/djvurenderer.cpp @@ -0,0 +1,719 @@ +/*************************************************************************** + * Copyright (C) 2005 by Stefan Kebekus * + * kebekus@kde.org * + * * + * Copyright (C) 2005 by Wilfried Huss * + * Wilfried.Huss@gmx.at * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include "GBitmap.h" +#include "BSByteStream.h" +#include "IFFByteStream.h" + +#include "prefs.h" + +#include "documentWidget.h" +#include "djvurenderer.h" +#include "djvumultipage.h" +#include "hyperlink.h" +#include "renderedDocumentPagePixmap.h" +#include "textBox.h" + +//#define KF_DEBUG + +inline GUTF8String GStringFromQString(const QString& x) +{ + GUTF8String retval=(const char*)x.utf8(); + return retval; +} + + +inline QString QStringFromGString(const GUTF8String& x) +{ + QString retval=QString::fromUtf8((const char*)x); + return retval; +} + + +DjVuRenderer::DjVuRenderer(QWidget* par) + : DocumentRenderer(par) +{ +#ifdef KF_DEBUG + kdError() << "DjVuRenderer( parent=" << par << " )" << endl; +#endif + + PPMstream = ByteStream::create(); +} + + + +DjVuRenderer::~DjVuRenderer() +{ +#ifdef KF_DEBUG + kdDebug() << "~DjVuRenderer" << endl; +#endif + + // Wait for all access to this documentRenderer to finish + QMutexLocker locker( &mutex ); +} + + +void DjVuRenderer::drawPage(double resolution, RenderedDocumentPage* page) +{ +#ifdef KF_DEBUG + kdDebug() << "DjVuRenderer::drawPage(documentPage*) called, page number " << page->getPageNumber() << endl; +#endif + + // Paranoid safety checks + if (page == 0) { + kdError() << "DjVuRenderer::drawPage(documentPage*) called with argument == 0" << endl; + return; + } + if (page->getPageNumber() == 0) { + kdError() << "DjVuRenderer::drawPage(documentPage*) called for a documentPage with page number 0" << endl; + return; + } + + // Wait for all access to this documentRenderer to finish + QMutexLocker locker( &mutex ); + + // more paranoid safety checks + if (page->getPageNumber() > numPages) { + kdError() << "DjVuRenderer::drawPage(documentPage*) called for a documentPage with page number " << page->getPageNumber() + << " but the current fax file has only " << numPages << " pages." << endl; + return; + } + + int pageNumber = page->getPageNumber() - 1; + + GP djvuPage = document->get_page(pageNumber, true); + if (!djvuPage->wait_for_complete_decode()) + { + kdDebug() << "decoding failed." << endl; + return; + } + + if (!pageSizes[pageNumber].isValid()) + { + int djvuResolution = djvuPage->get_dpi(); + int djvuPageWidth = djvuPage->get_width(); + int djvuPageHeight = djvuPage->get_height(); + + Length w, h; + w.setLength_in_inch(djvuPageWidth / (double)djvuResolution); + h.setLength_in_inch(djvuPageHeight / (double)djvuResolution); + pageSizes[pageNumber].setPageSize(w, h); + + SimplePageSize ps = sizeOfPage(page->getPageNumber()); + + // If we are not printing we need to resize the pixmap. + RenderedDocumentPagePixmap* pagePixmap = dynamic_cast(page); + if (pagePixmap) + pagePixmap->resize(ps.sizeInPixel(resolution)); + } + + //kdDebug() << "render page " << pageNumber + 1 << " at size (" << pageWidth << ", " << pageHeight << ")" << endl; + + int pageHeight = page->height(); + int pageWidth = page->width(); + + GRect pageRect(0, 0, pageWidth, pageHeight); + + + GP djvuPixmap; + if (Prefs::renderMode() == Prefs::EnumRenderMode::Color) + djvuPixmap = djvuPage->get_pixmap(pageRect, pageRect); + else if (Prefs::renderMode() == Prefs::EnumRenderMode::Foreground) + djvuPixmap = djvuPage->get_fg_pixmap(pageRect, pageRect); + else if (Prefs::renderMode() == Prefs::EnumRenderMode::Background) + djvuPixmap = djvuPage->get_bg_pixmap(pageRect, pageRect); + + QPainter* foreGroundPaint = page->getPainter(); + if (foreGroundPaint != 0) + { + if(djvuPixmap && Prefs::renderMode() != Prefs::EnumRenderMode::BlackAndWhite) + { + PPMstream->seek(0); + djvuPixmap->save_ppm(*PPMstream); + long pixmapsize = PPMstream->tell(); + PPMstream->seek(0); + uchar* buf = new uchar[pixmapsize]; + long bytesRead = PPMstream->readall(buf, pixmapsize); + + bool ok = pixmap.loadFromData(buf, bytesRead, "PPM"); + if (!ok) + { + kdError() << "loading failed" << endl; + //draw an empty page + foreGroundPaint->fillRect(0, 0, pageWidth, pageHeight, Qt::white); + } + foreGroundPaint->drawPixmap(0, 0, pixmap); + delete[] buf; + +/* for (int i = 0; i < pageHeight; i++) + { + GPixel* pixmapRow = (*djvuPixmap)[i]; + + for (int j = 0; j < pageWidth; j++) + { + GPixel pixel = pixmapRow[j]; + foreGroundPaint->setPen(QColor(pixel.r, pixel.g, pixel.b)); + foreGroundPaint->drawPoint(j, pageHeight - i - 1); + } + }*/ + } + else + { + GP djvuBitmap = djvuPage->get_bitmap(pageRect, pageRect); + if(djvuBitmap) + { + PPMstream->seek(0); + if(djvuBitmap->get_grays() == 2) + djvuBitmap->save_pbm(*PPMstream); + else + djvuBitmap->save_pgm(*PPMstream); + + long pixmapsize = PPMstream->tell(); + PPMstream->seek(0); + uchar* buf = new uchar[pixmapsize]; + long bytesRead = PPMstream->readall(buf, pixmapsize); + + bool ok = pixmap.loadFromData(buf, bytesRead, "PPM"); + if (!ok) + { + kdError() << "loading failed" << endl; + //draw an empty page + foreGroundPaint->fillRect(0, 0, pageWidth, pageHeight, Qt::white); + } + foreGroundPaint->drawPixmap(0, 0, pixmap); + delete[] buf; +/* + for (int i = 0; i < pageHeight; i++) + { + unsigned char* bitmapRow = (*djvuBitmap)[i]; + for (int j = 0; j < pageWidth; j++) + { + unsigned char pixel = 255-bitmapRow[j]; + foreGroundPaint->setPen(QColor(pixel, pixel, pixel)); + foreGroundPaint->drawPoint(j, pageHeight - i - 1); + } + }*/ + } + else + { + //draw an empty page + foreGroundPaint->fillRect(0, 0, pageWidth, pageHeight, Qt::white); + } + } + + //kdDebug() << "rendering page " << pageNumber + 1 << " at size (" << pageWidth << ", " << pageHeight << ") finished." << endl; + page->returnPainter(foreGroundPaint); + } + + GP pageText = getText(pageNumber); + + if (pageText) + { + QSize djvuPageSize(djvuPage->get_width(), djvuPage->get_real_height()); + fillInText(page, pageText, pageText->page_zone, djvuPageSize); + //kdDebug() << "Text of page " << pageNumber << endl; + //kdDebug() << (const char*)pageText->textUTF8 << endl; + } + + getAnnotations(page, djvuPage); + + page->isEmpty = false; +} + + +bool DjVuRenderer::setFile(const QString &fname, const KURL &) +{ +#ifdef KF_DEBUG + kdDebug() << "DjVuRenderer::setFile(" << fname << ") called" << endl; +#endif + + // Wait for all access to this documentRenderer to finish + QMutexLocker locker( &mutex ); + + // If fname is the empty string, then this means: "close". + if (fname.isEmpty()) { + kdDebug() << "DjVuRenderer::setFile( ... ) called with empty filename. Closing the file." << endl; + return true; + } + + // Paranoid saftey checks: make sure the file actually exists, and + // that it is a file, not a directory. Otherwise, show an error + // message and exit.. + QFileInfo fi(fname); + QString filename = fi.absFilePath(); + if (!fi.exists() || fi.isDir()) { + KMessageBox::error( parentWidget, + i18n("File error. The specified file '%1' does not exist.").arg(filename), + i18n("File Error")); + // the return value 'false' indicates that this operation was not successful. + return false; + } + + // Clear previously loaded document + clear(); + + // Now we assume that the file is fine and load the file. + G_TRY { + document = DjVuDocEditor::create_wait(GURL::Filename::UTF8(GStringFromQString(filename))); + } + G_CATCH(ex) { + ; + } + G_ENDCATCH; + + // If the above assumption was false. + if (!document) + { + KMessageBox::error( parentWidget, + i18n("File error. The specified file '%1' could not be loaded.").arg(filename), + i18n("File Error")); + + clear(); + kdDebug(1223) << "Loading of document failed." << endl; + return false; + } + + bool r = initializeDocument(); + + // the return value 'true' indicates that this operation was successful. + return r; +} + +void DjVuRenderer::getAnnotations(RenderedDocumentPage* page, GP djvuPage) +{ + GP annotations = djvuPage->get_anno(); + if (!(annotations && annotations->size())) + return; + + GP ant = DjVuANT::create(); + + GP iff = IFFByteStream::create(annotations); + + GUTF8String chkid; + + while (iff->get_chunk(chkid)) + { + if (chkid == "ANTa") + { + ant->merge(*iff->get_bytestream()); + } + else if (chkid == "ANTz") + { + GP bsiff = BSByteStream::create(iff->get_bytestream()); + ant->merge(*bsiff); + } + iff->close_chunk(); + } + + if (!ant->is_empty()) + { + // Scaling factors for the coordinate conversion. + // TODO: Refractor this into a function shared with fillInText. + int pageWidth = page->width(); + int pageHeight = page->height(); + + double scaleX = pageWidth / (double)djvuPage->get_width(); + double scaleY = pageHeight / (double)djvuPage->get_height(); + + GPList map = ant->map_areas; + + for (GPosition pos = map; pos; ++pos) + { + // Currently we only support rectangular links + if (!map[pos]->get_shape_type() == GMapArea::RECT) + continue; + + GRect rect = map[pos]->get_bound_rect(); + + QRect hyperlinkRect((int)(rect.xmin*scaleX+0.5), (int)((djvuPage->get_height()-rect.ymax)*scaleY+0.5), + (int)(rect.width()*scaleX +0.5), (int)(rect.height()*scaleY+0.5)); + + QString url((const char*)map[pos]->url); + QString target((const char*)map[pos]->target); + QString comment((const char*)map[pos]->comment); + + // Create an anchor for this link. + if (!anchorList.contains(url)) + { + // For now we only accept links to pages in the same document. + if(url[0] == '#' && target == "_self") + { + bool conversionOk; + PageNumber targetPage = url.remove('#').toInt(&conversionOk); + if (conversionOk) + anchorList[url] = Anchor(targetPage, Length()); + } + } + + Hyperlink hyperlink(hyperlinkRect.bottom(), hyperlinkRect, url); + page->hyperLinkList.push_back(hyperlink); + } + } +} + + +bool DjVuRenderer::initializeDocument() +{ + if (document == 0) + return false; + + if (!document->wait_for_complete_init()) { + kdDebug() << "Document Initialization failed." << endl; + return false; + } + + // Set the number of pages page sizes + numPages = document->get_pages_num(); + + pageSizes.resize(numPages); + Length w,h; + + // Set the page sizes in the pageSizes array. Give feedback for + // very long documents + if (numPages > 100) + emit setStatusBarText(i18n("Loading file. Computing page sizes...")); + for(Q_UINT16 i=0; iprocessEvents(); + + GP djvuFile = document->get_djvu_file(i); + int resolution; + int pageWidth; + int pageHeight; + bool ok = getPageInfo(djvuFile, pageWidth, pageHeight, resolution); + if (!ok) + kdError() << "Decoding info of page " << i << " failed." << endl; + else { + w.setLength_in_inch(pageWidth / (double)resolution); + h.setLength_in_inch(pageHeight / (double)resolution); + pageSizes[i].setPageSize(w, h); + } + } + emit setStatusBarText(QString::null); + + // We will also generate a list of hyperlink-anchors in the document. + // So declare the existing lists empty. + anchorList.clear(); + return true; +} + + +GP DjVuRenderer::getText(PageNumber pageNumber) +{ + GUTF8String chkid; + + const GP file = document->get_djvu_file(pageNumber); + const GP bs(file->get_text()); + if (bs) + { + long int i=0; + const GP iff(IFFByteStream::create(bs)); + while (iff->get_chunk(chkid)) + { + i++; + if (chkid == GUTF8String("TXTa")) + { + GP txt = DjVuTXT::create(); + txt->decode(iff->get_bytestream()); + return txt; + } + else if (chkid == GUTF8String("TXTz")) + { + GP txt = DjVuTXT::create(); + GP bsiff=BSByteStream::create(iff->get_bytestream()); + txt->decode(bsiff); + return txt; + } + iff->close_chunk(); + } + } + return 0; +} + +void DjVuRenderer::fillInText(RenderedDocumentPage* page, const GP& text, DjVuTXT::Zone& zone, QSize& djvuPageSize) +{ + if (zone.children.isempty()) + { + int pageWidth = page->width(); + int pageHeight = page->height(); + + double scaleX = pageWidth / (double)djvuPageSize.width(); + double scaleY = pageHeight / (double)djvuPageSize.height(); + + QString zoneString = QStringFromGString(text->textUTF8.substr(zone.text_start, zone.text_length)); + + //kdDebug() << "zone text: " << zoneString << endl; + + QRect textRect((int)(zone.rect.xmin*scaleX+0.5), (int)((djvuPageSize.height()-zone.rect.ymax)*scaleY+0.5), + (int)(zone.rect.width()*scaleX+0.5), (int)(zone.rect.height()*scaleY+0.5)); + //kdDebug() << "zone rect: " << textRect.x() << ", " << textRect.y() << ", " << textRect.width() << ", " << textRect.height() << endl; + TextBox textBox(textRect, zoneString); + page->textBoxList.push_back(textBox); + } + else + { + for (GPosition pos=zone.children; pos; ++pos) + { + fillInText(page, text, zone.children[pos], djvuPageSize); + } + } +} + +bool DjVuRenderer::getPageInfo(GP file, int& width, int& height, int& dpi) +{ + if (!file || !file->is_all_data_present()) + return false; + + const GP pbs(file->get_djvu_bytestream(false, false)); + const GP iff(IFFByteStream::create(pbs)); + + GUTF8String chkid; + if (iff->get_chunk(chkid)) + { + if (chkid == "FORM:DJVU") + { + while (iff->get_chunk(chkid) && chkid!="INFO") + iff->close_chunk(); + if (chkid == "INFO") + { + GP gbs = iff->get_bytestream(); + GP info=DjVuInfo::create(); + info->decode(*gbs); + int rot = ((360-GRect::findangle(info->orientation))/90)%4; + + width = (rot&1) ? info->height : info->width; + height = (rot&1) ? info->width : info->height; + dpi = info->dpi; + return true; + } + } + else if (chkid == "FORM:BM44" || chkid == "FORM:PM44") + { + while (iff->get_chunk(chkid) && chkid!="BM44" && chkid!="PM44") + iff->close_chunk(); + if (chkid=="BM44" || chkid=="PM44") + { + GP gbs = iff->get_bytestream(); + if (gbs->read8() == 0) + { + gbs->read8(); + gbs->read8(); + unsigned char xhi = gbs->read8(); + unsigned char xlo = gbs->read8(); + unsigned char yhi = gbs->read8(); + unsigned char ylo = gbs->read8(); + + width = (xhi<<8)+xlo; + height = (yhi<<8)+ylo; + dpi = 100; + return true; + } + } + } + } + return false; +} + +void DjVuRenderer::getText(RenderedDocumentPage* page) +{ + QMutexLocker locker( &mutex ); + + int pageNumber = page->getPageNumber() - 1; + GP pageText = getText(pageNumber); + + if (pageText) + { + GP djvuFile = document->get_djvu_file(pageNumber); + int resolution; + int pageWidth; + int pageHeight; + bool ok = getPageInfo(djvuFile, pageWidth, pageHeight, resolution); + + if (ok) + { + QSize djvuPageSize(pageWidth, pageHeight); + fillInText(page, pageText, pageText->page_zone, djvuPageSize); + } + } +} + + +bool DjVuRenderer::convertToPSFile( DjVuToPS &converter, QString filename, QValueList &pageList ) +{ + if (document == 0) { + kdError(1223) << "DjVuRenderer::convertToPSFile(..) called when document was 0" << endl; + return false; + } + + QMutexLocker locker( &mutex ); + + // Set up progress dialog + KProgressDialog *pdialog = new KProgressDialog(parentWidget, "Printing-ProgressDialog", i18n("Printing..."), i18n("Preparing pages for printing..."), true); + pdialog->setButtonText(i18n("Abort")); + pdialog->showCancelButton(true); + pdialog->progressBar()->setTotalSteps(pageList.size()); + pdialog->progressBar()->setFormat(QString::null); + + // Open output file + GURL outname = GURL::Filename::UTF8(GStringFromQString(filename)); + GP obs = ByteStream::create(outname, "w"); + + QString pagename; + QValueList::ConstIterator it = pageList.begin(); + while (true) { + pagename += QString::number(*it); + ++it; + if (it == pageList.end()) + break; + pagename += ","; + } + GUTF8String pages = GStringFromQString(pagename); + + converter.set_info_cb(printerInfoCallBack, (void*)pdialog); + bool iscancelled = false; + G_TRY { + converter.print(*obs, (DjVuDocument *)document, pages ); + } + G_CATCH(ex) { + iscancelled = true; + } + G_ENDCATCH; + + delete pdialog; + + // This is to keep the GUI updated. + kapp->processEvents(); + + obs->flush(); + return !iscancelled; +} + + +void DjVuRenderer::deletePages(Q_UINT16 from, Q_UINT16 to) +{ + // Paranoia security checks + if (document == 0) { + kdError(1223) << "DjVuRenderer::deletePages(...) called when no document was loaded" << endl; + return; + } + if ((from > to) || (from == 0) || (from > totalPages()) || (to > totalPages())) { + kdError(1223) << "DjVuRenderer::deletePages(...) called with invalid arguments" << endl; + return; + } + + QMutexLocker locker( &mutex ); + + KProgressDialog *pdialog = 0; + if (to-from > 9) { + pdialog = new KProgressDialog(parentWidget, "Printing-ProgressDialog", i18n("Deleting pages..."), i18n("Please wait while pages are removed..."), true); + pdialog->showCancelButton(false); + pdialog->progressBar()->setTotalSteps(to-from+1); + pdialog->progressBar()->setFormat(QString::null); + pdialog->show(); + kapp->processEvents(); + } + + // set the document pointer temporarily to 0, so that no-one tries + // to render a page while we are deleting pages + GP document_new = document; + document = 0; + + // Delete pages + if (pdialog == 0) { + GList pageList; + for(Q_UINT16 i=from; i<= to; i++) + pageList.append(i-1); + document_new->remove_pages(pageList); + } else { + for(Q_UINT16 i=from; i<=to; i++) { + document_new->remove_page(from-1); + pdialog->progressBar()->setProgress(i-from); + pdialog->progressBar()->setFormat(i18n("deleting page %1").arg(i)); + kapp->processEvents(); + } + delete pdialog; + } + _isModified = true; + document = document_new; + + initializeDocument(); +} + + +bool DjVuRenderer::save(const QString &filename) +{ + if (document == 0) { + kdError() << "DjVuRenderer::save(..) called when document==0" << endl; + return false; + } + + QMutexLocker locker( &mutex ); + + G_TRY { + document->save_as(GURL::Filename::UTF8(GStringFromQString(filename)), true); + } + G_CATCH(ex) { + return false; + } + G_ENDCATCH; + + document->save_as(GURL::Filename::UTF8(filename.ascii()), true); + + if (QFile::exists(filename) == false) + return false; + + _isModified = false; + return true; +} + + +void DjVuRenderer::printerInfoCallBack(int page_num, int page_count, int, DjVuToPS::Stage, void *pd) +{ + if (pd == 0) + return; + + // Update the progress dialog. + KProgressDialog *pdialog = (KProgressDialog *)pd; + + pdialog->progressBar()->setProgress(page_count); + pdialog->progressBar()->setFormat(i18n("processing page %1").arg(page_num+1)); + pdialog->show(); + + if (pdialog->wasCancelled()) + G_THROW("STOP"); + + // This is to keep the GUI updated. + kapp->processEvents(); +} + + +#include "djvurenderer.moc" diff --git a/kviewshell/plugins/djvu/djvurenderer.h b/kviewshell/plugins/djvu/djvurenderer.h new file mode 100644 index 00000000..40418c23 --- /dev/null +++ b/kviewshell/plugins/djvu/djvurenderer.h @@ -0,0 +1,146 @@ +/*************************************************************************** + * Copyright (C) 2005 by Stefan Kebekus * + * kebekus@kde.org * + * * + * Copyright (C) 2005 by Wilfried Huss * + * Wilfried.Huss@gmx.at * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef _DJVURENDERER_H_ +#define _DJVURENDERER_H_ + +#include +#include + +#include "DjVuImage.h" +#include "DjVuDocEditor.h" +#include "DjVuText.h" +#include "DjVuToPS.h" +#include "ByteStream.h" + +#include "documentRenderer.h" + +class RenderedDocumentPage; + +/*! \brief Well-documented minimal implementation of a documentRenderer + +This class provides a well-documented reference implementation of a +documentRenderer, suitable as a starting point for a real-world +implementation. This class is responsible for document loading and +rendering. Apart from the constructor and the descructor, it +implements only the necessary methods setFile() and drawPage(). The +method setFile() ignores the file content and simulates a document +with 10 empty pages of A4 size and a few anchors and bookmarks. +*/ + +class DjVuRenderer : public DocumentRenderer +{ + Q_OBJECT + +public: + /** Default constructor + + This constructor simply prints a message and calls the default constructor. + */ + DjVuRenderer(QWidget* parent); + + /** Destructor + + The destructor simply prints a message. It uses the mutex to + ensure that this class is not destructed while another thread + is currently using it. + */ + ~DjVuRenderer(); + + /** Opening a file + + This implementation does the necessary consistency checks and + complains, e.g. if the file does not exist, but otherwise completely + disregards the file content. It simulates a document of 10 empty pages of + A4 size, with a few sample bookmarks and anchors "p1", "p2", "p3" + for page 1, 2 and 3, respectively. + + @param fname the name of the file that should be opened. + */ + virtual bool setFile(const QString& fname, const KURL &); + + /** Rendering a page + @param res resolution at which drawing should take place + @param page pointer to a page structur on which we should draw + */ + virtual void drawPage(double res, RenderedDocumentPage* page); + + /** Extract the hidden text layer + + This function decodes the hidden text layer without actually decoding the full page. + It is therefore much faster then drawPage if you only need the text information. + */ + virtual void getText(RenderedDocumentPage* page); + + virtual bool supportsTextSearch() const { return true; }; + + /** DJVU to PostScript conversion + + This method uses the converter to convert the document to a PostScript. + + @param converter a DjVuToPS converter, whose options should already + be set to + + @param filename name of the PostScript file to generate + + @param pageList list of pages that are to be converted, with the + usual convention that "1" means "first page" + + @returns 'true' if the conversion was successful, 'false' if it + wasn't. The conversion can fail, for example, when the user aborts + the operation. + */ + bool convertToPSFile( DjVuToPS &converter, QString filename, QValueList &pageList ); + + /** Deletes pages from the document */ + void deletePages(Q_UINT16 from, Q_UINT16 to); + + /** Saves the file */ + bool save(const QString &filename); + +private: + /* This method is called after a document is loaded with + create_wait() or has been modified (e.g. inserting/deleting + pages). It sets "numPages", fills the "pageSizes" array, and + clear the anchorList. */ + bool initializeDocument(); + + void getAnnotations(RenderedDocumentPage* page, GP djvuPage); + + bool getPageInfo(GP file, int& width, int& height, int& dpi); + + GP getText(PageNumber pageNumber); + + void fillInText(RenderedDocumentPage* page, const GP& text, DjVuTXT::Zone& zone, QSize& djvuPageSize); + + GP document; + + /** Method used internally to report the progress of the DjVu to + PostScript conversion */ + static void printerInfoCallBack(int page_num, int page_count, int tot_pages, DjVuToPS::Stage, void *); + + QPixmap pixmap; + GP PPMstream; +}; + +#endif diff --git a/kviewshell/plugins/djvu/kprintDialogPage_DJVUconversionoptions.cpp b/kviewshell/plugins/djvu/kprintDialogPage_DJVUconversionoptions.cpp new file mode 100644 index 00000000..aea7d6b7 --- /dev/null +++ b/kviewshell/plugins/djvu/kprintDialogPage_DJVUconversionoptions.cpp @@ -0,0 +1,118 @@ +/*************************************************************************** + * Copyright (C) 2005 by Stefan Kebekus * + * kebekus@kde.org * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include "kprintDialogPage_DJVUconversionoptions.h" +#include "kprintDialogPage_DJVUconversionoptions_basewidget.h" + +KPrintDialogPage_DJVUConversionOptions::KPrintDialogPage_DJVUConversionOptions( QWidget *parent, const char *name ) + : KPrintDialogPage( parent, name ) +{ + setTitle( i18n("DJVU to PS Conversion") ); + + kprintDialogPage_pageoptions_baseLayout = new QVBoxLayout( this, 11, 6, "kprintDialogPage_pageoptions_baseLayout"); + if (kprintDialogPage_pageoptions_baseLayout == 0) { + kdError(1223) << "KPrintDialogPage_DJVUPageOptions::KPrintDialogPage_DJVUPageOptions() cannot create layout" << endl; + return; + } + + wdg = new kprintDialogPage_DJVUconversionoptions_basewidget(this, "basewdg" ); + if (wdg != 0) { + kprintDialogPage_pageoptions_baseLayout->addWidget( wdg ); + } +} + + + +void KPrintDialogPage_DJVUConversionOptions::getOptions( QMap& opts, bool ) +{ + if (wdg == 0) + return; + + opts["kde-kdjvu-pslevel"] = QString::number(wdg->psLevel->currentItem() + 1); + + kdDebug() << "getOptions: renderMode = " << wdg->renderMode->currentItem() << endl; + switch (wdg->renderMode->currentItem()) + { + case 1: + opts["kde-kdjvu-rendermode"] = "black-and-white"; + break; + case 2: + opts["kde-kdjvu-rendermode"] = "foreground"; + break; + case 3: + opts["kde-kdjvu-rendermode"] = "background"; + break; + default: // 0 + opts["kde-kdjvu-rendermode"] = "color"; + } +} + + +void KPrintDialogPage_DJVUConversionOptions::setOptions( const QMap& opts ) +{ + if (wdg == 0) + return; + + bool ok; + // Set PostScript Language Level, taking 2 as the default + int psLevel = opts["kde-kdjvu-pslevel"].toInt(&ok); + + if (ok && psLevel >= 1 && psLevel <= 3) + { + wdg->psLevel->setCurrentItem(psLevel - 1); + } + else + { + wdg->psLevel->setCurrentItem(1); // PostScript Level 2 + } + + // Set render mode, taking "color" as default + QString op = opts["kde-kdjvu-rendermode"]; + if (op == "black-and-white") + { + wdg->renderMode->setCurrentItem(1); + } + else + { + if (op == "foreground") + wdg->renderMode->setCurrentItem(2); + else + { + if (op == "background") + wdg->renderMode->setCurrentItem(3); + else + wdg->renderMode->setCurrentItem(0); + } + } +} + + +bool KPrintDialogPage_DJVUConversionOptions::isValid( QString& ) +{ + return true; +} diff --git a/kviewshell/plugins/djvu/kprintDialogPage_DJVUconversionoptions.h b/kviewshell/plugins/djvu/kprintDialogPage_DJVUconversionoptions.h new file mode 100644 index 00000000..9e3faa90 --- /dev/null +++ b/kviewshell/plugins/djvu/kprintDialogPage_DJVUconversionoptions.h @@ -0,0 +1,50 @@ +/*************************************************************************** + * Copyright (C) 2005 by Stefan Kebekus * + * kebekus@kde.org * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#ifndef KPRINTDIALOGPAGE_DJVUCONVERSIONOPTIONS_H +#define KPRINTDIALOGPAGE_DJVUCONVERSIONOPTIONS_H + +#include + + +class kprintDialogPage_DJVUconversionoptions_basewidget; + + +// This is a fairly standard KPrintDialogPage that allows the user to +// chose page size & placement options: shrink oversized pages, and +// expand small pages + +class KPrintDialogPage_DJVUConversionOptions : public KPrintDialogPage +{ + public: + KPrintDialogPage_DJVUConversionOptions( QWidget *parent = 0, const char *name = 0 ); + + void getOptions( QMap& opts, bool incldef = false ); + void setOptions( const QMap& opts ); + bool isValid( QString& msg ); + + kprintDialogPage_DJVUconversionoptions_basewidget* wdg; + + private: + QVBoxLayout* kprintDialogPage_pageoptions_baseLayout; +}; + + +#endif // KPRINTDIALOGPAGE_PAGEOPTIONS_H diff --git a/kviewshell/plugins/djvu/kprintDialogPage_DJVUconversionoptions_basewidget.ui b/kviewshell/plugins/djvu/kprintDialogPage_DJVUconversionoptions_basewidget.ui new file mode 100644 index 00000000..fbc48750 --- /dev/null +++ b/kviewshell/plugins/djvu/kprintDialogPage_DJVUconversionoptions_basewidget.ui @@ -0,0 +1,145 @@ + +kprintDialogPage_DJVUconversionoptions_basewidget + + + kprintDialogPage_DJVUconversionoptions_basewidget + + + + 0 + 0 + 548 + 126 + + + + + unnamed + + + + textLabel1 + + + + 4 + 5 + 0 + 0 + + + + PostScript language level: + + + + + textLabel2 + + + + 4 + 5 + 0 + 0 + + + + Render mode: + + + + + + Level 1 (almost obsolete) + + + + + Level 2 (default) + + + + + Level 3 (might print faster) + + + + psLevel + + + + 3 + 0 + 0 + 0 + + + + <p>With this dialog you can choose the PostScript language level used by KViewShell. The choice of a language level can dramatically affect printing speed, but has no impact on the quality of the printout.</p> +<p><b>Level 1:</b> This is the most conservative option, because PostScript Level 1 files can be printed on all printers. The files produced are, however, extremely long, and printing can be very slow.</p> +<p><b>Level 2:</b> Level 2 PostScript files are much smaller and print much faster than Level 1 files. Level 2 files are supported by almost all printers.</p> +<p><b>Level 3:</b> Level 3 PostScript files are much smaller and print even faster than Level 2 files. However, Level 3 files are supported only by some modern printers. If Level 3 works for you, this is the best option.</p> + + + + + + Print Full Page (default) + + + + + Black & White + + + + + Foreground Only + + + + + Background Only + + + + renderMode + + + + 3 + 0 + 0 + 0 + + + + <p>Good DJVU files are separated into foreground and background images. The foreground mostly contains the text. With the render mode you can decide what part of your page will be printed.</p> +<p><b>Print Full Page:</b> The full page, including foreground and background will be printed, either in color or in grayscale.</p> +<p><b>Black & White:</b> Foreground and background are printed, but only in black-and-white. If this option is chosen, the files generated will print much faster, but quality will not be as good.</p> +<p><b>Foreground Only:</b> This option is useful if the background of the page is disturbing and affects the readability of the text.</p> +<p><b>Background Only:</b> Print only the background of the page.</p> + + + + + spacer1 + + + Vertical + + + Expanding + + + + 20 + 40 + + + + + + + diff --git a/kviewshell/plugins/djvu/kprintDialogPage_DJVUpageoptions.cpp b/kviewshell/plugins/djvu/kprintDialogPage_DJVUpageoptions.cpp new file mode 100644 index 00000000..cd77fa0e --- /dev/null +++ b/kviewshell/plugins/djvu/kprintDialogPage_DJVUpageoptions.cpp @@ -0,0 +1,119 @@ +/*************************************************************************** + * Copyright (C) 2005 by Stefan Kebekus * + * kebekus@kde.org * + * * + * This program is free software; you can redistribute it and/or modify * + * it under the terms of the GNU General Public License as published by * + * the Free Software Foundation; either version 2 of the License, or * + * (at your option) any later version. * + * * + * This program is distributed in the hope that it will be useful, * + * but WITHOUT ANY WARRANTY; without even the implied warranty of * + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * + * GNU General Public License for more details. * + * * + * You should have received a copy of the GNU General Public License * + * along with this program; if not, write to the * + * Free Software Foundation, Inc., * + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * + ***************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include + +#include "kprintDialogPage_DJVUpageoptions.h" + +KPrintDialogPage_DJVUPageOptions::KPrintDialogPage_DJVUPageOptions( QWidget *parent, const char *name ) + : KPrintDialogPage( parent, name ) +{ + setTitle( i18n("Page Size & Placement") ); + + kprintDialogPage_pageoptions_baseLayout = 0; + checkBox_rotate = 0; + checkBox_fitpage = 0; + + + kprintDialogPage_pageoptions_baseLayout = new QVBoxLayout( this, 11, 6, "kprintDialogPage_pageoptions_baseLayout"); + if (kprintDialogPage_pageoptions_baseLayout == 0) { + kdError(1223) << "KPrintDialogPage_DJVUPageOptions::KPrintDialogPage_DJVUPageOptions() cannot create layout" << endl; + return; + } + + checkBox_rotate = new QCheckBox( this, "checkBox_rotate" ); + if (checkBox_rotate != 0) { + checkBox_rotate->setText( i18n( "Automatically choose landscape or portrait orientation" ) ); + QToolTip::add( checkBox_rotate, i18n( "If this option is enabled, some pages might be rotated to better fit the paper size." ) ); + QWhatsThis::add( checkBox_rotate, i18n( "

If this option is enabled, landscape or portrait orientation are automatically chosen on a " + "page-by-page basis. This makes better use of the paper and gives more visually-" + "appealing printouts.

" + "

Note: This option overrides the Portrait/Landscape option chosen in the printer " + "properties. If this option is enabled, and if the pages in your document have different sizes, " + "then some pages might be rotated while others are not.

" ) ); + kprintDialogPage_pageoptions_baseLayout->addWidget( checkBox_rotate ); + } + + checkBox_fitpage = new QCheckBox( this, "checkBox_shrink" ); + if (checkBox_fitpage != 0) { + checkBox_fitpage->setText( i18n( "Scale pages to fit paper size" ) ); + QToolTip::add( checkBox_fitpage, i18n( "If this option is enabled, all pages will be scaled to optimally fit the printer's paper size." ) ); + QWhatsThis::add( checkBox_fitpage, i18n( "

If this option is enabled, all pages will be scaled to optimally fit the printer's " + "paper size.

" + "

Note: If this option is enabled, and if the pages in your document have different sizes, " + "then different pages might be scaled by different scaling factors.

" ) ); + kprintDialogPage_pageoptions_baseLayout->addWidget( checkBox_fitpage ); + } + + kprintDialogPage_pageoptions_baseLayout->addStretch(); + + resize( QSize(319, 166).expandedTo(minimumSizeHint()) ); + clearWState( WState_Polished ); +} + + + +void KPrintDialogPage_DJVUPageOptions::getOptions( QMap& opts, bool ) +{ + // Save options, taking default values into consideration. Warning: + // The default values are also coded into setOptions() and + // kmultipage::print(..). + + if (checkBox_rotate != 0) + if (checkBox_rotate->isChecked()) + opts[ "kde-kviewshell-rotatepage" ] = "true"; + else + opts[ "kde-kviewshell-rotatepage" ] = "false"; + + if (checkBox_fitpage != 0) + if (checkBox_fitpage->isChecked()) + opts[ "kde-kdjvu-fitpage" ] = "true"; + else + opts[ "kde-kdjvu-fitpage" ] = "false"; +} + + +void KPrintDialogPage_DJVUPageOptions::setOptions( const QMap& opts ) +{ + // Warning: All default values are also coded into getOptions() and + // kmultipage::print(..). + + // same for rotation + QString op = opts[ "kde-kviewshell-rotatepage" ]; + if (checkBox_rotate != 0) + checkBox_rotate->setChecked( op != "false" ); + + // Sets the fitpage option. By default, this option is not checked + op = opts[ "kde-kdjvu-fitpage" ]; + if (checkBox_fitpage != 0) + checkBox_fitpage->setChecked( op == "true" ); +} + + +bool KPrintDialogPage_DJVUPageOptions::isValid( QString& ) +{ + return true; +} diff --git a/kviewshell/plugins/djvu/kprintDialogPage_DJVUpageoptions.h b/kviewshell/plugins/djvu/kprintDialogPage_DJVUpageoptions.h new file mode 100644 index 00000000..0121c1a0 --- /dev/null +++ b/kviewshell/plugins/djvu/kprintDialogPage_DJVUpageoptions.h @@ -0,0 +1,42 @@ +// KPrintDialogPage_PageOptions.h +// +// Part of KVIEWSHELL - A framework for multipage text/gfx viewers +// +// (C) 2005 Stefan Kebekus +// Distributed under the GPL + +// Add header files alphabetically + +#ifndef KPRINTDIALOGPAGE_DJVUPAGEOPTIONS_H +#define KPRINTDIALOGPAGE_DJVUPAGEOPTIONS_H + + +#include + + +class QVBoxLayout; +class QCheckBox; + + +// This is a fairly standard KPrintDialogPage that allows the user to +// chose page size & placement options: shrink oversized pages, and +// expand small pages + +class KPrintDialogPage_DJVUPageOptions : public KPrintDialogPage +{ + public: + KPrintDialogPage_DJVUPageOptions( QWidget *parent = 0, const char *name = 0 ); + + void getOptions( QMap& opts, bool incldef = false ); + void setOptions( const QMap& opts ); + bool isValid( QString& msg ); + + QCheckBox* checkBox_rotate; + QCheckBox* checkBox_fitpage; + + private: + QVBoxLayout* kprintDialogPage_pageoptions_baseLayout; +}; + + +#endif // KPRINTDIALOGPAGE_PAGEOPTIONS_H diff --git a/kviewshell/plugins/djvu/libdjvu/Arrays.cpp b/kviewshell/plugins/djvu/libdjvu/Arrays.cpp new file mode 100644 index 00000000..5cb7b04c --- /dev/null +++ b/kviewshell/plugins/djvu/libdjvu/Arrays.cpp @@ -0,0 +1,305 @@ +//C- -*- C++ -*- +//C- ------------------------------------------------------------------- +//C- DjVuLibre-3.5 +//C- Copyright (c) 2002 Leon Bottou and Yann Le Cun. +//C- Copyright (c) 2001 AT&T +//C- +//C- This software is subject to, and may be distributed under, the +//C- GNU General Public License, Version 2. The license should have +//C- accompanied the software or you may obtain a copy of the license +//C- from the Free Software Foundation at http://www.fsf.org . +//C- +//C- This program is distributed in the hope that it will be useful, +//C- but WITHOUT ANY WARRANTY; without even the implied warranty of +//C- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//C- GNU General Public License for more details. +//C- +//C- DjVuLibre-3.5 is derived from the DjVu(r) Reference Library +//C- distributed by Lizardtech Software. On July 19th 2002, Lizardtech +//C- Software authorized us to replace the original DjVu(r) Reference +//C- Library notice by the following text (see doc/lizard2002.djvu): +//C- +//C- ------------------------------------------------------------------ +//C- | DjVu (r) Reference Library (v. 3.5) +//C- | Copyright (c) 1999-2001 LizardTech, Inc. All Rights Reserved. +//C- | The DjVu Reference Library is protected by U.S. Pat. No. +//C- | 6,058,214 and patents pending. +//C- | +//C- | This software is subject to, and may be distributed under, the +//C- | GNU General Public License, Version 2. The license should have +//C- | accompanied the software or you may obtain a copy of the license +//C- | from the Free Software Foundation at http://www.fsf.org . +//C- | +//C- | The computer code originally released by LizardTech under this +//C- | license and unmodified by other parties is deemed "the LIZARDTECH +//C- | ORIGINAL CODE." Subject to any third party intellectual property +//C- | claims, LizardTech grants recipient a worldwide, royalty-free, +//C- | non-exclusive license to make, use, sell, or otherwise dispose of +//C- | the LIZARDTECH ORIGINAL CODE or of programs derived from the +//C- | LIZARDTECH ORIGINAL CODE in compliance with the terms of the GNU +//C- | General Public License. This grant only confers the right to +//C- | infringe patent claims underlying the LIZARDTECH ORIGINAL CODE to +//C- | the extent such infringement is reasonably necessary to enable +//C- | recipient to make, have made, practice, sell, or otherwise dispose +//C- | of the LIZARDTECH ORIGINAL CODE (or portions thereof) and not to +//C- | any greater extent that may be necessary to utilize further +//C- | modifications or combinations. +//C- | +//C- | The LIZARDTECH ORIGINAL CODE is provided "AS IS" WITHOUT WARRANTY +//C- | OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +//C- | TO ANY WARRANTY OF NON-INFRINGEMENT, OR ANY IMPLIED WARRANTY OF +//C- | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. +//C- +------------------------------------------------------------------ +// +// $Id: Arrays.cpp,v 1.8 2003/11/07 22:08:20 leonb Exp $ +// $Name: release_3_5_15 $ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif +#if NEED_GNUG_PRAGMAS +# pragma implementation +#endif + +#include "Arrays.h" +#include "GException.h" + + +#ifdef HAVE_NAMESPACES +namespace DJVU { +# ifdef NOT_DEFINED // Just to fool emacs c++ mode +} +#endif +#endif + +ArrayRep::ArrayRep(int xelsize, + void (* xdestroy)(void *, int, int), + void (* xinit1)(void *, int, int), + void (* xinit2)(void *, int, int, const void *, int, int), + void (* xcopy)(void *, int, int, const void *, int, int), + void (* xinsert)(void *, int, int, const void *, int)) : + data(0), minlo(0), maxhi(-1), lobound(0), hibound(-1), + elsize(xelsize), destroy(xdestroy), init1(xinit1), + init2(xinit2), copy(xcopy), insert(xinsert) +{ +} + +ArrayRep::ArrayRep(int xelsize, + void (* xdestroy)(void *, int, int), + void (* xinit1)(void *, int, int), + void (* xinit2)(void *, int, int, const void *, int, int), + void (* xcopy)(void *, int, int, const void *, int, int), + void (* xinsert)(void *, int, int, const void *, int), + int hi) : data(0), minlo(0), maxhi(-1), + lobound(0), hibound(-1), elsize(xelsize), destroy(xdestroy), init1(xinit1), + init2(xinit2), copy(xcopy), insert(xinsert) +{ + resize(0, hi); +} + +ArrayRep::ArrayRep(int xelsize, + void (* xdestroy)(void *, int, int), + void (* xinit1)(void *, int, int), + void (* xinit2)(void *, int, int, const void *, int, int), + void (* xcopy)(void *, int, int, const void *, int, int), + void (* xinsert)(void *, int, int, const void *, int), + int lo, int hi) : data(0), minlo(0), maxhi(-1), + lobound(0), hibound(-1), elsize(xelsize), destroy(xdestroy), init1(xinit1), + init2(xinit2), copy(xcopy), insert(xinsert) +{ + resize(lo,hi); +} + +ArrayRep::ArrayRep(const ArrayRep & arr) : data(0), minlo(0), maxhi(-1), + lobound(0), hibound(-1), elsize(arr.elsize), destroy(arr.destroy), + init1(arr.init1), init2(arr.init2), copy(arr.copy), insert(arr.insert) +{ + resize(arr.lobound, arr.hibound); + arr.copy(data, lobound-minlo, hibound-minlo, + arr.data, arr.lobound-arr.minlo, arr.hibound-arr.minlo); +} + +ArrayRep::~ArrayRep() +{ + destroy(data, lobound-minlo, hibound-minlo); + operator delete(data); + data=0; +} + +ArrayRep & +ArrayRep::operator= (const ArrayRep & rep) +{ + if (&rep == this) return *this; + empty(); + resize(rep.lobound, rep.hibound); + copy(data, lobound-minlo, hibound-minlo, + rep.data, rep.lobound-rep.minlo, rep.hibound-rep.minlo); + return *this; +} + +void +ArrayRep::resize(int lo, int hi) +{ + int nsize = hi - lo + 1; + // Validation + if (nsize < 0) + G_THROW( ERR_MSG("arrays.resize") ); + // Destruction + if (nsize == 0) + { + destroy(data, lobound-minlo, hibound-minlo); + operator delete(data); + data = 0; + lobound = minlo = lo; + hibound = maxhi = hi; + return; + } + // Simple extension + if (lo >= minlo && hi <= maxhi) + { + init1(data, lo-minlo, lobound-1-minlo); + destroy(data, lobound-minlo, lo-1-minlo); + init1(data, hibound+1-minlo, hi-minlo); + destroy(data, hi+1-minlo, hibound-minlo); + lobound = lo; + hibound = hi; + return; + } + // General case + int nminlo = minlo; + int nmaxhi = maxhi; + if (nminlo > nmaxhi) + nminlo = nmaxhi = lo; + while (nminlo > lo) { + int incr = nmaxhi - nminlo; + nminlo -= (incr < 8 ? 8 : (incr > 32768 ? 32768 : incr)); + } + while (nmaxhi < hi) { + int incr = nmaxhi - nminlo; + nmaxhi += (incr < 8 ? 8 : (incr > 32768 ? 32768 : incr)); + } + // allocate + int bytesize=elsize*(nmaxhi-nminlo+1); + void * ndata; + GPBufferBase gndata(ndata,bytesize,1); + memset(ndata, 0, bytesize); + // initialize + init1(ndata, lo-nminlo, lobound-1-nminlo); + init2(ndata, lobound-nminlo, hibound-nminlo, + data, lobound-minlo, hibound-minlo); + init1(ndata, hibound+1-nminlo, hi-nminlo); + destroy(data, lobound-minlo, hibound-minlo); + + // free and replace + void *tmp=data; + data = ndata; + ndata=tmp; + + minlo = nminlo; + maxhi = nmaxhi; + lobound = lo; + hibound = hi; +} + +void +ArrayRep::shift(int disp) +{ + lobound += disp; + hibound += disp; + minlo += disp; + maxhi += disp; +} + +void +ArrayRep::del(int n, unsigned int howmany) +{ + if (howmany == 0) + return; + if ((int)(n + howmany) > hibound +1) + G_THROW( ERR_MSG("arrays.ill_arg") ); + copy(data, n-minlo, hibound-howmany-minlo, + data, n+howmany-minlo, hibound-minlo); + destroy(data, hibound+1-howmany-minlo, hibound-minlo); + hibound = hibound - howmany; +} + +void +ArrayRep::ins(int n, const void * what, unsigned int howmany) +{ + int nhi = hibound + howmany; + if (howmany == 0) return; + if (maxhi < nhi) + { + int nmaxhi = maxhi; + while (nmaxhi < nhi) + nmaxhi += (nmaxhi < 8 ? 8 : (nmaxhi > 32768 ? 32768 : nmaxhi)); + int bytesize = elsize*(nmaxhi-minlo+1); + void *ndata; + GPBufferBase gndata(ndata,bytesize,1); + memset(ndata, 0, bytesize); + copy(ndata, lobound-minlo, hibound-minlo, + data, lobound-minlo, hibound-minlo); + destroy(data, lobound-minlo, hibound-minlo); + void *tmp=data; + data=ndata; + tmp=data; + maxhi = nmaxhi; + } + + insert(data, hibound+1-minlo, n-minlo, what, howmany); + hibound=nhi; +} + + + +#ifdef HAVE_NAMESPACES +} +# ifndef NOT_USING_DJVU_NAMESPACE +using namespace DJVU; +# endif +#endif + + +// --------------------------------------- +// BEGIN HACK +// --------------------------------------- +// Included here to avoid dependency +// from ByteStream.o to Arrays.o + +#ifndef DO_NOT_MOVE_GET_DATA_TO_ARRAYS_CPP +#include "ByteStream.h" + +#ifdef HAVE_NAMESPACES +namespace DJVU { +# ifdef NOT_DEFINED // Just to fool emacs c++ mode +} +#endif +#endif +TArray +ByteStream::get_data(void) +{ + const int s=size(); + if(s > 0) + { + TArray data(0, s-1); + readat((char*)data, s, 0); + return data; + }else + { + TArray data(0, -1); + return data; + } +} + +#ifdef HAVE_NAMESPACES +} +# ifndef NOT_USING_DJVU_NAMESPACE +using namespace DJVU; +# endif +#endif +#endif + +// --------------------------------------- +// END HACK +// --------------------------------------- + diff --git a/kviewshell/plugins/djvu/libdjvu/Arrays.h b/kviewshell/plugins/djvu/libdjvu/Arrays.h new file mode 100644 index 00000000..b2676d5a --- /dev/null +++ b/kviewshell/plugins/djvu/libdjvu/Arrays.h @@ -0,0 +1,997 @@ +//C- -*- C++ -*- +//C- ------------------------------------------------------------------- +//C- DjVuLibre-3.5 +//C- Copyright (c) 2002 Leon Bottou and Yann Le Cun. +//C- Copyright (c) 2001 AT&T +//C- +//C- This software is subject to, and may be distributed under, the +//C- GNU General Public License, Version 2. The license should have +//C- accompanied the software or you may obtain a copy of the license +//C- from the Free Software Foundation at http://www.fsf.org . +//C- +//C- This program is distributed in the hope that it will be useful, +//C- but WITHOUT ANY WARRANTY; without even the implied warranty of +//C- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +//C- GNU General Public License for more details. +//C- +//C- DjVuLibre-3.5 is derived from the DjVu(r) Reference Library +//C- distributed by Lizardtech Software. On July 19th 2002, Lizardtech +//C- Software authorized us to replace the original DjVu(r) Reference +//C- Library notice by the following text (see doc/lizard2002.djvu): +//C- +//C- ------------------------------------------------------------------ +//C- | DjVu (r) Reference Library (v. 3.5) +//C- | Copyright (c) 1999-2001 LizardTech, Inc. All Rights Reserved. +//C- | The DjVu Reference Library is protected by U.S. Pat. No. +//C- | 6,058,214 and patents pending. +//C- | +//C- | This software is subject to, and may be distributed under, the +//C- | GNU General Public License, Version 2. The license should have +//C- | accompanied the software or you may obtain a copy of the license +//C- | from the Free Software Foundation at http://www.fsf.org . +//C- | +//C- | The computer code originally released by LizardTech under this +//C- | license and unmodified by other parties is deemed "the LIZARDTECH +//C- | ORIGINAL CODE." Subject to any third party intellectual property +//C- | claims, LizardTech grants recipient a worldwide, royalty-free, +//C- | non-exclusive license to make, use, sell, or otherwise dispose of +//C- | the LIZARDTECH ORIGINAL CODE or of programs derived from the +//C- | LIZARDTECH ORIGINAL CODE in compliance with the terms of the GNU +//C- | General Public License. This grant only confers the right to +//C- | infringe patent claims underlying the LIZARDTECH ORIGINAL CODE to +//C- | the extent such infringement is reasonably necessary to enable +//C- | recipient to make, have made, practice, sell, or otherwise dispose +//C- | of the LIZARDTECH ORIGINAL CODE (or portions thereof) and not to +//C- | any greater extent that may be necessary to utilize further +//C- | modifications or combinations. +//C- | +//C- | The LIZARDTECH ORIGINAL CODE is provided "AS IS" WITHOUT WARRANTY +//C- | OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED +//C- | TO ANY WARRANTY OF NON-INFRINGEMENT, OR ANY IMPLIED WARRANTY OF +//C- | MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. +//C- +------------------------------------------------------------------ +// +// $Id: Arrays.h,v 1.10 2004/05/13 15:16:34 leonb Exp $ +// $Name: release_3_5_15 $ + +#ifndef _ARRAYS_H_ +#define _ARRAYS_H_ +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif +#if NEED_GNUG_PRAGMAS +# pragma interface +#endif + +#include "GException.h" +#include "GSmartPointer.h" +#include + +#ifdef HAVE_NAMESPACES +namespace DJVU { +# ifdef NOT_DEFINED // Just to fool emacs c++ mode +} +#endif +#endif + + + +/** @name Arrays.h + + Files #"Arrays.h"# and #"Arrays.cpp"# implement three array template classes. + Class \Ref{TArray} implements an array of objects of trivial types + such as #char#, #int#, #float#, etc. It is faster than general implementation + for any type done in \Ref{DArray} because it does not cope with + element's constructors, destructors and copy operators. Although + implemented as a template, which makes it possible to incorrectly use + \Ref{TArray} with non-trivial classes, it should not be done. + + A lot of things is shared by these three arrays. That is why there are + more base classes: + \begin{itemize} + \item \Ref{ArrayBase} defines functions independent of the elements type + \item \Ref{ArrayBaseT} template class defining functions shared by + \Ref{DArray} and \Ref{TArray} + \end{itemize} + + The main difference between \Ref{GArray} (now obsolete) and these ones + is the copy-on-demand strategy, which allows you to copy array objects + without copying the real data. It's the same thing, which has been + implemented in \Ref{GString} long ago: as long as you don't try to modify + the underlying data, it may be shared between several copies of array + objects. As soon as you attempt to make any changes, a private copy + is created automatically and transparently for you - the procedure, that + we call "copy-on-demand". + + Also, please note that now there is no separate class, which does fast + sorting. Both \Ref{TArray} (dynamic array for trivial types) and + \Ref{DArray} (dynamic array for arbitrary types) can sort their elements. + + {\bf Historical comments} --- Leon chose to implement his own arrays because + the STL classes were not universally available and the compilers were + rarely able to deal with such a template galore. Later it became clear + that there is no really good reason why arrays should be derived from + containers. It was also suggested to create separate arrays implementation + for simple classes and do the copy-on-demand strategy, which would allow + to assign array objects without immediate copying of their elements. + + At this point \Ref{DArray} and \Ref{TArray} should only be used when + it is critical to have the copy-on-demand feature. The \Ref{GArray} + implementation is a lot more efficient. + + @memo Template array classes. + @author + Andrei Erofeev -- Copy-on-demand implementation. + @version + #$Id: Arrays.h,v 1.10 2004/05/13 15:16:34 leonb Exp $# */ +//@{ + +// Auxiliary classes: Will be used in place of GPBase and GPEnabled objects +class _ArrayRep +{ + friend class _ArrayBase; +public: + _ArrayRep(void) : count(0) {} + _ArrayRep(const _ArrayRep &) {} + virtual ~_ArrayRep(void) {} + + _ArrayRep & operator=(const _ArrayRep &) { return *this; } + + int get_count(void) const { return count; } +private: + int count; + + void ref(void) { count++; } + void unref(void) { if (--count==0) delete this; } +}; + +class _ArrayBase +{ +public: + _ArrayBase(void) : rep(0) {} + _ArrayBase(const _ArrayBase & ab) : rep(0) + { + if (ab.rep) ab.rep->ref(); + rep=ab.rep; + } + _ArrayBase(_ArrayRep * ar) : rep(0) + { + if (ar) ar->ref(); + rep=ar; + } + virtual ~_ArrayBase(void) + { + if (rep) { rep->unref(); rep=0; } + } + + _ArrayRep * get(void) const { return rep; } + _ArrayBase & assign(_ArrayRep * ar) + { + if (ar) ar->ref(); + if (rep) rep->unref(); + rep=ar; + return *this; + } + _ArrayBase & operator=(const _ArrayBase & ab) { return assign(ab.rep); } + bool operator==(const _ArrayBase & ab) { return rep==ab.rep; } +private: + _ArrayRep * rep; +}; + +// Internal "Array repository" holding the pointer to the actual data, +// data bounds, etc. It copes with data elements with the help of five +// static functions which pointers are supposed to be passed to the +// constructor. +class ArrayRep : public _ArrayRep +{ +public: + ArrayRep(int elsize, + void (* xdestroy)(void *, int, int), + void (* xinit1)(void *, int, int), + void (* xinit2)(void *, int, int, const void *, int, int), + void (* xcopy)(void *, int, int, const void *, int, int), + void (* xinsert)(void *, int, int, const void *, int)); + ArrayRep(int elsize, + void (* xdestroy)(void *, int, int), + void (* xinit1)(void *, int, int), + void (* xinit2)(void *, int, int, const void *, int, int), + void (* xcopy)(void *, int, int, const void *, int, int), + void (* xinsert)(void *, int, int, const void *, int), + int hibound); + ArrayRep(int elsize, + void (* xdestroy)(void *, int, int), + void (* xinit1)(void *, int, int), + void (* xinit2)(void *, int, int, const void *, int, int), + void (* xcopy)(void *, int, int, const void *, int, int), + void (* xinsert)(void *, int, int, const void *, int), + int lobound, int hibound); + ArrayRep(const ArrayRep & rep); + + virtual ~ArrayRep(); + + // Following is the standard interface to DArray. DArray will call these + // functions to access data. + int size() const; + int lbound() const; + int hbound() const; + + void empty(); + void touch(int n); + void resize(int lobound, int hibound); + void shift(int disp); + void del(int n, unsigned int howmany=1); + + // ins() is an exception. It does it job only partially. + // The derived class is supposed to finish insertion. + void ins(int n, const void * what, unsigned int howmany); + + ArrayRep & operator=(const ArrayRep & rep); + + // All data is public because DArray... classes will need access to it + void *data; + int minlo; + int maxhi; + int lobound; + int hibound; + int elsize; +private: + // These functions can't be virtual as they're called from + // constructors and destructors :(( + // destroy(): should destroy elements in data[] array from 'lo' to 'hi' + void (* destroy)(void * data, int lo, int hi); + // init1(): should initialize elements in data[] from 'lo' to 'hi' + // using default constructors + void (* init1)(void * data, int lo, int hi); + // init2(): should initialize elements in data[] from 'lo' to 'hi' + // using corresponding elements from src[] (copy constructor) + void (* init2)(void * data, int lo, int hi, + const void * src, int src_lo, int src_hi); + // copy(): should copy elements from src[] to dst[] (copy operator) + void (* copy)(void * dst, int dst_lo, int dst_hi, + const void * src, int src_lo, int src_hi); + // insert(): should insert '*what' at position 'where' 'howmany' times + // into array data[] having 'els' initialized elements + void (* insert)(void * data, int els, int where, const void * what, + int howmany); +}; + +inline int +ArrayRep::size() const +{ + return hibound - lobound + 1; +} + +inline int +ArrayRep::lbound() const +{ + return lobound; +} + +inline int +ArrayRep::hbound() const +{ + return hibound; +} + +inline void +ArrayRep::empty() +{ + resize(0, -1); +} + +inline void +ArrayRep::touch(int n) +{ + if (hibound < lobound) + { + resize(n,n); + } else + { + int nlo = lobound; + int nhi = hibound; + if (n < nlo) nlo = n; + if (n > nhi) nhi = n; + resize(nlo, nhi); + } +} + +/** Dynamic array base class. + This is an auxiliary base class for \Ref{DArray} and \Ref{TArray} + implementing some shared functions independent of the type of array + elements. It's not supposed to be constructed by hands. Use \Ref{DArray} + and \Ref{TArray} instead. + */ + +class ArrayBase : protected _ArrayBase +{ +protected: + void check(void); + void detach(void); + + ArrayBase(void) {}; +public: + /// Returns the number of elements in the array + int size() const; + /** Returns the lower bound of the valid subscript range. */ + int lbound() const; + /** Returns the upper bound of the valid subscript range. */ + int hbound() const; + /** Erases the array contents. All elements in the array are destroyed. + The valid subscript range is set to the empty range. */ + void empty(); + /** Extends the subscript range so that is contains #n#. + This function does nothing if #n# is already int the valid subscript range. + If the valid range was empty, both the lower bound and the upper bound + are set to #n#. Otherwise the valid subscript range is extended + to encompass #n#. This function is very handy when called before setting + an array element: + \begin{verbatim} + int lineno=1; + DArray a; + while (! end_of_file()) { + a.touch[lineno]; + a[lineno++] = read_a_line(); + } + \end{verbatim} + */ + void touch(int n); + /** Resets the valid subscript range to #0#---#hibound#. + This function may destroy some array elements and may construct + new array elements with the null constructor. Setting #hibound# to + #-1# resets the valid subscript range to the empty range. + @param hibound upper bound of the new subscript range. */ + void resize(int hibound); + /** Resets the valid subscript range to #lobound#---#hibound#. + This function may destroy some array elements and may construct + new array elements with the null constructor. Setting #lobound# to #0# and + #hibound# to #-1# resets the valid subscript range to the empty range. + @param lobound lower bound of the new subscript range. + @param hibound upper bound of the new subscript range. */ + void resize(int lobound, int hibound); + /** Shifts the valid subscript range. Argument #disp# is added to both + bounds of the valid subscript range. Array elements previously + located at subscript #x# will now be located at subscript #x+disp#. */ + void shift(int disp); + /** Deletes array elements. The array elements corresponding to + subscripts #n#...#n+howmany-1# are destroyed. All array elements + previously located at subscripts greater or equal to #n+howmany# + are moved to subscripts starting with #n#. The new subscript upper + bound is reduced in order to account for this shift. + @param n subscript of the first element to delete. + @param howmany number of elements to delete. */ + void del(int n, unsigned int howmany=1); + + virtual ~ArrayBase(void) {}; +}; + +inline void +ArrayBase::detach(void) +{ + ArrayRep * new_rep=new ArrayRep(*(ArrayRep *) get()); + assign(new_rep); +} + +inline void +ArrayBase::check(void) +{ + if (get()->get_count()>1) detach(); +} + +inline int +ArrayBase::size() const +{ + return ((const ArrayRep *) get())->size(); +} + +inline int +ArrayBase::lbound() const +{ + return ((const ArrayRep *) get())->lobound; +} + +inline int +ArrayBase::hbound() const +{ + return ((const ArrayRep *) get())->hibound; +} + +inline void +ArrayBase::empty() +{ + check(); + ((ArrayRep *) get())->empty(); +} + +inline void +ArrayBase::resize(int lo, int hi) +{ + check(); + ((ArrayRep *) get())->resize(lo, hi); +} + +inline void +ArrayBase::resize(int hi) +{ + resize(0, hi); +} + +inline void +ArrayBase::touch(int n) +{ + check(); + ((ArrayRep *) get())->touch(n); +} + +inline void +ArrayBase::shift(int disp) +{ + check(); + ((ArrayRep *) get())->shift(disp); +} + +inline void +ArrayBase::del(int n, unsigned int howmany) +{ + check(); + + ((ArrayRep *) get())->del(n, howmany); +} + +/** Dynamic array template base class. + This is an auxiliary template base class for \Ref{DArray} and \Ref{TArray} + implementing some shared functions which {\em depend} on the type of + the array elements (this is contrary to \Ref{ArrayBase}). + It's not supposed to be constructed by hands. Use \Ref{DArray} and + \Ref{TArray} instead. + */ + +template +class ArrayBaseT : public ArrayBase +{ +public: + virtual ~ArrayBaseT(void) {}; + + /** Returns a reference to the array element for subscript #n#. This + reference can be used for both reading (as "#a[n]#") and writing (as + "#a[n]=v#") an array element. This operation will not extend the valid + subscript range: an exception \Ref{GException} is thrown if argument #n# + is not in the valid subscript range. */ + TYPE& operator[](int n); + /** Returns a constant reference to the array element for subscript #n#. + This reference can only be used for reading (as "#a[n]#") an array + element. This operation will not extend the valid subscript range: an + exception \Ref{GException} is thrown if argument #n# is not in the valid + subscript range. This variant of #operator[]# is necessary when dealing + with a #const DArray#. */ + const TYPE& operator[](int n) const; + + /** Returns a pointer for reading or writing the array elements. This + pointer can be used to access the array elements with the same + subscripts and the usual bracket syntax. This pointer remains valid as + long as the valid subscript range is unchanged. If you change the + subscript range, you must stop using the pointers returned by prior + invocation of this conversion operator. */ + operator TYPE* (); + /** Returns a pointer for reading (but not modifying) the array elements. + This pointer can be used to access the array elements with the same + subscripts and the usual bracket syntax. This pointer remains valid as + long as the valid subscript range is unchanged. If you change the + subscript range, you must stop using the pointers returned by prior + invocation of this conversion operator. */ + operator const TYPE* () const; + +#ifndef __MWERKS__ //MCW can't compile + operator const TYPE* (); +#endif + /** Insert new elements into an array. This function inserts + #howmany# elements at position #n# into the array. The initial value #val# + is copied into the new elements. All array elements previously located at subscripts + #n# and higher are moved to subscripts #n+howmany# and higher. The upper bound of the + valid subscript range is increased in order to account for this shift. + @param n subscript of the first inserted element. + @param val initial value of the new elements. + @param howmany number of elements to insert. */ + void ins(int n, const TYPE &val, unsigned int howmany=1); + + /** Sort array elements. Sort all array elements in ascending order. Array + elements are compared using the less-or-equal comparison operator for + type #TYPE#. */ + void sort(); + /** Sort array elements in subscript range #lo# to #hi#. Sort all array + elements whose subscripts are in range #lo#..#hi# in ascending order. + The other elements of the array are left untouched. An exception is + thrown if arguments #lo# and #hi# are not in the valid subscript range. + Array elements are compared using the less-or-equal comparison operator + for type #TYPE#. + @param lo low bound for the subscripts of the elements to sort. + @param hi high bound for the subscripts of the elements to sort. */ + void sort(int lo, int hi); +protected: + ArrayBaseT(void) {}; +private: + // Callbacks called from ArrayRep + static void destroy(void * data, int lo, int hi); + static void init1(void * data, int lo, int hi); + static void init2(void * data, int lo, int hi, + const void * src, int src_lo, int src_hi); + static void copy(void * dst, int dst_lo, int dst_hi, + const void * src, int src_lo, int src_hi); + static void insert(void * data, int els, int where, + const void * what, int howmany); +}; + +template inline +ArrayBaseT::operator TYPE* () +{ + check(); + + ArrayRep * rep=(ArrayRep *) get(); + return &((TYPE *) rep->data)[-rep->minlo]; +} + +#ifndef __MWERKS__ //MCW can't compile +template inline +ArrayBaseT::operator const TYPE* () +{ + const ArrayRep * rep=(const ArrayRep *) get(); + return &((const TYPE *) rep->data)[-rep->minlo]; +} +#endif + +template inline +ArrayBaseT::operator const TYPE* () const +{ + const ArrayRep * rep=(const ArrayRep *) get(); + return &((const TYPE *) rep->data)[-rep->minlo]; +} + +template inline TYPE& +ArrayBaseT::operator[](int n) +{ + check(); + + ArrayRep * rep=(ArrayRep *) get(); + if (nlobound || n>rep->hibound) + G_THROW( ERR_MSG("arrays.ill_sub") ); + return ((TYPE *) rep->data)[n - rep->minlo]; +} + +template inline const TYPE& +ArrayBaseT::operator[](int n) const +{ + const ArrayRep * rep=(const ArrayRep *) get(); + if (nlobound || n>rep->hibound) + G_THROW( ERR_MSG("arrays.ill_sub") ); + return ((const TYPE *) rep->data)[n - rep->minlo]; +} + +template inline void +ArrayBaseT::ins(int n, const TYPE &val, unsigned int howmany) +{ + check(); + + ((ArrayRep *) get())->ins(n, &val, howmany); +} + +template void +ArrayBaseT::sort() +{ + sort(lbound(), hbound()); +} + +template void +ArrayBaseT::sort(int lo, int hi) +{ + if (hi <= lo) + return; + // Test for insertion sort (optimize!) + if (hi <= lo + 20) + { + for (int i=lo+1; i<=hi; i++) + { + int j = i; + TYPE tmp = (*this)[i]; + while ((--j>=lo) && !((*this)[j]<=tmp)) + (*this)[j+1] = (*this)[j]; + (*this)[j+1] = tmp; + } + return; + } + // -- determine suitable quick-sort pivot + TYPE tmp = (*this)[lo]; + TYPE pivot = (*this)[(lo+hi)/2]; + if (pivot <= tmp) + { tmp = pivot; pivot=(*this)[lo]; } + if ((*this)[hi] <= tmp) + { pivot = tmp; } + else if ((*this)[hi] <= pivot) + { pivot = (*this)[hi]; } + // -- partition set + int h = hi; + int l = lo; + while (l < h) + { + while (! (pivot <= (*this)[l])) l++; + while (! ((*this)[h] <= pivot)) h--; + if (l < h) + { + tmp = (*this)[l]; + (*this)[l] = (*this)[h]; + (*this)[h] = tmp; + l = l+1; + h = h-1; + } + } + // -- recursively restart + sort(lo, h); + sort(l, hi); +} + +/** Dynamic array for simple types. + Template class #TArray# implements an array of + elements of {\em simple} type #TYPE#. {\em Simple} means that the type + may be #char#, #int#, #float# etc. The limitation is imposed by the + way in which the #TArray# is working with its elements: it's not trying + to execute elements' constructors, destructors or copy operators. It's + just doing bitwise copy. Except for this it's pretty much the same as + \Ref{DArray}. + + Please note that most of the methods are implemented in the base classes + \Ref{ArrayBase} and \Ref{ArrayBaseT}. +*/ + +template +class TArray : public ArrayBaseT { +public: + /** Constructs an empty array. The valid subscript range is initially + empty. Member function #touch# and #resize# provide convenient ways + to enlarge the subscript range. */ + TArray(); + /** Constructs an array with subscripts in range 0 to #hibound#. + The subscript range can be subsequently modified with member functions + #touch# and #resize#. + @param hibound upper bound of the initial subscript range. */ + TArray(int hibound); + /** Constructs an array with subscripts in range #lobound# to #hibound#. + The subscript range can be subsequently modified with member functions + #touch# and #resize#. + @param lobound lower bound of the initial subscript range. + @param hibound upper bound of the initial subscript range. */ + TArray(int lobound, int hibound); + + virtual ~TArray() {}; +private: + // Callbacks called from ArrayRep + static void destroy(void * data, int lo, int hi); + static void init1(void * data, int lo, int hi); + static void init2(void * data, int lo, int hi, + const void * src, int src_lo, int src_hi); + static void insert(void * data, int els, int where, + const void * what, int howmany); +}; + +template void +TArray::destroy(void * data, int lo, int hi) +{ +} + +template void +TArray::init1(void * data, int lo, int hi) +{ +} + +template void +TArray::init2(void * data, int lo, int hi, + const void * src, int src_lo, int src_hi) +{ + if (data && src) + { + int els=hi-lo+1; + if (els>src_hi-src_lo+1) els=src_hi-src_lo+1; + if (els>0) + memmove((void *) &((TYPE *) data)[lo], + (void *) &((TYPE *) src)[src_lo], els*sizeof(TYPE)); + }; +} + +// inline removed +template void +TArray::insert(void * data, int els, int where, + const void * what, int howmany) +{ + memmove(((TYPE *) data)+where+howmany, + ((TYPE *) data)+where, sizeof(TYPE)*(els-where)); + for(int i=0;i +TArray::TArray () +{ + this->assign(new ArrayRep(sizeof(TYPE), destroy, init1, + init2, init2, insert)); +} + +template +TArray::TArray(int hi) +{ + this->assign(new ArrayRep(sizeof(TYPE), destroy, init1, + init2, init2, insert, hi)); +} + +template +TArray::TArray(int lo, int hi) +{ + this->assign(new ArrayRep(sizeof(TYPE), destroy, init1, + init2, init2, insert, lo, hi)); +} + +//inline removal ends + +/** Dynamic array for general types. + Template class #DArray# implements an array of + elements of type #TYPE#. Each element is identified by an integer + subscript. The valid subscripts range is defined by dynamically + adjustable lower- and upper-bounds. Besides accessing and setting + elements, member functions are provided to insert or delete elements at + specified positions. + + This template class must be able to access + \begin{itemize} + \item a null constructor #TYPE::TYPE()#, + \item a copy constructor #TYPE::TYPE(const TYPE &)#, + \item and a copy operator #TYPE & operator=(const TYPE &)#. + \end{itemize} + + The class offers "copy-on-demand" policy, which means that when you + copy the array object, array elements will stay intact as long as you + don't try to modify them. As soon as you make an attempt to change + array contents, the copying is done automatically and transparently + for you - the procedure that we call "copy-on-demand". This is the main + difference between this class and \Ref{GArray} (now obsolete) + + Please note that most of the methods are implemented in the base classes + \Ref{ArrayBase} and \Ref{ArrayBaseT}. +*/ + +template +class DArray : public ArrayBaseT { +public: + /** Constructs an empty array. The valid subscript range is initially + empty. Member function #touch# and #resize# provide convenient ways + to enlarge the subscript range. */ + DArray(void); + /** Constructs an array with subscripts in range 0 to #hibound#. + The subscript range can be subsequently modified with member functions + #touch# and #resize#. + @param hibound upper bound of the initial subscript range. */ + DArray(const int hibound); + /** Constructs an array with subscripts in range #lobound# to #hibound#. + The subscript range can be subsequently modified with member functions + #touch# and #resize#. + @param lobound lower bound of the initial subscript range. + @param hibound upper bound of the initial subscript range. */ + DArray(const int lobound, const int hibound); + + virtual ~DArray() {}; +private: + // Callbacks called from ArrayRep + static void destroy(void * data, int lo, int hi); + static void init1(void * data, int lo, int hi); + static void init2(void * data, int lo, int hi, + const void * src, int src_lo, int src_hi); + static void copy(void * dst, int dst_lo, int dst_hi, + const void * src, int src_lo, int src_hi); + static void insert(void * data, int els, int where, + const void * what, int howmany); +}; + +template void +DArray::destroy(void * data, int lo, int hi) +{ + if (data) + for(int i=lo;i<=hi;i++) + ((TYPE *) data)[i].TYPE::~TYPE(); +} + +template void +DArray::init1(void * data, int lo, int hi) +{ + if (data) + for(int i=lo;i<=hi;i++) + new ((void *) &((TYPE *) data)[i]) TYPE; +} + +template void +DArray::init2(void * data, int lo, int hi, + const void * src, int src_lo, int src_hi) +{ + if (data && src) + { + int i, j; + for(i=lo, j=src_lo;i<=hi && j<=src_hi;i++, j++) + new ((void *) &((TYPE *) data)[i]) TYPE(((TYPE *) src)[j]); + }; +} + +template void +DArray::copy(void * dst, int dst_lo, int dst_hi, + const void * src, int src_lo, int src_hi) +{ + if (dst && src) + { + int i, j; + for(i=dst_lo, j=src_lo;i<=dst_hi && j<=src_hi;i++, j++) + ((TYPE *) dst)[i]=((TYPE *) src)[j]; + }; +} + +template inline void +DArray::insert(void * data, int els, int where, + const void * what, int howmany) +{ + // Now do the insertion + TYPE * d=(TYPE *) data; + + int i; + for (i=els+howmany-1; i>=els; i--) + { + if (i-where >= (int)howmany) + new ((void*) &d[i]) TYPE (d[i-howmany]); + else + new ((void*) &d[i]) TYPE (*(TYPE *) what); + } + + for (i=els-1; i>=where; i--) + { + if (i-where >= (int)howmany) + d[i] = d[i-howmany]; + else + d[i] = *(TYPE *) what; + } +} + +template inline +DArray::DArray () +{ + this->assign(new ArrayRep(sizeof(TYPE), destroy, init1, + init2, copy, insert)); +} + +template inline +DArray::DArray(const int hi) +{ + this->assign(new ArrayRep(sizeof(TYPE), destroy, init1, + init2, copy, insert, hi)); +} + +template inline +DArray::DArray(const int lo, const int hi) +{ + this->assign(new ArrayRep(sizeof(TYPE), destroy, init1, + init2, copy, insert, lo, hi)); +} + +/** Dynamic array for \Ref{GPBase}d classes. + + There are many situations when it's necessary to create arrays of + \Ref{GP} pointers. For example, #DArray ># or #DArray >#. + This would result in compilation of two instances of \Ref{DArray} because + from the viewpoint of the compiler there are two different classes used + as array elements: #GP# and #GP