tweak for const correctness on apple
[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, y1=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                 y1 = 0;
331                 y2 = h - 1;
332
333                 if (use_parent) {
334                         parent = get_parent();
335                         
336                         if (parent) {
337                                 c = parent->get_style()->get_fg (parent->get_state());
338                                 r = c.get_red_p ();
339                                 g = c.get_green_p ();
340                                 b = c.get_blue_p ();
341                                 context->set_source_rgb (r, g, b);
342                                 context->rectangle (0, 0, darea.get_width(), darea.get_height());
343                                 context->fill ();
344                         }
345
346                 } else {
347
348                         c = get_style()->get_bg (get_state());
349                         r = c.get_red_p ();
350                         g = c.get_green_p ();
351                         b = c.get_blue_p ();
352                         context->set_source_rgb (r, g, b);
353                         context->rectangle (0, 0, darea.get_width() - ((darea.get_width()+1) % 2), darea.get_height());
354                         context->fill ();
355                 }
356                 
357                 c = get_style()->get_fg (get_state());
358                 r = c.get_red_p ();
359                 g = c.get_green_p ();
360                 b = c.get_blue_p ();
361                 context->set_source_rgb (r, g, b);
362                 context->move_to (x1, 0);
363                 context->line_to (x1, h);
364                 context->stroke ();
365                 break;
366
367         case Blob:
368                 w = darea.get_width() - 1;
369                 h = darea.get_height();
370                 x1 = (gint) floor (w * fract);
371                 x2 = min (w-2,h-2);
372
373                 if (use_parent) {
374                         parent = get_parent();
375                         
376                         if (parent) {
377                                 c = parent->get_style()->get_fg (parent->get_state());
378                                 r = c.get_red_p ();
379                                 g = c.get_green_p ();
380                                 b = c.get_blue_p ();
381                                 context->set_source_rgb (r, g, b);
382                                 context->rectangle (0, 0, darea.get_width(), darea.get_height());
383                                 context->fill ();
384                         }
385
386                 } else {
387
388                         c = get_style()->get_bg (get_state());
389                         r = c.get_red_p ();
390                         g = c.get_green_p ();
391                         b = c.get_blue_p ();
392                         context->set_source_rgb (r, g, b);
393                         context->rectangle (0, 0, darea.get_width() - ((darea.get_width()+1) % 2), darea.get_height());
394                         context->fill ();
395                 }
396                 
397                 c = get_style()->get_fg (get_state());
398                 r = c.get_red_p ();
399                 g = c.get_green_p ();
400                 b = c.get_blue_p ();
401                 context->arc (x1, ((h-2)/2)-1, x2, 0, 2*M_PI);
402                 break;
403
404         case CenterOut:
405                 w = darea.get_width();
406                 h = darea.get_height()-2;
407                 if (use_parent) {
408                         parent = get_parent();
409                         if (parent) {
410                                 c = parent->get_style()->get_fg (parent->get_state());
411                                 r = c.get_red_p ();
412                                 g = c.get_green_p ();
413                                 b = c.get_blue_p ();
414                                 context->set_source_rgb (r, g, b);
415                                 context->rectangle (0, 0, darea.get_width(), darea.get_height());
416                                 context->fill ();
417                         }
418                 } else {
419                         c = get_style()->get_bg (get_state());
420                         r = c.get_red_p ();
421                         g = c.get_green_p ();
422                         b = c.get_blue_p ();
423                         context->set_source_rgb (r, g, b);
424                         context->rectangle (0, 0, darea.get_width(), darea.get_height());
425                         context->fill ();
426                 }
427                 c = get_style()->get_fg (get_state());
428                 r = c.get_red_p ();
429                 g = c.get_green_p ();
430                 b = c.get_blue_p ();
431                 x1 = (w/2) - ((w*fract)/2); // center, back up half the bar width
432                 context->set_source_rgb (r, g, b);
433                 context->rectangle (x1, 1, w*fract, h);
434                 context->fill ();
435                 break;
436
437         case LeftToRight:
438
439                 w = darea.get_width() - 2;
440                 h = darea.get_height() - 2;
441
442                 x2 = (gint) floor (w * fract);
443                 y2 = h;
444                 radius = 4;
445                 if (x2 < 8) x2 = 8;
446
447                 /* border */
448
449                 context->set_source_rgb (0,0,0);
450                 cairo_rectangle (context->cobj(), 0, 0, darea.get_width(), darea.get_height());
451                 context->fill ();
452
453                 /* draw active box */
454
455                 context->set_source (pattern);
456                 rounded_rectangle (context, 1, 1, x2, y2, radius-1.5);
457                 context->fill ();
458
459 //              context->set_source (shine_pattern);
460 //              rounded_rectangle (context, 2, 3, x2-2, y2-8, radius-2);
461 //              context->fill ();
462                 break;
463
464         case RightToLeft:
465                 break;
466         case TopToBottom:
467                 break;
468         case BottomToTop:
469                 break;
470         }
471
472         /* draw label */
473
474         double xpos = -1;
475         std::string const label = get_label (xpos);
476
477         if (!label.empty()) {
478                 
479                 layout->set_text (label);
480                 
481                 int width, height, x;
482                 layout->get_pixel_size (width, height);
483
484                 if (xpos == -1) {
485                         x = max (3, 1 + (x2 - (width/2)));
486                         x = min (darea.get_width() - width - 3, (int) lrint (xpos));
487                 } else {
488                         x = lrint (darea.get_width() * xpos);
489                 }
490
491                 c = get_style()->get_text (get_state());
492                 r = c.get_red_p ();
493                 g = c.get_green_p ();
494                 b = c.get_blue_p ();
495                 context->set_source_rgb (r, g, b);
496                 context->move_to (x, (darea.get_height()/2) - (height/2));
497                 layout->show_in_cairo_context (context);
498         }
499         
500         return true;
501 }
502
503 void
504 BarController::set_style (barStyle s)
505 {
506         _style = s;
507         darea.queue_draw ();
508 }
509
510 gint
511 BarController::switch_to_bar ()
512 {
513         if (switching) {
514                 return FALSE;
515         }
516
517         switching = true;
518
519         if (get_child() == &darea) {
520                 return FALSE;
521         }
522
523         remove ();
524         add (darea);
525         darea.show ();
526
527         switching = false;
528
529         SpinnerActive (false); /* EMIT SIGNAL */
530         
531         return FALSE;
532 }
533
534 gint
535 BarController::switch_to_spinner ()
536 {
537         if (switching) {
538                 return FALSE;
539         }
540
541         switching = true;
542
543         if (get_child() == &spinner) {
544                 return FALSE;
545         }
546
547         remove ();
548         add (spinner);
549         spinner.show ();
550         spinner.select_region (0, spinner.get_text_length());
551         spinner.grab_focus ();
552
553         switching = false;
554
555         SpinnerActive (true); /* EMIT SIGNAL */
556
557         return FALSE;
558 }
559
560 void
561 BarController::entry_activated ()
562 {
563         switch_to_bar ();
564 }
565
566 bool
567 BarController::entry_focus_out (GdkEventFocus* /*ev*/)
568 {
569         entry_activated ();
570         return true;
571 }
572
573 void
574 BarController::set_use_parent (bool yn)
575 {
576         use_parent = yn;
577         queue_draw ();
578 }
579
580 void
581 BarController::set_sensitive (bool yn)
582 {
583         Frame::set_sensitive (yn);
584         darea.set_sensitive (yn);
585 }
586
587 /* 
588     This is called when we need to update the adjustment with the value
589     from the spinner's text entry.
590     
591     We need to use Gtk::Entry::get_text to avoid recursive nastiness :)
592     
593     If we're not in logarithmic mode we can return false to use the 
594     default conversion.
595     
596     In theory we should check for conversion errors but set numeric
597     mode to true on the spinner prevents invalid input.
598 */
599 int
600 BarController::entry_input (double* new_value)
601 {
602         if (!logarithmic) {
603                 return false;
604         }
605
606         // extract a double from the string and take its log
607         Entry *entry = dynamic_cast<Entry *>(&spinner);
608         double value;
609
610         {
611                 // Switch to user's preferred locale so that
612                 // if they use different LC_NUMERIC conventions,
613                 // we will honor them.
614
615                 PBD::LocaleGuard lg ("");
616                 sscanf (entry->get_text().c_str(), "%lf", &value);
617         }
618
619         *new_value = log(value);
620
621         return true;
622 }
623
624 /* 
625     This is called when we need to update the spinner's text entry 
626     with the value of the adjustment.
627     
628     We need to use Gtk::Entry::set_text to avoid recursive nastiness :)
629     
630     If we're not in logarithmic mode we can return false to use the 
631     default conversion.
632 */
633 bool
634 BarController::entry_output ()
635 {
636         if (!logarithmic) {
637                 return false;
638         }
639
640         // generate the exponential and turn it into a string
641         // convert to correct locale. 
642         
643         stringstream stream;
644         string str;
645
646         char buf[128];
647
648         {
649                 // Switch to user's preferred locale so that
650                 // if they use different LC_NUMERIC conventions,
651                 // we will honor them.
652                 
653                 PBD::LocaleGuard lg ("");
654                 snprintf (buf, sizeof (buf), "%g", exp (spinner.get_adjustment()->get_value()));
655         }
656
657         Entry *entry = dynamic_cast<Entry *>(&spinner);
658         entry->set_text(buf);
659         
660         return true;
661 }
662
663
664