1 /*
2  * TilEm II
3  *
4  * Copyright (c) 2010-2011 Thibault Duponchelle
5  * Copyright (c) 2011 Benjamin Moody
6  *
7  * This program is free software: you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License as
9  * published by the Free Software Foundation, either version 3 of the
10  * License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful, but
13  * WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
19  */
20 
21 #ifdef HAVE_CONFIG_H
22 # include <config.h>
23 #endif
24 
25 #include <stdio.h>
26 #include <stdlib.h>
27 #include <string.h>
28 #include <gtk/gtk.h>
29 #include <glib/gstdio.h>
30 #include <ticalcs.h>
31 #include <tilem.h>
32 
33 #include "gui.h"
34 #include "files.h"
35 #include "filedlg.h"
36 #include "msgbox.h"
37 
38 #define DEFAULT_WIDTH_96    192
39 #define DEFAULT_HEIGHT_96   128
40 #define DEFAULT_WIDTH_128   256
41 #define DEFAULT_HEIGHT_128  128
42 #define DEFAULT_FORMAT      "png"
43 
44 struct imgsize {
45 	int width;
46 	int height;
47 };
48 
49 static const struct imgsize normal_sizes[] =
50 	{ { 96, 64 }, { 192, 128 }, { 288, 192 } };
51 
52 static const struct imgsize wide_sizes[] =
53 	/* actual aspect ratio is 92:55 or 1.673:1 */
54 	{ { 128, 64 }, { 128, 77 },
55 	  { 214, 128 }, { 256, 128 }, { 256, 153 },
56 	  { 321, 192 }, { 384, 192 } };
57 
58 static void grab_screen(GtkButton *btn, TilemScreenshotDialog *ssdlg);
59 static void begin_animation(GtkButton *btn, TilemScreenshotDialog *ssdlg);
60 static void end_animation(GtkButton *btn, TilemScreenshotDialog *ssdlg);
61 static gboolean save_output(TilemScreenshotDialog *ssdlg);
62 
63 static char* find_free_filename(const char* directory,
64                                 const char* filename,
65                                 const char* extension);
66 
67 /* Test if the calc has a wide screen (ti86) */
is_wide_screen(TilemCalcEmulator * emu)68 static gboolean is_wide_screen(TilemCalcEmulator *emu)
69 {
70 	g_return_val_if_fail(emu != NULL, FALSE);
71 	g_return_val_if_fail(emu->calc != NULL, FALSE);
72 
73 	return (emu->calc->hw.lcdwidth == 128);
74 }
75 
76 /* Quick screenshot: save a screenshot with predefined settings,
77    without prompting the user */
quick_screenshot(TilemEmulatorWindow * ewin)78 void quick_screenshot(TilemEmulatorWindow *ewin)
79 {
80 	char *folder, *filename, *format;
81 	int grayscale, w96, h96, w128, h128, width, height;
82 	TilemAnimation *anim;
83 	GError *err = NULL;
84 	GdkColor fg, bg;
85 
86 	tilem_config_get("screenshot",
87 	                 "directory/f", &folder,
88 	                 "format/s", &format,
89 	                 "grayscale/b=1", &grayscale,
90 	                 "width_96x64/i", &w96,
91 	                 "height_96x64/i", &h96,
92 	                 "width_128x64/i", &w128,
93 	                 "height_128x64/i", &h128,
94 	                 "foreground/c=#000", &fg,
95 	                 "background/c=#fff", &bg,
96 	                 NULL);
97 
98 	anim = tilem_calc_emulator_get_screenshot(ewin->emu, grayscale);
99 	if (!anim) {
100 		g_free(folder);
101 		g_free(format);
102 		return;
103 	}
104 
105 	if (is_wide_screen(ewin->emu)) {
106 		width = (w128 > 0 ? w128 : DEFAULT_WIDTH_128);
107 		height = (h128 > 0 ? h128 : DEFAULT_HEIGHT_128);
108 	}
109 	else {
110 		width = (w96 > 0 ? w96 : DEFAULT_WIDTH_96);
111 		height = (h96 > 0 ? h96 : DEFAULT_HEIGHT_96);
112 	}
113 
114 	tilem_animation_set_size(anim, width, height);
115 	tilem_animation_set_colors(anim, &fg, &bg);
116 
117 	if (!folder)
118 		folder = get_config_file_path("screenshots", NULL);
119 
120 	if (!format)
121 		format = g_strdup(DEFAULT_FORMAT);
122 
123 	g_mkdir_with_parents(folder, 0755);
124 
125 	filename = find_free_filename(folder, "screenshot", format);
126 	if (!filename) {
127 		g_free(folder);
128 		g_free(format);
129 		g_object_unref(anim);
130 		return;
131 	}
132 
133 	printf("screenshot saved : %s\n", filename);
134 
135 	if (!tilem_animation_save(anim, filename, format, NULL, NULL, &err)) {
136 		messagebox01(ewin->window, GTK_MESSAGE_ERROR,
137 		             "Unable to save screenshot",
138 		             "%s", err->message);
139 		g_error_free(err);
140 	}
141 
142 	g_object_unref(anim);
143 	g_free(filename);
144 	g_free(folder);
145 	g_free(format);
146 }
147 
148 /* Look for a free filename by testing [folder]/[basename]000.[extension] to [folder]/[basename]999.[extension]
149    Return a newly allocated string if success
150    Return null if no filename found */
find_free_filename(const char * folder,const char * basename,const char * extension)151 static char* find_free_filename(const char* folder,
152                                 const char* basename,
153                                 const char* extension)
154 {
155 	int i;
156 	char *filename, *prefix;
157 
158 	if(folder)
159 		prefix = g_build_filename(folder, basename, NULL);
160 	else
161 		prefix = g_build_filename(basename, NULL);
162 
163 	/* I do not use a while and limit number to 1000 because for any reason, if there's a problem in this scope
164 	   I don't want to freeze tilem (if tilem don't find a free filename and never return anything)
165 	   Limit to 1000 prevent this problem but if you prefer we could use a while wich wait a valid filename... */
166 	for(i=0; i<999; i++) {
167 		filename = g_strdup_printf("%s%03d.%s", prefix, i, extension);
168 		if(!g_file_test(filename, G_FILE_TEST_IS_REGULAR)) {
169 			g_free(prefix);
170 			return filename;
171 		}
172 		g_free(filename);
173 	}
174 
175 	g_free(prefix);
176 	return NULL;
177 }
178 
179 /* Change the review image to set the current animation */
set_current_animation(TilemScreenshotDialog * ssdlg,TilemAnimation * anim)180 static void set_current_animation(TilemScreenshotDialog *ssdlg,
181                                   TilemAnimation *anim)
182 {
183 	GtkImage *img = GTK_IMAGE(ssdlg->screenshot_preview_image);
184 	int width, height;
185 	GdkColor fg, bg;
186 	gdouble speed;
187 
188 	if (anim)
189 		g_object_ref(anim);
190 	if (ssdlg->current_anim)
191 		g_object_unref(ssdlg->current_anim);
192 	ssdlg->current_anim = anim;
193 
194 	if (!anim) {
195 		gtk_image_set_from_animation(img, NULL);
196 		gtk_dialog_set_response_sensitive(GTK_DIALOG(ssdlg->window),
197 		                                  GTK_RESPONSE_ACCEPT, FALSE);
198 	}
199 	else {
200 		width = gtk_spin_button_get_value_as_int
201 			(GTK_SPIN_BUTTON(ssdlg->width_spin));
202 		height = gtk_spin_button_get_value_as_int
203 			(GTK_SPIN_BUTTON(ssdlg->height_spin));
204 		tilem_animation_set_size(anim, width, height);
205 
206 		gtk_color_button_get_color
207 			(GTK_COLOR_BUTTON(ssdlg->foreground_color), &fg);
208 		gtk_color_button_get_color
209 			(GTK_COLOR_BUTTON(ssdlg->background_color), &bg);
210 		tilem_animation_set_colors(anim, &fg, &bg);
211 
212 		speed = gtk_spin_button_get_value
213 			(GTK_SPIN_BUTTON(ssdlg->animation_speed));
214 		tilem_animation_set_speed(anim, speed);
215 
216 		gtk_image_set_from_animation(img, GDK_PIXBUF_ANIMATION(anim));
217 
218 		/* Need to call gtk_widget_show because we hide it
219 		   while recording */
220 		gtk_widget_show(ssdlg->screenshot_preview_image);
221 
222 		gtk_dialog_set_response_sensitive(GTK_DIALOG(ssdlg->window),
223 		                                  GTK_RESPONSE_ACCEPT, TRUE);
224 	}
225 }
226 
dialog_response(G_GNUC_UNUSED GtkDialog * dialog,gint response,gpointer data)227 static void dialog_response(G_GNUC_UNUSED GtkDialog *dialog, gint response, gpointer data)
228 {
229 	TilemScreenshotDialog *ssdlg = data;
230 
231 	if (response == GTK_RESPONSE_ACCEPT) {
232 		if (!save_output(ssdlg))
233 			return;
234 	}
235 
236 	gtk_widget_hide(GTK_WIDGET(dialog));
237 	end_animation(NULL, ssdlg);
238 	set_current_animation(ssdlg, NULL);
239 }
240 
set_size_spin_buttons(TilemScreenshotDialog * ssdlg,int width,int height)241 static void set_size_spin_buttons(TilemScreenshotDialog *ssdlg,
242                                   int width, int height)
243 {
244 	gtk_spin_button_set_value(GTK_SPIN_BUTTON(ssdlg->width_spin), width);
245 	gtk_spin_button_set_value(GTK_SPIN_BUTTON(ssdlg->height_spin), height);
246 }
247 
248 enum {
249 	COL_TEXT,
250 	COL_WIDTH,
251 	COL_HEIGHT
252 };
253 
animation_speed_changed(GtkSpinButton * animation_speed,gpointer data)254 static void animation_speed_changed(GtkSpinButton *animation_speed,
255                                     gpointer data)
256 {
257 	TilemScreenshotDialog *ssdlg = data;
258 	TilemAnimation * anim = ssdlg->current_anim;
259 	gdouble value = gtk_spin_button_get_value(animation_speed);
260 	tilem_animation_set_speed(anim, value);
261 }
262 
263 /* Combo box changed.  Update spin buttons accordingly. */
size_combo_changed(GtkComboBox * combo,TilemScreenshotDialog * ssdlg)264 static void size_combo_changed(GtkComboBox *combo,
265                                TilemScreenshotDialog *ssdlg)
266 {
267 	GtkTreeModel *model;
268 	GtkTreeIter iter;
269 	int width, height;
270 
271 	if (gtk_combo_box_get_active_iter(combo, &iter)) {
272 		model = gtk_combo_box_get_model(combo);
273 		gtk_tree_model_get(model, &iter,
274 		                   COL_WIDTH, &width,
275 		                   COL_HEIGHT, &height,
276 		                   -1);
277 		if (width && height)
278 			set_size_spin_buttons(ssdlg, width, height);
279 	}
280 }
281 
size_spin_changed(G_GNUC_UNUSED GtkSpinButton * sb,TilemScreenshotDialog * ssdlg)282 static void size_spin_changed(G_GNUC_UNUSED GtkSpinButton *sb,
283                               TilemScreenshotDialog *ssdlg)
284 {
285 	GtkComboBox *combo = GTK_COMBO_BOX(ssdlg->ss_size_combo);
286 	GtkTreeModel *model;
287 	GtkTreeIter iter;
288 	int width, height, w, h;
289 
290 	model = gtk_combo_box_get_model(combo);
291 	if (!model || !gtk_tree_model_get_iter_first(model, &iter))
292 		return;
293 
294 	width = gtk_spin_button_get_value_as_int
295 		(GTK_SPIN_BUTTON(ssdlg->width_spin));
296 	height = gtk_spin_button_get_value_as_int
297 		(GTK_SPIN_BUTTON(ssdlg->height_spin));
298 
299 	do {
300 		gtk_tree_model_get(model, &iter,
301 		                   COL_WIDTH, &w,
302 		                   COL_HEIGHT, &h,
303 		                   -1);
304 
305 		if ((w == 0 && h == 0) || (w == width && h == height)) {
306 			gtk_combo_box_set_active_iter(combo, &iter);
307 			break;
308 		}
309 	} while (gtk_tree_model_iter_next(model, &iter));
310 
311 	set_current_animation(ssdlg, ssdlg->current_anim);
312 }
313 
fill_size_combobox(GtkComboBox * combo,const struct imgsize * sizes,int nsizes)314 static void fill_size_combobox(GtkComboBox *combo,
315                                const struct imgsize *sizes,
316                                int nsizes)
317 {
318 	GtkListStore *store;
319 	GtkTreeIter iter;
320 	int i;
321 	char *s;
322 
323 	store = gtk_list_store_new(3, G_TYPE_STRING, G_TYPE_INT, G_TYPE_INT);
324 
325 	for (i = 0; i < nsizes; i++) {
326 		s = g_strdup_printf("%d \303\227 %d",
327 		                    sizes[i].width,
328 		                    sizes[i].height);
329 
330 		gtk_list_store_append(store, &iter);
331 		gtk_list_store_set(store, &iter,
332 		                   COL_TEXT, s,
333 		                   COL_WIDTH, sizes[i].width,
334 		                   COL_HEIGHT, sizes[i].height,
335 		                   -1);
336 		g_free(s);
337 	}
338 
339 	gtk_list_store_append(store, &iter);
340 	gtk_list_store_set(store, &iter,
341 	                   COL_TEXT, "Custom",
342 	                   COL_WIDTH, 0,
343 	                   COL_HEIGHT, 0,
344 	                   -1);
345 
346 	gtk_combo_box_set_model(GTK_COMBO_BOX(combo), GTK_TREE_MODEL(store));
347 }
348 
349 /* This method is called when a color is set (foreground or background)
350  * It set a new palette based on new custom colors
351  * It refresh the screen to print new colors
352  */
color_changed(G_GNUC_UNUSED GtkSpinButton * sb,TilemScreenshotDialog * ssdlg)353 static void color_changed(G_GNUC_UNUSED GtkSpinButton *sb,
354                               TilemScreenshotDialog *ssdlg)
355 {
356 	set_current_animation(ssdlg, ssdlg->current_anim);
357 }
358 
359 /* Create the screenshot menu */
create_screenshot_window(TilemCalcEmulator * emu)360 static TilemScreenshotDialog * create_screenshot_window(TilemCalcEmulator *emu)
361 {
362 	TilemScreenshotDialog *ssdlg = g_slice_new0(TilemScreenshotDialog);
363 	GtkWidget *main_table, *vbox, *frame, *config_expander,
364 		*tbl, *lbl, *align;
365 	GtkCellRenderer *cell;
366 
367 	ssdlg->emu = emu;
368 
369 	ssdlg->window = gtk_dialog_new_with_buttons
370 		("Screenshot",
371 		 (emu->ewin ? GTK_WINDOW(emu->ewin->window) : NULL),
372 		 GTK_DIALOG_DESTROY_WITH_PARENT,
373 		 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
374 		 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
375 		 NULL);
376 
377 	gtk_window_set_resizable(GTK_WINDOW(ssdlg->window), FALSE);
378 
379 	gtk_dialog_set_alternative_button_order(GTK_DIALOG(ssdlg->window),
380 	                                        GTK_RESPONSE_ACCEPT,
381 	                                        GTK_RESPONSE_CANCEL,
382 	                                        -1);
383 
384 	gtk_dialog_set_default_response(GTK_DIALOG(ssdlg->window),
385 	                                GTK_RESPONSE_ACCEPT);
386 
387 	g_signal_connect(ssdlg->window, "response",
388 	                 G_CALLBACK(dialog_response), ssdlg);
389 
390 	g_signal_connect(ssdlg->window, "delete-event",
391 	                 G_CALLBACK(gtk_widget_hide_on_delete), NULL);
392 
393 	main_table = gtk_table_new(2, 2, FALSE);
394 	gtk_table_set_row_spacings(GTK_TABLE(main_table), 6);
395 	gtk_table_set_col_spacings(GTK_TABLE(main_table), 12);
396 	gtk_container_set_border_width(GTK_CONTAINER(main_table), 6);
397 
398 	/* Preview */
399 
400 	frame = gtk_frame_new("Preview");
401 	gtk_frame_set_shadow_type(GTK_FRAME(frame), GTK_SHADOW_NONE);
402 
403 	ssdlg->screenshot_preview_image = gtk_image_new();
404 	align = gtk_alignment_new(0.0, 0.0, 0.0, 0.0);
405 	gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 12, 0);
406 	gtk_container_add(GTK_CONTAINER(align), ssdlg->screenshot_preview_image);
407 
408 	gtk_container_add(GTK_CONTAINER(frame), align);
409 	gtk_table_attach(GTK_TABLE(main_table), frame, 0, 1, 0, 1,
410 	                 GTK_EXPAND | GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
411 
412 	/* Buttons */
413 
414 	vbox = gtk_vbutton_box_new();
415 	gtk_button_box_set_layout(GTK_BUTTON_BOX(vbox), GTK_BUTTONBOX_START);
416 	gtk_box_set_spacing(GTK_BOX(vbox), 6);
417 
418 	ssdlg->screenshot = gtk_button_new_with_mnemonic("_Grab");
419 	gtk_box_pack_start(GTK_BOX(vbox), ssdlg->screenshot, FALSE, FALSE, 0);
420 
421 	ssdlg->record = gtk_button_new_with_mnemonic("_Record");
422 	gtk_box_pack_start(GTK_BOX(vbox), ssdlg->record, FALSE, FALSE, 0);
423 
424 	ssdlg->stop = gtk_button_new_with_mnemonic("_Stop");
425 	gtk_box_pack_start(GTK_BOX(vbox), ssdlg->stop, FALSE, FALSE, 0);
426 	gtk_widget_set_sensitive(GTK_WIDGET(ssdlg->stop), FALSE);
427 
428 	gtk_table_attach(GTK_TABLE(main_table), vbox, 1, 2, 0, 2,
429 	                 GTK_FILL, GTK_EXPAND | GTK_FILL, 0, 0);
430 
431 	/* Options */
432 
433 	config_expander = gtk_expander_new("Options");
434 
435 	tbl = gtk_table_new(7, 2, FALSE);
436 	gtk_table_set_row_spacings(GTK_TABLE(tbl), 6);
437 	gtk_table_set_col_spacings(GTK_TABLE(tbl), 6);
438 
439 	ssdlg->grayscale_tb = gtk_check_button_new_with_mnemonic("Gra_yscale");
440 	gtk_table_attach(GTK_TABLE(tbl), ssdlg->grayscale_tb,
441 	                 0, 2, 0, 1, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
442 
443 	lbl = gtk_label_new_with_mnemonic("Image si_ze:");
444 	gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
445 	gtk_table_attach(GTK_TABLE(tbl), lbl,
446 	                 0, 1, 1, 2, GTK_FILL, GTK_FILL, 0, 0);
447 
448 	ssdlg->ss_size_combo = gtk_combo_box_new();
449 	cell = gtk_cell_renderer_text_new();
450 	gtk_cell_layout_pack_start(GTK_CELL_LAYOUT(ssdlg->ss_size_combo),
451 	                           cell, TRUE);
452 	gtk_cell_layout_set_attributes(GTK_CELL_LAYOUT(ssdlg->ss_size_combo),
453 	                               cell, "text", COL_TEXT, NULL);
454 	gtk_label_set_mnemonic_widget(GTK_LABEL(lbl), ssdlg->ss_size_combo);
455 	gtk_table_attach(GTK_TABLE(tbl), ssdlg->ss_size_combo,
456 	                 1, 2, 1, 2, GTK_EXPAND | GTK_FILL, GTK_FILL, 0, 0);
457 
458 	lbl = gtk_label_new_with_mnemonic("_Width:");
459 	gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
460 	gtk_table_attach(GTK_TABLE(tbl), lbl,
461 	                 0, 1, 2, 3, GTK_FILL, GTK_FILL, 0, 0);
462 
463 	ssdlg->width_spin = gtk_spin_button_new_with_range(1, 750, 1);
464 	gtk_label_set_mnemonic_widget(GTK_LABEL(lbl), ssdlg->width_spin);
465 	align = gtk_alignment_new(0.0, 0.5, 0.0, 1.0);
466 	gtk_container_add(GTK_CONTAINER(align), ssdlg->width_spin);
467 	gtk_table_attach(GTK_TABLE(tbl), align,
468 	                 1, 2, 2, 3, GTK_FILL, GTK_FILL, 0, 0);
469 
470 	lbl = gtk_label_new_with_mnemonic("_Height:");
471 	gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
472 	gtk_table_attach(GTK_TABLE(tbl), lbl,
473 	                 0, 1, 3, 4, GTK_FILL, GTK_FILL, 0, 0);
474 
475 	ssdlg->height_spin = gtk_spin_button_new_with_range(1, 500, 1);
476 	gtk_label_set_mnemonic_widget(GTK_LABEL(lbl), ssdlg->height_spin);
477 	align = gtk_alignment_new(0.0, 0.5, 0.0, 1.0);
478 	gtk_container_add(GTK_CONTAINER(align), ssdlg->height_spin);
479 	gtk_table_attach(GTK_TABLE(tbl), align,
480 	                 1, 2, 3, 4, GTK_FILL, GTK_FILL, 0, 0);
481 
482 
483 	lbl = gtk_label_new_with_mnemonic("Animation s_peed:");
484 	gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
485 	gtk_table_attach(GTK_TABLE(tbl), lbl,
486 	                 0, 1, 4, 5, GTK_FILL, GTK_FILL, 0, 0);
487 
488 	ssdlg->animation_speed = gtk_spin_button_new_with_range(0.1, 100.0, 0.1);
489 	gtk_label_set_mnemonic_widget(GTK_LABEL(lbl), ssdlg->animation_speed);
490 	gtk_spin_button_set_value(GTK_SPIN_BUTTON(ssdlg->animation_speed), 1.0);
491 	align = gtk_alignment_new(0.0, 0.5, 0.0, 1.0);
492 	gtk_container_add(GTK_CONTAINER(align), ssdlg->animation_speed);
493 	gtk_table_attach(GTK_TABLE(tbl), align,
494 	               1, 2, 4, 5, GTK_FILL, GTK_FILL, 0, 0);
495 
496 	/* Foreground color and background color */
497 	lbl = gtk_label_new_with_mnemonic("_Foreground:");
498 	gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
499 	gtk_table_attach(GTK_TABLE(tbl), lbl,
500 	                 0, 1, 5, 6, GTK_FILL, GTK_FILL, 0, 0);
501 
502 	ssdlg->foreground_color = gtk_color_button_new();
503 	gtk_label_set_mnemonic_widget(GTK_LABEL(lbl), ssdlg->foreground_color);
504 	align = gtk_alignment_new(0.0, 0.5, 0.0, 1.0);
505 	gtk_container_add(GTK_CONTAINER(align), ssdlg->foreground_color);
506 	gtk_table_attach(GTK_TABLE(tbl), align,
507 	                 1, 2, 5, 6, GTK_FILL, GTK_FILL, 0, 0);
508 
509 	lbl = gtk_label_new_with_mnemonic("_Background:");
510 	gtk_misc_set_alignment(GTK_MISC(lbl), LABEL_X_ALIGN, 0.5);
511 	gtk_table_attach(GTK_TABLE(tbl), lbl,
512 	                 0, 1, 6, 7, GTK_FILL, GTK_FILL, 0, 0);
513 
514 	ssdlg->background_color = gtk_color_button_new();
515 	gtk_label_set_mnemonic_widget(GTK_LABEL(lbl), ssdlg->background_color);
516 	align = gtk_alignment_new(0.0, 0.5, 0.0, 1.0);
517 	gtk_container_add(GTK_CONTAINER(align), ssdlg->background_color);
518 	gtk_table_attach(GTK_TABLE(tbl), align,
519 	                 1, 2, 6, 7, GTK_FILL, GTK_FILL, 0, 0);
520 
521 	align = gtk_alignment_new(0.5, 0.5, 1.0, 1.0);
522 	gtk_alignment_set_padding(GTK_ALIGNMENT(align), 0, 0, 12, 0);
523 	gtk_container_add(GTK_CONTAINER(align), tbl);
524 
525 	gtk_container_add(GTK_CONTAINER(config_expander), align);
526 
527 	gtk_table_attach(GTK_TABLE(main_table), config_expander, 0, 1, 1, 2,
528 	                 GTK_FILL, GTK_FILL, 0, 0);
529 
530 	g_signal_connect(ssdlg->screenshot, "clicked",
531 	                 G_CALLBACK(grab_screen), ssdlg);
532 	g_signal_connect(ssdlg->record, "clicked",
533 	                 G_CALLBACK(begin_animation), ssdlg);
534 	g_signal_connect(ssdlg->stop, "clicked",
535 	                 G_CALLBACK(end_animation), ssdlg);
536 
537 	g_signal_connect(ssdlg->ss_size_combo, "changed",
538 	                 G_CALLBACK(size_combo_changed), ssdlg);
539 	g_signal_connect(ssdlg->width_spin, "value-changed",
540 	                 G_CALLBACK(size_spin_changed), ssdlg);
541 	g_signal_connect(ssdlg->height_spin, "value-changed",
542 	                 G_CALLBACK(size_spin_changed), ssdlg);
543 	g_signal_connect(ssdlg->animation_speed, "value-changed",
544 	                 G_CALLBACK(animation_speed_changed), ssdlg);
545 
546 	g_signal_connect(ssdlg->foreground_color, "color-set",
547 	                 G_CALLBACK(color_changed), ssdlg);
548 	g_signal_connect(ssdlg->background_color, "color-set",
549 	                 G_CALLBACK(color_changed), ssdlg);
550 	/*g_signal_connect(config_expander, "activate",
551 	                 G_CALLBACK(on_config_expander_activate), ssdlg);
552 	*/
553 	vbox = gtk_dialog_get_content_area(GTK_DIALOG(ssdlg->window));
554 	gtk_container_add(GTK_CONTAINER(vbox), main_table);
555 	gtk_widget_show_all(main_table);
556 
557 	return ssdlg;
558 }
559 
560 /* Popup the screenshot window */
popup_screenshot_window(TilemEmulatorWindow * ewin)561 void popup_screenshot_window(TilemEmulatorWindow *ewin)
562 {
563 	TilemScreenshotDialog *ssdlg;
564 	int w96, h96, w128, h128, width, height, grayscale;
565 	GdkColor fg, bg;
566 
567 	g_return_if_fail(ewin != NULL);
568 	g_return_if_fail(ewin->emu != NULL);
569 
570 	if (!ewin->emu->ssdlg)
571 		ewin->emu->ssdlg = create_screenshot_window(ewin->emu);
572 	ssdlg = ewin->emu->ssdlg;
573 
574 	tilem_config_get("screenshot",
575 	                 "grayscale/b=1", &grayscale,
576 	                 "width_96x64/i", &w96,
577 	                 "height_96x64/i", &h96,
578 	                 "width_128x64/i", &w128,
579 	                 "height_128x64/i", &h128,
580 	                 "foreground/c=#000", &fg,
581 	                 "background/c=#fff", &bg,
582 	                 NULL);
583 
584 	gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(ssdlg->grayscale_tb),
585 	                             grayscale);
586 
587 	if (is_wide_screen(ewin->emu)) {
588 		fill_size_combobox(GTK_COMBO_BOX(ssdlg->ss_size_combo),
589 		                   wide_sizes, G_N_ELEMENTS(wide_sizes));
590 		width = (w128 > 0 ? w128 : DEFAULT_WIDTH_128);
591 		height = (h128 > 0 ? h128 : DEFAULT_HEIGHT_128);
592 	}
593 	else {
594 		fill_size_combobox(GTK_COMBO_BOX(ssdlg->ss_size_combo),
595 		                   normal_sizes, G_N_ELEMENTS(normal_sizes));
596 		width = (w96 > 0 ? w96 : DEFAULT_WIDTH_96);
597 		height = (h96 > 0 ? h96 : DEFAULT_HEIGHT_96);
598 	}
599 
600 	set_size_spin_buttons(ssdlg, width, height);
601 	size_spin_changed(NULL, ssdlg);
602 
603 	gtk_color_button_set_color(GTK_COLOR_BUTTON(ssdlg->foreground_color), &fg);
604 	gtk_color_button_set_color(GTK_COLOR_BUTTON(ssdlg->background_color), &bg);
605 
606 	grab_screen(NULL, ssdlg);
607 	gtk_window_present(GTK_WINDOW(ssdlg->window));
608 }
609 
610 /* Save the current (static) output */
save_output(TilemScreenshotDialog * ssdlg)611 static gboolean save_output(TilemScreenshotDialog *ssdlg)
612 {
613 	char *dir, *format, *filename, *basename;
614 	TilemAnimation *anim = ssdlg->current_anim;
615 	GdkPixbufAnimation *ganim = GDK_PIXBUF_ANIMATION(anim);
616 	const char *format_opt, *width_opt, *height_opt;
617 	gboolean is_static;
618 	int width, height;
619 	GdkColor fg, bg;
620 	GError *err = NULL;
621 
622 	g_return_val_if_fail(anim != NULL, FALSE);
623 
624 	is_static = gdk_pixbuf_animation_is_static_image(ganim);
625 	width = gdk_pixbuf_animation_get_width(ganim);
626 	height = gdk_pixbuf_animation_get_height(ganim);
627 
628 	gtk_color_button_get_color
629 		(GTK_COLOR_BUTTON(ssdlg->foreground_color), &fg);
630 	gtk_color_button_get_color
631 		(GTK_COLOR_BUTTON(ssdlg->background_color), &bg);
632 
633 	tilem_config_get("screenshot",
634 	                 "directory/f", &dir,
635 	                 "static_format/s", &format,
636 	                 NULL);
637 
638 	if (!dir)
639 		dir = g_get_current_dir();
640 
641 	if (!is_static) {
642 		g_free(format);
643 		format = g_strdup("gif");
644 	}
645 	else if (!format) {
646 		format = g_strdup(DEFAULT_FORMAT);
647 	}
648 
649 	filename = find_free_filename(dir, "screenshot", format);
650 	basename = (filename ? g_filename_display_basename(filename) : NULL);
651 	g_free(filename);
652 	g_free(format);
653 
654 	if (!is_static) {
655 		filename = prompt_save_file("Save Screenshot",
656 		                            GTK_WINDOW(ssdlg->window),
657 		                            basename, dir,
658 		                            "GIF images", "*.gif",
659 		                            "All files", "*",
660 		                            NULL);
661 	}
662 	else {
663 		/* FIXME: perhaps check the list of supported output
664 		   formats (gdk_pixbuf_get_formats()) - e.g., tiff is
665 		   usually supported, although it requires libtiff
666 		   installed (png and jpeg also require external
667 		   libraries, but we need those libraries anyway for
668 		   other reasons) */
669 		filename = prompt_save_file("Save Screenshot",
670 		                            GTK_WINDOW(ssdlg->window),
671 		                            basename, dir,
672 		                            "PNG images", "*.png",
673 		                            "GIF images", "*.gif",
674 		                            "BMP images", "*.bmp",
675 		                            "JPEG images", "*.jpg;*.jpe;*.jpeg",
676 		                            "All files", "*",
677 		                            NULL);
678 	}
679 
680 	g_free(basename);
681 	g_free(dir);
682 
683 	if (!filename)
684 		return FALSE;
685 
686 	if (!is_static) {
687 		format = g_strdup("gif");
688 	}
689 	else {
690 		basename = g_path_get_basename(filename);
691 		format = strrchr(basename, '.');
692 		if (!format) {
693 			messagebox00(ssdlg->window, GTK_MESSAGE_ERROR,
694 			             "Unable to save screenshot",
695 			             "File name does not have a"
696 			             " recognized suffix");
697 			g_free(filename);
698 			g_free(basename);
699 			return FALSE;
700 		}
701 		else {
702 			format = g_strdup(format + 1);
703 		}
704 	}
705 
706 	tilem_animation_save(anim, filename, format, NULL, NULL, &err);
707 
708 	dir = g_path_get_dirname(filename);
709 
710 	if (err) {
711 		messagebox01(ssdlg->window, GTK_MESSAGE_ERROR,
712 		             "Unable to save screenshot",
713 		             "%s", err->message);
714 		g_error_free(err);
715 		g_free(dir);
716 		g_free(filename);
717 		g_free(format);
718 		return FALSE;
719 	}
720 
721 	if (is_static)
722 		format_opt = "static_format/s";
723 	else
724 		format_opt = NULL;
725 
726 	if (is_wide_screen(ssdlg->emu)) {
727 		width_opt = "width_128x64/i";
728 		height_opt = "height_128x64/i";
729 	}
730 	else {
731 		width_opt = "width_96x64/i";
732 		height_opt = "height_96x64/i";
733 	}
734 
735 	tilem_config_set("screenshot",
736 	                 "directory/f", dir,
737 	                 "grayscale/b", ssdlg->current_anim_grayscale,
738 	                 "foreground/c", &fg,
739 	                 "background/c", &bg,
740 	                 width_opt, width,
741 	                 height_opt, height,
742 	                 format_opt, format,
743 	                 NULL);
744 
745 	g_free(dir);
746 	g_free(filename);
747 	g_free(format);
748 	return TRUE;
749 }
750 
751 /* Callback for record button */
begin_animation(G_GNUC_UNUSED GtkButton * btn,TilemScreenshotDialog * ssdlg)752 static void begin_animation(G_GNUC_UNUSED GtkButton *btn,
753                             TilemScreenshotDialog *ssdlg)
754 {
755 	gboolean grayscale = gtk_toggle_button_get_active
756 		(GTK_TOGGLE_BUTTON(ssdlg->grayscale_tb));
757 
758 	gtk_widget_set_sensitive(GTK_WIDGET(ssdlg->animation_speed), FALSE);
759 	gtk_widget_set_sensitive(GTK_WIDGET(ssdlg->screenshot), FALSE);
760 	gtk_widget_set_sensitive(GTK_WIDGET(ssdlg->record), FALSE);
761 	gtk_widget_set_sensitive(GTK_WIDGET(ssdlg->stop), TRUE);
762 	gtk_dialog_set_response_sensitive(GTK_DIALOG(ssdlg->window),
763 	                                  GTK_RESPONSE_ACCEPT, FALSE);
764 
765 	tilem_calc_emulator_begin_animation(ssdlg->emu, grayscale);
766 	ssdlg->current_anim_grayscale = grayscale;
767 
768 	/* You can choose to hide current animation while recording or not
769 	   It's as you prefer... For the moment I hide it */
770 	/*gtk_widget_hide(GTK_WIDGET(ssdlg->screenshot_preview_image)); */
771 
772 	//set_current_animation(ssdlg, NULL);
773 }
774 
775 /* Callback for stop button (stop the recording) */
end_animation(G_GNUC_UNUSED GtkButton * btn,TilemScreenshotDialog * ssdlg)776 static void end_animation(G_GNUC_UNUSED GtkButton *btn,
777                           TilemScreenshotDialog *ssdlg)
778 {
779 	TilemAnimation *anim;
780 
781 	if (ssdlg->emu->anim) {
782 		anim = tilem_calc_emulator_end_animation(ssdlg->emu);
783 		set_current_animation(ssdlg, anim);
784 		g_object_unref(anim);
785 	}
786 	else {
787 		set_current_animation(ssdlg, NULL);
788 	}
789 
790 	gtk_widget_set_sensitive(GTK_WIDGET(ssdlg->animation_speed), TRUE);
791 	gtk_widget_set_sensitive(GTK_WIDGET(ssdlg->screenshot), TRUE);
792 	gtk_widget_set_sensitive(GTK_WIDGET(ssdlg->record), TRUE);
793 	gtk_widget_set_sensitive(GTK_WIDGET(ssdlg->stop), FALSE);
794 }
795 
796 /* Callback for screenshot button (take a screenshot) */
grab_screen(G_GNUC_UNUSED GtkButton * btn,TilemScreenshotDialog * ssdlg)797 static void grab_screen(G_GNUC_UNUSED GtkButton *btn,
798                         TilemScreenshotDialog *ssdlg)
799 {
800 	TilemAnimation *anim;
801 
802 	gboolean grayscale = gtk_toggle_button_get_active
803 		(GTK_TOGGLE_BUTTON(ssdlg->grayscale_tb));
804 
805 	anim = tilem_calc_emulator_get_screenshot(ssdlg->emu, grayscale);
806 	ssdlg->current_anim_grayscale = grayscale;
807 	set_current_animation(ssdlg, anim);
808 	g_object_unref(anim);
809 }
810 
811