Add bbt_add that does not take Metric parameter.
[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
53 bool
54 Scroomer::on_motion_notify_event (GdkEventMotion* ev)
55 {
56         double range = adj.get_upper() - adj.get_lower();
57         double pixel2val = range / get_height();
58         double val_at_pointer = ((get_height() - ev->y) * pixel2val) + adj.get_lower();
59         double delta_y = ev->y - grab_y;
60         double half_min_page = min_page_size / 2;
61         double fract = delta_y / position[Total];
62         double scale, temp, zoom;
63         double val, page;
64
65         if (grab_comp == None || grab_comp == Total) {
66                 return true;
67         }
68
69         if (ev->window != grab_window) {
70                 grab_y = ev->y;
71                 grab_window = ev->window;
72                 return true;
73         }
74
75         grab_y = ev->y;
76
77         if (ev->state & GDK_CONTROL_MASK) {
78                 if (ev->state & GDK_MOD1_MASK) {
79                         scale = 0.05;
80                 } else {
81                         scale = 0.1;
82                 }
83         } else {
84                 scale = 1.0;
85         }
86
87         fract = min (1.0, fract);
88         fract = max (-1.0, fract);
89         fract = -fract;
90
91         switch (grab_comp) {
92         case TopBase:
93         case BottomBase:
94                 unzoomed_val += scale * fract * range;
95                 unzoomed_val = min(unzoomed_val, adj.get_upper() - unzoomed_page);
96                 unzoomed_val = max(unzoomed_val, adj.get_lower());
97                 break;
98         case Slider:
99                 unzoomed_val += scale * fract * range;
100                 unzoomed_val = min(unzoomed_val, adj.get_upper() - unzoomed_page);
101                 unzoomed_val = max(unzoomed_val, adj.get_lower());
102                 break;
103         case Handle1:
104                 unzoomed_page += scale * fract * range;
105                 unzoomed_page = min(unzoomed_page, adj.get_upper() - unzoomed_val);
106                 unzoomed_page = max(unzoomed_page, min_page_size);
107                 break;
108         case Handle2:
109                 temp = unzoomed_val + unzoomed_page;
110                 unzoomed_val += scale * fract * range;
111                 unzoomed_val = min(unzoomed_val, temp - min_page_size);
112                 unzoomed_val = max(unzoomed_val, adj.get_lower());
113
114                 unzoomed_page = temp - unzoomed_val;
115                 unzoomed_page = max(unzoomed_page, min_page_size);
116                 break;
117         default:
118                 break;
119         }
120
121         /* Then we handle zoom, which is dragging horizontally. We zoom around the area that is
122          * the current y pointer value, not from the area that was the start of the drag.
123          * the point of zoom must have the same 
124          */
125         
126         if (ev->x > get_width()) {
127                 zoom = ev->x - get_width();
128                 
129                 double higher = unzoomed_val + unzoomed_page - half_min_page - val_at_pointer;
130                 double lower = val_at_pointer - (unzoomed_val + half_min_page);
131
132                 higher *= zoom / 128;
133                 lower *= zoom / 128;
134
135                 val = unzoomed_val + lower;
136                 page = unzoomed_page - higher - lower;
137
138                 page = max(page, min_page_size);
139
140                 if (lower < 0) {
141                         val = max(val, val_at_pointer - half_min_page);
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         } else if (ev->x < 0) {
149                 /* on zoom out increase the page size as well as moving the range towards the mouse pos*/
150                 zoom = abs(ev->x);
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                 }
166                 else if (lower > 0) {
167                         val = min(val, val_at_pointer - half_min_page);
168                 }
169
170                 val = min(val, adj.get_upper() - min_page_size);
171                 page = min(page, adj.get_upper() - val);*/
172
173                 val = unzoomed_val;
174                 page = unzoomed_page;
175         } else {
176                 val = unzoomed_val;
177                 page = unzoomed_page;
178         }
179
180         adj.set_page_size(page);
181         
182         if (val == adj.get_value()) {
183                 adj.value_changed();
184         }
185
186         if (val < adj.get_lower()) {
187                 adj.value_changed();
188         } else if (val > adj.get_upper()) {
189                 adj.value_changed();
190         } else {
191                 adj.set_value(val);
192         }
193
194         return true;
195 }
196
197 bool
198 Scroomer::on_button_press_event (GdkEventButton* ev)
199 {
200         if (ev->button == 1) {
201                 Component comp = point_in(ev->y);
202
203                 if (comp == Total || comp == None) {
204                         return false;
205                 }
206
207                 add_modal_grab();
208                 grab_comp = comp;
209                 grab_y = ev->y;
210                 unzoomed_val = adj.get_value();
211                 unzoomed_page = adj.get_page_size();
212                 grab_window = ev->window;
213         }
214         return false;
215 }
216
217 bool
218 Scroomer::on_button_release_event (GdkEventButton* ev)
219 {
220         if (grab_comp == None || grab_comp == Total) {
221                 return true;
222         }
223
224         if (ev->window != grab_window) {
225                 grab_y = ev->y;
226                 grab_window = ev->window;
227                 return true;
228         }
229
230         if (ev->button != 1) {
231                 return true;
232         }
233
234         switch (grab_comp) {
235         case TopBase:
236                 break;
237         case Handle1:
238                 break;
239         case Slider:
240                 break;
241         case Handle2:
242                 break;
243         case BottomBase:
244                 break;
245         default:
246                 break;
247         }
248         
249         grab_comp = None;
250
251         remove_modal_grab();
252         return true;
253 }
254
255 bool
256 Scroomer::on_scroll_event (GdkEventScroll*)
257 {
258         return true;
259 }
260
261 void
262 Scroomer::on_size_allocate (Allocation& a)
263 {
264         Gtk::DrawingArea::on_size_allocate(a);
265
266         position[Total] = a.get_height();
267         set_min_page_size(min_page_size);
268         update();
269 }
270
271 /** Assumes that x and width are correct, and they will not be altered.
272  */
273 void
274 Scroomer::set_comp_rect(GdkRectangle& r, Component c) const
275 {
276         int index = (int) c;
277
278         switch (c) {
279         case None:
280                 return;
281         case Total:
282                 r.y = 0;
283                 r.height = position[Total];
284                 break;
285         default:
286                 r.y = position[index];
287                 r.height = position[index+1] - position[index];
288                 break;
289         }
290 }
291
292 Scroomer::Component
293 Scroomer::point_in(double point) const
294 {
295         for (int i = 0; i < Total; ++i) {
296                 if (position[i+1] >= point) {
297                         return (Component) i;
298                 }
299         }
300
301         return None;
302 }
303
304 void
305 Scroomer::set_min_page_size(double ps)
306 {
307         double coeff = ((double)position[Total]) / (adj.get_upper() - adj.get_lower());
308
309         min_page_size = ps;
310         handle_size = (int) floor((ps * coeff) / 2);
311 }
312
313 void
314 Scroomer::update()
315 {
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 {
336         //cerr << floor(adj.get_value()) << " " << floor(adj.get_value() + adj.get_page_size()) << endl;
337         Gdk::Rectangle rect;
338         Glib::RefPtr<Gdk::Window> win = get_window();
339
340         update();
341
342         if (!win) {
343                 return;
344         }
345
346         rect.set_x(0);
347         rect.set_width(get_width());
348
349         if (position[Handle1] < old_pos[Handle1]) {
350                 rect.set_y(position[Handle1]);
351                 rect.set_height(old_pos[Slider] - position[Handle1]);
352                 win->invalidate_rect(rect, false);
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         } else if (position[Handle2] > old_pos[Handle2]) {
364                 rect.set_y(old_pos[Handle2]);
365                 rect.set_height(position[BottomBase] - old_pos[Handle2]);
366                 win->invalidate_rect(rect, false);
367         }
368
369         win->process_updates(false);
370 }
371
372 std::string
373 Scroomer::get_comp_name(Component c)
374 {
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 }