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