Remove .gitignore do nothing is ignored.
[synfig.git] / synfig-core / trunk / src / synfig / canvas.cpp
index 2554981..2bbb743 100644 (file)
@@ -6,6 +6,7 @@
 **
 **     \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
@@ -38,6 +39,7 @@
 #include "time.h"
 #include "context.h"
 #include "layer_pastecanvas.h"
+#include "loadcanvas.h"
 #include <sigc++/bind.h>
 
 #endif
@@ -46,10 +48,12 @@ using namespace synfig;
 using namespace etl;
 using namespace std;
 
-namespace synfig { extern Canvas::Handle open_canvas(const String &filename); };
+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;
@@ -68,8 +72,9 @@ int _CanvasCounter::counter(0);
 
 /* === M E T H O D S ======================================================= */
 
-Canvas::Canvas(const string &id):
+Canvas::Canvas(const String &id):
        id_                     (id),
+       version_        (CURRENT_CANVAS_VERSION),
        cur_time_       (0),
        is_inline_      (false),
        is_dirty_       (true),
@@ -88,9 +93,28 @@ Canvas::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--;
-       //DEBUGPOINT();
        clear();
        begin_delete();
 }
@@ -348,7 +372,6 @@ Canvas::_get_relative_id(etl::loose_handle<const Canvas> x)const
        return id;
 }
 
-
 ValueNode::Handle
 Canvas::find_value_node(const String &id)
 {
@@ -379,7 +402,8 @@ Canvas::find_value_node(const String &id)const
        //synfig::warning("constfind:value_node_id: "+value_node_id);
        //synfig::warning("constfind:canvas_id: "+canvas_id);
 
-       return find_canvas(canvas_id)->value_node_list_.find(value_node_id);
+       String warnings;
+       return find_canvas(canvas_id, warnings)->value_node_list_.find(value_node_id);
 }
 
 ValueNode::Handle
@@ -401,7 +425,8 @@ Canvas::surefind_value_node(const String &id)
        if(canvas_id.empty())
                canvas_id=':';
 
-       return surefind_canvas(canvas_id)->value_node_list_.surefind(value_node_id);
+       String warnings;
+       return surefind_canvas(canvas_id,warnings)->value_node_list_.surefind(value_node_id);
 }
 
 void
@@ -411,7 +436,6 @@ Canvas::add_value_node(ValueNode::Handle x, const String &id)
                return parent_->add_value_node(x,id);
 //             throw runtime_error("You cannot add a ValueNode to an inline Canvas");
 
-       //DEBUGPOINT();
        if(x->is_exported())
                throw runtime_error("ValueNode is already exported");
 
@@ -423,16 +447,13 @@ Canvas::add_value_node(ValueNode::Handle x, const String &id)
 
        try
        {
-               //DEBUGPOINT();
                if(PlaceholderValueNode::Handle::cast_dynamic(value_node_list_.find(id)))
                        throw Exception::IDNotFound("add_value_node()");
 
-               //DEBUGPOINT();
                throw Exception::IDAlreadyExists(id);
        }
        catch(Exception::IDNotFound)
        {
-               //DEBUGPOINT();
                x->set_id(id);
 
                x->set_parent_canvas(this);
@@ -442,7 +463,6 @@ Canvas::add_value_node(ValueNode::Handle x, const String &id)
                        synfig::error("Unable to add ValueNode");
                        throw std::runtime_error("Unable to add ValueNode");
                }
-               //DEBUGPOINT();
 
                return;
        }
@@ -491,12 +511,11 @@ Canvas::remove_value_node(ValueNode::Handle x)
        x->set_id("");
 }
 
-
-etl::handle<Canvas>
-Canvas::surefind_canvas(const String &id)
+Canvas::Handle
+Canvas::surefind_canvas(const String &id, String &warnings)
 {
        if(is_inline() && parent_)
-               return parent_->surefind_canvas(id);
+               return parent_->surefind_canvas(id,warnings);
 
        if(id.empty())
                return this;
@@ -508,7 +527,7 @@ Canvas::surefind_canvas(const String &id)
                // If '#' is the first character, remove it
                // and attempt to parse the ID again.
                if(id[0]=='#')
-                       return surefind_canvas(String(id,1));
+                       return surefind_canvas(String(id,1),warnings);
 
                //! \todo This needs a lot more optimization
                String file_name(id,0,id.find_first_of('#'));
@@ -518,22 +537,22 @@ Canvas::surefind_canvas(const String &id)
 
                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
                {
-                       if(is_absolute_path(file_name))
-                               external_canvas=open_canvas(file_name);
-                       else
-                               external_canvas=open_canvas(get_file_path()+ETL_DIRECTORY_SEPARATOR+file_name);
-
+                       String errors;
+                       external_canvas=open_canvas(file_name, errors, warnings);
                        if(!external_canvas)
-                               throw Exception::FileNotFound(file_name);
+                               throw runtime_error(errors);
                        externals_[file_name]=external_canvas;
                }
 
-               return Handle::cast_const(external_canvas.constant()->find_canvas(external_id));
+               return Handle::cast_const(external_canvas.constant()->find_canvas(external_id, warnings));
        }
 
        // If we do not have any resolution, then we assume that the
@@ -556,7 +575,7 @@ Canvas::surefind_canvas(const String &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));
+               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
@@ -564,24 +583,25 @@ Canvas::surefind_canvas(const String &id)
 
        String canvas_name=string(id,0,id.find_first_of(':'));
 
-       Canvas::Handle child_canvas=surefind_canvas(canvas_name);
+       Canvas::Handle child_canvas=surefind_canvas(canvas_name,warnings);
 
-       return child_canvas->surefind_canvas(string(id,id.find_first_of(':')+1));
+       return child_canvas->surefind_canvas(string(id,id.find_first_of(':')+1),warnings);
 }
 
 Canvas::Handle
-Canvas::find_canvas(const String &id)
+Canvas::find_canvas(const String &id, String &warnings)
 {
        return
                Canvas::Handle::cast_const(
-                       const_cast<const Canvas*>(this)->find_canvas(id)
+                       const_cast<const Canvas*>(this)->find_canvas(id, warnings)
                );
 }
 
 Canvas::ConstHandle
-Canvas::find_canvas(const String &id)const
+Canvas::find_canvas(const String &id, String &warnings)const
 {
-       if(is_inline() && parent_)return parent_->find_canvas(id);
+       if(is_inline() && parent_)
+               return parent_->find_canvas(id, warnings);
 
        if(id.empty())
                return this;
@@ -593,7 +613,7 @@ Canvas::find_canvas(const String &id)const
                // If '#' is the first character, remove it
                // and attempt to parse the ID again.
                if(id[0]=='#')
-                       return find_canvas(String(id,1));
+                       return find_canvas(String(id,1), warnings);
 
                //! \todo This needs a lot more optimization
                String file_name(id,0,id.find_first_of('#'));
@@ -603,22 +623,22 @@ Canvas::find_canvas(const String &id)const
 
                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
                {
-                       if(is_absolute_path(file_name))
-                               external_canvas=open_canvas(file_name);
-                       else
-                               external_canvas=open_canvas(get_file_path()+ETL_DIRECTORY_SEPARATOR+file_name);
-
+                       String errors, warnings;
+                       external_canvas=open_canvas(file_name, errors, warnings);
                        if(!external_canvas)
-                               throw Exception::FileNotFound(file_name);
+                               throw runtime_error(errors);
                        externals_[file_name]=external_canvas;
                }
 
-               return Handle::cast_const(external_canvas.constant()->find_canvas(external_id));
+               return Handle::cast_const(external_canvas.constant()->find_canvas(external_id, warnings));
        }
 
        // If we do not have any resolution, then we assume that the
@@ -638,8 +658,8 @@ Canvas::find_canvas(const String &id)const
 
        // If the first character is the separator, then
        // this references the root canvas.
-       if(id.find_first_of(':')==0)
-               return get_root()->find_canvas(string(id,1));
+       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
@@ -647,12 +667,11 @@ Canvas::find_canvas(const String &id)const
 
        String canvas_name=string(id,0,id.find_first_of(':'));
 
-       Canvas::ConstHandle child_canvas=find_canvas(canvas_name);
+       Canvas::ConstHandle child_canvas=find_canvas(canvas_name, warnings);
 
-       return child_canvas->find_canvas(string(id,id.find_first_of(':')+1));
+       return child_canvas->find_canvas(string(id,id.find_first_of(':')+1), warnings);
 }
 
-
 Canvas::Handle
 Canvas::create()
 {
@@ -662,7 +681,6 @@ Canvas::create()
 void
 Canvas::push_back(etl::handle<Layer> x)
 {
-//     DEBUGPOINT();
 //     int i(x->count());
        insert(end(),x);
        //if(x->count()!=i+1)synfig::info("push_back before %d, after %d",i,x->count());
@@ -671,7 +689,6 @@ Canvas::push_back(etl::handle<Layer> x)
 void
 Canvas::push_front(etl::handle<Layer> x)
 {
-//     DEBUGPOINT();
 //     int i(x->count());
        insert(begin(),x);
        //if(x->count()!=i+1)synfig::error("push_front before %d, after %d",i,x->count());
@@ -692,10 +709,8 @@ Canvas::insert(iterator iter,etl::handle<Layer> x)
 
        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);
@@ -717,11 +732,9 @@ Canvas::insert(iterator iter,etl::handle<Layer> x)
                                                                   &Canvas::remove_group_pair),
                                                           loose_layer))));
 
-
        if(!x->get_group().empty())
                add_group_pair(x->get_group(),x);
 
-
        changed();
 }
 
@@ -733,7 +746,7 @@ Canvas::push_back_simple(etl::handle<Layer> x)
 }
 
 void
-Canvas::erase(Canvas::iterator iter)
+Canvas::erase(iterator iter)
 {
        if(!(*iter)->get_group().empty())
                remove_group_pair((*iter)->get_group(),(*iter));
@@ -768,7 +781,9 @@ Canvas::clone(const GUID& deriv_guid)const
        {
                name=get_id()+"_CLONE";
 
-               throw runtime_error("Cloning of non-inline canvases is not yet suported");
+#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));
@@ -776,7 +791,12 @@ Canvas::clone(const GUID& deriv_guid)const
        if(is_inline())
        {
                canvas->is_inline_=true;
-               canvas->parent_=0;
+               // \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());
        }
 
@@ -899,7 +919,8 @@ Canvas::add_child_canvas(Canvas::Handle child_canvas, const synfig::String& id)
 
        try
        {
-               find_canvas(id);
+               String warnings;
+               find_canvas(id, warnings);
                throw Exception::IDAlreadyExists(id);
        }
        catch(Exception::IDNotFound)
@@ -938,8 +959,24 @@ Canvas::set_file_name(const String &file_name)
                parent()->set_file_name(file_name);
        else
        {
+               String old_name(file_name_);
                file_name_=file_name;
-               signal_file_name_changed_();
+
+               // 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_();
+               }
        }
 }
 
@@ -947,7 +984,7 @@ sigc::signal<void>&
 Canvas::signal_file_name_changed()
 {
        if(parent())
-               return signal_file_name_changed();
+               return parent()->signal_file_name_changed();
        else
                return signal_file_name_changed_;
 }
@@ -968,7 +1005,6 @@ Canvas::get_file_path()const
        return dirname(file_name_);
 }
 
-
 String
 Canvas::get_meta_data(const String& key)const
 {
@@ -1012,8 +1048,13 @@ Canvas::get_meta_data_keys()const
        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(Context context, Canvas::Handle op_canvas, bool seen_motion_blur_in_parent)
+synfig::optimize_layers(Time time, Context context, Canvas::Handle op_canvas, bool seen_motion_blur_in_parent)
 {
        Context iter;
 
@@ -1040,7 +1081,7 @@ synfig::optimize_layers(Context context, Canvas::Handle op_canvas, bool seen_mot
                        if(value.get_type()==ValueBase::TYPE_REAL && value.get(Real())==0)
                                continue;
 
-                       if(layer->get_name()=="MotionBlur")
+                       if(layer->get_name()=="MotionBlur" || layer->get_name()=="duplicate")
                        {
                                float z_depth(layer->get_z_depth()*1.0001+i);
 
@@ -1079,12 +1120,12 @@ synfig::optimize_layers(Context context, Canvas::Handle op_canvas, bool seen_mot
                if(value.get_type()==ValueBase::TYPE_REAL && value.get(Real())==0)
                        continue;
 
-               Layer_PasteCanvas* paste_canvas(static_cast<Layer_PasteCanvas*>(layer.get()));
-
                // 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,
@@ -1109,41 +1150,49 @@ synfig::optimize_layers(Context context, Canvas::Handle op_canvas, bool seen_mot
                        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(paste_sub_canvas->get_context(),sub_canvas,motion_blurred);
-//#define SYNFIG_OPTIMIZE_PASTE_CANVAS 1
+                               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();
+                       // 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();
 
-                                       ValueBase value(layer->get_param("blend_method"));
-                                       if(value.get_type()!=ValueBase::TYPE_INTEGER || value.get(int())!=(int)Color::BLEND_COMPOSITE)
-                                               throw int();
-                               }
+                                               // 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();
 
-                               // 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();
+                                               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;
                                }
-                               continue;
-                       }catch(int) { }
-#endif
+                               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)
@@ -1159,6 +1208,46 @@ synfig::optimize_layers(Context context, Canvas::Handle op_canvas, bool seen_mot
                        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);
@@ -1253,13 +1342,13 @@ Canvas::remove_group_pair(String group, etl::handle<Layer> layer)
 }
 
 void
-Canvas::add_connection(Layer::LooseHandle layer, sigc::connection connection)
+Canvas::add_connection(etl::loose_handle<Layer> layer, sigc::connection connection)
 {
        connections_[layer].push_back(connection);
 }
 
 void
-Canvas::disconnect_connections(Layer::LooseHandle layer)
+Canvas::disconnect_connections(etl::loose_handle<Layer> layer)
 {
        std::vector<sigc::connection>::iterator iter;
        for(iter=connections_[layer].begin();iter!=connections_[layer].end();++iter)
@@ -1294,3 +1383,26 @@ Canvas::rename_group(const String&old_name,const String&new_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