[dia] app: use a common DiaExitDialog when closing diagrams



commit 5cb87e7d8b2a054f91f944255c1b4bb85d8982b6
Author: Zander Brown <zbrown gnome org>
Date:   Thu Apr 23 12:08:32 2020 +0100

    app: use a common DiaExitDialog when closing diagrams
    
    Rework exit_dialog as a GObject wrapping GtkMessageDialog for a UX which
    matches the HIG
    
    Nicely handles 1 diagram vs multiple diagrams
    
    Use DiaExitDialog for closing classic windows or tabs as well as
    integrated windows
    
    Fix a leak or two

 app/app_procs.c   |  58 ++---
 app/display.c     | 144 ++++++------
 app/exit_dialog.c | 660 ++++++++++++++++++++++++------------------------------
 app/exit_dialog.h |  66 +++---
 app/meson.build   |   2 +
 5 files changed, 421 insertions(+), 509 deletions(-)
---
diff --git a/app/app_procs.c b/app/app_procs.c
index 117c2ec4..cc66d71e 100644
--- a/app/app_procs.c
+++ b/app/app_procs.c
@@ -889,56 +889,60 @@ app_exit (void)
 
   if (diagram_modified_exists()) {
     if (is_integrated_ui ()) {
-      GtkWidget                *dialog;
-      int                       result;
-      exit_dialog_item_array_t *items  = NULL;
-      GList *                   diagrams;
-      Diagram *                 diagram;
+      DiaExitDialog *dialog;
+      int            result;
+      GPtrArray     *items  = NULL;
+      GList         *diagrams;
+      Diagram       *diagram;
 
-      dialog = exit_dialog_make (GTK_WINDOW (interface_get_toolbox_shell ()),
-                                 _("Exiting Dia"));
+      dialog = dia_exit_dialog_new (GTK_WINDOW (interface_get_toolbox_shell ()));
 
       diagrams = dia_open_diagrams ();
       while (diagrams) {
         diagram = diagrams->data;
 
         if (diagram_is_modified (diagram)) {
-          const gchar * name = diagram_get_name (diagram);
-          const gchar * path = diagram->filename;
-          exit_dialog_add_item (dialog, name, path, diagram);
+          const char *name = diagram_get_name (diagram);
+          const char *path = diagram->filename;
+          dia_exit_dialog_add_item (dialog, name, path, diagram);
         }
 
         diagrams = g_list_next (diagrams);
       }
 
-      result = exit_dialog_run (dialog, &items);
+      result = dia_exit_dialog_run (dialog, &items);
 
-      gtk_widget_destroy (dialog);
+      g_clear_object (&dialog);
 
-      if (result == EXIT_DIALOG_EXIT_CANCEL) {
+      if (result == DIA_EXIT_DIALOG_CANCEL) {
         return FALSE;
-      } else if (result == EXIT_DIALOG_EXIT_SAVE_SELECTED) {
-        DiaContext *ctx = dia_context_new(_("Save"));
-        int i;
-        for (i = 0; i < items->array_size; i++) {
-          gchar *filename;
-
-          diagram  = items->array[i].data;
-          filename = g_filename_from_utf8 (diagram->filename, -1, NULL, NULL, NULL);
-          diagram_update_extents (diagram);
+      } else if (result == DIA_EXIT_DIALOG_SAVE) {
+        DiaContext *ctx = dia_context_new (_("Save"));
+
+        for (int i = 0; i < items->len; i++) {
+          DiaExitDialogItem *item = g_ptr_array_index (items, i);
+          char *filename;
+
+          filename = g_filename_from_utf8 (item->data->filename, -1, NULL, NULL, NULL);
+          diagram_update_extents (item->data);
           dia_context_set_filename (ctx, filename);
-          if (!diagram_save (diagram, filename, ctx)) {
-            exit_dialog_free_items (items);
+
+          if (!diagram_save (item->data, filename, ctx)) {
             dia_context_release (ctx);
+
+            g_clear_pointer (&filename, g_free);
+            g_clear_pointer (&items, g_ptr_array_unref);
+
             return FALSE;
           } else {
             dia_context_reset (ctx);
           }
+
           g_clear_pointer (&filename, g_free);
         }
+
         dia_context_release (ctx);
-        exit_dialog_free_items (items);
-      } else if (result == EXIT_DIALOG_EXIT_NO_SAVE) {
+      } else if (result == DIA_EXIT_DIALOG_QUIT) {
         diagrams = dia_open_diagrams ();
         while (diagrams) {
           diagram = diagrams->data;
@@ -949,6 +953,8 @@ app_exit (void)
           diagrams = g_list_next (diagrams);
         }
       }
+
+      g_clear_pointer (&items, g_ptr_array_unref);
     } else {
       GtkWidget *dialog;
       GtkWidget *button;
diff --git a/app/display.c b/app/display.c
index c94c1fc9..8564fc0e 100644
--- a/app/display.c
+++ b/app/display.c
@@ -47,6 +47,8 @@
 #include "recent_files.h"
 #include "filedlg.h"
 #include "dia-layer.h"
+#include "exit_dialog.h"
+
 
 static GdkCursor *current_cursor = NULL;
 
@@ -1167,98 +1169,82 @@ ddisp_destroy (DDisplay *ddisp)
 }
 
 
-static void
-are_you_sure_close_dialog_respond (GtkWidget *widget, /* the dialog */
-                                   gint       response_id,
-                                   gpointer   user_data) /* the display */
-{
-  DDisplay *ddisp = (DDisplay *)user_data;
-  gboolean close_ddisp = TRUE;
-
-  switch (response_id) {
-  case GTK_RESPONSE_YES :
-    /* save changes */
-    if (ddisp->diagram->unsaved) {
-      /* we have to open the file dlg, close this one first */
-      gtk_widget_destroy(widget);
-      if (file_save_as(ddisp->diagram, ddisp))
-        ddisp_destroy (ddisp);
-      /* no way back */
-      return;
-    } else {
-      DiaContext *ctx = dia_context_new (_("Save"));
-      if (!diagram_save(ddisp->diagram, ddisp->diagram->filename, ctx))
-        close_ddisp = FALSE;
-      dia_context_release (ctx);
-    }
-    if (close_ddisp) /* saving succeeded */
-      recent_file_history_add(ddisp->diagram->filename);
-
-    /* fall through */
-  case GTK_RESPONSE_NO :
-    if (close_ddisp)
-      ddisp_destroy (ddisp);
-    /* fall through */
-  case GTK_RESPONSE_CANCEL :
-  case GTK_RESPONSE_NONE :
-  case GTK_RESPONSE_DELETE_EVENT : /* closing via window manager */
-    gtk_widget_destroy(widget);
-    break;
-  default :
-    g_assert_not_reached();
-  }
-}
-
 void
 ddisplay_close (DDisplay *ddisp)
 {
   Diagram *dia;
-  GtkWidget *dialog, *button;
-  gchar *fname;
+  DiaExitDialog *dialog;
+  DiaExitDialogResult res;
+  GPtrArray *items = NULL;
+  char *fname;
+  char *path;
+  GFile *file;
+  gboolean close_ddisp = TRUE;
 
-  g_return_if_fail(ddisp != NULL);
+  g_return_if_fail (ddisp != NULL);
 
   dia = ddisp->diagram;
 
-  if ( (g_slist_length(dia->displays) > 1) ||
-       (!diagram_is_modified(dia)) ) {
-    ddisp_destroy(ddisp);
+  if ((g_slist_length (dia->displays) > 1) || (!diagram_is_modified (dia))) {
+    ddisp_destroy (ddisp);
     return;
   }
 
-  fname = dia->filename;
-  if (!fname)
-    fname = _("<unnamed>");
-
-  dialog = gtk_message_dialog_new(GTK_WINDOW (ddisp->shell),
-                                  GTK_DIALOG_MODAL,
-                                  GTK_MESSAGE_QUESTION,
-                                  GTK_BUTTONS_NONE, /* no standard buttons */
-                                 _("Closing diagram without saving"));
-  gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(dialog),
-    _("The diagram '%s'\n"
-      "has not been saved. Save changes now?"), fname);
-  gtk_window_set_title (GTK_WINDOW(dialog), _("Close Diagram"));
-
-  button = gtk_button_new_with_mnemonic (_("_Cancel"));
-  gtk_dialog_add_action_widget (GTK_DIALOG(dialog), button, GTK_RESPONSE_CANCEL);
-
-  button = gtk_button_new_with_mnemonic (_("_Discard Changes"));
-  gtk_dialog_add_action_widget (GTK_DIALOG(dialog), button, GTK_RESPONSE_NO);
-
-  /* button = gtk_button_new_with_label (_("Save and Close")); */
-  button = gtk_button_new_with_mnemonic (_("_Save"));
-  gtk_dialog_add_action_widget (GTK_DIALOG(dialog), button, GTK_RESPONSE_YES);
-  gtk_widget_set_can_default (GTK_WIDGET (button), TRUE);
-  gtk_dialog_set_default_response (GTK_DIALOG(dialog), GTK_RESPONSE_YES);
-
-  g_signal_connect (G_OBJECT (dialog), "response",
-                   G_CALLBACK(are_you_sure_close_dialog_respond),
-                   ddisp);
-
-  gtk_widget_show_all(dialog);
+  file = dia_diagram_get_file (dia);
+
+  if (file) {
+    fname = g_file_get_basename (file);
+    path = g_file_get_path (file);
+  } else {
+    fname = g_strdup (_("Unsaved Diagram"));
+    path = NULL;
+  }
+
+  dialog = dia_exit_dialog_new (GTK_WINDOW (ddisp->shell));
+  dia_exit_dialog_add_item (dialog, fname, path, dia);
+
+  res = dia_exit_dialog_run (dialog, &items);
+
+  g_clear_object (&dialog);
+  g_clear_pointer (&items, g_ptr_array_unref);
+
+  g_clear_pointer (&fname, g_free);
+  g_clear_pointer (&path, g_free);
+
+  switch (res) {
+    case DIA_EXIT_DIALOG_SAVE:
+      /* save changes */
+      if (ddisp->diagram->unsaved) {
+        if (file_save_as (ddisp->diagram, ddisp)) {
+          ddisp_destroy (ddisp);
+        }
+        /* no way back */
+        return;
+      } else {
+        DiaContext *ctx = dia_context_new (_("Save"));
+        if (!diagram_save (ddisp->diagram, ddisp->diagram->filename, ctx)) {
+          close_ddisp = FALSE;
+        }
+        dia_context_release (ctx);
+      }
+
+      if (close_ddisp) {
+        /* saving succeeded */
+        recent_file_history_add(ddisp->diagram->filename);
+      }
+
+    case DIA_EXIT_DIALOG_QUIT:
+      if (close_ddisp) {
+        ddisp_destroy (ddisp);
+      }
+    case DIA_EXIT_DIALOG_CANCEL:
+      break;
+    default:
+      g_return_if_reached ();
+  }
 }
 
+
 void
 display_update_menu_state(DDisplay *ddisp)
 {
diff --git a/app/exit_dialog.c b/app/exit_dialog.c
index 16222d8d..7e3b6747 100644
--- a/app/exit_dialog.c
+++ b/app/exit_dialog.c
@@ -19,460 +19,374 @@
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */
 
-/* TODO: Non-modal api */
 
 #include <config.h>
 
+#include <gtk/gtk.h>
+#include <glib.h>
+#include <glib/gi18n-lib.h>
+
 #include "exit_dialog.h"
 
-#include "intl.h"
 
-#include <gtk/gtk.h>
+typedef struct _DiaExitDialogPrivate DiaExitDialogPrivate;
+struct _DiaExitDialogPrivate {
+  GtkWidget    *dialog;
 
-#include <glib.h>
+  GtkWidget    *file_box;
+  GtkWidget    *file_list;
+  GtkListStore *file_store;
+};
 
-#define EXIT_DIALOG_TREEVIEW "EXIT_DIALOG_TREEVIEW"
 
 enum {
-  CHECK_COL,
-  NAME_COL,
-  PATH_COL,
-  DATA_COL, /* To store optional data (not shown in listview) */
+  COL_SAVE,
+  COL_NAME,
+  COL_PATH,
+  COL_DIAGRAM,
   NUM_COL
 };
 
-static void selected_state_set_all   (GtkTreeView * treeview,
-                                      gboolean      state);
-
-static gint get_selected_items (GtkWidget * dialog,
-                                exit_dialog_item_array_t ** items);
 
-/* Event Handlers */
-static void exit_dialog_destroy  (GtkWidget * exit_dialog,
-                                  gpointer    data);
+G_DEFINE_TYPE_WITH_PRIVATE (DiaExitDialog, dia_exit_dialog, G_TYPE_OBJECT)
 
-static void select_all_clicked   (GtkButton * button,
-                                  gpointer    data);
 
-static void select_none_clicked  (GtkButton * button,
-                                  gpointer    data);
-
-static void toggle_check_button  (GtkCellRendererToggle * renderer,
-                                  gchar * path,
-                                  GtkTreeView * treeview);
-
-/* A dialog to allow a user to select which unsaved files to save
- * (if any) or to abort exiting
- *
- * @param parent_window This is needed for modal behavior.
- * @param title Text display on the dialog's title bar.
- * @return The dialog.
- */
-GtkWidget *
-exit_dialog_make (GtkWindow * parent_window,
-                  gchar *     title)
+static void
+dia_exit_dialog_finalize (GObject *object)
 {
-    GtkWidget * dialog = gtk_dialog_new_with_buttons (title, parent_window,
-                                                      GTK_DIALOG_MODAL,
-                                                      _("_Do Not Exit"),
-                                                      EXIT_DIALOG_EXIT_CANCEL,
-                                                      _("_Exit Without Save"),
-                                                      EXIT_DIALOG_EXIT_NO_SAVE,
-                                                      _("_Save Selected"),
-                                                      EXIT_DIALOG_EXIT_SAVE_SELECTED,
-                                                      NULL);
-
-    GtkBox * vbox = GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG(dialog)));
-
-    GtkWidget * label = gtk_label_new (_("The following are not saved:"));
-
-    GtkWidget * scrolled;
-    GtkWidget * button;
-    GtkWidget * hbox;
-
-    GtkWidget *         treeview;
-    GtkListStore *      model;
-    GtkCellRenderer *   renderer;
-    GtkTreeViewColumn * column;
-
-    GdkGeometry geometry = { 0 };
-
-    gtk_box_pack_start (vbox, label, FALSE, FALSE, 0);
-
-    gtk_widget_show (label);
-
-    /* Scrolled window for displaying things which need saving */
-    scrolled = gtk_scrolled_window_new (NULL, NULL);
-    gtk_box_pack_start (vbox, scrolled, TRUE, TRUE, 0);
-    gtk_widget_show (scrolled);
-
-    model = gtk_list_store_new (NUM_COL, G_TYPE_BOOLEAN, G_TYPE_STRING, G_TYPE_STRING, G_TYPE_POINTER);
-
-    treeview = gtk_tree_view_new_with_model ( GTK_TREE_MODEL(model));
-
-    renderer = gtk_cell_renderer_toggle_new ();
-
-    column   = gtk_tree_view_column_new_with_attributes (_("Save"), renderer,
-                                                         "active", CHECK_COL,
-                                                          NULL);
-    gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
-
-    g_signal_connect (G_OBJECT (renderer), "toggled",
-                      G_CALLBACK (toggle_check_button),
-                      treeview);
-
-    renderer = gtk_cell_renderer_text_new ();
-    column   = gtk_tree_view_column_new_with_attributes (_("Name"), renderer,
-                                                         "text", NAME_COL,
-                                                          NULL);
-    gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
-
-    renderer = gtk_cell_renderer_text_new ();
-    column   = gtk_tree_view_column_new_with_attributes (_("Path"), renderer,
-                                                         "text", PATH_COL,
-                                                          NULL);
-    gtk_tree_view_append_column (GTK_TREE_VIEW (treeview), column);
+  DiaExitDialog *self = DIA_EXIT_DIALOG (object);
+  DiaExitDialogPrivate *priv = dia_exit_dialog_get_instance_private (self);
 
+  g_signal_handlers_disconnect_by_data (priv->dialog, self);
+  gtk_widget_destroy (priv->dialog);
 
-    gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled), GTK_WIDGET (treeview));
+  g_clear_object (&priv->file_store);
 
-
-    hbox = gtk_hbox_new (FALSE, 3);
-    gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0);
-    gtk_widget_show (hbox);
-
-    button = gtk_button_new_with_label (_("Select All"));
-    gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 3);
-    gtk_widget_show (button);
-
-    g_signal_connect (G_OBJECT (button), "clicked",
-                      G_CALLBACK (select_all_clicked),
-                      treeview);
-
-    button = gtk_button_new_with_label (_("Select None"));
-    gtk_box_pack_start (GTK_BOX (hbox), button, FALSE, FALSE, 3);
-    gtk_widget_show (button);
-
-    g_signal_connect (G_OBJECT (button), "clicked",
-                      G_CALLBACK (select_none_clicked),
-                      treeview);
-
-    g_clear_object (&model);
-    gtk_widget_show (GTK_WIDGET (treeview));
-
-    gtk_widget_show_all (GTK_WIDGET(vbox));
-
-    g_object_set_data (G_OBJECT (dialog), EXIT_DIALOG_TREEVIEW,  treeview);
-
-    g_signal_connect (G_OBJECT (dialog), "destroy",
-                      G_CALLBACK (exit_dialog_destroy),
-                      treeview);
-
-    /* golden ratio */
-    geometry.min_aspect = 0.618;
-    geometry.max_aspect = 1.618;
-    geometry.win_gravity = GDK_GRAVITY_CENTER;
-    gtk_window_set_geometry_hints (GTK_WINDOW (dialog), GTK_WIDGET (vbox), &geometry,
-                                   GDK_HINT_ASPECT|GDK_HINT_WIN_GRAVITY);
-    return dialog;
+  G_OBJECT_CLASS (dia_exit_dialog_parent_class)->finalize (object);
 }
 
-/**
- * Add name and path of a file that needs to be saved
- * @param dialog Exit dialog created with exit_dialog_make()
- * @param name User identifiable name of the thing which needs saving.
- * @param path File system path of the thing which needs saving.
- * @param optional_data Optional data to be returned with selected file info.
- */
-void
-exit_dialog_add_item (GtkWidget *    dialog,
-                      const gchar *  name,
-                      const gchar *  path,
-                      const gpointer optional_data)
+
+static void
+dia_exit_dialog_class_init (DiaExitDialogClass *klass)
 {
-    GtkTreeView *  treeview;
-    GtkTreeIter    iter;
-    GtkListStore * model;
-    const gchar *  name_copy = g_strdup (name);
-    const gchar *  path_copy = g_strdup (path);
+  GObjectClass *object_class = G_OBJECT_CLASS (klass);
 
-    treeview = g_object_get_data (G_OBJECT (dialog), EXIT_DIALOG_TREEVIEW);
+  object_class->finalize = dia_exit_dialog_finalize;
+}
 
-    model = GTK_LIST_STORE (gtk_tree_view_get_model (treeview));
 
-    gtk_list_store_append (model, &iter);
+static void
+clear_item (gpointer data)
+{
+  DiaExitDialogItem *item = data;
 
-    gtk_list_store_set (model, &iter,
-                        CHECK_COL, 1,
-                        NAME_COL, name_copy,
-                        PATH_COL, path_copy,
-                        DATA_COL, optional_data,
-                        -1);
-}
+  g_clear_pointer (&item->name, g_free);
+  g_clear_pointer (&item->path, g_free);
+  g_clear_object (&item->data);
 
-gint
-exit_dialog_run (GtkWidget * dialog,
-                 exit_dialog_item_array_t ** items)
-{
-    gint                       result;
-    gint                       count;
-
-    while (TRUE)
-    {
-        result = gtk_dialog_run ( GTK_DIALOG(dialog));
-
-        switch (result)
-        {
-            case EXIT_DIALOG_EXIT_CANCEL:
-            case EXIT_DIALOG_EXIT_NO_SAVE:
-                *items = NULL;
-                return result;
-           case EXIT_DIALOG_EXIT_SAVE_SELECTED :
-               break;
-           default : /* e.g. if closed by window manager button */
-               return EXIT_DIALOG_EXIT_CANCEL;
-        }
-
-        count = get_selected_items (dialog, items);
-
-        if (count == 0)
-        {
-           GtkWidget * msg_dialog = gtk_message_dialog_new (GTK_WINDOW (dialog),
-                                                            GTK_DIALOG_MODAL,
-                                                            GTK_MESSAGE_WARNING,
-                                                            GTK_BUTTONS_YES_NO,
-                                                            _("Nothing selected for saving.  Would you like 
to try again?"));
-
-           gint yes_or_no = gtk_dialog_run (GTK_DIALOG (msg_dialog));
-
-           gtk_widget_destroy (msg_dialog);
-
-           if (yes_or_no == GTK_RESPONSE_NO)
-           {
-               return EXIT_DIALOG_EXIT_NO_SAVE;
-           }
-        }
-        else
-        {
-            return EXIT_DIALOG_EXIT_SAVE_SELECTED;
-        }
-    }
+  g_free (data);
 }
 
 
 /**
  * Gets the list of items selected for saving by the user.
- * @param dialog Exit dialog.
- * @param items Structure to hold the selected items.  Set to NULL if not items selected.
- * @return The number of selected items.
+ * @self: the #DiaExitDialog
+ *
+ * Returns: The selected items.
+ *
+ * Since: 0.98
  */
-int
-get_selected_items (GtkWidget                 *dialog,
-                    exit_dialog_item_array_t **items)
+static GPtrArray *
+get_selected_items (DiaExitDialog *self)
 {
-  GtkTreeView  *treeview;
-  GtkTreeIter   iter;
-  GtkListStore *model;
-  gboolean      valid;
-  GSList       *list = NULL;
-  GSList       *list_iter;
-  int           selected_count;
-  int           i;
-
-  treeview = g_object_get_data (G_OBJECT (dialog), EXIT_DIALOG_TREEVIEW);
+  DiaExitDialogPrivate *priv = dia_exit_dialog_get_instance_private (self);
+  GtkTreeIter           iter;
+  gboolean              valid;
+  GPtrArray            *selected;
 
-  model = GTK_LIST_STORE (gtk_tree_view_get_model (treeview));
+  selected = g_ptr_array_new_with_free_func (clear_item);
 
   /* Get the first iter in the list */
-  valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter);
+  valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (priv->file_store),
+                                         &iter);
 
   /* Get the selected items */
   while (valid) {
-    char     *name;
-    char     *path;
-    gpointer  data;
-    gboolean  is_selected;
-
-    gtk_tree_model_get (GTK_TREE_MODEL (model), &iter,
-                        CHECK_COL, &is_selected,
-                        NAME_COL,  &name,
-                        PATH_COL,  &path,
-                        DATA_COL,  &data,
+    DiaExitDialogItem *item = g_new (DiaExitDialogItem, 1);
+    gboolean           is_selected;
+
+    gtk_tree_model_get (GTK_TREE_MODEL (priv->file_store), &iter,
+                        COL_SAVE,    &is_selected,
+                        COL_NAME,    &item->name,
+                        COL_PATH,    &item->path,
+                        COL_DIAGRAM, &item->data,
                         -1);
 
     if (is_selected) {
-      exit_dialog_item_t *item = g_new (exit_dialog_item_t, 1);
-      item->name = name;
-      item->path = path;
-      item->data = data;
-      list = g_slist_prepend (list, item);
+      g_ptr_array_add (selected, item);
+    } else {
+      clear_item (item);
     }
 
-    valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (model), &iter);
+    valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (priv->file_store),
+                                      &iter);
   }
 
-    selected_count = g_slist_length (list);
-
-    if (selected_count > 0)
-    {
-        *items              = g_new (exit_dialog_item_array_t,1);
-       (*items)->array_size = selected_count;
-       (*items)->array      = g_new (exit_dialog_item_t, (*items)->array_size);
-
-        list_iter = list;
-        for(i = 0 ; i < selected_count ; i++)
-        {
-           exit_dialog_item_t * item;
-            g_assert(list_iter!=NULL); /* can't be if g_slist_length works */
-            item = list_iter->data;
-            (*items)->array[i].name = item->name;
-            (*items)->array[i].path = item->path;
-            (*items)->array[i].data = item->data;
-            list_iter = g_slist_next(list_iter);
-        }
-
-        g_slist_free (list);
-    }
-    else
-    {
-      *items = NULL;
-    }
-
-    return selected_count;
+  return selected;
 }
 
 
-/**
- * Free memory allocated for the exit_dialog_item_array_t.  This
- * will not free any memory not allocated by the dialog itself
- * @param items Item array struct returned by exit_dialog_run()
- */
-void
-exit_dialog_free_items (exit_dialog_item_array_t * items)
+static void
+save_sensitive (DiaExitDialog *self)
 {
-  if (items) {
-    for (int i = 0 ; i < items->array_size ; i++) {
-      /* Cast is needed to remove warning because of const decl */
-      g_clear_pointer (&items->array[i].name, g_free);
-      g_clear_pointer (&items->array[i].path, g_free);
-    }
+  DiaExitDialogPrivate *priv = dia_exit_dialog_get_instance_private (self);
+  GPtrArray *selected = get_selected_items (self);
 
-    g_clear_pointer (&items, g_free);
-  }
+  gtk_dialog_set_response_sensitive (GTK_DIALOG (priv->dialog),
+                                     DIA_EXIT_DIALOG_SAVE,
+                                     selected->len > 0);
+
+  g_clear_pointer (&selected, g_ptr_array_unref);
 }
 
 
-/**
+/*
  * Handler for the check box (button) in the exit dialogs treeview.
  * This is needed to cause the check box to change state when the
  * user clicks it
  */
-static void toggle_check_button (GtkCellRendererToggle * renderer,
-                                 gchar * path,
-                                 GtkTreeView * treeview)
+static void
+toggle_check_button (GtkCellRendererToggle *renderer,
+                     char                  *path,
+                     DiaExitDialog         *self)
 {
-    GtkTreeModel * model;
-    GtkTreeIter    iter;
-    gboolean       value;
-
-    model = gtk_tree_view_get_model (treeview);
-    if (gtk_tree_model_get_iter_from_string (model, &iter, path))
-    {
-        gtk_tree_model_get (model, &iter, CHECK_COL, &value, -1);
-        gtk_list_store_set (GTK_LIST_STORE (model), &iter, CHECK_COL, !value, -1);
-    }
+  DiaExitDialogPrivate *priv = dia_exit_dialog_get_instance_private (self);
+  GtkTreeIter           iter;
+  gboolean              value;
+
+  if (gtk_tree_model_get_iter_from_string (GTK_TREE_MODEL (priv->file_store),
+                                           &iter,
+                                           path)) {
+    gtk_tree_model_get (GTK_TREE_MODEL (priv->file_store), &iter,
+                        COL_SAVE, &value,
+                        -1);
+    gtk_list_store_set (priv->file_store, &iter, COL_SAVE, !value, -1);
+
+    save_sensitive (self);
+  }
 }
 
 
-/*
- * Signal handler for "destroy" event to free memory allocated for the exit_dialog.
- *
- * @param exit_dialog
- * @param data Exit dialog's treeview
- */
 static void
-exit_dialog_destroy (GtkWidget *exit_dialog,
-                     gpointer   data)
+dia_exit_dialog_init (DiaExitDialog *self)
 {
-  GtkTreeView  *treeview;
-  GtkTreeIter   iter;
-  GtkTreeModel *model;
-  gboolean      valid;
-
-  treeview = g_object_get_data (G_OBJECT (exit_dialog), EXIT_DIALOG_TREEVIEW);
-
-  model = GTK_TREE_MODEL (gtk_tree_view_get_model (treeview));
-
-  /* Get the first iter in the list */
-  valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter);
-
-  while (valid) {
-    char *name = NULL;
-    char *path = NULL;
-
-    gtk_tree_model_get (model, &iter,
-                        NAME_COL, &name,
-                        PATH_COL, &path,
-                        -1);
-
-    g_clear_pointer (&name, g_free);
-    g_clear_pointer (&path, g_free);
-
-    valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (model), &iter);
-  }
+  DiaExitDialogPrivate *priv = dia_exit_dialog_get_instance_private (self);
+
+  GtkWidget *label;
+
+  GtkWidget *scrolled;
+  GtkWidget *button;
+
+  GtkCellRenderer   *renderer;
+  GtkTreeViewColumn *column;
+
+  priv->dialog = gtk_message_dialog_new (NULL,
+                                         GTK_DIALOG_MODAL,
+                                         GTK_MESSAGE_QUESTION,
+                                         GTK_BUTTONS_NONE,
+                                         NULL);
+
+  gtk_message_dialog_format_secondary_text (GTK_MESSAGE_DIALOG (priv->dialog),
+                                            _("If you don’t save, all your changes will be permanently 
lost."));
+
+  button = gtk_dialog_add_button (GTK_DIALOG (priv->dialog),
+                                  _("Close _without Saving"),
+                                  DIA_EXIT_DIALOG_QUIT);
+
+  /* "use" it for gtk2 */
+  gtk_widget_get_name (button);
+  /*
+  GTK3:
+  gtk_style_context_add_class (gtk_widget_get_style_context (button),
+                               "destructive-action");
+  */
+
+  gtk_dialog_add_button (GTK_DIALOG (priv->dialog),
+                         _("_Cancel"),
+                         DIA_EXIT_DIALOG_CANCEL);
+
+  gtk_dialog_add_button (GTK_DIALOG (priv->dialog),
+                         _("_Save"),
+                         DIA_EXIT_DIALOG_SAVE);
+
+  gtk_dialog_set_default_response (GTK_DIALOG (priv->dialog),
+                                   DIA_EXIT_DIALOG_SAVE);
+
+
+  priv->file_box = gtk_vbox_new (FALSE, 6);
+  gtk_box_pack_start (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (priv->dialog))),
+                      priv->file_box,
+                      FALSE,
+                      FALSE,
+                      0);
+  gtk_widget_show (priv->file_box);
+
+
+  /* Scrolled window for displaying things which need saving */
+  scrolled = gtk_scrolled_window_new (NULL, NULL);
+  gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled),
+                                       GTK_SHADOW_IN);
+  /* gtk_scrolled_window_set_min_content_height (GTK_SCROLLED_WINDOW (scrolled),
+                                              90); */
+  g_object_set (scrolled, "height-request", 90, NULL);
+  gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled),
+                                  GTK_POLICY_NEVER,
+                                  GTK_POLICY_AUTOMATIC);
+  gtk_widget_show (scrolled);
+
+  priv->file_store = gtk_list_store_new (NUM_COL,
+                                         G_TYPE_BOOLEAN,
+                                         G_TYPE_STRING,
+                                         G_TYPE_STRING,
+                                         DIA_TYPE_DIAGRAM);
+
+  priv->file_list = gtk_tree_view_new_with_model (GTK_TREE_MODEL (priv->file_store));
+  gtk_tree_view_set_tooltip_column (GTK_TREE_VIEW (priv->file_list),
+                                    COL_PATH);
+  gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (priv->file_list), FALSE);
+  gtk_widget_show (priv->file_list);
+
+  renderer = gtk_cell_renderer_toggle_new ();
+  gtk_cell_renderer_toggle_set_activatable (GTK_CELL_RENDERER_TOGGLE (renderer),
+                                            TRUE);
+  column = gtk_tree_view_column_new_with_attributes (_("Save"), renderer,
+                                                     "active", COL_SAVE,
+                                                     NULL);
+  gtk_tree_view_append_column (GTK_TREE_VIEW (priv->file_list), column);
+
+  g_signal_connect (G_OBJECT (renderer), "toggled",
+                    G_CALLBACK (toggle_check_button),
+                    self);
+
+  renderer = gtk_cell_renderer_text_new ();
+  column = gtk_tree_view_column_new_with_attributes (_("Name"), renderer,
+                                                     "text", COL_NAME,
+                                                     NULL);
+  gtk_tree_view_append_column (GTK_TREE_VIEW (priv->file_list), column);
+
+
+  gtk_scrolled_window_add_with_viewport (GTK_SCROLLED_WINDOW (scrolled),
+                                         priv->file_list);
+
+  label = g_object_new (GTK_TYPE_LABEL,
+                        "label", _("S_elect the diagrams you want to save:"),
+                        "use-underline", TRUE,
+                        "mnemonic-widget", priv->file_list,
+                        "xalign", 0.0,
+                        "visible", TRUE,
+                        NULL);
+
+
+  gtk_box_pack_start (GTK_BOX (priv->file_box), label, FALSE, FALSE, 0);
+  gtk_box_pack_start (GTK_BOX (priv->file_box), scrolled, FALSE, FALSE, 0);
+
+  g_signal_connect_swapped (G_OBJECT (priv->dialog), "destroy",
+                            G_CALLBACK (g_object_unref),
+                            self);
 }
 
 
 /**
- * Sets the state of the save checkbox in the treeview to the
- * state specified by the caller.
- * @param treeview The treeview in the exit dialog box.
- * @param state Set to TRUE to select all, FALSE to de-select all.
+ * dia_exit_dialog_new:
+ * @parent: This is needed for modal behavior.
+ *
+ * A dialog to allow a user to select which unsaved files to save
+ * (if any) or to abort exiting
+ *
+ * Since: 0.98
  */
-static void selected_state_set_all (GtkTreeView * treeview,
-                                    gboolean      state)
+DiaExitDialog *
+dia_exit_dialog_new (GtkWindow *parent)
 {
-    GtkTreeIter    iter;
-    GtkListStore * model;
-    gboolean       valid;
+  DiaExitDialog *self = g_object_new (DIA_TYPE_EXIT_DIALOG, NULL);
+  DiaExitDialogPrivate *priv = dia_exit_dialog_get_instance_private (self);
 
-    model = GTK_LIST_STORE (gtk_tree_view_get_model (treeview));
+  gtk_window_set_transient_for (GTK_WINDOW (priv->dialog), parent);
 
-    /* Get the first iter in the list */
-    valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (model), &iter);
-
-    while (valid)
-    {
-        gtk_list_store_set (model, &iter,
-                            CHECK_COL, state,
-                            -1);
-
-        valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (model), &iter);
-    }
+  return self;
 }
 
+
 /**
- * Handler for Select All button.  Causes all checkboxes in the
- * exit dialog box treeview to be checked.
- * @param button The Select all button.
- * @param data The treeview
+ * dia_exit_dialog_add_item:
+ * @self: the #DiaExitDialog
+ * @name: User identifiable name of the thing which needs saving.
+ * @path: File system path of the thing which needs saving.
+ * @diagram: The unsaved #Diagram
+ *
+ * Add name and path of a file that needs to be saved
+ *
+ * Since: 0.98
  */
-static void select_all_clicked   (GtkButton * button,
-                                  gpointer    data)
+void
+dia_exit_dialog_add_item (DiaExitDialog *self,
+                          const char    *name,
+                          const char    *filepath,
+                          Diagram       *diagram)
 {
-    selected_state_set_all (data, TRUE);
+  DiaExitDialogPrivate *priv = dia_exit_dialog_get_instance_private (self);
+  GtkTreeIter   iter;
+  char *title = NULL;
+  int n;
+
+  gtk_list_store_append (priv->file_store, &iter);
+  gtk_list_store_set (priv->file_store, &iter,
+                      COL_SAVE, 1,
+                      COL_NAME, name,
+                      COL_PATH, filepath,
+                      COL_DIAGRAM, diagram,
+                      -1);
+
+  save_sensitive (self);
+
+  n = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (priv->file_store), NULL);
+
+  if (n == 1) {
+    title = g_markup_printf_escaped ("Save changes to diagram “%s” before closing?",
+                                     name);
+    gtk_widget_hide (priv->file_box);
+  } else {
+    title = g_markup_printf_escaped (ngettext ("There is %d diagram with unsaved changes. "
+                                              "Save changes before closing?",
+                                              "There are %d diagrams with unsaved changes. "
+                                              "Save changes before closing?",
+                                              n), n);
+    gtk_widget_show (priv->file_box);
+  }
+
+  g_object_set (priv->dialog, "text", title, NULL);
+
+  g_clear_pointer (&title, g_free);
 }
 
-/**
- * Handler for Select All button.  Causes all checkboxes in the
- * exit dialog box treeview to be un-checked.
- * @param button The Select all button.
- * @param data The treeview
- */
-static void select_none_clicked  (GtkButton * button,
-                                  gpointer    data)
+
+DiaExitDialogResult
+dia_exit_dialog_run (DiaExitDialog  *self,
+                     GPtrArray     **items)
 {
-    selected_state_set_all (data, FALSE);
-}
+  DiaExitDialogPrivate *priv = dia_exit_dialog_get_instance_private (self);
+  int                   result;
+
+  result = gtk_dialog_run (GTK_DIALOG (priv->dialog));
 
+  *items = NULL;
 
+  if (result == DIA_EXIT_DIALOG_SAVE) {
+    *items = get_selected_items (self);
+  } else if (result != DIA_EXIT_DIALOG_QUIT &&
+             result != DIA_EXIT_DIALOG_CANCEL) {
+    result = DIA_EXIT_DIALOG_CANCEL;
+  }
+
+  return result;
+}
diff --git a/app/exit_dialog.h b/app/exit_dialog.h
index 88e489e6..3307f638 100644
--- a/app/exit_dialog.h
+++ b/app/exit_dialog.h
@@ -19,51 +19,55 @@
  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  */
 
-#ifndef EXIT_DIALOG_H
-#define EXIT_DIALOG_H
+#pragma once
 
 #include <gtk/gtk.h>
 
-G_BEGIN_DECLS
+#include "diagram.h"
 
-enum {
-  EXIT_DIALOG_EXIT_NO_SAVE,
-  EXIT_DIALOG_EXIT_SAVE_SELECTED,
-  EXIT_DIALOG_EXIT_CANCEL
-};
 
-GtkWidget *
-exit_dialog_make (GtkWindow * parent_window,
-                  gchar *     title);
+G_BEGIN_DECLS
 
-void
-exit_dialog_add_item (GtkWidget      *dialog,
-                      const char     *name,
-                      const char     *filepath,
-                      const gpointer  optional_data);
+
+/**
+ * DiaExitDialogResult:
+ * @DIA_EXIT_DIALOG_SAVE: The selectd diagrams should be saved, then quit
+ * @DIA_EXIT_DIALOG_CANCEL: Don't quit, don't save
+ * @DIA_EXIT_DIALOG_QUIT: Close anyway, loosing changes
+ *
+ * Since: 0.98
+ */
+typedef enum /*< enum,prefix=DIA >*/
+{
+  DIA_EXIT_DIALOG_SAVE,   /*< nick=save >*/
+  DIA_EXIT_DIALOG_CANCEL, /*< nick=cancel >*/
+  DIA_EXIT_DIALOG_QUIT,   /*< nick=quit >*/
+} DiaExitDialogResult;
 
 
 typedef struct {
-  char     *name;
-  char     *path;
-  gpointer  data;
-} exit_dialog_item_t;
+  char    *name;
+  char    *path;
+  Diagram *data;
+} DiaExitDialogItem;
 
 
-typedef struct
-{
-  size_t               array_size;
-  exit_dialog_item_t * array;
+struct _DiaExitDialog {
+  GObject parent;
+};
 
-} exit_dialog_item_array_t;
 
-gint
-exit_dialog_run (GtkWidget * dialog,
-                 exit_dialog_item_array_t ** items_to_save);
+#define DIA_TYPE_EXIT_DIALOG dia_exit_dialog_get_type ()
+G_DECLARE_FINAL_TYPE (DiaExitDialog, dia_exit_dialog, DIA, EXIT_DIALOG, GObject)
 
-void exit_dialog_free_items (exit_dialog_item_array_t *);
 
-G_END_DECLS
+DiaExitDialog       *dia_exit_dialog_new      (GtkWindow      *parent);
+void                 dia_exit_dialog_add_item (DiaExitDialog  *self,
+                                               const char     *name,
+                                               const char     *filepath,
+                                               Diagram        *diagram);
+DiaExitDialogResult  dia_exit_dialog_run      (DiaExitDialog  *self,
+                                               GPtrArray     **items);
 
-#endif
 
+G_END_DECLS
diff --git a/app/meson.build b/app/meson.build
index 565335d3..7b3b63a0 100644
--- a/app/meson.build
+++ b/app/meson.build
@@ -79,6 +79,8 @@ dia_sources = [
     'textedit_tool.c',
 ]
 
+sources += gnome.mkenums_simple('dia-app-enums', sources: 'exit_dialog.h')
+
 #TODO: this is workaround for openbsd where libraries are in /usr/lib/local
 # Potential parent issues: https://github.com/mesonbuild/meson/issues/3570
 # https://github.com/mesonbuild/meson/issues/4468



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]