/*! \file widget_timeslider.cpp
** \brief Time Slider Widget Implementation File
**
-** $Id: widget_timeslider.cpp,v 1.1.1.1 2005/01/07 03:34:37 darco Exp $
+** $Id$
**
** \legal
** Copyright (c) 2004 Adrian Bentley
+** Copyright (c) 2007 Chris Moore
**
** This package is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public License as
#include <cmath>
+#include "general.h"
+
#endif
/* === U S I N G =========================================================== */
static Gdk::Color
color_darken(Gdk::Color x, float amount)
{
- x.set_rgb_p(
- x.get_red_p()*amount,
- x.get_green_p()*amount,
- x.get_blue_p()*amount
- );
+ double red = x.get_red_p() * amount;
+ double green = x.get_green_p() * amount;
+ double blue = x.get_blue_p() * amount;
+
+ x.set_rgb_p( red > 1 ? 1 : red,
+ green > 1 ? 1 : green,
+ blue > 1 ? 1 : blue);
+
return x;
}
{
Glib::RefPtr<Gdk::GC> gc(Gdk::GC::create(window));
const Gdk::Color black("#000000");
-
+
if(selected)
gc->set_line_attributes(2,Gdk::LINE_SOLID,Gdk::CAP_BUTT,Gdk::JOIN_MITER);
else
Gdk::Color color;
std::vector<Gdk::Point> points;
-
+
/*- BEFORE ------------------------------------- */
color=get_interp_color(tp.get_before());
points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
window->draw_polygon(gc,true,points);
gc->set_rgb_fg_color(black);
- window->draw_lines(gc,points);
+ window->draw_lines(gc,points);
break;
case INTERPOLATION_CONSTANT:
points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
window->draw_polygon(gc,true,points);
gc->set_rgb_fg_color(black);
- window->draw_lines(gc,points);
+ window->draw_lines(gc,points);
break;
case INTERPOLATION_UNDEFINED: default:
points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
points.push_back(Gdk::Point(area.get_x()+area.get_width()/3,area.get_y()));
points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()/3));
- points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()*2/3));
+ points.push_back(Gdk::Point(area.get_x(),area.get_y()+area.get_height()-area.get_height()/3));
points.push_back(Gdk::Point(area.get_x()+area.get_width()/3,area.get_y()+area.get_height()));
points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
window->draw_polygon(gc,true,points);
gc->set_rgb_fg_color(black);
- window->draw_lines(gc,points);
+ window->draw_lines(gc,points);
break;
}
-
+
/*- AFTER -------------------------------------- */
color=get_interp_color(tp.get_after());
if(selected)color=color_darken(color,1.3f);
gc->set_rgb_fg_color(color);
-
switch(tp.get_after())
{
case INTERPOLATION_TCB:
points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
window->draw_polygon(gc,true,points);
gc->set_rgb_fg_color(black);
- window->draw_lines(gc,points);
+ window->draw_lines(gc,points);
break;
case INTERPOLATION_CONSTANT:
points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()));
points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()+area.get_height()/2));
- points.push_back(Gdk::Point(area.get_x()+area.get_width()*3/4,area.get_y()+area.get_height()/2));
- points.push_back(Gdk::Point(area.get_x()+area.get_width()*3/4,area.get_y()+area.get_height()));
+ points.push_back(Gdk::Point(area.get_x()+area.get_width()-area.get_width()/4,area.get_y()+area.get_height()/2));
+ points.push_back(Gdk::Point(area.get_x()+area.get_width()-area.get_width()/4,area.get_y()+area.get_height()));
points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
window->draw_polygon(gc,true,points);
gc->set_rgb_fg_color(black);
- window->draw_lines(gc,points);
+ window->draw_lines(gc,points);
break;
case INTERPOLATION_UNDEFINED: default:
points.clear();
points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()));
- points.push_back(Gdk::Point(area.get_x()+area.get_width()*2/3,area.get_y()));
+ points.push_back(Gdk::Point(area.get_x()+area.get_width()-area.get_width()/3,area.get_y()));
points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()+area.get_height()/3));
- points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()+area.get_height()*2/3));
- points.push_back(Gdk::Point(area.get_x()+area.get_width()*2/3,area.get_y()+area.get_height()));
+ points.push_back(Gdk::Point(area.get_x()+area.get_width(),area.get_y()+area.get_height()-area.get_height()/3));
+ points.push_back(Gdk::Point(area.get_x()+area.get_width()-area.get_width()/3,area.get_y()+area.get_height()));
points.push_back(Gdk::Point(area.get_x()+area.get_width()/2,area.get_y()+area.get_height()));
window->draw_polygon(gc,true,points);
gc->set_rgb_fg_color(black);
- window->draw_lines(gc,points);
+ window->draw_lines(gc,points);
break;
}
/* === M E T H O D S ======================================================= */
/* === E N T R Y P O I N T ================================================= */
-double defaultfps = 0;
+double defaultfps = 24;
const int fullheight = 20;
Widget_Timeslider::Widget_Timeslider()
adj_default(0,0,2,1/defaultfps,10/defaultfps),
adj_timescale(0),
//invalidated(false),
+last_event_time(0),
fps(defaultfps),
dragscroll(false)
{
set_size_request(-1,fullheight);
-
+
// click scroll zoom
- add_events( Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK
+ add_events( Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK
| Gdk::BUTTON_MOTION_MASK | Gdk::SCROLL_MASK );
-
+
set_time_adjustment(&adj_default);
//update_times();
}
//disconnect old connections
time_value_change.disconnect();
time_other_change.disconnect();
-
+
//connect update function to new adjustment
adj_timescale = x;
-
+
if(x)
{
time_value_change = x->signal_value_changed().connect(sigc::mem_fun(*this,&Widget_Timeslider::queue_draw));
- time_other_change = x->signal_changed().connect(sigc::mem_fun(*this,&Widget_Timeslider::queue_draw));
+ time_other_change = x->signal_changed().connect(sigc::mem_fun(*this,&Widget_Timeslider::queue_draw));
//invalidated = true;
//refresh();
}
if(fps != d)
{
fps = d;
-
+
//update everything since we need to redraw already
//invalidated = true;
//refresh();
}
}*/
-void Widget_Timeslider::refresh()
+void Widget_Timeslider::refresh()
{
}
-/*
+/*
{
if(invalidated)
{
double l = adj_timescale->get_lower(),
u = adj_timescale->get_upper(),
v = adj_timescale->get_value();
-
+
bool invalid = (l != start) || (u != end) || (v != current);
-
+
start = l;
end = u;
current = v;
-
+
if(invalid) queue_draw();
}
}*/
-bool Widget_Timeslider::redraw(bool doublebuffer)
+bool Widget_Timeslider::redraw(bool /*doublebuffer*/)
{
Glib::RefPtr<Gdk::Window> window = get_window();
-
+
if(!window) return false;
-
- Glib::RefPtr<Gdk::GC> gc = Gdk::GC::create(window);
+
+ Glib::RefPtr<Gdk::GC> gc = Gdk::GC::create(window);
if(!gc) return false;
-
+
//synfig::info("Drawing Timeslider");
//clear and update to current values
//invalidated = false;
- //update_times();
-
+ //update_times();
+
//draw grey rectangle
Gdk::Color c("#7f7f7f");
gc->set_rgb_fg_color(c);
gc->set_background(c);
-
+
//Get the data for the window and the params to draw it...
int w = get_width(), h = get_height();
-
- window->draw_rectangle(gc,true,0,0,w,h);
-
+
+ window->draw_rectangle(gc,true,0,0,w,h);
+
const double EPSILON = 1e-6;
if(!adj_timescale || w == 0) return true;
double start = adj_timescale->get_lower(),
end = adj_timescale->get_upper(),
current = adj_timescale->get_value();
-
+
if(end-start < EPSILON) return true;
-
+
//synfig::info("Drawing Lines");
-
+
//draw all the time stuff
double dtdp = (end - start)/get_width();
double dpdt = 1/dtdp;
-
+
//lines
-
+
//Draw the time line...
double tpx = (current-start)*dpdt;
gc->set_rgb_fg_color(Gdk::Color("#ffaf00"));
window->draw_line(gc,round_to_int(tpx),0,round_to_int(tpx),fullheight);
-
+
//normal line/text color
- gc->set_rgb_fg_color(Gdk::Color("#333333"));
-
- //draw these lines... (always 5 between) maybe 6?
- const int subdiv = 4;
-
- //1h 45 30 20 10 5
- //..., 3m, 2m, 1m30s, 1m, 30s, 20s, 10s, 5s, 3s, 2s, 1s, 0.5s
- //frames... (how???)
- double ranges[] =
- { 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 };
- //{ 3600, 2700, 1800, 1200, 600, 300, 180, 120, 90, 60, 30, 20, 10, 5, 3, 2, 1, 0.5 };
- const int ranges_size = sizeof(ranges)/sizeof(double);
-
- double lowerrange = dtdp*75, upperrange = dtdp*150;
- double midrange = (lowerrange + upperrange)/2;
-
- //find most ideal scale
- double scale = ranges[0];
- {
- double *val = binary_find(ranges, ranges+ranges_size, midrange);
- double *after = val+1;
-
- if(val >= ranges+ranges_size)
- {
- val = ranges+ranges_size-1;
- }
-
- if(after >= ranges+ranges_size)
+ gc->set_rgb_fg_color(Gdk::Color("#333333"));
+
+ int ifps = round_to_int(fps);
+ if (ifps < 1) ifps = 1;
+
+ std::vector<double> ranges;
+
+ unsigned int pos = 0;
+
+ // build a list of all the factors of the frame rate
+ for (int i = 1; i*i <= ifps; i++)
+ if ((ifps%i) == 0)
{
- after = ranges+ranges_size-1;
+ ranges.insert(ranges.begin()+pos, i/fps);
+ if (i*i != ifps)
+ ranges.insert(ranges.begin()+pos+1, ifps/i/fps);
+ pos++;
}
-
- scale = *val;
-
- double diff = abs(scale - midrange), diff2 = abs(*after - midrange);
- if(diff2 < diff)
- scale = *after;
+
+ // fill in any gaps where one factor is more than 2 times the previous
+ std::vector<double>::iterator iter, next;
+ pos = 0;
+ for (pos = 0; pos < ranges.size()-1; pos++)
+ {
+ iter = ranges.begin()+pos;
+ next = iter+1;
+ if (*iter*2 < *next)
+ ranges.insert(next, *iter*2);
}
-
- //synfig::info("Range found: (l %.2lf,u %.2lf - m %.2lf) -> %.2lf",lowerrange,upperrange,midrange,scale);
-
- //search around this area to get the right one
-
-
- //get first valid line and it's position in pixel space
+
+ double more_ranges[] = {
+ 2, 3, 5, 10, 20, 30, 60, 90, 120, 180,
+ 300, 600, 1200, 1800, 2700, 3600, 3600*2,
+ 3600*4, 3600*8, 3600*16, 3600*32, 3600*64,
+ 3600*128, 3600*256, 3600*512, 3600*1024 };
+
+ ranges.insert(ranges.end(), more_ranges, more_ranges + sizeof(more_ranges)/sizeof(double));
+
+ double lowerrange = dtdp*140, upperrange = dtdp*280;
+ double midrange = (lowerrange + upperrange)/2;
+
+ //find most ideal scale
+ double scale;
+ next = binary_find(ranges.begin(), ranges.end(), midrange);
+ iter = next++;
+
+ if (iter == ranges.end()) iter--;
+ if (next == ranges.end()) next--;
+
+ if (abs(*next - midrange) < abs(*iter - midrange))
+ iter = next;
+
+ scale = *iter;
+ if (iter != ranges.begin()) iter--;
+ if (iter != ranges.begin()) iter--;
+ if (iter != ranges.begin()) iter--;
+
+ // subdivide into this many tick marks (8 or less)
+ const int subdiv = round_to_int(scale / *iter);
+
+ //get first valid line and its position in pixel space
double time = 0;
double pixel = 0;
-
+
int sdindex = 0;
double subr = scale / subdiv;
-
- //get it's position inside...
+
+ //get its position inside...
time = ceil(start/subr)*subr - start;
pixel = time*dpdt;
-
+
//absolute time of the line to be drawn
time += start;
-
+
{ //inside the big'n
double t = (time/scale - floor(time/scale))*subdiv; // the difference from the big mark in 0:1
//sdindex = (int)floor(t + 0.5); //get how far through the range it is...
sdindex = round_to_int(t); //get how far through the range it is...
-
+
//synfig::info("Extracted fr %.2lf -> %d", t, sdindex);
}
-
+
//synfig::info("Initial values: %.4lf t, %.1lf pixels, %d i", time,pixel,sdindex);
-
+
//loop to draw
const int heightbig = 12;
const int heightsmall = 4;
-
+
int width = get_width();
while( pixel < width )
{
int xpx = round_to_int(pixel);
-
- //draw big
+
+ //draw big
if(sdindex == 0)
{
window->draw_line(gc,xpx,0,xpx,heightbig);
Time tm((double)time);
if(get_global_fps()) tm.round(get_global_fps());
Glib::ustring timecode(tm.get_string(get_global_fps(),App::get_time_format()));
-
+
//gc->set_rgb_fg_color(Gdk::Color("#000000"));
layout->set_text(timecode);
window->draw_layout(gc,xpx+2,heightsmall,layout);
}else
{
- window->draw_line(gc,xpx,0,xpx,heightsmall);
+ window->draw_line(gc,xpx,0,xpx,heightsmall);
}
-
+
//increment time and position
pixel += subr / dtdp;
time += subr;
-
+
//increment index
if(++sdindex >= subdiv) sdindex -= subdiv;
}
-
+
return true;
}
bool Widget_Timeslider::on_motion_notify_event(GdkEventMotion* event) //for dragging
-{
+{
if(!adj_timescale) return false;
-
+
Gdk::ModifierType mod = Gdk::ModifierType(event->state);
-
+
//scrolling...
-
+
//NOTE: we might want to address the possibility of dragging with both buttons held down
-
+
if(mod & Gdk::BUTTON2_MASK)
{
//we need this for scrolling by dragging
double curx = event->x;
-
+
double start = adj_timescale->get_lower(),
end = adj_timescale->get_upper();
-
-
+
if(dragscroll)
{
if(event->time-last_event_time<30)
if(abs(lastx - curx) < 1 && end != start) return true;
//translate the window and correct it
-
+
//update our stuff so we are operating correctly
//invalidated = true;
//update_times();
-
+
//Note: Use inverse of mouse movement because of conceptual space relationship
double diff = lastx - curx; //curx - lastx;
-
+
//NOTE: This might be incorrect...
//fraction to move...
double dpx = (end - start)/get_width();
lastx = curx;
-
+
diff *= dpx;
-
+
//Adjust...
start += diff;
end += diff;
-
+
//But clamp to bounds if they exist...
//HACK - bounds should not be required for this slider
if(adj_bounds)
start += diff;
end += diff;
}
-
+
if(end > adj_bounds->get_upper())
{
diff = adj_bounds->get_upper() - end;
start += diff;
end += diff;
}
- }
-
+ }
+
//synfig::info("Scrolling timerange to (%.4f,%.4f)",start,end);
-
+
adj_timescale->set_lower(start);
adj_timescale->set_upper(end);
lastx = curx;
//lasty = cury;
}
-
- return true;
+
+ return true;
}
-
+
if(mod & Gdk::BUTTON1_MASK)
{
double curx = event->x;
-
+
//get time from drag...
double start = adj_timescale->get_lower(),
end = adj_timescale->get_upper(),
current = adj_timescale->get_value();
double t = start + curx*(end - start)/get_width();
-
+
//snap it to fps - if they exist...
if(fps)
{
t = floor(t*fps + 0.5)/fps;
}
-
+
//set time if needed
if(current != t)
- {
+ {
adj_timescale->set_value(t);
-
+
//Fixed this to actually do what it's supposed to...
if(event->time-last_event_time>50)
{
last_event_time = event->time;
}
}
-
+
return true;
}
-
+
return false;
}
bool Widget_Timeslider::on_scroll_event(GdkEventScroll* event) //for zooming
{
if(!adj_timescale) return false;
-
+
//Update so we are calculating based on current values
- //update_times();
-
+ //update_times();
+
//figure out if we should center ourselves on the current time
bool center = false;
{
center = true;
}
-
+
switch(event->direction)
{
case GDK_SCROLL_UP: //zoom in
{
zoom_in(center);
-
+
return true;
}
case GDK_SCROLL_DOWN: //zoom out
{
zoom_out(center);
-
+
+ return true;
+ }
+
+ case GDK_SCROLL_RIGHT:
+ case GDK_SCROLL_LEFT:
+ {
+ double t = adj_timescale->get_value();
+ double start = adj_timescale->get_lower();
+ double end = adj_timescale->get_upper();
+ /*
+ FIXME: be more intelligent about how far to scroll
+ Perhaps it should be based on the tickmarks?
+ for e.g. 1/4 of a tick mark per scroll event
+ Obviously this would need post-rounding to 1/fps
+ */
+ double adj = 1.0/fps;
+
+ if( event->direction == GDK_SCROLL_RIGHT )
+ t += adj;
+ else
+ t -= adj;
+
+ if( t < start ){
+ adj_timescale->set_lower(t);
+ adj_timescale->set_upper(t+end-start);
+ } else if( t > end ){
+ adj_timescale->set_upper(t);
+ adj_timescale->set_lower(t-end+start);
+ }
+
+ if(adj_timescale)
+ {
+ adj_timescale->set_value(t);
+ adj_timescale->value_changed();
+ }
return true;
}
- default:
+ default:
{
return false;
}
void Widget_Timeslider::zoom_in(bool centerontime)
{
if(!adj_timescale) return;
-
+
double start = adj_timescale->get_lower(),
end = adj_timescale->get_upper(),
current = adj_timescale->get_value();
-
+
double focuspoint = centerontime ? current : (start + end)/2;
-
+
//calculate new beginning and end
end = focuspoint + (end-focuspoint)*zoominfactor;
start = focuspoint + (start-focuspoint)*zoominfactor;
-
+
//synfig::info("Zooming in timerange to (%.4f,%.4f)",start,end);
if(adj_bounds)
{
{
start = adj_bounds->get_lower();
}
-
+
if(end > adj_bounds->get_upper())
{
end = adj_bounds->get_upper();
}
}
-
+
//reset values
adj_timescale->set_lower(start);
adj_timescale->set_upper(end);
-
+
//call changed function
adj_timescale->changed();
}
void Widget_Timeslider::zoom_out(bool centerontime)
{
if(!adj_timescale) return;
-
+
double start = adj_timescale->get_lower(),
end = adj_timescale->get_upper(),
current = adj_timescale->get_value();
-
+
double focuspoint = centerontime ? current : (start + end)/2;
-
+
//calculate new beginning and end
end = focuspoint + (end-focuspoint)*zoomoutfactor;
start = focuspoint + (start-focuspoint)*zoomoutfactor;
-
+
//synfig::info("Zooming out timerange to (%.4f,%.4f)",start,end);
if(adj_bounds)
{
{
start = adj_bounds->get_lower();
}
-
+
if(end > adj_bounds->get_upper())
{
- end = adj_bounds->get_upper();
+ end = adj_bounds->get_upper();
}
}
-
+
//reset values
adj_timescale->set_lower(start);
adj_timescale->set_upper(end);
-
+
//call changed function
adj_timescale->changed();
}
double start = adj_timescale->get_lower(),
end = adj_timescale->get_upper(),
current = adj_timescale->get_value();
-
+
double w = get_width();
double t = start + (end - start) * event->x / w;
-
+
t = floor(t*fps + 0.5)/fps;
-
- /*synfig::info("Clicking time from %.3lf to %.3lf [(%.2lf,%.2lf) %.2lf / %.2lf ... %.2lf",
+
+ /*synfig::info("Clicking time from %.3lf to %.3lf [(%.2lf,%.2lf) %.2lf / %.2lf ... %.2lf",
current, vt, start, end, event->x, w, fps);*/
-
+
if(t != current)
- {
+ {
current = t;
-
+
if(adj_timescale)
{
adj_timescale->set_value(current);
adj_timescale->value_changed();
}
}
-
+
break;
}
-
+
//scroll click
case 2:
{
dragscroll = true;
lastx = event->x;
//lasty = event->y;
-
+
return true;
}
-
+
default:
{
break;
}
}
-
+
return false;
}
bool Widget_Timeslider::on_button_release_event(GdkEventButton *event) //end drag
-{
+{
switch(event->button)
- {
+ {
case 2:
{
//start dragging
dragscroll = false;
return true;
}
-
+
default:
{
break;
}
}
-
+
return false;
}