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