prevent button1/2 clicks on pixfaders from being used to drive event processing in...
[ardour.git] / libs / gtkmm2ext / pixfader.cc
1 /*
2     Copyright (C) 2006 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     $Id: fastmeter.h 570 2006-06-07 21:21:21Z sampo $
19 */
20
21
22 #include <iostream>
23
24 #include "gtkmm2ext/pixfader.h"
25 #include "gtkmm2ext/keyboard.h"
26 #include "gtkmm2ext/rgb_macros.h"
27 #include "gtkmm2ext/utils.h"
28
29 using namespace Gtkmm2ext;
30 using namespace Gtk;
31 using namespace std;
32
33 #define CORNER_RADIUS 4
34 #define FADER_RESERVE (2*CORNER_RADIUS)
35
36 PixFader::PixFader (Gtk::Adjustment& adj, int orientation, int fader_length, int fader_girth)
37         : adjustment (adj)
38         , span (fader_length)
39         , girth (fader_girth)
40         , _orien (orientation)
41         , pattern (0)
42         , texture_pattern (0)
43         , _hovering (false)
44         , last_drawn (-1)
45         , dragging (false)
46 {
47         default_value = adjustment.get_value();
48         update_unity_position ();
49
50         add_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|Gdk::POINTER_MOTION_MASK|Gdk::SCROLL_MASK|Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
51
52         adjustment.signal_value_changed().connect (mem_fun (*this, &PixFader::adjustment_changed));
53         adjustment.signal_changed().connect (mem_fun (*this, &PixFader::adjustment_changed));
54 }
55
56 PixFader::~PixFader ()
57 {
58         free_patterns ();
59 }
60
61 void
62 PixFader::free_patterns ()
63 {
64         if (pattern) {
65                 cairo_pattern_destroy (pattern);
66                 pattern = 0;
67         }
68         if (texture_pattern) {
69                 cairo_pattern_destroy (texture_pattern);
70                 texture_pattern = 0;
71         }
72 }
73
74 void
75 PixFader::create_patterns ()
76 {
77         free_patterns ();
78
79         Gdk::Color c = get_style()->get_fg (get_state());
80         float fr, fg, fb;
81         float br, bg, bb;
82
83         fr = c.get_red_p ();
84         fg = c.get_green_p ();
85         fb = c.get_blue_p ();
86
87         c = get_style()->get_bg (get_state());
88
89         br = c.get_red_p ();
90         bg = c.get_green_p ();
91         bb = c.get_blue_p ();
92
93         cairo_surface_t* surface;
94         cairo_t* tc = 0;
95         float radius = CORNER_RADIUS;
96
97         double w = get_width();
98
99         if (_orien == VERT) {
100                 
101                 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, get_width(), get_height() * 2.0);
102                 tc = cairo_create (surface);
103
104                 /* paint background + border */
105
106                 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, get_width(), 0);
107                 cairo_pattern_add_color_stop_rgba (shade_pattern, 0, br*0.8,bg*0.8,bb*0.8, 1.0);
108                 cairo_pattern_add_color_stop_rgba (shade_pattern, 1, br*0.6,bg*0.6,bb*0.6, 1.0);
109                 cairo_set_source (tc, shade_pattern);
110                 cairo_rectangle (tc, 0, 0, get_width(), get_height() * 2.0);
111                 cairo_fill (tc);
112
113                 cairo_pattern_destroy (shade_pattern);
114                 
115                 /* paint lower shade */
116                 
117                 w -= 2.0;
118
119                 shade_pattern = cairo_pattern_create_linear (0.0, 0.0, w, 0);
120                 cairo_pattern_add_color_stop_rgba (shade_pattern, 0, fr*0.8,fg*0.8,fb*0.8, 1.0);
121                 cairo_pattern_add_color_stop_rgba (shade_pattern, 1, fr*0.6,fg*0.6,fb*0.6, 1.0);
122                 cairo_set_source (tc, shade_pattern);
123                 Gtkmm2ext::rounded_top_half_rectangle (tc, 1.0, get_height(), w, get_height(), radius-1.5);
124                 cairo_fill (tc);
125
126                 cairo_pattern_destroy (shade_pattern);
127
128                 pattern = cairo_pattern_create_for_surface (surface);
129
130         } else {
131
132                 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, get_width() * 2.0, get_height());
133                 tc = cairo_create (surface);
134
135                 /* paint right shade (background section)*/
136
137                 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, 0.0, get_height());
138                 cairo_pattern_add_color_stop_rgba (shade_pattern, 0, br*0.8,bg*0.8,bb*0.8, 1.0);
139                 cairo_pattern_add_color_stop_rgba (shade_pattern, 1, br*0.6,bg*0.6,bb*0.6, 1.0);
140                 cairo_set_source (tc, shade_pattern);
141                 cairo_rectangle (tc, 0, 0, get_width() * 2.0, get_height());
142                 cairo_fill (tc);
143
144                 /* paint left shade (active section/foreground) */
145                 
146                 shade_pattern = cairo_pattern_create_linear (0.0, 0.0, 0.0, get_height());
147                 cairo_pattern_add_color_stop_rgba (shade_pattern, 0, fr*0.8,fg*0.8,fb*0.8, 1.0);
148                 cairo_pattern_add_color_stop_rgba (shade_pattern, 1, fr*0.6,fg*0.6,fb*0.6, 1.0);
149                 cairo_set_source (tc, shade_pattern);
150                 Gtkmm2ext::rounded_right_half_rectangle (tc, 0, 1, get_width(), get_height() - 2.0, radius-1.5);
151                 cairo_fill (tc);
152                 cairo_pattern_destroy (shade_pattern);
153                 
154                 pattern = cairo_pattern_create_for_surface (surface);
155         }
156
157         cairo_destroy (tc);
158         cairo_surface_destroy (surface);
159
160         if ( !_text.empty()) {
161                 _layout->get_pixel_size (_text_width, _text_height);
162         } else {
163                 _text_width = 0;
164                 _text_height = 0;
165         }
166
167         c = get_style()->get_text (get_state());
168
169         text_r = c.get_red_p ();
170         text_g = c.get_green_p ();
171         text_b = c.get_blue_p ();
172 }
173
174 bool
175 PixFader::on_expose_event (GdkEventExpose* ev)
176 {
177         Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();
178         cairo_t* cr = context->cobj();
179
180         cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
181         cairo_clip (cr);
182
183         if (!pattern) {
184                 create_patterns();
185         }
186         
187         int ds = display_span ();
188         float w = get_width();
189         float h = get_height();
190
191         cairo_matrix_t matrix;
192
193         if (_orien == VERT) {
194
195                 if (ds > h - FADER_RESERVE) {
196                         ds = h - FADER_RESERVE;
197                 }
198
199                 cairo_set_source (cr, pattern);
200                 cairo_matrix_init_translate (&matrix, 0, (h - ds));
201                 cairo_pattern_set_matrix (pattern, &matrix);
202                 Gtkmm2ext::rounded_rectangle (cr, 0, 0, w, h, CORNER_RADIUS-1.5);
203                 cairo_fill (cr);
204
205         } else {
206
207                 if (ds < FADER_RESERVE) {
208                         ds = FADER_RESERVE;
209                 }
210
211                 /*
212                   if ds == w, the pattern does not need to be translated
213                   if ds == 0 (or FADER_RESERVE), the pattern needs to be moved
214                       w to the left, which is -w in pattern space, and w in
215                       user space
216                   if ds == 10, then the pattern needs to be moved w - 10
217                       to the left, which is -(w-10) in pattern space, which 
218                       is (w - 10) in user space
219
220                   thus: translation = (w - ds)
221                  */
222
223                 cairo_set_source (cr, pattern);
224                 cairo_matrix_init_translate (&matrix, w - ds, 0);
225                 cairo_pattern_set_matrix (pattern, &matrix);
226                 Gtkmm2ext::rounded_rectangle (cr, 0, 0, w, h, CORNER_RADIUS-1.5);
227                 cairo_fill (cr);
228         }
229                 
230         /* draw the unity-position line if it's not at either end*/
231         if (unity_loc > 0) {
232                 if ( _orien == VERT) {
233                         if (unity_loc < h ) {
234                                         context->set_line_width (1); 
235                                         context->set_source_rgb (0.0, 1.0, 0.0);
236                                         context->move_to (1, unity_loc);
237                                         context->line_to (girth - 2.0, unity_loc);
238                                         context->stroke ();
239                         }
240                 } else {
241                         if ( unity_loc < w ){
242                                 context->set_line_width (1); 
243                                 context->set_source_rgb (0.0, 1.0, 0.0);
244                                 context->move_to (unity_loc, 1);
245                                 context->line_to (unity_loc, girth - 2.0);
246                                 context->stroke ();
247                         }
248                 }
249         }
250         
251         if ( !_text.empty() ) {
252
253                 cairo_new_path (cr);    
254
255                 /* center text */
256                 cairo_move_to (cr, (get_width() - _text_width)/2.0, get_height()/2.0 - _text_height/2.0);
257
258                 cairo_set_source_rgba (cr, text_r, text_g, text_b, 0.9);
259                 pango_cairo_show_layout (cr, _layout->gobj());
260         } 
261         
262 //      if (Config->get_widget_prelight()) {  //pixfader does not have access to config
263                 if (_hovering) {
264                         Gtkmm2ext::rounded_rectangle (cr, 0, 0, get_width(), get_height(), 3);
265                         cairo_set_source_rgba (cr, 0.905, 0.917, 0.925, 0.1);
266                         cairo_fill (cr);
267                 }
268 //      }
269
270         last_drawn = ds;
271
272         return true;
273 }
274
275 void
276 PixFader::on_size_request (GtkRequisition* req)
277 {
278         if (_orien == VERT) {
279                 req->width = girth;
280                 req->height = span;
281         } else {
282                 req->height = girth;
283                 req->width = span;
284         }
285 }
286
287 void
288 PixFader::on_size_allocate (Gtk::Allocation& alloc)
289 {
290         DrawingArea::on_size_allocate(alloc);
291
292         if (_orien == VERT) {
293                 girth = alloc.get_width ();
294         } else {
295                 girth = alloc.get_height ();
296         }
297
298         update_unity_position ();
299
300         if (is_realized()) {
301                 create_patterns();
302                 queue_draw ();
303         }
304 }
305
306 bool
307 PixFader::on_button_press_event (GdkEventButton* ev)
308 {
309         if (ev->type != GDK_BUTTON_PRESS) {
310                 return true;
311         }
312
313         if (ev->button != 1 && ev->button != 2) {
314                 return false;
315         }
316
317         add_modal_grab ();
318         grab_loc = (_orien == VERT) ? ev->y : ev->x;
319         grab_start = (_orien == VERT) ? ev->y : ev->x;
320         grab_window = ev->window;
321         dragging = true;
322         gdk_pointer_grab(ev->window,false,
323                         GdkEventMask( Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK |Gdk::BUTTON_RELEASE_MASK),
324                         NULL,NULL,ev->time);
325
326         if (ev->button == 2) {
327                 set_adjustment_from_event (ev);
328         }
329         
330         return true;
331 }
332
333 bool
334 PixFader::on_button_release_event (GdkEventButton* ev)
335 {
336         double const ev_pos = (_orien == VERT) ? ev->y : ev->x;
337         
338         switch (ev->button) {
339         case 1:
340                 if (dragging) {
341                         remove_modal_grab();
342                         dragging = false;
343                         gdk_pointer_ungrab (GDK_CURRENT_TIME);
344
345                         if (!_hovering) {
346                                 Keyboard::magic_widget_drop_focus();
347                                 queue_draw ();
348                         }
349
350                         if (ev_pos == grab_start) {
351
352                                 /* no motion - just a click */
353
354                                 if (ev->state & Keyboard::TertiaryModifier) {
355                                         adjustment.set_value (default_value);
356                                 } else if (ev->state & Keyboard::GainFineScaleModifier) {
357                                         adjustment.set_value (adjustment.get_lower());
358                                 } else if ((_orien == VERT && ev_pos < display_span()) || (_orien == HORIZ && ev_pos > display_span())) {
359                                         /* above the current display height, remember X Window coords */
360                                         adjustment.set_value (adjustment.get_value() + adjustment.get_step_increment());
361                                 } else {
362                                         adjustment.set_value (adjustment.get_value() - adjustment.get_step_increment());
363                                 }
364                         }
365                         return true;
366                 } 
367                 break;
368                 
369         case 2:
370                 if (dragging) {
371                         remove_modal_grab();
372                         dragging = false;
373                         set_adjustment_from_event (ev);
374                         gdk_pointer_ungrab (GDK_CURRENT_TIME);
375                         return true;
376                 }
377                 break;
378
379         default:
380                 break;
381         }
382
383         return false;
384 }
385
386 bool
387 PixFader::on_scroll_event (GdkEventScroll* ev)
388 {
389         double scale;
390         bool ret = false;
391
392         if (ev->state & Keyboard::GainFineScaleModifier) {
393                 if (ev->state & Keyboard::GainExtraFineScaleModifier) {
394                         scale = 0.01;
395                 } else {
396                         scale = 0.05;
397                 }
398         } else {
399                 scale = 0.25;
400         }
401
402         if (_orien == VERT) {
403
404                 /* should left/right scroll affect vertical faders ? */
405
406                 switch (ev->direction) {
407
408                 case GDK_SCROLL_UP:
409                         adjustment.set_value (adjustment.get_value() + (adjustment.get_page_increment() * scale));
410                         ret = true;
411                         break;
412                 case GDK_SCROLL_DOWN:
413                         adjustment.set_value (adjustment.get_value() - (adjustment.get_page_increment() * scale));
414                         ret = true;
415                         break;
416                 default:
417                         break;
418                 }
419         } else {
420
421                 /* up/down scrolls should definitely affect horizontal faders
422                    because they are so much easier to use
423                 */
424
425                 switch (ev->direction) {
426
427                 case GDK_SCROLL_RIGHT:
428                 case GDK_SCROLL_UP:
429                         adjustment.set_value (adjustment.get_value() + (adjustment.get_page_increment() * scale));
430                         ret = true;
431                         break;
432                 case GDK_SCROLL_LEFT:
433                 case GDK_SCROLL_DOWN:
434                         adjustment.set_value (adjustment.get_value() - (adjustment.get_page_increment() * scale));
435                         ret = true;
436                         break;
437                 default:
438                         break;
439                 }
440         }
441         return ret;
442 }
443
444 bool
445 PixFader::on_motion_notify_event (GdkEventMotion* ev)
446 {
447         if (dragging) {
448                 double scale = 1.0;
449                 double const ev_pos = (_orien == VERT) ? ev->y : ev->x;
450                 
451                 if (ev->window != grab_window) {
452                         grab_loc = ev_pos;
453                         grab_window = ev->window;
454                         return true;
455                 }
456                 
457                 if (ev->state & Keyboard::GainFineScaleModifier) {
458                         if (ev->state & Keyboard::GainExtraFineScaleModifier) {
459                                 scale = 0.05;
460                         } else {
461                                 scale = 0.1;
462                         }
463                 }
464
465                 double const delta = ev_pos - grab_loc;
466                 grab_loc = ev_pos;
467
468                 double fract = (delta / span);
469
470                 fract = min (1.0, fract);
471                 fract = max (-1.0, fract);
472
473                 // X Window is top->bottom for 0..Y
474                 
475                 if (_orien == VERT) {
476                         fract = -fract;
477                 }
478
479                 adjustment.set_value (adjustment.get_value() + scale * fract * (adjustment.get_upper() - adjustment.get_lower()));
480         }
481
482         return true;
483 }
484
485 void
486 PixFader::adjustment_changed ()
487 {
488         if (display_span() != last_drawn) {
489                 queue_draw ();
490         }
491 }
492
493 /** @return pixel offset of the current value from the right or bottom of the fader */
494 int
495 PixFader::display_span ()
496 {
497         float fract = (adjustment.get_value () - adjustment.get_lower()) / ((adjustment.get_upper() - adjustment.get_lower()));
498         int ds;
499         if (_orien == VERT) {
500                 ds = (int)floor ( span * (1.0 - fract));
501         } else {
502                 ds = (int)floor (span * fract);
503         }
504         
505         return ds;
506 }
507
508 void
509 PixFader::set_fader_length (int l)
510 {
511         span = l;
512         update_unity_position ();
513         queue_resize ();
514 }
515
516 void
517 PixFader::update_unity_position ()
518 {
519         if (_orien == VERT) {
520                 unity_loc = (int) rint (span * (1 - (default_value / (adjustment.get_upper() - adjustment.get_lower())))) - 1;
521         } else {
522                 unity_loc = (int) rint (default_value * span / (adjustment.get_upper() - adjustment.get_lower()));
523         }
524
525         queue_draw ();
526 }
527
528 bool
529 PixFader::on_enter_notify_event (GdkEventCrossing*)
530 {
531         _hovering = true;
532         Keyboard::magic_widget_grab_focus ();
533         queue_draw ();
534         return false;
535 }
536
537 bool
538 PixFader::on_leave_notify_event (GdkEventCrossing*)
539 {
540         if (!dragging) {
541                 _hovering = false;
542                 Keyboard::magic_widget_drop_focus();
543                 queue_draw ();
544         }
545         return false;
546 }
547
548 void
549 PixFader::set_adjustment_from_event (GdkEventButton* ev)
550 {
551         double fract = (_orien == VERT) ? (1.0 - (ev->y / span)) : (ev->x / span);
552
553         fract = min (1.0, fract);
554         fract = max (0.0, fract);
555
556         adjustment.set_value (fract * (adjustment.get_upper () - adjustment.get_lower ()));
557 }
558
559 void
560 PixFader::set_default_value (float d)
561 {
562         default_value = d;
563         update_unity_position ();
564 }
565
566 void
567 PixFader::set_text (const std::string& str)
568 {
569         _text = str;
570
571         if (!_layout && !_text.empty()) {
572                 _layout = Pango::Layout::create (get_pango_context());
573         } 
574
575         if (_layout) {
576                 _layout->set_text (str);
577         }
578
579         queue_resize ();
580 }
581
582 void
583 PixFader::on_state_changed (Gtk::StateType old_state)
584 {
585         Widget::on_state_changed (old_state);
586         create_patterns ();
587 }