tweak search path for export profiles
[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         int 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;
402                 layout->get_pixel_size (width, height);
403
404                 if (xpos == -1) {
405                         xpos = max (3, 1 + (x2 - (width/2)));
406                         xpos = min (darea.get_width() - width - 3, xpos);
407                 }
408                 
409                 win->draw_layout (get_style()->get_text_gc (get_state()),
410                                   xpos,
411                                   (darea.get_height()/2) - (height/2),
412                                   layout);
413         }
414         
415         return true;
416 }
417
418 void
419 BarController::set_style (barStyle s)
420 {
421         _style = s;
422         darea.queue_draw ();
423 }
424
425 gint
426 BarController::switch_to_bar ()
427 {
428         if (switching) {
429                 return FALSE;
430         }
431
432         switching = true;
433
434         if (get_child() == &darea) {
435                 return FALSE;
436         }
437
438         remove ();
439         add (darea);
440         darea.show ();
441
442         switching = false;
443
444         SpinnerActive (false); /* EMIT SIGNAL */
445         
446         return FALSE;
447 }
448
449 gint
450 BarController::switch_to_spinner ()
451 {
452         if (switching) {
453                 return FALSE;
454         }
455
456         switching = true;
457
458         if (get_child() == &spinner) {
459                 return FALSE;
460         }
461
462         remove ();
463         add (spinner);
464         spinner.show ();
465         spinner.select_region (0, spinner.get_text_length());
466         spinner.grab_focus ();
467
468         switching = false;
469
470         SpinnerActive (true); /* EMIT SIGNAL */
471
472         return FALSE;
473 }
474
475 void
476 BarController::entry_activated ()
477 {
478         switch_to_bar ();
479 }
480
481 bool
482 BarController::entry_focus_out (GdkEventFocus* /*ev*/)
483 {
484         entry_activated ();
485         return true;
486 }
487
488 void
489 BarController::set_use_parent (bool yn)
490 {
491         use_parent = yn;
492         queue_draw ();
493 }
494
495 void
496 BarController::set_sensitive (bool yn)
497 {
498         Frame::set_sensitive (yn);
499         darea.set_sensitive (yn);
500 }
501
502 /* 
503     This is called when we need to update the adjustment with the value
504     from the spinner's text entry.
505     
506     We need to use Gtk::Entry::get_text to avoid recursive nastiness :)
507     
508     If we're not in logarithmic mode we can return false to use the 
509     default conversion.
510     
511     In theory we should check for conversion errors but set numeric
512     mode to true on the spinner prevents invalid input.
513 */
514 int
515 BarController::entry_input (double* new_value)
516 {
517         if (!logarithmic) {
518                 return false;
519         }
520
521         // extract a double from the string and take its log
522         Entry *entry = dynamic_cast<Entry *>(&spinner);
523         double value;
524
525         {
526                 // Switch to user's preferred locale so that
527                 // if they use different LC_NUMERIC conventions,
528                 // we will honor them.
529
530                 PBD::LocaleGuard lg ("");
531                 sscanf (entry->get_text().c_str(), "%lf", &value);
532         }
533
534         *new_value = log(value);
535
536         return true;
537 }
538
539 /* 
540     This is called when we need to update the spinner's text entry 
541     with the value of the adjustment.
542     
543     We need to use Gtk::Entry::set_text to avoid recursive nastiness :)
544     
545     If we're not in logarithmic mode we can return false to use the 
546     default conversion.
547 */
548 bool
549 BarController::entry_output ()
550 {
551         if (!logarithmic) {
552                 return false;
553         }
554
555         // generate the exponential and turn it into a string
556         // convert to correct locale. 
557         
558         stringstream stream;
559         string str;
560
561         char buf[128];
562
563         {
564                 // Switch to user's preferred locale so that
565                 // if they use different LC_NUMERIC conventions,
566                 // we will honor them.
567                 
568                 PBD::LocaleGuard lg ("");
569                 snprintf (buf, sizeof (buf), "%g", exp (spinner.get_adjustment()->get_value()));
570         }
571
572         Entry *entry = dynamic_cast<Entry *>(&spinner);
573         entry->set_text(buf);
574         
575         return true;
576 }
577
578
579