dRonin  adbada4
dRonin GCS
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Friends Groups Pages
dialgadgetwidget.cpp
Go to the documentation of this file.
1 
14 /*
15  * This program is free software; you can redistribute it and/or modify
16  * it under the terms of the GNU General Public License as published by
17  * the Free Software Foundation; either version 3 of the License, or
18  * (at your option) any later version.
19  *
20  * This program is distributed in the hope that it will be useful, but
21  * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
22  * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
23  * for more details.
24  *
25  * You should have received a copy of the GNU General Public License along
26  * with this program; if not, see <http://www.gnu.org/licenses/>
27  */
28 
29 #include <math.h>
30 
31 #include "dialgadgetwidget.h"
32 #include <iostream>
33 #include <QDebug>
34 
36  : QGraphicsView(parent)
37 {
38  // TODO: create a proper "needle" object instead of hardcoding all this
39  // which is ugly (but easy).
40 
41  setMinimumSize(64, 64);
42  setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
43  setScene(new QGraphicsScene(this));
44  setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
45 
46  m_renderer = new QSvgRenderer();
47 
48  obj1 = NULL;
49  obj2 = NULL;
50  obj3 = NULL;
51  m_text1 = NULL;
52  m_text2 = NULL;
53  m_text3 = NULL; // Should be initialized to NULL otherwise the setFont method
54  // might segfault upon initialization if called before SetDialFile
55 
56  needle1Target = 0;
57  needle2Target = 0;
58  needle3Target = 0;
59 
60  // beSmooth = true;
61  beSmooth = false;
62 
63  // This timer mechanism makes needles rotate smoothly
64  connect(&dialTimer, &QTimer::timeout, this, &DialGadgetWidget::rotateNeedles);
65 }
66 
68 {
69  // Do nothing
70 }
71 
75 void DialGadgetWidget::connectNeedles(QString object1, QString nfield1, QString object2,
76  QString nfield2, QString object3, QString nfield3)
77 {
78  if (obj1 != NULL)
80  if (obj2 != NULL)
82  if (obj3 != NULL)
84 
85  ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
86  UAVObjectManager *objManager = pm->getObject<UAVObjectManager>();
87 
88  // Check validity of arguments first, reject empty args and unknown fields.
89  if (!(object1.isEmpty() || nfield1.isEmpty())) {
90  obj1 = dynamic_cast<UAVDataObject *>(objManager->getObject(object1));
91  if (obj1 != NULL) {
92  // qDebug() << "Connected Object 1 (" << object1 << ").";
94  if (nfield1.contains("-")) {
95  QStringList fieldSubfield = nfield1.split("-", QString::SkipEmptyParts);
96  field1 = fieldSubfield.at(0);
97  subfield1 = fieldSubfield.at(1);
98  haveSubField1 = true;
99  } else {
100  field1 = nfield1;
101  haveSubField1 = false;
102  }
103  } else {
104  qDebug() << "Error: Object is unknown (" << object1 << ").";
105  }
106  }
107 
108  // And do the same for the second needle.
109  if (!(object2.isEmpty() || nfield2.isEmpty())) {
110  obj2 = dynamic_cast<UAVDataObject *>(objManager->getObject(object2));
111  if (obj2 != NULL) {
112  // qDebug() << "Connected Object 2 (" << object2 << ").";
114  if (nfield2.contains("-")) {
115  QStringList fieldSubfield = nfield2.split("-", QString::SkipEmptyParts);
116  field2 = fieldSubfield.at(0);
117  subfield2 = fieldSubfield.at(1);
118  haveSubField2 = true;
119  } else {
120  field2 = nfield2;
121  haveSubField2 = false;
122  }
123  } else {
124  qDebug() << "Error: Object is unknown (" << object2 << ").";
125  }
126  }
127 
128  // And do the same for the third needle.
129  if (!(object3.isEmpty() || nfield3.isEmpty())) {
130  obj3 = dynamic_cast<UAVDataObject *>(objManager->getObject(object3));
131  if (obj3 != NULL) {
132  // qDebug() << "Connected Object 3 (" << object3 << ").";
134  if (nfield3.contains("-")) {
135  QStringList fieldSubfield = nfield3.split("-", QString::SkipEmptyParts);
136  field3 = fieldSubfield.at(0);
137  subfield3 = fieldSubfield.at(1);
138  haveSubField3 = true;
139  } else {
140  field3 = nfield3;
141  haveSubField3 = false;
142  }
143  } else {
144  qDebug() << "Error: Object is unknown (" << object3 << ").";
145  }
146  }
147 }
148 
153 {
154  // Double check that the field exists:
155  double value;
156  UAVObjectField *field = object1->getField(field1);
157  if (field) {
158  if (haveSubField1) {
159  int indexOfSubField = field->getElementNames().indexOf(
160  QRegExp(subfield1, Qt::CaseSensitive, QRegExp::FixedString));
161  value = field->getDouble(indexOfSubField);
162  } else
163  value = field->getDouble();
164  if (value != value) {
165  qDebug() << "Dial widget: encountered NaN !!";
166  return;
167  }
168  setNeedle1(value);
169  } else {
170  qDebug() << "Wrong field, maybe an issue with object disconnection ?";
171  }
172 }
173 
178 {
179  double value;
180  UAVObjectField *field = object2->getField(field2);
181  if (field) {
182  if (haveSubField2) {
183  int indexOfSubField = field->getElementNames().indexOf(
184  QRegExp(subfield2, Qt::CaseSensitive, QRegExp::FixedString));
185  value = field->getDouble(indexOfSubField);
186  } else
187  value = field->getDouble();
188  if (value != value) {
189  qDebug() << "Dial widget: encountered NaN !!";
190  return;
191  }
192  setNeedle2(value);
193  } else {
194  qDebug() << "Wrong field, maybe an issue with object disconnection ?";
195  }
196 }
197 
202 {
203  double value;
204  UAVObjectField *field = object3->getField(field3);
205  if (field) {
206  if (haveSubField3) {
207  int indexOfSubField = field->getElementNames().indexOf(
208  QRegExp(subfield3, Qt::CaseSensitive, QRegExp::FixedString));
209  value = field->getDouble(indexOfSubField);
210  } else
211  value = field->getDouble();
212  if (value != value) {
213  qDebug() << "Dial widget: encountered NaN !!";
214  return;
215  }
216  setNeedle3(value);
217  } else {
218  qDebug() << "Wrong field, maybe an issue with object disconnection ?";
219  }
220 }
221 
222 /*
223  Initializes the dial file, and does all the one-time calculations for
224  display later. This is the method which really initializes the dial.
225  */
226 void DialGadgetWidget::setDialFile(QString dfn, QString bg, QString fg, QString n1, QString n2,
227  QString n3, QString n1Move, QString n2Move, QString n3Move)
228 {
229  fgenabled = false;
230  n2enabled = false;
231  n3enabled = false;
232  QGraphicsScene *l_scene = scene();
233 
234  if (QFile::exists(dfn) && m_renderer->load(dfn) && m_renderer->isValid()) {
235  l_scene->clear(); // This also deletes all items contained in the scene.
236  m_background = new QGraphicsSvgItem();
237  // All other items will be clipped to the shape of the background
238  m_background->setFlags(QGraphicsItem::ItemClipsChildrenToShape
239  | QGraphicsItem::ItemClipsToShape);
240  m_foreground = new QGraphicsSvgItem();
241  m_needle1 = new QGraphicsSvgItem();
242  m_needle2 = new QGraphicsSvgItem();
243  m_needle3 = new QGraphicsSvgItem();
244  m_needle1->setParentItem(m_background);
245  m_needle2->setParentItem(m_background);
246  m_needle3->setParentItem(m_background);
247  m_foreground->setParentItem(m_background);
248 
249  // We assume the dial contains at least the background
250  // and needle1
251  m_background->setSharedRenderer(m_renderer);
252  m_background->setElementId(bg);
253  l_scene->addItem(m_background);
254 
255  m_needle1->setSharedRenderer(m_renderer);
256  m_needle1->setElementId(n1);
257  // Note: no need to add the item explicitely because it
258  // is done automatically since it's a child item of the
259  // background.
260  // l_scene->addItem(m_needle1);
261 
262  // The dial gadget allows Needle1 and Needle2 to be
263  // the same element, for combined movement. Needle3
264  // is always independent.
265  if (n1 == n2) {
266  m_needle2 = m_needle1;
267  n2enabled = true;
268  } else {
269  if (m_renderer->elementExists(n2)) {
270  m_needle2->setSharedRenderer(m_renderer);
271  m_needle2->setElementId(n2);
272  // l_scene->addItem(m_needle2);
273  n2enabled = true;
274  }
275  }
276 
277  if (m_renderer->elementExists(n3)) {
278  m_needle3->setSharedRenderer(m_renderer);
279  m_needle3->setElementId(n3);
280  // l_scene->addItem(m_needle3);
281  n3enabled = true;
282  }
283 
284  if (m_renderer->elementExists(fg)) {
285  m_foreground->setSharedRenderer(m_renderer);
286  m_foreground->setElementId(fg);
287  // Center it on the scene
288  QRectF rectB = m_background->boundingRect();
289  QRectF rectF = m_foreground->boundingRect();
290  m_foreground->setPos(rectB.width() / 2 - rectF.width() / 2,
291  rectB.height() / 2 - rectF.height() / 2);
292  // l_scene->addItem(m_foreground);
293  fgenabled = true;
294  }
295 
296  rotateN1 = false;
297  horizN1 = false;
298  vertN1 = false;
299  rotateN2 = false;
300  horizN2 = false;
301  vertN2 = false;
302  rotateN3 = false;
303  horizN3 = false;
304  vertN3 = false;
305 
306  // Now setup the rotation/translation settings:
307  // this is UGLY UGLY UGLY, sorry...
308  if (n1Move.contains("Rotate")) {
309  rotateN1 = true;
310  } else if (n1Move.contains("Horizontal")) {
311  horizN1 = true;
312  } else if (n1Move.contains("Vertical")) {
313  vertN1 = true;
314  }
315 
316  if (n2Move.contains("Rotate")) {
317  rotateN2 = true;
318  } else if (n2Move.contains("Horizontal")) {
319  horizN2 = true;
320  } else if (n2Move.contains("Vertical")) {
321  vertN2 = true;
322  }
323 
324  if (n3Move.contains("Rotate")) {
325  rotateN3 = true;
326  } else if (n3Move.contains("Horizontal")) {
327  horizN3 = true;
328  } else if (n3Move.contains("Vertical")) {
329  vertN3 = true;
330  }
331 
332  l_scene->setSceneRect(m_background->boundingRect());
333 
334  // Now Initialize the center for all transforms of the dial needles to the
335  // center of the background:
336  // - Move the center of the needle to the center of the background.
337  QRectF rectB = m_background->boundingRect();
338  QRectF rectN = m_needle1->boundingRect();
339  m_needle1->setPos(rectB.width() / 2 - rectN.width() / 2,
340  rectB.height() / 2 - rectN.height() / 2);
341  // - Put the transform origin point of the needle at its center.
342  m_needle1->setTransformOriginPoint(rectN.width() / 2, rectN.height() / 2);
343 
344  // Check whether the dial also wants display the numeric value:
345  if (m_renderer->elementExists(n1 + "-text")) {
346  QMatrix textMatrix = m_renderer->matrixForElement(n1 + "-text");
347  qreal startX = textMatrix.mapRect(m_renderer->boundsOnElement(n1 + "-text")).x();
348  qreal startY = textMatrix.mapRect(m_renderer->boundsOnElement(n1 + "-text")).y();
349  QTransform matrix;
350  matrix.translate(startX, startY);
351  m_text1 = new QGraphicsTextItem("0.00");
352  m_text1->setDefaultTextColor(QColor("White"));
353  m_text1->setTransform(matrix, false);
354  l_scene->addItem(m_text1);
355  } else {
356  m_text1 = NULL;
357  }
358 
359  if ((n1 != n2) && n2enabled) {
360  // Only do it for needle2 if it is not the same as n1
361  rectN = m_needle2->boundingRect();
362  m_needle2->setPos(rectB.width() / 2 - rectN.width() / 2,
363  rectB.height() / 2 - rectN.height() / 2);
364  m_needle2->setTransformOriginPoint(rectN.width() / 2, rectN.height() / 2);
365  // Check whether the dial also wants display the numeric value:
366  if (m_renderer->elementExists(n2 + "-text")) {
367  QMatrix textMatrix = m_renderer->matrixForElement(n2 + "-text");
368  qreal startX = textMatrix.mapRect(m_renderer->boundsOnElement(n2 + "-text")).x();
369  qreal startY = textMatrix.mapRect(m_renderer->boundsOnElement(n2 + "-text")).y();
370  QTransform matrix;
371  matrix.translate(startX, startY);
372  m_text2 = new QGraphicsTextItem("0.00");
373  m_text2->setDefaultTextColor(QColor("White"));
374  m_text2->setTransform(matrix, false);
375  l_scene->addItem(m_text2);
376  } else {
377  m_text2 = NULL;
378  }
379  }
380  if (n3enabled) {
381  rectN = m_needle3->boundingRect();
382  m_needle3->setPos(rectB.width() / 2 - rectN.width() / 2,
383  rectB.height() / 2 - rectN.height() / 2);
384  m_needle3->setTransformOriginPoint(rectN.width() / 2, rectN.height() / 2);
385  // Check whether the dial also wants display the numeric value:
386  if (m_renderer->elementExists(n3 + "-text")) {
387  QMatrix textMatrix = m_renderer->matrixForElement(n3 + "-text");
388  qreal startX = textMatrix.mapRect(m_renderer->boundsOnElement(n3 + "-text")).x();
389  qreal startY = textMatrix.mapRect(m_renderer->boundsOnElement(n3 + "-text")).y();
390  QTransform matrix;
391  matrix.translate(startX, startY);
392  m_text3 = new QGraphicsTextItem("0.00");
393  m_text3->setDefaultTextColor(QColor("White"));
394  m_text3->setTransform(matrix, false);
395  l_scene->addItem(m_text3);
396  } else {
397  m_text3 = NULL;
398  }
399  }
400 
401  // Last: we just loaded the dial file which is by default positioned on a "zero" value
402  // of the needles, so we have to reset the needle values too upon dial file loading,
403  // otherwise
404  // we would end up with an offset whenever we change a dial file and the needle value
405  // is not zero at that time.
406  needle1Value = 0;
407  needle2Value = 0;
408  needle3Value = 0;
409  if (!dialTimer.isActive())
410  dialTimer.start();
411  dialError = false;
412  } else {
413  qDebug() << "no file: display default background.";
414  m_renderer->load(QString(":/dial/images/empty.svg"));
415  l_scene->clear(); // This also deletes all items contained in the scene.
416  m_background = new QGraphicsSvgItem();
417  m_background->setSharedRenderer(m_renderer);
418  l_scene->addItem(m_background);
419  m_text1 = NULL;
420  m_text2 = NULL;
421  m_text3 = NULL;
422  m_needle1 = NULL;
423  m_needle2 = NULL;
424  m_needle3 = NULL;
425  dialError = true;
426  }
427 }
428 
430 {
431  update();
432 }
433 
434 void DialGadgetWidget::paintEvent(QPaintEvent *event)
435 {
436  // Skip painting until the dial file is loaded
437  if (!m_renderer->isValid()) {
438  qDebug() << "Dial file not loaded, not rendering";
439  return;
440  }
441  QGraphicsView::paintEvent(event);
442 }
443 
444 // This event enables the dial to be dynamically resized
445 // whenever the gadget is resized, taking advantage of the vector
446 // nature of SVG dials.
447 void DialGadgetWidget::resizeEvent(QResizeEvent *event)
448 {
449  Q_UNUSED(event);
450  fitInView(m_background, Qt::KeepAspectRatio);
451 }
452 
453 void DialGadgetWidget::setDialFont(QString fontProps)
454 {
455  QFont font = QFont("Arial", 12);
456  font.fromString(fontProps);
457  if (m_text1) {
458  m_text1->setFont(font);
459  }
460 }
461 
462 // Converts the value into an angle:
463 // this enables smooth rotation in rotateNeedles below
465 {
466  if (rotateN1) {
467  needle1Target = 360 * (value * n1Factor) / (n1MaxValue - n1MinValue);
468  }
469  if (horizN1) {
470  needle1Target = (value * n1Factor) / (n1MaxValue - n1MinValue);
471  }
472  if (vertN1) {
473  needle1Target = (value * n1Factor) / (n1MaxValue - n1MinValue);
474  }
475  if (!dialTimer.isActive())
476  dialTimer.start();
477  if (m_text1) {
478  QString s;
479  s.sprintf("%.2f", value * n1Factor);
480  m_text1->setPlainText(s);
481  }
482 }
483 
485 {
486  if (rotateN2) {
487  needle2Target = 360 * (value * n2Factor) / (n2MaxValue - n2MinValue);
488  }
489  if (horizN2) {
490  needle2Target = (value * n2Factor) / (n2MaxValue - n2MinValue);
491  }
492  if (vertN2) {
493  needle2Target = (value * n2Factor) / (n2MaxValue - n2MinValue);
494  }
495  if (!dialTimer.isActive())
496  dialTimer.start();
497  if (m_text2) {
498  QString s;
499  s.sprintf("%.2f", value * n2Factor);
500  m_text2->setPlainText(s);
501  }
502 }
503 
505 {
506  if (rotateN3) {
507  needle3Target = 360 * (value * n3Factor) / (n3MaxValue - n3MinValue);
508  }
509  if (horizN3) {
510  needle3Target = (value * n3Factor) / (n3MaxValue - n3MinValue);
511  }
512  if (vertN3) {
513  needle3Target = (value * n3Factor) / (n3MaxValue - n3MinValue);
514  }
515  if (!dialTimer.isActive())
516  dialTimer.start();
517  if (m_text3) {
518  QString s;
519  s.sprintf("%.2f", value * n3Factor);
520  m_text3->setPlainText(s);
521  }
522 }
523 
524 // Take an input value and rotate the dial accordingly
525 // Rotation is smooth, starts fast and slows down when
526 // approaching the target.
527 // We aim for a 0.5 degree precision.
528 //
529 // Note: this code is valid even if needle1 and needle2 point
530 // to the same element.
531 void DialGadgetWidget::rotateNeedles()
532 {
533  if (dialError) {
534  // We get there in case the dial file is missing or corrupt.
535  dialTimer.stop();
536  return;
537  }
538  int dialRun = 3;
539  if (n2enabled) {
540  double needle2Diff;
541  if (fabs((needle2Value - needle2Target) * 10) > 5 && beSmooth) {
542  needle2Diff = (needle2Target - needle2Value) / 5;
543  } else {
544  needle2Diff = needle2Target - needle2Value;
545  dialRun--;
546  }
547  if (rotateN2) {
548  m_needle2->setRotation(m_needle2->rotation() + needle2Diff);
549  } else {
550  QPointF opd = QPointF(0, 0);
551  if (horizN2) {
552  opd = QPointF(needle2Diff, 0);
553  }
554  if (vertN2) {
555  opd = QPointF(0, needle2Diff);
556  }
557  m_needle2->setTransform(QTransform::fromTranslate(opd.x(), opd.y()), true);
558  // Since we have moved the needle, we need to move
559  // the transform origin point the opposite way
560  // so that it keeps rotating from the same point.
561  // (this is only useful if needle1 and needle2 are the
562  // same object, for combined movement such as attitude indicator).
563  QPointF oop = m_needle2->transformOriginPoint();
564  m_needle2->setTransformOriginPoint(oop.x() - opd.x(), oop.y() - opd.y());
565  }
566  needle2Value += needle2Diff;
567  } else {
568  dialRun--;
569  }
570 
571  // We assume that needle1 always exists!
572  double needle1Diff;
573  if ((fabs((needle1Value - needle1Target) * 10) > 5) && beSmooth) {
574  needle1Diff = (needle1Target - needle1Value) / 5;
575  } else {
576  needle1Diff = needle1Target - needle1Value;
577  dialRun--;
578  }
579  if (rotateN1) {
580  m_needle1->setRotation(m_needle1->rotation() + needle1Diff);
581  } else {
582  QPointF opd = QPointF(0, 0);
583  if (horizN1) {
584  opd = QPointF(needle1Diff, 0);
585  }
586  if (vertN1) {
587  opd = QPointF(0, needle1Diff);
588  }
589  m_needle1->setTransform(QTransform::fromTranslate(opd.x(), opd.y()), true);
590  QPointF oop = m_needle1->transformOriginPoint();
591  m_needle1->setTransformOriginPoint((oop.x() - opd.x()), (oop.y() - opd.y()));
592  }
593  needle1Value += needle1Diff;
594 
595  if (n3enabled) {
596  double needle3Diff;
597  if ((fabs((needle3Value - needle3Target) * 10) > 5) && beSmooth) {
598  needle3Diff = (needle3Target - needle3Value) / 5;
599  } else {
600  needle3Diff = needle3Target - needle3Value;
601  dialRun--;
602  }
603  if (rotateN3) {
604  m_needle3->setRotation(m_needle3->rotation() + needle3Diff);
605  } else {
606  QPointF opd = QPointF(0, 0);
607  if (horizN3) {
608  opd = QPointF(needle3Diff, 0);
609  }
610  if (vertN3) {
611  opd = QPointF(0, needle3Diff);
612  }
613  m_needle3->setTransform(QTransform::fromTranslate(opd.x(), opd.y()), true);
614  QPointF oop = m_needle3->transformOriginPoint();
615  m_needle3->setTransformOriginPoint((oop.x() - opd.x()), (oop.y() - opd.y()));
616  }
617  needle3Value += needle3Diff;
618  } else {
619  dialRun--;
620  }
621 
622  // Now check: if dialRun is now zero, we should
623  // just stop the timer since all needles have finished moving
624  if (!dialRun)
625  dialTimer.stop();
626 }
DialGadgetWidget(QWidget *parent=nullptr)
void updateNeedle2(UAVObject *object2)
Called by the UAVObject which got updated.
void setNeedle3(double value)
double getDouble(int index=0) const
void setDialFont(QString fontProps)
Core plugin system that manages the plugins, their life cycle and their registered objects...
Definition: pluginmanager.h:53
void objectUpdated(UAVObject *obj)
Signal sent whenever any field of the object is updated.
void connectNeedles(QString object1, QString field1, QString object2, QString field2, QString object3, QString field3)
Connects the widget to the relevant UAVObjects.
void paintEvent(QPaintEvent *event)
void setDialFile(QString dfn, QString bg, QString fg, QString n1, QString n2, QString n3, QString n1Move, QString n2Move, QString n3Move)
void setNeedle1(double value)
void updateNeedle3(UAVObject *object3)
Called by the UAVObject which got updated.
UAVObjectField * getField(const QString &name)
Definition: uavobject.cpp:236
void updateNeedle1(UAVObject *object1)
Called by the UAVObject which got updated.
void setNeedle2(double value)
QStringList getElementNames() const
void resizeEvent(QResizeEvent *event)
x
Definition: OPPlots.m:100
UAVObject * getObject(const QString &name, quint32 instId=0)
y
Definition: OPPlots.m:101