]> git.sesse.net Git - kdenlive/blob - src/dragvalue.cpp
Fix dragging of parameter widget with large range (for example size in geometry widget)
[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, m_maximum - m_minimum, 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         double factor = 100;
92         if (m_maximum - m_minimum > 10000) factor = 1000;
93         m_label->setStep(1);
94         m_doubleEdit->setSingleStep((m_maximum - m_minimum) / factor);
95         l->addWidget(m_doubleEdit);
96         connect(m_doubleEdit, SIGNAL(valueChanged(double)), this, SLOT(slotSetValue(double)));
97         connect(m_doubleEdit, SIGNAL(editingFinished()), this, SLOT(slotEditingFinished()));
98     }
99     
100     connect(m_label, SIGNAL(valueChanged(double,bool)), this, SLOT(setValueFromProgress(double,bool)));
101     connect(m_label, SIGNAL(resetValue()), this, SLOT(slotReset()));
102     setLayout(l);
103     if (m_intEdit) m_label->setMaximumHeight(m_intEdit->sizeHint().height());
104     else m_label->setMaximumHeight(m_doubleEdit->sizeHint().height());
105
106     m_menu = new QMenu(this);
107   
108     m_scale = new KSelectAction(i18n("Scaling"), this);
109     m_scale->addAction(i18n("Normal scale"));
110     m_scale->addAction(i18n("Pixel scale"));
111     m_scale->addAction(i18n("Nonlinear scale"));
112     m_scale->setCurrentItem(KdenliveSettings::dragvalue_mode());
113     m_menu->addAction(m_scale);
114     
115     m_directUpdate = new QAction(i18n("Direct update"), this);
116     m_directUpdate->setCheckable(true);
117     m_directUpdate->setChecked(KdenliveSettings::dragvalue_directupdate());
118     m_menu->addAction(m_directUpdate);
119     
120     QAction *reset = new QAction(KIcon("edit-undo"), i18n("Reset value"), this);
121     connect(reset, SIGNAL(triggered()), this, SLOT(slotReset()));
122     m_menu->addAction(reset);
123     
124     if (m_id > -1) {
125         QAction *timeline = new QAction(KIcon("go-jump"), i18n("Show %1 in timeline", label), this);
126         connect(timeline, SIGNAL(triggered()), this, SLOT(slotSetInTimeline()));
127         connect(m_label, SIGNAL(setInTimeline()), this, SLOT(slotSetInTimeline()));
128         m_menu->addAction(timeline);
129     }
130
131     connect(this, SIGNAL(customContextMenuRequested(const QPoint&)), this, SLOT(slotShowContextMenu(const QPoint&)));
132     connect(m_scale, SIGNAL(triggered(int)), this, SLOT(slotSetScaleMode(int)));
133     connect(m_directUpdate, SIGNAL(triggered(bool)), this, SLOT(slotSetDirectUpdate(bool)));
134 }
135
136
137 DragValue::~DragValue()
138 {
139     if (m_intEdit) delete m_intEdit;
140     if (m_doubleEdit) delete m_doubleEdit;
141     delete m_menu;
142     delete m_scale;
143     delete m_directUpdate;
144 }
145
146 int DragValue::spinSize()
147 {
148     if (m_intEdit) return m_intEdit->sizeHint().width();
149     else return m_doubleEdit->sizeHint().width();
150 }
151
152 void DragValue::setSpinSize(int width)
153 {
154     if (m_intEdit) m_intEdit->setMinimumWidth(width);
155     else m_doubleEdit->setMinimumWidth(width);
156 }
157
158 void DragValue::slotSetInTimeline()
159 {
160     emit inTimeline(m_id);
161 }
162
163 int DragValue::precision() const
164 {
165     return m_decimals;
166 }
167
168 qreal DragValue::maximum() const
169 {
170     return m_maximum;
171 }
172
173 qreal DragValue::minimum() const
174 {
175     return m_minimum;
176 }
177
178 qreal DragValue::value() const
179 {
180     if (m_intEdit) return m_intEdit->value();
181     else return m_doubleEdit->value();
182 }
183
184 void DragValue::setMaximum(qreal max)
185 {
186     m_maximum = max;
187     if (m_intEdit) m_intEdit->setRange(m_minimum, m_maximum);
188     else m_doubleEdit->setRange(m_minimum, m_maximum);
189 }
190
191 void DragValue::setMinimum(qreal min)
192 {
193     m_minimum = min;
194     if (m_intEdit) m_intEdit->setRange(m_minimum, m_maximum);
195     else m_doubleEdit->setRange(m_minimum, m_maximum);
196 }
197
198 void DragValue::setRange(qreal min, qreal max)
199 {
200     m_maximum = max;
201     m_minimum = min;
202     if (m_intEdit) m_intEdit->setRange(m_minimum, m_maximum);
203     else m_doubleEdit->setRange(m_minimum, m_maximum);
204 }
205
206 void DragValue::setPrecision(int /*precision*/)
207 {
208     //TODO: Not implemented, in case we need double value, we should replace the KIntSpinBox with KDoubleNumInput...
209     /*m_precision = precision;
210     if (precision == 0)
211         m_edit->setValidator(new QIntValidator(m_minimum, m_maximum, this));
212     else
213         m_edit->setValidator(new QDoubleValidator(m_minimum, m_maximum, precision, this));*/
214 }
215
216 void DragValue::setStep(qreal step)
217 {
218     if (m_intEdit)
219         m_intEdit->setSingleStep(step);
220     else
221         m_doubleEdit->setSingleStep(step);
222 }
223
224 void DragValue::slotReset()
225 {
226     if (m_intEdit) {
227         m_intEdit->blockSignals(true);
228         m_intEdit->setValue(m_default);
229         m_intEdit->blockSignals(false);
230         emit valueChanged((int) m_default, true);
231     }
232     else {
233         m_doubleEdit->blockSignals(true);
234         m_doubleEdit->setValue(m_default);
235         m_doubleEdit->blockSignals(false);
236         emit valueChanged(m_default, true);
237     }
238     m_label->setProgressValue((m_default - m_minimum) / (m_maximum - m_minimum) * m_label->maximum());
239 }
240
241 void DragValue::slotSetValue(int value)
242 {
243    setValue(value, KdenliveSettings::dragvalue_directupdate());
244 }
245
246 void DragValue::slotSetValue(double value)
247 {
248     setValue(value, KdenliveSettings::dragvalue_directupdate());
249 }
250
251 void DragValue::setValueFromProgress(double value, bool final)
252 {
253     value = m_minimum + value * (m_maximum - m_minimum) / m_label->maximum();
254     if (m_decimals == 0) setValue(qRound(value), final);
255     else setValue(value, final);
256 }
257
258 void DragValue::setValue(double value, bool final)
259 {
260     value = qBound(m_minimum, value, m_maximum);
261     if (m_intEdit) {
262         m_intEdit->blockSignals(true);
263         m_intEdit->setValue((int) value);
264         m_intEdit->blockSignals(false);
265         emit valueChanged((int) value, final);
266     }
267     else {
268         m_doubleEdit->blockSignals(true);
269         m_doubleEdit->setValue(value);
270         m_doubleEdit->blockSignals(false);
271         emit valueChanged(value, final);
272     }
273
274     m_label->setProgressValue((value - m_minimum) / (m_maximum - m_minimum) * m_label->maximum());
275 }
276
277 void DragValue::focusInEvent(QFocusEvent* e)
278 {
279     if (e->reason() == Qt::TabFocusReason || e->reason() == Qt::BacktabFocusReason) {
280         if (m_intEdit) m_intEdit->setFocus(e->reason());
281         else m_doubleEdit->setFocus(e->reason());
282     } else {
283         QWidget::focusInEvent(e);
284     }
285 }
286
287 void DragValue::slotEditingFinished()
288 {
289     if (m_intEdit) {
290         int value = m_intEdit->value();
291         m_intEdit->blockSignals(true);
292         m_intEdit->clearFocus();
293         m_intEdit->blockSignals(false);
294         if (!KdenliveSettings::dragvalue_directupdate()) emit valueChanged(value, true);
295     }
296     else {
297         double value = m_doubleEdit->value();
298         m_doubleEdit->blockSignals(true);
299         m_doubleEdit->clearFocus();
300         m_doubleEdit->blockSignals(false);
301         if (!KdenliveSettings::dragvalue_directupdate()) emit valueChanged(value, true);
302     }
303 }
304
305 void DragValue::slotShowContextMenu(const QPoint& pos)
306 {
307     // values might have been changed by another object of this class
308     m_scale->setCurrentItem(KdenliveSettings::dragvalue_mode());
309     m_directUpdate->setChecked(KdenliveSettings::dragvalue_directupdate());
310     m_menu->exec(mapToGlobal(pos));
311 }
312
313 void DragValue::slotSetScaleMode(int mode)
314 {
315     KdenliveSettings::setDragvalue_mode(mode);
316 }
317
318 void DragValue::slotSetDirectUpdate(bool directUpdate)
319 {
320     KdenliveSettings::setDragvalue_directupdate(directUpdate);
321 }
322
323 void DragValue::setInTimelineProperty(bool intimeline)
324 {
325     if (m_label->property("inTimeline").toBool() == intimeline) return;
326     m_label->setProperty("inTimeline", intimeline);
327     style()->unpolish(m_label);
328     style()->polish(m_label);
329     m_label->update();
330     if (m_intEdit) {
331         m_intEdit->setProperty("inTimeline", intimeline);
332         style()->unpolish(m_intEdit);
333         style()->polish(m_intEdit);
334         m_intEdit->update();
335     }
336     else {
337         m_doubleEdit->setProperty("inTimeline", intimeline);
338         style()->unpolish(m_doubleEdit);
339         style()->polish(m_doubleEdit);
340         m_doubleEdit->update();
341     }
342     
343 }
344
345 CustomLabel::CustomLabel(const QString &label, bool showSlider, int range, QWidget* parent) :
346     QProgressBar(parent),
347     m_dragMode(false),
348     m_showSlider(showSlider),
349     m_step(10.0)
350     //m_precision(pow(10, precision)),
351 {
352     setFont(KGlobalSettings::toolBarFont());
353     setFormat(" " + label);
354     setFocusPolicy(Qt::ClickFocus);
355     setCursor(Qt::PointingHandCursor);
356     setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Maximum);
357     if (showSlider) setRange(0, 1000);
358     else {
359         setRange(0, range);
360         QSize sh;
361         const QFontMetrics &fm = fontMetrics();
362         sh.setWidth(fm.width(" " + label + " "));
363         setMaximumWidth(sh.width());
364         setObjectName("dragOnly");
365     }
366     setValue(0);
367 }
368
369 void CustomLabel::mousePressEvent(QMouseEvent* e)
370 {
371     if (e->button() == Qt::LeftButton) {
372         m_dragStartPosition = m_dragLastPosition = e->pos();
373         e->accept();
374     }
375     else if (e->button() == Qt::MidButton) {
376         emit resetValue();
377         m_dragStartPosition = QPoint(-1, -1);
378     }
379     else QWidget::mousePressEvent(e);
380 }
381
382 void CustomLabel::mouseMoveEvent(QMouseEvent* e)
383 {
384     if (m_dragStartPosition != QPoint(-1, -1)) {
385         if (!m_dragMode && (e->pos() - m_dragStartPosition).manhattanLength() >= QApplication::startDragDistance()) {
386             m_dragMode = true;
387             m_dragLastPosition = e->pos();
388             e->accept();
389             return;
390         }
391         if (m_dragMode) {
392             if (KdenliveSettings::dragvalue_mode() > 0 || !m_showSlider) {
393                 int diff = e->x() - m_dragLastPosition.x();
394
395                 if (e->modifiers() == Qt::ControlModifier)
396                     diff *= 2;
397                 else if (e->modifiers() == Qt::ShiftModifier)
398                     diff /= 2;
399                 if (KdenliveSettings::dragvalue_mode() == 2)
400                     diff = (diff > 0 ? 1 : -1) * pow(diff, 2);
401
402                 double nv = value() + diff * m_step;
403                 if (nv != value()) setNewValue(nv, KdenliveSettings::dragvalue_directupdate());
404             }
405             else {
406                 double nv = minimum() + ((double) maximum() - minimum()) / width() * e->pos().x();
407                 if (nv != value()) setNewValue(nv, KdenliveSettings::dragvalue_directupdate());
408             }
409             m_dragLastPosition = e->pos();
410             e->accept();
411         }
412     }
413     else QWidget::mouseMoveEvent(e);
414 }
415
416 void CustomLabel::mouseReleaseEvent(QMouseEvent* e)
417 {
418     if (e->button() == Qt::MidButton) {
419         e->accept();
420         return;
421     }
422     if (e->modifiers() == Qt::ControlModifier) {
423         emit setInTimeline();
424         e->accept();
425         return;
426     }
427     if (m_dragMode) {
428         setNewValue(value(), true);
429         m_dragLastPosition = m_dragStartPosition;
430         e->accept();
431     }
432     else if (m_showSlider) {
433         setNewValue((double) maximum() * e->pos().x() / width(), true);
434         m_dragLastPosition = m_dragStartPosition;
435         e->accept();
436     }
437     m_dragMode = false;
438 }
439
440 void CustomLabel::wheelEvent(QWheelEvent* e)
441 {
442     if (e->delta() > 0) {
443         if (e->modifiers() == Qt::ControlModifier) slotValueInc(10);
444         else if (e->modifiers() == Qt::AltModifier) slotValueInc(0.1);
445         else slotValueInc();
446     }
447     else {
448         if (e->modifiers() == Qt::ControlModifier) slotValueDec(10);
449         else if (e->modifiers() == Qt::AltModifier) slotValueDec(0.1);
450         else slotValueDec();
451     }
452     e->accept();
453 }
454
455 void CustomLabel::slotValueInc(double factor)
456 {
457     setNewValue(value() + m_step * factor, true);
458 }
459
460 void CustomLabel::slotValueDec(double factor)
461 {
462     setNewValue(value() - m_step * factor, true);
463 }
464
465 void CustomLabel::setProgressValue(double value)
466 {
467     setValue(qRound(value));
468 }
469
470 void CustomLabel::setNewValue(double value, bool update)
471 {
472     setValue(qRound(value));
473     emit valueChanged(qRound(value), update);
474 }
475
476 void CustomLabel::setStep(double step)
477 {
478     m_step = step;
479 }
480
481 #include "dragvalue.moc"