QGIS API Documentation  2.2.0-Valmiera
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Macros Groups Pages
qgscomposermousehandles.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  qgscomposermousehandles.cpp
3  -------------------
4  begin : September 2013
5  copyright : (C) 2013 by Nyall Dawson, Radim Blazek
6  email : [email protected]
7  ***************************************************************************/
8 
9 /***************************************************************************
10  * *
11  * This program is free software; you can redistribute it and/or modify *
12  * it under the terms of the GNU General Public License as published by *
13  * the Free Software Foundation; either version 2 of the License, or *
14  * (at your option) any later version. *
15  * *
16  ***************************************************************************/
17 #include <QPainter>
18 #include <QWidget>
19 
20 #include <limits>
21 
23 #include "qgscomposeritem.h"
24 #include "qgscomposition.h"
25 #include "qgspaperitem.h"
26 #include "qgis.h"
27 #include "qgslogger.h"
28 
30  QGraphicsRectItem( 0 ),
31  mComposition( composition ),
32  mGraphicsView( 0 ),
33  mBeginHandleWidth( 0 ),
34  mBeginHandleHeight( 0 ),
35  mResizeMoveX( 0 ),
36  mResizeMoveY( 0 ),
37  mIsDragging( false ),
38  mIsResizing( false ),
39  mHAlignSnapItem( 0 ),
40  mVAlignSnapItem( 0 )
41 {
42  //listen for selection changes, and update handles accordingly
43  QObject::connect( mComposition, SIGNAL( selectionChanged() ), this, SLOT( selectionChanged() ) );
44 
45  //accept hover events, required for changing cursor to resize cursors
46  setAcceptsHoverEvents( true );
47 }
48 
50 {
51 
52 }
53 
55 {
56  //have we already found the current view?
57  if ( mGraphicsView )
58  {
59  return mGraphicsView;
60  }
61 
62  //otherwise, try and find current view attached to composition
63  if ( scene() )
64  {
65  QList<QGraphicsView*> viewList = scene()->views();
66  if ( viewList.size() > 0 )
67  {
68  mGraphicsView = viewList.at( 0 );
69  return mGraphicsView;
70  }
71  }
72 
73  //no view attached to composition
74  return 0;
75 }
76 
77 void QgsComposerMouseHandles::paint( QPainter* painter, const QStyleOptionGraphicsItem* itemStyle, QWidget* pWidget )
78 {
79  Q_UNUSED( itemStyle );
80  Q_UNUSED( pWidget );
81 
83  {
84  //don't draw selection handles in composition outputs
85  return;
86  }
87 
88  //draw resize handles around bounds of entire selection
89  double rectHandlerSize = rectHandlerBorderTolerance();
90  drawHandles( painter, rectHandlerSize );
91 
92  //draw dotted boxes around selected items
93  drawSelectedItemBounds( painter );
94 }
95 
96 void QgsComposerMouseHandles::drawHandles( QPainter* painter, double rectHandlerSize )
97 {
98  //blue, zero width cosmetic pen for outline
99  QPen handlePen = QPen( QColor( 55, 140, 195, 255 ) );
100  handlePen.setWidth( 0 );
101  painter->setPen( handlePen );
102 
103  //draw box around entire selection bounds
104  painter->setBrush( Qt::NoBrush );
105  painter->drawRect( QRectF( 0, 0, rect().width(), rect().height() ) );
106 
107  //draw resize handles, using a filled white box
108  painter->setBrush( QColor( 255, 255, 255, 255 ) );
109  //top left
110  painter->drawRect( QRectF( 0, 0, rectHandlerSize, rectHandlerSize ) );
111  //mid top
112  painter->drawRect( QRectF(( rect().width() - rectHandlerSize ) / 2, 0, rectHandlerSize, rectHandlerSize ) );
113  //top right
114  painter->drawRect( QRectF( rect().width() - rectHandlerSize, 0, rectHandlerSize, rectHandlerSize ) );
115  //mid left
116  painter->drawRect( QRectF( 0, ( rect().height() - rectHandlerSize ) / 2, rectHandlerSize, rectHandlerSize ) );
117  //mid right
118  painter->drawRect( QRectF( rect().width() - rectHandlerSize, ( rect().height() - rectHandlerSize ) / 2, rectHandlerSize, rectHandlerSize ) );
119  //bottom left
120  painter->drawRect( QRectF( 0, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
121  //mid bottom
122  painter->drawRect( QRectF(( rect().width() - rectHandlerSize ) / 2, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
123  //bottom right
124  painter->drawRect( QRectF( rect().width() - rectHandlerSize, rect().height() - rectHandlerSize, rectHandlerSize, rectHandlerSize ) );
125 }
126 
128 {
129  //draw dotted border around selected items to give visual feedback which items are selected
130  QList<QgsComposerItem*> selectedItems = mComposition->selectedComposerItems();
131  if ( selectedItems.size() == 0 )
132  {
133  return;
134  }
135 
136  //use difference mode so that they are visible regardless of item colors
137  painter->save();
138  painter->setCompositionMode( QPainter::CompositionMode_Difference );
139 
140  // use a grey dashed pen - in difference mode this should always be visible
141  QPen selectedItemPen = QPen( QColor( 144, 144, 144, 255 ) );
142  selectedItemPen.setStyle( Qt::DashLine );
143  selectedItemPen.setWidth( 0 );
144  painter->setPen( selectedItemPen );
145  painter->setBrush( Qt::NoBrush );
146 
147  QList<QgsComposerItem*>::iterator itemIter = selectedItems.begin();
148  for ( ; itemIter != selectedItems.end(); ++itemIter )
149  {
150  //get bounds of selected item
151  QPolygonF itemBounds;
152  if ( mIsDragging && !( *itemIter )->positionLock() )
153  {
154  //if currently dragging, draw selected item bounds relative to current mouse position
155  //first, get bounds of current item in scene coordinates
156  QPolygonF itemSceneBounds = ( *itemIter )->mapToScene(( *itemIter )->rectWithFrame() );
157  //now, translate it by the current movement amount
158  //IMPORTANT - this is done in scene coordinates, since we don't want any rotation/non-translation transforms to affect the movement
159  itemSceneBounds.translate( transform().dx(), transform().dy() );
160  //finally, remap it to the mouse handle item's coordinate system so it's ready for drawing
161  itemBounds = mapFromScene( itemSceneBounds );
162  }
163  else if ( mIsResizing && !( *itemIter )->positionLock() )
164  {
165  //if currently resizing, calculate relative resize of this item
166  if ( selectedItems.size() > 1 )
167  {
168  //get item bounds in mouse handle item's coordinate system
169  QRectF itemRect = mapRectFromItem(( *itemIter ), ( *itemIter )->rectWithFrame() );
170  //now, resize it relative to the current resized dimensions of the mouse handles
172  itemBounds = QPolygonF( itemRect );
173  }
174  else
175  {
176  //single item selected
177  itemBounds = rect();
178  }
179  }
180  else
181  {
182  //not resizing or moving, so just map from scene bounds
183  itemBounds = mapRectFromItem(( *itemIter ), ( *itemIter )->rectWithFrame() );
184  }
185  painter->drawPolygon( itemBounds );
186  }
187  painter->restore();
188 }
189 
191 {
192  //listen out for selected items' size and rotation changed signals
193  QList<QGraphicsItem *> itemList = composition()->items();
194  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
195  for ( ; itemIt != itemList.end(); ++itemIt )
196  {
197  QgsComposerItem* item = dynamic_cast<QgsComposerItem *>( *itemIt );
198  if ( item )
199  {
200  if ( item->selected() )
201  {
202  QObject::connect( item, SIGNAL( sizeChanged() ), this, SLOT( selectedItemSizeChanged() ) );
203  QObject::connect( item, SIGNAL( itemRotationChanged( double ) ), this, SLOT( selectedItemRotationChanged() ) );
204  QObject::connect( item, SIGNAL( frameChanged( ) ), this, SLOT( selectedItemSizeChanged() ) );
205  }
206  else
207  {
208  QObject::disconnect( item, SIGNAL( sizeChanged() ), this, 0 );
209  QObject::disconnect( item, SIGNAL( itemRotationChanged( double ) ), this, 0 );
210  QObject::disconnect( item, SIGNAL( frameChanged( ) ), this, 0 );
211  }
212  }
213  }
214 
215  resetStatusBar();
216  updateHandles();
217 }
218 
220 {
221  if ( !mIsDragging && !mIsResizing )
222  {
223  //only required for non-mouse initiated size changes
224  updateHandles();
225  }
226 }
227 
229 {
230  if ( !mIsDragging && !mIsResizing )
231  {
232  //only required for non-mouse initiated rotation changes
233  updateHandles();
234  }
235 }
236 
238 {
239  //recalculate size and position of handle item
240 
241  //first check to see if any items are selected
242  QList<QgsComposerItem*> selectedItems = mComposition->selectedComposerItems();
243  if ( selectedItems.size() > 0 )
244  {
245  //one or more items are selected, get bounds of all selected items
246 
247  //update rotation of handle object
248  double rotation;
249  if ( selectionRotation( rotation ) )
250  {
251  //all items share a common rotation value, so we rotate the mouse handles to match
252  setRotation( rotation );
253  }
254  else
255  {
256  //items have varying rotation values - we can't rotate the mouse handles to match
257  setRotation( 0 );
258  }
259 
260  //get bounds of all selected items
261  QRectF newHandleBounds = selectionBounds();
262 
263  //update size and position of handle object
264  setRect( 0, 0, newHandleBounds.width(), newHandleBounds.height() );
265  setPos( mapToScene( newHandleBounds.topLeft() ) );
266 
267  show();
268  }
269  else
270  {
271  //no items selected, hide handles
272  hide();
273  }
274  //force redraw
275  update();
276 }
277 
279 {
280  //calculate bounds of all currently selected items in mouse handle coordinate system
281  QList<QgsComposerItem*> selectedItems = mComposition->selectedComposerItems();
282  QList<QgsComposerItem*>::iterator itemIter = selectedItems.begin();
283 
284  //start with handle bounds of first selected item
285  QRectF bounds = mapFromItem(( *itemIter ), ( *itemIter )->rectWithFrame() ).boundingRect();
286 
287  //iterate through remaining items, expanding the bounds as required
288  for ( ++itemIter; itemIter != selectedItems.end(); ++itemIter )
289  {
290  bounds = bounds.united( mapFromItem(( *itemIter ), ( *itemIter )->rectWithFrame() ).boundingRect() );
291  }
292 
293  return bounds;
294 }
295 
296 bool QgsComposerMouseHandles::selectionRotation( double & rotation ) const
297 {
298  //check if all selected items have same rotation
299  QList<QgsComposerItem*> selectedItems = mComposition->selectedComposerItems();
300  QList<QgsComposerItem*>::iterator itemIter = selectedItems.begin();
301 
302  //start with rotation of first selected item
303  double firstItemRotation = ( *itemIter )->itemRotation();
304 
305  //iterate through remaining items, checking if they have same rotation
306  for ( ++itemIter; itemIter != selectedItems.end(); ++itemIter )
307  {
308  if (( *itemIter )->itemRotation() != firstItemRotation )
309  {
310  //item has a different rotation, so return false
311  return false;
312  }
313  }
314 
315  //all items have the same rotation, so set the rotation variable and return true
316  rotation = firstItemRotation;
317  return true;
318 }
319 
321 {
322  //calculate size for resize handles
323  //get view scale factor
324  double viewScaleFactor = graphicsView()->transform().m11();
325 
326  //size of handle boxes depends on zoom level in composer view
327  double rectHandlerSize = 10.0 / viewScaleFactor;
328 
329  //make sure the boxes don't get too large
330  if ( rectHandlerSize > ( rect().width() / 3 ) )
331  {
332  rectHandlerSize = rect().width() / 3;
333  }
334  if ( rectHandlerSize > ( rect().height() / 3 ) )
335  {
336  rectHandlerSize = rect().height() / 3;
337  }
338  return rectHandlerSize;
339 }
340 
341 Qt::CursorShape QgsComposerMouseHandles::cursorForPosition( const QPointF& itemCoordPos )
342 {
343  QgsComposerMouseHandles::MouseAction mouseAction = mouseActionForPosition( itemCoordPos );
344  switch ( mouseAction )
345  {
346  case NoAction:
347  return Qt::ForbiddenCursor;
348  case MoveItem:
349  return Qt::SizeAllCursor;
350  case ResizeUp:
351  case ResizeDown:
352  //account for rotation
353  if (( rotation() <= 22.5 || rotation() >= 337.5 ) || ( rotation() >= 157.5 && rotation() <= 202.5 ) )
354  {
355  return Qt::SizeVerCursor;
356  }
357  else if (( rotation() >= 22.5 && rotation() <= 67.5 ) || ( rotation() >= 202.5 && rotation() <= 247.5 ) )
358  {
359  return Qt::SizeBDiagCursor;
360  }
361  else if (( rotation() >= 67.5 && rotation() <= 112.5 ) || ( rotation() >= 247.5 && rotation() <= 292.5 ) )
362  {
363  return Qt::SizeHorCursor;
364  }
365  else
366  {
367  return Qt::SizeFDiagCursor;
368  }
369  case ResizeLeft:
370  case ResizeRight:
371  //account for rotation
372  if (( rotation() <= 22.5 || rotation() >= 337.5 ) || ( rotation() >= 157.5 && rotation() <= 202.5 ) )
373  {
374  return Qt::SizeHorCursor;
375  }
376  else if (( rotation() >= 22.5 && rotation() <= 67.5 ) || ( rotation() >= 202.5 && rotation() <= 247.5 ) )
377  {
378  return Qt::SizeFDiagCursor;
379  }
380  else if (( rotation() >= 67.5 && rotation() <= 112.5 ) || ( rotation() >= 247.5 && rotation() <= 292.5 ) )
381  {
382  return Qt::SizeVerCursor;
383  }
384  else
385  {
386  return Qt::SizeBDiagCursor;
387  }
388 
389  case ResizeLeftUp:
390  case ResizeRightDown:
391  //account for rotation
392  if (( rotation() <= 22.5 || rotation() >= 337.5 ) || ( rotation() >= 157.5 && rotation() <= 202.5 ) )
393  {
394  return Qt::SizeFDiagCursor;
395  }
396  else if (( rotation() >= 22.5 && rotation() <= 67.5 ) || ( rotation() >= 202.5 && rotation() <= 247.5 ) )
397  {
398  return Qt::SizeVerCursor;
399  }
400  else if (( rotation() >= 67.5 && rotation() <= 112.5 ) || ( rotation() >= 247.5 && rotation() <= 292.5 ) )
401  {
402  return Qt::SizeBDiagCursor;
403  }
404  else
405  {
406  return Qt::SizeHorCursor;
407  }
408  case ResizeRightUp:
409  case ResizeLeftDown:
410  //account for rotation
411  if (( rotation() <= 22.5 || rotation() >= 337.5 ) || ( rotation() >= 157.5 && rotation() <= 202.5 ) )
412  {
413  return Qt::SizeBDiagCursor;
414  }
415  else if (( rotation() >= 22.5 && rotation() <= 67.5 ) || ( rotation() >= 202.5 && rotation() <= 247.5 ) )
416  {
417  return Qt::SizeHorCursor;
418  }
419  else if (( rotation() >= 67.5 && rotation() <= 112.5 ) || ( rotation() >= 247.5 && rotation() <= 292.5 ) )
420  {
421  return Qt::SizeFDiagCursor;
422  }
423  else
424  {
425  return Qt::SizeVerCursor;
426  }
427  case SelectItem:
428  default:
429  return Qt::ArrowCursor;
430  }
431 }
432 
434 {
435  bool nearLeftBorder = false;
436  bool nearRightBorder = false;
437  bool nearLowerBorder = false;
438  bool nearUpperBorder = false;
439 
440  double borderTolerance = rectHandlerBorderTolerance();
441 
442  if ( itemCoordPos.x() >= 0 && itemCoordPos.x() < borderTolerance )
443  {
444  nearLeftBorder = true;
445  }
446  if ( itemCoordPos.y() >= 0 && itemCoordPos.y() < borderTolerance )
447  {
448  nearUpperBorder = true;
449  }
450  if ( itemCoordPos.x() <= rect().width() && itemCoordPos.x() > ( rect().width() - borderTolerance ) )
451  {
452  nearRightBorder = true;
453  }
454  if ( itemCoordPos.y() <= rect().height() && itemCoordPos.y() > ( rect().height() - borderTolerance ) )
455  {
456  nearLowerBorder = true;
457  }
458 
459  if ( nearLeftBorder && nearUpperBorder )
460  {
462  }
463  else if ( nearLeftBorder && nearLowerBorder )
464  {
466  }
467  else if ( nearRightBorder && nearUpperBorder )
468  {
470  }
471  else if ( nearRightBorder && nearLowerBorder )
472  {
474  }
475  else if ( nearLeftBorder )
476  {
478  }
479  else if ( nearRightBorder )
480  {
482  }
483  else if ( nearUpperBorder )
484  {
486  }
487  else if ( nearLowerBorder )
488  {
490  }
491 
492  //find out if cursor position is over a selected item
493  QPointF scenePoint = mapToScene( itemCoordPos );
494  QList<QGraphicsItem *> itemsAtCursorPos = mComposition->items( scenePoint );
495  if ( itemsAtCursorPos.size() == 0 )
496  {
497  //no items at cursor position
499  }
500  QList<QGraphicsItem*>::iterator itemIter = itemsAtCursorPos.begin();
501  for ( ; itemIter != itemsAtCursorPos.end(); ++itemIter )
502  {
503  QgsComposerItem* item = dynamic_cast<QgsComposerItem *>(( *itemIter ) );
504  if ( item && item->selected() )
505  {
506  //cursor is over a selected composer item
508  }
509  }
510 
511  //default
513 }
514 
516 {
517  // convert sceneCoordPos to item coordinates
518  QPointF itemPos = mapFromScene( sceneCoordPos );
519  return mouseActionForPosition( itemPos );
520 }
521 
522 void QgsComposerMouseHandles::hoverMoveEvent( QGraphicsSceneHoverEvent * event )
523 {
524  setViewportCursor( cursorForPosition( event->pos() ) );
525 }
526 
527 void QgsComposerMouseHandles::hoverLeaveEvent( QGraphicsSceneHoverEvent * event )
528 {
529  Q_UNUSED( event );
530  setViewportCursor( Qt::ArrowCursor );
531 }
532 
533 void QgsComposerMouseHandles::setViewportCursor( Qt::CursorShape cursor )
534 {
535  //workaround qt bug #3732 by setting cursor for QGraphicsView viewport,
536  //rather then setting it directly here
537 
539  {
540  graphicsView()->viewport()->setCursor( cursor );
541  }
542 }
543 
544 void QgsComposerMouseHandles::mouseMoveEvent( QGraphicsSceneMouseEvent* event )
545 {
546  bool shiftModifier = false;
547  bool controlModifier = false;
548  if ( event->modifiers() & Qt::ShiftModifier )
549  {
550  //shift key depressed
551  shiftModifier = true;
552  }
553  if ( event->modifiers() & Qt::ControlModifier )
554  {
555  //shift key depressed
556  controlModifier = true;
557  }
558 
559  if ( mIsDragging )
560  {
561  //currently dragging a selection
562  dragMouseMove( event->lastScenePos(), shiftModifier, controlModifier );
563  }
564  else if ( mIsResizing )
565  {
566  //currently resizing a selection
567  resizeMouseMove( event->lastScenePos(), shiftModifier, controlModifier );
568  }
569 
570  mLastMouseEventPos = event->lastScenePos();
571 }
572 
573 void QgsComposerMouseHandles::mouseReleaseEvent( QGraphicsSceneMouseEvent* event )
574 {
575  QPointF mouseMoveStopPoint = event->lastScenePos();
576  double diffX = mouseMoveStopPoint.x() - mMouseMoveStartPos.x();
577  double diffY = mouseMoveStopPoint.y() - mMouseMoveStartPos.y();
578 
579  //it was only a click
580  if ( qAbs( diffX ) < std::numeric_limits<double>::min() && qAbs( diffY ) < std::numeric_limits<double>::min() )
581  {
582  mIsDragging = false;
583  mIsResizing = false;
584  return;
585  }
586 
588  {
589  //move selected items
590  QUndoCommand* parentCommand = new QUndoCommand( tr( "Change item position" ) );
591 
592  QPointF mEndHandleMovePos = scenePos();
593 
594  //move all selected items
595  QList<QgsComposerItem*> selectedItems = mComposition->selectedComposerItems();
596  QList<QgsComposerItem*>::iterator itemIter = selectedItems.begin();
597  for ( ; itemIter != selectedItems.end(); ++itemIter )
598  {
599  if (( *itemIter )->positionLock() || (( *itemIter )->flags() & QGraphicsItem::ItemIsSelectable ) == 0 )
600  {
601  //don't move locked items
602  continue;
603  }
604  QgsComposerItemCommand* subcommand = new QgsComposerItemCommand( *itemIter, "", parentCommand );
605  subcommand->savePreviousState();
606  ( *itemIter )->move( mEndHandleMovePos.x() - mBeginHandlePos.x(), mEndHandleMovePos.y() - mBeginHandlePos.y() );
607  subcommand->saveAfterState();
608  }
609  mComposition->undoStack()->push( parentCommand );
610 
611  }
613  {
614  //resize selected items
615  QUndoCommand* parentCommand = new QUndoCommand( tr( "Change item size" ) );
616 
617  //resize all selected items
618  QList<QgsComposerItem*> selectedItems = mComposition->selectedComposerItems();
619  QList<QgsComposerItem*>::iterator itemIter = selectedItems.begin();
620  for ( ; itemIter != selectedItems.end(); ++itemIter )
621  {
622  if (( *itemIter )->positionLock() || (( *itemIter )->flags() & QGraphicsItem::ItemIsSelectable ) == 0 )
623  {
624  //don't resize locked items or unselectable items (eg, items which make up an item group)
625  continue;
626  }
627  QgsComposerItemCommand* subcommand = new QgsComposerItemCommand( *itemIter, "", parentCommand );
628  subcommand->savePreviousState();
629 
630  QRectF itemRect;
631  if ( selectedItems.size() == 1 )
632  {
633  //only a single item is selected, so set its size to the final resized mouse handle size
634  itemRect = mResizeRect;
635  }
636  else
637  {
638  //multiple items selected, so each needs to be scaled relatively to the final size of the mouse handles
639  itemRect = mapRectFromItem(( *itemIter ), ( *itemIter )->rectWithFrame() );
641  }
642 
643  itemRect = itemRect.normalized();
644  QPointF newPos = mapToScene( itemRect.topLeft() );
645  ( *itemIter )->setItemPosition( newPos.x(), newPos.y(), itemRect.width(), itemRect.height(), QgsComposerItem::UpperLeft, true );
646 
647  subcommand->saveAfterState();
648  }
649  mComposition->undoStack()->push( parentCommand );
650  }
651 
653 
654  if ( mIsDragging )
655  {
656  mIsDragging = false;
657  }
658  if ( mIsResizing )
659  {
660  mIsResizing = false;
661  }
662 
663  //reset default action
665  setViewportCursor( Qt::ArrowCursor );
666  //redraw handles
667  resetTransform();
668  updateHandles();
669  //reset status bar message
670  resetStatusBar();
671 }
672 
674 {
675  QList<QgsComposerItem*> selectedItems = mComposition->selectedComposerItems();
676  int selectedCount = selectedItems.size();
677  if ( selectedCount > 1 )
678  {
679  //set status bar message to count of selected items
680  mComposition->setStatusMessage( QString( tr( "%1 items selected" ) ).arg( selectedCount ) );
681  }
682  else if ( selectedCount == 1 )
683  {
684  //set status bar message to count of selected items
685  mComposition->setStatusMessage( tr( "1 item selected" ) );
686  }
687  else
688  {
689  //clear status bar message
690  mComposition->setStatusMessage( QString( "" ) );
691  }
692 }
693 
694 void QgsComposerMouseHandles::mousePressEvent( QGraphicsSceneMouseEvent* event )
695 {
696  //save current cursor position
697  mMouseMoveStartPos = event->lastScenePos();
698  mLastMouseEventPos = event->lastScenePos();
699  //save current item geometry
700  mBeginMouseEventPos = event->lastScenePos();
701  mBeginHandlePos = scenePos();
702  mBeginHandleWidth = rect().width();
703  mBeginHandleHeight = rect().height();
704  //type of mouse move action
706 
708 
710  {
711  //moving items
712  mIsDragging = true;
713  }
716  {
717  //resizing items
718  mIsResizing = true;
720  mResizeMoveX = 0;
721  mResizeMoveY = 0;
723 
724  }
725 
726 }
727 
728 QSizeF QgsComposerMouseHandles::calcCursorEdgeOffset( const QPointF &cursorPos )
729 {
730  //find offset between cursor position and actual edge of item
731  QPointF sceneMousePos = mapFromScene( cursorPos );
732 
733  switch ( mCurrentMouseMoveAction )
734  {
735  //vertical resize
737  return QSizeF( 0, sceneMousePos.y() );
738 
740  return QSizeF( 0, sceneMousePos.y() - rect().height() );
741 
742  //horizontal resize
744  return QSizeF( sceneMousePos.x(), 0 );
745 
747  return QSizeF( sceneMousePos.x() - rect().width(), 0 );
748 
749  //diagonal resize
751  return QSizeF( sceneMousePos.x(), sceneMousePos.y() );
752 
754  return QSizeF( sceneMousePos.x() - rect().width(), sceneMousePos.y() - rect().height() );
755 
757  return QSizeF( sceneMousePos.x() - rect().width(), sceneMousePos.y() );
758 
760  return QSizeF( sceneMousePos.x(), sceneMousePos.y() - rect().height() );
761 
762  default:
763  return QSizeF( 0, 0 );
764  }
765 
766 }
767 
768 void QgsComposerMouseHandles::dragMouseMove( const QPointF& currentPosition, bool lockMovement, bool preventSnap )
769 {
770  if ( !mComposition )
771  {
772  return;
773  }
774 
775  //calculate total amount of mouse movement since drag began
776  double moveX = currentPosition.x() - mBeginMouseEventPos.x();
777  double moveY = currentPosition.y() - mBeginMouseEventPos.y();
778 
779  //find target position before snapping (in scene coordinates)
780  QPointF upperLeftPoint( mBeginHandlePos.x() + moveX, mBeginHandlePos.y() + moveY );
781 
782  QPointF snappedLeftPoint;
783  //no snapping for rotated items for now
784  if ( !preventSnap && rotation() == 0 )
785  {
786  //snap to grid and guides
787  snappedLeftPoint = snapPoint( upperLeftPoint, QgsComposerMouseHandles::Item );
788  }
789  else
790  {
791  //no snapping
792  snappedLeftPoint = upperLeftPoint;
794  }
795 
796  //calculate total shift for item from beginning of drag operation to current position
797  double moveRectX = snappedLeftPoint.x() - mBeginHandlePos.x();
798  double moveRectY = snappedLeftPoint.y() - mBeginHandlePos.y();
799 
800  if ( lockMovement )
801  {
802  //constrained (shift) moving should lock to horizontal/vertical movement
803  //reset the smaller of the x/y movements
804  if ( abs( moveRectX ) <= abs( moveRectY ) )
805  {
806  moveRectX = 0;
807  }
808  else
809  {
810  moveRectY = 0;
811  }
812  }
813 
814  //shift handle item to new position
815  QTransform moveTransform;
816  moveTransform.translate( moveRectX, moveRectY );
817  setTransform( moveTransform );
818  //show current displacement of selection in status bar
819  mComposition->setStatusMessage( QString( tr( "dx: %1 mm dy: %2 mm" ) ).arg( moveRectX ).arg( moveRectY ) );
820 }
821 
822 void QgsComposerMouseHandles::resizeMouseMove( const QPointF& currentPosition, bool lockRatio, bool fromCenter )
823 {
824 
825  if ( !mComposition )
826  {
827  return;
828  }
829 
830  double mx = 0.0, my = 0.0, rx = 0.0, ry = 0.0;
831 
832  QPointF beginMousePos;
833  QPointF finalPosition;
834  if ( rotation() == 0 )
835  {
836  //snapping only occurs if handles are not rotated for now
837 
838  //subtract cursor edge offset from begin mouse event and current cursor position, so that snapping occurs to edge of mouse handles
839  //rather then cursor position
840  beginMousePos = mapFromScene( QPointF( mBeginMouseEventPos.x() - mCursorOffset.width(), mBeginMouseEventPos.y() - mCursorOffset.height() ) );
841  QPointF snappedPosition = snapPoint( QPointF( currentPosition.x() - mCursorOffset.width(), currentPosition.y() - mCursorOffset.height() ), QgsComposerMouseHandles::Point );
842  finalPosition = mapFromScene( snappedPosition );
843  }
844  else
845  {
846  //no snapping for rotated items for now
847  beginMousePos = mapFromScene( mBeginMouseEventPos );
848  finalPosition = mapFromScene( currentPosition );
849  }
850 
851  double diffX = finalPosition.x() - beginMousePos.x();
852  double diffY = finalPosition.y() - beginMousePos.y();
853 
854  double ratio = 0;
855  if ( lockRatio && mBeginHandleHeight != 0 )
856  {
858  }
859 
860  switch ( mCurrentMouseMoveAction )
861  {
862  //vertical resize
864  {
865  if ( ratio )
866  {
867  diffX = (( mBeginHandleHeight - diffY ) * ratio ) - mBeginHandleWidth;
868  mx = -diffX / 2; my = diffY; rx = diffX; ry = -diffY;
869  }
870  else
871  {
872  mx = 0; my = diffY; rx = 0; ry = -diffY;
873  }
874  break;
875  }
876 
878  {
879  if ( ratio )
880  {
881  diffX = (( mBeginHandleHeight + diffY ) * ratio ) - mBeginHandleWidth;
882  mx = -diffX / 2; my = 0; rx = diffX; ry = diffY;
883  }
884  else
885  {
886  mx = 0; my = 0; rx = 0; ry = diffY;
887  }
888  break;
889  }
890 
891  //horizontal resize
893  {
894  if ( ratio )
895  {
896  diffY = (( mBeginHandleWidth - diffX ) / ratio ) - mBeginHandleHeight;
897  mx = diffX; my = -diffY / 2; rx = -diffX; ry = diffY;
898  }
899  else
900  {
901  mx = diffX, my = 0; rx = -diffX; ry = 0;
902  }
903  break;
904  }
905 
907  {
908  if ( ratio )
909  {
910  diffY = (( mBeginHandleWidth + diffX ) / ratio ) - mBeginHandleHeight;
911  mx = 0; my = -diffY / 2; rx = diffX; ry = diffY;
912  }
913  else
914  {
915  mx = 0; my = 0; rx = diffX, ry = 0;
916  }
917  break;
918  }
919 
920  //diagonal resize
922  {
923  if ( ratio )
924  {
925  //ratio locked resize
926  if (( mBeginHandleWidth - diffX ) / ( mBeginHandleHeight - diffY ) > ratio )
927  {
928  diffX = mBeginHandleWidth - (( mBeginHandleHeight - diffY ) * ratio );
929  }
930  else
931  {
932  diffY = mBeginHandleHeight - (( mBeginHandleWidth - diffX ) / ratio );
933  }
934  }
935  mx = diffX, my = diffY; rx = -diffX; ry = -diffY;
936  break;
937  }
938 
940  {
941  if ( ratio )
942  {
943  //ratio locked resize
944  if (( mBeginHandleWidth + diffX ) / ( mBeginHandleHeight + diffY ) > ratio )
945  {
946  diffX = (( mBeginHandleHeight + diffY ) * ratio ) - mBeginHandleWidth;
947  }
948  else
949  {
950  diffY = (( mBeginHandleWidth + diffX ) / ratio ) - mBeginHandleHeight;
951  }
952  }
953  mx = 0; my = 0; rx = diffX, ry = diffY;
954  break;
955  }
956 
958  {
959  if ( ratio )
960  {
961  //ratio locked resize
962  if (( mBeginHandleWidth + diffX ) / ( mBeginHandleHeight - diffY ) > ratio )
963  {
964  diffX = (( mBeginHandleHeight - diffY ) * ratio ) - mBeginHandleWidth;
965  }
966  else
967  {
968  diffY = mBeginHandleHeight - (( mBeginHandleWidth + diffX ) / ratio );
969  }
970  }
971  mx = 0; my = diffY, rx = diffX, ry = -diffY;
972  break;
973  }
974 
976  {
977  if ( ratio )
978  {
979  //ratio locked resize
980  if (( mBeginHandleWidth - diffX ) / ( mBeginHandleHeight + diffY ) > ratio )
981  {
982  diffX = mBeginHandleWidth - (( mBeginHandleHeight + diffY ) * ratio );
983  }
984  else
985  {
986  diffY = (( mBeginHandleWidth - diffX ) / ratio ) - mBeginHandleHeight;
987  }
988  }
989  mx = diffX, my = 0; rx = -diffX; ry = diffY;
990  break;
991  }
992 
996  break;
997  }
998 
999  //resizing from center of objects?
1000  if ( fromCenter )
1001  {
1002  my = -ry;
1003  mx = -rx;
1004  ry = 2 * ry;
1005  rx = 2 * rx;
1006  }
1007 
1008  //update selection handle rectangle
1009 
1010  //make sure selection handle size rectangle is normalized (ie, left coord < right coord)
1011  mResizeMoveX = mBeginHandleWidth + rx > 0 ? mx : mx + mBeginHandleWidth + rx;
1012  mResizeMoveY = mBeginHandleHeight + ry > 0 ? my : my + mBeginHandleHeight + ry;
1013 
1014  //calculate movement in scene coordinates
1015  QLineF translateLine = QLineF( 0, 0, mResizeMoveX, mResizeMoveY );
1016  translateLine.setAngle( translateLine.angle() - rotation() );
1017  QPointF sceneTranslate = translateLine.p2();
1018 
1019  //move selection handles
1020  QTransform itemTransform;
1021  itemTransform.translate( sceneTranslate.x(), sceneTranslate.y() );
1022  setTransform( itemTransform );
1023 
1024  //handle non-normalised resizes - eg, dragging the left handle so far to the right that it's past the right handle
1025  if ( mBeginHandleWidth + rx >= 0 && mBeginHandleHeight + ry >= 0 )
1026  {
1027  mResizeRect = QRectF( 0, 0, mBeginHandleWidth + rx, mBeginHandleHeight + ry );
1028  }
1029  else if ( mBeginHandleHeight + ry >= 0 )
1030  {
1031  mResizeRect = QRectF( QPointF( -( mBeginHandleWidth + rx ), 0 ), QPointF( 0, mBeginHandleHeight + ry ) );
1032  }
1033  else if ( mBeginHandleWidth + rx >= 0 )
1034  {
1035  mResizeRect = QRectF( QPointF( 0, -( mBeginHandleHeight + ry ) ), QPointF( mBeginHandleWidth + rx, 0 ) );
1036  }
1037  else
1038  {
1039  mResizeRect = QRectF( QPointF( -( mBeginHandleWidth + rx ), -( mBeginHandleHeight + ry ) ), QPointF( 0, 0 ) );
1040  }
1041 
1042  setRect( 0, 0, fabs( mBeginHandleWidth + rx ), fabs( mBeginHandleHeight + ry ) );
1043 
1044  //show current size of selection in status bar
1045  mComposition->setStatusMessage( QString( tr( "width: %1 mm height: %2 mm" ) ).arg( rect().width() ).arg( rect().height() ) );
1046 }
1047 
1049 {
1050  //snap to grid
1051  QPointF snappedPoint = mComposition->snapPointToGrid( point );
1052 
1053  if ( snappedPoint != point ) //don't do align snap if grid snap has been done
1054  {
1055  deleteAlignItems();
1056  return snappedPoint;
1057  }
1058 
1059  //align item
1061  {
1062  return point;
1063  }
1064 
1065  double alignX = 0;
1066  double alignY = 0;
1067 
1068  //depending on the mode, we either snap just the single point, or all the bounds of the selection
1069  switch ( mode )
1070  {
1072  snappedPoint = alignItem( alignX, alignY, point.x(), point.y() );
1073  break;
1075  snappedPoint = alignPos( point, alignX, alignY );
1076  break;
1077  }
1078 
1079  if ( alignX != -1 )
1080  {
1081  QGraphicsLineItem* item = hAlignSnapItem();
1082  int numPages = mComposition->numPages();
1083  double yLineCoord = 300; //default in case there is no single page
1084  if ( numPages > 0 )
1085  {
1086  yLineCoord = mComposition->paperHeight() * numPages + mComposition->spaceBetweenPages() * ( numPages - 1 );
1087  }
1088  item->setLine( QLineF( alignX, 0, alignX, yLineCoord ) );
1089  item->show();
1090  }
1091  else
1092  {
1094  }
1095  if ( alignY != -1 )
1096  {
1097  QGraphicsLineItem* item = vAlignSnapItem();
1098  item->setLine( QLineF( 0, alignY, mComposition->paperWidth(), alignY ) );
1099  item->show();
1100  }
1101  else
1102  {
1104  }
1105  return snappedPoint;
1106 }
1107 
1109 {
1110  if ( !mHAlignSnapItem )
1111  {
1112  mHAlignSnapItem = new QGraphicsLineItem( 0 );
1113  mHAlignSnapItem->setPen( QPen( QColor( Qt::red ) ) );
1114  scene()->addItem( mHAlignSnapItem );
1115  mHAlignSnapItem->setZValue( 90 );
1116  }
1117  return mHAlignSnapItem;
1118 }
1119 
1121 {
1122  if ( !mVAlignSnapItem )
1123  {
1124  mVAlignSnapItem = new QGraphicsLineItem( 0 );
1125  mVAlignSnapItem->setPen( QPen( QColor( Qt::red ) ) );
1126  scene()->addItem( mVAlignSnapItem );
1127  mVAlignSnapItem->setZValue( 90 );
1128  }
1129  return mVAlignSnapItem;
1130 }
1131 
1133 {
1134  if ( mHAlignSnapItem )
1135  {
1136  scene()->removeItem( mHAlignSnapItem );
1137  delete mHAlignSnapItem;
1138  mHAlignSnapItem = 0;
1139  }
1140 }
1141 
1143 {
1144  if ( mVAlignSnapItem )
1145  {
1146  scene()->removeItem( mVAlignSnapItem );
1147  delete mVAlignSnapItem;
1148  mVAlignSnapItem = 0;
1149  }
1150 }
1151 
1153 {
1156 }
1157 
1158 QPointF QgsComposerMouseHandles::alignItem( double& alignX, double& alignY, double unalignedX, double unalignedY )
1159 {
1160  double left = unalignedX;
1161  double right = left + rect().width();
1162  double midH = ( left + right ) / 2.0;
1163  double top = unalignedY;
1164  double bottom = top + rect().height();
1165  double midV = ( top + bottom ) / 2.0;
1166 
1167  QMap<double, const QgsComposerItem* > xAlignCoordinates;
1168  QMap<double, const QgsComposerItem* > yAlignCoordinates;
1169  collectAlignCoordinates( xAlignCoordinates, yAlignCoordinates );
1170 
1171  //find nearest matches x
1172  double xItemLeft = left; //new left coordinate of the item
1173  double xAlignCoord = 0;
1174  double smallestDiffX = DBL_MAX;
1175 
1176  checkNearestItem( left, xAlignCoordinates, smallestDiffX, 0, xItemLeft, xAlignCoord );
1177  checkNearestItem( midH, xAlignCoordinates, smallestDiffX, ( left - right ) / 2.0, xItemLeft, xAlignCoord );
1178  checkNearestItem( right, xAlignCoordinates, smallestDiffX, left - right, xItemLeft, xAlignCoord );
1179 
1180  //find nearest matches y
1181  double yItemTop = top; //new top coordinate of the item
1182  double yAlignCoord = 0;
1183  double smallestDiffY = DBL_MAX;
1184 
1185  checkNearestItem( top, yAlignCoordinates, smallestDiffY, 0, yItemTop, yAlignCoord );
1186  checkNearestItem( midV, yAlignCoordinates, smallestDiffY, ( top - bottom ) / 2.0, yItemTop, yAlignCoord );
1187  checkNearestItem( bottom, yAlignCoordinates, smallestDiffY, top - bottom, yItemTop, yAlignCoord );
1188 
1189  double xCoord = ( smallestDiffX < 5 ) ? xItemLeft : unalignedX;
1190  alignX = ( smallestDiffX < 5 ) ? xAlignCoord : -1;
1191  double yCoord = ( smallestDiffY < 5 ) ? yItemTop : unalignedY;
1192  alignY = ( smallestDiffY < 5 ) ? yAlignCoord : -1;
1193  return QPointF( xCoord, yCoord );
1194 }
1195 
1196 QPointF QgsComposerMouseHandles::alignPos( const QPointF& pos, double& alignX, double& alignY )
1197 {
1198  QMap<double, const QgsComposerItem* > xAlignCoordinates;
1199  QMap<double, const QgsComposerItem* > yAlignCoordinates;
1200  collectAlignCoordinates( xAlignCoordinates, yAlignCoordinates );
1201 
1202  double nearestX = pos.x();
1203  double nearestY = pos.y();
1204  if ( !nearestItem( xAlignCoordinates, pos.x(), nearestX )
1205  || !nearestItem( yAlignCoordinates, pos.y(), nearestY ) )
1206  {
1207  alignX = -1;
1208  alignY = -1;
1209  return pos;
1210  }
1211 
1212  QPointF result( pos.x(), pos.y() );
1213  if ( abs( nearestX - pos.x() ) < mComposition->alignmentSnapTolerance() )
1214  {
1215  result.setX( nearestX );
1216  alignX = nearestX;
1217  }
1218  else
1219  {
1220  alignX = -1;
1221  }
1222 
1223  if ( abs( nearestY - pos.y() ) < mComposition->alignmentSnapTolerance() )
1224  {
1225  result.setY( nearestY );
1226  alignY = nearestY;
1227  }
1228  else
1229  {
1230  alignY = -1;
1231  }
1232  return result;
1233 }
1234 
1235 void QgsComposerMouseHandles::collectAlignCoordinates( QMap< double, const QgsComposerItem* >& alignCoordsX, QMap< double, const QgsComposerItem* >& alignCoordsY )
1236 {
1237  alignCoordsX.clear();
1238  alignCoordsY.clear();
1239 
1241  {
1242  QList<QGraphicsItem *> itemList = mComposition->items();
1243  QList<QGraphicsItem *>::iterator itemIt = itemList.begin();
1244  for ( ; itemIt != itemList.end(); ++itemIt )
1245  {
1246  const QgsComposerItem* currentItem = dynamic_cast<const QgsComposerItem *>( *itemIt );
1247  //don't snap to selected items, since they're the ones that will be snapping to something else
1248  if ( !currentItem || currentItem->selected() )
1249  {
1250  continue;
1251  }
1252  QRectF itemRect;
1253  if ( dynamic_cast<const QgsPaperItem *>( *itemIt ) )
1254  {
1255  //if snapping to paper use the paper item's rect rather then the bounding rect,
1256  //since we want to snap to the page edge and not any outlines drawn around the page
1257  itemRect = currentItem->rect();
1258  }
1259  else
1260  {
1261  itemRect = currentItem->mapRectToScene( currentItem->rectWithFrame() );
1262  }
1263  alignCoordsX.insert( itemRect.left(), currentItem );
1264  alignCoordsX.insert( itemRect.right(), currentItem );
1265  alignCoordsX.insert( itemRect.center().x(), currentItem );
1266  alignCoordsY.insert( itemRect.top(), currentItem );
1267  alignCoordsY.insert( itemRect.center().y(), currentItem );
1268  alignCoordsY.insert( itemRect.bottom(), currentItem );
1269  }
1270  }
1271 
1272  //arbitrary snap lines
1273  if ( mComposition->alignmentSnap() )
1274  {
1275  QList< QGraphicsLineItem* >::const_iterator sIt = mComposition->snapLines()->constBegin();
1276  for ( ; sIt != mComposition->snapLines()->constEnd(); ++sIt )
1277  {
1278  double x = ( *sIt )->line().x1();
1279  double y = ( *sIt )->line().y1();
1280  if ( qgsDoubleNear( y, 0.0 ) )
1281  {
1282  alignCoordsX.insert( x, 0 );
1283  }
1284  else
1285  {
1286  alignCoordsY.insert( y, 0 );
1287  }
1288  }
1289  }
1290 }
1291 
1292 void QgsComposerMouseHandles::checkNearestItem( double checkCoord, const QMap< double, const QgsComposerItem* >& alignCoords, double& smallestDiff, double itemCoordOffset, double& itemCoord, double& alignCoord ) const
1293 {
1294  double currentCoord = 0;
1295  if ( !nearestItem( alignCoords, checkCoord, currentCoord ) )
1296  {
1297  return;
1298  }
1299 
1300  double currentDiff = abs( checkCoord - currentCoord );
1301  if ( currentDiff < mComposition->alignmentSnapTolerance() )
1302  {
1303  itemCoord = currentCoord + itemCoordOffset;
1304  alignCoord = currentCoord;
1305  smallestDiff = currentDiff;
1306  }
1307 }
1308 
1309 bool QgsComposerMouseHandles::nearestItem( const QMap< double, const QgsComposerItem* >& coords, double value, double& nearestValue ) const
1310 {
1311  if ( coords.size() < 1 )
1312  {
1313  return false;
1314  }
1315 
1316  QMap< double, const QgsComposerItem* >::const_iterator it = coords.lowerBound( value );
1317  if ( it == coords.constBegin() ) //value smaller than first map value
1318  {
1319  nearestValue = it.key();
1320  return true;
1321  }
1322  else if ( it == coords.constEnd() ) //value larger than last map value
1323  {
1324  --it;
1325  nearestValue = it.key();
1326  return true;
1327  }
1328  else
1329  {
1330  //get smaller value and larger value and return the closer one
1331  double upperVal = it.key();
1332  --it;
1333  double lowerVal = it.key();
1334 
1335  double lowerDiff = value - lowerVal;
1336  double upperDiff = upperVal - value;
1337  if ( lowerDiff < upperDiff )
1338  {
1339  nearestValue = lowerVal;
1340  return true;
1341  }
1342  else
1343  {
1344  nearestValue = upperVal;
1345  return true;
1346  }
1347  }
1348 }