1 | /* |
2 | Copyright (c) 2003-2007 Clarence Dang <dang@kde.org> |
3 | Copyright (c) 2011 Martin Koller <kollix@aon.at> |
4 | All rights reserved. |
5 | |
6 | Redistribution and use in source and binary forms, with or without |
7 | modification, are permitted provided that the following conditions |
8 | are met: |
9 | |
10 | 1. Redistributions of source code must retain the above copyright |
11 | notice, this list of conditions and the following disclaimer. |
12 | 2. Redistributions in binary form must reproduce the above copyright |
13 | notice, this list of conditions and the following disclaimer in the |
14 | documentation and/or other materials provided with the distribution. |
15 | |
16 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR |
17 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
18 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. |
19 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, |
20 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
21 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
22 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
23 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
24 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
25 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
26 | */ |
27 | |
28 | #define DEBUG_KP_VIEW_SCROLLABLE_CONTAINER 0 |
29 | |
30 | #include <kpViewScrollableContainer.h> |
31 | |
32 | #include <qbitmap.h> |
33 | #include <qcursor.h> |
34 | #include <qevent.h> |
35 | #include <qpainter.h> |
36 | #include <qpen.h> |
37 | #include <qpixmap.h> |
38 | #include <qtimer.h> |
39 | #include <QScrollBar> |
40 | |
41 | #include <kdebug.h> |
42 | #include <klocale.h> |
43 | |
44 | #include <kpDefs.h> |
45 | #include <kpPixmapFX.h> |
46 | #include <kpView.h> |
47 | #include <kpWidgetMapper.h> |
48 | |
49 | //--------------------------------------------------------------------- |
50 | |
51 | // (Pulled from out of Thurston's hat) |
52 | static const int DragScrollLeftTopMargin = 0; |
53 | static const int DragScrollRightBottomMargin = 16; // scrollbarish |
54 | static const int DragScrollInterval = 150; |
55 | static const int DragScrollInitialInterval = DragScrollInterval * 2; |
56 | static const int DragScrollNumPixels = 10; |
57 | static const int DragDistanceFromRectMaxFor1stMultiplier = 50; |
58 | static const int DragDistanceFromRectMaxFor2ndMultiplier = 100; |
59 | |
60 | //--------------------------------------------------------------------- |
61 | //--------------------------------------------------------------------- |
62 | // a transparent widget above all others in the viewport used only while resizing the document |
63 | // to be able to show the resize lines above everything else |
64 | |
65 | class kpOverlay : public QWidget |
66 | { |
67 | public: |
68 | kpOverlay(QWidget *parent, kpViewScrollableContainer *container) |
69 | : QWidget(parent), m_container(container) |
70 | { |
71 | } |
72 | |
73 | virtual void paintEvent(QPaintEvent *) |
74 | { |
75 | m_container->drawResizeLines(); |
76 | } |
77 | |
78 | private: |
79 | kpViewScrollableContainer *m_container; |
80 | |
81 | }; |
82 | |
83 | //--------------------------------------------------------------------- |
84 | |
85 | const int kpGrip::Size = 5; |
86 | |
87 | //--------------------------------------------------------------------- |
88 | |
89 | kpGrip::kpGrip (GripType type, QWidget *parent) |
90 | : QWidget(parent), |
91 | m_type (type), |
92 | m_startPoint (KP_INVALID_POINT), |
93 | m_currentPoint (KP_INVALID_POINT), |
94 | m_shouldReleaseMouseButtons (false) |
95 | { |
96 | setCursor(cursorForType(m_type)); |
97 | |
98 | setMouseTracking(true); // mouseMoveEvent's even when no mousebtn down |
99 | |
100 | setAutoFillBackground(true); |
101 | setBackgroundRole(QPalette::Highlight); |
102 | |
103 | setFixedSize(kpGrip::Size, kpGrip::Size); |
104 | } |
105 | |
106 | //--------------------------------------------------------------------- |
107 | |
108 | // public |
109 | kpGrip::GripType kpGrip::type () const |
110 | { |
111 | return m_type; |
112 | } |
113 | |
114 | //--------------------------------------------------------------------- |
115 | |
116 | // public static |
117 | QCursor kpGrip::cursorForType (GripType type) |
118 | { |
119 | switch (type) |
120 | { |
121 | case kpGrip::Bottom: |
122 | return Qt::SizeVerCursor; |
123 | break; // one day you'll forget |
124 | |
125 | case kpGrip::Right: |
126 | return Qt::SizeHorCursor; |
127 | break; // one day you'll forget |
128 | |
129 | case kpGrip::BottomRight: |
130 | return Qt::SizeFDiagCursor; |
131 | break; // one day you'll forget |
132 | } |
133 | |
134 | return Qt::ArrowCursor; |
135 | } |
136 | |
137 | //--------------------------------------------------------------------- |
138 | |
139 | // public |
140 | bool kpGrip::containsCursor() |
141 | { |
142 | return isVisible() && |
143 | QRect(mapToGlobal(rect().topLeft()), |
144 | mapToGlobal(rect().bottomRight())).contains(QCursor::pos()); |
145 | } |
146 | |
147 | //--------------------------------------------------------------------- |
148 | |
149 | // public |
150 | bool kpGrip::isDrawing () const |
151 | { |
152 | return (m_startPoint != KP_INVALID_POINT); |
153 | } |
154 | |
155 | //--------------------------------------------------------------------- |
156 | |
157 | // public |
158 | QString kpGrip::haventBegunDrawUserMessage () const |
159 | { |
160 | return i18n ("Left drag the handle to resize the image." ); |
161 | } |
162 | |
163 | //--------------------------------------------------------------------- |
164 | |
165 | // public |
166 | QString kpGrip::userMessage () const |
167 | { |
168 | return m_userMessage; |
169 | } |
170 | |
171 | //--------------------------------------------------------------------- |
172 | |
173 | // public |
174 | void kpGrip::setUserMessage (const QString &message) |
175 | { |
176 | // Don't do NOP checking here since another grip might have changed |
177 | // the message so an apparent NOP for this grip is not a NOP in the |
178 | // global sense (kpViewScrollableContainer::slotGripStatusMessageChanged()). |
179 | |
180 | m_userMessage = message; |
181 | emit statusMessageChanged (message); |
182 | } |
183 | |
184 | //--------------------------------------------------------------------- |
185 | |
186 | // protected |
187 | void kpGrip::cancel () |
188 | { |
189 | #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER |
190 | kDebug () << "kpGrip::cancel()" ; |
191 | #endif |
192 | if (m_currentPoint == KP_INVALID_POINT) |
193 | return; |
194 | |
195 | m_startPoint = KP_INVALID_POINT; |
196 | m_currentPoint = KP_INVALID_POINT; |
197 | |
198 | setUserMessage (i18n ("Resize Image: Let go of all the mouse buttons." )); |
199 | setCursor (Qt::ArrowCursor); |
200 | m_shouldReleaseMouseButtons = true; |
201 | |
202 | releaseKeyboard (); |
203 | emit cancelledDraw (); |
204 | } |
205 | |
206 | //--------------------------------------------------------------------- |
207 | |
208 | // protected virtual [base QWidget] |
209 | void kpGrip::keyReleaseEvent (QKeyEvent *e) |
210 | { |
211 | if (m_startPoint != KP_INVALID_POINT && |
212 | e->key () == Qt::Key_Escape) |
213 | { |
214 | cancel (); |
215 | } |
216 | } |
217 | |
218 | //--------------------------------------------------------------------- |
219 | |
220 | // protected virtual [base QWidget] |
221 | void kpGrip::mousePressEvent (QMouseEvent *e) |
222 | { |
223 | if (m_startPoint == KP_INVALID_POINT && |
224 | (e->buttons () & Qt::MouseButtonMask) == Qt::LeftButton) |
225 | { |
226 | m_startPoint = e->pos (); |
227 | m_currentPoint = e->pos (); |
228 | emit beganDraw (); |
229 | grabKeyboard (); |
230 | |
231 | setUserMessage (i18n ("Resize Image: Right click to cancel." )); |
232 | setCursor (cursorForType (m_type)); |
233 | } |
234 | else |
235 | { |
236 | if (m_startPoint != KP_INVALID_POINT) |
237 | cancel (); |
238 | } |
239 | } |
240 | |
241 | //--------------------------------------------------------------------- |
242 | |
243 | // public |
244 | QPoint kpGrip::viewDeltaPoint () const |
245 | { |
246 | if (m_startPoint == KP_INVALID_POINT) |
247 | return KP_INVALID_POINT; |
248 | |
249 | const QPoint point = mapFromGlobal (QCursor::pos ()); |
250 | |
251 | // TODO: this is getting out of sync with m_currentPoint |
252 | |
253 | return QPoint (((m_type & kpGrip::Right) ? point.x () - m_startPoint.x () : 0), |
254 | ((m_type & kpGrip::Bottom) ? point.y () - m_startPoint.y () : 0)); |
255 | |
256 | } |
257 | |
258 | //--------------------------------------------------------------------- |
259 | |
260 | // public |
261 | void kpGrip::mouseMovedTo (const QPoint &point, bool dueToDragScroll) |
262 | { |
263 | if (m_startPoint == KP_INVALID_POINT) |
264 | return; |
265 | |
266 | m_currentPoint = point; |
267 | |
268 | emit continuedDraw (((m_type & kpGrip::Right) ? point.x () - m_startPoint.x () : 0), |
269 | ((m_type & kpGrip::Bottom) ? point.y () - m_startPoint.y () : 0), |
270 | dueToDragScroll); |
271 | } |
272 | |
273 | //--------------------------------------------------------------------- |
274 | |
275 | // protected virtual [base QWidget] |
276 | void kpGrip::mouseMoveEvent (QMouseEvent *e) |
277 | { |
278 | #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER |
279 | kDebug () << "kpGrip::mouseMoveEvent() m_startPoint=" << m_startPoint |
280 | << " stateAfter: buttons=" << (int *) (int) e->buttons () |
281 | << endl; |
282 | #endif |
283 | |
284 | if (m_startPoint == KP_INVALID_POINT) |
285 | { |
286 | if ((e->buttons () & Qt::MouseButtonMask) == 0) |
287 | setUserMessage (haventBegunDrawUserMessage ()); |
288 | return; |
289 | } |
290 | |
291 | mouseMovedTo (e->pos (), false/*not due to drag scroll*/); |
292 | } |
293 | |
294 | //--------------------------------------------------------------------- |
295 | |
296 | // protected virtual [base QWidget] |
297 | void kpGrip::mouseReleaseEvent (QMouseEvent *e) |
298 | { |
299 | #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER |
300 | kDebug () << "kpGrip::mouseReleaseEvent() m_startPoint=" << m_startPoint |
301 | << " stateAfter: buttons=" << (int *) (int) e->buttons () |
302 | << endl; |
303 | #endif |
304 | |
305 | if (m_startPoint != KP_INVALID_POINT) |
306 | { |
307 | const int dx = m_currentPoint.x () - m_startPoint.x (), |
308 | dy = m_currentPoint.y () - m_startPoint.y (); |
309 | |
310 | m_currentPoint = KP_INVALID_POINT; |
311 | m_startPoint = KP_INVALID_POINT; |
312 | |
313 | releaseKeyboard (); |
314 | emit endedDraw ((m_type & kpGrip::Right) ? dx : 0, |
315 | (m_type & kpGrip::Bottom) ? dy : 0); |
316 | } |
317 | |
318 | if ((e->buttons () & Qt::MouseButtonMask) == 0) |
319 | { |
320 | m_shouldReleaseMouseButtons = false; |
321 | setUserMessage(QString()); |
322 | setCursor (cursorForType (m_type)); |
323 | |
324 | releaseKeyboard (); |
325 | emit releasedAllButtons (); |
326 | } |
327 | } |
328 | |
329 | //--------------------------------------------------------------------- |
330 | |
331 | // protected virtual [base QWidget] |
332 | void kpGrip::enterEvent (QEvent * /*e*/) |
333 | { |
334 | #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER |
335 | kDebug () << "kpGrip::enterEvent()" |
336 | << " m_startPoint=" << m_startPoint |
337 | << " shouldReleaseMouseButtons=" |
338 | << m_shouldReleaseMouseButtons << endl; |
339 | #endif |
340 | |
341 | if (m_startPoint == KP_INVALID_POINT && |
342 | !m_shouldReleaseMouseButtons) |
343 | { |
344 | #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER |
345 | kDebug () << "\tsending message" ; |
346 | #endif |
347 | setUserMessage (haventBegunDrawUserMessage ()); |
348 | } |
349 | } |
350 | |
351 | //--------------------------------------------------------------------- |
352 | |
353 | // protected virtual [base QWidget] |
354 | void kpGrip::leaveEvent (QEvent * /*e*/) |
355 | { |
356 | #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER |
357 | kDebug () << "kpGrip::leaveEvent()" |
358 | << " m_startPoint=" << m_startPoint |
359 | << " shouldReleaseMouseButtons=" |
360 | << m_shouldReleaseMouseButtons << endl; |
361 | #endif |
362 | if (m_startPoint == KP_INVALID_POINT && |
363 | !m_shouldReleaseMouseButtons) |
364 | { |
365 | setUserMessage(QString()); |
366 | } |
367 | } |
368 | |
369 | //--------------------------------------------------------------------- |
370 | //--------------------------------------------------------------------- |
371 | //--------------------------------------------------------------------- |
372 | |
373 | // TODO: Are we checking for m_view == 0 often enough? Also an issue in KDE 3. |
374 | kpViewScrollableContainer::kpViewScrollableContainer(QWidget *parent) |
375 | : QScrollArea(parent), |
376 | m_view(0), m_overlay(new kpOverlay(viewport(), this)), |
377 | m_docResizingGrip (0), |
378 | m_dragScrollTimer (new QTimer (this)), |
379 | m_zoomLevel (100), |
380 | m_scrollTimerRunOnce (false), |
381 | m_resizeRoundedLastViewX (-1), m_resizeRoundedLastViewY (-1), |
382 | m_resizeRoundedLastViewDX (0), m_resizeRoundedLastViewDY (0), |
383 | m_haveMovedFromOriginalDocSize (false) |
384 | |
385 | { |
386 | // the base widget holding the documents view plus the resize grips |
387 | setWidget(new QWidget(viewport())); |
388 | |
389 | m_bottomGrip = new kpGrip(kpGrip::Bottom, widget()); |
390 | m_rightGrip = new kpGrip(kpGrip::Right, widget()); |
391 | m_bottomRightGrip = new kpGrip(kpGrip::BottomRight, widget()); |
392 | |
393 | m_bottomGrip->setObjectName(QLatin1String("Bottom Grip" )); |
394 | m_rightGrip->setObjectName(QLatin1String("Right Grip" )); |
395 | m_bottomRightGrip->setObjectName(QLatin1String("BottomRight Grip" )); |
396 | |
397 | m_bottomGrip->hide (); |
398 | connectGripSignals (m_bottomGrip); |
399 | |
400 | m_rightGrip->hide (); |
401 | connectGripSignals (m_rightGrip); |
402 | |
403 | m_bottomRightGrip->hide (); |
404 | connectGripSignals (m_bottomRightGrip); |
405 | |
406 | |
407 | connect (horizontalScrollBar(), SIGNAL(valueChanged(int)), |
408 | this, SLOT(slotContentsMoved())); |
409 | |
410 | connect (verticalScrollBar(), SIGNAL(valueChanged(int)), |
411 | this, SLOT(slotContentsMoved())); |
412 | |
413 | connect (m_dragScrollTimer, SIGNAL (timeout ()), |
414 | this, SLOT (slotDragScroll ())); |
415 | |
416 | m_overlay->hide(); |
417 | } |
418 | |
419 | //--------------------------------------------------------------------- |
420 | |
421 | // protected |
422 | void kpViewScrollableContainer::connectGripSignals (kpGrip *grip) |
423 | { |
424 | connect (grip, SIGNAL (beganDraw ()), |
425 | this, SLOT (slotGripBeganDraw ())); |
426 | connect (grip, SIGNAL (continuedDraw (int, int, bool)), |
427 | this, SLOT (slotGripContinuedDraw (int, int, bool))); |
428 | connect (grip, SIGNAL (cancelledDraw ()), |
429 | this, SLOT (slotGripCancelledDraw ())); |
430 | connect (grip, SIGNAL (endedDraw (int, int)), |
431 | this, SLOT (slotGripEndedDraw (int, int))); |
432 | |
433 | connect (grip, SIGNAL (statusMessageChanged (const QString &)), |
434 | this, SLOT (slotGripStatusMessageChanged (const QString &))); |
435 | |
436 | connect (grip, SIGNAL (releasedAllButtons ()), |
437 | this, SLOT (recalculateStatusMessage ())); |
438 | } |
439 | |
440 | //--------------------------------------------------------------------- |
441 | |
442 | // public |
443 | QSize kpViewScrollableContainer::newDocSize () const |
444 | { |
445 | return newDocSize (m_resizeRoundedLastViewDX, |
446 | m_resizeRoundedLastViewDY); |
447 | } |
448 | |
449 | //--------------------------------------------------------------------- |
450 | |
451 | // public |
452 | bool kpViewScrollableContainer::haveMovedFromOriginalDocSize () const |
453 | { |
454 | return m_haveMovedFromOriginalDocSize; |
455 | } |
456 | |
457 | //--------------------------------------------------------------------- |
458 | |
459 | // public |
460 | QString kpViewScrollableContainer::statusMessage () const |
461 | { |
462 | return m_gripStatusMessage; |
463 | } |
464 | |
465 | //--------------------------------------------------------------------- |
466 | |
467 | // public |
468 | void kpViewScrollableContainer::clearStatusMessage () |
469 | { |
470 | #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER && 1 |
471 | kDebug () << "kpViewScrollableContainer::clearStatusMessage()" ; |
472 | #endif |
473 | m_bottomRightGrip->setUserMessage(QString()); |
474 | m_bottomGrip->setUserMessage(QString()); |
475 | m_rightGrip->setUserMessage(QString()); |
476 | } |
477 | |
478 | //--------------------------------------------------------------------- |
479 | |
480 | // protected |
481 | QSize kpViewScrollableContainer::newDocSize (int viewDX, int viewDY) const |
482 | { |
483 | if (!m_view) |
484 | return QSize (); |
485 | |
486 | if (!docResizingGrip ()) |
487 | return QSize (); |
488 | |
489 | const int docX = (int) m_view->transformViewToDocX (m_view->width () + viewDX); |
490 | const int docY = (int) m_view->transformViewToDocY (m_view->height () + viewDY); |
491 | |
492 | return QSize (qMax (1, docX), qMax (1, docY)); |
493 | } |
494 | |
495 | //--------------------------------------------------------------------- |
496 | |
497 | // protected |
498 | void kpViewScrollableContainer::calculateDocResizingGrip () |
499 | { |
500 | if (m_bottomRightGrip->isDrawing ()) |
501 | m_docResizingGrip = m_bottomRightGrip; |
502 | else if (m_bottomGrip->isDrawing ()) |
503 | m_docResizingGrip = m_bottomGrip; |
504 | else if (m_rightGrip->isDrawing ()) |
505 | m_docResizingGrip = m_rightGrip; |
506 | else |
507 | m_docResizingGrip = 0; |
508 | } |
509 | |
510 | //--------------------------------------------------------------------- |
511 | |
512 | // protected |
513 | kpGrip *kpViewScrollableContainer::docResizingGrip () const |
514 | { |
515 | return m_docResizingGrip; |
516 | } |
517 | |
518 | //--------------------------------------------------------------------- |
519 | |
520 | // protected |
521 | int kpViewScrollableContainer::bottomResizeLineWidth () const |
522 | { |
523 | if (!docResizingGrip ()) |
524 | return -1; |
525 | |
526 | if (!m_view) |
527 | return -1; |
528 | |
529 | if (docResizingGrip ()->type () & kpGrip::Bottom) |
530 | return qMax (m_view->zoomLevelY () / 100, 1); |
531 | else |
532 | return 1; |
533 | } |
534 | |
535 | //--------------------------------------------------------------------- |
536 | |
537 | // protected |
538 | int kpViewScrollableContainer::rightResizeLineWidth () const |
539 | { |
540 | if (!docResizingGrip ()) |
541 | return -1; |
542 | |
543 | if (!m_view) |
544 | return -1; |
545 | |
546 | if (docResizingGrip ()->type () & kpGrip::Right) |
547 | return qMax (m_view->zoomLevelX () / 100, 1); |
548 | else |
549 | return 1; |
550 | } |
551 | |
552 | //--------------------------------------------------------------------- |
553 | |
554 | // protected |
555 | QRect kpViewScrollableContainer::bottomResizeLineRect () const |
556 | { |
557 | if (m_resizeRoundedLastViewX < 0 || m_resizeRoundedLastViewY < 0) |
558 | return QRect (); |
559 | |
560 | QRect visibleArea = QRect(QPoint(horizontalScrollBar()->value(),verticalScrollBar()->value()), viewport()->size()); |
561 | |
562 | return QRect (QPoint (0, |
563 | m_resizeRoundedLastViewY), |
564 | QPoint (m_resizeRoundedLastViewX - 1, |
565 | m_resizeRoundedLastViewY + bottomResizeLineWidth () - 1)).intersected(visibleArea); |
566 | } |
567 | |
568 | //--------------------------------------------------------------------- |
569 | |
570 | // protected |
571 | QRect kpViewScrollableContainer::rightResizeLineRect () const |
572 | { |
573 | if (m_resizeRoundedLastViewX < 0 || m_resizeRoundedLastViewY < 0) |
574 | return QRect (); |
575 | |
576 | QRect visibleArea = QRect(QPoint(horizontalScrollBar()->value(),verticalScrollBar()->value()), viewport()->size()); |
577 | |
578 | return QRect (QPoint (m_resizeRoundedLastViewX, |
579 | 0), |
580 | QPoint (m_resizeRoundedLastViewX + rightResizeLineWidth () - 1, |
581 | m_resizeRoundedLastViewY - 1)).intersected(visibleArea); |
582 | } |
583 | |
584 | //--------------------------------------------------------------------- |
585 | |
586 | // protected |
587 | QRect kpViewScrollableContainer::bottomRightResizeLineRect () const |
588 | { |
589 | if (m_resizeRoundedLastViewX < 0 || m_resizeRoundedLastViewY < 0) |
590 | return QRect (); |
591 | |
592 | QRect visibleArea = QRect(QPoint(horizontalScrollBar()->value(),verticalScrollBar()->value()), viewport()->size()); |
593 | |
594 | return QRect (QPoint (m_resizeRoundedLastViewX, |
595 | m_resizeRoundedLastViewY), |
596 | QPoint (m_resizeRoundedLastViewX + rightResizeLineWidth () - 1, |
597 | m_resizeRoundedLastViewY + bottomResizeLineWidth () - 1)).intersected(visibleArea); |
598 | } |
599 | |
600 | //--------------------------------------------------------------------- |
601 | |
602 | // private |
603 | QRect kpViewScrollableContainer::mapViewToViewport (const QRect &viewRect) |
604 | { |
605 | if (!viewRect.isValid ()) |
606 | return QRect (); |
607 | |
608 | QRect ret = viewRect; |
609 | ret.translate (-horizontalScrollBar()->value() - viewport()->x(), -verticalScrollBar()->value() - viewport()->y()); |
610 | return ret; |
611 | } |
612 | |
613 | //--------------------------------------------------------------------- |
614 | |
615 | void kpViewScrollableContainer::drawResizeLines () |
616 | { |
617 | static const char *stipple[] = |
618 | { |
619 | "8 8 2 1" , |
620 | ". c #000000" , |
621 | "# c #ffffff" , |
622 | "....####" , |
623 | "....####" , |
624 | "....####" , |
625 | "....####" , |
626 | "####...." , |
627 | "####...." , |
628 | "####...." , |
629 | "####...." |
630 | }; |
631 | |
632 | QPainter p(m_overlay); |
633 | p.setBackground(QPixmap(stipple)); |
634 | |
635 | const QRect rightRect = rightResizeLineRect(); |
636 | if ( rightRect.isValid() ) |
637 | { |
638 | QRect rect = mapViewToViewport(rightRect); |
639 | p.setBrushOrigin(rect.x(), rect.y()); |
640 | p.eraseRect(rect); |
641 | } |
642 | |
643 | const QRect bottomRect = bottomResizeLineRect(); |
644 | if ( bottomRect.isValid() ) |
645 | { |
646 | QRect rect = mapViewToViewport(bottomRect); |
647 | p.setBrushOrigin(rect.x(), rect.y()); |
648 | p.eraseRect(rect); |
649 | } |
650 | |
651 | const QRect bottomRightRect = bottomRightResizeLineRect (); |
652 | if ( bottomRightRect.isValid() ) |
653 | { |
654 | QRect rect = mapViewToViewport(bottomRightRect); |
655 | p.setBrushOrigin(rect.x(), rect.y()); |
656 | p.eraseRect(rect); |
657 | } |
658 | } |
659 | |
660 | //--------------------------------------------------------------------- |
661 | |
662 | // protected |
663 | void kpViewScrollableContainer::updateResizeLines (int viewX, int viewY, |
664 | int viewDX, int viewDY) |
665 | { |
666 | #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER && 0 |
667 | kDebug () << "kpViewScrollableContainer::updateResizeLines(" |
668 | << viewX << "," << viewY << ")" |
669 | << " oldViewX=" << m_resizeRoundedLastViewX |
670 | << " oldViewY=" << m_resizeRoundedLastViewY |
671 | << " viewDX=" << viewDX |
672 | << " viewDY=" << viewDY |
673 | << endl; |
674 | #endif |
675 | |
676 | |
677 | if (viewX >= 0 && viewY >= 0) |
678 | { |
679 | m_resizeRoundedLastViewX = (int) m_view->transformDocToViewX ((int) m_view->transformViewToDocX (viewX)); |
680 | m_resizeRoundedLastViewY = (int) m_view->transformDocToViewY ((int) m_view->transformViewToDocY (viewY)); |
681 | |
682 | m_resizeRoundedLastViewDX = viewDX; |
683 | m_resizeRoundedLastViewDY = viewDY; |
684 | } |
685 | else |
686 | { |
687 | m_resizeRoundedLastViewX = -1; |
688 | m_resizeRoundedLastViewY = -1; |
689 | |
690 | m_resizeRoundedLastViewDX = 0; |
691 | m_resizeRoundedLastViewDY = 0; |
692 | } |
693 | |
694 | m_overlay->update(); |
695 | } |
696 | |
697 | //--------------------------------------------------------------------- |
698 | |
699 | // protected slot |
700 | void kpViewScrollableContainer::slotGripBeganDraw () |
701 | { |
702 | if (!m_view) |
703 | return; |
704 | |
705 | m_overlay->resize(viewport()->size()); // make it cover whole viewport |
706 | m_overlay->move(viewport()->pos()); |
707 | m_overlay->show(); |
708 | m_overlay->raise(); // make it top-most |
709 | |
710 | calculateDocResizingGrip (); |
711 | |
712 | m_haveMovedFromOriginalDocSize = false; |
713 | |
714 | updateResizeLines (m_view->width (), m_view->height (), |
715 | 0/*viewDX*/, 0/*viewDY*/); |
716 | |
717 | emit beganDocResize (); |
718 | } |
719 | |
720 | //--------------------------------------------------------------------- |
721 | |
722 | // protected slot |
723 | void kpViewScrollableContainer::slotGripContinuedDraw (int inViewDX, int inViewDY, |
724 | bool dueToDragScroll) |
725 | { |
726 | int viewDX = inViewDX, |
727 | viewDY = inViewDY; |
728 | |
729 | #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER |
730 | kDebug () << "kpViewScrollableContainer::slotGripContinuedDraw(" |
731 | << viewDX << "," << viewDY << ") size=" |
732 | << newDocSize (viewDX, viewDY) |
733 | << " dueToDragScroll=" << dueToDragScroll |
734 | << endl; |
735 | #endif |
736 | |
737 | if (!m_view) |
738 | return; |
739 | |
740 | if (!dueToDragScroll && |
741 | beginDragScroll(m_view->zoomLevelX ())) |
742 | { |
743 | const QPoint newViewDeltaPoint = docResizingGrip ()->viewDeltaPoint (); |
744 | viewDX = newViewDeltaPoint.x (); |
745 | viewDY = newViewDeltaPoint.y (); |
746 | #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER |
747 | kDebug () << "\tdrag scrolled - new view delta point=" |
748 | << newViewDeltaPoint |
749 | << endl; |
750 | #endif |
751 | } |
752 | |
753 | m_haveMovedFromOriginalDocSize = true; |
754 | |
755 | updateResizeLines (qMax (1, qMax (m_view->width () + viewDX, (int) m_view->transformDocToViewX (1))), |
756 | qMax (1, qMax (m_view->height () + viewDY, (int) m_view->transformDocToViewY (1))), |
757 | viewDX, viewDY); |
758 | |
759 | emit continuedDocResize (newDocSize ()); |
760 | } |
761 | |
762 | //--------------------------------------------------------------------- |
763 | |
764 | // protected slot |
765 | void kpViewScrollableContainer::slotGripCancelledDraw () |
766 | { |
767 | m_haveMovedFromOriginalDocSize = false; |
768 | |
769 | updateResizeLines (-1, -1, 0, 0); |
770 | |
771 | calculateDocResizingGrip (); |
772 | |
773 | emit cancelledDocResize (); |
774 | |
775 | endDragScroll (); |
776 | |
777 | m_overlay->hide(); |
778 | } |
779 | |
780 | //--------------------------------------------------------------------- |
781 | |
782 | // protected slot |
783 | void kpViewScrollableContainer::slotGripEndedDraw (int viewDX, int viewDY) |
784 | { |
785 | #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER |
786 | kDebug () << "kpViewScrollableContainer::slotGripEndedDraw(" |
787 | << viewDX << "," << viewDY << ") size=" |
788 | << newDocSize (viewDX, viewDY) |
789 | << endl; |
790 | #endif |
791 | |
792 | if (!m_view) |
793 | return; |
794 | |
795 | const QSize newSize = newDocSize (viewDX, viewDY); |
796 | |
797 | m_haveMovedFromOriginalDocSize = false; |
798 | |
799 | // must erase lines before view size changes |
800 | updateResizeLines (-1, -1, 0, 0); |
801 | |
802 | calculateDocResizingGrip (); |
803 | |
804 | emit endedDocResize (newSize); |
805 | |
806 | endDragScroll (); |
807 | |
808 | m_overlay->hide(); |
809 | } |
810 | |
811 | //--------------------------------------------------------------------- |
812 | |
813 | // protected slot |
814 | void kpViewScrollableContainer::slotGripStatusMessageChanged (const QString &string) |
815 | { |
816 | if (string == m_gripStatusMessage) |
817 | return; |
818 | |
819 | m_gripStatusMessage = string; |
820 | emit statusMessageChanged (string); |
821 | } |
822 | |
823 | //--------------------------------------------------------------------- |
824 | |
825 | // public slot |
826 | void kpViewScrollableContainer::recalculateStatusMessage () |
827 | { |
828 | #if DEBUG_KP_VIEW_SCROLLABLE_CONTAINER |
829 | kDebug () << "kpViewScrollabelContainer::recalculateStatusMessage()" ; |
830 | kDebug () << "\tQCursor::pos=" << QCursor::pos () |
831 | << " global visibleRect=" |
832 | << kpWidgetMapper::toGlobal (this, |
833 | QRect(0, 0, viewport->width(), viewport->height())) |
834 | << endl; |
835 | #endif |
836 | |
837 | // HACK: After dragging to a new size, handles move so that they are now |
838 | // under the mouse pointer but no mouseMoveEvent() is generated for |
839 | // any grip. This also handles the case of canceling over any |
840 | // grip. |
841 | // |
842 | if (kpWidgetMapper::toGlobal (this, |
843 | QRect(0, 0, viewport()->width(), viewport()->height())) |
844 | .contains (QCursor::pos ())) |
845 | { |
846 | if ( m_bottomRightGrip->containsCursor() ) |
847 | { |
848 | m_bottomRightGrip->setUserMessage (i18n ("Left drag the handle to resize the image." )); |
849 | } |
850 | else if ( m_bottomGrip->containsCursor() ) |
851 | { |
852 | m_bottomGrip->setUserMessage (i18n ("Left drag the handle to resize the image." )); |
853 | } |
854 | else if ( m_rightGrip->containsCursor() ) |
855 | { |
856 | m_rightGrip->setUserMessage (i18n ("Left drag the handle to resize the image." )); |
857 | } |
858 | else |
859 | { |
860 | clearStatusMessage (); |
861 | } |
862 | } |
863 | else |
864 | { |
865 | clearStatusMessage (); |
866 | } |
867 | } |
868 | |
869 | //--------------------------------------------------------------------- |
870 | |
871 | // protected slot |
872 | void kpViewScrollableContainer::slotContentsMoved () |
873 | { |
874 | kpGrip *grip = docResizingGrip (); |
875 | if (grip) |
876 | { |
877 | grip->mouseMovedTo (grip->mapFromGlobal (QCursor::pos ()), |
878 | true/*moved due to drag scroll*/); |
879 | } |
880 | |
881 | m_overlay->move(viewport()->pos()); |
882 | m_overlay->update(); |
883 | |
884 | emit contentsMoved(); |
885 | } |
886 | |
887 | //--------------------------------------------------------------------- |
888 | |
889 | // protected |
890 | void kpViewScrollableContainer::disconnectViewSignals () |
891 | { |
892 | disconnect (m_view, SIGNAL (sizeChanged (const QSize &)), |
893 | this, SLOT (updateGrips ())); |
894 | disconnect (m_view, SIGNAL (destroyed ()), |
895 | this, SLOT (slotViewDestroyed ())); |
896 | } |
897 | |
898 | //--------------------------------------------------------------------- |
899 | |
900 | // protected |
901 | void kpViewScrollableContainer::connectViewSignals () |
902 | { |
903 | connect (m_view, SIGNAL (sizeChanged (const QSize &)), |
904 | this, SLOT (updateGrips ())); |
905 | connect (m_view, SIGNAL (destroyed ()), |
906 | this, SLOT (slotViewDestroyed ())); |
907 | } |
908 | |
909 | //--------------------------------------------------------------------- |
910 | |
911 | // public |
912 | kpView *kpViewScrollableContainer::view () const |
913 | { |
914 | return m_view; |
915 | } |
916 | |
917 | //--------------------------------------------------------------------- |
918 | |
919 | // public |
920 | void kpViewScrollableContainer::setView (kpView *view) |
921 | { |
922 | if (m_view == view) |
923 | return; |
924 | |
925 | if (m_view) |
926 | { |
927 | disconnectViewSignals (); |
928 | } |
929 | |
930 | m_view = view; |
931 | |
932 | if ( m_view ) |
933 | { |
934 | m_view->setParent(widget()); |
935 | m_view->show(); |
936 | } |
937 | |
938 | updateGrips (); |
939 | |
940 | if (m_view) |
941 | { |
942 | connectViewSignals (); |
943 | } |
944 | } |
945 | |
946 | //--------------------------------------------------------------------- |
947 | |
948 | // public slot |
949 | void kpViewScrollableContainer::updateGrips () |
950 | { |
951 | if (m_view) |
952 | { |
953 | widget()->resize(m_view->size() + m_bottomRightGrip->size()); |
954 | |
955 | // to make the grip more easily "touchable" make it as high as the view |
956 | m_rightGrip->setFixedHeight(m_view->height()); |
957 | m_rightGrip->move(m_view->width(), 0); |
958 | |
959 | // to make the grip more easily "touchable" make it as wide as the view |
960 | m_bottomGrip->setFixedWidth(m_view->width()); |
961 | m_bottomGrip->move(0, m_view->height ()); |
962 | |
963 | m_bottomRightGrip->move(m_view->width(), m_view->height()); |
964 | } |
965 | |
966 | m_bottomGrip->setHidden (m_view == 0); |
967 | m_rightGrip->setHidden (m_view == 0); |
968 | m_bottomRightGrip->setHidden (m_view == 0); |
969 | |
970 | recalculateStatusMessage (); |
971 | } |
972 | |
973 | //--------------------------------------------------------------------- |
974 | |
975 | // protected slot |
976 | void kpViewScrollableContainer::slotViewDestroyed () |
977 | { |
978 | m_view = 0; |
979 | updateGrips (); |
980 | } |
981 | |
982 | //--------------------------------------------------------------------- |
983 | |
984 | // public slot |
985 | bool kpViewScrollableContainer::beginDragScroll(int zoomLevel, bool *didSomething) |
986 | { |
987 | if (didSomething) |
988 | *didSomething = false; |
989 | |
990 | m_zoomLevel = zoomLevel; |
991 | |
992 | const QPoint p = mapFromGlobal (QCursor::pos ()); |
993 | |
994 | bool stopDragScroll = true; |
995 | bool scrolled = false; |
996 | |
997 | if (!noDragScrollRect ().contains (p)) |
998 | { |
999 | if (m_dragScrollTimer->isActive ()) |
1000 | { |
1001 | if (m_scrollTimerRunOnce) |
1002 | { |
1003 | scrolled = slotDragScroll (); |
1004 | } |
1005 | } |
1006 | else |
1007 | { |
1008 | m_scrollTimerRunOnce = false; |
1009 | m_dragScrollTimer->start (DragScrollInitialInterval); |
1010 | } |
1011 | |
1012 | stopDragScroll = false; |
1013 | } |
1014 | |
1015 | if (stopDragScroll) |
1016 | m_dragScrollTimer->stop (); |
1017 | |
1018 | if (didSomething) |
1019 | *didSomething = scrolled; |
1020 | |
1021 | return scrolled; |
1022 | } |
1023 | |
1024 | //--------------------------------------------------------------------- |
1025 | |
1026 | // public slot |
1027 | bool kpViewScrollableContainer::beginDragScroll(int zoomLevel) |
1028 | { |
1029 | return beginDragScroll(zoomLevel, |
1030 | 0/*don't want scrolled notification*/); |
1031 | } |
1032 | |
1033 | //--------------------------------------------------------------------- |
1034 | |
1035 | // public slot |
1036 | bool kpViewScrollableContainer::endDragScroll () |
1037 | { |
1038 | if (m_dragScrollTimer->isActive ()) |
1039 | { |
1040 | m_dragScrollTimer->stop (); |
1041 | return true; |
1042 | } |
1043 | else |
1044 | { |
1045 | return false; |
1046 | } |
1047 | } |
1048 | |
1049 | //--------------------------------------------------------------------- |
1050 | |
1051 | static int distanceFromRectToMultiplier (int dist) |
1052 | { |
1053 | if (dist < 0) |
1054 | return 0; |
1055 | else if (dist < DragDistanceFromRectMaxFor1stMultiplier) |
1056 | return 1; |
1057 | else if (dist < DragDistanceFromRectMaxFor2ndMultiplier) |
1058 | return 2; |
1059 | else |
1060 | return 4; |
1061 | } |
1062 | |
1063 | //--------------------------------------------------------------------- |
1064 | |
1065 | // protected slot |
1066 | bool kpViewScrollableContainer::slotDragScroll (bool *didSomething) |
1067 | { |
1068 | bool scrolled = false; |
1069 | |
1070 | if (didSomething) |
1071 | *didSomething = false; |
1072 | |
1073 | |
1074 | const QRect rect = noDragScrollRect (); |
1075 | const QPoint pos = mapFromGlobal (QCursor::pos ()); |
1076 | |
1077 | int dx = 0, dy = 0; |
1078 | int dxMultiplier = 0, dyMultiplier = 0; |
1079 | |
1080 | if (pos.x () < rect.left ()) |
1081 | { |
1082 | dx = -DragScrollNumPixels; |
1083 | dxMultiplier = distanceFromRectToMultiplier (rect.left () - pos.x ()); |
1084 | } |
1085 | else if (pos.x () > rect.right ()) |
1086 | { |
1087 | dx = +DragScrollNumPixels; |
1088 | dxMultiplier = distanceFromRectToMultiplier (pos.x () - rect.right ()); |
1089 | } |
1090 | |
1091 | if (pos.y () < rect.top ()) |
1092 | { |
1093 | dy = -DragScrollNumPixels; |
1094 | dyMultiplier = distanceFromRectToMultiplier (rect.top () - pos.y ()); |
1095 | } |
1096 | else if (pos.y () > rect.bottom ()) |
1097 | { |
1098 | dy = +DragScrollNumPixels; |
1099 | dyMultiplier = distanceFromRectToMultiplier (pos.y () - rect.bottom ()); |
1100 | } |
1101 | |
1102 | dx *= dxMultiplier;// * qMax (1, m_zoomLevel / 100); |
1103 | dy *= dyMultiplier;// * qMax (1, m_zoomLevel / 100); |
1104 | |
1105 | if (dx || dy) |
1106 | { |
1107 | const int oldContentsX = horizontalScrollBar()->value (), |
1108 | oldContentsY = verticalScrollBar()->value (); |
1109 | |
1110 | horizontalScrollBar()->setValue(oldContentsX + dx); |
1111 | verticalScrollBar()->setValue(oldContentsY + dy); |
1112 | |
1113 | scrolled = (oldContentsX != horizontalScrollBar()->value () || |
1114 | oldContentsY != verticalScrollBar()->value ()); |
1115 | |
1116 | if (scrolled) |
1117 | { |
1118 | QRegion region = QRect (horizontalScrollBar()->value (), verticalScrollBar()->value (), |
1119 | viewport()->width(), viewport()->height()); |
1120 | region -= QRect (oldContentsX, oldContentsY, |
1121 | viewport()->width(), viewport()->height()); |
1122 | |
1123 | // Repaint newly exposed region immediately to reduce tearing |
1124 | // of scrollView. |
1125 | m_view->repaint (region); |
1126 | } |
1127 | } |
1128 | |
1129 | m_dragScrollTimer->start (DragScrollInterval); |
1130 | m_scrollTimerRunOnce = true; |
1131 | |
1132 | if (didSomething) |
1133 | *didSomething = scrolled; |
1134 | |
1135 | return scrolled; |
1136 | } |
1137 | |
1138 | //--------------------------------------------------------------------- |
1139 | |
1140 | // protected slot |
1141 | bool kpViewScrollableContainer::slotDragScroll () |
1142 | { |
1143 | return slotDragScroll (0/*don't want scrolled notification*/); |
1144 | } |
1145 | |
1146 | //--------------------------------------------------------------------- |
1147 | |
1148 | // protected virtual |
1149 | void kpViewScrollableContainer::wheelEvent (QWheelEvent *e) |
1150 | { |
1151 | e->ignore (); |
1152 | |
1153 | if (m_view) |
1154 | m_view->wheelEvent (e); |
1155 | |
1156 | if ( !e->isAccepted() ) |
1157 | QScrollArea::wheelEvent(e); |
1158 | } |
1159 | |
1160 | //--------------------------------------------------------------------- |
1161 | |
1162 | QRect kpViewScrollableContainer::noDragScrollRect () const |
1163 | { |
1164 | return QRect (DragScrollLeftTopMargin, DragScrollLeftTopMargin, |
1165 | width () - DragScrollLeftTopMargin - DragScrollRightBottomMargin, |
1166 | height () - DragScrollLeftTopMargin - DragScrollRightBottomMargin); |
1167 | } |
1168 | |
1169 | //--------------------------------------------------------------------- |
1170 | |
1171 | // protected virtual [base QScrollView] |
1172 | void kpViewScrollableContainer::resizeEvent (QResizeEvent *e) |
1173 | { |
1174 | QScrollArea::resizeEvent (e); |
1175 | |
1176 | emit resized (); |
1177 | } |
1178 | |
1179 | //--------------------------------------------------------------------- |
1180 | |
1181 | #include <kpViewScrollableContainer.moc> |
1182 | |