/* === S Y N F I G ========================================================= */ /*! \file action_system.cpp ** \brief Template File ** ** $Id: action_system.cpp 336 2007-03-16 00:39:42Z dooglus $ ** ** \legal ** Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley ** ** 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 "action_system.h" #include "instance.h" #include "canvasinterface.h" #endif /* === U S I N G =========================================================== */ using namespace std; using namespace etl; using namespace synfigapp; using namespace synfig; /* === M A C R O S ========================================================= */ /* === G L O B A L S ======================================================= */ /* === P R O C E D U R E S ================================================= */ /* === M E T H O D S ======================================================= */ Action::System::System(): action_count_(0) { unset_ui_interface(); clear_redo_stack_on_new_action_=false; } Action::System::~System() { } bool Action::System::perform_action(handle action) { handle uim(get_ui_interface()); assert(action); if(!action->is_ready()) { uim->error(action->get_name()+": "+_("Action is not ready.")); return false; } most_recent_action_=action; static bool inuse=false; if(inuse) return false; inuse=true; try { assert(action); Action::CanvasSpecific* canvas_specific(dynamic_cast(action.get())); if(canvas_specific && canvas_specific->get_canvas()) { handle canvas_interface=static_cast(this)->find_canvas_interface(canvas_specific->get_canvas()); assert(canvas_interface); uim=canvas_interface->get_ui_interface(); } handle undoable_action=handle::cast_dynamic(action); // If we cannot undo this action, make sure // that the user knows this. if(!undoable_action) { if(uim->yes_no( action->get_name(), _("This action cannot be undone! Are you sure you want to continue?"), UIInterface::RESPONSE_NO ) == UIInterface::RESPONSE_NO ) return false; else { // Because this action cannot be undone, // we need to clear the undo stack clear_undo_stack(); } } else assert(undoable_action->is_active()); // Perform the action try { action->perform(); } catch(Action::Error err) { uim->task(action->get_name()+' '+_("Failed")); inuse=false; if(err.get_type()!=Action::Error::TYPE_UNABLE) { if(err.get_desc().empty()) uim->error(action->get_name()+": "+strprintf("%d",err.get_type())); else uim->error(action->get_name()+": "+err.get_desc()); } // If action failed for whatever reason, just return false and do // not add the action onto the list return false; } catch(std::exception err) { uim->task(action->get_name()+' '+_("Failed")); inuse=false; uim->error(action->get_name()+": "+err.what()); // If action failed for whatever reason, just return false and do // not add the action onto the list return false; } catch(...) { uim->task(action->get_name()+' '+_("Failed")); inuse=false; // If action failed for whatever reason, just return false and do // not add the action onto the list return false; } // Clear the redo stack if(clear_redo_stack_on_new_action_) clear_redo_stack(); if(!group_stack_.empty()) group_stack_.front()->inc_depth(); else inc_action_count(); // Push this action onto the action list if we can undo it if(undoable_action) { // If necessary, signal the change in status of undo if(undo_action_stack_.empty()) signal_undo_status_(true); // Add it to the list undo_action_stack_.push_front(undoable_action); // Signal that a new action has been added if(group_stack_.empty()) signal_new_action()(undoable_action); } inuse=false; uim->task(action->get_name()+' '+_("Successful")); // If the action has "dirtied" the preview, signal it. if(0)if(canvas_specific && canvas_specific->is_dirty()) { Canvas::Handle canvas=canvas_specific->get_canvas(); if(!group_stack_.empty()) group_stack_.front()->request_redraw(canvas_specific->get_canvas_interface()); else { handle canvas_interface=static_cast(this)->find_canvas_interface(canvas); assert(canvas_interface); DEBUGPOINT(); //canvas_interface->signal_dirty_preview()(); } } }catch(...) { inuse=false; throw; } return true; } bool synfigapp::Action::System::undo_(handle uim) { handle action(undo_action_stack().front()); most_recent_action_=action; try { if(action->is_active()) action->undo(); } catch(Action::Error err) { if(err.get_type()!=Action::Error::TYPE_UNABLE) { if(err.get_desc().empty()) uim->error(action->get_name()+_(" (Undo): ")+strprintf("%d",err.get_type())); else uim->error(action->get_name()+_(" (Undo): ")+err.get_desc()); } return false; } catch(std::runtime_error x) { uim->error(x.what()); return false; } catch(...) { return false; } dec_action_count(); if(redo_action_stack_.empty()) signal_redo_status()(true); redo_action_stack_.push_front(undo_action_stack_.front()); undo_action_stack_.pop_front(); if(undo_action_stack_.empty()) signal_undo_status()(false); if(!group_stack_.empty()) group_stack_.front()->dec_depth(); signal_undo_(); return true; } bool synfigapp::Action::System::undo() { //! \todo This function is not exception safe! static bool inuse=false; // If there is nothing on the action list, there is nothing to undo if(undo_action_stack().empty() || inuse) return false; handle action=undo_action_stack().front(); Action::CanvasSpecific* canvas_specific(dynamic_cast(action.get())); handle uim; if(canvas_specific && canvas_specific->get_canvas()) { Canvas::Handle canvas=canvas_specific->get_canvas(); handle canvas_interface=static_cast(this)->find_canvas_interface(canvas); assert(canvas_interface); uim=canvas_interface->get_ui_interface(); } else uim=get_ui_interface(); inuse=true; if(!undo_(uim)) { uim->error(undo_action_stack_.front()->get_name()+": "+_("Failed to undo.")); inuse=false; return false; } inuse=false; // If the action has "dirtied" the preview, signal it. if(0)if(action->is_active() && canvas_specific && canvas_specific->is_dirty()) { Canvas::Handle canvas=canvas_specific->get_canvas(); if(!group_stack_.empty()) group_stack_.front()->request_redraw(canvas_specific->get_canvas_interface()); else { handle canvas_interface=static_cast(this)->find_canvas_interface(canvas); assert(canvas_interface); //DEBUGPOINT(); //canvas_interface->signal_dirty_preview()(); } } return true; } bool Action::System::redo_(handle uim) { handle action(redo_action_stack().front()); most_recent_action_=action; try { if(action->is_active()) action->perform(); } catch(Action::Error err) { if(err.get_type()!=Action::Error::TYPE_UNABLE) { if(err.get_desc().empty()) uim->error(action->get_name()+_(" (Redo): ")+strprintf("%d",err.get_type())); else uim->error(action->get_name()+_(" (Redo): ")+err.get_desc()); } return false; } catch(std::runtime_error x) { uim->error(x.what()); return false; } catch(...) { return false; } inc_action_count(); if(undo_action_stack_.empty()) signal_undo_status()(true); undo_action_stack_.push_front(redo_action_stack_.front()); redo_action_stack_.pop_front(); if(redo_action_stack_.empty()) signal_redo_status()(false); if(!group_stack_.empty()) group_stack_.front()->inc_depth(); signal_redo_(); return true; } bool Action::System::redo() { //! \todo This function is not exception safe! static bool inuse=false; // If there is nothing on the action list, there is nothing to undo if(redo_action_stack_.empty() || inuse) return false; inuse=true; handle action=redo_action_stack().front(); Action::CanvasSpecific* canvas_specific(dynamic_cast(action.get())); handle uim; if(canvas_specific && canvas_specific->get_canvas()) { Canvas::Handle canvas=canvas_specific->get_canvas(); handle canvas_interface=static_cast(this)->find_canvas_interface(canvas); assert(canvas_interface); uim=canvas_interface->get_ui_interface(); } else uim=get_ui_interface(); if(!redo_(uim)) { uim->error(redo_action_stack_.front()->get_name()+": "+_("Failed to redo.")); inuse=false; return false; } inuse=false; // If the action has "dirtied" the preview, signal it. if(0)if(action->is_active() && canvas_specific && canvas_specific->is_dirty()) { Canvas::Handle canvas=canvas_specific->get_canvas(); if(!group_stack_.empty()) group_stack_.front()->request_redraw(canvas_specific->get_canvas_interface()); else { handle canvas_interface=static_cast(this)->find_canvas_interface(canvas); assert(canvas_interface); //DEBUGPOINT(); //canvas_interface->signal_dirty_preview()(); } } return true; } void Action::System::inc_action_count()const { action_count_++; if(action_count_==1) signal_unsaved_status_changed_(true); if(!action_count_) signal_unsaved_status_changed_(false); } void Action::System::dec_action_count()const { action_count_--; if(action_count_==-1) signal_unsaved_status_changed_(true); if(!action_count_) signal_unsaved_status_changed_(false); } void Action::System::reset_action_count()const { if(!action_count_) return; action_count_=0; signal_unsaved_status_changed_(false); } void Action::System::clear_undo_stack() { if(undo_action_stack_.empty()) return; undo_action_stack_.clear(); signal_undo_status_(false); signal_undo_stack_cleared_(); } void Action::System::clear_redo_stack() { if(redo_action_stack_.empty()) return; redo_action_stack_.clear(); signal_redo_status_(false); signal_redo_stack_cleared_(); } bool Action::System::set_action_status(etl::handle action, bool x) { Stack::iterator iter; int failed=false; if(action->is_active()==x) return true; handle cur_pos=undo_action_stack_.front(); Action::CanvasSpecific* canvas_specific(dynamic_cast(action.get())); handle uim=new ConfidentUIInterface(); iter=find(undo_action_stack_.begin(),undo_action_stack_.end(),action); if(iter!=undo_action_stack_.end()) { while(undo_action_stack_.front()!=action) { if(!undo_(uim)) { return false; } } if(!undo_(uim)) { return false; } action->set_active(x); if(redo_(get_ui_interface())) { signal_action_status_changed_(action); } else { action->set_active(!x); failed=true; } while(undo_action_stack_.front()!=cur_pos) { if(!redo_(uim)) { redo_action_stack_.front()->set_active(false); signal_action_status_changed_(redo_action_stack_.front()); } } if(!failed && canvas_specific && canvas_specific->is_dirty()) { Canvas::Handle canvas=canvas_specific->get_canvas(); handle canvas_interface=static_cast(this)->find_canvas_interface(canvas); assert(canvas_interface); //DEBUGPOINT(); //canvas_interface->signal_dirty_preview()(); } return true; } iter=find(redo_action_stack_.begin(),redo_action_stack_.end(),action); if(iter!=redo_action_stack_.end()) { action->set_active(x); signal_action_status_changed_(action); if(canvas_specific && canvas_specific->is_dirty()) { Canvas::Handle canvas=canvas_specific->get_canvas(); handle canvas_interface=static_cast(this)->find_canvas_interface(canvas); assert(canvas_interface); DEBUGPOINT(); //canvas_interface->signal_dirty_preview()(); } return true; } return false; } Action::PassiveGrouper::PassiveGrouper(etl::loose_handle instance_,synfig::String name_): instance_(instance_), name_(name_), redraw_requested_(false), depth_(0) { // Add this group onto the group stack instance_->group_stack_.push_front(this); } void Action::PassiveGrouper::request_redraw(handle x) { /* DEBUGPOINT(); if(instance_->group_stack_.empty()) { if(x!=canvas_interface_) { DEBUGPOINT(); x->signal_dirty_preview()(); } redraw_requested_=false; } else { DEBUGPOINT(); if(instance_->group_stack_.back()==this) { DEBUGPOINT(); redraw_requested_=true; } else { DEBUGPOINT(); instance_->group_stack_.back()->request_redraw(x); redraw_requested_=false; } DEBUGPOINT(); } DEBUGPOINT(); */ if(x) { redraw_requested_=true; canvas_interface_=x; } } Action::PassiveGrouper::~PassiveGrouper() { assert(instance_->group_stack_.front()==this); // Remove this group from the group stack instance_->group_stack_.pop_front(); handle group; if(depth_==1) { handle action(instance_->undo_action_stack_.front()); group=handle::cast_dynamic(action); if(group) { // If the only action inside of us is a group, // then we should rename the group to our name. group->set_name(name_); } else { Action::CanvasSpecific* canvas_specific(dynamic_cast(action.get())); if(0)if(canvas_specific && canvas_specific->is_dirty() && canvas_specific->get_canvas_interface()) { if(instance_->group_stack_.empty()) request_redraw(canvas_specific->get_canvas_interface()); } } if(instance_->group_stack_.empty()) { instance_->inc_action_count(); instance_->signal_new_action()(instance_->undo_action_stack_.front()); } else instance_->group_stack_.front()->inc_depth(); } else if(depth_>0) { group=new Action::Group(name_); for(int i=0;i action(instance_->undo_action_stack_.front()); Action::CanvasSpecific* canvas_specific(dynamic_cast(action.get())); if(0)if(canvas_specific && canvas_specific->is_dirty()) { group->set_dirty(true); group->set_canvas(canvas_specific->get_canvas()); group->set_canvas_interface(canvas_specific->get_canvas_interface()); } // Copy the action from the undo stack to the group group->add_action_front(action); // Remove the action from the undo stack instance_->undo_action_stack_.pop_front(); } // Push the group onto the stack instance_->undo_action_stack_.push_front(group); if(0)if(group->is_dirty()) request_redraw(group->get_canvas_interface()); // group->get_canvas_interface()->signal_dirty_preview()(); if(instance_->group_stack_.empty()) { instance_->inc_action_count(); instance_->signal_new_action()(instance_->undo_action_stack_.front()); } else instance_->group_stack_.front()->inc_depth(); } if(0)if(redraw_requested_) { if(instance_->group_stack_.empty()) { assert(canvas_interface_); DEBUGPOINT(); canvas_interface_->signal_dirty_preview()(); } else { instance_->group_stack_.front()->request_redraw(canvas_interface_); redraw_requested_=false; } } } void Action::PassiveGrouper::cancel() { bool error=false; // Cancel any groupers that may be on top of us first //while(instance_->group_stack_.front()!=this) // instance_->group_stack_.front()->cancel(); synfig::warning("Cancel depth: %d",depth_); while(depth_) if(!instance_->undo()) { error=true; break; } if(error) instance_->get_ui_interface()->error(_("State restore failure")); else redraw_requested_=false; }