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