1 // REFACTOR: Clean up bits of this file
4 Copyright (c) 2003-2007 Clarence Dang <dang@kde.org>
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions
11 1. Redistributions of source code must retain the above copyright
12 notice, this list of conditions and the following disclaimer.
13 2. Redistributions in binary form must reproduce the above copyright
14 notice, this list of conditions and the following disclaimer in the
15 documentation and/or other materials provided with the distribution.
17 THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 #include <kpMainWindow.h>
31 #include <kpMainWindowPrivate.h>
33 #include <qdatetime.h>
37 #include <kapplication.h>
40 #include <kselectaction.h>
41 #include <kstandardaction.h>
42 #include <ktoggleaction.h>
43 #include <kactioncollection.h>
45 #include <kpAbstractScrollAreaUtils.h>
47 #include <kpDocument.h>
48 #include <kpThumbnail.h>
50 #include <kpToolToolBar.h>
51 #include <kpUnzoomedThumbnailView.h>
52 #include <kpViewManager.h>
53 #include <kpViewScrollableContainer.h>
54 #include <kpWidgetMapper.h>
55 #include <kpZoomedView.h>
56 #include <kpZoomedThumbnailView.h>
59 static int ZoomLevelFromString (const QString
&stringIn
)
61 #if DEBUG_KP_MAIN_WINDOW
62 kDebug () << "kpMainWindow_View.cpp:ZoomLevelFromString(" << stringIn
<< ")";
65 // Remove any non-digits kdelibs sometimes adds behind our back :( e.g.:
67 // 1. kdelibs adds accelerators to actions' text directly
68 // 2. ',' is automatically added to change "1000%" to "1,000%"
69 QString string
= stringIn
;
70 string
.remove (QRegExp ("[^0-9]"));
71 #if DEBUG_KP_MAIN_WINDOW
72 kDebug () << "\twithout non-digits='" << string
<< "'";
75 // Convert zoom level to number.
77 int zoomLevel
= string
.toInt (&ok
);
78 #if DEBUG_KP_MAIN_WINDOW
79 kDebug () << "\tzoomLevel=" << zoomLevel
;
82 if (!ok
|| zoomLevel
< kpView::MinZoomLevel
|| zoomLevel
> kpView::MaxZoomLevel
)
88 static QString
ZoomLevelToString (int zoomLevel
)
90 return i18n ("%1%", zoomLevel
);
95 void kpMainWindow::setupViewMenuZoomActions ()
97 KActionCollection
*ac
= actionCollection ();
100 d
->actionActualSize
= KStandardAction::actualSize (this, SLOT (slotActualSize ()), ac
);
101 d
->actionFitToPage
= KStandardAction::fitToPage (this, SLOT (slotFitToPage ()), ac
);
102 d
->actionFitToWidth
= KStandardAction::fitToWidth (this, SLOT (slotFitToWidth ()), ac
);
103 d
->actionFitToHeight
= KStandardAction::fitToHeight (this, SLOT (slotFitToHeight ()), ac
);
106 d
->actionZoomIn
= KStandardAction::zoomIn (this, SLOT (slotZoomIn ()), ac
);
107 d
->actionZoomOut
= KStandardAction::zoomOut (this, SLOT (slotZoomOut ()), ac
);
110 d
->actionZoom
= ac
->add
<KSelectAction
> ("view_zoom_to");
111 d
->actionZoom
->setText (i18n ("&Zoom"));
112 connect (d
->actionZoom
, SIGNAL (triggered (QAction
*)), SLOT (slotZoom ()));
113 d
->actionZoom
->setEditable (true);
115 // create the zoom list for the 1st call to zoomTo() below
116 d
->zoomList
.append (10); d
->zoomList
.append (25); d
->zoomList
.append (33);
117 d
->zoomList
.append (50); d
->zoomList
.append (67); d
->zoomList
.append (75);
118 d
->zoomList
.append (100);
119 d
->zoomList
.append (200); d
->zoomList
.append (300);
120 d
->zoomList
.append (400); d
->zoomList
.append (600); d
->zoomList
.append (800);
121 d
->zoomList
.append (1000); d
->zoomList
.append (1200); d
->zoomList
.append (1600);
125 void kpMainWindow::enableViewMenuZoomDocumentActions (bool enable
)
127 d
->actionActualSize
->setEnabled (enable
);
128 /*d->actionFitToPage->setEnabled (enable);
129 d->actionFitToWidth->setEnabled (enable);
130 d->actionFitToHeight->setEnabled (enable);*/
132 d
->actionZoomIn
->setEnabled (enable
);
133 d
->actionZoomOut
->setEnabled (enable
);
135 // COMPAT: This seems to stay enabled no matter what.
136 // Looks like a KSelectAction bug.
137 d
->actionZoom
->setEnabled (enable
);
140 // TODO: for the time being, assume that we start at zoom 100%
143 // This function is only called when a new document is created
144 // or an existing document is closed. So the following will
145 // always be correct:
152 void kpMainWindow::sendZoomListToActionZoom ()
156 const QList
<int>::ConstIterator
zoomListEnd (d
->zoomList
.end ());
157 for (QList
<int>::ConstIterator it
= d
->zoomList
.constBegin ();
161 items
<< ::ZoomLevelToString (*it
);
164 // Work around a KDE bug - KSelectAction::setItems() enables the action.
165 // David Faure said it won't be fixed because it's a feature used by
166 // KRecentFilesAction.
167 bool e
= d
->actionZoom
->isEnabled ();
168 d
->actionZoom
->setItems (items
);
169 if (e
!= d
->actionZoom
->isEnabled ())
170 d
->actionZoom
->setEnabled (e
);
175 void kpMainWindow::zoomToPre (int zoomLevel
)
177 // We're called quite early in the init process and/or when there might
178 // not be a document or a view so we have a lot of "if (ptr)" guards.
180 #if DEBUG_KP_MAIN_WINDOW
181 kDebug () << "kpMainWindow::zoomToPre(" << zoomLevel
<< ")";
184 zoomLevel
= qBound (kpView::MinZoomLevel
, zoomLevel
, kpView::MaxZoomLevel
);
186 // mute point since the thumbnail suffers from this too
188 else if (d
->mainView
&& d
->mainView
->zoomLevelX () % 100 == 0 && zoomLevel
% 100)
190 if (KMessageBox::warningContinueCancel (this,
191 i18n ("Setting the zoom level to a value that is not a multiple of 100% "
192 "results in imprecise editing and redraw glitches.\n"
193 "Do you really want to set to zoom level to %1%?",
195 QString()/*caption*/,
196 i18n ("Set Zoom Level to %1%", zoomLevel
),
197 "DoNotAskAgain_ZoomLevelNotMultipleOf100") != KMessageBox::Continue
)
199 zoomLevel
= d
->mainView
->zoomLevelX ();
205 QList
<int>::Iterator it
= d
->zoomList
.begin ();
207 while (index
< (int) d
->zoomList
.count () && zoomLevel
> *it
)
210 if (zoomLevel
!= *it
)
211 d
->zoomList
.insert (it
, zoomLevel
);
213 // OPT: We get called twice on startup. sendZoomListToActionZoom() is very slow.
214 sendZoomListToActionZoom ();
216 #if DEBUG_KP_MAIN_WINDOW
217 kDebug () << "\tsetCurrentItem(" << index
<< ")";
219 d
->actionZoom
->setCurrentItem (index
);
220 #if DEBUG_KP_MAIN_WINDOW
221 kDebug () << "\tcurrentItem="
222 << d
->actionZoom
->currentItem ()
224 << d
->actionZoom
->action (d
->actionZoom
->currentItem ())
226 << d
->actionZoom
->selectableActionGroup ()->checkedAction ()
231 if (viewMenuDocumentActionsEnabled ())
233 d
->actionActualSize
->setEnabled (zoomLevel
!= 100);
235 d
->actionZoomIn
->setEnabled (d
->actionZoom
->currentItem () < (int) d
->zoomList
.count () - 1);
236 d
->actionZoomOut
->setEnabled (d
->actionZoom
->currentItem () > 0);
242 // Ordinary flicker is better than the whole view moving (try
243 // zooming in from 100%, without this code).
245 // TODO: Use a more flexible scrollarea implementation to
246 // eliminate this flicker.
248 // Later: The view takes a lot longer to resize in Qt4 so this
249 // blanking is seen for too long and is ultra-annoying.
250 // So I've disabled the blanking.
251 //d->mainView->setPaintBlank ();
254 // TODO: Is this actually needed?
256 d
->viewManager
->setQueueUpdates ();
260 d
->scrollView
->setUpdatesEnabled (false);
265 void kpMainWindow::zoomToPost ()
267 #if DEBUG_KP_MAIN_WINDOW && 1
268 kDebug () << "kpMainWindow::zoomToPost()";
273 actionShowGridUpdate ();
274 updateMainViewGrid ();
276 // Since Zoom Level KSelectAction on ToolBar grabs focus after changing
277 // Zoom, switch back to the Main View.
278 // TODO: back to the last view
279 d
->mainView
->setFocus ();
284 // The view magnified and moved beneath the cursor
286 tool ()->somethingBelowTheCursorChanged ();
291 // TODO: setUpdatesEnabled() should really return to old value
292 // - not neccessarily "true"
293 d
->scrollView
->setUpdatesEnabled (true);
296 if (d
->viewManager
&& d
->viewManager
->queueUpdates ()/*just in case*/)
297 d
->viewManager
->restoreQueueUpdates ();
301 //d->mainView->restorePaintBlank ();
305 setStatusBarZoom (d
->mainView
? d
->mainView
->zoomLevelX () : 0);
307 #if DEBUG_KP_MAIN_WINDOW && 1
308 kDebug () << "kpMainWindow::zoomToPost() done";
314 void kpMainWindow::zoomTo (int zoomLevel
, bool centerUnderCursor
)
316 zoomToPre (zoomLevel
);
319 if (d
->scrollView
&& d
->mainView
)
321 #if DEBUG_KP_MAIN_WINDOW && 1
322 kDebug () << "\tscrollView contentsX=" << d
->scrollView
->contentsX ()
323 << " contentsY=" << d
->scrollView
->contentsY ()
324 << " contentsWidth=" << d
->scrollView
->contentsWidth ()
325 << " contentsHeight=" << d
->scrollView
->contentsHeight ()
326 << " visibleWidth=" << d
->scrollView
->visibleWidth ()
327 << " visibleHeight=" << d
->scrollView
->visibleHeight ()
328 << " oldZoomX=" << d
->mainView
->zoomLevelX ()
329 << " oldZoomY=" << d
->mainView
->zoomLevelY ()
330 << " newZoom=" << zoomLevel
331 << " mainViewX=" << d
->scrollView
->childX (d
->mainView
)
332 << " mainViewY=" << d
->scrollView
->childY (d
->mainView
)
336 // TODO: when changing from no scrollbars to scrollbars, Qt lies about
337 // visibleWidth() & visibleHeight() (doesn't take into account the
338 // space taken by the would-be scrollbars) until it updates the
339 // scrollview; hence the centering is off by about 5-10 pixels.
341 // TODO: Use visibleRect() for greater accuracy?
342 // Or use kpAbstractScrollAreaUtils::EstimateUsableArea()
343 // instead of Q3ScrollView::visible{Width,Height}(), as
348 bool targetDocAvail
= false;
349 double targetDocX
= -1, targetDocY
= -1;
351 if (centerUnderCursor
&&
352 d
->viewManager
&& d
->viewManager
->viewUnderCursor ())
354 kpView
*const vuc
= d
->viewManager
->viewUnderCursor ();
355 QPoint viewPoint
= vuc
->mouseViewPoint ();
357 // vuc->transformViewToDoc() returns QPoint which only has int
358 // accuracy so we do X and Y manually.
359 targetDocX
= vuc
->transformViewToDocX (viewPoint
.x ());
360 targetDocY
= vuc
->transformViewToDocY (viewPoint
.y ());
361 targetDocAvail
= true;
363 if (vuc
!= d
->mainView
)
364 viewPoint
= vuc
->transformViewToOtherView (viewPoint
, d
->mainView
);
366 viewX
= viewPoint
.x ();
367 viewY
= viewPoint
.y ();
371 viewX
= d
->scrollView
->contentsX () +
372 qMin (d
->mainView
->width (),
373 d
->scrollView
->visibleWidth ()) / 2;
374 viewY
= d
->scrollView
->contentsY () +
375 qMin (d
->mainView
->height (),
376 d
->scrollView
->visibleHeight ()) / 2;
380 int newCenterX
= viewX
* zoomLevel
/ d
->mainView
->zoomLevelX ();
381 int newCenterY
= viewY
* zoomLevel
/ d
->mainView
->zoomLevelY ();
384 d
->mainView
->setZoomLevel (zoomLevel
, zoomLevel
);
386 #if DEBUG_KP_MAIN_WINDOW && 1
387 kDebug () << "\tvisibleWidth=" << d
->scrollView
->visibleWidth ()
388 << " visibleHeight=" << d
->scrollView
->visibleHeight ()
390 kDebug () << "\tnewCenterX=" << newCenterX
391 << " newCenterY=" << newCenterY
<< endl
;
394 d
->scrollView
->center (newCenterX
, newCenterY
);
397 if (centerUnderCursor
&&
399 d
->viewManager
&& d
->viewManager
->viewUnderCursor ())
401 // Move the mouse cursor so that it is still above the same
402 // document pixel as before the zoom.
404 kpView
*const vuc
= d
->viewManager
->viewUnderCursor ();
406 #if DEBUG_KP_MAIN_WINDOW
407 kDebug () << "\tcenterUnderCursor: reposition cursor; viewUnderCursor="
408 << vuc
->objectName () << endl
;
411 const double viewX
= vuc
->transformDocToViewX (targetDocX
);
412 const double viewY
= vuc
->transformDocToViewY (targetDocY
);
413 // Rounding error from zooming in and out :(
414 // TODO: do everything in terms of tool doc points in type "double".
415 const QPoint
viewPoint ((int) viewX
, (int) viewY
);
416 #if DEBUG_KP_MAIN_WINDOW
417 kDebug () << "\t\tdoc: (" << targetDocX
<< "," << targetDocY
<< ")"
418 << " viewUnderCursor: (" << viewX
<< "," << viewY
<< ")"
422 if (vuc
->visibleRegion ().contains (viewPoint
))
424 const QPoint globalPoint
=
425 kpWidgetMapper::toGlobal (vuc
, viewPoint
);
426 #if DEBUG_KP_MAIN_WINDOW
427 kDebug () << "\t\tglobalPoint=" << globalPoint
;
430 // TODO: Determine some sane cursor flashing indication -
431 // cursor movement is convenient but not conventional.
433 // Major problem: if using QApplication::setOverrideCursor()
434 // and in some stage of flash and window quits.
436 // Or if using kpView::setCursor() and change tool.
437 QCursor::setPos (globalPoint
);
439 // e.g. Zoom to 200%, scroll mainView to bottom-right.
440 // Unzoomed Thumbnail shows top-left portion of bottom-right of
443 // Aim cursor at bottom-right of thumbnail and zoom out with
446 // If mainView is now small enough to largely not need scrollbars,
447 // Unzoomed Thumbnail scrolls to show _top-left_ portion
448 // _of top-left_ of mainView.
450 // Unzoomed Thumbnail no longer contains the point we zoomed out
454 #if DEBUG_KP_MAIN_WINDOW
455 kDebug () << "\t\twon't move cursor - would get outside view"
459 // TODO: Sane cursor flashing indication that indicates
460 // that the normal cursor movement didn't happen.
464 #if DEBUG_KP_MAIN_WINDOW && 1
465 kDebug () << "\t\tcheck (contentsX=" << d
->scrollView
->contentsX ()
466 << ",contentsY=" << d
->scrollView
->contentsY ()
476 void kpMainWindow::zoomToRect (const QRect
&normalizedDocRect
,
477 bool accountForGrips
,
478 bool careAboutWidth
, bool careAboutHeight
)
480 #if DEBUG_KP_MAIN_WINDOW
481 kDebug () << "kpMainWindow::zoomToRect(normalizedDocRect="
483 << ",accountForGrips=" << accountForGrips
484 << ",careAboutWidth=" << careAboutWidth
485 << ",careAboutHeight=" << careAboutHeight
488 // You can't care about nothing.
489 Q_ASSERT (careAboutWidth
|| careAboutHeight
);
491 // The size of the scroll view minus the current or future scrollbars.
492 const QSize usableScrollArea
=
493 kpAbstractScrollAreaUtils::EstimateUsableArea (d
->scrollView
);
494 #if DEBUG_KP_MAIN_WINDOW
495 kDebug () << "size=" << d
->scrollView
->size ()
496 << "usableSize=" << usableScrollArea
;
498 // Handle rounding error, mis-estimating the scroll view size and
499 // cosmic rays. We do this because we really don't want unnecessary
500 // scrollbars. This seems to need to be at least 2 for slotFitToWidth()
503 // TODO: I might have fixed this but check later.
506 // The grip and slack are in view coordinates but are never zoomed.
507 const int viewWidth
=
508 usableScrollArea
.width () -
509 (accountForGrips
? kpGrip::Size
: 0) -
511 const int viewHeight
=
512 usableScrollArea
.height () -
513 (accountForGrips
? kpGrip::Size
: 0) -
516 // We want the selected document rectangle to fill the scroll view.
518 // The integer arithmetic rounds down, rather than to the nearest zoom
519 // level, as rounding down guarantees that the view, at the zoom level,
520 // will fit inside <viewWidth> x <viewHeight>.
523 qMax (1, viewWidth
* 100 / normalizedDocRect
.width ()) :
527 qMax (1, viewHeight
* 100 / normalizedDocRect
.height ()) :
530 // Since kpView only supports identical horizontal and vertical zooms,
531 // choose the one that will show the greatest amount of document
533 const int zoomLevel
= qMin (zoomX
, zoomY
);
535 #if DEBUG_KP_MAIN_WINDOW
536 kDebug () << "\tzoomX=" << zoomX
537 << " zoomY=" << zoomY
538 << " -> zoomLevel=" << zoomLevel
<< endl
;
541 zoomToPre (zoomLevel
);
543 d
->mainView
->setZoomLevel (zoomLevel
, zoomLevel
);
545 const QPoint viewPoint
=
546 d
->mainView
->transformDocToView (normalizedDocRect
.topLeft ());
548 d
->scrollView
->setContentsPos (viewPoint
.x (), viewPoint
.y ());
555 void kpMainWindow::slotActualSize ()
561 void kpMainWindow::slotFitToPage ()
564 d
->document
->rect (),
565 true/*account for grips*/,
566 true/*care about width*/, true/*care about height*/);
570 void kpMainWindow::slotFitToWidth ()
572 const QRect
docRect (
574 (int) d
->mainView
->transformViewToDocY (d
->scrollView
->contentsY ())/*maintain y*/,
575 d
->document
->width (),
576 1/*don't care about height*/);
579 true/*account for grips*/,
580 true/*care about width*/, false/*don't care about height*/);
584 void kpMainWindow::slotFitToHeight ()
586 const QRect
docRect (
587 (int) d
->mainView
->transformViewToDocX (d
->scrollView
->contentsX ())/*maintain x*/,
589 1/*don't care about width*/,
590 d
->document
->height ());
593 true/*account for grips*/,
594 false/*don't care about width*/, true/*care about height*/);
599 void kpMainWindow::zoomIn (bool centerUnderCursor
)
601 #if DEBUG_KP_MAIN_WINDOW
602 kDebug () << "kpMainWindow::zoomIn(centerUnderCursor="
603 << centerUnderCursor
<< ") currentItem="
604 << d
->actionZoom
->currentItem ()
607 const int targetItem
= d
->actionZoom
->currentItem () + 1;
609 if (targetItem
>= (int) d
->zoomList
.count ())
612 d
->actionZoom
->setCurrentItem (targetItem
);
614 #if DEBUG_KP_MAIN_WINDOW
615 kDebug () << "\tnew currentItem=" << d
->actionZoom
->currentItem ();
618 zoomAccordingToZoomAction (centerUnderCursor
);
622 void kpMainWindow::zoomOut (bool centerUnderCursor
)
624 #if DEBUG_KP_MAIN_WINDOW
625 kDebug () << "kpMainWindow::zoomOut(centerUnderCursor="
626 << centerUnderCursor
<< ") currentItem="
627 << d
->actionZoom
->currentItem ()
630 const int targetItem
= d
->actionZoom
->currentItem () - 1;
635 d
->actionZoom
->setCurrentItem (targetItem
);
637 #if DEBUG_KP_MAIN_WINDOW
638 kDebug () << "\tnew currentItem=" << d
->actionZoom
->currentItem ();
641 zoomAccordingToZoomAction (centerUnderCursor
);
646 void kpMainWindow::slotZoomIn ()
648 #if DEBUG_KP_MAIN_WINDOW
649 kDebug () << "kpMainWindow::slotZoomIn ()";
652 zoomIn (false/*don't center under cursor*/);
656 void kpMainWindow::slotZoomOut ()
658 #if DEBUG_KP_MAIN_WINDOW
659 kDebug () << "kpMainWindow::slotZoomOut ()";
662 zoomOut (false/*don't center under cursor*/);
667 void kpMainWindow::zoomAccordingToZoomAction (bool centerUnderCursor
)
669 #if DEBUG_KP_MAIN_WINDOW
670 kDebug () << "kpMainWindow::zoomAccordingToZoomAction(centerUnderCursor="
672 << ") currentItem=" << d
->actionZoom
->currentItem ()
673 << " currentText=" << d
->actionZoom
->currentText ()
677 // This might be a new zoom level the user has typed in.
678 zoomTo (::ZoomLevelFromString (d
->actionZoom
->currentText ()),
683 void kpMainWindow::slotZoom ()
685 #if DEBUG_KP_MAIN_WINDOW
686 kDebug () << "kpMainWindow::slotZoom () index=" << d
->actionZoom
->currentItem ()
687 << " text='" << d
->actionZoom
->currentText () << "'" << endl
;
690 zoomAccordingToZoomAction (false/*don't center under cursor*/);