1 //=============================================================================
2 // MuseScore
3 // Music Composition & Notation
4 //
5 // Copyright (C) 2002-2011 Werner Schweer
6 //
7 // This program is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License version 2
9 // as published by the Free Software Foundation and appearing in
10 // the file LICENCE.GPL
11 //=============================================================================
12
13 #include "page.h"
14 #include "score.h"
15 #include "text.h"
16 #include "xml.h"
17 #include "measure.h"
18 #include "style.h"
19 #include "chord.h"
20 #include "beam.h"
21 #include "tuplet.h"
22 #include "note.h"
23 #include "barline.h"
24 #include "slur.h"
25 #include "hook.h"
26 #include "bracket.h"
27 #include "line.h"
28 #include "staff.h"
29 #include "system.h"
30 #include "mscore.h"
31 #include "segment.h"
32
33 namespace Ms {
34
35 extern QString revision;
36
37 //---------------------------------------------------------
38 // Page
39 //---------------------------------------------------------
40
Page(Score * s)41 Page::Page(Score* s)
42 : Element(s, ElementFlag::NOT_SELECTABLE), _no(0)
43 {
44 bspTreeValid = false;
45 }
46
~Page()47 Page::~Page()
48 {
49 }
50
51 //---------------------------------------------------------
52 // items
53 //---------------------------------------------------------
54
items(const QRectF & r)55 QList<Element*> Page::items(const QRectF& r)
56 {
57 #ifdef USE_BSP
58 if (!bspTreeValid)
59 doRebuildBspTree();
60 QList<Element*> el = bspTree.items(r);
61 return el;
62 #else
63 Q_UNUSED(r)
64 return QList<Element*>();
65 #endif
66 }
67
items(const QPointF & p)68 QList<Element*> Page::items(const QPointF& p)
69 {
70 #ifdef USE_BSP
71 if (!bspTreeValid)
72 doRebuildBspTree();
73 return bspTree.items(p);
74 #else
75 Q_UNUSED(p)
76 return QList<Element*>();
77 #endif
78 }
79
80 //---------------------------------------------------------
81 // appendSystem
82 //---------------------------------------------------------
83
appendSystem(System * s)84 void Page::appendSystem(System* s)
85 {
86 s->setParent(this);
87 _systems.push_back(s);
88 }
89
90 //---------------------------------------------------------
91 // draw
92 // bounding rectangle fr is relative to page QPointF
93 //---------------------------------------------------------
94
draw(QPainter * painter) const95 void Page::draw(QPainter* painter) const
96 {
97 if (score()->layoutMode() != LayoutMode::PAGE)
98 return;
99 //
100 // draw header/footer
101 //
102
103 int n = no() + 1 + score()->pageNumberOffset();
104 painter->setPen(curColor());
105
106 QString s1, s2, s3;
107
108 if (score()->styleB(Sid::showHeader) && (no() || score()->styleB(Sid::headerFirstPage))) {
109 bool odd = (n & 1) || !score()->styleB(Sid::headerOddEven);
110 if (odd) {
111 s1 = score()->styleSt(Sid::oddHeaderL);
112 s2 = score()->styleSt(Sid::oddHeaderC);
113 s3 = score()->styleSt(Sid::oddHeaderR);
114 }
115 else {
116 s1 = score()->styleSt(Sid::evenHeaderL);
117 s2 = score()->styleSt(Sid::evenHeaderC);
118 s3 = score()->styleSt(Sid::evenHeaderR);
119 }
120
121 drawHeaderFooter(painter, 0, s1);
122 drawHeaderFooter(painter, 1, s2);
123 drawHeaderFooter(painter, 2, s3);
124 }
125
126 if (score()->styleB(Sid::showFooter) && (no() || score()->styleB(Sid::footerFirstPage))) {
127 bool odd = (n & 1) || !score()->styleB(Sid::footerOddEven);
128 if (odd) {
129 s1 = score()->styleSt(Sid::oddFooterL);
130 s2 = score()->styleSt(Sid::oddFooterC);
131 s3 = score()->styleSt(Sid::oddFooterR);
132 }
133 else {
134 s1 = score()->styleSt(Sid::evenFooterL);
135 s2 = score()->styleSt(Sid::evenFooterC);
136 s3 = score()->styleSt(Sid::evenFooterR);
137 }
138
139 drawHeaderFooter(painter, 3, s1);
140 drawHeaderFooter(painter, 4, s2);
141 drawHeaderFooter(painter, 5, s3);
142 }
143 }
144
145 //---------------------------------------------------------
146 // drawHeaderFooter
147 //---------------------------------------------------------
148
drawHeaderFooter(QPainter * p,int area,const QString & ss) const149 void Page::drawHeaderFooter(QPainter* p, int area, const QString& ss) const
150 {
151 QString s = replaceTextMacros(ss);
152 if (s.isEmpty())
153 return;
154
155 Text* text;
156 if (area < MAX_HEADERS) {
157 text = score()->headerText(area);
158 if (!text) {
159 text = new Text(score(), Tid::HEADER);
160 text->setFlag(ElementFlag::MOVABLE, false);
161 text->setFlag(ElementFlag::GENERATED, true); // set to disable editing
162 text->setLayoutToParentWidth(true);
163 score()->setHeaderText(text, area);
164 }
165 }
166 else {
167 text = score()->footerText(area - MAX_HEADERS); // because they are 3 4 5
168 if (!text) {
169 text = new Text(score(), Tid::FOOTER);
170 text->setFlag(ElementFlag::MOVABLE, false);
171 text->setFlag(ElementFlag::GENERATED, true); // set to disable editing
172 text->setLayoutToParentWidth(true);
173 score()->setFooterText(text, area - MAX_HEADERS);
174 }
175 }
176 text->setParent((Page*)this);
177 Align flags = Align::LEFT;
178 switch (area) {
179 case 0: flags = Align::LEFT | Align::TOP; break;
180 case 1: flags = Align::HCENTER | Align::TOP; break;
181 case 2: flags = Align::RIGHT | Align::TOP; break;
182 case 3: flags = Align::LEFT | Align::BOTTOM; break;
183 case 4: flags = Align::HCENTER | Align::BOTTOM; break;
184 case 5: flags = Align::RIGHT | Align::BOTTOM; break;
185 }
186 text->setAlign(flags);
187 text->setXmlText(s);
188 text->layout();
189 p->translate(text->pos());
190 text->draw(p);
191 p->translate(-text->pos());
192 text->setParent(0);
193 }
194
195 #if 0
196 //---------------------------------------------------------
197 // styleChanged
198 //---------------------------------------------------------
199
200 void Page::styleChanged()
201 {
202 Text* t = score()->headerText();
203 if (t)
204 t->styleChanged();
205 t = score()->footerText();
206 if (t)
207 t->styleChanged();
208 }
209 #endif
210
211 //---------------------------------------------------------
212 // scanElements
213 //---------------------------------------------------------
214
scanElements(void * data,void (* func)(void *,Element *),bool all)215 void Page::scanElements(void* data, void (*func)(void*, Element*), bool all)
216 {
217 for (System* s :qAsConst(_systems)) {
218 for (MeasureBase* m : s->measures())
219 m->scanElements(data, func, all);
220 s->scanElements(data, func, all);
221 }
222 func(data, this);
223 }
224
225 #ifdef USE_BSP
226 //---------------------------------------------------------
227 // bspInsert
228 //---------------------------------------------------------
229
bspInsert(void * bspTree,Element * e)230 static void bspInsert(void* bspTree, Element* e)
231 {
232 ((BspTree*) bspTree)->insert(e);
233 }
234
countElements(void * data,Element *)235 static void countElements(void* data, Element* /*e*/)
236 {
237 ++(*(int*)data);
238 }
239
240 //---------------------------------------------------------
241 // doRebuildBspTree
242 //---------------------------------------------------------
243
doRebuildBspTree()244 void Page::doRebuildBspTree()
245 {
246 int n = 0;
247 scanElements(&n, countElements, false);
248
249 QRectF r;
250 if (score()->layoutMode() == LayoutMode::LINE) {
251 qreal w = 0.0;
252 qreal h = 0.0;
253 if (!_systems.empty()) {
254 h = _systems.front()->height();
255 if (!_systems.front()->measures().empty()) {
256 MeasureBase* mb = _systems.front()->measures().back();
257 w = mb->x() + mb->width();
258 }
259 }
260 r = QRectF(0.0, 0.0, w, h);
261 }
262 else
263 r = abbox();
264
265 bspTree.initialize(r, n);
266 scanElements(&bspTree, &bspInsert, false);
267 bspTreeValid = true;
268 }
269 #endif
270
271 //---------------------------------------------------------
272 // replaceTextMacros
273 // (keep in sync with toolTipHeaderFooter in EditStyle::EditStyle())
274 // $p - page number, except on first page
275 // $P - page number, on all pages
276 // $N - page number, if there is more than one
277 // $n - number of pages
278 // $i - part name, except on first page
279 // $I - part name, on all pages
280 // $f - file name
281 // $F - file path+name
282 // $d - current date
283 // $D - creation date
284 // $m - last modification time
285 // $M - last modification date
286 // $C - copyright, on first page only
287 // $c - copyright, on all pages
288 // $v - MuseScore version the score was last saved with
289 // $r - MuseScore revision the score was last saved with
290 // $$ - the $ sign itself
291 // $:tag: - any metadata tag
292 //
293 // tags always defined (see Score::init())):
294 // copyright
295 // creationDate
296 // movementNumber
297 // movementTitle
298 // platform
299 // source
300 // workNumber
301 // workTitle
302 //---------------------------------------------------------
303
replaceTextMacros(const QString & s) const304 QString Page::replaceTextMacros(const QString& s) const
305 {
306 QString d;
307 for (int i = 0, n = s.size(); i < n; ++i) {
308 QChar c = s[i];
309 if (c == '$' && (i < (n-1))) {
310 QChar nc = s[i+1];
311 switch(nc.toLatin1()) {
312 case 'p': // not on page 1
313 if (_no) // FALLTHROUGH
314 case 'N': // on page 1 only if there are multiple pages
315 if ((score()->npages() + score()->pageNumberOffset()) > 1) // FALLTHROUGH
316 case 'P': // on all pages
317 {
318 int no = _no + 1 + score()->pageNumberOffset();
319 if (no > 0 ) // except page numbers < 1
320 d += QString("%1").arg(no);
321 }
322 break;
323 case 'n':
324 d += QString("%1").arg(score()->npages() + score()->pageNumberOffset());
325 break;
326 case 'i': // not on page 1
327 if (_no) // FALLTHROUGH
328 case 'I':
329 d += score()->metaTag("partName").toHtmlEscaped();
330 break;
331 case 'f':
332 d += masterScore()->fileInfo()->completeBaseName().toHtmlEscaped();
333 break;
334 case 'F':
335 d += masterScore()->fileInfo()->absoluteFilePath().toHtmlEscaped();
336 break;
337 case 'd':
338 d += QDate::currentDate().toString(Qt::DefaultLocaleShortDate);
339 break;
340 case 'D':
341 {
342 QString creationDate = score()->metaTag("creationDate");
343 if (creationDate.isNull())
344 d += masterScore()->fileInfo()->created().date().toString(Qt::DefaultLocaleShortDate);
345 else
346 d += QDate::fromString(creationDate, Qt::ISODate).toString(Qt::DefaultLocaleShortDate);
347 }
348 break;
349 case 'm':
350 if (score()->dirty())
351 d += QTime::currentTime().toString(Qt::DefaultLocaleShortDate);
352 else
353 d += masterScore()->fileInfo()->lastModified().time().toString(Qt::DefaultLocaleShortDate);
354 break;
355 case 'M':
356 if (score()->dirty())
357 d += QDate::currentDate().toString(Qt::DefaultLocaleShortDate);
358 else
359 d += masterScore()->fileInfo()->lastModified().date().toString(Qt::DefaultLocaleShortDate);
360 break;
361 case 'C': // only on page 1
362 if (!_no) // FALLTHROUGH
363 case 'c':
364 d += score()->metaTag("copyright").toHtmlEscaped();
365 break;
366 case 'v':
367 if (score()->dirty())
368 d += QString(VERSION);
369 else
370 d += score()->mscoreVersion();
371 break;
372 case 'r':
373 if (score()->dirty()) {
374 d += revision;
375 }
376 else {
377 int rev = score()->mscoreRevision();
378 if (rev > 99999) // MuseScore 1.3 is decimal 5702, 2.0 and later uses a 7-digit hex SHA
379 d += QString::number(rev, 16);
380 else
381 d += QString::number(rev, 10);
382 }
383 break;
384 case '$':
385 d += '$';
386 break;
387 case ':':
388 {
389 QString tag;
390 int k = i+2;
391 for (; k < n; ++k) {
392 if (s[k].toLatin1() == ':')
393 break;
394 tag += s[k];
395 }
396 if (k != n) { // found ':' ?
397 d += score()->metaTag(tag).toHtmlEscaped();
398 i = k-1;
399 }
400 }
401 break;
402 default:
403 d += '$';
404 d += nc;
405 break;
406 }
407 ++i;
408 }
409 else if (c == '&')
410 d += "&";
411 else
412 d += c;
413 }
414 return d;
415 }
416
417 //---------------------------------------------------------
418 // isOdd
419 //---------------------------------------------------------
420
isOdd() const421 bool Page::isOdd() const
422 {
423 return (_no + 1 + score()->pageNumberOffset()) & 1;
424 }
425
426 //---------------------------------------------------------
427 // write
428 //---------------------------------------------------------
429
write(XmlWriter & xml) const430 void Page::write(XmlWriter& xml) const
431 {
432 xml.stag(this);
433 foreach(System* system, _systems) {
434 system->write(xml);
435 }
436 xml.etag();
437 }
438
439 //---------------------------------------------------------
440 // read
441 //---------------------------------------------------------
442
read(XmlReader & e)443 void Page::read(XmlReader& e)
444 {
445 while (e.readNextStartElement()) {
446 if (e.name() == "System") {
447 System* system = new System(score());
448 score()->systems().push_back(system);
449 system->read(e);
450 }
451 else
452 e.unknown();
453 }
454 }
455
456 //---------------------------------------------------------
457 // elements
458 //---------------------------------------------------------
459
elements()460 QList<Element*> Page::elements()
461 {
462 QList<Element*> el;
463 scanElements(&el, collectElements, false);
464 return el;
465 }
466
467 //---------------------------------------------------------
468 // tm
469 //---------------------------------------------------------
470
tm() const471 qreal Page::tm() const
472 {
473 return ((!score()->styleB(Sid::pageTwosided) || isOdd())
474 ? score()->styleD(Sid::pageOddTopMargin) : score()->styleD(Sid::pageEvenTopMargin)) * DPI;
475 }
476
477 //---------------------------------------------------------
478 // bm
479 //---------------------------------------------------------
480
bm() const481 qreal Page::bm() const
482 {
483 return ((!score()->styleB(Sid::pageTwosided) || isOdd())
484 ? score()->styleD(Sid::pageOddBottomMargin) : score()->styleD(Sid::pageEvenBottomMargin)) * DPI;
485 }
486
487 //---------------------------------------------------------
488 // lm
489 //---------------------------------------------------------
490
lm() const491 qreal Page::lm() const
492 {
493 return ((!score()->styleB(Sid::pageTwosided) || isOdd())
494 ? score()->styleD(Sid::pageOddLeftMargin) : score()->styleD(Sid::pageEvenLeftMargin)) * DPI;
495 }
496
497 //---------------------------------------------------------
498 // rm
499 //---------------------------------------------------------
500
rm() const501 qreal Page::rm() const
502 {
503 return (score()->styleD(Sid::pageWidth) - score()->styleD(Sid::pagePrintableWidth)) * DPI - lm();
504 }
505
506 //---------------------------------------------------------
507 // tbbox
508 // calculates and returns smallest rectangle containing all (visible) page elements
509 //---------------------------------------------------------
510
tbbox()511 QRectF Page::tbbox()
512 {
513 qreal x1 = width();
514 qreal x2 = 0.0;
515 qreal y1 = height();
516 qreal y2 = 0.0;
517 const QList<Element*> el = elements();
518 for (Element* e : el) {
519 if (e == this || !e->isPrintable())
520 continue;
521 QRectF ebbox = e->pageBoundingRect();
522 if (ebbox.left() < x1)
523 x1 = ebbox.left();
524 if (ebbox.right() > x2)
525 x2 = ebbox.right();
526 if (ebbox.top() < y1)
527 y1 = ebbox.top();
528 if (ebbox.bottom() > y2)
529 y2 = ebbox.bottom();
530 }
531 if (x1 < x2 && y1 < y2)
532 return QRectF(x1, y1, x2 - x1, y2 - y1);
533 else
534 return abbox();
535 }
536
537 //---------------------------------------------------------
538 // endTick
539 //---------------------------------------------------------
540
endTick() const541 Fraction Page::endTick() const
542 {
543 return _systems.empty() ? Fraction(-1,1) : _systems.back()->measures().back()->endTick();
544 }
545 }
546
547