[ghex/gtk4-port: 37/91] Implement saving and associated GUI features
- From: Logan Rathbone <larathbone src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [ghex/gtk4-port: 37/91] Implement saving and associated GUI features
- Date: Thu, 12 Aug 2021 23:35:10 +0000 (UTC)
commit 9c23846d7abd2901304bea9ef97bad5e1e4e359d
Author: Logan Rathbone <poprocks gmail com>
Date: Sun Jan 17 23:23:06 2021 -0500
Implement saving and associated GUI features
src/Makefile | 1 +
src/ghex-application-window.c | 510 ++++++++++++++++++++++++++++++++++++++----
src/hex-document.c | 54 ++++-
src/hex-document.h | 3 +
4 files changed, 528 insertions(+), 40 deletions(-)
---
diff --git a/src/Makefile b/src/Makefile
index 8ef5bed1..fcf6132d 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -4,6 +4,7 @@ DEBUG=-g -DENABLE_DEBUG
CC=gcc
GTK_CFLAGS=$(shell pkg-config --libs --cflags gtk4)
CFLAGS=-Wall -Wextra -Werror=implicit -std=c11 -pedantic \
+ -Wno-unused-variable -Wno-unused-parameter \
$(GTK_CFLAGS) \
-I../build -I. \
$(DEBUG)
diff --git a/src/ghex-application-window.c b/src/ghex-application-window.c
index 389c9b4c..8c168e62 100644
--- a/src/ghex-application-window.c
+++ b/src/ghex-application-window.c
@@ -17,7 +17,6 @@
*/
#define offset_fmt "0x%X"
-
/* GHexNotebookTab GOBJECT DEFINITION */
/* This is just an object internal to the app window widget, so we don't
@@ -27,6 +26,11 @@
G_DECLARE_FINAL_TYPE (GHexNotebookTab, ghex_notebook_tab, GHEX, NOTEBOOK_TAB,
GtkWidget)
+enum notebook_signal_types {
+ CLOSED,
+ NOTEBOOK_LAST_SIGNAL
+};
+
struct _GHexNotebookTab
{
GtkWidget parent_instance;
@@ -36,6 +40,8 @@ struct _GHexNotebookTab
GtkHex *gh; /* GtkHex widget activated when tab is clicked */
};
+static guint notebook_signals[NOTEBOOK_LAST_SIGNAL];
+
G_DEFINE_TYPE (GHexNotebookTab, ghex_notebook_tab, GTK_TYPE_WIDGET)
/* GHexNotebookTab - Internal Method Decls */
@@ -43,6 +49,7 @@ G_DEFINE_TYPE (GHexNotebookTab, ghex_notebook_tab, GTK_TYPE_WIDGET)
static GtkWidget * ghex_notebook_tab_new (void);
static void ghex_notebook_tab_add_hex (GHexNotebookTab *self, GtkHex *gh);
static const char * ghex_notebook_tab_get_filename (GHexNotebookTab *self);
+static void ghex_notebook_tab_refresh_file_name (GHexNotebookTab *self);
/* ---- */
@@ -60,6 +67,7 @@ struct _GHexApplicationWindow
guint statusbar_id;
GtkAdjustment *adj;
GList *gh_list;
+ gboolean can_save;
// TEST - NOT 100% SURE I WANNA GO THIS ROUTE YET.
GtkWidget *find_dialog;
@@ -77,7 +85,6 @@ struct _GHexApplicationWindow
GtkWidget *hex_notebook;
GtkWidget *conversions_box;
GtkWidget *findreplace_box;
- GtkWidget *find_button;
GtkWidget *pane_toggle_button;
GtkWidget *insert_mode_button;
GtkWidget *statusbar;
@@ -92,11 +99,30 @@ typedef enum
PROP_FIND_OPEN,
PROP_REPLACE_OPEN,
PROP_JUMP_OPEN,
+ PROP_CAN_SAVE,
N_PROPERTIES
} GHexApplicationWindowProperty;
static GParamSpec *properties[N_PROPERTIES] = { NULL, };
+/* "Main" actions that should all be greyed out and enabled around the same
+ * time - eg, state goes from 'no doc loaded' to 'doc loaded'.
+ *
+ * Don't put open here, as even when we can do nothing else, the user
+ * still can open a document.
+ */
+static const char *main_actions[] = {
+ "ghex.save-as",
+ "ghex.show-conversions",
+ "ghex.insert-mode",
+ "ghex.find",
+ "ghex.replace",
+ "ghex.jump",
+ "ghex.chartable",
+ "ghex.converter",
+ NULL /* last action */
+};
+
G_DEFINE_TYPE (GHexApplicationWindow, ghex_application_window,
GTK_TYPE_APPLICATION_WINDOW)
@@ -116,12 +142,176 @@ static void ghex_application_window_set_show_replace (GHexApplicationWindow *sel
gboolean show);
static void ghex_application_window_set_show_jump (GHexApplicationWindow *self,
gboolean show);
+static void ghex_application_window_set_can_save (GHexApplicationWindow *self,
+ gboolean can_save);
static void set_statusbar(GHexApplicationWindow *self, const char *str);
static void update_status_message (GHexApplicationWindow *self);
-/* PRIVATE FUNCTIONS */
+/* GHexApplicationWindow -- PRIVATE FUNCTIONS */
+
+static void
+file_save (GHexApplicationWindow *self)
+{
+ HexDocument *doc;
+
+ g_return_if_fail (GTK_IS_HEX (self->gh));
+
+ doc = gtk_hex_get_document (self->gh);
+ g_return_if_fail (HEX_IS_DOCUMENT (doc));
+
+ if (hex_document_write (doc))
+ {
+ /* we're happy... */
+ g_debug ("%s: File saved successfully.", __func__);
+ }
+ else
+ {
+ g_debug("%s: NOT IMPLEMENTED - show following message in GUI:",
+ __func__);
+ g_debug(_("Error saving file!"));
+ }
+}
+
+
+static void
+close_doc_response_cb (GtkDialog *dialog,
+ int response_id,
+ gpointer user_data)
+{
+ GHexApplicationWindow *self = GHEX_APPLICATION_WINDOW(user_data);
+
+ /* Bail out if user didn't click Save. */
+ if (response_id != GTK_RESPONSE_ACCEPT) {
+ g_debug ("%s: User did NOT click Save.", __func__);
+ goto end;
+ }
+
+ g_debug ("%s: Decided to SAVE changes.",
+ __func__);
+
+ file_save (self);
+
+end:
+ gtk_window_destroy (GTK_WINDOW(dialog));
+}
+
+static void
+close_doc_confirmation_dialog (GHexApplicationWindow *self)
+{
+ GtkWidget *dialog;
+
+ dialog = gtk_message_dialog_new_with_markup (GTK_WINDOW(self),
+ GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
+ GTK_MESSAGE_QUESTION,
+ GTK_BUTTONS_NONE,
+ (_("<big><b>You have unsaved changes.</b></big>\n\n"
+ "Would you like to save your changes?")));
+
+ gtk_dialog_add_buttons (GTK_DIALOG(dialog),
+ _("_Save Changes"), GTK_RESPONSE_ACCEPT,
+ _("_Discard Changes"), GTK_RESPONSE_REJECT,
+ NULL);
+
+ g_signal_connect (dialog, "response",
+ G_CALLBACK(close_doc_response_cb), self);
+
+ gtk_widget_show (dialog);
+}
+
+static void
+enable_all_actions (GHexApplicationWindow *self, gboolean enable)
+{
+ GHexApplicationWindowClass *klass =
+ g_type_class_peek (GHEX_TYPE_APPLICATION_WINDOW);
+ GtkWidgetClass *widget_class = GTK_WIDGET_CLASS(klass);
+ guint i = 0;
+ /* Note: don't be tempted to omit any of these and pass NULL to the
+ * function below. The docs do not say you can do this, and looking at the
+ * gtkwidget.c source code, doing so may result in dereferencing a NULL ptr
+ */
+ GType owner;
+ const char *action_name;
+ const GVariantType *parameter_type;
+ const char *property_name;
+
+ while (gtk_widget_class_query_action (widget_class,
+ i,
+ &owner,
+ &action_name,
+ ¶meter_type,
+ &property_name))
+ {
+ g_debug("%s: action %u : %s - setting enabled: %d",
+ __func__, i, action_name, enable);
+
+ gtk_widget_action_set_enabled (GTK_WIDGET(self),
+ action_name, enable);
+ ++i;
+ }
+}
+
+/* Kinda like enable_all_actions, but only for ghex-specific ones. */
+static void
+enable_main_actions (GHexApplicationWindow *self, gboolean enable)
+{
+ for (int i = 0; main_actions[i] != NULL; ++i)
+ {
+ g_debug("%s: action %d : %s - setting enabled: %d",
+ __func__, i, main_actions[i], enable);
+
+ gtk_widget_action_set_enabled (GTK_WIDGET(self),
+ main_actions[i], enable);
+ }
+}
+
+/* GHexApplicationWindow -- CALLBACKS */
+static void
+ghex_application_window_file_saved_cb (HexDocument *doc,
+ gpointer user_data)
+{
+ GHexApplicationWindow *self = GHEX_APPLICATION_WINDOW(user_data);
+
+ g_return_if_fail (GHEX_IS_APPLICATION_WINDOW (self));
+
+ ghex_application_window_set_can_save (self,
+ hex_document_has_changed (doc));
+}
+
+static void
+ghex_application_window_document_changed_cb (HexDocument *doc,
+ gpointer change_data,
+ gboolean push_undo,
+ gpointer user_data)
+{
+ GHexApplicationWindow *self = GHEX_APPLICATION_WINDOW(user_data);
+
+ g_return_if_fail (GHEX_IS_APPLICATION_WINDOW (self));
+
+ ghex_application_window_set_can_save (self,
+ hex_document_has_changed (doc));
+}
+
+static void
+tab_close_cb (GHexNotebookTab *tab,
+ gpointer user_data)
+{
+ GHexApplicationWindow *self = GHEX_APPLICATION_WINDOW(user_data);
+ HexDocument *doc;
+
+ doc = gtk_hex_get_document (self->gh);
+ g_return_if_fail (HEX_IS_DOCUMENT (doc));
+
+ if (hex_document_has_changed (doc))
+ {
+ g_debug("%s: There are unsaved changes.", __func__);
+ close_doc_confirmation_dialog (self);
+ }
+ else {
+ g_debug("%s: No unsaved changes.", __func__);
+ }
+}
static void
notebook_switch_page_cb (GtkNotebook *notebook,
@@ -136,8 +326,8 @@ notebook_switch_page_cb (GtkNotebook *notebook,
g_return_if_fail (GHEX_IS_NOTEBOOK_TAB(tab));
- printf("%s: start - tab: %p\n",
- __func__, tab);
+ printf("%s: start - tab: %p - tab->gh: %p - self->gh: %p\n",
+ __func__, (void *)tab, (void *)tab->gh, (void *)self->gh);
if (tab->gh != self->gh) {
ghex_application_window_set_hex (self, tab->gh);
@@ -156,7 +346,7 @@ notebook_page_added_cb (GtkNotebook *notebook,
/* Let's play this super dumb. If a page is added, that will generally
* mind the Find button should be activated. Change if necessary.
*/
- gtk_widget_set_sensitive (self->find_button, TRUE);
+ enable_main_actions (self, TRUE);
}
static void
@@ -171,7 +361,7 @@ notebook_page_removed_cb (GtkNotebook *notebook,
__func__, gtk_notebook_get_n_pages(notebook));
if (gtk_notebook_get_n_pages (notebook) == 0) {
- gtk_widget_set_sensitive (self->find_button, FALSE);
+ enable_main_actions (self, FALSE);
}
}
@@ -236,7 +426,7 @@ setup_chartable (GHexApplicationWindow *self)
self->chartable = create_char_table (GTK_WINDOW(self), self->gh);
- g_signal_connect(G_OBJECT(self->chartable), "close-request",
+ g_signal_connect(self->chartable, "close-request",
G_CALLBACK(chartable_close_cb), self);
}
@@ -247,7 +437,7 @@ setup_converter (GHexApplicationWindow *self)
self->converter = create_converter (GTK_WINDOW(self), self->gh);
- g_signal_connect(G_OBJECT(self->converter), "close-request",
+ g_signal_connect(self->converter, "close-request",
G_CALLBACK(converter_close_cb), self);
}
@@ -316,6 +506,141 @@ PANE_SET_SHOW_TEMPLATE(find, replace, jump, PROP_FIND_OPEN)
PANE_SET_SHOW_TEMPLATE(replace, find, jump, PROP_REPLACE_OPEN)
PANE_SET_SHOW_TEMPLATE(jump, find, replace, PROP_JUMP_OPEN)
+static void
+ghex_application_window_set_can_save (GHexApplicationWindow *self,
+ gboolean can_save)
+{
+ g_return_if_fail (GHEX_IS_APPLICATION_WINDOW (self));
+
+ self->can_save = can_save;
+ g_debug("%s: start - can_save: %d", __func__, can_save);
+
+ gtk_widget_action_set_enabled (GTK_WIDGET(self),
+ "ghex.save", can_save);
+
+ g_object_notify_by_pspec (G_OBJECT(self), properties[PROP_CAN_SAVE]);
+}
+
+/* For now, at least, this is a mostly pointless wrapper around 'file_save'.
+ */
+static void
+save_action (GtkWidget *widget,
+ const char *action_name,
+ GVariant *parameter)
+{
+ GHexApplicationWindow *self = GHEX_APPLICATION_WINDOW(widget);
+
+ file_save (self);
+}
+
+/* save_as helper */
+static void
+save_as_response_cb (GtkNativeDialog *dialog,
+ int resp,
+ gpointer user_data)
+{
+ GHexApplicationWindow *self = GHEX_APPLICATION_WINDOW(user_data);
+ GtkFileChooser *chooser = GTK_FILE_CHOOSER(dialog);
+ HexDocument *doc;
+ GFile *gfile;
+ char *new_file_path;
+ FILE *file;
+ gchar *gtk_file_name;
+
+ /* If user doesn't click Save, just bail out now. */
+ if (resp != GTK_RESPONSE_ACCEPT)
+ goto end;
+
+ /* Fetch doc. No need for sanity checks as this is just a helper. */
+ doc = gtk_hex_get_document (self->gh);
+
+ /* Get filename. */
+ gfile = gtk_file_chooser_get_file (chooser);
+ new_file_path = g_file_get_path (gfile);
+ g_clear_object (&gfile);
+
+ g_debug("%s: GONNA OPEN FILE FOR WRITING: %s",
+ __func__, new_file_path);
+
+ file = fopen(new_file_path, "wb");
+
+ /* Sanity check */
+ if (file == NULL) {
+ g_debug ("%s: Error dialog not implemented! Can't open file rw!",
+ __func__);
+ goto end;
+ }
+
+ if (hex_document_write_to_file (doc, file))
+ {
+ gboolean change_ok;
+ char *gtk_file_name;
+
+ change_ok = hex_document_change_file_name (doc, new_file_path);
+
+ /* "set window to file-name"
+ * if (change_ok) ..... */
+
+ gtk_file_name = g_filename_to_utf8 (doc->file_name,
+ -1, NULL, NULL, NULL);
+
+ g_debug("%s: NOT IMPLEMENTED - show following message in GUI:",
+ __func__);
+ g_debug(_("Saved buffer to file %s"), gtk_file_name);
+
+ g_free(gtk_file_name);
+ }
+ else
+ {
+ g_debug("%s: NOT IMPLEMENTED - show following message in GUI:",
+ __func__);
+ g_debug(_("Error saving file!"));
+ }
+ fclose(file);
+
+end:
+ g_debug("%s: END.", __func__);
+ g_object_unref (dialog);
+}
+
+static void
+save_as (GtkWidget *widget,
+ const char *action_name,
+ GVariant *parameter)
+{
+ GHexApplicationWindow *self = GHEX_APPLICATION_WINDOW(widget);
+ GtkFileChooserNative *file_sel;
+ GtkResponseType resp;
+ HexDocument *doc;
+ GFile *existing_file;
+
+ g_return_if_fail (GTK_IS_HEX (self->gh));
+
+ doc = gtk_hex_get_document (self->gh);
+ g_return_if_fail (HEX_IS_DOCUMENT (doc));
+
+ existing_file = g_file_new_for_path (doc->file_name);
+
+ file_sel =
+ gtk_file_chooser_native_new (_("Select a file to save buffer as"),
+ GTK_WINDOW(self),
+ GTK_FILE_CHOOSER_ACTION_SAVE,
+ NULL, // const char *accept_label } NULL == default.
+ NULL); // const char *cancel_label }
+
+ /* Default suggested file == existing file. */
+ gtk_file_chooser_set_file (GTK_FILE_CHOOSER(file_sel), existing_file,
+ NULL); // GError **error
+
+ g_signal_connect (file_sel, "response",
+ G_CALLBACK(save_as_response_cb), self);
+
+ gtk_native_dialog_show (GTK_NATIVE_DIALOG(file_sel));
+
+ /* Clear the GFile ptr which is no longer necessary. */
+ g_clear_object (&existing_file);
+}
+
/* convenience helper function to build a GtkHex widget pre-loaded with
* a hex document, from a GFile *.
*/
@@ -324,7 +649,6 @@ new_gh_from_gfile (GFile *file)
{
char *path;
GFileInfo *info;
- const char *name;
GError *error = NULL;
HexDocument *doc;
GtkHex *gh;
@@ -335,7 +659,6 @@ new_gh_from_gfile (GFile *file)
G_FILE_QUERY_INFO_NONE, // GFileQueryInfoFlags flags
NULL, // GCancellable
*cancellable
&error);
- name = g_file_info_get_display_name (info);
g_debug("%s: path acc. to GFile: %s",
__func__, path);
@@ -354,7 +677,7 @@ new_gh_from_gfile (GFile *file)
}
static void
-open_response_cb (GtkDialog *dialog,
+open_response_cb (GtkNativeDialog *dialog,
int resp,
gpointer user_data)
{
@@ -363,7 +686,7 @@ open_response_cb (GtkDialog *dialog,
GFile *file;
GtkHex *gh;
- if (resp == GTK_RESPONSE_OK)
+ if (resp == GTK_RESPONSE_ACCEPT)
{
file = gtk_file_chooser_get_file (chooser);
gh = new_gh_from_gfile (file);
@@ -377,7 +700,7 @@ open_response_cb (GtkDialog *dialog,
g_debug ("%s: User didn't click Open. Bail out.",
__func__);
}
- gtk_window_destroy (GTK_WINDOW(dialog));
+ g_object_unref (dialog);
}
static void
@@ -386,21 +709,20 @@ open_file (GtkWidget *widget,
GVariant *parameter)
{
GHexApplicationWindow *self = GHEX_APPLICATION_WINDOW(widget);
- GtkWidget *file_sel;
+ GtkFileChooserNative *file_sel;
GtkResponseType resp;
- file_sel = gtk_file_chooser_dialog_new (_("Select a file to open"),
- GTK_WINDOW(self),
- GTK_FILE_CHOOSER_ACTION_OPEN,
- _("_Cancel"), GTK_RESPONSE_CANCEL,
- _("_Open"), GTK_RESPONSE_OK,
- NULL);
-
- gtk_window_set_modal (GTK_WINDOW(file_sel), TRUE);
- gtk_widget_show (file_sel);
+ file_sel =
+ gtk_file_chooser_native_new (_("Select a file to open"),
+ GTK_WINDOW(self),
+ GTK_FILE_CHOOSER_ACTION_OPEN,
+ NULL, // const char *accept_label } NULL == default.
+ NULL); // const char *cancel_label }
g_signal_connect (file_sel, "response",
G_CALLBACK(open_response_cb), self);
+
+ gtk_native_dialog_show (GTK_NATIVE_DIALOG(file_sel));
}
static void
@@ -548,6 +870,11 @@ ghex_application_window_set_property (GObject *object,
g_value_get_boolean (value));
break;
+ case PROP_CAN_SAVE:
+ ghex_application_window_set_can_save (self,
+ g_value_get_boolean (value));
+ break;
+
default:
/* We don't have any other property... */
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -595,6 +922,10 @@ ghex_application_window_get_property (GObject *object,
gtk_widget_get_visible (self->jump_dialog));
break;
+ case PROP_CAN_SAVE:
+ g_value_set_boolean (value, self->can_save);
+ break;
+
default:
/* We don't have any other property... */
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, property_id, pspec);
@@ -604,12 +935,53 @@ ghex_application_window_get_property (GObject *object,
/* GHexNotebookTab -- CALLBACKS */
+/* _document_changed_cb helper fcn. */
+static void
+tab_bold_label (GHexNotebookTab *self, gboolean bold)
+{
+ GtkLabel *label = GTK_LABEL(self->label);
+ const char *text;
+ char *new = NULL;
+
+ text = gtk_label_get_text (label);
+
+ if (bold) {
+ new = g_strdup_printf("<b>%s</b>", text);
+ }
+ else {
+ new = g_strdup (text);
+ }
+ gtk_label_set_markup (label, new);
+ g_free (new);
+}
+
+static void
+ghex_notebook_tab_document_changed_cb (HexDocument *doc,
+ gpointer change_data,
+ gboolean push_undo,
+ gpointer user_data)
+{
+ GHexNotebookTab *self = GHEX_NOTEBOOK_TAB(user_data);
+
+ g_debug ("%s: DETECTED DOC CHANGED.", __func__);
+
+ (void)change_data, (void)push_undo; /* unused */
+
+ tab_bold_label (self, hex_document_has_changed (doc));
+}
+
static void
-dumb_click_cb (GtkButton *button,
+ghex_notebook_tab_close_click_cb (GtkButton *button,
gpointer user_data)
{
- g_debug("%s: clicked btn: %p",
+ GHexNotebookTab *self = GHEX_NOTEBOOK_TAB(user_data);
+
+ g_debug("%s: clicked btn: %p - EMITTING CLOSED SIGNAL",
__func__, (void *)button);
+
+ g_signal_emit(self,
+ notebook_signals[CLOSED],
+ 0); // GQuark detail (just set to 0 if unknown)
}
@@ -644,8 +1016,8 @@ ghex_notebook_tab_init (GHexNotebookTab *self)
* because this only pertains to the label of the tab and not the
* tab as a whole.
*/
- g_signal_connect(self->close_btn, "clicked",
- G_CALLBACK(dumb_click_cb), self);
+ g_signal_connect (self->close_btn, "clicked",
+ G_CALLBACK(ghex_notebook_tab_close_click_cb), self);
}
static void
@@ -682,10 +1054,41 @@ ghex_notebook_tab_class_init (GHexNotebookTabClass *klass)
/* Layout manager: box-style layout. */
gtk_widget_class_set_layout_manager_type (widget_class,
GTK_TYPE_BOX_LAYOUT);
+
+ /* SIGNALS */
+
+ notebook_signals[CLOSED] = g_signal_new_class_handler("closed",
+ G_OBJECT_CLASS_TYPE(object_class),
+ G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
+ /* GCallback class_handler: */
+ NULL,
+ /* no accumulator or accu_data */
+ NULL, NULL,
+ /* GSignalCMarshaller c_marshaller: */
+ NULL, /* use generic marshaller */
+ /* GType return_type: */
+ G_TYPE_NONE,
+ /* guint n_params: */
+ 0);
}
/* GHexNotebookTab - Internal Methods */
+static void
+ghex_notebook_tab_refresh_file_name (GHexNotebookTab *self)
+{
+ HexDocument *doc;
+ char *basename;
+
+ doc = gtk_hex_get_document (self->gh);
+ basename = g_path_get_basename (doc->file_name);
+
+ gtk_label_set_markup (GTK_LABEL(self->label), basename);
+ tab_bold_label (self, hex_document_has_changed (doc));
+
+ g_free (basename);
+}
+
static GtkWidget *
ghex_notebook_tab_new (void)
{
@@ -698,7 +1101,6 @@ static void
ghex_notebook_tab_add_hex (GHexNotebookTab *self, GtkHex *gh)
{
HexDocument *doc;
- char *basename;
/* Do some sanity checks, as this method requires that some ducks be in
* a row -- we need a valid GtkHex that is pre-loaded with a valid
@@ -714,11 +1116,17 @@ ghex_notebook_tab_add_hex (GHexNotebookTab *self, GtkHex *gh)
self->gh = gh;
/* Set name of tab. */
- basename = g_path_get_basename (doc->file_name);
+ ghex_notebook_tab_refresh_file_name (self);
- gtk_label_set_text (GTK_LABEL(self->label), basename);
+ /* HexDocument - Setup signals */
+ g_signal_connect (doc, "document-changed",
+ G_CALLBACK(ghex_notebook_tab_document_changed_cb), self);
- g_free (basename);
+ g_signal_connect_swapped (doc, "file-name-changed",
+ G_CALLBACK(ghex_notebook_tab_refresh_file_name), self);
+
+ g_signal_connect_swapped (doc, "file-saved",
+ G_CALLBACK(ghex_notebook_tab_refresh_file_name), self);
}
static const char *
@@ -802,8 +1210,12 @@ ghex_application_window_init (GHexApplicationWindow *self)
clear_statusbar (self);
- /* Grey out Find button at the beginning */
- gtk_widget_set_sensitive (self->find_button, FALSE);
+ /* Grey out main actions at the beginning */
+ enable_main_actions (self, FALSE);
+
+ /* Grey out save (special case - it's not lumped in with mains */
+ gtk_widget_action_set_enabled (GTK_WIDGET(self),
+ "ghex.save", FALSE);
}
static void
@@ -876,6 +1288,13 @@ ghex_application_window_class_init(GHexApplicationWindowClass *klass)
FALSE, // gboolean default_value
G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+ properties[PROP_CAN_SAVE] =
+ g_param_spec_boolean ("can-save",
+ "Can save",
+ "Whether the Save button should currently be clickable",
+ FALSE, // gboolean default_value
+ G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | G_PARAM_EXPLICIT_NOTIFY);
+
g_object_class_install_properties (object_class, N_PROPERTIES, properties);
@@ -885,6 +1304,14 @@ ghex_application_window_class_init(GHexApplicationWindowClass *klass)
NULL, // GVariant string param_type
open_file);
+ gtk_widget_class_install_action (widget_class, "ghex.save",
+ NULL, // GVariant string param_type
+ save_action);
+
+ gtk_widget_class_install_action (widget_class, "ghex.save-as",
+ NULL, // GVariant string param_type
+ save_as);
+
gtk_widget_class_install_action (widget_class, "ghex.show-conversions",
NULL, // GVariant string param_type
toggle_conversions);
@@ -922,8 +1349,6 @@ ghex_application_window_class_init(GHexApplicationWindowClass *klass)
conversions_box);
gtk_widget_class_bind_template_child (widget_class, GHexApplicationWindow,
findreplace_box);
- gtk_widget_class_bind_template_child (widget_class, GHexApplicationWindow,
- find_button);
gtk_widget_class_bind_template_child (widget_class, GHexApplicationWindow,
pane_toggle_button);
gtk_widget_class_bind_template_child (widget_class, GHexApplicationWindow,
@@ -983,6 +1408,7 @@ ghex_application_window_add_hex (GHexApplicationWindow *self,
g_return_if_fail (HEX_IS_DOCUMENT(doc));
/* Add this GtkHex to our internal list */
+ // FIXME / TODO - used for nothing rn.
self->gh_list = g_list_append (self->gh_list, gh);
/* Set this GtkHex as the current viewed gh if there is no currently
@@ -994,6 +1420,8 @@ ghex_application_window_add_hex (GHexApplicationWindow *self,
tab = ghex_notebook_tab_new ();
printf ("%s: CREATED TAB -- %p\n", __func__, (void *)tab);
ghex_notebook_tab_add_hex (GHEX_NOTEBOOK_TAB(tab), gh);
+ g_signal_connect (tab, "closed",
+ G_CALLBACK(tab_close_cb), self);
gtk_notebook_append_page (GTK_NOTEBOOK(self->hex_notebook),
GTK_WIDGET(gh),
@@ -1006,8 +1434,14 @@ ghex_application_window_add_hex (GHexApplicationWindow *self,
ghex_notebook_tab_get_filename (GHEX_NOTEBOOK_TAB(tab)));
/* Setup signals */
- g_signal_connect(G_OBJECT(gh), "cursor-moved",
- G_CALLBACK(cursor_moved_cb), self);
+ g_signal_connect (gh, "cursor-moved",
+ G_CALLBACK(cursor_moved_cb), self);
+
+ g_signal_connect (doc, "document-changed",
+ G_CALLBACK(ghex_application_window_document_changed_cb), self);
+
+ g_signal_connect (doc, "file-saved",
+ G_CALLBACK(ghex_application_window_file_saved_cb), self);
/* Setup find_dialog & friends. */
diff --git a/src/hex-document.c b/src/hex-document.c
index f68b4ddb..abc6e451 100644
--- a/src/hex-document.c
+++ b/src/hex-document.c
@@ -58,6 +58,8 @@ enum {
UNDO,
REDO,
UNDO_STACK_FORGET,
+ FILE_NAME_CHANGED,
+ FILE_SAVED,
LAST_SIGNAL
};
@@ -328,6 +330,7 @@ hex_document_class_init (HexDocumentClass *klass)
klass->undo = hex_document_real_undo;
klass->redo = hex_document_real_redo;
klass->undo_stack_forget = NULL;
+ klass->file_name_changed = NULL;
hex_signals[DOCUMENT_CHANGED] =
g_signal_new ("document-changed",
@@ -366,6 +369,26 @@ hex_document_class_init (HexDocumentClass *klass)
NULL,
NULL,
G_TYPE_NONE, 0);
+
+ hex_signals[FILE_NAME_CHANGED] =
+ g_signal_new ("file-name-changed",
+ G_TYPE_FROM_CLASS(gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (HexDocumentClass, file_name_changed),
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE, 0);
+
+ hex_signals[FILE_SAVED] =
+ g_signal_new ("file-saved",
+ G_TYPE_FROM_CLASS(gobject_class),
+ G_SIGNAL_RUN_FIRST,
+ G_STRUCT_OFFSET (HexDocumentClass, file_saved),
+ NULL,
+ NULL,
+ NULL,
+ G_TYPE_NONE, 0);
}
static void
@@ -685,7 +708,6 @@ hex_document_write_to_file(HexDocument *doc, FILE *file)
ret = fwrite(doc->gap_pos + doc->gap_size, 1, exp_len, file);
ret = (ret == exp_len)?TRUE:FALSE;
}
-
return ret;
}
@@ -705,7 +727,7 @@ hex_document_write(HexDocument *doc)
doc->changed = FALSE;
}
}
-
+ g_signal_emit (G_OBJECT(doc), hex_signals[FILE_SAVED], 0);
return ret;
}
@@ -1107,3 +1129,31 @@ hex_document_get_list()
{
return doc_list;
}
+
+gboolean
+hex_document_change_file_name (HexDocument *doc, const char *new_file_name)
+{
+ char *new_path_end = NULL;
+
+ if(doc->file_name)
+ g_free(doc->file_name);
+ if(doc->path_end)
+ g_free(doc->path_end);
+
+ doc->file_name = g_strdup(new_file_name);
+ doc->changed = FALSE;
+
+ new_path_end = g_path_get_basename (doc->file_name);
+ doc->path_end = g_filename_to_utf8 (new_path_end, -1, NULL, NULL, NULL);
+
+ if (new_path_end)
+ g_free (new_path_end);
+
+
+ if (doc->file_name && doc->path_end) {
+ g_signal_emit (G_OBJECT(doc), hex_signals[FILE_NAME_CHANGED], 0);
+ return TRUE;
+ } else {
+ return FALSE;
+ }
+}
diff --git a/src/hex-document.h b/src/hex-document.h
index 3bc97b67..53c8940e 100644
--- a/src/hex-document.h
+++ b/src/hex-document.h
@@ -86,6 +86,8 @@ struct _HexDocumentClass
void (*undo)(HexDocument *);
void (*redo)(HexDocument *);
void (*undo_stack_forget)(HexDocument *);
+ void (*file_name_changed)(HexDocument *);
+ void (*file_saved)(HexDocument *);
};
GType hex_document_get_type(void);
@@ -126,6 +128,7 @@ void hex_document_remove_view(HexDocument *doc, GtkWidget *view);
GtkWidget *hex_document_add_view(HexDocument *doc);
const GList *hex_document_get_list(void);
gboolean hex_document_is_writable(HexDocument *doc);
+gboolean hex_document_change_file_name (HexDocument *doc, const char *new_file_name);
G_END_DECLS
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]