1 //======================================================================
2 // MusE
3 // Linux Music Editor
4 // $Id: knob.cpp,v 1.3.2.3 2009/03/09 02:05:18 terminator356 Exp $
5 //
6 // Adapted from Qwt Lib:
7 // Copyright (C) 1997 Josef Wilgen
8 // (C) Copyright 1999 Werner Schweer (ws@seh.de)
9 // (C) Copyright 2011 Orcan Ogetbil (ogetbilo at sf.net) completely redesigned.
10 //
11 // This program is free software; you can redistribute it and/or
12 // modify it under the terms of the GNU General Public License
13 // as published by the Free Software Foundation; version 2 of
14 // the License, or (at your option) any later version.
15 //
16 // This program is distributed in the hope that it will be useful,
17 // but WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 // GNU General Public License for more details.
20 //
21 // You should have received a copy of the GNU General Public License
22 // along with this program; if not, write to the Free Software
23 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24 //
25 //=========================================================
26
27 #include <stdio.h>
28 #include "knob.h"
29 #include "muse_math.h"
30 #include "mmath.h"
31
32 #include <QPainter>
33 #include <QPalette>
34 #include <QPaintEvent>
35 #include <QResizeEvent>
36
37 // For debugging output: Uncomment the fprintf section.
38 #define DEBUG_KNOB(dev, format, args...) //fprintf(dev, format, ##args);
39
40
41 namespace MusEGui {
42
43 //---------------------------------------------------------
44 // The QwtKnob widget imitates look and behaviour of a volume knob on a radio.
45 // It contains
46 // a scale around the knob which is set up automatically or can
47 // be configured manually (see @^QwtScaleIf@).
48 // Automatic scrolling is enabled when the user presses a mouse
49 // button on the scale. For a description of signals, slots and other
50 // members, see QwtSliderBase@.
51 //---------------------------------------------------------
52
53
54 //---------------------------------------------------------
55 // Knob
56 //---------------------------------------------------------
57
Knob(QWidget * parent,const char * name)58 Knob::Knob(QWidget* parent, const char* name)
59 : SliderBase(parent, name)
60 {
61 hasScale = false;
62
63 d_borderWidth = 4;
64 d_shineWidth = 3;
65 d_totalAngle = 270.0;
66 d_scaleDist = 1;
67 d_symbol = Line;
68 d_maxScaleTicks = 11;
69 d_knobWidth = 30;
70 _faceColSel = false;
71 d_faceColor = palette().color(QPalette::Window);
72 d_rimColor = palette().mid().color();
73 d_shinyColor = palette().mid().color();
74 d_curFaceColor = d_faceColor;
75 d_altFaceColor = d_faceColor;
76 d_markerColor = palette().dark().color().darker(125);
77 d_dotWidth = 8;
78
79 l_slope = 0;
80 l_const = 100;
81
82 setMinimumSize(30,30);
83 setUpdateTime(50);
84 }
85
86 //------------------------------------------------------------
87 // QwtKnob::setTotalAngle
88 // Set the total angle by which the knob can be turned
89 //
90 // Syntax
91 // void QwtKnob::setTotalAngle(double angle)
92 //
93 // Parameters
94 // double angle -- angle in degrees.
95 //
96 // Description
97 // The default angle is 270 degrees. It is possible to specify
98 // an angle of more than 360 degrees so that the knob can be
99 // turned several times around its axis.
100 //------------------------------------------------------------
101
setTotalAngle(double angle)102 void Knob::setTotalAngle (double angle)
103 {
104 if (angle < 10.0)
105 d_totalAngle = 10.0;
106 else
107 d_totalAngle = angle;
108 d_scale.setAngleRange( -0.5 * d_totalAngle, 0.5 * d_totalAngle);
109 }
110
111 //------------------------------------------------------------
112 // Knob::setRange
113 // Set the range and step size of the knob
114 //
115 // Sets the parameters that define the shininess of the ring
116 // surrounding the knob and then proceeds by passing the
117 // parameters to the parent class' setRange() function.
118 //------------------------------------------------------------
119
setRange(double vmin,double vmax,double vstep,int pagesize)120 void Knob::setRange(double vmin, double vmax, double vstep, int pagesize)
121 {
122 // divide by zero protection. probably too cautious
123 if (! (vmin == vmax || qMax(-vmin, vmax) == 0))
124 {
125 if (vmin * vmax < 0)
126 l_slope = 80.0 / qMax(-vmin, vmax);
127 else
128 {
129 l_slope = 80.0 / (vmax - vmin);
130 l_const = 100 - l_slope * vmin;
131 }
132 }
133 SliderBase::setRange(vmin, vmax, vstep, pagesize);
134 }
135
136 //------------------------------------------------------------
137 // QwtKnob::drawKnob
138 // const QRect &r -- borders of the knob
139 //------------------------------------------------------------
140
drawKnob(QPainter * p,const QRect & r)141 void Knob::drawKnob(QPainter* p, const QRect& r)
142 {
143 const QPalette& pal = palette();
144
145 QRect aRect;
146 aRect.setRect(r.x() + d_borderWidth,
147 r.y() + d_borderWidth,
148 r.width() - 2*d_borderWidth,
149 r.height() - 2*d_borderWidth);
150
151 int width = r.width();
152 int height = r.height();
153 int size = qMin(width, height);
154
155 p->setRenderHint(QPainter::Antialiasing, true);
156
157 //
158 // draw the rim
159 //
160
161 QLinearGradient linearg(QPoint(r.x(),r.y()), QPoint(size, size));
162 linearg.setColorAt(1 - M_PI_4, d_faceColor.lighter(125));
163 linearg.setColorAt(M_PI_4, d_faceColor.darker(175));
164 p->setBrush(linearg);
165 p->setPen(Qt::NoPen);
166 p->drawEllipse(r.x(),r.y(),size,size);
167
168
169 //
170 // draw shiny surrounding
171 //
172
173 QPen pn;
174 pn.setCapStyle(Qt::FlatCap);
175
176 pn.setColor(d_shinyColor.lighter(l_const + fabs(value() * l_slope)));
177 pn.setWidth(d_shineWidth * 2);
178 p->setPen(pn);
179 p->drawArc(aRect, 0, 360 * 16);
180
181 //
182 // draw button face
183 //
184
185 QRadialGradient gradient(size/2, size/2, size-d_borderWidth, size/2-d_borderWidth, size/2-d_borderWidth);
186 gradient.setColorAt(0, d_curFaceColor.lighter(150));
187 gradient.setColorAt(1, d_curFaceColor.darker(150));
188 p->setBrush(gradient);
189 p->setPen(Qt::NoPen);
190 p->drawEllipse(aRect);
191
192 //
193 // draw marker
194 //
195 //drawMarker(p, d_angle, isEnabled() ? d_markerColor : Qt::gray);
196 drawMarker(p, d_angle, pal.currentColorGroup() == QPalette::Disabled ?
197 pal.color(QPalette::Disabled, QPalette::WindowText) : d_markerColor);
198 }
199
200 //------------------------------------------------------------
201 //.F QwtSliderBase::valueChange
202 // Notify change of value
203 //
204 //.u Parameters
205 // double x -- new value
206 //
207 //.u Description
208 // Sets the slider's value to the nearest multiple
209 // of the step size.
210 //------------------------------------------------------------
211
valueChange()212 void Knob::valueChange()
213 {
214 recalcAngle();
215 d_newVal++;
216 repaint(kRect);
217
218 // HACK
219 // In direct mode let the inherited classes (this) call these in their valueChange() methods,
220 // so that they may be called BEFORE valueChanged signal is emitted by the setPosition() call above.
221 // ScrDirect mode only happens once upon press with a modifier. After that, another mode is set.
222 // Hack: Since valueChange() is NOT called if nothing changed, in that case these are called for us by the SliderBase.
223 if(d_scrollMode == ScrDirect)
224 {
225 processSliderPressed(id());
226 emit sliderPressed(value(), id());
227 }
228
229 // Emits valueChanged if tracking enabled.
230 SliderBase::valueChange();
231 }
232
233 //------------------------------------------------------------
234 //.F QwtKnob::getValue
235 // Determine the value corresponding to a specified position
236 //
237 //.u Parameters:
238 // const QPoint &p -- point
239 //
240 //.u Description:
241 // Called by QwtSliderBase
242 //------------------------------------------------------------
243
getValue(const QPoint & p)244 double Knob::getValue(const QPoint &p)
245 {
246 double newValue;
247 double oneTurn;
248 double eqValue;
249 double arc;
250
251 const QRect& r = rect();
252
253 double dx = double((r.x() + r.width() / 2) - p.x() );
254 double dy = double((r.y() + r.height() / 2) - p.y() );
255
256 arc = atan2(-dx,dy) * 180.0 / M_PI;
257
258 newValue = 0.5 * (minValue() + maxValue())
259 + (arc + d_nTurns * 360.0) * (maxValue() - minValue())
260 / d_totalAngle;
261
262 oneTurn = fabs(maxValue() - minValue()) * 360.0 / d_totalAngle;
263 eqValue = value() + d_mouseOffset;
264
265 if (fabs(newValue - eqValue) > 0.5 * oneTurn)
266 {
267 if (newValue < eqValue)
268 newValue += oneTurn;
269 else
270 newValue -= oneTurn;
271 }
272
273 return newValue;
274
275 }
276
277 //------------------------------------------------------------
278 //
279 //.F Knob::moveValue
280 // Determine the value corresponding to a specified mouse movement.
281 //
282 //.u Syntax
283 //.f void SliderBase::moveValue(const QPoint &deltaP, bool fineMode)
284 //
285 //.u Parameters
286 //.p const QPoint &deltaP -- Change in position
287 //.p bool fineMode -- Fine mode if true, coarse mode if false.
288 //
289 //.u Description
290 // Called by SliderBase
291 // Coarse mode (the normal mode) maps pixels to values depending on range and width,
292 // such that the slider follows the mouse cursor. Fine mode maps one step() value per pixel.
293 //------------------------------------------------------------
moveValue(const QPoint & deltaP,bool)294 double Knob::moveValue(const QPoint &deltaP, bool /*fineMode*/)
295 {
296 // FIXME: To make fine mode workable, we need a way to make the adjustments 'multi-turn'.
297
298 double oneTurn;
299 double eqValue;
300
301 const QRect& r = rect();
302 const QPoint new_p = _lastMousePos + deltaP;
303
304 const int cx = r.x() + r.width() / 2;
305 const int cy = r.y() + r.height() / 2;
306
307 const double last_dx = double(cx - _lastMousePos.x());
308 const double last_dy = double(cy - _lastMousePos.y());
309 const double last_arc = atan2(-last_dx, last_dy) * 180.0 / M_PI;
310
311 const double dx = double(cx - new_p.x());
312 const double dy = double(cy - new_p.y());
313 const double arc = atan2(-dx, dy) * 180.0 / M_PI;
314
315 const double val = value(ConvertNone);
316
317 // if((fineMode || borderlessMouse()) && d_scrollMode != ScrDirect)
318 // {
319 // const double arc_diff = arc - last_arc;
320 // const double dval_diff = arc_diff * step();
321 // const double new_val = val + dval_diff;
322 // d_valAccum = new_val; // Reset.
323 // return d_valAccum;
324 // }
325
326 const double min = minValue(ConvertNone);
327 const double max = maxValue(ConvertNone);
328 const double drange = max - min;
329
330 const double last_val = 0.5 * (min + max) + (last_arc + d_nTurns * 360.0) * drange / d_totalAngle;
331 const double new_val = 0.5 * (min + max) + (arc + d_nTurns * 360.0) * drange / d_totalAngle;
332 double dval_diff = new_val - last_val;
333
334 //if(fineMode)
335 // dval_diff /= 10.0;
336
337 d_valAccum += dval_diff;
338
339 DEBUG_KNOB(stderr, "Knob::moveValue value:%.20f last_val:%.20f new_val:%.20f p dx:%d dy:%d drange:%.20f step:%.20f dval_diff:%.20f d_valAccum:%.20f\n",
340 val, last_val, new_val, deltaP.x(), deltaP.y(), drange, step(), dval_diff, d_valAccum);
341
342
343 oneTurn = fabs(drange) * 360.0 / d_totalAngle;
344 eqValue = val + d_mouseOffset;
345
346 DEBUG_KNOB(stderr, " oneTurn:%.20f eqValue:%.20f\n", oneTurn, eqValue);
347 if(fabs(d_valAccum - eqValue) > 0.5 * oneTurn)
348 {
349 if (d_valAccum < eqValue)
350 {
351 d_valAccum += oneTurn;
352 DEBUG_KNOB(stderr, " added one turn, new d_valAccum:%.20f\n", d_valAccum);
353 }
354 else
355 {
356 d_valAccum -= oneTurn;
357 DEBUG_KNOB(stderr, " subtracted one turn, new d_valAccum:%.20f\n", d_valAccum);
358 }
359 }
360
361 return d_valAccum;
362 }
363
364
365 //------------------------------------------------------------
366 //.-
367 //.F QwtKnob::setScrollMode
368 // Determine the scrolling mode and direction
369 // corresponding to a specified position
370 //
371 //.u Parameters
372 // const QPoint &p -- point in question
373 //
374 //.u Description
375 // Called by QwtSliderBase
376 //------------------------------------------------------------
getScrollMode(QPoint & p,const Qt::MouseButton & button,const Qt::KeyboardModifiers & modifiers,int & scrollMode,int & direction)377 void Knob::getScrollMode( QPoint &p, const Qt::MouseButton &button, const Qt::KeyboardModifiers& modifiers, int &scrollMode, int &direction)
378 {
379 // If modifier or button is held, jump directly to the position at first.
380 // After handling it, the caller can change to SrcMouse scroll mode.
381 if(modifiers & Qt::ControlModifier || button == Qt::MidButton)
382 {
383 scrollMode = ScrDirect;
384 direction = 0;
385 return;
386 }
387
388 int dx, dy, r;
389 double arc;
390
391 /*Qt::ButtonState but= button ;*/ // prevent compiler warning : unsused variable
392 r = kRect.width() / 2;
393
394 dx = kRect.x() + r - p.x();
395 dy = kRect.y() + r - p.y();
396
397 if ( (dx * dx) + (dy * dy) <= (r * r)) // point is inside the knob
398 {
399 scrollMode = ScrMouse;
400 direction = 0;
401 }
402 else // point lies outside
403 {
404 scrollMode = ScrTimer;
405 arc = atan2(double(-dx),double(dy)) * 180.0 / M_PI;
406 if ( arc < d_angle)
407 direction = -1;
408 else if (arc > d_angle)
409 direction = 1;
410 else
411 direction = 0;
412 }
413 return;
414 }
415
416
417
418 //------------------------------------------------------------
419 //.F QwtKnob::rangeChange
420 // Notify a change of the range
421 //
422 //.u Description
423 // Called by QwtSliderBase
424 //------------------------------------------------------------
425
rangeChange()426 void Knob::rangeChange()
427 {
428 if (!hasUserScale())
429 {
430 d_scale.setScale(minValue(), maxValue(),
431 d_maxMajor, d_maxMinor);
432 }
433 recalcAngle();
434 resize(size());
435 repaint();
436 }
437
438 //---------------------------------------------------------
439 // resizeEvent
440 //---------------------------------------------------------
441
resizeEvent(QResizeEvent * ev)442 void Knob::resizeEvent(QResizeEvent* ev)
443 {
444 SliderBase::resizeEvent(ev);
445 int width, width_2;
446
447 const QRect& r = rect();
448
449 // printf("resize %d %d %d\n", r.height(), r.width(), d_knobWidth);
450
451 // width = MusECore::qwtMin(MusECore::qwtMin(r.height(), r.width()), d_knobWidth);
452 width = MusECore::qwtMin(r.height(), r.width());
453 width_2 = width / 2;
454
455 int x = r.x() + r.width() / 2 - width_2;
456 int y = r.y() + r.height() / 2 - width_2;
457
458 kRect.setRect(x, y, width, width);
459
460 x = kRect.x() - d_scaleDist;
461 y = kRect.y() - d_scaleDist;
462 int w = width + 2 * d_scaleDist;
463
464 d_scale.setGeometry(x, y, w, ScaleDraw::Round);
465 }
466
467 //------------------------------------------------------------
468 // paintEvent
469 //------------------------------------------------------------
470
paintEvent(QPaintEvent *)471 void Knob::paintEvent(QPaintEvent*)
472 {
473 /* QPainter p(this);
474 const QRect &r = e->rect();
475
476 if ((r == kRect) && d_newVal ) { // event from valueChange()
477 if (d_newVal > 1) // lost paintEvents()?
478 drawKnob(&p, kRect);
479 else {
480 drawMarker(&p, d_oldAngle, d_curFaceColor);
481 drawMarker(&p, d_angle, d_markerColor);
482 }
483 }
484 else {
485 p.eraseRect(rect());
486 if (hasScale)
487 d_scale.draw(&p);
488 drawKnob(&p, kRect);
489 }
490 d_newVal = 0;
491 */
492
493 QPainter p(this);
494 p.setRenderHint(QPainter::Antialiasing, true);
495 if(hasScale)
496 d_scale.draw(&p, palette());
497 drawKnob(&p, kRect);
498 //drawMarker(&p, d_oldAngle, d_curFaceColor);
499 //drawMarker(&p, d_angle, d_markerColor);
500
501 d_newVal = 0;
502 }
503
504 //------------------------------------------------------------
505 //.-
506 //.F QwtKnob::drawMarker
507 // Draw the marker at the knob's front
508 //
509 //.u Parameters
510 //.p QPainter *p -- painter
511 // double arc -- angle of the marker
512 // const QColor &c -- marker color
513 //
514 //.u Syntax
515 // void QwtKnob::drawMarker(QPainter *p)
516 //
517 //------------------------------------------------------------
drawMarker(QPainter * p,double arc,const QColor & c)518 void Knob::drawMarker(QPainter *p, double arc, const QColor &c)
519 {
520
521 QPen pn;
522 int radius;
523 double rb,re;
524 double rarc;
525
526 rarc = arc * M_PI / 180.0;
527 double ca = cos(rarc);
528 double sa = - sin(rarc);
529
530 radius = kRect.width() / 2 - d_borderWidth + d_shineWidth;
531 if (radius < 3) radius = 3;
532 int ym = kRect.y() + radius + d_borderWidth - d_shineWidth;
533 int xm = kRect.x() + radius + d_borderWidth - d_shineWidth;
534
535 switch (d_symbol)
536 {
537 case Dot:
538
539 p->setBrush(c);
540 p->setPen(Qt::NoPen);
541 rb = double(MusECore::qwtMax(radius - 4 - d_dotWidth / 2, 0));
542 p->drawEllipse(xm - int(rint(sa * rb)) - d_dotWidth / 2,
543 ym - int(rint(ca * rb)) - d_dotWidth / 2,
544 d_dotWidth, d_dotWidth);
545
546 break;
547
548 case Line:
549
550 pn.setColor(c);
551 pn.setWidth(2);
552 p->setPen(pn);
553
554 rb = MusECore::qwtMax(double((radius - 1) / 3.0), 0.0);
555 re = MusECore::qwtMax(double(radius - 1), 0.0);
556
557 p->setRenderHint(QPainter::Antialiasing, true);
558 p->drawLine( xm,
559 ym,
560 xm - int(rint(sa * re)),
561 ym - int(rint(ca * re)));
562
563 break;
564 }
565
566
567 }
568
569 //------------------------------------------------------------
570 //
571 //.F QwtKnob::setKnobWidth
572 // Change the knob's width.
573 //
574 //.u Syntax
575 //.f void QwtKnob::setKnobWidth(int w)
576 //
577 //.u Parameters
578 //.p int w -- new width
579 //
580 //.u Description
581 // The specified width must be >= 5, or it will be clipped.
582 //
583 //------------------------------------------------------------
setKnobWidth(int w)584 void Knob::setKnobWidth(int w)
585 {
586 d_knobWidth = MusECore::qwtMax(w,5);
587 resize(size());
588 repaint();
589 }
590
591 //------------------------------------------------------------
592 //
593 //.F QwtKnob::setBorderWidth
594 // Set the knob's border width
595 //
596 //.u Syntax
597 //.f void QwtKnob::setBorderWidth(int bw)
598 //
599 //.u Parameters
600 //.p int bw -- new border width
601 //
602 //------------------------------------------------------------
setBorderWidth(int bw)603 void Knob::setBorderWidth(int bw)
604 {
605 d_borderWidth = MusECore::qwtMax(bw, 0);
606 resize(size());
607 repaint();
608 }
609
610 //------------------------------------------------------------
611 //.-
612 //.F QwtKnob::recalcAngle
613 // Recalculate the marker angle corresponding to the
614 // current value
615 //
616 //.u Syntax
617 //.f void QwtKnob::recalcAngle()
618 //
619 //------------------------------------------------------------
recalcAngle()620 void Knob::recalcAngle()
621 {
622 d_oldAngle = d_angle;
623
624 //
625 // calculate the angle corresponding to the value
626 //
627 if (maxValue() == minValue())
628 {
629 d_angle = 0;
630 d_nTurns = 0;
631 }
632 else
633 {
634 d_angle = (value() - 0.5 * (minValue() + maxValue()))
635 / (maxValue() - minValue()) * d_totalAngle;
636 d_nTurns = floor((d_angle + 180.0) / 360.0);
637 d_angle = d_angle - d_nTurns * 360.0;
638
639 }
640
641 }
642
643 //------------------------------------------------------------
644 // setFaceColor
645 //------------------------------------------------------------
setFaceColor(const QColor c)646 void Knob::setFaceColor(const QColor c)
647 {
648 d_faceColor = c;
649 if(!_faceColSel)
650 //update(FALSE);
651 repaint();
652 }
653
654 //------------------------------------------------------------
655 // setAltFaceColor
656 //------------------------------------------------------------
setAltFaceColor(const QColor c)657 void Knob::setAltFaceColor(const QColor c)
658 {
659 d_altFaceColor = c;
660 if(_faceColSel)
661 //update(FALSE);
662 repaint();
663 }
664
665 //------------------------------------------------------------
666 // selectFaceColor
667 //------------------------------------------------------------
selectFaceColor(bool alt)668 void Knob::selectFaceColor(bool alt)
669 {
670 _faceColSel = alt;
671 if(alt)
672 d_curFaceColor = d_altFaceColor;
673 else
674 d_curFaceColor = d_faceColor;
675 //update(FALSE);
676 repaint();
677 }
678
679 //------------------------------------------------------------
680 // setMarkerColor
681 //------------------------------------------------------------
setMarkerColor(const QColor c)682 void Knob::setMarkerColor(const QColor c)
683 {
684 d_markerColor = c;
685 //update(FALSE);
686 repaint();
687 }
688
689 } // namespace MusEGui
690