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