41
|
1 /****************************************************************************
|
|
2 **
|
|
3 ** Copyright (C) 2009 Nokia Corporation and/or its subsidiary(-ies).
|
|
4 ** Contact: Qt Software Information (qt-info@nokia.com)
|
|
5 **
|
|
6 ** This file is part of the demonstration applications of the Qt Toolkit.
|
|
7 **
|
|
8 ** $QT_BEGIN_LICENSE:LGPL$
|
|
9 ** Commercial Usage
|
|
10 ** Licensees holding valid Qt Commercial licenses may use this file in
|
|
11 ** accordance with the Qt Commercial License Agreement provided with the
|
|
12 ** Software or, alternatively, in accordance with the terms contained in
|
|
13 ** a written agreement between you and Nokia.
|
|
14 **
|
|
15 ** GNU Lesser General Public License Usage
|
|
16 ** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
17 ** General Public License version 2.1 as published by the Free Software
|
|
18 ** Foundation and appearing in the file LICENSE.LGPL included in the
|
|
19 ** packaging of this file. Please review the following information to
|
|
20 ** ensure the GNU Lesser General Public License version 2.1 requirements
|
|
21 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
|
|
22 **
|
|
23 ** In addition, as a special exception, Nokia gives you certain
|
|
24 ** additional rights. These rights are described in the Nokia Qt LGPL
|
|
25 ** Exception version 1.0, included in the file LGPL_EXCEPTION.txt in this
|
|
26 ** package.
|
|
27 **
|
|
28 ** GNU General Public License Usage
|
|
29 ** Alternatively, this file may be used under the terms of the GNU
|
|
30 ** General Public License version 3.0 as published by the Free Software
|
|
31 ** Foundation and appearing in the file LICENSE.GPL included in the
|
|
32 ** packaging of this file. Please review the following information to
|
|
33 ** ensure the GNU General Public License version 3.0 requirements will be
|
|
34 ** met: http://www.gnu.org/copyleft/gpl.html.
|
|
35 **
|
|
36 ** If you are unsure which license is appropriate for your use, please
|
|
37 ** contact the sales department at qt-sales@nokia.com.
|
|
38 ** $QT_END_LICENSE$
|
|
39 **
|
|
40 ****************************************************************************/
|
162
|
41 module hoverpoints;
|
41
|
42
|
|
43 version (QT_OPENGL_SUPPORT)
|
|
44 import qt.opengl.QGLWidget;
|
|
45
|
|
46 version (D_Version2) {}
|
|
47 else
|
|
48 import tango.core.Array : sort;
|
|
49
|
|
50 import
|
|
51 qt.gui.QWidget,
|
|
52 qt.qtd.Array,
|
|
53 arthurwidgets;
|
|
54
|
|
55 final class HoverPoints : QObject
|
|
56 {
|
|
57 public:
|
|
58 enum PointShape {
|
|
59 CircleShape,
|
|
60 RectangleShape
|
|
61 }
|
|
62
|
|
63 enum LockType {
|
|
64 LockToLeft = 0x01,
|
|
65 LockToRight = 0x02,
|
|
66 LockToTop = 0x04,
|
|
67 LockToBottom = 0x08
|
|
68 }
|
|
69
|
|
70 enum SortType {
|
|
71 NoSort,
|
|
72 XSort,
|
|
73 YSort
|
|
74 }
|
|
75
|
|
76 enum ConnectionType {
|
|
77 NoConnection,
|
|
78 LineConnection,
|
|
79 CurveConnection
|
|
80 }
|
|
81
|
|
82 private:
|
|
83 QWidget m_widget;
|
|
84
|
|
85 QPolygonF m_points;
|
|
86 QRectF m_bounds;
|
|
87 PointShape m_shape;
|
|
88 SortType m_sortType;
|
|
89 ConnectionType m_connectionType;
|
|
90
|
|
91 uint[] m_locks;
|
|
92
|
|
93 QSizeF m_pointSize;
|
|
94 int m_currentIndex;
|
|
95 bool m_editable;
|
|
96 bool m_enabled;
|
|
97
|
|
98 QPen m_pointPen;
|
|
99 QBrush m_pointBrush;
|
|
100 QPen m_connectionPen;
|
|
101
|
|
102 public:
|
|
103 mixin Signal!("pointsChanged", QPolygonF /*points*/);
|
|
104
|
|
105 this(QWidget widget, PointShape shape)
|
|
106 {
|
|
107 super(widget);
|
|
108
|
|
109 m_widget = widget;
|
|
110 widget.installEventFilter(this);
|
|
111
|
|
112 m_connectionType = ConnectionType.CurveConnection;
|
|
113 m_sortType = SortType.NoSort;
|
|
114 m_shape = shape;
|
|
115 m_pointPen = new QPen(new QBrush(new QColor(255, 255, 255, 191)), 1);
|
|
116 m_connectionPen = new QPen(new QBrush(new QColor(255, 255, 255, 127)), 2);
|
|
117 m_pointBrush = new QBrush(new QColor(191, 191, 191, 127));
|
|
118 m_pointSize = QSizeF(11, 11);
|
|
119 m_currentIndex = -1;
|
|
120 m_editable = true;
|
|
121 m_enabled = true;
|
|
122
|
|
123 pointsChanged.connect(&m_widget.update);
|
|
124 }
|
|
125
|
|
126 void setBoundingRect(QRectF boundingRect) { m_bounds = boundingRect; }
|
|
127
|
|
128
|
|
129 QRectF pointBoundingRect(int i)
|
|
130 {
|
|
131 QPointF p = m_points.at(i);
|
|
132 qreal w = m_pointSize.width();
|
|
133 qreal h = m_pointSize.height();
|
|
134 qreal x = p.x() - w / 2;
|
|
135 qreal y = p.y() - h / 2;
|
|
136 return new QRectF(x, y, w, h);
|
|
137 }
|
|
138
|
|
139 QRectF boundingRect()
|
|
140 {
|
|
141 if (m_bounds.isEmpty())
|
|
142 return new QRectF(m_widget.rect());
|
|
143 else
|
|
144 return m_bounds;
|
|
145 }
|
|
146
|
|
147 QPolygonF points() { return m_points; }
|
|
148
|
|
149 QSizeF pointSize() { return m_pointSize; }
|
|
150 void setPointSize(QSizeF size) { m_pointSize = size; }
|
|
151
|
|
152 SortType sortType() { return m_sortType; }
|
|
153 void setSortType(SortType sortType) { m_sortType = sortType; }
|
|
154
|
|
155 ConnectionType connectionType() { return m_connectionType; }
|
|
156 void setConnectionType(ConnectionType connectionType) { m_connectionType = connectionType; }
|
|
157
|
|
158 void setConnectionPen(QPen pen) { m_connectionPen = pen; }
|
|
159 void setShapePen(QPen pen) { m_pointPen = pen; }
|
|
160 void setShapeBrush(QBrush brush) { m_pointBrush = brush; }
|
|
161
|
|
162 void setPointLock(int pos, LockType lock) { m_locks[pos] = lock; }
|
|
163
|
|
164 void setEditable(bool editable) { m_editable = editable; }
|
|
165 bool editable() { return m_editable; }
|
|
166
|
|
167 void setEnabled(bool enabled)
|
|
168 {
|
|
169 if (m_enabled != enabled) {
|
|
170 m_enabled = enabled;
|
|
171 m_widget.update();
|
|
172 }
|
|
173 }
|
|
174
|
|
175
|
|
176 override bool eventFilter(QObject object, QEvent event)
|
|
177 {
|
|
178 if ((object == m_widget) && m_enabled) {
|
|
179 switch (event.type()) {
|
|
180
|
|
181 case QEvent.MouseButtonPress:
|
|
182 {
|
|
183 QMouseEvent me = cast(QMouseEvent) event;
|
|
184
|
|
185 QPointF clickPos = me.pos();
|
|
186 int index = -1;
|
|
187 for (int i=0; i<m_points.size(); ++i) {
|
|
188 auto path = new QPainterPath;
|
|
189 if (m_shape == PointShape.CircleShape)
|
|
190 path.addEllipse(pointBoundingRect(i));
|
|
191 else
|
|
192 path.addRect(pointBoundingRect(i));
|
|
193
|
|
194 if (path.contains(clickPos)) {
|
|
195 index = i;
|
|
196 break;
|
|
197 }
|
|
198 }
|
|
199
|
|
200 if (me.button() == Qt.LeftButton) {
|
|
201 if (index == -1) {
|
|
202 if (!m_editable)
|
|
203 return false;
|
|
204 int pos = 0;
|
|
205 // Insert sort for x or y
|
|
206 if (m_sortType == SortType.XSort) {
|
|
207 for (int i=0; i<m_points.size(); ++i)
|
|
208 if (m_points.at(i).x() > clickPos.x()) {
|
|
209 pos = i;
|
|
210 break;
|
|
211 }
|
|
212 } else if (m_sortType == SortType.YSort) {
|
|
213 for (int i=0; i<m_points.size(); ++i)
|
|
214 if (m_points.at(i).y() > clickPos.y()) {
|
|
215 pos = i;
|
|
216 break;
|
|
217 }
|
|
218 }
|
|
219
|
|
220 // TODO: implement QPoligon(F).insert
|
|
221 auto tmpPoints = m_points.toList;
|
|
222 tmpPoints.insert(pos, clickPos);
|
|
223 m_points = new QPolygonF(tmpPoints);
|
|
224
|
|
225 m_locks.insert(pos, 0u);
|
|
226 m_currentIndex = pos;
|
|
227 firePointChange();
|
|
228 } else {
|
|
229 m_currentIndex = index;
|
|
230 }
|
|
231 return true;
|
|
232
|
|
233 } else if (me.button() == Qt.RightButton) {
|
|
234 if ((index >= 0) && m_editable) {
|
|
235 if (m_locks[index] == 0) {
|
|
236 m_locks.removeAt(index);
|
|
237 m_points.remove(index);
|
|
238 }
|
|
239 firePointChange();
|
|
240 return true;
|
|
241 }
|
|
242 }
|
|
243
|
|
244 }
|
|
245 break;
|
|
246
|
|
247 case QEvent.MouseButtonRelease:
|
|
248 m_currentIndex = -1;
|
|
249 break;
|
|
250
|
|
251 case QEvent.MouseMove:
|
|
252 if (m_currentIndex >= 0)
|
|
253 movePoint(m_currentIndex, QPointF((cast(QMouseEvent)event).pos));
|
|
254 break;
|
|
255
|
|
256 case QEvent.Resize:
|
|
257 {
|
|
258 QResizeEvent e = cast(QResizeEvent) event;
|
|
259 if (e.oldSize().width() == 0 || e.oldSize().height() == 0)
|
|
260 break;
|
|
261 qreal stretch_x = e.size().width() / cast(qreal)e.oldSize.width;
|
|
262 qreal stretch_y = e.size().height() / cast(qreal)e.oldSize.height;
|
|
263 for (int i=0; i<m_points.size(); ++i) {
|
|
264 QPointF p = m_points.at(i);
|
|
265 movePoint(i, QPointF(p.x() * stretch_x, p.y() * stretch_y), false);
|
|
266 }
|
|
267
|
|
268 firePointChange();
|
|
269 break;
|
|
270 }
|
|
271
|
|
272 case QEvent.Paint:
|
|
273 {
|
|
274 QWidget that_widget = m_widget;
|
|
275 m_widget = null;
|
|
276 QApplication.sendEvent(object, event);
|
|
277 m_widget = that_widget;
|
|
278 paintPoints();
|
|
279 version (QT_OPENGL_SUPPORT)
|
|
280 {
|
|
281 ArthurFrame af = cast(ArthurFrame)(that_widget);
|
|
282 if (af && af.usesOpenGL())
|
|
283 af.glWidget().swapBuffers();
|
|
284 }
|
|
285
|
|
286 return true;
|
|
287 }
|
|
288 default:
|
|
289 break;
|
|
290 }
|
|
291 }
|
|
292
|
|
293 return false;
|
|
294 }
|
|
295
|
|
296
|
|
297 void paintPoints()
|
|
298 {
|
|
299 scope p = new QPainter;
|
|
300 version (QT_OPENGL_SUPPORT)
|
|
301 {
|
|
302 ArthurFrame af = cast(ArthurFrame)(m_widget);
|
|
303 if (af && af.usesOpenGL())
|
|
304 p.begin(af.glWidget());
|
|
305 else
|
|
306 p.begin(m_widget);
|
|
307 }
|
|
308 else
|
|
309 p.begin(m_widget);
|
|
310
|
|
311 p.setRenderHint(QPainter.Antialiasing);
|
|
312
|
|
313 if (m_connectionPen.style() != Qt.NoPen && m_connectionType != ConnectionType.NoConnection) {
|
|
314 p.setPen(m_connectionPen);
|
|
315
|
|
316 if (m_connectionType == ConnectionType.CurveConnection) {
|
|
317 auto path = new QPainterPath;
|
|
318 path.moveTo(m_points.at(0));
|
|
319 for (int i=1; i<m_points.size(); ++i) {
|
|
320 QPointF p1 = m_points.at(i-1);
|
|
321 QPointF p2 = m_points.at(i);
|
|
322 qreal distance = p2.x() - p1.x();
|
|
323
|
|
324 path.cubicTo(p1.x() + distance / 2, p1.y(),
|
|
325 p1.x() + distance / 2, p2.y(),
|
|
326 p2.x(), p2.y());
|
|
327 }
|
|
328 p.drawPath(path);
|
|
329 } else {
|
|
330 p.drawPolyline(m_points);
|
|
331 }
|
|
332 }
|
|
333
|
|
334 p.setPen(m_pointPen);
|
|
335 p.setBrush(m_pointBrush);
|
|
336
|
|
337 for (int i=0; i<m_points.size(); ++i) {
|
|
338 QRectF bounds = pointBoundingRect(i);
|
|
339 if (m_shape == PointShape.CircleShape)
|
|
340 p.drawEllipse(bounds);
|
|
341 else
|
|
342 p.drawRect(bounds);
|
|
343 }
|
|
344 }
|
|
345
|
|
346
|
|
347 void setPoints(QPolygonF points)
|
|
348 {
|
|
349 delete m_points;
|
|
350 for (int i=0; i<points.size; ++i)
|
|
351 m_points.append(bound_point(points.at(i), boundingRect(), 0));
|
|
352
|
|
353 delete m_locks;
|
|
354 if (m_points.size > 0) {
|
|
355 m_locks.length = m_points.size;
|
|
356
|
|
357 m_locks[] = 0;
|
|
358 }
|
|
359 }
|
|
360
|
|
361 void movePoint(int index, QPointF point, bool emitUpdate = true)
|
|
362 {
|
|
363 m_points.replace(index, bound_point(point, boundingRect(), m_locks[index]));
|
|
364 if (emitUpdate)
|
|
365 firePointChange();
|
|
366 }
|
|
367
|
|
368 void firePointChange()
|
|
369 {
|
|
370 // printf("HoverPoints.firePointChange(), current=%d\n", m_currentIndex);
|
|
371
|
|
372 if (m_sortType != SortType.NoSort) {
|
|
373
|
|
374 QPointF oldCurrent;
|
|
375 if (m_currentIndex != -1) {
|
|
376 oldCurrent = m_points.at(m_currentIndex);
|
|
377 }
|
|
378
|
|
379 if (m_sortType == SortType.XSort)
|
|
380 {
|
|
381 auto tmpPoints = m_points.toList;
|
|
382 sort(tmpPoints, &x_less_than);
|
|
383 m_points = new QPolygonF(tmpPoints);
|
|
384 }
|
|
385 else if (m_sortType == SortType.YSort)
|
|
386 {
|
|
387 auto tmpPoints = m_points.toList;
|
|
388 sort(tmpPoints, &y_less_than);
|
|
389 m_points = new QPolygonF(tmpPoints);
|
|
390 }
|
|
391
|
|
392 // Compensate for changed order...
|
|
393 if (m_currentIndex != -1) {
|
|
394 for (int i=0; i<m_points.size; ++i) {
|
|
395 if (m_points.at(i) == oldCurrent) {
|
|
396 m_currentIndex = i;
|
|
397 break;
|
|
398 }
|
|
399 }
|
|
400 }
|
|
401
|
|
402 // printf(" - firePointChange(), current=%d\n", m_currentIndex);
|
|
403 }
|
|
404
|
|
405 // for (int i=0; i<m_points.size(); ++i) {
|
|
406 // printf(" - point(%2d)=[%.2f, %.2f], lock=%d\n",
|
|
407 // i, m_points.at(i).x(), m_points.at(i).y(), m_locks.at(i));
|
|
408 // }
|
|
409
|
|
410 pointsChanged.emit(m_points);
|
|
411 }
|
|
412 }
|
|
413
|
|
414 private QPointF bound_point(QPointF point, QRectF bounds, int lock)
|
|
415 {
|
|
416 QPointF p = point;
|
|
417
|
|
418 qreal left = bounds.left();
|
|
419 qreal right = bounds.right();
|
|
420 qreal top = bounds.top();
|
|
421 qreal bottom = bounds.bottom();
|
|
422
|
|
423 if (p.x() < left || (lock & HoverPoints.LockType.LockToLeft)) p.x = left;
|
|
424 else if (p.x() > right || (lock & HoverPoints.LockType.LockToRight)) p.x = right;
|
|
425
|
|
426 if (p.y() < top || (lock & HoverPoints.LockType.LockToTop)) p.y = top;
|
|
427 else if (p.y() > bottom || (lock & HoverPoints.LockType.LockToBottom)) p.y = bottom;
|
|
428
|
|
429 return p;
|
|
430 }
|
|
431
|
|
432 private bool x_less_than(QPointF p1, QPointF p2)
|
|
433 {
|
|
434 return p1.x() < p2.x();
|
|
435 }
|
|
436
|
|
437 private bool y_less_than(QPointF p1, QPointF p2)
|
|
438 {
|
|
439 return p1.y() < p2.y();
|
|
440 }
|