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