pannableview.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677
  1. //This file is part of the Enume project which provides libraries for
  2. //extending Qt functionality.
  3. //
  4. //Copyright (C) 2010 Marko Mattila, marko.a.mattila@gmail.com
  5. //
  6. //This library is free software; you can redistribute it and/or
  7. //modify it under the terms of the GNU Lesser General Public
  8. //License as published by the Free Software Foundation; either
  9. //version 2.1 of the License, or (at your option) any later version.
  10. //
  11. //This library is distributed in the hope that it will be useful,
  12. //but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. //Lesser General Public License for more details.
  15. //
  16. //You should have received a copy of the GNU Lesser General Public
  17. //License along with this library; if not, write to the Free Software
  18. //Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
  19. #include "pannableview.h"
  20. #include "pannableview_p.h"
  21. #include <QGraphicsScene>
  22. #include <QGraphicsWidget>
  23. #include <QTime>
  24. #include <QPointF>
  25. #include <QTimeLine>
  26. #include <QtDebug>
  27. #include <math.h>
  28. #include <QPaintEvent>
  29. #include <QGraphicsSceneMouseEvent>
  30. #include <QGraphicsLayout>
  31. #include <QGraphicsLinearLayout>
  32. #include <QRectF>
  33. #include <QCoreApplication>
  34. #include <QPropertyAnimation>
  35. #include <QCoreApplication>
  36. #include <QGraphicsView>
  37. #include <QList>
  38. #include <QPropertyAnimation>
  39. #define PANNABLE_FRICTION_COEFFICIENT 0.15 * 9.81 // F = y*N, 0.1 = friction coefficient for ice on ice.
  40. #define TAP_GESTURE_THRESHOLD 20
  41. #define VELOCITY_THRESHOLD 0.5
  42. #define MSECS 1000
  43. #define SCROLL_MIN_THRESHOLD 0
  44. #define SCROLL_MAX_THRESHOLD 0.35
  45. #define SLOW_PERCENT -0.4
  46. #define SWIPE_GESTURE_THRESHOLD 150
  47. PannableViewPrivate::PannableViewPrivate( PannableView * parent ):
  48. q_ptr (parent ),
  49. m_deltaX ( 0 ),
  50. m_deltaY ( 0 ),
  51. m_vX ( 0 ),
  52. m_vY ( 0 ),
  53. m_minX ( 0 ),
  54. m_minY ( 0 ),
  55. m_maxX ( 0 ),
  56. m_maxY ( 0 ),
  57. m_time ( ),
  58. m_scrollingTimeLine( 0 ),
  59. m_orientation ( 0 ),
  60. m_skipTapGesture ( false ),
  61. m_pannableWidget ( 0 ),
  62. m_enableIndicator ( true ),
  63. m_enableContinuousScrolling( false )
  64. {
  65. m_scrollingTimeLine = new QTimeLine( MSECS, this );
  66. m_scrollingTimeLine->setCurveShape( QTimeLine::LinearCurve );
  67. m_scrollingTimeLine->setUpdateInterval(30);
  68. connect( m_scrollingTimeLine, SIGNAL(valueChanged(qreal)), this, SLOT(scroll(qreal)));
  69. connect( m_scrollingTimeLine, SIGNAL(finished()), this, SLOT(emitPositionChanged()));
  70. }
  71. PannableViewPrivate::~PannableViewPrivate()
  72. {
  73. }
  74. void PannableViewPrivate::startScrolling( int duration )
  75. {
  76. m_scrollingTimeLine->setCurrentTime(0);
  77. m_scrollingTimeLine->setDuration( duration );
  78. m_scrollingTimeLine->start();
  79. }
  80. void PannableViewPrivate::stopScrolling( int clear )
  81. {
  82. m_scrollingTimeLine->stop();
  83. if ( clear ){
  84. m_scrollingTimeLine->setCurrentTime(0);
  85. m_vX = 0;
  86. m_vY = 0;
  87. }
  88. emitPositionChanged();
  89. }
  90. bool PannableViewPrivate::mousePressEvent( QGraphicsSceneMouseEvent * event ){
  91. Q_UNUSED(event);
  92. emit stopAnimation();
  93. if ( event->button() != Qt::LeftButton ){
  94. event->ignore();
  95. return false;
  96. }
  97. if (m_scrollingTimeLine->state() == QTimeLine::Running ){
  98. stopScrolling(false);
  99. m_skipTapGesture = true; // We don't want to stop and tap. We want stop or tap
  100. }
  101. m_time.start();
  102. return true;
  103. }
  104. bool PannableViewPrivate::mouseMoveEvent( QGraphicsSceneMouseEvent * event ){
  105. qreal deltaX = 0;
  106. qreal deltaY = 0;
  107. QPointF delta = event->pos() - event->lastPos();
  108. if ( m_orientation & Qt::Horizontal ){
  109. deltaX = delta.x();
  110. }
  111. if ( m_orientation & Qt::Vertical ){
  112. deltaY = delta.y();
  113. }
  114. // translate the view by delta
  115. m_pannableWidget->moveBy(deltaX, deltaY);
  116. return true;
  117. }
  118. bool PannableViewPrivate::mouseReleaseEvent( QGraphicsSceneMouseEvent *event ){
  119. if ( event->button() != Qt::LeftButton ){
  120. event->ignore();
  121. return false;
  122. }
  123. // For some weird reason the mouseGrapper thingy makes the buttonDownPos() to return
  124. // exactly the same as scenePos() therefore we need to store mouse press position and
  125. // use it here.
  126. QPointF pressPos = event->buttonDownPos(Qt::LeftButton);
  127. QPointF releasePos = event->pos();
  128. QPointF delta = pressPos - releasePos;
  129. qreal distance = sqrt( pow(delta.x(),2) + pow(delta.y(),2));
  130. if ( distance < TAP_GESTURE_THRESHOLD && !m_skipTapGesture ){
  131. // This is a tap gesture
  132. return tap( event );
  133. }else{
  134. // This is a swipe gesture
  135. return swipe( pressPos, releasePos );
  136. }
  137. }
  138. bool PannableViewPrivate::tap( QGraphicsSceneMouseEvent * event )
  139. {
  140. endReached();
  141. Q_Q(PannableView);
  142. if ( !(event->type() == QEvent::GraphicsSceneMousePress ||
  143. event->type() == QEvent::GraphicsSceneMouseRelease )){
  144. return false;
  145. }
  146. if ( event->button() != Qt::LeftButton ){
  147. return false;
  148. }
  149. QGraphicsSceneMouseEvent press(QEvent::GraphicsSceneMousePress);
  150. QGraphicsSceneMouseEvent release(QEvent::GraphicsSceneMouseRelease);
  151. press.setScenePos( event->scenePos());
  152. press.setScreenPos(event->screenPos() );
  153. press.setButton( event->button());
  154. press.setPos(event->pos());
  155. release.setScenePos( event->scenePos());
  156. release.setScreenPos(event->screenPos() );
  157. release.setButton( event->button());
  158. release.setPos(event->pos());
  159. QGraphicsScene * scene = q->scene();
  160. QList<QGraphicsItem*> items = scene->items(event->scenePos());
  161. Q_FOREACH(QGraphicsItem * item, items ){
  162. QGraphicsWidget * widget = static_cast<QGraphicsWidget*>(item);
  163. // Filter out this and parent items
  164. if ( 0 == widget || q == widget || q->parentItem() == widget ){
  165. continue;
  166. }
  167. // Send event to the receivers
  168. scene->sendEvent(widget, &press);
  169. scene->sendEvent(widget, &release);
  170. }
  171. q->emit tapped(event->pos());
  172. return true;
  173. }
  174. bool PannableViewPrivate::swipe( QPointF const & startPoint, QPointF const &endPoint )
  175. {
  176. m_skipTapGesture = false;
  177. QPointF delta = endPoint-startPoint;
  178. qint32 tD = m_time.elapsed(); // Delta Time
  179. qreal dxD = delta.x(); // Delta X distance
  180. qreal dyD = delta.y(); // Delta Y distance
  181. qreal vx = (dxD / tD ) * MSECS; // Velocity in x dimension (pixels / second).
  182. qreal vy = (dyD / tD ) * MSECS; // Velocity in y dimension (pixels / second).
  183. // Store swipe style for later use
  184. storeSwipeStyle(startPoint, endPoint);
  185. if ( fabs( vx ) > VELOCITY_THRESHOLD || fabs(vy) > VELOCITY_THRESHOLD ){
  186. qreal velocity = 0; // Current velocity in x or y dimension.
  187. m_deltaX = dxD;
  188. m_deltaY = dyD;
  189. m_vX = vx;
  190. m_vY = vy;
  191. if ( m_orientation & Qt::Horizontal ){
  192. velocity = fabs( vx );
  193. }else{
  194. velocity = fabs( vy );
  195. }
  196. if ( velocity > 0 && !endReached() ){
  197. // Actually this duration doesn't effect on anything at the moment.
  198. startScrolling( static_cast<int>( velocity/PANNABLE_FRICTION_COEFFICIENT ) );
  199. }
  200. return true;
  201. }
  202. return true;
  203. }
  204. void PannableViewPrivate::scroll( qreal value ){
  205. Q_UNUSED(value);
  206. // Check first if we have reached one of the ends
  207. if ( endReached() ){
  208. return;
  209. }
  210. qreal sX=0, sY=0;
  211. Qt::Orientations orientation = m_orientation;
  212. if ( m_orientation & Qt::Vertical && m_orientation & Qt::Horizontal){
  213. switch ( m_swipeStyle ){
  214. case Vertical:
  215. orientation = Qt::Vertical;
  216. break;
  217. case Horizontal:
  218. orientation = Qt::Horizontal;
  219. break;
  220. default:
  221. break;
  222. }
  223. }
  224. if ( orientation & Qt::Horizontal ){
  225. // s = 1/2 * (u + v)t,
  226. // u=0,
  227. // t=update interval (in ms) of the QTimeLine.
  228. sX = m_vX * (m_scrollingTimeLine->updateInterval()/ 2) / MSECS ;
  229. // v = u + at,
  230. // u = d_ptr->m_vX (current speed),
  231. // a = yN/m, m = 1kg (just to have something), yN = friction
  232. qreal v = ( fabs(m_vX) - (PANNABLE_FRICTION_COEFFICIENT * m_scrollingTimeLine->updateInterval()) );
  233. m_vX = (m_vX < 0 ? -v : v);
  234. if ( fabs(sX) > SCROLL_MIN_THRESHOLD && fabs(sX) < SCROLL_MAX_THRESHOLD ){
  235. stopScrolling();
  236. }
  237. }
  238. if ( orientation & Qt::Vertical ){
  239. // s = 1/2 * (u + v)t,
  240. // u=0,
  241. // t=update interval (in ms) of the QTimeLine.
  242. sY = m_vY * (m_scrollingTimeLine->updateInterval()/ 2) / MSECS;
  243. // v = u + at,
  244. // u = d_ptr->m_vY (current speed),
  245. // a = yN/m, m = 1kg (just to have something), yN = friction
  246. qreal v = ( fabs(m_vY) - (PANNABLE_FRICTION_COEFFICIENT * m_scrollingTimeLine->updateInterval()) );
  247. m_vY = (m_vY < 0 ? -v : v);
  248. if ( fabs(sY) > SCROLL_MIN_THRESHOLD && fabs(sY) < SCROLL_MAX_THRESHOLD ){
  249. stopScrolling();
  250. }
  251. }
  252. m_pannableWidget->moveBy(sX*2, sY*2);
  253. emitPositionChanged();
  254. }
  255. void PannableViewPrivate::animateEnd(QList<QPointF> const & points, int duration )
  256. {
  257. if ( points.isEmpty()){
  258. return;
  259. }
  260. QPropertyAnimation * animation = new QPropertyAnimation(m_pannableWidget,"pos");
  261. qreal step = 1.0 / (points.size() - 1);
  262. qreal value = 0;
  263. Q_FOREACH( QPointF point, points){
  264. animation->setKeyValueAt( value, point);
  265. value += step;
  266. }
  267. animation->setEasingCurve(QEasingCurve::OutQuart);
  268. animation->setDuration( duration );
  269. connect( animation, SIGNAL(finished()), this, SLOT(emitPositionChanged()));
  270. connect( this, SIGNAL(stopAnimation()), animation,SLOT(stop()));
  271. animation->start(QAbstractAnimation::DeleteWhenStopped);
  272. }
  273. void PannableViewPrivate::storeSwipeStyle( QPointF const &first, QPointF const &second )
  274. {
  275. qreal yD = fabs(second.y() - first.y());
  276. qreal xD = fabs(second.x() - first.x());
  277. if ( yD <= SWIPE_GESTURE_THRESHOLD ){
  278. m_swipeStyle = Horizontal;
  279. return;
  280. }
  281. if ( xD <= SWIPE_GESTURE_THRESHOLD ){
  282. m_swipeStyle = Vertical;
  283. return;
  284. }
  285. if ( yD > SWIPE_GESTURE_THRESHOLD &&
  286. xD > SWIPE_GESTURE_THRESHOLD){
  287. m_swipeStyle = Diagonal;
  288. }
  289. }
  290. bool PannableViewPrivate::endReached()
  291. {
  292. QPointF pos = m_pannableWidget->pos();
  293. qreal widgetX = pos.x();
  294. qreal widgetY = pos.y();
  295. qreal margin = 0;
  296. Q_Q(PannableView);
  297. QRectF limitRect( QPointF(widgetX, widgetY), m_pannableWidget->size() );
  298. QRectF viewRect = q->boundingRect();
  299. int viewHeight = viewRect.height();
  300. int viewWidth = viewRect.width();
  301. // Flags to indicate if e.g. the limit rect height or width is less than viewRect's
  302. // Then we can't use that value as a comparison because it will be true always.
  303. bool checkWidth = limitRect.width() >= viewWidth;
  304. bool checkHeight = limitRect.height() >= viewHeight;
  305. if ( !limitRect.contains( viewRect )){
  306. QRectF intersection = limitRect.intersected( viewRect );
  307. QList<QPointF> points;
  308. points << pos;
  309. int width = intersection.width();
  310. int height= intersection.height();
  311. // Handle the situation where we have intersection in corners
  312. if (width >= margin && width < viewWidth &&
  313. height >= margin && height < viewHeight ){
  314. stopScrolling();
  315. int x = widgetX, y = widgetY;
  316. if ( checkWidth && m_deltaX < 0 ){
  317. x = -m_maxX;
  318. }else{
  319. x = m_minX;
  320. }
  321. if ( checkHeight && m_deltaY < 0 ){
  322. y = -m_maxY;
  323. }else{
  324. y = m_minY;
  325. }
  326. points << QPointF(x,y);
  327. animateEnd(points);
  328. return true;
  329. }
  330. // Handle the situation where only left, top, right or bottom
  331. // has been intersected.
  332. if (checkWidth && width >= margin && width < viewWidth){
  333. stopScrolling();
  334. if ( m_deltaX < 0 ){
  335. //Panning to the right ->
  336. points << QPointF(-m_maxX, widgetY);
  337. }else{
  338. //Panning to the left <-
  339. points << QPointF(m_minX, widgetY);
  340. }
  341. animateEnd(points);
  342. return true;
  343. }
  344. if ( checkHeight && height >= margin && height < viewHeight ){
  345. stopScrolling();
  346. if ( m_deltaY < 0 ){
  347. //Panning to the down
  348. points << QPointF( widgetX, -m_maxY);
  349. }else{
  350. //Panning to the top
  351. points << QPointF( widgetX, m_minY);
  352. }
  353. animateEnd(points);
  354. return true;
  355. }
  356. }
  357. return false;
  358. }
  359. void PannableViewPrivate::emitPositionChanged()
  360. {
  361. Q_Q(PannableView);
  362. QPointF point = m_pannableWidget->pos() * -1.0;
  363. q->emit posChanged(QRectF(point, q->boundingRect().size()));
  364. // TODO: Add here indicator updating
  365. }
  366. void PannableViewPrivate::moveToBegin()
  367. {
  368. m_pannableWidget->setPos(0, 0);
  369. }
  370. void PannableViewPrivate::moveToEnd()
  371. {
  372. m_pannableWidget->setPos(m_maxX, m_maxY);
  373. }
  374. bool PannableViewPrivate::isScrolling() const
  375. {
  376. return (m_scrollingTimeLine->state() == QTimeLine::Running);
  377. }
  378. void PannableViewPrivate::moveTo(QPointF const & pos )
  379. {
  380. QPointF newPos;
  381. if (m_orientation & Qt::Vertical){
  382. newPos.setY( -pos.y());
  383. }
  384. if (m_orientation & Qt::Horizontal){
  385. newPos.setX( -pos.x());
  386. }
  387. if ( isScrolling() ){
  388. stopScrolling();
  389. }
  390. QList<QPointF> points;
  391. points << m_pannableWidget->pos() << newPos;
  392. animateEnd(points);
  393. }
  394. void PannableViewPrivate::recalculateGeometry()
  395. {
  396. Q_Q(PannableView);
  397. QRectF geom = m_pannableWidget->layout()->geometry();
  398. m_minX = geom.x();
  399. m_minY = geom.y();
  400. m_maxX = geom.width() - q->boundingRect().width();
  401. m_maxY = geom.height()- q->boundingRect().height();
  402. }
  403. //// Pannable Widget Implementation ////
  404. PannableWidget::PannableWidget( QGraphicsObject * parent ):
  405. QGraphicsWidget( parent )
  406. {
  407. setFlags( QGraphicsItem::ItemClipsChildrenToShape);
  408. setObjectName("PannableWidget");
  409. }
  410. PannableWidget::~PannableWidget(){
  411. }
  412. QPainterPath PannableWidget::shape () const{
  413. QPainterPath path;
  414. QSizeF size = QGraphicsWidget::size();
  415. path.addRect( QRectF( 0,0, size.width(), size.height() ) );
  416. return path;
  417. }
  418. void PannableWidget::setWidget( QGraphicsWidget * widget ){
  419. if ( 0 == widget ){
  420. setLayout(0);
  421. return;
  422. }
  423. setFlag(QGraphicsItem::ItemStacksBehindParent);
  424. QGraphicsLinearLayout * layout = new QGraphicsLinearLayout( Qt::Vertical );
  425. layout->setSpacing(0);
  426. layout->setContentsMargins(0,0,0,0);
  427. layout->addItem( widget );
  428. setLayout( layout );
  429. }
  430. //// Pannable View Implementation ////
  431. PannableView::PannableView( Qt::Orientations orientation, qreal width, qreal height, QGraphicsItem * parent ):
  432. QGraphicsWidget(parent),
  433. d_ptr( new PannableViewPrivate(this) )
  434. {
  435. setMinimumSize ( width, height );
  436. setPreferredSize( width, height );
  437. setMaximumSize ( width, height );
  438. setFlags( QGraphicsItem::ItemClipsChildrenToShape );
  439. Q_D(PannableView);
  440. d->m_orientation = orientation;
  441. d->m_pannableWidget = new PannableWidget( this );
  442. // TODO: Add here grabGesture() calls and make this widget to use QGestures
  443. }
  444. PannableView::PannableView( Qt::Orientations orientation, QGraphicsItem * parent ):
  445. QGraphicsWidget(parent),
  446. d_ptr( new PannableViewPrivate(this) )
  447. {
  448. setFlags( QGraphicsItem::ItemClipsChildrenToShape );
  449. Q_D(PannableView);
  450. d->m_orientation = orientation;
  451. d->m_pannableWidget = new PannableWidget( this );
  452. // TODO: Add here grabGesture() calls and make this widget to use QGestures
  453. }
  454. PannableView::~PannableView()
  455. {
  456. delete d_ptr;
  457. d_ptr = 0;
  458. }
  459. void PannableView::setWidget( QGraphicsWidget * widget )
  460. {
  461. Q_D(PannableView);
  462. d->m_pannableWidget->setWidget( widget );
  463. d->recalculateGeometry();
  464. }
  465. bool PannableView::isScrolling() const
  466. {
  467. Q_D(const PannableView);
  468. return d->isScrolling();
  469. }
  470. void PannableView::moveToBegin()
  471. {
  472. Q_D(PannableView);
  473. return d->moveToBegin();
  474. }
  475. void PannableView::moveToEnd()
  476. {
  477. Q_D(PannableView);
  478. return d->moveToEnd();
  479. }
  480. bool PannableView::sceneEvent ( QEvent * event )
  481. {
  482. Q_D(PannableView);
  483. switch( event->type()){
  484. case QEvent::GraphicsSceneMousePress:
  485. d->recalculateGeometry(); // In a case if pannable widget size has changed
  486. return d->mousePressEvent(static_cast<QGraphicsSceneMouseEvent*>(event));
  487. case QEvent::GraphicsSceneMouseMove:
  488. return d->mouseMoveEvent(static_cast<QGraphicsSceneMouseEvent*>(event));
  489. case QEvent::GraphicsSceneMouseRelease:
  490. return d->mouseReleaseEvent(static_cast<QGraphicsSceneMouseEvent*>(event));
  491. default:
  492. return false;
  493. }
  494. }
  495. void PannableView::enableContinuousScrolling( bool enable )
  496. {
  497. Q_D(PannableView);
  498. d->m_enableContinuousScrolling = enable;
  499. }
  500. Qt::Orientations PannableView::orientation() const
  501. {
  502. Q_D(const PannableView);
  503. return d->m_orientation;
  504. }
  505. void PannableView::enableScrollIndicator( bool enable )
  506. {
  507. Q_D(PannableView);
  508. d->m_enableIndicator = enable;
  509. }
  510. void PannableView::moveToItem( QGraphicsItem * item)
  511. {
  512. QList<QGraphicsItem *> items = scene()->items();
  513. if ( !items.contains( item )){
  514. return;
  515. }
  516. // Move view to the center of the item
  517. Q_D(PannableView);
  518. QSizeF viewSize = geometry().size();
  519. QSizeF itemSize = item->boundingRect().size();
  520. QPointF offset( (viewSize.width() - itemSize.width()) / 2, (viewSize.height() - itemSize.height()) / 2 );
  521. d->moveTo( item->pos() - offset );
  522. }
  523. void PannableView::setOrientation(Qt::Orientations orientation )
  524. {
  525. Q_D(PannableView);
  526. d->m_orientation = orientation;
  527. d->recalculateGeometry();
  528. }