Remove ancient trunk folder from svn repository
[synfig.git] / synfig-core / src / synfig / canvas.cpp
diff --git a/synfig-core/src/synfig/canvas.cpp b/synfig-core/src/synfig/canvas.cpp
new file mode 100644 (file)
index 0000000..2bbb743
--- /dev/null
@@ -0,0 +1,1408 @@
+/* === S Y N F I G ========================================================= */
+/*!    \file canvas.cpp
+**     \brief Canvas Class Member Definitions
+**
+**     $Id$
+**
+**     \legal
+**     Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
+**     Copyright (c) 2007, 2008 Chris Moore
+**
+**     This package 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 package 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.
+**     \endlegal
+*/
+/* ========================================================================= */
+
+/* === H E A D E R S ======================================================= */
+
+#define SYNFIG_NO_ANGLE
+
+#ifdef USING_PCH
+#      include "pch.h"
+#else
+#ifdef HAVE_CONFIG_H
+#      include <config.h>
+#endif
+
+#include "layer.h"
+#include "canvas.h"
+#include <cassert>
+#include "exception.h"
+#include "time.h"
+#include "context.h"
+#include "layer_pastecanvas.h"
+#include "loadcanvas.h"
+#include <sigc++/bind.h>
+
+#endif
+
+using namespace synfig;
+using namespace etl;
+using namespace std;
+
+namespace synfig { extern Canvas::Handle open_canvas(const String &filename, String &errors, String &warnings); };
+
+/* === M A C R O S ========================================================= */
+
+#define ALLOW_CLONE_NON_INLINE_CANVASES
+
+struct _CanvasCounter
+{
+       static int counter;
+       ~_CanvasCounter()
+       {
+               if(counter)
+                       synfig::error("%d canvases not yet deleted!",counter);
+       }
+} _canvas_counter;
+
+int _CanvasCounter::counter(0);
+
+/* === G L O B A L S ======================================================= */
+
+/* === P R O C E D U R E S ================================================= */
+
+/* === M E T H O D S ======================================================= */
+
+Canvas::Canvas(const String &id):
+       id_                     (id),
+       version_        (CURRENT_CANVAS_VERSION),
+       cur_time_       (0),
+       is_inline_      (false),
+       is_dirty_       (true),
+       op_flag_        (false)
+{
+       _CanvasCounter::counter++;
+       clear();
+}
+
+void
+Canvas::on_changed()
+{
+       is_dirty_=true;
+       Node::on_changed();
+}
+
+Canvas::~Canvas()
+{
+       // we were having a crash where pastecanvas layers were still
+       // refering to a canvas after it had been destroyed;  this code
+       // will stop the pastecanvas layers from refering to the canvas
+       // before the canvas is destroyed
+
+       // the set_sub_canvas(0) ends up deleting the parent-child link,
+       // which deletes the current element from the set we're iterating
+       // through, so we have to make sure we've incremented the iterator
+       // before we mess with the pastecanvas
+       std::set<Node*>::iterator iter = parent_set.begin();
+       while (iter != parent_set.end())
+       {
+               Layer_PasteCanvas* paste_canvas = dynamic_cast<Layer_PasteCanvas*>(*iter);
+               iter++;
+               if(paste_canvas)
+                       paste_canvas->set_sub_canvas(0);
+               else
+                       warning("destroyed canvas has a parent that is not a pastecanvas - please report if repeatable");
+       }
+
+       //if(is_inline() && parent_) assert(0);
+       _CanvasCounter::counter--;
+       clear();
+       begin_delete();
+}
+
+Canvas::iterator
+Canvas::end()
+{
+       return CanvasBase::end()-1;
+}
+
+Canvas::const_iterator
+Canvas::end()const
+{
+       return CanvasBase::end()-1;
+}
+
+Canvas::reverse_iterator
+Canvas::rbegin()
+{
+       return CanvasBase::rbegin()+1;
+}
+
+Canvas::const_reverse_iterator
+Canvas::rbegin()const
+{
+       return CanvasBase::rbegin()+1;
+}
+
+int
+Canvas::size()const
+{
+       return CanvasBase::size()-1;
+}
+
+void
+Canvas::clear()
+{
+       while(!empty())
+       {
+               Layer::Handle layer(front());
+               //if(layer->count()>2)synfig::info("before layer->count()=%d",layer->count());
+
+               erase(begin());
+               //if(layer->count()>1)synfig::info("after layer->count()=%d",layer->count());
+       }
+       //CanvasBase::clear();
+
+       // We need to keep a blank handle at the
+       // end of the image list, and acts at
+       // the bottom. Without it, the layers
+       // would just continue going when polled
+       // for a color.
+       CanvasBase::push_back(Layer::Handle());
+
+       changed();
+}
+
+bool
+Canvas::empty()const
+{
+       return CanvasBase::size()<=1;
+}
+
+Layer::Handle &
+Canvas::back()
+{
+       return *(CanvasBase::end()-1);
+}
+
+const Layer::Handle &
+Canvas::back()const
+{
+       return *(CanvasBase::end()-1);
+}
+
+Context
+Canvas::get_context()const
+{
+       return begin();
+}
+
+const ValueNodeList &
+Canvas::value_node_list()const
+{
+       if(is_inline() && parent_)
+               return parent_->value_node_list();
+       return value_node_list_;
+}
+
+KeyframeList &
+Canvas::keyframe_list()
+{
+       if(is_inline() && parent_)
+               return parent_->keyframe_list();
+       return keyframe_list_;
+}
+
+const KeyframeList &
+Canvas::keyframe_list()const
+{
+       if(is_inline() && parent_)
+               return parent_->keyframe_list();
+       return keyframe_list_;
+}
+
+etl::handle<Layer>
+Canvas::find_layer(const Point &pos)
+{
+       return get_context().hit_check(pos);
+}
+
+static bool
+valid_id(const String &x)
+{
+       static const char bad_chars[]=" :#@$^&()*";
+       unsigned int i;
+
+       if(!x.empty() && x[0]>='0' && x[0]<='9')
+               return false;
+
+       for(i=0;i<sizeof(bad_chars);i++)
+               if(x.find_first_of(bad_chars[i])!=string::npos)
+                       return false;
+
+       return true;
+}
+
+void
+Canvas::set_id(const String &x)
+{
+       if(is_inline() && parent_)
+               throw runtime_error("Inline Canvas cannot have an ID");
+
+       if(!valid_id(x))
+               throw runtime_error("Invalid ID");
+       id_=x;
+       signal_id_changed_();
+}
+
+void
+Canvas::set_name(const String &x)
+{
+       name_=x;
+       signal_meta_data_changed()("name");
+       signal_meta_data_changed("name")();
+}
+
+void
+Canvas::set_author(const String &x)
+{
+       author_=x;
+       signal_meta_data_changed()("author");
+       signal_meta_data_changed("author")();
+}
+
+void
+Canvas::set_description(const String &x)
+{
+       description_=x;
+       signal_meta_data_changed()("description");
+       signal_meta_data_changed("description")();
+}
+
+void
+Canvas::set_time(Time t)const
+{
+       if(is_dirty_ || !get_time().is_equal(t))
+       {
+#if 0
+               if(is_root())
+               {
+                       synfig::info("is_dirty_=%d",is_dirty_);
+                       synfig::info("get_time()=%f",(float)get_time());
+                       synfig::info("t=%f",(float)t);
+               }
+#endif
+
+               // ...questionable
+               const_cast<Canvas&>(*this).cur_time_=t;
+
+               is_dirty_=false;
+               get_context().set_time(t);
+       }
+       is_dirty_=false;
+}
+
+Canvas::LooseHandle
+Canvas::get_root()const
+{
+       return parent_?parent_->get_root().get():const_cast<synfig::Canvas *>(this);
+}
+
+int
+Canvas::get_depth(etl::handle<Layer> layer)const
+{
+       const_iterator iter;
+       int i(0);
+       for(iter=begin();iter!=end();++iter,i++)
+       {
+               if(layer==*iter)
+                       return i;
+       }
+       return -1;
+}
+
+String
+Canvas::get_relative_id(etl::loose_handle<const Canvas> x)const
+{
+       if(x->get_root()==this)
+               return ":";
+       if(is_inline() && parent_)
+               return parent_->_get_relative_id(x);
+       return _get_relative_id(x);
+}
+
+String
+Canvas::_get_relative_id(etl::loose_handle<const Canvas> x)const
+{
+       if(is_inline() && parent_)
+               return parent_->_get_relative_id(x);
+
+       if(x.get()==this)
+               return String();
+
+       if(parent()==x.get())
+               return get_id();
+
+       String id;
+
+       const Canvas* canvas=this;
+
+       for(;!canvas->is_root();canvas=canvas->parent().get())
+               id=':'+canvas->get_id()+id;
+
+       if(x && get_root()!=x->get_root())
+       {
+               //String file_name=get_file_name();
+               //String file_path=x->get_file_path();
+
+               String file_name;
+               if(is_absolute_path(get_file_name()))
+                       file_name=etl::relative_path(x->get_file_path(),get_file_name());
+               else
+                       file_name=get_file_name();
+
+               // If the path of X is inside of file_name,
+               // then remove it.
+               //if(file_name.size()>file_path.size())
+               //      if(file_path==String(file_name,0,file_path.size()))
+               //              file_name.erase(0,file_path.size()+1);
+
+               id=file_name+'#'+id;
+       }
+
+       return id;
+}
+
+ValueNode::Handle
+Canvas::find_value_node(const String &id)
+{
+       return
+               ValueNode::Handle::cast_const(
+                       const_cast<const Canvas*>(this)->find_value_node(id)
+               );
+}
+
+ValueNode::ConstHandle
+Canvas::find_value_node(const String &id)const
+{
+       if(is_inline() && parent_)
+               return parent_->find_value_node(id);
+
+       if(id.empty())
+               throw Exception::IDNotFound("Empty ID");
+
+       // If we do not have any resolution, then we assume that the
+       // request is for this immediate canvas
+       if(id.find_first_of(':')==string::npos && id.find_first_of('#')==string::npos)
+               return value_node_list_.find(id);
+
+       String canvas_id(id,0,id.rfind(':'));
+       String value_node_id(id,id.rfind(':')+1);
+       if(canvas_id.empty())
+               canvas_id=':';
+       //synfig::warning("constfind:value_node_id: "+value_node_id);
+       //synfig::warning("constfind:canvas_id: "+canvas_id);
+
+       String warnings;
+       return find_canvas(canvas_id, warnings)->value_node_list_.find(value_node_id);
+}
+
+ValueNode::Handle
+Canvas::surefind_value_node(const String &id)
+{
+       if(is_inline() && parent_)
+               return parent_->surefind_value_node(id);
+
+       if(id.empty())
+               throw Exception::IDNotFound("Empty ID");
+
+       // If we do not have any resolution, then we assume that the
+       // request is for this immediate canvas
+       if(id.find_first_of(':')==string::npos && id.find_first_of('#')==string::npos)
+               return value_node_list_.surefind(id);
+
+       String canvas_id(id,0,id.rfind(':'));
+       String value_node_id(id,id.rfind(':')+1);
+       if(canvas_id.empty())
+               canvas_id=':';
+
+       String warnings;
+       return surefind_canvas(canvas_id,warnings)->value_node_list_.surefind(value_node_id);
+}
+
+void
+Canvas::add_value_node(ValueNode::Handle x, const String &id)
+{
+       if(is_inline() && parent_)
+               return parent_->add_value_node(x,id);
+//             throw runtime_error("You cannot add a ValueNode to an inline Canvas");
+
+       if(x->is_exported())
+               throw runtime_error("ValueNode is already exported");
+
+       if(id.empty())
+               throw Exception::BadLinkName("Empty ID");
+
+       if(id.find_first_of(':',0)!=string::npos)
+               throw Exception::BadLinkName("Bad character");
+
+       try
+       {
+               if(PlaceholderValueNode::Handle::cast_dynamic(value_node_list_.find(id)))
+                       throw Exception::IDNotFound("add_value_node()");
+
+               throw Exception::IDAlreadyExists(id);
+       }
+       catch(Exception::IDNotFound)
+       {
+               x->set_id(id);
+
+               x->set_parent_canvas(this);
+
+               if(!value_node_list_.add(x))
+               {
+                       synfig::error("Unable to add ValueNode");
+                       throw std::runtime_error("Unable to add ValueNode");
+               }
+
+               return;
+       }
+}
+
+/*
+void
+Canvas::rename_value_node(ValueNode::Handle x, const String &id)
+{
+       if(id.empty())
+               throw Exception::BadLinkName("Empty ID");
+
+       if(id.find_first_of(": ",0)!=string::npos)
+               throw Exception::BadLinkName("Bad character");
+
+       try
+       {
+               if(PlaceholderValueNode::Handle::cast_dynamic(value_node_list_.find(id)))
+                       throw Exception::IDNotFound("rename_value_node");
+               throw Exception::IDAlreadyExists(id);
+       }
+       catch(Exception::IDNotFound)
+       {
+               x->set_id(id);
+
+               return;
+       }
+}
+*/
+
+void
+Canvas::remove_value_node(ValueNode::Handle x)
+{
+       if(is_inline() && parent_)
+               return parent_->remove_value_node(x);
+//             throw Exception::IDNotFound("Canvas::remove_value_node() was called from an inline canvas");
+
+       if(!x)
+               throw Exception::IDNotFound("Canvas::remove_value_node() was passed empty handle");
+
+       if(!value_node_list_.erase(x))
+               throw Exception::IDNotFound("Canvas::remove_value_node(): ValueNode was not found inside of this canvas");
+
+       //x->set_parent_canvas(0);
+
+       x->set_id("");
+}
+
+Canvas::Handle
+Canvas::surefind_canvas(const String &id, String &warnings)
+{
+       if(is_inline() && parent_)
+               return parent_->surefind_canvas(id,warnings);
+
+       if(id.empty())
+               return this;
+
+       // If the ID contains a "#" character, then a filename is
+       // expected on the left side.
+       if(id.find_first_of('#')!=string::npos)
+       {
+               // If '#' is the first character, remove it
+               // and attempt to parse the ID again.
+               if(id[0]=='#')
+                       return surefind_canvas(String(id,1),warnings);
+
+               //! \todo This needs a lot more optimization
+               String file_name(id,0,id.find_first_of('#'));
+               String external_id(id,id.find_first_of('#')+1);
+
+               file_name=unix_to_local_path(file_name);
+
+               Canvas::Handle external_canvas;
+
+               if(!is_absolute_path(file_name))
+                       file_name = get_file_path()+ETL_DIRECTORY_SEPARATOR+file_name;
+
+               // If the composition is already open, then use it.
+               if(externals_.count(file_name))
+                       external_canvas=externals_[file_name];
+               else
+               {
+                       String errors;
+                       external_canvas=open_canvas(file_name, errors, warnings);
+                       if(!external_canvas)
+                               throw runtime_error(errors);
+                       externals_[file_name]=external_canvas;
+               }
+
+               return Handle::cast_const(external_canvas.constant()->find_canvas(external_id, warnings));
+       }
+
+       // If we do not have any resolution, then we assume that the
+       // request is for this immediate canvas
+       if(id.find_first_of(':')==string::npos)
+       {
+               Children::iterator iter;
+
+               // Search for the image in the image list,
+               // and return it if it is found
+               for(iter=children().begin();iter!=children().end();iter++)
+                       if(id==(*iter)->get_id())
+                               return *iter;
+
+               // Create a new canvas and return it
+               //synfig::warning("Implicitly creating canvas named "+id);
+               return new_child_canvas(id);
+       }
+
+       // If the first character is the separator, then
+       // this references the root canvas.
+       if(id[0]==':')
+               return get_root()->surefind_canvas(string(id,1),warnings);
+
+       // Now we know that the requested Canvas is in a child
+       // of this canvas. We have to find that canvas and
+       // call "find_canvas" on it, and return the result.
+
+       String canvas_name=string(id,0,id.find_first_of(':'));
+
+       Canvas::Handle child_canvas=surefind_canvas(canvas_name,warnings);
+
+       return child_canvas->surefind_canvas(string(id,id.find_first_of(':')+1),warnings);
+}
+
+Canvas::Handle
+Canvas::find_canvas(const String &id, String &warnings)
+{
+       return
+               Canvas::Handle::cast_const(
+                       const_cast<const Canvas*>(this)->find_canvas(id, warnings)
+               );
+}
+
+Canvas::ConstHandle
+Canvas::find_canvas(const String &id, String &warnings)const
+{
+       if(is_inline() && parent_)
+               return parent_->find_canvas(id, warnings);
+
+       if(id.empty())
+               return this;
+
+       // If the ID contains a "#" character, then a filename is
+       // expected on the left side.
+       if(id.find_first_of('#')!=string::npos)
+       {
+               // If '#' is the first character, remove it
+               // and attempt to parse the ID again.
+               if(id[0]=='#')
+                       return find_canvas(String(id,1), warnings);
+
+               //! \todo This needs a lot more optimization
+               String file_name(id,0,id.find_first_of('#'));
+               String external_id(id,id.find_first_of('#')+1);
+
+               file_name=unix_to_local_path(file_name);
+
+               Canvas::Handle external_canvas;
+
+               if(!is_absolute_path(file_name))
+                       file_name = get_file_path()+ETL_DIRECTORY_SEPARATOR+file_name;
+
+               // If the composition is already open, then use it.
+               if(externals_.count(file_name))
+                       external_canvas=externals_[file_name];
+               else
+               {
+                       String errors, warnings;
+                       external_canvas=open_canvas(file_name, errors, warnings);
+                       if(!external_canvas)
+                               throw runtime_error(errors);
+                       externals_[file_name]=external_canvas;
+               }
+
+               return Handle::cast_const(external_canvas.constant()->find_canvas(external_id, warnings));
+       }
+
+       // If we do not have any resolution, then we assume that the
+       // request is for this immediate canvas
+       if(id.find_first_of(':')==string::npos)
+       {
+               Children::const_iterator iter;
+
+               // Search for the image in the image list,
+               // and return it if it is found
+               for(iter=children().begin();iter!=children().end();iter++)
+                       if(id==(*iter)->get_id())
+                               return *iter;
+
+               throw Exception::IDNotFound("Child Canvas in Parent Canvas: (child)"+id);
+       }
+
+       // If the first character is the separator, then
+       // this references the root canvas.
+       if(id[0]==':')
+               return get_root()->find_canvas(string(id,1), warnings);
+
+       // Now we know that the requested Canvas is in a child
+       // of this canvas. We have to find that canvas and
+       // call "find_canvas" on it, and return the result.
+
+       String canvas_name=string(id,0,id.find_first_of(':'));
+
+       Canvas::ConstHandle child_canvas=find_canvas(canvas_name, warnings);
+
+       return child_canvas->find_canvas(string(id,id.find_first_of(':')+1), warnings);
+}
+
+Canvas::Handle
+Canvas::create()
+{
+       return new Canvas("Untitled");
+}
+
+void
+Canvas::push_back(etl::handle<Layer> x)
+{
+//     int i(x->count());
+       insert(end(),x);
+       //if(x->count()!=i+1)synfig::info("push_back before %d, after %d",i,x->count());
+}
+
+void
+Canvas::push_front(etl::handle<Layer> x)
+{
+//     int i(x->count());
+       insert(begin(),x);
+       //if(x->count()!=i+1)synfig::error("push_front before %d, after %d",i,x->count());
+}
+
+void
+Canvas::insert(iterator iter,etl::handle<Layer> x)
+{
+//     int i(x->count());
+       CanvasBase::insert(iter,x);
+
+       /*if(x->count()!=i+1)
+       {
+               synfig::error(__FILE__":%d: Canvas::insert(): ***FAILURE*** before %d, after %d",__LINE__,i,x->count());
+               return;
+               //throw runtime_error("Canvas Insertion Failed");
+       }*/
+
+       x->set_canvas(this);
+
+       add_child(x.get());
+
+       LooseHandle correct_canvas(this);
+       //while(correct_canvas->is_inline())correct_canvas=correct_canvas->parent();
+       Layer::LooseHandle loose_layer(x);
+
+       add_connection(loose_layer,
+                                  sigc::connection::connection(
+                                          x->signal_added_to_group().connect(
+                                                  sigc::bind(
+                                                          sigc::mem_fun(
+                                                                  *correct_canvas,
+                                                                  &Canvas::add_group_pair),
+                                                          loose_layer))));
+       add_connection(loose_layer,
+                                  sigc::connection::connection(
+                                          x->signal_removed_from_group().connect(
+                                                  sigc::bind(
+                                                          sigc::mem_fun(
+                                                                  *correct_canvas,
+                                                                  &Canvas::remove_group_pair),
+                                                          loose_layer))));
+
+       if(!x->get_group().empty())
+               add_group_pair(x->get_group(),x);
+
+       changed();
+}
+
+void
+Canvas::push_back_simple(etl::handle<Layer> x)
+{
+       CanvasBase::insert(end(),x);
+       changed();
+}
+
+void
+Canvas::erase(iterator iter)
+{
+       if(!(*iter)->get_group().empty())
+               remove_group_pair((*iter)->get_group(),(*iter));
+
+       // HACK: We really shouldn't be wiping
+       // out these signals entirely. We should
+       // only be removing the specific connections
+       // that we made. At the moment, I'm too
+       // lazy to add the code to keep track
+       // of those connections, and no one else
+       // is using these signals, so I'll just
+       // leave these next two lines like they
+       // are for now - darco 07-30-2004
+
+       // so don't wipe them out entirely
+       // - dooglus 09-21-2007
+       disconnect_connections(*iter);
+
+       if(!op_flag_)remove_child(iter->get());
+
+       CanvasBase::erase(iter);
+       if(!op_flag_)changed();
+}
+
+Canvas::Handle
+Canvas::clone(const GUID& deriv_guid)const
+{
+       synfig::String name;
+       if(is_inline())
+               name="inline";
+       else
+       {
+               name=get_id()+"_CLONE";
+
+#ifndef ALLOW_CLONE_NON_INLINE_CANVASES
+               throw runtime_error("Cloning of non-inline canvases is not yet supported");
+#endif // ALLOW_CLONE_NON_INLINE_CANVASES
+       }
+
+       Handle canvas(new Canvas(name));
+
+       if(is_inline())
+       {
+               canvas->is_inline_=true;
+               // \todo this was setting parent_=0 - is there a reason for that?
+               // this was causing bug 1838132, where cloning an inline canvas that contains an imported image fails
+               // it was failing to ascertain the absolute pathname of the imported image, since it needs the pathname
+               // of the canvas to get that, which is stored in the parent canvas
+               canvas->parent_=parent();
+               canvas->rend_desc() = rend_desc();
+               //canvas->set_inline(parent());
+       }
+
+       canvas->set_guid(get_guid()^deriv_guid);
+
+       const_iterator iter;
+       for(iter=begin();iter!=end();++iter)
+       {
+               Layer::Handle layer((*iter)->clone(deriv_guid));
+               if(layer)
+               {
+                       assert(layer.count()==1);
+                       int presize(size());
+                       canvas->push_back(layer);
+                       if(!(layer.count()>1))
+                       {
+                               synfig::error("Canvas::clone(): Cloned layer insertion failure!");
+                               synfig::error("Canvas::clone(): \tlayer.count()=%d",layer.count());
+                               synfig::error("Canvas::clone(): \tlayer->get_name()=%s",layer->get_name().c_str());
+                               synfig::error("Canvas::clone(): \tbefore size()=%d",presize);
+                               synfig::error("Canvas::clone(): \tafter size()=%d",size());
+                       }
+                       assert(layer.count()>1);
+               }
+               else
+               {
+                       synfig::error("Unable to clone layer");
+               }
+       }
+
+       canvas->signal_group_pair_removed().clear();
+       canvas->signal_group_pair_added().clear();
+
+       return canvas;
+}
+
+void
+Canvas::set_inline(LooseHandle parent)
+{
+       if(is_inline_ && parent_)
+       {
+
+       }
+
+       id_="inline";
+       is_inline_=true;
+       parent_=parent;
+
+       // Have the parent inherit all of the group stuff
+
+       std::map<String,std::set<etl::handle<Layer> > >::const_iterator iter;
+
+       for(iter=group_db_.begin();iter!=group_db_.end();++iter)
+       {
+               parent->group_db_[iter->first].insert(iter->second.begin(),iter->second.end());
+       }
+
+       rend_desc()=parent->rend_desc();
+}
+
+Canvas::Handle
+Canvas::create_inline(Handle parent)
+{
+       assert(parent);
+       //if(parent->is_inline())
+       //      return create_inline(parent->parent());
+
+       Handle canvas(new Canvas("inline"));
+       canvas->set_inline(parent);
+       return canvas;
+}
+
+Canvas::Handle
+Canvas::new_child_canvas()
+{
+       if(is_inline() && parent_)
+               return parent_->new_child_canvas();
+//             runtime_error("You cannot create a child Canvas in an inline Canvas");
+
+       // Create a new canvas
+       children().push_back(create());
+       Canvas::Handle canvas(children().back());
+
+       canvas->parent_=this;
+
+       canvas->rend_desc()=rend_desc();
+
+       return canvas;
+}
+
+Canvas::Handle
+Canvas::new_child_canvas(const String &id)
+{
+       if(is_inline() && parent_)
+               return parent_->new_child_canvas(id);
+//             runtime_error("You cannot create a child Canvas in an inline Canvas");
+
+       // Create a new canvas
+       children().push_back(create());
+       Canvas::Handle canvas(children().back());
+
+       canvas->set_id(id);
+       canvas->parent_=this;
+       canvas->rend_desc()=rend_desc();
+
+       return canvas;
+}
+
+Canvas::Handle
+Canvas::add_child_canvas(Canvas::Handle child_canvas, const synfig::String& id)
+{
+       if(is_inline() && parent_)
+               return parent_->add_child_canvas(child_canvas,id);
+
+       if(child_canvas->parent() && !child_canvas->is_inline())
+               throw std::runtime_error("Cannot add child canvas because it belongs to someone else!");
+
+       if(!valid_id(id))
+               throw runtime_error("Invalid ID");
+
+       try
+       {
+               String warnings;
+               find_canvas(id, warnings);
+               throw Exception::IDAlreadyExists(id);
+       }
+       catch(Exception::IDNotFound)
+       {
+               if(child_canvas->is_inline())
+                       child_canvas->is_inline_=false;
+               child_canvas->id_=id;
+               children().push_back(child_canvas);
+               child_canvas->parent_=this;
+       }
+
+       return child_canvas;
+}
+
+void
+Canvas::remove_child_canvas(Canvas::Handle child_canvas)
+{
+       if(is_inline() && parent_)
+               return parent_->remove_child_canvas(child_canvas);
+
+       if(child_canvas->parent_!=this)
+               throw runtime_error("Given child does not belong to me");
+
+       if(find(children().begin(),children().end(),child_canvas)==children().end())
+               throw Exception::IDNotFound(child_canvas->get_id());
+
+       children().remove(child_canvas);
+
+       child_canvas->parent_=0;
+}
+
+void
+Canvas::set_file_name(const String &file_name)
+{
+       if(parent())
+               parent()->set_file_name(file_name);
+       else
+       {
+               String old_name(file_name_);
+               file_name_=file_name;
+
+               // when a canvas is made, its name is ""
+               // then, before it's saved or even edited, it gets a name like "Synfig Animation 23", in the local language
+               // we don't want to register the canvas' filename in the canvas map until it gets a real filename
+               if (old_name != "")
+               {
+                       file_name_=file_name;
+                       std::map<synfig::String, etl::loose_handle<Canvas> >::iterator iter;
+                       for(iter=get_open_canvas_map().begin();iter!=get_open_canvas_map().end();++iter)
+                               if(iter->second==this)
+                                       break;
+                       if (iter == get_open_canvas_map().end())
+                               CanvasParser::register_canvas_in_map(this, file_name);
+                       else
+                               signal_file_name_changed_();
+               }
+       }
+}
+
+sigc::signal<void>&
+Canvas::signal_file_name_changed()
+{
+       if(parent())
+               return parent()->signal_file_name_changed();
+       else
+               return signal_file_name_changed_;
+}
+
+String
+Canvas::get_file_name()const
+{
+       if(parent())
+               return parent()->get_file_name();
+       return file_name_;
+}
+
+String
+Canvas::get_file_path()const
+{
+       if(parent())
+               return parent()->get_file_path();
+       return dirname(file_name_);
+}
+
+String
+Canvas::get_meta_data(const String& key)const
+{
+       if(!meta_data_.count(key))
+               return String();
+       return meta_data_.find(key)->second;
+}
+
+void
+Canvas::set_meta_data(const String& key, const String& data)
+{
+       if(meta_data_[key]!=data)
+       {
+               meta_data_[key]=data;
+               signal_meta_data_changed()(key);
+               signal_meta_data_changed(key)();
+       }
+}
+
+void
+Canvas::erase_meta_data(const String& key)
+{
+       if(meta_data_.count(key))
+       {
+               meta_data_.erase(key);
+               signal_meta_data_changed()(key);
+               signal_meta_data_changed(key)();
+       }
+}
+
+std::list<String>
+Canvas::get_meta_data_keys()const
+{
+       std::list<String> ret;
+
+       std::map<String,String>::const_iterator iter;
+
+       for(iter=meta_data_.begin();!(iter==meta_data_.end());++iter)
+               ret.push_back(iter->first);
+
+       return ret;
+}
+
+/* note - the "Motion Blur" and "Duplicate" layers need the dynamic
+                 parameters of any PasteCanvas layers they loop over to be
+                 maintained.  When the variables in the following function
+                 refer to "motion blur", they mean either of these two
+                 layers. */
+void
+synfig::optimize_layers(Time time, Context context, Canvas::Handle op_canvas, bool seen_motion_blur_in_parent)
+{
+       Context iter;
+
+       std::vector< std::pair<float,Layer::Handle> > sort_list;
+       int i, motion_blur_i=0; // motion_blur_i is for resolving which layer comes first in the event of a z_depth tie
+       float motion_blur_z_depth=0; // the z_depth of the least deep motion blur layer in this context
+       bool seen_motion_blur_locally = false;
+       bool motion_blurred; // the final result - is this layer blurred or not?
+
+       // If the parent didn't cause us to already be motion blurred,
+       // check whether there's a motion blur in this context,
+       // and if so, calculate its z_depth.
+       if (!seen_motion_blur_in_parent)
+               for(iter=context,i=0;*iter;iter++,i++)
+               {
+                       Layer::Handle layer=*iter;
+
+                       // If the layer isn't active, don't worry about it
+                       if(!layer->active())
+                               continue;
+
+                       // Any layer with an amount of zero is implicitly disabled.
+                       ValueBase value(layer->get_param("amount"));
+                       if(value.get_type()==ValueBase::TYPE_REAL && value.get(Real())==0)
+                               continue;
+
+                       if(layer->get_name()=="MotionBlur" || layer->get_name()=="duplicate")
+                       {
+                               float z_depth(layer->get_z_depth()*1.0001+i);
+
+                               // If we've seen a motion blur before in this context...
+                               if (seen_motion_blur_locally)
+                               {
+                                       // ... then we're only interested in this one if it's less deep...
+                                       if (z_depth < motion_blur_z_depth)
+                                       {
+                                               motion_blur_z_depth = z_depth;
+                                               motion_blur_i = i;
+                                       }
+                               }
+                               // ... otherwise we're always interested in it.
+                               else
+                               {
+                                       motion_blur_z_depth = z_depth;
+                                       motion_blur_i = i;
+                                       seen_motion_blur_locally = true;
+                               }
+                       }
+               }
+
+       // Go ahead and start romping through the canvas to paste
+       for(iter=context,i=0;*iter;iter++,i++)
+       {
+               Layer::Handle layer=*iter;
+               float z_depth(layer->get_z_depth()*1.0001+i);
+
+               // If the layer isn't active, don't worry about it
+               if(!layer->active())
+                       continue;
+
+               // Any layer with an amount of zero is implicitly disabled.
+               ValueBase value(layer->get_param("amount"));
+               if(value.get_type()==ValueBase::TYPE_REAL && value.get(Real())==0)
+                       continue;
+
+               // note: this used to include "&& paste_canvas->get_time_offset()==0", but then
+               //               time-shifted layers weren't being sorted by z-depth (bug #1806852)
+               if(layer->get_name()=="PasteCanvas")
+               {
+                       Layer_PasteCanvas* paste_canvas(static_cast<Layer_PasteCanvas*>(layer.get()));
+
+                       // we need to blur the sub canvas if:
+                       // our parent is blurred,
+                       // or the child is lower than a local blur,
+                       // or the child is at the same z_depth as a local blur, but later in the context
+
+#if 0 // DEBUG
+                       if (seen_motion_blur_in_parent)                                 synfig::info("seen BLUR in parent\n");
+                       else if (seen_motion_blur_locally)
+                               if (z_depth > motion_blur_z_depth)                      synfig::info("paste is deeper than BLUR\n");
+                               else if (z_depth == motion_blur_z_depth) {      synfig::info("paste is same depth as BLUR\n");
+                                       if (i > motion_blur_i)                                  synfig::info("paste is physically deeper than BLUR\n");
+                                       else                                                                    synfig::info("paste is less physically deep than BLUR\n");
+                               } else                                                                          synfig::info("paste is less deep than BLUR\n");
+                       else                                                                                    synfig::info("no BLUR at all\n");
+#endif // DEBUG
+
+                       motion_blurred = (seen_motion_blur_in_parent ||
+                                                         (seen_motion_blur_locally &&
+                                                          (z_depth > motion_blur_z_depth ||
+                                                               (z_depth == motion_blur_z_depth && i > motion_blur_i))));
+
+                       Canvas::Handle sub_canvas(Canvas::create_inline(op_canvas));
+                       Canvas::Handle paste_sub_canvas = paste_canvas->get_sub_canvas();
+                       if(paste_sub_canvas)
+                               optimize_layers(time, paste_sub_canvas->get_context(),sub_canvas,motion_blurred);
+
+// \todo: uncommenting the following breaks the rendering of at least examples/backdrop.sifz quite severely
+// #define SYNFIG_OPTIMIZE_PASTE_CANVAS
+#ifdef SYNFIG_OPTIMIZE_PASTE_CANVAS
+                       Canvas::iterator sub_iter;
+
+                       // Determine if we can just remove the paste canvas altogether
+                       if (paste_canvas->get_blend_method()    == Color::BLEND_COMPOSITE       &&
+                               paste_canvas->get_amount()                      == 1.0f                                         &&
+                               paste_canvas->get_zoom()                        == 0                                            &&
+                               paste_canvas->get_time_offset()         == 0                                            &&
+                               paste_canvas->get_origin()                      == Point(0,0)                           )
+                               try {
+                                       for(sub_iter=sub_canvas->begin();sub_iter!=sub_canvas->end();++sub_iter)
+                                       {
+                                               Layer* layer=sub_iter->get();
+
+                                               // any layers that deform end up breaking things
+                                               // so do things the old way if we run into anything like this
+                                               if(!dynamic_cast<Layer_NoDeform*>(layer))
+                                                       throw int();
+
+                                               ValueBase value(layer->get_param("blend_method"));
+                                               if(value.get_type()!=ValueBase::TYPE_INTEGER || value.get(int())!=(int)Color::BLEND_COMPOSITE)
+                                                       throw int();
+                                       }
+
+                                       // It has turned out that we don't need a paste canvas
+                                       // layer, so just go ahead and add all the layers onto
+                                       // the current stack and be done with it
+                                       while(sub_canvas->size())
+                                       {
+                                               sort_list.push_back(std::pair<float,Layer::Handle>(z_depth,sub_canvas->front()));
+                                               //op_canvas->push_back_simple(sub_canvas->front());
+                                               sub_canvas->pop_front();
+                                       }
+                                       continue;
+                               }
+                               catch(int)
+                               { }
+#endif // SYNFIG_OPTIMIZE_PASTE_CANVAS
+
+                       Layer::Handle new_layer(Layer::create("PasteCanvas"));
+                       dynamic_cast<Layer_PasteCanvas*>(new_layer.get())->set_muck_with_time(false);
+                       if (motion_blurred)
+                       {
+                               Layer::DynamicParamList dynamic_param_list(paste_canvas->dynamic_param_list());
+                               for(Layer::DynamicParamList::const_iterator iter(dynamic_param_list.begin()); iter != dynamic_param_list.end(); ++iter)
+                                       new_layer->connect_dynamic_param(iter->first, iter->second);
+                       }
+                       Layer::ParamList param_list(paste_canvas->get_param_list());
+                       //param_list.erase("canvas");
+                       new_layer->set_param_list(param_list);
+                       dynamic_cast<Layer_PasteCanvas*>(new_layer.get())->set_sub_canvas(sub_canvas);
+                       dynamic_cast<Layer_PasteCanvas*>(new_layer.get())->set_muck_with_time(true);
+                       layer=new_layer;
+               }
+               else                                    // not a PasteCanvas - does it use blend method 'Straight'?
+               {
+                       /* when we use the 'straight' blend method, every pixel on the layer affects the layers underneath,
+                        * not just the non-transparent pixels; the following workarea wraps non-pastecanvas layers in a
+                        * new pastecanvas to ensure that the straight blend affects the full plane, not just the area
+                        * within the layer's bounding box
+                        */
+
+                       // \todo: this code probably needs modification to work properly with motionblur and duplicate
+                       etl::handle<Layer_Composite> composite = etl::handle<Layer_Composite>::cast_dynamic(layer);
+
+                       /* some layers (such as circle) don't touch pixels that aren't
+                        * part of the circle, so they don't get blended correctly when
+                        * using a straight blend.  so we encapsulate the circle, and the
+                        * encapsulation layer takes care of the transparent pixels
+                        * for us.  if we do that for all layers, however, then the
+                        * distortion layers no longer work, since they have no
+                        * context to work on.  the Layer::reads_context() method
+                        * returns true for layers which need to be able to see
+                        * their context.  we can't encapsulate those.
+                        */
+                       if (composite &&
+                               Color::is_straight(composite->get_blend_method()) &&
+                               !composite->reads_context())
+                       {
+                               Canvas::Handle sub_canvas(Canvas::create_inline(op_canvas));
+                               // don't use clone() because it re-randomizes the seeds of any random valuenodes
+                               sub_canvas->push_back(composite = composite->simple_clone());
+                               layer = Layer::create("PasteCanvas");
+                               composite->set_description(strprintf("Wrapped clone of '%s'", composite->get_non_empty_description().c_str()));
+                               layer->set_description(strprintf("PasteCanvas wrapper for '%s'", composite->get_non_empty_description().c_str()));
+                               Layer_PasteCanvas* paste_canvas(static_cast<Layer_PasteCanvas*>(layer.get()));
+                               paste_canvas->set_blend_method(composite->get_blend_method());
+                               paste_canvas->set_amount(composite->get_amount());
+                               sub_canvas->set_time(time); // region and outline don't calculate their bounding rects until their time is set
+                               composite->set_blend_method(Color::BLEND_STRAIGHT); // do this before calling set_sub_canvas(), but after set_time()
+                               composite->set_amount(1.0f); // after set_time()
+                               paste_canvas->set_sub_canvas(sub_canvas);
+                       }
+               }
+
+               sort_list.push_back(std::pair<float,Layer::Handle>(z_depth,layer));
+               //op_canvas->push_back_simple(layer);
+       }
+
+       //sort_list.sort();
+       stable_sort(sort_list.begin(),sort_list.end());
+       std::vector< std::pair<float,Layer::Handle> >::iterator iter2;
+       for(iter2=sort_list.begin();iter2!=sort_list.end();++iter2)
+               op_canvas->push_back_simple(iter2->second);
+       op_canvas->op_flag_=true;
+}
+
+void
+Canvas::get_times_vfunc(Node::time_set &set) const
+{
+       const_iterator  i = begin(),
+                               iend = end();
+
+       for(; i != iend; ++i)
+       {
+               const Node::time_set &tset = (*i)->get_times();
+               set.insert(tset.begin(),tset.end());
+       }
+}
+
+std::set<etl::handle<Layer> >
+Canvas::get_layers_in_group(const String&group)
+{
+       if(is_inline() && parent_)
+               return parent_->get_layers_in_group(group);
+
+       if(group_db_.count(group)==0)
+               return std::set<etl::handle<Layer> >();
+       return group_db_.find(group)->second;
+}
+
+std::set<String>
+Canvas::get_groups()const
+{
+       if(is_inline() && parent_)
+               return parent_->get_groups();
+
+       std::set<String> ret;
+       std::map<String,std::set<etl::handle<Layer> > >::const_iterator iter;
+       for(iter=group_db_.begin();iter!=group_db_.end();++iter)
+               ret.insert(iter->first);
+       return ret;
+}
+
+int
+Canvas::get_group_count()const
+{
+       if(is_inline() && parent_)
+               return parent_->get_group_count();
+
+       return group_db_.size();
+}
+
+void
+Canvas::add_group_pair(String group, etl::handle<Layer> layer)
+{
+       group_db_[group].insert(layer);
+       if(group_db_[group].size()==1)
+               signal_group_added()(group);
+       else
+               signal_group_changed()(group);
+
+       signal_group_pair_added()(group,layer);
+
+       if(is_inline()  && parent_)
+               return parent_->add_group_pair(group,layer);
+}
+
+void
+Canvas::remove_group_pair(String group, etl::handle<Layer> layer)
+{
+       group_db_[group].erase(layer);
+
+       signal_group_pair_removed()(group,layer);
+
+       if(group_db_[group].empty())
+       {
+               group_db_.erase(group);
+               signal_group_removed()(group);
+       }
+       else
+               signal_group_changed()(group);
+
+       if(is_inline() && parent_)
+               return parent_->remove_group_pair(group,layer);
+}
+
+void
+Canvas::add_connection(etl::loose_handle<Layer> layer, sigc::connection connection)
+{
+       connections_[layer].push_back(connection);
+}
+
+void
+Canvas::disconnect_connections(etl::loose_handle<Layer> layer)
+{
+       std::vector<sigc::connection>::iterator iter;
+       for(iter=connections_[layer].begin();iter!=connections_[layer].end();++iter)
+               iter->disconnect();
+       connections_[layer].clear();
+}
+
+void
+Canvas::rename_group(const String&old_name,const String&new_name)
+{
+       if(is_inline() && parent_)
+               return parent_->rename_group(old_name,new_name);
+
+       {
+               std::map<String,std::set<etl::handle<Layer> > >::iterator iter;
+               iter=group_db_.find(old_name);
+               if(iter!=group_db_.end())
+               for(++iter;iter!=group_db_.end() && iter->first.find(old_name)==0;iter=group_db_.find(old_name),++iter)
+               {
+                       String name(iter->first,old_name.size(),String::npos);
+                       name=new_name+name;
+                       rename_group(iter->first,name);
+               }
+       }
+
+       std::set<etl::handle<Layer> > layers(get_layers_in_group(old_name));
+       std::set<etl::handle<Layer> >::iterator iter;
+
+       for(iter=layers.begin();iter!=layers.end();++iter)
+       {
+               (*iter)->remove_from_group(old_name);
+               (*iter)->add_to_group(new_name);
+       }
+}
+
+void
+Canvas::register_external_canvas(String file_name, Handle canvas)
+{
+       if(!is_absolute_path(file_name)) file_name = get_file_path()+ETL_DIRECTORY_SEPARATOR+file_name;
+       externals_[file_name] = canvas;
+}
+
+#ifdef _DEBUG
+void
+Canvas::show_externals(String file, int line, String text) const
+{
+       printf("  .----- (externals for %lx '%s')\n  |  %s:%d %s\n", ulong(this), get_name().c_str(), file.c_str(), line, text.c_str());
+       std::map<String, Handle>::iterator iter;
+       for (iter = externals_.begin(); iter != externals_.end(); iter++)
+       {
+               synfig::String first(iter->first);
+               etl::loose_handle<Canvas> second(iter->second);
+               printf("  |    %40s : %lx (%d)\n", first.c_str(), ulong(&*second), second->count());
+       }
+       printf("  `-----\n\n");
+}
+#endif // _DEBUG