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 += "&amp;";
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