diff --git a/configure.ac b/configure.ac index eb78e6d9a..717fe6f4b 100644 --- a/configure.ac +++ b/configure.ac @@ -1,6 +1,7 @@ AC_INIT(lumiera, 0.1pre) -AC_CONFIG_SRCDIR(src/lib/luid.c) -AC_CONFIG_AUX_DIR(scripts) +AC_CONFIG_SRCDIR([src/lib/luid.c]) +AC_CONFIG_AUX_DIR([scripts]) +AC_CONFIG_MACRO_DIR([m4]) AM_INIT_AUTOMAKE AC_PREREQ(2.59) @@ -123,7 +124,7 @@ AC_CHECK_HEADER([boost/regex.hpp], AC_LANG_POP([C++]) -PKG_CHECK_MODULES(LUMIERA_COMMON_LIBS, [sigc++-2.0 >= 2.0.18]) +PKG_CHECK_MODULES(LUMIERA_COMMON_LIBS, [sigc++-2.0 >= 2.0.17]) ############## Internatinalization #GETTEXT_PACKAGE=gtk-lumiera diff --git a/icons/Makefile.am b/icons/Makefile.am index c4bb2c8ca..6490393ab 100644 --- a/icons/Makefile.am +++ b/icons/Makefile.am @@ -33,6 +33,7 @@ iconcommand = python $(top_srcdir)/admin/render-icon.py 48x48pre = $(prerendereddir)/48x48 dist_pkgdata_DATA += \ + $(16x16)/app-icon.png $(22x22)/app-icon.png $(24x24)/app-icon.png $(32x32)/app-icon.png $(48x48)/app-icon.png \ $(16x16)/tool-arrow.png $(22x22)/tool-arrow.png $(24x24)/tool-arrow.png $(32x32)/tool-arrow.png $(48x48)/tool-arrow.png \ $(16x16)/tool-i-beam.png $(22x22)/tool-i-beam.png $(24x24)/tool-i-beam.png $(32x32)/tool-i-beam.png $(48x48)/tool-i-beam.png \ $(16x16)/track-disabled.png \ @@ -48,6 +49,11 @@ clean-local: # ========== SVG Icons ========== +# App Icon + +$(16x16)/app-icon.png $(22x22)/app-icon.png $(24x24)/app-icon.png $(32x32)/app-icon.png $(48x48)/app-icon.png : $(svgdir)/app-icon.svg $(top_builddir)/rsvg-convert + $(iconcommand) $< $(icondir) + # Timeline Tools $(16x16)/tool-arrow.png $(22x22)/tool-arrow.png $(24x24)/tool-arrow.png $(32x32)/tool-arrow.png $(48x48)/tool-arrow.png : $(svgdir)/tool-arrow.svg $(top_builddir)/rsvg-convert diff --git a/icons/svg/app-icon.svg b/icons/svg/app-icon.svg new file mode 100644 index 000000000..1df75c265 --- /dev/null +++ b/icons/svg/app-icon.svg @@ -0,0 +1,903 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/gui/Makefile.am b/src/gui/Makefile.am index 467521430..70e8264fb 100644 --- a/src/gui/Makefile.am +++ b/src/gui/Makefile.am @@ -144,6 +144,8 @@ libgui_la_SOURCES = \ $(lumigui_srcdir)/widgets/timeline/timeline-group-track.hpp \ $(lumigui_srcdir)/widgets/timeline/timeline-clip.cpp \ $(lumigui_srcdir)/widgets/timeline/timeline-clip.hpp \ + $(lumigui_srcdir)/widgets/timeline/timeline-layout-helper.cpp \ + $(lumigui_srcdir)/widgets/timeline/timeline-layout-helper.hpp \ $(lumigui_srcdir)/model/project.cpp \ $(lumigui_srcdir)/model/project.hpp \ $(lumigui_srcdir)/model/track.cpp \ diff --git a/src/gui/dialogs/render.hpp b/src/gui/dialogs/render.hpp index 51c088b72..51989a25e 100644 --- a/src/gui/dialogs/render.hpp +++ b/src/gui/dialogs/render.hpp @@ -54,12 +54,11 @@ protected: Gtk::HBox containerFormatHBox; Gtk::Label containerFormatLabel; Gtk::ComboBox containerFormat; + + Gtk::Image renderButtonImage; Gtk::Frame audioFrame; - Gtk::Frame videoFrame; - - Gtk::Image renderButtonImage; }; } // namespace dialogs diff --git a/src/gui/gtk-lumiera.cpp b/src/gui/gtk-lumiera.cpp index e9c7b0088..34a49d4ad 100644 --- a/src/gui/gtk-lumiera.cpp +++ b/src/gui/gtk-lumiera.cpp @@ -44,6 +44,7 @@ using namespace gui; using namespace gui::workspace; using namespace gui::model; + GtkLumiera the_application; @@ -66,7 +67,9 @@ GtkLumiera::main(int argc, char *argv[]) WorkspaceWindow main_window(&project); - kit.run(main_window); + kit.run(main_window); + + return 0; } Glib::ustring @@ -83,6 +86,20 @@ application() return the_application; } +/* ===== Constants ===== */ + +const gchar* GtkLumiera::AppTitle = "Lumiera"; +const gchar* GtkLumiera::AppVersion = _("0.1-dev"); +const gchar* GtkLumiera::AppCopyright = + _("© 2008 The Lumiera Team"); +const gchar* GtkLumiera::AppWebsite = "www.lumiera.org"; +const gchar* GtkLumiera::AppAuthors[] = { + "Joel Holdsworth", + "Christian Thaeter", + "Hermann Vosseler", + ""}; +const int GtkLumiera::AppAuthorCount = 4; + } // namespace gui diff --git a/src/gui/gtk-lumiera.hpp b/src/gui/gtk-lumiera.hpp index f2e228851..a01bc5dc4 100644 --- a/src/gui/gtk-lumiera.hpp +++ b/src/gui/gtk-lumiera.hpp @@ -32,8 +32,10 @@ #include // need to include this after gtkmm.h, because types.h from GTK tries to shaddow the ERROR macro from windows, which kills NoBug's ERROR macro #include #include +#include #include #include +#include #include "lib/util.hpp" extern "C" { @@ -59,37 +61,6 @@ NOBUG_DECLARE_FLAG(gui); */ namespace gui { -/* ===== Global Constants ===== */ - -/** - * The name of the application - */ -static const gchar* AppTitle = "Lumiera"; - -/** - * The version number of the application - */ -static const gchar* AppVersion = N_("0.1-dev"); - -/** - * The copyright of the application - */ -static const gchar* AppCopyright = N_("© 2008 The Lumiera Team"); - -/** - * The website of the application - */ -static const gchar* AppWebsite = "www.lumiera.org"; - -/** - * An alphabetical list of the application's authors - */ -static const gchar* AppAuthors[] = { - "Joel Holdsworth", - "Christian Thaeter", - "Hermann Vosseler", - ""}; - /* ===== The Application Class ===== */ /** @@ -102,6 +73,38 @@ public: static Glib::ustring get_home_data_path(); + +public: + /* ----- Constants ----- */ + /** + * The name of the application + */ + static const gchar* AppTitle; + + /** + * The version number of the application + */ + static const gchar* AppVersion; + + /** + * The copyright of the application + */ + static const gchar* AppCopyright; + + /** + * The website of the application + */ + static const gchar* AppWebsite; + + /** + * An alphabetical list of the application's authors + */ + static const gchar* AppAuthors[]; + + /** + * The number of authors in AppAuthors + **/ + static const int AppAuthorCount; }; /** diff --git a/src/gui/model/clip-track.cpp b/src/gui/model/clip-track.cpp index 5f627eeca..5f8907599 100644 --- a/src/gui/model/clip-track.cpp +++ b/src/gui/model/clip-track.cpp @@ -29,5 +29,15 @@ ClipTrack::ClipTrack() { } +std::string +ClipTrack::print_track() +{ + std::ostringstream os; + + os << "ClipTrack\t\"" << get_name() << "\""; + + return os.str(); +} + } // namespace model } // namespace gui diff --git a/src/gui/model/clip-track.hpp b/src/gui/model/clip-track.hpp index f63ea4e6b..c3c7e0e2f 100644 --- a/src/gui/model/clip-track.hpp +++ b/src/gui/model/clip-track.hpp @@ -40,7 +40,7 @@ class ClipTrack : public Track public: ClipTrack(); - + std::string print_track(); private: diff --git a/src/gui/model/group-track.cpp b/src/gui/model/group-track.cpp index c7572b4d9..73ac08114 100644 --- a/src/gui/model/group-track.cpp +++ b/src/gui/model/group-track.cpp @@ -28,6 +28,16 @@ namespace model { GroupTrack::GroupTrack() { } + +std::string +GroupTrack::print_track() +{ + std::ostringstream os; + + os << "GroupTrack\t\"" << get_name() << "\""; + + return os.str(); +} } // namespace model } // namespace gui diff --git a/src/gui/model/group-track.hpp b/src/gui/model/group-track.hpp index de5ac8f4b..06da74060 100644 --- a/src/gui/model/group-track.hpp +++ b/src/gui/model/group-track.hpp @@ -36,6 +36,7 @@ class GroupTrack : public ParentTrack public: GroupTrack(); + std::string print_track(); }; } // namespace model diff --git a/src/gui/model/parent-track.cpp b/src/gui/model/parent-track.cpp index c00048129..612e112d7 100644 --- a/src/gui/model/parent-track.cpp +++ b/src/gui/model/parent-track.cpp @@ -21,6 +21,7 @@ * *****************************************************/ #include "parent-track.hpp" +#include namespace gui { namespace model { @@ -41,5 +42,27 @@ ParentTrack::get_child_track_list() return tracks; } +bool +ParentTrack::remove_child_track(const boost::shared_ptr track) +{ + REQUIRE(track); + + BOOST_FOREACH(const boost::shared_ptr child_track, tracks) + { + REQUIRE(child_track); + + if(track.get() == child_track.get()) + { + tracks.remove(track); + return true; + } + + if(child_track->remove_child_track(track)) + return true; + } + + return false; +} + } // namespace model } // namespace gui diff --git a/src/gui/model/parent-track.hpp b/src/gui/model/parent-track.hpp index a040297ca..a895b3427 100644 --- a/src/gui/model/parent-track.hpp +++ b/src/gui/model/parent-track.hpp @@ -39,14 +39,14 @@ class ParentTrack : public Track protected: ParentTrack(); -public: - virtual void add_child_track(Track* child) {}; - +public: std::list< boost::shared_ptr > get_child_tracks() const; lumiera::observable_list< boost::shared_ptr >& get_child_track_list(); + + bool remove_child_track(const boost::shared_ptr track); protected: lumiera::observable_list< boost::shared_ptr > tracks; diff --git a/src/gui/model/sequence.cpp b/src/gui/model/sequence.cpp index c312108c3..2503b88cc 100644 --- a/src/gui/model/sequence.cpp +++ b/src/gui/model/sequence.cpp @@ -33,21 +33,39 @@ Sequence::Sequence() static bool first = true; shared_ptr group_track, group_track2; + shared_ptr clip_track; tracks.push_back(group_track = shared_ptr(new GroupTrack())); group_track->set_name("Group Track"); - - /*if(first) + + if(first) { - group_track.add_child_track(shared_ptr(new ClipTrack())); - group_track.add_child_track( - group_track2 = shared_ptr(new GroupTrack())); - group_track2.add_child_track(shared_ptr(new ClipTrack())); + group_track->get_child_track_list().push_back( + clip_track = shared_ptr(new ClipTrack())); + group_track->get_child_track_list().push_back( + group_track2 = shared_ptr(new GroupTrack())); + group_track2->set_name("Group Track 2"); + group_track2->get_child_track_list().push_back( + shared_ptr(new ClipTrack())); first = false; - }*/ + } + + tracks.push_back(shared_ptr(new GroupTrack())); tracks.push_back(shared_ptr(new ClipTrack())); // END TEST CODE + + INFO(gui, "\n%s", print_branch().c_str()); +} + +std::string +Sequence::print_track() +{ + std::ostringstream os; + + os << "Sequence\t\"" << get_name() << "\""; + + return os.str(); } } // namespace model diff --git a/src/gui/model/sequence.hpp b/src/gui/model/sequence.hpp index 02a2869c2..277fbf426 100644 --- a/src/gui/model/sequence.hpp +++ b/src/gui/model/sequence.hpp @@ -43,7 +43,8 @@ class Sequence : public ParentTrack public: Sequence(); - + std::string print_track(); + private: }; diff --git a/src/gui/model/track.cpp b/src/gui/model/track.cpp index 4d49ea2b2..50d50e4b8 100644 --- a/src/gui/model/track.cpp +++ b/src/gui/model/track.cpp @@ -21,6 +21,7 @@ * *****************************************************/ #include "track.hpp" +#include namespace gui { namespace model { @@ -38,17 +39,48 @@ Track::get_child_tracks() const return Track::NoChildren; } -const Glib::ustring +const std::string Track::get_name() const { return name; } void -Track::set_name(const Glib::ustring &name) +Track::set_name(const std::string &name) { this->name = name; } +bool +Track::remove_child_track(const boost::shared_ptr track) +{ + return false; +} + +std::string +Track::print_branch() +{ + return print_branch_recursive(0); +} + +std::string +Track::print_branch_recursive(const unsigned int indentation) +{ + Glib::ustring str; + + for(unsigned int i = 0; i < indentation; i++) + str += " "; + str += print_track(); + str += "\n"; + + BOOST_FOREACH(boost::shared_ptr track, get_child_tracks()) + { + REQUIRE(track); + str += track->print_branch_recursive(indentation + 1); + } + + return str; +} + } // namespace model } // namespace gui diff --git a/src/gui/model/track.hpp b/src/gui/model/track.hpp index f57b1d254..42c6e890b 100644 --- a/src/gui/model/track.hpp +++ b/src/gui/model/track.hpp @@ -41,13 +41,24 @@ public: virtual std::list< boost::shared_ptr > get_child_tracks() const; - const Glib::ustring get_name() const; + const std::string get_name() const; - void set_name(const Glib::ustring &name); - + void set_name(const std::string &name); + + virtual bool remove_child_track(const boost::shared_ptr track); + + std::string print_branch(); + + virtual std::string print_track() = 0; + + + +protected: + std::string print_branch_recursive(const unsigned int indentation); + private: //----- Data -----// - Glib::ustring name; + std::string name; protected: static const std::list< boost::shared_ptr > NoChildren; diff --git a/src/gui/output/displayer.cpp b/src/gui/output/displayer.cpp index a7676c8dd..482db826e 100644 --- a/src/gui/output/displayer.cpp +++ b/src/gui/output/displayer.cpp @@ -74,10 +74,10 @@ Displayer::calculateVideoLayout( video_x = ( widget_width - video_width ) / 2; video_y = ( widget_height - video_height ) / 2; - ENSURE(video_x >= 0 && video_x < widget_width) - ENSURE(video_y >= 0 && video_y < widget_height) - ENSURE(video_width <= widget_width) - ENSURE(video_width <= widget_width) + ENSURE(video_x >= 0 && video_x < widget_width); + ENSURE(video_y >= 0 && video_y < widget_height); + ENSURE(video_width <= widget_width); + ENSURE(video_width <= widget_width); } } // namespace output diff --git a/src/gui/output/displayer.hpp b/src/gui/output/displayer.hpp index 235de278f..5dc576565 100644 --- a/src/gui/output/displayer.hpp +++ b/src/gui/output/displayer.hpp @@ -33,99 +33,97 @@ namespace gui { namespace output { -#define MAX_WIDTH 720 -#define MAX_HEIGHT 576 +/** + * Supported Displayer formats + **/ +typedef enum { + DISPLAY_NONE, + DISPLAY_YUV, + DISPLAY_RGB, + DISPLAY_BGR, + DISPLAY_BGR0, + DISPLAY_RGB16 +} +DisplayerInput; - /** Supported Displayer formats +/** + * A Displayer is a class which is responsible for rendering an image + * in some way (ie: Xvideo, GDK, OpenGL etc). + * + * @remarks All Displayer classes must extend the Displayer class and + * minimally rewrite: + * + * + usable() - to indicate if the object can be used, + * + format() - to indicate what type of input the put method expects + * + put( void * ) - deal with an image of the expected type and size + * + * By default, all images will be delivered to the put method in a + * resolution of IMG_WIDTH * IMG_HEIGHT. If another size is required, + * then the rewrite the methods: + * + * + preferredWidth + * + preferredHeight + * + * If the widget being written to doesn't need a fixed size, then + * rewrite the two other put methods as required. + */ +class Displayer +{ +public: + + /** + * Indicates if this object can be used to render images on the + * running system. */ - typedef enum { - DISPLAY_NONE, - DISPLAY_YUV, - DISPLAY_RGB, - DISPLAY_BGR, - DISPLAY_BGR0, - DISPLAY_RGB16 - } - DisplayerInput; + virtual bool usable(); + + /** + * Indicates the format required by the abstract put method. + */ + virtual DisplayerInput format(); + + /** + * Expected width of input to put. + */ + virtual int preferredWidth(); + + /** + * Expected height of input to put. + */ + virtual int preferredHeight(); /** - * A Displayer is a class which is responsible for rendering an image - * in some way (ie: Xvideo, GDK, OpenGL etc). - * - * @remarks All Displayer classes must extend the Displayer class and - * minimally rewrite: - * - * + usable() - to indicate if the object can be used, - * + format() - to indicate what type of input the put method expects - * + put( void * ) - deal with an image of the expected type and size - * - * By default, all images will be delivered to the put method in a - * resolution of IMG_WIDTH * IMG_HEIGHT. If another size is required, - * then the rewrite the methods: - * - * + preferredWidth - * + preferredHeight - * - * If the widget being written to doesn't need a fixed size, then - * rewrite the two other put methods as required. + * Put an image of a given width and height with the expected input + * format (as indicated by the format method). */ - class Displayer - { - public: + virtual void put( const void* ) = 0; - /** - * Indicates if an object can be used to render images on the - * running system. - */ - virtual bool usable(); - - /** - * Indicates the format required by the abstract put method. - */ - virtual DisplayerInput format(); - - /** - * Expected width of input to put. - */ - virtual int preferredWidth(); - - /** - * Expected height of input to put. - */ - virtual int preferredHeight(); - - /** - * Put an image of a given width and height with the expected input - * format (as indicated by the format method). - */ - virtual void put( void * ) = 0; - - protected: - - /** - * Calculates the coordinates for placing a video image inside a - * widget - * - * @param[in] widget_width The width of the display widget. - * @param[in] widget_height The height of the display widget. - * @param[in] image_width The width of the video image. - * @param[in] image_height The height of the video image. - * @param[out] video_x The x-coordinate of the top left - * corner of the scaled video image. - * @param[out] video_y The y-coordinate of the top left - * corner of the scaled video image. - * @param[out] video_width The width of the scale video image. - * @param[out] video_height The height of the scale video image. - */ - static void calculateVideoLayout( - int widget_width, int widget_height, - int image_width, int image_height, - int &video_x, int &video_y, int &video_width, int &video_height ); +protected: - protected: - int imageWidth; - int imageHeight; - }; + /** + * Calculates the coordinates for placing a video image inside a + * widget + * + * @param[in] widget_width The width of the display widget. + * @param[in] widget_height The height of the display widget. + * @param[in] image_width The width of the video image. + * @param[in] image_height The height of the video image. + * @param[out] video_x The x-coordinate of the top left + * corner of the scaled video image. + * @param[out] video_y The y-coordinate of the top left + * corner of the scaled video image. + * @param[out] video_width The width of the scale video image. + * @param[out] video_height The height of the scale video image. + */ + static void calculateVideoLayout( + int widget_width, int widget_height, + int image_width, int image_height, + int &video_x, int &video_y, int &video_width, int &video_height ); + +protected: + int imageWidth; + int imageHeight; +}; } // namespace output } // namespace gui diff --git a/src/gui/output/gdkdisplayer.cpp b/src/gui/output/gdkdisplayer.cpp index 0f83ceac4..523ed2bb5 100644 --- a/src/gui/output/gdkdisplayer.cpp +++ b/src/gui/output/gdkdisplayer.cpp @@ -36,49 +36,49 @@ namespace output { GdkDisplayer::GdkDisplayer( Gtk::Widget *drawing_area, int width, int height ) : drawingArea( drawing_area ) - { - REQUIRE(drawing_area != NULL); - REQUIRE(width > 0); - REQUIRE(height > 0); - - imageWidth = width, imageHeight = height; - } +{ + REQUIRE(drawing_area != NULL); + REQUIRE(width > 0); + REQUIRE(height > 0); + + imageWidth = width, imageHeight = height; +} bool GdkDisplayer::usable() - { - return true; - } +{ + return true; +} void -GdkDisplayer::put( void *image ) - { - int video_x = 0, video_y = 0, video_width = 0, video_height = 0; - calculateVideoLayout( - drawingArea->get_width(), - drawingArea->get_height(), - preferredWidth(), preferredHeight(), - video_x, video_y, video_width, video_height ); +GdkDisplayer::put( const void* image ) +{ + int video_x = 0, video_y = 0, video_width = 0, video_height = 0; + calculateVideoLayout( + drawingArea->get_width(), + drawingArea->get_height(), + preferredWidth(), preferredHeight(), + video_x, video_y, video_width, video_height ); - GdkWindow *window = drawingArea->get_window()->gobj(); - REQUIRE(window != NULL); - - GdkGC *gc = gdk_gc_new( window ); - REQUIRE(gc != NULL); - - GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data( (const guchar*)image, GDK_COLORSPACE_RGB, FALSE, 8, - preferredWidth(), preferredHeight(), preferredWidth() * 3, NULL, NULL ); - REQUIRE(pixbuf != NULL); - - GdkPixbuf *scaled_image = gdk_pixbuf_scale_simple( pixbuf, video_width, video_height, GDK_INTERP_NEAREST ); - REQUIRE(scaled_image != NULL); - - gdk_draw_pixbuf( window, gc, scaled_image, 0, 0, video_x, video_y, -1, -1, GDK_RGB_DITHER_NORMAL, 0, 0 ); + GdkWindow *window = drawingArea->get_window()->gobj(); + REQUIRE(window != NULL); - g_object_unref( scaled_image ); - g_object_unref( pixbuf ); - g_object_unref( gc ); - } + GdkGC *gc = gdk_gc_new( window ); + REQUIRE(gc != NULL); + + GdkPixbuf *pixbuf = gdk_pixbuf_new_from_data( (const guchar*)image, GDK_COLORSPACE_RGB, FALSE, 8, + preferredWidth(), preferredHeight(), preferredWidth() * 3, NULL, NULL ); + REQUIRE(pixbuf != NULL); + + GdkPixbuf *scaled_image = gdk_pixbuf_scale_simple( pixbuf, video_width, video_height, GDK_INTERP_NEAREST ); + REQUIRE(scaled_image != NULL); + + gdk_draw_pixbuf( window, gc, scaled_image, 0, 0, video_x, video_y, -1, -1, GDK_RGB_DITHER_NORMAL, 0, 0 ); + + g_object_unref( scaled_image ); + g_object_unref( pixbuf ); + g_object_unref( gc ); +} } // namespace output } // namespace gui diff --git a/src/gui/output/gdkdisplayer.hpp b/src/gui/output/gdkdisplayer.hpp index 8e74d4404..76bb66d0f 100644 --- a/src/gui/output/gdkdisplayer.hpp +++ b/src/gui/output/gdkdisplayer.hpp @@ -40,19 +40,48 @@ namespace Gtk { namespace gui { namespace output { +/** + * GdkDisplayer is a class which is responsible for rendering a video + * image via GDK. + **/ class GdkDisplayer : public Displayer - { - public: - GdkDisplayer( Gtk::Widget *drawing_area, int width, int height ); +{ +public: - void put( void *image ); - - protected: - bool usable(); + /** + * Constructor + * @param[in] drawing_area The widget into which the video image will + * be drawn. This value must not be NULL. + * @param[in] width The width of the video image in pixels. This value + * must be greater than zero. + * @param[in] height The height of the video image in pixels. This + * value must be greater than zero. + **/ + GdkDisplayer( Gtk::Widget *drawing_area, int width, int height ); - private: - Gtk::Widget *drawingArea; - }; + /** + * Put an image of a given width and height with the expected input + * format (as indicated by the format method). + * @param[in] image The video image array to draw. + */ + void put( const void* image ); + +protected: + + /** + * Indicates if this object can be used to render images on the + * running system. + */ + bool usable(); + +private: + + /** + * The widget that video will be drawn into. + * @remarks This value must be a valid pointer. + **/ + Gtk::Widget *drawingArea; +}; } // namespace output } // namespace gui diff --git a/src/gui/output/xvdisplayer.cpp b/src/gui/output/xvdisplayer.cpp index 13a692a40..ade6973e5 100644 --- a/src/gui/output/xvdisplayer.cpp +++ b/src/gui/output/xvdisplayer.cpp @@ -32,193 +32,195 @@ namespace gui { namespace output { XvDisplayer::XvDisplayer( Gtk::Widget *drawing_area, int width, int height ) : - xvImage( NULL ), + gotPort( false ), drawingArea( drawing_area ), - gotPort( false ) - { - INFO(gui, "Trying XVideo at %d x %d", width, height); + xvImage( NULL ) +{ + REQUIRE(drawing_area != NULL); + REQUIRE(width > 0); + REQUIRE(height > 0); + + INFO(gui, "Trying XVideo at %d x %d", width, height); - imageWidth = width, imageHeight = height; + imageWidth = width, imageHeight = height; - shmInfo.shmaddr = NULL; + shmInfo.shmaddr = NULL; - Glib::RefPtr area_window = drawing_area->get_window(); + Glib::RefPtr area_window = drawing_area->get_window(); - window = gdk_x11_drawable_get_xid( area_window->gobj() ); - display = gdk_x11_drawable_get_xdisplay( area_window->gobj() ); + window = gdk_x11_drawable_get_xid( area_window->gobj() ); + display = gdk_x11_drawable_get_xdisplay( area_window->gobj() ); - unsigned int count; - XvAdaptorInfo *adaptorInfo; + unsigned int count; + XvAdaptorInfo *adaptorInfo; - if ( XvQueryAdaptors( display, window, &count, &adaptorInfo ) == Success ) - { + if ( XvQueryAdaptors( display, window, &count, &adaptorInfo ) == Success ) + { - INFO(gui, "XvQueryAdaptors count: %d", count); - for ( unsigned int n = 0; gotPort == false && n < count; ++n ) - { - // Diagnostics - INFO(gui, "%s, %d, %d, %d", adaptorInfo[ n ].name, - adaptorInfo[ n ].base_id, adaptorInfo[ n ].num_ports - 1); + INFO(gui, "XvQueryAdaptors count: %d", count); + for ( unsigned int n = 0; gotPort == false && n < count; ++n ) + { + // Diagnostics + INFO(gui, "%s, %d, %d, %d", adaptorInfo[ n ].name, + adaptorInfo[ n ].base_id, adaptorInfo[ n ].num_ports - 1); - for ( port = adaptorInfo[ n ].base_id; - port < adaptorInfo[ n ].base_id + adaptorInfo[ n ].num_ports; - port ++ ) + for ( unsigned int port = adaptorInfo[ n ].base_id; + port < adaptorInfo[ n ].base_id + adaptorInfo[ n ].num_ports; + port ++ ) + { + if ( XvGrabPort( display, port, CurrentTime ) == Success ) + { + int formats; + XvImageFormatValues *list; + + list = XvListImageFormats( display, port, &formats ); + + INFO(gui, "formats supported: %d", formats); + + for ( int i = 0; i < formats; i ++ ) + { + INFO(gui, "0x%x (%c%c%c%c) %s", + list[ i ].id, + ( list[ i ].id ) & 0xff, + ( list[ i ].id >> 8 ) & 0xff, + ( list[ i ].id >> 16 ) & 0xff, + ( list[ i ].id >> 24 ) & 0xff, + ( list[ i ].format == XvPacked ) ? "packed" : "planar" ); + if ( list[ i ].id == 0x32595559 && !gotPort ) + gotPort = true; + } + + if ( !gotPort ) + { + XvUngrabPort( display, port, CurrentTime ); + } + else + { + grabbedPort = port; + break; + } + } + } + } + + if ( gotPort ) + { + int num; + unsigned int unum; + XvEncodingInfo *enc; + + XvQueryEncodings( display, grabbedPort, &unum, &enc ); + for ( unsigned int index = 0; index < unum; index ++ ) + { + INFO(gui, "%d: %s, %ldx%ld rate = %d/%d", index, enc->name, + enc->width, enc->height, enc->rate.numerator, + enc->rate.denominator ); + } + + XvAttribute *xvattr = XvQueryPortAttributes( display, grabbedPort, &num ); + for ( int k = 0; k < num; k++ ) + { + if ( xvattr[k].flags & XvSettable ) + { + if ( strcmp( xvattr[k].name, "XV_AUTOPAINT_COLORKEY") == 0 ) + { + Atom val_atom = XInternAtom( display, xvattr[k].name, False ); + if ( XvSetPortAttribute( display, grabbedPort, val_atom, 1 ) != Success ) + ERROR(gui, "Couldn't set Xv attribute %s\n", xvattr[k].name); + } + else if ( strcmp( xvattr[k].name, "XV_COLORKEY") == 0 ) + { + Atom val_atom = XInternAtom( display, xvattr[k].name, False ); + if ( XvSetPortAttribute( display, grabbedPort, val_atom, 0x010102 ) != Success ) + ERROR(gui, "Couldn't set Xv attribute %s\n", xvattr[k].name); + } + } + } + } + + if ( gotPort ) + { + XGCValues values; + memset(&values, 0, sizeof(XGCValues)); + gc = XCreateGC( display, window, 0, NULL ); + + xvImage = ( XvImage * ) XvShmCreateImage( display, grabbedPort, 0x32595559, 0, width, height, &shmInfo ); + + shmInfo.shmid = shmget( IPC_PRIVATE, xvImage->data_size, IPC_CREAT | 0777 ); + if (shmInfo.shmid < 0) { + perror("shmget"); + gotPort = false; + } + else + { + shmInfo.shmaddr = ( char * ) shmat( shmInfo.shmid, 0, 0 ); + xvImage->data = shmInfo.shmaddr; + shmInfo.readOnly = 0; + if ( !XShmAttach( gdk_display, &shmInfo ) ) { - if ( XvGrabPort( display, port, CurrentTime ) == Success ) - { - int formats; - XvImageFormatValues *list; - - list = XvListImageFormats( display, port, &formats ); - - INFO(gui, "formats supported: %d", formats); - - for ( int i = 0; i < formats; i ++ ) - { - INFO(gui, "0x%x (%c%c%c%c) %s", - list[ i ].id, - ( list[ i ].id ) & 0xff, - ( list[ i ].id >> 8 ) & 0xff, - ( list[ i ].id >> 16 ) & 0xff, - ( list[ i ].id >> 24 ) & 0xff, - ( list[ i ].format == XvPacked ) ? "packed" : "planar" ); - if ( list[ i ].id == 0x32595559 && !gotPort ) - gotPort = true; - } - - if ( !gotPort ) - { - XvUngrabPort( display, port, CurrentTime ); - } - else - { - grabbedPort = port; - break; - } - } - } - } - - if ( gotPort ) - { - int num; - unsigned int unum; - XvEncodingInfo *enc; - - XvQueryEncodings( display, grabbedPort, &unum, &enc ); - for ( unsigned int index = 0; index < unum; index ++ ) - { - INFO(gui, "%d: %s, %ldx%ld rate = %d/%d", index, enc->name, - enc->width, enc->height, enc->rate.numerator, - enc->rate.denominator ); - } - - XvAttribute *xvattr = XvQueryPortAttributes( display, port, &num ); - for ( int k = 0; k < num; k++ ) - { - if ( xvattr[k].flags & XvSettable ) - { - if ( strcmp( xvattr[k].name, "XV_AUTOPAINT_COLORKEY") == 0 ) - { - Atom val_atom = XInternAtom( display, xvattr[k].name, False ); - if ( XvSetPortAttribute( display, port, val_atom, 1 ) != Success ) - ERROR(gui, "Couldn't set Xv attribute %s\n", xvattr[k].name); - } - else if ( strcmp( xvattr[k].name, "XV_COLORKEY") == 0 ) - { - Atom val_atom = XInternAtom( display, xvattr[k].name, False ); - if ( XvSetPortAttribute( display, port, val_atom, 0x010102 ) != Success ) - ERROR(gui, "Couldn't set Xv attribute %s\n", xvattr[k].name); - } - } - } - } - - if ( gotPort ) - { - gc = XCreateGC( display, window, 0, &values ); - - xvImage = ( XvImage * ) XvShmCreateImage( display, port, 0x32595559, 0, width, height, &shmInfo ); - - shmInfo.shmid = shmget( IPC_PRIVATE, xvImage->data_size, IPC_CREAT | 0777 ); - if (shmInfo.shmid < 0) { - perror("shmget"); gotPort = false; } - else - { - shmInfo.shmaddr = ( char * ) shmat( shmInfo.shmid, 0, 0 ); - xvImage->data = shmInfo.shmaddr; - shmInfo.readOnly = 0; - if ( !XShmAttach( gdk_display, &shmInfo ) ) - { - gotPort = false; - } - XSync( display, false ); - shmctl( shmInfo.shmid, IPC_RMID, 0 ); -#if 0 - xvImage = ( XvImage * ) XvCreateImage( display, port, 0x32595559, pix, width , height ); -#endif - } - } - } - else - { - gotPort = false; - } - } + XSync( display, false ); + shmctl( shmInfo.shmid, IPC_RMID, 0 ); + } + } + } + else + { + gotPort = false; + } +} XvDisplayer::~XvDisplayer() - { - ERROR(gui, "Destroying XV Displayer"); +{ + ERROR(gui, "Destroying XV Displayer"); - if ( gotPort ) - { - XvUngrabPort( display, grabbedPort, CurrentTime ); - } + if ( gotPort ) + { + XvUngrabPort( display, grabbedPort, CurrentTime ); + } - //if ( xvImage != NULL ) - // XvStopVideo( display, port, window ); - - if ( shmInfo.shmaddr != NULL ) - { - XShmDetach( display, &shmInfo ); - shmctl( shmInfo.shmid, IPC_RMID, 0 ); - shmdt( shmInfo.shmaddr ); - } - - if ( xvImage != NULL ) - XFree( xvImage ); - } + if ( shmInfo.shmaddr != NULL ) + { + XShmDetach( display, &shmInfo ); + shmctl( shmInfo.shmid, IPC_RMID, 0 ); + shmdt( shmInfo.shmaddr ); + } + + if ( xvImage != NULL ) + XFree( xvImage ); +} bool XvDisplayer::usable() - { - return gotPort; - } +{ + return gotPort; +} void -XvDisplayer::put( void *image ) - { - REQUIRE(image != NULL); - REQUIRE(drawingArea != NULL); - - if(xvImage != NULL) - { - int video_x = 0, video_y = 0, video_width = 0, video_height = 0; - calculateVideoLayout( - drawingArea->get_width(), - drawingArea->get_height(), - preferredWidth(), preferredHeight(), - video_x, video_y, video_width, video_height ); +XvDisplayer::put( const void* image ) +{ + REQUIRE(image != NULL); + REQUIRE(drawingArea != NULL); + + if(xvImage != NULL) + { + REQUIRE(display != NULL); + + int video_x = 0, video_y = 0, video_width = 0, video_height = 0; + calculateVideoLayout( + drawingArea->get_width(), + drawingArea->get_height(), + preferredWidth(), preferredHeight(), + video_x, video_y, video_width, video_height ); - memcpy( xvImage->data, image, xvImage->data_size ); + memcpy( xvImage->data, image, xvImage->data_size ); - XvShmPutImage( display, port, window, gc, xvImage, - 0, 0, preferredWidth(), preferredHeight(), - video_x, video_y, video_width, video_height, false ); - } - } + XvShmPutImage( display, grabbedPort, window, gc, xvImage, + 0, 0, preferredWidth(), preferredHeight(), + video_x, video_y, video_width, video_height, false ); + } +} } // namespace output } // namespace gui diff --git a/src/gui/output/xvdisplayer.hpp b/src/gui/output/xvdisplayer.hpp index c43db099e..afc25c670 100644 --- a/src/gui/output/xvdisplayer.hpp +++ b/src/gui/output/xvdisplayer.hpp @@ -46,30 +46,90 @@ namespace Gtk { namespace gui { namespace output { - class XvDisplayer : public Displayer - { - public: - XvDisplayer( Gtk::Widget *drawing_area, int width, int height ); - ~XvDisplayer(); - - void put( void *image ); +/** + * XvDisplayer is a class which is responsible for rendering a video + * image via XVideo. + **/ +class XvDisplayer : public Displayer +{ +public: + /** + * Constructor + * @param drawing_area The widget into which the video image will be + * drawn. This value must not be NULL. + * @param width The width of the video image in pixels. This value + * must be greater than zero. + * @param height The height of the video image in pixels. This value + * must be greater than zero. + **/ + XvDisplayer( Gtk::Widget *drawing_area, int width, int height ); - protected: - bool usable(); + /** + * Destructor + **/ + ~XvDisplayer(); - private: - bool gotPort; - int grabbedPort; - Gtk::Widget *drawingArea; - Display *display; - Window window; - GC gc; - XGCValues values; - XvImage *xvImage; - unsigned int port; - XShmSegmentInfo shmInfo; - char pix[ MAX_WIDTH * MAX_HEIGHT * 4 ]; - }; + /** + * Put an image of a given width and height with the expected input + * format (as indicated by the format method). + * @param[in] image The video image array to draw. + */ + void put( const void* image ); + + /** + * Indicates if this object can be used to render images on the + * running system. + */ + bool usable(); + +private: + + /** + * Specifies whether the object is currently attached to an XVideo + * port. + * @remarks This value is false until the constructor has finished + * successfully. + **/ + bool gotPort; + + /** + * The current port being used. + * @remarks This value is meaninless unless gotPort is true. + **/ + unsigned int grabbedPort; + + /** + * The widget that video will be drawn into. + * @remarks This value must be a valid pointer. + **/ + Gtk::Widget *drawingArea; + + /** + * The display that video will be drawn into. + **/ + Display *display; + + /** + * The X11 window that video will be drawn into. + **/ + Window window; + + /** + * The graphics context which will be used when rednering video. + **/ + GC gc; + + /** + * The shared memory image object which video will be written into. + **/ + XvImage *xvImage; + + /** + * Info about the shared memory segment. + * @remarks shmInfo.shmaddr is set to NULL, when the SHM is detached. + **/ + XShmSegmentInfo shmInfo; +}; } // namespace output } // namespace gui diff --git a/src/gui/panels/timeline-panel.cpp b/src/gui/panels/timeline-panel.cpp index 03cdc8e2f..f1ca132d2 100644 --- a/src/gui/panels/timeline-panel.cpp +++ b/src/gui/panels/timeline-panel.cpp @@ -45,6 +45,7 @@ const int TimelinePanel::ZoomToolSteps = 2; // 2 seems comfortable TimelinePanel::TimelinePanel(model::Project *const owner_project) : Panel(owner_project, "timeline", _("Timeline"), "panel_timeline"), + timeIndicator(), previousButton(Stock::MEDIA_PREVIOUS), rewindButton(Stock::MEDIA_REWIND), playPauseButton(Stock::MEDIA_PLAY), @@ -55,7 +56,6 @@ TimelinePanel::TimelinePanel(model::Project *const owner_project) : iBeamTool(Gtk::StockID("tool_i_beam")), zoomIn(Stock::ZOOM_IN), zoomOut(Stock::ZOOM_OUT), - timeIndicator(), updatingToolbar(false), currentTool(timeline::IBeam) { @@ -191,7 +191,7 @@ TimelinePanel::on_page_switched(GtkNotebookPage*, guint) void TimelinePanel::on_mouse_hover(gavl_time_t time) { - + (void)time; } void diff --git a/src/gui/widgets/timeline-widget.cpp b/src/gui/widgets/timeline-widget.cpp index 7f375fae2..9859a3def 100644 --- a/src/gui/widgets/timeline-widget.cpp +++ b/src/gui/widgets/timeline-widget.cpp @@ -36,6 +36,7 @@ namespace widgets { const int TimelineWidget::TrackPadding = 1; const int TimelineWidget::HeaderWidth = 150; +const int TimelineWidget::HeaderIndentWidth = 10; const double TimelineWidget::ZoomIncrement = 1.25; const int64_t TimelineWidget::MaxScale = 30000000; @@ -43,25 +44,25 @@ TimelineWidget::TimelineWidget( shared_ptr source_sequence) : Table(2, 2), sequence(source_sequence), + layoutHelper(*this), viewWindow(this, 0, 1), - totalHeight(0), - horizontalAdjustment(0, 0, 0), - verticalAdjustment(0, 0, 0), selectionStart(0), selectionEnd(0), playbackPeriodStart(0), playbackPeriodEnd(0), playbackPoint(GAVL_TIME_UNDEFINED), + horizontalAdjustment(0, 0, 0), + verticalAdjustment(0, 0, 0), horizontalScroll(horizontalAdjustment), verticalScroll(verticalAdjustment) { REQUIRE(sequence); - body = new TimelineBody(this); + body = new TimelineBody(*this); ENSURE(body != NULL); - headerContainer = new TimelineHeaderContainer(this); + headerContainer = new TimelineHeaderContainer(*this); ENSURE(headerContainer != NULL); - ruler = new TimelineRuler(this); + ruler = new TimelineRuler(*this); ENSURE(ruler != NULL); horizontalAdjustment.signal_value_changed().connect( sigc::mem_fun( @@ -295,22 +296,9 @@ TimelineWidget::update_tracks() // Create timeline tracks from all the model tracks create_timeline_tracks(); - // Update the header container - REQUIRE(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, - sequence->get_child_tracks()) - { - REQUIRE(track); - totalHeight += measure_branch_height(track); - } + // Update the layout helper + layoutHelper.clone_tree_from_sequence(); + layoutHelper.update_layout(); } void @@ -323,6 +311,10 @@ TimelineWidget::create_timeline_tracks() BOOST_FOREACH(shared_ptr child, sequence->get_child_tracks()) create_timeline_tracks_from_branch(child); + + // Update the header container + REQUIRE(headerContainer != NULL); + headerContainer->update_headers(); } void @@ -359,7 +351,7 @@ TimelineWidget::create_timeline_track_from_model_track( *this, model_track)); else if(typeid(*model_track) == typeid(model::GroupTrack)) return shared_ptr(new timeline::GroupTrack( - *this, model_track)); + *this, dynamic_pointer_cast(model_track))); ASSERT(NULL); // Unknown track type; return shared_ptr(); @@ -387,7 +379,7 @@ TimelineWidget::remove_orphaned_tracks() pair; BOOST_FOREACH( pair, orphan_track_map ) { - ENSURE(pair.first) + ENSURE(pair.first); trackMap.erase(pair.first); } } @@ -415,6 +407,9 @@ TimelineWidget::lookup_timeline_track( shared_ptr model_track) const { REQUIRE(sequence); + REQUIRE(model_track); + REQUIRE(model_track != sequence); // The sequence isn't really a track + std::map, shared_ptr >:: const_iterator iterator = trackMap.find(model_track); if(iterator == trackMap.end()) @@ -430,30 +425,15 @@ TimelineWidget::lookup_timeline_track( return iterator->second; } -boost::shared_ptr -TimelineWidget::lookup_model_track( - const timeline::Track *timeline_track) const +void +TimelineWidget::on_layout_changed() { - REQUIRE(sequence); + REQUIRE(headerContainer != NULL); + REQUIRE(body != NULL); - 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(); -} + headerContainer->on_layout_changed(); + body->queue_draw(); +} void TimelineWidget::update_scroll() @@ -475,7 +455,8 @@ TimelineWidget::update_scroll() // Calculate the vertical length that can be scrolled: // the total height of all the tracks minus one screenful - int y_scroll_length = totalHeight - body_allocation.get_height(); + int y_scroll_length = layoutHelper.get_total_height() - + body_allocation.get_height(); if(y_scroll_length < 0) y_scroll_length = 0; // If by resizing we're now over-scrolled, scroll back to @@ -497,29 +478,6 @@ TimelineWidget::update_scroll() } -int -TimelineWidget::measure_branch_height( - shared_ptr model_track) -{ - REQUIRE(model_track); - - const shared_ptr timeline_track = - lookup_timeline_track(model_track); - ENSURE(timeline_track); - - int height = timeline_track->get_height() + TrackPadding; - - // Recurse through all the children - BOOST_FOREACH( shared_ptr child, - model_track->get_child_tracks() ) - { - REQUIRE(child); - height += measure_branch_height(child); - } - - return height; -} - int TimelineWidget::get_y_scroll_offset() const { diff --git a/src/gui/widgets/timeline-widget.hpp b/src/gui/widgets/timeline-widget.hpp index 41ad86b75..f362faaff 100644 --- a/src/gui/widgets/timeline-widget.hpp +++ b/src/gui/widgets/timeline-widget.hpp @@ -35,6 +35,7 @@ #include "timeline/timeline-ibeam-tool.hpp" #include "timeline/timeline-group-track.hpp" #include "timeline/timeline-clip-track.hpp" +#include "timeline/timeline-layout-helper.hpp" #include "../model/sequence.hpp" @@ -216,24 +217,12 @@ 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 ----- // - void update_scroll(); + void on_layout_changed(); - int measure_branch_height( - boost::shared_ptr model_track); + void update_scroll(); int get_y_scroll_offset() const; @@ -276,6 +265,9 @@ protected: std::map, boost::shared_ptr > trackMap; + + // Helper Classes + timeline::TimelineLayoutHelper layoutHelper; // View State timeline::TimelineViewWindow viewWindow; @@ -289,8 +281,6 @@ protected: boost::shared_ptr hoveringTrack; - int totalHeight; - // Child Widgets timeline::TimelineHeaderContainer *headerContainer; timeline::TimelineBody *body; @@ -318,16 +308,19 @@ public: protected: static const int TrackPadding; static const int HeaderWidth; + static const int HeaderIndentWidth; static const double ZoomIncrement; friend class timeline::TimelineViewWindow; friend class timeline::TimelineBody; friend class timeline::TimelineHeaderContainer; + friend class timeline::TimelineLayoutHelper; friend class timeline::TimelineRuler; friend class timeline::Tool; friend class timeline::ArrowTool; friend class timeline::IBeamTool; friend class timeline::Track; + friend class timeline::GroupTrack; }; } // namespace widgets diff --git a/src/gui/widgets/timeline/timeline-arrow-tool.cpp b/src/gui/widgets/timeline/timeline-arrow-tool.cpp index 52acb661e..c211e3ace 100644 --- a/src/gui/widgets/timeline/timeline-arrow-tool.cpp +++ b/src/gui/widgets/timeline/timeline-arrow-tool.cpp @@ -26,7 +26,7 @@ namespace gui { namespace widgets { namespace timeline { -ArrowTool::ArrowTool(TimelineBody *timeline_body) : +ArrowTool::ArrowTool(TimelineBody &timeline_body) : Tool(timeline_body) { diff --git a/src/gui/widgets/timeline/timeline-arrow-tool.hpp b/src/gui/widgets/timeline/timeline-arrow-tool.hpp index 902fb0a88..74d6f8e5c 100644 --- a/src/gui/widgets/timeline/timeline-arrow-tool.hpp +++ b/src/gui/widgets/timeline/timeline-arrow-tool.hpp @@ -43,7 +43,7 @@ public: * Constructor * @param timeline_body The owner timeline body object */ - ArrowTool(TimelineBody *timeline_body); + ArrowTool(TimelineBody &timeline_body); /** * Gets the type of tool represented by this class diff --git a/src/gui/widgets/timeline/timeline-body.cpp b/src/gui/widgets/timeline/timeline-body.cpp index 7e8125cac..e755ddaad 100644 --- a/src/gui/widgets/timeline/timeline-body.cpp +++ b/src/gui/widgets/timeline/timeline-body.cpp @@ -38,21 +38,18 @@ namespace gui { namespace widgets { namespace timeline { -TimelineBody::TimelineBody(gui::widgets::TimelineWidget - *timeline_widget) : +TimelineBody::TimelineBody(TimelineWidget &timeline_widget) : Glib::ObjectBase("TimelineBody"), tool(NULL), - dragType(None), mouseDownX(0), mouseDownY(0), + dragType(None), beginShiftTimeOffset(0), selectionAlpha(0.5), timelineWidget(timeline_widget) -{ - REQUIRE(timelineWidget != NULL); - +{ // Connect up some events - timelineWidget->get_view_window().changed_signal().connect( + timelineWidget.get_view_window().changed_signal().connect( sigc::mem_fun(this, &TimelineBody::on_update_view) ); // Install style properties @@ -61,42 +58,39 @@ TimelineBody::TimelineBody(gui::widgets::TimelineWidget TimelineBody::~TimelineBody() { - REQUIRE(tool != NULL); - if(tool != NULL) - delete tool; + WARN_IF(!tool, gui, "An invalid tool pointer is unexpected here"); } ToolType TimelineBody::get_tool() const { - REQUIRE(tool != NULL); - if(tool != NULL) - return tool->get_type(); - return gui::widgets::timeline::None; + REQUIRE(tool); + return tool->get_type(); } void TimelineBody::set_tool(timeline::ToolType tool_type) { // Tidy up old tool - if(tool != NULL) + if(tool) { // Do we need to change tools? if(tool->get_type() == tool_type) return; - - delete tool; } // Create the new tool switch(tool_type) { case timeline::Arrow: - tool = new ArrowTool(this); + tool.reset(new ArrowTool(*this)); break; case timeline::IBeam: - tool = new IBeamTool(this); + tool.reset(new IBeamTool(*this)); + break; + + default: break; } @@ -130,7 +124,6 @@ bool TimelineBody::on_expose_event(GdkEventExpose* event) { REQUIRE(event != NULL); - REQUIRE(timelineWidget != NULL); // This is where we draw on the window Glib::RefPtr window = get_window(); @@ -158,9 +151,8 @@ bool TimelineBody::on_scroll_event (GdkEventScroll* event) { REQUIRE(event != NULL); - REQUIRE(timelineWidget != NULL); - TimelineViewWindow &window = timelineWidget->get_view_window(); + TimelineViewWindow &window = timelineWidget.get_view_window(); if(event->state & GDK_CONTROL_MASK) { @@ -174,7 +166,10 @@ TimelineBody::on_scroll_event (GdkEventScroll* event) case GDK_SCROLL_DOWN: // User scrolled down. Zoom out window.zoom_view(event->x, -1); - break; + break; + + default: + break; } } else @@ -189,9 +184,14 @@ TimelineBody::on_scroll_event (GdkEventScroll* event) case GDK_SCROLL_DOWN: // User scrolled down. Shift 1/16th right window.shift_view(16); - break; + break; + + default: + break; } } + + return true; } bool @@ -239,7 +239,7 @@ TimelineBody::on_motion_notify_event(GdkEventMotion *event) { case Shift: { - TimelineViewWindow &window = timelineWidget->get_view_window(); + TimelineViewWindow &window = timelineWidget.get_view_window(); const int64_t scale = window.get_time_scale(); gavl_time_t offset = beginShiftTimeOffset + @@ -250,16 +250,19 @@ TimelineBody::on_motion_notify_event(GdkEventMotion *event) beginShiftVerticalOffset); break; } + + default: + break; } // Forward the event to the tool tool->on_motion_notify_event(event); // See if the track that we're hovering over has changed - shared_ptr new_hovering_track = - track_from_point(event->y); - if(timelineWidget->get_hovering_track() != new_hovering_track) - timelineWidget->set_hovering_track(new_hovering_track); + shared_ptr new_hovering_track( + timelineWidget.layoutHelper.track_from_y(event->y)); + if(timelineWidget.get_hovering_track() != new_hovering_track) + timelineWidget.set_hovering_track(new_hovering_track); // false so that the message is passed up to the owner TimelineWidget return false; @@ -269,39 +272,55 @@ void TimelineBody::draw_tracks(Cairo::RefPtr cr) { REQUIRE(cr); - REQUIRE(timelineWidget != NULL); - REQUIRE(timelineWidget->sequence); + REQUIRE(timelineWidget.sequence); // Prepare + TimelineLayoutHelper &layout_helper = timelineWidget.layoutHelper; + const TimelineLayoutHelper::TrackTree &layout_tree = + layout_helper.get_layout_tree(); const Allocation allocation = get_allocation(); // Save the view matrix Cairo::Matrix view_matrix; cr->get_matrix(view_matrix); - // Translate the view by the scroll distance - cr->translate(0, -get_vertical_offset()); - - // Interate drawing each track - BOOST_FOREACH( shared_ptr model_track, - timelineWidget->sequence->get_child_tracks() ) - draw_track_recursive(cr, model_track, allocation.get_width()); + // Iterate drawing each track + TimelineLayoutHelper::TrackTree::pre_order_iterator iterator; + for(iterator = ++layout_tree.begin(); // ++ so we skip the sequence root + iterator != layout_tree.end(); + iterator++) + { + const shared_ptr model_track(*iterator); + const shared_ptr timeline_track = + timelineWidget.lookup_timeline_track(*iterator); + + optional rect = + layout_helper.get_track_header_rect(timeline_track); + + // Is this track visible? + if(rect) + { + // Translate to the top of the track + cr->set_matrix(view_matrix); + cr->translate(0, rect->get_y()); + + // Draw the track + draw_track(cr, timeline_track, allocation.get_width()); + } + } // Restore the view matrix cr->set_matrix(view_matrix); } void -TimelineBody::draw_track_recursive(Cairo::RefPtr cr, - shared_ptr model_track, const int view_width) const +TimelineBody::draw_track(Cairo::RefPtr cr, + shared_ptr timeline_track, + const int view_width) const { REQUIRE(cr); - REQUIRE(model_track != NULL); - REQUIRE(timelineWidget != NULL); - - shared_ptr timeline_track = timelineWidget-> - lookup_timeline_track(model_track); - + REQUIRE(timeline_track != NULL); + const int height = timeline_track->get_height(); REQUIRE(height >= 0); @@ -313,32 +332,23 @@ TimelineBody::draw_track_recursive(Cairo::RefPtr cr, // Render the track cr->save(); - timeline_track->draw_track(cr, &timelineWidget->get_view_window()); + timeline_track->draw_track(cr, &timelineWidget.get_view_window()); cr->restore(); - - // Shift for the next track - cr->translate(0, height + TimelineWidget::TrackPadding); - - // Recurse drawing into children - BOOST_FOREACH( shared_ptr child, - model_track->get_child_tracks() ) - draw_track_recursive(cr, child, view_width); } void TimelineBody::draw_selection(Cairo::RefPtr cr) { REQUIRE(cr); - REQUIRE(timelineWidget != NULL); // Prepare const Allocation allocation = get_allocation(); - const TimelineViewWindow &window = timelineWidget->get_view_window(); + const TimelineViewWindow &window = timelineWidget.get_view_window(); const int start_x = window.time_to_x( - timelineWidget->get_selection_start()); + timelineWidget.get_selection_start()); const int end_x = window.time_to_x( - timelineWidget->get_selection_end()); + timelineWidget.get_selection_end()); // Draw the cover if(end_x > 0 && start_x < allocation.get_width()) @@ -377,16 +387,15 @@ void TimelineBody::draw_playback_point(Cairo::RefPtr cr) { REQUIRE(cr); - REQUIRE(timelineWidget != NULL); // Prepare const Allocation allocation = get_allocation(); - const gavl_time_t point = timelineWidget->get_playback_point(); - if(point == GAVL_TIME_UNDEFINED) + const gavl_time_t point = timelineWidget.get_playback_point(); + if(point == (gavl_time_t)GAVL_TIME_UNDEFINED) return; - const int x = timelineWidget->get_view_window().time_to_x(point); + const int x = timelineWidget.get_view_window().time_to_x(point); // Set source gdk_cairo_set_source_color(cr->cobj(), &playbackPointColour); @@ -406,74 +415,20 @@ TimelineBody::begin_shift_drag() { dragType = Shift; beginShiftTimeOffset = - timelineWidget->get_view_window().get_time_offset(); + timelineWidget.get_view_window().get_time_offset(); beginShiftVerticalOffset = get_vertical_offset(); } int TimelineBody::get_vertical_offset() const { - return (int)timelineWidget->verticalAdjustment.get_value(); + return (int)timelineWidget.verticalAdjustment.get_value(); } void TimelineBody::set_vertical_offset(int offset) { - timelineWidget->verticalAdjustment.set_value(offset); -} - -shared_ptr -TimelineBody::track_from_point(const int y) const -{ - REQUIRE(timelineWidget != NULL); - REQUIRE(timelineWidget->sequence); - - int offset = -get_vertical_offset(); - - BOOST_FOREACH( shared_ptr model_track, - timelineWidget->sequence->get_child_tracks() ) - { - shared_ptr result = track_from_branch( - model_track, y, offset); - if(result) - return result; - } - - // No track has been found with this point in it - return boost::shared_ptr(); -} - -shared_ptr TimelineBody::track_from_branch( - shared_ptr model_track, - const int y, int &offset) const -{ - REQUIRE(timelineWidget != NULL); - - shared_ptr timeline_track = timelineWidget-> - lookup_timeline_track(model_track); - - const int height = timeline_track->get_height(); - REQUIRE(height >= 0); - - // Does the point fall in this track? - if(offset <= y && y < offset + height) - return timeline_track; - - // Add the height of this track to the accumulation - offset += height; - - // Recurse drawing into children - BOOST_FOREACH( shared_ptr child, - model_track->get_child_tracks() ) - { - shared_ptr result = - track_from_branch(child, y, offset); - if(result != NULL) - return result; - } - - // No track has been found in this branch - return shared_ptr(); + timelineWidget.verticalAdjustment.set_value(offset); } void diff --git a/src/gui/widgets/timeline/timeline-body.hpp b/src/gui/widgets/timeline/timeline-body.hpp index 2489ee7de..683a59dec 100644 --- a/src/gui/widgets/timeline/timeline-body.hpp +++ b/src/gui/widgets/timeline/timeline-body.hpp @@ -54,9 +54,10 @@ public: /** * Constructor - * @param timeline_widget The owner widget of this ruler. + * @param timeline_widget A reference to the owner widget of this + * ruler. */ - TimelineBody(gui::widgets::TimelineWidget *timeline_widget); + TimelineBody(gui::widgets::TimelineWidget &timeline_widget); /** * Destructor @@ -118,8 +119,9 @@ private: */ void draw_tracks(Cairo::RefPtr cr); - void draw_track_recursive(Cairo::RefPtr cr, - boost::shared_ptr track, const int view_width) const; + void draw_track(Cairo::RefPtr cr, + boost::shared_ptr timeline_track, + const int view_width) const; /** * Draws the selected timeline period. @@ -138,14 +140,7 @@ private: int get_vertical_offset() const; void set_vertical_offset(int offset); - - boost::shared_ptr track_from_point(const int y) - const; - - boost::shared_ptr track_from_branch( - boost::shared_ptr model_track, - const int y, int &offset) const; - + /** * Registers all the styles that this class will respond to. */ @@ -165,7 +160,7 @@ private: Shift }; - timeline::Tool *tool; + boost::scoped_ptr tool; double mouseDownX, mouseDownY; // Scroll State @@ -179,7 +174,7 @@ private: float selectionAlpha; GdkColor playbackPointColour; - gui::widgets::TimelineWidget* const timelineWidget; + gui::widgets::TimelineWidget &timelineWidget; friend class Tool; friend class ArrowTool; diff --git a/src/gui/widgets/timeline/timeline-group-track.cpp b/src/gui/widgets/timeline/timeline-group-track.cpp index 4449a3227..56ad56c0a 100644 --- a/src/gui/widgets/timeline/timeline-group-track.cpp +++ b/src/gui/widgets/timeline/timeline-group-track.cpp @@ -21,24 +21,39 @@ * *****************************************************/ #include "timeline-group-track.hpp" +#include "../timeline-widget.hpp" using namespace Gtk; +using namespace boost; +using namespace sigc; namespace gui { namespace widgets { namespace timeline { GroupTrack::GroupTrack(TimelineWidget &timeline_widget, - boost::shared_ptr track) : + shared_ptr track) : Track(timeline_widget, track) +{ + REQUIRE(track); + + // Receive notifications of changes to the tracks + track->get_child_track_list().signal_changed().connect( + sigc::mem_fun( this, &GroupTrack::on_child_list_changed ) ); +} + +void +GroupTrack::on_child_list_changed() { + timelineWidget.on_track_list_changed(); } void GroupTrack::draw_track(Cairo::RefPtr cairo, TimelineViewWindow* const window) const { - + (void)cairo; + (void)window; } } // namespace timeline diff --git a/src/gui/widgets/timeline/timeline-group-track.hpp b/src/gui/widgets/timeline/timeline-group-track.hpp index 6530503c2..821acf9a8 100644 --- a/src/gui/widgets/timeline/timeline-group-track.hpp +++ b/src/gui/widgets/timeline/timeline-group-track.hpp @@ -37,11 +37,14 @@ class GroupTrack : public timeline::Track { public: GroupTrack(TimelineWidget &timeline_widget, - boost::shared_ptr track); + boost::shared_ptr track); void draw_track(Cairo::RefPtr cairo, TimelineViewWindow* constwindow) const; + +protected: + void on_child_list_changed(); }; } // namespace timeline diff --git a/src/gui/widgets/timeline/timeline-header-container.cpp b/src/gui/widgets/timeline/timeline-header-container.cpp index d9c15fc90..54ad4f0f7 100644 --- a/src/gui/widgets/timeline/timeline-header-container.cpp +++ b/src/gui/widgets/timeline/timeline-header-container.cpp @@ -37,14 +37,12 @@ namespace widgets { namespace timeline { TimelineHeaderContainer::TimelineHeaderContainer( - gui::widgets::TimelineWidget *timeline_widget) : + gui::widgets::TimelineWidget &timeline_widget) : Glib::ObjectBase("TimelineHeaderContainer"), timelineWidget(timeline_widget), margin(-1), expand_button_size(12) { - REQUIRE(timeline_widget != NULL); - // This widget will not have a window at first set_flags(Gtk::NO_WINDOW); @@ -52,12 +50,12 @@ TimelineHeaderContainer::TimelineHeaderContainer( // Connect to the timeline widget's vertical scroll event, // so that we get notified when the view shifts - timelineWidget->verticalAdjustment.signal_value_changed().connect( + timelineWidget.verticalAdjustment.signal_value_changed().connect( sigc::mem_fun(this, &TimelineHeaderContainer::on_scroll) ); // Connect to the timeline widget's hover event, // so that we get notified when tracks are hovered on - timelineWidget->hovering_track_changed_signal().connect( + timelineWidget.hovering_track_changed_signal().connect( sigc::mem_fun(this, &TimelineHeaderContainer::on_hovering_track_changed) ); @@ -69,14 +67,24 @@ TimelineHeaderContainer::TimelineHeaderContainer( // Install style properties register_styles(); + + // Load the styles up + read_styles(); } void TimelineHeaderContainer::update_headers() -{ - // Add fresh headers - BOOST_FOREACH( shared_ptr model_track, get_tracks() ) - set_parent_recursive(model_track); +{ + // Ensure headers are parented correctly + pair, shared_ptr > pair; + BOOST_FOREACH( pair, timelineWidget.trackMap ) + { + REQUIRE(pair.second); + Widget &widget = pair.second->get_header_widget(); + if(widget.get_parent() == NULL) // Is the header unparented? + widget.set_parent(*this); + ENSURE(widget.get_parent() == this); + } } void @@ -152,8 +160,9 @@ bool TimelineHeaderContainer::on_button_press_event ( case 3: // Right Click { // Popup the context menu - shared_ptr header = header_from_point( - Gdk::Point(event->x, event->y)); + shared_ptr header( + timelineWidget.layoutHelper.header_from_point( + Gdk::Point(event->x, event->y))); // Are we hovering on a header? if(header) @@ -181,9 +190,11 @@ bool TimelineHeaderContainer::on_button_release_event ( if(clickedExpander != NULL) { // Yes? The toggle the expanding - clickedExpander->set_expanded(!clickedExpander->get_expanded()); + clickedExpander->expand_collapse( + clickedExpander->get_expanded() ? Track::Collapse : Track::Expand); clickedExpander.reset(); - layout_headers(); + + timelineWidget.layoutHelper.update_layout(); } return Container::on_button_release_event(event); @@ -213,8 +224,20 @@ TimelineHeaderContainer::on_size_request (Requisition* requisition) // We don't care about the size of all the child widgets, but if we // don't send the size request down the tree, some widgets fail to // calculate their text layout correctly. - BOOST_FOREACH( shared_ptr model_track, get_tracks() ) - size_request_recursive(model_track); + + const TimelineLayoutHelper::TrackTree &layout_tree = + timelineWidget.layoutHelper.get_layout_tree(); + + TimelineLayoutHelper::TrackTree::pre_order_iterator iterator; + for(iterator = ++layout_tree.begin(); // ++ so that we skip the sequence root + iterator != layout_tree.end(); + iterator++) + { + Widget &widget = + lookup_timeline_track(*iterator)->get_header_widget(); + if(widget.is_visible()) + widget.size_request(); + } // Initialize the output parameter: *requisition = Gtk::Requisition(); @@ -246,35 +269,43 @@ TimelineHeaderContainer::forall_vfunc(gboolean /* include_internals */, { REQUIRE(callback != NULL); - BOOST_FOREACH( shared_ptr track, get_tracks() ) + pair, shared_ptr > pair; + BOOST_FOREACH( pair, timelineWidget.trackMap ) { - REQUIRE(track); - forall_vfunc_recursive(track, callback, callback_data); + REQUIRE(pair.second); + callback(pair.second->get_header_widget().gobj(), callback_data); } } void -TimelineHeaderContainer::on_remove(Widget* widget) +TimelineHeaderContainer::on_remove(Widget*) { // Do nothing - this is just to keep Gtk::Container happy } +void +TimelineHeaderContainer::on_layout_changed() +{ + layout_headers(); +} + bool TimelineHeaderContainer::on_expose_event(GdkEventExpose *event) { if(gdkWindow) { - // Start at an offset from the scroll offset - int offset = -timelineWidget->get_y_scroll_offset(); - const Allocation container_allocation = get_allocation(); - - read_styles(); - // Paint a border underneath all the root headers - BOOST_FOREACH( shared_ptr model_track, - get_tracks() ) + // Paint a border underneath all the headers + const TimelineLayoutHelper::TrackTree &layout_tree = + timelineWidget.layoutHelper.get_layout_tree(); + + TimelineLayoutHelper::TrackTree::pre_order_iterator iterator; + for(iterator = ++layout_tree.begin(); // ++ so that we skip the sequence root + iterator != layout_tree.end(); + iterator++) { + shared_ptr model_track = *iterator; REQUIRE(model_track); draw_header_decoration(model_track, @@ -299,145 +330,66 @@ void TimelineHeaderContainer::on_hovering_track_changed( boost::shared_ptr hovering_track) { + (void)hovering_track; + // The hovering track has changed, redraw so we can light the header queue_draw(); } void TimelineHeaderContainer::layout_headers() -{ +{ + REQUIRE(margin >= 0); // read_styles must have been called before now + // We can't layout before the widget has been set up if(!gdkWindow) return; - - // Make sure the style are loaded - read_styles(); - // Clear previously cached layout - headerBoxes.clear(); - - // Start at minus-the-scroll offset - int offset = -timelineWidget->get_y_scroll_offset(); - - const Allocation container_allocation = get_allocation(); - const int header_width = container_allocation.get_width(); + TimelineLayoutHelper &layout_helper = + timelineWidget.layoutHelper; + const TimelineLayoutHelper::TrackTree &layout_tree = + layout_helper.get_layout_tree(); - BOOST_FOREACH( shared_ptr model_track, get_tracks() ) - layout_headers_recursive( - model_track, offset, header_width, 0, true); + TimelineLayoutHelper::TrackTree::pre_order_iterator iterator; + for(iterator = ++layout_tree.begin(); // ++ so that we skip the sequence root + iterator != layout_tree.end(); + iterator++) + { + const shared_ptr timeline_track = + lookup_timeline_track(*iterator); + + Widget &widget = timeline_track->get_header_widget(); + + optional header_rect = + layout_helper.get_track_header_rect(timeline_track); + + if(header_rect) + { + REQUIRE(header_rect->get_width() >= 0); + REQUIRE(header_rect->get_height() >= 0); + + // Calculate the allocation of the header widget + Allocation header_allocation( + header_rect->get_x() + margin + expand_button_size, // x + header_rect->get_y() + margin, // y + max( header_rect->get_width() - expand_button_size - + margin * 2, 0 ), // width + header_rect->get_height() - margin * 2); // height + + // Apply the allocation to the header + widget.size_allocate (header_allocation); + if(!widget.is_visible()) + widget.show(); + } + else // No header rect, so the track must be hidden + if(widget.is_visible()) + widget.hide(); + } // Repaint the background of our parenting queue_draw (); } -void -TimelineHeaderContainer::layout_headers_recursive( - shared_ptr model_track, int &offset, - const int header_width, const int depth, bool parent_expanded) -{ - REQUIRE(depth >= 0); - REQUIRE(model_track != NULL); - - shared_ptr timeline_track = - lookup_timeline_track(model_track); - - const int indent = depth * 10; - Widget &widget = timeline_track->get_header_widget(); - - if(parent_expanded) - { - const int track_height = timeline_track->get_height(); - - // Calculate the box of the header - Gdk::Rectangle header_box( - indent, // x - offset, // y - max( header_width - indent, 0 ), // width - track_height); // height - REQUIRE(header_box.get_height() >= 0); - - // Cache the bounding box - headerBoxes[timeline_track] = header_box; - - // Calculate the allocation of the header widget - Allocation header_allocation( - header_box.get_x() + margin + expand_button_size, // x - header_box.get_y() + margin, // y - max( header_box.get_width() - expand_button_size - - margin * 2, 0 ), // width - header_box.get_height() - margin * 2); // height - - // Apply the allocation to the header - widget.size_allocate (header_allocation); - if(!widget.is_visible()) - widget.show(); - - // Offset for the next header - offset += track_height + TimelineWidget::TrackPadding; - } - else - if(widget.is_visible()) - widget.hide(); - - // Recurse through all the children - BOOST_FOREACH( boost::shared_ptr child, - model_track->get_child_tracks() ) - layout_headers_recursive( - child, offset, header_width, depth + 1, - timeline_track->get_expanded() && parent_expanded); -} - -void -TimelineHeaderContainer::set_parent_recursive( - boost::shared_ptr model_track) -{ - // Set the header's parent widget - Widget &widget = lookup_timeline_track(model_track)-> - get_header_widget(); - - const Container *parent = widget.get_parent(); - if(parent == NULL) // Is the header unparented? - widget.set_parent(*this); - else if(parent != this) // The header is parented by another container - widget.reparent(*this); - - // Recurse through all the children - BOOST_FOREACH( shared_ptr child, - model_track->get_child_tracks() ) - set_parent_recursive(child); -} - -void -TimelineHeaderContainer::size_request_recursive( - shared_ptr const model_track) -{ - Widget &widget = - lookup_timeline_track(model_track)->get_header_widget(); - if(widget.is_visible()) - widget.size_request(); - - // Recurse through all the children - BOOST_FOREACH( shared_ptr child, - model_track->get_child_tracks() ) - size_request_recursive(child); -} - -void -TimelineHeaderContainer::forall_vfunc_recursive( - shared_ptr model_track, GtkCallback callback, - gpointer callback_data) -{ - REQUIRE(callback != NULL); - - callback( lookup_timeline_track(model_track)-> - get_header_widget().gobj(), callback_data) ; - - // Recurse through all the children - BOOST_FOREACH( shared_ptr child, - model_track->get_child_tracks() ) - forall_vfunc_recursive(child, callback, callback_data); -} - void TimelineHeaderContainer::draw_header_decoration( shared_ptr model_track, @@ -453,102 +405,90 @@ TimelineHeaderContainer::draw_header_decoration( shared_ptr timeline_track = lookup_timeline_track(model_track); - // Get the cached header box - weak_ptr ptr(timeline_track); - REQUIRE(contains(headerBoxes, ptr)); - const Gdk::Rectangle &box = headerBoxes[timeline_track]; + // Get the header box + const optional &optional_box = + timelineWidget.layoutHelper.get_track_header_rect(timeline_track); + if(!optional_box) + return; + const Gdk::Rectangle box = *optional_box; - // Paint the box, if it will be visible - if(box.get_x() < clip_rect.get_width() && - box.get_height() > 0 && - box.get_y() + box.get_height() > clip_rect.get_y() && - box.get_y() < clip_rect.get_y() + clip_rect.get_height()) - { - // Use paint_box to draw a themed bevel around the header - style->paint_box(gdkWindow, STATE_NORMAL, - SHADOW_OUT, clip_rect, *this, "", - box.get_x(), box.get_y(), - box.get_width(), box.get_height()); - - // Paint the expander if there are child tracks - StateType state_type = STATE_NORMAL; - if(clickedExpander == timeline_track) - state_type = STATE_SELECTED; - else if(hoveringExpander == timeline_track) - state_type = STATE_PRELIGHT; - - const ExpanderStyle expander_style = - timeline_track->get_expanded() ? - EXPANDER_EXPANDED : EXPANDER_COLLAPSED; - - if(!model_track->get_child_tracks().empty()) - style->paint_expander (gdkWindow, - state_type, - clip_rect, *this, "", - box.get_x() + expand_button_size / 2 + margin, - box.get_y() + box.get_height() / 2, - expander_style); - } - - // Recurse through all the children - if(timeline_track->get_expanded()) - BOOST_FOREACH( shared_ptr child, - model_track->get_child_tracks() ) - draw_header_decoration(child, clip_rect); -} + // Don't paint the box, if it won't be visible + if(box.get_x() >= clip_rect.get_width() || + box.get_height() <= 0 || + box.get_y() + box.get_height() <= clip_rect.get_y() || + box.get_y() >= clip_rect.get_y() + clip_rect.get_height()) + return; -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; - } + // Use paint_box to draw a themed bevel around the header + style->paint_box(gdkWindow, STATE_NORMAL, + SHADOW_OUT, clip_rect, *this, "", + box.get_x(), box.get_y(), + box.get_width(), box.get_height()); - return shared_ptr(); + // Paint the expander if there are child tracks + StateType state_type = STATE_NORMAL; + if(clickedExpander == timeline_track) + state_type = STATE_SELECTED; + else if(hoveringExpander == timeline_track) + state_type = STATE_PRELIGHT; + + if(!model_track->get_child_tracks().empty()) + style->paint_expander (gdkWindow, + state_type, + clip_rect, *this, "", + box.get_x() + expand_button_size / 2 + margin, + box.get_y() + box.get_height() / 2, + timeline_track->get_expander_style()); } shared_ptr TimelineHeaderContainer::expander_button_from_point( const Gdk::Point &point) -{ - std::pair, Gdk::Rectangle> pair; - BOOST_FOREACH( pair, headerBoxes ) +{ + const TimelineLayoutHelper::TrackTree &layout_tree = + timelineWidget.layoutHelper.get_layout_tree(); + + TimelineLayoutHelper::TrackTree::pre_order_iterator iterator; + for(iterator = ++layout_tree.begin(); // ++ so we skip the sequence root + iterator != layout_tree.end(); + iterator++) { - // Hit test the rectangle - const Gdk::Rectangle rect = - get_expander_button_rectangle(pair.first); + const shared_ptr timeline_track = + lookup_timeline_track(*iterator); - 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; + // Hit test the rectangle + const optional rect = + get_expander_button_rectangle(timeline_track); + + if(rect) + { + 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 timeline_track; + } } return shared_ptr(); } -const Gdk::Rectangle +const optional TimelineHeaderContainer::get_expander_button_rectangle( shared_ptr track) { REQUIRE(track != NULL); - weak_ptr ptr(track); - REQUIRE(contains(headerBoxes, ptr)); - const Gdk::Rectangle &box = headerBoxes[track]; - return Gdk::Rectangle( - margin + box.get_x(), margin + box.get_y(), - expand_button_size, box.get_height() - margin * 2); + optional box = + timelineWidget.layoutHelper.get_track_header_rect(track); + if(box) + { + return optional(Gdk::Rectangle( + margin + box->get_x(), margin + box->get_y(), + expand_button_size, box->get_height() - margin * 2)); + } + + return optional(); } shared_ptr @@ -556,23 +496,14 @@ TimelineHeaderContainer::lookup_timeline_track( shared_ptr model_track) { REQUIRE(model_track != NULL); - REQUIRE(timelineWidget != NULL); shared_ptr timeline_track = - timelineWidget->lookup_timeline_track(model_track); + timelineWidget.lookup_timeline_track(model_track); ENSURE(timeline_track); return timeline_track; } -const std::list< boost::shared_ptr > -TimelineHeaderContainer::get_tracks() const -{ - REQUIRE(timelineWidget != NULL); - REQUIRE(timelineWidget->sequence); - return timelineWidget->sequence->get_child_tracks(); -} - void TimelineHeaderContainer::register_styles() const { @@ -593,7 +524,13 @@ void TimelineHeaderContainer::read_styles() { if(margin <= 0) - get_style_property("heading_margin", margin); + { + get_style_property("heading_margin", margin); + margin = max(margin, 0); + } + else + WARN(gui, "TimelineHeaderContainer::read_styles()" + " should only be called once"); } } // namespace timeline diff --git a/src/gui/widgets/timeline/timeline-header-container.hpp b/src/gui/widgets/timeline/timeline-header-container.hpp index 517ed3bce..23c75eb37 100644 --- a/src/gui/widgets/timeline/timeline-header-container.hpp +++ b/src/gui/widgets/timeline/timeline-header-container.hpp @@ -1,5 +1,5 @@ /* - timeline-header-container.cpp - Declaration of the timeline + timeline-header-container.hpp - Declaration of the timeline header container widget Copyright (C) Lumiera.org @@ -55,9 +55,10 @@ public: /** * Constructor * - * @param[in] timeline_widget A pointer to the owner timeline widget + * @param[in] timeline_widget A reference to the owner timeline widget */ - TimelineHeaderContainer(gui::widgets::TimelineWidget* timeline_widget); + TimelineHeaderContainer( + gui::widgets::TimelineWidget &timeline_widget); /** * Attaches the header all the header widgets of root @@ -123,11 +124,13 @@ private: * An event handler that is called when a widget is removed from the * container. **/ - void on_remove(Widget* widget); + void on_remove(Widget*); /* ===== Events ===== */ private: + void on_layout_changed(); + /** * An event handler for when the window must be redrawn. */ @@ -149,45 +152,7 @@ private: * stacking etc. */ void layout_headers(); - - /** - * Recursively lays out all the controls in the header widget. - * @param track The parent track object which will be recursed into. - * @param offset A shared value used to accumulate the y-offset of - * header widgets. - * @param header_width The width of this widget in pixels. - * @param depth The depth within the tree of track. - **/ - void layout_headers_recursive(boost::shared_ptr track, - int &offset, const int header_width, const int depth, - bool parent_expanded); - - /** - * Recursively sets all the track header widgets to be child widgets - * of this widget. - * @param track The parent track object which will be recursed into. - **/ - void set_parent_recursive(boost::shared_ptr const - model_track); - - /** - * Recursively causes all the visible track header widgets in a branch - * to call size_request( ). - **/ - void size_request_recursive( - boost::shared_ptr model_track); - - /** - * Recursively calls a callback on all the header widgets in a branch. - * @param model_track The root track of the branch. - * @param callback The callback to apply to the branch. This includes - * model_track and all it's children. - * @param callback_data The user data parameter for the callback. - **/ - void forall_vfunc_recursive( - boost::shared_ptr model_track, - GtkCallback callback, gpointer callback_data); - + /** * Draws the border decoration around the track header. * @param model_track The track to draw the decoration for. @@ -199,9 +164,6 @@ 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 @@ -219,7 +181,7 @@ private: * @param track The track to get the expander button rectangle of. * @return Returns the rectangle of the expander button of track. **/ - const Gdk::Rectangle get_expander_button_rectangle( + const boost::optional get_expander_button_rectangle( boost::shared_ptr track); /** @@ -234,17 +196,6 @@ private: **/ boost::shared_ptr lookup_timeline_track( boost::shared_ptr model_track); - -/** - * A helper function which calls get_tracks within the sequence of the - * parent timeline widget, but also applies lots of data consistency - * checks in the process. - * @param model_track The model track to look up in the parent widget. - * @return Returns the track found, or returns NULL if no matching - * track was found. - **/ - const std::list< boost::shared_ptr > - get_tracks() const; /** * Registers all the styles that this class will respond to. @@ -261,7 +212,7 @@ private: /** * The owner TimelineWidget of which this class is a helper */ - gui::widgets::TimelineWidget* const timelineWidget; + gui::widgets::TimelineWidget &timelineWidget; /** * The widget's window object. @@ -278,16 +229,7 @@ private: * click is not processed by track headers. **/ Gtk::Menu contextMenu; - - /** - * A map of tracks to the rectangles of their headers. - * @remarks This map is used as a cache, so that the rectangles don't - * need to be perpetually recalculated. This cache is regenerated by - * the layout_headers method. - **/ - std::map, Gdk::Rectangle> - headerBoxes; - + //----- User Interaction State -----// boost::shared_ptr hoveringExpander; @@ -307,6 +249,7 @@ private: **/ int expand_button_size; + friend class gui::widgets::TimelineWidget; friend class timeline::Track; }; diff --git a/src/gui/widgets/timeline/timeline-ibeam-tool.cpp b/src/gui/widgets/timeline/timeline-ibeam-tool.cpp index e736b6d3a..144fe9f11 100644 --- a/src/gui/widgets/timeline/timeline-ibeam-tool.cpp +++ b/src/gui/widgets/timeline/timeline-ibeam-tool.cpp @@ -37,11 +37,11 @@ const int IBeamTool::ScrollSlideEventInterval = 40; // ===== Implementation ===== // -IBeamTool::IBeamTool(TimelineBody *timeline_body) : +IBeamTool::IBeamTool(TimelineBody &timeline_body) : + Tool(timeline_body), dragType(None), pinnedDragTime(0), - scrollSlideRate(0), - Tool(timeline_body) + scrollSlideRate(0) { } @@ -70,6 +70,8 @@ IBeamTool::get_cursor() const return Gdk::Cursor(Gdk::LEFT_SIDE); case GrabEnd: return Gdk::Cursor(Gdk::RIGHT_SIDE); + default: + break; } // Are we hovering over the ends of the selection? @@ -88,32 +90,31 @@ IBeamTool::on_button_press_event(GdkEventButton* event) { Tool::on_button_press_event(event); - TimelineWidget *timeline_widget = get_timeline_widget(); - REQUIRE(timeline_widget != NULL); + TimelineWidget &timeline_widget = get_timeline_widget(); if(event->button == 1) { const gavl_time_t time = - get_timeline_widget()->get_view_window().x_to_time(event->x); + timeline_widget.get_view_window().x_to_time(event->x); if(is_mouse_in_start_drag_zone()) { // User began to drag the start of the selection dragType = GrabStart; - pinnedDragTime = timeline_widget->get_selection_end(); + pinnedDragTime = timeline_widget.get_selection_end(); } else if(is_mouse_in_end_drag_zone()) { // User began to drag the end of the selection dragType = GrabEnd; - pinnedDragTime = timeline_widget->get_selection_start(); + pinnedDragTime = timeline_widget.get_selection_start(); } else { // User began the drag in clear space, begin a Select drag dragType = Selection; pinnedDragTime = time; - timeline_widget->set_selection(time, time); + timeline_widget.set_selection(time, time); } } } @@ -172,7 +173,7 @@ IBeamTool::on_motion_notify_event(GdkEventMotion *event) bool IBeamTool::on_scroll_slide_timer() { - get_timeline_widget()->get_view_window().shift_view(scrollSlideRate); + get_timeline_widget().get_view_window().shift_view(scrollSlideRate); // Return true to keep the timer going return true; @@ -181,17 +182,16 @@ IBeamTool::on_scroll_slide_timer() void IBeamTool::set_leading_x(const int x) { - TimelineWidget *timeline_widget = get_timeline_widget(); - REQUIRE(timeline_widget != NULL); + TimelineWidget &timeline_widget = get_timeline_widget(); const bool set_playback_period = dragType == Selection; const gavl_time_t time = - timeline_widget->get_view_window().x_to_time(x); + timeline_widget.get_view_window().x_to_time(x); if(time > pinnedDragTime) - timeline_widget->set_selection( + timeline_widget.set_selection( pinnedDragTime, time, set_playback_period); else - timeline_widget->set_selection( + timeline_widget.set_selection( time, pinnedDragTime, set_playback_period); } @@ -216,10 +216,10 @@ IBeamTool::end_scroll_slide() bool IBeamTool::is_mouse_in_start_drag_zone() const { - TimelineWidget *timeline_widget = get_timeline_widget(); + TimelineWidget &timeline_widget = get_timeline_widget(); - const int start_x = timeline_widget->get_view_window().time_to_x( - timeline_widget->get_selection_start()); + const int start_x = timeline_widget.get_view_window().time_to_x( + timeline_widget.get_selection_start()); return (mousePoint.get_x() <= start_x && mousePoint.get_x() > start_x - DragZoneWidth); @@ -228,10 +228,10 @@ IBeamTool::is_mouse_in_start_drag_zone() const bool IBeamTool::is_mouse_in_end_drag_zone() const { - TimelineWidget *timeline_widget = get_timeline_widget(); + TimelineWidget &timeline_widget = get_timeline_widget(); - const int end_x = timeline_widget->get_view_window().time_to_x( - timeline_widget->get_selection_end()); + const int end_x = timeline_widget.get_view_window().time_to_x( + timeline_widget.get_selection_end()); return (mousePoint.get_x() >= end_x && mousePoint.get_x() < end_x + DragZoneWidth); diff --git a/src/gui/widgets/timeline/timeline-ibeam-tool.hpp b/src/gui/widgets/timeline/timeline-ibeam-tool.hpp index 93dcd9f60..b1ee49378 100644 --- a/src/gui/widgets/timeline/timeline-ibeam-tool.hpp +++ b/src/gui/widgets/timeline/timeline-ibeam-tool.hpp @@ -44,7 +44,7 @@ public: * Constructor * @param timeline_body The owner timeline body object */ - IBeamTool(TimelineBody *timeline_body); + IBeamTool(TimelineBody &timeline_body); /** * Gets the type of tool represented by this class diff --git a/src/gui/widgets/timeline/timeline-layout-helper.cpp b/src/gui/widgets/timeline/timeline-layout-helper.cpp new file mode 100644 index 000000000..aec2e0a61 --- /dev/null +++ b/src/gui/widgets/timeline/timeline-layout-helper.cpp @@ -0,0 +1,290 @@ +/* + timeline-layout-helper.cpp - Implementation of the timeline + layout helper class + + 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-layout-helper.hpp" +#include "../timeline-widget.hpp" +#include "../../model/sequence.hpp" + +using namespace Gtk; +using namespace std; +using namespace boost; +using namespace lumiera; +using namespace util; + +namespace gui { +namespace widgets { +namespace timeline { + +TimelineLayoutHelper::TimelineLayoutHelper(TimelineWidget &owner) : + timelineWidget(owner), + totalHeight(0), + animating(false) +{ +} + +void +TimelineLayoutHelper::clone_tree_from_sequence() +{ + const shared_ptr &sequence = timelineWidget.sequence; + REQUIRE(sequence); + + layoutTree.clear(); + TrackTree::iterator_base iterator = layoutTree.set_head(sequence); + add_branch(iterator, sequence); +} + +TimelineLayoutHelper::TrackTree& +TimelineLayoutHelper::get_layout_tree() +{ + return layoutTree; +} + +void +TimelineLayoutHelper::add_branch( + TrackTree::iterator_base parent_iterator, + shared_ptr parent) +{ + BOOST_FOREACH(shared_ptr child, + parent->get_child_tracks()) + { + TrackTree::iterator_base child_iterator = + layoutTree.append_child(parent_iterator, child); + add_branch(child_iterator, child); + } +} + +optional +TimelineLayoutHelper::get_track_header_rect( + boost::weak_ptr track) +{ + if(contains(headerBoxes, track)) + { + Gdk::Rectangle rect(headerBoxes[track]); + rect.set_y(rect.get_y() - timelineWidget.get_y_scroll_offset()); + return optional(rect); + } + return optional(); +} + +shared_ptr +TimelineLayoutHelper::header_from_point(Gdk::Point point) +{ + // Apply the scroll offset + point.set_y(point.get_y() + timelineWidget.get_y_scroll_offset()); + + // Search the headers + 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 shared_ptr(pair.first); + } + + // No track was found - return an empty pointer + return shared_ptr(); +} + +boost::shared_ptr +TimelineLayoutHelper::track_from_y(int y) +{ + // Apply the scroll offset + y += timelineWidget.get_y_scroll_offset(); + + // Search the tracks + std::pair, Gdk::Rectangle> pair; + BOOST_FOREACH( pair, headerBoxes ) + { + // Hit test the rectangle + const Gdk::Rectangle &rect = pair.second; + if(y >= rect.get_y() && y < rect.get_y() + rect.get_height()) + return shared_ptr(pair.first); + } + + // No track was found - return an empty pointer + return shared_ptr(); +} + +int +TimelineLayoutHelper::get_total_height() const +{ + ENSURE(totalHeight >= 0); + return totalHeight; +} + +bool +TimelineLayoutHelper::is_animating() const +{ + return animating; +} + +void +TimelineLayoutHelper::update_layout() +{ + // Reset the animation state value, before it gets recalculated + animating = false; + + // Clear previously cached layout + headerBoxes.clear(); + + // Do the layout + const int header_width = TimelineWidget::HeaderWidth; + const int indent_width = TimelineWidget::HeaderIndentWidth; + totalHeight = layout_headers_recursive(layoutTree.begin(), + 0, header_width, indent_width, 0, true); + + // Signal that the layout has changed + timelineWidget.on_layout_changed(); + + // Begin animating as necessary + if(animating && !animationTimer) + begin_animation(); +} + +int +TimelineLayoutHelper::layout_headers_recursive( + TrackTree::iterator_base parent_iterator, + const int branch_offset, const int header_width, + const int indent_width, const int depth, const bool parent_expanded) +{ + REQUIRE(depth >= 0); + + int child_offset = 0; + + TrackTree::sibling_iterator iterator; + for(iterator = layoutTree.begin(parent_iterator); + iterator != layoutTree.end(parent_iterator); + iterator++) + { + const shared_ptr &model_track = *iterator; + REQUIRE(model_track); + + shared_ptr timeline_track = + lookup_timeline_track(model_track); + + // Is the track going to be shown? + if(parent_expanded) + { + // Calculate and store the box of the header + const int track_height = timeline_track->get_height(); + const int indent = depth * indent_width; + + headerBoxes[timeline_track] = Gdk::Rectangle( + indent, // x + branch_offset + child_offset, // y + max( header_width - indent, 0 ), // width + track_height); // height + + // Offset for the next header + child_offset += track_height + TimelineWidget::TrackPadding; + } + + // Is the track animating? + const bool is_track_animating = + timeline_track->is_expand_animating(); + animating |= is_track_animating; + + // Recurse to children + const bool expand_child = + (animating || timeline_track->get_expanded()) + && parent_expanded; + + int child_branch_height = layout_headers_recursive( + iterator, branch_offset + child_offset, + header_width, indent_width, depth + 1, expand_child); + + // Do collapse animation as necessary + if(is_track_animating) + { + // Calculate the height of te area which will be + // shown as expanded + const float a = timeline_track->get_expand_animation_state(); + child_branch_height *= a * a; + const int y_limit = + branch_offset + child_offset + child_branch_height; + + // Obscure tracks according to the animation state + TrackTree::pre_order_iterator descendant_iterator(iterator); + descendant_iterator++; + TrackTree::sibling_iterator end(iterator); + end++; + + for(descendant_iterator = layoutTree.begin(parent_iterator); + descendant_iterator != end; + descendant_iterator++) + { + const weak_ptr track = + lookup_timeline_track(*descendant_iterator); + const Gdk::Rectangle &rect = headerBoxes[track]; + + if(rect.get_y() + rect.get_height() > y_limit) + headerBoxes.erase(track); + } + + // Tick the track expand animation + timeline_track->tick_expand_animation(); + } + + child_offset += child_branch_height; + } + + return child_offset; +} + +shared_ptr +TimelineLayoutHelper::lookup_timeline_track( + shared_ptr model_track) +{ + REQUIRE(model_track != NULL); + shared_ptr timeline_track = + timelineWidget.lookup_timeline_track(model_track); + ENSURE(timeline_track); + + return timeline_track; +} + +void +TimelineLayoutHelper::begin_animation() +{ + animationTimer = Glib::signal_idle().connect( + sigc::mem_fun(this, &TimelineLayoutHelper::on_animation_tick), + Glib::PRIORITY_DEFAULT); +} + +bool +TimelineLayoutHelper::on_animation_tick() +{ + update_layout(); + return animating; +} + +} // namespace timeline +} // namespace widgets +} // namespace gui diff --git a/src/gui/widgets/timeline/timeline-layout-helper.hpp b/src/gui/widgets/timeline/timeline-layout-helper.hpp new file mode 100644 index 000000000..e533babb4 --- /dev/null +++ b/src/gui/widgets/timeline/timeline-layout-helper.hpp @@ -0,0 +1,251 @@ +/* + timeline-layout-helper.hpp - Declaration of the timeline + layout helper class + + 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. + +*/ +/** @file timeline-layout-helper.cpp + ** This file contains the definition of the layout helpeer class + */ + +#ifndef TIMELINE_LAYOUT_HELPER_HPP +#define TIMELINE_LAYOUT_HELPER_HPP + +#include "../../gtk-lumiera.hpp" +#include "../../../lib/tree.hpp" + +namespace gui { + +namespace model { +class Track; +} + +namespace widgets { + +class TimelineWidget; + +namespace timeline { + +class Track; + +/** + * A helper class for the TimelineWidget. TimelineLayoutHelper + * is a class which calculates the layout of tracks in the timeline + * track tree. + * @see gui::widgets::TimelineWidget + */ +class TimelineLayoutHelper : public boost::noncopyable +{ +public: + /** + * Definition of the layout track tree type. + **/ + typedef lumiera::tree< boost::shared_ptr > TrackTree; + +public: + /** + * Constructor. + * @param owner The timeline widget which is the owner of this helper + * class. + **/ + TimelineLayoutHelper(TimelineWidget &owner); + + /** + * Clones the timelineWidget sequence's track tree to create a layout + * tree which will be identitcal to it. + * @remarks The current layout tree will be deleted and replaced with + * the clone. + * @see add_branch + **/ + void clone_tree_from_sequence(); + + /** + * Gets a reference to the helper's layout tree. + * @return Returns a reference to the helper's layout tree. + **/ + TrackTree& get_layout_tree(); + + /** + * Recalculates the track layout from layoutTree. + * @see layout_headers_recursive + **/ + void update_layout(); + + /** + * Get's the header rectangle of a given timeline track. + * @param[in] track The track which will be looked up. + * @return Returns the rectangle of the header offset by the y-scroll + * offset, or if the track is hidden, or not present in the layout + * tree, an empty optional will be returned. + * @remarks This function is only usable after update_layout() has + * been called on a valid tree of tracks. + * @see update_layout() + **/ + boost::optional get_track_header_rect( + boost::weak_ptr track); + + /** + * Searches for a header which has the specified point inside of it. + * @param[in] point The point to search with. + * @return Returns the header which has been found, or if no header is + * found, an empty shared pointer is returned. + * @remarks The point specified is relative to the scroll offset, so + * y = 0 is the top edge of the scroll view. This function is only + * usable after update_layout() has been called on a valid tree of + * tracks. + * @see update_layout() + **/ + boost::shared_ptr header_from_point( + Gdk::Point point); + + /** + * Searches for a tack which has the specified y-offset inside of it. + * @param[in] y The y-coordinate to search with. + * @return Returns the track which has been found, or if no track is + * found, an empty shared pointer is returned. + * @remarks The point specified is relative to the scroll offset, so + * y = 0 is the top edge of the scroll view. This function is only + * usable after update_layout() has been called on a valid tree of + * tracks. + * @see update_layout() + **/ + boost::shared_ptr track_from_y(int y); + + /** + * Returns the total height in pixels of the layout tree. + * @remarks This function is only on returns a valid value fter + * update_layout() has been called on a valid tree of tracks. + * @see update_layout() + **/ + int get_total_height() const; + + bool is_animating() const; + +protected: + + /** + * A helper function for clone_tree_from_sequence(). This function + * clones a branch within the model tree into the specified point in + * that layout tree. + * @param[in] parent_iterator The iterator of the node in the tree + * which will become the parent of any tracks added. + * @param[in] parent A pointer to the model track whose children + * will be added to the layout tree branch. + * @see clone_tree_from_sequence() + **/ + void add_branch(TrackTree::iterator_base parent_iterator, + boost::shared_ptr parent); + + /** + * Recursively calculates the boxes for a given branch in the timeline + * tree. + * @param[in] parent_iterator The iterator of the parent of the branch + * whose boxes will be laid out. + * @param[in] branch_offset The y-coordinate of the start of this + * branch as measured in pixels from the origin. + * @param[in] header_width The width of the header container widget in + * pixels. + * @param[in] header_width The width of indentation per branch in + * pixels. + * @param[in] depth The depth within the tree of tracks. depth = 0 for + * root tracks. + * @param[in] parent_expanded This value is set to true if all of the + * ancestors of this track, up to the root are expanded and visible, + * false if any of them are collapsed. + * @return Returns the height of the branch in pixels. + * @see update_layout() + **/ + int layout_headers_recursive( + TrackTree::iterator_base parent_iterator, const int branch_offset, + const int header_width, const int indent_width, const int depth, + const bool parent_expanded); + + /** + * A helper function which calls lookup_timeline_track within the + * parent timeline widget, but also applies lots of data consistency + * checks in the process. + * @param model_track The model track to look up in the parent widget. + * @return Returns the track found, or returns NULL if no matching + * track was found. + * @remarks If the return value is going to be NULL, an ENSURE will + * fail. + **/ + boost::shared_ptr lookup_timeline_track( + boost::shared_ptr model_track); + + /** + * A helper function which kicks off the animation timer. + **/ + void begin_animation(); + + /** + * The animation timer tick callback. + **/ + bool on_animation_tick(); + +protected: + + /** + * The owner timeline widget as provided to the constructor. + **/ + TimelineWidget &timelineWidget; + + /** + * The layout tree. + **/ + TrackTree layoutTree; + + /** + * A map of tracks to the rectangles of their headers. + * @remarks This map is used as a cache, so that the rectangles don't + * need to be perpetually recalculated. This cache is regenerated by + * the update_layout method. + * @see update_layout() + **/ + std::map, Gdk::Rectangle> + headerBoxes; + + /** + * The total height of the track tree layout in pixels. This value + * is only valid after layout_headers has been called. + * @see update_layout() + **/ + int totalHeight; + + /** + * The connection to the animation timer. + * @see begin_animation() + * @see on_animation_tick() + **/ + sigc::connection animationTimer; + + /** + * This value is true if the layout animation should continue. + * @remarks This value is recalculated by update_layout() + * @see update_layout() + * @see on_animation_tick() + **/ + bool animating; +}; + +} // namespace timeline +} // namespace widgets +} // namespace gui + +#endif // TIMELINE_LAYOUT_HELPER_HPP diff --git a/src/gui/widgets/timeline/timeline-ruler.cpp b/src/gui/widgets/timeline/timeline-ruler.cpp index bbea35c01..47b47ecef 100644 --- a/src/gui/widgets/timeline/timeline-ruler.cpp +++ b/src/gui/widgets/timeline/timeline-ruler.cpp @@ -42,7 +42,7 @@ namespace widgets { namespace timeline { TimelineRuler::TimelineRuler( - gui::widgets::TimelineWidget *timeline_widget) : + gui::widgets::TimelineWidget &timeline_widget) : Glib::ObjectBase("TimelineRuler"), isDragging(false), pinnedDragTime(0), @@ -61,11 +61,9 @@ TimelineRuler::TimelineRuler( playbackPeriodArrowSize(10), playbackPeriodArrowStemSize(3), timelineWidget(timeline_widget) -{ - REQUIRE(timelineWidget != NULL); - +{ // Connect event handlers - timelineWidget->get_view_window().changed_signal().connect( + timelineWidget.get_view_window().changed_signal().connect( sigc::mem_fun(this, &TimelineRuler::on_update_view) ); // Install style properties @@ -157,7 +155,7 @@ TimelineRuler::on_button_press_event(GdkEventButton* event) if(event->button == 1) { pinnedDragTime = - timelineWidget->get_view_window().x_to_time(event->x); + timelineWidget.get_view_window().x_to_time(event->x); isDragging = true; } @@ -168,12 +166,11 @@ bool TimelineRuler::on_button_release_event(GdkEventButton* event) { REQUIRE(event != NULL); - REQUIRE(timelineWidget != NULL); if(event->button == 1) { isDragging = false; - timelineWidget->on_playback_period_drag_released(); + timelineWidget.on_playback_period_drag_released(); } return true; @@ -214,14 +211,12 @@ TimelineRuler::on_size_allocate(Gtk::Allocation& allocation) void TimelineRuler::set_leading_x(const int x) { - REQUIRE(timelineWidget != NULL); - const gavl_time_t time = - timelineWidget->get_view_window().x_to_time(x); + timelineWidget.get_view_window().x_to_time(x); if(time > pinnedDragTime) - timelineWidget->set_playback_period(pinnedDragTime, time); + timelineWidget.set_playback_period(pinnedDragTime, time); else - timelineWidget->set_playback_period(time, pinnedDragTime); + timelineWidget.set_playback_period(time, pinnedDragTime); } void @@ -231,9 +226,8 @@ TimelineRuler::draw_ruler(Cairo::RefPtr cr, REQUIRE(cr); REQUIRE(ruler_rect.get_width() > 0); REQUIRE(ruler_rect.get_height() > 0); - REQUIRE(timelineWidget != NULL); - const TimelineViewWindow &window = timelineWidget->get_view_window(); + const TimelineViewWindow &window = timelineWidget.get_view_window(); const gavl_time_t left_offset = window.get_time_offset(); const int64_t time_scale = window.get_time_scale(); @@ -334,15 +328,14 @@ TimelineRuler::draw_selection(Cairo::RefPtr cr, REQUIRE(cr); REQUIRE(ruler_rect.get_width() > 0); REQUIRE(ruler_rect.get_height() > 0); - REQUIRE(timelineWidget != NULL); - const TimelineViewWindow &window = timelineWidget->get_view_window(); + const TimelineViewWindow &window = timelineWidget.get_view_window(); Glib::RefPtr