diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9c40ae23148d..b8a90d7905bb 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -341,7 +341,7 @@ jobs: env: AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} if: runner.os == 'Windows' && env.AZURE_TENANT_ID - uses: azure/trusted-signing-action@v0.5.10 + uses: azure/trusted-signing-action@v1.0.0 with: azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }} azure-client-id: ${{ secrets.AZURE_CLIENT_ID }} @@ -407,7 +407,7 @@ jobs: env: AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} if: runner.os == 'Windows' && env.AZURE_TENANT_ID - uses: azure/trusted-signing-action@v0.5.10 + uses: azure/trusted-signing-action@v1.0.0 with: azure-tenant-id: ${{ secrets.AZURE_TENANT_ID }} azure-client-id: ${{ secrets.AZURE_CLIENT_ID }} diff --git a/COPYING b/COPYING index b84655a62c0f..5914766cf2e1 100644 --- a/COPYING +++ b/COPYING @@ -1,3 +1,3 @@ -Mixxx is Copyright (C) 2000-2024 by its respective authors. This version +Mixxx is Copyright (C) 2000-2026 by its respective authors. This version of the program is distributed under the General Public License version 2, as described in the file LICENSE distributed with the program. diff --git a/LICENSE b/LICENSE index c6f0a9a4d1bd..5ac5e233fdf5 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ Mixxx 2.5.4, Digital DJ'ing software. -Copyright (C) 2001-2025 Mixxx Development Team +Copyright (C) 2001-2026 Mixxx Development Team Mixxx is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by diff --git a/packaging/debian/copyright b/packaging/debian/copyright index 9a8a6dde5034..19e042816d81 100644 --- a/packaging/debian/copyright +++ b/packaging/debian/copyright @@ -6,7 +6,7 @@ Source: https://downloads.mixxx.org/ Files: * Copyright: - 2001-2025 Mixxx development team + 2001-2026 Mixxx development team License: GPL-2+ License: GPL-2+ diff --git a/packaging/wix/LICENSE.rtf.in b/packaging/wix/LICENSE.rtf.in index 8b2cbf40480e..b98cbf43310a 100644 --- a/packaging/wix/LICENSE.rtf.in +++ b/packaging/wix/LICENSE.rtf.in @@ -2,7 +2,7 @@ {\colortbl ;\red0\green0\blue255;} {\*\generator Riched20 10.0.14393}\viewkind4\uc1 \pard\qj\ul\b\f0\fs22\lang1036 Mixxx @CMAKE_PROJECT_VERSION@, Digital DJ'ing software.\ulnone\b0\fs24\par -\fs22 Copyright (C) 2001-2025 Mixxx Development Team\par +\fs22 Copyright (C) 2001-2026 Mixxx Development Team\par \par Promotional tracks are copyright their respective owners and\par distributed with permission.\par diff --git a/src/library/basesqltablemodel.cpp b/src/library/basesqltablemodel.cpp index 8f35c7bd0425..4d5fdaf853a4 100644 --- a/src/library/basesqltablemodel.cpp +++ b/src/library/basesqltablemodel.cpp @@ -844,7 +844,59 @@ void BaseSqlTableModel::hideTracks(const QModelIndexList& indices) { // TODO(rryan) : do not select, instead route event to BTC and notify from // there. - select(); //Repopulate the data model. + + QSet tracksRemovedSet = QSet(trackIds.begin(), trackIds.end()); + removeTrackRows(tracksRemovedSet); +} + +void BaseSqlTableModel::removeTrackRows(const QSet& trackIdsToRemove) { + // This is called after hiding, purging or removing tracks from a track set. + // The purpose is to keep the tracks view constant after after these + // operations by avoiding pointless/confusing re-sorting of the model, + // which happens when we call select(), which rebuilds the row cache. + // We assume metadata of remaining tracks hasn't changed, we can simply + // remove the respective track rows. + + // However, if this is a playlist model, track removal likely also changes + // the tracks' positions in the playlist. We need to get the tracks' new + // positions from the database, so let's do a select(). + // FIXME Update position without re-sorting + if (hasPositionColumn()) { + select(); + return; + } + + // For other models we can now remove all track rows. + QVector rowInfos = m_rowInfo; + TrackId2Rows trackIdToRows; + TrackPos2Row trackPosToRows; // remains empty + + QMutableListIterator it(rowInfos); + while (it.hasNext()) { + const RowInfo& rowInfo = it.next(); + if (trackIdsToRemove.contains(rowInfo.trackId)) { + it.remove(); + } + } + + // Recreate TrackId2Rows, taken from select() + trackIdToRows.reserve(rowInfos.size()); + for (int i = 0; i < rowInfos.size(); ++i) { + const RowInfo& rowInfo = rowInfos[i]; + if (rowInfo.row == -1) { + // We've reached the end of valid rows. Resize rowInfo to cut off + // this and all further elements. + rowInfos.resize(i); + break; + } + trackIdToRows[rowInfo.trackId].push_back(i); + } + + clearRows(); + replaceRows( + std::move(rowInfos), + std::move(trackIdToRows), + std::move(trackPosToRows)); } QList BaseSqlTableModel::getTrackRefs( diff --git a/src/library/basesqltablemodel.h b/src/library/basesqltablemodel.h index 07926513f8bf..b327fef7d30e 100644 --- a/src/library/basesqltablemodel.h +++ b/src/library/basesqltablemodel.h @@ -67,6 +67,7 @@ class BaseSqlTableModel : public BaseTrackTableModel { int columnIndexFromSortColumnId(TrackModel::SortColumnId sortColumn) const override; void hideTracks(const QModelIndexList& indices) override; + void removeTrackRows(const QSet& trackIdsToRemove) override; void select() override; diff --git a/src/library/basetracktablemodel.cpp b/src/library/basetracktablemodel.cpp index bef134cddc5b..093d8f6d96ff 100644 --- a/src/library/basetracktablemodel.cpp +++ b/src/library/basetracktablemodel.cpp @@ -107,9 +107,9 @@ BaseTrackTableModel::BaseTrackTableModel( m_trackPlayedColor(QColor(WTrackTableView::kDefaultTrackPlayedColor)), m_trackMissingColor(QColor(WTrackTableView::kDefaultTrackMissingColor)) { connect(&pTrackCollectionManager->internalCollection()->getTrackDAO(), - &TrackDAO::forceModelUpdate, + &TrackDAO::tracksRemoved, this, - &BaseTrackTableModel::slotRefreshAllRows); + &BaseTrackTableModel::slotTracksRemoved); connect(&PlayerInfo::instance(), &PlayerInfo::trackChanged, this, @@ -989,6 +989,10 @@ void BaseTrackTableModel::slotRefreshAllRows() { select(); } +void BaseTrackTableModel::slotTracksRemoved(const QSet& trackIds) { + removeTrackRows(trackIds); +} + void BaseTrackTableModel::emitDataChangedForMultipleRowsInColumn( const QList& rows, int column, diff --git a/src/library/basetracktablemodel.h b/src/library/basetracktablemodel.h index 7947e783b4ed..02cd41af3b4c 100644 --- a/src/library/basetracktablemodel.h +++ b/src/library/basetracktablemodel.h @@ -248,6 +248,8 @@ class BaseTrackTableModel : public QAbstractTableModel, public TrackModel { void slotRefreshAllRows(); + void slotTracksRemoved(const QSet& trackIds); + void slotCoverFound( const QObject* pRequester, const CoverInfo& coverInfo, diff --git a/src/library/dao/trackdao.cpp b/src/library/dao/trackdao.cpp index cdd5ba2a4440..437c14ab7beb 100644 --- a/src/library/dao/trackdao.cpp +++ b/src/library/dao/trackdao.cpp @@ -1111,9 +1111,9 @@ void TrackDAO::afterPurgingTracks( #else QSet tracksRemovedSet = QSet::fromList(trackIds); #endif + // Notify BaseTrackCache it should remove tracks and track models + // that they should update their cache as well. emit tracksRemoved(tracksRemovedSet); - // notify trackmodels that they should update their cache as well. - emit forceModelUpdate(); } namespace { diff --git a/src/library/dao/trackdao.h b/src/library/dao/trackdao.h index 16412f1abf98..88037a152a89 100644 --- a/src/library/dao/trackdao.h +++ b/src/library/dao/trackdao.h @@ -116,6 +116,7 @@ class TrackDAO : public QObject, public virtual DAO, public virtual GlobalTrackC void progressVerifyTracksOutside(const QString& path); void progressCoverArt(const QString& file); void forceModelUpdate(); + void removeTrackRows(const QSet& trackIds); public slots: // Slots to inform the TrackDAO about changes that diff --git a/src/library/trackcollection.cpp b/src/library/trackcollection.cpp index 55a85efe6f13..d3f551a4599c 100644 --- a/src/library/trackcollection.cpp +++ b/src/library/trackcollection.cpp @@ -41,7 +41,7 @@ TrackCollection::TrackCollection( connect(&m_trackDao, &TrackDAO::tracksRemoved, this, - &TrackCollection::tracksRemoved, + &TrackCollection::tracksRemoved, // unused /*signal-to-signal*/ Qt::DirectConnection); connect(&m_trackDao, &TrackDAO::forceModelUpdate, diff --git a/src/library/trackmodel.h b/src/library/trackmodel.h index 85d212a15e3c..4c6e60932319 100644 --- a/src/library/trackmodel.h +++ b/src/library/trackmodel.h @@ -243,6 +243,8 @@ class TrackModel { virtual void select() { } + virtual void removeTrackRows(const QSet&) {}; + /// This is an interface to stop any potentially running /// model population when switching models in WTrackTableView. /// Only implemented in ProxyTrackModel. diff --git a/src/library/trackset/crate/cratetablemodel.cpp b/src/library/trackset/crate/cratetablemodel.cpp index b660fa159ae2..3d95a764a793 100644 --- a/src/library/trackset/crate/cratetablemodel.cpp +++ b/src/library/trackset/crate/cratetablemodel.cpp @@ -218,7 +218,9 @@ void CrateTableModel::removeTracks(const QModelIndexList& indices) { return; } - select(); + // Now remove the track rows + QSet tracksRemovedSet = QSet(trackIds.begin(), trackIds.end()); + removeTrackRows(tracksRemovedSet); } QString CrateTableModel::modelKey(bool noSearch) const { diff --git a/src/preferences/dialog/dlgprefinterface.cpp b/src/preferences/dialog/dlgprefinterface.cpp index 7cdc67e86a19..794c0038040c 100644 --- a/src/preferences/dialog/dlgprefinterface.cpp +++ b/src/preferences/dialog/dlgprefinterface.cpp @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -240,8 +241,19 @@ DlgPrefInterface::DlgPrefInterface( } QScreen* DlgPrefInterface::getScreen() const { - auto* pScreen = - mixxx::widgethelper::getScreen(*this); + QScreen* pScreen = nullptr; + const QWidgetList topLevelWidgets = QApplication::topLevelWidgets(); + for (QWidget* pWidget : topLevelWidgets) { + // Ignore other popups and hidden track menus + QMainWindow* pMainWindow = qobject_cast(pWidget); + if (pMainWindow) { + pScreen = mixxx::widgethelper::getScreen(*pMainWindow); + break; + } + } + VERIFY_OR_DEBUG_ASSERT(pScreen) { + pScreen = mixxx::widgethelper::getScreen(*this); + } if (!pScreen) { // Obtain the primary screen. This is necessary if no window is // available before the widget is displayed. diff --git a/tools/debian_buildenv.sh b/tools/debian_buildenv.sh index 97c43cf6bb5f..c5ee01f05a8d 100755 --- a/tools/debian_buildenv.sh +++ b/tools/debian_buildenv.sh @@ -52,16 +52,41 @@ case "$1" in fi # Install a faster linker. Prefer mold, fall back to lld - if apt-cache show mold 2>%1 >/dev/null; + if apt-cache show mold 2>/dev/null >/dev/null; then sudo apt-get install -y --no-install-recommends mold else - if apt-cache show lld 2>%1 >/dev/null; + if apt-cache show lld 2>/dev/null >/dev/null; then sudo apt-get install -y --no-install-recommends lld fi fi + # Check if fonts-ubuntu is available (from non-free repository) + if ! apt-cache show fonts-ubuntu 2>/dev/null | grep -q "Package: fonts-ubuntu"; then + echo "" + echo "⚠️ WARNING: The package 'fonts-ubuntu' is not available." + echo "This package is required for Mixxx and is located in the Debian non-free repository." + echo "" + read -p "Do you want to enable the non-free repository and install fonts-ubuntu? (y/n) " -n 1 -r + echo + if [[ $REPLY =~ ^[Yy]$ ]]; then + echo "Enabling non-free repository..." + # Add non-free to sources.list if not already present + if ! grep -q " non-free$" /etc/apt/sources.list; then + sudo sed -i 's/^\(deb.*\) \(main\|contrib\|non-free-firmware\)$/\1 \2 non-free/' /etc/apt/sources.list + fi + echo "Updating package list..." + sudo apt-get update + FONTS_UBUNTU_AVAILABLE=true + else + echo "Continuing without fonts-ubuntu..." + FONTS_UBUNTU_AVAILABLE=false + fi + else + FONTS_UBUNTU_AVAILABLE=true + fi + sudo apt-get install -y --no-install-recommends -- \ ccache \ cmake \ @@ -72,7 +97,7 @@ case "$1" in docbook-to-man \ dput \ fonts-open-sans \ - fonts-ubuntu \ + $([ "$FONTS_UBUNTU_AVAILABLE" = true ] && echo "fonts-ubuntu") \ g++ \ lcov \ libavformat-dev \