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