2 Copyright (C) 1998-99 Paul Barton-Davis
3 This program is free software; you can redistribute it and/or modify
4 it under the terms of the GNU General Public License as published by
5 the Free Software Foundation; either version 2 of the License, or
6 (at your option) any later version.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17 $Id: motionfeedback.cc,v 1.5 2004/03/01 03:44:19 pauld Exp $
23 #include <stdio.h> /* for snprintf, grrr */
25 #include <gdk/gdkkeysyms.h>
28 #include "gtkmm2ext/motionfeedback.h"
29 #include "gtkmm2ext/keyboard.h"
30 #include "gtkmm2ext/prolooks-helpers.h"
33 using namespace Gtkmm2ext;
36 MotionFeedback::MotionFeedback (Glib::RefPtr<Gdk::Pixbuf> pix,
38 const char *widget_name,
40 bool with_numeric_display, int subw, int subh)
48 char value_name[1024];
51 i_own_my_adjustment = true;
52 set_adjustment (new Adjustment (0, 0, 10000, 1, 10, 0));
54 i_own_my_adjustment = false;
58 default_value = adjustment->get_value();
60 HBox* hpacker = manage (new HBox);
61 hpacker->pack_start (pixwin, true, false);
63 pack_start (*hpacker, false, false);
66 if (with_numeric_display) {
68 value_packer = new HBox;
69 value = new SpinButton (*adjustment);
70 value_packer->pack_start (*value, false, false);
73 value->set_digits (abs ((int) ceil (log10 (step_inc))));
76 pack_start (*value_packer, false, false);
79 snprintf (value_name, sizeof(value_name), "%sValue", widget_name);
80 value->set_name (value_name);
86 adjustment->signal_value_changed().connect (mem_fun (*this, &MotionFeedback::adjustment_changed));
88 pixwin.set_events (Gdk::BUTTON_PRESS_MASK|
89 Gdk::BUTTON_RELEASE_MASK|
90 Gdk::POINTER_MOTION_MASK|
91 Gdk::ENTER_NOTIFY_MASK|
92 Gdk::LEAVE_NOTIFY_MASK|
95 Gdk::KEY_RELEASE_MASK);
97 pixwin.set_flags (CAN_FOCUS);
99 /* Proxy all important events on the pixwin to ourselves */
101 pixwin.signal_button_press_event().connect(mem_fun (*this,&MotionFeedback::pixwin_button_press_event));
102 pixwin.signal_button_release_event().connect(mem_fun (*this,&MotionFeedback::pixwin_button_release_event));
103 pixwin.signal_motion_notify_event().connect(mem_fun (*this,&MotionFeedback::pixwin_motion_notify_event));
104 pixwin.signal_enter_notify_event().connect(mem_fun (*this,&MotionFeedback::pixwin_enter_notify_event));
105 pixwin.signal_leave_notify_event().connect(mem_fun (*this,&MotionFeedback::pixwin_leave_notify_event));
106 pixwin.signal_key_press_event().connect(mem_fun (*this,&MotionFeedback::pixwin_key_press_event));
107 pixwin.signal_scroll_event().connect(mem_fun (*this,&MotionFeedback::pixwin_scroll_event));
108 pixwin.signal_expose_event().connect(mem_fun (*this,&MotionFeedback::pixwin_expose_event), true);
109 pixwin.signal_size_request().connect(mem_fun (*this,&MotionFeedback::pixwin_size_request));
110 pixwin.signal_realize().connect(mem_fun (*this,&MotionFeedback::pixwin_realized));
113 MotionFeedback::~MotionFeedback()
116 if (i_own_my_adjustment) {
125 MotionFeedback::set_adjustment (Adjustment *adj)
130 value->set_adjustment (*adj);
133 _lower = adj->get_lower();
134 _upper = adj->get_upper();
135 _range = _upper - _lower;
136 step_inc = adj->get_step_increment();
137 page_inc = adj->get_page_increment();
141 MotionFeedback::pixwin_button_press_event (GdkEventButton *ev)
143 if (binding_proxy.button_press_handler (ev)) {
147 switch (ev->button) {
149 return FALSE; /* XXX why ? */
152 grab_is_fine = false;
159 gtk_grab_add(GTK_WIDGET(pixwin.gobj()));
160 grabbed_y = ev->y_root;
161 grabbed_x = ev->x_root;
163 /* XXX should we return TRUE ? */
169 MotionFeedback::pixwin_button_release_event (GdkEventButton *ev)
171 switch (ev->button) {
173 if (pixwin.has_grab()) {
176 (GTK_WIDGET(pixwin.gobj()));
179 if (Keyboard::modifier_state_equals (ev->state, Keyboard::TertiaryModifier)) {
180 /* shift click back to the default */
181 adjustment->set_value (default_value);
187 if (pixwin.has_grab()) {
190 (GTK_WIDGET(pixwin.gobj()));
196 return VBox::on_button_release_event (ev);
200 MotionFeedback::pixwin_motion_notify_event (GdkEventMotion *ev)
206 if (!pixwin.has_grab()) {
207 return VBox::on_motion_notify_event (ev);
210 multiplier = ((ev->state & Keyboard::TertiaryModifier) ? 100 : 1) *
211 ((ev->state & Keyboard::SecondaryModifier) ? 10 : 1) *
212 ((ev->state & Keyboard::PrimaryModifier) ? 2 : 1);
215 if (ev->state & Gdk::BUTTON1_MASK) {
217 y_delta = grabbed_y - ev->y_root;
218 grabbed_y = ev->y_root;
220 x_delta = ev->x_root - grabbed_x;
222 if (y_delta == 0) return TRUE;
224 y_delta *= 1 + (x_delta/100);
225 y_delta *= multiplier;
228 adjustment->set_value (adjustment->get_value() +
229 ((grab_is_fine ? step_inc : page_inc) * y_delta));
231 } else if (ev->state & Gdk::BUTTON3_MASK) {
233 double range = adjustment->get_upper() - adjustment->get_lower();
234 double x = ev->x - subwidth/2;
235 double y = - ev->y + subwidth/2;
236 double angle = std::atan2 (y, x) / M_PI;
242 angle = -(2.0/3.0) * (angle - 1.25);
245 angle += adjustment->get_lower();
247 adjustment->set_value (angle);
255 MotionFeedback::pixwin_enter_notify_event (GdkEventCrossing *ev)
262 MotionFeedback::pixwin_leave_notify_event (GdkEventCrossing *ev)
264 pixwin.unset_flags (HAS_FOCUS);
269 MotionFeedback::pixwin_key_press_event (GdkEventKey *ev)
275 multiplier = ((ev->state & Keyboard::TertiaryModifier) ? 100 : 1) *
276 ((ev->state & Keyboard::SecondaryModifier) ? 10 : 1) *
277 ((ev->state & Keyboard::PrimaryModifier) ? 2 : 1);
279 switch (ev->keyval) {
282 curval = adjustment->get_value();
283 adjustment->set_value (curval + (multiplier * page_inc));
288 curval = adjustment->get_value();
289 adjustment->set_value (curval - (multiplier * page_inc));
294 curval = adjustment->get_value();
295 adjustment->set_value (curval + (multiplier * step_inc));
300 curval = adjustment->get_value();
301 adjustment->set_value (curval - (multiplier * step_inc));
306 adjustment->set_value (_lower);
311 adjustment->set_value (_upper);
319 MotionFeedback::adjustment_changed ()
321 pixwin.queue_draw ();
325 MotionFeedback::core_draw (cairo_t* cr, int phase, double radius, double x, double y)
337 double start_angle_x;
338 double start_angle_y;
341 double progress_width;
342 double progress_radius;
343 double progress_radius_inner;
344 double progress_radius_outer;
345 double knob_disc_radius;
346 cairo_pattern_t* pattern;
347 double progress_rim_width;
348 cairo_pattern_t* progress_shine;
350 cairo_pattern_t* knob_ripples;
352 g_return_if_fail (cr != NULL);
354 cairo_set_source_rgba (cr, 0.75, 0.75, 0.75, (double) 0);
355 cairo_rectangle (cr, (double) 0, (double) 0, subwidth, subheight);
365 value = (phase * 1.0) / (65 - 1);
367 start_angle = ((180 - 65) * G_PI) / 180;
368 end_angle = ((360 + 65) * G_PI) / 180;
370 value_angle = start_angle + (value * (end_angle - start_angle));
371 value_x = cos (value_angle);
372 value_y = sin (value_angle);
373 start_angle_x = cos (start_angle);
374 start_angle_y = sin (start_angle);
375 end_angle_x = cos (end_angle);
376 end_angle_y = sin (end_angle);
378 //cairo_translate (cr, x, (double) 0);
379 cairo_scale (cr, (2.0 * radius) / width, (2.0 * radius) / height);
380 //cairo_translate (cr, -xc, (double) 0);
382 pattern = prolooks_create_gradient_str ((double) 32, (double) 16, (double) 75, (double) 16, "#d4c8b9", "#ae977b", 1.0, 1.0);
383 cairo_set_source (cr, pattern);
384 cairo_pattern_destroy (pattern);
385 cairo_set_line_width (cr, 2.0);
386 cairo_arc (cr, xc, yc, 31.5, 0.0, 2 * G_PI);
389 progress_width = 10.0;
390 progress_radius = 40.0;
391 progress_radius_inner = progress_radius - (progress_width / 2.0);
392 progress_radius_outer = progress_radius + (progress_width / 2.0);
393 knob_disc_radius = progress_radius_inner - 5.0;
395 pattern = prolooks_create_gradient_str ((double) 20, (double) 20, (double) 89, (double) 87, "#2f2f4c", "#090a0d", 1.0, 1.0);
396 cairo_set_source (cr, pattern);
397 cairo_pattern_destroy (pattern);
398 cairo_set_line_width (cr, progress_width);
399 cairo_arc (cr, xc, yc, progress_radius, start_angle, end_angle);
402 pattern = prolooks_create_gradient ((double) 20, (double) 20, (double) 89, (double) 87, &lamp_bright, &lamp_dark, 1.0, 1.0);
403 cairo_set_source (cr, pattern);
404 cairo_pattern_destroy (pattern);
405 cairo_set_line_width (cr, progress_width);
406 cairo_arc (cr, xc, yc, progress_radius, start_angle, value_angle);
409 cairo_set_line_cap (cr, CAIRO_LINE_CAP_ROUND);
410 progress_rim_width = 2.0;
411 cairo_set_line_width (cr, progress_rim_width);
412 pattern = prolooks_create_gradient_str ((double) 18, (double) 79, (double) 35, (double) 79, "#dfd5c9", "#dfd5c9", 1.0, 0.0);
413 cairo_set_source (cr, pattern);
414 cairo_pattern_destroy (pattern);
415 cairo_move_to (cr, xc + (progress_radius_outer * start_angle_x), yc + (progress_radius_outer * start_angle_y));
416 cairo_line_to (cr, xc + (progress_radius_inner * start_angle_x), yc + (progress_radius_inner * start_angle_y));
419 prolooks_set_source_color_string (cr, "#000000", 1.0);
420 cairo_move_to (cr, xc + (progress_radius_outer * end_angle_x), yc + (progress_radius_outer * end_angle_y));
421 cairo_line_to (cr, xc + (progress_radius_inner * end_angle_x), yc + (progress_radius_inner * end_angle_y));
424 // pattern = prolooks_create_gradient_str ((double) 95, (double) 6, (double) 5, (double) 44, "#dfd5c9", "#b0a090", 1.0, 1.0);
425 pattern = prolooks_create_gradient_str ((double) 95, (double) 6, (double) 5, (double) 44, "#000000", "#000000", 1.0, 1.0);
426 cairo_set_source (cr, pattern);
427 cairo_pattern_destroy (pattern);
428 cairo_arc (cr, xc, yc, progress_radius_outer, start_angle, end_angle);
431 cairo_set_line_cap (cr, CAIRO_LINE_CAP_BUTT);
432 pattern = prolooks_create_gradient ((double) 20, (double) 20, (double) 89, (double) 87, &lamp_bright, &lamp_dark, 0.25, 0.25);
433 cairo_set_source (cr, pattern);
434 cairo_pattern_destroy (pattern);
435 cairo_set_line_width (cr, progress_width);
436 cairo_arc (cr, xc, yc, progress_radius, start_angle, value_angle + (G_PI / 180.0));
439 progress_shine = prolooks_create_gradient_str ((double) 89, (double) 73, (double) 34, (double) 16, "#ffffff", "#ffffff", 0.3, 0.04);
440 cairo_pattern_add_color_stop_rgba (progress_shine, 0.5, 1.0, 1.0, 1.0, 0.0);
442 cairo_pattern_add_color_stop_rgba (progress_shine, 0.75, 1.0, 1.0, 1.0, 0.3);
444 cairo_pattern_add_color_stop_rgba (progress_shine, 0.75, 1.0, 1.0, 1.0, 0.2);
446 cairo_set_source (cr, progress_shine);
447 cairo_set_line_width (cr, progress_width);
448 cairo_arc (cr, xc, yc, progress_radius, start_angle, end_angle);
450 cairo_pattern_destroy (progress_shine);
452 cairo_set_line_width (cr, 1.0);
453 cairo_set_source_rgb (cr, 0.0, 0.0, 0.0);
454 cairo_arc (cr, xc, yc, progress_radius_inner, (double) 0, 2 * G_PI);
455 pattern = prolooks_create_gradient_str ((double) 35, (double) 31, (double) 75, (double) 72, "#68625c", "#44494b", 1.0, 1.0);
456 cairo_set_source (cr, pattern);
457 cairo_pattern_destroy (pattern);
459 cairo_set_source_rgb (cr, (double) 0, (double) 0, (double) 0);
460 cairo_arc (cr, xc, yc, progress_radius_inner, (double) 0, 2 * G_PI);
463 pattern = prolooks_create_gradient_str ((double) 42, (double) 34, (double) 68, (double) 70, "#e7ecef", "#9cafb8", 1.0, 1.0);
464 cairo_set_source (cr, pattern);
465 cairo_pattern_destroy (pattern);
466 cairo_arc (cr, xc, yc, knob_disc_radius, (double) 0, 2 * G_PI);
469 cairo_set_line_width (cr, 2.0);
470 degrees = G_PI / 180.0;
471 pattern = prolooks_create_gradient_str ((double) 38, (double) 34, (double) 70, (double) 68, "#ffffff", "#caddf2", 0.2, 0.2);
472 cairo_set_source (cr, pattern);
473 cairo_pattern_destroy (pattern);
474 cairo_move_to (cr, xc, yc);
475 cairo_arc (cr, xc, yc, knob_disc_radius - 1, (-154) * degrees, (-120) * degrees);
476 cairo_move_to (cr, xc, yc);
477 cairo_arc (cr, xc, yc, knob_disc_radius - 1, (G_PI / 2.0) - (60 * degrees), (G_PI / 2.0) - (29 * degrees));
480 pattern = prolooks_create_gradient_str ((double) 50, (double) 40, (double) 62, (double) 60, "#a1adb6", "#47535c", 0.07, 0.15);
481 cairo_set_source (cr, pattern);
482 cairo_pattern_destroy (pattern);
483 cairo_move_to (cr, xc, yc);
484 cairo_arc (cr, xc, yc, knob_disc_radius - 1, (-67) * degrees, (-27) * degrees);
485 cairo_move_to (cr, xc, yc);
486 cairo_arc (cr, xc, yc, knob_disc_radius - 1, G_PI - (67 * degrees), G_PI - (27 * degrees));
489 knob_ripples = cairo_pattern_create_radial (xc, yc, (double) 0, xc, yc, (double) 4);
490 prolooks_add_color_stop_str (knob_ripples, 0.0, "#e7ecef", 0.05);
491 prolooks_add_color_stop_str (knob_ripples, 0.5, "#58717d", 0.05);
492 prolooks_add_color_stop_str (knob_ripples, 0.75, "#d1d9de", 0.05);
493 prolooks_add_color_stop_str (knob_ripples, 1.0, "#5d7682", 0.05);
494 cairo_pattern_set_extend (knob_ripples, CAIRO_EXTEND_REPEAT);
495 cairo_set_line_width (cr, 0.0);
496 cairo_set_source (cr, knob_ripples);
497 cairo_arc (cr, xc, yc, knob_disc_radius, (double) 0, 2 * G_PI);
501 cairo_translate (cr, xc + (knob_disc_radius * value_x), yc + (knob_disc_radius * value_y));
502 cairo_rotate (cr, value_angle - G_PI);
503 cairo_set_source (cr, pattern = prolooks_create_gradient_str ((double) 16, (double) (-2), (double) 9, (double) 13, "#e7ecef", "#9cafb8", 0.8, 0.8));
504 cairo_pattern_destroy (pattern);
505 cairo_move_to (cr, (double) 0, (double) 4);
506 cairo_line_to (cr, (double) 17, (double) 4);
507 cairo_curve_to (cr, (double) 19, (double) 4, (double) 21, (double) 2, (double) 21, (double) 0);
508 cairo_curve_to (cr, (double) 21, (double) (-2), (double) 19, (double) (-4), (double) 17, (double) (-4));
509 cairo_line_to (cr, (double) 0, (double) (-4));
510 cairo_close_path (cr);
513 pattern = prolooks_create_gradient_str ((double) 9, (double) (-2), (double) 9, (double) 2, "#68625c", "#44494b", 1.0, 1.0);
514 cairo_set_source (cr, pattern);
515 cairo_pattern_destroy (pattern);
516 cairo_move_to (cr, (double) 0, (double) 2);
517 cairo_line_to (cr, (double) 16, (double) 2);
518 cairo_curve_to (cr, (double) 17, (double) 2, (double) 18, (double) 1, (double) 18, (double) 0);
519 cairo_curve_to (cr, (double) 18, (double) (-1), (double) 17, (double) (-2), (double) 16, (double) (-2));
520 cairo_line_to (cr, (double) 0, (double) (-2));
521 cairo_close_path (cr);
525 cairo_set_line_width (cr, 2.0);
526 pattern = prolooks_create_gradient_str ((double) 38, (double) 32, (double) 70, (double) 67, "#3d3d3d", "#000000", 1.0, 1.0);
527 cairo_set_source (cr, pattern);
528 cairo_pattern_destroy (pattern);
529 cairo_arc (cr, xc, yc, knob_disc_radius, (double) 0, 2 * G_PI);
533 cairo_pattern_destroy (knob_ripples);
537 MotionFeedback::pixwin_expose_event (GdkEventExpose* ev)
539 // GtkWidget* widget = GTK_WIDGET(pixwin.gobj());
540 GdkWindow *window = pixwin.get_window()->gobj();
541 GtkAdjustment* adj = adjustment->gobj();
543 int phase = (int)((adj->value - adj->lower) * 64 /
544 (adj->upper - adj->lower));
546 // skip middle phase except for true middle value
548 if (type == Rotary && phase == 32) {
549 double pt = (adj->value - adj->lower) * 2.0 /
550 (adj->upper - adj->lower) - 1.0;
557 // endless knob: skip 90deg highlights unless the value is really a multiple of 90deg
559 if (type == Endless && !(phase % 16)) {
564 double nom = adj->lower + phase * (adj->upper - adj->lower) / 64.0;
565 double diff = (adj->value - nom) / (adj->upper - adj->lower);
568 phase = (phase + 1) % 64;
570 phase = (phase + 63) % 64;
574 cairo_t* cr = gdk_cairo_create (GDK_DRAWABLE (window));
576 gdk_cairo_rectangle (cr, &ev->area);
579 core_draw (cr, phase, subheight/2, subwidth/2, subheight/2);
584 gdk_draw_pixbuf (GDK_DRAWABLE(window), widget->style->fg_gc[0],
586 phase * subwidth, type * subheight,
587 0, 0, subwidth, subheight, GDK_RGB_DITHER_NORMAL, 0, 0);
594 MotionFeedback::pixwin_scroll_event (GdkEventScroll* ev)
598 if ((ev->state & (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) == (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) {
600 } else if (ev->state & Keyboard::PrimaryModifier) {
606 switch (ev->direction) {
608 case GDK_SCROLL_RIGHT:
609 adjustment->set_value (adjustment->get_value() + (scale * adjustment->get_step_increment()));
612 case GDK_SCROLL_DOWN:
613 case GDK_SCROLL_LEFT:
614 adjustment->set_value (adjustment->get_value() - (scale * adjustment->get_step_increment()));
622 MotionFeedback::pixwin_size_request (GtkRequisition* req)
624 req->width = subwidth;
625 req->height = subheight;
629 MotionFeedback::pixwin_realized ()
631 set_lamp_color (Gdk::Color ("#b9feff"));
635 MotionFeedback::set_lamp_color (const Gdk::Color& c)
637 GdkColor col2 = {0,0,0,0};
638 GdkColor col3 = {0,0,0,0};
641 lamp_hsv = prolooks_hsv_new_for_gdk_color (_lamp_color.gobj());
642 lamp_bright = (prolooks_hsv_to_gdk_color (lamp_hsv, &col2), col2);
643 prolooks_hsv_set_saturation (lamp_hsv, 0.66);
644 prolooks_hsv_set_value (lamp_hsv, 0.67);
645 lamp_dark = (prolooks_hsv_to_gdk_color (lamp_hsv, &col3), col3);