diff --git a/CMakeLists.txt b/CMakeLists.txt index ba670709e34..269b7a0ec3b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1267,6 +1267,10 @@ if(APPLE) # will automatically insert retain/release calls on Objective-C objects. target_compile_options(mixxx-lib PUBLIC -fobjc-arc) + target_sources(mixxx-lib PRIVATE + src/util/darkappearance.mm + ) + option(MACOS_ITUNES_LIBRARY "Native macOS iTunes/Music.app library integration" ON) if(MACOS_ITUNES_LIBRARY) target_sources(mixxx-lib PRIVATE @@ -1819,7 +1823,8 @@ add_executable(mixxx-test src/test/hotcuecontrol_test.cpp src/test/imageutils_test.cpp src/test/indexrange_test.cpp - src/test/itunesxmlimportertest.cpp + # TODO: reanable this after https://github.com/mixxxdj/mixxx/pull/11666 + # src/test/itunesxmlimportertest.cpp src/test/keyutilstest.cpp src/test/lcstest.cpp src/test/learningutilstest.cpp diff --git a/res/controllers/hid-controller-api.d.ts b/res/controllers/hid-controller-api.d.ts index dbe4ca70d95..2da9d5e2716 100644 --- a/res/controllers/hid-controller-api.d.ts +++ b/res/controllers/hid-controller-api.d.ts @@ -18,18 +18,54 @@ declare namespace controller { * @param dataList Data to send as list of bytes * @param length Unused but mandatory argument for backwards compatibility * @param reportID 1...255 for HID devices that uses ReportIDs - or 0 for devices, which don't use ReportIDs - * @param resendUnchangedReport If set, the report will also be send, if the data are unchanged since last sending [default = false] + * @param useNonSkippingFIFO If set, the report will send in FIFO mode + * + * `false` (default): + * - Reports with identical data will be sent only once. + * - If reports were superseded by newer data before they could be sent, + * the oudated data will be skipped. + * - This mode works for all USB HID class compatible reports, + * in these each field represents the state of a control (e.g. an LED). + * - This mode works best in overload situations, where more reports + * are to be sent, than can be processed. + * + * `true`: + * - The report will not be skipped under any circumstances, + * except FIFO memory overflow. + * - All reports with useNonSkippingFIFO set `true` will be send before + * any cached report with useNonSkippingFIFO set `false`. + * - All reports with useNonSkippingFIFO set `true` will be send in + * strict First In / First Out (FIFO) order. + * - Limit the use of this mode to the places, where it is really necessary. */ - function send(dataList: number[], length: number, reportID: number, resendUnchangedReport?: boolean): void; + function send(dataList: number[], length: number, reportID: number, useNonSkippingFIFO?: boolean): void; /** * Sends an OutputReport to HID device * * @param reportID 1...255 for HID devices that uses ReportIDs - or 0 for devices, which don't use ReportIDs * @param dataArray Data to send as byte array - * @param resendUnchangedReport If set, the report will also be send, if the data are unchanged since last sending [default = false] + * @param useNonSkippingFIFO If set, the report will send in FIFO mode + * + * `false` (default): + * - Reports with identical data will be sent only once. + * - If reports were superseded by newer data before they could be sent, + * the oudated data will be skipped. + * - This mode works for all USB HID class compatible reports, + * in these each field represents the state of a control (e.g. an LED). + * - This mode works best in overload situations, where more reports + * are to be sent, than can be processed. + * + * `true`: + * - The report will not be skipped under any circumstances, + * except FIFO memory overflow. + * - All reports with useNonSkippingFIFO set `true` will be send before + * any cached report with useNonSkippingFIFO set `false`. + * - All reports with useNonSkippingFIFO set `true` will be send in + * strict First In / First Out (FIFO) order. + * - Limit the use of this mode to the places, where it is really necessary. */ - function sendOutputReport(reportID: number, dataArray: ArrayBuffer, resendUnchangedReport?: boolean): void; + function sendOutputReport(reportID: number, dataArray: ArrayBuffer, useNonSkippingFIFO?: boolean): void; /** * getInputReport receives an InputReport from the HID device on request. @@ -56,7 +92,7 @@ declare namespace controller { * * @param reportID 1...255 for HID devices that uses ReportIDs - or 0 for devices, which don't use * @returns The returned array matches the input format of sendFeatureReport, - * allowing it to be read, modified and sent it back to the controller. + * allowing it to be read, modified and sent it back to the controller. */ function getFeatureReport(reportID: number): ArrayBuffer; } diff --git a/src/library/basetracktablemodel.cpp b/src/library/basetracktablemodel.cpp index 0d468637eb2..e3414e29338 100644 --- a/src/library/basetracktablemodel.cpp +++ b/src/library/basetracktablemodel.cpp @@ -590,21 +590,9 @@ QVariant BaseTrackTableModel::roleValue( break; } M_FALLTHROUGH_INTENDED; - // Right align BPM, duraation and bitrate so big/small values can easily be - // spotted by length (number of digits) - case Qt::TextAlignmentRole: { - switch (field) { - case ColumnCache::COLUMN_LIBRARYTABLE_BPM: - case ColumnCache::COLUMN_LIBRARYTABLE_DURATION: - case ColumnCache::COLUMN_LIBRARYTABLE_BITRATE: { - // We need to cast to int due to a bug similar to - // https://bugreports.qt.io/browse/QTBUG-67582 - return static_cast(Qt::AlignVCenter | Qt::AlignRight); - } - default: - return QVariant(); // default AlignLeft for all other columns - } - } + // NOTE: for export we need to fall through to Qt::DisplayRole, + // so do not add any other role cases here, or the export + // will be empty case Qt::DisplayRole: switch (field) { case ColumnCache::COLUMN_LIBRARYTABLE_DURATION: { @@ -873,6 +861,21 @@ QVariant BaseTrackTableModel::roleValue( return Qt::PartiallyChecked; } } + // Right align BPM, duration and bitrate so big/small values can easily be + // spotted by length (number of digits) + case Qt::TextAlignmentRole: { + switch (field) { + case ColumnCache::COLUMN_LIBRARYTABLE_BPM: + case ColumnCache::COLUMN_LIBRARYTABLE_DURATION: + case ColumnCache::COLUMN_LIBRARYTABLE_BITRATE: { + // We need to cast to int due to a bug similar to + // https://bugreports.qt.io/browse/QTBUG-67582 + return static_cast(Qt::AlignVCenter | Qt::AlignRight); + } + default: + return QVariant(); // default AlignLeft for all other columns + } + } default: DEBUG_ASSERT(!"unexpected role"); break; diff --git a/src/preferences/dialog/dlgpreferences.cpp b/src/preferences/dialog/dlgpreferences.cpp index 1fa301608a8..e31ae1b97cb 100644 --- a/src/preferences/dialog/dlgpreferences.cpp +++ b/src/preferences/dialog/dlgpreferences.cpp @@ -48,6 +48,10 @@ #include "util/color/color.h" #include "util/widgethelper.h" +#ifdef __APPLE__ +#include "util/darkappearance.h" +#endif + DlgPreferences::DlgPreferences( std::shared_ptr pScreensaverManager, std::shared_ptr pSkinLoader, @@ -61,6 +65,7 @@ DlgPreferences::DlgPreferences( m_pConfig(pSettingsManager->settings()), m_pageSizeHint(QSize(0, 0)) { setupUi(this); + fixSliderStyle(); contentsTreeWidget->setHeaderHidden(true); // Add '&' to default button labels to always have Alt shortcuts, indpependent @@ -564,3 +569,54 @@ QRect DlgPreferences::getDefaultGeometry() { return optimumRect; } + +void DlgPreferences::fixSliderStyle() { +#ifdef __APPLE__ + // Only used on macOS where the default slider style has several issues: + // - the handle is semi-transparent + // - the slider is higher than the space we give it, which causes that: + // - the groove is not correctly centered vertically + // - the handle is cut off at the top + // The style below is based on sliders in the macOS system settings dialogs. + if (darkAppearance()) { + setStyleSheet(R"--( +QSlider::handle:horizontal { + background-color: #8f8c8b; + border-radius: 4px; + width: 8px; + margin: -8px; +} +QSlider::handle:horizontal::pressed { + background-color: #a9a7a7; +} +QSlider::groove:horizontal { + background: #1e1e1e; + height: 4px; + border-radius: 2px; + margin-left: 8px; + margin-right: 8px; +} +)--"); + } else { + setStyleSheet(R"--( +QSlider::handle:horizontal { + background-color: #ffffff; + border-radius: 4px; + border: 1px solid #d4d3d3; + width: 7px; + margin: -8px; +} +QSlider::handle:horizontal::pressed { + background-color: #ececec; +} +QSlider::groove:horizontal { + background: #c6c5c5; + height: 4px; + border-radius: 2px; + margin-left: 8px; + margin-right: 8px; +} +)--"); + } +#endif // __APPLE__ +} diff --git a/src/preferences/dialog/dlgpreferences.h b/src/preferences/dialog/dlgpreferences.h index 2aeb8efd4eb..3968fa53964 100644 --- a/src/preferences/dialog/dlgpreferences.h +++ b/src/preferences/dialog/dlgpreferences.h @@ -111,6 +111,7 @@ class DlgPreferences : public QDialog, public Ui::DlgPreferencesDlg { private: DlgPreferencePage* currentPage(); + void fixSliderStyle(); QList m_allPages; void onShow(); void onHide(); diff --git a/src/preferences/dialog/dlgprefwaveformdlg.ui b/src/preferences/dialog/dlgprefwaveformdlg.ui index 96b6650efd1..6bdc3c26520 100644 --- a/src/preferences/dialog/dlgprefwaveformdlg.ui +++ b/src/preferences/dialog/dlgprefwaveformdlg.ui @@ -490,7 +490,7 @@ Select from different types of displays for the waveform, which differ primarily - + 100 diff --git a/src/util/darkappearance.h b/src/util/darkappearance.h new file mode 100644 index 00000000000..c838de69ffe --- /dev/null +++ b/src/util/darkappearance.h @@ -0,0 +1 @@ +bool darkAppearance(); diff --git a/src/util/darkappearance.mm b/src/util/darkappearance.mm new file mode 100644 index 00000000000..716901c8837 --- /dev/null +++ b/src/util/darkappearance.mm @@ -0,0 +1,14 @@ +#include "util/darkappearance.h" +#import + +bool darkAppearance() { + if (__builtin_available(macOS 10.14, *)) { + auto appearance = + [NSApp.effectiveAppearance bestMatchFromAppearancesWithNames:@[ + NSAppearanceNameAqua, + NSAppearanceNameDarkAqua + ]]; + return [appearance isEqualToString:NSAppearanceNameDarkAqua]; + } + return false; +} diff --git a/src/waveform/waveformwidgetfactory.cpp b/src/waveform/waveformwidgetfactory.cpp index b92ffd16d08..8c4289da832 100644 --- a/src/waveform/waveformwidgetfactory.cpp +++ b/src/waveform/waveformwidgetfactory.cpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -71,6 +72,8 @@ bool shouldRenderWaveform(WaveformWidgetAbstract* pWaveformWidget) { return glw->shouldRender(); } + +const QRegularExpression openGLVersionRegex(QStringLiteral("^(\\d+)\\.(\\d+).*$")); } // anonymous namespace /////////////////////////////////////////// @@ -142,13 +145,27 @@ WaveformWidgetFactory::WaveformWidgetFactory() reinterpret_cast(glFunctions->glGetString(GL_VENDOR)))); QString rendererString = QString(QLatin1String( reinterpret_cast(glFunctions->glGetString(GL_RENDERER)))); - qDebug() << versionString << vendorString << rendererString; - - // note: the requested version has been set in WGLWidget's OpenGLWindow constructor - const int majorVersion = context->surface()->format().majorVersion(); - const int minorVersion = context->surface()->format().minorVersion(); + qDebug().noquote() << QStringLiteral( + "OpenGL driver version string \"%1\", vendor \"%2\", " + "renderer \"%3\"") + .arg(versionString, vendorString, rendererString); + + GLint majorVersion, minorVersion = GL_INVALID_ENUM; + glFunctions->glGetIntegerv(GL_MAJOR_VERSION, &majorVersion); + glFunctions->glGetIntegerv(GL_MINOR_VERSION, &minorVersion); + if (majorVersion == GL_INVALID_ENUM || minorVersion == GL_INVALID_ENUM) { + // GL_MAJOR/MINOR_VERSION are not supported below OpenGL 3.0, so + // parse GL_VERSION string as a fallback. + // https://www.khronos.org/opengl/wiki/OpenGL_Context#OpenGL_version_number + auto match = openGLVersionRegex.match(versionString); + DEBUG_ASSERT(match.hasMatch()); + majorVersion = match.captured(1).toInt(); + minorVersion = match.captured(2).toInt(); + } - qDebug() << "QOpenGLContext surface format version:" << majorVersion << minorVersion; + qDebug().noquote() + << QStringLiteral("Supported OpenGL version: %1.%2") + .arg(QString::number(majorVersion), QString::number(minorVersion)); m_openGLShaderAvailable = QOpenGLShaderProgram::hasOpenGLShaderPrograms(context); diff --git a/src/widget/wmainmenubar.cpp b/src/widget/wmainmenubar.cpp index 7c4579c829b..92cc5c5c565 100644 --- a/src/widget/wmainmenubar.cpp +++ b/src/widget/wmainmenubar.cpp @@ -578,8 +578,9 @@ void WMainMenuBar::initialize() { auto* pHelpSupport = new QAction(supportTitle, this); pHelpSupport->setStatusTip(supportText); pHelpSupport->setWhatsThis(buildWhatsThis(supportTitle, supportText)); - connect(pHelpSupport, &QAction::triggered, - this, [this] { slotVisitUrl(MIXXX_SUPPORT_URL); }); + connect(pHelpSupport, &QAction::triggered, this, [this] { + slotVisitUrl(QUrl(MIXXX_SUPPORT_URL)); + }); pHelpMenu->addAction(pHelpSupport); // User Manual @@ -594,7 +595,7 @@ void WMainMenuBar::initialize() { pHelpManual->setStatusTip(manualText); pHelpManual->setWhatsThis(buildWhatsThis(manualTitle, manualText)); connect(pHelpManual, &QAction::triggered, this, [this, manualUrl] { - slotVisitUrl(manualUrl.toString()); + slotVisitUrl(manualUrl); }); pHelpMenu->addAction(pHelpManual); @@ -614,18 +615,32 @@ void WMainMenuBar::initialize() { &QAction::triggered, this, [this, keyboardShortcutsUrl] { - slotVisitUrl(keyboardShortcutsUrl.toString()); + slotVisitUrl(keyboardShortcutsUrl); }); pHelpMenu->addAction(pHelpKbdShortcuts); + // User Settings Directory + const QString& settingsDirPath = m_pConfig->getSettingsPath(); + QString settingsDirTitle = tr("&Settings directory"); + QString settingsDirText = tr("Open the Mixxx user settings directory."); + auto* pHelpSettingsDir = new QAction(settingsDirTitle, this); + pHelpSettingsDir->setMenuRole(QAction::NoRole); + pHelpSettingsDir->setStatusTip(settingsDirText); + pHelpSettingsDir->setWhatsThis(buildWhatsThis(settingsDirTitle, settingsDirText)); + connect(pHelpSettingsDir, &QAction::triggered, this, [this, settingsDirPath] { + slotVisitUrl(QUrl::fromLocalFile(settingsDirPath)); + }); + pHelpMenu->addAction(pHelpSettingsDir); + // Translate This Application QString translateTitle = tr("&Translate This Application") + externalLinkSuffix; QString translateText = tr("Help translate this application into your language."); auto* pHelpTranslation = new QAction(translateTitle, this); pHelpTranslation->setStatusTip(translateText); pHelpTranslation->setWhatsThis(buildWhatsThis(translateTitle, translateText)); - connect(pHelpTranslation, &QAction::triggered, - this, [this] { slotVisitUrl(MIXXX_TRANSLATION_URL); }); + connect(pHelpTranslation, &QAction::triggered, this, [this] { + slotVisitUrl(QUrl(MIXXX_TRANSLATION_URL)); + }); pHelpMenu->addAction(pHelpTranslation); pHelpMenu->addSeparator(); @@ -711,8 +726,8 @@ void WMainMenuBar::slotDeveloperDebugger(bool toggle) { ConfigValue(toggle ? 1 : 0)); } -void WMainMenuBar::slotVisitUrl(const QString& url) { - QDesktopServices::openUrl(QUrl(url)); +void WMainMenuBar::slotVisitUrl(const QUrl& url) { + QDesktopServices::openUrl(url); } void WMainMenuBar::createVisibilityControl(QAction* pAction, diff --git a/src/widget/wmainmenubar.h b/src/widget/wmainmenubar.h index 10136d66854..6fd35a42345 100644 --- a/src/widget/wmainmenubar.h +++ b/src/widget/wmainmenubar.h @@ -84,7 +84,7 @@ class WMainMenuBar : public QMenuBar { void slotDeveloperStatsExperiment(bool enable); void slotDeveloperStatsBase(bool enable); void slotDeveloperDebugger(bool toggle); - void slotVisitUrl(const QString& url); + void slotVisitUrl(const QUrl& url); private: void initialize();