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

ossia / score / 30754311710

02 Aug 2026 03:25PM UTC coverage: 15.222% (-0.04%) from 15.263%
30754311710

Pull #2163

github

web-flow
Merge 8b7bf0aad into e99b6a5da
Pull Request #2163: Fix backwards playback for audio plug-ins (VST, VST3, LV2, JSFX)

0 of 57 new or added lines in 5 files covered. (0.0%)

343 existing lines in 16 files now uncovered.

30381 of 199582 relevant lines covered (15.22%)

1075.26 hits per line

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

57.33
/src/plugins/score-plugin-scenario/Scenario/Document/Interval/FullView/FullViewIntervalPresenter.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 "FullViewIntervalPresenter.hpp"
4

5
#include "AddressBarItem.hpp"
6
#include "FullViewIntervalHeader.hpp"
7

8
#include <Process/Dataflow/NodeItem.hpp>
9
#include <Process/Focus/FocusDispatcher.hpp>
10
#include <Process/ApplicationPlugin.hpp>
11
#include <Process/HeaderDelegate.hpp>
12
#include <Process/LayerView.hpp>
13
#include <Process/ProcessContext.hpp>
14
#include <Process/ProcessList.hpp>
15
#include <Process/Style/Pixmaps.hpp>
16
#include <Process/Style/ScenarioStyle.hpp>
17

18
#include <Scenario/Application/Drops/ScenarioDropHandler.hpp>
19
#include <Scenario/Application/Menus/ScenarioContextMenuManager.hpp>
20
#include <Scenario/Application/ScenarioApplicationPlugin.hpp>
21
#include <Scenario/Commands/Interval/Rack/SwapSlots.hpp>
22
#include <Scenario/Document/BaseScenario/BaseScenario.hpp>
23
#include <Scenario/Document/Interval/DefaultHeaderDelegate.hpp>
24
#include <Scenario/Document/Interval/FullView/FullViewIntervalView.hpp>
25
#include <Scenario/Document/Interval/FullView/NodalIntervalView.hpp>
26
#include <Scenario/Document/Interval/FullView/TimeSignatureItem.hpp>
27
#include <Scenario/Document/Interval/FullView/Timebar.hpp>
28
#include <Scenario/Document/Interval/IntervalModel.hpp>
29
#include <Scenario/Document/Interval/IntervalPresenter.hpp>
30
#include <Scenario/Document/Interval/LayerData.hpp>
31
#include <Scenario/Document/Interval/SlotHeader.hpp>
32
#include <Scenario/Document/ScenarioDocument/MusicalGrid.hpp>
33
#include <Scenario/Document/ScenarioDocument/ScenarioDocumentPresenter.hpp>
34
#include <Scenario/Process/ScenarioModel.hpp>
35
#include <Scenario/Settings/ScenarioSettingsModel.hpp>
36

37
#include <Automation/AutomationColors.hpp>
38

39
#include <score/document/DocumentInterface.hpp>
40
#include <score/graphics/GraphicWidgets.hpp>
41
#include <score/graphics/GraphicsItem.hpp>
42
#include <score/tools/Bind.hpp>
43

44
#include <ossia/detail/algorithms.hpp>
45
#include <ossia/detail/closest_element.hpp>
46
#include <ossia/detail/flicks.hpp>
47
#include <ossia/detail/ssize.hpp>
48

49
#include <QGraphicsScene>
50
#include <QGraphicsView>
51
#include <QMenu>
52

53
#include <wobjectimpl.h>
54
W_OBJECT_IMPL(Scenario::FullViewIntervalPresenter)
65✔
55

56
namespace Scenario
57
{
58
static double timeSignatureBarY = -45.;
59

60
static SlotDragOverlay* full_slot_drag_overlay{};
61

62
Timebars::Timebars(FullViewIntervalPresenter& self)
12✔
63
    : timebar{self, self.view()}
6✔
64
{
65
  timebar.setPos(0, -47);
6✔
66
}
6✔
67

68
void FullViewIntervalPresenter::startSlotDrag(int curslot, QPointF pos) const
×
69
{
70
  // Create an overlay object
71
  full_slot_drag_overlay = new SlotDragOverlay{*this, Slot::FullView};
×
72
  connect(
×
73
      full_slot_drag_overlay, &SlotDragOverlay::dropBefore, this,
×
74
      [this, curslot](int slot) {
×
75
    CommandDispatcher<>{this->m_context.commandStack}
×
76
        .submit<Command::ChangeSlotPosition>(
×
77
            this->m_model, Slot::RackView::FullView, curslot, slot);
×
78
      },
×
79
      Qt::QueuedConnection); // needed because else SlotHeader is removed and
80
                             // stopSlotDrag can't be called
81

82
  full_slot_drag_overlay->setParentItem(view());
×
83
  full_slot_drag_overlay->onDrag(pos);
×
84
}
×
85

86
void FullViewIntervalPresenter::stopSlotDrag() const
×
87
{
88
  delete full_slot_drag_overlay;
×
89
  full_slot_drag_overlay = nullptr;
×
90
}
×
91

92
FullViewIntervalPresenter::FullViewIntervalPresenter(
6✔
93
    ZoomRatio zoom, const IntervalModel& interval, const Process::Context& ctx,
94
    QGraphicsItem* parentobject, QObject* parent)
95
    : IntervalPresenter{zoom, interval, new FullViewIntervalView{*this, parentobject}, new FullViewIntervalHeader{ctx, parentobject}, ctx, parent}
6✔
96
    , m_timebars{new Timebars{*this}}
97
    , m_grid{new MusicalGrid{*m_timebars}}
98
    , m_settings{ctx.app.settings<Scenario::Settings::Model>()}
99
{
12✔
100
  m_view->setPos(0, 0);
6✔
101
  m_header->setPos(0, -IntervalHeader::headerHeight());
6✔
102

103
  m_timebars->lightBars.setParentItem(m_view);
6✔
104
  m_timebars->lighterBars.setParentItem(m_view);
6✔
105

106
  // Address bar
107
  auto& addressBar = static_cast<FullViewIntervalHeader*>(m_header)->bar();
6✔
108
  addressBar.setTargetObject(score::IDocument::unsafe_path(interval));
6✔
109
  con(addressBar, &AddressBarItem::intervalSelected, this,
12✔
110
      &FullViewIntervalPresenter::intervalSelected);
6✔
111

112
  con(interval.selection, &Selectable::changed, (FullViewIntervalView*)m_view,
12✔
113
      &FullViewIntervalView::setSelected);
6✔
114

115
  // Time
116
  con(interval.duration, &IntervalDurations::defaultDurationChanged, this,
12✔
117
      [&](const TimeVal& val) { on_defaultDurationChanged(val); });
6✔
118
  con(interval.duration, &IntervalDurations::guiDurationChanged, this,
12✔
119
      [&](const TimeVal& val) {
6✔
120
    on_guiDurationChanged(val);
×
121
    updateChildren();
×
122
  });
×
123

124
  auto& settings = m_context.app.settings<Scenario::Settings::Model>();
6✔
125
  con(m_model, &IntervalModel::timeSignaturesChanged, this,
12✔
126
      [this] { updateTimeBars(); });
6✔
127
  con(m_model, &IntervalModel::hasTimeSignatureChanged, this,
12✔
128
      [this] { updateTimeBars(); });
6✔
129
  ::bind(settings, Settings::Model::p_MeasureBars{}, this, [this](bool show) {
12✔
130
    this->m_timebars->lightBars.setVisible(show);
6✔
131
    this->m_timebars->lighterBars.setVisible(show);
6✔
132
  });
6✔
133

134
  // Slots
135
  con(m_model, &IntervalModel::rackChanged, this, [this](Slot::RackView t) {
6✔
136
    if(t == Slot::FullView)
×
137
      on_rackChanged();
×
138
  });
×
139

140
  con(m_model, &IntervalModel::slotAdded, this, [this](const SlotId& s) {
13✔
141
    if(s.fullView())
7✔
142
      on_rackChanged();
5✔
143
  });
7✔
144

145
  con(m_model, &IntervalModel::slotRemoved, this, [this](const SlotId& s) {
20✔
146
    if(s.fullView())
14✔
147
      on_rackChanged();
12✔
148
  });
14✔
149

150
  con(m_model, &IntervalModel::slotsSwapped, this,
12✔
151
      [this](int i, int j, Slot::RackView v) {
6✔
152
    if(v == Slot::FullView)
×
153
      on_rackChanged();
×
154
  });
×
155

156
  con(m_model, &IntervalModel::slotResized, this, [this](const SlotId& s) {
6✔
157
    if(s.fullView())
×
158
      this->updatePositions();
×
159
  });
×
160

161
  // Execution
162
  con(
6✔
163
      interval, &IntervalModel::executionEvent, this,
6✔
164
      [this](Scenario::IntervalExecutionEvent ev) {
6✔
UNCOV
165
    switch(ev)
×
166
    {
167
      case IntervalExecutionEvent::Playing:
168
        m_view->setExecuting(true);
×
169
        m_view->updatePaths();
×
170
        m_view->update();
×
171
        break;
×
172
      case IntervalExecutionEvent::Stopped:
173
        m_view->setExecuting(false);
×
174
        break;
×
175
      case IntervalExecutionEvent::Finished:
UNCOV
176
        m_view->setExecuting(false);
×
UNCOV
177
        m_view->setPlayWidth(0.);
×
UNCOV
178
        m_view->updatePaths();
×
UNCOV
179
        m_view->update();
×
UNCOV
180
        break;
×
181
      default:
182
        break;
×
183
    }
UNCOV
184
      },
×
185
      Qt::QueuedConnection);
6✔
186

187
  // Drops
188
  con(*this->view(), &IntervalView::dropReceived, this,
6✔
189
      [this](const QPointF& pos, const QMimeData& mime) {
6✔
190
    m_context.app.interfaces<Scenario::IntervalDropHandlerList>().drop(
×
191
        m_context, m_model, {}, mime);
×
192
  });
×
193

194
  // Initial state
195
  on_rackChanged();
6✔
196
}
×
197

198
FullViewIntervalPresenter::~FullViewIntervalPresenter()
6✔
199
{
×
200
  delete m_grid;
6✔
201
  auto view = Scenario::view(this);
6✔
202
  QGraphicsScene* sc = view->scene();
6✔
203

204
  for(auto& slt : m_slots)
6✔
205
    slt.cleanup(sc);
×
206

207
  if(sc)
6✔
208
    sc->removeItem(view);
6✔
209

210
  delete m_timebars;
6✔
211
  delete view;
6✔
212
}
6✔
213

214
void FullViewIntervalPresenter::createSlot(int slot_i, const FullSlot& s)
23✔
215
{
216
  if(!s.nodal)
23✔
217
  {
218
    // Create the slot
219
    LayerSlotPresenter p;
21✔
220
    p.header = new SlotHeader{*this, slot_i, m_view};
21✔
221
    p.footer = new AmovibleSlotFooter{*this, slot_i, m_view};
21✔
222
    auto it = m_slots.emplace(
42✔
223
        m_slots.begin() + slot_i, Scenario::SlotPresenter{std::move(p)});
21✔
224
    Scenario::LayerSlotPresenter& slt = *it->getLayerSlot();
21✔
225
    if(auto p = m_model.processes.find(s.process); p != m_model.processes.end())
21✔
226
      setupSlot(slt, *p, slot_i);
21✔
227
  }
21✔
228
  else
229
  {
230
    NodalSlotPresenter p;
2✔
231
    p.header = new SlotHeader{*this, slot_i, m_view};
2✔
232
    auto nodal = new NodalIntervalView{
4✔
233
        NodalIntervalView::OnlyEffects, this->model(), this->context(), this->view()};
2✔
234
    nodal->setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);
2✔
235
    p.view = nodal;
2✔
236
    p.footer = new AmovibleSlotFooter{*this, slot_i, m_view};
2✔
237
    auto it = m_slots.emplace(m_slots.begin() + slot_i, std::move(p));
2✔
238

239
    setupSlot(*it->getNodalSlot(), slot_i);
2✔
240
  }
241
}
23✔
242

243
void FullViewIntervalPresenter::setupSlot(NodalSlotPresenter& slot, int slot_i)
2✔
244
{
245
  updateProcessShape(slot_i);
2✔
246
}
2✔
247

248
void FullViewIntervalPresenter::setupSlot(
21✔
249
    LayerSlotPresenter& slot, const Process::ProcessModel& proc, int slot_i)
250
{
251
  // Create a layer container
252
  auto& ld = slot.layers.emplace_back(&proc);
21✔
253

254
  // Create layers
255
  const auto factory = m_context.processList.findDefaultFactory(proc.concreteKey());
21✔
256

257
  const auto gui_width = m_model.duration.guiDuration().toPixels(m_zoomRatio);
21✔
258
  const auto def_width = m_model.duration.defaultDuration().toPixels(m_zoomRatio);
21✔
259
  const auto slot_height = ld.model().getSlotHeight();
21✔
260
  ld.updateLoops(
42✔
261
      m_context, m_zoomRatio, gui_width, def_width, slot_height, m_view, this);
21✔
262

263
  {
264
    slot.headerDelegate = factory->makeHeaderDelegate(proc, m_context, slot.header);
21✔
265
    slot.headerDelegate->updateText();
21✔
266
    slot.headerDelegate->setParentItem(slot.header);
21✔
267
    // slot.headerDelegate->setFlag(QGraphicsItem::GraphicsItemFlag::ItemClipsToShape);
268
    // slot.headerDelegate->setFlag(QGraphicsItem::GraphicsItemFlag::ItemClipsChildrenToShape);
269
    slot.headerDelegate->setPos(30, 0);
21✔
270
  }
271
  {
272
    slot.footerDelegate = factory->makeFooterDelegate(proc, m_context);
21✔
273
    slot.footerDelegate->setParentItem(slot.footer);
21✔
274
    // slot.footerDelegate->setFlag(QGraphicsItem::GraphicsItemFlag::ItemClipsToShape);
275
    // slot.footerDelegate->setFlag(QGraphicsItem::GraphicsItemFlag::ItemClipsChildrenToShape);
276
    slot.footerDelegate->setPos(30, 0);
21✔
277
  }
278

279
  // Connect loop things
280
  LayerData::disconnect(proc, *this);
21✔
281

282
  con(proc, &Process::ProcessModel::loopsChanged, this, [this, slot_i](bool b) {
21✔
283
    SCORE_ASSERT(slot_i < int(m_slots.size()));
×
284
    auto slt = this->m_slots[slot_i].getLayerSlot();
×
285

286
    SCORE_ASSERT(slt);
×
287
    SCORE_ASSERT(!slt->layers.empty());
×
288
    LayerData& ld = slt->layers.front();
×
289
    const auto gui_width = m_model.duration.guiDuration().toPixels(m_zoomRatio);
×
290
    const auto def_width = m_model.duration.defaultDuration().toPixels(m_zoomRatio);
×
291
    const auto slot_height = ld.model().getSlotHeight();
×
292

293
    if(!(ld.model().flags() & Process::ProcessFlags::HandlesLooping))
×
294
    {
295
      ld.updateLoops(
×
296
          m_context, m_zoomRatio, gui_width, def_width, slot_height, this->m_view, this);
×
297
    }
×
298
    else
299
    {
300
      ld.parentGeometryChanged();
×
301
    }
302
  });
×
303

304
  con(proc, &Process::ProcessModel::startOffsetChanged, this, [this, slot_i] {
21✔
305
    SCORE_ASSERT(slot_i < int(m_slots.size()));
×
306
    auto slt = this->m_slots[slot_i].getLayerSlot();
×
307

308
    SCORE_ASSERT(slt);
×
309
    SCORE_ASSERT(!slt->layers.empty());
×
310
    auto& ld = slt->layers.front();
×
311

312
    if(!(ld.model().flags() & Process::ProcessFlags::HandlesLooping))
×
313
    {
314
      ld.updateStartOffset(-ld.model().startOffset().toPixels(m_zoomRatio));
×
315
    }
×
316
    else
317
    {
318
      ld.parentGeometryChanged();
×
319
    }
320
  });
×
321
  con(proc, &Process::ProcessModel::loopDurationChanged, this, [this, slot_i] {
21✔
322
    SCORE_ASSERT(slot_i < int(m_slots.size()));
×
323
    auto slt = this->m_slots[slot_i].getLayerSlot();
×
324

325
    SCORE_ASSERT(slt);
×
326
    SCORE_ASSERT(!slt->layers.empty());
×
327
    LayerData& ld = slt->layers.front();
×
328
    if(!(ld.model().flags() & Process::ProcessFlags::HandlesLooping))
×
329
    {
330
      const auto gui_width = m_model.duration.guiDuration().toPixels(m_zoomRatio);
×
331
      const auto def_width = m_model.duration.defaultDuration().toPixels(m_zoomRatio);
×
332
      const auto slot_height = ld.model().getSlotHeight();
×
333
      ld.updateLoops(
×
334
          m_context, m_zoomRatio, gui_width, def_width, slot_height, this->m_view, this);
×
335
    }
×
336
    else
337
    {
338
      ld.parentGeometryChanged();
×
339
    }
340
  });
×
341

342
  updateProcessShape(slot_i);
21✔
343
}
21✔
344

345
void FullViewIntervalPresenter::requestSlotMenu(int slot, QPoint pos, QPointF sp) const
×
346
{
347
  auto slt = m_slots[slot].getLayerSlot();
×
348
  if(!slt)
×
349
    return;
×
350

351
  auto menu = new QMenu;
×
352
  auto& reg = score::GUIAppContext()
×
353
                  .applicationPlugin<Process::ApplicationPlugin>()
×
354
                  .layerContextMenuRegistrar();
×
355

356
  slt->layers.front().fillContextMenu(*menu, pos, sp, reg);
×
357
  menu->exec(pos);
×
358
  menu->close();
×
359
  menu->deleteLater();
×
360
}
×
361

362
void FullViewIntervalPresenter::updateProcessShape(
61✔
363
    const LayerData& ld, const LayerSlotPresenter& slot)
364
{
365
  const auto h = ld.model().getSlotHeight();
61✔
366
  ld.setHeight(h);
61✔
367
  ld.updateContainerHeights(h); // TODO merge with setHeight
61✔
368

369
  auto width = m_model.duration.guiDuration().toPixels(m_zoomRatio);
61✔
370
  auto dwidth = m_model.duration.defaultDuration().toPixels(m_zoomRatio);
61✔
371
  ld.setWidth(width, dwidth);
61✔
372

373
  slot.header->setWidth(width);
61✔
374
  slot.footer->setWidth(width);
61✔
375

376
  slot.headerDelegate->setSize(QSizeF{
122✔
377
      std::max(0., width - SlotHeader::handleWidth() - SlotHeader::menuWidth()),
61✔
378
      SlotHeader::headerHeight()});
61✔
379
  slot.headerDelegate->setX(30);
61✔
380
  slot.footerDelegate->setSize(QSizeF{width, SlotFooter::footerHeight()});
61✔
381
  slot.footerDelegate->setX(30);
61✔
382

383
  ld.parentGeometryChanged();
61✔
384
}
61✔
385

386
void FullViewIntervalPresenter::updateProcessShape(LayerSlotPresenter& slot, int idx)
61✔
387
{
388
  if(!slot.layers.empty())
61✔
389
    updateProcessShape(slot.layers.front(), slot);
61✔
390
}
61✔
391

392
void FullViewIntervalPresenter::updateProcessShape(NodalSlotPresenter& slot, int idx)
6✔
393
{
394
  const auto h = this->model().getSlotHeight({idx, Slot::FullView});
6✔
395
  auto width = m_model.duration.guiDuration().toPixels(m_zoomRatio);
6✔
396
  //auto dwidth = m_model.duration.defaultDuration().toPixels(m_zoomRatio);
397
  slot.header->setWidth(width);
6✔
398
  slot.view->setRect({0, 0, width, h});
6✔
399
  slot.height = h;
6✔
400
  slot.footer->setWidth(width);
6✔
401
}
6✔
402

403
void FullViewIntervalPresenter::updateProcessShape(int idx)
65✔
404
{
405
  auto& slt = m_slots.at(idx);
65✔
406
  slt.visit([this, idx](auto& slot) { updateProcessShape(slot, idx); });
130✔
407
}
65✔
408

409
void FullViewIntervalPresenter::on_slotRemoved(int pos)
23✔
410
{
411
  SlotPresenter& slot = m_slots.at(pos);
23✔
412
  auto lay_slt = slot.getLayerSlot();
23✔
413
  if(lay_slt)
23✔
414
  {
415
    for(const LayerData& proc : lay_slt->layers)
42✔
416
    {
417
      proc.disconnect(proc.model(), *this);
21✔
418
    }
419
  }
21✔
420
  slot.cleanup(this->view()->scene());
23✔
421

422
  m_slots.erase(m_slots.begin() + pos);
23✔
423
}
23✔
424

425
void FullViewIntervalPresenter::updateProcessesShape()
40✔
426
{
427
  for(int i = 0; i < std::ssize(m_slots); i++)
82✔
428
  {
429
    updateProcessShape(i);
42✔
430
  }
42✔
431
}
40✔
432

433
void FullViewIntervalPresenter::updatePositions()
23✔
434
{
435
  using namespace std;
436
  // Vertical shape
437
  m_view->setHeight(rackHeight() + IntervalHeader::headerHeight());
23✔
438

439
  // Set the slots position graphically in order.
440
  qreal currentSlotY = 2.;
23✔
441

442
  for(int i = 0; i < std::ssize(m_slots); i++)
46✔
443
  {
444
    m_slots[i].visit(
46✔
445
        [&](LayerSlotPresenter& slot) {
44✔
446
      if(!slot.layers.empty())
21✔
447
      {
448
        LayerData& ld = slot.layers.front();
21✔
449

450
        assert(slot.header);
21✔
451
        slot.header->setPos(QPointF{0, currentSlotY});
21✔
452
        slot.header->setSlotIndex(i);
21✔
453
        currentSlotY += slot.headerHeight();
21✔
454

455
        ld.updateYPositions(currentSlotY);
21✔
456
        ld.update();
21✔
457

458
        currentSlotY += ld.model().getSlotHeight();
21✔
459
        assert(slot.footer);
21✔
460
        slot.footer->setPos(QPointF{0, currentSlotY});
21✔
461
        slot.footer->setSlotIndex(i);
21✔
462
        currentSlotY += slot.footerHeight();
21✔
463
      }
21✔
464
      else
465
      {
466
        currentSlotY += SlotHeader::headerHeight();
×
467
        currentSlotY += SlotFooter::footerHeight();
×
468
      }
469
        },
21✔
470
        [&](NodalSlotPresenter& slot) {
25✔
471
      assert(slot.header);
2✔
472
      slot.header->setPos(QPointF{0, currentSlotY});
2✔
473
      slot.header->setSlotIndex(i);
2✔
474
      currentSlotY += SlotHeader::headerHeight();
2✔
475

476
      slot.view->setPos(QPointF{0, currentSlotY});
2✔
477
      updateProcessShape(slot, i);
2✔
478
      currentSlotY += slot.height;
2✔
479

480
      assert(slot.footer);
2✔
481
      slot.footer->setPos(QPointF{0, currentSlotY});
2✔
482
      slot.footer->setSlotIndex(i);
2✔
483
      currentSlotY += SlotFooter::footerHeight();
2✔
484
    });
2✔
485
  }
23✔
486

487
  // Horizontal shape
488
  on_defaultDurationChanged(m_model.duration.defaultDuration());
23✔
489
  on_guiDurationChanged(m_model.duration.guiDuration());
23✔
490

491
  updateProcessesShape();
23✔
492
  updateHeight();
23✔
493
}
23✔
494

495
double FullViewIntervalPresenter::rackHeight() const
69✔
496
{
497
  qreal height = 0;
69✔
498
  for(const SlotPresenter& slt : m_slots)
141✔
499
  {
500
    if(auto slot = slt.getLayerSlot())
72✔
501
    {
502
      if(!slot->layers.empty())
68✔
503
        height += slot->layers.front().model().getSlotHeight();
68✔
504

505
      height += slot->headerHeight() + slot->footerHeight();
68✔
506
    }
68✔
507
    else if(auto slot = slt.getNodalSlot())
4✔
508
    {
509
      height += SlotHeader::headerHeight();
4✔
510
      height += slot->height;
4✔
511
      height += SlotFooter::footerHeight();
4✔
512
    }
4✔
513
  }
514
  return height;
69✔
515
}
516

517
void FullViewIntervalPresenter::on_rackChanged()
23✔
518
{
519
  // Remove existing
520
  if(!m_slots.empty())
23✔
521
  {
522
    for(int i = std::ssize(m_slots); i-- > 0;)
40✔
523
    {
524
      on_slotRemoved(i);
23✔
525
    }
526
  }
17✔
527

528
  // Recreate
529
  m_slots.reserve(m_model.fullView().size());
23✔
530

531
  int i = 0;
23✔
532
  for(const auto& slt : m_model.fullView())
46✔
533
  {
534
    createSlot(i, slt);
23✔
535
    i++;
23✔
536
  }
537

538
  // Update view
539
  updatePositions();
23✔
540
}
23✔
541

542
void FullViewIntervalPresenter::updateScaling()
17✔
543
{
544
  on_defaultDurationChanged(model().duration.defaultDuration());
17✔
545
  on_guiDurationChanged(model().duration.guiDuration());
17✔
546
  IntervalPresenter::updateScaling();
17✔
547
  updateHeight();
17✔
548
}
17✔
549

550
void FullViewIntervalPresenter::selectedSlot(int i) const
×
551
{
552
  score::SelectionDispatcher disp{m_context.selectionStack};
×
553
  SCORE_ASSERT(size_t(i) < m_slots.size());
×
554
  auto slot = m_slots[i].getLayerSlot();
×
555
  if(slot)
×
556
  {
557
    if(!slot->layers.empty())
×
558
    {
559
      if(auto pres = slot->layers.front().mainPresenter())
×
560
      {
561
        m_context.focusDispatcher.focus(pres);
×
562
        disp.select(slot->layers.front().model());
×
563
      }
×
564
      else
565
      {
566
        SCORE_SOFT_ASSERT(!"No main presenter");
×
567
      }
568
    }
×
569
    else
570
    {
571
      SCORE_SOFT_ASSERT(!"No layer!");
×
572
    }
573
  }
×
574
}
×
575

576
void FullViewIntervalPresenter::on_defaultDurationChanged(const TimeVal& val)
40✔
577
{
578
  const auto w = val.toPixelsRaw(m_zoomRatio);
40✔
579
  m_view->setDefaultWidth(w);
40✔
580
  m_view->updateCounterPos();
40✔
581
  ((FullViewIntervalView*)m_view)->updateOverlayPos();
40✔
582

583
  m_header->update();
40✔
584
  m_view->update();
40✔
585
}
40✔
586

587
void FullViewIntervalPresenter::on_zoomRatioChanged(ZoomRatio ratio)
17✔
588
{
589
  IntervalPresenter::on_zoomRatioChanged(ratio);
17✔
590

591
  updateTimeBars();
17✔
592

593
  auto gui_width = m_model.duration.guiDuration().toPixels(ratio);
17✔
594
  auto def_width = m_model.duration.defaultDuration().toPixels(ratio);
17✔
595

596
  m_timebars->timebar.setWidth(gui_width);
17✔
597

598
  for(auto& slot : m_slots)
36✔
599
  {
600
    if(auto lay_slot = slot.getLayerSlot())
19✔
601
    {
602
      if(lay_slot->headerDelegate)
19✔
603
        lay_slot->headerDelegate->on_zoomRatioChanged(ratio);
19✔
604
      for(auto& ld : lay_slot->layers)
38✔
605
      {
606
        const auto slot_height = ld.model().getSlotHeight();
19✔
607
        ld.on_zoomRatioChanged(
38✔
608
            m_context, ratio, gui_width, def_width, slot_height, m_view, this);
19✔
609
      }
610
    }
19✔
611
  }
612

613
  updateProcessesShape();
17✔
614
}
17✔
615

616
Process::MagneticInfo FullViewIntervalPresenter::magneticPosition(
×
617
    const QObject* o, const TimeVal t) const noexcept
618
{
619
  // TODO instead call a virtual function on the process that return the date
620
  // of the closest thing If it's less close than the grid... return
621
  TimeVal scenarioT = t;
×
622
  TimeVal closestTimeSyncT = TimeVal::fromMsecs(std::numeric_limits<int32_t>::max());
×
623
  bool snapToScenario{};
×
624
  bool foundMagnetism = false;
×
625

626
  // Priority order:
627
  // 1. In the object's parent process
628
  if(auto given_ts = qobject_cast<const Scenario::TimeSyncModel*>(o))
×
629
  {
630
    if(auto scenario = qobject_cast<Scenario::ProcessModel*>(given_ts->parent()))
×
631
    {
632
      for(auto& ts : scenario->timeSyncs)
×
633
      {
634
        if(given_ts == &ts)
×
635
          continue;
×
636

637
        if(std::abs(ts.date().impl - t.impl) < std::abs(closestTimeSyncT.impl - t.impl))
×
638
        {
639
          closestTimeSyncT = ts.date();
×
640
        }
×
641
      }
642
      double delta = std::abs((closestTimeSyncT - t).toPixels(m_zoomRatio));
×
643
      if(delta < 10)
×
644
      {
645
        scenarioT = closestTimeSyncT;
×
646
        snapToScenario = true;
×
647
        foundMagnetism = true;
×
648
      }
×
649
    }
×
650
  }
×
651

652
  auto is_parent = [](QObject* const parent, const QObject* child) {
×
653
    while((child = child->parent()))
×
654
    {
655
      if(child == parent)
×
656
        return true;
×
657
    }
658
    return false;
×
659
  };
×
660

661
  // 2. In the processes around
662
  if(!foundMagnetism)
×
663
  {
664
    for(auto& proc : this->model().processes)
×
665
    {
666
      if(!is_parent(&proc, o))
×
667
      {
668
        if(auto pos = proc.magneticPosition(o, t))
×
669
        {
670
          if(std::abs(pos->time.impl - t.impl)
×
671
             < std::abs(closestTimeSyncT.impl - t.impl))
×
672
            closestTimeSyncT = pos->time;
×
673
        }
×
674
      }
×
675
    }
676

677
    double delta = std::abs((closestTimeSyncT - t).toPixels(m_zoomRatio));
×
678
    if(delta < 10)
×
679
    {
680
      scenarioT = closestTimeSyncT;
×
681
      snapToScenario = true;
×
682
      foundMagnetism = true;
×
683
    }
×
684
  }
×
685

686
  // 3. In the child processes around the closest scenario to the moved object
687

688
  if(!foundMagnetism)
×
689
  {
690
    auto parentScenar = Scenario::closestParentScenario(o);
×
691
    if(parentScenar)
×
692
    {
693
      for(auto& itv : parentScenar->intervals)
×
694
      {
695
        for(auto& proc : itv.processes)
×
696
        {
697
          if(!is_parent(&proc, o))
×
698
          {
699
            if(auto pos = proc.magneticPosition(o, t))
×
700
            {
701
              pos->time += itv.date();
×
702

703
              if(std::abs(pos->time.impl - t.impl)
×
704
                 < std::abs(closestTimeSyncT.impl - t.impl))
×
705
                closestTimeSyncT = pos->time;
×
706
            }
×
707
          }
×
708
        }
709
      }
710
    }
×
711

712
    double delta = std::abs((closestTimeSyncT - t).toPixels(m_zoomRatio));
×
713
    if(delta < 10)
×
714
    {
715
      scenarioT = closestTimeSyncT;
×
716
      snapToScenario = true;
×
717
      foundMagnetism = true;
×
718
    }
×
719
  }
×
720

721
  // 4. That's all, don't search deeper
722

723
  if(!m_settings.getMagneticMeasures() || !m_settings.getMeasureBars())
×
724
    return {scenarioT, snapToScenario};
×
725

726
  // t is the time in the context of obj
727
  // we have to find its closest parent interval with a time signature
728
  // definition and compute its time delta
729
  const IntervalModel* cur_model = nullptr;
×
730
  do
×
731
  {
732
    cur_model = qobject_cast<const IntervalModel*>(o);
×
733
    if(cur_model)
×
734
      break;
×
735
    else
736
      o = o->parent();
×
737
  } while(!cur_model && o);
×
738
  auto [model, lastFound, timeDelta] = closestParentWithMusicalMetrics(&m_model);
×
739

740
  if(!o || !model)
×
741
    return {scenarioT, snapToScenario};
×
742

743
  // Find leftmost signature
744
  const TimeVal event_time_in_parent_ref = t + timeDelta;
×
745
  const auto& sig = model->timeSignatureMap();
×
746
  if(sig.empty())
×
747
    return {scenarioT, snapToScenario};
×
748

749
  // Snap to grid
750
  if(m_timebars->magneticTimings.empty())
×
751
    return {scenarioT, snapToScenario};
×
752

753
  const TimeVal& closestBar
×
754
      = closest_element(m_timebars->magneticTimings, event_time_in_parent_ref);
×
755

756
  if(!snapToScenario)
×
757
  {
758
    return {closestBar - timeDelta, snapToScenario};
×
759
  }
760
  else if(std::abs(closestBar.impl - t.impl) < std::abs(scenarioT.impl - t.impl))
×
761
  {
762
    return {closestBar - timeDelta, false};
×
763
  }
764
  else
765
  {
766
    return {scenarioT, snapToScenario};
×
767
  }
768
}
×
769

770
double FullViewIntervalPresenter::on_playPercentageChanged(double t)
×
771
{
772
  return IntervalPresenter::on_playPercentageChanged(t);
×
773
}
774

775
MusicalGrid& FullViewIntervalPresenter::grid() const noexcept
6✔
776
{
777
  return *m_grid;
6✔
778
}
779

780
void FullViewIntervalPresenter::on_visibleRectChanged(QRectF r)
9✔
781
{
782
  if(r != m_sceneRect)
9✔
783
  {
784
    m_sceneRect = r;
8✔
785
    updateTimeBars();
8✔
786

787
    updateAllSlots();
8✔
788
  }
8✔
789
}
9✔
790

791
void FullViewIntervalPresenter::setSnapLine(TimeVal t, bool enabled) { }
×
792

793
void FullViewIntervalPresenter::updateTimeBars()
25✔
794
{
795
  if(m_zoomRatio <= 0.)
25✔
796
    return;
×
797

798
  auto [model, lastFound, timeDelta] = closestParentWithMusicalMetrics(&m_model);
175✔
799

800
  this->m_timebars->timebar.setModel(model, timeDelta);
75✔
801
  this->m_timebars->timebar.setZoomRatio(m_zoomRatio);
25✔
802

803
  if(!model || !this->m_settings.getMeasureBars())
25✔
804
  {
805
    if(this->m_timebars->timebar.isEnabled())
×
806
    {
807
      this->m_timebars->lightBars.setVisible(false);
×
808
      this->m_timebars->lighterBars.setVisible(false);
×
809
      this->m_timebars->timebar.setEnabled(true);
×
810
      this->m_timebars->timebar.setVisible(true);
×
811
    }
×
812
    return;
×
813
  }
814
  else
815
  {
816
    if(!this->m_timebars->timebar.isEnabled())
25✔
817
    {
818
      this->m_timebars->lightBars.setVisible(true);
×
819
      this->m_timebars->lighterBars.setVisible(true);
×
820
      this->m_timebars->timebar.setEnabled(true);
×
821
      this->m_timebars->timebar.setVisible(true);
×
822
    }
×
823
  }
824

825
  auto scene_x0 = m_view->mapToScene(QPointF{}).x();
25✔
826

827
  QRectF sceneRect = m_sceneRect;
25✔
828
  sceneRect.adjust(-100, timeSignatureBarY, 100, timeSignatureBarY);
25✔
829

830
  // x0_time: time of the currently visible leftmost pixel
831
  TimeVal x0_time
832
      = TimeVal::fromPixels(sceneRect.x() - scene_x0, m_zoomRatio) + timeDelta;
50✔
833

834
  m_grid->setMeasures(model->timeSignatureMap());
50✔
835
  m_grid->compute(timeDelta, m_zoomRatio, sceneRect, x0_time);
50✔
836
}
25✔
837

838
void FullViewIntervalPresenter::on_modeChanged(IntervalModel::ViewMode m)
×
839
{
840
  // on_rackChanged();
841
  // updateTimeBars();
842
}
×
843

844
void FullViewIntervalPresenter::on_guiDurationChanged(const TimeVal& val)
40✔
845
{
846
  const auto gui_width = val.toPixels(m_zoomRatio);
40✔
847
  const auto def_width = m_model.duration.defaultDuration().toPixels(m_zoomRatio);
40✔
848
  m_header->setWidth(gui_width);
40✔
849
  m_timebars->timebar.setWidth(gui_width);
40✔
850

851
  static_cast<FullViewIntervalView*>(m_view)->setGuiWidth(gui_width);
40✔
852

853
  for(SlotPresenter& slot : m_slots)
82✔
854
  {
855
    slot.visit([&](auto& slot) { on_guiDurationChanged(slot, gui_width, def_width); });
84✔
856
  }
857
}
40✔
858

859
void FullViewIntervalPresenter::on_guiDurationChanged(
40✔
860
    LayerSlotPresenter& slot, double gui_width, double def_width)
861
{
862
  if(!slot.layers.empty())
40✔
863
  {
864
    auto& ld = slot.layers.front();
40✔
865
    const auto slot_height = ld.model().getSlotHeight();
40✔
866

867
    ld.updateLoops(
80✔
868
        m_context, m_zoomRatio, gui_width, def_width, slot_height, m_view, this);
40✔
869
  }
40✔
870
  slot.header->setWidth(gui_width);
40✔
871
  slot.footer->setWidth(gui_width);
40✔
872
}
40✔
873

874
void FullViewIntervalPresenter::on_guiDurationChanged(
2✔
875
    NodalSlotPresenter& slot, double gui_width, double def_width)
876
{
877
  slot.header->setWidth(gui_width);
2✔
878
  slot.footer->setWidth(gui_width);
2✔
879
}
2✔
880

881
void FullViewIntervalPresenter::updateHeight()
46✔
882
{
883
  m_view->setHeight(rackHeight() + IntervalHeader::headerHeight());
46✔
884

885
  updateChildren();
46✔
886
  heightChanged();
46✔
887
}
46✔
888
}
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