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