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