Implements PXEGeek's http://wiki.synfig.com/Wish_list entry: "Optionally display...
[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 #endif
37
38 /* === U S I N G =========================================================== */
39
40 using namespace std;
41 using namespace etl;
42 using namespace synfigapp;
43 using namespace synfig;
44
45 /* === M A C R O S ========================================================= */
46
47 /* === G L O B A L S ======================================================= */
48
49 /* === P R O C E D U R E S ================================================= */
50
51 /* === M E T H O D S ======================================================= */
52
53
54
55 Action::System::System():
56         action_count_(0)
57 {
58         unset_ui_interface();
59         clear_redo_stack_on_new_action_=false;
60 }
61
62 Action::System::~System()
63 {
64 }
65
66 bool
67 Action::System::perform_action(handle<Action::Base> action)
68 {
69         handle<UIInterface> uim(get_ui_interface());
70
71         assert(action);
72
73         if(!action->is_ready())
74         {
75                 uim->error(action->get_name()+": "+_("Action is not ready."));
76                 return false;
77         }
78
79         most_recent_action_=action;
80
81         static bool inuse=false;
82
83         if(inuse) return false;
84
85         inuse=true;
86         try {
87
88         assert(action);
89
90         Action::CanvasSpecific* canvas_specific(dynamic_cast<Action::CanvasSpecific*>(action.get()));
91
92         if(canvas_specific && canvas_specific->get_canvas())
93         {
94                 handle<CanvasInterface> canvas_interface=static_cast<Instance*>(this)->find_canvas_interface(canvas_specific->get_canvas());
95                 assert(canvas_interface);
96                 uim=canvas_interface->get_ui_interface();
97         }
98
99         handle<Action::Undoable> undoable_action=handle<Action::Undoable>::cast_dynamic(action);
100
101         // If we cannot undo this action, make sure
102         // that the user knows this.
103         if(!undoable_action)
104         {
105                 if(uim->yes_no(
106                         action->get_name(),
107                         _("This action cannot be undone! Are you sure you want to continue?"),
108                         UIInterface::RESPONSE_NO
109                         ) == UIInterface::RESPONSE_NO
110                 )
111                         return false;
112                 else
113                 {
114                         // Because this action cannot be undone,
115                         // we need to clear the undo stack
116                         clear_undo_stack();
117                 }
118         }
119         else
120                 assert(undoable_action->is_active());
121
122         // Perform the action
123         try { action->perform(); }
124         catch(Action::Error err)
125         {
126                 uim->task(action->get_name()+' '+_("Failed"));
127                 inuse=false;
128
129                 if(err.get_type()!=Action::Error::TYPE_UNABLE)
130                 {
131                         if(err.get_desc().empty())
132                                 uim->error(action->get_name()+": "+strprintf("%d",err.get_type()));
133                         else
134                                 uim->error(action->get_name()+": "+err.get_desc());
135                 }
136
137                 // If action failed for whatever reason, just return false and do
138                 // not add the action onto the list
139                 return false;
140         }
141         catch(std::exception err)
142         {
143                 uim->task(action->get_name()+' '+_("Failed"));
144                 inuse=false;
145
146                 uim->error(action->get_name()+": "+err.what());
147
148                 // If action failed for whatever reason, just return false and do
149                 // not add the action onto the list
150                 return false;
151         }
152         catch(...)
153         {
154                 uim->task(action->get_name()+' '+_("Failed"));
155                 inuse=false;
156
157                 // If action failed for whatever reason, just return false and do
158                 // not add the action onto the list
159                 return false;
160         }
161
162         // Clear the redo stack
163         if(clear_redo_stack_on_new_action_)
164                 clear_redo_stack();
165
166         if(!group_stack_.empty())
167                 group_stack_.front()->inc_depth();
168         else
169                 inc_action_count();
170
171         // Push this action onto the action list if we can undo it
172         if(undoable_action)
173         {
174                 // If necessary, signal the change in status of undo
175                 if(undo_action_stack_.empty()) signal_undo_status_(true);
176
177                 // Add it to the list
178                 undo_action_stack_.push_front(undoable_action);
179
180                 // Signal that a new action has been added
181                 if(group_stack_.empty())
182                         signal_new_action()(undoable_action);
183         }
184
185         inuse=false;
186
187         uim->task(action->get_name()+' '+_("Successful"));
188
189         // If the action has "dirtied" the preview, signal it.
190         if(0)if(canvas_specific && canvas_specific->is_dirty())
191         {
192                 Canvas::Handle canvas=canvas_specific->get_canvas();
193                 if(!group_stack_.empty())
194                         group_stack_.front()->request_redraw(canvas_specific->get_canvas_interface());
195                 else
196                 {
197                         handle<CanvasInterface> canvas_interface=static_cast<Instance*>(this)->find_canvas_interface(canvas);
198                         assert(canvas_interface);
199                         DEBUGPOINT();
200                         //canvas_interface->signal_dirty_preview()();
201                 }
202         }
203
204         }catch(...) { inuse=false; throw; }
205
206         return true;
207 }
208
209 bool
210 synfigapp::Action::System::undo_(handle<UIInterface> uim)
211 {
212         handle<Action::Undoable> action(undo_action_stack().front());
213         most_recent_action_=action;
214
215         try { if(action->is_active()) action->undo(); }
216         catch(Action::Error err)
217         {
218                 if(err.get_type()!=Action::Error::TYPE_UNABLE)
219                 {
220                         if(err.get_desc().empty())
221                                 uim->error(action->get_name()+_(" (Undo): ")+strprintf("%d",err.get_type()));
222                         else
223                                 uim->error(action->get_name()+_(" (Undo): ")+err.get_desc());
224                 }
225
226                 return false;
227         }
228         catch(std::runtime_error x)
229         {
230                 uim->error(x.what());
231                 return false;
232         }
233         catch(...)
234         {
235                 return false;
236         }
237
238         dec_action_count();
239
240         if(redo_action_stack_.empty())  signal_redo_status()(true);
241
242         redo_action_stack_.push_front(undo_action_stack_.front());
243         undo_action_stack_.pop_front();
244
245         if(undo_action_stack_.empty())  signal_undo_status()(false);
246
247         if(!group_stack_.empty())
248                 group_stack_.front()->dec_depth();
249
250         signal_undo_();
251
252         return true;
253 }
254
255 bool
256 synfigapp::Action::System::undo()
257 {
258         //! \todo This function is not exception safe!
259         static bool inuse=false;
260
261         // If there is nothing on the action list, there is nothing to undo
262         if(undo_action_stack().empty() || inuse)
263                 return false;
264
265         handle<Action::Undoable> action=undo_action_stack().front();
266         Action::CanvasSpecific* canvas_specific(dynamic_cast<Action::CanvasSpecific*>(action.get()));
267
268         handle<UIInterface> uim;
269         if(canvas_specific && canvas_specific->get_canvas())
270         {
271                 Canvas::Handle canvas=canvas_specific->get_canvas();
272                 handle<CanvasInterface> canvas_interface=static_cast<Instance*>(this)->find_canvas_interface(canvas);
273                 assert(canvas_interface);
274                 uim=canvas_interface->get_ui_interface();
275         }
276         else
277                 uim=get_ui_interface();
278
279         inuse=true;
280
281         if(!undo_(uim))
282         {
283                 uim->error(undo_action_stack_.front()->get_name()+": "+_("Failed to undo."));
284                 inuse=false;
285                 return false;
286         }
287
288         inuse=false;
289
290         // If the action has "dirtied" the preview, signal it.
291         if(0)if(action->is_active() && canvas_specific && canvas_specific->is_dirty())
292         {
293                 Canvas::Handle canvas=canvas_specific->get_canvas();
294                 if(!group_stack_.empty())
295                         group_stack_.front()->request_redraw(canvas_specific->get_canvas_interface());
296                 else
297                 {
298                         handle<CanvasInterface> canvas_interface=static_cast<Instance*>(this)->find_canvas_interface(canvas);
299                         assert(canvas_interface);
300                         //DEBUGPOINT();
301                         //canvas_interface->signal_dirty_preview()();
302                 }
303         }
304
305         return true;
306 }
307
308 bool
309 Action::System::redo_(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                         //DEBUGPOINT();
400                         //canvas_interface->signal_dirty_preview()();
401                 }
402         }
403
404         return true;
405 }
406
407 void
408 Action::System::inc_action_count()const
409 {
410         action_count_++;
411         if(action_count_==1)
412                 signal_unsaved_status_changed_(true);
413         if(!action_count_)
414                 signal_unsaved_status_changed_(false);
415 }
416
417 void
418 Action::System::dec_action_count()const
419 {
420         action_count_--;
421         if(action_count_==-1)
422                 signal_unsaved_status_changed_(true);
423         if(!action_count_)
424                 signal_unsaved_status_changed_(false);
425 }
426
427 void
428 Action::System::reset_action_count()const
429 {
430         if(!action_count_)
431                 return;
432
433         action_count_=0;
434         signal_unsaved_status_changed_(false);
435 }
436
437 void
438 Action::System::clear_undo_stack()
439 {
440         if(undo_action_stack_.empty()) return;
441         undo_action_stack_.clear();
442         signal_undo_status_(false);
443         signal_undo_stack_cleared_();
444 }
445
446 void
447 Action::System::clear_redo_stack()
448 {
449         if(redo_action_stack_.empty()) return;
450         redo_action_stack_.clear();
451         signal_redo_status_(false);
452         signal_redo_stack_cleared_();
453 }
454
455 bool
456 Action::System::set_action_status(etl::handle<Action::Undoable> action, bool x)
457 {
458         Stack::iterator iter;
459         int failed=false;
460
461         if(action->is_active()==x)
462                 return true;
463
464         handle<Action::Undoable> cur_pos=undo_action_stack_.front();
465
466         Action::CanvasSpecific* canvas_specific(dynamic_cast<Action::CanvasSpecific*>(action.get()));
467
468         handle<UIInterface> uim=new ConfidentUIInterface();
469
470         iter=find(undo_action_stack_.begin(),undo_action_stack_.end(),action);
471         if(iter!=undo_action_stack_.end())
472         {
473                 while(undo_action_stack_.front()!=action)
474                 {
475                         if(!undo_(uim))
476                         {
477                                 return false;
478                         }
479                 }
480                 if(!undo_(uim))
481                 {
482                         return false;
483                 }
484
485                 action->set_active(x);
486
487                 if(redo_(get_ui_interface()))
488                 {
489                         signal_action_status_changed_(action);
490                 }
491                 else
492                 {
493                         action->set_active(!x);
494                         failed=true;
495                 }
496
497
498                 while(undo_action_stack_.front()!=cur_pos)
499                 {
500                         if(!redo_(uim))
501                         {
502                                 redo_action_stack_.front()->set_active(false);
503                                 signal_action_status_changed_(redo_action_stack_.front());
504                         }
505                 }
506
507                 if(!failed && canvas_specific && canvas_specific->is_dirty())
508                 {
509                         Canvas::Handle canvas=canvas_specific->get_canvas();
510                         handle<CanvasInterface> canvas_interface=static_cast<Instance*>(this)->find_canvas_interface(canvas);
511                         assert(canvas_interface);
512                         //DEBUGPOINT();
513                         //canvas_interface->signal_dirty_preview()();
514                 }
515
516                 return true;
517         }
518
519         iter=find(redo_action_stack_.begin(),redo_action_stack_.end(),action);
520         if(iter!=redo_action_stack_.end())
521         {
522                 action->set_active(x);
523                 signal_action_status_changed_(action);
524
525
526
527
528                 if(canvas_specific && canvas_specific->is_dirty())
529                 {
530                         Canvas::Handle canvas=canvas_specific->get_canvas();
531                         handle<CanvasInterface> canvas_interface=static_cast<Instance*>(this)->find_canvas_interface(canvas);
532                         assert(canvas_interface);
533                         DEBUGPOINT();
534                         //canvas_interface->signal_dirty_preview()();
535                 }
536
537                 return true;
538         }
539
540         return false;
541 }
542
543 Action::PassiveGrouper::PassiveGrouper(etl::loose_handle<Action::System> instance_,synfig::String name_):
544         instance_(instance_),
545         name_(name_),
546         redraw_requested_(false),
547         depth_(0)
548 {
549         // Add this group onto the group stack
550         instance_->group_stack_.push_front(this);
551 }
552
553 void
554 Action::PassiveGrouper::request_redraw(handle<CanvasInterface> x)
555 {
556 /*      DEBUGPOINT();
557         if(instance_->group_stack_.empty())
558         {
559                 if(x!=canvas_interface_)
560                 {
561                         DEBUGPOINT();
562                         x->signal_dirty_preview()();
563                 }
564
565                 redraw_requested_=false;
566         }
567         else
568         {
569                 DEBUGPOINT();
570                 if(instance_->group_stack_.back()==this)
571                 {
572                         DEBUGPOINT();
573                         redraw_requested_=true;
574                 }
575                 else
576                 {
577                         DEBUGPOINT();
578                         instance_->group_stack_.back()->request_redraw(x);
579                         redraw_requested_=false;
580                 }
581                 DEBUGPOINT();
582         }
583         DEBUGPOINT();
584 */
585         if(x)
586         {
587                 redraw_requested_=true;
588                 canvas_interface_=x;
589         }
590 }
591
592 Action::PassiveGrouper::~PassiveGrouper()
593 {
594         assert(instance_->group_stack_.front()==this);
595
596         // Remove this group from the group stack
597         instance_->group_stack_.pop_front();
598
599         handle<Action::Group> group;
600
601         if(depth_==1)
602         {
603                 handle<Action::Undoable> action(instance_->undo_action_stack_.front());
604
605                 group=handle<Action::Group>::cast_dynamic(action);
606
607                 if(group)
608                 {
609                         // If the only action inside of us is a group,
610                         // then we should rename the group to our name.
611                         group->set_name(name_);
612                 }
613                 else
614                 {
615                         Action::CanvasSpecific* canvas_specific(dynamic_cast<Action::CanvasSpecific*>(action.get()));
616
617                         if(0)if(canvas_specific && canvas_specific->is_dirty() && canvas_specific->get_canvas_interface())
618                         {
619                                 if(instance_->group_stack_.empty())
620                                         request_redraw(canvas_specific->get_canvas_interface());
621                         }
622                 }
623
624                 if(instance_->group_stack_.empty())
625                 {
626                         instance_->inc_action_count();
627                         instance_->signal_new_action()(instance_->undo_action_stack_.front());
628                 }
629                 else
630                         instance_->group_stack_.front()->inc_depth();
631
632         }
633         else
634         if(depth_>0)
635         {
636                 group=new Action::Group(name_);
637
638                 for(int i=0;i<depth_;i++)
639         //      for(;depth_;depth_--)
640                 {
641                         handle<Action::Undoable> action(instance_->undo_action_stack_.front());
642                         Action::CanvasSpecific* canvas_specific(dynamic_cast<Action::CanvasSpecific*>(action.get()));
643
644                         if(0)if(canvas_specific && canvas_specific->is_dirty())
645                         {
646                                 group->set_dirty(true);
647                                 group->set_canvas(canvas_specific->get_canvas());
648                                 group->set_canvas_interface(canvas_specific->get_canvas_interface());
649                         }
650
651                         // Copy the action from the undo stack to the group
652                         group->add_action_front(action);
653
654                         // Remove the action from the undo stack
655                         instance_->undo_action_stack_.pop_front();
656                 }
657
658                 // Push the group onto the stack
659                 instance_->undo_action_stack_.push_front(group);
660
661                 if(0)if(group->is_dirty())
662                         request_redraw(group->get_canvas_interface());
663                 //      group->get_canvas_interface()->signal_dirty_preview()();
664
665                 if(instance_->group_stack_.empty())
666                 {
667                         instance_->inc_action_count();
668                         instance_->signal_new_action()(instance_->undo_action_stack_.front());
669                 }
670                 else
671                         instance_->group_stack_.front()->inc_depth();
672         }
673
674         if(0)if(redraw_requested_)
675         {
676                 if(instance_->group_stack_.empty())
677                 {
678                         assert(canvas_interface_);
679                         DEBUGPOINT();
680                         canvas_interface_->signal_dirty_preview()();
681                 }
682                 else
683                 {
684                         instance_->group_stack_.front()->request_redraw(canvas_interface_);
685                         redraw_requested_=false;
686                 }
687         }
688 }
689
690 void
691 Action::PassiveGrouper::cancel()
692 {
693         bool error=false;
694
695         // Cancel any groupers that may be on top of us first
696         //while(instance_->group_stack_.front()!=this)
697         //      instance_->group_stack_.front()->cancel();
698
699         synfig::warning("Cancel depth: %d",depth_);
700
701         while(depth_)
702                 if(!instance_->undo())
703                 {
704                         error=true;
705                         break;
706                 }
707
708         if(error)
709                 instance_->get_ui_interface()->error(_("State restore failure"));
710         else
711                 redraw_requested_=false;
712 }