1 /* BEGIN_COMMON_COPYRIGHT_HEADER
2 * (c)LGPL2+
3 *
4 * Flacon - audio File Encoder
5 * https://github.com/flacon/flacon
6 *
7 * Copyright: 2012-2013
8 * Alexander Sokoloff <sokoloff.a@gmail.com>
9 *
10 * This library is free software; you can redistribute it and/or
11 * modify it under the terms of the GNU Lesser General Public
12 * License as published by the Free Software Foundation; either
13 * version 2.1 of the License, or (at your option) any later version.
14
15 * This library is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 * Lesser General Public License for more details.
19
20 * You should have received a copy of the GNU Lesser General Public
21 * License along with this library; if not, write to the Free Software
22 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
23 *
24 * END_COMMON_COPYRIGHT_HEADER */
25
26 #include "mainwindow.h"
27 #include "project.h"
28 #include "disc.h"
29 #include "settings.h"
30 #include "converter/converter.h"
31 #include "formats_out/outformat.h"
32 #include "inputaudiofile.h"
33 #include "formats_in/informat.h"
34 #include "preferences/preferencesdialog.h"
35 #include "aboutdialog/aboutdialog.h"
36 #include "scanner.h"
37 #include "gui/coverdialog/coverdialog.h"
38 #include "internet/dataprovider.h"
39 #include "gui/trackviewmodel.h"
40 #include "gui/tageditor/tageditor.h"
41 #include "controls.h"
42 #include "gui/icon.h"
43 #include "application.h"
44 #include "patternexpander.h"
45
46 #include <QFileDialog>
47 #include <QDir>
48 #include <QDebug>
49 #include <QMessageBox>
50 #include <QTextCodec>
51 #include <QQueue>
52 #include <QKeyEvent>
53 #include <QMimeData>
54 #include <QStyleFactory>
55 #include <QToolBar>
56 #include <QToolButton>
57 #include <QStandardPaths>
58
59 #ifdef MAC_UPDATER
60 #include "updater/updater.h"
61 #endif
62
63 /************************************************
64
65 ************************************************/
MainWindow(QWidget * parent)66 MainWindow::MainWindow(QWidget *parent) :
67 QMainWindow(parent),
68 mScanner(nullptr)
69 {
70 Messages::setHandler(this);
71
72 setupUi(this);
73
74 qApp->setWindowIcon(loadMainIcon());
75 toolBar->setToolButtonStyle(Qt::ToolButtonTextUnderIcon);
76 toolBar->setIconSize(QSize(24, 24));
77 qApp->setAttribute(Qt::AA_DontShowIconsInMenus, true);
78 qApp->setAttribute(Qt::AA_UseHighDpiPixmaps, true);
79
80 #ifdef Q_OS_MAC
81 this->setUnifiedTitleAndToolBarOnMac(true);
82 setWindowIcon(QIcon());
83
84 trackView->setFrameShape(QFrame::NoFrame);
85 splitter->setStyleSheet("::handle{ border-right: 1px solid #7F7F7F7F;}");
86 #endif
87
88 setAcceptDrops(true);
89 setAcceptDrops(true);
90 this->setContextMenuPolicy(Qt::NoContextMenu);
91
92 outPatternButton->setToolTip(outPatternEdit->toolTip());
93 outDirButton->setToolTip(outDirEdit->toolTip());
94
95 // TrackView ...............................................
96 trackView->setRootIsDecorated(false);
97 trackView->setItemsExpandable(false);
98 trackView->hideColumn((int)TrackView::ColumnComment);
99 trackView->setAlternatingRowColors(false);
100
101 // Tag edits ...............................................
102 tagGenreEdit->setTagId(TagId::Genre);
103 connect(tagGenreEdit, &TagLineEdit::textEdited, this, &MainWindow::setTrackTag);
104
105 tagYearEdit->setTagId(TagId::Date);
106 connect(tagYearEdit, &TagLineEdit::textEdited, this, &MainWindow::setTrackTag);
107
108 tagArtistEdit->setTagId(TagId::Artist);
109 connect(tagArtistEdit, &TagLineEdit::textEdited, this, &MainWindow::setTrackTag);
110 connect(tagArtistEdit, &TagLineEdit::textEdited, this, &MainWindow::refreshEdits);
111
112 tagDiscPerformerEdit->setTagId(TagId::AlbumArtist);
113 connect(tagDiscPerformerEdit, &TagLineEdit::textEdited, this, &MainWindow::setDiscTag);
114 connect(tagDiscPerformerEdit, &TagLineEdit::textEdited, this, &MainWindow::refreshEdits);
115
116 tagAlbumEdit->setTagId(TagId::Album);
117 connect(tagAlbumEdit, &TagLineEdit::textEdited, this, &MainWindow::setTrackTag);
118
119 tagDiscIdEdit->setTagId(TagId::DiscId);
120 connect(tagDiscIdEdit, &TagLineEdit::textEdited, this, &MainWindow::setTrackTag);
121 connect(tagDiscIdEdit, &TagLineEdit::textEdited, this, &MainWindow::setControlsEnable);
122
123 connect(tagStartNumEdit, &MultiValuesSpinBox::editingFinished, this, &MainWindow::setStartTrackNum);
124 connect(tagStartNumEdit, qOverload<int>(&MultiValuesSpinBox::valueChanged),
125 this, &MainWindow::setStartTrackNum);
126
127 connect(trackView->model(), &TrackViewModel::dataChanged, this, &MainWindow::refreshEdits);
128 connect(trackView, &TrackView::customContextMenuRequested, this, &MainWindow::trackViewMenu);
129
130 connect(editTagsButton, &QPushButton::clicked, this, &MainWindow::openEditTagsDialog);
131
132 initActions();
133
134 // Buttons .................................................
135 outDirButton->setBuddy(outDirEdit);
136 outDirButton->menu()->addSeparator();
137 QAction *act = outDirEdit->deleteItemAction();
138 act->setText(tr("Remove current directory from history"));
139 outDirButton->menu()->addAction(act);
140
141 configureProfileBtn->setBuddy(outProfileCombo);
142 configureProfileBtn->setDefaultAction(actionConfigureEncoder);
143
144 outPatternButton->setBuddy(outPatternEdit);
145 outPatternButton->addStandardPatterns();
146 outPatternButton->menu()->addSeparator();
147
148 outPatternEdit->deleteItemAction()->setText(tr("Delete current pattern from history"));
149 outPatternButton->menu()->addAction(outPatternEdit->deleteItemAction());
150
151 loadSettings();
152
153 // Signals .................................................
154 connect(Settings::i(), &Settings::changed,
155 [this]() { trackView->model()->layoutChanged(); });
156
157 connect(outDirEdit->lineEdit(), &QLineEdit::textChanged, this, &MainWindow::setOutDir);
158 connect(outPatternEdit->lineEdit(), &QLineEdit::textChanged, this, &MainWindow::setPattern);
159
160 connect(outProfileCombo, qOverload<int>(&QComboBox::currentIndexChanged),
161 this, &MainWindow::setOutProfile);
162
163 connect(codepageCombo, qOverload<int>(&QComboBox::currentIndexChanged),
164 this, &MainWindow::setCodePage);
165
166 connect(trackView, &TrackView::selectCueFile, this, &MainWindow::setCueForDisc);
167 connect(trackView, &TrackView::selectAudioFile, this, &MainWindow::setAudioForDisc);
168 connect(trackView, &TrackView::showAudioMenu, this, &MainWindow::showDiskAudioFileMenu);
169 connect(trackView, &TrackView::selectCoverImage, this, &MainWindow::setCoverImage);
170 connect(trackView, &TrackView::downloadInfo, this, &MainWindow::downloadDiscInfo);
171
172 connect(trackView->model(), &TrackViewModel::layoutChanged, this, &MainWindow::refreshEdits);
173 connect(trackView->model(), &TrackViewModel::layoutChanged, this, &MainWindow::setControlsEnable);
174
175 connect(trackView->selectionModel(), &QItemSelectionModel::selectionChanged,
176 this, &MainWindow::refreshEdits);
177 connect(trackView->selectionModel(), &QItemSelectionModel::selectionChanged,
178 this, &MainWindow::setControlsEnable);
179
180 connect(project, &Project::layoutChanged, trackView, &TrackView::layoutChanged);
181 connect(project, &Project::layoutChanged, this, &MainWindow::refreshEdits);
182 connect(project, &Project::layoutChanged, this, &MainWindow::setControlsEnable);
183
184 connect(project, &Project::discChanged, this, &MainWindow::refreshEdits);
185 connect(project, &Project::discChanged, this, &MainWindow::setControlsEnable);
186
187 connect(Application::instance(), &Application::visualModeChanged,
188 []() {
189 Icon::setDarkMode(Application::instance()->isDarkVisualMode());
190 });
191
192 Icon::setDarkMode(Application::instance()->isDarkVisualMode());
193
194 refreshEdits();
195 setControlsEnable();
196 polishView();
197 }
198
199 /************************************************
200 *
201 ************************************************/
202 #ifdef Q_OS_MAC
polishView()203 void MainWindow::polishView()
204 {
205 QList<QLabel *> labels;
206 labels << outFilesBox->findChildren<QLabel *>();
207 labels << tagsBox->findChildren<QLabel *>();
208
209 for (QLabel *label : labels) {
210 label->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
211 }
212 }
213 #else
polishView()214 void MainWindow::polishView()
215 {
216 }
217 #endif
218
219 /************************************************
220 *
221 ************************************************/
showEvent(QShowEvent *)222 void MainWindow::showEvent(QShowEvent *)
223 {
224 if (project->count())
225 trackView->selectDisc(project->disc(0));
226 }
227
228 /************************************************
229
230 ************************************************/
~MainWindow()231 MainWindow::~MainWindow()
232 {
233 }
234
235 /************************************************
236
237 ************************************************/
closeEvent(QCloseEvent *)238 void MainWindow::closeEvent(QCloseEvent *)
239 {
240 if (mConverter)
241 mConverter->stop();
242 saveSettings();
243 }
244
245 /************************************************
246
247 ************************************************/
dragEnterEvent(QDragEnterEvent * event)248 void MainWindow::dragEnterEvent(QDragEnterEvent *event)
249 {
250 foreach (QUrl url, event->mimeData()->urls()) {
251 if (url.isLocalFile()) {
252 event->acceptProposedAction();
253 return;
254 }
255 }
256 }
257
258 /************************************************
259
260 ************************************************/
dropEvent(QDropEvent * event)261 void MainWindow::dropEvent(QDropEvent *event)
262 {
263 foreach (QUrl url, event->mimeData()->urls()) {
264 addFileOrDir(url.toLocalFile());
265 }
266 }
267
268 /************************************************
269
270 ************************************************/
setPattern()271 void MainWindow::setPattern()
272 {
273 Settings::i()->currentProfile().setOutFilePattern(outPatternEdit->currentText());
274 trackView->model()->layoutChanged();
275 }
276
277 /************************************************
278
279 ************************************************/
setOutDir()280 void MainWindow::setOutDir()
281 {
282 Settings::i()->currentProfile().setOutFileDir(outDirEdit->currentText());
283 trackView->model()->layoutChanged();
284 }
285
286 /************************************************
287
288 ************************************************/
setCueForDisc(Disc * disc)289 void MainWindow::setCueForDisc(Disc *disc)
290 {
291 QString flt = getOpenFileFilter(false, true);
292
293 QString dir;
294 {
295 QStringList audioFiles = disc->audioFilePaths();
296 audioFiles.removeAll("");
297
298 if (!audioFiles.isEmpty()) {
299 dir = QFileInfo(audioFiles.first()).dir().absolutePath();
300 }
301 else if (!disc->cueFilePath().isEmpty()) {
302 dir = QFileInfo(disc->cueFilePath()).dir().absolutePath();
303 }
304 else {
305 dir = Settings::i()->value(Settings::Misc_LastDir).toString();
306 }
307 }
308
309 QString fileName = QFileDialog::getOpenFileName(this, tr("Select CUE file", "OpenFile dialog title"), dir, flt);
310
311 if (fileName.isEmpty())
312 return;
313
314 try {
315 Cue cue(fileName);
316 QString oldDir = QFileInfo(disc->cueFilePath()).dir().path();
317 disc->setCueFile(cue);
318 QString newDir = QFileInfo(disc->cueFilePath()).dir().path();
319
320 if (disc->isMultiAudio()) {
321 disc->searchAudioFiles(false);
322 }
323
324 if (newDir != oldDir) {
325 disc->searchCoverImage(false);
326 }
327 }
328 catch (FlaconError &err) {
329 Messages::error(err.what());
330 }
331 }
332
333 /************************************************
334
335 ************************************************/
setOutProfile()336 void MainWindow::setOutProfile()
337 {
338 int n = outProfileCombo->currentIndex();
339 if (n > -1) {
340 Settings::i()->selectProfile(outProfileCombo->itemData(n).toString());
341 }
342 trackView->model()->layoutChanged();
343 }
344
345 /************************************************
346
347 ************************************************/
setControlsEnable()348 void MainWindow::setControlsEnable()
349 {
350 bool convert = mConverter && mConverter->isRunning();
351 bool scan = mScanner;
352 bool running = scan || convert;
353
354 bool tracksSelected = !trackView->selectedTracks().isEmpty();
355 bool discsSelected = !trackView->selectedDiscs().isEmpty();
356 bool canConvert = Conv::Converter::canConvert();
357
358 bool canDownload = false;
359 foreach (const Disc *disc, trackView->selectedDiscs())
360 canDownload = canDownload || !disc->discId().isEmpty();
361
362 outFilesBox->setEnabled(!running);
363 tagsBox->setEnabled(!running && tracksSelected);
364 actionAddDisc->setEnabled(!running);
365 actionRemoveDisc->setEnabled(!running && discsSelected);
366 actionStartConvert->setEnabled(!running && canConvert);
367 actionDownloadTrackInfo->setEnabled(!running && canDownload);
368 actionAddFolder->setEnabled(!running);
369 actionConfigure->setEnabled(!running);
370 actionConfigureEncoder->setEnabled(!running);
371
372 actionStartConvert->setEnabled(!scan && canConvert);
373 actionAbortConvert->setEnabled(convert);
374
375 actionStartConvert->setVisible(!convert);
376 actionAbortConvert->setVisible(convert);
377 }
378
379 /************************************************
380
381 ************************************************/
refreshEdits()382 void MainWindow::refreshEdits()
383 {
384
385 // Discs ...............................
386 QSet<int> startNums;
387 QSet<QString> discId;
388 QSet<QString> codePage;
389
390 QList<Disc *> discs = trackView->selectedDiscs();
391 foreach (Disc *disc, discs) {
392 startNums << disc->startTrackNum();
393 discId << disc->discId();
394 codePage << disc->codecName();
395 }
396
397 // Tracks ..............................
398 QSet<QString> genre;
399 QSet<QString> artist;
400 QSet<QString> album;
401 QSet<QString> date;
402 QSet<QString> discPerformer;
403
404 QList<Track *> tracks = trackView->selectedTracks();
405 foreach (Track *track, tracks) {
406 genre << track->genre();
407 artist << track->artist();
408 album << track->album();
409 date << track->date();
410 discPerformer << track->tag(TagId::AlbumArtist);
411 }
412
413 tagGenreEdit->setMultiValue(genre);
414 tagYearEdit->setMultiValue(date);
415 tagArtistEdit->setMultiValue(artist);
416 tagAlbumEdit->setMultiValue(album);
417 tagStartNumEdit->setMultiValue(startNums);
418 tagDiscIdEdit->setMultiValue(discId);
419 codepageCombo->setMultiValue(codePage);
420 tagDiscPerformerEdit->setMultiValue(discPerformer);
421
422 const Profile &profile = Settings::i()->currentProfile();
423 if (outDirEdit->currentText() != profile.outFileDir())
424 outDirEdit->lineEdit()->setText(profile.outFileDir());
425
426 if (outPatternEdit->currentText() != profile.outFilePattern())
427 outPatternEdit->lineEdit()->setText(profile.outFilePattern());
428
429 refreshOutProfileCombo();
430 }
431
432 /************************************************
433
434 ************************************************/
refreshOutProfileCombo()435 void MainWindow::refreshOutProfileCombo()
436 {
437 outProfileCombo->blockSignals(true);
438 int n = 0;
439 for (const Profile &p : Settings::i()->profiles()) {
440 if (n < outProfileCombo->count()) {
441 outProfileCombo->setItemText(n, p.name());
442 outProfileCombo->setItemData(n, p.id());
443 }
444 else {
445 outProfileCombo->addItem(p.name(), p.id());
446 }
447 n++;
448 }
449 while (outProfileCombo->count() > n)
450 outProfileCombo->removeItem(outProfileCombo->count() - 1);
451
452 outProfileCombo->blockSignals(false);
453
454 n = outProfileCombo->findData(Settings::i()->currentProfile().id());
455 outProfileCombo->setCurrentIndex(qMax(0, n));
456 }
457
458 /************************************************
459
460 ************************************************/
setCodePage()461 void MainWindow::setCodePage()
462 {
463 int n = codepageCombo->currentIndex();
464 if (n > -1) {
465 QString codepage = codepageCombo->itemData(n).toString();
466
467 QList<Disc *> discs = trackView->selectedDiscs();
468 foreach (Disc *disc, discs)
469 disc->setCodecName(codepage);
470
471 Settings::i()->setValue(Settings::Tags_DefaultCodepage, codepage);
472 }
473 }
474
475 /************************************************
476
477 ************************************************/
setTrackTag()478 void MainWindow::setTrackTag()
479 {
480 TagLineEdit *edit = qobject_cast<TagLineEdit *>(sender());
481 if (!edit)
482 return;
483
484 QList<Track *> tracks = trackView->selectedTracks();
485 foreach (Track *track, tracks) {
486 track->setTag(edit->tagId(), edit->text());
487 trackView->update(*track);
488 }
489 }
490
491 /************************************************
492 *
493 ************************************************/
setDiscTag()494 void MainWindow::setDiscTag()
495 {
496 TagLineEdit *edit = qobject_cast<TagLineEdit *>(sender());
497 if (!edit)
498 return;
499
500 QList<Disc *> discs = trackView->selectedDiscs();
501 foreach (Disc *disc, discs) {
502 disc->setDiscTag(edit->tagId(), edit->text());
503 trackView->update(*disc);
504 }
505 }
506
507 /************************************************
508 *
509 ************************************************/
setDiscTagInt()510 void MainWindow::setDiscTagInt()
511 {
512 TagSpinBox *spinBox = qobject_cast<TagSpinBox *>(sender());
513 if (!spinBox)
514 return;
515
516 QList<Disc *> discs = trackView->selectedDiscs();
517 foreach (Disc *disc, discs) {
518 disc->setDiscTag(spinBox->tagId(), QString::number(spinBox->value()));
519 trackView->update(*disc);
520 }
521 }
522
523 /************************************************
524 *
525 ************************************************/
startConvertAll()526 void MainWindow::startConvertAll()
527 {
528 Conv::Converter::Jobs jobs;
529 for (int d = 0; d < project->count(); ++d) {
530 Conv::Converter::Job job;
531 job.disc = project->disc(d);
532
533 for (int t = 0; t < job.disc->count(); ++t) {
534 job.tracks << job.disc->track(t);
535 }
536
537 jobs << job;
538 }
539
540 startConvert(jobs);
541 }
542
543 /************************************************
544 *
545 ************************************************/
startConvertSelected()546 void MainWindow::startConvertSelected()
547 {
548 Conv::Converter::Jobs jobs;
549 for (Disc *disc : trackView->selectedDiscs()) {
550 Conv::Converter::Job job;
551 job.disc = disc;
552
553 for (int t = 0; t < disc->count(); ++t) {
554 Track *track = disc->track(t);
555 if (trackView->isSelected(*track)) {
556 job.tracks << track;
557 }
558 }
559
560 jobs << job;
561 }
562
563 startConvert(jobs);
564 }
565
566 /************************************************
567
568 ************************************************/
startConvert(const Conv::Converter::Jobs & jobs)569 void MainWindow::startConvert(const Conv::Converter::Jobs &jobs)
570 {
571 if (!Settings::i()->currentProfile().isValid())
572 return;
573
574 trackView->setFocus();
575
576 bool ok = true;
577 for (int i = 0; i < project->count(); ++i) {
578 ok = ok && project->disc(i)->canConvert();
579 }
580
581 if (!ok) {
582 int res = QMessageBox::warning(this,
583 windowTitle(),
584 tr("Some albums will not be converted, they contain errors.\nDo you want to continue?"),
585 QMessageBox::Ok | QMessageBox::Cancel);
586 if (res != QMessageBox::Ok)
587 return;
588 }
589
590 for (int d = 0; d < project->count(); ++d) {
591 Disc *disc = project->disc(d);
592 for (int t = 0; t < disc->count(); ++t)
593 trackView->model()->trackProgressChanged(*disc->track(t), TrackState::NotRunning, 0);
594 }
595
596 trackView->setColumnWidth(TrackView::ColumnPercent, 200);
597 mConverter = new Conv::Converter();
598 connect(mConverter, &Conv::Converter::finished,
599 this, &MainWindow::setControlsEnable);
600
601 connect(mConverter, &Conv::Converter::finished,
602 mConverter, &Conv::Converter::deleteLater);
603
604 connect(mConverter, &Conv::Converter::trackProgress,
605 trackView->model(), &TrackViewModel::trackProgressChanged);
606
607 connect(mConverter, &Conv::Converter::error,
608 this, &MainWindow::showErrorMessage);
609
610 mConverter->start(jobs, Settings::i()->currentProfile());
611 setControlsEnable();
612 }
613
614 /************************************************
615
616 ************************************************/
stopConvert()617 void MainWindow::stopConvert()
618 {
619 if (mConverter)
620 mConverter->stop();
621 setControlsEnable();
622 }
623
624 /************************************************
625
626 ************************************************/
configure()627 void MainWindow::configure()
628 {
629 auto dlg = PreferencesDialog::createAndShow(nullptr, this);
630 connect(dlg, &PreferencesDialog::finished, this, &MainWindow::refreshEdits, Qt::UniqueConnection);
631 }
632
633 /************************************************
634
635 ************************************************/
configureEncoder()636 void MainWindow::configureEncoder()
637 {
638 auto dlg = PreferencesDialog::createAndShow(Settings::i()->currentProfile().id(), this);
639 connect(dlg, &PreferencesDialog::finished, this, &MainWindow::refreshEdits, Qt::UniqueConnection);
640 }
641
642 /************************************************
643
644 ************************************************/
downloadInfo()645 void MainWindow::downloadInfo()
646 {
647 QList<Disc *> discs = trackView->selectedDiscs();
648 foreach (Disc *disc, discs) {
649 this->downloadDiscInfo(disc);
650 }
651 }
652
653 /************************************************
654
655 ************************************************/
getOpenFileFilter(bool includeAudio,bool includeCue)656 QString MainWindow::getOpenFileFilter(bool includeAudio, bool includeCue)
657 {
658 QStringList flt;
659 QStringList allFlt;
660 QString fltPattern = tr("%1 files", "OpenFile dialog filter line, like \"WAV files\"") + " (*.%2)";
661
662 if (includeAudio) {
663 foreach (const InputFormat *format, InputFormat::allFormats()) {
664 allFlt << QString(" *.%1").arg(format->ext());
665 flt << fltPattern.arg(format->name(), format->ext());
666 }
667 }
668
669 flt.sort();
670
671 if (includeCue) {
672 allFlt << QString("*.cue");
673 flt.insert(0, fltPattern.arg("CUE", "cue"));
674 }
675
676 if (allFlt.count() > 1)
677 flt.insert(0, tr("All supported formats", "OpenFile dialog filter line") + " (" + allFlt.join(" ") + ")");
678
679 flt << tr("All files", "OpenFile dialog filter line like \"All files\"") + " (*)";
680
681 return flt.join(";;");
682 }
683
684 /************************************************
685
686 ************************************************/
openAddFileDialog()687 void MainWindow::openAddFileDialog()
688 {
689 QString flt = getOpenFileFilter(true, true);
690 QString lastDir = Settings::i()->value(Settings::Misc_LastDir).toString();
691 QStringList fileNames = QFileDialog::getOpenFileNames(this, tr("Add CUE or audio file", "OpenFile dialog title"), lastDir, flt);
692
693 if (fileNames.isEmpty()) {
694 return;
695 }
696
697 Settings::i()->setValue(Settings::Misc_LastDir, QFileInfo(fileNames.last()).dir().path());
698
699 foreach (const QString &fileName, fileNames) {
700 addFileOrDir(fileName);
701 }
702 }
703
704 /************************************************
705
706 ************************************************/
setAudioForDisc(Disc * disc,int audioFileNum)707 void MainWindow::setAudioForDisc(Disc *disc, int audioFileNum)
708 {
709 QString flt = getOpenFileFilter(true, false);
710
711 QString dir;
712 {
713 QStringList audioFiles = disc->audioFilePaths();
714
715 if (!disc->cueFilePath().isEmpty()) {
716 dir = QFileInfo(disc->cueFilePath()).dir().absolutePath();
717 }
718 else if (audioFileNum < audioFiles.count() && !audioFiles[audioFileNum].isEmpty()) {
719 dir = QFileInfo(audioFiles[audioFileNum]).dir().absolutePath();
720 }
721 else {
722 dir = Settings::i()->value(Settings::Misc_LastDir).toString();
723 }
724 }
725
726 QString fileName = QFileDialog::getOpenFileName(this, tr("Select audio file", "OpenFile dialog title"), dir, flt);
727
728 if (fileName.isEmpty())
729 return;
730
731 InputAudioFile audio(fileName);
732 if (!audio.isValid()) {
733 Messages::error(tr("\"%1\" was not set.", "Error message, %1 is an filename.")
734 .arg(QFileInfo(fileName).fileName())
735 + "<br>" + audio.errorString());
736 return;
737 }
738
739 disc->setAudioFile(audio, audioFileNum);
740 trackView->update(*disc);
741 }
742
743 /************************************************
744 *
745 ************************************************/
setCoverImage(Disc * disc)746 void MainWindow::setCoverImage(Disc *disc)
747 {
748 CoverDialog::createAndShow(disc, this);
749 }
750
751 /************************************************
752 *
753 ************************************************/
downloadDiscInfo(Disc * disc)754 void MainWindow::downloadDiscInfo(Disc *disc)
755 {
756 if (!disc->canDownloadInfo())
757 return;
758
759 DataProvider *provider = new FreeDbProvider(*disc);
760 connect(provider, &DataProvider::finished,
761 provider, &DataProvider::deleteLater);
762
763 connect(provider, &DataProvider::finished, provider,
764 [disc, this]() { trackView->downloadFinished(*disc); });
765
766 connect(provider, &DataProvider::ready, provider,
767 [disc](const QVector<Tracks> data) { disc->addTagSets(data); });
768
769 provider->start();
770 trackView->downloadStarted(*disc);
771 }
772
773 /************************************************
774
775 ************************************************/
addFileOrDir(const QString & fileName)776 void MainWindow::addFileOrDir(const QString &fileName)
777 {
778 bool isFirst = true;
779 bool showError = false;
780 auto addFile = [&](const QString &file) {
781 try {
782 QFileInfo fi = QFileInfo(file);
783 DiscList discs;
784 if (fi.size() > 102400)
785 discs << project->addAudioFile(file);
786 else
787 discs << project->addCueFile(file);
788
789 if (!discs.isEmpty() && isFirst) {
790 isFirst = false;
791 this->trackView->selectDisc(discs.first());
792 }
793 }
794
795 catch (FlaconError &err) {
796 if (showError)
797 showErrorMessage(err.what());
798 }
799 };
800
801 QApplication::setOverrideCursor(Qt::WaitCursor);
802 QFileInfo fi = QFileInfo(fileName);
803
804 if (fi.isDir()) {
805 mScanner = new Scanner;
806 setControlsEnable();
807 showError = false;
808 connect(mScanner, &Scanner::found, addFile);
809 mScanner->start(fi.absoluteFilePath());
810 delete mScanner;
811 mScanner = nullptr;
812 setControlsEnable();
813 }
814 else {
815 showError = true;
816 addFile(fileName);
817 }
818 QApplication::restoreOverrideCursor();
819 }
820
821 /************************************************
822
823 ************************************************/
removeDiscs()824 void MainWindow::removeDiscs()
825 {
826 QList<Disc *> discs = trackView->selectedDiscs();
827 if (discs.isEmpty())
828 return;
829
830 int n = project->indexOf(discs.first());
831 project->removeDisc(&discs);
832
833 n = qMin(n, project->count() - 1);
834 if (n > -1)
835 trackView->selectDisc(project->disc(n));
836
837 setControlsEnable();
838 }
839
840 /************************************************
841
842 ************************************************/
openScanDialog()843 void MainWindow::openScanDialog()
844 {
845 QString lastDir = Settings::i()->value(Settings::Misc_LastDir).toString();
846 QString dir = QFileDialog::getExistingDirectory(this, tr("Select directory"), lastDir);
847
848 if (!dir.isEmpty()) {
849 Settings::i()->setValue(Settings::Misc_LastDir, dir);
850 addFileOrDir(dir);
851 }
852 }
853
854 /************************************************
855
856 ************************************************/
openAboutDialog()857 void MainWindow::openAboutDialog()
858 {
859 AboutDialog dlg;
860 dlg.exec();
861 }
862
863 /************************************************
864
865 ************************************************/
checkUpdates()866 void MainWindow::checkUpdates()
867 {
868 #ifdef MAC_UPDATER
869 Updater::sharedUpdater().checkForUpdates("io.github.flacon");
870 #endif
871 }
872
873 /************************************************
874 *
875 ************************************************/
fillAudioMenu(Disc * disc,QMenu & menu)876 void MainWindow::fillAudioMenu(Disc *disc, QMenu &menu)
877 {
878 QAction *act;
879 if (disc->audioFiles().count() == 1) {
880 act = new QAction(tr("Select another audio file…", "context menu"), &menu);
881 connect(act, &QAction::triggered, [this, disc]() { this->setAudioForDisc(disc, 0); });
882 menu.addAction(act);
883 }
884 else {
885 int n = 0;
886 for (TrackPtrList &l : disc->tracksByFileTag()) {
887 QString msg;
888 if (l.count() == 1) {
889 msg = tr("Select another audio file for %1 track…", "context menu. Placeholders are track number")
890 .arg(l.first()->trackNum());
891 }
892 else {
893 msg = tr("Select another audio file for tracks %1 to %2…", "context menu. Placeholders are track numbers")
894 .arg(l.first()->trackNum())
895 .arg(l.last()->trackNum());
896 }
897
898 act = new QAction(msg, &menu);
899 connect(act, &QAction::triggered, [this, disc, n]() { this->setAudioForDisc(disc, n); });
900 menu.addAction(act);
901
902 n++;
903 }
904 }
905 }
906
907 /************************************************
908 *
909 ************************************************/
trackViewMenu(const QPoint & pos)910 void MainWindow::trackViewMenu(const QPoint &pos)
911 {
912 QModelIndex index = trackView->indexAt(pos);
913 if (!index.isValid())
914 return;
915
916 Disc *disc = trackView->model()->discByIndex(index);
917 if (!disc)
918 return;
919
920 QMenu menu;
921 QAction *act = new QAction(tr("Edit tags…", "context menu"), &menu);
922 connect(act, &QAction::triggered, this, &MainWindow::openEditTagsDialog);
923 menu.addAction(act);
924
925 menu.addSeparator();
926 fillAudioMenu(disc, menu);
927
928 act = new QAction(tr("Select another CUE file…", "context menu"), &menu);
929 connect(act, &QAction::triggered, [this, disc]() { this->setCueForDisc(disc); });
930 menu.addAction(act);
931
932 act = new QAction(tr("Get data from CDDB", "context menu"), &menu);
933 act->setEnabled(disc->canDownloadInfo());
934 connect(act, &QAction::triggered, [this, disc]() { this->downloadDiscInfo(disc); });
935 menu.addAction(act);
936
937 menu.exec(trackView->viewport()->mapToGlobal(pos));
938 }
939
940 /************************************************
941 *
942 ************************************************/
showDiskAudioFileMenu(Disc * disc,const QPoint & pos)943 void MainWindow::showDiskAudioFileMenu(Disc *disc, const QPoint &pos)
944 {
945 QMenu menu;
946 fillAudioMenu(disc, menu);
947 menu.exec(trackView->viewport()->mapToGlobal(pos));
948 }
949
950 /************************************************
951 *
952 ************************************************/
openEditTagsDialog()953 void MainWindow::openEditTagsDialog()
954 {
955 TagEditor editor(trackView->selectedTracks(), trackView->selectedDiscs(), this);
956 editor.exec();
957 refreshEdits();
958 }
959
960 /************************************************
961
962 ************************************************/
keyPressEvent(QKeyEvent * event)963 void MainWindow::keyPressEvent(QKeyEvent *event)
964 {
965 if (event->key() == Qt::Key_Escape) {
966 if (mScanner)
967 mScanner->stop();
968 }
969 }
970
971 /************************************************
972
973 ************************************************/
event(QEvent * event)974 bool MainWindow::event(QEvent *event)
975 {
976 switch (event->type()) {
977 #ifdef Q_OS_MAC
978 case QEvent::WindowActivate:
979 toolBar->setEnabled(true);
980 break;
981
982 case QEvent::WindowDeactivate:
983 toolBar->setEnabled(false);
984 break;
985 #endif
986 default:
987 break;
988 }
989 return QMainWindow::event(event);
990 }
991
992 /************************************************
993
994 ************************************************/
setStartTrackNum()995 void MainWindow::setStartTrackNum()
996 {
997 if (!tagStartNumEdit->isModified())
998 return;
999
1000 int value = tagStartNumEdit->value();
1001 QList<Disc *> discs = trackView->selectedDiscs();
1002 foreach (Disc *disc, discs) {
1003 disc->setStartTrackNum(value);
1004 }
1005 }
1006
1007 /************************************************
1008
1009 ************************************************/
initActions()1010 void MainWindow::initActions()
1011 {
1012 actionAddDisc->setIcon(Icon("add-disk"));
1013 connect(actionAddDisc, &QAction::triggered, this, &MainWindow::openAddFileDialog);
1014
1015 actionRemoveDisc->setIcon(Icon("remove-disk"));
1016 connect(actionRemoveDisc, &QAction::triggered, this, &MainWindow::removeDiscs);
1017
1018 actionAddFolder->setIcon(Icon("scan"));
1019 connect(actionAddFolder, &QAction::triggered, this, &MainWindow::openScanDialog);
1020
1021 actionDownloadTrackInfo->setIcon(Icon("download-info"));
1022 connect(actionDownloadTrackInfo, &QAction::triggered, this, &MainWindow::downloadInfo);
1023
1024 actionStartConvert->setIcon(Icon("start-convert"));
1025 connect(actionStartConvert, &QAction::triggered, this, &MainWindow::startConvertAll);
1026
1027 actionStartConvertSelected->setIcon(Icon("start-convert"));
1028 connect(actionStartConvertSelected, &QAction::triggered, this, &MainWindow::startConvertSelected);
1029
1030 actionAbortConvert->setIcon(Icon("abort-convert"));
1031 connect(actionAbortConvert, &QAction::triggered, this, &MainWindow::stopConvert);
1032
1033 actionConfigure->setIcon(Icon("configure"));
1034 connect(actionConfigure, &QAction::triggered, this, &MainWindow::configure);
1035 actionConfigure->setMenuRole(QAction::PreferencesRole);
1036
1037 actionConfigureEncoder->setIcon(actionConfigure->icon());
1038 connect(actionConfigureEncoder, &QAction::triggered, this, &MainWindow::configureEncoder);
1039
1040 connect(actionAbout, &QAction::triggered, this, &MainWindow::openAboutDialog);
1041 actionAbout->setMenuRole(QAction::AboutRole);
1042
1043 Controls::arangeTollBarButtonsWidth(toolBar);
1044
1045 #ifdef MAC_UPDATER
1046 actionUpdates->setVisible(true);
1047 actionUpdates->setMenuRole(QAction::ApplicationSpecificRole);
1048
1049 connect(actionUpdates, &QAction::triggered,
1050 this, &MainWindow::checkUpdates);
1051 #else
1052 actionUpdates->setVisible(false);
1053 #endif
1054 }
1055
1056 /************************************************
1057 Load settings
1058 ************************************************/
loadSettings()1059 void MainWindow::loadSettings()
1060 {
1061 // MainWindow geometry
1062 int width = Settings::i()->value(Settings::MainWindow_Width, QVariant(987)).toInt();
1063 int height = Settings::i()->value(Settings::MainWindow_Height, QVariant(450)).toInt();
1064 this->resize(width, height);
1065
1066 splitter->restoreState(Settings::i()->value("MainWindow/Splitter").toByteArray());
1067 trackView->header()->restoreState(Settings::i()->value("MainWindow/TrackView").toByteArray());
1068
1069 outDirEdit->setHistory(Settings::i()->value(Settings::OutFiles_DirectoryHistory).toStringList());
1070 outPatternEdit->setHistory(Settings::i()->value(Settings::OutFiles_PatternHistory).toStringList());
1071 }
1072
1073 /************************************************
1074 Write settings
1075 ************************************************/
saveSettings()1076 void MainWindow::saveSettings()
1077 {
1078 Settings::i()->setValue("MainWindow/Width", QVariant(size().width()));
1079 Settings::i()->setValue("MainWindow/Height", QVariant(size().height()));
1080 Settings::i()->setValue("MainWindow/Splitter", QVariant(splitter->saveState()));
1081 Settings::i()->setValue("MainWindow/TrackView", QVariant(trackView->header()->saveState()));
1082
1083 Settings::i()->setValue(Settings::OutFiles_DirectoryHistory, outDirEdit->history());
1084 Settings::i()->setValue(Settings::OutFiles_PatternHistory, outPatternEdit->history());
1085 }
1086
1087 /************************************************
1088 *
1089 ************************************************/
loadMainIcon()1090 QIcon MainWindow::loadMainIcon()
1091 {
1092 if (QIcon::themeName() == "hicolor") {
1093 QStringList failback;
1094 failback << "oxygen";
1095 failback << "Tango";
1096 failback << "Prudence-icon";
1097 failback << "Humanity";
1098 failback << "elementary";
1099 failback << "gnome";
1100
1101 QDir usrDir("/usr/share/icons/");
1102 QDir usrLocalDir("/usr/local/share/icons/");
1103 foreach (QString s, failback) {
1104 if (usrDir.exists(s) || usrLocalDir.exists(s)) {
1105 QIcon::setThemeName(s);
1106 break;
1107 }
1108 }
1109 }
1110
1111 return QIcon::fromTheme("flacon", Icon("mainicon"));
1112 }
1113
1114 /************************************************
1115 *
1116 ************************************************/
showErrorMessage(const QString & message)1117 void MainWindow::showErrorMessage(const QString &message)
1118 {
1119 const QString name = "errorMessage";
1120 ErrorBox *box = this->findChild<ErrorBox *>(name);
1121 if (!box) {
1122 box = new ErrorBox(this);
1123 box->setObjectName(name);
1124 box->setWindowTitle(QObject::tr("Flacon", "Error"));
1125 box->setAttribute(Qt::WA_DeleteOnClose, true);
1126 }
1127
1128 QString msg = message;
1129 msg.replace('\n', "<br>\n");
1130 box->addMessage(msg);
1131 box->open();
1132 }
1133