Fix some compilation warnings
[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         /* draw label */
472
473         double xpos = -1;
474         std::string const label = get_label (xpos);
475
476         if (!label.empty()) {
477                 
478                 layout->set_text (label);
479                 
480                 int width, height, x;
481                 layout->get_pixel_size (width, height);
482
483                 if (xpos == -1) {
484                         x = max (3, 1 + (x2 - (width/2)));
485                         x = min (darea.get_width() - width - 3, (int) lrint (xpos));
486                 } else {
487                         x = lrint (darea.get_width() * xpos);
488                 }
489
490                 c = get_style()->get_text (get_state());
491                 r = c.get_red_p ();
492                 g = c.get_green_p ();
493                 b = c.get_blue_p ();
494                 context->set_source_rgb (r, g, b);
495                 context->move_to (x, (darea.get_height()/2) - (height/2));
496                 layout->show_in_cairo_context (context);
497         }
498         
499         return true;
500 }
501
502 void
503 BarController::set_style (barStyle s)
504 {
505         _style = s;
506         darea.queue_draw ();
507 }
508
509 gint
510 BarController::switch_to_bar ()
511 {
512         if (switching) {
513                 return FALSE;
514         }
515
516         switching = true;
517
518         if (get_child() == &darea) {
519                 return FALSE;
520         }
521
522         remove ();
523         add (darea);
524         darea.show ();
525
526         switching = false;
527
528         SpinnerActive (false); /* EMIT SIGNAL */
529         
530         return FALSE;
531 }
532
533 gint
534 BarController::switch_to_spinner ()
535 {
536         if (switching) {
537                 return FALSE;
538         }
539
540         switching = true;
541
542         if (get_child() == &spinner) {
543                 return FALSE;
544         }
545
546         remove ();
547         add (spinner);
548         spinner.show ();
549         spinner.select_region (0, spinner.get_text_length());
550         spinner.grab_focus ();
551
552         switching = false;
553
554         SpinnerActive (true); /* EMIT SIGNAL */
555
556         return FALSE;
557 }
558
559 void
560 BarController::entry_activated ()
561 {
562         switch_to_bar ();
563 }
564
565 bool
566 BarController::entry_focus_out (GdkEventFocus* /*ev*/)
567 {
568         entry_activated ();
569         return true;
570 }
571
572 void
573 BarController::set_use_parent (bool yn)
574 {
575         use_parent = yn;
576         queue_draw ();
577 }
578
579 void
580 BarController::set_sensitive (bool yn)
581 {
582         Frame::set_sensitive (yn);
583         darea.set_sensitive (yn);
584 }
585
586 /* 
587     This is called when we need to update the adjustment with the value
588     from the spinner's text entry.
589     
590     We need to use Gtk::Entry::get_text to avoid recursive nastiness :)
591     
592     If we're not in logarithmic mode we can return false to use the 
593     default conversion.
594     
595     In theory we should check for conversion errors but set numeric
596     mode to true on the spinner prevents invalid input.
597 */
598 int
599 BarController::entry_input (double* new_value)
600 {
601         if (!logarithmic) {
602                 return false;
603         }
604
605         // extract a double from the string and take its log
606         Entry *entry = dynamic_cast<Entry *>(&spinner);
607         double value;
608
609         {
610                 // Switch to user's preferred locale so that
611                 // if they use different LC_NUMERIC conventions,
612                 // we will honor them.
613
614                 PBD::LocaleGuard lg ("");
615                 sscanf (entry->get_text().c_str(), "%lf", &value);
616         }
617
618         *new_value = log(value);
619
620         return true;
621 }
622
623 /* 
624     This is called when we need to update the spinner's text entry 
625     with the value of the adjustment.
626     
627     We need to use Gtk::Entry::set_text to avoid recursive nastiness :)
628     
629     If we're not in logarithmic mode we can return false to use the 
630     default conversion.
631 */
632 bool
633 BarController::entry_output ()
634 {
635         if (!logarithmic) {
636                 return false;
637         }
638
639         // generate the exponential and turn it into a string
640         // convert to correct locale. 
641         
642         stringstream stream;
643         string str;
644
645         char buf[128];
646
647         {
648                 // Switch to user's preferred locale so that
649                 // if they use different LC_NUMERIC conventions,
650                 // we will honor them.
651                 
652                 PBD::LocaleGuard lg ("");
653                 snprintf (buf, sizeof (buf), "%g", exp (spinner.get_adjustment()->get_value()));
654         }
655
656         Entry *entry = dynamic_cast<Entry *>(&spinner);
657         entry->set_text(buf);
658         
659         return true;
660 }
661
662
663