diff --git a/CMakeLists.txt b/CMakeLists.txt index e6e8f9303f9a..9ec3eb023f58 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -188,7 +188,8 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/control/controlproxy.cpp src/control/controlpushbutton.cpp src/control/controlttrotary.cpp - src/controllers/colorjsproxy.cpp + src/controllers/colormapper.cpp + src/controllers/colormapperjsproxy.cpp src/controllers/controller.cpp src/controllers/controllerdebug.cpp src/controllers/controllerengine.cpp @@ -334,7 +335,6 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/errordialoghandler.cpp src/library/analysisfeature.cpp src/library/analysislibrarytablemodel.cpp - src/library/trackloader.cpp src/library/autodj/autodjfeature.cpp src/library/autodj/autodjprocessor.cpp src/library/autodj/dlgautodj.cpp @@ -431,6 +431,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/library/tableitemdelegate.cpp src/library/trackcollection.cpp src/library/trackcollectionmanager.cpp + src/library/trackloader.cpp src/library/traktor/traktorfeature.cpp src/library/treeitem.cpp src/library/treeitemmodel.cpp @@ -497,6 +498,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/preferences/dialog/dlgprefwaveformdlg.ui src/preferences/dlgpreferencepage.cpp src/preferences/effectsettingsmodel.cpp + src/preferences/colorpalettesettings.cpp src/preferences/replaygainsettings.cpp src/preferences/settingsmanager.cpp src/preferences/upgrade.cpp @@ -559,7 +561,7 @@ add_library(mixxx-lib STATIC EXCLUDE_FROM_ALL src/util/cache.cpp src/util/cmdlineargs.cpp src/util/color/color.cpp - src/util/color/predefinedcolor.cpp + src/util/color/colorpalette.cpp src/util/console.cpp src/util/db/dbconnection.cpp src/util/db/dbconnectionpool.cpp @@ -946,6 +948,9 @@ add_executable(mixxx-test src/test/broadcastsettings_test.cpp src/test/cache_test.cpp src/test/channelhandle_test.cpp + src/test/colorconfig_test.cpp + src/test/colormapperjsproxy_test.cpp + src/test/colorpalette_test.cpp src/test/compatibility_test.cpp src/test/configobject_test.cpp src/test/controller_preset_validation_test.cpp diff --git a/build/depends.py b/build/depends.py index 4740b2c5c087..e13d2df5b3b2 100644 --- a/build/depends.py +++ b/build/depends.py @@ -790,6 +790,7 @@ def sources(self, build): "src/preferences/effectsettingsmodel.cpp", "src/preferences/broadcastprofile.cpp", "src/preferences/upgrade.cpp", + "src/preferences/colorpalettesettings.cpp", "src/preferences/dlgpreferencepage.cpp", "src/effects/effectmanifest.cpp", @@ -925,7 +926,8 @@ def sources(self, build): "src/controllers/midi/midioutputhandler.cpp", "src/controllers/softtakeover.cpp", "src/controllers/keyboard/keyboardeventfilter.cpp", - "src/controllers/colorjsproxy.cpp", + "src/controllers/colormapper.cpp", + "src/controllers/colormapperjsproxy.cpp", "src/main.cpp", "src/mixxx.cpp", @@ -1283,6 +1285,7 @@ def sources(self, build): "src/util/cache.cpp", "src/util/console.cpp", "src/util/color/color.cpp", + "src/util/color/colorpalette.cpp", "src/util/db/dbconnection.cpp", "src/util/db/dbconnectionpool.cpp", "src/util/db/dbconnectionpooler.cpp", @@ -1309,8 +1312,7 @@ def sources(self, build): "src/util/desktophelper.cpp", "src/util/widgetrendertimer.cpp", "src/util/workerthread.cpp", - "src/util/workerthreadscheduler.cpp", - "src/util/color/predefinedcolor.cpp" + "src/util/workerthreadscheduler.cpp" ] proto_args = { diff --git a/res/controllers/.eslintrc.json b/res/controllers/.eslintrc.json index 5c726c8bc03b..2a6722aac48c 100644 --- a/res/controllers/.eslintrc.json +++ b/res/controllers/.eslintrc.json @@ -1,7 +1,7 @@ { "globals": { + "ColorMapper": "readonly", "_": "readonly", - "color": "readonly", "components": "readonly", "engine": "readonly", "midi": "readonly", @@ -10,6 +10,8 @@ "arrayContains": "readonly", "secondstominutes": "readonly", "msecondstominutes": "readonly", + "colorCodeToObject": "readonly", + "colorCodeFromObject": "readonly", "script": "readonly", "bpm": "readonly", "ButtonState": "readonly", diff --git a/res/controllers/Roland_DJ-505-scripts.js b/res/controllers/Roland_DJ-505-scripts.js index be2ddb4f7728..ceee4d06b3be 100644 --- a/res/controllers/Roland_DJ-505-scripts.js +++ b/res/controllers/Roland_DJ-505-scripts.js @@ -970,17 +970,24 @@ DJ505.PadColor = { DIM_MODIFIER: 0x10, }; -DJ505.PadColorMap = [ - DJ505.PadColor.OFF, - DJ505.PadColor.RED, - DJ505.PadColor.GREEN, - DJ505.PadColor.BLUE, - DJ505.PadColor.YELLOW, - DJ505.PadColor.CELESTE, - DJ505.PadColor.PURPLE, - DJ505.PadColor.APRICOT, - DJ505.PadColor.WHITE, -]; +DJ505.PadColorMap = new ColorMapper({ + "#CC0000": DJ505.PadColor.RED, + "#CC4400": DJ505.PadColor.CORAL, + "#CC8800": DJ505.PadColor.ORANGE, + "#CCCC00": DJ505.PadColor.YELLOW, + "#88CC00": DJ505.PadColor.GREEN, + "#00CC00": DJ505.PadColor.APPLEGREEN, + "#00CC88": DJ505.PadColor.AQUAMARINE, + "#00CCCC": DJ505.PadColor.TURQUOISE, + "#0088CC": DJ505.PadColor.CELESTE, + "#0000CC": DJ505.PadColor.BLUE, + "#4400CC": DJ505.PadColor.AZURE, + "#8800CC": DJ505.PadColor.PURPLE, + "#CC00CC": DJ505.PadColor.MAGENTA, + "#CC0044": DJ505.PadColor.RED, + "#FFCCCC": DJ505.PadColor.APRICOT, + "#FFFFFF": DJ505.PadColor.WHITE, +}); DJ505.PadSection = function(deck, offset) { // TODO: Add support for missing modes (flip, slicer, slicerloop) @@ -1239,7 +1246,6 @@ DJ505.HotcueMode = function(deck, offset) { this.ledControl = DJ505.PadMode.HOTCUE; this.color = DJ505.PadColor.WHITE; - var hotcueColors = [this.color].concat(DJ505.PadColorMap.slice(1)); this.pads = new components.ComponentContainer(); for (var i = 0; i <= 7; i++) { this.pads[i] = new components.HotcueButton({ @@ -1251,7 +1257,7 @@ DJ505.HotcueMode = function(deck, offset) { group: deck.currentDeck, on: this.color, off: this.color + DJ505.PadColor.DIM_MODIFIER, - colors: hotcueColors, + colorMapper: DJ505.PadColorMap, outConnect: false, }); } @@ -1287,7 +1293,6 @@ DJ505.CueLoopMode = function(deck, offset) { this.ledControl = DJ505.PadMode.HOTCUE; this.color = DJ505.PadColor.BLUE; - var cueloopColors = [this.color].concat(DJ505.PadColorMap.slice(1)); this.PerformancePad = function(n) { this.midi = [0x94 + offset, 0x14 + n]; this.number = n + 1; @@ -1303,7 +1308,7 @@ DJ505.CueLoopMode = function(deck, offset) { group: deck.currentDeck, on: this.color, off: this.color + DJ505.PadColor.DIM_MODIFIER, - colors: cueloopColors, + colorMapper: DJ505.PadColorMap, outConnect: false, unshift: function() { this.input = function(channel, control, value, status, group) { @@ -1596,14 +1601,13 @@ DJ505.PitchPlayMode = function(deck, offset) { this.color = DJ505.PadColor.GREEN; this.cuepoint = 1; this.range = PitchPlayRange.MID; - var pitchplayColors = [this.color].concat(DJ505.PadColorMap.slice(1)); this.PerformancePad = function(n) { this.midi = [0x94 + offset, 0x14 + n]; this.number = n + 1; this.on = this.color + DJ505.PadColor.DIM_MODIFIER; - this.colors = pitchplayColors; - this.colorIdKey = "hotcue_" + this.number + "_color_id"; + this.colorMapper = DJ505.PadColorMap; + this.colorKey = "hotcue_" + this.number + "_color"; components.Button.call(this); }; this.PerformancePad.prototype = new components.Button({ @@ -1614,10 +1618,10 @@ DJ505.PitchPlayMode = function(deck, offset) { mode: this, outConnect: false, off: DJ505.PadColor.OFF, - outputColor: function(id) { + outputColor: function(colorCode) { // For colored hotcues (shifted only) - var color = this.colors[id]; - this.send((this.mode.cuepoint === this.number) ? color : (color + DJ505.PadColor.DIM_MODIFIER)); + var midiColor = this.colorMapper.getNearestValue(colorCode); + this.send((this.mode.cuepoint === this.number) ? midiColor : (midiColor + DJ505.PadColor.DIM_MODIFIER)); }, unshift: function() { this.outKey = "pitch_adjust"; @@ -1665,8 +1669,8 @@ DJ505.PitchPlayMode = function(deck, offset) { this.outKey = "hotcue_" + this.number + "_enabled"; this.output = function(value, _group, _control) { var outval = this.outValueScale(value); - if (this.colorIdKey !== undefined && outval !== this.off) { - this.outputColor(engine.getValue(this.group, this.colorIdKey)); + if (this.colorKey !== undefined && outval !== this.off) { + this.outputColor(engine.getValue(this.group, this.colorKey)); } else { this.send(DJ505.PadColor.OFF); } @@ -1676,13 +1680,13 @@ DJ505.PitchPlayMode = function(deck, offset) { var previousCuepoint = this.mode.cuepoint; this.mode.cuepoint = this.number; this.mode.pads[previousCuepoint - 1].trigger(); - this.outputColor(engine.getValue(this.group, this.colorIdKey)); + this.outputColor(engine.getValue(this.group, this.colorKey)); } }; this.connect = function() { components.Button.prototype.connect.call(this); // call parent connect - if (undefined !== this.group && this.colorIdKey !== undefined) { - this.connections[1] = engine.makeConnection(this.group, this.colorIdKey, function(id) { + if (undefined !== this.group && this.colorKey !== undefined) { + this.connections[1] = engine.makeConnection(this.group, this.colorKey, function(id) { if (engine.getValue(this.group, this.outKey)) { this.outputColor(id); } diff --git a/res/controllers/common-controller-scripts.js b/res/controllers/common-controller-scripts.js index 5f8346f32c99..a7421d613f68 100644 --- a/res/controllers/common-controller-scripts.js +++ b/res/controllers/common-controller-scripts.js @@ -5,6 +5,8 @@ arrayContains:off secondstominutes:off msecondstominutes:off + colorCodeToObject:off + colorCodeFromObject:off script:off bpm:off ButtonState:off @@ -102,6 +104,24 @@ var msecondstominutes = function(msecs) { + (msecs < 10 ? "0" + msecs : msecs); }; +// Converts an object with "red", "green" and "blue" properties (value range +// 0-255) into an RGB color code (e.g. 0xFF0000). +// eslint-disable-next-line no-unused-vars +var colorCodeFromObject = function(color) { + return ((color.red & 0xFF) << 16 | (color.green & 0xFF) << 8 | (color.blue & 0xFF)); +}; + +// Converts an RGB color code (e.g. 0xFF0000) into an object with "red", +// "green" and "blue" properties (value range 0-255). +// eslint-disable-next-line no-unused-vars +var colorCodeToObject = function(colorCode) { + return { + "red": (colorCode >> 16) & 0xFF, + "green": (colorCode >> 8) & 0xFF, + "blue": colorCode & 0xFF, + }; +}; + var script = function() { }; diff --git a/res/controllers/midi-components-0.0.js b/res/controllers/midi-components-0.0.js index 2657bb2176e0..3a87fec128d3 100644 --- a/res/controllers/midi-components-0.0.js +++ b/res/controllers/midi-components-0.0.js @@ -294,11 +294,8 @@ print("ERROR: No hotcue number specified for new HotcueButton."); return; } - if (options.colors !== undefined || options.sendRGB !== undefined) { - this.colorIdKey = "hotcue_" + options.number + "_color_id"; - if (options.colors === undefined) { - options.colors = color.predefinedColorsList(); - } + if (options.colorMapper !== undefined || options.sendRGB !== undefined) { + this.colorKey = "hotcue_" + options.number + "_color"; } this.number = options.number; this.outKey = "hotcue_" + this.number + "_enabled"; @@ -311,45 +308,48 @@ shift: function() { this.inKey = "hotcue_" + this.number + "_clear"; }, - getColor: function() { - if (this.colorIdKey !== undefined) { - return color.predefinedColorFromId(engine.getValue(this.group, this.colorIdKey)); - } else { - return null; - } - }, output: function(value) { var outval = this.outValueScale(value); - // WARNING: outputColor only handles hotcueColors + // NOTE: outputColor only handles hotcueColors // and there is no hotcueColor for turning the LED // off. So the `send()` function is responsible for turning the // actual LED off. - if (this.colorIdKey !== undefined && outval !== this.off) { - this.outputColor(engine.getValue(this.group, this.colorIdKey)); + if (this.colorKey !== undefined && outval !== this.off) { + this.outputColor(engine.getValue(this.group, this.colorKey)); } else { this.send(outval); } }, - outputColor: function(id) { - var color = this.colors[id]; - if (color instanceof Array) { - if (color.length !== 3) { - print("ERROR: invalid color array for id: " + id); - return; - } - if (this.sendRGB === undefined) { - print("ERROR: no function defined for sending RGB colors"); - return; - } - this.sendRGB(color); - } else if (typeof color === "number") { - this.send(color); + outputColor: function(colorCode) { + // Sends the color from the colorCode to the controller. This + // method will not be called if no colorKey has been specified. + if (colorCode === undefined || colorCode < 0 || colorCode > 0xFFFFFF) { + print("Ignoring invalid color code '" + colorCode + "' in outputColor()"); + return; + } + + if (this.colorMapper !== undefined) { + // This HotcueButton holds a reference to a ColorMapper. This means + // that the controller only supports a fixed set of colors, so we + // get the MIDI value for the nearest supported color and send it. + var nearestColorValue = this.colorMapper.getValueForNearestColor(colorCode); + this.send(nearestColorValue); + } else { + // Since outputColor has been called but no ColorMapper is + // available, we can assume that controller supports arbitrary + // RGB color output. + this.sendRGB(colorCodeToObject(colorCode)); } }, + sendRGB: function(_colorObject) { + // This method needs to be overridden in controller mappings, + // because the procedure is controller-dependent. + throw Error("sendRGB(colorObject) not implemented - unable to send RGB colors!"); + }, connect: function() { Button.prototype.connect.call(this); // call parent connect - if (undefined !== this.group && this.colorIdKey !== undefined) { - this.connections[1] = engine.makeConnection(this.group, this.colorIdKey, function(id) { + if (undefined !== this.group && this.colorKey !== undefined) { + this.connections[1] = engine.makeConnection(this.group, this.colorKey, function(id) { if (engine.getValue(this.group, this.outKey)) { this.outputColor(id); } @@ -732,14 +732,14 @@ if (engine.getValue(eu.group, "show_focus") > 0) { engine.setValue(eu.group, "show_focus", 0); eu.previouslyFocusedEffect = engine.getValue(eu.group, - "focused_effect"); + "focused_effect"); engine.setValue(eu.group, "focused_effect", 0); } } else { engine.setValue(eu.group, "show_focus", 1); if (eu.previouslyFocusedEffect !== undefined) { engine.setValue(eu.group, "focused_effect", - eu.previouslyFocusedEffect); + eu.previouslyFocusedEffect); } } if (eu.enableButtons !== undefined) { @@ -770,8 +770,8 @@ // show_focus is always in the correct state, even if the user // presses the skin button for show_parameters. this.showParametersConnection = engine.makeConnection(this.group, - "show_parameters", - this.onShowParametersChange); + "show_parameters", + this.onShowParametersChange); this.showParametersConnection.trigger(); } @@ -916,7 +916,7 @@ outKey: "focused_effect", connect: function() { this.connections[0] = engine.makeConnection(eu.group, "focused_effect", - this.onFocusChange); + this.onFocusChange); }, disconnect: function() { engine.softTakeoverIgnoreNextValue(this.group, this.inKey); @@ -979,7 +979,7 @@ this.connect = function() { this.connections[0] = engine.makeConnection(eu.group, "focused_effect", - this.onFocusChange); + this.onFocusChange); // this.onFocusChange sets this.group and this.outKey, so trigger it // before making the connection for LED output this.connections[0].trigger(); @@ -1026,8 +1026,8 @@ // of assigning to this.connections[0] to avoid // Component.prototype.trigger() triggering the disconnected connection. this.connections = [engine.makeConnection(eu.group, - "focused_effect", - this.output)]; + "focused_effect", + this.output)]; }; }, }); @@ -1069,8 +1069,8 @@ var showParameters = engine.getValue(this.group, "show_parameters"); if (this.isPress(channel, control, value, status)) { this.longPressTimer = engine.beginTimer(this.longPressTimeout, - this.startEffectFocusChooseMode, - true); + this.startEffectFocusChooseMode, + true); if (!showParameters) { if (!allowFocusWhenParametersHidden) { engine.setValue(this.group, "show_parameters", 1); @@ -1093,11 +1093,11 @@ eu.focusChooseModeActive = false; } else { if (!showParameters && allowFocusWhenParametersHidden) { - engine.setValue(this.group, "show_parameters", 1); + engine.setValue(this.group, "show_parameters", 1); } else if (showParameters && !this.pressedWhenParametersHidden) { - engine.setValue(this.group, "show_parameters", 0); - // eu.onShowParametersChange will save the focused effect, - // unfocus, and hide focus buttons in skin + engine.setValue(this.group, "show_parameters", 0); + // eu.onShowParametersChange will save the focused effect, + // unfocus, and hide focus buttons in skin } } this.pressedWhenParametersHidden = false; diff --git a/res/schema.xml b/res/schema.xml index 29c6af59fe8d..5f332d04844d 100644 --- a/res/schema.xml +++ b/res/schema.xml @@ -465,4 +465,34 @@ METADATA ALTER TABLE library ADD COLUMN color INTEGER; + + + Convert the PredefinedColor ID to the actual RGB value. + + + + UPDATE cues SET color=0xFF8C00 WHERE color=0; + + + UPDATE cues SET color=0xC50A08 WHERE color=1; + + UPDATE cues SET color=0x32BE44 WHERE color=2; + + UPDATE cues SET color=0x0044FF WHERE color=3; + + UPDATE cues SET color=0xF8D200 WHERE color=4; + + UPDATE cues SET color=0x42D4F4 WHERE color=5; + + UPDATE cues SET color=0xAF00CC WHERE color=6; + + UPDATE cues SET color=0xFCA6D7 WHERE color=7; + + UPDATE cues SET color=0xF2F2FF WHERE color=8; + + UPDATE cues SET color = (color & 0xFFFFFF) WHERE color > 0xFFFFFF; + + diff --git a/res/skins/Deere/hotcue_button.xml b/res/skins/Deere/hotcue_button.xml index b301c6994eda..2712d6eb0f0e 100644 --- a/res/skins/Deere/hotcue_button.xml +++ b/res/skins/Deere/hotcue_button.xml @@ -46,8 +46,8 @@ false - ,hotcue__color_id - highlight + ,hotcue__color + backgroundColorRgba diff --git a/res/skins/Deere/style.qss b/res/skins/Deere/style.qss index c9dd33053b64..5480087178c2 100644 --- a/res/skins/Deere/style.qss +++ b/res/skins/Deere/style.qss @@ -1546,7 +1546,6 @@ WPushButton:hover { /*"Pressed" state*/ WPushButton[value="1"], WPushButton[value="2"] { - /*color: #FDFDFD;*/ color: #FDFDFD; background-color: #006596; border: 1px solid #006596; @@ -1559,139 +1558,17 @@ WPushButton[value="2"]:hover { border: 1px solid #0080BE; } -/* Hotcue Color: No Color */ -#HotcueButton[value="1"][highlight="0"], -#HotcueButton[value="2"][highlight="0"] { - background-color: #006596; - border-color: #006596; +#HotcueButton { + qproperty-shouldHighlightBackgroundOnHover: true; } -#HotcueButton[value="1"][highlight="0"]:hover, -#HotcueButton[value="2"][highlight="0"]:hover { - background-color: #0080BE; - border-color: #0080BE; -} - -/* Hotcue Color: Red */ -#HotcueButton[value="1"][highlight="1"], -#HotcueButton[value="2"][highlight="1"] { - background-color: #c50a08; - border-color: #c50a08; -} -#HotcueButton[value="1"][highlight="1"]:hover, -#HotcueButton[value="2"][highlight="1"]:hover { - background-color: #e50c08; - border-color: #e50c08; -} - -/* Hotcue Color: Green */ -#HotcueButton[value="1"][highlight="2"], -#HotcueButton[value="2"][highlight="2"] { - color: #3B3B3B; - background-color: #32be44; - border-color: #32be44; -} -#HotcueButton[value="1"][highlight="2"]:hover, -#HotcueButton[value="2"][highlight="2"]:hover { - background-color: #52de64; - border-color: #52de64; -} - -/* Hotcue Color: Blue */ -#HotcueButton[value="1"][highlight="3"], -#HotcueButton[value="2"][highlight="3"] { - background-color: #0044ff; - border-color: #0044ff; -} -#HotcueButton[value="1"][highlight="3"]:hover, -#HotcueButton[value="2"][highlight="3"]:hover { - background-color: #0064ff; - border-color: #0064ff; -} - -/* Hotcue Color: Yellow */ -#HotcueButton[value="1"][highlight="4"], -#HotcueButton[value="2"][highlight="4"] { - color: #3B3B3B; - background-color: #f8d200; - border-color: #f8d200; -} -#HotcueButton[value="1"][highlight="4"]:hover, -#HotcueButton[value="2"][highlight="4"]:hover { - color: #3B3B3B; - background-color: #f8f200; - border-color: #f8f200; -} - -/* Hotcue Color: Celeste */ -#HotcueButton[value="1"][highlight="5"], -#HotcueButton[value="2"][highlight="5"] { - color: #3B3B3B; - background-color: #42d4f4; - border-color: #42d4f4; -} -#HotcueButton[value="1"][highlight="5"]:hover, -#HotcueButton[value="2"][highlight="5"]:hover { - color: #3B3B3B; - background-color: #62f4f4; - border-color: #62f4f4; -} - -/* Hotcue Color: Purple */ -#HotcueButton[value="1"][highlight="6"], -#HotcueButton[value="2"][highlight="6"] { - background-color: #af00cc; - border-color: #af00cc; -} -#HotcueButton[value="1"][highlight="6"]:hover, -#HotcueButton[value="2"][highlight="6"]:hover { - background-color: #cf00ec; - border-color: #cf00ec; -} - -/* Hotcue Color: Pink */ -#HotcueButton[value="1"][highlight="7"], -#HotcueButton[value="2"][highlight="7"] { - color: #4B4B4B; - background-color: #fca6d7; - border-color: #fca6d7; -} -#HotcueButton[value="1"][highlight="7"]:hover, -#HotcueButton[value="2"][highlight="7"]:hover { - color: #4B4B4B; - background-color: #fcc6f7; - border-color: #fcc6f7; -} - -/* Hotcue Color: White */ -#HotcueButton[value="1"][highlight="8"], -#HotcueButton[value="2"][highlight="8"] { - color: #4B4B4B; - background-color: #f2f2ff; - border-color: #f2f2ff; -} -#HotcueButton[value="1"][highlight="8"]:hover, -#HotcueButton[value="2"][highlight="8"]:hover { - color: #4B4B4B; - background-color: #ffffff; - border-color: #ffffff; -} - -/*"Enabled" state, e.g. for recording status - 0 -- disconnected / off - 1 -- connecting / enabling - 2 -- connected / enabled -WPushButton[value="2"] { + +#HotcueButton[backgroundIsDark=true][hasBackgroundColor=true] { color: #FDFDFD; - background-color: #4B4B4B; - border: 0px solid #006596; } -WPushButton[value="2"]:hover { - color: #FDFDFD; - background-color: #4B4B4B; - border: 0px solid #0080BE; +#HotcueButton[backgroundIsDark=false][hasBackgroundColor=true] { + color: #1f1e1e; } -*/ #PlayToggle[value="0"] { image: url(skin:/icon/ic_play_48px.svg) no-repeat center center; diff --git a/res/skins/LateNight/button_hotcue.xml b/res/skins/LateNight/button_hotcue.xml index 647656855040..920ffc05d324 100644 --- a/res/skins/LateNight/button_hotcue.xml +++ b/res/skins/LateNight/button_hotcue.xml @@ -37,8 +37,8 @@ false - ,hotcue__color_id - highlight + ,hotcue__color + backgroundColorRgba diff --git a/res/skins/LateNight/style.qss b/res/skins/LateNight/style.qss index ec9943b3ae4e..c51102c3e5b2 100644 --- a/res/skins/LateNight/style.qss +++ b/res/skins/LateNight/style.qss @@ -716,44 +716,6 @@ QPushButton#pushButtonAutoDJ:checked, } /* ToDo * orange Play button when playing from Cue / Hotcue */ -/* Hotcue Color: Green */ -#HotcueButton[displayValue="1"][highlight="2"], -#HotcueButton[displayValue="2"][highlight="2"] { - background-color: #32be44; -} -/* Hotcue Color: Blue */ -#HotcueButton[displayValue="1"][highlight="3"], -#HotcueButton[displayValue="2"][highlight="3"], -#SpecialCueButton[value="1"] { - background-color: #0044ff; -} -/* Hotcue Color: Yellow */ -#HotcueButton[displayValue="1"][highlight="4"], -#HotcueButton[displayValue="2"][highlight="4"] { - background-color: #f8d200; -} -/* Hotcue Color: Celeste */ -#HotcueButton[displayValue="1"][highlight="5"], -#HotcueButton[displayValue="2"][highlight="5"] { - background-color: #42d4f4; -} -/* Hotcue Color: Purple */ -#HotcueButton[displayValue="1"][highlight="6"], -#HotcueButton[displayValue="2"][highlight="6"] { - background-color: #af00cc; -} -/* Hotcue Color: Pink */ -#HotcueButton[displayValue="1"][highlight="7"], -#HotcueButton[displayValue="2"][highlight="7"] { - background-color: #fca6d7; -} -/* Hotcue Color: White */ -#HotcueButton[displayValue="1"][highlight="8"], -#HotcueButton[displayValue="2"][highlight="8"] { - background-color: #f2f2ff; -} -/************** Button borders & backgrounds **********************************/ - /************** Button icons **************************************************/ @@ -1112,7 +1074,7 @@ QPushButton#pushButtonRepeatPlaylist:!checked { padding: 3px 6px; */ qproperty-icon: url(skin:/buttons_classic/btn__delete.svg); /* color buttons are 42x24 px. - To get the final size for the Delete button consider border width. + To get the final size for the Delete button consider border width. wide button: width: 38px; height: 20px; @@ -2216,7 +2178,7 @@ WCueMenuPopup QPushButton:focus { padding-right: 3px; border-right: 1px solid #000; /* gradient colors should match those of QHeaderView gradient, - with a litte transparency added to not cut off the header label */ + with a litte transparency added to not cut off the header label */ background-color: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 rgba(34,34,34,190), stop:1 rgba(17,17,17,190)); @@ -2561,7 +2523,7 @@ WEffectSelector, #fadeModeCombobox { height: 19px; padding: 0px 0px 1px 5px; - margin: 0px 1px 2px 1px; + margin: 0px 1px 2px 1px; } WEffectSelector::down-arrow, #fadeModeCombobox::down-arrow { @@ -2584,7 +2546,7 @@ WEffectSelector, WEffectSelector::indicator:checked, #fadeModeCombobox::indicator:checked { /* checkbox container is 28 x 22px; - use margin + border to create a square checkbox sized like kill buttons */ + use margin + border to create a square checkbox sized like kill buttons */ margin: 2px; image: url(skin:/buttons_classic/btn__lib_checkmark_orange.svg); } @@ -2633,7 +2595,7 @@ WEffectSelector, padding: 5px 12px 5px 26px; } /* Icons in QLineEdit menus: - beatsize spinbox, searchbox, editable track properties */ + beatsize spinbox, searchbox, editable track properties */ WBeatSpinBox QMenu::icon, #LibraryContainer QMenu::icon, WCueMenuPopup QMenu::icon, @@ -2774,7 +2736,3 @@ WEffectSelector, #Border58 { border: 1px solid #585858; } - - - - diff --git a/res/skins/Shade/btn/btn_hotcue_1.png b/res/skins/Shade/btn/btn_hotcue_1.png index bbcc063c5253..86f572261d97 100644 Binary files a/res/skins/Shade/btn/btn_hotcue_1.png and b/res/skins/Shade/btn/btn_hotcue_1.png differ diff --git a/res/skins/Shade/btn/btn_hotcue_1_down.png b/res/skins/Shade/btn/btn_hotcue_1_down.png deleted file mode 100644 index b2b78e0b2cdd..000000000000 Binary files a/res/skins/Shade/btn/btn_hotcue_1_down.png and /dev/null differ diff --git a/res/skins/Shade/btn/btn_hotcue_1_over.png b/res/skins/Shade/btn/btn_hotcue_1_over.png deleted file mode 100644 index b02f6cbbfc77..000000000000 Binary files a/res/skins/Shade/btn/btn_hotcue_1_over.png and /dev/null differ diff --git a/res/skins/Shade/btn/btn_hotcue_1_overdown.png b/res/skins/Shade/btn/btn_hotcue_1_overdown.png deleted file mode 100644 index d63e01ebedb4..000000000000 Binary files a/res/skins/Shade/btn/btn_hotcue_1_overdown.png and /dev/null differ diff --git a/res/skins/Shade/btn/btn_hotcue_2.png b/res/skins/Shade/btn/btn_hotcue_2.png index f085b9f63a35..6c93b48fe2ed 100644 Binary files a/res/skins/Shade/btn/btn_hotcue_2.png and b/res/skins/Shade/btn/btn_hotcue_2.png differ diff --git a/res/skins/Shade/btn/btn_hotcue_2_down.png b/res/skins/Shade/btn/btn_hotcue_2_down.png deleted file mode 100644 index 93b227bafe44..000000000000 Binary files a/res/skins/Shade/btn/btn_hotcue_2_down.png and /dev/null differ diff --git a/res/skins/Shade/btn/btn_hotcue_2_over.png b/res/skins/Shade/btn/btn_hotcue_2_over.png deleted file mode 100644 index 248fb1ea9574..000000000000 Binary files a/res/skins/Shade/btn/btn_hotcue_2_over.png and /dev/null differ diff --git a/res/skins/Shade/btn/btn_hotcue_2_overdown.png b/res/skins/Shade/btn/btn_hotcue_2_overdown.png deleted file mode 100644 index a89346b0e30b..000000000000 Binary files a/res/skins/Shade/btn/btn_hotcue_2_overdown.png and /dev/null differ diff --git a/res/skins/Shade/btn/btn_hotcue_3.png b/res/skins/Shade/btn/btn_hotcue_3.png index 2ccd932a1647..b6494126f05a 100644 Binary files a/res/skins/Shade/btn/btn_hotcue_3.png and b/res/skins/Shade/btn/btn_hotcue_3.png differ diff --git a/res/skins/Shade/btn/btn_hotcue_3_down.png b/res/skins/Shade/btn/btn_hotcue_3_down.png deleted file mode 100644 index 96b06c765968..000000000000 Binary files a/res/skins/Shade/btn/btn_hotcue_3_down.png and /dev/null differ diff --git a/res/skins/Shade/btn/btn_hotcue_3_over.png b/res/skins/Shade/btn/btn_hotcue_3_over.png deleted file mode 100644 index bc39f10974a6..000000000000 Binary files a/res/skins/Shade/btn/btn_hotcue_3_over.png and /dev/null differ diff --git a/res/skins/Shade/btn/btn_hotcue_3_overdown.png b/res/skins/Shade/btn/btn_hotcue_3_overdown.png deleted file mode 100644 index 87587d95efa3..000000000000 Binary files a/res/skins/Shade/btn/btn_hotcue_3_overdown.png and /dev/null differ diff --git a/res/skins/Shade/btn/btn_hotcue_4.png b/res/skins/Shade/btn/btn_hotcue_4.png index d973f1eb8504..f3c47cc7ef81 100644 Binary files a/res/skins/Shade/btn/btn_hotcue_4.png and b/res/skins/Shade/btn/btn_hotcue_4.png differ diff --git a/res/skins/Shade/btn/btn_hotcue_4_down.png b/res/skins/Shade/btn/btn_hotcue_4_down.png deleted file mode 100644 index da33bba1f704..000000000000 Binary files a/res/skins/Shade/btn/btn_hotcue_4_down.png and /dev/null differ diff --git a/res/skins/Shade/btn/btn_hotcue_4_over.png b/res/skins/Shade/btn/btn_hotcue_4_over.png deleted file mode 100644 index 944eef1c6327..000000000000 Binary files a/res/skins/Shade/btn/btn_hotcue_4_over.png and /dev/null differ diff --git a/res/skins/Shade/btn/btn_hotcue_4_overdown.png b/res/skins/Shade/btn/btn_hotcue_4_overdown.png deleted file mode 100644 index 6dd65f063ccf..000000000000 Binary files a/res/skins/Shade/btn/btn_hotcue_4_overdown.png and /dev/null differ diff --git a/res/skins/Shade/btn/btn_hotcue_5.png b/res/skins/Shade/btn/btn_hotcue_5.png index dab5e3ce7b09..002cf0cc546f 100644 Binary files a/res/skins/Shade/btn/btn_hotcue_5.png and b/res/skins/Shade/btn/btn_hotcue_5.png differ diff --git a/res/skins/Shade/btn/btn_hotcue_5_down.png b/res/skins/Shade/btn/btn_hotcue_5_down.png deleted file mode 100644 index b893778f3148..000000000000 Binary files a/res/skins/Shade/btn/btn_hotcue_5_down.png and /dev/null differ diff --git a/res/skins/Shade/btn/btn_hotcue_5_over.png b/res/skins/Shade/btn/btn_hotcue_5_over.png deleted file mode 100644 index 65f7deda2ed1..000000000000 Binary files a/res/skins/Shade/btn/btn_hotcue_5_over.png and /dev/null differ diff --git a/res/skins/Shade/btn/btn_hotcue_5_overdown.png b/res/skins/Shade/btn/btn_hotcue_5_overdown.png deleted file mode 100644 index 9c4a9e443d8a..000000000000 Binary files a/res/skins/Shade/btn/btn_hotcue_5_overdown.png and /dev/null differ diff --git a/res/skins/Shade/btn/btn_hotcue_6.png b/res/skins/Shade/btn/btn_hotcue_6.png index be5e45c35f81..dffdf0b39c3d 100644 Binary files a/res/skins/Shade/btn/btn_hotcue_6.png and b/res/skins/Shade/btn/btn_hotcue_6.png differ diff --git a/res/skins/Shade/btn/btn_hotcue_6_down.png b/res/skins/Shade/btn/btn_hotcue_6_down.png deleted file mode 100644 index 22a37ba05d8c..000000000000 Binary files a/res/skins/Shade/btn/btn_hotcue_6_down.png and /dev/null differ diff --git a/res/skins/Shade/btn/btn_hotcue_6_over.png b/res/skins/Shade/btn/btn_hotcue_6_over.png deleted file mode 100644 index 7d792e2992cd..000000000000 Binary files a/res/skins/Shade/btn/btn_hotcue_6_over.png and /dev/null differ diff --git a/res/skins/Shade/btn/btn_hotcue_6_overdown.png b/res/skins/Shade/btn/btn_hotcue_6_overdown.png deleted file mode 100644 index 2f3f39c0582a..000000000000 Binary files a/res/skins/Shade/btn/btn_hotcue_6_overdown.png and /dev/null differ diff --git a/res/skins/Shade/btn/btn_hotcue_7.png b/res/skins/Shade/btn/btn_hotcue_7.png index f0821440d8c2..998978cde5e3 100644 Binary files a/res/skins/Shade/btn/btn_hotcue_7.png and b/res/skins/Shade/btn/btn_hotcue_7.png differ diff --git a/res/skins/Shade/btn/btn_hotcue_7_down.png b/res/skins/Shade/btn/btn_hotcue_7_down.png deleted file mode 100644 index c6da80d83a13..000000000000 Binary files a/res/skins/Shade/btn/btn_hotcue_7_down.png and /dev/null differ diff --git a/res/skins/Shade/btn/btn_hotcue_7_over.png b/res/skins/Shade/btn/btn_hotcue_7_over.png deleted file mode 100644 index 782c28391727..000000000000 Binary files a/res/skins/Shade/btn/btn_hotcue_7_over.png and /dev/null differ diff --git a/res/skins/Shade/btn/btn_hotcue_7_overdown.png b/res/skins/Shade/btn/btn_hotcue_7_overdown.png deleted file mode 100644 index 81af392fc4ab..000000000000 Binary files a/res/skins/Shade/btn/btn_hotcue_7_overdown.png and /dev/null differ diff --git a/res/skins/Shade/btn/btn_hotcue_8.png b/res/skins/Shade/btn/btn_hotcue_8.png index d4261196257f..36542964c2e1 100644 Binary files a/res/skins/Shade/btn/btn_hotcue_8.png and b/res/skins/Shade/btn/btn_hotcue_8.png differ diff --git a/res/skins/Shade/btn/btn_hotcue_8_down.png b/res/skins/Shade/btn/btn_hotcue_8_down.png deleted file mode 100644 index 9644ffd1ca1b..000000000000 Binary files a/res/skins/Shade/btn/btn_hotcue_8_down.png and /dev/null differ diff --git a/res/skins/Shade/btn/btn_hotcue_8_over.png b/res/skins/Shade/btn/btn_hotcue_8_over.png deleted file mode 100644 index 331d76d12bf2..000000000000 Binary files a/res/skins/Shade/btn/btn_hotcue_8_over.png and /dev/null differ diff --git a/res/skins/Shade/btn/btn_hotcue_8_overdown.png b/res/skins/Shade/btn/btn_hotcue_8_overdown.png deleted file mode 100644 index 70c287b664a0..000000000000 Binary files a/res/skins/Shade/btn/btn_hotcue_8_overdown.png and /dev/null differ diff --git a/res/skins/Shade/hotcue_button.xml b/res/skins/Shade/hotcue_button.xml index cf889c255f90..40149e41b8d2 100644 --- a/res/skins/Shade/hotcue_button.xml +++ b/res/skins/Shade/hotcue_button.xml @@ -6,13 +6,13 @@ 2 0 - skin:/btn/btn_hotcue__down.png + skin:/btn/btn_hotcue_.png skin:/btn/btn_hotcue_.png 1 - skin:/btn/btn_hotcue__overdown.png - skin:/btn/btn_hotcue__over.png + skin:/btn/btn_hotcue_.png + skin:/btn/btn_hotcue_.png ,hotcue__activate @@ -27,8 +27,8 @@ false - ,hotcue__color_id - highlight + ,hotcue__color + backgroundColorRgba diff --git a/res/skins/Shade/style.qss b/res/skins/Shade/style.qss index 5051566ea5e7..b0bc06487d96 100644 --- a/res/skins/Shade/style.qss +++ b/res/skins/Shade/style.qss @@ -220,7 +220,7 @@ WCoverArtMenu { /* unchecked menu checkbox */ #LibraryContainer QMenu QCheckBox::indicator:enabled:!checked, #LibraryContainer QMenu::indicator:!checked { - border-color: #1a2025;/* + border-color: #1a2025;/* background-color: #7e868b; remove OS focus indicator */ outline: none; @@ -784,47 +784,6 @@ QPushButton#pushButtonRepeatPlaylist { } /* AutoDJ button icons */ -/* Hotcue Color: No Color */ -#HotcueButton[highlight="0"] { - background-color: #fd0564; -} - -/* Hotcue Color: Red */ -#HotcueButton[highlight="1"] { - background-color: #c50a08; -} - -/* Hotcue Color: Green */ -#HotcueButton[highlight="2"] { - background-color: #32be44; -} - -/* Hotcue Color: Blue */ -#HotcueButton[highlight="3"] { - background-color: #0044ff; -} - -/* Hotcue Color: Yellow */ -#HotcueButton[highlight="4"] { - background-color: #f8d200; -} - -/* Hotcue Color: Celeste */ -#HotcueButton[highlight="5"] { - background-color: #42d4f4; -} - -/* Hotcue Color: Purple */ -#HotcueButton[highlight="6"] { - background-color: #af00cc; -} - -/* Hotcue Color: Pink */ -#HotcueButton[highlight="7"] { - background-color: #fca6d7; -} - -/* Hotcue Color: White */ -#HotcueButton[highlight="8"] { - background-color: #f2f2ff; +#HotcueButton { + background-color: #aab2b7; } diff --git a/res/skins/Shade/style_dark.qss b/res/skins/Shade/style_dark.qss index fbe409391e08..365e5d283d5f 100644 --- a/res/skins/Shade/style_dark.qss +++ b/res/skins/Shade/style_dark.qss @@ -187,8 +187,3 @@ WLibrary QPushButton:enabled { WLibrary QRadioButton::indicator:checked { image: url(skin:/btn/btn_lib_radio_button_on_mustard.svg) center center; } - -/* Hotcue Color: No Color */ -#HotcueButton[highlight="0"] { - background-color: #b39a00; -} diff --git a/res/skins/Shade/style_summer_sunset.qss b/res/skins/Shade/style_summer_sunset.qss index 86da85fbe159..6d569a637603 100644 --- a/res/skins/Shade/style_summer_sunset.qss +++ b/res/skins/Shade/style_summer_sunset.qss @@ -109,7 +109,9 @@ WLibrary QRadioButton::indicator:checked { image: url(skin:/btn/btn_lib_radio_button_on_neongreen.svg) center center; } -/* Hotcue Color: No Color */ -#HotcueButton[highlight="0"] { - background-color: #52f904; -} +/* 'Enable AutoDJ' button */ +QPushButton#pushButtonAutoDJ:hover, +QPushButton#pushButtonRecording:hover, +QPushButton#pushButtonAnalyze:hover { + border: 1px solid #52F904; + } diff --git a/res/skins/Tango/button_hotcue_deck.xml b/res/skins/Tango/button_hotcue_deck.xml index bb1009dbf424..eb0785f234c6 100644 --- a/res/skins/Tango/button_hotcue_deck.xml +++ b/res/skins/Tango/button_hotcue_deck.xml @@ -38,8 +38,8 @@ Variables: false - ,hotcue__color_id - highlight + ,hotcue__color + backgroundColorRgba diff --git a/res/skins/Tango/style.qss b/res/skins/Tango/style.qss index 4e77414dbf82..0938c92be550 100644 --- a/res/skins/Tango/style.qss +++ b/res/skins/Tango/style.qss @@ -1006,63 +1006,12 @@ WLabel#TrackComment { border: 1px solid #eeeeee; } -/* Hotcue Color: No Color */ -#HotcueButton[displayValue="1"][highlight="0"], -#HotcueButton[displayValue="2"][highlight="0"] { - background-color: #666; -} - -/* Hotcue Color: Red */ -#HotcueButton[displayValue="1"][highlight="1"], -#HotcueButton[displayValue="2"][highlight="1"] { - background-color: #c50a08; -} - -/* Hotcue Color: Green */ -#HotcueButton[displayValue="1"][highlight="2"], -#HotcueButton[displayValue="2"][highlight="2"] { - background-color: #32be44; - color: #111; -} - -/* Hotcue Color: Blue */ -#HotcueButton[displayValue="1"][highlight="3"], -#HotcueButton[displayValue="2"][highlight="3"] { - background-color: #0044ff; -} - -/* Hotcue Color: Yellow */ -#HotcueButton[displayValue="1"][highlight="4"], -#HotcueButton[displayValue="2"][highlight="4"] { - color: #222; - background-color: #f8d200; -} - -/* Hotcue Color: Celeste */ -#HotcueButton[displayValue="1"][highlight="5"], -#HotcueButton[displayValue="2"][highlight="5"] { - color: #111; - background-color: #42d4f4; -} - -/* Hotcue Color: Purple */ -#HotcueButton[displayValue="1"][highlight="6"], -#HotcueButton[displayValue="2"][highlight="6"] { - background-color: #af00cc; -} - -/* Hotcue Color: Pink */ -#HotcueButton[displayValue="1"][highlight="7"], -#HotcueButton[displayValue="2"][highlight="7"] { - color: #333; - background-color: #fca6d7; +#HotcueButton[backgroundIsDark=true][hasBackgroundColor=true] { + color: #eeeeee; } -/* Hotcue Color: White */ -#HotcueButton[displayValue="1"][highlight="8"], -#HotcueButton[displayValue="2"][highlight="8"] { - color: #222; - background-color: #f2f2ff; +#HotcueButton[backgroundIsDark=false][hasBackgroundColor=true] { + color: #0f0f0f; } #CueButton { diff --git a/src/controllers/bulk/bulkcontroller.cpp b/src/controllers/bulk/bulkcontroller.cpp index 17459ae8d973..49468b1ceabf 100644 --- a/src/controllers/bulk/bulkcontroller.cpp +++ b/src/controllers/bulk/bulkcontroller.cpp @@ -69,15 +69,15 @@ static QString get_string(libusb_device_handle *handle, u_int8_t id) { return QString::fromLatin1((char*)buf); } - -BulkController::BulkController(libusb_context* context, - libusb_device_handle *handle, - struct libusb_device_descriptor *desc) - : m_context(context), +BulkController::BulkController(UserSettingsPointer pConfig, + libusb_context* context, + libusb_device_handle* handle, + struct libusb_device_descriptor* desc) + : Controller(pConfig), + m_context(context), m_phandle(handle), in_epaddr(0), - out_epaddr(0) -{ + out_epaddr(0) { vendor_id = desc->idVendor; product_id = desc->idProduct; diff --git a/src/controllers/bulk/bulkcontroller.h b/src/controllers/bulk/bulkcontroller.h index 480aa084a69f..9d67c1041ea5 100644 --- a/src/controllers/bulk/bulkcontroller.h +++ b/src/controllers/bulk/bulkcontroller.h @@ -42,8 +42,10 @@ class BulkReader : public QThread { class BulkController : public Controller { Q_OBJECT public: - BulkController(libusb_context* context, libusb_device_handle *handle, - struct libusb_device_descriptor *desc); + BulkController(UserSettingsPointer pConfig, + libusb_context* context, + libusb_device_handle* handle, + struct libusb_device_descriptor* desc); ~BulkController() override; QString presetExtension() override; diff --git a/src/controllers/bulk/bulkenumerator.cpp b/src/controllers/bulk/bulkenumerator.cpp index 2942c5b3ac7b..8df74892935c 100644 --- a/src/controllers/bulk/bulkenumerator.cpp +++ b/src/controllers/bulk/bulkenumerator.cpp @@ -11,9 +11,10 @@ #include "controllers/bulk/bulkenumerator.h" #include "controllers/bulk/bulksupported.h" -BulkEnumerator::BulkEnumerator() +BulkEnumerator::BulkEnumerator(UserSettingsPointer pConfig) : ControllerEnumerator(), - m_context(NULL) { + m_context(nullptr), + m_pConfig(pConfig) { libusb_init(&m_context); } @@ -55,7 +56,8 @@ QList BulkEnumerator::queryDevices() { continue; } - BulkController* currentDevice = new BulkController(m_context, handle, &desc); + BulkController* currentDevice = + new BulkController(m_pConfig, m_context, handle, &desc); m_devices.push_back(currentDevice); } } diff --git a/src/controllers/bulk/bulkenumerator.h b/src/controllers/bulk/bulkenumerator.h index 5e16dd67d11d..effd0044d68a 100644 --- a/src/controllers/bulk/bulkenumerator.h +++ b/src/controllers/bulk/bulkenumerator.h @@ -14,7 +14,7 @@ struct libusb_context; class BulkEnumerator : public ControllerEnumerator { public: - BulkEnumerator(); + explicit BulkEnumerator(UserSettingsPointer pConfig); virtual ~BulkEnumerator(); QList queryDevices(); @@ -22,6 +22,7 @@ class BulkEnumerator : public ControllerEnumerator { private: QList m_devices; libusb_context* m_context; + UserSettingsPointer m_pConfig; }; #endif diff --git a/src/controllers/colorjsproxy.cpp b/src/controllers/colorjsproxy.cpp deleted file mode 100644 index 4147cdcc124a..000000000000 --- a/src/controllers/colorjsproxy.cpp +++ /dev/null @@ -1,36 +0,0 @@ -#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::kPredefinedColorsSet.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::kPredefinedColorsSet.allColors.length(); - QScriptValue colorList = pScriptEngine->newArray(numColors); - for (int i = 0; i < numColors; ++i) { - PredefinedColorPointer color = Color::kPredefinedColorsSet.allColors.at(i); - colorList.setProperty(i, jsColorFrom(color)); - } - return colorList; -} diff --git a/src/controllers/colorjsproxy.h b/src/controllers/colorjsproxy.h deleted file mode 100644 index e6ebeda1d9b0..000000000000 --- a/src/controllers/colorjsproxy.h +++ /dev/null @@ -1,27 +0,0 @@ -#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/colormapper.cpp b/src/controllers/colormapper.cpp new file mode 100644 index 000000000000..e308c7b1d839 --- /dev/null +++ b/src/controllers/colormapper.cpp @@ -0,0 +1,64 @@ +#include "controllers/colormapper.h" + +#include +#include + +#include "util/logger.h" + +namespace { + +const mixxx::Logger kLogger("ColorMapper"); + +double colorDistance(QRgb a, QRgb b) { + // This algorithm calculates the distance between two colors. In + // contrast to the L2 norm, this also tries take the human perception + // of colors into account. More accurate algorithms like the CIELAB2000 + // Delta-E rely on sophisticated color space conversions and need a lot + // of costly computations. In contrast, this is a low-cost + // approximation and should be sufficently accurate. + // More details: https://www.compuphase.com/cmetric.htm + long mean_red = (static_cast(qRed(a)) + static_cast(qRed(b))) / 2; + long delta_red = static_cast(qRed(a)) - static_cast(qRed(b)); + long delta_green = static_cast(qGreen(a)) - static_cast(qGreen(b)); + long delta_blue = static_cast(qBlue(a)) - static_cast(qBlue(b)); + return sqrt( + (((512 + mean_red) * delta_red * delta_red) >> 8) + + (4 * delta_green * delta_green) + + (((767 - mean_red) * delta_blue * delta_blue) >> 8)); +} + +} // namespace + +QRgb ColorMapper::getNearestColor(QRgb desiredColor) { + // If desired color is already in cache, use cache entry + QMap::const_iterator i = m_cache.constFind(desiredColor); + if (i != m_cache.constEnd()) { + DEBUG_ASSERT(m_availableColors.contains(i.value())); + kLogger.trace() + << "ColorMapper cache hit for" << desiredColor << ":" + << "Color =" << i.value(); + return i.value(); + } + + // Color is not cached + QRgb nearestColor; + double nearestColorDistance = qInf(); + for (auto j = m_availableColors.constBegin(); j != m_availableColors.constEnd(); j++) { + QRgb availableColor = j.key(); + double distance = colorDistance(desiredColor, availableColor); + if (distance < nearestColorDistance) { + nearestColorDistance = distance; + nearestColor = j.key(); + } + } + + kLogger.trace() + << "ColorMapper found matching color for" << desiredColor << ":" + << "Color =" << nearestColor; + m_cache.insert(desiredColor, nearestColor); + return nearestColor; +} + +QVariant ColorMapper::getValueForNearestColor(QRgb desiredColor) { + return m_availableColors.value(getNearestColor(desiredColor)); +} diff --git a/src/controllers/colormapper.h b/src/controllers/colormapper.h new file mode 100644 index 000000000000..05e2cb905518 --- /dev/null +++ b/src/controllers/colormapper.h @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include +#include + +#include "util/assert.h" + +// This class allows to find the nearest color representation of a given color +// in a set of fixed colors. Additional user data (e.g. MIDI byte values) can +// be linked to colors in the color set as QVariant. +class ColorMapper final { + public: + ColorMapper() = delete; + explicit ColorMapper(QMap availableColors) + : m_availableColors(availableColors) { + DEBUG_ASSERT(!m_availableColors.isEmpty()); + } + + QRgb getNearestColor(QRgb desiredColor); + QVariant getValueForNearestColor(QRgb desiredColor); + + private: + const QMap m_availableColors; + QMap m_cache; +}; diff --git a/src/controllers/colormapperjsproxy.cpp b/src/controllers/colormapperjsproxy.cpp new file mode 100644 index 000000000000..12ed2d77df16 --- /dev/null +++ b/src/controllers/colormapperjsproxy.cpp @@ -0,0 +1,59 @@ +#include + +#include "controllers/colormapperjsproxy.h" + +ColorMapperJSProxy::ColorMapperJSProxy(QScriptEngine* pScriptEngine, QMap availableColors) + : m_pScriptEngine(pScriptEngine), + m_colorMapper(new ColorMapper(availableColors)) { +} + +QScriptValue ColorMapperJSProxy::getNearestColor(uint colorCode) { + QRgb result = m_colorMapper->getNearestColor(static_cast(colorCode)); + QScriptValue jsColor = m_pScriptEngine->newObject(); + jsColor.setProperty("red", qRed(result)); + jsColor.setProperty("green", qGreen(result)); + jsColor.setProperty("blue", qBlue(result)); + return jsColor; +} + +QScriptValue ColorMapperJSProxy::getValueForNearestColor(uint colorCode) { + return m_pScriptEngine->toScriptValue( + m_colorMapper->getValueForNearestColor(static_cast(colorCode))); +} + +QScriptValue ColorMapperJSProxyConstructor(QScriptContext* pScriptContext, QScriptEngine* pScriptEngine) { + QMap availableColors; + if (pScriptContext->argumentCount() != 1) { + pScriptContext->throwError( + QStringLiteral("Failed to create ColorMapper object: constructor takes exactly one argument!")); + return pScriptEngine->undefinedValue(); + } + QScriptValue argument = pScriptContext->argument(0); + if (!argument.isValid() || !argument.isObject()) { + pScriptContext->throwError( + QStringLiteral("Failed to create ColorMapper object: argument needs to be an object!")); + return pScriptEngine->undefinedValue(); + } + + QScriptValueIterator it(argument); + while (it.hasNext()) { + it.next(); + QColor color(it.name()); + if (color.isValid()) { + availableColors.insert(color.rgb(), it.value().toVariant()); + } else { + pScriptContext->throwError( + QStringLiteral("Invalid color name passed to ColorMapper: ") + it.name()); + continue; + } + } + + if (availableColors.isEmpty()) { + pScriptContext->throwError( + QStringLiteral("Failed to create ColorMapper object: available colors mustn't be empty!")); + return pScriptEngine->undefinedValue(); + } + + QObject* colorMapper = new ColorMapperJSProxy(pScriptEngine, availableColors); + return pScriptEngine->newQObject(colorMapper, QScriptEngine::ScriptOwnership); +} diff --git a/src/controllers/colormapperjsproxy.h b/src/controllers/colormapperjsproxy.h new file mode 100644 index 000000000000..3a08f062d556 --- /dev/null +++ b/src/controllers/colormapperjsproxy.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +#include "controllers/colormapper.h" + +// This is a wrapper class that exposes ColorMapper via the QScriptEngine and +// makes it possible to create and use ColorMapper object from JavaScript +// controller mappings. +class ColorMapperJSProxy final : public QObject { + Q_OBJECT + public: + ColorMapperJSProxy() = delete; + ColorMapperJSProxy(QScriptEngine* pScriptEngine, QMap availableColors); + + ~ColorMapperJSProxy() override { + delete m_colorMapper; + }; + + // Q_INVOKABLE is need here because these methods callable from controller + // scripts + + // For a given RGB color code (e.g. 0xFF0000), this finds the nearest + // available color and returns a JS object with properties "red", "green", + // "blue" (each with value range 0-255). + Q_INVOKABLE QScriptValue getNearestColor(uint ColorCode); + + // For a given RGB color code (e.g. 0xFF0000), this finds the nearest + // available color, then returns the value associated with that color + // (which could be a MIDI byte value for example). + Q_INVOKABLE QScriptValue getValueForNearestColor(uint ColorCode); + + private: + QScriptEngine* m_pScriptEngine; + ColorMapper* m_colorMapper; +}; + +QScriptValue ColorMapperJSProxyConstructor(QScriptContext* context, QScriptEngine* engine); diff --git a/src/controllers/controller.cpp b/src/controllers/controller.cpp index e30ff7a5f63a..15a1e1dd5ec0 100644 --- a/src/controllers/controller.cpp +++ b/src/controllers/controller.cpp @@ -13,14 +13,15 @@ #include "controllers/defs_controllers.h" #include "util/screensaver.h" -Controller::Controller() +Controller::Controller(UserSettingsPointer pConfig) : QObject(), m_pEngine(NULL), m_bIsOutputDevice(false), m_bIsInputDevice(false), m_bIsOpen(false), - m_bLearning(false) { - m_userActivityInhibitTimer.start(); + m_bLearning(false), + m_pConfig(pConfig) { + m_userActivityInhibitTimer.start(); } Controller::~Controller() { @@ -35,7 +36,7 @@ void Controller::startEngine() qWarning() << "Controller: Engine already exists! Restarting:"; stopEngine(); } - m_pEngine = new ControllerEngine(this); + m_pEngine = new ControllerEngine(this, m_pConfig); } void Controller::stopEngine() { diff --git a/src/controllers/controller.h b/src/controllers/controller.h index ec2ad82ceac3..09a20d8181bc 100644 --- a/src/controllers/controller.h +++ b/src/controllers/controller.h @@ -23,7 +23,7 @@ class Controller : public QObject, ConstControllerPresetVisitor { Q_OBJECT public: - Controller(); + explicit Controller(UserSettingsPointer pConfig); ~Controller() override; // Subclass should call close() at minimum. // Returns the extension for the controller (type) preset files. This is @@ -101,7 +101,7 @@ class Controller : public QObject, ConstControllerPresetVisitor { // To be called in sub-class' close() functions after stopping any input // polling/processing but before closing the device. void stopEngine(); - + // To be called when receiving events void triggerActivity(); @@ -162,6 +162,8 @@ class Controller : public QObject, ConstControllerPresetVisitor { bool m_bLearning; QElapsedTimer m_userActivityInhibitTimer; + UserSettingsPointer m_pConfig; + // accesses lots of our stuff, but in the same thread friend class ControllerManager; // For testing diff --git a/src/controllers/controllerengine.cpp b/src/controllers/controllerengine.cpp index 8b272c4e5286..1488353b5d48 100644 --- a/src/controllers/controllerengine.cpp +++ b/src/controllers/controllerengine.cpp @@ -6,8 +6,8 @@ email : spappalardo@mixxx.org ***************************************************************************/ +#include "controllers/colormapperjsproxy.h" #include "controllers/controllerengine.h" - #include "controllers/controller.h" #include "controllers/controllerdebug.h" #include "control/controlobject.h" @@ -29,9 +29,11 @@ const int kDecks = 16; const int kScratchTimerMs = 1; const double kAlphaBetaDt = kScratchTimerMs / 1000.0; -ControllerEngine::ControllerEngine(Controller* controller) +ControllerEngine::ControllerEngine( + Controller* controller, UserSettingsPointer pConfig) : m_pEngine(nullptr), m_pController(controller), + m_pConfig(pConfig), m_bPopups(false), m_pBaClass(nullptr) { // Handle error dialog buttons @@ -185,7 +187,6 @@ void ControllerEngine::gracefulShutdown() { ++it; } - m_pColorJSProxy.reset(); delete m_pBaClass; m_pBaClass = nullptr; } @@ -213,8 +214,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())); + QScriptValue constructor = m_pEngine->newFunction(ColorMapperJSProxyConstructor); + QScriptValue metaObject = m_pEngine->newQMetaObject(&ColorMapperJSProxy::staticMetaObject, constructor); + engineGlobalObject.setProperty("ColorMapper", metaObject); 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 2359a0bcbb3c..87a75abd730a 100644 --- a/src/controllers/controllerengine.h +++ b/src/controllers/controllerengine.h @@ -16,7 +16,6 @@ #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" @@ -80,7 +79,7 @@ class ScriptConnectionInvokableWrapper : public QObject { class ControllerEngine : public QObject { Q_OBJECT public: - ControllerEngine(Controller* controller); + ControllerEngine(Controller* controller, UserSettingsPointer pConfig); virtual ~ControllerEngine(); bool isReady(); @@ -198,6 +197,7 @@ class ControllerEngine : public QObject { double getDeckRate(const QString& group); Controller* m_pController; + const UserSettingsPointer m_pConfig; bool m_bPopups; QList m_scriptFunctionPrefixes; QMap m_scriptErrors; @@ -210,7 +210,6 @@ 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/controllermanager.cpp b/src/controllers/controllermanager.cpp index d6d4592bd273..ec74976f7f5d 100644 --- a/src/controllers/controllermanager.cpp +++ b/src/controllers/controllermanager.cpp @@ -127,15 +127,15 @@ void ControllerManager::slotInitialize() { // Instantiate all enumerators. Enumerators can take a long time to // construct since they interact with host MIDI APIs. - m_enumerators.append(new PortMidiEnumerator()); + m_enumerators.append(new PortMidiEnumerator(m_pConfig)); #ifdef __HSS1394__ - m_enumerators.append(new Hss1394Enumerator()); + m_enumerators.append(new Hss1394Enumerator(m_pConfig)); #endif #ifdef __BULK__ - m_enumerators.append(new BulkEnumerator()); + m_enumerators.append(new BulkEnumerator(m_pConfig)); #endif #ifdef __HID__ - m_enumerators.append(new HidEnumerator()); + m_enumerators.append(new HidEnumerator(m_pConfig)); #endif } diff --git a/src/controllers/hid/hidcontroller.cpp b/src/controllers/hid/hidcontroller.cpp index c317799155e5..ec118012088c 100644 --- a/src/controllers/hid/hidcontroller.cpp +++ b/src/controllers/hid/hidcontroller.cpp @@ -16,8 +16,9 @@ #include "controllers/controllerdebug.h" #include "util/time.h" -HidController::HidController(const hid_device_info deviceInfo) - : m_pHidDevice(NULL) { +HidController::HidController(const hid_device_info& deviceInfo, UserSettingsPointer pConfig) + : Controller(pConfig), + m_pHidDevice(NULL) { // Copy required variables from deviceInfo, which will be freed after // this class is initialized by caller. hid_vendor_id = deviceInfo.vendor_id; diff --git a/src/controllers/hid/hidcontroller.h b/src/controllers/hid/hidcontroller.h index 4f446c9ae9bc..f60843067a8e 100644 --- a/src/controllers/hid/hidcontroller.h +++ b/src/controllers/hid/hidcontroller.h @@ -20,7 +20,7 @@ class HidController final : public Controller { Q_OBJECT public: - HidController(const hid_device_info deviceInfo); + HidController(const hid_device_info& deviceInfo, UserSettingsPointer pConfig); ~HidController() override; QString presetExtension() override; diff --git a/src/controllers/hid/hidenumerator.cpp b/src/controllers/hid/hidenumerator.cpp index c1c2dc278342..802459f3c028 100644 --- a/src/controllers/hid/hidenumerator.cpp +++ b/src/controllers/hid/hidenumerator.cpp @@ -11,7 +11,8 @@ #include "controllers/hid/hidenumerator.h" #include "controllers/hid/hidblacklist.h" -HidEnumerator::HidEnumerator() : ControllerEnumerator() { +HidEnumerator::HidEnumerator(UserSettingsPointer pConfig) + : ControllerEnumerator(), m_pConfig(pConfig) { } HidEnumerator::~HidEnumerator() { @@ -98,7 +99,7 @@ QList HidEnumerator::queryDevices() { continue; } - HidController* currentDevice = new HidController(*cur_dev); + HidController* currentDevice = new HidController(*cur_dev, m_pConfig); m_devices.push_back(currentDevice); } hid_free_enumeration(devs); diff --git a/src/controllers/hid/hidenumerator.h b/src/controllers/hid/hidenumerator.h index 351e972beb13..a61490f1ab49 100644 --- a/src/controllers/hid/hidenumerator.h +++ b/src/controllers/hid/hidenumerator.h @@ -12,13 +12,14 @@ class HidEnumerator : public ControllerEnumerator { public: - HidEnumerator(); + explicit HidEnumerator(UserSettingsPointer pConfig); virtual ~HidEnumerator(); QList queryDevices(); private: QList m_devices; + UserSettingsPointer m_pConfig; }; #endif diff --git a/src/controllers/midi/hss1394controller.cpp b/src/controllers/midi/hss1394controller.cpp index 7c78a5b4a6a7..886530d29290 100644 --- a/src/controllers/midi/hss1394controller.cpp +++ b/src/controllers/midi/hss1394controller.cpp @@ -64,9 +64,11 @@ void DeviceChannelListener::Reconnected() { qDebug() << "HSS1394 device" << m_sName << "re-connected"; } -Hss1394Controller::Hss1394Controller(const hss1394::TNodeInfo deviceInfo, - int deviceIndex) - : MidiController(), +Hss1394Controller::Hss1394Controller( + const hss1394::TNodeInfo& deviceInfo, + int deviceIndex, + UserSettingsPointer pConfig) + : MidiController(pConfig), m_deviceInfo(deviceInfo), m_iDeviceIndex(deviceIndex) { // Note: We prepend the input stream's index to the device's name to prevent diff --git a/src/controllers/midi/hss1394controller.h b/src/controllers/midi/hss1394controller.h index ec6e55030931..c4a0045c59f3 100644 --- a/src/controllers/midi/hss1394controller.h +++ b/src/controllers/midi/hss1394controller.h @@ -43,7 +43,10 @@ class DeviceChannelListener : public QObject, public hss1394::ChannelListener { class Hss1394Controller : public MidiController { Q_OBJECT public: - Hss1394Controller(const hss1394::TNodeInfo deviceInfo, int deviceIndex); + Hss1394Controller( + const hss1394::TNodeInfo& deviceInfo, + int deviceIndex, + UserSettingsPointer pConfig); ~Hss1394Controller() override; private slots: diff --git a/src/controllers/midi/hss1394enumerator.cpp b/src/controllers/midi/hss1394enumerator.cpp index 8152abd0e367..9c29ac98ee30 100644 --- a/src/controllers/midi/hss1394enumerator.cpp +++ b/src/controllers/midi/hss1394enumerator.cpp @@ -9,7 +9,9 @@ #include "controllers/midi/hss1394controller.h" #include "controllers/midi/hss1394enumerator.h" -Hss1394Enumerator::Hss1394Enumerator() : MidiEnumerator() { +Hss1394Enumerator::Hss1394Enumerator(UserSettingsPointer pConfig) + : MidiEnumerator(), + m_pConfig(pConfig) { } Hss1394Enumerator::~Hss1394Enumerator() { @@ -42,8 +44,8 @@ QList Hss1394Enumerator::queryDevices() { QString("%1").arg(tNodeInfo.uGUID.mu32Low, 0, 16), QString("%1").arg(tNodeInfo.uProtocolVersion, 0, 16)); qDebug() << " " << message; - Hss1394Controller *currentDevice = new Hss1394Controller( - tNodeInfo, i); + Hss1394Controller* currentDevice = new Hss1394Controller( + tNodeInfo, i, m_pConfig); m_devices.push_back(currentDevice); } } diff --git a/src/controllers/midi/hss1394enumerator.h b/src/controllers/midi/hss1394enumerator.h index baa208701fc7..184fcfb233c9 100644 --- a/src/controllers/midi/hss1394enumerator.h +++ b/src/controllers/midi/hss1394enumerator.h @@ -13,12 +13,13 @@ class Hss1394Enumerator : public MidiEnumerator { Q_OBJECT public: - Hss1394Enumerator(); + explicit Hss1394Enumerator(UserSettingsPointer pConfig); virtual ~Hss1394Enumerator(); QList queryDevices(); private: + UserSettingsPointer m_pConfig; QList m_devices; }; diff --git a/src/controllers/midi/midicontroller.cpp b/src/controllers/midi/midicontroller.cpp index 479631b83885..877897f00f19 100644 --- a/src/controllers/midi/midicontroller.cpp +++ b/src/controllers/midi/midicontroller.cpp @@ -17,8 +17,8 @@ #include "util/math.h" #include "util/screensaver.h" -MidiController::MidiController() - : Controller() { +MidiController::MidiController(UserSettingsPointer pConfig) + : Controller(pConfig) { setDeviceCategory(tr("MIDI Controller")); } diff --git a/src/controllers/midi/midicontroller.h b/src/controllers/midi/midicontroller.h index 93d91fdf4436..a81ff7d680ce 100644 --- a/src/controllers/midi/midicontroller.h +++ b/src/controllers/midi/midicontroller.h @@ -23,7 +23,7 @@ class MidiController : public Controller { Q_OBJECT public: - MidiController(); + explicit MidiController(UserSettingsPointer pConfig); ~MidiController() override; QString presetExtension() override; diff --git a/src/controllers/midi/portmidicontroller.cpp b/src/controllers/midi/portmidicontroller.cpp index c4479a1ae68e..4be78583c781 100644 --- a/src/controllers/midi/portmidicontroller.cpp +++ b/src/controllers/midi/portmidicontroller.cpp @@ -12,12 +12,11 @@ #include "controllers/controllerdebug.h" PortMidiController::PortMidiController(const PmDeviceInfo* inputDeviceInfo, - const PmDeviceInfo* outputDeviceInfo, - int inputDeviceIndex, - int outputDeviceIndex) - : MidiController(), - m_cReceiveMsg_index(0), - m_bInSysex(false) { + const PmDeviceInfo* outputDeviceInfo, + int inputDeviceIndex, + int outputDeviceIndex, + UserSettingsPointer pConfig) + : MidiController(pConfig), m_cReceiveMsg_index(0), m_bInSysex(false) { for (unsigned int k = 0; k < MIXXX_PORTMIDI_BUFFER_LEN; ++k) { // Can be shortened to `m_midiBuffer[k] = {}` with C++11. m_midiBuffer[k].message = 0; diff --git a/src/controllers/midi/portmidicontroller.h b/src/controllers/midi/portmidicontroller.h index 96da9afce1a4..92d2954e18f2 100644 --- a/src/controllers/midi/portmidicontroller.h +++ b/src/controllers/midi/portmidicontroller.h @@ -60,9 +60,10 @@ class PortMidiController : public MidiController { Q_OBJECT public: PortMidiController(const PmDeviceInfo* inputDeviceInfo, - const PmDeviceInfo* outputDeviceInfo, - int inputDeviceIndex, - int outputDeviceIndex); + const PmDeviceInfo* outputDeviceInfo, + int inputDeviceIndex, + int outputDeviceIndex, + UserSettingsPointer pConfig); ~PortMidiController() override; private slots: diff --git a/src/controllers/midi/portmidienumerator.cpp b/src/controllers/midi/portmidienumerator.cpp index 47e6a23a829b..ba525ddeb737 100644 --- a/src/controllers/midi/portmidienumerator.cpp +++ b/src/controllers/midi/portmidienumerator.cpp @@ -21,7 +21,8 @@ bool shouldBlacklistDevice(const PmDeviceInfo* device) { deviceName.startsWith("Midi Through Port", Qt::CaseInsensitive); } -PortMidiEnumerator::PortMidiEnumerator() : MidiEnumerator() { +PortMidiEnumerator::PortMidiEnumerator(UserSettingsPointer pConfig) + : MidiEnumerator(), m_pConfig(pConfig) { PmError err = Pm_Initialize(); // Based on reading the source, it's not possible for this to fail. if (err != pmNoError) { @@ -257,9 +258,12 @@ QList PortMidiEnumerator::queryDevices() { // device (outputDeviceInfo != NULL). //.... so create our (aggregate) MIDI device! - PortMidiController *currentDevice = new PortMidiController( - inputDeviceInfo, outputDeviceInfo, - inputDevIndex, outputDevIndex); + PortMidiController* currentDevice = + new PortMidiController(inputDeviceInfo, + outputDeviceInfo, + inputDevIndex, + outputDevIndex, + m_pConfig); m_devices.push_back(currentDevice); } diff --git a/src/controllers/midi/portmidienumerator.h b/src/controllers/midi/portmidienumerator.h index 606c4feb4de3..5169efbfa066 100644 --- a/src/controllers/midi/portmidienumerator.h +++ b/src/controllers/midi/portmidienumerator.h @@ -13,13 +13,14 @@ class PortMidiEnumerator : public MidiEnumerator { Q_OBJECT public: - PortMidiEnumerator(); + explicit PortMidiEnumerator(UserSettingsPointer pConfig); virtual ~PortMidiEnumerator(); QList queryDevices(); private: QList m_devices; + UserSettingsPointer m_pConfig; }; // For testing. diff --git a/src/database/mixxxdb.cpp b/src/database/mixxxdb.cpp index e3a2e70cc7e4..ab01999a95ac 100644 --- a/src/database/mixxxdb.cpp +++ b/src/database/mixxxdb.cpp @@ -11,7 +11,7 @@ const QString MixxxDb::kDefaultSchemaFile(":/schema.xml"); //static -const int MixxxDb::kRequiredSchemaVersion = 31; +const int MixxxDb::kRequiredSchemaVersion = 32; namespace { diff --git a/src/engine/controls/cuecontrol.cpp b/src/engine/controls/cuecontrol.cpp index 7c0b35a32b37..d087506dbc4e 100644 --- a/src/engine/controls/cuecontrol.cpp +++ b/src/engine/controls/cuecontrol.cpp @@ -1,17 +1,18 @@ // cuecontrol.cpp // Created 11/5/2009 by RJ Ryan (rryan@mit.edu) -#include - -#include "engine/enginebuffer.h" #include "engine/controls/cuecontrol.h" +#include + +#include "control/controlindicator.h" #include "control/controlobject.h" #include "control/controlpushbutton.h" -#include "control/controlindicator.h" -#include "vinylcontrol/defs_vinylcontrol.h" -#include "util/sample.h" +#include "engine/enginebuffer.h" +#include "preferences/colorpalettesettings.h" #include "util/color/color.h" +#include "util/sample.h" +#include "vinylcontrol/defs_vinylcontrol.h" // TODO: Convert these doubles to a standard enum // and convert elseif logic to switch statements @@ -22,19 +23,39 @@ static const double CUE_MODE_NUMARK = 3.0; static const double CUE_MODE_MIXXX_NO_BLINK = 4.0; static const double CUE_MODE_CUP = 5.0; +constexpr double kNoColorControlValue = -1; + +namespace { + +// Helper function to convert control values (i.e. doubles) into RgbColor +// instances (or nullopt if value < 0). This happens by using the integer +// component as RGB color codes (e.g. 0xFF0000). +inline mixxx::RgbColor::optional_t doubleToRgbColor(double value) { + if (value < 0) { + return std::nullopt; + } + auto colorCode = static_cast(value); + if (value != mixxx::RgbColor::validateCode(colorCode)) { + return std::nullopt; + } + return mixxx::RgbColor::optional(colorCode); +} + +} // namespace + CueControl::CueControl(QString group, - UserSettingsPointer pConfig) : - EngineControl(group, pConfig), - m_bPreviewing(false), - // m_pPlay->toBoo() -> engine play state - // m_pPlay->set(1.0) -> emulate play button press - m_pPlay(ControlObject::getControl(ConfigKey(group, "play"))), - m_pStopButton(ControlObject::getControl(ConfigKey(group, "stop"))), - m_iCurrentlyPreviewingHotcues(0), - m_bypassCueSetByPlay(false), - m_iNumHotCues(NUM_HOT_CUES), - m_pLoadedTrack(), - m_mutex(QMutex::Recursive) { + UserSettingsPointer pConfig) + : EngineControl(group, pConfig), + m_pConfig(pConfig), + m_colorPaletteSettings(ColorPaletteSettings(pConfig)), + m_bPreviewing(false), + m_pPlay(ControlObject::getControl(ConfigKey(group, "play"))), + m_pStopButton(ControlObject::getControl(ConfigKey(group, "stop"))), + m_iCurrentlyPreviewingHotcues(0), + m_bypassCueSetByPlay(false), + m_iNumHotCues(NUM_HOT_CUES), + m_pLoadedTrack(), + m_mutex(QMutex::Recursive) { // To silence a compiler warning about CUE_MODE_PIONEER. Q_UNUSED(CUE_MODE_PIONEER); createControls(); @@ -314,6 +335,7 @@ void CueControl::detachCue(HotcueControl* pControl) { } disconnect(pCue.get(), 0, this, 0); pControl->resetCue(); + pControl->setColor(std::nullopt); } void CueControl::trackLoaded(TrackPointer pNewTrack) { @@ -594,14 +616,18 @@ void CueControl::hotcueSet(HotcueControl* pControl, double v) { pCue->setHotCue(hotcue); pCue->setLabel(); pCue->setType(mixxx::CueType::HotCue); + + ConfigKey autoHotcueColorsKey("[Controls]", "auto_hotcue_colors"); + if (getConfig()->getValue(autoHotcueColorsKey, false)) { + auto hotcueColorPalette = m_colorPaletteSettings.getHotcueColorPalette(); + pCue->setColor(hotcueColorPalette.colorForHotcueIndex(hotcue)); + } else { + pCue->setColor(ColorPalette::kDefaultCueColor); + } + // TODO(XXX) deal with spurious signals attachCue(pCue, pControl); - if (getConfig()->getValue(ConfigKey("[Controls]", "auto_hotcue_colors"), false)) { - const QList predefinedColors = Color::kPredefinedColorsSet.allColors; - pCue->setColor(predefinedColors.at((hotcue % (predefinedColors.count() - 1)) + 1)); - }; - // If quantize is enabled and we are not playing, jump to the cue point // since it's not necessarily where we currently are. TODO(XXX) is this // potentially invalid for vinyl control? @@ -1684,20 +1710,13 @@ void CueControl::hotcueFocusColorPrev(double v) { return; } - PredefinedColorPointer pColor = pControl->getColor(); - if (!pColor) { + mixxx::RgbColor::optional_t controlColor = pControl->getColor(); + if (!controlColor) { return; } - // Get previous color in color set - int iColorIndex = Color::kPredefinedColorsSet.predefinedColorIndex(pColor) - 1; - if (iColorIndex <= 0) { - iColorIndex = Color::kPredefinedColorsSet.allColors.size() - 1; - } - pColor = Color::kPredefinedColorsSet.allColors.at(iColorIndex); - DEBUG_ASSERT(pColor != nullptr); - - pControl->setColor(pColor); + ColorPalette colorPalette = m_colorPaletteSettings.getHotcueColorPalette(); + pControl->setColor(colorPalette.previousColor(*controlColor)); } void CueControl::hotcueFocusColorNext(double v) { @@ -1715,20 +1734,13 @@ void CueControl::hotcueFocusColorNext(double v) { return; } - PredefinedColorPointer pColor = pControl->getColor(); - if (!pColor) { + mixxx::RgbColor::optional_t controlColor = pControl->getColor(); + if (!controlColor) { return; } - // Get next color in color set - int iColorIndex = Color::kPredefinedColorsSet.predefinedColorIndex(pColor) + 1; - if (iColorIndex >= Color::kPredefinedColorsSet.allColors.size()) { - iColorIndex = 0; - } - pColor = Color::kPredefinedColorsSet.allColors.at(iColorIndex); - DEBUG_ASSERT(pColor != nullptr); - - pControl->setColor(pColor); + ColorPalette colorPalette = m_colorPaletteSettings.getHotcueColorPalette(); + pControl->setColor(colorPalette.nextColor(*controlColor)); } @@ -1755,8 +1767,9 @@ 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")); + // The rgba value of the color assigned to this color. + m_hotcueColor = new ControlObject(keyForControl(i, "color")); + m_hotcueColor->set(kNoColorControlValue); connect(m_hotcueColor, &ControlObject::valueChanged, this, @@ -1845,9 +1858,19 @@ void HotcueControl::slotHotcuePositionChanged(double newPosition) { emit hotcuePositionChanged(this, newPosition); } -void HotcueControl::slotHotcueColorChanged(double newColorId) { - m_pCue->setColor(Color::kPredefinedColorsSet.predefinedColorFromId(newColorId)); - emit hotcueColorChanged(this, newColorId); +void HotcueControl::slotHotcueColorChanged(double newColor) { + if (!m_pCue) { + return; + } + + mixxx::RgbColor::optional_t color = doubleToRgbColor(newColor); + if (!color) { + qWarning() << "slotHotcueColorChanged got invalid value:" << newColor; + return; + } + + m_pCue->setColor(*color); + emit hotcueColorChanged(this, newColor); } double HotcueControl::getPosition() const { @@ -1861,12 +1884,16 @@ 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::kPredefinedColorsSet.predefinedColorFromId(m_hotcueColor->get()); +mixxx::RgbColor::optional_t HotcueControl::getColor() const { + return doubleToRgbColor(m_hotcueColor->get()); } -void HotcueControl::setColor(PredefinedColorPointer newColor) { - m_hotcueColor->set(static_cast(newColor->m_iId)); +void HotcueControl::setColor(mixxx::RgbColor::optional_t newColor) { + if (newColor) { + m_hotcueColor->set(*newColor); + } else { + m_hotcueColor->set(kNoColorControlValue); + } } void HotcueControl::resetCue() { // clear pCue first because we have a null check for valid data else where diff --git a/src/engine/controls/cuecontrol.h b/src/engine/controls/cuecontrol.h index be65fd1a3337..4c9f0e32a784 100644 --- a/src/engine/controls/cuecontrol.h +++ b/src/engine/controls/cuecontrol.h @@ -7,9 +7,10 @@ #include #include +#include "control/controlproxy.h" #include "engine/controls/enginecontrol.h" +#include "preferences/colorpalettesettings.h" #include "preferences/usersettings.h" -#include "control/controlproxy.h" #include "track/track.h" #define NUM_HOT_CUES 37 @@ -50,8 +51,8 @@ class HotcueControl : public QObject { void setCue(CuePointer pCue); void resetCue(); void setPosition(double position); - void setColor(PredefinedColorPointer newColor); - PredefinedColorPointer getColor() const; + void setColor(mixxx::RgbColor::optional_t newColor); + mixxx::RgbColor::optional_t getColor() const; // Used for caching the preview state of this hotcue control. inline bool isPreviewing() { @@ -76,7 +77,7 @@ class HotcueControl : public QObject { void slotHotcueActivatePreview(double v); void slotHotcueClear(double v); void slotHotcuePositionChanged(double newPosition); - void slotHotcueColorChanged(double newColorId); + void slotHotcueColorChanged(double newColor); signals: void hotcueSet(HotcueControl* pHotcue, double v); @@ -87,7 +88,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 hotcueColorChanged(HotcueControl* pHotcue, double newColor); void hotcuePlay(double v); private: @@ -193,6 +194,8 @@ class CueControl : public EngineControl { double getQuantizedCurrentPosition(); TrackAt getTrackAt() const; + UserSettingsPointer m_pConfig; + ColorPaletteSettings m_colorPaletteSettings; bool m_bPreviewing; ControlObject* m_pPlay; ControlObject* m_pStopButton; diff --git a/src/library/dao/cuedao.cpp b/src/library/dao/cuedao.cpp index 5a7d50d851ae..c8576109d7df 100644 --- a/src/library/dao/cuedao.cpp +++ b/src/library/dao/cuedao.cpp @@ -1,18 +1,18 @@ // cuedao.cpp // Created 10/26/2009 by RJ Ryan (rryan@mit.edu) +#include "library/dao/cuedao.h" + +#include #include #include -#include -#include "library/dao/cuedao.h" +#include "library/queryutil.h" #include "track/cue.h" #include "track/track.h" -#include "library/queryutil.h" #include "util/assert.h" +#include "util/color/rgbcolor.h" #include "util/performancetimer.h" -#include "util/color/color.h" -#include "util/color/predefinedcolor.h" namespace { @@ -37,7 +37,7 @@ inline QString labelFromQVariant(const QVariant& value) { } } -} +} // namespace int CueDAO::cueCount() { qDebug() << "CueDAO::cueCount" << QThread::currentThread() << m_database.connectionName(); @@ -78,9 +78,18 @@ 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 = labelFromQVariant(record.value(record.indexOf("label"))); - int iColorId = record.value(record.indexOf("color")).toInt(); - PredefinedColorPointer color = Color::kPredefinedColorsSet.predefinedColorFromId(iColorId); - CuePointer pCue(new Cue(id, trackId, (mixxx::CueType)type, position, length, hotcue, label, color)); + mixxx::RgbColor::optional_t color = mixxx::RgbColor::fromQVariant(record.value(record.indexOf("color"))); + VERIFY_OR_DEBUG_ASSERT(color) { + return CuePointer(); + } + CuePointer pCue(new Cue(id, + trackId, + static_cast(type), + position, + length, + hotcue, + label, + *color)); m_cues[id] = pCue; return pCue; } @@ -171,9 +180,8 @@ bool CueDAO::saveCue(Cue* cue) { query.bindValue(":position", cue->getPosition()); query.bindValue(":length", cue->getLength()); query.bindValue(":hotcue", cue->getHotCue()); - query.bindValue(":label", labelToQVariant(cue->getLabel())); - query.bindValue(":color", cue->getColor()->m_iId); + query.bindValue(":color", mixxx::RgbColor::toQVariant(cue->getColor())); if (query.exec()) { int id = query.lastInsertId().toInt(); @@ -201,7 +209,7 @@ bool CueDAO::saveCue(Cue* cue) { query.bindValue(":length", cue->getLength()); query.bindValue(":hotcue", cue->getHotCue()); query.bindValue(":label", labelToQVariant(cue->getLabel())); - query.bindValue(":color", cue->getColor()->m_iId); + query.bindValue(":color", mixxx::RgbColor::toQVariant(cue->getColor())); if (query.exec()) { cue->setDirty(false); diff --git a/src/library/dlgtrackinfo.cpp b/src/library/dlgtrackinfo.cpp index fdffce1de51c..35848013acd8 100644 --- a/src/library/dlgtrackinfo.cpp +++ b/src/library/dlgtrackinfo.cpp @@ -1,16 +1,19 @@ +#include "library/dlgtrackinfo.h" + +#include #include +#include #include -#include #include "library/coverartcache.h" #include "library/coverartutils.h" -#include "library/dlgtrackinfo.h" +#include "preferences/colorpalettesettings.h" #include "sources/soundsourceproxy.h" #include "track/beatfactory.h" #include "track/cue.h" #include "track/keyfactory.h" #include "track/keyutils.h" -#include "util/color/color.h" +#include "util/color/colorpalette.h" #include "util/compatibility.h" #include "util/desktophelper.h" #include "util/duration.h" @@ -21,11 +24,12 @@ const int kMinBpm = 30; // Maximum allowed interval between beats (calculated from kMinBpm). const mixxx::Duration kMaxInterval = mixxx::Duration::fromMillis(1000.0 * (60.0 / kMinBpm)); -DlgTrackInfo::DlgTrackInfo(QWidget* parent) - : QDialog(parent), - m_pTapFilter(new TapFilter(this, kFilterLength, kMaxInterval)), - m_dLastTapedBpm(-1.), - m_pWCoverArtLabel(new WCoverArtLabel(this)) { +DlgTrackInfo::DlgTrackInfo(UserSettingsPointer pConfig, QWidget* parent) + : QDialog(parent), + m_pTapFilter(new TapFilter(this, kFilterLength, kMaxInterval)), + m_dLastTapedBpm(-1.), + m_pWCoverArtLabel(new WCoverArtLabel(this)), + m_pConfig(pConfig) { init(); } @@ -36,7 +40,6 @@ DlgTrackInfo::~DlgTrackInfo() { void DlgTrackInfo::init() { setupUi(this); - cueTable->hideColumn(0); coverBox->insertWidget(1, m_pWCoverArtLabel); connect(btnNext, &QPushButton::clicked, this, &DlgTrackInfo::slotNext); @@ -89,14 +92,6 @@ void DlgTrackInfo::init() { this, &DlgTrackInfo::slotKeyTextChanged); - connect(btnCueActivate, - &QPushButton::clicked, - this, - &DlgTrackInfo::cueActivate); - connect(btnCueDelete, - &QPushButton::clicked, - this, - &DlgTrackInfo::cueDelete); connect(bpmTap, &QPushButton::pressed, m_pTapFilter.data(), @@ -162,39 +157,6 @@ void DlgTrackInfo::slotPrev() { emit previous(); } -void DlgTrackInfo::cueActivate() { - -} - -void DlgTrackInfo::cueDelete() { - QList selected = cueTable->selectedItems(); - QListIterator item_it(selected); - - QSet rowsToDelete; - while(item_it.hasNext()) { - QTableWidgetItem* item = item_it.next(); - rowsToDelete.insert(item->row()); - } - - // TODO: QList::fromSet(const QSet&) is deprecated and should be - // replaced with QList(set.begin(), set.end()). - // However, the proposed alternative has just been introduced in Qt - // 5.14. Until the minimum required Qt version of Mixx is increased, - // we need a version check here -#if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) - QList rowsList = QList(rowsToDelete.begin(), rowsToDelete.end()); -#else - QList rowsList = QList::fromSet(rowsToDelete); -#endif - std::sort(rowsList.begin(), rowsList.end()); - - QListIterator it(rowsList); - it.toBack(); - while (it.hasPrevious()) { - cueTable->removeRow(it.previous()); - } -} - void DlgTrackInfo::populateFields(const Track& track) { setWindowTitle(track.getArtist() % " - " % track.getTitle()); @@ -260,7 +222,6 @@ void DlgTrackInfo::loadTrack(TrackPointer pTrack) { m_pLoadedTrack = pTrack; populateFields(*m_pLoadedTrack); - populateCues(m_pLoadedTrack); m_pWCoverArtLabel->loadTrack(m_pLoadedTrack); // We already listen to changed() so we don't need to listen to individual @@ -313,129 +274,6 @@ void DlgTrackInfo::slotOpenInFileBrowser() { mixxx::DesktopHelper::openInFileBrowser(QStringList(m_pLoadedTrack->getLocation())); } -void DlgTrackInfo::populateCues(TrackPointer pTrack) { - int sampleRate = pTrack->getSampleRate(); - - QList listPoints; - const QList cuePoints = pTrack->getCuePoints(); - QListIterator it(cuePoints); - while (it.hasNext()) { - CuePointer pCue = it.next(); - mixxx::CueType type = pCue->getType(); - if (type == mixxx::CueType::HotCue || type == mixxx::CueType::MainCue || type == mixxx::CueType::Intro || type == mixxx::CueType::Outro) { - listPoints.push_back(pCue); - } - } - it = QListIterator(listPoints); - cueTable->setSortingEnabled(false); - int row = 0; - - while (it.hasNext()) { - CuePointer pCue(it.next()); - - QString rowStr = QString("%1").arg(row); - - // All hotcues are stored in Cue's as 0-indexed, but the GUI presents - // them to the user as 1-indexex. Add 1 here. rryan 9/2010 - int iHotcue = pCue->getHotCue() + 1; - QString hotcue = ""; - hotcue = QString("%1").arg(iHotcue); - double position = pCue->getPosition(); - if (position == -1) { - continue; - } - - double totalSeconds = position / sampleRate / 2.0; - - bool negative = false; - if (totalSeconds < 0) { - totalSeconds *= -1; - negative = true; - } - - int iTotalSeconds = static_cast(totalSeconds); - int fraction = 100 * (totalSeconds - iTotalSeconds); - int seconds = iTotalSeconds % 60; - int mins = iTotalSeconds / 60; - //int hours = mins / 60; //Not going to worry about this for now. :) - - //Construct a nicely formatted duration string now. - QString duration = QString("%1%2:%3.%4").arg( - negative ? QString("-") : QString(), - QString::number(mins), - QString("%1").arg(seconds, 2, 10, QChar('0')), - QString("%1").arg(fraction, 2, 10, QChar('0'))); - - QTableWidgetItem* durationItem = new QTableWidgetItem(duration); - // Make the duration read only - durationItem->setFlags(Qt::NoItemFlags); - - // Decode cue type to display text - QString cueType; - switch (pCue->getType()) { - case mixxx::CueType::Invalid: - cueType = "?"; - break; - case mixxx::CueType::HotCue: - cueType = "Hotcue"; - break; - case mixxx::CueType::MainCue: - cueType = "Main Cue"; - break; - case mixxx::CueType::Beat: - cueType = "Beat"; - break; - case mixxx::CueType::Loop: - cueType = "Loop"; - break; - case mixxx::CueType::Jump: - cueType = "Jump"; - break; - case mixxx::CueType::Intro: - cueType = "Intro"; - break; - case mixxx::CueType::Outro: - cueType = "Outro"; - break; - default: - break; - } - - QTableWidgetItem* typeItem = new QTableWidgetItem(cueType); - // Make the type read only - typeItem->setFlags(Qt::NoItemFlags); - - QComboBox* colorComboBox = new QComboBox(); - const QList predefinedColors = Color::kPredefinedColorsSet.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::kPredefinedColorsSet.noColor) { - QPixmap pixmap(80, 80); - pixmap.fill(defaultRgba); - QIcon icon(pixmap); - colorComboBox->setItemIcon(i, icon); - } - } - PredefinedColorPointer cueColor = pCue->getColor(); - colorComboBox->setCurrentIndex(Color::kPredefinedColorsSet.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, typeItem); - cueTable->setItem(row, 3, new QTableWidgetItem(hotcue)); - cueTable->setCellWidget(row, 4, colorComboBox); - cueTable->setItem(row, 5, new QTableWidgetItem(pCue->getLabel())); - row += 1; - } - cueTable->setSortingEnabled(true); - cueTable->horizontalHeader()->setStretchLastSection(true); - cueTable->horizontalHeader()->setSectionResizeMode(3, QHeaderView::ResizeToContents); -} - void DlgTrackInfo::saveTrack() { if (!m_pLoadedTrack) return; @@ -470,67 +308,6 @@ void DlgTrackInfo::saveTrack() { slotKeyTextChanged(); m_pLoadedTrack->setKeys(m_keysClone); - - QSet updatedRows; - for (int row = 0; row < cueTable->rowCount(); ++row) { - QTableWidgetItem* rowItem = cueTable->item(row, 0); - QTableWidgetItem* hotcueItem = cueTable->item(row, 3); - QWidget* colorWidget = cueTable->cellWidget(row, 4); - QTableWidgetItem* labelItem = cueTable->item(row, 5); - - 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())); - if (!pCue) { - continue; - } - updatedRows.insert(oldRow); - - QVariant vHotcue = hotcueItem->data(Qt::DisplayRole); - bool ok; - int iTableHotcue = vHotcue.toInt(&ok); - if (ok) { - // The GUI shows hotcues as 1-indexed, but they are actually - // 0-indexed, so subtract 1 - pCue->setHotCue(iTableHotcue - 1); - } else { - pCue->setHotCue(-1); - } - - if (pCue->getType() == mixxx::CueType::HotCue) { - auto colorComboBox = qobject_cast(colorWidget); - if (colorComboBox) { - PredefinedColorPointer color = Color::kPredefinedColorsSet.allColors.at(colorComboBox->currentIndex()); - pCue->setColor(color); - } - } - // do nothing for now. - - QString label = labelItem->data(Qt::DisplayRole).toString(); - pCue->setLabel(label); - } - - QMutableHashIterator it(m_cueMap); - // Everything that was not processed above was removed. - while (it.hasNext()) { - it.next(); - int oldRow = it.key(); - - // If cue's old row is not in updatedRows then it must have been - // deleted. - if (updatedRows.contains(oldRow)) { - continue; - } - CuePointer pCue(it.value()); - it.remove(); - qDebug() << "Deleting cue" << pCue->getId() << pCue->getHotCue(); - m_pLoadedTrack->removeCue(pCue); - } - m_pLoadedTrack->setCoverInfo(m_loadedCoverInfo); // Reconnect changed signals now. @@ -582,10 +359,6 @@ void DlgTrackInfo::clear() { txtKey->setText(""); txtReplayGain->setText(""); - m_cueMap.clear(); - cueTable->clearContents(); - cueTable->setRowCount(0); - m_loadedCoverInfo = CoverInfo(); m_pWCoverArtLabel->setCoverArt(m_loadedCoverInfo, QPixmap()); } diff --git a/src/library/dlgtrackinfo.h b/src/library/dlgtrackinfo.h index eb721c96c79f..98dc17c5279d 100644 --- a/src/library/dlgtrackinfo.h +++ b/src/library/dlgtrackinfo.h @@ -18,7 +18,7 @@ class DlgTrackInfo : public QDialog, public Ui::DlgTrackInfo { Q_OBJECT public: - DlgTrackInfo(QWidget* parent); + DlgTrackInfo(UserSettingsPointer pConfig, QWidget* parent); virtual ~DlgTrackInfo(); public slots: @@ -39,9 +39,6 @@ class DlgTrackInfo : public QDialog, public Ui::DlgTrackInfo { void cancel(); void trackUpdated(); - void cueActivate(); - void cueDelete(); - void slotBpmDouble(); void slotBpmHalve(); void slotBpmTwoThirds(); @@ -73,12 +70,10 @@ class DlgTrackInfo : public QDialog, public Ui::DlgTrackInfo { private: void populateFields(const Track& track); void reloadTrackBeats(const Track& track); - void populateCues(TrackPointer pTrack); void saveTrack(); void unloadTrack(bool save); void clear(); void init(); - QHash m_cueMap; TrackPointer m_pLoadedTrack; BeatsPointer m_pBeatsClone; Keys m_keysClone; @@ -89,6 +84,7 @@ class DlgTrackInfo : public QDialog, public Ui::DlgTrackInfo { CoverInfo m_loadedCoverInfo; WCoverArtLabel* m_pWCoverArtLabel; + UserSettingsPointer m_pConfig; }; #endif /* DLGTRACKINFO_H */ diff --git a/src/library/dlgtrackinfo.ui b/src/library/dlgtrackinfo.ui index 4435134c7a04..caf42ac2c25a 100644 --- a/src/library/dlgtrackinfo.ui +++ b/src/library/dlgtrackinfo.ui @@ -7,7 +7,7 @@ 0 0 700 - 588 + 595 @@ -41,7 +41,7 @@ - 0 + 2 @@ -376,7 +376,16 @@ QLayout::SetDefaultConstraint - + + 0 + + + 0 + + + 0 + + 0 @@ -798,90 +807,6 @@ Often results in higher quality beatgrids, but will not do well on tracks that h - - - Cuepoints - - - - - - - Cue Id - - - - - Position - - - - - Type - - - - - Hotcue - - - - - Color - - - - - Label - - - - - - - - - - - 125 - 0 - - - - Delete Cue - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - - 125 - 0 - - - - Activate Cue - - - - - - - @@ -1030,9 +955,6 @@ Often results in higher quality beatgrids, but will not do well on tracks that h bpmThreeFourth bpmThreeHalves bpmFourThirds - cueTable - btnCueDelete - btnCueActivate diff --git a/src/preferences/colorpalettesettings.cpp b/src/preferences/colorpalettesettings.cpp new file mode 100644 index 000000000000..63976e4cbdb1 --- /dev/null +++ b/src/preferences/colorpalettesettings.cpp @@ -0,0 +1,112 @@ +#include "preferences/colorpalettesettings.h" + +namespace { +const mixxx::RgbColor kColorBlack(0x000000); +const QString kColorPaletteConfigGroup = QStringLiteral("[Config]"); +const QString kColorPaletteGroupStart = QStringLiteral("[ColorPalette "); +const QString kColorPaletteGroupEnd = QStringLiteral("]"); +const QRegExp kColorPaletteGroupNameRegex(QStringLiteral("^\\[ColorPalette (.+)\\]$")); +const ConfigKey kHotcueColorPaletteConfigKey(kColorPaletteConfigGroup, QStringLiteral("HotcueColorPalette")); +const ConfigKey kTrackColorPaletteConfigKey(kColorPaletteConfigGroup, QStringLiteral("TrackColorPalette")); + +int numberOfDecimalDigits(int number) { + int numDigits = 1; + while (number /= 10) { + numDigits++; + } + return numDigits; +} + +ConfigKey keyForIndex(const QString& group, int index, int numDigits) { + return ConfigKey(group, QString::number(index).rightJustified(numDigits, '0')); +} + +} // anonymous namespace + +ColorPalette ColorPaletteSettings::getColorPalette( + const QString& name, const ColorPalette& defaultPalette) const { + QList colorList; + + const QString group = kColorPaletteGroupStart + name + kColorPaletteGroupEnd; + for (const ConfigKey& key : m_pConfig->getKeysWithGroup(group)) { + mixxx::RgbColor color = mixxx::RgbColor(m_pConfig->getValue(key, kColorBlack)); + colorList.append(color); + } + + // If no palette is defined in the settings, we use the default one. + if (colorList.isEmpty()) { + return defaultPalette; + } + + return ColorPalette(name, colorList); +} + +void ColorPaletteSettings::setColorPalette(const QString& name, const ColorPalette& colorPalette) { + VERIFY_OR_DEBUG_ASSERT(!name.isEmpty()) { + qWarning() << "Palette name must not be empty!"; + return; + } + + removePalette(name); + const QString group = kColorPaletteGroupStart + name + kColorPaletteGroupEnd; + + int numDigits = numberOfDecimalDigits(colorPalette.size() - 1); + for (int index = 0; index < colorPalette.size(); ++index) { + mixxx::RgbColor color = colorPalette.at(index); + m_pConfig->setValue(keyForIndex(group, index, numDigits), color); + } +} + +void ColorPaletteSettings::removePalette(const QString& name) { + const QString group = kColorPaletteGroupStart + name + kColorPaletteGroupEnd; + for (const ConfigKey& key : m_pConfig->getKeysWithGroup(group)) { + m_pConfig->remove(key); + } +} + +ColorPalette ColorPaletteSettings::getHotcueColorPalette() const { + QString name = m_pConfig->getValueString(kHotcueColorPaletteConfigKey); + if (name.isEmpty()) { + return ColorPalette::mixxxHotcuePalette; + } + return getColorPalette(name, ColorPalette::mixxxHotcuePalette); +} + +void ColorPaletteSettings::setHotcueColorPalette(const ColorPalette& colorPalette) { + QString name = colorPalette.getName(); + VERIFY_OR_DEBUG_ASSERT(!name.isEmpty()) { + qWarning() << "Palette name must not be empty!"; + return; + } + m_pConfig->setValue(kHotcueColorPaletteConfigKey, name); + setColorPalette(name, colorPalette); +} + +ColorPalette ColorPaletteSettings::getTrackColorPalette() const { + QString name = m_pConfig->getValueString(kTrackColorPaletteConfigKey); + if (name.isEmpty()) { + return ColorPalette::mixxxHotcuePalette; + } + return getColorPalette(name, ColorPalette::mixxxHotcuePalette); +} + +void ColorPaletteSettings::setTrackColorPalette(const ColorPalette& colorPalette) { + QString name = colorPalette.getName(); + VERIFY_OR_DEBUG_ASSERT(!name.isEmpty()) { + qWarning() << "Palette name must not be empty!"; + return; + } + m_pConfig->setValue(kTrackColorPaletteConfigKey, name); + setColorPalette(name, colorPalette); +} + +QSet ColorPaletteSettings::getColorPaletteNames() const { + QSet names; + for (const QString& group : m_pConfig->getGroups()) { + int pos = kColorPaletteGroupNameRegex.indexIn(group); + if (pos > -1) { + names.insert(kColorPaletteGroupNameRegex.cap(1)); + } + } + return names; +} diff --git a/src/preferences/colorpalettesettings.h b/src/preferences/colorpalettesettings.h new file mode 100644 index 000000000000..39ce03e49e18 --- /dev/null +++ b/src/preferences/colorpalettesettings.h @@ -0,0 +1,25 @@ +#pragma once + +#include "preferences/usersettings.h" +#include "util/color/colorpalette.h" + +class ColorPaletteSettings { + public: + explicit ColorPaletteSettings(UserSettingsPointer pConfig) + : m_pConfig(pConfig) { + } + + ColorPalette getHotcueColorPalette() const; + void setHotcueColorPalette(const ColorPalette& colorPalette); + + ColorPalette getTrackColorPalette() const; + void setTrackColorPalette(const ColorPalette& colorPalette); + + ColorPalette getColorPalette(const QString& name, const ColorPalette& defaultPalette) const; + void setColorPalette(const QString& name, const ColorPalette& colorPalette); + void removePalette(const QString& name); + QSet getColorPaletteNames() const; + + private: + UserSettingsPointer m_pConfig; +}; diff --git a/src/preferences/configobject.cpp b/src/preferences/configobject.cpp index 6eecb2bb991f..d49617887720 100644 --- a/src/preferences/configobject.cpp +++ b/src/preferences/configobject.cpp @@ -1,14 +1,15 @@ #include "preferences/configobject.h" -#include -#include #include #include +#include +#include #include -#include "widget/wwidget.h" #include "util/cmdlineargs.h" +#include "util/color/rgbcolor.h" #include "util/xml.h" +#include "widget/wwidget.h" // TODO(rryan): Move to a utility file. namespace { @@ -215,6 +216,28 @@ template void ConfigObject::save() { } } +template +QSet ConfigObject::getGroups() { + QWriteLocker lock(&m_valuesLock); + QSet groups; + for (const ConfigKey& key : m_values.uniqueKeys()) { + groups.insert(key.group); + } + return groups; +} + +template +QList ConfigObject::getKeysWithGroup(QString group) const { + QWriteLocker lock(&m_valuesLock); + QList filteredList; + for (const ConfigKey& key : m_values.uniqueKeys()) { + if (key.group == group) { + filteredList.append(key); + } + } + return filteredList; +} + template ConfigObject::ConfigObject(const QDomNode& node) { if (!node.isNull() && node.isElement()) { QDomNode ctrl = node.firstChild(); @@ -270,6 +293,24 @@ void ConfigObject::setValue( set(key, ConfigValue(QString::number(value))); } +template<> +template<> +void ConfigObject::setValue( + const ConfigKey& key, const mixxx::RgbColor::optional_t& value) { + if (!value) { + remove(key); + return; + } + set(key, ConfigValue(mixxx::RgbColor::toQString(value))); +} + +template<> +template<> +void ConfigObject::setValue( + const ConfigKey& key, const mixxx::RgbColor& value) { + set(key, ConfigValue(mixxx::RgbColor::toQString(value))); +} + template <> template <> bool ConfigObject::getValue( const ConfigKey& key, const bool& default_value) const { @@ -306,6 +347,40 @@ double ConfigObject::getValue( return ok ? result : default_value; } +template<> +template<> +mixxx::RgbColor::optional_t ConfigObject::getValue( + const ConfigKey& key, const mixxx::RgbColor::optional_t& default_value) const { + const ConfigValue value = get(key); + if (value.isNull()) { + return default_value; + } + return mixxx::RgbColor::fromQString(value.value, default_value); +} + +template<> +template<> +mixxx::RgbColor::optional_t ConfigObject::getValue(const ConfigKey& key) const { + return getValue(key, mixxx::RgbColor::optional_t(std::nullopt)); +} + +template<> +template<> +mixxx::RgbColor ConfigObject::getValue( + const ConfigKey& key, const mixxx::RgbColor& default_value) const { + const mixxx::RgbColor::optional_t value = getValue(key, mixxx::RgbColor::optional_t(std::nullopt)); + if (!value) { + return default_value; + } + return *value; +} + +template<> +template<> +mixxx::RgbColor ConfigObject::getValue(const ConfigKey& key) const { + return getValue(key, mixxx::RgbColor(0)); +} + // For string literal default template <> QString ConfigObject::getValue( diff --git a/src/preferences/configobject.h b/src/preferences/configobject.h index 3e4ba6aa20e4..93d1b2425231 100644 --- a/src/preferences/configobject.h +++ b/src/preferences/configobject.h @@ -187,6 +187,9 @@ template class ConfigObject { return m_settingsPath; } + QSet getGroups(); + QList getKeysWithGroup(QString group) const; + protected: // We use QMap because we want a sorted list in mixxx.cfg QMap m_values; diff --git a/src/skin/skincontext.h b/src/skin/skincontext.h index 10bba69feffc..8898b337f35b 100644 --- a/src/skin/skincontext.h +++ b/src/skin/skincontext.h @@ -15,7 +15,6 @@ #include "preferences/usersettings.h" #include "skin/pixmapsource.h" #include "util/color/color.h" -#include "util/color/predefinedcolorsrepresentation.h" #include "widget/wsingletoncontainer.h" #include "widget/wpixmapstore.h" @@ -258,20 +257,6 @@ class SkinContext { return m_scaleFactor; } - PredefinedColorsRepresentation getCueColorRepresentation(const QDomNode& node, QColor defaultColor) const { - PredefinedColorsRepresentation colorRepresentation = Color::kPredefinedColorsSet.defaultRepresentation(); - for (PredefinedColorPointer color : Color::kPredefinedColorsSet.allColors) { - QString sColorName(color->m_sName); - QColor skinRgba = selectColor(node, "Cue" + sColorName); - if (skinRgba.isValid()) { - PredefinedColorPointer originalColor = Color::kPredefinedColorsSet.predefinedColorFromName(sColorName); - colorRepresentation.setCustomRgba(originalColor, skinRgba); - } - } - colorRepresentation.setCustomRgba(Color::kPredefinedColorsSet.noColor, defaultColor); - return colorRepresentation; - } - private: PixmapSource getPixmapSourceInner(const QString& filename) const; diff --git a/src/test/colorconfig_test.cpp b/src/test/colorconfig_test.cpp new file mode 100644 index 000000000000..58a17f706a29 --- /dev/null +++ b/src/test/colorconfig_test.cpp @@ -0,0 +1,105 @@ +#include + +#include "preferences/colorpalettesettings.h" +#include "test/mixxxtest.h" +#include "util/color/colorpalette.h" +#include "util/color/rgbcolor.h" + +class ColorConfigTest : public MixxxTest {}; + +TEST_F(ColorConfigTest, SavingColor) { + ConfigKey key("[Color]", "color"); + mixxx::RgbColor originalColor(0xFF9900); + config()->setValue(key, originalColor); + saveAndReloadConfig(); + mixxx::RgbColor colorFromConfig = config()->getValue(key, mixxx::RgbColor(0)); + ASSERT_EQ(originalColor, colorFromConfig); +} + +TEST_F(ColorConfigTest, GetDefaultColorWhenNoStoredColor) { + ConfigKey key("[Color]", "color"); + mixxx::RgbColor defaultColor(0x66FF99); + mixxx::RgbColor colorFromConfig = config()->getValue(key, defaultColor); + ASSERT_EQ(defaultColor, colorFromConfig); +} + +TEST_F(ColorConfigTest, SaveColorPalette) { + ColorPaletteSettings colorPaletteSettings(config()); + ColorPalette originalColorPalette( + "SaveColorPaletteTest", QList{ + mixxx::RgbColor(0x66FF99), + mixxx::RgbColor(0xFF9900), + mixxx::RgbColor(0x000000), + mixxx::RgbColor(0xFFFFFF), + }); + colorPaletteSettings.setHotcueColorPalette(originalColorPalette); + saveAndReloadConfig(); + ColorPalette colorPaletteFromConfig = + colorPaletteSettings.getHotcueColorPalette(); + ASSERT_EQ(originalColorPalette, colorPaletteFromConfig); +} + +TEST_F(ColorConfigTest, ReplaceColorPalette) { + ColorPaletteSettings colorPaletteSettings(config()); + ColorPalette colorPalette1( + "ReplaceColorPaletteTest", QList{ + mixxx::RgbColor(0x66FF99), + mixxx::RgbColor(0xFF9900), + mixxx::RgbColor(0x000000), + mixxx::RgbColor(0xFFFFFF), + }); + ColorPalette colorPalette2( + "ReplaceColorPaletteTest", QList{ + mixxx::RgbColor(0x0000FF), + mixxx::RgbColor(0xFF0000), + }); + colorPaletteSettings.setHotcueColorPalette(colorPalette1); + saveAndReloadConfig(); + colorPaletteSettings.setHotcueColorPalette(colorPalette2); + saveAndReloadConfig(); + ColorPalette colorPaletteFromConfig = + colorPaletteSettings.getHotcueColorPalette(); + ASSERT_EQ(colorPalette2, colorPaletteFromConfig); +} + +TEST_F(ColorConfigTest, LoadSavePalettes) { + const QString kName1 = QStringLiteral("Custom Palette No. 1"); + const QString kName2 = QStringLiteral("My Custom Palette 2"); + const QString kName3 = QStringLiteral("I'm blue, da ba dee"); + ColorPaletteSettings colorPaletteSettings(config()); + ColorPalette colorPalette1( + kName1, QList{ + mixxx::RgbColor(0x66FF99), + mixxx::RgbColor(0xFF9900), + mixxx::RgbColor(0x000000), + mixxx::RgbColor(0xFFFFFF), + }); + colorPaletteSettings.setColorPalette(colorPalette1.getName(), colorPalette1); + ColorPalette colorPalette2( + kName2, QList{ + mixxx::RgbColor(0x0000FF), + mixxx::RgbColor(0xFF0000), + }); + colorPaletteSettings.setColorPalette(colorPalette2.getName(), colorPalette2); + ColorPalette colorPalette3( + kName3, QList{ + mixxx::RgbColor(0x0000FF), + mixxx::RgbColor(0x123456), + mixxx::RgbColor(0x000080), + }); + colorPaletteSettings.setColorPalette(colorPalette3.getName(), colorPalette3); + saveAndReloadConfig(); + QSet expectedNames{ + kName1, + kName2, + kName3, + }; + ASSERT_EQ(expectedNames, colorPaletteSettings.getColorPaletteNames()); +} + +TEST_F(ColorConfigTest, DefaultColorPalette) { + ColorPaletteSettings colorPaletteSettings(config()); + ColorPalette colorPaletteFromConfig = + colorPaletteSettings.getHotcueColorPalette(); + ASSERT_EQ(ColorPalette::mixxxHotcuePalette, colorPaletteFromConfig); +} diff --git a/src/test/colormapperjsproxy_test.cpp b/src/test/colormapperjsproxy_test.cpp new file mode 100644 index 000000000000..4f1a291aadf9 --- /dev/null +++ b/src/test/colormapperjsproxy_test.cpp @@ -0,0 +1,128 @@ +#include "controllers/colormapperjsproxy.h" + +#include + +#include "test/mixxxtest.h" + +namespace { + +QScriptEngine* createScriptEngine() { + QScriptEngine* pEngine = new QScriptEngine(); + QScriptValue constructor = pEngine->newFunction(ColorMapperJSProxyConstructor); + QScriptValue metaObject = pEngine->newQMetaObject(&ColorMapperJSProxy::staticMetaObject, constructor); + pEngine->globalObject().setProperty("ColorMapper", metaObject); + return pEngine; +} + +} // namespace + +class ColorMapperJSProxyTest : public MixxxTest {}; + +TEST_F(ColorMapperJSProxyTest, Instantiation) { + QScriptEngine* pEngine = createScriptEngine(); + + // Valid instantiation + pEngine->evaluate( + R"JavaScript( + var mapper = new ColorMapper({ + '#FF0000': 1, + '#00FF00': 2, + '#0000FF': 3, + }); + )JavaScript"); + EXPECT_FALSE(pEngine->hasUncaughtException()); + pEngine->clearExceptions(); + + // Invalid instantiation: no arguments + pEngine->evaluate("var mapper = new ColorMapper();"); + EXPECT_TRUE(pEngine->hasUncaughtException()); + pEngine->clearExceptions(); + + // Invalid instantiation: invalid argument + pEngine->evaluate("var mapper = new ColorMapper('hello');"); + EXPECT_TRUE(pEngine->hasUncaughtException()); + pEngine->clearExceptions(); + + // Invalid instantiation: argument is an empty object + pEngine->evaluate("var mapper = new ColorMapper({});"); + EXPECT_TRUE(pEngine->hasUncaughtException()); + pEngine->clearExceptions(); + + // Invalid instantiation: argument is an empty object + pEngine->evaluate( + R"JavaScript( + var mapper = new ColorMapper({ + 'not a color': 1 + }); + )JavaScript"); + EXPECT_TRUE(pEngine->hasUncaughtException()); + pEngine->clearExceptions(); +} + +TEST_F(ColorMapperJSProxyTest, GetNearestColor) { + QScriptEngine* pEngine = createScriptEngine(); + pEngine->evaluate( + R"JavaScript( + var mapper = new ColorMapper({ + '#C50A08': 1, + '#32BE44': 2, + '#42D4F4': 3, + '#F8D200': 4, + '#0044FF': 5, + '#AF00CC': 6, + '#FCA6D7': 7, + '#F2F2FF': 8, + }); + /* white */ + var color1 = mapper.getNearestColor(0xFFFFFF); + if (color1.red != 0xF2 || color1.green != 0xF2 || color1.blue != 0xFF) { + throw Error(); + }; + /* white */ + var color2 = mapper.getNearestColor(0xDCDCDC); + if (color2.red != 0xF2 || color2.green != 0xF2 || color2.blue != 0xFF) { + throw Error(); + }; + /* red */ + var color3 = mapper.getNearestColor(0xFF0000); + if (color3.red != 0xC5 || color3.green != 0x0A || color3.blue != 0x08) { + throw Error(); + }; + /* yellow */ + var color4 = mapper.getNearestColor(0x22CC22); + if (color4.red != 0x32 || color4.green != 0xBE || color4.blue != 0x44) { + throw Error(); + } + )JavaScript"); + EXPECT_FALSE(pEngine->hasUncaughtException()); +} + +TEST_F(ColorMapperJSProxyTest, GetNearestValue) { + QScriptEngine* pEngine = createScriptEngine(); + pEngine->evaluate( + R"JavaScript( + var mapper = new ColorMapper({ + '#C50A08': 1, + '#32BE44': 2, + '#42D4F4': 3, + '#F8D200': 4, + '#0044FF': 5, + '#AF00CC': 6, + '#FCA6D7': 7, + '#F2F2FF': 8, + }); + /* red */ + if (mapper.getValueForNearestColor(0xFF0000) != 1) { + throw Error(); + }; + /* blue */ + if (mapper.getValueForNearestColor(0x0000AA) != 5) { + throw Error(); + }; + /* white */ + if (mapper.getValueForNearestColor(0xFFFFFF) != 8) { + throw Error(); + }; + )JavaScript"); + EXPECT_FALSE(pEngine->hasUncaughtException()); +} diff --git a/src/test/colorpalette_test.cpp b/src/test/colorpalette_test.cpp new file mode 100644 index 000000000000..77b13d731de3 --- /dev/null +++ b/src/test/colorpalette_test.cpp @@ -0,0 +1,30 @@ +#include "util/color/colorpalette.h" + +#include + +#include "test/mixxxtest.h" + +class ColorPaletteTest : public MixxxTest {}; + +TEST_F(ColorPaletteTest, NextColor) { + const ColorPalette palette = ColorPalette::mixxxHotcuePalette; + ASSERT_TRUE(palette.size() >= 1); + ASSERT_EQ(palette.nextColor(palette.at(0)), palette.at(1)); + ASSERT_EQ(palette.nextColor(palette.at(palette.size() - 1)), palette.at(0)); +} + +TEST_F(ColorPaletteTest, PreviousColor) { + const ColorPalette palette = ColorPalette::mixxxHotcuePalette; + ASSERT_TRUE(palette.size() >= 1); + ASSERT_EQ(palette.previousColor(palette.at(1)), palette.at(0)); + ASSERT_EQ(palette.previousColor(palette.at(0)), palette.at(palette.size() - 1)); +} + +TEST_F(ColorPaletteTest, NextAndPreviousColorRoundtrip) { + const ColorPalette palette = ColorPalette::mixxxHotcuePalette; + ASSERT_TRUE(palette.size() >= 1); + ASSERT_EQ(palette.nextColor(palette.previousColor(palette.at(0))), palette.at(0)); + ASSERT_EQ(palette.nextColor(palette.previousColor(palette.at(palette.size() - 1))), palette.at(palette.size() - 1)); + ASSERT_EQ(palette.previousColor(palette.nextColor(palette.at(0))), palette.at(0)); + ASSERT_EQ(palette.previousColor(palette.nextColor(palette.at(palette.size() - 1))), palette.at(palette.size() - 1)); +} diff --git a/src/test/controller_preset_validation_test.cpp b/src/test/controller_preset_validation_test.cpp index d49ee3e50bac..a1a65f4015ff 100644 --- a/src/test/controller_preset_validation_test.cpp +++ b/src/test/controller_preset_validation_test.cpp @@ -15,7 +15,7 @@ class FakeController : public Controller { public: - FakeController(); + explicit FakeController(UserSettingsPointer pConfig); ~FakeController() override; QString presetExtension() override { @@ -112,9 +112,8 @@ class FakeController : public Controller { HidControllerPreset m_hidPreset; }; -FakeController::FakeController() - : m_bMidiPreset(false), - m_bHidPreset(false) { +FakeController::FakeController(UserSettingsPointer pConfig) + : Controller(pConfig), m_bMidiPreset(false), m_bHidPreset(false) { } FakeController::~FakeController() { @@ -135,7 +134,7 @@ class ControllerPresetValidationTest : public MixxxTest { return false; } - FakeController controller; + FakeController controller(config()); controller.setDeviceName("Test Controller"); controller.startEngine(); controller.setPreset(*pPreset); diff --git a/src/test/controllerengine_test.cpp b/src/test/controllerengine_test.cpp index d84f736a6ff8..4b16d53b8e7a 100644 --- a/src/test/controllerengine_test.cpp +++ b/src/test/controllerengine_test.cpp @@ -1,13 +1,14 @@ -#include #include +#include #include "control/controlobject.h" #include "control/controlpotmeter.h" -#include "preferences/usersettings.h" -#include "controllers/controllerengine.h" #include "controllers/controllerdebug.h" +#include "controllers/controllerengine.h" #include "controllers/softtakeover.h" +#include "preferences/usersettings.h" #include "test/mixxxtest.h" +#include "util/color/colorpalette.h" #include "util/memory.h" #include "util/time.h" @@ -17,7 +18,7 @@ class ControllerEngineTest : public MixxxTest { mixxx::Time::setTestMode(true); mixxx::Time::setTestElapsedTime(mixxx::Duration::fromMillis(10)); QThread::currentThread()->setObjectName("Main"); - cEngine = new ControllerEngine(nullptr); + cEngine = new ControllerEngine(nullptr, config()); pScriptEngine = cEngine->m_pEngine; ControllerDebug::enable(); cEngine->setPopups(false); @@ -617,25 +618,3 @@ 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::kPredefinedColorsSet.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/cue_test.cpp b/src/test/cue_test.cpp index 0518c15eaa3b..8154d6104d9d 100644 --- a/src/test/cue_test.cpp +++ b/src/test/cue_test.cpp @@ -2,17 +2,17 @@ #include -#include "test/mixxxtest.h" - #include "engine/engine.h" +#include "test/mixxxtest.h" #include "util/color/color.h" namespace mixxx { TEST(CueTest, DefaultCueToCueInfoTest) { const Cue cueObject; - const auto cueInfo = cueObject.getCueInfo( + auto cueInfo = cueObject.getCueInfo( AudioSignal::SampleRate(44100)); + cueInfo.setColor(std::nullopt); EXPECT_EQ(CueInfo(), cueInfo); } @@ -21,8 +21,9 @@ TEST(CueTest, DefaultCueInfoToCueRoundtrip) { const Cue cueObject( cueInfo1, AudioSignal::SampleRate(44100)); - const auto cueInfo2 = cueObject.getCueInfo( + auto cueInfo2 = cueObject.getCueInfo( AudioSignal::SampleRate(44100)); + cueInfo2.setColor(std::nullopt); EXPECT_EQ(cueInfo1, cueInfo2); } @@ -30,16 +31,13 @@ TEST(CueTest, ConvertCueInfoToCueRoundtrip) { // Due to rounding errors this test may fail if the // cue position/sample conversions don't always result // in integer numbers. - const auto predefinedColor = - Color::kPredefinedColorsSet.predefinedColorFromRgbColor( - RgbColor::optional(0xabcdef))->m_defaultRgba; const auto cueInfo1 = CueInfo( CueType::HotCue, std::make_optional(1.0 * 44100 * mixxx::kEngineChannelCount), std::make_optional(2.0 * 44100 * mixxx::kEngineChannelCount), std::make_optional(3), QStringLiteral("label"), - RgbColor::fromQColor(predefinedColor)); + RgbColor::optional(0xABCDEF)); const Cue cueObject( cueInfo1, AudioSignal::SampleRate(44100)); diff --git a/src/test/midicontrollertest.cpp b/src/test/midicontrollertest.cpp index 696483c05fd1..ac520908638e 100644 --- a/src/test/midicontrollertest.cpp +++ b/src/test/midicontrollertest.cpp @@ -12,7 +12,9 @@ class MockMidiController : public MidiController { public: - MockMidiController() { } + explicit MockMidiController(UserSettingsPointer pConfig) + : MidiController(pConfig) { + } ~MockMidiController() override { } MOCK_METHOD0(open, int()); @@ -27,7 +29,7 @@ class MockMidiController : public MidiController { class MidiControllerTest : public MixxxTest { protected: void SetUp() override { - m_pController.reset(new MockMidiController()); + m_pController.reset(new MockMidiController(config())); } void addMapping(MidiInputMapping mapping) { diff --git a/src/test/portmidicontroller_test.cpp b/src/test/portmidicontroller_test.cpp index ad007b790958..9d8fccdaa697 100644 --- a/src/test/portmidicontroller_test.cpp +++ b/src/test/portmidicontroller_test.cpp @@ -16,11 +16,15 @@ using ::testing::SetArrayArgument; class MockPortMidiController : public PortMidiController { public: MockPortMidiController(const PmDeviceInfo* inputDeviceInfo, - const PmDeviceInfo* outputDeviceInfo, - int inputDeviceIndex, - int outputDeviceIndex) : PortMidiController( - inputDeviceInfo, outputDeviceInfo, - inputDeviceIndex, outputDeviceIndex) { + const PmDeviceInfo* outputDeviceInfo, + int inputDeviceIndex, + int outputDeviceIndex, + UserSettingsPointer pConfig) + : PortMidiController(inputDeviceInfo, + outputDeviceInfo, + inputDeviceIndex, + outputDeviceIndex, + pConfig) { } ~MockPortMidiController() override { } @@ -71,9 +75,8 @@ class PortMidiControllerTest : public MixxxTest { m_outputDeviceInfo.output = 1; m_outputDeviceInfo.opened = 0; - m_pController.reset(new MockPortMidiController(&m_inputDeviceInfo, - &m_outputDeviceInfo, - 0, 0)); + m_pController.reset(new MockPortMidiController( + &m_inputDeviceInfo, &m_outputDeviceInfo, 0, 0, config())); m_pController->setPortMidiInputDevice(m_mockInput); m_pController->setPortMidiOutputDevice(m_mockOutput); } diff --git a/src/track/cue.cpp b/src/track/cue.cpp index c5dc66f8622a..a923ab7a9416 100644 --- a/src/track/cue.cpp +++ b/src/track/cue.cpp @@ -9,6 +9,7 @@ #include "engine/engine.h" #include "util/assert.h" #include "util/color/color.h" +#include "util/color/colorpalette.h" namespace { @@ -37,7 +38,6 @@ inline double positionMillisToSamples( // Try to avoid rounding errors return (*positionMillis * sampleRate * mixxx::kEngineChannelCount) / 1000; } - } //static @@ -54,7 +54,7 @@ Cue::Cue() m_sampleStartPosition(Cue::kNoPosition), m_sampleEndPosition(Cue::kNoPosition), m_iHotCue(Cue::kNoHotCue), - m_color(Color::kPredefinedColorsSet.noColor) { + m_color(ColorPalette::kDefaultCueColor) { } Cue::Cue( @@ -65,7 +65,7 @@ Cue::Cue( double length, int hotCue, QString label, - PredefinedColorPointer color) + mixxx::RgbColor color) : m_bDirty(false), m_iId(id), m_trackId(trackId), @@ -101,7 +101,7 @@ Cue::Cue( sampleRate)), m_iHotCue(cueInfo.getHotCueNumber() ? *cueInfo.getHotCueNumber() : kNoHotCue), m_label(cueInfo.getLabel()), - m_color(Color::kPredefinedColorsSet.predefinedColorFromRgbColor(cueInfo.getColor())) { + m_color(cueInfo.getColor().value_or(ColorPalette::kDefaultCueColor)) { } mixxx::CueInfo Cue::getCueInfo( @@ -113,7 +113,7 @@ mixxx::CueInfo Cue::getCueInfo( positionSamplesToMillis(m_sampleEndPosition, sampleRate), m_iHotCue == kNoHotCue ? std::nullopt : std::make_optional(m_iHotCue), m_label, - m_color ? mixxx::RgbColor::fromQColor(m_color->m_defaultRgba) : std::nullopt); + m_color); } int Cue::getId() const { @@ -214,12 +214,12 @@ void Cue::setLabel(const QString label) { emit updated(); } -PredefinedColorPointer Cue::getColor() const { +mixxx::RgbColor Cue::getColor() const { QMutexLocker lock(&m_mutex); return m_color; } -void Cue::setColor(const PredefinedColorPointer color) { +void Cue::setColor(mixxx::RgbColor color) { QMutexLocker lock(&m_mutex); m_color = color; m_bDirty = true; diff --git a/src/track/cue.h b/src/track/cue.h index 0ce931eebf06..14f605c28f73 100644 --- a/src/track/cue.h +++ b/src/track/cue.h @@ -7,7 +7,7 @@ #include "track/cueinfo.h" #include "track/trackid.h" #include "util/audiosignal.h" -#include "util/color/predefinedcolor.h" +#include "util/color/rgbcolor.h" #include "util/memory.h" class CuePosition; @@ -50,8 +50,8 @@ class Cue : public QObject { void setLabel( QString label = QString()); - PredefinedColorPointer getColor() const; - void setColor(PredefinedColorPointer color); + mixxx::RgbColor getColor() const; + void setColor(mixxx::RgbColor color); double getEndPosition() const; @@ -70,7 +70,7 @@ class Cue : public QObject { double length, int hotCue, QString label, - PredefinedColorPointer color); + mixxx::RgbColor color); void setDirty(bool dirty); @@ -87,7 +87,7 @@ class Cue : public QObject { double m_sampleEndPosition; int m_iHotCue; QString m_label; - PredefinedColorPointer m_color; + mixxx::RgbColor m_color; friend class Track; friend class CueDAO; diff --git a/src/util/color/color.cpp b/src/util/color/color.cpp index 4627fdf3d9a5..92116acb7482 100644 --- a/src/util/color/color.cpp +++ b/src/util/color/color.cpp @@ -4,8 +4,6 @@ namespace Color { -const PredefinedColorsSet kPredefinedColorsSet = 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 diff --git a/src/util/color/color.h b/src/util/color/color.h index 13401e05254e..e5196a409e79 100644 --- a/src/util/color/color.h +++ b/src/util/color/color.h @@ -1,11 +1,9 @@ #pragma once -#include "util/color/predefinedcolorsset.h" +#include namespace Color { -extern const PredefinedColorsSet kPredefinedColorsSet; - int brightness(int red, int green, int blue); inline int brightness(const QColor& color) { diff --git a/src/util/color/colorpalette.cpp b/src/util/color/colorpalette.cpp new file mode 100644 index 000000000000..a606f8e73b4e --- /dev/null +++ b/src/util/color/colorpalette.cpp @@ -0,0 +1,55 @@ +#include "colorpalette.h" + +namespace { + +constexpr mixxx::RgbColor kColorMixxxRed(0xC50A08); +constexpr mixxx::RgbColor kColorMixxxYellow(0x32BE44); +constexpr mixxx::RgbColor kColorMixxxGreen(0x42D4F4); +constexpr mixxx::RgbColor kColorMixxxCeleste(0xF8D200); +constexpr mixxx::RgbColor kColorMixxxBlue(0x0044FF); +constexpr mixxx::RgbColor kColorMixxxPurple(0xAF00CC); +constexpr mixxx::RgbColor kColorMixxxPink(0xFCA6D7); +constexpr mixxx::RgbColor kColorMixxxWhite(0xF2F2FF); + +// Replaces "no color" values and is used for new cues if auto_hotcue_colors is +// disabled +constexpr mixxx::RgbColor kSchemaMigrationReplacementColor(0xFF8C00); + +} // anonymous namespace + +const ColorPalette ColorPalette::mixxxHotcuePalette = + ColorPalette( + QStringLiteral("Mixxx Hotcue Colors"), + QList{ + kColorMixxxRed, + kColorMixxxYellow, + kColorMixxxGreen, + kColorMixxxCeleste, + kColorMixxxBlue, + kColorMixxxPurple, + kColorMixxxPink, + kColorMixxxWhite, + }); + +const mixxx::RgbColor ColorPalette::kDefaultCueColor = kSchemaMigrationReplacementColor; + +mixxx::RgbColor ColorPalette::nextColor(mixxx::RgbColor color) const { + // Return first color if color not in palette ((-1 + 1) % size) + return at((indexOf(color) + 1) % size()); +} + +mixxx::RgbColor ColorPalette::previousColor(mixxx::RgbColor color) const { + int iIndex = indexOf(color); + if (iIndex < 0) { + // Return last color if color not in palette + iIndex = size() - 1; + } else { + iIndex = (iIndex + size() - 1) % size(); + } + return at(iIndex); +} + +mixxx::RgbColor ColorPalette::colorForHotcueIndex(unsigned int index) const { + // For hotcue n, get nth color from palette + return at(index % size()); +} diff --git a/src/util/color/colorpalette.h b/src/util/color/colorpalette.h new file mode 100644 index 000000000000..1f0165a8b396 --- /dev/null +++ b/src/util/color/colorpalette.h @@ -0,0 +1,63 @@ +#pragma once + +#include + +#include "util/color/rgbcolor.h" + +class ColorPalette final { + public: + explicit ColorPalette(QString name, QList colorList) + : m_name(name), + m_colorList(colorList) { + DEBUG_ASSERT(m_colorList.size() != 0); + } + + mixxx::RgbColor at(int i) const { + return m_colorList.at(i); + } + + int size() const { + return m_colorList.size(); + } + + int indexOf(mixxx::RgbColor color) const { + return m_colorList.indexOf(color); + } + + mixxx::RgbColor nextColor(mixxx::RgbColor color) const; + mixxx::RgbColor previousColor(mixxx::RgbColor color) const; + mixxx::RgbColor colorForHotcueIndex(unsigned int index) const; + + QList::const_iterator begin() const { + return m_colorList.begin(); + } + + QList::const_iterator end() const { + return m_colorList.end(); + } + + QString getName() const { + return m_name; + } + + void setName(const QString name) { + m_name = name; + } + + static const ColorPalette mixxxHotcuePalette; + static const mixxx::RgbColor kDefaultCueColor; + + const QList& getColorList() const { + return m_colorList; + } + + private: + QString m_name; + QList m_colorList; +}; + +inline bool operator==( + const ColorPalette& lhs, const ColorPalette& rhs) { + return lhs.getName() == rhs.getName() && + lhs.getColorList() == rhs.getColorList(); +} diff --git a/src/util/color/predefinedcolor.cpp b/src/util/color/predefinedcolor.cpp deleted file mode 100644 index 2a5db56e9873..000000000000 --- a/src/util/color/predefinedcolor.cpp +++ /dev/null @@ -1,10 +0,0 @@ -#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 deleted file mode 100644 index 4e5745c0883b..000000000000 --- a/src/util/color/predefinedcolor.h +++ /dev/null @@ -1,35 +0,0 @@ -#pragma once - -#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; diff --git a/src/util/color/predefinedcolorsrepresentation.h b/src/util/color/predefinedcolorsrepresentation.h deleted file mode 100644 index 83f16405447d..000000000000 --- a/src/util/color/predefinedcolorsrepresentation.h +++ /dev/null @@ -1,41 +0,0 @@ -#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 deleted file mode 100644 index 8e758b9c7f48..000000000000 --- a/src/util/color/predefinedcolorsset.h +++ /dev/null @@ -1,140 +0,0 @@ -#pragma once - -#include -#include - -#include "predefinedcolorsrepresentation.h" -#include "util/color/predefinedcolor.h" -#include "util/color/rgbcolor.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(0xC50A08), - QLatin1String("Red"), - QObject::tr("Red"), - 1); - const PredefinedColorPointer green = std::make_shared( - QColor(0x32BE44), - QLatin1String("Green"), - QObject::tr("Green"), - 2); - const PredefinedColorPointer blue = std::make_shared( - QColor(0x0044FF), - QLatin1String("Blue"), - QObject::tr("Blue"), - 3); - const PredefinedColorPointer yellow = std::make_shared( - QColor(0xF8D200), - QLatin1String("Yellow"), - QObject::tr("Yellow"), - 4); - const PredefinedColorPointer cyan = std::make_shared( - QColor(0x42D4F4), - QLatin1String("Celeste"), - QObject::tr("Celeste"), - 5); - const PredefinedColorPointer magenta = std::make_shared( - QColor(0xAF00CC), - QLatin1String("Purple"), - QObject::tr("Purple"), - 6); - const PredefinedColorPointer pink = std::make_shared( - QColor(0xFCA6D7), - QLatin1String("Pink"), - QObject::tr("Pink"), - 7); - const PredefinedColorPointer white = std::make_shared( - QColor(0xF2F2FF), - QLatin1String("White"), - QObject::tr("White"), - 8); - - // The list of the predefined colors. - const QList allColors{ - noColor, - red, - green, - yellow, - blue, - cyan, - magenta, - pink, - white, - }; - - 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; - } - } - qWarning() << "No color matches name" << name; - 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; - } - } - qWarning() << "No color matches id" << iId; - return noColor; - }; - - PredefinedColorPointer predefinedColorFromRgbColor(mixxx::RgbColor color) const { - for (PredefinedColorPointer pColor : allColors) { - if (mixxx::RgbColor(pColor->m_defaultRgba.rgb()) == color) { - return pColor; - } - } - qWarning() << "No color matches RgbColor" << color; - return noColor; - }; - - PredefinedColorPointer predefinedColorFromRgbColor(mixxx::RgbColor::optional_t color) const { - if (color) { - return predefinedColorFromRgbColor(*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; -}; diff --git a/src/waveform/renderers/waveformrendermark.cpp b/src/waveform/renderers/waveformrendermark.cpp index 1d338688e08c..61aa4f1b1f5c 100644 --- a/src/waveform/renderers/waveformrendermark.cpp +++ b/src/waveform/renderers/waveformrendermark.cpp @@ -27,11 +27,6 @@ WaveformRenderMark::WaveformRenderMark( void WaveformRenderMark::setup(const QDomNode& node, const SkinContext& context) { WaveformSignalColors signalColors = *m_waveformRenderer->getWaveformSignalColors(); m_marks.setup(m_waveformRenderer->getGroup(), node, context, signalColors); - WaveformMarkPointer defaultMark(m_marks.getDefaultMark()); - QColor defaultColor = defaultMark - ? defaultMark->fillColor() - : signalColors.getAxesColor(); - m_predefinedColorsRepresentation = context.getCueColorRepresentation(node, defaultColor); } void WaveformRenderMark::draw(QPainter* painter, QPaintEvent* /*event*/) { @@ -128,7 +123,7 @@ void WaveformRenderMark::slotCuesUpdated() { } QString newLabel = pCue->getLabel(); - QColor newColor = m_predefinedColorsRepresentation.representationFor(pCue->getColor()); + QColor newColor = mixxx::RgbColor::toQColor(pCue->getColor()); if (pMark->m_text.isNull() || newLabel != pMark->m_text || !pMark->fillColor().isValid() || newColor != pMark->fillColor()) { pMark->m_text = newLabel; diff --git a/src/waveform/renderers/waveformrendermark.h b/src/waveform/renderers/waveformrendermark.h index 693332c81624..dc0fad17c90f 100644 --- a/src/waveform/renderers/waveformrendermark.h +++ b/src/waveform/renderers/waveformrendermark.h @@ -35,8 +35,6 @@ class WaveformRenderMark : public QObject, public WaveformRendererAbstract { private: void generateMarkImage(WaveformMarkPointer pMark); - PredefinedColorsRepresentation m_predefinedColorsRepresentation; - WaveformMarkSet m_marks; DISALLOW_COPY_AND_ASSIGN(WaveformRenderMark); }; diff --git a/src/widget/wcolorpicker.cpp b/src/widget/wcolorpicker.cpp index c6c942786989..fb1547206503 100644 --- a/src/widget/wcolorpicker.cpp +++ b/src/widget/wcolorpicker.cpp @@ -1,6 +1,5 @@ #include "widget/wcolorpicker.h" -#include #include #include #include @@ -9,11 +8,37 @@ #include "util/parented_ptr.h" namespace { - const int kNumColumns = 4; +constexpr int kNumColumnsCandidates[] = {5, 4, 3}; } -WColorPicker::WColorPicker(ColorOption colorOption, QWidget* parent) - : QWidget(parent) { +// Determine the best number of columns for items in a QGridView. +// +// Ideally, numItems % numColumn == 0 holds true. Rows that are almost +// empty do not look good, so if the we can't find the ideal column count, +// we fall back to a column count with the biggest number of elements in +// the last row. +inline int idealColumnCount(int numItems) { + int numColumns = 4; // Default in case kNumColumnsCandidates is empty + int numColumnsRemainder = -1; + for (const int numColumnsCandidate : kNumColumnsCandidates) { + const int remainder = numItems % numColumnsCandidate; + if (remainder == 0) { + numColumns = numColumnsCandidate; + break; + } + if (remainder > numColumnsRemainder) { + numColumnsRemainder = numColumnsCandidate; + numColumns = numColumnsCandidate; + } + } + + return numColumns; +} + +WColorPicker::WColorPicker(ColorOption colorOption, const ColorPalette& palette, QWidget* parent) + : QWidget(parent), + m_colorOption(colorOption), + m_palette(palette) { QGridLayout* pLayout = new QGridLayout(); pLayout->setMargin(0); pLayout->setContentsMargins(0, 0, 0, 0); @@ -30,96 +55,155 @@ WColorPicker::WColorPicker(ColorOption colorOption, QWidget* parent) // better than having buttons without any colors (which would make the // color picker unusable). m_pStyle = QStyleFactory::create(QString("fusion")); + setLayout(pLayout); + addColorButtons(); +} + +void WColorPicker::removeColorButtons() { + QGridLayout* pLayout = static_cast(layout()); + VERIFY_OR_DEBUG_ASSERT(pLayout) { + qWarning() << "Color Picker has no layout!"; + return; + } + + while (!m_colorButtons.isEmpty()) { + QPushButton* pColorButton = m_colorButtons.takeLast(); + pLayout->removeWidget(pColorButton); + delete pColorButton; + } +} + +void WColorPicker::addColorButtons() { + QGridLayout* pLayout = static_cast(layout()); + VERIFY_OR_DEBUG_ASSERT(pLayout) { + qWarning() << "Color Picker has no layout!"; + return; + } int row = 0; int column = 0; - for (const auto& pColor : Color::kPredefinedColorsSet.allColors) { - if (colorOption != ColorOption::AllowNoColor && - pColor == Color::kPredefinedColorsSet.noColor) { - continue; - } - parented_ptr pColorButton = make_parented("", this); - if (m_pStyle) { - pColorButton->setStyle(m_pStyle); - } - - if (pColor->m_defaultRgba.isValid()) { - // Set the background color of the button. This can't be overridden in skin stylesheets. - pColorButton->setStyleSheet( - QString("QPushButton { background-color: #%1; }").arg(pColor->m_defaultRgba.rgb(), 6, 16, QChar('0')) - ); - } else { - pColorButton->setProperty("noColor", true); - } + int numColors = m_palette.size(); + if (m_colorOption == ColorOption::AllowNoColor) { + numColors++; + } - pColorButton->setToolTip(pColor->m_sDisplayName); - pColorButton->setCheckable(true); - m_pColorButtons.insert(pColor, pColorButton); + int numColumns = idealColumnCount(numColors); + if (m_colorOption == ColorOption::AllowNoColor) { + addColorButton(std::nullopt, pLayout, row, column); + column++; + } - pLayout->addWidget(pColorButton, row, column); + for (const auto& color : m_palette) { + addColorButton(color, pLayout, row, column); column++; - if (column == kNumColumns) { + if (column == numColumns) { column = 0; row++; } - - connect(this, - &WColorPicker::colorPicked, - this, - &WColorPicker::slotColorPicked); - connect(pColorButton, - &QPushButton::clicked, - this, - [pColor, this]() { - emit colorPicked(pColor); - }); } - setLayout(pLayout); } -void WColorPicker::setSelectedColor(PredefinedColorPointer pColor) { - if (m_pSelectedColor) { - auto it = m_pColorButtons.constFind(m_pSelectedColor); - if (it != m_pColorButtons.constEnd()) { - it.value()->setChecked(false); - // This is needed to re-apply skin styles (e.g. to show/hide a checkmark icon) - it.value()->style()->unpolish(it.value()); - it.value()->style()->polish(it.value()); - } +void WColorPicker::addColorButton(mixxx::RgbColor::optional_t color, QGridLayout* pLayout, int row, int column) { + parented_ptr pColorButton = make_parented("", this); + if (m_pStyle) { + pColorButton->setStyle(m_pStyle); } - if (pColor) { - auto it = m_pColorButtons.constFind(pColor); - if (it != m_pColorButtons.constEnd()) { - it.value()->setChecked(true); - // This is needed to re-apply skin styles (e.g. to show/hide a checkmark icon) - it.value()->style()->unpolish(it.value()); - it.value()->style()->polish(it.value()); + if (color) { + // Set the background color of the button. This can't be overridden in skin stylesheets. + pColorButton->setStyleSheet( + QString("QPushButton { background-color: %1; }").arg(mixxx::RgbColor::toQString(color))); + } else { + pColorButton->setProperty("noColor", true); + } + pColorButton->setToolTip(mixxx::RgbColor::toQString(color, tr("No Color"))); + + pColorButton->setCheckable(true); + m_colorButtons.append(pColorButton); + + pLayout->addWidget(pColorButton, row, column); + + connect(this, + &WColorPicker::colorPicked, + this, + &WColorPicker::slotColorPicked); + connect(pColorButton, + &QPushButton::clicked, + this, + [this, color]() { + emit colorPicked(color); + }); +} + +void WColorPicker::resetSelectedColor() { + // Unset currently selected color + int i = 0; + if (m_selectedColor) { + i = m_palette.indexOf(*m_selectedColor); + if (i == -1) { + return; + } + if (m_colorOption == ColorOption::AllowNoColor) { + i++; } + } else if (m_colorOption != ColorOption::AllowNoColor) { + return; } - m_pSelectedColor = pColor; + DEBUG_ASSERT(i < m_colorButtons.size()); + + QPushButton* pButton = m_colorButtons.at(i); + VERIFY_OR_DEBUG_ASSERT(pButton != nullptr) { + return; + } + pButton->setChecked(false); + // This is needed to re-apply skin styles (e.g. to show/hide a checkmark icon) + pButton->style()->unpolish(pButton); + pButton->style()->polish(pButton); } -void WColorPicker::useColorSet(PredefinedColorsRepresentation* pColorRepresentation) { - QMapIterator i(m_pColorButtons); - while (i.hasNext()) { - i.next(); - PredefinedColorPointer pColor = i.key(); - QPushButton* pColorButton = i.value(); - QColor color = (pColorRepresentation == nullptr) ? pColor->m_defaultRgba : pColorRepresentation->representationFor(pColor); +void WColorPicker::setSelectedColor(mixxx::RgbColor::optional_t color) { + resetSelectedColor(); - // Set the background color of the button. This can't be overridden in skin stylesheets. - pColorButton->setStyleSheet( - QString("QPushButton { background-color: #%1; }").arg(color.rgb(), 6, 16, QChar('0')) - ); + m_selectedColor = color; + + int i = 0; + if (color) { + i = m_palette.indexOf(*color); + if (i == -1) { + return; + } + if (m_colorOption == ColorOption::AllowNoColor) { + i++; + } + } else if (m_colorOption != ColorOption::AllowNoColor) { + return; + } + + DEBUG_ASSERT(i < m_colorButtons.size()); - pColorButton->setToolTip(pColor->m_sDisplayName); + QPushButton* pButton = m_colorButtons.at(i); + VERIFY_OR_DEBUG_ASSERT(pButton != nullptr) { + return; } + pButton->setChecked(true); + // This is needed to re-apply skin styles (e.g. to show/hide a checkmark icon) + pButton->style()->unpolish(pButton); + pButton->style()->polish(pButton); } +void WColorPicker::setColorPalette(const ColorPalette& palette) { + if (m_palette == palette) { + return; + } + + resetSelectedColor(); + removeColorButtons(); + m_palette = palette; + addColorButtons(); +} -void WColorPicker::slotColorPicked(PredefinedColorPointer pColor) { - setSelectedColor(pColor); +void WColorPicker::slotColorPicked(mixxx::RgbColor::optional_t color) { + setSelectedColor(color); } diff --git a/src/widget/wcolorpicker.h b/src/widget/wcolorpicker.h index e3e10e570962..d905e6124623 100644 --- a/src/widget/wcolorpicker.h +++ b/src/widget/wcolorpicker.h @@ -1,11 +1,13 @@ #pragma once +#include #include #include #include #include #include "util/color/color.h" +#include "util/color/colorpalette.h" class WColorPicker : public QWidget { Q_OBJECT @@ -15,19 +17,25 @@ class WColorPicker : public QWidget { AllowNoColor, }; - explicit WColorPicker(ColorOption colorOption, QWidget* parent = nullptr); + explicit WColorPicker(ColorOption colorOption, const ColorPalette& palette, QWidget* parent = nullptr); - void setSelectedColor(PredefinedColorPointer pColor = nullptr); - void useColorSet(PredefinedColorsRepresentation* pColorRepresentation); + void resetSelectedColor(); + void setSelectedColor(mixxx::RgbColor::optional_t color); + void setColorPalette(const ColorPalette& palette); signals: - void colorPicked(PredefinedColorPointer pColor); + void colorPicked(mixxx::RgbColor::optional_t color); private slots: - void slotColorPicked(PredefinedColorPointer pColor); + void slotColorPicked(mixxx::RgbColor::optional_t color); private: - QMap m_pColorButtons; - PredefinedColorPointer m_pSelectedColor; + void addColorButtons(); + void removeColorButtons(); + void addColorButton(mixxx::RgbColor::optional_t color, QGridLayout* pLayout, int row, int column); + ColorOption m_colorOption; + mixxx::RgbColor::optional_t m_selectedColor; + ColorPalette m_palette; + QList m_colorButtons; QStyle* m_pStyle; }; diff --git a/src/widget/wcolorpickeraction.cpp b/src/widget/wcolorpickeraction.cpp index db72bc97e951..8837695ac616 100644 --- a/src/widget/wcolorpickeraction.cpp +++ b/src/widget/wcolorpickeraction.cpp @@ -1,8 +1,8 @@ #include "widget/wcolorpickeraction.h" -WColorPickerAction::WColorPickerAction(WColorPicker::ColorOption colorOption, QWidget* parent) +WColorPickerAction::WColorPickerAction(WColorPicker::ColorOption colorOption, const ColorPalette& palette, QWidget* parent) : QWidgetAction(parent), - m_pColorPicker(make_parented(colorOption)) { + m_pColorPicker(make_parented(colorOption, palette)) { connect(m_pColorPicker.get(), &WColorPicker::colorPicked, this, &WColorPickerAction::colorPicked); QHBoxLayout* pLayout = new QHBoxLayout(); @@ -13,6 +13,14 @@ WColorPickerAction::WColorPickerAction(WColorPicker::ColorOption colorOption, QW setDefaultWidget(pWidget); } -void WColorPickerAction::setSelectedColor(PredefinedColorPointer pColor) { - m_pColorPicker->setSelectedColor(pColor); +void WColorPickerAction::resetSelectedColor() { + m_pColorPicker->resetSelectedColor(); +} + +void WColorPickerAction::setSelectedColor(mixxx::RgbColor::optional_t color) { + m_pColorPicker->setSelectedColor(color); +} + +void WColorPickerAction::setColorPalette(const ColorPalette& palette) { + m_pColorPicker->setColorPalette(palette); } diff --git a/src/widget/wcolorpickeraction.h b/src/widget/wcolorpickeraction.h index 48e1012772b2..ce222ff49cc8 100644 --- a/src/widget/wcolorpickeraction.h +++ b/src/widget/wcolorpickeraction.h @@ -12,12 +12,15 @@ class WColorPickerAction : public QWidgetAction { public: explicit WColorPickerAction( WColorPicker::ColorOption colorOption, + const ColorPalette& palette, QWidget* parent = nullptr); - void setSelectedColor(PredefinedColorPointer pColor = nullptr); + void resetSelectedColor(); + void setSelectedColor(mixxx::RgbColor::optional_t color); + void setColorPalette(const ColorPalette& palette); signals: - void colorPicked(PredefinedColorPointer pColor); + void colorPicked(mixxx::RgbColor::optional_t color); private: parented_ptr m_pColorPicker; diff --git a/src/widget/wcuemenupopup.cpp b/src/widget/wcuemenupopup.cpp index 6cefca6aef22..3c2817c4ab3a 100644 --- a/src/widget/wcuemenupopup.cpp +++ b/src/widget/wcuemenupopup.cpp @@ -7,8 +7,9 @@ #include "engine/engine.h" #include "util/color/color.h" -WCueMenuPopup::WCueMenuPopup(QWidget* parent) - : QWidget(parent) { +WCueMenuPopup::WCueMenuPopup(UserSettingsPointer pConfig, QWidget* parent) + : QWidget(parent), + m_colorPaletteSettings(ColorPaletteSettings(pConfig)) { QWidget::hide(); setWindowFlags(Qt::Popup); setAttribute(Qt::WA_StyledBackground); @@ -30,7 +31,7 @@ WCueMenuPopup::WCueMenuPopup(QWidget* parent) connect(m_pEditLabel, &QLineEdit::textEdited, this, &WCueMenuPopup::slotEditLabel); connect(m_pEditLabel, &QLineEdit::returnPressed, this, &WCueMenuPopup::hide); - m_pColorPicker = new WColorPicker(WColorPicker::ColorOption::DenyNoColor, this); + m_pColorPicker = new WColorPicker(WColorPicker::ColorOption::DenyNoColor, m_colorPaletteSettings.getHotcueColorPalette(), this); m_pColorPicker->setObjectName("CueColorPicker"); connect(m_pColorPicker, &WColorPicker::colorPicked, this, &WCueMenuPopup::slotChangeCueColor); @@ -97,7 +98,7 @@ void WCueMenuPopup::setTrackAndCue(TrackPointer pTrack, CuePointer pCue) { m_pCueNumber->setText(QString("")); m_pCuePosition->setText(QString("")); m_pEditLabel->setText(QString("")); - m_pColorPicker->setSelectedColor(); + m_pColorPicker->setSelectedColor(std::nullopt); } } @@ -108,14 +109,15 @@ void WCueMenuPopup::slotEditLabel() { m_pCue->setLabel(m_pEditLabel->text()); } -void WCueMenuPopup::slotChangeCueColor(PredefinedColorPointer pColor) { +void WCueMenuPopup::slotChangeCueColor(mixxx::RgbColor::optional_t color) { VERIFY_OR_DEBUG_ASSERT(m_pCue != nullptr) { return; } - VERIFY_OR_DEBUG_ASSERT(pColor != nullptr) { + VERIFY_OR_DEBUG_ASSERT(color) { return; } - m_pCue->setColor(pColor); + m_pCue->setColor(*color); + m_pColorPicker->setSelectedColor(color); hide(); } diff --git a/src/widget/wcuemenupopup.h b/src/widget/wcuemenupopup.h index cac6a9825c15..7e1743590d0d 100644 --- a/src/widget/wcuemenupopup.h +++ b/src/widget/wcuemenupopup.h @@ -4,6 +4,7 @@ #include #include +#include "preferences/colorpalettesettings.h" #include "track/cue.h" #include "track/track.h" #include "widget/wcolorpicker.h" @@ -11,7 +12,7 @@ class WCueMenuPopup : public QWidget { Q_OBJECT public: - WCueMenuPopup(QWidget* parent = nullptr); + WCueMenuPopup(UserSettingsPointer pConfig, QWidget* parent = nullptr); ~WCueMenuPopup() { delete m_pCueNumber; @@ -23,9 +24,9 @@ class WCueMenuPopup : public QWidget { void setTrackAndCue(TrackPointer pTrack, CuePointer pCue); - void useColorSet(PredefinedColorsRepresentation* pColorRepresentation) { + void setColorPalette(const ColorPalette& palette) { if (m_pColorPicker != nullptr) { - m_pColorPicker->useColorSet(pColorRepresentation); + m_pColorPicker->setColorPalette(palette); } } @@ -42,6 +43,7 @@ class WCueMenuPopup : public QWidget { } void show() { + setColorPalette(m_colorPaletteSettings.getHotcueColorPalette()); m_pEditLabel->setFocus(); emit aboutToShow(); QWidget::show(); @@ -54,9 +56,10 @@ class WCueMenuPopup : public QWidget { private slots: void slotEditLabel(); void slotDeleteCue(); - void slotChangeCueColor(PredefinedColorPointer pColor); + void slotChangeCueColor(mixxx::RgbColor::optional_t color); private: + ColorPaletteSettings m_colorPaletteSettings; CuePointer m_pCue; TrackPointer m_pTrack; diff --git a/src/widget/woverview.cpp b/src/widget/woverview.cpp index b2a5379cfd04..4fb65c6fbfec 100644 --- a/src/widget/woverview.cpp +++ b/src/widget/woverview.cpp @@ -10,21 +10,23 @@ // // +#include "woverview.h" + #include -#include +#include #include #include #include -#include #include #include -#include +#include #include "analyzer/analyzerprogress.h" #include "control/controlobject.h" #include "control/controlproxy.h" #include "engine/engine.h" #include "mixer/playermanager.h" +#include "preferences/colorpalettesettings.h" #include "track/track.h" #include "util/color/color.h" #include "util/compatibility.h" @@ -33,12 +35,10 @@ #include "util/math.h" #include "util/painterscope.h" #include "util/timer.h" -#include "widget/controlwidgetconnection.h" -#include "woverview.h" -#include "wskincolor.h" - #include "waveform/waveform.h" #include "waveform/waveformwidgetfactory.h" +#include "widget/controlwidgetconnection.h" +#include "wskincolor.h" WOverview::WOverview( const char* group, @@ -54,7 +54,7 @@ WOverview::WOverview( m_group(group), m_pConfig(pConfig), m_endOfTrack(false), - m_pCueMenuPopup(std::make_unique(this)), + m_pCueMenuPopup(std::make_unique(pConfig, this)), m_bShowCueTimes(true), m_iPosSeconds(0), m_bLeftClickDragging(false), @@ -131,12 +131,10 @@ 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->fillColor() - : m_signalColors.getAxesColor(); - m_predefinedColorsRepresentation = context.getCueColorRepresentation(node, defaultColor); - m_pCueMenuPopup->useColorSet(&m_predefinedColorsRepresentation); + + ColorPaletteSettings colorPaletteSettings(m_pConfig); + auto colorPalette = colorPaletteSettings.getHotcueColorPalette(); + m_pCueMenuPopup->setColorPalette(colorPalette); for (const auto& pMark: m_marks) { if (pMark->isValid()) { @@ -349,8 +347,9 @@ void WOverview::updateCues(const QList &loadedCues) { for (CuePointer currentCue: loadedCues) { const WaveformMarkPointer pMark = m_marks.getHotCueMark(currentCue->getHotCue()); - if (pMark != nullptr && pMark->isValid() && pMark->isVisible() && pMark->getSamplePosition() != Cue::kNoPosition) { - QColor newColor = m_predefinedColorsRepresentation.representationFor(currentCue->getColor()); + if (pMark != nullptr && pMark->isValid() && pMark->isVisible() + && pMark->getSamplePosition() != Cue::kNoPosition) { + QColor newColor = mixxx::RgbColor::toQColor(currentCue->getColor()); if (newColor != pMark->fillColor() || newColor != pMark->m_textColor) { pMark->setBaseColor(newColor); } diff --git a/src/widget/woverview.h b/src/widget/woverview.h index 4837b89f002b..25ce680bd181 100644 --- a/src/widget/woverview.h +++ b/src/widget/woverview.h @@ -169,7 +169,6 @@ class WOverview : public WWidget, public TrackDropTarget { QColor m_labelBackgroundColor; QColor m_endOfTrackColor; - PredefinedColorsRepresentation m_predefinedColorsRepresentation; // All WaveformMarks WaveformMarkSet m_marks; // List of visible WaveformMarks sorted by the order they appear in the track diff --git a/src/widget/wtracktableview.cpp b/src/widget/wtracktableview.cpp index 35b426e76284..3fc47d469c2a 100644 --- a/src/widget/wtracktableview.cpp +++ b/src/widget/wtracktableview.cpp @@ -1,45 +1,45 @@ -#include -#include +#include "widget/wtracktableview.h" + +#include #include -#include #include -#include -#include -#include +#include #include +#include #include +#include +#include +#include -#include "widget/wtracktableview.h" - -#include "widget/wcolorpickeraction.h" -#include "widget/wcoverartmenu.h" -#include "widget/wskincolor.h" -#include "widget/wtracktableviewheader.h" -#include "widget/wwidget.h" +#include "control/controlobject.h" +#include "control/controlproxy.h" #include "library/coverartutils.h" -#include "library/dlgtagfetcher.h" -#include "library/dlgtrackinfo.h" -#include "library/librarytablemodel.h" #include "library/crate/cratefeaturehelper.h" #include "library/dao/trackschema.h" +#include "library/dlgtagfetcher.h" +#include "library/dlgtrackinfo.h" #include "library/dlgtrackmetadataexport.h" #include "library/externaltrackcollection.h" +#include "library/librarytablemodel.h" #include "library/trackcollection.h" #include "library/trackcollectionmanager.h" -#include "control/controlobject.h" -#include "control/controlproxy.h" -#include "track/track.h" -#include "track/trackref.h" -#include "sources/soundsourceproxy.h" #include "mixer/playermanager.h" +#include "preferences/colorpalettesettings.h" #include "preferences/dialog/dlgpreflibrary.h" -#include "waveform/guitick.h" -#include "util/dnd.h" -#include "util/time.h" +#include "sources/soundsourceproxy.h" +#include "track/track.h" +#include "track/trackref.h" #include "util/assert.h" -#include "util/parented_ptr.h" #include "util/desktophelper.h" -#include "util/color/predefinedcolorsset.h" +#include "util/dnd.h" +#include "util/parented_ptr.h" +#include "util/time.h" +#include "waveform/guitick.h" +#include "widget/wcolorpickeraction.h" +#include "widget/wcoverartmenu.h" +#include "widget/wskincolor.h" +#include "widget/wtracktableviewheader.h" +#include "widget/wwidget.h" WTrackTableView::WTrackTableView(QWidget * parent, UserSettingsPointer pConfig, @@ -574,7 +574,8 @@ void WTrackTableView::createActions() { connect(m_pBpmThreeHalvesAction, &QAction::triggered, this, [this] { slotScaleBpm(Beats::THREEHALVES); }); - m_pColorPickerAction = new WColorPickerAction(WColorPicker::ColorOption::AllowNoColor, this); + ColorPaletteSettings colorPaletteSettings(m_pConfig); + m_pColorPickerAction = new WColorPickerAction(WColorPicker::ColorOption::AllowNoColor, colorPaletteSettings.getTrackColorPalette(), this); m_pColorPickerAction->setObjectName("TrackColorPickerAction"); connect(m_pColorPickerAction, &WColorPickerAction::colorPicked, @@ -751,7 +752,7 @@ void WTrackTableView::showTrackInfo(QModelIndex index) { if (m_pTrackInfo.isNull()) { // Give a NULL parent because otherwise it inherits our style which can // make it unreadable. Bug #673411 - m_pTrackInfo.reset(new DlgTrackInfo(nullptr)); + m_pTrackInfo.reset(new DlgTrackInfo(m_pConfig, nullptr)); connect(m_pTrackInfo.data(), SIGNAL(next()), this, SLOT(slotNextTrackInfo())); @@ -1088,6 +1089,9 @@ void WTrackTableView::contextMenuEvent(QContextMenuEvent* event) { // Track color menu only appears if at least one track is selected if (indices.size()) { + m_pColorPickerAction->setColorPalette( + ColorPaletteSettings(m_pConfig).getTrackColorPalette()); + // Get color of first selected track int column = trackModel->fieldIndex(LIBRARYTABLE_COLOR); QModelIndex index = indices.at(0).sibling(indices.at(0).row(), column); @@ -1106,19 +1110,11 @@ void WTrackTableView::contextMenuEvent(QContextMenuEvent* event) { } } - // Get the predefined color of the selected tracks. If they have - // different colors, do not preselect a color (by using nullptr - // instead). - PredefinedColorPointer predefinedTrackColor = nullptr; - if (trackColor) { - // All tracks have the same color - predefinedTrackColor = Color::kPredefinedColorsSet.predefinedColorFromRgbColor(trackColor); - } else if (!multipleTrackColors) { - // All tracks have no color - predefinedTrackColor = Color::kPredefinedColorsSet.noColor; + if (multipleTrackColors) { + m_pColorPickerAction->resetSelectedColor(); + } else { + m_pColorPickerAction->setSelectedColor(trackColor); } - - m_pColorPickerAction->setSelectedColor(predefinedTrackColor); m_pColorMenu->addAction(m_pColorPickerAction); m_pMenu->addMenu(m_pColorMenu); } @@ -1977,7 +1973,7 @@ void WTrackTableView::lockBpm(bool lock) { } } -void WTrackTableView::slotColorPicked(PredefinedColorPointer pColor) { +void WTrackTableView::slotColorPicked(mixxx::RgbColor::optional_t color) { TrackModel* trackModel = getTrackModel(); if (trackModel == nullptr) { return; @@ -1988,7 +1984,7 @@ void WTrackTableView::slotColorPicked(PredefinedColorPointer pColor) { for (const auto& index : selectedTrackIndices) { TrackPointer pTrack = trackModel->getTrack(index); if (pTrack) { - pTrack->setColor(mixxx::RgbColor::fromQColor(pColor->m_defaultRgba)); + pTrack->setColor(color); } } diff --git a/src/widget/wtracktableview.h b/src/widget/wtracktableview.h index 2c1b90d7c651..fd6beae9d6ae 100644 --- a/src/widget/wtracktableview.h +++ b/src/widget/wtracktableview.h @@ -81,7 +81,7 @@ class WTrackTableView : public WLibraryTableView { void slotLockBpm(); void slotUnlockBpm(); void slotScaleBpm(int); - void slotColorPicked(PredefinedColorPointer pColor); + void slotColorPicked(mixxx::RgbColor::optional_t color); void slotClearBeats(); void slotClearPlayCount(); diff --git a/src/widget/wwidget.cpp b/src/widget/wwidget.cpp index 364b46280a13..801678a107a3 100644 --- a/src/widget/wwidget.cpp +++ b/src/widget/wwidget.cpp @@ -26,7 +26,8 @@ WWidget::WWidget(QWidget* parent, Qt::WindowFlags flags) : QWidget(parent, flags), WBaseWidget(this), m_activeTouchButton(Qt::NoButton), - m_scaleFactor(1.0) { + m_scaleFactor(1.0), + m_bShouldHighlightBackgroundOnHover(false) { m_pTouchShift = new ControlProxy("[Controls]", "touch_shift"); setAttribute(Qt::WA_StaticContents); setAttribute(Qt::WA_AcceptTouchEvents); @@ -37,6 +38,31 @@ WWidget::~WWidget() { delete m_pTouchShift; } +double WWidget::getBackgroundColorRgba() const { + if (m_backgroundColorRgba < 0) { + return -1; + } + return m_backgroundColorRgba; +} + +void WWidget::setBackgroundColorRgba(double rgba) { + QColor backgroundColor = QColor::fromRgba(rgba); + QColor highlightedBackgroundColor = backgroundColor.lighter(); + QString style = QString("WWidget { background-color: %1; }"); + if (m_bShouldHighlightBackgroundOnHover) { + style += "WWidget:hover { background-color: %2; }"; + } + + if (rgba >= 0) { + setStyleSheet(style.arg(backgroundColor.name()) + .arg(highlightedBackgroundColor.name())); + } else { + setStyleSheet(""); + } + m_backgroundColorRgba = rgba; + m_bBackgroundIsDark = Color::isDimmColor(backgroundColor); +} + bool WWidget::touchIsRightButton() { return (m_pTouchShift->get() != 0.0); } diff --git a/src/widget/wwidget.h b/src/widget/wwidget.h index cbadf8ab2fe6..0fbf10631c25 100644 --- a/src/widget/wwidget.h +++ b/src/widget/wwidget.h @@ -23,6 +23,7 @@ #include #include "preferences/usersettings.h" +#include "util/color/color.h" #include "widget/wbasewidget.h" class ControlProxy; @@ -44,6 +45,21 @@ class WWidget : public QWidget, public WBaseWidget { ~WWidget() override; Q_PROPERTY(double value READ getControlParameterDisplay); + Q_PROPERTY(double backgroundColorRgba READ getBackgroundColorRgba WRITE + setBackgroundColorRgba); + Q_PROPERTY(bool shouldHighlightBackgroundOnHover MEMBER + m_bShouldHighlightBackgroundOnHover); + Q_PROPERTY(bool hasBackgroundColor READ hasBackgroundColor); + Q_PROPERTY(bool backgroundIsDark READ backgroundIsDark); + + double getBackgroundColorRgba() const; + void setBackgroundColorRgba(double rgba); + bool hasBackgroundColor() const { + return m_backgroundColorRgba >= 0; + } + bool backgroundIsDark() const { + return m_bBackgroundIsDark; + } protected: bool touchIsRightButton(); @@ -60,6 +76,9 @@ class WWidget : public QWidget, public WBaseWidget { private: ControlProxy* m_pTouchShift; double m_scaleFactor; + double m_backgroundColorRgba; + bool m_bBackgroundIsDark; + bool m_bShouldHighlightBackgroundOnHover; }; #endif