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