Merge branch 'master' into windows
[ardour.git] / libs / gtkmm2ext / barcontroller.cc
1 /*
2     Copyright (C) 2004 Paul Davis
3     This program is free software; you can redistribute it and/or modify
4     it under the terms of the GNU General Public License as published by
5     the Free Software Foundation; either version 2 of the License, or
6     (at your option) any later version.
7
8     This program is distributed in the hope that it will be useful,
9     but WITHOUT ANY WARRANTY; without even the implied warranty of
10     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11     GNU General Public License for more details.
12
13     You should have received a copy of the GNU General Public License
14     along with this program; if not, write to the Free Software
15     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
16
17     $Id$
18 */
19
20 #include <string>
21 #include <sstream>
22 #include <climits>
23 #include <cstdio>
24 #include <cmath>
25 #include <algorithm>
26
27 #include <pbd/controllable.h>
28 #include <pbd/locale_guard.h>
29
30 #include "gtkmm2ext/gtk_ui.h"
31 #include "gtkmm2ext/utils.h"
32 #include "gtkmm2ext/keyboard.h"
33 #include "gtkmm2ext/barcontroller.h"
34
35 #include "i18n.h"
36
37 using namespace std;
38 using namespace Gtk;
39 using namespace Gtkmm2ext;
40
41 BarController::BarController (Gtk::Adjustment& adj,
42                               boost::shared_ptr<PBD::Controllable> mc)
43
44         : adjustment (adj),
45           binding_proxy (mc),
46           spinner (adjustment)
47
48 {                         
49         _style = LeftToRight;
50         grabbed = false;
51         switching = false;
52         switch_on_release = false;
53         use_parent = false;
54         logarithmic = false;
55
56         layout = darea.create_pango_layout("");
57
58         set_shadow_type (SHADOW_NONE);
59
60         initial_value = adjustment.get_value ();
61
62         adjustment.signal_value_changed().connect (mem_fun (*this, &Gtk::Widget::queue_draw));
63         adjustment.signal_changed().connect (mem_fun (*this, &Gtk::Widget::queue_draw));
64
65         darea.add_events (Gdk::BUTTON_RELEASE_MASK|
66                           Gdk::BUTTON_PRESS_MASK|
67                           Gdk::POINTER_MOTION_MASK|
68                           Gdk::ENTER_NOTIFY_MASK|
69                           Gdk::LEAVE_NOTIFY_MASK|
70                           Gdk::SCROLL_MASK);
71
72         darea.signal_expose_event().connect (mem_fun (*this, &BarController::expose));
73         darea.signal_motion_notify_event().connect (mem_fun (*this, &BarController::motion));
74         darea.signal_button_press_event().connect (mem_fun (*this, &BarController::button_press), false);
75         darea.signal_button_release_event().connect (mem_fun (*this, &BarController::button_release), false);
76         darea.signal_scroll_event().connect (mem_fun (*this, &BarController::scroll));
77
78         spinner.signal_activate().connect (mem_fun (*this, &BarController::entry_activated));
79         spinner.signal_focus_out_event().connect (mem_fun (*this, &BarController::entry_focus_out));
80         spinner.signal_input().connect (mem_fun (*this, &BarController::entry_input));
81         spinner.signal_output().connect (mem_fun (*this, &BarController::entry_output));
82         spinner.set_digits (9);
83         spinner.set_numeric (true);
84         
85         add (darea);
86
87         show_all ();
88 }
89
90 BarController::~BarController ()
91 {
92 //      delete pattern;
93 //      delete shine_pattern;
94 }
95
96 void
97 BarController::drop_grab ()
98 {
99         if (grabbed) {
100                 grabbed = false;
101                 darea.remove_modal_grab();
102                 StopGesture ();
103         }
104 }
105
106 bool
107 BarController::button_press (GdkEventButton* ev)
108 {
109         double fract;
110
111         if (binding_proxy.button_press_handler (ev)) {
112                 return true;
113         }
114
115         switch (ev->button) {
116         case 1:
117                 if (ev->type == GDK_2BUTTON_PRESS) {
118                         switch_on_release = true;
119                         drop_grab ();
120                 } else {
121                         switch_on_release = false;
122                         darea.add_modal_grab();
123                         grabbed = true;
124                         grab_x = ev->x;
125                         grab_window = ev->window;
126                         StartGesture ();
127                 }
128                 return true;
129                 break;
130
131         case 2:
132                 fract = ev->x / (darea.get_width() - 2.0);
133                 adjustment.set_value (adjustment.get_lower() + fract * (adjustment.get_upper() - adjustment.get_lower()));
134
135         case 3:
136                 break;
137
138         case 4:
139         case 5:
140                 break;
141         }
142
143         return false;
144 }
145
146 bool
147 BarController::button_release (GdkEventButton* ev)
148 {
149         drop_grab ();
150         
151         switch (ev->button) {
152         case 1:
153                 if (switch_on_release) {
154                         Glib::signal_idle().connect (mem_fun (*this, &BarController::switch_to_spinner));
155                         return true;
156                 }
157
158                 if ((ev->state & (Keyboard::TertiaryModifier|Keyboard::PrimaryModifier)) == Keyboard::TertiaryModifier) {
159                         adjustment.set_value (initial_value);
160                 } else {
161                         double scale;
162
163                         if ((ev->state & (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) == (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) {
164                                 scale = 0.01;
165                         } else if (ev->state & Keyboard::PrimaryModifier) {
166                                 scale = 0.1;
167                         } else {
168                                 scale = 1.0;
169                         }
170
171                         mouse_control (ev->x, ev->window, scale);
172                 }
173                 break;
174
175         case 2:
176                 break;
177                 
178         case 3:
179                 return false;
180                 
181         default:
182                 break;
183         }
184
185         return true;
186 }
187
188 bool
189 BarController::scroll (GdkEventScroll* ev)
190 {
191         double scale;
192
193         if ((ev->state & (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) == (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) {
194                 scale = 0.01;
195         } else if (ev->state & Keyboard::PrimaryModifier) {
196                 scale = 0.1;
197         } else {
198                 scale = 1.0;
199         }
200
201         switch (ev->direction) {
202         case GDK_SCROLL_UP:
203         case GDK_SCROLL_RIGHT:
204                 adjustment.set_value (adjustment.get_value() + (scale * adjustment.get_step_increment()));
205                 break;
206
207         case GDK_SCROLL_DOWN:
208         case GDK_SCROLL_LEFT:
209                 adjustment.set_value (adjustment.get_value() - (scale * adjustment.get_step_increment()));
210                 break;
211         }
212
213         return true;
214 }
215
216 bool
217 BarController::motion (GdkEventMotion* ev)
218 {
219         double scale;
220         
221         if (!grabbed) {
222                 return true;
223         }
224
225         if ((ev->state & (Keyboard::TertiaryModifier|Keyboard::PrimaryModifier)) == Keyboard::TertiaryModifier) {
226                 return TRUE;
227         }
228
229         if ((ev->state & (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) == (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) {
230                 scale = 0.01;
231         } else if (ev->state & Keyboard::PrimaryModifier) {
232                 scale = 0.1;
233         } else {
234                 scale = 1.0;
235         }
236
237         return mouse_control (ev->x, ev->window, scale);
238 }
239
240 gint
241 BarController::mouse_control (double x, GdkWindow* window, double scaling)
242 {
243         double fract = 0.0;
244         double delta;
245
246         if (window != grab_window) {
247                 grab_x = x;
248                 grab_window = window;
249                 return TRUE;
250         }
251
252         delta = x - grab_x;
253         grab_x = x;
254         
255         switch (_style) {
256         case Line:
257         case Blob:
258         case LeftToRight:
259         case CenterOut:
260                 fract = scaling * (delta / (darea.get_width() - 2));
261                 fract = min (1.0, fract);
262                 fract = max (-1.0, fract);
263                 adjustment.set_value (adjustment.get_value() + fract * (adjustment.get_upper() - adjustment.get_lower()));
264                 break;
265         default:
266                 fract = 0.0;
267         }
268         
269         
270         return TRUE;
271 }
272
273 void
274 BarController::create_patterns ()
275 {
276         Glib::RefPtr<Gdk::Window> win (darea.get_window());
277     Cairo::RefPtr<Cairo::Context> context = win->create_cairo_context();
278
279         Gdk::Color c = get_style()->get_fg (get_state());
280     float r, g, b;
281         r = c.get_red_p ();
282         g = c.get_green_p ();
283         b = c.get_blue_p ();
284
285         float rheight = darea.get_height()-2;
286
287         cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, rheight);
288         cairo_pattern_add_color_stop_rgba (pat, 0, r*0.8,g*0.8,b*0.8, 1.0);
289         cairo_pattern_add_color_stop_rgba (pat, 1, r*0.6,g*0.6,b*0.6, 1.0);
290         Cairo::RefPtr<Cairo::Pattern> p (new Cairo::Pattern (pat, false));
291         pattern = p;
292         cairo_pattern_destroy(pat);
293
294         pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, rheight);
295         cairo_pattern_add_color_stop_rgba (pat, 0, 1,1,1,0.0);
296         cairo_pattern_add_color_stop_rgba (pat, 0.2, 1,1,1,0.3);
297         cairo_pattern_add_color_stop_rgba (pat, 0.5, 1,1,1,0.0);
298         cairo_pattern_add_color_stop_rgba (pat, 1, 1,1,1,0.0);
299         Cairo::RefPtr<Cairo::Pattern> p2 (new Cairo::Pattern (pat, false));
300         shine_pattern = p2;
301         cairo_pattern_destroy(pat);
302
303 }
304
305 bool
306 BarController::expose (GdkEventExpose* /*event*/)
307 {
308         Glib::RefPtr<Gdk::Window> win (darea.get_window());
309         Cairo::RefPtr<Cairo::Context> context = win->create_cairo_context();
310
311         if( !pattern )
312                 create_patterns();
313
314         Gdk::Color c;
315         Widget* parent;
316         gint x1=0, x2=0, y2=0;
317         gint w, h;
318         double fract, radius;
319     float r, g, b;
320
321         fract = ((adjustment.get_value() - adjustment.get_lower()) /
322                  (adjustment.get_upper() - adjustment.get_lower()));
323         
324         switch (_style) {
325         case Line:
326                 w = darea.get_width() - 1;
327                 h = darea.get_height();
328                 x1 = (gint) floor (w * fract);
329                 x2 = x1;
330                 y2 = h - 1;
331
332                 if (use_parent) {
333                         parent = get_parent();
334                         
335                         if (parent) {
336                                 c = parent->get_style()->get_fg (parent->get_state());
337                                 r = c.get_red_p ();
338                                 g = c.get_green_p ();
339                                 b = c.get_blue_p ();
340                                 context->set_source_rgb (r, g, b);
341                                 context->rectangle (0, 0, darea.get_width(), darea.get_height());
342                                 context->fill ();
343                         }
344
345                 } else {
346
347                         c = get_style()->get_bg (get_state());
348                         r = c.get_red_p ();
349                         g = c.get_green_p ();
350                         b = c.get_blue_p ();
351                         context->set_source_rgb (r, g, b);
352                         context->rectangle (0, 0, darea.get_width() - ((darea.get_width()+1) % 2), darea.get_height());
353                         context->fill ();
354                 }
355                 
356                 c = get_style()->get_fg (get_state());
357                 r = c.get_red_p ();
358                 g = c.get_green_p ();
359                 b = c.get_blue_p ();
360                 context->set_source_rgb (r, g, b);
361                 context->move_to (x1, 0);
362                 context->line_to (x1, h);
363                 context->stroke ();
364                 break;
365
366         case Blob:
367                 w = darea.get_width() - 1;
368                 h = darea.get_height();
369                 x1 = (gint) floor (w * fract);
370                 x2 = min (w-2,h-2);
371
372                 if (use_parent) {
373                         parent = get_parent();
374                         
375                         if (parent) {
376                                 c = parent->get_style()->get_fg (parent->get_state());
377                                 r = c.get_red_p ();
378                                 g = c.get_green_p ();
379                                 b = c.get_blue_p ();
380                                 context->set_source_rgb (r, g, b);
381                                 context->rectangle (0, 0, darea.get_width(), darea.get_height());
382                                 context->fill ();
383                         }
384
385                 } else {
386
387                         c = get_style()->get_bg (get_state());
388                         r = c.get_red_p ();
389                         g = c.get_green_p ();
390                         b = c.get_blue_p ();
391                         context->set_source_rgb (r, g, b);
392                         context->rectangle (0, 0, darea.get_width() - ((darea.get_width()+1) % 2), darea.get_height());
393                         context->fill ();
394                 }
395                 
396                 c = get_style()->get_fg (get_state());
397                 r = c.get_red_p ();
398                 g = c.get_green_p ();
399                 b = c.get_blue_p ();
400                 context->arc (x1, ((h-2)/2)-1, x2, 0, 2*M_PI);
401                 break;
402
403         case CenterOut:
404                 w = darea.get_width();
405                 h = darea.get_height()-2;
406                 if (use_parent) {
407                         parent = get_parent();
408                         if (parent) {
409                                 c = parent->get_style()->get_fg (parent->get_state());
410                                 r = c.get_red_p ();
411                                 g = c.get_green_p ();
412                                 b = c.get_blue_p ();
413                                 context->set_source_rgb (r, g, b);
414                                 context->rectangle (0, 0, darea.get_width(), darea.get_height());
415                                 context->fill ();
416                         }
417                 } else {
418                         c = get_style()->get_bg (get_state());
419                         r = c.get_red_p ();
420                         g = c.get_green_p ();
421                         b = c.get_blue_p ();
422                         context->set_source_rgb (r, g, b);
423                         context->rectangle (0, 0, darea.get_width(), darea.get_height());
424                         context->fill ();
425                 }
426                 c = get_style()->get_fg (get_state());
427                 r = c.get_red_p ();
428                 g = c.get_green_p ();
429                 b = c.get_blue_p ();
430                 x1 = (w/2) - ((w*fract)/2); // center, back up half the bar width
431                 context->set_source_rgb (r, g, b);
432                 context->rectangle (x1, 1, w*fract, h);
433                 context->fill ();
434                 break;
435
436         case LeftToRight:
437
438                 w = darea.get_width() - 2;
439                 h = darea.get_height() - 2;
440
441                 x2 = (gint) floor (w * fract);
442                 y2 = h;
443                 radius = 4;
444                 if (x2 < 8) x2 = 8;
445
446                 /* border */
447
448                 context->set_source_rgb (0,0,0);
449                 cairo_rectangle (context->cobj(), 0, 0, darea.get_width(), darea.get_height());
450                 context->fill ();
451
452                 /* draw active box */
453
454                 context->set_source (pattern);
455                 rounded_rectangle (context, 1, 1, x2, y2, radius-1.5);
456                 context->fill ();
457
458 //              context->set_source (shine_pattern);
459 //              rounded_rectangle (context, 2, 3, x2-2, y2-8, radius-2);
460 //              context->fill ();
461                 break;
462
463         case RightToLeft:
464                 break;
465         case TopToBottom:
466                 break;
467         case BottomToTop:
468                 break;
469         }
470
471         if (!darea.get_sensitive()) {
472                 rounded_rectangle (context, 0, 0, darea.get_width(), darea.get_height(), 3);
473                 context->set_source_rgba (0.505, 0.517, 0.525, 0.6);
474                 context->fill ();
475         }
476
477         /* draw label */
478
479         double xpos = -1;
480         std::string const label = get_label (xpos);
481
482         if (!label.empty()) {
483                 
484                 layout->set_text (label);
485                 
486                 int width, height, x;
487                 layout->get_pixel_size (width, height);
488
489                 if (xpos == -1) {
490                         x = max (3, 1 + (x2 - (width/2)));
491                         x = min (darea.get_width() - width - 3, (int) lrint (xpos));
492                 } else {
493                         x = lrint (darea.get_width() * xpos);
494                 }
495
496                 c = get_style()->get_text (get_state());
497                 r = c.get_red_p ();
498                 g = c.get_green_p ();
499                 b = c.get_blue_p ();
500                 context->set_source_rgb (r, g, b);
501                 context->move_to (x, (darea.get_height()/2) - (height/2));
502                 layout->show_in_cairo_context (context);
503         }
504         
505         return true;
506 }
507
508 void
509 BarController::set_style (barStyle s)
510 {
511         _style = s;
512         darea.queue_draw ();
513 }
514
515 gint
516 BarController::switch_to_bar ()
517 {
518         if (switching) {
519                 return FALSE;
520         }
521
522         switching = true;
523
524         if (get_child() == &darea) {
525                 return FALSE;
526         }
527
528         remove ();
529         add (darea);
530         darea.show ();
531
532         switching = false;
533
534         SpinnerActive (false); /* EMIT SIGNAL */
535         
536         return FALSE;
537 }
538
539 gint
540 BarController::switch_to_spinner ()
541 {
542         if (switching) {
543                 return FALSE;
544         }
545
546         switching = true;
547
548         if (get_child() == &spinner) {
549                 return FALSE;
550         }
551
552         remove ();
553         add (spinner);
554         spinner.show ();
555         spinner.select_region (0, spinner.get_text_length());
556         spinner.grab_focus ();
557
558         switching = false;
559
560         SpinnerActive (true); /* EMIT SIGNAL */
561
562         return FALSE;
563 }
564
565 void
566 BarController::entry_activated ()
567 {
568         switch_to_bar ();
569 }
570
571 bool
572 BarController::entry_focus_out (GdkEventFocus* /*ev*/)
573 {
574         entry_activated ();
575         return true;
576 }
577
578 void
579 BarController::set_use_parent (bool yn)
580 {
581         use_parent = yn;
582         queue_draw ();
583 }
584
585 void
586 BarController::set_sensitive (bool yn)
587 {
588         Frame::set_sensitive (yn);
589         darea.set_sensitive (yn);
590 }
591
592 /* 
593     This is called when we need to update the adjustment with the value
594     from the spinner's text entry.
595     
596     We need to use Gtk::Entry::get_text to avoid recursive nastiness :)
597     
598     If we're not in logarithmic mode we can return false to use the 
599     default conversion.
600     
601     In theory we should check for conversion errors but set numeric
602     mode to true on the spinner prevents invalid input.
603 */
604 int
605 BarController::entry_input (double* new_value)
606 {
607         if (!logarithmic) {
608                 return false;
609         }
610
611         // extract a double from the string and take its log
612         Entry *entry = dynamic_cast<Entry *>(&spinner);
613         double value;
614
615         {
616                 // Switch to user's preferred locale so that
617                 // if they use different LC_NUMERIC conventions,
618                 // we will honor them.
619
620                 PBD::LocaleGuard lg ("");
621                 sscanf (entry->get_text().c_str(), "%lf", &value);
622         }
623
624         *new_value = log(value);
625
626         return true;
627 }
628
629 /* 
630     This is called when we need to update the spinner's text entry 
631     with the value of the adjustment.
632     
633     We need to use Gtk::Entry::set_text to avoid recursive nastiness :)
634     
635     If we're not in logarithmic mode we can return false to use the 
636     default conversion.
637 */
638 bool
639 BarController::entry_output ()
640 {
641         if (!logarithmic) {
642                 return false;
643         }
644
645         // generate the exponential and turn it into a string
646         // convert to correct locale. 
647         
648         stringstream stream;
649         string str;
650
651         char buf[128];
652
653         {
654                 // Switch to user's preferred locale so that
655                 // if they use different LC_NUMERIC conventions,
656                 // we will honor them.
657                 
658                 PBD::LocaleGuard lg ("");
659                 snprintf (buf, sizeof (buf), "%g", exp (spinner.get_adjustment()->get_value()));
660         }
661
662         Entry *entry = dynamic_cast<Entry *>(&spinner);
663         entry->set_text(buf);
664         
665         return true;
666 }
667
668
669