Plugin selector useability changes, fix mouse grab bug in plugin barcontroller, preve...
[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 <climits>
22 #include <cstdio>
23 #include <cmath>
24 #include <algorithm>
25
26 #include <midi++/controllable.h>
27
28 #include <gtkmm2ext/gtk_ui.h>
29 #include <gtkmm2ext/utils.h>
30 #include <gtkmm2ext/barcontroller.h>
31
32 #include "i18n.h"
33
34 using namespace std;
35 using namespace Gtk;
36 using namespace Gtkmm2ext;
37
38 BarController::BarController (Gtk::Adjustment& adj,
39                               MIDI::Controllable *mc,
40                               sigc::slot<void,char*,unsigned int> lc) 
41
42         : adjustment (adj),
43           prompter (Gtk::WIN_POS_MOUSE, 30000, false),
44           midi_control (mc),
45           label_callback (lc),
46           spinner (adjustment),
47           bind_button (2),
48           bind_statemask (Gdk::CONTROL_MASK)
49
50 {                         
51         _style = LeftToRight;
52         grabbed = false;
53         switching = false;
54         switch_on_release = false;
55         with_text = true;
56         use_parent = false;
57
58         layout = darea.create_pango_layout("");
59
60         set_shadow_type (SHADOW_NONE);
61
62         initial_value = adjustment.get_value ();
63
64         adjustment.signal_value_changed().connect (mem_fun (*this, &Gtk::Widget::queue_draw));
65         adjustment.signal_changed().connect (mem_fun (*this, &Gtk::Widget::queue_draw));
66
67         darea.add_events (Gdk::BUTTON_RELEASE_MASK|
68                           Gdk::BUTTON_PRESS_MASK|
69                           Gdk::POINTER_MOTION_MASK|
70                           Gdk::ENTER_NOTIFY_MASK|
71                           Gdk::LEAVE_NOTIFY_MASK|
72                           Gdk::SCROLL_MASK);
73
74         darea.signal_expose_event().connect (mem_fun (*this, &BarController::expose));
75         darea.signal_motion_notify_event().connect (mem_fun (*this, &BarController::motion));
76         darea.signal_button_press_event().connect (mem_fun (*this, &BarController::button_press));
77         darea.signal_button_release_event().connect (mem_fun (*this, &BarController::button_release));
78         darea.signal_scroll_event().connect (mem_fun (*this, &BarController::scroll));
79
80         prompter.signal_unmap_event().connect (mem_fun (*this, &BarController::prompter_hiding));
81         
82         prompting = false;
83         unprompting = false;
84         
85         if (mc) {
86                 mc->learning_started.connect (mem_fun (*this, &BarController::midicontrol_prompt));
87                 mc->learning_stopped.connect (mem_fun (*this, &BarController::midicontrol_unprompt));
88         }
89
90         spinner.signal_activate().connect (mem_fun (*this, &BarController::entry_activated));
91         spinner.signal_focus_out_event().connect (mem_fun (*this, &BarController::entry_focus_out));
92         spinner.set_digits (3);
93
94         add (darea);
95         show_all ();
96 }
97
98 void
99 BarController::set_bind_button_state (guint button, guint statemask)
100 {
101         bind_button = button;
102         bind_statemask = statemask;
103 }
104
105 void
106 BarController::get_bind_button_state (guint &button, guint &statemask)
107 {
108         button = bind_button;
109         statemask = bind_statemask;
110 }
111
112
113 bool
114 BarController::button_press (GdkEventButton* ev)
115 {
116         switch (ev->button) {
117         case 1:
118                 if (ev->type == GDK_2BUTTON_PRESS) {
119                         switch_on_release = true;
120                         grabbed = false;
121                         darea.remove_modal_grab();
122                 } else {
123                         switch_on_release = false;
124                         darea.add_modal_grab();
125                         grabbed = true;
126                         grab_x = ev->x;
127                         grab_window = ev->window;
128                         StartGesture ();
129                 }
130                 return true;
131                 break;
132
133         case 2:
134         case 3:
135                 break;
136
137         case 4:
138         case 5:
139                 break;
140         }
141
142         return false;
143 }
144
145 bool
146 BarController::button_release (GdkEventButton* ev)
147 {
148         switch (ev->button) {
149         case 1:
150                 if (switch_on_release) {
151                         Glib::signal_idle().connect (mem_fun (*this, &BarController::switch_to_spinner));
152                         return true;
153                 }
154
155                 if ((ev->state & (GDK_SHIFT_MASK|GDK_CONTROL_MASK)) == GDK_SHIFT_MASK) {
156                         adjustment.set_value (initial_value);
157                 } else {
158                         double scale;
159
160                         if (ev->state & (GDK_CONTROL_MASK|GDK_SHIFT_MASK) == (GDK_CONTROL_MASK|GDK_SHIFT_MASK)) {
161                                 scale = 0.01;
162                         } else if (ev->state & GDK_CONTROL_MASK) {
163                                 scale = 0.1;
164                         } else {
165                                 scale = 1.0;
166                         }
167
168                         mouse_control (ev->x, ev->window, scale);
169                 }
170                 darea.remove_modal_grab();
171                 grabbed = false;
172                 StopGesture ();
173                 grabbed = false;
174                 break;
175
176         case 2:
177                 if ((ev->state & bind_statemask) && bind_button == 2) {
178                         midi_learn ();
179                 } else {
180                         double fract;
181                         fract = ev->x / (darea.get_width() - 2.0);
182                         adjustment.set_value (adjustment.get_lower() + 
183                                               fract * (adjustment.get_upper() - adjustment.get_lower()));
184                 }
185                 return true;
186
187         case 3:
188                 if ((ev->state & bind_statemask) && bind_button == 3) {
189                         midi_learn ();
190                         return TRUE;
191                 }
192                 return false;
193                 
194         default:
195                 break;
196         }
197
198         return true;
199 }
200
201 bool
202 BarController::scroll (GdkEventScroll* ev)
203 {
204         double scale;
205
206         if (ev->state & (GDK_CONTROL_MASK|GDK_SHIFT_MASK) == (GDK_CONTROL_MASK|GDK_SHIFT_MASK)) {
207                 scale = 0.01;
208         } else if (ev->state & GDK_CONTROL_MASK) {
209                 scale = 0.1;
210         } else {
211                 scale = 1.0;
212         }
213
214         switch (ev->direction) {
215         case GDK_SCROLL_UP:
216         case GDK_SCROLL_RIGHT:
217                 adjustment.set_value (adjustment.get_value() + (scale * adjustment.get_step_increment()));
218                 break;
219
220         case GDK_SCROLL_DOWN:
221         case GDK_SCROLL_LEFT:
222                 adjustment.set_value (adjustment.get_value() - (scale * adjustment.get_step_increment()));
223                 break;
224         }
225
226         return true;
227 }
228
229 bool
230 BarController::motion (GdkEventMotion* ev)
231 {
232         double scale;
233         
234         if (!grabbed) {
235                 return TRUE;
236         }
237
238         if ((ev->state & (GDK_SHIFT_MASK|GDK_CONTROL_MASK)) == GDK_SHIFT_MASK) {
239                 return TRUE;
240         }
241
242         if (ev->state & (GDK_CONTROL_MASK|GDK_SHIFT_MASK) == (GDK_CONTROL_MASK|GDK_SHIFT_MASK)) {
243                 scale = 0.01;
244         } else if (ev->state & GDK_CONTROL_MASK) {
245                 scale = 0.1;
246         } else {
247                 scale = 1.0;
248         }
249
250         return mouse_control (ev->x, ev->window, scale);
251 }
252
253 gint
254 BarController::mouse_control (double x, GdkWindow* window, double scaling)
255 {
256         double fract;
257         double delta;
258
259         if (window != grab_window) {
260                 grab_x = x;
261                 grab_window = window;
262                 return TRUE;
263         }
264
265         delta = x - grab_x;
266         grab_x = x;
267
268         switch (_style) {
269         case Line:
270         case LeftToRight:
271                 fract = scaling * (delta / (darea.get_width() - 2));
272                 fract = min (1.0, fract);
273                 fract = max (-1.0, fract);
274                 adjustment.set_value (adjustment.get_value() + fract * (adjustment.get_upper() - adjustment.get_lower()));
275                 break;
276
277         default:
278                 fract = 0.0;
279         }
280         
281         
282         return TRUE;
283 }
284
285 bool
286 BarController::expose (GdkEventExpose* event)
287 {
288         Glib::RefPtr<Gdk::Window> win (darea.get_window());
289         Widget* parent;
290         gint x1, x2, y1, y2;
291         gint w, h;
292         double fract;
293
294         w = darea.get_width() - 2;
295         h = darea.get_height() - 2;
296
297         fract = ((adjustment.get_value() - adjustment.get_lower()) /
298                  (adjustment.get_upper() - adjustment.get_lower()));
299         
300         switch (_style) {
301         case Line:
302                 x1 = (gint) floor (w * fract);
303                 x2 = x1;
304                 y1 = 0;
305                 y2 = h - 1;
306
307                 if (use_parent) {
308                         parent = get_parent();
309                         
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                         }
315                 } else {
316                         win->draw_rectangle (get_style()->get_bg_gc (get_state()),
317                                             true,
318                                             0, 0, darea.get_width(), darea.get_height());
319                 }
320
321                 if (fract == 0.0) {
322                         win->draw_rectangle (get_style()->get_fg_gc (get_state()),
323                                             true, x1, 1, 2, darea.get_height() - 2);
324                 } else {
325                         win->draw_rectangle (get_style()->get_fg_gc (get_state()),
326                                             true, x1 - 1, 1, 3, darea.get_height() - 2);
327                 }
328                 break;
329
330         case CenterOut:
331                 break;
332
333         case LeftToRight:
334                 x1 = 0;
335                 x2 = (gint) floor (w * fract);
336                 y1 = 0;
337                 y2 = h - 1;
338
339                 win->draw_rectangle (get_style()->get_bg_gc (get_state()),
340                                     false,
341                                     0, 0, darea.get_width() - 1, darea.get_height() - 1);
342
343                 /* draw active box */
344
345                 win->draw_rectangle (get_style()->get_fg_gc (get_state()),
346                                     true,
347                                     1 + x1,
348                                     1 + y1,
349                                     x2,
350                                     1 + y2);
351                 
352                 /* draw inactive box */
353
354                 win->draw_rectangle (get_style()->get_fg_gc (STATE_INSENSITIVE),
355                                     true,
356                                     1 + x2,
357                                     1 + y1,
358                                     w - x2,
359                                     1 + y2);
360
361                 break;
362
363         case RightToLeft:
364                 break;
365         case TopToBottom:
366                 break;
367         case BottomToTop:
368                 break;
369         }
370
371         if (with_text) {
372                 /* draw label */
373                 
374                 char buf[64];
375                 buf[0] = '\0';
376
377                 label_callback (buf, 64);
378
379                 if (buf[0] != '\0') {
380
381                         layout->set_text (buf);                 
382
383                         int width, height;
384                         layout->get_pixel_size (width, height);
385
386                         int xpos;
387
388                         xpos = max (3, 1 + (x2 - (width/2)));
389                         xpos = min (darea.get_width() - width - 3, xpos);
390                         
391                         win->draw_layout (get_style()->get_text_gc (get_state()),
392                                           xpos,
393                                           (darea.get_height()/2) - (height/2),
394                                           layout);
395                 }
396         }
397
398         return true;
399 }
400
401 void
402 BarController::set_with_text (bool yn)
403 {
404         if (with_text != yn) {
405                 with_text = yn;
406                 queue_draw ();
407         }
408 }
409
410 void
411 BarController::midicontrol_set_tip ()
412 {
413         if (midi_control) {
414                 // Gtkmm2ext::UI::instance()->set_tip (&darea, midi_control->control_description());
415         }
416 }
417
418 void
419 BarController::midi_learn()
420 {
421         if (midi_control) {
422                 prompting = true;
423                 midi_control->learn_about_external_control ();
424         }
425 }
426
427
428 void
429 BarController::midicontrol_prompt ()
430 {
431         if (prompting) {
432                 string prompt = _("operate MIDI controller now");
433                 prompter.set_text (prompt);
434                 Gtkmm2ext::UI::instance()->touch_display (&prompter);
435
436                 unprompting = true;
437                 prompting = false;
438         }
439 }
440
441 void
442 BarController::midicontrol_unprompt ()
443 {
444         if (unprompting) {
445                 Gtkmm2ext::UI::instance()->touch_display (&prompter);
446                 
447                 unprompting = false;
448         }
449 }
450
451 gint
452 BarController::prompter_hiding (GdkEventAny *ev)
453 {
454         if (unprompting) {
455                 if (midi_control) {
456                         midi_control->stop_learning();
457                 }
458                 unprompting = false;
459         }
460         
461         return FALSE;
462 }
463
464
465 void
466 BarController::set_style (Style s)
467 {
468         _style = s;
469         darea.queue_draw ();
470 }
471
472 gint
473 BarController::switch_to_bar ()
474 {
475         if (switching) {
476                 return FALSE;
477         }
478
479         switching = true;
480
481         if (get_child() == &darea) {
482                 return FALSE;
483         }
484
485         remove ();
486         add (darea);
487         darea.show ();
488
489         switching = false;
490         return FALSE;
491 }
492
493 gint
494 BarController::switch_to_spinner ()
495 {
496         if (switching) {
497                 return FALSE;
498         }
499
500         switching = true;
501
502         if (get_child() == &spinner) {
503                 return FALSE;
504         }
505
506         remove ();
507         add (spinner);
508         spinner.show ();
509         spinner.grab_focus ();
510
511         switching = false;
512         return FALSE;
513 }
514
515 void
516 BarController::entry_activated ()
517 {
518         string text = spinner.get_text ();
519         float val;
520
521         if (sscanf (text.c_str(), "%f", &val) == 1) {
522                 adjustment.set_value (val);
523         }
524         
525         switch_to_bar ();
526 }
527
528 bool
529 BarController::entry_focus_out (GdkEventFocus* ev)
530 {
531         entry_activated ();
532         return true;
533 }
534
535 void
536 BarController::set_use_parent (bool yn)
537 {
538         use_parent = yn;
539         queue_draw ();
540 }