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