QGraphicsItem鼠標旋轉控制研究

在QT場景視圖中2D圖形項Item的基類爲QGraphicsItem,若是咱們須要自定義Item則能夠從其派生,而後重寫boundingRect以及paint虛函數實現圖形項的外邊界定義以及內容繪製工做。若是須要將Qt基本的Widget組件加入到場景中,該框架爲咱們提供了QGraphicsWidget(QGraphicsProxyWidget)類,若是須要圖形項具備信號槽的功能,該框架又提供了QGraphicsObject類,方便咱們根據須要選擇相關的類。html

QgraphicsItem類提供了簡單方便的setRotation方法傳入旋轉角度(-360,360)來直接控制圖形項繞Z軸(垂直於屏幕的軸)的順時針以及逆時針旋轉,也能夠經過更加綜合性的QTransform類來實現。旋轉的中心點則能夠經過setTransformOriginPoint()方法來設定,默認是(0,0)座標原點。框架

須要明白的是,對圖形項的旋轉實質上是對項座標系的旋轉,圖形繪製的座標相對於項座標是不變的。dom

調用setRotation方法當然方便,但須要手動指定旋轉的角度值,而實際的狀況中,咱們經常須要經過鼠標選中並移動來實現旋轉,這就須要經過鼠標移動來不斷計算正確的旋轉角度值,這是重點,也是難點。   ide

思路:函數

1) 肯定旋轉中心座標:centerPoint(O)測試

2) 在mousePressEvent事件響應中獲取按下時旋轉控制點座標:pressPoint(A)this

3) 在mouseMoveEvent事件響應中獲取當前移動點的座標:movePoint(B)spa

4)      centerPoint、pressPoint、movePoint三點肯定的旋轉角度angle:角AOB.net

5)      在原始旋轉角度rotation()基礎上設置新的累積角度orm

         setRotation(rotation() + angle);

 

難點:如何計算角AOB

inline qreal GetDegreeAngle(QVector2D vector2d) const

{

        return fmod((atan2((qreal)vector2d.y(), (qreal)vector2d.x()) * AnglePerPI + 360.0), 360.0 );

}

C 語言裏 double atan2(double y,double x) 返回的是原點至點(x,y)的方位角,即與 x 軸的夾角。返回值的單位爲弧度,取值範圍爲(-π, π]。結果爲正表示從 X 軸逆時針旋轉的角度,結果爲負表示從 X 軸順時針旋轉的角度。若要用度表示反正切值,請將結果再乘以 180/π。fmod是浮點數求餘數運算,函數總體返回向量與+x軸的順時針角度值。

測試代碼:

僅供參考,若有錯誤,歡迎指正,歡迎改進幫助更加完善。

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
 
#ifndef  ITEMBASE_H
#define  ITEMBASE_H

#include  <QGraphicsItem>
#include  <QTime>
#include  <QVector2D>
#include  <cmath>

const  qreal PI =  3 . 141592653 ;
const  qreal AnglePerPI =  180 . 0  / PI;

class  QGraphicsScene;
class  ItemBase :  public  QGraphicsItem
{
public :
    
enum  { Type = UserType +  1  };

    ItemBase(
int  size,  int  x,  int  y);
    ~ItemBase() override;

    QRectF boundingRect() 
const  override;
    
void  paint(QPainter *painter,  const  QStyleOptionGraphicsItem *option, QWidget *widget) override;

    
inline  qreal GetDegreeAngle(QVector2D vector2d)  const
    {
        
return  fmod((atan2((qreal)vector2d.y(), (qreal)vector2d.x()) * AnglePerPI +  360 . 0 ),  360 . 0  );
    }

protected :
    
virtual  ItemBase *createNew( int  size,  int  x,  int  y) =  0 ;
    
void  contextMenuEvent(QGraphicsSceneContextMenuEvent *event) override;
    
void  mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
    
void  hoverMoveEvent(QGraphicsSceneHoverEvent *event) override;
    
void  mousePressEvent(QGraphicsSceneMouseEvent *event) override;
    
void  mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
    
void  keyPressEvent(QKeyEvent *event) override;
    
void  wheelEvent(QGraphicsSceneWheelEvent *event) override;
    
int  type()  const  override;
    
bool  isInResizeArea( const  QPointF &pos);
    
bool  isInRotateArea( const  QPointF &pos);

    
static   void  duplicateSelectedItems(QGraphicsScene *scene);
    
static   void  deleteSelectedItems(QGraphicsScene *scene);
    
static   void  growSelectedItems(QGraphicsScene *scene);
    
static   void  shrinkSelectedItems(QGraphicsScene *scene);

    
int          m_size;
    QTime       m_startTime;
    
bool         m_isResizing;
    
bool         m_isRotating;
};


#endif   // ITEMBASE_H



#include   "ItemBase.h"
#include  <QStyleOptionGraphicsItem>
#include  <QPainter>
#include  <QGraphicsScene>
#include  <QGraphicsEllipseItem>
#include  <QMenu>
#include  <QGraphicsSceneMouseEvent>
#include  <QKeyEvent>
#include  <qmath.h>
#include  <GL/gl.h>
#include  <QDebug>
#include  <QRandomGenerator>

const   int  MAX_ITEM_SIZE    =  512 ;
const   int  MIN_ITEM_SIZE    =  16 ;

//============================================================================//
//                                  ItemBase                                  //
//============================================================================//

ItemBase::ItemBase(
int  size,  int  x,  int  y)
    : m_size(size)
    , m_isResizing(
false )
    , m_isRotating(
false )
{
    setFlag(QGraphicsItem::ItemIsMovable, 
true );
    setFlag(QGraphicsItem::ItemIsSelectable, 
true );
    setFlag(QGraphicsItem::ItemIsFocusable, 
true );
    setFlag(QGraphicsItem::ItemSendsGeometryChanges, 
true );
    setAcceptHoverEvents(
true );
    setPos(x, y);
    m_startTime = QTime::currentTime();
}

ItemBase::~ItemBase()
{

}

QRectF ItemBase::boundingRect() 
const
{
    
return  QRectF(-m_size /  2 , -m_size /  2 , m_size, m_size);
}

void  ItemBase::paint(QPainter *painter,  const  QStyleOptionGraphicsItem *option, QWidget *)
{
    
if  (option->state & QStyle::State_Selected)
    {
        painter->setRenderHint(QPainter::Antialiasing, 
true );
        
if  (option->state & QStyle::State_HasFocus)
        {
            painter->setPen(Qt::yellow);
        }
        
else
        {
            painter->setPen(Qt::white);
        }
        painter->drawRect(boundingRect());
        
// resize contrl area...
        painter->drawLine(m_size /  2  -  9 , m_size /  2 , m_size /  2 , m_size /  2  -  9 );
        painter->drawLine(m_size / 
2  -  6 , m_size /  2 , m_size /  2 , m_size /  2  -  6 );
        painter->drawLine(m_size / 
2  -  3 , m_size /  2 , m_size /  2 , m_size /  2  -  3 );
        
// rotate control point
        painter->setPen(Qt::blue);
        painter->drawLine(
0 , m_size /  2 0 , -m_size /  2 );
        painter->setPen(QPen(Qt::blue, 
5 ));
        painter->drawPoint(
0 , -m_size /  2 );

        painter->setRenderHint(QPainter::Antialiasing, 
false );

    }
    
else
    {

    }
}

void  ItemBase::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
    
if  (!isSelected() && scene())
    {
        scene()->clearSelection();
        setSelected(
true );
    }

    QMenu menu;
    QAction *delAction = menu.addAction(
"Delete" );
    QAction *newAction = menu.addAction(
"New" );
    QAction *growAction = menu.addAction(
"Grow" );
    QAction *shrinkAction = menu.addAction(
"Shrink" );

    QAction *selectedAction = menu.exec(event->screenPos());

    
if  (selectedAction == delAction)
        deleteSelectedItems(scene());
    
else   if  (selectedAction == newAction)
        duplicateSelectedItems(scene());
    
else   if  (selectedAction == growAction)
        growSelectedItems(scene());
    
else   if  (selectedAction == shrinkAction)
        shrinkSelectedItems(scene());
}

void  ItemBase::duplicateSelectedItems(QGraphicsScene *scene)
{
    
if  (!scene)
        
return ;

    QList<QGraphicsItem *> selected;
    selected = scene->selectedItems();

    foreach (QGraphicsItem *item, selected)
    {
        ItemBase *itemBase = qgraphicsitem_cast<ItemBase *>(item);
        
if  (itemBase)
            scene->addItem(itemBase->createNew(itemBase->m_size,
                                               
static_cast < int >(itemBase->pos().x()) + itemBase->m_size,
                                               
static_cast < int >(itemBase->pos().y())));
    }
}

void  ItemBase::deleteSelectedItems(QGraphicsScene *scene)
{
    
if  (!scene)
        
return ;

    QList<QGraphicsItem *> selected;
    selected = scene->selectedItems();

    foreach (QGraphicsItem *item, selected)
    {
        ItemBase *itemBase = qgraphicsitem_cast<ItemBase *>(item);
        
if  (itemBase)
            
delete  itemBase;
    }
}

void  ItemBase::growSelectedItems(QGraphicsScene *scene)
{
    
if  (!scene)
        
return ;

    QList<QGraphicsItem *> selected;
    selected = scene->selectedItems();

    foreach (QGraphicsItem *item, selected)
    {
        ItemBase *itemBase = qgraphicsitem_cast<ItemBase *>(item);
        
if  (itemBase)
        {
            itemBase->prepareGeometryChange();
            itemBase->m_size *= 
2 ;
            
if  (itemBase->m_size > MAX_ITEM_SIZE)
                itemBase->m_size = MAX_ITEM_SIZE;
        }
    }
}

void  ItemBase::shrinkSelectedItems(QGraphicsScene *scene)
{
    
if  (!scene)
        
return ;

    QList<QGraphicsItem *> selected;
    selected = scene->selectedItems();

    foreach (QGraphicsItem *item, selected)
    {
        ItemBase *itemBase = qgraphicsitem_cast<ItemBase *>(item);
        
if  (itemBase)
        {
            itemBase->prepareGeometryChange();
            itemBase->m_size /= 
2 ;
            
if  (itemBase->m_size < MIN_ITEM_SIZE)
                itemBase->m_size = MIN_ITEM_SIZE;
        }
    }
}

void  ItemBase::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    
if  (m_isResizing)
    {
        
int  dx =  int ( 2 . 0  * event->pos().x());
        
int  dy =  int ( 2 . 0  * event->pos().y());
        prepareGeometryChange();
        m_size = (dx > dy ? dx : dy);
        
if  (m_size < MIN_ITEM_SIZE)
            m_size = MIN_ITEM_SIZE;
        
else   if  (m_size > MAX_ITEM_SIZE)
            m_size = MAX_ITEM_SIZE;
    }
    
else   if  (m_isRotating)
    {
        QPointF cursorPos = event->pos();
        QVector2D vectorStart = QVector2D(QPointF(
0 . 0 , -m_size /  2 ) - QPointF( 0 . 0 0 . 0 ));            // 起始向量
        QVector2D vectorEnd = QVector2D(cursorPos - QPointF( 0 . 0 0 . 0 ));                              // 結束向量
         // 計算起始向量和結束向量之間的角度
        qreal angle =  0 . 0 ;
        qreal angleEnd = GetDegreeAngle(vectorEnd);
        qreal angleStart = GetDegreeAngle(vectorStart);
        angle = angleEnd - angleStart + rotation();
        
if  (angle >  360 . 0 )
        {
            
while ( 1 )
            {
                angle -= 
360 . 0 ;
                
if  (angle <  360 . 0 break ;
            }
        }
        
else   if  (angle <  0 . 0 )
        {
            
while ( 1 )
            {
                angle += 
360 . 0 ;
                
if  (angle >  0 . 0 break ;
            }
        }
        setRotation(angle);
    }
    
else
    {
        QGraphicsItem::mouseMoveEvent(event);
    }
}

void  ItemBase::hoverMoveEvent(QGraphicsSceneHoverEvent *event)
{
    
if  (m_isResizing || (isInResizeArea(event->pos()) && isSelected()))
        setCursor(Qt::SizeFDiagCursor);
    
else   if  ((isInRotateArea(event->pos()) && isSelected()))
    {
        QCursor *rotateCursor = 
new  QCursor(QPixmap( ":/rotate_handler.png" ));
        setCursor(*rotateCursor);
    }
    
else
        setCursor(Qt::ArrowCursor);
    QGraphicsItem::hoverMoveEvent(event);
}

void  ItemBase::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    
static  qreal z =  0 . 0 ;
    setZValue(z += 
1 . 0 );
    
if  (event->button() == Qt::LeftButton && isInResizeArea(event->pos()))
    {
        m_isResizing = 
true ;
    }
    
else   if  (event->button() == Qt::LeftButton && isInRotateArea(event->pos()))
    {
        m_isRotating = 
true ;
    }
    
else
    {
        QGraphicsItem::mousePressEvent(event);
    }
}

void  ItemBase::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
{
    
if  (event->button() == Qt::LeftButton && m_isResizing)
    {
        m_isResizing = 
false ;
    }
    
else   if  (event->button() == Qt::LeftButton && m_isRotating)
    {
        m_isRotating = 
false ;
    }
    
else
    {
        QGraphicsItem::mouseReleaseEvent(event);
    }
}

void  ItemBase::keyPressEvent(QKeyEvent *event)
{
    
switch  (event->key())
    {
    
case  Qt::Key_Delete:
        deleteSelectedItems(scene());
        
break ;
    
case  Qt::Key_Insert:
        duplicateSelectedItems(scene());
        
break ;
    
case  Qt::Key_Plus:
        growSelectedItems(scene());
        
break ;
    
case  Qt::Key_Minus:
        shrinkSelectedItems(scene());
        
break ;
    
default :
        QGraphicsItem::keyPressEvent(event);
        
break ;
    }
}

void  ItemBase::wheelEvent(QGraphicsSceneWheelEvent *event)
{
    prepareGeometryChange();
    m_size = 
int (m_size * qExp(-event->delta() /  600 . 0 ));
    
if  (m_size > MAX_ITEM_SIZE)
        m_size = MAX_ITEM_SIZE;
    
else   if  (m_size < MIN_ITEM_SIZE)
        m_size = MIN_ITEM_SIZE;
}

int  ItemBase::type()  const
{
    
return  Type;
}


bool  ItemBase::isInResizeArea( const  QPointF &pos)
{
    
return  (-pos.y() < pos.x() - m_size +  9 );
}

bool  ItemBase::isInRotateArea( const  QPointF &pos)
{
    
if  (   - 5  <= pos.x()
            && pos.x() <= 
5
            && -m_size / 
2  <= pos.y()
            && pos.y() <= -m_size / 
2  +  5 )
    {
        
return   true ;
    }
    
else
    {
        
return   false ;
    }
}




#ifndef  MYITEM_H
#define  MYITEM_H

#include   "ItemBase.h"

class  MyItem :  public  ItemBase
{
public :
    MyItem(
int  size,  int  x,  int  y);
    
virtual  ~MyItem() override;
    
void  paint(QPainter *painter,  const  QStyleOptionGraphicsItem *option, QWidget *widget) override;

protected :
    ItemBase *createNew(
int  size,  int  x,  int  y) override;
};

#endif   // MYITEM_H


#include   "MyItem.h"
#include  <QPainter>

MyItem::MyItem(
int  size,  int  x,  int  y)
    : ItemBase (size, x, y)
{

}

MyItem::~MyItem()
{

}

void  MyItem::paint(QPainter *painter,  const  QStyleOptionGraphicsItem *option, QWidget *widget)
{
    painter->setRenderHint(QPainter::Antialiasing, 
true );
    QVector<QLine> vec;
    QLine l1(-m_size * 
0 . 4 , m_size *  0 . 4 , m_size *  0 . 4 , m_size *  0 . 4 );
    QLine l2(-m_size * 
0 . 4 , m_size *  0 . 2 , m_size *  0 . 4 , m_size *  0 . 2 );
    QLine l3(-m_size * 
0 . 4 , -m_size *  0 . 2 , m_size *  0 . 4 , -m_size *  0 . 2 );
    QLine l4(-m_size * 
0 . 4 , -m_size *  0 . 4 , m_size *  0 . 4 , -m_size *  0 . 4 );
    vec.push_back(l1);
    vec.push_back(l2);
    vec.push_back(l3);
    vec.push_back(l4);
    painter->drawLines(vec);

    ItemBase::paint(painter, option, widget);
}

ItemBase *MyItem::createNew(
int  size,  int  x,  int  y)
{
    
return   new  MyItem(size, x, y);
}



#ifndef  MAINWINDOW_H
#define  MAINWINDOW_H

#include  <QMainWindow>

class  QGraphicsView;
class  QGraphicsScene;
class  MainWindow :  public  QMainWindow
{
    Q_OBJECT

public :
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private :
    QGraphicsView   *m_view;
    QGraphicsScene  *m_scene;
};

#endif   // MAINWINDOW_H


#include   "MainWindow.h"
#include  <QGraphicsView>
#include  <QGraphicsScene>
#include  <QRandomGenerator>
#include   "MyItem.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    m_scene = 
new  QGraphicsScene( this );
    m_scene->setSceneRect(
0 . 0 0 . 0 1024 . 0 768 . 0 );
    m_view = 
new  QGraphicsView( this );
    m_view->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
    m_view->setScene(m_scene);
    setCentralWidget(m_view);

    QSize size = m_scene->sceneRect().size().toSize();
    m_scene->addItem(
new  MyItem( 64 , QRandomGenerator::global()->bounded(size.width() -  64 ) +  32 ,
                                QRandomGenerator::global()->bounded(size.height() - 
64 ) +  32 ));
}

MainWindow::~MainWindow()
{

}

測試運行:

相關文章
相關標籤/搜索