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