2 Copyright (C) 2010-2011 Paul Davis
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.
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.
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.
18 $Id: motionfeedback.cc,v 1.5 2004/03/01 03:44:19 pauld Exp $
26 #include <stdio.h> /* for snprintf, grrr */
28 #include <gdk/gdkkeysyms.h>
31 #include "pbd/controllable.h"
33 #include "gtkmm2ext/motionfeedback.h"
34 #include "gtkmm2ext/keyboard.h"
35 #include "gtkmm2ext/prolooks-helpers.h"
36 #include "gtkmm2ext/gui_thread.h"
39 using namespace Gtkmm2ext
;
42 MotionFeedback::MotionFeedback (Glib::RefPtr
<Gdk::Pixbuf
> pix
,
44 boost::shared_ptr
<PBD::Controllable
> c
,
46 double step_increment
,
47 double page_increment
,
48 const char *widget_name
,
49 bool with_numeric_display
,
54 , default_value (default_val
)
55 , step_inc (step_increment
)
56 , page_inc (page_increment
)
63 char value_name
[1024];
65 print_func
= default_printer
;
69 HBox
* hpacker
= manage (new HBox
);
70 hpacker
->pack_start (pixwin
, true, true);
72 pack_start (*hpacker
, false, false);
75 if (with_numeric_display
) {
77 value_packer
= new EventBox
;
78 value_packer
->set_name ("MotionControllerValue");
79 value_packer
->show ();
80 value_packer
->set_border_width (6);
83 value
->set_justify (Gtk::JUSTIFY_RIGHT
);
86 value_packer
->add (*value
);
88 hpacker
= manage (new HBox
);
89 hpacker
->pack_start (*value_packer
, true, false);
92 pack_start (*hpacker
, false, false);
95 snprintf (value_name
, sizeof(value_name
), "%sValue", widget_name
);
96 value
->set_name (value_name
);
101 print_func (buf
, _controllable
, print_arg
);
102 value
->set_text (buf
);
106 pixwin
.set_events (Gdk::BUTTON_PRESS_MASK
|
107 Gdk::BUTTON_RELEASE_MASK
|
108 Gdk::POINTER_MOTION_MASK
|
109 Gdk::ENTER_NOTIFY_MASK
|
110 Gdk::LEAVE_NOTIFY_MASK
|
113 Gdk::KEY_RELEASE_MASK
);
115 pixwin
.set_flags (CAN_FOCUS
);
117 /* Proxy all important events on the pixwin to ourselves */
119 pixwin
.signal_button_press_event().connect(mem_fun (*this,&MotionFeedback::pixwin_button_press_event
));
120 pixwin
.signal_button_release_event().connect(mem_fun (*this,&MotionFeedback::pixwin_button_release_event
));
121 pixwin
.signal_motion_notify_event().connect(mem_fun (*this,&MotionFeedback::pixwin_motion_notify_event
));
122 pixwin
.signal_enter_notify_event().connect(mem_fun (*this,&MotionFeedback::pixwin_enter_notify_event
));
123 pixwin
.signal_leave_notify_event().connect(mem_fun (*this,&MotionFeedback::pixwin_leave_notify_event
));
124 pixwin
.signal_key_press_event().connect(mem_fun (*this,&MotionFeedback::pixwin_key_press_event
));
125 pixwin
.signal_scroll_event().connect(mem_fun (*this,&MotionFeedback::pixwin_scroll_event
));
126 pixwin
.signal_expose_event().connect(mem_fun (*this,&MotionFeedback::pixwin_expose_event
), true);
127 pixwin
.signal_size_request().connect(mem_fun (*this,&MotionFeedback::pixwin_size_request
));
130 MotionFeedback::~MotionFeedback()
137 MotionFeedback::pixwin_button_press_event (GdkEventButton
*ev
)
139 if (binding_proxy
.button_press_handler (ev
)) {
143 switch (ev
->button
) {
145 grab_is_fine
= false;
154 gtk_grab_add(GTK_WIDGET(pixwin
.gobj()));
156 grabbed_y
= ev
->y_root
;
157 grabbed_x
= ev
->x_root
;
163 MotionFeedback::pixwin_button_release_event (GdkEventButton
*ev
)
165 if (!_controllable
) {
169 switch (ev
->button
) {
171 if (pixwin
.has_grab()) {
174 (GTK_WIDGET(pixwin
.gobj()));
177 if (Keyboard::modifier_state_equals (ev
->state
, Keyboard::TertiaryModifier
)) {
178 /* shift click back to the default */
179 _controllable
->set_value (default_value
);
185 if (pixwin
.has_grab()) {
188 (GTK_WIDGET(pixwin
.gobj()));
194 return VBox::on_button_release_event (ev
);
198 MotionFeedback::pixwin_motion_notify_event (GdkEventMotion
*ev
)
200 if (!_controllable
) {
208 if (!pixwin
.has_grab()) {
209 return VBox::on_motion_notify_event (ev
);
212 multiplier
= ((ev
->state
& Keyboard::TertiaryModifier
) ? 100 : 1) *
213 ((ev
->state
& Keyboard::PrimaryModifier
) ? 10 : 1) *
214 ((ev
->state
& Keyboard::SecondaryModifier
) ? 0.1 : 1);
216 if (ev
->state
& Gdk::BUTTON1_MASK
) {
218 /* vertical control */
220 y_delta
= grabbed_y
- ev
->y_root
;
221 grabbed_y
= ev
->y_root
;
223 x_delta
= ev
->x_root
- grabbed_x
;
225 if (y_delta
== 0) return TRUE
;
227 y_delta
*= 1 + (x_delta
/100);
228 y_delta
*= multiplier
;
231 _controllable
->set_value (adjust ((grab_is_fine
? step_inc
: page_inc
) * y_delta
));
233 } else if (ev
->state
& Gdk::BUTTON2_MASK
) {
237 double x
= ev
->x
- subwidth
/2;
238 double y
= - ev
->y
+ subwidth
/2;
239 double angle
= std::atan2 (y
, x
) / M_PI
;
245 angle
= -(2.0/3.0) * (angle
- 1.25);
248 _controllable
->set_value (to_control_value (angle
));
256 MotionFeedback::pixwin_enter_notify_event (GdkEventCrossing
*ev
)
263 MotionFeedback::pixwin_leave_notify_event (GdkEventCrossing
*ev
)
265 pixwin
.unset_flags (HAS_FOCUS
);
270 MotionFeedback::pixwin_key_press_event (GdkEventKey
*ev
)
272 if (!_controllable
) {
279 multiplier
= ((ev
->state
& Keyboard::TertiaryModifier
) ? 100.0 : 1.0) *
280 ((ev
->state
& Keyboard::SecondaryModifier
) ? 10.0 : 1.0) *
281 ((ev
->state
& Keyboard::PrimaryModifier
) ? 2.0 : 1.0);
283 switch (ev
->keyval
) {
286 _controllable
->set_value (adjust (multiplier
* page_inc
));
291 _controllable
->set_value (adjust (multiplier
* page_inc
));
296 _controllable
->set_value (adjust (multiplier
* step_inc
));
301 _controllable
->set_value (adjust (multiplier
* step_inc
));
306 _controllable
->set_value (_controllable
->lower());
311 _controllable
->set_value (_controllable
->upper());
319 MotionFeedback::pixwin_expose_event (GdkEventExpose
* ev
)
321 if (!_controllable
) {
325 GdkWindow
*window
= pixwin
.get_window()->gobj();
326 double display_val
= to_display_value (_controllable
->get_value());
327 int32_t phase
= lrint (display_val
* 64.0);
329 // skip middle phase except for true middle value
331 if (type
== Rotary
&& phase
== 32) {
332 double pt
= (display_val
* 2.0) - 1.0;
339 // endless knob: skip 90deg highlights unless the value is really a multiple of 90deg
341 if (type
== Endless
&& !(phase
% 16)) {
346 double nom
= phase
/ 64.0;
347 double diff
= display_val
- nom
;
350 phase
= (phase
+ 1) % 64;
352 phase
= (phase
+ 63) % 64;
355 phase
= std::min (phase
, (int32_t) 63);
357 GtkWidget
* widget
= GTK_WIDGET(pixwin
.gobj());
358 gdk_draw_pixbuf (GDK_DRAWABLE(window
), widget
->style
->fg_gc
[0],
360 phase
* subwidth
, type
* subheight
,
361 /* center image in allocated area */
362 (get_width() - subwidth
)/2,
364 subwidth
, subheight
, GDK_RGB_DITHER_NORMAL
, 0, 0);
370 MotionFeedback::pixwin_scroll_event (GdkEventScroll
* ev
)
374 if (!_controllable
) {
378 if ((ev
->state
& (Keyboard::PrimaryModifier
|Keyboard::TertiaryModifier
)) == (Keyboard::PrimaryModifier
|Keyboard::TertiaryModifier
)) {
380 } else if (ev
->state
& Keyboard::PrimaryModifier
) {
386 switch (ev
->direction
) {
388 case GDK_SCROLL_RIGHT
:
389 _controllable
->set_value (adjust (scale
* step_inc
));
392 case GDK_SCROLL_DOWN
:
393 case GDK_SCROLL_LEFT
:
394 _controllable
->set_value (adjust (-scale
* step_inc
));
402 MotionFeedback::pixwin_size_request (GtkRequisition
* req
)
404 req
->width
= subwidth
;
405 req
->height
= subheight
;
410 MotionFeedback::controllable_value_changed ()
414 print_func (buf
, _controllable
, print_arg
);
415 value
->set_text (buf
);
418 pixwin
.queue_draw ();
422 MotionFeedback::set_controllable (boost::shared_ptr
<PBD::Controllable
> c
)
425 binding_proxy
.set_controllable (c
);
426 controller_connection
.disconnect ();
429 c
->Changed
.connect (controller_connection
, MISSING_INVALIDATOR
, boost::bind (&MotionFeedback::controllable_value_changed
, this), gui_context());
432 print_func (buf
, _controllable
, print_arg
);
433 value
->set_text (buf
);
436 pixwin
.queue_draw ();
439 boost::shared_ptr
<PBD::Controllable
>
440 MotionFeedback::controllable () const
442 return _controllable
;
446 MotionFeedback::default_printer (char buf
[32], const boost::shared_ptr
<PBD::Controllable
>& c
, void *)
449 sprintf (buf
, "%.2f", c
->get_value());
455 Glib::RefPtr
<Gdk::Pixbuf
>
456 MotionFeedback::render_pixbuf (int size
)
458 Glib::RefPtr
<Gdk::Pixbuf
> pixbuf
;
462 snprintf (path
, sizeof (path
), "/tmp/mfimg%dXXXXXX", size
);
464 if ((fd
= mkstemp (path
)) < 0) {
468 GdkColor col2
= {0,0,0,0};
469 GdkColor col3
= {0,0,0,0};
470 Gdk::Color
base ("#b9feff");
475 hsv
= prolooks_hsv_new_for_gdk_color (base
.gobj());
476 bright
= (prolooks_hsv_to_gdk_color (hsv
, &col2
), col2
);
477 prolooks_hsv_set_saturation (hsv
, 0.66);
478 prolooks_hsv_set_value (hsv
, 0.67);
479 dark
= (prolooks_hsv_to_gdk_color (hsv
, &col3
), col3
);
481 cairo_surface_t
*surface
= cairo_image_surface_create (CAIRO_FORMAT_ARGB32
, size
* 64, size
);
482 cairo_t
* cr
= cairo_create (surface
);
484 for (int i
= 0; i
< 64; ++i
) {
486 core_draw (cr
, i
, size
, 20, size
*i
, 0, &bright
, &dark
);
490 if (cairo_surface_write_to_png (surface
, path
) != CAIRO_STATUS_SUCCESS
) {
491 std::cerr
<< "could not save image set to " << path
<< std::endl
;
498 cairo_surface_destroy (surface
);
501 pixbuf
= Gdk::Pixbuf::create_from_file (path
);
502 } catch (const Gdk::PixbufError
&e
) {
503 std::cerr
<< "Caught PixbufError: " << e
.what() << std::endl
;
508 g_message("Caught ... ");
518 MotionFeedback::core_draw (cairo_t
* cr
, int phase
, double size
, double progress_width
, double xorigin
, double yorigin
,
519 const GdkColor
* bright
, const GdkColor
* dark
)
529 double start_angle_x
;
530 double start_angle_y
;
533 double progress_radius
;
534 double progress_radius_inner
;
535 double progress_radius_outer
;
536 double knob_disc_radius
;
537 cairo_pattern_t
* pattern
;
538 double progress_rim_width
;
539 cairo_pattern_t
* progress_shine
;
541 cairo_pattern_t
* knob_ripples
;
545 g_return_if_fail (cr
!= NULL
);
547 progress_radius
= 40.0;
548 progress_radius_inner
= progress_radius
- (progress_width
/ 2.0);
549 progress_radius_outer
= progress_radius
+ (progress_width
/ 2.0);
550 knob_disc_radius
= progress_radius_inner
- 5.0;
552 const double pad
= 2.0; /* line width for boundary of progress ring */
553 const double actual_width
= ((2.0 * pad
) + (2.0 * progress_radius_outer
));
554 const double scale_factor
= size
/ actual_width
;
556 /* knob center is at middle of the area bounded by (xorigin,yorigin) and (xorigin+size, yorigin+size)
557 but the coordinates will be scaled by the scale factor when cairo uses them so first
558 adjust them by the reciprocal of the scale factor.
561 xc
= (xorigin
+ (size
/ 2.0)) * (1.0/scale_factor
);
562 yc
= (yorigin
+ (size
/ 2.0)) * (1.0/scale_factor
);
564 pxs
= xorigin
* (1.0/scale_factor
);
565 pys
= yorigin
* (1.0/scale_factor
);
570 value
= (phase
* 1.0) / (65 - 1);
572 start_angle
= ((180 - 65) * G_PI
) / 180;
573 end_angle
= ((360 + 65) * G_PI
) / 180;
575 value_angle
= start_angle
+ (value
* (end_angle
- start_angle
));
576 value_x
= cos (value_angle
);
577 value_y
= sin (value_angle
);
578 start_angle_x
= cos (start_angle
);
579 start_angle_y
= sin (start_angle
);
580 end_angle_x
= cos (end_angle
);
581 end_angle_y
= sin (end_angle
);
583 cairo_scale (cr
, scale_factor
, scale_factor
);
585 pattern
= prolooks_create_gradient_str (pxs
+ 32.0, pys
+ 16.0, pxs
+ 75.0, pys
+ 16.0, "#d4c8b9", "#ae977b", 1.0, 1.0);
586 cairo_set_source (cr
, pattern
);
587 cairo_pattern_destroy (pattern
);
588 cairo_set_line_width (cr
, 2.0);
589 cairo_arc (cr
, xc
, yc
, 31.5, 0.0, 2 * G_PI
);
592 pattern
= prolooks_create_gradient_str (pxs
+ 20.0, pys
+ 20.0, pxs
+ 89.0, pys
+ 87.0, "#2f2f4c", "#090a0d", 1.0, 1.0);
593 cairo_set_source (cr
, pattern
);
594 cairo_pattern_destroy (pattern
);
595 cairo_set_line_width (cr
, progress_width
);
596 cairo_arc (cr
, xc
, yc
, progress_radius
, start_angle
, end_angle
);
599 pattern
= prolooks_create_gradient (pxs
+ 20.0, pys
+ 20.0, pxs
+ 89.0, pys
+ 87.0, bright
, dark
, 1.0, 1.0);
600 cairo_set_source (cr
, pattern
);
601 cairo_pattern_destroy (pattern
);
602 cairo_set_line_width (cr
, progress_width
);
603 cairo_arc (cr
, xc
, yc
, progress_radius
, start_angle
, value_angle
);
606 cairo_set_line_cap (cr
, CAIRO_LINE_CAP_ROUND
);
607 progress_rim_width
= 2.0;
608 cairo_set_line_width (cr
, progress_rim_width
);
609 pattern
= prolooks_create_gradient_str (pxs
+ 18.0, pys
+ 79.0, pxs
+ 35.0, pys
+ 79.0, "#dfd5c9", "#dfd5c9", 1.0, 0.0);
610 cairo_set_source (cr
, pattern
);
611 cairo_pattern_destroy (pattern
);
612 cairo_move_to (cr
, xc
+ (progress_radius_outer
* start_angle_x
), yc
+ (progress_radius_outer
* start_angle_y
));
613 cairo_line_to (cr
, xc
+ (progress_radius_inner
* start_angle_x
), yc
+ (progress_radius_inner
* start_angle_y
));
616 prolooks_set_source_color_string (cr
, "#000000", 1.0);
617 cairo_move_to (cr
, xc
+ (progress_radius_outer
* end_angle_x
), yc
+ (progress_radius_outer
* end_angle_y
));
618 cairo_line_to (cr
, xc
+ (progress_radius_inner
* end_angle_x
), yc
+ (progress_radius_inner
* end_angle_y
));
621 // pattern = prolooks_create_gradient_str (95.0, 6.0, 5.0, 44.0, "#dfd5c9", "#b0a090", 1.0, 1.0);
622 pattern
= prolooks_create_gradient_str (pxs
+ 95.0, pys
+ 6.0, pxs
+ 5.0, pys
+ 44.0, "#000000", "#000000", 1.0, 1.0);
623 cairo_set_source (cr
, pattern
);
624 cairo_pattern_destroy (pattern
);
625 cairo_arc (cr
, xc
, yc
, progress_radius_outer
, start_angle
, end_angle
);
628 cairo_set_line_cap (cr
, CAIRO_LINE_CAP_BUTT
);
629 pattern
= prolooks_create_gradient (pxs
+ 20.0, pys
+ 20.0, pxs
+ 89.0, pys
+ 87.0, bright
, dark
, 0.25, 0.25);
630 cairo_set_source (cr
, pattern
);
631 cairo_pattern_destroy (pattern
);
632 cairo_set_line_width (cr
, progress_width
);
633 cairo_arc (cr
, xc
, yc
, progress_radius
, start_angle
, value_angle
+ (G_PI
/ 180.0));
636 progress_shine
= prolooks_create_gradient_str (pxs
+ 89.0, pys
+ 73.0, pxs
+ 34.0, pys
+ 16.0, "#ffffff", "#ffffff", 0.3, 0.04);
637 cairo_pattern_add_color_stop_rgba (progress_shine
, 0.5, 1.0, 1.0, 1.0, 0.0);
639 cairo_pattern_add_color_stop_rgba (progress_shine
, 0.75, 1.0, 1.0, 1.0, 0.3);
641 cairo_pattern_add_color_stop_rgba (progress_shine
, 0.75, 1.0, 1.0, 1.0, 0.2);
643 cairo_set_source (cr
, progress_shine
);
644 cairo_set_line_width (cr
, progress_width
);
645 cairo_arc (cr
, xc
, yc
, progress_radius
, start_angle
, end_angle
);
647 cairo_pattern_destroy (progress_shine
);
649 cairo_set_line_width (cr
, 1.0);
650 cairo_set_source_rgb (cr
, 0.0, 0.0, 0.0);
651 cairo_arc (cr
, xc
, yc
, progress_radius_inner
, 0.0, 2 * G_PI
);
652 pattern
= prolooks_create_gradient_str (pxs
+ 35.0, pys
+ 31.0, pxs
+ 75.0, pys
+ 72.0, "#68625c", "#44494b", 1.0, 1.0);
653 cairo_set_source (cr
, pattern
);
654 cairo_pattern_destroy (pattern
);
656 cairo_set_source_rgb (cr
, 0.0, 0.0, 0.0);
657 cairo_arc (cr
, xc
, yc
, progress_radius_inner
, 0.0, 2 * G_PI
);
660 pattern
= prolooks_create_gradient_str (pxs
+ 42.0, pys
+ 34.0, pxs
+ 68.0, pys
+ 70.0, "#e7ecef", "#9cafb8", 1.0, 1.0);
661 cairo_set_source (cr
, pattern
);
662 cairo_pattern_destroy (pattern
);
663 cairo_arc (cr
, xc
, yc
, knob_disc_radius
, 0.0, 2 * G_PI
);
666 cairo_set_line_width (cr
, 2.0);
667 degrees
= G_PI
/ 180.0;
668 pattern
= prolooks_create_gradient_str (pxs
+ 38.0, pys
+ 34.0, pxs
+ 70.0, pys
+ 68.0, "#ffffff", "#caddf2", 0.2, 0.2);
669 cairo_set_source (cr
, pattern
);
670 cairo_pattern_destroy (pattern
);
671 cairo_move_to (cr
, xc
, yc
);
672 cairo_arc (cr
, xc
, yc
, knob_disc_radius
- 1, (-154) * degrees
, (-120) * degrees
);
673 cairo_move_to (cr
, xc
, yc
);
674 cairo_arc (cr
, xc
, yc
, knob_disc_radius
- 1, (G_PI
/ 2.0) - (60 * degrees
), (G_PI
/ 2.0) - (29 * degrees
));
677 pattern
= prolooks_create_gradient_str (pxs
+ 50.0, pys
+ 40.0, pxs
+ 62.0, pys
+ 60.0, "#a1adb6", "#47535c", 0.07, 0.15);
678 cairo_set_source (cr
, pattern
);
679 cairo_pattern_destroy (pattern
);
680 cairo_move_to (cr
, xc
, yc
);
681 cairo_arc (cr
, xc
, yc
, knob_disc_radius
- 1, (-67) * degrees
, (-27) * degrees
);
682 cairo_move_to (cr
, xc
, yc
);
683 cairo_arc (cr
, xc
, yc
, knob_disc_radius
- 1, G_PI
- (67 * degrees
), G_PI
- (27 * degrees
));
686 knob_ripples
= cairo_pattern_create_radial (xc
, yc
, 0.0, xc
, yc
, 4.0);
687 prolooks_add_color_stop_str (knob_ripples
, 0.0, "#e7ecef", 0.05);
688 prolooks_add_color_stop_str (knob_ripples
, 0.5, "#58717d", 0.05);
689 prolooks_add_color_stop_str (knob_ripples
, 0.75, "#d1d9de", 0.05);
690 prolooks_add_color_stop_str (knob_ripples
, 1.0, "#5d7682", 0.05);
691 cairo_pattern_set_extend (knob_ripples
, CAIRO_EXTEND_REPEAT
);
692 cairo_set_line_width (cr
, 0.0);
693 cairo_set_source (cr
, knob_ripples
);
694 cairo_arc (cr
, xc
, yc
, knob_disc_radius
, 0.0, 2 * G_PI
);
698 cairo_translate (cr
, xc
+ (knob_disc_radius
* value_x
), yc
+ (knob_disc_radius
* value_y
));
699 cairo_rotate (cr
, value_angle
- G_PI
);
700 pattern
= prolooks_create_gradient_str (pxs
+ 16.0, pys
+ -2.0, pxs
+ 9.0, pys
+ 13.0, "#e7ecef", "#9cafb8", 0.8, 0.8);
701 cairo_set_source (cr
, pattern
);
702 cairo_pattern_destroy (pattern
);
703 cairo_move_to (cr
, 0.0, 4.0);
704 cairo_line_to (cr
, 17.0, 4.0);
705 cairo_curve_to (cr
, 19.0, 4.0, 21.0, 2.0, 21.0, 0.0);
706 cairo_curve_to (cr
, 21.0, -2.0, 19.0, -4.0, 17.0, -4.0);
707 cairo_line_to (cr
, 0.0, -4.0);
708 cairo_close_path (cr
);
711 pattern
= prolooks_create_gradient_str (pxs
+ 9.0, pys
+ -2.0, pxs
+ 9.0, pys
+ 2.0, "#68625c", "#44494b", 1.0, 1.0);
712 cairo_set_source (cr
, pattern
);
713 cairo_pattern_destroy (pattern
);
714 cairo_move_to (cr
, 0.0, 2.0);
715 cairo_line_to (cr
, 16.0, 2.0);
716 cairo_curve_to (cr
, 17.0, 2.0, 18.0, 1.0, 18.0, 0.0);
717 cairo_curve_to (cr
, 18.0, -1.0, 17.0, -2.0, 16.0, -2.0);
718 cairo_line_to (cr
, 0.0, -2.0);
719 cairo_close_path (cr
);
723 cairo_set_line_width (cr
, 2.0);
724 pattern
= prolooks_create_gradient_str (pxs
+ 38.0, pys
+ 32.0, pxs
+ 70.0, pys
+ 67.0, "#3d3d3d", "#000000", 1.0, 1.0);
725 cairo_set_source (cr
, pattern
);
726 cairo_pattern_destroy (pattern
);
727 cairo_arc (cr
, xc
, yc
, knob_disc_radius
, 0.0, 2 * G_PI
);
730 cairo_pattern_destroy (knob_ripples
);