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