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

ossia / score / 36173285676

25 Sep 2026 06:25PM UTC coverage: 42.611% (+0.4%) from 42.23%
36173285676

Pull #2308

github

web-flow
Merge 1ec9d19a0 into c0c64ec39
Pull Request #2308: tests: a green test suite, and the sanitizer findings in score fixed

529 of 703 new or added lines in 51 files covered. (75.25%)

197 existing lines in 23 files now uncovered.

127086 of 298248 relevant lines covered (42.61%)

118390.02 hits per line

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

68.16
/src/plugins/score-lib-process/Process/Dataflow/PortAddressComboBox.cpp
1
#include "PortAddressComboBox.hpp"
2

3
#include <State/MessageListSerialization.hpp>
4

5
#include <Device/Node/DeviceNode.hpp>
6
#include <Device/Node/NodeListMimeSerialization.hpp>
7

8
#include <Process/Commands/EditPort.hpp>
9
#include <Process/Dataflow/Port.hpp>
10

11
#include <Explorer/DeviceList.hpp>
12
#include <Explorer/DocumentPlugin/DeviceDocumentPlugin.hpp>
13

14
#include <score/command/Dispatchers/CommandDispatcher.hpp>
15
#include <score/document/DocumentContext.hpp>
16

17
#include <ossia/audio/audio_parameter.hpp>
18
#include <ossia/network/base/device.hpp>
19
#include <ossia/network/base/node.hpp>
20
#include <ossia/network/base/parameter.hpp>
21

22
#include <QDragEnterEvent>
23
#include <QDropEvent>
24
#include <QLineEdit>
25
#include <QMimeData>
26
#include <QTimer>
27

28
#include <wobjectimpl.h>
29

30
W_OBJECT_IMPL(Process::PortAddressComboBox)
1,234✔
31

32
namespace Process
33
{
34
Device::NodeKind nodeKindOf(PortType type, bool inlet) noexcept
180✔
35
{
36
  using K = Device::NodeKind;
37
  switch(type)
180✔
38
  {
39
    case PortType::Message:
40
      return K::Value;
2✔
41
    case PortType::Audio:
42
      return inlet ? K::AudioIn : K::AudioOut;
159✔
43
    case PortType::Midi:
44
      return inlet ? K::MidiIn : K::MidiOut;
5✔
45
    case PortType::Texture:
46
      return inlet ? K::TextureIn : K::TextureOut;
10✔
47
    case PortType::Geometry:
48
      return inlet ? K::GeometryIn : K::GeometryOut;
4✔
49
  }
50
  return K::None;
×
51
}
180✔
52

53
namespace
54
{
55
// Three cases for an audio node: a pure input (usable by inlets), a pure
56
// output (usable by outlets), or a virtual port which goes both ways.
57
bool audioMatches(
97✔
58
    const ossia::net::node_base& n, const ossia::net::parameter_base& p,
59
    bool inlet) noexcept
60
{
61
  if(dynamic_cast<const ossia::virtual_audio_parameter*>(&p))
97✔
62
    return true;
2✔
63

64
  // The custom ports added to the audio device know their direction...
65
  if(auto m = dynamic_cast<const ossia::mapped_audio_parameter*>(&p))
95✔
66
    return inlet ? !m->is_output : m->is_output;
8✔
67

68
  // ... the hardware ports libossia sets up (/in/N, /in/main, /out/N...) only
69
  // by where they sit.
70
  for(auto node = &n; node; node = node->get_parent())
173✔
71
  {
72
    if(auto parent = node->get_parent(); parent && !parent->get_parent())
173✔
73
    {
74
      const auto& top = node->get_name();
87✔
75
      if(top == "in")
87✔
76
        return inlet;
44✔
77
      if(top == "out")
43✔
78
        return !inlet;
42✔
79
      break;
1✔
80
    }
81
  }
86✔
82

83
  // The streams of other devices (libav, gstreamer...) follow the device's
84
  // declared kinds.
85
  return true;
1✔
86
}
97✔
87

88
bool nodeMatches(const ossia::net::node_base& n, PortType type, bool inlet) noexcept
237✔
89
{
90
  auto p = n.get_parameter();
237✔
91
  if(!p)
237✔
92
    return false;
127✔
93

94
  switch(type)
110✔
95
  {
96
    case PortType::Audio:
97
      return p->get_type() == ossia::parameter_type::AUDIO && audioMatches(n, *p, inlet);
98✔
98
    case PortType::Texture:
99
      return p->get_type() == ossia::parameter_type::TEXTURE;
10✔
100
    case PortType::Geometry:
101
      return p->get_type() == ossia::parameter_type::GEOMETRY;
2✔
102
    default:
103
      return false;
×
104
  }
105
}
237✔
106

107
void walk(
237✔
108
    const ossia::net::node_base& n, State::Address& addr, PortType type, bool inlet,
109
    std::vector<State::Address>& out)
110
{
111
  if(nodeMatches(n, type, inlet))
237✔
112
    out.push_back(addr);
57✔
113

114
  for(auto child : n.children_copy())
428✔
115
  {
116
    addr.path.push_back(QString::fromStdString(child->get_name()));
191✔
117
    walk(*child, addr, type, inlet, out);
191✔
118
    addr.path.pop_back();
191✔
119
  }
120
}
237✔
121
}
122

123
std::vector<State::Address>
124
listPortAddresses(Device::DeviceInterface& dev, PortType type, bool inlet)
94✔
125
{
126
  std::vector<State::Address> out;
94✔
127
  if(!Device::carries(dev.capabilities().nodeKinds, nodeKindOf(type, inlet)))
94✔
128
    return out;
47✔
129

130
  auto ossia_dev = dev.getDevice();
47✔
131
  if(!ossia_dev)
47✔
132
    return out;
×
133

134
  auto& root = ossia_dev->get_root_node();
47✔
135
  State::Address addr{dev.name(), {}};
47✔
136

137
  if(type == PortType::Midi)
47✔
138
  {
139
    out.push_back(addr);
1✔
140
    for(auto child : root.children_copy())
17✔
141
    {
142
      addr.path = {QString::fromStdString(child->get_name())};
16✔
143
      out.push_back(addr);
16✔
144
    }
145
    return out;
1✔
146
  }
147

148
  walk(root, addr, type, inlet, out);
46✔
149
  return out;
46✔
150
}
94✔
151

152
std::vector<State::Address>
153
listPortAddresses(Device::DeviceList& devices, PortType type, bool inlet)
38✔
154
{
155
  std::vector<State::Address> out;
38✔
156
  devices.apply([&](Device::DeviceInterface& dev) {
114✔
157
    auto addrs = listPortAddresses(dev, type, inlet);
76✔
158
    out.insert(out.end(), addrs.begin(), addrs.end());
76✔
159
  });
76✔
160
  return out;
38✔
161
}
38✔
162

163
PortAddressComboBox::PortAddressComboBox(
38✔
164
    Device::DeviceList& devices, PortType type, bool inlet, QWidget* parent)
165
    : QComboBox{parent}
38✔
166
    , m_devices{&devices}
38✔
167
    , m_type{type}
38✔
168
    , m_inlet{inlet}
38✔
169
{
38✔
170
  setEditable(true);
38✔
171
  setInsertPolicy(QComboBox::NoInsert);
38✔
172
  setAcceptDrops(true);
38✔
173
  // Addresses can be long: never let the widest one dictate the panel's width
174
  setSizeAdjustPolicy(QComboBox::AdjustToMinimumContentsLengthWithIcon);
38✔
175
  setMinimumContentsLength(8);
38✔
176
  lineEdit()->setPlaceholderText(tr("None"));
38✔
177

178
  connect(this, qOverload<int>(&QComboBox::activated), this, [this](int) {
38✔
179
    commitText();
×
180
  });
×
181
  connect(lineEdit(), &QLineEdit::editingFinished, this, &PortAddressComboBox::commitText);
38✔
182

183
  devices.apply([this](Device::DeviceInterface& dev) { watch(dev); });
114✔
184
  connect(
38✔
185
      &devices, &Device::DeviceList::deviceAdded, this,
38✔
186
      [this](Device::DeviceInterface* dev) {
38✔
187
    watch(*dev);
×
188
    scheduleReload();
×
189
      });
×
190
  connect(
38✔
191
      &devices, &Device::DeviceList::deviceRemoved, this,
38✔
192
      [this](Device::DeviceInterface*) { scheduleReload(); });
38✔
193

194
  reload();
38✔
195
}
×
196

197
PortAddressComboBox::~PortAddressComboBox() { }
76✔
198

199
void PortAddressComboBox::watch(Device::DeviceInterface& dev)
76✔
200
{
201
  // A device may start carrying our kind when it reconnects (libav flips
202
  // between input and output), so the coarse events are followed everywhere;
203
  // the per-node ones only on devices already worth walking.
204
  auto reload = [this] { scheduleReload(); };
76✔
205
  connect(&dev, &Device::DeviceInterface::deviceChanged, this, reload);
76✔
206
  connect(&dev, &Device::DeviceInterface::namespaceUpdated, this, reload);
76✔
207
  connect(&dev, &Device::DeviceInterface::connectionChanged, this, reload);
76✔
208

209
  if(Device::carries(dev.capabilities().nodeKinds, nodeKindOf(m_type, m_inlet)))
76✔
210
  {
211
    connect(&dev, &Device::DeviceInterface::pathAdded, this, reload);
38✔
212
    connect(&dev, &Device::DeviceInterface::pathRemoved, this, reload);
38✔
213
    connect(&dev, &Device::DeviceInterface::pathUpdated, this, reload);
38✔
214
  }
38✔
215
}
76✔
216

UNCOV
217
void PortAddressComboBox::scheduleReload()
×
218
{
UNCOV
219
  if(m_reloadPending)
×
220
    return;
×
UNCOV
221
  m_reloadPending = true;
×
UNCOV
222
  QTimer::singleShot(0, this, [this] {
×
UNCOV
223
    m_reloadPending = false;
×
UNCOV
224
    reload();
×
UNCOV
225
  });
×
UNCOV
226
}
×
227

228
void PortAddressComboBox::reload()
38✔
229
{
230
  if(!m_devices)
38✔
UNCOV
231
    return;
×
232

233
  const QSignalBlocker block{this};
38✔
234
  clear();
38✔
235
  addItem(QString{});
38✔
236
  for(const auto& addr : listPortAddresses(*m_devices, m_type, m_inlet))
76✔
237
    addItem(addr.toString());
38✔
238
  showAddress();
38✔
239
}
38✔
240

241
void PortAddressComboBox::showAddress()
77✔
242
{
243
  const QSignalBlocker block{this};
77✔
244
  const auto txt
245
      = m_address.address.device.isEmpty() ? QString{} : m_address.toString_unsafe();
77✔
246
  if(int idx = findText(txt); idx >= 0)
77✔
247
    setCurrentIndex(idx);
74✔
248
  else
249
    setCurrentIndex(0);
3✔
250
  // Still shown when it is not in the list (device down, typed by hand...)
251
  setEditText(txt);
77✔
252
}
77✔
253

254
void PortAddressComboBox::setAddress(const State::AddressAccessor& addr)
39✔
255
{
256
  m_address = addr;
39✔
257
  showAddress();
39✔
258
}
39✔
259

260
void PortAddressComboBox::commitText()
×
261
{
262
  const auto txt = currentText().trimmed();
×
263
  if(txt.isEmpty())
×
264
  {
265
    commit({});
×
266
    return;
×
267
  }
268

269
  if(auto addr = State::parseAddressAccessor(txt))
×
270
    commit(std::move(*addr));
×
271
  else
272
    showAddress(); // not an address: back to what the port has
×
273
}
×
274

275
void PortAddressComboBox::commit(State::AddressAccessor addr)
×
276
{
277
  if(addr == m_address)
×
278
  {
279
    showAddress();
×
280
    return;
×
281
  }
282
  m_address = std::move(addr);
×
283
  showAddress();
×
284
  addressChanged(m_address);
×
285
}
×
286

287
void PortAddressComboBox::dragEnterEvent(QDragEnterEvent* event)
×
288
{
289
  const auto& formats = event->mimeData()->formats();
×
290
  if(formats.contains(score::mime::messagelist())
×
291
     || formats.contains(score::mime::nodelist()))
×
292
    event->acceptProposedAction();
×
293
}
×
294

295
void PortAddressComboBox::dropEvent(QDropEvent* event)
×
296
{
297
  auto& mime = *event->mimeData();
×
298
  if(mime.hasFormat(score::mime::nodelist()))
×
299
  {
300
    Mime<Device::FreeNodeList>::Deserializer des{mime};
×
301
    if(auto res = Device::addressOfDroppedNodes(des.deserialize(), m_address))
×
302
      commit(std::move(res->address));
×
303
    event->acceptProposedAction();
×
304
  }
×
305
  else if(mime.hasFormat(score::mime::messagelist()))
×
306
  {
307
    Mime<State::MessageList>::Deserializer des{mime};
×
308
    auto ml = des.deserialize();
×
309
    if(!ml.empty())
×
310
      commit(ml.front().address);
×
311
    event->acceptProposedAction();
×
312
  }
×
313
}
×
314

315
QComboBox* makePortAddressCombo(
38✔
316
    const Process::Port& port, const score::DocumentContext& ctx, QWidget* parent)
317
{
318
  auto& devices = ctx.plugin<Explorer::DeviceDocumentPlugin>().list();
38✔
319
  const bool inlet = qobject_cast<const Process::Inlet*>(&port) != nullptr;
38✔
320

321
  auto edit = new PortAddressComboBox{devices, port.type(), inlet, parent};
38✔
322
  edit->setAddress(port.address());
38✔
323

324
  QObject::connect(
38✔
325
      &port, &Process::Port::addressChanged, edit,
38✔
326
      [edit](const State::AddressAccessor& addr) {
39✔
327
    if(addr != edit->address())
1✔
328
      edit->setAddress(addr);
1✔
329
      });
1✔
330

331
  QObject::connect(
38✔
332
      edit, &PortAddressComboBox::addressChanged, parent,
38✔
333
      [&port, &ctx](const State::AddressAccessor& addr) {
38✔
334
    if(addr == port.address())
×
335
      return;
×
336

337
    CommandDispatcher<>{ctx.dispatcher}.submit(
×
338
        new Process::ChangePortAddress{port, addr});
×
339
      });
×
340

341
  return edit;
38✔
342
}
×
343
}
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