Thumbwheel.cpp
Go to the documentation of this file.
1 /* -*- c-basic-offset: 4 indent-tabs-mode: nil -*- vi:set ts=8 sts=4 sw=4: */
2 
3 /*
4  Sonic Visualiser
5  An audio file viewer and annotation editor.
6  Centre for Digital Music, Queen Mary, University of London.
7  This file copyright 2006 QMUL.
8 
9  This program is free software; you can redistribute it and/or
10  modify it under the terms of the GNU General Public License as
11  published by the Free Software Foundation; either version 2 of the
12  License, or (at your option) any later version. See the file
13  COPYING included with this distribution for more information.
14 */
15 
16 #include "Thumbwheel.h"
17 
18 #include "base/RangeMapper.h"
19 #include "base/Profiler.h"
20 
21 #include <QMouseEvent>
22 #include <QPaintEvent>
23 #include <QWheelEvent>
24 #include <QInputDialog>
25 #include <QPainter>
26 #include <QPainterPath>
27 #include <QMenu>
28 
29 #include "MenuTitle.h"
30 
31 #include <cmath>
32 #include <iostream>
33 
34 Thumbwheel::Thumbwheel(Qt::Orientation orientation,
35  QWidget *parent) :
36  QWidget(parent),
37  m_min(0),
38  m_max(100),
39  m_default(50),
40  m_value(50),
41  m_mappedValue(50),
42  m_noMappedUpdate(false),
43  m_rotation(0.5),
44  m_orientation(orientation),
45  m_speed(1.0),
46  m_tracking(true),
47  m_showScale(true),
48  m_clicked(false),
49  m_atDefault(true),
50  m_clickRotation(m_rotation),
51  m_showTooltip(true),
52  m_provideContextMenu(true),
53  m_lastContextMenu(nullptr),
54  m_rangeMapper(nullptr)
55 {
56  setContextMenuPolicy(Qt::CustomContextMenu);
57  connect(this, SIGNAL(customContextMenuRequested(const QPoint &)),
58  this, SLOT(contextMenuRequested(const QPoint &)));
59 }
60 
62 {
63  delete m_rangeMapper;
64  delete m_lastContextMenu;
65 }
66 
67 void
69 {
70  if (!m_provideContextMenu) {
71  return;
72  }
73 
74  delete m_lastContextMenu;
75  m_lastContextMenu = new QMenu;
76  auto m = m_lastContextMenu;
77 
78  if (m_title == "") {
79  MenuTitle::addTitle(m, tr("Thumbwheel"));
80  } else {
82  }
83 
84  m->addAction(tr("&Edit..."), this, SLOT(edit()));
85  m->addAction(tr("&Reset to Default"), this, SLOT(resetToDefault()));
86 
87  m->popup(mapToGlobal(pos));
88 }
89 
90 void
91 Thumbwheel::setRangeMapper(RangeMapper *mapper)
92 {
93  if (m_rangeMapper == mapper) return;
94 
95  if (!m_rangeMapper && mapper) {
96  connect(this, SIGNAL(valueChanged(int)),
97  this, SLOT(updateMappedValue(int)));
98  }
99 
100  delete m_rangeMapper;
101  m_rangeMapper = mapper;
102 
104 }
105 
106 void
108 {
109  m_showTooltip = show;
110  m_noMappedUpdate = true;
112  m_noMappedUpdate = false;
113 }
114 
115 void
117 {
118  m_provideContextMenu = provide;
119 }
120 
121 void
123 {
124  if (m_min == min) return;
125 
126  m_min = min;
127  if (m_max <= m_min) m_max = m_min + 1;
128  if (m_value < m_min) m_value = m_min;
129  if (m_value > m_max) m_value = m_max;
130 
131  m_rotation = float(m_value - m_min) / float(m_max - m_min);
132  update();
133 }
134 
135 int
137 {
138  return m_min;
139 }
140 
141 void
143 {
144  if (m_max == max) return;
145 
146  m_max = max;
147  if (m_min >= m_max) m_min = m_max - 1;
148  if (m_value < m_min) m_value = m_min;
149  if (m_value > m_max) m_value = m_max;
150 
151  m_rotation = float(m_value - m_min) / float(m_max - m_min);
152  update();
153 }
154 
155 int
157 {
158  return m_max;
159 }
160 
161 void
163 {
164  if (m_default == deft) return;
165 
166  m_default = deft;
167  if (m_atDefault) {
169  m_atDefault = true; // setValue unsets this
170  m_cache = QImage();
171  emit valueChanged(getValue());
172  }
173 }
174 
175 void
176 Thumbwheel::setMappedValue(double mappedValue)
177 {
178  if (m_rangeMapper) {
179  int newValue = m_rangeMapper->getPositionForValue(mappedValue);
180  bool changed = (m_mappedValue != mappedValue);
181  m_mappedValue = mappedValue;
182  m_noMappedUpdate = true;
183 // SVDEBUG << "Thumbwheel::setMappedValue(" << mappedValue << "): new value is " << newValue << " (visible " << isVisible() << ")" << endl;
184  if (newValue != getValue()) {
185  setValue(newValue);
186  changed = true;
187  m_cache = QImage();
188  }
189  if (changed) emit valueChanged(newValue);
190  m_noMappedUpdate = false;
191  } else {
192  int v = int(mappedValue);
193  if (v != getValue()) {
194  setValue(v);
195  m_cache = QImage();
196  emit valueChanged(v);
197  }
198  }
199 }
200 
201 int
203 {
204  return m_default;
205 }
206 
207 void
209 {
210 // SVDEBUG << "Thumbwheel::setValue(" << value << ") (from " << m_value
211 // << ", rotation " << m_rotation << ")" << " (visible " << isVisible() << ")" << endl;
212 
213  if (m_value != value) {
214 
215  m_atDefault = false;
216 
217  if (value < m_min) value = m_min;
218  if (value > m_max) value = m_max;
219  m_value = value;
220  }
221 
222  m_rotation = float(m_value - m_min) / float(m_max - m_min);
223  m_cache = QImage();
224 
225  if (isVisible()) {
226  update();
227  updateTitle();
228  }
229 }
230 
231 void
233 {
234  if (m_default == m_value) return;
236  m_atDefault = true;
237  m_cache = QImage();
238  emit valueChanged(getValue());
239 }
240 
241 int
243 {
244  return m_value;
245 }
246 
247 double
249 {
250  if (m_rangeMapper) {
251 // SVDEBUG << "Thumbwheel::getMappedValue(): value = " << getValue() << ", mappedValue = " << m_mappedValue << endl;
252  return m_mappedValue;
253  }
254  return getValue();
255 }
256 
257 void
259 {
260  if (!m_noMappedUpdate) {
261  if (m_rangeMapper) {
262  m_mappedValue = m_rangeMapper->getValueForPosition(value);
263  } else {
264  m_mappedValue = value;
265  }
266  }
267 
268  updateTitle();
269 }
270 
271 void
273 {
274  QString name = objectName();
275  QString unit = "";
276  QString text;
277  double mappedValue = getMappedValue();
278 
279  if (m_rangeMapper) unit = m_rangeMapper->getUnit();
280  if (name != "") {
281  text = tr("%1: %2%3").arg(name).arg(mappedValue).arg(unit);
282  } else {
283  text = tr("%2%3").arg(mappedValue).arg(unit);
284  }
285 
286  m_title = text;
287 
288  if (m_showTooltip) {
289  setToolTip(text);
290  } else {
291  setToolTip("");
292  }
293 }
294 
295 void
297 {
298  int step = int(lrintf(m_speed));
299  if (step == 0) step = 1;
300 
301  if (up) {
302  setValue(m_value + step);
303  } else {
304  setValue(m_value - step);
305  }
306 
307  emit valueChanged(getValue());
308 }
309 
310 void
312 {
313  m_speed = speed;
314 }
315 
316 float
318 {
319  return m_speed;
320 }
321 
322 void
324 {
325  m_tracking = tracking;
326 }
327 
328 bool
330 {
331  return m_tracking;
332 }
333 
334 void
336 {
337  m_showScale = showScale;
338 }
339 
340 bool
342 {
343  return m_showScale;
344 }
345 
346 void
348 {
349  emit mouseEntered();
350 }
351 
352 void
354 {
355  emit mouseLeft();
356 }
357 
358 void
360 {
361  if (e->button() == Qt::MidButton ||
362  ((e->button() == Qt::LeftButton) &&
363  (e->modifiers() & Qt::ControlModifier))) {
364  resetToDefault();
365  } else if (e->button() == Qt::LeftButton) {
366  m_clicked = true;
367  m_clickPos = e->pos();
369  }
370 }
371 
372 void
373 Thumbwheel::mouseDoubleClickEvent(QMouseEvent *mouseEvent)
374 {
376 
377  if (mouseEvent->button() != Qt::LeftButton) {
378  return;
379  }
380 
381  edit();
382 }
383 
384 void
386 {
387  bool ok = false;
388 
389  if (m_rangeMapper) {
390 
391  double min = m_rangeMapper->getValueForPosition(m_min);
392  double max = m_rangeMapper->getValueForPosition(m_max);
393 
394  if (min > max) {
395  double tmp = min;
396  min = max;
397  max = tmp;
398  }
399 
400  QString unit = m_rangeMapper->getUnit();
401 
402  QString text;
403  if (objectName() != "") {
404  if (unit != "") {
405  text = tr("New value for %1, from %2 to %3 %4:")
406  .arg(objectName()).arg(min).arg(max).arg(unit);
407  } else {
408  text = tr("New value for %1, from %2 to %3:")
409  .arg(objectName()).arg(min).arg(max);
410  }
411  } else {
412  if (unit != "") {
413  text = tr("Enter a new value from %1 to %2 %3:")
414  .arg(min).arg(max).arg(unit);
415  } else {
416  text = tr("Enter a new value from %1 to %2:")
417  .arg(min).arg(max);
418  }
419  }
420 
421  double newValue = QInputDialog::getDouble
422  (this,
423  tr("Enter new value"),
424  text,
426  min,
427  max,
428  4,
429  &ok);
430 
431  if (ok) {
432  setMappedValue(newValue);
433  }
434 
435  } else {
436 
437  int newValue = QInputDialog::getInt
438  (this,
439  tr("Enter new value"),
440  tr("Enter a new value from %1 to %2:")
441  .arg(m_min).arg(m_max),
442  getValue(), m_min, m_max, 1, &ok);
443 
444  if (ok) {
445  setValue(newValue);
446  }
447  }
448 }
449 
450 
451 void
453 {
454  if (!m_clicked) return;
455  int dist = 0;
456  if (m_orientation == Qt::Horizontal) {
457  dist = e->x() - m_clickPos.x();
458  } else {
459  dist = e->y() - m_clickPos.y();
460  }
461 
462  float rotation = m_clickRotation + (m_speed * float(dist)) / 100;
463  if (rotation < 0.f) rotation = 0.f;
464  if (rotation > 1.f) rotation = 1.f;
465  int value = int(lrintf(float(m_min) + float(m_max - m_min) * m_rotation));
466  if (value != m_value) {
467  setValue(value);
468  if (m_tracking) emit valueChanged(getValue());
469  m_rotation = rotation;
470  } else if (fabsf(rotation - m_rotation) > 0.001) {
471  m_rotation = rotation;
472  repaint();
473  }
474 }
475 
476 void
478 {
479  if (!m_clicked) return;
480  bool reallyTracking = m_tracking;
481  m_tracking = true;
482  mouseMoveEvent(e);
483  m_tracking = reallyTracking;
484  m_clicked = false;
485 }
486 
487 void
488 Thumbwheel::wheelEvent(QWheelEvent *e)
489 {
490  int delta = m_wheelCounter.count(e);
491 
492  if (delta == 0) {
493  return;
494  }
495 
496  setValue(m_value + delta);
497  emit valueChanged(getValue());
498 }
499 
500 void
502 {
503  Profiler profiler("Thumbwheel::paintEvent");
504 
505  if (!m_cache.isNull()) {
506  QPainter paint(this);
507  paint.drawImage(rect(), m_cache, m_cache.rect());
508  return;
509  }
510 
511  Profiler profiler2("Thumbwheel::paintEvent (no cache)");
512 
513  QSize imageSize = size() * devicePixelRatio();
514  m_cache = QImage(imageSize, QImage::Format_ARGB32);
515  m_cache.fill(Qt::transparent);
516 
517  int w = m_cache.width();
518  int h = m_cache.height();
519  int bw = 3; // border width
520 
521  QRect subclip;
522  if (m_orientation == Qt::Horizontal) {
523  subclip = QRect(bw, bw+1, w - bw*2, h - bw*2 - 2);
524  } else {
525  subclip = QRect(bw+1, bw, w - bw*2 - 2, h - bw*2);
526  }
527 
528  QPainter paint(&m_cache);
529  paint.setClipRect(m_cache.rect());
530  paint.fillRect(subclip, palette().window().color());
531 
532  paint.setRenderHint(QPainter::Antialiasing, true);
533 
534  double w0 = 0.5;
535  double w1 = w - 0.5;
536 
537  double h0 = 0.5;
538  double h1 = h - 0.5;
539 
540  for (int i = bw-1; i >= 0; --i) {
541 
542  int grey = (i + 1) * (256 / (bw + 1));
543  QColor fc = QColor(grey, grey, grey);
544  paint.setPen(fc);
545 
546  QPainterPath path;
547 
548  if (m_orientation == Qt::Horizontal) {
549  path.moveTo(w0 + i, h0 + i + 2);
550  path.quadTo(w/2, i * 1.25, w1 - i, h0 + i + 2);
551  path.lineTo(w1 - i, h1 - i - 2);
552  path.quadTo(w/2, h - i * 1.25, w0 + i, h1 - i - 2);
553  path.closeSubpath();
554  } else {
555  path.moveTo(w0 + i + 2, h0 + i);
556  path.quadTo(i * 1.25, h/2, w0 + i + 2, h1 - i);
557  path.lineTo(w1 - i - 2, h1 - i);
558  path.quadTo(w - i * 1.25, h/2, w1 - i - 2, h0 + i);
559  path.closeSubpath();
560  }
561 
562  paint.drawPath(path);
563  }
564 
565  paint.setClipRect(subclip);
566 
567  double radians = m_rotation * 1.5f * M_PI;
568 
569 // cerr << "value = " << m_value << ", min = " << m_min << ", max = " << m_max << ", rotation = " << rotation << endl;
570 
571  int ww = (m_orientation == Qt::Horizontal ? w : h) - bw*2; // wheel width
572 
573  // total number of notches on the entire wheel
574  int notches = 25;
575 
576  // radius of the wheel including invisible part
577  int radius = int(ww / 2 + 2);
578 
579  for (int i = 0; i < notches; ++i) {
580 
581  double a0 = (2.0 * M_PI * i) / notches + radians;
582  double a1 = a0 + M_PI / (notches * 2);
583  double a2 = (2.0 * M_PI * (i + 1)) / notches + radians;
584 
585  double depth = cos((a0 + a2) / 2);
586  if (depth < 0) continue;
587 
588  double x0 = radius * sin(a0) + ww/2;
589  double x1 = radius * sin(a1) + ww/2;
590  double x2 = radius * sin(a2) + ww/2;
591  if (x2 < 0 || x0 > ww) continue;
592 
593  if (x0 < 0) x0 = 0;
594  if (x2 > ww) x2 = ww;
595 
596  x0 += bw;
597  x1 += bw;
598  x2 += bw;
599 
600  int grey = int(lrint(120 * depth));
601 
602  QColor fc = QColor(grey, grey, grey);
603  QColor oc = palette().highlight().color();
604 
605  paint.setPen(fc);
606 
607  if (m_showScale) {
608 
609  paint.setBrush(oc);
610 
611  double prop;
612  if (i >= notches / 4) {
613  prop = double(notches - (((i - double(notches) / 4.f) * 4.f) / 3.f))
614  / notches;
615  } else {
616  prop = 0.f;
617  }
618 
619  if (m_orientation == Qt::Horizontal) {
620  paint.drawRect(QRectF(x1, h - (h - bw*2) * prop - bw,
621  x2 - x1, h * prop));
622  } else {
623  paint.drawRect(QRectF(bw, x1, (w - bw*2) * prop, x2 - x1));
624  }
625  }
626 
627  paint.setPen(fc);
628  paint.setBrush(palette().window().color());
629 
630  if (m_orientation == Qt::Horizontal) {
631  paint.drawRect(QRectF(x0, bw, x1 - x0, h - bw*2));
632  } else {
633  paint.drawRect(QRectF(bw, x0, w - bw*2, x1 - x0));
634  }
635  }
636 
637  QPainter paint2(this);
638  paint2.drawImage(rect(), m_cache, m_cache.rect());
639 }
640 
641 QSize
643 {
644  if (m_orientation == Qt::Horizontal) {
645  return QSize(80, 12);
646  } else {
647  return QSize(12, 80);
648  }
649 }
650 
void setDefaultValue(int deft)
Definition: Thumbwheel.cpp:162
int getMinimumValue() const
Definition: Thumbwheel.cpp:136
void paintEvent(QPaintEvent *e) override
Definition: Thumbwheel.cpp:501
bool m_provideContextMenu
Definition: Thumbwheel.h:104
void contextMenuRequested(const QPoint &)
Definition: Thumbwheel.cpp:68
void setRangeMapper(RangeMapper *mapper)
Definition: Thumbwheel.cpp:91
static void addTitle(QMenu *m, QString text)
Definition: MenuTitle.h:29
float m_speed
Definition: Thumbwheel.h:96
bool m_atDefault
Definition: Thumbwheel.h:100
bool getTracking() const
Definition: Thumbwheel.cpp:329
QPoint m_clickPos
Definition: Thumbwheel.h:101
float getSpeed() const
Definition: Thumbwheel.cpp:317
bool getShowScale() const
Definition: Thumbwheel.cpp:341
Thumbwheel(Qt::Orientation orientation, QWidget *parent=0)
Definition: Thumbwheel.cpp:34
bool m_showTooltip
Definition: Thumbwheel.h:103
double getMappedValue() const
Definition: Thumbwheel.cpp:248
bool m_clicked
Definition: Thumbwheel.h:99
void enterEvent(QEvent *) override
Definition: Thumbwheel.cpp:347
void setShowScale(bool show)
Definition: Thumbwheel.cpp:335
QString m_title
Definition: Thumbwheel.h:105
void mouseDoubleClickEvent(QMouseEvent *e) override
Definition: Thumbwheel.cpp:373
void setMappedValue(double mappedValue)
Definition: Thumbwheel.cpp:176
float m_rotation
Definition: Thumbwheel.h:94
bool m_tracking
Definition: Thumbwheel.h:97
void mouseReleaseEvent(QMouseEvent *e) override
Definition: Thumbwheel.cpp:477
void setMinimumValue(int min)
Definition: Thumbwheel.cpp:122
QSize sizeHint() const override
Definition: Thumbwheel.cpp:642
RangeMapper * m_rangeMapper
Definition: Thumbwheel.h:107
int getValue() const
Definition: Thumbwheel.cpp:242
void setMaximumValue(int max)
Definition: Thumbwheel.cpp:142
WheelCounter m_wheelCounter
Definition: Thumbwheel.h:109
void setTracking(bool tracking)
Definition: Thumbwheel.cpp:323
float m_clickRotation
Definition: Thumbwheel.h:102
void updateMappedValue(int value)
Definition: Thumbwheel.cpp:258
void setValue(int value)
Definition: Thumbwheel.cpp:208
virtual ~Thumbwheel()
Definition: Thumbwheel.cpp:61
bool m_showScale
Definition: Thumbwheel.h:98
void scroll(bool up)
Definition: Thumbwheel.cpp:296
void mousePressEvent(QMouseEvent *e) override
Definition: Thumbwheel.cpp:359
QMenu * m_lastContextMenu
Definition: Thumbwheel.h:106
QImage m_cache
Definition: Thumbwheel.h:108
void mouseMoveEvent(QMouseEvent *e) override
Definition: Thumbwheel.cpp:452
void setSpeed(float speed)
Definition: Thumbwheel.cpp:311
void updateTitle()
Definition: Thumbwheel.cpp:272
Qt::Orientation m_orientation
Definition: Thumbwheel.h:95
bool m_noMappedUpdate
Definition: Thumbwheel.h:93
void leaveEvent(QEvent *) override
Definition: Thumbwheel.cpp:353
int getDefaultValue() const
Definition: Thumbwheel.cpp:202
void resetToDefault()
Definition: Thumbwheel.cpp:232
int count(QWheelEvent *e)
Definition: WheelCounter.h:31
int m_default
Definition: Thumbwheel.h:90
void setProvideContextMenu(bool provide)
Definition: Thumbwheel.cpp:116
int getMaximumValue() const
Definition: Thumbwheel.cpp:156
int m_value
Definition: Thumbwheel.h:91
void wheelEvent(QWheelEvent *e) override
Definition: Thumbwheel.cpp:488
double m_mappedValue
Definition: Thumbwheel.h:92
void setShowToolTip(bool show)
Definition: Thumbwheel.cpp:107
void edit()
Definition: Thumbwheel.cpp:385
void mouseLeft()
void valueChanged(int)
void mouseEntered()