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