• Skip to content
  • Skip to link menu
  • KDE API Reference
  • kdelibs-4.14.38 API Reference
  • KDE Home
  • Contact Us
 

Plasma

  • plasma
  • widgets
scrollwidget.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2009 Marco Martin <notmart@gmail.com>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU Library General Public License as
6 * published by the Free Software Foundation; either version 2, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details
13 *
14 * You should have received a copy of the GNU Library General Public
15 * License along with this program; if not, write to the
16 * Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 */
19
20#include "scrollwidget.h"
21
22#include <cmath>
23
24//Qt
25#include <QGraphicsSceneResizeEvent>
26#include <QGraphicsGridLayout>
27#include <QGraphicsScene>
28#include <QApplication>
29#include <QKeyEvent>
30#include <QWidget>
31#include <QTimer>
32#include <QTime>
33#include <QPropertyAnimation>
34#include <QSequentialAnimationGroup>
35
36#include <QLabel>
37
38//KDE
39#include <kmimetype.h>
40#include <kdebug.h>
41#include <kglobalsettings.h>
42#include <kiconloader.h>
43#include <ktextedit.h>
44#include <ktextbrowser.h>
45
46//Plasma
47#include <plasma/widgets/scrollbar.h>
48#include <plasma/widgets/svgwidget.h>
49#include <plasma/widgets/label.h>
50#include <plasma/widgets/textedit.h>
51#include <plasma/widgets/textbrowser.h>
52#include <plasma/animator.h>
53#include <plasma/svg.h>
54
55
56#define DEBUG 0
57
58/*
59 The flicking code is largely based on the behavior of
60 the flickable widget in QDeclerative so porting between
61 the two should preserve the behavior.
62 The code that figures out velocity could use some
63 improvements, in particular IGNORE_SUSPICIOUS_MOVES
64 is a hack that shouldn't be necessary.
65 */
66
67//XXX fixme
68// we use a timer between move events to figure out
69// the velocity of a move, but sometimes we're getting move
70// events with big positional changes with no break
71// in between them, which causes us to compute
72// huge velocities. this define just filters out
73// events which come at insanly small time intervals.
74// at some point we need to figure out how to do it properly
75#define IGNORE_SUSPICIOUS_MOVES 1
76
77// FlickThreshold determines how far the "mouse" must have moved
78// before we perform a flick.
79static const int FlickThreshold = 20;
80
81
82static const qreal MinimumFlickVelocity = 200;
83static const qreal MaxVelocity = 2000;
84
85// time it takes the widget to flick back to its
86// bounds when overshot
87static const qreal FixupDuration = 600;
88
89namespace Plasma
90{
91
92class ScrollWidgetPrivate
93{
94public:
95 enum Gesture {
96 GestureNone = 0,
97 GestureUndefined,
98 GestureScroll,
99 GestureZoom
100 };
101
102 ScrollWidgetPrivate(ScrollWidget *parent)
103 : q(parent),
104 topBorder(0),
105 bottomBorder(0),
106 leftBorder(0),
107 rightBorder(0),
108 dragging(false),
109 overflowBordersVisible(true),
110 multitouchGesture(GestureNone)
111 {
112 }
113
114 ~ScrollWidgetPrivate()
115 {
116 }
117
118 void commonConstructor()
119 {
120 q->setFocusPolicy(Qt::StrongFocus);
121 q->setFiltersChildEvents(true);
122 layout = new QGraphicsGridLayout(q);
123 q->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
124 layout->setContentsMargins(0, 0, 0, 0);
125 scrollingWidget = new QGraphicsWidget(q);
126 scrollingWidget->setFlag(QGraphicsItem::ItemHasNoContents);
127 scrollingWidget->installEventFilter(q);
128 layout->addItem(scrollingWidget, 0, 0);
129 borderSvg = new Plasma::Svg(q);
130 borderSvg->setImagePath("widgets/scrollwidget");
131
132 adjustScrollbarsTimer = new QTimer(q);
133 adjustScrollbarsTimer->setSingleShot(true);
134 QObject::connect(adjustScrollbarsTimer, SIGNAL(timeout()), q, SLOT(adjustScrollbars()));
135
136 wheelTimer = new QTimer(q);
137 wheelTimer->setSingleShot(true);
138
139 verticalScrollBarPolicy = Qt::ScrollBarAsNeeded;
140 verticalScrollBar = new Plasma::ScrollBar(q);
141 verticalScrollBar->setFocusPolicy(Qt::NoFocus);
142 layout->addItem(verticalScrollBar, 0, 1);
143 verticalScrollBar->nativeWidget()->setMinimum(0);
144 verticalScrollBar->nativeWidget()->setMaximum(100);
145 QObject::connect(verticalScrollBar, SIGNAL(valueChanged(int)), q, SLOT(verticalScroll(int)));
146
147 horizontalScrollBarPolicy = Qt::ScrollBarAsNeeded;
148 horizontalScrollBar = new Plasma::ScrollBar(q);
149 verticalScrollBar->setFocusPolicy(Qt::NoFocus);
150 horizontalScrollBar->setOrientation(Qt::Horizontal);
151 layout->addItem(horizontalScrollBar, 1, 0);
152 horizontalScrollBar->nativeWidget()->setMinimum(0);
153 horizontalScrollBar->nativeWidget()->setMaximum(100);
154 QObject::connect(horizontalScrollBar, SIGNAL(valueChanged(int)), q, SLOT(horizontalScroll(int)));
155
156 layout->setColumnSpacing(0, 0);
157 layout->setColumnSpacing(1, 0);
158 layout->setRowSpacing(0, 0);
159 layout->setRowSpacing(1, 0);
160
161 flickAnimationX = 0;
162 flickAnimationY = 0;
163 fixupAnimation.groupX = 0;
164 fixupAnimation.startX = 0;
165 fixupAnimation.endX = 0;
166 fixupAnimation.groupY = 0;
167 fixupAnimation.startY = 0;
168 fixupAnimation.endY = 0;
169 fixupAnimation.snapX = 0;
170 fixupAnimation.snapY = 0;
171 directMoveAnimation = 0;
172 stealEvent = false;
173 hasOvershoot = true;
174
175 alignment = Qt::AlignLeft | Qt::AlignTop;
176
177 hasContentsProperty = false;
178 hasOffsetProperty = false;
179 hasXProperty = false;
180 hasYProperty = false;
181 }
182
183 void adjustScrollbars()
184 {
185 if (!widget) {
186 return;
187 }
188
189 const bool verticalVisible = widget.data()->size().height() > q->size().height();
190 const bool horizontalVisible = widget.data()->size().width() > q->size().width();
191
192 verticalScrollBar->nativeWidget()->setMaximum(qMax(0, int((widget.data()->size().height() - scrollingWidget->size().height())/10)));
193 verticalScrollBar->nativeWidget()->setPageStep(int(scrollingWidget->size().height())/10);
194
195 if (verticalScrollBarPolicy == Qt::ScrollBarAlwaysOff ||
196 !verticalVisible) {
197 if (layout->count() > 2 && layout->itemAt(2) == verticalScrollBar) {
198 layout->removeAt(2);
199 } else if (layout->count() > 1 && layout->itemAt(1) == verticalScrollBar) {
200 layout->removeAt(1);
201 }
202 verticalScrollBar->hide();
203 } else if (!verticalScrollBar->isVisible()) {
204 layout->addItem(verticalScrollBar, 0, 1);
205 verticalScrollBar->show();
206 }
207
208 horizontalScrollBar->nativeWidget()->setMaximum(qMax(0, int((widget.data()->size().width() - scrollingWidget->size().width())/10)));
209 horizontalScrollBar->nativeWidget()->setPageStep(int(scrollingWidget->size().width())/10);
210
211 if (horizontalScrollBarPolicy == Qt::ScrollBarAlwaysOff ||
212 !horizontalVisible) {
213 if (layout->count() > 2 && layout->itemAt(2) == horizontalScrollBar) {
214 layout->removeAt(2);
215 } else if (layout->count() > 1 && layout->itemAt(1) == horizontalScrollBar) {
216 layout->removeAt(1);
217 }
218 horizontalScrollBar->hide();
219 } else if (!horizontalScrollBar->isVisible()) {
220 layout->addItem(horizontalScrollBar, 1, 0);
221 horizontalScrollBar->show();
222 }
223
224 if (widget && !topBorder && verticalVisible) {
225 topBorder = new Plasma::SvgWidget(q);
226 topBorder->setSvg(borderSvg);
227 topBorder->setElementID("border-top");
228 topBorder->setZValue(900);
229 topBorder->resize(topBorder->effectiveSizeHint(Qt::PreferredSize));
230 topBorder->setVisible(overflowBordersVisible);
231
232 bottomBorder = new Plasma::SvgWidget(q);
233 bottomBorder->setSvg(borderSvg);
234 bottomBorder->setElementID("border-bottom");
235 bottomBorder->setZValue(900);
236 bottomBorder->resize(bottomBorder->effectiveSizeHint(Qt::PreferredSize));
237 bottomBorder->setVisible(overflowBordersVisible);
238 } else if (topBorder && widget && !verticalVisible) {
239 //FIXME: in some cases topBorder->deleteLater() is deleteNever(), why?
240 topBorder->hide();
241 bottomBorder->hide();
242 topBorder->deleteLater();
243 bottomBorder->deleteLater();
244 topBorder = 0;
245 bottomBorder = 0;
246 }
247
248
249 if (widget && !leftBorder && horizontalVisible) {
250 leftBorder = new Plasma::SvgWidget(q);
251 leftBorder->setSvg(borderSvg);
252 leftBorder->setElementID("border-left");
253 leftBorder->setZValue(900);
254 leftBorder->resize(leftBorder->effectiveSizeHint(Qt::PreferredSize));
255 leftBorder->setVisible(overflowBordersVisible);
256
257 rightBorder = new Plasma::SvgWidget(q);
258 rightBorder->setSvg(borderSvg);
259 rightBorder->setElementID("border-right");
260 rightBorder->setZValue(900);
261 rightBorder->resize(rightBorder->effectiveSizeHint(Qt::PreferredSize));
262 rightBorder->setVisible(overflowBordersVisible);
263 } else if (leftBorder && widget && !horizontalVisible) {
264 leftBorder->hide();
265 rightBorder->hide();
266 leftBorder->deleteLater();
267 rightBorder->deleteLater();
268 leftBorder = 0;
269 rightBorder = 0;
270 }
271
272 layout->activate();
273
274 if (topBorder) {
275 topBorder->resize(q->size().width(), topBorder->size().height());
276 bottomBorder->resize(q->size().width(), bottomBorder->size().height());
277 bottomBorder->setPos(0, q->size().height() - topBorder->size().height());
278 }
279 if (leftBorder) {
280 leftBorder->resize(leftBorder->size().width(), q->size().height());
281 rightBorder->resize(rightBorder->size().width(), q->size().height());
282 rightBorder->setPos(q->size().width() - rightBorder->size().width(), 0);
283 }
284
285 QSizeF widgetSize = widget.data()->size();
286 if (widget.data()->sizePolicy().expandingDirections() & Qt::Horizontal) {
287 //keep a 1 pixel border
288 widgetSize.setWidth(scrollingWidget->size().width());
289 }
290 if (widget.data()->sizePolicy().expandingDirections() & Qt::Vertical) {
291 widgetSize.setHeight(scrollingWidget->size().height());
292 }
293 widget.data()->resize(widgetSize);
294
295 adjustClipping();
296 }
297
298 void verticalScroll(int value)
299 {
300 if (!widget) {
301 return;
302 }
303
304 if (!dragging) {
305 widget.data()->setPos(QPoint(widget.data()->pos().x(), -value*10));
306 }
307 }
308
309 void horizontalScroll(int value)
310 {
311 if (!widget) {
312 return;
313 }
314
315 if (!dragging) {
316 widget.data()->setPos(QPoint(-value*10, widget.data()->pos().y()));
317 }
318 }
319
320 void adjustClipping()
321 {
322 if (!widget) {
323 return;
324 }
325
326 const bool clip = widget.data()->size().width() > scrollingWidget->size().width() || widget.data()->size().height() > scrollingWidget->size().height();
327
328 scrollingWidget->setFlag(QGraphicsItem::ItemClipsChildrenToShape, clip);
329 }
330
331 qreal overShootDistance(qreal velocity, qreal size) const
332 {
333 if (MaxVelocity <= 0)
334 return 0.0;
335
336 velocity = qAbs(velocity);
337 if (velocity > MaxVelocity)
338 velocity = MaxVelocity;
339 qreal dist = size / 4 * velocity / MaxVelocity;
340 return dist;
341 }
342
343 void animateMoveTo(const QPointF &pos)
344 {
345 qreal duration = 800;
346 QPointF start = q->scrollPosition();
347 QSizeF threshold = q->viewportGeometry().size();
348 QPointF diff = pos - start;
349
350 //reduce if it's within the viewport
351 if (qAbs(diff.x()) < threshold.width() ||
352 qAbs(diff.y()) < threshold.height())
353 duration /= 2;
354
355 fixupAnimation.groupX->stop();
356 fixupAnimation.groupY->stop();
357 fixupAnimation.snapX->stop();
358 fixupAnimation.snapY->stop();
359
360 directMoveAnimation->setStartValue(start);
361 directMoveAnimation->setEndValue(pos);
362 directMoveAnimation->setDuration(duration);
363 directMoveAnimation->start();
364 }
365
366 void flick(QPropertyAnimation *anim,
367 qreal velocity,
368 qreal val,
369 qreal minExtent,
370 qreal maxExtent,
371 qreal size)
372 {
373 qreal deceleration = 500;
374 qreal maxDistance = -1;
375 qreal target = 0;
376 // -ve velocity means list is moving up
377 if (velocity > 0) {
378 if (val < minExtent)
379 maxDistance = qAbs(minExtent - val + (hasOvershoot?overShootDistance(velocity,size):0));
380 target = minExtent;
381 deceleration = -deceleration;
382 } else {
383 if (val > maxExtent)
384 maxDistance = qAbs(maxExtent - val) + (hasOvershoot?overShootDistance(velocity,size):0);
385 target = maxExtent;
386 }
387 if (maxDistance > 0) {
388 qreal v = velocity;
389 if (MaxVelocity != -1 && MaxVelocity < qAbs(v)) {
390 if (v < 0)
391 v = -MaxVelocity;
392 else
393 v = MaxVelocity;
394 }
395 qreal duration = qAbs(v / deceleration);
396 qreal diffY = v * duration + (0.5 * deceleration * duration * duration);
397 qreal startY = val;
398
399 qreal endY = startY + diffY;
400
401 if (velocity > 0) {
402 if (endY > target)
403 endY = startY + maxDistance;
404 } else {
405 if (endY < target)
406 endY = startY - maxDistance;
407 }
408 duration = qAbs((endY-startY)/ (-v/2));
409
410 if (hasYProperty) {
411 startY = -startY;
412 endY = -endY;
413 }
414
415
416#if DEBUG
417 qDebug()<<"XXX velocity = "<<v <<", target = "<< target
418 <<", maxDist = "<<maxDistance;
419 qDebug()<<"duration = "<<duration<<" secs, ("
420 << (duration * 1000) <<" msecs)";
421 qDebug()<<"startY = "<<startY;
422 qDebug()<<"endY = "<<endY;
423 qDebug()<<"overshoot = "<<overShootDistance(v, size);
424 qDebug()<<"avg velocity = "<< ((endY-startY)/duration);
425#endif
426
427 anim->setStartValue(startY);
428 anim->setEndValue(endY);
429 anim->setDuration(duration * 1000);
430 anim->start();
431 } else {
432 if (anim == flickAnimationX)
433 fixupX();
434 else
435 fixupY();
436 }
437 }
438 void flickX(qreal velocity)
439 {
440 flick(flickAnimationX, velocity, widgetX(), minXExtent(), maxXExtent(),
441 q->viewportGeometry().width());
442 }
443 void flickY(qreal velocity)
444 {
445 flick(flickAnimationY, velocity, widgetY(),minYExtent(), maxYExtent(),
446 q->viewportGeometry().height());
447 }
448 void fixup(QAnimationGroup *group,
449 QPropertyAnimation *start, QPropertyAnimation *end,
450 qreal val, qreal minExtent, qreal maxExtent)
451 {
452 if (val > minExtent || maxExtent > minExtent) {
453 if (!qFuzzyCompare(val, minExtent)) {
454 if (FixupDuration) {
455 //TODO: we should consider the case where there is one axis available not the other
456 if (hasXProperty && hasYProperty) {
457 val = -val;
458 minExtent = -minExtent;
459 }
460 qreal dist = minExtent - val;
461 start->setStartValue(val);
462 start->setEndValue(minExtent - dist/2);
463 end->setStartValue(minExtent - dist/2);
464 end->setEndValue(minExtent);
465 start->setDuration(FixupDuration/4);
466 end->setDuration(3*FixupDuration/4);
467 group->start();
468 } else {
469 QObject *obj = start->targetObject();
470 obj->setProperty(start->propertyName(), minExtent);
471 }
472 }
473 } else if (val < maxExtent) {
474 if (FixupDuration) {
475 if (hasXProperty && hasYProperty) {
476 val = -val;
477 maxExtent = -maxExtent;
478 }
479 qreal dist = maxExtent - val;
480 start->setStartValue(val);
481 start->setEndValue(maxExtent - dist/2);
482 end->setStartValue(maxExtent - dist/2);
483 end->setEndValue(maxExtent);
484 start->setDuration(FixupDuration/4);
485 end->setDuration(3*FixupDuration/4);
486 group->start();
487 } else {
488 QObject *obj = start->targetObject();
489 obj->setProperty(start->propertyName(), maxExtent);
490 }
491 } else if (end == fixupAnimation.endX && snapSize.width() > 1 &&
492 q->contentsSize().width() > q->viewportGeometry().width()) {
493 int target = snapSize.width() * round(val/snapSize.width());
494 fixupAnimation.snapX->setStartValue(val);
495 fixupAnimation.snapX->setEndValue(target);
496 fixupAnimation.snapX->setDuration(FixupDuration);
497 fixupAnimation.snapX->start();
498 } else if (end == fixupAnimation.endY && snapSize.height() > 1 &&
499 q->contentsSize().height() > q->viewportGeometry().height()) {
500 int target = snapSize.height() * round(val/snapSize.height());
501 fixupAnimation.snapY->setStartValue(val);
502 fixupAnimation.snapY->setEndValue(target);
503 fixupAnimation.snapY->setDuration(FixupDuration);
504 fixupAnimation.snapY->start();
505 }
506 }
507 void fixupX()
508 {
509 fixup(fixupAnimation.groupX, fixupAnimation.startX, fixupAnimation.endX,
510 widgetX(), minXExtent(), maxXExtent());
511 }
512 void fixupY()
513 {
514 fixup(fixupAnimation.groupY, fixupAnimation.startY, fixupAnimation.endY,
515 widgetY(), minYExtent(), maxYExtent());
516 }
517
518 void makeRectVisible()
519 {
520 if (!widget) {
521 return;
522 }
523
524 QRectF viewRect = scrollingWidget->boundingRect();
525 //ensure the rect is not outside the widget bounding rect
526 QRectF mappedRect = QRectF(QPointF(qBound((qreal)0.0, rectToBeVisible.x(), widget.data()->size().width() - rectToBeVisible.width()),
527 qBound((qreal)0.0, rectToBeVisible.y(), widget.data()->size().height() - rectToBeVisible.height())),
528 rectToBeVisible.size());
529 mappedRect = widget.data()->mapToItem(scrollingWidget, mappedRect).boundingRect();
530
531 if (viewRect.contains(mappedRect)) {
532 return;
533 }
534
535 QPointF delta(0, 0);
536
537 if (mappedRect.top() < 0) {
538 delta.setY(-mappedRect.top());
539 } else if (mappedRect.bottom() > viewRect.bottom()) {
540 delta.setY(viewRect.bottom() - mappedRect.bottom());
541 }
542
543 if (mappedRect.left() < 0) {
544 delta.setX(-mappedRect.left());
545 } else if (mappedRect.right() > viewRect.right()) {
546 delta.setX(viewRect.right() - mappedRect.right());
547 }
548
549 animateMoveTo(q->scrollPosition() - delta);
550 }
551
552 void makeItemVisible(QGraphicsItem *itemToBeVisible)
553 {
554 if (!widget) {
555 return;
556 }
557
558 QRectF rect(widget.data()->mapFromScene(itemToBeVisible->scenePos()), itemToBeVisible->boundingRect().size());
559 rectToBeVisible = rect;
560
561 makeRectVisible();
562 }
563
564 void makeItemVisible()
565 {
566 if (widgetToBeVisible) {
567 makeItemVisible(widgetToBeVisible.data());
568 }
569 }
570
571 void stopAnimations()
572 {
573 flickAnimationX->stop();
574 flickAnimationY->stop();
575 fixupAnimation.groupX->stop();
576 fixupAnimation.groupY->stop();
577 }
578
579 void setWidgetX(qreal x)
580 {
581 if (hasXProperty) {
582 widget.data()->setProperty("scrollPositionX", -x);
583 } else
584 widget.data()->setX(x);
585 }
586 void setWidgetY(qreal y)
587 {
588 if (hasYProperty) {
589 widget.data()->setProperty("scrollPositionY", -y);
590 } else
591 widget.data()->setY(y);
592 }
593 qreal widgetX() const
594 {
595 if (hasXProperty) {
596 return -widget.data()->property("scrollPositionX").toReal();
597 } else
598 return widget.data()->x();
599 }
600 qreal widgetY() const
601 {
602 if (hasYProperty) {
603 return -widget.data()->property("scrollPositionY").toReal();
604 } else
605 return widget.data()->y();
606 }
607
608 void handleKeyPressEvent(QKeyEvent *event)
609 {
610 if (!widget.data()) {
611 event->ignore();
612 return;
613 }
614
615 QPointF start = q->scrollPosition();
616 QPointF end = start;
617
618 qreal step = 100;
619
620 switch (event->key()) {
621 case Qt::Key_Left:
622 if (canXFlick()) {
623 end += QPointF(-step, 0);
624 }
625 break;
626 case Qt::Key_Right:
627 if (canXFlick()) {
628 end += QPointF(step, 0);
629 }
630 break;
631 case Qt::Key_Up:
632 if (canYFlick()) {
633 end += QPointF(0, -step);
634 }
635 break;
636 case Qt::Key_Down:
637 if (canYFlick()) {
638 end += QPointF(0, step);
639 }
640 break;
641 default:
642 event->ignore();
643 return;
644 }
645
646 fixupAnimation.groupX->stop();
647 fixupAnimation.groupY->stop();
648 fixupAnimation.snapX->stop();
649 fixupAnimation.snapY->stop();
650 directMoveAnimation->setStartValue(start);
651 directMoveAnimation->setEndValue(end);
652 directMoveAnimation->setDuration(200);
653 directMoveAnimation->start();
654 }
655
656 void handleMousePressEvent(QGraphicsSceneMouseEvent *event)
657 {
658 lastPos = QPoint();
659 lastPosTime = QTime::currentTime();
660 pressPos = event->scenePos();
661 pressScrollPos = -q->scrollPosition();
662 pressTime = QTime::currentTime();
663 velocity = QPointF();
664 stopAnimations();
665 }
666
667 void handleMouseMoveEvent(QGraphicsSceneMouseEvent *event)
668 {
669 if (lastPosTime.isNull())
670 return;
671 bool rejectY = false;
672 bool rejectX = false;
673 bool moved = false;
674
675 if (canYFlick()) {
676 int dy = int(event->scenePos().y() - pressPos.y());
677 if (qAbs(dy) > KGlobalSettings::dndEventDelay() || elapsed(pressTime) > 200) {
678 qreal newY = dy + pressScrollPos.y();
679 const qreal minY = minYExtent();
680 const qreal maxY = maxYExtent();
681 if (newY > minY)
682 newY = minY + (newY - minY) / 2;
683 if (newY < maxY && maxY - minY <= 0)
684 newY = maxY + (newY - maxY) / 2;
685 if (!hasOvershoot && (newY > minY || newY < maxY)) {
686 if (newY > minY)
687 newY = minY;
688 else if (newY < maxY)
689 newY = maxY;
690 else
691 rejectY = true;
692 }
693 if (!rejectY && stealEvent) {
694 setWidgetY(qRound(newY));
695 moved = true;
696 }
697 if (qAbs(dy) > KGlobalSettings::dndEventDelay())
698 stealEvent = true;
699 }
700 }
701
702 if (canXFlick()) {
703 int dx = int(event->scenePos().x() - pressPos.x());
704 if (qAbs(dx) > KGlobalSettings::dndEventDelay() || elapsed(pressTime) > 200) {
705 qreal newX = dx + pressScrollPos.x();
706 const qreal minX = minXExtent();
707 const qreal maxX = maxXExtent();
708 if (newX > minX)
709 newX = minX + (newX - minX) / 2;
710 if (newX < maxX && maxX - minX <= 0)
711 newX = maxX + (newX - maxX) / 2;
712 if (!hasOvershoot && (newX > minX || newX < maxX)) {
713 if (newX > minX)
714 newX = minX;
715 else if (newX < maxX)
716 newX = maxX;
717 else
718 rejectX = true;
719 }
720 if (!rejectX && stealEvent) {
721 setWidgetX(qRound(newX));
722 moved = true;
723 }
724
725 if (qAbs(dx) > KGlobalSettings::dndEventDelay())
726 stealEvent = true;
727 }
728 }
729
730 if (!lastPos.isNull()) {
731 qreal msecs = qreal(restart(lastPosTime));
732 qreal elapsed = msecs / 1000.;
733#if IGNORE_SUSPICIOUS_MOVES
734 if (msecs > 3) {
735#endif
736 if (elapsed <= 0)
737 elapsed = 1;
738 if (canYFlick()) {
739 qreal diff = event->scenePos().y() - lastPos.y();
740 // average to reduce the effect of spurious moves
741 velocity.setY( velocity.y() + (diff / elapsed) );
742 velocity.setY( velocity.y() / 2 );
743 }
744
745 if (canXFlick()) {
746 qreal diff = event->scenePos().x() - lastPos.x();
747 // average to reduce the effect of spurious moves
748 velocity.setX( velocity.x() + (diff / elapsed) );
749 velocity.setX( velocity.x() / 2 );
750 }
751#if IGNORE_SUSPICIOUS_MOVES
752 }
753#endif
754 }
755
756 if (rejectX) velocity.setX(0);
757 if (rejectY) velocity.setY(0);
758
759 lastPos = event->scenePos();
760 }
761
762 void handleMouseReleaseEvent(QGraphicsSceneMouseEvent *event)
763 {
764 stealEvent = false;
765 if (lastPosTime.isNull())
766 return;
767
768 if (elapsed(lastPosTime) > 100) {
769 // if we drag then pause before release we should not cause a flick.
770 velocity = QPointF();
771 }
772
773 if (qAbs(velocity.y()) > 10 &&
774 qAbs(event->scenePos().y() - pressPos.y()) > FlickThreshold) {
775 qreal vVelocity = velocity.y();
776 // Minimum velocity to avoid annoyingly slow flicks.
777 if (qAbs(vVelocity) < MinimumFlickVelocity)
778 vVelocity = vVelocity < 0 ? -MinimumFlickVelocity : MinimumFlickVelocity;
779 flickY(vVelocity);
780 } else {
781 fixupY();
782 }
783
784 if (qAbs(velocity.x()) > 10 &&
785 qAbs(event->scenePos().x() - pressPos.x()) > FlickThreshold) {
786 qreal hVelocity = velocity.x();
787 // Minimum velocity to avoid annoyingly slow flicks.
788 if (qAbs(hVelocity) < MinimumFlickVelocity)
789 hVelocity = hVelocity < 0 ? -MinimumFlickVelocity : MinimumFlickVelocity;
790 flickX(hVelocity);
791 } else {
792 fixupX();
793 }
794
795 lastPosTime = QTime();
796 }
797
798 void handleWheelEvent(QGraphicsSceneWheelEvent *event)
799 {
800 //only scroll when the animation is done, this avoids to receive too many events and getting mad when they arrive from a touchpad
801 if (!widget.data() || wheelTimer->isActive()) {
802 return;
803 }
804
805 QPointF start = q->scrollPosition();
806 QPointF end = start;
807
808 //At some point we should switch to
809 // step = QApplication::wheelScrollLines() *
810 // (event->delta()/120) *
811 // scrollBar->singleStep();
812 // which gives us exactly the number of lines to scroll but the issue
813 // is that at this point we don't have any clue what a "line" is and if
814 // we make it a pixel then scrolling by 3 (default) pixels will be
815 // very painful
816 qreal step = -event->delta()/3;
817
818 //ifthe widget can scroll in a single axis and the wheel is the other one, scroll the other one
819 Qt::Orientation orientation = event->orientation();
820 if (orientation == Qt::Vertical) {
821 if (!canYFlick() && canXFlick()) {
822 end += QPointF(step, 0);
823 } else if (canYFlick()) {
824 end += QPointF(0, step);
825 } else {
826 return;
827 }
828 } else {
829 if (canYFlick() && !canXFlick()) {
830 end += QPointF(0, step);
831 } else if (canXFlick()) {
832 end += QPointF(step, 0);
833 } else {
834 return;
835 }
836 }
837
838 fixupAnimation.groupX->stop();
839 fixupAnimation.groupY->stop();
840 fixupAnimation.snapX->stop();
841 fixupAnimation.snapY->stop();
842 directMoveAnimation->setStartValue(start);
843 directMoveAnimation->setEndValue(end);
844 directMoveAnimation->setDuration(200);
845 directMoveAnimation->start();
846 wheelTimer->start(50);
847 }
848
849 qreal minXExtent() const
850 {
851 if (alignment & Qt::AlignLeft)
852 return 0;
853 else {
854 qreal vWidth = q->viewportGeometry().width();
855 qreal cWidth = q->contentsSize().width();
856 if (cWidth < vWidth) {
857 if (alignment & Qt::AlignRight)
858 return vWidth - cWidth;
859 else if (alignment & Qt::AlignHCenter)
860 return vWidth / 2 - cWidth / 2;
861 }
862 }
863
864 return 0;
865 }
866
867 qreal maxXExtent() const
868 {
869 return q->viewportGeometry().width() -
870 q->contentsSize().width();
871 }
872
873 qreal minYExtent() const
874 {
875 if (alignment & Qt::AlignTop)
876 return 0;
877 else {
878 qreal vHeight = q->viewportGeometry().height();
879 qreal cHeight = q->contentsSize().height();
880 if (cHeight < vHeight) {
881 if (alignment & Qt::AlignBottom)
882 return vHeight - cHeight;
883 else if (alignment & Qt::AlignVCenter)
884 return vHeight / 2 - cHeight / 2;
885 }
886 }
887
888 return 0;
889 }
890
891 qreal maxYExtent() const
892 {
893 return q->viewportGeometry().height() -
894 q->contentsSize().height();
895 }
896
897 bool canXFlick() const
898 {
899 //make the thing feel quite "fixed" don't permit to flick when the contents size is less than the viewport
900 return q->contentsSize().width() > q->viewportGeometry().width();
901 }
902
903 bool canYFlick() const
904 {
905 return q->contentsSize().height() > q->viewportGeometry().height();
906 }
907
908 int elapsed(const QTime &t) const
909 {
910 int n = t.msecsTo(QTime::currentTime());
911 if (n < 0) // passed midnight
912 n += 86400 * 1000;
913 return n;
914 }
915
916 int restart(QTime &t) const
917 {
918 QTime time = QTime::currentTime();
919 int n = t.msecsTo(time);
920 if (n < 0) // passed midnight
921 n += 86400*1000;
922 t = time;
923 return n;
924 }
925
926 void createFlickAnimations()
927 {
928 if (widget.data()) {
929 QString xProp = QString::fromLatin1("x");
930 QString yProp = QString::fromLatin1("y");
931
932 if (hasXProperty)
933 xProp = QString::fromLatin1("scrollPositionX");
934 if (hasYProperty)
935 yProp = QString::fromLatin1("scrollPositionY");
936
937 flickAnimationX = new QPropertyAnimation(widget.data(),
938 xProp.toLatin1(), widget.data());
939 flickAnimationY = new QPropertyAnimation(widget.data(),
940 yProp.toLatin1(), widget.data());
941 QObject::connect(flickAnimationX, SIGNAL(finished()),
942 q, SLOT(fixupX()));
943 QObject::connect(flickAnimationY, SIGNAL(finished()),
944 q, SLOT(fixupY()));
945
946 QObject::connect(flickAnimationX,
947 SIGNAL(stateChanged(QAbstractAnimation::State,
948 QAbstractAnimation::State)),
949 q, SIGNAL(scrollStateChanged(QAbstractAnimation::State,
950 QAbstractAnimation::State)));
951 QObject::connect(flickAnimationY,
952 SIGNAL(stateChanged(QAbstractAnimation::State,
953 QAbstractAnimation::State)),
954 q, SIGNAL(scrollStateChanged(QAbstractAnimation::State,
955 QAbstractAnimation::State)));
956
957 flickAnimationX->setEasingCurve(QEasingCurve::OutCirc);
958 flickAnimationY->setEasingCurve(QEasingCurve::OutCirc);
959
960
961 fixupAnimation.groupX = new QSequentialAnimationGroup(widget.data());
962 fixupAnimation.groupY = new QSequentialAnimationGroup(widget.data());
963 fixupAnimation.startX = new QPropertyAnimation(widget.data(),
964 xProp.toLatin1(), widget.data());
965 fixupAnimation.startY = new QPropertyAnimation(widget.data(),
966 yProp.toLatin1(), widget.data());
967 fixupAnimation.endX = new QPropertyAnimation(widget.data(),
968 xProp.toLatin1(), widget.data());
969 fixupAnimation.endY = new QPropertyAnimation(widget.data(),
970 yProp.toLatin1(), widget.data());
971 fixupAnimation.groupX->addAnimation(
972 fixupAnimation.startX);
973 fixupAnimation.groupY->addAnimation(
974 fixupAnimation.startY);
975 fixupAnimation.groupX->addAnimation(
976 fixupAnimation.endX);
977 fixupAnimation.groupY->addAnimation(
978 fixupAnimation.endY);
979
980 fixupAnimation.startX->setEasingCurve(QEasingCurve::InQuad);
981 fixupAnimation.endX->setEasingCurve(QEasingCurve::OutQuint);
982 fixupAnimation.startY->setEasingCurve(QEasingCurve::InQuad);
983 fixupAnimation.endY->setEasingCurve(QEasingCurve::OutQuint);
984
985 fixupAnimation.snapX = new QPropertyAnimation(widget.data(),
986 xProp.toLatin1(), widget.data());
987 fixupAnimation.snapY = new QPropertyAnimation(widget.data(),
988 yProp.toLatin1(), widget.data());
989 fixupAnimation.snapX->setEasingCurve(QEasingCurve::InOutQuad);
990 fixupAnimation.snapY->setEasingCurve(QEasingCurve::InOutQuad);
991
992 QObject::connect(fixupAnimation.groupX,
993 SIGNAL(stateChanged(QAbstractAnimation::State,
994 QAbstractAnimation::State)),
995 q, SIGNAL(scrollStateChanged(QAbstractAnimation::State,
996 QAbstractAnimation::State)));
997 QObject::connect(fixupAnimation.groupY,
998 SIGNAL(stateChanged(QAbstractAnimation::State,
999 QAbstractAnimation::State)),
1000 q, SIGNAL(scrollStateChanged(QAbstractAnimation::State,
1001 QAbstractAnimation::State)));
1002
1003 directMoveAnimation = new QPropertyAnimation(q,
1004 "scrollPosition",
1005 q);
1006 QObject::connect(directMoveAnimation, SIGNAL(finished()),
1007 q, SLOT(fixupX()));
1008 QObject::connect(directMoveAnimation, SIGNAL(finished()),
1009 q, SLOT(fixupY()));
1010 QObject::connect(directMoveAnimation,
1011 SIGNAL(stateChanged(QAbstractAnimation::State,
1012 QAbstractAnimation::State)),
1013 q, SIGNAL(scrollStateChanged(QAbstractAnimation::State,
1014 QAbstractAnimation::State)));
1015 directMoveAnimation->setEasingCurve(QEasingCurve::OutCirc);
1016 }
1017 }
1018
1019 void deleteFlickAnimations()
1020 {
1021 if (flickAnimationX)
1022 flickAnimationX->stop();
1023 if (flickAnimationY)
1024 flickAnimationY->stop();
1025 delete flickAnimationX;
1026 delete flickAnimationY;
1027 delete fixupAnimation.groupX;
1028 delete fixupAnimation.groupY;
1029 delete directMoveAnimation;
1030 delete fixupAnimation.snapX;
1031 delete fixupAnimation.snapY;
1032 }
1033
1034 void setScrollX()
1035 {
1036 if (horizontalScrollBarPolicy != Qt::ScrollBarAlwaysOff) {
1037 horizontalScrollBar->blockSignals(true);
1038 horizontalScrollBar->setValue(-widget.data()->pos().x()/10.);
1039 horizontalScrollBar->blockSignals(false);
1040 }
1041 }
1042
1043 void setScrollY()
1044 {
1045 if (verticalScrollBarPolicy != Qt::ScrollBarAlwaysOff) {
1046 verticalScrollBar->blockSignals(true);
1047 verticalScrollBar->setValue(-widget.data()->pos().y()/10.);
1048 verticalScrollBar->blockSignals(false);
1049 }
1050 }
1051
1052 ScrollWidget *q;
1053 QGraphicsWidget *scrollingWidget;
1054 QWeakPointer<QGraphicsWidget> widget;
1055 Plasma::Svg *borderSvg;
1056 Plasma::SvgWidget *topBorder;
1057 Plasma::SvgWidget *bottomBorder;
1058 Plasma::SvgWidget *leftBorder;
1059 Plasma::SvgWidget *rightBorder;
1060 QGraphicsGridLayout *layout;
1061 ScrollBar *verticalScrollBar;
1062 Qt::ScrollBarPolicy verticalScrollBarPolicy;
1063 ScrollBar *horizontalScrollBar;
1064 Qt::ScrollBarPolicy horizontalScrollBarPolicy;
1065 QString styleSheet;
1066 QWeakPointer<QGraphicsWidget> widgetToBeVisible;
1067 QRectF rectToBeVisible;
1068 QPointF dragHandleClicked;
1069 bool dragging;
1070 QTimer *adjustScrollbarsTimer;
1071 QTimer *wheelTimer;
1072
1073 QPointF pressPos;
1074 QPointF pressScrollPos;
1075 QPointF velocity;
1076 QPointF lastPos;
1077 QTime pressTime;
1078 QTime lastPosTime;
1079 QPropertyAnimation *flickAnimationX;
1080 QPropertyAnimation *flickAnimationY;
1081 struct {
1082 QAnimationGroup *groupX;
1083 QPropertyAnimation *startX;
1084 QPropertyAnimation *endX;
1085
1086 QAnimationGroup *groupY;
1087 QPropertyAnimation *startY;
1088 QPropertyAnimation *endY;
1089
1090 QPropertyAnimation *snapX;
1091 QPropertyAnimation *snapY;
1092 } fixupAnimation;
1093 QPropertyAnimation *directMoveAnimation;
1094 QSizeF snapSize;
1095 bool stealEvent;
1096 bool hasOvershoot;
1097 bool overflowBordersVisible;
1098
1099 Qt::Alignment alignment;
1100
1101 Gesture multitouchGesture;
1102
1103 bool hasContentsProperty;
1104 bool hasOffsetProperty;
1105 bool hasXProperty;
1106 bool hasYProperty;
1107};
1108
1109
1110ScrollWidget::ScrollWidget(QGraphicsItem *parent)
1111 : QGraphicsWidget(parent),
1112 d(new ScrollWidgetPrivate(this))
1113{
1114 d->commonConstructor();
1115}
1116
1117ScrollWidget::ScrollWidget(QGraphicsWidget *parent)
1118 : QGraphicsWidget(parent),
1119 d(new ScrollWidgetPrivate(this))
1120{
1121 d->commonConstructor();
1122}
1123
1124ScrollWidget::~ScrollWidget()
1125{
1126 delete d;
1127}
1128
1129void ScrollWidget::setWidget(QGraphicsWidget *widget)
1130{
1131 if (d->widget && d->widget.data() != widget) {
1132 d->deleteFlickAnimations();
1133 d->widget.data()->removeEventFilter(this);
1134 delete d->widget.data();
1135 }
1136
1137 d->widget = widget;
1138 //it's not good it's setting a size policy here, but it's done to be retrocompatible with older applications
1139 if (widget) {
1140 d->hasContentsProperty = widget->property("contentsSize").isValid();
1141 d->hasOffsetProperty = widget->property("scrollPosition").isValid();
1142 d->hasXProperty = widget->property("scrollPositionX").isValid();
1143 d->hasYProperty = widget->property("scrollPositionY").isValid();
1144 d->createFlickAnimations();
1145
1146 connect(widget, SIGNAL(xChanged()), this, SLOT(setScrollX()));
1147 connect(widget, SIGNAL(yChanged()), this, SLOT(setScrollY()));
1148 widget->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed);
1149 widget->setParentItem(d->scrollingWidget);
1150 widget->setPos(d->minXExtent(), d->minYExtent());
1151 widget->installEventFilter(this);
1152 d->adjustScrollbarsTimer->start(200);
1153 }
1154}
1155
1156QGraphicsWidget *ScrollWidget::widget() const
1157{
1158 return d->widget.data();
1159}
1160
1161
1162void ScrollWidget::setHorizontalScrollBarPolicy(const Qt::ScrollBarPolicy policy)
1163{
1164 d->horizontalScrollBarPolicy = policy;
1165}
1166
1167
1168Qt::ScrollBarPolicy ScrollWidget::horizontalScrollBarPolicy() const
1169{
1170 return d->horizontalScrollBarPolicy;
1171}
1172
1173
1174void ScrollWidget::setVerticalScrollBarPolicy(const Qt::ScrollBarPolicy policy)
1175{
1176 d->verticalScrollBarPolicy = policy;
1177}
1178
1179Qt::ScrollBarPolicy ScrollWidget::verticalScrollBarPolicy() const
1180{
1181 return d->verticalScrollBarPolicy;
1182}
1183
1184bool ScrollWidget::overflowBordersVisible() const
1185{
1186 return d->overflowBordersVisible;
1187}
1188
1189void ScrollWidget::setOverflowBordersVisible(const bool visible)
1190{
1191 if (d->overflowBordersVisible == visible) {
1192 return;
1193 }
1194
1195 d->overflowBordersVisible = visible;
1196 d->adjustScrollbars();
1197}
1198
1199void ScrollWidget::ensureRectVisible(const QRectF &rect)
1200{
1201 if (!d->widget) {
1202 return;
1203 }
1204
1205 d->rectToBeVisible = rect;
1206 d->makeRectVisible();
1207}
1208
1209void ScrollWidget::ensureItemVisible(QGraphicsItem *item)
1210{
1211 if (!d->widget || !item) {
1212 return;
1213 }
1214
1215 QGraphicsItem *parentOfItem = item->parentItem();
1216 while (parentOfItem != d->widget.data()) {
1217 if (!parentOfItem) {
1218 return;
1219 }
1220
1221 parentOfItem = parentOfItem->parentItem();
1222 }
1223
1224 //since we can't ensure it'll stay alive we can delay only if it's a qgraphicswidget
1225 QGraphicsWidget *widget = qgraphicsitem_cast<QGraphicsWidget *>(item);
1226 if (widget) {
1227 d->widgetToBeVisible = widget;
1228
1229 // We need to wait for the parent item to resize...
1230 QTimer::singleShot(0, this, SLOT(makeItemVisible()));
1231 } else {
1232 d->makeItemVisible(item);
1233 }
1234}
1235
1236#ifndef KDE_NO_DEPRECATED
1237void ScrollWidget::registerAsDragHandle(QGraphicsWidget *item)
1238{
1239 Q_UNUSED(item);
1240 return;
1241}
1242#endif
1243
1244#ifndef KDE_NO_DEPRECATED
1245void ScrollWidget::unregisterAsDragHandle(QGraphicsWidget *item)
1246{
1247 Q_UNUSED(item);
1248 return;
1249}
1250#endif
1251
1252QRectF ScrollWidget::viewportGeometry() const
1253{
1254 QRectF result;
1255 if (!d->widget) {
1256 return result;
1257 }
1258
1259 return d->scrollingWidget->boundingRect();
1260}
1261
1262QSizeF ScrollWidget::contentsSize() const
1263{
1264 if (d->widget) {
1265 if (d->hasContentsProperty) {
1266 QVariant var = d->widget.data()->property("contentsSize");
1267 return var.toSizeF();
1268 } else
1269 return d->widget.data()->size();
1270 }
1271 return QSizeF();
1272}
1273
1274void ScrollWidget::setScrollPosition(const QPointF &position)
1275{
1276 if (d->widget) {
1277 if (d->hasOffsetProperty)
1278 d->widget.data()->setProperty("scrollPosition", position);
1279 else
1280 d->widget.data()->setPos(-position.toPoint());
1281 }
1282}
1283
1284QPointF ScrollWidget::scrollPosition() const
1285{
1286 if (d->widget) {
1287 if (d->hasOffsetProperty) {
1288 QVariant var = d->widget.data()->property("scrollPosition");
1289 return var.toPointF();
1290 } else {
1291 return -d->widget.data()->pos();
1292 }
1293 }
1294 return QPointF();
1295}
1296
1297void ScrollWidget::setSnapSize(const QSizeF &size)
1298{
1299 d->snapSize = size;
1300}
1301
1302QSizeF ScrollWidget::snapSize() const
1303{
1304 return d->snapSize;
1305}
1306
1307void ScrollWidget::setStyleSheet(const QString &styleSheet)
1308{
1309 d->styleSheet = styleSheet;
1310 d->verticalScrollBar->setStyleSheet(styleSheet);
1311 d->horizontalScrollBar->setStyleSheet(styleSheet);
1312}
1313
1314QString ScrollWidget::styleSheet() const
1315{
1316 return d->styleSheet;
1317}
1318
1319QWidget *ScrollWidget::nativeWidget() const
1320{
1321 return 0;
1322}
1323
1324void ScrollWidget::focusInEvent(QFocusEvent *event)
1325{
1326 Q_UNUSED(event)
1327
1328 if (d->widget) {
1329 d->widget.data()->setFocus();
1330 }
1331}
1332
1333
1334void ScrollWidget::resizeEvent(QGraphicsSceneResizeEvent *event)
1335{
1336 if (!d->widget) {
1337 QGraphicsWidget::resizeEvent(event);
1338 return;
1339 }
1340
1341 d->adjustScrollbarsTimer->start(200);
1342
1343 //if topBorder exists bottomBorder too
1344 if (d->topBorder) {
1345 d->topBorder->resize(event->newSize().width(), d->topBorder->size().height());
1346 d->bottomBorder->resize(event->newSize().width(), d->bottomBorder->size().height());
1347 d->bottomBorder->setPos(0, event->newSize().height() - d->bottomBorder->size().height());
1348 }
1349 if (d->leftBorder) {
1350 d->leftBorder->resize(d->leftBorder->size().width(), event->newSize().height());
1351 d->rightBorder->resize(d->rightBorder->size().width(), event->newSize().height());
1352 d->rightBorder->setPos(event->newSize().width() - d->rightBorder->size().width(), 0);
1353 }
1354
1355 QGraphicsWidget::resizeEvent(event);
1356}
1357
1358void ScrollWidget::keyPressEvent(QKeyEvent *event)
1359{
1360 d->handleKeyPressEvent(event);
1361}
1362
1363void ScrollWidget::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
1364{
1365 if (!d->widget) {
1366 return;
1367 }
1368
1369 d->handleMouseMoveEvent(event);
1370 event->accept();
1371
1372 return QGraphicsWidget::mouseMoveEvent(event);
1373}
1374
1375void ScrollWidget::mousePressEvent(QGraphicsSceneMouseEvent *event)
1376{
1377 if (!d->widget) {
1378 return;
1379 } else if (!d->canYFlick() && !d->canXFlick()) {
1380 event->ignore();
1381 return;
1382 }
1383
1384 d->handleMousePressEvent(event);
1385
1386 if (event->button() == Qt::LeftButton) {
1387 event->accept();
1388 } else {
1389 QGraphicsWidget::mousePressEvent(event);
1390 }
1391}
1392
1393void ScrollWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
1394{
1395 if (!d->widget) {
1396 return;
1397 }
1398
1399 d->handleMouseReleaseEvent(event);
1400 event->accept();
1401}
1402
1403void ScrollWidget::wheelEvent(QGraphicsSceneWheelEvent *event)
1404{
1405 if (!d->widget) {
1406 return;
1407 } else if (!d->canYFlick() && !d->canXFlick()) {
1408 event->ignore();
1409 return;
1410 }
1411 d->handleWheelEvent(event);
1412 event->accept();
1413}
1414
1415bool ScrollWidget::eventFilter(QObject *watched, QEvent *event)
1416{
1417 if (!d->widget) {
1418 return false;
1419 }
1420
1421 if (watched == d->scrollingWidget && (event->type() == QEvent::GraphicsSceneResize ||
1422 event->type() == QEvent::Move)) {
1423 emit viewportGeometryChanged(viewportGeometry());
1424 } else if (watched == d->widget.data() && event->type() == QEvent::GraphicsSceneResize) {
1425 d->stopAnimations();
1426 d->adjustScrollbarsTimer->start(200);
1427 updateGeometry();
1428
1429 QPointF newPos = d->widget.data()->pos();
1430 if (d->widget.data()->size().width() <= viewportGeometry().width()) {
1431 newPos.setX(d->minXExtent());
1432 }
1433 if (d->widget.data()->size().height() <= viewportGeometry().height()) {
1434 newPos.setY(d->minYExtent());
1435 }
1436 //check if the content is visible
1437 if (d->widget.data()->geometry().right() < 0) {
1438 newPos.setX(-d->widget.data()->geometry().width()+viewportGeometry().width());
1439 }
1440 if (d->widget.data()->geometry().bottom() < 0) {
1441 newPos.setY(-d->widget.data()->geometry().height()+viewportGeometry().height());
1442 }
1443 d->widget.data()->setPos(newPos);
1444
1445 } else if (watched == d->widget.data() && event->type() == QEvent::GraphicsSceneMove) {
1446 d->horizontalScrollBar->blockSignals(true);
1447 d->verticalScrollBar->blockSignals(true);
1448 d->horizontalScrollBar->setValue(-d->widget.data()->pos().x()/10);
1449 d->verticalScrollBar->setValue(-d->widget.data()->pos().y()/10);
1450 d->horizontalScrollBar->blockSignals(false);
1451 d->verticalScrollBar->blockSignals(false);
1452 }
1453
1454 return false;
1455}
1456
1457QSizeF ScrollWidget::sizeHint(Qt::SizeHint which, const QSizeF & constraint) const
1458{
1459 if (!d->widget || which == Qt::MaximumSize) {
1460 return QGraphicsWidget::sizeHint(which, constraint);
1461 //FIXME: it should ake the minimum hint of the contained widget, but the result is in a ridiculously big widget
1462 } else if (which == Qt::MinimumSize) {
1463 return QSizeF(KIconLoader::SizeEnormous, KIconLoader::SizeEnormous);
1464 }
1465
1466 QSizeF hint = d->widget.data()->effectiveSizeHint(which, constraint);
1467 if (d->horizontalScrollBar && d->horizontalScrollBar->isVisible()) {
1468 hint += QSize(0, d->horizontalScrollBar->size().height());
1469 }
1470 if (d->verticalScrollBar && d->verticalScrollBar->isVisible()) {
1471 hint += QSize(d->verticalScrollBar->size().width(), 0);
1472 }
1473
1474 return hint;
1475}
1476
1477
1478bool ScrollWidget::sceneEventFilter(QGraphicsItem *i, QEvent *e)
1479{
1480 //only the scrolling widget and its children
1481 if (!d->widget.data() ||
1482 (!d->scrollingWidget->isAncestorOf(i) && i != d->scrollingWidget) ||
1483 i == d->horizontalScrollBar || i == d->verticalScrollBar) {
1484 return false;
1485 }
1486
1487 if (i->isWidget()) {
1488 Plasma::Label *label = dynamic_cast<Plasma::Label *>(static_cast<QGraphicsWidget *>(i));
1489 if (label && (label->nativeWidget()->textInteractionFlags() & Qt::TextSelectableByMouse)) {
1490 return false;
1491 }
1492
1493 Plasma::TextEdit *textEdit = dynamic_cast<Plasma::TextEdit *>(static_cast<QGraphicsWidget *>(i));
1494 if (textEdit && (textEdit->nativeWidget()->textInteractionFlags() & Qt::TextSelectableByMouse)) {
1495 return false;
1496 }
1497
1498 Plasma::TextBrowser *textBrowser= dynamic_cast<Plasma::TextBrowser *>(static_cast<QGraphicsWidget *>(i));
1499 if (textBrowser && (textBrowser->nativeWidget()->textInteractionFlags() & Qt::TextSelectableByMouse)) {
1500 return false;
1501 }
1502 }
1503
1504 bool stealThisEvent = d->stealEvent;
1505 //still pass around mouse moves: try to make still possible to make items start a drag event. thi could be either necessary or annoying, let's see how it goes. (add QEvent::GraphicsSceneMouseMove to block them)
1506 stealThisEvent &= (e->type() == QEvent::GraphicsSceneMousePress ||
1507 e->type() == QEvent::GraphicsSceneMouseRelease);
1508#if DEBUG
1509 qDebug()<<"sceneEventFilter = " <<i<<", "
1510 <<QTime::currentTime().toString(QString::fromLatin1("hh:mm:ss.zzz"));
1511#endif
1512 switch (e->type()) {
1513 case QEvent::GraphicsSceneMousePress:
1514 d->handleMousePressEvent(static_cast<QGraphicsSceneMouseEvent*>(e));
1515 break;
1516 case QEvent::GraphicsSceneMouseMove:
1517 d->handleMouseMoveEvent(static_cast<QGraphicsSceneMouseEvent*>(e));
1518 break;
1519 case QEvent::GraphicsSceneMouseRelease:
1520 d->handleMouseReleaseEvent(static_cast<QGraphicsSceneMouseEvent*>(e));
1521 break;
1522
1523 //Multitouch related events, we actually need only TouchUpdate
1524 case QEvent::TouchUpdate: {
1525 QList<QTouchEvent::TouchPoint> touchPoints = static_cast<QTouchEvent *>(e)->touchPoints();
1526 if (touchPoints.count() == 2) {
1527 const QTouchEvent::TouchPoint &touchPoint0 = touchPoints.first();
1528 const QTouchEvent::TouchPoint &touchPoint1 = touchPoints.last();
1529 const QLineF line0(touchPoint0.lastPos(), touchPoint1.lastPos());
1530 const QLineF line1(touchPoint0.pos(), touchPoint1.pos());
1531 const QLineF startLine(touchPoint0.startPos(), touchPoint1.startPos());
1532 const QPointF point = line1.pointAt(0.5);
1533 const QPointF lastPoint = line0.pointAt(0.5);
1534
1535 if (d->multitouchGesture == ScrollWidgetPrivate::GestureNone) {
1536 d->multitouchGesture = ScrollWidgetPrivate::GestureUndefined;
1537 }
1538 if (d->multitouchGesture == ScrollWidgetPrivate::GestureUndefined) {
1539 const int zoomDistance = qAbs(line1.length() - startLine.length());
1540 const int dragDistance = (startLine.pointAt(0.5) - point).manhattanLength();
1541
1542 if (zoomDistance - dragDistance > 30) {
1543 d->multitouchGesture = ScrollWidgetPrivate::GestureZoom;
1544 } else if (dragDistance - zoomDistance > 30) {
1545 d->multitouchGesture = ScrollWidgetPrivate::GestureScroll;
1546 }
1547 }
1548
1549 if (d->multitouchGesture == ScrollWidgetPrivate::GestureScroll) {
1550 QGraphicsSceneMouseEvent fakeEvent;
1551 fakeEvent.setPos(point);
1552 fakeEvent.setLastPos(lastPoint);
1553 d->handleMouseMoveEvent(&fakeEvent);
1554 } else if (d->multitouchGesture == ScrollWidgetPrivate::GestureZoom) {
1555 if (d->widget && d->widget.data()->property("zoomFactor").isValid()) {
1556 qreal scaleFactor = 1;
1557 if (line0.length() > 0) {
1558 scaleFactor = line1.length() / line0.length();
1559 }
1560
1561 qreal zoom = d->widget.data()->property("zoomFactor").toReal();
1562 d->widget.data()->setProperty("zoomFactor", zoom * scaleFactor);
1563 }
1564 }
1565 }
1566 break;
1567 }
1568 default:
1569 break;
1570 }
1571 if (stealThisEvent)
1572 return true;
1573 return QGraphicsWidget::sceneEventFilter(i, e);
1574}
1575
1576void Plasma::ScrollWidget::setAlignment(Qt::Alignment align)
1577{
1578 d->alignment = align;
1579 if (d->widget.data() &&
1580 d->widget.data()->isVisible()) {
1581 d->widget.data()->setPos(d->minXExtent(),
1582 d->minYExtent());
1583 }
1584}
1585
1586Qt::Alignment Plasma::ScrollWidget::alignment() const
1587{
1588 return d->alignment;
1589}
1590
1591void ScrollWidget::setOverShoot(bool enable)
1592{
1593 d->hasOvershoot = enable;
1594}
1595
1596bool ScrollWidget::hasOverShoot() const
1597{
1598 return d->hasOvershoot;
1599}
1600
1601} // namespace Plasma
1602
1603
1604#include <scrollwidget.moc>
1605
animator.h
Plasma::Label
Provides a plasma-themed QLabel.
Definition: label.h:41
Plasma::Label::nativeWidget
QLabel * nativeWidget
Definition: label.h:52
Plasma::ScrollBar
Provides a plasma-themed QScrollBar.
Definition: scrollbar.h:40
Plasma::ScrollWidget::setAlignment
void setAlignment(Qt::Alignment align)
Sets the alignment for the inner widget.
Definition: scrollwidget.cpp:1576
Plasma::ScrollWidget::widget
QGraphicsWidget * widget
Definition: scrollwidget.h:46
Plasma::ScrollWidget::viewportGeometry
QRectF viewportGeometry
Definition: scrollwidget.h:52
Plasma::ScrollWidget::viewportGeometryChanged
void viewportGeometryChanged(const QRectF &geomety)
The viewport geomety changed, for instance due a widget resize.
Plasma::ScrollWidget::alignment
Qt::Alignment alignment
Definition: scrollwidget.h:55
Plasma::ScrollWidget::styleSheet
QString styleSheet
Definition: scrollwidget.h:54
Plasma::SvgWidget
Definition: svgwidget.h:40
Plasma::Svg
A theme aware image-centric SVG class.
Definition: svg.h:57
Plasma::TextBrowser
Provides a plasma-themed KTextBrowser.
Definition: textbrowser.h:43
Plasma::TextBrowser::nativeWidget
KTextBrowser * nativeWidget
Definition: textbrowser.h:49
Plasma::TextEdit
Provides a plasma-themed KTextEdit.
Definition: textedit.h:41
Plasma::TextEdit::nativeWidget
KTextEdit * nativeWidget
Definition: textedit.h:47
QGraphicsWidget
QObject
QWidget
label.h
Plasma
Namespace for everything in libplasma.
Definition: abstractdialogmanager.cpp:25
scrollbar.h
MaxVelocity
static const qreal MaxVelocity
Definition: scrollwidget.cpp:83
FlickThreshold
static const int FlickThreshold
Definition: scrollwidget.cpp:79
MinimumFlickVelocity
static const qreal MinimumFlickVelocity
Definition: scrollwidget.cpp:82
FixupDuration
static const qreal FixupDuration
Definition: scrollwidget.cpp:87
scrollwidget.h
svg.h
svgwidget.h
textbrowser.h
textedit.h
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Mon Feb 20 2023 00:00:00 by doxygen 1.9.6 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

Plasma

Skip menu "Plasma"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Related Pages

kdelibs-4.14.38 API Reference

Skip menu "kdelibs-4.14.38 API Reference"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDEWebKit
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • kio
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • KPty
  • Kross
  • KUnitConversion
  • KUtils
  • Nepomuk
  • Plasma
  • Solid
  • Sonnet
  • ThreadWeaver
Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal