fix step increment direction for mouse-click on faders
[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
294         if (ev->button == 2) {
295                 set_adjustment_from_event (ev);
296         }
297         
298         return true;
299 }
300
301 bool
302 PixFader::on_button_release_event (GdkEventButton* ev)
303 {
304         double const ev_pos = (_orien == VERT) ? ev->y : ev->x;
305         
306         switch (ev->button) {
307         case 1:
308                 if (dragging) {
309                         remove_modal_grab();
310                         dragging = false;
311
312                         if (!_hovering) {
313                                 Keyboard::magic_widget_drop_focus();
314                                 queue_draw ();
315                         }
316
317                         if (ev_pos == grab_start) {
318
319                                 /* no motion - just a click */
320
321                                 if (ev->state & Keyboard::TertiaryModifier) {
322                                         adjustment.set_value (default_value);
323                                 } else if (ev->state & Keyboard::GainFineScaleModifier) {
324                                         adjustment.set_value (adjustment.get_lower());
325                                 } else if ((_orien == VERT && ev_pos < display_span()) || (_orien == HORIZ && ev_pos > display_span())) {
326                                         /* above the current display height, remember X Window coords */
327                                         adjustment.set_value (adjustment.get_value() + adjustment.get_step_increment());
328                                 } else {
329                                         adjustment.set_value (adjustment.get_value() - adjustment.get_step_increment());
330                                 }
331                         }
332
333                 } 
334                 break;
335                 
336         case 2:
337                 if (dragging) {
338                         remove_modal_grab();
339                         dragging = false;
340                         set_adjustment_from_event (ev);
341                 }
342                 break;
343
344         default:
345                 break;
346         }
347
348         return false;
349 }
350
351 bool
352 PixFader::on_scroll_event (GdkEventScroll* ev)
353 {
354         double scale;
355         bool ret = false;
356
357         if (ev->state & Keyboard::GainFineScaleModifier) {
358                 if (ev->state & Keyboard::GainExtraFineScaleModifier) {
359                         scale = 0.01;
360                 } else {
361                         scale = 0.05;
362                 }
363         } else {
364                 scale = 0.25;
365         }
366
367         if (_orien == VERT) {
368
369                 /* should left/right scroll affect vertical faders ? */
370
371                 switch (ev->direction) {
372
373                 case GDK_SCROLL_UP:
374                         adjustment.set_value (adjustment.get_value() + (adjustment.get_page_increment() * scale));
375                         ret = true;
376                         break;
377                 case GDK_SCROLL_DOWN:
378                         adjustment.set_value (adjustment.get_value() - (adjustment.get_page_increment() * scale));
379                         ret = true;
380                         break;
381                 default:
382                         break;
383                 }
384         } else {
385
386                 /* up/down scrolls should definitely affect horizontal faders
387                    because they are so much easier to use
388                 */
389
390                 switch (ev->direction) {
391
392                 case GDK_SCROLL_RIGHT:
393                 case GDK_SCROLL_UP:
394                         adjustment.set_value (adjustment.get_value() + (adjustment.get_page_increment() * scale));
395                         ret = true;
396                         break;
397                 case GDK_SCROLL_LEFT:
398                 case GDK_SCROLL_DOWN:
399                         adjustment.set_value (adjustment.get_value() - (adjustment.get_page_increment() * scale));
400                         ret = true;
401                         break;
402                 default:
403                         break;
404                 }
405         }
406         return ret;
407 }
408
409 bool
410 PixFader::on_motion_notify_event (GdkEventMotion* ev)
411 {
412         if (dragging) {
413                 double scale = 1.0;
414                 double const ev_pos = (_orien == VERT) ? ev->y : ev->x;
415                 
416                 if (ev->window != grab_window) {
417                         grab_loc = ev_pos;
418                         grab_window = ev->window;
419                         return true;
420                 }
421                 
422                 if (ev->state & Keyboard::GainFineScaleModifier) {
423                         if (ev->state & Keyboard::GainExtraFineScaleModifier) {
424                                 scale = 0.05;
425                         } else {
426                                 scale = 0.1;
427                         }
428                 }
429
430                 double const delta = ev_pos - grab_loc;
431                 grab_loc = ev_pos;
432
433                 double fract = (delta / span);
434
435                 fract = min (1.0, fract);
436                 fract = max (-1.0, fract);
437
438                 // X Window is top->bottom for 0..Y
439                 
440                 if (_orien == VERT) {
441                         fract = -fract;
442                 }
443
444                 adjustment.set_value (adjustment.get_value() + scale * fract * (adjustment.get_upper() - adjustment.get_lower()));
445         }
446
447         return true;
448 }
449
450 void
451 PixFader::adjustment_changed ()
452 {
453         if (display_span() != last_drawn) {
454                 queue_draw ();
455         }
456 }
457
458 /** @return pixel offset of the current value from the right or bottom of the fader */
459 int
460 PixFader::display_span ()
461 {
462         float fract = (adjustment.get_value () - adjustment.get_lower()) / ((adjustment.get_upper() - adjustment.get_lower()));
463         int ds;
464         if (_orien == VERT) {
465                 ds = (int)floor ( span * (1.0 - fract));
466         } else {
467                 ds = (int)floor (span * fract);
468         }
469         
470         return ds;
471 }
472
473 void
474 PixFader::set_fader_length (int l)
475 {
476         if (_orien == VERT) {
477                 view.height = span = l;
478         } else {
479                 view.width = span = l;
480         }
481
482         update_unity_position ();
483
484         queue_draw ();
485 }
486
487 void
488 PixFader::update_unity_position ()
489 {
490         if (_orien == VERT) {
491                 unity_loc = (int) rint (view.height * (1 - (default_value / (adjustment.get_upper() - adjustment.get_lower())))) - 1;
492         } else {
493                 unity_loc = (int) rint (default_value * view.width);
494         }
495
496         queue_draw ();
497 }
498
499 bool
500 PixFader::on_enter_notify_event (GdkEventCrossing*)
501 {
502         _hovering = true;
503         Keyboard::magic_widget_grab_focus ();
504         queue_draw ();
505         return false;
506 }
507
508 bool
509 PixFader::on_leave_notify_event (GdkEventCrossing*)
510 {
511         if (!dragging) {
512                 _hovering = false;
513                 Keyboard::magic_widget_drop_focus();
514                 queue_draw ();
515         }
516         return false;
517 }
518
519 void
520 PixFader::set_adjustment_from_event (GdkEventButton* ev)
521 {
522         double fract = (_orien == VERT) ? (1.0 - (ev->y / span)) : (ev->x / span);
523
524         fract = min (1.0, fract);
525         fract = max (0.0, fract);
526
527         adjustment.set_value (fract * (adjustment.get_upper () - adjustment.get_lower ()));
528 }
529
530 void
531 PixFader::set_default_value (float d)
532 {
533         default_value = d;
534         update_unity_position ();
535 }
536
537 void
538 PixFader::set_text (const std::string& str)
539 {
540         _text = str;
541
542         if (!_layout && !_text.empty()) {
543                 _layout = Pango::Layout::create (get_pango_context());
544         } 
545
546         if (_layout) {
547                 _layout->set_text (str);
548         }
549
550         queue_resize ();
551 }
552