1 /* === S Y N F I G ========================================================= */
2 /*! \file widget_timeslider.cpp
3 ** \brief Time Slider Widget Implementation File
5 ** $Id: widget_timeslider.cpp,v 1.1.1.1 2005/01/07 03:34:37 darco Exp $
8 ** Copyright (c) 2004 Adrian Bentley
10 ** This package is free software; you can redistribute it and/or
11 ** modify it under the terms of the GNU General Public License as
12 ** published by the Free Software Foundation; either version 2 of
13 ** the License, or (at your option) any later version.
15 ** This package is distributed in the hope that it will be useful,
16 ** but WITHOUT ANY WARRANTY; without even the implied warranty of
17 ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 ** General Public License for more details.
21 /* ========================================================================= */
23 /* === H E A D E R S ======================================================= */
32 #include "widget_timeslider.h"
40 /* === U S I N G =========================================================== */
44 using namespace synfig;
46 using studio::Widget_Timeslider;
48 /* === M A C R O S ========================================================= */
50 /* === G L O B A L S ======================================================= */
51 const double zoominfactor = 0.75;
52 const double zoomoutfactor = 1/zoominfactor;
54 /* === P R O C E D U R E S ================================================= */
56 Gdk::Color get_interp_color(synfig::Interpolation x)
60 case INTERPOLATION_TCB:
61 return Gdk::Color("#00B000");
65 case INTERPOLATION_LINEAR:
66 return Gdk::Color("#B0B000");
69 case INTERPOLATION_CONSTANT:
70 return Gdk::Color("#C70000");
73 case INTERPOLATION_HALT:
74 return Gdk::Color("#00b0b0");
77 case INTERPOLATION_MANUAL:
78 return Gdk::Color("#B000B0");
81 case INTERPOLATION_UNDEFINED: default:
82 return Gdk::Color("#808080");
88 color_darken(Gdk::Color x, float amount)
92 x.get_green_p()*amount,
99 studio::render_time_point_to_window(
100 const Glib::RefPtr<Gdk::Drawable>& window,
101 const Gdk::Rectangle& area,
102 const synfig::TimePoint &tp,
106 Glib::RefPtr<Gdk::GC> gc(Gdk::GC::create(window));
107 const Gdk::Color black("#000000");
110 gc->set_line_attributes(2,Gdk::LINE_SOLID,Gdk::CAP_BUTT,Gdk::JOIN_MITER);
112 gc->set_line_attributes(1,Gdk::LINE_SOLID,Gdk::CAP_BUTT,Gdk::JOIN_MITER);
115 std::vector<Gdk::Point> points;
117 /*- BEFORE ------------------------------------- */
119 color=get_interp_color(tp.get_before());
120 color=color_darken(color,1.0f);
121 if(selected)color=color_darken(color,1.3f);
122 gc->set_rgb_fg_color(color);
124 switch(tp.get_before())
126 case INTERPOLATION_TCB:
137 gc->set_rgb_fg_color(black);
150 case INTERPOLATION_HALT:
161 gc->set_rgb_fg_color(black);
174 case INTERPOLATION_LINEAR:
176 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
177 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()));
178 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
179 window->draw_polygon(gc,true,points);
180 gc->set_rgb_fg_color(black);
181 window->draw_lines(gc,points);
184 case INTERPOLATION_CONSTANT:
186 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
187 points.push_back(Gdk::Point(area.get_x()+area.get_width()/4,area.get_y()));
188 points.push_back(Gdk::Point(area.get_x()+area.get_width()/4,area.get_y()+area.get_height()/2));
189 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()/2));
190 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()));
191 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
192 window->draw_polygon(gc,true,points);
193 gc->set_rgb_fg_color(black);
194 window->draw_lines(gc,points);
197 case INTERPOLATION_UNDEFINED: default:
199 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
200 points.push_back(Gdk::Point(area.get_x()+area.get_width()/3,area.get_y()));
201 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()/3));
202 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()*2/3));
203 points.push_back(Gdk::Point(area.get_x()+area.get_width()/3,area.get_y()+area.get_height()));
204 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
205 window->draw_polygon(gc,true,points);
206 gc->set_rgb_fg_color(black);
207 window->draw_lines(gc,points);
211 /*- AFTER -------------------------------------- */
213 color=get_interp_color(tp.get_after());
214 color=color_darken(color,0.8f);
215 if(selected)color=color_darken(color,1.3f);
216 gc->set_rgb_fg_color(color);
219 switch(tp.get_after())
221 case INTERPOLATION_TCB:
232 gc->set_rgb_fg_color(black);
245 case INTERPOLATION_HALT:
250 area.get_y()-area.get_height(),
256 gc->set_rgb_fg_color(black);
261 area.get_y()-area.get_height(),
269 case INTERPOLATION_LINEAR:
271 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
272 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()));
273 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
274 window->draw_polygon(gc,true,points);
275 gc->set_rgb_fg_color(black);
276 window->draw_lines(gc,points);
279 case INTERPOLATION_CONSTANT:
281 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
282 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()));
283 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()+area.get_height()/2));
284 points.push_back(Gdk::Point(area.get_x()+area.get_width()*3/4,area.get_y()+area.get_height()/2));
285 points.push_back(Gdk::Point(area.get_x()+area.get_width()*3/4,area.get_y()+area.get_height()));
286 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
287 window->draw_polygon(gc,true,points);
288 gc->set_rgb_fg_color(black);
289 window->draw_lines(gc,points);
292 case INTERPOLATION_UNDEFINED: default:
294 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
295 points.push_back(Gdk::Point(area.get_x()+area.get_width()*2/3,area.get_y()));
296 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()+area.get_height()/3));
297 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()+area.get_height()*2/3));
298 points.push_back(Gdk::Point(area.get_x()+area.get_width()*2/3,area.get_y()+area.get_height()));
299 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
300 window->draw_polygon(gc,true,points);
301 gc->set_rgb_fg_color(black);
302 window->draw_lines(gc,points);
308 /* === M E T H O D S ======================================================= */
310 /* === E N T R Y P O I N T ================================================= */
311 double defaultfps = 0;
312 const int fullheight = 20;
314 Widget_Timeslider::Widget_Timeslider()
315 :layout(Pango::Layout::create(get_pango_context())),
316 adj_default(0,0,2,1/defaultfps,10/defaultfps),
318 //invalidated(false),
322 set_size_request(-1,fullheight);
325 add_events( Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK
326 | Gdk::BUTTON_MOTION_MASK | Gdk::SCROLL_MASK );
328 set_time_adjustment(&adj_default);
332 Widget_Timeslider::~Widget_Timeslider()
336 void Widget_Timeslider::set_time_adjustment(Gtk::Adjustment *x)
338 //disconnect old connections
339 time_value_change.disconnect();
340 time_other_change.disconnect();
342 //connect update function to new adjustment
347 time_value_change = x->signal_value_changed().connect(sigc::mem_fun(*this,&Widget_Timeslider::queue_draw));
348 time_other_change = x->signal_changed().connect(sigc::mem_fun(*this,&Widget_Timeslider::queue_draw));
349 //invalidated = true;
354 void Widget_Timeslider::set_global_fps(float d)
360 //update everything since we need to redraw already
361 //invalidated = true;
367 /*void Widget_Timeslider::update_times()
371 start = adj_timescale->get_lower();
372 end = adj_timescale->get_upper();
373 current = adj_timescale->get_value();
377 void Widget_Timeslider::refresh()
385 }else if(adj_timescale)
387 double l = adj_timescale->get_lower(),
388 u = adj_timescale->get_upper(),
389 v = adj_timescale->get_value();
391 bool invalid = (l != start) || (u != end) || (v != current);
397 if(invalid) queue_draw();
401 bool Widget_Timeslider::redraw(bool doublebuffer)
403 Glib::RefPtr<Gdk::Window> window = get_window();
405 if(!window) return false;
407 Glib::RefPtr<Gdk::GC> gc = Gdk::GC::create(window);
408 if(!gc) return false;
410 //synfig::info("Drawing Timeslider");
411 //clear and update to current values
412 //invalidated = false;
415 //draw grey rectangle
416 Gdk::Color c("#7f7f7f");
417 gc->set_rgb_fg_color(c);
418 gc->set_background(c);
420 //Get the data for the window and the params to draw it...
421 int w = get_width(), h = get_height();
423 window->draw_rectangle(gc,true,0,0,w,h);
425 const double EPSILON = 1e-6;
426 if(!adj_timescale || w == 0) return true;
428 //Get the time information since we now know it's valid
429 double start = adj_timescale->get_lower(),
430 end = adj_timescale->get_upper(),
431 current = adj_timescale->get_value();
433 if(end-start < EPSILON) return true;
435 //synfig::info("Drawing Lines");
437 //draw all the time stuff
438 double dtdp = (end - start)/get_width();
439 double dpdt = 1/dtdp;
443 //Draw the time line...
444 double tpx = (current-start)*dpdt;
445 gc->set_rgb_fg_color(Gdk::Color("#ffaf00"));
446 window->draw_line(gc,round_to_int(tpx),0,round_to_int(tpx),fullheight);
448 //normal line/text color
449 gc->set_rgb_fg_color(Gdk::Color("#333333"));
451 //draw these lines... (always 5 between) maybe 6?
452 const int subdiv = 4;
455 //..., 3m, 2m, 1m30s, 1m, 30s, 20s, 10s, 5s, 3s, 2s, 1s, 0.5s
458 { 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 };
459 //{ 3600, 2700, 1800, 1200, 600, 300, 180, 120, 90, 60, 30, 20, 10, 5, 3, 2, 1, 0.5 };
460 const int ranges_size = sizeof(ranges)/sizeof(double);
462 double lowerrange = dtdp*75, upperrange = dtdp*150;
463 double midrange = (lowerrange + upperrange)/2;
465 //find most ideal scale
466 double scale = ranges[0];
468 double *val = binary_find(ranges, ranges+ranges_size, midrange);
469 double *after = val+1;
471 if(val >= ranges+ranges_size)
473 val = ranges+ranges_size-1;
476 if(after >= ranges+ranges_size)
478 after = ranges+ranges_size-1;
483 double diff = abs(scale - midrange), diff2 = abs(*after - midrange);
488 //synfig::info("Range found: (l %.2lf,u %.2lf - m %.2lf) -> %.2lf",lowerrange,upperrange,midrange,scale);
490 //search around this area to get the right one
493 //get first valid line and it's position in pixel space
499 double subr = scale / subdiv;
501 //get it's position inside...
502 time = ceil(start/subr)*subr - start;
505 //absolute time of the line to be drawn
509 double t = (time/scale - floor(time/scale))*subdiv; // the difference from the big mark in 0:1
510 //sdindex = (int)floor(t + 0.5); //get how far through the range it is...
511 sdindex = round_to_int(t); //get how far through the range it is...
513 //synfig::info("Extracted fr %.2lf -> %d", t, sdindex);
516 //synfig::info("Initial values: %.4lf t, %.1lf pixels, %d i", time,pixel,sdindex);
519 const int heightbig = 12;
520 const int heightsmall = 4;
522 int width = get_width();
523 while( pixel < width )
525 int xpx = round_to_int(pixel);
530 window->draw_line(gc,xpx,0,xpx,heightbig);
531 //round the time to nearest frame and draw the text
532 Time tm((double)time);
533 if(get_global_fps()) tm.round(get_global_fps());
534 Glib::ustring timecode(tm.get_string(get_global_fps(),App::get_time_format()));
536 //gc->set_rgb_fg_color(Gdk::Color("#000000"));
537 layout->set_text(timecode);
538 window->draw_layout(gc,xpx+2,heightsmall,layout);
541 window->draw_line(gc,xpx,0,xpx,heightsmall);
544 //increment time and position
545 pixel += subr / dtdp;
549 if(++sdindex >= subdiv) sdindex -= subdiv;
555 bool Widget_Timeslider::on_motion_notify_event(GdkEventMotion* event) //for dragging
557 if(!adj_timescale) return false;
559 Gdk::ModifierType mod = Gdk::ModifierType(event->state);
563 //NOTE: we might want to address the possibility of dragging with both buttons held down
565 if(mod & Gdk::BUTTON2_MASK)
568 //we need this for scrolling by dragging
569 double curx = event->x;
571 double start = adj_timescale->get_lower(),
572 end = adj_timescale->get_upper();
577 if(event->time-last_event_time<30)
580 last_event_time=event->time;
582 if(abs(lastx - curx) < 1 && end != start) return true;
583 //translate the window and correct it
585 //update our stuff so we are operating correctly
586 //invalidated = true;
589 //Note: Use inverse of mouse movement because of conceptual space relationship
590 double diff = lastx - curx; //curx - lastx;
592 //NOTE: This might be incorrect...
593 //fraction to move...
594 double dpx = (end - start)/get_width();
603 //But clamp to bounds if they exist...
604 //HACK - bounds should not be required for this slider
607 if(start < adj_bounds->get_lower())
609 diff = adj_bounds->get_lower() - start;
614 if(end > adj_bounds->get_upper())
616 diff = adj_bounds->get_upper() - end;
622 //synfig::info("Scrolling timerange to (%.4f,%.4f)",start,end);
624 adj_timescale->set_lower(start);
625 adj_timescale->set_upper(end);
627 adj_timescale->changed();
638 if(mod & Gdk::BUTTON1_MASK)
640 double curx = event->x;
642 //get time from drag...
643 double start = adj_timescale->get_lower(),
644 end = adj_timescale->get_upper(),
645 current = adj_timescale->get_value();
646 double t = start + curx*(end - start)/get_width();
648 //snap it to fps - if they exist...
651 t = floor(t*fps + 0.5)/fps;
657 adj_timescale->set_value(t);
659 //Fixed this to actually do what it's supposed to...
660 if(event->time-last_event_time>50)
662 adj_timescale->value_changed();
663 last_event_time = event->time;
673 bool Widget_Timeslider::on_scroll_event(GdkEventScroll* event) //for zooming
675 if(!adj_timescale) return false;
677 //Update so we are calculating based on current values
680 //figure out if we should center ourselves on the current time
683 //we want to zoom in on the time value if control is held down
684 if(Gdk::ModifierType(event->state) & Gdk::CONTROL_MASK)
689 switch(event->direction)
691 case GDK_SCROLL_UP: //zoom in
697 case GDK_SCROLL_DOWN: //zoom out
711 void Widget_Timeslider::zoom_in(bool centerontime)
713 if(!adj_timescale) return;
715 double start = adj_timescale->get_lower(),
716 end = adj_timescale->get_upper(),
717 current = adj_timescale->get_value();
719 double focuspoint = centerontime ? current : (start + end)/2;
721 //calculate new beginning and end
722 end = focuspoint + (end-focuspoint)*zoominfactor;
723 start = focuspoint + (start-focuspoint)*zoominfactor;
725 //synfig::info("Zooming in timerange to (%.4f,%.4f)",start,end);
728 if(start < adj_bounds->get_lower())
730 start = adj_bounds->get_lower();
733 if(end > adj_bounds->get_upper())
735 end = adj_bounds->get_upper();
740 adj_timescale->set_lower(start);
741 adj_timescale->set_upper(end);
743 //call changed function
744 adj_timescale->changed();
747 void Widget_Timeslider::zoom_out(bool centerontime)
749 if(!adj_timescale) return;
751 double start = adj_timescale->get_lower(),
752 end = adj_timescale->get_upper(),
753 current = adj_timescale->get_value();
755 double focuspoint = centerontime ? current : (start + end)/2;
757 //calculate new beginning and end
758 end = focuspoint + (end-focuspoint)*zoomoutfactor;
759 start = focuspoint + (start-focuspoint)*zoomoutfactor;
761 //synfig::info("Zooming out timerange to (%.4f,%.4f)",start,end);
764 if(start < adj_bounds->get_lower())
766 start = adj_bounds->get_lower();
769 if(end > adj_bounds->get_upper())
771 end = adj_bounds->get_upper();
776 adj_timescale->set_lower(start);
777 adj_timescale->set_upper(end);
779 //call changed function
780 adj_timescale->changed();
783 bool Widget_Timeslider::on_button_press_event(GdkEventButton *event) //for clicking
785 switch(event->button)
790 double start = adj_timescale->get_lower(),
791 end = adj_timescale->get_upper(),
792 current = adj_timescale->get_value();
794 double w = get_width();
795 double t = start + (end - start) * event->x / w;
797 t = floor(t*fps + 0.5)/fps;
799 /*synfig::info("Clicking time from %.3lf to %.3lf [(%.2lf,%.2lf) %.2lf / %.2lf ... %.2lf",
800 current, vt, start, end, event->x, w, fps);*/
808 adj_timescale->set_value(current);
809 adj_timescale->value_changed();
836 bool Widget_Timeslider::on_button_release_event(GdkEventButton *event) //end drag
838 switch(event->button)