Added copyright lines for files I've edited this year.
[synfig.git] / synfig-studio / trunk / src / gtkmm / instance.cpp
index 42a6ec8..c921c3f 100644 (file)
@@ -1,11 +1,12 @@
 /* === S Y N F I G ========================================================= */
-/*!    \file instance.cpp
+/*!    \file gtkmm/instance.cpp
 **     \brief writeme
 **
-**     $Id: instance.cpp,v 1.2 2005/01/13 18:37:30 darco Exp $
+**     $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
 #include <sigc++/adaptors/hide.h>
 #include "toolbox.h"
 #include "onemoment.h"
+#include <synfig/savecanvas.h>
 
 #include "autorecover.h"
 #include <sigc++/retype_return.h>
 #include <sigc++/retype.h>
 //#include <sigc++/hide.h>
 #include <synfig/valuenode_composite.h>
+#include <synfig/valuenode_duplicate.h>
 #include "widget_waypointmodel.h"
 #include <gtkmm/actiongroup.h>
-#include "iconcontroler.h"
+#include "iconcontroller.h"
+#include <sys/stat.h>
+#include <errno.h>
+#include <ETL/stringf>
+
+#include "general.h"
 
 #endif
 
@@ -57,7 +65,7 @@ using namespace std;
 using namespace etl;
 using namespace synfig;
 using namespace studio;
-using namespace SigC;
+using namespace sigc;
 
 /* === M A C R O S ========================================================= */
 
@@ -69,7 +77,7 @@ int studio::Instance::instance_count_=0;
 
 /* === M E T H O D S ======================================================= */
 
-Instance::Instance(Canvas::Handle canvas):
+Instance::Instance(synfig::Canvas::Handle canvas):
        synfigapp::Instance             (canvas),
 //     canvas_tree_store_              (Gtk::TreeStore::create(CanvasTreeModel())),
 //     canvas_tree_store_              (Gtk::TreeStore::create()),
@@ -81,7 +89,7 @@ Instance::Instance(Canvas::Handle canvas):
        canvas_tree_store_=Gtk::TreeStore::create(model);
 
        id_=instance_count_++;
-       
+
        // Connect up all the signals
        signal_filename_changed().connect(sigc::mem_fun(*this,&studio::Instance::update_all_titles));
        signal_unsaved_status_changed().connect(sigc::hide(sigc::mem_fun(*this,&studio::Instance::update_all_titles)));
@@ -95,9 +103,9 @@ Instance::Instance(Canvas::Handle canvas):
                        )
                )
        );
-       
+
        canvas_tree_store_=Gtk::TreeStore::create(canvas_tree_model);
-       
+
        refresh_canvas_tree();
 }
 
@@ -117,31 +125,31 @@ Instance::get_visible_canvases()const
 }
 
 handle<Instance>
-Instance::create(Canvas::Handle canvas)
+Instance::create(synfig::Canvas::Handle canvas)
 {
        // Construct a new instance
        handle<Instance> instance(new Instance(canvas));
 
        // Add the new instance to the application's instance list
        App::instance_list.push_back(instance);
-       
+
        // Set up the instance with the default UI manager
        instance->synfigapp::Instance::set_ui_interface(App::get_ui_interface());
-       
+
        // Signal the new instance
        App::signal_instance_created()(instance);
-       
+
        // And then make sure that is has been selected
        App::set_selected_instance(instance);
-       
+
        // Create the initial window for the root canvas
        instance->focus(canvas);
-       
-       return instance;        
+
+       return instance;
 }
 
 handle<CanvasView>
-Instance::find_canvas_view(Canvas::Handle canvas)
+Instance::find_canvas_view(etl::handle<synfig::Canvas> canvas)
 {
        if(!canvas)
                return 0;
@@ -150,16 +158,16 @@ Instance::find_canvas_view(Canvas::Handle canvas)
                canvas=canvas->parent();
 
        CanvasViewList::iterator iter;
-       
+
        for(iter=canvas_view_list().begin();iter!=canvas_view_list().end();iter++)
                if((*iter)->get_canvas()==canvas)
                        return *iter;
-       
+
        return CanvasView::create(this,canvas);
 }
 
 void
-Instance::focus(Canvas::Handle canvas)
+Instance::focus(etl::handle<synfig::Canvas> canvas)
 {
        handle<CanvasView> canvas_view=find_canvas_view(canvas);
        assert(canvas_view);
@@ -183,47 +191,47 @@ Instance::set_redo_status(bool x)
 }
 
 bool
-studio::Instance::save_as(const synfig::String &file_name)const
-{
-       if(synfigapp::Instance::save_as(file_name))
-       {
-               App::add_recent_file(file_name);
-               return true;
-       }
-       return false;
-}
-
-bool
 studio::Instance::save_as(const synfig::String &file_name)
 {
        if(synfigapp::Instance::save_as(file_name))
        {
+               // after changing the filename, update the render settings with the new filename
+               list<handle<CanvasView> >::iterator iter;
+               for(iter=canvas_view_list().begin();iter!=canvas_view_list().end();iter++)
+                       (*iter)->render_settings.set_entry_filename();
                App::add_recent_file(file_name);
                return true;
        }
        return false;
 }
 
-bool
+Instance::Status
 studio::Instance::save()
 {
-       if(basename(get_file_name()).find("untitled")==0)
+       // the filename will be set to "Synfig Animation 1" or some such when first created
+       // and will be changed to an absolute path once it has been saved
+       // so if it still begins with "Synfig Animation " then we need to ask where to save it
+       if(get_file_name().find(DEFAULT_FILENAME_PREFIX)==0)
        {
-               dialog_save_as();
-               return true;
+               if (dialog_save_as())
+                       return STATUS_OK;
+               else
+                       return STATUS_CANCEL;
        }
 
-       return synfigapp::Instance::save();
-       
+       if (synfigapp::Instance::save())
+               return STATUS_OK;
+
+       App::dialog_error_blocking("Save - Error","Unable to save to '" + get_file_name() + "'");
+       return STATUS_ERROR;
 }
 
-void
+bool
 studio::Instance::dialog_save_as()
 {
-       string filename="*.sif";
-       
+       string filename = get_file_name();
        Canvas::Handle canvas(get_canvas());
-       
+
        {
                OneMoment one_moment;
                std::set<Node*>::iterator iter;
@@ -241,46 +249,81 @@ studio::Instance::dialog_save_as()
                                                "files that are currently open. Close these\n"
                                                "other files first before trying to use \"SaveAs\"."
                                        );
-                                       
-                                       return;
+
+                                       return false;
                                }
                                if(parent_layer)
                                        break;
                        }
                }
        }
-       
-       while(App::dialog_saveas_file("SaveAs", filename))
+
+       if (get_file_name().find(DEFAULT_FILENAME_PREFIX) != 0)
+               filename = absolute_path(filename);
+
+       // show the canvas' name if it has one, else its ID
+       while (App::dialog_save_file((_("Choose a Filename to Save As") +
+                                                                 String(" (") +
+                                                                 (canvas->get_name().empty() ? canvas->get_id() : canvas->get_name()) +
+                                                                 ") ..."),
+                                                                filename, ANIMATION_DIR_PREFERENCE))
        {
                // If the filename still has wildcards, then we should
                // continue looking for the file we want
-               if(find(filename.begin(),filename.end(),'*')!=filename.end())
+               string base_filename = basename(filename);
+               if (find(base_filename.begin(),base_filename.end(),'*')!=base_filename.end())
                        continue;
 
-               if(find(filename.begin(),filename.end(),'.')==filename.end())
-                       filename+=".sif";
+               if (filename_extension(filename) == "")
+                       filename+=".sifz";
 
                try
                {
-                       String ext(String(filename.begin()+filename.find_last_of('.')+1,filename.end()));
-                       if(ext!="sif" && ext!="sifz" && !App::dialog_yes_no(_("Unknown extension"),
-                               _("You have given the file name an extension\nwhich I do not recognise. Are you sure this is what you want?")))
-                       {
+                       String ext(filename_extension(filename));
+                       if(ext!=".sif" && ext!=".sifz" && !App::dialog_yes_no(_("Unknown extension"),
+                               _("You have given the file name an extension\nwhich I do not recognize. Are you sure this is what you want?")))
                                continue;
-                       }
                }
                catch(...)
                {
                        continue;
                }
-                       
+
+               {
+                       struct stat     s;
+                       int stat_return = stat(filename.c_str(), &s);
+
+                       // if stat() fails with something other than 'file doesn't exist', there's been a real
+                       // error of some kind.  let's give up now and ask for a new path.
+                       if (stat_return == -1 && errno != ENOENT)
+                       {
+                               perror(filename.c_str());
+                               App::dialog_error_blocking("SaveAs - Error","Unable to check whether '" + filename + "' exists.");
+                               continue;
+                       }
+
+                       // if the file exists and the user doesn't want to overwrite it, keep prompting for a filename
+                       if ((stat_return == 0) &&
+                               !App::dialog_yes_no("File exists",
+                                                                       "A file named '" +
+                                                                       filename +
+                                                                       "' already exists.\n\n"
+                                                                       "Do you want to replace it with the file you are saving?"))
+                               continue;
+               }
+
                if(save_as(filename))
-                       break;
-               
-               App::dialog_error_blocking("SaveAs - Error","Unable to save file");
+               {
+                       synfig::set_file_version(ReleaseVersion(RELEASE_VERSION_END-1));
+                       return true;
+               }
+
+               App::dialog_error_blocking("SaveAs - Error","Unable to save to '" + filename + "'");
        }
+
+       return false;
 }
-       
+
 void
 Instance::update_all_titles()
 {
@@ -319,6 +362,9 @@ Instance::close()
        for(std::list<etl::handle<CanvasView> >::iterator iter=canvas_view_list().begin();iter!=canvas_view_list().end();iter++)
                (*iter)->hide();
 
+       // Consume pending events before deleting the canvas views
+       while(studio::App::events_pending())studio::App::iteration(false);
+
        // Delete all of the canvas views
        canvas_view_list().clear();
 
@@ -330,20 +376,17 @@ Instance::close()
                studio::App::set_selected_instance(0);
        }
        else
-       {
-               studio::App::set_selected_canvas_view(studio::App::instance_list.front()->canvas_view_list().front());
-               //studio::App::set_selected_instance(studio::App::instance_list.front());
-       }
+               studio::App::instance_list.front()->canvas_view_list().front()->present();
 }
 
-       
+
 void
-Instance::insert_canvas(Gtk::TreeRow row,Canvas::Handle canvas)
+Instance::insert_canvas(Gtk::TreeRow row, synfig::Canvas::Handle canvas)
 {
        CanvasTreeModel canvas_tree_model;
        assert(canvas);
 
-       row[canvas_tree_model.icon] = Gtk::Button().render_icon(Gtk::StockID("synfig-canvas"),Gtk::ICON_SIZE_SMALL_TOOLBAR);    
+       row[canvas_tree_model.icon] = Gtk::Button().render_icon(Gtk::StockID("synfig-canvas"),Gtk::ICON_SIZE_SMALL_TOOLBAR);
        row[canvas_tree_model.id] = canvas->get_id();
        row[canvas_tree_model.name] = canvas->get_name();
        if(canvas->is_root())
@@ -353,10 +396,10 @@ Instance::insert_canvas(Gtk::TreeRow row,Canvas::Handle canvas)
                row[canvas_tree_model.label] = canvas->get_id();
        else
        if(!canvas->get_name().empty())
-               row[canvas_tree_model.label] = canvas->get_name();              
+               row[canvas_tree_model.label] = canvas->get_name();
        else
-               row[canvas_tree_model.label] = _("[Unnamed]");          
-               
+               row[canvas_tree_model.label] = _("[Unnamed]");
+
        row[canvas_tree_model.canvas] = canvas;
        row[canvas_tree_model.is_canvas] = true;
        row[canvas_tree_model.is_value_node] = false;
@@ -364,7 +407,7 @@ Instance::insert_canvas(Gtk::TreeRow row,Canvas::Handle canvas)
        {
                synfig::Canvas::Children::iterator iter;
                synfig::Canvas::Children &children(canvas->children());
-       
+
                for(iter=children.begin();iter!=children.end();iter++)
                        insert_canvas(*(canvas_tree_store()->append(row.children())),*iter);
        }
@@ -444,13 +487,13 @@ Instance::dialog_cvs_commit()
 
                if(!App::dialog_entry(_("CVS Commit"),_("Enter a log message describing the changes you have made"), message))
                        return;
-               
+
                OneMoment one_moment;
                cvs_commit(message);
        }
        catch(...)
        {
-               App::dialog_error_blocking(_("Error"),_("An error has occured when trying to COMMIT"));
+               App::dialog_error_blocking(_("Error"),_("An error has occurred when trying to COMMIT"));
        }
        update_all_titles();
 }
@@ -467,7 +510,7 @@ Instance::dialog_cvs_add()
        try
        {
                string message;
-               
+
                //if(!App::dialog_entry(_("CVS Add"),_("Enter a log message describing the file"), message))
                //      return;
                OneMoment one_moment;
@@ -475,7 +518,7 @@ Instance::dialog_cvs_add()
        }
        catch(...)
        {
-               App::dialog_error_blocking(_("Error"),_("An error has occured when trying to ADD"));
+               App::dialog_error_blocking(_("Error"),_("An error has occurred when trying to ADD"));
        }
        update_all_titles();
 }
@@ -494,7 +537,7 @@ Instance::dialog_cvs_update()
                App::dialog_error_blocking(_("Info"),_("This file is up-to-date"));
                return;
        }
-       
+
        try
        {
                String filename(get_file_name());
@@ -507,7 +550,7 @@ Instance::dialog_cvs_update()
                OneMoment one_moment;
                time_t oldtime=get_original_timestamp();
                cvs_update();
-               calc_repository_info();         
+               calc_repository_info();
                // If something has been updated...
                if(oldtime!=get_original_timestamp())
                {
@@ -516,7 +559,7 @@ Instance::dialog_cvs_update()
        }
        catch(...)
        {
-               App::dialog_error_blocking(_("Error"),_("An error has occured when trying to UPDATE"));
+               App::dialog_error_blocking(_("Error"),_("An error has occurred when trying to UPDATE"));
        }
        //update_all_titles();
 }
@@ -552,7 +595,7 @@ Instance::dialog_cvs_revert()
        }
        catch(...)
        {
-               App::dialog_error_blocking(_("Error"),_("An error has occured when trying to UPDATE"));
+               App::dialog_error_blocking(_("Error"),_("An error has occurred when trying to UPDATE"));
        }
        //update_all_titles();
 }
@@ -565,17 +608,17 @@ Instance::_revert(Instance *instance)
        String filename(instance->get_file_name());
 
        Canvas::Handle canvas(instance->get_canvas());
-       
+
        instance->close();
 
        if(canvas->count()!=1)
        {
                one_moment.hide();
-               App::dialog_error_blocking(_("Error: Revert Failed"),_("The revert operation has failed. This can be due to it being\nreferenced by another composition that is already open, or\nbecause of an internal error in SYNFIG Studio. Try closing any\ncompositions that might reference this composition and try\nagain, or restart SYNFIG studio."));
+               App::dialog_error_blocking(_("Error: Revert Failed"),_("The revert operation has failed. This can be due to it being\nreferenced by another composition that is already open, or\nbecause of an internal error in Synfig Studio. Try closing any\ncompositions that might reference this composition and try\nagain, or restart Synfig Studio."));
                one_moment.show();
        }
        canvas=0;
-       
+
        App::open(filename);
 }
 
@@ -599,7 +642,7 @@ bool
 Instance::safe_revert()
 {
        if(synfigapp::Instance::get_action_count())
-               if(!App::dialog_yes_no(_("Revert to saved"), _("You will loose any changes you have made since your last save.\nAre you sure?")))
+               if(!App::dialog_yes_no(_("Revert to saved"), _("You will lose any changes you have made since your last save.\nAre you sure?")))
                        return false;
        revert();
        return true;
@@ -608,18 +651,33 @@ Instance::safe_revert()
 bool
 Instance::safe_close()
 {
-       handle<synfigapp::UIInterface> uim;
-       uim=find_canvas_view(get_canvas())->get_ui_interface();
+       handle<CanvasView> canvas_view = find_canvas_view(get_canvas());
+       handle<synfigapp::UIInterface> uim=canvas_view->get_ui_interface();
 
-       if(get_action_count())
+       // if the animation is currently playing, closing the window will cause a crash,
+       // so don't allow it
+       if (canvas_view->is_playing())
        {
-               string str=strprintf(_("Would you like to save your changes to %s?"),basename(get_file_name()).c_str() );
-               int answer=uim->yes_no_cancel(get_canvas()->get_name(),str,synfigapp::UIInterface::RESPONSE_YES);
-               if(answer==synfigapp::UIInterface::RESPONSE_YES)
-                       save();
-               if(answer==synfigapp::UIInterface::RESPONSE_CANCEL)
-                       return false;
+               canvas_view->present();
+               App::dialog_error_blocking("Close Error", "The animation is currently playing so the window cannot be closed.");
+               return false;
        }
+       if(get_action_count())
+               do
+               {
+                       string str=strprintf(_("Would you like to save your changes to %s?"),basename(get_file_name()).c_str() );
+                       int answer=uim->yes_no_cancel(get_canvas()->get_name(),str,synfigapp::UIInterface::RESPONSE_YES);
+                       if(answer==synfigapp::UIInterface::RESPONSE_YES)
+                       {
+                               enum Status status = save();
+                               if (status == STATUS_OK) break;
+                               else if (status == STATUS_CANCEL) return false;
+                       }
+                       if(answer==synfigapp::UIInterface::RESPONSE_NO)
+                               break;
+                       if(answer==synfigapp::UIInterface::RESPONSE_CANCEL)
+                               return false;
+               } while (true);
 
        if(is_modified())
        {
@@ -631,10 +689,8 @@ Instance::safe_close()
                if(answer==synfigapp::UIInterface::RESPONSE_CANCEL)
                        return false;
        }
-       
-       close();
 
-       while(studio::App::events_pending())studio::App::iteration(false);
+       close();
 
        return true;
 }
@@ -643,20 +699,20 @@ Instance::safe_close()
 void
 Instance::add_actions_to_group(const Glib::RefPtr<Gtk::ActionGroup>& action_group, synfig::String& ui_info,   const synfigapp::Action::ParamList &param_list, synfigapp::Action::Category category)const
 {
-       synfigapp::Action::CanidateList canidate_list;
-       synfigapp::Action::CanidateList::iterator iter;
-       
-       canidate_list=compile_canidate_list(param_list,category);
-       
-       canidate_list.sort();
-
-       if(canidate_list.empty())
-               synfig::warning("Action CanidateList is empty!");
-       
-       for(iter=canidate_list.begin();iter!=canidate_list.end();++iter)
+       synfigapp::Action::CandidateList candidate_list;
+       synfigapp::Action::CandidateList::iterator iter;
+
+       candidate_list=compile_candidate_list(param_list,category);
+
+       candidate_list.sort();
+
+       // if(candidate_list.empty())
+       //      synfig::warning("%s:%d Action CandidateList is empty!", __FILE__, __LINE__);
+
+       for(iter=candidate_list.begin();iter!=candidate_list.end();++iter)
        {
                Gtk::StockID stock_id(get_action_stock_id(*iter));
-               
+
                if(!(iter->category&synfigapp::Action::CATEGORY_HIDDEN))
                {
                        action_group->add(Gtk::Action::create(
@@ -683,23 +739,23 @@ Instance::add_actions_to_group(const Glib::RefPtr<Gtk::ActionGroup>& action_grou
 void
 Instance::add_actions_to_menu(Gtk::Menu *menu, const synfigapp::Action::ParamList &param_list,synfigapp::Action::Category category)const
 {
-       synfigapp::Action::CanidateList canidate_list;
-       synfigapp::Action::CanidateList::iterator iter;
-       
-       canidate_list=compile_canidate_list(param_list,category);
-       
-       canidate_list.sort();
-
-       if(canidate_list.empty())
-               synfig::warning("Action CanidateList is empty!");
-       
-       for(iter=canidate_list.begin();iter!=canidate_list.end();++iter)
+       synfigapp::Action::CandidateList candidate_list;
+       synfigapp::Action::CandidateList::iterator iter;
+
+       candidate_list=compile_candidate_list(param_list,category);
+
+       candidate_list.sort();
+
+       if(candidate_list.empty())
+               synfig::warning("%s:%d Action CandidateList is empty!", __FILE__, __LINE__);
+
+       for(iter=candidate_list.begin();iter!=candidate_list.end();++iter)
        {
                if(!(iter->category&synfigapp::Action::CATEGORY_HIDDEN))
                {
                        Gtk::Image* image(manage(new Gtk::Image()));
                        Gtk::Stock::lookup(get_action_stock_id(*iter),Gtk::ICON_SIZE_MENU,*image);
-                       
+
                        /*
                        if(iter->task=="raise")
                                Gtk::Stock::lookup(Gtk::Stock::GO_UP,Gtk::ICON_SIZE_MENU,*image);
@@ -749,30 +805,30 @@ Instance::add_actions_to_menu(Gtk::Menu *menu, const synfigapp::Action::ParamLis
 void
 Instance::add_actions_to_menu(Gtk::Menu *menu, const synfigapp::Action::ParamList &param_list,const synfigapp::Action::ParamList &param_list2,synfigapp::Action::Category category)const
 {
-       synfigapp::Action::CanidateList canidate_list;
-       synfigapp::Action::CanidateList canidate_list2;
-       
-       synfigapp::Action::CanidateList::iterator iter;
-       
-       canidate_list=compile_canidate_list(param_list,category);
-       canidate_list2=compile_canidate_list(param_list2,category);
-       
-       canidate_list.sort();
-
-       if(canidate_list.empty())
-               synfig::warning("Action CanidateList is empty!");
-       if(canidate_list2.empty())
-               synfig::warning("Action CanidateList2 is empty!");
-
-       // Seperate out the canidate lists so that there are no conflicts
-       for(iter=canidate_list.begin();iter!=canidate_list.end();++iter)
+       synfigapp::Action::CandidateList candidate_list;
+       synfigapp::Action::CandidateList candidate_list2;
+
+       synfigapp::Action::CandidateList::iterator iter;
+
+       candidate_list=compile_candidate_list(param_list,category);
+       candidate_list2=compile_candidate_list(param_list2,category);
+
+       candidate_list.sort();
+
+       if(candidate_list.empty())
+               synfig::warning("%s:%d Action CandidateList is empty!", __FILE__, __LINE__);
+       if(candidate_list2.empty())
+               synfig::warning("%s:%d Action CandidateList2 is empty!", __FILE__, __LINE__);
+
+       // Separate out the candidate lists so that there are no conflicts
+       for(iter=candidate_list.begin();iter!=candidate_list.end();++iter)
        {
-               synfigapp::Action::CanidateList::iterator iter2(canidate_list2.find(iter->name));
-               if(iter2!=canidate_list2.end())
-                       canidate_list2.erase(iter2);
+               synfigapp::Action::CandidateList::iterator iter2(candidate_list2.find(iter->name));
+               if(iter2!=candidate_list2.end())
+                       candidate_list2.erase(iter2);
        }
-               
-       for(iter=canidate_list2.begin();iter!=canidate_list2.end();++iter)
+
+       for(iter=candidate_list2.begin();iter!=candidate_list2.end();++iter)
        {
                if(!(iter->category&synfigapp::Action::CATEGORY_HIDDEN))
                {
@@ -823,7 +879,7 @@ Instance::add_actions_to_menu(Gtk::Menu *menu, const synfigapp::Action::ParamLis
                }
        }
 
-       for(iter=canidate_list.begin();iter!=canidate_list.end();++iter)
+       for(iter=candidate_list.begin();iter!=candidate_list.end();++iter)
        {
                if(!(iter->category&synfigapp::Action::CATEGORY_HIDDEN))
                {
@@ -875,12 +931,12 @@ Instance::add_actions_to_menu(Gtk::Menu *menu, const synfigapp::Action::ParamLis
 }
 
 void
-Instance::process_action(String name, synfigapp::Action::ParamList param_list)
+Instance::process_action(synfig::String name, synfigapp::Action::ParamList param_list)
 {
        assert(synfigapp::Action::book().count(name));
 
        synfigapp::Action::BookEntry entry(synfigapp::Action::book().find(name)->second);
-       
+
        synfigapp::Action::Handle action(entry.factory());
 
        if(!action)
@@ -888,12 +944,12 @@ Instance::process_action(String name, synfigapp::Action::ParamList param_list)
                synfig::error("Bad Action");
                return;
        }
-       
+
        action->set_param_list(param_list);
 
        synfigapp::Action::ParamVocab param_vocab(entry.get_param_vocab());
        synfigapp::Action::ParamVocab::const_iterator iter;
-       
+
        for(iter=param_vocab.begin();iter!=param_vocab.end();++iter)
        {
                if(!iter->get_mutual_exclusion().empty() && param_list.count(iter->get_mutual_exclusion()))
@@ -922,7 +978,7 @@ Instance::process_action(String name, synfigapp::Action::ParamList param_list)
                        }
                }
        }
-               
+
        if(!action->is_ready())
        {
                synfig::error("Action not ready");
@@ -936,12 +992,12 @@ void
 Instance::make_param_menu(Gtk::Menu *menu,synfig::Canvas::Handle canvas, synfigapp::ValueDesc value_desc, float location)
 {
        Gtk::Menu& parammenu(*menu);
-       
+
        etl::handle<synfigapp::CanvasInterface> canvas_interface(find_canvas_interface(canvas));
-       
+
        if(!canvas_interface)
                return;
-       
+
        synfigapp::Action::ParamList param_list,param_list2;
        param_list=canvas_interface->generate_param_list(value_desc);
        param_list.add("origin",location);
@@ -957,10 +1013,22 @@ Instance::make_param_menu(Gtk::Menu *menu,synfig::Canvas::Handle canvas, synfiga
                param_list2.add("origin",location);
        }
 
-       
+
        // Populate the convert menu by looping through
        // the ValueNode book and find the ones that are
        // relevant.
+
+       // show the 'Convert' sub-menu if this valuedesc is anything other than either:
+       //   the 'Index' parameter of a Duplicate layer
+       // or
+       //   a Duplicate ValueNode whose parent is not a (layer or ValueNode)
+       if (!((value_desc.parent_is_layer_param() &&
+                  value_desc.get_layer()->get_name() == "duplicate" &&
+                  value_desc.get_param_name() == "index") ||
+                 (value_desc.is_value_node() &&
+                  ValueNode_Duplicate::Handle::cast_dynamic(value_desc.get_value_node()) &&
+                  !(value_desc.parent_is_layer_param() ||
+                        value_desc.parent_is_value_node()))))
        {
                Gtk::Menu *convert_menu=manage(new Gtk::Menu());
                LinkableValueNode::Book::const_iterator iter;
@@ -982,7 +1050,7 @@ Instance::make_param_menu(Gtk::Menu *menu,synfig::Canvas::Handle canvas, synfiga
                        }
                }
 
-               parammenu.items().push_back(Gtk::Menu_Helpers::StockMenuElem(Gtk::Stock::CONVERT,*convert_menu));                       
+               parammenu.items().push_back(Gtk::Menu_Helpers::StockMenuElem(Gtk::Stock::CONVERT,*convert_menu));
        }
 
        if(param_list2.empty())
@@ -998,18 +1066,26 @@ Instance::make_param_menu(Gtk::Menu *menu,synfig::Canvas::Handle canvas, synfiga
        if(value_desc.is_value_node() && ValueNode_Animated::Handle::cast_dynamic(value_desc.get_value_node()))
        {
                ValueNode_Animated::Handle value_node(ValueNode_Animated::Handle::cast_dynamic(value_desc.get_value_node()));
-               
+
                try
                {
+                       // try to find a waypoint at the current time - if we
+                       // can't, we don't want the menu entry - an exception is thrown
                        WaypointList::iterator iter(value_node->find(canvas->get_time()));
+                       std::set<synfig::Waypoint, std::less<UniqueID> > waypoint_set;
+                       waypoint_set.insert(*iter);
+
                        parammenu.items().push_back(Gtk::Menu_Helpers::MenuElem(_("Edit Waypoint"),
                                sigc::bind(
                                        sigc::bind(
                                                sigc::bind(
-                                                       sigc::mem_fun(*find_canvas_view(canvas),&studio::CanvasView::on_waypoint_clicked),
+                                                       sigc::bind(
+                                                               sigc::mem_fun(*find_canvas_view(canvas),&studio::CanvasView::on_waypoint_clicked_canvasview),
+                                                               synfig::Waypoint::SIDE_UNSPECIFIED
+                                                       ),
                                                        -1
                                                ),
-                                               *iter
+                                               waypoint_set
                                        ),
                                        value_desc
                                )
@@ -1036,28 +1112,26 @@ edit_several_waypoints(etl::handle<CanvasView> canvas_view, std::list<synfigapp:
        widget_waypoint_model.show();
 
        dialog.get_vbox()->pack_start(widget_waypoint_model);
-       
-       
+
+
        dialog.add_button(Gtk::StockID("gtk-apply"),1);
        dialog.add_button(Gtk::StockID("gtk-cancel"),0);
        dialog.show();
-       
-       DEBUGPOINT();
+
        if(dialog.run()==0 || widget_waypoint_model.get_waypoint_model().is_trivial())
                return;
-       DEBUGPOINT();
        synfigapp::Action::PassiveGrouper group(canvas_interface->get_instance().get(),_("Set Waypoints"));
 
        std::list<synfigapp::ValueDesc>::iterator iter;
        for(iter=value_desc_list.begin();iter!=value_desc_list.end();++iter)
        {
                synfigapp::ValueDesc value_desc(*iter);
-               
+
                if(!value_desc.is_valid())
                        continue;
 
                ValueNode_Animated::Handle value_node;
-               
+
                // If this value isn't a ValueNode_Animated, but
                // it is somewhat constant, then go ahead and convert
                // it to a ValueNode_Animated.
@@ -1068,11 +1142,11 @@ edit_several_waypoints(etl::handle<CanvasView> canvas_view, std::list<synfigapp:
                                value=ValueNode_Const::Handle::cast_dynamic(value_desc.get_value_node())->get_value();
                        else
                                value=value_desc.get_value();
-                       
+
                        value_node=ValueNode_Animated::create(value,canvas_interface->get_time());
-                       
+
                        synfigapp::Action::Handle action;
-                       
+
                        if(!value_desc.is_value_node())
                        {
                                action=synfigapp::Action::create("value_desc_connect");
@@ -1085,11 +1159,11 @@ edit_several_waypoints(etl::handle<CanvasView> canvas_view, std::list<synfigapp:
                                action->set_param("dest",value_desc.get_value_node());
                                action->set_param("src",ValueNode::Handle(value_node));
                        }
-                       
+
                        action->set_param("canvas",canvas_view->get_canvas());
                        action->set_param("canvas_interface",canvas_interface);
-                       
-       
+
+
                        if(!canvas_interface->get_instance()->perform_action(action))
                        {
                                canvas_view->get_ui_interface()->error(_("Unable to convert to animated waypoint"));
@@ -1102,11 +1176,11 @@ edit_several_waypoints(etl::handle<CanvasView> canvas_view, std::list<synfigapp:
                        if(value_desc.is_value_node())
                                value_node=ValueNode_Animated::Handle::cast_dynamic(value_desc.get_value_node());
                }
-               
-               
+
+
                if(value_node)
                {
-                       
+
                        synfigapp::Action::Handle action(synfigapp::Action::create("waypoint_set_smart"));
 
                        if(!action)
@@ -1115,14 +1189,14 @@ edit_several_waypoints(etl::handle<CanvasView> canvas_view, std::list<synfigapp:
                                group.cancel();
                                return;
                        }
-                               
+
 
                        action->set_param("canvas",canvas_view->get_canvas());
-                       action->set_param("canvas_interface",canvas_interface);                 
-                       action->set_param("value_node",ValueNode::Handle(value_node));                  
-                       action->set_param("time",canvas_interface->get_time());                                         
+                       action->set_param("canvas_interface",canvas_interface);
+                       action->set_param("value_node",ValueNode::Handle(value_node));
+                       action->set_param("time",canvas_interface->get_time());
                        action->set_param("model",widget_waypoint_model.get_waypoint_model());
-               
+
                        if(!canvas_interface->get_instance()->perform_action(action))
                        {
                                canvas_view->get_ui_interface()->error(_("Unable to set a specific waypoint"));
@@ -1136,7 +1210,7 @@ edit_several_waypoints(etl::handle<CanvasView> canvas_view, std::list<synfigapp:
                        //group.cancel();
                        //return;
                }
-                       
+
        }
 }