diff --git a/src/gui/widgets/timeline-widget.cpp b/src/gui/widgets/timeline-widget.cpp index cce5682c5..7798fba76 100644 --- a/src/gui/widgets/timeline-widget.cpp +++ b/src/gui/widgets/timeline-widget.cpp @@ -289,13 +289,20 @@ TimelineWidget::update_tracks() { REQUIRE(sequence); + // 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 header container ASSERT(headerContainer != NULL); + headerContainer->show_all_children(); headerContainer->update_headers(); + // Update the body + body->queue_draw(); + // Recalculate the total height of the timeline scrolled area totalHeight = 0; BOOST_FOREACH(shared_ptr track, @@ -316,9 +323,6 @@ TimelineWidget::create_timeline_tracks() BOOST_FOREACH(shared_ptr child, sequence->get_child_tracks()) create_timeline_tracks_from_branch(child); - - headerContainer->show_all_children(); - body->queue_draw(); } void @@ -351,14 +355,59 @@ TimelineWidget::create_timeline_track_from_model_track( // Choose a corresponding timeline track class from the model track's // class if(typeid(*model_track) == typeid(model::ClipTrack)) - return shared_ptr(new timeline::ClipTrack()); + return shared_ptr(new timeline::ClipTrack(*this)); else if(typeid(*model_track) == typeid(model::GroupTrack)) - return shared_ptr(new timeline::GroupTrack()); + return shared_ptr(new timeline::GroupTrack(*this)); ASSERT(NULL); // Unknown track type; return shared_ptr(); } +void +TimelineWidget::remove_orphaned_tracks() +{ + REQUIRE(sequence); + REQUIRE(headerContainer != NULL); + REQUIRE(body != NULL); + + std::map, + boost::shared_ptr > + orphan_track_map(trackMap); + + // Remove all tracks which are still present in the sequence + BOOST_FOREACH(shared_ptr child, + sequence->get_child_tracks()) + search_orphaned_tracks_in_branch(child, orphan_track_map); + + // orphan_track_map now contains all the orphaned tracks + // Remove them + std::pair, shared_ptr > + pair; + BOOST_FOREACH( pair, orphan_track_map ) + { + ENSURE(pair.first) + trackMap.erase(pair.first); + } +} + +void +TimelineWidget::search_orphaned_tracks_in_branch( + boost::shared_ptr model_track, + std::map, + boost::shared_ptr > &orphan_track_map) +{ + REQUIRE(model_track); + + // Is the timeline UI still present? + if(contains(orphan_track_map, model_track)) + orphan_track_map.erase(model_track); + + // Recurse to child tracks + BOOST_FOREACH(shared_ptr child, + model_track->get_child_tracks()) + search_orphaned_tracks_in_branch(child, orphan_track_map); +} + shared_ptr TimelineWidget::lookup_timeline_track( shared_ptr model_track) const @@ -378,7 +427,32 @@ TimelineWidget::lookup_timeline_track( ENSURE(iterator->second != NULL); return iterator->second; } + +boost::shared_ptr +TimelineWidget::lookup_model_track( + const timeline::Track *timeline_track) const +{ + REQUIRE(sequence); + std::pair, shared_ptr > + pair; + BOOST_FOREACH( pair, trackMap ) + { + if(pair.second.get() == timeline_track) + { + ENSURE(pair.first); + return pair.first; + } + } + + // The track is not present in the map + // We are in an error condition if the timeline track is not found + // - the timeline tracks must always be synchronous with the model + // tracks. + ENSURE(0); + return shared_ptr(); +} + void TimelineWidget::update_scroll() { diff --git a/src/gui/widgets/timeline-widget.hpp b/src/gui/widgets/timeline-widget.hpp index 0e379eb11..41ad86b75 100644 --- a/src/gui/widgets/timeline-widget.hpp +++ b/src/gui/widgets/timeline-widget.hpp @@ -191,10 +191,21 @@ private: * @return The timeline track created, or an empty shared_ptr if * model_track has an unreckognised type (this is an error condition). **/ - static boost::shared_ptr + boost::shared_ptr create_timeline_track_from_model_track( boost::shared_ptr model_track); + /** + * Removes any UI tracks which no longer have corresponding model + * tracks present in the sequence. + **/ + void remove_orphaned_tracks(); + + void search_orphaned_tracks_in_branch( + boost::shared_ptr model_track, + std::map, + boost::shared_ptr > &orphan_track_map); + /** * Looks up a timeline UI track in trackMap that corresponds to a * given model_track. @@ -205,6 +216,17 @@ private: **/ boost::shared_ptr lookup_timeline_track( boost::shared_ptr model_track) const; + + /** + * Looks up a model track in trackMap that corresponds to a + * given timeline track. + * @param timeline_track The timeline UI track to look up. + * @returns The model track found, or an empty shared_ptr if + * timeline_track has no corresponding timeline UI track (this is an + * error condition). + **/ + boost::shared_ptr lookup_model_track( + const timeline::Track *timeline_track) const; // ----- Layout Functions ----- // @@ -305,6 +327,7 @@ protected: friend class timeline::Tool; friend class timeline::ArrowTool; friend class timeline::IBeamTool; + friend class timeline::Track; }; } // namespace widgets diff --git a/src/gui/widgets/timeline/timeline-clip-track.cpp b/src/gui/widgets/timeline/timeline-clip-track.cpp index 8e9e0bd2d..c5d384bb0 100644 --- a/src/gui/widgets/timeline/timeline-clip-track.cpp +++ b/src/gui/widgets/timeline/timeline-clip-track.cpp @@ -29,7 +29,8 @@ namespace gui { namespace widgets { namespace timeline { -ClipTrack::ClipTrack() +ClipTrack::ClipTrack(TimelineWidget &timeline_widget) : + Track(timeline_widget) { } diff --git a/src/gui/widgets/timeline/timeline-clip-track.hpp b/src/gui/widgets/timeline/timeline-clip-track.hpp index 0100763a3..5b5b33e75 100644 --- a/src/gui/widgets/timeline/timeline-clip-track.hpp +++ b/src/gui/widgets/timeline/timeline-clip-track.hpp @@ -41,7 +41,7 @@ class TimelineViewWindow; class ClipTrack : public timeline::Track { public: - ClipTrack(); + ClipTrack(TimelineWidget &timeline_widget); void draw_track(Cairo::RefPtr cairo, TimelineViewWindow* const window) const; diff --git a/src/gui/widgets/timeline/timeline-group-track.cpp b/src/gui/widgets/timeline/timeline-group-track.cpp index 49c1077f1..570095bb1 100644 --- a/src/gui/widgets/timeline/timeline-group-track.cpp +++ b/src/gui/widgets/timeline/timeline-group-track.cpp @@ -28,7 +28,8 @@ namespace gui { namespace widgets { namespace timeline { -GroupTrack::GroupTrack() +GroupTrack::GroupTrack(TimelineWidget &timeline_widget) : + Track(timeline_widget) { } diff --git a/src/gui/widgets/timeline/timeline-group-track.hpp b/src/gui/widgets/timeline/timeline-group-track.hpp index 5e2368a54..46e0fdef7 100644 --- a/src/gui/widgets/timeline/timeline-group-track.hpp +++ b/src/gui/widgets/timeline/timeline-group-track.hpp @@ -36,7 +36,7 @@ namespace timeline { class GroupTrack : public timeline::Track { public: - GroupTrack(); + GroupTrack(TimelineWidget &timeline_widget); void draw_track(Cairo::RefPtr cairo, TimelineViewWindow* constwindow) diff --git a/src/gui/widgets/timeline/timeline-header-container.cpp b/src/gui/widgets/timeline/timeline-header-container.cpp index e5b58d486..506eb0316 100644 --- a/src/gui/widgets/timeline/timeline-header-container.cpp +++ b/src/gui/widgets/timeline/timeline-header-container.cpp @@ -150,11 +150,25 @@ bool TimelineHeaderContainer::on_button_press_event ( break; case 3: // Right Click - // Popup the context menu - contextMenu.popup(event->button, event->time); - break; - - + { + // Popup the context menu + shared_ptr header = header_from_point( + Gdk::Point(event->x, event->y)); + + // Are we hovering on a header? + if(header) + { + // Yes - show the header's context menu + header->show_header_context_menu( + event->button, event->time); + } + else + { + // No - show the default context menu + contextMenu.popup(event->button, event->time); + } + break; + } } return true; @@ -239,6 +253,12 @@ TimelineHeaderContainer::forall_vfunc(gboolean /* include_internals */, } } +void +TimelineHeaderContainer::on_remove(Widget* widget) +{ + // Do nothing - this is just to keep Gtk::Container happy +} + bool TimelineHeaderContainer::on_expose_event(GdkEventExpose *event) { @@ -477,6 +497,25 @@ TimelineHeaderContainer::draw_header_decoration( draw_header_decoration(child, clip_rect); } +boost::shared_ptr +TimelineHeaderContainer::header_from_point(const Gdk::Point &point) +{ + std::pair, Gdk::Rectangle> pair; + BOOST_FOREACH( pair, headerBoxes ) + { + // Hit test the rectangle + const Gdk::Rectangle &rect = pair.second; + + if(point.get_x() >= rect.get_x() && + point.get_x() < rect.get_x() + rect.get_width() && + point.get_y() >= rect.get_y() && + point.get_y() < rect.get_y() + rect.get_height()) + return pair.first; + } + + return shared_ptr(); +} + shared_ptr TimelineHeaderContainer::expander_button_from_point( const Gdk::Point &point) diff --git a/src/gui/widgets/timeline/timeline-header-container.hpp b/src/gui/widgets/timeline/timeline-header-container.hpp index a035e104a..517ed3bce 100644 --- a/src/gui/widgets/timeline/timeline-header-container.hpp +++ b/src/gui/widgets/timeline/timeline-header-container.hpp @@ -118,6 +118,12 @@ private: **/ void forall_vfunc(gboolean include_internals, GtkCallback callback, gpointer callback_data); + + /** + * An event handler that is called when a widget is removed from the + * container. + **/ + void on_remove(Widget* widget); /* ===== Events ===== */ private: @@ -193,6 +199,9 @@ private: void draw_header_decoration( boost::shared_ptr model_track, const Gdk::Rectangle &clip_rect); + + boost::shared_ptr header_from_point( + const Gdk::Point &point); /** * Given a point, expander_button_from_point finds the track of the @@ -297,6 +306,8 @@ private: * in pixels. **/ int expand_button_size; + + friend class timeline::Track; }; } // namespace timeline diff --git a/src/gui/widgets/timeline/timeline-track.cpp b/src/gui/widgets/timeline/timeline-track.cpp index 6cf2ae95f..05f49b523 100644 --- a/src/gui/widgets/timeline/timeline-track.cpp +++ b/src/gui/widgets/timeline/timeline-track.cpp @@ -24,15 +24,18 @@ #include #include "timeline-track.hpp" +#include "../timeline-widget.hpp" #include "../../window-manager.hpp" +using namespace boost; using namespace Gtk; namespace gui { namespace widgets { namespace timeline { -Track::Track() : +Track::Track(TimelineWidget &timeline_widget) : + timelineWidget(timeline_widget), expanded(true), enableButton(Gtk::StockID("track_enabled")), lockButton(Gtk::StockID("track_unlocked")) @@ -54,6 +57,14 @@ Track::Track() : headerWidget.pack_start(titleBox, PACK_SHRINK); headerWidget.pack_start(buttonBar, PACK_SHRINK); + + // Setup the context menu + Menu::MenuList& menu_list = contextMenu.items(); + menu_list.push_back( Menu_Helpers::MenuElem(_("_Add Track"), + sigc::mem_fun(timelineWidget, + &TimelineWidget::on_add_track_command) ) ); + menu_list.push_back( Menu_Helpers::MenuElem(_("_Remove Track"), + sigc::mem_fun(this, &Track::on_remove_track) ) ); } Gtk::Widget& @@ -80,6 +91,23 @@ Track::set_expanded(bool expanded) this->expanded = expanded; } +void +Track::show_header_context_menu(guint button, guint32 time) +{ + contextMenu.popup(button, time); +} + +void +Track::on_remove_track() +{ + shared_ptr model_track = + timelineWidget.lookup_model_track(this); + ASSERT(model_track); + ASSERT(timelineWidget.sequence); + + timelineWidget.sequence->get_child_track_list().remove(model_track); +} + } // namespace timeline } // namespace widgets } // namespace gui diff --git a/src/gui/widgets/timeline/timeline-track.hpp b/src/gui/widgets/timeline/timeline-track.hpp index 2c5aff97c..d5ad70538 100644 --- a/src/gui/widgets/timeline/timeline-track.hpp +++ b/src/gui/widgets/timeline/timeline-track.hpp @@ -25,6 +25,7 @@ #include "../../gtk-lumiera.hpp" #include "../../model/track.hpp" +#include "timeline-header-container.hpp" #ifndef TIMELINE_TRACK_HPP #define TIMELINE_TRACK_HPP @@ -38,7 +39,7 @@ class TimelineViewWindow; class Track { public: - Track(); + Track(TimelineWidget &timeline_widget); Gtk::Widget& get_header_widget(); @@ -47,12 +48,19 @@ public: bool get_expanded() const; void set_expanded(bool expanded); + + void show_header_context_menu(guint button, guint32 time); virtual void draw_track(Cairo::RefPtr cairo, TimelineViewWindow* const window) const = 0; private: + void on_remove_track(); + +private: + + TimelineWidget &timelineWidget; bool expanded; @@ -65,6 +73,8 @@ private: Gtk::Entry titleBox; Gtk::Toolbar buttonBar; + + Gtk::Menu contextMenu; };