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
10 * @addtogroup UAVObjectBrowserPlugin UAVObject Browser Plugin
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
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"
37 #include "uavobjectmanager.h"
38 #include "extensionsystem/pluginmanager.h"
39 #include "utils/mustache.h"
41 #include <QTextStream>
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()
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
);
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 ?
125 ObjectTreeItem
*objItem
= findCurrentObjectTreeItem();
127 if (objItem
!= NULL
) {
129 UAVObject
*obj
= objItem
->object();
135 void UAVObjectBrowserWidget::requestUpdate()
137 ObjectTreeItem
*objItem
= findCurrentObjectTreeItem();
139 if (objItem
!= NULL
) {
140 UAVObject
*obj
= objItem
->object();
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;
153 objItem
= dynamic_cast<ObjectTreeItem
*>(item
);
157 item
= item
->parentItem();
162 QString
UAVObjectBrowserWidget::loadFileIntoString(QString fileName
)
164 QFile
file(fileName
);
166 file
.open(QIODevice::ReadOnly
);
167 QTextStream
stream(&file
);
168 QString line
= stream
.readAll();
173 void UAVObjectBrowserWidget::saveObject()
175 // TODO why steal focus ?
178 // Send update so that the latest value is saved
182 ObjectTreeItem
*objItem
= findCurrentObjectTreeItem();
184 if (objItem
!= NULL
) {
185 UAVObject
*obj
= objItem
->object();
187 updateObjectPersistence(ObjectPersistence::OPERATION_SAVE
, obj
);
191 void UAVObjectBrowserWidget::loadObject()
194 ObjectTreeItem
*objItem
= findCurrentObjectTreeItem();
196 if (objItem
!= NULL
) {
197 UAVObject
*obj
= objItem
->object();
199 updateObjectPersistence(ObjectPersistence::OPERATION_LOAD
, obj
);
200 // Retrieve object so that latest value is displayed
205 void UAVObjectBrowserWidget::eraseObject()
207 ObjectTreeItem
*objItem
= findCurrentObjectTreeItem();
209 if (objItem
!= NULL
) {
210 UAVObject
*obj
= objItem
->object();
212 updateObjectPersistence(ObjectPersistence::OPERATION_DELETE
, obj
);
213 // Retrieve object so that correct default value is displayed
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
));
225 ObjectPersistence::DataFields data
;
227 data
.Selection
= ObjectPersistence::SELECTION_SINGLEOBJECT
;
228 data
.ObjectID
= obj
->getObjID();
229 data
.InstanceID
= obj
->getInstID();
230 objper
->setData(data
);
235 void UAVObjectBrowserWidget::currentChanged(const QModelIndex
¤t
, const QModelIndex
&previous
)
240 if (!current
.isValid()) {
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())) {
249 enableSendRequest(enable
);
253 void UAVObjectBrowserWidget::viewSlot()
255 if (m_viewoptionsDialog
->isVisible()) {
256 m_viewoptionsDialog
->setVisible(false);
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
);
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());
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");
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();
402 UAVObject
*obj
= objItem
->object();
404 m_browser
->descriptionText
->setText(createObjectDescription(obj
));
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
);
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();
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();
451 return QModelIndex();
457 void UAVObjectBrowserWidget::saveState(QSettings
&settings
) const
462 foreach(QModelIndex index
, m_modelProxy
->getPersistentIndexList()) {
463 if (m_browser
->treeView
->isExpanded(index
)) {
464 QString path
= indexToPath(index
);
470 settings
.setValue("expandedItems", QVariant::fromValue(list
));
473 void UAVObjectBrowserWidget::restoreState(QSettings
&settings
)
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
)
499 * @brief TreeSortFilterProxyModel::filterAcceptsRow Taken from
500 * http://qt-project.org/forums/viewthread/7782. This proxy model
502 * - That match themselves, or
503 * - That have a parent that matches (on its own), or
504 * - That have a child that matches.
506 * @param sourceParent
509 bool TreeSortFilterProxyModel::filterAcceptsRow(int source_row
, const QModelIndex
&source_parent
) const
511 if (filterAcceptsRowItself(source_row
, source_parent
)) {
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())) {
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
)) {
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()) {
545 // check if there are children
546 int childCount
= item
.model()->rowCount(item
);
547 if (childCount
== 0) {
551 for (int i
= 0; i
< childCount
; ++i
) {
552 if (filterAcceptsRowItself(i
, item
)) {
556 if (hasAcceptedChildren(i
, item
)) {