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