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