1 #include "preferences/dialog/dlgpreflibrary.h"
2
3 #include <QApplication>
4 #include <QDesktopServices>
5 #include <QDir>
6 #include <QFileDialog>
7 #include <QFontDialog>
8 #include <QFontMetrics>
9 #include <QMessageBox>
10 #include <QStandardPaths>
11 #include <QStringList>
12 #include <QUrl>
13
14 #include "library/dlgtrackmetadataexport.h"
15 #include "moc_dlgpreflibrary.cpp"
16 #include "sources/soundsourceproxy.h"
17 #include "widget/wsearchlineedit.h"
18
19 namespace {
20 const ConfigKey kSearchDebouncingTimeoutMillisKey = ConfigKey("[Library]","SearchDebouncingTimeoutMillis");
21 } // namespace
22
DlgPrefLibrary(QWidget * pParent,UserSettingsPointer pConfig,Library * pLibrary)23 DlgPrefLibrary::DlgPrefLibrary(
24 QWidget* pParent,
25 UserSettingsPointer pConfig,
26 Library* pLibrary)
27 : DlgPreferencePage(pParent),
28 m_dirListModel(),
29 m_pConfig(pConfig),
30 m_pLibrary(pLibrary),
31 m_bAddedDirectory(false),
32 m_iOriginalTrackTableRowHeight(Library::kDefaultRowHeightPx) {
33 setupUi(this);
34
35 connect(this,
36 &DlgPrefLibrary::requestAddDir,
37 m_pLibrary,
38 &Library::slotRequestAddDir);
39 connect(this,
40 &DlgPrefLibrary::requestRemoveDir,
41 m_pLibrary,
42 &Library::slotRequestRemoveDir);
43 connect(this,
44 &DlgPrefLibrary::requestRelocateDir,
45 m_pLibrary,
46 &Library::slotRequestRelocateDir);
47 connect(PushButtonAddDir,
48 &QPushButton::clicked,
49 this,
50 &DlgPrefLibrary::slotAddDir);
51 connect(PushButtonRemoveDir,
52 &QPushButton::clicked,
53 this,
54 &DlgPrefLibrary::slotRemoveDir);
55 connect(PushButtonRelocateDir,
56 &QPushButton::clicked,
57 this,
58 &DlgPrefLibrary::slotRelocateDir);
59 connect(checkBox_SeratoMetadataExport,
60 &QAbstractButton::clicked,
61 this,
62 &DlgPrefLibrary::slotSeratoMetadataExportClicked);
63 const QString& settingsDir = m_pConfig->getSettingsPath();
64 connect(PushButtonOpenSettingsDir,
65 &QPushButton::clicked,
66 [settingsDir] {
67 QDesktopServices::openUrl(QUrl::fromLocalFile(settingsDir));
68 });
69
70 // Set default direction as stored in config file
71 int rowHeight = m_pLibrary->getTrackTableRowHeight();
72 spinBoxRowHeight->setValue(rowHeight);
73 connect(spinBoxRowHeight,
74 QOverload<int>::of(&QSpinBox::valueChanged),
75 this,
76 &DlgPrefLibrary::slotRowHeightValueChanged);
77
78 searchDebouncingTimeoutSpinBox->setMinimum(WSearchLineEdit::kMinDebouncingTimeoutMillis);
79 searchDebouncingTimeoutSpinBox->setMaximum(WSearchLineEdit::kMaxDebouncingTimeoutMillis);
80 const auto searchDebouncingTimeoutMillis =
81 m_pConfig->getValue(
82 ConfigKey("[Library]","SearchDebouncingTimeoutMillis"),
83 WSearchLineEdit::kDefaultDebouncingTimeoutMillis);
84 searchDebouncingTimeoutSpinBox->setValue(searchDebouncingTimeoutMillis);
85 connect(searchDebouncingTimeoutSpinBox,
86 QOverload<int>::of(&QSpinBox::valueChanged),
87 this,
88 &DlgPrefLibrary::slotSearchDebouncingTimeoutMillisChanged);
89
90 connect(libraryFontButton, &QAbstractButton::clicked, this, &DlgPrefLibrary::slotSelectFont);
91
92 // TODO(XXX) this string should be extracted from the soundsources
93 QString builtInFormatsStr = "Ogg Vorbis, FLAC, WAVE, AIFF";
94 #if defined(__MAD__) || defined(__COREAUDIO__)
95 builtInFormatsStr += ", MP3";
96 #endif
97 #if defined(__MEDIAFOUNDATION__) || defined(__COREAUDIO__) || defined(__FAAD__)
98 builtInFormatsStr += ", M4A/MP4";
99 #endif
100 #ifdef __OPUS__
101 builtInFormatsStr += ", Opus";
102 #endif
103 #ifdef __MODPLUG__
104 builtInFormatsStr += ", ModPlug";
105 #endif
106 #ifdef __WV__
107 builtInFormatsStr += ", WavPack";
108 #endif
109 builtInFormats->setText(builtInFormatsStr);
110
111 connect(checkBox_SyncTrackMetadataExport,
112 &QCheckBox::toggled,
113 this,
114 &DlgPrefLibrary::slotSyncTrackMetadataExportToggled);
115
116 // Initialize the controls after all slots have been connected
117 slotUpdate();
118 }
119
slotShow()120 void DlgPrefLibrary::slotShow() {
121 m_bAddedDirectory = false;
122 }
123
slotHide()124 void DlgPrefLibrary::slotHide() {
125 if (!m_bAddedDirectory) {
126 return;
127 }
128
129 QMessageBox msgBox;
130 msgBox.setIcon(QMessageBox::Warning);
131 msgBox.setWindowTitle(tr("Music Directory Added"));
132 msgBox.setText(tr("You added one or more music directories. The tracks in "
133 "these directories won't be available until you rescan "
134 "your library. Would you like to rescan now?"));
135 QPushButton* scanButton = msgBox.addButton(
136 tr("Scan"), QMessageBox::AcceptRole);
137 msgBox.addButton(QMessageBox::Cancel);
138 msgBox.setDefaultButton(scanButton);
139 msgBox.exec();
140
141 if (msgBox.clickedButton() == scanButton) {
142 emit scanLibrary();
143 return;
144 }
145 }
146
helpUrl() const147 QUrl DlgPrefLibrary::helpUrl() const {
148 return QUrl(MIXXX_MANUAL_LIBRARY_URL);
149 }
150
initializeDirList()151 void DlgPrefLibrary::initializeDirList() {
152 // save which index was selected
153 const QString selected = dirList->currentIndex().data().toString();
154 // clear and fill model
155 m_dirListModel.clear();
156 QStringList dirs = m_pLibrary->getDirs();
157 foreach (QString dir, dirs) {
158 m_dirListModel.appendRow(new QStandardItem(dir));
159 }
160 dirList->setModel(&m_dirListModel);
161 dirList->setCurrentIndex(m_dirListModel.index(0, 0));
162 // reselect index if it still exists
163 for (int i=0 ; i<m_dirListModel.rowCount() ; ++i) {
164 const QModelIndex index = m_dirListModel.index(i, 0);
165 if (index.data().toString() == selected) {
166 dirList->setCurrentIndex(index);
167 break;
168 }
169 }
170 }
171
slotResetToDefaults()172 void DlgPrefLibrary::slotResetToDefaults() {
173 checkBox_library_scan->setChecked(false);
174 checkBox_SyncTrackMetadataExport->setChecked(false);
175 checkBox_SeratoMetadataExport->setChecked(false);
176 checkBox_use_relative_path->setChecked(false);
177 checkBox_show_rhythmbox->setChecked(true);
178 checkBox_show_banshee->setChecked(true);
179 checkBox_show_itunes->setChecked(true);
180 checkBox_show_traktor->setChecked(true);
181 checkBox_show_rekordbox->setChecked(true);
182 radioButton_dbclick_bottom->setChecked(false);
183 checkBoxEditMetadataSelectedClicked->setChecked(PREF_LIBRARY_EDIT_METADATA_DEFAULT);
184 radioButton_dbclick_top->setChecked(false);
185 radioButton_dbclick_deck->setChecked(true);
186 spinBoxRowHeight->setValue(Library::kDefaultRowHeightPx);
187 setLibraryFont(QApplication::font());
188 }
189
slotUpdate()190 void DlgPrefLibrary::slotUpdate() {
191 initializeDirList();
192 checkBox_library_scan->setChecked(m_pConfig->getValue(
193 ConfigKey("[Library]","RescanOnStartup"), false));
194 checkBox_SyncTrackMetadataExport->setChecked(m_pConfig->getValue(
195 ConfigKey("[Library]","SyncTrackMetadataExport"), false));
196 checkBox_SeratoMetadataExport->setChecked(m_pConfig->getValue(
197 ConfigKey("[Library]", "SeratoMetadataExport"), false));
198 checkBox_use_relative_path->setChecked(m_pConfig->getValue(
199 ConfigKey("[Library]","UseRelativePathOnExport"), false));
200 checkBox_show_rhythmbox->setChecked(m_pConfig->getValue(
201 ConfigKey("[Library]","ShowRhythmboxLibrary"), true));
202 checkBox_show_banshee->setChecked(m_pConfig->getValue(
203 ConfigKey("[Library]","ShowBansheeLibrary"), true));
204 checkBox_show_itunes->setChecked(m_pConfig->getValue(
205 ConfigKey("[Library]","ShowITunesLibrary"), true));
206 checkBox_show_traktor->setChecked(m_pConfig->getValue(
207 ConfigKey("[Library]","ShowTraktorLibrary"), true));
208 checkBox_show_rekordbox->setChecked(m_pConfig->getValue(
209 ConfigKey("[Library]","ShowRekordboxLibrary"), true));
210 checkBox_show_serato->setChecked(m_pConfig->getValue(
211 ConfigKey("[Library]", "ShowSeratoLibrary"), true));
212
213 switch (m_pConfig->getValue<int>(
214 ConfigKey("[Library]", "TrackLoadAction"),
215 static_cast<int>(TrackDoubleClickAction::LoadToDeck))) {
216 case static_cast<int>(TrackDoubleClickAction::AddToAutoDJBottom):
217 radioButton_dbclick_bottom->setChecked(true);
218 break;
219 case static_cast<int>(TrackDoubleClickAction::AddToAutoDJTop):
220 radioButton_dbclick_top->setChecked(true);
221 break;
222 case static_cast<int>(TrackDoubleClickAction::Ignore):
223 radioButton_dbclick_ignore->setChecked(true);
224 break;
225 default:
226 radioButton_dbclick_deck->setChecked(true);
227 break;
228 }
229
230 bool editMetadataSelectedClick = m_pConfig->getValue(
231 ConfigKey("[Library]","EditMetadataSelectedClick"),
232 PREF_LIBRARY_EDIT_METADATA_DEFAULT);
233 checkBoxEditMetadataSelectedClicked->setChecked(editMetadataSelectedClick);
234 m_pLibrary->setEditMedatataSelectedClick(editMetadataSelectedClick);
235
236 m_originalTrackTableFont = m_pLibrary->getTrackTableFont();
237 m_iOriginalTrackTableRowHeight = m_pLibrary->getTrackTableRowHeight();
238 spinBoxRowHeight->setValue(m_iOriginalTrackTableRowHeight);
239 setLibraryFont(m_originalTrackTableFont);
240 }
241
slotCancel()242 void DlgPrefLibrary::slotCancel() {
243 // Undo any changes in the library font or row height.
244 m_pLibrary->setFont(m_originalTrackTableFont);
245 m_pLibrary->setRowHeight(m_iOriginalTrackTableRowHeight);
246 }
247
slotAddDir()248 void DlgPrefLibrary::slotAddDir() {
249 QString fd = QFileDialog::getExistingDirectory(
250 this, tr("Choose a music directory"),
251 QStandardPaths::writableLocation(QStandardPaths::MusicLocation));
252 if (!fd.isEmpty()) {
253 emit requestAddDir(fd);
254 slotUpdate();
255 m_bAddedDirectory = true;
256 }
257 }
258
slotRemoveDir()259 void DlgPrefLibrary::slotRemoveDir() {
260 QModelIndex index = dirList->currentIndex();
261 QString fd = index.data().toString();
262 QMessageBox removeMsgBox;
263
264 removeMsgBox.setIcon(QMessageBox::Warning);
265 removeMsgBox.setWindowTitle(tr("Confirm Directory Removal"));
266
267 removeMsgBox.setText(tr(
268 "Mixxx will no longer watch this directory for new tracks. "
269 "What would you like to do with the tracks from this directory and "
270 "subdirectories?"
271 "<ul>"
272 "<li>Hide all tracks from this directory and subdirectories.</li>"
273 "<li>Delete all metadata for these tracks from Mixxx permanently.</li>"
274 "<li>Leave the tracks unchanged in your library.</li>"
275 "</ul>"
276 "Hiding tracks saves their metadata in case you re-add them in the "
277 "future."));
278 removeMsgBox.setInformativeText(tr(
279 "Metadata means all track details (artist, title, playcount, etc.) as "
280 "well as beatgrids, hotcues, and loops. This choice only affects the "
281 "Mixxx library. No files on disk will be changed or deleted."));
282
283 QPushButton* cancelButton =
284 removeMsgBox.addButton(QMessageBox::Cancel);
285 QPushButton* hideAllButton = removeMsgBox.addButton(
286 tr("Hide Tracks"), QMessageBox::AcceptRole);
287 QPushButton* deleteAllButton = removeMsgBox.addButton(
288 tr("Delete Track Metadata"), QMessageBox::AcceptRole);
289 QPushButton* leaveUnchangedButton = removeMsgBox.addButton(
290 tr("Leave Tracks Unchanged"), QMessageBox::AcceptRole);
291 Q_UNUSED(leaveUnchangedButton); // Only used in DEBUG_ASSERT
292 removeMsgBox.setDefaultButton(cancelButton);
293 removeMsgBox.exec();
294
295 if (removeMsgBox.clickedButton() == cancelButton) {
296 return;
297 }
298
299 Library::RemovalType removalType;
300 if (removeMsgBox.clickedButton() == hideAllButton) {
301 removalType = Library::RemovalType::HideTracks;
302 } else if (removeMsgBox.clickedButton() == deleteAllButton) {
303 removalType = Library::RemovalType::PurgeTracks;
304 } else {
305 DEBUG_ASSERT(removeMsgBox.clickedButton() == leaveUnchangedButton);
306 removalType = Library::RemovalType::KeepTracks;
307 }
308
309 emit requestRemoveDir(fd, removalType);
310 slotUpdate();
311 }
312
slotRelocateDir()313 void DlgPrefLibrary::slotRelocateDir() {
314 QModelIndex index = dirList->currentIndex();
315 QString currentFd = index.data().toString();
316
317 // If the selected directory exists, use it. If not, go up one directory (if
318 // that directory exists). If neither exist, use the default music
319 // directory.
320 QString startDir = currentFd;
321 QDir dir(startDir);
322 if (!dir.exists() && dir.cdUp()) {
323 startDir = dir.absolutePath();
324 } else if (!dir.exists()) {
325 startDir = QStandardPaths::writableLocation(QStandardPaths::MusicLocation);
326 }
327
328 QString fd = QFileDialog::getExistingDirectory(
329 this, tr("Relink music directory to new location"), startDir);
330
331 if (!fd.isEmpty()) {
332 emit requestRelocateDir(currentFd, fd);
333 slotUpdate();
334 }
335 }
336
slotSeratoMetadataExportClicked(bool checked)337 void DlgPrefLibrary::slotSeratoMetadataExportClicked(bool checked) {
338 if (checked) {
339 if (QMessageBox::warning(this,
340 QStringLiteral("Serato Metadata Export"),
341 QStringLiteral(
342 "Exporting Serato Metadata from Mixxx is "
343 "experimental. There is no official documentation "
344 "of the format. Existing Serato Metadata might be "
345 "lost and files with Serato metadata written by "
346 "Mixxx could potentially crash Serato DJ, "
347 "therefore caution is advised and backups are "
348 "recommended. Are you sure you want to enable this "
349 "option?"),
350 QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) {
351 checkBox_SeratoMetadataExport->setChecked(false);
352 }
353 }
354 }
355
slotApply()356 void DlgPrefLibrary::slotApply() {
357 m_pConfig->set(ConfigKey("[Library]","RescanOnStartup"),
358 ConfigValue((int)checkBox_library_scan->isChecked()));
359 m_pConfig->set(ConfigKey("[Library]","SyncTrackMetadataExport"),
360 ConfigValue((int)checkBox_SyncTrackMetadataExport->isChecked()));
361 m_pConfig->set(ConfigKey("[Library]", "SeratoMetadataExport"),
362 ConfigValue(static_cast<int>(checkBox_SeratoMetadataExport->isChecked())));
363 m_pConfig->set(ConfigKey("[Library]","UseRelativePathOnExport"),
364 ConfigValue((int)checkBox_use_relative_path->isChecked()));
365 m_pConfig->set(ConfigKey("[Library]","ShowRhythmboxLibrary"),
366 ConfigValue((int)checkBox_show_rhythmbox->isChecked()));
367 m_pConfig->set(ConfigKey("[Library]","ShowBansheeLibrary"),
368 ConfigValue((int)checkBox_show_banshee->isChecked()));
369 m_pConfig->set(ConfigKey("[Library]","ShowITunesLibrary"),
370 ConfigValue((int)checkBox_show_itunes->isChecked()));
371 m_pConfig->set(ConfigKey("[Library]","ShowTraktorLibrary"),
372 ConfigValue((int)checkBox_show_traktor->isChecked()));
373 m_pConfig->set(ConfigKey("[Library]","ShowRekordboxLibrary"),
374 ConfigValue((int)checkBox_show_rekordbox->isChecked()));
375 m_pConfig->set(ConfigKey("[Library]", "ShowSeratoLibrary"),
376 ConfigValue((int)checkBox_show_serato->isChecked()));
377 int dbclick_status;
378 if (radioButton_dbclick_bottom->isChecked()) {
379 dbclick_status = static_cast<int>(TrackDoubleClickAction::AddToAutoDJBottom);
380 } else if (radioButton_dbclick_top->isChecked()) {
381 dbclick_status = static_cast<int>(TrackDoubleClickAction::AddToAutoDJTop);
382 } else if (radioButton_dbclick_deck->isChecked()) {
383 dbclick_status = static_cast<int>(TrackDoubleClickAction::LoadToDeck);
384 } else { // radioButton_dbclick_ignore
385 dbclick_status = static_cast<int>(TrackDoubleClickAction::Ignore);
386 }
387 m_pConfig->set(ConfigKey("[Library]","TrackLoadAction"),
388 ConfigValue(dbclick_status));
389
390 m_pConfig->set(ConfigKey("[Library]", "EditMetadataSelectedClick"),
391 ConfigValue(checkBoxEditMetadataSelectedClicked->checkState()));
392 m_pLibrary->setEditMedatataSelectedClick(
393 checkBoxEditMetadataSelectedClicked->checkState());
394
395 QFont font = m_pLibrary->getTrackTableFont();
396 if (m_originalTrackTableFont != font) {
397 m_pConfig->set(ConfigKey("[Library]", "Font"),
398 ConfigValue(font.toString()));
399 }
400
401 int rowHeight = spinBoxRowHeight->value();
402 if (m_iOriginalTrackTableRowHeight != rowHeight) {
403 m_pConfig->set(ConfigKey("[Library]","RowHeight"),
404 ConfigValue(rowHeight));
405 }
406
407 // TODO(rryan): Don't save here.
408 m_pConfig->save();
409 }
410
slotRowHeightValueChanged(int height)411 void DlgPrefLibrary::slotRowHeightValueChanged(int height) {
412 m_pLibrary->setRowHeight(height);
413 }
414
setLibraryFont(const QFont & font)415 void DlgPrefLibrary::setLibraryFont(const QFont& font) {
416 libraryFont->setText(QString("%1 %2 %3pt").arg(
417 font.family(), font.styleName(), QString::number(font.pointSizeF())));
418 m_pLibrary->setFont(font);
419
420 // Don't let the font height exceed the row height.
421 QFontMetrics metrics(font);
422 int fontHeight = metrics.height();
423 spinBoxRowHeight->setMinimum(fontHeight);
424 // library.cpp takes care of setting the new row height according to the
425 // previous font height/ row height ratio
426 spinBoxRowHeight->setValue(m_pLibrary->getTrackTableRowHeight());
427 }
428
slotSelectFont()429 void DlgPrefLibrary::slotSelectFont() {
430 // False if the user cancels font selection.
431 bool ok = false;
432 QFont font = QFontDialog::getFont(&ok, m_pLibrary->getTrackTableFont(),
433 this, tr("Select Library Font"));
434 if (ok) {
435 setLibraryFont(font);
436 }
437 }
438
slotSearchDebouncingTimeoutMillisChanged(int searchDebouncingTimeoutMillis)439 void DlgPrefLibrary::slotSearchDebouncingTimeoutMillisChanged(int searchDebouncingTimeoutMillis) {
440 m_pConfig->setValue(
441 kSearchDebouncingTimeoutMillisKey,
442 searchDebouncingTimeoutMillis);
443 WSearchLineEdit::setDebouncingTimeoutMillis(searchDebouncingTimeoutMillis);
444 }
445
slotSyncTrackMetadataExportToggled()446 void DlgPrefLibrary::slotSyncTrackMetadataExportToggled() {
447 if (isVisible() && checkBox_SyncTrackMetadataExport->isChecked()) {
448 mixxx::DlgTrackMetadataExport::showMessageBoxOncePerSession();
449 }
450 }
451