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