• 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

28.87
/src/plugins/score-plugin-deviceexplorer/Explorer/Explorer/DeviceExplorerModel.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 "DeviceExplorerModel.hpp"
4

5
#include <State/MessageListSerialization.hpp>
6
#include <State/ValueConversion.hpp>
7

8
#include <Device/Address/AddressSettings.hpp>
9
#include <Device/ItemModels/NodeBasedItemModel.hpp>
10
#include <Device/ItemModels/NodeDisplayMethods.hpp>
11
#include <Device/Node/DeviceNode.hpp>
12
#include <Device/Node/NodeListMimeSerialization.hpp>
13
#include <Device/Protocol/DeviceSettings.hpp>
14
#include <Device/Protocol/ProtocolFactoryInterface.hpp>
15
#include <Device/Protocol/ProtocolList.hpp>
16

17
#include <Explorer/Commands/Add/LoadDevice.hpp>
18
#include <Explorer/Commands/Update/UpdateAddressSettings.hpp>
19
#include <Explorer/DeviceList.hpp>
20
#include <Explorer/DocumentPlugin/DeviceDocumentPlugin.hpp>
21
#include <Explorer/DocumentPlugin/NodeUpdateProxy.hpp>
22
#include <Explorer/Explorer/DeviceExplorerMimeTypes.hpp>
23
#include <Explorer/Explorer/DeviceExplorerView.hpp>
24
#include <Explorer/Explorer/Widgets/DeviceEditDialog.hpp> // TODO why here??!!
25

26
#include <score/application/ApplicationContext.hpp>
27
#include <score/application/GUIApplicationContext.hpp>
28
#include <score/command/CommandStackFacade.hpp>
29
#include <score/document/DocumentContext.hpp>
30
#include <score/document/DocumentInterface.hpp>
31
#include <score/model/path/Path.hpp>
32
#include <score/model/tree/TreeNode.hpp>
33
#include <score/model/tree/TreeNodeSerialization.hpp>
34
#include <score/plugins/InterfaceList.hpp>
35
#include <score/plugins/StringFactoryKey.hpp>
36
#include <score/serialization/JSONVisitor.hpp>
37
#include <score/serialization/MimeVisitor.hpp>
38
#include <score/widgets/MessageBox.hpp>
39

40
#include <ossia/detail/ssize.hpp>
41
#include <ossia/network/base/node_attributes.hpp>
42
#include <ossia/network/common/destination_qualifiers.hpp>
43
#include <ossia/network/domain/domain.hpp>
44

45
#include <ossia-qt/name_utils.hpp>
46

47
#include <QAbstractProxyModel>
48
#include <QApplication>
49
#include <QMainWindow>
50
#include <QMap>
51
#include <QMimeData>
52
#include <QObject>
53
#include <QTreeView>
54

55
#include <wobjectimpl.h>
56

57
#include <iostream>
58
#include <string>
59
#include <vector>
60
W_OBJECT_IMPL(Explorer::DeviceExplorerModel)
3,363✔
61
namespace Explorer
62
{
63
static const QMap<Explorer::Column, QString> HEADERS{
59✔
64
    {Explorer::Column::Name, QObject::tr("Address")},
59✔
65
    {Explorer::Column::Value, QObject::tr("Value")}};
59✔
66

67
DeviceExplorerModel::DeviceExplorerModel(DeviceDocumentPlugin& plug, QObject* parent)
63✔
68
    : NodeBasedItemModel{parent}
63✔
69
    , m_devicePlugin{plug}
63✔
70
    , m_rootNode{plug.rootNode()}
63✔
71
    , m_cmdQ{plug.context().commandStack}
63✔
72
{
63✔
73
  this->setObjectName("DeviceExplorerModel");
63✔
74

75
  beginResetModel();
63✔
76
  endResetModel();
63✔
77
}
×
78

79
DeviceExplorerModel::~DeviceExplorerModel() { }
126✔
80

81
DeviceDocumentPlugin& DeviceExplorerModel::deviceModel() const
214✔
82
{
83
  return m_devicePlugin;
214✔
84
}
85

86
QModelIndexList DeviceExplorerModel::selectedIndexes() const
×
87
{
88
  if(!m_view)
×
89
  {
90
    return {};
×
91
  }
92
  else
93
  {
94
    // We have to do this check if we have a proxy
95
    if(m_view->hasProxy())
×
96
    {
97
      auto indexes = m_view->selectedIndexes();
×
98
      for(auto& index : indexes)
×
99
        index = static_cast<const QAbstractProxyModel*>(m_view->QTreeView::model())
×
100
                    ->mapToSource(index);
×
101
      return indexes;
×
102
    }
×
103
    else
104
    {
105
      return m_view->selectedIndexes();
×
106
    }
107
  }
108
}
×
109

110
QStringList DeviceExplorerModel::getColumns() const
42✔
111
{
112
  return HEADERS.values();
42✔
113
}
114

115
int DeviceExplorerModel::addDevice(const Device::Node& deviceNode)
×
116
{
117
  int row = m_rootNode.childCount();
×
118
  QModelIndex parent; // invalid
×
119

120
  beginInsertRows(parent, row, row);
×
121
  rootNode().push_back(deviceNode);
×
122
  endInsertRows();
×
123

124
  return row;
×
125
}
126

127
int DeviceExplorerModel::addDevice(Device::Node&& deviceNode)
19✔
128
{
129
  deviceNode.setParent(&rootNode());
19✔
130

131
  int row = m_rootNode.childCount();
19✔
132
  QModelIndex parent; // invalid
19✔
133

134
  beginInsertRows(parent, row, row);
19✔
135
  rootNode().push_back(std::move(deviceNode));
19✔
136
  endInsertRows();
19✔
137

138
  return row;
19✔
139
}
140

141
void DeviceExplorerModel::updateDevice(
×
142
    const QString& name, const Device::DeviceSettings& dev)
143
{
144
  int i = 0;
×
145
  for(auto& n : m_rootNode)
×
146
  {
147
    if(n.get<Device::DeviceSettings>().name == name)
×
148
    {
149
      n.set(dev);
×
150

151
      QModelIndex index = createIndex(i, 0, n.parent());
×
152
      dataChanged(index, index);
×
153
      return;
×
154
    }
155
    i++;
×
156
  }
157
}
×
158

159
Device::Node* DeviceExplorerModel::addAddress(
×
160
    Device::Node* parentNode, const Device::AddressSettings& addressSettings, int row)
161
{
162
  SCORE_ASSERT(parentNode);
×
163
  SCORE_ASSERT(parentNode != &m_rootNode);
×
164

165
  Device::Node* grandparent = parentNode->parent();
×
166
  SCORE_ASSERT(grandparent);
×
167
  int rowParent = grandparent->indexOfChild(parentNode);
×
168
  QModelIndex parentIndex = createIndex(rowParent, 0, parentNode);
×
169

170
  beginInsertRows(parentIndex, row, row);
×
171

172
  auto it = parentNode->begin();
×
173
  std::advance(it, row);
×
174

175
  auto& ret = parentNode->emplace(it, addressSettings, parentNode);
×
176

177
  endInsertRows();
×
178

179
  return &ret;
×
180
}
181

182
void DeviceExplorerModel::addNode(
×
183
    Device::Node* parentNode, Device::Node&& child, int row)
184
{
185
  SCORE_ASSERT(parentNode);
×
186
  SCORE_ASSERT(parentNode != &m_rootNode);
×
187

188
  Device::Node* grandparent = parentNode->parent();
×
189
  SCORE_ASSERT(grandparent);
×
190
  int rowParent = grandparent->indexOfChild(parentNode);
×
191
  QModelIndex parentIndex = createIndex(rowParent, 0, parentNode);
×
192

193
  beginInsertRows(parentIndex, row, row);
×
194

195
  auto it = parentNode->begin();
×
196
  std::advance(it, row);
×
197
  parentNode->emplace(it, std::move(child));
×
198

199
  endInsertRows();
×
200
}
×
201

202
void DeviceExplorerModel::updateAddress(
10✔
203
    Device::Node* node, const Device::AddressSettings& addressSettings)
204
{
205
  SCORE_ASSERT(node);
10✔
206
  SCORE_ASSERT(node != &m_rootNode);
10✔
207

208
  node->set(addressSettings);
10✔
209

210
  nodeChanged(node);
10✔
211

212
  dataChanged(
10✔
213
      modelIndexFromNode(*node, 0), modelIndexFromNode(*node, (int)Column::Count - 1));
10✔
214
}
10✔
215

216
void DeviceExplorerModel::updateValue(
1✔
217
    Device::Node* n, const State::AddressAccessor& addr, const ossia::value& v)
218
{
219
  if(!addr.qualifiers.get().accessors.empty())
1✔
220
  {
221
    SCORE_TODO;
×
222
  }
×
223
  n->get<Device::AddressSettings>().value = v;
1✔
224

225
  QModelIndex nodeIndex = modelIndexFromNode(*n, 1);
1✔
226

227
  dataChanged(nodeIndex, nodeIndex);
1✔
228
}
1✔
229

230
bool DeviceExplorerModel::checkDeviceInstantiatable(
×
231
    const Device::DeviceSettings& n) const
232
{
233
  // No name -> no love
234
  if(n.name.isEmpty())
×
235
    return false;
×
236

237
  // Request from the protocol factory the protocol to see
238
  // if it is compatible.
239
  auto& context = m_devicePlugin.context().app;
×
240
  auto prot = context.interfaces<Device::ProtocolFactoryList>().get(n.protocol);
×
241
  if(!prot)
×
242
    return false;
×
243

244
  // Look for other childs in the same protocol.
245
  bool none_incompatible = std::none_of(
×
246
      rootNode().begin(), rootNode().end(), [&](const Device::Node& child) {
×
247
    SCORE_ASSERT(child.is<Device::DeviceSettings>());
×
248
    const auto& set = child.get<Device::DeviceSettings>();
×
249
    return (set.name == n.name)
×
250
           || (set.protocol
×
251
                   == n.protocol // FIXME distinct protocols could use the same port...
×
252
               && !prot->checkCompatibility(n, child.get<Device::DeviceSettings>()));
×
253
  });
254
  if(!none_incompatible)
×
255
    return false;
×
256

257
  // Check compatibility with an empty device
258
  return prot->checkCompatibility(n, Device::DeviceSettings{});
×
259
}
×
260

261
bool DeviceExplorerModel::checkDeviceEditable(
×
262
    const QString& originalName, const Device::DeviceSettings& n) const
263
{
264
  // No name -> no love
265
  if(n.name.isEmpty())
×
266
    return false;
×
267

268
  // Request from the protocol factory the protocol to see
269
  // if it is compatible.
270
  auto& context = m_devicePlugin.context().app;
×
271
  auto prot = context.interfaces<Device::ProtocolFactoryList>().get(n.protocol);
×
272
  if(!prot)
×
273
    return false;
×
274

275
  // Look for other childs in the same protocol.
276
  bool none_incompatible = std::none_of(
×
277
      rootNode().begin(), rootNode().end(), [&](const Device::Node& child) {
×
278
    SCORE_ASSERT(child.is<Device::DeviceSettings>());
×
279
    const auto& set = child.get<Device::DeviceSettings>();
×
280
    // Ignore the device that is being edited
281
    if(set.name == originalName)
×
282
      return false;
×
283

284
    return (set.name == n.name)
×
285
           || (set.protocol == n.protocol
×
286
               && !prot->checkCompatibility(n, child.get<Device::DeviceSettings>()));
×
287
  });
×
288
  if(!none_incompatible)
×
289
    return false;
×
290

291
  // Check compatibility with an empty device
292
  return prot->checkCompatibility(n, Device::DeviceSettings{});
×
293
}
×
294

295
bool DeviceExplorerModel::checkAddressInstantiatable(
×
296
    Device::Node& parent, const Device::AddressSettings& addr)
297
{
298
  SCORE_ASSERT(!parent.is<InvisibleRootNode>());
×
299

300
  if(addr.name.isEmpty())
×
301
    return false;
×
302

303
  return std::none_of(parent.begin(), parent.end(), [&](const Device::Node& n) {
×
304
    return n.get<Device::AddressSettings>().name == addr.name;
×
305
  });
306
}
×
307

308
bool DeviceExplorerModel::checkAddressEditable(
6✔
309
    Device::Node& parent, const Device::AddressSettings& before,
310
    const Device::AddressSettings& after)
311
{
312
  SCORE_ASSERT(!parent.is<InvisibleRootNode>());
6✔
313

314
  if(after.name.isEmpty())
6✔
315
    return false;
×
316

317
  auto it = std::find_if(parent.begin(), parent.end(), [&](const Device::Node& n) {
17✔
318
    return n.get<Device::AddressSettings>().name == after.name;
11✔
319
  });
320
  if(it != parent.end())
6✔
321
  {
322
    //  We didn't change name, it's ok
323
    if(after.name == before.name)
4✔
324
      return true;
3✔
325
    else
326
      return false;
1✔
327
  }
328
  else
329
  {
330
    // Ok, no conflicts
331
    return true;
2✔
332
  }
333
}
6✔
334

335
bool DeviceExplorerModel::canRenameNode(const Device::Node& n) const
5✔
336
{
337
  if(!n.is<Device::AddressSettings>())
5✔
UNCOV
338
    return false;
×
339

340
  // Called for every visible cell: walk up to the device rather than building
341
  // the whole address just to read its first component.
342
  const Device::Node* root = &n;
5✔
343
  while(root->parent() && !root->is<Device::DeviceSettings>())
10✔
344
    root = root->parent();
5✔
345

346
  if(!root->is<Device::DeviceSettings>())
5✔
UNCOV
347
    return false;
×
348

349
  auto* dev = m_devicePlugin.list().findDevice(root->get<Device::DeviceSettings>().name);
5✔
350
  return dev && dev->capabilities().canRenameNode;
5✔
351
}
5✔
352

353
int DeviceExplorerModel::columnCount() const
42✔
354
{
355
  return (int)Column::Count;
42✔
356
}
357

358
int DeviceExplorerModel::columnCount(const QModelIndex& /*parent*/) const
229✔
359
{
360
  return (int)Column::Count;
229✔
361
}
362

363
// must return an invalid QVariant for cases not handled
364
QVariant DeviceExplorerModel::data(const QModelIndex& index, int role) const
60✔
365
{
366
  const int col = index.column();
60✔
367

368
  if(col < 0 || col >= (int)Column::Count)
60✔
369
  {
UNCOV
370
    return QVariant();
×
371
  }
372

373
  const Device::Node& n = nodeFromModelIndex(index);
60✔
374
  if(role != Qt::ToolTipRole)
60✔
375
  {
376
    switch((Column)col)
60✔
377
    {
378
      case Column::Name: {
379
        if(n.is<Device::AddressSettings>())
60✔
380
          return Device::nameColumnData(n, role);
41✔
381
        else if(n.is<Device::DeviceSettings>())
19✔
382
        {
383
          auto& dev_set = n.get<Device::DeviceSettings>();
19✔
384
          return Device::deviceNameColumnData(
19✔
385
              n, deviceModel().list().device(dev_set.name).connected(), role);
19✔
386
        }
387
        return {};
×
388
      }
389

390
      case Column::Value:
UNCOV
391
        return Device::valueColumnData(n, role);
×
392

UNCOV
393
      case Column::Count:
×
394
      default:
395
        SCORE_ABORT;
×
396
        return {};
×
397
    }
398
  }
399
  else
400
  {
401
    // Tooltip
UNCOV
402
    if(n.is<Device::AddressSettings>())
×
403
    {
UNCOV
404
      auto& addr_set = n.get<Device::AddressSettings>();
×
UNCOV
405
      if(const auto& desc = ossia::net::get_description(addr_set))
×
UNCOV
406
        return QString::fromStdString(*desc);
×
407
    }
×
408
    else
409
    {
UNCOV
410
      return {};
×
411
    }
412
  }
UNCOV
413
  return {};
×
414
}
60✔
415

416
QVariant
417
DeviceExplorerModel::headerData(int section, Qt::Orientation orientation, int role) const
420✔
418
{
419
  if(orientation == Qt::Horizontal && role == Qt::DisplayRole)
420✔
420
  {
421
    if(section >= 0 && section < (int)Column::Count)
168✔
422
    {
423
      return HEADERS[(Column)section];
168✔
424
    }
425
  }
×
426

427
  return {};
252✔
428
}
420✔
429

430
Qt::ItemFlags DeviceExplorerModel::flags(const QModelIndex& index) const
30✔
431
{
432
  Qt::ItemFlags f = Qt::ItemIsEnabled;
30✔
433
  // by default QAbstractItemModel::flags(index); returns Qt::ItemIsEnabled |
434
  // Qt::ItemIsSelectable
435

436
  if(index.isValid())
30✔
437
  {
438
    const Device::Node& n = nodeFromModelIndex(index);
30✔
439

440
    if(n.isSelectable())
30✔
441
    {
442
      f |= Qt::ItemIsSelectable;
30✔
443
    }
30✔
444

445
    // we allow drag'n drop only from the name column
446
    if(index.column() == (int)Column::Name)
30✔
447
    {
448
      f |= Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
30✔
449
    }
30✔
450

451
    if(n.isEditable() && index.column() == (int)Column::Value)
30✔
452
    {
UNCOV
453
      f |= Qt::ItemIsEditable;
×
UNCOV
454
    }
×
455

456
    if(index.column() == (int)Column::Name && n.is<Device::AddressSettings>()
30✔
457
       && canRenameNode(n))
30✔
458
    {
459
      f |= Qt::ItemIsEditable;
1✔
460
    }
1✔
461
  }
30✔
462
  else
463
  {
464
    // to be able to drop even where there is nothing
UNCOV
465
    f |= Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
×
466
  }
467

468
  return f;
30✔
469
}
470

471
/*
472
  return false if no change was made.
473
  dataChanged() & return true if a change is made.
474

475
  Note: this is the function that gets called when the user changes the value
476
  in the tree.
477
  It then sends a command that calls editData.
478
*/
479
bool DeviceExplorerModel::setData(
8✔
480
    const QModelIndex& index, const QVariant& value, int role)
481
{
482
  if(!index.isValid())
8✔
UNCOV
483
    return false;
×
484

485
  auto& n = nodeFromModelIndex(index);
8✔
486

487
  if(!n.is<Device::AddressSettings>())
8✔
488
    return false;
×
489

490
  auto col = Explorer::Column(index.column());
8✔
491

492
  if(role == Qt::EditRole || role == Qt::CheckStateRole)
8✔
493
  {
494
    if(col == Column::Value)
8✔
495
    {
496
      // In this case we don't make a command, but we directly push the
497
      // new value.
498
      auto copy = value.canConvert<ossia::value>() ? value.value<ossia::value>()
5✔
499
                                                   : State::convert::fromQVariant(value);
×
500

501
      // We may have to convert types.
502
      const ossia::value& orig = n.get<Device::AddressSettings>().value;
5✔
503
      if(copy.v.which() != orig.v.which() && !State::convert::convert(orig, copy))
5✔
UNCOV
504
        return false;
×
505

506
      n.get<Device::AddressSettings>().value = copy;
5✔
507

508
      // Note : if we want to disable remote updating, we have to do it
509
      // here (e.g. if this becomes a settings)
510
      m_devicePlugin.updateProxy.updateRemoteValue(Device::address(n).address, copy);
5✔
511

512
      dataChanged(index, index);
5✔
513
      return true;
5✔
514
    }
5✔
515
    else
516
    {
517
      // Here we make a command because we change the structure of the tree.
518
      auto settings = n.get<Device::AddressSettings>();
3✔
519
      if(col == Column::Name)
3✔
520
      {
521
        const QString s = value.toString();
3✔
522
        auto* parent = n.parent();
3✔
523
        if(s.isEmpty() || !parent || !canRenameNode(n))
3✔
524
          return false;
1✔
525

526
        auto renamed = n.get<Device::AddressSettings>();
2✔
527
        renamed.name = s;
2✔
528

529
        if(!checkAddressEditable(*parent, n.get<Device::AddressSettings>(), renamed))
2✔
530
          return false;
1✔
531

532
        settings.name = s;
1✔
533
      }
3✔
534

535
      if(settings != n.get<Device::AddressSettings>())
1✔
536
      {
537
        // We changed
538
        m_cmdQ.redoAndPush(new Explorer::Command::UpdateAddressSettings{
2✔
539
            this->deviceModel(), Device::NodePath{n}, settings});
1✔
540
        return true;
1✔
541
      }
542
    }
3✔
543
  }
×
544

545
  return false;
×
546
}
8✔
547

548
bool DeviceExplorerModel::setHeaderData(int, Qt::Orientation, const QVariant&, int)
×
549
{
550
  return false; // we prevent editing the (column) headers
×
551
}
552

553
/**
554
 * @brief DeviceExplorerModel::editData
555
 *
556
 * This functions gets called by the command
557
 * that edit the columns.
558
 */
UNCOV
559
void DeviceExplorerModel::editData(
×
560
    const Device::NodePath& path, Explorer::Column column, const ossia::value& value,
561
    int role)
562
{
UNCOV
563
  Device::Node* node = path.toNode(&rootNode());
×
564
  editData(*node, column, value, role);
×
565
}
×
566

567
void DeviceExplorerModel::editData(
1✔
568
    Device::Node& node, Column column, const ossia::value& value, int role)
569
{
570
  SCORE_ASSERT(node.parent());
1✔
571

572
  if(node.is<Device::DeviceSettings>())
1✔
573
    return;
×
574

575
  if(role == Qt::EditRole && column == Column::Value)
1✔
576
  {
577
    node.get<Device::AddressSettings>().value = value;
1✔
578
  }
1✔
579

580
  const QModelIndex index = modelIndexFromNode(node, (int)column);
1✔
581
  dataChanged(index, index);
1✔
582
}
1✔
583

584
QModelIndex DeviceExplorerModel::bottomIndex(const QModelIndex& index) const
×
585
{
586
  auto& node = nodeFromModelIndex(index);
×
587

588
  if(!node.hasChildren())
×
589
  {
UNCOV
590
    return index;
×
591
  }
592

593
  return bottomIndex(createIndex(
×
UNCOV
594
      node.childCount() - 1, index.column(), &node.childAt(node.childCount() - 1)));
×
UNCOV
595
}
×
596

UNCOV
597
bool DeviceExplorerModel::isDevice(QModelIndex index) const
×
598
{
UNCOV
599
  if(!index.isValid())
×
600
  {
UNCOV
601
    return false;
×
602
  }
603

UNCOV
604
  const Device::Node& n = nodeFromModelIndex(index);
×
UNCOV
605
  return n.is<Device::DeviceSettings>();
×
UNCOV
606
}
×
607

608
bool DeviceExplorerModel::isEmpty() const
84✔
609
{
610
  return m_rootNode.childCount() == 0;
84✔
611
}
612

613
/*
614
Drag and drop works by deleting the dragged items and creating a new set of
615
dropped items that match those dragged.
616
I will/may call insertRows(), removeRows(), dropMimeData(), ...
617
We define two MimeTypes : address and device.
618
It allows to distinguish whether we are drag'n dropping devices or addresses.
619
 */
620

621
Qt::DropActions DeviceExplorerModel::supportedDropActions() const
×
622
{
623
  return (Qt::CopyAction);
×
624
}
625

626
// Default supportedDragActions() implementation returns
627
// supportedDropActions().
628

629
Qt::DropActions DeviceExplorerModel::supportedDragActions() const
×
630
{
631
  return (Qt::CopyAction);
×
632
}
633

634
QStringList DeviceExplorerModel::mimeTypes() const
×
635
{
636
  return {score::mime::device(), score::mime::address()};
×
637
}
×
638

639
SelectedNodes
640
DeviceExplorerModel::uniqueSelectedNodes(const QModelIndexList& indexes) const
×
641
{
642
  SelectedNodes nodes;
×
UNCOV
643
  ossia::transform(
×
644
      indexes, std::back_inserter(nodes.parents),
×
645
      [&](const QModelIndex& idx) { return &nodeFromModelIndex(idx); });
×
646

647
  ossia::remove_erase(nodes.parents, &m_rootNode);
×
648

649
  nodes.messages.reserve(nodes.messages.size() + nodes.parents.size());
×
UNCOV
650
  if(nodes.parents.size() > 1)
×
651
  {
652
    // Filter messages
653
    for(auto node : nodes.parents)
×
654
    {
655
      if(node->is<Device::AddressSettings>())
×
656
      {
657
        auto& val = node->get<Device::AddressSettings>();
×
658
        if(val.ioType == ossia::access_mode::SET)
×
659
        {
UNCOV
660
          nodes.messages.push_back(node);
×
661
        }
×
662
      }
×
663
    }
664

665
    // Filter parents
666
    nodes.parents = filterUniqueParents(nodes.parents);
×
UNCOV
667
  }
×
668
  else if(nodes.parents.size() == 1)
×
669
  {
670
    Device::Node* node = nodes.parents[0];
×
UNCOV
671
    if(node->is<Device::AddressSettings>() && node->childCount() == 0)
×
672
    {
673
      // If a single node is selected we copy it even if it is "get"
674
      nodes.messages = std::move(nodes.parents);
×
UNCOV
675
    }
×
676
  }
×
677

UNCOV
678
  return nodes;
×
679
}
×
680
// method called when a drag is initiated
681
QMimeData* DeviceExplorerModel::mimeData(const QModelIndexList& indexes) const
×
682
{
683
  auto uniqueNodes = uniqueSelectedNodes(indexes);
×
684

685
  // Handle case of a single device which can have custom mimeData
UNCOV
686
  if(uniqueNodes.parents.size() == 1
×
UNCOV
687
     && uniqueNodes.parents[0]->is<Device::DeviceSettings>())
×
688
  {
UNCOV
689
    auto node = uniqueNodes.parents[0];
×
UNCOV
690
    if(node->is<Device::DeviceSettings>())
×
691
    {
692
      auto& dev = deviceModel().list().device(node->get<Device::DeviceSettings>().name);
×
693
      if(auto d = dev.mimeData())
×
UNCOV
694
        return d;
×
695
    }
×
UNCOV
696
  }
×
697

698
  auto mimeData = new QMimeData;
×
699

700
  // Now we request an update to the device explorer.
UNCOV
701
  m_devicePlugin.updateProxy.refreshRemoteValues(uniqueNodes.parents);
×
702

703
  // The "MessagesList" part.
UNCOV
704
  State::MessageList messages;
×
705
  messages.reserve(uniqueNodes.parents.size() + uniqueNodes.messages.size());
×
706
  for(const auto& node : uniqueNodes.parents)
×
707
  {
UNCOV
708
    Device::parametersList(*node, messages);
×
709
  }
710

711
  for(const auto& node : uniqueNodes.messages)
×
712
  {
713
    messages.push_back(Device::message(*node));
×
714
  }
715

716
  if(!messages.empty())
×
717
  {
UNCOV
718
    Mime<State::MessageList>::Serializer s{*mimeData};
×
719
    s.serialize(messages);
×
UNCOV
720
  }
×
721

722
  // The "Nodes" part. Deserialize with FreeNodeList to get the addresses.
723
  {
UNCOV
724
    Mime<Device::NodeList>::Serializer s{*mimeData};
×
725
    Device::NodeList vec;
×
726
    vec.reserve(uniqueNodes.parents.size() + uniqueNodes.messages.size());
×
UNCOV
727
    vec.insert(vec.end(), uniqueNodes.parents.begin(), uniqueNodes.parents.end());
×
728
    vec.insert(vec.end(), uniqueNodes.messages.begin(), uniqueNodes.messages.end());
×
UNCOV
729
    s.serialize(vec);
×
UNCOV
730
  }
×
731

732
  if(messages.empty() && uniqueNodes.parents.empty() && uniqueNodes.messages.empty())
×
733
  {
734
    delete mimeData;
×
UNCOV
735
    return nullptr;
×
736
  }
737

UNCOV
738
  return mimeData;
×
739
}
×
740

UNCOV
741
bool DeviceExplorerModel::canDropMimeData(
×
742
    const QMimeData* mimeData, Qt::DropAction action, int /*row*/, int /*column*/,
743
    const QModelIndex& parent) const
744
{
UNCOV
745
  if(action == Qt::IgnoreAction)
×
746
  {
UNCOV
747
    return true;
×
748
  }
749

UNCOV
750
  if(action != Qt::MoveAction && action != Qt::CopyAction)
×
751
  {
UNCOV
752
    return false;
×
753
  }
754

755
  if(!mimeData
×
UNCOV
756
     || (!mimeData->hasFormat(score::mime::device())
×
757
         && !mimeData->hasFormat(score::mime::address())))
×
758
  {
UNCOV
759
    return false;
×
760
  }
761

762
  const Device::Node& parentNode = nodeFromModelIndex(parent);
×
763

764
  if(mimeData->hasFormat(score::mime::address()))
×
765
  {
UNCOV
766
    if(&parentNode == &m_rootNode)
×
767
    {
768
      return false;
×
769
    }
UNCOV
770
  }
×
771
  else
772
  {
UNCOV
773
    SCORE_ASSERT(mimeData->hasFormat(score::mime::device()));
×
774

UNCOV
775
    if(&parentNode != &m_rootNode)
×
776
    {
UNCOV
777
      return false;
×
778
    }
779
  }
780

UNCOV
781
  return true;
×
782
}
×
783

784
// method called when a drop occurs
785
// return true if drop really handled, false otherwise.
786
//
787
// if dropMimeData returns true && action==Qt::MoveAction, removeRows is called
788
// immediately after
UNCOV
789
bool DeviceExplorerModel::dropMimeData(
×
790
    const QMimeData* mimeData, Qt::DropAction action, int row, int column,
791
    const QModelIndex& parent)
792
{
UNCOV
793
  if(action == Qt::IgnoreAction)
×
794
  {
UNCOV
795
    return true;
×
796
  }
797

798
  if(action != Qt::MoveAction && action != Qt::CopyAction)
×
799
  {
UNCOV
800
    return false;
×
801
  }
802

803
  if(!mimeData
×
804
     || (!mimeData->hasFormat(score::mime::device())
×
805
         && !mimeData->hasFormat(score::mime::address())))
×
806
  {
807
    return false;
×
808
  }
809

UNCOV
810
  QModelIndex parentIndex; // invalid
×
811
  Device::Node* parentNode = &m_rootNode;
×
UNCOV
812
  QString mimeType = score::mime::device();
×
813

814
  if(mimeData->hasFormat(score::mime::address()))
×
815
  {
UNCOV
816
    parentIndex = parent;
×
UNCOV
817
    parentNode = &nodeFromModelIndex(parent);
×
818
    mimeType = score::mime::address();
×
819

UNCOV
820
    if(parentNode == &m_rootNode)
×
821
    {
UNCOV
822
      return false;
×
823
    }
824
  }
×
825
  else
826
  {
827
    SCORE_ASSERT(mimeData->hasFormat(score::mime::device()));
×
UNCOV
828
    SCORE_ASSERT(mimeType == score::mime::device());
×
829
  }
830

831
  if(parentNode)
×
832
  {
833
    // Note : when dropping a device,
834
    // if there is an existing device that would use the same ports, etc.
835
    // we have to open a dialog to change the device settings.
836

UNCOV
837
    const auto& json = readJson(mimeData->data(mimeType));
×
838
    JSONObject::Deserializer deser{json};
×
UNCOV
839
    Device::Node n;
×
840
    deser.writeTo(n);
×
841

UNCOV
842
    if(mimeType == score::mime::device())
×
843
    {
UNCOV
844
      checkAndLoadDevice(n);
×
845
    }
×
846
    return true;
×
847
  }
×
UNCOV
848
  return false;
×
849
}
×
850

UNCOV
851
void DeviceExplorerModel::checkAndLoadDevice(Device::Node n)
×
852
{
853
  SCORE_ASSERT(n.is<Device::DeviceSettings>());
×
854
  auto& deviceSettings = *n.target<Device::DeviceSettings>();
×
855

856
  // We ask the user to fix the incompatibilities by himself, maybe change
857
  // the name also.
UNCOV
858
  auto dialog = new DeviceEditDialog{
×
859
      *this, m_devicePlugin.context().app.interfaces<Device::ProtocolFactoryList>(),
×
860
      DeviceEditDialog::Creating, QApplication::activeWindow()};
×
861

862
  dialog->setSettings(deviceSettings);
×
863
  dialog->setBrowserEnabled(false);
×
864

865
  connect(
×
UNCOV
866
      dialog, &QDialog::accepted, this, [this, dialog, node = std::move(n)]() mutable {
×
867
        auto new_settings = dialog->getSettings();
×
868
        auto& deviceSettings = *node.target<Device::DeviceSettings>();
×
869
        deviceSettings = new_settings;
×
870
        if(!checkDeviceInstantiatable(deviceSettings))
×
871
        {
872
          score::warning(
×
873
              m_devicePlugin.context().app.mainWindow, "Error",
×
UNCOV
874
              "Could not create the device");
×
875
          dialog->deleteLater();
×
UNCOV
876
          return;
×
877
        }
UNCOV
878
        ossia::net::sanitize_device_name(deviceSettings.name);
×
879

880
        auto cmd = new Command::LoadDevice{deviceModel(), std::move(node)};
×
881
        m_cmdQ.redoAndPush(cmd);
×
UNCOV
882
        dialog->deleteLater();
×
883
      });
×
884
  connect(dialog, &QDialog::rejected, this, [dialog] { dialog->deleteLater(); });
×
UNCOV
885
  dialog->show();
×
886
}
×
887

888
void DeviceExplorerModel::checkAndLoadDevice(Device::DeviceSettings deviceSettings)
×
889
{
890
  // We ask the user to fix the incompatibilities by himself, maybe change
891
  // the name also.
892
  auto dialog = new DeviceEditDialog{
×
893
      *this, m_devicePlugin.context().app.interfaces<Device::ProtocolFactoryList>(),
×
894
      DeviceEditDialog::Creating, QApplication::activeWindow()};
×
895

UNCOV
896
  dialog->setSettings(deviceSettings);
×
897
  dialog->setBrowserEnabled(false);
×
898

899
  connect(dialog, &QDialog::accepted, this, [this, dialog] {
×
900
    auto node = dialog->getDevice();
×
901
    auto& deviceSettings = *node.target<Device::DeviceSettings>();
×
902
    if(!checkDeviceInstantiatable(deviceSettings))
×
903
    {
904
      score::warning(
×
905
          m_devicePlugin.context().app.mainWindow, "Error",
×
UNCOV
906
          "Could not create the device");
×
907
      dialog->deleteLater();
×
UNCOV
908
      return;
×
909
    }
UNCOV
910
    ossia::net::sanitize_device_name(deviceSettings.name);
×
911

UNCOV
912
    auto cmd = new Command::LoadDevice{deviceModel(), std::move(node)};
×
913
    m_cmdQ.redoAndPush(cmd);
×
914
    dialog->deleteLater();
×
UNCOV
915
  });
×
916
  connect(dialog, &QDialog::rejected, this, [dialog] { dialog->deleteLater(); });
×
917
  dialog->show();
×
UNCOV
918
}
×
919

UNCOV
920
void DeviceExplorerModel::debug_printPath(const Device::NodePath& path)
×
921
{
922
  const int pathSize = std::ssize(path);
×
923

924
  for(int i = 0; i < pathSize; ++i)
×
925
  {
926
    std::cerr << path.at(i) << " ";
×
927
  }
×
928

929
  std::cerr << "\n";
×
UNCOV
930
}
×
931

UNCOV
932
void DeviceExplorerModel::debug_printIndexes(const QModelIndexList& indexes)
×
933
{
934
  std::cerr << "indexes: " << indexes.size() << " nodes: \n";
×
UNCOV
935
  Q_FOREACH(const QModelIndex& index, indexes)
×
936
  {
937
    if(index.isValid())
×
938
    {
939
      std::cerr << " index.row=" << index.row() << " col=" << index.column() << " ";
×
UNCOV
940
      Device::Node* n = &nodeFromModelIndex(index);
×
941
      std::cerr << " n=" << n << " ";
×
UNCOV
942
      Device::Node* parent = n->parent();
×
943

944
      if(n == &m_rootNode)
×
945
      {
UNCOV
946
        std::cerr << " rootNode parent=" << parent << "\n";
×
947
      }
×
948
      else
949
      {
UNCOV
950
        std::cerr << " n->name=" << n->displayName().toStdString();
×
UNCOV
951
        std::cerr << " parent=" << parent;
×
952
        std::cerr << " parent->name=" << parent->displayName().toStdString() << "\n";
×
953
      }
UNCOV
954
    }
×
955
    else
956
    {
UNCOV
957
      std::cerr << " invalid index \n";
×
958
    }
959
  }
960
}
×
961

962
State::MessageList getSelectionSnapshot(DeviceExplorerModel& model)
×
963
{
964
  // Filter
UNCOV
965
  auto uniqueNodes = model.uniqueSelectedNodes(model.selectedIndexes());
×
966

967
  // Recursive refresh
UNCOV
968
  model.deviceModel().updateProxy.refreshRemoteValues(uniqueNodes.parents);
×
969

970
  // Conversion
UNCOV
971
  State::MessageList messages;
×
UNCOV
972
  messages.reserve(uniqueNodes.parents.size() + uniqueNodes.messages.size());
×
UNCOV
973
  for(const auto& node : uniqueNodes.parents)
×
974
  {
UNCOV
975
    Device::parametersList(*node, messages);
×
976
  }
977
  for(const auto& node : uniqueNodes.messages)
×
978
  {
UNCOV
979
    messages.push_back(Device::message(*node));
×
980
  }
981

UNCOV
982
  return messages;
×
UNCOV
983
}
×
984

985
DeviceExplorerModel* try_deviceExplorerFromObject(const QObject& obj)
107✔
986
{
987
  auto plug = score::IDocument::documentContext(obj).findPlugin<DeviceDocumentPlugin>();
107✔
988
  if(plug)
107✔
989
    return &plug->explorer();
107✔
990
  return nullptr;
×
991
}
107✔
992

993
DeviceExplorerModel& deviceExplorerFromObject(const QObject& obj)
3✔
994
{
995
  auto expl = try_deviceExplorerFromObject(obj);
3✔
996
  SCORE_ASSERT(expl);
3✔
997
  return *expl;
3✔
998
}
999

UNCOV
1000
DeviceExplorerModel* try_deviceExplorerFromContext(const score::DocumentContext& ctx)
×
1001
{
1002
  auto plug = ctx.findPlugin<DeviceDocumentPlugin>();
×
UNCOV
1003
  if(plug)
×
UNCOV
1004
    return &plug->explorer();
×
UNCOV
1005
  return nullptr;
×
UNCOV
1006
}
×
1007

1008
DeviceExplorerModel& deviceExplorerFromContext(const score::DocumentContext& ctx)
×
1009
{
UNCOV
1010
  auto expl = try_deviceExplorerFromContext(ctx);
×
1011
  SCORE_ASSERT(expl);
×
1012
  return *expl;
×
1013
}
1014

UNCOV
1015
Device::FullAddressAccessorSettings makeFullAddressAccessorSettings(
×
1016
    const State::AddressAccessor& addr, const score::DocumentContext& ctx,
1017
    ossia::value min, ossia::value max, ossia::value val)
1018
{
1019
  // First try to find if there is a matching address
1020
  // in the device explorer
1021
  auto deviceexplorer = Explorer::try_deviceExplorerFromContext(ctx);
×
UNCOV
1022
  if(deviceexplorer)
×
1023
  {
UNCOV
1024
    return Device::makeFullAddressAccessorSettings(
×
UNCOV
1025
        addr, *deviceexplorer, std::move(min), std::move(max), std::move(val));
×
1026
  }
1027

1028
  // If there is none, build with some default settings
UNCOV
1029
  Device::FullAddressAccessorSettings s;
×
UNCOV
1030
  s.address = addr;
×
UNCOV
1031
  s.value = std::move(val);
×
UNCOV
1032
  s.domain = ossia::make_domain(std::move(min), std::move(max));
×
UNCOV
1033
  return s;
×
UNCOV
1034
}
×
1035
}
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