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