OP-1900 have path_progress updated correctly for leg_remaining and error_below end...
[librepilot.git] / ground / openpilotgcs / src / plugins / dial / dialgadgetwidget.cpp
blobfd31187f3a45aaebe9e0886e932c7a38480a6a5a
1 /**
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
8 * @{
9 * @addtogroup DialPlugin Dial Plugin
10 * @{
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
22 * for more details.
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>
31 #include <iostream>
32 #include <QtOpenGL/QGLWidget>
33 #include <QDebug>
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();
47 obj1 = NULL;
48 obj2 = NULL;
49 obj3 = NULL;
50 m_text1 = NULL;
51 m_text2 = NULL;
52 m_text3 = NULL; // Should be initialized to NULL otherwise the setFont method
53 // might segfault upon initialization if called before SetDialFile
55 needle1Target = 0;
56 needle2Target = 0;
57 needle3Target = 0;
59 // beSmooth = true;
60 beSmooth = false;
62 // This timer mechanism makes needles rotate smoothly
63 connect(&dialTimer, SIGNAL(timeout()), this, SLOT(rotateNeedles()));
66 DialGadgetWidget::~DialGadgetWidget()
68 // Do nothing
71 /*!
72 \brief Enables/Disables OpenGL
74 void DialGadgetWidget::enableOpenGL(bool flag)
76 if (flag) {
77 setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers)));
78 } else {
79 setViewport(new QWidget);
83 /*!
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)
90 if (obj1 != NULL) {
91 disconnect(obj1, SIGNAL(objectUpdated(UAVObject *)), this, SLOT(updateNeedle1(UAVObject *)));
93 if (obj2 != NULL) {
94 disconnect(obj2, SIGNAL(objectUpdated(UAVObject *)), this, SLOT(updateNeedle2(UAVObject *)));
96 if (obj3 != NULL) {
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));
106 if (obj1 != NULL) {
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;
114 } else {
115 field1 = nfield1;
116 haveSubField1 = false;
118 } else {
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));
126 if (obj2 != NULL) {
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;
134 } else {
135 field2 = nfield2;
136 haveSubField2 = false;
138 } else {
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));
146 if (obj3 != NULL) {
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;
154 } else {
155 field3 = nfield3;
156 haveSubField3 = false;
158 } else {
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:
170 double value;
171 UAVObjectField *field = object1->getField(field1);
173 if (field) {
174 if (haveSubField1) {
175 int indexOfSubField = field->getElementNames().indexOf(QRegExp(subfield1, Qt::CaseSensitive, QRegExp::FixedString));
176 value = field->getDouble(indexOfSubField);
177 } else {
178 value = field->getDouble();
180 if (value != value) {
181 qDebug() << "Dial widget: encountered NaN !!";
182 return;
184 setNeedle1(value);
185 } else {
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)
195 double value;
196 UAVObjectField *field = object2->getField(field2);
198 if (field) {
199 if (haveSubField2) {
200 int indexOfSubField = field->getElementNames().indexOf(QRegExp(subfield2, Qt::CaseSensitive, QRegExp::FixedString));
201 value = field->getDouble(indexOfSubField);
202 } else {
203 value = field->getDouble();
205 if (value != value) {
206 qDebug() << "Dial widget: encountered NaN !!";
207 return;
209 setNeedle2(value);
210 } else {
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)
220 double value;
221 UAVObjectField *field = object3->getField(field3);
223 if (field) {
224 if (haveSubField3) {
225 int indexOfSubField = field->getElementNames().indexOf(QRegExp(subfield3, Qt::CaseSensitive, QRegExp::FixedString));
226 value = field->getDouble(indexOfSubField);
227 } else {
228 value = field->getDouble();
230 if (value != value) {
231 qDebug() << "Dial widget: encountered NaN !!";
232 return;
234 setNeedle3(value);
235 } else {
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)
247 fgenabled = false;
248 n2enabled = false;
249 n3enabled = false;
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
268 // and needle1
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
277 // background.
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.
283 if (n1 == n2) {
284 m_needle2 = m_needle1;
285 n2enabled = true;
286 } else {
287 if (m_renderer->elementExists(n2)) {
288 m_needle2->setSharedRenderer(m_renderer);
289 m_needle2->setElementId(n2);
290 // l_scene->addItem(m_needle2);
291 n2enabled = true;
295 if (m_renderer->elementExists(n3)) {
296 m_needle3->setSharedRenderer(m_renderer);
297 m_needle3->setElementId(n3);
298 // l_scene->addItem(m_needle3);
299 n3enabled = true;
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);
310 fgenabled = true;
313 rotateN1 = false;
314 horizN1 = false;
315 vertN1 = false;
316 rotateN2 = false;
317 horizN2 = false;
318 vertN2 = false;
319 rotateN3 = false;
320 horizN3 = false;
321 vertN3 = false;
323 // Now setup the rotation/translation settings:
324 // this is UGLY UGLY UGLY, sorry...
325 if (n1Move.contains("Rotate")) {
326 rotateN1 = true;
327 } else if (n1Move.contains("Horizontal")) {
328 horizN1 = true;
329 } else if (n1Move.contains("Vertical")) {
330 vertN1 = true;
333 if (n2Move.contains("Rotate")) {
334 rotateN2 = true;
335 } else if (n2Move.contains("Horizontal")) {
336 horizN2 = true;
337 } else if (n2Move.contains("Vertical")) {
338 vertN2 = true;
341 if (n3Move.contains("Rotate")) {
342 rotateN3 = true;
343 } else if (n3Move.contains("Horizontal")) {
344 horizN3 = true;
345 } else if (n3Move.contains("Vertical")) {
346 vertN3 = true;
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();
365 QTransform matrix;
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);
371 } else {
372 m_text1 = NULL;
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();
386 QTransform matrix;
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);
392 } else {
393 m_text2 = NULL;
396 if (n3enabled) {
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();
405 QTransform matrix;
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);
411 } else {
412 m_text3 = NULL;
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.
420 needle1Value = 0;
421 needle2Value = 0;
422 needle3Value = 0;
423 if (!dialTimer.isActive()) {
424 dialTimer.start();
426 dialError = false;
427 } else {
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);
434 m_text1 = NULL;
435 m_text2 = NULL;
436 m_text3 = NULL;
437 m_needle1 = NULL;
438 m_needle2 = NULL;
439 m_needle3 = NULL;
440 dialError = true;
444 void DialGadgetWidget::paint()
446 update();
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";
454 return;
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)
464 Q_UNUSED(event);
465 fitInView(m_background, Qt::KeepAspectRatio);
468 void DialGadgetWidget::setDialFont(QString fontProps)
470 QFont font = QFont("Arial", 12);
472 font.fromString(fontProps);
473 if (m_text1) {
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)
483 if (rotateN1) {
484 needle1Target = 360 * (value * n1Factor) / (n1MaxValue - n1MinValue);
486 if (horizN1) {
487 needle1Target = (value * n1Factor) / (n1MaxValue - n1MinValue);
489 if (vertN1) {
490 needle1Target = (value * n1Factor) / (n1MaxValue - n1MinValue);
492 if (!dialTimer.isActive()) {
493 dialTimer.start();
495 if (m_text1) {
496 QString s;
497 s.sprintf("%.2f", value * n1Factor);
498 m_text1->setPlainText(s);
502 void DialGadgetWidget::setNeedle2(double value)
504 if (rotateN2) {
505 needle2Target = 360 * (value * n2Factor) / (n2MaxValue - n2MinValue);
507 if (horizN2) {
508 needle2Target = (value * n2Factor) / (n2MaxValue - n2MinValue);
510 if (vertN2) {
511 needle2Target = (value * n2Factor) / (n2MaxValue - n2MinValue);
513 if (!dialTimer.isActive()) {
514 dialTimer.start();
516 if (m_text2) {
517 QString s;
518 s.sprintf("%.2f", value * n2Factor);
519 m_text2->setPlainText(s);
523 void DialGadgetWidget::setNeedle3(double value)
525 if (rotateN3) {
526 needle3Target = 360 * (value * n3Factor) / (n3MaxValue - n3MinValue);
528 if (horizN3) {
529 needle3Target = (value * n3Factor) / (n3MaxValue - n3MinValue);
531 if (vertN3) {
532 needle3Target = (value * n3Factor) / (n3MaxValue - n3MinValue);
534 if (!dialTimer.isActive()) {
535 dialTimer.start();
537 if (m_text3) {
538 QString s;
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()
553 if (dialError) {
554 // We get there in case the dial file is missing or corrupt.
555 dialTimer.stop();
556 return;
558 int dialRun = 3;
559 if (n2enabled) {
560 double needle2Diff;
561 if (abs((needle2Value - needle2Target) * 10) > 5 && beSmooth) {
562 needle2Diff = (needle2Target - needle2Value) / 5;
563 } else {
564 needle2Diff = needle2Target - needle2Value;
565 dialRun--;
567 if (rotateN2) {
568 m_needle2->setRotation(m_needle2->rotation() + needle2Diff);
569 } else {
570 QPointF opd = QPointF(0, 0);
571 if (horizN2) {
572 opd = QPointF(needle2Diff, 0);
574 if (vertN2) {
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;
587 } else {
588 dialRun--;
591 // We assume that needle1 always exists!
592 double needle1Diff;
593 if ((abs((needle1Value - needle1Target) * 10) > 5) && beSmooth) {
594 needle1Diff = (needle1Target - needle1Value) / 5;
595 } else {
596 needle1Diff = needle1Target - needle1Value;
597 dialRun--;
599 if (rotateN1) {
600 m_needle1->setRotation(m_needle1->rotation() + needle1Diff);
601 } else {
602 QPointF opd = QPointF(0, 0);
603 if (horizN1) {
604 opd = QPointF(needle1Diff, 0);
606 if (vertN1) {
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;
615 if (n3enabled) {
616 double needle3Diff;
617 if ((abs((needle3Value - needle3Target) * 10) > 5) && beSmooth) {
618 needle3Diff = (needle3Target - needle3Value) / 5;
619 } else {
620 needle3Diff = needle3Target - needle3Value;
621 dialRun--;
623 if (rotateN3) {
624 m_needle3->setRotation(m_needle3->rotation() + needle3Diff);
625 } else {
626 QPointF opd = QPointF(0, 0);
627 if (horizN3) {
628 opd = QPointF(needle3Diff, 0);
630 if (vertN3) {
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;
638 } else {
639 dialRun--;
642 // Now check: if dialRun is now zero, we should
643 // just stop the timer since all needles have finished moving
644 if (!dialRun) {
645 dialTimer.stop();