0fedd33696f14789ed666c7406ecedcc48be977c
[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         // Add the distance to A, moving
67         // towards B
68         double x = xa + a_to_b_x * t;
69         double y = ya + a_to_b_y * t;
70
71         if ((xa != xb)) {
72                 if ((x < xa) && (x < xb)) {
73                         if (xa < xb) {
74                                 x = xa;
75                                 y = ya;
76                         } else {
77                                 x = xb;
78                                 y = yb;
79                         }
80                 } else if ((x > xa) && (x > xb)) {
81                         if (xb > xa) {
82                                 x = xb;
83                                 y = yb;
84                         } else {
85                                 x = xa;
86                                 y = ya;
87                         }
88                 }
89         } else {
90                 if ((y < ya) && (y < yb)) {
91                         if (ya < yb) {
92                                 x = xa;
93                                 y = ya;
94                         } else {
95                                 x = xb;
96                                 y = yb;
97                         }
98                 } else if ((y > ya) && (y > yb)) {
99                         if (yb > ya) {
100                                 x = xb;
101                                 y = yb;
102                         } else {
103                                 x = xa;
104                                 y = ya;
105                         }
106                 }
107         }
108
109         xl = x;
110         yl = y;
111 }
112
113 Fader::Fader (Gtk::Adjustment& adj,
114               const Glib::RefPtr<Gdk::Pixbuf>& face_pixbuf,
115               const Glib::RefPtr<Gdk::Pixbuf>& active_face_pixbuf,
116               const Glib::RefPtr<Gdk::Pixbuf>& underlay_pixbuf,
117               const Glib::RefPtr<Gdk::Pixbuf>& handle_pixbuf,
118               const Glib::RefPtr<Gdk::Pixbuf>& active_handle_pixbuf,
119               int min_pos_x, 
120               int min_pos_y,
121               int max_pos_x,
122               int max_pos_y,
123               bool read_only)
124         : adjustment (adj)
125         , _face_pixbuf (face_pixbuf)
126         , _active_face_pixbuf (active_face_pixbuf)
127         , _underlay_pixbuf (underlay_pixbuf)
128         , _handle_pixbuf (handle_pixbuf)
129         , _active_handle_pixbuf (active_handle_pixbuf)
130         , _min_pos_x (min_pos_x)
131         , _min_pos_y (min_pos_y)
132         , _max_pos_x (max_pos_x)
133         , _max_pos_y (max_pos_y)
134         , _grab_window (0)
135         , _touch_cursor (0)
136         , _dragging (false)
137         , _default_value (adjustment.get_value())
138         , _read_only (read_only)
139 {
140         update_unity_position ();
141
142         if (!_read_only) {
143                 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);
144         }
145
146         adjustment.signal_value_changed().connect (mem_fun (*this, &Fader::adjustment_changed));
147         adjustment.signal_changed().connect (mem_fun (*this, &Fader::adjustment_changed));
148         CairoWidget::set_size_request(_face_pixbuf->get_width(), _face_pixbuf->get_height());
149 }
150
151 Fader::~Fader ()
152 {
153         if (_touch_cursor) {
154                 delete _touch_cursor;
155         }
156 }
157
158 void
159 Fader::get_image_scales (double &x_scale, double &y_scale)
160 {
161         int pbwidth = _face_pixbuf->get_width ();
162         int pbheight = _face_pixbuf->get_height ();
163         int width = get_width ();
164         int height = get_height ();
165         
166         if ((width != pbwidth) || (height != pbheight)) {
167                 x_scale = double (width) / double (pbwidth);
168                 if (x_scale == 0.0) {
169                         x_scale = 1.0;
170                 }
171                 y_scale = double (height) / double (pbheight);
172                 if (y_scale == 0.0) {
173                         y_scale = 1.0;
174                 }
175         } else {
176                 x_scale = y_scale = 1.0;
177         }
178 }
179
180 void
181 Fader::set_touch_cursor (const Glib::RefPtr<Gdk::Pixbuf>& touch_cursor)
182 {
183         _touch_cursor = new Gdk::Cursor (Gdk::Display::get_default(), touch_cursor, 12, 12);
184 }
185
186 void
187 Fader::render (cairo_t* cr, cairo_rectangle_t*)
188 {
189         
190         double xscale = 1.0;
191         double yscale = 1.0;
192         
193         get_image_scales (xscale, yscale);
194         
195         cairo_matrix_t matrix;
196         cairo_get_matrix (cr, &matrix);
197         cairo_matrix_scale (&matrix, xscale, yscale);
198         cairo_set_matrix (cr, &matrix);
199         
200         get_handle_position (_last_drawn_x, _last_drawn_y);
201
202         if (_underlay_pixbuf != 0) {
203                 gdk_cairo_set_source_pixbuf (cr,
204                                              _underlay_pixbuf->gobj(),
205                                              (_last_drawn_x - (int)((_underlay_pixbuf->get_width() * xscale) / 2.0 + 0.5)) / xscale,
206                                              (_last_drawn_y - (int)((_underlay_pixbuf->get_height() * yscale) / 2.0 + 0.5)) / yscale);
207                 cairo_paint (cr);
208         }
209
210         gdk_cairo_set_source_pixbuf (cr,
211                                      ((get_state () == Gtk::STATE_ACTIVE) && (_active_face_pixbuf != 0)) ? 
212                                      _active_face_pixbuf->gobj() : 
213                                      _face_pixbuf->gobj(),
214                                      0,
215                                      0);
216         cairo_paint (cr);
217
218         const Glib::RefPtr<Gdk::Pixbuf> handle_pixbuf (_dragging ? _active_handle_pixbuf : _handle_pixbuf);
219         gdk_cairo_set_source_pixbuf (cr,
220                                      handle_pixbuf->gobj(),
221                                      (_last_drawn_x - (int)((handle_pixbuf->get_width() * xscale) / 2.0 + 0.5)) / xscale,
222                                      (_last_drawn_y - (int)((handle_pixbuf->get_height() * yscale) / 2.0 + 0.5)) / yscale);
223         cairo_paint (cr);
224 }
225
226 void
227 Fader::on_size_request (GtkRequisition* req)
228 {
229         req->width = _face_pixbuf->get_width();
230         req->height = _face_pixbuf->get_height();
231 }
232
233 void
234 Fader::on_size_allocate (Gtk::Allocation& alloc)
235 {
236         CairoWidget::on_size_allocate(alloc);
237         update_unity_position ();
238 }
239
240 bool
241 Fader::on_button_press_event (GdkEventButton* ev)
242 {
243         focus_handler();
244     
245         if (_read_only) {
246                 return false;
247         }
248     
249         if (ev->type != GDK_BUTTON_PRESS) {
250                 return false;
251         }
252
253         if (ev->button != 1 && ev->button != 2) {
254                 return false;
255         }
256
257         if (_touch_cursor) {
258                 get_window()->set_cursor (*_touch_cursor);
259         }
260
261         _grab_start_mouse_x = ev->x;
262         _grab_start_mouse_y = ev->y;
263         get_handle_position (_grab_start_handle_x, _grab_start_handle_y);
264
265         double xscale = 1.0;
266         double yscale = 1.0;
267         
268         get_image_scales (xscale, yscale);
269         
270         double hw = _handle_pixbuf->get_width() * xscale;
271         double hh = _handle_pixbuf->get_height() * yscale;
272
273         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))) {
274                 return false;
275         }
276     
277         double ev_pos_x;
278         double ev_pos_y;
279                 
280         get_closest_point_on_line(_min_pos_x, _min_pos_y,
281                                   _max_pos_x, _max_pos_y, 
282                                   ev->x, ev->y,
283                                   ev_pos_x, ev_pos_y );
284         add_modal_grab ();
285         
286         _grab_window = ev->window;
287         _dragging = true;
288         
289         gdk_pointer_grab(ev->window,false,
290                          GdkEventMask (Gdk::POINTER_MOTION_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK),
291                          NULL,
292                          NULL,
293                          ev->time);
294
295         queue_draw();
296         
297         return true;
298 }
299
300 bool
301 Fader::on_button_release_event (GdkEventButton* ev)
302 {
303         if (_read_only) {
304                 return false;
305         }
306
307         if (_touch_cursor) {
308                 get_window()->set_cursor ();
309         }
310
311         if (_dragging) { //temp
312                 remove_modal_grab();
313                 _dragging = false;
314                 gdk_pointer_ungrab (GDK_CURRENT_TIME);
315                 queue_draw ();
316         }
317         return false;
318 }
319
320 bool
321 Fader::on_scroll_event (GdkEventScroll* ev)
322 {
323         if (_read_only) {
324                 return false;
325         }
326
327         int step_factor = 1;
328
329         switch (ev->direction) {
330         case GDK_SCROLL_RIGHT:
331         case GDK_SCROLL_UP:
332 #ifdef __APPLE__
333                 if ( ev->state & GDK_SHIFT_MASK ) {
334                         step_factor = -1;
335                 } else {
336                         step_factor = 1;
337                 }
338 #else
339                 step_factor = 1;
340 #endif
341                 break;
342         case GDK_SCROLL_LEFT:
343         case GDK_SCROLL_DOWN:
344 #ifdef __APPLE__
345                 if ( ev->state & GDK_SHIFT_MASK ) {
346                         step_factor = 1;
347                 } else {
348                         step_factor = -1;
349                 }
350 #else
351                 step_factor = -1;
352 #endif
353                 break;
354         default:
355                 return false;
356         }
357         adjustment.set_value (adjustment.get_value() + step_factor * (adjustment.get_step_increment() ));
358         return true;
359 }
360
361 bool
362 Fader::on_motion_notify_event (GdkEventMotion* ev)
363 {
364         if (_read_only) {
365                 return false;
366         }
367
368         if (_dragging) {
369                 double ev_pos_x;
370                 double ev_pos_y;
371                 
372                 if (ev->window != _grab_window) {
373                         _grab_window = ev->window;
374                         return true;
375                 }
376
377                 get_closest_point_on_line(_min_pos_x, _min_pos_y,
378                                           _max_pos_x, _max_pos_y, 
379                                           _grab_start_handle_x + (ev->x - _grab_start_mouse_x), _grab_start_handle_y + (ev->y - _grab_start_mouse_y),
380                                           ev_pos_x, ev_pos_y );
381                 
382                 double const fract = sqrt((ev_pos_x - _min_pos_x) * (ev_pos_x - _min_pos_x) +
383                                           (ev_pos_y - _min_pos_y) * (ev_pos_y - _min_pos_y)) /
384                         sqrt((double)((_max_pos_x - _min_pos_x) * (_max_pos_x - _min_pos_x) +
385                                       (_max_pos_y - _min_pos_y) * (_max_pos_y - _min_pos_y)));
386                 
387                 adjustment.set_value (adjustment.get_lower() + (adjustment.get_upper() - adjustment.get_lower()) * fract);
388         }
389         return true;
390 }
391
392 void
393 Fader::adjustment_changed ()
394 {
395         double handle_x;
396         double handle_y;
397         get_handle_position (handle_x, handle_y);
398
399         if ((handle_x != _last_drawn_x) || (handle_y != _last_drawn_y)) {
400                 queue_draw ();
401         }
402 }
403
404 /** @return pixel offset of the current value from the right or bottom of the fader */
405 void
406 Fader::get_handle_position (double& x, double& y)
407 {
408         double fract = (adjustment.get_value () - adjustment.get_lower()) / ((adjustment.get_upper() - adjustment.get_lower()));
409
410         x = (int)(_min_pos_x + (_max_pos_x - _min_pos_x) * fract);
411         y = (int)(_min_pos_y + (_max_pos_y - _min_pos_y) * fract);
412 }
413
414 bool
415 Fader::on_enter_notify_event (GdkEventCrossing*)
416 {
417         _hovering = true;
418         Keyboard::magic_widget_grab_focus ();
419         queue_draw ();
420         return false;
421 }
422
423 bool
424 Fader::on_leave_notify_event (GdkEventCrossing*)
425 {
426         if (_read_only) {
427                 return false;
428         }
429
430         if (!_dragging) {
431                 _hovering = false;
432                 Keyboard::magic_widget_drop_focus();
433                 queue_draw ();
434         }
435         return false;
436 }
437
438 void
439 Fader::set_default_value (float d)
440 {
441         _default_value = d;
442         update_unity_position ();
443 }
444
445 void
446 Fader::update_unity_position ()
447 {
448 }