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