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

ossia / score / 33834678478

04 Sep 2026 03:50AM UTC coverage: 24.333% (+0.3%) from 23.997%
33834678478

push

github

jcelerier
tests: the editor-look cases assumed a Windows desktop

Three of them failed under the coverage job's Xvfb display.

Redo is Ctrl+Y only on Windows -- qplatformtheme.cpp binds that one under
KB_Win, and X11 and macOS use Ctrl+Shift+Z -- so the redo half of the hex
undo case tested nothing there. Ask QKeySequence instead.

The other two turn on an editor committing when the focus leaves it, and
QWidget::setFocus only routes through setFocusWidget in an active window.
There is no window manager on that display, so the view was shown but never
activated and no focus event was ever sent: the value simply stayed put.
The fixture now activates the window and waits for it, and the cases that
depend on it say so -- one of them was passing on an empty check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014L61CWsHUpxm7opHyZbsKu

55424 of 227776 relevant lines covered (24.33%)

63160.69 hits per line

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

37.3
/src/plugins/score-plugin-deviceexplorer/Explorer/Explorer/AddressItemModel.cpp
1
#include "AddressItemModel.hpp"
2

3
#include <State/Expression.hpp>
4
#include <State/ValueConversion.hpp>
5
#include <State/Widgets/UnitWidget.hpp>
6
#include <State/Widgets/Values/ExpandableTextEdit.hpp>
7

8
#include <Explorer/Commands/Update/UpdateAddressSettings.hpp>
9
#include <Explorer/Common/AddressSettings/Widgets/AddressSettingsWidget.hpp>
10
#include <Explorer/DocumentPlugin/DeviceDocumentPlugin.hpp>
11
#include <Explorer/DocumentPlugin/NodeUpdateProxy.hpp>
12

13
#include <score/command/Dispatchers/CommandDispatcher.hpp>
14
#include <score/serialization/AnySerialization.hpp>
15
#include <score/serialization/MapSerialization.hpp>
16
#include <score/widgets/DoubleSlider.hpp>
17
#include <score/widgets/IntSlider.hpp>
18
#include <score/widgets/MarginLess.hpp>
19
#include <score/widgets/SignalUtils.hpp>
20

21
#include <ossia/network/base/node_attributes.hpp>
22
#include <ossia/network/dataspace/dataspace_visitors.hpp>
23
#include <ossia/network/domain/domain.hpp>
24
#include <ossia/network/value/value_traits.hpp>
25

26
#include <ossia-qt/metatypes.hpp>
27

28
#include <QHBoxLayout>
29
#include <QLineEdit>
30
#include <QPainter>
31
#include <QPointer>
32
#include <QSpinBox>
33
#include <QTimer>
34

35
#include <wobjectimpl.h>
36

37
Q_DECLARE_METATYPE(ossia::net::tags)
×
38
W_OBJECT_IMPL(Explorer::AddressItemModel)
12,916✔
39

40
namespace Explorer
41
{
42
AddressItemModel::AddressItemModel(QObject* parent)
266✔
43
    : QAbstractItemModel{parent}
133✔
44
{
133✔
45
}
133✔
46

47
void AddressItemModel::setState(
10✔
48
    DeviceExplorerModel* model, Device::NodePath nodepath,
49
    const Device::FullAddressSettings& s)
50
{
51
  beginResetModel();
10✔
52
  m_model = model;
10✔
53
  m_path = nodepath;
10✔
54
  m_settings = s;
10✔
55

56
  endResetModel();
10✔
57
}
10✔
58

59
void AddressItemModel::clear()
342✔
60
{
61
  beginResetModel();
342✔
62
  m_settings = {};
342✔
63
  m_model = nullptr;
342✔
64
  m_path = {};
342✔
65
  endResetModel();
342✔
66
}
342✔
67

68
bool AddressItemModel::setData(const QModelIndex& index, const QVariant& value, int role)
12✔
69
{
70
  namespace onet = ossia::net;
71
  if(index.column() != 1)
12✔
72
    return false;
×
73

74
  if(role != Qt::EditRole && role != Qt::CheckStateRole)
12✔
75
    return false;
1✔
76

77
  if(index.row() < 0 || index.row() >= rowCount({}))
11✔
78
    return false;
×
79

80
  if(!m_model)
11✔
81
    return false;
×
82

83
  if(m_settings.address.path.empty())
11✔
84
    return false; // TODO rename the device itself
×
85

86
  auto* node = static_cast<Device::Node*>(
11✔
87
      m_model->convertPathToIndex(m_path).internalPointer());
11✔
88
  if(!node || !node->is<Device::AddressSettings>() || !node->parent())
11✔
89
    return false;
×
90

91
  const auto address = Device::address(*node).address;
11✔
92

93
  const bool checked = (role == Qt::CheckStateRole)
22✔
94
                           ? (static_cast<Qt::CheckState>(value.toInt()) == Qt::Checked)
2✔
95
                           : value.toBool();
9✔
96

97
  const Device::AddressSettings before = node->get<Device::AddressSettings>();
11✔
98
  Device::AddressSettings after = before;
11✔
99

100
  switch(index.row())
11✔
101
  {
102
    case Rows::Value: {
103
      if(role == Qt::CheckStateRole && before.value.target<bool>())
4✔
104
      {
105
        after.value = checked;
2✔
106
        m_model->deviceModel().updateProxy.updateRemoteValue(address, after.value);
2✔
107
        pushedValue(address, after.value);
2✔
108
        return true;
2✔
109
      }
110
      else if(value.canConvert<ossia::value>())
2✔
111
      {
112
        after.value = value.value<ossia::value>();
2✔
113

114
        // Note : if we want to disable remote updating, we have to do it
115
        // here (e.g. if this becomes a settings)
116
        m_model->deviceModel().updateProxy.updateRemoteValue(address, after.value);
2✔
117
        pushedValue(address, after.value);
2✔
118
        return true;
2✔
119
      }
120
      else
121
      {
122
        // In this case we don't make a command, but we directly push the
123
        // new value.
124
        auto copy = State::convert::fromQVariant(value);
×
125

126
        // We may have to convert types.
127
        const ossia::value& orig = before.value;
×
128
        if(copy.v.which() != orig.v.which() && !State::convert::convert(orig, copy))
×
129
          return false;
×
130

131
        after.value = copy;
×
132

133
        // Note : if we want to disable remote updating, we have to do it
134
        // here (e.g. if this becomes a settings)
135
        m_model->deviceModel().updateProxy.updateRemoteValue(address, copy);
×
136
        pushedValue(address, after.value);
×
137
        return true;
×
138
      }
×
139
    }
140

141
    case Rows::Name: {
142
      const auto name = value.toString();
1✔
143
      if(name.isEmpty() || !m_model->canRenameNode(*node))
1✔
144
        return false;
×
145
      after.name = name;
1✔
146
      break;
1✔
147
    }
1✔
148

149
    case Rows::Type: {
150
      const auto type = value.value<ossia::val_type>();
1✔
151
      after.value = ossia::convert(before.value, type);
1✔
152

153
      if(after.value.get_type() != before.value.get_type())
1✔
154
        after.domain = ossia::init_domain(type);
1✔
155
      break;
1✔
156
    }
157

158
    case Rows::Min: {
159
      if(value.canConvert<ossia::value>())
×
160
      {
161
        after.domain.get().set_min(value.value<ossia::value>());
×
162
      }
×
163
      else
164
      {
165
        // In this case we don't make a command, but we directly push the
166
        // new value.
167
        auto copy = State::convert::fromQVariant(value);
×
168

169
        // We may have to convert types.
170
        const ossia::value& orig = before.value;
×
171
        if(copy.v.which() != orig.v.which() && !State::convert::convert(orig, copy))
×
172
          return false;
×
173

174
        after.domain.get().set_min(copy);
×
175
      }
×
176
      break;
×
177
    }
178
    case Rows::Max: {
179
      if(value.canConvert<ossia::value>())
×
180
      {
181
        after.domain.get().set_max(value.value<ossia::value>());
×
182
      }
×
183
      else
184
      {
185
        // In this case we don't make a command, but we directly push the
186
        // new value.
187
        auto copy = State::convert::fromQVariant(value);
×
188

189
        // We may have to convert types.
190
        const ossia::value& orig = before.value;
×
191
        if(copy.v.which() != orig.v.which() && !State::convert::convert(orig, copy))
×
192
          return false;
×
193

194
        after.domain.get().set_max(copy);
×
195
      }
×
196
      break;
×
197
    }
198
    case Rows::Values: {
199
      auto vals = State::convert::value<std::vector<ossia::value>>(
1✔
200
          value.canConvert<ossia::value>() ? value.value<ossia::value>()
1✔
201
                                           : State::convert::fromQVariant(value));
×
202

203
      if(before.value.valid())
1✔
204
      {
205
        for(auto& v : vals)
4✔
206
        {
207
          if(v.get_type() != before.value.get_type())
3✔
208
            State::convert::convert(before.value, v);
×
209
        }
210
      }
1✔
211

212
      auto& dom = after.domain.get();
1✔
213
      if(dom)
1✔
214
      {
215
        ossia::set_values(dom, vals);
1✔
216
      }
1✔
217
      else if(!vals.empty())
×
218
      {
219
        // ossia has no domain for every type, and building an unknown one throws.
220
        auto fresh = ossia::init_domain(vals.front().get_type());
×
221
        if(!fresh)
×
222
          return false;
×
223
        ossia::set_values(fresh, vals);
×
224
        dom = std::move(fresh);
×
225
      }
×
226
      break;
1✔
227
    }
1✔
228
    case Rows::Access: {
229
      after.ioType = (ossia::access_mode)value.toInt();
1✔
230
      break;
1✔
231
    }
232
    case Rows::Bounding: {
233
      after.clipMode = (ossia::bounding_mode)value.toInt();
×
234
      break;
×
235
    }
236
    case Rows::Repetition: {
237
      after.repetitionFilter = value.toInt() != 0 ? ossia::repetition_filter::ON
×
238
                                                  : ossia::repetition_filter::OFF;
239
      break;
×
240
    }
241
    case Rows::Unit: {
242
      after.unit.get() = value.value<State::Unit>().get();
×
243
      break;
×
244
    }
245
    default: {
246
      int idx = index.row() - Rows::Count;
3✔
247
      if(ossia::valid_index(idx, after.extendedAttributes))
3✔
248
      {
249
        auto it = after.extendedAttributes.begin();
2✔
250
        std::advance(it, idx);
2✔
251
        if(it->first == onet::text_description())
2✔
252
        {
253
          if(auto* cur = ossia::any_cast<onet::description>(&it->second);
×
254
             cur && *cur == value.toString().toStdString())
×
255
            return false;
×
256
          it->second = value.toString().toStdString();
×
257
        }
×
258
        else if(it->first == onet::text_tags())
2✔
259
        {
260
          onet::tags tags;
2✔
261
          for(const auto& tag : value.toString().split(',', Qt::SkipEmptyParts))
6✔
262
          {
263
            const auto trimmed = tag.trimmed();
4✔
264
            if(!trimmed.isEmpty())
4✔
265
              tags.push_back(trimmed.toStdString());
4✔
266
          }
4✔
267

268
          if(auto* cur = ossia::any_cast<onet::tags>(&it->second); cur && *cur == tags)
2✔
269
            return false;
1✔
270
          it->second = std::move(tags);
1✔
271
        }
2✔
272
        else if(it->first == onet::text_default_value())
×
273
        {
274
          if(value.canConvert<ossia::value>())
×
275
          {
276
            it->second = value.value<ossia::value>();
×
277
          }
×
278
        }
×
279
        else if(it->first == onet::text_refresh_rate())
×
280
        {
281
          if(auto* cur = ossia::any_cast<onet::refresh_rate_attribute::type>(&it->second);
×
282
             cur && *cur == value.toInt())
×
283
            return false;
×
284
          it->second = value.toInt();
×
285
        }
×
286
        else if(it->first == onet::text_value_step_size())
×
287
        {
288
          it->second = value.toDouble();
×
289
        }
×
290
        else if(it->first == onet::text_priority())
×
291
        {
292
          it->second = value.toInt();
×
293
        }
×
294
      }
1✔
295
    }
296
  }
2✔
297

298
  // Extended attributes are excluded: ossia::any does not compare.
299
  if(index.row() < Rows::Count && after == before)
6✔
300
    return false;
2✔
301

302
  if(!m_model->checkAddressEditable(*node->parent(), before, after))
4✔
303
    return false;
×
304

305
  CommandDispatcher<> disp{m_model->commandStack()};
4✔
306
  disp.submit(new Explorer::Command::UpdateAddressSettings{
8✔
307
      m_model->deviceModel(), m_path, after});
4✔
308

309
  return true;
4✔
310
}
14✔
311

312
QModelIndex AddressItemModel::index(int row, int column, const QModelIndex& parent) const
17✔
313
{
314
  if(parent == QModelIndex{})
17✔
315
  {
316
    return createIndex(row, column, nullptr);
17✔
317
  }
318
  return {};
×
319
}
17✔
320

321
QModelIndex AddressItemModel::parent(const QModelIndex&) const
×
322
{
323
  return {};
×
324
}
325

326
int AddressItemModel::rowCount(const QModelIndex&) const
1,831✔
327
{
328
  if(m_settings.address.device.isEmpty())
1,831✔
329
    return 0;
1,818✔
330

331
  if(!m_settings.value.valid())
13✔
332
    return 2;
×
333

334
  return Rows::Count + extendedCount();
13✔
335
}
1,831✔
336

337
int AddressItemModel::columnCount(const QModelIndex&) const
834✔
338
{
339
  return 2;
834✔
340
}
341

342
QVariant AddressItemModel::valueColumnData(const State::Value& val, int role) const
1✔
343
{
344
  if(role == Qt::DisplayRole || role == Qt::EditRole)
1✔
345
  {
346
    if(ossia::is_array(val))
1✔
347
    {
348
      return State::convert::toPrettyString(val);
×
349
    }
350
    else if(role == Qt::DisplayRole && val.get_type() == ossia::val_type::STRING)
1✔
351
    {
352
      return State::convert::stringCellText(
1✔
353
          QByteArray::fromStdString(*val.target<std::string>()));
1✔
354
    }
355
    else
356
    {
357
      return State::convert::value<QVariant>(val);
×
358
    }
359
  }
360
  else if(role == Qt::ToolTipRole)
×
361
  {
362
    // What the one-line row had to fold away.
363
    if(const auto* s = val.target<std::string>())
×
364
    {
365
      const auto tip = State::convert::stringCellToolTip(QByteArray::fromStdString(*s));
×
366
      if(!tip.isEmpty())
×
367
        return tip;
×
368
    }
×
369
  }
×
370

371
  return {};
×
372
}
1✔
373

374
QVariant AddressItemModel::data(const QModelIndex& index, int role) const
4✔
375
{
376
  namespace onet = ossia::net;
377
  if(role == Qt::DisplayRole)
4✔
378
  {
379
    if(!m_settings.value.valid())
2✔
380
    {
381
      switch(index.column())
×
382
      {
383
        case 0: {
384
          switch(index.row())
×
385
          {
386
            case Rows::Name:
387
              return tr("Name");
×
388
            case Rows::Address:
389
              return tr("Address");
×
390
            default:
391
              break;
×
392
          }
393
          break;
×
394
        }
395
        case 1: {
396
          switch(index.row())
×
397
          {
398
            case Rows::Name:
399
              return m_settings.address.path.last();
×
400
            case Rows::Address:
401
              return m_settings.address.toString();
×
402
            default:
403
              break;
×
404
          }
405
          break;
×
406
        }
407
        default:
408
          break;
×
409
      }
410
      return {};
×
411
    }
412

413
    switch(index.column())
2✔
414
    {
415
      case 0: {
416
        switch(index.row())
1✔
417
        {
418
          case Rows::Name:
419
            return tr("Name");
×
420
          case Rows::Address:
421
            return tr("Address");
×
422
          case Rows::Value:
423
            return tr("Value");
×
424
          case Rows::Type:
425
            return tr("Type");
×
426
          case Rows::Min:
427
            return tr("Min");
×
428
          case Rows::Max:
429
            return tr("Max");
×
430
          case Rows::Values:
431
            return tr("Values");
×
432
          case Rows::Unit:
433
            return tr("Unit");
×
434
          case Rows::Access:
435
            return tr("Access");
×
436
          case Rows::Bounding:
437
            return tr("Bounding");
×
438
          case Rows::Repetition:
439
            return tr("Repetition");
×
440
          default: {
441
            int idx = index.row() - Rows::Count;
1✔
442
            if(ossia::valid_index(idx, m_settings.extendedAttributes))
1✔
443
            {
444
              auto it = m_settings.extendedAttributes.begin();
1✔
445
              std::advance(it, idx);
1✔
446
              auto str = QString::fromStdString(it->first);
1✔
447
              if(!str.isEmpty())
1✔
448
                str[0] = str[0].toUpper();
1✔
449
              for(int i = 1; i < str.size(); i++)
4✔
450
              {
451
                if(str[i].isUpper())
3✔
452
                {
453
                  str.insert(i, ' ');
×
454
                  i++;
×
455
                }
×
456
              }
3✔
457
              return str;
1✔
458
            }
1✔
459
          }
460
        }
×
461

462
        break;
×
463
      }
464
      case 1: {
465
        switch(index.row())
1✔
466
        {
467
          case Rows::Name:
468
            return m_settings.address.path.last();
×
469
          case Rows::Address:
470
            return m_settings.address.toString();
×
471
          case Rows::Value:
472
            return valueColumnData(m_settings.value, role);
1✔
473
          case Rows::Type: {
474
            return State::convert::ValuePrettyTypesArray()[(int)m_settings.value
×
475
                                                               .get_type()];
×
476
          }
477
          case Rows::Min: {
478
            return valueColumnData(ossia::get_min(m_settings.domain.get()), role);
×
479
          }
480
          case Rows::Max: {
481
            return valueColumnData(ossia::get_max(m_settings.domain.get()), role);
×
482
          }
483
          case Rows::Values: {
484
            return valueColumnData(ossia::get_values(m_settings.domain.get()), role);
×
485
          }
486
          case Rows::Unit: {
487
            return State::prettyUnitText(m_settings.unit.get());
×
488
          }
489
          case Rows::Access: {
490
            if(m_settings.ioType)
×
491
            {
492
              return Device::AccessModePrettyText()[*m_settings.ioType];
×
493
            }
494
            else
495
            {
496
              return tr("None");
×
497
            }
498
          }
499
          case Rows::Bounding: {
500
            return Device::ClipModePrettyStringMap()[m_settings.clipMode];
×
501
          }
502
          case Rows::Repetition: {
503
            return m_settings.repetitionFilter == ossia::repetition_filter::ON
×
504
                       ? tr("Filtered")
×
505
                       : tr("Unfiltered");
×
506
          }
507
          default: {
508
            int idx = index.row() - Rows::Count;
×
509
            if(ossia::valid_index(idx, m_settings.extendedAttributes))
×
510
            {
511
              auto it = m_settings.extendedAttributes.begin();
×
512
              std::advance(it, idx);
×
513
              if(it->first == onet::text_description())
×
514
              {
515
                return State::convert::toSingleLine(QString::fromStdString(
×
516
                    ossia::any_cast<onet::description>(it->second)));
×
517
              }
518
              else if(it->first == onet::text_tags())
×
519
              {
520
                const auto& tags = ossia::any_cast<onet::tags>(it->second);
×
521

522
                QStringList l;
×
523
                for(const auto& s : tags)
×
524
                  l += QString::fromStdString(s);
×
525
                return l.join(", ");
×
526
              }
×
527
              else if(it->first == onet::text_default_value())
×
528
              {
529
                const auto& v
×
530
                    = ossia::any_cast<onet::default_value_attribute::type>(it->second);
×
531
                return valueColumnData(v, role);
×
532
              }
×
533
              else if(it->first == onet::text_refresh_rate())
×
534
              {
535
                return ossia::any_cast<onet::refresh_rate_attribute::type>(it->second);
×
536
              }
537
              else if(it->first == onet::text_value_step_size())
×
538
              {
539
                return ossia::any_cast<onet::value_step_size_attribute::type>(
×
540
                    it->second);
×
541
              }
542
              else if(it->first == onet::text_priority())
×
543
              {
544
                return ossia::any_cast<onet::priority_attribute::type>(it->second);
×
545
              }
546
            }
×
547
            return {};
×
548
          }
549
        }
550

551
        break;
552
      }
553
      default:
554
        break;
×
555
    }
556
  }
×
557
  else if(role == Qt::EditRole)
2✔
558
  {
559
    if(index.column() == 1)
2✔
560
    {
561
      switch(index.row())
2✔
562
      {
563
        case Rows::Name:
564
          return m_settings.address.path.last();
1✔
565
        case Rows::Type:
566
          return (int)m_settings.value.get_type();
×
567
        case Rows::Access:
568
          return m_settings.ioType ? (int)*m_settings.ioType : -1;
×
569
        case Rows::Bounding:
570
          return (int)m_settings.clipMode;
×
571
        case Rows::Unit:
572
          return QVariant::fromValue(m_settings.unit);
×
573
        case Rows::Value:
574
          return QVariant::fromValue(m_settings.value);
×
575
        case Rows::Min:
576
          return QVariant::fromValue(ossia::get_min(m_settings.domain.get()));
×
577
        case Rows::Max:
578
          return QVariant::fromValue(ossia::get_max(m_settings.domain.get()));
×
579
        case Rows::Values:
580
          // As one value, so that the editor reads it like any other.
581
          return QVariant::fromValue(
1✔
582
              ossia::value{ossia::get_values(m_settings.domain.get())});
1✔
583
        default: {
584
          int idx = index.row() - Rows::Count;
×
585
          if(ossia::valid_index(idx, m_settings.extendedAttributes))
×
586
          {
587
            auto it = m_settings.extendedAttributes.begin();
×
588
            std::advance(it, idx);
×
589
            if(it->first == onet::text_description())
×
590
            {
591
              return QString::fromStdString(
×
592
                  ossia::any_cast<onet::description>(it->second));
×
593
            }
594
            else if(it->first == onet::text_tags())
×
595
            {
596
              return QVariant::fromValue(ossia::any_cast<onet::tags>(it->second));
×
597
            }
598
            else if(it->first == onet::text_default_value())
×
599
            {
600
              return QVariant::fromValue(
×
601
                  ossia::any_cast<onet::default_value_attribute::type>(it->second));
×
602
            }
603
            else if(it->first == onet::text_refresh_rate())
×
604
            {
605
              return ossia::any_cast<onet::refresh_rate_attribute::type>(it->second);
×
606
            }
607
            else if(it->first == onet::text_value_step_size())
×
608
            {
609
              return ossia::any_cast<onet::value_step_size_attribute::type>(it->second);
×
610
            }
611
            else if(it->first == onet::text_priority())
×
612
            {
613
              return ossia::any_cast<onet::priority_attribute::type>(it->second);
×
614
            }
615
          }
×
616
          return {};
×
617
        }
618
      }
619
    }
620
    else
621
    {
622
      return {};
×
623
    }
624
  }
625
  else if(role == Qt::ToolTipRole)
×
626
  {
627
    // What a one-line row had to fold away, and nothing otherwise.
628
    if(index.column() != 1)
×
629
      return {};
×
630

631
    if(index.row() == Rows::Value)
×
632
      return valueColumnData(m_settings.value, role);
×
633

634
    const int idx = index.row() - Rows::Count;
×
635
    if(ossia::valid_index(idx, m_settings.extendedAttributes))
×
636
    {
637
      auto it = m_settings.extendedAttributes.begin();
×
638
      std::advance(it, idx);
×
639
      if(it->first == onet::text_description())
×
640
      {
641
        const auto text
642
            = QString::fromStdString(ossia::any_cast<onet::description>(it->second));
×
643
        if(State::convert::isMultiLine(text))
×
644
          return text;
×
645
      }
×
646
      else if(it->first == onet::text_tags())
×
647
      {
648
        return tr("One tag per comma");
×
649
      }
650
    }
×
651
    return {};
×
652
  }
653
  else if(role == Qt::CheckStateRole)
×
654
  {
655
    if(index.column() == 1)
×
656
    {
657
      switch(index.row())
×
658
      {
659
        case Rows::Repetition:
660
          return m_settings.repetitionFilter == ossia::repetition_filter::ON
×
661
                     ? Qt::Checked
662
                     : Qt::Unchecked;
663
        case Rows::Value: {
664
          if(auto b = m_settings.value.target<bool>())
×
665
          {
666
            return *b ? Qt::Checked : Qt::Unchecked;
×
667
          }
668
        }
×
669
        default:
670
          break;
×
671
      }
672
    }
×
673
  }
×
674

675
  return {};
×
676
}
4✔
677

678
bool AddressItemModel::editableProperties() const
1✔
679
{
680
  if(!m_model)
1✔
681
    return false;
×
682
  auto* dev = m_model->deviceModel().list().findDevice(m_settings.address.device);
1✔
683
  return dev && dev->capabilities().canSetProperties;
1✔
684
}
1✔
685

686
bool AddressItemModel::editableName() const
1✔
687
{
688
  if(!m_model)
1✔
689
    return false;
×
690

691
  auto* node = static_cast<Device::Node*>(
1✔
692
      m_model->convertPathToIndex(m_path).internalPointer());
1✔
693
  return node && m_model->canRenameNode(*node);
1✔
694
}
1✔
695

696
void AddressItemModel::pushedValue(const State::Address& addr, const ossia::value& v)
4✔
697
{
698
  m_settings.value = v;
4✔
699
  const auto idx = index(Rows::Value, 1, {});
4✔
700
  dataChanged(idx, idx);
4✔
701

702
  // Deferred: the tree pushes the change back here and resets this model.
703
  // An impulse is not a state: there is nothing to mirror.
704
  if(v.get_type() == ossia::val_type::IMPULSE)
4✔
705
    return;
×
706

707
  QPointer self{this};
4✔
708
  QTimer::singleShot(0, this, [self, addr, v] {
5✔
709
    if(self && self->m_model)
1✔
710
      self->m_model->deviceModel().updateProxy.updateLocalValue(
2✔
711
          State::AddressAccessor{addr}, v);
1✔
712
  });
1✔
713
}
4✔
714

715
int AddressItemModel::extendedCount() const noexcept
13✔
716
{
717
  return int(m_settings.extendedAttributes.size());
13✔
718
}
719

720
Qt::ItemFlags AddressItemModel::flags(const QModelIndex& index) const
2✔
721
{
722
  if(index.column() == 0)
2✔
723
    return {Qt::ItemIsEnabled};
×
724

725
  Qt::ItemFlags f = QAbstractItemModel::flags(index);
2✔
726
  static const constexpr std::array<Qt::ItemFlags, Rows::Count> flags{{
727
      {Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable} // name
728
      ,
729
      {Qt::ItemIsEnabled | Qt::ItemIsSelectable} // address
730
      ,
731
      {Qt::ItemIsEditable} // value
732
      ,
733
      {Qt::ItemIsEditable} // type
734
      ,
735
      {Qt::ItemIsEditable} // min
736
      ,
737
      {Qt::ItemIsEditable} // max
738
      ,
739
      {Qt::ItemIsEditable} // values
740
      ,
741
      {Qt::ItemIsEditable} // unit
742
      ,
743
      {Qt::ItemIsEditable} // access
744
      ,
745
      {Qt::ItemIsEditable} // bounding
746
      ,
747
      {Qt::ItemIsUserCheckable | Qt::ItemIsEnabled} // repetition
748
  }};
749

750
  if(index.row() < Rows::Count)
2✔
751
    f |= flags[index.row()];
2✔
752
  else
753
    f |= Qt::ItemIsEditable;
×
754

755
  // Every row but the value describes the parameter rather than holding it.
756
  if(index.row() != Rows::Value && !editableProperties())
2✔
757
    f &= ~Qt::ItemIsEditable;
×
758

759
  if(index.row() == Rows::Name && !editableName())
2✔
760
    f &= ~Qt::ItemIsEditable;
×
761

762
  if(index.row() == Value)
2✔
763
  {
764
    if(m_settings.value.target<bool>())
1✔
765
    {
766
      f |= Qt::ItemIsUserCheckable;
1✔
767
      f |= Qt::ItemIsEnabled;
1✔
768
    }
1✔
769
  }
1✔
770

771
  return f;
2✔
772
}
2✔
773

774
namespace
775
{
776
namespace onet = ossia::net;
777

778
//! The extended attribute a row past Rows::Count stands for, or nothing.
779
const ossia::extended_attributes::value_type*
780
extendedAttributeAt(const AddressItemModel& model, const QModelIndex& index)
×
781
{
782
  const auto& attrs = model.settings().extendedAttributes;
×
783
  const int idx = index.row() - AddressItemModel::Rows::Count;
×
784
  if(!ossia::valid_index(idx, attrs))
×
785
    return nullptr;
×
786

787
  auto it = attrs.begin();
×
788
  std::advance(it, idx);
×
789
  return &*it;
×
790
}
×
791
}
792

793
AddressItemDelegate::AddressItemDelegate(QObject* parent)
124✔
794
    : QStyledItemDelegate(parent)
124✔
795
{
124✔
796
}
124✔
797

798
AddressItemDelegate::~AddressItemDelegate() { }
1✔
799

800
void AddressItemDelegate::paint(
×
801
    QPainter* painter, const QStyleOptionViewItem& option,
802
    const QModelIndex& index) const
803
{
804
  QStyledItemDelegate::paint(painter, option, index);
×
805
}
×
806

807
QWidget* AddressItemDelegate::createEditor(
1✔
808
    QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const
809
{
810
  auto model = qobject_cast<const AddressItemModel*>(index.model());
1✔
811
  if(index.column() == 0 || !model)
1✔
812
    return QStyledItemDelegate::createEditor(parent, option, index);
×
813

814
  switch(index.row())
1✔
815
  {
816
    case AddressItemModel::Rows::Type: {
817
      auto t = new State::TypeComboBox{parent};
×
818
      t->set(index.data(Qt::EditRole).value<ossia::val_type>());
×
819
      return t;
×
820
    }
821
    case AddressItemModel::Rows::Access: {
822
      auto t = new Explorer::AccessModeComboBox{parent};
×
823
      t->set(index.data(Qt::EditRole).value<ossia::access_mode>());
×
824
      return t;
×
825
    }
826
    case AddressItemModel::Rows::Bounding: {
827
      auto t = new Explorer::BoundingModeComboBox{parent};
×
828
      t->set(index.data(Qt::EditRole).value<ossia::bounding_mode>());
×
829
      return t;
×
830
    }
831
    case AddressItemModel::Rows::Unit: {
832
      auto t = new State::UnitWidget{Qt::Horizontal, parent};
×
833
      t->setUnit(index.data(Qt::EditRole).value<State::Unit>());
×
834
      return t;
×
835
    }
836
    case AddressItemModel::Rows::Value: {
837
      if(auto t = make_value_widget(model->settings(), parent))
×
838
      {
839
        if(t->commitsImmediately())
×
840
        {
841
          auto* self = const_cast<AddressItemDelegate*>(this);
×
842
          connect(t, &AddressValueWidget::changed, self, [self, t](const ossia::value&) {
×
843
            self->commitData(t);
×
844
          });
×
845
        }
×
846
        return t;
×
847
      }
848
      break;
×
849
    }
850
    case AddressItemModel::Rows::Min:
851
    case AddressItemModel::Rows::Max: {
852
      if(auto t = make_bound_widget(model->settings(), parent))
×
853
        return t;
×
854
      break;
×
855
    }
856
    case AddressItemModel::Rows::Values: {
857
      return make_values_widget(model->settings(), parent);
1✔
858
    }
859
    default: {
860
      // Rows past Rows::Count are the extended attributes; Qt's stock factory
861
      // gives prose a one-line field and an ossia::value no editor at all.
862
      auto* attr = extendedAttributeAt(*model, index);
×
863
      if(!attr)
×
864
        break;
×
865

866
      if(attr->first == onet::text_description())
×
867
      {
868
        auto* t = new State::ExpandableTextEdit{parent};
×
869
        t->setSubject(QObject::tr("Description"));
×
870
        t->setFullText(index.data(Qt::EditRole).toString());
×
871
        return t;
×
872
      }
873

874
      if(attr->first == onet::text_default_value())
×
875
      {
876
        // The default is a value of the parameter's own type, so it gets the
877
        // value row's editor, domain and unit included.
878
        Device::AddressSettingsCommon as = model->settings();
×
879
        if(auto v = index.data(Qt::EditRole).value<ossia::value>(); v.valid())
×
880
          as.value = v;
×
881

882
        // Otherwise the row would offer to reset the default to itself.
883
        as.extendedAttributes.erase(onet::text_default_value());
×
884

885
        if(auto* t = make_value_widget(as, parent))
×
886
          return t;
×
887
      }
×
888
      break;
×
889
    }
890
  }
891

892
  return QStyledItemDelegate::createEditor(parent, option, index);
×
893
}
1✔
894

895
void AddressItemDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const
1✔
896
{
897
  if(index.column() == 0)
1✔
898
  {
899
    QStyledItemDelegate::setEditorData(editor, index);
×
900
    return;
×
901
  }
902

903
  switch(index.row())
1✔
904
  {
905
    case AddressItemModel::Rows::Type: {
906
      if(auto cb = qobject_cast<State::TypeComboBox*>(editor))
×
907
      {
908
        auto cur = index.data(Qt::EditRole).toInt();
×
909
        if(cur >= 0 && cur < (int)State::convert::ValuePrettyTypesArray().size())
×
910
          cb->set((ossia::val_type)cur);
×
911
        return;
×
912
      }
913
      break;
×
914
    }
915
    case AddressItemModel::Rows::Access: {
916
      if(auto cb = qobject_cast<Explorer::AccessModeComboBox*>(editor))
×
917
      {
918
        auto cur = index.data(Qt::EditRole).toInt();
×
919
        if(const int max = Device::AccessModePrettyText().size(); cur >= 0 && cur < max)
×
920
          cb->set((ossia::access_mode)cur);
×
921
        return;
×
922
      }
923
      break;
×
924
    }
925
    case AddressItemModel::Rows::Bounding: {
926
      if(auto cb = qobject_cast<Explorer::BoundingModeComboBox*>(editor))
×
927
      {
928
        auto cur = index.data(Qt::EditRole).toInt();
×
929

930
        if(ossia::valid_index(cur, Device::ClipModePrettyStringMap()))
×
931
          cb->set((ossia::bounding_mode)cur);
×
932
        return;
×
933
      }
934
      break;
×
935
    }
936
    case AddressItemModel::Rows::Unit: {
937
      if(auto cb = qobject_cast<State::UnitWidget*>(editor))
×
938
      {
939
        auto cur = index.data(Qt::EditRole).value<State::Unit>();
×
940
        cb->setUnit(cur);
×
941
        return;
942
      }
×
943
      break;
×
944
    }
945
    case AddressItemModel::Rows::Value:
946
    case AddressItemModel::Rows::Min:
947
    case AddressItemModel::Rows::Max:
948
    case AddressItemModel::Rows::Values: {
949
      if(auto cb = qobject_cast<AddressValueWidget*>(editor))
1✔
950
      {
951
        auto cur = index.data(Qt::EditRole).value<ossia::value>();
1✔
952
        cb->set(cur);
1✔
953
        return;
954
      }
1✔
955
      break;
×
956
    }
957
    default: {
958
      if(auto t = qobject_cast<State::ExpandableTextEdit*>(editor))
×
959
      {
960
        t->setFullText(index.data(Qt::EditRole).toString());
×
961
        return;
×
962
      }
963
      if(auto cb = qobject_cast<AddressValueWidget*>(editor))
×
964
      {
965
        cb->set(index.data(Qt::EditRole).value<ossia::value>());
×
966
        return;
×
967
      }
968
      break;
×
969
    }
970
  }
971

972
  QStyledItemDelegate::setEditorData(editor, index);
×
973
}
1✔
974

975
void AddressItemDelegate::setModelData(
1✔
976
    QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const
977
{
978
  if(index.column() == 0)
1✔
979
  {
980
    QStyledItemDelegate::setModelData(editor, model, index);
×
981
    return;
×
982
  }
983

984
  switch(index.row())
1✔
985
  {
986
    case AddressItemModel::Rows::Type: {
987
      if(auto cb = qobject_cast<State::TypeComboBox*>(editor))
×
988
      {
989
        model->setData(index, cb->itemData(cb->currentIndex()), Qt::EditRole);
×
990
      }
×
991
      return;
×
992
    }
993
    case AddressItemModel::Rows::Access: {
994
      if(auto cb = qobject_cast<Explorer::AccessModeComboBox*>(editor))
×
995
      {
996
        model->setData(index, cb->itemData(cb->currentIndex()), Qt::EditRole);
×
997
      }
×
998
      return;
×
999
    }
1000
    case AddressItemModel::Rows::Bounding: {
1001
      if(auto cb = qobject_cast<Explorer::BoundingModeComboBox*>(editor))
×
1002
      {
1003
        model->setData(index, cb->itemData(cb->currentIndex()), Qt::EditRole);
×
1004
      }
×
1005
      return;
×
1006
    }
1007
    case AddressItemModel::Rows::Unit: {
1008
      if(auto cb = qobject_cast<State::UnitWidget*>(editor))
×
1009
      {
1010
        model->setData(index, QVariant::fromValue(cb->unit()), Qt::EditRole);
×
1011
      }
×
1012
      return;
×
1013
    }
1014
    case AddressItemModel::Rows::Value:
1015
    case AddressItemModel::Rows::Min:
1016
    case AddressItemModel::Rows::Max:
1017
    case AddressItemModel::Rows::Values: {
1018
      if(auto cb = qobject_cast<AddressValueWidget*>(editor))
1✔
1019
      {
1020
        if(!cb->edited())
1✔
1021
          return;
1✔
1022

1023
        if(auto v = cb->get(); v.valid())
×
1024
          model->setData(index, QVariant::fromValue(v), Qt::EditRole);
×
1025
        return;
×
1026
      }
1027
      break;
×
1028
    }
1029
    default: {
1030
      if(auto t = qobject_cast<State::ExpandableTextEdit*>(editor))
×
1031
      {
1032
        model->setData(index, t->fullText(), Qt::EditRole);
×
1033
        return;
×
1034
      }
1035
      if(auto cb = qobject_cast<AddressValueWidget*>(editor))
×
1036
      {
1037
        if(!cb->edited())
×
1038
          return;
×
1039

1040
        if(auto v = cb->get(); v.valid())
×
1041
          model->setData(index, QVariant::fromValue(v), Qt::EditRole);
×
1042
        return;
×
1043
      }
1044
      break;
×
1045
    }
1046
  }
1047

1048
  QStyledItemDelegate::setModelData(editor, model, index);
×
1049
}
1✔
1050
}
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