• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

ossia / score / 31952604910

16 Aug 2026 02:24PM UTC coverage: 19.438% (+3.2%) from 16.231%
31952604910

push

github

jcelerier
execution: fix a crash that has been lurking since 2018 in execution cleanup

0 of 1 new or added line in 1 file covered. (0.0%)

2699 existing lines in 72 files now uncovered.

41088 of 211385 relevant lines covered (19.44%)

4325.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

34.47
/src/plugins/score-plugin-deviceexplorer/Explorer/Explorer/DeviceExplorerWidget.cpp
1
// This is an open source non-commercial project. Dear PVS-Studio, please check
2
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
3
#include "DeviceExplorerWidget.hpp"
4

5
#include "DeviceExplorerFilterProxyModel.hpp"
6
#include "DeviceExplorerView.hpp"
7
#include "ExplorationWorkerWrapper.hpp"
8
#include "QProgressIndicator.h"
9
#include "Widgets/AddressEditDialog.hpp"
10
#include "Widgets/DeviceEditDialog.hpp"
11

12
#include <State/Address.hpp>
13
#include <State/Value.hpp>
14

15
#include <Device/Address/AddressSettings.hpp>
16
#include <Device/Loading/JamomaDeviceLoader.hpp>
17
#include <Device/Loading/ScoreDeviceLoader.hpp>
18
#include <Device/Protocol/DeviceInterface.hpp>
19
#include <Device/Protocol/DeviceSettings.hpp>
20
#include <Device/Protocol/ProtocolList.hpp>
21

22
#include <Explorer/Commands/Add/AddAddress.hpp>
23
#include <Explorer/Commands/Add/LoadDevice.hpp>
24
#include <Explorer/Commands/Remove.hpp>
25
#include <Explorer/Commands/RemoveNodes.hpp>
26
#include <Explorer/Commands/ReplaceDevice.hpp>
27
#include <Explorer/Commands/Update/UpdateAddressSettings.hpp>
28
#include <Explorer/Commands/Update/UpdateDeviceSettings.hpp>
29
#include <Explorer/DeviceList.hpp>
30
#include <Explorer/DocumentPlugin/DeviceDocumentPlugin.hpp>
31
#include <Explorer/Explorer/AddressItemModel.hpp>
32
#include <Explorer/Explorer/DeviceExplorerDelegate.hpp>
33
#include <Explorer/Explorer/DeviceExplorerModel.hpp>
34
#include <Explorer/Listening/ListeningHandler.hpp>
35
#include <Explorer/Panel/DeviceExplorerPanelDelegate.hpp>
36

37
#include <score/command/Dispatchers/CommandDispatcher.hpp>
38
#include <score/command/Dispatchers/MacroCommandDispatcher.hpp>
39
#include <score/model/IdentifiedObject.hpp>
40
#include <score/model/path/Path.hpp>
41
#include <score/model/tree/InvisibleRootNode.hpp>
42
#include <score/model/tree/TreeNode.hpp>
43
#include <score/model/tree/TreeNodeSerialization.hpp>
44
#include <score/model/tree/TreeViewExpandState.hpp>
45
#include <score/plugins/application/GUIApplicationPlugin.hpp>
46
#include <score/serialization/AnySerialization.hpp>
47
#include <score/serialization/MapSerialization.hpp>
48
#include <score/tools/Bind.hpp>
49
#include <score/tools/std/Optional.hpp>
50
#include <score/widgets/HelpInteraction.hpp>
51
#include <score/widgets/SearchLineEdit.hpp>
52
#include <score/widgets/SetIcons.hpp>
53
#include <score/widgets/SignalUtils.hpp>
54

55
#include <ossia/detail/algorithms.hpp>
56
#include <ossia/detail/ssize.hpp>
57

58
#include <ossia-qt/js_utilities.hpp>
59

60
#include <QAbstractProxyModel>
61
#include <QAction>
62
#include <QClipboard>
63
#include <QComboBox>
64
#include <QContextMenuEvent>
65
#include <QDialog>
66
#include <QDialogButtonBox>
67
#include <QFileDialog>
68
#include <QGridLayout>
69
#include <QHeaderView>
70
#include <QIcon>
71
#include <QKeySequence>
72
#include <QLabel>
73
#include <QLineEdit>
74
#include <QList>
75
#include <QListWidget>
76
#include <QMenu>
77
#include <QPair>
78
#include <QRegularExpression>
79
#include <QShortcut>
80
#include <QSize>
81
#include <QStackedLayout>
82
#include <QString>
83
#include <QStringList>
84
#include <QTableView>
85
#include <QTimer>
86
#include <QToolButton>
87
#include <QTreeView>
88
#include <qnamespace.h>
89

90
#include <wobjectimpl.h>
91

92
#include <set>
93
#include <stdexcept>
94
W_OBJECT_IMPL(Explorer::DeviceExplorerWidget)
2,026✔
95
namespace Explorer
96
{
UNCOV
97
static const Device::DeviceSettings* getDevice(const Device::Node& n)
×
98
{
UNCOV
99
  if(n.is<Device::AddressSettings>())
×
100
  {
101
    const Device::Node* p = &n;
×
UNCOV
102
    while((p = p->parent()))
×
103
    {
104
      if(p->is<Device::DeviceSettings>())
×
UNCOV
105
        return &p->get<Device::DeviceSettings>();
×
106
    }
107
  }
×
UNCOV
108
  else if(n.is<Device::DeviceSettings>())
×
109
  {
UNCOV
110
    return &n.get<Device::DeviceSettings>();
×
111
  }
112
  return nullptr;
×
UNCOV
113
}
×
114

115
class ExplorerSearchLineEdit final : public score::SearchLineEdit
116
{
117
  using ExpandState = score::TreeViewExpandState<Device::Node, Device::NodePath>;
118
  ExpandState m_expandState;
119

120
public:
121
  ExplorerSearchLineEdit(DeviceExplorerWidget& parent)
108✔
122
      : score::SearchLineEdit{&parent}
54✔
123
      , m_widget{parent}
54✔
124
  {
54✔
125
    connect(this, &QLineEdit::textEdited, this, [this] { search(); });
54✔
126
  }
54✔
127

128
  void search() override
43✔
129
  {
130
    auto m = m_widget.proxyModel();
43✔
131
    if(!m)
43✔
UNCOV
132
      return;
×
133
    auto v = m_widget.view();
43✔
134
    if(!v)
43✔
UNCOV
135
      return;
×
136

137
    const QString& current_filter = m->filterRegularExpression().pattern();
43✔
138
    const QString& new_filter = text();
43✔
139

140
    if(current_filter.isEmpty())
43✔
141
    {
142
      m_expandState.save(m, v);
43✔
143
    }
43✔
144

145
    if(new_filter != current_filter)
43✔
146
    {
147
      m->setFilterFixedString(new_filter);
×
148
      m->setFilterCaseSensitivity(Qt::CaseInsensitive);
×
UNCOV
149
      if(new_filter.isEmpty())
×
150
      {
151
        // Restore the indices
152
        m_expandState.restore(m, v);
×
UNCOV
153
      }
×
154
      else
155
      {
UNCOV
156
        v->expandAll();
×
157
      }
UNCOV
158
    }
×
159
  }
43✔
160

161
  DeviceExplorerWidget& m_widget;
162
};
163

164
class LearnDialog final : public QDialog
165
{
166
public:
167
  LearnDialog(Device::DeviceInterface& dev, QWidget* w)
×
168
      : QDialog{w}
×
169
      , m_dev{dev}
×
170
  {
×
171
    this->setWindowTitle(dev.name() + tr(": learning"));
×
UNCOV
172
    auto lay = new QVBoxLayout{this};
×
173

174
    QDialogButtonBox* buttonBox = new QDialogButtonBox(
×
175
        QDialogButtonBox::Ok | QDialogButtonBox::Cancel, Qt::Horizontal, this);
×
176
    connect(buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept);
×
UNCOV
177
    connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject);
×
178

179
    m_list = new QListWidget{this};
×
180
    lay->addWidget(m_list);
×
UNCOV
181
    lay->addWidget(buttonBox);
×
182

183
    con(dev, &Device::DeviceInterface::pathAdded, this, [this](const State::Address& a) {
×
184
      m_list->addItem(a.toString());
×
185
      m_list->scrollToBottom();
×
UNCOV
186
    });
×
187

188
    m_dev.setLearning(true);
×
UNCOV
189
  }
×
190

UNCOV
191
  ~LearnDialog() { m_dev.setLearning(false); }
×
192

193
  Device::DeviceInterface& m_dev;
UNCOV
194
  QListWidget* m_list{};
×
195
};
196

197
DeviceExplorerWidget::DeviceExplorerWidget(
54✔
198
    const Device::ProtocolFactoryList& pl, QWidget* parent)
199
    : QWidget(parent)
54✔
200
    , m_protocolList{pl}
54✔
201
    , m_proxyModel(nullptr)
202
    , m_deviceDialog(nullptr)
203
{
54✔
204
  setMinimumWidth(150);
54✔
205
  buildGUI();
54✔
206

207
  // Set the expansion signals
208
  connect(
54✔
209
      m_ntView, &DeviceExplorerView::created, this,
54✔
210
      [&](const QModelIndex& parent, int start, int end) {
73✔
211
    if(m_listeningManager)
19✔
212
    {
213
      for(int i = start; i <= end; i++)
38✔
214
      {
215
        Device::Node* node{};
19✔
216
        if(m_ntView->hasProxy())
19✔
217
        {
218
          node = (Device::Node*)sourceIndex(
38✔
219
                     ((QTreeView*)m_ntView)->model()->index(i, 0, parent))
19✔
220
                     .internalPointer();
19✔
221
        }
19✔
222
        else
223
        {
UNCOV
224
          node = ((Device::Node*)model()->index(i, 0, parent).internalPointer());
×
225
        }
226

227
        m_listeningManager->enableListening(*node);
19✔
228
      }
19✔
229
    }
19✔
230
      });
19✔
231
  connect(m_ntView, &QTreeView::expanded, this, [&](const QModelIndex& idx) {
54✔
232
    if(m_listeningManager)
×
233
      m_listeningManager->setListening(idx, true);
×
UNCOV
234
  });
×
235
  connect(m_ntView, &QTreeView::collapsed, this, [&](const QModelIndex& idx) {
54✔
236
    if(m_listeningManager)
×
237
      m_listeningManager->setListening(idx, false);
×
238
  });
×
UNCOV
239
}
×
240

241
void DeviceExplorerWidget::buildGUI()
54✔
242
{
243
  m_ntView = new DeviceExplorerView(this);
54✔
244
  m_ntView->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::MinimumExpanding);
54✔
245
  m_ntView->setItemDelegate(new DeviceExplorerDelegate{m_ntView});
54✔
246

247
  m_addressModel = new AddressItemModel{this};
54✔
248
  m_addressView = new QTableView{this};
54✔
249
  {
250
    auto qs = new QShortcut(QKeySequence::Copy, m_addressView, m_addressView, [this]() {
54✔
251
      auto idx = m_addressView->currentIndex();
×
252
      auto data = m_addressView->model()->data(idx);
×
UNCOV
253
      qApp->clipboard()->setText(data.toString());
×
UNCOV
254
    }, [] {});
×
255
    qs->setContext(Qt::WidgetShortcut);
54✔
256
  }
257
  auto delegate = new AddressItemDelegate{m_addressView};
54✔
258
  m_addressView->setItemDelegate(delegate);
54✔
259
  m_addressView->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Maximum);
54✔
260
  m_addressView->setMinimumHeight(100);
54✔
261

262
  m_addressView->horizontalHeader()->hide();
54✔
263
  m_addressView->verticalHeader()->hide();
54✔
264
  m_addressView->horizontalHeader()->setCascadingSectionResizes(true);
54✔
265
  m_addressView->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
54✔
266
  m_addressView->horizontalHeader()->setStretchLastSection(true);
54✔
267
  m_addressView->horizontalHeader()->setResizeContentsPrecision(-1);
54✔
268
  m_addressView->setAlternatingRowColors(true);
54✔
269
  m_addressView->setHorizontalScrollMode(QAbstractItemView::ScrollPerPixel);
54✔
270
  m_addressView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
54✔
271
  m_addressView->setSizeAdjustPolicy(QAbstractScrollArea::AdjustToContents);
54✔
272
  m_addressView->verticalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
54✔
273
  m_addressView->verticalHeader()->setDefaultSectionSize(14);
54✔
274
  m_addressView->verticalHeader()->setResizeContentsPrecision(-1);
54✔
275
  connect(
54✔
276
      m_addressView->horizontalHeader(), &QHeaderView::sectionResized, m_addressView,
54✔
277
      &QTableView::resizeRowsToContents);
278

279
  m_addressView->setModel(m_addressModel);
54✔
280
  connect(
54✔
281
      m_ntView,
54✔
282
      static_cast<void (DeviceExplorerView::*)()>(&DeviceExplorerView::selectionChanged),
283
      this,
284
      [this] {
191✔
285
    updateAddressView();
137✔
286
    updateActions();
137✔
287
      },
137✔
288
      Qt::QueuedConnection);
289

290
  m_editAction = new QAction(tr("Edit"), this);
54✔
291
  score::setHelp(m_editAction, tr("Edit the device."));
54✔
292

293
  m_refreshAction = new QAction(tr("Refresh namespace"), this);
54✔
294
  m_refreshAction->setShortcut(QKeySequence::Refresh);
54✔
295

296
  m_disconnect = new QAction{tr("Disconnect"), this};
54✔
297
  m_reconnect = new QAction{tr("Reconnect"), this};
54✔
298

299
  m_refreshValueAction = new QAction(tr("Refresh value"), this);
54✔
300

301
  m_removeNodeAction = new QAction(tr("Remove"), this);
54✔
302
  m_importDeviceAction = new QAction{tr("Import device"), this};
54✔
303
  m_exportDeviceAction = new QAction{tr("Export device"), this};
54✔
304
  m_learnAction = new QAction{tr("Learn"), this};
54✔
305
  m_findUsageAction = new QAction{tr("Find usage"), this};
54✔
306

307
#ifdef __APPLE__
308
  m_removeNodeAction->setShortcut(QKeySequence(tr("Ctrl+Backspace")));
309
#else
310
  m_removeNodeAction->setShortcut(QKeySequence::Delete);
54✔
311
#endif
312

313
  m_editAction->setEnabled(false);
54✔
314
  m_refreshAction->setEnabled(false);
54✔
315
  m_refreshValueAction->setEnabled(false);
54✔
316
  m_removeNodeAction->setEnabled(false);
54✔
317
  m_disconnect->setEnabled(false);
54✔
318
  m_reconnect->setEnabled(false);
54✔
319
  m_importDeviceAction->setEnabled(true);
54✔
320
  m_exportDeviceAction->setEnabled(false);
54✔
321
  m_learnAction->setEnabled(false);
54✔
322
  m_findUsageAction->setEnabled(false);
54✔
323

324
  m_editAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
54✔
325
  m_refreshAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
54✔
326
  m_refreshValueAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
54✔
327
  m_removeNodeAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
54✔
328
  m_disconnect->setShortcutContext(Qt::WidgetWithChildrenShortcut);
54✔
329
  m_reconnect->setShortcutContext(Qt::WidgetWithChildrenShortcut);
54✔
330
  m_importDeviceAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
54✔
331
  m_exportDeviceAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
54✔
332
  m_learnAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
54✔
333
  m_findUsageAction->setShortcutContext(Qt::WidgetWithChildrenShortcut);
54✔
334

335
  connect(m_editAction, &QAction::triggered, this, &DeviceExplorerWidget::edit);
54✔
336
  connect(m_refreshAction, &QAction::triggered, this, &DeviceExplorerWidget::refresh);
54✔
337
  connect(
54✔
338
      m_refreshValueAction, &QAction::triggered, this,
54✔
339
      &DeviceExplorerWidget::refreshValue);
340
  connect(m_disconnect, &QAction::triggered, this, &DeviceExplorerWidget::disconnect);
54✔
341
  connect(m_reconnect, &QAction::triggered, this, &DeviceExplorerWidget::reconnect);
54✔
342
  connect(
54✔
343
      m_removeNodeAction, &QAction::triggered, this, &DeviceExplorerWidget::removeNodes);
54✔
344
  connect(
54✔
345
      m_importDeviceAction, &QAction::triggered, this,
54✔
346
      &DeviceExplorerWidget::importDevice);
347
  connect(
54✔
348
      m_exportDeviceAction, &QAction::triggered, this,
54✔
349
      &DeviceExplorerWidget::exportDevice);
350
  connect(m_learnAction, &QAction::triggered, this, &DeviceExplorerWidget::learn);
54✔
351
  connect(
54✔
352
      m_findUsageAction, &QAction::triggered, this, &DeviceExplorerWidget::findUsage);
54✔
353

354
  m_openMenu = new QToolButton(this);
54✔
355
  m_openMenu->setIcon(makeIcons(
108✔
356
      QStringLiteral(":/icons/add_on.png"), QStringLiteral(":/icons/add_off.png"),
54✔
357
      QStringLiteral(":/icons/add_disabled.png")));
54✔
358
  m_openMenu->setAutoRaise(true);
54✔
359

360
  m_addDeviceAction = new QAction(tr("Add device"), this);
54✔
361
  setIcons(
54✔
362
      m_addDeviceAction, QStringLiteral(":/icons/add_device_on.png"),
54✔
363
      QStringLiteral(":/icons/add_device_off.png"),
54✔
364
      QStringLiteral(":/icons/add_device_disabled.png"));
54✔
365
  m_addDeviceAction->setShortcut(tr("Ctrl+B"));
54✔
366

367
  m_addSiblingAction = new QAction(tr("Add sibling"), this);
54✔
368
  setIcons(
54✔
369
      m_addSiblingAction, QStringLiteral(":/icons/add_sibling_on.png"),
54✔
370
      QStringLiteral(":/icons/add_sibling_off.png"),
54✔
371
      QStringLiteral(":/icons/add_sibling_disabled.png"));
54✔
372

373
  m_addChildAction = new QAction(tr("Add child"), this);
54✔
374
  setIcons(
54✔
375
      m_addChildAction, QStringLiteral(":/icons/add_child_on.png"),
54✔
376
      QStringLiteral(":/icons/add_child_off.png"),
54✔
377
      QStringLiteral(":/icons/add_child_disabled.png"));
54✔
378
  connect(
54✔
379
      m_addDeviceAction, &QAction::triggered, this, &DeviceExplorerWidget::addDevice);
54✔
380
  connect(
54✔
381
      m_addSiblingAction, &QAction::triggered, this, &DeviceExplorerWidget::addSibling);
54✔
382
  connect(m_addChildAction, &QAction::triggered, this, &DeviceExplorerWidget::addChild);
54✔
383

384
  m_addSiblingAction->setEnabled(false);
54✔
385
  m_addChildAction->setEnabled(false);
54✔
386

387
  // Setup menus
388

389
  QMenu* addMenu = new QMenu(this);
54✔
390
  addMenu->addAction(m_addDeviceAction);
54✔
391
  addMenu->addAction(m_importDeviceAction);
54✔
392
  addMenu->addAction(m_addSiblingAction);
54✔
393
  addMenu->addAction(m_addChildAction);
54✔
394
  addMenu->addAction(m_exportDeviceAction);
54✔
395
  addMenu->addSeparator();
54✔
396
  addMenu->addAction(m_removeNodeAction);
54✔
397

398
  connect(m_openMenu, &QToolButton::clicked, addMenu, [addMenu]() {
54✔
UNCOV
399
    addMenu->popup(QCursor::pos());
×
UNCOV
400
  });
×
401

402
  // Add actions to the current widget so that shortcuts work
403
  {
404
    this->addAction(m_addDeviceAction);
54✔
405
    this->addAction(m_importDeviceAction);
54✔
406
    this->addAction(m_addSiblingAction);
54✔
407
    this->addAction(m_addChildAction);
54✔
408
    this->addAction(m_exportDeviceAction);
54✔
409

410
    this->addAction(m_refreshAction);
54✔
411
    this->addAction(m_refreshValueAction);
54✔
412
    this->addAction(m_learnAction);
54✔
413
    this->addAction(m_findUsageAction);
54✔
414

415
    this->addAction(m_removeNodeAction);
54✔
416
  }
417

418
  m_columnCBox = new QComboBox(this);
54✔
419
  m_nameLEdit = new ExplorerSearchLineEdit(*this);
54✔
420

421
  connect(
54✔
422
      m_columnCBox, SignalUtils::QComboBox_currentIndexChanged_int(), m_nameLEdit,
54✔
423
      [this] {
97✔
424
    m_proxyModel->setColumn((Explorer::Column)m_columnCBox->currentIndex());
43✔
425
    m_nameLEdit->search();
43✔
426
      });
43✔
427

428
  auto hLayout = new score::MarginLess<QHBoxLayout>;
54✔
429
  hLayout->setSpacing(0);
54✔
430
  hLayout->addWidget(m_openMenu);
54✔
431
  hLayout->addWidget(m_columnCBox);
54✔
432
  hLayout->addWidget(m_nameLEdit);
54✔
433

434
  QWidget* mainWidg = new QWidget;
54✔
435
  mainWidg->setContentsMargins(0, 0, 0, 2);
54✔
436
  auto vLayout = new score::MarginLess<QVBoxLayout>;
54✔
437
  vLayout->addLayout(hLayout);
54✔
438
  vLayout->addWidget(m_ntView);
54✔
439
  vLayout->addWidget(m_addressView);
54✔
440
  mainWidg->setLayout(vLayout);
54✔
441
  mainWidg->setObjectName("DeviceExplorer");
54✔
442

443
  m_lay = new QStackedLayout;
54✔
444
  m_lay->addWidget(mainWidg);
54✔
445

446
  auto refreshParent = new QWidget;
54✔
447
  auto refreshLay = new QGridLayout;
54✔
448
  refreshParent->setLayout(refreshLay);
54✔
449
  m_refreshIndicator = new QProgressIndicator{refreshParent};
54✔
450
  QPalette palette;
54✔
451
  palette.setBrush(QPalette::Window, Qt::transparent);
54✔
452
  m_refreshIndicator->setPalette(palette);
54✔
453

454
  refreshLay->addWidget(m_refreshIndicator);
54✔
455
  m_lay->addWidget(refreshParent);
54✔
456
  setLayout(m_lay);
54✔
457
}
54✔
458

459
void DeviceExplorerWidget::blockGUI(bool b)
×
460
{
461
  m_ntView->setDisabled(b);
×
UNCOV
462
  m_addressView->setDisabled(b);
×
UNCOV
463
  if(b)
×
464
  {
465
    // m_ntView to front
466
    m_lay->setCurrentIndex(1);
×
UNCOV
467
    m_refreshIndicator->startAnimation();
×
UNCOV
468
  }
×
469
  else
470
  {
471
    // progress widget to front
UNCOV
472
    m_lay->setCurrentIndex(0);
×
473
    m_refreshIndicator->stopAnimation();
×
474
  }
UNCOV
475
}
×
476

477
void DeviceExplorerWidget::setEditable(bool b)
66✔
478
{
479
  if(m_openMenu)
66✔
480
    m_openMenu->setEnabled(b);
66✔
481
  m_addressView->setEnabled(b);
66✔
482
}
66✔
483

484
QModelIndex DeviceExplorerWidget::sourceIndex(QModelIndex index) const
19✔
485
{
486
  if(m_ntView->hasProxy())
19✔
487
    index = static_cast<const QAbstractProxyModel*>(m_ntView->QTreeView::model())
38✔
488
                ->mapToSource(index);
19✔
489
  return index;
19✔
490
}
491

492
QModelIndex DeviceExplorerWidget::proxyIndex(QModelIndex index) const
29✔
493
{
494
  if(m_ntView->hasProxy())
29✔
495
    index = static_cast<const QAbstractProxyModel*>(m_ntView->QTreeView::model())
58✔
496
                ->mapFromSource(index);
29✔
497
  return index;
29✔
498
}
499

500
QSize DeviceExplorerWidget::sizeHint() const
×
501
{
UNCOV
502
  return {200, 800};
×
503
}
504

505
void DeviceExplorerWidget::contextMenuEvent(QContextMenuEvent* event)
×
506
{
UNCOV
507
  updateActions();
×
508
  QMenu* contextMenu = new QMenu{this};
×
509

510
  if(auto m = model())
×
511
  {
512
    if(!m->isEmpty())
×
513
    {
514
      QModelIndexList selection = m_ntView->selectedIndexes();
×
515

516
      if(selection.size() == 1)
×
517
      {
UNCOV
518
        auto& node = m->nodeFromModelIndex(m_ntView->selectedIndex());
×
519
        if(node.is<Device::DeviceSettings>())
×
520
        {
521
          auto& lst = m->deviceModel().list();
×
522
          auto& dev = lst.device(node.get<Device::DeviceSettings>().name);
×
523
          dev.setupContextMenu(*contextMenu);
×
524
          contextMenu->addSeparator();
×
525
        }
×
526
      }
×
UNCOV
527
    }
×
528
  }
×
529

530
  contextMenu->addAction(m_editAction);
×
UNCOV
531
  contextMenu->addAction(m_refreshAction);
×
532
  contextMenu->addAction(m_refreshValueAction);
×
533

UNCOV
534
  contextMenu->addAction(m_disconnect);
×
535
  contextMenu->addAction(m_reconnect);
×
536

537
  contextMenu->addSeparator();
×
538
  contextMenu->addAction(m_addDeviceAction);
×
539
  contextMenu->addAction(m_importDeviceAction);
×
540
  contextMenu->addAction(m_addSiblingAction);
×
541
  contextMenu->addAction(m_addChildAction);
×
542
  contextMenu->addAction(m_exportDeviceAction);
×
543
  contextMenu->addAction(m_learnAction);
×
544
  contextMenu->addAction(m_findUsageAction);
×
UNCOV
545
  contextMenu->addSeparator();
×
546
  contextMenu->addAction(m_removeNodeAction);
×
547

548
  contextMenu->exec(event->globalPos());
×
UNCOV
549
  contextMenu->deleteLater();
×
UNCOV
550
}
×
551

552
void DeviceExplorerWidget::setModel(DeviceExplorerModel* model)
137✔
553
{
554
  delete m_proxyModel; //? will also delete previous model ??
137✔
555
  m_proxyModel = nullptr;
137✔
556
  m_listeningManager.reset();
137✔
557
  QObject::disconnect(m_modelCon);
137✔
558
  QObject::disconnect(m_addressCon);
137✔
559

560
  if(model)
137✔
561
  {
562
    m_proxyModel = new DeviceExplorerFilterProxyModel(this);
42✔
563
    m_proxyModel->setSourceModel(model);
42✔
564
    m_ntView->setModel(m_proxyModel);
42✔
565
    model->setView(m_ntView);
42✔
566

567
    m_listeningManager = std::make_unique<ListeningManager>(*model, *this);
42✔
568
    m_cmdDispatcher = std::make_unique<CommandDispatcher<>>(model->commandStack());
42✔
569

570
    populateColumnCBox();
42✔
571

572
    updateActions();
42✔
573

574
    m_modelCon = connect(
42✔
575
        model, &DeviceExplorerModel::nodeChanged, this, [this](Device::Node* n) {
52✔
576
          bool parent_is_expanded = m_ntView->isExpanded(
20✔
577
              proxyIndex(m_ntView->model()->modelIndexFromNode(*n->parent(), 0)));
10✔
578
          if(parent_is_expanded)
10✔
579
          {
580
            if(m_listeningManager)
×
581
              m_listeningManager->enableListening(*n);
×
UNCOV
582
          }
×
583
        });
10✔
584

585
    connect(
42✔
586
        model, &DeviceExplorerModel::dataChanged, this,
42✔
587
        [this](
59✔
588
            const QModelIndex& topLeft, const QModelIndex& bottomRight,
589
            const QVector<int>& roles) {
590
      auto indexes = m_ntView->selectedIndexes();
17✔
591

592
      if(indexes.size() == 1)
17✔
593
      {
594
        auto selected = sourceIndex(indexes.first());
×
595

596
        if(selected.parent() == topLeft.parent() && selected.row() == topLeft.row())
×
597
          updateAddressView();
×
UNCOV
598
      }
×
599
        });
17✔
600
  }
42✔
601
  else
602
  {
603
    m_ntView->setModel((QAbstractItemModel*)nullptr);
95✔
604
  }
605

606
  setEnabled(bool(model));
137✔
607
}
137✔
608

609
void DeviceExplorerWidget::populateColumnCBox()
42✔
610
{
611
  SCORE_ASSERT(model());
42✔
612
  SCORE_ASSERT(m_columnCBox);
42✔
613

614
  QStringList columns = model()->getColumns();
42✔
615
  m_columnCBox->clear();
42✔
616
  m_columnCBox->addItems(columns);
42✔
617
}
42✔
618

619
// The bool indicates if the passed node was a device
620
std::pair<Device::DeviceCapas, bool>
621
getCapas(Device::Node* p, const Device::DeviceList& lst)
×
622
{
623
  if(p->is<Device::DeviceSettings>())
×
624
  {
625
    return {lst.device(p->get<Device::DeviceSettings>().name).capabilities(), true};
×
626
  }
627
  while(p && !p->is<Device::DeviceSettings>())
×
628
  {
629
    p = p->parent();
×
630
  }
UNCOV
631
  if(!p)
×
632
    throw std::runtime_error("Cannot get capabilities of no device");
×
633

UNCOV
634
  return {lst.device(p->get<Device::DeviceSettings>().name).capabilities(), false};
×
UNCOV
635
}
×
636

637
void DeviceExplorerWidget::updateActions()
179✔
638
{
639
  auto m = model();
179✔
640
  if(!m)
179✔
641
    return;
95✔
642

643
  const bool editable = this->editable();
84✔
644
  m_addDeviceAction->setEnabled(true);
84✔
645
  m_importDeviceAction->setEnabled(true);
84✔
646
  m_exportDeviceAction->setEnabled(false);
84✔
647
  m_learnAction->setEnabled(false);
84✔
648
  m_addSiblingAction->setEnabled(false);
84✔
649
  m_addChildAction->setEnabled(false);
84✔
650
  m_editAction->setEnabled(false);
84✔
651
  m_refreshAction->setEnabled(false);
84✔
652
  m_refreshValueAction->setEnabled(false);
84✔
653
  m_removeNodeAction->setEnabled(false);
84✔
654
  m_findUsageAction->setEnabled(false);
84✔
655
  m_reconnect->setEnabled(false);
84✔
656
  m_disconnect->setEnabled(false);
84✔
657

658
  if(!m->isEmpty())
84✔
659
  {
660
    // TODO: choice for multi selection
661

662
    SCORE_ASSERT(m_ntView);
×
663

UNCOV
664
    QModelIndexList selection = m_ntView->selectedIndexes();
×
665
    if(!selection.isEmpty())
×
666
    {
UNCOV
667
      m_findUsageAction->setEnabled(true);
×
668
    }
×
669

670
    if(selection.size() == 1)
×
671
    {
UNCOV
672
      const auto [capas, aDeviceIsSelected] = getCapas(
×
673
          &m->nodeFromModelIndex(m_ntView->selectedIndex()), m->deviceModel().list());
×
674

675
      if(!aDeviceIsSelected)
×
676
      {
677
        m_refreshValueAction->setEnabled(capas.canRefreshValue && editable);
×
678
        m_addSiblingAction->setEnabled(capas.canAddNode && editable);
×
679
        m_addChildAction->setEnabled(capas.canAddNode && editable);
×
UNCOV
680
        m_removeNodeAction->setEnabled(capas.canRemoveNode && editable);
×
UNCOV
681
      }
×
682
      else
683
      {
684
        m_refreshAction->setEnabled(capas.canRefreshTree && editable);
×
685
        m_reconnect->setEnabled(capas.canDisconnect && editable);
×
686
        m_disconnect->setEnabled(capas.canDisconnect && editable);
×
687
        m_importDeviceAction->setEnabled(false);
×
688
        m_exportDeviceAction->setEnabled(true);
×
689
        m_addSiblingAction->setEnabled(false);
×
690
        m_addChildAction->setEnabled(capas.canAddNode && editable);
×
UNCOV
691
        m_removeNodeAction->setEnabled(editable);
×
692
        m_learnAction->setEnabled(capas.canLearn && editable);
×
693
      }
694
      m_editAction->setEnabled(editable);
×
UNCOV
695
    }
×
696
    else if(selection.size() > 1)
×
697
    {
698
      int selectionSize = m_ntView->selectedIndexes().size();
×
UNCOV
699
      bool validSelection = true;
×
700
      for(int i = 0; i < selectionSize; i++)
×
701
      {
702
        QModelIndex ind = proxyModel()->mapToSource(m_ntView->selectedIndexes().at(i));
×
703
        const auto [capas, aDeviceIsSelected]
×
UNCOV
704
            = getCapas(&m->nodeFromModelIndex(ind), m->deviceModel().list());
×
705
        if(!aDeviceIsSelected || !capas.canDisconnect)
×
706
        {
UNCOV
707
          validSelection = false;
×
708
          break;
×
709
        }
710
      }
×
711
      m_reconnect->setEnabled(validSelection && editable);
×
712
      m_disconnect->setEnabled(validSelection && editable);
×
UNCOV
713
    }
×
UNCOV
714
  }
×
715
  else
716
  {
717
    m_editAction->setEnabled(false);
84✔
718
    m_refreshAction->setEnabled(false);
84✔
719
    m_refreshValueAction->setEnabled(false);
84✔
720
    m_removeNodeAction->setEnabled(false);
84✔
721
    m_addSiblingAction->setEnabled(false);
84✔
722
    m_addChildAction->setEnabled(false);
84✔
723
    m_findUsageAction->setEnabled(false);
84✔
724
  }
725
}
179✔
726

727
Device::FullAddressSettings make(const Device::Node& node)
×
728
{
UNCOV
729
  SCORE_ASSERT(node.is<Device::AddressSettings>());
×
730
  auto& other = node.get<Device::AddressSettings>();
×
731

732
  Device::FullAddressSettings as;
×
UNCOV
733
  static_cast<Device::AddressSettingsCommon&>(as) = other;
×
734
  as.address = Device::address(node).address;
×
735

UNCOV
736
  return as;
×
UNCOV
737
}
×
738

739
void DeviceExplorerWidget::updateAddressView()
137✔
740
{
741
  auto indexes = m_ntView->selectedIndexes();
137✔
742

743
  if(indexes.size() != 1)
137✔
744
  {
745
    m_addressModel->clear();
137✔
746
    return;
137✔
747
  }
748

UNCOV
749
  auto& n = model()->nodeFromModelIndex(sourceIndex(indexes.first()));
×
750
  if(n.is<Device::AddressSettings>())
×
751
  {
752
    m_addressModel->setState(model(), Device::NodePath(n), make(n));
×
UNCOV
753
    m_addressView->resizeRowsToContents();
×
UNCOV
754
  }
×
755
  else
756
  {
UNCOV
757
    m_addressModel->clear();
×
758
  }
759
}
137✔
760

761
bool DeviceExplorerWidget::editable() const noexcept
84✔
762
{
763
  return m_openMenu ? m_openMenu->isEnabled() : false;
84✔
764
}
765

766
DeviceExplorerModel* DeviceExplorerWidget::model() const
263✔
767
{
768
  return m_ntView->model();
263✔
769
}
770

771
DeviceExplorerView* DeviceExplorerWidget::view() const
104✔
772
{
773
  return m_ntView;
104✔
774
}
775

776
DeviceExplorerFilterProxyModel* DeviceExplorerWidget::proxyModel()
43✔
777
{
778
  return m_proxyModel;
43✔
779
}
780

781
void DeviceExplorerWidget::edit()
×
782
{
UNCOV
783
  if(!editable())
×
784
    return;
×
785

UNCOV
786
  const auto& select = model()->nodeFromModelIndex(m_ntView->selectedIndex());
×
787
  if(select.is<Device::DeviceSettings>())
×
788
  {
789
    if(!m_deviceDialog)
×
790
    {
791
      m_deviceDialog = new DeviceEditDialog{
×
792
          *model(), m_protocolList, DeviceEditDialog::Editing, this};
×
793
    }
×
UNCOV
794
    auto set = select.get<Device::DeviceSettings>();
×
795
    m_deviceDialog->setSettings(set);
×
796

797
    QDialog::DialogCode code = static_cast<QDialog::DialogCode>(m_deviceDialog->exec());
×
798

799
    if(code == QDialog::Accepted)
×
800
    {
801
      auto proto = m_protocolList.get(set.protocol);
×
802
      if(!proto)
×
803
        return;
×
UNCOV
804
      auto flags = proto->flags();
×
UNCOV
805
      if(flags & Device::ProtocolFactory::EditingReloadsEverything)
×
806
      {
807
        using namespace Command;
808
        MacroCommandDispatcher<UpdateAndReloadMacro> disp{m_cmdDispatcher->stack()};
×
809
        disp.submit(new Remove(model()->deviceModel(), select));
×
810
        disp.submit(
×
811
            new LoadDevice(model()->deviceModel(), m_deviceDialog->getSettings()));
×
UNCOV
812
        disp.commit();
×
UNCOV
813
      }
×
814
      else
815
      {
UNCOV
816
        auto cmd = new Explorer::Command::UpdateDeviceSettings{
×
817
            model()->deviceModel(), set.name, m_deviceDialog->getSettings()};
×
818

819
        m_cmdDispatcher->submit(cmd);
×
820
      }
821
    }
×
822

823
    updateActions();
×
824

825
    m_deviceDialog->deleteLater();
×
UNCOV
826
    m_deviceDialog = nullptr;
×
UNCOV
827
  }
×
828
  else
829
  {
830
    auto before = select.get<Device::AddressSettings>();
×
831

UNCOV
832
    if(!model())
×
833
      return;
×
834

835
    auto dev_s = getDevice(select);
×
836
    if(!dev_s)
×
837
      return;
×
838
    auto proto = m_protocolList.get(dev_s->protocol);
×
839
    if(!proto)
×
840
      return;
×
841
    auto dev = model()->deviceModel().list().findDevice(dev_s->name);
×
UNCOV
842
    if(!dev)
×
843
      return;
×
844

UNCOV
845
    auto dial = proto->makeEditAddressDialog(
×
846
        before, *dev, model()->deviceModel().context(), this);
×
847

UNCOV
848
    if(!dial)
×
849
      return;
×
850

851
    connect(
×
852
        dial, &QDialog::accepted, this,
×
UNCOV
853
        [this, dial, parent = select.parent(), path = Device::NodePath{select}, before] {
×
854
      auto stgs = dial->getSettings();
×
855
      // TODO do like for DeviceSettings
UNCOV
856
      if(!model()->checkAddressEditable(*parent, before, stgs))
×
857
        return;
×
858

UNCOV
859
      auto cmd = new Explorer::Command::UpdateAddressSettings{
×
860
          model()->deviceModel(), path, stgs};
×
861

862
      m_cmdDispatcher->submit(cmd);
×
863
      updateActions();
×
UNCOV
864
      dial->deleteLater();
×
865
        });
×
866

867
    connect(dial, &QDialog::rejected, this, [this, dial] {
×
868
      updateActions();
×
UNCOV
869
      dial->deleteLater();
×
870
    });
×
871

872
    dial->show();
×
UNCOV
873
  }
×
874
}
×
875

876
void DeviceExplorerWidget::refresh()
×
877
{
UNCOV
878
  if(!editable())
×
879
    return;
×
880

881
  auto m = model();
×
UNCOV
882
  if(!m)
×
883
    return;
×
884

UNCOV
885
  const auto& select = m->nodeFromModelIndex(m_ntView->selectedIndex());
×
UNCOV
886
  if(select.is<Device::DeviceSettings>())
×
887
  {
888
    // Create a thread, ask the device, when it is done put a command on the
889
    // chain.
890
    auto& dev
×
891
        = m->deviceModel().list().device(select.get<Device::DeviceSettings>().name);
×
UNCOV
892
    if(!dev.capabilities().canRefreshTree)
×
893
      return;
×
894

895
    if(!dev.connected())
×
896
      return;
×
897
    auto wrkr = make_worker(
×
898
        [this, m](Device::Node&& node) {
×
UNCOV
899
      auto cmd = new Explorer::Command::ReplaceDevice{
×
900
          m->deviceModel(), m_ntView->selectedIndex().row(), std::move(node)};
×
901

902
      m_cmdDispatcher->submit(cmd);
×
UNCOV
903
        },
×
904
        *this, dev);
×
905

906
    wrkr->start();
×
UNCOV
907
  }
×
908
}
×
909

910
void DeviceExplorerWidget::refreshValue()
×
911
{
UNCOV
912
  if(!editable())
×
UNCOV
913
    return;
×
914

915
  // TODO deprecate this
916
  QList<QPair<const Device::Node*, ossia::value>> lst;
×
917

918
  auto expl = model();
×
919

UNCOV
920
  const auto& indices = m_ntView->selectedIndexes();
×
UNCOV
921
  for(auto index : indices)
×
922
  {
923
    // Model checks
924
    index = sourceIndex(index);
×
UNCOV
925
    Device::Node* node = index.isValid()
×
UNCOV
926
                             ? static_cast<Device::Node*>(index.internalPointer())
×
927
                             : nullptr;
928

UNCOV
929
    if(!node || node->is<Device::DeviceSettings>())
×
UNCOV
930
      continue;
×
931

932
    // Device checks
933
    auto addr = Device::address(*node);
×
934
    auto& dev = model()->deviceModel().list().device(addr.address.device);
×
935
    if(!dev.capabilities().canRefreshValue)
×
936
      return;
×
UNCOV
937
    if(!dev.connected())
×
UNCOV
938
      return;
×
939

940
    // Getting the new values
UNCOV
941
    auto val = dev.refresh(addr.address);
×
942
    if(val)
×
943
    {
944
      expl->editData(*node, Explorer::Column::Value, *val, Qt::EditRole);
×
945
    }
×
UNCOV
946
  }
×
947
}
×
948

949
void DeviceExplorerWidget::disconnect()
×
950
{
UNCOV
951
  if(!editable())
×
952
    return;
×
953

954
  auto m = model();
×
955
  if(!m)
×
956
    return;
×
UNCOV
957
  int selectionSize = m_ntView->selectedIndexes().size();
×
958
  for(int i = 0; i < selectionSize; i++)
×
959
  {
UNCOV
960
    const Device::Node& select = m->nodeFromModelIndex(
×
961
        proxyModel()->mapToSource(m_ntView->selectedIndexes().at(i)));
×
962

963
    if(select.is<Device::DeviceSettings>())
×
964
    {
965
      auto& dev
×
966
          = m->deviceModel().list().device(select.get<Device::DeviceSettings>().name);
×
967
      dev.disconnect();
×
968
    }
×
UNCOV
969
  }
×
970
}
×
971

972
void DeviceExplorerWidget::reconnect()
×
973
{
UNCOV
974
  if(!editable())
×
975
    return;
×
976

977
  auto m = model();
×
978
  if(!m)
×
979
    return;
×
UNCOV
980
  int selectionSize = m_ntView->selectedIndexes().size();
×
UNCOV
981
  for(int i = 0; i < selectionSize; i++)
×
982
  {
983

984
    const Device::Node& select = m->nodeFromModelIndex(
×
UNCOV
985
        proxyModel()->mapToSource(m_ntView->selectedIndexes().at(i)));
×
986
    if(select.is<Device::DeviceSettings>())
×
987
    {
988
      auto& dev
×
989
          = m->deviceModel().list().device(select.get<Device::DeviceSettings>().name);
×
990
      auto con_handle = std::make_shared<QMetaObject::Connection>();
×
991
      *con_handle = con(
×
992
          dev, &Device::DeviceInterface::deviceChanged, this,
×
UNCOV
993
          [&dev, con_handle, select](auto oldd, auto newd) {
×
994
        if(newd)
×
995
        {
996
          dev.recreate(select);
×
997
          QObject::disconnect(*con_handle);
×
998
        }
×
999
          });
×
1000
      dev.reconnect();
×
1001
    }
×
UNCOV
1002
  }
×
1003
}
×
1004

1005
void DeviceExplorerWidget::addDevice()
×
1006
{
1007
  if(!m_deviceDialog)
×
1008
  {
1009
    m_deviceDialog = new DeviceEditDialog{
×
UNCOV
1010
        *model(), m_protocolList, DeviceEditDialog::Creating, this};
×
1011
  }
×
1012

1013
  connect(m_deviceDialog, &QDialog::accepted, this, [this] {
×
UNCOV
1014
    if(!m_deviceDialog)
×
1015
      return;
×
1016

1017
    SCORE_ASSERT(model());
×
1018
    auto node = m_deviceDialog->getDevice();
×
UNCOV
1019
    auto& deviceSettings = *node.target<Device::DeviceSettings>();
×
1020
    if(!model()->checkDeviceInstantiatable(deviceSettings))
×
1021
    {
1022
      m_deviceDialog->deleteLater();
×
UNCOV
1023
      m_deviceDialog = nullptr;
×
1024
      return;
×
1025
    }
1026
    ossia::net::sanitize_device_name(deviceSettings.name);
×
1027

1028
    auto& devplug = model()->deviceModel();
×
1029
    QTimer::singleShot(1, this, [this, n = std::move(node), &devplug]() mutable {
×
1030
      blockGUI(true);
×
1031
      m_cmdDispatcher->submit(new Command::LoadDevice{devplug, std::move(n)});
×
UNCOV
1032
      blockGUI(false);
×
1033
    });
×
1034

1035
    updateActions();
×
1036
    m_deviceDialog->deleteLater();
×
UNCOV
1037
    m_deviceDialog = nullptr;
×
1038
  });
×
1039

1040
  connect(m_deviceDialog, &QDialog::rejected, this, [this] {
×
1041
    updateActions();
×
1042
    m_deviceDialog->deleteLater();
×
UNCOV
1043
    m_deviceDialog = nullptr;
×
1044
  });
×
1045

UNCOV
1046
  m_deviceDialog->show();
×
1047
}
×
1048

1049
void DeviceExplorerWidget::importDevice()
×
1050
{
1051
  auto m = model();
×
UNCOV
1052
  if(!m)
×
1053
    return;
×
1054

1055
  auto fileName = QFileDialog::getOpenFileName(
×
1056
      this, tr("Device file"), QString{}, tr("Device file (*.device)"));
×
UNCOV
1057
  if(fileName.isEmpty())
×
1058
    return;
×
1059

1060
  Device::Node n;
×
UNCOV
1061
  if(!Device::loadDeviceFromScoreJSON(fileName, n))
×
1062
    return;
×
1063

UNCOV
1064
  if(!n.is<Device::DeviceSettings>())
×
1065
    return;
×
1066

UNCOV
1067
  auto& deviceSettings = n.get<Device::DeviceSettings>();
×
1068

1069
  // Check that this protocol is known
1070
  auto prot = m_protocolList.get(deviceSettings.protocol);
×
UNCOV
1071
  if(!prot)
×
1072
    return;
×
1073

UNCOV
1074
  if(!m->checkDeviceInstantiatable(deviceSettings))
×
1075
    return;
×
1076

1077
  ossia::net::sanitize_device_name(deviceSettings.name);
×
1078

1079
  auto& devplug = m->deviceModel();
×
1080
  blockGUI(true);
×
1081
  m_cmdDispatcher->submit(new Command::LoadDevice{devplug, std::move(n)});
×
UNCOV
1082
  blockGUI(false);
×
1083
}
×
1084

1085
void DeviceExplorerWidget::exportDevice()
×
1086
{
1087
  auto indexes = m_ntView->selectedIndexes();
×
1088

1089
  if(indexes.size() != 1)
×
1090
    return;
×
1091
  Device::Node& n = model()->nodeFromModelIndex(sourceIndex(indexes.first()));
×
UNCOV
1092
  if(!n.is<Device::DeviceSettings>())
×
1093
    return;
×
1094

1095
  auto fileName = QFileDialog::getSaveFileName(
×
1096
      this, tr("Device file"), QString{}, tr("Device file (*.device)"));
×
UNCOV
1097
  if(!fileName.endsWith(".device"))
×
1098
    fileName.append(".device");
×
1099

UNCOV
1100
  QFile f{fileName};
×
1101
  if(f.open(QIODevice::WriteOnly))
×
1102
  {
1103
    f.write(toJson(n));
×
UNCOV
1104
  }
×
1105
}
×
1106

1107
void DeviceExplorerWidget::addChild()
×
1108
{
UNCOV
1109
  addAddress(InsertMode::AsChild);
×
1110
}
×
1111

1112
void DeviceExplorerWidget::addSibling()
×
1113
{
UNCOV
1114
  addAddress(InsertMode::AsSibling);
×
1115
}
×
1116

1117
void DeviceExplorerWidget::removeNodes()
×
1118
{
UNCOV
1119
  if(!editable())
×
1120
    return;
×
1121

1122
  auto indexes = m_ntView->selectedIndexes();
×
1123

UNCOV
1124
  Device::NodeList nodes;
×
1125
  for(auto index : indexes)
×
1126
  {
1127
    auto& n = model()->nodeFromModelIndex(sourceIndex(index));
×
UNCOV
1128
    if(!n.is<InvisibleRootNode>())
×
UNCOV
1129
      nodes.push_back(&n);
×
1130
  }
1131

UNCOV
1132
  auto cmd = new Command::RemoveNodes;
×
UNCOV
1133
  const auto& dev_model = model()->deviceModel();
×
1134

1135
  // If two nodes have the same parent,
1136
  // we should send the commands in reverse order
1137
  // (from the last to the first)
1138
  // so that they are emplaced in correct order afterwards.
1139
  // IMPORTANT ! don't use emplace, only emplace_back in D.E. model
1140
  struct PathComparator
1141
  {
UNCOV
1142
    bool operator()(const Device::NodePath& lhs, const Device::NodePath& rhs) const
×
1143
    {
1144
      // We iterate on the shorter.
1145
      // The shorter is considered "smaller" : it comes before.
1146
      int l_size = std::ssize(lhs);
×
UNCOV
1147
      int r_size = std::ssize(rhs);
×
UNCOV
1148
      if(l_size < r_size)
×
1149
      {
1150
        // lhs shorter
1151
        for(int i = 0; i < l_size; i++)
×
1152
        {
1153
          if(lhs[i] < rhs[i])
×
1154
            return true;
×
1155
          else if(lhs[i] == rhs[i])
×
1156
            continue;
×
1157
          else if(lhs[i] > rhs[i])
×
1158
            return false;
×
UNCOV
1159
        }
×
1160
        return true;
×
1161
      }
1162
      else if(l_size == r_size)
×
1163
      {
1164
        for(int i = 0; i < l_size; i++)
×
1165
        {
1166
          if(lhs[i] < rhs[i])
×
1167
            return true;
×
1168
          else if(lhs[i] == rhs[i])
×
1169
            continue;
×
1170
          else if(lhs[i] > rhs[i])
×
1171
            return false;
×
1172
        }
×
UNCOV
1173
        SCORE_ABORT;
×
UNCOV
1174
      }
×
1175
      else
1176
      {
1177
        // rhs shorter
1178
        for(int i = 0; i < r_size; i++)
×
1179
        {
1180
          if(lhs[i] < rhs[i])
×
1181
            return true;
×
1182
          else if(lhs[i] == rhs[i])
×
1183
            continue;
×
1184
          else if(lhs[i] > rhs[i])
×
1185
            return false;
×
UNCOV
1186
        }
×
UNCOV
1187
        return false;
×
1188
      }
1189

UNCOV
1190
      SCORE_ABORT;
×
UNCOV
1191
    }
×
1192
  };
1193

UNCOV
1194
  ossia::flat_set<Device::NodePath, PathComparator> paths;
×
1195
  for(const auto& n : filterUniqueParents(nodes))
×
1196
  {
1197
    if(n->is<Device::DeviceSettings>())
×
1198
    {
UNCOV
1199
      cmd->addCommand(new Explorer::Command::Remove{dev_model, *n});
×
UNCOV
1200
    }
×
1201
    else
1202
    {
UNCOV
1203
      paths.insert(*n);
×
1204
    }
1205
  }
1206
  /*
1207
      for(auto path : paths)
1208
      {
1209
          qDebug() << path;
1210
      }
1211
  */
1212
  for(auto it = paths.rbegin(); it != paths.rend(); ++it)
×
1213
  {
UNCOV
1214
    cmd->addCommand(new Explorer::Command::Remove{dev_model, Device::NodePath{*it}});
×
1215
  }
×
1216

UNCOV
1217
  m_cmdDispatcher->submit(cmd);
×
1218
}
×
1219

1220
void DeviceExplorerWidget::learn()
×
1221
{
UNCOV
1222
  if(!editable())
×
UNCOV
1223
    return;
×
1224

1225
  // Get the device
1226
  auto indexes = m_ntView->selectedIndexes();
×
1227

UNCOV
1228
  if(indexes.size() != 1)
×
1229
    return;
×
1230

1231
  auto m = model();
×
1232
  Device::Node& n = m->nodeFromModelIndex(sourceIndex(indexes.first()));
×
1233
  if(!n.is<Device::DeviceSettings>())
×
1234
    return;
×
1235
  auto di = m->deviceModel().list().findDevice(n.get<Device::DeviceSettings>().name);
×
UNCOV
1236
  if(!di)
×
1237
    return;
×
1238

UNCOV
1239
  if(!di->capabilities().canLearn)
×
1240
    return;
×
1241

UNCOV
1242
  if(!di->connected())
×
UNCOV
1243
    return;
×
1244

1245
  // Make a copy of the node
1246
  Device::Node oldDevice = n;
×
1247
  // Show a dialog for as long as there is learn status active
1248
  auto d = new LearnDialog{*di, this};
×
1249

UNCOV
1250
  auto res = d->exec();
×
UNCOV
1251
  delete d; // Stops learning
×
1252

1253
  // Create a command and push it if we agree, undo it and don't push it if we
1254
  // refuse
UNCOV
1255
  Device::Node newDevice = n;
×
UNCOV
1256
  if(res)
×
1257
  {
1258
    // Create a command with the current state of the device
UNCOV
1259
    auto cmd = new Explorer::Command::ReloadWholeDevice{
×
UNCOV
1260
        m->deviceModel(), std::move(oldDevice), std::move(newDevice)};
×
1261

1262
    // Push it without redoing it since the device already has the nodes
UNCOV
1263
    CommandDispatcher<SendStrategy::Quiet> disp{m_cmdDispatcher->stack()};
×
UNCOV
1264
    disp.submit(cmd);
×
1265

1266
    // This way we're able to undo the learn operation
UNCOV
1267
  }
×
1268
  else
1269
  {
1270
    // We still have to rollback the messages that may have been received
UNCOV
1271
    Explorer::Command::ReloadWholeDevice cmd{
×
UNCOV
1272
        m->deviceModel(), std::move(oldDevice), std::move(newDevice)};
×
1273

1274
    // No need to push anything
1275
    cmd.undo(m_cmdDispatcher->stack().context());
×
UNCOV
1276
  }
×
1277
}
×
1278

UNCOV
1279
void DeviceExplorerWidget::findUsage()
×
1280
{
1281

1282
  auto indexes = m_ntView->selectedIndexes();
×
1283

UNCOV
1284
  QStringList search_txt;
×
1285
  for(auto index : indexes)
×
1286
  {
1287
    auto& n = model()->nodeFromModelIndex(sourceIndex(index));
×
1288

1289
    State::AddressAccessor address = Device::address(n);
×
1290

1291
    search_txt.push_back(address.address.toString());
×
1292
  }
×
UNCOV
1293
  findAddresses(search_txt);
×
1294
}
×
1295

UNCOV
1296
void DeviceExplorerWidget::do_addAddress(
×
1297
    InsertMode insert, QModelIndex index, Device::Node* parent,
1298
    Device::AddressSettings& stgs)
1299
{
1300
  // TODO checking for expansion should not be necessary anymore
UNCOV
1301
  const bool parent_is_expanded = m_ntView->isExpanded(
×
1302
      proxyIndex(m_ntView->model()->modelIndexFromNode(*parent, 0)));
×
1303

UNCOV
1304
  m_cmdDispatcher->submit(new Explorer::Command::AddAddress{
×
UNCOV
1305
      model()->deviceModel(), Device::NodePath{index}, insert, stgs});
×
1306

1307
  // If the node is going to be visible, we have to start listening to it.
1308
  if(parent_is_expanded && m_listeningManager)
×
1309
  {
UNCOV
1310
    auto child_it = ossia::find_if(*parent, [&](const Device::Node& child) {
×
1311
      return child.get<Device::AddressSettings>().name == stgs.name;
×
1312
    });
1313
    SCORE_ASSERT(child_it != parent->end());
×
1314

1315
    m_listeningManager->enableListening(*child_it);
×
UNCOV
1316
  }
×
1317
}
×
1318

1319
void DeviceExplorerWidget::addAddress(InsertMode insert)
×
1320
{
UNCOV
1321
  if(!editable())
×
1322
    return;
×
1323

UNCOV
1324
  SCORE_ASSERT(model());
×
UNCOV
1325
  QModelIndex index = proxyModel()->mapToSource(m_ntView->currentIndex());
×
1326

1327
  // If the node is added in sibling mode, we check that no sibling have
1328
  // the same name
1329
  // Else we check that no child of the index has the same name.
UNCOV
1330
  auto& node = model()->nodeFromModelIndex(index);
×
1331

1332
  // TODO not very elegant.
1333
  if(insert == InsertMode::AsSibling && node.is<Device::DeviceSettings>())
×
1334
  {
UNCOV
1335
    return;
×
1336
  }
1337

1338
  auto dev_s = getDevice(node);
×
1339
  if(!dev_s)
×
1340
    return;
×
1341
  auto proto = m_protocolList.get(dev_s->protocol);
×
1342
  if(!proto)
×
1343
    return;
×
1344
  auto dev = model()->deviceModel().list().findDevice(dev_s->name);
×
UNCOV
1345
  if(!dev)
×
1346
    return;
×
1347

UNCOV
1348
  QScopedPointer<Device::AddressDialog> dial{
×
1349
      proto->makeAddAddressDialog(*dev, model()->deviceModel().context(), this)};
×
1350

UNCOV
1351
  if(!dial)
×
1352
    return;
×
1353

UNCOV
1354
  dial->setParent(this);
×
1355
  auto code = static_cast<QDialog::DialogCode>(dial->exec());
×
1356

1357
  if(code == QDialog::Accepted)
×
1358
  {
1359
    Device::Node* parent = (insert == InsertMode::AsChild) ? &node : node.parent();
×
1360

UNCOV
1361
    auto stgs = dial->getSettings();
×
1362

1363
    // Clean-up the address entered by the user with some common typos
1364
    stgs.name = stgs.name.trimmed();
×
1365
    if(stgs.name.startsWith('/'))
×
1366
      stgs.name.remove(0, 1);
×
1367
    while(stgs.name.contains(QLatin1String("//")))
×
UNCOV
1368
      stgs.name.remove(QLatin1String("//"));
×
1369
    stgs.name = stgs.name.trimmed();
×
1370

1371
    auto addr_request = stgs.name.toStdString();
×
UNCOV
1372
    auto [names, is_brace_exp] = ossia::net::expand_address(addr_request);
×
1373
    if(is_brace_exp || stgs.name.contains('/'))
×
1374
    {
UNCOV
1375
      std::vector<QString> correct_names;
×
1376
      for(auto& name : names)
×
1377
      {
UNCOV
1378
        stgs.name = QString::fromStdString(name);
×
1379
        if(model()->checkAddressInstantiatable(*parent, stgs))
×
1380
        {
UNCOV
1381
          correct_names.push_back(std::move(stgs.name));
×
UNCOV
1382
        }
×
1383
      }
1384

UNCOV
1385
      if(!correct_names.empty())
×
1386
      {
1387
        // bool parent_is_expanded = m_ntView->isExpanded(
1388
        //     proxyIndex(m_ntView->model()->modelIndexFromNode(*parent, 0)));
1389

UNCOV
1390
        auto cmd = new Explorer::Command::AddAddresses{};
×
1391
        for(auto& name : correct_names)
×
1392
        {
1393
          stgs.name = name;
×
UNCOV
1394
          cmd->addCommand(new Explorer::Command::AddAddress{
×
1395
              model()->deviceModel(), Device::NodePath{index}, insert, stgs});
×
1396
        }
UNCOV
1397
        this->m_cmdDispatcher->submit(cmd);
×
1398

1399
        // If the node is going to be visible, we have to start listening to it.
1400
        // FIXME
1401
        // if (parent_is_expanded && m_listeningManager)
1402
        // {
1403
        //   for(auto& name : correct_names)
1404
        //   {
1405
        //     auto child_it = ossia::find_if(*parent, [&](const Device::Node& child) {
1406
        //       return child.get<Device::AddressSettings>().name == stgs.name;
1407
        //     });
1408
        //     SCORE_ASSERT(child_it != parent->end());
1409
        //
1410
        //     m_listeningManager->enableListening(*child_it);
1411
        //   }
1412
        // }
UNCOV
1413
      }
×
UNCOV
1414
    }
×
1415
    else
1416
    {
UNCOV
1417
      if(!model()->checkAddressInstantiatable(*parent, stgs))
×
1418
        return;
×
1419

1420
      do_addAddress(insert, index, parent, stgs);
×
1421
    }
1422
    updateActions();
×
UNCOV
1423
  }
×
UNCOV
1424
}
×
1425

1426
DeviceExplorerWidget*
1427
findDeviceExplorerWidgetInstance(const score::GUIApplicationContext& ctx) noexcept
145✔
1428
{
1429
  // findPanel tolerates empty slots in the panel list (indirect iteration
1430
  // dereferences unconditionally and asserts on hardened stdlibs).
1431
  if(auto* panel = ctx.components.findPanel<Explorer::PanelDelegate>())
145✔
1432
  {
1433
    return static_cast<Explorer::DeviceExplorerWidget*>(panel->widget());
122✔
1434
  }
1435
  return nullptr;
23✔
1436
}
145✔
1437

1438
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc