ardour
ardour_knob.cc
Go to the documentation of this file.
1 /*
2  Copyright (C) 2010 Paul Davis
3 
4  This program is free software; you can redistribute it and/or modify
5  it under the terms of the GNU General Public License as published by
6  the Free Software Foundation; either version 2 of the License, or
7  (at your option) any later version.
8 
9  This program is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  GNU General Public License for more details.
13 
14  You should have received a copy of the GNU General Public License
15  along with this program; if not, write to the Free Software
16  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 
18 */
19 
20 #include <iostream>
21 #include <cmath>
22 #include <algorithm>
23 
24 #include <pangomm/layout.h>
25 
26 #include "pbd/compose.h"
27 #include "pbd/error.h"
28 #include "pbd/stacktrace.h"
29 
30 #include "gtkmm2ext/utils.h"
31 #include "gtkmm2ext/rgb_macros.h"
32 #include "gtkmm2ext/gui_thread.h"
33 #include "gtkmm2ext/keyboard.h"
34 
35 #include "ardour/rc_configuration.h" // for widget prelight preference
36 
37 #include "ardour_knob.h"
38 #include "ardour_ui.h"
39 #include "global_signals.h"
40 #include "timers.h"
41 
42 #include "canvas/colors.h"
43 #include "canvas/utils.h"
44 
45 #include "i18n.h"
46 
47 using namespace Gtkmm2ext;
48 using namespace Gdk;
49 using namespace Gtk;
50 using namespace Glib;
51 using namespace PBD;
52 using std::max;
53 using std::min;
54 using namespace std;
55 
57 
59  : _elements (e)
60  , _hovering (false)
61  , _grabbed_x (0)
62  , _grabbed_y (0)
63  , _val (0)
64  , _normal (0)
65  , _dead_zone_delta (0)
66  , _flags (flags)
67  , _tooltip (this)
68 {
69  ARDOUR_UI_UTILS::ColorsChanged.connect (sigc::mem_fun (*this, &ArdourKnob::color_handler));
70 
71  // watch automation :(
73 }
74 
76 {
77 }
78 
79 void
80 ArdourKnob::render (cairo_t* cr, cairo_rectangle_t *)
81 {
82  cairo_pattern_t* shade_pattern;
83 
84  float width = get_width();
85  float height = get_height();
86 
87  const float scale = min(width, height);
88  const float pointer_thickness = 3.0 * (scale/80); //(if the knob is 80 pixels wide, we want a 3-pix line on it)
89 
90  const float start_angle = ((180 - 65) * G_PI) / 180;
91  const float end_angle = ((360 + 65) * G_PI) / 180;
92 
93  float zero = 0;
94  if (_flags & ArcToZero) {
95  zero = _normal;
96  }
97 
98  const float value_angle = start_angle + (_val * (end_angle - start_angle));
99  const float zero_angle = start_angle + (zero * (end_angle - start_angle));
100 
101  float value_x = cos (value_angle);
102  float value_y = sin (value_angle);
103 
104  float xc = 0.5 + width/ 2.0;
105  float yc = 0.5 + height/ 2.0;
106 
107  cairo_translate (cr, xc, yc); //after this, everything is based on the center of the knob
108 
109  //get the knob color from the theme
110  ArdourCanvas::Color knob_color = ARDOUR_UI::config()->color (string_compose ("%1", get_name()));
111 
112  float center_radius = 0.48*scale;
113  float border_width = 0.8;
114 
115  bool arc = (_elements & Arc)==Arc;
116  bool bevel = (_elements & Bevel)==Bevel;
117  bool flat = _flat_buttons;
118 
119  if ( arc ) {
120  center_radius = scale*0.33;
121 
122  float inner_progress_radius = scale*0.38;
123  float outer_progress_radius = scale*0.48;
124  float progress_width = (outer_progress_radius-inner_progress_radius);
125  float progress_radius = inner_progress_radius + progress_width/2.0;
126 
127  //dark arc background
128  cairo_set_source_rgb (cr, 0.3, 0.3, 0.3 );
129  cairo_set_line_width (cr, progress_width);
130  cairo_arc (cr, 0, 0, progress_radius, start_angle, end_angle);
131  cairo_stroke (cr);
132 
133  //look up the arc colors from the config
134  double red_start, green_start, blue_start, unused;
135  ArdourCanvas::Color arc_start_color = ARDOUR_UI::config()->color ( string_compose ("%1: arc start", get_name()));
136  ArdourCanvas::color_to_rgba( arc_start_color, red_start, green_start, blue_start, unused );
137  double red_end, green_end, blue_end;
138  ArdourCanvas::Color arc_end_color = ARDOUR_UI::config()->color ( string_compose ("%1: arc end", get_name()) );
139  ArdourCanvas::color_to_rgba( arc_end_color, red_end, green_end, blue_end, unused );
140 
141  //vary the arc color over the travel of the knob
142  float intensity = fabsf (_val - zero) / std::max(zero, (1.f - zero));
143  const float intensity_inv = 1.0 - intensity;
144  float r = intensity_inv * red_end + intensity * red_start;
145  float g = intensity_inv * green_end + intensity * green_start;
146  float b = intensity_inv * blue_end + intensity * blue_start;
147 
148  //draw the arc
149  cairo_set_source_rgb (cr, r,g,b);
150  cairo_set_line_width (cr, progress_width);
151  if (zero_angle > value_angle) {
152  cairo_arc (cr, 0, 0, progress_radius, value_angle, zero_angle);
153  } else {
154  cairo_arc (cr, 0, 0, progress_radius, zero_angle, value_angle);
155  }
156  cairo_stroke (cr);
157 
158  //shade the arc
159  if (!flat) {
160  shade_pattern = cairo_pattern_create_linear (0.0, -yc, 0.0, yc); //note we have to offset the pattern from our centerpoint
161  cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1,1,1, 0.15);
162  cairo_pattern_add_color_stop_rgba (shade_pattern, 0.5, 1,1,1, 0.0);
163  cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 1,1,1, 0.0);
164  cairo_set_source (cr, shade_pattern);
165  cairo_arc (cr, 0, 0, outer_progress_radius-1, 0, 2.0*G_PI);
166  cairo_fill (cr);
167  cairo_pattern_destroy (shade_pattern);
168  }
169 
170 #if 0 //black border
171  const float start_angle_x = cos (start_angle);
172  const float start_angle_y = sin (start_angle);
173  const float end_angle_x = cos (end_angle);
174  const float end_angle_y = sin (end_angle);
175 
176  cairo_set_source_rgb (cr, 0, 0, 0 );
177  cairo_set_line_width (cr, border_width);
178  cairo_move_to (cr, (outer_progress_radius * start_angle_x), (outer_progress_radius * start_angle_y));
179  cairo_line_to (cr, (inner_progress_radius * start_angle_x), (inner_progress_radius * start_angle_y));
180  cairo_stroke (cr);
181  cairo_move_to (cr, (outer_progress_radius * end_angle_x), (outer_progress_radius * end_angle_y));
182  cairo_line_to (cr, (inner_progress_radius * end_angle_x), (inner_progress_radius * end_angle_y));
183  cairo_stroke (cr);
184  cairo_arc (cr, 0, 0, outer_progress_radius, start_angle, end_angle);
185  cairo_stroke (cr);
186 #endif
187  }
188 
189  if (!flat) {
190  //knob shadow
191  cairo_save(cr);
192  cairo_translate(cr, pointer_thickness+1, pointer_thickness+1 );
193  cairo_set_source_rgba (cr, 0, 0, 0, 0.1 );
194  cairo_arc (cr, 0, 0, center_radius-1, 0, 2.0*G_PI);
195  cairo_fill (cr);
196  cairo_restore(cr);
197 
198  //inner circle
199  ArdourCanvas::set_source_rgba(cr, knob_color);
200  cairo_arc (cr, 0, 0, center_radius, 0, 2.0*G_PI);
201  cairo_fill (cr);
202 
203  //gradient
204  if (bevel) {
205  //knob gradient
206  shade_pattern = cairo_pattern_create_linear (0.0, -yc, 0.0, yc); //note we have to offset the gradient from our centerpoint
207  cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1,1,1, 0.2);
208  cairo_pattern_add_color_stop_rgba (shade_pattern, 0.2, 1,1,1, 0.2);
209  cairo_pattern_add_color_stop_rgba (shade_pattern, 0.8, 0,0,0, 0.2);
210  cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 0,0,0, 0.2);
211  cairo_set_source (cr, shade_pattern);
212  cairo_arc (cr, 0, 0, center_radius, 0, 2.0*G_PI);
213  cairo_fill (cr);
214  cairo_pattern_destroy (shade_pattern);
215 
216  //flat top over beveled edge
217  ArdourCanvas::set_source_rgb_a (cr, knob_color, 0.5 );
218  cairo_arc (cr, 0, 0, center_radius-pointer_thickness, 0, 2.0*G_PI);
219  cairo_fill (cr);
220  } else {
221  //radial gradient
222  shade_pattern = cairo_pattern_create_radial ( -center_radius, -center_radius, 1, -center_radius, -center_radius, center_radius*2.5 ); //note we have to offset the gradient from our centerpoint
223  cairo_pattern_add_color_stop_rgba (shade_pattern, 0.0, 1,1,1, 0.2);
224  cairo_pattern_add_color_stop_rgba (shade_pattern, 1.0, 0,0,0, 0.3);
225  cairo_set_source (cr, shade_pattern);
226  cairo_arc (cr, 0, 0, center_radius, 0, 2.0*G_PI);
227  cairo_fill (cr);
228  cairo_pattern_destroy (shade_pattern);
229  }
230 
231  } else {
232  //inner circle
233  ArdourCanvas::set_source_rgba(cr, knob_color);
234  cairo_arc (cr, 0, 0, center_radius, 0, 2.0*G_PI);
235  cairo_fill (cr);
236  }
237 
238 
239  //black knob border
240  cairo_set_line_width (cr, border_width);
241  cairo_set_source_rgba (cr, 0,0,0, 1 );
242  cairo_arc (cr, 0, 0, center_radius, 0, 2.0*G_PI);
243  cairo_stroke (cr);
244 
245  //line shadow
246  if (!flat) {
247  cairo_save(cr);
248  cairo_translate(cr, 1, 1 );
249  cairo_set_source_rgba (cr, 0,0,0,0.3 );
250  cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
251  cairo_set_line_width (cr, pointer_thickness);
252  cairo_move_to (cr, (center_radius * value_x), (center_radius * value_y));
253  cairo_line_to (cr, ((center_radius*0.4) * value_x), ((center_radius*0.4) * value_y));
254  cairo_stroke (cr);
255  cairo_restore(cr);
256  }
257 
258  //line
259  cairo_set_source_rgba (cr, 1,1,1, 1 );
260  cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
261  cairo_set_line_width (cr, pointer_thickness);
262  cairo_move_to (cr, (center_radius * value_x), (center_radius * value_y));
263  cairo_line_to (cr, ((center_radius*0.4) * value_x), ((center_radius*0.4) * value_y));
264  cairo_stroke (cr);
265 
266  //highlight if grabbed or if mouse is hovering over me
267  if (_tooltip.dragging() || (_hovering && ARDOUR_UI::config()->get_widget_prelight() ) ) {
268  cairo_set_source_rgba (cr, 1,1,1, 0.12 );
269  cairo_arc (cr, 0, 0, center_radius, 0, 2.0*G_PI);
270  cairo_fill (cr);
271  }
272 
273  cairo_identity_matrix(cr);
274 }
275 
276 void
277 ArdourKnob::on_size_request (Gtk::Requisition* req)
278 {
279  // see ardour-button VectorIcon size, use font scaling as default
280  CairoWidget::on_size_request (req); // allow to override
281 
282  // we're square
283  if (req->width < req->height) {
284  req->width = req->height;
285  }
286  if (req->height < req->width) {
287  req->height = req->width;
288  }
289 }
290 
291 bool
292 ArdourKnob::on_scroll_event (GdkEventScroll* ev)
293 {
294  /* mouse wheel */
295 
296  float scale = 0.05; //by default, we step in 1/20ths of the knob travel
297  if (ev->state & Keyboard::GainFineScaleModifier) {
298  if (ev->state & Keyboard::GainExtraFineScaleModifier) {
299  scale *= 0.01;
300  } else {
301  scale *= 0.10;
302  }
303  }
304 
306  if (c) {
307  float val = c->get_interface();
308 
309  if ( ev->direction == GDK_SCROLL_UP )
310  val += scale;
311  else
312  val -= scale;
313 
314  c->set_interface(val);
315  }
316 
317  return true;
318 }
319 
320 bool
322 {
323  if (!(ev->state & Gdk::BUTTON1_MASK)) {
324  return true;
325  }
326 
328  if (!c) {
329  return true;
330  }
331 
332 
333  //scale the adjustment based on keyboard modifiers & GUI size
334  const float ui_scale = max (1.f, ARDOUR_UI::ui_scale);
335  float scale = 0.0025 / ui_scale;
336 
337  if (ev->state & Keyboard::GainFineScaleModifier) {
338  if (ev->state & Keyboard::GainExtraFineScaleModifier) {
339  scale *= 0.01;
340  } else {
341  scale *= 0.10;
342  }
343  }
344 
345  //calculate the travel of the mouse
346  int delta = (_grabbed_y - ev->y) - (_grabbed_x - ev->x);
347  if (delta == 0) {
348  return true;
349  }
350 
351  _grabbed_x = ev->x;
352  _grabbed_y = ev->y;
353  float val = c->get_interface();
354 
355  if (_flags & Detent) {
356  const float px_deadzone = 42.f * ui_scale;
357 
358  if ((val - _normal) * (val - _normal + delta * scale) < 0) {
359  /* detent */
360  const int tozero = (_normal - val) * scale;
361  int remain = delta - tozero;
362  if (abs (remain) > px_deadzone) {
363  /* slow down passing the default value */
364  remain += (remain > 0) ? px_deadzone * -.5 : px_deadzone * .5;
365  delta = tozero + remain;
366  _dead_zone_delta = 0;
367  } else {
368  c->set_value (c->normal());
369  _dead_zone_delta = remain / px_deadzone;
370  return true;
371  }
372  }
373 
374  if (fabsf (rintf((val - _normal) / scale) + _dead_zone_delta) < 1) {
375  c->set_value (c->normal());
376  _dead_zone_delta += delta / px_deadzone;
377  return true;
378  }
379 
380  _dead_zone_delta = 0;
381  }
382 
383  val += delta * scale;
384  c->set_interface(val);
385 
386  return true;
387 }
388 
389 bool
391 {
392  _grabbed_x = ev->x;
393  _grabbed_y = ev->y;
394  _dead_zone_delta = 0;
395 
396  if (ev->type != GDK_BUTTON_PRESS) {
397  if (_grabbed) {
398  remove_modal_grab();
399  gdk_pointer_ungrab (GDK_CURRENT_TIME);
400  }
401  return true;
402  }
403 
405  return true;
406  }
407 
408  if (ev->button != 1 && ev->button != 2) {
409  return false;
410  }
411 
414  add_modal_grab();
415  _grabbed = true;
416  gdk_pointer_grab(ev->window,false,
417  GdkEventMask( Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK |Gdk::BUTTON_RELEASE_MASK),
418  NULL,NULL,ev->time);
419  return true;
420 }
421 
422 bool
424 {
426  _grabbed = false;
427  remove_modal_grab();
428  gdk_pointer_ungrab (GDK_CURRENT_TIME);
429 
430  if ( (_grabbed_y == ev->y && _grabbed_x == ev->x) && Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) { //no move, shift-click sets to default
432  if (!c) return false;
433  c->set_value (c->normal());
434  return true;
435  }
436 
438 
439  return true;
440 }
441 
442 void
444 {
445  set_dirty ();
446 }
447 
448 void
449 ArdourKnob::on_size_allocate (Allocation& alloc)
450 {
452 }
453 
454 void
456 {
457  watch_connection.disconnect (); //stop watching the old controllable
458 
459  if (!c) return;
460 
462 
463  c->Changed.connect (watch_connection, invalidator(*this), boost::bind (&ArdourKnob::controllable_changed, this), gui_context());
464 
466 
468 }
469 
470 void
472 {
474  if (!c) return;
475 
476  float val = c->get_interface();
477  val = min( max(0.0f, val), 1.0f); // clamp
478 
479  if (val == _val) {
480  return;
481  }
482 
483  _val = val;
484  if (!_tooltip_prefix.empty()) {
486  }
487  set_dirty();
488 }
489 
490 void
491 ArdourKnob::on_style_changed (const RefPtr<Gtk::Style>&)
492 {
493  set_dirty ();
494 }
495 
496 void
498 {
499  set_dirty ();
500 }
501 
502 
503 void
504 ArdourKnob::set_active_state (Gtkmm2ext::ActiveState s)
505 {
506  if (_active_state != s)
508 }
509 
510 void
511 ArdourKnob::set_visual_state (Gtkmm2ext::VisualState s)
512 {
513  if (_visual_state != s)
515 }
516 
517 
518 bool
519 ArdourKnob::on_focus_in_event (GdkEventFocus* ev)
520 {
521  set_dirty ();
522  return CairoWidget::on_focus_in_event (ev);
523 }
524 
525 bool
527 {
528  set_dirty ();
529  return CairoWidget::on_focus_out_event (ev);
530 }
531 
532 bool
533 ArdourKnob::on_enter_notify_event (GdkEventCrossing* ev)
534 {
535  _hovering = true;
536 
537  set_dirty ();
538 
539  return CairoWidget::on_enter_notify_event (ev);
540 }
541 
542 bool
543 ArdourKnob::on_leave_notify_event (GdkEventCrossing* ev)
544 {
545  _hovering = false;
546 
547  set_dirty ();
548 
549  return CairoWidget::on_leave_notify_event (ev);
550 }
551 
552 void
554 {
555  _elements = e;
556 }
557 
558 void
560 {
562 }
563 
564 
566  : PersistentTooltip (w, 3)
567  , _dragging (false)
568 {
569 }
570 
571 void
573 {
574  _dragging = true;
575 }
576 
577 void
579 {
580  _dragging = false;
581 }
582 
583 bool
585 {
586  return _dragging;
587 }
virtual float get_interface() const
Definition: controllable.h:77
bool on_focus_in_event(GdkEventFocus *)
Definition: ardour_knob.cc:519
KnobPersistentTooltip _tooltip
Definition: ardour_knob.h:124
ArdourCanvas::Color color(const std::string &, bool *failed=0) const
Definition: ui_config.cc:567
void set_elements(Element)
Definition: ardour_knob.cc:553
void on_style_changed(const Glib::RefPtr< Gtk::Style > &)
Definition: ardour_knob.cc:491
boost::shared_ptr< PBD::Controllable > get_controllable() const
Definition: binding_proxy.h:48
float _val
Definition: ardour_knob.h:113
float _grabbed_x
Definition: ardour_knob.h:110
bool on_scroll_event(GdkEventScroll *ev)
Definition: ardour_knob.cc:292
virtual void set_interface(float percent)
Definition: controllable.h:78
Definition: ardour_ui.h:130
void add_elements(Element)
Definition: ardour_knob.cc:559
bool dragging() const
Definition: ardour_knob.cc:584
void set_controllable(boost::shared_ptr< PBD::Controllable >)
tuple f
Definition: signals.py:35
Definition: Beats.hpp:239
virtual void set_active_state(Gtkmm2ext::ActiveState)
float _normal
Definition: ardour_knob.h:114
virtual std::string get_user_string() const
Definition: controllable.h:83
void render(cairo_t *, cairo_rectangle_t *)
Definition: ardour_knob.cc:80
virtual ~ArdourKnob()
Definition: ardour_knob.cc:75
float _grabbed_y
Definition: ardour_knob.h:111
static Element default_elements
Definition: ardour_knob.h:75
void unset_active_state()
Definition: cairo_widget.h:50
std::string _tooltip_prefix
Definition: ardour_knob.h:123
#define invalidator(x)
Definition: gui_thread.h:40
void on_size_request(Gtk::Requisition *req)
Definition: ardour_knob.cc:277
bool on_focus_out_event(GdkEventFocus *)
Definition: ardour_knob.cc:526
ExplicitActive
Definition: widget_state.h:13
void set_controllable(boost::shared_ptr< PBD::Controllable > c)
Definition: ardour_knob.cc:455
virtual double internal_to_interface(double i) const
Definition: controllable.h:71
KnobPersistentTooltip(Gtk::Widget *w)
Definition: ardour_knob.cc:565
void color_handler()
Definition: ardour_knob.cc:443
float _dead_zone_delta
Definition: ardour_knob.h:115
#define gui_context()
Definition: gui_thread.h:36
virtual double normal() const
Definition: controllable.h:109
void set_dirty()
bool on_leave_notify_event(GdkEventCrossing *)
Definition: ardour_knob.cc:543
Element _elements
Definition: ardour_knob.h:105
void set_active_state(Gtkmm2ext::ActiveState)
Definition: ardour_knob.cc:504
bool button_press_handler(GdkEventButton *)
Gtkmm2ext::VisualState _visual_state
Definition: cairo_widget.h:114
void set_visual_state(Gtkmm2ext::VisualState)
Definition: ardour_knob.cc:511
void on_size_allocate(Gtk::Allocation &)
Definition: ardour_knob.cc:449
bool on_button_press_event(GdkEventButton *)
Definition: ardour_knob.cc:390
void controllable_changed()
Definition: ardour_knob.cc:471
virtual void set_value(double)=0
static UIConfiguration * config()
Definition: ardour_ui.h:188
PBD::Signal0< void > Changed
Definition: controllable.h:94
ArdourKnob(Element e=default_elements, Flags flags=NoFlags)
Definition: ardour_knob.cc:58
sigc::signal< void > ColorsChanged
PBD::ScopedConnection watch_connection
Definition: ardour_knob.h:101
Definition: debug.h:30
static bool _flat_buttons
Definition: cairo_widget.h:117
virtual void set_visual_state(Gtkmm2ext::VisualState)
bool _hovering
Definition: ardour_knob.h:109
Flags _flags
Definition: ardour_knob.h:117
bool on_button_release_event(GdkEventButton *)
Definition: ardour_knob.cc:423
BindingProxy binding_proxy
Definition: ardour_knob.h:107
void on_name_changed()
Definition: ardour_knob.cc:497
static float ui_scale
Definition: ardour_ui.h:189
bool on_enter_notify_event(GdkEventCrossing *)
Definition: ardour_knob.cc:533
sigc::connection rapid_connect(const sigc::slot< void > &slot)
Definition: timers.cc:183
std::string string_compose(const std::string &fmt, const T1 &o1)
Definition: compose.h:208
bool on_motion_notify_event(GdkEventMotion *ev)
Definition: ardour_knob.cc:321
Gtkmm2ext::ActiveState _active_state
Definition: cairo_widget.h:111
void on_size_allocate(Gtk::Allocation &)