Engauge Digitizer  2
ViewProfileDivider.cpp
1 #include <QBrush>
2 #include <QCursor>
3 #include <QDebug>
4 #include <QGraphicsLineItem>
5 #include <QGraphicsPolygonItem>
6 #include <QGraphicsScene>
7 #include <QGraphicsSceneMouseEvent>
8 #include <QGraphicsView>
9 #include <QPen>
10 #include "ViewProfileDivider.h"
11 
12 const double ARROW_WIDTH = 4.0;
13 const double ARROW_HEIGHT = 5.0;
14 const double DIVIDER_WIDTH = 0.0; // Zero value gives a concise line that is a single pixel wide
15 const int PADDLE_HEIGHT = 10;
16 const int PADDLE_WIDTH = 10;
17 const double SHADED_AREA_OPACITY = 0.4;
18 const int X_INITIAL = 0;
19 const int SLOP = 2; // Pixels of shading added at each boundary to prevent a gap
20 const QColor ARROW_COLOR (Qt::NoPen);
21 const QColor SHADED_AREA_COLOR = QColor (220, 220, 220); // Light gray
22 const QColor DIVIDER_COLOR = QColor (140, 140, 255); // Slightly darker gray
23 
25  QGraphicsView &view,
26  int sceneWidth,
27  int sceneHeight,
28  int yCenter,
29  bool isLowerBoundary) :
30  QGraphicsRectItem (X_INITIAL,
31  0,
32  PADDLE_WIDTH,
33  PADDLE_HEIGHT),
34  m_view (view),
35  m_yCenter (yCenter),
36  m_divider (0),
37  m_shadedArea (0),
38  m_sceneWidth (sceneWidth),
39  m_sceneHeight (sceneHeight),
40  m_isLowerBoundary (isLowerBoundary)
41 {
42  // Initial positions will not appear since they are overridden by setX
43 
44  // Paddle
45  setVisible (true);
46  setPen (QPen (DIVIDER_COLOR));
47  setBrush (QBrush (QColor (140, 255, 140)));
48  setOpacity (1.0);
49  scene.addItem (this);
50  setFlags (QGraphicsItem::ItemIsMovable |
51  QGraphicsItem::ItemSendsGeometryChanges);
52  setCursor (Qt::OpenHandCursor);
53  setZValue (2.0);
54 
55  // Arrow on paddle
56  m_arrow = new QGraphicsPolygonItem (this);
57 
58  // Shaded area
59  m_shadedArea = new QGraphicsRectItem (X_INITIAL,
60  0,
61  0,
62  sceneHeight - 1);
63  m_shadedArea->setOpacity (SHADED_AREA_OPACITY);
64  m_shadedArea->setBrush (QBrush (SHADED_AREA_COLOR));
65  m_shadedArea->setPen (Qt::NoPen);
66  m_shadedArea->setZValue (0.0);
67  scene.addItem (m_shadedArea);
68 
69  // Vertical divider. This is not made a child of the paddle since that will force the divider
70  // to always be drawn above the paddle, rather than underneath the paddle as we want. Even setting
71  // the z values will not succeed in drawing the divider under the paddle if they are child-parent.
72  m_divider = new QGraphicsLineItem (X_INITIAL,
73  -SLOP,
74  X_INITIAL,
75  2 * SLOP + sceneHeight);
76  m_divider->setPen (QPen (QBrush (DIVIDER_COLOR), DIVIDER_WIDTH));
77  m_divider->setZValue (1.0);
78  scene.addItem (m_divider);
79 }
80 
81 QVariant ViewProfileDivider::itemChange (GraphicsItemChange change, const QVariant &value)
82 {
83  if (change == ItemPositionChange && scene ()) {
84 
85  // Clip x coordinate, in pixel coordinates. Y coordinate stays the same (by setting delta to zero)
86  QPointF newPos = QPointF (value.toPointF().x(), 0.0) + m_startDragPos;
87  double newX = newPos.x();
88  newX = qMax (newX, 0.0);
89  newX = qMin (newX, (double) m_sceneWidth);
90  newPos.setX (newX);
91  newPos -= m_startDragPos; // Change from absolute coordinates back to relative coordinates
92 
93  // Before returning newPos for the paddle, we apply its movement to the divider and shaded area
94  m_xScene = newX;
95  updateGeometryDivider();
96  updateGeometryNonPaddle ();
97 
98  sendSignalMoved ();
99 
100  return newPos;
101  }
102 
103  return QGraphicsRectItem::itemChange (change, value);
104 }
105 
106 void ViewProfileDivider::mousePressEvent(QGraphicsSceneMouseEvent * /* event */)
107 {
108  // Since the cursor position is probably not in the center of the paddle, we save the paddle center
109  m_startDragPos = QPointF (rect().x () + rect().width () / 2.0,
110  rect().y () + rect().height () / 2.0);
111 }
112 
113 void ViewProfileDivider::sendSignalMoved ()
114 {
115  if (m_isLowerBoundary) {
116  emit signalMovedLow (m_xScene);
117  } else {
118  emit signalMovedHigh (m_xScene);
119  }
120 }
121 
123  double xLow,
124  double xHigh)
125 {
126  // Convert to screen coordinates
127  m_xScene = m_sceneWidth * (x - xLow) / (xHigh - xLow);
128  sendSignalMoved ();
129 
130  updateGeometryPaddle ();
131  updateGeometryDivider ();
132  updateGeometryNonPaddle ();
133 
134  // Triangle vertices
135  double xLeft = rect().left() + rect().width() / 2.0 - ARROW_WIDTH / 2.0;
136  double xRight = rect().left() + rect().width() / 2.0 + ARROW_WIDTH / 2.0;
137  double yTop = rect().top() + rect().height() / 2.0 - ARROW_HEIGHT / 2.0;
138  double yMiddle = rect().top() + rect().height() / 2.0;
139  double yBottom = rect().top() + rect().height() / 2.0 + ARROW_HEIGHT / 2.0;
140 
141  QPolygonF polygonArrow;
142  if (m_isLowerBoundary) {
143 
144  // Draw arrow pointing to the right
145  polygonArrow.push_front (QPointF (xLeft, yTop));
146  polygonArrow.push_front (QPointF (xRight, yMiddle));
147  polygonArrow.push_front (QPointF (xLeft, yBottom));
148 
149  } else {
150 
151  // Draw arrow pointing to the left
152  polygonArrow.push_front (QPointF (xRight, yTop));
153  polygonArrow.push_front (QPointF (xLeft, yMiddle));
154  polygonArrow.push_front (QPointF (xRight, yBottom));
155  }
156  m_arrow->setPolygon (polygonArrow);
157  m_arrow->setPen (QPen (Qt::black));
158  m_arrow->setBrush (QBrush (ARROW_COLOR));
159 }
160 
161 void ViewProfileDivider::slotOtherMoved(double xSceneOther)
162 {
163  m_xSceneOther = xSceneOther;
164  updateGeometryNonPaddle ();
165 }
166 
167 void ViewProfileDivider::updateGeometryDivider ()
168 {
169  m_divider->setLine (m_xScene,
170  -SLOP,
171  m_xScene,
172  2 * SLOP + m_sceneHeight);
173 }
174 
175 void ViewProfileDivider::updateGeometryNonPaddle()
176 {
177  if (m_isLowerBoundary) {
178  if (m_xScene <= m_xSceneOther) {
179 
180  // There is one unshaded region in the center
181  m_shadedArea->setRect (-SLOP,
182  -SLOP,
183  SLOP + m_xScene,
184  2 * SLOP + m_sceneHeight);
185 
186  } else {
187 
188  // There are two unshaded regions on the two sides
189  m_shadedArea->setRect (m_xSceneOther,
190  -SLOP,
191  m_xScene - m_xSceneOther,
192  2 * SLOP + m_sceneHeight);
193 
194  }
195  } else {
196 
197  if (m_xSceneOther <= m_xScene) {
198 
199  // There are two unshaded regions on the two sides
200  m_shadedArea->setRect (m_xScene,
201  -SLOP,
202  SLOP + m_sceneWidth - m_xScene,
203  2 * SLOP + m_sceneHeight);
204 
205  } else {
206 
207  // There is one unshaded region in the center. To prevent extra-dark shading due to having two
208  // overlapping shaded areas, this shaded area is given zero extent
209  m_shadedArea->setRect (m_xSceneOther,
210  -SLOP,
211  0,
212  2 * SLOP + m_sceneHeight);
213  }
214  }
215 }
216 
217 void ViewProfileDivider::updateGeometryPaddle ()
218 {
219  setRect (m_xScene - PADDLE_WIDTH / 2,
220  m_yCenter - PADDLE_HEIGHT / 2,
221  PADDLE_WIDTH,
222  PADDLE_HEIGHT);
223 }
void signalMovedHigh(double xSceneOther)
Signal used when divider is dragged and m_isLowerBoundary is false.
void setX(double x, double xLow, double xHigh)
Set the position by specifying the new x coordinate.
ViewProfileDivider(QGraphicsScene &scene, QGraphicsView &view, int sceneWidth, int sceneHeight, int yCenter, bool isLowerBoundary)
Single constructor.
void signalMovedLow(double xSceneOther)
Signal used when divider is dragged and m_isLowerBoundary is true.
virtual QVariant itemChange(GraphicsItemChange change, const QVariant &value)
Intercept changes so divider movement can be restricted to horizontal direction only.
virtual void mousePressEvent(QGraphicsSceneMouseEvent *event)
Save paddle position at start of click-and-drag.