/* === 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 #endif #include "instance.h" #include "canvasinterface.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "actions/valuedescexport.h" #include "actions/layerparamset.h" #include "actions/layerembed.h" #include #include "general.h" #include #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 > 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 synfigapp::find_instance(etl::handle canvas) { if(instance_map_.count(canvas)==0) return 0; return instance_map_[canvas]; } /* === M E T H O D S ======================================================= */ Instance::Instance(etl::handle canvas, etl::handle< synfig::FileContainerTemporary > container): CVSInfo(canvas->get_file_name()), canvas_(canvas), file_system_(new FileSystemGroup(FileSystemNative::instance())), container_(container) { file_system_->register_system("#", container_); assert(canvas->is_root()); unset_selection_manager(); instance_map_[canvas]=this; } // END of synfigapp::Instance::Instance() handle Instance::create(etl::handle canvas, etl::handle< synfig::FileContainerTemporary > container) { // Construct a new instance handle 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 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::save_canvas_callback(void *instance_ptr, synfig::Layer::ConstHandle layer, const std::string ¶m_name, std::string &filename) { // todo: "container:" and "images" literals Instance *instance = (Instance*)instance_ptr; std::string actual_filename = filename; if (actual_filename.substr(0, std::string("#").size()) == "#") actual_filename = "#images/" + actual_filename.substr(std::string("#").size()); // skip already packed (or unpacked) files bool file_already_in_container = actual_filename.substr(0, std::string("#").size()) == "#"; if (file_already_in_container && instance->save_canvas_into_container_) return false; if (!file_already_in_container && !instance->save_canvas_into_container_) return false; const std::string src_dir = instance->get_canvas()->get_file_path(); const std::string &dir = instance->save_canvas_reference_directory_; const std::string &localdir = instance->save_canvas_reference_local_directory_; std::string absolute_filename = file_already_in_container ? actual_filename : actual_filename.empty() ? src_dir : is_absolute_path(actual_filename) ? actual_filename : cleanup_path(src_dir+ETL_DIRECTORY_SEPARATOR+actual_filename); // is file already copied? for(FileReferenceList::iterator i = instance->save_canvas_references_.begin(); i != instance->save_canvas_references_.end(); i++) { if (i->old_filename == absolute_filename) { FileReference r = *i; r.layer = layer; r.param_name = param_name; instance->save_canvas_references_.push_back(r); filename = r.new_filename; return true; } } // try to create directory if (!instance->file_system_->directory_create(dir.substr(0,dir.size()-1))) return false; // generate new actual_filename int i = 0; std::string new_filename = basename(actual_filename); while(instance->file_system_->is_exists(dir + new_filename)) { new_filename = filename_sans_extension(basename(actual_filename)) + strprintf("_%d", ++i) + filename_extension(actual_filename); } // try to copy file if (!FileSystem::copy(instance->file_system_, absolute_filename, instance->file_system_, dir + new_filename)) return false; // save information about copied file FileReference r; r.layer = layer; r.param_name = param_name; r.old_filename = absolute_filename; r.new_filename = localdir + new_filename; if (r.new_filename.substr(0, String("#images/").size())=="#images/") r.new_filename = "#" + r.new_filename.substr(String("#images/").size()); instance->save_canvas_references_.push_back(r); filename = r.new_filename; return true; } void Instance::update_references_in_canvas(synfig::Canvas::Handle canvas) { for(std::list::const_iterator i = canvas->children().begin(); i != canvas->children().end(); i++) update_references_in_canvas(*i); for(IndependentContext c = canvas->get_independent_context(); *c; c++) { for(FileReferenceList::iterator j = save_canvas_references_.begin(); j != save_canvas_references_.end();) { if (*c == j->layer) { ValueBase value; value.set(j->new_filename); (*c)->set_param(j->param_name, value); (*c)->changed(); find_canvas_interface(get_canvas())->signal_layer_param_changed()(*c,j->param_name); j = save_canvas_references_.erase(j); } else j++; } } } bool Instance::import_external_canvas(Canvas::Handle canvas, std::map &imported) { etl::handle canvas_interface; for(IndependentContext i = canvas->get_independent_context(); *i; i++) { etl::handle paste_canvas = etl::handle::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::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::const_iterator i = canvas->children().begin(); i != canvas->children().end(); i++) if (import_external_canvas(*i, imported)) return true; return false; } void Instance::import_external_canvases() { std::map imported; while(import_external_canvas(get_canvas(), imported)); } void Instance::save_surface(const synfig::Surface &surface, const synfig::String &filename) { if (surface.get_h() <= 0 || surface.get_w() <= 0) return; String ext = filename_extension(filename); if (ext.empty()) return; ext.erase(0, 1); String tmpfile = FileContainerTemporary::generate_temporary_filename(); etl::handle target = etl::handle(Target::create(Target::ext_book()[ext],tmpfile,TargetParam())); if (!target) return; 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); target->add_frame(&surface); target = NULL; FileSystem::copy(FileSystemNative::instance(), tmpfile, get_file_system(), filename); FileSystemNative::instance()->file_remove(tmpfile); } void Instance::embed_all(synfig::Canvas::Handle canvas, bool &success, bool &restart) { etl::handle canvas_interface = find_canvas_interface(canvas); Action::ParamList paramList; paramList.add("canvas",canvas); paramList.add("canvas_interface",canvas_interface); for(synfig::Canvas::iterator i = canvas->begin(); i != canvas->end(); ++i) { // process layer paramList.remove_all("layer").add("layer",*i); if (Action::LayerEmbed::is_candidate(paramList)) { Action::Handle action(Action::LayerEmbed::create()); if (action) { action->set_param_list(paramList); if(action->is_ready()) { if(perform_action(action)) { restart = true; return; } } } success = false; } // process sub-canvas etl::handle layer_pastecanvas = etl::handle::cast_dynamic(*i); if (layer_pastecanvas) { synfig::Canvas::Handle sub_canvas = layer_pastecanvas->get_sub_canvas(); if (sub_canvas) { embed_all(sub_canvas, success, restart); if (restart) return; } } } } bool Instance::embed_all() { bool success = true; bool restart = true; while(restart) { restart = false; embed_all(get_canvas(), success, restart); } return success; } //! make relative filenames from animated valuenodes void Instance::convert_animated_filenames(const Canvas::Handle &canvas, const synfig::String &old_path, const synfig::String &new_path) { for(Canvas::iterator i = canvas->begin(); i != canvas->end(); ++i) { const Layer::DynamicParamList &dynamic_param_list = (*i)->dynamic_param_list(); Layer::DynamicParamList::const_iterator j = dynamic_param_list.find("filename"); if (j != dynamic_param_list.end()) { ValueNode_Animated::Handle valuenode_animated = ValueNode_Animated::Handle::cast_dynamic(j->second); if (valuenode_animated) { WaypointList &waypoint_list = valuenode_animated->editable_waypoint_list(); for(WaypointList::iterator k = waypoint_list.begin(); k != waypoint_list.end(); ++k) { ValueNode_Const::Handle valuenode_const = ValueNode_Const::Handle::cast_dynamic(k->get_value_node()); if (valuenode_const && valuenode_const->get_type() == type_string) { String s = valuenode_const->get_value().get(String()); if (!s.empty() && s[0] != '#') { warning(old_path); warning(new_path); if (!is_absolute_path(s) && !old_path.empty()) s = old_path + ETL_DIRECTORY_SEPARATOR + s; s = relative_path(new_path, s); valuenode_const->set_value(s); } } } } } etl::handle layer_paste_canvas = etl::handle::cast_dynamic(*i); if (layer_paste_canvas && layer_paste_canvas->get_sub_canvas() && !layer_paste_canvas->get_sub_canvas()->is_root()) convert_animated_filenames(Canvas::Handle(layer_paste_canvas->get_sub_canvas()), old_path, new_path); } } bool Instance::save() { return save_as(get_canvas()->get_file_name()); } bool Instance::save_as(const synfig::String &file_name) { if (filename_extension(file_name) == ".sfg") embed_all(); save_canvas_into_container_ = false; bool embed_data = false; bool extract_data = false; std::string canvas_filename = file_name; convert_animated_filenames(get_canvas(), absolute_path(get_canvas()->get_file_path()), absolute_path(dirname(file_name))); // save bitmaps std::set layers_to_save_set; for(std::list::iterator i = layers_to_save.begin(); i != layers_to_save.end(); i++) layers_to_save_set.insert(*i); for(std::set::iterator i = layers_to_save_set.begin(); i != layers_to_save_set.end(); i++) { etl::handle layer_bitmap = etl::handle::cast_dynamic(*i); if (!layer_bitmap) continue; if (!layer_bitmap->get_canvas()) continue; if (!(*i)->get_param_list().count("filename")) continue; ValueBase value = (*i)->get_param("filename"); if (!value.same_type_as(String())) continue; String filename = value.get(String()); // TODO: literals '#' and 'images/' if (!filename.empty() && filename[0] == '#') filename.insert(1, "images/"); save_surface(layer_bitmap->surface, filename); } if (filename_extension(file_name) == ".sfg") { save_canvas_reference_directory_ = "#images/"; save_canvas_reference_local_directory_ = "#images/"; canvas_filename = "#project.sifz"; save_canvas_into_container_ = true; embed_data = filename_extension(get_canvas()->get_file_name()) != ".sfg"; } else { save_canvas_reference_directory_ = filename_sans_extension(file_name) + ".images" + ETL_DIRECTORY_SEPARATOR; save_canvas_reference_local_directory_ = filename_sans_extension(basename(file_name)) + ".images" + ETL_DIRECTORY_SEPARATOR; extract_data = filename_extension(get_canvas()->get_file_name()) == ".sfg"; } if (embed_data) import_external_canvases(); bool ret; String old_file_name(get_file_name()); set_file_name(file_name); get_canvas()->set_identifier(file_system_->get_identifier(canvas_filename)); if (embed_data || extract_data) set_save_canvas_external_file_callback(save_canvas_callback, this); else set_save_canvas_external_file_callback(NULL, NULL); ret = save_canvas(file_system_->get_identifier(canvas_filename),canvas_,!save_canvas_into_container_); if (ret && save_canvas_into_container_) ret = container_->save_changes(file_name, false); if (ret && (embed_data || extract_data)) update_references_in_canvas(get_canvas()); set_save_canvas_external_file_callback(NULL, NULL); save_canvas_references_.clear(); if(ret) { reset_action_count(); signal_saved_(); } else set_file_name(old_file_name); signal_filename_changed_(); return ret; } bool Instance::generate_new_name( synfig::Layer::Handle layer, synfig::Canvas::Handle canvas, synfig::FileSystem::Handle file_system, synfig::String &out_description, synfig::String &out_filename, synfig::String &out_filename_param) { out_description.clear(); out_filename.clear(); out_filename_param.clear(); String description = layer->get_description(); String filename; etl::handle layer_bitmap = etl::handle::cast_dynamic(layer); if (layer_bitmap && layer_bitmap->surface.get_w() > 0 && layer_bitmap->surface.get_h() > 0 && layer_bitmap->get_param_list().count("filename")) { ValueBase value = layer_bitmap->get_param("filename"); if (value.same_type_as(String()) && filename_extension(value.get(String())) == ".png") filename = basename(value.get(String())); } // extract name from filename or from description String name = filename.empty() ? description : filename_sans_extension(filename); String ext = filename_extension(name); if (ext.find_first_not_of(".0123456789") == String::npos) name = filename_sans_extension(name); for(size_t i = name.find("#", 0); i != String::npos; i = name.find("#", i)) name.erase(i, 1); // if name based on description add extension ext = filename.empty() ? ".png" : filename_extension(filename); // generate new names for(int i = 0; i < 10000; i++) { bool valid = true; String number = strprintf("%04d", i); // TODO: literal '#' String current_description = name + "." + number; String current_filename = "#images/" + name + "." + number + ext; String current_filename_param = "#" + name + "." + number + ext; if (current_description == description || current_filename == filename) valid = false; if (valid && canvas) for(IndependentContext ic = canvas->get_independent_context(); *ic; ic++) if ((*ic)->get_description() == current_description) { valid = false; break; } if (valid && file_system && file_system->is_exists(current_filename)) valid = false; if (valid) { out_description = current_description; out_filename = current_filename; out_filename_param = current_filename_param; break; } } return true; }