Fix 1800324: closing a window while an animation is playing crashes studio, so don...
[synfig.git] / synfig-studio / trunk / src / gtkmm / instance.cpp
1 /* === S Y N F I G ========================================================= */
2 /*!     \file gtkmm/instance.cpp
3 **      \brief writeme
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 "instance.h"
33 #include <cassert>
34 #include <gtkmm/stock.h>
35 #include <gtkmm/image.h>
36 #include <iostream>
37 #include <gtkmm/button.h>
38 #include "canvasview.h"
39 #include "app.h"
40 #include <sigc++/signal.h>
41 #include <sigc++/adaptors/hide.h>
42 #include "toolbox.h"
43 #include "onemoment.h"
44
45 #include "autorecover.h"
46 #include <sigc++/retype_return.h>
47 #include <sigc++/retype.h>
48 //#include <sigc++/hide.h>
49 #include <synfig/valuenode_composite.h>
50 #include "widget_waypointmodel.h"
51 #include <gtkmm/actiongroup.h>
52 #include "iconcontroler.h"
53 #include <sys/stat.h>
54 #include <errno.h>
55
56 #endif
57
58 using namespace std;
59 using namespace etl;
60 using namespace synfig;
61 using namespace studio;
62 using namespace SigC;
63
64 /* === M A C R O S ========================================================= */
65
66 /* === G L O B A L S ======================================================= */
67
68 int studio::Instance::instance_count_=0;
69
70 /* === P R O C E D U R E S ================================================= */
71
72 /* === M E T H O D S ======================================================= */
73
74 Instance::Instance(Canvas::Handle canvas):
75         synfigapp::Instance             (canvas),
76 //      canvas_tree_store_              (Gtk::TreeStore::create(CanvasTreeModel())),
77 //      canvas_tree_store_              (Gtk::TreeStore::create()),
78         history_tree_store_             (HistoryTreeStore::create(this)),
79         undo_status_(false),
80         redo_status_(false)
81 {
82         CanvasTreeModel model;
83         canvas_tree_store_=Gtk::TreeStore::create(model);
84
85         id_=instance_count_++;
86
87         // Connect up all the signals
88         signal_filename_changed().connect(sigc::mem_fun(*this,&studio::Instance::update_all_titles));
89         signal_unsaved_status_changed().connect(sigc::hide(sigc::mem_fun(*this,&studio::Instance::update_all_titles)));
90         signal_undo_status().connect(sigc::mem_fun(*this,&studio::Instance::set_undo_status));
91         signal_redo_status().connect(sigc::mem_fun(*this,&studio::Instance::set_redo_status));
92
93         signal_saved().connect(
94                 sigc::hide_return(
95                         sigc::ptr_fun(
96                                 studio::AutoRecover::auto_backup
97                         )
98                 )
99         );
100
101         canvas_tree_store_=Gtk::TreeStore::create(canvas_tree_model);
102
103         refresh_canvas_tree();
104 }
105
106 Instance::~Instance()
107 {
108 }
109
110 int
111 Instance::get_visible_canvases()const
112 {
113         int count(0);
114         CanvasViewList::const_iterator iter;
115         for(iter=canvas_view_list_.begin();iter!=canvas_view_list_.end();++iter)
116                 if((*iter)->is_visible())
117                         count++;
118         return count;
119 }
120
121 handle<Instance>
122 Instance::create(Canvas::Handle canvas)
123 {
124         // Construct a new instance
125         handle<Instance> instance(new Instance(canvas));
126
127         // Add the new instance to the application's instance list
128         App::instance_list.push_back(instance);
129
130         // Set up the instance with the default UI manager
131         instance->synfigapp::Instance::set_ui_interface(App::get_ui_interface());
132
133         // Signal the new instance
134         App::signal_instance_created()(instance);
135
136         // And then make sure that is has been selected
137         App::set_selected_instance(instance);
138
139         // Create the initial window for the root canvas
140         instance->focus(canvas);
141
142         return instance;
143 }
144
145 handle<CanvasView>
146 Instance::find_canvas_view(Canvas::Handle canvas)
147 {
148         if(!canvas)
149                 return 0;
150
151         while(canvas->is_inline())
152                 canvas=canvas->parent();
153
154         CanvasViewList::iterator iter;
155
156         for(iter=canvas_view_list().begin();iter!=canvas_view_list().end();iter++)
157                 if((*iter)->get_canvas()==canvas)
158                         return *iter;
159
160         return CanvasView::create(this,canvas);
161 }
162
163 void
164 Instance::focus(Canvas::Handle canvas)
165 {
166         handle<CanvasView> canvas_view=find_canvas_view(canvas);
167         assert(canvas_view);
168         canvas_view->present();
169 }
170
171 void
172 Instance::set_undo_status(bool x)
173 {
174         undo_status_=x;
175         App::toolbox->update_undo_redo();
176         signal_undo_redo_status_changed()();
177 }
178
179 void
180 Instance::set_redo_status(bool x)
181 {
182         redo_status_=x;
183         App::toolbox->update_undo_redo();
184         signal_undo_redo_status_changed()();
185 }
186
187 bool
188 studio::Instance::save_as(const synfig::String &file_name)
189 {
190         if(synfigapp::Instance::save_as(file_name))
191         {
192                 // after changing the filename, update the render settings with the new filename
193                 list<handle<CanvasView> >::iterator iter;
194                 for(iter=canvas_view_list().begin();iter!=canvas_view_list().end();iter++)
195                         (*iter)->render_settings.set_entry_filename();
196                 App::add_recent_file(file_name);
197                 return true;
198         }
199         return false;
200 }
201
202 Instance::Status
203 studio::Instance::save()
204 {
205         // the filename will be set to "Synfig Animation 1" or some such when first created
206         // and will be changed to an absolute path once it has been saved
207         // so if it still begins with "Synfig Animation " then we need to ask where to save it
208         if(get_file_name().find(DEFAULT_FILENAME_PREFIX)==0)
209                 if (dialog_save_as())
210                         return STATUS_OK;
211                 else
212                         return STATUS_CANCEL;
213
214         if (synfigapp::Instance::save())
215                 return STATUS_OK;
216
217         App::dialog_error_blocking("Save - Error","Unable to save to '" + get_file_name() + "'");
218         return STATUS_ERROR;
219 }
220
221 bool
222 studio::Instance::dialog_save_as()
223 {
224         string filename=basename(get_file_name());
225         Canvas::Handle canvas(get_canvas());
226
227         {
228                 OneMoment one_moment;
229                 std::set<Node*>::iterator iter;
230                 for(iter=canvas->parent_set.begin();iter!=canvas->parent_set.end();++iter)
231                 {
232                         synfig::Node* node(*iter);
233                         for(;!node->parent_set.empty();node=*node->parent_set.begin())
234                         {
235                                 Layer::Handle parent_layer(dynamic_cast<Layer*>(node));
236                                 if(parent_layer && parent_layer->get_canvas()->get_root()!=get_canvas())
237                                 {
238                                         App::dialog_error_blocking("SaveAs - Error",
239                                                 "There is currently a bug when using \"SaveAs\"\n"
240                                                 "on a composition that is being referenced by other\n"
241                                                 "files that are currently open. Close these\n"
242                                                 "other files first before trying to use \"SaveAs\"."
243                                         );
244
245                                         return false;
246                                 }
247                                 if(parent_layer)
248                                         break;
249                         }
250                 }
251         }
252
253         // show the canvas' name if it has one, else its ID
254         while(App::dialog_save_file(_("Choose a Filename to Save As") +
255                                                                 String(" (") +
256                                                                 (canvas->get_name().empty()
257                                                                  ? canvas->get_id()
258                                                                  : canvas->get_name()) +
259                                                                 ") ...", filename))
260         {
261                 // If the filename still has wildcards, then we should
262                 // continue looking for the file we want
263                 if(find(filename.begin(),filename.end(),'*')!=filename.end())
264                         continue;
265
266                 std::string base = basename(filename);
267                 if(find(base.begin(),base.end(),'.')==base.end())
268                         filename+=".sifz";
269
270                 try
271                 {
272                         String ext(String(filename.begin()+filename.find_last_of('.')+1,filename.end()));
273                         if(ext!="sif" && ext!="sifz" && !App::dialog_yes_no(_("Unknown extension"),
274                                 _("You have given the file name an extension\nwhich I do not recognise. Are you sure this is what you want?")))
275                         {
276                                 continue;
277                         }
278                 }
279                 catch(...)
280                 {
281                         continue;
282                 }
283
284                 {
285                         struct stat     s;
286                         int stat_return = stat(filename.c_str(), &s);
287
288                         // if stat() fails with something other than 'file doesn't exist', there's been a real
289                         // error of some kind.  let's give up now and ask for a new path.
290                         if (stat_return == -1 && errno != ENOENT)
291                         {
292                                 perror(filename.c_str());
293                                 App::dialog_error_blocking("SaveAs - Error","Unable to check whether '" + filename + "' exists.");
294                                 continue;
295                         }
296
297                         // if the file exists and the user doesn't want to overwrite it, keep prompting for a filename
298                         if ((stat_return == 0) &&
299                                 !App::dialog_yes_no("File exists",
300                                                                         "A file named '" +
301                                                                         filename +
302                                                                         "' already exists.\n\n"
303                                                                         "Do you want to replace it with the file you are saving?"))
304                                 continue;
305                 }
306
307                 if(save_as(filename))
308                         return true;
309
310                 App::dialog_error_blocking("SaveAs - Error","Unable to save to '" + filename + "'");
311         }
312
313         return false;
314 }
315
316 void
317 Instance::update_all_titles()
318 {
319         list<handle<CanvasView> >::iterator iter;
320         for(iter=canvas_view_list().begin();iter!=canvas_view_list().end();iter++)
321                 (*iter)->update_title();
322 }
323
324 void
325 Instance::close()
326 {
327         // This will increase the reference count so we don't get DELETED
328         // until we are ready
329         handle<Instance> me(this);
330
331         // Make sure we aren't selected as the current instance
332         if(studio::App::get_selected_instance()==this)
333                 studio::App::set_selected_instance(0);
334
335         // Turn-off/clean-up auto recovery
336         studio::App::auto_recover->clear_backup(get_canvas());
337
338         // Remove us from the active instance list
339         std::list<etl::handle<studio::Instance> >::iterator iter;
340         for(iter=studio::App::instance_list.begin();iter!=studio::App::instance_list.end();iter++)
341                 if(*iter==this)
342                         break;
343         assert(iter!=studio::App::instance_list.end());
344         if(iter!=studio::App::instance_list.end())
345                 studio::App::instance_list.erase(iter);
346
347         // Send out a signal that we are being deleted
348         studio::App::signal_instance_deleted()(this);
349
350         // Hide all of the canvas views
351         for(std::list<etl::handle<CanvasView> >::iterator iter=canvas_view_list().begin();iter!=canvas_view_list().end();iter++)
352                 (*iter)->hide();
353
354         // Consume pending events before deleting the canvas views
355         while(studio::App::events_pending())studio::App::iteration(false);
356
357         // Delete all of the canvas views
358         canvas_view_list().clear();
359
360         // If there is another open instance to select,
361         // go ahead and do so. If not, never mind.
362         if(studio::App::instance_list.empty())
363         {
364                 studio::App::set_selected_canvas_view(0);
365                 studio::App::set_selected_instance(0);
366         }
367         else
368         {
369                 studio::App::set_selected_canvas_view(studio::App::instance_list.front()->canvas_view_list().front());
370                 //studio::App::set_selected_instance(studio::App::instance_list.front());
371         }
372 }
373
374
375 void
376 Instance::insert_canvas(Gtk::TreeRow row,Canvas::Handle canvas)
377 {
378         CanvasTreeModel canvas_tree_model;
379         assert(canvas);
380
381         row[canvas_tree_model.icon] = Gtk::Button().render_icon(Gtk::StockID("synfig-canvas"),Gtk::ICON_SIZE_SMALL_TOOLBAR);
382         row[canvas_tree_model.id] = canvas->get_id();
383         row[canvas_tree_model.name] = canvas->get_name();
384         if(canvas->is_root())
385                 row[canvas_tree_model.label] = basename(canvas->get_file_name());
386         else
387         if(!canvas->get_id().empty())
388                 row[canvas_tree_model.label] = canvas->get_id();
389         else
390         if(!canvas->get_name().empty())
391                 row[canvas_tree_model.label] = canvas->get_name();
392         else
393                 row[canvas_tree_model.label] = _("[Unnamed]");
394
395         row[canvas_tree_model.canvas] = canvas;
396         row[canvas_tree_model.is_canvas] = true;
397         row[canvas_tree_model.is_value_node] = false;
398
399         {
400                 synfig::Canvas::Children::iterator iter;
401                 synfig::Canvas::Children &children(canvas->children());
402
403                 for(iter=children.begin();iter!=children.end();iter++)
404                         insert_canvas(*(canvas_tree_store()->append(row.children())),*iter);
405         }
406
407         /*
408         if(!canvas->value_node_list().empty())
409         {
410                 Gtk::TreeRow valuenode_row = *(canvas_tree_store()->append(row.children()));
411
412                 valuenode_row[canvas_tree_model.label] = "<defs>";
413                 valuenode_row[canvas_tree_model.canvas] = canvas;
414                 valuenode_row[canvas_tree_model.is_canvas] = false;
415                 valuenode_row[canvas_tree_model.is_value_node] = false;
416
417                 synfig::ValueNodeList::iterator iter;
418                 synfig::ValueNodeList &value_node_list(canvas->value_node_list());
419
420                 for(iter=value_node_list.begin();iter!=value_node_list.end();iter++)
421                         insert_value_node(*(canvas_tree_store()->append(valuenode_row.children())),canvas,*iter);
422         }
423         */
424 }
425
426
427 /*
428 void
429 Instance::insert_value_node(Gtk::TreeRow row,Canvas::Handle canvas,etl::handle<synfig::ValueNode> value_node)
430 {
431         CanvasTreeModel canvas_tree_model;
432         assert(value_node);
433         assert(canvas);
434
435         row[canvas_tree_model.id] = value_node->get_id();
436         row[canvas_tree_model.name] = value_node->get_id();
437         row[canvas_tree_model.label] = value_node->get_id();
438         row[canvas_tree_model.canvas] = canvas;
439         row[canvas_tree_model.value_node] = value_node;
440         row[canvas_tree_model.is_canvas] = false;
441         row[canvas_tree_model.is_value_node] = true;
442         row[canvas_tree_model.icon] = Gtk::Button().render_icon(valuenode_icon(value_node),Gtk::ICON_SIZE_SMALL_TOOLBAR);
443 }
444 */
445
446 void
447 Instance::refresh_canvas_tree()
448 {
449         canvas_tree_store()->clear();
450         Gtk::TreeRow row = *(canvas_tree_store()->prepend());
451         insert_canvas(row,get_canvas());
452 }
453
454 void
455 Instance::dialog_cvs_commit()
456 {
457         calc_repository_info();
458         if(!in_repository())
459         {
460                 App::dialog_error_blocking(_("Error"),_("You must first add this composition to the repository"));
461                 return;
462         }
463         try
464         {
465                 string message;
466
467                 if(synfigapp::Instance::get_action_count())
468                 {
469                         if(!App::dialog_yes_no(_("CVS Commit"), _("This will save any changes you have made. Are you sure?")))
470                                 return;
471                         save();
472                 }
473
474                 if(!is_modified())
475                 {
476                         App::dialog_error_blocking(_("Error"),_("The local copy of the file hasn't been changed since the last update.\nNothing to commit!"));
477                         return;
478                 }
479
480                 if(!App::dialog_entry(_("CVS Commit"),_("Enter a log message describing the changes you have made"), message))
481                         return;
482
483                 OneMoment one_moment;
484                 cvs_commit(message);
485         }
486         catch(...)
487         {
488                 App::dialog_error_blocking(_("Error"),_("An error has occured when trying to COMMIT"));
489         }
490         update_all_titles();
491 }
492
493 void
494 Instance::dialog_cvs_add()
495 {
496         calc_repository_info();
497         if(in_repository())
498         {
499                 App::dialog_error_blocking(_("Error"),_("This composition has already been added to the repository"));
500                 return;
501         }
502         try
503         {
504                 string message;
505
506                 //if(!App::dialog_entry(_("CVS Add"),_("Enter a log message describing the file"), message))
507                 //      return;
508                 OneMoment one_moment;
509                 cvs_add();
510         }
511         catch(...)
512         {
513                 App::dialog_error_blocking(_("Error"),_("An error has occured when trying to ADD"));
514         }
515         update_all_titles();
516 }
517
518 void
519 Instance::dialog_cvs_update()
520 {
521         calc_repository_info();
522         if(!in_repository())
523         {
524                 App::dialog_error_blocking(_("Error"),_("This file is not under version control, so there is nothing to update from!"));
525                 return;
526         }
527         if(!is_updated())
528         {
529                 App::dialog_error_blocking(_("Info"),_("This file is up-to-date"));
530                 return;
531         }
532
533         try
534         {
535                 String filename(get_file_name());
536                 if(synfigapp::Instance::get_action_count())
537                 {
538                         if(!App::dialog_yes_no(_("CVS Update"), _("This will save any changes you have made. Are you sure?")))
539                                 return;
540                         save();
541                 }
542                 OneMoment one_moment;
543                 time_t oldtime=get_original_timestamp();
544                 cvs_update();
545                 calc_repository_info();
546                 // If something has been updated...
547                 if(oldtime!=get_original_timestamp())
548                 {
549                         revert();
550                 }
551         }
552         catch(...)
553         {
554                 App::dialog_error_blocking(_("Error"),_("An error has occured when trying to UPDATE"));
555         }
556         //update_all_titles();
557 }
558
559 void
560 Instance::dialog_cvs_revert()
561 {
562         calc_repository_info();
563         if(!in_repository())
564         {
565                 App::dialog_error_blocking(_("Error"),_("This file is not under version control, so there is nothing to revert to!"));
566                 return;
567         }
568         try
569         {
570                 String filename(get_file_name());
571                 if(!App::dialog_yes_no(_("CVS Revert"),
572                         _("This will abandon all changes you have made\nsince the last time you performed a commit\noperation. This cannot be undone! Are you sure\nyou want to do this?")
573                 ))
574                         return;
575
576                 OneMoment one_moment;
577
578                 // Remove the old file
579                 if(remove(get_file_name().c_str())!=0)
580                 {
581                         App::dialog_error_blocking(_("Error"),_("Unable to remove previous version"));
582                         return;
583                 }
584
585                 cvs_update();
586                 revert();
587         }
588         catch(...)
589         {
590                 App::dialog_error_blocking(_("Error"),_("An error has occured when trying to UPDATE"));
591         }
592         //update_all_titles();
593 }
594
595 void
596 Instance::_revert(Instance *instance)
597 {
598         OneMoment one_moment;
599
600         String filename(instance->get_file_name());
601
602         Canvas::Handle canvas(instance->get_canvas());
603
604         instance->close();
605
606         if(canvas->count()!=1)
607         {
608                 one_moment.hide();
609                 App::dialog_error_blocking(_("Error: Revert Failed"),_("The revert operation has failed. This can be due to it being\nreferenced by another composition that is already open, or\nbecause of an internal error in Synfig Studio. Try closing any\ncompositions that might reference this composition and try\nagain, or restart Synfig Studio."));
610                 one_moment.show();
611         }
612         canvas=0;
613
614         App::open(filename);
615 }
616
617 void
618 Instance::revert()
619 {
620         // Schedule a revert to occur in a few moments
621         Glib::signal_timeout().connect(
622                 sigc::bind_return(
623                         sigc::bind(
624                                 sigc::ptr_fun(&Instance::_revert),
625                                 this
626                         ),
627                         false
628                 )
629                 ,500
630         );
631 }
632
633 bool
634 Instance::safe_revert()
635 {
636         if(synfigapp::Instance::get_action_count())
637                 if(!App::dialog_yes_no(_("Revert to saved"), _("You will lose any changes you have made since your last save.\nAre you sure?")))
638                         return false;
639         revert();
640         return true;
641 }
642
643 bool
644 Instance::safe_close()
645 {
646         handle<CanvasView> canvas_view = find_canvas_view(get_canvas());
647         handle<synfigapp::UIInterface> uim=canvas_view->get_ui_interface();
648
649         // if the animation is currently playing, closing the window will cause a crash,
650         // so don't allow it
651         if (canvas_view->is_playing())
652         {
653                 canvas_view->present();
654                 App::dialog_error_blocking("Close Error", "The animation is currently playing so the window cannot be closed.");
655                 return false;
656         }
657         if(get_action_count())
658                 do
659                 {
660                         string str=strprintf(_("Would you like to save your changes to %s?"),basename(get_file_name()).c_str() );
661                         int answer=uim->yes_no_cancel(get_canvas()->get_name(),str,synfigapp::UIInterface::RESPONSE_YES);
662                         if(answer==synfigapp::UIInterface::RESPONSE_YES)
663                         {
664                                 enum Status status = save();
665                                 if (status == STATUS_OK) break;
666                                 else if (status == STATUS_CANCEL) return false;
667                         }
668                         if(answer==synfigapp::UIInterface::RESPONSE_NO)
669                                 break;
670                         if(answer==synfigapp::UIInterface::RESPONSE_CANCEL)
671                                 return false;
672                 } while (true);
673
674         if(is_modified())
675         {
676                 string str=strprintf(_("%s has changes not yet on the CVS repository.\nWould you like to commit these changes?"),basename(get_file_name()).c_str());
677                 int answer=uim->yes_no_cancel(get_canvas()->get_name(),str,synfigapp::UIInterface::RESPONSE_YES);
678
679                 if(answer==synfigapp::UIInterface::RESPONSE_YES)
680                         dialog_cvs_commit();
681                 if(answer==synfigapp::UIInterface::RESPONSE_CANCEL)
682                         return false;
683         }
684
685         close();
686
687         return true;
688 }
689
690
691 void
692 Instance::add_actions_to_group(const Glib::RefPtr<Gtk::ActionGroup>& action_group, synfig::String& ui_info,   const synfigapp::Action::ParamList &param_list, synfigapp::Action::Category category)const
693 {
694         synfigapp::Action::CandidateList candidate_list;
695         synfigapp::Action::CandidateList::iterator iter;
696
697         candidate_list=compile_candidate_list(param_list,category);
698
699         candidate_list.sort();
700
701         if(candidate_list.empty())
702                 synfig::warning("Action CandidateList is empty!");
703
704         for(iter=candidate_list.begin();iter!=candidate_list.end();++iter)
705         {
706                 Gtk::StockID stock_id(get_action_stock_id(*iter));
707
708                 if(!(iter->category&synfigapp::Action::CATEGORY_HIDDEN))
709                 {
710                         action_group->add(Gtk::Action::create(
711                                 "action-"+iter->name,
712                                 stock_id,
713                                 iter->local_name,iter->local_name
714                         ),
715                                 sigc::bind(
716                                         sigc::bind(
717                                                 sigc::mem_fun(
718                                                         *const_cast<studio::Instance*>(this),
719                                                         &studio::Instance::process_action
720                                                 ),
721                                                 param_list
722                                         ),
723                                         iter->name
724                                 )
725                         );
726                         ui_info+=strprintf("<menuitem action='action-%s' />",iter->name.c_str());
727                 }
728         }
729 }
730
731 void
732 Instance::add_actions_to_menu(Gtk::Menu *menu, const synfigapp::Action::ParamList &param_list,synfigapp::Action::Category category)const
733 {
734         synfigapp::Action::CandidateList candidate_list;
735         synfigapp::Action::CandidateList::iterator iter;
736
737         candidate_list=compile_candidate_list(param_list,category);
738
739         candidate_list.sort();
740
741         if(candidate_list.empty())
742                 synfig::warning("Action CandidateList is empty!");
743
744         for(iter=candidate_list.begin();iter!=candidate_list.end();++iter)
745         {
746                 if(!(iter->category&synfigapp::Action::CATEGORY_HIDDEN))
747                 {
748                         Gtk::Image* image(manage(new Gtk::Image()));
749                         Gtk::Stock::lookup(get_action_stock_id(*iter),Gtk::ICON_SIZE_MENU,*image);
750
751                         /*
752                         if(iter->task=="raise")
753                                 Gtk::Stock::lookup(Gtk::Stock::GO_UP,Gtk::ICON_SIZE_MENU,*image);
754                         else if(iter->task=="lower")
755                                 Gtk::Stock::lookup(Gtk::Stock::GO_DOWN,Gtk::ICON_SIZE_MENU,*image);
756                         else if(iter->task=="move_top")
757                                 Gtk::Stock::lookup(Gtk::Stock::GOTO_TOP,Gtk::ICON_SIZE_MENU,*image);
758                         else if(iter->task=="move_bottom")
759                                 Gtk::Stock::lookup(Gtk::Stock::GOTO_BOTTOM,Gtk::ICON_SIZE_MENU,*image);
760                         else if(iter->task=="remove")
761                                 Gtk::Stock::lookup(Gtk::Stock::DELETE,Gtk::ICON_SIZE_MENU,*image);
762                         else if(iter->task=="set_on")
763                                 Gtk::Stock::lookup(Gtk::Stock::YES,Gtk::ICON_SIZE_MENU,*image);
764                         else if(iter->task=="set_off")
765                                 Gtk::Stock::lookup(Gtk::Stock::NO,Gtk::ICON_SIZE_MENU,*image);
766                         else if(iter->task=="duplicate")
767                                 Gtk::Stock::lookup(Gtk::Stock::COPY,Gtk::ICON_SIZE_MENU,*image);
768                         else if(iter->task=="remove")
769                                 Gtk::Stock::lookup(Gtk::Stock::DELETE,Gtk::ICON_SIZE_MENU,*image);
770                         else
771                         {
772                                 Gtk::Stock::lookup(Gtk::StockID("synfig-"+iter->name),Gtk::ICON_SIZE_MENU,*image) ||
773                                 Gtk::Stock::lookup(Gtk::StockID("gtk-"+iter->task),Gtk::ICON_SIZE_MENU,*image) ||
774                                 Gtk::Stock::lookup(Gtk::StockID("synfig-"+iter->task),Gtk::ICON_SIZE_MENU,*image);
775                         }
776                         */
777                         menu->items().push_back(
778                                 Gtk::Menu_Helpers::ImageMenuElem(
779                                         iter->local_name,
780                                         *image,
781                                         sigc::bind(
782                                                 sigc::bind(
783                                                         sigc::mem_fun(
784                                                                 *const_cast<studio::Instance*>(this),
785                                                                 &studio::Instance::process_action
786                                                         ),
787                                                         param_list
788                                                 ),
789                                                 iter->name
790                                         )
791                                 )
792                         );
793                 }
794         }
795 }
796
797 void
798 Instance::add_actions_to_menu(Gtk::Menu *menu, const synfigapp::Action::ParamList &param_list,const synfigapp::Action::ParamList &param_list2,synfigapp::Action::Category category)const
799 {
800         synfigapp::Action::CandidateList candidate_list;
801         synfigapp::Action::CandidateList candidate_list2;
802
803         synfigapp::Action::CandidateList::iterator iter;
804
805         candidate_list=compile_candidate_list(param_list,category);
806         candidate_list2=compile_candidate_list(param_list2,category);
807
808         candidate_list.sort();
809
810         if(candidate_list.empty())
811                 synfig::warning("Action CandidateList is empty!");
812         if(candidate_list2.empty())
813                 synfig::warning("Action CandidateList2 is empty!");
814
815         // Seperate out the candidate lists so that there are no conflicts
816         for(iter=candidate_list.begin();iter!=candidate_list.end();++iter)
817         {
818                 synfigapp::Action::CandidateList::iterator iter2(candidate_list2.find(iter->name));
819                 if(iter2!=candidate_list2.end())
820                         candidate_list2.erase(iter2);
821         }
822
823         for(iter=candidate_list2.begin();iter!=candidate_list2.end();++iter)
824         {
825                 if(!(iter->category&synfigapp::Action::CATEGORY_HIDDEN))
826                 {
827                         Gtk::Image* image(manage(new Gtk::Image()));
828                         Gtk::Stock::lookup(get_action_stock_id(*iter),Gtk::ICON_SIZE_MENU,*image);
829
830 /*                      if(iter->task=="raise")
831                                 Gtk::Stock::lookup(Gtk::Stock::GO_UP,Gtk::ICON_SIZE_MENU,*image);
832                         else if(iter->task=="lower")
833                                 Gtk::Stock::lookup(Gtk::Stock::GO_DOWN,Gtk::ICON_SIZE_MENU,*image);
834                         else if(iter->task=="move_top")
835                                 Gtk::Stock::lookup(Gtk::Stock::GOTO_TOP,Gtk::ICON_SIZE_MENU,*image);
836                         else if(iter->task=="move_bottom")
837                                 Gtk::Stock::lookup(Gtk::Stock::GOTO_BOTTOM,Gtk::ICON_SIZE_MENU,*image);
838                         else if(iter->task=="remove")
839                                 Gtk::Stock::lookup(Gtk::Stock::DELETE,Gtk::ICON_SIZE_MENU,*image);
840                         else if(iter->task=="set_on")
841                                 Gtk::Stock::lookup(Gtk::Stock::YES,Gtk::ICON_SIZE_MENU,*image);
842                         else if(iter->task=="set_off")
843                                 Gtk::Stock::lookup(Gtk::Stock::NO,Gtk::ICON_SIZE_MENU,*image);
844                         else if(iter->task=="duplicate")
845                                 Gtk::Stock::lookup(Gtk::Stock::COPY,Gtk::ICON_SIZE_MENU,*image);
846                         else if(iter->task=="remove")
847                                 Gtk::Stock::lookup(Gtk::Stock::DELETE,Gtk::ICON_SIZE_MENU,*image);
848                         else
849                         {
850                                 Gtk::Stock::lookup(Gtk::StockID("synfig-"+iter->name),Gtk::ICON_SIZE_MENU,*image) ||
851                                 Gtk::Stock::lookup(Gtk::StockID("gtk-"+iter->task),Gtk::ICON_SIZE_MENU,*image) ||
852                                 Gtk::Stock::lookup(Gtk::StockID("synfig-"+iter->task),Gtk::ICON_SIZE_MENU,*image);
853                         }
854 */
855                         menu->items().push_back(
856                                 Gtk::Menu_Helpers::ImageMenuElem(
857                                         iter->local_name,
858                                         *image,
859                                         sigc::bind(
860                                                 sigc::bind(
861                                                         sigc::mem_fun(
862                                                                 *const_cast<studio::Instance*>(this),
863                                                                 &studio::Instance::process_action
864                                                         ),
865                                                         param_list2
866                                                 ),
867                                                 iter->name
868                                         )
869                                 )
870                         );
871                 }
872         }
873
874         for(iter=candidate_list.begin();iter!=candidate_list.end();++iter)
875         {
876                 if(!(iter->category&synfigapp::Action::CATEGORY_HIDDEN))
877                 {
878                         Gtk::Image* image(manage(new Gtk::Image()));
879                         Gtk::Stock::lookup(get_action_stock_id(*iter),Gtk::ICON_SIZE_MENU,*image);
880 /*                      if(iter->task=="raise")
881                                 Gtk::Stock::lookup(Gtk::Stock::GO_UP,Gtk::ICON_SIZE_MENU,*image);
882                         else if(iter->task=="lower")
883                                 Gtk::Stock::lookup(Gtk::Stock::GO_DOWN,Gtk::ICON_SIZE_MENU,*image);
884                         else if(iter->task=="move_top")
885                                 Gtk::Stock::lookup(Gtk::Stock::GOTO_TOP,Gtk::ICON_SIZE_MENU,*image);
886                         else if(iter->task=="move_bottom")
887                                 Gtk::Stock::lookup(Gtk::Stock::GOTO_BOTTOM,Gtk::ICON_SIZE_MENU,*image);
888                         else if(iter->task=="remove")
889                                 Gtk::Stock::lookup(Gtk::Stock::DELETE,Gtk::ICON_SIZE_MENU,*image);
890                         else if(iter->task=="set_on")
891                                 Gtk::Stock::lookup(Gtk::Stock::YES,Gtk::ICON_SIZE_MENU,*image);
892                         else if(iter->task=="set_off")
893                                 Gtk::Stock::lookup(Gtk::Stock::NO,Gtk::ICON_SIZE_MENU,*image);
894                         else if(iter->task=="duplicate")
895                                 Gtk::Stock::lookup(Gtk::Stock::COPY,Gtk::ICON_SIZE_MENU,*image);
896                         else if(iter->task=="remove")
897                                 Gtk::Stock::lookup(Gtk::Stock::DELETE,Gtk::ICON_SIZE_MENU,*image);
898                         else
899                         {
900                                 Gtk::Stock::lookup(Gtk::StockID("synfig-"+iter->name),Gtk::ICON_SIZE_MENU,*image) ||
901                                 Gtk::Stock::lookup(Gtk::StockID("gtk-"+iter->task),Gtk::ICON_SIZE_MENU,*image) ||
902                                 Gtk::Stock::lookup(Gtk::StockID("synfig-"+iter->task),Gtk::ICON_SIZE_MENU,*image);
903                         }
904 */
905                         menu->items().push_back(
906                                 Gtk::Menu_Helpers::ImageMenuElem(
907                                         iter->local_name,
908                                         *image,
909                                         sigc::bind(
910                                                 sigc::bind(
911                                                         sigc::mem_fun(
912                                                                 *const_cast<studio::Instance*>(this),
913                                                                 &studio::Instance::process_action
914                                                         ),
915                                                         param_list
916                                                 ),
917                                                 iter->name
918                                         )
919                                 )
920                         );
921                 }
922         }
923 }
924
925 void
926 Instance::process_action(String name, synfigapp::Action::ParamList param_list)
927 {
928         assert(synfigapp::Action::book().count(name));
929
930         synfigapp::Action::BookEntry entry(synfigapp::Action::book().find(name)->second);
931
932         synfigapp::Action::Handle action(entry.factory());
933
934         if(!action)
935         {
936                 synfig::error("Bad Action");
937                 return;
938         }
939
940         action->set_param_list(param_list);
941
942         synfigapp::Action::ParamVocab param_vocab(entry.get_param_vocab());
943         synfigapp::Action::ParamVocab::const_iterator iter;
944
945         for(iter=param_vocab.begin();iter!=param_vocab.end();++iter)
946         {
947                 if(!iter->get_mutual_exclusion().empty() && param_list.count(iter->get_mutual_exclusion()))
948                         continue;
949
950                 // If the parameter is optionally user-supplied,
951                 // and has not been already provided in the param_list,
952                 // then we should go ahead and see if we can
953                 // provide that data.
954                 if(iter->get_user_supplied() && param_list.count(iter->get_name())==0)
955                 {
956                         switch(iter->get_type())
957                         {
958                         case synfigapp::Action::Param::TYPE_STRING:
959                         {
960                                 String str;
961                                 if(!studio::App::dialog_entry(entry.local_name, iter->get_local_name()+":"+iter->get_desc(),str))
962                                         return;
963                                 action->set_param(iter->get_name(),str);
964                                 break;
965                         }
966                         default:
967                                 synfig::error("Unsupported user-supplied action parameter");
968                                 return;
969                                 break;
970                         }
971                 }
972         }
973
974         if(!action->is_ready())
975         {
976                 synfig::error("Action not ready");
977                 return;
978         }
979
980         perform_action(action);
981 }
982
983 void
984 Instance::make_param_menu(Gtk::Menu *menu,synfig::Canvas::Handle canvas, synfigapp::ValueDesc value_desc, float location)
985 {
986         Gtk::Menu& parammenu(*menu);
987
988         etl::handle<synfigapp::CanvasInterface> canvas_interface(find_canvas_interface(canvas));
989
990         if(!canvas_interface)
991                 return;
992
993         synfigapp::Action::ParamList param_list,param_list2;
994         param_list=canvas_interface->generate_param_list(value_desc);
995         param_list.add("origin",location);
996
997         if(value_desc.get_value_type()==ValueBase::TYPE_BLINEPOINT && value_desc.is_value_node() && ValueNode_Composite::Handle::cast_dynamic(value_desc.get_value_node()))
998         {
999                 param_list2=canvas_interface->generate_param_list(
1000                         synfigapp::ValueDesc(
1001                                 ValueNode_Composite::Handle::cast_dynamic(value_desc.get_value_node())
1002                                 ,0
1003                         )
1004                 );
1005                 param_list2.add("origin",location);
1006         }
1007
1008
1009         // Populate the convert menu by looping through
1010         // the ValueNode book and find the ones that are
1011         // relevant.
1012         {
1013                 Gtk::Menu *convert_menu=manage(new Gtk::Menu());
1014                 LinkableValueNode::Book::const_iterator iter;
1015                 for(iter=LinkableValueNode::book().begin();iter!=LinkableValueNode::book().end();++iter)
1016                 {
1017                         if(iter->second.check_type(value_desc.get_value_type()))
1018                         {
1019                                 convert_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(iter->second.local_name,
1020                                         sigc::hide_return(
1021                                                 sigc::bind(
1022                                                         sigc::bind(
1023                                                                 sigc::mem_fun(*canvas_interface.get(),&synfigapp::CanvasInterface::convert),
1024                                                                 iter->first
1025                                                         ),
1026                                                         value_desc
1027                                                 )
1028                                         )
1029                                 ));
1030                         }
1031                 }
1032
1033                 parammenu.items().push_back(Gtk::Menu_Helpers::StockMenuElem(Gtk::Stock::CONVERT,*convert_menu));
1034         }
1035
1036         if(param_list2.empty())
1037                 add_actions_to_menu(&parammenu, param_list,synfigapp::Action::CATEGORY_VALUEDESC|synfigapp::Action::CATEGORY_VALUENODE);
1038         else
1039                 add_actions_to_menu(&parammenu, param_list2,param_list,synfigapp::Action::CATEGORY_VALUEDESC|synfigapp::Action::CATEGORY_VALUENODE);
1040
1041         if(value_desc.get_value_type()==ValueBase::TYPE_BLINEPOINT && value_desc.is_value_node() && ValueNode_Composite::Handle::cast_dynamic(value_desc.get_value_node()))
1042         {
1043                 value_desc=synfigapp::ValueDesc(ValueNode_Composite::Handle::cast_dynamic(value_desc.get_value_node()),0);
1044         }
1045
1046         if(value_desc.is_value_node() && ValueNode_Animated::Handle::cast_dynamic(value_desc.get_value_node()))
1047         {
1048                 ValueNode_Animated::Handle value_node(ValueNode_Animated::Handle::cast_dynamic(value_desc.get_value_node()));
1049
1050                 try
1051                 {
1052                         WaypointList::iterator iter(value_node->find(canvas->get_time()));
1053                         parammenu.items().push_back(Gtk::Menu_Helpers::MenuElem(_("Edit Waypoint"),
1054                                 sigc::bind(
1055                                         sigc::bind(
1056                                                 sigc::bind(
1057                                                         sigc::mem_fun(*find_canvas_view(canvas),&studio::CanvasView::on_waypoint_clicked),
1058                                                         -1
1059                                                 ),
1060                                                 *iter
1061                                         ),
1062                                         value_desc
1063                                 )
1064                         ));
1065                 }
1066                 catch(...)
1067                 {
1068                 }
1069         }
1070 }
1071
1072 void
1073 edit_several_waypoints(etl::handle<CanvasView> canvas_view, std::list<synfigapp::ValueDesc> value_desc_list)
1074 {
1075         etl::handle<synfigapp::CanvasInterface> canvas_interface(canvas_view->canvas_interface());
1076
1077         Gtk::Dialog dialog(
1078                 "Edit Multiple Waypoints",              // Title
1079                 true,           // Modal
1080                 true            // use_separator
1081         );
1082
1083         Widget_WaypointModel widget_waypoint_model;
1084         widget_waypoint_model.show();
1085
1086         dialog.get_vbox()->pack_start(widget_waypoint_model);
1087
1088
1089         dialog.add_button(Gtk::StockID("gtk-apply"),1);
1090         dialog.add_button(Gtk::StockID("gtk-cancel"),0);
1091         dialog.show();
1092
1093         DEBUGPOINT();
1094         if(dialog.run()==0 || widget_waypoint_model.get_waypoint_model().is_trivial())
1095                 return;
1096         DEBUGPOINT();
1097         synfigapp::Action::PassiveGrouper group(canvas_interface->get_instance().get(),_("Set Waypoints"));
1098
1099         std::list<synfigapp::ValueDesc>::iterator iter;
1100         for(iter=value_desc_list.begin();iter!=value_desc_list.end();++iter)
1101         {
1102                 synfigapp::ValueDesc value_desc(*iter);
1103
1104                 if(!value_desc.is_valid())
1105                         continue;
1106
1107                 ValueNode_Animated::Handle value_node;
1108
1109                 // If this value isn't a ValueNode_Animated, but
1110                 // it is somewhat constant, then go ahead and convert
1111                 // it to a ValueNode_Animated.
1112                 if(!value_desc.is_value_node() || ValueNode_Const::Handle::cast_dynamic(value_desc.get_value_node()))
1113                 {
1114                         ValueBase value;
1115                         if(value_desc.is_value_node())
1116                                 value=ValueNode_Const::Handle::cast_dynamic(value_desc.get_value_node())->get_value();
1117                         else
1118                                 value=value_desc.get_value();
1119
1120                         value_node=ValueNode_Animated::create(value,canvas_interface->get_time());
1121
1122                         synfigapp::Action::Handle action;
1123
1124                         if(!value_desc.is_value_node())
1125                         {
1126                                 action=synfigapp::Action::create("value_desc_connect");
1127                                 action->set_param("dest",value_desc);
1128                                 action->set_param("src",ValueNode::Handle(value_node));
1129                         }
1130                         else
1131                         {
1132                                 action=synfigapp::Action::create("value_node_replace");
1133                                 action->set_param("dest",value_desc.get_value_node());
1134                                 action->set_param("src",ValueNode::Handle(value_node));
1135                         }
1136
1137                         action->set_param("canvas",canvas_view->get_canvas());
1138                         action->set_param("canvas_interface",canvas_interface);
1139
1140
1141                         if(!canvas_interface->get_instance()->perform_action(action))
1142                         {
1143                                 canvas_view->get_ui_interface()->error(_("Unable to convert to animated waypoint"));
1144                                 group.cancel();
1145                                 return;
1146                         }
1147                 }
1148                 else
1149                 {
1150                         if(value_desc.is_value_node())
1151                                 value_node=ValueNode_Animated::Handle::cast_dynamic(value_desc.get_value_node());
1152                 }
1153
1154
1155                 if(value_node)
1156                 {
1157
1158                         synfigapp::Action::Handle action(synfigapp::Action::create("waypoint_set_smart"));
1159
1160                         if(!action)
1161                         {
1162                                 canvas_view->get_ui_interface()->error(_("Unable to find waypoint_set_smart action"));
1163                                 group.cancel();
1164                                 return;
1165                         }
1166
1167
1168                         action->set_param("canvas",canvas_view->get_canvas());
1169                         action->set_param("canvas_interface",canvas_interface);
1170                         action->set_param("value_node",ValueNode::Handle(value_node));
1171                         action->set_param("time",canvas_interface->get_time());
1172                         action->set_param("model",widget_waypoint_model.get_waypoint_model());
1173
1174                         if(!canvas_interface->get_instance()->perform_action(action))
1175                         {
1176                                 canvas_view->get_ui_interface()->error(_("Unable to set a specific waypoint"));
1177                                 group.cancel();
1178                                 return;
1179                         }
1180                 }
1181                 else
1182                 {
1183                         //get_canvas_view()->get_ui_interface()->error(_("Unable to animate a specific valuedesc"));
1184                         //group.cancel();
1185                         //return;
1186                 }
1187
1188         }
1189 }
1190
1191 void
1192 Instance::make_param_menu(Gtk::Menu *menu,synfig::Canvas::Handle canvas,const std::list<synfigapp::ValueDesc>& value_desc_list)
1193 {
1194         etl::handle<synfigapp::CanvasInterface> canvas_interface(find_canvas_interface(canvas));
1195
1196         synfigapp::Action::ParamList param_list;
1197         param_list=canvas_interface->generate_param_list(value_desc_list);
1198
1199         add_actions_to_menu(menu, param_list,synfigapp::Action::CATEGORY_VALUEDESC|synfigapp::Action::CATEGORY_VALUENODE);
1200
1201         // Add the edit waypoints option if that might be useful
1202         if(canvas->rend_desc().get_time_end()-Time::epsilon()>canvas->rend_desc().get_time_start())
1203         {
1204                 menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Edit Waypoints"),
1205                         sigc::bind(
1206                                 sigc::bind(
1207                                         sigc::ptr_fun(
1208                                                 &edit_several_waypoints
1209                                         ),
1210                                         value_desc_list
1211                                 ),
1212                                 find_canvas_view(canvas)
1213                         )
1214                 ));
1215         }
1216 }