more changes to broken-out tempo code
[ardour.git] / libs / widgets / scroomer.cc
1 /*
2     Copyright (C) 2008 Paul Davis
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 */
19
20 #include <iostream>
21
22 #include "gtkmm2ext/keyboard.h"
23 #include "widgets/scroomer.h"
24
25 using namespace std;
26 using namespace Gdk;
27 using namespace Gtk;
28 using namespace ArdourWidgets;
29
30 Scroomer::Scroomer(Gtk::Adjustment& adjustment)
31         : adj(adjustment)
32         , handle_size(0)
33         , grab_comp(None)
34 {
35         position[TopBase] = 0;
36         position[Handle1] = 0;
37         position[Slider] = 0;
38         position[Handle2] = 0;
39         position[BottomBase] = 0;
40         position[Total] = 0;
41
42         add_events (Gdk::BUTTON_PRESS_MASK |
43                     Gdk::BUTTON_RELEASE_MASK |
44                     Gdk::POINTER_MOTION_MASK |
45                     Gdk::SCROLL_MASK);
46
47         adjustment.signal_value_changed().connect (mem_fun (*this, &Scroomer::adjustment_changed));
48         //adjustment.signal_changed().connect (mem_fun (*this, &Scroomer::adjustment_changed));
49 }
50
51 Scroomer::~Scroomer()
52 {
53 }
54
55 bool
56 Scroomer::on_motion_notify_event (GdkEventMotion* ev)
57 {
58         double range = adj.get_upper() - adj.get_lower();
59         double pixel2val = range / get_height();
60         double val_at_pointer = ((get_height() - ev->y) * pixel2val) + adj.get_lower();
61         double delta_y = ev->y - grab_y;
62         double half_min_page = min_page_size / 2;
63         double fract = delta_y / position[Total];
64         double scale, temp, zoom;
65         double val, page;
66
67         if (grab_comp == None || grab_comp == Total) {
68                 return true;
69         }
70
71         if (ev->window != grab_window) {
72                 grab_y = ev->y;
73                 grab_window = ev->window;
74                 return true;
75         }
76
77         if (ev->y < 0 || ev->y > get_height ()) {
78                 return true;
79         }
80
81         grab_y = ev->y;
82
83         if (ev->state & Gtkmm2ext::Keyboard::PrimaryModifier) {
84                 if (ev->state & Gtkmm2ext::Keyboard::SecondaryModifier) {
85                         scale = 0.05;
86                 } else {
87                         scale = 0.1;
88                 }
89         } else {
90                 scale = 1.0;
91         }
92
93         fract = min (1.0, fract);
94         fract = max (-1.0, fract);
95         fract = -fract;
96
97         switch (grab_comp) {
98         case TopBase:
99         case BottomBase:
100                 unzoomed_val += scale * fract * range;
101                 unzoomed_val = min(unzoomed_val, adj.get_upper() - unzoomed_page);
102                 unzoomed_val = max(unzoomed_val, adj.get_lower());
103                 break;
104         case Slider:
105                 unzoomed_val += scale * fract * range;
106                 unzoomed_val = min(unzoomed_val, adj.get_upper() - unzoomed_page);
107                 unzoomed_val = max(unzoomed_val, adj.get_lower());
108                 break;
109         case Handle1:
110
111                 unzoomed_page += scale * fract * range;
112                 unzoomed_page = min(unzoomed_page, adj.get_upper() - unzoomed_val);
113                 unzoomed_page = max(unzoomed_page, min_page_size);
114
115                 if (pinch){
116                         temp = unzoomed_val + unzoomed_page;
117                         unzoomed_val -= scale * fract * range * 0.5;
118                         unzoomed_val = min(unzoomed_val, temp - min_page_size);
119                         unzoomed_val = max(unzoomed_val, adj.get_lower());
120                 }
121
122                 break;
123         case Handle2:
124                 temp = unzoomed_val + unzoomed_page;
125                 unzoomed_val += scale * fract * range;
126                 unzoomed_val = min(unzoomed_val, temp - min_page_size);
127                 unzoomed_val = max(unzoomed_val, adj.get_lower());
128
129                 unzoomed_page = temp - unzoomed_val;
130
131                 if (pinch){
132
133                         unzoomed_page -= scale * fract * range;
134                 }
135
136                 unzoomed_page = min(unzoomed_page, adj.get_upper() - unzoomed_val);
137                 unzoomed_page = max(unzoomed_page, min_page_size);
138                 break;
139         default:
140                 break;
141         }
142
143         /* Then we handle zoom, which is dragging horizontally. We zoom around the area that is
144          * the current y pointer value, not from the area that was the start of the drag.
145          * We don't start doing zoom until we are at least one scroomer width outside the scroomer's
146          * area.
147          */
148
149         if (ev->x > (get_width() * 2)) {
150                 zoom = ev->x - get_width();
151
152                 double higher = unzoomed_val + unzoomed_page - half_min_page - val_at_pointer;
153                 double lower = val_at_pointer - (unzoomed_val + half_min_page);
154
155                 higher *= zoom / 128;
156                 lower *= zoom / 128;
157
158                 val = unzoomed_val + lower;
159                 page = unzoomed_page - higher - lower;
160
161                 page = max(page, min_page_size);
162
163                 if (lower < 0) {
164                         val = max(val, val_at_pointer - half_min_page);
165                 } else if (lower > 0) {
166                         val = min(val, val_at_pointer - half_min_page);
167                 }
168
169                 val = min(val, adj.get_upper() - min_page_size);
170                 page = min(page, adj.get_upper() - val);
171         } else if (ev->x < 0) {
172                 /* on zoom out increase the page size as well as moving the range towards the mouse pos*/
173                 /*zoom = abs(ev->x);
174
175                 double higher = unzoomed_val + unzoomed_page - half_min_page - val_at_pointer;
176                 double lower = val_at_pointer - (unzoomed_val + half_min_page);
177
178                 higher *= zoom / 128;
179                 lower *= zoom / 128;
180
181                 val = unzoomed_val + lower;
182                 page = unzoomed_page - higher - lower;
183
184                 page = max(page, min_page_size);
185
186                 if (lower < 0) {
187                         val = max(val, val_at_pointer - half_min_page);
188                 }
189                 else if (lower > 0) {
190                         val = min(val, val_at_pointer - half_min_page);
191                 }
192
193                 val = min(val, adj.get_upper() - min_page_size);
194                 page = min(page, adj.get_upper() - val);*/
195
196                 val = unzoomed_val;
197                 page = unzoomed_page;
198         } else {
199                 val = unzoomed_val;
200                 page = unzoomed_page;
201         }
202
203         /* Round these values to stop the scroomer handlers quivering about during drags */
204         adj.set_page_size (rint (page));
205         adj.set_value (rint (val));
206         adj.value_changed();
207
208         return true;
209 }
210
211 bool
212 Scroomer::on_scroll_event (GdkEventScroll* ev)
213 {
214         switch (ev->direction) {
215         case GDK_SCROLL_UP:
216                 adj.set_value (min (adj.get_value() + adj.get_page_size() / 10.0, adj.get_upper() - adj.get_page_size()));
217                 break;
218         case GDK_SCROLL_DOWN:
219                 adj.set_value (adj.get_value() - adj.get_page_size() / 10.0);
220                 break;
221         default:
222                 return false;
223         }
224
225         return true;
226 }
227
228 bool
229 Scroomer::on_button_press_event (GdkEventButton* ev)
230 {
231         if (ev->button == 1 || ev->button == 3) {
232                 Component comp = point_in(ev->y);
233
234                 if (comp == Total || comp == None) {
235                         return false;
236                 }
237
238                 add_modal_grab();
239                 grab_comp = comp;
240                 grab_y = ev->y;
241                 unzoomed_val = adj.get_value();
242                 unzoomed_page = adj.get_page_size();
243                 grab_window = ev->window;
244
245                 if (ev->button == 3){
246                         pinch = true;
247                 } else {
248                         pinch = false;
249                 }
250
251                 DragStarting (); /* EMIT SIGNAL */
252         }
253
254         if (ev->type == GDK_2BUTTON_PRESS && ev->button == 1) {
255                 DoubleClicked();
256         }
257
258         return true;
259 }
260
261 bool
262 Scroomer::on_button_release_event (GdkEventButton* ev)
263 {
264         if (grab_comp == None || grab_comp == Total) {
265                 return true;
266         }
267
268         if (ev->window != grab_window) {
269                 grab_y = ev->y;
270                 grab_window = ev->window;
271                 return true;
272         }
273
274         if (ev->button != 1 && ev->button != 3) {
275                 return true;
276         }
277
278         switch (grab_comp) {
279         case TopBase:
280                 break;
281         case Handle1:
282                 break;
283         case Slider:
284                 break;
285         case Handle2:
286                 break;
287         case BottomBase:
288                 break;
289         default:
290                 break;
291         }
292
293         grab_comp = None;
294
295         remove_modal_grab();
296         DragFinishing (); /* EMIT SIGNAL */
297         return true;
298 }
299
300 void
301 Scroomer::on_size_allocate (Allocation& a)
302 {
303         Gtk::DrawingArea::on_size_allocate(a);
304
305         position[Total] = a.get_height();
306         set_min_page_size(min_page_size);
307         update();
308 }
309
310 /** Assumes that x and width are correct, and they will not be altered.
311  */
312 void
313 Scroomer::set_comp_rect(GdkRectangle& r, Component c) const
314 {
315         int index = (int) c;
316
317         switch (c) {
318         case None:
319                 return;
320         case Total:
321                 r.y = 0;
322                 r.height = position[Total];
323                 break;
324         default:
325                 r.y = position[index];
326                 r.height = position[index+1] - position[index];
327                 break;
328         }
329 }
330
331 Scroomer::Component
332 Scroomer::point_in(double point) const
333 {
334         for (int i = 0; i < Total; ++i) {
335                 if (position[i+1] >= point) {
336                         return (Component) i;
337                 }
338         }
339
340         return None;
341 }
342
343 void
344 Scroomer::set_min_page_size(double ps)
345 {
346         double coeff = ((double)position[Total]) / (adj.get_upper() - adj.get_lower());
347
348         min_page_size = ps;
349         handle_size = (int) floor((ps * coeff) / 2);
350 }
351
352 void
353 Scroomer::update()
354 {
355         double range = adj.get_upper() - adj.get_lower();
356         //double value = adj.get_value() - adj.get_lower();
357         int height = position[Total];
358         double coeff = ((double) height) / range;
359
360         /* save the old positions to calculate update regions later*/
361         for (int i = Handle1; i < Total; ++i) {
362                 old_pos[i] = position[i];
363         }
364
365         position[BottomBase] = (int) floor(height - (adj.get_value() * coeff));
366         position[Handle2] = position[BottomBase] - handle_size;
367
368         position[Handle1] = (int) floor(height - ((adj.get_value() + adj.get_page_size()) * coeff));
369         position[Slider] = position[Handle1] + handle_size;
370 }
371
372 void
373 Scroomer::adjustment_changed()
374 {
375         //cerr << floor(adj.get_value()) << " " << floor(adj.get_value() + adj.get_page_size()) << endl;
376         Gdk::Rectangle rect;
377         Glib::RefPtr<Gdk::Window> win = get_window();
378
379         update();
380
381         if (!win) {
382                 return;
383         }
384
385         rect.set_x(0);
386         rect.set_width(get_width());
387
388         if (position[Handle1] < old_pos[Handle1]) {
389                 rect.set_y(position[Handle1]);
390                 rect.set_height(old_pos[Slider] - position[Handle1]);
391                 win->invalidate_rect(rect, false);
392         } else if (position[Handle1] > old_pos[Handle1]) {
393                 rect.set_y(old_pos[Handle1]);
394                 rect.set_height(position[Slider] - old_pos[Handle1]);
395                 win->invalidate_rect(rect, false);
396         }
397
398         if (position[Handle2] < old_pos[Handle2]) {
399                 rect.set_y(position[Handle2]);
400                 rect.set_height(old_pos[BottomBase] - position[Handle2]);
401                 win->invalidate_rect(rect, false);
402         } else if (position[Handle2] > old_pos[Handle2]) {
403                 rect.set_y(old_pos[Handle2]);
404                 rect.set_height(position[BottomBase] - old_pos[Handle2]);
405                 win->invalidate_rect(rect, false);
406         }
407 }
408