fix up handling of size allocation + setting
[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
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                 }
376                 break;
377
378         default:
379                 break;
380         }
381
382         return false;
383 }
384
385 bool
386 PixFader::on_scroll_event (GdkEventScroll* ev)
387 {
388         double scale;
389         bool ret = false;
390
391         if (ev->state & Keyboard::GainFineScaleModifier) {
392                 if (ev->state & Keyboard::GainExtraFineScaleModifier) {
393                         scale = 0.01;
394                 } else {
395                         scale = 0.05;
396                 }
397         } else {
398                 scale = 0.25;
399         }
400
401         if (_orien == VERT) {
402
403                 /* should left/right scroll affect vertical faders ? */
404
405                 switch (ev->direction) {
406
407                 case GDK_SCROLL_UP:
408                         adjustment.set_value (adjustment.get_value() + (adjustment.get_page_increment() * scale));
409                         ret = true;
410                         break;
411                 case GDK_SCROLL_DOWN:
412                         adjustment.set_value (adjustment.get_value() - (adjustment.get_page_increment() * scale));
413                         ret = true;
414                         break;
415                 default:
416                         break;
417                 }
418         } else {
419
420                 /* up/down scrolls should definitely affect horizontal faders
421                    because they are so much easier to use
422                 */
423
424                 switch (ev->direction) {
425
426                 case GDK_SCROLL_RIGHT:
427                 case GDK_SCROLL_UP:
428                         adjustment.set_value (adjustment.get_value() + (adjustment.get_page_increment() * scale));
429                         ret = true;
430                         break;
431                 case GDK_SCROLL_LEFT:
432                 case GDK_SCROLL_DOWN:
433                         adjustment.set_value (adjustment.get_value() - (adjustment.get_page_increment() * scale));
434                         ret = true;
435                         break;
436                 default:
437                         break;
438                 }
439         }
440         return ret;
441 }
442
443 bool
444 PixFader::on_motion_notify_event (GdkEventMotion* ev)
445 {
446         if (dragging) {
447                 double scale = 1.0;
448                 double const ev_pos = (_orien == VERT) ? ev->y : ev->x;
449                 
450                 if (ev->window != grab_window) {
451                         grab_loc = ev_pos;
452                         grab_window = ev->window;
453                         return true;
454                 }
455                 
456                 if (ev->state & Keyboard::GainFineScaleModifier) {
457                         if (ev->state & Keyboard::GainExtraFineScaleModifier) {
458                                 scale = 0.05;
459                         } else {
460                                 scale = 0.1;
461                         }
462                 }
463
464                 double const delta = ev_pos - grab_loc;
465                 grab_loc = ev_pos;
466
467                 double fract = (delta / span);
468
469                 fract = min (1.0, fract);
470                 fract = max (-1.0, fract);
471
472                 // X Window is top->bottom for 0..Y
473                 
474                 if (_orien == VERT) {
475                         fract = -fract;
476                 }
477
478                 adjustment.set_value (adjustment.get_value() + scale * fract * (adjustment.get_upper() - adjustment.get_lower()));
479         }
480
481         return true;
482 }
483
484 void
485 PixFader::adjustment_changed ()
486 {
487         if (display_span() != last_drawn) {
488                 queue_draw ();
489         }
490 }
491
492 /** @return pixel offset of the current value from the right or bottom of the fader */
493 int
494 PixFader::display_span ()
495 {
496         float fract = (adjustment.get_value () - adjustment.get_lower()) / ((adjustment.get_upper() - adjustment.get_lower()));
497         int ds;
498         if (_orien == VERT) {
499                 ds = (int)floor ( span * (1.0 - fract));
500         } else {
501                 ds = (int)floor (span * fract);
502         }
503         
504         return ds;
505 }
506
507 void
508 PixFader::set_fader_length (int l)
509 {
510         span = l;
511         update_unity_position ();
512         queue_resize ();
513 }
514
515 void
516 PixFader::update_unity_position ()
517 {
518         if (_orien == VERT) {
519                 unity_loc = (int) rint (span * (1 - (default_value / (adjustment.get_upper() - adjustment.get_lower())))) - 1;
520         } else {
521                 unity_loc = (int) rint (default_value * span / (adjustment.get_upper() - adjustment.get_lower()));
522         }
523
524         queue_draw ();
525 }
526
527 bool
528 PixFader::on_enter_notify_event (GdkEventCrossing*)
529 {
530         _hovering = true;
531         Keyboard::magic_widget_grab_focus ();
532         queue_draw ();
533         return false;
534 }
535
536 bool
537 PixFader::on_leave_notify_event (GdkEventCrossing*)
538 {
539         if (!dragging) {
540                 _hovering = false;
541                 Keyboard::magic_widget_drop_focus();
542                 queue_draw ();
543         }
544         return false;
545 }
546
547 void
548 PixFader::set_adjustment_from_event (GdkEventButton* ev)
549 {
550         double fract = (_orien == VERT) ? (1.0 - (ev->y / span)) : (ev->x / span);
551
552         fract = min (1.0, fract);
553         fract = max (0.0, fract);
554
555         adjustment.set_value (fract * (adjustment.get_upper () - adjustment.get_lower ()));
556 }
557
558 void
559 PixFader::set_default_value (float d)
560 {
561         default_value = d;
562         update_unity_position ();
563 }
564
565 void
566 PixFader::set_text (const std::string& str)
567 {
568         _text = str;
569
570         if (!_layout && !_text.empty()) {
571                 _layout = Pango::Layout::create (get_pango_context());
572         } 
573
574         if (_layout) {
575                 _layout->set_text (str);
576         }
577
578         queue_resize ();
579 }
580
581 void
582 PixFader::on_state_changed (Gtk::StateType old_state)
583 {
584         Widget::on_state_changed (old_state);
585         create_patterns ();
586 }