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