1/* 2 * Copyright (c) 2017-2021 Meltytech, LLC 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18import QtQuick 2.12 19import QtQuick.Controls 2.12 20import QtQuick.Layouts 1.12 21import Shotcut.Controls 1.0 as Shotcut 22 23Item { 24 property string rectProperty: "rect" 25 property rect filterRect: filter.getRect(rectProperty) 26 property var defaultParameters: [rectProperty, 'type', 'color.1', 'color.2', 'color.3', 'color.4', 'color.5', 'color.6', 'color.7', 'color.8', 'color.9', 'color.10', 'bgcolor', 'thickness', 'fill', 'mirror', 'reverse', 'tension', 'bands', 'frequency_low', 'frequency_high', 'threshold'] 27 28 property int _minFreqDelta: 1000 29 property bool _disableUpdate: true 30 31 width: 350 32 height: 425 33 34 Component.onCompleted: { 35 if (filter.isNew) { 36 filter.set(rectProperty, '0/50%:50%x50%') 37 filter.set('type', 'line') 38 filter.set('color.1', '#ffffffff') 39 filter.set('bgcolor', '#00ffffff') 40 filter.set('thickness', '1') 41 filter.set('fill', '0') 42 filter.set('mirror', '0') 43 filter.set('reverse', '0') 44 filter.set('tension', '0.4') 45 filter.set('bands', '31') 46 filter.set('frequency_low', '20') 47 filter.set('frequency_high', '20000') 48 filter.set('threshold', '-60') 49 filter.savePreset(defaultParameters) 50 } 51 setControls() 52 } 53 54 function setFilter() { 55 var x = parseFloat(rectX.text) 56 var y = parseFloat(rectY.text) 57 var w = parseFloat(rectW.text) 58 var h = parseFloat(rectH.text) 59 if (x !== filterRect.x || 60 y !== filterRect.y || 61 w !== filterRect.width || 62 h !== filterRect.height) { 63 filterRect.x = x 64 filterRect.y = y 65 filterRect.width = w 66 filterRect.height = h 67 filter.set(rectProperty, filterRect) 68 } 69 } 70 71 function setControls() { 72 _disableUpdate = true 73 fgGradient.colors = filter.getGradient('color') 74 bgColor.value = filter.get('bgcolor') 75 thicknessSlider.value = filter.getDouble('thickness') 76 fillCheckbox.checked = filter.get('fill') == 1 77 mirrorCheckbox.checked = filter.get('mirror') == 1 78 reverseCheckbox.checked = filter.get('reverse') == 1 79 tensionSlider.value = filter.getDouble('tension') 80 bandsSlider.value = filter.getDouble('bands') 81 freqLowSlider.value = filter.getDouble('frequency_low') 82 freqHighSlider.value = filter.getDouble('frequency_high') 83 thresholdSlider.value = filter.getDouble('threshold') 84 _disableUpdate = false 85 } 86 87 GridLayout { 88 columns: 5 89 anchors.fill: parent 90 anchors.margins: 8 91 92 Label { 93 text: qsTr('Preset') 94 Layout.alignment: Qt.AlignRight 95 } 96 Shotcut.Preset { 97 id: preset 98 parameters: defaultParameters 99 Layout.columnSpan: 4 100 onPresetSelected: setControls() 101 onBeforePresetLoaded: { 102 // Clear all gradient colors before loading the new values 103 filter.setGradient('color', []) 104 } 105 } 106 107 Label { 108 text: qsTr('Type') 109 Layout.alignment: Qt.AlignRight 110 } 111 Shotcut.ComboBox { 112 Layout.columnSpan: 4 113 id: typeCombo 114 model: [qsTr('Line'), qsTr('Bar')] 115 property var values: ['line', 'bar'] 116 function valueToIndex() { 117 var w = filter.get('type') 118 for (var i = 0; i < values.length; ++i) 119 if (values[i] === w) break; 120 if (i === values.length) i = 0; 121 return i; 122 } 123 onActivated: filter.set('type', values[index]) 124 } 125 126 Label { 127 text: qsTr('Spectrum Color') 128 Layout.alignment: Qt.AlignRight 129 } 130 Shotcut.GradientControl { 131 Layout.columnSpan: 4 132 id: fgGradient 133 onGradientChanged: { 134 if (_disableUpdate) return 135 filter.setGradient('color', colors) 136 } 137 } 138 139 Label { 140 text: qsTr('Background Color') 141 Layout.alignment: Qt.AlignRight 142 } 143 Shotcut.ColorPicker { 144 Layout.columnSpan: 4 145 id: bgColor 146 eyedropper: true 147 alpha: true 148 onValueChanged: filter.set('bgcolor', value) 149 } 150 151 Label { 152 text: qsTr('Thickness') 153 Layout.alignment: Qt.AlignRight 154 } 155 Shotcut.SliderSpinner { 156 Layout.columnSpan: 3 157 id: thicknessSlider 158 minimumValue: 0 159 maximumValue: 20 160 decimals: 0 161 suffix: ' px' 162 onValueChanged: filter.set("thickness", value) 163 } 164 Shotcut.UndoButton { 165 onClicked: thicknessSlider.value = 1 166 } 167 168 Label { 169 text: qsTr('Position') 170 Layout.alignment: Qt.AlignRight 171 } 172 RowLayout { 173 Layout.columnSpan: 4 174 TextField { 175 id: rectX 176 text: filterRect.x 177 horizontalAlignment: Qt.AlignRight 178 selectByMouse: true 179 onEditingFinished: if (filterRect.x !== parseFloat(text)) setFilter() 180 } 181 Label { text: ',' } 182 TextField { 183 id: rectY 184 text: filterRect.y 185 horizontalAlignment: Qt.AlignRight 186 selectByMouse: true 187 onEditingFinished: if (filterRect.y !== parseFloat(text)) setFilter() 188 } 189 } 190 191 Label { 192 text: qsTr('Size') 193 Layout.alignment: Qt.AlignRight 194 } 195 RowLayout { 196 Layout.columnSpan: 4 197 TextField { 198 id: rectW 199 text: filterRect.width 200 horizontalAlignment: Qt.AlignRight 201 selectByMouse: true 202 onEditingFinished: if (filterRect.width !== parseFloat(text)) setFilter() 203 } 204 Label { text: 'x' } 205 TextField { 206 id: rectH 207 text: filterRect.height 208 horizontalAlignment: Qt.AlignRight 209 selectByMouse: true 210 onEditingFinished: if (filterRect.height !== parseFloat(text)) setFilter() 211 } 212 } 213 214 Label { 215 text: qsTr('Fill') 216 Layout.alignment: Qt.AlignRight 217 } 218 CheckBox { 219 Layout.columnSpan: 4 220 id: fillCheckbox 221 text: qsTr('Fill the area under the spectrum.') 222 onClicked: filter.set('fill', checked ? 1 : 0) 223 } 224 225 Label { 226 text: qsTr('Mirror') 227 Layout.alignment: Qt.AlignRight 228 } 229 CheckBox { 230 Layout.columnSpan: 4 231 id: mirrorCheckbox 232 text: qsTr('Mirror the spectrum.') 233 onClicked: filter.set('mirror', checked ? 1 : 0) 234 } 235 236 Label { 237 text: qsTr('Reverse') 238 Layout.alignment: Qt.AlignRight 239 } 240 CheckBox { 241 Layout.columnSpan: 4 242 id: reverseCheckbox 243 text: qsTr('Reverse the spectrum.') 244 onClicked: filter.set('reverse', checked ? 1 : 0) 245 } 246 247 Label { 248 text: qsTr('Tension') 249 Layout.alignment: Qt.AlignRight 250 } 251 Shotcut.SliderSpinner { 252 Layout.columnSpan: 3 253 id: tensionSlider 254 minimumValue: 0.0 255 maximumValue: 1.0 256 decimals: 1 257 onValueChanged: filter.set("tension", value) 258 } 259 Shotcut.UndoButton { 260 onClicked: tensionSlider.value = 0.4 261 } 262 263 Label { 264 text: qsTr('Bands') 265 Layout.alignment: Qt.AlignRight 266 } 267 Shotcut.SliderSpinner { 268 Layout.columnSpan: 3 269 id: bandsSlider 270 minimumValue: 5 271 maximumValue: 100 272 decimals: 0 273 onValueChanged: filter.set("bands", value) 274 } 275 Shotcut.UndoButton { 276 onClicked: bandsSlider.value = 31 277 } 278 279 Label { 280 text: qsTr('Low Frequency') 281 Layout.alignment: Qt.AlignRight 282 Shotcut.HoverTip { text: qsTr('The low end of the frequency range of the spectrum.') } 283 } 284 Shotcut.SliderSpinner { 285 Layout.columnSpan: 3 286 id: freqLowSlider 287 minimumValue: 20 288 maximumValue: 20000 - _minFreqDelta 289 decimals: 0 290 suffix: ' Hz' 291 onValueChanged: { 292 filter.set("frequency_low", value) 293 if (!_disableUpdate && (value + _minFreqDelta) > freqHighSlider.value) { 294 freqHighSlider.value = value + _minFreqDelta 295 } 296 } 297 } 298 Shotcut.UndoButton { 299 onClicked: freqLowSlider.value = 20 300 } 301 302 Label { 303 text: qsTr('High Frequency') 304 Layout.alignment: Qt.AlignRight 305 Shotcut.HoverTip { text: qsTr('The high end of the frequency range of the spectrum.') } 306 } 307 Shotcut.SliderSpinner { 308 Layout.columnSpan: 3 309 id: freqHighSlider 310 minimumValue: 20 + _minFreqDelta 311 maximumValue: 20000 312 decimals: 0 313 suffix: ' Hz' 314 onValueChanged: { 315 filter.set("frequency_high", value) 316 if (!_disableUpdate && (value - _minFreqDelta) < freqLowSlider.value) { 317 freqLowSlider.value = value - _minFreqDelta 318 } 319 } 320 } 321 Shotcut.UndoButton { 322 onClicked: freqHighSlider.value = 20000 323 } 324 325 Label { 326 text: qsTr('Threshold') 327 Layout.alignment: Qt.AlignRight 328 } 329 Shotcut.SliderSpinner { 330 Layout.columnSpan: 3 331 id: thresholdSlider 332 minimumValue: -60 333 maximumValue: 0 334 decimals: 0 335 suffix: ' dB' 336 onValueChanged: filter.set("threshold", value) 337 } 338 Shotcut.UndoButton { 339 onClicked: thresholdSlider.value = -60 340 } 341 342 Item { Layout.fillHeight: true } 343 } 344 345 Connections { 346 target: filter 347 onChanged: { 348 var newValue = filter.getRect(rectProperty) 349 if (filterRect !== newValue) 350 filterRect = newValue 351 } 352 } 353} 354