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