]> git.sesse.net Git - kdenlive/blob - src/dragvalue.cpp
Fix mouse wheel in drag value slider for parameters with a large range (> 1000).
[kdenlive] / src / dragvalue.cpp
1 /***************************************************************************
2  *   Copyright (C) 2011 by Till Theato (root@ttill.de)                     *
3  *   Copyright (C) 2011 by Jean-Baptiste Mardelle (jb@kdenlive.org)        *
4  *   This file is part of Kdenlive (www.kdenlive.org).                     *
5  *                                                                         *
6  *   Kdenlive 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 2 of the License, or     *
9  *   (at your option) any later version.                                   *
10  *                                                                         *
11  *   Kdenlive 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 Kdenlive.  If not, see <http://www.gnu.org/licenses/>.     *
18  ***************************************************************************/
19
20 #include "dragvalue.h"
21 #include "kdenlivesettings.h"
22
23 #include <math.h>
24
25 #include <QHBoxLayout>
26 #include <QToolButton>
27 #include <QLineEdit>
28 #include <QDoubleValidator>
29 #include <QIntValidator>
30 #include <QMouseEvent>
31 #include <QFocusEvent>
32 #include <QWheelEvent>
33 #include <QApplication>
34 #include <QMenu>
35 #include <QAction>
36 #include <QLabel>
37 #include <QPainter>
38 #include <QStyle>
39
40 #include <KDebug>
41 #include <KIcon>
42 #include <KLocalizedString>
43 #include <KGlobalSettings>
44
45
46 DragValue::DragValue(const QString &label, double defaultValue, int decimals, double min, double max, int id, const QString suffix, bool showSlider, QWidget* parent) :
47         QWidget(parent),
48         m_maximum(max),
49         m_minimum(min),
50         m_decimals(decimals),
51         m_default(defaultValue),
52         m_id(id),
53         m_intEdit(NULL),
54         m_doubleEdit(NULL)
55 {
56     if (showSlider) setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::MinimumExpanding);
57     else setSizePolicy(QSizePolicy::Maximum, QSizePolicy::MinimumExpanding);
58     setFocusPolicy(Qt::StrongFocus);
59     setContextMenuPolicy(Qt::CustomContextMenu);
60     
61     QHBoxLayout *l = new QHBoxLayout;
62     l->setSpacing(0);
63     l->setContentsMargins(0, 0, 0, 0);
64     m_label = new CustomLabel(label, showSlider, this);
65     l->addWidget(m_label);
66     if (decimals == 0) {
67         m_label->setMaximum(max - min);
68         m_label->setStep(1);
69         m_intEdit = new QSpinBox(this);
70         m_intEdit->setObjectName("dragBox");
71         if (!suffix.isEmpty()) m_intEdit->setSuffix(suffix);
72         m_intEdit->setKeyboardTracking(false);
73         m_intEdit->setButtonSymbols(QAbstractSpinBox::NoButtons);
74         m_intEdit->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
75         m_intEdit->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
76         m_intEdit->setRange((int) m_minimum, (int)m_maximum);
77         l->addWidget(m_intEdit);
78         connect(m_intEdit, SIGNAL(valueChanged(int)), this, SLOT(slotSetValue(int)));
79         connect(m_intEdit, SIGNAL(editingFinished()), this, SLOT(slotEditingFinished()));
80     }
81     else {
82         m_doubleEdit = new QDoubleSpinBox(this);
83         m_doubleEdit->setDecimals(decimals);
84         m_doubleEdit->setObjectName("dragBox");
85         if (!suffix.isEmpty()) m_doubleEdit->setSuffix(suffix);
86         m_doubleEdit->setKeyboardTracking(false);
87         m_doubleEdit->setButtonSymbols(QAbstractSpinBox::NoButtons);
88         m_doubleEdit->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
89         m_doubleEdit->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
90         m_doubleEdit->setRange(m_minimum, m_maximum);
91         m_doubleEdit->setSingleStep((m_maximum - m_minimum) / 100.0);
92         l->addWidget(m_doubleEdit);
93         connect(m_doubleEdit, SIGNAL(valueChanged(double)), this, SLOT(slotSetValue(double)));
94         connect(m_doubleEdit, SIGNAL(editingFinished()), this, SLOT(slotEditingFinished()));
95     }
96     
97     connect(m_label, SIGNAL(valueChanged(double,bool)), this, SLOT(setValueFromProgress(double,bool)));
98     connect(m_label, SIGNAL(resetValue()), this, SLOT(slotReset()));
99     setLayout(l);
100     if (m_intEdit) m_label->setMaximumHeight(m_intEdit->sizeHint().height());
101     else m_label->setMaximumHeight(m_doubleEdit->sizeHint().height());
102
103     m_menu = new QMenu(this);
104   
105     m_scale = new KSelectAction(i18n("Scaling"), this);
106     m_scale->addAction(i18n("Normal scale"));
107     m_scale->addAction(i18n("Pixel scale"));
108     m_scale->addAction(i18n("Nonlinear scale"));
109     m_scale->setCurrentItem(KdenliveSettings::dragvalue_mode());
110     m_menu->addAction(m_scale);
111     
112     m_directUpdate = new QAction(i18n("Direct update"), this);
113     m_directUpdate->setCheckable(true);
114     m_directUpdate->setChecked(KdenliveSettings::dragvalue_directupdate());
115     m_menu->addAction(m_directUpdate);
116     
117     QAction *reset = new QAction(KIcon("edit-undo"), i18n("Reset value"), this);
118     connect(reset, SIGNAL(triggered()), this, SLOT(slotReset()));
119     m_menu->addAction(reset);
120     
121     if (m_id > -1) {
122         QAction *timeline = new QAction(KIcon("go-jump"), i18n("Show %1 in timeline", label), this);
123         connect(timeline, SIGNAL(triggered()), this, SLOT(slotSetInTimeline()));
124         connect(m_label, SIGNAL(setInTimeline()), this, SLOT(slotSetInTimeline()));
125         m_menu->addAction(timeline);
126     }
127
128     connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(slotShowContextMenu(const QPoint&)));
129     connect(m_scale, SIGNAL(triggered(int)), this, SLOT(slotSetScaleMode(int)));
130     connect(m_directUpdate, SIGNAL(triggered(bool)), this, SLOT(slotSetDirectUpdate(bool)));
131 }
132
133
134 DragValue::~DragValue()
135 {
136     if (m_intEdit) delete m_intEdit;
137     if (m_doubleEdit) delete m_doubleEdit;
138     delete m_menu;
139     delete m_scale;
140     delete m_directUpdate;
141 }
142
143 int DragValue::spinSize()
144 {
145     if (m_intEdit) return m_intEdit->sizeHint().width();
146     else return m_doubleEdit->sizeHint().width();
147 }
148
149 void DragValue::setSpinSize(int width)
150 {
151     if (m_intEdit) m_intEdit->setMinimumWidth(width);
152     else m_doubleEdit->setMinimumWidth(width);
153 }
154
155 void DragValue::slotSetInTimeline()
156 {
157     emit inTimeline(m_id);
158 }
159
160 int DragValue::precision() const
161 {
162     return m_decimals;
163 }
164
165 qreal DragValue::maximum() const
166 {
167     return m_maximum;
168 }
169
170 qreal DragValue::minimum() const
171 {
172     return m_minimum;
173 }
174
175 qreal DragValue::value() const
176 {
177     if (m_intEdit) return m_intEdit->value();
178     else return m_doubleEdit->value();
179 }
180
181 void DragValue::setMaximum(qreal max)
182 {
183     m_maximum = max;
184     if (m_intEdit) m_intEdit->setRange(m_minimum, m_maximum);
185     else m_doubleEdit->setRange(m_minimum, m_maximum);
186 }
187
188 void DragValue::setMinimum(qreal min)
189 {
190     m_minimum = min;
191     if (m_intEdit) m_intEdit->setRange(m_minimum, m_maximum);
192     else m_doubleEdit->setRange(m_minimum, m_maximum);
193 }
194
195 void DragValue::setRange(qreal min, qreal max)
196 {
197     m_maximum = max;
198     m_minimum = min;
199     if (m_intEdit) m_intEdit->setRange(m_minimum, m_maximum);
200     else m_doubleEdit->setRange(m_minimum, m_maximum);
201 }
202
203 void DragValue::setPrecision(int /*precision*/)
204 {
205     //TODO: Not implemented, in case we need double value, we should replace the KIntSpinBox with KDoubleNumInput...
206     /*m_precision = precision;
207     if (precision == 0)
208         m_edit->setValidator(new QIntValidator(m_minimum, m_maximum, this));
209     else
210         m_edit->setValidator(new QDoubleValidator(m_minimum, m_maximum, precision, this));*/
211 }
212
213 void DragValue::setStep(qreal step)
214 {
215     if (m_intEdit)
216         m_intEdit->setSingleStep(step);
217     else
218         m_doubleEdit->setSingleStep(step);
219 }
220
221 void DragValue::slotReset()
222 {
223     if (m_intEdit) {
224         m_intEdit->blockSignals(true);
225         m_intEdit->setValue(m_default);
226         m_intEdit->blockSignals(false);
227         emit valueChanged((int) m_default, true);
228     }
229     else {
230         m_doubleEdit->blockSignals(true);
231         m_doubleEdit->setValue(m_default);
232         m_doubleEdit->blockSignals(false);
233         emit valueChanged(m_default, true);
234     }
235     m_label->setProgressValue((m_default - m_minimum) / (m_maximum - m_minimum) * m_label->maximum());
236 }
237
238 void DragValue::slotSetValue(int value)
239 {
240    setValue(value, KdenliveSettings::dragvalue_directupdate());
241 }
242
243 void DragValue::slotSetValue(double value)
244 {
245     setValue(value, KdenliveSettings::dragvalue_directupdate());
246 }
247
248 void DragValue::setValueFromProgress(double value, bool final)
249 {
250     value = m_minimum + value * (m_maximum - m_minimum) / m_label->maximum();
251     if (m_decimals == 0) setValue(qRound(value), final);
252     else setValue(value, final);
253 }
254
255 void DragValue::setValue(double value, bool final)
256 {
257     value = qBound(m_minimum, value, m_maximum);
258     if (m_intEdit) {
259         m_intEdit->blockSignals(true);
260         m_intEdit->setValue((int) value);
261         m_intEdit->blockSignals(false);
262         emit valueChanged((int) value, final);
263     }
264     else {
265         m_doubleEdit->blockSignals(true);
266         m_doubleEdit->setValue(value);
267         m_doubleEdit->blockSignals(false);
268         emit valueChanged(value, final);
269     }
270
271     m_label->setProgressValue((value - m_minimum) / (m_maximum - m_minimum) * m_label->maximum());
272 }
273
274 void DragValue::focusInEvent(QFocusEvent* e)
275 {
276     if (e->reason() == Qt::TabFocusReason || e->reason() == Qt::BacktabFocusReason) {
277         if (m_intEdit) m_intEdit->setFocus(e->reason());
278         else m_doubleEdit->setFocus(e->reason());
279     } else {
280         QWidget::focusInEvent(e);
281     }
282 }
283
284 void DragValue::slotEditingFinished()
285 {
286     if (m_intEdit) {
287         int value = m_intEdit->value();
288         m_intEdit->blockSignals(true);
289         m_intEdit->clearFocus();
290         m_intEdit->blockSignals(false);
291         if (!KdenliveSettings::dragvalue_directupdate()) emit valueChanged(value, true);
292     }
293     else {
294         double value = m_doubleEdit->value();
295         m_doubleEdit->blockSignals(true);
296         m_doubleEdit->clearFocus();
297         m_doubleEdit->blockSignals(false);
298         if (!KdenliveSettings::dragvalue_directupdate()) emit valueChanged(value, true);
299     }
300 }
301
302 void DragValue::slotShowContextMenu(const QPoint& pos)
303 {
304     // values might have been changed by another object of this class
305     m_scale->setCurrentItem(KdenliveSettings::dragvalue_mode());
306     m_directUpdate->setChecked(KdenliveSettings::dragvalue_directupdate());
307     m_menu->exec(mapToGlobal(pos));
308 }
309
310 void DragValue::slotSetScaleMode(int mode)
311 {
312     KdenliveSettings::setDragvalue_mode(mode);
313 }
314
315 void DragValue::slotSetDirectUpdate(bool directUpdate)
316 {
317     KdenliveSettings::setDragvalue_directupdate(directUpdate);
318 }
319
320 void DragValue::setInTimelineProperty(bool intimeline)
321 {
322     if (m_label->property("inTimeline").toBool() == intimeline) return;
323     m_label->setProperty("inTimeline", intimeline);
324     style()->unpolish(m_label);
325     style()->polish(m_label);
326     m_label->update();
327     if (m_intEdit) {
328         m_intEdit->setProperty("inTimeline", intimeline);
329         style()->unpolish(m_intEdit);
330         style()->polish(m_intEdit);
331         m_intEdit->update();
332     }
333     else {
334         m_doubleEdit->setProperty("inTimeline", intimeline);
335         style()->unpolish(m_doubleEdit);
336         style()->polish(m_doubleEdit);
337         m_doubleEdit->update();
338     }
339     
340 }
341
342 CustomLabel::CustomLabel(const QString &label, bool showSlider, QWidget* parent) :
343     QProgressBar(parent),
344     m_dragMode(false),
345     m_showSlider(showSlider),
346     m_step(10.0)
347     //m_precision(pow(10, precision)),
348 {
349     setFont(KGlobalSettings::toolBarFont());
350     setFormat(" " + label);
351     setFocusPolicy(Qt::ClickFocus);
352     setCursor(Qt::PointingHandCursor);
353     setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum);
354     setRange(0, 1000);
355     
356     if (!showSlider) {
357         QSize sh;
358         const QFontMetrics &fm = fontMetrics();
359         sh.setWidth(fm.width(" " + label + " "));
360         setMaximumWidth(sh.width());
361         setObjectName("dragOnly");
362     }
363     setValue(0);
364 }
365
366 void CustomLabel::mousePressEvent(QMouseEvent* e)
367 {
368     if (e->button() == Qt::LeftButton) {
369         m_dragStartPosition = m_dragLastPosition = e->pos();
370         e->accept();
371     }
372     else if (e->button() == Qt::MidButton) {
373         emit resetValue();
374         m_dragStartPosition = QPoint(-1, -1);
375     }
376     else QWidget::mousePressEvent(e);
377 }
378
379 void CustomLabel::mouseMoveEvent(QMouseEvent* e)
380 {
381     if (m_dragStartPosition != QPoint(-1, -1)) {
382         if (!m_dragMode && (e->pos() - m_dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) {
383             m_dragMode = true;
384             m_dragLastPosition = e->pos();
385             e->accept();
386             return;
387         }
388         if (m_dragMode) {
389             if (KdenliveSettings::dragvalue_mode() > 0 || !m_showSlider) {
390                 int diff = e->x() - m_dragLastPosition.x();
391
392                 if (e->modifiers() == Qt::ControlModifier)
393                     diff *= 2;
394                 else if (e->modifiers() == Qt::ShiftModifier)
395                     diff /= 2;
396                 if (KdenliveSettings::dragvalue_mode() == 2)
397                     diff = (diff > 0 ? 1 : -1) * pow(diff, 2);
398
399                 double nv = value() + diff * m_step;
400                 if (nv != value()) setNewValue(nv, KdenliveSettings::dragvalue_directupdate());
401             }
402             else {
403                 double nv = minimum() + ((double) maximum() - minimum()) / width() * e->pos().x();
404                 if (nv != value()) setNewValue(nv, KdenliveSettings::dragvalue_directupdate());
405             }
406             m_dragLastPosition = e->pos();
407             e->accept();
408         }
409     }
410     else QWidget::mouseMoveEvent(e);
411 }
412
413 void CustomLabel::mouseReleaseEvent(QMouseEvent* e)
414 {
415     if (e->button() == Qt::MidButton) {
416         e->accept();
417         return;
418     }
419     if (e->modifiers() == Qt::ControlModifier) {
420         emit setInTimeline();
421         e->accept();
422         return;
423     }
424     if (m_dragMode) {
425         setNewValue(value(), true);
426         m_dragLastPosition = m_dragStartPosition;
427         e->accept();
428     }
429     else if (m_showSlider) {
430         setNewValue((double) maximum() * e->pos().x() / width(), true);
431         m_dragLastPosition = m_dragStartPosition;
432         e->accept();
433     }
434     m_dragMode = false;
435 }
436
437 void CustomLabel::wheelEvent(QWheelEvent* e)
438 {
439     if (e->delta() > 0) {
440         if (e->modifiers() == Qt::ControlModifier) slotValueInc(10);
441         else if (e->modifiers() == Qt::AltModifier) slotValueInc(0.1);
442         else slotValueInc();
443     }
444     else {
445         if (e->modifiers() == Qt::ControlModifier) slotValueDec(10);
446         else if (e->modifiers() == Qt::AltModifier) slotValueDec(0.1);
447         else slotValueDec();
448     }
449     e->accept();
450 }
451
452 void CustomLabel::slotValueInc(double factor)
453 {
454     setNewValue(value() + m_step * factor, true);
455 }
456
457 void CustomLabel::slotValueDec(double factor)
458 {
459     setNewValue(value() - m_step * factor, true);
460 }
461
462 void CustomLabel::setProgressValue(double value)
463 {
464     setValue(qRound(value));
465 }
466
467 void CustomLabel::setNewValue(double value, bool update)
468 {
469     setValue(qRound(value));
470     emit valueChanged(qRound(value), update);
471 }
472
473 void CustomLabel::setStep(double step)
474 {
475     m_step = step;
476 }
477
478 #include "dragvalue.moc"