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