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