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