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