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

ossia / score / 33560887155

01 Sep 2026 09:24PM UTC coverage: 24.648% (+0.7%) from 23.997%
33560887155

Pull #2179

github

web-flow
Merge ecc0a2ad3 into 50d28933c
Pull Request #2179: Survive a build that does not have every plug-in

408 of 757 new or added lines in 47 files covered. (53.9%)

15 existing lines in 12 files now uncovered.

55851 of 226593 relevant lines covered (24.65%)

61526.39 hits per line

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

43.24
/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)
7,397✔
61
namespace Explorer
62
{
63
static const QMap<Explorer::Column, QString> HEADERS{
119✔
64
    {Explorer::Column::Name, QObject::tr("Address")},
119✔
65
    {Explorer::Column::Value, QObject::tr("Value")}};
119✔
66

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

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

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

81
DeviceDocumentPlugin& DeviceExplorerModel::deviceModel() const
435✔
82
{
83
  return m_devicePlugin;
435✔
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
96✔
111
{
112
  return HEADERS.values();
96✔
113
}
114

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

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

124
  return row;
8✔
125
}
126

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

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

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

138
  return row;
42✔
139
}
140

141
bool DeviceExplorerModel::replaceDevice(Device::Node&& deviceNode)
14✔
142
{
143
  const auto& name = deviceNode.get<Device::DeviceSettings>().name;
14✔
144
  int row = 0;
14✔
145
  for(auto& n : m_rootNode)
14✔
146
  {
147
    if(n.get<Device::DeviceSettings>().name == name)
14✔
148
    {
149
      const QModelIndex index = createIndex(row, 0, &n);
14✔
150

151
      deviceTreeAboutToChange(&n);
14✔
152

153
      // Out with the old children...
154
      if(const int count = n.childCount(); count > 0)
14✔
155
      {
156
        beginRemoveRows(index, 0, count - 1);
14✔
157
        n.takeChildren();
14✔
158
        endRemoveRows();
14✔
159
      }
14✔
160

161
      // ... update the settings ...
162
      n.set(deviceNode.get<Device::DeviceSettings>());
14✔
163
      dataChanged(index, createIndex(row, columnCount() - 1, &n));
14✔
164

165
      // ... and in with the new.
166
      if(const int count = deviceNode.childCount(); count > 0)
14✔
167
      {
168
        beginInsertRows(index, 0, count - 1);
14✔
169
        deviceNode.moveChildren(n);
14✔
170
        endInsertRows();
14✔
171
      }
14✔
172

173
      deviceTreeChanged(&n);
14✔
174
      return true;
14✔
175
    }
176
    row++;
×
177
  }
178
  return false;
×
179
}
14✔
180

181
void DeviceExplorerModel::updateDevice(
27✔
182
    const QString& name, const Device::DeviceSettings& dev)
183
{
184
  int i = 0;
27✔
185
  for(auto& n : m_rootNode)
27✔
186
  {
187
    if(n.get<Device::DeviceSettings>().name == name)
27✔
188
    {
189
      n.set(dev);
27✔
190

191
      QModelIndex index = createIndex(i, 0, &n);
27✔
192
      dataChanged(index, index);
27✔
193
      return;
27✔
194
    }
195
    i++;
×
196
  }
197
}
27✔
198

199
Device::Node* DeviceExplorerModel::addAddress(
1✔
200
    Device::Node* parentNode, const Device::AddressSettings& addressSettings, int row)
201
{
202
  SCORE_ASSERT(parentNode);
1✔
203
  SCORE_ASSERT(parentNode != &m_rootNode);
1✔
204

205
  Device::Node* grandparent = parentNode->parent();
1✔
206
  SCORE_ASSERT(grandparent);
1✔
207
  int rowParent = grandparent->indexOfChild(parentNode);
1✔
208
  QModelIndex parentIndex = createIndex(rowParent, 0, parentNode);
1✔
209

210
  beginInsertRows(parentIndex, row, row);
1✔
211

212
  auto it = parentNode->begin();
1✔
213
  std::advance(it, row);
1✔
214

215
  auto& ret = parentNode->emplace(it, addressSettings, parentNode);
1✔
216

217
  endInsertRows();
1✔
218

219
  return &ret;
1✔
220
}
221

222
void DeviceExplorerModel::addNode(
2✔
223
    Device::Node* parentNode, Device::Node&& child, int row)
224
{
225
  SCORE_ASSERT(parentNode);
2✔
226
  SCORE_ASSERT(parentNode != &m_rootNode);
2✔
227

228
  Device::Node* grandparent = parentNode->parent();
2✔
229
  SCORE_ASSERT(grandparent);
2✔
230
  int rowParent = grandparent->indexOfChild(parentNode);
2✔
231
  QModelIndex parentIndex = createIndex(rowParent, 0, parentNode);
2✔
232

233
  beginInsertRows(parentIndex, row, row);
2✔
234

235
  auto it = parentNode->begin();
2✔
236
  std::advance(it, row);
2✔
237
  parentNode->emplace(it, std::move(child));
2✔
238

239
  endInsertRows();
2✔
240
}
2✔
241

242
void DeviceExplorerModel::updateAddress(
10✔
243
    Device::Node* node, const Device::AddressSettings& addressSettings)
244
{
245
  SCORE_ASSERT(node);
10✔
246
  SCORE_ASSERT(node != &m_rootNode);
10✔
247

248
  node->set(addressSettings);
10✔
249

250
  nodeChanged(node);
10✔
251

252
  dataChanged(
10✔
253
      modelIndexFromNode(*node, 0), modelIndexFromNode(*node, (int)Column::Count - 1));
10✔
254
}
10✔
255

256
void DeviceExplorerModel::updateValue(
24✔
257
    Device::Node* n, const State::AddressAccessor& addr, const ossia::value& v)
258
{
259
  if(!addr.qualifiers.get().accessors.empty())
24✔
260
  {
261
    SCORE_TODO;
×
262
  }
×
263
  n->get<Device::AddressSettings>().value = v;
24✔
264

265
  QModelIndex nodeIndex = modelIndexFromNode(*n, 1);
24✔
266

267
  dataChanged(nodeIndex, nodeIndex);
24✔
268
}
24✔
269

270
bool DeviceExplorerModel::checkDeviceInstantiatable(
17✔
271
    const Device::DeviceSettings& n) const
272
{
273
  // No name -> no love
274
  if(n.name.isEmpty())
17✔
275
    return false;
×
276

277
  // Request from the protocol factory the protocol to see
278
  // if it is compatible.
279
  auto& context = m_devicePlugin.context().app;
17✔
280
  auto prot = context.interfaces<Device::ProtocolFactoryList>().get(n.protocol);
17✔
281
  if(!prot)
17✔
282
    return false;
17✔
283

284
  // Look for other childs in the same protocol.
285
  bool none_incompatible = std::none_of(
×
286
      rootNode().begin(), rootNode().end(), [&](const Device::Node& child) {
×
287
    SCORE_ASSERT(child.is<Device::DeviceSettings>());
×
288
    const auto& set = child.get<Device::DeviceSettings>();
×
289
    return (set.name == n.name)
×
290
           || (set.protocol
×
291
                   == n.protocol // FIXME distinct protocols could use the same port...
×
292
               && !prot->checkCompatibility(n, child.get<Device::DeviceSettings>()));
×
293
  });
294
  if(!none_incompatible)
×
295
    return false;
×
296

297
  // Check compatibility with an empty device
298
  return prot->checkCompatibility(n, Device::DeviceSettings{});
×
299
}
17✔
300

301
bool DeviceExplorerModel::checkDeviceEditable(
3✔
302
    const QString& originalName, const Device::DeviceSettings& n) const
303
{
304
  // No name -> no love
305
  if(n.name.isEmpty())
3✔
306
    return false;
×
307

308
  // Request from the protocol factory the protocol to see
309
  // if it is compatible.
310
  auto& context = m_devicePlugin.context().app;
3✔
311
  auto prot = context.interfaces<Device::ProtocolFactoryList>().get(n.protocol);
3✔
312
  if(!prot)
3✔
313
    return false;
3✔
314

315
  // Look for other childs in the same protocol.
316
  bool none_incompatible = std::none_of(
×
317
      rootNode().begin(), rootNode().end(), [&](const Device::Node& child) {
×
318
    SCORE_ASSERT(child.is<Device::DeviceSettings>());
×
319
    const auto& set = child.get<Device::DeviceSettings>();
×
320
    // Ignore the device that is being edited
321
    if(set.name == originalName)
×
322
      return false;
×
323

324
    return (set.name == n.name)
×
325
           || (set.protocol == n.protocol
×
326
               && !prot->checkCompatibility(n, child.get<Device::DeviceSettings>()));
×
327
  });
×
328
  if(!none_incompatible)
×
329
    return false;
×
330

331
  // Check compatibility with an empty device
332
  return prot->checkCompatibility(n, Device::DeviceSettings{});
×
333
}
3✔
334

335
bool DeviceExplorerModel::checkAddressInstantiatable(
×
336
    Device::Node& parent, const Device::AddressSettings& addr)
337
{
338
  SCORE_ASSERT(!parent.is<InvisibleRootNode>());
×
339

340
  if(addr.name.isEmpty())
×
341
    return false;
×
342

343
  return std::none_of(parent.begin(), parent.end(), [&](const Device::Node& n) {
×
344
    return n.get<Device::AddressSettings>().name == addr.name;
×
345
  });
346
}
×
347

348
bool DeviceExplorerModel::checkAddressEditable(
6✔
349
    Device::Node& parent, const Device::AddressSettings& before,
350
    const Device::AddressSettings& after)
351
{
352
  SCORE_ASSERT(!parent.is<InvisibleRootNode>());
6✔
353

354
  if(after.name.isEmpty())
6✔
355
    return false;
×
356

357
  auto it = std::find_if(parent.begin(), parent.end(), [&](const Device::Node& n) {
17✔
358
    return n.get<Device::AddressSettings>().name == after.name;
11✔
359
  });
360
  if(it != parent.end())
6✔
361
  {
362
    //  We didn't change name, it's ok
363
    if(after.name == before.name)
4✔
364
      return true;
3✔
365
    else
366
      return false;
1✔
367
  }
368
  else
369
  {
370
    // Ok, no conflicts
371
    return true;
2✔
372
  }
373
}
6✔
374

375
bool DeviceExplorerModel::canRenameNode(const Device::Node& n) const
15✔
376
{
377
  if(!n.is<Device::AddressSettings>())
15✔
378
    return false;
×
379

380
  // Called for every visible cell: walk up to the device rather than building
381
  // the whole address just to read its first component.
382
  const Device::Node* root = &n;
15✔
383
  while(root->parent() && !root->is<Device::DeviceSettings>())
30✔
384
    root = root->parent();
15✔
385

386
  if(!root->is<Device::DeviceSettings>())
15✔
387
    return false;
×
388

389
  auto* dev = m_devicePlugin.list().findDevice(root->get<Device::DeviceSettings>().name);
15✔
390
  return dev && dev->capabilities().canRenameNode;
15✔
391
}
15✔
392

393
int DeviceExplorerModel::columnCount() const
110✔
394
{
395
  return (int)Column::Count;
110✔
396
}
397

398
int DeviceExplorerModel::columnCount(const QModelIndex& /*parent*/) const
419✔
399
{
400
  return (int)Column::Count;
419✔
401
}
402

403
// must return an invalid QVariant for cases not handled
404
QVariant DeviceExplorerModel::data(const QModelIndex& index, int role) const
92✔
405
{
406
  const int col = index.column();
92✔
407

408
  if(col < 0 || col >= (int)Column::Count)
92✔
409
  {
410
    return QVariant();
×
411
  }
412

413
  const Device::Node& n = nodeFromModelIndex(index);
92✔
414
  if(role != Qt::ToolTipRole)
92✔
415
  {
416
    switch((Column)col)
92✔
417
    {
418
      case Column::Name: {
419
        if(n.is<Device::AddressSettings>())
92✔
420
          return Device::nameColumnData(n, role);
64✔
421
        else if(n.is<Device::DeviceSettings>())
28✔
422
        {
423
          auto& dev_set = n.get<Device::DeviceSettings>();
28✔
424
          auto* impl = deviceModel().list().findDevice(dev_set.name);
28✔
425
          return Device::deviceNameColumnData(n, impl && impl->connected(), role);
28✔
426
        }
427
        return {};
×
428
      }
429

430
      case Column::Value:
431
        return Device::valueColumnData(n, role);
×
432

433
      case Column::Count:
×
434
      default:
435
        SCORE_ABORT;
×
436
        return {};
×
437
    }
438
  }
439
  else
440
  {
441
    // Tooltip
442
    if(n.is<Device::AddressSettings>())
×
443
    {
444
      auto& addr_set = n.get<Device::AddressSettings>();
×
445
      if(const auto& desc = ossia::net::get_description(addr_set))
×
446
        return QString::fromStdString(*desc);
×
447
    }
×
448
    else
449
    {
450
      return {};
×
451
    }
452
  }
453
  return {};
×
454
}
92✔
455

456
QVariant
457
DeviceExplorerModel::headerData(int section, Qt::Orientation orientation, int role) const
960✔
458
{
459
  if(orientation == Qt::Horizontal && role == Qt::DisplayRole)
960✔
460
  {
461
    if(section >= 0 && section < (int)Column::Count)
384✔
462
    {
463
      return HEADERS[(Column)section];
384✔
464
    }
465
  }
×
466

467
  return {};
576✔
468
}
960✔
469

470
Qt::ItemFlags DeviceExplorerModel::flags(const QModelIndex& index) const
56✔
471
{
472
  Qt::ItemFlags f = Qt::ItemIsEnabled;
56✔
473
  // by default QAbstractItemModel::flags(index); returns Qt::ItemIsEnabled |
474
  // Qt::ItemIsSelectable
475

476
  if(index.isValid())
56✔
477
  {
478
    const Device::Node& n = nodeFromModelIndex(index);
56✔
479

480
    if(n.isSelectable())
56✔
481
    {
482
      f |= Qt::ItemIsSelectable;
56✔
483
    }
56✔
484

485
    // we allow drag'n drop only from the name column
486
    if(index.column() == (int)Column::Name)
56✔
487
    {
488
      f |= Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
56✔
489
    }
56✔
490

491
    if(n.isEditable() && index.column() == (int)Column::Value)
56✔
492
    {
493
      f |= Qt::ItemIsEditable;
×
494
    }
×
495

496
    if(index.column() == (int)Column::Name && n.is<Device::AddressSettings>()
56✔
497
       && canRenameNode(n))
56✔
498
    {
499
      f |= Qt::ItemIsEditable;
11✔
500
    }
11✔
501
  }
56✔
502
  else
503
  {
504
    // to be able to drop even where there is nothing
505
    f |= Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled;
×
506
  }
507

508
  return f;
56✔
509
}
510

511
/*
512
  return false if no change was made.
513
  dataChanged() & return true if a change is made.
514

515
  Note: this is the function that gets called when the user changes the value
516
  in the tree.
517
  It then sends a command that calls editData.
518
*/
519
bool DeviceExplorerModel::setData(
8✔
520
    const QModelIndex& index, const QVariant& value, int role)
521
{
522
  if(!index.isValid())
8✔
523
    return false;
×
524

525
  auto& n = nodeFromModelIndex(index);
8✔
526

527
  if(!n.is<Device::AddressSettings>())
8✔
528
    return false;
×
529

530
  auto col = Explorer::Column(index.column());
8✔
531

532
  if(role == Qt::EditRole || role == Qt::CheckStateRole)
8✔
533
  {
534
    if(col == Column::Value)
8✔
535
    {
536
      // In this case we don't make a command, but we directly push the
537
      // new value.
538
      auto copy = value.canConvert<ossia::value>() ? value.value<ossia::value>()
5✔
539
                                                   : State::convert::fromQVariant(value);
×
540

541
      // We may have to convert types.
542
      const ossia::value& orig = n.get<Device::AddressSettings>().value;
5✔
543
      if(copy.v.which() != orig.v.which() && !State::convert::convert(orig, copy))
5✔
544
        return false;
×
545

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

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

552
      dataChanged(index, index);
5✔
553
      return true;
5✔
554
    }
5✔
555
    else
556
    {
557
      // Here we make a command because we change the structure of the tree.
558
      auto settings = n.get<Device::AddressSettings>();
3✔
559
      if(col == Column::Name)
3✔
560
      {
561
        const QString s = value.toString();
3✔
562
        auto* parent = n.parent();
3✔
563
        if(s.isEmpty() || !parent || !canRenameNode(n))
3✔
564
          return false;
1✔
565

566
        auto renamed = n.get<Device::AddressSettings>();
2✔
567
        renamed.name = s;
2✔
568

569
        if(!checkAddressEditable(*parent, n.get<Device::AddressSettings>(), renamed))
2✔
570
          return false;
1✔
571

572
        settings.name = s;
1✔
573
      }
3✔
574

575
      if(settings != n.get<Device::AddressSettings>())
1✔
576
      {
577
        // We changed
578
        m_cmdQ.redoAndPush(new Explorer::Command::UpdateAddressSettings{
2✔
579
            this->deviceModel(), Device::NodePath{n}, settings});
1✔
580
        return true;
1✔
581
      }
582
    }
3✔
583
  }
×
584

585
  return false;
×
586
}
8✔
587

588
bool DeviceExplorerModel::setHeaderData(int, Qt::Orientation, const QVariant&, int)
×
589
{
590
  return false; // we prevent editing the (column) headers
×
591
}
592

593
/**
594
 * @brief DeviceExplorerModel::editData
595
 *
596
 * This functions gets called by the command
597
 * that edit the columns.
598
 */
599
void DeviceExplorerModel::editData(
×
600
    const Device::NodePath& path, Explorer::Column column, const ossia::value& value,
601
    int role)
602
{
603
  Device::Node* node = path.toNode(&rootNode());
×
604
  editData(*node, column, value, role);
×
605
}
×
606

607
void DeviceExplorerModel::editData(
1✔
608
    Device::Node& node, Column column, const ossia::value& value, int role)
609
{
610
  SCORE_ASSERT(node.parent());
1✔
611

612
  if(node.is<Device::DeviceSettings>())
1✔
613
    return;
×
614

615
  if(role == Qt::EditRole && column == Column::Value)
1✔
616
  {
617
    node.get<Device::AddressSettings>().value = value;
1✔
618
  }
1✔
619

620
  const QModelIndex index = modelIndexFromNode(node, (int)column);
1✔
621
  dataChanged(index, index);
1✔
622
}
1✔
623

624
QModelIndex DeviceExplorerModel::bottomIndex(const QModelIndex& index) const
×
625
{
626
  auto& node = nodeFromModelIndex(index);
×
627

628
  if(!node.hasChildren())
×
629
  {
630
    return index;
×
631
  }
632

633
  return bottomIndex(createIndex(
×
634
      node.childCount() - 1, index.column(), &node.childAt(node.childCount() - 1)));
×
635
}
×
636

637
bool DeviceExplorerModel::isDevice(QModelIndex index) const
×
638
{
639
  if(!index.isValid())
×
640
  {
641
    return false;
×
642
  }
643

644
  const Device::Node& n = nodeFromModelIndex(index);
×
645
  return n.is<Device::DeviceSettings>();
×
646
}
×
647

648
bool DeviceExplorerModel::isEmpty() const
193✔
649
{
650
  return m_rootNode.childCount() == 0;
193✔
651
}
652

653
/*
654
Drag and drop works by deleting the dragged items and creating a new set of
655
dropped items that match those dragged.
656
I will/may call insertRows(), removeRows(), dropMimeData(), ...
657
We define two MimeTypes : address and device.
658
It allows to distinguish whether we are drag'n dropping devices or addresses.
659
 */
660

661
Qt::DropActions DeviceExplorerModel::supportedDropActions() const
×
662
{
663
  return (Qt::CopyAction);
×
664
}
665

666
// Default supportedDragActions() implementation returns
667
// supportedDropActions().
668

669
Qt::DropActions DeviceExplorerModel::supportedDragActions() const
×
670
{
671
  return (Qt::CopyAction);
×
672
}
673

674
QStringList DeviceExplorerModel::mimeTypes() const
×
675
{
676
  return {score::mime::device(), score::mime::address()};
×
677
}
×
678

679
SelectedNodes
680
DeviceExplorerModel::uniqueSelectedNodes(const QModelIndexList& indexes) const
×
681
{
682
  SelectedNodes nodes;
×
683
  ossia::transform(
×
684
      indexes, std::back_inserter(nodes.parents),
×
685
      [&](const QModelIndex& idx) { return &nodeFromModelIndex(idx); });
×
686

687
  ossia::remove_erase(nodes.parents, &m_rootNode);
×
688

689
  nodes.messages.reserve(nodes.messages.size() + nodes.parents.size());
×
690
  if(nodes.parents.size() > 1)
×
691
  {
692
    // Filter messages
693
    for(auto node : nodes.parents)
×
694
    {
695
      if(node->is<Device::AddressSettings>())
×
696
      {
697
        auto& val = node->get<Device::AddressSettings>();
×
698
        if(val.ioType == ossia::access_mode::SET)
×
699
        {
700
          nodes.messages.push_back(node);
×
701
        }
×
702
      }
×
703
    }
704

705
    // Filter parents
706
    nodes.parents = filterUniqueParents(nodes.parents);
×
707
  }
×
708
  else if(nodes.parents.size() == 1)
×
709
  {
710
    Device::Node* node = nodes.parents[0];
×
711
    if(node->is<Device::AddressSettings>() && node->childCount() == 0)
×
712
    {
713
      // If a single node is selected we copy it even if it is "get"
714
      nodes.messages = std::move(nodes.parents);
×
715
    }
×
716
  }
×
717

718
  return nodes;
×
719
}
×
720
// method called when a drag is initiated
721
QMimeData* DeviceExplorerModel::mimeData(const QModelIndexList& indexes) const
×
722
{
723
  auto uniqueNodes = uniqueSelectedNodes(indexes);
×
724

725
  // Handle case of a single device which can have custom mimeData
726
  if(uniqueNodes.parents.size() == 1
×
727
     && uniqueNodes.parents[0]->is<Device::DeviceSettings>())
×
728
  {
729
    auto node = uniqueNodes.parents[0];
×
730
    if(node->is<Device::DeviceSettings>())
×
731
    {
NEW
732
      if(auto* dev
×
NEW
733
         = deviceModel().list().findDevice(node->get<Device::DeviceSettings>().name))
×
NEW
734
        if(auto d = dev->mimeData())
×
NEW
735
          return d;
×
736
    }
×
737
  }
×
738

739
  auto mimeData = new QMimeData;
×
740

741
  // Now we request an update to the device explorer.
742
  m_devicePlugin.updateProxy.refreshRemoteValues(uniqueNodes.parents);
×
743

744
  // The "MessagesList" part.
745
  State::MessageList messages;
×
746
  messages.reserve(uniqueNodes.parents.size() + uniqueNodes.messages.size());
×
747
  for(const auto& node : uniqueNodes.parents)
×
748
  {
749
    Device::parametersList(*node, messages);
×
750
  }
751

752
  for(const auto& node : uniqueNodes.messages)
×
753
  {
754
    messages.push_back(Device::message(*node));
×
755
  }
756

757
  if(!messages.empty())
×
758
  {
759
    Mime<State::MessageList>::Serializer s{*mimeData};
×
760
    s.serialize(messages);
×
761
  }
×
762

763
  // The "Nodes" part. Deserialize with FreeNodeList to get the addresses.
764
  {
765
    Mime<Device::NodeList>::Serializer s{*mimeData};
×
766
    Device::NodeList vec;
×
767
    vec.reserve(uniqueNodes.parents.size() + uniqueNodes.messages.size());
×
768
    vec.insert(vec.end(), uniqueNodes.parents.begin(), uniqueNodes.parents.end());
×
769
    vec.insert(vec.end(), uniqueNodes.messages.begin(), uniqueNodes.messages.end());
×
770
    s.serialize(vec);
×
771
  }
×
772

773
  if(messages.empty() && uniqueNodes.parents.empty() && uniqueNodes.messages.empty())
×
774
  {
775
    delete mimeData;
×
776
    return nullptr;
×
777
  }
778

779
  return mimeData;
×
780
}
×
781

782
bool DeviceExplorerModel::canDropMimeData(
×
783
    const QMimeData* mimeData, Qt::DropAction action, int /*row*/, int /*column*/,
784
    const QModelIndex& parent) const
785
{
786
  if(action == Qt::IgnoreAction)
×
787
  {
788
    return true;
×
789
  }
790

791
  if(action != Qt::MoveAction && action != Qt::CopyAction)
×
792
  {
793
    return false;
×
794
  }
795

796
  if(!mimeData
×
797
     || (!mimeData->hasFormat(score::mime::device())
×
798
         && !mimeData->hasFormat(score::mime::address())))
×
799
  {
800
    return false;
×
801
  }
802

803
  const Device::Node& parentNode = nodeFromModelIndex(parent);
×
804

805
  if(mimeData->hasFormat(score::mime::address()))
×
806
  {
807
    if(&parentNode == &m_rootNode)
×
808
    {
809
      return false;
×
810
    }
811
  }
×
812
  else
813
  {
814
    SCORE_ASSERT(mimeData->hasFormat(score::mime::device()));
×
815

816
    if(&parentNode != &m_rootNode)
×
817
    {
818
      return false;
×
819
    }
820
  }
821

822
  return true;
×
823
}
×
824

825
// method called when a drop occurs
826
// return true if drop really handled, false otherwise.
827
//
828
// if dropMimeData returns true && action==Qt::MoveAction, removeRows is called
829
// immediately after
830
bool DeviceExplorerModel::dropMimeData(
×
831
    const QMimeData* mimeData, Qt::DropAction action, int row, int column,
832
    const QModelIndex& parent)
833
{
834
  if(action == Qt::IgnoreAction)
×
835
  {
836
    return true;
×
837
  }
838

839
  if(action != Qt::MoveAction && action != Qt::CopyAction)
×
840
  {
841
    return false;
×
842
  }
843

844
  if(!mimeData
×
845
     || (!mimeData->hasFormat(score::mime::device())
×
846
         && !mimeData->hasFormat(score::mime::address())))
×
847
  {
848
    return false;
×
849
  }
850

851
  QModelIndex parentIndex; // invalid
×
852
  Device::Node* parentNode = &m_rootNode;
×
853
  QString mimeType = score::mime::device();
×
854

855
  if(mimeData->hasFormat(score::mime::address()))
×
856
  {
857
    parentIndex = parent;
×
858
    parentNode = &nodeFromModelIndex(parent);
×
859
    mimeType = score::mime::address();
×
860

861
    if(parentNode == &m_rootNode)
×
862
    {
863
      return false;
×
864
    }
865
  }
×
866
  else
867
  {
868
    SCORE_ASSERT(mimeData->hasFormat(score::mime::device()));
×
869
    SCORE_ASSERT(mimeType == score::mime::device());
×
870
  }
871

872
  if(parentNode)
×
873
  {
874
    // Note : when dropping a device,
875
    // if there is an existing device that would use the same ports, etc.
876
    // we have to open a dialog to change the device settings.
877

878
    const auto& json = readJson(mimeData->data(mimeType));
×
879
    JSONObject::Deserializer deser{json};
×
880
    Device::Node n;
×
881
    deser.writeTo(n);
×
882

883
    if(mimeType == score::mime::device())
×
884
    {
885
      checkAndLoadDevice(n);
×
886
    }
×
887
    return true;
×
888
  }
×
889
  return false;
×
890
}
×
891

892
void DeviceExplorerModel::checkAndLoadDevice(Device::Node n)
×
893
{
894
  SCORE_ASSERT(n.is<Device::DeviceSettings>());
×
895
  auto& deviceSettings = *n.target<Device::DeviceSettings>();
×
896

897
  // We ask the user to fix the incompatibilities by himself, maybe change
898
  // the name also.
899
  auto dialog = new DeviceEditDialog{
×
900
      *this, m_devicePlugin.context().app.interfaces<Device::ProtocolFactoryList>(),
×
901
      DeviceEditDialog::Creating, QApplication::activeWindow()};
×
902

903
  dialog->setSettings(deviceSettings);
×
904
  dialog->setBrowserEnabled(false);
×
905

906
  connect(
×
907
      dialog, &QDialog::accepted, this, [this, dialog, node = std::move(n)]() mutable {
×
908
        auto new_settings = dialog->getSettings();
×
909
        auto& deviceSettings = *node.target<Device::DeviceSettings>();
×
910
        deviceSettings = new_settings;
×
911
        if(!checkDeviceInstantiatable(deviceSettings))
×
912
        {
913
          score::warning(
×
914
              m_devicePlugin.context().app.mainWindow, "Error",
×
915
              "Could not create the device");
×
916
          dialog->deleteLater();
×
917
          return;
×
918
        }
919
        ossia::net::sanitize_device_name(deviceSettings.name);
×
920

921
        auto cmd = new Command::LoadDevice{deviceModel(), std::move(node)};
×
922
        m_cmdQ.redoAndPush(cmd);
×
923
        dialog->deleteLater();
×
924
      });
×
925
  connect(dialog, &QDialog::rejected, this, [dialog] { dialog->deleteLater(); });
×
926
  dialog->show();
×
927
}
×
928

929
void DeviceExplorerModel::checkAndLoadDevice(Device::DeviceSettings deviceSettings)
×
930
{
931
  // We ask the user to fix the incompatibilities by himself, maybe change
932
  // the name also.
933
  auto dialog = new DeviceEditDialog{
×
934
      *this, m_devicePlugin.context().app.interfaces<Device::ProtocolFactoryList>(),
×
935
      DeviceEditDialog::Creating, QApplication::activeWindow()};
×
936

937
  dialog->setSettings(deviceSettings);
×
938
  dialog->setBrowserEnabled(false);
×
939

940
  connect(dialog, &QDialog::accepted, this, [this, dialog] {
×
941
    auto node = dialog->getDevice();
×
942
    auto& deviceSettings = *node.target<Device::DeviceSettings>();
×
943
    if(!checkDeviceInstantiatable(deviceSettings))
×
944
    {
945
      score::warning(
×
946
          m_devicePlugin.context().app.mainWindow, "Error",
×
947
          "Could not create the device");
×
948
      dialog->deleteLater();
×
949
      return;
×
950
    }
951
    ossia::net::sanitize_device_name(deviceSettings.name);
×
952

953
    auto cmd = new Command::LoadDevice{deviceModel(), std::move(node)};
×
954
    m_cmdQ.redoAndPush(cmd);
×
955
    dialog->deleteLater();
×
956
  });
×
957
  connect(dialog, &QDialog::rejected, this, [dialog] { dialog->deleteLater(); });
×
958
  dialog->show();
×
959
}
×
960

961
void DeviceExplorerModel::debug_printPath(const Device::NodePath& path)
×
962
{
963
  const int pathSize = std::ssize(path);
×
964

965
  for(int i = 0; i < pathSize; ++i)
×
966
  {
967
    std::cerr << path.at(i) << " ";
×
968
  }
×
969

970
  std::cerr << "\n";
×
971
}
×
972

973
void DeviceExplorerModel::debug_printIndexes(const QModelIndexList& indexes)
×
974
{
975
  std::cerr << "indexes: " << indexes.size() << " nodes: \n";
×
976
  Q_FOREACH(const QModelIndex& index, indexes)
×
977
  {
978
    if(index.isValid())
×
979
    {
980
      std::cerr << " index.row=" << index.row() << " col=" << index.column() << " ";
×
981
      Device::Node* n = &nodeFromModelIndex(index);
×
982
      std::cerr << " n=" << n << " ";
×
983
      Device::Node* parent = n->parent();
×
984

985
      if(n == &m_rootNode)
×
986
      {
987
        std::cerr << " rootNode parent=" << parent << "\n";
×
988
      }
×
989
      else
990
      {
991
        std::cerr << " n->name=" << n->displayName().toStdString();
×
992
        std::cerr << " parent=" << parent;
×
993
        std::cerr << " parent->name=" << parent->displayName().toStdString() << "\n";
×
994
      }
995
    }
×
996
    else
997
    {
998
      std::cerr << " invalid index \n";
×
999
    }
1000
  }
1001
}
×
1002

1003
State::MessageList getSelectionSnapshot(DeviceExplorerModel& model)
×
1004
{
1005
  // Filter
1006
  auto uniqueNodes = model.uniqueSelectedNodes(model.selectedIndexes());
×
1007

1008
  // Recursive refresh
1009
  model.deviceModel().updateProxy.refreshRemoteValues(uniqueNodes.parents);
×
1010

1011
  // Conversion
1012
  State::MessageList messages;
×
1013
  messages.reserve(uniqueNodes.parents.size() + uniqueNodes.messages.size());
×
1014
  for(const auto& node : uniqueNodes.parents)
×
1015
  {
1016
    Device::parametersList(*node, messages);
×
1017
  }
1018
  for(const auto& node : uniqueNodes.messages)
×
1019
  {
1020
    messages.push_back(Device::message(*node));
×
1021
  }
1022

1023
  return messages;
×
1024
}
×
1025

1026
DeviceExplorerModel* try_deviceExplorerFromObject(const QObject& obj)
270✔
1027
{
1028
  auto plug = score::IDocument::documentContext(obj).findPlugin<DeviceDocumentPlugin>();
270✔
1029
  if(plug)
270✔
1030
    return &plug->explorer();
270✔
1031
  return nullptr;
×
1032
}
270✔
1033

1034
DeviceExplorerModel& deviceExplorerFromObject(const QObject& obj)
3✔
1035
{
1036
  auto expl = try_deviceExplorerFromObject(obj);
3✔
1037
  SCORE_ASSERT(expl);
3✔
1038
  return *expl;
3✔
1039
}
1040

1041
DeviceExplorerModel* try_deviceExplorerFromContext(const score::DocumentContext& ctx)
6✔
1042
{
1043
  auto plug = ctx.findPlugin<DeviceDocumentPlugin>();
6✔
1044
  if(plug)
6✔
1045
    return &plug->explorer();
6✔
1046
  return nullptr;
×
1047
}
6✔
1048

1049
DeviceExplorerModel& deviceExplorerFromContext(const score::DocumentContext& ctx)
6✔
1050
{
1051
  auto expl = try_deviceExplorerFromContext(ctx);
6✔
1052
  SCORE_ASSERT(expl);
6✔
1053
  return *expl;
6✔
1054
}
1055

1056
Device::FullAddressAccessorSettings makeFullAddressAccessorSettings(
×
1057
    const State::AddressAccessor& addr, const score::DocumentContext& ctx,
1058
    ossia::value min, ossia::value max, ossia::value val)
1059
{
1060
  // First try to find if there is a matching address
1061
  // in the device explorer
1062
  auto deviceexplorer = Explorer::try_deviceExplorerFromContext(ctx);
×
1063
  if(deviceexplorer)
×
1064
  {
1065
    return Device::makeFullAddressAccessorSettings(
×
1066
        addr, *deviceexplorer, std::move(min), std::move(max), std::move(val));
×
1067
  }
1068

1069
  // If there is none, build with some default settings
1070
  Device::FullAddressAccessorSettings s;
×
1071
  s.address = addr;
×
1072
  s.value = std::move(val);
×
1073
  s.domain = ossia::make_domain(std::move(min), std::move(max));
×
1074
  return s;
×
1075
}
×
1076
}
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