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