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