cc20a2bc06c3c5a1575f562adcf9277a927ee7a7
[synfig.git] /
1 /* === S Y N F I G ========================================================= */
2 /*!     \file action_system.cpp
3 **      \brief Template File
4 **
5 **      $Id$
6 **
7 **      \legal
8 **      Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
9 **
10 **      This package is free software; you can redistribute it and/or
11 **      modify it under the terms of the GNU General Public License as
12 **      published by the Free Software Foundation; either version 2 of
13 **      the License, or (at your option) any later version.
14 **
15 **      This package is distributed in the hope that it will be useful,
16 **      but WITHOUT ANY WARRANTY; without even the implied warranty of
17 **      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 **      General Public License for more details.
19 **      \endlegal
20 */
21 /* ========================================================================= */
22
23 /* === H E A D E R S ======================================================= */
24
25 #ifdef USING_PCH
26 #       include "pch.h"
27 #else
28 #ifdef HAVE_CONFIG_H
29 #       include <config.h>
30 #endif
31
32 #include "action_system.h"
33 #include "instance.h"
34 #include "canvasinterface.h"
35
36 #include "general.h"
37
38 #endif
39
40 /* === U S I N G =========================================================== */
41
42 using namespace std;
43 using namespace etl;
44 using namespace synfigapp;
45 using namespace synfig;
46
47 /* === M A C R O S ========================================================= */
48
49 /* === G L O B A L S ======================================================= */
50
51 /* === P R O C E D U R E S ================================================= */
52
53 /* === M E T H O D S ======================================================= */
54
55
56
57 Action::System::System():
58         action_count_(0)
59 {
60         unset_ui_interface();
61         clear_redo_stack_on_new_action_=false;
62 }
63
64 Action::System::~System()
65 {
66 }
67
68 bool
69 Action::System::perform_action(etl::handle<Action::Base> action)
70 {
71         handle<UIInterface> uim(get_ui_interface());
72
73         assert(action);
74
75         if(!action->is_ready())
76         {
77                 uim->error(action->get_name()+": "+_("Action is not ready."));
78                 return false;
79         }
80
81         most_recent_action_=action;
82
83         static bool inuse=false;
84
85         if(inuse) return false;
86
87         inuse=true;
88         try {
89
90         assert(action);
91
92         Action::CanvasSpecific* canvas_specific(dynamic_cast<Action::CanvasSpecific*>(action.get()));
93
94         if(canvas_specific && canvas_specific->get_canvas())
95         {
96                 handle<CanvasInterface> canvas_interface=static_cast<Instance*>(this)->find_canvas_interface(canvas_specific->get_canvas());
97                 assert(canvas_interface);
98                 uim=canvas_interface->get_ui_interface();
99         }
100
101         handle<Action::Undoable> undoable_action=handle<Action::Undoable>::cast_dynamic(action);
102
103         // If we cannot undo this action, make sure
104         // that the user knows this.
105         if(!undoable_action)
106         {
107                 if(uim->yes_no(
108                         action->get_name(),
109                         _("This action cannot be undone! Are you sure you want to continue?"),
110                         UIInterface::RESPONSE_NO
111                         ) == UIInterface::RESPONSE_NO
112                 )
113                         return false;
114                 else
115                 {
116                         // Because this action cannot be undone,
117                         // we need to clear the undo stack
118                         clear_undo_stack();
119                 }
120         }
121         else
122                 assert(undoable_action->is_active());
123
124         // Perform the action
125         try { action->perform(); }
126         catch(Action::Error err)
127         {
128                 uim->task(action->get_name()+' '+_("Failed"));
129                 inuse=false;
130
131                 if(err.get_type()!=Action::Error::TYPE_UNABLE)
132                 {
133                         if(err.get_desc().empty())
134                                 uim->error(action->get_name()+": "+strprintf("%d",err.get_type()));
135                         else
136                                 uim->error(action->get_name()+": "+err.get_desc());
137                 }
138
139                 // If action failed for whatever reason, just return false and do
140                 // not add the action onto the list
141                 return false;
142         }
143         catch(std::exception err)
144         {
145                 uim->task(action->get_name()+' '+_("Failed"));
146                 inuse=false;
147
148                 uim->error(action->get_name()+": "+err.what());
149
150                 // If action failed for whatever reason, just return false and do
151                 // not add the action onto the list
152                 return false;
153         }
154         catch(...)
155         {
156                 uim->task(action->get_name()+' '+_("Failed"));
157                 inuse=false;
158
159                 // If action failed for whatever reason, just return false and do
160                 // not add the action onto the list
161                 return false;
162         }
163
164         // Clear the redo stack
165         if(clear_redo_stack_on_new_action_)
166                 clear_redo_stack();
167
168         if(!group_stack_.empty())
169                 group_stack_.front()->inc_depth();
170         else
171                 inc_action_count();
172
173         // Push this action onto the action list if we can undo it
174         if(undoable_action)
175         {
176                 // If necessary, signal the change in status of undo
177                 if(undo_action_stack_.empty()) signal_undo_status_(true);
178
179                 // Add it to the list
180                 undo_action_stack_.push_front(undoable_action);
181
182                 // Signal that a new action has been added
183                 if(group_stack_.empty())
184                         signal_new_action()(undoable_action);
185         }
186
187         inuse=false;
188
189         uim->task(action->get_name()+' '+_("Successful"));
190
191         // If the action has "dirtied" the preview, signal it.
192         if(0)if(canvas_specific && canvas_specific->is_dirty())
193         {
194                 Canvas::Handle canvas=canvas_specific->get_canvas();
195                 if(!group_stack_.empty())
196                         group_stack_.front()->request_redraw(canvas_specific->get_canvas_interface());
197                 else
198                 {
199                         handle<CanvasInterface> canvas_interface=static_cast<Instance*>(this)->find_canvas_interface(canvas);
200                         assert(canvas_interface);
201                         //canvas_interface->signal_dirty_preview()();
202                 }
203         }
204
205         }catch(...) { inuse=false; throw; }
206
207         return true;
208 }
209
210 bool
211 synfigapp::Action::System::undo_(etl::handle<UIInterface> uim)
212 {
213         handle<Action::Undoable> action(undo_action_stack().front());
214         most_recent_action_=action;
215
216         try { if(action->is_active()) action->undo(); }
217         catch(Action::Error err)
218         {
219                 if(err.get_type()!=Action::Error::TYPE_UNABLE)
220                 {
221                         if(err.get_desc().empty())
222                                 uim->error(action->get_name()+_(" (Undo): ")+strprintf("%d",err.get_type()));
223                         else
224                                 uim->error(action->get_name()+_(" (Undo): ")+err.get_desc());
225                 }
226
227                 return false;
228         }
229         catch(std::runtime_error x)
230         {
231                 uim->error(x.what());
232                 return false;
233         }
234         catch(...)
235         {
236                 return false;
237         }
238
239         dec_action_count();
240
241         if(redo_action_stack_.empty())  signal_redo_status()(true);
242
243         redo_action_stack_.push_front(undo_action_stack_.front());
244         undo_action_stack_.pop_front();
245
246         if(undo_action_stack_.empty())  signal_undo_status()(false);
247
248         if(!group_stack_.empty())
249                 group_stack_.front()->dec_depth();
250
251         signal_undo_();
252
253         return true;
254 }
255
256 bool
257 synfigapp::Action::System::undo()
258 {
259         //! \todo This function is not exception safe!
260         static bool inuse=false;
261
262         // If there is nothing on the action list, there is nothing to undo
263         if(undo_action_stack().empty() || inuse)
264                 return false;
265
266         handle<Action::Undoable> action=undo_action_stack().front();
267         Action::CanvasSpecific* canvas_specific(dynamic_cast<Action::CanvasSpecific*>(action.get()));
268
269         handle<UIInterface> uim;
270         if(canvas_specific && canvas_specific->get_canvas())
271         {
272                 Canvas::Handle canvas=canvas_specific->get_canvas();
273                 handle<CanvasInterface> canvas_interface=static_cast<Instance*>(this)->find_canvas_interface(canvas);
274                 assert(canvas_interface);
275                 uim=canvas_interface->get_ui_interface();
276         }
277         else
278                 uim=get_ui_interface();
279
280         inuse=true;
281
282         if(!undo_(uim))
283         {
284                 uim->error(undo_action_stack_.front()->get_name()+": "+_("Failed to undo."));
285                 inuse=false;
286                 return false;
287         }
288
289         inuse=false;
290
291         // If the action has "dirtied" the preview, signal it.
292         if(0)if(action->is_active() && canvas_specific && canvas_specific->is_dirty())
293         {
294                 Canvas::Handle canvas=canvas_specific->get_canvas();
295                 if(!group_stack_.empty())
296                         group_stack_.front()->request_redraw(canvas_specific->get_canvas_interface());
297                 else
298                 {
299                         handle<CanvasInterface> canvas_interface=static_cast<Instance*>(this)->find_canvas_interface(canvas);
300                         assert(canvas_interface);
301                         //canvas_interface->signal_dirty_preview()();
302                 }
303         }
304
305         return true;
306 }
307
308 bool
309 Action::System::redo_(etl::handle<UIInterface> uim)
310 {
311         handle<Action::Undoable> action(redo_action_stack().front());
312         most_recent_action_=action;
313
314         try { if(action->is_active()) action->perform(); }
315         catch(Action::Error err)
316         {
317                 if(err.get_type()!=Action::Error::TYPE_UNABLE)
318                 {
319                         if(err.get_desc().empty())
320                                 uim->error(action->get_name()+_(" (Redo): ")+strprintf("%d",err.get_type()));
321                         else
322                                 uim->error(action->get_name()+_(" (Redo): ")+err.get_desc());
323                 }
324
325                 return false;
326         }
327         catch(std::runtime_error x)
328         {
329                 uim->error(x.what());
330                 return false;
331         }
332         catch(...)
333         {
334                 return false;
335         }
336
337         inc_action_count();
338
339         if(undo_action_stack_.empty())  signal_undo_status()(true);
340
341         undo_action_stack_.push_front(redo_action_stack_.front());
342         redo_action_stack_.pop_front();
343
344         if(redo_action_stack_.empty())  signal_redo_status()(false);
345
346         if(!group_stack_.empty())
347                 group_stack_.front()->inc_depth();
348
349         signal_redo_();
350
351         return true;
352 }
353
354 bool
355 Action::System::redo()
356 {
357         //! \todo This function is not exception safe!
358         static bool inuse=false;
359
360         // If there is nothing on the action list, there is nothing to undo
361         if(redo_action_stack_.empty() || inuse)
362                 return false;
363
364         inuse=true;
365
366         handle<Action::Undoable> action=redo_action_stack().front();
367         Action::CanvasSpecific* canvas_specific(dynamic_cast<Action::CanvasSpecific*>(action.get()));
368
369         handle<UIInterface> uim;
370         if(canvas_specific && canvas_specific->get_canvas())
371         {
372                 Canvas::Handle canvas=canvas_specific->get_canvas();
373                 handle<CanvasInterface> canvas_interface=static_cast<Instance*>(this)->find_canvas_interface(canvas);
374                 assert(canvas_interface);
375                 uim=canvas_interface->get_ui_interface();
376         }
377         else
378                 uim=get_ui_interface();
379
380         if(!redo_(uim))
381         {
382                 uim->error(redo_action_stack_.front()->get_name()+": "+_("Failed to redo."));
383                 inuse=false;
384                 return false;
385         }
386
387         inuse=false;
388
389         // If the action has "dirtied" the preview, signal it.
390         if(0)if(action->is_active() && canvas_specific && canvas_specific->is_dirty())
391         {
392                 Canvas::Handle canvas=canvas_specific->get_canvas();
393                 if(!group_stack_.empty())
394                         group_stack_.front()->request_redraw(canvas_specific->get_canvas_interface());
395                 else
396                 {
397                         handle<CanvasInterface> canvas_interface=static_cast<Instance*>(this)->find_canvas_interface(canvas);
398                         assert(canvas_interface);
399                         //canvas_interface->signal_dirty_preview()();
400                 }
401         }
402
403         return true;
404 }
405
406 void
407 Action::System::inc_action_count()const
408 {
409         action_count_++;
410         if(action_count_==1)
411                 signal_unsaved_status_changed_(true);
412         if(!action_count_)
413                 signal_unsaved_status_changed_(false);
414 }
415
416 void
417 Action::System::dec_action_count()const
418 {
419         action_count_--;
420         if(action_count_==-1)
421                 signal_unsaved_status_changed_(true);
422         if(!action_count_)
423                 signal_unsaved_status_changed_(false);
424 }
425
426 void
427 Action::System::reset_action_count()const
428 {
429         if(!action_count_)
430                 return;
431
432         action_count_=0;
433         signal_unsaved_status_changed_(false);
434 }
435
436 void
437 Action::System::clear_undo_stack()
438 {
439         if(undo_action_stack_.empty()) return;
440         undo_action_stack_.clear();
441         signal_undo_status_(false);
442         signal_undo_stack_cleared_();
443 }
444
445 void
446 Action::System::clear_redo_stack()
447 {
448         if(redo_action_stack_.empty()) return;
449         redo_action_stack_.clear();
450         signal_redo_status_(false);
451         signal_redo_stack_cleared_();
452 }
453
454 bool
455 Action::System::set_action_status(etl::handle<Action::Undoable> action, bool x)
456 {
457         Stack::iterator iter;
458         int failed=false;
459
460         if(action->is_active()==x)
461                 return true;
462
463         handle<Action::Undoable> cur_pos=undo_action_stack_.front();
464
465         Action::CanvasSpecific* canvas_specific(dynamic_cast<Action::CanvasSpecific*>(action.get()));
466
467         handle<UIInterface> uim=new ConfidentUIInterface();
468
469         iter=find(undo_action_stack_.begin(),undo_action_stack_.end(),action);
470         if(iter!=undo_action_stack_.end())
471         {
472                 while(undo_action_stack_.front()!=action)
473                 {
474                         if(!undo_(uim))
475                         {
476                                 return false;
477                         }
478                 }
479                 if(!undo_(uim))
480                 {
481                         return false;
482                 }
483
484                 action->set_active(x);
485
486                 if(redo_(get_ui_interface()))
487                 {
488                         signal_action_status_changed_(action);
489                 }
490                 else
491                 {
492                         action->set_active(!x);
493                         failed=true;
494                 }
495
496
497                 while(undo_action_stack_.front()!=cur_pos)
498                 {
499                         if(!redo_(uim))
500                         {
501                                 redo_action_stack_.front()->set_active(false);
502                                 signal_action_status_changed_(redo_action_stack_.front());
503                         }
504                 }
505
506                 if(!failed && canvas_specific && canvas_specific->is_dirty())
507                 {
508                         Canvas::Handle canvas=canvas_specific->get_canvas();
509                         handle<CanvasInterface> canvas_interface=static_cast<Instance*>(this)->find_canvas_interface(canvas);
510                         assert(canvas_interface);
511                         //canvas_interface->signal_dirty_preview()();
512                 }
513
514                 return true;
515         }
516
517         iter=find(redo_action_stack_.begin(),redo_action_stack_.end(),action);
518         if(iter!=redo_action_stack_.end())
519         {
520                 action->set_active(x);
521                 signal_action_status_changed_(action);
522
523
524
525
526                 if(canvas_specific && canvas_specific->is_dirty())
527                 {
528                         Canvas::Handle canvas=canvas_specific->get_canvas();
529                         handle<CanvasInterface> canvas_interface=static_cast<Instance*>(this)->find_canvas_interface(canvas);
530                         assert(canvas_interface);
531                         //canvas_interface->signal_dirty_preview()();
532                 }
533
534                 return true;
535         }
536
537         return false;
538 }
539
540 Action::PassiveGrouper::PassiveGrouper(etl::loose_handle<System> instance_,synfig::String name_):
541         instance_(instance_),
542         name_(name_),
543         redraw_requested_(false),
544         depth_(0)
545 {
546         // Add this group onto the group stack
547         instance_->group_stack_.push_front(this);
548 }
549
550 void
551 Action::PassiveGrouper::request_redraw(etl::handle<CanvasInterface> x)
552 {
553 /*      if(instance_->group_stack_.empty())
554         {
555                 if(x!=canvas_interface_)
556                 {
557                         x->signal_dirty_preview()();
558                 }
559
560                 redraw_requested_=false;
561         }
562         else
563         {
564                 if(instance_->group_stack_.back()==this)
565                 {
566                         redraw_requested_=true;
567                 }
568                 else
569                 {
570                         instance_->group_stack_.back()->request_redraw(x);
571                         redraw_requested_=false;
572                 }
573         }
574 */
575         if(x)
576         {
577                 redraw_requested_=true;
578                 canvas_interface_=x;
579         }
580 }
581
582 Action::PassiveGrouper::~PassiveGrouper()
583 {
584         assert(instance_->group_stack_.front()==this);
585
586         // Remove this group from the group stack
587         instance_->group_stack_.pop_front();
588
589         handle<Action::Group> group;
590
591         if(depth_==1)
592         {
593                 handle<Action::Undoable> action(instance_->undo_action_stack_.front());
594
595                 group=handle<Action::Group>::cast_dynamic(action);
596
597                 if(group)
598                 {
599                         // If the only action inside of us is a group,
600                         // then we should rename the group to our name.
601                         group->set_name(name_);
602                 }
603                 else
604                 {
605                         Action::CanvasSpecific* canvas_specific(dynamic_cast<Action::CanvasSpecific*>(action.get()));
606
607                         if(0)if(canvas_specific && canvas_specific->is_dirty() && canvas_specific->get_canvas_interface())
608                         {
609                                 if(instance_->group_stack_.empty())
610                                         request_redraw(canvas_specific->get_canvas_interface());
611                         }
612                 }
613
614                 if(instance_->group_stack_.empty())
615                 {
616                         instance_->inc_action_count();
617                         instance_->signal_new_action()(instance_->undo_action_stack_.front());
618                 }
619                 else
620                         instance_->group_stack_.front()->inc_depth();
621
622         }
623         else
624         if(depth_>0)
625         {
626                 group=new Action::Group(name_);
627
628                 for(int i=0;i<depth_;i++)
629         //      for(;depth_;depth_--)
630                 {
631                         handle<Action::Undoable> action(instance_->undo_action_stack_.front());
632                         Action::CanvasSpecific* canvas_specific(dynamic_cast<Action::CanvasSpecific*>(action.get()));
633
634                         if(0)if(canvas_specific && canvas_specific->is_dirty())
635                         {
636                                 group->set_dirty(true);
637                                 group->set_canvas(canvas_specific->get_canvas());
638                                 group->set_canvas_interface(canvas_specific->get_canvas_interface());
639                         }
640
641                         // Copy the action from the undo stack to the group
642                         group->add_action_front(action);
643
644                         // Remove the action from the undo stack
645                         instance_->undo_action_stack_.pop_front();
646                 }
647
648                 // Push the group onto the stack
649                 instance_->undo_action_stack_.push_front(group);
650
651                 if(0)if(group->is_dirty())
652                         request_redraw(group->get_canvas_interface());
653                 //      group->get_canvas_interface()->signal_dirty_preview()();
654
655                 if(instance_->group_stack_.empty())
656                 {
657                         instance_->inc_action_count();
658                         instance_->signal_new_action()(instance_->undo_action_stack_.front());
659                 }
660                 else
661                         instance_->group_stack_.front()->inc_depth();
662         }
663
664         if(0)if(redraw_requested_)
665         {
666                 if(instance_->group_stack_.empty())
667                 {
668                         assert(canvas_interface_);
669                         canvas_interface_->signal_dirty_preview()();
670                 }
671                 else
672                 {
673                         instance_->group_stack_.front()->request_redraw(canvas_interface_);
674                         redraw_requested_=false;
675                 }
676         }
677 }
678
679 void
680 Action::PassiveGrouper::cancel()
681 {
682         bool error=false;
683
684         // Cancel any groupers that may be on top of us first
685         //while(instance_->group_stack_.front()!=this)
686         //      instance_->group_stack_.front()->cancel();
687
688         synfig::warning("Cancel depth: %d",depth_);
689
690         while(depth_)
691                 if(!instance_->undo())
692                 {
693                         error=true;
694                         break;
695                 }
696
697         if(error)
698                 instance_->get_ui_interface()->error(_("State restore failure"));
699         else
700                 redraw_requested_=false;
701 }