1 //=============================================================================
2 // MuseScore
3 // Linux Music Score Editor
4 //
5 // Copyright (C) 2020 MuseScore BVBA and others
6 //
7 // This program is free software; you can redistribute it and/or modify
8 // it under the terms of the GNU General Public License version 2.
9 //
10 // This program is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 // GNU General Public License for more details.
14 //
15 // You should have received a copy of the GNU General Public License
16 // along with this program; if not, write to the Free Software
17 // Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18 //=============================================================================
19
20 #include "exportdialog.h"
21 #include "musescore.h"
22 #include "preferences.h"
23
24 // Supported export formats
25 // ------------------------
26 // - PDF
27 // - PNG Bitmap Graphic (*.png)
28 // - Scalable Vector Graphics (*.svg)
29 // - MP3 Audio (*.mp3)
30 // - Wave Audio (*.wav)
31 // - FLAC Audio (*.flac)
32 // - Ogg Vorbis Audio (*.ogg)
33 // - MIDI
34 // - MusicXML:
35 // - Compressed (*.mxl)
36 // - Uncompressed (*.musicxml)
37 // - Uncompressed (outdated) (*.xml)
38 // - Uncompressed MuseScore File (*.mscx)
39
40 namespace Ms {
41
42 //---------------------------------------------------------
43 // ExportScoreItem
44 //---------------------------------------------------------
45
ExportScoreItem(Score * s,QListWidget * parent)46 ExportScoreItem::ExportScoreItem(Score* s, QListWidget* parent)
47 : QListWidgetItem(parent)
48 {
49 _score = s;
50 setText(s->isMaster() ? ExportDialog::tr("Full Score") : s->title());
51 }
52
53 //---------------------------------------------------------
54 // ExportDialog
55 //---------------------------------------------------------
56
ExportDialog(Score * s,QWidget * parent)57 ExportDialog::ExportDialog(Score* s, QWidget* parent)
58 : AbstractDialog(parent), cs(s)
59 {
60 setObjectName("ExportDialog");
61 setupUi(this);
62 setWindowFlags(this->windowFlags() & ~Qt::WindowContextHelpButtonHint);
63
64 connect(listWidget, &QListWidget::itemChanged, this, &ExportDialog::setOkButtonEnabled);
65 connect(fileTypeComboBox, SIGNAL(currentIndexChanged(int)), SLOT(fileTypeChosen(int)));
66
67 pdfSeparateOrSingleFiles = new QButtonGroup(this);
68 pdfSeparateOrSingleFiles->addButton(pdfSeparateFilesRadioButton, 0);
69 pdfSeparateOrSingleFiles->addButton(pdfOneFileRadioButton, 1);
70
71 #if !defined(HAS_AUDIOFILE) || !defined(USE_LAME)
72 // Disable audio options that are unavailable
73 // Source: https://stackoverflow.com/a/38915478
74 QStandardItemModel* fileTypeComboBoxModel = qobject_cast<QStandardItemModel*>(fileTypeComboBox->model());
75 Q_ASSERT(fileTypeComboBoxModel != nullptr);
76 # ifndef USE_LAME
77 // Disable .mp3 option if unavailable
78 QStandardItem* mp3Item = fileTypeComboBoxModel->item(3);
79 mp3Item->setFlags(audioItem->flags() & ~Qt::ItemIsEnabled);
80 # endif
81 # ifndef HAS_AUDIOFILE
82 // Disable .wav, .flac and .ogg options if unavailable
83 for (int i = 4; i < 7; i++) {
84 QStandardItem* audioItem = fileTypeComboBoxModel->item(i);
85 audioItem->setFlags(audioItem->flags() & ~Qt::ItemIsEnabled);
86 }
87 # endif
88 #endif
89
90 fileTypeComboBox->setCurrentIndex(0);
91 pageStack->setCurrentIndex(0);
92
93 pdfSeparateFilesRadioButton->setChecked(true);
94
95 audioSampleRate->clear();
96 audioSampleRate->addItem(tr("32000"), 32000);
97 audioSampleRate->addItem(tr("44100"), 44100); // default
98 audioSampleRate->addItem(tr("48000"), 48000);
99
100 mp3BitRate->clear();
101 mp3BitRate->addItem( tr("32"), 32);
102 mp3BitRate->addItem( tr("40"), 40);
103 mp3BitRate->addItem( tr("48"), 48);
104 mp3BitRate->addItem( tr("56"), 56);
105 mp3BitRate->addItem( tr("64"), 64);
106 mp3BitRate->addItem( tr("80"), 80);
107 mp3BitRate->addItem( tr("96"), 96);
108 mp3BitRate->addItem(tr("112"), 112);
109 mp3BitRate->addItem(tr("128"), 128); // default
110 mp3BitRate->addItem(tr("160"), 160);
111 mp3BitRate->addItem(tr("192"), 192);
112 mp3BitRate->addItem(tr("224"), 224);
113 mp3BitRate->addItem(tr("256"), 256);
114 mp3BitRate->addItem(tr("320"), 320);
115
116 retranslate();
117 MuseScore::restoreGeometry(this);
118 }
119
120 //---------------------------------------------------------
121 // retranslate
122 //---------------------------------------------------------
123
retranslate()124 void ExportDialog::retranslate()
125 {
126 retranslateUi(this);
127
128 if (listWidget->item(0))
129 listWidget->item(0)->setText(tr("Full Score"));
130
131 fileTypeComboBox->setItemText(0, tr("PDF File"));
132 fileTypeComboBox->setItemText(1, tr("PNG Images"));
133 fileTypeComboBox->setItemText(2, tr("SVG Images"));
134 fileTypeComboBox->setItemText(3, tr("MP3 Audio"));
135 fileTypeComboBox->setItemText(4, tr("WAV Audio"));
136 fileTypeComboBox->setItemText(5, tr("FLAC Audio"));
137 fileTypeComboBox->setItemText(6, tr("OGG Audio"));
138 fileTypeComboBox->setItemText(7, tr("MIDI"));
139 fileTypeComboBox->setItemText(8, tr("MusicXML"));
140 fileTypeComboBox->setItemText(9, tr("Uncompressed MuseScore File") + " (*.mscx)");
141
142 musicxmlFileTypeComboBox->setItemText(0, tr("Compressed") + " (*.mxl)");
143 musicxmlFileTypeComboBox->setItemText(1, tr("Uncompressed") + " (*.musicxml)");
144 musicxmlFileTypeComboBox->setItemText(2, tr("Uncompressed (outdated)") + " (*.xml)");
145
146 buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Export…"));
147 }
148
149 //---------------------------------------------------------
150 // loadValues
151 /// Load the preferences values into the UI
152 //---------------------------------------------------------
153
loadValues()154 void ExportDialog::loadValues()
155 {
156 pdfDpiSpinbox->setValue(preferences.getInt(PREF_EXPORT_PDF_DPI));
157
158 pngDpiSpinbox->setValue(preferences.getDouble(PREF_EXPORT_PNG_RESOLUTION));
159 pngTransparentBackgroundCheckBox->setChecked(preferences.getBool(PREF_EXPORT_PNG_USETRANSPARENCY));
160
161 audioNormaliseCheckBox->setChecked(preferences.getBool(PREF_EXPORT_AUDIO_NORMALIZE));
162 int audioSampleRateIndex = audioSampleRate->findData(preferences.getInt(PREF_EXPORT_AUDIO_SAMPLERATE));
163 if (audioSampleRateIndex == -1)
164 audioSampleRateIndex = audioSampleRate->findData(preferences.defaultValue(PREF_EXPORT_AUDIO_SAMPLERATE));
165 audioSampleRate->setCurrentIndex(audioSampleRateIndex);
166 int mp3BitRateIndex = mp3BitRate->findData(preferences.getInt(PREF_EXPORT_MP3_BITRATE));
167 if (mp3BitRateIndex == -1)
168 mp3BitRateIndex = mp3BitRate->findData(preferences.defaultValue(PREF_EXPORT_MP3_BITRATE));
169 mp3BitRate->setCurrentIndex(mp3BitRateIndex);
170
171 midiExpandRepeats->setChecked(preferences.getBool(PREF_IO_MIDI_EXPANDREPEATS));
172 midiExportRPNs->setChecked(preferences.getBool(PREF_IO_MIDI_EXPORTRPNS));
173
174 if (preferences.getBool(PREF_EXPORT_MUSICXML_EXPORTLAYOUT)) {
175 musicxmlExportAllLayout->setChecked(true);
176 } else {
177 switch (preferences.musicxmlExportBreaks()) {
178 case MusicxmlExportBreaks::ALL:
179 musicxmlExportAllBreaks->setChecked(true);
180 break;
181 case MusicxmlExportBreaks::MANUAL:
182 musicxmlExportManualBreaks->setChecked(true);
183 break;
184 case MusicxmlExportBreaks::NO:
185 musicxmlExportNoBreaks->setChecked(true);
186 break;
187 }
188 }
189 }
190
191 //---------------------------------------------------------
192 // loadScoreAndPartsList
193 //---------------------------------------------------------
194
loadScoreAndPartsList()195 void ExportDialog::loadScoreAndPartsList()
196 {
197 listWidget->clear();
198
199 ExportScoreItem* scoreItem = new ExportScoreItem(cs->masterScore()->score());
200 listWidget->addItem(scoreItem);
201
202 for (Excerpt* e : cs->masterScore()->excerpts()) {
203 Score* s = e->partScore();
204 ExportScoreItem* item = new ExportScoreItem(s);
205 item->setChecked(s == cs);
206 listWidget->addItem(item);
207 }
208 }
209
210 //---------------------------------------------------------
211 // selection
212 // Change the selection in the list of scores/parts.
213 //---------------------------------------------------------
214
215 // Select all parts and the score
selectAll()216 void ExportDialog::selectAll()
217 {
218 for (int i = 0; i < listWidget->count(); i++) {
219 ExportScoreItem* item = static_cast<ExportScoreItem*>(listWidget->item(i));
220 item->setChecked(true);
221 }
222 }
223
224 // Select the currently opened score/part
selectCurrent()225 void ExportDialog::selectCurrent()
226 {
227 for (int i = 0; i < listWidget->count(); i++) {
228 ExportScoreItem* item = static_cast<ExportScoreItem*>(listWidget->item(i));
229 item->setChecked(item->score() == cs);
230 }
231 }
232
233 // Select the score
selectScore()234 void ExportDialog::selectScore()
235 {
236 for (int i = 0; i < listWidget->count(); i++) {
237 ExportScoreItem* item = static_cast<ExportScoreItem*>(listWidget->item(i));
238 item->setChecked(item->score()->isMaster());
239 }
240 }
241
242 // Select the parts
selectParts()243 void ExportDialog::selectParts()
244 {
245 for (int i = 0; i < listWidget->count(); i++) {
246 ExportScoreItem* item = static_cast<ExportScoreItem*>(listWidget->item(i));
247 item->setChecked(!item->score()->isMaster());
248 }
249 }
250
251 // Select nothing
clearSelection()252 void ExportDialog::clearSelection()
253 {
254 for (int i = 0; i < listWidget->count(); i++) {
255 ExportScoreItem* item = static_cast<ExportScoreItem*>(listWidget->item(i));
256 item->setChecked(false);
257 }
258 }
259
260 //---------------------------------------------------------
261 // setOkButtonEnabled
262 //---------------------------------------------------------
263
setOkButtonEnabled()264 void ExportDialog::setOkButtonEnabled()
265 {
266 bool isAnythingSelected = false;
267 for (int i = 0; i < listWidget->count(); i++) {
268 ExportScoreItem* item = static_cast<ExportScoreItem*>(listWidget->item(i));
269 if (item->isChecked()) {
270 isAnythingSelected = true;
271 break;
272 }
273 }
274 buttonBox->button(QDialogButtonBox::Ok)->setEnabled(isAnythingSelected);
275 }
276
277 //---------------------------------------------------------
278 // fileTypeChosen
279 //---------------------------------------------------------
280
fileTypeChosen(int index)281 void ExportDialog::fileTypeChosen(int index)
282 {
283 if (index <= 2) // Pdf, png and svg
284 pageStack->setCurrentIndex(index);
285 else if (index <= 6) { // Audio formats share their page (because they share many settings)
286 pageStack->setCurrentWidget(audioPage);
287 mp3BitRateLabel->setVisible(index == 3);
288 mp3BitRate->setVisible(index == 3);
289 mp3kBitSLabel->setVisible(index == 3);
290 }
291 else // And others have their own page again
292 pageStack->setCurrentIndex(index - 3);
293 }
294
295 //---------------------------------------------------------
296 // setType
297 //---------------------------------------------------------
298
setType(const QString & type)299 void ExportDialog::setType(const QString& type)
300 {
301 if (type == "pdf")
302 fileTypeComboBox->setCurrentIndex(0);
303 else if (type == "png")
304 fileTypeComboBox->setCurrentIndex(1);
305 else if (type == "svg")
306 fileTypeComboBox->setCurrentIndex(2);
307 #ifdef USE_LAME
308 else if (type == "mp3")
309 fileTypeComboBox->setCurrentIndex(3);
310 #endif
311 #ifdef HAS_AUDIOFILE
312 else if (type == "wav")
313 fileTypeComboBox->setCurrentIndex(4);
314 else if (type == "flac")
315 fileTypeComboBox->setCurrentIndex(5);
316 else if (type == "ogg")
317 fileTypeComboBox->setCurrentIndex(6);
318 #endif
319 else if (type == "midi")
320 fileTypeComboBox->setCurrentIndex(7);
321 else if (type == "musicxml")
322 fileTypeComboBox->setCurrentIndex(8);
323 else if (type == "uncompressed")
324 fileTypeComboBox->setCurrentIndex(9);
325 else // Default
326 return setType("pdf");
327 }
328
329 //---------------------------------------------------------
330 // showExportDialog
331 //---------------------------------------------------------
332
showExportDialog(const QString & type)333 void MuseScore::showExportDialog(const QString& type)
334 {
335 if (!exportDialog)
336 exportDialog = new ExportDialog(cs, this);
337 else
338 exportDialog->setScore(cs);
339
340 if (type.size() > 0)
341 exportDialog->setType(type);
342 exportDialog->exec();
343 }
344
345 //---------------------------------------------------------
346 // accept
347 //---------------------------------------------------------
348
accept()349 void ExportDialog::accept()
350 {
351 // Collect which scores to export
352 QList<Score*> scores;
353 bool containsMasterScore = false;
354 for (int i = 0; i < listWidget->count(); i++) {
355 ExportScoreItem* item = static_cast<ExportScoreItem*>(listWidget->item(i));
356 if (item->isChecked()) {
357 scores.append(item->score());
358 if (item->score()->isMaster())
359 containsMasterScore = true;
360 }
361 }
362 if (scores.isEmpty())
363 return; // Should never happen
364
365 // Ask for save name and location
366 QString saveDirectory;
367 if (cs->masterScore()->fileInfo()->exists())
368 saveDirectory = cs->masterScore()->fileInfo()->dir().path();
369 else {
370 QSettings set;
371 if (mscore->lastSaveCopyDirectory.isEmpty())
372 mscore->lastSaveCopyDirectory = set.value("lastSaveCopyDirectory", preferences.getString(PREF_APP_PATHS_MYSCORES)).toString();
373 saveDirectory = mscore->lastSaveCopyDirectory;
374 }
375 if (saveDirectory.isEmpty())
376 saveDirectory = preferences.getString(PREF_APP_PATHS_MYSCORES);
377
378 QString saveFormat;
379 int currentIndex = fileTypeComboBox->currentIndex();
380 if (currentIndex == 0) {
381 saveFormat = "pdf";
382 if (pdfDpiSpinbox->value() != preferences.getInt(PREF_EXPORT_PDF_DPI))
383 preferences.setPreference(PREF_EXPORT_PDF_DPI, pdfDpiSpinbox->value());
384 } else if (currentIndex == 1) {
385 saveFormat = "png";
386 if (pngDpiSpinbox->value() != preferences.getDouble(PREF_EXPORT_PNG_RESOLUTION))
387 preferences.setPreference(PREF_EXPORT_PNG_RESOLUTION, pngDpiSpinbox->value());
388 if (pngTransparentBackgroundCheckBox->isChecked() != preferences.getBool(PREF_EXPORT_PNG_USETRANSPARENCY))
389 preferences.setPreference(PREF_EXPORT_PNG_USETRANSPARENCY, pngTransparentBackgroundCheckBox->isChecked());
390 } else if (currentIndex == 2) {
391 saveFormat = "svg";
392 } else if (currentIndex <= 6) { // The audio formats share some settings
393 if (currentIndex == 3) {
394 saveFormat = "mp3";
395 if (mp3BitRate->currentData() != preferences.getInt(PREF_EXPORT_MP3_BITRATE))
396 preferences.setPreference(PREF_EXPORT_MP3_BITRATE, mp3BitRate->currentData());
397 } else if (currentIndex == 4) {
398 saveFormat = "wav";
399 } else if (currentIndex == 5) {
400 saveFormat = "flac";
401 } else if (currentIndex == 6) {
402 saveFormat = "ogg";
403 }
404 if (audioSampleRate->currentData() != preferences.getInt(PREF_EXPORT_AUDIO_SAMPLERATE))
405 preferences.setPreference(PREF_EXPORT_AUDIO_SAMPLERATE, audioSampleRate->currentData());
406 if (audioNormaliseCheckBox->isChecked() != preferences.getBool(PREF_EXPORT_AUDIO_NORMALIZE))
407 preferences.setPreference(PREF_EXPORT_AUDIO_NORMALIZE, audioNormaliseCheckBox->isChecked());
408 } else if (currentIndex == 7) {
409 saveFormat = "mid";
410 if (midiExpandRepeats->isChecked() != preferences.getBool(PREF_IO_MIDI_EXPANDREPEATS))
411 preferences.setPreference(PREF_IO_MIDI_EXPANDREPEATS, midiExpandRepeats->isChecked());
412 if (midiExportRPNs->isChecked() != preferences.getBool(PREF_IO_MIDI_EXPORTRPNS))
413 preferences.setPreference(PREF_IO_MIDI_EXPORTRPNS, midiExportRPNs->isChecked());
414 } else if (currentIndex == 8) {
415 if (musicxmlFileTypeComboBox->currentIndex() == 1)
416 saveFormat = "musicxml";
417 else if (musicxmlFileTypeComboBox->currentIndex() == 2)
418 saveFormat = "xml";
419 else
420 saveFormat = "mxl";
421 if (musicxmlExportAllLayout->isChecked() && !preferences.getBool(PREF_EXPORT_MUSICXML_EXPORTLAYOUT))
422 preferences.setPreference(PREF_EXPORT_MUSICXML_EXPORTLAYOUT, true);
423 else if (musicxmlExportAllBreaks->isChecked() && preferences.musicxmlExportBreaks() != MusicxmlExportBreaks::ALL)
424 preferences.setCustomPreference<MusicxmlExportBreaks>(PREF_EXPORT_MUSICXML_EXPORTBREAKS, MusicxmlExportBreaks::ALL);
425 else if (musicxmlExportManualBreaks->isChecked() && preferences.musicxmlExportBreaks() != MusicxmlExportBreaks::MANUAL)
426 preferences.setCustomPreference<MusicxmlExportBreaks>(PREF_EXPORT_MUSICXML_EXPORTBREAKS, MusicxmlExportBreaks::MANUAL);
427 else if (musicxmlExportNoBreaks->isChecked() && preferences.musicxmlExportBreaks() != MusicxmlExportBreaks::NO)
428 preferences.setCustomPreference<MusicxmlExportBreaks>(PREF_EXPORT_MUSICXML_EXPORTBREAKS, MusicxmlExportBreaks::NO);
429 } else if (currentIndex == 9)
430 saveFormat = "mscx";
431
432 // If only one file will be created during export, the filename will be exactly
433 // as the user types in the SaveDialog, so therefore, we can have the SaveDialog
434 // responsible for asking whether the user wants to replace any existing files.
435 // Otherwise, we will ask that per file, here below.
436 bool oneScore = scores.size() == 1;
437 bool singlePDF = saveFormat == "pdf" && pdfOneFileRadioButton->isChecked();
438 bool oneFile = (oneScore && saveFormat != "png" && saveFormat != "svg") || singlePDF;
439
440 QString filter;
441 if (saveFormat == "mid")
442 filter = "*.mid;*.midi";
443 else
444 filter = QString("*.%1").arg(saveFormat);
445
446 QString name = QString("%1/%2").arg(saveDirectory, cs->masterScore()->fileInfo()->completeBaseName());
447 if (oneScore) {
448 Score* score = scores.first();
449 name.append(QString("%1").arg(score->isMaster() ? "" : "-" + mscore->saveFilename(score->title())));
450 }
451 else if (singlePDF)
452 name.append(QString("-%1").arg(containsMasterScore ? tr("Score_and_Parts") : tr("Parts")));
453
454 #ifdef Q_OS_WIN
455 if (QOperatingSystemVersion::current() > QOperatingSystemVersion(QOperatingSystemVersion::Windows, 5, 1)) // XP
456 #endif
457 name.append(QString(".%1").arg(saveFormat));
458
459 QString filename = mscore->getSaveScoreName(tr("Export"), name, filter, /*selectFolder*/ false, /*askOverwrite*/ oneFile);
460 if (filename.isEmpty())
461 return; // User cancels; keep Export dialog open
462
463 QFileInfo fileinfo(filename);
464 mscore->lastSaveCopyDirectory = fileinfo.absolutePath();
465 mscore->lastSaveCopyFormat = fileinfo.suffix();
466
467 QString suffix = fileinfo.suffix();
468 if (suffix.isEmpty()) {
469 QMessageBox::critical(this, tr("Export"), tr("Cannot determine file type."));
470 return; // Error occurred; keep Export dialog open
471 }
472
473 // At this moment, close the dialog
474 QDialog::accept();
475
476 if (singlePDF)
477 // Export the selected scores as one pdf file, directly with the filename the user typed
478 mscore->savePdf(scores, filename);
479 else if (oneScore)
480 // Export the selected score, directly with the filename the user typed
481 mscore->saveAs(scores.first(), true, filename, suffix);
482 else {
483 // Export the selected scores as separate files, appending the part names to the filename
484 SaveReplacePolicy replacePolicy = SaveReplacePolicy::NO_CHOICE;
485
486 for (Score* score : scores) {
487 QString definitiveFilename = QString("%1/%2%3.%4")
488 .arg(fileinfo.absolutePath(),
489 fileinfo.completeBaseName(),
490 score->isMaster() ? "" : "-" + mscore->saveFilename(score->title()),
491 suffix);
492 if (saveFormat != "png" && saveFormat != "svg" && QFileInfo(definitiveFilename).exists()) {
493 // Png and Svg export functions change the filename, so they
494 // are responsible for asking the user about overwriting.
495 switch (replacePolicy) {
496 case SaveReplacePolicy::NO_CHOICE:
497 {
498 int responseCode = mscore->askOverwriteAll(definitiveFilename);
499 if (responseCode == QMessageBox::YesToAll) {
500 replacePolicy = SaveReplacePolicy::REPLACE_ALL;
501 break; // Break out of the switch; go on and replace the existing file
502 }
503 else if (responseCode == QMessageBox::NoToAll) {
504 replacePolicy = SaveReplacePolicy::SKIP_ALL;
505 continue; // Continue in the `for` loop
506 }
507 else if (responseCode == QMessageBox::No)
508 continue;
509 break;
510 }
511 case SaveReplacePolicy::SKIP_ALL:
512 continue;
513 case SaveReplacePolicy::REPLACE_ALL:
514 break;
515 }
516 }
517 mscore->saveAs(score, true, definitiveFilename, suffix, &replacePolicy);
518 }
519 }
520 }
521
522 //---------------------------------------------------------
523 // showEvent
524 //---------------------------------------------------------
525
showEvent(QShowEvent * event)526 void ExportDialog::showEvent(QShowEvent* event)
527 {
528 loadValues();
529 loadScoreAndPartsList();
530 selectScore();
531
532 QDialog::showEvent(event);
533 }
534
535 //---------------------------------------------------------
536 // hideEvent
537 //---------------------------------------------------------
538
hideEvent(QHideEvent * event)539 void ExportDialog::hideEvent(QHideEvent* event)
540 {
541 MuseScore::saveGeometry(this);
542 QDialog::hideEvent(event);
543 }
544 }
545