From: Ben Hutchings Date: Wed, 9 Aug 2006 01:42:07 +0000 (+0000) Subject: Renamed package due to name clash. X-Git-Tag: 0.8~10 X-Git-Url: https://git.decadent.org.uk/gitweb/?p=videolink.git;a=commitdiff_plain;h=4b8bff9ce93df43b120c5cf4d1476d3a435f99d3 Renamed package due to name clash. --- diff --git a/COPYING b/COPYING index a115095..d7bc9df 100644 --- a/COPYING +++ b/COPYING @@ -1,11 +1,11 @@ -WebDVD is licenced under the GNU GPL, version 2, with the following +VideoLink is licenced under the GNU GPL, version 2, with the following additions: -A. You may combine and distribute WebDVD and derivative works with +A. You may combine and distribute VideoLink and derivative works with libraries licenced under the GNU LGPL without exercising the option to treat them as licenced under the GPL. -B. When distributing WebDVD or derivative works without source code +B. When distributing VideoLink or derivative works without source code included you must ensure that the documentation retains the notice that "this software is based in part on the work of the Independent JPEG Group", or else remove the code contained in jquant2.c to which that diff --git a/INSTALL b/INSTALL index e272e3d..61da855 100644 --- a/INSTALL +++ b/INSTALL @@ -1,8 +1,8 @@ -Building WebDVD -=============== +Building VideoLink +================== -WebDVD is written in C++ and requires a recent C++ compiler e.g. g++ -3.3. +VideoLink is written in C++ and requires a recent C++ compiler +e.g. g++ 3.3. It requires headers and libraries for Boost, gtkmm, Mozilla and expat. I have developed and tested it with Boost 1.32, gtkmm 2.2.12, Mozilla diff --git a/Makefile b/Makefile index bc26293..671ba39 100644 --- a/Makefile +++ b/Makefile @@ -1,6 +1,6 @@ prefix := /usr/local -webdvd_lib_dir := $(prefix)/lib/webdvd +videolink_lib_dir := $(prefix)/lib/videolink moz_include_dir := \ $(shell pkg-config --variable=prefix mozilla-gtkmozembed)/include/mozilla @@ -33,46 +33,46 @@ endif cxxsources := \ auto_proc.cpp browser_widget.cpp child_iterator.cpp generate_dvd.cpp \ link_iterator.cpp null_prompt_service.cpp pixbufs.cpp style_sheets.cpp \ - temp_file.cpp video.cpp vob_list.cpp webdvd.cpp x_frame_buffer.cpp \ + temp_file.cpp video.cpp vob_list.cpp videolink.cpp x_frame_buffer.cpp \ xml_utils.cpp xpcom_support.cpp csources := jquant2.c -webdvd : $(cxxsources:%.cpp=.objs/%.o) $(csources:%.c=.objs/%.o) +videolink : $(cxxsources:%.cpp=.objs/%.o) $(csources:%.c=.objs/%.o) $(CXX) $(LDFLAGS) -o $@ $^ clean : rm -rf .objs - rm -f webdvd *~ .\#* *.orig *.rej svn-commit*.tmp + rm -f videolink *~ .\#* *.orig *.rej svn-commit*.tmp distclean : clean rm -rf .svn install : - mkdir -p -m 755 $(prefix)/bin $(prefix)/lib/webdvd - install -m 755 -s webdvd $(prefix)/bin - install -m 644 webdvd.css $(prefix)/lib/webdvd + mkdir -p -m 755 $(prefix)/bin $(videolink_lib_dir) + install -m 755 -s videolink $(prefix)/bin + install -m 644 videolink.css $(videolink_lib_dir) .PHONY : clean distclean install .objs/browser_widget.% : CPPFLAGS += -DMOZ_LIB_DIR='"$(moz_lib_dir)"' -.objs/webdvd.% \ - : CPPFLAGS += -DWEBDVD_LIB_DIR='"$(webdvd_lib_dir)"' \ +.objs/videolink.% \ + : CPPFLAGS += -DVIDEOLINK_LIB_DIR='"$(videolink_lib_dir)"' \ -DMOZ_VERSION_MAJOR=$(moz_version_major) \ -DMOZ_VERSION_MINOR=$(moz_version_minor) \ -DMOZ_VERSION_PATCHLEVEL=$(moz_version_patchlevel) .objs/browser_widget.% .objs/generate_dvd.% .objs/pixbufs.% \ -.objs/temp_file.% .objs/vob_list.% .objs/webdvd.% \ +.objs/temp_file.% .objs/vob_list.% .objs/videolink.% \ : CPPFLAGS += $(shell pkg-config --cflags gtkmm-2.0) .objs/browser_widget.% .objs/child_iterator.% .objs/link_iterator.% \ -.objs/null_prompt_service.% .objs/style_sheets.% .objs/webdvd.% \ +.objs/null_prompt_service.% .objs/style_sheets.% .objs/videolink.% \ .objs/xpcom_support.% \ : CPPFLAGS += $(shell pkg-config --cflags mozilla-gtkmozembed) # These dig a bit deeper into Mozilla -.objs/link_iterator.% .objs/style_sheets.% .objs/webdvd.% \ +.objs/link_iterator.% .objs/style_sheets.% .objs/videolink.% \ : CPPFLAGS += $(addprefix -I$(moz_include_dir)/, \ content docshell dom gfx layout necko webshell widget) diff --git a/README b/README index e209fff..dfa4ff5 100644 --- a/README +++ b/README @@ -1,16 +1,19 @@ -WebDVD -====== +VideoLink +========= -WebDVD is intended to provide a simple way of producing DVDs with +VideoLink is intended to provide a simple way of producing DVDs with attractive and usable menus. It converts HTML pages into DVD menus by rendering them in Mozilla and reproducing their link structure. This allows you to design DVDs using familiar HTML editing tools or your favourite text editor. +Prior to version 0.8, VideoLink was called WebDVD, but that name is +also used for an extended DVD format. + Requirements ------------ -WebDVD depends on the following software: +VideoLink depends on the following software: - dvdauthor - expat 1.x @@ -91,19 +94,19 @@ Currently Preview -To get a rough preview of the menus, run "webdvd --preview menu-url" +To get a rough preview of the menus, run "videolink --preview menu-url" where menu-url is the URL or filename of the first page to show. Currently videos cannot be displayed in this preview mode. Processing -To create a DVD filesystem, run "webdvd menu-url output-dir" where +To create a DVD filesystem, run "videolink menu-url output-dir" where menu-url is the URL or filename of the top menu page and output-dir is the directory in which to create the filesystem (which should be -either nonexistent or empty). WebDVD will automatically follow links +either nonexistent or empty). VideoLink will automatically follow links to the other pages and to the video files. -By default, WebDVD now calls ffmpeg to generate MPEG-2 streams for +By default, VideoLink now calls ffmpeg to generate MPEG-2 streams for menus. If you want it to use mjpegtools as it previously did, you must add the option "--encoder mjpegtools". If you use mjpegtools 1.6.2 or earlier you must instead use "--encoder mjpegtools-old". @@ -127,12 +130,12 @@ Limitations ----------- Each page must fit within the frame - DVD players do not support -scrolling menus and WebDVD currently is not able to split them into +scrolling menus and VideoLink currently is not able to split them into multiple menus. The frame size is dictated by the video standard; see above. The exact visible area varies between TVs so the background should cover all or very nearly all the frame whereas the important content such as text must not be placed near the edge. For this -reason WebDVD applies a stylesheet to all pages that adds 60 pixels of +reason VideoLink applies a stylesheet to all pages that adds 60 pixels of padding on all sides of the body; this doesn't apply to the background. @@ -140,28 +143,28 @@ Prior to Mozilla version 1.8, which I have not yet tested, Mozilla may signal that a page is completely loaded before any background images are loaded and displayed. This results in snapshots that do not include background images. You can work around this by using -absolutely-positioned "inline" images, or attempt to build WebDVD +absolutely-positioned "inline" images, or attempt to build VideoLink against Mozilla 1.8. DVD players do not have "back" buttons, so you should generally provide links to "higher" menu pages. However, they do have a button for returning to the top menu. -WebDVD sends a "mouseover" event for each link and sets it into its +VideoLink sends a "mouseover" event for each link and sets it into its "hover" state, then records how this changes its appearance. This change is then shown when the corresponding button on the DVD menu is -highlighted. WebDVD applies a stylesheet which changes the colour of +highlighted. VideoLink applies a stylesheet which changes the colour of text links in the "hover" state, but this has no effect on image links. You must ensure that image links are highlighted in an obvious way when the mouse pointer is over them. The DVD specifications limit each menu to having no more than 36 buttons. In any case, it is poor design to have very large numbers of -buttons on a single menu. WebDVD will warn you if you use more than +buttons on a single menu. VideoLink will warn you if you use more than this number of a links on a page, and will ignore any additional ones. The DVD specification also limits the overlays that are used for -highlighting of buttons to using no more than 4 colours. WebDVD will +highlighting of buttons to using no more than 4 colours. VideoLink will reduce link highlighting to 1 transparent and 3 opaque colours using Floyd-Steinberg dithering, which is certainly good enough for anti-aliased text but may not be so good for complex highlighting. @@ -171,12 +174,12 @@ than this number of video sequences, you could arrange them as chapters of a title, so long as they use the same codecs, resolution, aspect ratio and sample rate. However, each chapter will run into the next. If this is a real problem, let me know, and I may be able to -provide a better solution in a later version of WebDVD. +provide a better solution in a later version of VideoLink. Author and copyright -------------------- -WebDVD was written by Ben Hutchings . +VideoLink was written by Ben Hutchings . Copyright 2005-2006 Ben Hutchings. This software is based in part on the work of the Independent JPEG Group. diff --git a/debian/control b/debian/control index de94297..67b3c3c 100644 --- a/debian/control +++ b/debian/control @@ -1,19 +1,19 @@ -Source: webdvd +Source: videolink Maintainer: Ben Hutchings Section: graphics Priority: extra Build-Depends: debhelper (>=4), libboost-dev, libgtkmm2.0-dev, mozilla-dev, libexpat1-dev Standards-Version: 3.6.2 -Package: webdvd +Package: videolink Architecture: any Depends: xvfb, xfonts-base, dvdauthor, ffmpeg | mjpegtools, netpbm, ${shlibs:Depends}, ${mozilla:Depends} Recommends: mkisofs Description: Converts HTML pages into DVD menus. - WebDVD is intended to provide a simple way of producing DVDs with + VideoLink is intended to provide a simple way of producing DVDs with attractive and usable menus. It converts HTML pages into DVD menus by rendering them in Mozilla and reproducing their link structure. This allows you to design DVDs using familiar HTML editing tools or your favourite text editor. . - Homepage: http://womble.decadent.org.uk/software/webdvd/ + Homepage: http://womble.decadent.org.uk/software/videolink/ diff --git a/debian/rules b/debian/rules index 2806168..b18a9a3 100755 --- a/debian/rules +++ b/debian/rules @@ -7,13 +7,13 @@ binary : binary-arch binary-indep binary-arch : build dh_testroot - make prefix=debian/webdvd/usr install + make prefix=debian/videolink/usr install dh_strip dh_shlibdeps @echo "Despite the warnings from dh_shlibdeps, this should complete dependencies:" - echo "mozilla:Depends=mozilla-browser (= $$(dpkg-query -W --showformat='$${version}' mozilla-browser))" >> debian/webdvd.substvars - mkdir -p -m755 debian/webdvd/usr/share/doc/webdvd - cp COPYING debian/webdvd/usr/share/doc/webdvd/copyright + echo "mozilla:Depends=mozilla-browser (= $$(dpkg-query -W --showformat='$${version}' mozilla-browser))" >> debian/videolink.substvars + mkdir -p -m755 debian/videolink/usr/share/doc/videolink + cp COPYING debian/videolink/usr/share/doc/videolink/copyright dh_installchangelogs dh_installdocs dh_compress diff --git a/debian/videolink.docs b/debian/videolink.docs new file mode 100644 index 0000000..e845566 --- /dev/null +++ b/debian/videolink.docs @@ -0,0 +1 @@ +README diff --git a/debian/webdvd.docs b/debian/webdvd.docs deleted file mode 100644 index e845566..0000000 --- a/debian/webdvd.docs +++ /dev/null @@ -1 +0,0 @@ -README diff --git a/generate_dvd.cpp b/generate_dvd.cpp index 6c507b9..32ce0f2 100644 --- a/generate_dvd.cpp +++ b/generate_dvd.cpp @@ -11,7 +11,7 @@ #include "xml_utils.hpp" dvd_contents::menu::menu() - : vob_temp(new temp_file("webdvd-vob-")) + : vob_temp(new temp_file("videolink-vob-")) { vob_temp->close(); } @@ -19,7 +19,7 @@ dvd_contents::menu::menu() void generate_dvd(const dvd_contents & contents, const std::string & output_dir) { - temp_file temp("webdvd-dvdauthor-"); + temp_file temp("videolink-dvdauthor-"); temp.close(); std::ofstream file(temp.get_name().c_str()); diff --git a/null_prompt_service.cpp b/null_prompt_service.cpp index 37303c0..03355ac 100644 --- a/null_prompt_service.cpp +++ b/null_prompt_service.cpp @@ -14,7 +14,7 @@ #include #include "null_prompt_service.hpp" -#include "webdvd.hpp" +#include "videolink.hpp" #include "xpcom_support.hpp" using xpcom_support::check; diff --git a/videolink.cpp b/videolink.cpp new file mode 100644 index 0000000..3d2daeb --- /dev/null +++ b/videolink.cpp @@ -0,0 +1,1057 @@ +// Copyright 2005-6 Ben Hutchings . +// See the file "COPYING" for licence details. + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // required before nsILink.h +#include +#include +#include +#include +#include +#include +#include +#include + +#include "browser_widget.hpp" +#include "child_iterator.hpp" +#include "dvd.hpp" +#include "generate_dvd.hpp" +#include "link_iterator.hpp" +#include "null_prompt_service.hpp" +#include "pixbufs.hpp" +#include "style_sheets.hpp" +#include "temp_file.hpp" +#include "video.hpp" +#include "x_frame_buffer.hpp" +#include "xml_utils.hpp" +#include "xpcom_support.hpp" + +using xpcom_support::check; + +namespace +{ + // We can try using any of these encoders to convert PNG to MPEG. + enum mpeg_encoder + { + mpeg_encoder_ffmpeg, // ffmpeg + mpeg_encoder_mjpegtools_old, // mjpegtools before version 1.8 + mpeg_encoder_mjpegtools_new // mjpegtools from version 1.8 + }; + + struct rectangle + { + int left, top; // inclusive + int right, bottom; // exclusive + + rectangle operator|=(const rectangle & other) + { + if (other.empty()) + { + // use current extents unchanged + } + else if (empty()) + { + // use other extents + *this = other; + } + else + { + // find rectangle enclosing both extents + left = std::min(left, other.left); + top = std::min(top, other.top); + right = std::max(right, other.right); + bottom = std::max(bottom, other.bottom); + } + + return *this; + } + + rectangle operator&=(const rectangle & other) + { + // find rectangle enclosed in both extents + left = std::max(left, other.left); + top = std::max(top, other.top); + right = std::max(left, std::min(right, other.right)); + bottom = std::max(top, std::min(bottom, other.bottom)); + return *this; + } + + bool empty() const + { + return left == right || bottom == top; + } + }; + + rectangle get_elem_rect(nsIDOMNSDocument * ns_doc, + nsIDOMElement * elem) + { + rectangle result; + + // Start with this element's bounding box + nsCOMPtr box; + check(ns_doc->GetBoxObjectFor(elem, getter_AddRefs(box))); + int width, height; + check(box->GetScreenX(&result.left)); + check(box->GetScreenY(&result.top)); + check(box->GetWidth(&width)); + check(box->GetHeight(&height)); + result.right = result.left + width; + result.bottom = result.top + height; + + // Merge bounding boxes of all child elements + for (child_iterator it = child_iterator(elem), end; it != end; ++it) + { + nsCOMPtr child_node(*it); + PRUint16 child_type; + if (check(child_node->GetNodeType(&child_type)), + child_type == nsIDOMNode::ELEMENT_NODE) + { + nsCOMPtr child_elem( + do_QueryInterface(child_node)); + result |= get_elem_rect(ns_doc, child_elem); + } + } + + return result; + } + + + class videolink_window : public Gtk::Window + { + public: + videolink_window( + const video::frame_params & frame_params, + const std::string & main_page_uri, + const std::string & output_dir, + mpeg_encoder encoder); + + bool is_finished() const; + + private: + dvd_contents::pgc_ref add_menu(const std::string & uri); + dvd_contents::pgc_ref add_title(const std::string & uri); + void load_next_page(); + bool on_idle(); + void on_net_state_change(const char * uri, gint flags, guint status); + bool browser_is_busy() const + { + return pending_window_update_ || pending_req_count_; + } + bool process_page(); + void save_screenshot(); + void process_links(nsIPresShell * pres_shell, + nsIPresContext * pres_context, + nsIDOMWindow * dom_window); + + video::frame_params frame_params_; + std::string output_dir_; + mpeg_encoder encoder_; + browser_widget browser_widget_; + nsCOMPtr stylesheet_; + + dvd_contents contents_; + typedef std::map resource_map_type; + resource_map_type resource_map_; + + std::queue page_queue_; + bool pending_window_update_; + int pending_req_count_; + bool have_tweaked_page_; + std::auto_ptr background_temp_; + struct page_state; + std::auto_ptr page_state_; + + bool finished_; + }; + + videolink_window::videolink_window( + const video::frame_params & frame_params, + const std::string & main_page_uri, + const std::string & output_dir, + mpeg_encoder encoder) + : frame_params_(frame_params), + output_dir_(output_dir), + encoder_(encoder), + stylesheet_(load_css("file://" VIDEOLINK_LIB_DIR "/videolink.css")), + pending_window_update_(false), + pending_req_count_(0), + have_tweaked_page_(false), + finished_(false) + { + set_size_request(frame_params_.width, frame_params_.height); + set_resizable(false); + + add(browser_widget_); + browser_widget_.show(); + Glib::signal_idle().connect( + SigC::slot(*this, &videolink_window::on_idle)); + browser_widget_.signal_net_state().connect( + SigC::slot(*this, &videolink_window::on_net_state_change)); + + add_menu(main_page_uri); + } + + bool videolink_window::is_finished() const + { + return finished_; + } + + dvd_contents::pgc_ref videolink_window::add_menu(const std::string & uri) + { + dvd_contents::pgc_ref next_menu(dvd_contents::menu_pgc, + contents_.menus.size()); + std::pair insert_result( + resource_map_.insert(std::make_pair(uri, next_menu))); + + if (!insert_result.second) + { + return insert_result.first->second; + } + else + { + page_queue_.push(uri); + contents_.menus.resize(contents_.menus.size() + 1); + return next_menu; + } + } + + dvd_contents::pgc_ref videolink_window::add_title(const std::string & uri) + { + dvd_contents::pgc_ref next_title(dvd_contents::title_pgc, + contents_.titles.size()); + std::pair insert_result( + resource_map_.insert(std::make_pair(uri, next_title))); + + if (!insert_result.second) + { + return insert_result.first->second; + } + else + { + Glib::ustring hostname; + std::string path(Glib::filename_from_uri(uri, hostname)); + // FIXME: Should check the hostname + + vob_list list; + + // Store a reference to a linked VOB file, or the contents + // of a linked VOB list file. + if (path.compare(path.size() - 4, 4, ".vob") == 0) + { + if (!Glib::file_test(path, Glib::FILE_TEST_IS_REGULAR)) + throw std::runtime_error( + path + " is missing or not a regular file"); + vob_ref ref; + ref.file = path; + list.push_back(ref); + } + else + { + assert(path.compare(path.size() - 8, 8, ".voblist") == 0); + read_vob_list(path).swap(list); + } + + contents_.titles.resize(contents_.titles.size() + 1); + contents_.titles.back().swap(list); + return next_title; + } + } + + void videolink_window::load_next_page() + { + assert(!page_queue_.empty()); + const std::string & uri = page_queue_.front(); + std::cout << "loading " << uri << std::endl; + + browser_widget_.load_uri(uri); + } + + bool videolink_window::on_idle() + { + load_next_page(); + return false; // don't call again thankyou + } + + void videolink_window::on_net_state_change(const char * uri, + gint flags, guint status) + { +# ifdef DEBUG_ON_NET_STATE_CHANGE + std::cout << "videolink_window::on_net_state_change("; + if (uri) + std::cout << '"' << uri << '"'; + else + std::cout << "NULL"; + std::cout << ", "; + { + gint flags_left = flags; + static const struct { + gint value; + const char * name; + } flag_names[] = { + { GTK_MOZ_EMBED_FLAG_START, "STATE_START" }, + { GTK_MOZ_EMBED_FLAG_REDIRECTING, "STATE_REDIRECTING" }, + { GTK_MOZ_EMBED_FLAG_TRANSFERRING, "STATE_TRANSFERRING" }, + { GTK_MOZ_EMBED_FLAG_NEGOTIATING, "STATE_NEGOTIATING" }, + { GTK_MOZ_EMBED_FLAG_STOP, "STATE_STOP" }, + { GTK_MOZ_EMBED_FLAG_IS_REQUEST, "STATE_IS_REQUEST" }, + { GTK_MOZ_EMBED_FLAG_IS_DOCUMENT, "STATE_IS_DOCUMENT" }, + { GTK_MOZ_EMBED_FLAG_IS_NETWORK, "STATE_IS_NETWORK" }, + { GTK_MOZ_EMBED_FLAG_IS_WINDOW, "STATE_IS_WINDOW" } + }; + for (int i = 0; i != sizeof(flag_names)/sizeof(flag_names[0]); ++i) + { + if (flags & flag_names[i].value) + { + std::cout << flag_names[i].name; + flags_left -= flag_names[i].value; + if (flags_left) + std::cout << " | "; + } + } + if (flags_left) + std::cout << "0x" << std::setbase(16) << flags_left; + } + std::cout << ", " << "0x" << std::setbase(16) << status << ")\n"; +# endif // DEBUG_ON_NET_STATE_CHANGE + + if (flags & GTK_MOZ_EMBED_FLAG_IS_REQUEST) + { + if (flags & GTK_MOZ_EMBED_FLAG_START) + ++pending_req_count_; + + if (flags & GTK_MOZ_EMBED_FLAG_STOP) + { + assert(pending_req_count_ != 0); + --pending_req_count_; + } + } + + if (flags & GTK_MOZ_EMBED_FLAG_IS_DOCUMENT + && flags & GTK_MOZ_EMBED_FLAG_START) + { + pending_window_update_ = true; + have_tweaked_page_ = false; + } + + if (flags & GTK_MOZ_EMBED_FLAG_IS_WINDOW + && flags & GTK_MOZ_EMBED_FLAG_STOP) + { + // Check whether the load was successful, ignoring this + // pseudo-error. + if (status != NS_IMAGELIB_ERROR_LOAD_ABORTED) + check(status); + + pending_window_update_ = false; + } + + if (!browser_is_busy()) + { + try + { + if (!process_page()) + { + finished_ = true; + Gtk::Main::quit(); + } + } + catch (std::exception & e) + { + std::cerr << "Fatal error"; + if (!page_queue_.empty()) + std::cerr << " while processing <" << page_queue_.front() + << ">"; + std::cerr << ": " << e.what() << "\n"; + Gtk::Main::quit(); + } + catch (Glib::Exception & e) + { + std::cerr << "Fatal error"; + if (!page_queue_.empty()) + std::cerr << " while processing <" << page_queue_.front() + << ">"; + std::cerr << ": " << e.what() << "\n"; + Gtk::Main::quit(); + } + } + } + + bool videolink_window::process_page() + { + assert(!page_queue_.empty()); + + nsCOMPtr browser(browser_widget_.get_browser()); + nsCOMPtr doc_shell(do_GetInterface(browser)); + assert(doc_shell); + nsCOMPtr pres_shell; + check(doc_shell->GetPresShell(getter_AddRefs(pres_shell))); + nsCOMPtr pres_context; + check(doc_shell->GetPresContext(getter_AddRefs(pres_context))); + nsCOMPtr dom_window; + check(browser->GetContentDOMWindow(getter_AddRefs(dom_window))); + + // If we haven't done so already, apply the stylesheet and + // disable scrollbars. + if (!have_tweaked_page_) + { + apply_style_sheet(stylesheet_, pres_shell); + + // This actually only needs to be done once. + nsCOMPtr dom_bar_prop; + check(dom_window->GetScrollbars(getter_AddRefs(dom_bar_prop))); + check(dom_bar_prop->SetVisible(false)); + + have_tweaked_page_ = true; + + // Might need to wait a while for things to load or more + // likely for a re-layout. + if (browser_is_busy()) + return true; + } + + // All further work should only be done if we're not in preview mode. + if (!output_dir_.empty()) + { + // If we haven't already started work on this menu, save a + // screenshot of its normal appearance. + if (!page_state_.get()) + save_screenshot(); + + // Start or continue processing links. + process_links(pres_shell, pres_context, dom_window); + + // If we've finished work on the links, move on to the + // next page, if any, or else generate the DVD filesystem. + if (!page_state_.get()) + { + page_queue_.pop(); + if (page_queue_.empty()) + { + generate_dvd(contents_, output_dir_); + return false; + } + else + { + load_next_page(); + } + } + } + + return true; + } + + void videolink_window::save_screenshot() + { + Glib::RefPtr window(get_window()); + assert(window); + window->process_updates(true); + + background_temp_.reset(new temp_file("videolink-back-")); + background_temp_->close(); + std::cout << "saving " << background_temp_->get_name() << std::endl; + Gdk::Pixbuf::create(Glib::RefPtr(window), + window->get_colormap(), + 0, 0, 0, 0, + frame_params_.width, frame_params_.height) + ->save(background_temp_->get_name(), "png"); + } + + struct videolink_window::page_state + { + page_state(nsIDOMDocument * doc, int width, int height) + : diff_pixbuf(Gdk::Pixbuf::create( + Gdk::COLORSPACE_RGB, + true, 8, // has_alpha, bits_per_sample + width, height)), + spumux_temp("videolink-spumux-"), + links_temp("videolink-links-"), + link_num(0), + links_it(doc), + link_changing(false) + { + spumux_temp.close(); + links_temp.close(); + } + + Glib::RefPtr diff_pixbuf; + + temp_file spumux_temp; + std::ofstream spumux_file; + + temp_file links_temp; + + unsigned link_num; + link_iterator links_it, links_end; + + rectangle link_rect; + bool link_changing; + Glib::RefPtr norm_pixbuf; + }; + + void videolink_window::process_links(nsIPresShell * pres_shell, + nsIPresContext * pres_context, + nsIDOMWindow * dom_window) + { + Glib::RefPtr window(get_window()); + assert(window); + + nsCOMPtr basic_doc; + check(dom_window->GetDocument(getter_AddRefs(basic_doc))); + nsCOMPtr ns_doc(do_QueryInterface(basic_doc)); + assert(ns_doc); + nsCOMPtr event_state_man( + pres_context->EventStateManager()); // does not AddRef + assert(event_state_man); + nsCOMPtr event_factory( + do_QueryInterface(basic_doc)); + assert(event_factory); + nsCOMPtr doc_view(do_QueryInterface(basic_doc)); + assert(doc_view); + nsCOMPtr view; + check(doc_view->GetDefaultView(getter_AddRefs(view))); + + // Set up or recover our iteration state. + std::auto_ptr state(page_state_); + if (!state.get()) + { + state.reset( + new page_state( + basic_doc, frame_params_.width, frame_params_.height)); + + state->spumux_file.open(state->spumux_temp.get_name().c_str()); + state->spumux_file << + "\n" + " \n" + " \n"; + } + + rectangle window_rect = { + 0, 0, frame_params_.width, frame_params_.height + }; + + unsigned menu_num = resource_map_[page_queue_.front()].index; + + for (/* no initialisation */; + state->links_it != state->links_end; + ++state->links_it) + { + nsCOMPtr node(*state->links_it); + + // Find the link URI and separate any fragment from it. + nsCOMPtr link(do_QueryInterface(node)); + assert(link); + nsCOMPtr uri_iface; + check(link->GetHrefURI(getter_AddRefs(uri_iface))); + std::string uri_and_fragment, uri, fragment; + { + nsCString uri_and_fragment_ns; + check(uri_iface->GetSpec(uri_and_fragment_ns)); + uri_and_fragment.assign(uri_and_fragment_ns.BeginReading(), + uri_and_fragment_ns.EndReading()); + + std::size_t hash_pos = uri_and_fragment.find('#'); + uri.assign(uri_and_fragment, 0, hash_pos); + if (hash_pos != std::string::npos) + fragment.assign(uri_and_fragment, + hash_pos + 1, std::string::npos); + } + + // Is this a new link? + if (!state->link_changing) + { + // Find a rectangle enclosing the link and clip it to the + // window. + nsCOMPtr elem(do_QueryInterface(node)); + assert(elem); + state->link_rect = get_elem_rect(ns_doc, elem); + state->link_rect &= window_rect; + + if (state->link_rect.empty()) + { + std::cerr << "Ignoring invisible link to " + << uri_and_fragment << "\n"; + continue; + } + + ++state->link_num; + + if (state->link_num >= unsigned(dvd::menu_buttons_max)) + { + if (state->link_num == unsigned(dvd::menu_buttons_max)) + std::cerr << "No more than " << dvd::menu_buttons_max + << " buttons can be placed on a menu\n"; + std::cerr << "Ignoring link to " << uri_and_fragment + << "\n"; + continue; + } + + state->spumux_file << + "