/* timeline-ruler.cpp - 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 #include "timeline-ruler.hpp" #include "../timeline-widget.hpp" #include "../../window-manager.hpp" extern "C" { #include "../../../lib/time.h" } using namespace Gtk; using namespace Cairo; using namespace std; using namespace boost; using namespace gui; using namespace gui::widgets; using namespace gui::widgets::timeline; using namespace lumiera; namespace gui { namespace widgets { namespace timeline { TimelineRuler::TimelineRuler( gui::widgets::TimelineWidget &timeline_widget) : Glib::ObjectBase("TimelineRuler"), isDragging(false), pinnedDragTime(0), 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) { const Time time = view_window().x_to_time(x); if(time > pinnedDragTime) state->set_playback_period(pinnedDragTime, time); else state->set_playback_period(time, pinnedDragTime); } } 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 = 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