2 ******************************************************************************
4 * @file dialgadgetwidget.cpp
5 * @author The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
6 * @see The GNU Public License (GPL) Version 3
7 * @addtogroup GCSPlugins GCS Plugins
9 * @addtogroup DialPlugin Dial Plugin
11 * @brief Plots flight information rotary style dials
12 *****************************************************************************/
14 * This program is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU General Public License as published by
16 * the Free Software Foundation; either version 3 of the License, or
17 * (at your option) any later version.
19 * This program is distributed in the hope that it will be useful, but
20 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
21 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
24 * You should have received a copy of the GNU General Public License along
25 * with this program; if not, write to the Free Software Foundation, Inc.,
26 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 #include "dialgadgetwidget.h"
30 #include <utils/stylehelper.h>
32 #include <QtOpenGL/QGLWidget>
35 DialGadgetWidget::DialGadgetWidget(QWidget
*parent
) : QGraphicsView(parent
)
37 // TODO: create a proper "needle" object instead of hardcoding all this
38 // which is ugly (but easy).
40 setMinimumSize(64, 64);
41 setSizePolicy(QSizePolicy::MinimumExpanding
, QSizePolicy::MinimumExpanding
);
42 setScene(new QGraphicsScene(this));
43 setRenderHints(QPainter::Antialiasing
| QPainter::TextAntialiasing
);
45 m_renderer
= new QSvgRenderer();
52 m_text3
= NULL
; // Should be initialized to NULL otherwise the setFont method
53 // might segfault upon initialization if called before SetDialFile
62 // This timer mechanism makes needles rotate smoothly
63 connect(&dialTimer
, SIGNAL(timeout()), this, SLOT(rotateNeedles()));
66 DialGadgetWidget::~DialGadgetWidget()
72 \brief Enables/Disables OpenGL
74 void DialGadgetWidget::enableOpenGL(bool flag
)
77 setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers
)));
79 setViewport(new QWidget
);
84 \brief Connects the widget to the relevant UAVObjects
86 void DialGadgetWidget::connectNeedles(QString object1
, QString nfield1
,
87 QString object2
, QString nfield2
,
88 QString object3
, QString nfield3
)
91 disconnect(obj1
, SIGNAL(objectUpdated(UAVObject
*)), this, SLOT(updateNeedle1(UAVObject
*)));
94 disconnect(obj2
, SIGNAL(objectUpdated(UAVObject
*)), this, SLOT(updateNeedle2(UAVObject
*)));
97 disconnect(obj3
, SIGNAL(objectUpdated(UAVObject
*)), this, SLOT(updateNeedle3(UAVObject
*)));
100 ExtensionSystem::PluginManager
*pm
= ExtensionSystem::PluginManager::instance();
101 UAVObjectManager
*objManager
= pm
->getObject
<UAVObjectManager
>();
103 // Check validity of arguments first, reject empty args and unknown fields.
104 if (!(object1
.isEmpty() || nfield1
.isEmpty())) {
105 obj1
= dynamic_cast<UAVDataObject
*>(objManager
->getObject(object1
));
107 // qDebug() << "Connected Object 1 (" << object1 << ").";
108 connect(obj1
, SIGNAL(objectUpdated(UAVObject
*)), this, SLOT(updateNeedle1(UAVObject
*)));
109 if (nfield1
.contains("-")) {
110 QStringList fieldSubfield
= nfield1
.split("-", QString::SkipEmptyParts
);
111 field1
= fieldSubfield
.at(0);
112 subfield1
= fieldSubfield
.at(1);
113 haveSubField1
= true;
116 haveSubField1
= false;
119 qDebug() << "Error: Object is unknown (" << object1
<< ").";
123 // And do the same for the second needle.
124 if (!(object2
.isEmpty() || nfield2
.isEmpty())) {
125 obj2
= dynamic_cast<UAVDataObject
*>(objManager
->getObject(object2
));
127 // qDebug() << "Connected Object 2 (" << object2 << ").";
128 connect(obj2
, SIGNAL(objectUpdated(UAVObject
*)), this, SLOT(updateNeedle2(UAVObject
*)));
129 if (nfield2
.contains("-")) {
130 QStringList fieldSubfield
= nfield2
.split("-", QString::SkipEmptyParts
);
131 field2
= fieldSubfield
.at(0);
132 subfield2
= fieldSubfield
.at(1);
133 haveSubField2
= true;
136 haveSubField2
= false;
139 qDebug() << "Error: Object is unknown (" << object2
<< ").";
143 // And do the same for the third needle.
144 if (!(object3
.isEmpty() || nfield3
.isEmpty())) {
145 obj3
= dynamic_cast<UAVDataObject
*>(objManager
->getObject(object3
));
147 // qDebug() << "Connected Object 3 (" << object3 << ").";
148 connect(obj3
, SIGNAL(objectUpdated(UAVObject
*)), this, SLOT(updateNeedle3(UAVObject
*)));
149 if (nfield3
.contains("-")) {
150 QStringList fieldSubfield
= nfield3
.split("-", QString::SkipEmptyParts
);
151 field3
= fieldSubfield
.at(0);
152 subfield3
= fieldSubfield
.at(1);
153 haveSubField3
= true;
156 haveSubField3
= false;
159 qDebug() << "Error: Object is unknown (" << object3
<< ").";
165 \brief Called by the UAVObject which got updated
167 void DialGadgetWidget::updateNeedle1(UAVObject
*object1
)
169 // Double check that the field exists:
171 UAVObjectField
*field
= object1
->getField(field1
);
175 int indexOfSubField
= field
->getElementNames().indexOf(QRegExp(subfield1
, Qt::CaseSensitive
, QRegExp::FixedString
));
176 value
= field
->getDouble(indexOfSubField
);
178 value
= field
->getDouble();
180 if (value
!= value
) {
181 qDebug() << "Dial widget: encountered NaN !!";
186 qDebug() << "Wrong field, maybe an issue with object disconnection ?";
191 \brief Called by the UAVObject which got updated
193 void DialGadgetWidget::updateNeedle2(UAVObject
*object2
)
196 UAVObjectField
*field
= object2
->getField(field2
);
200 int indexOfSubField
= field
->getElementNames().indexOf(QRegExp(subfield2
, Qt::CaseSensitive
, QRegExp::FixedString
));
201 value
= field
->getDouble(indexOfSubField
);
203 value
= field
->getDouble();
205 if (value
!= value
) {
206 qDebug() << "Dial widget: encountered NaN !!";
211 qDebug() << "Wrong field, maybe an issue with object disconnection ?";
216 \brief Called by the UAVObject which got updated
218 void DialGadgetWidget::updateNeedle3(UAVObject
*object3
)
221 UAVObjectField
*field
= object3
->getField(field3
);
225 int indexOfSubField
= field
->getElementNames().indexOf(QRegExp(subfield3
, Qt::CaseSensitive
, QRegExp::FixedString
));
226 value
= field
->getDouble(indexOfSubField
);
228 value
= field
->getDouble();
230 if (value
!= value
) {
231 qDebug() << "Dial widget: encountered NaN !!";
236 qDebug() << "Wrong field, maybe an issue with object disconnection ?";
241 Initializes the dial file, and does all the one-time calculations for
242 display later. This is the method which really initializes the dial.
244 void DialGadgetWidget::setDialFile(QString dfn
, QString bg
, QString fg
, QString n1
, QString n2
,
245 QString n3
, QString n1Move
, QString n2Move
, QString n3Move
)
250 QGraphicsScene
*l_scene
= scene();
251 setBackgroundBrush(QBrush(Utils::StyleHelper::baseColor()));
252 if (QFile::exists(dfn
) && m_renderer
->load(dfn
) && m_renderer
->isValid()) {
253 l_scene
->clear(); // This also deletes all items contained in the scene.
254 m_background
= new QGraphicsSvgItem();
255 // All other items will be clipped to the shape of the background
256 m_background
->setFlags(QGraphicsItem::ItemClipsChildrenToShape
|
257 QGraphicsItem::ItemClipsToShape
);
258 m_foreground
= new QGraphicsSvgItem();
259 m_needle1
= new QGraphicsSvgItem();
260 m_needle2
= new QGraphicsSvgItem();
261 m_needle3
= new QGraphicsSvgItem();
262 m_needle1
->setParentItem(m_background
);
263 m_needle2
->setParentItem(m_background
);
264 m_needle3
->setParentItem(m_background
);
265 m_foreground
->setParentItem(m_background
);
267 // We assume the dial contains at least the background
269 m_background
->setSharedRenderer(m_renderer
);
270 m_background
->setElementId(bg
);
271 l_scene
->addItem(m_background
);
273 m_needle1
->setSharedRenderer(m_renderer
);
274 m_needle1
->setElementId(n1
);
275 // Note: no need to add the item explicitely because it
276 // is done automatically since it's a child item of the
278 // l_scene->addItem(m_needle1);
280 // The dial gadget allows Needle1 and Needle2 to be
281 // the same element, for combined movement. Needle3
282 // is always independent.
284 m_needle2
= m_needle1
;
287 if (m_renderer
->elementExists(n2
)) {
288 m_needle2
->setSharedRenderer(m_renderer
);
289 m_needle2
->setElementId(n2
);
290 // l_scene->addItem(m_needle2);
295 if (m_renderer
->elementExists(n3
)) {
296 m_needle3
->setSharedRenderer(m_renderer
);
297 m_needle3
->setElementId(n3
);
298 // l_scene->addItem(m_needle3);
302 if (m_renderer
->elementExists(fg
)) {
303 m_foreground
->setSharedRenderer(m_renderer
);
304 m_foreground
->setElementId(fg
);
305 // Center it on the scene
306 QRectF rectB
= m_background
->boundingRect();
307 QRectF rectF
= m_foreground
->boundingRect();
308 m_foreground
->setPos(rectB
.width() / 2 - rectF
.width() / 2, rectB
.height() / 2 - rectF
.height() / 2);
309 // l_scene->addItem(m_foreground);
323 // Now setup the rotation/translation settings:
324 // this is UGLY UGLY UGLY, sorry...
325 if (n1Move
.contains("Rotate")) {
327 } else if (n1Move
.contains("Horizontal")) {
329 } else if (n1Move
.contains("Vertical")) {
333 if (n2Move
.contains("Rotate")) {
335 } else if (n2Move
.contains("Horizontal")) {
337 } else if (n2Move
.contains("Vertical")) {
341 if (n3Move
.contains("Rotate")) {
343 } else if (n3Move
.contains("Horizontal")) {
345 } else if (n3Move
.contains("Vertical")) {
349 l_scene
->setSceneRect(m_background
->boundingRect());
351 // Now Initialize the center for all transforms of the dial needles to the
352 // center of the background:
353 // - Move the center of the needle to the center of the background.
354 QRectF rectB
= m_background
->boundingRect();
355 QRectF rectN
= m_needle1
->boundingRect();
356 m_needle1
->setPos(rectB
.width() / 2 - rectN
.width() / 2, rectB
.height() / 2 - rectN
.height() / 2);
357 // - Put the transform origin point of the needle at its center.
358 m_needle1
->setTransformOriginPoint(rectN
.width() / 2, rectN
.height() / 2);
360 // Check whether the dial also wants display the numeric value:
361 if (m_renderer
->elementExists(n1
+ "-text")) {
362 QMatrix textMatrix
= m_renderer
->matrixForElement(n1
+ "-text");
363 qreal startX
= textMatrix
.mapRect(m_renderer
->boundsOnElement(n1
+ "-text")).x();
364 qreal startY
= textMatrix
.mapRect(m_renderer
->boundsOnElement(n1
+ "-text")).y();
366 matrix
.translate(startX
, startY
);
367 m_text1
= new QGraphicsTextItem("0.00");
368 m_text1
->setDefaultTextColor(QColor("White"));
369 m_text1
->setTransform(matrix
, false);
370 l_scene
->addItem(m_text1
);
376 if ((n1
!= n2
) && n2enabled
) {
377 // Only do it for needle2 if it is not the same as n1
378 rectN
= m_needle2
->boundingRect();
379 m_needle2
->setPos(rectB
.width() / 2 - rectN
.width() / 2, rectB
.height() / 2 - rectN
.height() / 2);
380 m_needle2
->setTransformOriginPoint(rectN
.width() / 2, rectN
.height() / 2);
381 // Check whether the dial also wants display the numeric value:
382 if (m_renderer
->elementExists(n2
+ "-text")) {
383 QMatrix textMatrix
= m_renderer
->matrixForElement(n2
+ "-text");
384 qreal startX
= textMatrix
.mapRect(m_renderer
->boundsOnElement(n2
+ "-text")).x();
385 qreal startY
= textMatrix
.mapRect(m_renderer
->boundsOnElement(n2
+ "-text")).y();
387 matrix
.translate(startX
, startY
);
388 m_text2
= new QGraphicsTextItem("0.00");
389 m_text2
->setDefaultTextColor(QColor("White"));
390 m_text2
->setTransform(matrix
, false);
391 l_scene
->addItem(m_text2
);
397 rectN
= m_needle3
->boundingRect();
398 m_needle3
->setPos(rectB
.width() / 2 - rectN
.width() / 2, rectB
.height() / 2 - rectN
.height() / 2);
399 m_needle3
->setTransformOriginPoint(rectN
.width() / 2, rectN
.height() / 2);
400 // Check whether the dial also wants display the numeric value:
401 if (m_renderer
->elementExists(n3
+ "-text")) {
402 QMatrix textMatrix
= m_renderer
->matrixForElement(n3
+ "-text");
403 qreal startX
= textMatrix
.mapRect(m_renderer
->boundsOnElement(n3
+ "-text")).x();
404 qreal startY
= textMatrix
.mapRect(m_renderer
->boundsOnElement(n3
+ "-text")).y();
406 matrix
.translate(startX
, startY
);
407 m_text3
= new QGraphicsTextItem("0.00");
408 m_text3
->setDefaultTextColor(QColor("White"));
409 m_text3
->setTransform(matrix
, false);
410 l_scene
->addItem(m_text3
);
416 // Last: we just loaded the dial file which is by default positioned on a "zero" value
417 // of the needles, so we have to reset the needle values too upon dial file loading, otherwise
418 // we would end up with an offset whenever we change a dial file and the needle value
419 // is not zero at that time.
423 if (!dialTimer
.isActive()) {
428 qDebug() << "no file: display default background.";
429 m_renderer
->load(QString(":/dial/images/empty.svg"));
430 l_scene
->clear(); // This also deletes all items contained in the scene.
431 m_background
= new QGraphicsSvgItem();
432 m_background
->setSharedRenderer(m_renderer
);
433 l_scene
->addItem(m_background
);
444 void DialGadgetWidget::paint()
449 void DialGadgetWidget::paintEvent(QPaintEvent
*event
)
451 // Skip painting until the dial file is loaded
452 if (!m_renderer
->isValid()) {
453 qDebug() << "Dial file not loaded, not rendering";
456 QGraphicsView::paintEvent(event
);
459 // This event enables the dial to be dynamically resized
460 // whenever the gadget is resized, taking advantage of the vector
461 // nature of SVG dials.
462 void DialGadgetWidget::resizeEvent(QResizeEvent
*event
)
465 fitInView(m_background
, Qt::KeepAspectRatio
);
468 void DialGadgetWidget::setDialFont(QString fontProps
)
470 QFont font
= QFont("Arial", 12);
472 font
.fromString(fontProps
);
474 m_text1
->setFont(font
);
479 // Converts the value into an angle:
480 // this enables smooth rotation in rotateNeedles below
481 void DialGadgetWidget::setNeedle1(double value
)
484 needle1Target
= 360 * (value
* n1Factor
) / (n1MaxValue
- n1MinValue
);
487 needle1Target
= (value
* n1Factor
) / (n1MaxValue
- n1MinValue
);
490 needle1Target
= (value
* n1Factor
) / (n1MaxValue
- n1MinValue
);
492 if (!dialTimer
.isActive()) {
497 s
.sprintf("%.2f", value
* n1Factor
);
498 m_text1
->setPlainText(s
);
502 void DialGadgetWidget::setNeedle2(double value
)
505 needle2Target
= 360 * (value
* n2Factor
) / (n2MaxValue
- n2MinValue
);
508 needle2Target
= (value
* n2Factor
) / (n2MaxValue
- n2MinValue
);
511 needle2Target
= (value
* n2Factor
) / (n2MaxValue
- n2MinValue
);
513 if (!dialTimer
.isActive()) {
518 s
.sprintf("%.2f", value
* n2Factor
);
519 m_text2
->setPlainText(s
);
523 void DialGadgetWidget::setNeedle3(double value
)
526 needle3Target
= 360 * (value
* n3Factor
) / (n3MaxValue
- n3MinValue
);
529 needle3Target
= (value
* n3Factor
) / (n3MaxValue
- n3MinValue
);
532 needle3Target
= (value
* n3Factor
) / (n3MaxValue
- n3MinValue
);
534 if (!dialTimer
.isActive()) {
539 s
.sprintf("%.2f", value
* n3Factor
);
540 m_text3
->setPlainText(s
);
544 // Take an input value and rotate the dial accordingly
545 // Rotation is smooth, starts fast and slows down when
546 // approaching the target.
547 // We aim for a 0.5 degree precision.
549 // Note: this code is valid even if needle1 and needle2 point
550 // to the same element.
551 void DialGadgetWidget::rotateNeedles()
554 // We get there in case the dial file is missing or corrupt.
561 if (abs((needle2Value
- needle2Target
) * 10) > 5 && beSmooth
) {
562 needle2Diff
= (needle2Target
- needle2Value
) / 5;
564 needle2Diff
= needle2Target
- needle2Value
;
568 m_needle2
->setRotation(m_needle2
->rotation() + needle2Diff
);
570 QPointF opd
= QPointF(0, 0);
572 opd
= QPointF(needle2Diff
, 0);
575 opd
= QPointF(0, needle2Diff
);
577 m_needle2
->setTransform(QTransform::fromTranslate(opd
.x(), opd
.y()), true);
578 // Since we have moved the needle, we need to move
579 // the transform origin point the opposite way
580 // so that it keeps rotating from the same point.
581 // (this is only useful if needle1 and needle2 are the
582 // same object, for combined movement such as attitude indicator).
583 QPointF oop
= m_needle2
->transformOriginPoint();
584 m_needle2
->setTransformOriginPoint(oop
.x() - opd
.x(), oop
.y() - opd
.y());
586 needle2Value
+= needle2Diff
;
591 // We assume that needle1 always exists!
593 if ((abs((needle1Value
- needle1Target
) * 10) > 5) && beSmooth
) {
594 needle1Diff
= (needle1Target
- needle1Value
) / 5;
596 needle1Diff
= needle1Target
- needle1Value
;
600 m_needle1
->setRotation(m_needle1
->rotation() + needle1Diff
);
602 QPointF opd
= QPointF(0, 0);
604 opd
= QPointF(needle1Diff
, 0);
607 opd
= QPointF(0, needle1Diff
);
609 m_needle1
->setTransform(QTransform::fromTranslate(opd
.x(), opd
.y()), true);
610 QPointF oop
= m_needle1
->transformOriginPoint();
611 m_needle1
->setTransformOriginPoint((oop
.x() - opd
.x()), (oop
.y() - opd
.y()));
613 needle1Value
+= needle1Diff
;
617 if ((abs((needle3Value
- needle3Target
) * 10) > 5) && beSmooth
) {
618 needle3Diff
= (needle3Target
- needle3Value
) / 5;
620 needle3Diff
= needle3Target
- needle3Value
;
624 m_needle3
->setRotation(m_needle3
->rotation() + needle3Diff
);
626 QPointF opd
= QPointF(0, 0);
628 opd
= QPointF(needle3Diff
, 0);
631 opd
= QPointF(0, needle3Diff
);
633 m_needle3
->setTransform(QTransform::fromTranslate(opd
.x(), opd
.y()), true);
634 QPointF oop
= m_needle3
->transformOriginPoint();
635 m_needle3
->setTransformOriginPoint((oop
.x() - opd
.x()), (oop
.y() - opd
.y()));
637 needle3Value
+= needle3Diff
;
642 // Now check: if dialRun is now zero, we should
643 // just stop the timer since all needles have finished moving