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