fulfill the promise of an in-library Keyboard object, and use platform specific modif...
[ardour.git] / libs / gtkmm2ext / 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/scroomer.h"
23 #include "gtkmm2ext/keyboard.h"
24
25 using namespace Gtkmm2ext;
26 using namespace Gtk;
27 using namespace Gdk;
28 using namespace std;
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         grab_y = ev->y;
78
79         if (ev->state & Keyboard::PrimaryModifier) {
80                 if (ev->state & Keyboard::SecondaryModifier) {
81                         scale = 0.05;
82                 } else {
83                         scale = 0.1;
84                 }
85         } else {
86                 scale = 1.0;
87         }
88
89         fract = min (1.0, fract);
90         fract = max (-1.0, fract);
91         fract = -fract;
92
93         switch (grab_comp) {
94         case TopBase:
95         case BottomBase:
96                 unzoomed_val += scale * fract * range;
97                 unzoomed_val = min(unzoomed_val, adj.get_upper() - unzoomed_page);
98                 unzoomed_val = max(unzoomed_val, adj.get_lower());
99                 break;
100         case Slider:
101                 unzoomed_val += scale * fract * range;
102                 unzoomed_val = min(unzoomed_val, adj.get_upper() - unzoomed_page);
103                 unzoomed_val = max(unzoomed_val, adj.get_lower());
104                 break;
105         case Handle1:
106                 unzoomed_page += scale * fract * range;
107                 unzoomed_page = min(unzoomed_page, adj.get_upper() - unzoomed_val);
108                 unzoomed_page = max(unzoomed_page, min_page_size);
109                 break;
110         case Handle2:
111                 temp = unzoomed_val + unzoomed_page;
112                 unzoomed_val += scale * fract * range;
113                 unzoomed_val = min(unzoomed_val, temp - min_page_size);
114                 unzoomed_val = max(unzoomed_val, adj.get_lower());
115
116                 unzoomed_page = temp - unzoomed_val;
117                 unzoomed_page = max(unzoomed_page, min_page_size);
118                 break;
119         default:
120                 break;
121         }
122
123         /* Then we handle zoom, which is dragging horizontally. We zoom around the area that is
124          * the current y pointer value, not from the area that was the start of the drag.
125          * the point of zoom must have the same 
126          */
127         
128         if (ev->x > get_width()) {
129                 zoom = ev->x - get_width();
130                 
131                 double higher = unzoomed_val + unzoomed_page - half_min_page - val_at_pointer;
132                 double lower = val_at_pointer - (unzoomed_val + half_min_page);
133
134                 higher *= zoom / 128;
135                 lower *= zoom / 128;
136
137                 val = unzoomed_val + lower;
138                 page = unzoomed_page - higher - lower;
139
140                 page = max(page, min_page_size);
141
142                 if (lower < 0) {
143                         val = max(val, val_at_pointer - half_min_page);
144                 } else if (lower > 0) {
145                         val = min(val, val_at_pointer - half_min_page);
146                 }
147
148                 val = min(val, adj.get_upper() - min_page_size);
149                 page = min(page, adj.get_upper() - val);
150         } else if (ev->x < 0) {
151                 /* on zoom out increase the page size as well as moving the range towards the mouse pos*/
152                 zoom = abs(ev->x);
153
154                 /*double higher = unzoomed_val + unzoomed_page - half_min_page - val_at_pointer;
155                 double lower = val_at_pointer - (unzoomed_val + half_min_page);
156
157                 higher *= zoom / 128;
158                 lower *= zoom / 128;
159
160                 val = unzoomed_val + lower;
161                 page = unzoomed_page - higher - lower;
162
163                 page = max(page, min_page_size);
164
165                 if (lower < 0) {
166                         val = max(val, val_at_pointer - half_min_page);
167                 }
168                 else if (lower > 0) {
169                         val = min(val, val_at_pointer - half_min_page);
170                 }
171
172                 val = min(val, adj.get_upper() - min_page_size);
173                 page = min(page, adj.get_upper() - val);*/
174
175                 val = unzoomed_val;
176                 page = unzoomed_page;
177         } else {
178                 val = unzoomed_val;
179                 page = unzoomed_page;
180         }
181
182         adj.set_page_size(page);
183         
184         if (val == adj.get_value()) {
185                 adj.value_changed();
186         }
187
188         if (val < adj.get_lower()) {
189                 adj.value_changed();
190         } else if (val > adj.get_upper()) {
191                 adj.value_changed();
192         } else {
193                 adj.set_value(val);
194         }
195
196         return true;
197 }
198
199 bool
200 Scroomer::on_button_press_event (GdkEventButton* ev)
201 {
202         if (ev->button == 1) {
203                 Component comp = point_in(ev->y);
204
205                 if (comp == Total || comp == None) {
206                         return false;
207                 }
208
209                 add_modal_grab();
210                 grab_comp = comp;
211                 grab_y = ev->y;
212                 unzoomed_val = adj.get_value();
213                 unzoomed_page = adj.get_page_size();
214                 grab_window = ev->window;
215         }
216         return false;
217 }
218
219 bool
220 Scroomer::on_button_release_event (GdkEventButton* ev)
221 {
222         if (grab_comp == None || grab_comp == Total) {
223                 return true;
224         }
225
226         if (ev->window != grab_window) {
227                 grab_y = ev->y;
228                 grab_window = ev->window;
229                 return true;
230         }
231
232         if (ev->button != 1) {
233                 return true;
234         }
235
236         switch (grab_comp) {
237         case TopBase:
238                 break;
239         case Handle1:
240                 break;
241         case Slider:
242                 break;
243         case Handle2:
244                 break;
245         case BottomBase:
246                 break;
247         default:
248                 break;
249         }
250         
251         grab_comp = None;
252
253         remove_modal_grab();
254         return true;
255 }
256
257 bool
258 Scroomer::on_scroll_event (GdkEventScroll*)
259 {
260         return true;
261 }
262
263 void
264 Scroomer::on_size_allocate (Allocation& a)
265 {
266         Gtk::DrawingArea::on_size_allocate(a);
267
268         position[Total] = a.get_height();
269         set_min_page_size(min_page_size);
270         update();
271 }
272
273 /** Assumes that x and width are correct, and they will not be altered.
274  */
275 void
276 Scroomer::set_comp_rect(GdkRectangle& r, Component c) const
277 {
278         int index = (int) c;
279
280         switch (c) {
281         case None:
282                 return;
283         case Total:
284                 r.y = 0;
285                 r.height = position[Total];
286                 break;
287         default:
288                 r.y = position[index];
289                 r.height = position[index+1] - position[index];
290                 break;
291         }
292 }
293
294 Scroomer::Component
295 Scroomer::point_in(double point) const
296 {
297         for (int i = 0; i < Total; ++i) {
298                 if (position[i+1] >= point) {
299                         return (Component) i;
300                 }
301         }
302
303         return None;
304 }
305
306 void
307 Scroomer::set_min_page_size(double ps)
308 {
309         double coeff = ((double)position[Total]) / (adj.get_upper() - adj.get_lower());
310
311         min_page_size = ps;
312         handle_size = (int) floor((ps * coeff) / 2);
313 }
314
315 void
316 Scroomer::update()
317 {
318         double range = adj.get_upper() - adj.get_lower();
319         //double value = adj.get_value() - adj.get_lower();
320         int height = position[Total];
321         double coeff = ((double) height) / range;
322
323         /* save the old positions to calculate update regions later*/
324         for (int i = Handle1; i < Total; ++i) {
325                 old_pos[i] = position[i];
326         }
327
328         position[BottomBase] = (int) floor(height - (adj.get_value() * coeff));
329         position[Handle2] = position[BottomBase] - handle_size;
330
331         position[Handle1] = (int) floor(height - ((adj.get_value() + adj.get_page_size()) * coeff));
332         position[Slider] = position[Handle1] + handle_size;
333 }
334
335 void
336 Scroomer::adjustment_changed()
337 {
338         //cerr << floor(adj.get_value()) << " " << floor(adj.get_value() + adj.get_page_size()) << endl;
339         Gdk::Rectangle rect;
340         Glib::RefPtr<Gdk::Window> win = get_window();
341
342         update();
343
344         if (!win) {
345                 return;
346         }
347
348         rect.set_x(0);
349         rect.set_width(get_width());
350
351         if (position[Handle1] < old_pos[Handle1]) {
352                 rect.set_y(position[Handle1]);
353                 rect.set_height(old_pos[Slider] - position[Handle1]);
354                 win->invalidate_rect(rect, false);
355         } else if (position[Handle1] > old_pos[Handle1]) {
356                 rect.set_y(old_pos[Handle1]);
357                 rect.set_height(position[Slider] - old_pos[Handle1]);
358                 win->invalidate_rect(rect, false);
359         }
360
361         if (position[Handle2] < old_pos[Handle2]) {
362                 rect.set_y(position[Handle2]);
363                 rect.set_height(old_pos[BottomBase] - position[Handle2]);
364                 win->invalidate_rect(rect, false);
365         } else if (position[Handle2] > old_pos[Handle2]) {
366                 rect.set_y(old_pos[Handle2]);
367                 rect.set_height(position[BottomBase] - old_pos[Handle2]);
368                 win->invalidate_rect(rect, false);
369         }
370
371         win->process_updates(false);
372 }
373
374 std::string
375 Scroomer::get_comp_name(Component c)
376 {
377         switch (c) {
378         case TopBase:
379                 return "TopBase";
380         case Handle1:
381                 return "Handle1";
382         case Slider:
383                 return "Slider";
384         case Handle2:
385                 return "Handle2";
386         case BottomBase:
387                 return "BottomBase";
388         case Total:
389                 return "Total";
390         case None:
391                 return "None";
392         default:
393                 return "ERROR";
394         }
395 }