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"); // ä
147 fn = fn.replace(QChar(0xf6), "oe"); // ö
148 fn = fn.replace(QChar(0xfc), "ue"); // ü
149 fn = fn.replace(QChar(0xdf), "ss"); // ß
150 fn = fn.replace(QChar(0xc4), "Ae"); // Ä
151 fn = fn.replace(QChar(0xd6), "Oe"); // Ö
152 fn = fn.replace(QChar(0xdc), "Ue"); // Ü
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