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