/* === S Y N F I G ========================================================= */
/*! \file synfigapp/instance.cpp
** \brief Instance
**
** $Id$
**
** \legal
** Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
** Copyright (c) 2007, 2008 Chris Moore
** Copyright (c) 2011 Carlos López
**
** This package is free software; you can redistribute it and/or
** modify it under the terms of the GNU General Public License as
** published by the Free Software Foundation; either version 2 of
** the License, or (at your option) any later version.
**
** This package is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
** General Public License for more details.
** \endlegal
*/
/* ========================================================================= */
/* === H E A D E R S ======================================================= */
#ifdef USING_PCH
# include "pch.h"
#else
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <synfig/general.h>
#include "instance.h"
#include "canvasinterface.h"
#include <iostream>
#include <synfig/context.h>
#include <synfig/canvasfilenaming.h>
#include <synfig/loadcanvas.h>
#include <synfig/savecanvas.h>
#include <synfig/filecontainerzip.h>
#include <synfig/filesystem.h>
#include <synfig/filesystemnative.h>
#include <synfig/filesystemtemporary.h>
#include <synfig/valuenodes/valuenode_add.h>
#include <synfig/valuenodes/valuenode_composite.h>
#include <synfig/valuenodes/valuenode_const.h>
#include <synfig/valuenodes/valuenode_radialcomposite.h>
#include <synfig/valuenodes/valuenode_reference.h>
#include <synfig/valuenodes/valuenode_boneinfluence.h>
#include <synfig/valuenodes/valuenode_greyed.h>
#include <synfig/valuenodes/valuenode_blinecalctangent.h>
#include <synfig/valuenodes/valuenode_blinecalcvertex.h>
#include <synfig/valuenodes/valuenode_blinecalcwidth.h>
#include <synfig/valuenodes/valuenode_wplist.h>
#include <synfig/valuenodes/valuenode_scale.h>
#include <synfig/valuenodes/valuenode_range.h>
#include <synfig/valuenodes/valuenode_integer.h>
#include <synfig/valuenodes/valuenode_real.h>
#include <synfig/valuenodes/valuenode_bonelink.h>
#include <synfig/valuenodes/valuenode_average.h>
#include <synfig/valuenodes/valuenode_weightedaverage.h>
#include <synfig/layers/layer_pastecanvas.h>
#include <synfig/layers/layer_bitmap.h>
#include <synfig/rendering/software/surfacesw.h>
#include <synfig/target_scanline.h>
#include "actions/valuedescexport.h"
#include "actions/layerparamset.h"
#include "actions/layerembed.h"
#include <map>
#include <synfigapp/localization.h>
#include <synfig/importer.h>
#endif
/* === U S I N G =========================================================== */
using namespace std;
using namespace etl;
using namespace synfig;
using namespace synfigapp;
/* === M A C R O S ========================================================= */
/* === G L O B A L S ======================================================= */
static std::map<loose_handle<Canvas>, loose_handle<Instance> > instance_map_;
/* === P R O C E D U R E S ================================================= */
bool
synfigapp::is_editable(synfig::ValueNode::Handle value_node)
{
if(ValueNode_Const::Handle::cast_dynamic(value_node)
|| ValueNode_Animated::Handle::cast_dynamic(value_node)
|| ValueNode_Add::Handle::cast_dynamic(value_node)
|| ValueNode_Composite::Handle::cast_dynamic(value_node)
|| ValueNode_RadialComposite::Handle::cast_dynamic(value_node)
||(ValueNode_Reference::Handle::cast_dynamic(value_node) && !ValueNode_Greyed::Handle::cast_dynamic(value_node))
|| ValueNode_BoneInfluence::Handle::cast_dynamic(value_node)
|| ValueNode_BLineCalcVertex::Handle::cast_dynamic(value_node)
|| ValueNode_BLineCalcTangent::Handle::cast_dynamic(value_node)
|| ValueNode_BLineCalcWidth::Handle::cast_dynamic(value_node)
|| ValueNode_Scale::Handle::cast_dynamic(value_node)
|| ValueNode_Range::Handle::cast_dynamic(value_node)
|| ValueNode_Integer::Handle::cast_dynamic(value_node)
|| ValueNode_Real::Handle::cast_dynamic(value_node)
|| ValueNode_BoneLink::Handle::cast_dynamic(value_node)
|| ValueNode_Average::Handle::cast_dynamic(value_node)
|| ValueNode_WeightedAverage::Handle::cast_dynamic(value_node)
)
return true;
return false;
}
etl::handle<Instance>
synfigapp::find_instance(etl::handle<synfig::Canvas> canvas)
{
if(instance_map_.count(canvas)==0)
return 0;
return instance_map_[canvas];
}
/* === M E T H O D S ======================================================= */
Instance::Instance(etl::handle<synfig::Canvas> canvas, synfig::FileSystem::Handle container):
CVSInfo(canvas->get_file_name()),
canvas_(canvas),
container_(container)
{
assert(canvas->is_root());
unset_selection_manager();
instance_map_[canvas]=this;
} // END of synfigapp::Instance::Instance()
handle<Instance>
Instance::create(etl::handle<synfig::Canvas> canvas, synfig::FileSystem::Handle container)
{
// Construct a new instance
handle<Instance> instance(new Instance(canvas, container));
return instance;
} // END of synfigapp::Instance::create()
synfig::String
Instance::get_file_name()const
{
return get_canvas()->get_file_name();
}
void
Instance::set_file_name(const synfig::String &name)
{
get_canvas()->set_file_name(name);
CVSInfo::set_file_name(name);
}
Instance::~Instance()
{
instance_map_.erase(canvas_);
if (getenv("SYNFIG_DEBUG_DESTRUCTORS"))
synfig::info("Instance::~Instance(): Deleted");
}
handle<CanvasInterface>
Instance::find_canvas_interface(synfig::Canvas::Handle canvas)
{
if(!canvas)
return 0;
while(canvas->is_inline())
canvas=canvas->parent();
CanvasInterfaceList::iterator iter;
for(iter=canvas_interface_list().begin();iter!=canvas_interface_list().end();iter++)
if((*iter)->get_canvas()==canvas)
return *iter;
return CanvasInterface::create(this,canvas);
}
bool
Instance::import_external_canvas(Canvas::Handle canvas, std::map<Canvas*, Canvas::Handle> &imported)
{
etl::handle<CanvasInterface> canvas_interface;
for(IndependentContext i = canvas->get_independent_context(); *i; i++)
{
etl::handle<Layer_PasteCanvas> paste_canvas = etl::handle<Layer_PasteCanvas>::cast_dynamic(*i);
if (!paste_canvas) continue;
Canvas::Handle sub_canvas = paste_canvas->get_sub_canvas();
if (!sub_canvas) continue;
if (!sub_canvas->is_root()) continue;
if (imported.count(sub_canvas.get()) != 0) {
// link already exported canvas
Canvas::Handle new_canvas = imported[sub_canvas.get()];
if (!new_canvas) continue;
// Action to link canvas
try
{
Action::Handle action(Action::LayerParamSet::create());
if (!action) continue;
canvas_interface = find_canvas_interface(canvas);
action->set_param("canvas",canvas);
action->set_param("canvas_interface",canvas_interface);
action->set_param("layer",Layer::Handle(paste_canvas));
action->set_param("param","canvas");
action->set_param("new_value",ValueBase(new_canvas));
if(!action->is_ready()) continue;
if(!perform_action(action)) continue;
}
catch(...)
{
continue;
}
} else {
imported[sub_canvas.get()] = NULL;
// generate name
std::string fname = filename_sans_extension(basename(sub_canvas->get_file_name()));
static const char bad_chars[]=" :#@$^&()*";
for(std::string::iterator j = fname.begin(); j != fname.end(); j++)
for(const char *k = bad_chars; *k != 0; k++)
if (*j == *k) { *j = '_'; break; }
if (fname.empty()) fname = "canvas";
if (fname[0]>='0' && fname[0]<='9')
fname = "_" + fname;
std::string name;
bool found = false;
for(int j = 1; j < 1000; j++)
{
name = j == 1 ? fname : strprintf("%s_%d", fname.c_str(), j);
if (canvas->value_node_list().count(name) == false)
{
found = true;
for(std::list<Canvas::Handle>::const_iterator iter=canvas->children().begin();iter!=canvas->children().end();iter++)
if(name==(*iter)->get_id())
{ found = false; break; }
if (found) break;
}
}
if (!found) continue;
// Action to import canvas
try {
Action::Handle action(Action::ValueDescExport::create());
if (!action) continue;
canvas_interface = find_canvas_interface(canvas);
action->set_param("canvas",canvas);
action->set_param("canvas_interface",canvas_interface);
action->set_param("value_desc",ValueDesc(Layer::Handle(paste_canvas),std::string("canvas")));
action->set_param("name",name);
if(!action->is_ready()) continue;
if(!perform_action(action)) continue;
std::string warnings;
imported[sub_canvas.get()] = canvas->find_canvas(name, warnings);
}
catch(...)
{
continue;
}
return true;
}
}
for(std::list<Canvas::Handle>::const_iterator i = canvas->children().begin(); i != canvas->children().end(); i++)
if (import_external_canvas(*i, imported))
return true;
return false;
}
etl::handle<Action::Group>
Instance::import_external_canvases()
{
synfigapp::Action::PassiveGrouper group(this, _("Import external canvases"));
std::map<Canvas*, Canvas::Handle> imported;
while(import_external_canvas(get_canvas(), imported));
return group.finish();
}
bool Instance::save_surface(const rendering::SurfaceResource::Handle &surface, const synfig::String &filename)
{
rendering::SurfaceResource::LockRead<rendering::SurfaceSW> lock(surface);
if (!lock) return false;
return save_surface(lock->get_surface(), filename);
}
bool Instance::save_surface(const synfig::Surface &surface, const synfig::String &filename)
{
if (surface.get_h() <= 0 || surface.get_w() <= 0)
return false;
String ext = filename_extension(filename);
if (ext.empty())
return false;
ext.erase(0, 1);
String tmpfile = FileSystemTemporary::generate_system_temporary_filename("surface");
etl::handle<Target_Scanline> target =
etl::handle<Target_Scanline>::cast_dynamic(
Target::create(Target::ext_book()[ext],tmpfile,TargetParam()) );
if (!target)
return false;
bool success = true;
target->set_canvas(get_canvas());
RendDesc desc;
desc.set_w(surface.get_w());
desc.set_h(surface.get_h());
desc.set_x_res(1);
desc.set_y_res(1);
desc.set_frame_rate(1);
desc.set_frame(0);
desc.set_frame_start(0);
desc.set_frame_end(0);
target->set_rend_desc(&desc);
if (success)
success = target->add_frame(&surface);
target = NULL;
if (success)
success = get_canvas()->get_file_system()->directory_create(etl::dirname(filename));
if (success)
success = FileSystem::copy(FileSystemNative::instance(), tmpfile, get_canvas()->get_file_system(), filename);
FileSystemNative::instance()->file_remove(tmpfile);
return success;
}
void
Instance::process_filename(const ProcessFilenamesParams ¶ms, const synfig::String &filename, synfig::String &out_filename)
{
String full_filename = CanvasFileNaming::make_full_filename(params.previous_canvas_filename, filename);
map<String, String>::const_iterator i = params.processed_files.find(full_filename);
if (i != params.processed_files.end())
{ out_filename = i->second; return; }
if (params.embed_files)
{
if ( CanvasFileNaming::can_embed(filename)
&& !CanvasFileNaming::is_embeded(params.previous_canvas_filename, filename))
{
String new_filename = CanvasFileNaming::generate_container_filename(get_canvas()->get_identifier().file_system, filename);
String new_full_filename = CanvasFileNaming::make_full_filename(get_canvas()->get_file_name(), out_filename);
if (FileSystem::copy(
params.previous_canvas_filesystem,
full_filename,
get_canvas()->get_identifier().file_system,
new_full_filename ))
{
out_filename = new_filename;
params.processed_files[full_filename] = out_filename;
info("embed file: %s -> %s", filename.c_str(), out_filename.c_str());
return;
}
else
{
warning("Cannot embed file: %s", filename.c_str());
}
}
}
out_filename = CanvasFileNaming::make_short_filename(params.canvas->get_file_name(), full_filename);
params.processed_files[full_filename] = out_filename;
info("refine filename: %s -> %s", filename.c_str(), out_filename.c_str());
}
void
Instance::process_filenames(const ProcessFilenamesParams ¶ms, const synfig::NodeHandle &node, bool self)
{
if (!node || params.processed_nodes.count(node)) return;
params.processed_nodes.insert(node);
// ValueNodeConst
if (ValueNode_Const::Handle value_node = ValueNode_Const::Handle::cast_dynamic(node))
{
// allow to process valuenodes without canvas
if ( value_node->get_parent_canvas()
&& value_node->get_parent_canvas()->get_root() != params.canvas)
return;
ValueBase value = value_node->get_value();
if (self)
{
if (value.same_type_as(String()))
{
String filename = value.get(String());
String new_filename;
process_filename(params, filename, new_filename);
if (filename != new_filename)
{
params.processed_valuenodes[value_node] = filename;
value_node->set_value(new_filename);
}
return;
}
warning("Cannot process filename for node: %s", node->get_string().c_str());
}
if (value.can_get(Canvas::Handle()))
process_filenames(params, value.get(Canvas::Handle()));
return;
}
// ValueNode_Animated
if (ValueNode_Animated::Handle animated = ValueNode_Animated::Handle::cast_dynamic(node))
{
const WaypointList &waypoints = animated->waypoint_list();
for(WaypointList::const_iterator i = waypoints.begin(); i != waypoints.end(); ++i)
process_filenames(params, i->get_value_node(), self);
return;
}
// Followed node-types cannot by processed by it self (childs only)
if (self)
warning("Cannot process filename for node: %s", node->get_string().c_str());
// Canvas
if (Canvas::Handle canvas = Canvas::Handle::cast_dynamic(node))
{
if (canvas->get_root() != params.canvas) return;
// exported values
if (canvas->is_inline())
{
const ValueNodeList &list = canvas->value_node_list();
for(ValueNodeList::const_iterator i = list.begin(); i != list.end(); ++i)
process_filenames(params, *i);
}
// layers
for(Canvas::const_iterator i = canvas->begin(); i != canvas->end(); ++i)
process_filenames(params, *i);
return;
}
// Layer
if (Layer::Handle layer = Layer::Handle::cast_dynamic(node))
{
// skip layers without canvas
if ( !layer->get_canvas()
|| layer->get_canvas()->get_root() != params.canvas)
return;
const ParamVocab vocab = layer->get_param_vocab();
const Layer::DynamicParamList &dynamic_params = layer->dynamic_param_list();
for(ParamVocab::const_iterator i = vocab.begin(); i != vocab.end(); ++i)
{
Layer::DynamicParamList::const_iterator j = dynamic_params.find(i->get_name());
ValueNode::Handle value_node = j == dynamic_params.end() ? ValueNode::Handle() : ValueNode::Handle(j->second);
bool is_filename = i->get_hint() == "filename";
if (value_node)
{
process_filenames(params, value_node, is_filename);
continue;
}
ValueBase value = layer->get_param(i->get_name());
if (value.can_get(Canvas::Handle()))
process_filenames(params, value.get(Canvas::Handle()));
if (!is_filename || !value.same_type_as(String()))
continue;
String filename = value.get(String());
String new_filename;
process_filename(params, filename, new_filename);
if (filename != new_filename)
{
params.processed_params[std::make_pair(layer, i->get_name())] = filename;
layer->set_param(i->get_name(), new_filename);
}
}
return;
}
// LinkableValueNode
if (LinkableValueNode::Handle linkable = LinkableValueNode::Handle::cast_dynamic(node))
{
// allow to process valuenodes without canvas
if ( linkable->get_parent_canvas()
&& linkable->get_parent_canvas()->get_root() != params.canvas)
return;
const ParamVocab vocab = linkable->get_children_vocab();
for(ParamVocab::const_iterator i = vocab.begin(); i != vocab.end(); ++i)
process_filenames(params, ValueNode::Handle(linkable->get_link(i->get_name())), i->get_hint() == "filename");
return;
}
}
void
Instance::process_filenames_undo(const ProcessFilenamesParams ¶ms)
{
// restore layer params
for(std::map<std::pair<Layer::Handle, String>, String>::const_iterator i = params.processed_params.begin(); i != params.processed_params.end(); ++i)
i->first.first->set_param(i->first.second, i->second);
// restore value nodes
for(std::map<ValueNode_Const::Handle, String>::const_iterator i = params.processed_valuenodes.begin(); i != params.processed_valuenodes.end(); ++i)
i->first->set_value(i->second);
}
void
Instance::find_unsaved_layers(std::vector<synfig::Layer::Handle> &out_layers, const synfig::Canvas::Handle canvas)
{
for(Canvas::const_iterator i = canvas->begin(); i != canvas->end(); ++i)
{
if (Layer_PasteCanvas::Handle layer_pastecanvas = Layer_PasteCanvas::Handle::cast_dynamic(*i))
if (Canvas::Handle sub_canvas = layer_pastecanvas->get_sub_canvas())
find_unsaved_layers(out_layers, sub_canvas);
if (Layer_Bitmap::Handle layer_bitmap = Layer_Bitmap::Handle::cast_dynamic(*i))
if (layer_bitmap->is_surface_modified())
out_layers.push_back(layer_bitmap);
}
}
bool
Instance::save()
{
return save_as(get_canvas()->get_file_name());
}
bool
Instance::save_layer(const synfig::Layer::Handle &layer)
{
if (Layer_Bitmap::Handle layer_bitmap = Layer_Bitmap::Handle::cast_dynamic(layer))
{
if ( layer_bitmap->is_surface_modified()
&& layer_bitmap->get_param_list().count("filename"))
{
ValueBase value = layer_bitmap->get_param("filename");
if (value.same_type_as(String()))
{
String filename = value.get(String());
if (save_surface(layer_bitmap->rendering_surface, filename))
return true;
error("Cannot save image: %s", filename.c_str());
return false;
}
}
}
error("Don't know how to save layer type: %s", layer->get_name().c_str());
return false;
}
void
Instance::save_all_layers()
{
std::vector<Layer::Handle> layers;
find_unsaved_layers(layers);
for(std::vector<Layer::Handle>::const_iterator i = layers.begin(); i != layers.end(); ++i)
save_layer(*i);
}
bool
Instance::backup(bool save_even_if_unchanged)
{
if (!get_action_count() && !save_even_if_unchanged)
return true;
FileSystemTemporary::Handle temporary_filesystem = FileSystemTemporary::Handle::cast_dynamic(get_canvas()->get_file_system());
if (!temporary_filesystem)
{
warning("Cannot backup, canvas was not attached to temporary file system: %s", get_file_name().c_str());
return false;
}
// don't save images while backup
//if (success)
// save_all_layers();
if (!save_canvas(get_canvas()->get_identifier(), get_canvas(), false))
return false;
return temporary_filesystem->save_temporary();
}
bool
Instance::save_as(const synfig::String &file_name)
{
Canvas::Handle canvas = get_canvas();
FileSystem::Identifier previous_canvas_identifier = canvas->get_identifier();
FileSystem::Handle previous_canvas_filesystem = previous_canvas_identifier.file_system;
FileSystem::Handle previous_container = get_container();
String previous_canvas_filename = canvas->get_file_name();
FileSystem::Identifier new_canvas_identifier = previous_canvas_identifier;
FileSystem::Handle new_canvas_filesystem = previous_canvas_filesystem;
FileSystem::Handle new_container = previous_container;
String new_canvas_filename = file_name;
bool is_filename_changed = previous_canvas_filename != new_canvas_filename;
if (is_filename_changed)
{
// new canvas filesystem
FileSystem::Handle new_container = CanvasFileNaming::make_filesystem_container(new_canvas_filename, 0, true);
if (!new_container)
{
warning("Cannot create container: %s", new_canvas_filename.c_str());
return false;
}
new_canvas_filesystem = CanvasFileNaming::make_filesystem(new_container);
if (!new_canvas_filesystem)
{
warning("Cannot create canvas filesysem for: %s", new_canvas_filename.c_str());
return false;
}
// wrap into temporary file system
if (FileSystemTemporary::Handle previous_temporary_filesystem = FileSystemTemporary::Handle::cast_dynamic(previous_canvas_filesystem))
{
FileSystemTemporary::Handle new_temporary_filesystem = new FileSystemTemporary(
previous_temporary_filesystem->get_tag(),
previous_temporary_filesystem->get_temporary_directory(),
new_canvas_filesystem );
new_temporary_filesystem->set_meta("filename", new_canvas_filename);
new_temporary_filesystem->set_meta("as", new_canvas_filename);
new_temporary_filesystem->set_meta("truncate", "0");
new_canvas_filesystem = new_temporary_filesystem;
previous_temporary_filesystem->discard_changes();
}
new_canvas_identifier = new_canvas_filesystem->get_identifier(CanvasFileNaming::project_file(new_canvas_filename));
// copy embedded files
if (!FileSystem::copy_recursive(
previous_canvas_filesystem,
CanvasFileNaming::container_prefix,
new_canvas_filesystem,
CanvasFileNaming::container_prefix ))
{
//new_canvas_filesystem->remove_recursive(CanvasFileNaming::container_prefix);
new_canvas_filesystem.reset();
new_container.reset();
//FileSystemNative::instance()->file_remove(new_canvas_filename);
return false;
}
// remove previous canvas file
if (previous_canvas_identifier.filename != new_canvas_identifier.filename)
new_canvas_filesystem->file_remove(previous_canvas_identifier.filename);
// set new canvas filename
canvas->set_file_name(new_canvas_filename);
canvas->set_identifier(new_canvas_identifier);
container_ = new_container;
}
// save bitmaps
save_all_layers();
// find zip-container
FileContainerZip::Handle new_container_zip = FileContainerZip::Handle::cast_dynamic(new_container);
bool embed_files = (bool)new_container_zip;
bool save_files = true;
etl::handle<Action::Group> import_external_canvases_action;
if (embed_files)
import_external_canvases_action = import_external_canvases();
// process filenames
ProcessFilenamesParams params(
canvas,
previous_canvas_filesystem,
previous_canvas_filename,
(bool)embed_files,
save_files );
process_filenames(params, canvas);
// save
bool success = true;
if (success)
success = save_canvas(new_canvas_identifier, canvas, false);
if (success)
if (FileSystemTemporary::Handle temporary_filesystem = FileSystemTemporary::Handle::cast_dynamic(new_canvas_filesystem))
success = temporary_filesystem->save_changes();
if (success && new_container_zip)
success = new_container_zip->save();
if (success)
reset_action_count();
if (success)
{
signal_saved_();
signal_filename_changed_();
if (is_filename_changed) CVSInfo::set_file_name(new_canvas_filename);
return true;
}
// undo
canvas->set_file_name(previous_canvas_filename);
canvas->set_identifier(previous_canvas_identifier);
container_ = previous_container;
process_filenames_undo(params);
//new_canvas_filesystem->remove_recursive(CanvasFileNaming::container_prefix);
new_canvas_filesystem.reset();
new_container.reset();
//FileSystemNative::instance()->file_remove(new_canvas_filename);
if (import_external_canvases_action)
import_external_canvases_action->undo();
return false;
}
void
Instance::generate_new_name(
const Layer::Handle &layer,
String &out_description,
String &out_filename,
String &out_filename_param )
{
String filename;
if (layer->get_param_list().count("filename")) {
ValueBase value = layer->get_param("filename");
if (value.same_type_as(String()))
filename = basename(value.get(String()));
}
if (filename.empty())
filename = !layer->get_description().empty()
? layer->get_description()
: layer->get_local_name();
if (CanvasFileNaming::filename_extension_lower(filename) != "png")
filename += ".png";
assert(get_canvas()->get_file_system());
String short_filename = CanvasFileNaming::generate_container_filename(get_canvas()->get_file_system(), filename);
String full_filename = CanvasFileNaming::make_full_filename(get_canvas()->get_file_name(), short_filename);
String base = etl::filename_sans_extension(CanvasFileNaming::filename_base(short_filename));
out_description = base;
out_filename = full_filename;
out_filename_param = short_filename;
}