ed5d63c4f3e970c58174154a493f75c3160101f3
[synfig.git] / 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, 2008 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         switch(tp.get_after())
225         {
226         case INTERPOLATION_TCB:
227                 window->draw_arc(
228                         gc,
229                         true,
230                         area.get_x(),
231                         area.get_y(),
232                         area.get_width(),
233                         area.get_height(),
234                         64*270,
235                         64*180
236                 );
237                 gc->set_rgb_fg_color(black);
238                 window->draw_arc(
239                         gc,
240                         false,
241                         area.get_x(),
242                         area.get_y(),
243                         area.get_width(),
244                         area.get_height(),
245                         64*270,
246                         64*180
247                 );
248                 break;
249
250         case INTERPOLATION_HALT:
251                 window->draw_arc(
252                         gc,
253                         true,
254                         area.get_x(),
255                         area.get_y()-area.get_height(),
256                         area.get_width(),
257                         area.get_height()*2,
258                         64*270,
259                         64*90
260                 );
261                 gc->set_rgb_fg_color(black);
262                 window->draw_arc(
263                         gc,
264                         false,
265                         area.get_x(),
266                         area.get_y()-area.get_height(),
267                         area.get_width(),
268                         area.get_height()*2,
269                         64*270,
270                         64*90
271                 );
272                 break;
273
274         case INTERPOLATION_LINEAR:
275                 points.clear();
276                 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
277                 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()));
278                 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
279                 window->draw_polygon(gc,true,points);
280                 gc->set_rgb_fg_color(black);
281                 window->draw_lines(gc,points);
282                 break;
283
284         case INTERPOLATION_CONSTANT:
285                 points.clear();
286                 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
287                 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()));
288                 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()+area.get_height()/2));
289                 points.push_back(Gdk::Point(area.get_x()+area.get_width()-area.get_width()/4,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()));
291                 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
292                 window->draw_polygon(gc,true,points);
293                 gc->set_rgb_fg_color(black);
294                 window->draw_lines(gc,points);
295                 break;
296
297         case INTERPOLATION_UNDEFINED: default:
298                 points.clear();
299                 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
300                 points.push_back(Gdk::Point(area.get_x()+area.get_width()-area.get_width()/3,area.get_y()));
301                 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()+area.get_height()/3));
302                 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()+area.get_height()-area.get_height()/3));
303                 points.push_back(Gdk::Point(area.get_x()+area.get_width()-area.get_width()/3,area.get_y()+area.get_height()));
304                 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
305                 window->draw_polygon(gc,true,points);
306                 gc->set_rgb_fg_color(black);
307                 window->draw_lines(gc,points);
308                 break;
309         }
310
311 }
312
313 /* === M E T H O D S ======================================================= */
314
315 /* === E N T R Y P O I N T ================================================= */
316 double defaultfps = 24;
317 const int fullheight = 20;
318
319 Widget_Timeslider::Widget_Timeslider()
320 :layout(Pango::Layout::create(get_pango_context())),
321 adj_default(0,0,2,1/defaultfps,10/defaultfps),
322 adj_timescale(0),
323 //invalidated(false),
324 last_event_time(0),
325 fps(defaultfps),
326 dragscroll(false)
327 {
328         set_size_request(-1,fullheight);
329
330         //                click                    scroll                     zoom
331         add_events( Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK
332                                 | Gdk::BUTTON_MOTION_MASK | Gdk::SCROLL_MASK );
333
334         set_time_adjustment(&adj_default);
335         //update_times();
336 }
337
338 Widget_Timeslider::~Widget_Timeslider()
339 {
340 }
341
342 void Widget_Timeslider::set_time_adjustment(Gtk::Adjustment *x)
343 {
344         //disconnect old connections
345         time_value_change.disconnect();
346         time_other_change.disconnect();
347
348         //connect update function to new adjustment
349         adj_timescale = x;
350
351         if(x)
352         {
353                 time_value_change = x->signal_value_changed().connect(sigc::mem_fun(*this,&Widget_Timeslider::queue_draw));
354                 time_other_change = x->signal_changed().connect(sigc::mem_fun(*this,&Widget_Timeslider::queue_draw));
355                 //invalidated = true;
356                 //refresh();
357         }
358 }
359
360 void Widget_Timeslider::set_global_fps(float d)
361 {
362         if(fps != d)
363         {
364                 fps = d;
365
366                 //update everything since we need to redraw already
367                 //invalidated = true;
368                 //refresh();
369                 queue_draw();
370         }
371 }
372
373 /*void Widget_Timeslider::update_times()
374 {
375         if(adj_timescale)
376         {
377                 start = adj_timescale->get_lower();
378                 end = adj_timescale->get_upper();
379                 current = adj_timescale->get_value();
380         }
381 }*/
382
383 void Widget_Timeslider::refresh()
384 {
385 }
386 /*
387 {
388         if(invalidated)
389         {
390                 queue_draw();
391         }else if(adj_timescale)
392         {
393                 double  l = adj_timescale->get_lower(),
394                                 u = adj_timescale->get_upper(),
395                                 v = adj_timescale->get_value();
396
397                 bool invalid = (l != start) || (u != end) || (v != current);
398
399                 start = l;
400                 end = u;
401                 current = v;
402
403                 if(invalid) queue_draw();
404         }
405 }*/
406
407 bool Widget_Timeslider::redraw(bool /*doublebuffer*/)
408 {
409         Glib::RefPtr<Gdk::Window> window = get_window();
410
411         if(!window) return false;
412
413         Glib::RefPtr<Gdk::GC>   gc = Gdk::GC::create(window);
414         if(!gc) return false;
415
416         //synfig::info("Drawing Timeslider");
417         //clear and update to current values
418         //invalidated = false;
419         //update_times();
420
421         //draw grey rectangle
422         Gdk::Color      c("#7f7f7f");
423         gc->set_rgb_fg_color(c);
424         gc->set_background(c);
425
426         //Get the data for the window and the params to draw it...
427         int w = get_width(), h = get_height();
428
429         window->draw_rectangle(gc,true,0,0,w,h);
430
431         const double EPSILON = 1e-6;
432         if(!adj_timescale || w == 0) return true;
433
434         //Get the time information since we now know it's valid
435         double  start = adj_timescale->get_lower(),
436                         end = adj_timescale->get_upper(),
437                         current = adj_timescale->get_value();
438
439         if(end-start < EPSILON) return true;
440
441         //synfig::info("Drawing Lines");
442
443         //draw all the time stuff
444         double dtdp = (end - start)/get_width();
445         double dpdt = 1/dtdp;
446
447         //lines
448
449         //Draw the time line...
450         double tpx = (current-start)*dpdt;
451         gc->set_rgb_fg_color(Gdk::Color("#ffaf00"));
452         window->draw_line(gc,round_to_int(tpx),0,round_to_int(tpx),fullheight);
453
454         //normal line/text color
455         gc->set_rgb_fg_color(Gdk::Color("#333333"));
456
457         int ifps = round_to_int(fps);
458         if (ifps < 1) ifps = 1;
459
460         std::vector<double> ranges;
461
462         unsigned int pos = 0;
463
464         // build a list of all the factors of the frame rate
465         for (int i = 1; i*i <= ifps; i++)
466                 if ((ifps%i) == 0)
467                 {
468                         ranges.insert(ranges.begin()+pos, i/fps);
469                         if (i*i != ifps)
470                                 ranges.insert(ranges.begin()+pos+1, ifps/i/fps);
471                         pos++;
472                 }
473
474         // fill in any gaps where one factor is more than 2 times the previous
475         std::vector<double>::iterator iter, next;
476         pos = 0;
477         for (pos = 0; pos < ranges.size()-1; pos++)
478         {
479                 iter = ranges.begin()+pos;
480                 next = iter+1;
481                 if (*iter*2 < *next)
482                         ranges.insert(next, *iter*2);
483         }
484
485         double more_ranges[] = {
486                 2, 3, 5, 10, 20, 30, 60, 90, 120, 180,
487                 300, 600, 1200, 1800, 2700, 3600, 3600*2,
488                 3600*4, 3600*8, 3600*16, 3600*32, 3600*64,
489                 3600*128, 3600*256, 3600*512, 3600*1024 };
490
491         ranges.insert(ranges.end(), more_ranges, more_ranges + sizeof(more_ranges)/sizeof(double));
492
493         double lowerrange = dtdp*140, upperrange = dtdp*280;
494         double midrange = (lowerrange + upperrange)/2;
495
496         //find most ideal scale
497         double scale;
498         next = binary_find(ranges.begin(), ranges.end(), midrange);
499         iter = next++;
500
501         if (iter == ranges.end()) iter--;
502         if (next == ranges.end()) next--;
503
504         if (abs(*next - midrange) < abs(*iter - midrange))
505                 iter = next;
506
507         scale = *iter;
508
509         // subdivide into this many tick marks (8 or less)
510         int subdiv = round_to_int(scale * ifps);
511
512         if (subdiv > 8)
513         {
514                 const int ideal = subdiv;
515
516                 // find a number of tick marks that nicely divides the scale
517                 // (5 minutes divided by 6 is 50s, but that's not 'nice' -
518                 //  5 ticks of 1m each is much simpler than 6 ticks of 50s)
519                 for (subdiv = 8; subdiv > 0; subdiv--)
520                         if ((ideal <= ifps*2       && (ideal % (subdiv           )) == 0) ||
521                                 (ideal <= ifps*2*60    && (ideal % (subdiv*ifps      )) == 0) ||
522                                 (ideal <= ifps*2*60*60 && (ideal % (subdiv*ifps*60   )) == 0) ||
523                                 (true                  && (ideal % (subdiv*ifps*60*60)) == 0))
524                                 break;
525
526                 // if we didn't find anything, use 4 ticks
527                 if (!subdiv)
528                         subdiv = 4;
529         }
530
531         time_per_tickmark = scale / subdiv;
532
533         //get first valid line and its position in pixel space
534         double time = 0;
535         double pixel = 0;
536
537         int sdindex = 0;
538
539         double subr = scale / subdiv;
540
541         //get its position inside...
542         time = ceil(start/subr)*subr - start;
543         pixel = time*dpdt;
544
545         //absolute time of the line to be drawn
546         time += start;
547
548         { //inside the big'n
549                 double t = (time/scale - floor(time/scale))*subdiv; // the difference from the big mark in 0:1
550                 //sdindex = (int)floor(t + 0.5); //get how far through the range it is...
551                 sdindex = round_to_int(t); //get how far through the range it is...
552                 if (sdindex == subdiv) sdindex = 0;
553
554                 //synfig::info("Extracted fr %.2lf -> %d", t, sdindex);
555         }
556
557         //synfig::info("Initial values: %.4lf t, %.1lf pixels, %d i", time,pixel,sdindex);
558
559         //loop to draw
560         const int heightbig = 12;
561         const int heightsmall = 4;
562
563         int width = get_width();
564         while( pixel < width )
565         {
566                 int xpx = round_to_int(pixel);
567
568                 //draw big
569                 if(sdindex == 0)
570                 {
571                         window->draw_line(gc,xpx,0,xpx,heightbig);
572                         //round the time to nearest frame and draw the text
573                         Time tm((double)time);
574                         if(get_global_fps()) tm.round(get_global_fps());
575                         Glib::ustring timecode(tm.get_string(get_global_fps(),App::get_time_format()));
576
577                         //gc->set_rgb_fg_color(Gdk::Color("#000000"));
578                         layout->set_text(timecode);
579                         Pango::AttrList attr_list;
580                         // Aproximately a font size of 8 pixels.
581                         // Pango::SCALE = 1024
582                         // create_attr_size waits a number in 1000th of pixels.
583                         // Should be user customizable in the future. Now it is fixed to 10
584                         Pango::AttrInt pango_size(Pango::Attribute::create_attr_size(Pango::SCALE*10));
585                         pango_size.set_start_index(0);
586                         pango_size.set_end_index(64);
587                         attr_list.change(pango_size);
588                         layout->set_attributes(attr_list);
589                         window->draw_layout(gc,xpx+2,0,layout);
590                 }else
591                 {
592                         window->draw_line(gc,xpx,0,xpx,heightsmall);
593                 }
594
595                 //increment time and position
596                 pixel += subr / dtdp;
597                 time += subr;
598
599                 //increment index
600                 if(++sdindex >= subdiv) sdindex -= subdiv;
601         }
602
603         return true;
604 }
605
606 bool Widget_Timeslider::on_motion_notify_event(GdkEventMotion* event) //for dragging
607 {
608         if(!adj_timescale) return false;
609
610         Gdk::ModifierType mod = Gdk::ModifierType(event->state);
611
612         //scrolling...
613
614         //NOTE: we might want to address the possibility of dragging with both buttons held down
615
616         if(mod & Gdk::BUTTON2_MASK)
617         {
618
619                 //we need this for scrolling by dragging
620                 double  curx = event->x;
621
622                 double  start = adj_timescale->get_lower(),
623                                 end = adj_timescale->get_upper();
624
625                 if(dragscroll)
626                 {
627                         if(event->time-last_event_time<30)
628                                 return false;
629                         else
630                                 last_event_time=event->time;
631
632                         if(abs(lastx - curx) < 1 && end != start) return true;
633                         //translate the window and correct it
634
635                         //update our stuff so we are operating correctly
636                         //invalidated = true;
637                         //update_times();
638
639                         //Note: Use inverse of mouse movement because of conceptual space relationship
640                         double diff = lastx - curx; //curx - lastx;
641
642                         //NOTE: This might be incorrect...
643                         //fraction to move...
644                         double dpx = (end - start)/get_width();
645                         lastx = curx;
646
647                         diff *= dpx;
648
649                         //Adjust...
650                         start += diff;
651                         end += diff;
652
653                         //But clamp to bounds if they exist...
654                         //HACK - bounds should not be required for this slider
655                         if(adj_bounds)
656                         {
657                                 if(start < adj_bounds->get_lower())
658                                 {
659                                         diff = adj_bounds->get_lower() - start;
660                                         start += diff;
661                                         end += diff;
662                                 }
663
664                                 if(end > adj_bounds->get_upper())
665                                 {
666                                         diff = adj_bounds->get_upper() - end;
667                                         start += diff;
668                                         end += diff;
669                                 }
670                         }
671
672                         //synfig::info("Scrolling timerange to (%.4f,%.4f)",start,end);
673
674                         adj_timescale->set_lower(start);
675                         adj_timescale->set_upper(end);
676
677                         adj_timescale->changed();
678                 }else
679                 {
680                         dragscroll = true;
681                         lastx = curx;
682                         //lasty = cury;
683                 }
684
685                 return true;
686         }
687
688         if(mod & Gdk::BUTTON1_MASK)
689         {
690                 double curx = event->x;
691
692                 //get time from drag...
693                 double  start = adj_timescale->get_lower(),
694                                 end = adj_timescale->get_upper(),
695                                 current = adj_timescale->get_value();
696                 double t = start + curx*(end - start)/get_width();
697
698                 //snap it to fps - if they exist...
699                 if(fps)
700                 {
701                         t = floor(t*fps + 0.5)/fps;
702                 }
703
704                 //set time if needed
705                 if(current != t)
706                 {
707                         adj_timescale->set_value(t);
708
709                         //Fixed this to actually do what it's supposed to...
710                         if(event->time-last_event_time>50)
711                         {
712                                 adj_timescale->value_changed();
713                                 last_event_time = event->time;
714                         }
715                 }
716
717                 return true;
718         }
719
720         return false;
721 }
722
723 bool Widget_Timeslider::on_scroll_event(GdkEventScroll* event) //for zooming
724 {
725         if(!adj_timescale) return false;
726
727         //Update so we are calculating based on current values
728         //update_times();
729
730         //figure out if we should center ourselves on the current time
731         bool center = false;
732
733         //we want to zoom in on the time value if control is held down
734         if(Gdk::ModifierType(event->state) & Gdk::CONTROL_MASK)
735                 center = true;
736
737         switch(event->direction)
738         {
739                 case GDK_SCROLL_UP: //zoom in
740                         zoom_in(center);
741                         return true;
742
743                 case GDK_SCROLL_DOWN: //zoom out
744                         zoom_out(center);
745                         return true;
746
747                 case GDK_SCROLL_RIGHT:
748                 case GDK_SCROLL_LEFT:
749                 {
750                         double t = adj_timescale->get_value();
751                         double orig_t = t;
752                         double start = adj_timescale->get_lower();
753                         double end = adj_timescale->get_upper();
754                         double lower = adj_bounds->get_lower();
755                         double upper = adj_bounds->get_upper();
756                         double adj = time_per_tickmark;
757
758                         if( event->direction == GDK_SCROLL_RIGHT )
759                         {
760                                 // step forward one tick
761                                 t += adj;
762
763                                 // don't go past the end of time
764                                 if (t > upper)
765                                         t = upper;
766
767                                 // if we are already in the right half of the slider
768                                 if ((t-start)*2 > (end-start))
769                                 {
770                                         // if we can't scroll the background left one whole tick, scroll it to the end
771                                         if (end > upper - (t-orig_t))
772                                         {
773                                                 adj_timescale->set_lower(upper - (end-start));
774                                                 adj_timescale->set_upper(upper);
775                                         }
776                                         // else scroll the background left
777                                         else
778                                         {
779                                                 adj_timescale->set_lower(start + (t-orig_t));
780                                                 adj_timescale->set_upper(start + (t-orig_t) + (end-start));
781                                         }
782                                 }
783                         }
784                         else
785                         {
786                                 // step backwards one tick
787                                 t -= adj;
788
789                                 // don't go past the start of time
790                                 if (t < lower)
791                                         t = lower;
792
793                                 // if we are already in the left half of the slider
794                                 if ((t-start)*2 < (end-start))
795                                 {
796                                         // if we can't scroll the background right one whole tick, scroll it to the beginning
797                                         if (start < lower + (orig_t-t))
798                                         {
799                                                 adj_timescale->set_lower(lower);
800                                                 adj_timescale->set_upper(lower + (end-start));
801                                         }
802                                         // else scroll the background right
803                                         else
804                                         {
805                                                 adj_timescale->set_lower(start - (orig_t-t));
806                                                 adj_timescale->set_upper(start - (orig_t-t) + (end-start));
807                                         }
808                                 }
809                         }
810
811                         if(adj_timescale)
812                         {
813                                 adj_timescale->set_value(t);
814                                 adj_timescale->value_changed();
815                         }
816                         return true;
817                 }
818                 default:
819                         return false;
820         }
821 }
822
823 void Widget_Timeslider::zoom_in(bool centerontime)
824 {
825         if(!adj_timescale) return;
826
827         double  start = adj_timescale->get_lower(),
828                         end = adj_timescale->get_upper(),
829                         current = adj_timescale->get_value();
830
831         double focuspoint = centerontime ? current : (start + end)/2;
832
833         //calculate new beginning and end
834         end = focuspoint + (end-focuspoint)*zoominfactor;
835         start = focuspoint + (start-focuspoint)*zoominfactor;
836
837         //synfig::info("Zooming in timerange to (%.4f,%.4f)",start,end);
838         if(adj_bounds)
839         {
840                 if(start < adj_bounds->get_lower())
841                 {
842                         start = adj_bounds->get_lower();
843                 }
844
845                 if(end > adj_bounds->get_upper())
846                 {
847                         end = adj_bounds->get_upper();
848                 }
849         }
850
851         //reset values
852         adj_timescale->set_lower(start);
853         adj_timescale->set_upper(end);
854
855         //call changed function
856         adj_timescale->changed();
857 }
858
859 void Widget_Timeslider::zoom_out(bool centerontime)
860 {
861         if(!adj_timescale) return;
862
863         double  start = adj_timescale->get_lower(),
864                         end = adj_timescale->get_upper(),
865                         current = adj_timescale->get_value();
866
867         double focuspoint = centerontime ? current : (start + end)/2;
868
869         //calculate new beginning and end
870         end = focuspoint + (end-focuspoint)*zoomoutfactor;
871         start = focuspoint + (start-focuspoint)*zoomoutfactor;
872
873         //synfig::info("Zooming out timerange to (%.4f,%.4f)",start,end);
874         if(adj_bounds)
875         {
876                 if(start < adj_bounds->get_lower())
877                 {
878                         start = adj_bounds->get_lower();
879                 }
880
881                 if(end > adj_bounds->get_upper())
882                 {
883                         end = adj_bounds->get_upper();
884                 }
885         }
886
887         //reset values
888         adj_timescale->set_lower(start);
889         adj_timescale->set_upper(end);
890
891         //call changed function
892         adj_timescale->changed();
893 }
894
895 bool Widget_Timeslider::on_button_press_event(GdkEventButton *event) //for clicking
896 {
897         switch(event->button)
898         {
899                 //time click...
900                 case 1:
901                 {
902                         double  start = adj_timescale->get_lower(),
903                                         end = adj_timescale->get_upper(),
904                                         current = adj_timescale->get_value();
905
906                         double w = get_width();
907                         double t = start + (end - start) * event->x / w;
908
909                         t = floor(t*fps + 0.5)/fps;
910
911                         /*synfig::info("Clicking time from %.3lf to %.3lf [(%.2lf,%.2lf) %.2lf / %.2lf ... %.2lf",
912                                                 current, vt, start, end, event->x, w, fps);*/
913
914                         if(t != current)
915                         {
916                                 current = t;
917
918                                 if(adj_timescale)
919                                 {
920                                         adj_timescale->set_value(current);
921                                         adj_timescale->value_changed();
922                                 }
923                         }
924
925                         break;
926                 }
927
928                 //scroll click
929                 case 2:
930                 {
931                         //start dragging
932                         dragscroll = true;
933                         lastx = event->x;
934                         //lasty = event->y;
935
936                         return true;
937                 }
938
939                 default:
940                 {
941                         break;
942                 }
943         }
944
945         return false;
946 }
947
948 bool Widget_Timeslider::on_button_release_event(GdkEventButton *event) //end drag
949 {
950         switch(event->button)
951         {
952                 case 2:
953                 {
954                         //start dragging
955                         dragscroll = false;
956                         return true;
957                 }
958
959                 default:
960                 {
961                         break;
962                 }
963         }
964
965         return false;
966 }