Working on 1799250: After changing the filename, update the render settings with...
[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 bool
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                 return dialog_save_as();
210
211         return synfigapp::Instance::save();
212 }
213
214 bool
215 studio::Instance::dialog_save_as()
216 {
217         string filename=basename(get_file_name());
218         Canvas::Handle canvas(get_canvas());
219
220         {
221                 OneMoment one_moment;
222                 std::set<Node*>::iterator iter;
223                 for(iter=canvas->parent_set.begin();iter!=canvas->parent_set.end();++iter)
224                 {
225                         synfig::Node* node(*iter);
226                         for(;!node->parent_set.empty();node=*node->parent_set.begin())
227                         {
228                                 Layer::Handle parent_layer(dynamic_cast<Layer*>(node));
229                                 if(parent_layer && parent_layer->get_canvas()->get_root()!=get_canvas())
230                                 {
231                                         App::dialog_error_blocking("SaveAs - Error",
232                                                 "There is currently a bug when using \"SaveAs\"\n"
233                                                 "on a composition that is being referenced by other\n"
234                                                 "files that are currently open. Close these\n"
235                                                 "other files first before trying to use \"SaveAs\"."
236                                         );
237
238                                         return false;
239                                 }
240                                 if(parent_layer)
241                                         break;
242                         }
243                 }
244         }
245
246         // show the canvas' name if it has one, else its ID
247         while(App::dialog_save_file(_("Choose a Filename to Save As") +
248                                                                 String(" (") +
249                                                                 (canvas->get_name().empty()
250                                                                  ? canvas->get_id()
251                                                                  : canvas->get_name()) +
252                                                                 ") ...", filename))
253         {
254                 // If the filename still has wildcards, then we should
255                 // continue looking for the file we want
256                 if(find(filename.begin(),filename.end(),'*')!=filename.end())
257                         continue;
258
259                 std::string base = basename(filename);
260                 if(find(base.begin(),base.end(),'.')==base.end())
261                         filename+=".sifz";
262
263                 try
264                 {
265                         String ext(String(filename.begin()+filename.find_last_of('.')+1,filename.end()));
266                         if(ext!="sif" && ext!="sifz" && !App::dialog_yes_no(_("Unknown extension"),
267                                 _("You have given the file name an extension\nwhich I do not recognise. Are you sure this is what you want?")))
268                         {
269                                 continue;
270                         }
271                 }
272                 catch(...)
273                 {
274                         continue;
275                 }
276
277                 {
278                         struct stat     s;
279                         // if stat() succeeds, or it fails with something other than 'file doesn't exist', the file exists
280                         // if the file exists and the user doesn't want to overwrite it, keep prompting for a filename
281                         if ((stat(filename.c_str(), &s) != -1 || errno != ENOENT) &&
282                                 !App::dialog_yes_no("File exists",
283                                                                         "A file named '" +
284                                                                         filename +
285                                                                         "' already exists.\n\n"
286                                                                         "Do you want to replace it with the file you are saving?"))
287                                 continue;
288                 }
289
290                 if(save_as(filename))
291                         return true;
292
293                 App::dialog_error_blocking("SaveAs - Error","Unable to save file");
294         }
295
296         return false;
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 lose 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                 do
634                 {
635                         string str=strprintf(_("Would you like to save your changes to %s?"),basename(get_file_name()).c_str() );
636                         int answer=uim->yes_no_cancel(get_canvas()->get_name(),str,synfigapp::UIInterface::RESPONSE_YES);
637                         if(answer==synfigapp::UIInterface::RESPONSE_YES)
638                                 if (save()) break;
639                         if(answer==synfigapp::UIInterface::RESPONSE_NO)
640                                 break;
641                         if(answer==synfigapp::UIInterface::RESPONSE_CANCEL)
642                                 return false;
643                 } while (true);
644
645         if(is_modified())
646         {
647                 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());
648                 int answer=uim->yes_no_cancel(get_canvas()->get_name(),str,synfigapp::UIInterface::RESPONSE_YES);
649
650                 if(answer==synfigapp::UIInterface::RESPONSE_YES)
651                         dialog_cvs_commit();
652                 if(answer==synfigapp::UIInterface::RESPONSE_CANCEL)
653                         return false;
654         }
655
656         close();
657
658         return true;
659 }
660
661
662 void
663 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
664 {
665         synfigapp::Action::CandidateList candidate_list;
666         synfigapp::Action::CandidateList::iterator iter;
667
668         candidate_list=compile_candidate_list(param_list,category);
669
670         candidate_list.sort();
671
672         if(candidate_list.empty())
673                 synfig::warning("Action CandidateList is empty!");
674
675         for(iter=candidate_list.begin();iter!=candidate_list.end();++iter)
676         {
677                 Gtk::StockID stock_id(get_action_stock_id(*iter));
678
679                 if(!(iter->category&synfigapp::Action::CATEGORY_HIDDEN))
680                 {
681                         action_group->add(Gtk::Action::create(
682                                 "action-"+iter->name,
683                                 stock_id,
684                                 iter->local_name,iter->local_name
685                         ),
686                                 sigc::bind(
687                                         sigc::bind(
688                                                 sigc::mem_fun(
689                                                         *const_cast<studio::Instance*>(this),
690                                                         &studio::Instance::process_action
691                                                 ),
692                                                 param_list
693                                         ),
694                                         iter->name
695                                 )
696                         );
697                         ui_info+=strprintf("<menuitem action='action-%s' />",iter->name.c_str());
698                 }
699         }
700 }
701
702 void
703 Instance::add_actions_to_menu(Gtk::Menu *menu, const synfigapp::Action::ParamList &param_list,synfigapp::Action::Category category)const
704 {
705         synfigapp::Action::CandidateList candidate_list;
706         synfigapp::Action::CandidateList::iterator iter;
707
708         candidate_list=compile_candidate_list(param_list,category);
709
710         candidate_list.sort();
711
712         if(candidate_list.empty())
713                 synfig::warning("Action CandidateList is empty!");
714
715         for(iter=candidate_list.begin();iter!=candidate_list.end();++iter)
716         {
717                 if(!(iter->category&synfigapp::Action::CATEGORY_HIDDEN))
718                 {
719                         Gtk::Image* image(manage(new Gtk::Image()));
720                         Gtk::Stock::lookup(get_action_stock_id(*iter),Gtk::ICON_SIZE_MENU,*image);
721
722                         /*
723                         if(iter->task=="raise")
724                                 Gtk::Stock::lookup(Gtk::Stock::GO_UP,Gtk::ICON_SIZE_MENU,*image);
725                         else if(iter->task=="lower")
726                                 Gtk::Stock::lookup(Gtk::Stock::GO_DOWN,Gtk::ICON_SIZE_MENU,*image);
727                         else if(iter->task=="move_top")
728                                 Gtk::Stock::lookup(Gtk::Stock::GOTO_TOP,Gtk::ICON_SIZE_MENU,*image);
729                         else if(iter->task=="move_bottom")
730                                 Gtk::Stock::lookup(Gtk::Stock::GOTO_BOTTOM,Gtk::ICON_SIZE_MENU,*image);
731                         else if(iter->task=="remove")
732                                 Gtk::Stock::lookup(Gtk::Stock::DELETE,Gtk::ICON_SIZE_MENU,*image);
733                         else if(iter->task=="set_on")
734                                 Gtk::Stock::lookup(Gtk::Stock::YES,Gtk::ICON_SIZE_MENU,*image);
735                         else if(iter->task=="set_off")
736                                 Gtk::Stock::lookup(Gtk::Stock::NO,Gtk::ICON_SIZE_MENU,*image);
737                         else if(iter->task=="duplicate")
738                                 Gtk::Stock::lookup(Gtk::Stock::COPY,Gtk::ICON_SIZE_MENU,*image);
739                         else if(iter->task=="remove")
740                                 Gtk::Stock::lookup(Gtk::Stock::DELETE,Gtk::ICON_SIZE_MENU,*image);
741                         else
742                         {
743                                 Gtk::Stock::lookup(Gtk::StockID("synfig-"+iter->name),Gtk::ICON_SIZE_MENU,*image) ||
744                                 Gtk::Stock::lookup(Gtk::StockID("gtk-"+iter->task),Gtk::ICON_SIZE_MENU,*image) ||
745                                 Gtk::Stock::lookup(Gtk::StockID("synfig-"+iter->task),Gtk::ICON_SIZE_MENU,*image);
746                         }
747                         */
748                         menu->items().push_back(
749                                 Gtk::Menu_Helpers::ImageMenuElem(
750                                         iter->local_name,
751                                         *image,
752                                         sigc::bind(
753                                                 sigc::bind(
754                                                         sigc::mem_fun(
755                                                                 *const_cast<studio::Instance*>(this),
756                                                                 &studio::Instance::process_action
757                                                         ),
758                                                         param_list
759                                                 ),
760                                                 iter->name
761                                         )
762                                 )
763                         );
764                 }
765         }
766 }
767
768 void
769 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
770 {
771         synfigapp::Action::CandidateList candidate_list;
772         synfigapp::Action::CandidateList candidate_list2;
773
774         synfigapp::Action::CandidateList::iterator iter;
775
776         candidate_list=compile_candidate_list(param_list,category);
777         candidate_list2=compile_candidate_list(param_list2,category);
778
779         candidate_list.sort();
780
781         if(candidate_list.empty())
782                 synfig::warning("Action CandidateList is empty!");
783         if(candidate_list2.empty())
784                 synfig::warning("Action CandidateList2 is empty!");
785
786         // Seperate out the candidate lists so that there are no conflicts
787         for(iter=candidate_list.begin();iter!=candidate_list.end();++iter)
788         {
789                 synfigapp::Action::CandidateList::iterator iter2(candidate_list2.find(iter->name));
790                 if(iter2!=candidate_list2.end())
791                         candidate_list2.erase(iter2);
792         }
793
794         for(iter=candidate_list2.begin();iter!=candidate_list2.end();++iter)
795         {
796                 if(!(iter->category&synfigapp::Action::CATEGORY_HIDDEN))
797                 {
798                         Gtk::Image* image(manage(new Gtk::Image()));
799                         Gtk::Stock::lookup(get_action_stock_id(*iter),Gtk::ICON_SIZE_MENU,*image);
800
801 /*                      if(iter->task=="raise")
802                                 Gtk::Stock::lookup(Gtk::Stock::GO_UP,Gtk::ICON_SIZE_MENU,*image);
803                         else if(iter->task=="lower")
804                                 Gtk::Stock::lookup(Gtk::Stock::GO_DOWN,Gtk::ICON_SIZE_MENU,*image);
805                         else if(iter->task=="move_top")
806                                 Gtk::Stock::lookup(Gtk::Stock::GOTO_TOP,Gtk::ICON_SIZE_MENU,*image);
807                         else if(iter->task=="move_bottom")
808                                 Gtk::Stock::lookup(Gtk::Stock::GOTO_BOTTOM,Gtk::ICON_SIZE_MENU,*image);
809                         else if(iter->task=="remove")
810                                 Gtk::Stock::lookup(Gtk::Stock::DELETE,Gtk::ICON_SIZE_MENU,*image);
811                         else if(iter->task=="set_on")
812                                 Gtk::Stock::lookup(Gtk::Stock::YES,Gtk::ICON_SIZE_MENU,*image);
813                         else if(iter->task=="set_off")
814                                 Gtk::Stock::lookup(Gtk::Stock::NO,Gtk::ICON_SIZE_MENU,*image);
815                         else if(iter->task=="duplicate")
816                                 Gtk::Stock::lookup(Gtk::Stock::COPY,Gtk::ICON_SIZE_MENU,*image);
817                         else if(iter->task=="remove")
818                                 Gtk::Stock::lookup(Gtk::Stock::DELETE,Gtk::ICON_SIZE_MENU,*image);
819                         else
820                         {
821                                 Gtk::Stock::lookup(Gtk::StockID("synfig-"+iter->name),Gtk::ICON_SIZE_MENU,*image) ||
822                                 Gtk::Stock::lookup(Gtk::StockID("gtk-"+iter->task),Gtk::ICON_SIZE_MENU,*image) ||
823                                 Gtk::Stock::lookup(Gtk::StockID("synfig-"+iter->task),Gtk::ICON_SIZE_MENU,*image);
824                         }
825 */
826                         menu->items().push_back(
827                                 Gtk::Menu_Helpers::ImageMenuElem(
828                                         iter->local_name,
829                                         *image,
830                                         sigc::bind(
831                                                 sigc::bind(
832                                                         sigc::mem_fun(
833                                                                 *const_cast<studio::Instance*>(this),
834                                                                 &studio::Instance::process_action
835                                                         ),
836                                                         param_list2
837                                                 ),
838                                                 iter->name
839                                         )
840                                 )
841                         );
842                 }
843         }
844
845         for(iter=candidate_list.begin();iter!=candidate_list.end();++iter)
846         {
847                 if(!(iter->category&synfigapp::Action::CATEGORY_HIDDEN))
848                 {
849                         Gtk::Image* image(manage(new Gtk::Image()));
850                         Gtk::Stock::lookup(get_action_stock_id(*iter),Gtk::ICON_SIZE_MENU,*image);
851 /*                      if(iter->task=="raise")
852                                 Gtk::Stock::lookup(Gtk::Stock::GO_UP,Gtk::ICON_SIZE_MENU,*image);
853                         else if(iter->task=="lower")
854                                 Gtk::Stock::lookup(Gtk::Stock::GO_DOWN,Gtk::ICON_SIZE_MENU,*image);
855                         else if(iter->task=="move_top")
856                                 Gtk::Stock::lookup(Gtk::Stock::GOTO_TOP,Gtk::ICON_SIZE_MENU,*image);
857                         else if(iter->task=="move_bottom")
858                                 Gtk::Stock::lookup(Gtk::Stock::GOTO_BOTTOM,Gtk::ICON_SIZE_MENU,*image);
859                         else if(iter->task=="remove")
860                                 Gtk::Stock::lookup(Gtk::Stock::DELETE,Gtk::ICON_SIZE_MENU,*image);
861                         else if(iter->task=="set_on")
862                                 Gtk::Stock::lookup(Gtk::Stock::YES,Gtk::ICON_SIZE_MENU,*image);
863                         else if(iter->task=="set_off")
864                                 Gtk::Stock::lookup(Gtk::Stock::NO,Gtk::ICON_SIZE_MENU,*image);
865                         else if(iter->task=="duplicate")
866                                 Gtk::Stock::lookup(Gtk::Stock::COPY,Gtk::ICON_SIZE_MENU,*image);
867                         else if(iter->task=="remove")
868                                 Gtk::Stock::lookup(Gtk::Stock::DELETE,Gtk::ICON_SIZE_MENU,*image);
869                         else
870                         {
871                                 Gtk::Stock::lookup(Gtk::StockID("synfig-"+iter->name),Gtk::ICON_SIZE_MENU,*image) ||
872                                 Gtk::Stock::lookup(Gtk::StockID("gtk-"+iter->task),Gtk::ICON_SIZE_MENU,*image) ||
873                                 Gtk::Stock::lookup(Gtk::StockID("synfig-"+iter->task),Gtk::ICON_SIZE_MENU,*image);
874                         }
875 */
876                         menu->items().push_back(
877                                 Gtk::Menu_Helpers::ImageMenuElem(
878                                         iter->local_name,
879                                         *image,
880                                         sigc::bind(
881                                                 sigc::bind(
882                                                         sigc::mem_fun(
883                                                                 *const_cast<studio::Instance*>(this),
884                                                                 &studio::Instance::process_action
885                                                         ),
886                                                         param_list
887                                                 ),
888                                                 iter->name
889                                         )
890                                 )
891                         );
892                 }
893         }
894 }
895
896 void
897 Instance::process_action(String name, synfigapp::Action::ParamList param_list)
898 {
899         assert(synfigapp::Action::book().count(name));
900
901         synfigapp::Action::BookEntry entry(synfigapp::Action::book().find(name)->second);
902
903         synfigapp::Action::Handle action(entry.factory());
904
905         if(!action)
906         {
907                 synfig::error("Bad Action");
908                 return;
909         }
910
911         action->set_param_list(param_list);
912
913         synfigapp::Action::ParamVocab param_vocab(entry.get_param_vocab());
914         synfigapp::Action::ParamVocab::const_iterator iter;
915
916         for(iter=param_vocab.begin();iter!=param_vocab.end();++iter)
917         {
918                 if(!iter->get_mutual_exclusion().empty() && param_list.count(iter->get_mutual_exclusion()))
919                         continue;
920
921                 // If the parameter is optionally user-supplied,
922                 // and has not been already provided in the param_list,
923                 // then we should go ahead and see if we can
924                 // provide that data.
925                 if(iter->get_user_supplied() && param_list.count(iter->get_name())==0)
926                 {
927                         switch(iter->get_type())
928                         {
929                         case synfigapp::Action::Param::TYPE_STRING:
930                         {
931                                 String str;
932                                 if(!studio::App::dialog_entry(entry.local_name, iter->get_local_name()+":"+iter->get_desc(),str))
933                                         return;
934                                 action->set_param(iter->get_name(),str);
935                                 break;
936                         }
937                         default:
938                                 synfig::error("Unsupported user-supplied action parameter");
939                                 return;
940                                 break;
941                         }
942                 }
943         }
944
945         if(!action->is_ready())
946         {
947                 synfig::error("Action not ready");
948                 return;
949         }
950
951         perform_action(action);
952 }
953
954 void
955 Instance::make_param_menu(Gtk::Menu *menu,synfig::Canvas::Handle canvas, synfigapp::ValueDesc value_desc, float location)
956 {
957         Gtk::Menu& parammenu(*menu);
958
959         etl::handle<synfigapp::CanvasInterface> canvas_interface(find_canvas_interface(canvas));
960
961         if(!canvas_interface)
962                 return;
963
964         synfigapp::Action::ParamList param_list,param_list2;
965         param_list=canvas_interface->generate_param_list(value_desc);
966         param_list.add("origin",location);
967
968         if(value_desc.get_value_type()==ValueBase::TYPE_BLINEPOINT && value_desc.is_value_node() && ValueNode_Composite::Handle::cast_dynamic(value_desc.get_value_node()))
969         {
970                 param_list2=canvas_interface->generate_param_list(
971                         synfigapp::ValueDesc(
972                                 ValueNode_Composite::Handle::cast_dynamic(value_desc.get_value_node())
973                                 ,0
974                         )
975                 );
976                 param_list2.add("origin",location);
977         }
978
979
980         // Populate the convert menu by looping through
981         // the ValueNode book and find the ones that are
982         // relevant.
983         {
984                 Gtk::Menu *convert_menu=manage(new Gtk::Menu());
985                 LinkableValueNode::Book::const_iterator iter;
986                 for(iter=LinkableValueNode::book().begin();iter!=LinkableValueNode::book().end();++iter)
987                 {
988                         if(iter->second.check_type(value_desc.get_value_type()))
989                         {
990                                 convert_menu->items().push_back(Gtk::Menu_Helpers::MenuElem(iter->second.local_name,
991                                         sigc::hide_return(
992                                                 sigc::bind(
993                                                         sigc::bind(
994                                                                 sigc::mem_fun(*canvas_interface.get(),&synfigapp::CanvasInterface::convert),
995                                                                 iter->first
996                                                         ),
997                                                         value_desc
998                                                 )
999                                         )
1000                                 ));
1001                         }
1002                 }
1003
1004                 parammenu.items().push_back(Gtk::Menu_Helpers::StockMenuElem(Gtk::Stock::CONVERT,*convert_menu));
1005         }
1006
1007         if(param_list2.empty())
1008                 add_actions_to_menu(&parammenu, param_list,synfigapp::Action::CATEGORY_VALUEDESC|synfigapp::Action::CATEGORY_VALUENODE);
1009         else
1010                 add_actions_to_menu(&parammenu, param_list2,param_list,synfigapp::Action::CATEGORY_VALUEDESC|synfigapp::Action::CATEGORY_VALUENODE);
1011
1012         if(value_desc.get_value_type()==ValueBase::TYPE_BLINEPOINT && value_desc.is_value_node() && ValueNode_Composite::Handle::cast_dynamic(value_desc.get_value_node()))
1013         {
1014                 value_desc=synfigapp::ValueDesc(ValueNode_Composite::Handle::cast_dynamic(value_desc.get_value_node()),0);
1015         }
1016
1017         if(value_desc.is_value_node() && ValueNode_Animated::Handle::cast_dynamic(value_desc.get_value_node()))
1018         {
1019                 ValueNode_Animated::Handle value_node(ValueNode_Animated::Handle::cast_dynamic(value_desc.get_value_node()));
1020
1021                 try
1022                 {
1023                         WaypointList::iterator iter(value_node->find(canvas->get_time()));
1024                         parammenu.items().push_back(Gtk::Menu_Helpers::MenuElem(_("Edit Waypoint"),
1025                                 sigc::bind(
1026                                         sigc::bind(
1027                                                 sigc::bind(
1028                                                         sigc::mem_fun(*find_canvas_view(canvas),&studio::CanvasView::on_waypoint_clicked),
1029                                                         -1
1030                                                 ),
1031                                                 *iter
1032                                         ),
1033                                         value_desc
1034                                 )
1035                         ));
1036                 }
1037                 catch(...)
1038                 {
1039                 }
1040         }
1041 }
1042
1043 void
1044 edit_several_waypoints(etl::handle<CanvasView> canvas_view, std::list<synfigapp::ValueDesc> value_desc_list)
1045 {
1046         etl::handle<synfigapp::CanvasInterface> canvas_interface(canvas_view->canvas_interface());
1047
1048         Gtk::Dialog dialog(
1049                 "Edit Multiple Waypoints",              // Title
1050                 true,           // Modal
1051                 true            // use_separator
1052         );
1053
1054         Widget_WaypointModel widget_waypoint_model;
1055         widget_waypoint_model.show();
1056
1057         dialog.get_vbox()->pack_start(widget_waypoint_model);
1058
1059
1060         dialog.add_button(Gtk::StockID("gtk-apply"),1);
1061         dialog.add_button(Gtk::StockID("gtk-cancel"),0);
1062         dialog.show();
1063
1064         DEBUGPOINT();
1065         if(dialog.run()==0 || widget_waypoint_model.get_waypoint_model().is_trivial())
1066                 return;
1067         DEBUGPOINT();
1068         synfigapp::Action::PassiveGrouper group(canvas_interface->get_instance().get(),_("Set Waypoints"));
1069
1070         std::list<synfigapp::ValueDesc>::iterator iter;
1071         for(iter=value_desc_list.begin();iter!=value_desc_list.end();++iter)
1072         {
1073                 synfigapp::ValueDesc value_desc(*iter);
1074
1075                 if(!value_desc.is_valid())
1076                         continue;
1077
1078                 ValueNode_Animated::Handle value_node;
1079
1080                 // If this value isn't a ValueNode_Animated, but
1081                 // it is somewhat constant, then go ahead and convert
1082                 // it to a ValueNode_Animated.
1083                 if(!value_desc.is_value_node() || ValueNode_Const::Handle::cast_dynamic(value_desc.get_value_node()))
1084                 {
1085                         ValueBase value;
1086                         if(value_desc.is_value_node())
1087                                 value=ValueNode_Const::Handle::cast_dynamic(value_desc.get_value_node())->get_value();
1088                         else
1089                                 value=value_desc.get_value();
1090
1091                         value_node=ValueNode_Animated::create(value,canvas_interface->get_time());
1092
1093                         synfigapp::Action::Handle action;
1094
1095                         if(!value_desc.is_value_node())
1096                         {
1097                                 action=synfigapp::Action::create("value_desc_connect");
1098                                 action->set_param("dest",value_desc);
1099                                 action->set_param("src",ValueNode::Handle(value_node));
1100                         }
1101                         else
1102                         {
1103                                 action=synfigapp::Action::create("value_node_replace");
1104                                 action->set_param("dest",value_desc.get_value_node());
1105                                 action->set_param("src",ValueNode::Handle(value_node));
1106                         }
1107
1108                         action->set_param("canvas",canvas_view->get_canvas());
1109                         action->set_param("canvas_interface",canvas_interface);
1110
1111
1112                         if(!canvas_interface->get_instance()->perform_action(action))
1113                         {
1114                                 canvas_view->get_ui_interface()->error(_("Unable to convert to animated waypoint"));
1115                                 group.cancel();
1116                                 return;
1117                         }
1118                 }
1119                 else
1120                 {
1121                         if(value_desc.is_value_node())
1122                                 value_node=ValueNode_Animated::Handle::cast_dynamic(value_desc.get_value_node());
1123                 }
1124
1125
1126                 if(value_node)
1127                 {
1128
1129                         synfigapp::Action::Handle action(synfigapp::Action::create("waypoint_set_smart"));
1130
1131                         if(!action)
1132                         {
1133                                 canvas_view->get_ui_interface()->error(_("Unable to find waypoint_set_smart action"));
1134                                 group.cancel();
1135                                 return;
1136                         }
1137
1138
1139                         action->set_param("canvas",canvas_view->get_canvas());
1140                         action->set_param("canvas_interface",canvas_interface);
1141                         action->set_param("value_node",ValueNode::Handle(value_node));
1142                         action->set_param("time",canvas_interface->get_time());
1143                         action->set_param("model",widget_waypoint_model.get_waypoint_model());
1144
1145                         if(!canvas_interface->get_instance()->perform_action(action))
1146                         {
1147                                 canvas_view->get_ui_interface()->error(_("Unable to set a specific waypoint"));
1148                                 group.cancel();
1149                                 return;
1150                         }
1151                 }
1152                 else
1153                 {
1154                         //get_canvas_view()->get_ui_interface()->error(_("Unable to animate a specific valuedesc"));
1155                         //group.cancel();
1156                         //return;
1157                 }
1158
1159         }
1160 }
1161
1162 void
1163 Instance::make_param_menu(Gtk::Menu *menu,synfig::Canvas::Handle canvas,const std::list<synfigapp::ValueDesc>& value_desc_list)
1164 {
1165         etl::handle<synfigapp::CanvasInterface> canvas_interface(find_canvas_interface(canvas));
1166
1167         synfigapp::Action::ParamList param_list;
1168         param_list=canvas_interface->generate_param_list(value_desc_list);
1169
1170         add_actions_to_menu(menu, param_list,synfigapp::Action::CATEGORY_VALUEDESC|synfigapp::Action::CATEGORY_VALUENODE);
1171
1172         // Add the edit waypoints option if that might be useful
1173         if(canvas->rend_desc().get_time_end()-Time::epsilon()>canvas->rend_desc().get_time_start())
1174         {
1175                 menu->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Edit Waypoints"),
1176                         sigc::bind(
1177                                 sigc::bind(
1178                                         sigc::ptr_fun(
1179                                                 &edit_several_waypoints
1180                                         ),
1181                                         value_desc_list
1182                                 ),
1183                                 find_canvas_view(canvas)
1184                         )
1185                 ));
1186         }
1187 }