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

ossia / score / 35310244323

18 Sep 2026 05:17AM UTC coverage: 39.902% (+2.2%) from 37.724%
35310244323

push

github

jcelerier
tests: include CommandDispatcher where JsLibraryScriptSavingTest uses it

ee4b67af6f added the test with a CommandDispatcher<> but without its header,
relying on it arriving transitively. The Coverage build compiles with clang-19
at gnu++23 and does not get it that way:

  JsLibraryScriptSavingTest.cpp:70:3: error: no template named 'CommandDispatcher'

This has been broken since 2026-09-13 and was hidden behind the boost download
failure, which stopped that job before it ever reached the compile.

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

114906 of 287968 relevant lines covered (39.9%)

77906.02 hits per line

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

78.45
/src/plugins/score-lib-process/Process/HeaderDelegate.cpp
1
#include <Process/Dataflow/Port.hpp>
2
#include <Process/Dataflow/PortFactory.hpp>
3
#include <Process/Dataflow/PortItem.hpp>
4
#include <Process/HeaderDelegate.hpp>
5
#include <Process/Process.hpp>
6
#include <Process/Style/Pixmaps.hpp>
7
#include <Process/Style/ScenarioStyle.hpp>
8

9
#include <Effect/EffectLayer.hpp>
10

11
#include <score/application/GUIApplicationContext.hpp>
12
#include <score/graphics/GraphicWidgets.hpp>
13
#include <score/graphics/GraphicsItem.hpp>
14
#include <score/graphics/YPos.hpp>
15
#include <score/tools/Bind.hpp>
16
#include <score/tools/std/StringHash.hpp>
17

18
#include <ossia/detail/hash_map.hpp>
19

20
#include <QCursor>
21
#include <QDebug>
22
#include <QGuiApplication>
23
#include <QApplication>
24
#include <QPainter>
25
#include <QTextLayout>
26
#include <QTextLine>
27
namespace std
28
{
29
template <>
30
struct hash<std::pair<QString, const QPen*>>
31
{
32
  std::size_t operator()(const std::pair<QString, const QPen*>& p) const noexcept
917✔
33
  {
34
    return qHash(p);
917✔
35
  }
36
};
37
}
38

39
namespace Process
40
{
41

42
using GlyphCache = ossia::hash_map<std::pair<QString, const QPen*>, QPixmap>;
43

44
static GlyphCache& glyphCache() noexcept
825✔
45
{
46
  // FIXME LRU
47
  static GlyphCache cache;
825✔
48

49
  // The key has neither the font nor the devicePixelRatio in it, so drop the
50
  // whole cache rather than widen it. Each delegate also holds its own copy
51
  // of the result and has to ask again.
52
  static bool connected = false;
53
  if(!connected)
825✔
54
  {
55
    connected = true;
52✔
56
    QObject::connect(&score::Skin::instance(), &score::Skin::changed, qApp, [] {
55✔
57
      glyphCache().clear();
3✔
58
    });
3✔
59
  }
52✔
60
  return cache;
825✔
61
}
62

63
QPixmap makeGlyphs(const QString& glyph, const QPen& pen)
822✔
64
{
65
  auto& cache = glyphCache();
822✔
66

67
  auto k = std::make_pair(glyph, &pen);
822✔
68
  if(auto it = cache.find(k); it != cache.end())
822✔
69
    return it->second;
727✔
70

71
  QImage path;
95✔
72

73
  QTextLayout lay;
95✔
74
  lay.setFont(score::Skin::instance().Medium8Pt);
95✔
75
  lay.setText(glyph);
95✔
76
  lay.beginLayout();
95✔
77
  QTextLine line = lay.createLine();
95✔
78
  lay.endLayout();
95✔
79
  line.setPosition(QPointF{0., 0.});
95✔
80

81
  auto r = line.glyphRuns();
95✔
82
  if(r.size() >= 1)
95✔
83
  {
84
    auto rect = line.naturalTextRect();
95✔
85
    path = newImage(rect.width(), rect.height());
95✔
86

87
    QPainter p{&path};
95✔
88

89
    p.setPen(pen);
95✔
90
    p.drawGlyphRun(QPointF{0, 0}, r[0]);
95✔
91
  }
95✔
92

93
  const auto& res = cache.emplace(
95✔
94
      std::move(k), QPixmap::fromImage(std::move(path), Qt::NoFormatConversion));
95✔
95
  return res.first->second;
95✔
96
}
822✔
97

98
static double portY()
454✔
99
{
100
  return SCORE_YPOS(1., 1.);
454✔
101
}
102

103
static double minPortWidth()
192✔
104
{
105
  return 20.;
192✔
106
}
107

108
DefaultHeaderDelegate::DefaultHeaderDelegate(
1,484✔
109
    const Process::ProcessModel& m, const Process::Context& doc)
110
    : HeaderDelegate{m, doc}
742✔
111
{
742✔
112
  m_portStartX = 0.;
742✔
113
  const auto flags = m.flags();
742✔
114
  auto& pixmaps = Process::Pixmaps::instance();
742✔
115
  m_ui = Process::makeExternalUIButton(
742✔
116
      const_cast<Process::ProcessModel&>(m_model), m_context, this, this);
742✔
117
  if(m_ui)
742✔
118
  {
119
    m_ui->setPos({m_portStartX, 2});
×
120
    m_portStartX += 12;
×
121
  }
×
122

123
  if(flags & Process::ProcessFlags::Recordable)
742✔
124
  {
125
    auto rec_btn
×
126
        = new score::QGraphicsPixmapToggle{pixmaps.record_on, pixmaps.record_off, this};
×
127
    rec_btn->setPos(m_portStartX, 0);
×
128
    m_portStartX += 12;
×
129
  }
×
130
  if(flags & Process::ProcessFlags::Snapshottable)
742✔
131
  {
132
    auto rec_btn = new score::QGraphicsPixmapButton{
×
133
        pixmaps.snapshot_on, pixmaps.snapshot_off, this};
×
134
    rec_btn->setPos(m_portStartX, 0);
×
135
    m_portStartX += 18;
×
136
  }
×
137

138
  con(m_model, &Process::ProcessModel::prettyNameChanged, this, [this] {
807✔
139
    updateText();
65✔
140
    update();
65✔
141
  });
65✔
142

143
  con(m_model, &Process::ProcessModel::controlAdded, this,
1,484✔
144
      &DefaultHeaderDelegate::updatePorts);
742✔
145
  con(m_model, &Process::ProcessModel::controlRemoved, this,
1,484✔
146
      &DefaultHeaderDelegate::updatePorts);
742✔
147
  con(m_model, &Process::ProcessModel::inletsChanged, this,
1,484✔
148
      &DefaultHeaderDelegate::updatePorts);
742✔
149
  updatePorts();
742✔
150

151
  con(m_model, &Process::ProcessModel::benchmark, this,
1,484✔
152
      [this](double d) { updateBench(d); });
771✔
153
  con(
742✔
154
      m_model.selection, &Selectable::changed, this,
742✔
155
      [this](bool b) {
745✔
156
    m_sel = b;
3✔
157
    updateText();
3✔
158
    update();
3✔
159
      },
3✔
160
      Qt::QueuedConnection);
742✔
161

162
  con(score::Skin::instance(), &score::Skin::changed, this, [this] {
768✔
163
    // updateText() keeps its pixmap unless the text or pen changed, and a
164
    // font change moves neither.
165
    m_lastText.clear();
26✔
166
    m_lastPen = nullptr;
26✔
167
    m_bench = QPixmap{};
26✔
168
    updateText();
26✔
169
    update();
26✔
170
  });
26✔
171
}
×
172

173
DefaultHeaderDelegate::~DefaultHeaderDelegate() { }
1,307✔
174

175
void DefaultHeaderDelegate::updateBench(double d)
29✔
176
{
177
  if(d >= 0.)
29✔
178
  {
179
    const auto& style = Process::Style::instance();
×
180
    m_bench = makeGlyphs(
×
181
        QString::number(d, 'g', 3),
×
182
        m_sel ? style.IntervalHeaderTextPen() : style.SlotHeaderTextPen());
×
183
  }
×
184
  else
185
  {
186
    m_bench = QPixmap{};
29✔
187
  }
188
  update();
29✔
189
}
29✔
190

191
void DefaultHeaderDelegate::updateText()
1,272✔
192
{
193
  auto& style = Process::Style::instance();
1,272✔
194
  const QPen& pen = m_sel ? style.IntervalHeaderTextPen() : textPen(style, m_model);
1,272✔
195
  if(&pen != m_lastPen || m_model.prettyName() != m_lastText)
1,272✔
196
  {
197
    m_line = makeGlyphs(m_model.prettyName(), pen);
595✔
198
    m_lastPen = &pen;
595✔
199
    m_lastText = m_model.prettyName();
595✔
200
    update();
595✔
201
  }
595✔
202
}
1,272✔
203

204
const QPen& DefaultHeaderDelegate::textPen(
1,497✔
205
    Style& style, const Process::ProcessModel& model) const noexcept
206
{
207
  score::ModelMetadata* parent_col
1,497✔
208
      = model.parent()->template findChild<score::ModelMetadata*>(
1,497✔
209
          {}, Qt::FindDirectChildrenOnly);
1,497✔
210
  auto& b = parent_col->getColor().getBrush();
1,497✔
211
  if(&b == &style.IntervalDefaultBackground())
1,497✔
212
    return style.skin.HalfLight.main.pen_cosmetic;
1,497✔
213
  else
214
    return b.lighter180.pen_cosmetic;
×
215
}
1,497✔
216

217
void DefaultHeaderDelegate::setSize(QSizeF sz)
2,147✔
218
{
219
  GraphicsShapeItem::setSize(sz);
2,147✔
220

221
  for(auto p : m_inPorts)
3,699✔
222
  {
223
    if(p->x() > sz.width())
1,552✔
224
    {
225
      if(p->isVisible())
×
226
        p->setPortVisible(false);
×
227
    }
×
228
    else
229
    {
230
      if(!p->isVisible())
1,552✔
231
        p->setPortVisible(true);
×
232
    }
233
  }
234
}
2,147✔
235

236
void DefaultHeaderDelegate::updatePorts()
969✔
237
{
238
  qDeleteAll(m_inPorts);
969✔
239
  m_inPorts.clear();
969✔
240

241
  m_portEndX = m_portStartX;
969✔
242
  const auto& inlets = m_model.inlets();
969✔
243

244
  auto& portFactory = score::AppContext().interfaces<Process::PortFactoryList>();
969✔
245
  for(Process::Inlet* port : inlets)
1,423✔
246
  {
247
    if(port->displayHandledExplicitly)
454✔
248
      continue;
×
249
    if(auto fact = portFactory.get(port->concreteKey()))
454✔
250
    {
251
      if(auto item = fact->makePortItem(*port, m_context, this, this))
454✔
252
      {
253
        item->setPos(m_portEndX, portY());
454✔
254
        m_inPorts.push_back(item);
454✔
255
        m_portEndX += ((QGraphicsItem*)item)->boundingRect().width() - 2.;
454✔
256
      }
454✔
257
    }
454✔
258
    else
259
    {
260
      qWarning() << "Port factory for " << port << " not found !";
×
261
    }
262
  }
263
}
969✔
264

265
void DefaultHeaderDelegate::paint(
192✔
266
    QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
267
{
268
  painter->setRenderHint(QPainter::SmoothPixmapTransform, false);
192✔
269
  const auto start = 3. + m_portStartX;
192✔
270
  const auto w = boundingRect().width();
192✔
271
  if(w > minPortWidth())
192✔
272
  {
273
    if(m_inPorts.empty())
192✔
274
    {
275
      painter->drawPixmap(QPointF{start, SCORE_YPOS(2., -1.)}, m_line);
×
276
    }
×
277
    else
278
    {
279
      double startText = start + m_portEndX;
192✔
280
      painter->drawPixmap(QPointF{startText, SCORE_YPOS(2., -1.)}, m_line);
192✔
281
    }
282

283
    if(!m_bench.isNull())
192✔
284
      painter->drawPixmap(QPointF{w - 32., SCORE_YPOS(2., -1.)}, m_bench);
×
285
  }
192✔
286
}
192✔
287

288
DefaultFooterDelegate::DefaultFooterDelegate(
1,484✔
289
    const Process::ProcessModel& model, const Process::Context& context)
290
    : FooterDelegate{model, context}
742✔
291
{
742✔
292
  auto& skin = score::Skin::instance();
742✔
293
  setCursor(skin.CursorScaleV);
742✔
294
  setFlag(ItemHasNoContents, true);
742✔
295
  con(m_model, &Process::ProcessModel::controlOutletAdded, this,
1,484✔
296
      &DefaultFooterDelegate::updatePorts);
742✔
297
  con(m_model, &Process::ProcessModel::controlOutletRemoved, this,
1,484✔
298
      &DefaultFooterDelegate::updatePorts);
742✔
299
  con(model, &Process::ProcessModel::outletsChanged, this,
1,484✔
300
      &DefaultFooterDelegate::updatePorts);
742✔
301
  updatePorts();
742✔
302
}
×
303

304
DefaultFooterDelegate::~DefaultFooterDelegate() { }
1,484✔
305

306
void DefaultFooterDelegate::setSize(QSizeF sz)
2,147✔
307
{
308
  if(sz != m_size)
2,147✔
309
  {
310
    prepareGeometryChange();
1,100✔
311
    m_size = sz;
1,100✔
312

313
    for(auto p : m_outPorts)
2,200✔
314
    {
315
      if(p->x() > sz.width())
1,100✔
316
      {
317
        if(p->isVisible())
×
318
          p->setPortVisible(false);
×
319
      }
×
320
      else
321
      {
322
        if(!p->isVisible())
1,100✔
323
          p->setPortVisible(true);
×
324
      }
325
    }
326
    update();
1,100✔
327
  }
1,100✔
328
}
2,147✔
329

330
void DefaultFooterDelegate::updatePorts()
742✔
331
{
332
  qDeleteAll(m_outPorts);
742✔
333
  m_outPorts.clear();
742✔
334

335
  auto& portFactory = score::AppContext().interfaces<Process::PortFactoryList>();
742✔
336

337
  m_portEndX = 0.;
742✔
338
  for(Process::Outlet* port : m_model.outlets())
1,484✔
339
  {
340
    if(port->displayHandledExplicitly)
742✔
341
      continue;
×
342
    if(auto fact = portFactory.get(port->concreteKey()))
742✔
343
    {
344
      auto item = fact->makePortItem(*port, m_context, this, this);
742✔
345
      item->setPos(m_portEndX, SCORE_YPOS(0., 0.));
742✔
346
      m_outPorts.push_back(item);
742✔
347
      m_portEndX += ((QGraphicsItem*)item)->boundingRect().width() - 2.;
742✔
348
    }
742✔
349
    else
350
    {
351
      qWarning() << "Port factory for " << port << " not found !";
×
352
    }
353
  }
354
}
742✔
355

356
void DefaultFooterDelegate::paint(
×
357
    QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget)
358
{
359
  // painter->fillRect(boundingRect(), Qt::white);
360
}
×
361

362
FooterDelegate::FooterDelegate(
1,484✔
363
    const Process::ProcessModel& model, const Process::Context& context)
364
    : m_model{model}
742✔
365
    , m_context{context}
742✔
366
{
742✔
367
  setAcceptedMouseButtons(Qt::NoButton);
742✔
368
}
×
369

370
FooterDelegate::~FooterDelegate() { }
742✔
371

372
QRectF FooterDelegate::boundingRect() const
609✔
373
{
374
  return {0., 0., m_size.width(), m_size.height()};
609✔
375
}
376

377
void FooterDelegate::mousePressEvent(QGraphicsSceneMouseEvent* event)
×
378
{
379
  event->ignore();
×
380
}
×
381

382
void FooterDelegate::mouseMoveEvent(QGraphicsSceneMouseEvent* event)
×
383
{
384
  event->ignore();
×
385
}
×
386

387
void FooterDelegate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
×
388
{
389
  event->ignore();
×
390
}
×
391

392
int FooterDelegate::type() const
×
393
{
394
  return UserType + 10000; // See ScenarioDocumentViewConstants
×
395
}
396
}
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