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