code: //=============================================================================
  //  MuseScore
  //  Music Composition & Notation
  //
  //  Copyright (C) 2002-2014 Werner Schweer
  //
  //  This program is free software; you can redistribute it and/or modify
  //  it under the terms of the GNU General Public License version 2
  //  as published by the Free Software Foundation and appearing in
  //  the file LICENCE.GPL
  //=============================================================================
  
  /**
   File handling: loading and saving.
   */
  
  #include <QFileInfo>
  
  #include "config.h"
  #include "globals.h"
  #include "musescore.h"
  #include "scoreview.h"
  #include "exportmidi.h"
  #include "libmscore/xml.h"
  #include "libmscore/element.h"
  #include "libmscore/note.h"
  #include "libmscore/rest.h"
  #include "libmscore/sig.h"
  #include "libmscore/clef.h"
  #include "libmscore/key.h"
  #include "instrdialog.h"
  #include "libmscore/score.h"
  #include "libmscore/page.h"
  #include "libmscore/dynamic.h"
  #include "file.h"
  #include "libmscore/style.h"
  #include "libmscore/tempo.h"
  #include "libmscore/select.h"
  #include "preferences.h"
  #include "playpanel.h"
  #include "libmscore/staff.h"
  #include "libmscore/part.h"
  #include "libmscore/utils.h"
  #include "libmscore/barline.h"
  #include "palette.h"
  #include "symboldialog.h"
  #include "libmscore/slur.h"
  #include "libmscore/hairpin.h"
  #include "libmscore/ottava.h"
  #include "libmscore/textline.h"
  #include "libmscore/pedal.h"
  #include "libmscore/trill.h"
  #include "libmscore/volta.h"
  #include "newwizard.h"
  #include "libmscore/timesig.h"
  #include "libmscore/box.h"
  #include "libmscore/excerpt.h"
  #include "libmscore/system.h"
  #include "libmscore/tuplet.h"
  #include "libmscore/keysig.h"
  #include "magbox.h"
  #include "libmscore/measure.h"
  #include "libmscore/undo.h"
  #include "libmscore/repeatlist.h"
  #include "scoretab.h"
  #include "libmscore/beam.h"
  #include "libmscore/stafftype.h"
  #include "seq.h"
  #include "libmscore/revisions.h"
  #include "libmscore/lyrics.h"
  #include "libmscore/segment.h"
  #include "libmscore/tempotext.h"
  #include "libmscore/sym.h"
  #include "libmscore/image.h"
  #include "libmscore/stafflines.h"
  #include "synthesizer/msynthesizer.h"
  #include "svggenerator.h"
  #include "scorePreview.h"
  #include "scorecmp/scorecmp.h"
  #include "extension.h"
  #include "tourhandler.h"
  
  #ifdef OMR
  #include "omr/omr.h"
  #include "omr/omrpage.h"
  #include "omr/importpdf.h"
  #endif
  
  #include "libmscore/chordlist.h"
  #include "libmscore/mscore.h"
  #include "thirdparty/qzip/qzipreader_p.h"
  
  
  namespace Ms {
  
  extern void importSoundfont(QString name);
  
  extern MasterSynthesizer* synti;
  
  //---------------------------------------------------------
  //   paintElement(s)
  //---------------------------------------------------------
  
  static void paintElement(QPainter& p, const Element* e)
        {
        QPointF pos(e->pagePos());
        p.translate(pos);
        e->draw(&p);
        p.translate(-pos);
        }
  
  static void paintElements(QPainter& p, const QList<Element*>& el)
        {
        for (Element* e : el) {
              if (!e->visible())
                    continue;
              paintElement(p, e);
              }
        }
  
  //---------------------------------------------------------
  //   createDefaultFileName
  //---------------------------------------------------------
  
  static QString createDefaultFileName(QString fn)
        {
        //
        // special characters in filenames are a constant source
        // of trouble, this replaces some of them common in german:
        //
        fn = fn.simplified();
        fn = fn.replace(QChar(' '),  "_");
        fn = fn.replace(QChar('\n'), "_");
        fn = fn.replace(QChar(0xe4), "ae"); // &auml;
        fn = fn.replace(QChar(0xf6), "oe"); // &ouml;
        fn = fn.replace(QChar(0xfc), "ue"); // &uuml;
        fn = fn.replace(QChar(0xdf), "ss"); // &szlig;
        fn = fn.replace(QChar(0xc4), "Ae"); // &Auml;
        fn = fn.replace(QChar(0xd6), "Oe"); // &Ouml;
        fn = fn.replace(QChar(0xdc), "Ue"); // &Uuml;
        fn = fn.replace(QChar(0x266d),"b"); // musical flat sign, happen in instrument names, so can happen in part (file) names
        fn = fn.replace(QChar(0x266f),"#"); // musical sharp sign, can happen in titles, so can happen in score (file) names
        fn = fn.replace( QRegExp( "[" + QRegExp::escape( "\\/:*?\"<>|" ) + "]" ), "_" ); //FAT/NTFS special chars
        return fn;
        }
  
  QString MuseScore::saveFilename(QString fn)
        {
        return createDefaultFileName(fn);
        }
  
  //---------------------------------------------------------
  //   readScoreError
  //    if "ask" is true, ask to ignore; returns true if
  //    ignore is pressed by user
  //    returns true if -f is used in converter mode
  //---------------------------------------------------------
  
  static bool readScoreError(const QString& name, Score::FileError error, bool ask)
        {
  //printf("<%s> %d\n", qPrintable(name), int(error));
        QString msg = QObject::tr("Cannot read file %1:\n").arg(name);
        QString detailedMsg;
        bool canIgnore = false;
        switch(error) {
              case Score::FileError::FILE_NO_ERROR:
                    return false;
              case Score::FileError::FILE_BAD_FORMAT:
                    msg +=  QObject::tr("bad format");
                    detailedMsg = MScore::lastError;
                    break;
              case Score::FileError::FILE_UNKNOWN_TYPE:
                    msg += QObject::tr("unknown type");
                    break;
              case Score::FileError::FILE_NO_ROOTFILE:
                    break;
              case Score::FileError::FILE_TOO_OLD:
                    msg += QObject::tr("It was last saved with a version older than 2.0.0.\n"
                                       "You can convert this score by opening and then\n"
                                       "saving with MuseScore version 2.x.\n"
                                       "Visit the %1MuseScore download page%2 to obtain such a 2.x version.")
                                .arg("<a href=\"https://musescore.org/download#older-versions\">")
                                .arg("</a>");
                    canIgnore = true;
                    break;
              case Score::FileError::FILE_TOO_NEW:
                    msg += QObject::tr("This score was saved using a newer version of MuseScore.\n"
                                       "Visit the %1MuseScore website%2 to obtain the latest version.")
                                .arg("<a href=\"https://musescore.org\">")
                                .arg("</a>");
                    canIgnore = true;
                    break;
              case Score::FileError::FILE_NOT_FOUND:
                    msg = QObject::tr("File \"%1\" not found.").arg(name);
                    break;
              case Score::FileError::FILE_CORRUPTED:
                    msg = QObject::tr("File \"%1\" corrupted.").arg(name);
                    detailedMsg = MScore::lastError;
                    canIgnore = true;
                    break;
              case Score::FileError::FILE_OLD_300_FORMAT:
                    msg += QObject::tr("It was last saved with a developer version of 3.0.\n");
                    canIgnore = true;
                    break;
              case Score::FileError::FILE_ERROR:
              case Score::FileError::FILE_OPEN_ERROR:
              default:
                    msg += MScore::lastError;
                    break;
              }
        if (converterMode && canIgnore && ignoreWarnings) {
              fprintf(stderr, "%s\n\nWarning ignored, forcing score to load\n", qPrintable(msg));
              return true;
              }
         if (converterMode || pluginMode) {
              fprintf(stderr, "%s\n", qPrintable(msg));
              return false;
              }
        QMessageBox msgBox;
        msgBox.setWindowTitle(QObject::tr("Load Error"));
        msgBox.setText(msg.replace("\n", "<br/>"));
        msgBox.setDetailedText(detailedMsg);
        msgBox.setTextFormat(Qt::RichText);
        if (canIgnore && ask)  {
              msgBox.setIcon(QMessageBox::Warning);
              msgBox.setStandardButtons(
                 QMessageBox::Cancel | QMessageBox::Ignore
                 );
              return msgBox.exec() == QMessageBox::Ignore;
              }
        else {
              msgBox.setIcon(QMessageBox::Critical);
              msgBox.setStandardButtons(
                 QMessageBox::Ok
                 );
              msgBox.exec();
              }
        return false;
        }
  
  //---------------------------------------------------------
  //   checkDirty
  //    if dirty, save score
  //    return true on cancel
  //---------------------------------------------------------
  
  bool MuseScore::checkDirty(MasterScore* s)
        {
        if (s->dirty() || s->created()) {
              QMessageBox::StandardButton n = QMessageBox::warning(this, tr("MuseScore"),
                 tr("Save changes to the score \"%1\"\n"
                 "before closing?").arg(s->fileInfo()->completeBaseName()),
                 QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel,
                 QMessageBox::Save);
              if (n == QMessageBox::Save) {
                    if (s->masterScore()->isSavable()) {
                          if (!saveFile(s))
                                return true;
                          }
                    else {
                          if (!saveAs(s, false))
                                return true;
                          }
  
                    }
              else if (n == QMessageBox::Cancel)
                    return true;
              }
        return false;
        }
  
  //---------------------------------------------------------
  //   openFiles
  //---------------------------------------------------------
  
  void MuseScore::openFiles(bool switchTab, bool singleFile)
        {
        QString allExt = "*.mscz *.mscx *.mxl *.musicxml *.xml *.mid *.midi *.kar *.md *.mgu *.sgu *.cap *.capx *.ove *.scw *.bww *.gtp *.gp3 *.gp4 *.gp5 *.gpx *.ptb";
  #ifdef AVSOMR
        allExt += " *.msmr"; // omr project with omr data and musicxml or score
  #endif
  
        QStringList filter;
        filter << tr("All Supported Files") + " (" + allExt + ")"
               << tr("MuseScore Files") + " (*.mscz *.mscx)"
               << tr("MusicXML Files") + " (*.mxl *.musicxml *.xml)"
               << tr("MIDI Files") + " (*.mid *.midi *.kar)"
               << tr("MuseData Files") + " (*.md)"
               << tr("Capella Files") + " (*.cap *.capx)"
               << tr("BB Files (experimental)") + " (*.mgu *.sgu)"
               << tr("Overture / Score Writer Files (experimental)") + " (*.ove *.scw)"
               << tr("Bagpipe Music Writer Files (experimental)") + " (*.bww)"
               << tr("Guitar Pro Files") + " (*.gtp *.gp3 *.gp4 *.gp5 *.gpx)"
               << tr("Power Tab Editor Files (experimental)") + " (*.ptb)";
  
        doLoadFiles(filter, switchTab, singleFile);
        }
  
  //---------------------------------------------------------
  //   importFiles
  //---------------------------------------------------------
  
  void MuseScore::importScore(bool switchTab, bool singleFile)
        {
  #ifndef AVSOMR
        Q_UNUSED(switchTab);
        Q_UNUSED(singleFile);
        openExternalLink("https://musescore.com/import");
  #else
        QStringList filter;
        filter << tr("Optical Music Recognition") + " (*.pdf *.png *.jpg)";
        doLoadFiles(filter, switchTab, singleFile);
  #endif
        }
  
  //---------------------------------------------------------
  //   doLoadFiles
  //---------------------------------------------------------
  
  /**
   Create a modal file open dialog.
   If a file is selected, load it.
   Handles the GUI's file-open action.
   */
  
  void MuseScore::doLoadFiles(const QStringList& filter, bool switchTab, bool singleFile)
        {
        QString filterStr = filter.join(";;");
        QStringList files = getOpenScoreNames(filterStr, tr("Load Score"), singleFile);
        for (const QString& s : files)
              openScore(s, switchTab);
        mscore->tourHandler()->showDelayedWelcomeTour();
        }
  
  //---------------------------------------------------------
  //   openScore
  //---------------------------------------------------------
  
  Score* MuseScore::openScore(const QString& fn, bool switchTab)
        {
        //
        // make sure we load a file only once
        //
        QFileInfo fi(fn);
        QString path = fi.canonicalFilePath();
        for (Score* s : scoreList) {
              if (s->masterScore()->fileInfo()->canonicalFilePath() == path) {
                    if (switchTab)
                          setCurrentScoreView(scoreList.indexOf(s->masterScore()));
                    return 0;
                    }
              }
  
        MasterScore* score = readScore(fn);
        if (score) {
              score->updateCapo();
              const int tabIdx = appendScore(score);
              if (switchTab)
                    setCurrentScoreView(tabIdx);
              writeSessionFile(false);
              }
        return score;
        }
  
  //---------------------------------------------------------
  //   readScore
  //---------------------------------------------------------
  
  MasterScore* MuseScore::readScore(const QString& name)
        {
        if (name.isEmpty())
              return 0;
  
        MasterScore* score = new MasterScore(MScore::baseStyle());
        setMidiReopenInProgress(name);
        Score::FileError rv = Ms::readScore(score, name, false);
        if (rv == Score::FileError::FILE_TOO_OLD || rv == Score::FileError::FILE_TOO_NEW || rv == Score::FileError::FILE_CORRUPTED) {
              if (readScoreError(name, rv, true)) {
                    if (rv != Score::FileError::FILE_CORRUPTED) {
                          // don’t read file again if corrupted
                          // the check routine may try to fix it
                          delete score;
                          score = new MasterScore();
                          score->setMovements(new Movements());
                          score->setStyle(MScore::baseStyle());
                          rv = Ms::readScore(score, name, true);
                          }
                    else
                          rv = Score::FileError::FILE_NO_ERROR;
                    }
              else {
                    delete score;
                    return 0;
                    }
              }
        if (rv != Score::FileError::FILE_NO_ERROR) {
              // in case of user abort while reading, the error has already been reported
              // else report it now
              if (rv != Score::FileError::FILE_USER_ABORT && rv != Score::FileError::FILE_IGNORE_ERROR)
                    readScoreError(name, rv, false);
              delete score;
              score = 0;
              return 0;
              }
        allowShowMidiPanel(name);
        if (score)
              addRecentScore(score);
  
        return score;
        }
  
  //---------------------------------------------------------
  //   saveFile
  ///   Save the current score.
  ///   Handles the GUI's file-save action.
  //
  //    return true on success
  //---------------------------------------------------------
  
  bool MuseScore::saveFile()
        {
        return saveFile(cs->masterScore());
        }
  
  //---------------------------------------------------------
  //   saveFile
  ///   Save the score.
  //
  //    return true on success
  //---------------------------------------------------------
  
  bool MuseScore::saveFile(MasterScore* score)
        {
        if (score == 0)
              return false;
        if (score->created()) {
              QString fn = score->masterScore()->fileInfo()->fileName();
              Text* t = score->getText(Tid::TITLE);
              if (t)
                    fn = t->plainText();
              QString name = createDefaultFileName(fn);
              QString msczType = tr("MuseScore 3 File") + " (*.mscz)";
              QString mscxType = tr("Uncompressed MuseScore 3 File") + " (*.mscx)";     // for debugging purposes
  
              QSettings set;
              if (mscore->lastSaveDirectory.isEmpty())
                    mscore->lastSaveDirectory = set.value("lastSaveDirectory", preferences.getString(PREF_APP_PATHS_MYSCORES)).toString();
              QString saveDirectory = mscore->lastSaveDirectory;
  
              if (saveDirectory.isEmpty())
                    saveDirectory = preferences.getString(PREF_APP_PATHS_MYSCORES);
  
              QString fname = QString("%1/%2").arg(saveDirectory).arg(name);
              QString filter;
  #ifdef AVSOMR
              if (score->avsOmr()) {
                    QString msmrType = tr("Music Recognition MuseScore 3 File") + " (*.msmr)";
                    fname = QFileInfo(fname).baseName() + ".msmr";
                    filter = msmrType + ";;" + mscxType + ";;" + msczType;
                    }
              else
  #endif
              if (QFileInfo(fname).suffix().isEmpty()) {
                    fname += ".mscz";
                    filter = msczType + ";;" + mscxType;
                    }
  
              fn = mscore->getSaveScoreName(tr("Save Score"), fname, filter);
              if (fn.isEmpty())
                    return false;
              score->masterScore()->fileInfo()->setFile(fn);
  
              mscore->lastSaveDirectory = score->masterScore()->fileInfo()->absolutePath();
  
              if (!score->masterScore()->saveFile(preferences.getBool(PREF_APP_BACKUP_GENERATE_BACKUP))) {
                    QMessageBox::critical(mscore, tr("Save File"), MScore::lastError);
                    return false;
                    }
              addRecentScore(score);
              writeSessionFile(false);
              }
        else if (!score->masterScore()->saveFile(preferences.getBool(PREF_APP_BACKUP_GENERATE_BACKUP))) {
              QMessageBox::critical(mscore, tr("Save File"), MScore::lastError);
              return false;
              }
        score->setCreated(false);
        updateWindowTitle(score);
        scoreCmpTool->updateScoreVersions(score);
        int idx = scoreList.indexOf(score->masterScore());
        tab1->setTabText(idx, score->fileInfo()->completeBaseName());
        if (tab2)
              tab2->setTabText(idx, score->fileInfo()->completeBaseName());
        QString tmp = score->tmpName();
        if (!tmp.isEmpty()) {
              QFile f(tmp);
              if (!f.remove())
                    qDebug("cannot remove temporary file <%s>", qPrintable(f.fileName()));
              score->setTmpName("");
              }
        writeSessionFile(false);
        return true;
        }
  
  //---------------------------------------------------------
  //   createDefaultName
  //---------------------------------------------------------
  
  QString MuseScore::createDefaultName() const
        {
        QString name(tr("Untitled"));
        int n;
        for (n = 1; ; ++n) {
              bool nameExists = false;
              QString tmpName;
              if (n == 1)
                    tmpName = name;
              else
                    tmpName = QString("%1-%2").arg(name).arg(n);
              for (MasterScore* s : scoreList) {
                    if (s->fileInfo()->completeBaseName() == tmpName) {
                          nameExists = true;
                          break;
                          }
                    }
              if (!nameExists) {
                    name = tmpName;
                    break;
                    }
              }
        return name;
        }
  
  //---------------------------------------------------------
  //   getNewFile
  //    create new score
  //---------------------------------------------------------
  
  MasterScore* MuseScore::getNewFile()
        {
        if (!newWizard)
              newWizard = new NewWizard(this);
        else {
              newWizard->updateValues();
              newWizard->restart();
              }
        if (newWizard->exec() != QDialog::Accepted)
              return 0;
        int measures            = newWizard->measures();
        Fraction timesig        = newWizard->timesig();
        TimeSigType timesigType = newWizard->timesigType();
        KeySigEvent ks          = newWizard->keysig();
        VBox* nvb               = nullptr;
  
        int pickupTimesigZ;
        int pickupTimesigN;
        bool pickupMeasure = newWizard->pickupMeasure(&pickupTimesigZ, &pickupTimesigN);
        if (pickupMeasure)
              measures += 1;
  
        MasterScore* score = new MasterScore(MScore::defaultStyle());
        QString tp         = newWizard->templatePath();
  
        QList<Excerpt*> excerpts;
        if (!newWizard->emptyScore()) {
              MasterScore* tscore = new MasterScore(MScore::defaultStyle());
              Score::FileError rv = Ms::readScore(tscore, tp, false);
              if (rv != Score::FileError::FILE_NO_ERROR) {
                    readScoreError(newWizard->templatePath(), rv, false);
                    delete tscore;
                    delete score;
                    return 0;
                    }
              score->setStyle(tscore->style());
  
              // create instruments from template
              for (Part* tpart : tscore->parts()) {
                    Part* part = new Part(score);
                    part->setInstrument(tpart->instrument());
                    part->setPartName(tpart->partName());
  
                    for (Staff* tstaff : *tpart->staves()) {
                          Staff* staff = new Staff(score);
                          staff->setPart(part);
                          staff->init(tstaff);
                          if (tstaff->links() && !part->staves()->isEmpty()) {
                                Staff* linkedStaff = part->staves()->back();
                                staff->linkTo(linkedStaff);
                                }
                          part->insertStaff(staff, -1);
                          score->staves().append(staff);
                          }
                    score->appendPart(part);
                    }
              for (Excerpt* ex : tscore->excerpts()) {
                    Excerpt* x = new Excerpt(score);
                    x->setTitle(ex->title());
                    for (Part* p : ex->parts()) {
                          int pidx = tscore->parts().indexOf(p);
                          if (pidx == -1)
                                qDebug("newFile: part not found");
                          else
                                x->parts().append(score->parts()[pidx]);
                          }
                    excerpts.append(x);
                    }
              MeasureBase* mb = tscore->first();
              if (mb && mb->isVBox()) {
                    VBox* tvb = toVBox(mb);
                    nvb = new VBox(score);
                    nvb->setBoxHeight(tvb->boxHeight());
                    nvb->setBoxWidth(tvb->boxWidth());
                    nvb->setTopGap(tvb->topGap());
                    nvb->setBottomGap(tvb->bottomGap());
                    nvb->setTopMargin(tvb->topMargin());
                    nvb->setBottomMargin(tvb->bottomMargin());
                    nvb->setLeftMargin(tvb->leftMargin());
                    nvb->setRightMargin(tvb->rightMargin());
                    }
              delete tscore;
              }
        else {
              score = new MasterScore(MScore::defaultStyle());
              newWizard->createInstruments(score);
              }
        score->setCreated(true);
        score->fileInfo()->setFile(createDefaultName());
  
        score->style().checkChordList();
        if (!newWizard->title().isEmpty())
              score->fileInfo()->setFile(newWizard->title());
  
        score->sigmap()->add(0, timesig);
  
        Fraction firstMeasureTicks = pickupMeasure ? Fraction(pickupTimesigZ, pickupTimesigN) : timesig;
  
        for (int i = 0; i < measures; ++i) {
              Fraction tick = firstMeasureTicks + timesig * (i - 1);
              if (i == 0)
                    tick = Fraction(0,1);
              QList<Rest*> puRests;
              for (Score* _score : score->scoreList()) {
                    Rest* rest = 0;
                    Measure* measure = new Measure(_score);
                    measure->setTimesig(timesig);
                    measure->setTicks(timesig);
                    measure->setTick(tick);
  
                    if (pickupMeasure && tick.isZero()) {
                          measure->setIrregular(true);        // don’t count pickup measure
                          measure->setTicks(Fraction(pickupTimesigZ, pickupTimesigN));
                          }
                    _score->measures()->add(measure);
  
                    for (Staff* staff : _score->staves()) {
                          int staffIdx = staff->idx();
                          if (tick.isZero()) {
                                TimeSig* ts = new TimeSig(_score);
                                ts->setTrack(staffIdx * VOICES);
                                ts->setSig(timesig, timesigType);
                                Measure* m = _score->firstMeasure();
                                Segment* s = m->getSegment(SegmentType::TimeSig, Fraction(0,1));
                                s->add(ts);
                                Part* part = staff->part();
                                if (!part->instrument()->useDrumset()) {
                                      //
                                      // transpose key
                                      //
                                      KeySigEvent nKey = ks;
                                      if (!nKey.custom() && !nKey.isAtonal() && part->instrument()->transpose().chromatic && !score->styleB(Sid::concertPitch)) {
                                            int diff = -part->instrument()->transpose().chromatic;
                                            nKey.setKey(transposeKey(nKey.key(), diff, part->preferSharpFlat()));
                                            }
                                      // do not create empty keysig unless custom or atonal
                                      if (nKey.custom() || nKey.isAtonal() || nKey.key() != Key::C) {
                                            staff->setKey(Fraction(0,1), nKey);
                                            KeySig* keysig = new KeySig(score);
                                            keysig->setTrack(staffIdx * VOICES);
                                            keysig->setKeySigEvent(nKey);
                                            Segment* ss = measure->getSegment(SegmentType::KeySig, Fraction(0,1));
                                            ss->add(keysig);
                                            }
                                      }
                                }
  
                          // determined if this staff is linked to previous so we can reuse rests
                          bool linkedToPrevious = staffIdx && staff->isLinked(_score->staff(staffIdx - 1));
                          if (measure->timesig() != measure->ticks()) {
                                if (!linkedToPrevious)
                                      puRests.clear();
                                std::vector<TDuration> dList = toDurationList(measure->ticks(), false);
                                if (!dList.empty()) {
                                      Fraction ltick = tick;
                                      int k = 0;
                                      foreach (TDuration d, dList) {
                                            if (k < puRests.count())
                                                  rest = static_cast<Rest*>(puRests[k]->linkedClone());
                                            else {
                                                  rest = new Rest(score, d);
                                                  puRests.append(rest);
                                                  }
                                            rest->setScore(_score);
                                            rest->setTicks(d.fraction());
                                            rest->setTrack(staffIdx * VOICES);
                                            Segment* seg = measure->getSegment(SegmentType::ChordRest, ltick);
                                            seg->add(rest);
                                            ltick += rest->actualTicks();
                                            k++;
                                            }
                                      }
                                }
                          else {
                                if (linkedToPrevious && rest)
                                      rest = static_cast<Rest*>(rest->linkedClone());
                                else
                                      rest = new Rest(score, TDuration(TDuration::DurationType::V_MEASURE));
                                rest->setScore(_score);
                                rest->setTicks(measure->ticks());
                                rest->setTrack(staffIdx * VOICES);
                                Segment* seg = measure->getSegment(SegmentType::ChordRest, tick);
                                seg->add(rest);
                                }
                          }
                    }
              }
  //TODO      score->lastMeasure()->setEndBarLineType(BarLineType::END, false);
  
        //
        // select first rest
        //
        Measure* m = score->firstMeasure();
        for (Segment* s = m->first(); s; s = s->next()) {
              if (s->segmentType() == SegmentType::ChordRest) {
                    if (s->element(0)) {
                          score->select(s->element(0), SelectType::SINGLE, 0);
                          break;
                          }
                    }
              }
  
        QString title     = newWizard->title();
        QString subtitle  = newWizard->subtitle();
        QString composer  = newWizard->composer();
        QString poet      = newWizard->poet();
        QString copyright = newWizard->copyright();
  
        if (!title.isEmpty() || !subtitle.isEmpty() || !composer.isEmpty() || !poet.isEmpty()) {
              MeasureBase* measure = score->measures()->first();
              if (measure->type() != ElementType::VBOX) {
                    MeasureBase* nm = nvb ? nvb : new VBox(score);
                    nm->setTick(Fraction(0,1));
                    nm->setNext(measure);
                    score->measures()->add(nm);
                    measure = nm;
                    }
              else if (nvb) {
                    delete nvb;
                    }
              if (!title.isEmpty()) {
                    Text* s = new Text(score, Tid::TITLE);
                    s->setPlainText(title);
                    measure->add(s);
                    score->setMetaTag("workTitle", title);
                    }
              if (!subtitle.isEmpty()) {
                    Text* s = new Text(score, Tid::SUBTITLE);
                    s->setPlainText(subtitle);
                    measure->add(s);
                    }
              if (!composer.isEmpty()) {
                    Text* s = new Text(score, Tid::COMPOSER);
                    s->setPlainText(composer);
                    measure->add(s);
                    score->setMetaTag("composer", composer);
                    }
              if (!poet.isEmpty()) {
                    Text* s = new Text(score, Tid::POET);
                    s->setPlainText(poet);
                    measure->add(s);
                    // the poet() functions returns data called lyricist in the dialog
                    score->setMetaTag("lyricist", poet);
                    }
              }
        else if (nvb) {
              delete nvb;
              }
  
        if (newWizard->createTempo()) {
              double tempo = newWizard->tempo();
              TempoText* tt = new TempoText(score);
              tt->setXmlText(QString("<sym>metNoteQuarterUp</sym> = %1").arg(tempo));
              tempo /= 60;      // bpm -> bps
  
              tt->setTempo(tempo);
              tt->setFollowText(true);
              tt->setTrack(0);
              Segment* seg = score->firstMeasure()->first(SegmentType::ChordRest);
              seg->add(tt);
              score->setTempo(seg, tempo);
              }
        if (!copyright.isEmpty())
              score->setMetaTag("copyright", copyright);
  
        if (synti)
              score->setSynthesizerState(synti->state());
  
        // Call this even if synti doesn't exist - we need to rebuild either way
        score->rebuildAndUpdateExpressive(MuseScore::synthesizer("Fluid"));
  
        {
              ScoreLoad sl;
              score->doLayout();
              }
  
        for (Excerpt* x : excerpts) {
              Score* xs = new Score(static_cast<MasterScore*>(score));
              xs->style().set(Sid::createMultiMeasureRests, true);
              x->setPartScore(xs);
              xs->setExcerpt(x);
              score->excerpts().append(x);
              Excerpt::createExcerpt(x);
              }
        score->setExcerptsChanged(true);
        return score;
        }
  
  //---------------------------------------------------------
  //   newFile
  //    create new score
  //---------------------------------------------------------
  
  void MuseScore::newFile()
        {
        MasterScore* score = getNewFile();
        if (score)
              setCurrentScoreView(appendScore(score));
        mscore->tourHandler()->showDelayedWelcomeTour();
        }
  
  //---------------------------------------------------------
  //   copy
  //    Copy content of src file do dest file overwriting it.
  //    Implemented manually as QFile::copy refuses to
  //    overwrite existing files.
  //---------------------------------------------------------
  
  static bool copy(QFile& src, QFile& dest)
        {
        src.open(QIODevice::ReadOnly);
        dest.open(QIODevice::WriteOnly);
        constexpr qint64 size = 1024 * 1024;
        char* buf = new char[size];
        bool err = false;
        while (qint64 r = src.read(buf, size)) {
              if (r < 0) {
                    err = true;
                    break;
                    }
              qint64 w = dest.write(buf, r);
              if (w < r) {
                    err = true;
                    break;
                    }
              }
        dest.close();
        src.close();
        delete[] buf;
        return !err;
        }
  
  //---------------------------------------------------------
  //   getTemporaryScoreFileCopy
  //    baseNameTemplate is the template to be passed to
  //    QTemporaryFile constructor but without suffix and
  //    directory --- they are defined automatically.
  //---------------------------------------------------------
  
  QTemporaryFile* MuseScore::getTemporaryScoreFileCopy(const QFileInfo& info, const QString& baseNameTemplate)
        {
        QString suffix(info.suffix());
        if (suffix.endsWith(",")) // some backup files created by MuseScore
              suffix.chop(1);
        QTemporaryFile* f = new QTemporaryFile(
           QDir::temp().absoluteFilePath(baseNameTemplate + '.' + suffix),
           this
           );
        QFile src(info.absoluteFilePath());
        if (!copy(src, *f)) {
              delete f;
              return nullptr;
              }
        return f;
        }
  
  //---------------------------------------------------------
  //   addScorePreview
  //    add a score preview to the file dialog
  //---------------------------------------------------------
  
  static void addScorePreview(QFileDialog* dialog)
        {
        QSplitter* splitter = dialog->findChild<QSplitter*>("splitter");
        if (splitter) {
              ScorePreview* preview = new ScorePreview;
              splitter->addWidget(preview);
              dialog->connect(dialog, SIGNAL(currentChanged(const QString&)), preview, SLOT(setScore(const QString&)));
              }
        }
  
  //---------------------------------------------------------
  //   sidebarUrls
  //    return a list of standard file dialog sidebar urls
  //---------------------------------------------------------
  
  static QList<QUrl> sidebarUrls()
        {
        QList<QUrl> urls;
        urls.append(QUrl::fromLocalFile(QDir::homePath()));
        QFileInfo myScores(preferences.getString(PREF_APP_PATHS_MYSCORES));
        urls.append(QUrl::fromLocalFile(myScores.absoluteFilePath()));
        urls.append(QUrl::fromLocalFile(QDir::currentPath()));
        return urls;
        }
  
  //---------------------------------------------------------
  //   getOpenScoreNames
  //---------------------------------------------------------
  
  QStringList MuseScore::getOpenScoreNames(const QString& filter, const QString& title, bool singleFile)
        {
        QSettings set;
        QString dir = set.value("lastOpenPath", preferences.getString(PREF_APP_PATHS_MYSCORES)).toString();
        if (preferences.getBool(PREF_UI_APP_USENATIVEDIALOGS)) {
              QStringList fileList = QFileDialog::getOpenFileNames(this,
                 title, dir, filter);
              if (fileList.count() > 0) {
                    QFileInfo fi(fileList[0]);
                    set.setValue("lastOpenPath", fi.absolutePath());
                    }
              return fileList;
              }
        QFileInfo myScores(preferences.getString(PREF_APP_PATHS_MYSCORES));
        if (myScores.isRelative())
              myScores.setFile(QDir::home(), preferences.getString(PREF_APP_PATHS_MYSCORES));
  
        if (loadScoreDialog == 0) {
              loadScoreDialog = new QFileDialog(this);
              loadScoreDialog->setFileMode(singleFile ? QFileDialog::ExistingFile : QFileDialog::ExistingFiles);
              loadScoreDialog->setOption(QFileDialog::DontUseNativeDialog, true);
              loadScoreDialog->setWindowTitle(title);
              addScorePreview(loadScoreDialog);
  
              loadScoreDialog->setNameFilter(filter);
              restoreDialogState("loadScoreDialog", loadScoreDialog);
              loadScoreDialog->setAcceptMode(QFileDialog::AcceptOpen);
              loadScoreDialog->setDirectory(dir);
              }
        else {
              // dialog already exists, but set title and filter
              loadScoreDialog->setWindowTitle(title);
              loadScoreDialog->setNameFilter(filter);
              }
        // setup side bar urls
        QList<QUrl> urls = sidebarUrls();
        urls.append(QUrl::fromLocalFile(mscoreGlobalShare+"/demos"));
        loadScoreDialog->setSidebarUrls(urls);
  
        QStringList result;
        if (loadScoreDialog->exec())
              result = loadScoreDialog->selectedFiles();
        set.setValue("lastOpenPath", loadScoreDialog->directory().absolutePath());
        return result;
        }
  
  //---------------------------------------------------------
  //   getSaveScoreName
  //---------------------------------------------------------
  
  QString MuseScore::getSaveScoreName(const QString& title, QString& name, const QString& filter, bool selectFolder)
        {
        QFileInfo myName(name);
        if (myName.isRelative())
              myName.setFile(QDir::home(), name);
        name = myName.absoluteFilePath();
  
        if (preferences.getBool(PREF_UI_APP_USENATIVEDIALOGS)) {
              QString s;
              QFileDialog::Options options = selectFolder ? QFileDialog::ShowDirsOnly : QFileDialog::Options(0);
              return QFileDialog::getSaveFileName(this, title, name, filter, &s, options);
              }
  
        QFileInfo myScores(preferences.getString(PREF_APP_PATHS_MYSCORES));
        if (myScores.isRelative())
              myScores.setFile(QDir::home(), preferences.getString(PREF_APP_PATHS_MYSCORES));
        if (saveScoreDialog == 0) {
              saveScoreDialog = new QFileDialog(this);
              saveScoreDialog->setFileMode(QFileDialog::AnyFile);
              saveScoreDialog->setOption(QFileDialog::DontConfirmOverwrite, false);
              saveScoreDialog->setOption(QFileDialog::DontUseNativeDialog, true);
              saveScoreDialog->setAcceptMode(QFileDialog::AcceptSave);
              addScorePreview(saveScoreDialog);
  
              restoreDialogState("saveScoreDialog", saveScoreDialog);
              }
        // setup side bar urls
        saveScoreDialog->setSidebarUrls(sidebarUrls());
  
        if (selectFolder)
              saveScoreDialog->setFileMode(QFileDialog::Directory);
  
        saveScoreDialog->setWindowTitle(title);
        saveScoreDialog->setNameFilter(filter);
        saveScoreDialog->selectFile(name);
  
        if (!selectFolder) {
              connect(saveScoreDialog, SIGNAL(filterSelected(const QString&)),
                 SLOT(saveScoreDialogFilterSelected(const QString&)));
              }
        QString s;
        if (saveScoreDialog->exec())
              s = saveScoreDialog->selectedFiles().front();
        return s;
        }
  
  //---------------------------------------------------------
  //   saveScoreDialogFilterSelected
  //    update selected file name extensions, when filter
  //    has changed
  //---------------------------------------------------------
  
  void MuseScore::saveScoreDialogFilterSelected(const QString& s)
        {
        QRegExp rx(QString(".+\\(\\*\\.(.+)\\)"));
        if (rx.exactMatch(s)) {
              QFileInfo fi(saveScoreDialog->selectedFiles().front());
              saveScoreDialog->selectFile(fi.completeBaseName() + "." + rx.cap(1));
              }
        }
  
  //---------------------------------------------------------
  //   getStyleFilename
  //---------------------------------------------------------
  
  QString MuseScore::getStyleFilename(bool open, const QString& title)
        {
        QFileInfo myStyles(preferences.getString(PREF_APP_PATHS_MYSTYLES));
        if (myStyles.isRelative())
              myStyles.setFile(QDir::home(), preferences.getString(PREF_APP_PATHS_MYSTYLES));
        QString defaultPath = myStyles.absoluteFilePath();
  
        if (preferences.getBool(PREF_UI_APP_USENATIVEDIALOGS)) {
              QString fn;
              if (open) {
                    fn = QFileDialog::getOpenFileName(
                       this, tr("Load Style"),
                       defaultPath,
                       tr("MuseScore Styles") + " (*.mss)"
                       );
                    }
              else {
                    fn = QFileDialog::getSaveFileName(
                       this, tr("Save Style"),
                       defaultPath,
                       tr("MuseScore Style File") + " (*.mss)"
                       );
                    }
              return fn;
              }
  
        QFileDialog* dialog;
        QList<QUrl> urls;
        QString home = QDir::homePath();
        urls.append(QUrl::fromLocalFile(home));
        urls.append(QUrl::fromLocalFile(defaultPath));
        urls.append(QUrl::fromLocalFile(QDir::currentPath()));
  
        if (open) {
              if (loadStyleDialog == 0) {
                    loadStyleDialog = new QFileDialog(this);
                    loadStyleDialog->setFileMode(QFileDialog::ExistingFile);
                    loadStyleDialog->setOption(QFileDialog::DontUseNativeDialog, true);
                    loadStyleDialog->setWindowTitle(title.isEmpty() ? tr("Load Style") : title);
                    loadStyleDialog->setNameFilter(tr("MuseScore Style File") + " (*.mss)");
                    loadStyleDialog->setDirectory(defaultPath);
  
                    restoreDialogState("loadStyleDialog", loadStyleDialog);
                    loadStyleDialog->setAcceptMode(QFileDialog::AcceptOpen);
                    }
              urls.append(QUrl::fromLocalFile(mscoreGlobalShare+"/styles"));
              dialog = loadStyleDialog;
              }
        else {
              if (saveStyleDialog == 0) {
                    saveStyleDialog = new QFileDialog(this);
                    saveStyleDialog->setAcceptMode(QFileDialog::AcceptSave);
                    saveStyleDialog->setFileMode(QFileDialog::AnyFile);
                    saveStyleDialog->setOption(QFileDialog::DontConfirmOverwrite, false);
                    saveStyleDialog->setOption(QFileDialog::DontUseNativeDialog, true);
                    saveStyleDialog->setWindowTitle(title.isEmpty() ? tr("Save Style") : title);
                    saveStyleDialog->setNameFilter(tr("MuseScore Style File") + " (*.mss)");
                    saveStyleDialog->setDirectory(defaultPath);
  
                    restoreDialogState("saveStyleDialog", saveStyleDialog);
                    saveStyleDialog->setAcceptMode(QFileDialog::AcceptSave);
                    }
              dialog = saveStyleDialog;
              }
        // setup side bar urls
        dialog->setSidebarUrls(urls);
  
        if (dialog->exec()) {
              QStringList result = dialog->selectedFiles();
              return result.front();
              }
        return QString();
        }
  
  //---------------------------------------------------------
  //   getChordStyleFilename
  //---------------------------------------------------------
  
  QString MuseScore::getChordStyleFilename(bool open)
        {
        QString filter = tr("Chord Symbols Style File") + " (*.xml)";
  
        QFileInfo myStyles(preferences.getString(PREF_APP_PATHS_MYSTYLES));
        if (myStyles.isRelative())
              myStyles.setFile(QDir::home(), preferences.getString(PREF_APP_PATHS_MYSTYLES));
        QString defaultPath = myStyles.absoluteFilePath();
  
        if (preferences.getBool(PREF_UI_APP_USENATIVEDIALOGS)) {
              QString fn;
              if (open) {
                    fn = QFileDialog::getOpenFileName(
                       this, tr("Load Chord Symbols Style"),
                       defaultPath,
                       filter
                       );
                    }
              else {
                    fn = QFileDialog::getSaveFileName(
                       this, tr("Save Chord Symbols Style"),
                       defaultPath,
                       filter
                       );
                    }
              return fn;
              }
  
        QFileDialog* dialog;
        QList<QUrl> urls;
        QString home = QDir::homePath();
        urls.append(QUrl::fromLocalFile(home));
        urls.append(QUrl::fromLocalFile(defaultPath));
        urls.append(QUrl::fromLocalFile(QDir::currentPath()));
  
        QSettings set;
        if (open) {
              if (loadChordStyleDialog == 0) {
                    loadChordStyleDialog = new QFileDialog(this);
                    loadChordStyleDialog->setFileMode(QFileDialog::ExistingFile);
                    loadChordStyleDialog->setOption(QFileDialog::DontUseNativeDialog, true);
                    loadChordStyleDialog->setWindowTitle(tr("Load Chord Symbols Style"));
                    loadChordStyleDialog->setNameFilter(filter);
                    loadChordStyleDialog->setDirectory(defaultPath);
  
                    restoreDialogState("loadChordStyleDialog", loadChordStyleDialog);
                    loadChordStyleDialog->restoreState(set.value("loadChordStyleDialog").toByteArray());
                    loadChordStyleDialog->setAcceptMode(QFileDialog::AcceptOpen);
                    }
              // setup side bar urls
              urls.append(QUrl::fromLocalFile(mscoreGlobalShare+"/styles"));
              dialog = loadChordStyleDialog;
              }
        else {
              if (saveChordStyleDialog == 0) {
                    saveChordStyleDialog = new QFileDialog(this);
                    saveChordStyleDialog->setAcceptMode(QFileDialog::AcceptSave);
                    saveChordStyleDialog->setFileMode(QFileDialog::AnyFile);
                    saveChordStyleDialog->setOption(QFileDialog::DontConfirmOverwrite, false);
                    saveChordStyleDialog->setOption(QFileDialog::DontUseNativeDialog, true);
                    saveChordStyleDialog->setWindowTitle(tr("Save Style"));
                    saveChordStyleDialog->setNameFilter(filter);
                    saveChordStyleDialog->setDirectory(defaultPath);
  
                    restoreDialogState("saveChordStyleDialog", saveChordStyleDialog);
                    saveChordStyleDialog->setAcceptMode(QFileDialog::AcceptSave);
                    }
              dialog = saveChordStyleDialog;
              }
        // setup side bar urls
        dialog->setSidebarUrls(urls);
        if (dialog->exec()) {
              QStringList result = dialog->selectedFiles();
              return result.front();
              }
        return QString();
        }
  
  //---------------------------------------------------------
  //   getScanFile
  //---------------------------------------------------------
  
  QString MuseScore::getScanFile(const QString& d)
        {
        QString filter = tr("PDF Scan File") + " (*.pdf);;All (*)";
        QString defaultPath = d.isEmpty() ? QDir::homePath() : d;
        if (preferences.getBool(PREF_UI_APP_USENATIVEDIALOGS)) {
              QString s = QFileDialog::getOpenFileName(
                 mscore,
                 MuseScore::tr("Choose PDF Scan"),
                 defaultPath,
                 filter
                 );
              return s;
              }
  
        if (loadScanDialog == 0) {
              loadScanDialog = new QFileDialog(this);
              loadScanDialog->setFileMode(QFileDialog::ExistingFile);
              loadScanDialog->setOption(QFileDialog::DontUseNativeDialog, true);
              loadScanDialog->setWindowTitle(tr("Choose PDF Scan"));
              loadScanDialog->setNameFilter(filter);
              loadScanDialog->setDirectory(defaultPath);
  
              restoreDialogState("loadScanDialog", loadScanDialog);
              loadScanDialog->setAcceptMode(QFileDialog::AcceptOpen);
              }
  
        //
        // setup side bar urls
        //
        QList<QUrl> urls;
        QString home = QDir::homePath();
        urls.append(QUrl::fromLocalFile(home));
        urls.append(QUrl::fromLocalFile(QDir::currentPath()));
        loadScanDialog->setSidebarUrls(urls);
  
        if (loadScanDialog->exec()) {
              QStringList result = loadScanDialog->selectedFiles();
              return result.front();
              }
        return QString();
        }
  
  //---------------------------------------------------------
  //   getAudioFile
  //---------------------------------------------------------
  
  QString MuseScore::getAudioFile(const QString& d)
        {
        QString filter = tr("Ogg Audio File") + " (*.ogg);;All (*)";
        QString defaultPath = d.isEmpty() ? QDir::homePath() : d;
        if (preferences.getBool(PREF_UI_APP_USENATIVEDIALOGS)) {
              QString s = QFileDialog::getOpenFileName(
                 mscore,
                 MuseScore::tr("Choose Audio File"),
                 defaultPath,
                 filter
                 );
              return s;
              }
  
        if (loadAudioDialog == 0) {
              loadAudioDialog = new QFileDialog(this);
              loadAudioDialog->setFileMode(QFileDialog::ExistingFile);
              loadAudioDialog->setOption(QFileDialog::DontUseNativeDialog, true);
              loadAudioDialog->setWindowTitle(tr("Choose Ogg Audio File"));
              loadAudioDialog->setNameFilter(filter);
              loadAudioDialog->setDirectory(defaultPath);
  
              restoreDialogState("loadAudioDialog", loadAudioDialog);
              loadAudioDialog->setAcceptMode(QFileDialog::AcceptOpen);
              }
  
        //
        // setup side bar urls
        //
        QList<QUrl> urls;
        QString home = QDir::homePath();
        urls.append(QUrl::fromLocalFile(home));
        urls.append(QUrl::fromLocalFile(QDir::currentPath()));
        loadAudioDialog->setSidebarUrls(urls);
  
        if (loadAudioDialog->exec()) {
              QStringList result = loadAudioDialog->selectedFiles();
              return result.front();
              }
        return QString();
        }
  
  //---------------------------------------------------------
  //   getFotoFilename
  //---------------------------------------------------------
  
  QString MuseScore::getFotoFilename(QString& filter, QString* selectedFilter)
        {
        QString title = tr("Save Image");
  
        QFileInfo myImages(preferences.getString(PREF_APP_PATHS_MYIMAGES));
        if (myImages.isRelative())
              myImages.setFile(QDir::home(), preferences.getString(PREF_APP_PATHS_MYIMAGES));
        QString defaultPath = myImages.absoluteFilePath();
  
        // compute the image capture path
        QString myCapturePath;
        // if no saves were made for current score, then use the score's name as default
        if (!cs->masterScore()->savedCapture()) {
              // set the current score's name as the default name for saved captures
              QString scoreName = cs->masterScore()->fileInfo()->completeBaseName();
              QString name = createDefaultFileName(scoreName);
              QString fname = QString("%1/%2").arg(defaultPath).arg(name);
              QFileInfo myCapture(fname);
              if (myCapture.isRelative())
                  myCapture.setFile(QDir::home(), fname);
              myCapturePath = myCapture.absoluteFilePath();
              }
        else
              myCapturePath = lastSaveCaptureName;
  
        if (preferences.getBool(PREF_UI_APP_USENATIVEDIALOGS)) {
              QString fn;
              fn = QFileDialog::getSaveFileName(
                 this,
                 title,
                 myCapturePath,
                 filter,
                 selectedFilter
                 );
              // if a save was made for this current score
              if (!fn.isEmpty()) {
                  cs->masterScore()->setSavedCapture(true);
                  // store the last name used for saving an image capture
                  lastSaveCaptureName = fn;
                  }
              return fn;
              }
  
        QList<QUrl> urls;
        urls.append(QUrl::fromLocalFile(QDir::homePath()));
        urls.append(QUrl::fromLocalFile(defaultPath));
        urls.append(QUrl::fromLocalFile(QDir::currentPath()));
  
        if (saveImageDialog == 0) {
              saveImageDialog = new QFileDialog(this);
              saveImageDialog->setFileMode(QFileDialog::AnyFile);
              saveImageDialog->setAcceptMode(QFileDialog::AcceptSave);
              saveImageDialog->setOption(QFileDialog::DontConfirmOverwrite, false);
              saveImageDialog->setOption(QFileDialog::DontUseNativeDialog, true);
              saveImageDialog->setWindowTitle(title);
              saveImageDialog->setNameFilter(filter);
              saveImageDialog->setDirectory(defaultPath);
  
              restoreDialogState("saveImageDialog", saveImageDialog);
              saveImageDialog->setAcceptMode(QFileDialog::AcceptSave);
              }
  
        // setup side bar urls
        saveImageDialog->setSidebarUrls(urls);
  
        // set file's name using the computed path
        saveImageDialog->selectFile(myCapturePath);
  
        if (saveImageDialog->exec()) {
              QStringList result = saveImageDialog->selectedFiles();
              *selectedFilter = saveImageDialog->selectedNameFilter();
              // a save was made for this current score
              cs->masterScore()->setSavedCapture(true);
              // store the last name used for saving an image capture
              lastSaveCaptureName = result.front();
              return result.front();
              }
        return QString();
        }
  
  //---------------------------------------------------------
  //   getPaletteFilename
  //---------------------------------------------------------
  
  QString MuseScore::getPaletteFilename(bool open, const QString& name)
        {
        QString title;
        QString filter;
        QString wd      = QString("%1/%2").arg(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)).arg(QCoreApplication::applicationName());
        if (open) {
              title  = tr("Load Palette");
              filter = tr("MuseScore Palette") + " (*.mpal)";
              }
        else {
              title  = tr("Save Palette");
              filter = tr("MuseScore Palette") + " (*.mpal)";
              }
  
        QFileInfo myPalettes(wd);
        QString defaultPath = myPalettes.absoluteFilePath();
        if (!name.isEmpty()) {
              QString fname = createDefaultFileName(name);
              QFileInfo myName(fname);
              if (myName.isRelative())
                    myName.setFile(defaultPath, fname);
              defaultPath = myName.absoluteFilePath();
              }
  
        if (preferences.getBool(PREF_UI_APP_USENATIVEDIALOGS)) {
              QString fn;
              if (open)
                    fn = QFileDialog::getOpenFileName(this, title, defaultPath, filter);
              else
                    fn = QFileDialog::getSaveFileName(this, title, defaultPath, filter);
              return fn;
              }
  
        QFileDialog* dialog;
        QList<QUrl> urls;
        urls.append(QUrl::fromLocalFile(QDir::homePath()));
        urls.append(QUrl::fromLocalFile(QDir::currentPath()));
        urls.append(QUrl::fromLocalFile(defaultPath));
  
        if (open) {
              if (loadPaletteDialog == 0) {
                    loadPaletteDialog = new QFileDialog(this);
                    loadPaletteDialog->setFileMode(QFileDialog::ExistingFile);
                    loadPaletteDialog->setOption(QFileDialog::DontUseNativeDialog, true);
                    loadPaletteDialog->setDirectory(defaultPath);
  
                    restoreDialogState("loadPaletteDialog", loadPaletteDialog);
                    loadPaletteDialog->setAcceptMode(QFileDialog::AcceptOpen);
                    }
              dialog = loadPaletteDialog;
              }
        else {
              if (savePaletteDialog == 0) {
                    savePaletteDialog = new QFileDialog(this);
                    savePaletteDialog->setAcceptMode(QFileDialog::AcceptSave);
                    savePaletteDialog->setFileMode(QFileDialog::AnyFile);
                    savePaletteDialog->setOption(QFileDialog::DontConfirmOverwrite, false);
                    savePaletteDialog->setOption(QFileDialog::DontUseNativeDialog, true);
                    savePaletteDialog->setDirectory(defaultPath);
  
                    restoreDialogState("savePaletteDialog", savePaletteDialog);
                    savePaletteDialog->setAcceptMode(QFileDialog::AcceptSave);
                    }
              dialog = savePaletteDialog;
              }
        dialog->setWindowTitle(title);
        dialog->setNameFilter(filter);
  
        // setup side bar urls
        dialog->setSidebarUrls(urls);
  
        if (dialog->exec()) {
              QStringList result = dialog->selectedFiles();
              return result.front();
              }
        return QString();
        }
  
  //---------------------------------------------------------
  //   getPluginFilename
  //---------------------------------------------------------
  
  QString MuseScore::getPluginFilename(bool open)
        {
        QString title;
        QString filter;
        if (open) {
              title  = tr("Load Plugin");
              filter = tr("MuseScore Plugin") + " (*.qml)";
              }
        else {
              title  = tr("Save Plugin");
              filter = tr("MuseScore Plugin File") + " (*.qml)";
              }
  
        QFileInfo myPlugins(preferences.getString(PREF_APP_PATHS_MYPLUGINS));
        if (myPlugins.isRelative())
              myPlugins.setFile(QDir::home(), preferences.getString(PREF_APP_PATHS_MYPLUGINS));
        QString defaultPath = myPlugins.absoluteFilePath();
  
        QString name  = createDefaultFileName("Plugin");
        QString fname = QString("%1/%2.qml").arg(defaultPath).arg(name);
        if (preferences.getBool(PREF_UI_APP_USENATIVEDIALOGS)) {
              QString fn;
              if (open)
                    fn = QFileDialog::getOpenFileName(this, title, defaultPath, filter);
              else
                    fn = QFileDialog::getSaveFileName(this, title, defaultPath, filter);
              return fn;
              }
  
        QFileDialog* dialog;
        QList<QUrl> urls;
        QString home = QDir::homePath();
        urls.append(QUrl::fromLocalFile(home));
        urls.append(QUrl::fromLocalFile(defaultPath));
        urls.append(QUrl::fromLocalFile(QDir::currentPath()));
  
        if (open) {
              if (loadPluginDialog == 0) {
                    loadPluginDialog = new QFileDialog(this);
                    loadPluginDialog->setFileMode(QFileDialog::ExistingFile);
                    loadPluginDialog->setOption(QFileDialog::DontUseNativeDialog, true);
                    loadPluginDialog->setDirectory(defaultPath);
  
                    QSettings set;
                    loadPluginDialog->restoreState(set.value("loadPluginDialog").toByteArray());
                    loadPluginDialog->setAcceptMode(QFileDialog::AcceptOpen);
                    }
              urls.append(QUrl::fromLocalFile(mscoreGlobalShare+"/plugins"));
              dialog = loadPluginDialog;
              }
        else {
              if (savePluginDialog == 0) {
                    savePluginDialog = new QFileDialog(this);
                    QSettings set;
                    savePluginDialog->restoreState(set.value("savePluginDialog").toByteArray());
                    savePluginDialog->setAcceptMode(QFileDialog::AcceptSave);
                    savePluginDialog->setFileMode(QFileDialog::AnyFile);
                    savePluginDialog->setOption(QFileDialog::DontConfirmOverwrite, false);
                    savePluginDialog->setOption(QFileDialog::DontUseNativeDialog, true);
                    savePluginDialog->setWindowTitle(tr("Save Plugin"));
                    savePluginDialog->setNameFilter(filter);
                    savePluginDialog->setDirectory(defaultPath);
                    savePluginDialog->selectFile(fname);
                    }
              dialog = savePluginDialog;
              }
        dialog->setWindowTitle(title);
        dialog->setNameFilter(filter);
  
        // setup side bar urls
        dialog->setSidebarUrls(urls);
  
        if (dialog->exec()) {
              QStringList result = dialog->selectedFiles();
              return result.front();
              }
        return QString();
        }
  
  //---------------------------------------------------------
  //   getDrumsetFilename
  //---------------------------------------------------------
  
  QString MuseScore::getDrumsetFilename(bool open)
        {
        QString title;
        QString filter;
        if (open) {
              title  = tr("Load Drumset");
              filter = tr("MuseScore Drumset") + " (*.drm)";
              }
        else {
              title  = tr("Save Drumset");
              filter = tr("MuseScore Drumset File") + " (*.drm)";
              }
  
        QFileInfo myStyles(preferences.getString(PREF_APP_PATHS_MYSTYLES));
        if (myStyles.isRelative())
              myStyles.setFile(QDir::home(), preferences.getString(PREF_APP_PATHS_MYSTYLES));
        QString defaultPath  = myStyles.absoluteFilePath();
  
        if (preferences.getBool(PREF_UI_APP_USENATIVEDIALOGS)) {
              QString fn;
              if (open)
                    fn = QFileDialog::getOpenFileName(this, title, defaultPath, filter);
              else
                    fn = QFileDialog::getSaveFileName(this, title, defaultPath, filter);
              return fn;
              }
  
  
        QFileDialog* dialog;
        QList<QUrl> urls;
        QString home = QDir::homePath();
        urls.append(QUrl::fromLocalFile(home));
        urls.append(QUrl::fromLocalFile(defaultPath));
        urls.append(QUrl::fromLocalFile(QDir::currentPath()));
  
        if (open) {
              if (loadDrumsetDialog == 0) {
                    loadDrumsetDialog = new QFileDialog(this);
                    loadDrumsetDialog->setFileMode(QFileDialog::ExistingFile);
                    loadDrumsetDialog->setOption(QFileDialog::DontUseNativeDialog, true);
                    loadDrumsetDialog->setDirectory(defaultPath);
  
                    restoreDialogState("loadDrumsetDialog", loadDrumsetDialog);
                    loadDrumsetDialog->setAcceptMode(QFileDialog::AcceptOpen);
                    }
              dialog = loadDrumsetDialog;
              }
        else {
              if (saveDrumsetDialog == 0) {
                    saveDrumsetDialog = new QFileDialog(this);
                    saveDrumsetDialog->setAcceptMode(QFileDialog::AcceptSave);
                    saveDrumsetDialog->setFileMode(QFileDialog::AnyFile);
                    saveDrumsetDialog->setOption(QFileDialog::DontConfirmOverwrite, false);
                    saveDrumsetDialog->setOption(QFileDialog::DontUseNativeDialog, true);
                    saveDrumsetDialog->setDirectory(defaultPath);
  
                    restoreDialogState("saveDrumsetDialog", saveDrumsetDialog);
                    saveDrumsetDialog->setAcceptMode(QFileDialog::AcceptSave);
                    }
              dialog = saveDrumsetDialog;
              }
        dialog->setWindowTitle(title);
        dialog->setNameFilter(filter);
  
        // setup side bar urls
        dialog->setSidebarUrls(urls);
  
        if (dialog->exec()) {
              QStringList result = dialog->selectedFiles();
              return result.front();
              }
        return QString();
        }
  
  //---------------------------------------------------------
  //   printFile
  //---------------------------------------------------------
  
  void MuseScore::printFile()
        {
  #ifndef QT_NO_PRINTER
        LayoutMode layoutMode = cs->layoutMode();
        if (layoutMode != LayoutMode::PAGE) {
              cs->setLayoutMode(LayoutMode::PAGE);
              cs->doLayout();
              }
  
        QPrinter printerDev(QPrinter::HighResolution);
        QSizeF size(cs->styleD(Sid::pageWidth), cs->styleD(Sid::pageHeight));
        QPageSize ps(QPageSize::id(size, QPageSize::Inch));
        printerDev.setPageSize(ps);
        printerDev.setPageOrientation(size.width() > size.height() ? QPageLayout::Landscape : QPageLayout::Portrait);
  
        printerDev.setCreator("MuseScore Version: " VERSION);
        printerDev.setFullPage(true);
        if (!printerDev.setPageMargins(QMarginsF()))
              qDebug("unable to clear printer margins");
        printerDev.setColorMode(QPrinter::Color);
        if (cs->isMaster())
              printerDev.setDocName(cs->masterScore()->fileInfo()->completeBaseName());
        else
              printerDev.setDocName(cs->excerpt()->title());
        printerDev.setOutputFormat(QPrinter::NativeFormat);
        int pages    = cs->pages().size();
        printerDev.setFromTo(1, pages);
  
  #if defined(Q_OS_MAC) || defined(Q_OS_WIN)
        printerDev.setOutputFileName("");
  #else
        // when setting this on windows platform, pd.exec() does not
        // show dialog
        if (cs->isMaster())
              printerDev.setOutputFileName(cs->masterScore()->fileInfo()->path() + "/" + cs->masterScore()->fileInfo()->completeBaseName() + ".pdf");
        else
              printerDev.setOutputFileName(cs->masterScore()->fileInfo()->path() + "/" + cs->excerpt()->title() + ".pdf");
  #endif
  
        QPrintDialog pd(&printerDev, 0);
  
        if (pd.exec()) {
              QPainter p(&printerDev);
              p.setRenderHint(QPainter::Antialiasing, true);
              p.setRenderHint(QPainter::TextAntialiasing, true);
              double mag_ = printerDev.logicalDpiX() / DPI;
  
              double pr = MScore::pixelRatio;
              MScore::pixelRatio = 1.0 / mag_;
              p.scale(mag_, mag_);
  
              int fromPage = printerDev.fromPage() - 1;
              int toPage   = printerDev.toPage() - 1;
              if (fromPage < 0)
                    fromPage = 0;
              if ((toPage < 0) || (toPage >= pages))
                    toPage = pages - 1;
  
              for (int copy = 0; copy < printerDev.numCopies(); ++copy) {
                    bool firstPage = true;
                    for (int n = fromPage; n <= toPage; ++n) {
                          if (!firstPage)
                                printerDev.newPage();
                          firstPage = false;
  
                          cs->print(&p, n);
                          if ((copy + 1) < printerDev.numCopies())
                                printerDev.newPage();
                          }
                    }
              p.end();
              MScore::pixelRatio = pr;
              }
  
        if (layoutMode != cs->layoutMode()) {
              cs->setLayoutMode(layoutMode);
              cs->doLayout();
              }
  #endif
        }
  
  //---------------------------------------------------------
  //   exportFile
  //    return true on success
  //---------------------------------------------------------
  
  void MuseScore::exportFile()
        {
        QStringList fl;
        fl.append(tr("PDF File") + " (*.pdf)");
        fl.append(tr("PNG Bitmap Graphic") + " (*.png)");
        fl.append(tr("Scalable Vector Graphics") + " (*.svg)");
  #ifdef HAS_AUDIOFILE
        fl.append(tr("Wave Audio") + " (*.wav)");
        fl.append(tr("FLAC Audio") + " (*.flac)");
        fl.append(tr("Ogg Vorbis Audio") + " (*.ogg)");
  #endif
  #ifdef USE_LAME
        fl.append(tr("MP3 Audio") + " (*.mp3)");
  #endif
        fl.append(tr("Standard MIDI File") + " (*.mid)");
        fl.append(tr("Compressed MusicXML File") + " (*.mxl)");
        fl.append(tr("Uncompressed MusicXML File") + " (*.musicxml)");
        fl.append(tr("Uncompressed MusicXML File (outdated)") + " (*.xml)");
        fl.append(tr("Uncompressed MuseScore 3 File") + " (*.mscx)");     // for debugging purposes
  
        QString saveDialogTitle = tr("Export");
  
        QString saveDirectory;
        if (cs->masterScore()->fileInfo()->exists())
              saveDirectory = cs->masterScore()->fileInfo()->dir().path();
        else {
              QSettings set;
              if (lastSaveCopyDirectory.isEmpty())
                    lastSaveCopyDirectory = set.value("lastSaveCopyDirectory", preferences.getString(PREF_APP_PATHS_MYSCORES)).toString();
              saveDirectory = lastSaveCopyDirectory;
              }
  
        if (saveDirectory.isEmpty())
              saveDirectory = preferences.getString(PREF_APP_PATHS_MYSCORES);
  
        if (lastSaveCopyFormat.isEmpty()) {
              QSettings set;
              lastSaveCopyFormat = set.value("lastSaveCopyFormat", "pdf").toString();
              }
        QString saveFormat = lastSaveCopyFormat;
  
        if (saveFormat.isEmpty())
              saveFormat = "pdf";
  
        QString name;
  #ifdef Q_OS_WIN
        if (QSysInfo::WindowsVersion == QSysInfo::WV_XP) {
              if (!cs->isMaster())
                    name = QString("%1/%2-%3").arg(saveDirectory).arg(cs->masterScore()->fileInfo()->completeBaseName()).arg(createDefaultFileName(cs->title()));
              else
                    name = QString("%1/%2").arg(saveDirectory).arg(cs->masterScore()->fileInfo()->completeBaseName());
              }
        else
  #endif
        if (!cs->isMaster())
              name = QString("%1/%2-%3.%4").arg(saveDirectory).arg(cs->masterScore()->fileInfo()->completeBaseName()).arg(createDefaultFileName(cs->title())).arg(saveFormat);
        else
              name = QString("%1/%2.%3").arg(saveDirectory).arg(cs->masterScore()->fileInfo()->completeBaseName()).arg(saveFormat);
  
        int idx = fl.indexOf(QRegExp(".+\\(\\*\\." + saveFormat + "\\)"), Qt::CaseInsensitive);
        if (idx != -1)
              fl.move(idx, 0);
        QString filter = fl.join(";;");
        QString fn = getSaveScoreName(saveDialogTitle, name, filter);
        if (fn.isEmpty())
              return;
  
        QFileInfo fi(fn);
        lastSaveCopyDirectory = fi.absolutePath();
        lastSaveCopyFormat = fi.suffix();
  
        if (fi.suffix().isEmpty())
              QMessageBox::critical(this, tr("Export"), tr("Cannot determine file type"));
        else
              saveAs(cs, true, fn, fi.suffix());
        }
  
  //---------------------------------------------------------
  //   exportParts
  //    return true on success
  //---------------------------------------------------------
  
  bool MuseScore::exportParts()
        {
        QStringList fl;
        fl.append(tr("PDF File") + " (*.pdf)");
        fl.append(tr("PNG Bitmap Graphic") + " (*.png)");
        fl.append(tr("Scalable Vector Graphics") + " (*.svg)");
  #ifdef HAS_AUDIOFILE
        fl.append(tr("Wave Audio") + " (*.wav)");
        fl.append(tr("FLAC Audio") + " (*.flac)");
        fl.append(tr("Ogg Vorbis Audio") + " (*.ogg)");
  #endif
  #ifdef USE_LAME
        fl.append(tr("MP3 Audio") + " (*.mp3)");
  #endif
        fl.append(tr("Standard MIDI File") + " (*.mid)");
        fl.append(tr("Compressed MusicXML File") + " (*.mxl)");
        fl.append(tr("Uncompressed MusicXML File") + " (*.musicxml)");
        fl.append(tr("Uncompressed MusicXML File (outdated)") + " (*.xml)");
        fl.append(tr("MuseScore 3 File") + " (*.mscz)");
        fl.append(tr("Uncompressed MuseScore 3 File") + " (*.mscx)");     // for debugging purposes
  
        QString saveDialogTitle = tr("Export Parts");
  
        QString saveDirectory;
        if (cs->masterScore()->fileInfo()->exists())
              saveDirectory = cs->masterScore()->fileInfo()->dir().path();
        else {
              QSettings set;
              if (lastSaveCopyDirectory.isEmpty())
                  lastSaveCopyDirectory = set.value("lastSaveCopyDirectory", preferences.getString(PREF_APP_PATHS_MYSCORES)).toString();
              saveDirectory = lastSaveCopyDirectory;
              }
  
        if (saveDirectory.isEmpty())
              saveDirectory = preferences.getString(PREF_APP_PATHS_MYSCORES);
  
        if (lastSaveCopyFormat.isEmpty()) {
              QSettings set;
              lastSaveCopyFormat = set.value("lastSaveCopyFormat", "pdf").toString();
              }
        QString saveFormat = lastSaveCopyFormat;
  
        if (saveFormat.isEmpty())
              saveFormat = "pdf";
  
        QString scoreName = cs->isMaster() ? cs->masterScore()->fileInfo()->completeBaseName() : cs->title();
        QString name;
  #ifdef Q_OS_WIN
        if (QSysInfo::WindowsVersion == QSysInfo::WV_XP)
              name = QString("%1/%2").arg(saveDirectory).arg(scoreName);
        else
  #endif
        name = QString("%1/%2.%3").arg(saveDirectory).arg(scoreName).arg(saveFormat);
  
        int idx = fl.indexOf(QRegExp(".+\\(\\*\\." + saveFormat + "\\)"), Qt::CaseInsensitive);
        if (idx != -1)
              fl.move(idx, 0);
        QString filter = fl.join(";;");
        QString fn = getSaveScoreName(saveDialogTitle, name, filter);
        if (fn.isEmpty())
            return false;
  
        QFileInfo fi(fn);
        lastSaveCopyDirectory = fi.absolutePath();
        lastSaveCopyFormat = fi.suffix();
  
        QString ext = fi.suffix();
        if (ext.isEmpty()) {
              QMessageBox::critical(this, tr("Export Parts"), tr("Cannot determine file type"));
              return false;
              }
  
        Score* thisScore = cs->masterScore();
        bool overwrite = false;
        bool noToAll = false;
        QString confirmReplaceTitle = tr("Confirm Replace");
        QString confirmReplaceMessage = tr("\"%1\" already exists.\nDo you want to replace it?\n");
        QString replaceMessage = tr("Replace");
        QString skipMessage = tr("Skip");
        foreach (Excerpt* e, thisScore->excerpts())  {
              Score* pScore = e->partScore();
              QString partfn = fi.absolutePath() + "/" + fi.completeBaseName() + "-" + createDefaultFileName(pScore->title()) + "." + ext;
              QFileInfo fip(partfn);
              if (fip.exists() && !overwrite) {
                    if(noToAll)
                          continue;
                    QMessageBox msgBox( QMessageBox::Question, confirmReplaceTitle,
                          confirmReplaceMessage.arg(QDir::toNativeSeparators(partfn)),
                          QMessageBox::Yes |  QMessageBox::YesToAll | QMessageBox::No |  QMessageBox::NoToAll);
                    msgBox.setButtonText(QMessageBox::Yes, replaceMessage);
                    msgBox.setButtonText(QMessageBox::No, skipMessage);
                    msgBox.setButtonText(QMessageBox::YesToAll, tr("Replace All"));
                    msgBox.setButtonText(QMessageBox::NoToAll, tr("Skip All"));
                    int sb = msgBox.exec();
                    if(sb == QMessageBox::YesToAll) {
                          overwrite = true;
                          }
                    else if (sb == QMessageBox::NoToAll) {
                          noToAll = true;
                          continue;
                          }
                    else if (sb == QMessageBox::No)
                          continue;
                    }
  
              if (!saveAs(pScore, true, partfn, ext))
                    return false;
              }
        // For PDF, also export score and parts together
        if (ext.toLower() == "pdf") {
              QList<Score*> scores;
              scores.append(thisScore);
              foreach(Excerpt* e, thisScore->excerpts())  {
                    scores.append(e->partScore());
                    }
              QString partfn(fi.absolutePath() + "/" + fi.completeBaseName() + "-" + createDefaultFileName(tr("Score_and_Parts")) + ".pdf");
              QFileInfo fip(partfn);
              if(fip.exists() && !overwrite) {
                    if (!noToAll) {
                          QMessageBox msgBox( QMessageBox::Question, confirmReplaceTitle,
                                confirmReplaceMessage.arg(QDir::toNativeSeparators(partfn)),
                                QMessageBox::Yes | QMessageBox::No);
                          msgBox.setButtonText(QMessageBox::Yes, replaceMessage);
                          msgBox.setButtonText(QMessageBox::No, skipMessage);
                          int sb = msgBox.exec();
                          if(sb == QMessageBox::Yes) {
                                if (!savePdf(scores, partfn))
                                      return false;
                                }
                          }
                    }
              else if (!savePdf(scores, partfn))
                    return false;
        }
        if(!noToAll)
              QMessageBox::information(this, tr("Export Parts"), tr("Parts were successfully exported"));
        return true;
        }
  
  //---------------------------------------------------------
  //   saveAs
  //---------------------------------------------------------
  
  bool MuseScore::saveAs(Score* cs_, bool saveCopy, const QString& path, const QString& ext)
        {
        bool rv = false;
        QString suffix = "." + ext;
        QString fn(path);
        if (!fn.endsWith(suffix))
              fn += suffix;
  
        LayoutMode layoutMode = cs_->layoutMode();
        if (ext == "mscx" || ext == "mscz") {
              // save as mscore *.msc[xz] file
              QFileInfo fi(fn);
              rv = true;
              // store new file and path into score fileInfo
              // to have it accessible to resources
              QFileInfo originalScoreFileInfo(*cs_->masterScore()->fileInfo());
              cs_->masterScore()->fileInfo()->setFile(fn);
              if (!cs_->isMaster()) { // clone metaTags from masterScore
                    QMapIterator<QString, QString> j(cs_->masterScore()->metaTags());
                    while (j.hasNext()) {
                          j.next();
                          if (j.key() != "partName") // don't copy "partName" should that exist in masterScore
                                cs_->metaTags().insert(j.key(), j.value());
  #if defined(Q_OS_WIN)   // Update "platform", may not be worth the effort
                          cs_->metaTags().insert("platform", "Microsoft Windows");
  #elif defined(Q_OS_MAC)
                          cs_->metaTags().insert("platform", "Apple Macintosh");
  #elif defined(Q_OS_LINUX)
                          cs_->metaTags().insert("platform", "Linux");
  #else
                          cs_->metaTags().insert("platform", "Unknown");
  #endif
                          cs_->metaTags().insert("source", ""); // Empty "source" to avoid clashes with masterrScore when doing "Save online"
                          cs_->metaTags().insert("creationDate", QDate::currentDate().toString(Qt::ISODate)); // update "creationDate"
                          }
                    }
              try {
                    if (ext == "mscz")
                          cs_->saveCompressedFile(fi, false);
                    else
                          cs_->saveFile(fi);
                    }
              catch (QString s) {
                    rv = false;
                    QMessageBox::critical(this, tr("Save As"), s);
                    }
              if (!cs_->isMaster()) { // remove metaTags added above
                    QMapIterator<QString, QString> j(cs_->masterScore()->metaTags());
                    while (j.hasNext()) {
                          j.next();
                          // remove all but "partName", should that exist in masterScore
                          if (j.key() != "partName")
                                cs_->metaTags().remove(j.key());
                          }
                    }
              *cs_->masterScore()->fileInfo() = originalScoreFileInfo;          // restore original file info
  
              if (rv && !saveCopy) {
                    cs_->masterScore()->fileInfo()->setFile(fn);
                    updateWindowTitle(cs_);
                    cs_->undoStack()->setClean();
                    dirtyChanged(cs_);
                    cs_->setCreated(false);
                    scoreCmpTool->updateScoreVersions(cs_);
                    addRecentScore(cs_);
                    writeSessionFile(false);
                    }
              }
        else if ((ext == "musicxml") || (ext == "xml")) {
              // save as MusicXML *.musicxml file
              rv = saveXml(cs_, fn);
              }
        else if (ext == "mxl") {
              // save as compressed MusicXML *.mxl file
              rv = saveMxl(cs_, fn);
              }
        else if (ext == "mid") {
              // save as midi file *.mid
              rv = saveMidi(cs_, fn);
              }
        else if (ext == "pdf") {
              // save as pdf file *.pdf
              cs_->switchToPageMode();
              rv = savePdf(cs_, fn);
              }
        else if (ext == "png") {
              // save as png file *.png
              cs_->switchToPageMode();
              rv = savePng(cs_, fn);
              }
        else if (ext == "svg") {
              // save as svg file *.svg
              cs_->switchToPageMode();
              rv = saveSvg(cs_, fn);
              }
  #ifdef HAS_AUDIOFILE
        else if (ext == "wav" || ext == "flac" || ext == "ogg")
              rv = saveAudio(cs_, fn);
  #endif
  #ifdef USE_LAME
        else if (ext == "mp3")
              rv = saveMp3(cs_, fn);
  #endif
        else if (ext == "spos") {
              cs_->switchToPageMode();
              // save positions of segments
              rv = savePositions(cs_, fn, true);
              }
        else if (ext == "mpos") {
              cs_->switchToPageMode();
              // save positions of measures
              rv = savePositions(cs_, fn, false);
              }
        else if (ext == "mlog") {
              rv = cs_->sanityCheck(fn);
              }
        else if (ext == "metajson") {
              rv = saveMetadataJSON(cs, fn);
              }
        else {
              qDebug("Internal error: unsupported extension <%s>",
                 qPrintable(ext));
              return false;
              }
        if (!rv && !MScore::noGui)
              QMessageBox::critical(this, tr("MuseScore:"), tr("Cannot write into %1").arg(fn));
  
        if (layoutMode != cs_->layoutMode()) {
              cs_->setLayoutMode(layoutMode);
              cs_->doLayout();
              }
        return rv;
        }
  
  //---------------------------------------------------------
  //   saveMidi
  //---------------------------------------------------------
  
  bool MuseScore::saveMidi(Score* score, const QString& name)
        {
        ExportMidi em(score);
        return em.write(name, preferences.getBool(PREF_IO_MIDI_EXPANDREPEATS), preferences.getBool(PREF_IO_MIDI_EXPORTRPNS), synthesizerState());
        }
  
  bool MuseScore::saveMidi(Score* score, QIODevice* device)
        {
        ExportMidi em(score);
        return em.write(device, preferences.getBool(PREF_IO_MIDI_EXPANDREPEATS), preferences.getBool(PREF_IO_MIDI_EXPORTRPNS), synthesizerState());
        }
  
  //---------------------------------------------------------
  //   savePdf
  //---------------------------------------------------------
  
  bool MuseScore::savePdf(const QString& saveName)
        {
        return savePdf(cs, saveName);
        }
  
  bool MuseScore::savePdf(Score* cs_, const QString& saveName)
        {
        QPrinter printer;
        printer.setOutputFileName(saveName);
        return savePdf(cs_, printer);
        }
  
  bool MuseScore::savePdf(Score* cs_, QPrinter& printer)
        {
        cs_->setPrinting(true);
        MScore::pdfPrinting = true;
  
        printer.setResolution(preferences.getInt(PREF_EXPORT_PDF_DPI));
        QSizeF size(cs_->styleD(Sid::pageWidth), cs_->styleD(Sid::pageHeight));
        printer.setPaperSize(size, QPrinter::Inch);
        printer.setFullPage(true);
        printer.setColorMode(QPrinter::Color);
  #if defined(Q_OS_MAC)
        printer.setOutputFormat(QPrinter::NativeFormat);
  #else
        printer.setOutputFormat(QPrinter::PdfFormat);
  #endif
  
        printer.setCreator("MuseScore Version: " VERSION);
        if (!printer.setPageMargins(QMarginsF()))
              qDebug("unable to clear printer margins");
  
        QString title = cs_->metaTag("workTitle");
        if (title.isEmpty()) // workTitle unset?
              title = cs_->masterScore()->title(); // fall back to (master)score's tab title
        if (!cs_->isMaster()) { // excerpt?
              QString partname = cs_->metaTag("partName");
              if (partname.isEmpty()) // partName unset?
                    partname = cs_->title(); // fall back to excerpt's tab title
              title += " - " + partname;
              }
        printer.setDocName(title); // set PDF's meta data for Title
  
        QPainter p;
        if (!p.begin(&printer))
              return false;
        p.setRenderHint(QPainter::Antialiasing, true);
        p.setRenderHint(QPainter::TextAntialiasing, true);
  
        p.setViewport(QRect(0.0, 0.0, size.width() * printer.logicalDpiX(),
           size.height() * printer.logicalDpiY()));
        p.setWindow(QRect(0.0, 0.0, size.width() * DPI, size.height() * DPI));
  
        double pr = MScore::pixelRatio;
        MScore::pixelRatio = DPI / printer.logicalDpiX();
  
        const QList<Page*> pl = cs_->pages();
        int pages = pl.size();
        bool firstPage = true;
        for (int n = 0; n < pages; ++n) {
              if (!firstPage)
                    printer.newPage();
              firstPage = false;
              cs_->print(&p, n);
              }
        p.end();
        cs_->setPrinting(false);
  
        MScore::pixelRatio = pr;
        MScore::pdfPrinting = false;
        return true;
        }
  
  bool MuseScore::savePdf(QList<Score*> cs_, const QString& saveName)
        {
        if (cs_.empty())
              return false;
        Score* firstScore = cs_[0];
  
        QPrinter printer;
        printer.setOutputFileName(saveName);
        printer.setResolution(preferences.getInt(PREF_EXPORT_PDF_DPI));
        QSizeF size(firstScore->styleD(Sid::pageWidth), firstScore->styleD(Sid::pageHeight));
        QPageSize ps(QPageSize::id(size, QPageSize::Inch, QPageSize::FuzzyOrientationMatch));
        printer.setPageSize(ps);
        printer.setPageOrientation(size.width() > size.height() ? QPageLayout::Landscape : QPageLayout::Portrait);
        printer.setFullPage(true);
        printer.setColorMode(QPrinter::Color);
  #if defined(Q_OS_MAC)
        printer.setOutputFormat(QPrinter::NativeFormat);
  #else
        printer.setOutputFormat(QPrinter::PdfFormat);
  #endif
        
        printer.setCreator("MuseScore Version: " VERSION);
        if (!printer.setPageMargins(QMarginsF()))
              qDebug("unable to clear printer margins");
  
        QString title = firstScore->metaTag("workTitle");
        if (title.isEmpty()) // workTitle unset?
              title = firstScore->title(); // fall back to (master)score's tab title
        title += " - " + tr("Score and Parts");
        printer.setDocName(title); // set PDF's meta data for Title
  
        QPainter p;
        if (!p.begin(&printer))
              return false;
  
        p.setRenderHint(QPainter::Antialiasing, true);
        p.setRenderHint(QPainter::TextAntialiasing, true);
  
        double pr = MScore::pixelRatio;
  
        bool firstPage = true;
        for (Score* s : cs_) {
              LayoutMode layoutMode = s->layoutMode();
              if (layoutMode != LayoutMode::PAGE) {
                    s->setLayoutMode(LayoutMode::PAGE);
              //      s->doLayout();
                    }
              s->doLayout();
  
              // done in Score::print() also, but do it here as well to be safe
              s->setPrinting(true);
              MScore::pdfPrinting = true;
  
              QSizeF size1(s->styleD(Sid::pageWidth), s->styleD(Sid::pageHeight));
              QPageSize ps1(QPageSize::id(size1, QPageSize::Inch, QPageSize::FuzzyOrientationMatch));
              printer.setPageSize(ps1);
              printer.setPageOrientation(size1.width() > size1.height() ? QPageLayout::Landscape : QPageLayout::Portrait);
              p.setViewport(QRect(0.0, 0.0, size1.width() * printer.logicalDpiX(),
                 size1.height() * printer.logicalDpiY()));
              p.setWindow(QRect(0.0, 0.0, size1.width() * DPI, size1.height() * DPI));
  
              MScore::pixelRatio = DPI / printer.logicalDpiX();
              const QList<Page*> pl = s->pages();
              int pages    = pl.size();
              for (int n = 0; n < pages; ++n) {
                    if (!firstPage)
                          printer.newPage();
                    firstPage = false;
                    s->print(&p, n);
                    }
              MScore::pixelRatio = pr;
  
              //reset score
              s->setPrinting(false);
              MScore::pdfPrinting = false;
  
              if (layoutMode != s->layoutMode()) {
                    s->setLayoutMode(layoutMode);
                    s->doLayout();
                    }
              }
        p.end();
        return true;
        }
  
  //---------------------------------------------------------
  //   importSoundfont
  //---------------------------------------------------------
  
  void importSoundfont(QString name)
        {
        QFileInfo info(name);
        int ret = QMessageBox::question(0, QWidget::tr("Install SoundFont"),
              QWidget::tr("Do you want to install the SoundFont %1?").arg(info.fileName()),
               QMessageBox::Yes|QMessageBox::No, QMessageBox::NoButton);
        if (ret == QMessageBox::Yes) {
              QStringList pl = preferences.getString(PREF_APP_PATHS_MYSOUNDFONTS).split(";");
              QString destPath;
              for (QString s : pl) {
                    QFileInfo dest(s);
                    if (dest.isWritable())
                          destPath = s;
                    }
              if (!destPath.isEmpty()) {
                    QString destFilePath = destPath+ "/" +info.fileName();
                    QFileInfo destFileInfo(destFilePath);
                    QFile destFile(destFilePath);
                    if (destFileInfo.exists()) {
                          int ret1 = QMessageBox::question(0, QWidget::tr("Overwrite?"),
                            QWidget::tr("%1 already exists.\nDo you want to overwrite it?").arg(destFileInfo.absoluteFilePath()),
                            QMessageBox::Yes|QMessageBox::No, QMessageBox::No);
                          if (ret1 == QMessageBox::No)
                                return;
                          destFile.remove();
                          }
                    QFile orig(name);
                    if (orig.copy(destFilePath)) {
                          QMessageBox::information(0, QWidget::tr("SoundFont installed"), QWidget::tr("SoundFont installed. Please go to View > Synthesizer to add it and View > Mixer to choose an instrument sound."));
                          }
                    }
              }
        }
  
  //---------------------------------------------------------
  //   importExtension
  //---------------------------------------------------------
  
  void importExtension(QString name)
        {
        mscore->importExtension(name);
        }
  
  //---------------------------------------------------------
  //   readScore
  ///   Import file \a name
  //---------------------------------------------------------
  
  Score::FileError readScore(MasterScore* score, QString name, bool ignoreVersionError)
        {
        ScoreLoad sl;
  
        QFileInfo info(name);
        QString suffix  = info.suffix().toLower();
        score->setName(info.completeBaseName());
        score->setImportedFilePath(name);
  
        // Set the default synthesizer state before we read
        if (synti)
              score->setSynthesizerState(synti->state());
  
        if (suffix == "mscz" || suffix == "mscx") {
              Score::FileError rv = score->loadMsc(name, ignoreVersionError);
              if (score && score->masterScore()->fileInfo()->path().startsWith(":/"))
                    score->setCreated(true);
              if (rv != Score::FileError::FILE_NO_ERROR)
                    return rv;
              }
        else if (suffix == "sf2" || suffix == "sf3") {
              importSoundfont(name);
              return Score::FileError::FILE_IGNORE_ERROR;
              }
        else if (suffix == "muxt") {
             importExtension(name);
             return Score::FileError::FILE_IGNORE_ERROR;
             }
        else {
              // typedef Score::FileError (*ImportFunction)(MasterScore*, const QString&);
              struct ImportDef {
                    const char* extension;
                    // ImportFunction importF;
                    Score::FileError (*importF)(MasterScore*, const QString&);
                    };
              static const ImportDef imports[] = {
                    { "xml",  &importMusicXml           },
                    { "musicxml", &importMusicXml       },
                    { "mxl",  &importCompressedMusicXml },
                    { "mid",  &importMidi               },
                    { "midi", &importMidi               },
                    { "kar",  &importMidi               },
                    { "md",   &importMuseData           },
                    { "mgu",  &importBB                 },
                    { "sgu",  &importBB                 },
                    { "cap",  &importCapella            },
                    { "capx", &importCapXml             },
                    { "ove",  &importOve                },
                    { "scw",  &importOve                },
  #ifdef OMR
                    { "pdf",  &importPdf                },
  #endif
                    { "bww",  &importBww                },
                    { "gtp",  &importGTP                },
                    { "gp3",  &importGTP                },
                    { "gp4",  &importGTP                },
                    { "gp5",  &importGTP                },
                    { "gpx",  &importGTP                },
                    { "ptb",  &importGTP                },
  #ifdef AVSOMR
                    { "msmr", &importMSMR                },
                    { "pdf",  &loadAndImportMSMR         },
                    { "png",  &loadAndImportMSMR         },
                    { "jpg",  &loadAndImportMSMR         },
  #endif
                    };
  
              // import
              if (!preferences.getString(PREF_IMPORT_STYLE_STYLEFILE).isEmpty()) {
                    QFile f(preferences.getString(PREF_IMPORT_STYLE_STYLEFILE));
                    // silently ignore style file on error
                    if (f.open(QIODevice::ReadOnly))
                          score->style().load(&f);
                    }
              else {
                    score->style().checkChordList();
                    }
              bool found = false;
              for (auto i : imports) {
                    if (i.extension == suffix) {
                          Score::FileError rv = (*i.importF)(score, name);
                          if (rv != Score::FileError::FILE_NO_ERROR)
                                return rv;
                          found = true;
                          break;
                          }
                    }
              if (!found) {
                    qDebug("unknown file suffix <%s>, name <%s>", qPrintable(suffix), qPrintable(name));
                    return Score::FileError::FILE_UNKNOWN_TYPE;
                    }
              score->setMetaTag("originalFormat", suffix);
              score->connectTies();
              if (!score->avsOmr()) //! NOTE For avsomr сreated is set upon import
                    score->setCreated(true); // force save as for imported files
              }
  
        score->rebuildMidiMapping();
        score->setSoloMute();
        for (Score* s : score->scoreList()) {
              s->setPlaylistDirty();
              s->addLayoutFlags(LayoutFlag::FIX_PITCH_VELO);
              s->setLayoutAll();
              }
        score->updateChannel();
        score->updateExpressive(MuseScore::synthesizer("Fluid"));
        score->setSaved(false);
        score->update();
  
        if (!ignoreVersionError && !MScore::noGui)
              if (!score->sanityCheck(QString()))
                    return Score::FileError::FILE_CORRUPTED;
        return Score::FileError::FILE_NO_ERROR;
        }
  
  //---------------------------------------------------------
  //   saveAs
  //    return true on success
  //---------------------------------------------------------
  
  /**
   Save the current score using a different name or type.
   Handles the GUI's file-save-as and file-save-a-copy actions.
   The saveCopy flag, if true, does not change the name of the active score nor marks it clean.
   Return true if OK and false on error.
   */
  
  bool MuseScore::saveAs(Score* cs_, bool saveCopy)
        {
        QStringList fl;
        fl.append(tr("MuseScore 3 File") + " (*.mscz)");
        fl.append(tr("Uncompressed MuseScore 3 File") + " (*.mscx)");     // for debugging purposes
        QString saveDialogTitle = saveCopy ? tr("Save a Copy") :
                                             tr("Save As");
  
        QString saveDirectory;
        if (cs_->masterScore()->fileInfo()->exists())
              saveDirectory = cs_->masterScore()->fileInfo()->dir().path();
        else {
              QSettings set;
              if (saveCopy) {
                    if (mscore->lastSaveCopyDirectory.isEmpty())
                          mscore->lastSaveCopyDirectory = set.value("lastSaveCopyDirectory", preferences.getString(PREF_APP_PATHS_MYSCORES)).toString();
                    saveDirectory = mscore->lastSaveCopyDirectory;
                    }
              else {
                    if (mscore->lastSaveDirectory.isEmpty())
                          mscore->lastSaveDirectory = set.value("lastSaveDirectory", preferences.getString(PREF_APP_PATHS_MYSCORES)).toString();
                    saveDirectory = mscore->lastSaveDirectory;
                    }
              }
  
        if (saveDirectory.isEmpty())
              saveDirectory = preferences.getString(PREF_APP_PATHS_MYSCORES);
  
        QString name;
  #ifdef Q_OS_WIN
        if (QSysInfo::WindowsVersion == QSysInfo::WV_XP) {
              if (!cs_->isMaster())
                    name = QString("%1/%2-%3").arg(saveDirectory).arg(cs_->masterScore()->fileInfo()->completeBaseName()).arg(createDefaultFileName(cs->title()));
              else
                    name = QString("%1/%2").arg(saveDirectory).arg(cs_->masterScore()->fileInfo()->completeBaseName());
              }
        else
  #endif
        if (!cs_->isMaster())
              name = QString("%1/%2-%3.mscz").arg(saveDirectory).arg(cs_->masterScore()->fileInfo()->completeBaseName()).arg(createDefaultFileName(cs->title()));
        else
              name = QString("%1/%2.mscz").arg(saveDirectory).arg(cs_->masterScore()->fileInfo()->completeBaseName());
  
        QString filter = fl.join(";;");
        QString fn     = mscore->getSaveScoreName(saveDialogTitle, name, filter);
        if (fn.isEmpty())
              return false;
  
        QFileInfo fi(fn);
        if (saveCopy)
              mscore->lastSaveCopyDirectory = fi.absolutePath();
        else
              mscore->lastSaveDirectory = fi.absolutePath();
  
        if (fi.suffix().isEmpty()) {
              if (!MScore::noGui)
                    QMessageBox::critical(mscore, tr("Save As"), tr("Cannot determine file type"));
              return false;
              }
        return saveAs(cs_, saveCopy, fn, fi.suffix());
        }
  
  //---------------------------------------------------------
  //   saveSelection
  //    return true on success
  //---------------------------------------------------------
  
  bool MuseScore::saveSelection(Score* cs_)
        {
        if (!cs_->selection().isRange()) {
              if(!MScore::noGui) QMessageBox::warning(mscore, tr("Save Selection"), tr("Please select one or more measures"));
              return false;
              }
        QStringList fl;
        fl.append(tr("MuseScore 3 File") + " (*.mscz)");
        fl.append(tr("Uncompressed MuseScore 3 File") + " (*.mscx)");     // for debugging purposes
        QString saveDialogTitle = tr("Save Selection");
  
        QString saveDirectory;
        if (cs_->masterScore()->fileInfo()->exists())
              saveDirectory = cs_->masterScore()->fileInfo()->dir().path();
        else {
              QSettings set;
              if (mscore->lastSaveDirectory.isEmpty())
                    mscore->lastSaveDirectory = set.value("lastSaveDirectory", preferences.getString(PREF_APP_PATHS_MYSCORES)).toString();
              saveDirectory = mscore->lastSaveDirectory;
              }
  
        if (saveDirectory.isEmpty())
              saveDirectory = preferences.getString(PREF_APP_PATHS_MYSCORES);
  
        QString name   = QString("%1/%2.mscz").arg(saveDirectory).arg(cs_->title());
        QString filter = fl.join(";;");
        QString fn     = mscore->getSaveScoreName(saveDialogTitle, name, filter);
        if (fn.isEmpty())
              return false;
  
        QFileInfo fi(fn);
        mscore->lastSaveDirectory = fi.absolutePath();
  
        QString ext = fi.suffix();
        if (ext.isEmpty()) {
              QMessageBox::critical(mscore, tr("Save Selection"), tr("Cannot determine file type"));
              return false;
              }
        bool rv = true;
        try {
              cs_->saveCompressedFile(fi, true);
              }
        catch (QString s) {
              rv = false;
              QMessageBox::critical(this, tr("Save Selected"), s);
              }
        return rv;
        }
  
  //---------------------------------------------------------
  //   addImage
  //---------------------------------------------------------
  
  void MuseScore::addImage(Score* score, Element* e)
        {
        QString fn = QFileDialog::getOpenFileName(
           0,
           tr("Insert Image"),
           "",            // lastOpenPath,
           tr("All Supported Files") + " (*.svg *.jpg *.jpeg *.png);;" +
           tr("Scalable Vector Graphics") + " (*.svg);;" +
           tr("JPEG") + " (*.jpg *.jpeg);;" +
           tr("PNG Bitmap Graphic") + " (*.png)",
           0,
           preferences.getBool(PREF_UI_APP_USENATIVEDIALOGS) ? QFileDialog::Options() : QFileDialog::DontUseNativeDialog
           );
        if (fn.isEmpty())
              return;
  
        QFileInfo fi(fn);
        Image* s = new Image(score);
        QString suffix(fi.suffix().toLower());
  
        if (suffix == "svg")
              s->setImageType(ImageType::SVG);
        else if (suffix == "jpg" || suffix == "jpeg" || suffix == "png")
              s->setImageType(ImageType::RASTER);
        else
              return;
        s->load(fn);
        s->setParent(e);
        score->undoAddElement(s);
        }
  
  #if 0
  //---------------------------------------------------------
  //   trim
  //    returns copy of source with whitespace trimmed and margin added
  //---------------------------------------------------------
  
  static QRect trim(QImage source, int margin)
        {
        int w = source.width();
        int h = source.height();
        int x1 = w;
        int x2 = 0;
        int y1 = h;
        int y2 = 0;
        for (int x = 0; x < w; ++x) {
              for (int y = 0; y < h; ++y) {
                    QRgb c = source.pixel(x, y);
                    if (c != 0 && c != 0xffffffff) {
                          if (x < x1)
                                x1 = x;
                          if (x > x2)
                                x2 = x;
                          if (y < y1)
                                y1 = y;
                          if (y > y2)
                                y2 = y;
                          }
                    }
              }
        int x = qMax(x1 - margin, 0);
        int y = qMax(y1 - margin, 0);
        w = qMin(w, x2 + 1 + margin) - x;
        h = qMin(h, y2 + 1 + margin) - y;
        return QRect(x, y, w, h);
        }
  #endif
  
  //---------------------------------------------------------
  //   savePng
  //    return true on success.  Works with editor, shows additional windows.
  //---------------------------------------------------------
  
  bool MuseScore::savePng(Score* score, const QString& name)
        {
        const QList<Page*>& pl = score->pages();
        int pages = pl.size();
        int padding = QString("%1").arg(pages).size();
        bool overwrite = false;
        bool noToAll = false;
        for (int pageNumber = 0; pageNumber < pages; ++pageNumber) {
              QString fileName(name);
              if (fileName.endsWith(".png"))
                    fileName = fileName.left(fileName.size() - 4);
              fileName += QString("-%1.png").arg(pageNumber+1, padding, 10, QLatin1Char('0'));
              if (!converterMode) {
                    QFileInfo fip(fileName);
                    if(fip.exists() && !overwrite) {
                          if(noToAll)
                                continue;
                          QMessageBox msgBox( QMessageBox::Question, tr("Confirm Replace"),
                                tr("\"%1\" already exists.\nDo you want to replace it?\n").arg(QDir::toNativeSeparators(fileName)),
                                QMessageBox::Yes |  QMessageBox::YesToAll | QMessageBox::No |  QMessageBox::NoToAll);
                          msgBox.setButtonText(QMessageBox::Yes, tr("Replace"));
                          msgBox.setButtonText(QMessageBox::No, tr("Skip"));
                          msgBox.setButtonText(QMessageBox::YesToAll, tr("Replace All"));
                          msgBox.setButtonText(QMessageBox::NoToAll, tr("Skip All"));
                          int sb = msgBox.exec();
                          if(sb == QMessageBox::YesToAll) {
                                overwrite = true;
                                }
                          else if (sb == QMessageBox::NoToAll) {
                                noToAll = true;
                                continue;
                                }
                          else if (sb == QMessageBox::No)
                                continue;
                          }
                    }
              QFile f(fileName);
              if (!f.open(QIODevice::WriteOnly))
                    return false;
              bool rv = savePng(score, &f, pageNumber);
              if (!rv)
                    return false;
              }
        return true;
        }
  
  //---------------------------------------------------------
  //   savePng with options
  //    return true on success
  //---------------------------------------------------------
  
  bool MuseScore::savePng(Score* score, QIODevice* device, int pageNumber, bool drawPageBackground)
        {
        const bool screenshot = false;
        const bool transparent = preferences.getBool(PREF_EXPORT_PNG_USETRANSPARENCY) && !drawPageBackground;
        const double convDpi = preferences.getDouble(PREF_EXPORT_PNG_RESOLUTION);
        const int localTrimMargin = trimMargin;
        const QImage::Format format = QImage::Format_ARGB32_Premultiplied;
  
        bool rv = true;
        score->setPrinting(!screenshot);    // don’t print page break symbols etc.
        double pr = MScore::pixelRatio;
  
        QImage::Format f;
        if (format != QImage::Format_Indexed8)
            f = format;
        else
            f = QImage::Format_ARGB32_Premultiplied;
  
        const QList<Page*>& pl = score->pages();
  
        Page* page = pl.at(pageNumber);
        QRectF r;
        if (localTrimMargin >= 0) {
              QMarginsF margins(localTrimMargin, localTrimMargin, localTrimMargin, localTrimMargin);
              r = page->tbbox() + margins;
              }
        else
              r = page->abbox();
        int w = lrint(r.width()  * convDpi / DPI);
        int h = lrint(r.height() * convDpi / DPI);
  
        QImage printer(w, h, f);
        printer.setDotsPerMeterX(lrint((convDpi * 1000) / INCH));
        printer.setDotsPerMeterY(lrint((convDpi * 1000) / INCH));
  
        printer.fill(transparent ? 0 : 0xffffffff);
        double mag_ = convDpi / DPI;
        MScore::pixelRatio = 1.0 / mag_;
  
        QPainter p(&printer);
        p.setRenderHint(QPainter::Antialiasing, true);
        p.setRenderHint(QPainter::TextAntialiasing, true);
        p.scale(mag_, mag_);
        if (localTrimMargin >= 0)
              p.translate(-r.topLeft());
  
        QList< Element*> pel = page->elements();
        qStableSort(pel.begin(), pel.end(), elementLessThan);
        paintElements(p, pel);
         if (format == QImage::Format_Indexed8) {
              //convert to grayscale & respect alpha
              QVector<QRgb> colorTable;
              colorTable.push_back(QColor(0, 0, 0, 0).rgba());
              if (!transparent) {
                    for (int i = 1; i < 256; i++)
                          colorTable.push_back(QColor(i, i, i).rgb());
                    }
              else {
                    for (int i = 1; i < 256; i++)
                          colorTable.push_back(QColor(0, 0, 0, i).rgba());
                    }
              printer = printer.convertToFormat(QImage::Format_Indexed8, colorTable);
              }
        printer.save(device, "png");
        score->setPrinting(false);
        MScore::pixelRatio = pr;
        return rv;
        }
  
  //---------------------------------------------------------
  //   WallpaperPreview
  //---------------------------------------------------------
  
  WallpaperPreview::WallpaperPreview(QWidget* parent)
     : QFrame(parent)
        {
        _pixmap = 0;
        }
  
  //---------------------------------------------------------
  //   paintEvent
  //---------------------------------------------------------
  
  void WallpaperPreview::paintEvent(QPaintEvent* ev)
        {
        QPainter p(this);
        int fw = frameWidth();
        QRect r(frameRect().adjusted(fw, fw, -2*fw, -2*fw));
        if (_pixmap)
              p.drawTiledPixmap(r, *_pixmap);
        QFrame::paintEvent(ev);
        }
  
  //---------------------------------------------------------
  //   setImage
  //---------------------------------------------------------
  
  void WallpaperPreview::setImage(const QString& path)
        {
        qDebug("setImage <%s>", qPrintable(path));
        delete _pixmap;
        _pixmap = new QPixmap(path);
        update();
        }
  
  //---------------------------------------------------------
  //   getWallpaper
  //---------------------------------------------------------
  
  QString MuseScore::getWallpaper(const QString& caption)
        {
        QString filter = tr("Images") + " (*.jpg *.jpeg *.png);;" + tr("All") + " (*)";
        QString d = mscoreGlobalShare + "/wallpaper";
  
        if (preferences.getBool(PREF_UI_APP_USENATIVEDIALOGS)) {
              QString s = QFileDialog::getOpenFileName(
                 this,                            // parent
                 caption,
                 d,
                 filter
                 );
              return s;
              }
  
        if (loadBackgroundDialog == 0) {
              loadBackgroundDialog = new QFileDialog(this);
              loadBackgroundDialog->setFileMode(QFileDialog::ExistingFile);
              loadBackgroundDialog->setOption(QFileDialog::DontUseNativeDialog, true);
              loadBackgroundDialog->setWindowTitle(caption);
              loadBackgroundDialog->setNameFilter(filter);
              loadBackgroundDialog->setDirectory(d);
  
              QSettings set;
              loadBackgroundDialog->restoreState(set.value("loadBackgroundDialog").toByteArray());
              loadBackgroundDialog->setAcceptMode(QFileDialog::AcceptOpen);
  
              QSplitter* sp = loadBackgroundDialog->findChild<QSplitter*>("splitter");
              if (sp) {
                    WallpaperPreview* preview = new WallpaperPreview;
                    sp->addWidget(preview);
                    connect(loadBackgroundDialog, SIGNAL(currentChanged(const QString&)),
                       preview, SLOT(setImage(const QString&)));
                    }
              }
  
        //
        // setup side bar urls
        //
        QList<QUrl> urls;
        QString home = QDir::homePath();
        urls.append(QUrl::fromLocalFile(d));
        urls.append(QUrl::fromLocalFile(home));
        urls.append(QUrl::fromLocalFile(QDir::currentPath()));
        loadBackgroundDialog->setSidebarUrls(urls);
  
        if (loadBackgroundDialog->exec()) {
              QStringList result = loadBackgroundDialog->selectedFiles();
              return result.front();
              }
        return QString();
        }
  
  //---------------------------------------------------------
  //   MuseScore::saveSvg
  //---------------------------------------------------------
  // [TODO:
  // [In most of the functions above the Score* parameter is named "cs".
  // [But there is also a member variable in class MuseScoreCore called "cs"
  // [and it too is a Score*. If the member variable exists, the functions
  // [should not bother to pass it around, especially with multiple names.
  // [I have continued to use the "score" argument in this function, but
  // [I was just following the existing convention, inconsistent as it is.
  // [All the class MuseScore member functions should use the member variable.
  // [This file is currently undergoing a bunch of changes, and that's the kind
  // [of edit that must be coordinated with the MuseScore master code base.
  //
  bool MuseScore::saveSvg(Score* score, const QString& saveName)
        {
        const QList<Page*>& pl = score->pages();
        int pages = pl.size();
        int padding = QString("%1").arg(pages).size();
        bool overwrite = false;
        bool noToAll = false;
        for (int pageNumber = 0; pageNumber < pages; ++pageNumber) {
              QString fileName(saveName);
              if (fileName.endsWith(".svg"))
                    fileName = fileName.left(fileName.size() - 4);
              fileName += QString("-%1.svg").arg(pageNumber+1, padding, 10, QLatin1Char('0'));
              if (!converterMode) {
                    QFileInfo fip(fileName);
                    if(fip.exists() && !overwrite) {
                          if(noToAll)
                                continue;
                          QMessageBox msgBox( QMessageBox::Question, tr("Confirm Replace"),
                                tr("\"%1\" already exists.\nDo you want to replace it?\n").arg(QDir::toNativeSeparators(fileName)),
                                QMessageBox::Yes |  QMessageBox::YesToAll | QMessageBox::No |  QMessageBox::NoToAll);
                          msgBox.setButtonText(QMessageBox::Yes, tr("Replace"));
                          msgBox.setButtonText(QMessageBox::No, tr("Skip"));
                          msgBox.setButtonText(QMessageBox::YesToAll, tr("Replace All"));
                          msgBox.setButtonText(QMessageBox::NoToAll, tr("Skip All"));
                          int sb = msgBox.exec();
                          if(sb == QMessageBox::YesToAll) {
                                overwrite = true;
                                }
                          else if (sb == QMessageBox::NoToAll) {
                                noToAll = true;
                                continue;
                                }
                          else if (sb == QMessageBox::No)
                                continue;
                          }
                    }
              QFile f(fileName);
              if (!f.open(QIODevice::WriteOnly))
                    return false;
              bool rv = saveSvg(score, &f, pageNumber);
              if (!rv)
                    return false;
              }
        return true;
        }
  
  //---------------------------------------------------------
  //   MuseScore::saveSvg
  ///  Save a single page
  //---------------------------------------------------------
  
  bool MuseScore::saveSvg(Score* score, QIODevice* device, int pageNumber, bool drawPageBackground)
        {
        QString title(score->title());
        score->setPrinting(true);
        MScore::pdfPrinting = true;
        MScore::svgPrinting = true;
        const QList<Page*>& pl = score->pages();
        int pages = pl.size();
        double pr = MScore::pixelRatio;
  
        Page* page = pl.at(pageNumber);
        SvgGenerator printer;
        printer.setTitle(pages > 1 ? QString("%1 (%2)").arg(title).arg(pageNumber + 1) : title);
        printer.setOutputDevice(device);
  
        QRectF r;
        if (trimMargin >= 0) {
              QMarginsF margins(trimMargin, trimMargin, trimMargin, trimMargin);
              r = page->tbbox() + margins;
              }
        else
              r = page->abbox();
        qreal w = r.width();
        qreal h = r.height();
        printer.setSize(QSize(w, h));
        printer.setViewBox(QRectF(0, 0, w, h));
        QPainter p(&printer);
        p.setRenderHint(QPainter::Antialiasing, true);
        p.setRenderHint(QPainter::TextAntialiasing, true);
        if (trimMargin >= 0 && score->npages() == 1)
              p.translate(-r.topLeft());
        MScore::pixelRatio = DPI / printer.logicalDpiX();
        if (trimMargin >= 0)
               p.translate(-r.topLeft());
  
        if (drawPageBackground)
              p.fillRect(r, Qt::white);
  
        // 1st pass: StaffLines
        for  (System* s : page->systems()) {
              for (int i = 0, n = s->staves()->size(); i < n; i++) {
                    if (score->staff(i)->invisible() || !score->staff(i)->show())
                          continue;  // ignore invisible staves
                    if (s->staves()->isEmpty() || !s->staff(i)->show())
                          continue;
                    Measure* fm = s->firstMeasure();
                    if (!fm) // only boxes, hence no staff lines
                          continue;
  
                    // The goal here is to draw SVG staff lines more efficiently.
                    // MuseScore draws staff lines by measure, but for SVG they can
                    // generally be drawn once for each system. This makes a big
                    // difference for scores that scroll horizontally on a single
                    // page. But there are exceptions to this rule:
                    //
                    //   ~ One (or more) invisible measure(s) in a system/staff ~
                    //   ~ One (or more) elements of type HBOX or VBOX          ~
                    //
                    // In these cases the SVG staff lines for the system/staff
                    // are drawn by measure.
                    //
                    bool byMeasure = false;
                    for (MeasureBase* mb = fm; mb; mb = s->nextMeasure(mb)) {
                          if (!mb->isMeasure() || !toMeasure(mb)->visible(i)) {
                                byMeasure = true;
                                break;
                                }
                          }
                    if (byMeasure) { // Draw visible staff lines by measure
                          for (MeasureBase* mb = fm; mb; mb = s->nextMeasure(mb)) {
                                if (mb->isMeasure() && toMeasure(mb)->visible(i)) {
                                      StaffLines* sl = toMeasure(mb)->staffLines(i);
                                      printer.setElement(sl);
                                      paintElement(p, sl);
                                      }
                                }
                          }
                    else { // Draw staff lines once per system
                          StaffLines* firstSL = s->firstMeasure()->staffLines(i)->clone();
                          StaffLines*  lastSL =  s->lastMeasure()->staffLines(i);
  
                          qreal lastX =  lastSL->bbox().right()
                                      +  lastSL->pagePos().x()
                                      - firstSL->pagePos().x();
                          QVector<QLineF>& lines = firstSL->getLines();
                          for (int l = 0, c = lines.size(); l < c; l++)
                                lines[l].setP2(QPointF(lastX, lines[l].p2().y()));
  
                          printer.setElement(firstSL);
                          paintElement(p, firstSL);
                          }
                    }
              }
        // 2nd pass: the rest of the elements
        QList<Element*> pel = page->elements();
        qStableSort(pel.begin(), pel.end(), elementLessThan);
        ElementType eType;
        for (const Element* e : pel) {
              // Always exclude invisible elements
              if (!e->visible())
                    continue;
  
              eType = e->type();
              switch (eType) { // In future sub-type code, this switch() grows, and eType gets used
              case ElementType::STAFF_LINES : // Handled in the 1st pass above
                    continue; // Exclude from 2nd pass
                    break;
              default:
                    break;
              } // switch(eType)
  
              // Set the Element pointer inside SvgGenerator/SvgPaintEngine
              printer.setElement(e);
  
              // Paint it
              paintElement(p, e);
              }
        p.end(); // Writes MuseScore SVG file to disk, finally
  
        // Clean up and return
        MScore::pixelRatio = pr;
        score->setPrinting(false);
        MScore::pdfPrinting = false;
        MScore::svgPrinting = false;
        return true;
        }
  
  //---------------------------------------------------------
  //   createThumbnail
  //---------------------------------------------------------
  
  static QPixmap createThumbnail(const QString& name)
        {
        if (!(name.endsWith(".mscx") || name.endsWith(".mscz")))
              return QPixmap();
        MasterScore* score = new MasterScore(MScore::defaultStyle());
        Score::FileError error = readScore(score, name, true);
        if (error != Score::FileError::FILE_NO_ERROR || !score->firstMeasure()) {
              delete score;
              return QPixmap();
              }
        score->doLayout();
        QImage pm = score->createThumbnail();
        delete score;
        return QPixmap::fromImage(pm);
        }
  
  //---------------------------------------------------------
  //   extractThumbnail
  //---------------------------------------------------------
  
  QPixmap MuseScore::extractThumbnail(const QString& name)
        {
        QPixmap pm; //  = icons[File_ICON].pixmap(QSize(100,140));
        if (!name.endsWith(".mscz"))
              return createThumbnail(name);
        MQZipReader uz(name);
        if (!uz.exists()) {
              qDebug("extractThumbnail: <%s> not found", qPrintable(name));
              return pm;
              }
        QByteArray ba = uz.fileData("Thumbnails/thumbnail.png");
        if (ba.isEmpty())
              return createThumbnail(name);
        pm.loadFromData(ba, "PNG");
        return pm;
        }
  
  //---------------------------------------------------------
  //   saveMetadataJSON
  //---------------------------------------------------------
  
  bool MuseScore::saveMetadataJSON(Score* score, const QString& name)
        {
        QFile f(name);
        if (!f.open(QIODevice::WriteOnly))
              return false;
  
        QJsonObject json = saveMetadataJSON(score);
        QJsonDocument saveDoc(json);
        f.write(saveDoc.toJson());
        f.close();
        return true;
        }
  
  //---------------------------------------------------------
  //   findTextByType
  //    @data must contain std::pair<Tid, QStringList*>*
  //          Tid specifies text style
  //          QStringList* specifies the container to keep found text
  //
  //    For usage with Score::scanElements().
  //    Finds all text elements with specified style.
  //---------------------------------------------------------
  static void findTextByType(void* data, Element* element)
        {
        if (!element->isTextBase())
              return;
        TextBase* text = toTextBase(element);
        auto* typeStringsData = static_cast<std::pair<Tid, QStringList*>*>(data);
        if (text->tid() == typeStringsData->first) {
              // or if score->getTextStyleUserName().contains("Title") ???
              // That is bad since it may be localized
              QStringList* titleStrings = typeStringsData->second;
              Q_ASSERT(titleStrings);
              titleStrings->append(text->plainText());
              }
        }
  
  QJsonObject MuseScore::saveMetadataJSON(Score* score)
        {
        auto boolToString = [](bool b) { return b ? "true" : "false"; };
        QJsonObject json;
  
        // title
        QString title;
        Text* t = score->getText(Tid::TITLE);
        if (t)
              title = t->plainText();
        if (title.isEmpty())
              title = score->metaTag("workTitle");
        if (title.isEmpty())
              title = score->title();
        json.insert("title", title);
  
        // subtitle
        QString subtitle;
        t = score->getText(Tid::SUBTITLE);
        if (t)
              subtitle = t->plainText();
        json.insert("subtitle", subtitle);
  
        // composer
        QString composer;
        t = score->getText(Tid::COMPOSER);
        if (t)
              composer = t->plainText();
        if (composer.isEmpty())
              composer = score->metaTag("composer");
        json.insert("composer", composer);
  
        // poet
        QString poet;
        t = score->getText(Tid::POET);
        if (t)
              poet = t->plainText();
        if (poet.isEmpty())
              poet = score->metaTag("lyricist");
        json.insert("poet", poet);
  
        json.insert("mscoreVersion", score->mscoreVersion());
        json.insert("fileVersion", score->mscVersion());
  
        json.insert("pages", score->npages());
        json.insert("measures", score->nmeasures());
        json.insert("hasLyrics", boolToString(score->hasLyrics()));
        json.insert("hasHarmonies", boolToString(score->hasHarmonies()));
        json.insert("keysig", score->keysig());
  
        // timeSig
        QString timeSig;
        int staves = score->nstaves();
        int tracks = staves * VOICES;
        Segment* tss = score->firstSegmentMM(SegmentType::TimeSig);
        if (tss) {
              Element* e = nullptr;
              for (int track = 0; track < tracks; ++track) {
                    e = tss->element(track);
                    if (e) break;
                    }
              if (e && e->isTimeSig()) {
                    TimeSig* ts = toTimeSig(e);
                    timeSig = QString("%1/%2").arg(ts->numerator()).arg(ts->denominator());
                    }
              }
        json.insert("timesig", timeSig);
  
        json.insert("duration", score->duration());
        json.insert("lyrics", score->extractLyrics());
  
        // tempo
         int tempo = 0;
         QString tempoText;
         for (Segment* seg = score->firstSegmentMM(SegmentType::All); seg; seg = seg->next1MM()) {
               auto annotations = seg->annotations();
               for (Element* a : annotations) {
                     if (a && a->isTempoText()) {
                           TempoText* tt = toTempoText(a);
                           tempo = round(tt->tempo() * 60);
                           tempoText = tt->xmlText();
                           }
                     }
               }
        json.insert("tempo", tempo);
        json.insert("tempoText", tempoText);
  
        // parts
        QJsonArray jsonPartsArray;
        for (Part* p : score->parts()) {
              QJsonObject jsonPart;
              jsonPart.insert("name", p->longName().replace("\n", ""));
              int midiProgram = p->midiProgram();
              if (p->midiChannel() == 9)
                  midiProgram = 128;
              jsonPart.insert("program", midiProgram);
              jsonPart.insert("instrumentId", p->instrumentId());
              jsonPart.insert("lyricCount", p->lyricCount());
              jsonPart.insert("harmonyCount", p->harmonyCount());
              jsonPart.insert("hasPitchedStaff", boolToString(p->hasPitchedStaff()));
              jsonPart.insert("hasTabStaff", boolToString(p->hasTabStaff()));
              jsonPart.insert("hasDrumStaff", boolToString(p->hasDrumStaff()));
              jsonPart.insert("isVisible", boolToString(p->show()));
              jsonPartsArray.append(jsonPart);
              }
        json.insert("parts", jsonPartsArray);
  
        // pageFormat
        QJsonObject jsonPageformat;
        jsonPageformat.insert("height",round(score->styleD(Sid::pageHeight) * INCH));
        jsonPageformat.insert("width", round(score->styleD(Sid::pageWidth) * INCH));
        jsonPageformat.insert("twosided", boolToString(score->styleB(Sid::pageTwosided)));
        json.insert("pageFormat", jsonPageformat);
  
        //text frames metadata
        QJsonObject jsonTypeData;
        static std::vector<std::pair<QString, Tid>> namesTypesList {
              {"titles", Tid::TITLE},
              {"subtitles", Tid::SUBTITLE},
              {"composers", Tid::COMPOSER},
              {"poets", Tid::POET}
              };
        for (auto nameType : namesTypesList) {
              QJsonArray typeData;
              QStringList typeTextStrings;
              std::pair<Tid, QStringList*> extendedTitleData = std::make_pair(nameType.second, &typeTextStrings);
              score->scanElements(&extendedTitleData, findTextByType);
              for (auto typeStr : typeTextStrings)
                    typeData.append(typeStr);
              jsonTypeData.insert(nameType.first, typeData);
              }
        json.insert("textFramesData", jsonTypeData);
  
        return json;
        }
  
  class CustomJsonWriter
  {
  public:
        CustomJsonWriter(const QString& filePath)
        {
        jsonFormatFile.setFileName(filePath);
        jsonFormatFile.open(QIODevice::WriteOnly);
        jsonFormatFile.write("{\n");
        }
  
        ~CustomJsonWriter()
        {
        jsonFormatFile.write("\n}\n");
        jsonFormatFile.close();
        }
  
        void addKey(const char* arrayName)
        {
        jsonFormatFile.write("\"");
        jsonFormatFile.write(arrayName);
        jsonFormatFile.write("\": ");
        }
  
        void addValue(const QByteArray& data, bool lastJsonElement = false, bool isJson = false)
        {
        if (!isJson)
              jsonFormatFile.write("\"");
        jsonFormatFile.write(data);
        if (!isJson)
              jsonFormatFile.write("\"");
        if (!lastJsonElement)
              jsonFormatFile.write(",\n");
        }
  
        void openArray()
        {
        jsonFormatFile.write(" [");
        }
  
        void closeArray(bool lastJsonElement = false)
        {
        jsonFormatFile.write("]");
        if (!lastJsonElement)
              jsonFormatFile.write(",");
        jsonFormatFile.write("\n");
        }
  
  private:
        QFile jsonFormatFile;
  };
  
  //---------------------------------------------------------
  //   exportMp3AsJSON
  //---------------------------------------------------------
  
  bool MuseScore::exportMp3AsJSON(const QString& inFilePath, const QString& outFilePath)
        {
        std::unique_ptr<MasterScore> score(mscore->readScore(inFilePath));
        if (!score)
              return false;
  
        CustomJsonWriter jsonWriter(outFilePath);
        jsonWriter.addKey("mp3");
        //export score audio
        QByteArray mp3Data;
        QBuffer mp3Device(&mp3Data);
        mp3Device.open(QIODevice::ReadWrite);
        bool dummy = false;
        mscore->saveMp3(score.get(), &mp3Device, dummy);
        jsonWriter.addValue(mp3Data.toBase64(), true);
        return true;
        }
  
  QByteArray MuseScore::exportPdfAsJSON(Score* score)
        {
        QPrinter printer;
        auto tempPdfFileName = "/tmp/MUTempPdf.pdf";
        printer.setOutputFileName(tempPdfFileName);
        mscore->savePdf(score, printer);
        QFile tempPdfFile(tempPdfFileName);
        QByteArray pdfData;
        if (tempPdfFile.open(QIODevice::ReadWrite)) {
              pdfData = tempPdfFile.readAll();
              tempPdfFile.close();
              tempPdfFile.remove();
              }
  
        return pdfData.toBase64();
        }
  
  //---------------------------------------------------------
  //   exportAllMediaFiles
  //---------------------------------------------------------
  
  bool MuseScore::exportAllMediaFiles(const QString& inFilePath, const QString& outFilePath)
        {
        std::unique_ptr<MasterScore> score(mscore->readScore(inFilePath));
        if (!score)
              return false;
  
        score->switchToPageMode();
  
        //// JSON specification ///////////////////////////
        //jsonForMedia["pngs"] = pngsJsonArray;
        //jsonForMedia["mposXML"] = mposJson;
        //jsonForMedia["sposXML"] = sposJson;
        //jsonForMedia["pdf"] = pdfJson;
        //jsonForMedia["svgs"] = svgsJsonArray;
        //jsonForMedia["midi"] = midiJson;
        //jsonForMedia["mxml"] = mxmlJson;
        //jsonForMedia["metadata"] = mdJson;
        ///////////////////////////////////////////////////
  
        bool res = true;
        CustomJsonWriter jsonWriter(outFilePath);
        //export score pngs and svgs
        jsonWriter.addKey("pngs");
        jsonWriter.openArray();
        for (int i = 0; i < score->pages().size(); ++i) {
              QByteArray pngData;
              QBuffer pngDevice(&pngData);
              pngDevice.open(QIODevice::ReadWrite);
              res &= mscore->savePng(score.get(), &pngDevice, i, /* drawPageBackground */ true);
              bool lastArrayValue = ((score->pages().size() - 1) == i);
              jsonWriter.addValue(pngData.toBase64(), lastArrayValue);
              }
        jsonWriter.closeArray();
  
        jsonWriter.addKey("svgs");
        jsonWriter.openArray();
        for (int i = 0; i < score->pages().size(); ++i) {
              QByteArray svgData;
              QBuffer svgDevice(&svgData);
              svgDevice.open(QIODevice::ReadWrite);
              res &= mscore->saveSvg(score.get(), &svgDevice, i, /* drawPageBackground */ true);
              bool lastArrayValue = ((score->pages().size() - 1) == i);
              jsonWriter.addValue(svgData.toBase64(), lastArrayValue);
              }
        jsonWriter.closeArray();
  
        {
        //export score .spos
        QByteArray partDataPos;
        QBuffer partPosDevice(&partDataPos);
        partPosDevice.open(QIODevice::ReadWrite);
        savePositions(score.get(), &partPosDevice, true);
        jsonWriter.addKey("sposXML");
        jsonWriter.addValue(partDataPos.toBase64());
        partPosDevice.close();
        partDataPos.clear();
  
        //export score .mpos
        partPosDevice.open(QIODevice::ReadWrite);
        savePositions(score.get(), &partPosDevice, false);
        jsonWriter.addKey("mposXML");
        jsonWriter.addValue(partDataPos.toBase64());
        }
  
        //export score pdf
        jsonWriter.addKey("pdf");
        jsonWriter.addValue(exportPdfAsJSON(score.get()));
  
        {
        //export score midi
        QByteArray midiData;
        QBuffer midiDevice(&midiData);
        midiDevice.open(QIODevice::ReadWrite);
        res &= mscore->saveMidi(score.get(), &midiDevice);
        jsonWriter.addKey("midi");
        jsonWriter.addValue(midiData.toBase64());
        }
  
        {
        //export musicxml
        QByteArray mxmlData;
        QBuffer mxmlDevice(&mxmlData);
        mxmlDevice.open(QIODevice::ReadWrite);
        res &= saveMxl(score.get(), &mxmlDevice);
        jsonWriter.addKey("mxml");
        jsonWriter.addValue(mxmlData.toBase64());
        }
  
        //export metadata
        QJsonDocument doc(mscore->saveMetadataJSON(score.get()));
        jsonWriter.addKey("metadata");
        jsonWriter.addValue(doc.toJson(QJsonDocument::Compact), true, true);
  
        return res;
        }
  
  //---------------------------------------------------------
  //   exportScoreMetadata
  //---------------------------------------------------------
  
  bool MuseScore::exportScoreMetadata(const QString& inFilePath, const QString& outFilePath)
        {
        std::unique_ptr<MasterScore> score(mscore->readScore(inFilePath));
        if (!score)
              return false;
  
        score->switchToPageMode();
  
        //// JSON specification ///////////////////////////
        //jsonForMedia["metadata"] = mdJson;
        ///////////////////////////////////////////////////
  
        CustomJsonWriter jsonWriter(outFilePath);
  
        //export metadata
        QJsonDocument doc(mscore->saveMetadataJSON(score.get()));
        jsonWriter.addKey("metadata");
        jsonWriter.addValue(doc.toJson(QJsonDocument::Compact), true, true);
  
        return true;
        }
  
  //---------------------------------------------------------
  //   exportTransposedScoreToJSON
  //---------------------------------------------------------
  
  bool MuseScore::exportTransposedScoreToJSON(const QString& inFilePath, const QString& transposeOptions, const QString& outFilePath)
        {
        QJsonDocument doc = QJsonDocument::fromJson(transposeOptions.toUtf8());
        if (!doc.isObject()) {
              qCritical("Transpose options JSON is not an object: %s", qUtf8Printable(transposeOptions));
              return false;
              }
  
        QJsonObject options = doc.object();
  
        TransposeMode mode;
        const QString modeName = options["mode"].toString();
        if (modeName == "by_key" || modeName == "to_key") // "by_key" for backwards compatibility
              mode = TransposeMode::TO_KEY;
        else if (modeName == "by_interval")
              mode = TransposeMode::BY_INTERVAL;
        else if (modeName == "diatonically")
              mode = TransposeMode::DIATONICALLY;
        else {
              qCritical("Transpose: invalid \"mode\" option: %s", qUtf8Printable(modeName));
              return false;
              }
  
        TransposeDirection direction;
        const QString directionName = options["direction"].toString();
        if (directionName == "up")
              direction = TransposeDirection::UP;
        else if (directionName == "down")
              direction = TransposeDirection::DOWN;
        else if (directionName == "closest")
              direction = TransposeDirection::CLOSEST;
        else {
              qCritical("Transpose: invalid \"direction\" option: %s", qUtf8Printable(directionName));
              return false;
              }
  
        constexpr int defaultKey = int(Key::INVALID);
        const Key targetKey = Key(options["targetKey"].toInt(defaultKey));
        if (mode == TransposeMode::TO_KEY) {
              const bool targetKeyValid = int(Key::MIN) <= int(targetKey) && int(targetKey) <= int(Key::MAX);
              if (!targetKeyValid) {
                    qCritical("Transpose: invalid targetKey: %d", int(targetKey));
                    return false;
                    }
              }
  
        const int transposeInterval = options["transposeInterval"].toInt(-1);
        if (mode != TransposeMode::TO_KEY) {
              const bool transposeIntervalValid = -1 < transposeInterval && transposeInterval < intervalListSize;
              if (!transposeIntervalValid) {
                    qCritical("Transpose: invalid transposeInterval: %d", transposeInterval);
                    return false;
                    }
              }
  
        const bool transposeKeySignatures = options["transposeKeySignatures"].toBool();
        const bool transposeChordNames = options["transposeChordNames"].toBool();
        const bool useDoubleSharpsFlats = options["useDoubleSharpsFlats"].toBool();
  
        std::unique_ptr<MasterScore> score(mscore->readScore(inFilePath));
        if (!score)
              return false;
  
        score->switchToPageMode();
        score->cmdSelectAll();
  
        score->startCmd();
        const bool transposed = score->transpose(mode, direction, targetKey, transposeInterval, transposeKeySignatures, transposeChordNames, useDoubleSharpsFlats);
        if (!transposed) {
              qCritical("Transposition failed");
              return false;
              }
        score->endCmd();
  
        bool res = true;
        CustomJsonWriter jsonWriter(outFilePath);
  
        // export mscz
        {
        jsonWriter.addKey("mscz");
        bool saved = false;
        QTemporaryFile tmpFile(QString("%1_transposed.XXXXXX.mscz").arg(score->title()));
        if (tmpFile.open()) {
              QFileInfo fi(tmpFile.fileName());
              saved = score->Score::saveCompressedFile(&tmpFile, fi, /* onlySelection */ false);
              tmpFile.close();
              tmpFile.open();
              jsonWriter.addValue(tmpFile.readAll().toBase64());
              tmpFile.close();
              }
  
        if (!saved) {
              qCritical("Transpose: adding mscz failed");
              jsonWriter.addValue("");
              res = false;
              }
        }
  
        // export score pdf
        jsonWriter.addKey("pdf");
        jsonWriter.addValue(exportPdfAsJSON(score.get()), /* lastJsonElement */ true);
  
        return res;
        }
  
  }
  
  