Get rid of all references to the bootstrap script since it was removed
[synfig.git] / synfig-studio / trunk / src / gtkmm / widget_timeslider.cpp
1 /* === S Y N F I G ========================================================= */
2 /*!     \file widget_timeslider.cpp
3 **      \brief Time Slider Widget Implementation File
4 **
5 **      $Id$
6 **
7 **      \legal
8 **      Copyright (c) 2004 Adrian Bentley
9 **      Copyright (c) 2007 Chris Moore
10 **
11 **      This package is free software; you can redistribute it and/or
12 **      modify it under the terms of the GNU General Public License as
13 **      published by the Free Software Foundation; either version 2 of
14 **      the License, or (at your option) any later version.
15 **
16 **      This package is distributed in the hope that it will be useful,
17 **      but WITHOUT ANY WARRANTY; without even the implied warranty of
18 **      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 **      General Public License for more details.
20 **      \endlegal
21 */
22 /* ========================================================================= */
23
24 /* === H E A D E R S ======================================================= */
25
26 #ifdef USING_PCH
27 #       include "pch.h"
28 #else
29 #ifdef HAVE_CONFIG_H
30 #       include <config.h>
31 #endif
32
33 #include "widget_timeslider.h"
34
35 #include <ETL/misc>
36
37 #include <cmath>
38
39 #include "general.h"
40
41 #endif
42
43 /* === U S I N G =========================================================== */
44
45 using namespace std;
46 using namespace etl;
47 using namespace synfig;
48
49 using studio::Widget_Timeslider;
50
51 /* === M A C R O S ========================================================= */
52
53 /* === G L O B A L S ======================================================= */
54 const double zoominfactor = 0.75;
55 const double zoomoutfactor = 1/zoominfactor;
56
57 /* === P R O C E D U R E S ================================================= */
58
59 Gdk::Color get_interp_color(synfig::Interpolation x)
60 {
61         switch(x)
62         {
63         case INTERPOLATION_TCB:
64                 return Gdk::Color("#00B000");
65
66                 break;
67
68         case INTERPOLATION_LINEAR:
69                 return Gdk::Color("#B0B000");
70                 break;
71
72         case INTERPOLATION_CONSTANT:
73                 return Gdk::Color("#C70000");
74                 break;
75
76         case INTERPOLATION_HALT:
77                 return Gdk::Color("#00b0b0");
78                 break;
79
80         case INTERPOLATION_MANUAL:
81                 return Gdk::Color("#B000B0");
82                 break;
83
84         case INTERPOLATION_UNDEFINED: default:
85                 return Gdk::Color("#808080");
86                 break;
87         }
88 }
89
90 static Gdk::Color
91 color_darken(Gdk::Color x, float amount)
92 {
93         double   red = x.get_red_p()   * amount;
94         double green = x.get_green_p() * amount;
95         double  blue = x.get_blue_p()  * amount;
96
97         x.set_rgb_p(  red > 1 ? 1 : red,
98                                 green > 1 ? 1 : green,
99                                  blue > 1 ? 1 : blue);
100
101         return x;
102 }
103
104 void
105 studio::render_time_point_to_window(
106         const Glib::RefPtr<Gdk::Drawable>& window,
107         const Gdk::Rectangle& area,
108         const synfig::TimePoint &tp,
109         bool selected
110 )
111 {
112         Glib::RefPtr<Gdk::GC> gc(Gdk::GC::create(window));
113         const Gdk::Color black("#000000");
114
115         if(selected)
116                 gc->set_line_attributes(2,Gdk::LINE_SOLID,Gdk::CAP_BUTT,Gdk::JOIN_MITER);
117         else
118                 gc->set_line_attributes(1,Gdk::LINE_SOLID,Gdk::CAP_BUTT,Gdk::JOIN_MITER);
119
120         Gdk::Color color;
121         std::vector<Gdk::Point> points;
122
123 /*-     BEFORE ------------------------------------- */
124
125         color=get_interp_color(tp.get_before());
126         color=color_darken(color,1.0f);
127         if(selected)color=color_darken(color,1.3f);
128         gc->set_rgb_fg_color(color);
129
130         switch(tp.get_before())
131         {
132         case INTERPOLATION_TCB:
133                 window->draw_arc(
134                         gc,
135                         true,
136                         area.get_x(),
137                         area.get_y(),
138                         area.get_width(),
139                         area.get_height(),
140                         64*90,
141                         64*180
142                 );
143                 gc->set_rgb_fg_color(black);
144                 window->draw_arc(
145                         gc,
146                         false,
147                         area.get_x(),
148                         area.get_y(),
149                         area.get_width(),
150                         area.get_height(),
151                         64*90,
152                         64*180
153                 );
154                 break;
155
156         case INTERPOLATION_HALT:
157                 window->draw_arc(
158                         gc,
159                         true,
160                         area.get_x(),
161                         area.get_y(),
162                         area.get_width(),
163                         area.get_height()*2,
164                         64*90,
165                         64*90
166                 );
167                 gc->set_rgb_fg_color(black);
168                 window->draw_arc(
169                         gc,
170                         false,
171                         area.get_x(),
172                         area.get_y(),
173                         area.get_width(),
174                         area.get_height()*2,
175                         64*90,
176                         64*90
177                 );
178                 break;
179
180         case INTERPOLATION_LINEAR:
181                 points.clear();
182                 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
183                 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()));
184                 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
185                 window->draw_polygon(gc,true,points);
186                 gc->set_rgb_fg_color(black);
187                 window->draw_lines(gc,points);
188                 break;
189
190         case INTERPOLATION_CONSTANT:
191                 points.clear();
192                 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
193                 points.push_back(Gdk::Point(area.get_x()+area.get_width()/4,area.get_y()));
194                 points.push_back(Gdk::Point(area.get_x()+area.get_width()/4,area.get_y()+area.get_height()/2));
195                 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()/2));
196                 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()));
197                 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
198                 window->draw_polygon(gc,true,points);
199                 gc->set_rgb_fg_color(black);
200                 window->draw_lines(gc,points);
201                 break;
202
203         case INTERPOLATION_UNDEFINED: default:
204                 points.clear();
205                 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
206                 points.push_back(Gdk::Point(area.get_x()+area.get_width()/3,area.get_y()));
207                 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()/3));
208                 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()-area.get_height()/3));
209                 points.push_back(Gdk::Point(area.get_x()+area.get_width()/3,area.get_y()+area.get_height()));
210                 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
211                 window->draw_polygon(gc,true,points);
212                 gc->set_rgb_fg_color(black);
213                 window->draw_lines(gc,points);
214                 break;
215         }
216
217 /*-     AFTER -------------------------------------- */
218
219         color=get_interp_color(tp.get_after());
220         color=color_darken(color,0.8f);
221         if(selected)color=color_darken(color,1.3f);
222         gc->set_rgb_fg_color(color);
223
224
225         switch(tp.get_after())
226         {
227         case INTERPOLATION_TCB:
228                 window->draw_arc(
229                         gc,
230                         true,
231                         area.get_x(),
232                         area.get_y(),
233                         area.get_width(),
234                         area.get_height(),
235                         64*270,
236                         64*180
237                 );
238                 gc->set_rgb_fg_color(black);
239                 window->draw_arc(
240                         gc,
241                         false,
242                         area.get_x(),
243                         area.get_y(),
244                         area.get_width(),
245                         area.get_height(),
246                         64*270,
247                         64*180
248                 );
249                 break;
250
251         case INTERPOLATION_HALT:
252                 window->draw_arc(
253                         gc,
254                         true,
255                         area.get_x(),
256                         area.get_y()-area.get_height(),
257                         area.get_width(),
258                         area.get_height()*2,
259                         64*270,
260                         64*90
261                 );
262                 gc->set_rgb_fg_color(black);
263                 window->draw_arc(
264                         gc,
265                         false,
266                         area.get_x(),
267                         area.get_y()-area.get_height(),
268                         area.get_width(),
269                         area.get_height()*2,
270                         64*270,
271                         64*90
272                 );
273                 break;
274
275         case INTERPOLATION_LINEAR:
276                 points.clear();
277                 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
278                 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()));
279                 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
280                 window->draw_polygon(gc,true,points);
281                 gc->set_rgb_fg_color(black);
282                 window->draw_lines(gc,points);
283                 break;
284
285         case INTERPOLATION_CONSTANT:
286                 points.clear();
287                 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
288                 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()));
289                 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()+area.get_height()/2));
290                 points.push_back(Gdk::Point(area.get_x()+area.get_width()-area.get_width()/4,area.get_y()+area.get_height()/2));
291                 points.push_back(Gdk::Point(area.get_x()+area.get_width()-area.get_width()/4,area.get_y()+area.get_height()));
292                 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
293                 window->draw_polygon(gc,true,points);
294                 gc->set_rgb_fg_color(black);
295                 window->draw_lines(gc,points);
296                 break;
297
298         case INTERPOLATION_UNDEFINED: default:
299                 points.clear();
300                 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
301                 points.push_back(Gdk::Point(area.get_x()+area.get_width()-area.get_width()/3,area.get_y()));
302                 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()+area.get_height()/3));
303                 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()+area.get_height()-area.get_height()/3));
304                 points.push_back(Gdk::Point(area.get_x()+area.get_width()-area.get_width()/3,area.get_y()+area.get_height()));
305                 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
306                 window->draw_polygon(gc,true,points);
307                 gc->set_rgb_fg_color(black);
308                 window->draw_lines(gc,points);
309                 break;
310         }
311
312 }
313
314 /* === M E T H O D S ======================================================= */
315
316 /* === E N T R Y P O I N T ================================================= */
317 double defaultfps = 24;
318 const int fullheight = 20;
319
320 Widget_Timeslider::Widget_Timeslider()
321 :layout(Pango::Layout::create(get_pango_context())),
322 adj_default(0,0,2,1/defaultfps,10/defaultfps),
323 adj_timescale(0),
324 //invalidated(false),
325 last_event_time(0),
326 fps(defaultfps),
327 dragscroll(false)
328 {
329         set_size_request(-1,fullheight);
330
331         //                click                    scroll                     zoom
332         add_events( Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK
333                                 | Gdk::BUTTON_MOTION_MASK | Gdk::SCROLL_MASK );
334
335         set_time_adjustment(&adj_default);
336         //update_times();
337 }
338
339 Widget_Timeslider::~Widget_Timeslider()
340 {
341 }
342
343 void Widget_Timeslider::set_time_adjustment(Gtk::Adjustment *x)
344 {
345         //disconnect old connections
346         time_value_change.disconnect();
347         time_other_change.disconnect();
348
349         //connect update function to new adjustment
350         adj_timescale = x;
351
352         if(x)
353         {
354                 time_value_change = x->signal_value_changed().connect(sigc::mem_fun(*this,&Widget_Timeslider::queue_draw));
355                 time_other_change = x->signal_changed().connect(sigc::mem_fun(*this,&Widget_Timeslider::queue_draw));
356                 //invalidated = true;
357                 //refresh();
358         }
359 }
360
361 void Widget_Timeslider::set_global_fps(float d)
362 {
363         if(fps != d)
364         {
365                 fps = d;
366
367                 //update everything since we need to redraw already
368                 //invalidated = true;
369                 //refresh();
370                 queue_draw();
371         }
372 }
373
374 /*void Widget_Timeslider::update_times()
375 {
376         if(adj_timescale)
377         {
378                 start = adj_timescale->get_lower();
379                 end = adj_timescale->get_upper();
380                 current = adj_timescale->get_value();
381         }
382 }*/
383
384 void Widget_Timeslider::refresh()
385 {
386 }
387 /*
388 {
389         if(invalidated)
390         {
391                 queue_draw();
392         }else if(adj_timescale)
393         {
394                 double  l = adj_timescale->get_lower(),
395                                 u = adj_timescale->get_upper(),
396                                 v = adj_timescale->get_value();
397
398                 bool invalid = (l != start) || (u != end) || (v != current);
399
400                 start = l;
401                 end = u;
402                 current = v;
403
404                 if(invalid) queue_draw();
405         }
406 }*/
407
408 bool Widget_Timeslider::redraw(bool /*doublebuffer*/)
409 {
410         Glib::RefPtr<Gdk::Window> window = get_window();
411
412         if(!window) return false;
413
414         Glib::RefPtr<Gdk::GC>   gc = Gdk::GC::create(window);
415         if(!gc) return false;
416
417         //synfig::info("Drawing Timeslider");
418         //clear and update to current values
419         //invalidated = false;
420         //update_times();
421
422         //draw grey rectangle
423         Gdk::Color      c("#7f7f7f");
424         gc->set_rgb_fg_color(c);
425         gc->set_background(c);
426
427         //Get the data for the window and the params to draw it...
428         int w = get_width(), h = get_height();
429
430         window->draw_rectangle(gc,true,0,0,w,h);
431
432         const double EPSILON = 1e-6;
433         if(!adj_timescale || w == 0) return true;
434
435         //Get the time information since we now know it's valid
436         double  start = adj_timescale->get_lower(),
437                         end = adj_timescale->get_upper(),
438                         current = adj_timescale->get_value();
439
440         if(end-start < EPSILON) return true;
441
442         //synfig::info("Drawing Lines");
443
444         //draw all the time stuff
445         double dtdp = (end - start)/get_width();
446         double dpdt = 1/dtdp;
447
448         //lines
449
450         //Draw the time line...
451         double tpx = (current-start)*dpdt;
452         gc->set_rgb_fg_color(Gdk::Color("#ffaf00"));
453         window->draw_line(gc,round_to_int(tpx),0,round_to_int(tpx),fullheight);
454
455         //normal line/text color
456         gc->set_rgb_fg_color(Gdk::Color("#333333"));
457
458         //draw these lines... (always 5 between) maybe 6?
459         const int subdiv = 4;
460
461         //1h 45 30 20 10 5
462         //..., 3m, 2m, 1m30s, 1m, 30s, 20s, 10s, 5s, 3s, 2s, 1s, 0.5s
463         //frames... (how???)
464         double ranges[] =
465         { 1.0/fps,subdiv/fps,0.25,0.5, 1, 2, 3, 5, 10, 20, 30, 60, 90, 120, 180, 300, 600, 1200, 1800, 2700, 3600 };
466         //{ 3600, 2700, 1800, 1200, 600, 300, 180, 120, 90, 60, 30, 20, 10, 5, 3, 2, 1, 0.5 };
467         const int ranges_size = sizeof(ranges)/sizeof(double);
468
469         double lowerrange = dtdp*75, upperrange = dtdp*150;
470         double midrange = (lowerrange + upperrange)/2;
471
472         //find most ideal scale
473         double scale = ranges[0];
474         {
475                 double *val = binary_find(ranges, ranges+ranges_size, midrange);
476                 double *after = val+1;
477
478                 if(val >= ranges+ranges_size)
479                 {
480                         val = ranges+ranges_size-1;
481                 }
482
483                 if(after >= ranges+ranges_size)
484                 {
485                         after = ranges+ranges_size-1;
486                 }
487
488                 scale = *val;
489
490                 double diff = abs(scale - midrange), diff2 = abs(*after - midrange);
491                 if(diff2 < diff)
492                         scale = *after;
493         }
494
495         //synfig::info("Range found: (l %.2lf,u %.2lf - m %.2lf) -> %.2lf",lowerrange,upperrange,midrange,scale);
496
497         //search around this area to get the right one
498
499
500         //get first valid line and its position in pixel space
501         double time = 0;
502         double pixel = 0;
503
504         int sdindex = 0;
505
506         double subr = scale / subdiv;
507
508         //get its position inside...
509         time = ceil(start/subr)*subr - start;
510         pixel = time*dpdt;
511
512         //absolute time of the line to be drawn
513         time += start;
514
515         { //inside the big'n
516                 double t = (time/scale - floor(time/scale))*subdiv; // the difference from the big mark in 0:1
517                 //sdindex = (int)floor(t + 0.5); //get how far through the range it is...
518                 sdindex = round_to_int(t); //get how far through the range it is...
519
520                 //synfig::info("Extracted fr %.2lf -> %d", t, sdindex);
521         }
522
523         //synfig::info("Initial values: %.4lf t, %.1lf pixels, %d i", time,pixel,sdindex);
524
525         //loop to draw
526         const int heightbig = 12;
527         const int heightsmall = 4;
528
529         int width = get_width();
530         while( pixel < width )
531         {
532                 int xpx = round_to_int(pixel);
533
534                 //draw big
535                 if(sdindex == 0)
536                 {
537                         window->draw_line(gc,xpx,0,xpx,heightbig);
538                         //round the time to nearest frame and draw the text
539                         Time tm((double)time);
540                         if(get_global_fps()) tm.round(get_global_fps());
541                         Glib::ustring timecode(tm.get_string(get_global_fps(),App::get_time_format()));
542
543                         //gc->set_rgb_fg_color(Gdk::Color("#000000"));
544                         layout->set_text(timecode);
545                         window->draw_layout(gc,xpx+2,heightsmall,layout);
546                 }else
547                 {
548                         window->draw_line(gc,xpx,0,xpx,heightsmall);
549                 }
550
551                 //increment time and position
552                 pixel += subr / dtdp;
553                 time += subr;
554
555                 //increment index
556                 if(++sdindex >= subdiv) sdindex -= subdiv;
557         }
558
559         return true;
560 }
561
562 bool Widget_Timeslider::on_motion_notify_event(GdkEventMotion* event) //for dragging
563 {
564         if(!adj_timescale) return false;
565
566         Gdk::ModifierType mod = Gdk::ModifierType(event->state);
567
568         //scrolling...
569
570         //NOTE: we might want to address the possibility of dragging with both buttons held down
571
572         if(mod & Gdk::BUTTON2_MASK)
573         {
574
575                 //we need this for scrolling by dragging
576                 double  curx = event->x;
577
578                 double  start = adj_timescale->get_lower(),
579                                 end = adj_timescale->get_upper();
580
581
582                 if(dragscroll)
583                 {
584                         if(event->time-last_event_time<30)
585                                 return false;
586                         else
587                                 last_event_time=event->time;
588
589                         if(abs(lastx - curx) < 1 && end != start) return true;
590                         //translate the window and correct it
591
592                         //update our stuff so we are operating correctly
593                         //invalidated = true;
594                         //update_times();
595
596                         //Note: Use inverse of mouse movement because of conceptual space relationship
597                         double diff = lastx - curx; //curx - lastx;
598
599                         //NOTE: This might be incorrect...
600                         //fraction to move...
601                         double dpx = (end - start)/get_width();
602                         lastx = curx;
603
604                         diff *= dpx;
605
606                         //Adjust...
607                         start += diff;
608                         end += diff;
609
610                         //But clamp to bounds if they exist...
611                         //HACK - bounds should not be required for this slider
612                         if(adj_bounds)
613                         {
614                                 if(start < adj_bounds->get_lower())
615                                 {
616                                         diff = adj_bounds->get_lower() - start;
617                                         start += diff;
618                                         end += diff;
619                                 }
620
621                                 if(end > adj_bounds->get_upper())
622                                 {
623                                         diff = adj_bounds->get_upper() - end;
624                                         start += diff;
625                                         end += diff;
626                                 }
627                         }
628
629                         //synfig::info("Scrolling timerange to (%.4f,%.4f)",start,end);
630
631                         adj_timescale->set_lower(start);
632                         adj_timescale->set_upper(end);
633
634                         adj_timescale->changed();
635                 }else
636                 {
637                         dragscroll = true;
638                         lastx = curx;
639                         //lasty = cury;
640                 }
641
642                 return true;
643         }
644
645         if(mod & Gdk::BUTTON1_MASK)
646         {
647                 double curx = event->x;
648
649                 //get time from drag...
650                 double  start = adj_timescale->get_lower(),
651                                 end = adj_timescale->get_upper(),
652                                 current = adj_timescale->get_value();
653                 double t = start + curx*(end - start)/get_width();
654
655                 //snap it to fps - if they exist...
656                 if(fps)
657                 {
658                         t = floor(t*fps + 0.5)/fps;
659                 }
660
661                 //set time if needed
662                 if(current != t)
663                 {
664                         adj_timescale->set_value(t);
665
666                         //Fixed this to actually do what it's supposed to...
667                         if(event->time-last_event_time>50)
668                         {
669                                 adj_timescale->value_changed();
670                                 last_event_time = event->time;
671                         }
672                 }
673
674                 return true;
675         }
676
677         return false;
678 }
679
680 bool Widget_Timeslider::on_scroll_event(GdkEventScroll* event) //for zooming
681 {
682         if(!adj_timescale) return false;
683
684         //Update so we are calculating based on current values
685         //update_times();
686
687         //figure out if we should center ourselves on the current time
688         bool center = false;
689
690         //we want to zoom in on the time value if control is held down
691         if(Gdk::ModifierType(event->state) & Gdk::CONTROL_MASK)
692         {
693                 center = true;
694         }
695
696         switch(event->direction)
697         {
698                 case GDK_SCROLL_UP: //zoom in
699                 {
700                         zoom_in(center);
701
702                         return true;
703                 }
704                 case GDK_SCROLL_DOWN: //zoom out
705                 {
706                         zoom_out(center);
707
708                         return true;
709                 }
710                 
711                 case GDK_SCROLL_RIGHT:
712                 case GDK_SCROLL_LEFT:
713                 {       
714                         double t = adj_timescale->get_value();
715                         double start = adj_timescale->get_lower();
716                         double end = adj_timescale->get_upper();
717                         /*
718                         FIXME: be more intelligent about how far to scroll
719                         Perhaps it should be based on the tickmarks?
720                         for e.g. 1/4 of a tick mark per scroll event
721                         Obviously this  would need post-rounding to 1/fps
722                         */
723                         double adj = 1.0/fps;
724
725                         if( event->direction == GDK_SCROLL_RIGHT )
726                                 t += adj;
727                         else
728                                 t -= adj;
729
730                         if( t < start ){
731                                 adj_timescale->set_lower(t);
732                                 adj_timescale->set_upper(t+end-start);
733                         } else if( t > end ){ 
734                                 adj_timescale->set_upper(t);
735                                 adj_timescale->set_lower(t-end+start);
736                         }
737
738                         if(adj_timescale)
739                         {
740                                 adj_timescale->set_value(t);
741                                 adj_timescale->value_changed();
742                         }
743                         return true;
744                 }
745                 
746                 default:
747                 {
748                         return false;
749                 }
750         }
751 }
752
753 void Widget_Timeslider::zoom_in(bool centerontime)
754 {
755         if(!adj_timescale) return;
756
757         double  start = adj_timescale->get_lower(),
758                         end = adj_timescale->get_upper(),
759                         current = adj_timescale->get_value();
760
761         double focuspoint = centerontime ? current : (start + end)/2;
762
763         //calculate new beginning and end
764         end = focuspoint + (end-focuspoint)*zoominfactor;
765         start = focuspoint + (start-focuspoint)*zoominfactor;
766
767         //synfig::info("Zooming in timerange to (%.4f,%.4f)",start,end);
768         if(adj_bounds)
769         {
770                 if(start < adj_bounds->get_lower())
771                 {
772                         start = adj_bounds->get_lower();
773                 }
774
775                 if(end > adj_bounds->get_upper())
776                 {
777                         end = adj_bounds->get_upper();
778                 }
779         }
780
781         //reset values
782         adj_timescale->set_lower(start);
783         adj_timescale->set_upper(end);
784
785         //call changed function
786         adj_timescale->changed();
787 }
788
789 void Widget_Timeslider::zoom_out(bool centerontime)
790 {
791         if(!adj_timescale) return;
792
793         double  start = adj_timescale->get_lower(),
794                         end = adj_timescale->get_upper(),
795                         current = adj_timescale->get_value();
796
797         double focuspoint = centerontime ? current : (start + end)/2;
798
799         //calculate new beginning and end
800         end = focuspoint + (end-focuspoint)*zoomoutfactor;
801         start = focuspoint + (start-focuspoint)*zoomoutfactor;
802
803         //synfig::info("Zooming out timerange to (%.4f,%.4f)",start,end);
804         if(adj_bounds)
805         {
806                 if(start < adj_bounds->get_lower())
807                 {
808                         start = adj_bounds->get_lower();
809                 }
810
811                 if(end > adj_bounds->get_upper())
812                 {
813                         end = adj_bounds->get_upper();
814                 }
815         }
816
817         //reset values
818         adj_timescale->set_lower(start);
819         adj_timescale->set_upper(end);
820
821         //call changed function
822         adj_timescale->changed();
823 }
824
825 bool Widget_Timeslider::on_button_press_event(GdkEventButton *event) //for clicking
826 {
827         switch(event->button)
828         {
829                 //time click...
830                 case 1:
831                 {
832                         double  start = adj_timescale->get_lower(),
833                                         end = adj_timescale->get_upper(),
834                                         current = adj_timescale->get_value();
835
836                         double w = get_width();
837                         double t = start + (end - start) * event->x / w;
838
839                         t = floor(t*fps + 0.5)/fps;
840
841                         /*synfig::info("Clicking time from %.3lf to %.3lf [(%.2lf,%.2lf) %.2lf / %.2lf ... %.2lf",
842                                                 current, vt, start, end, event->x, w, fps);*/
843
844                         if(t != current)
845                         {
846                                 current = t;
847
848                                 if(adj_timescale)
849                                 {
850                                         adj_timescale->set_value(current);
851                                         adj_timescale->value_changed();
852                                 }
853                         }
854
855                         break;
856                 }
857
858                 //scroll click
859                 case 2:
860                 {
861                         //start dragging
862                         dragscroll = true;
863                         lastx = event->x;
864                         //lasty = event->y;
865
866                         return true;
867                 }
868
869                 default:
870                 {
871                         break;
872                 }
873         }
874
875         return false;
876 }
877
878 bool Widget_Timeslider::on_button_release_event(GdkEventButton *event) //end drag
879 {
880         switch(event->button)
881         {
882                 case 2:
883                 {
884                         //start dragging
885                         dragscroll = false;
886                         return true;
887                 }
888
889                 default:
890                 {
891                         break;
892                 }
893         }
894
895         return false;
896 }