fix some compiler 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 #include "gtkmm2ext/cairo_widget.h"
35
36 #include "i18n.h"
37
38 using namespace std;
39 using namespace Gtk;
40 using namespace Gtkmm2ext;
41
42 BarController::BarController (Gtk::Adjustment& adj,
43                               boost::shared_ptr<PBD::Controllable> mc)
44
45         : adjustment (adj)
46         , binding_proxy (mc)
47         , spinner (adjustment)
48         , _hovering (false)
49 {                         
50         _style = LeftToRight;
51         grabbed = false;
52         switching = false;
53         switch_on_release = false;
54         use_parent = false;
55         logarithmic = false;
56
57         layout = darea.create_pango_layout("");
58
59         set_shadow_type (SHADOW_NONE);
60
61         initial_value = adjustment.get_value ();
62
63         adjustment.signal_value_changed().connect (mem_fun (*this, &Gtk::Widget::queue_draw));
64         adjustment.signal_changed().connect (mem_fun (*this, &Gtk::Widget::queue_draw));
65
66         darea.add_events (Gdk::BUTTON_RELEASE_MASK|
67                           Gdk::BUTTON_PRESS_MASK|
68                           Gdk::POINTER_MOTION_MASK|
69                           Gdk::ENTER_NOTIFY_MASK|
70                           Gdk::LEAVE_NOTIFY_MASK|
71                           Gdk::SCROLL_MASK);
72
73         darea.signal_expose_event().connect (mem_fun (*this, &BarController::expose));
74         darea.signal_motion_notify_event().connect (mem_fun (*this, &BarController::motion));
75         darea.signal_button_press_event().connect (mem_fun (*this, &BarController::button_press), false);
76         darea.signal_button_release_event().connect (mem_fun (*this, &BarController::button_release), false);
77         darea.signal_scroll_event().connect (mem_fun (*this, &BarController::scroll));
78         darea.signal_enter_notify_event().connect (mem_fun (*this, &BarController::on_enter_notify_event));
79         darea.signal_leave_notify_event().connect (mem_fun (*this, &BarController::on_leave_notify_event));
80
81         spinner.signal_activate().connect (mem_fun (*this, &BarController::entry_activated));
82         spinner.signal_focus_out_event().connect (mem_fun (*this, &BarController::entry_focus_out));
83         spinner.signal_input().connect (mem_fun (*this, &BarController::entry_input));
84         spinner.signal_output().connect (mem_fun (*this, &BarController::entry_output));
85         spinner.set_digits (9);
86         spinner.set_numeric (true);
87         
88         add (darea);
89
90         show_all ();
91 }
92
93 BarController::~BarController ()
94 {
95 }
96
97 bool
98 BarController::on_enter_notify_event (GdkEventCrossing*)
99 {
100         _hovering = true;
101         Keyboard::magic_widget_grab_focus ();
102         queue_draw ();
103         return false;
104 }
105
106 bool
107 BarController::on_leave_notify_event (GdkEventCrossing*)
108 {
109         _hovering = false;
110         Keyboard::magic_widget_drop_focus();
111         queue_draw ();
112         return false;
113 }
114
115 void
116 BarController::drop_grab ()
117 {
118         if (grabbed) {
119                 grabbed = false;
120                 darea.remove_modal_grab();
121                 StopGesture ();
122         }
123 }
124
125 bool
126 BarController::button_press (GdkEventButton* ev)
127 {
128         double fract;
129
130         if (binding_proxy.button_press_handler (ev)) {
131                 return true;
132         }
133
134         switch (ev->button) {
135         case 1:
136                 if (ev->type == GDK_2BUTTON_PRESS) {
137                         switch_on_release = true;
138                         drop_grab ();
139                 } else {
140                         switch_on_release = false;
141                         darea.add_modal_grab();
142                         grabbed = true;
143                         grab_x = ev->x;
144                         grab_window = ev->window;
145                         StartGesture ();
146                 }
147                 return true;
148                 break;
149
150         case 2:
151                 fract = ev->x / (darea.get_width() - 2.0);
152                 adjustment.set_value (adjustment.get_lower() + fract * (adjustment.get_upper() - adjustment.get_lower()));
153
154         case 3:
155                 break;
156
157         case 4:
158         case 5:
159                 break;
160         }
161
162         return false;
163 }
164
165 bool
166 BarController::button_release (GdkEventButton* ev)
167 {
168         drop_grab ();
169         
170         switch (ev->button) {
171         case 1:
172                 if (switch_on_release) {
173                         Glib::signal_idle().connect (mem_fun (*this, &BarController::switch_to_spinner));
174                         return true;
175                 }
176
177                 if ((ev->state & (Keyboard::TertiaryModifier|Keyboard::PrimaryModifier)) == Keyboard::TertiaryModifier) {
178                         adjustment.set_value (initial_value);
179                 } else {
180                         double scale;
181
182                         if ((ev->state & (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) == (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) {
183                                 scale = 0.01;
184                         } else if (ev->state & Keyboard::PrimaryModifier) {
185                                 scale = 0.1;
186                         } else {
187                                 scale = 1.0;
188                         }
189
190                         mouse_control (ev->x, ev->window, scale);
191                 }
192                 break;
193
194         case 2:
195                 break;
196                 
197         case 3:
198                 return false;
199                 
200         default:
201                 break;
202         }
203
204         return true;
205 }
206
207 bool
208 BarController::scroll (GdkEventScroll* ev)
209 {
210         double scale;
211
212         if ((ev->state & (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) == (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) {
213                 scale = 0.01;
214         } else if (ev->state & Keyboard::PrimaryModifier) {
215                 scale = 0.1;
216         } else {
217                 scale = 1.0;
218         }
219
220         switch (ev->direction) {
221         case GDK_SCROLL_UP:
222         case GDK_SCROLL_RIGHT:
223                 adjustment.set_value (adjustment.get_value() + (scale * adjustment.get_step_increment()));
224                 break;
225
226         case GDK_SCROLL_DOWN:
227         case GDK_SCROLL_LEFT:
228                 adjustment.set_value (adjustment.get_value() - (scale * adjustment.get_step_increment()));
229                 break;
230         }
231
232         return true;
233 }
234
235 bool
236 BarController::motion (GdkEventMotion* ev)
237 {
238         double scale;
239         
240         if (!grabbed) {
241                 return true;
242         }
243
244         if ((ev->state & (Keyboard::TertiaryModifier|Keyboard::PrimaryModifier)) == Keyboard::TertiaryModifier) {
245                 return TRUE;
246         }
247
248         if ((ev->state & (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) == (Keyboard::PrimaryModifier|Keyboard::TertiaryModifier)) {
249                 scale = 0.01;
250         } else if (ev->state & Keyboard::PrimaryModifier) {
251                 scale = 0.1;
252         } else {
253                 scale = 1.0;
254         }
255
256         return mouse_control (ev->x, ev->window, scale);
257 }
258
259 gint
260 BarController::mouse_control (double x, GdkWindow* window, double scaling)
261 {
262         double fract = 0.0;
263         double delta;
264
265         if (window != grab_window) {
266                 grab_x = x;
267                 grab_window = window;
268                 return TRUE;
269         }
270
271         delta = x - grab_x;
272         grab_x = x;
273         
274         switch (_style) {
275         case Line:
276         case Blob:
277         case LeftToRight:
278         case CenterOut:
279                 fract = scaling * (delta / (darea.get_width() - 2));
280                 fract = min (1.0, fract);
281                 fract = max (-1.0, fract);
282                 adjustment.set_value (adjustment.get_value() + fract * (adjustment.get_upper() - adjustment.get_lower()));
283                 break;
284         default:
285                 fract = 0.0;
286         }
287         
288         
289         return TRUE;
290 }
291
292 Gdk::Color
293 BarController::get_parent_bg ()
294 {
295         Widget* parent;
296
297         parent = get_parent ();
298
299         while (parent) {
300                 static const char* has_cairo_widget_background_info = "has_cairo_widget_background_info";
301                 void* p = g_object_get_data (G_OBJECT(parent->gobj()), has_cairo_widget_background_info);
302
303                 if (p) {
304                         Glib::RefPtr<Gtk::Style> style = parent->get_style();
305                         return style->get_bg (get_state());
306                 }
307                 
308                 if (!parent->get_has_window()) {
309                         parent = parent->get_parent();
310                 } else {
311                         break;
312                 }
313         }
314
315         if (parent && parent->get_has_window()) {
316                 return parent->get_style ()->get_bg (parent->get_state());
317         } 
318
319         return get_style ()->get_bg (get_state());
320 }
321
322 bool
323 BarController::expose (GdkEventExpose* /*event*/)
324 {
325         Glib::RefPtr<Gdk::Window> win (darea.get_window());
326         Cairo::RefPtr<Cairo::Context> context = win->create_cairo_context();
327         cairo_t* cr = context->cobj();
328
329         Gdk::Color fg_col = get_style()->get_fg (get_state());
330
331         double fract = ((adjustment.get_value() - adjustment.get_lower()) /
332                  (adjustment.get_upper() - adjustment.get_lower()));
333         
334         gint w = darea.get_width() ;
335         gint h = darea.get_height();
336         gint bar_start, bar_width;
337         double radius = 4;
338
339         switch (_style) {
340         case Line:
341                 bar_start = (gint) floor ((w-1) * fract);
342                 bar_width = 1;
343                 break;
344
345         case Blob:
346                 // ????
347                 break;
348
349         case CenterOut:
350         bar_width = (w*fract);
351         bar_start = (w/2) - bar_width/2; // center, back up half the bar width
352                 break;
353
354         case LeftToRight:
355                 bar_start = 1;
356                 bar_width = floor((w-2)*fract);
357
358                 break;
359
360         case RightToLeft:
361                 break;
362         case TopToBottom:
363                 break;
364         case BottomToTop:
365                 break;
366         }
367
368         //fill in the bg rect ... 
369         Gdk::Color c = get_parent_bg(); //get_style()->get_bg (Gtk::STATE_PRELIGHT);  //why prelight?  Shouldn't we be using the parent's color?  maybe   get_parent_bg  ?
370         CairoWidget::set_source_rgb_a (cr, c);
371         cairo_rectangle (cr, 0, 0, w, h);
372         cairo_fill(cr);
373
374         //"slot"
375         cairo_set_source_rgba (cr, 0.17, 0.17, 0.17, 1.0);
376         Gtkmm2ext::rounded_rectangle (cr, 1, 1, w-2, h-2, radius-0.5);
377         cairo_fill(cr);
378
379         //mask off the corners
380         Gtkmm2ext::rounded_rectangle (cr, 1, 1, w-2, h-2, radius-0.5);
381         cairo_clip(cr);
382         
383                 //background gradient
384                 if ( !CairoWidget::flat_buttons() ) {
385                         cairo_pattern_t *bg_gradient = cairo_pattern_create_linear (0.0, 0.0, 0, h);
386                         cairo_pattern_add_color_stop_rgba (bg_gradient, 0, 0, 0, 0, 0.4);
387                         cairo_pattern_add_color_stop_rgba (bg_gradient, 0.2, 0, 0, 0, 0.2);
388                         cairo_pattern_add_color_stop_rgba (bg_gradient, 1, 0, 0, 0, 0.0);
389                         cairo_set_source (cr, bg_gradient);
390                         Gtkmm2ext::rounded_rectangle (cr, 1, 1, w-2, h-2, radius-1.5);
391                         cairo_fill (cr);
392                         cairo_pattern_destroy(bg_gradient);
393                 }
394                 
395                 //fg color
396                 CairoWidget::set_source_rgb_a (cr, fg_col, 1.0);
397                 Gtkmm2ext::rounded_rectangle (cr, bar_start, 1, bar_width, h-2, radius - 1.5);
398                 cairo_fill(cr);
399
400                 //fg gradient
401                 if (!CairoWidget::flat_buttons() ) {
402                         cairo_pattern_t * fg_gradient = cairo_pattern_create_linear (0.0, 0.0, 0, h);
403                         cairo_pattern_add_color_stop_rgba (fg_gradient, 0, 0, 0, 0, 0.0);
404                         cairo_pattern_add_color_stop_rgba (fg_gradient, 0.1, 0, 0, 0, 0.0);
405                         cairo_pattern_add_color_stop_rgba (fg_gradient, 1, 0, 0, 0, 0.3);
406                         cairo_set_source (cr, fg_gradient);
407                         Gtkmm2ext::rounded_rectangle (cr, bar_start, 1, bar_width, h-2, radius - 1.5);
408                         cairo_fill (cr);
409                         cairo_pattern_destroy(fg_gradient);
410                 }
411                 
412         cairo_reset_clip(cr);
413
414         //black border
415         cairo_set_line_width (cr, 1.0);
416         cairo_set_source_rgba (cr, 0, 0, 0, 1.0);
417         Gtkmm2ext::rounded_rectangle (cr, 0.5, 0.5, w-1, h-1, radius);
418         cairo_stroke(cr);
419
420         /* draw the unity-position line if it's not at either end*/
421 /*      if (unity_loc > 0) {
422                 context->set_line_width (1);
423                 cairo_set_source_rgba (cr, 1,1,1, 1.0);
424                 if ( _orien == VERT) {
425                         if (unity_loc < h ) {
426                                 context->move_to (2.5, unity_loc + radius + .5);
427                                 context->line_to (girth-2.5, unity_loc + radius + .5);
428                                 context->stroke ();
429                         }
430                 } else {
431                         if ( unity_loc < w ){
432                                 context->move_to (unity_loc - radius + .5, 3.5);
433                                 context->line_to (unity_loc - radius + .5, girth-3.5);
434                                 context->stroke ();
435                         }
436                 }
437         }*/
438         
439         if (!darea.get_sensitive()) {
440                 rounded_rectangle (context, 0, 0, darea.get_width(), darea.get_height(), 3);
441                 context->set_source_rgba (0.505, 0.517, 0.525, 0.6);
442                 context->fill ();
443         } else if (_hovering) {
444                 Gtkmm2ext::rounded_rectangle (cr, 1, 1, w-2, h-2, radius);
445                 cairo_set_source_rgba (cr, 0.905, 0.917, 0.925, 0.1);
446                 cairo_fill (cr);
447         }
448
449         /* draw label */
450
451         double xpos = -1;
452         std::string const label = get_label (xpos);
453         if (!label.empty()) {
454                 
455                 int twidth, theight;
456                 layout->set_text (label);
457                 layout->get_pixel_size (twidth, theight);
458
459                 if (fract > 0.5) {
460                         cairo_set_source_rgba (cr, 0.17, 0.17, 0.17, 1.0);
461                         context->move_to ( 5, (darea.get_height()/2) - (theight/2));
462                 } else {
463                         c = get_style()->get_text (get_state());
464                         CairoWidget::set_source_rgb_a (cr, c, 0.7);
465                         context->move_to ( w-twidth-5, (darea.get_height()/2) - (theight/2));
466                 }
467                 layout->show_in_cairo_context (context);
468         }
469         
470         return true;
471 }
472
473 void
474 BarController::set_style (barStyle s)
475 {
476         _style = s;
477         darea.queue_draw ();
478 }
479
480 gint
481 BarController::switch_to_bar ()
482 {
483         if (switching) {
484                 return FALSE;
485         }
486
487         switching = true;
488
489         if (get_child() == &darea) {
490                 return FALSE;
491         }
492
493         remove ();
494         add (darea);
495         darea.show ();
496
497         switching = false;
498
499         SpinnerActive (false); /* EMIT SIGNAL */
500         
501         return FALSE;
502 }
503
504 gint
505 BarController::switch_to_spinner ()
506 {
507         if (switching) {
508                 return FALSE;
509         }
510
511         switching = true;
512
513         if (get_child() == &spinner) {
514                 return FALSE;
515         }
516
517         remove ();
518         add (spinner);
519         spinner.show ();
520         spinner.select_region (0, spinner.get_text_length());
521         spinner.grab_focus ();
522
523         switching = false;
524
525         SpinnerActive (true); /* EMIT SIGNAL */
526
527         return FALSE;
528 }
529
530 void
531 BarController::entry_activated ()
532 {
533         switch_to_bar ();
534 }
535
536 bool
537 BarController::entry_focus_out (GdkEventFocus* /*ev*/)
538 {
539         entry_activated ();
540         return true;
541 }
542
543 void
544 BarController::set_use_parent (bool yn)
545 {
546         use_parent = yn;
547         queue_draw ();
548 }
549
550 void
551 BarController::set_sensitive (bool yn)
552 {
553         Frame::set_sensitive (yn);
554         darea.set_sensitive (yn);
555 }
556
557 /* 
558     This is called when we need to update the adjustment with the value
559     from the spinner's text entry.
560     
561     We need to use Gtk::Entry::get_text to avoid recursive nastiness :)
562     
563     If we're not in logarithmic mode we can return false to use the 
564     default conversion.
565     
566     In theory we should check for conversion errors but set numeric
567     mode to true on the spinner prevents invalid input.
568 */
569 int
570 BarController::entry_input (double* new_value)
571 {
572         if (!logarithmic) {
573                 return false;
574         }
575
576         // extract a double from the string and take its log
577         Entry *entry = dynamic_cast<Entry *>(&spinner);
578         double value;
579
580         {
581                 // Switch to user's preferred locale so that
582                 // if they use different LC_NUMERIC conventions,
583                 // we will honor them.
584
585                 PBD::LocaleGuard lg ("");
586                 sscanf (entry->get_text().c_str(), "%lf", &value);
587         }
588
589         *new_value = log(value);
590
591         return true;
592 }
593
594 /* 
595     This is called when we need to update the spinner's text entry 
596     with the value of the adjustment.
597     
598     We need to use Gtk::Entry::set_text to avoid recursive nastiness :)
599     
600     If we're not in logarithmic mode we can return false to use the 
601     default conversion.
602 */
603 bool
604 BarController::entry_output ()
605 {
606         if (!logarithmic) {
607                 return false;
608         }
609
610         // generate the exponential and turn it into a string
611         // convert to correct locale. 
612         
613         stringstream stream;
614         string str;
615
616         char buf[128];
617
618         {
619                 // Switch to user's preferred locale so that
620                 // if they use different LC_NUMERIC conventions,
621                 // we will honor them.
622                 
623                 PBD::LocaleGuard lg ("");
624                 snprintf (buf, sizeof (buf), "%g", exp (spinner.get_adjustment()->get_value()));
625         }
626
627         Entry *entry = dynamic_cast<Entry *>(&spinner);
628         entry->set_text(buf);
629         
630         return true;
631 }
632
633
634