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