/*************************************************************************** * Copyright (C) 2004 by Enrico Ros * * Copyright (C) 2004-2006 by Albert Astals Cid * * * * With portions of code from kpdf/kpdf_pagewidget.cc by: * * Copyright (C) 2002 by Wilco Greven * * Copyright (C) 2003 by Christophe Devriese * * * * Copyright (C) 2003 by Laurent Montel * * Copyright (C) 2003 by Dirk Mueller * * Copyright (C) 2004 by James Ots * * * * 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. * ***************************************************************************/ // qt/kde includes #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // system includes #include #include // local includes #include "pageview.h" #include "pageviewutils.h" #include "pagepainter.h" #include "core/document.h" #include "core/page.h" #include "core/link.h" #include "core/generator.h" #include "conf/settings.h" #define ROUND(x) (int(x + 0.5)) // definition of searchID for this class #define PAGEVIEW_SEARCH_ID 2 // structure used internally by PageView for data storage class PageViewPrivate { public: // the document, pageviewItems and the 'visible cache' KPDFDocument * document; TQValueVector< PageViewItem * > items; TQValueList< PageViewItem * > visibleItems; // view layout (columns and continuous in Settings), zoom and mouse PageView::ZoomMode zoomMode; float zoomFactor; int rotation; PageView::MouseMode mouseMode; TQPoint mouseGrabPos; TQPoint mousePressPos; int mouseMidStartY; bool mouseOnRect; TQRect mouseSelectionRect; TQColor selectionRectColor; // type ahead find bool typeAheadActive; TQString typeAheadString; TQTimer * findTimeoutTimer; // viewport move bool viewportMoveActive; TQTime viewportMoveTime; TQPoint viewportMoveDest; TQTimer * viewportMoveTimer; // auto scroll int scrollIncrement; TQTimer * autoScrollTimer; // other stuff TQTimer * delayResizeTimer; bool dirtyLayout; bool blockViewport; // prevents changes to viewport bool blockPixmapsRequest; // prevent pixmap requests PageViewMessage * messageWindow; // in pageviewutils.h PageViewTip * tip; // drag scroll TQPoint dragScrollVector; TQTimer dragScrollTimer; // actions KToggleAction * aMouseNormal; KToggleAction * aMouseSelect; KToggleAction * aMouseEdit; KSelectAction * aZoom; KToggleAction * aZoomFitWidth; KToggleAction * aZoomFitPage; KToggleAction * aZoomFitText; KToggleAction * aViewTwoPages; KToggleAction * aViewContinuous; KAction * aPrevAction; }; class PageViewTip : public TQToolTip { public: PageViewTip( PageView * view ) : TQToolTip( view->viewport() ), m_view( view ) { } ~PageViewTip() { remove( m_view->viewport() ); } protected: void maybeTip( const TQPoint &p ); private: PageView * m_view; }; void PageViewTip::maybeTip( const TQPoint &_p ) { TQPoint p( _p.x() + m_view->contentsX(), _p.y() + m_view->contentsY() ); PageViewItem * pageItem = m_view->pickItemOnPoint( p.x(), p.y() ); if ( pageItem && m_view->d->mouseMode == PageView::MouseNormal ) { double nX = (double)(p.x() - pageItem->geometry().left()) / (double)pageItem->width(), nY = (double)(p.y() - pageItem->geometry().top()) / (double)pageItem->height(); // if over a ObjectRect (of type Link) change cursor to hand const ObjectRect * object = pageItem->page()->hasObject( ObjectRect::Link, nX, nY ); if ( object ) { // set tooltip over link's rect KPDFLink *link = (KPDFLink *)object->pointer(); TQString strtip = link->linkTip(); if ( !strtip.isEmpty() ) { TQRect linkRect = object->geometry( pageItem->width(), pageItem->height() ); linkRect.moveBy( - m_view->contentsX() + pageItem->geometry().left(), - m_view->contentsY() + pageItem->geometry().top() ); tip( linkRect, strtip ); } } } } /* PageView. What's in this file? -> quick overview. * Code weight (in rows) and meaning: * 160 - constructor and creating actions plus their connected slots (empty stuff) * 70 - DocumentObserver inherited methodes (important) * 550 - events: mouse, keyboard, drag/drop * 170 - slotRelayoutPages: set contents of the scrollview on continuous/single modes * 100 - zoom: zooming pages in different ways, keeping update the toolbar actions, etc.. * other misc functions: only slotRequestVisiblePixmaps and pickItemOnPoint noticeable, * and many insignificant stuff like this comment :-) */ PageView::PageView( TQWidget *parent, KPDFDocument *document ) : TQScrollView( parent, "KPDF::pageView", WStaticContents | WNoAutoErase ) { // create and initialize private storage structure d = new PageViewPrivate(); d->document = document; d->zoomMode = (PageView::ZoomMode)KpdfSettings::zoomMode(); d->zoomFactor = KpdfSettings::zoomFactor(); d->rotation = 0; d->mouseMode = MouseNormal; d->mouseMidStartY = -1; d->mouseOnRect = false; d->typeAheadActive = false; d->findTimeoutTimer = 0; d->viewportMoveActive = false; d->viewportMoveTimer = 0; d->scrollIncrement = 0; d->autoScrollTimer = 0; d->delayResizeTimer = 0; d->dirtyLayout = false; d->blockViewport = false; d->blockPixmapsRequest = false; d->messageWindow = new PageViewMessage(this); d->tip = new PageViewTip( this ); d->aPrevAction = 0; // widget setup: setup focus, accept drops and track mouse viewport()->setFocusProxy( this ); viewport()->setFocusPolicy( TQ_StrongFocus ); //viewport()->setPaletteBackgroundColor( TQt::white ); viewport()->setBackgroundMode( TQt::NoBackground ); setResizePolicy( Manual ); setAcceptDrops( true ); setDragAutoScroll( false ); viewport()->setMouseTracking( true ); // conntect the padding of the viewport to pixmaps requests connect( this, TQT_SIGNAL(contentsMoving(int, int)), this, TQT_SLOT(slotRequestVisiblePixmaps(int, int)) ); connect( &d->dragScrollTimer, TQT_SIGNAL(timeout()), this, TQT_SLOT(slotDragScroll()) ); // set a corner button to resize the view to the page size // TQPushButton * resizeButton = new TQPushButton( viewport() ); // resizeButton->setPixmap( SmallIcon("crop") ); // setCornerWidget( resizeButton ); // resizeButton->setEnabled( false ); // connect(...); setInputMethodEnabled( true ); // schedule the welcome message TQTimer::singleShot( 0, this, TQT_SLOT( slotShowWelcome() ) ); } PageView::~PageView() { // delete all widgets TQValueVector< PageViewItem * >::iterator dIt = d->items.begin(), dEnd = d->items.end(); for ( ; dIt != dEnd; ++dIt ) delete *dIt; delete d->tip; d->tip = 0; d->document->removeObserver( this ); delete d; } void PageView::setupActions( KActionCollection * ac ) { // Zoom actions ( higher scales takes lots of memory! ) d->aZoom = new KSelectAction( i18n( "Zoom" ), "viewmag", 0, TQT_TQOBJECT(this), TQT_SLOT( slotZoom() ), ac, "zoom_to" ); d->aZoom->setEditable( true ); #if KDE_IS_VERSION(3,4,89) d->aZoom->setMaxComboViewCount( 13 ); #endif updateZoomText(); KStdAction::zoomIn( TQT_TQOBJECT(this), TQT_SLOT( slotZoomIn() ), ac, "zoom_in" ); KStdAction::zoomOut( TQT_TQOBJECT(this), TQT_SLOT( slotZoomOut() ), ac, "zoom_out" ); d->aZoomFitWidth = new KToggleAction( i18n("Fit to Page &Width"), "view_fit_width", 0, ac, "zoom_fit_width" ); connect( d->aZoomFitWidth, TQT_SIGNAL( toggled( bool ) ), TQT_SLOT( slotFitToWidthToggled( bool ) ) ); d->aZoomFitPage = new KToggleAction( i18n("Fit to &Page"), "view_fit_window", 0, ac, "zoom_fit_page" ); connect( d->aZoomFitPage, TQT_SIGNAL( toggled( bool ) ), TQT_SLOT( slotFitToPageToggled( bool ) ) ); d->aZoomFitText = new KToggleAction( i18n("Fit to &Text"), "viewmagfit", 0, ac, "zoom_fit_text" ); connect( d->aZoomFitText, TQT_SIGNAL( toggled( bool ) ), TQT_SLOT( slotFitToTextToggled( bool ) ) ); // rotate actions KAction *action; action = new KAction( i18n("Rotate Right"), "rotate_cw", KShortcut( "Ctrl+Shift++" ), TQT_TQOBJECT(this), TQT_SLOT( slotRotateRight() ), ac, "rotate_right" ); action = new KAction( i18n("Rotate Left"), "rotate_ccw", KShortcut( "Ctrl+Shift+-" ), TQT_TQOBJECT(this), TQT_SLOT( slotRotateLeft() ), ac, "rotate_left" ); // View-Layout actions d->aViewTwoPages = new KToggleAction( i18n("&Two Pages"), "view_left_right", 0, ac, "view_twopages" ); connect( d->aViewTwoPages, TQT_SIGNAL( toggled( bool ) ), TQT_SLOT( slotTwoPagesToggled( bool ) ) ); d->aViewTwoPages->setChecked( KpdfSettings::viewColumns() > 1 ); d->aViewContinuous = new KToggleAction( i18n("&Continuous"), "view_text", 0, ac, "view_continuous" ); connect( d->aViewContinuous, TQT_SIGNAL( toggled( bool ) ), TQT_SLOT( slotContinuousToggled( bool ) ) ); d->aViewContinuous->setChecked( KpdfSettings::viewContinuous() ); // Mouse-Mode actions d->aMouseNormal = new KRadioAction( i18n("&Browse Tool"), "mouse", 0, TQT_TQOBJECT(this), TQT_SLOT( slotSetMouseNormal() ), ac, "mouse_drag" ); d->aMouseNormal->setExclusiveGroup( "MouseType" ); d->aMouseNormal->setChecked( true ); KToggleAction * mz = new KRadioAction( i18n("&Zoom Tool"), "viewmag", 0, TQT_TQOBJECT(this), TQT_SLOT( slotSetMouseZoom() ), ac, "mouse_zoom" ); mz->setExclusiveGroup( "MouseType" ); d->aMouseSelect = new KRadioAction( i18n("&Select Tool"), "frame_edit", 0, TQT_TQOBJECT(this), TQT_SLOT( slotSetMouseSelect() ), ac, "mouse_select" ); d->aMouseSelect->setExclusiveGroup( "MouseType" ); /* d->aMouseEdit = new KRadioAction( i18n("Draw"), "edit", 0, TQT_TQOBJECT(this), TQT_SLOT( slotSetMouseDraw() ), ac, "mouse_draw" ); d->aMouseEdit->setExclusiveGroup("MouseType"); d->aMouseEdit->setEnabled( false ); // implement feature before removing this line*/ // Other actions KAction * su = new KAction( i18n("Scroll Up"), 0, TQT_TQOBJECT(this), TQT_SLOT( slotScrollUp() ), ac, "view_scroll_up" ); su->setShortcut( "Shift+Up" ); KAction * sd = new KAction( i18n("Scroll Down"), 0, TQT_TQOBJECT(this), TQT_SLOT( slotScrollDown() ), ac, "view_scroll_down" ); sd->setShortcut( "Shift+Down" ); } bool PageView::canFitPageWidth() { return d->zoomMode != ZoomFitWidth; } void PageView::fitPageWidth( int /*page*/ ) { d->aZoom->setCurrentItem(0); slotZoom(); } //BEGIN DocumentObserver inherited methods void PageView::notifySetup( const TQValueVector< KPDFPage * > & pageSet, bool documentChanged ) { // reuse current pages if nothing new if ( ( pageSet.count() == d->items.count() ) && !documentChanged ) { int count = pageSet.count(); for ( int i = 0; (i < count) && !documentChanged; i++ ) if ( (int)pageSet[i]->number() != d->items[i]->pageNumber() ) documentChanged = true; if ( !documentChanged ) return; } // delete all widgets (one for each page in pageSet) TQValueVector< PageViewItem * >::iterator dIt = d->items.begin(), dEnd = d->items.end(); for ( ; dIt != dEnd; ++dIt ) delete *dIt; d->items.clear(); d->visibleItems.clear(); // create children widgets TQValueVector< KPDFPage * >::const_iterator setIt = pageSet.begin(), setEnd = pageSet.end(); for ( ; setIt != setEnd; ++setIt ) d->items.push_back( new PageViewItem( *setIt ) ); if ( pageSet.count() > 0 ) // TODO for Enrico: Check if doing always the slotRelayoutPages() is not // suboptimal in some cases, i'd say it is not but a recheck will not hurt // Need slotRelayoutPages() here instead of d->dirtyLayout = true // because opening a pdf from another pdf will not trigger a viewportchange // so pages are never relayouted TQTimer::singleShot(0, this, TQT_SLOT(slotRelayoutPages())); else { // update the mouse cursor when closing because we may have close through a link and // want the cursor to come back to the normal cursor updateCursor( viewportToContents( mapFromGlobal( TQCursor::pos() ) ) ); resizeContents( 0, 0 ); } // OSD to display pages if ( documentChanged && pageSet.count() > 0 && KpdfSettings::showOSD() ) d->messageWindow->display( i18n(" Loaded a one-page document.", " Loaded a %n-page document.", pageSet.count() ), PageViewMessage::Info, 4000 ); } void PageView::notifyViewportChanged( bool smoothMove ) { // if we are the one changing viewport, skip this nofity if ( d->blockViewport ) return; // block setViewport outgoing calls d->blockViewport = true; // find PageViewItem matching the viewport description const DocumentViewport & vp = d->document->viewport(); PageViewItem * item = 0; TQValueVector< PageViewItem * >::iterator iIt = d->items.begin(), iEnd = d->items.end(); for ( ; iIt != iEnd; ++iIt ) if ( (*iIt)->pageNumber() == vp.pageNumber ) { item = *iIt; break; } if ( !item ) { kdDebug() << "viewport has no matching item!" << endl; d->blockViewport = false; return; } // relayout in "Single Pages" mode or if a relayout is pending d->blockPixmapsRequest = true; if ( !KpdfSettings::viewContinuous() || d->dirtyLayout ) slotRelayoutPages(); // restore viewport center or use default {x-center,v-top} alignment const TQRect & r = item->geometry(); int newCenterX = r.left(), newCenterY = r.top(); if ( vp.rePos.enabled ) { if (vp.rePos.pos == DocumentViewport::Center) { newCenterX += (int)( vp.rePos.normalizedX * (double)r.width() ); newCenterY += (int)( vp.rePos.normalizedY * (double)r.height() ); } else { // TopLeft newCenterX += (int)( vp.rePos.normalizedX * (double)r.width() + viewport()->width() / 2 ); newCenterY += (int)( vp.rePos.normalizedY * (double)r.height() + viewport()->height() / 2 ); } } else { newCenterX += r.width() / 2; newCenterY += visibleHeight() / 2 - 10; } // if smooth movement requested, setup parameters and start it if ( smoothMove ) { d->viewportMoveActive = true; d->viewportMoveTime.start(); d->viewportMoveDest.setX( newCenterX ); d->viewportMoveDest.setY( newCenterY ); if ( !d->viewportMoveTimer ) { d->viewportMoveTimer = new TQTimer( this ); connect( d->viewportMoveTimer, TQT_SIGNAL( timeout() ), this, TQT_SLOT( slotMoveViewport() ) ); } d->viewportMoveTimer->start( 25 ); verticalScrollBar()->setEnabled( false ); horizontalScrollBar()->setEnabled( false ); } else center( newCenterX, newCenterY ); d->blockPixmapsRequest = false; // request visible pixmaps in the current viewport and recompute it slotRequestVisiblePixmaps(); // enable setViewport calls d->blockViewport = false; // update zoom text if in a ZoomFit/* zoom mode if ( d->zoomMode != ZoomFixed ) updateZoomText(); // since the page has moved below cursor, update it updateCursor( viewportToContents( mapFromGlobal( TQCursor::pos() ) ) ); } void PageView::notifyPageChanged( int pageNumber, int changedFlags ) { // only handle pixmap / highlight changes notifies if ( changedFlags & DocumentObserver::Bookmark ) return; // iterate over visible items: if page(pageNumber) is one of them, repaint it TQValueList< PageViewItem * >::iterator iIt = d->visibleItems.begin(), iEnd = d->visibleItems.end(); for ( ; iIt != iEnd; ++iIt ) if ( (*iIt)->pageNumber() == pageNumber ) { // update item's rectangle plus the little outline TQRect expandedRect = (*iIt)->geometry(); expandedRect.addCoords( -1, -1, 3, 3 ); updateContents( expandedRect ); // if we were "zoom-dragging" do not overwrite the "zoom-drag" cursor if ( cursor().shape() != TQt::SizeVerCursor ) { // since the page has been regenerated below cursor, update it updateCursor( viewportToContents( mapFromGlobal( TQCursor::pos() ) ) ); } break; } } void PageView::notifyContentsCleared( int changedFlags ) { // if pixmaps were cleared, re-ask them if ( changedFlags & DocumentObserver::Pixmap ) slotRequestVisiblePixmaps(); } bool PageView::canUnloadPixmap( int pageNumber ) { // if the item is visible, forbid unloading TQValueList< PageViewItem * >::iterator vIt = d->visibleItems.begin(), vEnd = d->visibleItems.end(); for ( ; vIt != vEnd; ++vIt ) if ( (*vIt)->pageNumber() == pageNumber ) return false; // if hidden premit unloading return true; } //END DocumentObserver inherited methods void PageView::showText( const TQString &text, int ms ) { d->messageWindow->display(text, PageViewMessage::Info, ms ); } //BEGIN widget events void PageView::viewportPaintEvent( TQPaintEvent * pe ) { // create the rect into contents from the clipped screen rect TQRect viewportRect = viewport()->rect(); TQRect contentsRect = pe->rect().intersect( viewportRect ); contentsRect.moveBy( contentsX(), contentsY() ); if ( !contentsRect.isValid() ) return; // create the screen painter. a pixel painted ar contentsX,contentsY // appears to the top-left corner of the scrollview. TQPainter screenPainter( viewport(), true ); screenPainter.translate( -contentsX(), -contentsY() ); // selectionRect is the normalized mouse selection rect TQRect selectionRect = d->mouseSelectionRect; if ( !selectionRect.isNull() ) selectionRect = selectionRect.normalize(); // selectionRectInternal without the border TQRect selectionRectInternal = selectionRect; selectionRectInternal.addCoords( 1, 1, -1, -1 ); // color for blending TQColor selBlendColor = (selectionRect.width() > 8 || selectionRect.height() > 8) ? d->selectionRectColor : TQt::red; // subdivide region into rects TQMemArray allRects = TQRegion(pe->region()).rects(); uint numRects = allRects.count(); // preprocess rects area to see if it worths or not using subdivision uint summedArea = 0; for ( uint i = 0; i < numRects; i++ ) { const TQRect & r = allRects[i]; summedArea += r.width() * r.height(); } // very elementary check: SUMj(Region[j].area) is less than boundingRect.area bool useSubdivision = summedArea < (0.7 * contentsRect.width() * contentsRect.height()); if ( !useSubdivision ) numRects = 1; // iterate over the rects (only one loop if not using subdivision) for ( uint i = 0; i < numRects; i++ ) { if ( useSubdivision ) { // set 'contentsRect' to a part of the sub-divided region contentsRect = allRects[i].normalize().intersect( viewportRect ); contentsRect.moveBy( contentsX(), contentsY() ); if ( !contentsRect.isValid() ) continue; } // note: this check will take care of all things requiring alpha blending (not only selection) bool wantCompositing = !selectionRect.isNull() && contentsRect.intersects( selectionRect ); if ( wantCompositing && KpdfSettings::enableCompositing() ) { // create pixmap and open a painter over it (contents{left,top} becomes pixmap {0,0}) TQPixmap doubleBuffer( contentsRect.size() ); TQPainter pixmapPainter( &doubleBuffer ); pixmapPainter.translate( -contentsRect.left(), -contentsRect.top() ); // 1) Layer 0: paint items and clear bg on unpainted rects paintItems( &pixmapPainter, contentsRect ); // 2) Layer 1: pixmap manipulated areas // 3) Layer 2: paint (blend) transparent selection if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) && !selectionRectInternal.contains( contentsRect ) ) { TQRect blendRect = selectionRectInternal.intersect( contentsRect ); // skip rectangles covered by the selection's border if ( blendRect.isValid() ) { // grab current pixmap into a new one to colorize contents TQPixmap blendedPixmap( blendRect.width(), blendRect.height() ); copyBlt( &blendedPixmap, 0,0, &doubleBuffer, blendRect.left() - contentsRect.left(), blendRect.top() - contentsRect.top(), blendRect.width(), blendRect.height() ); // blend selBlendColor into the background pixmap TQImage blendedImage = blendedPixmap.convertToImage(); KImageEffect::blend( selBlendColor.dark(140), blendedImage, 0.2 ); // copy the blended pixmap back to its place pixmapPainter.drawPixmap( blendRect.left(), blendRect.top(), blendedImage ); } // draw border (red if the selection is too small) pixmapPainter.setPen( selBlendColor ); pixmapPainter.drawRect( selectionRect ); } // 4) Layer 3: overlays if ( KpdfSettings::debugDrawBoundaries() ) { pixmapPainter.setPen( TQt::blue ); pixmapPainter.drawRect( contentsRect ); } // finish painting and draw contents pixmapPainter.end(); screenPainter.drawPixmap( contentsRect.left(), contentsRect.top(), doubleBuffer ); } else { // 1) Layer 0: paint items and clear bg on unpainted rects paintItems( &screenPainter, contentsRect ); // 2) Layer 1: opaque manipulated ares (filled / contours) // 3) Layer 2: paint opaque selection if ( !selectionRect.isNull() && selectionRect.intersects( contentsRect ) && !selectionRectInternal.contains( contentsRect ) ) { screenPainter.setPen( palette().active().highlight().dark(110) ); screenPainter.drawRect( selectionRect ); } // 4) Layer 3: overlays if ( KpdfSettings::debugDrawBoundaries() ) { screenPainter.setPen( TQt::red ); screenPainter.drawRect( contentsRect ); } } } } void PageView::viewportResizeEvent( TQResizeEvent * ) { // start a timer that will refresh the pixmap after 0.5s if ( !d->delayResizeTimer ) { d->delayResizeTimer = new TQTimer( this ); connect( d->delayResizeTimer, TQT_SIGNAL( timeout() ), this, TQT_SLOT( slotRelayoutPages() ) ); } d->delayResizeTimer->start( 333, true ); } void PageView::keyPressEvent( TQKeyEvent * e ) { e->accept(); // if performing a selection or dyn zooming, disable keys handling if ( ( !d->mouseSelectionRect.isNull() && e->key() != TQt::Key_Escape ) || d->mouseMidStartY != -1 ) return; // handle 'find as you type' (based on khtml/khtmlview.cpp) if( d->typeAheadActive ) { // backspace: remove a char and search or terminates search if( e->key() == Key_BackSpace ) { if( d->typeAheadString.length() > 1 ) { d->typeAheadString = d->typeAheadString.left( d->typeAheadString.length() - 1 ); bool found = d->document->searchText( PAGEVIEW_SEARCH_ID, d->typeAheadString, true, false, KPDFDocument::NextMatch, true, tqRgb( 128, 255, 128 ), true ); TQString status = found ? i18n("Text found: \"%1\".") : i18n("Text not found: \"%1\"."); d->messageWindow->display( status.arg(d->typeAheadString.lower()), found ? PageViewMessage::Find : PageViewMessage::Warning, 4000 ); d->findTimeoutTimer->start( 3000, true ); } else { findAheadStop(); d->document->resetSearch( PAGEVIEW_SEARCH_ID ); } } // F3: go to next occurrency else if( e->key() == KStdAccel::findNext() ) { // part doesn't get this key event because of the keyboard grab d->findTimeoutTimer->stop(); // restore normal operation during possible messagebox is displayed // it is needed to grab the keyboard becase people may have Space assigned to a // accel and without grabbing the keyboard you can not vim-search for space // because it activates the accel releaseKeyboard(); if ( d->document->continueSearch( PAGEVIEW_SEARCH_ID ) ) d->messageWindow->display( i18n("Text found: \"%1\".").arg(d->typeAheadString.lower()), PageViewMessage::Find, 3000 ); d->findTimeoutTimer->start( 3000, true ); // it is needed to grab the keyboard becase people may have Space assigned to a // accel and without grabbing the keyboard you can not vim-search for space // because it activates the accel grabKeyboard(); } // esc and return: end search else if( e->key() == Key_Escape || e->key() == Key_Return ) { findAheadStop(); } // other key: add to text and search else if( !e->text().isEmpty() ) { d->typeAheadString += e->text(); doTypeAheadSearch(); } return; } else if( e->key() == '/' && d->document->isOpened() && d->document->supportsSearching() ) { // stop scrolling the page (if doing it) if ( d->autoScrollTimer ) { d->scrollIncrement = 0; d->autoScrollTimer->stop(); } // start type-adeas search d->typeAheadString = TQString(); d->messageWindow->display( i18n("Starting -- find text as you type"), PageViewMessage::Find, 3000 ); d->typeAheadActive = true; if ( !d->findTimeoutTimer ) { // create the timer on demand d->findTimeoutTimer = new TQTimer( this ); connect( d->findTimeoutTimer, TQT_SIGNAL( timeout() ), this, TQT_SLOT( findAheadStop() ) ); } d->findTimeoutTimer->start( 3000, true ); // it is needed to grab the keyboard becase people may have Space assigned to a // accel and without grabbing the keyboard you can not vim-search for space // because it activates the accel grabKeyboard(); return; } // if viewport is moving, disable keys handling if ( d->viewportMoveActive ) return; // move/scroll page by using keys switch ( e->key() ) { case Key_Up: case Key_PageUp: case Key_Backspace: // if in single page mode and at the top of the screen, go to previous page if ( KpdfSettings::viewContinuous() || verticalScrollBar()->value() > verticalScrollBar()->minValue() ) { if ( e->key() == Key_Up ) verticalScrollBar()->subtractLine(); else verticalScrollBar()->subtractPage(); } else if ( d->document->currentPage() > 0 ) { // more optimized than document->setPrevPage and then move view to bottom DocumentViewport newViewport = d->document->viewport(); newViewport.pageNumber -= 1; newViewport.rePos.enabled = true; newViewport.rePos.normalizedY = 1.0; d->document->setViewport( newViewport ); } break; case Key_Down: case Key_PageDown: case Key_Space: // if in single page mode and at the bottom of the screen, go to next page if ( KpdfSettings::viewContinuous() || verticalScrollBar()->value() < verticalScrollBar()->maxValue() ) { if ( e->key() == Key_Down ) verticalScrollBar()->addLine(); else verticalScrollBar()->addPage(); } else if ( d->document->currentPage() < d->items.count() - 1 ) { // more optmized than document->setNextPage and then move view to top DocumentViewport newViewport = d->document->viewport(); newViewport.pageNumber += 1; newViewport.rePos.enabled = true; newViewport.rePos.normalizedY = 0.0; d->document->setViewport( newViewport ); } break; case Key_Left: horizontalScrollBar()->subtractLine(); break; case Key_Right: horizontalScrollBar()->addLine(); break; case TQt::Key_Escape: selectionClear(); d->mousePressPos = TQPoint(); if ( d->aPrevAction ) { d->aPrevAction->activate(); d->aPrevAction = 0; } break; case Key_Shift: case Key_Control: if ( d->autoScrollTimer ) { if ( d->autoScrollTimer->isActive() ) d->autoScrollTimer->stop(); else slotAutoScoll(); return; } // else fall trhough default: e->ignore(); return; } // if a known key has been pressed, stop scrolling the page if ( d->autoScrollTimer ) { d->scrollIncrement = 0; d->autoScrollTimer->stop(); } } void PageView::imEndEvent( TQIMEvent * e ) { if( d->typeAheadActive ) { if( !e->text().isEmpty() ) { d->typeAheadString += e->text(); doTypeAheadSearch(); e->accept(); } } } void PageView::contentsMouseMoveEvent( TQMouseEvent * e ) { // don't perform any mouse action when no document is shown if ( d->items.isEmpty() ) return; // don't perform any mouse action when viewport is autoscrolling if ( d->viewportMoveActive ) return; // if holding mouse mid button, perform zoom if ( (e->state() & Qt::MidButton) && d->mouseMidStartY >= 0 ) { int deltaY = d->mouseMidStartY - e->globalPos().y(); d->mouseMidStartY = e->globalPos().y(); d->zoomFactor *= ( 1.0 + ( (double)deltaY / 500.0 ) ); updateZoom( ZoomRefreshCurrent ); // uncomment following line to force a complete redraw viewport()->repaint( false ); return; } bool leftButton = e->state() & Qt::LeftButton, rightButton = e->state() & Qt::RightButton; switch ( d->mouseMode ) { case MouseNormal: if ( leftButton ) { // drag page if ( !d->mouseGrabPos.isNull() ) { // scroll page by position increment TQPoint delta = d->mouseGrabPos - e->globalPos(); scrollBy( delta.x(), delta.y() ); d->mouseGrabPos = e->globalPos(); } } else if ( rightButton && !d->mousePressPos.isNull() ) { // if mouse moves 5 px away from the press point, switch to 'selection' int deltaX = d->mousePressPos.x() - e->globalPos().x(), deltaY = d->mousePressPos.y() - e->globalPos().y(); if ( deltaX > 5 || deltaX < -5 || deltaY > 5 || deltaY < -5 ) { d->aPrevAction = d->aMouseNormal; d->aMouseSelect->activate(); TQColor selColor = palette().active().highlight().light( 120 ); selectionStart( e->x() + deltaX, e->y() + deltaY, selColor, false ); selectionEndPoint( e->x(), e->y() ); break; } } else { // only hovering the page, so update the cursor updateCursor( e->pos() ); } break; case MouseZoom: case MouseSelect: // set second corner of selection if ( !d->mousePressPos.isNull() && ( leftButton || d->aPrevAction ) ) selectionEndPoint( e->x(), e->y() ); break; case MouseEdit: // ? update graphics ? break; } } void PageView::contentsMousePressEvent( TQMouseEvent * e ) { // don't perform any mouse action when no document is shown if ( d->items.isEmpty() ) return; // if performing a selection or dyn zooming, disable mouse press if ( !d->mouseSelectionRect.isNull() || d->mouseMidStartY != -1 || d->viewportMoveActive ) return; // if the page is scrolling, stop it if ( d->autoScrollTimer ) { d->scrollIncrement = 0; d->autoScrollTimer->stop(); } // if pressing mid mouse button while not doing other things, begin 'comtinous zoom' mode if ( e->button() & Qt::MidButton ) { d->mouseMidStartY = e->globalPos().y(); setCursor( KCursor::sizeVerCursor() ); return; } // update press / 'start drag' mouse position d->mousePressPos = e->globalPos(); // handle mode dependant mouse press actions bool leftButton = e->button() & Qt::LeftButton, rightButton = e->button() & Qt::RightButton; switch ( d->mouseMode ) { case MouseNormal: // drag start / click / link following if ( leftButton ) { d->mouseGrabPos = d->mouseOnRect ? TQPoint() : d->mousePressPos; if ( !d->mouseOnRect ) setCursor( KCursor::sizeAllCursor() ); } break; case MouseZoom: // set first corner of the zoom rect if ( leftButton ) selectionStart( e->x(), e->y(), palette().active().highlight(), false ); else if ( rightButton ) updateZoom( ZoomOut ); break; case MouseSelect: // set first corner of the selection rect if ( leftButton ) { TQColor selColor = palette().active().highlight().light( 120 ); selectionStart( e->x(), e->y(), selColor, false ); } break; case MouseEdit: // ..to do.. break; } } void PageView::contentsMouseReleaseEvent( TQMouseEvent * e ) { // stop the drag scrolling d->dragScrollTimer.stop(); // don't perform any mouse action when no document is shown if ( d->items.isEmpty() ) { // ..except for right Clicks (emitted even it viewport is empty) if ( e->button() == Qt::RightButton ) emit rightClick( 0, e->globalPos() ); return; } // don't perform any mouse action when viewport is autoscrolling if ( d->viewportMoveActive ) return; // handle mode indepent mid buttom zoom bool midButton = e->button() & Qt::MidButton; if ( midButton && d->mouseMidStartY > 0 ) { d->mouseMidStartY = -1; // while drag-zooming we could have gone over a link updateCursor( e->pos() ); return; } bool leftButton = e->button() & Qt::LeftButton, rightButton = e->button() & Qt::RightButton; switch ( d->mouseMode ) { case MouseNormal:{ // return the cursor to its normal state after dragging if ( cursor().shape() == TQt::SizeAllCursor ) updateCursor( e->pos() ); PageViewItem * pageItem = pickItemOnPoint( e->x(), e->y() ); // if the mouse has not moved since the press, that's a -click- if ( leftButton && pageItem && d->mousePressPos == e->globalPos()) { double nX = (double)(e->x() - pageItem->geometry().left()) / (double)pageItem->width(), nY = (double)(e->y() - pageItem->geometry().top()) / (double)pageItem->height(); const ObjectRect * linkRect, * imageRect; linkRect = pageItem->page()->hasObject( ObjectRect::Link, nX, nY ); if ( linkRect ) { // handle click over a link const KPDFLink * link = static_cast< const KPDFLink * >( linkRect->pointer() ); d->document->processLink( link ); } else { // a link can move us to another page or even to another document, there's no point in trying to process the click on the image once we have processes the click on the link imageRect = pageItem->page()->hasObject( ObjectRect::Image, nX, nY ); if ( imageRect ) { // handle click over a image } // Enrico and me have decided this is not worth the trouble it generates // else // { // if not on a rect, the click selects the page // d->document->setViewportPage( pageItem->pageNumber(), PAGEVIEW_ID ); // } } } else if ( rightButton ) { // right click (if not within 5 px of the press point, the mode // had been already changed to 'Selection' instead of 'Normal') emit rightClick( pageItem ? pageItem->page() : 0, e->globalPos() ); } }break; case MouseZoom: // if a selection rect has been defined, zoom into it if ( leftButton && !d->mouseSelectionRect.isNull() ) { TQRect selRect = d->mouseSelectionRect.normalize(); if ( selRect.width() <= 8 && selRect.height() <= 8 ) { selectionClear(); break; } // find out new zoom ratio and normalized view center (relative to the contentsRect) double zoom = TQMIN( (double)visibleWidth() / (double)selRect.width(), (double)visibleHeight() / (double)selRect.height() ); double nX = (double)(selRect.left() + selRect.right()) / (2.0 * (double)contentsWidth()); double nY = (double)(selRect.top() + selRect.bottom()) / (2.0 * (double)contentsHeight()); // zoom up to 400% if ( d->zoomFactor <= 4.0 || zoom <= 1.0 ) { d->zoomFactor *= zoom; viewport()->setUpdatesEnabled( false ); updateZoom( ZoomRefreshCurrent ); viewport()->setUpdatesEnabled( true ); } // recenter view and update the viewport center( (int)(nX * contentsWidth()), (int)(nY * contentsHeight()) ); updateContents(); // hide message box and delete overlay window selectionClear(); } break; case MouseSelect:{ if (d->mouseSelectionRect.isNull() && rightButton) { PageViewItem * pageItem = pickItemOnPoint( e->x(), e->y() ); emit rightClick( pageItem ? pageItem->page() : 0, e->globalPos() ); } // if a selection is defined, display a popup if ( (!leftButton && !d->aPrevAction) || (leftButton && d->aPrevAction) || d->mouseSelectionRect.isNull() ) break; TQRect selectionRect = d->mouseSelectionRect.normalize(); if ( selectionRect.width() <= 8 && selectionRect.height() <= 8 ) { selectionClear(); if ( d->aPrevAction ) { d->aPrevAction->activate(); d->aPrevAction = 0; } break; } // grab text in selection by extracting it from all intersected pages TQString selectedText; TQValueVector< PageViewItem * >::iterator iIt = d->items.begin(), iEnd = d->items.end(); for ( ; iIt != iEnd; ++iIt ) { PageViewItem * item = *iIt; const TQRect & itemRect = item->geometry(); if ( selectionRect.intersects( itemRect ) ) { // request the textpage if there isn't one const KPDFPage * kpdfPage = item->page(); if ( !kpdfPage->hasSearchPage() ) d->document->requestTextPage( kpdfPage->number() ); // grab text in the rect that intersects itemRect TQRect relativeRect = selectionRect.intersect( itemRect ); relativeRect.moveBy( -itemRect.left(), -itemRect.top() ); NormalizedRect normRect( relativeRect, item->width(), item->height() ); selectedText += kpdfPage->getText( normRect ); } } // popup that ask to copy:text and copy/save:image KPopupMenu menu( this ); if ( !selectedText.isEmpty() ) { menu.insertTitle( i18n( "Text (1 character)", "Text (%n characters)", selectedText.length() ) ); menu.insertItem( SmallIcon("editcopy"), i18n( "Copy to Clipboard" ), 1 ); if ( !d->document->isAllowed( KPDFDocument::AllowCopy ) ) menu.setItemEnabled( 1, false ); if ( KpdfSettings::useKTTSD() ) menu.insertItem( SmallIcon("kttsd"), i18n( "Speak Text" ), 2 ); } menu.insertTitle( i18n( "Image (%1 by %2 pixels)" ).arg( selectionRect.width() ).arg( selectionRect.height() ) ); menu.insertItem( SmallIcon("image"), i18n( "Copy to Clipboard" ), 3 ); menu.insertItem( SmallIcon("filesave"), i18n( "Save to File..." ), 4 ); int choice = menu.exec( e->globalPos() ); // IMAGE operation choosen if ( choice > 2 ) { // renders page into a pixmap TQPixmap copyPix( selectionRect.width(), selectionRect.height() ); TQPainter copyPainter( ©Pix ); copyPainter.translate( -selectionRect.left(), -selectionRect.top() ); paintItems( ©Painter, selectionRect ); if ( choice == 3 ) { // [2] copy pixmap to clipboard TQClipboard *cb = TQApplication::clipboard(); cb->setPixmap( copyPix, TQClipboard::Clipboard ); if ( cb->supportsSelection() ) cb->setPixmap( copyPix, TQClipboard::Selection ); d->messageWindow->display( i18n( "Image [%1x%2] copied to clipboard." ).arg( copyPix.width() ).arg( copyPix.height() ) ); } else if ( choice == 4 ) { // [3] save pixmap to file TQString fileName = KFileDialog::getSaveFileName( TQString(), "image/png image/jpeg", this ); if ( fileName.isNull() ) d->messageWindow->display( i18n( "File not saved." ), PageViewMessage::Warning ); else { TQString type( KImageIO::type( fileName ) ); if ( type.isNull() ) type = "PNG"; copyPix.save( fileName, type.latin1() ); d->messageWindow->display( i18n( "Image [%1x%2] saved to %3 file." ).arg( copyPix.width() ).arg( copyPix.height() ).arg( type ) ); } } } // TEXT operation choosen else { if ( choice == 1 ) { // [1] copy text to clipboard TQClipboard *cb = TQApplication::clipboard(); cb->setText( selectedText, TQClipboard::Clipboard ); if ( cb->supportsSelection() ) cb->setText( selectedText, TQClipboard::Selection ); } else if ( choice == 2 ) { // [2] speech selection using KTTSD DCOPClient * client = DCOPClient::mainClient(); // Albert says is this ever necessary? // we already attached on Part constructor // if ( !client->isAttached() ) // client->attach(); // If KTTSD not running, start it. if (!client->isApplicationRegistered("kttsd")) { TQString error; if (TDEApplication::startServiceByDesktopName("kttsd", TQStringList(), &error)) { d->messageWindow->display( i18n("Starting KTTSD Failed: %1").arg(error) ); KpdfSettings::setUseKTTSD(false); KpdfSettings::writeConfig(); } } if ( KpdfSettings::useKTTSD() ) { // serialize the text to speech (selectedText) and the // preferred reader ("" is the default voice) ... TQByteArray data; TQDataStream arg( data, IO_WriteOnly ); arg << selectedText; arg << TQString(); TQCString replyType; TQByteArray replyData; // ..and send it to KTTSD if (client->call( "kttsd", "KSpeech", "setText(TQString,TQString)", data, replyType, replyData, true )) { TQByteArray data2; TQDataStream arg2(data2, IO_WriteOnly); arg2 << 0; client->send("kttsd", "KSpeech", "startText(uint)", data2 ); } } } } // clear widget selection and invalidate rect selectionClear(); // restore previous action if came from it using right button if ( d->aPrevAction ) { d->aPrevAction->activate(); d->aPrevAction = 0; } }break; case MouseEdit: // ? apply [tool] ? break; } // reset mouse press / 'drag start' position d->mousePressPos = TQPoint(); } void PageView::wheelEvent( TQWheelEvent *e ) { // don't perform any mouse action when viewport is autoscrolling if ( d->viewportMoveActive ) return; if ( !d->document->isOpened() ) { TQScrollView::wheelEvent( e ); return; } int delta = e->delta(), vScroll = verticalScrollBar()->value(); e->accept(); if ( (e->state() & ControlButton) == ControlButton ) { if ( e->delta() < 0 ) slotZoomOut(); else slotZoomIn(); } else if ( delta <= -120 && !KpdfSettings::viewContinuous() && vScroll == verticalScrollBar()->maxValue() ) { // go to next page if ( d->document->currentPage() < d->items.count() - 1 ) { // more optmized than document->setNextPage and then move view to top DocumentViewport newViewport = d->document->viewport(); newViewport.pageNumber += 1; newViewport.rePos.enabled = true; newViewport.rePos.normalizedY = 0.0; d->document->setViewport( newViewport ); } } else if ( delta >= 120 && !KpdfSettings::viewContinuous() && vScroll == verticalScrollBar()->minValue() ) { // go to prev page if ( d->document->currentPage() > 0 ) { // more optmized than document->setPrevPage and then move view to bottom DocumentViewport newViewport = d->document->viewport(); newViewport.pageNumber -= 1; newViewport.rePos.enabled = true; newViewport.rePos.normalizedY = 1.0; d->document->setViewport( newViewport ); } } else TQScrollView::wheelEvent( e ); TQPoint cp = viewportToContents(e->pos()); updateCursor(cp); } void PageView::dragEnterEvent( TQDragEnterEvent * ev ) { ev->accept(); } void PageView::dropEvent( TQDropEvent * ev ) { KURL::List lst; if ( KURLDrag::decode( ev, lst ) ) emit urlDropped( lst.first() ); } //END widget events void PageView::paintItems( TQPainter * p, const TQRect & contentsRect ) { // when checking if an Item is contained in contentsRect, instead of // growing PageViewItems rects (for keeping outline into account), we // grow the contentsRect TQRect checkRect = contentsRect; checkRect.addCoords( -3, -3, 1, 1 ); // create a region from wich we'll subtract painted rects TQRegion remainingArea( contentsRect ); //TQValueVector< PageViewItem * >::iterator iIt = d->visibleItems.begin(), iEnd = d->visibleItems.end(); TQValueVector< PageViewItem * >::iterator iIt = d->items.begin(), iEnd = d->items.end(); for ( ; iIt != iEnd; ++iIt ) { // check if a piece of the page intersects the contents rect if ( !(*iIt)->geometry().intersects( checkRect ) ) continue; PageViewItem * item = *iIt; TQRect pixmapGeometry = item->geometry(); // translate the painter so we draw top-left pixmap corner in 0,0 p->save(); p->translate( pixmapGeometry.left(), pixmapGeometry.top() ); // item pixmap and outline geometry TQRect outlineGeometry = pixmapGeometry; outlineGeometry.addCoords( -1, -1, 3, 3 ); // draw the page outline (little black border and 2px shadow) if ( !pixmapGeometry.contains( contentsRect ) ) { int pixmapWidth = pixmapGeometry.width(), pixmapHeight = pixmapGeometry.height(); // draw simple outline p->setPen( TQt::black ); p->drawRect( -1, -1, pixmapWidth + 2, pixmapHeight + 2 ); // draw bottom/right gradient int levels = 2; int r = TQt::gray.red() / (levels + 2), g = TQt::gray.green() / (levels + 2), b = TQt::gray.blue() / (levels + 2); for ( int i = 0; i < levels; i++ ) { p->setPen( TQColor( r * (i+2), g * (i+2), b * (i+2) ) ); p->drawLine( i, i + pixmapHeight + 1, i + pixmapWidth + 1, i + pixmapHeight + 1 ); p->drawLine( i + pixmapWidth + 1, i, i + pixmapWidth + 1, i + pixmapHeight ); p->setPen( TQt::gray ); p->drawLine( -1, i + pixmapHeight + 1, i - 1, i + pixmapHeight + 1 ); p->drawLine( i + pixmapWidth + 1, -1, i + pixmapWidth + 1, i - 1 ); } } // draw the pixmap (note: this modifies the painter) if ( contentsRect.intersects( pixmapGeometry ) ) { TQRect pixmapRect = contentsRect.intersect( pixmapGeometry ); pixmapRect.moveBy( -pixmapGeometry.left(), -pixmapGeometry.top() ); int flags = PagePainter::Accessibility | PagePainter::EnhanceLinks | PagePainter::EnhanceImages | PagePainter::Highlights; PagePainter::paintPageOnPainter( item->page(), PAGEVIEW_ID, flags, p, pixmapRect, pixmapGeometry.width(), pixmapGeometry.height() ); } // remove painted area from 'remainingArea' and restore painter remainingArea -= outlineGeometry.intersect( contentsRect ); p->restore(); } // paint with background color the unpainted area TQMemArray backRects = remainingArea.rects(); uint backRectsNumber = backRects.count(); for ( uint jr = 0; jr < backRectsNumber; jr++ ) p->fillRect( backRects[ jr ], TQt::gray ); } void PageView::updateItemSize( PageViewItem * item, int colWidth, int rowHeight ) { const KPDFPage * kpdfPage = item->page(); double width = kpdfPage->width(), height = kpdfPage->height(), zoom = d->zoomFactor; if ( d->zoomMode == ZoomFixed ) { width *= zoom; height *= zoom; item->setWHZ( (int)width, (int)height, d->zoomFactor ); } else if ( d->zoomMode == ZoomFitWidth ) { height = kpdfPage->ratio() * colWidth; item->setWHZ( colWidth, (int)height, (double)colWidth / width ); d->zoomFactor = (double)colWidth / width; } else if ( d->zoomMode == ZoomFitPage ) { double scaleW = (double)colWidth / (double)width; double scaleH = (double)rowHeight / (double)height; zoom = TQMIN( scaleW, scaleH ); item->setWHZ( (int)(zoom * width), (int)(zoom * height), zoom ); d->zoomFactor = zoom; } #ifndef NDEBUG else kdDebug() << "calling updateItemSize with unrecognized d->zoomMode!" << endl; #endif } PageViewItem * PageView::pickItemOnPoint( int x, int y ) { PageViewItem * item = 0; TQValueList< PageViewItem * >::iterator iIt = d->visibleItems.begin(), iEnd = d->visibleItems.end(); for ( ; iIt != iEnd; ++iIt ) { PageViewItem * i = *iIt; const TQRect & r = i->geometry(); if ( x < r.right() && x > r.left() && y < r.bottom() ) { if ( y > r.top() ) item = i; break; } } return item; } void PageView::selectionStart( int x, int y, const TQColor & color, bool /*aboveAll*/ ) { d->mouseSelectionRect.setRect( x, y, 1, 1 ); d->selectionRectColor = color; // ensures page doesn't scroll if ( d->autoScrollTimer ) { d->scrollIncrement = 0; d->autoScrollTimer->stop(); } } void PageView::selectionEndPoint( int x, int y ) { if (x < contentsX()) d->dragScrollVector.setX(x - contentsX()); else if (contentsX() + viewport()->width() < x) d->dragScrollVector.setX(x - contentsX() - viewport()->width()); else d->dragScrollVector.setX(0); if (y < contentsY()) d->dragScrollVector.setY(y - contentsY()); else if (contentsY() + viewport()->height() < y) d->dragScrollVector.setY(y - contentsY() - viewport()->height()); else d->dragScrollVector.setY(0); if (d->dragScrollVector != TQPoint(0, 0)) { if (!d->dragScrollTimer.isActive()) d->dragScrollTimer.start(100); } else d->dragScrollTimer.stop(); // clip selection to the viewport TQRect viewportRect( contentsX(), contentsY(), visibleWidth(), visibleHeight() ); x = TQMAX( TQMIN( x, viewportRect.right() ), viewportRect.left() ); y = TQMAX( TQMIN( y, viewportRect.bottom() ), viewportRect.top() ); // if selection changed update rect if ( d->mouseSelectionRect.right() != x || d->mouseSelectionRect.bottom() != y ) { // send incremental paint events TQRect oldRect = d->mouseSelectionRect.normalize(); d->mouseSelectionRect.setRight( x ); d->mouseSelectionRect.setBottom( y ); TQRect newRect = d->mouseSelectionRect.normalize(); // generate diff region: [ OLD.unite(NEW) - OLD.intersect(NEW) ] TQRegion compoundRegion = TQRegion( oldRect ).unite( newRect ); if ( oldRect.intersects( newRect ) ) { TQRect intersection = oldRect.intersect( newRect ); intersection.addCoords( 1, 1, -1, -1 ); if ( intersection.width() > 20 && intersection.height() > 20 ) compoundRegion -= intersection; } // tassellate region with rects and enqueue paint events TQMemArray rects = compoundRegion.rects(); for ( uint i = 0; i < rects.count(); i++ ) updateContents( rects[i] ); } } void PageView::selectionClear() { updateContents( d->mouseSelectionRect.normalize() ); d->mouseSelectionRect.setCoords( 0, 0, -1, -1 ); } void PageView::updateZoom( ZoomMode newZoomMode ) { if ( newZoomMode == ZoomFixed ) { if ( d->aZoom->currentItem() == 0 ) newZoomMode = ZoomFitWidth; else if ( d->aZoom->currentItem() == 1 ) newZoomMode = ZoomFitPage; } float newFactor = d->zoomFactor; KAction * checkedZoomAction = 0; switch ( newZoomMode ) { case ZoomFixed:{ //ZoomFixed case TQString z = d->aZoom->currentText(); newFactor = KGlobal::locale()->readNumber( z.remove( z.find( '%' ), 1 ) ) / 100.0; }break; case ZoomIn: newFactor += (newFactor > 0.99) ? ( newFactor > 1.99 ? 0.5 : 0.2 ) : 0.1; newZoomMode = ZoomFixed; break; case ZoomOut: newFactor -= (newFactor > 0.99) ? ( newFactor > 1.99 ? 0.5 : 0.2 ) : 0.1; newZoomMode = ZoomFixed; break; case ZoomFitWidth: checkedZoomAction = d->aZoomFitWidth; break; case ZoomFitPage: checkedZoomAction = d->aZoomFitPage; break; case ZoomFitText: checkedZoomAction = d->aZoomFitText; break; case ZoomRefreshCurrent: newZoomMode = ZoomFixed; d->zoomFactor = -1; break; } if ( newFactor > 4.0 ) newFactor = 4.0; if ( newFactor < 0.1 ) newFactor = 0.1; if ( newZoomMode != d->zoomMode || (newZoomMode == ZoomFixed && newFactor != d->zoomFactor ) ) { // rebuild layout and update the whole viewport d->zoomMode = newZoomMode; d->zoomFactor = newFactor; // be sure to block updates to document's viewport bool prevState = d->blockViewport; d->blockViewport = true; slotRelayoutPages(); d->blockViewport = prevState; // request pixmaps slotRequestVisiblePixmaps(); // update zoom text updateZoomText(); // update actions checked state d->aZoomFitWidth->setChecked( checkedZoomAction == d->aZoomFitWidth ); d->aZoomFitPage->setChecked( checkedZoomAction == d->aZoomFitPage ); d->aZoomFitText->setChecked( checkedZoomAction == d->aZoomFitText ); // save selected zoom factor KpdfSettings::setZoomMode(newZoomMode); KpdfSettings::setZoomFactor(newFactor); KpdfSettings::writeConfig(); } } void PageView::updateZoomText() { // use current page zoom as zoomFactor if in ZoomFit/* mode if ( d->zoomMode != ZoomFixed && d->items.count() > 0 ) d->zoomFactor = d->items[ TQMAX( 0, (int)d->document->currentPage() ) ]->zoomFactor(); float newFactor = d->zoomFactor; d->aZoom->clear(); // add items that describe fit actions TQStringList translated; translated << i18n("Fit Width") << i18n("Fit Page"); // << i18n("Fit Text"); // add percent items TQString double_oh( "00" ); const float zoomValue[10] = { 0.125, 0.25, 0.333, 0.5, 0.667, 0.75, 1, 1.25, 1.50, 2 }; int idx = 0, selIdx = 2; // use 3 if "fit text" present bool inserted = false; //use: "d->zoomMode != ZoomFixed" to hide Fit/* zoom ratio while ( idx < 10 || !inserted ) { float value = idx < 10 ? zoomValue[ idx ] : newFactor; if ( !inserted && newFactor < (value - 0.0001) ) value = newFactor; else idx ++; if ( value > (newFactor - 0.0001) && value < (newFactor + 0.0001) ) inserted = true; if ( !inserted ) selIdx++; TQString localValue( KGlobal::locale()->formatNumber( value * 100.0, 2 ) ); localValue.remove( KGlobal::locale()->decimalSymbol() + double_oh ); translated << TQString( "%1%" ).arg( localValue ); } d->aZoom->setItems( translated ); // select current item in list if ( d->zoomMode == ZoomFitWidth ) selIdx = 0; else if ( d->zoomMode == ZoomFitPage ) selIdx = 1; else if ( d->zoomMode == ZoomFitText ) selIdx = 2; d->aZoom->setCurrentItem( selIdx ); } void PageView::updateCursor( const TQPoint &p ) { // detect the underlaying page (if present) PageViewItem * pageItem = pickItemOnPoint( p.x(), p.y() ); if ( pageItem && d->mouseMode == MouseNormal ) { double nX = (double)(p.x() - pageItem->geometry().left()) / (double)pageItem->width(), nY = (double)(p.y() - pageItem->geometry().top()) / (double)pageItem->height(); // if over a ObjectRect (of type Link) change cursor to hand d->mouseOnRect = pageItem->page()->hasObject( ObjectRect::Link, nX, nY ); if ( d->mouseOnRect ) setCursor( KCursor::handCursor() ); else setCursor( KCursor::arrowCursor() ); } else { // if there's no page over the cursor and we were showing the pointingHandCursor // go back to the normal one d->mouseOnRect = false; setCursor( KCursor::arrowCursor() ); } } void PageView::doTypeAheadSearch() { bool found = d->document->searchText( PAGEVIEW_SEARCH_ID, d->typeAheadString, false, false, KPDFDocument::NextMatch, true, tqRgb( 128, 255, 128 ), true ); TQString status = found ? i18n("Text found: \"%1\".") : i18n("Text not found: \"%1\"."); d->messageWindow->display( status.arg(d->typeAheadString.lower()), found ? PageViewMessage::Find : PageViewMessage::Warning, 4000 ); d->findTimeoutTimer->start( 3000, true ); } //BEGIN private SLOTS void PageView::slotRelayoutPages() // called by: notifySetup, viewportResizeEvent, slotTwoPagesToggled, slotContinuousToggled, updateZoom { // set an empty container if we have no pages int pageCount = d->items.count(); if ( pageCount < 1 ) { resizeContents( 0, 0 ); return; } // if viewport was auto-moving, stop it if ( d->viewportMoveActive ) { d->viewportMoveActive = false; d->viewportMoveTimer->stop(); verticalScrollBar()->setEnabled( true ); horizontalScrollBar()->setEnabled( true ); } // common iterator used in this method and viewport parameters TQValueVector< PageViewItem * >::iterator iIt, iEnd = d->items.end(); int viewportWidth = visibleWidth(), viewportHeight = visibleHeight(), fullWidth = 0, fullHeight = 0; TQRect viewportRect( contentsX(), contentsY(), viewportWidth, viewportHeight ); // set all items geometry and resize contents. handle 'continuous' and 'single' modes separately if ( KpdfSettings::viewContinuous() ) { // Here we find out column's width and row's height to compute a table // so we can place widgets 'centered in virtual cells'. int nCols = KpdfSettings::viewColumns(), nRows = (int)ceil( (float)pageCount / (float)nCols ), * colWidth = new int[ nCols ], * rowHeight = new int[ nRows ], cIdx = 0, rIdx = 0; for ( int i = 0; i < nCols; i++ ) colWidth[ i ] = viewportWidth / nCols; for ( int i = 0; i < nRows; i++ ) rowHeight[ i ] = 0; // 1) find the maximum columns width and rows height for a grid in // which each page must well-fit inside a cell for ( iIt = d->items.begin(); iIt != iEnd; ++iIt ) { PageViewItem * item = *iIt; // update internal page size (leaving a little margin in case of Fit* modes) updateItemSize( item, colWidth[ cIdx ] - 6, viewportHeight - 8 ); // find row's maximum height and column's max width if ( item->width() + 6 > colWidth[ cIdx ] ) colWidth[ cIdx ] = item->width() + 6; if ( item->height() > rowHeight[ rIdx ] ) rowHeight[ rIdx ] = item->height(); // update col/row indices if ( ++cIdx == nCols ) { cIdx = 0; rIdx++; } } // 2) arrange widgets inside cells int insertX = 0, insertY = 4; // 2 + 4*d->zoomFactor ? cIdx = 0; rIdx = 0; for ( iIt = d->items.begin(); iIt != iEnd; ++iIt ) { PageViewItem * item = *iIt; int cWidth = colWidth[ cIdx ], rHeight = rowHeight[ rIdx ]; // center widget inside 'cells' item->moveTo( insertX + (cWidth - item->width()) / 2, insertY + (rHeight - item->height()) / 2 ); // advance col/row index insertX += cWidth; if ( ++cIdx == nCols ) { cIdx = 0; rIdx++; insertX = 0; insertY += rHeight + 15; // 5 + 15*d->zoomFactor ? } } fullHeight = cIdx ? (insertY + rowHeight[ rIdx ] + 10) : insertY; for ( int i = 0; i < nCols; i++ ) fullWidth += colWidth[ i ]; delete [] colWidth; delete [] rowHeight; } else // viewContinuous is FALSE { PageViewItem * currentItem = d->items[ TQMAX( 0, (int)d->document->currentPage() ) ]; // setup varialbles for a 1(row) x N(columns) grid int nCols = KpdfSettings::viewColumns(), * colWidth = new int[ nCols ], cIdx = 0; fullHeight = viewportHeight; for ( int i = 0; i < nCols; i++ ) colWidth[ i ] = viewportWidth / nCols; // 1) find out maximum area extension for the pages for ( iIt = d->items.begin(); iIt != iEnd; ++iIt ) { PageViewItem * item = *iIt; if ( item == currentItem || (cIdx > 0 && cIdx < nCols) ) { // update internal page size (leaving a little margin in case of Fit* modes) updateItemSize( item, colWidth[ cIdx ] - 6, viewportHeight - 8 ); // find row's maximum height and column's max width if ( item->width() + 6 > colWidth[ cIdx ] ) colWidth[ cIdx ] = item->width() + 6; if ( item->height() + 8 > fullHeight ) fullHeight = item->height() + 8; cIdx++; } } // 2) hide all widgets except the displayable ones and dispose those int insertX = 0; cIdx = 0; for ( iIt = d->items.begin(); iIt != iEnd; ++iIt ) { PageViewItem * item = *iIt; if ( item == currentItem || (cIdx > 0 && cIdx < nCols) ) { // center widget inside 'cells' item->moveTo( insertX + (colWidth[ cIdx ] - item->width()) / 2, (fullHeight - item->height()) / 2 ); // advance col index insertX += colWidth[ cIdx ]; cIdx++; } else item->setGeometry( 0, 0, -1, -1 ); } for ( int i = 0; i < nCols; i++ ) fullWidth += colWidth[ i ]; delete [] colWidth; } // 3) reset dirty state d->dirtyLayout = false; // 4) update scrollview's contents size and recenter view bool wasUpdatesEnabled = viewport()->isUpdatesEnabled(); if ( fullWidth != contentsWidth() || fullHeight != contentsHeight() ) { // disable updates and resize the viewportContents if ( wasUpdatesEnabled ) viewport()->setUpdatesEnabled( false ); resizeContents( fullWidth, fullHeight ); // restore previous viewport if defined and updates enabled if ( wasUpdatesEnabled ) { const DocumentViewport & vp = d->document->viewport(); if ( vp.pageNumber >= 0 ) { int prevX = contentsX(), prevY = contentsY(); const TQRect & geometry = d->items[ vp.pageNumber ]->geometry(); double nX = vp.rePos.enabled ? vp.rePos.normalizedX : 0.5, nY = vp.rePos.enabled ? vp.rePos.normalizedY : 0.0; center( geometry.left() + ROUND( nX * (double)geometry.width() ), geometry.top() + ROUND( nY * (double)geometry.height() ) ); // center() usually moves the viewport, that requests pixmaps too. // if that doesn't happen we have to request them by hand if ( prevX == contentsX() && prevY == contentsY() ) slotRequestVisiblePixmaps(); } // or else go to center page else center( fullWidth / 2, 0 ); viewport()->setUpdatesEnabled( true ); } } // 5) update the whole viewport if updated enabled if ( wasUpdatesEnabled ) updateContents(); } void PageView::slotRequestVisiblePixmaps( int newLeft, int newTop ) { // if requests are blocked (because raised by an unwanted event), exit if ( d->blockPixmapsRequest || d->viewportMoveActive ) return; // precalc view limits for intersecting with page coords inside the lOOp bool isEvent = newLeft != -1 && newTop != -1 && !d->blockViewport; TQRect viewportRect( isEvent ? newLeft : contentsX(), isEvent ? newTop : contentsY(), visibleWidth(), visibleHeight() ); // some variables used to determine the viewport int nearPageNumber = -1; double viewportCenterX = (viewportRect.left() + viewportRect.right()) / 2.0, viewportCenterY = (viewportRect.top() + viewportRect.bottom()) / 2.0, focusedX = 0.5, focusedY = 0.0, minDistance = -1.0; // iterate over all items d->visibleItems.clear(); TQValueList< PixmapRequest * > requestedPixmaps; TQValueVector< PageViewItem * >::iterator iIt = d->items.begin(), iEnd = d->items.end(); for ( ; iIt != iEnd; ++iIt ) { PageViewItem * i = *iIt; // if the item doesn't intersect the viewport, skip it if ( !viewportRect.intersects( i->geometry() ) ) continue; // add the item to the 'visible list' d->visibleItems.push_back( i ); // if the item has not the right pixmap, add a request for it if ( !i->page()->hasPixmap( PAGEVIEW_ID, i->width(), i->height() ) ) { PixmapRequest * p = new PixmapRequest( PAGEVIEW_ID, i->pageNumber(), i->width(), i->height(), PAGEVIEW_PRIO, true, d->rotation ); requestedPixmaps.push_back( p ); } // look for the item closest to viewport center and the relative // position between the item and the viewport center if ( isEvent ) { const TQRect & geometry = i->geometry(); // compute distance between item center and viewport center double distance = hypot( (geometry.left() + geometry.right()) / 2 - viewportCenterX, (geometry.top() + geometry.bottom()) / 2 - viewportCenterY ); if ( distance >= minDistance && nearPageNumber != -1 ) continue; nearPageNumber = i->pageNumber(); minDistance = distance; if ( geometry.height() > 0 && geometry.width() > 0 ) { focusedX = ( viewportCenterX - (double)geometry.left() ) / (double)geometry.width(); focusedY = ( viewportCenterY - (double)geometry.top() ) / (double)geometry.height(); } } } // if preloading is enabled, add the pages before and after in preloading if ( !d->visibleItems.isEmpty() && KpdfSettings::memoryLevel() != KpdfSettings::EnumMemoryLevel::Low && KpdfSettings::enableThreading() ) { // as the requests are done in the order as they appear in the list, // request first the next page and then the previous // add the page after the 'visible series' in preload int tailRequest = d->visibleItems.last()->pageNumber() + 1; if ( tailRequest < (int)d->items.count() ) { PageViewItem * i = d->items[ tailRequest ]; // request the pixmap if not already present if ( !i->page()->hasPixmap( PAGEVIEW_ID, i->width(), i->height() ) && i->width() > 0 ) requestedPixmaps.push_back( new PixmapRequest( PAGEVIEW_ID, i->pageNumber(), i->width(), i->height(), PAGEVIEW_PRELOAD_PRIO, true, d->rotation ) ); } // add the page before the 'visible series' in preload int headRequest = d->visibleItems.first()->pageNumber() - 1; if ( headRequest >= 0 ) { PageViewItem * i = d->items[ headRequest ]; // request the pixmap if not already present if ( !i->page()->hasPixmap( PAGEVIEW_ID, i->width(), i->height() ) && i->width() > 0 ) requestedPixmaps.push_back( new PixmapRequest( PAGEVIEW_ID, i->pageNumber(), i->width(), i->height(), PAGEVIEW_PRELOAD_PRIO, true, d->rotation ) ); } } // send requests to the document if ( !requestedPixmaps.isEmpty() ) d->document->requestPixmaps( requestedPixmaps ); // if this functions was invoked by viewport events, send update to document if ( isEvent && nearPageNumber != -1 ) { // determine the document viewport DocumentViewport newViewport( nearPageNumber ); newViewport.rePos.enabled = true; newViewport.rePos.normalizedX = focusedX; newViewport.rePos.normalizedY = focusedY; // set the viewport to other observers d->document->setViewport( newViewport , PAGEVIEW_ID); } } void PageView::slotMoveViewport() { // converge to viewportMoveDest in 1 second int diffTime = d->viewportMoveTime.elapsed(); if ( diffTime >= 667 || !d->viewportMoveActive ) { center( d->viewportMoveDest.x(), d->viewportMoveDest.y() ); d->viewportMoveTimer->stop(); d->viewportMoveActive = false; slotRequestVisiblePixmaps(); verticalScrollBar()->setEnabled( true ); horizontalScrollBar()->setEnabled( true ); return; } // move the viewport smoothly (kmplot: p(x)=x+x*(1-x)*(1-x)) float convergeSpeed = (float)diffTime / 667.0, x = ((float)visibleWidth() / 2.0) + contentsX(), y = ((float)visibleHeight() / 2.0) + contentsY(), diffX = (float)d->viewportMoveDest.x() - x, diffY = (float)d->viewportMoveDest.y() - y; convergeSpeed *= convergeSpeed * (1.4 - convergeSpeed); center( (int)(x + diffX * convergeSpeed), (int)(y + diffY * convergeSpeed ) ); } void PageView::slotAutoScoll() { // the first time create the timer if ( !d->autoScrollTimer ) { d->autoScrollTimer = new TQTimer( this ); connect( d->autoScrollTimer, TQT_SIGNAL( timeout() ), this, TQT_SLOT( slotAutoScoll() ) ); } // if scrollIncrement is zero, stop the timer if ( !d->scrollIncrement ) { d->autoScrollTimer->stop(); return; } // compute delay between timer ticks and scroll amount per tick int index = abs( d->scrollIncrement ) - 1; // 0..9 const int scrollDelay[10] = { 200, 100, 50, 30, 20, 30, 25, 20, 30, 20 }; const int scrollOffset[10] = { 1, 1, 1, 1, 1, 2, 2, 2, 4, 4 }; d->autoScrollTimer->changeInterval( scrollDelay[ index ] ); scrollBy( 0, d->scrollIncrement > 0 ? scrollOffset[ index ] : -scrollOffset[ index ] ); } void PageView::slotDragScroll() { scrollBy(d->dragScrollVector.x(), d->dragScrollVector.y()); TQPoint p = viewportToContents( mapFromGlobal( TQCursor::pos() ) ); selectionEndPoint( p.x(), p.y() ); } void PageView::findAheadStop() { d->typeAheadActive = false; d->typeAheadString = ""; d->messageWindow->display( i18n("Find stopped."), PageViewMessage::Find, 1000 ); // it is needed to grab the keyboard becase people may have Space assigned to a // accel and without grabbing the keyboard you can not vim-search for space // because it activates the accel releaseKeyboard(); } void PageView::slotShowWelcome() { // show initial welcome text d->messageWindow->display( i18n( "Welcome" ), PageViewMessage::Info, 2000 ); } void PageView::slotZoom() { setFocus(); updateZoom( ZoomFixed ); } void PageView::slotZoomIn() { updateZoom( ZoomIn ); } void PageView::slotZoomOut() { updateZoom( ZoomOut ); } void PageView::slotFitToWidthToggled( bool on ) { if ( on ) updateZoom( ZoomFitWidth ); } void PageView::slotFitToPageToggled( bool on ) { if ( on ) updateZoom( ZoomFitPage ); } void PageView::slotFitToTextToggled( bool on ) { if ( on ) updateZoom( ZoomFitText ); } void PageView::slotRotateRight() { d->rotation = ( d->rotation + 90 ) % 360; TQValueVector< PageViewItem * >::iterator iIt = d->items.begin(), iEnd = d->items.end(); for ( ; iIt != iEnd; ++iIt ) { int r = const_cast((*iIt)->page())->rotation(); r = ( r + 90 ) % 360; const_cast((*iIt)->page())->rotate90degrees(); } // be sure to block updates to document's viewport bool prevState = d->blockViewport; d->blockViewport = true; slotRelayoutPages(); d->blockViewport = prevState; // request pixmaps slotRequestVisiblePixmaps(); } void PageView::slotRotateLeft() { d->rotation -= 90; if ( d->rotation < 0 ) d->rotation += 360; TQValueVector< PageViewItem * >::iterator iIt = d->items.begin(), iEnd = d->items.end(); for ( ; iIt != iEnd; ++iIt ) { int r = const_cast((*iIt)->page())->rotation(); r -= 90; if ( r < 0 ) r += 360; const_cast((*iIt)->page())->rotate90degrees(); } // be sure to block updates to document's viewport bool prevState = d->blockViewport; d->blockViewport = true; slotRelayoutPages(); d->blockViewport = prevState; // request pixmaps slotRequestVisiblePixmaps(); } void PageView::slotTwoPagesToggled( bool on ) { uint newColumns = on ? 2 : 1; if ( KpdfSettings::viewColumns() != newColumns ) { KpdfSettings::setViewColumns( newColumns ); KpdfSettings::writeConfig(); if ( d->document->pages() > 0 ) slotRelayoutPages(); } } void PageView::slotContinuousToggled( bool on ) { if ( KpdfSettings::viewContinuous() != on ) { KpdfSettings::setViewContinuous( on ); KpdfSettings::writeConfig(); if ( d->document->pages() > 0 ) slotRelayoutPages(); } } void PageView::slotSetMouseNormal() { d->mouseMode = MouseNormal; d->messageWindow->hide(); } void PageView::slotSetMouseZoom() { d->mouseMode = MouseZoom; d->messageWindow->display( i18n( "Select zooming area. Right-click to zoom out." ), PageViewMessage::Info, -1 ); } void PageView::slotSetMouseSelect() { d->mouseMode = MouseSelect; d->messageWindow->display( i18n( "Draw a rectangle around the text/graphics to copy." ), PageViewMessage::Info, -1 ); } void PageView::slotSetMouseDraw() { d->mouseMode = MouseEdit; d->aMouseEdit->setChecked( true ); d->messageWindow->hide(); } void PageView::slotScrollUp() { if ( d->scrollIncrement < -9 ) return; d->scrollIncrement--; slotAutoScoll(); setFocus(); } void PageView::slotScrollDown() { if ( d->scrollIncrement > 9 ) return; d->scrollIncrement++; slotAutoScoll(); setFocus(); } //END private SLOTS #include "pageview.moc"