From 30c4dd918ad3fd79065f5253e012b39e5fe942c8 Mon Sep 17 00:00:00 2001 From: Robert Roebling Date: Fri, 24 Nov 2006 10:50:20 +0000 Subject: [PATCH] Commit FM's GTK+ native assert dialog code. git-svn-id: https://svn.wxwidgets.org/svn/wx/wxWidgets/trunk@43626 c3d73ce0-8a6f-49c7-b76d-6d57e0e08775 --- include/wx/gtk/assertdlg_gtk.h | 90 ++++++ src/gtk/assertdlg_gtk.c | 498 +++++++++++++++++++++++++++++++++ 2 files changed, 588 insertions(+) create mode 100644 include/wx/gtk/assertdlg_gtk.h create mode 100644 src/gtk/assertdlg_gtk.c diff --git a/include/wx/gtk/assertdlg_gtk.h b/include/wx/gtk/assertdlg_gtk.h new file mode 100644 index 0000000000..228cf02433 --- /dev/null +++ b/include/wx/gtk/assertdlg_gtk.h @@ -0,0 +1,90 @@ +/* /////////////////////////////////////////////////////////////////////////// +// Name: assertdlg_gtk.h +// Purpose: GtkAssertDialog +// Author: Francesco Montorsi +// Id: $Id$ +// Copyright: (c) 2006 Francesco Montorsi +// Licence: wxWindows licence +/////////////////////////////////////////////////////////////////////////// */ + +#ifndef __GTK_ASSERTDLG_H__ +#define __GTK_ASSERTDLG_H__ + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include +#include + +#define GTK_TYPE_ASSERT_DIALOG (gtk_assert_dialog_get_type ()) +#define GTK_ASSERT_DIALOG(object) (G_TYPE_CHECK_INSTANCE_CAST ((object), GTK_TYPE_ASSERT_DIALOG, GtkAssertDialog)) +#define GTK_ASSERT_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_CAST ((klass), GTK_TYPE_ASSERT_DIALOG, GtkAssertDialogClass)) +#define GTK_IS_ASSERT_DIALOG(object) (G_TYPE_CHECK_INSTANCE_TYPE ((object), GTK_TYPE_ASSERT_DIALOG)) +#define GTK_IS_ASSERT_DIALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass), GTK_TYPE_ASSERT_DIALOG)) +#define GTK_ASSERT_DIALOG_GET_CLASS(obj) (G_TYPE_INSTANCE_GET_CLASS ((obj), GTK_TYPE_ASSERT_DIALOG, GtkAssertDialogClass)) + +typedef struct _GtkAssertDialog GtkAssertDialog; +typedef struct _GtkAssertDialogClass GtkAssertDialogClass; +typedef void (*GtkAssertDialogStackFrameCallback)(void *); + +struct _GtkAssertDialog +{ + GtkDialog parent_instance; + + /* GtkAssertDialog widgets */ + GtkWidget *expander; + GtkWidget *message; + GtkWidget *treeview; + + /* callback for processing the stack frame */ + GtkAssertDialogStackFrameCallback callback; + void *userdata; +}; + +struct _GtkAssertDialogClass +{ + GtkDialogClass parent_class; +}; + +typedef enum +{ + GTK_ASSERT_DIALOG_STOP, + GTK_ASSERT_DIALOG_CONTINUE, + GTK_ASSERT_DIALOG_CONTINUE_SUPPRESSING +} GtkAssertDialogResponseID; + + + + +GType gtk_assert_dialog_get_type(); +GtkWidget *gtk_assert_dialog_new(); + +/* get the assert message */ +gchar *gtk_assert_dialog_get_message(GtkAssertDialog *assertdlg); + +/* set the assert message */ +void gtk_assert_dialog_set_message(GtkAssertDialog *assertdlg, const gchar *msg); + +/* get a string containing all stack frames appended to the dialog */ +gchar *gtk_assert_dialog_get_backtrace(GtkAssertDialog *assertdlg); + +/* sets the callback to use when the user wants to see the stackframe */ +void gtk_assert_dialog_set_backtrace_callback(GtkAssertDialog *assertdlg, + GtkAssertDialogStackFrameCallback callback, + void *userdata); + +/* appends a stack frame to the dialog */ +void gtk_assert_dialog_append_stack_frame(GtkAssertDialog *dlg, + const gchar *function, + const gchar *arguments, + const gchar *sourcefile, + guint line_number); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* __GTK_ASSERTDLG_H__ */ + + diff --git a/src/gtk/assertdlg_gtk.c b/src/gtk/assertdlg_gtk.c new file mode 100644 index 0000000000..b5a877aab2 --- /dev/null +++ b/src/gtk/assertdlg_gtk.c @@ -0,0 +1,498 @@ +/* /////////////////////////////////////////////////////////////////////////// +// Name: assertdlg_gtk.c +// Purpose: GtkAssertDialog +// Author: Francesco Montorsi +// Id: $Id$ +// Copyright: (c) 2006 Francesco Montorsi +// Licence: wxWindows licence +/////////////////////////////////////////////////////////////////////////// */ + +#ifdef VMS +#define XCheckIfEvent XCHECKIFEVENT +#endif + +#include "wx/platform.h" +#include "wx/gtk/assertdlg_gtk.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +#include + + +#if GTK_CHECK_VERSION(2,4,0) +#include +#endif + + +/* ---------------------------------------------------------------------------- + Constants + ---------------------------------------------------------------------------- */ + +// NB: when changing order of the columns also update the gtk_list_store_new() call +// in gtk_assert_dialog_create_backtrace_list_model() function +#define STACKFRAME_LEVEL_COLIDX 0 +#define FUNCTION_NAME_COLIDX 1 +#define SOURCE_FILE_COLIDX 2 +#define LINE_NUMBER_COLIDX 3 +#define FUNCTION_ARGS_COLIDX 4 + + + + +/* ---------------------------------------------------------------------------- + GtkAssertDialog helpers + ---------------------------------------------------------------------------- */ + +GtkWidget *gtk_assert_dialog_add_button_to (GtkBox *box, const gchar *label, + const gchar *stock, gint response_id) +{ + /* create the button */ + GtkWidget *button = gtk_button_new_with_mnemonic (label); + GTK_WIDGET_SET_FLAGS (button, GTK_CAN_DEFAULT); + +#if GTK_CHECK_VERSION(2,6,0) + if (!gtk_check_version (2, 6, 0)) + { + /* add a stock icon inside it */ + GtkWidget *image = gtk_image_new_from_stock (stock, GTK_ICON_SIZE_BUTTON); + gtk_button_set_image (GTK_BUTTON (button), image); + } +#endif + + /* add to the given (container) widget */ + if (box) + gtk_box_pack_end (box, button, FALSE, TRUE, 8); + + return button; +} + +void gtk_assert_dialog_add_button (GtkAssertDialog *dlg, const gchar *label, + const gchar *stock, gint response_id) +{ + /* create the button */ + GtkWidget *button = gtk_assert_dialog_add_button_to (NULL, label, stock, response_id); + + /* add the button to the dialog's action area */ + gtk_dialog_add_action_widget (GTK_DIALOG (dlg), button, response_id); +} + +void gtk_assert_dialog_append_text_column (GtkWidget *treeview, const gchar *name, int index) +{ + GtkCellRenderer *renderer; + GtkTreeViewColumn *column; + + renderer = gtk_cell_renderer_text_new (); + column = gtk_tree_view_column_new_with_attributes (name, renderer, + "text", index, NULL); + gtk_tree_view_insert_column (GTK_TREE_VIEW (treeview), column, index); + gtk_tree_view_column_set_resizable (column, TRUE); + gtk_tree_view_column_set_reorderable (column, TRUE); +} + +GtkWidget *gtk_assert_dialog_create_backtrace_list_model () +{ + GtkListStore *store; + GtkWidget *treeview; + + /* create list store */ + store = gtk_list_store_new (5, + G_TYPE_UINT, // stack frame number + G_TYPE_STRING, // function name + G_TYPE_STRING, // source file name + G_TYPE_UINT, // line number + G_TYPE_STRING); // function arguments + + /* create the tree view */ + treeview = gtk_tree_view_new_with_model (GTK_TREE_MODEL(store)); + g_object_unref (store); + gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (treeview), TRUE); + + // append columns + gtk_assert_dialog_append_text_column(treeview, "#", STACKFRAME_LEVEL_COLIDX); + gtk_assert_dialog_append_text_column(treeview, "Function name", FUNCTION_NAME_COLIDX); + gtk_assert_dialog_append_text_column(treeview, "Function args", FUNCTION_ARGS_COLIDX); + gtk_assert_dialog_append_text_column(treeview, "Source file", SOURCE_FILE_COLIDX); + gtk_assert_dialog_append_text_column(treeview, "Line #", LINE_NUMBER_COLIDX); + + return treeview; +} + +void gtk_assert_dialog_process_backtrace (GtkAssertDialog *dlg) +{ + /* set busy cursor */ + GdkWindow *parent = GTK_WIDGET(dlg)->window; + GdkCursor *cur = gdk_cursor_new (GDK_WATCH); + gdk_window_set_cursor (parent, cur); + gdk_flush (); + + (*dlg->callback)(dlg->userdata); + + /* toggle busy cursor */ + gdk_window_set_cursor (parent, NULL); + gdk_cursor_unref (cur); +} + + + +/* ---------------------------------------------------------------------------- + GtkAssertDialog signal handlers + ---------------------------------------------------------------------------- */ + +#if GTK_CHECK_VERSION(2,4,0) // GtkFileChooserDialog and GtkExpander + // are only available in GTK+ >= 2.4 + +void gtk_assert_dialog_expander_callback (GtkWidget *widget, GtkAssertDialog *dlg) +{ + // for some reason we need to invert the return value of gtk_expander_get_expanded + // to get the real expanded status + gboolean expanded = !gtk_expander_get_expanded (GTK_EXPANDER(dlg->expander)); + gtk_window_set_resizable (GTK_WINDOW (dlg), expanded); + + if (dlg->callback == NULL) /* was the backtrace already processed? */ + return; + + gtk_assert_dialog_process_backtrace (dlg); + + /* mark the work as done (so that next activate we won't call the callback again) */ + dlg->callback = NULL; +} + +void gtk_assert_dialog_save_backtrace_callback (GtkWidget *widget, GtkAssertDialog *dlg) +{ + GtkWidget *dialog; + + dialog = gtk_file_chooser_dialog_new ("Save assert info to file", GTK_WINDOW(dlg), + GTK_FILE_CHOOSER_ACTION_SAVE, + GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL, + GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT, + NULL); + + if (gtk_dialog_run (GTK_DIALOG (dialog)) == GTK_RESPONSE_ACCEPT) + { + char *filename, *msg, *backtrace; + FILE *fp; + + filename = gtk_file_chooser_get_filename (GTK_FILE_CHOOSER (dialog)); + msg = gtk_assert_dialog_get_message (dlg); + backtrace = gtk_assert_dialog_get_backtrace (dlg); + + /* open the file and write all info inside it */ + fp = fopen (filename, "w"); + if (fp && filename) + fprintf (fp, "ASSERT INFO:\n%s\n\nBACKTRACE:\n%s", msg, backtrace); + + g_free (filename); + g_free (msg); + g_free (backtrace); + fclose (fp); + } + + gtk_widget_destroy (dialog); +} +#endif + +void gtk_assert_dialog_copy_callback (GtkWidget *widget, GtkAssertDialog *dlg) +{ + char *msg, *backtrace; + GtkClipboard *clipboard; + GString *str; + + msg = gtk_assert_dialog_get_message (dlg); + backtrace = gtk_assert_dialog_get_backtrace (dlg); + + /* combine both in a single string */ + str = g_string_new(""); + g_string_printf (str, "ASSERT INFO:\n%s\n\nBACKTRACE:\n%s\n\n", msg, backtrace); + + /* copy everything in default clipboard */ + clipboard = gtk_clipboard_get (GDK_SELECTION_CLIPBOARD); + gtk_clipboard_set_text (clipboard, str->str, str->len); + + /* copy everything in primary clipboard too */ + clipboard = gtk_clipboard_get (GDK_SELECTION_PRIMARY); + gtk_clipboard_set_text (clipboard, str->str, str->len); + + g_free (msg); + g_free (backtrace); + g_string_free (str, TRUE); +} + + +/* ---------------------------------------------------------------------------- + GtkAssertDialogClass implementation + ---------------------------------------------------------------------------- */ + +static void gtk_assert_dialog_init (GtkAssertDialog *self); +static void gtk_assert_dialog_class_init (GtkAssertDialogClass *klass); + + +GtkType gtk_assert_dialog_get_type (void) +{ + static GtkType assert_dialog_type = 0; + + if (!assert_dialog_type) + { + static const GTypeInfo assert_dialog_info = + { + sizeof (GtkAssertDialogClass), + NULL, /* base_init */ + NULL, /* base_finalize */ + (GClassInitFunc) gtk_assert_dialog_class_init, + NULL, /* class_finalize */ + NULL, /* class_data */ + sizeof (GtkAssertDialog), + 16, /* n_preallocs */ + (GInstanceInitFunc) gtk_assert_dialog_init, + NULL + }; + assert_dialog_type = g_type_register_static (GTK_TYPE_DIALOG, "GtkAssertDialog", &assert_dialog_info, (GTypeFlags)0); + } + + return assert_dialog_type; +} + +void gtk_assert_dialog_class_init(GtkAssertDialogClass *klass) +{ + /* no special initializations required */ +} + +void gtk_assert_dialog_init(GtkAssertDialog *dlg) +{ + GtkWidget *vbox, *hbox, *image; + + /* start the main vbox */ + gtk_widget_push_composite_child (); + vbox = gtk_vbox_new (FALSE, 8); + gtk_container_set_border_width (GTK_CONTAINER(vbox), 8); + gtk_box_pack_start (GTK_BOX (GTK_DIALOG (dlg)->vbox), vbox, TRUE, TRUE, 5); + + + /* add the icon+message hbox */ + hbox = gtk_hbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + + /* icon */ + image = gtk_image_new_from_stock (GTK_STOCK_DIALOG_ERROR, GTK_ICON_SIZE_DIALOG); + gtk_box_pack_start (GTK_BOX(hbox), image, FALSE, FALSE, 12); + + { + GtkWidget *vbox2, *info; + + /* message */ + vbox2 = gtk_vbox_new (FALSE, 0); + gtk_box_pack_start (GTK_BOX (hbox), vbox2, TRUE, TRUE, 0); + info = gtk_label_new ("An assertion failed!"); + gtk_box_pack_start (GTK_BOX(vbox2), info, TRUE, TRUE, 8); + + /* assert message */ + dlg->message = gtk_label_new (NULL); + gtk_label_set_selectable (GTK_LABEL (dlg->message), TRUE); + gtk_label_set_line_wrap (GTK_LABEL (dlg->message), TRUE); + gtk_label_set_justify (GTK_LABEL (dlg->message), GTK_JUSTIFY_LEFT); + gtk_widget_set_size_request (GTK_WIDGET(dlg->message), 450, -1); + + gtk_box_pack_end (GTK_BOX(vbox2), GTK_WIDGET(dlg->message), TRUE, TRUE, 8); + } + + /* add the expander */ +#if GTK_CHECK_VERSION(2,4,0) + if (!gtk_check_version (2, 4, 0)) + { + dlg->expander = gtk_expander_new_with_mnemonic ("Back_trace:"); + gtk_box_pack_start (GTK_BOX(vbox), dlg->expander, TRUE, TRUE, 0); + g_signal_connect (GTK_EXPANDER(dlg->expander), "activate", + G_CALLBACK(gtk_assert_dialog_expander_callback), dlg); + } + else +#endif + { + // if GtkExpander is unavailable, then use a static frame instead + dlg->expander = gtk_frame_new ("Back_trace:"); + gtk_box_pack_start (GTK_BOX(vbox), dlg->expander, TRUE, TRUE, 0); + } + + { + GtkWidget *hbox, *vbox, *button, *sw; + + /* create expander's vbox */ + vbox = gtk_vbox_new (FALSE, 0); + gtk_container_add (GTK_CONTAINER (dlg->expander), vbox); + + /* add a scrollable window under the expander */ + sw = gtk_scrolled_window_new (NULL, NULL); + gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (sw), GTK_SHADOW_ETCHED_IN); + gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (sw), GTK_POLICY_AUTOMATIC, + GTK_POLICY_AUTOMATIC); + gtk_box_pack_start (GTK_BOX(vbox), sw, TRUE, TRUE, 8); + + /* add the treeview to the scrollable window */ + dlg->treeview = gtk_assert_dialog_create_backtrace_list_model (); + gtk_widget_set_size_request (GTK_WIDGET(dlg->treeview), -1, 180); + gtk_container_add (GTK_CONTAINER (sw), dlg->treeview); + + /* create button's hbox */ + hbox = gtk_hbutton_box_new (); + gtk_box_pack_end (GTK_BOX(vbox), hbox, FALSE, FALSE, 0); + gtk_button_box_set_layout (GTK_BUTTON_BOX(hbox), GTK_BUTTONBOX_END); + + /* add the buttons */ +#if GTK_CHECK_VERSION(2,4,0) + if (!gtk_check_version (2, 4, 0)) + { + /* add this button only if GTK supports GtkFileChooserDialog */ + button = gtk_assert_dialog_add_button_to (GTK_BOX(hbox), "Save to _file", + GTK_STOCK_SAVE, GTK_RESPONSE_NONE); + g_signal_connect (button, "clicked", + G_CALLBACK(gtk_assert_dialog_save_backtrace_callback), dlg); + } +#endif + + button = gtk_assert_dialog_add_button_to (GTK_BOX(hbox), "Copy to clip_board", + GTK_STOCK_COPY, GTK_RESPONSE_NONE); + g_signal_connect (button, "clicked", G_CALLBACK(gtk_assert_dialog_copy_callback), dlg); + } + + /* add the buttons */ + gtk_assert_dialog_add_button (dlg, "_Stop", GTK_STOCK_QUIT, GTK_ASSERT_DIALOG_STOP); + gtk_assert_dialog_add_button (dlg, "_Continue", GTK_STOCK_YES, GTK_ASSERT_DIALOG_CONTINUE); + gtk_assert_dialog_add_button (dlg, "Continue su_ppressing", GTK_STOCK_OK, + GTK_ASSERT_DIALOG_CONTINUE_SUPPRESSING); + gtk_dialog_set_default_response (GTK_DIALOG (dlg), GTK_ASSERT_DIALOG_STOP); + + /* complete creation */ + dlg->callback = NULL; + dlg->userdata = NULL; + + /* the resizeable property of this window is modified by the expander: + when it's collapsed, the window must be non-resizeable! */ + gtk_window_set_resizable (GTK_WINDOW (dlg), FALSE); + gtk_widget_pop_composite_child (); + gtk_widget_show_all (GTK_WIDGET(dlg)); +} + + + +/* ---------------------------------------------------------------------------- + GtkAssertDialog public API + ---------------------------------------------------------------------------- */ + +gchar *gtk_assert_dialog_get_message (GtkAssertDialog *dlg) +{ + /* NOTES: + 1) returned string must g_free()d ! + 2) Pango markup is automatically stripped off by GTK + */ + return g_strdup (gtk_label_get_text (GTK_LABEL(dlg->message))); +} + +gchar *gtk_assert_dialog_get_backtrace (GtkAssertDialog *dlg) +{ + gchar *function, *arguments, *sourcefile; + guint count, linenum; + + GtkTreeModel *model; + GtkTreeIter iter; + GString *string; + + g_return_val_if_fail (GTK_IS_ASSERT_DIALOG (dlg), NULL); + model = gtk_tree_view_get_model (GTK_TREE_VIEW(dlg->treeview)); + string = g_string_new(""); + + /* iterate over the list */ + if (!gtk_tree_model_get_iter_first (model, &iter)) + return NULL; + + do + { + /* append this stack frame's info to the string */ + gtk_tree_model_get (model, &iter, + STACKFRAME_LEVEL_COLIDX, &count, + FUNCTION_NAME_COLIDX, &function, + FUNCTION_ARGS_COLIDX, &arguments, + SOURCE_FILE_COLIDX, &sourcefile, + LINE_NUMBER_COLIDX, &linenum, + -1); + + if (sourcefile[0] != '\0') + g_string_append_printf (string, "[%d] %s(%s) %s:%d\n", + count, function, arguments, + sourcefile, linenum); + else + g_string_append_printf (string, "[%d] %s(%s)\n", + count, function, arguments); + + g_free (function); + g_free (arguments); + g_free (sourcefile); + + } while (gtk_tree_model_iter_next (model, &iter)); + + /* returned string must g_free()d */ + return g_string_free (string, FALSE); +} + +void gtk_assert_dialog_set_message(GtkAssertDialog *dlg, const gchar *msg) +{ + /* prepend and append the tag */ + gchar *decorated_msg = g_strdup_printf("%s", msg); + + g_return_if_fail (GTK_IS_ASSERT_DIALOG (dlg)); + gtk_label_set_markup (GTK_LABEL(dlg->message), decorated_msg); + + g_free (decorated_msg); +} + +void gtk_assert_dialog_set_backtrace_callback(GtkAssertDialog *assertdlg, + GtkAssertDialogStackFrameCallback callback, + void *userdata) +{ + assertdlg->callback = callback; + assertdlg->userdata = userdata; + + if (gtk_check_version (2, 4, 0)) + { + // we need to immediately process the stack trace as we're not using + // an expander since GTK does not support it + gtk_assert_dialog_process_backtrace (assertdlg); + } +} + +void gtk_assert_dialog_append_stack_frame(GtkAssertDialog *dlg, + const gchar *function, + const gchar *arguments, + const gchar *sourcefile, + guint line_number) +{ + GtkTreeModel *model; + GtkTreeIter iter; + gint count; + + g_return_if_fail (GTK_IS_ASSERT_DIALOG (dlg)); + model = gtk_tree_view_get_model (GTK_TREE_VIEW(dlg->treeview)); + + /* how many items are in the list up to now ? */ + count = gtk_tree_model_iter_n_children (model, NULL); + + /* add data to the list store */ + gtk_list_store_append (GTK_LIST_STORE(model), &iter); + gtk_list_store_set (GTK_LIST_STORE(model), &iter, + STACKFRAME_LEVEL_COLIDX, count+1, /* start from 1 and not from 0 */ + FUNCTION_NAME_COLIDX, function, + FUNCTION_ARGS_COLIDX, arguments, + SOURCE_FILE_COLIDX, sourcefile, + LINE_NUMBER_COLIDX, line_number, + -1); +} + +GtkWidget *gtk_assert_dialog_new(void) +{ + GtkAssertDialog *dialog = g_object_new (GTK_TYPE_ASSERT_DIALOG, NULL); + + return GTK_WIDGET (dialog); +} + +#ifdef __cplusplus +} +#endif /* __cplusplus */