Skip to content

Commit a84520a

Browse files
committed
feat(library): Add customizable date format option for Library and History
This commit introduces a new user preference to customize how dates are displayed across the Mixxx library and history views. This addresses a common user request for ISO 8601 compliance and regional date format preferences. Key changes: - **Core Utility**: Enhanced `datetime.h` with a new `DateFormat` enum and updated helper functions (`formatDate`, `formatDateTime`) to support multiple formats: - Native (System Locale) - YYYY-MM-DD (ISO 8601) - DD/MM/YYYY - MM/DD/YYYY - YYYY/MM/DD - **Preferences UI**: Added a new "Date Format" dropdown in the Library Preferences page (`DlgPrefLibrary`), allowing users to easily switch between formats. - **Backend Storage**: Added `kDateFormatConfigKey` to persist the user's choice in `mixxx.cfg`. - **Model Integration**: Updated `BaseTrackTableModel` to respect the global date format setting. This ensures that columns like "Date Added" and "Last Played" dynamically update to reflect the user's preference without requiring a restart. - **History Integration**: The History view's Timestamp column also adheres to the selected format, ensuring consistency across the application. Validation: - Tested locally. Fixes #15327
1 parent c4a64f1 commit a84520a

8 files changed

Lines changed: 186 additions & 52 deletions

File tree

src/library/basetracktablemodel.cpp

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,13 @@ void BaseTrackTableModel::setApplyPlayedTrackColor(bool apply) {
114114
s_bApplyPlayedTrackColor = apply;
115115
}
116116

117+
mixxx::DateFormat BaseTrackTableModel::s_dateFormat =
118+
BaseTrackTableModel::kDateFormatDefault;
119+
120+
void BaseTrackTableModel::setDateFormat(mixxx::DateFormat format) {
121+
s_dateFormat = format;
122+
}
123+
117124
BaseTrackTableModel::BaseTrackTableModel(
118125
QObject* parent,
119126
TrackCollectionManager* pTrackCollectionManager,
@@ -688,10 +695,10 @@ QVariant BaseTrackTableModel::roleValue(
688695
if (field == ColumnCache::COLUMN_PLAYLISTTRACKSTABLE_DATETIMEADDED) {
689696
// Timestamp column in history feature:
690697
// Use localized date/time format without text: "5/20/98 03:40 AM"
691-
return mixxx::displayLocalDateTime(dt);
698+
return mixxx::displayLocalDateTime(dt, s_dateFormat);
692699
}
693700
// For Date Added, use just the date: "5/20/98"
694-
return dt.date();
701+
return mixxx::formatDate(dt.date(), s_dateFormat);
695702
}
696703
case ColumnCache::COLUMN_LIBRARYTABLE_LAST_PLAYED_AT: {
697704
QDateTime lastPlayedAt;
@@ -717,7 +724,7 @@ QVariant BaseTrackTableModel::roleValue(
717724
if (role == Qt::ToolTipRole || role == kDataExportRole) {
718725
return dt;
719726
}
720-
return dt.date();
727+
return mixxx::formatDate(dt.date(), s_dateFormat);
721728
}
722729
case ColumnCache::COLUMN_LIBRARYTABLE_BPM: {
723730
mixxx::Bpm bpm;

src/library/basetracktablemodel.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#include "library/trackmodel.h"
1010
#include "track/track_decl.h"
1111
#include "util/color/colorpalette.h"
12+
#include "util/datetime.h"
1213

1314
class TrackCollectionManager;
1415

@@ -132,6 +133,9 @@ class BaseTrackTableModel : public QAbstractTableModel, public TrackModel {
132133
static constexpr bool kApplyPlayedTrackColorDefault = true;
133134
static void setApplyPlayedTrackColor(bool apply);
134135

136+
static constexpr mixxx::DateFormat kDateFormatDefault = mixxx::DateFormat::Native;
137+
static void setDateFormat(mixxx::DateFormat format);
138+
135139
protected:
136140
// Build a map from the column names to their indices
137141
// used by fieldIndex().
@@ -307,4 +311,5 @@ class BaseTrackTableModel : public QAbstractTableModel, public TrackModel {
307311
static std::optional<ColorPalette> s_keyColorPalette;
308312

309313
static bool s_bApplyPlayedTrackColor;
314+
static mixxx::DateFormat s_dateFormat;
310315
};

src/library/library_prefs.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,3 +114,8 @@ const ConfigKey mixxx::library::prefs::kTagFetcherApplyCoverConfigKey =
114114
ConfigKey{
115115
mixxx::library::prefs::kConfigGroup,
116116
QStringLiteral("TagFetcherApplyCover")};
117+
118+
const ConfigKey mixxx::library::prefs::kDateFormatConfigKey =
119+
ConfigKey{
120+
mixxx::library::prefs::kConfigGroup,
121+
QStringLiteral("DateFormat")};

src/library/library_prefs.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ extern const ConfigKey kTagFetcherApplyTagsConfigKey;
5858

5959
extern const ConfigKey kTagFetcherApplyCoverConfigKey;
6060

61+
extern const ConfigKey kDateFormatConfigKey;
62+
6163
} // namespace prefs
6264

6365
} // namespace library

src/preferences/dialog/dlgpreflibrary.cpp

Lines changed: 86 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,21 @@ DlgPrefLibrary::DlgPrefLibrary(
111111

112112
updateSearchLineEditHistoryOptions();
113113

114+
comboBox_dateFormat->addItem(tr("Native (System Default)"),
115+
static_cast<int>(mixxx::DateFormat::Native));
116+
comboBox_dateFormat->addItem(tr("YYYY-MM-DD"),
117+
static_cast<int>(mixxx::DateFormat::ISO8601));
118+
comboBox_dateFormat->addItem(tr("DD/MM/YYYY"),
119+
static_cast<int>(mixxx::DateFormat::DayMonthYear));
120+
comboBox_dateFormat->addItem(tr("MM/DD/YYYY"),
121+
static_cast<int>(mixxx::DateFormat::MonthDayYear));
122+
comboBox_dateFormat->addItem(tr("YYYY/MM/DD"),
123+
static_cast<int>(mixxx::DateFormat::YearMonthDay));
124+
connect(comboBox_dateFormat,
125+
QOverload<int>::of(&QComboBox::currentIndexChanged),
126+
this,
127+
&DlgPrefLibrary::slotDateFormatChanged);
128+
114129
connect(btn_library_font, &QAbstractButton::clicked, this, &DlgPrefLibrary::slotSelectFont);
115130

116131
// TODO(XXX) this string should be extracted from the soundsources
@@ -184,11 +199,12 @@ void DlgPrefLibrary::slotHide() {
184199
QMessageBox msgBox;
185200
msgBox.setIcon(QMessageBox::Warning);
186201
msgBox.setWindowTitle(tr("Music Directory Added"));
187-
msgBox.setText(tr("You added one or more music directories. The tracks in "
188-
"these directories won't be available until you rescan "
189-
"your library. Would you like to rescan now?"));
202+
msgBox.setText(tr(
203+
"You added one or more music directories. The tracks in "
204+
"these directories won't be available until you rescan "
205+
"your library. Would you like to rescan now?"));
190206
QPushButton* scanButton = msgBox.addButton(
191-
tr("Scan"), QMessageBox::AcceptRole);
207+
tr("Scan"), QMessageBox::AcceptRole);
192208
msgBox.addButton(QMessageBox::Cancel);
193209
msgBox.setDefaultButton(scanButton);
194210
msgBox.exec();
@@ -231,7 +247,7 @@ void DlgPrefLibrary::populateDirList() {
231247
dirList->setModel(&m_dirListModel);
232248
dirList->setCurrentIndex(m_dirListModel.index(0, 0));
233249
// reselect index if it still exists
234-
for (int i=0 ; i<m_dirListModel.rowCount() ; ++i) {
250+
for (int i = 0; i < m_dirListModel.rowCount(); ++i) {
235251
const QModelIndex index = m_dirListModel.index(i, 0);
236252
if (index.data().toString() == selected) {
237253
dirList->setCurrentIndex(index);
@@ -267,6 +283,12 @@ void DlgPrefLibrary::slotResetToDefaults() {
267283
comboBox_search_bpm_fuzzy_range->setCurrentIndex(
268284
comboBox_search_bpm_fuzzy_range->findData(kDefaultFuzzyRateRangePercent));
269285

286+
int defaultDateFormat = static_cast<int>(BaseTrackTableModel::kDateFormatDefault);
287+
int dateIndex = comboBox_dateFormat->findData(defaultDateFormat);
288+
if (dateIndex != -1) {
289+
comboBox_dateFormat->setCurrentIndex(dateIndex);
290+
}
291+
270292
checkBox_show_rhythmbox->setChecked(true);
271293
checkBox_show_banshee->setChecked(true);
272294
checkBox_show_itunes->setChecked(true);
@@ -297,18 +319,28 @@ void DlgPrefLibrary::slotUpdate() {
297319
kUseRelativePathOnExportConfigKey, false));
298320

299321
checkBox_show_rhythmbox->setChecked(m_pConfig->getValue(
300-
ConfigKey("[Library]","ShowRhythmboxLibrary"), true));
322+
ConfigKey("[Library]", "ShowRhythmboxLibrary"), true));
301323
checkBox_show_banshee->setChecked(m_pConfig->getValue(
302-
ConfigKey("[Library]","ShowBansheeLibrary"), true));
324+
ConfigKey("[Library]", "ShowBansheeLibrary"), true));
303325
checkBox_show_itunes->setChecked(m_pConfig->getValue(
304-
ConfigKey("[Library]","ShowITunesLibrary"), true));
326+
ConfigKey("[Library]", "ShowITunesLibrary"), true));
305327
checkBox_show_traktor->setChecked(m_pConfig->getValue(
306-
ConfigKey("[Library]","ShowTraktorLibrary"), true));
328+
ConfigKey("[Library]", "ShowTraktorLibrary"), true));
307329
checkBox_show_rekordbox->setChecked(m_pConfig->getValue(
308-
ConfigKey("[Library]","ShowRekordboxLibrary"), true));
330+
ConfigKey("[Library]", "ShowRekordboxLibrary"), true));
309331
checkBox_show_serato->setChecked(m_pConfig->getValue(
310332
ConfigKey("[Library]", "ShowSeratoLibrary"), true));
311333

334+
int dateFormat = m_pConfig->getValue(
335+
kDateFormatConfigKey,
336+
static_cast<int>(BaseTrackTableModel::kDateFormatDefault));
337+
int dateIndex = comboBox_dateFormat->findData(dateFormat);
338+
if (dateIndex != -1) {
339+
comboBox_dateFormat->setCurrentIndex(dateIndex);
340+
}
341+
// Ensure the static member is updated on startup/load
342+
BaseTrackTableModel::setDateFormat(static_cast<mixxx::DateFormat>(dateFormat));
343+
312344
switch (m_pConfig->getValue<int>(
313345
kTrackDoubleClickActionConfigKey,
314346
static_cast<int>(TrackDoubleClickAction::LoadToDeck))) {
@@ -403,9 +435,9 @@ void DlgPrefLibrary::resetLibraryFont() {
403435
}
404436

405437
void DlgPrefLibrary::slotAddDir() {
406-
QString fd = QFileDialog::getExistingDirectory(
407-
this, tr("Choose a music directory"),
408-
QStandardPaths::writableLocation(QStandardPaths::MusicLocation));
438+
QString fd = QFileDialog::getExistingDirectory(this,
439+
tr("Choose a music directory"),
440+
QStandardPaths::writableLocation(QStandardPaths::MusicLocation));
409441
if (!fd.isEmpty()) {
410442
if (m_pLibrary->requestAddDir(fd)) {
411443
populateDirList();
@@ -423,29 +455,29 @@ void DlgPrefLibrary::slotRemoveDir() {
423455
removeMsgBox.setWindowTitle(tr("Confirm Directory Removal"));
424456

425457
removeMsgBox.setText(tr(
426-
"Mixxx will no longer watch this directory for new tracks. "
427-
"What would you like to do with the tracks from this directory and "
428-
"subdirectories?"
429-
"<ul>"
430-
"<li>Hide all tracks from this directory and subdirectories.</li>"
431-
"<li>Delete all metadata for these tracks from Mixxx permanently.</li>"
432-
"<li>Leave the tracks unchanged in your library.</li>"
433-
"</ul>"
434-
"Hiding tracks saves their metadata in case you re-add them in the "
435-
"future."));
458+
"Mixxx will no longer watch this directory for new tracks. "
459+
"What would you like to do with the tracks from this directory and "
460+
"subdirectories?"
461+
"<ul>"
462+
"<li>Hide all tracks from this directory and subdirectories.</li>"
463+
"<li>Delete all metadata for these tracks from Mixxx permanently.</li>"
464+
"<li>Leave the tracks unchanged in your library.</li>"
465+
"</ul>"
466+
"Hiding tracks saves their metadata in case you re-add them in the "
467+
"future."));
436468
removeMsgBox.setInformativeText(tr(
437-
"Metadata means all track details (artist, title, playcount, etc.) as "
438-
"well as beatgrids, hotcues, and loops. This choice only affects the "
439-
"Mixxx library. No files on disk will be changed or deleted."));
469+
"Metadata means all track details (artist, title, playcount, etc.) as "
470+
"well as beatgrids, hotcues, and loops. This choice only affects the "
471+
"Mixxx library. No files on disk will be changed or deleted."));
440472

441473
QPushButton* cancelButton =
442474
removeMsgBox.addButton(QMessageBox::Cancel);
443475
QPushButton* hideAllButton = removeMsgBox.addButton(
444-
tr("Hide Tracks"), QMessageBox::AcceptRole);
476+
tr("Hide Tracks"), QMessageBox::AcceptRole);
445477
QPushButton* deleteAllButton = removeMsgBox.addButton(
446-
tr("Delete Track Metadata"), QMessageBox::AcceptRole);
478+
tr("Delete Track Metadata"), QMessageBox::AcceptRole);
447479
QPushButton* leaveUnchangedButton = removeMsgBox.addButton(
448-
tr("Leave Tracks Unchanged"), QMessageBox::AcceptRole);
480+
tr("Leave Tracks Unchanged"), QMessageBox::AcceptRole);
449481
Q_UNUSED(leaveUnchangedButton); // Only used in DEBUG_ASSERT
450482
removeMsgBox.setDefaultButton(cancelButton);
451483
removeMsgBox.exec();
@@ -487,7 +519,7 @@ void DlgPrefLibrary::slotRelocateDir() {
487519
}
488520

489521
QString fd = QFileDialog::getExistingDirectory(
490-
this, tr("Relink music directory to new location"), startDir);
522+
this, tr("Relink music directory to new location"), startDir);
491523

492524
if (!fd.isEmpty() && m_pLibrary->requestRelocateDir(currentFd, fd)) {
493525
populateDirList();
@@ -541,16 +573,16 @@ void DlgPrefLibrary::slotApply() {
541573
ConfigValue(checkBox_enable_search_history_shortcuts->isChecked()));
542574
updateSearchLineEditHistoryOptions();
543575

544-
m_pConfig->set(ConfigKey("[Library]","ShowRhythmboxLibrary"),
545-
ConfigValue((int)checkBox_show_rhythmbox->isChecked()));
546-
m_pConfig->set(ConfigKey("[Library]","ShowBansheeLibrary"),
547-
ConfigValue((int)checkBox_show_banshee->isChecked()));
548-
m_pConfig->set(ConfigKey("[Library]","ShowITunesLibrary"),
549-
ConfigValue((int)checkBox_show_itunes->isChecked()));
550-
m_pConfig->set(ConfigKey("[Library]","ShowTraktorLibrary"),
551-
ConfigValue((int)checkBox_show_traktor->isChecked()));
552-
m_pConfig->set(ConfigKey("[Library]","ShowRekordboxLibrary"),
553-
ConfigValue((int)checkBox_show_rekordbox->isChecked()));
576+
m_pConfig->set(ConfigKey("[Library]", "ShowRhythmboxLibrary"),
577+
ConfigValue((int)checkBox_show_rhythmbox->isChecked()));
578+
m_pConfig->set(ConfigKey("[Library]", "ShowBansheeLibrary"),
579+
ConfigValue((int)checkBox_show_banshee->isChecked()));
580+
m_pConfig->set(ConfigKey("[Library]", "ShowITunesLibrary"),
581+
ConfigValue((int)checkBox_show_itunes->isChecked()));
582+
m_pConfig->set(ConfigKey("[Library]", "ShowTraktorLibrary"),
583+
ConfigValue((int)checkBox_show_traktor->isChecked()));
584+
m_pConfig->set(ConfigKey("[Library]", "ShowRekordboxLibrary"),
585+
ConfigValue((int)checkBox_show_rekordbox->isChecked()));
554586
m_pConfig->set(ConfigKey("[Library]", "ShowSeratoLibrary"),
555587
ConfigValue((int)checkBox_show_serato->isChecked()));
556588

@@ -587,14 +619,14 @@ void DlgPrefLibrary::slotApply() {
587619
QFont font = m_pLibrary->getTrackTableFont();
588620
if (m_originalTrackTableFont != font) {
589621
m_pConfig->set(ConfigKey("[Library]", "Font"),
590-
ConfigValue(font.toString()));
622+
ConfigValue(font.toString()));
591623
m_originalTrackTableFont = font;
592624
}
593625

594626
int rowHeight = spinBox_row_height->value();
595627
if (m_iOriginalTrackTableRowHeight != rowHeight) {
596-
m_pConfig->set(ConfigKey("[Library]","RowHeight"),
597-
ConfigValue(rowHeight));
628+
m_pConfig->set(ConfigKey("[Library]", "RowHeight"),
629+
ConfigValue(rowHeight));
598630
m_iOriginalTrackTableRowHeight = rowHeight;
599631
}
600632

@@ -660,8 +692,10 @@ void DlgPrefLibrary::setLibraryFont(const QFont& font) {
660692
void DlgPrefLibrary::slotSelectFont() {
661693
// False if the user cancels font selection.
662694
bool ok = false;
663-
QFont font = QFontDialog::getFont(&ok, m_pLibrary->getTrackTableFont(),
664-
this, tr("Select Library Font"));
695+
QFont font = QFontDialog::getFont(&ok,
696+
m_pLibrary->getTrackTableFont(),
697+
this,
698+
tr("Select Library Font"));
665699
if (ok) {
666700
setLibraryFont(font);
667701
}
@@ -712,3 +746,10 @@ void DlgPrefLibrary::setSeratoMetadataEnabled(bool shouldSyncTrackMetadata) {
712746
checkBox_serato_metadata_export->setChecked(false);
713747
}
714748
}
749+
750+
void DlgPrefLibrary::slotDateFormatChanged(int index) {
751+
int formatInt = comboBox_dateFormat->itemData(index).toInt();
752+
mixxx::DateFormat format = static_cast<mixxx::DateFormat>(formatInt);
753+
m_pConfig->setValue(kDateFormatConfigKey, formatInt);
754+
BaseTrackTableModel::setDateFormat(format);
755+
}

src/preferences/dialog/dlgpreflibrary.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
class QWidget;
1414
class ControlProxy;
1515

16-
class DlgPrefLibrary : public DlgPreferencePage, public Ui::DlgPrefLibraryDlg {
16+
class DlgPrefLibrary : public DlgPreferencePage, public Ui::DlgPrefLibraryDlg {
1717
Q_OBJECT
1818
public:
1919
enum class TrackDoubleClickAction : int {
@@ -67,6 +67,7 @@ class DlgPrefLibrary : public DlgPreferencePage, public Ui::DlgPrefLibraryDlg {
6767
void slotBpmRangeSelected(int index);
6868
void slotBpmColumnPrecisionChanged(int bpmPrecision);
6969
void slotSeratoMetadataExportClicked(bool);
70+
void slotDateFormatChanged(int index);
7071

7172
private:
7273
void populateDirList();

src/preferences/dialog/dlgpreflibrarydlg.ui

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,6 +298,20 @@
298298
</widget>
299299
</item>
300300

301+
<item row="11" column="0">
302+
<widget class="QLabel" name="label_dateFormat">
303+
<property name="text">
304+
<string>Date Format:</string>
305+
</property>
306+
<property name="alignment">
307+
<set>Qt::AlignLeft|Qt::AlignVCenter</set>
308+
</property>
309+
</widget>
310+
</item>
311+
<item row="11" column="1" colspan="2">
312+
<widget class="QComboBox" name="comboBox_dateFormat"/>
313+
</item>
314+
301315
</layout>
302316
</widget>
303317
</item><!-- Track Table View -->
@@ -695,6 +709,7 @@
695709
<tabstop>btn_library_font</tabstop>
696710
<tabstop>spinbox_bpm_precision</tabstop>
697711
<tabstop>checkbox_played_track_color</tabstop>
712+
<tabstop>comboBox_dateFormat</tabstop>
698713
<tabstop>spinBox_search_debouncing_timeout</tabstop>
699714
<tabstop>checkBox_enable_search_completions</tabstop>
700715
<tabstop>checkBox_enable_search_history_shortcuts</tabstop>

0 commit comments

Comments
 (0)