1 //=============================================================================
2 //  MuseScore
3 //  Music Composition & Notation
4 //
5 //  Copyright (C) 2002-2014 Werner Schweer
6 //
7 //  This program is free software; you can redistribute it and/or modify
8 //  it under the terms of the GNU General Public License version 2
9 //  as published by the Free Software Foundation and appearing in
10 //  the file LICENCE.GPL
11 //=============================================================================
12 
13 /**
14  File handling: loading and saving.
15  */
16 
17 #include <QFileInfo>
18 #include <QMessageBox>
19 
20 #include "cloud/loginmanager.h"
21 
22 #include "config.h"
23 #include "globals.h"
24 #include "musescore.h"
25 #include "scoreview.h"
26 
27 #include "audio/exports/exportmidi.h"
28 
29 #include "libmscore/xml.h"
30 #include "libmscore/element.h"
31 #include "libmscore/note.h"
32 #include "libmscore/rest.h"
33 #include "libmscore/sig.h"
34 #include "libmscore/clef.h"
35 #include "libmscore/key.h"
36 #include "instrdialog.h"
37 #include "libmscore/score.h"
38 #include "libmscore/page.h"
39 #include "libmscore/dynamic.h"
40 #include "file.h"
41 #include "libmscore/style.h"
42 #include "libmscore/tempo.h"
43 #include "libmscore/select.h"
44 #include "preferences.h"
45 #include "playpanel.h"
46 #include "libmscore/staff.h"
47 #include "libmscore/part.h"
48 #include "libmscore/utils.h"
49 #include "libmscore/barline.h"
50 #include "palette.h"
51 #include "symboldialog.h"
52 #include "libmscore/slur.h"
53 #include "libmscore/hairpin.h"
54 #include "libmscore/ottava.h"
55 #include "libmscore/textline.h"
56 #include "libmscore/pedal.h"
57 #include "libmscore/trill.h"
58 #include "libmscore/volta.h"
59 #include "newwizard.h"
60 #include "libmscore/timesig.h"
61 #include "libmscore/box.h"
62 #include "libmscore/excerpt.h"
63 #include "libmscore/system.h"
64 #include "libmscore/tuplet.h"
65 #include "libmscore/keysig.h"
66 #include "zoombox.h"
67 #include "libmscore/measure.h"
68 #include "libmscore/undo.h"
69 #include "libmscore/repeatlist.h"
70 #include "scoretab.h"
71 #include "libmscore/beam.h"
72 #include "libmscore/stafftype.h"
73 #include "seq.h"
74 #include "libmscore/revisions.h"
75 #include "libmscore/lyrics.h"
76 #include "libmscore/segment.h"
77 #include "libmscore/tempotext.h"
78 #include "libmscore/sym.h"
79 #include "libmscore/image.h"
80 #include "libmscore/stafflines.h"
81 #include "audio/midi/msynthesizer.h"
82 #include "svggenerator.h"
83 #include "scorePreview.h"
84 #include "scorecmp/scorecmp.h"
85 #include "extension.h"
86 #include "tourhandler.h"
87 #include "preferences.h"
88 #include "libmscore/instrtemplate.h"
89 
90 #ifdef OMR
91 #include "omr/omr.h"
92 #include "omr/omrpage.h"
93 #include "omr/importpdf.h"
94 #endif
95 
96 #include "libmscore/chordlist.h"
97 #include "libmscore/mscore.h"
98 #include "thirdparty/qzip/qzipreader_p.h"
99 #include "migration/scoremigrator_3_6.h"
100 #include "migration/handlers/styledefaultshandler.h"
101 #include "migration/handlers/lelandstylehandler.h"
102 #include "migration/handlers/edwinstylehandler.h"
103 #include "migration/handlers/resetallelementspositionshandler.h"
104 
105 
106 namespace Ms {
107 
108 extern void importSoundfont(QString name);
109 
110 extern MasterSynthesizer* synti;
111 
112 //---------------------------------------------------------
113 //   paintElement(s)
114 //---------------------------------------------------------
115 
paintElement(QPainter & p,const Element * e)116 static void paintElement(QPainter& p, const Element* e)
117       {
118       QPointF pos(e->pagePos());
119       p.translate(pos);
120       e->draw(&p);
121       p.translate(-pos);
122       }
123 
paintElements(QPainter & p,const QList<Element * > & el)124 static void paintElements(QPainter& p, const QList<Element*>& el)
125       {
126       for (Element* e : el) {
127             if (!e->visible())
128                   continue;
129             paintElement(p, e);
130             }
131       }
132 
133 //---------------------------------------------------------
134 //   createDefaultFileName
135 //---------------------------------------------------------
136 
createDefaultFileName(QString fn)137 static QString createDefaultFileName(QString fn)
138       {
139       //
140       // special characters in filenames are a constant source
141       // of trouble, this replaces some of them common in german:
142       //
143       fn = fn.simplified();
144       fn = fn.replace(QChar(' '),  "_");
145       fn = fn.replace(QChar('\n'), "_");
146       fn = fn.replace(QChar(0xe4), "ae"); // &auml;
147       fn = fn.replace(QChar(0xf6), "oe"); // &ouml;
148       fn = fn.replace(QChar(0xfc), "ue"); // &uuml;
149       fn = fn.replace(QChar(0xdf), "ss"); // &szlig;
150       fn = fn.replace(QChar(0xc4), "Ae"); // &Auml;
151       fn = fn.replace(QChar(0xd6), "Oe"); // &Ouml;
152       fn = fn.replace(QChar(0xdc), "Ue"); // &Uuml;
153       fn = fn.replace(QChar(0x266d),"b"); // musical flat sign, happen in instrument names, so can happen in part (file) names
154       fn = fn.replace(QChar(0x266f),"#"); // musical sharp sign, can happen in titles, so can happen in score (file) names
155       fn = fn.replace( QRegExp( "[" + QRegExp::escape( "\\/:*?\"<>|" ) + "]" ), "_" ); //FAT/NTFS special chars
156       return fn;
157       }
158 
saveFilename(QString fn)159 QString MuseScore::saveFilename(QString fn)
160       {
161       return createDefaultFileName(fn);
162       }
163 
164 //---------------------------------------------------------
165 //   readScoreError
166 //    if "ask" is true, ask to ignore; returns true if
167 //    ignore is pressed by user
168 //    returns true if -f is used in converter mode
169 //---------------------------------------------------------
170 
readScoreError(const QString & name,Score::FileError error,bool ask)171 static bool readScoreError(const QString& name, Score::FileError error, bool ask)
172       {
173 //printf("<%s> %d\n", qPrintable(name), int(error));
174       QString msg = QObject::tr("Cannot read file %1:\n").arg(name);
175       QString detailedMsg;
176       bool canIgnore = false;
177       switch(error) {
178             case Score::FileError::FILE_NO_ERROR:
179                   return false;
180             case Score::FileError::FILE_BAD_FORMAT:
181                   msg +=  QObject::tr("bad format");
182                   detailedMsg = MScore::lastError;
183                   break;
184             case Score::FileError::FILE_UNKNOWN_TYPE:
185                   msg += QObject::tr("unknown type");
186                   break;
187             case Score::FileError::FILE_NO_ROOTFILE:
188                   break;
189             case Score::FileError::FILE_TOO_OLD:
190                   msg += QObject::tr("It was last saved with a version older than 2.0.0.\n"
191                                      "You can convert this score by opening and then\n"
192                                      "saving with MuseScore version 2.x.\n"
193                                      "Visit the %1MuseScore download page%2 to obtain such a 2.x version.")
194                               .arg("<a href=\"https://musescore.org/download#older-versions\">")
195                               .arg("</a>");
196                   canIgnore = true;
197                   break;
198             case Score::FileError::FILE_TOO_NEW:
199                   msg += QObject::tr("This score was saved using a newer version of MuseScore.\n"
200                                      "Visit the %1MuseScore website%2 to obtain the latest version.")
201                               .arg("<a href=\"https://musescore.org\">")
202                               .arg("</a>");
203                   canIgnore = true;
204                   break;
205             case Score::FileError::FILE_NOT_FOUND:
206                   msg = QObject::tr("File \"%1\" not found.").arg(name);
207                   break;
208             case Score::FileError::FILE_CORRUPTED:
209                   msg = QObject::tr("File \"%1\" corrupted.").arg(name);
210                   detailedMsg = MScore::lastError;
211                   canIgnore = true;
212                   break;
213             case Score::FileError::FILE_CRITICALLY_CORRUPTED:
214                   msg = QObject::tr("File \"%1\" is critically corrupted and cannot be processed.").arg(name);
215                   detailedMsg = MScore::lastError;
216                   break;
217             case Score::FileError::FILE_OLD_300_FORMAT:
218                   msg += QObject::tr("It was last saved with a developer version of 3.0.\n");
219                   canIgnore = true;
220                   break;
221             case Score::FileError::FILE_ERROR:
222             case Score::FileError::FILE_OPEN_ERROR:
223             default:
224                   msg += MScore::lastError;
225                   break;
226             }
227       if (converterMode && canIgnore && ignoreWarnings) {
228             fprintf(stderr, "%s\n\nWarning ignored, forcing score to load\n", qPrintable(msg));
229             return true;
230             }
231        if (converterMode || pluginMode) {
232             fprintf(stderr, "%s\n", qPrintable(msg));
233             return false;
234             }
235       QMessageBox msgBox;
236       msgBox.setWindowTitle(QObject::tr("Load Error"));
237       msgBox.setText(msg.replace("\n", "<br/>"));
238       msgBox.setDetailedText(detailedMsg);
239       msgBox.setTextFormat(Qt::RichText);
240       if (canIgnore && ask)  {
241             msgBox.setIcon(QMessageBox::Warning);
242             msgBox.setStandardButtons(
243                QMessageBox::Cancel | QMessageBox::Ignore
244                );
245             return msgBox.exec() == QMessageBox::Ignore;
246             }
247       else {
248             msgBox.setIcon(QMessageBox::Critical);
249             msgBox.setStandardButtons(
250                QMessageBox::Ok
251                );
252             msgBox.exec();
253             }
254       return false;
255       }
256 
257 //---------------------------------------------------------
258 //   checkDirty
259 //    if dirty, save score
260 //    return true on cancel
261 //---------------------------------------------------------
262 
checkDirty(MasterScore * s)263 bool MuseScore::checkDirty(MasterScore* s)
264       {
265       if (s->dirty() || (s->created() && !s->startedEmpty())) {
266             QMessageBox::StandardButton n = QMessageBox::warning(this, tr("MuseScore"),
267                tr("Save changes to the score \"%1\"\n"
268                "before closing?").arg(s->fileInfo()->completeBaseName()),
269                QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel,
270                QMessageBox::Save);
271             if (n == QMessageBox::Save) {
272                   if (s->masterScore()->isSavable()) {
273                         if (!saveFile(s))
274                               return true;
275                         }
276                   else {
277                         if (!saveAs(s, false))
278                               return true;
279                         }
280 
281                   }
282             else if (n == QMessageBox::Cancel)
283                   return true;
284             }
285       return false;
286       }
287 
288 //---------------------------------------------------------
289 //   openFiles
290 //---------------------------------------------------------
291 
openFiles(bool switchTab,bool singleFile)292 void MuseScore::openFiles(bool switchTab, bool singleFile)
293       {
294       QString allExt = "*.mscz *.mscx *.mxl *.musicxml *.xml *.mid *.midi *.kar *.md *.mgu *.sgu *.cap *.capx *.ove *.scw *.bmw *.bww *.gtp *.gp3 *.gp4 *.gp5 *.gpx *.gp *.ptb *.mscz, *.mscx,";
295 #ifdef AVSOMR
296       allExt += " *.msmr"; // omr project with omr data and musicxml or score
297 #endif
298 
299       QStringList filter;
300       filter << tr("All Supported Files") + " (" + allExt + ")"
301              << tr("MuseScore Files") + " (*.mscz *.mscx)"
302              << tr("MusicXML Files") + " (*.mxl *.musicxml *.xml)"
303              << tr("MIDI Files") + " (*.mid *.midi *.kar)"
304              << tr("MuseData Files") + " (*.md)"
305              << tr("Capella Files") + " (*.cap *.capx)"
306              << tr("BB Files (experimental)") + " (*.mgu *.sgu)"
307              << tr("Overture / Score Writer Files (experimental)") + " (*.ove *.scw)"
308              << tr("Bagpipe Music Writer Files (experimental)") + " (*.bmw *.bww)"
309              << tr("Guitar Pro Files") + " (*.gtp *.gp3 *.gp4 *.gp5 *.gpx *.gp)"
310              << tr("Power Tab Editor Files (experimental)") + " (*.ptb)"
311              << tr("MuseScore Backup Files") + " (*.mscz, *.mscx,)";
312 
313       doLoadFiles(filter, switchTab, singleFile);
314       }
315 
316 //---------------------------------------------------------
317 //   importFiles
318 //---------------------------------------------------------
319 
importScore(bool switchTab,bool singleFile)320 void MuseScore::importScore(bool switchTab, bool singleFile)
321       {
322 #ifndef AVSOMR
323       Q_UNUSED(switchTab);
324       Q_UNUSED(singleFile);
325       openExternalLink("https://musescore.com/import");
326 #else
327       QStringList filter;
328       filter << tr("Optical Music Recognition") + " (*.pdf *.png *.jpg)";
329       doLoadFiles(filter, switchTab, singleFile);
330 #endif
331       }
332 
333 //---------------------------------------------------------
334 //   doLoadFiles
335 //---------------------------------------------------------
336 
337 /**
338  Create a modal file open dialog.
339  If a file is selected, load it.
340  Handles the GUI's file-open action.
341  */
342 
doLoadFiles(const QStringList & filter,bool switchTab,bool singleFile)343 void MuseScore::doLoadFiles(const QStringList& filter, bool switchTab, bool singleFile)
344       {
345       QString filterStr = filter.join(";;");
346       QStringList files = getOpenScoreNames(filterStr, tr("Load Score"), singleFile);
347       for (const QString& s : files)
348             openScore(s, switchTab);
349       mscore->tourHandler()->showDelayedWelcomeTour();
350       }
351 
askAboutApplyingEdwinIfNeed(const QString & fileSuffix)352 void MuseScore::askAboutApplyingEdwinIfNeed(const QString& fileSuffix)
353 {
354     if (MScore::noGui) {
355         return;
356     }
357 
358     static const QSet<QString> suitedSuffixes {
359         "xml",
360         "musicxml",
361         "mxl"
362     };
363 
364     if (!suitedSuffixes.contains(fileSuffix)) {
365         return;
366     }
367 
368     if (preferences.getBool(PREF_MIGRATION_DO_NOT_ASK_ME_AGAIN_XML)) {
369         return;
370     }
371 
372     QMessageBox dialog;
373     dialog.setWindowTitle(QObject::tr("MuseScore"));
374     dialog.setText(QObject::tr("Would you like to apply our default typeface (Edwin) to this score?"));
375     QPushButton* noButton = dialog.addButton(QObject::tr("Do not apply Edwin"), QMessageBox::NoRole);
376     QPushButton* yesButton = dialog.addButton(QObject::tr("Apply Edwin"), QMessageBox::YesRole);
377     dialog.setDefaultButton(noButton);
378 
379     QCheckBox askAgainCheckbox(QObject::tr("Remember my choice and don't ask again"));
380     dialog.setCheckBox(&askAgainCheckbox);
381 
382     QObject::connect(&askAgainCheckbox, &QCheckBox::stateChanged, [](int state) {
383         if (static_cast<Qt::CheckState>(state) == Qt::CheckState::Checked) {
384             preferences.setPreference(PREF_MIGRATION_DO_NOT_ASK_ME_AGAIN_XML, true);
385         }
386     });
387 
388     dialog.exec();
389 
390     bool needApplyEdwin = dialog.clickedButton() == yesButton;
391     preferences.setPreference(PREF_MIGRATION_APPLY_EDWIN_FOR_XML_FILES, needApplyEdwin);
392 }
393 
394 //---------------------------------------------------------
395 //   openScore
396 //---------------------------------------------------------
397 
openScore(const QString & fn,bool switchTab,const bool considerInCurrentSession,const QString & name)398 Score* MuseScore::openScore(const QString& fn, bool switchTab, const bool considerInCurrentSession, const QString& name)
399       {
400       //
401       // make sure we load a file only once
402       //
403       QFileInfo fi(fn);
404       QString path = fi.canonicalFilePath();
405       for (Score* s : scoreList) {
406             if (s->masterScore() && s->masterScore()->fileInfo()->canonicalFilePath() == path) {
407                   if (switchTab && QApplication::activeModalWidget() == nullptr)
408                         setCurrentScoreView(scoreList.indexOf(s->masterScore()));
409                   return 0;
410                   }
411             }
412 
413       askAboutApplyingEdwinIfNeed(fi.suffix().toLower());
414 
415       MasterScore* score = readScore(fn);
416       if (score) {
417             if (!name.isEmpty())
418                   score->masterScore()->fileInfo()->setFile(name);
419                   // TODO: what if that path is no longer valid?
420 
421             ScoreMigrator_3_6 migrator;
422 
423             migrator.registerHandler(new StyleDefaultsHandler());
424 
425             if (preferences.getBool(PREF_MIGRATION_DO_NOT_ASK_ME_AGAIN) && score->mscVersion() < MSCVERSION) {
426                   if (preferences.getBool(PREF_MIGRATION_APPLY_LELAND_STYLE))
427                         migrator.registerHandler(new LelandStyleHandler());
428 
429                   if (preferences.getBool(PREF_MIGRATION_APPLY_EDWIN_STYLE))
430                         migrator.registerHandler(new EdwinStyleHandler());
431 
432                   if (preferences.getBool(PREF_MIGRATION_RESET_ELEMENT_POSITIONS))
433                         migrator.registerHandler(new ResetAllElementsPositionsHandler());
434                   }
435 
436             migrator.migrateScore(score);
437 
438             score->updateCapo();
439             score->update();
440             score->styleChanged();
441             score->doLayout();
442 
443             if (considerInCurrentSession) {
444                   const int tabIdx = appendScore(score);
445                   if (switchTab && QApplication::activeModalWidget() == nullptr)
446                         setCurrentScoreView(tabIdx);
447                   writeSessionFile(false);
448                   }
449             }
450       return score;
451       }
452 
453 //---------------------------------------------------------
454 //   readScore
455 //---------------------------------------------------------
456 
readScore(const QString & name)457 MasterScore* MuseScore::readScore(const QString& name)
458       {
459       if (name.isEmpty())
460             return 0;
461 
462       if (instrumentGroups.isEmpty()) {
463             QString tmplPath = preferences.getString(PREF_APP_PATHS_INSTRUMENTLIST1);
464 
465             if (tmplPath.isEmpty())
466                   tmplPath = preferences.getString(PREF_APP_PATHS_INSTRUMENTLIST2);
467 
468             loadInstrumentTemplates(tmplPath);
469             }
470 
471       MasterScore* score = new MasterScore(MScore::baseStyle());
472       setMidiReopenInProgress(name);
473       Score::FileError rv = Ms::readScore(score, name, false);
474       if (rv == Score::FileError::FILE_TOO_OLD || rv == Score::FileError::FILE_TOO_NEW || rv == Score::FileError::FILE_CORRUPTED) {
475             if (readScoreError(name, rv, true)) {
476                   if (rv != Score::FileError::FILE_CORRUPTED) {
477                         // don’t read file again if corrupted
478                         // the check routine may try to fix it
479                         delete score;
480                         score = new MasterScore();
481                         score->setMovements(new Movements());
482                         score->setStyle(MScore::baseStyle());
483                         rv = Ms::readScore(score, name, true);
484                         }
485                   else
486                         rv = Score::FileError::FILE_NO_ERROR;
487                   }
488             else {
489                   delete score;
490                   return 0;
491                   }
492             }
493       if (rv != Score::FileError::FILE_NO_ERROR) {
494             // in case of user abort while reading, the error has already been reported
495             // else report it now
496             if (rv != Score::FileError::FILE_USER_ABORT && rv != Score::FileError::FILE_IGNORE_ERROR)
497                   readScoreError(name, rv, false);
498             delete score;
499             score = 0;
500             return 0;
501             }
502       allowShowMidiPanel(name);
503       if (score)
504             addRecentScore(score);
505 
506       return score;
507       }
508 
509 //---------------------------------------------------------
510 //   saveFile
511 ///   Save the current score.
512 ///   Handles the GUI's file-save action.
513 //
514 //    return true on success
515 //---------------------------------------------------------
516 
saveFile()517 bool MuseScore::saveFile()
518       {
519       return saveFile(cs->masterScore());
520       }
521 
522 //---------------------------------------------------------
523 //   saveFile
524 ///   Save the score.
525 //
526 //    return true on success
527 //---------------------------------------------------------
528 
saveFile(MasterScore * score)529 bool MuseScore::saveFile(MasterScore* score)
530       {
531       if (score == 0)
532             return false;
533       if (score->created()) {
534             QString fileBaseName = score->masterScore()->fileInfo()->completeBaseName();
535             QString fileName = score->masterScore()->fileInfo()->fileName();
536             // is backup file
537             if (fileBaseName.startsWith(".")
538                && (fileName.endsWith(".mscz,") || fileName.endsWith(".mscx,")))
539                   fileBaseName.remove(0, 1); // remove the "." at the beginning of file name
540             Text* t = score->getText(Tid::TITLE);
541             if (t)
542                   fileBaseName = t->plainText();
543             QString name = createDefaultFileName(fileBaseName);
544             QString msczType = tr("MuseScore 3 File") + " (*.mscz)";
545             QString mscxType = tr("Uncompressed MuseScore 3 File") + " (*.mscx)";     // for debugging purposes
546 
547             QSettings set;
548             if (mscore->lastSaveDirectory.isEmpty())
549                   mscore->lastSaveDirectory = set.value("lastSaveDirectory", preferences.getString(PREF_APP_PATHS_MYSCORES)).toString();
550             QString saveDirectory = mscore->lastSaveDirectory;
551 
552             if (saveDirectory.isEmpty())
553                   saveDirectory = preferences.getString(PREF_APP_PATHS_MYSCORES);
554 
555             QString fname = QString("%1/%2").arg(saveDirectory).arg(name);
556             QString filter;
557 #ifdef AVSOMR
558             if (score->avsOmr()) {
559                   QString msmrType = tr("Music Recognition MuseScore 3 File") + " (*.msmr)";
560                   fname = QFileInfo(fname).baseName() + ".msmr";
561                   filter = msmrType + ";;" + mscxType + ";;" + msczType;
562                   }
563             else {
564 #endif
565             filter = msczType + ";;" + mscxType;
566             if (QFileInfo(fname).suffix().isEmpty())
567                   fname += ".mscz";
568 #ifdef AVSOMR
569             }
570 #endif
571 
572             fileBaseName = mscore->getSaveScoreName(tr("Save Score"), fname, filter);
573             if (fileBaseName.isEmpty())
574                   return false;
575             score->masterScore()->fileInfo()->setFile(fileBaseName);
576 
577             mscore->lastSaveDirectory = score->masterScore()->fileInfo()->absolutePath();
578 
579             if (!score->masterScore()->saveFile(preferences.getBool(PREF_APP_BACKUP_GENERATE_BACKUP))) {
580                   QMessageBox::critical(mscore, tr("Save File"), MScore::lastError);
581                   return false;
582                   }
583             addRecentScore(score);
584             writeSessionFile(false);
585             }
586       else if (!score->masterScore()->saveFile(preferences.getBool(PREF_APP_BACKUP_GENERATE_BACKUP))) {
587             QMessageBox::critical(mscore, tr("Save File"), MScore::lastError);
588             return false;
589             }
590       score->setCreated(false);
591       updateWindowTitle(score);
592       scoreCmpTool->updateScoreVersions(score);
593       int idx = scoreList.indexOf(score->masterScore());
594       tab1->setTabText(idx, score->fileInfo()->completeBaseName());
595       if (tab2)
596             tab2->setTabText(idx, score->fileInfo()->completeBaseName());
597       QString tmp = score->tmpName();
598       if (!tmp.isEmpty()) {
599             QFile f(tmp);
600             if (!f.remove())
601                   qDebug("cannot remove temporary file <%s>", qPrintable(f.fileName()));
602             score->setTmpName("");
603             }
604       writeSessionFile(false);
605       return true;
606       }
607 
608 //---------------------------------------------------------
609 //   createDefaultName
610 //---------------------------------------------------------
611 
createDefaultName() const612 QString MuseScore::createDefaultName() const
613       {
614       QString name(tr("Untitled"));
615       int n;
616       for (n = 1; ; ++n) {
617             bool nameExists = false;
618             QString tmpName;
619             if (n == 1)
620                   tmpName = name;
621             else
622                   tmpName = QString("%1-%2").arg(name).arg(n);
623             for (MasterScore* s : scoreList) {
624                   if (s->fileInfo()->completeBaseName() == tmpName) {
625                         nameExists = true;
626                         break;
627                         }
628                   }
629             if (!nameExists) {
630                   name = tmpName;
631                   break;
632                   }
633             }
634       return name;
635       }
636 
637 //---------------------------------------------------------
638 //   getNewFile
639 //    create new score
640 //---------------------------------------------------------
641 
getNewFile()642 MasterScore* MuseScore::getNewFile()
643       {
644       if (!newWizard)
645             newWizard = new NewWizard(this);
646       else {
647             newWizard->updateValues();
648             newWizard->restart();
649             }
650       if (newWizard->exec() != QDialog::Accepted)
651             return 0;
652       int measures            = newWizard->measures();
653       Fraction timesig        = newWizard->timesig();
654       TimeSigType timesigType = newWizard->timesigType();
655       KeySigEvent ks          = newWizard->keysig();
656       VBox* nvb               = nullptr;
657 
658       int pickupTimesigZ;
659       int pickupTimesigN;
660       bool pickupMeasure = newWizard->pickupMeasure(&pickupTimesigZ, &pickupTimesigN);
661       if (pickupMeasure)
662             measures += 1;
663 
664       MasterScore* score = new MasterScore(MScore::defaultStyle());
665       QString tp         = newWizard->templatePath();
666 
667       QList<Excerpt*> excerpts;
668       if (!newWizard->emptyScore()) {
669             MasterScore* tscore = new MasterScore(MScore::defaultStyle());
670             tscore->setCreated(true);
671             Score::FileError rv = Ms::readScore(tscore, tp, false);
672             if (rv != Score::FileError::FILE_NO_ERROR) {
673                   readScoreError(newWizard->templatePath(), rv, false);
674                   delete tscore;
675                   delete score;
676                   return 0;
677                   }
678             if (MScore::harmonyPlayDisableNew) {
679                   tscore->style().set(Sid::harmonyPlay, false);
680                   }
681             else if (MScore::harmonyPlayDisableCompatibility) {
682                   // if template was older, then harmonyPlay may have been forced off by the compatibility preference
683                   // that's not appropriate when creating new scores from old templates
684                   // if template was pre-3.5, return harmonyPlay to default
685                   QString programVersion = tscore->mscoreVersion();
686                   if (!programVersion.isEmpty() && programVersion < "3.5")
687                         tscore->style().set(Sid::harmonyPlay, MScore::defaultStyle().value(Sid::harmonyPlay));
688                   }
689             score->setStyle(tscore->style());
690             score->setScoreOrder(tscore->scoreOrder());
691 
692             // create instruments from template
693             for (Part* tpart : tscore->parts()) {
694                   Part* part = new Part(score);
695                   part->setInstrument(tpart->instrument());
696                   part->setPartName(tpart->partName());
697 
698                   for (Staff* tstaff : *tpart->staves()) {
699                         Staff* staff = new Staff(score);
700                         staff->setPart(part);
701                         staff->init(tstaff);
702                         if (tstaff->links() && !part->staves()->isEmpty()) {
703                               Staff* linkedStaff = part->staves()->back();
704                               staff->linkTo(linkedStaff);
705                               }
706                         part->insertStaff(staff, -1);
707                         score->staves().append(staff);
708                         }
709                   score->appendPart(part);
710                   }
711             for (Excerpt* ex : tscore->excerpts()) {
712                   Excerpt* x = new Excerpt(score);
713                   x->setTitle(ex->title());
714                   for (Part* p : ex->parts()) {
715                         int pidx = tscore->parts().indexOf(p);
716                         if (pidx == -1)
717                               qDebug("newFile: part not found");
718                         else
719                               x->parts().append(score->parts()[pidx]);
720                         }
721                   excerpts.append(x);
722                   }
723             MeasureBase* mb = tscore->first();
724             if (mb && mb->isVBox()) {
725                   VBox* tvb = toVBox(mb);
726                   nvb = new VBox(score);
727                   nvb->setBoxHeight(tvb->boxHeight());
728                   nvb->setBoxWidth(tvb->boxWidth());
729                   nvb->setTopGap(tvb->topGap());
730                   nvb->setBottomGap(tvb->bottomGap());
731                   nvb->setTopMargin(tvb->topMargin());
732                   nvb->setBottomMargin(tvb->bottomMargin());
733                   nvb->setLeftMargin(tvb->leftMargin());
734                   nvb->setRightMargin(tvb->rightMargin());
735                   }
736             delete tscore;
737             }
738       else {
739             score = new MasterScore(MScore::defaultStyle());
740             if (MScore::harmonyPlayDisableNew) {
741                   score->style().set(Sid::harmonyPlay, false);
742                   }
743             newWizard->createInstruments(score);
744             }
745       score->setCreated(true);
746       score->fileInfo()->setFile(createDefaultName());
747 
748       score->style().checkChordList();
749       if (!newWizard->title().isEmpty())
750             score->fileInfo()->setFile(newWizard->title());
751 
752       score->sigmap()->add(0, timesig);
753 
754       Fraction firstMeasureTicks = pickupMeasure ? Fraction(pickupTimesigZ, pickupTimesigN) : timesig;
755 
756       for (int i = 0; i < measures; ++i) {
757             Fraction tick = firstMeasureTicks + timesig * (i - 1);
758             if (i == 0)
759                   tick = Fraction(0,1);
760             QList<Rest*> puRests;
761             for (Score* _score : score->scoreList()) {
762                   Rest* rest = 0;
763                   Measure* measure = new Measure(_score);
764                   measure->setTimesig(timesig);
765                   measure->setTicks(timesig);
766                   measure->setTick(tick);
767 
768                   if (pickupMeasure && tick.isZero()) {
769                         measure->setIrregular(true);        // don’t count pickup measure
770                         measure->setTicks(Fraction(pickupTimesigZ, pickupTimesigN));
771                         }
772                   _score->measures()->add(measure);
773 
774                   for (Staff* staff : _score->staves()) {
775                         int staffIdx = staff->idx();
776                         if (tick.isZero()) {
777                               TimeSig* ts = new TimeSig(_score);
778                               ts->setTrack(staffIdx * VOICES);
779                               ts->setSig(timesig, timesigType);
780                               Measure* m = _score->firstMeasure();
781                               Segment* s = m->getSegment(SegmentType::TimeSig, Fraction(0,1));
782                               s->add(ts);
783                               Part* part = staff->part();
784                               if (!part->instrument()->useDrumset()) {  //tick?
785                                     //
786                                     // transpose key
787                                     //
788                                     KeySigEvent nKey = ks;
789                                     if (!nKey.custom() && !nKey.isAtonal() && part->instrument()->transpose().chromatic && !score->styleB(Sid::concertPitch)) {
790                                           int diff = -part->instrument()->transpose().chromatic;
791                                           nKey.setKey(transposeKey(nKey.key(), diff, part->preferSharpFlat()));
792                                           }
793                                     // do not create empty keysig unless custom or atonal
794                                     if (nKey.custom() || nKey.isAtonal() || nKey.key() != Key::C) {
795                                           staff->setKey(Fraction(0,1), nKey);
796                                           KeySig* keysig = new KeySig(score);
797                                           keysig->setTrack(staffIdx * VOICES);
798                                           keysig->setKeySigEvent(nKey);
799                                           Segment* ss = measure->getSegment(SegmentType::KeySig, Fraction(0,1));
800                                           ss->add(keysig);
801                                           }
802                                     }
803                               }
804 
805                         // determined if this staff is linked to previous so we can reuse rests
806                         bool linkedToPrevious = staffIdx && staff->isLinked(_score->staff(staffIdx - 1));
807                         if (measure->timesig() != measure->ticks()) {
808                               if (!linkedToPrevious)
809                                     puRests.clear();
810                               std::vector<TDuration> dList = toDurationList(measure->ticks(), false);
811                               if (!dList.empty()) {
812                                     Fraction ltick = tick;
813                                     int k = 0;
814                                     foreach (TDuration d, dList) {
815                                           if (k < puRests.count())
816                                                 rest = static_cast<Rest*>(puRests[k]->linkedClone());
817                                           else {
818                                                 rest = new Rest(score, d);
819                                                 puRests.append(rest);
820                                                 }
821                                           rest->setScore(_score);
822                                           rest->setTicks(d.fraction());
823                                           rest->setTrack(staffIdx * VOICES);
824                                           Segment* seg = measure->getSegment(SegmentType::ChordRest, ltick);
825                                           seg->add(rest);
826                                           ltick += rest->actualTicks();
827                                           k++;
828                                           }
829                                     }
830                               }
831                         else {
832                               if (linkedToPrevious && rest)
833                                     rest = static_cast<Rest*>(rest->linkedClone());
834                               else
835                                     rest = new Rest(score, TDuration(TDuration::DurationType::V_MEASURE));
836                               rest->setScore(_score);
837                               rest->setTicks(measure->ticks());
838                               rest->setTrack(staffIdx * VOICES);
839                               Segment* seg = measure->getSegment(SegmentType::ChordRest, tick);
840                               seg->add(rest);
841                               }
842                         }
843                   }
844             }
845 //TODO      score->lastMeasure()->setEndBarLineType(BarLineType::END, false);
846 
847       //
848       // select first rest
849       //
850       Measure* m = score->firstMeasure();
851       for (Segment* s = m->first(); s; s = s->next()) {
852             if (s->segmentType() == SegmentType::ChordRest) {
853                   if (s->element(0)) {
854                         score->select(s->element(0), SelectType::SINGLE, 0);
855                         break;
856                         }
857                   }
858             }
859 
860       QString title     = newWizard->title();
861       QString subtitle  = newWizard->subtitle();
862       QString composer  = newWizard->composer();
863       QString poet      = newWizard->poet();
864       QString copyright = newWizard->copyright();
865 
866       if (!title.isEmpty() || !subtitle.isEmpty() || !composer.isEmpty() || !poet.isEmpty()) {
867             MeasureBase* measure = score->measures()->first();
868             if (measure->type() != ElementType::VBOX) {
869                   MeasureBase* nm = nvb ? nvb : new VBox(score);
870                   nm->setTick(Fraction(0,1));
871                   nm->setNext(measure);
872                   score->measures()->add(nm);
873                   measure = nm;
874                   }
875             else if (nvb) {
876                   delete nvb;
877                   }
878             if (!title.isEmpty()) {
879                   Text* s = new Text(score, Tid::TITLE);
880                   s->setPlainText(title);
881                   measure->add(s);
882                   score->setMetaTag("workTitle", title);
883                   }
884             if (!subtitle.isEmpty()) {
885                   Text* s = new Text(score, Tid::SUBTITLE);
886                   s->setPlainText(subtitle);
887                   measure->add(s);
888                   }
889             if (!composer.isEmpty()) {
890                   Text* s = new Text(score, Tid::COMPOSER);
891                   s->setPlainText(composer);
892                   measure->add(s);
893                   score->setMetaTag("composer", composer);
894                   }
895             if (!poet.isEmpty()) {
896                   Text* s = new Text(score, Tid::POET);
897                   s->setPlainText(poet);
898                   measure->add(s);
899                   // the poet() functions returns data called lyricist in the dialog
900                   score->setMetaTag("lyricist", poet);
901                   }
902             }
903       else if (nvb) {
904             delete nvb;
905             }
906 
907       double tempo = Score::defaultTempo() * 60; // quarter notes per minute
908       if (newWizard->tempo(&tempo)) {
909 
910             Fraction ts = newWizard->timesig();
911 
912             QString text("<sym>metNoteQuarterUp</sym> = %1");
913             double bpm = tempo;
914             switch (ts.denominator()) {
915                   case 1:
916                         text = "<sym>metNoteWhole</sym> = %1";
917                         bpm /= 4;
918                         break;
919                   case 2:
920                         text = "<sym>metNoteHalfUp</sym> = %1";
921                         bpm /= 2;
922                         break;
923                   case 4:
924                         text = "<sym>metNoteQuarterUp</sym> = %1";
925                         break;
926                   case 8:
927                         if (ts.numerator() % 3 == 0) {
928                               text = "<sym>metNoteQuarterUp</sym><sym>space</sym><sym>metAugmentationDot</sym> = %1";
929                               bpm /= 1.5;
930                               }
931                         else {
932                               text = "<sym>metNote8thUp</sym> = %1";
933                               bpm *= 2;
934                               }
935                         break;
936                   case 16:
937                         if (ts.numerator() % 3 == 0) {
938                               text = "<sym>metNote8thUp</sym><sym>space</sym><sym>metAugmentationDot</sym> = %1";
939                               bpm *= 1.5;
940                               }
941                         else {
942                               text = "<sym>metNote16thUp</sym> = %1";
943                               bpm *= 4;
944                               }
945                         break;
946                   case 32:
947                         if (ts.numerator() % 3 == 0) {
948                               text = "<sym>metNote16thUp</sym><sym>space</sym><sym>metAugmentationDot</sym> = %1";
949                               bpm *= 3;
950                               }
951                         else {
952                               text = "<sym>metNote32ndUp</sym> = %1";
953                               bpm *= 8;
954                               }
955                         break;
956                   case 64:
957                         if (ts.numerator() % 3 == 0) {
958                               text = "<sym>metNote32ndUp</sym><sym>space</sym><sym>metAugmentationDot</sym> = %1";
959                               bpm *= 6;
960                               }
961                         else {
962                               text = "<sym>metNote64thUp</sym> = %1";
963                               bpm *= 16;
964                               }
965                         break;
966                   default:
967                         break;
968                   }
969 
970             TempoText* tt = new TempoText(score);
971             tt->setXmlText(text.arg(bpm));
972             tempo /= 60;      // bpm -> bps
973 
974             tt->setTempo(tempo);
975             tt->setFollowText(true);
976             tt->setTrack(0);
977             Segment* seg = score->firstMeasure()->first(SegmentType::ChordRest);
978             seg->add(tt);
979             score->setTempo(seg, tempo);
980             }
981       if (!copyright.isEmpty())
982             score->setMetaTag("copyright", copyright);
983 
984       if (synti)
985             score->setSynthesizerState(synti->state());
986 
987       // Call this even if synti doesn't exist - we need to rebuild either way
988       score->rebuildAndUpdateExpressive(MuseScore::synthesizer("Fluid"));
989 
990       {
991             ScoreLoad sl;
992             score->doLayout();
993             }
994 
995       for (Excerpt* x : excerpts) {
996             Score* xs = new Score(static_cast<MasterScore*>(score));
997             xs->style().set(Sid::createMultiMeasureRests, true);
998             x->setPartScore(xs);
999             xs->setExcerpt(x);
1000             score->excerpts().append(x);
1001             Excerpt::createExcerpt(x);
1002             }
1003       score->setExcerptsChanged(true);
1004       return score;
1005       }
1006 
1007 //---------------------------------------------------------
1008 //   newFile
1009 //    create new score
1010 //---------------------------------------------------------
1011 
newFile()1012 void MuseScore::newFile()
1013       {
1014       MasterScore* score = getNewFile();
1015       if (score)
1016             setCurrentScoreView(appendScore(score));
1017       mscore->tourHandler()->showDelayedWelcomeTour();
1018       }
1019 
1020 //---------------------------------------------------------
1021 //   copy
1022 //    Copy content of src file do dest file overwriting it.
1023 //    Implemented manually as QFile::copy refuses to
1024 //    overwrite existing files.
1025 //---------------------------------------------------------
1026 
copy(QFile & src,QFile & dest)1027 static bool copy(QFile& src, QFile& dest)
1028       {
1029       src.open(QIODevice::ReadOnly);
1030       dest.open(QIODevice::WriteOnly);
1031       constexpr qint64 size = 1024 * 1024;
1032       char* buf = new char[size];
1033       bool err = false;
1034       while (qint64 r = src.read(buf, size)) {
1035             if (r < 0) {
1036                   err = true;
1037                   break;
1038                   }
1039             qint64 w = dest.write(buf, r);
1040             if (w < r) {
1041                   err = true;
1042                   break;
1043                   }
1044             }
1045       dest.close();
1046       src.close();
1047       delete[] buf;
1048       return !err;
1049       }
1050 
1051 //---------------------------------------------------------
1052 //   getTemporaryScoreFileCopy
1053 //    baseNameTemplate is the template to be passed to
1054 //    QTemporaryFile constructor but without suffix and
1055 //    directory --- they are defined automatically.
1056 //---------------------------------------------------------
1057 
getTemporaryScoreFileCopy(const QFileInfo & info,const QString & baseNameTemplate)1058 QTemporaryFile* MuseScore::getTemporaryScoreFileCopy(const QFileInfo& info, const QString& baseNameTemplate)
1059       {
1060       QString suffix(info.suffix());
1061       if (suffix.endsWith(",")) // some backup files created by MuseScore
1062             suffix.chop(1);
1063       QTemporaryFile* f = new QTemporaryFile(
1064          QDir::temp().absoluteFilePath(baseNameTemplate + '.' + suffix),
1065          this
1066          );
1067       QFile src(info.absoluteFilePath());
1068       if (!copy(src, *f)) {
1069             delete f;
1070             return nullptr;
1071             }
1072       return f;
1073       }
1074 
1075 //---------------------------------------------------------
1076 //   addScorePreview
1077 //    add a score preview to the file dialog
1078 //---------------------------------------------------------
1079 
addScorePreview(QFileDialog * dialog)1080 static void addScorePreview(QFileDialog* dialog)
1081       {
1082       QSplitter* splitter = dialog->findChild<QSplitter*>("splitter");
1083       if (splitter) {
1084             ScorePreview* preview = new ScorePreview;
1085             splitter->addWidget(preview);
1086             dialog->connect(dialog, SIGNAL(currentChanged(const QString&)), preview, SLOT(setScore(const QString&)));
1087             }
1088       }
1089 
1090 //---------------------------------------------------------
1091 //   sidebarUrls
1092 //    return a list of standard file dialog sidebar urls
1093 //---------------------------------------------------------
1094 
sidebarUrls()1095 static QList<QUrl> sidebarUrls()
1096       {
1097       QList<QUrl> urls;
1098       urls.append(QUrl::fromLocalFile(QDir::homePath()));
1099       QFileInfo myScores(preferences.getString(PREF_APP_PATHS_MYSCORES));
1100       urls.append(QUrl::fromLocalFile(myScores.absoluteFilePath()));
1101       urls.append(QUrl::fromLocalFile(QDir::currentPath()));
1102       return urls;
1103       }
1104 
1105 //---------------------------------------------------------
1106 //   getOpenScoreNames
1107 //---------------------------------------------------------
1108 
getOpenScoreNames(const QString & filter,const QString & title,bool singleFile)1109 QStringList MuseScore::getOpenScoreNames(const QString& filter, const QString& title, bool singleFile)
1110       {
1111       QSettings set;
1112       QString dir = set.value("lastOpenPath", preferences.getString(PREF_APP_PATHS_MYSCORES)).toString();
1113       if (preferences.getBool(PREF_UI_APP_USENATIVEDIALOGS)) {
1114             QStringList fileList = QFileDialog::getOpenFileNames(this,
1115                title, dir, filter);
1116             if (fileList.count() > 0) {
1117                   QFileInfo fi(fileList[0]);
1118                   set.setValue("lastOpenPath", fi.absolutePath());
1119                   }
1120             return fileList;
1121             }
1122       QFileInfo myScores(preferences.getString(PREF_APP_PATHS_MYSCORES));
1123       if (myScores.isRelative())
1124             myScores.setFile(QDir::home(), preferences.getString(PREF_APP_PATHS_MYSCORES));
1125 
1126       if (loadScoreDialog == 0) {
1127             loadScoreDialog = new QFileDialog(this);
1128             loadScoreDialog->setFileMode(singleFile ? QFileDialog::ExistingFile : QFileDialog::ExistingFiles);
1129             loadScoreDialog->setOption(QFileDialog::DontUseNativeDialog, true);
1130             loadScoreDialog->setWindowTitle(title);
1131             addScorePreview(loadScoreDialog);
1132 
1133             loadScoreDialog->setNameFilter(filter);
1134             restoreDialogState("loadScoreDialog", loadScoreDialog);
1135             loadScoreDialog->setAcceptMode(QFileDialog::AcceptOpen);
1136             loadScoreDialog->setDirectory(dir);
1137             }
1138       else {
1139             // dialog already exists, but set title and filter
1140             loadScoreDialog->setWindowTitle(title);
1141             loadScoreDialog->setNameFilter(filter);
1142             }
1143       // setup side bar urls
1144       QList<QUrl> urls = sidebarUrls();
1145       urls.append(QUrl::fromLocalFile(mscoreGlobalShare+"/demos"));
1146       loadScoreDialog->setSidebarUrls(urls);
1147 
1148       QStringList result;
1149       if (loadScoreDialog->exec())
1150             result = loadScoreDialog->selectedFiles();
1151       set.setValue("lastOpenPath", loadScoreDialog->directory().absolutePath());
1152       return result;
1153       }
1154 
1155 //---------------------------------------------------------
1156 //   getSaveScoreName
1157 //---------------------------------------------------------
1158 
getSaveScoreName(const QString & title,QString & name,const QString & filter,bool selectFolder,bool askOverwrite)1159 QString MuseScore::getSaveScoreName(const QString& title, QString& name, const QString& filter, bool selectFolder, bool askOverwrite)
1160       {
1161       QFileInfo myName(name);
1162       if (myName.isRelative())
1163             myName.setFile(QDir::home(), name);
1164       name = myName.absoluteFilePath();
1165 
1166       if (preferences.getBool(PREF_UI_APP_USENATIVEDIALOGS)) {
1167             QString s;
1168             QFileDialog::Options options;
1169             if (!askOverwrite) {
1170                   options |= QFileDialog::DontConfirmOverwrite;
1171             }
1172             if (selectFolder)
1173                   options |= QFileDialog::ShowDirsOnly;
1174             return QFileDialog::getSaveFileName(this, title, name, filter, &s, options);
1175             }
1176 
1177       QFileInfo myScores(preferences.getString(PREF_APP_PATHS_MYSCORES));
1178       if (myScores.isRelative())
1179             myScores.setFile(QDir::home(), preferences.getString(PREF_APP_PATHS_MYSCORES));
1180       if (saveScoreDialog == 0) {
1181             saveScoreDialog = new QFileDialog(this);
1182             saveScoreDialog->setFileMode(QFileDialog::AnyFile);
1183             saveScoreDialog->setOption(QFileDialog::DontUseNativeDialog, true);
1184             saveScoreDialog->setAcceptMode(QFileDialog::AcceptSave);
1185             addScorePreview(saveScoreDialog);
1186 
1187             restoreDialogState("saveScoreDialog", saveScoreDialog);
1188             }
1189       // setup side bar urls
1190       saveScoreDialog->setSidebarUrls(sidebarUrls());
1191 
1192       if (selectFolder)
1193             saveScoreDialog->setFileMode(QFileDialog::Directory);
1194       saveScoreDialog->setOption(QFileDialog::DontConfirmOverwrite, !askOverwrite);
1195 
1196       saveScoreDialog->setWindowTitle(title);
1197       saveScoreDialog->setNameFilter(filter);
1198       saveScoreDialog->selectFile(name);
1199 
1200       if (!selectFolder) {
1201             connect(saveScoreDialog, SIGNAL(filterSelected(const QString&)),
1202                SLOT(saveScoreDialogFilterSelected(const QString&)));
1203             }
1204       QString s;
1205       if (saveScoreDialog->exec())
1206             s = saveScoreDialog->selectedFiles().front();
1207       return s;
1208       }
1209 
1210 //---------------------------------------------------------
1211 //   saveScoreDialogFilterSelected
1212 //    update selected file name extensions, when filter
1213 //    has changed
1214 //---------------------------------------------------------
1215 
saveScoreDialogFilterSelected(const QString & s)1216 void MuseScore::saveScoreDialogFilterSelected(const QString& s)
1217       {
1218       QRegExp rx(QString(".+\\(\\*\\.(.+)\\)"));
1219       if (rx.exactMatch(s)) {
1220             QFileInfo fi(saveScoreDialog->selectedFiles().front());
1221             saveScoreDialog->selectFile(fi.completeBaseName() + "." + rx.cap(1));
1222             }
1223       }
1224 
1225 //---------------------------------------------------------
1226 //   getStyleFilename
1227 //---------------------------------------------------------
1228 
getStyleFilename(bool open,const QString & title)1229 QString MuseScore::getStyleFilename(bool open, const QString& title)
1230       {
1231       QFileInfo myStyles(preferences.getString(PREF_APP_PATHS_MYSTYLES));
1232       if (myStyles.isRelative())
1233             myStyles.setFile(QDir::home(), preferences.getString(PREF_APP_PATHS_MYSTYLES));
1234       QString defaultPath = myStyles.absoluteFilePath();
1235 
1236       if (preferences.getBool(PREF_UI_APP_USENATIVEDIALOGS)) {
1237             QString fn;
1238             if (open) {
1239                   fn = QFileDialog::getOpenFileName(
1240                      this, tr("Load Style"),
1241                      defaultPath,
1242                      tr("MuseScore Styles") + " (*.mss)"
1243                      );
1244                   }
1245             else {
1246                   fn = QFileDialog::getSaveFileName(
1247                      this, tr("Save Style"),
1248                      defaultPath,
1249                      tr("MuseScore Style File") + " (*.mss)"
1250                      );
1251                   }
1252             return fn;
1253             }
1254 
1255       QFileDialog* dialog;
1256       QList<QUrl> urls;
1257       QString home = QDir::homePath();
1258       urls.append(QUrl::fromLocalFile(home));
1259       urls.append(QUrl::fromLocalFile(defaultPath));
1260       urls.append(QUrl::fromLocalFile(QDir::currentPath()));
1261 
1262       if (open) {
1263             if (loadStyleDialog == 0) {
1264                   loadStyleDialog = new QFileDialog(this);
1265                   loadStyleDialog->setFileMode(QFileDialog::ExistingFile);
1266                   loadStyleDialog->setOption(QFileDialog::DontUseNativeDialog, true);
1267                   loadStyleDialog->setWindowTitle(title.isEmpty() ? tr("Load Style") : title);
1268                   loadStyleDialog->setNameFilter(tr("MuseScore Style File") + " (*.mss)");
1269                   loadStyleDialog->setDirectory(defaultPath);
1270 
1271                   restoreDialogState("loadStyleDialog", loadStyleDialog);
1272                   loadStyleDialog->setAcceptMode(QFileDialog::AcceptOpen);
1273                   }
1274             urls.append(QUrl::fromLocalFile(mscoreGlobalShare+"/styles"));
1275             dialog = loadStyleDialog;
1276             }
1277       else {
1278             if (saveStyleDialog == 0) {
1279                   saveStyleDialog = new QFileDialog(this);
1280                   saveStyleDialog->setAcceptMode(QFileDialog::AcceptSave);
1281                   saveStyleDialog->setFileMode(QFileDialog::AnyFile);
1282                   saveStyleDialog->setOption(QFileDialog::DontConfirmOverwrite, false);
1283                   saveStyleDialog->setOption(QFileDialog::DontUseNativeDialog, true);
1284                   saveStyleDialog->setWindowTitle(title.isEmpty() ? tr("Save Style") : title);
1285                   saveStyleDialog->setNameFilter(tr("MuseScore Style File") + " (*.mss)");
1286                   saveStyleDialog->setDirectory(defaultPath);
1287 
1288                   restoreDialogState("saveStyleDialog", saveStyleDialog);
1289                   saveStyleDialog->setAcceptMode(QFileDialog::AcceptSave);
1290                   }
1291             dialog = saveStyleDialog;
1292             }
1293       // setup side bar urls
1294       dialog->setSidebarUrls(urls);
1295 
1296       if (dialog->exec()) {
1297             QStringList result = dialog->selectedFiles();
1298             return result.front();
1299             }
1300       return QString();
1301       }
1302 
1303 //---------------------------------------------------------
1304 //   getChordStyleFilename
1305 //---------------------------------------------------------
1306 
getChordStyleFilename(bool open)1307 QString MuseScore::getChordStyleFilename(bool open)
1308       {
1309       QString filter = tr("Chord Symbols Style File") + " (*.xml)";
1310 
1311       QFileInfo myStyles(preferences.getString(PREF_APP_PATHS_MYSTYLES));
1312       if (myStyles.isRelative())
1313             myStyles.setFile(QDir::home(), preferences.getString(PREF_APP_PATHS_MYSTYLES));
1314       QString defaultPath = myStyles.absoluteFilePath();
1315 
1316       if (preferences.getBool(PREF_UI_APP_USENATIVEDIALOGS)) {
1317             QString fn;
1318             if (open) {
1319                   fn = QFileDialog::getOpenFileName(
1320                      this, tr("Load Chord Symbols Style"),
1321                      defaultPath,
1322                      filter
1323                      );
1324                   }
1325             else {
1326                   fn = QFileDialog::getSaveFileName(
1327                      this, tr("Save Chord Symbols Style"),
1328                      defaultPath,
1329                      filter
1330                      );
1331                   }
1332             return fn;
1333             }
1334 
1335       QFileDialog* dialog;
1336       QList<QUrl> urls;
1337       QString home = QDir::homePath();
1338       urls.append(QUrl::fromLocalFile(home));
1339       urls.append(QUrl::fromLocalFile(defaultPath));
1340       urls.append(QUrl::fromLocalFile(QDir::currentPath()));
1341 
1342       QSettings set;
1343       if (open) {
1344             if (loadChordStyleDialog == 0) {
1345                   loadChordStyleDialog = new QFileDialog(this);
1346                   loadChordStyleDialog->setFileMode(QFileDialog::ExistingFile);
1347                   loadChordStyleDialog->setOption(QFileDialog::DontUseNativeDialog, true);
1348                   loadChordStyleDialog->setWindowTitle(tr("Load Chord Symbols Style"));
1349                   loadChordStyleDialog->setNameFilter(filter);
1350                   loadChordStyleDialog->setDirectory(defaultPath);
1351 
1352                   restoreDialogState("loadChordStyleDialog", loadChordStyleDialog);
1353                   loadChordStyleDialog->restoreState(set.value("loadChordStyleDialog").toByteArray());
1354                   loadChordStyleDialog->setAcceptMode(QFileDialog::AcceptOpen);
1355                   }
1356             // setup side bar urls
1357             urls.append(QUrl::fromLocalFile(mscoreGlobalShare+"/styles"));
1358             dialog = loadChordStyleDialog;
1359             }
1360       else {
1361             if (saveChordStyleDialog == 0) {
1362                   saveChordStyleDialog = new QFileDialog(this);
1363                   saveChordStyleDialog->setAcceptMode(QFileDialog::AcceptSave);
1364                   saveChordStyleDialog->setFileMode(QFileDialog::AnyFile);
1365                   saveChordStyleDialog->setOption(QFileDialog::DontConfirmOverwrite, false);
1366                   saveChordStyleDialog->setOption(QFileDialog::DontUseNativeDialog, true);
1367                   saveChordStyleDialog->setWindowTitle(tr("Save Style"));
1368                   saveChordStyleDialog->setNameFilter(filter);
1369                   saveChordStyleDialog->setDirectory(defaultPath);
1370 
1371                   restoreDialogState("saveChordStyleDialog", saveChordStyleDialog);
1372                   saveChordStyleDialog->setAcceptMode(QFileDialog::AcceptSave);
1373                   }
1374             dialog = saveChordStyleDialog;
1375             }
1376       // setup side bar urls
1377       dialog->setSidebarUrls(urls);
1378       if (dialog->exec()) {
1379             QStringList result = dialog->selectedFiles();
1380             return result.front();
1381             }
1382       return QString();
1383       }
1384 
1385 //---------------------------------------------------------
1386 //   getScanFile
1387 //---------------------------------------------------------
1388 
getScanFile(const QString & d)1389 QString MuseScore::getScanFile(const QString& d)
1390       {
1391       QString filter = tr("PDF Scan File") + " (*.pdf);;All (*)";
1392       QString defaultPath = d.isEmpty() ? QDir::homePath() : d;
1393       if (preferences.getBool(PREF_UI_APP_USENATIVEDIALOGS)) {
1394             QString s = QFileDialog::getOpenFileName(
1395                mscore,
1396                MuseScore::tr("Choose PDF Scan"),
1397                defaultPath,
1398                filter
1399                );
1400             return s;
1401             }
1402 
1403       if (loadScanDialog == 0) {
1404             loadScanDialog = new QFileDialog(this);
1405             loadScanDialog->setFileMode(QFileDialog::ExistingFile);
1406             loadScanDialog->setOption(QFileDialog::DontUseNativeDialog, true);
1407             loadScanDialog->setWindowTitle(tr("Choose PDF Scan"));
1408             loadScanDialog->setNameFilter(filter);
1409             loadScanDialog->setDirectory(defaultPath);
1410 
1411             restoreDialogState("loadScanDialog", loadScanDialog);
1412             loadScanDialog->setAcceptMode(QFileDialog::AcceptOpen);
1413             }
1414 
1415       //
1416       // setup side bar urls
1417       //
1418       QList<QUrl> urls;
1419       QString home = QDir::homePath();
1420       urls.append(QUrl::fromLocalFile(home));
1421       urls.append(QUrl::fromLocalFile(QDir::currentPath()));
1422       loadScanDialog->setSidebarUrls(urls);
1423 
1424       if (loadScanDialog->exec()) {
1425             QStringList result = loadScanDialog->selectedFiles();
1426             return result.front();
1427             }
1428       return QString();
1429       }
1430 
1431 //---------------------------------------------------------
1432 //   getAudioFile
1433 //---------------------------------------------------------
1434 
getAudioFile(const QString & d)1435 QString MuseScore::getAudioFile(const QString& d)
1436       {
1437       QString filter = tr("Ogg Audio File") + " (*.ogg);;All (*)";
1438       QString defaultPath = d.isEmpty() ? QDir::homePath() : d;
1439       if (preferences.getBool(PREF_UI_APP_USENATIVEDIALOGS)) {
1440             QString s = QFileDialog::getOpenFileName(
1441                mscore,
1442                MuseScore::tr("Choose Audio File"),
1443                defaultPath,
1444                filter
1445                );
1446             return s;
1447             }
1448 
1449       if (loadAudioDialog == 0) {
1450             loadAudioDialog = new QFileDialog(this);
1451             loadAudioDialog->setFileMode(QFileDialog::ExistingFile);
1452             loadAudioDialog->setOption(QFileDialog::DontUseNativeDialog, true);
1453             loadAudioDialog->setWindowTitle(tr("Choose Ogg Audio File"));
1454             loadAudioDialog->setNameFilter(filter);
1455             loadAudioDialog->setDirectory(defaultPath);
1456 
1457             restoreDialogState("loadAudioDialog", loadAudioDialog);
1458             loadAudioDialog->setAcceptMode(QFileDialog::AcceptOpen);
1459             }
1460 
1461       //
1462       // setup side bar urls
1463       //
1464       QList<QUrl> urls;
1465       QString home = QDir::homePath();
1466       urls.append(QUrl::fromLocalFile(home));
1467       urls.append(QUrl::fromLocalFile(QDir::currentPath()));
1468       loadAudioDialog->setSidebarUrls(urls);
1469 
1470       if (loadAudioDialog->exec()) {
1471             QStringList result = loadAudioDialog->selectedFiles();
1472             return result.front();
1473             }
1474       return QString();
1475       }
1476 
1477 //---------------------------------------------------------
1478 //   getFotoFilename
1479 //---------------------------------------------------------
1480 
getFotoFilename(QString & filter,QString * selectedFilter)1481 QString MuseScore::getFotoFilename(QString& filter, QString* selectedFilter)
1482       {
1483       QString title = tr("Save Image");
1484 
1485       QFileInfo myImages(preferences.getString(PREF_APP_PATHS_MYIMAGES));
1486       if (myImages.isRelative())
1487             myImages.setFile(QDir::home(), preferences.getString(PREF_APP_PATHS_MYIMAGES));
1488       QString defaultPath = myImages.absoluteFilePath();
1489 
1490       // compute the image capture path
1491       QString myCapturePath;
1492       // if no saves were made for current score, then use the score's name as default
1493       if (!cs->masterScore()->savedCapture()) {
1494             // set the current score's name as the default name for saved captures
1495             QString scoreName = cs->masterScore()->fileInfo()->completeBaseName();
1496             QString name = createDefaultFileName(scoreName);
1497             QString fname = QString("%1/%2").arg(defaultPath).arg(name);
1498             QFileInfo myCapture(fname);
1499             if (myCapture.isRelative())
1500                 myCapture.setFile(QDir::home(), fname);
1501             myCapturePath = myCapture.absoluteFilePath();
1502             }
1503       else
1504             myCapturePath = lastSaveCaptureName;
1505 
1506       if (preferences.getBool(PREF_UI_APP_USENATIVEDIALOGS)) {
1507             QString fn;
1508             fn = QFileDialog::getSaveFileName(
1509                this,
1510                title,
1511                myCapturePath,
1512                filter,
1513                selectedFilter
1514                );
1515             // if a save was made for this current score
1516             if (!fn.isEmpty()) {
1517                 cs->masterScore()->setSavedCapture(true);
1518                 // store the last name used for saving an image capture
1519                 lastSaveCaptureName = fn;
1520                 }
1521             return fn;
1522             }
1523 
1524       QList<QUrl> urls;
1525       urls.append(QUrl::fromLocalFile(QDir::homePath()));
1526       urls.append(QUrl::fromLocalFile(defaultPath));
1527       urls.append(QUrl::fromLocalFile(QDir::currentPath()));
1528 
1529       if (saveImageDialog == 0) {
1530             saveImageDialog = new QFileDialog(this);
1531             saveImageDialog->setFileMode(QFileDialog::AnyFile);
1532             saveImageDialog->setAcceptMode(QFileDialog::AcceptSave);
1533             saveImageDialog->setOption(QFileDialog::DontConfirmOverwrite, false);
1534             saveImageDialog->setOption(QFileDialog::DontUseNativeDialog, true);
1535             saveImageDialog->setWindowTitle(title);
1536             saveImageDialog->setNameFilter(filter);
1537             saveImageDialog->setDirectory(defaultPath);
1538 
1539             restoreDialogState("saveImageDialog", saveImageDialog);
1540             saveImageDialog->setAcceptMode(QFileDialog::AcceptSave);
1541             }
1542 
1543       // setup side bar urls
1544       saveImageDialog->setSidebarUrls(urls);
1545 
1546       // set file's name using the computed path
1547       saveImageDialog->selectFile(myCapturePath);
1548 
1549       if (saveImageDialog->exec()) {
1550             QStringList result = saveImageDialog->selectedFiles();
1551             *selectedFilter = saveImageDialog->selectedNameFilter();
1552             // a save was made for this current score
1553             cs->masterScore()->setSavedCapture(true);
1554             // store the last name used for saving an image capture
1555             lastSaveCaptureName = result.front();
1556             return result.front();
1557             }
1558       return QString();
1559       }
1560 
1561 //---------------------------------------------------------
1562 //   getPaletteFilename
1563 //---------------------------------------------------------
1564 
getPaletteFilename(bool open,const QString & name)1565 QString MuseScore::getPaletteFilename(bool open, const QString& name)
1566       {
1567       QString title;
1568       QString filter;
1569 #if defined(WIN_PORTABLE)
1570       QString wd      = QDir::cleanPath(QString("%1/../../../Data/settings").arg(QCoreApplication::applicationDirPath()).arg(QCoreApplication::applicationName()));
1571 #else
1572       QString wd      = QString("%1/%2").arg(QStandardPaths::writableLocation(QStandardPaths::HomeLocation)).arg(QCoreApplication::applicationName());
1573 #endif
1574       if (open) {
1575             title  = tr("Load Palette");
1576             filter = tr("MuseScore Palette") + " (*.mpal)";
1577             }
1578       else {
1579             title  = tr("Save Palette");
1580             filter = tr("MuseScore Palette") + " (*.mpal)";
1581             }
1582 
1583       QFileInfo myPalettes(wd);
1584       QString defaultPath = myPalettes.absoluteFilePath();
1585       if (!name.isEmpty()) {
1586             QString fname = createDefaultFileName(name);
1587             QFileInfo myName(fname);
1588             if (myName.isRelative())
1589                   myName.setFile(defaultPath, fname);
1590             defaultPath = myName.absoluteFilePath();
1591             }
1592 
1593       if (preferences.getBool(PREF_UI_APP_USENATIVEDIALOGS)) {
1594             QString fn;
1595             if (open)
1596                   fn = QFileDialog::getOpenFileName(this, title, defaultPath, filter);
1597             else
1598                   fn = QFileDialog::getSaveFileName(this, title, defaultPath, filter);
1599             return fn;
1600             }
1601 
1602       QFileDialog* dialog;
1603       QList<QUrl> urls;
1604       urls.append(QUrl::fromLocalFile(QDir::homePath()));
1605       urls.append(QUrl::fromLocalFile(QDir::currentPath()));
1606       urls.append(QUrl::fromLocalFile(defaultPath));
1607 
1608       if (open) {
1609             if (loadPaletteDialog == 0) {
1610                   loadPaletteDialog = new QFileDialog(this);
1611                   loadPaletteDialog->setFileMode(QFileDialog::ExistingFile);
1612                   loadPaletteDialog->setOption(QFileDialog::DontUseNativeDialog, true);
1613                   loadPaletteDialog->setDirectory(defaultPath);
1614 
1615                   restoreDialogState("loadPaletteDialog", loadPaletteDialog);
1616                   loadPaletteDialog->setAcceptMode(QFileDialog::AcceptOpen);
1617                   }
1618             dialog = loadPaletteDialog;
1619             }
1620       else {
1621             if (savePaletteDialog == 0) {
1622                   savePaletteDialog = new QFileDialog(this);
1623                   savePaletteDialog->setAcceptMode(QFileDialog::AcceptSave);
1624                   savePaletteDialog->setFileMode(QFileDialog::AnyFile);
1625                   savePaletteDialog->setOption(QFileDialog::DontConfirmOverwrite, false);
1626                   savePaletteDialog->setOption(QFileDialog::DontUseNativeDialog, true);
1627                   savePaletteDialog->setDirectory(defaultPath);
1628 
1629                   restoreDialogState("savePaletteDialog", savePaletteDialog);
1630                   savePaletteDialog->setAcceptMode(QFileDialog::AcceptSave);
1631                   }
1632             dialog = savePaletteDialog;
1633             }
1634       dialog->setWindowTitle(title);
1635       dialog->setNameFilter(filter);
1636 
1637       // setup side bar urls
1638       dialog->setSidebarUrls(urls);
1639 
1640       if (dialog->exec()) {
1641             QStringList result = dialog->selectedFiles();
1642             return result.front();
1643             }
1644       return QString();
1645       }
1646 
1647 //---------------------------------------------------------
1648 //   getPluginFilename
1649 //---------------------------------------------------------
1650 
getPluginFilename(bool open)1651 QString MuseScore::getPluginFilename(bool open)
1652       {
1653       QString title;
1654       QString filter;
1655       if (open) {
1656             title  = tr("Load Plugin");
1657             filter = tr("MuseScore Plugin") + " (*.qml)";
1658             }
1659       else {
1660             title  = tr("Save Plugin");
1661             filter = tr("MuseScore Plugin File") + " (*.qml)";
1662             }
1663 
1664       QFileInfo myPlugins(preferences.getString(PREF_APP_PATHS_MYPLUGINS));
1665       if (myPlugins.isRelative())
1666             myPlugins.setFile(QDir::home(), preferences.getString(PREF_APP_PATHS_MYPLUGINS));
1667       QString defaultPath = myPlugins.absoluteFilePath();
1668 
1669       QString name  = createDefaultFileName("Plugin");
1670       QString fname = QString("%1/%2.qml").arg(defaultPath).arg(name);
1671       if (preferences.getBool(PREF_UI_APP_USENATIVEDIALOGS)) {
1672             QString fn;
1673             if (open)
1674                   fn = QFileDialog::getOpenFileName(this, title, defaultPath, filter);
1675             else
1676                   fn = QFileDialog::getSaveFileName(this, title, defaultPath, filter);
1677             return fn;
1678             }
1679 
1680       QFileDialog* dialog;
1681       QList<QUrl> urls;
1682       QString home = QDir::homePath();
1683       urls.append(QUrl::fromLocalFile(home));
1684       urls.append(QUrl::fromLocalFile(defaultPath));
1685       urls.append(QUrl::fromLocalFile(QDir::currentPath()));
1686 
1687       if (open) {
1688             if (loadPluginDialog == 0) {
1689                   loadPluginDialog = new QFileDialog(this);
1690                   loadPluginDialog->setFileMode(QFileDialog::ExistingFile);
1691                   loadPluginDialog->setOption(QFileDialog::DontUseNativeDialog, true);
1692                   loadPluginDialog->setDirectory(defaultPath);
1693 
1694                   QSettings set;
1695                   loadPluginDialog->restoreState(set.value("loadPluginDialog").toByteArray());
1696                   loadPluginDialog->setAcceptMode(QFileDialog::AcceptOpen);
1697                   }
1698             urls.append(QUrl::fromLocalFile(mscoreGlobalShare+"/plugins"));
1699             dialog = loadPluginDialog;
1700             }
1701       else {
1702             if (savePluginDialog == 0) {
1703                   savePluginDialog = new QFileDialog(this);
1704                   QSettings set;
1705                   savePluginDialog->restoreState(set.value("savePluginDialog").toByteArray());
1706                   savePluginDialog->setAcceptMode(QFileDialog::AcceptSave);
1707                   savePluginDialog->setFileMode(QFileDialog::AnyFile);
1708                   savePluginDialog->setOption(QFileDialog::DontConfirmOverwrite, false);
1709                   savePluginDialog->setOption(QFileDialog::DontUseNativeDialog, true);
1710                   savePluginDialog->setWindowTitle(tr("Save Plugin"));
1711                   savePluginDialog->setNameFilter(filter);
1712                   savePluginDialog->setDirectory(defaultPath);
1713                   savePluginDialog->selectFile(fname);
1714                   }
1715             dialog = savePluginDialog;
1716             }
1717       dialog->setWindowTitle(title);
1718       dialog->setNameFilter(filter);
1719 
1720       // setup side bar urls
1721       dialog->setSidebarUrls(urls);
1722 
1723       if (dialog->exec()) {
1724             QStringList result = dialog->selectedFiles();
1725             return result.front();
1726             }
1727       return QString();
1728       }
1729 
1730 //---------------------------------------------------------
1731 //   getDrumsetFilename
1732 //---------------------------------------------------------
1733 
getDrumsetFilename(bool open)1734 QString MuseScore::getDrumsetFilename(bool open)
1735       {
1736       QString title;
1737       QString filter;
1738       if (open) {
1739             title  = tr("Load Drumset");
1740             filter = tr("MuseScore Drumset") + " (*.drm)";
1741             }
1742       else {
1743             title  = tr("Save Drumset");
1744             filter = tr("MuseScore Drumset File") + " (*.drm)";
1745             }
1746 
1747       QFileInfo myStyles(preferences.getString(PREF_APP_PATHS_MYSTYLES));
1748       if (myStyles.isRelative())
1749             myStyles.setFile(QDir::home(), preferences.getString(PREF_APP_PATHS_MYSTYLES));
1750       QString defaultPath  = myStyles.absoluteFilePath();
1751 
1752       if (preferences.getBool(PREF_UI_APP_USENATIVEDIALOGS)) {
1753             QString fn;
1754             if (open)
1755                   fn = QFileDialog::getOpenFileName(this, title, defaultPath, filter);
1756             else
1757                   fn = QFileDialog::getSaveFileName(this, title, defaultPath, filter);
1758             return fn;
1759             }
1760 
1761 
1762       QFileDialog* dialog;
1763       QList<QUrl> urls;
1764       QString home = QDir::homePath();
1765       urls.append(QUrl::fromLocalFile(home));
1766       urls.append(QUrl::fromLocalFile(defaultPath));
1767       urls.append(QUrl::fromLocalFile(QDir::currentPath()));
1768 
1769       if (open) {
1770             if (loadDrumsetDialog == 0) {
1771                   loadDrumsetDialog = new QFileDialog(this);
1772                   loadDrumsetDialog->setFileMode(QFileDialog::ExistingFile);
1773                   loadDrumsetDialog->setOption(QFileDialog::DontUseNativeDialog, true);
1774                   loadDrumsetDialog->setDirectory(defaultPath);
1775 
1776                   restoreDialogState("loadDrumsetDialog", loadDrumsetDialog);
1777                   loadDrumsetDialog->setAcceptMode(QFileDialog::AcceptOpen);
1778                   }
1779             dialog = loadDrumsetDialog;
1780             }
1781       else {
1782             if (saveDrumsetDialog == 0) {
1783                   saveDrumsetDialog = new QFileDialog(this);
1784                   saveDrumsetDialog->setAcceptMode(QFileDialog::AcceptSave);
1785                   saveDrumsetDialog->setFileMode(QFileDialog::AnyFile);
1786                   saveDrumsetDialog->setOption(QFileDialog::DontConfirmOverwrite, false);
1787                   saveDrumsetDialog->setOption(QFileDialog::DontUseNativeDialog, true);
1788                   saveDrumsetDialog->setDirectory(defaultPath);
1789 
1790                   restoreDialogState("saveDrumsetDialog", saveDrumsetDialog);
1791                   saveDrumsetDialog->setAcceptMode(QFileDialog::AcceptSave);
1792                   }
1793             dialog = saveDrumsetDialog;
1794             }
1795       dialog->setWindowTitle(title);
1796       dialog->setNameFilter(filter);
1797 
1798       // setup side bar urls
1799       dialog->setSidebarUrls(urls);
1800 
1801       if (dialog->exec()) {
1802             QStringList result = dialog->selectedFiles();
1803             return result.front();
1804             }
1805       return QString();
1806       }
1807 
1808 //---------------------------------------------------------
1809 //   printFile
1810 //---------------------------------------------------------
1811 
printFile()1812 void MuseScore::printFile()
1813       {
1814 #ifndef QT_NO_PRINTER
1815       LayoutMode layoutMode = cs->layoutMode();
1816       if (layoutMode != LayoutMode::PAGE) {
1817             cs->setLayoutMode(LayoutMode::PAGE);
1818             cs->doLayout();
1819             }
1820 
1821       QPrinter printerDev(QPrinter::HighResolution);
1822       QSizeF size(cs->styleD(Sid::pageWidth), cs->styleD(Sid::pageHeight));
1823       QPageSize ps(QPageSize::id(size, QPageSize::Inch));
1824       printerDev.setPageSize(ps);
1825       printerDev.setPageOrientation(size.width() > size.height() ? QPageLayout::Landscape : QPageLayout::Portrait);
1826 
1827       printerDev.setCreator("MuseScore Version: " VERSION);
1828       printerDev.setFullPage(true);
1829       if (!printerDev.setPageMargins(QMarginsF()))
1830             qDebug("unable to clear printer margins");
1831       printerDev.setColorMode(QPrinter::Color);
1832       if (cs->isMaster())
1833             printerDev.setDocName(cs->masterScore()->fileInfo()->completeBaseName());
1834       else
1835             printerDev.setDocName(cs->excerpt()->title());
1836       printerDev.setOutputFormat(QPrinter::NativeFormat);
1837       int pages    = cs->pages().size();
1838       printerDev.setFromTo(1, pages);
1839 
1840 #if defined(Q_OS_MAC) || defined(Q_OS_WIN)
1841       printerDev.setOutputFileName("");
1842 #else
1843       // when setting this on windows platform, pd.exec() does not
1844       // show dialog
1845       if (cs->isMaster())
1846             printerDev.setOutputFileName(cs->masterScore()->fileInfo()->path() + "/" + cs->masterScore()->fileInfo()->completeBaseName() + ".pdf");
1847       else
1848             printerDev.setOutputFileName(cs->masterScore()->fileInfo()->path() + "/" + cs->excerpt()->title() + ".pdf");
1849 #endif
1850 
1851       QPrintDialog pd(&printerDev, 0);
1852 
1853       if (pd.exec()) {
1854             QPainter p(&printerDev);
1855             p.setRenderHint(QPainter::Antialiasing, true);
1856             p.setRenderHint(QPainter::TextAntialiasing, true);
1857             double mag_ = printerDev.logicalDpiX() / DPI;
1858 
1859             double pr = MScore::pixelRatio;
1860             MScore::pixelRatio = 1.0 / mag_;
1861             p.scale(mag_, mag_);
1862 
1863             int fromPage = printerDev.fromPage() - 1;
1864             int toPage   = printerDev.toPage() - 1;
1865             if (fromPage < 0)
1866                   fromPage = 0;
1867             if ((toPage < 0) || (toPage >= pages))
1868                   toPage = pages - 1;
1869 
1870             for (int copy = 0; copy < printerDev.numCopies(); ++copy) {
1871                   bool firstPage = true;
1872                   for (int n = fromPage; n <= toPage; ++n) {
1873                         if (!firstPage)
1874                               printerDev.newPage();
1875                         firstPage = false;
1876 
1877                         cs->print(&p, n);
1878                         if ((copy + 1) < printerDev.numCopies())
1879                               printerDev.newPage();
1880                         }
1881                   }
1882             p.end();
1883             MScore::pixelRatio = pr;
1884             }
1885 
1886       if (layoutMode != cs->layoutMode()) {
1887             cs->setLayoutMode(layoutMode);
1888             cs->doLayout();
1889             }
1890 #endif
1891       }
1892 
1893 //---------------------------------------------------------
1894 //   saveAs
1895 //---------------------------------------------------------
1896 
saveAs(Score * cs_,bool saveCopy,const QString & path,const QString & ext,SaveReplacePolicy * replacePolicy)1897 bool MuseScore::saveAs(Score* cs_, bool saveCopy, const QString& path, const QString& ext, SaveReplacePolicy* replacePolicy)
1898       {
1899       bool rv = false;
1900       QString suffix = "." + ext;
1901       QString fn(path);
1902       if (!fn.endsWith(suffix))
1903             fn += suffix;
1904 
1905       LayoutMode layoutMode = cs_->layoutMode();
1906       if (ext == "mscx" || ext == "mscz") {
1907             // save as mscore *.msc[xz] file
1908             QFileInfo fi(fn);
1909             rv = true;
1910             // store new file and path into score fileInfo
1911             // to have it accessible to resources
1912             QFileInfo originalScoreFileInfo(*cs_->masterScore()->fileInfo());
1913             cs_->masterScore()->fileInfo()->setFile(fn);
1914             if (!cs_->isMaster()) { // clone metaTags from masterScore
1915                   QMapIterator<QString, QString> j(cs_->masterScore()->metaTags());
1916                   while (j.hasNext()) {
1917                         j.next();
1918                         if (j.key() != "partName") // don't copy "partName" should that exist in masterScore
1919                               cs_->metaTags().insert(j.key(), j.value());
1920 #if defined(Q_OS_WIN)   // Update "platform", may not be worth the effort
1921                         cs_->metaTags().insert("platform", "Microsoft Windows");
1922 #elif defined(Q_OS_MAC)
1923                         cs_->metaTags().insert("platform", "Apple Macintosh");
1924 #elif defined(Q_OS_LINUX)
1925                         cs_->metaTags().insert("platform", "Linux");
1926 #else
1927                         cs_->metaTags().insert("platform", "Unknown");
1928 #endif
1929                         cs_->metaTags().insert("source", ""); // Empty "source" to avoid clashes with masterrScore when doing "Save online"
1930                         cs_->metaTags().insert("creationDate", QDate::currentDate().toString(Qt::ISODate)); // update "creationDate"
1931                         }
1932                   }
1933             try {
1934                   if (ext == "mscz")
1935                         cs_->saveCompressedFile(fi, false);
1936                   else
1937                         cs_->saveFile(fi);
1938                   }
1939             catch (QString s) {
1940                   rv = false;
1941                   QMessageBox::critical(this, tr("Save As"), s);
1942                   }
1943             if (!cs_->isMaster()) { // remove metaTags added above
1944                   QMapIterator<QString, QString> j(cs_->masterScore()->metaTags());
1945                   while (j.hasNext()) {
1946                         j.next();
1947                         // remove all but "partName", should that exist in masterScore
1948                         if (j.key() != "partName")
1949                               cs_->metaTags().remove(j.key());
1950                         }
1951                   }
1952             *cs_->masterScore()->fileInfo() = originalScoreFileInfo;          // restore original file info
1953 
1954             if (rv && !saveCopy) {
1955                   cs_->masterScore()->fileInfo()->setFile(fn);
1956                   updateWindowTitle(cs_);
1957                   cs_->undoStack()->setClean();
1958                   dirtyChanged(cs_);
1959                   cs_->setCreated(false);
1960                   scoreCmpTool->updateScoreVersions(cs_);
1961                   addRecentScore(cs_);
1962                   writeSessionFile(false);
1963                   }
1964             }
1965       else if ((ext == "musicxml") || (ext == "xml")) {
1966             // save as MusicXML *.musicxml file
1967             rv = saveXml(cs_, fn);
1968             }
1969       else if (ext == "mxl") {
1970             // save as compressed MusicXML *.mxl file
1971             rv = saveMxl(cs_, fn);
1972             }
1973       else if ((ext == "mid") || (ext == "midi")) {
1974             // save as midi file *.mid resp. *.midi
1975             rv = saveMidi(cs_, fn);
1976             }
1977       else if (ext == "pdf") {
1978             // save as pdf file *.pdf
1979             cs_->switchToPageMode();
1980             rv = savePdf(cs_, fn);
1981             }
1982       else if (ext == "png") {
1983             // save as png file *.png
1984             cs_->switchToPageMode();
1985             rv = savePng(cs_, fn, replacePolicy);
1986             }
1987       else if (ext == "svg") {
1988             // save as svg file *.svg
1989             cs_->switchToPageMode();
1990             rv = saveSvg(cs_, fn, NotesColors(), replacePolicy);
1991             }
1992 #ifdef HAS_AUDIOFILE
1993       else if (ext == "wav" || ext == "flac" || ext == "ogg")
1994             rv = saveAudio(cs_, fn);
1995 #endif
1996 #ifdef USE_LAME
1997       else if (ext == "mp3")
1998             rv = saveMp3(cs_, fn);
1999 #endif
2000       else if (ext == "spos") {
2001             cs_->switchToPageMode();
2002             // save positions of segments
2003             rv = savePositions(cs_, fn, true);
2004             }
2005       else if (ext == "mpos") {
2006             cs_->switchToPageMode();
2007             // save positions of measures
2008             rv = savePositions(cs_, fn, false);
2009             }
2010       else if (ext == "mlog") {
2011             rv = cs_->sanityCheck(fn);
2012             }
2013       else if (ext == "metajson") {
2014             rv = saveMetadataJSON(cs, fn);
2015             }
2016       else {
2017             qDebug("Internal error: unsupported extension <%s>",
2018                qPrintable(ext));
2019             return false;
2020             }
2021       if (!rv && !MScore::noGui)
2022             QMessageBox::critical(this, tr("MuseScore:"), tr("Cannot write into %1").arg(fn));
2023 
2024       if (layoutMode != cs_->layoutMode()) {
2025             cs_->setLayoutMode(layoutMode);
2026             cs_->doLayout();
2027             }
2028       return rv;
2029       }
2030 
2031 //---------------------------------------------------------
2032 //   saveMidi
2033 //---------------------------------------------------------
2034 
saveMidi(Score * score,const QString & name)2035 bool MuseScore::saveMidi(Score* score, const QString& name)
2036       {
2037       ExportMidi em(score);
2038       return em.write(name, preferences.getBool(PREF_IO_MIDI_EXPANDREPEATS), preferences.getBool(PREF_IO_MIDI_EXPORTRPNS), synthesizerState());
2039       }
2040 
saveMidi(Score * score,QIODevice * device)2041 bool MuseScore::saveMidi(Score* score, QIODevice* device)
2042       {
2043       ExportMidi em(score);
2044       return em.write(device, preferences.getBool(PREF_IO_MIDI_EXPANDREPEATS), preferences.getBool(PREF_IO_MIDI_EXPORTRPNS), synthesizerState());
2045       }
2046 
2047 //---------------------------------------------------------
2048 //   savePdf
2049 //---------------------------------------------------------
2050 
savePdf(const QString & saveName)2051 bool MuseScore::savePdf(const QString& saveName)
2052       {
2053       return savePdf(cs, saveName);
2054       }
2055 
savePdf(Score * cs_,const QString & saveName)2056 bool MuseScore::savePdf(Score* cs_, const QString& saveName)
2057       {
2058       QPrinter printer;
2059       printer.setOutputFileName(saveName);
2060       return savePdf(cs_, printer);
2061       }
2062 
savePdf(Score * cs_,QPrinter & printer)2063 bool MuseScore::savePdf(Score* cs_, QPrinter& printer)
2064       {
2065       cs_->setPrinting(true);
2066       MScore::pdfPrinting = true;
2067 
2068       printer.setResolution(preferences.getInt(PREF_EXPORT_PDF_DPI));
2069       QSizeF size(cs_->styleD(Sid::pageWidth), cs_->styleD(Sid::pageHeight));
2070       printer.setPaperSize(size, QPrinter::Inch);
2071       printer.setFullPage(true);
2072       printer.setColorMode(QPrinter::Color);
2073 #if defined(Q_OS_MAC)
2074       printer.setOutputFormat(QPrinter::NativeFormat);
2075 #else
2076       printer.setOutputFormat(QPrinter::PdfFormat);
2077 #endif
2078 
2079       printer.setCreator("MuseScore Version: " VERSION);
2080       if (!printer.setPageMargins(QMarginsF()))
2081             qDebug("unable to clear printer margins");
2082 
2083       QString title = cs_->metaTag("workTitle");
2084       if (title.isEmpty()) // workTitle unset?
2085             title = cs_->masterScore()->title(); // fall back to (master)score's tab title
2086       if (!cs_->isMaster()) { // excerpt?
2087             QString partname = cs_->metaTag("partName");
2088             if (partname.isEmpty()) // partName unset?
2089                   partname = cs_->title(); // fall back to excerpt's tab title
2090             title += " - " + partname;
2091             }
2092       printer.setDocName(title); // set PDF's meta data for Title
2093 
2094       QPainter p;
2095       if (!p.begin(&printer))
2096             return false;
2097       p.setRenderHint(QPainter::Antialiasing, true);
2098       p.setRenderHint(QPainter::TextAntialiasing, true);
2099 
2100       p.setViewport(QRect(0.0, 0.0, size.width() * printer.logicalDpiX(),
2101          size.height() * printer.logicalDpiY()));
2102       p.setWindow(QRect(0.0, 0.0, size.width() * DPI, size.height() * DPI));
2103 
2104       double pr = MScore::pixelRatio;
2105       MScore::pixelRatio = DPI / printer.logicalDpiX();
2106 
2107       const QList<Page*> pl = cs_->pages();
2108       int pages = pl.size();
2109       bool firstPage = true;
2110       for (int n = 0; n < pages; ++n) {
2111             if (!firstPage)
2112                   printer.newPage();
2113             firstPage = false;
2114             cs_->print(&p, n);
2115             }
2116       p.end();
2117       cs_->setPrinting(false);
2118 
2119       MScore::pixelRatio = pr;
2120       MScore::pdfPrinting = false;
2121       return true;
2122       }
2123 
savePdf(QList<Score * > cs_,const QString & saveName)2124 bool MuseScore::savePdf(QList<Score*> cs_, const QString& saveName)
2125       {
2126       if (cs_.empty())
2127             return false;
2128       Score* firstScore = cs_[0];
2129 
2130       QPrinter printer;
2131       printer.setOutputFileName(saveName);
2132       printer.setResolution(preferences.getInt(PREF_EXPORT_PDF_DPI));
2133       QSizeF size(firstScore->styleD(Sid::pageWidth), firstScore->styleD(Sid::pageHeight));
2134       QPageSize ps(QPageSize::id(size, QPageSize::Inch, QPageSize::FuzzyOrientationMatch));
2135       printer.setPageSize(ps);
2136       printer.setPageOrientation(size.width() > size.height() ? QPageLayout::Landscape : QPageLayout::Portrait);
2137       printer.setFullPage(true);
2138       printer.setColorMode(QPrinter::Color);
2139 #if defined(Q_OS_MAC)
2140       printer.setOutputFormat(QPrinter::NativeFormat);
2141 #else
2142       printer.setOutputFormat(QPrinter::PdfFormat);
2143 #endif
2144 
2145       printer.setCreator("MuseScore Version: " VERSION);
2146       if (!printer.setPageMargins(QMarginsF()))
2147             qDebug("unable to clear printer margins");
2148 
2149       QString title = firstScore->metaTag("workTitle");
2150       if (title.isEmpty()) // workTitle unset?
2151             title = firstScore->title(); // fall back to (master)score's tab title
2152       printer.setDocName(title); // set PDF's meta data for Title
2153 
2154       QPainter p;
2155       if (!p.begin(&printer))
2156             return false;
2157 
2158       p.setRenderHint(QPainter::Antialiasing, true);
2159       p.setRenderHint(QPainter::TextAntialiasing, true);
2160 
2161       double pr = MScore::pixelRatio;
2162 
2163       bool firstPage = true;
2164       for (Score* s : cs_) {
2165             LayoutMode layoutMode = s->layoutMode();
2166             if (layoutMode != LayoutMode::PAGE) {
2167                   s->setLayoutMode(LayoutMode::PAGE);
2168                   }
2169             s->doLayout();
2170 
2171             // done in Score::print() also, but do it here as well to be safe
2172             s->setPrinting(true);
2173             MScore::pdfPrinting = true;
2174 
2175             QSizeF size1(s->styleD(Sid::pageWidth), s->styleD(Sid::pageHeight));
2176             QPageSize ps1(QPageSize::id(size1, QPageSize::Inch, QPageSize::FuzzyOrientationMatch));
2177             printer.setPageSize(ps1);
2178             printer.setPageOrientation(size1.width() > size1.height() ? QPageLayout::Landscape : QPageLayout::Portrait);
2179             p.setViewport(QRect(0.0, 0.0, size1.width() * printer.logicalDpiX(),
2180                size1.height() * printer.logicalDpiY()));
2181             p.setWindow(QRect(0.0, 0.0, size1.width() * DPI, size1.height() * DPI));
2182 
2183             MScore::pixelRatio = DPI / printer.logicalDpiX();
2184             const QList<Page*> pl = s->pages();
2185             int pages    = pl.size();
2186             for (int n = 0; n < pages; ++n) {
2187                   if (!firstPage)
2188                         printer.newPage();
2189                   firstPage = false;
2190                   s->print(&p, n);
2191                   }
2192             MScore::pixelRatio = pr;
2193 
2194             //reset score
2195             s->setPrinting(false);
2196             MScore::pdfPrinting = false;
2197 
2198             if (layoutMode != s->layoutMode()) {
2199                   s->setLayoutMode(layoutMode);
2200                   s->doLayout();
2201                   }
2202             }
2203       p.end();
2204       return true;
2205       }
2206 
2207 //---------------------------------------------------------
2208 //   importSoundfont
2209 //---------------------------------------------------------
2210 
importSoundfont(QString name)2211 void importSoundfont(QString name)
2212       {
2213       QFileInfo info(name);
2214       int ret = QMessageBox::question(0, QWidget::tr("Install SoundFont"),
2215             QWidget::tr("Do you want to install the SoundFont %1?").arg(info.fileName()),
2216              QMessageBox::Yes|QMessageBox::No, QMessageBox::NoButton);
2217       if (ret == QMessageBox::Yes) {
2218             QStringList pl = preferences.getString(PREF_APP_PATHS_MYSOUNDFONTS).split(";");
2219             QString destPath;
2220             for (QString s : pl) {
2221                   QFileInfo dest(s);
2222                   if (dest.isWritable())
2223                         destPath = s;
2224                   }
2225             if (!destPath.isEmpty()) {
2226                   QString destFilePath = destPath+ "/" +info.fileName();
2227                   QFileInfo destFileInfo(destFilePath);
2228                   QFile destFile(destFilePath);
2229                   if (destFileInfo.exists()) {
2230                         int ret1 = QMessageBox::question(0, QWidget::tr("Overwrite?"),
2231                           QWidget::tr("%1 already exists.\nDo you want to overwrite it?").arg(destFileInfo.absoluteFilePath()),
2232                           QMessageBox::Yes|QMessageBox::No, QMessageBox::No);
2233                         if (ret1 == QMessageBox::No)
2234                               return;
2235                         destFile.remove();
2236                         }
2237                   QFile orig(name);
2238                   if (orig.copy(destFilePath)) {
2239                         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."));
2240                         }
2241                   }
2242             }
2243       }
2244 
2245 //---------------------------------------------------------
2246 //   importExtension
2247 //---------------------------------------------------------
2248 
importExtension(QString name)2249 void importExtension(QString name)
2250       {
2251       mscore->importExtension(name);
2252       }
2253 
2254 //---------------------------------------------------------
2255 //   readScore
2256 ///   Import file \a name
2257 //---------------------------------------------------------
2258 
readScore(MasterScore * score,QString name,bool ignoreVersionError)2259 Score::FileError readScore(MasterScore* score, QString name, bool ignoreVersionError)
2260       {
2261       ScoreLoad sl;
2262 
2263       QFileInfo info(name);
2264       QString suffix  = info.suffix().toLower();
2265       score->setName(info.completeBaseName());
2266       score->setImportedFilePath(name);
2267 
2268       // Set the default synthesizer state before we read
2269       if (synti)
2270             score->setSynthesizerState(synti->state());
2271 
2272       auto read = [](MasterScore* score, const QString& name, bool ignoreVersionError, bool imported = false)->Score::FileError {
2273             Score::FileError rv = score->loadMsc(name, ignoreVersionError);
2274             if (imported || (score && score->masterScore()->fileInfo()->path().startsWith(":/")))
2275                   score->setCreated(true);
2276             score->setAutosaveDirty(!imported);
2277             return rv;
2278             };
2279 
2280       if (suffix == "mscz" || suffix == "mscx") {
2281             Score::FileError rv = read(score, name, ignoreVersionError);
2282             if (rv != Score::FileError::FILE_NO_ERROR)
2283                   return rv;
2284             }
2285       else if (suffix == "mscz," || suffix == "mscx,") {
2286             Score::FileError rv = read(score, name, ignoreVersionError, true);
2287             if (rv != Score::FileError::FILE_NO_ERROR)
2288                   return rv;
2289             }
2290       else if (suffix == "sf2" || suffix == "sf3") {
2291             importSoundfont(name);
2292             return Score::FileError::FILE_IGNORE_ERROR;
2293             }
2294       else if (suffix == "muxt") {
2295             importExtension(name);
2296             return Score::FileError::FILE_IGNORE_ERROR;
2297             }
2298       else {
2299             // typedef Score::FileError (*ImportFunction)(MasterScore*, const QString&);
2300             struct ImportDef {
2301                   const char* extension;
2302                   // ImportFunction importF;
2303                   Score::FileError (*importF)(MasterScore*, const QString&);
2304                   };
2305             static const ImportDef imports[] = {
2306                   { "xml",  &importMusicXml           },
2307                   { "musicxml", &importMusicXml       },
2308                   { "mxl",  &importCompressedMusicXml },
2309                   { "mid",  &importMidi               },
2310                   { "midi", &importMidi               },
2311                   { "kar",  &importMidi               },
2312                   { "md",   &importMuseData           },
2313                   { "mgu",  &importBB                 },
2314                   { "sgu",  &importBB                 },
2315                   { "cap",  &importCapella            },
2316                   { "capx", &importCapXml             },
2317                   { "ove",  &importOve                },
2318                   { "scw",  &importOve                },
2319 #ifdef OMR
2320                   { "pdf",  &importPdf                },
2321 #endif
2322                   { "bmw",  &importBww                },
2323                   { "bww",  &importBww                },
2324                   { "gtp",  &importGTP                },
2325                   { "gp3",  &importGTP                },
2326                   { "gp4",  &importGTP                },
2327                   { "gp5",  &importGTP                },
2328                   { "gpx",  &importGTP                },
2329                   { "gp",   &importGTP                },
2330                   { "ptb",  &importGTP                },
2331 #ifdef AVSOMR
2332                   { "msmr", &importMSMR               },
2333                   { "pdf",  &loadAndImportMSMR        },
2334                   { "png",  &loadAndImportMSMR        },
2335                   { "jpg",  &loadAndImportMSMR        },
2336 #endif
2337                   };
2338 
2339             // import
2340             if (!preferences.getString(PREF_IMPORT_STYLE_STYLEFILE).isEmpty()) {
2341                   QFile f(preferences.getString(PREF_IMPORT_STYLE_STYLEFILE));
2342                   // silently ignore style file on error
2343                   if (f.open(QIODevice::ReadOnly))
2344                         score->style().load(&f);
2345                   }
2346             else {
2347                   score->style().checkChordList();
2348                   }
2349             bool found = false;
2350             for (auto i : imports) {
2351                   if (i.extension == suffix) {
2352                         Score::FileError rv = (*i.importF)(score, name);
2353                         if (rv != Score::FileError::FILE_NO_ERROR)
2354                               return rv;
2355                         found = true;
2356                         break;
2357                         }
2358                   }
2359             if (!found) {
2360                   qDebug("unknown file suffix <%s>, name <%s>", qPrintable(suffix), qPrintable(name));
2361                   return Score::FileError::FILE_UNKNOWN_TYPE;
2362                   }
2363             score->setMetaTag("originalFormat", suffix);
2364             score->connectTies();
2365             if (!score->avsOmr()) //! NOTE For avsomr сreated is set upon import
2366                   score->setCreated(true); // force save as for imported files
2367             }
2368 
2369       for (Part* p : score->parts()) {
2370             p->updateHarmonyChannels(false);
2371             }
2372       score->rebuildMidiMapping();
2373       score->setSoloMute();
2374       for (Score* s : score->scoreList()) {
2375             s->setPlaylistDirty();
2376             s->addLayoutFlags(LayoutFlag::FIX_PITCH_VELO);
2377             s->setLayoutAll();
2378             }
2379       score->updateChannel();
2380       score->updateExpressive(MuseScore::synthesizer("Fluid"));
2381       score->setSaved(false);
2382       score->update();
2383       score->styleChanged();
2384 
2385       if (!ignoreVersionError && !MScore::noGui)
2386             if (!score->sanityCheck(QString()))
2387                   return Score::FileError::FILE_CORRUPTED;
2388       return Score::FileError::FILE_NO_ERROR;
2389       }
2390 
2391 //---------------------------------------------------------
2392 //   saveAs
2393 //    return true on success
2394 //---------------------------------------------------------
2395 
2396 /**
2397  Save the current score using a different name or type.
2398  Handles the GUI's file-save-as and file-save-a-copy actions.
2399  The saveCopy flag, if true, does not change the name of the active score nor marks it clean.
2400  Return true if OK and false on error.
2401  */
2402 
saveAs(Score * cs_,bool saveCopy)2403 bool MuseScore::saveAs(Score* cs_, bool saveCopy)
2404       {
2405       QStringList fl;
2406       fl.append(tr("MuseScore 3 File") + " (*.mscz)");
2407       fl.append(tr("Uncompressed MuseScore 3 File") + " (*.mscx)");     // for debugging purposes
2408       QString saveDialogTitle = saveCopy ? tr("Save a Copy") :
2409                                            tr("Save As");
2410 
2411       QString saveDirectory;
2412       if (cs_->masterScore()->fileInfo()->exists())
2413             saveDirectory = cs_->masterScore()->fileInfo()->dir().path();
2414       else {
2415             QSettings set;
2416             if (saveCopy) {
2417                   if (mscore->lastSaveCopyDirectory.isEmpty())
2418                         mscore->lastSaveCopyDirectory = set.value("lastSaveCopyDirectory", preferences.getString(PREF_APP_PATHS_MYSCORES)).toString();
2419                   saveDirectory = mscore->lastSaveCopyDirectory;
2420                   }
2421             else {
2422                   if (mscore->lastSaveDirectory.isEmpty())
2423                         mscore->lastSaveDirectory = set.value("lastSaveDirectory", preferences.getString(PREF_APP_PATHS_MYSCORES)).toString();
2424                   saveDirectory = mscore->lastSaveDirectory;
2425                   }
2426             }
2427 
2428       if (saveDirectory.isEmpty())
2429             saveDirectory = preferences.getString(PREF_APP_PATHS_MYSCORES);
2430 
2431       QString fileBaseName = cs_->masterScore()->fileInfo()->completeBaseName();
2432       QString fileName = cs_->masterScore()->fileInfo()->fileName();
2433       // is backup file
2434       if (fileBaseName.startsWith(".")
2435          && (fileName.endsWith(".mscz,") || fileName.endsWith(".mscx,")))
2436             fileBaseName.remove(0, 1); // remove the "." at the beginning of file name
2437 
2438       QString name;
2439 #ifdef Q_OS_WIN
2440       if (QOperatingSystemVersion::current() <= QOperatingSystemVersion(QOperatingSystemVersion::Windows, 5, 1)) {   //XP
2441             if (!cs_->isMaster())
2442                   name = QString("%1/%2-%3").arg(saveDirectory).arg(fileBaseName).arg(createDefaultFileName(cs->title()));
2443             else
2444                   name = QString("%1/%2").arg(saveDirectory).arg(fileBaseName);
2445             }
2446       else
2447 #endif
2448       if (!cs_->isMaster())
2449             name = QString("%1/%2-%3.mscz").arg(saveDirectory).arg(fileBaseName).arg(createDefaultFileName(cs->title()));
2450       else
2451             name = QString("%1/%2.mscz").arg(saveDirectory).arg(fileBaseName);
2452 
2453       QString filter = fl.join(";;");
2454       QString fn     = mscore->getSaveScoreName(saveDialogTitle, name, filter);
2455       if (fn.isEmpty())
2456             return false;
2457 
2458       QFileInfo fi(fn);
2459       if (saveCopy)
2460             mscore->lastSaveCopyDirectory = fi.absolutePath();
2461       else
2462             mscore->lastSaveDirectory = fi.absolutePath();
2463 
2464       if (fi.suffix().isEmpty()) {
2465             if (!MScore::noGui)
2466                   QMessageBox::critical(mscore, tr("Save As"), tr("Cannot determine file type"));
2467             return false;
2468             }
2469       return saveAs(cs_, saveCopy, fn, fi.suffix());
2470       }
2471 
2472 //---------------------------------------------------------
2473 //   saveSelection
2474 //    return true on success
2475 //---------------------------------------------------------
2476 
saveSelection(Score * cs_)2477 bool MuseScore::saveSelection(Score* cs_)
2478       {
2479       if (!cs_->selection().isRange()) {
2480             if(!MScore::noGui) QMessageBox::warning(mscore, tr("Save Selection"), tr("Please select one or more measures"));
2481             return false;
2482             }
2483       QStringList fl;
2484       fl.append(tr("MuseScore 3 File") + " (*.mscz)");
2485       fl.append(tr("Uncompressed MuseScore 3 File") + " (*.mscx)");     // for debugging purposes
2486       QString saveDialogTitle = tr("Save Selection");
2487 
2488       QString saveDirectory;
2489       if (cs_->masterScore()->fileInfo()->exists())
2490             saveDirectory = cs_->masterScore()->fileInfo()->dir().path();
2491       else {
2492             QSettings set;
2493             if (mscore->lastSaveDirectory.isEmpty())
2494                   mscore->lastSaveDirectory = set.value("lastSaveDirectory", preferences.getString(PREF_APP_PATHS_MYSCORES)).toString();
2495             saveDirectory = mscore->lastSaveDirectory;
2496             }
2497 
2498       if (saveDirectory.isEmpty())
2499             saveDirectory = preferences.getString(PREF_APP_PATHS_MYSCORES);
2500 
2501       QString name   = QString("%1/%2.mscz").arg(saveDirectory).arg(cs_->title());
2502       QString filter = fl.join(";;");
2503       QString fn     = mscore->getSaveScoreName(saveDialogTitle, name, filter);
2504       if (fn.isEmpty())
2505             return false;
2506 
2507       QFileInfo fi(fn);
2508       mscore->lastSaveDirectory = fi.absolutePath();
2509 
2510       QString ext = fi.suffix();
2511       if (ext.isEmpty()) {
2512             QMessageBox::critical(mscore, tr("Save Selection"), tr("Cannot determine file type"));
2513             return false;
2514             }
2515       bool rv = true;
2516       try {
2517             cs_->saveCompressedFile(fi, true);
2518             }
2519       catch (QString s) {
2520             rv = false;
2521             QMessageBox::critical(this, tr("Save Selected"), s);
2522             }
2523       return rv;
2524       }
2525 
2526 //---------------------------------------------------------
2527 //   addImage
2528 //---------------------------------------------------------
2529 
addImage(Score * score,Element * e)2530 void MuseScore::addImage(Score* score, Element* e)
2531       {
2532       QString fn = QFileDialog::getOpenFileName(
2533          0,
2534          tr("Insert Image"),
2535          "",            // lastOpenPath,
2536          tr("All Supported Files") + " (*.svg *.jpg *.jpeg *.png);;" +
2537          tr("Scalable Vector Graphics") + " (*.svg);;" +
2538          tr("JPEG") + " (*.jpg *.jpeg);;" +
2539          tr("PNG Bitmap Graphic") + " (*.png)",
2540          0,
2541          preferences.getBool(PREF_UI_APP_USENATIVEDIALOGS) ? QFileDialog::Options() : QFileDialog::DontUseNativeDialog
2542          );
2543       if (fn.isEmpty())
2544             return;
2545 
2546       QFileInfo fi(fn);
2547       Image* s = new Image(score);
2548       QString suffix(fi.suffix().toLower());
2549 
2550       if (suffix == "svg")
2551             s->setImageType(ImageType::SVG);
2552       else if (suffix == "jpg" || suffix == "jpeg" || suffix == "png")
2553             s->setImageType(ImageType::RASTER);
2554       else
2555             return;
2556       s->load(fn);
2557       s->setParent(e);
2558       score->undoAddElement(s);
2559       }
2560 
2561 #if 0
2562 //---------------------------------------------------------
2563 //   trim
2564 //    returns copy of source with whitespace trimmed and margin added
2565 //---------------------------------------------------------
2566 
2567 static QRect trim(QImage source, int margin)
2568       {
2569       int w = source.width();
2570       int h = source.height();
2571       int x1 = w;
2572       int x2 = 0;
2573       int y1 = h;
2574       int y2 = 0;
2575       for (int x = 0; x < w; ++x) {
2576             for (int y = 0; y < h; ++y) {
2577                   QRgb c = source.pixel(x, y);
2578                   if (c != 0 && c != 0xffffffff) {
2579                         if (x < x1)
2580                               x1 = x;
2581                         if (x > x2)
2582                               x2 = x;
2583                         if (y < y1)
2584                               y1 = y;
2585                         if (y > y2)
2586                               y2 = y;
2587                         }
2588                   }
2589             }
2590       int x = qMax(x1 - margin, 0);
2591       int y = qMax(y1 - margin, 0);
2592       w = qMin(w, x2 + 1 + margin) - x;
2593       h = qMin(h, y2 + 1 + margin) - y;
2594       return QRect(x, y, w, h);
2595       }
2596 #endif
2597 
2598 //---------------------------------------------------------
2599 //   savePng
2600 //    return true on success.  Works with editor, shows additional windows.
2601 //---------------------------------------------------------
2602 
savePng(Score * score,const QString & name,SaveReplacePolicy * replacePolicy)2603 bool MuseScore::savePng(Score* score, const QString& name, SaveReplacePolicy* replacePolicy)
2604       {
2605       int pages    = score->pages().size();
2606       int padding  = QString("%1").arg(pages).size();
2607       bool success = true;
2608       SaveReplacePolicy _replacePolicy = (replacePolicy != nullptr ? *replacePolicy : SaveReplacePolicy::NO_CHOICE);
2609 
2610       for (int pageNumber = 0; pageNumber < pages; ++pageNumber) {
2611             QString fileName(name);
2612             if (fileName.endsWith(".png"))
2613                   fileName = fileName.left(fileName.size() - 4);
2614             fileName += QString("-%1.png").arg(pageNumber+1, padding, 10, QLatin1Char('0'));
2615             if (!converterMode && QFileInfo(fileName).exists()) {
2616                   switch (_replacePolicy) {
2617                         case SaveReplacePolicy::NO_CHOICE:
2618                               {
2619                               int responseCode = mscore->askOverwriteAll(fileName);
2620                               if (responseCode == QMessageBox::YesToAll) {
2621                                     _replacePolicy = SaveReplacePolicy::REPLACE_ALL;
2622                                     break; // Break out of the switch; go on and replace the existing file
2623                                     }
2624                               else if (responseCode == QMessageBox::NoToAll) {
2625                                     _replacePolicy = SaveReplacePolicy::SKIP_ALL;
2626                                     continue; // Continue in the `for` loop
2627                                     }
2628                               else if (responseCode == QMessageBox::No)
2629                                     continue;
2630                               break;
2631                               }
2632                         case SaveReplacePolicy::SKIP_ALL:
2633                               continue;
2634                         case SaveReplacePolicy::REPLACE_ALL:
2635                               break;
2636                         }
2637                   }
2638             QFile f(fileName);
2639             if (!f.open(QIODevice::WriteOnly) || !savePng(score, &f, pageNumber)) {
2640                   success = false;
2641                   break;
2642                   }
2643             }
2644       if (replacePolicy != nullptr)
2645             *replacePolicy = _replacePolicy;
2646       return success;
2647       }
2648 
2649 //---------------------------------------------------------
2650 //   savePng with options
2651 //    return true on success
2652 //---------------------------------------------------------
2653 
savePng(Score * score,QIODevice * device,int pageNumber,bool drawPageBackground)2654 bool MuseScore::savePng(Score* score, QIODevice* device, int pageNumber, bool drawPageBackground)
2655       {
2656       const bool screenshot = false;
2657       const bool transparent = preferences.getBool(PREF_EXPORT_PNG_USETRANSPARENCY) && !drawPageBackground;
2658       const double convDpi = preferences.getDouble(PREF_EXPORT_PNG_RESOLUTION);
2659       const int localTrimMargin = trimMargin;
2660       const QImage::Format format = QImage::Format_ARGB32_Premultiplied;
2661 
2662       bool rv = true;
2663       score->setPrinting(!screenshot);    // don’t print page break symbols etc.
2664       double pr = MScore::pixelRatio;
2665 
2666       QImage::Format f;
2667       if (format != QImage::Format_Indexed8)
2668           f = format;
2669       else
2670           f = QImage::Format_ARGB32_Premultiplied;
2671 
2672       const QList<Page*>& pl = score->pages();
2673 
2674       Page* page = pl.at(pageNumber);
2675       QRectF r;
2676       if (localTrimMargin >= 0) {
2677             QMarginsF margins(localTrimMargin, localTrimMargin, localTrimMargin, localTrimMargin);
2678             r = page->tbbox() + margins;
2679             }
2680       else
2681             r = page->abbox();
2682       int w = lrint(r.width()  * convDpi / DPI);
2683       int h = lrint(r.height() * convDpi / DPI);
2684 
2685       QImage printer(w, h, f);
2686       printer.setDotsPerMeterX(lrint((convDpi * 1000) / INCH));
2687       printer.setDotsPerMeterY(lrint((convDpi * 1000) / INCH));
2688 
2689       printer.fill(transparent ? 0 : 0xffffffff);
2690       double mag_ = convDpi / DPI;
2691       MScore::pixelRatio = 1.0 / mag_;
2692 
2693       QPainter p(&printer);
2694       p.setRenderHint(QPainter::Antialiasing, true);
2695       p.setRenderHint(QPainter::TextAntialiasing, true);
2696       p.scale(mag_, mag_);
2697       if (localTrimMargin >= 0)
2698             p.translate(-r.topLeft());
2699 
2700       QList< Element*> pel = page->elements();
2701       std::stable_sort(pel.begin(), pel.end(), elementLessThan);
2702       paintElements(p, pel);
2703        if (format == QImage::Format_Indexed8) {
2704             //convert to grayscale & respect alpha
2705             QVector<QRgb> colorTable;
2706             colorTable.push_back(QColor(0, 0, 0, 0).rgba());
2707             if (!transparent) {
2708                   for (int i = 1; i < 256; i++)
2709                         colorTable.push_back(QColor(i, i, i).rgb());
2710                   }
2711             else {
2712                   for (int i = 1; i < 256; i++)
2713                         colorTable.push_back(QColor(0, 0, 0, i).rgba());
2714                   }
2715             printer = printer.convertToFormat(QImage::Format_Indexed8, colorTable);
2716             }
2717       printer.save(device, "png");
2718       score->setPrinting(false);
2719       MScore::pixelRatio = pr;
2720       return rv;
2721       }
2722 
2723 //---------------------------------------------------------
2724 //   WallpaperPreview
2725 //---------------------------------------------------------
2726 
WallpaperPreview(QWidget * parent)2727 WallpaperPreview::WallpaperPreview(QWidget* parent)
2728    : QFrame(parent)
2729       {
2730       _pixmap = 0;
2731       }
2732 
2733 //---------------------------------------------------------
2734 //   paintEvent
2735 //---------------------------------------------------------
2736 
paintEvent(QPaintEvent * ev)2737 void WallpaperPreview::paintEvent(QPaintEvent* ev)
2738       {
2739       QPainter p(this);
2740       int fw = frameWidth();
2741       QRect r(frameRect().adjusted(fw, fw, -2*fw, -2*fw));
2742       if (_pixmap)
2743             p.drawTiledPixmap(r, *_pixmap);
2744       QFrame::paintEvent(ev);
2745       }
2746 
2747 //---------------------------------------------------------
2748 //   setImage
2749 //---------------------------------------------------------
2750 
setImage(const QString & path)2751 void WallpaperPreview::setImage(const QString& path)
2752       {
2753       qDebug("setImage <%s>", qPrintable(path));
2754       delete _pixmap;
2755       _pixmap = new QPixmap(path);
2756       update();
2757       }
2758 
2759 //---------------------------------------------------------
2760 //   getWallpaper
2761 //---------------------------------------------------------
2762 
getWallpaper(const QString & caption)2763 QString MuseScore::getWallpaper(const QString& caption)
2764       {
2765       QString filter = tr("Images") + " (*.jpg *.jpeg *.png);;" + tr("All") + " (*)";
2766       QString d = mscoreGlobalShare + "/wallpaper";
2767 
2768       if (preferences.getBool(PREF_UI_APP_USENATIVEDIALOGS)) {
2769             QString s = QFileDialog::getOpenFileName(
2770                this,                            // parent
2771                caption,
2772                d,
2773                filter
2774                );
2775             return s;
2776             }
2777 
2778       if (loadBackgroundDialog == 0) {
2779             loadBackgroundDialog = new QFileDialog(this);
2780             loadBackgroundDialog->setFileMode(QFileDialog::ExistingFile);
2781             loadBackgroundDialog->setOption(QFileDialog::DontUseNativeDialog, true);
2782             loadBackgroundDialog->setWindowTitle(caption);
2783             loadBackgroundDialog->setNameFilter(filter);
2784             loadBackgroundDialog->setDirectory(d);
2785 
2786             QSettings set;
2787             loadBackgroundDialog->restoreState(set.value("loadBackgroundDialog").toByteArray());
2788             loadBackgroundDialog->setAcceptMode(QFileDialog::AcceptOpen);
2789 
2790             QSplitter* sp = loadBackgroundDialog->findChild<QSplitter*>("splitter");
2791             if (sp) {
2792                   WallpaperPreview* preview = new WallpaperPreview;
2793                   sp->addWidget(preview);
2794                   connect(loadBackgroundDialog, SIGNAL(currentChanged(const QString&)),
2795                      preview, SLOT(setImage(const QString&)));
2796                   }
2797             }
2798 
2799       //
2800       // setup side bar urls
2801       //
2802       QList<QUrl> urls;
2803       QString home = QDir::homePath();
2804       urls.append(QUrl::fromLocalFile(d));
2805       urls.append(QUrl::fromLocalFile(home));
2806       urls.append(QUrl::fromLocalFile(QDir::currentPath()));
2807       loadBackgroundDialog->setSidebarUrls(urls);
2808 
2809       if (loadBackgroundDialog->exec()) {
2810             QStringList result = loadBackgroundDialog->selectedFiles();
2811             return result.front();
2812             }
2813       return QString();
2814       }
2815 
2816 //---------------------------------------------------------
2817 //   askOverwriteAll
2818 //---------------------------------------------------------
2819 
askOverwriteAll(QString & filename)2820 int MuseScore::askOverwriteAll(QString& filename)
2821 {
2822       QMessageBox msgBox(QMessageBox::Question,
2823                          tr("Confirm Replace"),
2824                          tr("\"%1\" already exists.\nDo you want to replace it?\n").arg(QDir::toNativeSeparators(filename)),
2825                          QMessageBox::Yes | QMessageBox::YesToAll | QMessageBox::No | QMessageBox::NoToAll);
2826       msgBox.setButtonText(QMessageBox::Yes, tr("Replace"));
2827       msgBox.setButtonText(QMessageBox::No,  tr("Skip"));
2828       msgBox.setButtonText(QMessageBox::YesToAll, tr("Replace All"));
2829       msgBox.setButtonText(QMessageBox::NoToAll,  tr("Skip All"));
2830       return msgBox.exec();
2831 }
2832 
2833 //---------------------------------------------------------
2834 //   MuseScore::saveSvg
2835 //---------------------------------------------------------
2836 // [TODO:
2837 // [In most of the functions above the Score* parameter is named "cs".
2838 // [But there is also a member variable in class MuseScoreCore called "cs"
2839 // [and it too is a Score*. If the member variable exists, the functions
2840 // [should not bother to pass it around, especially with multiple names.
2841 // [I have continued to use the "score" argument in this function, but
2842 // [I was just following the existing convention, inconsistent as it is.
2843 // [All the class MuseScore member functions should use the member variable.
2844 // [This file is currently undergoing a bunch of changes, and that's the kind
2845 // [of edit that must be coordinated with the MuseScore master code base.
2846 //
saveSvg(Score * score,const QString & name,const NotesColors & notesColors,SaveReplacePolicy * replacePolicy)2847 bool MuseScore::saveSvg(Score* score, const QString& name, const NotesColors& notesColors, SaveReplacePolicy* replacePolicy)
2848       {
2849       int pages    = score->pages().size();
2850       int padding  = QString("%1").arg(pages).size();
2851       bool success = true;
2852       SaveReplacePolicy _replacePolicy = (replacePolicy != nullptr ? *replacePolicy : SaveReplacePolicy::NO_CHOICE);
2853 
2854       for (int pageNumber = 0; pageNumber < pages; ++pageNumber) {
2855             QString fileName(name);
2856             if (fileName.endsWith(".svg"))
2857                   fileName = fileName.left(fileName.size() - 4);
2858             fileName += QString("-%1.svg").arg(pageNumber+1, padding, 10, QLatin1Char('0'));
2859             if (!converterMode && QFileInfo(fileName).exists()) {
2860                   switch (_replacePolicy) {
2861                         case SaveReplacePolicy::NO_CHOICE:
2862                               {
2863                               int responseCode = mscore->askOverwriteAll(fileName);
2864                               if (responseCode == QMessageBox::YesToAll) {
2865                                     _replacePolicy = SaveReplacePolicy::REPLACE_ALL;
2866                                     break; // Break out of the switch; go on and replace the existing file
2867                                     }
2868                               else if (responseCode == QMessageBox::NoToAll) {
2869                                     _replacePolicy = SaveReplacePolicy::SKIP_ALL;
2870                                     continue; // Continue in the `for` loop
2871                                     }
2872                               else if (responseCode == QMessageBox::No)
2873                                     continue;
2874                               break;
2875                               }
2876                         case SaveReplacePolicy::SKIP_ALL:
2877                               continue;
2878                         case SaveReplacePolicy::REPLACE_ALL:
2879                               break;
2880                         }
2881                   }
2882             QFile f(fileName);
2883             if (!f.open(QIODevice::WriteOnly) || !saveSvg(score, &f, pageNumber, /*drawPageBackground*/ false, notesColors)) {
2884                   success = false;
2885                   break;
2886                   }
2887             }
2888       if (replacePolicy != nullptr)
2889             *replacePolicy = _replacePolicy;
2890       return success;
2891       }
2892 
2893 //---------------------------------------------------------
2894 //   MuseScore::readNotesColors
2895 ///  Read notes colors from json file
2896 //---------------------------------------------------------
2897 
readNotesColors(const QString & filePath) const2898 NotesColors MuseScore::readNotesColors(const QString& filePath) const
2899 {
2900     if (filePath.isEmpty()) {
2901         return NotesColors();
2902     }
2903 
2904     QFile file;
2905     file.setFileName(filePath);
2906     file.open(QIODevice::ReadOnly | QIODevice::Text);
2907     QString content = file.readAll();
2908     file.close();
2909 
2910     QJsonDocument document = QJsonDocument::fromJson(content.toUtf8());
2911     QJsonObject obj = document.object();
2912     QJsonArray colors = obj.value("highlight").toArray();
2913 
2914     NotesColors result;
2915 
2916     for (const QJsonValue colorObj: colors) {
2917         QJsonObject cobj = colorObj.toObject();
2918         QJsonArray notesIndexes = cobj.value("notes").toArray();
2919         QColor notesColor = QColor(cobj.value("color").toString());
2920 
2921         for (const QJsonValue index: notesIndexes) {
2922             result.insert(index.toInt(), notesColor);
2923         }
2924     }
2925 
2926     return result;
2927 }
2928 
2929 //---------------------------------------------------------
2930 //   MuseScore::saveSvg
2931 ///  Save a single page
2932 //---------------------------------------------------------
2933 
saveSvg(Score * score,QIODevice * device,int pageNumber,bool drawPageBackground,const NotesColors & notesColors)2934 bool MuseScore::saveSvg(Score* score, QIODevice* device, int pageNumber, bool drawPageBackground, const NotesColors& notesColors)
2935       {
2936       QString title(score->title());
2937       score->setPrinting(true);
2938       MScore::pdfPrinting = true;
2939       MScore::svgPrinting = true;
2940       const QList<Page*>& pl = score->pages();
2941       int pages = pl.size();
2942       double pr = MScore::pixelRatio;
2943 
2944       Page* page = pl.at(pageNumber);
2945       SvgGenerator printer;
2946       printer.setTitle(pages > 1 ? QString("%1 (%2)").arg(title).arg(pageNumber + 1) : title);
2947       printer.setOutputDevice(device);
2948 
2949       QRectF r;
2950       if (trimMargin >= 0) {
2951             QMarginsF margins(trimMargin, trimMargin, trimMargin, trimMargin);
2952             r = page->tbbox() + margins;
2953             }
2954       else
2955             r = page->abbox();
2956       qreal w = r.width();
2957       qreal h = r.height();
2958       printer.setSize(QSize(w, h));
2959       printer.setViewBox(QRectF(0, 0, w, h));
2960       QPainter p(&printer);
2961       p.setRenderHint(QPainter::Antialiasing, true);
2962       p.setRenderHint(QPainter::TextAntialiasing, true);
2963       if (trimMargin >= 0 && score->npages() == 1)
2964             p.translate(-r.topLeft());
2965       MScore::pixelRatio = DPI / printer.logicalDpiX();
2966 
2967       if (drawPageBackground)
2968             p.fillRect(r, Qt::white);
2969 
2970       // 1st pass: StaffLines
2971       for  (System* s : page->systems()) {
2972             for (int i = 0, n = s->staves()->size(); i < n; i++) {
2973                   if (score->staff(i)->invisible(Fraction(0,1)) || !score->staff(i)->show())
2974                         continue;  // ignore invisible staves
2975                   if (s->staves()->isEmpty() || !s->staff(i)->show())
2976                         continue;
2977                   Measure* fm = s->firstMeasure();
2978                   if (!fm) // only boxes, hence no staff lines
2979                         continue;
2980 
2981                   // The goal here is to draw SVG staff lines more efficiently.
2982                   // MuseScore draws staff lines by measure, but for SVG they can
2983                   // generally be drawn once for each system. This makes a big
2984                   // difference for scores that scroll horizontally on a single
2985                   // page. But there are exceptions to this rule:
2986                   //
2987                   //   ~ One (or more) invisible measure(s) in a system/staff ~
2988                   //   ~ One (or more) elements of type HBOX or VBOX          ~
2989                   //
2990                   // In these cases the SVG staff lines for the system/staff
2991                   // are drawn by measure.
2992                   //
2993                   bool byMeasure = false;
2994                   for (MeasureBase* mb = fm; mb; mb = s->nextMeasure(mb)) {
2995                         if (!mb->isMeasure() || !toMeasure(mb)->visible(i)) {
2996                               byMeasure = true;
2997                               break;
2998                               }
2999                         }
3000                   if (byMeasure) { // Draw visible staff lines by measure
3001                         for (MeasureBase* mb = fm; mb; mb = s->nextMeasure(mb)) {
3002                               if (mb->isMeasure() && toMeasure(mb)->visible(i)) {
3003                                     StaffLines* sl = toMeasure(mb)->staffLines(i);
3004                                     printer.setElement(sl);
3005                                     paintElement(p, sl);
3006                                     }
3007                               }
3008                         }
3009                   else { // Draw staff lines once per system
3010                         StaffLines* firstSL = s->firstMeasure()->staffLines(i)->clone();
3011                         StaffLines*  lastSL =  s->lastMeasure()->staffLines(i);
3012 
3013                         qreal lastX =  lastSL->bbox().right()
3014                                     +  lastSL->pagePos().x()
3015                                     - firstSL->pagePos().x();
3016                         QVector<QLineF>& lines = firstSL->getLines();
3017                         for (int l = 0, c = lines.size(); l < c; l++)
3018                               lines[l].setP2(QPointF(lastX, lines[l].p2().y()));
3019 
3020                         printer.setElement(firstSL);
3021                         paintElement(p, firstSL);
3022                         }
3023                   }
3024             }
3025       // 2nd pass: the rest of the elements
3026       QList<Element*> pel = page->elements();
3027       std::stable_sort(pel.begin(), pel.end(), elementLessThan);
3028       ElementType eType;
3029 
3030       int lastNoteIndex = -1;
3031       for (int i = 0; i < pageNumber; ++i) {
3032           for (const Element* element: score->pages()[i]->elements()) {
3033               if (element->type() == ElementType::NOTE) {
3034                   lastNoteIndex++;
3035               }
3036           }
3037       }
3038 
3039       for (const Element* e : pel) {
3040             // Always exclude invisible elements
3041             if (!e->visible())
3042                   continue;
3043 
3044             eType = e->type();
3045             switch (eType) { // In future sub-type code, this switch() grows, and eType gets used
3046             case ElementType::STAFF_LINES : // Handled in the 1st pass above
3047                   continue; // Exclude from 2nd pass
3048                   break;
3049             default:
3050                   break;
3051             } // switch(eType)
3052 
3053             // Set the Element pointer inside SvgGenerator/SvgPaintEngine
3054             printer.setElement(e);
3055 
3056             // Paint it
3057             if (e->type() == ElementType::NOTE && !notesColors.isEmpty()) {
3058                 QColor color = e->color();
3059                 int currentNoteIndex = (++lastNoteIndex);
3060 
3061                 if (notesColors.contains(currentNoteIndex)) {
3062                     color = notesColors[currentNoteIndex];
3063                 }
3064 
3065                 Element *note = dynamic_cast<const Note*>(e)->clone();
3066                 note->setColor(color);
3067                 paintElement(p, note);
3068                 delete note;
3069             } else {
3070                 paintElement(p, e);
3071             }
3072             }
3073       p.end(); // Writes MuseScore SVG file to disk, finally
3074 
3075       // Clean up and return
3076       MScore::pixelRatio = pr;
3077       score->setPrinting(false);
3078       MScore::pdfPrinting = false;
3079       MScore::svgPrinting = false;
3080       return true;
3081       }
3082 
3083 //---------------------------------------------------------
3084 //   createThumbnail
3085 //---------------------------------------------------------
3086 
createThumbnail(const QString & name)3087 static QPixmap createThumbnail(const QString& name)
3088       {
3089       if (!(name.endsWith(".mscx") || name.endsWith(".mscz")))
3090             return QPixmap();
3091       MasterScore* score = new MasterScore(MScore::defaultStyle());
3092       Score::FileError error = readScore(score, name, true);
3093       if (error != Score::FileError::FILE_NO_ERROR || !score->firstMeasure()) {
3094             delete score;
3095             return QPixmap();
3096             }
3097       score->doLayout();
3098       QImage pm = score->createThumbnail();
3099       delete score;
3100       return QPixmap::fromImage(pm);
3101       }
3102 
3103 //---------------------------------------------------------
3104 //   extractThumbnail
3105 //---------------------------------------------------------
3106 
extractThumbnail(const QString & name)3107 QPixmap MuseScore::extractThumbnail(const QString& name)
3108       {
3109       QPixmap pm; //  = icons[File_ICON].pixmap(QSize(100,140));
3110       if (!name.endsWith(".mscz"))
3111             return createThumbnail(name);
3112       MQZipReader uz(name);
3113       if (!uz.exists()) {
3114             qDebug("extractThumbnail: <%s> not found", qPrintable(name));
3115             return pm;
3116             }
3117       QByteArray ba = uz.fileData("Thumbnails/thumbnail.png");
3118       if (ba.isEmpty())
3119             return createThumbnail(name);
3120       pm.loadFromData(ba, "PNG");
3121       return pm;
3122       }
3123 
3124 //---------------------------------------------------------
3125 //   saveMetadataJSON
3126 //---------------------------------------------------------
3127 
saveMetadataJSON(Score * score,const QString & name)3128 bool MuseScore::saveMetadataJSON(Score* score, const QString& name)
3129       {
3130       QFile f(name);
3131       if (!f.open(QIODevice::WriteOnly))
3132             return false;
3133 
3134       QJsonObject json = saveMetadataJSON(score);
3135       QJsonDocument saveDoc(json);
3136       f.write(saveDoc.toJson());
3137       f.close();
3138       return true;
3139       }
3140 
3141 //---------------------------------------------------------
3142 //   findTextByType
3143 //    @data must contain std::pair<Tid, QStringList*>*
3144 //          Tid specifies text style
3145 //          QStringList* specifies the container to keep found text
3146 //
3147 //    For usage with Score::scanElements().
3148 //    Finds all text elements with specified style.
3149 //---------------------------------------------------------
findTextByType(void * data,Element * element)3150 static void findTextByType(void* data, Element* element)
3151       {
3152       if (!element->isTextBase())
3153             return;
3154       TextBase* text = toTextBase(element);
3155       auto* typeStringsData = static_cast<std::pair<Tid, QStringList*>*>(data);
3156       if (text->tid() == typeStringsData->first) {
3157             // or if score->getTextStyleUserName().contains("Title") ???
3158             // That is bad since it may be localized
3159             QStringList* titleStrings = typeStringsData->second;
3160             Q_ASSERT(titleStrings);
3161             titleStrings->append(text->plainText());
3162             }
3163       }
3164 
saveMetadataJSON(Score * score)3165 QJsonObject MuseScore::saveMetadataJSON(Score* score)
3166       {
3167       auto boolToString = [](bool b) { return b ? "true" : "false"; };
3168       QJsonObject json;
3169 
3170       // title
3171       QString title;
3172       Text* t = score->getText(Tid::TITLE);
3173       if (t)
3174             title = t->plainText();
3175       if (title.isEmpty())
3176             title = score->metaTag("workTitle");
3177       if (title.isEmpty())
3178             title = score->title();
3179       json.insert("title", title);
3180 
3181       // subtitle
3182       QString subtitle;
3183       t = score->getText(Tid::SUBTITLE);
3184       if (t)
3185             subtitle = t->plainText();
3186       json.insert("subtitle", subtitle);
3187 
3188       // composer
3189       QString composer;
3190       t = score->getText(Tid::COMPOSER);
3191       if (t)
3192             composer = t->plainText();
3193       if (composer.isEmpty())
3194             composer = score->metaTag("composer");
3195       json.insert("composer", composer);
3196 
3197       // poet
3198       QString poet;
3199       t = score->getText(Tid::POET);
3200       if (t)
3201             poet = t->plainText();
3202       if (poet.isEmpty())
3203             poet = score->metaTag("lyricist");
3204       json.insert("poet", poet);
3205 
3206       json.insert("mscoreVersion", score->mscoreVersion());
3207       json.insert("fileVersion", score->mscVersion());
3208 
3209       json.insert("pages", score->npages());
3210       json.insert("measures", score->nmeasures());
3211       json.insert("hasLyrics", boolToString(score->hasLyrics()));
3212       json.insert("hasHarmonies", boolToString(score->hasHarmonies()));
3213       json.insert("keysig", score->keysig());
3214       json.insert("previousSource", score->metaTag("source"));
3215 
3216       // timeSig
3217       QString timeSig;
3218       int staves = score->nstaves();
3219       int tracks = staves * VOICES;
3220       Segment* tss = score->firstSegmentMM(SegmentType::TimeSig);
3221       if (tss) {
3222             Element* e = nullptr;
3223             for (int track = 0; track < tracks; ++track) {
3224                   e = tss->element(track);
3225                   if (e) break;
3226                   }
3227             if (e && e->isTimeSig()) {
3228                   TimeSig* ts = toTimeSig(e);
3229                   timeSig = QString("%1/%2").arg(ts->numerator()).arg(ts->denominator());
3230                   }
3231             }
3232       json.insert("timesig", timeSig);
3233 
3234       json.insert("duration", score->duration());
3235       json.insert("lyrics", score->extractLyrics());
3236 
3237       // tempo
3238        int tempo = 0;
3239        QString tempoText;
3240        for (Segment* seg = score->firstSegmentMM(SegmentType::All); seg; seg = seg->next1MM()) {
3241              auto annotations = seg->annotations();
3242              for (Element* a : annotations) {
3243                    if (a && a->isTempoText()) {
3244                          TempoText* tt = toTempoText(a);
3245                          tempo = round(tt->tempo() * 60);
3246                          tempoText = tt->xmlText();
3247                          }
3248                    }
3249              }
3250       json.insert("tempo", tempo);
3251       json.insert("tempoText", tempoText);
3252 
3253       // parts
3254       QJsonArray jsonPartsArray;
3255       for (Part* p : score->parts()) {
3256             QJsonObject jsonPart;
3257             jsonPart.insert("name", p->longName().replace("\n", ""));
3258             int midiProgram = p->midiProgram();
3259             jsonPart.insert("program", midiProgram);
3260             jsonPart.insert("instrumentId", p->instrumentId());
3261             jsonPart.insert("lyricCount", p->lyricCount());
3262             jsonPart.insert("harmonyCount", p->harmonyCount());
3263             jsonPart.insert("hasPitchedStaff", boolToString(p->hasPitchedStaff()));
3264             jsonPart.insert("hasTabStaff", boolToString(p->hasTabStaff()));
3265             jsonPart.insert("hasDrumStaff", boolToString(p->hasDrumStaff()));
3266             jsonPart.insert("isVisible", boolToString(p->show()));
3267             jsonPartsArray.append(jsonPart);
3268             }
3269       json.insert("parts", jsonPartsArray);
3270 
3271       // pageFormat
3272       QJsonObject jsonPageformat;
3273       jsonPageformat.insert("height",round(score->styleD(Sid::pageHeight) * INCH));
3274       jsonPageformat.insert("width", round(score->styleD(Sid::pageWidth) * INCH));
3275       jsonPageformat.insert("twosided", boolToString(score->styleB(Sid::pageTwosided)));
3276       json.insert("pageFormat", jsonPageformat);
3277 
3278       //text frames metadata
3279       QJsonObject jsonTypeData;
3280       static std::vector<std::pair<QString, Tid>> namesTypesList {
3281             {"titles", Tid::TITLE},
3282             {"subtitles", Tid::SUBTITLE},
3283             {"composers", Tid::COMPOSER},
3284             {"poets", Tid::POET}
3285             };
3286       for (auto nameType : namesTypesList) {
3287             QJsonArray typeData;
3288             QStringList typeTextStrings;
3289             std::pair<Tid, QStringList*> extendedTitleData = std::make_pair(nameType.second, &typeTextStrings);
3290             score->scanElements(&extendedTitleData, findTextByType);
3291             for (auto typeStr : typeTextStrings)
3292                   typeData.append(typeStr);
3293             jsonTypeData.insert(nameType.first, typeData);
3294             }
3295       json.insert("textFramesData", jsonTypeData);
3296 
3297       return json;
3298       }
3299 
3300 class CustomJsonWriter
3301 {
3302 public:
CustomJsonWriter(const QString & filePath)3303       CustomJsonWriter(const QString& filePath)
3304       {
3305       jsonFormatFile.setFileName(filePath);
3306       jsonFormatFile.open(QIODevice::WriteOnly);
3307       jsonFormatFile.write("{\n");
3308       }
3309 
~CustomJsonWriter()3310       ~CustomJsonWriter()
3311       {
3312       jsonFormatFile.write("\n}\n");
3313       jsonFormatFile.close();
3314       }
3315 
addKey(const char * arrayName)3316       void addKey(const char* arrayName)
3317       {
3318       jsonFormatFile.write("\"");
3319       jsonFormatFile.write(arrayName);
3320       jsonFormatFile.write("\": ");
3321       }
3322 
addValue(const QByteArray & data,bool lastJsonElement=false,bool isJson=false)3323       void addValue(const QByteArray& data, bool lastJsonElement = false, bool isJson = false)
3324       {
3325       if (!isJson)
3326             jsonFormatFile.write("\"");
3327       jsonFormatFile.write(data);
3328       if (!isJson)
3329             jsonFormatFile.write("\"");
3330       if (!lastJsonElement)
3331             jsonFormatFile.write(",\n");
3332       }
3333 
openArray()3334       void openArray()
3335       {
3336       jsonFormatFile.write(" [");
3337       }
3338 
closeArray(bool lastJsonElement=false)3339       void closeArray(bool lastJsonElement = false)
3340       {
3341       jsonFormatFile.write("]");
3342       if (!lastJsonElement)
3343             jsonFormatFile.write(",");
3344       jsonFormatFile.write("\n");
3345       }
3346 
3347 private:
3348       QFile jsonFormatFile;
3349 };
3350 
3351 //---------------------------------------------------------
3352 //   exportMp3AsJSON
3353 //---------------------------------------------------------
3354 
exportMp3AsJSON(const QString & inFilePath,const QString & outFilePath)3355 bool MuseScore::exportMp3AsJSON(const QString& inFilePath, const QString& outFilePath)
3356       {
3357       std::unique_ptr<MasterScore> score(mscore->readScore(inFilePath));
3358       if (!score)
3359             return false;
3360 
3361       CustomJsonWriter jsonWriter(outFilePath);
3362       jsonWriter.addKey("mp3");
3363       //export score audio
3364       QByteArray mp3Data;
3365       QBuffer mp3Device(&mp3Data);
3366       mp3Device.open(QIODevice::ReadWrite);
3367       bool dummy = false;
3368       mscore->saveMp3(score.get(), &mp3Device, dummy);
3369       jsonWriter.addValue(mp3Data.toBase64(), true);
3370       return true;
3371       }
3372 
exportPdfAsJSON(Score * score)3373 QByteArray MuseScore::exportPdfAsJSON(Score* score)
3374       {
3375       QTemporaryFile tempPdfFile;
3376       bool ok = tempPdfFile.open();
3377 
3378       if (!ok) {
3379           return QByteArray();
3380       }
3381 
3382       QPrinter printer;
3383       printer.setOutputFileName(tempPdfFile.fileName());
3384       mscore->savePdf(score, printer);
3385 
3386       QByteArray pdfData = tempPdfFile.readAll();
3387       tempPdfFile.close();
3388 
3389       return pdfData.toBase64();
3390       }
3391 
3392 //---------------------------------------------------------
3393 //   parseSourceUrl
3394 //---------------------------------------------------------
3395 
parseSourceUrl(const QString & sourceUrl,int & uid,int & nid)3396 static void parseSourceUrl(const QString& sourceUrl, int& uid, int& nid)
3397       {
3398       if (!sourceUrl.isEmpty()) {
3399             QStringList sl = sourceUrl.split("/");
3400             if (sl.length() >= 1) {
3401                   nid = sl.last().toInt();
3402                   if (sl.length() >= 3) {
3403                         uid = sl.at(sl.length() - 3).toInt();
3404                         }
3405                   }
3406             }
3407       }
3408 
3409 //---------------------------------------------------------
3410 //   saveOnline
3411 //---------------------------------------------------------
3412 
saveOnline(const QStringList & inFilePaths)3413 bool MuseScore::saveOnline(const QStringList& inFilePaths)
3414       {
3415       if (MuseScore::unstable()) {
3416             qCritical() << qUtf8Printable(tr("Error: Saving scores online is disabled in this unstable prerelease version of MuseScore."));
3417             return false;
3418       }
3419       if (!_loginManager->syncGetUser()) {
3420             return false;
3421             }
3422 
3423       QTemporaryDir tempDir;
3424       if (!tempDir.isValid()) {
3425             qCritical() << qUtf8Printable(tr("Error: %1").arg(tempDir.errorString()));
3426             return false;
3427             }
3428       QString tempPath = tempDir.path() + "/score.mscz";
3429 
3430       bool all_successful = true;
3431 
3432       for (auto path : inFilePaths) {
3433             Score* score = mscore->readScore(path);
3434             if (!score) {
3435                   all_successful = false;
3436                   continue;
3437                   }
3438 
3439             int uid = 0;
3440             int nid = 0;
3441             parseSourceUrl(score->metaTag("source"), uid, nid);
3442 
3443             if (nid <= 0) {
3444                   qCritical() << qUtf8Printable(tr("Error: '%1' tag missing or malformed in %2").arg("source").arg(path));
3445                   all_successful = false;
3446                   continue;
3447                   }
3448 
3449             if (uid && uid != _loginManager->uid()) {
3450                   qCritical() << qUtf8Printable(tr("Error: You are not the owner of the online score for %1").arg(path));
3451                   all_successful = false;
3452                   continue;
3453                   }
3454 
3455             if (!_loginManager->syncGetScoreInfo(nid)) {
3456                   all_successful = false;
3457                   continue;
3458                   }
3459             QString title = _loginManager->scoreTitle();
3460 
3461             if (!mscore->saveAs(score, true, tempPath, "mscz")) {
3462                   all_successful = false;
3463                   continue;
3464                   }
3465 
3466             if (!_loginManager->syncUpload(tempPath, nid, title)) { // keep same title
3467                   all_successful = false;
3468                   continue;
3469                   }
3470 
3471             qInfo() << qUtf8Printable(tr("Uploaded score")) << path;
3472             }
3473 
3474       return all_successful;
3475       }
3476 
3477 //---------------------------------------------------------
3478 //   exportAllMediaFiles
3479 //---------------------------------------------------------
3480 
exportAllMediaFiles(const QString & inFilePath,const QString & highlightConfigPath,const QString & outFilePath)3481 bool MuseScore::exportAllMediaFiles(const QString& inFilePath, const QString& highlightConfigPath, const QString& outFilePath)
3482       {
3483       std::unique_ptr<MasterScore> score(mscore->readScore(inFilePath));
3484       if (!score)
3485             return false;
3486 
3487       score->switchToPageMode();
3488 
3489       //// JSON specification ///////////////////////////
3490       //jsonForMedia["pngs"] = pngsJsonArray;
3491       //jsonForMedia["mposXML"] = mposJson;
3492       //jsonForMedia["sposXML"] = sposJson;
3493       //jsonForMedia["pdf"] = pdfJson;
3494       //jsonForMedia["svgs"] = svgsJsonArray;
3495       //jsonForMedia["midi"] = midiJson;
3496       //jsonForMedia["mxml"] = mxmlJson;
3497       //jsonForMedia["metadata"] = mdJson;
3498       ///////////////////////////////////////////////////
3499 
3500       bool res = true;
3501       CustomJsonWriter jsonWriter(outFilePath);
3502       //export score pngs and svgs
3503       jsonWriter.addKey("pngs");
3504       jsonWriter.openArray();
3505       for (int i = 0; i < score->pages().size(); ++i) {
3506             QByteArray pngData;
3507             QBuffer pngDevice(&pngData);
3508             pngDevice.open(QIODevice::ReadWrite);
3509             res &= mscore->savePng(score.get(), &pngDevice, i, /* drawPageBackground */ true);
3510             bool lastArrayValue = ((score->pages().size() - 1) == i);
3511             jsonWriter.addValue(pngData.toBase64(), lastArrayValue);
3512             }
3513       jsonWriter.closeArray();
3514 
3515       jsonWriter.addKey("svgs");
3516       jsonWriter.openArray();
3517       for (int i = 0; i < score->pages().size(); ++i) {
3518             QByteArray svgData;
3519             QBuffer svgDevice(&svgData);
3520             svgDevice.open(QIODevice::ReadWrite);
3521 
3522             NotesColors notesColors = readNotesColors(highlightConfigPath);
3523             res &= mscore->saveSvg(score.get(), &svgDevice, i, /* drawPageBackground */ true, notesColors);
3524 
3525             bool lastArrayValue = ((score->pages().size() - 1) == i);
3526             jsonWriter.addValue(svgData.toBase64(), lastArrayValue);
3527             }
3528       jsonWriter.closeArray();
3529 
3530       {
3531       //export score .spos
3532       QByteArray partDataPos;
3533       QBuffer partPosDevice(&partDataPos);
3534       partPosDevice.open(QIODevice::ReadWrite);
3535       savePositions(score.get(), &partPosDevice, true);
3536       jsonWriter.addKey("sposXML");
3537       jsonWriter.addValue(partDataPos.toBase64());
3538       partPosDevice.close();
3539       partDataPos.clear();
3540 
3541       //export score .mpos
3542       partPosDevice.open(QIODevice::ReadWrite);
3543       savePositions(score.get(), &partPosDevice, false);
3544       jsonWriter.addKey("mposXML");
3545       jsonWriter.addValue(partDataPos.toBase64());
3546       }
3547 
3548       //export score pdf
3549       jsonWriter.addKey("pdf");
3550       jsonWriter.addValue(exportPdfAsJSON(score.get()));
3551 
3552       {
3553       //export score midi
3554       QByteArray midiData;
3555       QBuffer midiDevice(&midiData);
3556       midiDevice.open(QIODevice::ReadWrite);
3557       res &= mscore->saveMidi(score.get(), &midiDevice);
3558       jsonWriter.addKey("midi");
3559       jsonWriter.addValue(midiData.toBase64());
3560       }
3561 
3562       {
3563       //export musicxml
3564       QByteArray mxmlData;
3565       QBuffer mxmlDevice(&mxmlData);
3566       mxmlDevice.open(QIODevice::ReadWrite);
3567       res &= saveMxl(score.get(), &mxmlDevice);
3568       jsonWriter.addKey("mxml");
3569       jsonWriter.addValue(mxmlData.toBase64());
3570       }
3571 
3572       //export metadata
3573       QJsonDocument doc(mscore->saveMetadataJSON(score.get()));
3574       jsonWriter.addKey("metadata");
3575       jsonWriter.addValue(doc.toJson(QJsonDocument::Compact), true, true);
3576 
3577       return res;
3578       }
3579 
3580 //---------------------------------------------------------
3581 //   exportScoreMetadata
3582 //---------------------------------------------------------
3583 
exportScoreMetadata(const QString & inFilePath,const QString & outFilePath)3584 bool MuseScore::exportScoreMetadata(const QString& inFilePath, const QString& outFilePath)
3585       {
3586       std::unique_ptr<MasterScore> score(mscore->readScore(inFilePath));
3587       if (!score)
3588             return false;
3589 
3590       score->switchToPageMode();
3591 
3592       //// JSON specification ///////////////////////////
3593       //jsonForMedia["metadata"] = mdJson;
3594       ///////////////////////////////////////////////////
3595 
3596       CustomJsonWriter jsonWriter(outFilePath);
3597 
3598       //export metadata
3599       QJsonDocument doc(mscore->saveMetadataJSON(score.get()));
3600       jsonWriter.addKey("metadata");
3601       jsonWriter.addValue(doc.toJson(QJsonDocument::Compact), true, true);
3602 
3603       return true;
3604       }
3605 
3606 //---------------------------------------------------------
3607 //   exportTransposedScoreToJSON
3608 //---------------------------------------------------------
3609 
exportTransposedScoreToJSON(const QString & inFilePath,const QString & transposeOptions,const QString & outFilePath)3610 bool MuseScore::exportTransposedScoreToJSON(const QString& inFilePath, const QString& transposeOptions, const QString& outFilePath)
3611       {
3612       QJsonDocument doc = QJsonDocument::fromJson(transposeOptions.toUtf8());
3613       if (!doc.isObject()) {
3614             qCritical("Transpose options JSON is not an object: %s", qUtf8Printable(transposeOptions));
3615             return false;
3616             }
3617 
3618       QJsonObject options = doc.object();
3619 
3620       TransposeMode mode;
3621       const QString modeName = options["mode"].toString();
3622       if (modeName == "by_key" || modeName == "to_key") // "by_key" for backwards compatibility
3623             mode = TransposeMode::TO_KEY;
3624       else if (modeName == "by_interval")
3625             mode = TransposeMode::BY_INTERVAL;
3626       else if (modeName == "diatonically")
3627             mode = TransposeMode::DIATONICALLY;
3628       else {
3629             qCritical("Transpose: invalid \"mode\" option: %s", qUtf8Printable(modeName));
3630             return false;
3631             }
3632 
3633       TransposeDirection direction;
3634       const QString directionName = options["direction"].toString();
3635       if (directionName == "up")
3636             direction = TransposeDirection::UP;
3637       else if (directionName == "down")
3638             direction = TransposeDirection::DOWN;
3639       else if (directionName == "closest")
3640             direction = TransposeDirection::CLOSEST;
3641       else {
3642             qCritical("Transpose: invalid \"direction\" option: %s", qUtf8Printable(directionName));
3643             return false;
3644             }
3645 
3646       constexpr int defaultKey = int(Key::INVALID);
3647       const Key targetKey = Key(options["targetKey"].toInt(defaultKey));
3648       if (mode == TransposeMode::TO_KEY) {
3649             const bool targetKeyValid = int(Key::MIN) <= int(targetKey) && int(targetKey) <= int(Key::MAX);
3650             if (!targetKeyValid) {
3651                   qCritical("Transpose: invalid targetKey: %d", int(targetKey));
3652                   return false;
3653                   }
3654             }
3655 
3656       const int transposeInterval = options["transposeInterval"].toInt(-1);
3657       if (mode != TransposeMode::TO_KEY) {
3658             const bool transposeIntervalValid = -1 < transposeInterval && transposeInterval < intervalListSize;
3659             if (!transposeIntervalValid) {
3660                   qCritical("Transpose: invalid transposeInterval: %d", transposeInterval);
3661                   return false;
3662                   }
3663             }
3664 
3665       const bool transposeKeySignatures = options["transposeKeySignatures"].toBool();
3666       const bool transposeChordNames = options["transposeChordNames"].toBool();
3667       const bool useDoubleSharpsFlats = options["useDoubleSharpsFlats"].toBool();
3668 
3669       std::unique_ptr<MasterScore> score(mscore->readScore(inFilePath));
3670       if (!score)
3671             return false;
3672 
3673       score->switchToPageMode();
3674       score->cmdSelectAll();
3675 
3676       score->startCmd();
3677       const bool transposed = score->transpose(mode, direction, targetKey, transposeInterval, transposeKeySignatures, transposeChordNames, useDoubleSharpsFlats);
3678       if (!transposed) {
3679             qCritical("Transposition failed");
3680             return false;
3681             }
3682       score->endCmd();
3683 
3684       bool res = true;
3685       CustomJsonWriter jsonWriter(outFilePath);
3686 
3687       // export mscz
3688       {
3689       jsonWriter.addKey("mscz");
3690       bool saved = false;
3691       QTemporaryFile tmpFile(QString("%1_transposed.XXXXXX.mscz").arg(score->title()));
3692       if (tmpFile.open()) {
3693             QString fileName = QFileInfo(tmpFile.fileName()).completeBaseName() + ".mscx";
3694             saved = score->Score::saveCompressedFile(&tmpFile, fileName, /* onlySelection */ false);
3695             tmpFile.close();
3696             tmpFile.open();
3697             jsonWriter.addValue(tmpFile.readAll().toBase64());
3698             tmpFile.close();
3699             }
3700 
3701       if (!saved) {
3702             qCritical("Transpose: adding mscz failed");
3703             jsonWriter.addValue("");
3704             res = false;
3705             }
3706       }
3707 
3708       // export score pdf
3709       jsonWriter.addKey("pdf");
3710       jsonWriter.addValue(exportPdfAsJSON(score.get()), /* lastJsonElement */ true);
3711 
3712       return res;
3713       }
3714 
updateSource(const QString & scorePath,const QString & newSource)3715 bool MuseScore::updateSource(const QString& scorePath, const QString& newSource)
3716 {
3717     MasterScore* score = mscore->readScore(scorePath);
3718     if (!score) {
3719         return false;
3720     }
3721 
3722     score->setMetaTag("source", newSource);
3723 
3724     return score->saveFile(false);
3725 }
3726 }
3727