diff --git a/src/gui/panels/timeline-panel.cpp b/src/gui/panels/timeline-panel.cpp index d912c1bf1..65e216f16 100644 --- a/src/gui/panels/timeline-panel.cpp +++ b/src/gui/panels/timeline-panel.cpp @@ -37,6 +37,7 @@ using namespace Gtk; using namespace sigc; using namespace std; using namespace boost; +using namespace util; using namespace gui::widgets; using namespace gui::model; @@ -62,24 +63,23 @@ TimelinePanel::TimelinePanel(workspace::WorkspaceWindow zoomOut(Stock::ZOOM_OUT), updatingToolbar(false), currentTool(timeline::IBeam) -{ - //timelineWidget.mouse_hover_signal().connect( - // mem_fun(this, &TimelinePanel::on_mouse_hover)); - //timelineWidget.playback_period_drag_released_signal().connect( - // mem_fun(this, &TimelinePanel::on_playback_period_drag_released)); - +{ // Hook up notifications workspace.get_project().get_sequences().signal_changed().connect( mem_fun(this, &TimelinePanel::on_sequence_list_changed)); - // Setup the notebook - notebook.signal_switch_page().connect( - mem_fun(this, &TimelinePanel::on_page_switched)); - notebook.popup_enable(); + // Setup the sequence chooser + sequenceChooserModel = Gtk::ListStore::create(sequenceChooserColumns); + ENSURE(sequenceChooserModel); - // Setup the sequence chooser; + sequenceChooser.set_model(sequenceChooserModel); + sequenceChooser.pack_start(sequenceChooserColumns.nameColumn); sequenceChooser.show_all(); - panelBar.pack_start(sequenceChooser, PACK_EXPAND_WIDGET); + + sequenceChooserChangedConnection = sequenceChooser.signal_changed(). + connect( sigc::mem_fun(*this, &TimelinePanel::on_sequence_chosen) ); + + panelBar.pack_start(sequenceChooser, PACK_SHRINK); // Setup the toolbar timeIndicatorButton.add(timeIndicator); @@ -112,11 +112,13 @@ TimelinePanel::TimelinePanel(workspace::WorkspaceWindow toolbar.show_all(); panelBar.pack_start(toolbar, PACK_SHRINK); - // Add the notebook - pack_start(notebook, PACK_EXPAND_WIDGET); + // Setup the timeline widget + shared_ptr sequence + = *workspace.get_project().get_sequences().begin(); + timelineWidget.reset(new TimelineWidget(load_state(sequence))); + pack_start(*timelineWidget, PACK_EXPAND_WIDGET); // Set the initial UI state - update_notebook(); update_sequence_chooser(); update_tool_buttons(); update_zoom_buttons(); @@ -165,32 +167,16 @@ TimelinePanel::on_ibeam_tool() void TimelinePanel::on_zoom_in() { - TimelineWidget *const widget = get_current_page(); - REQUIRE(widget != NULL); - - widget->zoom_view(ZoomToolSteps); + REQUIRE(timelineWidget); + timelineWidget->zoom_view(ZoomToolSteps); update_zoom_buttons(); } void TimelinePanel::on_zoom_out() { - TimelineWidget *const widget = get_current_page(); - REQUIRE(widget != NULL); - - widget->zoom_view(-ZoomToolSteps); - update_zoom_buttons(); -} - -void -TimelinePanel::on_page_switched(GtkNotebookPage*, guint) -{ - // The page has changed. Update the UI for this new page - - // Set the tool in the new page to be the same as the tool in the last - // page - set_tool(currentTool); - + REQUIRE(timelineWidget); + timelineWidget->zoom_view(-ZoomToolSteps); update_zoom_buttons(); } @@ -206,11 +192,10 @@ TimelinePanel::on_playback_period_drag_released() //----- TEST CODE - this needs to set the playback point via the // real backend - TimelineWidget *const widget = get_current_page(); - REQUIRE(widget != NULL); + REQUIRE(timelineWidget); - widget->get_state()->set_playback_point( - widget->get_state()->get_playback_period_start()); + timelineWidget->get_state()->set_playback_point( + timelineWidget->get_state()->get_playback_period_start()); //----- END TEST CODE play(); @@ -219,55 +204,63 @@ TimelinePanel::on_playback_period_drag_released() void TimelinePanel::on_sequence_list_changed() { - update_notebook(); update_sequence_chooser(); } +void +TimelinePanel::on_sequence_chosen() +{ + REQUIRE(timelineWidget); + + Gtk::TreeIter iter = sequenceChooser.get_active(); + if(iter) + { + weak_ptr sequence_ptr = + (*iter)[sequenceChooserColumns.sequenceColumn]; + shared_ptr sequence(sequence_ptr.lock()); + if(sequence) + { + shared_ptr old_state( + timelineWidget->get_state()); + REQUIRE(old_state); + + if(sequence != old_state->get_sequence()) + timelineWidget->set_state(load_state(sequence)); + } + } + + update_zoom_buttons(); +} + void TimelinePanel::update_sequence_chooser() { - sequenceChooser.clear_items(); + REQUIRE(sequenceChooserModel); + + // Block the event handler + sequenceChooserChangedConnection.block(); + + // Repopulate the sequence chooser + sequenceChooserModel->clear(); + + shared_ptr state = + timelineWidget->get_state(); + REQUIRE(state); BOOST_FOREACH( shared_ptr< model::Sequence > sequence, workspace.get_project().get_sequences() ) { - sequenceChooser.append_text(sequence->get_name()); - } -} - -void -TimelinePanel::update_notebook() -{ - std::map > - old_pages; - old_pages.swap(notebook_pages); - - BOOST_FOREACH( shared_ptr< model::Sequence > sequence, - workspace.get_project().get_sequences() ) - { - std::map >:: - iterator iterator = old_pages.find(sequence.get()); - if(iterator != old_pages.end()) - { - // This sequence has not been changed - // leave it in the new list - notebook_pages[iterator->first] = iterator->second; - old_pages.erase(iterator->first); - } - else - { - // This is a new sequence, add it in - shared_ptr< timeline::TimelineState > state( - new timeline::TimelineState(sequence)); - shared_ptr< TimelineWidget > widget( - new TimelineWidget(state)); - notebook_pages[sequence.get()] = widget; - notebook.append_page(*widget.get(), sequence->get_name()); - notebook.set_tab_reorderable(*widget.get()); - } + Gtk::TreeIter iter = sequenceChooserModel->append(); + Gtk::TreeModel::Row row = *iter; + row[sequenceChooserColumns.sequenceColumn] = sequence; + row[sequenceChooserColumns.nameColumn] = sequence->get_name(); + + if(state->get_sequence() == sequence) + sequenceChooser.set_active(iter); } - notebook.show_all_children(); + // Unblock the event handler + sequenceChooserChangedConnection.unblock(); } void @@ -292,17 +285,14 @@ TimelinePanel::update_tool_buttons() void TimelinePanel::update_zoom_buttons() { - TimelineWidget *const widget = get_current_page(); + REQUIRE(timelineWidget); + + timeline::TimelineViewWindow &viewWindow = + timelineWidget->get_state()->get_view_window(); - if(widget != NULL) - { - timeline::TimelineViewWindow &viewWindow = - widget->get_state()->get_view_window(); - - zoomIn.set_sensitive(viewWindow.get_time_scale() != 1); - zoomOut.set_sensitive(viewWindow.get_time_scale() - != TimelineWidget::MaxScale); - } + zoomIn.set_sensitive(viewWindow.get_time_scale() != 1); + zoomOut.set_sensitive(viewWindow.get_time_scale() + != TimelineWidget::MaxScale); } void @@ -327,15 +317,13 @@ TimelinePanel::is_playing() const void TimelinePanel::set_tool(timeline::ToolType tool) { + REQUIRE(timelineWidget); + if(updatingToolbar) return; - TimelineWidget *const widget = get_current_page(); - if(widget != NULL) - { - currentTool = tool; - widget->set_tool(tool); - update_tool_buttons(); - } + currentTool = tool; + timelineWidget->set_tool(tool); + update_tool_buttons(); } void @@ -344,17 +332,6 @@ TimelinePanel::show_time(gavl_time_t time) timeIndicator.set_text(lumiera_tmpbuf_print_time(time)); } -TimelineWidget* -TimelinePanel::get_current_page() -{ - Notebook::PageList::iterator page_iterator = notebook.get_current(); - if(!page_iterator) return NULL; - - Widget* const widget = (*page_iterator).get_child(); - REQUIRE(widget != NULL); - return (TimelineWidget*)widget; -} - bool TimelinePanel::on_frame() { @@ -374,5 +351,18 @@ TimelinePanel::on_frame() return true; } +shared_ptr +TimelinePanel::load_state(weak_ptr sequence) +{ + if(contains(timelineStates, sequence)) + return timelineStates[sequence]; + + shared_ptr shared_sequence = sequence.lock(); + if(shared_sequence) + return shared_ptr< timeline::TimelineState >( + new timeline::TimelineState(shared_sequence)); + return shared_ptr< timeline::TimelineState >(); +} + } // namespace panels } // namespace gui diff --git a/src/gui/panels/timeline-panel.hpp b/src/gui/panels/timeline-panel.hpp index 79b130c61..522206bbd 100644 --- a/src/gui/panels/timeline-panel.hpp +++ b/src/gui/panels/timeline-panel.hpp @@ -32,7 +32,7 @@ using namespace gui::widgets; namespace gui { - + namespace model { class Sequence; } @@ -70,20 +70,25 @@ private: void on_zoom_out(); void on_time_pressed(); - - void on_page_switched(GtkNotebookPage*, guint); - + void on_mouse_hover(gavl_time_t time); void on_playback_period_drag_released(); + /** + * An event handler for when the list of sequences changes. + **/ void on_sequence_list_changed(); + /** + * An event handler for when a new sequence is chosen in the + * sequenceChooser. + **/ + void on_sequence_chosen(); + private: void update_sequence_chooser(); - - void update_notebook(); - + void update_playback_buttons(); void update_tool_buttons(); void update_zoom_buttons(); @@ -97,8 +102,36 @@ private: void set_tool(gui::widgets::timeline::ToolType tool); void show_time(gavl_time_t time); + + boost::shared_ptr load_state( + boost::weak_ptr sequence); - TimelineWidget* get_current_page(); +private: + + /** + * The definition of the sequence chooser combo box columns + **/ + class SequenceChooserColumns : public Gtk::TreeModel::ColumnRecord + { + public: + /** + * Constructor + **/ + SequenceChooserColumns() + { add(nameColumn); add(sequenceColumn); } + + /** + * An invisible column which will be used to identify the sequence + * of a row. + **/ + Gtk::TreeModelColumn< boost::weak_ptr > + sequenceColumn; + + /** + * The column to use as the label for the combo box widget items. + **/ + Gtk::TreeModelColumn< Glib::ustring > nameColumn; + }; private: @@ -106,12 +139,19 @@ private: // Grip Widgets ButtonBar toolbar; - Gtk::ComboBoxText sequenceChooser; + + // Sequence Chooser + SequenceChooserColumns sequenceChooserColumns; + Glib::RefPtr sequenceChooserModel; + Gtk::ComboBox sequenceChooser; + sigc::connection sequenceChooserChangedConnection; // Body Widgets - Gtk::Notebook notebook; - std::map< const model::Sequence*, boost::shared_ptr > - notebook_pages; + boost::scoped_ptr timelineWidget; + + std::map< boost::weak_ptr, + boost::shared_ptr > + timelineStates; // Toolbar Widgets Gtk::Label timeIndicator; diff --git a/src/gui/widgets/timeline-widget.cpp b/src/gui/widgets/timeline-widget.cpp index 179bab06e..387927ce1 100644 --- a/src/gui/widgets/timeline-widget.cpp +++ b/src/gui/widgets/timeline-widget.cpp @@ -109,8 +109,14 @@ TimelineWidget::get_state() void TimelineWidget::set_state(shared_ptr 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( @@ -125,6 +131,9 @@ TimelineWidget::set_state(shared_ptr new_state) &TimelineWidget::on_body_changed)); update_tracks(); + + // Send the state changed signal + stateChangedSignal.emit(); } void @@ -175,6 +184,11 @@ TimelineWidget::hovering_track_changed_signal() const { return hoveringTrackChangedSignal; } +sigc::signal +TimelineWidget::state_changed_signal() const +{ + return stateChangedSignal; +} /* ===== Events ===== */ diff --git a/src/gui/widgets/timeline-widget.hpp b/src/gui/widgets/timeline-widget.hpp index 711e0eb7f..4ce4d9e72 100644 --- a/src/gui/widgets/timeline-widget.hpp +++ b/src/gui/widgets/timeline-widget.hpp @@ -57,6 +57,8 @@ class TimelineWidget : public Gtk::Table public: /** * Constructor + * @param source_state The state that will be used as the data source + * for this timeline widget. */ TimelineWidget( boost::shared_ptr source_state); @@ -69,8 +71,17 @@ public: /* ===== Data Access ===== */ public: + /** + * Gets a pointer to the current state object. + * @return The state object that the timeline widget is currently + * working with. + **/ boost::shared_ptr get_state(); + /** + * Replaces the current TimelineState object with another. + * @param new_state The new state to swap in. + **/ void set_state(boost::shared_ptr new_state); /** @@ -101,6 +112,8 @@ public: sigc::signal > hovering_track_changed_signal() const; + + sigc::signal state_changed_signal() const; /* ===== Events ===== */ protected: @@ -217,6 +230,10 @@ private: protected: + /** + * The state that will be used as the data source for this timeline + * widget. + **/ boost::shared_ptr state; // Model Data @@ -251,6 +268,7 @@ protected: sigc::signal playbackPeriodDragReleasedSignal; sigc::signal > hoveringTrackChangedSignal; + sigc::signal stateChangedSignal; bool update_tracks_frozen; diff --git a/src/gui/widgets/timeline/timeline-body.cpp b/src/gui/widgets/timeline/timeline-body.cpp index a8e006948..3d52f7fe5 100644 --- a/src/gui/widgets/timeline/timeline-body.cpp +++ b/src/gui/widgets/timeline/timeline-body.cpp @@ -50,11 +50,14 @@ TimelineBody::TimelineBody(TimelineWidget &timeline_widget) : timelineWidget(timeline_widget) { // Connect up some events - view_window().changed_signal().connect( - sigc::mem_fun(this, &TimelineBody::on_update_view) ); + timeline_widget.state_changed_signal().connect( + sigc::mem_fun(this, &TimelineBody::on_state_changed) ); // Install style properties register_styles(); + + // Reset the state + on_state_changed(); } TimelineBody::~TimelineBody() @@ -270,6 +273,17 @@ TimelineBody::on_motion_notify_event(GdkEventMotion *event) return false; } +void +TimelineBody::on_state_changed() +{ + // Connect up some events + view_window().changed_signal().connect( + sigc::mem_fun(this, &TimelineBody::on_update_view) ); + + // Redraw + queue_draw(); +} + void TimelineBody::draw_tracks(Cairo::RefPtr cr) { diff --git a/src/gui/widgets/timeline/timeline-body.hpp b/src/gui/widgets/timeline/timeline-body.hpp index 651e70a75..0cb351021 100644 --- a/src/gui/widgets/timeline/timeline-body.hpp +++ b/src/gui/widgets/timeline/timeline-body.hpp @@ -75,7 +75,7 @@ public: * @param tool_type The type of tool to set. */ void set_tool(ToolType tool_type); - + /* ===== Events ===== */ protected: @@ -111,6 +111,12 @@ protected: */ bool on_motion_notify_event(GdkEventMotion *event); + /** + * The event handler for when the TimelineWidget's state object is + * replaced. + **/ + void on_state_changed(); + /* ===== Internals ===== */ private: diff --git a/src/gui/widgets/timeline/timeline-state.hpp b/src/gui/widgets/timeline/timeline-state.hpp index a4e22261f..2c9c07043 100644 --- a/src/gui/widgets/timeline/timeline-state.hpp +++ b/src/gui/widgets/timeline/timeline-state.hpp @@ -37,14 +37,28 @@ class Sequence; namespace widgets { namespace timeline { +/** + * TimelineState is a container for the state data for TimelineWidget. + * @remarks TimelineState s can be swapped out so that TimelineWidget + * can flip between different views. + **/ class TimelineState { public: + /** + * Constructor + * @param source_sequence The sequence on which the TimelineWidget + * will operate when this TimelineState is attached. + **/ TimelineState(boost::shared_ptr source_sequence); public: + /** + * Gets the sequence that is attached to this timeline state object. + * @return Returns a shared_ptr to the sequence object. + **/ boost::shared_ptr get_sequence() const; /** @@ -104,9 +118,15 @@ public: */ gavl_time_t get_playback_point() const; - + /** + * A signal to notify when the selected period has changed. + **/ sigc::signal selection_changed_signal() const; + /** + * A signal to notify when the playback point or playback periods have + * changed. + **/ sigc::signal playback_changed_signal() const; private: @@ -120,17 +140,49 @@ private: boost::shared_ptr sequence; // View State + /** + * The ViewWindow for the TimelineWidget display with. + **/ timeline::TimelineViewWindow viewWindow; // Selection State + + /** + * The start time of the selection period. + **/ gavl_time_t selectionStart; + + /** + * The end time of the selection period. + **/ gavl_time_t selectionEnd; + + /** + * The start time of the playback period. + **/ gavl_time_t playbackPeriodStart; + + /** + * The end time of the playback period. + **/ gavl_time_t playbackPeriodEnd; + + /** + * The time of the playback point. + **/ gavl_time_t playbackPoint; // Signals + + /** + * A signal to notify when the selected period has changed. + **/ sigc::signal selectionChangedSignal; + + /** + * A signal to notify when the playback point or playback periods have + * changed. + **/ sigc::signal playbackChangedSignal; };