1 //=============================================================================
2 //  MuseScore
3 //  Music Composition & Notation
4 //
5 //  Copyright (C) 2011-2012 Werner Schweer and others
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 LICENSE.GPL
11 //=============================================================================
12 
13 #include "config.h"
14 #include "score.h"
15 #include "xml.h"
16 #include "element.h"
17 #include "measure.h"
18 #include "segment.h"
19 #include "slur.h"
20 #include "chordrest.h"
21 #include "chord.h"
22 #include "tuplet.h"
23 #include "beam.h"
24 #include "revisions.h"
25 #include "page.h"
26 #include "part.h"
27 #include "staff.h"
28 #include "system.h"
29 #include "keysig.h"
30 #include "clef.h"
31 #include "text.h"
32 #include "ottava.h"
33 #include "volta.h"
34 #include "excerpt.h"
35 #include "mscore.h"
36 #include "stafftype.h"
37 #include "sym.h"
38 #include "scoreOrder.h"
39 
40 #include "mscore/preferences.h"
41 
42 #ifdef OMR
43 #include "omr/omr.h"
44 #include "omr/omrpage.h"
45 #endif
46 
47 #ifdef AVSOMR
48 #include "avsomr/msmrwriter.h"
49 #endif
50 
51 #include "sig.h"
52 #include "undo.h"
53 #include "imageStore.h"
54 #include "audio.h"
55 #include "barline.h"
56 #include "thirdparty/qzip/qzipreader_p.h"
57 #include "thirdparty/qzip/qzipwriter_p.h"
58 #ifdef Q_OS_WIN
59 #include <windows.h>
60 #endif
61 
62 namespace Ms {
63 
64 //---------------------------------------------------------
65 //   writeMeasure
66 //---------------------------------------------------------
67 
writeMeasure(XmlWriter & xml,MeasureBase * m,int staffIdx,bool writeSystemElements,bool forceTimeSig)68 static void writeMeasure(XmlWriter& xml, MeasureBase* m, int staffIdx, bool writeSystemElements, bool forceTimeSig)
69       {
70       //
71       // special case multi measure rest
72       //
73       if (m->isMeasure() || staffIdx == 0)
74             m->write(xml, staffIdx, writeSystemElements, forceTimeSig);
75 
76       if (m->score()->styleB(Sid::createMultiMeasureRests) && m->isMeasure() && toMeasure(m)->mmRest())
77             toMeasure(m)->mmRest()->write(xml, staffIdx, writeSystemElements, forceTimeSig);
78 
79       xml.setCurTick(m->endTick());
80       }
81 
82 //---------------------------------------------------------
83 //   writeMovement
84 //---------------------------------------------------------
85 
writeMovement(XmlWriter & xml,bool selectionOnly)86 void Score::writeMovement(XmlWriter& xml, bool selectionOnly)
87       {
88       // if we have multi measure rests and some parts are hidden,
89       // then some layout information is missing:
90       // relayout with all parts set visible
91 
92       QList<Part*> hiddenParts;
93       bool unhide = false;
94       if (styleB(Sid::createMultiMeasureRests)) {
95             for (Part* part : qAsConst(_parts)) {
96                   if (!part->show()) {
97                         if (!unhide) {
98                               startCmd();
99                               unhide = true;
100                               }
101                         part->undoChangeProperty(Pid::VISIBLE, true);
102                         hiddenParts.append(part);
103                         }
104                   }
105             }
106       if (unhide) {
107             doLayout();
108             for (Part* p : hiddenParts)
109                   p->setShow(false);
110             }
111 
112       xml.stag(this);
113       if (excerpt()) {
114             Excerpt* e = excerpt();
115             QMultiMap<int, int> trackList = e->tracks();
116             QMapIterator<int, int> i(trackList);
117             if (!(trackList.size() == e->nstaves() * VOICES) && !trackList.isEmpty()) {
118                   while (i.hasNext()) {
119                       i.next();
120                       xml.tagE(QString("Tracklist sTrack=\"%1\" dstTrack=\"%2\"").arg(i.key()).arg(i.value()));
121                       }
122                   }
123             }
124 
125       if (lineMode())
126             xml.tag("layoutMode", "line");
127       if (systemMode())
128             xml.tag("layoutMode", "system");
129 
130 #ifdef OMR
131       if (masterScore()->omr() && xml.writeOmr())
132             masterScore()->omr()->write(xml);
133 #endif
134       if (isMaster() && masterScore()->showOmr() && xml.writeOmr())
135             xml.tag("showOmr", masterScore()->showOmr());
136       if (_audio && xml.writeOmr()) {
137             xml.tag("playMode", int(_playMode));
138             _audio->write(xml);
139             }
140 
141       for (int i = 0; i < 32; ++i) {
142             if (!_layerTags[i].isEmpty()) {
143                   xml.tag(QString("LayerTag id=\"%1\" tag=\"%2\"").arg(i).arg(_layerTags[i]),
144                      _layerTagComments[i]);
145                   }
146             }
147       int n = _layer.size();
148       for (int i = 1; i < n; ++i) {       // don’t save default variant
149             const Layer& l = _layer[i];
150             xml.tagE(QString("Layer name=\"%1\" mask=\"%2\"").arg(l.name).arg(l.tags));
151             }
152       xml.tag("currentLayer", _currentLayer);
153 
154       if (isTopScore() && !MScore::testMode)
155             _synthesizerState.write(xml);
156 
157       if (pageNumberOffset())
158             xml.tag("page-offset", pageNumberOffset());
159       xml.tag("Division", MScore::division);
160       xml.setCurTrack(-1);
161 
162       if (isTopScore())                    // only top score
163             style().save(xml, true);       // save only differences to buildin style
164 
165       xml.tag("showInvisible",   _showInvisible);
166       xml.tag("showUnprintable", _showUnprintable);
167       xml.tag("showFrames",      _showFrames);
168       xml.tag("showMargins",     _showPageborders);
169       xml.tag("markIrregularMeasures", _markIrregularMeasures, true);
170 
171       QMapIterator<QString, QString> i(_metaTags);
172       while (i.hasNext()) {
173             i.next();
174             // do not output "platform" and "creationDate" in test and save template mode
175             if ((!MScore::testMode && !MScore::saveTemplateMode) || (i.key() != "platform" && i.key() != "creationDate"))
176                   xml.tag(QString("metaTag name=\"%1\"").arg(i.key().toHtmlEscaped()), i.value());
177             }
178 
179       if (_scoreOrder && !_scoreOrder->isCustom()) {
180             ScoreOrder* order = _scoreOrder->clone();
181             order->updateInstruments(this);
182             order->write(xml);
183             delete order;
184             }
185 
186       xml.setCurTrack(0);
187       int staffStart;
188       int staffEnd;
189       MeasureBase* measureStart;
190       MeasureBase* measureEnd;
191 
192       if (selectionOnly) {
193             staffStart   = _selection.staffStart();
194             staffEnd     = _selection.staffEnd();
195             // make sure we select full parts
196             Staff* sStaff = staff(staffStart);
197             Part* sPart = sStaff->part();
198             Staff* eStaff = staff(staffEnd - 1);
199             Part* ePart = eStaff->part();
200             staffStart = staffIdx(sPart);
201             staffEnd = staffIdx(ePart) + ePart->nstaves();
202             measureStart = _selection.startSegment()->measure();
203             if (measureStart->isMeasure() && toMeasure(measureStart)->isMMRest())
204                   measureStart = toMeasure(measureStart)->mmRestFirst();
205             if (_selection.endSegment())
206                   measureEnd   = _selection.endSegment()->measure()->next();
207             else
208                   measureEnd   = 0;
209             }
210       else {
211             staffStart   = 0;
212             staffEnd     = nstaves();
213             measureStart = first();
214             measureEnd   = 0;
215             }
216 
217       // Let's decide: write midi mapping to a file or not
218       masterScore()->checkMidiMapping();
219       for (const Part* part : qAsConst(_parts)) {
220             if (!selectionOnly || ((staffIdx(part) >= staffStart) && (staffEnd >= staffIdx(part) + part->nstaves())))
221                   part->write(xml);
222             }
223 
224       xml.setCurTrack(0);
225       xml.setTrackDiff(-staffStart * VOICES);
226       if (measureStart) {
227             for (int staffIdx = staffStart; staffIdx < staffEnd; ++staffIdx) {
228                   xml.stag(staff(staffIdx), QString("id=\"%1\"").arg(staffIdx + 1 - staffStart));
229                   xml.setCurTick(measureStart->tick());
230                   xml.setTickDiff(xml.curTick());
231                   xml.setCurTrack(staffIdx * VOICES);
232                   bool writeSystemElements = (staffIdx == staffStart);
233                   bool firstMeasureWritten = false;
234                   bool forceTimeSig = false;
235                   for (MeasureBase* m = measureStart; m != measureEnd; m = m->next()) {
236                         // force timesig if first measure and selectionOnly
237                         if (selectionOnly && m->isMeasure()) {
238                               if (!firstMeasureWritten) {
239                                     forceTimeSig = true;
240                                     firstMeasureWritten = true;
241                                     }
242                               else
243                                     forceTimeSig = false;
244                               }
245                         writeMeasure(xml, m, staffIdx, writeSystemElements, forceTimeSig);
246                         }
247                   xml.etag();
248                   }
249             }
250       xml.setCurTrack(-1);
251       if (isMaster()) {
252             if (!selectionOnly) {
253                   for (const Excerpt* excerpt : excerpts()) {
254                         if (excerpt->partScore() != this)
255                               excerpt->partScore()->write(xml, false);       // recursion
256                         }
257                   }
258             }
259       else
260             xml.tag("name", excerpt()->title());
261       xml.etag();
262 
263       if (unhide)
264             endCmd(true);
265       }
266 
267 //---------------------------------------------------------
268 //   write
269 //---------------------------------------------------------
270 
write(XmlWriter & xml,bool selectionOnly)271 void Score::write(XmlWriter& xml, bool selectionOnly)
272       {
273       if (isMaster()) {
274             MasterScore* score = static_cast<MasterScore*>(this);
275 
276             while (score->prev())
277                   score = score->prev();
278             while (score) {
279                   score->writeMovement(xml, selectionOnly);
280                   score = score->next();
281                   }
282             }
283       else
284             writeMovement(xml, selectionOnly);
285       }
286 
287 //---------------------------------------------------------
288 //   Staff
289 //---------------------------------------------------------
290 
readStaff(XmlReader & e)291 void Score::readStaff(XmlReader& e)
292       {
293       int staff = e.intAttribute("id", 1) - 1;
294       int measureIdx = 0;
295       e.setCurrentMeasureIndex(0);
296       e.setTick(Fraction(0,1));
297       e.setTrack(staff * VOICES);
298 
299       if (staff == 0) {
300             while (e.readNextStartElement()) {
301                   const QStringRef& tag(e.name());
302 
303                   if (tag == "Measure") {
304                         Measure* measure = 0;
305                         measure = new Measure(this);
306                         measure->setTick(e.tick());
307                         e.setCurrentMeasureIndex(measureIdx++);
308                         //
309                         // inherit timesig from previous measure
310                         //
311                         Measure* m = e.lastMeasure(); // measure->prevMeasure();
312                         Fraction f(m ? m->timesig() : Fraction(4,4));
313                         measure->setTicks(f);
314                         measure->setTimesig(f);
315 
316                         measure->read(e, staff);
317                         measure->checkMeasure(staff);
318                         if (!measure->isMMRest()) {
319                               measures()->add(measure);
320                               e.setLastMeasure(measure);
321                               e.setTick(measure->tick() + measure->ticks());
322                               }
323                         else {
324                               // this is a multi measure rest
325                               // always preceded by the first measure it replaces
326                               Measure* m1 = e.lastMeasure();
327 
328                               if (m1) {
329                                     m1->setMMRest(measure);
330                                     measure->setTick(m1->tick());
331                                     }
332                               }
333                         }
334                   else if (tag == "HBox" || tag == "VBox" || tag == "TBox" || tag == "FBox") {
335                         MeasureBase* mb = toMeasureBase(Element::name2Element(tag, this));
336                         mb->read(e);
337                         mb->setTick(e.tick());
338                         measures()->add(mb);
339                         }
340                   else if (tag == "tick")
341                         e.setTick(Fraction::fromTicks(fileDivision(e.readInt())));
342                   else
343                         e.unknown();
344                   }
345             }
346       else {
347             Measure* measure = firstMeasure();
348             while (e.readNextStartElement()) {
349                   const QStringRef& tag(e.name());
350 
351                   if (tag == "Measure") {
352                         if (measure == 0) {
353                               qDebug("Score::readStaff(): missing measure!");
354                               measure = new Measure(this);
355                               measure->setTick(e.tick());
356                               measures()->add(measure);
357                               }
358                         e.setTick(measure->tick());
359                         e.setCurrentMeasureIndex(measureIdx++);
360                         measure->read(e, staff);
361                         measure->checkMeasure(staff);
362                         if (measure->isMMRest())
363                               measure = e.lastMeasure()->nextMeasure();
364                         else {
365                               e.setLastMeasure(measure);
366                               if (measure->mmRest())
367                                     measure = measure->mmRest();
368                               else
369                                     measure = measure->nextMeasure();
370                               }
371                         }
372                   else if (tag == "tick")
373                         e.setTick(Fraction::fromTicks(fileDivision(e.readInt())));
374                   else
375                         e.unknown();
376                   }
377             }
378       }
379 
380 //---------------------------------------------------------
381 //   saveFile
382 ///   If file has generated name, create a modal file save dialog
383 ///   and ask filename.
384 ///   Rename old file to backup file (.xxxx.msc?,).
385 ///   Default is to save score in .mscz format,
386 ///   Return true if OK and false on error.
387 //---------------------------------------------------------
388 
saveFile(bool generateBackup)389 bool MasterScore::saveFile(bool generateBackup)
390       {
391       if (readOnly())
392             return false;
393       QString suffix = info.suffix();
394       if (info.exists() && !info.isWritable()) {
395             MScore::lastError = tr("The following file is locked: \n%1 \n\nTry saving to a different location.").arg(info.filePath());
396             return false;
397             }
398       //
399       // step 1
400       // save into temporary file to prevent partially overwriting
401       // the original file in case of "disc full"
402       //
403 
404       QString tempName = info.filePath() + QString(".temp");
405       QFile temp(tempName);
406       if (!temp.open(QIODevice::WriteOnly)) {
407             MScore::lastError = tr("Open Temp File\n%1\nfailed: %2").arg(tempName, strerror(errno));
408             return false;
409             }
410 
411       bool rv = false;
412       if ("mscx" == suffix) {
413            rv = Score::saveFile(&temp, false);
414             }
415 #ifdef AVSOMR
416       else if ("msmr" == suffix) {
417             Avs::MsmrWriter msmrWriter;
418             rv = msmrWriter.saveMsmrFile(this, &temp, info);
419             }
420 #endif
421       else {
422            QString fileName = info.completeBaseName() + ".mscx";
423            rv = Score::saveCompressedFile(&temp, fileName, false);
424             }
425 
426       if (!rv) {
427             return false;
428             }
429 
430       if (temp.error() != QFile::NoError) {
431             MScore::lastError = tr("Save File failed: %1").arg(temp.errorString());
432             return false;
433             }
434       temp.close();
435 
436       const QString name(info.filePath());
437       const QString basename(info.fileName());
438       QDir dir(info.path());
439       if (!saved() && generateBackup) {
440             // if file was already saved in this session
441             // save but don't overwrite backup again
442 
443             const QString backupSubdirString = preferences.getString(PREF_APP_BACKUP_SUBFOLDER);
444             const QString backupDirString = info.path() + QString(QDir::separator()) + backupSubdirString;
445             QDir backupDir(backupDirString);
446             if (!backupDir.exists()) {
447                   dir.mkdir(backupSubdirString);
448 #ifdef Q_OS_WIN
449                   const QString backupDirNativePath = QDir::toNativeSeparators(backupDirString);
450 #if (defined (_MSCVER) || defined (_MSC_VER))
451    #if (defined (UNICODE))
452                   SetFileAttributes((LPCTSTR)backupDirNativePath.unicode(), FILE_ATTRIBUTE_HIDDEN);
453    #else
454                   // Use byte-based Windows function
455                   SetFileAttributes((LPCTSTR)backupDirNativePath.toLocal8Bit(), FILE_ATTRIBUTE_HIDDEN);
456    #endif
457 #else
458                   SetFileAttributes((LPCTSTR)backupDirNativePath.toLocal8Bit(), FILE_ATTRIBUTE_HIDDEN);
459 #endif
460 #endif
461                   }
462             const QString backupName = QString(".") + info.fileName() + QString(",");
463             if (backupDir.exists(backupName)) {
464                   if (!backupDir.remove(backupName)) {
465 //                      if (!MScore::noGui)
466 //                            QMessageBox::critical(0, QObject::tr("Save File"),
467 //                               tr("Removing old backup file %1 failed").arg(backupName));
468                         }
469                   }
470             //
471             // step 2
472             // backup files prior to 3.5 were saved in the same directory as the file itself.
473             // remove these old backup files if needed
474             //
475             if (dir != backupDir && dir.exists(backupName)) {
476                   if (!dir.remove(backupName)) {
477 //                      if (!MScore::noGui)
478 //                            QMessageBox::critical(0, QObject::tr("Save File"),
479 //                               tr("Removing old backup file %1 failed").arg(backupName));
480                         }
481                   }
482 
483             //
484             // step 3
485             // rename old file into backup
486             //
487             if (dir.exists(basename)) {
488                   if (!QFile::rename(name, backupDirString + (backupDirString.endsWith("/") ? "" : "/") + backupName)) {
489 //                      if (!MScore::noGui)
490 //                            QMessageBox::critical(0, tr("Save File"),
491 //                               tr("Renaming old file <%1> to backup <%2> failed").arg(name, backupDirString + "/" + backupName);
492                         }
493                   }
494 
495             QFileInfo fileBackup(backupDir, backupName);
496             _sessionStartBackupInfo = fileBackup;
497             }
498       else {
499             // file has previously been saved - remove the old file
500             if (dir.exists(basename)) {
501                   if (!dir.remove(basename)) {
502 //                      if (!MScore::noGui)
503 //                            QMessageBox::critical(0, tr("Save File"),
504 //                               tr("Removing old file %1 failed").arg(name));
505                         }
506                   }
507             }
508 
509       //
510       // step 4
511       // rename temp name into file name
512       //
513       if (!QFile::rename(tempName, name)) {
514             MScore::lastError = tr("Renaming temp. file <%1> to <%2> failed:\n%3").arg(tempName, name, strerror(errno));
515             return false;
516             }
517       // make file readable by all
518       QFile::setPermissions(name, QFile::ReadOwner | QFile::WriteOwner | QFile::ReadUser
519          | QFile::ReadGroup | QFile::ReadOther);
520 
521       undoStack()->setClean();
522       setSaved(true);
523       info.refresh();
524       update();
525       return true;
526       }
527 
528 //---------------------------------------------------------
529 //   saveCompressedFile
530 //---------------------------------------------------------
531 
saveCompressedFile(QFileInfo & info,bool onlySelection,bool createThumbnail)532 bool Score::saveCompressedFile(QFileInfo& info, bool onlySelection, bool createThumbnail)
533       {
534       if (readOnly() && info == *masterScore()->fileInfo())
535             return false;
536       QFile fp(info.filePath());
537       if (!fp.open(QIODevice::WriteOnly)) {
538             MScore::lastError = tr("Open File\n%1\nfailed: %2").arg(info.filePath(), strerror(errno));
539             return false;
540             }
541 
542       QString fileName = info.completeBaseName() + ".mscx";
543       return saveCompressedFile(&fp, fileName, onlySelection, createThumbnail);
544       }
545 
546 //---------------------------------------------------------
547 //   createThumbnail
548 //---------------------------------------------------------
549 
createThumbnail()550 QImage Score::createThumbnail()
551       {
552       LayoutMode mode = layoutMode();
553       setLayoutMode(LayoutMode::PAGE);
554       doLayout();
555 
556       Page* page = pages().at(0);
557       QRectF fr  = page->abbox();
558       qreal mag  = 256.0 / qMax(fr.width(), fr.height());
559       int w      = int(fr.width() * mag);
560       int h      = int(fr.height() * mag);
561 
562       QImage pm(w, h, QImage::Format_ARGB32_Premultiplied);
563 
564       int dpm = lrint(DPMM * 1000.0);
565       pm.setDotsPerMeterX(dpm);
566       pm.setDotsPerMeterY(dpm);
567       pm.fill(0xffffffff);
568 
569       double pr = MScore::pixelRatio;
570       MScore::pixelRatio = 1.0;
571 
572       QPainter p(&pm);
573       p.setRenderHint(QPainter::Antialiasing, true);
574       p.setRenderHint(QPainter::TextAntialiasing, true);
575       p.scale(mag, mag);
576       print(&p, 0);
577       p.end();
578 
579       MScore::pixelRatio = pr;
580 
581       if (layoutMode() != mode) {
582             setLayoutMode(mode);
583             doLayout();
584             }
585       return pm;
586       }
587 
588 //---------------------------------------------------------
589 //   saveCompressedFile
590 //    file is already opened
591 //---------------------------------------------------------
592 
saveCompressedFile(QIODevice * f,const QString & fn,bool onlySelection,bool doCreateThumbnail)593 bool Score::saveCompressedFile(QIODevice* f, const QString& fn, bool onlySelection, bool doCreateThumbnail)
594       {
595       MQZipWriter uz(f);
596 
597       QBuffer cbuf;
598       cbuf.open(QIODevice::ReadWrite);
599       XmlWriter xml(this, &cbuf);
600       xml << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n";
601       xml.stag("container");
602       xml.stag("rootfiles");
603       xml.stag(QString("rootfile full-path=\"%1\"").arg(XmlWriter::xmlString(fn)));
604       xml.etag();
605       for (ImageStoreItem* ip : imageStore) {
606             if (!ip->isUsed(this))
607                   continue;
608             QString path = QString("Pictures/") + ip->hashName();
609             xml.tag("file", path);
610             }
611 
612       xml.etag();
613       xml.etag();
614       cbuf.seek(0);
615       //uz.addDirectory("META-INF");
616       uz.addFile("META-INF/container.xml", cbuf.data());
617 
618       QBuffer dbuf;
619       dbuf.open(QIODevice::ReadWrite);
620       saveFile(&dbuf, true, onlySelection);
621       dbuf.seek(0);
622       uz.addFile(fn, dbuf.data());
623 
624       QFileDevice* fd = dynamic_cast<QFileDevice*>(f);
625       if (fd) // if is file (may be buffer)
626             fd->flush(); // flush to preserve score data in case of
627                          // any failures on the further operations.
628 
629       // save images
630       //uz.addDirectory("Pictures");
631       for (ImageStoreItem* ip : imageStore) {
632             if (!ip->isUsed(this))
633                   continue;
634             QString path = QString("Pictures/") + ip->hashName();
635             uz.addFile(path, ip->buffer());
636             }
637 
638       // create thumbnail
639       if (doCreateThumbnail && !pages().isEmpty()) {
640             QImage pm = createThumbnail();
641 
642             QByteArray ba;
643             QBuffer b(&ba);
644             if (!b.open(QIODevice::WriteOnly))
645                   qDebug("open buffer failed");
646             if (!pm.save(&b, "PNG"))
647                   qDebug("save failed");
648             uz.addFile("Thumbnails/thumbnail.png", ba);
649             }
650 
651 #ifdef OMR
652       //
653       // save OMR page images
654       //
655       if (masterScore()->omr()) {
656             int n = masterScore()->omr()->numPages();
657             for (int i = 0; i < n; ++i) {
658                   QString path = QString("OmrPages/page%1.png").arg(i+1);
659                   QBuffer cbuf1;
660                   OmrPage* page = masterScore()->omr()->page(i);
661                   const QImage& image = page->image();
662                   if (!image.save(&cbuf1, "PNG")) {
663                         MScore::lastError = tr("Save file: cannot save image (%1x%2)").arg(image.width(), image.height());
664                         return false;
665                         }
666                   uz.addFile(path, cbuf1.data());
667                   cbuf1.close();
668                   }
669             }
670 #endif
671       //
672       // save audio
673       //
674       if (_audio)
675             uz.addFile("audio.ogg", _audio->data());
676 
677       uz.close();
678       return true;
679       }
680 
681 //---------------------------------------------------------
682 //   saveFile
683 //    return true on success
684 //---------------------------------------------------------
685 
saveFile(QFileInfo & info)686 bool Score::saveFile(QFileInfo& info)
687       {
688       if (readOnly() && info == *masterScore()->fileInfo())
689             return false;
690       if (info.suffix().isEmpty())
691             info.setFile(info.filePath() + ".mscx");
692       QFile fp(info.filePath());
693       if (!fp.open(QIODevice::WriteOnly)) {
694             MScore::lastError = tr("Open File\n%1\nfailed: %2").arg(info.filePath(), strerror(errno));
695             return false;
696             }
697       saveFile(&fp, false, false);
698       fp.close();
699       return true;
700       }
701 
702 //---------------------------------------------------------
703 //   loadStyle
704 //---------------------------------------------------------
705 
loadStyle(const QString & fn,bool ign,const bool overlap)706 bool Score::loadStyle(const QString& fn, bool ign, const bool overlap)
707       {
708       QFile f(fn);
709       if (f.open(QIODevice::ReadOnly)) {
710             MStyle st = style();
711             if (st.load(&f, ign)) {
712                   undo(new ChangeStyle(this, st, overlap));
713                   return true;
714                   }
715              else {
716                   MScore::lastError = QObject::tr("The style file is not compatible with this version of MuseScore.");
717                   return false;
718                   }
719             }
720       MScore::lastError = strerror(errno);
721       return false;
722       }
723 
724 //---------------------------------------------------------
725 //   saveStyle
726 //---------------------------------------------------------
727 
saveStyle(const QString & name)728 bool Score::saveStyle(const QString& name)
729       {
730       QString ext(".mss");
731       QFileInfo info(name);
732 
733       if (info.suffix().isEmpty())
734             info.setFile(info.filePath() + ext);
735       QFile f(info.filePath());
736       if (!f.open(QIODevice::WriteOnly)) {
737             MScore::lastError = tr("Open Style File\n%1\nfailed: %2").arg(info.filePath(), strerror(errno));
738             return false;
739             }
740 
741       XmlWriter xml(this, &f);
742       xml.header();
743       xml.stag("museScore version=\"" MSC_VERSION "\"");
744       style().save(xml, false);     // save complete style
745       xml.etag();
746       if (f.error() != QFile::NoError) {
747             MScore::lastError = QObject::tr("Write Style failed: %1").arg(f.errorString());
748             return false;
749             }
750       return true;
751       }
752 
753 //---------------------------------------------------------
754 //   saveFile
755 //    return true on success
756 //---------------------------------------------------------
757 
758 extern QString revision;
759 
saveFile(QIODevice * f,bool msczFormat,bool onlySelection)760 bool Score::saveFile(QIODevice* f, bool msczFormat, bool onlySelection)
761       {
762       XmlWriter xml(this, f);
763       xml.setWriteOmr(msczFormat);
764       xml.header();
765 
766       xml.stag("museScore version=\"" MSC_VERSION "\"");
767 
768       if (!MScore::testMode) {
769             xml.tag("programVersion", VERSION);
770             xml.tag("programRevision", revision);
771             }
772       write(xml, onlySelection);
773       xml.etag();
774       if (isMaster())
775             masterScore()->revisions()->write(xml);
776       if (!onlySelection) {
777             //update version values for i.e. plugin access
778             _mscoreVersion = VERSION;
779             _mscoreRevision = revision.toInt(0, 16);
780             _mscVersion = MSCVERSION;
781             }
782       return true;
783       }
784 
785 //---------------------------------------------------------
786 //   readRootFile
787 //---------------------------------------------------------
788 
readRootFile(MQZipReader * uz,QList<QString> & images)789 QString readRootFile(MQZipReader* uz, QList<QString>& images)
790       {
791       QString rootfile;
792 
793       QByteArray cbuf = uz->fileData("META-INF/container.xml");
794       if (cbuf.isEmpty()) {
795             qDebug("can't find container.xml");
796             return rootfile;
797             }
798 
799       XmlReader e(cbuf);
800 
801       while (e.readNextStartElement()) {
802             if (e.name() != "container") {
803                   e.unknown();
804                   continue;
805                   }
806             while (e.readNextStartElement()) {
807                   if (e.name() != "rootfiles") {
808                         e.unknown();
809                         continue;
810                         }
811                   while (e.readNextStartElement()) {
812                         const QStringRef& tag(e.name());
813 
814                         if (tag == "rootfile") {
815                               if (rootfile.isEmpty()) {
816                                     rootfile = e.attribute("full-path");
817                                     e.skipCurrentElement();
818                                     }
819                               }
820                         else if (tag == "file")
821                               images.append(e.readElementText());
822                         else
823                               e.unknown();
824                         }
825                   }
826             }
827       return rootfile;
828       }
829 
830 //---------------------------------------------------------
831 //   loadCompressedMsc
832 //    return false on error
833 //---------------------------------------------------------
834 
loadCompressedMsc(QIODevice * io,bool ignoreVersionError)835 Score::FileError MasterScore::loadCompressedMsc(QIODevice* io, bool ignoreVersionError)
836       {
837       MQZipReader uz(io);
838 
839       QList<QString> sl;
840       QString rootfile = readRootFile(&uz, sl);
841       if (rootfile.isEmpty())
842             return FileError::FILE_NO_ROOTFILE;
843 
844       //
845       // load images
846       //
847       if (!MScore::noImages) {
848             foreach(const QString& s, sl) {
849                   QByteArray dbuf = uz.fileData(s);
850                   imageStore.add(s, dbuf);
851                   }
852             }
853 
854       QByteArray dbuf = uz.fileData(rootfile);
855       if (dbuf.isEmpty()) {
856             QVector<MQZipReader::FileInfo> fil = uz.fileInfoList();
857             foreach(const MQZipReader::FileInfo& fi, fil) {
858                   if (fi.filePath.endsWith(".mscx")) {
859                         dbuf = uz.fileData(fi.filePath);
860                         break;
861                         }
862                   }
863             }
864 
865       XmlReader e(dbuf);
866       e.setDocName(masterScore()->fileInfo()->completeBaseName());
867 
868       FileError retval = read1(e, ignoreVersionError);
869 
870 #ifdef OMR
871       //
872       // load OMR page images
873       //
874       if (masterScore()->omr()) {
875             int n = masterScore()->omr()->numPages();
876             for (int i = 0; i < n; ++i) {
877                   QString path = QString("OmrPages/page%1.png").arg(i+1);
878                   QByteArray dbuf1 = uz.fileData(path);
879                   OmrPage* page = masterScore()->omr()->page(i);
880                   QImage image;
881                   if (image.loadFromData(dbuf1, "PNG")) {
882                         page->setImage(image);
883                         }
884                   else
885                         qDebug("load image failed");
886                   }
887             }
888 #endif
889       //
890       //  read audio
891       //
892       if (audio()) {
893             QByteArray dbuf1 = uz.fileData("audio.ogg");
894             audio()->setData(dbuf1);
895             }
896       return retval;
897       }
898 
styleDefaultByMscVersion(const int mscVer) const899 int MasterScore::styleDefaultByMscVersion(const int mscVer) const
900       {
901       constexpr int LEGACY_MSC_VERSION_V3 = 301;
902       constexpr int LEGACY_MSC_VERSION_V2 = 206;
903       constexpr int LEGACY_MSC_VERSION_V1 = 114;
904 
905       if (mscVer > LEGACY_MSC_VERSION_V2 && mscVer < MSCVERSION)
906             return LEGACY_MSC_VERSION_V3;
907 
908       if (mscVer > LEGACY_MSC_VERSION_V1 && mscVer <= LEGACY_MSC_VERSION_V2)
909             return LEGACY_MSC_VERSION_V2;
910 
911       if (mscVer <= LEGACY_MSC_VERSION_V1)
912             return LEGACY_MSC_VERSION_V1;
913 
914       return MSCVERSION;
915       }
916 
readStyleDefaultsVersion()917 int MasterScore::readStyleDefaultsVersion()
918       {
919       if (styleB(Sid::usePre_3_6_defaults))
920             return style().defaultStyleVersion();
921 
922       XmlReader e(readToBuffer());
923       e.setDocName(masterScore()->fileInfo()->completeBaseName());
924 
925       while (!e.atEnd()) {
926             e.readNext();
927             if (e.name() == "defaultsVersion")
928                   return e.readInt();
929             }
930 
931       return styleDefaultByMscVersion(mscVersion());
932       }
933 
934 //---------------------------------------------------------
935 //   loadMsc
936 //    return true on success
937 //---------------------------------------------------------
938 
loadMsc(QString name,bool ignoreVersionError)939 Score::FileError MasterScore::loadMsc(QString name, bool ignoreVersionError)
940       {
941       QFile f(name);
942       if (!f.open(QIODevice::ReadOnly)) {
943             MScore::lastError = f.errorString();
944             return FileError::FILE_OPEN_ERROR;
945             }
946       return loadMsc(name, &f, ignoreVersionError);
947       }
948 
loadMsc(QString name,QIODevice * io,bool ignoreVersionError)949 Score::FileError MasterScore::loadMsc(QString name, QIODevice* io, bool ignoreVersionError)
950       {
951       ScoreLoad sl;
952       fileInfo()->setFile(name);
953 
954       if (name.endsWith(".mscz") || name.endsWith(".mscz,"))
955             return loadCompressedMsc(io, ignoreVersionError);
956       else {
957             XmlReader r(io);
958             return read1(r, ignoreVersionError);
959             }
960       }
961 
962 //---------------------------------------------------------
963 //   parseVersion
964 //---------------------------------------------------------
965 
parseVersion(const QString & val)966 void MasterScore::parseVersion(const QString& val)
967       {
968       QRegExp re("(\\d+)\\.(\\d+)\\.(\\d+)");
969       int v1, v2, v3, rv1, rv2, rv3;
970       if (re.indexIn(VERSION) != -1) {
971             QStringList sl = re.capturedTexts();
972             if (sl.size() == 4) {
973                   v1 = sl[1].toInt();
974                   v2 = sl[2].toInt();
975                   v3 = sl[3].toInt();
976                   if (re.indexIn(val) != -1) {
977                         sl = re.capturedTexts();
978                         if (sl.size() == 4) {
979                               rv1 = sl[1].toInt();
980                               rv2 = sl[2].toInt();
981                               rv3 = sl[3].toInt();
982 
983                               int currentVersion = v1 * 10000 + v2 * 100 + v3;
984                               int readVersion = rv1 * 10000 + rv2 * 100 + rv3;
985                               if (readVersion > currentVersion) {
986                                     qDebug("read future version");
987                                     }
988                               }
989                         }
990                   else {
991                         QRegExp re1("(\\d+)\\.(\\d+)");
992                         if (re1.indexIn(val) != -1) {
993                               sl = re.capturedTexts();
994                               if (sl.size() == 3) {
995                                     rv1 = sl[1].toInt();
996                                     rv2 = sl[2].toInt();
997 
998                                     int currentVersion = v1 * 10000 + v2 * 100 + v3;
999                                     int readVersion = rv1 * 10000 + rv2 * 100;
1000                                     if (readVersion > currentVersion) {
1001                                           qDebug("read future version");
1002                                           }
1003                                     }
1004                               }
1005                         else
1006                               qDebug("1cannot parse <%s>", qPrintable(val));
1007                         }
1008                   }
1009             }
1010       else
1011             qDebug("2cannot parse <%s>", VERSION);
1012       }
1013 
1014 //---------------------------------------------------------
1015 //   read1
1016 //    return true on success
1017 //---------------------------------------------------------
1018 
read1(XmlReader & e,bool ignoreVersionError)1019 Score::FileError MasterScore::read1(XmlReader& e, bool ignoreVersionError)
1020       {
1021       while (e.readNextStartElement()) {
1022             if (e.name() == "museScore") {
1023                   const QString& version = e.attribute("version");
1024                   QStringList sl = version.split('.');
1025                   setMscVersion(sl[0].toInt() * 100 + sl[1].toInt());
1026 
1027                   if (!ignoreVersionError) {
1028                         if (mscVersion() > MSCVERSION)
1029                               return FileError::FILE_TOO_NEW;
1030                         if (mscVersion() < 114)
1031                               return FileError::FILE_TOO_OLD;
1032                         if (mscVersion() == 300)
1033                               return FileError::FILE_OLD_300_FORMAT;
1034                         }
1035 
1036                   if (created() && !preferences.getString(PREF_SCORE_STYLE_DEFAULTSTYLEFILE).isEmpty()) {
1037                         setStyle(MScore::defaultStyle());
1038                         }
1039                   else {
1040                         int defaultsVersion = readStyleDefaultsVersion();
1041 
1042                         setStyle(*MStyle::resolveStyleDefaults(defaultsVersion));
1043                         style().setDefaultStyleVersion(defaultsVersion);
1044                         }
1045 
1046                   Score::FileError error;
1047                   if (mscVersion() <= 114)
1048                         error = read114(e);
1049                   else if (mscVersion() <= 207)
1050                         error = read206(e);
1051                   else
1052                         error = read302(e);
1053                   setExcerptsChanged(false);
1054                   return error;
1055                   }
1056             else
1057                   e.unknown();
1058             }
1059       return FileError::FILE_CORRUPTED;
1060       }
1061 
1062 //---------------------------------------------------------
1063 //   print
1064 //---------------------------------------------------------
1065 
print(QPainter * painter,int pageNo)1066 void Score::print(QPainter* painter, int pageNo)
1067       {
1068       _printing  = true;
1069       MScore::pdfPrinting = true;
1070       Page* page = pages().at(pageNo);
1071       QRectF fr  = page->abbox();
1072 
1073       QList<Element*> ell = page->items(fr);
1074       std::stable_sort(ell.begin(), ell.end(), elementLessThan);
1075       for (const Element* e : qAsConst(ell)) {
1076             if (!e->visible())
1077                   continue;
1078             painter->save();
1079             painter->translate(e->pagePos());
1080             e->draw(painter);
1081             painter->restore();
1082             }
1083       MScore::pdfPrinting = false;
1084       _printing = false;
1085       }
1086 
1087 //---------------------------------------------------------
1088 //   readCompressedToBuffer
1089 //---------------------------------------------------------
1090 
readCompressedToBuffer()1091 QByteArray MasterScore::readCompressedToBuffer()
1092       {
1093       MQZipReader uz(info.filePath());
1094       if (!uz.exists()) {
1095             qDebug("Score::readCompressedToBuffer: cannot read zip file");
1096             return QByteArray();
1097             }
1098       QList<QString> images;
1099       QString rootfile = readRootFile(&uz, images);
1100 
1101       //
1102       // load images
1103       //
1104       foreach(const QString& s, images) {
1105             QByteArray dbuf = uz.fileData(s);
1106             imageStore.add(s, dbuf);
1107             }
1108 
1109       if (rootfile.isEmpty()) {
1110             qDebug("=can't find rootfile in: %s", qPrintable(info.filePath()));
1111             return QByteArray();
1112             }
1113       return uz.fileData(rootfile);
1114       }
1115 
1116 //---------------------------------------------------------
1117 //   readToBuffer
1118 //---------------------------------------------------------
1119 
readToBuffer()1120 QByteArray MasterScore::readToBuffer()
1121       {
1122       QByteArray ba;
1123       QString cs  = info.suffix();
1124 
1125       if (cs == "mscz") {
1126             ba = readCompressedToBuffer();
1127             }
1128       if (cs.toLower() == "msc" || cs.toLower() == "mscx") {
1129             QFile f(info.filePath());
1130             if (f.open(QIODevice::ReadOnly)) {
1131                   ba = f.readAll();
1132                   f.close();
1133                   }
1134             }
1135       return ba;
1136       }
1137 
1138 //---------------------------------------------------------
1139 //   createRevision
1140 //---------------------------------------------------------
1141 
createRevision()1142 void Score::createRevision()
1143       {
1144 #if 0
1145 qDebug("createRevision");
1146       QBuffer dbuf;
1147       dbuf.open(QIODevice::ReadWrite);
1148       saveFile(&dbuf, false, false);
1149       dbuf.close();
1150 
1151       QByteArray ba1 = readToBuffer();
1152 
1153       QString os = QString::fromUtf8(ba1.data(), ba1.size());
1154       QString ns = QString::fromUtf8(dbuf.buffer().data(), dbuf.buffer().size());
1155 
1156       diff_match_patch dmp;
1157       Revision* r = new Revision();
1158       r->setDiff(dmp.patch_toText(dmp.patch_make(ns, os)));
1159       r->setId("1");
1160       _revisions->add(r);
1161 
1162 //      qDebug("patch:\n%s\n==========", qPrintable(patch));
1163       //
1164 #endif
1165       }
1166 
1167 //---------------------------------------------------------
1168 //   writeVoiceMove
1169 //    write <move> and starting <voice> tags to denote
1170 //    change in position.
1171 //    Returns true if <voice> tag was written.
1172 //---------------------------------------------------------
1173 
writeVoiceMove(XmlWriter & xml,Segment * seg,const Fraction & startTick,int track,int * lastTrackWrittenPtr)1174 static bool writeVoiceMove(XmlWriter& xml, Segment* seg, const Fraction& startTick, int track, int* lastTrackWrittenPtr)
1175       {
1176       bool voiceTagWritten = false;
1177       int& lastTrackWritten = *lastTrackWrittenPtr;
1178       if ((lastTrackWritten < track) && !xml.clipboardmode()) {
1179             while (lastTrackWritten < (track - 1)) {
1180                   xml.tagE("voice");
1181                   ++lastTrackWritten;
1182                   }
1183             xml.stag("voice");
1184             xml.setCurTick(startTick);
1185             xml.setCurTrack(track);
1186             ++lastTrackWritten;
1187             voiceTagWritten = true;
1188             }
1189 
1190       if ((xml.curTick() != seg->tick()) || (track != xml.curTrack())) {
1191             Location curr = Location::absolute();
1192             Location dest = Location::absolute();
1193             curr.setFrac(xml.curTick());
1194             dest.setFrac(seg->tick());
1195             curr.setTrack(xml.curTrack());
1196             dest.setTrack(track);
1197 
1198             dest.toRelative(curr);
1199             dest.write(xml);
1200 
1201             xml.setCurTick(seg->tick());
1202             xml.setCurTrack(track);
1203             }
1204 
1205       return voiceTagWritten;
1206       }
1207 
1208 //---------------------------------------------------------
1209 //   writeSegments
1210 //    ls  - write upto this segment (excluding)
1211 //          can be zero
1212 //---------------------------------------------------------
1213 
writeSegments(XmlWriter & xml,int strack,int etrack,Segment * sseg,Segment * eseg,bool writeSystemElements,bool forceTimeSig)1214 void Score::writeSegments(XmlWriter& xml, int strack, int etrack,
1215    Segment* sseg, Segment* eseg, bool writeSystemElements, bool forceTimeSig)
1216       {
1217       Fraction startTick = xml.curTick();
1218       Fraction endTick   = eseg ? eseg->tick() : lastMeasure()->endTick();
1219       bool clip          = xml.clipboardmode();
1220 
1221       // in clipboard mode, ls might be in an mmrest
1222       // since we are traversing regular measures,
1223       // force them out of mmRest
1224       if (clip) {
1225             Measure* lm = eseg ? eseg->measure() : 0;
1226             Measure* fm = sseg ? sseg->measure() : 0;
1227             if (lm && lm->isMMRest()) {
1228                   lm = lm->mmRestLast();
1229                   if (lm)
1230                         eseg = lm->nextMeasure() ? lm->nextMeasure()->first() : nullptr;
1231                   else
1232                         qDebug("writeSegments: no measure for end segment in mmrest");
1233                   }
1234             if (fm && fm->isMMRest()) {
1235                   fm = fm->mmRestFirst();
1236                   if (fm)
1237                         sseg = fm->first();
1238                   }
1239             }
1240 
1241       QList<Spanner*> spanners;
1242 #if 0
1243       auto endIt   = spanner().upper_bound(endTick);
1244       for (auto i = spanner().begin(); i != endIt; ++i) {
1245             Spanner* s = i->second;
1246 #else
1247       auto sl = spannerMap().findOverlapping(sseg->tick().ticks(), endTick.ticks());
1248       for (auto i : sl) {
1249             Spanner* s = i.value;
1250 #endif
1251             if (s->generated() || !xml.canWrite(s))
1252                   continue;
1253             // don't write voltas to clipboard
1254             if (clip && s->isVolta())
1255                   continue;
1256             spanners.push_back(s);
1257             }
1258 
1259       int lastTrackWritten = strack - 1; // for counting necessary <voice> tags
1260       for (int track = strack; track < etrack; ++track) {
1261             if (!xml.canWriteVoice(track))
1262                   continue;
1263 
1264             bool voiceTagWritten = false;
1265 
1266             bool timeSigWritten = false; // for forceTimeSig
1267             bool crWritten = false;      // for forceTimeSig
1268             bool keySigWritten = false;  // for forceTimeSig
1269 
1270             for (Segment* segment = sseg; segment && segment != eseg; segment = segment->next1()) {
1271                   if (!segment->enabled())
1272                         continue;
1273                   if (track == 0)
1274                         segment->setWritten(false);
1275                   Element* e = segment->element(track);
1276 
1277                   //
1278                   // special case: - barline span > 1
1279                   //               - part (excerpt) staff starts after
1280                   //                 barline element
1281                   bool needMove = (segment->tick() != xml.curTick() || (track > lastTrackWritten));
1282                   if ((segment->isEndBarLineType()) && !e && writeSystemElements && ((track % VOICES) == 0)) {
1283                         // search barline:
1284                         for (int idx = track - VOICES; idx >= 0; idx -= VOICES) {
1285                               if (segment->element(idx)) {
1286                                     int oDiff = xml.trackDiff();
1287                                     xml.setTrackDiff(idx);          // staffIdx should be zero
1288                                     segment->element(idx)->write(xml);
1289                                     xml.setTrackDiff(oDiff);
1290                                     break;
1291                                     }
1292                               }
1293                         }
1294                   for (Element* e1 : segment->annotations()) {
1295                         if (e1->track() != track || e1->generated() || (e1->systemFlag() && !writeSystemElements))
1296                               continue;
1297                         if (needMove) {
1298                               voiceTagWritten |= writeVoiceMove(xml, segment, startTick, track, &lastTrackWritten);
1299                               needMove = false;
1300                               }
1301                         e1->write(xml);
1302                         }
1303                   Measure* m = segment->measure();
1304                   // don't write spanners for multi measure rests
1305 
1306                   if ((!(m && m->isMMRest())) && segment->isChordRestType()) {
1307                         for (Spanner* s : spanners) {
1308                               if (s->track() == track) {
1309                                     bool end = false;
1310                                     if (s->anchor() == Spanner::Anchor::CHORD || s->anchor() == Spanner::Anchor::NOTE)
1311                                           end = s->tick2() < endTick;
1312                                     else
1313                                           end = s->tick2() <= endTick;
1314                                     if (s->tick() == segment->tick() && (!clip || end) && !s->isSlur()) {
1315                                           if (needMove) {
1316                                                 voiceTagWritten |= writeVoiceMove(xml, segment, startTick, track, &lastTrackWritten);
1317                                                 needMove = false;
1318                                                 }
1319                                           s->writeSpannerStart(xml, segment, track);
1320                                           }
1321                                     }
1322                               if ((s->tick2() == segment->tick())
1323                                  && !s->isSlur()
1324                                  && (s->effectiveTrack2() == track)
1325                                  && (!clip || s->tick() >= sseg->tick())
1326                                  ) {
1327                                     if (needMove) {
1328                                           voiceTagWritten |= writeVoiceMove(xml, segment, startTick, track, &lastTrackWritten);
1329                                           needMove = false;
1330                                           }
1331                                     s->writeSpannerEnd(xml, segment, track);
1332                                     }
1333                               }
1334                         }
1335 
1336                   if (!e || !xml.canWrite(e))
1337                         continue;
1338                   if (e->generated())
1339                         continue;
1340                   if (forceTimeSig && track2voice(track) == 0 && segment->segmentType() == SegmentType::ChordRest && !timeSigWritten && !crWritten) {
1341                         // Ensure that <voice> tag is open
1342                         voiceTagWritten |= writeVoiceMove(xml, segment, startTick, track, &lastTrackWritten);
1343                         // we will miss a key sig!
1344                         if (!keySigWritten) {
1345                               Key k = score()->staff(track2staff(track))->key(segment->tick());
1346                               KeySig* ks = new KeySig(this);
1347                               ks->setKey(k);
1348                               ks->write(xml);
1349                               delete ks;
1350                               keySigWritten = true;
1351                               }
1352                         // we will miss a time sig!
1353                         Fraction tsf = sigmap()->timesig(segment->tick()).timesig();
1354                         TimeSig* ts = new TimeSig(this);
1355                         ts->setSig(tsf);
1356                         ts->write(xml);
1357                         delete ts;
1358                         timeSigWritten = true;
1359                         }
1360                   if (needMove) {
1361                         voiceTagWritten |= writeVoiceMove(xml, segment, startTick, track, &lastTrackWritten);
1362                         needMove = false;
1363                         }
1364                   if (e->isChordRest()) {
1365                         ChordRest* cr = toChordRest(e);
1366                         cr->writeTupletStart(xml);
1367                         }
1368 //                  if (segment->isEndBarLine() && (m->mmRestCount() < 0 || m->mmRest())) {
1369 //                        BarLine* bl = toBarLine(e);
1370 //TODO                        bl->setBarLineType(m->endBarLineType());
1371 //                        bl->setVisible(m->endBarLineVisible());
1372 //                        }
1373                   e->write(xml);
1374 
1375                   if (e->isChordRest()) {
1376                         ChordRest* cr = toChordRest(e);
1377                         cr->writeTupletEnd(xml);
1378                         }
1379 
1380                   if (!(e->isRest() && toRest(e)->isGap()))
1381                         segment->write(xml);    // write only once
1382                   if (forceTimeSig) {
1383                         if (segment->segmentType() == SegmentType::KeySig)
1384                               keySigWritten = true;
1385                         if (segment->segmentType() == SegmentType::TimeSig)
1386                               timeSigWritten = true;
1387                         if (segment->segmentType() == SegmentType::ChordRest)
1388                               crWritten = true;
1389                         }
1390                   }
1391 
1392             //write spanner ending after the last segment, on the last tick
1393             if (clip || eseg == 0) {
1394                   for (Spanner* s : spanners) {
1395                         if ((s->tick2() == endTick)
1396                           && !s->isSlur()
1397                           && (s->track2() == track || (s->track2() == -1 && s->track() == track))
1398                           && (!clip || s->tick() >= sseg->tick())
1399                           ) {
1400                               s->writeSpannerEnd(xml, lastMeasure(), track, endTick);
1401                               }
1402                         }
1403                   }
1404 
1405             if (voiceTagWritten)
1406                   xml.etag(); // </voice>
1407             }
1408       }
1409 
1410 //---------------------------------------------------------
1411 //   searchTuplet
1412 //    search complete Dom for tuplet id
1413 //    last resort in case of error
1414 //---------------------------------------------------------
1415 
1416 Tuplet* Score::searchTuplet(XmlReader& /*e*/, int /*id*/)
1417       {
1418       return 0;
1419       }
1420 
1421 }
1422 
1423