/* === S Y N F I G ========================================================= */
/*!	\file gtkmm/instance.cpp
**	\brief writeme
**
**	$Id$
**
**	\legal
**	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
**	Copyright (c) 2007, 2008 Chris Moore
**	Copyright (c) 2008, 2011 Carlos López
**	Copyright (c) 2009 Nikita Kitaev
**	Copyright (c) 2012 Konstantin Dmitriev
**
**	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 ======================================================= */

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include <synfig/general.h>

#include <cassert>
#include <cerrno>
#include <iostream>

#include <sys/stat.h>
#include <sigc++/sigc++.h>

#include <giomm.h>

#include <gtkmm/stock.h>
#include <gtkmm/image.h>
#include <gtkmm/menuitem.h>
#include <gtkmm/imagemenuitem.h>
#include <gtkmm/separatormenuitem.h>
#include <gtkmm/button.h>
#include <gtkmm/actiongroup.h>

#include <ETL/stringf>

#include <synfig/savecanvas.h>
#include <synfig/canvasfilenaming.h>
#include <synfig/layers/layer_pastecanvas.h>
#include <synfig/layers/layer_bitmap.h>
#include <synfig/valuenode_registry.h>
#include <synfig/valuenodes/valuenode_composite.h>
#include <synfig/valuenodes/valuenode_duplicate.h>
#include <synfig/widthpoint.h>

#include "instance.h"
#include "canvasview.h"
#include "app.h"
#include "onemoment.h"
#include "autorecover.h"
#include "iconcontroller.h"
#include "workarea.h"

#include "docks/dock_toolbox.h"
#include "widgets/widget_waypointmodel.h"

#include <gui/localization.h>

#endif

using namespace std;
using namespace etl;
using namespace synfig;
using namespace studio;
using namespace sigc;

/* === M A C R O S ========================================================= */

/* === G L O B A L S ======================================================= */

int studio::Instance::instance_count_=0;

/* === P R O C E D U R E S ================================================= */

/* === M E T H O D S ======================================================= */

Instance::Instance(synfig::Canvas::Handle canvas, synfig::FileSystem::Handle container):
	synfigapp::Instance		(canvas, container),
	canvas_tree_store_		(Gtk::TreeStore::create(canvas_tree_model)),
	history_tree_store_		(HistoryTreeStore::create(this)),
	undo_status_(false),
	redo_status_(false)
{
	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)));
	signal_undo_status().connect(sigc::mem_fun(*this,&studio::Instance::set_undo_status));
	signal_redo_status().connect(sigc::mem_fun(*this,&studio::Instance::set_redo_status));

	refresh_canvas_tree();
}

Instance::~Instance()
{
}

int
Instance::get_visible_canvases()const
{
	int count(0);
	CanvasViewList::const_iterator iter;
	for(iter=canvas_view_list_.begin();iter!=canvas_view_list_.end();++iter)
		if((*iter)->get_visible())
			count++;
	return count;
}

handle<Instance>
Instance::create(synfig::Canvas::Handle canvas, synfig::FileSystem::Handle container)
{
	// Construct a new instance
	handle<Instance> instance(new Instance(canvas, container));

	// 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;
}

handle<CanvasView>
Instance::find_canvas_view(etl::handle<synfig::Canvas> canvas)
{
	if(!canvas)
		return 0;

	while(canvas->is_inline())
		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(etl::handle<synfig::Canvas> canvas)
{
	handle<CanvasView> canvas_view=find_canvas_view(canvas);
	assert(canvas_view);
	canvas_view->present();
}

void
Instance::set_undo_status(bool x)
{
	undo_status_=x;
	App::dock_toolbox->update_tools();
	signal_undo_redo_status_changed()();
}

void
Instance::set_redo_status(bool x)
{
	redo_status_=x;
	App::dock_toolbox->update_tools();
	signal_undo_redo_status_changed()();
}

void
studio::Instance::run_plugin(std::string plugin_path)
{
	handle<synfigapp::UIInterface> uim = this->find_canvas_view(this->get_canvas())->get_ui_interface();

	string message = strprintf(_("Do you realy want to run plugin for file \"%s\"?" ),
				this->get_canvas()->get_name().c_str());

	string details = strprintf(_("This operation cannot be undone and all undo history will be cleared."));

	int answer = uim->confirmation(
				message,
				details,
				_("Cancel"),
				_("Proceed"),
				synfigapp::UIInterface::RESPONSE_OK);

	if(answer == synfigapp::UIInterface::RESPONSE_OK){

		OneMoment one_moment;

		Canvas::Handle canvas(this->get_canvas());

		synfigapp::PluginLauncher launcher(canvas);

		Time cur_time;
		cur_time = canvas->get_time();

		this->close();

		if(canvas->count()!=1)
		{
			one_moment.hide();
			App::dialog_message_1b(
					"ERROR",
					_("The plugin operation has failed."),
					_("This can be due to current file "
						"being referenced by another composition that is already open, "
						"or because of an internal error in Synfig Studio. Try closing "
						"any compositions that might reference this file and try again, "
						"or restart Synfig Studio."),
					_("Close"));

			one_moment.show();

		} else {
			bool result;
			result = launcher.execute( plugin_path, App::get_base_path() );
			if (!result){
				one_moment.hide();
				App::dialog_message_1b(
						"Error",
						launcher.get_output(),
						"details",
						_("Close"));

				one_moment.show();

			}
		}


		canvas=0;

		App::open_as(launcher.get_result_path(),launcher.get_original_path());


		etl::handle<Instance> new_instance = App::instance_list.back();
		new_instance->inc_action_count(); // This file isn't saved! mark it as such

		// Restore time cursor position
		canvas = App::instance_list.back()->get_canvas();
		etl::handle<synfigapp::CanvasInterface> new_canvas_interface(new_instance->find_canvas_interface(canvas));
		new_canvas_interface->set_time(cur_time);

	}
	return;
}


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
		for(list<handle<CanvasView> >::iterator iter = canvas_view_list().begin(); iter!=canvas_view_list().end(); iter++)
			(*iter)->render_settings.set_entry_filename();
		App::add_recent_file(etl::handle<Instance>(this));

		// check for unsaved layers (external bitmaps, etc)
		std::vector<Layer::Handle> layers;
		find_unsaved_layers(layers);
		if (!layers.empty())
		{
			String message = _("Current canvas saved, but unable to save data for following layers:");
			String details;
			for(std::vector<Layer::Handle>::const_iterator i = layers.begin(); i != layers.end(); ++i)
			{
				details.append((*i)->get_description());
				if ( Layer_Bitmap::Handle::cast_dynamic(*i)
			  	  && (*i)->get_param_list().count("filename") )
			  	{
			  		ValueBase value = (*i)->get_param("filename");
			  		if (value.same_type_as(String()))
			  			details.append(" (" + value.get(String()) + ")");
			  	}
				details.append("\n");
			}
			App::dialog_message_1b("WARNING", message, "details", _("Close"), details);
		}

		return true;
	}
	return false;
}

void
studio::Instance::open()
{
	App::dialog_open(get_file_name());
}

Instance::Status
studio::Instance::save()
{
	// if we don't have a real filename yet then we need to ask where to save it
	if (!has_real_filename())
	{
		if (dialog_save_as())
			return STATUS_OK;
		else
			return STATUS_CANCEL;
	}

	if (!save_as(get_canvas()->get_file_name()))
	{
		string msg(strprintf(_("Unable to save to '%s'"), get_file_name().c_str()));
		App::dialog_message_1b(
				"ERROR",
				msg.c_str(),
				"details",
				_("Close"));
		return STATUS_ERROR;
	}

	return STATUS_OK;
}

// 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 don't have a real filename yet
bool
studio::Instance::has_real_filename()
{
	return get_file_name().find(App::custom_filename_prefix.c_str()) != 0;
}

bool
studio::Instance::dialog_save_as()
{
	string filename = get_file_name();
	Canvas::Handle canvas(get_canvas());

	{
		OneMoment one_moment;
		std::set<Node*>::iterator iter;
		for(iter=canvas->parent_set.begin();iter!=canvas->parent_set.end();++iter)
		{
			synfig::Node* node(*iter);
			for(;!node->parent_set.empty();node=*node->parent_set.begin())
			{
				Layer::Handle parent_layer(dynamic_cast<Layer*>(node));
				if(parent_layer && parent_layer->get_canvas()->get_root()!=get_canvas())
				{
					//! \todo Fix big involving "Save As" with referenced compositions
					string msg(strprintf(_("There is currently a bug when using \"SaveAs\"\n"
						"on a composition that is being referenced by other\n"
						"files that are currently open. Close these\n"
						"other files first before trying to use \"SaveAs\".")));
					App::dialog_message_1b(
							"ERROR",
							msg.c_str(),
							"details",
							_("Close"));

					return false;
				}
				if(parent_layer)
					break;
			}
		}
	}

	if (has_real_filename())
		filename = absolute_path(filename);

	// show the canvas' name if it has one, else its ID
	while (App::dialog_save_file((_("Please choose a file name") +
								  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
		string base_filename = basename(filename);
		if (find(base_filename.begin(),base_filename.end(),'*')!=base_filename.end())
			continue;

		// if file extension is not recognized, then forced to .sifz
		if (filename_extension(filename) == "")
			filename+=".sifz";

		// forced to .sifz, the below code is not need anymore
		try
		{
			String ext(filename_extension(filename));
			// todo: ".sfg" literal and others
			if (ext != ".sif" && ext != ".sifz" && ext != ".sfg" && !App::dialog_message_2b(
				_("Unknown extension"),
				_("You have given the file name an extension which I do not recognize. "
					"Are you sure this is what you want?"),
				Gtk::MESSAGE_QUESTION,
				_("Cancel"),
				_("Sure"))
			)
				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());
				string msg(strprintf(_("Unable to check whether '%s' exists."), filename.c_str()));
				App::dialog_message_1b(
						"ERROR",
						msg.c_str(),
						"details",
						_("Close"));

				continue;
			}

			// If the file exists and the user doesn't want to overwrite it, keep prompting for a filename
			string message = strprintf(_("A file named \"%s\" already exists. "
							"Do you want to replace it?"),
						basename(filename).c_str());

			string details = strprintf(_("The file already exists in \"%s\". "
							"Replacing it will overwrite its contents."),
						basename(dirname(filename)).c_str());

			if ((stat_return == 0) && !App::dialog_message_2b(
				message,
				details,
				Gtk::MESSAGE_QUESTION,
				_("Use Another Name…"),
				_("Replace"))
			)
				continue;
		}

		if(save_as(filename))
		{
			synfig::set_file_version(ReleaseVersion(RELEASE_VERSION_END-1));
			return true;
		}
		string msg(strprintf(_("Unable to save to '%s'"), filename.c_str()));
		App::dialog_message_1b(
				"ERROR",
				msg.c_str(),
				"details",
				_("Close"));
	}

	return false;
}

void
Instance::update_all_titles()
{
	list<handle<CanvasView> >::iterator iter;
	for(iter=canvas_view_list().begin();iter!=canvas_view_list().end();iter++)
		(*iter)->update_title();
}

void
Instance::close()
{
	// This will increase the reference count so we don't get DELETED
	// until we are ready
	handle<Instance> me(this);

	/*
	We need to hide some panels when instance is closed.
	This is done to avoid the crash when two conditions met:
	 1) the list is scrolled down
	 2) user closes file
	*/
	handle<CanvasView> canvas_view=find_canvas_view(get_canvas());
	Gtk::Widget* tree_view_keyframes = canvas_view->get_ext_widget("keyframes");
	tree_view_keyframes->hide();

	Gtk::Widget* tree_view_params = canvas_view->get_ext_widget("params");
	tree_view_params->hide();

	Gtk::Widget* tree_view_children = canvas_view->get_ext_widget("children");
	tree_view_children->hide();

	// Make sure we aren't selected as the current instance
	if(studio::App::get_selected_instance()==this)
		studio::App::set_selected_instance(0);

	// Remove us from the active instance list
	std::list<etl::handle<studio::Instance> >::iterator iter;
	for(iter=studio::App::instance_list.begin();iter!=studio::App::instance_list.end();iter++)
		if(*iter==this)
			break;
	assert(iter!=studio::App::instance_list.end());
	if(iter!=studio::App::instance_list.end())
		studio::App::instance_list.erase(iter);

	// Send out a signal that we are being deleted
	studio::App::signal_instance_deleted()(this);

	// Hide all of the canvas views
	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(true);

	// Delete all of the canvas views
	canvas_view_list().clear();

	// If there is another open instance to select,
	// go ahead and do so. If not, never mind.
	if(studio::App::instance_list.empty())
	{
		studio::App::set_selected_canvas_view(0);
		studio::App::set_selected_instance(0);
	}
	else if(studio::App::instance_list.size() == 1)
	{
		studio::App::instance_list.front()->canvas_view_list().front()->present();
	}
}

void
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_pixbuf(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())
		row[canvas_tree_model.label] = basename(canvas->get_file_name());
	else
	if(!canvas->get_id().empty())
		row[canvas_tree_model.label] = canvas->get_id();
	else
	if(!canvas->get_name().empty())
		row[canvas_tree_model.label] = canvas->get_name();
	else
		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;

	{
		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);
	}
}

void
Instance::refresh_canvas_tree()
{
	canvas_tree_store()->clear();
	Gtk::TreeRow row = *(canvas_tree_store()->prepend());
	insert_canvas(row,get_canvas());
}

void
Instance::dialog_cvs_commit()
{
	calc_repository_info();
	if(!in_repository())
	{
		App::dialog_message_1b(
				"ERROR",
				_("You must first add this composition to the repository"),
				"details",
				_("Close"));

		return;
	}
	try
	{
		string message;

		if(synfigapp::Instance::get_action_count())
		{
			if (!App::dialog_message_2b(
				_("CVS Commit"),
				_("This will save any changes you have made. Are you sure?"),
				Gtk::MESSAGE_QUESTION,
				_("Cancel"),
				_("Commit"))
			)
				return;

			save();
		}

		if(!is_modified())
		{
			App::dialog_message_1b(
					"ERROR",
					_("The local copy of the file hasn't been changed since the last update. Nothing to commit!"),
					"details",
					_("Close"));

			return;
		}

		if(!App::dialog_entry(_("CVS Commit"),
				_("Log Message: "),
				message,
				_("Cancel"),
				_("Commit"))
		)
			return;

		OneMoment one_moment;
		cvs_commit(message);
	}
	catch(...)
	{
		App::dialog_message_1b(
				"ERROR",
				_("An error has occurred when trying to COMMIT"),
				"details",
				_("Close"));
	}
	update_all_titles();
}

void
Instance::dialog_cvs_add()
{
	calc_repository_info();
	if(in_repository())
	{
		App::dialog_message_1b(
				"ERROR",
				_("This composition has already been added to the repository"),
				"details",
				_("Close"));
		return;
	}
	try
	{
		string message;

		//if(!App::dialog_entry(_("CVS Add"),_("Enter a log message describing the file"), message))
		//	return;
		OneMoment one_moment;
		cvs_add();
	}
	catch(...)
	{
		App::dialog_message_1b(
				"ERROR",
				_("An error has occurred when trying to ADD"),
				"details",
				_("Close"));
	}
	update_all_titles();
}

void
Instance::dialog_cvs_update()
{
	calc_repository_info();
	if(!in_repository())
	{
		App::dialog_message_1b(
				"ERROR",
				_("This file is not under version control, so there is nothing to update from!"),
				"details",
				_("Close"));

		return;
	}
	if(!is_updated())
	{
		App::dialog_message_1b(
				"INFO",
				_("This file is up-to-date"),
				"details",
				_("Close"));

		return;
	}

	try
	{
		String filename(get_file_name());
		if(synfigapp::Instance::get_action_count())
		{
			if (!App::dialog_message_2b(
				_("CVS Update"),
				_("This will save any changes you have made. Are you sure?"),
				Gtk::MESSAGE_QUESTION,
				_("Cancel"),
				_("Update"))
			)
				return;

			save();
		}
		OneMoment one_moment;
		time_t oldtime=get_original_timestamp();
		cvs_update();
		calc_repository_info();
		// If something has been updated...
		if(oldtime!=get_original_timestamp())
		{
			revert();
		}
	}
	catch(...)
	{
		App::dialog_message_1b(
				"ERROR",
				_("An error has occurred when trying to UPDATE"),
				"details",
				_("Close"));
	}
	//update_all_titles();
}

void
Instance::dialog_cvs_revert()
{
	calc_repository_info();
	if(!in_repository())
{
		App::dialog_message_1b(
				"ERROR",
				_("This file is not under version control, so there is nothing to revert to!"),
				"details",
				_("Close"));
		return;
	}
	try
	{
		String filename(get_file_name());

		if (!App::dialog_message_2b(
			_("CVS Revert"),
			_("This will abandon all changes you have made since the last time you "
				"performed a commit operation. This cannot be undone! "
				"Are you sure you want to do this?"),
			Gtk::MESSAGE_QUESTION,
			_("Cancel"),
			_("Revert"))
		)
			return;

		OneMoment one_moment;

		// Remove the old file
		if(remove(get_file_name().c_str())!=0)
		{
			App::dialog_message_1b(
					"ERROR",
					_("Unable to remove previous version"),
					"details",
					_("Close"));

			return;
		}

		cvs_update();
		revert();
	}
	catch(...)
	{
		App::dialog_message_1b(
				"ERROR",
				_("An error has occurred when trying to UPDATE"),
				"details",
				_("Close"));
	}
	//update_all_titles();
}

void
Instance::revert()
{
	OneMoment one_moment;

	String filename(get_file_name());

	Canvas::Handle canvas(get_canvas());

	close();

	if(canvas->count()!=1)
	{
		one_moment.hide();
		App::dialog_message_1b(
				"ERROR",
				_("The revert operation has failed."),
				_("This can be due to it being referenced by another composition"
					" that is already open, or because of an internal error "
					"in Synfig Studio. Try closing any compositions that "
					"might reference this composition and try again, or "
					"restart Synfig Studio."),
				_("Close"));

		one_moment.show();
	}
	canvas=0;

	App::open(filename);
}

bool
Instance::safe_revert()
{
	if(synfigapp::Instance::get_action_count())
	{
		if (!App::dialog_message_2b(
			_("Revert to saved"),
			_("You will lose any changes you have made since your last save."
				"Are you sure?"),
			Gtk::MESSAGE_QUESTION,
			_("Cancel"),
			_("Revert"))
		)
			return false;
	}

	revert();
	return true;
}

bool
Instance::safe_close()
{
	handle<CanvasView> canvas_view = find_canvas_view(get_canvas());
	handle<synfigapp::UIInterface> uim=canvas_view->get_ui_interface();

	// if the animation is currently playing, closing the window will cause a crash,
	// so don't allow it
	if (canvas_view->is_playing())
	{
		canvas_view->present();
		App::dialog_message_1b(
				"ERROR",
				_("The animation is currently playing so the window cannot be closed."),
				"details",
				_("Thanks!"));

		return false;
	}
	if(get_action_count())
		do
		{
			string message = strprintf(_("Save changes to document \"%s\" before closing?"),
					basename(get_file_name()).c_str() );

			string details = (_("If you don't save, changes from the last time you saved "
					"will be permanently lost."));

			int answer=uim->yes_no_cancel(
						message,
						details,
						_("Close without Saving"),
						_("Cancel"),
						_("Save"),
						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())
	{
		string message = strprintf(_("Commit changes of \"%s\" to  the CVS repository?"),
				basename(get_file_name()).c_str());

		string details = (_("If you don't commit, changes not yet on the CVS repository will "
				"be permanently lost."));

		int answer=uim->yes_no_cancel(
					message,
					details,
					_("Close without Committing"),
					_("Cancel"),
					_("Commit…"),
					synfigapp::UIInterface::RESPONSE_YES
		);

		if(answer==synfigapp::UIInterface::RESPONSE_YES)
			dialog_cvs_commit();
		if(answer==synfigapp::UIInterface::RESPONSE_CANCEL)
			return false;
	}

	close();

	return true;
}

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::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(
				"action-"+iter->name,
				stock_id,
				iter->local_name,iter->local_name
			),
				sigc::bind(
					sigc::bind(
						sigc::mem_fun(
							*const_cast<studio::Instance*>(this),
							&studio::Instance::process_action
						),
						param_list
					),
					iter->name
				)
			);
			ui_info+=strprintf("<menuitem action='action-%s' />",iter->name.c_str());
		}
	}
}

void
Instance::add_actions_to_menu(Gtk::Menu *menu, const synfigapp::Action::ParamList &param_list,synfigapp::Action::Category category)const
{
	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::MenuItem *item = Gtk::manage(new Gtk::ImageMenuItem(
				*Gtk::manage(new Gtk::Image(get_action_stock_id(*iter),Gtk::ICON_SIZE_MENU)),
				iter->local_name ));
			item->signal_activate().connect(
				sigc::bind(
					sigc::bind(
						sigc::mem_fun(
							*const_cast<studio::Instance*>(this),
							&studio::Instance::process_action ),
						param_list ),
					iter->name ));
			item->show_all();
			menu->append(*item);
		}
	}
}

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::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::CandidateList::iterator iter2(candidate_list2.find(iter->name));
		if(iter2!=candidate_list2.end())
			candidate_list2.erase(iter2);
	}

	for(iter=candidate_list2.begin();iter!=candidate_list2.end();++iter)
	{
		if(!(iter->category&synfigapp::Action::CATEGORY_HIDDEN))
		{
			Gtk::MenuItem *item = Gtk::manage(new Gtk::ImageMenuItem(
				*Gtk::manage(new Gtk::Image(get_action_stock_id(*iter),Gtk::ICON_SIZE_MENU)),
				iter->local_name ));
			item->signal_activate().connect(
				sigc::bind(
					sigc::bind(
						sigc::mem_fun(
							*const_cast<studio::Instance*>(this),
							&studio::Instance::process_action ),
						param_list2 ),
					iter->name ));
			item->show_all();
			menu->append(*item);
		}
	}

	for(iter=candidate_list.begin();iter!=candidate_list.end();++iter)
	{
		if(!(iter->category&synfigapp::Action::CATEGORY_HIDDEN))
		{
			Gtk::MenuItem *item = Gtk::manage(new Gtk::ImageMenuItem(
				*Gtk::manage(new Gtk::Image(get_action_stock_id(*iter),Gtk::ICON_SIZE_MENU)),
				iter->local_name ));
			item->signal_activate().connect(
				sigc::bind(
					sigc::bind(
						sigc::mem_fun(
							*const_cast<studio::Instance*>(this),
							&studio::Instance::process_action ),
						param_list ),
					iter->name ));
			item->show_all();
			menu->append(*item);
		}
	}
}

void
Instance::process_action(synfig::String name, synfigapp::Action::ParamList param_list)
{
	if (getenv("SYNFIG_DEBUG_ACTIONS"))
		synfig::info("%s:%d process_action: '%s'", __FILE__, __LINE__, name.c_str());

	assert(synfigapp::Action::book().count(name));

	synfigapp::Action::BookEntry entry(synfigapp::Action::book().find(name)->second);

	synfigapp::Action::Handle action(entry.factory());

	if(!action)
	{
		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()))
			continue;

		// If the parameter is optionally user-supplied,
		// and has not been already provided in the param_list,
		// then we should go ahead and see if we can
		// provide that data.
		if(iter->get_user_supplied() && param_list.count(iter->get_name())==0)
		{
			switch(iter->get_type())
			{
			case synfigapp::Action::Param::TYPE_STRING:
			{
				String str;
				if(iter->get_value_provided())
				{
					synfigapp::Action::Param param;
					if (action->get_param(iter->get_name(),param))
					{
						if(param.get_type()==synfigapp::Action::Param::TYPE_STRING)
							str = param.get_string();
					}
				}
				String button2 = _("Export");
				String label = _("Name: ");

				// export and rename value dialog
				if (entry.name == "ValueNodeRename") button2 = _("Rename");
				// set layer description dialog
				if (entry.name == "LayerSetDesc")
				{
					button2 = _("Set");
					label = _("Description: ");
				}

				if(!studio::App::dialog_entry(entry.local_name,
							label,
							//iter->get_local_name()+": "+iter->get_desc(),
							str,
							_("Cancel"),
							button2))
					return;
				action->set_param(iter->get_name(),str);
				break;
			}
			default:
				synfig::error("Unsupported user-supplied action parameter");
				return;
				break;
			}
		}
	}

	if(!action->is_ready())
	{
		synfig::error("Action not ready");
		return;
	}

	perform_action(action);
}

void
Instance::make_param_menu(Gtk::Menu *menu,synfig::Canvas::Handle canvas, synfigapp::ValueDesc value_desc, float location, bool bezier)
{
	Gtk::Menu& parammenu(*menu);
	synfigapp::ValueDesc value_desc2(value_desc);
	etl::handle<synfigapp::CanvasInterface> canvas_interface(find_canvas_interface(canvas));

	if(!canvas_interface)
		return;

	Gtk::MenuItem *item = NULL;

	synfigapp::Action::ParamList param_list,param_list2;
	param_list=canvas_interface->generate_param_list(value_desc);
	param_list.add("origin",location);

#ifdef BLINEPOINT_MENU_IS_VERTEX_MENU
	if(value_desc.get_value_type()==type_bline_point && value_desc.is_value_node() && ValueNode_Composite::Handle::cast_dynamic(value_desc.get_value_node()))
	{
		param_list2=canvas_interface->generate_param_list(
			synfigapp::ValueDesc(
				ValueNode_Composite::Handle::cast_dynamic(value_desc.get_value_node())
				,ValueNode_Composite::Handle::cast_dynamic(value_desc.get_value_node())
                                                           ->get_link_index_from_name("point")
			)
		);
		param_list2.add("origin",location);
	}
#endif	// BLINEPOINT_MENU_IS_VERTEX_MENU

	// 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() &&
		   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() ||
			 value_desc.parent_is_value_node()))))
	{
		Gtk::Menu *convert_menu=Gtk::manage(new Gtk::Menu());
		ValueNodeRegistry::Book::const_iterator iter;
		for(iter=ValueNodeRegistry::book().begin();iter!=ValueNodeRegistry::book().end();++iter)
		{
			if(iter->second.check_type(value_desc.get_value_type()))
			{
				item = Gtk::manage(new Gtk::MenuItem(iter->second.local_name));
				item->signal_activate().connect(
					sigc::hide_return(
						sigc::bind(
							sigc::bind(
								sigc::mem_fun(*canvas_interface.get(),&synfigapp::CanvasInterface::convert),
								iter->first ),
							value_desc )));
				item->show();
				convert_menu->append(*item);
			}
		}

		item = Gtk::manage(new Gtk::ImageMenuItem(
			*manage(new Gtk::Image(
				Gtk::StockID("gtk-convert"),
				Gtk::ICON_SIZE_MENU )),
			_("Convert") ));
		item->set_submenu(*convert_menu);
		item->show();
		parammenu.append(*item);
	}

	// Interpolation menu: Show it only if
	// the value description is constant or animated (layer parameter or exported) and not static
	// and value is not canvas type
	if (((value_desc.is_const() && !value_desc.get_static()) || value_desc.is_animated())
	&&
		value_desc.get_value_type()!= type_canvas
		)
	{
		Gtk::Menu *param_interpolation_menu=Gtk::manage(new Gtk::Menu());
		synfigapp::Action::ParamList param_list;
		param_list.add("canvas", get_canvas());
		param_list.add("value_desc", value_desc);
		param_list.add("canvas_interface",canvas_interface);

		/////// Default
		param_list.add("new_value", INTERPOLATION_UNDEFINED);
		item = Gtk::manage(new Gtk::MenuItem(_("Default")));
		item->signal_activate().connect(
			sigc::bind(
				sigc::bind(
					sigc::mem_fun(*const_cast<studio::Instance*>(this),&studio::Instance::process_action),
					param_list ),
				"ValueDescSetInterpolation" ));
		item->show();
		param_interpolation_menu->append(*item);
		param_list.erase("new_value");

		#define ADD_IMAGE_MENU_ITEM(Interpolation, StockId, Text) \
		param_list.add("new_value", Interpolation); \
		item = Gtk::manage(new Gtk::ImageMenuItem( \
			*Gtk::manage(new Gtk::Image(Gtk::StockID(StockId), Gtk::IconSize::from_name("synfig-small_icon"))), \
			_(Text) )); \
		item->signal_activate().connect( \
			sigc::bind( \
				sigc::bind( \
					sigc::mem_fun(*const_cast<studio::Instance*>(this),&studio::Instance::process_action), \
					param_list ), \
				"ValueDescSetInterpolation" )); \
		item->show_all(); \
		param_interpolation_menu->append(*item); \
		param_list.erase("new_value");


		ADD_IMAGE_MENU_ITEM(INTERPOLATION_TCB, "synfig-interpolation_type_tcb", _("TCB"));
		ADD_IMAGE_MENU_ITEM(INTERPOLATION_LINEAR, "synfig-interpolation_type_linear", _("Linear"));
		ADD_IMAGE_MENU_ITEM(INTERPOLATION_HALT, "synfig-interpolation_type_ease", _("Ease"));
		ADD_IMAGE_MENU_ITEM(INTERPOLATION_CONSTANT, "synfig-interpolation_type_const", _("Constant"));
		ADD_IMAGE_MENU_ITEM(INTERPOLATION_CLAMPED, "synfig-interpolation_type_clamped", _("Clamped"));

		#undef ADD_IMAGE_MENU_ITEM

		item = Gtk::manage(new Gtk::MenuItem(_("Interpolation")));
		item->set_submenu(*param_interpolation_menu);
		item->show();
		parammenu.append(*item);
	}

	synfigapp::Action::Category categories = synfigapp::Action::CATEGORY_VALUEDESC|synfigapp::Action::CATEGORY_VALUENODE;
	if (bezier)
		categories = categories|synfigapp::Action::CATEGORY_BEZIER;

	const DuckList selected_ducks(find_canvas_view(canvas)->get_work_area()->get_selected_ducks());
	for(DuckList::const_iterator iter=selected_ducks.begin();iter!=selected_ducks.end();++iter)
	{
		synfigapp::ValueDesc selected_value_desc((*iter)->get_value_desc());
		if(selected_value_desc.is_valid() && value_desc != selected_value_desc)
			param_list.add("selected_value_desc",selected_value_desc);
	}

	if(param_list2.empty())
		add_actions_to_menu(&parammenu, param_list,categories);
	else
		add_actions_to_menu(&parammenu, param_list2,param_list,categories);

	if((value_desc2.get_value_type()==type_bline_point || value_desc2.get_value_type()==type_width_point)
	 && value_desc2.is_value_node() && ValueNode_Composite::Handle::cast_dynamic(value_desc2.get_value_node()))
	{
		// the index=0 is position for widthpoint and vertex for blinepoint
		value_desc2=synfigapp::ValueDesc(ValueNode_Composite::Handle::cast_dynamic(value_desc2.get_value_node()),0);
	}

	if(value_desc2.is_value_node() && ValueNode_Animated::Handle::cast_dynamic(value_desc2.get_value_node()))
	{
		ValueNode_Animated::Handle value_node(ValueNode_Animated::Handle::cast_dynamic(value_desc2.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);

			item = Gtk::manage(new Gtk::MenuItem(_("Edit Waypoint")));
			item->signal_activate().connect(
				sigc::bind(
					sigc::bind(
						sigc::bind(
							sigc::mem_fun(*find_canvas_view(canvas),&studio::CanvasView::on_waypoint_clicked_canvasview),
							-1 ),
						waypoint_set ),
					value_desc2 ));
			item->show();
			parammenu.append(*item);
		}
		catch(...)
		{ }
	}
	//// Add here the rest of actions here for specific single value descriptions
	//
	// Specific actions for Widthpoints (Composite)
	if(value_desc.get_value_type()==type_width_point && value_desc.is_value_node() && ValueNode_Composite::Handle::cast_dynamic(value_desc.get_value_node()))
	{
		ValueNode_Composite::Handle wpoint_composite(ValueNode_Composite::Handle::cast_dynamic(value_desc.get_value_node()));
		synfigapp::Action::ParamList param_list;
		param_list.add("canvas",canvas);
		param_list.add("canvas_interface",canvas_interface);
		param_list.add("time",canvas_interface->get_time());
		item = Gtk::manage(new Gtk::SeparatorMenuItem());
		item->show();
		parammenu.append(*item);


		#define ADD_IMAGE_MENU_ITEM(Type, StockId, Text) \
			param_list.add("new_value", ValueBase((int)WidthPoint::Type)); \
			item = Gtk::manage(new Gtk::ImageMenuItem( \
				*Gtk::manage(new Gtk::Image(Gtk::StockID(StockId),Gtk::IconSize::from_name("synfig-small_icon"))), \
				_(Text) )); \
			item->signal_activate().connect( \
				sigc::bind( \
					sigc::bind( \
						sigc::mem_fun(*const_cast<studio::Instance*>(this),&studio::Instance::process_action), \
						param_list ), \
					"ValueDescSet" )); \
			item->show_all(); \
			parammenu.append(*item); \
			param_list.erase("new_value");

		////// Before //////////////////
		param_list.add("value_desc",synfigapp::ValueDesc(wpoint_composite, wpoint_composite->get_link_index_from_name("side_before")));

		ADD_IMAGE_MENU_ITEM(TYPE_INTERPOLATE, "synfig-interpolate_interpolation", "Cusp Before: Interpolate")
		ADD_IMAGE_MENU_ITEM(TYPE_ROUNDED, "synfig-rounded_interpolation", "Cusp Before: Rounded")
		ADD_IMAGE_MENU_ITEM(TYPE_SQUARED, "synfig-squared_interpolation", "Cusp Before: Squared")
		ADD_IMAGE_MENU_ITEM(TYPE_PEAK, "synfig-peak_interpolation", "Cusp Before: Peak")
		ADD_IMAGE_MENU_ITEM(TYPE_FLAT, "synfig-flat_interpolation", "Cusp Before: Flat")

		///////
		item = Gtk::manage(new Gtk::SeparatorMenuItem());
		item->show();
		parammenu.append(*item);

		////// After ///////////////////////
		param_list.erase("value_desc");
		param_list.add("value_desc",synfigapp::ValueDesc(wpoint_composite, wpoint_composite->get_link_index_from_name("side_after")));

		ADD_IMAGE_MENU_ITEM(TYPE_INTERPOLATE, "synfig-interpolate_interpolation", "Cusp After: Interpolate")
		ADD_IMAGE_MENU_ITEM(TYPE_ROUNDED, "synfig-rounded_interpolation", "Cusp After: Rounded")
		ADD_IMAGE_MENU_ITEM(TYPE_SQUARED, "synfig-squared_interpolation", "Cusp After: Squared")
		ADD_IMAGE_MENU_ITEM(TYPE_PEAK, "synfig-peak_interpolation", "Cusp After: Peak")
		ADD_IMAGE_MENU_ITEM(TYPE_FLAT, "synfig-flat_interpolation", "Cusp After: Flat")

		///////
		item = Gtk::manage(new Gtk::SeparatorMenuItem());
		item->show();
		parammenu.append(*item);

		/////// Set WIDTH to ZERO
		param_list.erase("value_desc");
		param_list.erase("new_value");
		param_list.add("value_desc",synfigapp::ValueDesc(wpoint_composite, wpoint_composite->get_link_index_from_name("width")));
		param_list.add("new_value", ValueBase(Real(0.0)));
		item = Gtk::manage(new Gtk::MenuItem(_("Set width to zero")));
		item->signal_activate().connect(
			sigc::bind(
				sigc::bind(
					sigc::mem_fun(*const_cast<studio::Instance*>(this),&studio::Instance::process_action),
					param_list ),
				"ValueDescSet" ));
		item->show();
		parammenu.append(*item);

		/////// Set WIDTH to DEFAULT
		param_list.erase("new_value");
		param_list.add("value_desc",synfigapp::ValueDesc(wpoint_composite, wpoint_composite->get_link_index_from_name("width")));
		param_list.add("new_value", ValueBase(Real(1.0)));
		item = Gtk::manage(new Gtk::MenuItem(_("Set width to default")));
		item->signal_activate().connect(
			sigc::bind(
				sigc::bind(
					sigc::mem_fun(*const_cast<studio::Instance*>(this),&studio::Instance::process_action),
					param_list ),
				"ValueDescSet" ));
		item->show();
		parammenu.append(*item);
	}
}

void
edit_several_waypoints(etl::handle<CanvasView> canvas_view, std::list<synfigapp::ValueDesc> value_desc_list)
{
	etl::handle<synfigapp::CanvasInterface> canvas_interface(canvas_view->canvas_interface());

	Gtk::Dialog dialog(
		"Edit Multiple Waypoints",
		true
	);

	Widget_WaypointModel widget_waypoint_model;
	widget_waypoint_model.show();

	dialog.get_vbox()->pack_start(widget_waypoint_model);

	dialog.add_button(_("Cancel"), 0);
	dialog.add_button(_("Apply"), 1);
	dialog.show();

	if(dialog.run()==0 || widget_waypoint_model.get_waypoint_model().is_trivial())
		return;
	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;
		// Check if we are dealing with a BLinePoint or a WidthPoint value desc
		// If so, then change the value desc to be the position or the point.

		if(value_desc.is_value_node() && value_desc.parent_is_linkable_value_node())
		{
			synfig::ValueNode_Composite::Handle compo(synfig::ValueNode_Composite::Handle::cast_dynamic(value_desc.get_value_node()));
			if(compo && compo->get_type() == type_width_point)
			{
				value_desc=synfigapp::ValueDesc(compo, compo->get_link_index_from_name("position"));
				//value_node=ValueNode_Animated::Handle::cast_dynamic(compo->get_link(compo->get_link_index_from_name("position")));
			}
			if(compo && compo->get_type() == type_bline_point)
			{
				value_desc=synfigapp::ValueDesc(compo, compo->get_link_index_from_name("point"));
				//value_node=ValueNode_Animated::Handle::cast_dynamic(compo->get_link(compo->get_link_index_from_name("point")));
			}
		}

		// If this value isn't a ValueNode_Animated, but
		// it is somewhat constant, then go ahead and convert
		// it to a ValueNode_Animated.
		if(!value_desc.is_value_node() || ValueNode_Const::Handle::cast_dynamic(value_desc.get_value_node()))
		{
			ValueBase value;
			if(value_desc.is_value_node())
				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("ValueDescConnect");
				action->set_param("dest",value_desc);
				action->set_param("src",ValueNode::Handle(value_node));
			}
			else
			{
				action=synfigapp::Action::create("ValueNodeReplace");
				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"));
				group.cancel();
				return;
			}
		}
		else
		{
			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("WaypointSetSmart"));

			if(!action)
			{
				canvas_view->get_ui_interface()->error(_("Unable to find WaypointSetSmart action"));
				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("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"));
				group.cancel();
				return;
			}
		}
		else
		{
			//get_canvas_view()->get_ui_interface()->error(_("Unable to animate a specific valuedesc"));
			//group.cancel();
			//return;
		}

	}
}

void
Instance::make_param_menu(Gtk::Menu *menu,synfig::Canvas::Handle canvas,const std::list<synfigapp::ValueDesc>& value_desc_list, const synfigapp::ValueDesc &value_desc)
{
	etl::handle<synfigapp::CanvasInterface> canvas_interface(find_canvas_interface(canvas));

	synfigapp::Action::ParamList param_list;
	param_list=canvas_interface->generate_param_list(value_desc_list);

	add_actions_to_menu(menu, param_list,synfigapp::Action::CATEGORY_VALUEDESC|synfigapp::Action::CATEGORY_VALUENODE);

	// Add the edit waypoints option if that might be useful
	if(canvas->rend_desc().get_time_end()-Time::epsilon()>canvas->rend_desc().get_time_start())
	{
		Gtk::MenuItem *item = Gtk::manage(new Gtk::MenuItem(_("Edit Waypoints")));
		item->signal_activate().connect(
			sigc::bind(
				sigc::bind(
					sigc::ptr_fun(&edit_several_waypoints),
					value_desc_list ),
				find_canvas_view(canvas) ));
		item->show();
		menu->append(*item);
	}
	// add here the rest of specific actions for multiple selected value_descs
	if (value_desc.is_valid())
		make_param_menu(menu,canvas,value_desc, 0.f, false);
}


void
Instance::gather_uri(std::set<synfig::String> &x, const synfig::ValueNode::Handle &value_node) const
{
	if (!value_node || !value_node->get_parent_canvas()) return;

	LinkableValueNode::Handle linkable_value_node = LinkableValueNode::Handle::cast_dynamic(value_node);
	if (!linkable_value_node) return;

	FileSystem::Handle file_system = value_node->get_parent_canvas()->get_file_system();
	if (!file_system) return;

	Time t = value_node->get_parent_canvas()->get_time();

	ParamVocab vocab = linkable_value_node->get_children_vocab();
	for(ParamVocab::const_iterator i = vocab.begin(); i != vocab.end(); ++i)
	{
		ValueNode::Handle child_node = linkable_value_node->get_link(i->get_name());
		if (!child_node) continue;

		if (i->get_hint() == "filename")
		{
			ValueBase v = (*child_node)(t);
			if (v.can_get(String()))
			{
				String filename = CanvasFileNaming::make_full_filename(value_node->get_parent_canvas()->get_file_name(), v.get(String()));
				String uri = file_system->get_real_uri(filename);
				if (!uri.empty()) x.insert(uri);
			}
		}

		gather_uri(x, child_node);
	}
}

void
Instance::gather_uri(std::set<synfig::String> &x, const synfig::Layer::Handle &layer) const
{
	if (!layer || !layer->get_canvas()) return;

	FileSystem::Handle file_system = layer->get_canvas()->get_file_system();
	if (!file_system) return;

	ParamVocab vocab = layer->get_param_vocab();
	for(ParamVocab::const_iterator i = vocab.begin(); i != vocab.end(); ++i)
	{
		if (i->get_hint() == "filename")
		{
			ValueBase v = layer->get_param(i->get_name());
			if (v.can_get(String()))
			{
				String filename = CanvasFileNaming::make_full_filename(layer->get_canvas()->get_file_name(), v.get(String()));
				String uri = file_system->get_real_uri(filename);
				if (!uri.empty()) x.insert(uri);
			}
		}

		Layer::DynamicParamList::const_iterator j = layer->dynamic_param_list().find(i->get_name());
		if (j != layer->dynamic_param_list().end() && j->second)
			gather_uri(x, j->second);
	}

	//if (etl::handle<Layer_PasteCanvas> paste = etl::handle<Layer_PasteCanvas>::cast_dynamic(layer))
	//	gather_uri(x, paste->get_param("canvas").get(Canvas::Handle()));
}

void
Instance::gather_uri(std::set<synfig::String> &x, const synfig::Canvas::Handle &canvas) const
{
	if (!canvas) return;

	FileSystem::Handle file_system = canvas->get_file_system();
	if (!file_system) return;

	for(Canvas::const_iterator i = canvas->begin(); i != canvas->end(); ++i)
		gather_uri(x, *i);
}

void
Instance::gather_uri(std::set<synfig::String> &x, const synfigapp::SelectionManager::LayerList &layers) const
{
	for(synfigapp::SelectionManager::LayerList::const_iterator i = layers.begin(); i != layers.end(); ++i)
		gather_uri(x, *i);
}

void
Instance::gather_uri(std::map<synfig::String, synfig::String> &x, const synfigapp::SelectionManager::LayerList &layers) const
{
	std::set<String> uri_set;
	gather_uri(uri_set, layers);
	std::vector<String> uri_list;
	std::vector<String> filename_list;
	for(std::set<String>::const_iterator i = uri_set.begin(); i != uri_set.end(); ++i)
	{
		String filename = Glib::filename_from_uri(*i);
		if (!filename.empty())
		{
			uri_list.push_back(*i);
			filename_list.push_back(filename);
		}
	}

	std::vector<String> shortnames;
	MainWindow::make_short_filenames(filename_list, shortnames);
	for(int i = 0; i < (int)uri_list.size() && i < (int)shortnames.size(); ++i)
		x[shortnames[i]] = uri_list[i];
}

void
Instance::add_special_layer_actions_to_menu(Gtk::Menu *menu, const synfig::Layer::Handle &layer) const
{
	add_special_layer_actions_to_menu(menu, synfigapp::SelectionManager::LayerList(1, layer));
}

void
Instance::add_special_layer_actions_to_group(const Glib::RefPtr<Gtk::ActionGroup>& action_group, synfig::String& ui_info, const synfig::Layer::Handle &layer) const
{
	add_special_layer_actions_to_group(action_group, ui_info, synfigapp::SelectionManager::LayerList(1, layer));
}

void
Instance::add_special_layer_actions_to_menu(Gtk::Menu *menu, const synfigapp::SelectionManager::LayerList &layers) const
{
	std::map<String, String> uris;
	gather_uri(uris, layers);
	for(std::map<String, String>::const_iterator i = uris.begin(); i != uris.end(); ++i)
	{
		Gtk::MenuItem *item = manage(new Gtk::ImageMenuItem(Gtk::Stock::OPEN));
		item->set_label( (String(_("Open file")) + " '" + i->first + "'").c_str() );
		item->signal_activate().connect(
			sigc::bind(sigc::ptr_fun(&App::open_uri), i->second) );
		item->show();
		menu->append(*item);
	}
}

void
Instance::add_special_layer_actions_to_group(const Glib::RefPtr<Gtk::ActionGroup>& action_group, synfig::String& ui_info, const synfigapp::SelectionManager::LayerList &layers) const
{
	std::map<String, String> uris;
	gather_uri(uris, layers);
	int index = 0;
	for(std::map<String, String>::const_iterator i = uris.begin(); i != uris.end(); ++i, ++index)
	{
		String action_name = etl::strprintf("special-action-open-file-%d", index);
		String local_name = String(_("Open file")) + " '" + i->first + "'";
		action_group->add(
			Gtk::Action::create(
				action_name,
				Gtk::Stock::OPEN,
				local_name, local_name ),
			sigc::bind(sigc::ptr_fun(&App::open_uri), i->second) );
		ui_info += strprintf("<menuitem action='%s' />", action_name.c_str());
	}
}
