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