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