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