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