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