Merged in f5soh/librepilot/update_credits (pull request #529)
[librepilot.git] / ground / gcs / src / plugins / lineardial / lineardialgadgetwidget.cpp
blob9bfb4a84f2c3503084fea199f157c23878167277
1 /**
2 ******************************************************************************
4 * @file lineardialgadgetwidget.cpp
5 * @author Edouard Lafargue Copyright (C) 2010.
6 * @addtogroup GCSPlugins GCS Plugins
7 * @{
8 * @addtogroup LinearDialPlugin Linear Dial Plugin
9 * @{
10 * @brief Implements a gadget that displays linear gauges and generic indicators
11 *****************************************************************************/
13 * This program is free software; you can redistribute it and/or modify
14 * it under the terms of the GNU General Public License as published by
15 * the Free Software Foundation; either version 3 of the License, or
16 * (at your option) any later version.
18 * This program is distributed in the hope that it will be useful, but
19 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
20 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21 * for more details.
23 * You should have received a copy of the GNU General Public License along
24 * with this program; if not, write to the Free Software Foundation, Inc.,
25 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28 #include "lineardialgadgetwidget.h"
29 #include <utils/stylehelper.h>
30 #include <QFileDialog>
31 #include <QOpenGLWidget>
32 #include <QDebug>
34 LineardialGadgetWidget::LineardialGadgetWidget(QWidget *parent) : QGraphicsView(parent)
36 setMinimumSize(32, 32);
37 setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
38 setScene(new QGraphicsScene(this));
39 setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing);
40 m_renderer = new QSvgRenderer();
41 verticalDial = false;
43 paint();
45 obj1 = NULL;
46 fieldName = NULL;
47 fieldValue = NULL;
48 indexTarget = 0;
49 indexValue = 0;
50 places = 0;
51 factor = 1;
53 // This timer mechanism makes the index rotate smoothly
54 connect(&dialTimer, SIGNAL(timeout()), this, SLOT(moveIndex()));
55 dialTimer.start(30);
58 LineardialGadgetWidget::~LineardialGadgetWidget()
60 // Do nothing
63 /*!
64 \brief Enables/Disables OpenGL
66 void LineardialGadgetWidget::enableOpenGL(bool flag)
68 if (flag) {
69 setViewport(new QOpenGLWidget()); // QGLFormat(QGL::SampleBuffers)));
70 } else {
71 setViewport(new QWidget);
75 /*!
76 \brief Connects the widget to the relevant UAVObjects
78 void LineardialGadgetWidget::connectInput(QString object1, QString nfield1)
80 if (obj1 != NULL) {
81 disconnect(obj1, SIGNAL(objectUpdated(UAVObject *)), this, SLOT(updateIndex(UAVObject *)));
83 ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
84 UAVObjectManager *objManager = pm->getObject<UAVObjectManager>();
86 // qDebug() << "Lineardial Connect needles - " << object1 << "-"<< nfield1;
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 connect(obj1, SIGNAL(objectUpdated(UAVObject *)), this, SLOT(updateIndex(UAVObject *)));
93 if (nfield1.contains("-")) {
94 QStringList fieldSubfield = nfield1.split("-", QString::SkipEmptyParts);
95 field1 = fieldSubfield.at(0);
96 subfield1 = fieldSubfield.at(1);
97 haveSubField1 = true;
98 } else {
99 field1 = nfield1;
100 haveSubField1 = false;
102 if (fieldName) {
103 fieldName->setPlainText(nfield1);
105 updateIndex(obj1);
106 } else {
107 qDebug() << "Error: Object is unknown (" << object1 << ") this should not happen.";
113 \brief Called by the UAVObject which got updated
115 Updates the numeric value and/or the icon if the dial wants this.
117 void LineardialGadgetWidget::updateIndex(UAVObject *object1)
119 // Double check that the field exists:
120 UAVObjectField *field = object1->getField(field1);
122 if (field) {
123 QString s;
124 if (field->isNumeric()) {
125 double v;
126 if (haveSubField1) {
127 int indexOfSubField = field->getElementNames().indexOf(QRegExp(subfield1, Qt::CaseSensitive, QRegExp::FixedString));
128 v = field->getDouble(indexOfSubField) * factor;
129 } else {
130 v = field->getDouble() * factor;
132 setIndex(v);
133 s.sprintf("%.*f", places, v);
135 if (field->isText()) {
136 s = field->getValue().toString();
137 if (fieldSymbol) {
138 // If we defined a symbol, we will look for a matching
139 // SVG element to display:
140 if (m_renderer->elementExists("symbol-" + s)) {
141 fieldSymbol->setElementId("symbol-" + s);
142 } else {
143 fieldSymbol->setElementId("symbol");
148 if (fieldValue) {
149 fieldValue->setPlainText(s);
152 if (index && !dialTimer.isActive()) {
153 dialTimer.start();
155 } else {
156 qDebug() << "Wrong field, maybe an issue with object disconnection ?";
161 \brief Setup dial using its master SVG template.
163 Should only be called after the min/max ranges have been set.
166 void LineardialGadgetWidget::setDialFile(QString dfn)
168 QGraphicsScene *l_scene = scene();
170 setBackgroundBrush(QBrush(Utils::StyleHelper::baseColor()));
171 if (QFile::exists(dfn) && m_renderer->load(dfn) && m_renderer->isValid()) {
172 l_scene->clear(); // Beware: clear also deletes all objects
173 // which are currently in the scene
174 background = new QGraphicsSvgItem();
175 background->setSharedRenderer(m_renderer);
176 background->setElementId("background");
177 background->setFlags(QGraphicsItem::ItemClipsChildrenToShape |
178 QGraphicsItem::ItemClipsToShape);
179 l_scene->addItem(background);
181 // The red/yellow/green zones are optional, we just
182 // test on the presence of "red"
183 if (m_renderer->elementExists("red")) {
184 // Order is important: red, then yellow then green
185 // overlayed on top of each other
186 red = new QGraphicsSvgItem();
187 red->setSharedRenderer(m_renderer);
188 red->setElementId("red");
189 red->setParentItem(background);
190 yellow = new QGraphicsSvgItem();
191 yellow->setSharedRenderer(m_renderer);
192 yellow->setElementId("yellow");
193 yellow->setParentItem(background);
194 green = new QGraphicsSvgItem();
195 green->setSharedRenderer(m_renderer);
196 green->setElementId("green");
197 green->setParentItem(background);
198 // In order to properly render the Green/Yellow/Red graphs, we need to find out
199 // the starting location of the bargraph rendering area:
200 QMatrix textMatrix = m_renderer->matrixForElement("bargraph");
201 qreal bgX = textMatrix.mapRect(m_renderer->boundsOnElement("bargraph")).x();
202 qreal bgY = textMatrix.mapRect(m_renderer->boundsOnElement("bargraph")).y();
203 bargraphSize = textMatrix.mapRect(m_renderer->boundsOnElement("bargraph")).width();
204 // Detect if the bargraph is vertical or horizontal.
205 qreal bargraphHeight = textMatrix.mapRect(m_renderer->boundsOnElement("bargraph")).height();
206 if (bargraphHeight > bargraphSize) {
207 verticalDial = true;
208 bargraphSize = bargraphHeight;
209 } else {
210 verticalDial = false;
212 // Now adjust the red/yellow/green zones:
213 double range = maxValue - minValue;
215 green->resetTransform();
216 double greenScale = (greenMax - greenMin) / range;
217 double greenStart = verticalDial ? (maxValue - greenMax) / range * green->boundingRect().height() :
218 (greenMin - minValue) / range * green->boundingRect().width();
219 QTransform matrix;
220 matrix.reset();
221 if (verticalDial) {
222 matrix.scale(1, greenScale);
223 matrix.translate(bgX, (greenStart + bgY) / greenScale);
224 } else {
225 matrix.scale(greenScale, 1);
226 matrix.translate((greenStart + bgX) / greenScale, bgY);
228 green->setTransform(matrix, false);
230 yellow->resetTransform();
231 double yellowScale = (yellowMax - yellowMin) / range;
232 double yellowStart = verticalDial ? (maxValue - yellowMax) / range * yellow->boundingRect().height() :
233 (yellowMin - minValue) / range * yellow->boundingRect().width();
234 matrix.reset();
235 if (verticalDial) {
236 matrix.scale(1, yellowScale);
237 matrix.translate(bgX, (yellowStart + bgY) / yellowScale);
238 } else {
239 matrix.scale(yellowScale, 1);
240 matrix.translate((yellowStart + bgX) / yellowScale, bgY);
242 yellow->setTransform(matrix, false);
244 red->resetTransform();
245 double redScale = (redMax - redMin) / range;
246 double redStart = verticalDial ? (maxValue - redMax) / range * red->boundingRect().height() :
247 (redMin - minValue) / range * red->boundingRect().width();
248 matrix.reset();
249 if (verticalDial) {
250 matrix.scale(1, redScale);
251 matrix.translate(bgX, (redStart + bgY) / redScale);
252 } else {
253 matrix.scale(redScale, 1);
254 matrix.translate((redStart + bgX) / redScale, bgY);
256 red->setTransform(matrix, false);
257 } else {
258 red = NULL;
259 yellow = NULL;
260 green = NULL;
263 // Check whether the dial wants to display a moving index:
264 if (m_renderer->elementExists("needle")) {
265 QMatrix textMatrix = m_renderer->matrixForElement("needle");
266 QRectF nRect = textMatrix.mapRect(m_renderer->boundsOnElement("needle"));
267 startX = nRect.x();
268 startY = nRect.y();
269 QTransform matrix;
270 matrix.translate(startX, startY);
271 index = new QGraphicsSvgItem();
272 index->setSharedRenderer(m_renderer);
273 index->setElementId("needle");
274 index->setTransform(matrix, false);
275 index->setParentItem(background);
276 } else {
277 index = NULL;
280 // Check whether the dial wants display its field name:
281 if (m_renderer->elementExists("field")) {
282 QMatrix textMatrix = m_renderer->matrixForElement("field");
283 QRectF rect = textMatrix.mapRect(m_renderer->boundsOnElement("field"));
284 qreal startX = rect.x();
285 qreal startY = rect.y();
286 qreal elHeight = rect.height();
287 QTransform matrix;
288 matrix.translate(startX, startY - elHeight / 2);
289 fieldName = new QGraphicsTextItem("field");
290 fieldName->setFont(QFont("Arial", (int)elHeight));
291 fieldName->setDefaultTextColor(QColor("White"));
292 fieldName->setTransform(matrix, false);
293 fieldName->setParentItem(background);
294 } else {
295 fieldName = NULL;
298 // Check whether the dial wants display the numeric value:
299 if (m_renderer->elementExists("value")) {
300 QMatrix textMatrix = m_renderer->matrixForElement("value");
301 QRectF nRect = textMatrix.mapRect(m_renderer->boundsOnElement("value"));
302 qreal startX = nRect.x();
303 qreal startY = nRect.y();
304 qreal elHeight = nRect.height();
305 QTransform matrix;
306 matrix.translate(startX, startY - elHeight / 2);
307 fieldValue = new QGraphicsTextItem("0.00");
308 fieldValue->setFont(QFont("Arial", (int)elHeight));
309 fieldValue->setDefaultTextColor(QColor("White"));
310 fieldValue->setTransform(matrix, false);
311 fieldValue->setParentItem(background);
312 } else {
313 fieldValue = NULL;
316 // Check whether the dial wants to display the value as a
317 // symbol (only works for text values):
318 if (m_renderer->elementExists("symbol")) {
319 QMatrix textMatrix = m_renderer->matrixForElement("symbol");
320 qreal startX = textMatrix.mapRect(m_renderer->boundsOnElement("symbol")).x();
321 qreal startY = textMatrix.mapRect(m_renderer->boundsOnElement("symbol")).y();
322 QTransform matrix;
323 matrix.translate(startX, startY);
324 fieldSymbol = new QGraphicsSvgItem();
325 fieldSymbol->setElementId("symbol");
326 fieldSymbol->setSharedRenderer(m_renderer);
327 fieldSymbol->setTransform(matrix, false);
328 fieldSymbol->setParentItem(background);
329 } else {
330 fieldSymbol = NULL;
333 if (m_renderer->elementExists("foreground")) {
334 foreground = new QGraphicsSvgItem();
335 foreground->setSharedRenderer(m_renderer);
336 foreground->setElementId("foreground");
337 foreground->setParentItem(background);
338 fgenabled = true;
339 } else {
340 fgenabled = false;
343 l_scene->setSceneRect(background->boundingRect());
345 // Reset the current index value:
346 indexValue = 0;
347 if (!dialTimer.isActive() && index) {
348 dialTimer.start();
350 } else {
351 qDebug() << "no file ";
352 m_renderer->load(QString(":/lineardial/images/empty.svg"));
353 l_scene->clear(); // This also deletes all items contained in the scene.
354 background = new QGraphicsSvgItem();
355 background->setSharedRenderer(m_renderer);
356 l_scene->addItem(background);
357 fieldName = NULL;
358 fieldValue = NULL;
359 fieldSymbol = NULL;
360 index = NULL;
365 void LineardialGadgetWidget::setDialFont(QString fontProps)
367 // Note: a bit of juggling to preserve the automatic
368 // font size which was calculated upon dial initialization.
369 QFont font = QFont("Arial", 12);
371 font.fromString(fontProps);
372 if (fieldName) {
373 int fieldSize = fieldName->font().pointSize();
374 font.setPointSize(fieldSize);
375 fieldName->setFont(font);
377 if (fieldValue) {
378 int fieldSize = fieldValue->font().pointSize();
379 font.setPointSize(fieldSize);
380 fieldValue->setFont(font);
385 void LineardialGadgetWidget::paint()
387 update();
390 void LineardialGadgetWidget::paintEvent(QPaintEvent *event)
392 // Skip painting until the dial file is loaded
393 if (!m_renderer->isValid()) {
394 qDebug() << "Dial file not loaded, not rendering";
395 return;
397 QGraphicsView::paintEvent(event);
400 // This event enables the dial to be dynamically resized
401 // whenever the gadget is resized, taking advantage of the vector
402 // nature of SVG dials.
403 void LineardialGadgetWidget::resizeEvent(QResizeEvent *event)
405 Q_UNUSED(event);
406 fitInView(background, Qt::KeepAspectRatio);
409 // Converts the value into an percentage:
410 // this enables smooth movement in moveIndex below
411 void LineardialGadgetWidget::setIndex(double value)
413 if (verticalDial) {
414 indexTarget = 100 * (maxValue - value) / (maxValue - minValue);
415 } else {
416 indexTarget = 100 * (value - minValue) / (maxValue - minValue);
420 // Take an input value and move the index accordingly
421 // Move is smooth, starts fast and slows down when
422 // approaching the target.
423 void LineardialGadgetWidget::moveIndex()
425 if (!index) { // Safeguard
426 dialTimer.stop();
427 return;
429 if ((abs(int((indexValue - indexTarget) * 10)) > 3)) {
430 indexValue += (indexTarget - indexValue) / 5;
431 } else {
432 indexValue = indexTarget;
433 dialTimer.stop();
435 QTransform matrix;
436 index->resetTransform();
437 qreal trans = indexValue * bargraphSize / 100;
438 if (verticalDial) {
439 matrix.translate(startX, trans + startY);
440 } else {
441 matrix.translate(trans + startX, startY);
443 index->setTransform(matrix, false);
445 update();