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