From 49f87b28bbb0ae144a31e2eb644af5bd16f8517c Mon Sep 17 00:00:00 2001 From: Joel Holdsworth Date: Sat, 30 Aug 2008 22:34:26 +0100 Subject: [PATCH] Added support for audacity style playback period, and added some documentation --- src/gui/lumiera_ui.rc | 11 +- src/gui/widgets/timeline-widget.cpp | 48 +++++- src/gui/widgets/timeline-widget.hpp | 26 ++- src/gui/widgets/timeline/timeline-body.cpp | 15 +- .../widgets/timeline/timeline-ibeam-tool.cpp | 9 +- src/gui/widgets/timeline/timeline-ruler.cpp | 151 +++++++++++++++++- src/gui/widgets/timeline/timeline-ruler.hpp | 41 ++++- 7 files changed, 278 insertions(+), 23 deletions(-) diff --git a/src/gui/lumiera_ui.rc b/src/gui/lumiera_ui.rc index b68407039..02f6d90fc 100644 --- a/src/gui/lumiera_ui.rc +++ b/src/gui/lumiera_ui.rc @@ -140,16 +140,15 @@ style "timeline_ruler" = "default_base" gtkmm__CustomObject_TimelineRuler::min_division_width = 100 gtkmm__CustomObject_TimelineRuler::mouse_chevron_size = 5 gtkmm__CustomObject_TimelineRuler::selection_chevron_size = 5 + gtkmm__CustomObject_TimelineRuler::playback_arrow_colour = "#2D2D90" + gtkmm__CustomObject_TimelineRuler::playback_arrow_alpha = 0.5 + gtkmm__CustomObject_TimelineRuler::playback_arrow_size = 10 + gtkmm__CustomObject_TimelineRuler::playback_arrow_stem_size = 3 } style "timeline_header_base" = "default_base" { -# fg[NORMAL] = { 0.77, 0.77, 0.72 } -# bg[NORMAL] = { 0.18, 0.19, 0.22 } -# bg[ACTIVE] = { 0.20, 0.20, 0.20 } -# bg[PRELIGHT] = { 0.20, 0.20, 0.20 } -# bg[INSENSITIVE] = { 0.20, 0.20, 0.20 } -# bg[SELECTED] = { 0.20, 0.20, 0.20 } + } class "gtkmm__CustomObject_TimelineBody" style:highest "timeline_body" diff --git a/src/gui/widgets/timeline-widget.cpp b/src/gui/widgets/timeline-widget.cpp index e1931ab5b..9345efe59 100644 --- a/src/gui/widgets/timeline-widget.cpp +++ b/src/gui/widgets/timeline-widget.cpp @@ -46,6 +46,8 @@ TimelineWidget::TimelineWidget() : verticalAdjustment(0, 0, 0), selectionStart(0), selectionEnd(0), + playbackPeriodStart(0), + playbackPeriodEnd(0), horizontalScroll(horizontalAdjustment), verticalScroll(verticalAdjustment) { @@ -183,8 +185,12 @@ TimelineWidget::get_selection_end() const } void -TimelineWidget::set_selection(gavl_time_t start, gavl_time_t end) +TimelineWidget::set_selection(gavl_time_t start, gavl_time_t end, + bool reset_playback_period) { + REQUIRE(ruler != NULL); + REQUIRE(body != NULL); + if(start < end) { selectionStart = start; @@ -196,6 +202,46 @@ TimelineWidget::set_selection(gavl_time_t start, gavl_time_t end) selectionStart = end; selectionEnd = start; } + + if(reset_playback_period) + { + playbackPeriodStart = selectionStart; + playbackPeriodEnd = selectionEnd; + } + + ruler->queue_draw(); + body->queue_draw(); +} + +gavl_time_t +TimelineWidget::get_playback_period_start() const +{ + return playbackPeriodStart; +} + +gavl_time_t +TimelineWidget::get_playback_period_end() const +{ + return playbackPeriodEnd; +} + +void +TimelineWidget::set_playback_period(gavl_time_t start, gavl_time_t end) +{ + REQUIRE(ruler != NULL); + REQUIRE(body != NULL); + + if(start < end) + { + playbackPeriodStart = start; + playbackPeriodEnd = end; + } + else + { + // The period is back-to-front, flip it round + playbackPeriodStart = end; + playbackPeriodEnd = start; + } ruler->queue_draw(); body->queue_draw(); diff --git a/src/gui/widgets/timeline-widget.hpp b/src/gui/widgets/timeline-widget.hpp index aba15cc04..004a01ca6 100644 --- a/src/gui/widgets/timeline-widget.hpp +++ b/src/gui/widgets/timeline-widget.hpp @@ -120,8 +120,30 @@ public: /** * Sets the period of the selection. + * @param start The start time. + * @param end The end time. + * @param reset_playback_period Specifies whether to set the playback + * period to the same as this new selection. */ - void set_selection(gavl_time_t start, gavl_time_t end); + void set_selection(gavl_time_t start, gavl_time_t end, + bool reset_playback_period = true); + + /** + * Gets the time at which the playback period begins. + */ + gavl_time_t get_playback_period_start() const; + + /** + * Gets the time at which the playback period ends. + */ + gavl_time_t get_playback_period_end() const; + + /** + * Sets the playback period. + * @param start The start time. + * @param end The end time. + */ + void set_playback_period(gavl_time_t start, gavl_time_t end); /** * Gets the type of the tool currently active. @@ -169,6 +191,8 @@ protected: // Selection State gavl_time_t selectionStart; gavl_time_t selectionEnd; + gavl_time_t playbackPeriodStart; + gavl_time_t playbackPeriodEnd; int totalHeight; diff --git a/src/gui/widgets/timeline/timeline-body.cpp b/src/gui/widgets/timeline/timeline-body.cpp index 64f545190..231193105 100644 --- a/src/gui/widgets/timeline/timeline-body.cpp +++ b/src/gui/widgets/timeline/timeline-body.cpp @@ -349,23 +349,18 @@ TimelineBody::register_styles() const { GtkWidgetClass *klass = GTK_WIDGET_CLASS(G_OBJECT_GET_CLASS(gobj())); - gtk_widget_class_install_style_property( - GTK_WIDGET_CLASS(G_OBJECT_GET_CLASS(gobj())), - g_param_spec_boxed("background", - "Track Background", + gtk_widget_class_install_style_property(klass, + g_param_spec_boxed("background", "Track Background", "The background colour of timeline tracks", GDK_TYPE_COLOR, G_PARAM_READABLE)); - gtk_widget_class_install_style_property( - GTK_WIDGET_CLASS(G_OBJECT_GET_CLASS(gobj())), - g_param_spec_boxed("selection", - "End lines of a selection", + gtk_widget_class_install_style_property(klass, + g_param_spec_boxed("selection", "End lines of a selection", "The colour of selection limit lines", GDK_TYPE_COLOR, G_PARAM_READABLE)); gtk_widget_class_install_style_property(klass, - g_param_spec_float("selection_alpha", - "Selection Alpha", + g_param_spec_float("selection_alpha", "Selection Alpha", "The transparency of the selection marque.", 0, 1.0, 0.5, G_PARAM_READABLE)); } diff --git a/src/gui/widgets/timeline/timeline-ibeam-tool.cpp b/src/gui/widgets/timeline/timeline-ibeam-tool.cpp index 92a4376f8..fe3ce5d23 100644 --- a/src/gui/widgets/timeline/timeline-ibeam-tool.cpp +++ b/src/gui/widgets/timeline/timeline-ibeam-tool.cpp @@ -90,6 +90,7 @@ IBeamTool::on_button_press_event(GdkEventButton* event) Tool::on_button_press_event(event); TimelineWidget *timeline_widget = get_timeline_widget(); + REQUIRE(timeline_widget != NULL); if(event->button == 1) { @@ -181,12 +182,16 @@ void IBeamTool::set_leading_x(const int x) { TimelineWidget *timeline_widget = get_timeline_widget(); + REQUIRE(timeline_widget != NULL); + const bool set_playback_period = dragType == Selection; const gavl_time_t time = timeline_widget->x_to_time(x); if(time > pinnedDragTime) - timeline_widget->set_selection(pinnedDragTime, time); + timeline_widget->set_selection( + pinnedDragTime, time, set_playback_period); else - timeline_widget->set_selection(time, pinnedDragTime); + timeline_widget->set_selection( + time, pinnedDragTime, set_playback_period); } void diff --git a/src/gui/widgets/timeline/timeline-ruler.cpp b/src/gui/widgets/timeline/timeline-ruler.cpp index 3f7d808b9..17a9a1445 100644 --- a/src/gui/widgets/timeline/timeline-ruler.cpp +++ b/src/gui/widgets/timeline/timeline-ruler.cpp @@ -45,6 +45,8 @@ namespace timeline { TimelineRuler::TimelineRuler( lumiera::gui::widgets::TimelineWidget *timeline_widget) : Glib::ObjectBase("TimelineRuler"), + isDragging(false), + pinnedDragTime(0), mouseChevronOffset(0), annotationHorzMargin(0), annotationVertMargin(0), @@ -54,6 +56,9 @@ TimelineRuler::TimelineRuler( minDivisionWidth(100), mouseChevronSize(5), selectionChevronSize(5), + playbackArrowAlpha(0.5f), + playbackArrowSize(10), + playbackArrowStemSize(3), timelineWidget(timeline_widget) { REQUIRE(timelineWidget != NULL); @@ -82,7 +87,10 @@ TimelineRuler::on_realize() Widget::on_realize(); // Set event notifications - add_events(Gdk::POINTER_MOTION_MASK | Gdk::SCROLL_MASK); + add_events(Gdk::POINTER_MOTION_MASK | + Gdk::SCROLL_MASK | + Gdk::BUTTON_PRESS_MASK | + Gdk::BUTTON_RELEASE_MASK); // Load styles read_styles(); @@ -130,16 +138,47 @@ TimelineRuler::on_expose_event(GdkEventExpose* event) // Draw the overlays draw_mouse_chevron(cr, allocation); draw_selection(cr, allocation); + draw_playback_period(cr, allocation); return true; } + +bool +TimelineRuler::on_button_press_event(GdkEventButton* event) +{ + REQUIRE(event != NULL); + + if(event->button == 1) + { + pinnedDragTime = timelineWidget->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; + + 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; } @@ -162,6 +201,18 @@ TimelineRuler::on_size_allocate(Gtk::Allocation& allocation) rulerImage.clear(); // The widget has changed size - redraw } +void +TimelineRuler::set_leading_x(const int x) +{ + REQUIRE(timelineWidget != NULL); + + const gavl_time_t time = timelineWidget->x_to_time(x); + if(time > pinnedDragTime) + timelineWidget->set_playback_period(pinnedDragTime, time); + else + timelineWidget->set_playback_period(time, pinnedDragTime); +} + void TimelineRuler::draw_ruler(Cairo::RefPtr cr, const Gdk::Rectangle ruler_rect) @@ -299,6 +350,74 @@ TimelineRuler::draw_selection(Cairo::RefPtr cr, } } +void +TimelineRuler::draw_playback_period(Cairo::RefPtr cr, + const Gdk::Rectangle ruler_rect) +{ + REQUIRE(cr); + REQUIRE(ruler_rect.get_width() > 0); + REQUIRE(ruler_rect.get_height() > 0); + REQUIRE(timelineWidget != NULL); + + // Calculate coordinates + const float halfSize = playbackArrowSize / 2; + + const float a = timelineWidget->time_to_x( + timelineWidget->playbackPeriodStart) + 1 + 0.5f; + const float b = a + halfSize; + const float d = timelineWidget->time_to_x( + timelineWidget->playbackPeriodEnd) + 0.5f; + const float c = d - halfSize; + + const float e = ruler_rect.get_height() - playbackArrowSize - 0.5f; + const float f = e + (playbackArrowSize - playbackArrowStemSize) / 2; + const float g = ruler_rect.get_height() - playbackArrowSize / 2 + - 0.5f; + const float i = ruler_rect.get_height() - 0.5f; + const float h = i - (playbackArrowSize - playbackArrowStemSize) / 2; + + // Contruct the path + if(d - a >= playbackArrowSize) + { + // Draw an arrow: <===> + cr->move_to(a, g); + cr->line_to(b, e); + cr->line_to(b, f); + cr->line_to(c, f); + cr->line_to(c, e); + cr->line_to(d, g); + cr->line_to(c, i); + cr->line_to(c, h); + cr->line_to(b, h); + cr->line_to(b, i); + cr->line_to(a, g); + } + else + { + // The space is too narrow for an arrow, so draw calipers: > < + cr->move_to(a, g); + cr->rel_line_to(-halfSize, -halfSize); + cr->rel_line_to(0, playbackArrowSize); + + cr->move_to(d, g); + cr->rel_line_to(halfSize, -halfSize); + cr->rel_line_to(0, playbackArrowSize); + } + + // Fill + cr->set_source_rgba( + (float)playbackArrowColour.red / 0xFFFF, + (float)playbackArrowColour.green / 0xFFFF, + (float)playbackArrowColour.blue / 0xFFFF, + playbackArrowAlpha); + cr->fill_preserve(); + + // Stroke + gdk_cairo_set_source_color(cr->cobj(), &playbackArrowColour); + cr->set_line_width(1); + cr->stroke(); +} + gavl_time_t TimelineRuler::calculate_major_spacing() const { @@ -402,6 +521,29 @@ TimelineRuler::register_styles() const "Selection Chevron Size", "The height of the selection chevrons in pixels.", 0, G_MAXINT, 5, G_PARAM_READABLE)); + + gtk_widget_class_install_style_property(klass, + g_param_spec_boxed("playback_arrow_colour", + "End lines of a selection", + "The colour of selection limit lines", + GDK_TYPE_COLOR, G_PARAM_READABLE)); + + gtk_widget_class_install_style_property(klass, + g_param_spec_float("playback_arrow_alpha", "Playback Arrow Alpha", + "The transparency of the playback period arrow.", + 0, 1.0, 0.5, G_PARAM_READABLE)); + + gtk_widget_class_install_style_property(klass, + g_param_spec_int("playback_arrow_size", + "Playback Arrow Head Size", + "The height of the playback arrow head in pixels.", + 0, G_MAXINT, 10, G_PARAM_READABLE)); + + gtk_widget_class_install_style_property(klass, + g_param_spec_int("playback_arrow_stem_size", + "Playback Arrow Stem Size", + "The height of the playback arrow head in pixels.", + 0, G_MAXINT, 3, G_PARAM_READABLE)); } void @@ -415,6 +557,13 @@ TimelineRuler::read_styles() get_style_property("min_division_width", minDivisionWidth); get_style_property("mouse_chevron_size", mouseChevronSize); get_style_property("selection_chevron_size", selectionChevronSize); + + playbackArrowColour = WindowManager::read_style_colour_property( + *this, "playback_arrow_colour", 0, 0, 0); + get_style_property("playback_arrow_alpha", playbackArrowAlpha); + get_style_property("playback_arrow_size", playbackArrowSize); + get_style_property("playback_arrow_stem_size", + playbackArrowStemSize); } } // namespace timeline diff --git a/src/gui/widgets/timeline/timeline-ruler.hpp b/src/gui/widgets/timeline/timeline-ruler.hpp index 236eddd95..9bd6651bb 100644 --- a/src/gui/widgets/timeline/timeline-ruler.hpp +++ b/src/gui/widgets/timeline/timeline-ruler.hpp @@ -59,14 +59,34 @@ protected: bool on_expose_event(GdkEventExpose *event); + /** + * The event handler for button press events. + */ + bool on_button_press_event(GdkEventButton* event); + + /** + * The event handler for button release events. + */ + bool on_button_release_event(GdkEventButton* event); + + /** + * The event handler for mouse move events. + */ bool on_motion_notify_event(GdkEventMotion *event); void on_size_request(Gtk::Requisition *requisition); void on_size_allocate(Gtk::Allocation& allocation); - /* ===== Internals ===== */ private: + /* ===== Internal Methods ===== */ + + /** + * As the user drags, this function is called to update the position + * of the moving end of the playback period. + */ + void set_leading_x(const int x); + void draw_ruler(Cairo::RefPtr cairo, const Gdk::Rectangle ruler_rect); @@ -75,6 +95,9 @@ private: void draw_selection(Cairo::RefPtr cr, const Gdk::Rectangle ruler_rect); + + void draw_playback_period(Cairo::RefPtr cr, + const Gdk::Rectangle ruler_rect); gavl_time_t calculate_major_spacing() const; @@ -83,6 +106,16 @@ private: void read_styles(); private: + + // State values + bool isDragging; + + /** + * During a selection drag, one end of the selection is moving with + * the mouse, the other is pinned. pinnedDragTime specifies the time + * of that point. + */ + gavl_time_t pinnedDragTime; // Indicated values int mouseChevronOffset; @@ -96,7 +129,11 @@ private: int minDivisionWidth; int mouseChevronSize; int selectionChevronSize; - + GdkColor playbackArrowColour; + float playbackArrowAlpha; + int playbackArrowSize; + int playbackArrowStemSize; + // Owner lumiera::gui::widgets::TimelineWidget *timelineWidget;