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