/* TimelineRuler - Implementation of the time ruler widget Copyright (C) Lumiera.org 2008, Joel Holdsworth This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. * *****************************************************/ #include "gui/widgets/timeline/timeline-ruler.hpp" #include "gui/widgets/timeline-widget.hpp" #include "gui/window-manager.hpp" #include "gui/util/cairo-util.hpp" #include "lib/time/timevalue.hpp" #include "lib/time.h" #include using namespace Gtk; using namespace Cairo; using namespace gui; using namespace gui::widgets; using namespace gui::widgets::timeline; using boost::shared_ptr; ////////////////////TICKET #796 using gui::util::CairoUtil; using lib::time::Time; using lib::time::TimeVar; namespace gui { namespace widgets { namespace timeline { TimelineRuler::TimelineRuler (TimelineWidget &timeline_widget) : Glib::ObjectBase("TimelineRuler") , isDragging(false) , pinnedDragTime(Time::ZERO) , mouseChevronOffset(0) , annotationHorzMargin(0) , annotationVertMargin(0) , majorTickHeight(0) , minorLongTickHeight(0) , minorShortTickHeight(0) , minDivisionWidth(100) , mouseChevronSize(5) , selectionChevronSize(5) , playbackPointAlpha(0.5f) , playbackPointSize(12) , playbackPeriodArrowAlpha(0.5f) , playbackPeriodArrowSize(10) , playbackPeriodArrowStemSize(3) , timelineWidget(timeline_widget) { // Connect up some events timeline_widget.state_changed_signal().connect( sigc::mem_fun(this, &TimelineRuler::on_state_changed) ); // Install style properties register_styles(); } void TimelineRuler::set_mouse_chevron_offset(int offset) { mouseChevronOffset = offset; queue_draw(); } void TimelineRuler::on_update_view() { rulerImage.clear(); queue_draw(); } void TimelineRuler::on_realize() { Widget::on_realize(); // Set event notifications add_events(Gdk::POINTER_MOTION_MASK | Gdk::SCROLL_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK); // Load styles read_styles(); } bool TimelineRuler::on_expose_event(GdkEventExpose* event) { REQUIRE(event != NULL); // This is where we draw on the window Glib::RefPtr window = get_window(); if(!window) return false; if(timelineWidget.get_state()) { // Prepare to render via cairo const Allocation allocation = get_allocation(); Cairo::RefPtr 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 image_cairo = Context::create(rulerImage); ENSURE(image_cairo); 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); } return true; } bool TimelineRuler::on_button_press_event(GdkEventButton* event) { REQUIRE(event != NULL); if(timelineWidget.get_state()) { if(event->button == 1) { pinnedDragTime = view_window().x_to_time(event->x); isDragging = true; } } return true; } bool TimelineRuler::on_button_release_event(GdkEventButton* event) { REQUIRE(event != NULL); if(event->button == 1) { isDragging = false; timelineWidget.on_playback_period_drag_released(); } return true; } bool TimelineRuler::on_motion_notify_event(GdkEventMotion *event) { REQUIRE(event != NULL); set_mouse_chevron_offset(event->x); if(isDragging) set_leading_x(event->x); return true; } void TimelineRuler::on_size_request (Gtk::Requisition *requisition) { REQUIRE(requisition != NULL); // Initialize the output parameter *requisition = Gtk::Requisition(); requisition->width = 0; get_style_property("height", requisition->height); } void TimelineRuler::on_size_allocate(Gtk::Allocation& allocation) { Widget::on_size_allocate(allocation); rulerImage.clear(); // The widget has changed size - redraw } void TimelineRuler::on_state_changed() { if(timelineWidget.get_state()) { // Connect up some events view_window().changed_signal().connect( sigc::mem_fun(this, &TimelineRuler::on_update_view) ); } // Redraw on_update_view(); } void TimelineRuler::set_leading_x(const int x) { shared_ptr state = timelineWidget.get_state(); if(state) { TimeVar newStartPoint (view_window().x_to_time(x)); Offset selectionLength (pinnedDragTime, newStartPoint); if (newStartPoint > pinnedDragTime) newStartPoint=pinnedDragTime; // use the smaller one as selection start state->setPlaybackPeriod (Mutation::changeTime(newStartPoint) ); state->setPlaybackPeriod (Mutation::changeDuration(selectionLength)); //////////////////////////////////////////////////////TICKET #797 : this is cheesy. Should provide a single Mutation to change all at once ////////////////////TODO : code duplication with timeline-ibeam-tool 205 } } void TimelineRuler::draw_ruler(Cairo::RefPtr cr, const Gdk::Rectangle ruler_rect) { REQUIRE(cr); REQUIRE(ruler_rect.get_width() > 0); REQUIRE(ruler_rect.get_height() > 0); const TimelineViewWindow &window = view_window(); const gavl_time_t left_offset = _raw(window.get_time_offset()); const int64_t time_scale = window.get_time_scale(); // Preparation steps const int height = ruler_rect.get_height(); Glib::RefPtr pango_layout = create_pango_layout(""); Glib::RefPtr