Line data Source code
1 : /*
2 : * SPDX-License-Identifier: GPL-3.0-only
3 : * MuseScore-Studio-CLA-applies
4 : *
5 : * MuseScore Studio
6 : * Music Composition & Notation
7 : *
8 : * Copyright (C) 2021 MuseScore Limited and others
9 : *
10 : * This program is free software: you can redistribute it and/or modify
11 : * it under the terms of the GNU General Public License version 3 as
12 : * published by the Free Software Foundation.
13 : *
14 : * This program is distributed in the hope that it will be useful,
15 : * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 : * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 : * GNU General Public License for more details.
18 : *
19 : * You should have received a copy of the GNU General Public License
20 : * along with this program. If not, see <https://www.gnu.org/licenses/>.
21 : *
22 : */
23 :
24 : #include "continuouspanel.h"
25 :
26 : #include "containers.h"
27 :
28 : #include "draw/painter.h"
29 :
30 : #include "engraving/dom/barline.h"
31 : #include "engraving/dom/factory.h"
32 : #include "engraving/dom/instrument.h"
33 : #include "engraving/dom/keysig.h"
34 : #include "engraving/dom/measure.h"
35 : #include "engraving/dom/page.h"
36 : #include "engraving/dom/part.h"
37 : #include "engraving/dom/score.h"
38 : #include "engraving/dom/segment.h"
39 : #include "engraving/dom/staff.h"
40 : #include "engraving/dom/stafflines.h"
41 : #include "engraving/dom/system.h"
42 : #include "engraving/dom/text.h"
43 : #include "engraving/dom/timesig.h"
44 : #include "engraving/rendering/score/tlayout.h"
45 :
46 : #include "notation/inotationelements.h" // IWYU pragma: keep
47 :
48 : #include "log.h"
49 :
50 : using namespace muse;
51 : using namespace muse::draw;
52 : using namespace mu::notation;
53 : using namespace mu::engraving::rendering::score;
54 :
55 : static constexpr bool ACCESSIBILITY_DISABLED = false;
56 :
57 0 : ContinuousPanel::~ContinuousPanel()
58 : {
59 0 : clearCache();
60 0 : }
61 :
62 0 : void ContinuousPanel::clearCache()
63 : {
64 0 : delete m_cachedMeasureNumberText;
65 0 : m_cachedMeasureNumberText = nullptr;
66 :
67 0 : muse::DeleteAll(m_cachedStaffNameTexts);
68 0 : m_cachedStaffNameTexts.clear();
69 :
70 0 : muse::DeleteAll(m_cachedClefs);
71 0 : m_cachedClefs.clear();
72 :
73 0 : muse::DeleteAll(m_cachedKeySigs);
74 0 : m_cachedKeySigs.clear();
75 :
76 0 : muse::DeleteAll(m_cachedTimeSigs);
77 0 : m_cachedTimeSigs.clear();
78 :
79 0 : muse::DeleteAll(m_cachedBarLines);
80 0 : m_cachedBarLines.clear();
81 0 : }
82 :
83 0 : void ContinuousPanel::ensureCacheSize(size_t staffCount)
84 : {
85 : // Resize vectors if needed and create objects
86 0 : if (m_cachedStaffNameTexts.size() < staffCount) {
87 0 : m_cachedStaffNameTexts.resize(staffCount, nullptr);
88 : }
89 0 : if (m_cachedClefs.size() < staffCount) {
90 0 : m_cachedClefs.resize(staffCount, nullptr);
91 : }
92 0 : if (m_cachedKeySigs.size() < staffCount) {
93 0 : m_cachedKeySigs.resize(staffCount, nullptr);
94 : }
95 0 : if (m_cachedTimeSigs.size() < staffCount) {
96 0 : m_cachedTimeSigs.resize(staffCount, nullptr);
97 : }
98 0 : if (m_cachedBarLines.size() < staffCount) {
99 0 : m_cachedBarLines.resize(staffCount, nullptr);
100 : }
101 0 : }
102 :
103 0 : void ContinuousPanel::setNotation(INotationPtr notation)
104 : {
105 0 : if (notation == m_notation) {
106 0 : return;
107 : }
108 :
109 0 : clearCache();
110 0 : m_width = 0;
111 0 : m_notation = notation;
112 : }
113 :
114 0 : qreal ContinuousPanel::width() const
115 : {
116 0 : return m_width;
117 : }
118 :
119 0 : void ContinuousPanel::paint(Painter& painter, const NotationViewContext& ctx, const engraving::rendering::PaintOptions& opt)
120 : {
121 0 : TRACEFUNC;
122 :
123 0 : if (!m_notation) {
124 0 : return;
125 : }
126 :
127 0 : const engraving::Score* score = m_notation->elements()->msScore();
128 :
129 0 : double offsetPanel = -ctx.xOffset / ctx.scaling;
130 0 : double y = 0;
131 0 : double oldWidth = 0; // The last final panel width
132 0 : double newWidth = 0; // New panel width
133 0 : double height = 0;
134 0 : double leftMarginTotal = 0; // Sum of all elements left margin
135 0 : double panelRightPadding = 5; // Extra space for the panel after last element
136 :
137 0 : const engraving::Measure* measure = score->firstMeasure();
138 0 : if (!measure) {
139 0 : return;
140 : }
141 :
142 0 : measure = measure->coveringMMRestOrThis();
143 0 : if (!measure) {
144 0 : return;
145 : }
146 :
147 0 : const engraving::System* system = measure->system();
148 0 : if (!system) {
149 0 : return;
150 : }
151 :
152 0 : const double spatium = score->style().spatium();
153 0 : if (m_width <= 0) {
154 0 : m_width = measure->first()->x();
155 : }
156 :
157 : //
158 : // Set panel height for whole system
159 : //
160 0 : height = 6 * spatium;
161 0 : y = system->staffYpage(0) + system->page()->pos().y();
162 0 : double y2 = 0.0;
163 0 : const size_t staffCount = score->nstaves();
164 0 : for (mu::engraving::staff_idx_t i = 0; i < staffCount; ++i) {
165 0 : mu::engraving::SysStaff* ss = system->staff(i);
166 0 : if (!ss->show() || !score->staff(i)->show()) {
167 0 : continue;
168 : }
169 0 : y2 = ss->y() + ss->bbox().height();
170 : }
171 0 : height += y2 + 6 * spatium;
172 0 : y -= 6 * spatium;
173 :
174 : //
175 : // Check elements at current panel position
176 : //
177 :
178 0 : m_rect = RectF(offsetPanel + m_width, y, 1, height);
179 :
180 0 : mu::engraving::Page* page = score->pages().front();
181 0 : std::vector<mu::engraving::EngravingItem*> el = page->items(m_rect);
182 0 : if (el.empty()) {
183 0 : return;
184 : }
185 :
186 0 : std::stable_sort(el.begin(), el.end(), mu::engraving::elementLessThan);
187 :
188 0 : const mu::engraving::Measure* currentMeasure = nullptr;
189 0 : const bool showInvisible = score->isShowInvisible();
190 0 : for (const mu::engraving::EngravingItem* e : el) {
191 0 : e->itemDiscovered = false;
192 0 : if (!e->visible() && !showInvisible) {
193 0 : continue;
194 : }
195 :
196 0 : if (e->isStaffLines()) {
197 0 : currentMeasure = toStaffLines(e)->measure();
198 0 : break;
199 : }
200 : }
201 :
202 0 : if (!currentMeasure) {
203 0 : return;
204 : }
205 :
206 : // Don't show panel if staff names are visible
207 0 : if (!ctx.fromLogical) {
208 0 : return;
209 : }
210 :
211 0 : const double canvasPosX = ctx.fromLogical(currentMeasure->canvasPos()).x();
212 0 : if (currentMeasure == score->firstMeasure() && canvasPosX > 0) {
213 0 : return;
214 : }
215 :
216 0 : double xPosMeasure = currentMeasure->canvasX();
217 0 : const double _measureWidth = currentMeasure->width();
218 0 : const engraving::Fraction tick = currentMeasure->tick();
219 0 : const engraving::Fraction currentTimeSigFraction = currentMeasure->timesig();
220 :
221 : //---------------------------------------------------------
222 : // findElementWidths
223 : // determines the max width for each element types
224 : //---------------------------------------------------------
225 :
226 : // The first pass serves to get the maximum width for each elements
227 :
228 0 : double lineWidthName = 0;
229 0 : double widthClef = 0;
230 0 : double widthKeySig = 0;
231 0 : double widthTimeSig = 0;
232 0 : double xPosTimeSig = 0;
233 :
234 : // Ensure we have a cached segment to use as parent for temporary objects
235 0 : Segment* seg = score->tick2segmentMM(tick);
236 :
237 : // Ensure cache size matches staff count
238 0 : ensureCacheSize(staffCount);
239 :
240 0 : if (!m_cachedMeasureNumberText) {
241 0 : m_cachedMeasureNumberText = engraving::Factory::createText(seg, mu::engraving::TextStyleType::DEFAULT, ACCESSIBILITY_DISABLED);
242 0 : m_cachedMeasureNumberText->setFlag(engraving::ElementFlag::MOVABLE, false);
243 0 : m_cachedMeasureNumberText->setFamily(u"FreeSans");
244 0 : m_cachedMeasureNumberText->setSizeIsSpatiumDependent(true);
245 : }
246 :
247 0 : for (const engraving::EngravingItem* e : std::as_const(el)) {
248 0 : e->itemDiscovered = false;
249 0 : if (!e->visible() && !showInvisible) {
250 0 : continue;
251 : }
252 :
253 0 : if (e->isStaffLines()) {
254 0 : const engraving::staff_idx_t staffIdx = e->staffIdx();
255 0 : const engraving::Staff* currentStaff = score->staff(staffIdx);
256 0 : const engraving::Instrument* instrument = currentStaff->part()->instrument(tick);
257 :
258 : // Staff name
259 0 : String staffName = instrument->longName();
260 0 : if (staffName.empty()) {
261 0 : staffName = instrument->shortName();
262 : }
263 :
264 0 : engraving::Text*& nameText = m_cachedStaffNameTexts[staffIdx];
265 0 : if (!nameText) {
266 0 : nameText = engraving::Factory::createText(seg, mu::engraving::TextStyleType::DEFAULT, ACCESSIBILITY_DISABLED);
267 0 : nameText->setFlag(engraving::ElementFlag::MOVABLE, false);
268 0 : nameText->setFamily(u"FreeSans");
269 0 : nameText->setSizeIsSpatiumDependent(true);
270 : }
271 :
272 0 : nameText->setOwnershipParent(seg);
273 0 : nameText->setXmlText(staffName);
274 0 : nameText->setTrack(e->track());
275 0 : nameText->mutldata()->reset();
276 0 : scoreRender()->layoutItem(nameText);
277 0 : nameText->setPlainText(nameText->plainText());
278 0 : nameText->mutldata()->reset();
279 0 : scoreRender()->layoutItem(nameText);
280 :
281 0 : if (nameText->width() > lineWidthName && !nameText->xmlText().empty()) {
282 0 : lineWidthName = nameText->width();
283 : }
284 :
285 : // Clef
286 0 : engraving::Clef*& clef = m_cachedClefs[staffIdx];
287 0 : if (!clef) {
288 0 : clef = engraving::Factory::createClef(seg, ACCESSIBILITY_DISABLED);
289 0 : clef->setTrack(e->track());
290 : }
291 :
292 0 : clef->setOwnershipParent(seg);
293 0 : engraving::ClefType currentClef = currentStaff->clef(tick);
294 0 : clef->setClefType(currentClef);
295 0 : clef->mutldata()->reset();
296 0 : scoreRender()->layoutItem(clef);
297 0 : if (clef->width() > widthClef) {
298 0 : widthClef = clef->width();
299 : }
300 :
301 : // Key Signature
302 0 : engraving::KeySig*& keySig = m_cachedKeySigs[staffIdx];
303 0 : if (!keySig) {
304 0 : keySig = engraving::Factory::createKeySig(seg, ACCESSIBILITY_DISABLED);
305 0 : keySig->setHideNaturals(true);
306 0 : keySig->setTrack(e->track());
307 : }
308 :
309 0 : keySig->setOwnershipParent(seg);
310 0 : engraving::KeySigEvent currentKeySigEvent = currentStaff->keySigEvent(tick);
311 0 : keySig->setKeySigEvent(currentKeySigEvent);
312 0 : keySig->mutldata()->reset();
313 0 : scoreRender()->layoutItem(keySig);
314 0 : if (keySig->width() > widthKeySig) {
315 0 : widthKeySig = keySig->width();
316 : }
317 :
318 : // Time Signature
319 0 : engraving::TimeSig*& timeSig = m_cachedTimeSigs[staffIdx];
320 0 : if (!timeSig) {
321 0 : timeSig = engraving::Factory::createTimeSig(seg, ACCESSIBILITY_DISABLED);
322 0 : timeSig->setTrack(e->track());
323 : }
324 0 : timeSig->setOwnershipParent(seg);
325 :
326 : // Try to get local time signature, if not, get the current measure one
327 0 : engraving::TimeSig* currentTimeSig = currentStaff->timeSig(tick);
328 0 : if (currentTimeSig) {
329 0 : timeSig->setFrom(currentTimeSig);
330 : } else {
331 0 : timeSig->setSig(currentTimeSigFraction, TimeSigType::NORMAL);
332 : }
333 0 : timeSig->mutldata()->reset();
334 0 : scoreRender()->layoutItem(timeSig);
335 :
336 0 : if (timeSig->width() > widthTimeSig) {
337 0 : widthTimeSig = timeSig->width();
338 : }
339 :
340 : // Bar line - layout and store
341 0 : engraving::BarLine*& barLine = m_cachedBarLines[staffIdx];
342 0 : if (!barLine) {
343 0 : barLine = engraving::Factory::createBarLine(seg, ACCESSIBILITY_DISABLED);
344 0 : barLine->setBarLineType(engraving::BarLineType::NORMAL);
345 0 : barLine->setTrack(e->track());
346 : }
347 :
348 0 : barLine->setOwnershipParent(seg);
349 0 : barLine->setSpanStaff(currentStaff->barLineSpan());
350 0 : barLine->setSpanFrom(currentStaff->barLineFrom());
351 0 : barLine->setSpanTo(currentStaff->barLineTo());
352 0 : barLine->mutldata()->reset();
353 0 : scoreRender()->layoutItem(barLine);
354 0 : }
355 : }
356 :
357 0 : const double clefLeftMargin = score->style().styleAbsolute(engraving::Sid::clefLeftMargin);
358 0 : const double keySigLeftMargin = score->style().styleAbsolute(engraving::Sid::keysigLeftMargin);
359 0 : const double timeSigLeftMargin = score->style().styleAbsolute(engraving::Sid::timesigLeftMargin);
360 :
361 0 : leftMarginTotal = clefLeftMargin;
362 0 : leftMarginTotal += keySigLeftMargin;
363 0 : leftMarginTotal += timeSigLeftMargin;
364 :
365 0 : newWidth = widthClef + widthKeySig + widthTimeSig + leftMarginTotal + panelRightPadding;
366 0 : xPosMeasure -= offsetPanel;
367 :
368 0 : lineWidthName += spatium + clefLeftMargin + widthClef;
369 0 : if (newWidth < lineWidthName) {
370 0 : newWidth = lineWidthName;
371 0 : oldWidth = 0;
372 : }
373 0 : if (oldWidth == 0) {
374 0 : oldWidth = newWidth;
375 0 : m_width = newWidth;
376 0 : } else if (newWidth > 0) {
377 0 : if (newWidth == m_width) {
378 0 : oldWidth = m_width;
379 0 : m_width = newWidth;
380 0 : } else if (((xPosMeasure <= newWidth) && (xPosMeasure >= oldWidth))
381 0 : || ((xPosMeasure >= newWidth) && (xPosMeasure <= oldWidth))) {
382 0 : m_width = xPosMeasure;
383 0 : } else if (((xPosMeasure + _measureWidth <= newWidth) && (xPosMeasure + _measureWidth >= oldWidth))
384 0 : || ((xPosMeasure + _measureWidth >= newWidth) && (xPosMeasure + _measureWidth <= oldWidth))) {
385 0 : m_width = xPosMeasure + _measureWidth;
386 : } else {
387 0 : oldWidth = m_width;
388 0 : m_width = newWidth;
389 : }
390 : }
391 :
392 0 : m_rect = RectF(0, y, m_width, height);
393 :
394 : //====================
395 :
396 0 : painter.save();
397 :
398 : // Draw background rectangle
399 0 : PointF pos(offsetPanel, 0);
400 :
401 0 : painter.translate(pos);
402 0 : Pen pen;
403 0 : pen.setWidthF(0.0);
404 0 : pen.setStyle(PenStyle::NoPen);
405 0 : painter.setPen(pen);
406 0 : painter.setBrush(notationConfiguration()->foregroundColor());
407 :
408 0 : RectF bg(m_rect);
409 0 : bg.setWidth(widthClef + widthKeySig + widthTimeSig + leftMarginTotal + panelRightPadding);
410 :
411 0 : const QPixmap& wallpaper = notationConfiguration()->foregroundWallpaper();
412 :
413 0 : if (notationConfiguration()->foregroundUseColor() || wallpaper.isNull()) {
414 0 : painter.fillRect(bg, notationConfiguration()->foregroundColor());
415 : } else {
416 0 : painter.drawTiledPixmap(bg, wallpaper, bg.topLeft() - PointF(ctx.xOffset, ctx.yOffset));
417 : }
418 :
419 0 : const Color color = engravingConfiguration()->invisibleColor();
420 :
421 : // Draw measure number
422 0 : m_cachedMeasureNumberText->setXmlText(String(u"#%1").arg(currentMeasure->measureNumber() + 1));
423 0 : m_cachedMeasureNumberText->setColor(color);
424 0 : m_cachedMeasureNumberText->mutldata()->reset();
425 0 : m_cachedMeasureNumberText->renderer()->layoutText1(m_cachedMeasureNumberText);
426 0 : pos = PointF(clefLeftMargin + widthClef, y + m_cachedMeasureNumberText->height());
427 0 : painter.translate(pos);
428 0 : m_cachedMeasureNumberText->renderer()->drawItem(m_cachedMeasureNumberText, &painter, opt);
429 :
430 0 : pos += PointF(offsetPanel, 0);
431 0 : painter.translate(-pos);
432 :
433 : // This second pass draws the elements using already laid-out cached objects
434 0 : for (const engraving::EngravingItem* e : std::as_const(el)) {
435 0 : if (!e->visible() && !showInvisible) {
436 0 : continue;
437 : }
438 :
439 0 : if (e->isStaffLines()) {
440 0 : painter.save();
441 0 : const engraving::staff_idx_t staffIdx = e->staffIdx();
442 0 : const engraving::Staff* currentStaff = score->staff(staffIdx);
443 :
444 0 : pos = PointF(offsetPanel, e->pagePos().y());
445 0 : painter.translate(pos);
446 :
447 : // Staff lines
448 0 : engraving::StaffLines newStaffLines(*toStaffLines(e));
449 0 : newStaffLines.setOwnershipParent(seg->measure());
450 0 : newStaffLines.setTrack(e->track());
451 : {
452 0 : LayoutContext cntx(newStaffLines.score());
453 0 : TLayout::layoutForWidth(&newStaffLines, bg.width(), cntx);
454 0 : }
455 0 : newStaffLines.setColor(color);
456 0 : scoreRender()->drawItem(&newStaffLines, &painter, opt);
457 :
458 : // Barline
459 0 : engraving::BarLine*& barLine = m_cachedBarLines[staffIdx];
460 0 : barLine->setColor(color);
461 0 : barLine->renderer()->drawItem(barLine, &painter, opt);
462 :
463 : // Staff name
464 0 : if (currentStaff->part()->staff(0) == currentStaff) {
465 0 : pos = PointF(clefLeftMargin + widthClef, -spatium * 2);
466 0 : painter.translate(pos);
467 0 : engraving::Text*& staffNameText = m_cachedStaffNameTexts[staffIdx];
468 0 : staffNameText->setColor(color);
469 0 : scoreRender()->drawItem(staffNameText, &painter, opt);
470 :
471 0 : painter.translate(-pos);
472 : }
473 :
474 0 : double posX = 0.0;
475 :
476 : // Clef
477 0 : engraving::Clef*& clef = m_cachedClefs[staffIdx];
478 0 : clef->setColor(color);
479 0 : posX += clefLeftMargin;
480 0 : const PointF clefPos = PointF(posX, clef->pos().y());
481 0 : painter.translate(clefPos);
482 0 : scoreRender()->drawItem(clef, &painter, opt);
483 0 : painter.translate(-clefPos);
484 0 : posX += widthClef;
485 :
486 : // Key signature
487 0 : engraving::KeySig*& keySig = m_cachedKeySigs[staffIdx];
488 0 : keySig->setColor(color);
489 0 : posX += keySigLeftMargin;
490 0 : const PointF ksPos = PointF(posX, 0.0);
491 0 : painter.translate(ksPos);
492 0 : scoreRender()->drawItem(keySig, &painter, opt);
493 0 : painter.translate(-ksPos);
494 :
495 0 : posX += widthKeySig + xPosTimeSig;
496 :
497 : // Time signature
498 0 : engraving::TimeSig*& timeSig = m_cachedTimeSigs[staffIdx];
499 0 : timeSig->setColor(color);
500 0 : posX += timeSigLeftMargin;
501 0 : const PointF tsPos = PointF(posX, 0.0);
502 0 : painter.translate(tsPos);
503 0 : scoreRender()->drawItem(timeSig, &painter, opt);
504 0 : painter.translate(-tsPos);
505 :
506 0 : painter.restore();
507 0 : }
508 : }
509 :
510 0 : painter.restore();
511 0 : }
|