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