1 /** \file uisnapshot.c
2 * \brief Snapshot dialogs and menu item handlers
3 *
4 * \author Bas Wassink <b.wassink@ziggo.nl>
5 */
6
7 /*
8 * This file is part of VICE, the Versatile Commodore Emulator.
9 * See README for copyright notice.
10 *
11 * This program is free software; you can redistribute it and/or modify
12 * it under the terms of the GNU General Public License as published by
13 * the Free Software Foundation; either version 2 of the License, or
14 * (at your option) any later version.
15 *
16 * This program 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
19 * GNU General Public License for more details.
20 *
21 * You should have received a copy of the GNU General Public License
22 * along with this program; if not, write to the Free Software
23 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
24 * 02111-1307 USA.
25 *
26 */
27
28 #include "vice.h"
29
30 #include <gtk/gtk.h>
31 #include <stdlib.h>
32 #include <stdbool.h>
33
34 #include "lib.h"
35 #include "util.h"
36 #include "archdep.h"
37 #include "basewidgets.h"
38 #include "widgethelpers.h"
39 #include "debug_gtk3.h"
40 #include "machine.h"
41 #include "mainlock.h"
42 #include "resources.h"
43 #include "filechooserhelpers.h"
44 #include "openfiledialog.h"
45 #include "savefiledialog.h"
46 #include "selectdirectorydialog.h"
47 #include "tick.h"
48 #include "basedialogs.h"
49 #include "interrupt.h"
50 #include "vsync.h"
51 #include "vsyncapi.h"
52 #include "snapshot.h"
53 #include "vice-event.h"
54 #include "uistatusbar.h"
55 #include "ui.h"
56 #include "uiapi.h"
57
58 #include "uisnapshot.h"
59
60
61 /*****************************************************************************
62 * Helper functions *
63 ****************************************************************************/
64
65
66 /** \brief Create a string in the format 'yyyymmddHHMMss' of the current time
67 *
68 * \return string owned by GLib, free with g_free()
69 */
create_datetime_string(void)70 static gchar *create_datetime_string(void)
71 {
72 GDateTime *d;
73 gchar *s;
74
75 d = g_date_time_new_now_local();
76 s = g_date_time_format(d, "%Y%m%d%H%M%S");
77 g_date_time_unref(d);
78 return s;
79 }
80
81
82 /** \brief Construct filename for quickload/quicksave snapshots
83 *
84 * \return filename for the quickload/save file, heap-allocated by VICE, so
85 * free after use with lib_free()
86 */
quicksnap_filename(void)87 static char *quicksnap_filename(void)
88 {
89 char *fname;
90 const char *mname;
91 char *cfg;
92
93 mname = machine_class == VICE_MACHINE_C64SC ? "c64sc" : machine_name;
94 cfg = archdep_user_config_path();
95 fname = util_concat(cfg, "/", mname, ".vsf", NULL);
96 #if 0
97 lib_free(cfg);
98 #endif
99 return fname;
100 }
101
102
103 /** \brief Create a filename based on the current datetime
104 *
105 * \return heap-allocated string, owned by VICE, free with lib_free()
106 */
create_proposed_snapshot_name(void)107 static char *create_proposed_snapshot_name(void)
108 {
109 char *date;
110 char *filename;
111
112 date = create_datetime_string();
113 filename = lib_msprintf("vice-snapshot-%s.vsf", date);
114 g_free(date);
115 return filename;
116 }
117
118
119
120 /** \brief Show dialog to save a snapshot
121 */
save_snapshot_dialog(void)122 static void save_snapshot_dialog(void)
123 {
124 GtkWidget *dialog;
125 GtkWidget *extra;
126 GtkWidget *roms_widget;
127 GtkWidget *disks_widget;
128 gint response_id;
129 int save_roms;
130 int save_disks;
131
132 dialog = gtk_file_chooser_dialog_new("Save snapshot file",
133 ui_get_active_window(),
134 GTK_FILE_CHOOSER_ACTION_SAVE,
135 "Save", GTK_RESPONSE_ACCEPT,
136 "Cancel", GTK_RESPONSE_CANCEL,
137 NULL);
138
139 gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dialog),
140 create_file_chooser_filter(file_chooser_filter_snapshot, FALSE));
141
142 /* set proposed filename */
143 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog),
144 create_proposed_snapshot_name());
145
146 /* create extras widget */
147 extra = gtk_grid_new();
148 gtk_grid_set_column_spacing(GTK_GRID(extra), 16);
149
150 disks_widget = gtk_check_button_new_with_label("Save attached disks");
151 roms_widget = gtk_check_button_new_with_label("Save attached ROMs");
152 gtk_grid_attach(GTK_GRID(extra), disks_widget, 0, 0, 1, 1);
153 gtk_grid_attach(GTK_GRID(extra), roms_widget, 1, 0, 1, 1);
154 gtk_widget_show_all(extra);
155
156 gtk_file_chooser_set_extra_widget(GTK_FILE_CHOOSER(dialog), extra);
157 response_id = gtk_dialog_run(GTK_DIALOG(dialog));
158 save_roms = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(roms_widget));
159 save_disks = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(disks_widget));
160 #if 0
161 debug_gtk3("response_id = %d.", response_id);
162 debug_gtk3("save disks = %s.", save_disks ? "YES" : "NO");
163 debug_gtk3("save ROMs = %s.", save_roms ? "YES" : "NO");
164 #endif
165 if (response_id == GTK_RESPONSE_ACCEPT) {
166 gchar *filename;
167
168 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
169 if (filename != NULL) {
170 char *fname_copy;
171 char buffer[1024]; /* I guess this was meant to be used to display
172 a message on the statusbar? */
173
174 fname_copy = util_add_extension_const(filename, "vsf");
175
176 if (machine_write_snapshot(fname_copy, save_roms, save_disks, 0) < 0) {
177 snapshot_display_error();
178 g_snprintf(buffer, 1024, "Failed to save snapshot '%s'",
179 fname_copy);
180 } else {
181 g_snprintf(buffer, 1024, "Saved snapshot '%s'", fname_copy);
182 }
183 lib_free(fname_copy);
184 g_free(filename);
185 }
186 }
187 gtk_widget_destroy(dialog);
188 }
189
190
191 /*****************************************************************************
192 * CPU trap handlers *
193 ****************************************************************************/
194
195 /** \brief Flag indicating the UI is 'done', whatever that means
196 */
197 static bool ui_done;
198
199
200 /** \brief file-open dialog callback for "Load snapshot file"
201 *
202 * \param[in,out] dialog file-open dialog
203 * \param[in,out] filename filename
204 * \param[in] data extra callback data (unused)
205 */
load_snapshot_filename_callback(GtkDialog * dialog,gchar * filename,gpointer data)206 static void load_snapshot_filename_callback(GtkDialog *dialog,
207 gchar *filename,
208 gpointer data)
209 {
210 if (filename != NULL) {
211 /* load snapshot */
212 if (machine_read_snapshot(filename, 0) < 0) {
213 snapshot_display_error();
214 }
215 g_free(filename);
216 }
217 gtk_widget_destroy(GTK_WIDGET(dialog));
218 ui_done = true;
219 }
220
221
222 /** \brief load-snapshot handler
223 *
224 * \param[in] user_data extra callback data (unused)
225 *
226 * \return FALSE
227 */
load_snapshot_trap_impl(gpointer user_data)228 static gboolean load_snapshot_trap_impl(gpointer user_data)
229 {
230 const char *filters[] = { "*.vsf", NULL };
231
232 vice_gtk3_open_file_dialog(
233 "Open snapshot file",
234 "Snapshot files", filters, NULL,
235 load_snapshot_filename_callback,
236 NULL);
237 /* FIXME: shouldn't this return TRUE? */
238 return FALSE;
239 }
240
241 /** \brief CPU trap handler for the load snapshot dialog
242 *
243 * \param[in] addr memory address (unused)
244 * \param[in] data unused
245 */
load_snapshot_trap(uint16_t addr,void * data)246 static void load_snapshot_trap(uint16_t addr, void *data)
247 {
248 vsync_suspend_speed_eval();
249 sound_suspend();
250
251 /*
252 * We need to use the main thread to do UI stuff. And we
253 * also need to block the VICE thread until we get the
254 * user decision.
255 */
256 ui_done = false;
257 gdk_threads_add_timeout(0, load_snapshot_trap_impl, NULL);
258
259 /* block until the operation is done */
260 while (!ui_done) {
261 tick_sleep(tick_per_second() / 60);
262 }
263 }
264
265 /****/
266
267
268 /** \brief save-snapshot handler
269 *
270 * \param[in] user_data extra event data (unused)
271 *
272 * \return FALSE
273 */
save_snapshot_trap_impl(gpointer user_data)274 static gboolean save_snapshot_trap_impl(gpointer user_data)
275 {
276 save_snapshot_dialog();
277
278 ui_done = true;
279
280 return FALSE;
281 }
282
283
284 /** \brief CPU trap handler to trigger the Save dialog
285 *
286 * \param[in] addr memory address (unused)
287 * \param[in] data unused
288 */
save_snapshot_trap(uint16_t addr,void * data)289 static void save_snapshot_trap(uint16_t addr, void *data)
290 {
291 vsync_suspend_speed_eval();
292 sound_suspend();
293
294 /*
295 * We need to use the main thread to do UI stuff. And we
296 * also need to block the VICE thread until we get the
297 * user decision.
298 */
299 ui_done = false;
300 gdk_threads_add_timeout(0, save_snapshot_trap_impl, NULL);
301
302 /* block until the operation is done */
303 while (!ui_done) {
304 tick_sleep(tick_per_second() / 60);
305 }
306 }
307
308
309 /** \brief CPU trap handler for the QuickLoad snapshot menu item
310 *
311 * \param[in] addr memory address (unused)
312 * \param[in] data quickload snapshot filename
313 */
quickload_snapshot_trap(uint16_t addr,void * data)314 static void quickload_snapshot_trap(uint16_t addr, void *data)
315 {
316 char *filename = (char *)data;
317
318 vsync_suspend_speed_eval();
319 sound_suspend();
320
321 if (machine_read_snapshot(filename, 0) < 0) {
322 snapshot_display_error();
323 }
324 lib_free(filename);
325 }
326
327
328 /** \brief CPU trap handler for the QuickSave snapshot menu item
329 *
330 * \param[in] addr memory address (unused)
331 * \param[in] data quicksave snapshot filename
332 */
quicksave_snapshot_trap(uint16_t addr,void * data)333 static void quicksave_snapshot_trap(uint16_t addr, void *data)
334 {
335 char *filename = (char *)data;
336
337 vsync_suspend_speed_eval();
338 sound_suspend();
339
340 if (machine_write_snapshot(filename, TRUE, TRUE, 0) < 0) {
341 snapshot_display_error();
342 }
343 lib_free(filename);
344 }
345
346
347 /*****************************************************************************
348 * Public functions *
349 ****************************************************************************/
350
351
352 /** \brief Display UI to load a snapshot file
353 *
354 * \param[in] parent parent widget
355 * \param[in] user_data unused
356 *
357 * \return TRUE
358 */
ui_snapshot_open_file(GtkWidget * parent,gpointer user_data)359 gboolean ui_snapshot_open_file(GtkWidget *parent, gpointer user_data)
360 {
361 if (!ui_pause_active()) {
362 interrupt_maincpu_trigger_trap(load_snapshot_trap, NULL);
363 } else {
364 load_snapshot_trap_impl(NULL);
365 }
366 return TRUE;
367 }
368
369
370 /** \brief Display UI to save a snapshot file
371 *
372 * \param[in] parent parent widget
373 * \param[in] user_data unused
374 *
375 * \return TRUE
376 */
ui_snapshot_save_file(GtkWidget * parent,gpointer user_data)377 gboolean ui_snapshot_save_file(GtkWidget *parent, gpointer user_data)
378 {
379 if (!ui_pause_active()) {
380 interrupt_maincpu_trigger_trap(save_snapshot_trap, NULL);
381 } else {
382 save_snapshot_trap_impl(NULL);
383 }
384 return TRUE;
385 }
386
387
388 /** \brief Gtk event handler for the QuickLoad menu item
389 *
390 * \param[in] parent parent widget
391 * \param[in] user_data unused
392 *
393 * \return TRUE
394 */
ui_snapshot_quickload_snapshot(GtkWidget * parent,gpointer user_data)395 gboolean ui_snapshot_quickload_snapshot(GtkWidget *parent, gpointer user_data)
396 {
397 char *fname = quicksnap_filename();
398
399 interrupt_maincpu_trigger_trap(quickload_snapshot_trap, (void *)fname);
400 return TRUE;
401 }
402
403
404 /** \brief Gtk event handler for the QuickSave menu item
405 *
406 * \param[in] parent parent widget
407 * \param[in] user_data unused
408 *
409 * \return TRUE
410 */
ui_snapshot_quicksave_snapshot(GtkWidget * parent,gpointer user_data)411 gboolean ui_snapshot_quicksave_snapshot(GtkWidget *parent, gpointer user_data)
412 {
413 char *fname = quicksnap_filename();
414
415 interrupt_maincpu_trigger_trap(quicksave_snapshot_trap, (void *)fname);
416 return TRUE;
417 }
418
419
420 /** \brief Gtk event handler for the "Start recording events" menu item
421 *
422 * \param[in] parent parent widget
423 * \param[in] user_data unused
424 *
425 * \return TRUE
426 */
ui_snapshot_history_record_start(GtkWidget * parent,gpointer user_data)427 gboolean ui_snapshot_history_record_start(GtkWidget *parent, gpointer user_data)
428 {
429 event_record_start();
430 ui_display_recording(1);
431 return TRUE;
432 }
433
434
435 /** \brief Gtk event handler for the "Stop recording events" menu item
436 *
437 * \param[in] parent parent widget
438 * \param[in] user_data unused
439 *
440 * \return TRUE
441 */
ui_snapshot_history_record_stop(GtkWidget * parent,gpointer user_data)442 gboolean ui_snapshot_history_record_stop(GtkWidget *parent, gpointer user_data)
443 {
444 event_record_stop();
445 ui_display_recording(0);
446 return TRUE;
447 }
448
449
450 /** \brief Gtk event handler for the "Start playing back events" menu item
451 *
452 * \param[in] parent parent widget
453 * \param[in] user_data unused
454 *
455 * \return TRUE
456 */
ui_snapshot_history_playback_start(GtkWidget * parent,gpointer user_data)457 gboolean ui_snapshot_history_playback_start(GtkWidget *parent, gpointer user_data)
458 {
459 event_playback_start();
460 return TRUE;
461 }
462
463
464
465 /** \brief Gtk event handler for the "Stop playing back events" menu item
466 *
467 * \param[in] parent parent widget
468 * \param[in] user_data unused
469 *
470 * \return TRUE
471 */
ui_snapshot_history_playback_stop(GtkWidget * parent,gpointer user_data)472 gboolean ui_snapshot_history_playback_stop(GtkWidget *parent, gpointer user_data)
473 {
474 event_playback_stop();
475 return TRUE;
476 }
477
478
479 /** \brief Gtk event handler for the "Set recording milestone" menu item
480 *
481 * \param[in] parent parent widget
482 * \param[in] user_data unused
483 *
484 * \return TRUE
485 */
ui_snapshot_history_milestone_set(GtkWidget * parent,gpointer user_data)486 gboolean ui_snapshot_history_milestone_set(GtkWidget *parent, gpointer user_data)
487 {
488 event_record_set_milestone();
489 return TRUE;
490 }
491
492
493 /** \brief Gtk event handler for the "Return to milestone" menu item
494 *
495 * \param[in] parent parent widget
496 * \param[in] user_data unused
497 *
498 * \return TRUE
499 */
ui_snapshot_history_milestone_reset(GtkWidget * parent,gpointer user_data)500 gboolean ui_snapshot_history_milestone_reset(GtkWidget *parent, gpointer user_data)
501 {
502 event_record_reset_milestone();
503 return TRUE;
504 }
505