/* * stree.c * * Copyright 2010 Alexander Petukhov * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, * MA 02110-1301, USA. */ /* * Contains function to manipulate stack trace tree view. */ #include #include #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include "stree.h" #include "breakpoints.h" #include "utils.h" #include "debug_module.h" #include "pixbuf.h" #include "cell_renderers/cellrendererframeicon.h" /* Tree view columns */ enum { S_FRAME, /* the frame if it's a frame, or NULL if it's a thread */ S_THREAD_ID, S_ACTIVE, S_N_COLUMNS }; /* active thread and frame */ static glong active_thread_id = 0; static int active_frame_index = 0; /* callbacks */ static select_frame_cb select_frame = NULL; static select_thread_cb select_thread = NULL; static move_to_line_cb move_to_line = NULL; /* tree view, model and store handles */ static GtkWidget *tree = NULL; static GtkTreeModel *model = NULL; static GtkTreeStore *store = NULL; static GtkTreeViewColumn *column_filepath = NULL; static GtkTreeViewColumn *column_address = NULL; /* cell renderer for a frame arrow */ static GtkCellRenderer *renderer_arrow = NULL; static GType frame_get_type (void); G_DEFINE_BOXED_TYPE(frame, frame, frame_ref, frame_unref) #define STREE_TYPE_FRAME (frame_get_type ()) /* finds the iter for thread @thread_id */ static gboolean find_thread_iter (gint thread_id, GtkTreeIter *iter) { gboolean found = FALSE; if (gtk_tree_model_get_iter_first(model, iter)) { do { gint existing_thread_id; gtk_tree_model_get(model, iter, S_THREAD_ID, &existing_thread_id, -1); if (existing_thread_id == thread_id) found = TRUE; } while (! found && gtk_tree_model_iter_next(model, iter)); } return found; } /* * frame arrow clicked callback */ static void on_frame_arrow_clicked(CellRendererFrameIcon *cell_renderer, gchar *path, gpointer user_data) { GtkTreePath *new_active_frame = gtk_tree_path_new_from_string (path); if (gtk_tree_path_get_indices(new_active_frame)[1] != active_frame_index) { GtkTreeIter thread_iter; GtkTreeIter iter; GtkTreePath *old_active_frame; find_thread_iter (active_thread_id, &thread_iter); old_active_frame = gtk_tree_model_get_path (model, &thread_iter); gtk_tree_path_append_index(old_active_frame, active_frame_index); gtk_tree_model_get_iter(model, &iter, old_active_frame); gtk_tree_store_set (store, &iter, S_ACTIVE, FALSE, -1); active_frame_index = gtk_tree_path_get_indices(new_active_frame)[1]; select_frame(active_frame_index); gtk_tree_model_get_iter(model, &iter, new_active_frame); gtk_tree_store_set (store, &iter, S_ACTIVE, TRUE, -1); gtk_tree_path_free(old_active_frame); } gtk_tree_path_free(new_active_frame); } /* * shows a tooltip for a file name */ static gboolean on_query_tooltip(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data) { GtkTreePath *tpath = NULL; GtkTreeViewColumn *column = NULL; gboolean show = FALSE; int bx, by; gtk_tree_view_convert_widget_to_bin_window_coords(GTK_TREE_VIEW(widget), x, y, &bx, &by); if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bx, by, &tpath, &column, NULL, NULL)) { if (2 == gtk_tree_path_get_depth(tpath)) { gint start_pos, width; gtk_tree_view_column_cell_get_position(column, renderer_arrow, &start_pos, &width); if (column == column_filepath) { frame *f; GtkTreeIter iter; gtk_tree_model_get_iter(model, &iter, tpath); gtk_tree_model_get(model, &iter, S_FRAME, &f, -1); gtk_tooltip_set_text(tooltip, f->file); gtk_tree_view_set_tooltip_row(GTK_TREE_VIEW(widget), tooltip, tpath); show = TRUE; frame_unref(f); } else if (column == column_address && bx >= start_pos && bx < start_pos + width) { gtk_tooltip_set_text(tooltip, gtk_tree_path_get_indices(tpath)[1] == active_frame_index ? _("Active frame") : _("Click an arrow to switch to a frame")); gtk_tree_view_set_tooltip_row(GTK_TREE_VIEW(widget), tooltip, tpath); show = TRUE; } } gtk_tree_path_free(tpath); } return show; } /* * shows arrow icon for the frame rows, hides renderer for a thread ones */ static void on_render_arrow(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) { GtkTreePath *tpath = gtk_tree_model_get_path(model, iter); g_object_set(cell, "visible", 1 != gtk_tree_path_get_depth(tpath), NULL); gtk_tree_path_free(tpath); } /* * empty line renderer text for thread row */ static void on_render_line(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) { frame *f; gtk_tree_model_get (model, iter, S_FRAME, &f, -1); if (! f) g_object_set(cell, "text", "", NULL); else { GValue value = G_VALUE_INIT; g_value_init(&value, G_TYPE_INT); g_value_set_int (&value, f->line); g_object_set_property (G_OBJECT (cell), "text", &value); g_value_unset (&value); frame_unref (f); } } /* * shows only the file name instead of a full path */ static void on_render_filename(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) { frame *f; gtk_tree_model_get(model, iter, S_FRAME, &f, -1); if (! f) g_object_set(cell, "text", "", NULL); else { gchar *name; name = f->file ? g_path_get_basename(f->file) : NULL; g_object_set(cell, "text", name ? name : f->file, NULL); g_free(name); frame_unref (f); } } /* * Handles same tree row click to open frame position */ static gboolean on_msgwin_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data) { if (event->type == GDK_BUTTON_PRESS) { GtkTreePath *pressed_path = NULL; GtkTreeViewColumn *column = NULL; if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tree), (int)event->x, (int)event->y, &pressed_path, &column, NULL, NULL)) { if (2 == gtk_tree_path_get_depth(pressed_path)) { GtkTreePath *selected_path; gtk_tree_view_get_cursor(GTK_TREE_VIEW(tree), &selected_path, NULL); if (selected_path && !gtk_tree_path_compare(pressed_path, selected_path)) { frame *f; GtkTreeIter iter; gtk_tree_model_get_iter ( model, &iter, pressed_path); gtk_tree_model_get (model, &iter, S_FRAME, &f, -1); /* check if file name is not empty and we have source files for the frame */ if (f->have_source) { move_to_line(f->file, f->line); } frame_unref(f); } if (selected_path) gtk_tree_path_free(selected_path); } gtk_tree_path_free(pressed_path); } } return FALSE; } /* * Tree view cursor changed callback */ static void on_cursor_changed(GtkTreeView *treeview, gpointer user_data) { GtkTreePath *path; GtkTreeIter iter; frame *f; int thread_id; gtk_tree_view_get_cursor(treeview, &path, NULL); if (! path) return; gtk_tree_model_get_iter (model, &iter, path); gtk_tree_model_get (model, &iter, S_FRAME, &f, S_THREAD_ID, &thread_id, -1); if (f) /* frame */ { /* check if file name is not empty and we have source files for the frame */ if (f->have_source) { move_to_line(f->file, f->line); } frame_unref(f); } else /* thread */ { if (thread_id != active_thread_id) select_thread(thread_id); } gtk_tree_path_free(path); } static void on_render_function (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) { frame *f; gtk_tree_model_get (tree_model, iter, S_FRAME, &f, -1); if (! f) g_object_set (cell, "text", "", NULL); else { g_object_set (cell, "text", f->function, NULL); frame_unref (f); } } static void on_render_address (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data) { frame *f; gtk_tree_model_get (tree_model, iter, S_FRAME, &f, -1); if (f) { g_object_set (cell, "text", f->address, NULL); frame_unref (f); } else { gint thread_id; gchar *thread_label; gtk_tree_model_get (model, iter, S_THREAD_ID, &thread_id, -1); thread_label = g_strdup_printf(_("Thread %i"), thread_id); g_object_set (cell, "text", thread_label, NULL); g_free(thread_label); } } /* * inits stack trace tree */ GtkWidget* stree_init(move_to_line_cb ml, select_thread_cb st, select_frame_cb sf) { GtkTreeViewColumn *column; GtkCellRenderer *renderer; move_to_line = ml; select_thread = st; select_frame = sf; /* create tree view */ store = gtk_tree_store_new ( S_N_COLUMNS, STREE_TYPE_FRAME, /* frame */ G_TYPE_INT /* thread ID */, G_TYPE_BOOLEAN /* active */); model = GTK_TREE_MODEL(store); tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL(store)); g_object_unref(store); /* set tree view properties */ gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), 1); gtk_widget_set_has_tooltip(tree, TRUE); gtk_tree_view_set_show_expanders(GTK_TREE_VIEW(tree), FALSE); /* connect signals */ g_signal_connect(G_OBJECT(tree), "cursor-changed", G_CALLBACK (on_cursor_changed), NULL); /* for clicking on already selected frame */ g_signal_connect(G_OBJECT(tree), "button-press-event", G_CALLBACK(on_msgwin_button_press), NULL); g_signal_connect(G_OBJECT(tree), "query-tooltip", G_CALLBACK (on_query_tooltip), NULL); /* creating columns */ /* address */ column_address = column = gtk_tree_view_column_new(); gtk_tree_view_column_set_title(column, _("Address")); renderer_arrow = cell_renderer_frame_icon_new (); g_object_set(renderer_arrow, "pixbuf_active", (gpointer)frame_current_pixbuf, NULL); g_object_set(renderer_arrow, "pixbuf_highlighted", (gpointer)frame_pixbuf, NULL); gtk_tree_view_column_pack_start(column, renderer_arrow, TRUE); gtk_tree_view_column_set_attributes(column, renderer_arrow, "active_frame", S_ACTIVE, NULL); gtk_tree_view_column_set_cell_data_func(column, renderer_arrow, on_render_arrow, NULL, NULL); g_signal_connect (G_OBJECT(renderer_arrow), "clicked", G_CALLBACK(on_frame_arrow_clicked), NULL); renderer = gtk_cell_renderer_text_new (); gtk_tree_view_column_pack_start(column, renderer, TRUE); gtk_tree_view_column_set_cell_data_func(column, renderer, on_render_address, NULL, NULL); gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column); /* function */ renderer = gtk_cell_renderer_text_new (); column = gtk_tree_view_column_new_with_attributes (_("Function"), renderer, NULL); gtk_tree_view_column_set_cell_data_func(column, renderer, on_render_function, NULL, NULL); gtk_tree_view_column_set_resizable (column, TRUE); gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column); /* file */ renderer = gtk_cell_renderer_text_new (); column_filepath = column = gtk_tree_view_column_new_with_attributes (_("File"), renderer, NULL); gtk_tree_view_column_set_resizable (column, TRUE); gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column); gtk_tree_view_column_set_cell_data_func(column, renderer, on_render_filename, NULL, NULL); /* line */ renderer = gtk_cell_renderer_text_new (); column = gtk_tree_view_column_new_with_attributes (_("Line"), renderer, NULL); gtk_tree_view_column_set_cell_data_func(column, renderer, on_render_line, NULL, NULL); gtk_tree_view_column_set_resizable (column, TRUE); gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column); /* Last invisible column */ column = gtk_tree_view_column_new (); gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column); return tree; } /* * add frames to the tree view */ void stree_add(GList *frames) { GtkTreeIter thread_iter; GList *item; g_object_ref (model); gtk_tree_view_set_model (GTK_TREE_VIEW (tree), NULL); find_thread_iter (active_thread_id, &thread_iter); /* prepending is a *lot* faster than appending, so prepend with a reversed data set */ for (item = g_list_last (frames); item; item = item->prev) { gtk_tree_store_insert_with_values (store, NULL, &thread_iter, 0, S_FRAME, item->data, -1); } gtk_tree_view_set_model (GTK_TREE_VIEW (tree), model); g_object_unref (model); } /* * clear tree view completely */ void stree_clear(void) { gtk_tree_store_clear(store); } /* * select first frame in the stack */ void stree_select_first_frame(gboolean make_active) { GtkTreeIter thread_iter, frame_iter; gtk_tree_view_expand_all(GTK_TREE_VIEW(tree)); if (find_thread_iter (active_thread_id, &thread_iter) && gtk_tree_model_iter_children(model, &frame_iter, &thread_iter)) { GtkTreePath* path; if (make_active) { gtk_tree_store_set (store, &frame_iter, S_ACTIVE, TRUE, -1); active_frame_index = 0; } path = gtk_tree_model_get_path(model, &frame_iter); gtk_tree_view_set_cursor(GTK_TREE_VIEW(tree), path, NULL, FALSE); gtk_tree_path_free(path); } } /* * called on plugin exit to free module data */ void stree_destroy(void) { } /* * add new thread to the tree view */ void stree_add_thread(int thread_id) { GtkTreeIter thread_iter, new_thread_iter; if (gtk_tree_model_get_iter_first(model, &thread_iter)) { GtkTreeIter *consecutive = NULL; do { int existing_thread_id; gtk_tree_model_get(model, &thread_iter, S_THREAD_ID, &existing_thread_id, -1); if (existing_thread_id > thread_id) { consecutive = &thread_iter; break; } } while(gtk_tree_model_iter_next(model, &thread_iter)); if(consecutive) { gtk_tree_store_prepend(store, &new_thread_iter, consecutive); } else { gtk_tree_store_append(store, &new_thread_iter, NULL); } } else { gtk_tree_store_append (store, &new_thread_iter, NULL); } gtk_tree_store_set (store, &new_thread_iter, S_FRAME, NULL, S_THREAD_ID, thread_id, -1); } /* * remove a thread from the tree view */ void stree_remove_thread(int thread_id) { GtkTreeIter iter; if (find_thread_iter (thread_id, &iter)) gtk_tree_store_remove(store, &iter); } /* * remove all frames */ void stree_remove_frames(void) { GtkTreeIter child; GtkTreeIter thread_iter; if (find_thread_iter (active_thread_id, &thread_iter) && gtk_tree_model_iter_children(model, &child, &thread_iter)) { while(gtk_tree_store_remove(GTK_TREE_STORE(model), &child)) ; } } /* * set current thread id */ void stree_set_active_thread_id(int thread_id) { active_thread_id = thread_id; }