1 /* === S I N F 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 software and associated documentation
11 ** are CONFIDENTIAL and PROPRIETARY property of
12 ** the above-mentioned copyright holder.
14 ** You may not copy, print, publish, or in any
15 ** other way distribute this software without
16 ** a prior written agreement with
17 ** the copyright holder.
20 /* ========================================================================= */
22 /* === H E A D E R S ======================================================= */
31 #include "widget_timeslider.h"
39 /* === U S I N G =========================================================== */
43 using namespace sinfg;
45 using studio::Widget_Timeslider;
47 /* === M A C R O S ========================================================= */
49 /* === G L O B A L S ======================================================= */
50 const double zoominfactor = 0.75;
51 const double zoomoutfactor = 1/zoominfactor;
53 /* === P R O C E D U R E S ================================================= */
55 Gdk::Color get_interp_color(sinfg::Interpolation x)
59 case INTERPOLATION_TCB:
60 return Gdk::Color("#00B000");
64 case INTERPOLATION_LINEAR:
65 return Gdk::Color("#B0B000");
68 case INTERPOLATION_CONSTANT:
69 return Gdk::Color("#C70000");
72 case INTERPOLATION_HALT:
73 return Gdk::Color("#00b0b0");
76 case INTERPOLATION_MANUAL:
77 return Gdk::Color("#B000B0");
80 case INTERPOLATION_UNDEFINED: default:
81 return Gdk::Color("#808080");
87 color_darken(Gdk::Color x, float amount)
91 x.get_green_p()*amount,
98 studio::render_time_point_to_window(
99 const Glib::RefPtr<Gdk::Drawable>& window,
100 const Gdk::Rectangle& area,
101 const sinfg::TimePoint &tp,
105 Glib::RefPtr<Gdk::GC> gc(Gdk::GC::create(window));
106 const Gdk::Color black("#000000");
109 gc->set_line_attributes(2,Gdk::LINE_SOLID,Gdk::CAP_BUTT,Gdk::JOIN_MITER);
111 gc->set_line_attributes(1,Gdk::LINE_SOLID,Gdk::CAP_BUTT,Gdk::JOIN_MITER);
114 std::vector<Gdk::Point> points;
116 /*- BEFORE ------------------------------------- */
118 color=get_interp_color(tp.get_before());
119 color=color_darken(color,1.0f);
120 if(selected)color=color_darken(color,1.3f);
121 gc->set_rgb_fg_color(color);
123 switch(tp.get_before())
125 case INTERPOLATION_TCB:
136 gc->set_rgb_fg_color(black);
149 case INTERPOLATION_HALT:
160 gc->set_rgb_fg_color(black);
173 case INTERPOLATION_LINEAR:
175 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
176 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()));
177 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
178 window->draw_polygon(gc,true,points);
179 gc->set_rgb_fg_color(black);
180 window->draw_lines(gc,points);
183 case INTERPOLATION_CONSTANT:
185 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
186 points.push_back(Gdk::Point(area.get_x()+area.get_width()/4,area.get_y()));
187 points.push_back(Gdk::Point(area.get_x()+area.get_width()/4,area.get_y()+area.get_height()/2));
188 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()/2));
189 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()));
190 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
191 window->draw_polygon(gc,true,points);
192 gc->set_rgb_fg_color(black);
193 window->draw_lines(gc,points);
196 case INTERPOLATION_UNDEFINED: default:
198 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
199 points.push_back(Gdk::Point(area.get_x()+area.get_width()/3,area.get_y()));
200 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()/3));
201 points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()*2/3));
202 points.push_back(Gdk::Point(area.get_x()+area.get_width()/3,area.get_y()+area.get_height()));
203 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
204 window->draw_polygon(gc,true,points);
205 gc->set_rgb_fg_color(black);
206 window->draw_lines(gc,points);
210 /*- AFTER -------------------------------------- */
212 color=get_interp_color(tp.get_after());
213 color=color_darken(color,0.8f);
214 if(selected)color=color_darken(color,1.3f);
215 gc->set_rgb_fg_color(color);
218 switch(tp.get_after())
220 case INTERPOLATION_TCB:
231 gc->set_rgb_fg_color(black);
244 case INTERPOLATION_HALT:
249 area.get_y()-area.get_height(),
255 gc->set_rgb_fg_color(black);
260 area.get_y()-area.get_height(),
268 case INTERPOLATION_LINEAR:
270 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
271 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()));
272 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
273 window->draw_polygon(gc,true,points);
274 gc->set_rgb_fg_color(black);
275 window->draw_lines(gc,points);
278 case INTERPOLATION_CONSTANT:
280 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
281 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()));
282 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()+area.get_height()/2));
283 points.push_back(Gdk::Point(area.get_x()+area.get_width()*3/4,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()));
285 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
286 window->draw_polygon(gc,true,points);
287 gc->set_rgb_fg_color(black);
288 window->draw_lines(gc,points);
291 case INTERPOLATION_UNDEFINED: default:
293 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
294 points.push_back(Gdk::Point(area.get_x()+area.get_width()*2/3,area.get_y()));
295 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()+area.get_height()/3));
296 points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()+area.get_height()*2/3));
297 points.push_back(Gdk::Point(area.get_x()+area.get_width()*2/3,area.get_y()+area.get_height()));
298 points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
299 window->draw_polygon(gc,true,points);
300 gc->set_rgb_fg_color(black);
301 window->draw_lines(gc,points);
307 /* === M E T H O D S ======================================================= */
309 /* === E N T R Y P O I N T ================================================= */
310 double defaultfps = 0;
311 const int fullheight = 20;
313 Widget_Timeslider::Widget_Timeslider()
314 :layout(Pango::Layout::create(get_pango_context())),
315 adj_default(0,0,2,1/defaultfps,10/defaultfps),
317 //invalidated(false),
321 set_size_request(-1,fullheight);
324 add_events( Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK
325 | Gdk::BUTTON_MOTION_MASK | Gdk::SCROLL_MASK );
327 set_time_adjustment(&adj_default);
331 Widget_Timeslider::~Widget_Timeslider()
335 void Widget_Timeslider::set_time_adjustment(Gtk::Adjustment *x)
337 //disconnect old connections
338 time_value_change.disconnect();
339 time_other_change.disconnect();
341 //connect update function to new adjustment
346 time_value_change = x->signal_value_changed().connect(sigc::mem_fun(*this,&Widget_Timeslider::queue_draw));
347 time_other_change = x->signal_changed().connect(sigc::mem_fun(*this,&Widget_Timeslider::queue_draw));
348 //invalidated = true;
353 void Widget_Timeslider::set_global_fps(float d)
359 //update everything since we need to redraw already
360 //invalidated = true;
366 /*void Widget_Timeslider::update_times()
370 start = adj_timescale->get_lower();
371 end = adj_timescale->get_upper();
372 current = adj_timescale->get_value();
376 void Widget_Timeslider::refresh()
384 }else if(adj_timescale)
386 double l = adj_timescale->get_lower(),
387 u = adj_timescale->get_upper(),
388 v = adj_timescale->get_value();
390 bool invalid = (l != start) || (u != end) || (v != current);
396 if(invalid) queue_draw();
400 bool Widget_Timeslider::redraw(bool doublebuffer)
402 Glib::RefPtr<Gdk::Window> window = get_window();
404 if(!window) return false;
406 Glib::RefPtr<Gdk::GC> gc = Gdk::GC::create(window);
407 if(!gc) return false;
409 //sinfg::info("Drawing Timeslider");
410 //clear and update to current values
411 //invalidated = false;
414 //draw grey rectangle
415 Gdk::Color c("#7f7f7f");
416 gc->set_rgb_fg_color(c);
417 gc->set_background(c);
419 //Get the data for the window and the params to draw it...
420 int w = get_width(), h = get_height();
422 window->draw_rectangle(gc,true,0,0,w,h);
424 const double EPSILON = 1e-6;
425 if(!adj_timescale || w == 0) return true;
427 //Get the time information since we now know it's valid
428 double start = adj_timescale->get_lower(),
429 end = adj_timescale->get_upper(),
430 current = adj_timescale->get_value();
432 if(end-start < EPSILON) return true;
434 //sinfg::info("Drawing Lines");
436 //draw all the time stuff
437 double dtdp = (end - start)/get_width();
438 double dpdt = 1/dtdp;
442 //Draw the time line...
443 double tpx = (current-start)*dpdt;
444 gc->set_rgb_fg_color(Gdk::Color("#ffaf00"));
445 window->draw_line(gc,round_to_int(tpx),0,round_to_int(tpx),fullheight);
447 //normal line/text color
448 gc->set_rgb_fg_color(Gdk::Color("#333333"));
450 //draw these lines... (always 5 between) maybe 6?
451 const int subdiv = 4;
454 //..., 3m, 2m, 1m30s, 1m, 30s, 20s, 10s, 5s, 3s, 2s, 1s, 0.5s
457 { 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 };
458 //{ 3600, 2700, 1800, 1200, 600, 300, 180, 120, 90, 60, 30, 20, 10, 5, 3, 2, 1, 0.5 };
459 const int ranges_size = sizeof(ranges)/sizeof(double);
461 double lowerrange = dtdp*75, upperrange = dtdp*150;
462 double midrange = (lowerrange + upperrange)/2;
464 //find most ideal scale
465 double scale = ranges[0];
467 double *val = binary_find(ranges, ranges+ranges_size, midrange);
468 double *after = val+1;
470 if(val >= ranges+ranges_size)
472 val = ranges+ranges_size-1;
475 if(after >= ranges+ranges_size)
477 after = ranges+ranges_size-1;
482 double diff = abs(scale - midrange), diff2 = abs(*after - midrange);
487 //sinfg::info("Range found: (l %.2lf,u %.2lf - m %.2lf) -> %.2lf",lowerrange,upperrange,midrange,scale);
489 //search around this area to get the right one
492 //get first valid line and it's position in pixel space
498 double subr = scale / subdiv;
500 //get it's position inside...
501 time = ceil(start/subr)*subr - start;
504 //absolute time of the line to be drawn
508 double t = (time/scale - floor(time/scale))*subdiv; // the difference from the big mark in 0:1
509 //sdindex = (int)floor(t + 0.5); //get how far through the range it is...
510 sdindex = round_to_int(t); //get how far through the range it is...
512 //sinfg::info("Extracted fr %.2lf -> %d", t, sdindex);
515 //sinfg::info("Initial values: %.4lf t, %.1lf pixels, %d i", time,pixel,sdindex);
518 const int heightbig = 12;
519 const int heightsmall = 4;
521 int width = get_width();
522 while( pixel < width )
524 int xpx = round_to_int(pixel);
529 window->draw_line(gc,xpx,0,xpx,heightbig);
530 //round the time to nearest frame and draw the text
531 Time tm((double)time);
532 if(get_global_fps()) tm.round(get_global_fps());
533 Glib::ustring timecode(tm.get_string(get_global_fps(),App::get_time_format()));
535 //gc->set_rgb_fg_color(Gdk::Color("#000000"));
536 layout->set_text(timecode);
537 window->draw_layout(gc,xpx+2,heightsmall,layout);
540 window->draw_line(gc,xpx,0,xpx,heightsmall);
543 //increment time and position
544 pixel += subr / dtdp;
548 if(++sdindex >= subdiv) sdindex -= subdiv;
554 bool Widget_Timeslider::on_motion_notify_event(GdkEventMotion* event) //for dragging
556 if(!adj_timescale) return false;
558 Gdk::ModifierType mod = Gdk::ModifierType(event->state);
562 //NOTE: we might want to address the possibility of dragging with both buttons held down
564 if(mod & Gdk::BUTTON2_MASK)
567 //we need this for scrolling by dragging
568 double curx = event->x;
570 double start = adj_timescale->get_lower(),
571 end = adj_timescale->get_upper();
576 if(event->time-last_event_time<30)
579 last_event_time=event->time;
581 if(abs(lastx - curx) < 1 && end != start) return true;
582 //translate the window and correct it
584 //update our stuff so we are operating correctly
585 //invalidated = true;
588 //Note: Use inverse of mouse movement because of conceptual space relationship
589 double diff = lastx - curx; //curx - lastx;
591 //NOTE: This might be incorrect...
592 //fraction to move...
593 double dpx = (end - start)/get_width();
602 //But clamp to bounds if they exist...
603 //HACK - bounds should not be required for this slider
606 if(start < adj_bounds->get_lower())
608 diff = adj_bounds->get_lower() - start;
613 if(end > adj_bounds->get_upper())
615 diff = adj_bounds->get_upper() - end;
621 //sinfg::info("Scrolling timerange to (%.4f,%.4f)",start,end);
623 adj_timescale->set_lower(start);
624 adj_timescale->set_upper(end);
626 adj_timescale->changed();
637 if(mod & Gdk::BUTTON1_MASK)
639 double curx = event->x;
641 //get time from drag...
642 double start = adj_timescale->get_lower(),
643 end = adj_timescale->get_upper(),
644 current = adj_timescale->get_value();
645 double t = start + curx*(end - start)/get_width();
647 //snap it to fps - if they exist...
650 t = floor(t*fps + 0.5)/fps;
656 adj_timescale->set_value(t);
658 //Fixed this to actually do what it's supposed to...
659 if(event->time-last_event_time>50)
661 adj_timescale->value_changed();
662 last_event_time = event->time;
672 bool Widget_Timeslider::on_scroll_event(GdkEventScroll* event) //for zooming
674 if(!adj_timescale) return false;
676 //Update so we are calculating based on current values
679 //figure out if we should center ourselves on the current time
682 //we want to zoom in on the time value if control is held down
683 if(Gdk::ModifierType(event->state) & Gdk::CONTROL_MASK)
688 switch(event->direction)
690 case GDK_SCROLL_UP: //zoom in
696 case GDK_SCROLL_DOWN: //zoom out
710 void Widget_Timeslider::zoom_in(bool centerontime)
712 if(!adj_timescale) return;
714 double start = adj_timescale->get_lower(),
715 end = adj_timescale->get_upper(),
716 current = adj_timescale->get_value();
718 double focuspoint = centerontime ? current : (start + end)/2;
720 //calculate new beginning and end
721 end = focuspoint + (end-focuspoint)*zoominfactor;
722 start = focuspoint + (start-focuspoint)*zoominfactor;
724 //sinfg::info("Zooming in timerange to (%.4f,%.4f)",start,end);
727 if(start < adj_bounds->get_lower())
729 start = adj_bounds->get_lower();
732 if(end > adj_bounds->get_upper())
734 end = adj_bounds->get_upper();
739 adj_timescale->set_lower(start);
740 adj_timescale->set_upper(end);
742 //call changed function
743 adj_timescale->changed();
746 void Widget_Timeslider::zoom_out(bool centerontime)
748 if(!adj_timescale) return;
750 double start = adj_timescale->get_lower(),
751 end = adj_timescale->get_upper(),
752 current = adj_timescale->get_value();
754 double focuspoint = centerontime ? current : (start + end)/2;
756 //calculate new beginning and end
757 end = focuspoint + (end-focuspoint)*zoomoutfactor;
758 start = focuspoint + (start-focuspoint)*zoomoutfactor;
760 //sinfg::info("Zooming out timerange to (%.4f,%.4f)",start,end);
763 if(start < adj_bounds->get_lower())
765 start = adj_bounds->get_lower();
768 if(end > adj_bounds->get_upper())
770 end = adj_bounds->get_upper();
775 adj_timescale->set_lower(start);
776 adj_timescale->set_upper(end);
778 //call changed function
779 adj_timescale->changed();
782 bool Widget_Timeslider::on_button_press_event(GdkEventButton *event) //for clicking
784 switch(event->button)
789 double start = adj_timescale->get_lower(),
790 end = adj_timescale->get_upper(),
791 current = adj_timescale->get_value();
793 double w = get_width();
794 double t = start + (end - start) * event->x / w;
796 t = floor(t*fps + 0.5)/fps;
798 /*sinfg::info("Clicking time from %.3lf to %.3lf [(%.2lf,%.2lf) %.2lf / %.2lf ... %.2lf",
799 current, vt, start, end, event->x, w, fps);*/
807 adj_timescale->set_value(current);
808 adj_timescale->value_changed();
835 bool Widget_Timeslider::on_button_release_event(GdkEventButton *event) //end drag
837 switch(event->button)