rework pixfader design again: remove texture, draw entire (double-sized) pattern...
[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 (Gtk::Adjustment& adj, int orientation, int fader_length, int fader_girth)
36         : adjustment (adj)
37         , span (fader_length)
38         , girth (fader_girth)
39         , _orien (orientation)
40         , pattern (0)
41         , texture_pattern (0)
42         , _hovering (false)
43         , last_drawn (-1)
44         , dragging (false)
45 {
46         default_value = adjustment.get_value();
47         update_unity_position ();
48
49         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);
50
51         adjustment.signal_value_changed().connect (mem_fun (*this, &PixFader::adjustment_changed));
52         adjustment.signal_changed().connect (mem_fun (*this, &PixFader::adjustment_changed));
53 }
54
55 PixFader::~PixFader ()
56 {
57         free_patterns ();
58 }
59
60 void
61 PixFader::free_patterns ()
62 {
63         if (pattern) {
64                 cairo_pattern_destroy (pattern);
65                 pattern = 0;
66         }
67         if (texture_pattern) {
68                 cairo_pattern_destroy (texture_pattern);
69                 texture_pattern = 0;
70         }
71 }
72
73 void
74 PixFader::create_patterns ()
75 {
76         free_patterns ();
77
78         Gdk::Color c = get_style()->get_fg (get_state());
79         float fr, fg, fb;
80         float br, bg, bb;
81
82         fr = c.get_red_p ();
83         fg = c.get_green_p ();
84         fb = c.get_blue_p ();
85
86         c = get_style()->get_bg (get_state());
87
88         br = c.get_red_p ();
89         bg = c.get_green_p ();
90         bb = c.get_blue_p ();
91
92         cairo_surface_t* surface;
93         cairo_t* tc = 0;
94         float radius = CORNER_RADIUS;
95
96         double w = get_width();
97
98         if (_orien == VERT) {
99                 
100                 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, get_width(), get_height() * 2.0);
101                 tc = cairo_create (surface);
102
103                 /* paint background + border */
104
105                 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, get_width(), 0);
106                 cairo_pattern_add_color_stop_rgba (shade_pattern, 0, br*0.8,bg*0.8,bb*0.8, 1.0);
107                 cairo_pattern_add_color_stop_rgba (shade_pattern, 1, br*0.6,bg*0.6,bb*0.6, 1.0);
108                 cairo_set_source (tc, shade_pattern);
109                 cairo_rectangle (tc, 0, 0, get_width(), get_height() * 2.0);
110                 cairo_fill (tc);
111
112                 cairo_pattern_destroy (shade_pattern);
113                 
114                 /* paint lower shade */
115                 
116                 w -= 2.0;
117
118                 shade_pattern = cairo_pattern_create_linear (0.0, 0.0, w, 0);
119                 cairo_pattern_add_color_stop_rgba (shade_pattern, 0, fr*0.8,fg*0.8,fb*0.8, 1.0);
120                 cairo_pattern_add_color_stop_rgba (shade_pattern, 1, fr*0.6,fg*0.6,fb*0.6, 1.0);
121                 cairo_set_source (tc, shade_pattern);
122                 Gtkmm2ext::rounded_top_half_rectangle (tc, 1.0, get_height(), w, get_height(), radius-1.5);
123                 cairo_fill (tc);
124
125                 cairo_pattern_destroy (shade_pattern);
126
127                 pattern = cairo_pattern_create_for_surface (surface);
128
129         } else {
130
131                 surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, get_width() * 2.0, get_height());
132                 tc = cairo_create (surface);
133
134                 /* paint right shade (background section)*/
135
136                 cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, 0.0, get_height());
137                 cairo_pattern_add_color_stop_rgba (shade_pattern, 0, br*0.8,bg*0.8,bb*0.8, 1.0);
138                 cairo_pattern_add_color_stop_rgba (shade_pattern, 1, br*0.6,bg*0.6,bb*0.6, 1.0);
139                 cairo_set_source (tc, shade_pattern);
140                 cairo_rectangle (tc, 0, 0, get_width() * 2.0, get_height());
141                 cairo_fill (tc);
142
143                 /* paint left shade (active section/foreground) */
144                 
145                 shade_pattern = cairo_pattern_create_linear (0.0, 0.0, 0.0, get_height());
146                 cairo_pattern_add_color_stop_rgba (shade_pattern, 0, fr*0.8,fg*0.8,fb*0.8, 1.0);
147                 cairo_pattern_add_color_stop_rgba (shade_pattern, 1, fr*0.6,fg*0.6,fb*0.6, 1.0);
148                 cairo_set_source (tc, shade_pattern);
149                 Gtkmm2ext::rounded_right_half_rectangle (tc, 0, 1, get_width(), get_height() - 2.0, radius-1.5);
150                 cairo_fill (tc);
151                 cairo_pattern_destroy (shade_pattern);
152                 
153                 pattern = cairo_pattern_create_for_surface (surface);
154         }
155
156         cairo_destroy (tc);
157         cairo_surface_destroy (surface);
158
159         if ( !_text.empty()) {
160                 _layout->get_pixel_size (_text_width, _text_height);
161         } else {
162                 _text_width = 0;
163                 _text_height = 0;
164         }
165
166         c = get_style()->get_text (get_state());
167
168         text_r = c.get_red_p ();
169         text_g = c.get_green_p ();
170         text_b = c.get_blue_p ();
171 }
172
173 bool
174 PixFader::on_expose_event (GdkEventExpose* ev)
175 {
176         Cairo::RefPtr<Cairo::Context> context = get_window()->create_cairo_context();
177         cairo_t* cr = context->cobj();
178
179         cairo_rectangle (cr, ev->area.x, ev->area.y, ev->area.width, ev->area.height);
180         cairo_clip (cr);
181
182         if (!pattern) {
183                 create_patterns();
184         }
185         
186         int ds = display_span ();
187         float w = get_width();
188         float h = get_height();
189
190         cairo_matrix_t matrix;
191
192         if (_orien == VERT) {
193
194                 if (ds > h - FADER_RESERVE) {
195                         ds = h - FADER_RESERVE;
196                 }
197
198                 cairo_set_source (cr, pattern);
199                 cairo_matrix_init_translate (&matrix, 0, (h - ds));
200                 cairo_pattern_set_matrix (pattern, &matrix);
201                 cairo_rectangle (cr, 0, 0, w, h);
202                 cairo_fill (cr);
203
204         } else {
205
206                 if (ds < FADER_RESERVE) {
207                         ds = FADER_RESERVE;
208                 }
209
210                 /*
211                   if ds == w, the pattern does not need to be translated
212                   if ds == 0 (or FADER_RESERVE), the pattern needs to be moved
213                       w to the left, which is -w in pattern space, and w in
214                       user space
215                   if ds == 10, then the pattern needs to be moved w - 10
216                       to the left, which is -(w-10) in pattern space, which 
217                       is (w - 10) in user space
218
219                   thus: translation = (w - ds)
220                  */
221
222                 cairo_set_source (cr, pattern);
223                 cairo_matrix_init_translate (&matrix, w - ds, 0);
224                 cairo_pattern_set_matrix (pattern, &matrix);
225                 cairo_rectangle (cr, 0, 0, w, h);
226                 cairo_fill (cr);
227         }
228                 
229         /* draw the unity-position line if it's not at either end*/
230         if (unity_loc > 0) {
231                 if ( _orien == VERT) {
232                         if (unity_loc < h ) {
233                                         context->set_line_width (1); 
234                                         context->set_source_rgb (0.0, 1.0, 0.0);
235                                         context->move_to (1, unity_loc);
236                                         context->line_to (girth - 2.0, unity_loc);
237                                         context->stroke ();
238                         }
239                 } else {
240                         if ( unity_loc < w ){
241                                 context->set_line_width (1); 
242                                 context->set_source_rgb (0.0, 1.0, 0.0);
243                                 context->move_to (unity_loc, 1);
244                                 context->line_to (unity_loc, girth - 2.0);
245                                 context->stroke ();
246                         }
247                 }
248         }
249         
250         if ( !_text.empty() ) {
251
252                 cairo_new_path (cr);    
253
254                 /* center text */
255                 cairo_move_to (cr, (get_width() - _text_width)/2.0, get_height()/2.0 - _text_height/2.0);
256
257                 cairo_set_source_rgba (cr, text_r, text_g, text_b, 0.9);
258                 pango_cairo_show_layout (cr, _layout->gobj());
259         } 
260         
261 //      if (Config->get_widget_prelight()) {  //pixfader does not have access to config
262                 if (_hovering) {
263                         Gtkmm2ext::rounded_rectangle (cr, 0, 0, get_width(), get_height(), 3);
264                         cairo_set_source_rgba (cr, 0.905, 0.917, 0.925, 0.1);
265                         cairo_fill (cr);
266                 }
267 //      }
268
269         last_drawn = ds;
270
271         return true;
272 }
273
274 void
275 PixFader::on_size_request (GtkRequisition* req)
276 {
277         if (_orien == VERT) {
278                 req->width = girth;
279                 req->height = span;
280         } else {
281                 req->height = girth;
282                 req->width = span;
283         }
284 }
285
286 void
287 PixFader::on_size_allocate (Gtk::Allocation& alloc)
288 {
289         DrawingArea::on_size_allocate(alloc);
290
291         if (_orien == VERT) {
292                 span = alloc.get_height();
293                 girth = alloc.get_width ();
294         } else {
295                 span = alloc.get_width();
296                 girth = alloc.get_height ();
297         }
298
299         update_unity_position ();
300
301         if (is_realized()) {
302                 create_patterns();
303                 queue_draw ();
304         }
305 }
306
307 bool
308 PixFader::on_button_press_event (GdkEventButton* ev)
309 {
310         if (ev->type != GDK_BUTTON_PRESS) {
311                 return true;
312         }
313
314         if (ev->button != 1 && ev->button != 2) {
315                 return false;
316         }
317
318         add_modal_grab ();
319         grab_loc = (_orien == VERT) ? ev->y : ev->x;
320         grab_start = (_orien == VERT) ? ev->y : ev->x;
321         grab_window = ev->window;
322         dragging = true;
323         gdk_pointer_grab(ev->window,false,
324                         GdkEventMask( Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK |Gdk::BUTTON_RELEASE_MASK),
325                         NULL,NULL,ev->time);
326
327         if (ev->button == 2) {
328                 set_adjustment_from_event (ev);
329         }
330         
331         return true;
332 }
333
334 bool
335 PixFader::on_button_release_event (GdkEventButton* ev)
336 {
337         double const ev_pos = (_orien == VERT) ? ev->y : ev->x;
338         
339         switch (ev->button) {
340         case 1:
341                 if (dragging) {
342                         remove_modal_grab();
343                         dragging = false;
344                         gdk_pointer_ungrab (GDK_CURRENT_TIME);
345
346                         if (!_hovering) {
347                                 Keyboard::magic_widget_drop_focus();
348                                 queue_draw ();
349                         }
350
351                         if (ev_pos == grab_start) {
352
353                                 /* no motion - just a click */
354
355                                 if (ev->state & Keyboard::TertiaryModifier) {
356                                         adjustment.set_value (default_value);
357                                 } else if (ev->state & Keyboard::GainFineScaleModifier) {
358                                         adjustment.set_value (adjustment.get_lower());
359                                 } else if ((_orien == VERT && ev_pos < display_span()) || (_orien == HORIZ && ev_pos > display_span())) {
360                                         /* above the current display height, remember X Window coords */
361                                         adjustment.set_value (adjustment.get_value() + adjustment.get_step_increment());
362                                 } else {
363                                         adjustment.set_value (adjustment.get_value() - adjustment.get_step_increment());
364                                 }
365                         }
366
367                 } 
368                 break;
369                 
370         case 2:
371                 if (dragging) {
372                         remove_modal_grab();
373                         dragging = false;
374                         set_adjustment_from_event (ev);
375                         gdk_pointer_ungrab (GDK_CURRENT_TIME);
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_draw ();
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);
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 }