Add bbt_add that does not take Metric parameter.
[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
29 #include <gtkmm2ext/gtk_ui.h>
30 #include <gtkmm2ext/utils.h>
31 #include <gtkmm2ext/barcontroller.h>
32
33 #include "i18n.h"
34
35 using namespace std;
36 using namespace Gtk;
37 using namespace Gtkmm2ext;
38
39 BarController::BarController (Gtk::Adjustment& adj,
40                               boost::shared_ptr<PBD::Controllable> mc)
41
42         : adjustment (adj),
43           binding_proxy (mc),
44           spinner (adjustment)
45
46 {                         
47         _style = LeftToRight;
48         grabbed = false;
49         switching = false;
50         switch_on_release = false;
51         use_parent = false;
52         logarithmic = false;
53
54         layout = darea.create_pango_layout("");
55
56         set_shadow_type (SHADOW_NONE);
57
58         initial_value = adjustment.get_value ();
59
60         adjustment.signal_value_changed().connect (mem_fun (*this, &Gtk::Widget::queue_draw));
61         adjustment.signal_changed().connect (mem_fun (*this, &Gtk::Widget::queue_draw));
62
63         darea.add_events (Gdk::BUTTON_RELEASE_MASK|
64                           Gdk::BUTTON_PRESS_MASK|
65                           Gdk::POINTER_MOTION_MASK|
66                           Gdk::ENTER_NOTIFY_MASK|
67                           Gdk::LEAVE_NOTIFY_MASK|
68                           Gdk::SCROLL_MASK);
69
70         darea.signal_expose_event().connect (mem_fun (*this, &BarController::expose));
71         darea.signal_motion_notify_event().connect (mem_fun (*this, &BarController::motion));
72         darea.signal_button_press_event().connect (mem_fun (*this, &BarController::button_press), false);
73         darea.signal_button_release_event().connect (mem_fun (*this, &BarController::button_release), false);
74         darea.signal_scroll_event().connect (mem_fun (*this, &BarController::scroll));
75
76         spinner.signal_activate().connect (mem_fun (*this, &BarController::entry_activated));
77         spinner.signal_focus_out_event().connect (mem_fun (*this, &BarController::entry_focus_out));
78         spinner.signal_input().connect (mem_fun (*this, &BarController::entry_input));
79         spinner.signal_output().connect (mem_fun (*this, &BarController::entry_output));
80         spinner.set_digits (3);
81         spinner.set_numeric (true);
82
83         add (darea);
84         show_all ();
85 }
86
87 void
88 BarController::drop_grab ()
89 {
90         if (grabbed) {
91                 grabbed = false;
92                 darea.remove_modal_grab();
93                 StopGesture ();
94         }
95 }
96
97 bool
98 BarController::button_press (GdkEventButton* ev)
99 {
100         double fract;
101
102         if (binding_proxy.button_press_handler (ev)) {
103                 return true;
104         }
105
106         switch (ev->button) {
107         case 1:
108                 if (ev->type == GDK_2BUTTON_PRESS) {
109                         switch_on_release = true;
110                         drop_grab ();
111                 } else {
112                         switch_on_release = false;
113                         darea.add_modal_grab();
114                         grabbed = true;
115                         grab_x = ev->x;
116                         grab_window = ev->window;
117                         StartGesture ();
118                 }
119                 return true;
120                 break;
121
122         case 2:
123                 fract = ev->x / (darea.get_width() - 2.0);
124                 adjustment.set_value (adjustment.get_lower() + fract * (adjustment.get_upper() - adjustment.get_lower()));
125
126         case 3:
127                 break;
128
129         case 4:
130         case 5:
131                 break;
132         }
133
134         return false;
135 }
136
137 bool
138 BarController::button_release (GdkEventButton* ev)
139 {
140         drop_grab ();
141         
142         switch (ev->button) {
143         case 1:
144                 if (switch_on_release) {
145                         Glib::signal_idle().connect (mem_fun (*this, &BarController::switch_to_spinner));
146                         return true;
147                 }
148
149                 if ((ev->state & (GDK_SHIFT_MASK|GDK_CONTROL_MASK)) == GDK_SHIFT_MASK) {
150                         adjustment.set_value (initial_value);
151                 } else {
152                         double scale;
153
154                         if ((ev->state & (GDK_CONTROL_MASK|GDK_SHIFT_MASK)) == (GDK_CONTROL_MASK|GDK_SHIFT_MASK)) {
155                                 scale = 0.01;
156                         } else if (ev->state & GDK_CONTROL_MASK) {
157                                 scale = 0.1;
158                         } else {
159                                 scale = 1.0;
160                         }
161
162                         mouse_control (ev->x, ev->window, scale);
163                 }
164                 break;
165
166         case 2:
167                 break;
168                 
169         case 3:
170                 return false;
171                 
172         default:
173                 break;
174         }
175
176         return true;
177 }
178
179 bool
180 BarController::scroll (GdkEventScroll* ev)
181 {
182         double scale;
183
184         if ((ev->state & (GDK_CONTROL_MASK|GDK_SHIFT_MASK)) == (GDK_CONTROL_MASK|GDK_SHIFT_MASK)) {
185                 scale = 0.01;
186         } else if (ev->state & GDK_CONTROL_MASK) {
187                 scale = 0.1;
188         } else {
189                 scale = 1.0;
190         }
191
192         switch (ev->direction) {
193         case GDK_SCROLL_UP:
194         case GDK_SCROLL_RIGHT:
195                 adjustment.set_value (adjustment.get_value() + (scale * adjustment.get_step_increment()));
196                 break;
197
198         case GDK_SCROLL_DOWN:
199         case GDK_SCROLL_LEFT:
200                 adjustment.set_value (adjustment.get_value() - (scale * adjustment.get_step_increment()));
201                 break;
202         }
203
204         return true;
205 }
206
207 bool
208 BarController::motion (GdkEventMotion* ev)
209 {
210         double scale;
211         
212         if (!grabbed) {
213                 return true;
214         }
215
216         if ((ev->state & (GDK_SHIFT_MASK|GDK_CONTROL_MASK)) == GDK_SHIFT_MASK) {
217                 return TRUE;
218         }
219
220         if ((ev->state & (GDK_CONTROL_MASK|GDK_SHIFT_MASK)) == (GDK_CONTROL_MASK|GDK_SHIFT_MASK)) {
221                 scale = 0.01;
222         } else if (ev->state & GDK_CONTROL_MASK) {
223                 scale = 0.1;
224         } else {
225                 scale = 1.0;
226         }
227
228         return mouse_control (ev->x, ev->window, scale);
229 }
230
231 gint
232 BarController::mouse_control (double x, GdkWindow* window, double scaling)
233 {
234         double fract = 0.0;
235         double delta;
236
237         if (window != grab_window) {
238                 grab_x = x;
239                 grab_window = window;
240                 return TRUE;
241         }
242
243         delta = x - grab_x;
244         grab_x = x;
245
246         switch (_style) {
247         case Line:
248         case LeftToRight:
249                 fract = scaling * (delta / (darea.get_width() - 2));
250                 fract = min (1.0, fract);
251                 fract = max (-1.0, fract);
252                 adjustment.set_value (adjustment.get_value() + fract * (adjustment.get_upper() - adjustment.get_lower()));
253                 break;
254
255         default:
256                 fract = 0.0;
257         }
258         
259         
260         return TRUE;
261 }
262
263 bool
264 BarController::expose (GdkEventExpose* /*event*/)
265 {
266         Glib::RefPtr<Gdk::Window> win (darea.get_window());
267         Widget* parent;
268         gint x1=0, x2=0, y1=0, y2=0;
269         gint w, h;
270         double fract;
271
272         fract = ((adjustment.get_value() - adjustment.get_lower()) /
273                  (adjustment.get_upper() - adjustment.get_lower()));
274         
275         switch (_style) {
276         case Line:
277                 w = darea.get_width() - 1;
278                 h = darea.get_height();
279                 x1 = (gint) floor (w * fract);
280                 x2 = x1;
281                 y1 = 0;
282                 y2 = h - 1;
283
284                 if (use_parent) {
285                         parent = get_parent();
286                         
287                         if (parent) {
288                                 win->draw_rectangle (parent->get_style()->get_fg_gc (parent->get_state()),
289                                                      true,
290                                                      0, 0, darea.get_width(), darea.get_height());
291                         }
292
293                 } else {
294
295                         win->draw_rectangle (get_style()->get_bg_gc (get_state()),
296                                              true,
297                                              0, 0, darea.get_width() - ((darea.get_width()+1) % 2), darea.get_height());
298                 }
299                 
300                 win->draw_line (get_style()->get_fg_gc (get_state()), x1, 0, x1, h);
301                 break;
302
303         case CenterOut:
304                 break;
305
306         case LeftToRight:
307
308                 w = darea.get_width() - 2;
309                 h = darea.get_height() - 2;
310
311                 x1 = 0;
312                 x2 = (gint) floor (w * fract);
313                 y1 = 0;
314                 y2 = h - 1;
315
316                 win->draw_rectangle (get_style()->get_bg_gc (get_state()),
317                                     false,
318                                     0, 0, darea.get_width() - 1, darea.get_height() - 1);
319
320                 /* draw active box */
321
322                 win->draw_rectangle (get_style()->get_fg_gc (get_state()),
323                                     true,
324                                     1 + x1,
325                                     1 + y1,
326                                     x2,
327                                     1 + y2);
328                 
329                 /* draw inactive box */
330
331                 win->draw_rectangle (get_style()->get_fg_gc (STATE_INSENSITIVE),
332                                     true,
333                                     1 + x2,
334                                     1 + y1,
335                                     w - x2,
336                                     1 + y2);
337
338                 break;
339
340         case RightToLeft:
341                 break;
342         case TopToBottom:
343                 break;
344         case BottomToTop:
345                 break;
346         }
347
348         /* draw label */
349
350         int xpos = -1;
351         std::string const label = get_label (xpos);
352
353         if (!label.empty()) {
354                 
355                 layout->set_text (label);
356                 
357                 int width, height;
358                 layout->get_pixel_size (width, height);
359
360                 if (xpos == -1) {
361                         xpos = max (3, 1 + (x2 - (width/2)));
362                         xpos = min (darea.get_width() - width - 3, xpos);
363                 }
364                 
365                 win->draw_layout (get_style()->get_text_gc (get_state()),
366                                   xpos,
367                                   (darea.get_height()/2) - (height/2),
368                                   layout);
369         }
370         
371         return true;
372 }
373
374 void
375 BarController::set_style (barStyle s)
376 {
377         _style = s;
378         darea.queue_draw ();
379 }
380
381 gint
382 BarController::switch_to_bar ()
383 {
384         if (switching) {
385                 return FALSE;
386         }
387
388         switching = true;
389
390         if (get_child() == &darea) {
391                 return FALSE;
392         }
393
394         remove ();
395         add (darea);
396         darea.show ();
397
398         switching = false;
399         return FALSE;
400 }
401
402 gint
403 BarController::switch_to_spinner ()
404 {
405         if (switching) {
406                 return FALSE;
407         }
408
409         switching = true;
410
411         if (get_child() == &spinner) {
412                 return FALSE;
413         }
414
415         remove ();
416         add (spinner);
417         spinner.show ();
418         spinner.select_region (0, spinner.get_text_length());
419         spinner.grab_focus ();
420
421         switching = false;
422         return FALSE;
423 }
424
425 void
426 BarController::entry_activated ()
427 {
428         switch_to_bar ();
429 }
430
431 bool
432 BarController::entry_focus_out (GdkEventFocus* /*ev*/)
433 {
434         entry_activated ();
435         return true;
436 }
437
438 void
439 BarController::set_use_parent (bool yn)
440 {
441         use_parent = yn;
442         queue_draw ();
443 }
444
445 void
446 BarController::set_sensitive (bool yn)
447 {
448         Frame::set_sensitive (yn);
449         darea.set_sensitive (yn);
450 }
451
452 /* 
453     This is called when we need to update the adjustment with the value
454     from the spinner's text entry.
455     
456     We need to use Gtk::Entry::get_text to avoid recursive nastiness :)
457     
458     If we're not in logarithmic mode we can return false to use the 
459     default conversion.
460     
461     In theory we should check for conversion errors but set numeric
462     mode to true on the spinner prevents invalid input.
463 */
464 int
465 BarController::entry_input (double* new_value)
466 {
467         if (!logarithmic) {
468                 return false;
469         }
470
471         // extract a double from the string and take its log
472         Entry *entry = dynamic_cast<Entry *>(&spinner);
473         stringstream stream(entry->get_text());
474         stream.imbue(std::locale(""));
475
476         double value;
477         stream >> value;
478         
479         *new_value = log(value);
480         return true;
481 }
482
483 /* 
484     This is called when we need to update the spinner's text entry 
485     with the value of the adjustment.
486     
487     We need to use Gtk::Entry::set_text to avoid recursive nastiness :)
488     
489     If we're not in logarithmic mode we can return false to use the 
490     default conversion.
491 */
492 bool
493 BarController::entry_output ()
494 {
495         if (!logarithmic) {
496                 return false;
497         }
498
499         // generate the exponential and turn it into a string
500         // convert to correct locale. 
501         
502         stringstream stream;
503         string str;
504         size_t found;
505
506         // Gtk.Entry does not like the thousands separator, so we have to  
507         // remove it after conversion from float to string.
508
509         stream.imbue(std::locale(""));
510         stream.precision(spinner.get_digits());
511
512         stream << fixed << exp(spinner.get_adjustment()->get_value());
513         
514         str=stream.str();
515
516         // find thousands separators, remove them
517         found = str.find(use_facet<numpunct<char> >(std::locale("")).thousands_sep());
518         while(found != str.npos) {
519                 str.erase(found,1);
520
521                 //find next
522                 found = str.find(use_facet<numpunct<char> >(std::locale("")).thousands_sep());
523         }
524
525         Entry *entry = dynamic_cast<Entry *>(&spinner);
526         entry->set_text(str);
527         
528         return true;
529 }
530
531
532