1 /** \file uimedia.c
2 * \brief Media recording dialog
3 *
4 * \author Bas Wassink <b.wassink@ziggo.nl>
5 */
6
7 /*
8 * $VICERES SoundRecordDeviceArg all
9 * $VICERES SoundRecordDeviceName all
10 * $VICERES DoodleOversizeHandling -vsid
11 * $VICERES DoodleUndersizeHandling -vsid
12 * $VICERES DoodleMultiColorHandling -vsid
13 * $VICERES DoodleTEDLumHandling -vsid
14 * $VICERES DoodleCRTCTextColor -vsid
15 * $VICERES KoalaOversizeHandling -vsid
16 * $VICERES KoalaUndersizeHandling -vsid
17 * $VICERES KoalaTEDLumHandling -vsid
18 * $VICERES KoalaCRTCTextColor -vsid
19 */
20
21 /*
22 * This file is part of VICE, the Versatile Commodore Emulator.
23 * See README for copyright notice.
24 *
25 * This program is free software; you can redistribute it and/or modify
26 * it under the terms of the GNU General Public License as published by
27 * the Free Software Foundation; either version 2 of the License, or
28 * (at your option) any later version.
29 *
30 * This program is distributed in the hope that it will be useful,
31 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 * GNU General Public License for more details.
34 *
35 * You should have received a copy of the GNU General Public License
36 * along with this program; if not, write to the Free Software
37 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
38 * 02111-1307 USA.
39 */
40
41 #include "vice.h"
42 #include <gtk/gtk.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <stdint.h>
46
47 #include "debug_gtk3.h"
48 #include "lib.h"
49 #include "resources.h"
50 #include "machine.h"
51 #include "sound.h"
52 #include "screenshot.h"
53 #include "widgethelpers.h"
54 #include "basewidgets.h"
55 #include "basedialogs.h"
56 #include "openfiledialog.h"
57 #include "savefiledialog.h"
58 #include "selectdirectorydialog.h"
59 #include "ui.h"
60 #include "gfxoutput.h"
61
62 #ifdef HAVE_FFMPEG
63 #include "ffmpegwidget.h"
64 #endif
65
66 #include "uimedia.h"
67
68
69 /** \brief Custom response IDs for the dialog
70 */
71 enum {
72 RESPONSE_SAVE = 1 /**< Save button clicked */
73 };
74
75
76 /** \brief Name of the stack child for screenshots
77 */
78 #define CHILD_SCREENSHOT "Screenshot"
79
80 /** \brief Name of the stack child for sound recording
81 */
82 #define CHILD_SOUND "Sound"
83
84 /** \brief Name of the stack child for video recording
85 */
86 #define CHILD_VIDEO "Video"
87
88
89 /** \brief Struct to hold information on available video recording drivers
90 */
91 typedef struct video_driver_info_s {
92 const char *display; /**< display string (used in the UI) */
93 const char *name; /**< driver name */
94 const char *ext; /**< default file extension */
95 } video_driver_info_t;
96
97
98 /** \brief Struct to hold information on available audio recording drivers
99 */
100 typedef struct audio_driver_info_s {
101 const char *display; /**< display name (used in the UI) */
102 const char *name; /**< driver name */
103 const char *ext; /**< default file extension */
104 } audio_driver_info_t;
105
106
107 /** \brief List of available video drivers, gotten during runtime
108 */
109 static video_driver_info_t *video_driver_list = NULL;
110
111
112 /** \brief Number of available video drivers
113 */
114 static int video_driver_count = 0;
115
116
117 /** \brief Index of currently selected video driver
118 */
119 static int video_driver_index = 0;
120
121
122 /** \brief Index of currently selected audio driver
123 */
124 static int audio_driver_index = 0;
125
126
127 /** \brief List of available audio recording drivers
128 *
129 * This list is dependent on compile-time options
130 */
131 static audio_driver_info_t audio_driver_list[] = {
132 { "WAV", "wav", "wav" },
133 { "AIFF", "aiff", "aif" },
134 { "VOC", "voc", "voc" },
135 { "IFF", "iff", "iff" },
136 #ifdef USE_LAMEMP3
137 { "MP3", "mp3", "mp3" },
138 #endif
139 #ifdef USE_FLAC
140 { "FLAC", "flac", "flac" },
141 #endif
142 #ifdef USE_VORBIS
143 { "Ogg/Vorbis", "ogg", "ogg" },
144 #endif
145 { NULL, NULL, NULL }
146 };
147
148
149 /** \brief List of 'oversize modes' for some screenshot output drivers
150 */
151 static vice_gtk3_combo_entry_int_t oversize_modes[] = {
152 { "scale down", 0 },
153 { "crop left top", 1, },
154 { "crop center top", 2 },
155 { "crop right top", 3 },
156 { "crop left center", 4 },
157 { "crop center", 5 },
158 { "crop right center", 6 },
159 { "crop left bottom (huhu)", 7 },
160 { "crop center bottom", 8 },
161 { "crop right bottom", 9 },
162 { NULL, -1 }
163 };
164
165
166 /** \brief List of 'undersize modes' for some screenshot output drivers
167 */
168 static vice_gtk3_combo_entry_int_t undersize_modes[] = {
169 { "scale up", 0 },
170 { "border size", 1 },
171 { NULL, -1 }
172 };
173
174
175 /** \brief List of multi color modes for some screenshot output drivers
176 */
177 static const vice_gtk3_combo_entry_int_t multicolor_modes[] = {
178 { "B&W", 0 },
179 { "2 colors", 1 },
180 { "4 colors", 2 },
181 { "Grey scale", 3 },
182 { "Best cell colors", 4 },
183 { NULL, -1 }
184 };
185
186
187 /** \brief TED output driver Luma modes
188 */
189 static const vice_gtk3_combo_entry_int_t ted_luma_modes[] = {
190 { "ignore", 0 },
191 { "dither", 1 },
192 { NULL, -1 },
193 };
194
195
196 /** \brief List of available colors to use for CRTC screenshots
197 */
198 static const vice_gtk3_combo_entry_int_t crtc_colors[] = {
199 { "White", 0 },
200 { "Amber", 1 },
201 { "Green", 2 },
202 { NULL, -1 }
203 };
204
205
206 /** \brief Reference to the GtkStack containing the media types
207 *
208 * Used in the dialog response callback to determine recording mode and params
209 */
210 static GtkWidget *stack;
211
212 /* references to widgets, used from various event handlers */
213 static GtkWidget *screenshot_options_grid = NULL;
214 static GtkWidget *oversize_widget = NULL;
215 static GtkWidget *undersize_widget = NULL;
216 static GtkWidget *multicolor_widget = NULL;
217 static GtkWidget *ted_luma_widget = NULL;
218 static GtkWidget *crtc_textcolor_widget = NULL;
219 static GtkWidget *video_driver_options_grid = NULL;
220
221
222 /* forward declarations of helper functions */
223 static GtkWidget *create_screenshot_param_widget(const char *prefix);
224 static void save_screenshot_handler(void);
225 static void save_audio_recording_handler(void);
226 static void save_video_recording_handler(void);
227
228
229 /*****************************************************************************
230 * Event handlers *
231 ****************************************************************************/
232
233 /** \brief Handler for the "destroy" event of the dialog
234 *
235 * \param[in] widget dialog
236 * \param[in] data extra event data (unused)
237 */
on_dialog_destroy(GtkWidget * widget,gpointer data)238 static void on_dialog_destroy(GtkWidget *widget, gpointer data)
239 {
240 if (machine_class != VICE_MACHINE_VSID) {
241 debug_gtk3("called: cleaning up driver list.");
242 lib_free(video_driver_list);
243 }
244 ui_pause_emulation(FALSE);
245 }
246
247
248 /** \brief Set new widget for the screenshot options, replacing the old one
249 *
250 * Destroys any widget present before setting the \a new widget.
251 *
252 * \param[in] new new widget
253 */
update_screenshot_options_grid(GtkWidget * new)254 static void update_screenshot_options_grid(GtkWidget *new)
255 {
256 GtkWidget *old = gtk_grid_get_child_at(GTK_GRID(screenshot_options_grid),
257 0, 1);
258 if (old != NULL) {
259 gtk_widget_destroy(old);
260 }
261 gtk_grid_attach(GTK_GRID(screenshot_options_grid), new, 0, 1, 1, 1);
262 }
263
264
265 /** \brief Handler for the "response" event of the \a dialog
266 *
267 * \param[in,out] dialog dialog triggering the event
268 * \param[in] response_id response ID
269 * \param[in] data extra event data (unused)
270 */
on_response(GtkDialog * dialog,gint response_id,gpointer data)271 static void on_response(GtkDialog *dialog, gint response_id, gpointer data)
272 {
273 const gchar *child_name;
274
275 debug_gtk3("got response ID %d.", response_id);
276
277 switch (response_id) {
278 case GTK_RESPONSE_DELETE_EVENT:
279 debug_gtk3("destroying dialog.");
280 gtk_widget_destroy(GTK_WIDGET(dialog));
281 break;
282
283 case RESPONSE_SAVE:
284
285 if (machine_class != VICE_MACHINE_VSID) {
286 /* stack child name determines what to do next */
287 child_name = gtk_stack_get_visible_child_name(GTK_STACK(stack));
288 debug_gtk3("Saving media, tab '%s' selected.", child_name);
289
290 if (strcmp(child_name, CHILD_SCREENSHOT) == 0) {
291 debug_gtk3("Screenshot requested, driver %d.",
292 video_driver_index);
293 save_screenshot_handler();
294 } else if (strcmp(child_name, CHILD_SOUND) == 0) {
295 debug_gtk3("Audio recording requested, driver %d.",
296 audio_driver_index);
297 save_audio_recording_handler();
298 } else if (strcmp(child_name, CHILD_VIDEO) == 0) {
299 debug_gtk3("Video recording requested, driver %d.",
300 video_driver_index);
301 save_video_recording_handler();
302 }
303 } else {
304 debug_gtk3("Audio recording requested, driver %d.",
305 audio_driver_index);
306 save_audio_recording_handler();
307 }
308 break;
309
310
311 default:
312 break;
313 }
314 }
315
316
317 /** \brief Handler for the "toggled" event of the screenshot driver radios
318 *
319 * \param[in] widget radio button riggering the event
320 * \param[in] data extra event data (unused)
321 */
on_screenshot_driver_toggled(GtkWidget * widget,gpointer data)322 static void on_screenshot_driver_toggled(GtkWidget *widget, gpointer data)
323 {
324 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
325 int index = GPOINTER_TO_INT(data);
326 debug_gtk3("screenshot driver %d (%s) selected.",
327 index, video_driver_list[index].name);
328 video_driver_index = index;
329 update_screenshot_options_grid(
330 create_screenshot_param_widget(video_driver_list[index].name));
331 }
332 }
333
334
335 /** \brief Handler for the "toggled" event of the audio driver radios
336 *
337 * \param[in] widget radio button riggering the event
338 * \param[in] data extra event data (unused)
339 */
on_audio_driver_toggled(GtkWidget * widget,gpointer data)340 static void on_audio_driver_toggled(GtkWidget *widget, gpointer data)
341 {
342 if (gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(widget))) {
343 int index = GPOINTER_TO_INT(data);
344 debug_gtk3("audio driver %d (%s) selected.",
345 index, audio_driver_list[index].name);
346 audio_driver_index = index;
347 }
348 }
349
350
351
352 /*****************************************************************************
353 * Helpers functions *
354 ****************************************************************************/
355
356 /** \brief Create a string in the format 'yyyymmddHHMM' of the current time
357 *
358 * \return string owned by GLib, free with g_free()
359 */
create_datetime_string(void)360 static gchar *create_datetime_string(void)
361 {
362 GDateTime *d;
363 gchar *s;
364
365 d = g_date_time_new_now_local();
366 s = g_date_time_format(d, "%Y%m%d%H%M");
367 g_date_time_unref(d);
368 return s;
369 }
370
371
372 /** \brief Create a filename based on the current datetime and \a ext
373 *
374 * \param[in] ext file extension (without the dot)
375 *
376 * \return heap-allocated string, owned by VICE, free with lib_free()
377 */
create_proposed_screenshot_name(const char * ext)378 static char *create_proposed_screenshot_name(const char *ext)
379 {
380 char *date;
381 char *filename;
382
383 date = create_datetime_string();
384 filename = lib_msprintf("vice-screen-%s.%s", date, ext);
385 g_free(date);
386 return filename;
387 }
388
389
390
391 /** \brief Create a filename based on the current datetime and \a ext
392 *
393 * \param[in] ext file extension (without the dot)
394 *
395 * \return heap-allocated string, owned by VICE, free with lib_free()
396 */
create_proposed_video_recording_name(const char * ext)397 static char *create_proposed_video_recording_name(const char *ext)
398 {
399 gchar *date;
400 char *filename;
401
402 date = create_datetime_string();
403 filename = lib_msprintf("vice-video-%s.%s", date, ext);
404 g_free(date);
405 return filename;
406 }
407
408
409
410 /** \brief Create a filename based on the current datetime and \a ext
411 *
412 * \param[in] ext file extension (without the dot)
413 *
414 * \return heap-allocated string, owned by VICE, free with lib_free()
415 */
create_proposed_audio_recording_name(const char * ext)416 static char *create_proposed_audio_recording_name(const char *ext)
417 {
418 gchar *date;
419 char *filename;
420
421 date = create_datetime_string();
422 filename = lib_msprintf("vice-audio-%s.%s", date, ext);
423 g_free(date);
424 return filename;
425 }
426
427
428
429 /** \brief Save a screenshot
430 *
431 * Pops up a save-file dialog with a proposed filename (ie
432 * 'screenshot-197411151210.png'.
433 */
save_screenshot_handler(void)434 static void save_screenshot_handler(void)
435 {
436 const char *display;
437 const char *name;
438 const char *ext;
439 gchar *filename;
440 char *title;
441 char *proposed;
442
443 display = video_driver_list[video_driver_index].display;
444 name = video_driver_list[video_driver_index].name;
445 ext = video_driver_list[video_driver_index].ext;
446
447 title = lib_msprintf("Save %s file", display);
448 proposed = create_proposed_screenshot_name(ext);
449
450 filename = vice_gtk3_save_file_dialog(title, proposed, TRUE, NULL);
451 if (filename != NULL) {
452 /* TODO: add extension if not present? */
453 if (screenshot_save(name, filename, ui_get_active_canvas()) < 0) {
454 vice_gtk3_message_error("VICE Error",
455 "Failed to write screenshot file '%s'", filename);
456 } else {
457 vice_gtk3_message_info("VICE Info",
458 "Saved screenshot as '%s'", filename);
459 }
460 g_free(filename);
461 }
462 lib_free(proposed);
463 lib_free(title);
464 }
465
466
467 /** \brief Start an audio recording
468 *
469 * Pops up a save-file dialog with a proposed filename (ie
470 * 'audio-recording-197411151210.png'.
471 */
save_audio_recording_handler(void)472 static void save_audio_recording_handler(void)
473 {
474 const char *display;
475 const char *name;
476 const char *ext;
477 gchar *filename;
478 char *title;
479 char *proposed;
480
481 display = audio_driver_list[audio_driver_index].display;
482 name = audio_driver_list[audio_driver_index].name;
483 ext = audio_driver_list[audio_driver_index].ext;
484
485 title = lib_msprintf("Save %s file", display);
486 proposed = create_proposed_audio_recording_name(ext);
487
488 filename = vice_gtk3_save_file_dialog(title, proposed, TRUE, NULL);
489 if (filename != NULL) {
490 /* XXX: setting resources doesn't exactly help with catching errors */
491 resources_set_string("SoundRecordDeviceArg", filename);
492 resources_set_string("SoundRecordDeviceName", name);
493 g_free(filename);
494 }
495 }
496
497
498 /** \brief Start recording a video
499 *
500 * Pops up a save-file dialog with a proposed filename (ie
501 * 'video-recording-197411151210.png'.
502 */
save_video_recording_handler(void)503 static void save_video_recording_handler(void)
504 {
505 /* these may be useful once QuickTime is supported */
506 #if 0
507 const char *display;
508 const char *name;
509 #endif
510 const char *ext;
511 gchar *filename;
512 char *title;
513 char *proposed;
514
515 debug_gtk3("video driver index = %d.", video_driver_index);
516
517 #if 0
518 display = video_driver_list[video_driver_index].display;
519 name = video_driver_list[video_driver_index].name;
520 #endif
521 /* we dont' have a format->extension mapping, so the format name itself is
522 better than `video_driver_list[video_driver_index].ext' */
523 resources_get_string("FFMPEGFormat", &ext);
524
525 title = lib_msprintf("Save %s file", "FFMPEG");
526 proposed = create_proposed_video_recording_name(ext);
527
528 filename = vice_gtk3_save_file_dialog(title, proposed, TRUE, NULL);
529 if (filename != NULL) {
530
531 const char *driver;
532 int ac;
533 int vc;
534 int ab;
535 int vb;
536
537 resources_get_string("FFMPEGFormat", &driver);
538 resources_get_int("FFMPEGVideoCodec", &vc);
539 resources_get_int("FFMPEGVideoBitrate", &vb);
540 resources_get_int("FFMPEGAudioCodec", &ac);
541 resources_get_int("FFMPEGAudioBitrate", &ab);
542
543 debug_gtk3("Format = '%s'.", driver);
544 debug_gtk3("Video = %d, bitrate %d.", vc, vb);
545 debug_gtk3("Audio = %d, bitrate %d.", ac, ab);
546
547
548 ui_pause_emulation(FALSE);
549
550 /* TODO: add extension if not present? */
551 if (screenshot_save("FFMPEG", filename, ui_get_active_canvas()) < 0) {
552 vice_gtk3_message_error("VICE Error",
553 "Failed to write video file '%s'", filename);
554 }
555 g_free(filename);
556 }
557 lib_free(proposed);
558 lib_free(title);
559 }
560
561
562 /** \brief Create heap-allocated list of available video drivers
563 *
564 * Queries the gfxoutputdrv subsystem and builds a list of currently available
565 * drivers and stores it in `driver_list`. Must be freed with lib_free() after
566 * use.
567 */
create_video_driver_list(void)568 static void create_video_driver_list(void)
569 {
570 gfxoutputdrv_t *driver;
571 int index;
572
573 video_driver_count = gfxoutput_num_drivers();
574 debug_gtk3("got %d output drivers.", video_driver_count);
575
576 video_driver_list = lib_malloc((size_t)(video_driver_count + 1) *
577 sizeof *video_driver_list);
578
579 index = 0;
580
581 if (video_driver_count > 0) {
582 driver = gfxoutput_drivers_iter_init();
583 while (driver != NULL) {
584 debug_gtk3(".. adding driver '%s'. ext: %s.",
585 driver->name,
586 driver->default_extension);
587 video_driver_list[index].display = driver->displayname;
588 video_driver_list[index].name = driver->name;
589 video_driver_list[index].ext = driver->default_extension;
590 index++;
591 driver = gfxoutput_drivers_iter_next();
592 }
593 }
594 video_driver_list[index].display = NULL;
595 video_driver_list[index].name = NULL;
596 video_driver_list[index].ext = NULL;
597 }
598
599
600 /** \brief Determine if driver \a name is a video driver
601 *
602 * \param[in] name driver name
603 *
604 * \return bool
605 *
606 * \todo There has to be a better, more reliable way than this
607 */
driver_is_video(const char * name)608 static int driver_is_video(const char *name)
609 {
610 return strcmp(name, "FFMPEG") == 0 || strcmp(name, "QuickTime") == 0;
611 }
612
613
614 /** \brief Create widget to control screenshot parameters
615 *
616 * This widget exposes controls to alter screenshot parameters, depending on
617 * machine class and output file format
618 *
619 * \param[in] prefix resource prefix (either "Doodle", "Koala", or ""/NULL)
620 */
create_screenshot_param_widget(const char * prefix)621 static GtkWidget *create_screenshot_param_widget(const char *prefix)
622 {
623 GtkWidget *grid;
624 GtkWidget *label;
625 int row;
626 int doodle = 0;
627 int koala = 0;
628
629 grid = gtk_grid_new();
630 gtk_grid_set_column_spacing(GTK_GRID(grid), 16);
631 gtk_grid_set_row_spacing(GTK_GRID(grid), 8);
632
633 /* according to the standard, doing a strcmp() with one or more `NULL`
634 * arguments is implementation-defined, so better safe than sorry */
635 if (prefix == NULL) {
636 debug_gtk3("some idiot passed NULL.");
637 return grid;
638 }
639
640 if ((strcmp(prefix, "DOODLE") == 0)
641 || (strcmp(prefix, "DOODLE_COMPRESSED") == 0)) {
642 prefix = "Doodle"; /* XXX: not strictly required since resource names
643 seem to be case insensitive, but better
644 safe than sorry */
645 doodle = 1;
646 } else if ((strcmp(prefix, "KOALA") == 0)
647 || (strcmp(prefix, "KOALA_COMPRESSED") == 0)) {
648 prefix = "Koala";
649 koala = 1;
650 }
651
652 if (!koala && !doodle) {
653 label = gtk_label_new("No parameters required");
654 g_object_set(label, "margin-left", 16, NULL);
655 gtk_grid_attach(GTK_GRID(grid), label, 0, 0, 1, 1);
656 gtk_widget_show_all(grid);
657 return grid;
658 }
659
660 /* ${FORMAT}OversizeHandling */
661 label = gtk_label_new("Oversize handling");
662 g_object_set(label, "margin-left", 16, NULL);
663 gtk_widget_set_halign(label, GTK_ALIGN_START);
664 oversize_widget = vice_gtk3_resource_combo_box_int_new_sprintf(
665 "%sOversizeHandling", oversize_modes, prefix);
666 gtk_grid_attach(GTK_GRID(grid), label, 0, 0, 1, 1);
667 gtk_grid_attach(GTK_GRID(grid), oversize_widget, 1, 0, 1, 1);
668
669 /* ${FORMAT}UndersizeHandling */
670 label = gtk_label_new("Undersize handling");
671 g_object_set(label, "margin-left", 16, NULL);
672 gtk_widget_set_halign(label, GTK_ALIGN_START);
673 undersize_widget = vice_gtk3_resource_combo_box_int_new_sprintf(
674 "%sUndersizeHandling", undersize_modes, prefix);
675 gtk_grid_attach(GTK_GRID(grid), label, 0, 1, 1, 1);
676 gtk_grid_attach(GTK_GRID(grid), undersize_widget, 1, 1, 1, 1);
677
678 /* ${FORMAT}MultiColorHandling */
679
680 row = 2; /* from now on, the widgets depend on machine and image type */
681
682 /* DoodleMultiColorHandling */
683 if (doodle) {
684 label = gtk_label_new("Multi color handling");
685 g_object_set(label, "margin-left", 16, NULL);
686 gtk_widget_set_halign(label, GTK_ALIGN_START);
687 multicolor_widget = vice_gtk3_resource_combo_box_int_new_sprintf(
688 "%sMultiColorHandling", multicolor_modes, prefix);
689 gtk_grid_attach(GTK_GRID(grid), label, 0, row, 1, 1);
690 gtk_grid_attach(GTK_GRID(grid), multicolor_widget, 1, row, 1, 1);
691 row++;
692 }
693
694 /* ${FORMAT}TEDLumaHandling */
695 if (machine_class == VICE_MACHINE_PLUS4) {
696 label = gtk_label_new("TED luma handling");
697 g_object_set(label, "margin-left", 16, NULL);
698 gtk_widget_set_halign(label, GTK_ALIGN_START);
699 ted_luma_widget = vice_gtk3_resource_combo_box_int_new_sprintf(
700 "%sTEDLumHandling", ted_luma_modes, prefix);
701 gtk_grid_attach(GTK_GRID(grid), label, 0, row, 1, 1);
702 gtk_grid_attach(GTK_GRID(grid), ted_luma_widget, 1, row, 1, 1);
703 row++;
704 }
705
706 /* ${FORMAT}CRTCTextColor */
707 if (machine_class == VICE_MACHINE_PET
708 || machine_class == VICE_MACHINE_CBM6x0) {
709 label = gtk_label_new("CRTC text color");
710 g_object_set(label, "margin-left", 16, NULL);
711 gtk_widget_set_halign(label, GTK_ALIGN_START);
712 crtc_textcolor_widget = vice_gtk3_resource_combo_box_int_new_sprintf(
713 "%sCRTCTextColor", crtc_colors, prefix);
714 gtk_grid_attach(GTK_GRID(grid), label, 0, row, 1, 1);
715 gtk_grid_attach(GTK_GRID(grid), crtc_textcolor_widget, 1, row, 1, 1);
716 row++;
717 }
718
719 gtk_widget_show_all(grid);
720 return grid;
721 }
722
723
724
725
726 /** \brief Create the main 'screenshot' widget
727 *
728 * \return GtkGrid
729 */
create_screenshot_widget(void)730 static GtkWidget *create_screenshot_widget(void)
731 {
732 GtkWidget *grid;
733 GtkWidget *drv_grid;
734 GtkWidget *radio;
735 GtkWidget *last;
736 GSList *group = NULL;
737 int index;
738 int grid_index;
739
740 grid = gtk_grid_new();
741 gtk_grid_set_column_spacing(GTK_GRID(grid), 16);
742 gtk_grid_set_row_spacing(GTK_GRID(grid), 8);
743
744 drv_grid = uihelpers_create_grid_with_label("Driver", 1);
745 grid_index = 1;
746 last = NULL;
747 for (index = 0; video_driver_list[index].name != NULL; index++) {
748 const char *display = video_driver_list[index].display;
749 const char *name = video_driver_list[index].name;
750
751 if (!driver_is_video(name)) {
752 radio = gtk_radio_button_new_with_label(group, display);
753 g_object_set(radio, "margin-left", 8, NULL);
754 gtk_radio_button_join_group(GTK_RADIO_BUTTON(radio),
755 GTK_RADIO_BUTTON(last));
756 gtk_grid_attach(GTK_GRID(drv_grid), radio, 0, grid_index, 1, 1);
757
758 g_signal_connect(radio, "toggled",
759 G_CALLBACK(on_screenshot_driver_toggled),
760 GINT_TO_POINTER(index));
761
762 last = radio;
763 grid_index++;
764 }
765 }
766
767 /* this is where the various options go per screenshot driver (for example
768 * Koala or Doodle) */
769 screenshot_options_grid = uihelpers_create_grid_with_label(
770 "Driver options", 1);
771
772 gtk_grid_attach(GTK_GRID(grid), drv_grid, 0, 0, 1, 1);
773 gtk_grid_attach(GTK_GRID(grid), screenshot_options_grid, 1, 0, 1, 1);
774
775 update_screenshot_options_grid(create_screenshot_param_widget(""));
776
777 gtk_widget_show_all(grid);
778 return grid;
779 }
780
781
782 /** \brief Create the main 'sound recording' widget
783 *
784 * \return GtkGrid
785 */
create_sound_widget(void)786 static GtkWidget *create_sound_widget(void)
787 {
788 GtkWidget *grid;
789 GtkWidget *drv_grid;
790 GtkWidget *radio;
791 GtkWidget *last;
792 GSList *group = NULL;
793 int index;
794
795 grid = gtk_grid_new();
796 gtk_grid_set_column_spacing(GTK_GRID(grid), 16);
797 gtk_grid_set_row_spacing(GTK_GRID(grid), 8);
798
799 drv_grid = uihelpers_create_grid_with_label("Driver", 1);
800 last = NULL;
801 for (index = 0; audio_driver_list[index].name != NULL; index++) {
802 const char *display = audio_driver_list[index].display;
803
804 radio = gtk_radio_button_new_with_label(group, display);
805 g_object_set(radio, "margin-left", 8, NULL);
806 gtk_radio_button_join_group(GTK_RADIO_BUTTON(radio),
807 GTK_RADIO_BUTTON(last));
808 gtk_grid_attach(GTK_GRID(drv_grid), radio, 0, index + 1, 1, 1);
809
810 g_signal_connect(radio, "toggled",
811 G_CALLBACK(on_audio_driver_toggled),
812 GINT_TO_POINTER(index));
813
814 last = radio;
815 }
816
817 gtk_grid_attach(GTK_GRID(grid), drv_grid, 0, 0, 1, 1);
818
819 gtk_widget_show_all(grid);
820 return grid;
821 }
822
823
824 /** \brief Create the main 'video recording' widget
825 *
826 * \return GtkGrid
827 */
create_video_widget(void)828 static GtkWidget *create_video_widget(void)
829 {
830 GtkWidget *grid;
831 GtkWidget *label;
832 GtkWidget *combo;
833 int index;
834
835 GtkWidget *selection_grid;
836 GtkWidget *options_grid;
837
838 grid = gtk_grid_new();
839 gtk_grid_set_column_spacing(GTK_GRID(grid), 16);
840 gtk_grid_set_row_spacing(GTK_GRID(grid), 8);
841
842 label = gtk_label_new("Video driver");
843 g_object_set(label, "margin-left", 16, NULL);
844
845 combo = gtk_combo_box_text_new();
846 for (index = 0; video_driver_list[index].name != NULL; index++) {
847 const char *display = video_driver_list[index].display;
848 const char *name = video_driver_list[index].name;
849
850 if (driver_is_video(name)) {
851 gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(combo), name, display);
852 }
853 }
854 gtk_widget_set_hexpand(combo, TRUE);
855 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
856
857 selection_grid = uihelpers_create_grid_with_label("Driver selection", 2);
858 gtk_grid_set_column_spacing(GTK_GRID(selection_grid), 16);
859 gtk_grid_set_row_spacing(GTK_GRID(selection_grid), 8);
860 gtk_grid_attach(GTK_GRID(selection_grid), label, 0, 1, 1, 1);
861 gtk_grid_attach(GTK_GRID(selection_grid), combo, 1, 1, 1, 1);
862 gtk_widget_show_all(selection_grid);
863
864 gtk_grid_attach(GTK_GRID(grid), selection_grid, 0, 0, 1, 1);
865
866 /* grid around ffmpeg/quicktime options */
867 options_grid = uihelpers_create_grid_with_label("Driver options", 1);
868 gtk_grid_set_column_spacing(GTK_GRID(options_grid), 16);
869 gtk_grid_set_row_spacing(GTK_GRID(options_grid), 8);
870
871 /* XXX: this obviously needs a cleaner solution which also handles QuickTime
872 * on MacOS
873 */
874 #ifdef HAVE_FFMPEG
875 gtk_grid_attach(GTK_GRID(options_grid), ffmpeg_widget_create(), 0, 1, 1,1);
876 #endif
877 video_driver_options_grid = options_grid;
878
879
880
881 gtk_grid_attach(GTK_GRID(grid), options_grid, 0, 1, 1, 1);
882
883
884 gtk_widget_show_all(grid);
885 return grid;
886 }
887
888
889 /** \brief Create the content widget for the dialog
890 *
891 * Contains a GtkStack and GtkStackSwitcher to switch between 'screenshot',
892 * 'sound' and 'video' sub widgets.
893 *
894 * \return GtkGrid
895 */
create_content_widget(void)896 static GtkWidget *create_content_widget(void)
897 {
898 GtkWidget *grid;
899 GtkWidget *switcher;
900
901 grid = gtk_grid_new();
902 gtk_grid_set_column_spacing(GTK_GRID(grid), 16);
903 gtk_grid_set_row_spacing(GTK_GRID(grid), 8);
904
905 stack = gtk_stack_new();
906 gtk_stack_add_titled(GTK_STACK(stack), create_screenshot_widget(),
907 CHILD_SCREENSHOT, "Screenshot");
908 gtk_stack_add_titled(GTK_STACK(stack), create_sound_widget(),
909 CHILD_SOUND, "Sound recording");
910 gtk_stack_add_titled(GTK_STACK(stack), create_video_widget(),
911 CHILD_VIDEO, "Video recording");
912 gtk_stack_set_transition_type(GTK_STACK(stack),
913 GTK_STACK_TRANSITION_TYPE_SLIDE_LEFT_RIGHT);
914 gtk_stack_set_transition_duration(GTK_STACK(stack), 1000);
915 /* avoid resizing the dialog when switching */
916 gtk_stack_set_homogeneous(GTK_STACK(stack), TRUE);
917
918 switcher = gtk_stack_switcher_new();
919 gtk_widget_set_halign(switcher, GTK_ALIGN_CENTER);
920 gtk_widget_set_hexpand(switcher, TRUE);
921 gtk_stack_switcher_set_stack(GTK_STACK_SWITCHER(switcher), GTK_STACK(stack));
922 /* make all titles of the switcher the same size */
923 gtk_box_set_homogeneous(GTK_BOX(switcher), TRUE);
924
925 gtk_widget_show_all(stack);
926 gtk_widget_show_all(switcher);
927
928 gtk_grid_attach(GTK_GRID(grid), switcher, 0, 0, 1, 1);
929 gtk_grid_attach(GTK_GRID(grid), stack, 0, 1, 1, 1);
930
931 gtk_widget_show_all(grid);
932 return grid;
933 }
934
935
936
937 /** \brief Show dialog to save screenshot, record sound and/or video
938 *
939 * \param[in] parent parent widget (unused)
940 * \param[in] data extra data (unused)
941 */
uimedia_dialog_show(GtkWidget * parent,gpointer data)942 void uimedia_dialog_show(GtkWidget *parent, gpointer data)
943 {
944 GtkWidget *dialog;
945 GtkWidget *content;
946
947 /*
948 * Pause emulation, the ui_emulation_is_paused() check is required since
949 * the ui_pause_emulation() function is a little weird: when passed FALSE
950 * it unpauses, when passed TRUE it toggles the paused state.
951 */
952 if (!ui_emulation_is_paused()) {
953 ui_pause_emulation(TRUE);
954 }
955
956 /* create driver list */
957 if (machine_class != VICE_MACHINE_VSID) {
958 create_video_driver_list();
959 }
960
961 dialog = gtk_dialog_new_with_buttons(
962 "Record media file",
963 ui_get_active_window(),
964 GTK_DIALOG_MODAL,
965 "Save", RESPONSE_SAVE,
966 "Close", GTK_RESPONSE_DELETE_EVENT,
967 NULL);
968
969 /* add content widget */
970 content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
971 if (machine_class != VICE_MACHINE_VSID) {
972 gtk_container_add(GTK_CONTAINER(content), create_content_widget());
973 } else {
974 gtk_container_add(GTK_CONTAINER(content), create_sound_widget());
975 }
976
977 gtk_window_set_resizable(GTK_WINDOW(dialog), FALSE);
978 g_signal_connect(dialog, "response", G_CALLBACK(on_response), NULL);
979 g_signal_connect(dialog, "destroy", G_CALLBACK(on_dialog_destroy), NULL);
980
981 gtk_widget_show_all(dialog);
982 }
983
984
985 /** \brief Stop audio or video recording, if active
986 *
987 * \return TRUE, so the emulated machine doesn't get the shortcut key
988 */
uimedia_stop_recording(GtkWidget * parent,gpointer data)989 gboolean uimedia_stop_recording(GtkWidget *parent, gpointer data)
990 {
991 debug_gtk3("Stopping media recording.");
992
993 /* stop sound recording, if active */
994 if (sound_is_recording()) {
995 sound_stop_recording();
996 }
997 /* stop video recording */
998 if (screenshot_is_recording()) {
999 screenshot_stop_recording();
1000 }
1001 return TRUE;
1002 }
1003