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

ossia / score / 30213925900

26 Jul 2026 06:03PM UTC coverage: 15.298% (-0.01%) from 15.311%
30213925900

push

github

jcelerier
3rdparty: bump libossia for the network-context poll fix

Brings in ossia/libossia#913: SDL joystick init no longer requires haptic
support (which the Emscripten SDL2 port does not have), and
network_context::poll() restarts the io_context, without which the
WebAssembly polling path stops after its first tick.

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

30400 of 198713 relevant lines covered (15.3%)

997.67 hits per line

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

14.34
/src/plugins/score-lib-process/Effect/EffectLayer.cpp
1
#include "EffectLayer.hpp"
2

3
#include <Process/ApplicationPlugin.hpp>
4
#include <Process/Commands/LoadPreset.hpp>
5
#include <Process/Commands/LoadPresetCommandFactory.hpp>
6
#include <Process/Dataflow/CableCopy.hpp>
7
#include <Process/Focus/FocusDispatcher.hpp>
8
#include <Process/Process.hpp>
9
#include <Process/ProcessMimeSerialization.hpp>
10
#include <Process/Style/Pixmaps.hpp>
11

12
#include <score/command/Dispatchers/CommandDispatcher.hpp>
13
#include <score/document/DocumentContext.hpp>
14
#include <score/graphics/GraphicWidgets.hpp>
15
#include <score/model/EntitySerialization.hpp>
16
#include <score/model/path/PathSerialization.hpp>
17
#include <score/widgets/HelpInteraction.hpp>
18

19
#include <core/document/Document.hpp>
20

21
#include <ossia/detail/thread.hpp>
22

23
#include <QHash>
24
#include <QMenu>
25

26
#include <wobjectimpl.h>
27
W_OBJECT_IMPL(Process::EffectLayerPresenter)
×
28
namespace Process
29
{
30

31
EffectLayerView::EffectLayerView(QGraphicsItem* parent)
×
32
    : Process::LayerView{parent}
×
33
{
×
34
}
35

36
EffectLayerView::~EffectLayerView() { }
×
37

38
void EffectLayerView::setWidth(qreal val, qreal defaultWidth)
×
39
{
40
  m_defaultWidth = defaultWidth;
×
41
  LayerView::setWidth(val);
×
42
}
×
43

44
void EffectLayerView::paint_impl(QPainter*) const { }
×
45

46
EffectLayerPresenter::EffectLayerPresenter(
×
47
    const ProcessModel& model, Process::EffectLayerView* view, const Context& ctx,
48
    QObject* parent)
49
    : LayerPresenter{model, view, ctx, parent}
×
50
    , m_view{view}
×
51
{
×
52
  putToFront();
×
53
}
×
54

55
EffectLayerPresenter::~EffectLayerPresenter() { }
×
56

57
void EffectLayerPresenter::setWidth(qreal val, qreal defaultWidth)
×
58
{
59
  m_view->setWidth(val, defaultWidth);
×
60
}
×
61

62
void EffectLayerPresenter::setHeight(qreal val)
×
63
{
64
  m_view->setHeight(val);
×
65
}
×
66

67
void EffectLayerPresenter::putToFront()
×
68
{
69
  m_view->setVisible(true);
×
70
}
×
71

72
void EffectLayerPresenter::putBehind()
×
73
{
74
  m_view->setVisible(false);
×
75
}
×
76

77
void EffectLayerPresenter::on_zoomRatioChanged(ZoomRatio) { }
×
78

79
void EffectLayerPresenter::parentGeometryChanged() { }
×
80

81
void EffectLayerPresenter::fillContextMenu(
×
82
    QMenu& menu, QPoint pos, QPointF scenepos, const LayerContextMenuManager& mgr)
83
{
84
}
×
85

86
QGraphicsItem* makeScriptButton(
2✔
87
    ProcessModel& effect, const score::DocumentContext& context, QObject* self,
88
    QGraphicsItem* root)
89
{
90
  auto& pixmaps = Process::Pixmaps::instance();
2✔
91
  auto& facts = context.app.interfaces<Process::LayerFactoryList>();
2✔
92
  auto fact = facts.findDefaultFactory(effect);
2✔
93
  if(effect.flags() & Process::ProcessFlags::ScriptEditingSupported)
2✔
94
  {
95
    auto ui_btn = new score::QGraphicsPixmapToggle{
2✔
96
        pixmaps.show_script_on, pixmaps.show_script_off, root};
1✔
97
    ui_btn->setToolTip(
2✔
98
        QObject::tr("Show/hide UI\nShow the process's script editor for JS, shaders, etc."));
1✔
99
    QObject::connect(
1✔
100
        ui_btn, &score::QGraphicsPixmapToggle::toggled, self,
1✔
101
        [=, &effect, &context](bool b) {
1✔
102
      Process::setupScriptUI(effect, *fact, context, b);
×
103
    });
×
104

105
    if(effect.scriptUI)
1✔
106
      ui_btn->setState(true);
×
107
    QObject::connect(
1✔
108
        &effect, &Process::ProcessModel::scriptUIVisible, ui_btn,
1✔
109
        [=](bool v) { ui_btn->setState(v); });
1✔
110
    return ui_btn;
1✔
111
  }
112
  return nullptr;
1✔
113
}
2✔
114

115
void setupScriptUI(
×
116
    Process::ProcessModel& proc, const Process::LayerFactory& fact,
117
    const score::DocumentContext& ctx, bool show)
118
{
119
  if(show)
×
120
  {
121
    if(proc.scriptUI)
×
122
      return;
×
123

124
    if(auto win = fact.makeScriptUI(proc, ctx, nullptr))
×
125
    {
126
      const_cast<QWidget*&>(proc.scriptUI) = win;
×
127
#if defined(__EMSCRIPTEN__)
128
      // No OS window manager on wasm: otherwise the editor opens behind/unfocused
129
      // and the first click on "Compile" only activates it instead of pressing the
130
      // button. Force it on top and active on show.
131
      win->setWindowFlag(Qt::WindowStaysOnTopHint, true);
132
      #if defined(__EMSCRIPTEN__)
133
      // Qt for wasm reports ShowIsFullScreen unconditionally, and QWidget::show()
134
      // acts on it exactly as QWindow::show() does: the window covers the page and
135
      // loses the frame Qt draws for it, leaving no way to close or move it.
136
      win->showNormal();
137
#else
138
      win->show();
139
#endif
140
      win->raise();
141
      win->activateWindow();
142
#else
143
      #if defined(__EMSCRIPTEN__)
144
      // Qt for wasm reports ShowIsFullScreen unconditionally, and QWidget::show()
145
      // acts on it exactly as QWindow::show() does: the window covers the page and
146
      // loses the frame Qt draws for it, leaving no way to close or move it.
147
      win->showNormal();
148
#else
149
      win->show();
×
150
#endif
151
#endif
152
    }
×
153
  }
×
154
  else
155
  {
156
    if(auto win = proc.scriptUI)
×
157
    {
158
      win->close();
×
159
      delete win;
×
160
      const_cast<QWidget*&>(proc.scriptUI) = nullptr;
×
161
    }
×
162
  }
163
}
×
164

165
void setupExternalUI(
×
166
    Process::ProcessModel& proc, const Process::LayerFactory& fact,
167
    const score::DocumentContext& ctx, bool show)
168
{
169
  if(show)
×
170
  {
171
    if(proc.externalUI)
×
172
      return;
×
173

174
    if(auto win = fact.makeExternalUI(proc, ctx, nullptr))
×
175
    {
176
      const_cast<QWidget*&>(proc.externalUI) = win;
×
177
      #if defined(__EMSCRIPTEN__)
178
      // Qt for wasm reports ShowIsFullScreen unconditionally, and QWidget::show()
179
      // acts on it exactly as QWindow::show() does: the window covers the page and
180
      // loses the frame Qt draws for it, leaving no way to close or move it.
181
      win->showNormal();
182
#else
183
      win->show();
×
184
#endif
185
    }
×
186
  }
×
187
  else
188
  {
189
    if(auto win = proc.externalUI)
×
190
    {
191
      win->close();
×
192
      delete win;
×
193
      const_cast<QWidget*&>(proc.externalUI) = nullptr;
×
194
    }
×
195
  }
196
}
×
197

198
void setupExternalUI(
×
199
    Process::ProcessModel& proc, const score::DocumentContext& ctx, bool show)
200
{
201
  auto& facts = ctx.app.interfaces<Process::LayerFactoryList>();
×
202

203
  auto fact = facts.findDefaultFactory(proc);
×
204
  if(!fact || !fact->hasExternalUI(proc, ctx))
×
205
    return;
×
206

207
  setupExternalUI(proc, *fact, ctx, show);
×
208
}
×
209

210
QGraphicsItem* makeExternalUIButton(
23✔
211
    ProcessModel& effect, const score::DocumentContext& context, QObject* self,
212
    QGraphicsItem* root)
213
{
214
  auto& pixmaps = Process::Pixmaps::instance();
23✔
215
  auto& facts = context.app.interfaces<Process::LayerFactoryList>();
23✔
216
  auto fact = facts.findDefaultFactory(effect);
23✔
217
  if(fact && fact->hasExternalUI(effect, context))
23✔
218
  {
219
    auto ui_btn = new score::QGraphicsPixmapToggle{
×
220
        pixmaps.show_ui_on, pixmaps.show_ui_off, root};
×
221
    ui_btn->setToolTip(
×
222
        QObject::tr("Show/hide UI\nShow the process's interface for instance for VSTs "
×
223
                    "or script editor for JS, etc."));
224
    QObject::connect(
×
225
        ui_btn, &score::QGraphicsPixmapToggle::toggled, self,
×
226
        [=, &effect, &context](bool b) {
×
227
      Process::setupExternalUI(effect, *fact, context, b);
×
228
        });
×
229

230
    if(effect.externalUI)
×
231
      ui_btn->setState(true);
×
232
    QObject::connect(
×
233
        &effect, &Process::ProcessModel::externalUIVisible, ui_btn,
×
234
        [=](bool v) { ui_btn->setState(v); });
×
235
    return ui_btn;
×
236
  }
237
  return nullptr;
23✔
238
}
23✔
239

240
// Order presets the way the menu displays them: grouped by category so each
241
// submenu's entries stay together, then alphabetically by name within a category.
242
static bool
243
presetMenuOrder(const Process::Preset* lhs, const Process::Preset* rhs) noexcept
×
244
{
245
  if(lhs->category != rhs->category)
×
246
    return lhs->category < rhs->category;
×
247
  return lhs->name < rhs->name;
×
248
}
×
249

250
score::QGraphicsDraggablePixmap* makePresetButton(
2✔
251
    const ProcessModel& proc, const score::DocumentContext& context, QObject* self,
252
    QGraphicsItem* root)
253
{
254
  auto& pixmaps = Process::Pixmaps::instance();
2✔
255
  auto ui_btn
2✔
256
      = new score::QGraphicsDraggablePixmap{pixmaps.preset_on, pixmaps.preset_off, root};
2✔
257
  ui_btn->setToolTip(
4✔
258
      QObject::tr(
2✔
259
          "Presets\nDrag to the library to save the current preset. If there "
260
          "are existing presets, they will be shown in a menu."));
261
  ui_btn->createDrag = [&proc](QMimeData& mime) {
2✔
262
    QByteArray data;
×
263
    {
264
      JSONReader r;
×
265
      r.stream.StartObject();
×
266
      copyProcess(r, proc);
×
267
      r.obj["Path"] = score::IDocument::path(proc);
×
268
      r.obj["View"] = QStringLiteral("Nodal");
×
269
      r.stream.EndObject();
×
270
      data = r.toByteArray();
×
271
    }
×
272

273
    mime.setData(score::mime::layerdata(), data);
×
274
    mime.setData(score::mime::processpreset(), proc.savePreset().toJson());
×
275
  };
×
276

277
  ui_btn->click = [&proc, &context](Qt::MouseButton btn, QPointF screenPos) {
2✔
278
    auto& pplug = context.app.applicationPlugin<Process::ApplicationPlugin>();
×
279
    const auto& presets = pplug.presets;
×
280

281
    switch(btn)
×
282
    {
283
      default:
284
        break;
×
285
      case Qt::LeftButton: {
286
        // Preset menu
287
        auto menu = new QMenu;
×
288
        menu->addAction(
×
289
            "Save current preset", menu, [&proc, &pplug] { pplug.savePreset(&proc); });
×
290

291
        auto loadPreset = [&proc, &context](const Process::Preset& preset) {
×
292
          auto& load_preset_ifaces
×
293
              = context.app.interfaces<LoadPresetCommandFactoryList>();
×
294

295
          auto cmd = load_preset_ifaces.make(
×
296
              &LoadPresetCommandFactory::make, proc, preset, context);
×
297
          if(cmd)
×
298
          {
299
            CommandDispatcher<> c{context.commandStack};
×
300
            c.submit(cmd);
×
301
          }
×
302
        };
×
303

304
        // Resolve a "Foo/Bar/Baz" category path to the submenu it designates,
305
        // creating (and caching) the intermediate submenus on the way.
306
        // An empty category resolves to the root menu.
307
        QHash<QString, QMenu*> submenus;
×
308
        auto categoryMenu = [&](const QString& category) -> QMenu* {
×
309
          QMenu* current = menu;
×
310
          QString path;
×
311
          const auto parts = category.split('/', Qt::SkipEmptyParts);
×
312
          for(const QString& rawPart : parts)
×
313
          {
314
            const QString part = rawPart.trimmed();
×
315
            if(part.isEmpty())
×
316
              continue;
×
317
            if(!path.isEmpty())
×
318
              path += '/';
×
319
            path += part;
×
320

321
            auto it = submenus.find(path);
×
322
            if(it != submenus.end())
×
323
              current = it.value();
×
324
            else
325
              current = *submenus.insert(path, current->addMenu(part));
×
326
          }
×
327
          return current;
×
328
        };
×
329

330
        std::vector<const Process::Preset*> goodPresets;
×
331
        const auto& k = proc.concreteKey();
×
332
        const auto& e = proc.effect();
×
333
        for(auto& preset : presets)
×
334
        {
335
          if(preset.key.key == k && preset.key.effect == e)
×
336
            goodPresets.push_back(&preset);
×
337
        }
338
        std::sort(goodPresets.begin(), goodPresets.end(), presetMenuOrder);
×
339

340
        menu->addSeparator();
×
341

342
        for(auto p : goodPresets)
×
343
        {
344
          categoryMenu(p->category)
×
345
              ->addAction(p->name, menu, [p, loadPreset] { loadPreset(*p); });
×
346
        }
347

348
        if(auto proc_builtins = proc.builtinPresets(); !proc_builtins.empty())
×
349
        {
350
          if(!goodPresets.empty())
×
351
            menu->addSeparator();
×
352

353
          for(auto& p : proc_builtins)
×
354
          {
355
            // FIXME try to understand why just p.name does not work here
356
            categoryMenu(p.category)
×
357
                ->addAction("" + p.name, menu, [p = std::move(p), loadPreset] {
×
358
              loadPreset(p);
×
359
            });
×
360
          }
361
        }
×
362

363
        menu->exec(screenPos.toPoint());
×
364
        menu->deleteLater();
×
365
        break;
366
      }
×
367

368
      case Qt::ForwardButton:
369
      case Qt::BackButton: {
370
        std::vector<const Process::Preset*> goodPresets;
×
371
        const auto& k = proc.concreteKey();
×
372
        const auto& e = proc.effect();
×
373
        for(auto& preset : presets)
×
374
          if(preset.key.key == k && preset.key.effect == e)
×
375
            goodPresets.push_back(&preset);
×
376
        auto bps = proc.builtinPresets();
×
377
        for(auto& bp : bps)
×
378
          goodPresets.push_back(&bp);
×
379

380
        // Cycle in the same order the menu displays them.
381
        std::sort(goodPresets.begin(), goodPresets.end(), presetMenuOrder);
×
382

383
        if(goodPresets.size() < 2)
×
384
          return;
×
385

386
        // loadPreset() renames the process to the preset's name, so the
387
        // currently-loaded preset is the one whose name matches.
388
        const QString cur = proc.metadata().getName();
×
389
        int i = -1;
×
390
        for(int j = 0; j < std::ssize(goodPresets); j++)
×
391
        {
392
          if(goodPresets[j]->name == cur)
×
393
          {
394
            i = j;
×
395
            break;
×
396
          }
397
        }
×
398

399
        const int n = std::ssize(goodPresets);
×
400
        if(i < 0)
×
401
          // Nothing matches (renamed / never loaded): start at the relevant end.
402
          i = (btn == Qt::ForwardButton) ? 0 : (n - 1);
×
403
        else if(btn == Qt::ForwardButton)
×
404
          i = (i + 1) % n;
×
405
        else
406
          i = (i - 1 + n) % n;
×
407

408
        auto& load_preset_ifaces
×
409
            = context.app.interfaces<LoadPresetCommandFactoryList>();
×
410

411
        auto cmd = load_preset_ifaces.make(
×
412
            &LoadPresetCommandFactory::make, proc, *goodPresets[i], context);
×
413
        if(cmd)
×
414
        {
415
          CommandDispatcher<> c{context.commandStack};
×
416
          c.submit(cmd);
×
417
        }
×
418
        break;
419
      }
×
420
    }
421
  };
×
422

423
  return ui_btn;
2✔
424
}
×
425

426
void copyProcess(JSONReader& r, const Process::ProcessModel& proc)
×
427
{
428
  const auto& ctx = score::IDocument::documentContext(proc);
×
429

430
  std::vector<const Process::ProcessModel*> vp{&proc};
×
431
  std::vector<Path<Process::ProcessModel>> vpath{proc};
×
432
  // Object is not created here but in SlotHeader
433
  r.obj["PID"] = ossia::get_pid();
×
434
  r.obj["Document"] = ctx.document.id();
×
435
  r.obj["Process"] = proc;
×
436
  r.obj["Cables"] = Process::cablesToCopy(vp, vpath, ctx);
×
437
}
×
438
}
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