Blob Blame Raw
/* === S Y N F I G ========================================================= */
/*!	\file app.cpp
**	\brief writeme
**
**	$Id$
**
**	\legal
**	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
**	Copyright (c) 2007, 2008 Chris Moore
**	Copyright (c) 2008 Gerald Young
**	Copyright (c) 2008, 2010-2013 Carlos López
**	Copyright (c) 2009, 2011 Nikita Kitaev
**	Copyright (c) 2012-2015 Konstantin Dmitriev
**	Copyright (c) 2013-2016 Jerome Blanchi
**
**	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 <fstream>
#include <iostream>
#include <locale>
#include <cstring>

#ifdef __OpenBSD__
#include <errno.h>
#elif defined(HAVE_SYS_ERRNO_H)
#include <sys/errno.h>
#endif
#include <gtkmm/accelmap.h>
#include <gtkmm/cssprovider.h>
#include <gtkmm/dialog.h>
#include <gtkmm/filechooserdialog.h>
#include <gtkmm/filefilter.h>
#include <gtkmm/iconsource.h>
#include <gtkmm/label.h>
#include <gtkmm/messagedialog.h>
#include <gtkmm/stock.h>
#include <gtkmm/stockitem.h>
#include <gtkmm/textview.h>
#include <gtkmm/uimanager.h>

#include <glibmm/main.h>
#include <glibmm/miscutils.h>
#include <glibmm/spawn.h>
#include <glibmm/thread.h>
#include <glibmm/timer.h>

#include <gtk/gtk.h>
#include <gdk/gdk.h>

#include <gdkmm/general.h>

#ifdef _WIN32
#define WINVER 0x0500
#include <windows.h>
#endif

#include <synfig/general.h>
#include <synfig/color.h>
#include <synfig/canvasfilenaming.h>
#include <synfig/filesystemnative.h>
#include <synfig/filesystemgroup.h>
#include <synfig/filesystemtemporary.h>
#include <synfig/importer.h>
#include <synfig/loadcanvas.h>
#include <synfig/savecanvas.h>
#include <synfig/layer.h>
#include <synfig/soundprocessor.h>

#include "app.h"
#include "splash.h"
#include "instance.h"
#include "canvasview.h"
#include "dialogs/about.h"
#include "dialogs/dialog_color.h"
#include "dialogs/dialog_gradient.h"
#include "dialogs/dialog_input.h"
#include "dialogs/dialog_setup.h"
#include "dialogs/dialog_workspaces.h"
#include "dialogs/vectorizersettings.h"
#include "onemoment.h"
#include "devicetracker.h"
#include "widgets/widget_enum.h"

#include <synfigapp/canvasinterface.h>
#include <synfigapp/actions/layersetdesc.h>

#include "statemanager.h"

#include "states/state_bline.h"
#include "states/state_brush.h"
#include "states/state_circle.h"
#include "states/state_draw.h"
#include "states/state_eyedrop.h"
#include "states/state_fill.h"
#include "states/state_gradient.h"
#include "states/state_lasso.h"
#include "states/state_mirror.h"
#include "states/state_normal.h"
#include "states/state_polygon.h"
#include "states/state_rectangle.h"
#include "states/state_rotate.h"
#include "states/state_scale.h"
#include "states/state_sketch.h"
#include "states/state_smoothmove.h"
#include "states/state_star.h"
#include "states/state_text.h"
#include "states/state_width.h"
#include "states/state_zoom.h"

#include "autorecover.h"

#include <synfigapp/settings.h>
#include <synfigapp/canvasinterface.h>
#include <synfigapp/action.h>

#include "docks/dockmanager.h"
#include "docks/dialog_tooloptions.h"
#include "docks/dock_canvases.h"
#include "docks/dock_children.h"
#include "docks/dock_curves.h"
#include "docks/dock_history.h"
#include "docks/dock_info.h"
#include "docks/dock_keyframes.h"
#include "docks/dock_layers.h"
#include "docks/dock_layergroups.h"
#include "docks/dock_params.h"
#include "docks/dock_metadata.h"
#include "docks/dock_navigator.h"
#include "docks/dock_soundwave.h"
#include "docks/dock_timetrack.h"
#include "docks/dock_toolbox.h"

#include "modules/module.h"
#include "modules/mod_palette/mod_palette.h"

#include "ipc.h"

#include <gui/localization.h>

#include "gui/resourcehelper.h"
#include "gui/workspacehandler.h"

#endif

/* === U S I N G =========================================================== */

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

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

#ifndef DPM2DPI
#define DPM2DPI(x)	(float(x)/39.3700787402f)
#define DPI2DPM(x)	(float(x)*39.3700787402f)
#endif

#ifndef IMAGE_EXT
#	define IMAGE_EXT	"tif"
#endif

#ifdef _WIN32
#	ifdef PLUGIN_DIR
#		undef PLUGIN_DIR
#		define PLUGIN_DIR "share\\synfig\\plugins"
#	endif
#endif

#ifndef PLUGIN_DIR
#	define PLUGIN_DIR "/usr/local/share/synfig/plugins"
#endif

#ifdef _WIN32
#	ifdef SOUND_DIR
#		undef SOUND_DIR
#		define SOUND_DIR "share\\synfig\\sounds"
#	endif
#endif

#ifndef SOUND_DIR
#	define SOUND_DIR "/usr/local/share/synfig/sounds"
#endif

#include <synfigapp/main.h>

/* === S I G N A L S ======================================================= */

static sigc::signal<void> signal_present_all_;
sigc::signal<void>&
App::signal_present_all() { return signal_present_all_; }

static sigc::signal<void> signal_recent_files_changed_;
sigc::signal<void>&
App::signal_recent_files_changed() { return signal_recent_files_changed_; }

static sigc::signal<void> signal_custom_workspaces_changed_;
sigc::signal<void>&
App::signal_custom_workspaces_changed()
{
	return signal_custom_workspaces_changed_;
}

static sigc::signal<void,etl::loose_handle<CanvasView> > signal_canvas_view_focus_;
sigc::signal<void,etl::loose_handle<CanvasView> >&
App::signal_canvas_view_focus() { return signal_canvas_view_focus_; }

static sigc::signal<void,etl::handle<Instance> > signal_instance_selected_;
sigc::signal<void,etl::handle<Instance> >&
App::signal_instance_selected() { return signal_instance_selected_; }

static sigc::signal<void,etl::handle<Instance> > signal_instance_created_;
sigc::signal<void,etl::handle<Instance> >&
App::signal_instance_created() { return signal_instance_created_; }

static sigc::signal<void,etl::handle<Instance> > signal_instance_deleted_;
sigc::signal<void,etl::handle<Instance> >&
App::signal_instance_deleted() { return signal_instance_deleted_; }

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

static std::list<std::string>           recent_files;
const  std::list<std::string>& App::get_recent_files() { return recent_files; }

const std::vector<std::string>
App::get_workspaces()
{
	std::vector<std::string> list;
	if (workspaces)
		workspaces->get_name_list(list);
	return list;
}

int	 App::Busy::count;
bool App::shutdown_in_progress;

Glib::RefPtr<studio::UIManager>	App::ui_manager_;

int        App::jack_locks_ = 0;
Dock_Info* App::dock_info_  = 0;

synfig::Distance::System  App::distance_system;

studio::Dialog_Setup     *App::dialog_setup;

etl::handle< studio::ModPalette > mod_palette_;
//studio::Dialog_Palette* App::dialog_palette;

std::list<etl::handle<Instance> > App::instance_list;

static etl::handle<synfigapp::UIInterface>           ui_interface_;
const  etl::handle<synfigapp::UIInterface>& App::get_ui_interface() { return ui_interface_; }

etl::handle<Instance>   App::selected_instance;
etl::handle<CanvasView> App::selected_canvas_view;

studio::About              *studio::App::about          = NULL;
studio::MainWindow         *studio::App::main_window    = NULL;
studio::Dock_Toolbox       *studio::App::dock_toolbox   = NULL;
studio::AutoRecover        *studio::App::auto_recover   = NULL;
studio::IPC                *ipc                         = NULL;
studio::DockManager        *studio::App::dock_manager   = 0;
studio::DeviceTracker      *studio::App::device_tracker = 0;

studio::Dialog_Gradient    *studio::App::dialog_gradient;
studio::Dialog_Color       *studio::App::dialog_color;
studio::Dialog_Input       *studio::App::dialog_input;
studio::Dialog_ToolOptions *studio::App::dialog_tool_options;
studio::VectorizerSettings *studio::App::vectorizerpopup;

studio::Dock_History       *dock_history;
studio::Dock_Canvases      *dock_canvases;
studio::Dock_Keyframes     *dock_keyframes;
studio::Dock_Layers        *dock_layers;
studio::Dock_Params        *dock_params;
studio::Dock_MetaData      *dock_meta_data;
studio::Dock_Children      *dock_children;
studio::Dock_Info          *dock_info;
studio::Dock_LayerGroups   *dock_layer_groups;
studio::Dock_Navigator     *dock_navigator;
studio::Dock_SoundWave     *dock_soundwave;
studio::Dock_Timetrack     *dock_timetrack;
studio::Dock_Curves        *dock_curves;

std::list< etl::handle< studio::Module > > module_list_;

bool   studio::App::restrict_radius_ducks        = true;
bool   studio::App::resize_imported_images       = false;
bool   studio::App::enable_experimental_features = false;
bool   studio::App::use_dark_theme               = false;
bool   studio::App::show_file_toolbar            = true;
String studio::App::custom_filename_prefix       (DEFAULT_FILENAME_PREFIX);
int    studio::App::preferred_x_size             = 480;
int    studio::App::preferred_y_size             = 270;
String studio::App::predefined_size              (DEFAULT_PREDEFINED_SIZE);
String studio::App::predefined_fps               (DEFAULT_PREDEFINED_FPS);
float  studio::App::preferred_fps                = 24.0;
PluginManager studio::App::plugin_manager;
std::set< String >       studio::App::brushes_path;
String studio::App::image_editor_path;

String studio::App::sequence_separator(".");
String studio::App::navigator_renderer;
String studio::App::workarea_renderer;

String        studio::App::default_background_layer_type  = "none";
synfig::Color studio::App::default_background_layer_color =
	synfig::Color(1.000000, 1.000000, 1.000000, 1.000000);  //White
String        studio::App::default_background_layer_image = "undefined";
synfig::Color studio::App::preview_background_color =
	synfig::Color(0.742187, 0.742187, 0.742187, 1.000000);  //X11 Gray

bool   studio::App::enable_mainwin_menubar = true;
String studio::App::ui_language ("os_LANG");
long   studio::App::ui_handle_tooltip_flag(Duck::STRUCT_DEFAULT);

static int max_recent_files_=25;
int    studio::App::get_max_recent_files()      { return max_recent_files_; }
void   studio::App::set_max_recent_files(int x) {        max_recent_files_ = x; }

static synfig::String app_base_path_;

studio::WorkspaceHandler *studio::App::workspaces = nullptr;

namespace studio {

bool
really_delete_widget(Gtk::Widget *widget)
{
	// synfig::info("really delete %p", (void*)widget);
	delete widget;
	return false;
}

// nasty workaround - when we've finished with a popup menu, we want to delete it
// attaching to the signal_hide() signal gets us here before the action on the menu has run,
// so schedule the real delete to happen in 50ms, giving the action a chance to run
void
delete_widget(Gtk::Widget *widget)
{
	// synfig::info("delete %p", (void*)widget);
	Glib::signal_timeout().connect(sigc::bind(sigc::ptr_fun(&really_delete_widget), widget), 50);
}

//Static members need to be initialized outside of class declaration
SoundProcessor *App::sound_render_done = NULL;
bool App::use_render_done_sound = true;

}; // END of namespace studio
studio::StateManager* state_manager;




class GlobalUIInterface : public synfigapp::UIInterface
{
public:

	virtual Response confirmation(
			const std::string &message,
			const std::string &details,
			const std::string &cancel,
			const std::string &confirm,
			Response dflt
	)
	{
		Gtk::MessageDialog dialog(
			message,
			false,
			Gtk::MESSAGE_WARNING,
			Gtk::BUTTONS_NONE,
			true
		);

		if (! details.empty())
			dialog.set_secondary_text(details);

		dialog.add_button(cancel,  RESPONSE_CANCEL);
		dialog.add_button(confirm, RESPONSE_OK);
		dialog.set_default_response(dflt);

		dialog.show_all();
		return (Response) dialog.run();
	}


	virtual Response yes_no_cancel(
				const std::string &message,
				const std::string &details,
				const std::string &button1,
				const std::string &button2,
				const std::string &button3,
				Response dflt=RESPONSE_YES
	)
	{
		Gtk::MessageDialog dialog(
			message,
			false,
			Gtk::MESSAGE_QUESTION,
			Gtk::BUTTONS_NONE,
			true
		);

		dialog.set_secondary_text(details);
		dialog.add_button(button1, RESPONSE_NO);
		dialog.add_button(button2, RESPONSE_CANCEL);
		dialog.add_button(button3, RESPONSE_YES);

		dialog.set_default_response(dflt);
		dialog.show();
		return (Response)dialog.run();
	}


	virtual bool
	task(const std::string &task)
	{
		std::cerr<<task.c_str()<<std::endl;
		App::process_all_events();
		return true;
	}

	virtual bool
	error(const std::string &err)
	{
		Gtk::MessageDialog dialog(err, false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_CLOSE, true);
		dialog.set_transient_for(*App::main_window);
		dialog.show();
		dialog.run();
		return true;
	}

	virtual bool
	warning(const std::string &err)
	{
		std::cerr<<"warning: "<<err.c_str()<<std::endl;
		App::process_all_events();
		return true;
	}

	virtual bool
	amount_complete(int /*current*/, int /*total*/)
	{
		App::process_all_events();
		return true;
	}
};

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

/*
void
studio::UIManager::insert_action_group (const Glib::RefPtr<Gtk::ActionGroup>& action_group, int pos)
{
	action_group_list.push_back(action_group);
	Gtk::UIManager::insert_action_group(action_group, pos);
}

void
studio::UIManager::remove_action_group (const Glib::RefPtr<Gtk::ActionGroup>& action_group)
{
	std::list<Glib::RefPtr<Gtk::ActionGroup> >::iterator iter;
	for(iter=action_group_list.begin();iter!=action_group_list.end();++iter)
		if(*iter==action_group)
		{
			action_group_list.erase(iter);
			Gtk::UIManager::remove_action_group(action_group);
			return;
		}
	synfig::error("Unable to find action group");
}

void
studio::add_action_group_to_top(Glib::RefPtr<studio::UIManager> ui_manager, Glib::RefPtr<Gtk::ActionGroup> group)
{
	ui_manager->insert_action_group(group,0);
	return;
	std::list<Glib::RefPtr<Gtk::ActionGroup> > prev_groups(ui_manager->get_action_groups());
	std::list<Glib::RefPtr<Gtk::ActionGroup> >::reverse_iterator iter;

	for(iter=prev_groups.rbegin();iter!=prev_groups.rend();++iter)
	{
		if(*iter && (*iter)->get_name()!="menus")
		{
			synfig::info("Removing action group "+(*iter)->get_name());
			ui_manager->remove_action_group(*iter);
		}
	}
	ui_manager->insert_action_group(group,0);

	for(;!prev_groups.empty();prev_groups.pop_front())
	{
		if(prev_groups.front() && prev_groups.front()!=group && prev_groups.front()->get_name()!="menus")
			ui_manager->insert_action_group(prev_groups.front(),1);
	}
}
*/
class Preferences : public synfigapp::Settings
{
public:
	virtual bool get_value(const synfig::String& key, synfig::String& value)const
	{
		try
		{
			synfig::ChangeLocale change_locale(LC_NUMERIC, "C");
			if(key=="time_format")
			{
				value=strprintf("%i",App::get_time_format());
				return true;
			}
			if(key=="file_history.size")
			{
				value=strprintf("%i",App::get_max_recent_files());
				return true;
			}
			if(key=="distance_system")
			{
				value=strprintf("%s",Distance::system_name(App::distance_system).c_str());
				return true;
			}
			if(key=="autosave_backup")
			{
				value=strprintf("%i",App::auto_recover->get_enabled());
				return true;
			}
			if(key=="autosave_backup_interval")
			{
				value=strprintf("%i",App::auto_recover->get_timeout_ms());
				return true;
			}
			if(key=="restrict_radius_ducks")
			{
				value=strprintf("%i",(int)App::restrict_radius_ducks);
				return true;
			}
			if(key=="resize_imported_images")
			{
				value=strprintf("%i",(int)App::resize_imported_images);
				return true;
			}
			if(key=="enable_experimental_features")
			{
				value=strprintf("%i",(int)App::enable_experimental_features);
				return true;
			}
			if(key=="use_dark_theme")
			{
				value=strprintf("%i",(int)App::use_dark_theme);
				return true;
			}
			if(key=="show_file_toolbar")
			{
				value=strprintf("%i",(int)App::show_file_toolbar);
				return true;
			}
			//! "Keep brushes_path" preferences entry for backward compatibility (15/12 - v1.0.3)
			//! Now brush path(s) are hold by input preferences : brush.path_count & brush.path_%d
			if(key=="brushes_path")
			{
				value="";
				if(!App::brushes_path.empty())
					value=*(App::brushes_path.begin());
				return true;
			}
			if(key=="custom_filename_prefix")
			{
				value=App::custom_filename_prefix;
				return true;
			}
			if(key=="preferred_x_size")
			{
				value=strprintf("%i",App::preferred_x_size);
				return true;
			}
			if(key=="preferred_y_size")
			{
				value=strprintf("%i",App::preferred_y_size);
				return true;
			}
			if(key=="predefined_size")
			{
				value=strprintf("%s",App::predefined_size.c_str());
				return true;
			}
			if(key=="preferred_fps")
			{
				value=strprintf("%f",App::preferred_fps);
				return true;
			}
			if(key=="predefined_fps")
			{
				value=strprintf("%s",App::predefined_fps.c_str());
				return true;
			}
			if(key=="sequence_separator")
			{
				value=App::sequence_separator;
				return true;
			}
			if(key=="navigator_renderer")
			{
				value=App::navigator_renderer;
				return true;
			}
			if(key=="workarea_renderer")
			{
				value=App::workarea_renderer;
				return true;
			}
			if (key == "default_background_layer_type")
			{
                value = strprintf("%s", App::default_background_layer_type.c_str());
				return true;
			}
			if (key == "default_background_layer_color")
			{
				value = strprintf("%f %f %f %f",
					App::default_background_layer_color.get_r(),
					App::default_background_layer_color.get_g(),
					App::default_background_layer_color.get_b(),
					App::default_background_layer_color.get_a()
					);
				return true;
			}
			if (key == "default_background_layer_image")
			{
				value = strprintf("%s", App::default_background_layer_image.c_str());
				return true;
			}
			if (key == "preview_background_color")
			{
				value = strprintf("%f %f %f %f",
					App::preview_background_color.get_r(),
					App::preview_background_color.get_g(),
					App::preview_background_color.get_b(),
					App::preview_background_color.get_a()
					);
				return true;
			}
			if(key=="use_render_done_sound")
			{
				value=strprintf("%i",(int)App::use_render_done_sound);
				return true;
			}
			if(key=="enable_mainwin_menubar")
			{
				value=strprintf("%i", (int)App::enable_mainwin_menubar);
				return true;
			}
			if(key=="ui_handle_tooltip_flag")
			{
				value=strprintf("%ld", (long)App::ui_handle_tooltip_flag);
				return true;
			}
			if(key=="image_editor_path")
			{
				value=App::image_editor_path;
				return true;
			}
		}
		catch(...)
		{
			synfig::warning("Preferences: Caught exception when attempting to get value.");
		}
		return synfigapp::Settings::get_value(key,value);
	}

	virtual bool set_value(const synfig::String& key,const synfig::String& value)
	{
		try
		{
			synfig::ChangeLocale change_locale(LC_NUMERIC, "C");
			if(key=="time_format")
			{
				int i(atoi(value.c_str()));
				App::set_time_format(static_cast<synfig::Time::Format>(i));
				return true;
			}
			if(key=="autosave_backup")
			{
				int i(atoi(value.c_str()));
				App::auto_recover->set_enabled(i);
				return true;
			}
			if(key=="autosave_backup_interval")
			{
				int i(atoi(value.c_str()));
				App::auto_recover->set_timeout_ms(i);
				return true;
			}
			if(key=="file_history.size")
			{
				int i(atoi(value.c_str()));
				App::set_max_recent_files(i);
				return true;
			}
			if(key=="distance_system")
			{
				App::distance_system=Distance::ident_system(value);
				return true;
			}
			if(key=="restrict_radius_ducks")
			{
				int i(atoi(value.c_str()));
				App::restrict_radius_ducks=i;
				return true;
			}
			if(key=="resize_imported_images")
			{
				int i(atoi(value.c_str()));
				App::resize_imported_images=i;
				return true;
			}
			if(key=="enable_experimental_features")
			{
				int i(atoi(value.c_str()));
				App::enable_experimental_features=i;
				return true;
			}
			if(key=="use_dark_theme")
			{
				int i(atoi(value.c_str()));
				App::use_dark_theme=i;
				return true;
			}
			if(key=="show_file_toolbar")
			{
				int i(atoi(value.c_str()));
				App::show_file_toolbar=i;
				return true;
			}
			//! "Keep brushes_path" preferences entry for backward compatibility (15/12 - v1.0.3)
			//! Now brush path(s) are hold by input preferences : brush.path_count & brush.path_%d
			if(key=="brushes_path")
			{
				App::brushes_path.insert(value);
				return true;
			}
			if(key=="custom_filename_prefix")
			{
				App::custom_filename_prefix=value;
				return true;
			}
			if(key=="preferred_x_size")
			{
				int i(atoi(value.c_str()));
				App::preferred_x_size=i;
				return true;
			}
			if(key=="preferred_y_size")
			{
				int i(atoi(value.c_str()));
				App::preferred_y_size=i;
				return true;
			}
			if(key=="predefined_size")
			{
				App::predefined_size=value;
				return true;
			}
			if(key=="preferred_fps")
			{
				float i(atof(value.c_str()));
				App::preferred_fps=i;
				return true;
			}
			if(key=="predefined_fps")
			{
				App::predefined_fps=value;
				return true;
			}
			if(key=="sequence_separator")
			{
				App::sequence_separator=value;
				return true;
			}
			if(key=="navigator_renderer")
			{
				App::navigator_renderer=value;
				return true;
			}
			if(key=="workarea_renderer")
			{
				App::workarea_renderer=value;
				return true;
			}
			if (key == "default_background_layer_type")
			{
				App::default_background_layer_type = value;
				return true;
			}
			if (key == "default_background_layer_color")
			{
				float r,g,b,a;
				strscanf(value,"%f %f %f %f", &r,&g,&b,&a);
				App::default_background_layer_color = synfig::Color(r,g,b,a);
				return true;
			}
			if (key == "default_background_layer_image")
			{
				App::default_background_layer_image = value;
				return true;
			}
			if (key == "preview_background_color")
			{
				float r,g,b,a;
				strscanf(value,"%f %f %f %f", &r,&g,&b,&a);
				App::preview_background_color = synfig::Color(r,g,b,a);
				return true;
			}
			if(key=="use_render_done_sound")
			{
				int i(atoi(value.c_str()));
				App::use_render_done_sound=i;
				return true;
			}
			if(key=="enable_mainwin_menubar")
			{
				int i(atoi(value.c_str()));
				App::enable_mainwin_menubar = i;
				return true;
			}
			if(key=="ui_handle_tooltip_flag")
			{
				long l(atol(value.c_str()));
				App::ui_handle_tooltip_flag = l;
				return true;
			}
			if(key=="image_editor_path")
			{
				App::image_editor_path=value;
				return true;
			}
		}
		catch(...)
		{
			synfig::warning("Preferences: Caught exception when attempting to set value.");
		}
		return synfigapp::Settings::set_value(key,value);
	}

	virtual KeyList get_key_list()const
	{
		KeyList ret(synfigapp::Settings::get_key_list());
		ret.push_back("time_format");
		ret.push_back("distance_system");
		ret.push_back("file_history.size");
		ret.push_back("autosave_backup");
		ret.push_back("autosave_backup_interval");
		ret.push_back("restrict_radius_ducks");
		ret.push_back("resize_imported_images");
		ret.push_back("enable_experimental_features");
		ret.push_back("use_dark_theme");
		ret.push_back("show_file_toolbar");
		ret.push_back("brushes_path");
		ret.push_back("custom_filename_prefix");
		ret.push_back("ui_language");
		ret.push_back("preferred_x_size");
		ret.push_back("preferred_y_size");
		ret.push_back("predefined_size");
		ret.push_back("preferred_fps");
		ret.push_back("predefined_fps");
		ret.push_back("sequence_separator");
		ret.push_back("navigator_renderer");
		ret.push_back("workarea_renderer");
		ret.push_back("default_background_layer_type");
		ret.push_back("default_background_layer_color");
		ret.push_back("default_background_layer_image");
		ret.push_back("preview_background_color");
		ret.push_back("use_render_done_sound");
		ret.push_back("enable_mainwin_menubar");
		ret.push_back("ui_handle_tooltip_flag");
		ret.push_back("image_editor_path");


		return ret;
	}
};

static ::Preferences _preferences;

void
init_ui_manager()
{
	Glib::RefPtr<Gtk::ActionGroup> menus_action_group = Gtk::ActionGroup::create("menus");

	Glib::RefPtr<Gtk::ActionGroup> actions_action_group = Gtk::ActionGroup::create("actions");

	menus_action_group->add( Gtk::Action::create("menu-file",            _("_File")));
	menus_action_group->add( Gtk::Action::create("menu-open-recent",     _("Open Recent")));

	menus_action_group->add( Gtk::Action::create("menu-edit",            _("_Edit")));

	menus_action_group->add( Gtk::Action::create("menu-view",            _("_View")));
	menus_action_group->add( Gtk::Action::create("menu-navigation",            _("_Navigation")));
	menus_action_group->add( Gtk::Action::create("menu-duck-mask",       _("Show/Hide Handles")));
	menus_action_group->add( Gtk::Action::create("menu-preview-quality", _("Preview Quality")));
	menus_action_group->add( Gtk::Action::create("menu-lowres-pixel",    _("Low-Res Pixel Size")));

	menus_action_group->add( Gtk::Action::create("menu-canvas",          _("_Canvas")));

	menus_action_group->add( Gtk::Action::create("menu-layer",           _("_Layer")));
	menus_action_group->add( Gtk::Action::create("menu-layer-new",       _("New Layer")));
	menus_action_group->add( Gtk::Action::create("menu-toolbox",         _("Toolbox")));
	menus_action_group->add( Gtk::Action::create("menu-plugins",         _("Plug-Ins")));

	menus_action_group->add( Gtk::Action::create("menu-window",          _("_Window")));
	menus_action_group->add( Gtk::Action::create("menu-arrange",         _("_Arrange")));
	menus_action_group->add( Gtk::Action::create("menu-workspace",       _("Work_space")));

	menus_action_group->add( Gtk::Action::create("menu-help",            _("_Help")));

	menus_action_group->add(Gtk::Action::create("menu-keyframe",          _("Keyframe")));

	// Add the synfigapp actions (layer panel toolbar items, etc...)
	synfigapp::Action::Book::iterator iter;
	for(iter=synfigapp::Action::book().begin();iter!=synfigapp::Action::book().end();++iter)
	{
		actions_action_group->add(Gtk::Action::create(
			"action-"+iter->second.name,
			get_action_stock_id(iter->second),
			iter->second.local_name,iter->second.local_name
		));
	}

// predefined actions to initial menu items, so that there is all menu items listing
// even there is no any canvas instance existed, for example, when app just opened.
// the menu items (action names) should be named consistently with those in canvasview.cpp and others.
#define DEFINE_ACTION(x,stock) { Glib::RefPtr<Gtk::Action> action( Gtk::Action::create(x, stock) ); actions_action_group->add(action); }

// actions in File menu
DEFINE_ACTION("new",            Gtk::StockID("synfig-new"));
DEFINE_ACTION("open",           Gtk::StockID("synfig-open"));
DEFINE_ACTION("save",           Gtk::StockID("synfig-save"));
DEFINE_ACTION("save-as",        Gtk::StockID("synfig-save_as"));
DEFINE_ACTION("save-all",       Gtk::StockID("synfig-save_all"));
DEFINE_ACTION("revert",         Gtk::Stock::REVERT_TO_SAVED);
DEFINE_ACTION("import",         _("Import..."));
DEFINE_ACTION("import-sequence",_("Import Sequence..."));
DEFINE_ACTION("render",         _("Render..."));
DEFINE_ACTION("preview",        _("Preview..."));
DEFINE_ACTION("close-document", _("Close Document"));
DEFINE_ACTION("quit",           Gtk::Stock::QUIT);

// actions in Edit menu
DEFINE_ACTION("undo",                     Gtk::StockID("synfig-undo"));
DEFINE_ACTION("redo",                     Gtk::StockID("synfig-redo"));
DEFINE_ACTION("copy",                     Gtk::Stock::COPY);
DEFINE_ACTION("cut",                      Gtk::Stock::CUT);
DEFINE_ACTION("paste",                    Gtk::Stock::PASTE);
DEFINE_ACTION("select-all-ducks",         _("Select All Handles"));
DEFINE_ACTION("unselect-all-ducks",       _("Unselect All Handles"));
DEFINE_ACTION("select-all-layers",        _("Select All Layers"));
DEFINE_ACTION("unselect-all-layers",      _("Unselect All Layers"));
DEFINE_ACTION("input-devices",            _("Input Devices..."));
DEFINE_ACTION("setup",                    _("Preferences..."));

// actions in View menu
DEFINE_ACTION("toggle-mainwin-menubar",   _("Menubar"));
DEFINE_ACTION("toggle-mainwin-toolbar",   _("Toolbar"));

DEFINE_ACTION("mask-none-ducks",                _("Toggle None/Last visible Handles"));
DEFINE_ACTION("mask-position-ducks",            _("Show Position Handles"));
DEFINE_ACTION("mask-vertex-ducks",              _("Show Vertex Handles"));
DEFINE_ACTION("mask-tangent-ducks",             _("Show Tangent Handles"));
DEFINE_ACTION("mask-radius-ducks",              _("Show Radius Handles"));
DEFINE_ACTION("mask-width-ducks",               _("Show Width Handles"));
DEFINE_ACTION("mask-widthpoint-position-ducks", _("Show WidthPoints Position Handles"));
DEFINE_ACTION("mask-angle-ducks",               _("Show Angle Handles"));
DEFINE_ACTION("mask-bone-setup-ducks",          _("Show Bone Setup Handles"));
DEFINE_ACTION("mask-bone-recursive-ducks",      _("Show Recursive Scale Bone Handles"));
DEFINE_ACTION("mask-bone-ducks",                _("Next Bone Handles"));

DEFINE_ACTION("quality-00", _("Use Parametric Renderer"));
DEFINE_ACTION("quality-01", _("Use Quality Level 1"));
DEFINE_ACTION("quality-02", _("Use Quality Level 2"));
DEFINE_ACTION("quality-03", _("Use Quality Level 3"));
DEFINE_ACTION("quality-04", _("Use Quality Level 4"));
DEFINE_ACTION("quality-05", _("Use Quality Level 5"));
DEFINE_ACTION("quality-06", _("Use Quality Level 6"));
DEFINE_ACTION("quality-07", _("Use Quality Level 7"));
DEFINE_ACTION("quality-08", _("Use Quality Level 8"));
DEFINE_ACTION("quality-09", _("Use Quality Level 9"));
DEFINE_ACTION("quality-10", _("Use Quality Level 10"));

for(list<int>::iterator iter = CanvasView::get_pixel_sizes().begin(); iter != CanvasView::get_pixel_sizes().end(); iter++)
  DEFINE_ACTION(strprintf("lowres-pixel-%d", *iter), strprintf(_("Set Low-Res pixel size to %d"), *iter));

DEFINE_ACTION("toggle-grid-show",  _("Toggle Grid Show"));
DEFINE_ACTION("toggle-grid-snap",  _("Toggle Grid Snap"));
DEFINE_ACTION("toggle-guide-show", _("Toggle Guide Show"));
DEFINE_ACTION("toggle-guide-snap", _("Toggle Guide Snap"));
DEFINE_ACTION("toggle-low-res",    _("Toggle Low-Res"));

DEFINE_ACTION("decrease-low-res-pixel-size", _("Decrease Low-Res Pixel Size"));
DEFINE_ACTION("increase-low-res-pixel-size", _("Increase Low-Res Pixel Size"));
DEFINE_ACTION("toggle-background-rendering", _("Toggle Background Rendering"));
DEFINE_ACTION("toggle-onion-skin",           _("Toggle Onion Skin"));
DEFINE_ACTION("canvas-zoom-in",              Gtk::StockID("gtk-zoom-in"));
DEFINE_ACTION("canvas-zoom-out",             Gtk::StockID("gtk-zoom-out"));
DEFINE_ACTION("canvas-zoom-fit",             Gtk::StockID("gtk-zoom-fit"));
DEFINE_ACTION("canvas-zoom-100",             Gtk::StockID("gtk-zoom-100"));
DEFINE_ACTION("time-zoom-in",                Gtk::StockID("gtk-zoom-in"));
DEFINE_ACTION("time-zoom-out",               Gtk::StockID("gtk-zoom-out"));

//actions in Navigation menu
DEFINE_ACTION("play", _("Play"));
// the stop is not a normal stop but a pause. So use "Pause" in UI, including TEXT and
// icon. the internal code is still using stop.
DEFINE_ACTION("stop", _("Pause"));

DEFINE_ACTION("jump-next-keyframe",          _("Seek to Next Keyframe"));
DEFINE_ACTION("jump-prev-keyframe",          _("Seek to previous Keyframe"));
DEFINE_ACTION("seek-next-frame",             _("Seek to Next Frame"));
DEFINE_ACTION("seek-prev-frame",             _("Seek to Previous Frame"));
DEFINE_ACTION("seek-next-second",            _("Seek Forward"));
DEFINE_ACTION("seek-prev-second",            _("Seek Backward"));
DEFINE_ACTION("seek-begin",                  _("Seek to Begin"));
DEFINE_ACTION("seek-end",                    _("Seek to End"));


// actions in Canvas menu
DEFINE_ACTION("properties", _("Properties..."));
DEFINE_ACTION("options",    _("Options..."));

// actions in Layer menu
DEFINE_ACTION("amount-inc", _("Increase Layer Amount"))
DEFINE_ACTION("amount-dec", _("Decrease Layer Amount"))

// actions in Window menu
DEFINE_ACTION("workspace-compositing", _("Compositing"));
DEFINE_ACTION("workspace-default",     _("Default"));
DEFINE_ACTION("workspace-animating",   _("Animating"));
DEFINE_ACTION("save-workspace",        _("Save workspace..."));
DEFINE_ACTION("dialog-flipbook",       _("Preview Dialog"));
DEFINE_ACTION("panel-toolbox",         _("Toolbox"));
DEFINE_ACTION("panel-tool_options",    _("Tool Options"));
DEFINE_ACTION("panel-history",         _("History"));
DEFINE_ACTION("panel-canvases",        _("Canvas Browser"));
DEFINE_ACTION("panel-keyframes",       _("Keyframes"));
DEFINE_ACTION("panel-layers",          _("Layers"));
DEFINE_ACTION("panel-params",          _("Parameters"));
DEFINE_ACTION("panel-meta_data",       _("Canvas MetaData"));
DEFINE_ACTION("panel-children",        _("Library"));
DEFINE_ACTION("panel-info",            _("Info"));
DEFINE_ACTION("panel-navigator",       _("Navigator"));
DEFINE_ACTION("panel-timetrack",       _("Timetrack"));
DEFINE_ACTION("panel-curves",          _("Graphs"));
DEFINE_ACTION("panel-groups",          _("Sets"));
DEFINE_ACTION("panel-pal_edit",        _("Palette Editor"));
DEFINE_ACTION("panel-soundwave",       _("Sound"));

// actions in Help menu
DEFINE_ACTION("help",           Gtk::Stock::HELP);
DEFINE_ACTION("help-tutorials", Gtk::Stock::HELP);
DEFINE_ACTION("help-reference", Gtk::Stock::HELP);
DEFINE_ACTION("help-faq",       Gtk::Stock::HELP);
DEFINE_ACTION("help-support",   Gtk::Stock::HELP);
DEFINE_ACTION("help-about",     Gtk::StockID("synfig-about"));

// actions: Keyframe
DEFINE_ACTION("keyframe-properties", _("Properties"));


//Layout the actions in the main menu (caret menu, right click on canvas menu) and toolbar:
	Glib::ustring ui_info_menu =
"	<menu action='menu-file'>"
"		<menuitem action='new' />"
"		<menuitem action='open' />"
"		<menu action='menu-open-recent' />"
"		<separator name='sep-file1'/>"
"		<menuitem action='save' />"
"		<menuitem action='save-as' />"
"		<menuitem action='save-all' />"
"		<menuitem action='revert' />"
"		<separator name='sep-file2'/>"
"		<menuitem action='import' />"
"		<menuitem action='import-sequence' />"
"		<separator name='sep-file4'/>"
"		<menuitem action='preview' />"
"		<menuitem action='render' />"
"		<separator name='sep-file5'/>"
"		<menuitem action='close-document' />"
"		<separator name='sep-file6'/>"
"		<menuitem action='quit' />"
"	</menu>"
"	<menu action='menu-edit'>"
"		<menuitem action='undo'/>"
"		<menuitem action='redo'/>"
"		<separator name='sep-edit1'/>"
"		<menuitem action='cut'/>"
"		<menuitem action='copy'/>"
"		<menuitem action='paste'/>"
"		<separator name='sep-edit2'/>"
"		<menuitem action='select-all-layers'/>"
"		<menuitem action='unselect-all-layers'/>"
"		<menuitem action='select-all-ducks'/>"
"		<menuitem action='unselect-all-ducks'/>"
"		<separator name='sep-edit3'/>"
"		<menuitem action='input-devices' />"
"		<menuitem action='setup' />"
"	</menu>"
"	<menu action='menu-view'>"
"		<menuitem action='toggle-mainwin-menubar' />"
"		<menuitem action='toggle-mainwin-toolbar' />"
"		<separator />"
"		<menu action='menu-duck-mask'>"
"			<menuitem action='mask-none-ducks' />"
"			<menuitem action='mask-position-ducks' />"
"			<menuitem action='mask-vertex-ducks' />"
"			<menuitem action='mask-tangent-ducks' />"
"			<menuitem action='mask-radius-ducks' />"
"			<menuitem action='mask-width-ducks' />"
"			<menuitem action='mask-widthpoint-position-ducks' />"
"			<menuitem action='mask-angle-ducks' />"
"			<menuitem action='mask-bone-setup-ducks' />"
"			<menuitem action='mask-bone-recursive-ducks' />"
"			<menuitem action='mask-bone-ducks' />"
"		</menu>"
"		<menu action='menu-preview-quality'>"
"			<menuitem action='quality-00' />"
"			<menuitem action='quality-01' />"
"			<menuitem action='quality-02' />"
"			<menuitem action='quality-03' />"
"			<menuitem action='quality-04' />"
"			<menuitem action='quality-05' />"
"			<menuitem action='quality-06' />"
"			<menuitem action='quality-07' />"
"			<menuitem action='quality-08' />"
"			<menuitem action='quality-09' />"
"			<menuitem action='quality-10' />"
"		</menu>"
"		<menu action='menu-lowres-pixel'>"
"			<menuitem action='decrease-low-res-pixel-size'/>"
"			<menuitem action='increase-low-res-pixel-size'/>"
"			<separator name='pixel-size-separator'/>"
;

	for(list<int>::iterator iter = CanvasView::get_pixel_sizes().begin(); iter != CanvasView::get_pixel_sizes().end(); iter++)
		ui_info_menu += strprintf("			<menuitem action='lowres-pixel-%d' />", *iter);

	ui_info_menu +=
"		</menu>"
"		<separator name='sep-view1'/>"
"		<menuitem action='toggle-grid-show'/>"
"		<menuitem action='toggle-grid-snap'/>"
"		<menuitem action='toggle-guide-show'/>"
"		<menuitem action='toggle-guide-snap'/>"
"		<menuitem action='toggle-low-res'/>"
"		<menuitem action='toggle-background-rendering'/>"
"		<menuitem action='toggle-onion-skin'/>"
"		<separator name='sep-view2'/>"
"		<menuitem action='canvas-zoom-in'/>"
"		<menuitem action='canvas-zoom-out'/>"
"		<menuitem action='canvas-zoom-fit'/>"
"		<menuitem action='canvas-zoom-100'/>"
"		<separator name='sep-view3'/>"
"		<menuitem action='time-zoom-in'/>"
"		<menuitem action='time-zoom-out'/>"
"	</menu>"
"    <menu action='menu-navigation'>"
"		<menuitem action='play'/>"
"		<menuitem action='stop'/>"
"		<separator name='sep-view1'/>"
"		<menuitem action='jump-prev-keyframe'/>"
"		<menuitem action='jump-next-keyframe'/>"
"		<menuitem action='seek-prev-frame'/>"
"		<menuitem action='seek-next-frame'/>"
"		<menuitem action='seek-prev-second'/>"
"		<menuitem action='seek-next-second'/>"
"		<menuitem action='seek-begin'/>"
"		<menuitem action='seek-end'/>"
"	</menu>"
"	<menu action='menu-canvas'>"
"		<menuitem action='properties'/>"
"		<menuitem action='options'/>"
"	</menu>"
"	<menu action='menu-toolbox'>"
"	</menu>"
"	<menu action='menu-layer'>"
"		<menu action='menu-layer-new'></menu>"
"		<menuitem action='amount-inc'/>"
"		<menuitem action='amount-dec'/>"
"	</menu>"
"	<menu action='menu-plugins'>"
;

	list<PluginManager::plugin> plugin_list = studio::App::plugin_manager.get_list();
	for(list<PluginManager::plugin>::const_iterator p=plugin_list.begin();p!=plugin_list.end();++p) {

		// TODO: (Plugins) Arrange menu items into groups

		PluginManager::plugin plugin = *p;

		DEFINE_ACTION(plugin.id, plugin.name);
		ui_info_menu += strprintf("	<menuitem action='%s'/>", plugin.id.c_str());
	}

	ui_info_menu +=
"	</menu>"
"	<menu action='menu-window'>"
"		<menu action='menu-arrange'> </menu>"
"		<menu action='menu-workspace'>"
"			<menuitem action='workspace-default' />"
"			<menuitem action='workspace-compositing' />"
"			<menuitem action='workspace-animating' />"
"		</menu>"
"		<separator />"
"		<menuitem action='dialog-flipbook'/>"
"		<menuitem action='panel-toolbox' />"
"		<menuitem action='panel-tool_options' />"
"		<menuitem action='panel-history' />"
"		<menuitem action='panel-canvases' />"
"		<menuitem action='panel-keyframes' />"
"		<menuitem action='panel-layers' />"
"		<menuitem action='panel-params' />"
"		<menuitem action='panel-meta_data' />"
"		<menuitem action='panel-children' />"
"		<menuitem action='panel-info' />"
"		<menuitem action='panel-navigator' />"
"		<menuitem action='panel-timetrack' />"
"		<menuitem action='panel-curves' />"
"		<menuitem action='panel-groups' />"
"		<menuitem action='panel-pal_edit' />"
"		<menuitem action='panel-soundwave' />"
"		<separator />"
// opened documents will be listed here below the above separator.
"	</menu>"
"	<menu action='menu-help'>"
"		<menuitem action='help'/>"
"		<separator name='sep-help1'/>"
"		<menuitem action='help-tutorials'/>"
"		<menuitem action='help-reference'/>"
"		<menuitem action='help-faq'/>"
"		<separator name='sep-help2'/>"
"		<menuitem action='help-support'/>"
"		<separator name='sep-help3'/>"
"		<menuitem action='help-about'/>"
"	</menu>";

	Glib::ustring ui_info_main_tool =
"		<toolitem action='new'/>"
"		<toolitem action='open'/>"
"		<toolitem action='save'/>"
"		<toolitem action='save-as'/>"
"		<toolitem action='save-all'/>"
"		<separator />"
"		<toolitem action='undo'/>"
"		<toolitem action='redo'/>"
"		<separator />"
"		<toolitem action='render'/>"
"		<toolitem action='preview'/>";

	Glib::ustring ui_info =
"<ui>"
"   <popup name='menu-toolbox' action='menu-toolbox'>"
"	<menu action='menu-file'>"
"	</menu>"
"	</popup>"
"	<popup name='menu-main' action='menu-main'>" + ui_info_menu + "</popup>"
"	<menubar name='menubar-main' action='menubar-main'>" + ui_info_menu + "</menubar>"
"	<toolbar name='toolbar-main'>" + ui_info_main_tool + "</toolbar>"
"</ui>";

	#undef DEFINE_ACTION

	try
	{
		actions_action_group->set_sensitive(false);
		App::ui_manager()->set_add_tearoffs(false);
		App::ui_manager()->insert_action_group(menus_action_group,1);
		App::ui_manager()->insert_action_group(actions_action_group,1);
		App::ui_manager()->add_ui_from_string(ui_info);

		//App::ui_manager()->get_accel_group()->unlock();
	}
	catch(const Glib::Error& ex)
	{
		synfig::error("building menus and toolbars failed: " + ex.what());
	}

	// Add default keyboard accelerators
#define ACCEL(accel,path)						\
	{											\
		Gtk::AccelKey accel_key(accel,path);	\
		Gtk::AccelMap::add_entry(accel_key.get_path(), accel_key.get_key(), accel_key.get_mod());	\
	}

#define ACCEL2(accel)							\
	{											\
		Gtk::AccelKey accel_key(accel);			\
		Gtk::AccelMap::add_entry(accel_key.get_path(), accel_key.get_key(), accel_key.get_mod());	\
	}

	// the toolbox
	ACCEL("<Mod1>a",								"<Actions>/action_group_state_manager/state-normal"			);
	ACCEL("<Mod1>v",								"<Actions>/action_group_state_manager/state-smooth_move"		);
	ACCEL("<Mod1>s",								"<Actions>/action_group_state_manager/state-scale"			);
	ACCEL("<Mod1>t",								"<Actions>/action_group_state_manager/state-rotate"			);
	ACCEL("<Mod1>m",								"<Actions>/action_group_state_manager/state-mirror"			);
	ACCEL("<Mod1>c",								"<Actions>/action_group_state_manager/state-circle"			);
	ACCEL("<Mod1>r",								"<Actions>/action_group_state_manager/state-rectangle"			);
	ACCEL("<Mod1>q",								"<Actions>/action_group_state_manager/state-star"			);
	ACCEL("<Mod1>g",								"<Actions>/action_group_state_manager/state-gradient"			);
	ACCEL("<Mod1>p",								"<Actions>/action_group_state_manager/state-polygon"			);
	ACCEL("<Mod1>b",								"<Actions>/action_group_state_manager/state-bline"			);
	ACCEL("<Mod1>x",								"<Actions>/action_group_state_manager/state-text"			);
	ACCEL("<Mod1>f",								"<Actions>/action_group_state_manager/state-fill"			);
	ACCEL("<Mod1>e",								"<Actions>/action_group_state_manager/state-eyedrop"			);
	ACCEL("<Mod1>z",								"<Actions>/action_group_state_manager/state-zoom"			);
	ACCEL("<Mod1>d",								"<Actions>/action_group_state_manager/state-draw"			);
	ACCEL("<Mod1>k",								"<Actions>/action_group_state_manager/state-sketch"			);
	ACCEL("<Mod1>w",								"<Actions>/action_group_state_manager/state-width"			);

	// everything else
	ACCEL("<Control>a",								"<Actions>/canvasview/select-all-ducks"					);
	ACCEL("<Control>d",								"<Actions>/canvasview/unselect-all-ducks"				);
	ACCEL("<Control><Shift>a",							"<Actions>/canvasview/select-all-layers"				);
	ACCEL("<Control><Shift>d",							"<Actions>/canvasview/unselect-all-layers"				);
	ACCEL("F9",									"<Actions>/canvasview/render"						);
	ACCEL("F11",									"<Actions>/canvasview/preview"						);
	ACCEL("F8",									"<Actions>/canvasview/properties"					);
	ACCEL("F12",									"<Actions>/canvasview/options"						);
	ACCEL("<control>i",								"<Actions>/canvasview/import"						);
	//ACCEL2(Gtk::AccelKey(GDK_KEY_Escape,static_cast<Gdk::ModifierType>(0), 		"<Actions>/canvasview/stop"						));
	ACCEL2(Gtk::AccelKey(GDK_KEY_Escape, Gdk::ModifierType(), 		"<Actions>/canvasview/stop"						));
	ACCEL("<Control>g",								"<Actions>/canvasview/toggle-grid-show"					);
	ACCEL("<Control>l",								"<Actions>/canvasview/toggle-grid-snap"					);
	ACCEL("<Control>n",								"<Actions>/mainwindow/new"						);
	ACCEL("<Control>o",								"<Actions>/mainwindow/open"						);
	ACCEL("<Control>s",								"<Actions>/canvasview/save"						);
	ACCEL("<Control><Shift>s",							"<Actions>/canvasview/save-as"						);
	ACCEL2(Gtk::AccelKey('`',Gdk::CONTROL_MASK,					"<Actions>/canvasview/toggle-low-res"					));
	ACCEL("<Mod1>0",                                                    		"<Actions>/canvasview/mask-none-ducks"          			);
	ACCEL("<Mod1>1",								"<Actions>/canvasview/mask-position-ducks"				);
	ACCEL("<Mod1>2",								"<Actions>/canvasview/mask-vertex-ducks"				);
	ACCEL("<Mod1>3",								"<Actions>/canvasview/mask-tangent-ducks"				);
	ACCEL("<Mod1>4",								"<Actions>/canvasview/mask-radius-ducks"				);
	ACCEL("<Mod1>5",								"<Actions>/canvasview/mask-width-ducks"					);
	ACCEL("<Mod1>6",								"<Actions>/canvasview/mask-angle-ducks"					);
	ACCEL("<Mod1>7",								"<Actions>/canvasview/mask-bone-setup-ducks"				);
	ACCEL("<Mod1>8",								"<Actions>/canvasview/mask-bone-recursive-ducks"			);
	ACCEL("<Mod1>9",								"<Actions>/canvasview/mask-bone-ducks"					);
	ACCEL("<Mod1>5",								"<Actions>/canvasview/mask-widthpoint-position-ducks"			);
	ACCEL2(Gtk::AccelKey(GDK_KEY_Page_Up,Gdk::SHIFT_MASK,				"<Actions>/action_group_layer_action_manager/action-LayerRaise"		));
	ACCEL2(Gtk::AccelKey(GDK_KEY_Page_Down,Gdk::SHIFT_MASK,				"<Actions>/action_group_layer_action_manager/action-LayerLower"		));
	ACCEL("<Control>1",								"<Actions>/canvasview/quality-01"					);
	ACCEL("<Control>2",								"<Actions>/canvasview/quality-02"					);
	ACCEL("<Control>3",								"<Actions>/canvasview/quality-03"					);
	ACCEL("<Control>4",								"<Actions>/canvasview/quality-04"					);
	ACCEL("<Control>5",								"<Actions>/canvasview/quality-05"					);
	ACCEL("<Control>6",								"<Actions>/canvasview/quality-06"					);
	ACCEL("<Control>7",								"<Actions>/canvasview/quality-07"					);
	ACCEL("<Control>8",								"<Actions>/canvasview/quality-08"					);
	ACCEL("<Control>9",								"<Actions>/canvasview/quality-09"					);
	ACCEL("<Control>0",								"<Actions>/canvasview/quality-10"					);
	ACCEL("<Control>z",								"<Actions>/action_group_dock_history/undo"				);
	ACCEL("<Control>r",								"<Actions>/action_group_dock_history/redo"				);
	ACCEL2(Gtk::AccelKey(GDK_KEY_Delete,Gdk::ModifierType(),				"<Actions>/action_group_layer_action_manager/action-LayerRemove"	));
	ACCEL2(Gtk::AccelKey('(',Gdk::CONTROL_MASK,					"<Actions>/canvasview/decrease-low-res-pixel-size"			));
	ACCEL2(Gtk::AccelKey(')',Gdk::CONTROL_MASK,					"<Actions>/canvasview/increase-low-res-pixel-size"			));
	ACCEL2(Gtk::AccelKey('(',Gdk::MOD1_MASK|Gdk::CONTROL_MASK,			"<Actions>/action_group_layer_action_manager/amount-dec"		));
	ACCEL2(Gtk::AccelKey(')',Gdk::MOD1_MASK|Gdk::CONTROL_MASK,			"<Actions>/action_group_layer_action_manager/amount-inc"		));
	ACCEL2(Gtk::AccelKey(']',Gdk::CONTROL_MASK,					"<Actions>/canvasview/jump-next-keyframe"				));
	ACCEL2(Gtk::AccelKey('[',Gdk::CONTROL_MASK,					"<Actions>/canvasview/jump-prev-keyframe"				));
	ACCEL2(Gtk::AccelKey('=',Gdk::CONTROL_MASK,					"<Actions>/canvasview/canvas-zoom-in"					));
	ACCEL2(Gtk::AccelKey('-',Gdk::CONTROL_MASK,					"<Actions>/canvasview/canvas-zoom-out"					));
	ACCEL2(Gtk::AccelKey('+',Gdk::CONTROL_MASK,					"<Actions>/canvasview/time-zoom-in"					));
	ACCEL2(Gtk::AccelKey('_',Gdk::CONTROL_MASK,					"<Actions>/canvasview/time-zoom-out"					));
	ACCEL2(Gtk::AccelKey('.',Gdk::CONTROL_MASK,					"<Actions>/canvasview/seek-next-frame"					));
	ACCEL2(Gtk::AccelKey(',',Gdk::CONTROL_MASK,					"<Actions>/canvasview/seek-prev-frame"					));
	ACCEL2(Gtk::AccelKey('>',Gdk::CONTROL_MASK,					"<Actions>/canvasview/seek-next-second"					));
	ACCEL2(Gtk::AccelKey('<',Gdk::CONTROL_MASK,					"<Actions>/canvasview/seek-prev-second"					));
	ACCEL("<Mod1>o",								"<Actions>/canvasview/toggle-onion-skin"				);
	ACCEL("<Control><Shift>z",							"<Actions>/canvasview/canvas-zoom-fit"					);
	ACCEL("<Control>p",								"<Actions>/canvasview/play"						);


#undef ACCEL
#undef ACCEL2
}

#ifdef _WIN32
#define mkdir(x,y) mkdir(x)
#endif

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

App::App(const synfig::String& basepath, int *argc, char ***argv):
	Gtk::Main(argc,argv)
{

	Glib::init(); // need to use Gio functions before app is started
	app_base_path_=etl::dirname(basepath);

	// Set ui language
	load_language_settings();
	if (ui_language != "os_LANG")
	{
		Glib::setenv ("LANGUAGE",  App::ui_language.c_str(), 1);
	}

	// paths
#ifdef _WIN32
	String path_to_plugins = get_base_path()
		+ ETL_DIRECTORY_SEPARATOR + PLUGIN_DIR;
	String path_to_sounds = get_base_path()
		+ ETL_DIRECTORY_SEPARATOR + SOUND_DIR;
#else
	String path_to_plugins = PLUGIN_DIR;
	String path_to_sounds = SOUND_DIR;
#endif

	String path_to_icons = ResourceHelper::get_image_path();

	if (char* synfig_root = getenv("SYNFIG_ROOT")) {
		path_to_plugins = String(synfig_root)
			+ ETL_DIRECTORY_SEPARATOR + "share"
			+ ETL_DIRECTORY_SEPARATOR + "synfig"
			+ ETL_DIRECTORY_SEPARATOR + "plugins";
		path_to_sounds = String(synfig_root)
			+ ETL_DIRECTORY_SEPARATOR + "share"
			+ ETL_DIRECTORY_SEPARATOR + "synfig"
			+ ETL_DIRECTORY_SEPARATOR + "sounds";
	}

	String path_to_user_plugins = synfigapp::Main::get_user_app_directory()
		+ ETL_DIRECTORY_SEPARATOR + "plugins";
	
	// icons
	init_icons(path_to_icons + ETL_DIRECTORY_SEPARATOR);

	ui_interface_=new GlobalUIInterface();

	// don't call thread_init() if threads are already initialized
	// on some machines bonobo_init() initialized threads before we get here
	if (!g_thread_supported())
		Glib::thread_init();

	distance_system=Distance::SYSTEM_PIXELS;
	
#ifdef _WIN32
	// Do not show "No disc in drive" errors
	// - https://github.com/synfig/synfig/issues/489
	// - https://github.com/synfig/synfig/issues/724
	SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX);
#endif

	if(mkdir(synfigapp::Main::get_user_app_directory().c_str(),ACCESSPERMS)<0)
	{
		if(errno!=EEXIST)
			synfig::error("UNABLE TO CREATE \"%s\"",synfigapp::Main::get_user_app_directory().c_str());
	}
	else
	{
		synfig::info("Created directory \"%s\"",synfigapp::Main::get_user_app_directory().c_str());
	}


	ipc=new IPC();

	if(!SYNFIG_CHECK_VERSION())
	{
		cerr<<"FATAL: Synfig Version Mismatch"<<endl;
		dialog_message_1b(
			"ERROR",
			_("Synfig version mismatched!"),
			_("This copy of Synfig Studio was compiled against a "
			"different version of libsynfig than what is currently "
			"installed. Synfig Studio will now abort. Try downloading "
			"the latest version from the Synfig website at "
			"https://www.synfig.org/download-development/"),
			_("Close"));

		throw 40;
	}
	Glib::set_application_name(_("Synfig Studio"));

	Splash splash_screen;
	splash_screen.show();

	shutdown_in_progress=false;
	SuperCallback synfig_init_cb(splash_screen.get_callback(),0,9000,10000);
	SuperCallback studio_init_cb(splash_screen.get_callback(),9000,10000,10000);

	// Initialize the Synfig library
	try { synfigapp_main=etl::smart_ptr<synfigapp::Main>(new synfigapp::Main(basepath,&synfig_init_cb)); }
	catch(std::runtime_error &x)
	{
		get_ui_interface()->error(strprintf("%s\n\n%s", _("Failed to initialize synfig!"), x.what()));
		throw;
	}
	catch(...)
	{
		get_ui_interface()->error(_("Failed to initialize synfig!"));
		throw;
	}


	// add the preferences to the settings
	synfigapp::Main::settings().add_domain(&_preferences,"pref");

	try
	{
		// Try to load settings early to get access to some important
		// values, like "enable_experimental_features".
		studio_init_cb.task(_("Loading Basic Settings..."));

		load_settings("pref.use_dark_theme");
		App::apply_gtk_settings();

		load_settings("pref.show_file_toolbar");

		// Set experimental features
		load_settings("pref.enable_experimental_features");

		// Set main window menu and toolbar
		load_settings("pref.enable_mainwin_menubar");

		load_settings("pref.default_background_layer_type");
		load_settings("pref.default_background_layer_color");
		load_settings("pref.default_background_layer_image");
		load_settings("pref.preview_background_color");
		load_settings("pref.image_editor_path");

		studio_init_cb.task(_("Loading Plugins..."));
		plugin_manager.load_dir(path_to_plugins);
		plugin_manager.load_dir(path_to_user_plugins);

		studio_init_cb.task(_("Init UI Manager..."));
		App::ui_manager_=studio::UIManager::create();
		init_ui_manager();

		studio_init_cb.task(_("Init Dock Manager..."));
		dock_manager=new studio::DockManager();

		studio_init_cb.task(_("Init State Manager..."));
		state_manager=new StateManager();

		studio_init_cb.task(_("Init Main Window..."));
		main_window=new studio::MainWindow();
		main_window->add_accel_group(App::ui_manager_->get_accel_group());

		studio_init_cb.task(_("Init Toolbox..."));
		dock_toolbox=new studio::Dock_Toolbox();
		dock_manager->register_dockable(*dock_toolbox);

		studio_init_cb.task(_("Init About Dialog..."));
		about=new studio::About();

		studio_init_cb.task(_("Init Tool Options..."));
		dialog_tool_options=new studio::Dialog_ToolOptions();
		dock_manager->register_dockable(*dialog_tool_options);

		studio_init_cb.task(_("Init History..."));
		dock_history=new studio::Dock_History();
		dock_manager->register_dockable(*dock_history);

		studio_init_cb.task(_("Init Canvases..."));
		dock_canvases=new studio::Dock_Canvases();
		dock_manager->register_dockable(*dock_canvases);

		studio_init_cb.task(_("Init Keyframes..."));
		dock_keyframes=new studio::Dock_Keyframes();
		dock_manager->register_dockable(*dock_keyframes);

//! Must be done before Dock_Timetrack and Dock_Curves :
//! both are connected to a studio::LayerTree::param_tree_view_'s signal, and
//! studio::LayerTree is created from Dock_Layers::init_canvas_view_vfunc
		studio_init_cb.task(_("Init Layers..."));
		dock_layers=new studio::Dock_Layers();
		dock_manager->register_dockable(*dock_layers);

		studio_init_cb.task(_("Init Parameters..."));
		dock_params=new studio::Dock_Params();
		dock_manager->register_dockable(*dock_params);

		studio_init_cb.task(_("Init MetaData..."));
		dock_meta_data=new studio::Dock_MetaData();
		dock_manager->register_dockable(*dock_meta_data);

		studio_init_cb.task(_("Init Library..."));
		dock_children=new studio::Dock_Children();
		dock_manager->register_dockable(*dock_children);

		studio_init_cb.task(_("Init Info..."));
		dock_info = new studio::Dock_Info();
		dock_manager->register_dockable(*dock_info);

		studio_init_cb.task(_("Init Navigator..."));
		dock_navigator = new studio::Dock_Navigator();
		dock_manager->register_dockable(*dock_navigator);

		studio_init_cb.task(_("Init SoundWave..."));
		dock_soundwave = new studio::Dock_SoundWave();
		dock_manager->register_dockable(*dock_soundwave);

		studio_init_cb.task(_("Init Timetrack..."));
		dock_timetrack = new studio::Dock_Timetrack();
		dock_manager->register_dockable(*dock_timetrack);

		studio_init_cb.task(_("Init Curve Editor..."));
		dock_curves = new studio::Dock_Curves();
		dock_manager->register_dockable(*dock_curves);

		studio_init_cb.task(_("Init Layer Sets..."));
		dock_layer_groups = new studio::Dock_LayerGroups();
		dock_manager->register_dockable(*dock_layer_groups);


		studio_init_cb.task(_("Init Color Dialog..."));
		dialog_color=new studio::Dialog_Color();

		studio_init_cb.task(_("Init Gradient Dialog..."));
		dialog_gradient=new studio::Dialog_Gradient();

		studio_init_cb.task(_("Init DeviceTracker..."));
		device_tracker=new studio::DeviceTracker();

		//Init Tools...was here

		studio_init_cb.task(_("Init ModPalette..."));
		module_list_.push_back(new ModPalette()); module_list_.back()->start();

		studio_init_cb.task(_("Init Setup Dialog..."));
		dialog_setup=new studio::Dialog_Setup(*App::main_window);

		studio_init_cb.task(_("Init Input Dialog..."));
		dialog_input=new studio::Dialog_Input(*App::main_window);
		dialog_input->signal_apply().connect( sigc::mem_fun( *device_tracker, &DeviceTracker::save_preferences) );

		studio_init_cb.task(_("Loading Custom Workspace List..."));
		workspaces = new WorkspaceHandler();
		workspaces->signal_list_changed().connect( sigc::mem_fun(signal_custom_workspaces_changed_, &sigc::signal<void>::emit) );
		load_custom_workspaces();

		studio_init_cb.task(_("Init auto recovery..."));
		auto_recover=new AutoRecover();

		studio_init_cb.amount_complete(9250,10000);
		studio_init_cb.task(_("Loading Settings..."));
		load_accel_map();
		if (!load_settings())
			set_workspace_default();
		if (!load_settings("workspace.layout"))
			set_workspace_default();
		load_file_window_size();

		// Init Tools must be done after load_accel_map() : accelerators keys
		// are displayed in toolbox labels
		studio_init_cb.task(_("Init Tools..."));
		/* editing tools */
		state_manager->add_state(&state_normal);
		state_manager->add_state(&state_smooth_move);
		state_manager->add_state(&state_scale);
		state_manager->add_state(&state_rotate);
		state_manager->add_state(&state_mirror);

		/* geometry */
		state_manager->add_state(&state_circle);
		state_manager->add_state(&state_rectangle);
		state_manager->add_state(&state_star);
		if(!getenv("SYNFIG_DISABLE_POLYGON")) state_manager->add_state(&state_polygon); // Enabled - for working without ducks
		state_manager->add_state(&state_gradient);

		/* bline tools */
		state_manager->add_state(&state_bline);
		if(!getenv("SYNFIG_DISABLE_DRAW"   )) state_manager->add_state(&state_draw ); // Enabled for now.  Let's see whether they're good enough yet.
                state_manager->add_state(&state_lasso); // Enabled for now.  Let's see whether they're good enough yet.
		if(!getenv("SYNFIG_DISABLE_WIDTH"  )) state_manager->add_state(&state_width); // Enabled since 0.61.09
		state_manager->add_state(&state_fill);
		state_manager->add_state(&state_eyedrop);

		/* other */
		state_manager->add_state(&state_text);
		if(!getenv("SYNFIG_DISABLE_SKETCH" )) state_manager->add_state(&state_sketch);
		if(!getenv("SYNFIG_DISABLE_BRUSH"  ) && App::enable_experimental_features) state_manager->add_state(&state_brush);
		state_manager->add_state(&state_zoom);


		device_tracker->load_preferences();
		// If the default bline width is modified before focus a canvas
		// window, the Distance widget doesn't understand the given value
		// and produces this message:
		// Distance::ident_system(): Unknown distance system ".00pt"
		// setting the default bline width to 1 unit.
		// This line fixes that.
		synfigapp::Main::set_bline_width(synfigapp::Main::get_selected_input_device()->get_bline_width());

		studio_init_cb.task(_("Checking auto-recover..."));

		studio_init_cb.amount_complete(9900,10000);

		bool opened_any = false;
		if (!getenv("SYNFIG_DISABLE_AUTO_RECOVERY") && auto_recover->recovery_needed())
		{
			splash_screen.hide();
			if (get_ui_interface()->confirmation(
					_("Auto recovery file(s) found. Do you want to recover unsaved changes?"),
					_("Synfig Studio seems to have crashed before you could save all your files."),
					_("Ignore"),
					_("Recover")
				) == synfigapp::UIInterface::RESPONSE_OK)
			{
				int number_recovered;
				if(!auto_recover->recover(number_recovered))
					if (number_recovered)
						get_ui_interface()->error(_("Unable to fully recover from previous crash"));
					else
						get_ui_interface()->error(_("Unable to recover from previous crash"));
				else
					dialog_message_1b(
						"WARNING",
						_("It would be a good idea to review and save recovered files now."),
						_("Synfig Studio has attempted to recover from a previous crash. "
						"The files just recovered are NOT YET SAVED."),
						_("Thanks"));

				if (number_recovered)
					opened_any = true;
			}
			else
			{
				auto_recover->clear_backups();
			}
			splash_screen.show();
		}

		// Look for any files given on the command line,
		// and load them if found.
		for(;*argc>=1;(*argc)--)
			if((*argv)[*argc] && (*argv)[*argc][0]!='-')
			{
				studio_init_cb.task(_("Loading files..."));
				splash_screen.hide();
				open(Glib::locale_to_utf8((*argv)[*argc]));
				opened_any = true;
				splash_screen.show();
			}

		// if no file was specified to be opened, create a new document to help new users get started more easily
		if (!opened_any && !getenv("SYNFIG_DISABLE_AUTOMATIC_DOCUMENT_CREATION"))
			new_instance();

		studio_init_cb.task(_("Done."));
		studio_init_cb.amount_complete(10000,10000);

		// To avoid problems with some window managers and gtk >= 2.18
		// we should show dock dialogs after the settings load.
		// If dock dialogs are shown before the settings are loaded,
		// the windows manager can act over it.
		// See discussions here:
		// * https://synfig.org/forums/viewtopic.php?f=1&t=1131&st=0&sk=t&sd=a&start=30
		// * https://synfig.org/forums/viewtopic.php?f=15&t=1062
		dock_manager->show_all_dock_dialogs();

		main_window->present();
		dock_toolbox->present();

		splash_screen.hide();

		String message;
		String details;
		/*
		if (App::enable_experimental_features) {
			message = _("Following experimental features are enabled: ");
			message += ("Skeleton Layer");
			detials = _("The experimental features are NOT intended for production use. "
					"It is quite posiible their functionality will change in the "
					"future versions, which can break compatibility for your "
					"files. Use for testing purposes only. You can disable "
					"experimental features on the \"Misc\" tab of Setup dialog.");
		}
		*/
#ifdef _WIN32
		if (message!=""){
			message = _("There is a bug, which can cause computer to hang/freeze when "
					"resizing the canvas window.");
			details = _("If you got affected by this issue, consider pressing ALT+TAB "
					"to unfreeze your system and get it back to the working "
					"state. Please accept our apologies for inconvenience, we "
					"hope to get this issue resolved in the future versions.");
		}
#endif
		if (message!="")
			dialog_message_1b("WARNING",
					message,
					details,
					_("Got it"));
	}
	catch(String &x)
	{
		get_ui_interface()->error(_("Unknown exception caught when constructing App.\nThis software may be unstable.") + String("\n\n") + x);
	}
	catch(...)
	{
		get_ui_interface()->error(_("Unknown exception caught when constructing App.\nThis software may be unstable."));
	}

	// Load sound effects
	sound_render_done = new SoundProcessor();
	sound_render_done->addSound(
		SoundProcessor::PlayOptions(),
		SoundProcessor::Sound(path_to_sounds + ETL_DIRECTORY_SEPARATOR + "renderdone.wav") );
	
	App::dock_info_ = dock_info;
}

StateManager* App::get_state_manager() { return state_manager; }

App::~App()
{
	shutdown_in_progress=true;

	save_settings();

	synfigapp::Main::settings().remove_domain("pref");

	selected_instance=0;

	// Unload all of the modules
	for(;!module_list_.empty();module_list_.pop_back())
		;

	delete state_manager;

	delete ipc;

	delete auto_recover;

	delete about;

	main_window->hide();

	delete main_window;

	delete dialog_setup;

	delete dialog_gradient;

	delete dialog_color;

	delete dialog_input;

	delete dock_manager;

	delete workspaces;

	instance_list.clear();

	if (sound_render_done) delete sound_render_done;
	sound_render_done = NULL;
}

synfig::String
App::get_config_file(const synfig::String& file)
{
	return Glib::build_filename(synfigapp::Main::get_user_app_directory(),file);
}

void
App::add_recent_file(const etl::handle<Instance> instance)
{
	add_recent_file(absolute_path(instance->get_file_name()));
}

void
App::add_recent_file(const std::string &file_name)
{
	std::string filename(FileSystem::fix_slashes(file_name));

	assert(!filename.empty());

	if(filename.empty())
		return;

	// Toss out any "hidden" files
	if(basename(filename)[0]=='.')
		return;

	// If we aren't an absolute path, turn ourselves into one
	if(!is_absolute_path(filename))
		filename=absolute_path(filename);

	list<string>::iterator iter;
	// Check to see if the file is already on the list.
	// If it is, then remove it from the list
	for(iter=recent_files.begin();iter!=recent_files.end();iter++)
		if(*iter==filename)
		{
			recent_files.erase(iter);
			break;
		}


	// Push the filename to the front of the list
	recent_files.push_front(filename);

	// Clean out the files at the end of the list.
	while(recent_files.size()>(unsigned)get_max_recent_files())
	{
		recent_files.pop_back();
	}

	signal_recent_files_changed_();

	return;
}

static Time::Format _App_time_format(Time::FORMAT_FRAMES);

bool App::jack_is_locked()
{
	return jack_locks_ > 0;
}

void App::jack_lock()
{
	++jack_locks_;
	if (jack_locks_ == 1)
	{
		// lock jack in instances
		for(std::list< etl::handle<Instance> >::const_iterator i = instance_list.begin(); i != instance_list.end(); ++i)
		{
			const Instance::CanvasViewList &views = (*i)->canvas_view_list();
			for(Instance::CanvasViewList::const_iterator j = views.begin(); j != views.end(); ++j)
				(*j)->jack_lock();
		}
	}
}

void App::jack_unlock()
{
	--jack_locks_;
	assert(jack_locks_ >= 0);
	if (jack_locks_ == 0)
	{
		// unlock jack in instances
		for(std::list< etl::handle<Instance> >::const_iterator i = instance_list.begin(); i != instance_list.end(); ++i)
		{
			const Instance::CanvasViewList &views = (*i)->canvas_view_list();
			for(Instance::CanvasViewList::const_iterator j = views.begin(); j != views.end(); ++j)
				(*j)->jack_unlock();
		}
	}
}


Time::Format
App::get_time_format()
{
	return _App_time_format;
}

void
App::set_time_format(synfig::Time::Format x)
{
	_App_time_format=x;
}


void
App::save_settings()
{
	try
	{
		synfig::ChangeLocale change_locale(LC_NUMERIC, "C");
		{
			std::string filename=get_config_file("accelrc");
			Gtk::AccelMap::save(filename);
		}
		{
			std::string filename=get_config_file("language");

			std::ofstream file(filename.c_str());

			if(!file)
			{
				synfig::warning("Unable to save %s",filename.c_str());
			} else {
				file<<App::ui_language.c_str()<<endl;
			}
		}
		do{
			std::string filename=get_config_file("recentfiles");

			std::ofstream file(filename.c_str());

			if(!file)
			{
				synfig::warning("Unable to save %s",filename.c_str());
				break;
			}

			list<string>::reverse_iterator iter;

			for(iter=recent_files.rbegin();iter!=recent_files.rend();iter++)
				file<<(*iter).c_str()<<endl;
		}while(0);
		std::string filename=get_config_file("settings-1.3");
		synfigapp::Main::settings().save_to_file(filename);

		{
			std::string filename = get_config_file("workspaces");
			workspaces->save(filename);
		}
	}
	catch(...)
	{
		synfig::warning("Caught exception when attempting to save settings.");
	}
}

bool
App::load_settings(const synfig::String& key_filter)
{
	bool ret=false;
	try
	{
		synfig::ChangeLocale change_locale(LC_NUMERIC, "C");
		std::string filename=get_config_file("settings-1.3");
		ret=synfigapp::Main::settings().load_from_file(filename, key_filter);
	}
	catch(...)
	{
		synfig::warning("Caught exception when attempting to load settings.");
	}
	return ret;
}

void
App::load_accel_map()
{
	try
	{
		synfig::ChangeLocale change_locale(LC_NUMERIC, "C");
		{
			std::string filename=get_config_file("accelrc");
			Gtk::AccelMap::load(filename);
		}
	}
	catch(...)
	{
		synfig::warning("Caught exception when attempting to load accel map settings.");
	}
}

void
App::load_file_window_size()
{
	try
	{
		synfig::ChangeLocale change_locale(LC_NUMERIC, "C");
		{
			std::string filename=get_config_file("recentfiles");
			std::ifstream file(filename.c_str());

			while(file)
			{
				std::string recent_file;
				std::string recent_file_window_size;
				getline(file,recent_file);
				if(!recent_file.empty() && FileSystemNative::instance()->is_file(recent_file))
					add_recent_file(recent_file);
			}
		}

	}
	catch(...)
	{
		synfig::warning("Caught exception when attempting to load window settings.");
	}
}

void
App::load_language_settings()
{
	try
	{
		synfig::ChangeLocale change_locale(LC_NUMERIC, "C");
		{
			std::string filename=get_config_file("language");
			std::ifstream file(filename.c_str());

			while(file)
			{
				std::string language;
				getline(file,language);
				if(!language.empty())
					App::ui_language=language;
			}
		}

	}
	catch(...)
	{
		synfig::warning("Caught exception when attempting to loading language settings.");
	}
}

void
App::set_workspace_default()
{
	std::string tpl =
	"[mainwindow|%0X|%0Y|%100x|%90y|"
		"[hor|%75x"
			"|[vert|%70y"
				"|[hor|%10x"
					"|[book|toolbox]"
					"|[mainnotebook]"
				"]"
				"|[hor|%25x"
					"|[book|params|keyframes]"
					"|[book|timetrack|curves|children|meta_data|soundwave]"
				"]"
			"]"
			"|[vert|%20y"
				"|[book|canvases|pal_edit|navigator|info]"
				"|[vert|%25y"
					"|[book|tool_options|history]"
                                        "|[book|layers|groups]"
				"]"
			"]"
		"]"
	"]";

	set_workspace_from_template(tpl);
}

void
App::set_workspace_compositing()
{
	std::string tpl =
	"[mainwindow|%0X|%0Y|%100x|%90y|"
		"[hor|%1x"
			"|[vert|%1y|[book|toolbox]|[book|tool_options]]"
			"|[hor|%60x|[mainnotebook]"
				"|[hor|%50x|[book|params]"
					"|[vert|%30y|[book|history|groups]|[book|layers|canvases]]"
			"]"
		"]"
	"]";

	set_workspace_from_template(tpl);
}

void
App::set_workspace_animating()
{
	std::string tpl =
	"[mainwindow|%0X|%0Y|%100x|%90y|"
		"[hor|%70x"
			"|[vert|%1y"
				"|[hor|%1x|[book|toolbox]|[mainnotebook]]"
				"|[hor|%25x|[book|params|children]|[book|timetrack|curves|soundwave|]]"
			"]"
			"|[vert|%30y"
				"|[book|keyframes|history|groups]|[book|layers|canvases]]"
			"]"
		"]"
	"]";

	set_workspace_from_template(tpl);
}

void App::set_workspace_from_template(const string& tpl)
{
	Glib::RefPtr<Gdk::Display> display(Gdk::Display::get_default());
	Glib::RefPtr<const Gdk::Screen> screen(display->get_default_screen());
	Gdk::Rectangle rect;
	// A proper way to obtain the primary monitor is to use the
	// Gdk::Screen::get_primary_monitor () const member. But as it
	// was introduced in gtkmm 2.20 I assume that the monitor 0 is the
	// primary one.
	screen->get_monitor_geometry(0,rect);
	float dx = (float)rect.get_x();
	float dy = (float)rect.get_y();
	float sx = (float)rect.get_width();
	float sy = (float)rect.get_height();

	std::string layout = DockManager::layout_from_template(tpl, dx, dy, sx, sy);
	dock_manager->load_layout_from_string(layout);
	dock_manager->show_all_dock_dialogs();
}

void App::set_workspace_from_name(const string& name)
{
	std::string tpl;
	bool ok = workspaces->get_workspace(name, tpl);
	if (!ok)
		return;
	set_workspace_from_template(tpl);
}

void App::load_custom_workspaces()
{
	workspaces->clear();
	std::string filename = get_config_file("workspaces");
	workspaces->load(filename);
}

static void trim_string(std::string &text)
{
	text.erase(text.begin(),
			   std::find_if(text.begin(), text.end(),
							[](int chr) { return !std::isspace(chr);})
			   );
	text.erase(std::find_if(text.rbegin(), text.rend(),
							[](int chr) { return !std::isspace(chr);}).base(),
			   text.end()
			   );
}

void App::save_custom_workspace()
{
	Gtk::MessageDialog dialog(*App::main_window, _("Type a name for this custom workspace:"), false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_NONE);

	dialog.add_button(_("Cancel"), Gtk::RESPONSE_CANCEL);
	Gtk::Button * ok_button = dialog.add_button(_("Ok"), Gtk::RESPONSE_OK);
	ok_button->set_sensitive(false);

	Gtk::Entry * name_entry = Gtk::manage(new Gtk::Entry());
	name_entry->set_margin_left(16);
	name_entry->set_margin_right(16);
	name_entry->signal_changed().connect([&](){
		std::string name = name_entry->get_text();
		trim_string(name);
		bool has_equal_sign = name.find("=") != std::string::npos;
		ok_button->set_sensitive(!name.empty() && !has_equal_sign);
		if (ok_button->is_sensitive())
			ok_button->grab_default();
	});
	name_entry->signal_activate().connect(sigc::mem_fun(*ok_button, &Gtk::Button::clicked));

	dialog.get_content_area()->set_spacing(12);
	dialog.get_content_area()->add(*name_entry);

	ok_button->set_can_default(true);

	dialog.show_all();

	int response = dialog.run();
	if (response == Gtk::RESPONSE_CANCEL)
		return;

	std::string name = name_entry->get_text();
	trim_string(name);

	std::string tpl = dock_manager->save_layout_to_string();
	if (!workspaces->has_workspace(name))
		workspaces->add_workspace(name, tpl);
	else {
		Gtk::MessageDialog confirm_dlg(dialog, _("Do you want to overwrite this workspace?"), false, Gtk::MESSAGE_QUESTION, Gtk::BUTTONS_OK_CANCEL);
		if (confirm_dlg.run() == Gtk::RESPONSE_CANCEL)
			return;
		workspaces->set_workspace(name, tpl);
	}
}

void App::edit_custom_workspace_list()
{
	Dialog_Workspaces * dlg = Dialog_Workspaces::create(*App::main_window);
	if (!dlg) {
		synfig::warning("Can't load Dialog_Workspaces");
		return;
	}
	dlg->run();
	delete dlg;
}

void
App::restore_default_settings()
{
	synfigapp::Main::settings().set_value("pref.distance_system",               "pt");
	synfigapp::Main::settings().set_value("pref.restrict_radius_ducks",          "1");
	synfigapp::Main::settings().set_value("pref.resize_imported_images",         "0");
	synfigapp::Main::settings().set_value("pref.enable_experimental_features",   "0");
	synfigapp::Main::settings().set_value("pref.custom_filename_prefix",         DEFAULT_FILENAME_PREFIX);
	synfigapp::Main::settings().set_value("pref.ui_language",                    "os_LANG");
	synfigapp::Main::settings().set_value("pref.preferred_x_size",               "480");
	synfigapp::Main::settings().set_value("pref.preferred_y_size",               "270");
	synfigapp::Main::settings().set_value("pref.predefined_size",                DEFAULT_PREDEFINED_SIZE);
	synfigapp::Main::settings().set_value("pref.preferred_fps",                  "24.0");
	synfigapp::Main::settings().set_value("pref.predefined_fps",                 DEFAULT_PREDEFINED_FPS);
	synfigapp::Main::settings().set_value("pref.sequence_separator",             ".");
	synfigapp::Main::settings().set_value("pref.navigator_renderer",             "");
	synfigapp::Main::settings().set_value("pref.workarea_renderer",              "");
	synfigapp::Main::settings().set_value("pref.use_render_done_sound",          "1");
	synfigapp::Main::settings().set_value("pref.default_background_layer_type",  "none");
	synfigapp::Main::settings().set_value("pref.default_background_layer_color", "1.000000 1.000000 1.000000 1.000000"); //White
	synfigapp::Main::settings().set_value("pref.preview_background_color",       "0.742187 0.742187 0.742187 1.000000"); //X11 Gray

	synfigapp::Main::settings().set_value("pref.default_background_layer_image", "");
	synfigapp::Main::settings().set_value("pref.enable_mainwin_menubar",         "1");
	ostringstream temp;
	temp << Duck::STRUCT_DEFAULT;
	synfigapp::Main::settings().set_value("pref.ui_handle_tooltip_flag",         temp.str());
	synfigapp::Main::settings().set_value("pref.autosave_backup",                "1");
	synfigapp::Main::settings().set_value("pref.autosave_backup_interval",       "15000");
	synfigapp::Main::settings().set_value("pref.image_editor_path",             "");
}

void
App::apply_gtk_settings()
{
	GtkSettings *gtk_settings;
	gtk_settings = gtk_settings_get_default ();

	gchar *theme_name=getenv("SYNFIG_GTK_THEME");
	if(theme_name) {
		g_object_set (G_OBJECT (gtk_settings), "gtk-theme-name", theme_name, NULL);
	}

	// dark theme
	g_object_set (G_OBJECT (gtk_settings), "gtk-application-prefer-dark-theme", App::use_dark_theme, NULL);

	// enable menu icons
	g_object_set (G_OBJECT (gtk_settings), "gtk-menu-images", TRUE, NULL);

	// fix CSS
	Glib::ustring data;
	// Fix GtkPaned (big margin makes it hard to grab first keyframe))
	data += "GtkPaned { margin: 2px; }\n";
	// Fix #348: Synfig's Interface went Too Thick
	// following css works in gtk since 3.14:
	data += ".button                            { padding-left: 4px; padding-right: 4px; }\n";
	data += ".button                            { padding-top: 0px; padding-bottom: 0px; }\n";
	data += ".button *                          { padding-top: 4px; padding-bottom: 4px; }\n";
	data += ".button > GtkBox                   { padding-top: 0px; padding-bottom: 0px; }\n";
	data += ".button > GtkBox > *               { padding-top: 4px; padding-bottom: 4px; }\n";
	data += ".button > GtkLabel                 { padding-top: 0px; padding-bottom: 0px; }\n";
	data += "GtkComboBox > .button > GtkBox > * { padding-top: 0px; padding-bottom: 0px; }\n";
	data += ".entry                             { padding-top: 0px; padding-bottom: 0px; }\n";
	data += "progress, trough 					{ min-height: 20px; }\n";
#if GTKMM_MAJOR_VERSION < 3 || (GTKMM_MAJOR_VERSION == 3 && GTKMM_MINOR_VERSION < 22)
	// following css works in old versions of gtk
	data += "button { padding: 0px; }\n";
#else
	// following css works for gtk 3.22:
	data += "entry, spinbutton { min-height: 16px; }\n";
	data += "button { min-height: 16px; min-width: 16px; padding: 0px; }\n";
#endif
	data += "button > box { padding: 5px; }\n";
	data += "button > image { padding: 5px; }\n";
	data += "combobox > box > button > box { padding-top: 0px; padding-bottom: 0px; }\n";
	// Fix #810: Insetsetive context menus on OSX
	g_object_get (G_OBJECT (gtk_settings), "gtk-theme-name", &theme_name, NULL);
	if ( String(theme_name) == "Adwaita" )
		data += ".window-frame, .window-frame:backdrop { box-shadow: none; margin: 0; }\n";
	g_free(theme_name);

	if (!data.empty()) {
		Glib::RefPtr<Gtk::CssProvider> css = Gtk::CssProvider::create();
		try {
			css->load_from_data(data);
		} catch (Gtk::CssProviderError &e) {
			synfig::warning("Failed to load css rules. %s", e.what().c_str());
		}
		Glib::RefPtr<Gdk::Screen> screen = Gdk::Screen::get_default();
		Gtk::StyleContext::add_provider_for_screen(screen,css, GTK_STYLE_PROVIDER_PRIORITY_USER);
	}
}

bool
App::shutdown_request(GdkEventAny*)
{
	quit();
	return true;
	//return !shutdown_in_progress;
}

void
App::quit()
{
	if (shutdown_in_progress) return;

	get_ui_interface()->task(_("Quit Request"));
	
	if(Busy::count)
	{
		dialog_message_1b(
			"ERROR",
			_("Tasks are currently running. Please cancel the current tasks and try again"),
			"details",
			_("Close"));
		return;
	}

	while(!instance_list.empty())
		if (!instance_list.front()->safe_close())
			return;
	process_all_events();

	Gtk::Main::quit();

	get_ui_interface()->task(_("Quit Request sent"));
}

void
App::show_setup()
{
	dialog_setup->refresh();
	dialog_setup->show();
}

gint Signal_Open_Ok    (GtkWidget */*widget*/, int *val){*val=1; return 0;}
gint Signal_Open_Cancel(GtkWidget */*widget*/, int *val){*val=2; return 0;}

//#ifdef _WIN32
//#define USE_WIN32_FILE_DIALOGS 1
//#endif

#ifdef USE_WIN32_FILE_DIALOGS
static OPENFILENAME ofn={};
#endif

#ifdef _WIN32
#include <gdk/gdkwin32.h>
#endif

bool
App::dialog_open_file(const std::string &title, std::string &filename, std::string preference)
{
	// info("App::dialog_open_file('%s', '%s', '%s')", title.c_str(), filename.c_str(), preference.c_str());
	// TODO: Win32 native dialod not ready yet
#ifdef USE_WIN32_FILE_DIALOGS
	static TCHAR szFilter[] = TEXT (_("All Files (*.*)\0*.*\0\0")) ;

	GdkWindow *gdkWinPtr=toolbox->get_window()->gobj();
	HINSTANCE hInstance=static_cast<HINSTANCE>(GetModuleHandle(NULL));
	HWND hWnd=static_cast<HWND>(GDK_WINDOW_HWND(gdkWinPtr));

	ofn.lStructSize=sizeof(OPENFILENAME);
	ofn.hwndOwner = hWnd;
	ofn.hInstance = hInstance;
	ofn.lpstrFilter = szFilter;
//	ofn.lpstrCustomFilter=NULL;
//	ofn.nMaxCustFilter=0;
//	ofn.nFilterIndex=0;
//	ofn.lpstrFile=NULL;
	ofn.nMaxFile=MAX_PATH;
//	ofn.lpstrFileTitle=NULL;
//	ofn.lpstrInitialDir=NULL;
//	ofn.lpstrTitle=NULL;
	ofn.Flags=OFN_HIDEREADONLY;
//	ofn.nFileOffset=0;
//	ofn.nFileExtension=0;
	ofn.lpstrDefExt=TEXT("sif");
//	ofn.lCustData = 0l;
	ofn.lpfnHook=NULL;
//	ofn.lpTemplateName=NULL;

	CHAR szFilename[MAX_PATH];
	CHAR szTitle[500];
	strcpy(szFilename,filename.c_str());
	strcpy(szTitle,title.c_str());

	ofn.lpstrFile=szFilename;
	ofn.lpstrFileTitle=szTitle;

	if(GetOpenFileName(&ofn))
	{
		filename=szFilename;
		return true;
	}
	return false;

#else   // not USE_WIN32_FILE_DIALOGS
	synfig::String prev_path;

	if(!_preferences.get_value(preference, prev_path))
		prev_path = Glib::get_home_dir();
	prev_path = absolute_path(prev_path);

	Gtk::FileChooserDialog *dialog = new Gtk::FileChooserDialog(*App::main_window,
				title, Gtk::FILE_CHOOSER_ACTION_OPEN);

	dialog->set_transient_for(*App::main_window);
	dialog->set_current_folder(prev_path);
	dialog->add_button(_("Cancel"), Gtk::RESPONSE_CANCEL)->set_image_from_icon_name("gtk-cancel", Gtk::ICON_SIZE_BUTTON);
	dialog->add_button(_("Import"), Gtk::RESPONSE_ACCEPT)->set_image_from_icon_name("gtk-open",   Gtk::ICON_SIZE_BUTTON);

	// 0 All supported files
	// 0.1 Synfig documents. sfg is not supported to import
	Glib::RefPtr<Gtk::FileFilter> filter_supported = Gtk::FileFilter::create();
	filter_supported->set_name(_("All supported files"));
	filter_supported->add_mime_type("application/x-sif");
	filter_supported->add_pattern("*.sif");
	filter_supported->add_pattern("*.sifz");
	// 0.2 Image files
	filter_supported->add_mime_type("image/png");
	filter_supported->add_mime_type("image/jpeg");
	filter_supported->add_mime_type("image/jpg");
	filter_supported->add_mime_type("image/bmp");
	filter_supported->add_mime_type("image/svg+xml");
	filter_supported->add_pattern("*.png");
	filter_supported->add_pattern("*.jpeg");
	filter_supported->add_pattern("*.jpg");
	filter_supported->add_pattern("*.bmp");
	filter_supported->add_pattern("*.svg");
	filter_supported->add_pattern("*.lst");
	// 0.3 Audio files
	filter_supported->add_mime_type("audio/x-vorbis+ogg");
	filter_supported->add_mime_type("audio/mpeg");
	filter_supported->add_mime_type("audio/x-wav");
	filter_supported->add_pattern("*.ogg");
	filter_supported->add_pattern("*.mp3");
	filter_supported->add_pattern("*.wav");
	// 0.4 Video files
	filter_supported->add_pattern("*.avi");
	filter_supported->add_pattern("*.mp4");
	filter_supported->add_pattern("*.gif");
	// 0.5 lipsync files
	filter_supported->add_pattern("*.pgo");

	// Sub fileters
	// 1 Synfig documents. sfg is not supported to import
	Glib::RefPtr<Gtk::FileFilter> filter_synfig = Gtk::FileFilter::create();
	filter_synfig->set_name(_("Synfig files (*.sif, *.sifz)"));
	filter_synfig->add_mime_type("application/x-sif");
	filter_synfig->add_pattern("*.sif");
	filter_synfig->add_pattern("*.sifz");

	// 2.1 Image files
	Glib::RefPtr<Gtk::FileFilter> filter_image = Gtk::FileFilter::create();
	filter_image->set_name(_("Images (*.png, *.jpeg, *.bmp, *.svg)"));
	filter_image->add_mime_type("image/png");
	filter_image->add_mime_type("image/jpeg");
	filter_image->add_mime_type("image/jpg");
	filter_image->add_mime_type("image/bmp");
	filter_image->add_mime_type("image/svg+xml");
	filter_image->add_pattern("*.png");
	filter_image->add_pattern("*.jpeg");
	filter_image->add_pattern("*.jpg");
	filter_image->add_pattern("*.bmp");
	filter_image->add_pattern("*.svg");

	// 2.2 Image sequence/list files
	Glib::RefPtr<Gtk::FileFilter> filter_image_list = Gtk::FileFilter::create();
	filter_image_list->set_name(_("Image sequence files (*.lst)"));
	filter_image_list->add_pattern("*.lst");

	// 3 Audio files
	Glib::RefPtr<Gtk::FileFilter> filter_audio = Gtk::FileFilter::create();
	filter_audio->set_name(_("Audio (*.ogg, *.mp3, *.wav)"));
	filter_audio->add_mime_type("audio/x-vorbis+ogg");
	filter_audio->add_mime_type("audio/mpeg");
	filter_audio->add_mime_type("audio/x-wav");
	filter_audio->add_pattern("*.ogg");
	filter_audio->add_pattern("*.mp3");
	filter_audio->add_pattern("*.wav");

	// 4 Video files
	Glib::RefPtr<Gtk::FileFilter> filter_video = Gtk::FileFilter::create();
	filter_video->set_name(_("Video (*.avi, *.mp4)"));
	filter_video->add_mime_type("video/x-msvideo");
	filter_video->add_mime_type("video/mp4");
	filter_video->add_pattern("*.avi");
	filter_video->add_pattern("*.mp4");

	// 5 Lipsync files
	Glib::RefPtr<Gtk::FileFilter> filter_lipsync = Gtk::FileFilter::create();
	filter_lipsync->set_name(_("Lipsync (*.pgo)"));
	filter_lipsync->add_pattern("*.pgo");

	// 6 Any files
	Glib::RefPtr<Gtk::FileFilter> filter_any = Gtk::FileFilter::create();
	filter_any->set_name(_("Any files"));
	filter_any->add_pattern("*");

	dialog->add_filter(filter_supported);
	dialog->add_filter(filter_synfig);
	dialog->add_filter(filter_image);
	dialog->add_filter(filter_image_list);
	dialog->add_filter(filter_audio);
	dialog->add_filter(filter_video);
	dialog->add_filter(filter_lipsync);
	dialog->add_filter(filter_any);

	if (filename.empty())
		dialog->set_filename(prev_path);
	else if (is_absolute_path(filename))
		dialog->set_filename(filename);
	else
		dialog->set_filename(prev_path + ETL_DIRECTORY_SEPARATOR + filename);

	if(dialog->run() == GTK_RESPONSE_ACCEPT) {
		filename = dialog->get_filename();
		// info("Saving preference %s = '%s' in App::dialog_open_file()", preference.c_str(), dirname(filename).c_str());
		_preferences.set_value(preference, dirname(filename));
		delete dialog;
		return true;
	}

	delete dialog;
	return false;
#endif   // not USE_WIN32_FILE_DIALOGS
}

bool
App::dialog_open_file_spal(const std::string &title, std::string &filename, std::string preference)
{
	synfig::String prev_path;

	if(!_preferences.get_value(preference, prev_path))
		prev_path = Glib::get_home_dir();
	prev_path = absolute_path(prev_path);

	Gtk::FileChooserDialog *dialog = new Gtk::FileChooserDialog(*App::main_window,
				title, Gtk::FILE_CHOOSER_ACTION_OPEN);

	dialog->set_transient_for(*App::main_window);
	dialog->set_current_folder(prev_path);
	dialog->add_button(_("Cancel"), Gtk::RESPONSE_CANCEL)->set_image_from_icon_name("gtk-cancel", Gtk::ICON_SIZE_BUTTON);
	dialog->add_button(_("Load"),   Gtk::RESPONSE_ACCEPT)->set_image_from_icon_name("gtk-open", Gtk::ICON_SIZE_BUTTON);

	Glib::RefPtr<Gtk::FileFilter> filter_supported = Gtk::FileFilter::create();
	filter_supported->set_name(_("Palette files (*.spal, *.gpl)"));
	filter_supported->add_pattern("*.spal");
	filter_supported->add_pattern("*.gpl");
	dialog->add_filter(filter_supported);

	// show only Synfig color palette file (*.spal)
	Glib::RefPtr<Gtk::FileFilter> filter_spal = Gtk::FileFilter::create();
	filter_spal->set_name(_("Synfig palette files (*.spal)"));
	filter_spal->add_pattern("*.spal");
	dialog->add_filter(filter_spal);

	// ...and add GIMP color palette file too (*.gpl)
	Glib::RefPtr<Gtk::FileFilter> filter_gpl = Gtk::FileFilter::create();
	filter_gpl->set_name(_("GIMP palette files (*.gpl)"));
	filter_gpl->add_pattern("*.gpl");
	dialog->add_filter(filter_gpl);

	if (filename.empty())
	dialog->set_filename(prev_path);
	else if (is_absolute_path(filename))
	dialog->set_filename(filename);
	else
	dialog->set_filename(prev_path + ETL_DIRECTORY_SEPARATOR + filename);

	if(dialog->run() == GTK_RESPONSE_ACCEPT) {
		filename = dialog->get_filename();
		_preferences.set_value(preference, dirname(filename));
		delete dialog;
		return true;
	}

	delete dialog;
	return false;
}

bool
App::dialog_open_file_sketch(const std::string &title, std::string &filename, std::string preference)
{
	synfig::String prev_path;

	if(!_preferences.get_value(preference, prev_path))
		prev_path = Glib::get_home_dir();
	prev_path = absolute_path(prev_path);

	Gtk::FileChooserDialog *dialog = new Gtk::FileChooserDialog(*App::main_window,
				title, Gtk::FILE_CHOOSER_ACTION_OPEN);

	dialog->set_transient_for(*App::main_window);
	dialog->set_current_folder(prev_path);
	dialog->add_button(_("Cancel"), Gtk::RESPONSE_CANCEL)->set_image_from_icon_name("gtk-cancel", Gtk::ICON_SIZE_BUTTON);
	dialog->add_button(_("Load"),   Gtk::RESPONSE_ACCEPT)->set_image_from_icon_name("gtk-open", Gtk::ICON_SIZE_BUTTON);

	// show only Synfig sketch file (*.sketch)
	Glib::RefPtr<Gtk::FileFilter> filter_sketch = Gtk::FileFilter::create();
	filter_sketch->set_name(_("Synfig sketch files (*.sketch)"));
	filter_sketch->add_pattern("*.sketch");
	dialog->add_filter(filter_sketch);

	if (filename.empty())
	dialog->set_filename(prev_path);
	else if (is_absolute_path(filename))
	dialog->set_filename(filename);
	else
	dialog->set_filename(prev_path + ETL_DIRECTORY_SEPARATOR + filename);

	if(dialog->run() == GTK_RESPONSE_ACCEPT) {
		filename = dialog->get_filename();
		_preferences.set_value(preference, dirname(filename));
		delete dialog;
		return true;
	}

	delete dialog;
	return false;
}


bool
App::dialog_open_file_image(const std::string &title, std::string &filename, std::string preference)
{
	synfig::String prev_path;

	if(!_preferences.get_value(preference, prev_path))
		prev_path = Glib::get_home_dir();

	prev_path = absolute_path(prev_path);

	Gtk::FileChooserDialog *dialog = new Gtk::FileChooserDialog(*App::main_window,
				title, Gtk::FILE_CHOOSER_ACTION_OPEN);

	dialog->set_transient_for(*App::main_window);
	dialog->set_current_folder(prev_path);
	dialog->add_button(_("Cancel"), Gtk::RESPONSE_CANCEL)->set_image_from_icon_name("gtk-cancel", Gtk::ICON_SIZE_BUTTON);
	dialog->add_button(_("Load"),   Gtk::RESPONSE_ACCEPT)->set_image_from_icon_name("gtk-open", Gtk::ICON_SIZE_BUTTON);

	// show only images
	Glib::RefPtr<Gtk::FileFilter> filter_image = Gtk::FileFilter::create();
	filter_image->set_name(_("Images and sequence files (*.png, *.jpg, *.jpeg, *.bmp, *.svg, *.lst)"));
	filter_image->add_mime_type("image/png");
	filter_image->add_mime_type("image/jpeg");
	filter_image->add_mime_type("image/jpg");
	filter_image->add_mime_type("image/bmp");
	filter_image->add_mime_type("image/svg+xml");
	filter_image->add_pattern("*.png");
	filter_image->add_pattern("*.jpeg");
	filter_image->add_pattern("*.jpg");
	filter_image->add_pattern("*.bmp");
	filter_image->add_pattern("*.svg");
	filter_image->add_pattern("*.lst");
	dialog->add_filter(filter_image);

	// Any files
	Glib::RefPtr<Gtk::FileFilter> filter_any = Gtk::FileFilter::create();
	filter_any->set_name(_("Any files"));
	filter_any->add_pattern("*");
	dialog->add_filter(filter_any);

	if (filename.empty())
		dialog->set_filename(prev_path);
	else if (is_absolute_path(filename))
		dialog->set_filename(filename);
	else
		dialog->set_filename(prev_path + ETL_DIRECTORY_SEPARATOR + filename);

	if(dialog->run() == GTK_RESPONSE_ACCEPT) {
		filename = dialog->get_filename();
		_preferences.set_value(preference, dirname(filename));
		delete dialog;
		return true;
	}

	delete dialog;
	return false;
}


bool
App::dialog_open_file_audio(const std::string &title, std::string &filename, std::string preference)
{
	synfig::String prev_path;

	if(!_preferences.get_value(preference, prev_path))
		prev_path = Glib::get_home_dir();

	prev_path = absolute_path(prev_path);

	Gtk::FileChooserDialog *dialog = new Gtk::FileChooserDialog(*App::main_window,
				title, Gtk::FILE_CHOOSER_ACTION_OPEN);

	dialog->set_transient_for(*App::main_window);
	dialog->set_current_folder(prev_path);
	dialog->add_button(_("Cancel"), Gtk::RESPONSE_CANCEL)->set_image_from_icon_name("gtk-cancel", Gtk::ICON_SIZE_BUTTON);
	dialog->add_button(_("Load"),   Gtk::RESPONSE_ACCEPT)->set_image_from_icon_name("gtk-open", Gtk::ICON_SIZE_BUTTON);

	// Audio files
	Glib::RefPtr<Gtk::FileFilter> filter_audio = Gtk::FileFilter::create();
	filter_audio->set_name(_("Audio (*.ogg, *.mp3, *.wav)"));
	filter_audio->add_mime_type("audio/x-vorbis+ogg");
	filter_audio->add_mime_type("audio/mpeg");
	filter_audio->add_mime_type("audio/x-wav");
	filter_audio->add_pattern("*.ogg");
	filter_audio->add_pattern("*.mp3");
	filter_audio->add_pattern("*.wav");
	dialog->add_filter(filter_audio);

	// Any files
	Glib::RefPtr<Gtk::FileFilter> filter_any = Gtk::FileFilter::create();
	filter_any->set_name(_("Any files"));
	filter_any->add_pattern("*");
	dialog->add_filter(filter_any);

	if (filename.empty())
	dialog->set_filename(prev_path);
	else if (is_absolute_path(filename))
	dialog->set_filename(filename);
	else
	dialog->set_filename(prev_path + ETL_DIRECTORY_SEPARATOR + filename);

	if(dialog->run() == GTK_RESPONSE_ACCEPT) {
		filename = dialog->get_filename();
		_preferences.set_value(preference, dirname(filename));
		delete dialog;
		return true;
	}

	delete dialog;
	return false;
}

bool
App::dialog_open_file_image_sequence(const std::string &title, std::set<synfig::String> &filenames, std::string preference)
{
	synfig::String prev_path;

	if(!_preferences.get_value(preference, prev_path))
		prev_path = Glib::get_home_dir();

	prev_path = absolute_path(prev_path);

	Gtk::FileChooserDialog *dialog = new Gtk::FileChooserDialog(*App::main_window,
				title, Gtk::FILE_CHOOSER_ACTION_OPEN);

	dialog->set_transient_for(*App::main_window);
	dialog->set_current_folder(prev_path);
	dialog->set_select_multiple(true);
	dialog->add_button(_("Cancel"), Gtk::RESPONSE_CANCEL)->set_image_from_icon_name("gtk-cancel", Gtk::ICON_SIZE_BUTTON);
	dialog->add_button(_("Load"),   Gtk::RESPONSE_ACCEPT)->set_image_from_icon_name("gtk-open", Gtk::ICON_SIZE_BUTTON);

	// show only images
	Glib::RefPtr<Gtk::FileFilter> filter_image = Gtk::FileFilter::create();
	filter_image->set_name(_("Images files (*.png, *.jpg, *.jpeg, *.bmp)"));
	filter_image->add_mime_type("image/png");
	filter_image->add_mime_type("image/jpeg");
	filter_image->add_mime_type("image/jpg");
	filter_image->add_mime_type("image/bmp");
	filter_image->add_mime_type("image/svg+xml");
	filter_image->add_pattern("*.png");
	filter_image->add_pattern("*.jpeg");
	filter_image->add_pattern("*.jpg");
	filter_image->add_pattern("*.bmp");
	dialog->add_filter(filter_image);

	// Any files
	Glib::RefPtr<Gtk::FileFilter> filter_any = Gtk::FileFilter::create();
	filter_any->set_name(_("Any files"));
	filter_any->add_pattern("*");
	dialog->add_filter(filter_any);

	std::string filename = filenames.empty() ? std::string() : *filenames.begin();
	if (filename.empty())
		dialog->set_filename(prev_path);
	else if (is_absolute_path(filename))
		dialog->set_filename(filename);
	else
		dialog->set_filename(prev_path + ETL_DIRECTORY_SEPARATOR + filename);

	filenames.clear();
	if(dialog->run() == GTK_RESPONSE_ACCEPT) {
		std::vector<std::string> files = dialog->get_filenames();
		filenames.insert(files.begin(), files.end());
		_preferences.set_value(preference, dirname(dialog->get_filename()));
		delete dialog;
		return true;
	}

	delete dialog;
	return false;
}

void
on_open_dialog_with_history_selection_changed(Gtk::FileChooserDialog *dialog, Gtk::Button* history_button)
{
	// activate the history button when something is selected
	history_button->set_sensitive(!dialog->get_filename().empty());
}

bool
App::dialog_open_file_with_history_button(const std::string &title, std::string &filename, bool &show_history, std::string preference)
{
	// info("App::dialog_open_file('%s', '%s', '%s')", title.c_str(), filename.c_str(), preference.c_str());

// TODO: Win32 native dialog not ready yet
//#ifdef USE_WIN32_FILE_DIALOGS
#if 0
	static TCHAR szFilter[] = TEXT (_("All Files (*.*)\0*.*\0\0")) ;

	GdkWindow *gdkWinPtr=toolbox->get_window()->gobj();
	HINSTANCE hInstance=static_cast<HINSTANCE>(GetModuleHandle(NULL));
	HWND hWnd=static_cast<HWND>(GDK_WINDOW_HWND(gdkWinPtr));

	ofn.lStructSize=sizeof(OPENFILENAME);
	ofn.hwndOwner = hWnd;
	ofn.hInstance = hInstance;
	ofn.lpstrFilter = szFilter;
//	ofn.lpstrCustomFilter=NULL;
//	ofn.nMaxCustFilter=0;
//	ofn.nFilterIndex=0;
//	ofn.lpstrFile=NULL;
	ofn.nMaxFile=MAX_PATH;
//	ofn.lpstrFileTitle=NULL;
//	ofn.lpstrInitialDir=NULL;
//	ofn.lpstrTitle=NULL;
	ofn.Flags=OFN_HIDEREADONLY;
//	ofn.nFileOffset=0;
//	ofn.nFileExtension=0;
	ofn.lpstrDefExt=TEXT("sif");
//	ofn.lCustData = 0l;
	ofn.lpfnHook=NULL;
//	ofn.lpTemplateName=NULL;

	CHAR szFilename[MAX_PATH];
	CHAR szTitle[500];
	strcpy(szFilename,filename.c_str());
	strcpy(szTitle,title.c_str());

	ofn.lpstrFile=szFilename;
	ofn.lpstrFileTitle=szTitle;

	if(GetOpenFileName(&ofn))
	{
		filename=szFilename;
		return true;
	}
	return false;

#else   // not USE_WIN32_FILE_DIALOGS
	synfig::String prev_path;

	if(!_preferences.get_value(preference, prev_path))
		prev_path = Glib::get_home_dir();

	prev_path = absolute_path(prev_path);

	Gtk::FileChooserDialog *dialog = new Gtk::FileChooserDialog(*App::main_window,
				title, Gtk::FILE_CHOOSER_ACTION_OPEN);

		dialog->set_transient_for(*App::main_window);
	dialog->set_current_folder(prev_path);
	dialog->add_button(_("Cancel"), Gtk::RESPONSE_CANCEL)->set_image_from_icon_name("gtk-cancel", Gtk::ICON_SIZE_BUTTON);
	dialog->add_button(_("Open"),   Gtk::RESPONSE_ACCEPT)->set_image_from_icon_name("gtk-open", Gtk::ICON_SIZE_BUTTON);
	Gtk::Button* history_button = dialog->add_button(_("Open history"), RESPONSE_ACCEPT_WITH_HISTORY);
	// TODO: the Open history button should be file type sensitive one.
	dialog->set_response_sensitive(RESPONSE_ACCEPT_WITH_HISTORY, true);

	// File filters
	// Synfig Documents
	Glib::RefPtr<Gtk::FileFilter> filter_supported = Gtk::FileFilter::create();
	filter_supported->set_name(_("Synfig files (*.sif, *.sifz, *.sfg)"));
	filter_supported->add_mime_type("application/x-sif");
	filter_supported->add_pattern("*.sif");
	filter_supported->add_pattern("*.sifz");
	filter_supported->add_pattern("*.sfg");
	// Any files
	Glib::RefPtr<Gtk::FileFilter> filter_any = Gtk::FileFilter::create();
	filter_any->set_name(_("Any files"));
	filter_any->add_pattern("*");

	dialog->add_filter(filter_supported);
	dialog->add_filter(filter_any);

	if (filename.empty())
		dialog->set_filename(prev_path);
	else if (is_absolute_path(filename))
		dialog->set_filename(filename);
	else
		dialog->set_filename(prev_path + ETL_DIRECTORY_SEPARATOR + filename);

	// this ptr is't available to a static member fnc, connect to global function.
	sigc::connection connection_sc = dialog->signal_selection_changed().connect(sigc::bind(sigc::ptr_fun(on_open_dialog_with_history_selection_changed), dialog, history_button));

	int response = dialog->run();
	if (response == Gtk::RESPONSE_ACCEPT || response == RESPONSE_ACCEPT_WITH_HISTORY) {
		filename = dialog->get_filename();
		show_history = response == RESPONSE_ACCEPT_WITH_HISTORY;
		// info("Saving preference %s = '%s' in App::dialog_open_file()", preference.c_str(), dirname(filename).c_str());
		_preferences.set_value(preference, dirname(filename));
		delete dialog;
		return true;
	}

	connection_sc.disconnect();
	delete dialog;
	return false;
#endif   // not USE_WIN32_FILE_DIALOGS
}

bool
App::dialog_open_folder(const std::string &title, std::string &foldername, std::string preference, Gtk::Window& transientwind)
{
	synfig::String prev_path;
	synfigapp::Settings settings;
	if(settings.get_value(preference, prev_path))
		prev_path = ".";

	prev_path = absolute_path(prev_path);

	Gtk::FileChooserDialog *dialog = new Gtk::FileChooserDialog(*App::main_window,
			title, Gtk::FILE_CHOOSER_ACTION_SELECT_FOLDER);

	dialog->set_transient_for(transientwind);
	dialog->set_current_folder(prev_path);
	dialog->add_button(_("Cancel"), Gtk::RESPONSE_CANCEL)->set_image_from_icon_name("gtk-cancel", Gtk::ICON_SIZE_BUTTON);
	dialog->add_button(_("Open"),   Gtk::RESPONSE_ACCEPT)->set_image_from_icon_name("gtk-open", Gtk::ICON_SIZE_BUTTON);

	if(dialog->run() == GTK_RESPONSE_ACCEPT)
	{
		foldername = dialog->get_filename();
		delete dialog;
		return true;
	}
	delete dialog;
	return false;
}


bool
App::dialog_save_file(const std::string &title, std::string &filename, std::string preference)
{
	// info("App::dialog_save_file('%s', '%s', '%s')", title.c_str(), filename.c_str(), preference.c_str());

#if USE_WIN32_FILE_DIALOGS
	static TCHAR szFilter[] = TEXT (_("All Files (*.*)\0*.*\0\0")) ;

	GdkWindow *gdkWinPtr=toolbox->get_window()->gobj();
	HINSTANCE hInstance=static_cast<HINSTANCE>(GetModuleHandle(NULL));
	HWND hWnd=static_cast<HWND>(GDK_WINDOW_HWND(gdkWinPtr));

	ofn.lStructSize=sizeof(OPENFILENAME);
	ofn.hwndOwner = hWnd;
	ofn.hInstance = hInstance;
	ofn.lpstrFilter = szFilter;
//	ofn.lpstrCustomFilter=NULL;
//	ofn.nMaxCustFilter=0;
//	ofn.nFilterIndex=0;
//	ofn.lpstrFile=NULL;
	ofn.nMaxFile=MAX_PATH;
//	ofn.lpstrFileTitle=NULL;
//	ofn.lpstrInitialDir=NULL;
//	ofn.lpstrTitle=NULL;
	ofn.Flags=OFN_OVERWRITEPROMPT;
//	ofn.nFileOffset=0;
//	ofn.nFileExtension=0;
	ofn.lpstrDefExt=TEXT("sif");
//	ofn.lCustData = 0l;
	ofn.lpfnHook=NULL;
//	ofn.lpTemplateName=NULL;

	CHAR szFilename[MAX_PATH];
	CHAR szTitle[500];
	strcpy(szFilename,filename.c_str());
	strcpy(szTitle,title.c_str());

	ofn.lpstrFile=szFilename;
	ofn.lpstrFileTitle=szTitle;

	if(GetSaveFileName(&ofn))
	{
		filename=szFilename;
		_preferences.set_value(preference,dirname(filename));
		return true;
	}
	return false;
#else
	synfig::String prev_path;

	if(!_preferences.get_value(preference, prev_path))
		prev_path = Glib::get_home_dir();

	prev_path = absolute_path(prev_path);

	Gtk::FileChooserDialog *dialog = new Gtk::FileChooserDialog(*App::main_window, title, Gtk::FILE_CHOOSER_ACTION_SAVE);

	// file type filters
	Glib::RefPtr<Gtk::FileFilter> filter_sif = Gtk::FileFilter::create();
	filter_sif->set_name(_("Uncompressed Synfig file (*.sif)"));

	// sif share same mime type "application/x-sif" with sifz, so it will mixed .sif and .sifz files. Use only
	// pattern ("*.sif") for sif file format should be oK.
	//filter_sif->add_mime_type("application/x-sif");
	filter_sif->add_pattern("*.sif");

	Glib::RefPtr<Gtk::FileFilter> filter_sifz = Gtk::FileFilter::create();
	filter_sifz->set_name(_("Compressed Synfig file (*.sifz)"));
	filter_sifz->add_pattern("*.sifz");

	Glib::RefPtr<Gtk::FileFilter> filter_sfg = Gtk::FileFilter::create();
	filter_sfg->set_name(_("Container format file (*.sfg)"));
	filter_sfg->add_pattern("*.sfg");

	dialog->set_current_folder(prev_path);
	dialog->add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
	dialog->add_button(Gtk::Stock::SAVE,   Gtk::RESPONSE_ACCEPT);

	dialog->add_filter(filter_sifz);
	dialog->add_filter(filter_sif);
	dialog->add_filter(filter_sfg);

	Widget_Enum *file_type_enum = 0;
	if (preference == ANIMATION_DIR_PREFERENCE)
	{
		file_type_enum = manage(new Widget_Enum());
		file_type_enum->set_param_desc(ParamDesc().set_hint("enum")
				.add_enum_value(synfig::RELEASE_VERSION_CURRENT, "Current", _("Current"))
				.add_enum_value(synfig::RELEASE_VERSION_1_4_0, "1.4.0", "1.4.0")
				.add_enum_value(synfig::RELEASE_VERSION_1_2_0, "1.2.0", "1.2.0")
				.add_enum_value(synfig::RELEASE_VERSION_1_0_2, "1.0.2", "1.0.2")
				.add_enum_value(synfig::RELEASE_VERSION_1_0, "1.0", "1.0")
				.add_enum_value(synfig::RELEASE_VERSION_0_64_3, "0.64.3", "0.64.3")
				.add_enum_value(synfig::RELEASE_VERSION_0_64_2, "0.64.2", "0.64.2")
				.add_enum_value(synfig::RELEASE_VERSION_0_64_1, "0.64.1", "0.64.1")
				.add_enum_value(synfig::RELEASE_VERSION_0_64_0, "0.64.0", "0.64.0")
				.add_enum_value(synfig::RELEASE_VERSION_0_63_04, "0.63.05", "0.63.05")
				.add_enum_value(synfig::RELEASE_VERSION_0_63_04, "0.63.04", "0.63.04")
				.add_enum_value(synfig::RELEASE_VERSION_0_63_03, "0.63.03", "0.63.03")
				.add_enum_value(synfig::RELEASE_VERSION_0_63_02, "0.63.02", "0.63.02")
				.add_enum_value(synfig::RELEASE_VERSION_0_63_01, "0.63.01", "0.63.01")
				.add_enum_value(synfig::RELEASE_VERSION_0_63_00, "0.63.00", "0.63.00")
				.add_enum_value(synfig::RELEASE_VERSION_0_62_02, "0.62.02", "0.62.02")
				.add_enum_value(synfig::RELEASE_VERSION_0_62_01, "0.62.01", "0.62.01")
				.add_enum_value(synfig::RELEASE_VERSION_0_62_00, "0.62.00", "0.61.00")
				.add_enum_value(synfig::RELEASE_VERSION_0_61_09, "0.61.09", "0.61.09")
				.add_enum_value(synfig::RELEASE_VERSION_0_61_08, "0.61.08", "0.61.08")
				.add_enum_value(synfig::RELEASE_VERSION_0_61_07, "0.61.07", "0.61.07")
				.add_enum_value(synfig::RELEASE_VERSION_0_61_06, "0.61.06", strprintf("0.61.06 %s", _("and older"))));
		file_type_enum->set_value(RELEASE_VERSION_END-1); // default to the most recent version

		Gtk::Grid *grid = manage(new Gtk::Grid);
		grid->attach(*manage(new Gtk::Label(_("File Format Version: "))),0,0,1,1);
		grid->attach(*file_type_enum,1,0,1,1);
		grid->show_all();

		dialog->set_extra_widget(*grid);
	}

	if (filename.empty()) {
		dialog->set_filename(prev_path);

	}else{
		std::string full_path;
		if (is_absolute_path(filename))
			full_path = filename;
		else
			full_path = prev_path + ETL_DIRECTORY_SEPARATOR + filename;

		// select the file if it exists
		dialog->set_filename(full_path);

		// if the file doesn't exist, put its name into the filename box
		struct stat s;
		if(stat(full_path.c_str(),&s) == -1 && errno == ENOENT)
			dialog->set_current_name(basename(filename));

	}
	// set file filter according to previous file format
	if (filename_extension(filename) == ".sif" ) dialog->set_filter(filter_sif);
	if (filename_extension(filename)== ".sifz" ) dialog->set_filter(filter_sifz);
	if (filename_extension(filename) == ".sfg" ) dialog->set_filter(filter_sfg);

	// set focus to the file name entry(box) of dialog instead to avoid the name
	// we are going to save changes while changing file filter each time.
	dialog->set_current_name(basename(filename));

	if(dialog->run() == GTK_RESPONSE_ACCEPT) {

		if (preference == ANIMATION_DIR_PREFERENCE)
			set_file_version(synfig::ReleaseVersion(file_type_enum->get_value()));

		// add file extension according to file filter selected by user if he doesn't type file extension in
		// file name entry. Right now it still detects file extension from file name entry, if extension is one
		// of .sif, sifz and sfg, it will be used otherwise, saved file format will depend on selected file filter.
		// It should be improved by changing file extension according to set file type filter, such as:
		// dialog->property_filter().signal_changed().connect(sigc::mem_fun(*this, &App::on_save_dialog_filter_changed));
		filename = dialog->get_filename();

		if (filename_extension(filename) != ".sif" &&
			filename_extension(filename) != ".sifz" &&
			filename_extension(filename) != ".sfg")
		{
			if (dialog->get_filter() == filter_sif)
				filename = dialog->get_filename() + ".sif";
			else if (dialog->get_filter() == filter_sifz)
				filename = dialog->get_filename() + ".sifz";
			else if (dialog->get_filter() == filter_sfg)
				filename = dialog->get_filename() + ".sfg";
		}

	// info("Saving preference %s = '%s' in App::dialog_save_file()", preference.c_str(), dirname(filename).c_str());
	_preferences.set_value(preference, dirname(filename));
	delete dialog;
	return true;
    }

    delete dialog;
    return false;
#endif
}


bool
App::dialog_save_file_spal(const std::string &title, std::string &filename, std::string preference)
{
	synfig::String prev_path;
	if(!_preferences.get_value(preference, prev_path))
		prev_path=Glib::get_home_dir();
	prev_path = absolute_path(prev_path);

	Gtk::FileChooserDialog *dialog = new Gtk::FileChooserDialog(*App::main_window, title, Gtk::FILE_CHOOSER_ACTION_SAVE);

	// file type filters
	Glib::RefPtr<Gtk::FileFilter> filter_spal = Gtk::FileFilter::create();
	filter_spal->set_name(_("Synfig palette files (*.spal)"));
	filter_spal->add_pattern("*.spal");

	dialog->set_current_folder(prev_path);
	dialog->add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
	dialog->add_button(Gtk::Stock::SAVE,   Gtk::RESPONSE_ACCEPT);

	dialog->add_filter(filter_spal);

	if (filename.empty()) {
		dialog->set_filename(prev_path);

	}else{
		std::string full_path;
		if (is_absolute_path(filename))
			full_path = filename;
		else
			full_path = prev_path + ETL_DIRECTORY_SEPARATOR + filename;

		// select the file if it exists
		dialog->set_filename(full_path);

		// if the file doesn't exist, put its name into the filename box
		struct stat s;
		if(stat(full_path.c_str(),&s) == -1 && errno == ENOENT)
			dialog->set_current_name(basename(filename));

	}

	dialog->set_filter(filter_spal);

	// set focus to the file name entry(box) of dialog instead to avoid the name
	// we are going to save changes while changing file filter each time.
	dialog->set_current_name(basename(filename));

	if(dialog->run() == GTK_RESPONSE_ACCEPT) {

		// add file extension according to file filter selected by user
		filename = dialog->get_filename();
		if (filename_extension(filename) != ".spal")
			filename = dialog->get_filename() + ".spal";

	delete dialog;
	return true;
	}

	delete dialog;
	return false;
}

bool
App::dialog_save_file_sketch(const std::string &title, std::string &filename, std::string preference)
{
	synfig::String prev_path;
	if(!_preferences.get_value(preference, prev_path))
		prev_path=Glib::get_home_dir();
	prev_path = absolute_path(prev_path);

	Gtk::FileChooserDialog *dialog = new Gtk::FileChooserDialog(*App::main_window, title, Gtk::FILE_CHOOSER_ACTION_SAVE);

	// file type filters
	Glib::RefPtr<Gtk::FileFilter> filter_sketch = Gtk::FileFilter::create();
	filter_sketch->set_name(_("Synfig sketch files (*.sketch)"));
	filter_sketch->add_pattern("*.sketch");

	dialog->set_current_folder(prev_path);
	dialog->add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
	dialog->add_button(Gtk::Stock::SAVE,   Gtk::RESPONSE_ACCEPT);

	dialog->add_filter(filter_sketch);

	if (filename.empty()) {
		dialog->set_filename(prev_path);

	}else{
		std::string full_path;
		if (is_absolute_path(filename))
			full_path = filename;
		else
			full_path = prev_path + ETL_DIRECTORY_SEPARATOR + filename;

		// select the file if it exists
		dialog->set_filename(full_path);

		// if the file doesn't exist, put its name into the filename box
		struct stat s;
		if(stat(full_path.c_str(),&s) == -1 && errno == ENOENT)
			dialog->set_current_name(basename(filename));

	}

	dialog->set_filter(filter_sketch);

	// set focus to the file name entry(box) of dialog instead to avoid the name
	// we are going to save changes while changing file filter each time.
	dialog->set_current_name(basename(filename));

	if(dialog->run() == GTK_RESPONSE_ACCEPT) {

		// add file extension according to file filter selected by user
		filename = dialog->get_filename();
		if (filename_extension(filename) != ".sketch")
			filename = dialog->get_filename() + ".sketch";

	delete dialog;
	return true;
	}

	delete dialog;
	return false;
}


bool
App::dialog_save_file_render(const std::string &title, std::string &filename, std::string preference)
{
	synfig::String prev_path;
	if(!_preferences.get_value(preference, prev_path))
		prev_path=Glib::get_home_dir();
	prev_path = absolute_path(prev_path);

	Gtk::FileChooserDialog *dialog = new Gtk::FileChooserDialog(*App::main_window, title, Gtk::FILE_CHOOSER_ACTION_SAVE);

	dialog->set_current_folder(prev_path);
	dialog->add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
	dialog->add_button(Gtk::Stock::OK,   Gtk::RESPONSE_ACCEPT);

	if (filename.empty()) {
		dialog->set_filename(prev_path);

	}else{
		std::string full_path;
		if (is_absolute_path(filename))
			full_path = filename;
		else
			full_path = prev_path + ETL_DIRECTORY_SEPARATOR + filename;

		// select the file if it exists
		dialog->set_filename(full_path);

		// if the file doesn't exist, put its name into the filename box
		struct stat s;
		if(stat(full_path.c_str(),&s) == -1 && errno == ENOENT)
			dialog->set_current_name(basename(filename));

	}

	if(dialog->run() == GTK_RESPONSE_ACCEPT)
	{
		filename = dialog->get_filename();

		delete dialog;
		return true;
	}

	delete dialog;
	return false;
}


bool
App::dialog_select_list_item(const std::string &title, const std::string &message, const std::list<std::string> &list, int &item_index)
{
	Gtk::Dialog dialog(title, *App::main_window, true);

	Gtk::Label* label = manage (new Gtk::Label(message, 0, 0));
	label->set_line_wrap();

	class ModelColumns : public Gtk::TreeModel::ColumnRecord
	{
	public:
		Gtk::TreeModelColumn<int> column_index;
		Gtk::TreeModelColumn<Glib::ustring> column_main;
		ModelColumns() { add(column_index); add(column_main); }
	} model_columns;

	Glib::RefPtr<Gtk::ListStore> list_store = Gtk::ListStore::create(model_columns);

	int k = 0;
	for(std::list<std::string>::const_iterator i = list.begin(); i != list.end(); i++) {
		Gtk::ListStore::iterator j = list_store->append();
		j->set_value(model_columns.column_index, k++);
		j->set_value(model_columns.column_main, Glib::ustring(*i));
	}

	Gtk::TreeView * tree = manage (new Gtk::TreeView(list_store));
	Gtk::TreeViewColumn column_index("", model_columns.column_index);
	Gtk::TreeViewColumn column_main("", model_columns.column_main);
	column_index.set_visible(false);
	tree->append_column(column_index);
	tree->append_column(column_main);
	tree->set_hexpand(TRUE);
	tree->set_halign(Gtk::ALIGN_FILL);
	tree->set_vexpand(TRUE);
	tree->set_valign(Gtk::ALIGN_FILL);

	Gtk::TreeModel::Row selected_row = list_store->children()[item_index];
	if (selected_row)
		tree->get_selection()->select(selected_row);

	Gtk::Grid* grid = manage(new Gtk::Grid());
	grid->attach(*label, 0, 0, 1, 1);
	grid->attach(*tree, 0, 1, 1, 2);

	dialog.get_content_area()->pack_start(*grid);
	dialog.add_button(Gtk::Stock::CANCEL, Gtk::RESPONSE_CANCEL);
	dialog.add_button(Gtk::Stock::OPEN,   Gtk::RESPONSE_ACCEPT);
	dialog.set_default_size(300, 450);
	dialog.show_all();

	if (dialog.run() == Gtk::RESPONSE_ACCEPT) {
		item_index = tree->get_selection()->get_selected()->get_value(model_columns.column_index);
		return true;
	}

	return false;
}


void
App::dialog_not_implemented()
{
	Gtk::MessageDialog dialog(*App::main_window, _("Feature not available"), false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_CLOSE, true);
	dialog.set_secondary_text(_("Sorry, this feature has not yet been implemented."));
	dialog.run();
}


// message dialog with 1 button.
void
App::dialog_message_1b(
	const std::string &type,
		//INFO:		Gtk::MESSAGE_INFO - Informational message.
		//WARNING:	Gtk::MESSAGE_WARNING - Non-fatal warning message.
		//QUESTION:	Gtk::MESSAGE_QUESTION - Question requiring a choice.
		//ERROR:	Gtk::MESSAGE_ERROR - Fatal error message.
		//OTHER:	Gtk::MESSAGE_OTHER - None of the above, doesn’t get an icon.
	const std::string &message,
	const std::string &details,
	const std::string &button1,
	const std::string &long_details)
{
	Gtk::MessageType _type = Gtk::MESSAGE_OTHER; // default
	if (type == "INFO")
		_type = Gtk::MESSAGE_INFO;
	if (type == "WARNING")
		_type = Gtk::MESSAGE_WARNING;
	if (type == "QUESTION")
		_type = Gtk::MESSAGE_QUESTION;
	if (type == "ERROR")
		_type = Gtk::MESSAGE_ERROR;
	if (type == "OTHER")
		_type = Gtk::MESSAGE_OTHER;

	Gtk::MessageDialog dialog(*App::main_window, message, false, _type, Gtk::BUTTONS_NONE, true);

	if (details != "details")
		dialog.set_secondary_text(details);

	Gtk::Label label;
	Gtk::ScrolledWindow sw;
	if (long_details != "long_details")
	{
		label.set_text(long_details);
		label.show();
		sw.add(label);
		sw.set_size_request(400,300);
		sw.show();
		dialog.get_content_area()->pack_end(sw);
		dialog.set_resizable(true);
	}

	dialog.add_button(button1, 0);

	dialog.run();
}


// message dialog with 2 buttons.
bool
App::dialog_message_2b(const std::string &message,
	const std::string &details,
	const Gtk::MessageType &type,
		//MESSAGE_INFO - Informational message.
		//MESSAGE_WARNING - Non-fatal warning message.
		//MESSAGE_QUESTION - Question requiring a choice.
		//MESSAGE_ERROR - Fatal error message.
		//MESSAGE_OTHER - None of the above, doesn’t get an icon.
	const std::string &button1,
	const std::string &button2)
{
	Gtk::MessageDialog dialog(*App::main_window, message, false, type, Gtk::BUTTONS_NONE, true);
	dialog.set_secondary_text(details);
	dialog.add_button(button1, 0);
	dialog.add_button(button2, 1);

	return	dialog.run();
}


// message dialog with 3 buttons.
int
App::dialog_message_3b(const std::string &message,
	const std::string &detials,
	const Gtk::MessageType &type,
		//MESSAGE_INFO - Informational message.
		//MESSAGE_WARNING - Non-fatal warning message.
		//MESSAGE_QUESTION - Question requiring a choice.
		//MESSAGE_ERROR - Fatal error message.
		//MESSAGE_OTHER - None of the above, doesn’t get an icon.
	const std::string &button1,
	const std::string &button2,
	const std::string &button3)
{
	Gtk::MessageDialog dialog(*App::main_window, message, false, type, Gtk::BUTTONS_NONE, true);
	dialog.set_secondary_text(detials);
	dialog.add_button(button1, 0);
	dialog.add_button(button2, 1);
	dialog.add_button(button3, 2);

	return dialog.run();
}

static bool
try_open_img_external(const std::string &uri)
{
	std::string new_uri=uri;
	std::string s = "file://";
	std::string::size_type i = new_uri.find(s);
	if (i != std::string::npos)
	{
		new_uri.erase(i, s.length());
	}
   	size_t start_pos = 0;
	std::string to = " ";
	std::string from = "%20";
    while((start_pos = new_uri.find(from, start_pos)) != std::string::npos) 
	{
        new_uri.replace(start_pos, from.length(), to);
        start_pos += to.length(); // In case 'to' contains 'from', like replacing 'x' with 'yx'
    }
	new_uri = "\"" + new_uri + "\"";
	if(App::image_editor_path!="")
	{
		#ifdef WIN32
			char buffer[512];
    		::snprintf(buffer, sizeof(buffer), "%s %s",App::image_editor_path.c_str(), new_uri.c_str());
    		Glib::spawn_command_line_async(buffer);
		#elif defined(__APPLE__)
    		char buffer[512];
    		::snprintf(buffer, sizeof(buffer), "open -a %s %s", App::image_editor_path.c_str(), new_uri.c_str());
    		Glib::spawn_command_line_async(buffer);
		#else
    		char buffer[512];
    		::snprintf(buffer, sizeof(buffer), "%s %s",App::image_editor_path.c_str(), new_uri.c_str());
			Glib::spawn_command_line_async(buffer);
		#endif
		return true;

	}
	else
	{
		return false;
	}
	
	
}
static bool
try_open_uri(const std::string &uri)
{
#if GTK_CHECK_VERSION(3, 22, 0)
	return gtk_show_uri_on_window(
		App::main_window ? App::main_window->gobj() : NULL,
		uri.c_str(), GDK_CURRENT_TIME, NULL );
#else
	return gtk_show_uri(NULL, uri.c_str(), GDK_CURRENT_TIME, NULL);
#endif
}


void
App::dialog_help()
{
	if (!try_open_uri("https://wiki.synfig.org/Category:Manual"))
	{
		Gtk::MessageDialog dialog(*App::main_window, _("Documentation"), false, Gtk::MESSAGE_INFO, Gtk::BUTTONS_CLOSE, true);
		dialog.set_secondary_text(_("Documentation for Synfig Studio is available on the website:\n\nhttps://wiki.synfig.org/Category:Manual"));
		dialog.set_title(_("Help"));
		dialog.run();
	}
}
void App::open_img_in_external(const std::string &uri)
{
	synfig::info("Opening with external tool: " + uri);
	if(!try_open_img_external(uri))
	{
		Gtk::MessageDialog dialog(*App::main_window, _("Make sure Preferred editing tool was set in \n Edit->Preferences->Editing:"), false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_CLOSE, true);
		dialog.set_secondary_text(uri);
		dialog.set_title(_("Error"));
		dialog.run();
	}

}
unordered_map<std::string, int> configmap({ { "threshold", 8 },{ "accuracy", 9 },{ "despeckling", 5 },{ "maxthickness", 200 }});
void App::open_vectorizerpopup(const etl::handle<synfig::Layer_Bitmap> my_layer_bitmap, const etl::handle<synfig::Layer> reference_layer)
{
	String desc = my_layer_bitmap->get_description();
	synfig::info("Opening Vectorizerpopup for :"+desc);
	App::vectorizerpopup = new studio::VectorizerSettings(*App::main_window,my_layer_bitmap,selected_instance,configmap,reference_layer);
	App::vectorizerpopup->show();

}
void App::open_uri(const std::string &uri)
{
	synfig::info("Opening URI: " + uri);
	if(!try_open_uri(uri))
	{
		Gtk::MessageDialog dialog(*App::main_window, _("No compatible application was found. Please load open file manually:"), false, Gtk::MESSAGE_ERROR, Gtk::BUTTONS_CLOSE, true);
		dialog.set_secondary_text(uri);
		dialog.set_title(_("Error"));
		dialog.run();
	}
}

bool
App::dialog_entry(const std::string &action, const std::string &content, std::string &text, const std::string &button1, const std::string &button2)
{
	Gtk::MessageDialog dialog(
		*App::main_window,
		action,
		false,
		Gtk::MESSAGE_INFO,
		Gtk::BUTTONS_NONE,
		true
	);

	// TODO Group All HARDCODED user interface information somewhere "global"
	// TODO All UI info from .rc
#define DIALOG_ENTRY_MARGIN 18
	Gtk::Label* label = manage (new Gtk::Label(content));
	label->set_margin_start(DIALOG_ENTRY_MARGIN);

	Gtk::Entry* entry = manage(new Gtk::Entry());
	entry->set_text(text);
	entry->set_margin_end(DIALOG_ENTRY_MARGIN);
	entry->set_activates_default(true);
	entry->set_hexpand(TRUE);
	entry->set_halign(Gtk::ALIGN_FILL);
#undef DIALOG_ENTRY_MARGIN

	Gtk::Grid* grid = manage (new Gtk::Grid());
	grid->add(*label);
	grid->add(*entry);

	grid->show_all();

	dialog.get_content_area()->pack_start(*grid);
	dialog.add_button(button1, Gtk::RESPONSE_CANCEL);
	dialog.add_button(button2, Gtk::RESPONSE_OK);

	dialog.set_default_response(Gtk::RESPONSE_OK);
	entry->signal_activate().connect(sigc::bind(sigc::mem_fun(dialog,&Gtk::Dialog::response),Gtk::RESPONSE_OK));
	dialog.show();

	if(dialog.run()!=Gtk::RESPONSE_OK)
		return false;

	text = entry->get_text();

	return true;
}


bool
App::dialog_paragraph(const std::string &title, const std::string &message,std::string &text)
{
	Gtk::Dialog dialog(
		title,			// Title
		*App::main_window,	// Parent
		true			// Modal
	);

	Gtk::Label* label = manage(new Gtk::Label(message));
	label->show();
	dialog.get_content_area()->pack_start(*label);

	Glib::RefPtr<Gtk::TextBuffer> text_buffer(Gtk::TextBuffer::create());
	text_buffer->set_text(text);
	Gtk::TextView text_view(text_buffer);
	text_view.show();

	dialog.get_content_area()->pack_start(text_view);

	dialog.add_button(_("OK"),   Gtk::RESPONSE_OK)->set_image_from_icon_name("gtk-ok", Gtk::ICON_SIZE_BUTTON);
	dialog.add_button(_("Cancel"), Gtk::RESPONSE_CANCEL)->set_image_from_icon_name("gtk-cancel", Gtk::ICON_SIZE_BUTTON);
	dialog.set_default_response(Gtk::RESPONSE_OK);

	//text_entry.signal_activate().connect(sigc::bind(sigc::mem_fun(dialog,&Gtk::Dialog::response),Gtk::RESPONSE_OK));
	dialog.show();

	if(dialog.run()!=Gtk::RESPONSE_OK)
		return false;

	text=text_buffer->get_text();

	return true;
}

std::string
App::get_temporary_directory()
{
	return synfigapp::Main::get_user_app_directory() + ETL_DIRECTORY_SEPARATOR + "tmp";
}

synfig::FileSystemTemporary::Handle
App::wrap_into_temporary_filesystem(
	synfig::FileSystem::Handle canvas_file_system,
	std::string filename,
	std::string as,
	synfig::FileContainerZip::file_size_t truncate_storage_size )
{
	FileSystemTemporary::Handle temporary_file_system = new FileSystemTemporary("instance", get_temporary_directory(), canvas_file_system);
	temporary_file_system->set_meta("filename", filename);
	temporary_file_system->set_meta("as", as);
	temporary_file_system->set_meta("truncate", etl::strprintf("%lld", truncate_storage_size));
	return temporary_file_system;
}

bool
App::open(std::string filename, /* std::string as, */ synfig::FileContainerZip::file_size_t truncate_storage_size)
{
#ifdef _WIN32
    size_t buf_size = PATH_MAX - 1;
    char* long_name = (char*)malloc(buf_size);
    long_name[0] = '\0';
    if(GetLongPathName(filename.c_str(),long_name,sizeof(long_name)));
    // when called from autorecover.cpp, filename doesn't exist, and so long_name is empty
    // don't use it if that's the case
    if (long_name[0] != '\0')
        filename=String(long_name);
    free(long_name);
#endif

	try
	{
		OneMoment one_moment;
		String errors, warnings;

		// try open container
		FileSystem::Handle container = CanvasFileNaming::make_filesystem_container(filename, truncate_storage_size);
		if (!container)
			throw (String)strprintf(_("Unable to open container \"%s\"\n\n"),filename.c_str());

		// make canvas file system
		FileSystem::Handle canvas_file_system = CanvasFileNaming::make_filesystem(container);

		// wrap into temporary file system
		canvas_file_system = wrap_into_temporary_filesystem(canvas_file_system, filename, filename, truncate_storage_size);

		// file to open inside canvas file-system
		String canvas_filename = CanvasFileNaming::project_file(filename);

		etl::handle<synfig::Canvas> canvas = open_canvas_as(canvas_file_system ->get_identifier(canvas_filename), filename, errors, warnings);
		if(canvas && get_instance(canvas))
		{
			get_instance(canvas)->find_canvas_view(canvas)->present();
			info("%s is already open", canvas_filename.c_str());
			// throw (String)strprintf(_("\"%s\" appears to already be open!"),filename.c_str());
		}
		else
		{
			if(!canvas)
				throw (String)strprintf(_("Unable to load \"%s\":\n\n"),filename.c_str()) + errors;

			if (warnings != "")
				dialog_message_1b(
					"WARNING",
					_("Warning"),
					"details",
					_("Close"),
					warnings);

			if (filename.find(custom_filename_prefix.c_str()) != 0)
				add_recent_file(filename);

			handle<Instance> instance(Instance::create(canvas, container));

			if(!instance)
				throw (String)strprintf(_("Unable to create instance for \"%s\""),filename.c_str());

			one_moment.hide();

			if(instance->is_updated() && App::dialog_message_2b(
				_("Newer version of this file available on the CVS repository!"),
				_("repository. Would you like to update now? (It would probably be a good idea)"),
				Gtk::MESSAGE_QUESTION,
				_("Cancel"),
				_("Update Anyway"))
			)
				instance->dialog_cvs_update();
		}
	}
	catch(String &x)
	{
		dialog_message_1b(
			"ERROR",
			x,
			"details",
			_("Close"));

		return false;
	}
	catch(runtime_error &x)
	{
		dialog_message_1b(
			"ERROR",
			x.what(),
			"details",
			_("Close"));

		return false;
	}
	catch(...)
	{
		dialog_message_1b(
			"ERROR",
			_("Uncaught error on file open (BUG)"),
			"details",
			_("Close"));

		return false;
	}

	return true;
}

// this is called from autorecover.cpp
bool
App::open_from_temporary_filesystem(std::string temporary_filename)
{
	try
	{
		OneMoment one_moment;
		String errors, warnings;

		// try open temporary container
		FileSystemTemporary::Handle file_system_temporary(new FileSystemTemporary(""));
		if (!file_system_temporary->open_temporary(temporary_filename))
			throw (String)strprintf(_("Unable to open temporary container \"%s\"\n\n"), temporary_filename.c_str());

		// get original filename
		String filename = file_system_temporary->get_meta("filename");
		String as = file_system_temporary->get_meta("as");
		String truncate = file_system_temporary->get_meta("truncate");
		if (filename.empty() || as.empty() || truncate.empty())
			throw (String)strprintf(_("Original filename was not set in temporary container \"%s\"\n\n"), temporary_filename.c_str());
#ifdef __APPLE__
		FileContainerZip::file_size_t truncate_storage_size = atoll(truncate.c_str());
#else
		FileContainerZip::file_size_t truncate_storage_size = stoll(truncate);
#endif

		// make canvas file-system
		FileSystem::Handle canvas_container = CanvasFileNaming::make_filesystem_container(filename, truncate_storage_size);
		FileSystem::Handle canvas_file_system = CanvasFileNaming::make_filesystem(canvas_container);

		// wrap into temporary
		file_system_temporary->set_sub_file_system(canvas_file_system);
		canvas_file_system = file_system_temporary;

		// file to open inside canvas file system
		String canvas_filename = CanvasFileNaming::project_file(canvas_file_system);

		etl::handle<synfig::Canvas> canvas(open_canvas_as(canvas_file_system->get_identifier(canvas_filename), as, errors, warnings));
		if(canvas && get_instance(canvas))
		{
			get_instance(canvas)->find_canvas_view(canvas)->present();
			info("%s is already open", as.c_str());
			// throw (String)strprintf(_("\"%s\" appears to already be open!"),filename.c_str());
		}
		else
		{
			if(!canvas)
				throw (String)strprintf(_("Unable to load \"%s\":\n\n"), temporary_filename.c_str()) + errors;

			if (warnings != "")
				dialog_message_1b(
						"WARNING",
						strprintf("%s:\n\n%s", _("Warning"), warnings.c_str()),
						"details",
						_("Close"));

			if (as.find(custom_filename_prefix.c_str()) != 0)
				add_recent_file(as);

			handle<Instance> instance(Instance::create(canvas, canvas_container));

			if(!instance)
				throw (String)strprintf(_("Unable to create instance for \"%s\""), temporary_filename.c_str());

			one_moment.hide();

			if(instance->is_updated() && App::dialog_message_2b(
				_("Newer version of this file available on the CVS repository!"),
				_("Would you like to update now (It would probably be a good idea)"),
				Gtk::MESSAGE_QUESTION,
				_("Cancel"),
				_("Update Anyway"))
			)
				instance->dialog_cvs_update();

			// This file isn't saved! mark it as such
			instance->inc_action_count();
		}
	}
	catch(String &x)
	{
		dialog_message_1b(
				"ERROR",
				 x,
				"details",
				_("Close"));

		return false;
	}
	catch(runtime_error &x)
	{
		dialog_message_1b(
				"ERROR",
				x.what(),
				"details",
				_("Close"));

		return false;
	}
	catch(...)
	{
		dialog_message_1b(
				"ERROR",
				_("Uncaught error on file open (BUG)"),
				"details",
				_("Close"));

		return false;
	}

	return true;
}


void
App::new_instance()
{
	handle<synfig::Canvas> canvas=synfig::Canvas::create();

	String filename(strprintf("%s%d", App::custom_filename_prefix.c_str(), Instance::get_count()+1));
	canvas->set_name(filename);

	canvas->rend_desc().set_frame_rate(preferred_fps);
	canvas->rend_desc().set_time_start(0.0);
	canvas->rend_desc().set_time_end(5.0);
	canvas->rend_desc().set_x_res(DPI2DPM(72.0f));
	canvas->rend_desc().set_y_res(DPI2DPM(72.0f));
	// The top left and bottom right positions are expressed in units
	// Original convention is that 1 unit = 60 pixels
	canvas->rend_desc().set_tl(Vector(-(preferred_x_size/60.0)/2.0,  (preferred_y_size/60.0)/2.0));
	canvas->rend_desc().set_br(Vector( (preferred_x_size/60.0)/2.0, -(preferred_y_size/60.0)/2.0));
	canvas->rend_desc().set_w(preferred_x_size);
	canvas->rend_desc().set_h(preferred_y_size);
	canvas->rend_desc().set_antialias(1);
	canvas->rend_desc().set_flags(RendDesc::PX_ASPECT|RendDesc::IM_SPAN);
	canvas->set_file_name(filename);
	canvas->keyframe_list().add(synfig::Keyframe());

	FileSystem::Handle container = new FileSystemEmpty();
	FileSystem::Handle file_system = CanvasFileNaming::make_filesystem(container);
	file_system = wrap_into_temporary_filesystem(file_system, filename, filename);

	// file name inside canvas file-system
	String canvas_filename = CanvasFileNaming::project_file(filename);
	canvas->set_identifier(file_system->get_identifier(canvas_filename));

	handle<Instance> instance = Instance::create(canvas, container);

    if (App::default_background_layer_type == "solid_color")
    {
		//Create a SolidColor layer
		synfig::Layer::Handle layer(instance->find_canvas_interface(canvas)->add_layer_to("SolidColor",
			                        canvas,
			                        0)); //target_depth

		//Rename it as Background
		synfigapp::Action::Handle action_LayerSetDesc(synfigapp::Action::create("LayerSetDesc"));
		if (action_LayerSetDesc) {
			action_LayerSetDesc->set_param("canvas",           canvas);
			action_LayerSetDesc->set_param("canvas_interface", instance->find_canvas_interface(canvas));
			action_LayerSetDesc->set_param("layer",            layer);
			action_LayerSetDesc->set_param("new_description",  "Background");
			App::get_selected_canvas_view()->canvas_interface()->get_instance()->perform_action(action_LayerSetDesc);
		}

		//Change its color to the selected one
		synfigapp::Action::Handle action_LayerParamSet(synfigapp::Action::create("LayerParamSet"));
		if (action_LayerParamSet) {
			action_LayerParamSet->set_param("canvas",           canvas);
			action_LayerParamSet->set_param("canvas_interface", instance->find_canvas_interface(canvas));
			action_LayerParamSet->set_param("layer",            layer);
			action_LayerParamSet->set_param("param",            "color");
			action_LayerParamSet->set_param("new_value",        ValueBase(App::default_background_layer_color));
			App::get_selected_canvas_view()->canvas_interface()->get_instance()->perform_action(action_LayerParamSet);
		}

	}
	else if (App::default_background_layer_type == "image")
	{
		String errors, warnings;
		instance->find_canvas_interface(canvas)->import(App::default_background_layer_image,
		                                                errors,
		                                                warnings,
		                                                App::resize_imported_images);

		synfig::Layer::Handle layer = instance->find_canvas_interface(canvas)->get_selection_manager()->get_selected_layer();

		//Rename it as Background
		synfigapp::Action::Handle action_LayerSetDesc(synfigapp::Action::create("LayerSetDesc"));
		if (action_LayerSetDesc) {
			action_LayerSetDesc->set_param("canvas",           canvas);
			action_LayerSetDesc->set_param("canvas_interface", instance->find_canvas_interface(canvas));
			action_LayerSetDesc->set_param("layer",            layer);
			action_LayerSetDesc->set_param("new_description",  "Background");
			App::get_selected_canvas_view()->canvas_interface()->get_instance()->perform_action(action_LayerSetDesc);
		}

		if (warnings != "")
			App::dialog_message_1b(
				"WARNING",
				etl::strprintf("%s:\n\n%s", _("Warning"), warnings.c_str()),
				"details",
				_("Close"));
		if (errors != "")
			App::dialog_message_1b(
				"ERROR",
				etl::strprintf("%s:\n\n%s", _("Error"), errors.c_str()),
				"details",
				_("Close"));
	}

	if (getenv("SYNFIG_AUTO_ADD_SKELETON_LAYER"))
		instance->find_canvas_view(canvas)->add_layer("skeleton");

	if (getenv("SYNFIG_AUTO_ADD_MOTIONBLUR_LAYER"))
		instance->find_canvas_view(canvas)->add_layer("MotionBlur");

	if (getenv("SYNFIG_ENABLE_NEW_CANVAS_EDIT_PROPERTIES"))
		instance->find_canvas_view(canvas)->canvas_properties.present();
}

void
App::dialog_open(string filename)
{
	if (filename.empty() && selected_instance)
		filename = selected_instance->get_file_name();
	if (filename.empty())
		filename="*.sif";

	bool show_history = false;
	while(dialog_open_file_with_history_button(_("Please select a file"), filename, show_history, 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())
			continue;

		FileContainerZip::file_size_t truncate_storage_size = 0;

		// TODO: ".sfg" literal
		if (show_history && filename_extension(filename) == ".sfg")
		{
			// read history
			std::list<FileContainerZip::HistoryRecord> history
				= FileContainerZip::read_history(filename);

			// build list of history entries for dialog (descending)
			std::list<std::string> list;
			int index = 0;
			for(std::list<FileContainerZip::HistoryRecord>::const_iterator i = history.begin(); i != history.end(); i++)
				list.push_front(strprintf("%s%d", _("History entry #"), ++index));

			// show dialog
			index=0;
			if (!dialog_select_list_item(_("Please select a file"), _("Select one of previous versions of file"), list, index))
				continue;

			// find selected entry in list (descending)
			for(std::list<FileContainerZip::HistoryRecord>::const_reverse_iterator i = history.rbegin(); i != history.rend(); i++)
				if (0 == index--)
					truncate_storage_size = i->storage_size;
		}

		if(open(filename,truncate_storage_size))
			break;

		get_ui_interface()->error(_("Unable to open file"));
	}
}

void
App::set_selected_instance(etl::loose_handle<Instance> instance)
{
	if (selected_instance == instance)
		return;
	
	if (get_selected_canvas_view() && get_selected_canvas_view()->get_instance() != instance) {
		if (instance) {
			instance->focus( instance->get_canvas() );
		} else {
			set_selected_canvas_view(0);
		}
	} else {
		selected_instance = instance;
		signal_instance_selected()(selected_instance);
	}
}

void
App::set_selected_canvas_view(etl::loose_handle<CanvasView> canvas_view)
{
	if (selected_canvas_view == canvas_view)
		return;
	
	etl::loose_handle<CanvasView> prev = selected_canvas_view;
	etl::loose_handle<Instance> prev_instance = selected_instance;

	selected_canvas_view.reset();
	if (prev)
		prev->deactivate();

	selected_canvas_view = canvas_view;
	if (selected_canvas_view) {
		selected_instance = canvas_view->get_instance();
		selected_canvas_view->activate();
	} else {
		selected_instance.reset();
	}

	signal_canvas_view_focus()(selected_canvas_view);
	if (selected_instance != prev_instance)
		signal_instance_selected()(selected_instance);
}

etl::loose_handle<Instance>
App::get_instance(etl::handle<synfig::Canvas> canvas)
{
	if(!canvas) return 0;
	canvas=canvas->get_root();

	std::list<etl::handle<Instance> >::iterator iter;
	for(iter=instance_list.begin();iter!=instance_list.end();++iter)
	{
		if((*iter)->get_canvas()==canvas)
			return *iter;
	}
	return 0;
}

Gamma
App::get_selected_canvas_gamma()
{
	if (etl::loose_handle<CanvasView> canvas_view = App::get_selected_canvas_view())
		return canvas_view->get_canvas()->rend_desc().get_gamma();
	return Gamma();
}

void
App::dialog_about()
{
	if(about)
		about->show();
}

void
studio::App::undo()
{
	if(selected_instance)
		selected_instance->undo();
}

void
studio::App::redo()
{
	if(selected_instance)
		selected_instance->redo();
}

synfig::String
studio::App::get_base_path()
{
	return FileSystem::fix_slashes(app_base_path_);
}

void
studio::App::setup_changed()
{
	std::list<etl::handle<Instance> >::iterator iter;
	for(iter=instance_list.begin();iter!=instance_list.end();++iter)
	{
		std::list< etl::handle<synfigapp::CanvasInterface> >::iterator citer;
		std::list< etl::handle<synfigapp::CanvasInterface> >& cilist((*iter)->canvas_interface_list());
		for(citer=cilist.begin();citer!=cilist.end();++citer)
			{
				(*citer)->signal_rend_desc_changed()();
			}
	}
}

void
studio::App::process_all_events(long unsigned int us)
{
	Glib::usleep(us);
	while(studio::App::events_pending()) {
		while(studio::App::events_pending())
			studio::App::iteration(false);
		Glib::usleep(us);
	}
}

bool
studio::App::check_python_version(String path)
{
	String command;
	String result;
	command = path + " --version 2>&1";
	FILE* pipe = popen(command.c_str(), "r");
	if (!pipe) {
		return false;
	}
	char buffer[128];
	while(!feof(pipe)) {
		if(fgets(buffer, 128, pipe) != NULL)
				result += buffer;
	}
	pclose(pipe);
	// Output is like: "Python 3.3.0"
	if (result.substr(7,1) != "3"){
		return false;
	}
	return true;
}