there is no moc file generated for this class
[kdegraphics.git] / kolourpaint / mainWindow / kpMainWindow_View_Zoom.cpp
blob0b8a78471f4253b74822d1fddc4bdec831b975c8
1 // REFACTOR: Clean up bits of this file
3 /*
4 Copyright (c) 2003-2007 Clarence Dang <dang@kde.org>
5 All rights reserved.
7 Redistribution and use in source and binary forms, with or without
8 modification, are permitted provided that the following conditions
9 are met:
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>
34 #include <qpainter.h>
35 #include <qtimer.h>
37 #include <kapplication.h>
38 #include <kdebug.h>
39 #include <klocale.h>
40 #include <kselectaction.h>
41 #include <kstandardaction.h>
42 #include <ktoggleaction.h>
43 #include <kactioncollection.h>
45 #include <kpAbstractScrollAreaUtils.h>
46 #include <kpDefs.h>
47 #include <kpDocument.h>
48 #include <kpThumbnail.h>
49 #include <kpTool.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 << ")";
63 #endif
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 << "'";
73 #endif
75 // Convert zoom level to number.
76 bool ok = false;
77 int zoomLevel = string.toInt (&ok);
78 #if DEBUG_KP_MAIN_WINDOW
79 kDebug () << "\tzoomLevel=" << zoomLevel;
80 #endif
82 if (!ok || zoomLevel < kpView::MinZoomLevel || zoomLevel > kpView::MaxZoomLevel)
83 return 0; // error
84 else
85 return zoomLevel;
88 static QString ZoomLevelToString (int zoomLevel)
90 return i18n ("%1%", zoomLevel);
94 // private
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);
124 // private
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%
141 // with no grid
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:
147 zoomTo (100);
151 // private
152 void kpMainWindow::sendZoomListToActionZoom ()
154 QStringList items;
156 const QList <int>::ConstIterator zoomListEnd (d->zoomList.end ());
157 for (QList <int>::ConstIterator it = d->zoomList.constBegin ();
158 it != zoomListEnd;
159 it++)
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);
174 // private
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 << ")";
182 #endif
184 zoomLevel = qBound (kpView::MinZoomLevel, zoomLevel, kpView::MaxZoomLevel);
186 // mute point since the thumbnail suffers from this too
187 #if 0
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%?",
194 zoomLevel),
195 QString()/*caption*/,
196 i18n ("Set Zoom Level to %1%", zoomLevel),
197 "DoNotAskAgain_ZoomLevelNotMultipleOf100") != KMessageBox::Continue)
199 zoomLevel = d->mainView->zoomLevelX ();
202 #endif
204 int index = 0;
205 QList <int>::Iterator it = d->zoomList.begin ();
207 while (index < (int) d->zoomList.count () && zoomLevel > *it)
208 it++, index++;
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 << ")";
218 #endif
219 d->actionZoom->setCurrentItem (index);
220 #if DEBUG_KP_MAIN_WINDOW
221 kDebug () << "\tcurrentItem="
222 << d->actionZoom->currentItem ()
223 << " action="
224 << d->actionZoom->action (d->actionZoom->currentItem ())
225 << " checkedAction"
226 << d->actionZoom->selectableActionGroup ()->checkedAction ()
227 << endl;;
228 #endif
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);
240 if (d->mainView)
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?
255 if (d->viewManager)
256 d->viewManager->setQueueUpdates ();
258 if (d->scrollView)
260 d->scrollView->setUpdatesEnabled (false);
264 // private
265 void kpMainWindow::zoomToPost ()
267 #if DEBUG_KP_MAIN_WINDOW && 1
268 kDebug () << "kpMainWindow::zoomToPost()";
269 #endif
271 if (d->mainView)
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
285 if (tool ())
286 tool ()->somethingBelowTheCursorChanged ();
289 if (d->scrollView)
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 ();
299 if (d->mainView)
301 //d->mainView->restorePaintBlank ();
305 setStatusBarZoom (d->mainView ? d->mainView->zoomLevelX () : 0);
307 #if DEBUG_KP_MAIN_WINDOW && 1
308 kDebug () << "kpMainWindow::zoomToPost() done";
309 #endif
313 // private
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)
333 << endl;
334 #endif
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
344 // per zoomToRect()?
346 int viewX, viewY;
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 ();
369 else
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 ();
383 // Do the zoom.
384 d->mainView->setZoomLevel (zoomLevel, zoomLevel);
386 #if DEBUG_KP_MAIN_WINDOW && 1
387 kDebug () << "\tvisibleWidth=" << d->scrollView->visibleWidth ()
388 << " visibleHeight=" << d->scrollView->visibleHeight ()
389 << endl;
390 kDebug () << "\tnewCenterX=" << newCenterX
391 << " newCenterY=" << newCenterY << endl;
392 #endif
394 d->scrollView->center (newCenterX, newCenterY);
397 if (centerUnderCursor &&
398 targetDocAvail &&
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;
409 #endif
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 << ")"
419 << endl;
420 #endif
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;
428 #endif
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
441 // mainView.
443 // Aim cursor at bottom-right of thumbnail and zoom out with
444 // CTRL+Wheel.
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
451 // on top of.
452 else
454 #if DEBUG_KP_MAIN_WINDOW
455 kDebug () << "\t\twon't move cursor - would get outside view"
456 << endl;
457 #endif
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 ()
467 << ")" << endl;
468 #endif
472 zoomToPost ();
475 // private
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="
482 << normalizedDocRect
483 << ",accountForGrips=" << accountForGrips
484 << ",careAboutWidth=" << careAboutWidth
485 << ",careAboutHeight=" << careAboutHeight
486 << ")";
487 #endif
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;
497 #endif
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()
501 // and friends.
502 // least 2.
503 // TODO: I might have fixed this but check later.
504 const int slack = 5;
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) -
510 slack;
511 const int viewHeight =
512 usableScrollArea.height () -
513 (accountForGrips ? kpGrip::Size : 0) -
514 slack;
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>.
521 const int zoomX =
522 careAboutWidth ?
523 qMax (1, viewWidth * 100 / normalizedDocRect.width ()) :
524 INT_MAX;
525 const int zoomY =
526 careAboutHeight ?
527 qMax (1, viewHeight * 100 / normalizedDocRect.height ()) :
528 INT_MAX;
530 // Since kpView only supports identical horizontal and vertical zooms,
531 // choose the one that will show the greatest amount of document
532 // content.
533 const int zoomLevel = qMin (zoomX, zoomY);
535 #if DEBUG_KP_MAIN_WINDOW
536 kDebug () << "\tzoomX=" << zoomX
537 << " zoomY=" << zoomY
538 << " -> zoomLevel=" << zoomLevel << endl;
539 #endif
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 ());
550 zoomToPost ();
554 // public slot
555 void kpMainWindow::slotActualSize ()
557 zoomTo (100);
560 // public slot
561 void kpMainWindow::slotFitToPage ()
563 zoomToRect (
564 d->document->rect (),
565 true/*account for grips*/,
566 true/*care about width*/, true/*care about height*/);
569 // public slot
570 void kpMainWindow::slotFitToWidth ()
572 const QRect docRect (
573 0/*x*/,
574 (int) d->mainView->transformViewToDocY (d->scrollView->contentsY ())/*maintain y*/,
575 d->document->width (),
576 1/*don't care about height*/);
577 zoomToRect (
578 docRect,
579 true/*account for grips*/,
580 true/*care about width*/, false/*don't care about height*/);
583 // public slot
584 void kpMainWindow::slotFitToHeight ()
586 const QRect docRect (
587 (int) d->mainView->transformViewToDocX (d->scrollView->contentsX ())/*maintain x*/,
588 0/*y*/,
589 1/*don't care about width*/,
590 d->document->height ());
591 zoomToRect (
592 docRect,
593 true/*account for grips*/,
594 false/*don't care about width*/, true/*care about height*/);
598 // public
599 void kpMainWindow::zoomIn (bool centerUnderCursor)
601 #if DEBUG_KP_MAIN_WINDOW
602 kDebug () << "kpMainWindow::zoomIn(centerUnderCursor="
603 << centerUnderCursor << ") currentItem="
604 << d->actionZoom->currentItem ()
605 << endl;
606 #endif
607 const int targetItem = d->actionZoom->currentItem () + 1;
609 if (targetItem >= (int) d->zoomList.count ())
610 return;
612 d->actionZoom->setCurrentItem (targetItem);
614 #if DEBUG_KP_MAIN_WINDOW
615 kDebug () << "\tnew currentItem=" << d->actionZoom->currentItem ();
616 #endif
618 zoomAccordingToZoomAction (centerUnderCursor);
621 // public
622 void kpMainWindow::zoomOut (bool centerUnderCursor)
624 #if DEBUG_KP_MAIN_WINDOW
625 kDebug () << "kpMainWindow::zoomOut(centerUnderCursor="
626 << centerUnderCursor << ") currentItem="
627 << d->actionZoom->currentItem ()
628 << endl;
629 #endif
630 const int targetItem = d->actionZoom->currentItem () - 1;
632 if (targetItem < 0)
633 return;
635 d->actionZoom->setCurrentItem (targetItem);
637 #if DEBUG_KP_MAIN_WINDOW
638 kDebug () << "\tnew currentItem=" << d->actionZoom->currentItem ();
639 #endif
641 zoomAccordingToZoomAction (centerUnderCursor);
645 // public slot
646 void kpMainWindow::slotZoomIn ()
648 #if DEBUG_KP_MAIN_WINDOW
649 kDebug () << "kpMainWindow::slotZoomIn ()";
650 #endif
652 zoomIn (false/*don't center under cursor*/);
655 // public slot
656 void kpMainWindow::slotZoomOut ()
658 #if DEBUG_KP_MAIN_WINDOW
659 kDebug () << "kpMainWindow::slotZoomOut ()";
660 #endif
662 zoomOut (false/*don't center under cursor*/);
666 // public
667 void kpMainWindow::zoomAccordingToZoomAction (bool centerUnderCursor)
669 #if DEBUG_KP_MAIN_WINDOW
670 kDebug () << "kpMainWindow::zoomAccordingToZoomAction(centerUnderCursor="
671 << centerUnderCursor
672 << ") currentItem=" << d->actionZoom->currentItem ()
673 << " currentText=" << d->actionZoom->currentText ()
674 << endl;
675 #endif
677 // This might be a new zoom level the user has typed in.
678 zoomTo (::ZoomLevelFromString (d->actionZoom->currentText ()),
679 centerUnderCursor);
682 // private slot
683 void kpMainWindow::slotZoom ()
685 #if DEBUG_KP_MAIN_WINDOW
686 kDebug () << "kpMainWindow::slotZoom () index=" << d->actionZoom->currentItem ()
687 << " text='" << d->actionZoom->currentText () << "'" << endl;
688 #endif
690 zoomAccordingToZoomAction (false/*don't center under cursor*/);