add fader implementation from Tracks
[ardour.git] / libs / gtkmm2ext / fader.cc
1 /*
2     Copyright (C) 2014 Waves Audio Ltd.
3
4     This program is free software; you can redistribute it and/or modify
5     it under the terms of the GNU General Public License as published by
6     the Free Software Foundation; either version 2 of the License, or
7     (at your option) any later version.
8
9     This program is distributed in the hope that it will be useful,
10     but WITHOUT ANY WARRANTY; without even the implied warranty of
11     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12     GNU General Public License for more details.
13
14     You should have received a copy of the GNU General Public License
15     along with this program; if not, write to the Free Software
16     Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17
18     $Id: fastmeter.h 570 2006-06-07 21:21:21Z sampo $
19 */
20
21
22 #include <iostream>
23
24 #include "pbd/stacktrace.h"
25
26 #include "gtkmm2ext/fader.h"
27 #include "gtkmm2ext/keyboard.h"
28 #include "gtkmm2ext/rgb_macros.h"
29 #include "gtkmm2ext/utils.h"
30 #include "pbd/failed_constructor.h"
31 #include "pbd/file_utils.h"
32 #include "ardour/filesystem_paths.h"
33
34 using namespace Gtkmm2ext;
35 using namespace Gtk;
36 using namespace std;
37
38 #define CORNER_RADIUS 4
39 #define CORNER_SIZE   2
40 #define CORNER_OFFSET 1
41 #define FADER_RESERVE 5
42
43
44 static void get_closest_point_on_line(double xa, double ya, double xb, double yb, double xp, double yp, double& xl, double& yl )
45 {
46         // Storing vector A->B
47     double a_to_b_x = xb - xa;
48         double a_to_b_y = yb - ya;
49     
50         // Storing vector A->P
51     double a_to_p_x = xp - xa;
52         double a_to_p_y = yp - ya;
53     
54
55     // Basically finding the squared magnitude
56     // of a_to_b
57     double atb2 = a_to_b_x * a_to_b_x + a_to_b_y * a_to_b_y;
58  
59     // The dot product of a_to_p and a_to_b
60     double atp_dot_atb = a_to_p_x * a_to_b_x + a_to_p_y * a_to_b_y;
61     
62     // The normalized "distance" from a to
63     // your closest point
64     double t = atp_dot_atb / atb2;
65     
66     // The vector perpendicular to a_to_b;
67     // This step can also be combined with the next
68         double perpendicular_x = -a_to_b_y;
69         double perpendicular_y = a_to_b_x;
70
71     // Finding Q, the point "in the right direction"
72     // If you want a mess, you can also combine this
73     // with the next step.
74         double xq = xp + perpendicular_x;
75         double yq = yp + perpendicular_y;
76
77     // Add the distance to A, moving
78     // towards B
79     double x = xa + a_to_b_x * t;
80     double y = ya + a_to_b_y * t;
81
82         if ((xa != xb)) {
83                 if ((x < xa) && (x < xb)) {
84                         if (xa < xb) {
85                                 x = xa;
86                                 y = ya;
87                         } else {
88                                 x = xb;
89                                 y = yb;
90                         }
91                 } else if ((x > xa) && (x > xb)) {
92                         if (xb > xa) {
93                                 x = xb;
94                                 y = yb;
95                         } else {
96                                 x = xa;
97                                 y = ya;
98                         }
99                 }
100         } else {
101                 if ((y < ya) && (y < yb)) {
102                         if (ya < yb) {
103                                 x = xa;
104                                 y = ya;
105                         } else {
106                                 x = xb;
107                                 y = yb;
108                         }
109                 } else if ((y > ya) && (y > yb)) {
110                         if (yb > ya) {
111                                 x = xb;
112                                 y = yb;
113                         } else {
114                                 x = xa;
115                                 y = ya;
116                         }
117                 }
118         }
119
120         xl = x;
121         yl = y;
122 }
123
124 Fader::Fader (Gtk::Adjustment& adj,
125                           const Glib::RefPtr<Gdk::Pixbuf>& face_pixbuf,
126                           const Glib::RefPtr<Gdk::Pixbuf>& active_face_pixbuf,
127                           const Glib::RefPtr<Gdk::Pixbuf>& underlay_pixbuf,
128                           const Glib::RefPtr<Gdk::Pixbuf>& handle_pixbuf,
129                           const Glib::RefPtr<Gdk::Pixbuf>& active_handle_pixbuf,
130                           int min_pos_x, 
131                           int min_pos_y,
132                           int max_pos_x,
133                           int max_pos_y,
134                           bool read_only)
135         : adjustment (adj)
136         , _face_pixbuf (face_pixbuf)
137         , _active_face_pixbuf (active_face_pixbuf)
138         , _underlay_pixbuf (underlay_pixbuf)
139         , _handle_pixbuf (handle_pixbuf)
140         , _active_handle_pixbuf (active_handle_pixbuf)
141         , _min_pos_x (min_pos_x)
142         , _min_pos_y (min_pos_y)
143         , _max_pos_x (max_pos_x)
144         , _max_pos_y (max_pos_y)
145         , _default_value (adjustment.get_value())
146         , _dragging (false)
147         , _read_only (read_only)
148         , _grab_window (0)
149         , _touch_cursor (0)
150 {
151         update_unity_position ();
152
153         if (!_read_only) {
154                 add_events (Gdk::BUTTON_PRESS_MASK|Gdk::BUTTON_RELEASE_MASK|Gdk::POINTER_MOTION_MASK|Gdk::SCROLL_MASK|Gdk::ENTER_NOTIFY_MASK|Gdk::LEAVE_NOTIFY_MASK);
155         }
156
157         adjustment.signal_value_changed().connect (mem_fun (*this, &Fader::adjustment_changed));
158         adjustment.signal_changed().connect (mem_fun (*this, &Fader::adjustment_changed));
159     CairoWidget::set_size_request(_face_pixbuf->get_width(), _face_pixbuf->get_height());
160 }
161
162 Fader::~Fader ()
163 {
164         if (_touch_cursor) {
165                 delete _touch_cursor;
166         }
167 }
168
169 void
170 Fader::set_touch_cursor (const Glib::RefPtr<Gdk::Pixbuf>& touch_cursor)
171 {
172         _touch_cursor = new Gdk::Cursor (Gdk::Display::get_default(), touch_cursor, 12, 12);
173 }
174
175 void
176 Fader::render (cairo_t* cr, cairo_rectangle_t*)
177 {
178         get_handle_position (_last_drawn_x, _last_drawn_y);
179
180         if (_underlay_pixbuf != 0) {
181             cairo_rectangle (cr, 0, 0, get_width(), get_height());
182                 gdk_cairo_set_source_pixbuf (cr,
183                                                                          _underlay_pixbuf->gobj(),
184                                                                          _last_drawn_x - (int)(_underlay_pixbuf->get_width()/2.0 + 0.5),
185                                                                          _last_drawn_y - (int)(_underlay_pixbuf->get_height()/2.0 + 0.5));
186             cairo_fill (cr);
187         }
188
189         cairo_rectangle (cr, 0, 0, get_width(), get_height());
190         gdk_cairo_set_source_pixbuf (cr, 
191                                                                  ((get_state () == Gtk::STATE_ACTIVE) && (_active_face_pixbuf != 0)) ? 
192                                                                         _active_face_pixbuf->gobj() : 
193                                                                         _face_pixbuf->gobj(),
194                                                                  0,
195                                                                  0);
196         cairo_fill (cr);
197
198     cairo_rectangle (cr, 0, 0, get_width(), get_height());
199         if (_dragging) {
200                 gdk_cairo_set_source_pixbuf (cr,
201                                                                          _active_handle_pixbuf->gobj(),
202                                                                          _last_drawn_x - (int)(_active_handle_pixbuf->get_width()/2.0 + 0.5),
203                                                                          _last_drawn_y - (int)(_active_handle_pixbuf->get_height()/2.0 + 0.5));
204         } else {
205                 gdk_cairo_set_source_pixbuf (cr,
206                                                                          _handle_pixbuf->gobj(),
207                                                                          _last_drawn_x - (int)(_handle_pixbuf->get_width()/2.0 + 0.5),
208                                                                          _last_drawn_y - (int)(_handle_pixbuf->get_height()/2.0 + 0.5));
209         }
210     cairo_fill (cr);
211 }
212
213 void
214 Fader::on_size_request (GtkRequisition* req)
215 {
216         req->width = _face_pixbuf->get_width();
217         req->height = _face_pixbuf->get_height();
218 }
219
220 void
221 Fader::on_size_allocate (Gtk::Allocation& alloc)
222 {
223     CairoWidget::on_size_allocate(alloc);
224         update_unity_position ();
225 }
226
227 bool
228 Fader::on_button_press_event (GdkEventButton* ev)
229 {
230     focus_handler();
231     
232         if (_read_only) {
233                 return false;
234         }
235     
236         if (ev->type != GDK_BUTTON_PRESS) {
237         return false;
238         }
239
240         if (ev->button != 1 && ev->button != 2) {
241                 return false;
242         }
243
244         if (_touch_cursor) {
245                 get_window()->set_cursor (*_touch_cursor);
246         }
247
248         _grab_start_mouse_x = ev->x;
249         _grab_start_mouse_y = ev->y;
250         get_handle_position (_grab_start_handle_x, _grab_start_handle_y);
251
252         double hw = _handle_pixbuf->get_width();
253         double hh = _handle_pixbuf->get_height();
254
255         if ((ev->x < (_grab_start_handle_x - hw/2)) || (ev->x > (_grab_start_handle_x + hw/2)) || (ev->y < (_grab_start_handle_y - hh/2)) || (ev->y > (_grab_start_handle_y + hh/2))) {
256                 return false;
257         }
258     
259         double ev_pos_x;
260         double ev_pos_y;
261                 
262         get_closest_point_on_line(_min_pos_x, _min_pos_y,
263                                                           _max_pos_x, _max_pos_y, 
264                                                           ev->x, ev->y,
265                                                           ev_pos_x, ev_pos_y );
266         add_modal_grab ();
267         
268         _grab_window = ev->window;
269         _dragging = true;
270         
271         gdk_pointer_grab(ev->window,false,
272                                          GdkEventMask (Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK),
273                                          NULL,
274                                          NULL,
275                                          ev->time);
276
277         queue_draw();
278         
279         return true;
280 }
281
282 bool
283 Fader::on_button_release_event (GdkEventButton* ev)
284 {
285         if (_read_only) {
286                 return false;
287         }
288
289         if (_touch_cursor) {
290                 get_window()->set_cursor ();
291         }
292
293         if (_dragging) { //temp
294                 remove_modal_grab();
295                 _dragging = false;
296                 gdk_pointer_ungrab (GDK_CURRENT_TIME);
297                 queue_draw ();
298         }
299         return false;
300 }
301
302 bool
303 Fader::on_scroll_event (GdkEventScroll* ev)
304 {
305         if (_read_only) {
306                 return false;
307         }
308
309     int step_factor = 1;
310
311         switch (ev->direction) {
312         case GDK_SCROLL_RIGHT:
313         case GDK_SCROLL_UP:
314 #ifdef __APPLE__
315         if ( ev->state & GDK_SHIFT_MASK ) {
316             step_factor = -1;
317         } else {
318             step_factor = 1;
319         }
320 #else
321         step_factor = 1;
322 #endif
323                 break;
324         case GDK_SCROLL_LEFT:
325         case GDK_SCROLL_DOWN:
326 #ifdef __APPLE__
327         if ( ev->state & GDK_SHIFT_MASK ) {
328             step_factor = 1;
329         } else {
330             step_factor = -1;
331         }
332 #else
333         step_factor = -1;
334 #endif
335                 break;
336         default:
337                 return false;
338         }
339     adjustment.set_value (adjustment.get_value() + step_factor * (adjustment.get_step_increment() ));
340         return true;
341 }
342
343 bool
344 Fader::on_motion_notify_event (GdkEventMotion* ev)
345 {
346         if (_read_only) {
347                 return false;
348         }
349
350         if (_dragging) {
351                 double ev_pos_x;
352                 double ev_pos_y;
353                 
354                 if (ev->window != _grab_window) {
355                         _grab_window = ev->window;
356                         return true;
357                 }
358
359                 get_closest_point_on_line(_min_pos_x, _min_pos_y,
360                                                                   _max_pos_x, _max_pos_y, 
361                                                                   _grab_start_handle_x + (ev->x - _grab_start_mouse_x), _grab_start_handle_y + (ev->y - _grab_start_mouse_y),
362                                                                   ev_pos_x, ev_pos_y );
363                 
364                 double const fract = sqrt((ev_pos_x - _min_pos_x) * (ev_pos_x - _min_pos_x) +
365                                                                   (ev_pos_y - _min_pos_y) * (ev_pos_y - _min_pos_y)) /
366                                                          sqrt((_max_pos_x - _min_pos_x) * (_max_pos_x - _min_pos_x) +
367                                                                   (_max_pos_y - _min_pos_y) * (_max_pos_y - _min_pos_y));
368                 
369                 adjustment.set_value (adjustment.get_lower() + (adjustment.get_upper() - adjustment.get_lower()) * fract);
370         }
371         return true;
372 }
373
374 void
375 Fader::adjustment_changed ()
376 {
377     double handle_x;
378         double handle_y;
379         get_handle_position (handle_x, handle_y);
380
381         if ((handle_x != _last_drawn_x) || (handle_y != _last_drawn_y)) {
382                 queue_draw ();
383         }
384 }
385
386 /** @return pixel offset of the current value from the right or bottom of the fader */
387 void
388 Fader::get_handle_position (double& x, double& y)
389 {
390         double fract = (adjustment.get_value () - adjustment.get_lower()) / ((adjustment.get_upper() - adjustment.get_lower()));
391
392         x = (int)(_min_pos_x + (_max_pos_x - _min_pos_x) * fract);
393         y = (int)(_min_pos_y + (_max_pos_y - _min_pos_y) * fract);
394 }
395
396 bool
397 Fader::on_enter_notify_event (GdkEventCrossing*)
398 {
399         _hovering = true;
400         Keyboard::magic_widget_grab_focus ();
401         queue_draw ();
402         return false;
403 }
404
405 bool
406 Fader::on_leave_notify_event (GdkEventCrossing*)
407 {
408         if (_read_only) {
409                 return false;
410         }
411
412         if (!_dragging) {
413                 _hovering = false;
414                 Keyboard::magic_widget_drop_focus();
415                 queue_draw ();
416         }
417         return false;
418 }
419
420 void
421 Fader::set_default_value (float d)
422 {
423         _default_value = d;
424         update_unity_position ();
425 }
426
427 void
428 Fader::update_unity_position ()
429 {
430 }