Merge branch 'master' into nikitakit_restructure
[synfig.git] / synfig-studio / src / gui / render.cpp
1 /* === S Y N F I G ========================================================= */
2 /*!     \file gtkmm/render.cpp
3 **      \brief Template File
4 **
5 **      $Id$
6 **
7 **      \legal
8 **      Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
9 **      Copyright (c) 2007, 2008 Chris Moore
10 **
11 **      This package is free software; you can redistribute it and/or
12 **      modify it under the terms of the GNU General Public License as
13 **      published by the Free Software Foundation; either version 2 of
14 **      the License, or (at your option) any later version.
15 **
16 **      This package is distributed in the hope that it will be useful,
17 **      but WITHOUT ANY WARRANTY; without even the implied warranty of
18 **      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 **      General Public License for more details.
20 **      \endlegal
21 */
22 /* ========================================================================= */
23
24 /* === H E A D E R S ======================================================= */
25
26 #ifdef USING_PCH
27 #       include "pch.h"
28 #else
29 #ifdef HAVE_CONFIG_H
30 #       include <config.h>
31 #endif
32
33 #include "render.h"
34 #include "app.h"
35 #include <gtkmm/frame.h>
36 #include <gtkmm/alignment.h>
37 #include <synfig/target_scanline.h>
38 #include <synfig/canvas.h>
39 #include "asyncrenderer.h"
40 #include "dialogs/dialog_targetparam.h"
41
42 #include "general.h"
43
44 #include <fstream>
45
46 #endif
47
48 /* === U S I N G =========================================================== */
49
50 using namespace std;
51 using namespace etl;
52 using namespace synfig;
53 using namespace studio;
54
55 /* === M A C R O S ========================================================= */
56
57 /* === G L O B A L S ======================================================= */
58
59 /* === P R O C E D U R E S ================================================= */
60
61 /* === M E T H O D S ======================================================= */
62
63 RenderSettings::RenderSettings(Gtk::Window& parent, etl::handle<synfigapp::CanvasInterface> canvas_interface):
64         Gtk::Dialog(_("Render Settings"),parent,false,true),
65         canvas_interface_(canvas_interface),
66         adjustment_quality(3,0,9),
67         entry_quality(adjustment_quality,1,0),
68         adjustment_antialias(1,1,31),
69         entry_antialias(adjustment_antialias,1,0),
70         toggle_single_frame(_("Use _current frame"), true),
71         tparam("mpeg4",200)
72 {
73         widget_rend_desc.show();
74         widget_rend_desc.signal_changed().connect(sigc::mem_fun(*this,&studio::RenderSettings::on_rend_desc_changed));
75         widget_rend_desc.set_rend_desc(canvas_interface_->get_canvas()->rend_desc());
76
77         canvas_interface->signal_rend_desc_changed().connect(sigc::mem_fun(*this,&RenderSettings::on_rend_desc_changed));
78
79         menu_target=manage(new class Gtk::Menu());
80
81         menu_target->items().push_back(Gtk::Menu_Helpers::MenuElem(_("Auto"),
82                         sigc::bind(sigc::mem_fun(*this,&RenderSettings::set_target),String())
83                 ));
84
85         synfig::Target::Book::iterator iter;
86         synfig::Target::Book book(synfig::Target::book());
87
88         for(iter=book.begin();iter!=book.end();iter++)
89         {
90                 menu_target->items().push_back(Gtk::Menu_Helpers::MenuElem(iter->first,
91                         sigc::bind(sigc::mem_fun(*this,&RenderSettings::set_target),iter->first)
92                 ));
93         }
94         optionmenu_target.set_menu(*menu_target);
95
96         optionmenu_target.set_history(0);
97
98         Gtk::Alignment *dialogPadding = manage(new Gtk::Alignment(0, 0, 1, 1));
99         dialogPadding->set_padding(12, 12, 12, 12);
100         get_vbox()->pack_start(*dialogPadding, false, false, 0);
101
102         Gtk::VBox *dialogBox = manage(new Gtk::VBox(false, 12));
103         dialogPadding->add(*dialogBox);
104
105         Gtk::Button *choose_button(manage(new class Gtk::Button(Gtk::StockID(_("Choose...")))));
106         choose_button->show();
107         choose_button->signal_clicked().connect(sigc::mem_fun(*this, &studio::RenderSettings::on_choose_pressed));
108
109         tparam_button=manage(new class Gtk::Button(Gtk::StockID(_("Parameters..."))));
110         tparam_button->show();
111         tparam_button->set_sensitive(false);
112         tparam_button->signal_clicked().connect(sigc::mem_fun(*this, &studio::RenderSettings::on_targetparam_pressed));
113
114         Gtk::Frame *target_frame=manage(new Gtk::Frame(_("Target")));
115         target_frame->set_shadow_type(Gtk::SHADOW_NONE);
116         ((Gtk::Label *) target_frame->get_label_widget())->set_markup(_("<b>Target</b>"));
117         dialogBox->pack_start(*target_frame);
118         Gtk::Alignment *targetPadding = manage(new Gtk::Alignment(0, 0, 1, 1));
119         targetPadding->set_padding(6, 0, 24, 0);
120         target_frame->add(*targetPadding);
121
122         Gtk::Table *target_table = manage(new Gtk::Table(2, 3, false));
123         target_table->set_row_spacings(6);
124         target_table->set_col_spacings(12);
125         targetPadding->add(*target_table);
126
127         Gtk::Label *filenameLabel = manage(new Gtk::Label(_("_Filename"), true));
128         filenameLabel->set_alignment(0, 0.5);
129         filenameLabel->set_mnemonic_widget(entry_filename);
130         target_table->attach(*filenameLabel, 0, 1, 0, 1, Gtk::SHRINK|Gtk::FILL, Gtk::SHRINK|Gtk::FILL, 0, 0);
131         target_table->attach(entry_filename, 1, 2, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::SHRINK|Gtk::FILL, 0, 0);
132         target_table->attach(*choose_button, 2, 3, 0, 1, Gtk::SHRINK|Gtk::FILL, Gtk::SHRINK|Gtk::FILL, 0, 0);
133
134         Gtk::Label *targetLabel = manage(new Gtk::Label(_("_Target"), true));
135         targetLabel->set_alignment(0, 0.5);
136         targetLabel->set_mnemonic_widget(optionmenu_target);
137         target_table->attach(*targetLabel, 0, 1, 1, 2, Gtk::SHRINK|Gtk::FILL, Gtk::SHRINK|Gtk::FILL, 0, 0);
138         target_table->attach(optionmenu_target, 1, 2, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::SHRINK|Gtk::FILL, 0, 0);
139         target_table->attach(*tparam_button, 2, 3, 1, 2, Gtk::SHRINK|Gtk::FILL, Gtk::SHRINK|Gtk::FILL, 0, 0);
140
141         toggle_single_frame.signal_toggled().connect(sigc::mem_fun(*this, &studio::RenderSettings::on_single_frame_toggle));
142
143         Gtk::Frame *settings_frame=manage(new Gtk::Frame(_("Settings")));
144         settings_frame->set_shadow_type(Gtk::SHADOW_NONE);
145         ((Gtk::Label *) settings_frame->get_label_widget())->set_markup(_("<b>Settings</b>"));
146         dialogBox->pack_start(*settings_frame);
147
148         Gtk::Alignment *settingsPadding = manage(new Gtk::Alignment(0, 0, 1, 1));
149         settingsPadding->set_padding(6, 0, 24, 0);
150         settings_frame->add(*settingsPadding);
151
152         Gtk::Table *settings_table=manage(new Gtk::Table(2,2,false));
153         settings_table->set_row_spacings(6);
154         settings_table->set_col_spacings(12);
155         settingsPadding->add(*settings_table);
156
157         Gtk::Label *qualityLabel = manage(new Gtk::Label(_("_Quality"), true));
158         qualityLabel->set_alignment(0, 0.5);
159         qualityLabel->set_mnemonic_widget(entry_quality);
160         settings_table->attach(*qualityLabel, 0, 1, 0, 1, Gtk::SHRINK|Gtk::FILL, Gtk::SHRINK|Gtk::FILL, 0, 0);
161         settings_table->attach(entry_quality, 1, 2, 0, 1, Gtk::EXPAND|Gtk::FILL, Gtk::SHRINK|Gtk::FILL, 0, 0);
162
163         Gtk::Label *antiAliasLabel = manage(new Gtk::Label(_("_Anti-Aliasing"), true));
164         antiAliasLabel->set_alignment(0, 0.5);
165         antiAliasLabel->set_mnemonic_widget(entry_antialias);
166         settings_table->attach(*antiAliasLabel, 0, 1, 1, 2, Gtk::SHRINK|Gtk::FILL, Gtk::SHRINK|Gtk::FILL, 0, 0);
167         settings_table->attach(entry_antialias, 1, 2, 1, 2, Gtk::EXPAND|Gtk::FILL, Gtk::SHRINK|Gtk::FILL, 0, 0);
168
169         toggle_single_frame.set_alignment(0, 0.5);
170         settings_table->attach(toggle_single_frame, 0, 2, 2, 3, Gtk::EXPAND|Gtk::FILL, Gtk::SHRINK|Gtk::FILL, 0, 0);
171
172         dialogBox->pack_start(widget_rend_desc);
173
174
175         Gtk::Button *render_button(manage(new class Gtk::Button(Gtk::StockID(_("Render")))));
176         render_button->show();
177         add_action_widget(*render_button,1);
178         render_button->signal_clicked().connect(sigc::mem_fun(*this, &studio::RenderSettings::on_render_pressed));
179
180         Gtk::Button *cancel_button(manage(new class Gtk::Button(Gtk::StockID("gtk-cancel"))));
181         cancel_button->show();
182         add_action_widget(*cancel_button,0);
183         cancel_button->signal_clicked().connect(sigc::mem_fun(*this, &studio::RenderSettings::on_cancel_pressed));
184
185         //set_default_response(1);
186
187         set_title(_("Render Settings")+String(" - ")+canvas_interface_->get_canvas()->get_name());
188
189         toggle_single_frame.set_active(false);
190         widget_rend_desc.enable_time_section();
191
192         set_entry_filename();
193
194         get_vbox()->show_all();
195 }
196
197 RenderSettings::~RenderSettings()
198 {
199 }
200
201 void
202 RenderSettings::set_entry_filename()
203 {
204         String filename(filename_sans_extension(canvas_interface_->get_canvas()->get_file_name()));
205
206         // if this isn't the root canvas, append (<canvasname>) to the filename
207         etl::handle<synfig::Canvas> canvas = canvas_interface_->get_canvas();
208         if (!canvas->is_root())
209         {
210                 if(canvas->get_name().empty())
211                         filename+=" ("+canvas->get_id()+')';
212                 else
213                         filename+=" ("+canvas->get_name()+')';
214         }
215
216         filename += ".png";
217
218         try
219         {
220                 entry_filename.set_text((filename));
221         }
222         catch(...)
223         {
224                 synfig::warning("Averted crash!");
225                 entry_filename.set_text("output.png");
226         }
227 }
228
229 void
230 RenderSettings::on_rend_desc_changed()
231 {
232         widget_rend_desc.set_rend_desc(canvas_interface_->get_canvas()->rend_desc());
233 }
234
235 void
236 RenderSettings::set_target(synfig::String name)
237 {
238         target_name=name;
239         tparam_button->set_sensitive(target_name.compare("ffmpeg")?false:true);
240 }
241
242 void
243 RenderSettings::on_choose_pressed()
244 {
245         String filename=entry_filename.get_text();
246         if(App::dialog_save_file("Save Render As", filename, RENDER_DIR_PREFERENCE))
247                 entry_filename.set_text(filename);
248 }
249
250 void
251 RenderSettings::on_targetparam_pressed()
252 {
253         Dialog_TargetParam *dialogtp = new Dialog_TargetParam(*this, tparam);
254         if(dialogtp->run()==Gtk::RESPONSE_OK)
255                 tparam=dialogtp->get_tparam();
256
257         delete dialogtp;
258 }
259
260 void
261 RenderSettings::on_render_pressed()
262 {
263         String filename=entry_filename.get_text();
264         synfig::String calculated_target_name(target_name);
265
266         if(filename.empty())
267         {
268                 canvas_interface_->get_ui_interface()->error(_("You must supply a filename!"));
269                 return;
270         }
271
272         // If the target type is not yet defined,
273         // try to figure it out from the outfile.
274         if(calculated_target_name.empty())
275         {
276                 try
277                 {
278                         String ext(filename_extension(filename));
279                         if (ext.size()) ext=ext.substr(1); // skip initial '.'
280                         synfig::info("render target filename: '%s'; extension: '%s'", filename.c_str(), ext.c_str());
281                         if(Target::ext_book().count(ext))
282                         {
283                                 calculated_target_name=Target::ext_book()[ext];
284                                 synfig::info("'%s' is a known extension - using target '%s'", ext.c_str(), calculated_target_name.c_str());
285                         }
286                         else
287                         {
288                                 calculated_target_name=ext;
289                                 synfig::info("unknown extension");
290                         }
291                 }
292                 catch(std::runtime_error x)
293                 {
294                         canvas_interface_->get_ui_interface()->error(_("Unable to determine proper target from filename."));
295                         return;
296                 }
297         }
298
299         if(filename.empty() && calculated_target_name!="null")
300         {
301                 canvas_interface_->get_ui_interface()->error(_("A filename is required for this target"));
302                 return;
303         }
304
305         Target::Handle target=Target::create(calculated_target_name,filename, tparam);
306         if(!target)
307         {
308                 canvas_interface_->get_ui_interface()->error(_("Unable to create target for ")+filename);
309                 return;
310         }
311         // This is the only way I've found to avoid send a non writable
312         // filename path to the renderer.
313         fstream filetest (filename.c_str(), fstream::out);
314         if (filetest.fail())
315         {
316                 canvas_interface_->get_ui_interface()->error(_("Unable to create file for ")+filename);
317                 return;
318         }
319
320         hide();
321
322         target->set_canvas(canvas_interface_->get_canvas());
323         RendDesc rend_desc(widget_rend_desc.get_rend_desc());
324         rend_desc.set_antialias((int)adjustment_antialias.get_value());
325
326         // If we are to only render the current frame
327         if(toggle_single_frame.get_active())
328                 rend_desc.set_time(canvas_interface_->get_time());
329
330         target->set_rend_desc(&rend_desc);
331         target->set_quality((int)adjustment_quality.get_value());
332         if( !target->init() ){
333                 canvas_interface_->get_ui_interface()->error(_("Target initialization failure"));
334                 return;
335         }
336
337         canvas_interface_->get_ui_interface()->task(_("Rendering ")+filename);
338
339         if(async_renderer)
340         {
341                 async_renderer->stop();
342                 async_renderer.detach();
343         }
344         async_renderer=new AsyncRenderer(target);
345         async_renderer->signal_finished().connect( sigc::mem_fun(*this,&RenderSettings::on_finished));
346         async_renderer->start();
347         /*
348         if(!target->render(canvas_interface_->get_ui_interface().get()))
349         {
350                 canvas_interface_->get_ui_interface()->error(_("Render Failure"));
351                 canvas_interface_->get_ui_interface()->amount_complete(0,10000);
352                 return;
353         }
354
355         // Success!
356         canvas_interface_->get_ui_interface()->task(filename+_(" rendered successfully"));
357         canvas_interface_->get_ui_interface()->amount_complete(0,10000);
358         */
359         return;
360 }
361
362 void
363 RenderSettings::on_finished()
364 {
365         canvas_interface_->get_ui_interface()->task(_("File rendered successfully"));
366         canvas_interface_->get_ui_interface()->amount_complete(0,10000);
367 }
368
369 void
370 RenderSettings::on_cancel_pressed()
371 {
372         hide();
373 }
374
375 void
376 RenderSettings::on_single_frame_toggle()
377 {
378         if(toggle_single_frame.get_active())
379                 widget_rend_desc.disable_time_section();
380         else
381                 widget_rend_desc.enable_time_section();
382 }