diff --git a/build/depends.py b/build/depends.py index 628839ec5f52..6f19eb91788e 100644 --- a/build/depends.py +++ b/build/depends.py @@ -871,6 +871,7 @@ def sources(self, build): "src/controllers/midi/midioutputhandler.cpp", "src/controllers/softtakeover.cpp", "src/controllers/keyboard/keyboardeventfilter.cpp", + "src/controllers/colorjsproxy.cpp", "src/main.cpp", "src/mixxx.cpp", @@ -1225,6 +1226,7 @@ def sources(self, build): "src/util/widgetrendertimer.cpp", "src/util/workerthread.cpp", "src/util/workerthreadscheduler.cpp", + "src/util/color/predefinedcolor.cpp" ] proto_args = { diff --git a/res/controllers/Hercules-DJ-Console-Mk4-scripts.js b/res/controllers/Hercules-DJ-Console-Mk4-scripts.js index 0e3fb7b74c72..4985ff64f6c6 100644 --- a/res/controllers/Hercules-DJ-Console-Mk4-scripts.js +++ b/res/controllers/Hercules-DJ-Console-Mk4-scripts.js @@ -224,13 +224,8 @@ HerculesMk4.pfl = function (midino, control, value, status, group) { HerculesMk4.pitchbend = function (midino, control, value, status, group) { -<<<<<<< HEAD - // Pitch - : set pitch sensitivity - // Pitch +: set jog fast position -======= // Pitch - : set pitch sensivity // Pitch +: set jog fast position ->>>>>>> upstream/2.2 //ignore when releasing the button if(value==0x00) return; @@ -240,16 +235,6 @@ HerculesMk4.pitchbend = function (midino, control, value, status, group) { HerculesMk4.jogFastPosition[HerculesMk4.deck(group)]=newValue; -<<<<<<< HEAD - if(newValue==1){ - midi.sendShortMsg(0x90, HerculesMk4.selectLed(group,11), 0x7f); - }else{ - midi.sendShortMsg(0x90, HerculesMk4.selectLed(group,11), 0x00); - } - } - else { // Pitchbend - - HerculesMk4.sensitivityPitch[HerculesMk4.deck(group)]=HerculesMk4.toglePitchSensitivity(group,HerculesMk4.sensitivityPitch[HerculesMk4.deck(group)]); -======= if(newValue==1){ midi.sendShortMsg(0x90, HerculesMk4.selectLed(group,11), 0x7f); }else{ @@ -258,34 +243,12 @@ HerculesMk4.pitchbend = function (midino, control, value, status, group) { } else { // Pitchbend - HerculesMk4.sensivityPitch[HerculesMk4.deck(group)]=HerculesMk4.toglePitchSensivity(group,HerculesMk4.sensivityPitch[HerculesMk4.deck(group)]); ->>>>>>> upstream/2.2 - } }; HerculesMk4.toglePitchSensitivity=function (group,sensitivity) { - -<<<<<<< HEAD - sensitivity=sensitivity+2; - - if(sensitivity>5){ - sensitivity=1; - } - - - - if(sensitivity==1){ - //pitch very fine - midi.sendShortMsg(0x90, HerculesMk4.selectLed(group,10), 0x00); // minus led off - midi.sendShortMsg(0x90, HerculesMk4.selectLed(group,58), 0x7F); // Blink minus led - - } else if (sensitivity==3){ - //pitch fine - midi.sendShortMsg(0x90, HerculesMk4.selectLed(group,58), 0x00); // Blink minus led off - midi.sendShortMsg(0x90, HerculesMk4.selectLed(group,10), 0x7F); // minus led -======= sensivity=sensivity+2; if(sensivity>5){ @@ -303,20 +266,13 @@ HerculesMk4.toglePitchSensitivity=function (group,sensitivity) { //pitch fine midi.sendShortMsg(0x90, HerculesMk4.selectLed(group,58), 0x00); // Blink minus led off midi.sendShortMsg(0x90, HerculesMk4.selectLed(group,10), 0x7F); // minus led ->>>>>>> upstream/2.2 - } else { //pitch coarse midi.sendShortMsg(0x90, HerculesMk4.selectLed(group,58), 0x00); // Blink minus led off midi.sendShortMsg(0x90, HerculesMk4.selectLed(group,10), 0x00); // minus led off } -<<<<<<< HEAD - return sensitivity; -======= return sensivity; ->>>>>>> upstream/2.2 - } HerculesMk4.cue = function (midino, control, value, status, group) { diff --git a/res/skins/Deere (64 Samplers)/preferences_preview_screenshot.png b/res/skins/Deere (64 Samplers)/skin_preview.png similarity index 100% rename from res/skins/Deere (64 Samplers)/preferences_preview_screenshot.png rename to res/skins/Deere (64 Samplers)/skin_preview.png diff --git a/res/skins/Deere/preferences_preview_screenshot.png b/res/skins/Deere/skin_preview.png similarity index 100% rename from res/skins/Deere/preferences_preview_screenshot.png rename to res/skins/Deere/skin_preview.png diff --git a/res/skins/LateNight/preferences_preview_screenshot.png b/res/skins/LateNight/skin_preview.png similarity index 100% rename from res/skins/LateNight/preferences_preview_screenshot.png rename to res/skins/LateNight/skin_preview.png diff --git a/res/skins/Shade/preferences_preview_screenshot.png b/res/skins/Shade/preferences_preview_screenshot.png deleted file mode 100644 index 1c39f14db2c3..000000000000 Binary files a/res/skins/Shade/preferences_preview_screenshot.png and /dev/null differ diff --git a/res/skins/Shade/skin_preview_Classic.png b/res/skins/Shade/skin_preview_Classic.png new file mode 100644 index 000000000000..b5ce3dd15a4e Binary files /dev/null and b/res/skins/Shade/skin_preview_Classic.png differ diff --git a/res/skins/Shade/skin_preview_Dark.png b/res/skins/Shade/skin_preview_Dark.png new file mode 100644 index 000000000000..96a8fbc5e80a Binary files /dev/null and b/res/skins/Shade/skin_preview_Dark.png differ diff --git a/res/skins/Shade/skin_preview_SummerSunset.png b/res/skins/Shade/skin_preview_SummerSunset.png new file mode 100644 index 000000000000..8934698bc8d2 Binary files /dev/null and b/res/skins/Shade/skin_preview_SummerSunset.png differ diff --git a/res/skins/Tango (64 Samplers)/preferences_preview_screenshot.png b/res/skins/Tango (64 Samplers)/skin_preview_Classic.png similarity index 100% rename from res/skins/Tango (64 Samplers)/preferences_preview_screenshot.png rename to res/skins/Tango (64 Samplers)/skin_preview_Classic.png diff --git a/res/skins/Tango (64 Samplers)/skin_preview_ClubTwist.png b/res/skins/Tango (64 Samplers)/skin_preview_ClubTwist.png new file mode 100644 index 000000000000..dc4c0646459c Binary files /dev/null and b/res/skins/Tango (64 Samplers)/skin_preview_ClubTwist.png differ diff --git a/res/skins/Tango/preferences_preview_screenshot.png b/res/skins/Tango/skin_preview_Classic.png similarity index 100% rename from res/skins/Tango/preferences_preview_screenshot.png rename to res/skins/Tango/skin_preview_Classic.png diff --git a/res/skins/Tango/skin_preview_ClubTwist.png b/res/skins/Tango/skin_preview_ClubTwist.png new file mode 100644 index 000000000000..7fcba2f06098 Binary files /dev/null and b/res/skins/Tango/skin_preview_ClubTwist.png differ diff --git a/src/controllers/colorjsproxy.cpp b/src/controllers/colorjsproxy.cpp new file mode 100644 index 000000000000..2208510cc750 --- /dev/null +++ b/src/controllers/colorjsproxy.cpp @@ -0,0 +1,36 @@ +#include "controllers/colorjsproxy.h" + +ColorJSProxy::ColorJSProxy(QScriptEngine* pScriptEngine) + : m_pScriptEngine(pScriptEngine), + m_predefinedColorsList(makePredefinedColorsList(pScriptEngine)){}; + +ColorJSProxy::~ColorJSProxy() {}; + +QScriptValue ColorJSProxy::predefinedColorFromId(int iId) { + PredefinedColorPointer color(Color::predefinedColorSet.predefinedColorFromId(iId)); + return jsColorFrom(color); +}; + +Q_INVOKABLE QScriptValue ColorJSProxy::predefinedColorsList() { + return m_predefinedColorsList; +} + +QScriptValue ColorJSProxy::jsColorFrom(PredefinedColorPointer predefinedColor) { + QScriptValue jsColor = m_pScriptEngine->newObject(); + jsColor.setProperty("red", predefinedColor->m_defaultRgba.red()); + jsColor.setProperty("green", predefinedColor->m_defaultRgba.green()); + jsColor.setProperty("blue", predefinedColor->m_defaultRgba.blue()); + jsColor.setProperty("alpha", predefinedColor->m_defaultRgba.alpha()); + jsColor.setProperty("id", predefinedColor->m_iId); + return jsColor; +} + +QScriptValue ColorJSProxy::makePredefinedColorsList(QScriptEngine* pScriptEngine) { + int numColors = Color::predefinedColorSet.allColors.length(); + QScriptValue colorList = pScriptEngine->newArray(numColors); + for (int i = 0; i < numColors; ++i) { + PredefinedColorPointer color = Color::predefinedColorSet.allColors.at(i); + colorList.setProperty(i, jsColorFrom(color)); + } + return colorList; +} diff --git a/src/controllers/colorjsproxy.h b/src/controllers/colorjsproxy.h new file mode 100644 index 000000000000..e6ebeda1d9b0 --- /dev/null +++ b/src/controllers/colorjsproxy.h @@ -0,0 +1,27 @@ +#ifndef COLORJSPROXY_H +#define COLORJSPROXY_H + +#include +#include +#include + +#include "util/color/color.h" + +class ColorJSProxy: public QObject { + Q_OBJECT + public: + ColorJSProxy(QScriptEngine* pScriptEngine); + + virtual ~ColorJSProxy(); + + Q_INVOKABLE QScriptValue predefinedColorFromId(int iId); + Q_INVOKABLE QScriptValue predefinedColorsList(); + + private: + QScriptValue jsColorFrom(PredefinedColorPointer predefinedColor); + QScriptValue makePredefinedColorsList(QScriptEngine* pScriptEngine); + QScriptEngine* m_pScriptEngine; + QScriptValue m_predefinedColorsList; +}; + +#endif /* COLORJSPROXY_H */ diff --git a/src/controllers/controllerengine.cpp b/src/controllers/controllerengine.cpp index 0aa1d102a5b5..87d7d56071d5 100644 --- a/src/controllers/controllerengine.cpp +++ b/src/controllers/controllerengine.cpp @@ -185,6 +185,7 @@ void ControllerEngine::gracefulShutdown() { ++it; } + m_pColorJSProxy.reset(); delete m_pBaClass; m_pBaClass = nullptr; } @@ -212,6 +213,9 @@ void ControllerEngine::initializeScriptEngine() { engineGlobalObject.setProperty("midi", m_pEngine->newQObject(m_pController)); } + m_pColorJSProxy = std::make_unique(m_pEngine); + engineGlobalObject.setProperty("color", m_pEngine->newQObject(m_pColorJSProxy.get())); + m_pBaClass = new ByteArrayClass(m_pEngine); engineGlobalObject.setProperty("ByteArray", m_pBaClass->constructor()); } diff --git a/src/controllers/controllerengine.h b/src/controllers/controllerengine.h index 6b00c46172b2..e9ed4bb88feb 100644 --- a/src/controllers/controllerengine.h +++ b/src/controllers/controllerengine.h @@ -16,9 +16,11 @@ #include "bytearrayclass.h" #include "preferences/usersettings.h" #include "controllers/controllerpreset.h" +#include "controllers/colorjsproxy.h" #include "controllers/softtakeover.h" #include "util/alphabetafilter.h" #include "util/duration.h" +#include "util/memory.h" // Forward declaration(s) class Controller; @@ -204,6 +206,7 @@ class ControllerEngine : public QObject { QHash m_timers; SoftTakeoverCtrl m_st; ByteArrayClass* m_pBaClass; + std::unique_ptr m_pColorJSProxy; // 256 (default) available virtual decks is enough I would think. // If more are needed at run-time, these will move to the heap automatically QVarLengthArray m_intervalAccumulator; diff --git a/src/controllers/controlpickermenu.cpp b/src/controllers/controlpickermenu.cpp index 16e41e895203..ee158be6068f 100644 --- a/src/controllers/controlpickermenu.cpp +++ b/src/controllers/controlpickermenu.cpp @@ -802,6 +802,20 @@ ControlPickerMenu::ControlPickerMenu(QWidget* pParent) addDeckControl("waveform_zoom", tr("Waveform Zoom"), tr("Waveform zoom"), guiMenu); addDeckControl("waveform_zoom_down", tr("Waveform Zoom In"), tr("Zoom waveform in"), guiMenu); addDeckControl("waveform_zoom_up", tr("Waveform Zoom Out"), tr("Zoom waveform out"), guiMenu); + + // Controls to change a deck's star rating + QString starsUpTitle = tr("Star Rating Up"); + QString starsUpDescription = tr("Increase the track rating by one star"); + QString starsDownTitle = tr("Star Rating Down"); + QString starsDownDescription = tr("Decrease the track rating by one star"); + for (int i = 1; i <= iNumDecks; ++i) { + addControl(QString("[Deck%1]").arg(i), "stars_up", + QString("%1: %2").arg(m_deckStr.arg(i), starsUpTitle), + QString("%1: %2").arg(m_deckStr.arg(i), starsUpDescription), guiMenu); + addControl(QString("[Deck%1]").arg(i), "stars_down", + QString("%1: %2").arg(m_deckStr.arg(i), starsDownTitle), + QString("%1: %2").arg(m_deckStr.arg(i), starsDownDescription), guiMenu); + } } ControlPickerMenu::~ControlPickerMenu() { diff --git a/src/effects/builtin/balanceeffect.cpp b/src/effects/builtin/balanceeffect.cpp index b4ca00aedae9..41c23fe6bcb5 100644 --- a/src/effects/builtin/balanceeffect.cpp +++ b/src/effects/builtin/balanceeffect.cpp @@ -5,7 +5,6 @@ namespace { const double kMaxCornerHz = 500; const double kMinCornerHz = 16; - const unsigned int kStartupSamplerate = 44100; } // anonymous namespace // static diff --git a/src/effects/builtin/moogladder4filtereffect.cpp b/src/effects/builtin/moogladder4filtereffect.cpp index f73bfe6e780e..d0d622d9de1e 100644 --- a/src/effects/builtin/moogladder4filtereffect.cpp +++ b/src/effects/builtin/moogladder4filtereffect.cpp @@ -4,7 +4,6 @@ static const double kMinCorner = 0.0003; // 13 Hz @ 44100 static const double kMaxCorner = 0.5; // 22050 Hz @ 44100 -static const unsigned int kStartupSamplerate = 44100; // static QString MoogLadder4FilterEffect::getId() { diff --git a/src/engine/controls/cuecontrol.cpp b/src/engine/controls/cuecontrol.cpp index 31e7a882e1ce..c5c8951ab7ac 100644 --- a/src/engine/controls/cuecontrol.cpp +++ b/src/engine/controls/cuecontrol.cpp @@ -12,6 +12,7 @@ #include "control/controlindicator.h" #include "vinylcontrol/defs_vinylcontrol.h" #include "util/sample.h" +#include "util/color/color.h" // TODO: Convert these doubles to a standard enum // and convert elseif logic to switch statements @@ -297,6 +298,7 @@ void CueControl::trackCuesUpdated() { } else { // If the old hotcue is the same, then we only need to update pControl->setPosition(pCue->getPosition()); + pControl->setColor(pCue->getColor()); } // Add the hotcue to the list of active hotcues active_hotcues.insert(hotcue); @@ -1036,6 +1038,12 @@ HotcueControl::HotcueControl(QString group, int i) m_hotcueEnabled = new ControlObject(keyForControl(i, "enabled")); m_hotcueEnabled->setReadOnly(); + // The id of the predefined color assigned to this color. + m_hotcueColor = new ControlObject(keyForControl(i, "color_id")); + connect(m_hotcueColor, SIGNAL(valueChanged(double)), + this, SLOT(slotHotcueColorChanged(double)), + Qt::DirectConnection); + m_hotcueSet = new ControlPushButton(keyForControl(i, "set")); connect(m_hotcueSet, &ControlObject::valueChanged, this, &HotcueControl::slotHotcueSet, @@ -1075,6 +1083,7 @@ HotcueControl::HotcueControl(QString group, int i) HotcueControl::~HotcueControl() { delete m_hotcuePosition; delete m_hotcueEnabled; + delete m_hotcueColor; delete m_hotcueSet; delete m_hotcueGoto; delete m_hotcueGotoAndPlay; @@ -1117,6 +1126,11 @@ void HotcueControl::slotHotcuePositionChanged(double newPosition) { emit(hotcuePositionChanged(this, newPosition)); } +void HotcueControl::slotHotcueColorChanged(double newColorId) { + m_pCue->setColor(Color::predefinedColorSet.predefinedColorFromId(newColorId)); + emit(hotcueColorChanged(this, newColorId)); +} + double HotcueControl::getPosition() const { return m_hotcuePosition->get(); } @@ -1127,7 +1141,13 @@ void HotcueControl::setCue(CuePointer pCue) { // because we have a null check for valid data else where in the code m_pCue = pCue; } +PredefinedColorPointer HotcueControl::getColor() const { + return Color::predefinedColorSet.predefinedColorFromId(m_hotcueColor->get()); +} +void HotcueControl::setColor(PredefinedColorPointer newColor) { + m_hotcueColor->set(static_cast(newColor->m_iId)); +} void HotcueControl::resetCue() { // clear pCue first because we have a null check for valid data else where // in the code diff --git a/src/engine/controls/cuecontrol.h b/src/engine/controls/cuecontrol.h index cec7ff86a3e6..ae2dfab701eb 100644 --- a/src/engine/controls/cuecontrol.h +++ b/src/engine/controls/cuecontrol.h @@ -30,6 +30,8 @@ class HotcueControl : public QObject { void setCue(CuePointer pCue); void resetCue(); void setPosition(double position); + void setColor(PredefinedColorPointer newColor); + PredefinedColorPointer getColor() const; // Used for caching the preview state of this hotcue control. inline bool isPreviewing() { @@ -54,6 +56,7 @@ class HotcueControl : public QObject { void slotHotcueActivatePreview(double v); void slotHotcueClear(double v); void slotHotcuePositionChanged(double newPosition); + void slotHotcueColorChanged(double newColorId); signals: void hotcueSet(HotcueControl* pHotcue, double v); @@ -64,6 +67,7 @@ class HotcueControl : public QObject { void hotcueActivatePreview(HotcueControl* pHotcue, double v); void hotcueClear(HotcueControl* pHotcue, double v); void hotcuePositionChanged(HotcueControl* pHotcue, double newPosition); + void hotcueColorChanged(HotcueControl* pHotcue, double newColorId); void hotcuePlay(double v); private: @@ -76,6 +80,7 @@ class HotcueControl : public QObject { // Hotcue state controls ControlObject* m_hotcuePosition; ControlObject* m_hotcueEnabled; + ControlObject* m_hotcueColor; // Hotcue button controls ControlObject* m_hotcueSet; ControlObject* m_hotcueGoto; diff --git a/src/library/dao/cue.cpp b/src/library/dao/cue.cpp index 8efc6f9a272b..377c97740d13 100644 --- a/src/library/dao/cue.cpp +++ b/src/library/dao/cue.cpp @@ -6,9 +6,9 @@ #include "library/dao/cue.h" #include "util/assert.h" +#include "util/color/color.h" namespace { - const QColor kDefaultColor = QColor("#FF0000"); const QString kDefaultLabel = ""; // empty string, not null } @@ -25,13 +25,13 @@ Cue::Cue(TrackId trackId) m_length(0.0), m_iHotCue(-1), m_label(kDefaultLabel), - m_color(kDefaultColor) { + m_color(Color::predefinedColorSet.noColor) { DEBUG_ASSERT(!m_label.isNull()); } Cue::Cue(int id, TrackId trackId, Cue::CueType type, double position, double length, - int hotCue, QString label, QColor color) + int hotCue, QString label, PredefinedColorPointer color) : m_bDirty(false), m_iId(id), m_trackId(trackId), @@ -138,12 +138,12 @@ void Cue::setLabel(const QString label) { emit(updated()); } -QColor Cue::getColor() const { +PredefinedColorPointer Cue::getColor() const { QMutexLocker lock(&m_mutex); return m_color; } -void Cue::setColor(const QColor color) { +void Cue::setColor(const PredefinedColorPointer color) { QMutexLocker lock(&m_mutex); m_color = color; m_bDirty = true; diff --git a/src/library/dao/cue.h b/src/library/dao/cue.h index 796dba0493af..e249645bf97f 100644 --- a/src/library/dao/cue.h +++ b/src/library/dao/cue.h @@ -6,6 +6,7 @@ #include #include "track/trackid.h" +#include "util/color/predefinedcolor.h" #include "util/memory.h" class CueDAO; @@ -44,8 +45,8 @@ class Cue : public QObject { QString getLabel() const; void setLabel(QString label); - QColor getColor() const; - void setColor(QColor color); + PredefinedColorPointer getColor() const; + void setColor(PredefinedColorPointer color); signals: void updated(); @@ -53,7 +54,7 @@ class Cue : public QObject { private: explicit Cue(TrackId trackId); Cue(int id, TrackId trackId, CueType type, double position, double length, - int hotCue, QString label, QColor color); + int hotCue, QString label, PredefinedColorPointer color); void setDirty(bool dirty); void setId(int id); void setTrackId(TrackId trackId); @@ -68,7 +69,7 @@ class Cue : public QObject { double m_length; int m_iHotCue; QString m_label; - QColor m_color; + PredefinedColorPointer m_color; friend class Track; friend class CueDAO; diff --git a/src/library/dao/cuedao.cpp b/src/library/dao/cuedao.cpp index 66a4e440c900..872f5b1da146 100644 --- a/src/library/dao/cuedao.cpp +++ b/src/library/dao/cuedao.cpp @@ -11,6 +11,8 @@ #include "library/queryutil.h" #include "util/assert.h" #include "util/performancetimer.h" +#include "util/color/color.h" +#include "util/color/predefinedcolor.h" int CueDAO::cueCount() { qDebug() << "CueDAO::cueCount" << QThread::currentThread() << m_database.connectionName(); @@ -51,7 +53,8 @@ CuePointer CueDAO::cueFromRow(const QSqlQuery& query) const { int length = record.value(record.indexOf("length")).toInt(); int hotcue = record.value(record.indexOf("hotcue")).toInt(); QString label = record.value(record.indexOf("label")).toString(); - QColor color = QColor::fromRgba(record.value(record.indexOf("color")).toInt()); + int iColorId = record.value(record.indexOf("color")).toInt(); + PredefinedColorPointer color = Color::predefinedColorSet.predefinedColorFromId(iColorId); CuePointer pCue(new Cue(id, trackId, (Cue::CueType)type, position, length, hotcue, label, color)); m_cues[id] = pCue; @@ -145,7 +148,7 @@ bool CueDAO::saveCue(Cue* cue) { query.bindValue(":length", cue->getLength()); query.bindValue(":hotcue", cue->getHotCue()); query.bindValue(":label", cue->getLabel()); - query.bindValue(":color", cue->getColor().rgba()); + query.bindValue(":color", cue->getColor()->m_iId); if (query.exec()) { int id = query.lastInsertId().toInt(); @@ -173,7 +176,7 @@ bool CueDAO::saveCue(Cue* cue) { query.bindValue(":length", cue->getLength()); query.bindValue(":hotcue", cue->getHotCue()); query.bindValue(":label", cue->getLabel()); - query.bindValue(":color", cue->getColor().rgba()); + query.bindValue(":color", cue->getColor()->m_iId); if (query.exec()) { cue->setDirty(false); diff --git a/src/library/dlgtrackinfo.cpp b/src/library/dlgtrackinfo.cpp index 7e62c7082a7f..0af9256dca56 100644 --- a/src/library/dlgtrackinfo.cpp +++ b/src/library/dlgtrackinfo.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "util/desktophelper.h" #include "library/dlgtrackinfo.h" @@ -12,9 +13,11 @@ #include "track/keyfactory.h" #include "track/keyutils.h" #include "util/duration.h" +#include "util/color/color.h" const int kFilterLength = 80; const int kMinBpm = 30; + // Maximum allowed interval between beats (calculated from kMinBpm). const mixxx::Duration kMaxInterval = mixxx::Duration::fromMillis(1000.0 * (60.0 / kMinBpm)); @@ -274,7 +277,7 @@ void DlgTrackInfo::populateCues(TrackPointer pTrack) { QListIterator it(cuePoints); while (it.hasNext()) { CuePointer pCue = it.next(); - if (pCue->getType() == Cue::CUE || pCue->getType() == Cue::LOAD) { + if (pCue->getType() == Cue::CUE) { listPoints.push_back(pCue); } } @@ -318,16 +321,35 @@ void DlgTrackInfo::populateCues(TrackPointer pTrack) { // Make the duration read only durationItem->setFlags(Qt::NoItemFlags); + + QComboBox* colorComboBox = new QComboBox(); + const QList predefinedColors = Color::predefinedColorSet.allColors; + for (int i = 0; i < predefinedColors.count(); i++) { + PredefinedColorPointer color = predefinedColors.at(i); + QColor defaultRgba = color->m_defaultRgba; + colorComboBox->addItem(color->m_sDisplayName, defaultRgba); + if (*color != *Color::predefinedColorSet.noColor) { + QPixmap pixmap(80,80); + pixmap.fill(defaultRgba); + QIcon icon(pixmap); + colorComboBox->setItemIcon(i, icon); + } + } + PredefinedColorPointer cueColor = pCue->getColor(); + colorComboBox->setCurrentIndex(Color::predefinedColorSet.predefinedColorIndex(cueColor)); + m_cueMap[row] = pCue; cueTable->insertRow(row); cueTable->setItem(row, 0, new QTableWidgetItem(rowStr)); cueTable->setItem(row, 1, durationItem); cueTable->setItem(row, 2, new QTableWidgetItem(hotcue)); - cueTable->setItem(row, 3, new QTableWidgetItem(pCue->getLabel())); + cueTable->setCellWidget(row, 3, colorComboBox); + cueTable->setItem(row, 4, new QTableWidgetItem(pCue->getLabel())); row += 1; } cueTable->setSortingEnabled(true); cueTable->horizontalHeader()->setStretchLastSection(true); + cueTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::ResizeToContents); } void DlgTrackInfo::saveTrack() { @@ -367,10 +389,13 @@ void DlgTrackInfo::saveTrack() { for (int row = 0; row < cueTable->rowCount(); ++row) { QTableWidgetItem* rowItem = cueTable->item(row, 0); QTableWidgetItem* hotcueItem = cueTable->item(row, 2); - QTableWidgetItem* labelItem = cueTable->item(row, 3); + QWidget* colorWidget = cueTable->cellWidget(row, 3); + QTableWidgetItem* labelItem = cueTable->item(row, 4); - if (!rowItem || !hotcueItem || !labelItem) + VERIFY_OR_DEBUG_ASSERT(rowItem && hotcueItem && colorWidget && labelItem) { + qWarning() << "unable to retrieve cells from cueTable row"; continue; + } int oldRow = rowItem->data(Qt::DisplayRole).toInt(); CuePointer pCue(m_cueMap.value(oldRow, CuePointer())); @@ -390,6 +415,13 @@ void DlgTrackInfo::saveTrack() { pCue->setHotCue(-1); } + auto colorComboBox = qobject_cast(colorWidget); + if (colorComboBox) { + PredefinedColorPointer color = Color::predefinedColorSet.allColors.at(colorComboBox->currentIndex()); + pCue->setColor(color); + } + // do nothing for now. + QString label = labelItem->data(Qt::DisplayRole).toString(); pCue->setLabel(label); } @@ -430,7 +462,6 @@ void DlgTrackInfo::unloadTrack(bool save) { } void DlgTrackInfo::clear() { - disconnect(this, SLOT(updateTrackMetadata())); m_pLoadedTrack.reset(); diff --git a/src/library/dlgtrackinfo.ui b/src/library/dlgtrackinfo.ui index d839df7fc1a3..a89817273e3e 100644 --- a/src/library/dlgtrackinfo.ui +++ b/src/library/dlgtrackinfo.ui @@ -820,6 +820,11 @@ Often results in higher quality beatgrids, but will not do well on tracks that h Hotcue + + + Color + + Label diff --git a/src/library/itunes/itunesfeature.cpp b/src/library/itunes/itunesfeature.cpp index 43842fd35e4d..3c2ee57ece68 100644 --- a/src/library/itunes/itunesfeature.cpp +++ b/src/library/itunes/itunesfeature.cpp @@ -284,8 +284,7 @@ void ITunesFeature::guessMusicLibraryMountpoint(QXmlStreamReader& xml) { // iTunes/Album Artwork // iTunes/iTunes Media <- this is the "Music Folder" // iTunes/iTunes Music Library.xml <- this location we already knew - QByteArray strlocbytes = xml.readElementText().toUtf8(); - QString music_folder = QUrl::fromEncoded(strlocbytes).toLocalFile(); + QString music_folder = QUrl(xml.readElementText()).toLocalFile(); QString music_folder_test = music_folder; music_folder_test.replace(localhost_token(), ""); @@ -577,8 +576,7 @@ void ITunesFeature::parseTrack(QXmlStreamReader& xml, QSqlQuery& query) { continue; } if (key == kLocation) { - QByteArray strlocbytes = content.toUtf8(); - location = QUrl::fromEncoded(strlocbytes).toLocalFile(); + location = QUrl(content).toLocalFile(); // Replace first part of location with the mixxx iTunes Root // on systems where iTunes installed it only strips //localhost // on iTunes from foreign systems the mount point is replaced diff --git a/src/library/parser.cpp b/src/library/parser.cpp index ecbdeff6c626..6b05e388ccd2 100644 --- a/src/library/parser.cpp +++ b/src/library/parser.cpp @@ -14,6 +14,7 @@ #include #include #include +#include #include "library/parser.h" @@ -22,33 +23,20 @@ **/ -Parser::Parser() : QObject() -{ +Parser::Parser() { } -Parser::~Parser() -{ - - +Parser::~Parser() { } -void Parser::clearLocations() -{ +void Parser::clearLocations() { m_sLocations.clear(); } -long Parser::countParsed() -{ +long Parser::countParsed() { return (long)m_sLocations.count(); } -bool Parser::isFilepath(QString sFilepath) { - QFile file(sFilepath); - bool exists = file.exists(); - file.close(); - return exists; -} - bool Parser::isBinary(QString filename) { QFile file(filename); @@ -159,3 +147,12 @@ bool Parser::isUtf8(const char* string) { return true; } + +QString Parser::playlistEntrytoLocalFile(const QString& playlistEntry) { + if (playlistEntry.startsWith("file:")) { + return QUrl(playlistEntry).toLocalFile(); + } + + return QString(playlistEntry).replace('\\', '/'); +} + diff --git a/src/library/parser.h b/src/library/parser.h index 50e4464b7601..64ecc22630fc 100644 --- a/src/library/parser.h +++ b/src/library/parser.h @@ -25,6 +25,8 @@ it afterwards for proper functioning #include #include +#include + class Parser : public QObject { public: static bool isPlaylistFilenameSupported(const QString& fileName) { @@ -35,7 +37,7 @@ class Parser : public QObject { } Parser(); - ~Parser(); + ~Parser() override; /**Can be called to parse a pls file Note for developers: This function should return an empty PtrList @@ -44,18 +46,20 @@ class Parser : public QObject { protected: - /**Pointer to the parsed Filelocations**/ + // Pointer to the parsed Filelocations QList m_sLocations; - /**Returns the number of parsed locations**/ + // Returns the number of parsed locations long countParsed(); - /**Clears m_psLocations**/ + // Clears m_psLocations void clearLocations(); - /**Checks if the file does contain binary content**/ + // Checks if the file does contain binary content bool isBinary(QString); - /**Checks if the given string represents a local filepath**/ - bool isFilepath(QString); // check for Utf8 encoding static bool isUtf8(const char* string); + // reads URLs an plain Parth and retuns a local file path + QString playlistEntrytoLocalFile(const QString& playlistEntry); + + FRIEND_TEST(PlaylistTest, Normalize); }; #endif diff --git a/src/library/parserm3u.cpp b/src/library/parserm3u.cpp index c97d44b826ff..b770242ff232 100644 --- a/src/library/parserm3u.cpp +++ b/src/library/parserm3u.cpp @@ -96,11 +96,8 @@ QList ParserM3u::parse(QString sFilename) } -QString ParserM3u::getFilepath(QTextStream* stream, QString basepath) -{ - QString textline,filename = ""; - - textline = stream->readLine(); +QString ParserM3u::getFilepath(QTextStream* stream, QString basepath) { + QString textline = stream->readLine(); while (!textline.isEmpty()) { //qDebug() << "Untransofrmed text: " << textline; @@ -109,23 +106,17 @@ QString ParserM3u::getFilepath(QTextStream* stream, QString basepath) } if (!textline.contains("#")) { - filename = textline; - filename.remove("file://"); - QByteArray strlocbytes = filename.toUtf8(); - //qDebug() << "QByteArray UTF-8: " << strlocbytes; - QUrl location = QUrl::fromEncoded(strlocbytes); - //qDebug() << "QURL UTF-8: " << location; - QString trackLocation = location.toString(); - //qDebug() << "UTF8 TrackLocation:" << trackLocation; - if(isFilepath(trackLocation)) { + QString trackLocation = playlistEntrytoLocalFile(textline); + if(QFile::exists(trackLocation)) { return trackLocation; } else { // Try relative to m3u dir QString rel = QDir(basepath).filePath(trackLocation); - if (isFilepath(rel)) { + if (QFile::exists(rel)) { return rel; } // We couldn't match this to a real file so ignore it + qWarning() << trackLocation << "not found"; } } textline = stream->readLine(); diff --git a/src/library/parserpls.cpp b/src/library/parserpls.cpp index 310200c8a447..ee58887961c0 100644 --- a/src/library/parserpls.cpp +++ b/src/library/parserpls.cpp @@ -28,16 +28,13 @@ not only the filepath; **/ -ParserPls::ParserPls() : Parser() -{ +ParserPls::ParserPls() : Parser() { } -ParserPls::~ParserPls() -{ +ParserPls::~ParserPls() { } -QList ParserPls::parse(QString sFilename) -{ +QList ParserPls::parse(QString sFilename) { //long numEntries =0; QFile file(sFilename); QString basepath = sFilename.section('/', 0, -2); @@ -84,9 +81,7 @@ QList ParserPls::parse(QString sFilename) return QList(); //if we get here something went wrong :D } -long ParserPls::getNumEntries(QTextStream *stream) -{ - +long ParserPls::getNumEntries(QTextStream *stream) { QString textline; textline = stream->readLine(); @@ -107,38 +102,30 @@ long ParserPls::getNumEntries(QTextStream *stream) } -QString ParserPls::getFilepath(QTextStream *stream, QString basepath) -{ - QString textline,filename = ""; - textline = stream->readLine(); +QString ParserPls::getFilepath(QTextStream *stream, QString basepath) { + QString textline = stream->readLine(); while (!textline.isEmpty()) { if (textline.isNull()) { break; } if(textline.contains("File")) { - int iPos = textline.indexOf("=",0); + int iPos = textline.indexOf("=", 0); ++iPos; - filename = textline.right(textline.length()-iPos); - - //Rythmbox playlists starts with file:// - //We remove the file protocol if found. - filename.remove("file://"); - QByteArray strlocbytes = filename.toUtf8(); - QUrl location = QUrl::fromEncoded(strlocbytes); - QString trackLocation = location.toString(); - //qDebug() << trackLocation; + QString filename = textline.right(textline.length() - iPos); + QString trackLocation = playlistEntrytoLocalFile(filename); - if(isFilepath(trackLocation)) { + if(QFile::exists(trackLocation)) { return trackLocation; } else { - // Try relative to m3u dir + // Try relative to pls dir QString rel = QDir(basepath).filePath(trackLocation); - if (isFilepath(rel)) { + if (QFile::exists(rel)) { return rel; } // We couldn't match this to a real file so ignore it + qWarning() << trackLocation << "not found"; } } textline = stream->readLine(); @@ -146,8 +133,8 @@ QString ParserPls::getFilepath(QTextStream *stream, QString basepath) // Signal we reached the end return 0; - } + bool ParserPls::writePLSFile(const QString &file_str, QList &items, bool useRelativePath) { QFile file(file_str); diff --git a/src/library/rhythmbox/rhythmboxfeature.cpp b/src/library/rhythmbox/rhythmboxfeature.cpp index 370c7c276cb5..deac4379c1c3 100644 --- a/src/library/rhythmbox/rhythmboxfeature.cpp +++ b/src/library/rhythmbox/rhythmboxfeature.cpp @@ -325,7 +325,7 @@ void RhythmboxFeature::importTrack(QXmlStreamReader &xml, QSqlQuery &query) { continue; } if (xml.name() == "location") { - locationUrl = QUrl::fromEncoded(xml.readElementText().toUtf8()); + locationUrl = QUrl(xml.readElementText()); continue; } } @@ -377,8 +377,7 @@ void RhythmboxFeature::importPlaylist(QXmlStreamReader &xml, if (xml.isStartElement() && xml.name() == "location") { QString location = xml.readElementText(); location.remove("file://"); - QByteArray strlocbytes = location.toUtf8(); - QUrl locationUrl = QUrl::fromEncoded(strlocbytes); + QUrl locationUrl = QUrl(location); location = locationUrl.toLocalFile(); //get the ID of the file in the rhythmbox_library table diff --git a/src/library/starrating.cpp b/src/library/starrating.cpp index 8e7bb37e7ee9..68de743992a8 100644 --- a/src/library/starrating.cpp +++ b/src/library/starrating.cpp @@ -20,14 +20,26 @@ #include "library/starrating.h" #include "util/math.h" +// Magic number? Explain what this factor affects and how const int PaintingScaleFactor = 15; StarRating::StarRating(int starCount, int maxStarCount) : m_myStarCount(starCount), m_myMaxStarCount(maxStarCount) { + // 1st star cusp at 0° of the unit circle whose center is shifted to adapt the 0,0-based paint area m_starPolygon << QPointF(1.0, 0.5); - for (int i = 1; i < 5; ++i) + for (int i = 1; i < 5; ++i) { + // add QPointF 2-5 to polygon point array, equally distributed on a circumference. + // To create a star (not a pentagon) we need to connect every second point. + // + // understand those equations m_starPolygon << QPointF(0.5 + 0.5 * cos(0.8 * i * 3.14), 0.5 + 0.5 * sin(0.8 * i * 3.14)); + float QPointFx = 0.5 + 0.5 * cos(0.8 * i * 3.14); + float QPointFy = 0.5 + 0.5 * sin(0.8 * i * 3.14); +// qDebug() << "Debug: star point #" << i << ": " << QPointFx << ", " << QPointFy; + } + // creates 5 points for a tiny diamond/rhombe (square turned by 45°) + // why do we need 5 cusps here? 4 should suffice as the polygon is closed automatically for the star above.. m_diamondPolygon << QPointF(0.4, 0.5) << QPointF(0.5, 0.4) << QPointF(0.6, 0.5) << QPointF(0.5, 0.6) << QPointF(0.4, 0.5); } diff --git a/src/mixer/basetrackplayer.cpp b/src/mixer/basetrackplayer.cpp index ac419f0400d6..deb9bd3bea3b 100644 --- a/src/mixer/basetrackplayer.cpp +++ b/src/mixer/basetrackplayer.cpp @@ -118,8 +118,6 @@ BaseTrackPlayerImpl::BaseTrackPlayerImpl(QObject* pParent, m_pPlay->connectValueChanged(this, &BaseTrackPlayerImpl::slotPlayToggled); pVisualsManager->addDeck(group); - - m_cloneTimer.start(); } BaseTrackPlayerImpl::~BaseTrackPlayerImpl() { @@ -265,18 +263,6 @@ void BaseTrackPlayerImpl::disconnectLoadedTrack() { } void BaseTrackPlayerImpl::slotLoadTrack(TrackPointer pNewTrack, bool bPlay) { - mixxx::Duration elapsed = m_cloneTimer.restart(); - if (elapsed < mixxx::Duration::fromSeconds(0.5)) { - // load pressed twice quickly, clone instead of loading - EngineChannel* pChannel = m_pEngineMaster->getEngineSync()->pickNonSyncSyncTarget(m_pChannel); - slotCloneChannel(pChannel); - } - else { - slotLoadTrackInternal(pNewTrack, bPlay); - } -} - -void BaseTrackPlayerImpl::slotLoadTrackInternal(TrackPointer pNewTrack, bool bPlay) { qDebug() << "BaseTrackPlayerImpl::slotLoadTrack" << getGroup(); // Before loading the track, ensure we have access. This uses lazy // evaluation to make sure track isn't NULL before we dereference it. @@ -439,7 +425,11 @@ TrackPointer BaseTrackPlayerImpl::getLoadedTrack() const { return m_pLoadedTrack; } -void BaseTrackPlayerImpl::slotCloneDeck(const QString& group) { +void BaseTrackPlayerImpl::slotCloneDeck() { + slotCloneChannel(m_pEngineMaster->getEngineSync()->pickNonSyncSyncTarget(m_pChannel)); +} + +void BaseTrackPlayerImpl::slotCloneFromGroup(const QString& group) { EngineChannel* pChannel = m_pEngineMaster->getChannel(group); if (!pChannel) { return; @@ -451,9 +441,9 @@ void BaseTrackPlayerImpl::slotCloneDeck(const QString& group) { void BaseTrackPlayerImpl::slotCloneFromDeck(double d) { int deck = std::lround(d); if (deck < 1) { - slotCloneChannel(m_pEngineMaster->getEngineSync()->pickNonSyncSyncTarget(m_pChannel)); + slotCloneDeck(); } else { - slotCloneDeck(PlayerManager::groupForDeck(deck-1)); + slotCloneFromGroup(PlayerManager::groupForDeck(deck-1)); } } @@ -474,7 +464,7 @@ void BaseTrackPlayerImpl::slotCloneChannel(EngineChannel* pChannel) { return; } - slotLoadTrackInternal(pTrack, false); + slotLoadTrack(pTrack, false); } void BaseTrackPlayerImpl::slotSetReplayGain(mixxx::ReplayGain replayGain) { diff --git a/src/mixer/basetrackplayer.h b/src/mixer/basetrackplayer.h index 61e33a4fd96b..b47ca81f405b 100644 --- a/src/mixer/basetrackplayer.h +++ b/src/mixer/basetrackplayer.h @@ -11,7 +11,6 @@ #include "mixer/baseplayer.h" #include "track/track.h" #include "util/memory.h" -#include "util/performancetimer.h" class EngineMaster; class ControlObject; @@ -39,8 +38,8 @@ class BaseTrackPlayer : public BasePlayer { public slots: virtual void slotLoadTrack(TrackPointer pTrack, bool bPlay = false) = 0; - virtual void slotCloneChannel(EngineChannel* pChannel) = 0; - virtual void slotCloneDeck(const QString& group) = 0; + virtual void slotCloneFromGroup(const QString& group) = 0; + virtual void slotCloneDeck() = 0; signals: void newTrackLoaded(TrackPointer pLoadedTrack); @@ -77,15 +76,15 @@ class BaseTrackPlayerImpl : public BaseTrackPlayer { public slots: void slotLoadTrack(TrackPointer track, bool bPlay) final; - void slotCloneChannel(EngineChannel* pChannel) final; - void slotCloneDeck(const QString& group) final; + void slotCloneFromGroup(const QString& group) final; + void slotCloneDeck() final; void slotTrackLoaded(TrackPointer pNewTrack, TrackPointer pOldTrack); void slotLoadFailed(TrackPointer pTrack, QString reason); void slotSetReplayGain(mixxx::ReplayGain replayGain); void slotPlayToggled(double); private slots: - void slotLoadTrackInternal(TrackPointer pNewTrack, bool bPlay); + void slotCloneChannel(EngineChannel* pChannel); void slotCloneFromDeck(double deck); void slotPassthroughEnabled(double v); void slotVinylControlEnabled(double v); @@ -109,7 +108,6 @@ class BaseTrackPlayerImpl : public BaseTrackPlayer { EngineDeck* m_pChannel; bool m_replaygainPending; EngineChannel* m_pChannelToCloneFrom; - PerformanceTimer m_cloneTimer; // Deck clone control std::unique_ptr m_pCloneFromDeck; diff --git a/src/mixer/playermanager.cpp b/src/mixer/playermanager.cpp index 28bf0812684d..4d95dd9bc7c3 100644 --- a/src/mixer/playermanager.cpp +++ b/src/mixer/playermanager.cpp @@ -78,6 +78,8 @@ PlayerManager::PlayerManager(UserSettingsPointer pConfig, // This is parented to the PlayerManager so does not need to be deleted m_pSamplerBank = new SamplerBank(this); + + m_cloneTimer.start(); } PlayerManager::~PlayerManager() { @@ -564,7 +566,7 @@ void PlayerManager::slotCloneDeck(QString source_group, QString target_group) { return; } - pPlayer->slotCloneDeck(source_group); + pPlayer->slotCloneFromGroup(source_group); } void PlayerManager::slotLoadTrackToPlayer(TrackPointer pTrack, QString group, bool play) { @@ -577,7 +579,15 @@ void PlayerManager::slotLoadTrackToPlayer(TrackPointer pTrack, QString group, bo return; } - pPlayer->slotLoadTrack(pTrack, play); + mixxx::Duration elapsed = m_cloneTimer.restart(); + if (m_lastLoadedPlayer == group && elapsed < mixxx::Duration::fromSeconds(0.5)) { + // load pressed twice quickly, clone instead of loading + pPlayer->slotCloneDeck(); + } else { + pPlayer->slotLoadTrack(pTrack, play); + } + + m_lastLoadedPlayer = group; } void PlayerManager::slotLoadToPlayer(QString location, QString group) { diff --git a/src/mixer/playermanager.h b/src/mixer/playermanager.h index a80731c041f6..629428e1dc90 100644 --- a/src/mixer/playermanager.h +++ b/src/mixer/playermanager.h @@ -12,6 +12,7 @@ #include "analyzer/trackanalysisscheduler.h" #include "preferences/usersettings.h" #include "track/track.h" +#include "util/performancetimer.h" class Auxiliary; class BaseTrackPlayer; @@ -250,6 +251,9 @@ class PlayerManager : public QObject, public PlayerManagerInterface { // Used to protect access to PlayerManager state across threads. mutable QMutex m_mutex; + PerformanceTimer m_cloneTimer; + QString m_lastLoadedPlayer; + UserSettingsPointer m_pConfig; SoundManager* m_pSoundManager; EffectsManager* m_pEffectsManager; diff --git a/src/preferences/dialog/dlgprefinterface.cpp b/src/preferences/dialog/dlgprefinterface.cpp index 95d33da72bb3..2c1e151ea180 100644 --- a/src/preferences/dialog/dlgprefinterface.cpp +++ b/src/preferences/dialog/dlgprefinterface.cpp @@ -70,6 +70,8 @@ DlgPrefInterface::DlgPrefInterface(QWidget * parent, MixxxMainWindow * mixxx, warningLabel->setText(warningString); ComboBoxSkinconf->clear(); + // align left edge of preview image with comboboxes + skinPreviewLabel->setStyleSheet("QLabel { margin-left: 2px; }"); skinPreviewLabel->setText(""); QList skinSearchPaths = m_pSkinLoader->getSkinSearchPaths(); @@ -93,7 +95,9 @@ DlgPrefInterface::DlgPrefInterface(QWidget * parent, MixxxMainWindow * mixxx, if (skinInfo.absoluteFilePath() == configuredSkinPath) { m_skin = skinInfo.fileName(); ComboBoxSkinconf->setCurrentIndex(index); - skinPreviewLabel->setPixmap(m_pSkinLoader->getSkinPreview(m_skin)); + // schemes must be updated here to populate the drop-down box and set m_colorScheme + slotUpdateSchemes(); + skinPreviewLabel->setPixmap(m_pSkinLoader->getSkinPreview(m_skin, m_colorScheme)); if (size_ok) { warningLabel->hide(); } else { @@ -106,7 +110,6 @@ DlgPrefInterface::DlgPrefInterface(QWidget * parent, MixxxMainWindow * mixxx, connect(ComboBoxSkinconf, SIGNAL(activated(int)), this, SLOT(slotSetSkin(int))); connect(ComboBoxSchemeconf, SIGNAL(activated(int)), this, SLOT(slotSetScheme(int))); - slotUpdateSchemes(); #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) @@ -181,6 +184,7 @@ DlgPrefInterface::~DlgPrefInterface() { } void DlgPrefInterface::slotUpdateSchemes() { + // Re-populates the scheme combobox and attempts to pick the color scheme from config file. // Since this involves opening a file we won't do this as part of regular slotUpdate QList schlist = LegacySkinParser::getSchemeList( m_pSkinLoader->getSkinPath(m_skin)); @@ -191,16 +195,28 @@ void DlgPrefInterface::slotUpdateSchemes() { ComboBoxSchemeconf->setEnabled(false); ComboBoxSchemeconf->addItem(tr("This skin does not support color schemes", 0)); ComboBoxSchemeconf->setCurrentIndex(0); + // clear m_colorScheme so that SkinLoader::getSkinPreview returns the correct preview + m_colorScheme = QString(); } else { ComboBoxSchemeconf->setEnabled(true); - QString selectedScheme = m_pConfig->getValueString(ConfigKey("[Config]", "Scheme")); + QString configScheme = m_pConfig->getValueString(ConfigKey("[Config]", "Scheme")); + bool foundConfigScheme = false; for (int i = 0; i < schlist.size(); i++) { ComboBoxSchemeconf->addItem(schlist[i]); - if (schlist[i] == selectedScheme) { + if (schlist[i] == configScheme) { ComboBoxSchemeconf->setCurrentIndex(i); + m_colorScheme = configScheme; + foundConfigScheme = true; } } + // There might be a skin configured that has color schemes but none of them + // matches the configured color scheme. + // The combobox would pick the first item then. Also choose this item for + // m_colorScheme to avoid an empty skin preview. + if (!foundConfigScheme) { + m_colorScheme = schlist[0]; + } } } @@ -311,11 +327,11 @@ void DlgPrefInterface::slotSetScheme(int) { m_colorScheme = newScheme; m_bRebootMixxxView = true; } + skinPreviewLabel->setPixmap(m_pSkinLoader->getSkinPreview(m_skin, m_colorScheme)); } void DlgPrefInterface::slotSetSkin(int) { QString newSkin = ComboBoxSkinconf->currentText(); - skinPreviewLabel->setPixmap(m_pSkinLoader->getSkinPreview(newSkin)); if (newSkin != m_skin) { m_skin = newSkin; m_bRebootMixxxView = newSkin != m_skinOnUpdate; @@ -323,6 +339,7 @@ void DlgPrefInterface::slotSetSkin(int) { ? warningLabel->hide() : warningLabel->show(); slotUpdateSchemes(); } + skinPreviewLabel->setPixmap(m_pSkinLoader->getSkinPreview(newSkin, m_colorScheme)); } void DlgPrefInterface::slotApply() { diff --git a/src/preferences/dialog/dlgprefinterfacedlg.ui b/src/preferences/dialog/dlgprefinterfacedlg.ui index 317991cffba5..fd8a432e93ea 100644 --- a/src/preferences/dialog/dlgprefinterfacedlg.ui +++ b/src/preferences/dialog/dlgprefinterfacedlg.ui @@ -20,14 +20,7 @@ Interface options - - - - skin preview screenshot goes here - - - - + Screen saver @@ -66,16 +59,6 @@ - - - - - - - Qt::AlignJustify|Qt::AlignVCenter - - - @@ -92,7 +75,7 @@ - + Locale @@ -102,14 +85,14 @@ - + Locales determine country and language specific settings. - + Full-screen mode @@ -119,14 +102,14 @@ - + Start in full-screen mode - + true @@ -145,7 +128,7 @@ - + @@ -179,7 +162,7 @@ - + HiDPI / Retina scaling @@ -189,7 +172,7 @@ - + Change the size of text, buttons, and other items. @@ -211,7 +194,7 @@ - + Adopt scale factor from the operating system @@ -221,7 +204,7 @@ - + @@ -243,6 +226,23 @@ + + + + skin preview screenshot goes here + + + + + + + + + + Qt::AlignJustify|Qt::AlignVCenter + + + @@ -314,7 +314,7 @@ - + diff --git a/src/skin/skincontext.h b/src/skin/skincontext.h index 205a204aedc4..6773e2c39b57 100644 --- a/src/skin/skincontext.h +++ b/src/skin/skincontext.h @@ -12,8 +12,10 @@ #include #include +#include "../util/color/predefinedcolorsrepresentation.h" #include "preferences/usersettings.h" #include "skin/pixmapsource.h" +#include "util/color/color.h" #include "widget/wsingletoncontainer.h" #include "widget/wpixmapstore.h" @@ -266,6 +268,20 @@ class SkinContext { return m_scaleFactor; } + PredefinedColorsRepresentation getCueColorRepresentation(const QDomNode& node, QColor defaultColor) const { + PredefinedColorsRepresentation colorRepresentation = Color::predefinedColorSet.defaultRepresentation(); + for (PredefinedColorPointer color : Color::predefinedColorSet.allColors) { + QString sColorName(color->m_sName); + QColor skinRgba = selectColor(node, "Cue" + sColorName); + if (skinRgba.isValid()) { + PredefinedColorPointer originalColor = Color::predefinedColorSet.predefinedColorFromName(sColorName); + colorRepresentation.setCustomRgba(originalColor, skinRgba); + } + } + colorRepresentation.setCustomRgba(Color::predefinedColorSet.noColor, defaultColor); + return colorRepresentation; + } + private: PixmapSource getPixmapSourceInner(const QString& filename) const; diff --git a/src/skin/skinloader.cpp b/src/skin/skinloader.cpp index f03e3e3064e7..7d19ae964668 100644 --- a/src/skin/skinloader.cpp +++ b/src/skin/skinloader.cpp @@ -56,12 +56,21 @@ QString SkinLoader::getSkinPath(const QString& skinName) const { return QString(); } -QPixmap SkinLoader::getSkinPreview(const QString& skinName) const { - QPixmap preview(getSkinPath(skinName) + "/preferences_preview_screenshot.png"); +QPixmap SkinLoader::getSkinPreview(const QString& skinName, const QString& schemeName) const { + qDebug() << "schemeName =" << schemeName; + QPixmap preview; + if (!schemeName.isEmpty()) { + QString schemeNameUnformatted = schemeName; + QString schemeNameFormatted = schemeNameUnformatted.replace(" ",""); + preview.load(getSkinPath(skinName) + "/skin_preview_" + schemeNameFormatted + ".png"); + } else { + preview.load(getSkinPath(skinName) + "/skin_preview.png"); + } if (!preview.isNull()){ return preview; } - return QPixmap(":/images/skin_preview_placeholder.png"); + preview.load(":/images/skin_preview_placeholder.png"); + return preview; } QString SkinLoader::getConfiguredSkinPath() const { diff --git a/src/skin/skinloader.h b/src/skin/skinloader.h index e31741823e10..d6e45486b646 100644 --- a/src/skin/skinloader.h +++ b/src/skin/skinloader.h @@ -33,7 +33,7 @@ class SkinLoader { LaunchImage* loadLaunchImage(QWidget* pParent); QString getSkinPath(const QString& skinName) const; - QPixmap getSkinPreview(const QString& skinName) const; + QPixmap getSkinPreview(const QString& skinName, const QString& schemeName) const; QString getConfiguredSkinPath() const; QString getDefaultSkinName() const; QList getSkinSearchPaths() const; diff --git a/src/sources/metadatasourcetaglib.cpp b/src/sources/metadatasourcetaglib.cpp index 846ffe0cf616..5a4f2abf4a4f 100644 --- a/src/sources/metadatasourcetaglib.cpp +++ b/src/sources/metadatasourcetaglib.cpp @@ -627,22 +627,53 @@ class AiffTagSaver: public TagSaver { */ class SafelyWritableFile final { public: - SafelyWritableFile(QString origFileName, bool useTemporaryFile) - : m_origFileName(std::move(origFileName)) { + SafelyWritableFile(QString origFileName, bool useTemporaryFile) { + // Both file names remain uninitialized until all prerequisite operations + // in the constructor have been completed successfully. Otherwise failure + // to create the temporary file will not be handled correctly! + // See also: https://bugs.launchpad.net/mixxx/+bug/1815305 + DEBUG_ASSERT(m_origFileName.isNull()); DEBUG_ASSERT(m_tempFileName.isNull()); if (useTemporaryFile) { - QString tempFileName = m_origFileName + kSafelyWritableTempFileSuffix; - QFile origFile(m_origFileName); - if (origFile.copy(tempFileName)) { - m_tempFileName = std::move(tempFileName); - } else { + QString tempFileName = origFileName + kSafelyWritableTempFileSuffix; + QFile origFile(origFileName); + if (!origFile.copy(tempFileName)) { kLogger.warning() << origFile.errorString() - << "- Failed to copy original into temporary file before writing:" - << origFile.fileName() + << "- Failed to clone original into temporary file before writing:" + << origFileName << "->" << tempFileName; + // Abort constructor + return; + } + QFile tempFile(tempFileName); + DEBUG_ASSERT(tempFile.exists()); + // Both file sizes are expected to be equal after successfully + // copying the file contents. + VERIFY_OR_DEBUG_ASSERT(origFile.size() == tempFile.size()) { + kLogger.warning() + << "Failed to verify size after cloning original into temporary file before writing:" + << origFile.size() + << "<>" + << tempFile.size(); + // Cleanup + if (tempFile.exists() && !tempFile.remove()) { + kLogger.warning() + << tempFile.errorString() + << "- Failed to remove temporary file:" + << tempFileName; + } + // Abort constructor + return; } + // Successfully cloned original into temporary file for writing - finish initialization + m_origFileName = std::move(origFileName); + m_tempFileName = std::move(tempFileName); + } else { + // Directly write into original file - finish initialization + m_origFileName = std::move(origFileName); + DEBUG_ASSERT(m_tempFileName.isNull()); } } ~SafelyWritableFile() { @@ -651,12 +682,18 @@ class SafelyWritableFile final { const QString& fileName() const { if (m_tempFileName.isNull()) { + // If m_tempFileName has not been initialized then no temporary + // copy was requested in the constructor. return m_origFileName; } else { return m_tempFileName; } } + bool isReady() const { + return !fileName().isEmpty(); + } + bool commit() { if (m_tempFileName.isNull()) { return true; // nothing to do @@ -723,10 +760,7 @@ class SafelyWritableFile final { return; // nothing to do } QFile tempFile(m_tempFileName); - if (!tempFile.exists()) { - return; // nothing to do - } - if (!tempFile.remove()) { + if (tempFile.exists() && !tempFile.remove()) { kLogger.warning() << tempFile.errorString() << "- Failed to remove temporary file:" @@ -755,6 +789,13 @@ MetadataSourceTagLib::exportTrackMetadata( << "with type" << m_fileType; SafelyWritableFile safelyWritableFile(m_fileName, kExportTrackMetadataIntoTemporaryFile); + if (!safelyWritableFile.isReady()) { + kLogger.warning() + << "Unable to export track metadata into file" + << m_fileName + << "- Please check file permissions and storage space"; + return afterExport(ExportResult::Failed); + } std::unique_ptr pTagSaver; switch (m_fileType) { diff --git a/src/sources/soundsourcemp3.cpp b/src/sources/soundsourcemp3.cpp index a4d1d89cbbef..d550b35208be 100644 --- a/src/sources/soundsourcemp3.cpp +++ b/src/sources/soundsourcemp3.cpp @@ -270,13 +270,20 @@ SoundSource::OpenResult SoundSourceMp3::tryOpen( } const ChannelCount madChannelCount(MAD_NCHANNELS(&madHeader)); - if (maxChannelCount.valid() && (madChannelCount != maxChannelCount)) { - kLogger.warning() << "Differing number of channels" - << madChannelCount << "<>" << maxChannelCount - << "in some MP3 frame headers:" + if (madChannelCount.valid()) { + if (maxChannelCount.valid() && (madChannelCount != maxChannelCount)) { + kLogger.warning() + << "Differing number of channels" + << madChannelCount << "<>" << maxChannelCount + << "in MP3 frame headers:" + << m_file.fileName(); + } + maxChannelCount = math_max(madChannelCount, maxChannelCount); + } else { + kLogger.warning() + << "Missing number of channels in MP3 frame header:" << m_file.fileName(); } - maxChannelCount = math_max(madChannelCount, maxChannelCount); const int sampleRateIndex = getIndexBySampleRate(SampleRate(madSampleRate)); if (sampleRateIndex >= kSampleRateCount) { @@ -321,7 +328,7 @@ SoundSource::OpenResult SoundSourceMp3::tryOpen( if (m_seekFrameList.empty()) { // This is not a working MP3 file. - kLogger.warning() << "SSMP3: This is not a working MP3 file:" + kLogger.warning() << "This is not a working MP3 file:" << m_file.fileName(); // Abort return OpenResult::Failed; @@ -354,15 +361,24 @@ SoundSource::OpenResult SoundSourceMp3::tryOpen( kLogger.warning() << "Mixxx tries to plays it with the most common sample rate for this file"; } - if (mostCommonSampleRateIndex < kSampleRateCount) { - setSampleRate(getSampleRateByIndex(mostCommonSampleRateIndex)); - } else { - kLogger.warning() << "No single valid sample rate in header"; + // Initialize the AudioSource + if (mostCommonSampleRateIndex > kSampleRateCount) { + kLogger.warning() + << "Unknown sample rate in MP3 file:" + << m_file.fileName(); + // Abort + return OpenResult::Failed; + } + setSampleRate(getSampleRateByIndex(mostCommonSampleRateIndex)); + if (!maxChannelCount.valid() || (maxChannelCount > kChannelCountMax)) { + kLogger.warning() + << "Invalid number of channels" + << maxChannelCount + << "in MP3 file:" + << m_file.fileName(); // Abort return OpenResult::Failed; } - - // Initialize the AudioSource setChannelCount(maxChannelCount); initFrameIndexRangeOnce(IndexRange::forward(0, m_curFrameIndex)); diff --git a/src/test/autodjprocessor_test.cpp b/src/test/autodjprocessor_test.cpp index c731a6959fca..19b55b3a87a6 100644 --- a/src/test/autodjprocessor_test.cpp +++ b/src/test/autodjprocessor_test.cpp @@ -77,8 +77,8 @@ class FakeDeck : public BaseTrackPlayer { play.set(bPlay); } - MOCK_METHOD1(slotCloneChannel, void(EngineChannel* pChannel)); - MOCK_METHOD1(slotCloneDeck, void(const QString& group)); + MOCK_METHOD1(slotCloneFromGroup, void(const QString& group)); + MOCK_METHOD0(slotCloneDeck, void()); TrackPointer loadedTrack; ControlLinPotmeter playposition; diff --git a/src/test/controllerengine_test.cpp b/src/test/controllerengine_test.cpp index 2f5ba3cfa985..1c9876fdf77d 100644 --- a/src/test/controllerengine_test.cpp +++ b/src/test/controllerengine_test.cpp @@ -18,6 +18,7 @@ class ControllerEngineTest : public MixxxTest { mixxx::Time::setTestElapsedTime(mixxx::Duration::fromMillis(10)); QThread::currentThread()->setObjectName("Main"); cEngine = new ControllerEngine(nullptr); + pScriptEngine = cEngine->m_pEngine; ControllerDebug::enable(); cEngine->setPopups(false); } @@ -35,6 +36,7 @@ class ControllerEngineTest : public MixxxTest { } ControllerEngine *cEngine; + QScriptEngine *pScriptEngine; }; TEST_F(ControllerEngineTest, commonScriptHasNoErrors) { @@ -588,3 +590,25 @@ TEST_F(ControllerEngineTest, connectionExecutesWithCorrectThisObject) { // The counter should have been incremented exactly once. EXPECT_DOUBLE_EQ(1.0, pass->get()); } + +TEST_F(ControllerEngineTest, colorProxy) { + QList allColors = Color::predefinedColorSet.allColors; + for (int i = 0; i < allColors.length(); ++i) { + PredefinedColorPointer color = allColors[i]; + qDebug() << "Testing color " << color->m_sName; + QScriptValue jsColor = pScriptEngine->evaluate("color.predefinedColorFromId(" + QString::number(color->m_iId) + ")"); + EXPECT_EQ(jsColor.property("red").toInt32(), color->m_defaultRgba.red()); + EXPECT_EQ(jsColor.property("green").toInt32(), color->m_defaultRgba.green()); + EXPECT_EQ(jsColor.property("blue").toInt32(), color->m_defaultRgba.blue()); + EXPECT_EQ(jsColor.property("alpha").toInt32(), color->m_defaultRgba.alpha()); + EXPECT_EQ(jsColor.property("id").toInt32(), color->m_iId); + + QScriptValue jsColor2 = pScriptEngine->evaluate("color.predefinedColorsList()[" + + QString::number(i) + "]"); + EXPECT_EQ(jsColor2.property("red").toInt32(), color->m_defaultRgba.red()); + EXPECT_EQ(jsColor2.property("green").toInt32(), color->m_defaultRgba.green()); + EXPECT_EQ(jsColor2.property("blue").toInt32(), color->m_defaultRgba.blue()); + EXPECT_EQ(jsColor2.property("alpha").toInt32(), color->m_defaultRgba.alpha()); + EXPECT_EQ(jsColor2.property("id").toInt32(), color->m_iId); + } +} diff --git a/src/test/durationutiltest.cpp b/src/test/durationutiltest.cpp index 69307bda1be6..7c7f107db4e0 100644 --- a/src/test/durationutiltest.cpp +++ b/src/test/durationutiltest.cpp @@ -39,7 +39,7 @@ class DurationUtilTest : public testing::Test { EXPECT_EQ(expectedCentiseconds, actualCentiseconds); const QString actualMilliseconds = mixxx::Duration::formatTime(dSeconds, mixxx::Duration::Precision::MILLISECONDS); - EXPECT_EQ(actualMilliseconds, actualMilliseconds); + EXPECT_EQ(expectedMilliseconds, actualMilliseconds); } void formatSeconds(QString expectedMilliseconds, double dSeconds) { @@ -56,7 +56,7 @@ class DurationUtilTest : public testing::Test { EXPECT_EQ(expectedCentiseconds, actualCentiseconds); const QString actualMilliseconds = mixxx::Duration::formatSeconds(dSeconds, mixxx::Duration::Precision::MILLISECONDS); - EXPECT_EQ(actualMilliseconds, actualMilliseconds); + EXPECT_EQ(expectedMilliseconds, actualMilliseconds); } void formatSecondsLong(QString expectedMilliseconds, double dSeconds) { @@ -73,7 +73,7 @@ class DurationUtilTest : public testing::Test { EXPECT_EQ(expectedCentiseconds, actualCentiseconds); const QString actualMilliseconds = mixxx::Duration::formatSecondsLong(dSeconds, mixxx::Duration::Precision::MILLISECONDS); - EXPECT_EQ(actualMilliseconds, actualMilliseconds); + EXPECT_EQ(expectedMilliseconds, actualMilliseconds); } void formatKiloSeconds(QString expectedMilliseconds, double dSeconds) { @@ -90,7 +90,7 @@ class DurationUtilTest : public testing::Test { EXPECT_EQ(expectedCentiseconds, actualCentiseconds); const QString actualMilliseconds = mixxx::Duration::formatKiloSeconds(dSeconds, mixxx::Duration::Precision::MILLISECONDS); - EXPECT_EQ(actualMilliseconds, actualMilliseconds); + EXPECT_EQ(expectedMilliseconds, actualMilliseconds); } }; @@ -126,21 +126,23 @@ TEST_F(DurationUtilTest, formatSecondsLong) { formatSecondsLong("000.000", 0); formatSecondsLong("001.000", 1); formatSecondsLong("059.000", 59); + // TODO() rounding is done with single precision which fails using + // these high values. Find out why. Tested on Ubuntu Trusty. formatSecondsLong("321.123", 321.1234); - formatSecondsLong("321.124", 321.1235); - formatSecondsLong("4321.123", 4321.1234); + formatSecondsLong("321.124", 321.1236); + formatSecondsLong("4321.123", 4321.1230); } -TEST_F(DurationUtilTest, DISABLED_FormatKiloSeconds) { - formatKiloSeconds(QString::fromUtf8("0.000\u2009000"), 0); - formatKiloSeconds(QString::fromUtf8("0.001\u2009000"), 1); - formatKiloSeconds(QString::fromUtf8("0.001\u2009490"), 1.49); - formatKiloSeconds(QString::fromUtf8("0.059\u2009000"), 59); - formatKiloSeconds(QString::fromUtf8("0.061\u2009123"), 61.1234); - formatKiloSeconds(QString::fromUtf8("0.999\u2009990"), 999.99); - formatKiloSeconds(QString::fromUtf8("1.000\u2009000"), 1000.00); - formatKiloSeconds(QString::fromUtf8("86.400\u2009000"), 24 * 3600); +TEST_F(DurationUtilTest, FormatKiloSeconds) { + formatKiloSeconds(QStringLiteral(u"0.000\u2009000"), 0); + formatKiloSeconds(QStringLiteral(u"0.001\u2009000"), 1); + formatKiloSeconds(QStringLiteral(u"0.001\u2009490"), 1.49); + formatKiloSeconds(QStringLiteral(u"0.059\u2009000"), 59); + formatKiloSeconds(QStringLiteral(u"0.061\u2009123"), 61.1234); + formatKiloSeconds(QStringLiteral(u"0.999\u2009990"), 999.99); + formatKiloSeconds(QStringLiteral(u"1.000\u2009000"), 1000.00); + formatKiloSeconds(QStringLiteral(u"86.400\u2009000"), 24 * 3600); } diff --git a/src/test/playlisttest.cpp b/src/test/playlisttest.cpp new file mode 100644 index 000000000000..1b41ce281796 --- /dev/null +++ b/src/test/playlisttest.cpp @@ -0,0 +1,32 @@ +#include + +#include +#include +#include + +#include "library/parserm3u.h" + + +class PlaylistTest : public testing::Test {}; + +TEST_F(PlaylistTest, Normalize) { + ParserM3u parser; + + EXPECT_STREQ(parser.playlistEntrytoLocalFile("file:///foo/bar.mp3").toStdString().c_str(), + "/foo/bar.mp3"); + EXPECT_STREQ(parser.playlistEntrytoLocalFile("file:foo/bar.mp3").toStdString().c_str(), + "foo/bar.mp3"); +#ifdef Q_OS_WIN + EXPECT_STREQ(parser.playlistEntrytoLocalFile("file:///c:/foo/bar.mp3").toStdString().c_str(), + "c:/foo/bar.mp3"); +#else + EXPECT_STREQ(parser.playlistEntrytoLocalFile("file:///c:/foo/bar.mp3").toStdString().c_str(), + "/c:/foo/bar.mp3"); +#endif + EXPECT_STREQ(parser.playlistEntrytoLocalFile("file:///foo%20/bar.mp3").toStdString().c_str(), + "/foo /bar.mp3"); + EXPECT_STREQ(parser.playlistEntrytoLocalFile("c:/foo/bar.mp3").toStdString().c_str(), + "c:/foo/bar.mp3"); + EXPECT_STREQ(parser.playlistEntrytoLocalFile("c:\\foo\\bar.mp3").toStdString().c_str(), + "c:/foo/bar.mp3"); +} diff --git a/src/track/beatgrid.cpp b/src/track/beatgrid.cpp index 57ee8c97f0c0..b796a811a383 100644 --- a/src/track/beatgrid.cpp +++ b/src/track/beatgrid.cpp @@ -55,8 +55,7 @@ BeatGrid::BeatGrid( } BeatGrid::BeatGrid(const BeatGrid& other) - : QObject(), - m_mutex(QMutex::Recursive), + : m_mutex(QMutex::Recursive), m_subVersion(other.m_subVersion), m_iSampleRate(other.m_iSampleRate), m_grid(other.m_grid), diff --git a/src/track/beatgrid.h b/src/track/beatgrid.h index ea5aa32d2946..90ad5ca0c851 100644 --- a/src/track/beatgrid.h +++ b/src/track/beatgrid.h @@ -2,7 +2,6 @@ #define BEATGRID_H #include -#include #include "track/track.h" #include "track/beats.h" @@ -14,8 +13,7 @@ // BeatGrid is an implementation of the Beats interface that implements an // infinite grid of beats, aligned to a song simply by a starting offset of the // first beat and the song's average beats-per-minute. -class BeatGrid : public QObject, public virtual Beats { - Q_OBJECT +class BeatGrid final : public Beats { public: // Construct a BeatGrid. If a more accurate sample rate is known, provide it // in the iSampleRate parameter -- otherwise pass 0. @@ -73,9 +71,6 @@ class BeatGrid : public QObject, public virtual Beats { virtual void scale(enum BPMScale scale); virtual void setBpm(double dBpm); - signals: - void updated(); - private: BeatGrid(const BeatGrid& other); double firstBeatSample() const; diff --git a/src/track/beatmap.cpp b/src/track/beatmap.cpp index bb022a968414..b6460a527c9b 100644 --- a/src/track/beatmap.cpp +++ b/src/track/beatmap.cpp @@ -83,8 +83,7 @@ BeatMap::BeatMap(const Track& track, SINT iSampleRate, } BeatMap::BeatMap (const BeatMap& other) - : QObject(), - m_mutex(QMutex::Recursive), + : m_mutex(QMutex::Recursive), m_subVersion(other.m_subVersion), m_iSampleRate(other.m_iSampleRate), m_dCachedBpm(other.m_dCachedBpm), diff --git a/src/track/beatmap.h b/src/track/beatmap.h index 42da0d477ae3..184e28d35ca4 100644 --- a/src/track/beatmap.h +++ b/src/track/beatmap.h @@ -8,7 +8,6 @@ #ifndef BEATMAP_H_ #define BEATMAP_H_ -#include #include #include "track/track.h" @@ -19,8 +18,7 @@ typedef QList BeatList; -class BeatMap : public QObject, public Beats { - Q_OBJECT +class BeatMap final : public Beats { public: // Construct a BeatMap. iSampleRate may be provided if a more accurate // sample rate is known than the one associated with the Track. @@ -82,9 +80,6 @@ class BeatMap : public QObject, public Beats { virtual void scale(enum BPMScale scale); virtual void setBpm(double dBpm); - signals: - void updated(); - private: BeatMap(const BeatMap& other); bool readByteArray(const QByteArray& byteArray); diff --git a/src/track/beats.h b/src/track/beats.h index 11baf45bbf07..c0f1a5a8a4d1 100644 --- a/src/track/beats.h +++ b/src/track/beats.h @@ -1,6 +1,7 @@ #ifndef BEATS_H #define BEATS_H +#include #include #include #include @@ -25,7 +26,8 @@ class BeatIterator { // Beats is a pure abstract base class for BPM and beat management classes. It // provides a specification of all methods a beat-manager class must provide, as // well as a capability model for representing optional features. -class Beats { +class Beats : public QObject { + Q_OBJECT public: Beats() { } virtual ~Beats() { } @@ -161,6 +163,9 @@ class Beats { // Adjust the beats so the global average BPM matches dBpm. Beats class must // have the capability BEATSCAP_SET. virtual void setBpm(double dBpm) = 0; + + signals: + void updated(); }; #endif /* BEATS_H */ diff --git a/src/track/track.cpp b/src/track/track.cpp index 4581718469b9..184b81add878 100644 --- a/src/track/track.cpp +++ b/src/track/track.cpp @@ -353,23 +353,15 @@ void Track::setBeatsAndUnlock(QMutexLocker* pLock, BeatsPointer pBeats) { } if (m_pBeats) { - auto pObject = dynamic_cast(m_pBeats.data()); - if (pObject) { - disconnect(pObject, SIGNAL(updated()), - this, SLOT(slotBeatsUpdated())); - } + disconnect(m_pBeats.data(), &Beats::updated, this, &Track::slotBeatsUpdated); } - m_pBeats = pBeats; + m_pBeats = std::move(pBeats); auto bpmValue = mixxx::Bpm::kValueUndefined; if (m_pBeats) { bpmValue = m_pBeats->getBpm(); - auto pObject = dynamic_cast(m_pBeats.data()); - if (pObject) { - connect(pObject, SIGNAL(updated()), - this, SLOT(slotBeatsUpdated())); - } + connect(m_pBeats.data(), &Beats::updated, this, &Track::slotBeatsUpdated); } m_record.refMetadata().refTrackInfo().setBpm(mixxx::Bpm(bpmValue)); diff --git a/src/util/color/color.h b/src/util/color/color.h new file mode 100644 index 000000000000..df6eea05c60b --- /dev/null +++ b/src/util/color/color.h @@ -0,0 +1,72 @@ +#ifndef PREDEFINEDCOLORSET_H +#define PREDEFINEDCOLORSET_H + +#include +#include + +#include "predefinedcolorsset.h" +#include "util/math.h" + +namespace Color { + static const PredefinedColorsSet predefinedColorSet = PredefinedColorsSet(); + + // algorithm by http://www.nbdtech.com/Blog/archive/2008/04/27/Calculating-the-Perceived-Brightness-of-a-Color.aspx + // NOTE(Swiftb0y): please suggest if I should you use other methods + // (like the W3C algorithm) or if this approach is to to performance hungry + // NOTE: the author did not take alpha transparency into account! + static inline int brightness(int red, int green, int blue) { + return static_cast(sqrtf( + red * red * .241 + + green * green * .691 + + blue * blue * .068) + ); + }; + + static inline int brightness(const QColor& color) { + return brightness(color.red(), color.green(), color.red()); + } + + static inline bool isDimmColor(const QColor& color) { + return brightness(color) <= 127; + } + + // If the colorToChooseBy is darker than the global threshold, + // dimmColor will be returned. Otherwise brightColor will be returned. + static inline QColor chooseColorByBrightness(QColor colorToChooseBy, QColor dimmColor , QColor brightColor) { + return isDimmColor(colorToChooseBy) ? dimmColor : brightColor; + } + + // If the baseColor is darker than the global threshold, + // returns a lighter color, otherwise returns a darker color. + static inline QColor chooseContrastColor(QColor baseColor) { + // Will produce a color that is 60% brighter. + static const int iLighterFactor = 160; + // We consider a hsv color dark if its value is <= 20% of max value + static const int iMinimumValue = 20 * 255 / 100; + + // Convert to Hsv to make sure the conversion only happens once. + // QColor::darker() and QColor::lighter() internally convert from and to Hsv if the color is not already in Hsv. + baseColor = baseColor.toHsv(); + + QColor lightColor; + QColor darkColor = baseColor.darker().toRgb(); + // QColor::lighter() multiplies the HSV Value by some factor. When baseColor is dark, Value is near 0, + // thus after multiplication it's still near 0 and we get roughly the same color. + // We manually set lightColor in this case. + if (baseColor.value() <= iMinimumValue) { + lightColor = baseColor; + int newValue = iMinimumValue * iLighterFactor / 100; + lightColor.setHsl(baseColor.hue(), baseColor.saturation(), newValue); + } else { + lightColor = baseColor.lighter(iLighterFactor); + } + + // Even though we have the HSV representation fo the color here, the "value" component alone is + // not a good indicator of a color brightness (saturation comes into play too). + // That's why we call chooseColorByBrightness so the proper brightness of the color is used + // to choose between the light and the dark colors. + QColor contrastColor = chooseColorByBrightness(baseColor, lightColor.toRgb(), darkColor.toRgb()); + return contrastColor; + } +} +#endif /* PREDEFINEDCOLORSET_H */ diff --git a/src/util/color/predefinedcolor.cpp b/src/util/color/predefinedcolor.cpp new file mode 100644 index 000000000000..2a5db56e9873 --- /dev/null +++ b/src/util/color/predefinedcolor.cpp @@ -0,0 +1,10 @@ +#include "util/color/predefinedcolor.h" + +#include "util/color/color.h" + +PredefinedColor::PredefinedColor(QColor defaultRgba, QString sName, QString sDisplayName, int iId) + : m_defaultRgba(defaultRgba), + m_sName(sName), + m_sDisplayName(sDisplayName), + m_iId(iId) { +} diff --git a/src/util/color/predefinedcolor.h b/src/util/color/predefinedcolor.h new file mode 100644 index 000000000000..54e9eac02c79 --- /dev/null +++ b/src/util/color/predefinedcolor.h @@ -0,0 +1,38 @@ +#ifndef PREDEFINEDCOLOR_H +#define PREDEFINEDCOLOR_H + +#include + +#include "util/memory.h" + +// The PredefinedColor class is used to represent a Mixxx identificable color. +// A PredefinedColor can be uniquely identified with its m_iId property. +// +// PredefinedColors have a default Rgba value. A PredefinedColorsMap can provide with an alternative +// Rgba value for each PredefinedColor. Thus, a PredefinedColorsMap defines a particular way to render +// the PredefinedColors. +class PredefinedColor final { + public: + PredefinedColor(QColor defaultRgba, QString sName, QString sDisplayName, int iId); + + inline bool operator==(const PredefinedColor& other) const { + return m_iId == other.m_iId; + } + + inline bool operator!=(const PredefinedColor& other) const { + return m_iId != other.m_iId; + } + + // The QColor that is used by default to render this PredefinedColor. + const QColor m_defaultRgba; + // The name of the color used programmatically, e.g. on skin files. + const QString m_sName; + // The name of the color used on UI. + const QString m_sDisplayName; + // An Id uniquely identifying this predefined color. + // This value is used to identify a color on the DB and control objects. + const int m_iId; +}; +typedef std::shared_ptr PredefinedColorPointer; + +#endif /* PREDEFINEDCOLOR_H */ diff --git a/src/util/color/predefinedcolorsrepresentation.h b/src/util/color/predefinedcolorsrepresentation.h new file mode 100644 index 000000000000..83f16405447d --- /dev/null +++ b/src/util/color/predefinedcolorsrepresentation.h @@ -0,0 +1,41 @@ +#ifndef PREDEFINEDCOLORSREPRESENTATION_H +#define PREDEFINEDCOLORSREPRESENTATION_H + +#include +#include + +#include "util/color/predefinedcolor.h" + +// PredefinedColorsRepresentation defines a particular way to render Mixxx PredefinedColors. +// +// PredefinedColorsRepresentation maps a PredefinedColor to a custom Rgba color. +// Initially no color has a custom Rgba set. +// Call setCustomRgba(PredefinedColorPointer, QColor) to add a custom Rgba for a predefined color +// and customize the color map. +// +// This class uses the color's name() property as key, e.g. "#A9A9A9" +// Since QHash has copy-on-write, making a copy of PredefinedColorsRepresentation is fast. +// A deep copy of the QHash will be made when a copy is modified. +class PredefinedColorsRepresentation final { + public: + // Set a custom Rgba for a given color + void setCustomRgba(PredefinedColorPointer color, QColor cutomizedRgba) { + m_colorNameMap[color->m_defaultRgba.name()] = cutomizedRgba.name(); + } + + // Returns the custom Rgba of a color. + // If no custom Rgba is set for color, returns color->m_defaultRgba. + QColor representationFor(PredefinedColorPointer color) const { + QColor defaultRgba = color->m_defaultRgba; + if (m_colorNameMap.contains(defaultRgba.name())) { + return QColor(m_colorNameMap[defaultRgba.name()]); + } + return defaultRgba; + } + + + private: + QHash m_colorNameMap; +}; + +#endif /* PREDEFINEDCOLORSREPRESENTATION_H */ diff --git a/src/util/color/predefinedcolorsset.h b/src/util/color/predefinedcolorsset.h new file mode 100644 index 000000000000..0d9bc054c807 --- /dev/null +++ b/src/util/color/predefinedcolorsset.h @@ -0,0 +1,137 @@ +#ifndef COLOR_H +#define COLOR_H + +#include +#include + +#include "predefinedcolorsrepresentation.h" +#include "util/color/predefinedcolor.h" + +// This class defines a set of predefined colors and provides some handy functions to work with them. +// A single global instance is create in the Color namespace. +// This class is thread-safe because all its methods and public properties are const. +class PredefinedColorsSet final { + public: + const PredefinedColorPointer noColor = std::make_shared( + QColor(), + QLatin1String("No Color"), + QObject::tr("No Color"), + 0 + ); + const PredefinedColorPointer red = std::make_shared( + QColor("#E6194B"), + QLatin1String("Red"), + QObject::tr("Red"), + 1 + ); + const PredefinedColorPointer green = std::make_shared( + QColor("#3CB44B"), + QLatin1String("Green"), + QObject::tr("Green"), + 2 + ); + const PredefinedColorPointer yellow = std::make_shared( + QColor("#FFE119"), + QLatin1String("Yellow"), + QObject::tr("Yellow"), + 3 + ); + const PredefinedColorPointer blue = std::make_shared( + QColor("#4363D8"), + QLatin1String("Blue"), + QObject::tr("Blue"), + 4 + ); + const PredefinedColorPointer cyan = std::make_shared( + QColor("#42D4F4"), + QLatin1String("Cyan"), + QObject::tr("Cyan"), + 5 + ); + const PredefinedColorPointer magenta = std::make_shared( + QColor("#F032E6"), + QLatin1String("Magenta"), + QObject::tr("Magenta"), + 6 + ); + const PredefinedColorPointer pink = std::make_shared( + QColor("#FABEBE"), + QLatin1String("Pink"), + QObject::tr("Pink"), + 7 + ); + const PredefinedColorPointer teal = std::make_shared( + QColor("#469990"), + QLatin1String("Teal"), + QObject::tr("Teal"), + 8 + ); + const PredefinedColorPointer grey = std::make_shared( + QColor("#A9A9A9"), + QLatin1String("Grey"), + QObject::tr("Grey"), + 9 + ); + + // The list of the predefined colors. + const QList allColors { + noColor, + red, + green, + yellow, + blue, + cyan, + magenta, + pink, + teal, + grey, + }; + + PredefinedColorsSet() + : m_defaultRepresentation() { + + for (PredefinedColorPointer color : allColors) { + m_defaultRepresentation.setCustomRgba(color, color->m_defaultRgba); + } + } + + // Returns the position of a PredefinedColor in the allColors list. + int predefinedColorIndex(PredefinedColorPointer searchedColor) const { + for (int position = 0; position < allColors.count(); ++position) { + PredefinedColorPointer color(allColors.at(position)); + if (*color == *searchedColor) { + return position; + } + } + return 0; + }; + + // Return a predefined color from its name. + // Return noColor if there's no color with such name. + PredefinedColorPointer predefinedColorFromName(QString name) const { + for (PredefinedColorPointer color : allColors) { + if (color->m_sName == name) { + return color; + } + } + return noColor; + }; + + // Return a predefined color from its id. + // Return noColor if there's no color with iId. + PredefinedColorPointer predefinedColorFromId(int iId) const { + for (PredefinedColorPointer color : allColors) { + if (color->m_iId == iId) { + return color; + } + } + return noColor; + }; + + // The default color representation, i.e. maps each predefined color to its default Rgba. + PredefinedColorsRepresentation defaultRepresentation() const { return m_defaultRepresentation; }; + + private: + PredefinedColorsRepresentation m_defaultRepresentation; +}; +#endif /* COLOR_H */ diff --git a/src/util/denormalsarezero.h b/src/util/denormalsarezero.h index 0608154d02f3..694463d03947 100644 --- a/src/util/denormalsarezero.h +++ b/src/util/denormalsarezero.h @@ -1,6 +1,5 @@ -#ifndef DENORMALSAREZERO_H -#define DENORMALSAREZERO_H +#pragma once // This was copied from the gcc header pmmintrin.h which requires SSE3 // According to https://gcc.gnu.org/bugzilla/show_bug.cgi?id=21408 @@ -12,18 +11,29 @@ // See: https://software.intel.com/en-us/articles/x87-and-sse-floating-point-assists-in-ia-32-flush-to-zero-ftz-and-denormals-are-zero-daz /* Additional bits in the MXCSR. */ +#if !defined(_MM_DENORMALS_ZERO_MASK) #define _MM_DENORMALS_ZERO_MASK 0x0040 +#endif +#if !defined(_MM_DENORMALS_ZERO_ON) #define _MM_DENORMALS_ZERO_ON 0x0040 +#endif +#if !defined(_MM_DENORMALS_ZERO_OFF) #define _MM_DENORMALS_ZERO_OFF 0x0000 +#endif #ifdef __SSE__ #include +#if !defined(_MM_SET_DENORMALS_ZERO_MODE) #define _MM_SET_DENORMALS_ZERO_MODE(mode) \ _mm_setcsr ((_mm_getcsr () & ~_MM_DENORMALS_ZERO_MASK) | (mode)) +#endif + +#if !defined(_MM_GET_DENORMALS_ZERO_MODE) #define _MM_GET_DENORMALS_ZERO_MODE() \ (_mm_getcsr() & _MM_DENORMALS_ZERO_MASK) +#endif #else @@ -35,5 +45,3 @@ #define _MM_GET_DENORMALS_ZERO_MODE() #endif - -#endif /* DENORMALSAREZERO_H */ diff --git a/src/waveform/renderers/waveformmarkproperties.cpp b/src/waveform/renderers/waveformmarkproperties.cpp index 3efae649beb4..18ee9195e493 100644 --- a/src/waveform/renderers/waveformmarkproperties.cpp +++ b/src/waveform/renderers/waveformmarkproperties.cpp @@ -46,14 +46,15 @@ WaveformMarkProperties::WaveformMarkProperties(const QDomNode& node, const SkinContext& context, const WaveformSignalColors& signalColors, int hotCue) { - m_color = context.selectString(node, "Color"); - if (!m_color.isValid()) { + QColor color(context.selectString(node, "Color")); + if (!color.isValid()) { // As a fallback, grab the color from the parent's AxesColor - m_color = signalColors.getAxesColor(); - qDebug() << "Didn't get mark , using parent's :" << m_color; + color = signalColors.getAxesColor(); + qDebug() << "Didn't get mark , using parent's :" << color; } else { - m_color = WSkinColor::getCorrectColor(m_color); + color = WSkinColor::getCorrectColor(color); } + setBaseColor(color); m_textColor = context.selectString(node, "TextColor"); if (!m_textColor.isValid()) { @@ -75,3 +76,20 @@ WaveformMarkProperties::WaveformMarkProperties(const QDomNode& node, m_pixmapPath = context.makeSkinPath(m_pixmapPath); } } + +void WaveformMarkProperties::setBaseColor(QColor baseColor) { + m_fillColor = baseColor; + m_borderColor = Color::chooseContrastColor(baseColor); + m_labelColor = Color::chooseColorByBrightness(baseColor, QColor(255,255,255,255), QColor(0,0,0,255)); +} + +QColor WaveformMarkProperties::fillColor() const { + return m_fillColor; +} + +QColor WaveformMarkProperties::borderColor() const { + return m_borderColor; +} +QColor WaveformMarkProperties::labelColor() const { + return m_labelColor; +} diff --git a/src/waveform/renderers/waveformmarkproperties.h b/src/waveform/renderers/waveformmarkproperties.h index 1215b36e28fb..6f3cf0ea4c2b 100644 --- a/src/waveform/renderers/waveformmarkproperties.h +++ b/src/waveform/renderers/waveformmarkproperties.h @@ -15,11 +15,21 @@ class WaveformMarkProperties final { const WaveformSignalColors& signalColors, int hotCue); - QColor m_color; + // Sets the appropriate mark colors based on the base color + void setBaseColor(QColor baseColor); + QColor fillColor() const; + QColor borderColor() const; + QColor labelColor() const; + QColor m_textColor; QString m_text; Qt::Alignment m_align; QString m_pixmapPath; + + private: + QColor m_fillColor; + QColor m_borderColor; + QColor m_labelColor; }; #endif // WAVEFORMMARKPROPERTIES_H diff --git a/src/waveform/renderers/waveformmarkset.cpp b/src/waveform/renderers/waveformmarkset.cpp index 3865930a7d50..d35266b2a4cf 100644 --- a/src/waveform/renderers/waveformmarkset.cpp +++ b/src/waveform/renderers/waveformmarkset.cpp @@ -27,7 +27,7 @@ void WaveformMarkSet::setup(const QString& group, const QDomNode& node, QDomNode defaultChild; while (!child.isNull()) { if (child.nodeName() == "DefaultMark") { - m_pDefaultMark = std::make_unique(group, child, context, signalColors); + m_pDefaultMark = WaveformMarkPointer(new WaveformMark(group, child, context, signalColors)); hasDefaultMark = true; defaultChild = child; } else if (child.nodeName() == "Mark") { @@ -65,3 +65,7 @@ void WaveformMarkSet::setup(const QString& group, const QDomNode& node, WaveformMarkPointer WaveformMarkSet::getHotCueMark(int hotCue) const { return m_hotCueMarks.value(hotCue); } + +WaveformMarkPointer WaveformMarkSet::getDefaultMark() const { + return m_pDefaultMark; +} diff --git a/src/waveform/renderers/waveformmarkset.h b/src/waveform/renderers/waveformmarkset.h index b7e49a857e39..198d6876620f 100644 --- a/src/waveform/renderers/waveformmarkset.h +++ b/src/waveform/renderers/waveformmarkset.h @@ -23,10 +23,11 @@ class WaveformMarkSet { // hotCue must be valid (>= 0 and < NUM_HOT_CUES) WaveformMarkPointer getHotCueMark(int hotCue) const; + WaveformMarkPointer getDefaultMark() const; private: - void clear(){ m_marks.clear(); } - std::unique_ptr m_pDefaultMark; + void clear() { m_marks.clear(); } + WaveformMarkPointer m_pDefaultMark; QList m_marks; QMap m_hotCueMarks; diff --git a/src/waveform/renderers/waveformrendermark.cpp b/src/waveform/renderers/waveformrendermark.cpp index 4904e024bfbf..dd38852aecda 100644 --- a/src/waveform/renderers/waveformrendermark.cpp +++ b/src/waveform/renderers/waveformrendermark.cpp @@ -6,6 +6,7 @@ #include "control/controlproxy.h" #include "track/track.h" +#include "util/color/color.h" #include "waveform/renderers/waveformwidgetrenderer.h" #include "waveform/waveform.h" #include "widget/wskincolor.h" @@ -22,8 +23,13 @@ WaveformRenderMark::WaveformRenderMark( } void WaveformRenderMark::setup(const QDomNode& node, const SkinContext& context) { - m_marks.setup(m_waveformRenderer->getGroup(), node, context, - *m_waveformRenderer->getWaveformSignalColors()); + WaveformSignalColors signalColors = *m_waveformRenderer->getWaveformSignalColors(); + m_marks.setup(m_waveformRenderer->getGroup(), node, context, signalColors); + WaveformMarkPointer defaultMark(m_marks.getDefaultMark()); + QColor defaultColor = defaultMark + ? defaultMark->getProperties().fillColor() + : signalColors.getAxesColor(); + m_predefinedColorsRepresentation = context.getCueColorRepresentation(node, defaultColor); } void WaveformRenderMark::draw(QPainter* painter, QPaintEvent* /*event*/) { @@ -107,20 +113,20 @@ void WaveformRenderMark::slotCuesUpdated() { continue; } - QString newLabel = pCue->getLabel(); - QColor newColor = pCue->getColor(); - // Here we assume no two cues can have the same hotcue assigned, // because WaveformMarkSet stores one mark for each hotcue. WaveformMarkPointer pMark = m_marks.getHotCueMark(hotCue); if (pMark.isNull()) { continue; } + WaveformMarkProperties markProperties = pMark->getProperties(); + QString newLabel = pCue->getLabel(); + QColor newColor = m_predefinedColorsRepresentation.representationFor(pCue->getColor()); if (markProperties.m_text.isNull() || newLabel != markProperties.m_text || - !markProperties.m_color.isValid() || newColor != markProperties.m_color) { + !markProperties.fillColor().isValid() || newColor != markProperties.fillColor()) { markProperties.m_text = newLabel; - markProperties.m_color = newColor; + markProperties.setBaseColor(newColor); pMark->setProperties(markProperties); generateMarkImage(pMark.data()); } @@ -221,37 +227,32 @@ void WaveformRenderMark::generateMarkImage(WaveformMark* pMark) { painter.setWorldMatrixEnabled(false); - // Prepare colors for drawing of marker lines - QColor lineColor = markProperties.m_color; - lineColor.setAlpha(200); - QColor contrastLineColor(0,0,0,120); - // Draw marker lines if (m_waveformRenderer->getOrientation() == Qt::Horizontal) { int middle = width / 2; if (markAlignH == Qt::AlignHCenter) { if (labelRect.top() > 0) { - painter.setPen(lineColor); + painter.setPen(markProperties.fillColor()); painter.drawLine(middle, 0, middle, labelRect.top()); - painter.setPen(contrastLineColor); + painter.setPen(markProperties.borderColor()); painter.drawLine(middle - 1, 0, middle - 1, labelRect.top()); painter.drawLine(middle + 1, 0, middle + 1, labelRect.top()); } if (labelRect.bottom() < height) { - painter.setPen(lineColor); + painter.setPen(markProperties.fillColor()); painter.drawLine(middle, labelRect.bottom(), middle, height); - painter.setPen(contrastLineColor); + painter.setPen(markProperties.borderColor()); painter.drawLine(middle - 1, labelRect.bottom(), middle - 1, height); painter.drawLine(middle + 1, labelRect.bottom(), middle + 1, height); } } else { // AlignLeft || AlignRight - painter.setPen(lineColor); + painter.setPen(markProperties.fillColor()); painter.drawLine(middle, 0, middle, height); - painter.setPen(contrastLineColor); + painter.setPen(markProperties.borderColor()); painter.drawLine(middle - 1, 0, middle - 1, height); painter.drawLine(middle + 1, 0, middle + 1, height); } @@ -259,43 +260,41 @@ void WaveformRenderMark::generateMarkImage(WaveformMark* pMark) { int middle = height / 2; if (markAlignV == Qt::AlignVCenter) { if (labelRect.left() > 0) { - painter.setPen(lineColor); + painter.setPen(markProperties.fillColor()); painter.drawLine(0, middle, labelRect.left(), middle); - painter.setPen(contrastLineColor); + painter.setPen(markProperties.borderColor()); painter.drawLine(0, middle - 1, labelRect.left(), middle - 1); painter.drawLine(0, middle + 1, labelRect.left(), middle + 1); } if (labelRect.right() < width) { - painter.setPen(lineColor); + painter.setPen(markProperties.fillColor()); painter.drawLine(labelRect.right(), middle, width, middle); - painter.setPen(contrastLineColor); + painter.setPen(markProperties.borderColor()); painter.drawLine(labelRect.right(), middle - 1, width, middle - 1); painter.drawLine(labelRect.right(), middle + 1, width, middle + 1); } } else { // AlignTop || AlignBottom - painter.setPen(lineColor); + painter.setPen(markProperties.fillColor()); painter.drawLine(0, middle, width, middle); - painter.setPen(contrastLineColor); + painter.setPen(markProperties.borderColor()); painter.drawLine(0, middle - 1, width, middle - 1); painter.drawLine(0, middle + 1, width, middle + 1); } } // Draw the label rect - QColor rectColor = markProperties.m_color; - rectColor.setAlpha(200); - painter.setPen(markProperties.m_color); - painter.setBrush(QBrush(rectColor)); + painter.setPen(markProperties.borderColor()); + painter.setBrush(QBrush(markProperties.fillColor())); painter.drawRoundedRect(labelRect, 2.0, 2.0); // Draw text painter.setBrush(QBrush(QColor(0,0,0,0))); painter.setFont(font); - painter.setPen(markProperties.m_textColor); + painter.setPen(markProperties.labelColor()); painter.drawText(labelRect, Qt::AlignCenter, label); } else //no text draw triangle @@ -327,8 +326,7 @@ void WaveformRenderMark::generateMarkImage(WaveformMark* pMark) { painter.setTransform(QTransform(0, 1, 1, 0, 0, 0)); } - QColor triangleColor = markProperties.m_color; - triangleColor.setAlpha(140); + QColor triangleColor = markProperties.fillColor(); painter.setPen(QColor(0,0,0,0)); painter.setBrush(QBrush(triangleColor)); @@ -351,8 +349,7 @@ void WaveformRenderMark::generateMarkImage(WaveformMark* pMark) { //TODO vRince duplicated code make a method //draw line - QColor lineColor = markProperties.m_color; - lineColor.setAlpha(140); + QColor lineColor = markProperties.fillColor(); painter.setPen(lineColor); float middle = markLength / 2.0; diff --git a/src/waveform/renderers/waveformrendermark.h b/src/waveform/renderers/waveformrendermark.h index fd515478b5b5..acd65f81a53a 100644 --- a/src/waveform/renderers/waveformrendermark.h +++ b/src/waveform/renderers/waveformrendermark.h @@ -5,6 +5,7 @@ #include "skin/skincontext.h" #include "util/class.h" +#include "util/color/color.h" #include "waveform/renderers/waveformmarkset.h" #include "waveform/renderers/waveformrendererabstract.h" #include "library/dao/cue.h" @@ -12,6 +13,7 @@ class WaveformRenderMark : public QObject, public WaveformRendererAbstract { Q_OBJECT + public: explicit WaveformRenderMark(WaveformWidgetRenderer* waveformWidgetRenderer); @@ -32,6 +34,8 @@ class WaveformRenderMark : public QObject, public WaveformRendererAbstract { private: void generateMarkImage(WaveformMark* pMark); + PredefinedColorsRepresentation m_predefinedColorsRepresentation; + WaveformMarkSet m_marks; DISALLOW_COPY_AND_ASSIGN(WaveformRenderMark); }; diff --git a/src/waveform/waveformwidgetfactory.cpp b/src/waveform/waveformwidgetfactory.cpp index 1721af7d4a2d..cb122ce2b919 100644 --- a/src/waveform/waveformwidgetfactory.cpp +++ b/src/waveform/waveformwidgetfactory.cpp @@ -6,8 +6,11 @@ #include #include #include +#include #include #include +#include +#include #include "waveform/waveformwidgetfactory.h" @@ -33,6 +36,7 @@ #include "util/time.h" #include "util/timer.h" #include "util/math.h" +#include "util/memory.h" namespace { // Returns true if the given waveform should be rendered. @@ -117,14 +121,51 @@ WaveformWidgetFactory::WaveformWidgetFactory() : m_openGLVersion = tr("Safe Mode"); } else if (pWindow && pWindow->supportsOpenGL()) { m_openGLAvailable = true; - const auto format = pWindow->format(); - const auto major_minor = format.version(); - m_openGLVersion = QString("%1 %2.%3").arg( - format.renderableType() == QSurfaceFormat::OpenGL ? "OpenGL" : "OpenGLES", - QString::number(major_minor.first), - QString::number(major_minor.second)); - // Mixxx requires GLSL 1.20, which corresponds to OpenGL version 2.1. - m_openGLShaderAvailable = major_minor.first > 2 || (major_minor.first == 2 && major_minor.second >= 1); + + QWidget* w = nullptr; + foreach(QWidget* widget, QApplication::topLevelWidgets()) + if (widget->inherits("QMainWindow")) { + w = widget; + break; + } + std::unique_ptr pGlW = std::make_unique(w); + //glw->makeCurrent(); // does not work here + pGlW->show(); // the context is only valid if the wiget is shown. + QOpenGLContext* pContext = pGlW->context(); + + QString version; + QString renderer; + if (pContext) { + version = QString::fromUtf8(reinterpret_cast( + pContext->functions()->glGetString(GL_VERSION))); + renderer = QString::fromUtf8(reinterpret_cast( + pContext->functions()->glGetString(GL_RENDERER))); + } + + m_openGLShaderAvailable = QOpenGLShaderProgram::hasOpenGLShaderPrograms(pContext); + + const QSurfaceFormat format = pWindow->format(); + + const QSurfaceFormat::RenderableType renderableType = format.renderableType(); + QString strRenderableType; + switch (renderableType) { + case QSurfaceFormat::DefaultRenderableType: + strRenderableType = "DefaultRenderableType"; + break; + case QSurfaceFormat::OpenGL: + strRenderableType = "OpenGL"; + break; + case QSurfaceFormat::OpenGLES: + strRenderableType = "OpenGLES"; + break; + case QSurfaceFormat::OpenVG: + strRenderableType = "OpenVG"; + break; + default: + strRenderableType = "Unknown Type"; + break; + } + m_openGLVersion = strRenderableType + ": " + version + " (" + renderer + ")"; } else { m_openGLVersion = tr("None"); } diff --git a/src/widget/woverview.cpp b/src/widget/woverview.cpp index 8b5ed67a7c6b..a0c48563e65f 100644 --- a/src/widget/woverview.cpp +++ b/src/widget/woverview.cpp @@ -27,6 +27,7 @@ #include "widget/controlwidgetconnection.h" #include "track/track.h" #include "analyzer/analyzerprogress.h" +#include "util/color/color.h" #include "util/math.h" #include "util/timer.h" #include "util/dnd.h" @@ -90,6 +91,11 @@ void WOverview::setup(const QDomNode& node, const SkinContext& context) { // setup hotcues and cue and loop(s) m_marks.setup(m_group, node, context, m_signalColors); + WaveformMarkPointer defaultMark(m_marks.getDefaultMark()); + QColor defaultColor = defaultMark + ? defaultMark->getProperties().fillColor() + : m_signalColors.getAxesColor(); + m_predefinedColorsRepresentation = context.getCueColorRepresentation(node, defaultColor); for (const auto& pMark: m_marks) { if (pMark->isValid()) { @@ -200,6 +206,9 @@ void WOverview::onTrackAnalyzerProgress(TrackId trackId, AnalyzerProgress analyz void WOverview::slotTrackLoaded(TrackPointer pTrack) { DEBUG_ASSERT(m_pCurrentTrack == pTrack); m_trackLoaded = true; + if (m_pCurrentTrack) { + updateCues(m_pCurrentTrack->getCuePoints()); + } update(); } @@ -226,6 +235,8 @@ void WOverview::slotLoadingTrack(TrackPointer pNewTrack, TrackPointer pOldTrack) connect(pNewTrack.get(), SIGNAL(waveformSummaryUpdated()), this, SLOT(slotWaveformSummaryUpdated())); slotWaveformSummaryUpdated(); + connect(pNewTrack.get(), SIGNAL(cuesUpdated()), + this, SLOT(receiveCuesUpdated())); } else { m_pCurrentTrack.reset(); m_pWaveform.clear(); @@ -241,6 +252,7 @@ void WOverview::onEndOfTrackChange(double v) { void WOverview::onMarkChanged(double /*v*/) { //qDebug() << "WOverview::onMarkChanged()" << v; + updateCues(m_pCurrentTrack->getCuePoints()); update(); } @@ -249,6 +261,28 @@ void WOverview::onMarkRangeChange(double /*v*/) { update(); } +// currently only updates the mark color but it could be easily extended. +void WOverview::updateCues(const QList &loadedCues) { + for (CuePointer currentCue: loadedCues) { + const WaveformMarkPointer currentMark = m_marks.getHotCueMark(currentCue->getHotCue()); + + if (currentMark && currentMark->isValid()) { + WaveformMarkProperties markProperties = currentMark->getProperties(); + QColor newColor = m_predefinedColorsRepresentation.representationFor(currentCue->getColor()); + if (newColor != markProperties.fillColor() || newColor != markProperties.m_textColor) { + markProperties.setBaseColor(newColor); + currentMark->setProperties(markProperties); + } + } + } +} + +// connecting the tracks cuesUpdated and onMarkChanged is not possible +// due to the incompatible signatures. This is a "wrapper" workaround +void WOverview::receiveCuesUpdated() { + onMarkChanged(0); +} + void WOverview::mouseMoveEvent(QMouseEvent* e) { if (m_orientation == Qt::Horizontal) { m_iPos = math_clamp(e->x(), 0, width() - 1); @@ -430,8 +464,6 @@ void WOverview::paintEvent(QPaintEvent * /*unused*/) { } // Draw markers (Cue & hotcues) - QPen shadowPen(QBrush(m_qColorBackground), 2.5 * m_scaleFactor); - QFont markerFont = painter.font(); markerFont.setPixelSize(10 * m_scaleFactor); @@ -448,6 +480,8 @@ void WOverview::paintEvent(QPaintEvent * /*unused*/) { // (currentMark.m_pointControl->get() / (float)m_trackSamplesControl->get()) * (float)(width()-2); const float markPosition = offset + currentMark->getSamplePosition() * gain; + QPen shadowPen(QBrush(markProperties.borderColor()), 2.5 * m_scaleFactor); + QLineF line; if (m_orientation == Qt::Horizontal) { line.setLine(markPosition, 0.0, markPosition, static_cast(height())); @@ -457,7 +491,7 @@ void WOverview::paintEvent(QPaintEvent * /*unused*/) { painter.setPen(shadowPen); painter.drawLine(line); - painter.setPen(markProperties.m_color); + painter.setPen(markProperties.fillColor()); painter.drawLine(line); if (!markProperties.m_text.isEmpty()) { diff --git a/src/widget/woverview.h b/src/widget/woverview.h index d13bc759a7a9..ea5a3f031f98 100644 --- a/src/widget/woverview.h +++ b/src/widget/woverview.h @@ -23,6 +23,8 @@ #include "widget/wwidget.h" #include "analyzer/analyzerprogress.h" +#include "util/color/color.h" + #include "waveform/renderers/waveformsignalcolors.h" #include "waveform/renderers/waveformmarkset.h" #include "waveform/renderers/waveformmarkrange.h" @@ -90,6 +92,7 @@ class WOverview : public WWidget, public TrackDropTarget { void onMarkChanged(double v); void onMarkRangeChange(double v); + void receiveCuesUpdated(); void slotWaveformSummaryUpdated(); @@ -104,6 +107,8 @@ class WOverview : public WWidget, public TrackDropTarget { return (static_cast(position) + m_b) / m_a; } + void updateCues(const QList &loadedCues); + const QString m_group; UserSettingsPointer m_pConfig; ControlProxy* m_endOfTrackControl; @@ -126,6 +131,7 @@ class WOverview : public WWidget, public TrackDropTarget { QColor m_qColorBackground; QColor m_endOfTrackColor; + PredefinedColorsRepresentation m_predefinedColorsRepresentation; WaveformMarkSet m_marks; std::vector m_markRanges; diff --git a/src/widget/wstarrating.cpp b/src/widget/wstarrating.cpp index ca991956b8cb..025a587ed8d3 100644 --- a/src/widget/wstarrating.cpp +++ b/src/widget/wstarrating.cpp @@ -10,6 +10,11 @@ WStarRating::WStarRating(QString group, QWidget* pParent) m_starRating(0,5), m_pGroup(group), m_focused(false) { + // Controls to change the star rating with controllers + m_pStarsUp = std::make_unique(ConfigKey(group, "stars_up")); + m_pStarsDown = std::make_unique(ConfigKey(group, "stars_down")); + connect(m_pStarsUp.get(), SIGNAL(valueChanged(double)),this, SLOT(slotStarsUp(double))); + connect(m_pStarsDown.get(), SIGNAL(valueChanged(double)),this, SLOT(slotStarsDown(double))); } void WStarRating::setup(const QDomNode& node, const SkinContext& context) { @@ -87,6 +92,32 @@ void WStarRating::mouseMoveEvent(QMouseEvent *event) { } } +void WStarRating::slotStarsUp(double v) { + if (!m_pCurrentTrack) { + return; + } + if (v > 0 && m_starRating.starCount() < m_starRating.maxStarCount()) { + int star = m_starRating.starCount() + 1; + qDebug() << " stars " << m_starRating.starCount() << " > " << star; + m_starRating.setStarCount(star); + update(); + m_pCurrentTrack->setRating(star); + } +} + +void WStarRating::slotStarsDown(double v) { + if (!m_pCurrentTrack) { + return; + } + if (v > 0 && m_starRating.starCount() > 0) { + int star = m_starRating.starCount() - 1; + qDebug() << " stars " << m_starRating.starCount() << " > " << star; + m_starRating.setStarCount(star); + update(); + m_pCurrentTrack->setRating(star); + } +} + void WStarRating::leaveEvent(QEvent* /*unused*/) { m_focused = false; updateRating(); diff --git a/src/widget/wstarrating.h b/src/widget/wstarrating.h index b5624b3ff3d7..2deebd3625b3 100644 --- a/src/widget/wstarrating.h +++ b/src/widget/wstarrating.h @@ -11,6 +11,11 @@ #include "library/starrating.h" #include "widget/wwidget.h" +#include "control/controlpushbutton.h" + +class ControlObject; +class ControlPushButton; + class WStarRating : public WWidget { Q_OBJECT public: @@ -24,6 +29,8 @@ class WStarRating : public WWidget { private slots: void updateRating(Track*); + void slotStarsUp(double v); + void slotStarsDown(double v); protected: void paintEvent(QPaintEvent* e) override; @@ -41,6 +48,8 @@ class WStarRating : public WWidget { private: void updateRating(); int starAtPosition(int x); + std::unique_ptr m_pStarsUp; + std::unique_ptr m_pStarsDown; }; #endif /* WSTARRATING_H */