Added support for no-state mode to TimelineWidget

This commit is contained in:
Joel Holdsworth 2009-03-27 16:56:37 +00:00
parent 7766a223eb
commit 390354ea8a
6 changed files with 345 additions and 286 deletions

View file

@ -245,7 +245,6 @@ TimelinePanel::update_sequence_chooser()
shared_ptr<timeline::TimelineState> state =
timelineWidget->get_state();
REQUIRE(state);
BOOST_FOREACH( shared_ptr< model::Sequence > sequence,
workspace.get_project().get_sequences() )
@ -255,10 +254,14 @@ TimelinePanel::update_sequence_chooser()
row[sequenceChooserColumns.sequenceColumn] = sequence;
row[sequenceChooserColumns.nameColumn] = sequence->get_name();
if(state->get_sequence() == sequence)
if(state && state->get_sequence() == sequence)
sequenceChooser.set_active(iter);
}
// If there's no active sequence, then unselect
if(!state)
sequenceChooser.set_active(-1);
// Unblock the event handler
sequenceChooserChangedConnection.unblock();
}
@ -287,12 +290,17 @@ TimelinePanel::update_zoom_buttons()
{
REQUIRE(timelineWidget);
timeline::TimelineViewWindow &viewWindow =
timelineWidget->get_state()->get_view_window();
zoomIn.set_sensitive(viewWindow.get_time_scale() != 1);
zoomOut.set_sensitive(viewWindow.get_time_scale()
!= TimelineWidget::MaxScale);
const shared_ptr<timeline::TimelineState> state =
timelineWidget->get_state();
if(state)
{
timeline::TimelineViewWindow &viewWindow =
state->get_view_window();
zoomIn.set_sensitive(viewWindow.get_time_scale() != 1);
zoomOut.set_sensitive(viewWindow.get_time_scale()
!= TimelineWidget::MaxScale);
}
}
void

View file

@ -51,11 +51,8 @@ TimelineWidget::TimelineWidget(
verticalAdjustment(0, 0, 0),
horizontalScroll(horizontalAdjustment),
verticalScroll(verticalAdjustment),
update_tracks_frozen(true)
update_tracks_frozen(false)
{
set_state(source_state);
thaw_update_tracks();
body = new TimelineBody(*this);
ENSURE(body != NULL);
headerContainer = new TimelineHeaderContainer(*this);
@ -78,6 +75,8 @@ TimelineWidget::TimelineWidget(
attach(horizontalScroll, 1, 2, 2, 3, FILL|EXPAND, SHRINK);
attach(verticalScroll, 2, 3, 1, 2, SHRINK, FILL|EXPAND);
set_state(source_state);
set_tool(timeline::Arrow);
}
@ -102,34 +101,32 @@ TimelineWidget::~TimelineWidget()
boost::shared_ptr<timeline::TimelineState>
TimelineWidget::get_state()
{
ENSURE(state);
return state;
}
void
TimelineWidget::set_state(shared_ptr<timeline::TimelineState> new_state)
{
if(!new_state)
return;
{
state = new_state;
REQUIRE(state);
// Clear the track tree
trackMap.clear();
// Hook up event handlers
state->get_view_window().changed_signal().connect( sigc::mem_fun(
this, &TimelineWidget::on_view_window_changed) );
state->get_sequence()->get_child_track_list().signal_changed().
connect(sigc::mem_fun(
this, &TimelineWidget::on_track_list_changed ) );
state->selection_changed_signal().connect(mem_fun(*this,
&TimelineWidget::on_body_changed));
state->playback_changed_signal().connect(mem_fun(*this,
&TimelineWidget::on_body_changed));
if(state)
{
// Hook up event handlers
state->get_view_window().changed_signal().connect( sigc::mem_fun(
this, &TimelineWidget::on_view_window_changed) );
state->get_sequence()->get_child_track_list().signal_changed().
connect(sigc::mem_fun(
this, &TimelineWidget::on_track_list_changed ) );
state->selection_changed_signal().connect(mem_fun(*this,
&TimelineWidget::on_body_changed));
state->playback_changed_signal().connect(mem_fun(*this,
&TimelineWidget::on_body_changed));
}
update_tracks();
// Send the state changed signal
@ -139,10 +136,11 @@ TimelineWidget::set_state(shared_ptr<timeline::TimelineState> new_state)
void
TimelineWidget::zoom_view(int zoom_size)
{
REQUIRE(state);
const int view_width = body->get_allocation().get_width();
state->get_view_window().zoom_view(view_width / 2, zoom_size);
if(state)
{
const int view_width = body->get_allocation().get_width();
state->get_view_window().zoom_view(view_width / 2, zoom_size);
}
}
ToolType
@ -195,9 +193,9 @@ TimelineWidget::state_changed_signal() const
void
TimelineWidget::on_scroll()
{
REQUIRE(state);
state->get_view_window().set_time_offset(
horizontalAdjustment.get_value());
if(state)
state->get_view_window().set_time_offset(
horizontalAdjustment.get_value());
}
void
@ -212,14 +210,16 @@ void
TimelineWidget::on_view_window_changed()
{
REQUIRE(ruler != NULL);
REQUIRE(state);
timeline::TimelineViewWindow &window = state->get_view_window();
const int view_width = body->get_allocation().get_width();
horizontalAdjustment.set_page_size(
window.get_time_scale() * view_width);
horizontalAdjustment.set_value(window.get_time_offset());
if(state)
{
timeline::TimelineViewWindow &window = state->get_view_window();
const int view_width = body->get_allocation().get_width();
horizontalAdjustment.set_page_size(
window.get_time_scale() * view_width);
horizontalAdjustment.set_value(window.get_time_offset());
}
}
void
@ -235,8 +235,9 @@ void
TimelineWidget::on_add_track_command()
{
// # TEST CODE
sequence()->get_child_track_list().push_back(
shared_ptr<model::Track>(new model::ClipTrack()));
if(sequence())
sequence()->get_child_track_list().push_back(
shared_ptr<model::Track>(new model::ClipTrack()));
}
/* ===== Internals ===== */
@ -247,15 +248,20 @@ TimelineWidget::update_tracks()
if(update_tracks_frozen)
return;
// Remove any tracks which are no longer present in the model
remove_orphaned_tracks();
// Create timeline tracks from all the model tracks
create_timeline_tracks();
// Update the layout helper
layoutHelper.clone_tree_from_sequence();
layoutHelper.update_layout();
if(state)
{
// Remove any tracks which are no longer present in the model
remove_orphaned_tracks();
// Create timeline tracks from all the model tracks
create_timeline_tracks();
// Update the layout helper
layoutHelper.clone_tree_from_sequence();
layoutHelper.update_layout();
}
else
trackMap.clear();
}
void
@ -273,6 +279,8 @@ TimelineWidget::thaw_update_tracks()
void
TimelineWidget::create_timeline_tracks()
{
REQUIRE(state);
BOOST_FOREACH(shared_ptr<model::Track> child,
sequence()->get_child_tracks())
create_timeline_tracks_from_branch(child);
@ -403,44 +411,45 @@ TimelineWidget::update_scroll()
REQUIRE(body != NULL);
const Allocation body_allocation = body->get_allocation();
REQUIRE(state);
timeline::TimelineViewWindow &window = state->get_view_window();
//----- Horizontal Scroll ------//
// TEST CODE
horizontalAdjustment.set_upper(1000 * GAVL_TIME_SCALE / 200);
horizontalAdjustment.set_lower(-1000 * GAVL_TIME_SCALE / 200);
// Set the page size
horizontalAdjustment.set_page_size(
window.get_time_scale() * body_allocation.get_width());
//----- Vertical Scroll -----//
// Calculate the vertical length that can be scrolled:
// the total height of all the tracks minus one screenful
int y_scroll_length = layoutHelper.get_total_height() -
body_allocation.get_height();
if(y_scroll_length < 0) y_scroll_length = 0;
// If by resizing we're now over-scrolled, scroll back to
// maximum distance
if((int)verticalAdjustment.get_value() > y_scroll_length)
verticalAdjustment.set_value(y_scroll_length);
verticalAdjustment.set_upper(y_scroll_length);
// Hide the scrollbar if no scrolling is possible
if(state)
{
timeline::TimelineViewWindow &window = state->get_view_window();
//----- Horizontal Scroll ------//
// TEST CODE
horizontalAdjustment.set_upper(1000 * GAVL_TIME_SCALE / 200);
horizontalAdjustment.set_lower(-1000 * GAVL_TIME_SCALE / 200);
// Set the page size
horizontalAdjustment.set_page_size(
window.get_time_scale() * body_allocation.get_width());
//----- Vertical Scroll -----//
// Calculate the vertical length that can be scrolled:
// the total height of all the tracks minus one screenful
int y_scroll_length = layoutHelper.get_total_height() -
body_allocation.get_height();
if(y_scroll_length < 0) y_scroll_length = 0;
// If by resizing we're now over-scrolled, scroll back to
// maximum distance
if((int)verticalAdjustment.get_value() > y_scroll_length)
verticalAdjustment.set_value(y_scroll_length);
verticalAdjustment.set_upper(y_scroll_length);
// Hide the scrollbar if no scrolling is possible
#if 0
// Having this code included seems to cause a layout loop as the
// window is shrunk
if(y_scroll_length <= 0 && verticalScroll.is_visible())
verticalScroll.hide();
else if(y_scroll_length > 0 && !verticalScroll.is_visible())
verticalScroll.show();
// Having this code included seems to cause a layout loop as the
// window is shrunk
if(y_scroll_length <= 0 && verticalScroll.is_visible())
verticalScroll.hide();
else if(y_scroll_length > 0 && !verticalScroll.is_visible())
verticalScroll.show();
#endif
}
}
int
@ -461,9 +470,11 @@ TimelineWidget::on_motion_in_body_notify_event(GdkEventMotion *event)
REQUIRE(event != NULL);
ruler->set_mouse_chevron_offset(event->x);
REQUIRE(state);
timeline::TimelineViewWindow &window = state->get_view_window();
mouseHoverSignal.emit(window.x_to_time(event->x));
if(state)
{
timeline::TimelineViewWindow &window = state->get_view_window();
mouseHoverSignal.emit(window.x_to_time(event->x));
}
return true;
}
@ -471,8 +482,9 @@ TimelineWidget::on_motion_in_body_notify_event(GdkEventMotion *event)
boost::shared_ptr<model::Sequence>
TimelineWidget::sequence() const
{
REQUIRE(state);
boost::shared_ptr<model::Sequence> sequence = state->get_sequence();
if(!state)
return shared_ptr<model::Sequence>();
shared_ptr<model::Sequence> sequence = state->get_sequence();
ENSURE(sequence);
return sequence;
}

View file

@ -137,16 +137,19 @@ TimelineBody::on_expose_event(GdkEventExpose* event)
// Makes sure the widget styles have been loaded
read_styles();
// Prepare to render via cairo
const Allocation allocation = get_allocation();
Cairo::RefPtr<Cairo::Context> cr = window->create_cairo_context();
if(timelineWidget.get_state())
{
// Prepare to render via cairo
const Allocation allocation = get_allocation();
Cairo::RefPtr<Cairo::Context> cr = window->create_cairo_context();
REQUIRE(cr);
//----- Draw the view -----//
draw_tracks(cr);
draw_selection(cr);
draw_playback_point(cr);
REQUIRE(cr);
//----- Draw the view -----//
draw_tracks(cr);
draw_selection(cr);
draw_playback_point(cr);
}
return true;
}
@ -156,45 +159,48 @@ TimelineBody::on_scroll_event (GdkEventScroll* event)
{
REQUIRE(event != NULL);
TimelineViewWindow &window = view_window();
const Allocation allocation = get_allocation();
if(event->state & GDK_CONTROL_MASK)
{
switch(event->direction)
if(timelineWidget.get_state())
{
case GDK_SCROLL_UP:
// User scrolled up. Zoom in
window.zoom_view(event->x, 1);
break;
TimelineViewWindow &window = view_window();
const Allocation allocation = get_allocation();
case GDK_SCROLL_DOWN:
// User scrolled down. Zoom out
window.zoom_view(event->x, -1);
break;
default:
break;
if(event->state & GDK_CONTROL_MASK)
{
switch(event->direction)
{
case GDK_SCROLL_UP:
// User scrolled up. Zoom in
window.zoom_view(event->x, 1);
break;
case GDK_SCROLL_DOWN:
// User scrolled down. Zoom out
window.zoom_view(event->x, -1);
break;
default:
break;
}
}
else
{
switch(event->direction)
{
case GDK_SCROLL_UP:
// User scrolled up. Shift 1/16th left
window.shift_view(allocation.get_width(), -16);
break;
case GDK_SCROLL_DOWN:
// User scrolled down. Shift 1/16th right
window.shift_view(allocation.get_width(), 16);
break;
default:
break;
}
}
}
}
else
{
switch(event->direction)
{
case GDK_SCROLL_UP:
// User scrolled up. Shift 1/16th left
window.shift_view(allocation.get_width(), -16);
break;
case GDK_SCROLL_DOWN:
// User scrolled down. Shift 1/16th right
window.shift_view(allocation.get_width(), 16);
break;
default:
break;
}
}
return true;
}
@ -239,36 +245,39 @@ TimelineBody::on_motion_notify_event(GdkEventMotion *event)
{
REQUIRE(event != NULL);
// Handle a middle-mouse drag if one is occuring
switch(dragType)
if(timelineWidget.get_state())
{
case Shift:
{
TimelineViewWindow &window = view_window();
const int64_t scale = window.get_time_scale();
gavl_time_t offset = beginShiftTimeOffset +
(int64_t)(mouseDownX - event->x) * scale;
window.set_time_offset(offset);
set_vertical_offset((int)(mouseDownY - event->y) +
beginShiftVerticalOffset);
break;
}
// Handle a middle-mouse drag if one is occuring
switch(dragType)
{
case Shift:
{
TimelineViewWindow &window = view_window();
const int64_t scale = window.get_time_scale();
gavl_time_t offset = beginShiftTimeOffset +
(int64_t)(mouseDownX - event->x) * scale;
window.set_time_offset(offset);
set_vertical_offset((int)(mouseDownY - event->y) +
beginShiftVerticalOffset);
break;
}
default:
break;
}
default:
break;
// Forward the event to the tool
tool->on_motion_notify_event(event);
// See if the track that we're hovering over has changed
shared_ptr<timeline::Track> new_hovering_track(
timelineWidget.layoutHelper.track_from_y(event->y));
if(timelineWidget.get_hovering_track() != new_hovering_track)
timelineWidget.set_hovering_track(new_hovering_track);
}
// Forward the event to the tool
tool->on_motion_notify_event(event);
// See if the track that we're hovering over has changed
shared_ptr<timeline::Track> new_hovering_track(
timelineWidget.layoutHelper.track_from_y(event->y));
if(timelineWidget.get_hovering_track() != new_hovering_track)
timelineWidget.set_hovering_track(new_hovering_track);
// false so that the message is passed up to the owner TimelineWidget
return false;
}
@ -276,9 +285,12 @@ TimelineBody::on_motion_notify_event(GdkEventMotion *event)
void
TimelineBody::on_state_changed()
{
// Connect up some events
view_window().changed_signal().connect(
sigc::mem_fun(this, &TimelineBody::on_update_view) );
if(timelineWidget.get_state())
{
// Connect up some events
view_window().changed_signal().connect(
sigc::mem_fun(this, &TimelineBody::on_update_view) );
}
// Redraw
queue_draw();
@ -299,6 +311,9 @@ TimelineBody::draw_tracks(Cairo::RefPtr<Cairo::Context> cr)
Cairo::Matrix view_matrix;
cr->get_matrix(view_matrix);
// If the tree's empty that means there's no sequence root
REQUIRE(!layout_tree.empty());
// Iterate drawing each track
TimelineLayoutHelper::TrackTree::pre_order_iterator iterator;
for(iterator = ++layout_tree.begin(); // ++ so we skip the sequence root
@ -408,35 +423,39 @@ TimelineBody::draw_playback_point(Cairo::RefPtr<Cairo::Context> cr)
// Prepare
shared_ptr<TimelineState> state = timelineWidget.get_state();
REQUIRE(state);
const Allocation allocation = get_allocation();
const gavl_time_t point = state->get_playback_point();
if(point == (gavl_time_t)GAVL_TIME_UNDEFINED)
return;
const int x = view_window().time_to_x(point);
// Set source
gdk_cairo_set_source_color(cr->cobj(), &playbackPointColour);
cr->set_line_width(1);
// Draw
if(x >= 0 && x < allocation.get_width())
if(state)
{
cr->move_to(x + 0.5, 0);
cr->line_to(x + 0.5, allocation.get_height());
cr->stroke();
const Allocation allocation = get_allocation();
const gavl_time_t point = state->get_playback_point();
if(point == (gavl_time_t)GAVL_TIME_UNDEFINED)
return;
const int x = view_window().time_to_x(point);
// Set source
gdk_cairo_set_source_color(cr->cobj(), &playbackPointColour);
cr->set_line_width(1);
// Draw
if(x >= 0 && x < allocation.get_width())
{
cr->move_to(x + 0.5, 0);
cr->line_to(x + 0.5, allocation.get_height());
cr->stroke();
}
}
}
void
TimelineBody::begin_shift_drag()
{
dragType = Shift;
beginShiftTimeOffset = view_window().get_time_offset();
beginShiftVerticalOffset = get_vertical_offset();
if(timelineWidget.get_state())
{
dragType = Shift;
beginShiftTimeOffset = view_window().get_time_offset();
beginShiftVerticalOffset = get_vertical_offset();
}
}
int

View file

@ -150,6 +150,8 @@ private:
/**
* A helper function to get the view window
* @remarks This function must not be called unless the TimlineWidget
* has a valid state.
**/
TimelineViewWindow& view_window() const;

View file

@ -241,16 +241,20 @@ TimelineHeaderContainer::on_size_request (Requisition* requisition)
const TimelineLayoutHelper::TrackTree &layout_tree =
timelineWidget.layoutHelper.get_layout_tree();
TimelineLayoutHelper::TrackTree::pre_order_iterator iterator;
for(iterator = ++layout_tree.begin(); // ++ so that we skip the sequence root
iterator != layout_tree.end();
iterator++)
// Send a size request to all the children
if(!layout_tree.empty())
{
Widget &widget =
lookup_timeline_track(*iterator)->get_header_widget();
if(widget.is_visible())
widget.size_request();
TimelineLayoutHelper::TrackTree::pre_order_iterator iterator;
for(iterator = ++layout_tree.begin(); // ++ so that we skip the sequence root
iterator != layout_tree.end();
iterator++)
{
Widget &widget =
lookup_timeline_track(*iterator)->get_header_widget();
if(widget.is_visible())
widget.size_request();
}
}
// Initialize the output parameter:
@ -348,45 +352,48 @@ TimelineHeaderContainer::layout_headers()
timelineWidget.layoutHelper;
const TimelineLayoutHelper::TrackTree &layout_tree =
layout_helper.get_layout_tree();
TimelineLayoutHelper::TrackTree::pre_order_iterator iterator;
for(iterator = ++layout_tree.begin(); // ++ so that we skip the sequence root
iterator != layout_tree.end();
iterator++)
{
const shared_ptr<timeline::Track> timeline_track =
lookup_timeline_track(*iterator);
Widget &widget = timeline_track->get_header_widget();
optional<Gdk::Rectangle> header_rect =
layout_helper.get_track_header_rect(timeline_track);
if(header_rect)
{
REQUIRE(header_rect->get_width() >= 0);
REQUIRE(header_rect->get_height() >= 0);
// Apply the allocation to the header
widget.size_allocate (*header_rect);
if(!widget.is_visible())
{
widget.show();
headers_shown = true;
}
}
else // No header rect, so the track must be hidden
if(widget.is_visible())
widget.hide();
}
// If headers have been shown while we're dragging, the dragging
// branch headers have to be brought back to the top again
if(headers_shown && layout_helper.is_dragging_track())
raise_recursive(layout_helper.get_dragging_track_iter());
// Repaint the background of our parenting
queue_draw();
if(!layout_tree.empty())
{
TimelineLayoutHelper::TrackTree::pre_order_iterator iterator;
for(iterator = ++layout_tree.begin(); // ++ so that we skip the sequence root
iterator != layout_tree.end();
iterator++)
{
const shared_ptr<timeline::Track> timeline_track =
lookup_timeline_track(*iterator);
Widget &widget = timeline_track->get_header_widget();
optional<Gdk::Rectangle> header_rect =
layout_helper.get_track_header_rect(timeline_track);
if(header_rect)
{
REQUIRE(header_rect->get_width() >= 0);
REQUIRE(header_rect->get_height() >= 0);
// Apply the allocation to the header
widget.size_allocate (*header_rect);
if(!widget.is_visible())
{
widget.show();
headers_shown = true;
}
}
else // No header rect, so the track must be hidden
if(widget.is_visible())
widget.hide();
}
// If headers have been shown while we're dragging, the dragging
// branch headers have to be brought back to the top again
if(headers_shown && layout_helper.is_dragging_track())
raise_recursive(layout_helper.get_dragging_track_iter());
// Repaint the background of our parenting
queue_draw();
}
}
shared_ptr<timeline::Track>

View file

@ -62,11 +62,14 @@ TimelineRuler::TimelineRuler(
playbackPeriodArrowSize(10),
playbackPeriodArrowStemSize(3),
timelineWidget(timeline_widget)
{
// Connect event handlers
view_window().changed_signal().connect(
sigc::mem_fun(this, &TimelineRuler::on_update_view) );
{
if(timelineWidget.get_state())
{
// Connect event handlers
view_window().changed_signal().connect(
sigc::mem_fun(this, &TimelineRuler::on_update_view) );
}
// Install style properties
register_styles();
}
@ -109,41 +112,44 @@ TimelineRuler::on_expose_event(GdkEventExpose* event)
Glib::RefPtr<Gdk::Window> window = get_window();
if(!window)
return false;
// Prepare to render via cairo
const Allocation allocation = get_allocation();
Cairo::RefPtr<Context> cr = window->create_cairo_context();
REQUIRE(cr);
// Draw the ruler
if(!rulerImage)
if(timelineWidget.get_state())
{
// We have no cached rendering - it must be redrawn
// but do we need ro allocate a new image?
if(!rulerImage ||
rulerImage->get_width() != allocation.get_width() ||
rulerImage->get_height() != allocation.get_height())
rulerImage = ImageSurface::create(FORMAT_RGB24,
allocation.get_width(), allocation.get_height());
// Prepare to render via cairo
const Allocation allocation = get_allocation();
Cairo::RefPtr<Context> cr = window->create_cairo_context();
REQUIRE(cr);
// Draw the ruler
if(!rulerImage)
{
// We have no cached rendering - it must be redrawn
// but do we need ro allocate a new image?
if(!rulerImage ||
rulerImage->get_width() != allocation.get_width() ||
rulerImage->get_height() != allocation.get_height())
rulerImage = ImageSurface::create(FORMAT_RGB24,
allocation.get_width(), allocation.get_height());
ENSURE(rulerImage);
Cairo::RefPtr<Context> image_cairo = Context::create(rulerImage);
ENSURE(image_cairo);
ENSURE(rulerImage);
Cairo::RefPtr<Context> image_cairo = Context::create(rulerImage);
ENSURE(image_cairo);
draw_ruler(image_cairo, allocation);
}
draw_ruler(image_cairo, allocation);
// Draw the cached ruler image
cr->set_source(rulerImage, 0, 0);
cr->paint();
// Draw the overlays
draw_mouse_chevron(cr, allocation);
draw_selection(cr, allocation);
draw_playback_period(cr, allocation);
draw_playback_point(cr, allocation);
}
// Draw the cached ruler image
cr->set_source(rulerImage, 0, 0);
cr->paint();
// Draw the overlays
draw_mouse_chevron(cr, allocation);
draw_selection(cr, allocation);
draw_playback_period(cr, allocation);
draw_playback_point(cr, allocation);
return true;
}
@ -153,11 +159,14 @@ TimelineRuler::on_button_press_event(GdkEventButton* event)
{
REQUIRE(event != NULL);
if(event->button == 1)
{
pinnedDragTime = view_window().x_to_time(event->x);
isDragging = true;
}
if(timelineWidget.get_state())
{
if(event->button == 1)
{
pinnedDragTime = view_window().x_to_time(event->x);
isDragging = true;
}
}
return true;
}
@ -212,13 +221,15 @@ void
TimelineRuler::set_leading_x(const int x)
{
shared_ptr<TimelineState> state = timelineWidget.get_state();
REQUIRE(state);
const gavl_time_t time = view_window().x_to_time(x);
if(time > pinnedDragTime)
state->set_playback_period(pinnedDragTime, time);
else
state->set_playback_period(time, pinnedDragTime);
if(state)
{
const gavl_time_t time = view_window().x_to_time(x);
if(time > pinnedDragTime)
state->set_playback_period(pinnedDragTime, time);
else
state->set_playback_period(time, pinnedDragTime);
}
}
void