1 /***
2
3 Olive - Non-Linear Video Editor
4 Copyright (C) 2019 Olive Team
5
6 This program is free software: you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation, either version 3 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19 ***/
20
21 #include "labelslider.h"
22
23 #include <QMouseEvent>
24 #include <QInputDialog>
25 #include <QApplication>
26 #include <QMenu>
27
28 #include "undo/undo.h"
29 #include "panels/viewer.h"
30 #include "global/config.h"
31 #include "global/math.h"
32 #include "global/debug.h"
33 #include "ui/styling.h"
34 #include "ui/menu.h"
35
LabelSlider(QWidget * parent)36 LabelSlider::LabelSlider(QWidget* parent) : QLabel(parent) {
37 // set a default frame rate - fallback, shouldn't ever really be used
38 frame_rate = 30;
39
40 decimal_places = 1;
41 drag_start = false;
42 drag_proc = false;
43 min_enabled = false;
44 max_enabled = false;
45 SetColor();
46 SetDefaultCursor();
47 internal_value = -1;
48 set = false;
49 display_type = Normal;
50
51 SetDefault(0);
52
53 setContextMenuPolicy(Qt::CustomContextMenu);
54 connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(ShowContextMenu(const QPoint&)));
55 }
56
SetFrameRate(double d)57 void LabelSlider::SetFrameRate(double d) {
58 frame_rate = d;
59 }
60
SetDecimalPlaces(int places)61 void LabelSlider::SetDecimalPlaces(int places)
62 {
63 decimal_places = places;
64 }
65
SetDisplayType(const DisplayType & type)66 void LabelSlider::SetDisplayType(const DisplayType& type) {
67 display_type = type;
68 setText(ValueToString());
69 }
70
SetValue(double v)71 void LabelSlider::SetValue(double v) {
72 set = true;
73 if (!qFuzzyCompare(v, internal_value)) {
74 if (min_enabled && v < min_value) {
75 internal_value = min_value;
76 } else if (max_enabled && v > max_value) {
77 internal_value = max_value;
78 } else {
79 internal_value = v;
80 }
81
82 setText(ValueToString());
83 }
84 }
85
IsDragging()86 bool LabelSlider::IsDragging() {
87 return drag_proc;
88 }
89
ValueToString()90 QString LabelSlider::ValueToString() {
91 double v = internal_value;
92 if (qIsNaN(v)) {
93 return "---";
94 } else {
95 switch (display_type) {
96 case FrameNumber:
97 return frame_to_timecode(long(v), olive::CurrentConfig.timecode_view, frame_rate);
98 case Percent:
99 return QString::number((v*100), 'f', decimal_places).append("%");
100 case Decibel:
101 {
102 QString db_str;
103
104 // -96 dB is considered -infinity
105 if (amplitude_to_db(v) <= -96) {
106 // hex sequence for -infinity
107 db_str = "-\xE2\x88\x9E";
108 } else {
109 db_str = QString::number(amplitude_to_db(v), 'f', decimal_places);
110 }
111
112 // add "dB" suffix
113 db_str.append(" dB");
114
115 return db_str;
116 }
117 default:
118 return QString::number(v, 'f', decimal_places);
119 }
120 }
121 }
122
SetColor(QString c)123 void LabelSlider::SetColor(QString c) {
124 if (c.isEmpty()) {
125 if (olive::styling::UseDarkIcons()) {
126 c = "#0080ff";
127 } else {
128 c = "#ffc000";
129 }
130 }
131 setStyleSheet("QLabel{color:" + c + ";text-decoration:underline;}QLabel:disabled{color:#808080;}");
132 }
133
value()134 double LabelSlider::value() {
135 return internal_value;
136 }
137
SetDefault(double v)138 void LabelSlider::SetDefault(double v) {
139 default_value = v;
140 if (!set) {
141 SetValue(v);
142 set = false;
143 }
144 }
145
SetMinimum(double v)146 void LabelSlider::SetMinimum(double v) {
147 min_value = v;
148 min_enabled = true;
149 }
150
SetMaximum(double v)151 void LabelSlider::SetMaximum(double v) {
152 max_value = v;
153 max_enabled = true;
154 }
155
mousePressEvent(QMouseEvent * ev)156 void LabelSlider::mousePressEvent(QMouseEvent *ev) {
157 // if primary button is clicked
158 if (ev->button() == Qt::LeftButton && !drag_start) {
159
160 // store initial value to be compared while dragging
161 drag_start_value = internal_value;
162
163 // alt + click sets labelslider to default value
164 if (ev->modifiers() & Qt::AltModifier) {
165
166 // reset to default
167 ResetToDefault();
168
169 } else {
170
171 // if value isn't valid, we set to a "default" of 0
172 if (qIsNaN(internal_value)) internal_value = 0;
173
174 // hide the cursor and store information about it for dragging
175 SetActiveCursor();
176
177 drag_start = true;
178 drag_start_x = cursor().pos().x();
179 drag_start_y = cursor().pos().y();
180 }
181
182 emit clicked();
183
184 } else {
185
186 // handler if the user clicks with a non-primary button
187 // prevents mouse from getting stuck in "dragging" mode
188 mouseReleaseEvent(ev);
189
190 }
191 }
192
mouseMoveEvent(QMouseEvent * event)193 void LabelSlider::mouseMoveEvent(QMouseEvent* event) {
194 if (drag_start) {
195 // if we're dragging
196
197 // we don't actually start fully dragging until the cursor has moved
198 // while the mouse is down, this helps prevent accidental drags
199 drag_proc = true;
200
201 // get amount cursor moved by
202 double diff = (cursor().pos().x()-drag_start_x) + (drag_start_y-cursor().pos().y());
203
204 // ctrl + drag drags in smaller increments
205 if (event->modifiers() & Qt::ControlModifier) diff *= 0.01;
206
207 if (display_type == Percent) {
208 // we'll also need to drag in smaller increments for a percent value
209
210 diff *= 0.01;
211 }
212
213 // determine what the new value will be
214 double new_value;
215
216 if (display_type == Decibel) {
217 // we move in terms of dB for decibel display
218
219 new_value = db_to_amplitude(amplitude_to_db(internal_value) + diff);
220
221 } else {
222 // for most display types, just add the mouse difference
223
224 new_value = internal_value + diff;
225 }
226
227 // set internal value
228 SetValue(new_value);
229 emit valueChanged(internal_value);
230
231 // keep the cursor in the same location while dragging
232 cursor().setPos(drag_start_x, drag_start_y);
233 }
234 }
235
mouseReleaseEvent(QMouseEvent *)236 void LabelSlider::mouseReleaseEvent(QMouseEvent*) {
237 if (drag_start) {
238
239 // unhide cursor
240 SetDefaultCursor();
241
242 drag_start = false;
243
244 if (drag_proc) {
245 // if we just finished fully dragging
246
247 drag_proc = false;
248
249 emit valueChanged(internal_value);
250
251 } else {
252
253 ShowDialog();
254
255 }
256 }
257 }
258
SetDefaultCursor()259 void LabelSlider::SetDefaultCursor() {
260 setCursor(Qt::SizeHorCursor);
261 }
262
SetActiveCursor()263 void LabelSlider::SetActiveCursor() {
264 setCursor(Qt::BlankCursor);
265 }
266
ShowContextMenu(const QPoint & pos)267 void LabelSlider::ShowContextMenu(const QPoint &pos)
268 {
269 Menu menu(this);
270
271 menu.addAction(tr("&Edit"), this, SLOT(ShowDialog()));
272
273 menu.addSeparator();
274
275 menu.addAction(tr("&Reset to Default"), this, SLOT(ResetToDefault()));
276
277 menu.exec(mapToGlobal(pos));
278 }
279
ResetToDefault()280 void LabelSlider::ResetToDefault()
281 {
282 // if the value is not already default, and there is a default to set
283 if (!qFuzzyCompare(internal_value, default_value)
284 && !qIsNaN(default_value)) {
285
286 // set back to default
287 SetValue(default_value);
288 emit valueChanged(internal_value);
289
290 }
291 }
292
ShowDialog()293 void LabelSlider::ShowDialog()
294 {
295 // if the user didn't actually drag, and just clicked in one place,
296 // we instead present a dialog prompt for the user to enter a
297 // specific value
298
299 double d = internal_value;
300
301 if (display_type == FrameNumber) {
302
303 // ask the user to enter a timecode
304 QString s = QInputDialog::getText(
305 this,
306 tr("Set Value"),
307 tr("New value:"),
308 QLineEdit::Normal,
309 ValueToString()
310 );
311 if (s.isEmpty()) return;
312
313 // parse string timecode to a frame number
314 d = timecode_to_frame(s, olive::CurrentConfig.timecode_view, frame_rate);
315
316 } else {
317
318 // ask the user to enter a normal number value
319 bool ok;
320
321 // value to show
322 double shown_value = internal_value;
323 if (display_type == Percent) {
324 shown_value *= 100;
325 } else if (display_type == Decibel) {
326 shown_value = amplitude_to_db(shown_value);
327 }
328
329 // set correct minimum value
330 double shown_minimum_value;
331 if (min_enabled) {
332 // if this field has a minimum value set, use it
333 shown_minimum_value = min_value;
334 } else if (display_type == Decibel) {
335 // minimum decibel amount is -96db
336 shown_minimum_value = -96;
337 } else {
338 // lowest possible minimum integer
339 shown_minimum_value = INT_MIN;
340 }
341
342 // percentages are stored 0.0 - 1.0 but displayed as 0% - 100%
343 d = QInputDialog::getDouble(
344 this,
345 tr("Set Value"),
346 tr("New value:"),
347 shown_value,
348 shown_minimum_value,
349 (max_enabled) ? max_value : INT_MAX,
350 decimal_places,
351 &ok
352 );
353 if (!ok) return;
354
355 // convert shown value back to internal value
356 if (display_type == Percent) {
357 d *= 0.01;
358 } else if (display_type == Decibel) {
359 d = db_to_amplitude(d);
360 }
361 }
362
363 // if the value actually changed, trigger a change event
364 if (!qFuzzyCompare(d, internal_value)) {
365 SetValue(d);
366 emit valueChanged(internal_value);
367 }
368 }
369