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