1 //=============================================================================
2 //  MuseScore
3 //  Music Composition & Notation
4 //
5 //  Copyright (C) 2002-2016 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 "xml.h"
14 #include "measure.h"
15 #include "score.h"
16 #include "spanner.h"
17 #include "staff.h"
18 #include "beam.h"
19 #include "tuplet.h"
20 
21 namespace Ms {
22 
23 //---------------------------------------------------------
24 //   ~XmlReader
25 //---------------------------------------------------------
26 
~XmlReader()27 XmlReader::~XmlReader()
28       {
29       if (!_connectors.empty() || !_pendingConnectors.empty()) {
30             qDebug("XmlReader::~XmlReader: there are unpaired connectors left");
31             for (auto& c : _connectors) {
32                   Element* conn = c->releaseConnector();
33                   if (conn && !conn->isTuplet()) // tuplets are added to score even when not finished
34                         delete conn;
35                   }
36             for (auto& c : _pendingConnectors)
37                   delete c->releaseConnector();
38             }
39       }
40 
41 //---------------------------------------------------------
42 //   intAttribute
43 //---------------------------------------------------------
44 
intAttribute(const char * s,int _default) const45 int XmlReader::intAttribute(const char* s, int _default) const
46       {
47       if (attributes().hasAttribute(s))
48             // return attributes().value(s).toString().toInt();
49             return attributes().value(s).toInt();
50       else
51             return _default;
52       }
53 
intAttribute(const char * s) const54 int XmlReader::intAttribute(const char* s) const
55       {
56       return attributes().value(s).toInt();
57       }
58 
59 //---------------------------------------------------------
60 //   doubleAttribute
61 //---------------------------------------------------------
62 
doubleAttribute(const char * s) const63 double XmlReader::doubleAttribute(const char* s) const
64       {
65       return attributes().value(s).toDouble();
66       }
67 
doubleAttribute(const char * s,double _default) const68 double XmlReader::doubleAttribute(const char* s, double _default) const
69       {
70       if (attributes().hasAttribute(s))
71             return attributes().value(s).toDouble();
72       else
73             return _default;
74       }
75 
76 //---------------------------------------------------------
77 //   attribute
78 //---------------------------------------------------------
79 
attribute(const char * s,const QString & _default) const80 QString XmlReader::attribute(const char* s, const QString& _default) const
81       {
82       if (attributes().hasAttribute(s))
83             return attributes().value(s).toString();
84       else
85             return _default;
86       }
87 
88 //---------------------------------------------------------
89 //   hasAttribute
90 //---------------------------------------------------------
91 
hasAttribute(const char * s) const92 bool XmlReader::hasAttribute(const char* s) const
93       {
94       return attributes().hasAttribute(s);
95       }
96 
97 //---------------------------------------------------------
98 //   readPoint
99 //---------------------------------------------------------
100 
readPoint()101 QPointF XmlReader::readPoint()
102       {
103       Q_ASSERT(tokenType() == QXmlStreamReader::StartElement);
104 #ifndef NDEBUG
105       if (!attributes().hasAttribute("x")) {
106             QXmlStreamAttributes map = attributes();
107             qDebug("XmlReader::readPoint: x attribute missing: %s (%d)",
108                name().toUtf8().data(), map.size());
109             for (int i = 0; i < map.size(); ++i) {
110                   const QXmlStreamAttribute& a = map.at(i);
111                   qDebug(" attr <%s> <%s>", a.name().toUtf8().data(), a.value().toUtf8().data());
112                   }
113             unknown();
114             }
115       if (!attributes().hasAttribute("y")) {
116             qDebug("XmlReader::readPoint: y attribute missing: %s", name().toUtf8().data());
117             unknown();
118             }
119 #endif
120       qreal x = doubleAttribute("x", 0.0);
121       qreal y = doubleAttribute("y", 0.0);
122       readNext();
123       return QPointF(x, y);
124       }
125 
126 //---------------------------------------------------------
127 //   readColor
128 //---------------------------------------------------------
129 
readColor()130 QColor XmlReader::readColor()
131       {
132       Q_ASSERT(tokenType() == QXmlStreamReader::StartElement);
133       QColor c;
134       c.setRed(intAttribute("r"));
135       c.setGreen(intAttribute("g"));
136       c.setBlue(intAttribute("b"));
137       c.setAlpha(intAttribute("a", 255));
138       skipCurrentElement();
139       return c;
140       }
141 
142 //---------------------------------------------------------
143 //   readSize
144 //---------------------------------------------------------
145 
readSize()146 QSizeF XmlReader::readSize()
147       {
148       Q_ASSERT(tokenType() == QXmlStreamReader::StartElement);
149       QSizeF p;
150       p.setWidth(doubleAttribute("w", 0.0));
151       p.setHeight(doubleAttribute("h", 0.0));
152       skipCurrentElement();
153       return p;
154       }
155 
156 //---------------------------------------------------------
157 //   readRect
158 //---------------------------------------------------------
159 
readRect()160 QRectF XmlReader::readRect()
161       {
162       Q_ASSERT(tokenType() == QXmlStreamReader::StartElement);
163       QRectF p;
164       p.setX(doubleAttribute("x", 0.0));
165       p.setY(doubleAttribute("y", 0.0));
166       p.setWidth(doubleAttribute("w", 0.0));
167       p.setHeight(doubleAttribute("h", 0.0));
168       skipCurrentElement();
169       return p;
170       }
171 
172 //---------------------------------------------------------
173 //   readFraction
174 //    recognizes this two styles:
175 //    <move z="2" n="4"/>     (old style)
176 //    <move>2/4</move>        (new style)
177 //---------------------------------------------------------
178 
readFraction()179 Fraction XmlReader::readFraction()
180       {
181       Q_ASSERT(tokenType() == QXmlStreamReader::StartElement);
182       int z = attribute("z", "0").toInt();
183       int n = attribute("n", "1").toInt();
184       const QString& s(readElementText());
185       if (!s.isEmpty()) {
186             int i = s.indexOf('/');
187             if (i == -1) {
188                   return Fraction::fromTicks(s.toInt());
189                   }
190             else {
191                   z = s.leftRef(i).toInt();
192                   n = s.midRef(i+1).toInt();
193                   }
194             }
195       return Fraction(z, n);
196       }
197 
198 //---------------------------------------------------------
199 //   unknown
200 //    unknown tag read
201 //---------------------------------------------------------
202 
unknown()203 void XmlReader::unknown()
204       {
205       if (QXmlStreamReader::error())
206             qDebug("%s ", qPrintable(errorString()));
207       if (!docName.isEmpty())
208             qDebug("tag in <%s> line %lld col %lld: %s",
209                qPrintable(docName), lineNumber() + _offsetLines, columnNumber(), name().toUtf8().data());
210       else
211             qDebug("line %lld col %lld: %s", lineNumber() + _offsetLines, columnNumber(), name().toUtf8().data());
212       skipCurrentElement();
213       }
214 
215 //---------------------------------------------------------
216 //   location
217 //---------------------------------------------------------
218 
location(bool forceAbsFrac) const219 Location XmlReader::location(bool forceAbsFrac) const
220       {
221       Location l = Location::absolute();
222       fillLocation(l, forceAbsFrac);
223       return l;
224       }
225 
226 //---------------------------------------------------------
227 //   fillLocation
228 //    fills location fields which have default values with
229 //    values relevant for the current reader's position.
230 //    When in paste mode (or forceAbsFrac is true) absolute
231 //    fraction values are used and measure number is set to
232 //    zero.
233 //---------------------------------------------------------
234 
fillLocation(Location & l,bool forceAbsFrac) const235 void XmlReader::fillLocation(Location& l, bool forceAbsFrac) const
236       {
237       constexpr Location defaults = Location::absolute();
238       const bool absFrac = (pasteMode() || forceAbsFrac);
239       if (l.track() == defaults.track())
240             l.setTrack(track());
241       if (l.frac() == defaults.frac())
242             l.setFrac(absFrac ? tick() : rtick());
243       if (l.measure() == defaults.measure())
244             l.setMeasure(absFrac ? 0 : currentMeasureIndex());
245       }
246 
247 //---------------------------------------------------------
248 //   setLocation
249 //    sets a new reading location, taking into account its
250 //    type (absolute or relative).
251 //---------------------------------------------------------
252 
setLocation(const Location & l)253 void XmlReader::setLocation(const Location& l)
254       {
255       if (l.isRelative()) {
256             Location newLoc = l;
257             newLoc.toAbsolute(location());
258             int intTicks = l.frac().ticks();
259             if (_tick == Fraction::fromTicks(_intTick + intTicks)) {
260                   _intTick += intTicks;
261                   setTrack(newLoc.track() - _trackOffset);
262                   return;
263                   }
264             setLocation(newLoc); // recursion
265             return;
266             }
267       setTrack(l.track() - _trackOffset);
268       setTick(l.frac() - _tickOffset);
269       if (!pasteMode()) {
270             Q_ASSERT(l.measure() == currentMeasureIndex());
271             incTick(currentMeasure()->tick());
272             }
273       }
274 
275 //---------------------------------------------------------
276 //   addBeam
277 //---------------------------------------------------------
278 
addBeam(Beam * s)279 void XmlReader::addBeam(Beam* s)
280       {
281       _beams.insert(s->id(), s);
282       }
283 
284 //---------------------------------------------------------
285 //   addTuplet
286 //---------------------------------------------------------
287 
addTuplet(Tuplet * s)288 void XmlReader::addTuplet(Tuplet* s)
289       {
290       _tuplets.insert(s->id(), s);
291       }
292 
293 //---------------------------------------------------------
294 //   readDouble
295 //---------------------------------------------------------
296 
readDouble(double min,double max)297 double XmlReader::readDouble(double min, double max)
298       {
299       double val = readElementText().toDouble();
300       if (val < min)
301             val = min;
302       else if (val > max)
303             val = max;
304       return val;
305       }
306 
307 //---------------------------------------------------------
308 //   readBool
309 //---------------------------------------------------------
310 
readBool()311 bool XmlReader::readBool()
312       {
313       bool val;
314       QXmlStreamReader::TokenType tt = readNext();
315       if (tt == QXmlStreamReader::Characters) {
316             val = text().toInt() != 0;
317             readNext();
318             }
319       else
320             val = true;
321       return val;
322       }
323 
324 //---------------------------------------------------------
325 //   checkTuplets
326 //---------------------------------------------------------
327 
checkTuplets()328 void XmlReader::checkTuplets()
329       {
330       for (Tuplet* tuplet : tuplets()) {
331             if (tuplet->elements().empty()) {
332                   // this should not happen and is a sign of input file corruption
333                   qDebug("Measure:read(): empty tuplet id %d (%p), input file corrupted?",
334                      tuplet->id(), tuplet);
335                   delete tuplet;
336                   }
337             else {
338                   //sort tuplet elements. Needed for nested tuplets #22537
339                   tuplet->sortElements();
340                   tuplet->sanitizeTuplet();
341                   }
342             }
343       // This requires a separate pass in case of nested tuplets that required sanitizing
344       for (Tuplet* tuplet : tuplets())
345             tuplet->addMissingElements();
346       }
347 
348 //---------------------------------------------------------
349 //   htmlToString
350 //---------------------------------------------------------
351 
htmlToString(int level,QString * s)352 void XmlReader::htmlToString(int level, QString* s)
353       {
354       *s += QString("<%1").arg(name().toString());
355       for (const QXmlStreamAttribute& a : attributes())
356             *s += QString(" %1=\"%2\"").arg(a.name().toString(), a.value().toString());
357       *s += ">";
358       ++level;
359       for (;;) {
360             QXmlStreamReader::TokenType t = readNext();
361             switch(t) {
362                   case QXmlStreamReader::StartElement:
363                         htmlToString(level, s);
364                         break;
365                   case QXmlStreamReader::EndElement:
366                         *s += QString("</%1>").arg(name().toString());
367                         --level;
368                         return;
369                   case QXmlStreamReader::Characters:
370                         if (!isWhitespace())
371                               *s += text().toString().toHtmlEscaped();
372                         break;
373                   case QXmlStreamReader::Comment:
374                         break;
375 
376                   default:
377                         qDebug("htmlToString: read token: %s", qPrintable(tokenString()));
378                         return;
379                   }
380             }
381       }
382 
383 //-------------------------------------------------------------------
384 //   readXml
385 //    read verbatim until end tag of current level is reached
386 //-------------------------------------------------------------------
387 
readXml()388 QString XmlReader::readXml()
389       {
390       QString s;
391       int level = 1;
392       for (QXmlStreamReader::TokenType t = readNext(); t != QXmlStreamReader::EndElement; t = readNext()) {
393             switch (t) {
394                   case QXmlStreamReader::StartElement:
395                         htmlToString(level, &s);
396                         break;
397                   case QXmlStreamReader::EndElement:
398                         break;
399                   case QXmlStreamReader::Characters:
400                         s += text().toString().toHtmlEscaped();
401                         break;
402                   case QXmlStreamReader::Comment:
403                         break;
404 
405                   default:
406                         qDebug("htmlToString: read token: %s", qPrintable(tokenString()));
407                         return s;
408                   }
409             }
410       return s;
411       }
412 
413 //---------------------------------------------------------
414 //   readPlacement
415 //---------------------------------------------------------
416 
readPlacement(XmlReader & e)417 PlaceText readPlacement(XmlReader& e)
418       {
419       const QString& s(e.readElementText());
420       if (s == "auto" || s == "0")
421             return PlaceText::AUTO;
422       if (s == "above" || s == "1")
423             return PlaceText::ABOVE;
424       if (s == "below" || s == "2")
425             return PlaceText::BELOW;
426       if (s == "left" || s == "3")
427             return PlaceText::LEFT;
428       qDebug("unknown placement value <%s>", qPrintable(s));
429       return PlaceText::AUTO;
430       }
431 
432 //---------------------------------------------------------
433 //   spannerValues
434 //---------------------------------------------------------
435 
spannerValues(int id) const436 const SpannerValues* XmlReader::spannerValues(int id) const
437       {
438       for (const SpannerValues& v : _spannerValues) {
439             if (v.spannerId == id)
440                   return &v;
441             }
442       return 0;
443       }
444 
445 //---------------------------------------------------------
446 //   addSpanner
447 //---------------------------------------------------------
448 
addSpanner(int id,Spanner * s)449 void XmlReader::addSpanner(int id, Spanner* s)
450       {
451       _spanner.append(std::pair<int, Spanner*>(id, s));
452       }
453 
454 //---------------------------------------------------------
455 //   removeSpanner
456 //---------------------------------------------------------
457 
removeSpanner(const Spanner * s)458 void XmlReader::removeSpanner(const Spanner* s)
459       {
460       for (auto i : qAsConst(_spanner)) {
461             if (i.second == s) {
462                   _spanner.removeOne(i);
463                   return;
464                   }
465             }
466       }
467 
468 //---------------------------------------------------------
469 //   findSpanner
470 //---------------------------------------------------------
471 
findSpanner(int id)472 Spanner* XmlReader::findSpanner(int id)
473       {
474       for (auto i : qAsConst(_spanner)) {
475             if (i.first == id)
476                   return i.second;
477             }
478       return nullptr;
479       }
480 
481 //---------------------------------------------------------
482 //   spannerId
483 //---------------------------------------------------------
484 
spannerId(const Spanner * s)485 int XmlReader::spannerId(const Spanner* s)
486       {
487       for (auto i : qAsConst(_spanner)) {
488             if (i.second == s)
489                   return i.first;
490             }
491       qDebug("XmlReader::spannerId not found");
492       return -1;
493       }
494 
495 //---------------------------------------------------------
496 //   addUserTextStyle
497 //    return false if mapping is not possible
498 //      (too many user text styles)
499 //---------------------------------------------------------
500 
addUserTextStyle(const QString & name)501 Tid XmlReader::addUserTextStyle(const QString& name)
502       {
503       qDebug("%s", qPrintable(name));
504       Tid id = Tid::TEXT_STYLES;
505       if (userTextStyles.size() == 0)
506             id = Tid::USER1;
507       else if (userTextStyles.size() == 1)
508             id = Tid::USER2;
509       else if (userTextStyles.size() == 2)
510             id = Tid::USER3;
511       else if (userTextStyles.size() == 3)
512             id = Tid::USER4;
513       else if (userTextStyles.size() == 4)
514             id = Tid::USER5;
515       else if (userTextStyles.size() == 5)
516             id = Tid::USER6;
517       else if (userTextStyles.size() == 6)
518             id = Tid::USER7;
519       else if (userTextStyles.size() == 7)
520             id = Tid::USER8;
521       else if (userTextStyles.size() == 8)
522             id = Tid::USER9;
523       else if (userTextStyles.size() == 9)
524             id = Tid::USER10;
525       else if (userTextStyles.size() == 10)
526             id = Tid::USER11;
527       else if (userTextStyles.size() == 11)
528             id = Tid::USER12;
529       else
530             qDebug("too many user defined textstyles");
531       if (id != Tid::TEXT_STYLES)
532             userTextStyles.push_back({name, id});
533       return id;
534       }
535 
536 //---------------------------------------------------------
537 //   lookupUserTextStyle
538 //---------------------------------------------------------
539 
lookupUserTextStyle(const QString & name) const540 Tid XmlReader::lookupUserTextStyle(const QString& name) const
541       {
542       for (const auto& i : userTextStyles) {
543             if (i.name == name)
544                   return i.ss;
545             }
546       return Tid::TEXT_STYLES;       // not found
547       }
548 
549 //---------------------------------------------------------
550 //   addConnectorInfo
551 //---------------------------------------------------------
552 
addConnectorInfo(std::unique_ptr<ConnectorInfoReader> c)553 void XmlReader::addConnectorInfo(std::unique_ptr<ConnectorInfoReader> c)
554       {
555       _connectors.push_back(std::move(c));
556       ConnectorInfoReader* c1 = _connectors.back().get();
557       c1->update();
558       for (std::unique_ptr<ConnectorInfoReader>& c2 : _connectors) {
559             if (c2->connect(c1)) {
560                   if (c2->finished()) {
561                         c2->addToScore(pasteMode());
562                         removeConnector(c2.get());
563                         }
564                   break;
565                   }
566             }
567       }
568 
569 //---------------------------------------------------------
570 //   removeConnector
571 //---------------------------------------------------------
572 
removeConnector(const ConnectorInfoReader * c)573 void XmlReader::removeConnector(const ConnectorInfoReader* c)
574       {
575       while (c->prev())
576             c = c->prev();
577       while (c) {
578             ConnectorInfoReader* next = c->next();
579             for (auto it = _connectors.begin(); it != _connectors.end(); ++it) {
580                   if (it->get() == c) {
581                         _connectors.erase(it);
582                         break;
583                         }
584                   }
585             c = next;
586             }
587       }
588 
589 //---------------------------------------------------------
590 //   checkConnectors
591 //---------------------------------------------------------
592 
checkConnectors()593 void XmlReader::checkConnectors()
594       {
595       for (std::unique_ptr<ConnectorInfoReader>& c : _pendingConnectors)
596             addConnectorInfo(std::move(c));
597       _pendingConnectors.clear();
598       }
599 
600 //---------------------------------------------------------
601 //   distanceSort
602 //---------------------------------------------------------
603 
distanceSort(const QPair<int,QPair<ConnectorInfoReader *,ConnectorInfoReader * >> & p1,const QPair<int,QPair<ConnectorInfoReader *,ConnectorInfoReader * >> & p2)604 static bool distanceSort(const QPair<int, QPair<ConnectorInfoReader*, ConnectorInfoReader*>>& p1, const QPair<int, QPair<ConnectorInfoReader*, ConnectorInfoReader*>>& p2)
605       {
606       return p1.first < p2.first;
607       }
608 
609 //---------------------------------------------------------
610 //   reconnectBrokenConnectors
611 //---------------------------------------------------------
612 
reconnectBrokenConnectors()613 void XmlReader::reconnectBrokenConnectors()
614       {
615       if (_connectors.empty())
616             return;
617       qDebug("Reconnecting broken connectors (%d nodes)", int(_connectors.size()));
618       QList<QPair<int, QPair<ConnectorInfoReader*, ConnectorInfoReader*>>> brokenPairs;
619       for (size_t i = 1; i < _connectors.size(); ++i) {
620             for (size_t j = 0; j < i; ++j) {
621                   ConnectorInfoReader* c1 = _connectors[i].get();
622                   ConnectorInfoReader* c2 = _connectors[j].get();
623                   int d = c1->connectionDistance(*c2);
624                   if (d >= 0)
625                         brokenPairs.append(qMakePair(d, qMakePair(c1, c2)));
626                   else
627                         brokenPairs.append(qMakePair(-d, qMakePair(c2, c1)));
628                   }
629             }
630       std::sort(brokenPairs.begin(), brokenPairs.end(), distanceSort);
631       for (auto& distPair : brokenPairs) {
632             if (distPair.first == INT_MAX)
633                   continue;
634             auto& pair = distPair.second;
635             if (pair.first->next() || pair.second->prev())
636                   continue;
637             pair.first->forceConnect(pair.second);
638             }
639       QSet<ConnectorInfoReader*> reconnected;
640       for (auto& conn : _connectors) {
641             ConnectorInfoReader* c = conn.get();
642             if (c->finished())
643                   reconnected.insert(static_cast<ConnectorInfoReader*>(c->start()));
644             }
645       for (ConnectorInfoReader* cptr : reconnected) {
646             cptr->addToScore(pasteMode());
647             removeConnector(cptr);
648             }
649       qDebug("reconnected %d broken connectors", reconnected.count());
650       }
651 
652 //---------------------------------------------------------
653 //   addLink
654 //---------------------------------------------------------
655 
addLink(Staff * s,LinkedElements * link)656 void XmlReader::addLink(Staff* s, LinkedElements* link)
657       {
658       int staff = s->idx();
659       const bool masterScore = s->score()->isMaster();
660       if (!masterScore)
661             staff *= -1;
662 
663       QList<QPair<LinkedElements*, Location>>& staffLinks = _staffLinkedElements[staff];
664       if (!masterScore) {
665             if (!staffLinks.empty()
666                && (link->mainElement()->score() != staffLinks.front().first->mainElement()->score())
667                )
668                   staffLinks.clear();
669             }
670 
671       Location l = location(true);
672       _linksIndexer.assignLocalIndex(l);
673       staffLinks.push_back(qMakePair(link, l));
674       }
675 
676 //---------------------------------------------------------
677 //   getLink
678 //---------------------------------------------------------
679 
getLink(bool masterScore,const Location & l,int localIndexDiff)680 LinkedElements* XmlReader::getLink(bool masterScore, const Location& l, int localIndexDiff)
681       {
682       int staff = l.staff();
683       if (!masterScore)
684             staff *= -1;
685       const int localIndex = _linksIndexer.assignLocalIndex(l) + localIndexDiff;
686       QList<QPair<LinkedElements*, Location>>& staffLinks = _staffLinkedElements[staff];
687 
688       if (!staffLinks.isEmpty() && staffLinks.constLast().second == l) {
689             // This element potentially affects local index for "main"
690             // elements that may go afterwards at the same tick, so
691             // append it to staffLinks as well.
692             staffLinks.push_back(staffLinks.constLast()); // nothing should reference exactly this local index, so it shouldn't matter what to append
693             }
694 
695       for (int i = 0; i < staffLinks.size(); ++i) {
696             if (staffLinks[i].second == l) {
697                   if (localIndex == 0)
698                         return staffLinks[i].first;
699                   i += localIndex;
700                   if ((i < 0) || (i >= staffLinks.size()))
701                         return nullptr;
702                   if (staffLinks[i].second == l)
703                         return staffLinks[i].first;
704                   return nullptr;
705                   }
706             }
707       return nullptr;
708       }
709 
710 //---------------------------------------------------------
711 //   assignLocalIndex
712 //---------------------------------------------------------
713 
assignLocalIndex(const Location & mainElementLocation)714 int LinksIndexer::assignLocalIndex(const Location& mainElementLocation)
715       {
716       if (_lastLinkedElementLoc == mainElementLocation)
717             return (++_lastLocalIndex);
718       _lastLocalIndex = 0;
719       _lastLinkedElementLoc = mainElementLocation;
720       return 0;
721       }
722 
723 //---------------------------------------------------------
724 //   rtick
725 //    return relative position in measure
726 //---------------------------------------------------------
727 
rtick() const728 Fraction XmlReader::rtick() const
729       {
730       return _curMeasure ? _tick - _curMeasure->tick() : _tick;
731       }
732 
733 //---------------------------------------------------------
734 //   setTick
735 //---------------------------------------------------------
736 
setTick(const Fraction & f)737 void XmlReader::setTick(const Fraction& f)
738       {
739       _tick = f.reduced();
740       _intTick = _tick.ticks();
741       }
742 
743 //---------------------------------------------------------
744 //   incTick
745 //---------------------------------------------------------
746 
incTick(const Fraction & f)747 void XmlReader::incTick(const Fraction& f)
748       {
749       _tick += f;
750       _tick.reduce();
751       _intTick += f.ticks();
752       }
753 }
754 
755 
756