Merged in f5soh/librepilot/update_credits (pull request #529)
[librepilot.git] / ground / gcs / src / plugins / uavobjectbrowser / uavobjectbrowserwidget.cpp
bloba18c2ce0dfc931cce42d2bb87bc07cd2b08f8fc0
1 /**
2 ******************************************************************************
4 * @file uavobjectbrowserwidget.cpp
5 * @author The LibrePilot Project, http://www.librepilot.org Copyright (C) 2016.
6 * Tau Labs, http://taulabs.org, Copyright (C) 2013
7 * The OpenPilot Team, http://www.openpilot.org Copyright (C) 2010.
8 * @addtogroup GCSPlugins GCS Plugins
9 * @{
10 * @addtogroup UAVObjectBrowserPlugin UAVObject Browser Plugin
11 * @{
12 * @brief The UAVObject Browser gadget plugin
13 *****************************************************************************/
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.
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.
25 * You should have received a copy of the GNU General Public License along
26 * with this program; if not, write to the Free Software Foundation, Inc.,
27 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
29 #include "uavobjectbrowserwidget.h"
31 #include "ui_uavobjectbrowser.h"
32 #include "ui_viewoptions.h"
34 #include "uavobjecttreemodel.h"
35 #include "browseritemdelegate.h"
36 #include "treeitem.h"
37 #include "uavobjectmanager.h"
38 #include "extensionsystem/pluginmanager.h"
39 #include "utils/mustache.h"
41 #include <QTextStream>
42 #include <QDebug>
44 UAVObjectBrowserWidget::UAVObjectBrowserWidget(QWidget *parent) : QWidget(parent)
46 m_viewoptionsDialog = new QDialog(this);
48 m_viewoptions = new Ui_viewoptions();
49 m_viewoptions->setupUi(m_viewoptionsDialog);
51 m_model = createTreeModel();
53 m_modelProxy = new TreeSortFilterProxyModel(this);
54 m_modelProxy->setSourceModel(m_model);
55 m_modelProxy->setDynamicSortFilter(true);
57 m_browser = new Ui_UAVObjectBrowser();
58 m_browser->setupUi(this);
59 m_browser->treeView->setModel(m_modelProxy);
60 m_browser->treeView->setColumnWidth(0, 300);
62 m_browser->treeView->setItemDelegate(new BrowserItemDelegate());
63 m_browser->treeView->setEditTriggers(QAbstractItemView::AllEditTriggers);
64 m_browser->treeView->setSelectionBehavior(QAbstractItemView::SelectItems);
66 m_mustacheTemplate = loadFileIntoString(QString(":/uavobjectbrowser/resources/uavodescription.mustache"));
68 showDescription(m_viewoptions->cbDescription->isChecked());
70 connect(m_browser->treeView->selectionModel(), SIGNAL(currentChanged(QModelIndex, QModelIndex)),
71 this, SLOT(currentChanged(QModelIndex, QModelIndex)), Qt::UniqueConnection);
72 connect(m_browser->saveSDButton, SIGNAL(clicked()), this, SLOT(saveObject()));
73 connect(m_browser->readSDButton, SIGNAL(clicked()), this, SLOT(loadObject()));
74 connect(m_browser->sendButton, SIGNAL(clicked()), this, SLOT(sendUpdate()));
75 connect(m_browser->requestButton, SIGNAL(clicked()), this, SLOT(requestUpdate()));
76 connect(m_browser->eraseSDButton, SIGNAL(clicked()), this, SLOT(eraseObject()));
77 connect(m_browser->tbView, SIGNAL(clicked()), this, SLOT(viewSlot()));
78 connect(m_browser->splitter, SIGNAL(splitterMoved(int, int)), this, SLOT(splitterMoved()));
80 connect(m_viewoptions->cbDescription, SIGNAL(toggled(bool)), this, SLOT(showDescription(bool)));
82 connect(m_viewoptions->cbCategorized, SIGNAL(toggled(bool)), this, SLOT(updateViewOptions()));
83 connect(m_viewoptions->cbMetaData, SIGNAL(toggled(bool)), this, SLOT(updateViewOptions()));
84 connect(m_viewoptions->cbScientific, SIGNAL(toggled(bool)), this, SLOT(updateViewOptions()));
86 // search field and button
87 connect(m_browser->searchLine, SIGNAL(textChanged(QString)), this, SLOT(searchLineChanged(QString)));
88 connect(m_browser->searchClearButton, SIGNAL(clicked(bool)), this, SLOT(searchTextCleared()));
90 enableSendRequest(false);
93 UAVObjectBrowserWidget::~UAVObjectBrowserWidget()
95 delete m_browser;
98 void UAVObjectBrowserWidget::setViewOptions(bool showCategories, bool showMetadata, bool useScientificNotation, bool showDescription)
100 m_viewoptions->cbCategorized->setChecked(showCategories);
101 m_viewoptions->cbMetaData->setChecked(showMetadata);
102 m_viewoptions->cbScientific->setChecked(useScientificNotation);
103 m_viewoptions->cbDescription->setChecked(showDescription);
106 void UAVObjectBrowserWidget::setSplitterState(QByteArray state)
108 m_browser->splitter->restoreState(state);
111 void UAVObjectBrowserWidget::showDescription(bool show)
113 m_browser->descriptionText->setVisible(show);
115 // persist options
116 emit viewOptionsChanged(m_viewoptions->cbCategorized->isChecked(), m_viewoptions->cbScientific->isChecked(),
117 m_viewoptions->cbMetaData->isChecked(), m_viewoptions->cbDescription->isChecked());
120 void UAVObjectBrowserWidget::sendUpdate()
122 // TODO why steal focus ?
123 this->setFocus();
125 ObjectTreeItem *objItem = findCurrentObjectTreeItem();
127 if (objItem != NULL) {
128 objItem->apply();
129 UAVObject *obj = objItem->object();
130 Q_ASSERT(obj);
131 obj->updated();
135 void UAVObjectBrowserWidget::requestUpdate()
137 ObjectTreeItem *objItem = findCurrentObjectTreeItem();
139 if (objItem != NULL) {
140 UAVObject *obj = objItem->object();
141 Q_ASSERT(obj);
142 obj->requestUpdate();
146 ObjectTreeItem *UAVObjectBrowserWidget::findCurrentObjectTreeItem()
148 QModelIndex current = m_browser->treeView->currentIndex();
149 TreeItem *item = static_cast<TreeItem *>(current.data(Qt::UserRole).value<void *>());
150 ObjectTreeItem *objItem = 0;
152 while (item) {
153 objItem = dynamic_cast<ObjectTreeItem *>(item);
154 if (objItem) {
155 break;
157 item = item->parentItem();
159 return objItem;
162 QString UAVObjectBrowserWidget::loadFileIntoString(QString fileName)
164 QFile file(fileName);
166 file.open(QIODevice::ReadOnly);
167 QTextStream stream(&file);
168 QString line = stream.readAll();
169 file.close();
170 return line;
173 void UAVObjectBrowserWidget::saveObject()
175 // TODO why steal focus ?
176 this->setFocus();
178 // Send update so that the latest value is saved
179 sendUpdate();
181 // Save object
182 ObjectTreeItem *objItem = findCurrentObjectTreeItem();
184 if (objItem != NULL) {
185 UAVObject *obj = objItem->object();
186 Q_ASSERT(obj);
187 updateObjectPersistence(ObjectPersistence::OPERATION_SAVE, obj);
191 void UAVObjectBrowserWidget::loadObject()
193 // Load object
194 ObjectTreeItem *objItem = findCurrentObjectTreeItem();
196 if (objItem != NULL) {
197 UAVObject *obj = objItem->object();
198 Q_ASSERT(obj);
199 updateObjectPersistence(ObjectPersistence::OPERATION_LOAD, obj);
200 // Retrieve object so that latest value is displayed
201 requestUpdate();
205 void UAVObjectBrowserWidget::eraseObject()
207 ObjectTreeItem *objItem = findCurrentObjectTreeItem();
209 if (objItem != NULL) {
210 UAVObject *obj = objItem->object();
211 Q_ASSERT(obj);
212 updateObjectPersistence(ObjectPersistence::OPERATION_DELETE, obj);
213 // Retrieve object so that correct default value is displayed
214 requestUpdate();
218 void UAVObjectBrowserWidget::updateObjectPersistence(ObjectPersistence::OperationOptions op, UAVObject *obj)
220 ExtensionSystem::PluginManager *pm = ExtensionSystem::PluginManager::instance();
221 UAVObjectManager *objManager = pm->getObject<UAVObjectManager>();
222 ObjectPersistence *objper = dynamic_cast<ObjectPersistence *>(objManager->getObject(ObjectPersistence::NAME));
224 if (obj != NULL) {
225 ObjectPersistence::DataFields data;
226 data.Operation = op;
227 data.Selection = ObjectPersistence::SELECTION_SINGLEOBJECT;
228 data.ObjectID = obj->getObjID();
229 data.InstanceID = obj->getInstID();
230 objper->setData(data);
231 objper->updated();
235 void UAVObjectBrowserWidget::currentChanged(const QModelIndex &current, const QModelIndex &previous)
237 Q_UNUSED(previous);
239 bool enable = true;
240 if (!current.isValid()) {
241 enable = false;
243 TreeItem *item = static_cast<TreeItem *>(current.data(Qt::UserRole).value<void *>());
244 TopTreeItem *top = dynamic_cast<TopTreeItem *>(item);
245 ObjectTreeItem *data = dynamic_cast<ObjectTreeItem *>(item);
246 if (top || (data && !data->object())) {
247 enable = false;
249 enableSendRequest(enable);
250 updateDescription();
253 void UAVObjectBrowserWidget::viewSlot()
255 if (m_viewoptionsDialog->isVisible()) {
256 m_viewoptionsDialog->setVisible(false);
257 } else {
258 QPoint pos = QCursor::pos();
259 pos.setX(pos.x() - m_viewoptionsDialog->width());
260 m_viewoptionsDialog->move(pos);
261 m_viewoptionsDialog->show();
265 UAVObjectTreeModel *UAVObjectBrowserWidget::createTreeModel()
267 UAVObjectTreeModel *model = new UAVObjectTreeModel(this);
269 model->setShowCategories(m_viewoptions->cbCategorized->isChecked());
270 model->setShowMetadata(m_viewoptions->cbMetaData->isChecked());
271 model->setUseScientificNotation(m_viewoptions->cbScientific->isChecked());
273 model->setRecentlyUpdatedColor(m_recentlyUpdatedColor);
274 model->setManuallyChangedColor(m_manuallyChangedColor);
275 model->setRecentlyUpdatedTimeout(m_recentlyUpdatedTimeout);
276 model->setUnknownObjectColor(m_unknownObjectColor);
277 model->setOnlyHighlightChangedValues(m_onlyHighlightChangedValues);
279 return model;
282 void UAVObjectBrowserWidget::updateViewOptions()
284 bool showCategories = m_viewoptions->cbCategorized->isChecked();
285 bool useScientificNotation = m_viewoptions->cbScientific->isChecked();
286 bool showMetadata = m_viewoptions->cbMetaData->isChecked();
287 bool showDesc = m_viewoptions->cbDescription->isChecked();
289 m_model->setShowCategories(showCategories);
290 m_model->setShowMetadata(showMetadata);
291 m_model->setUseScientificNotation(useScientificNotation);
293 // force an expand all if search text is not empty
294 if (!m_browser->searchLine->text().isEmpty()) {
295 searchLineChanged(m_browser->searchLine->text());
298 // persist options
299 emit viewOptionsChanged(showCategories, useScientificNotation, showMetadata, showDesc);
302 void UAVObjectBrowserWidget::splitterMoved()
304 emit splitterChanged(m_browser->splitter->saveState());
307 QString UAVObjectBrowserWidget::createObjectDescription(UAVObject *object)
309 QString mustache(m_mustacheTemplate);
311 QVariantHash uavoHash;
313 uavoHash["OBJECT_NAME_TITLE"] = tr("Name");
314 uavoHash["OBJECT_NAME"] = object->getName();
315 uavoHash["CATEGORY_TITLE"] = tr("Category");
316 uavoHash["CATEGORY"] = object->getCategory();
317 uavoHash["TYPE_TITLE"] = tr("Type");
318 uavoHash["TYPE"] = object->isMetaDataObject() ? tr("Metadata") : object->isSettingsObject() ? tr("Setting") : tr("Data");
319 uavoHash["SIZE_TITLE"] = tr("Size");
320 uavoHash["SIZE"] = object->getNumBytes();
321 uavoHash["DESCRIPTION_TITLE"] = tr("Description");
322 uavoHash["DESCRIPTION"] = object->getDescription().replace("@ref", "");
323 uavoHash["MULTI_INSTANCE_TITLE"] = tr("Multi");
324 uavoHash["MULTI_INSTANCE"] = object->isSingleInstance() ? tr("No") : tr("Yes");
325 uavoHash["FIELDS_NAME_TITLE"] = tr("Fields");
326 QVariantList fields;
327 foreach(UAVObjectField * field, object->getFields()) {
328 QVariantHash fieldHash;
330 fieldHash["FIELD_NAME_TITLE"] = tr("Name");
331 fieldHash["FIELD_NAME"] = field->getName();
332 fieldHash["FIELD_TYPE_TITLE"] = tr("Type");
333 fieldHash["FIELD_TYPE"] = QString("%1%2").arg(field->getTypeAsString(),
334 (field->getNumElements() > 1 ? QString("[%1]").arg(field->getNumElements()) : QString()));
335 if (!field->getUnits().isEmpty()) {
336 fieldHash["FIELD_UNIT_TITLE"] = tr("Unit");
337 fieldHash["FIELD_UNIT"] = field->getUnits();
339 if (!field->getOptions().isEmpty()) {
340 fieldHash["FIELD_OPTIONS_TITLE"] = tr("Options");
341 QVariantList options;
342 foreach(QString option, field->getOptions()) {
343 QVariantHash optionHash;
345 optionHash["FIELD_OPTION"] = option;
346 if (!options.isEmpty()) {
347 optionHash["FIELD_OPTION_DELIM"] = ", ";
349 options.append(optionHash);
351 fieldHash["FIELD_OPTIONS"] = options;
353 if (field->getElementNames().count() > 1) {
354 fieldHash["FIELD_ELEMENTS_TITLE"] = tr("Elements");
355 QVariantList elements;
356 for (int i = 0; i < field->getElementNames().count(); i++) {
357 QString element = field->getElementNames().at(i);
358 QVariantHash elementHash;
359 elementHash["FIELD_ELEMENT"] = element;
360 QString limitsString = field->getLimitsAsString(i);
361 if (!limitsString.isEmpty()) {
362 elementHash["FIELD_ELEMENT_LIMIT"] = limitsString.prepend(" (").append(")");
364 if (!elements.isEmpty()) {
365 elementHash["FIELD_ELEMENT_DELIM"] = ", ";
367 elements.append(elementHash);
369 fieldHash["FIELD_ELEMENTS"] = elements;
370 } else if (!field->getLimitsAsString(0).isEmpty()) {
371 fieldHash["FIELD_LIMIT_TITLE"] = tr("Limits");
372 fieldHash["FIELD_LIMIT"] = field->getLimitsAsString(0);
375 if (!field->getDescription().isEmpty()) {
376 fieldHash["FIELD_DESCRIPTION_TITLE"] = tr("Description");
377 fieldHash["FIELD_DESCRIPTION"] = field->getDescription();
380 fields.append(fieldHash);
382 uavoHash["FIELDS"] = fields;
383 Mustache::QtVariantContext context(uavoHash);
384 Mustache::Renderer renderer;
385 return renderer.render(mustache, &context);
388 void UAVObjectBrowserWidget::enableSendRequest(bool enable)
390 m_browser->sendButton->setEnabled(enable);
391 m_browser->requestButton->setEnabled(enable);
392 m_browser->saveSDButton->setEnabled(enable);
393 m_browser->readSDButton->setEnabled(enable);
394 m_browser->eraseSDButton->setEnabled(enable);
397 void UAVObjectBrowserWidget::updateDescription()
399 ObjectTreeItem *objItem = findCurrentObjectTreeItem();
401 if (objItem) {
402 UAVObject *obj = objItem->object();
403 if (obj) {
404 m_browser->descriptionText->setText(createObjectDescription(obj));
405 return;
408 m_browser->descriptionText->setText("");
412 * @brief UAVObjectBrowserWidget::searchLineChanged Looks for matching text in the UAVO fields
414 void UAVObjectBrowserWidget::searchLineChanged(QString searchText)
416 m_modelProxy->setFilterRegExp(QRegExp(searchText, Qt::CaseInsensitive, QRegExp::FixedString));
417 if (!searchText.isEmpty()) {
418 int depth = m_viewoptions->cbCategorized->isChecked() ? 2 : 1;
419 m_browser->treeView->expandToDepth(depth);
420 } else {
421 m_browser->treeView->collapseAll();
425 QString UAVObjectBrowserWidget::indexToPath(const QModelIndex &index) const
427 QString path = index.data(Qt::DisplayRole).toString();
429 QModelIndex parent = index.parent();
431 while (parent.isValid()) {
432 path = parent.data(Qt::DisplayRole).toString() + "/" + path;
433 parent = parent.parent();
435 return path;
438 QModelIndex UAVObjectBrowserWidget::indexFromPath(const QString &path) const
440 QStringList list = path.split("/");
442 QModelIndex index = m_modelProxy->index(0, 0);
444 foreach(QString name, list) {
445 QModelIndexList items = m_modelProxy->match(index, Qt::DisplayRole, name, 1, Qt::MatchFlags(Qt::MatchExactly | Qt::MatchRecursive));
447 if (!items.isEmpty()) {
448 index = items.first();
449 } else {
450 // bail out
451 return QModelIndex();
454 return index;
457 void UAVObjectBrowserWidget::saveState(QSettings &settings) const
459 QStringList list;
461 // prepare list
462 foreach(QModelIndex index, m_modelProxy->getPersistentIndexList()) {
463 if (m_browser->treeView->isExpanded(index)) {
464 QString path = indexToPath(index);
465 list << path;
469 // save list
470 settings.setValue("expandedItems", QVariant::fromValue(list));
473 void UAVObjectBrowserWidget::restoreState(QSettings &settings)
475 // get list
476 QStringList list = settings.value("expandedItems").toStringList();
478 foreach(QString path, list) {
479 QModelIndex index = indexFromPath(path);
481 if (index.isValid()) {
482 m_browser->treeView->setExpanded(index, true);
487 void UAVObjectBrowserWidget::searchTextCleared()
489 m_browser->searchLine->clear();
492 TreeSortFilterProxyModel::TreeSortFilterProxyModel(QObject *p) :
493 QSortFilterProxyModel(p)
495 Q_ASSERT(p);
499 * @brief TreeSortFilterProxyModel::filterAcceptsRow Taken from
500 * http://qt-project.org/forums/viewthread/7782. This proxy model
501 * will accept rows:
502 * - That match themselves, or
503 * - That have a parent that matches (on its own), or
504 * - That have a child that matches.
505 * @param sourceRow
506 * @param sourceParent
507 * @return
509 bool TreeSortFilterProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
511 if (filterAcceptsRowItself(source_row, source_parent)) {
512 return true;
515 // accept if any of the parents is accepted on it's own merits
516 QModelIndex parent = source_parent;
517 while (parent.isValid()) {
518 if (filterAcceptsRowItself(parent.row(), parent.parent())) {
519 return true;
521 parent = parent.parent();
524 // accept if any of the children is accepted on it's own merits
525 if (hasAcceptedChildren(source_row, source_parent)) {
526 return true;
529 return false;
532 bool TreeSortFilterProxyModel::filterAcceptsRowItself(int source_row, const QModelIndex &source_parent) const
534 return QSortFilterProxyModel::filterAcceptsRow(source_row, source_parent);
537 bool TreeSortFilterProxyModel::hasAcceptedChildren(int source_row, const QModelIndex &source_parent) const
539 QModelIndex item = sourceModel()->index(source_row, 0, source_parent);
541 if (!item.isValid()) {
542 return false;
545 // check if there are children
546 int childCount = item.model()->rowCount(item);
547 if (childCount == 0) {
548 return false;
551 for (int i = 0; i < childCount; ++i) {
552 if (filterAcceptsRowItself(i, item)) {
553 return true;
556 if (hasAcceptedChildren(i, item)) {
557 return true;
561 return false;