Made a start at implementing the ideas in http://synfig.org/Linking_to_Blines . ...
[synfig.git] / synfig-studio / trunk / src / gtkmm / instance.cpp
1 /* === S Y N F I G ========================================================= */
2 /*!     \file gtkmm/instance.cpp
3 **      \brief writeme
4 **
5 **      $Id$
6 **
7 **      \legal
8 **      Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
9 **      Copyright (c) 2007, 2008 Chris Moore
10 **
11 **      This package is free software; you can redistribute it and/or
12 **      modify it under the terms of the GNU General Public License as
13 **      published by the Free Software Foundation; either version 2 of
14 **      the License, or (at your option) any later version.
15 **
16 **      This package is distributed in the hope that it will be useful,
17 **      but WITHOUT ANY WARRANTY; without even the implied warranty of
18 **      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 **      General Public License for more details.
20 **      \endlegal
21 */
22 /* ========================================================================= */
23
24 /* === H E A D E R S ======================================================= */
25
26 #ifdef USING_PCH
27 #       include "pch.h"
28 #else
29 #ifdef HAVE_CONFIG_H
30 #       include <config.h>
31 #endif
32
33 #include "instance.h"
34 #include <cassert>
35 #include <gtkmm/stock.h>
36 #include <gtkmm/image.h>
37 #include <iostream>
38 #include <gtkmm/button.h>
39 #include "canvasview.h"
40 #include "app.h"
41 #include <sigc++/signal.h>
42 #include <sigc++/adaptors/hide.h>
43 #include "toolbox.h"
44 #include "onemoment.h"
45 #include <synfig/savecanvas.h>
46
47 #include "autorecover.h"
48 #include <sigc++/retype_return.h>
49 #include <sigc++/retype.h>
50 //#include <sigc++/hide.h>
51 #include <synfig/valuenode_composite.h>
52 #include <synfig/valuenode_duplicate.h>
53 #include "widget_waypointmodel.h"
54 #include <gtkmm/actiongroup.h>
55 #include "iconcontroller.h"
56 #include "workarea.h"
57 #include <sys/stat.h>
58 #include <errno.h>
59 #include <ETL/stringf>
60
61 #include "general.h"
62
63 #endif
64
65 using namespace std;
66 using namespace etl;
67 using namespace synfig;
68 using namespace studio;
69 using namespace sigc;
70
71 /* === M A C R O S ========================================================= */
72
73 /* === G L O B A L S ======================================================= */
74
75 int studio::Instance::instance_count_=0;
76
77 /* === P R O C E D U R E S ================================================= */
78
79 /* === M E T H O D S ======================================================= */
80
81 Instance::Instance(synfig::Canvas::Handle canvas):
82         synfigapp::Instance             (canvas),
83 //      canvas_tree_store_              (Gtk::TreeStore::create(CanvasTreeModel())),
84 //      canvas_tree_store_              (Gtk::TreeStore::create()),
85         history_tree_store_             (HistoryTreeStore::create(this)),
86         undo_status_(false),
87         redo_status_(false)
88 {
89         CanvasTreeModel model;
90         canvas_tree_store_=Gtk::TreeStore::create(model);
91
92         id_=instance_count_++;
93
94         // Connect up all the signals
95         signal_filename_changed().connect(sigc::mem_fun(*this,&studio::Instance::update_all_titles));
96         signal_unsaved_status_changed().connect(sigc::hide(sigc::mem_fun(*this,&studio::Instance::update_all_titles)));
97         signal_undo_status().connect(sigc::mem_fun(*this,&studio::Instance::set_undo_status));
98         signal_redo_status().connect(sigc::mem_fun(*this,&studio::Instance::set_redo_status));
99
100         signal_saved().connect(
101                 sigc::hide_return(
102                         sigc::ptr_fun(
103                                 studio::AutoRecover::auto_backup
104                         )
105                 )
106         );
107
108         canvas_tree_store_=Gtk::TreeStore::create(canvas_tree_model);
109
110         refresh_canvas_tree();
111 }
112
113 Instance::~Instance()
114 {
115 }
116
117 int
118 Instance::get_visible_canvases()const
119 {
120         int count(0);
121         CanvasViewList::const_iterator iter;
122         for(iter=canvas_view_list_.begin();iter!=canvas_view_list_.end();++iter)
123                 if((*iter)->is_visible())
124                         count++;
125         return count;
126 }
127
128 handle<Instance>
129 Instance::create(synfig::Canvas::Handle canvas)
130 {
131         // Construct a new instance
132         handle<Instance> instance(new Instance(canvas));
133
134         // Add the new instance to the application's instance list
135         App::instance_list.push_back(instance);
136
137         // Set up the instance with the default UI manager
138         instance->synfigapp::Instance::set_ui_interface(App::get_ui_interface());
139
140         // Signal the new instance
141         App::signal_instance_created()(instance);
142
143         // And then make sure that is has been selected
144         App::set_selected_instance(instance);
145
146         // Create the initial window for the root canvas
147         instance->focus(canvas);
148
149         return instance;
150 }
151
152 handle<CanvasView>
153 Instance::find_canvas_view(etl::handle<synfig::Canvas> canvas)
154 {
155         if(!canvas)
156                 return 0;
157
158         while(canvas->is_inline())
159                 canvas=canvas->parent();
160
161         CanvasViewList::iterator iter;
162
163         for(iter=canvas_view_list().begin();iter!=canvas_view_list().end();iter++)
164                 if((*iter)->get_canvas()==canvas)
165                         return *iter;
166
167         return CanvasView::create(this,canvas);
168 }
169
170 void
171 Instance::focus(etl::handle<synfig::Canvas> canvas)
172 {
173         handle<CanvasView> canvas_view=find_canvas_view(canvas);
174         assert(canvas_view);
175         canvas_view->present();
176 }
177
178 void
179 Instance::set_undo_status(bool x)
180 {
181         undo_status_=x;
182         App::toolbox->update_undo_redo();
183         signal_undo_redo_status_changed()();
184 }
185
186 void
187 Instance::set_redo_status(bool x)
188 {
189         redo_status_=x;
190         App::toolbox->update_undo_redo();
191         signal_undo_redo_status_changed()();
192 }
193
194 bool
195 studio::Instance::save_as(const synfig::String &file_name)
196 {
197         if(synfigapp::Instance::save_as(file_name))
198         {
199                 // after changing the filename, update the render settings with the new filename
200                 list<handle<CanvasView> >::iterator iter;
201                 for(iter=canvas_view_list().begin();iter!=canvas_view_list().end();iter++)
202                         (*iter)->render_settings.set_entry_filename();
203                 App::add_recent_file(etl::handle<Instance>(this));
204                 return true;
205         }
206         return false;
207 }
208
209 void
210 studio::Instance::open()
211 {
212         App::dialog_open(get_file_name());
213 }
214
215 Instance::Status
216 studio::Instance::save()
217 {
218         // if we don't have a real filename yet then we need to ask where to save it
219         if (!has_real_filename())
220         {
221                 if (dialog_save_as())
222                         return STATUS_OK;
223                 else
224                         return STATUS_CANCEL;
225         }
226
227         if (synfigapp::Instance::save())
228         {
229                 App::add_recent_file(etl::handle<Instance>(this));
230                 return STATUS_OK;
231         }
232
233         App::dialog_error_blocking("Save - Error","Unable to save to '" + get_file_name() + "'");
234         return STATUS_ERROR;
235 }
236
237 // the filename will be set to "Synfig Animation 1" or some such when first created
238 // and will be changed to an absolute path once it has been saved
239 // so if it still begins with "Synfig Animation " then we don't have a real filename yet
240 bool
241 studio::Instance::has_real_filename()
242 {
243         return get_file_name().find(DEFAULT_FILENAME_PREFIX) != 0;
244 }
245
246 bool
247 studio::Instance::dialog_save_as()
248 {
249         string filename = get_file_name();
250         Canvas::Handle canvas(get_canvas());
251
252         {
253                 OneMoment one_moment;
254                 std::set<Node*>::iterator iter;
255                 for(iter=canvas->parent_set.begin();iter!=canvas->parent_set.end();++iter)
256                 {
257                         synfig::Node* node(*iter);
258                         for(;!node->parent_set.empty();node=*node->parent_set.begin())
259                         {
260                                 Layer::Handle parent_layer(dynamic_cast<Layer*>(node));
261                                 if(parent_layer && parent_layer->get_canvas()->get_root()!=get_canvas())
262                                 {
263                                         App::dialog_error_blocking("SaveAs - Error",
264                                                 "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                                         );
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                                 App::dialog_error_blocking("SaveAs - Error","Unable to check whether '" + filename + "' exists.");
319                                 continue;
320                         }
321
322                         // if the file exists and the user doesn't want to overwrite it, keep prompting for a filename
323                         if ((stat_return == 0) &&
324                                 !App::dialog_yes_no("File exists",
325                                                                         "A file named '" +
326                                                                         filename +
327                                                                         "' already exists.\n\n"
328                                                                         "Do you want to replace it with the file you are saving?"))
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
338                 App::dialog_error_blocking("SaveAs - Error","Unable to save to '" + filename + "'");
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         assert(synfigapp::Action::book().count(name));
951
952         synfigapp::Action::BookEntry entry(synfigapp::Action::book().find(name)->second);
953
954         synfigapp::Action::Handle action(entry.factory());
955
956         if(!action)
957         {
958                 synfig::error("Bad Action");
959                 return;
960         }
961
962         action->set_param_list(param_list);
963
964         synfigapp::Action::ParamVocab param_vocab(entry.get_param_vocab());
965         synfigapp::Action::ParamVocab::const_iterator iter;
966
967         for(iter=param_vocab.begin();iter!=param_vocab.end();++iter)
968         {
969                 if(!iter->get_mutual_exclusion().empty() && param_list.count(iter->get_mutual_exclusion()))
970                         continue;
971
972                 // If the parameter is optionally user-supplied,
973                 // and has not been already provided in the param_list,
974                 // then we should go ahead and see if we can
975                 // provide that data.
976                 if(iter->get_user_supplied() && param_list.count(iter->get_name())==0)
977                 {
978                         switch(iter->get_type())
979                         {
980                         case synfigapp::Action::Param::TYPE_STRING:
981                         {
982                                 String str;
983                                 if(!studio::App::dialog_entry(entry.local_name, iter->get_local_name()+":"+iter->get_desc(),str))
984                                         return;
985                                 action->set_param(iter->get_name(),str);
986                                 break;
987                         }
988                         default:
989                                 synfig::error("Unsupported user-supplied action parameter");
990                                 return;
991                                 break;
992                         }
993                 }
994         }
995
996         if(!action->is_ready())
997         {
998                 synfig::error("Action not ready");
999                 return;
1000         }
1001
1002         perform_action(action);
1003 }
1004
1005 void
1006 Instance::make_param_menu(Gtk::Menu *menu,synfig::Canvas::Handle canvas, synfigapp::ValueDesc value_desc, float location, bool bezier)
1007 {
1008         Gtk::Menu& parammenu(*menu);
1009
1010         etl::handle<synfigapp::CanvasInterface> canvas_interface(find_canvas_interface(canvas));
1011
1012         if(!canvas_interface)
1013                 return;
1014
1015         synfigapp::Action::ParamList param_list,param_list2;
1016         param_list=canvas_interface->generate_param_list(value_desc);
1017         param_list.add("origin",location);
1018
1019         if(value_desc.get_value_type()==ValueBase::TYPE_BLINEPOINT && value_desc.is_value_node() && ValueNode_Composite::Handle::cast_dynamic(value_desc.get_value_node()))
1020         {
1021                 param_list2=canvas_interface->generate_param_list(
1022                         synfigapp::ValueDesc(
1023                                 ValueNode_Composite::Handle::cast_dynamic(value_desc.get_value_node())
1024                                 ,0
1025                         )
1026                 );
1027                 param_list2.add("origin",location);
1028         }
1029
1030         // Populate the convert menu by looping through
1031         // the ValueNode book and find the ones that are
1032         // relevant.
1033
1034         // show the 'Convert' sub-menu if this valuedesc is anything other than either:
1035         //   the 'Index' parameter of a Duplicate layer
1036         // or
1037         //   a Duplicate ValueNode whose parent is not a (layer or ValueNode)
1038         if (!((value_desc.parent_is_layer_param() &&
1039                    value_desc.get_layer()->get_name() == "duplicate" &&
1040                    value_desc.get_param_name() == "index") ||
1041                   (value_desc.is_value_node() &&
1042                    ValueNode_Duplicate::Handle::cast_dynamic(value_desc.get_value_node()) &&
1043                    !(value_desc.parent_is_layer_param() ||
1044                          value_desc.parent_is_value_node()))))
1045         {
1046                 Gtk::Menu *convert_menu=manage(new Gtk::Menu());
1047                 LinkableValueNode::Book::const_iterator iter;
1048                 for(iter=LinkableValueNode::book().begin();iter!=LinkableValueNode::book().end();++iter)
1049                 {
1050                         if(iter->second.check_type(value_desc.get_value_type()))
1051                         {
1052                                 convert_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(iter->second.local_name,
1053                                         sigc::hide_return(
1054                                                 sigc::bind(
1055                                                         sigc::bind(
1056                                                                 sigc::mem_fun(*canvas_interface.get(),&synfigapp::CanvasInterface::convert),
1057                                                                 iter->first
1058                                                         ),
1059                                                         value_desc
1060                                                 )
1061                                         )
1062                                 ));
1063                         }
1064                 }
1065
1066                 parammenu.items().push_back(Gtk::Menu_Helpers::StockMenuElem(Gtk::Stock::CONVERT,*convert_menu));
1067         }
1068
1069         synfigapp::Action::Category categories = synfigapp::Action::CATEGORY_VALUEDESC|synfigapp::Action::CATEGORY_VALUENODE;
1070         if (bezier)
1071         {
1072                 categories = categories|synfigapp::Action::CATEGORY_BEZIER;
1073
1074                 const DuckList selected_ducks(find_canvas_view(canvas)->get_work_area()->get_selected_ducks());
1075                 for(DuckList::const_iterator iter=selected_ducks.begin();iter!=selected_ducks.end();++iter)
1076                 {
1077                         synfigapp::ValueDesc value_desc((*iter)->get_value_desc());
1078                         if(value_desc.is_valid())
1079                                 param_list.add("selected_value_desc",value_desc);
1080                 }
1081         }
1082
1083         if(param_list2.empty())
1084                 add_actions_to_menu(&parammenu, param_list,categories);
1085         else
1086                 add_actions_to_menu(&parammenu, param_list2,param_list,categories);
1087
1088         if(value_desc.get_value_type()==ValueBase::TYPE_BLINEPOINT && value_desc.is_value_node() && ValueNode_Composite::Handle::cast_dynamic(value_desc.get_value_node()))
1089         {
1090                 value_desc=synfigapp::ValueDesc(ValueNode_Composite::Handle::cast_dynamic(value_desc.get_value_node()),0);
1091         }
1092
1093         if(value_desc.is_value_node() && ValueNode_Animated::Handle::cast_dynamic(value_desc.get_value_node()))
1094         {
1095                 ValueNode_Animated::Handle value_node(ValueNode_Animated::Handle::cast_dynamic(value_desc.get_value_node()));
1096
1097                 try
1098                 {
1099                         // try to find a waypoint at the current time - if we
1100                         // can't, we don't want the menu entry - an exception is thrown
1101                         WaypointList::iterator iter(value_node->find(canvas->get_time()));
1102                         std::set<synfig::Waypoint, std::less<UniqueID> > waypoint_set;
1103                         waypoint_set.insert(*iter);
1104
1105                         parammenu.items().push_back(Gtk::Menu_Helpers::MenuElem(_("Edit Waypoint"),
1106                                 sigc::bind(
1107                                         sigc::bind(
1108                                                 sigc::bind(
1109                                                         sigc::mem_fun(*find_canvas_view(canvas),&studio::CanvasView::on_waypoint_clicked_canvasview),
1110                                                         -1
1111                                                 ),
1112                                                 waypoint_set
1113                                         ),
1114                                         value_desc
1115                                 )
1116                         ));
1117                 }
1118                 catch(...)
1119                 {
1120                 }
1121         }
1122 }
1123
1124 void
1125 edit_several_waypoints(etl::handle<CanvasView> canvas_view, std::list<synfigapp::ValueDesc> value_desc_list)
1126 {
1127         etl::handle<synfigapp::CanvasInterface> canvas_interface(canvas_view->canvas_interface());
1128
1129         Gtk::Dialog dialog(
1130                 "Edit Multiple Waypoints",              // Title
1131                 true,           // Modal
1132                 true            // use_separator
1133         );
1134
1135         Widget_WaypointModel widget_waypoint_model;
1136         widget_waypoint_model.show();
1137
1138         dialog.get_vbox()->pack_start(widget_waypoint_model);
1139
1140         dialog.add_button(Gtk::StockID("gtk-apply"),1);
1141         dialog.add_button(Gtk::StockID("gtk-cancel"),0);
1142         dialog.show();
1143
1144         if(dialog.run()==0 || widget_waypoint_model.get_waypoint_model().is_trivial())
1145                 return;
1146         synfigapp::Action::PassiveGrouper group(canvas_interface->get_instance().get(),_("Set Waypoints"));
1147
1148         std::list<synfigapp::ValueDesc>::iterator iter;
1149         for(iter=value_desc_list.begin();iter!=value_desc_list.end();++iter)
1150         {
1151                 synfigapp::ValueDesc value_desc(*iter);
1152
1153                 if(!value_desc.is_valid())
1154                         continue;
1155
1156                 ValueNode_Animated::Handle value_node;
1157
1158                 // If this value isn't a ValueNode_Animated, but
1159                 // it is somewhat constant, then go ahead and convert
1160                 // it to a ValueNode_Animated.
1161                 if(!value_desc.is_value_node() || ValueNode_Const::Handle::cast_dynamic(value_desc.get_value_node()))
1162                 {
1163                         ValueBase value;
1164                         if(value_desc.is_value_node())
1165                                 value=ValueNode_Const::Handle::cast_dynamic(value_desc.get_value_node())->get_value();
1166                         else
1167                                 value=value_desc.get_value();
1168
1169                         value_node=ValueNode_Animated::create(value,canvas_interface->get_time());
1170
1171                         synfigapp::Action::Handle action;
1172
1173                         if(!value_desc.is_value_node())
1174                         {
1175                                 action=synfigapp::Action::create("value_desc_connect");
1176                                 action->set_param("dest",value_desc);
1177                                 action->set_param("src",ValueNode::Handle(value_node));
1178                         }
1179                         else
1180                         {
1181                                 action=synfigapp::Action::create("value_node_replace");
1182                                 action->set_param("dest",value_desc.get_value_node());
1183                                 action->set_param("src",ValueNode::Handle(value_node));
1184                         }
1185
1186                         action->set_param("canvas",canvas_view->get_canvas());
1187                         action->set_param("canvas_interface",canvas_interface);
1188
1189                         if(!canvas_interface->get_instance()->perform_action(action))
1190                         {
1191                                 canvas_view->get_ui_interface()->error(_("Unable to convert to animated waypoint"));
1192                                 group.cancel();
1193                                 return;
1194                         }
1195                 }
1196                 else
1197                 {
1198                         if(value_desc.is_value_node())
1199                                 value_node=ValueNode_Animated::Handle::cast_dynamic(value_desc.get_value_node());
1200                 }
1201
1202                 if(value_node)
1203                 {
1204                         synfigapp::Action::Handle action(synfigapp::Action::create("waypoint_set_smart"));
1205
1206                         if(!action)
1207                         {
1208                                 canvas_view->get_ui_interface()->error(_("Unable to find waypoint_set_smart action"));
1209                                 group.cancel();
1210                                 return;
1211                         }
1212
1213                         action->set_param("canvas",canvas_view->get_canvas());
1214                         action->set_param("canvas_interface",canvas_interface);
1215                         action->set_param("value_node",ValueNode::Handle(value_node));
1216                         action->set_param("time",canvas_interface->get_time());
1217                         action->set_param("model",widget_waypoint_model.get_waypoint_model());
1218
1219                         if(!canvas_interface->get_instance()->perform_action(action))
1220                         {
1221                                 canvas_view->get_ui_interface()->error(_("Unable to set a specific waypoint"));
1222                                 group.cancel();
1223                                 return;
1224                         }
1225                 }
1226                 else
1227                 {
1228                         //get_canvas_view()->get_ui_interface()->error(_("Unable to animate a specific valuedesc"));
1229                         //group.cancel();
1230                         //return;
1231                 }
1232
1233         }
1234 }
1235
1236 void
1237 Instance::make_param_menu(Gtk::Menu *menu,synfig::Canvas::Handle canvas,const std::list<synfigapp::ValueDesc>& value_desc_list)
1238 {
1239         etl::handle<synfigapp::CanvasInterface> canvas_interface(find_canvas_interface(canvas));
1240
1241         synfigapp::Action::ParamList param_list;
1242         param_list=canvas_interface->generate_param_list(value_desc_list);
1243
1244         add_actions_to_menu(menu, param_list,synfigapp::Action::CATEGORY_VALUEDESC|synfigapp::Action::CATEGORY_VALUENODE);
1245
1246         // Add the edit waypoints option if that might be useful
1247         if(canvas->rend_desc().get_time_end()-Time::epsilon()>canvas->rend_desc().get_time_start())
1248         {
1249                 menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Edit Waypoints"),
1250                         sigc::bind(
1251                                 sigc::bind(
1252                                         sigc::ptr_fun(
1253                                                 &edit_several_waypoints
1254                                         ),
1255                                         value_desc_list
1256                                 ),
1257                                 find_canvas_view(canvas)
1258                         )
1259                 ));
1260         }
1261 }