1 /* === S Y N F I G ========================================================= */
2 /*! \file widget_timeslider.cpp
3 ** \brief Time Slider Widget Implementation File
8 ** Copyright (c) 2004 Adrian Bentley
9 ** Copyright (c) 2007 Chris Moore
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.
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.
22 /* ========================================================================= */
24 /* === H E A D E R S ======================================================= */
33 #include "widget_timeslider.h"
41 /* === U S I N G =========================================================== */
45 using namespace synfig;
47 using studio::Widget_Timeslider;
49 /* === M A C R O S ========================================================= */
51 /* === G L O B A L S ======================================================= */
52 const double zoominfactor = 0.75;
53 const double zoomoutfactor = 1/zoominfactor;
55 /* === P R O C E D U R E S ================================================= */
57 Gdk::Color get_interp_color(synfig::Interpolation x)
61 case INTERPOLATION_TCB:
62 return Gdk::Color("#00B000");
66 case INTERPOLATION_LINEAR:
67 return Gdk::Color("#B0B000");
70 case INTERPOLATION_CONSTANT:
71 return Gdk::Color("#C70000");
74 case INTERPOLATION_HALT:
75 return Gdk::Color("#00b0b0");
78 case INTERPOLATION_MANUAL:
79 return Gdk::Color("#B000B0");
82 case INTERPOLATION_UNDEFINED: default:
83 return Gdk::Color("#808080");
89 color_darken(Gdk::Color x, float amount)
91 double red = x.get_red_p() * amount;
92 double green = x.get_green_p() * amount;
93 double blue = x.get_blue_p() * amount;
95 x.set_rgb_p( red > 1 ? 1 : red,
96 green > 1 ? 1 : green,
103 studio::render_time_point_to_window(
104 const Glib::RefPtr<Gdk::Drawable>& window,
105 const Gdk::Rectangle& area,
106 const synfig::TimePoint &tp,
110 Glib::RefPtr<Gdk::GC> gc(Gdk::GC::create(window));
111 const Gdk::Color black("#000000");
114 gc->set_line_attributes(2,Gdk::LINE_SOLID,Gdk::CAP_BUTT,Gdk::JOIN_MITER);
116 gc->set_line_attributes(1,Gdk::LINE_SOLID,Gdk::CAP_BUTT,Gdk::JOIN_MITER);
119 std::vector<Gdk::Point> points;
121 /*- BEFORE ------------------------------------- */
123 color=get_interp_color(tp.get_before());
124 color=color_darken(color,1.0f);
125 if(selected)color=color_darken(color,1.3f);
126 gc->set_rgb_fg_color(color);
128 switch(tp.get_before())
130 case INTERPOLATION_TCB:
141 gc->set_rgb_fg_color(black);
154 case INTERPOLATION_HALT:
165 gc->set_rgb_fg_color(black);
178 case INTERPOLATION_LINEAR:
180 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
181 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()));
182 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
183 window->draw_polygon(gc,true,points);
184 gc->set_rgb_fg_color(black);
185 window->draw_lines(gc,points);
188 case INTERPOLATION_CONSTANT:
190 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
191 points.push_back(Gdk::Point(area.get_x()+area.get_width()/4,area.get_y()));
192 points.push_back(Gdk::Point(area.get_x()+area.get_width()/4,area.get_y()+area.get_height()/2));
193 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()/2));
194 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()));
195 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
196 window->draw_polygon(gc,true,points);
197 gc->set_rgb_fg_color(black);
198 window->draw_lines(gc,points);
201 case INTERPOLATION_UNDEFINED: default:
203 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
204 points.push_back(Gdk::Point(area.get_x()+area.get_width()/3,area.get_y()));
205 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()/3));
206 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()*2/3));
207 points.push_back(Gdk::Point(area.get_x()+area.get_width()/3,area.get_y()+area.get_height()));
208 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
209 window->draw_polygon(gc,true,points);
210 gc->set_rgb_fg_color(black);
211 window->draw_lines(gc,points);
215 /*- AFTER -------------------------------------- */
217 color=get_interp_color(tp.get_after());
218 color=color_darken(color,0.8f);
219 if(selected)color=color_darken(color,1.3f);
220 gc->set_rgb_fg_color(color);
223 switch(tp.get_after())
225 case INTERPOLATION_TCB:
236 gc->set_rgb_fg_color(black);
249 case INTERPOLATION_HALT:
254 area.get_y()-area.get_height(),
260 gc->set_rgb_fg_color(black);
265 area.get_y()-area.get_height(),
273 case INTERPOLATION_LINEAR:
275 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
276 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()));
277 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
278 window->draw_polygon(gc,true,points);
279 gc->set_rgb_fg_color(black);
280 window->draw_lines(gc,points);
283 case INTERPOLATION_CONSTANT:
285 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
286 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()));
287 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()+area.get_height()/2));
288 points.push_back(Gdk::Point(area.get_x()+area.get_width()*3/4,area.get_y()+area.get_height()/2));
289 points.push_back(Gdk::Point(area.get_x()+area.get_width()*3/4,area.get_y()+area.get_height()));
290 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
291 window->draw_polygon(gc,true,points);
292 gc->set_rgb_fg_color(black);
293 window->draw_lines(gc,points);
296 case INTERPOLATION_UNDEFINED: default:
298 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
299 points.push_back(Gdk::Point(area.get_x()+area.get_width()*2/3,area.get_y()));
300 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()+area.get_height()/3));
301 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()+area.get_height()*2/3));
302 points.push_back(Gdk::Point(area.get_x()+area.get_width()*2/3,area.get_y()+area.get_height()));
303 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
304 window->draw_polygon(gc,true,points);
305 gc->set_rgb_fg_color(black);
306 window->draw_lines(gc,points);
312 /* === M E T H O D S ======================================================= */
314 /* === E N T R Y P O I N T ================================================= */
315 double defaultfps = 24;
316 const int fullheight = 20;
318 Widget_Timeslider::Widget_Timeslider()
319 :layout(Pango::Layout::create(get_pango_context())),
320 adj_default(0,0,2,1/defaultfps,10/defaultfps),
322 //invalidated(false),
327 set_size_request(-1,fullheight);
330 add_events( Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK
331 | Gdk::BUTTON_MOTION_MASK | Gdk::SCROLL_MASK );
333 set_time_adjustment(&adj_default);
337 Widget_Timeslider::~Widget_Timeslider()
341 void Widget_Timeslider::set_time_adjustment(Gtk::Adjustment *x)
343 //disconnect old connections
344 time_value_change.disconnect();
345 time_other_change.disconnect();
347 //connect update function to new adjustment
352 time_value_change = x->signal_value_changed().connect(sigc::mem_fun(*this,&Widget_Timeslider::queue_draw));
353 time_other_change = x->signal_changed().connect(sigc::mem_fun(*this,&Widget_Timeslider::queue_draw));
354 //invalidated = true;
359 void Widget_Timeslider::set_global_fps(float d)
365 //update everything since we need to redraw already
366 //invalidated = true;
372 /*void Widget_Timeslider::update_times()
376 start = adj_timescale->get_lower();
377 end = adj_timescale->get_upper();
378 current = adj_timescale->get_value();
382 void Widget_Timeslider::refresh()
390 }else if(adj_timescale)
392 double l = adj_timescale->get_lower(),
393 u = adj_timescale->get_upper(),
394 v = adj_timescale->get_value();
396 bool invalid = (l != start) || (u != end) || (v != current);
402 if(invalid) queue_draw();
406 bool Widget_Timeslider::redraw(bool /*doublebuffer*/)
408 Glib::RefPtr<Gdk::Window> window = get_window();
410 if(!window) return false;
412 Glib::RefPtr<Gdk::GC> gc = Gdk::GC::create(window);
413 if(!gc) return false;
415 //synfig::info("Drawing Timeslider");
416 //clear and update to current values
417 //invalidated = false;
420 //draw grey rectangle
421 Gdk::Color c("#7f7f7f");
422 gc->set_rgb_fg_color(c);
423 gc->set_background(c);
425 //Get the data for the window and the params to draw it...
426 int w = get_width(), h = get_height();
428 window->draw_rectangle(gc,true,0,0,w,h);
430 const double EPSILON = 1e-6;
431 if(!adj_timescale || w == 0) return true;
433 //Get the time information since we now know it's valid
434 double start = adj_timescale->get_lower(),
435 end = adj_timescale->get_upper(),
436 current = adj_timescale->get_value();
438 if(end-start < EPSILON) return true;
440 //synfig::info("Drawing Lines");
442 //draw all the time stuff
443 double dtdp = (end - start)/get_width();
444 double dpdt = 1/dtdp;
448 //Draw the time line...
449 double tpx = (current-start)*dpdt;
450 gc->set_rgb_fg_color(Gdk::Color("#ffaf00"));
451 window->draw_line(gc,round_to_int(tpx),0,round_to_int(tpx),fullheight);
453 //normal line/text color
454 gc->set_rgb_fg_color(Gdk::Color("#333333"));
456 //draw these lines... (always 5 between) maybe 6?
457 const int subdiv = 4;
460 //..., 3m, 2m, 1m30s, 1m, 30s, 20s, 10s, 5s, 3s, 2s, 1s, 0.5s
463 { 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 };
464 //{ 3600, 2700, 1800, 1200, 600, 300, 180, 120, 90, 60, 30, 20, 10, 5, 3, 2, 1, 0.5 };
465 const int ranges_size = sizeof(ranges)/sizeof(double);
467 double lowerrange = dtdp*75, upperrange = dtdp*150;
468 double midrange = (lowerrange + upperrange)/2;
470 //find most ideal scale
471 double scale = ranges[0];
473 double *val = binary_find(ranges, ranges+ranges_size, midrange);
474 double *after = val+1;
476 if(val >= ranges+ranges_size)
478 val = ranges+ranges_size-1;
481 if(after >= ranges+ranges_size)
483 after = ranges+ranges_size-1;
488 double diff = abs(scale - midrange), diff2 = abs(*after - midrange);
493 //synfig::info("Range found: (l %.2lf,u %.2lf - m %.2lf) -> %.2lf",lowerrange,upperrange,midrange,scale);
495 //search around this area to get the right one
498 //get first valid line and its position in pixel space
504 double subr = scale / subdiv;
506 //get its position inside...
507 time = ceil(start/subr)*subr - start;
510 //absolute time of the line to be drawn
514 double t = (time/scale - floor(time/scale))*subdiv; // the difference from the big mark in 0:1
515 //sdindex = (int)floor(t + 0.5); //get how far through the range it is...
516 sdindex = round_to_int(t); //get how far through the range it is...
518 //synfig::info("Extracted fr %.2lf -> %d", t, sdindex);
521 //synfig::info("Initial values: %.4lf t, %.1lf pixels, %d i", time,pixel,sdindex);
524 const int heightbig = 12;
525 const int heightsmall = 4;
527 int width = get_width();
528 while( pixel < width )
530 int xpx = round_to_int(pixel);
535 window->draw_line(gc,xpx,0,xpx,heightbig);
536 //round the time to nearest frame and draw the text
537 Time tm((double)time);
538 if(get_global_fps()) tm.round(get_global_fps());
539 Glib::ustring timecode(tm.get_string(get_global_fps(),App::get_time_format()));
541 //gc->set_rgb_fg_color(Gdk::Color("#000000"));
542 layout->set_text(timecode);
543 window->draw_layout(gc,xpx+2,heightsmall,layout);
546 window->draw_line(gc,xpx,0,xpx,heightsmall);
549 //increment time and position
550 pixel += subr / dtdp;
554 if(++sdindex >= subdiv) sdindex -= subdiv;
560 bool Widget_Timeslider::on_motion_notify_event(GdkEventMotion* event) //for dragging
562 if(!adj_timescale) return false;
564 Gdk::ModifierType mod = Gdk::ModifierType(event->state);
568 //NOTE: we might want to address the possibility of dragging with both buttons held down
570 if(mod & Gdk::BUTTON2_MASK)
573 //we need this for scrolling by dragging
574 double curx = event->x;
576 double start = adj_timescale->get_lower(),
577 end = adj_timescale->get_upper();
582 if(event->time-last_event_time<30)
585 last_event_time=event->time;
587 if(abs(lastx - curx) < 1 && end != start) return true;
588 //translate the window and correct it
590 //update our stuff so we are operating correctly
591 //invalidated = true;
594 //Note: Use inverse of mouse movement because of conceptual space relationship
595 double diff = lastx - curx; //curx - lastx;
597 //NOTE: This might be incorrect...
598 //fraction to move...
599 double dpx = (end - start)/get_width();
608 //But clamp to bounds if they exist...
609 //HACK - bounds should not be required for this slider
612 if(start < adj_bounds->get_lower())
614 diff = adj_bounds->get_lower() - start;
619 if(end > adj_bounds->get_upper())
621 diff = adj_bounds->get_upper() - end;
627 //synfig::info("Scrolling timerange to (%.4f,%.4f)",start,end);
629 adj_timescale->set_lower(start);
630 adj_timescale->set_upper(end);
632 adj_timescale->changed();
643 if(mod & Gdk::BUTTON1_MASK)
645 double curx = event->x;
647 //get time from drag...
648 double start = adj_timescale->get_lower(),
649 end = adj_timescale->get_upper(),
650 current = adj_timescale->get_value();
651 double t = start + curx*(end - start)/get_width();
653 //snap it to fps - if they exist...
656 t = floor(t*fps + 0.5)/fps;
662 adj_timescale->set_value(t);
664 //Fixed this to actually do what it's supposed to...
665 if(event->time-last_event_time>50)
667 adj_timescale->value_changed();
668 last_event_time = event->time;
678 bool Widget_Timeslider::on_scroll_event(GdkEventScroll* event) //for zooming
680 if(!adj_timescale) return false;
682 //Update so we are calculating based on current values
685 //figure out if we should center ourselves on the current time
688 //we want to zoom in on the time value if control is held down
689 if(Gdk::ModifierType(event->state) & Gdk::CONTROL_MASK)
694 switch(event->direction)
696 case GDK_SCROLL_UP: //zoom in
702 case GDK_SCROLL_DOWN: //zoom out
716 void Widget_Timeslider::zoom_in(bool centerontime)
718 if(!adj_timescale) return;
720 double start = adj_timescale->get_lower(),
721 end = adj_timescale->get_upper(),
722 current = adj_timescale->get_value();
724 double focuspoint = centerontime ? current : (start + end)/2;
726 //calculate new beginning and end
727 end = focuspoint + (end-focuspoint)*zoominfactor;
728 start = focuspoint + (start-focuspoint)*zoominfactor;
730 //synfig::info("Zooming in timerange to (%.4f,%.4f)",start,end);
733 if(start < adj_bounds->get_lower())
735 start = adj_bounds->get_lower();
738 if(end > adj_bounds->get_upper())
740 end = adj_bounds->get_upper();
745 adj_timescale->set_lower(start);
746 adj_timescale->set_upper(end);
748 //call changed function
749 adj_timescale->changed();
752 void Widget_Timeslider::zoom_out(bool centerontime)
754 if(!adj_timescale) return;
756 double start = adj_timescale->get_lower(),
757 end = adj_timescale->get_upper(),
758 current = adj_timescale->get_value();
760 double focuspoint = centerontime ? current : (start + end)/2;
762 //calculate new beginning and end
763 end = focuspoint + (end-focuspoint)*zoomoutfactor;
764 start = focuspoint + (start-focuspoint)*zoomoutfactor;
766 //synfig::info("Zooming out timerange to (%.4f,%.4f)",start,end);
769 if(start < adj_bounds->get_lower())
771 start = adj_bounds->get_lower();
774 if(end > adj_bounds->get_upper())
776 end = adj_bounds->get_upper();
781 adj_timescale->set_lower(start);
782 adj_timescale->set_upper(end);
784 //call changed function
785 adj_timescale->changed();
788 bool Widget_Timeslider::on_button_press_event(GdkEventButton *event) //for clicking
790 switch(event->button)
795 double start = adj_timescale->get_lower(),
796 end = adj_timescale->get_upper(),
797 current = adj_timescale->get_value();
799 double w = get_width();
800 double t = start + (end - start) * event->x / w;
802 t = floor(t*fps + 0.5)/fps;
804 /*synfig::info("Clicking time from %.3lf to %.3lf [(%.2lf,%.2lf) %.2lf / %.2lf ... %.2lf",
805 current, vt, start, end, event->x, w, fps);*/
813 adj_timescale->set_value(current);
814 adj_timescale->value_changed();
841 bool Widget_Timeslider::on_button_release_event(GdkEventButton *event) //end drag
843 switch(event->button)