33f71f987e5a82536cb375bf3b2c6f35cc9aa6c7
[synfig.git] / synfig-studio / trunk / src / synfigapp / action_system.cpp
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                         DEBUGPOINT();
202                         //canvas_interface->signal_dirty_preview()();
203                 }
204         }
205
206         }catch(...) { inuse=false; throw; }
207
208         return true;
209 }
210
211 bool
212 synfigapp::Action::System::undo_(etl::handle<UIInterface> uim)
213 {
214         handle<Action::Undoable> action(undo_action_stack().front());
215         most_recent_action_=action;
216
217         try { if(action->is_active()) action->undo(); }
218         catch(Action::Error err)
219         {
220                 if(err.get_type()!=Action::Error::TYPE_UNABLE)
221                 {
222                         if(err.get_desc().empty())
223                                 uim->error(action->get_name()+_(" (Undo): ")+strprintf("%d",err.get_type()));
224                         else
225                                 uim->error(action->get_name()+_(" (Undo): ")+err.get_desc());
226                 }
227
228                 return false;
229         }
230         catch(std::runtime_error x)
231         {
232                 uim->error(x.what());
233                 return false;
234         }
235         catch(...)
236         {
237                 return false;
238         }
239
240         dec_action_count();
241
242         if(redo_action_stack_.empty())  signal_redo_status()(true);
243
244         redo_action_stack_.push_front(undo_action_stack_.front());
245         undo_action_stack_.pop_front();
246
247         if(undo_action_stack_.empty())  signal_undo_status()(false);
248
249         if(!group_stack_.empty())
250                 group_stack_.front()->dec_depth();
251
252         signal_undo_();
253
254         return true;
255 }
256
257 bool
258 synfigapp::Action::System::undo()
259 {
260         //! \todo This function is not exception safe!
261         static bool inuse=false;
262
263         // If there is nothing on the action list, there is nothing to undo
264         if(undo_action_stack().empty() || inuse)
265                 return false;
266
267         handle<Action::Undoable> action=undo_action_stack().front();
268         Action::CanvasSpecific* canvas_specific(dynamic_cast<Action::CanvasSpecific*>(action.get()));
269
270         handle<UIInterface> uim;
271         if(canvas_specific && canvas_specific->get_canvas())
272         {
273                 Canvas::Handle canvas=canvas_specific->get_canvas();
274                 handle<CanvasInterface> canvas_interface=static_cast<Instance*>(this)->find_canvas_interface(canvas);
275                 assert(canvas_interface);
276                 uim=canvas_interface->get_ui_interface();
277         }
278         else
279                 uim=get_ui_interface();
280
281         inuse=true;
282
283         if(!undo_(uim))
284         {
285                 uim->error(undo_action_stack_.front()->get_name()+": "+_("Failed to undo."));
286                 inuse=false;
287                 return false;
288         }
289
290         inuse=false;
291
292         // If the action has "dirtied" the preview, signal it.
293         if(0)if(action->is_active() && canvas_specific && canvas_specific->is_dirty())
294         {
295                 Canvas::Handle canvas=canvas_specific->get_canvas();
296                 if(!group_stack_.empty())
297                         group_stack_.front()->request_redraw(canvas_specific->get_canvas_interface());
298                 else
299                 {
300                         handle<CanvasInterface> canvas_interface=static_cast<Instance*>(this)->find_canvas_interface(canvas);
301                         assert(canvas_interface);
302                         //DEBUGPOINT();
303                         //canvas_interface->signal_dirty_preview()();
304                 }
305         }
306
307         return true;
308 }
309
310 bool
311 Action::System::redo_(etl::handle<UIInterface> uim)
312 {
313         handle<Action::Undoable> action(redo_action_stack().front());
314         most_recent_action_=action;
315
316         try { if(action->is_active()) action->perform(); }
317         catch(Action::Error err)
318         {
319                 if(err.get_type()!=Action::Error::TYPE_UNABLE)
320                 {
321                         if(err.get_desc().empty())
322                                 uim->error(action->get_name()+_(" (Redo): ")+strprintf("%d",err.get_type()));
323                         else
324                                 uim->error(action->get_name()+_(" (Redo): ")+err.get_desc());
325                 }
326
327                 return false;
328         }
329         catch(std::runtime_error x)
330         {
331                 uim->error(x.what());
332                 return false;
333         }
334         catch(...)
335         {
336                 return false;
337         }
338
339         inc_action_count();
340
341         if(undo_action_stack_.empty())  signal_undo_status()(true);
342
343         undo_action_stack_.push_front(redo_action_stack_.front());
344         redo_action_stack_.pop_front();
345
346         if(redo_action_stack_.empty())  signal_redo_status()(false);
347
348         if(!group_stack_.empty())
349                 group_stack_.front()->inc_depth();
350
351         signal_redo_();
352
353         return true;
354 }
355
356 bool
357 Action::System::redo()
358 {
359         //! \todo This function is not exception safe!
360         static bool inuse=false;
361
362         // If there is nothing on the action list, there is nothing to undo
363         if(redo_action_stack_.empty() || inuse)
364                 return false;
365
366         inuse=true;
367
368         handle<Action::Undoable> action=redo_action_stack().front();
369         Action::CanvasSpecific* canvas_specific(dynamic_cast<Action::CanvasSpecific*>(action.get()));
370
371         handle<UIInterface> uim;
372         if(canvas_specific && canvas_specific->get_canvas())
373         {
374                 Canvas::Handle canvas=canvas_specific->get_canvas();
375                 handle<CanvasInterface> canvas_interface=static_cast<Instance*>(this)->find_canvas_interface(canvas);
376                 assert(canvas_interface);
377                 uim=canvas_interface->get_ui_interface();
378         }
379         else
380                 uim=get_ui_interface();
381
382         if(!redo_(uim))
383         {
384                 uim->error(redo_action_stack_.front()->get_name()+": "+_("Failed to redo."));
385                 inuse=false;
386                 return false;
387         }
388
389         inuse=false;
390
391         // If the action has "dirtied" the preview, signal it.
392         if(0)if(action->is_active() && canvas_specific && canvas_specific->is_dirty())
393         {
394                 Canvas::Handle canvas=canvas_specific->get_canvas();
395                 if(!group_stack_.empty())
396                         group_stack_.front()->request_redraw(canvas_specific->get_canvas_interface());
397                 else
398                 {
399                         handle<CanvasInterface> canvas_interface=static_cast<Instance*>(this)->find_canvas_interface(canvas);
400                         assert(canvas_interface);
401                         //DEBUGPOINT();
402                         //canvas_interface->signal_dirty_preview()();
403                 }
404         }
405
406         return true;
407 }
408
409 void
410 Action::System::inc_action_count()const
411 {
412         action_count_++;
413         if(action_count_==1)
414                 signal_unsaved_status_changed_(true);
415         if(!action_count_)
416                 signal_unsaved_status_changed_(false);
417 }
418
419 void
420 Action::System::dec_action_count()const
421 {
422         action_count_--;
423         if(action_count_==-1)
424                 signal_unsaved_status_changed_(true);
425         if(!action_count_)
426                 signal_unsaved_status_changed_(false);
427 }
428
429 void
430 Action::System::reset_action_count()const
431 {
432         if(!action_count_)
433                 return;
434
435         action_count_=0;
436         signal_unsaved_status_changed_(false);
437 }
438
439 void
440 Action::System::clear_undo_stack()
441 {
442         if(undo_action_stack_.empty()) return;
443         undo_action_stack_.clear();
444         signal_undo_status_(false);
445         signal_undo_stack_cleared_();
446 }
447
448 void
449 Action::System::clear_redo_stack()
450 {
451         if(redo_action_stack_.empty()) return;
452         redo_action_stack_.clear();
453         signal_redo_status_(false);
454         signal_redo_stack_cleared_();
455 }
456
457 bool
458 Action::System::set_action_status(etl::handle<Action::Undoable> action, bool x)
459 {
460         Stack::iterator iter;
461         int failed=false;
462
463         if(action->is_active()==x)
464                 return true;
465
466         handle<Action::Undoable> cur_pos=undo_action_stack_.front();
467
468         Action::CanvasSpecific* canvas_specific(dynamic_cast<Action::CanvasSpecific*>(action.get()));
469
470         handle<UIInterface> uim=new ConfidentUIInterface();
471
472         iter=find(undo_action_stack_.begin(),undo_action_stack_.end(),action);
473         if(iter!=undo_action_stack_.end())
474         {
475                 while(undo_action_stack_.front()!=action)
476                 {
477                         if(!undo_(uim))
478                         {
479                                 return false;
480                         }
481                 }
482                 if(!undo_(uim))
483                 {
484                         return false;
485                 }
486
487                 action->set_active(x);
488
489                 if(redo_(get_ui_interface()))
490                 {
491                         signal_action_status_changed_(action);
492                 }
493                 else
494                 {
495                         action->set_active(!x);
496                         failed=true;
497                 }
498
499
500                 while(undo_action_stack_.front()!=cur_pos)
501                 {
502                         if(!redo_(uim))
503                         {
504                                 redo_action_stack_.front()->set_active(false);
505                                 signal_action_status_changed_(redo_action_stack_.front());
506                         }
507                 }
508
509                 if(!failed && canvas_specific && canvas_specific->is_dirty())
510                 {
511                         Canvas::Handle canvas=canvas_specific->get_canvas();
512                         handle<CanvasInterface> canvas_interface=static_cast<Instance*>(this)->find_canvas_interface(canvas);
513                         assert(canvas_interface);
514                         //DEBUGPOINT();
515                         //canvas_interface->signal_dirty_preview()();
516                 }
517
518                 return true;
519         }
520
521         iter=find(redo_action_stack_.begin(),redo_action_stack_.end(),action);
522         if(iter!=redo_action_stack_.end())
523         {
524                 action->set_active(x);
525                 signal_action_status_changed_(action);
526
527
528
529
530                 if(canvas_specific && canvas_specific->is_dirty())
531                 {
532                         Canvas::Handle canvas=canvas_specific->get_canvas();
533                         handle<CanvasInterface> canvas_interface=static_cast<Instance*>(this)->find_canvas_interface(canvas);
534                         assert(canvas_interface);
535                         DEBUGPOINT();
536                         //canvas_interface->signal_dirty_preview()();
537                 }
538
539                 return true;
540         }
541
542         return false;
543 }
544
545 Action::PassiveGrouper::PassiveGrouper(etl::loose_handle<System> instance_,synfig::String name_):
546         instance_(instance_),
547         name_(name_),
548         redraw_requested_(false),
549         depth_(0)
550 {
551         // Add this group onto the group stack
552         instance_->group_stack_.push_front(this);
553 }
554
555 void
556 Action::PassiveGrouper::request_redraw(etl::handle<CanvasInterface> x)
557 {
558 /*      DEBUGPOINT();
559         if(instance_->group_stack_.empty())
560         {
561                 if(x!=canvas_interface_)
562                 {
563                         DEBUGPOINT();
564                         x->signal_dirty_preview()();
565                 }
566
567                 redraw_requested_=false;
568         }
569         else
570         {
571                 DEBUGPOINT();
572                 if(instance_->group_stack_.back()==this)
573                 {
574                         DEBUGPOINT();
575                         redraw_requested_=true;
576                 }
577                 else
578                 {
579                         DEBUGPOINT();
580                         instance_->group_stack_.back()->request_redraw(x);
581                         redraw_requested_=false;
582                 }
583                 DEBUGPOINT();
584         }
585         DEBUGPOINT();
586 */
587         if(x)
588         {
589                 redraw_requested_=true;
590                 canvas_interface_=x;
591         }
592 }
593
594 Action::PassiveGrouper::~PassiveGrouper()
595 {
596         assert(instance_->group_stack_.front()==this);
597
598         // Remove this group from the group stack
599         instance_->group_stack_.pop_front();
600
601         handle<Action::Group> group;
602
603         if(depth_==1)
604         {
605                 handle<Action::Undoable> action(instance_->undo_action_stack_.front());
606
607                 group=handle<Action::Group>::cast_dynamic(action);
608
609                 if(group)
610                 {
611                         // If the only action inside of us is a group,
612                         // then we should rename the group to our name.
613                         group->set_name(name_);
614                 }
615                 else
616                 {
617                         Action::CanvasSpecific* canvas_specific(dynamic_cast<Action::CanvasSpecific*>(action.get()));
618
619                         if(0)if(canvas_specific && canvas_specific->is_dirty() && canvas_specific->get_canvas_interface())
620                         {
621                                 if(instance_->group_stack_.empty())
622                                         request_redraw(canvas_specific->get_canvas_interface());
623                         }
624                 }
625
626                 if(instance_->group_stack_.empty())
627                 {
628                         instance_->inc_action_count();
629                         instance_->signal_new_action()(instance_->undo_action_stack_.front());
630                 }
631                 else
632                         instance_->group_stack_.front()->inc_depth();
633
634         }
635         else
636         if(depth_>0)
637         {
638                 group=new Action::Group(name_);
639
640                 for(int i=0;i<depth_;i++)
641         //      for(;depth_;depth_--)
642                 {
643                         handle<Action::Undoable> action(instance_->undo_action_stack_.front());
644                         Action::CanvasSpecific* canvas_specific(dynamic_cast<Action::CanvasSpecific*>(action.get()));
645
646                         if(0)if(canvas_specific && canvas_specific->is_dirty())
647                         {
648                                 group->set_dirty(true);
649                                 group->set_canvas(canvas_specific->get_canvas());
650                                 group->set_canvas_interface(canvas_specific->get_canvas_interface());
651                         }
652
653                         // Copy the action from the undo stack to the group
654                         group->add_action_front(action);
655
656                         // Remove the action from the undo stack
657                         instance_->undo_action_stack_.pop_front();
658                 }
659
660                 // Push the group onto the stack
661                 instance_->undo_action_stack_.push_front(group);
662
663                 if(0)if(group->is_dirty())
664                         request_redraw(group->get_canvas_interface());
665                 //      group->get_canvas_interface()->signal_dirty_preview()();
666
667                 if(instance_->group_stack_.empty())
668                 {
669                         instance_->inc_action_count();
670                         instance_->signal_new_action()(instance_->undo_action_stack_.front());
671                 }
672                 else
673                         instance_->group_stack_.front()->inc_depth();
674         }
675
676         if(0)if(redraw_requested_)
677         {
678                 if(instance_->group_stack_.empty())
679                 {
680                         assert(canvas_interface_);
681                         DEBUGPOINT();
682                         canvas_interface_->signal_dirty_preview()();
683                 }
684                 else
685                 {
686                         instance_->group_stack_.front()->request_redraw(canvas_interface_);
687                         redraw_requested_=false;
688                 }
689         }
690 }
691
692 void
693 Action::PassiveGrouper::cancel()
694 {
695         bool error=false;
696
697         // Cancel any groupers that may be on top of us first
698         //while(instance_->group_stack_.front()!=this)
699         //      instance_->group_stack_.front()->cancel();
700
701         synfig::warning("Cancel depth: %d",depth_);
702
703         while(depth_)
704                 if(!instance_->undo())
705                 {
706                         error=true;
707                         break;
708                 }
709
710         if(error)
711                 instance_->get_ui_interface()->error(_("State restore failure"));
712         else
713                 redraw_requested_=false;
714 }