5c0d444be73578ac35a661074be7d1691865702d
[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         case CenterOut:
252                 fract = scaling * (delta / (darea.get_width() - 2));
253                 fract = min (1.0, fract);
254                 fract = max (-1.0, fract);
255                 adjustment.set_value (adjustment.get_value() + fract * (adjustment.get_upper() - adjustment.get_lower()));
256                 break;
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                 w = darea.get_width();
307                 h = darea.get_height()-2;
308                 if (use_parent) {
309                         parent = get_parent();
310                         if (parent) {
311                                 win->draw_rectangle (parent->get_style()->get_fg_gc (parent->get_state()),
312                                                      true,
313                                                      0, 0, darea.get_width(), darea.get_height());
314                         } else {
315                                 win->draw_rectangle (parent->get_style()->get_bg_gc (parent->get_state()),
316                                                      true,
317                                                      0, 0, darea.get_width(), darea.get_height());
318                         }
319                 }
320                 x1 = (w/2) - ((w*fract)/2); // center, back up half the bar width
321                 win->draw_rectangle (get_style()->get_fg_gc (get_state()), true, x1, 1, w*fract, h);
322                 break;
323
324         case LeftToRight:
325
326                 w = darea.get_width() - 2;
327                 h = darea.get_height() - 2;
328
329                 x1 = 0;
330                 x2 = (gint) floor (w * fract);
331                 y1 = 0;
332                 y2 = h - 1;
333
334                 win->draw_rectangle (get_style()->get_bg_gc (get_state()),
335                                     false,
336                                     0, 0, darea.get_width() - 1, darea.get_height() - 1);
337
338                 /* draw active box */
339
340                 win->draw_rectangle (get_style()->get_fg_gc (get_state()),
341                                     true,
342                                     1 + x1,
343                                     1 + y1,
344                                     x2,
345                                     1 + y2);
346                 
347                 /* draw inactive box */
348
349                 win->draw_rectangle (get_style()->get_fg_gc (STATE_INSENSITIVE),
350                                     true,
351                                     1 + x2,
352                                     1 + y1,
353                                     w - x2,
354                                     1 + y2);
355
356                 break;
357
358         case RightToLeft:
359                 break;
360         case TopToBottom:
361                 break;
362         case BottomToTop:
363                 break;
364         }
365
366         /* draw label */
367
368         int xpos = -1;
369         std::string const label = get_label (xpos);
370
371         if (!label.empty()) {
372                 
373                 layout->set_text (label);
374                 
375                 int width, height;
376                 layout->get_pixel_size (width, height);
377
378                 if (xpos == -1) {
379                         xpos = max (3, 1 + (x2 - (width/2)));
380                         xpos = min (darea.get_width() - width - 3, xpos);
381                 }
382                 
383                 win->draw_layout (get_style()->get_text_gc (get_state()),
384                                   xpos,
385                                   (darea.get_height()/2) - (height/2),
386                                   layout);
387         }
388         
389         return true;
390 }
391
392 void
393 BarController::set_style (barStyle s)
394 {
395         _style = s;
396         darea.queue_draw ();
397 }
398
399 gint
400 BarController::switch_to_bar ()
401 {
402         if (switching) {
403                 return FALSE;
404         }
405
406         switching = true;
407
408         if (get_child() == &darea) {
409                 return FALSE;
410         }
411
412         remove ();
413         add (darea);
414         darea.show ();
415
416         switching = false;
417
418         SpinnerActive (false); /* EMIT SIGNAL */
419         
420         return FALSE;
421 }
422
423 gint
424 BarController::switch_to_spinner ()
425 {
426         if (switching) {
427                 return FALSE;
428         }
429
430         switching = true;
431
432         if (get_child() == &spinner) {
433                 return FALSE;
434         }
435
436         remove ();
437         add (spinner);
438         spinner.show ();
439         spinner.select_region (0, spinner.get_text_length());
440         spinner.grab_focus ();
441
442         switching = false;
443
444         SpinnerActive (true); /* EMIT SIGNAL */
445
446         return FALSE;
447 }
448
449 void
450 BarController::entry_activated ()
451 {
452         switch_to_bar ();
453 }
454
455 bool
456 BarController::entry_focus_out (GdkEventFocus* /*ev*/)
457 {
458         entry_activated ();
459         return true;
460 }
461
462 void
463 BarController::set_use_parent (bool yn)
464 {
465         use_parent = yn;
466         queue_draw ();
467 }
468
469 void
470 BarController::set_sensitive (bool yn)
471 {
472         Frame::set_sensitive (yn);
473         darea.set_sensitive (yn);
474 }
475
476 /* 
477     This is called when we need to update the adjustment with the value
478     from the spinner's text entry.
479     
480     We need to use Gtk::Entry::get_text to avoid recursive nastiness :)
481     
482     If we're not in logarithmic mode we can return false to use the 
483     default conversion.
484     
485     In theory we should check for conversion errors but set numeric
486     mode to true on the spinner prevents invalid input.
487 */
488 int
489 BarController::entry_input (double* new_value)
490 {
491         if (!logarithmic) {
492                 return false;
493         }
494
495         // extract a double from the string and take its log
496         Entry *entry = dynamic_cast<Entry *>(&spinner);
497         double value;
498
499         {
500                 // Switch to user's preferred locale so that
501                 // if they use different LC_NUMERIC conventions,
502                 // we will honor them.
503
504                 PBD::LocaleGuard lg ("");
505                 sscanf (entry->get_text().c_str(), "%lf", &value);
506         }
507
508         *new_value = log(value);
509
510         return true;
511 }
512
513 /* 
514     This is called when we need to update the spinner's text entry 
515     with the value of the adjustment.
516     
517     We need to use Gtk::Entry::set_text to avoid recursive nastiness :)
518     
519     If we're not in logarithmic mode we can return false to use the 
520     default conversion.
521 */
522 bool
523 BarController::entry_output ()
524 {
525         if (!logarithmic) {
526                 return false;
527         }
528
529         // generate the exponential and turn it into a string
530         // convert to correct locale. 
531         
532         stringstream stream;
533         string str;
534
535         char buf[128];
536
537         {
538                 // Switch to user's preferred locale so that
539                 // if they use different LC_NUMERIC conventions,
540                 // we will honor them.
541                 
542                 PBD::LocaleGuard lg ("");
543                 snprintf (buf, sizeof (buf), "%g", exp (spinner.get_adjustment()->get_value()));
544         }
545
546         Entry *entry = dynamic_cast<Entry *>(&spinner);
547         entry->set_text(buf);
548         
549         return true;
550 }
551
552
553