From aebc5bf5dd42073be3e69b43108b6fd9621db048 Mon Sep 17 00:00:00 2001 From: Roland Lutz Date: Mon, 7 Oct 2019 13:38:47 +0200 Subject: [PATCH] gschem: Warn on open/save if file was changed on disk --- gschem/src/x_highlevel.c | 161 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 160 insertions(+), 1 deletion(-) diff --git a/gschem/src/x_highlevel.c b/gschem/src/x_highlevel.c index f1c04bf1b..8659cb8a2 100644 --- a/gschem/src/x_highlevel.c +++ b/gschem/src/x_highlevel.c @@ -29,9 +29,54 @@ */ #include +#include +#include + #include "gschem.h" +/*! \brief Check if file on disk is more recent than saved timestamp. + * + * \param [in] filename filename on disk + * \param [in] has_known_mtime whether \a known_mtime is assumed to + * contain a valid value + * \param [in] known_mtime timestamp to compare to + * + * \returns If the file is missing, returns \c FALSE. If no timestamp + * was recorded (\a has_known_mtime is \c FALSE) but the file + * exists, returns \c TRUE. Otherwise, returns whether the + * file on disk is more recent than the saved timestamp. + */ +static gboolean +file_changed_since (const gchar *filename, + gboolean has_known_mtime, + struct timespec known_mtime) +{ + struct stat buf; + + if (stat (filename, &buf) == -1) + /* file currently doesn't exist */ + return FALSE; + + if (!has_known_mtime) + /* file has been created on disk */ + return TRUE; + + if (buf.st_mtim.tv_sec > known_mtime.tv_sec) + /* disk seconds are more recent than known seconds */ + return TRUE; + if (buf.st_mtim.tv_sec < known_mtime.tv_sec) + /* disk seconds are older than known seconds (file went back in time?) */ + return FALSE; + + if (buf.st_mtim.tv_nsec > known_mtime.tv_nsec) + /* disk nanoseconds are more recent than known nanoseconds */ + return TRUE; + /* disk nanoseconds are equal or older than known nanoseconds */ + return FALSE; +} + + /*! \brief Create a new page with a titleblock. * * If \a filename is \c NULL, the name of the new page is build from @@ -58,6 +103,65 @@ x_highlevel_new_page (GschemToplevel *w_current, const gchar *filename) } +/*! \brief Show "File changed. Reread from disk?" dialog. + */ +static gint +x_dialog_reread_from_disk (GschemToplevel *w_current, PAGE *page) +{ + const gchar *fmt, *secondary_text; + GtkWidget *dialog, *button; + gint response_id; + + if (page->exists_on_disk) { + fmt = _("The file \"%s\" has changed on disk."), + secondary_text = page->CHANGED + ? _("Do you want to drop your changes and reload the file?") + : _("Do you want to reload it?"); + } else { + fmt = _("The file \"%s\" has been created on disk."), + secondary_text = page->CHANGED + ? _("Do you want to drop your changes and load the file?") + : _("Do you want to open it?"); + } + + dialog = gtk_message_dialog_new (GTK_WINDOW (w_current->main_window), + GTK_DIALOG_MODAL | + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_NONE, + fmt, page->page_filename); + g_object_set (dialog, "secondary-text", secondary_text, NULL); + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + NULL); + button = gtk_button_new_with_mnemonic (page->CHANGED ? _("_Revert") : + page->exists_on_disk ? _("_Reload") : + _("_Open")); + gtk_widget_set_can_default (button, TRUE); + gtk_button_set_image (GTK_BUTTON (button), + gtk_image_new_from_stock ( + page->CHANGED ? GTK_STOCK_REVERT_TO_SAVED : + page->exists_on_disk ? GTK_STOCK_REFRESH : + GTK_STOCK_OPEN, + GTK_ICON_SIZE_BUTTON)); + gtk_widget_show (button); + gtk_dialog_add_action_widget (GTK_DIALOG (dialog), button, + GTK_RESPONSE_ACCEPT); + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_ACCEPT, + GTK_RESPONSE_CANCEL, + -1); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), + page->CHANGED ? GTK_RESPONSE_CANCEL + : GTK_RESPONSE_ACCEPT); + gtk_window_set_title (GTK_WINDOW (dialog), _("gschem")); + response_id = gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + + return response_id; +} + + /*! \brief Helper function for \c x_highlevel_open_page(s). * * If there is a matching page in \a w_current, returns a pointer to @@ -88,8 +192,17 @@ open_or_create_page (GschemToplevel *w_current, const gchar *filename, exist, x_lowlevel_open_page() will take care of finding and returning the correct page. */ page = s_page_search (toplevel, filename); - if (page != NULL) + if (page != NULL) { + if (page->page_filename != NULL && + file_changed_since (page->page_filename, + page->exists_on_disk, + page->last_modified) && + x_dialog_reread_from_disk (w_current, page) == GTK_RESPONSE_ACCEPT) { + x_lowlevel_revert_page (w_current, page); + return s_page_search (toplevel, filename); + } return page; + } /* open page if file exists */ full_filename = f_normalize_filename (filename, NULL); @@ -140,6 +253,7 @@ x_highlevel_open_page (GschemToplevel *w_current, const gchar *filename) x_window_set_current_page (w_current, page); if (sole_page != NULL && toplevel->page_current != sole_page && + g_list_find (geda_list_get_glist (toplevel->pages), sole_page) != NULL && sole_page->is_untitled && !sole_page->CHANGED) x_lowlevel_close_page (w_current, sole_page); @@ -195,6 +309,7 @@ x_highlevel_open_pages (GschemToplevel *w_current, GSList *filenames, x_window_set_current_page (w_current, first_page); if (sole_page != NULL && toplevel->page_current != sole_page && + g_list_find (geda_list_get_glist (toplevel->pages), sole_page) != NULL && sole_page->is_untitled && !sole_page->CHANGED) x_lowlevel_close_page (w_current, sole_page); @@ -206,6 +321,43 @@ x_highlevel_open_pages (GschemToplevel *w_current, GSList *filenames, } +/*! \brief Show "File changed. Save anyway?" dialog. + */ +static gint +x_dialog_save_anyway (GschemToplevel *w_current, PAGE *page) +{ + gchar *basename; + GtkWidget *dialog; + gint response_id; + + basename = g_path_get_basename (page->page_filename); + dialog = gtk_message_dialog_new (GTK_WINDOW (w_current->main_window), + GTK_DIALOG_MODAL | + GTK_DIALOG_DESTROY_WITH_PARENT, + GTK_MESSAGE_WARNING, + GTK_BUTTONS_NONE, + _("\"%s\" changed since visited or " + "saved."), + basename); + g_free (basename); + g_object_set (dialog, "secondary-text", _("Save anyway?"), NULL); + gtk_dialog_add_buttons (GTK_DIALOG (dialog), + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + gtk_dialog_set_alternative_button_order (GTK_DIALOG (dialog), + GTK_RESPONSE_ACCEPT, + GTK_RESPONSE_CANCEL, + -1); + gtk_dialog_set_default_response (GTK_DIALOG (dialog), GTK_RESPONSE_CANCEL); + gtk_window_set_title (GTK_WINDOW (dialog), _("gschem")); + response_id = gtk_dialog_run (GTK_DIALOG (dialog)); + gtk_widget_destroy (dialog); + + return response_id; +} + + /*! \brief Save a page. * * Saves a page to its current filename. If the page is untitled, @@ -235,6 +387,13 @@ x_highlevel_save_page (GschemToplevel *w_current, PAGE *page) return x_fileselect_save (w_current); } + if (page->page_filename != NULL && + file_changed_since (page->page_filename, + page->exists_on_disk, + page->last_modified) && + x_dialog_save_anyway (w_current, page) != GTK_RESPONSE_ACCEPT) + return FALSE; + return x_lowlevel_save_page (w_current, page, page->page_filename); } -- 2.11.4.GIT