1 /*
2 * stree.c
3 *
4 * Copyright 2010 Alexander Petukhov <devel(at)apetukhov.ru>
5 *
6 * This program is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; either version 2 of the License, or
9 * (at your option) any later version.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301, USA.
20 */
21
22 /*
23 * Contains function to manipulate stack trace tree view.
24 */
25
26 #include <stdlib.h>
27 #include <string.h>
28
29 #ifdef HAVE_CONFIG_H
30 #include "config.h"
31 #endif
32 #include <geanyplugin.h>
33
34 #include "stree.h"
35 #include "breakpoints.h"
36 #include "utils.h"
37 #include "debug_module.h"
38 #include "pixbuf.h"
39
40 #include "cell_renderers/cellrendererframeicon.h"
41
42 /* Tree view columns */
43 enum
44 {
45 S_FRAME, /* the frame if it's a frame, or NULL if it's a thread */
46 S_THREAD_ID,
47 S_ACTIVE,
48 S_N_COLUMNS
49 };
50
51 /* active thread and frame */
52 static glong active_thread_id = 0;
53 static int active_frame_index = 0;
54
55 /* callbacks */
56 static select_frame_cb select_frame = NULL;
57 static select_thread_cb select_thread = NULL;
58 static move_to_line_cb move_to_line = NULL;
59
60 /* tree view, model and store handles */
61 static GtkWidget *tree = NULL;
62 static GtkTreeModel *model = NULL;
63 static GtkTreeStore *store = NULL;
64
65 static GtkTreeViewColumn *column_filepath = NULL;
66 static GtkTreeViewColumn *column_address = NULL;
67
68 /* cell renderer for a frame arrow */
69 static GtkCellRenderer *renderer_arrow = NULL;
70
71 static GType frame_get_type (void);
G_DEFINE_BOXED_TYPE(frame,frame,frame_ref,frame_unref)72 G_DEFINE_BOXED_TYPE(frame, frame, frame_ref, frame_unref)
73 #define STREE_TYPE_FRAME (frame_get_type ())
74
75 /* finds the iter for thread @thread_id */
76 static gboolean find_thread_iter (gint thread_id, GtkTreeIter *iter)
77 {
78 gboolean found = FALSE;
79
80 if (gtk_tree_model_get_iter_first(model, iter))
81 {
82 do
83 {
84 gint existing_thread_id;
85
86 gtk_tree_model_get(model, iter, S_THREAD_ID, &existing_thread_id, -1);
87 if (existing_thread_id == thread_id)
88 found = TRUE;
89 }
90 while (! found && gtk_tree_model_iter_next(model, iter));
91 }
92
93 return found;
94 }
95
96 /*
97 * frame arrow clicked callback
98 */
on_frame_arrow_clicked(CellRendererFrameIcon * cell_renderer,gchar * path,gpointer user_data)99 static void on_frame_arrow_clicked(CellRendererFrameIcon *cell_renderer, gchar *path, gpointer user_data)
100 {
101 GtkTreePath *new_active_frame = gtk_tree_path_new_from_string (path);
102 if (gtk_tree_path_get_indices(new_active_frame)[1] != active_frame_index)
103 {
104 GtkTreeIter thread_iter;
105 GtkTreeIter iter;
106 GtkTreePath *old_active_frame;
107
108 find_thread_iter (active_thread_id, &thread_iter);
109 old_active_frame = gtk_tree_model_get_path (model, &thread_iter);
110
111 gtk_tree_path_append_index(old_active_frame, active_frame_index);
112
113 gtk_tree_model_get_iter(model, &iter, old_active_frame);
114 gtk_tree_store_set (store, &iter, S_ACTIVE, FALSE, -1);
115
116 active_frame_index = gtk_tree_path_get_indices(new_active_frame)[1];
117 select_frame(active_frame_index);
118
119 gtk_tree_model_get_iter(model, &iter, new_active_frame);
120 gtk_tree_store_set (store, &iter, S_ACTIVE, TRUE, -1);
121
122 gtk_tree_path_free(old_active_frame);
123 }
124
125 gtk_tree_path_free(new_active_frame);
126 }
127
128 /*
129 * shows a tooltip for a file name
130 */
on_query_tooltip(GtkWidget * widget,gint x,gint y,gboolean keyboard_mode,GtkTooltip * tooltip,gpointer user_data)131 static gboolean on_query_tooltip(GtkWidget *widget, gint x, gint y, gboolean keyboard_mode, GtkTooltip *tooltip, gpointer user_data)
132 {
133 GtkTreePath *tpath = NULL;
134 GtkTreeViewColumn *column = NULL;
135 gboolean show = FALSE;
136 int bx, by;
137 gtk_tree_view_convert_widget_to_bin_window_coords(GTK_TREE_VIEW(widget), x, y, &bx, &by);
138
139 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(widget), bx, by, &tpath, &column, NULL, NULL))
140 {
141 if (2 == gtk_tree_path_get_depth(tpath))
142 {
143 gint start_pos, width;
144 gtk_tree_view_column_cell_get_position(column, renderer_arrow, &start_pos, &width);
145
146 if (column == column_filepath)
147 {
148 frame *f;
149 GtkTreeIter iter;
150 gtk_tree_model_get_iter(model, &iter, tpath);
151
152 gtk_tree_model_get(model, &iter, S_FRAME, &f, -1);
153
154 gtk_tooltip_set_text(tooltip, f->file);
155
156 gtk_tree_view_set_tooltip_row(GTK_TREE_VIEW(widget), tooltip, tpath);
157
158 show = TRUE;
159
160 frame_unref(f);
161 }
162 else if (column == column_address && bx >= start_pos && bx < start_pos + width)
163 {
164 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"));
165 gtk_tree_view_set_tooltip_row(GTK_TREE_VIEW(widget), tooltip, tpath);
166 show = TRUE;
167 }
168 }
169 gtk_tree_path_free(tpath);
170 }
171
172 return show;
173 }
174
175 /*
176 * shows arrow icon for the frame rows, hides renderer for a thread ones
177 */
on_render_arrow(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * tree_model,GtkTreeIter * iter,gpointer data)178 static void on_render_arrow(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model,
179 GtkTreeIter *iter, gpointer data)
180 {
181 GtkTreePath *tpath = gtk_tree_model_get_path(model, iter);
182 g_object_set(cell, "visible", 1 != gtk_tree_path_get_depth(tpath), NULL);
183 gtk_tree_path_free(tpath);
184 }
185
186 /*
187 * empty line renderer text for thread row
188 */
on_render_line(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * tree_model,GtkTreeIter * iter,gpointer data)189 static void on_render_line(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model,
190 GtkTreeIter *iter, gpointer data)
191 {
192 frame *f;
193
194 gtk_tree_model_get (model, iter, S_FRAME, &f, -1);
195
196 if (! f)
197 g_object_set(cell, "text", "", NULL);
198 else
199 {
200 GValue value = G_VALUE_INIT;
201
202 g_value_init(&value, G_TYPE_INT);
203 g_value_set_int (&value, f->line);
204 g_object_set_property (G_OBJECT (cell), "text", &value);
205
206 g_value_unset (&value);
207 frame_unref (f);
208 }
209 }
210
211 /*
212 * shows only the file name instead of a full path
213 */
on_render_filename(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * tree_model,GtkTreeIter * iter,gpointer data)214 static void on_render_filename(GtkTreeViewColumn *tree_column, GtkCellRenderer *cell, GtkTreeModel *tree_model,
215 GtkTreeIter *iter, gpointer data)
216 {
217 frame *f;
218
219 gtk_tree_model_get(model, iter, S_FRAME, &f, -1);
220
221 if (! f)
222 g_object_set(cell, "text", "", NULL);
223 else
224 {
225 gchar *name;
226
227 name = f->file ? g_path_get_basename(f->file) : NULL;
228 g_object_set(cell, "text", name ? name : f->file, NULL);
229
230 g_free(name);
231 frame_unref (f);
232 }
233 }
234
235 /*
236 * Handles same tree row click to open frame position
237 */
on_msgwin_button_press(GtkWidget * widget,GdkEventButton * event,gpointer user_data)238 static gboolean on_msgwin_button_press(GtkWidget *widget, GdkEventButton *event, gpointer user_data)
239 {
240 if (event->type == GDK_BUTTON_PRESS)
241 {
242 GtkTreePath *pressed_path = NULL;
243 GtkTreeViewColumn *column = NULL;
244 if (gtk_tree_view_get_path_at_pos(GTK_TREE_VIEW(tree), (int)event->x, (int)event->y, &pressed_path, &column, NULL, NULL))
245 {
246 if (2 == gtk_tree_path_get_depth(pressed_path))
247 {
248 GtkTreePath *selected_path;
249 gtk_tree_view_get_cursor(GTK_TREE_VIEW(tree), &selected_path, NULL);
250
251 if (selected_path && !gtk_tree_path_compare(pressed_path, selected_path))
252 {
253 frame *f;
254 GtkTreeIter iter;
255 gtk_tree_model_get_iter (
256 model,
257 &iter,
258 pressed_path);
259
260 gtk_tree_model_get (model, &iter, S_FRAME, &f, -1);
261
262 /* check if file name is not empty and we have source files for the frame */
263 if (f->have_source)
264 {
265 move_to_line(f->file, f->line);
266 }
267
268 frame_unref(f);
269 }
270
271 if (selected_path)
272 gtk_tree_path_free(selected_path);
273 }
274
275 gtk_tree_path_free(pressed_path);
276 }
277 }
278
279 return FALSE;
280 }
281
282 /*
283 * Tree view cursor changed callback
284 */
on_cursor_changed(GtkTreeView * treeview,gpointer user_data)285 static void on_cursor_changed(GtkTreeView *treeview, gpointer user_data)
286 {
287 GtkTreePath *path;
288 GtkTreeIter iter;
289 frame *f;
290 int thread_id;
291
292 gtk_tree_view_get_cursor(treeview, &path, NULL);
293 if (! path)
294 return;
295
296 gtk_tree_model_get_iter (model, &iter, path);
297 gtk_tree_model_get (model, &iter,
298 S_FRAME, &f, S_THREAD_ID, &thread_id, -1);
299
300 if (f) /* frame */
301 {
302 /* check if file name is not empty and we have source files for the frame */
303 if (f->have_source)
304 {
305 move_to_line(f->file, f->line);
306 }
307 frame_unref(f);
308 }
309 else /* thread */
310 {
311 if (thread_id != active_thread_id)
312 select_thread(thread_id);
313 }
314
315 gtk_tree_path_free(path);
316 }
317
on_render_function(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * tree_model,GtkTreeIter * iter,gpointer data)318 static void on_render_function (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
319 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
320 {
321 frame *f;
322
323 gtk_tree_model_get (tree_model, iter, S_FRAME, &f, -1);
324 if (! f)
325 g_object_set (cell, "text", "", NULL);
326 else
327 {
328 g_object_set (cell, "text", f->function, NULL);
329 frame_unref (f);
330 }
331 }
332
on_render_address(GtkTreeViewColumn * tree_column,GtkCellRenderer * cell,GtkTreeModel * tree_model,GtkTreeIter * iter,gpointer data)333 static void on_render_address (GtkTreeViewColumn *tree_column, GtkCellRenderer *cell,
334 GtkTreeModel *tree_model, GtkTreeIter *iter, gpointer data)
335 {
336 frame *f;
337
338 gtk_tree_model_get (tree_model, iter, S_FRAME, &f, -1);
339 if (f)
340 {
341 g_object_set (cell, "text", f->address, NULL);
342 frame_unref (f);
343 }
344 else
345 {
346 gint thread_id;
347 gchar *thread_label;
348
349 gtk_tree_model_get (model, iter, S_THREAD_ID, &thread_id, -1);
350 thread_label = g_strdup_printf(_("Thread %i"), thread_id);
351 g_object_set (cell, "text", thread_label, NULL);
352 g_free(thread_label);
353 }
354 }
355
356 /*
357 * inits stack trace tree
358 */
stree_init(move_to_line_cb ml,select_thread_cb st,select_frame_cb sf)359 GtkWidget* stree_init(move_to_line_cb ml, select_thread_cb st, select_frame_cb sf)
360 {
361 GtkTreeViewColumn *column;
362 GtkCellRenderer *renderer;
363
364 move_to_line = ml;
365 select_thread = st;
366 select_frame = sf;
367
368 /* create tree view */
369 store = gtk_tree_store_new (
370 S_N_COLUMNS,
371 STREE_TYPE_FRAME, /* frame */
372 G_TYPE_INT /* thread ID */,
373 G_TYPE_BOOLEAN /* active */);
374
375 model = GTK_TREE_MODEL(store);
376 tree = gtk_tree_view_new_with_model (GTK_TREE_MODEL(store));
377 g_object_unref(store);
378
379 /* set tree view properties */
380 gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), 1);
381 gtk_widget_set_has_tooltip(tree, TRUE);
382 gtk_tree_view_set_show_expanders(GTK_TREE_VIEW(tree), FALSE);
383
384 /* connect signals */
385 g_signal_connect(G_OBJECT(tree), "cursor-changed", G_CALLBACK (on_cursor_changed), NULL);
386
387 /* for clicking on already selected frame */
388 g_signal_connect(G_OBJECT(tree), "button-press-event", G_CALLBACK(on_msgwin_button_press), NULL);
389
390 g_signal_connect(G_OBJECT(tree), "query-tooltip", G_CALLBACK (on_query_tooltip), NULL);
391
392 /* creating columns */
393 /* address */
394 column_address = column = gtk_tree_view_column_new();
395 gtk_tree_view_column_set_title(column, _("Address"));
396
397 renderer_arrow = cell_renderer_frame_icon_new ();
398 g_object_set(renderer_arrow, "pixbuf_active", (gpointer)frame_current_pixbuf, NULL);
399 g_object_set(renderer_arrow, "pixbuf_highlighted", (gpointer)frame_pixbuf, NULL);
400 gtk_tree_view_column_pack_start(column, renderer_arrow, TRUE);
401 gtk_tree_view_column_set_attributes(column, renderer_arrow, "active_frame", S_ACTIVE, NULL);
402 gtk_tree_view_column_set_cell_data_func(column, renderer_arrow, on_render_arrow, NULL, NULL);
403 g_signal_connect (G_OBJECT(renderer_arrow), "clicked", G_CALLBACK(on_frame_arrow_clicked), NULL);
404
405 renderer = gtk_cell_renderer_text_new ();
406 gtk_tree_view_column_pack_start(column, renderer, TRUE);
407 gtk_tree_view_column_set_cell_data_func(column, renderer, on_render_address, NULL, NULL);
408
409 gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
410
411 /* function */
412 renderer = gtk_cell_renderer_text_new ();
413 column = gtk_tree_view_column_new_with_attributes (_("Function"), renderer, NULL);
414 gtk_tree_view_column_set_cell_data_func(column, renderer, on_render_function, NULL, NULL);
415 gtk_tree_view_column_set_resizable (column, TRUE);
416 gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
417
418 /* file */
419 renderer = gtk_cell_renderer_text_new ();
420 column_filepath = column = gtk_tree_view_column_new_with_attributes (_("File"), renderer, NULL);
421 gtk_tree_view_column_set_resizable (column, TRUE);
422 gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
423 gtk_tree_view_column_set_cell_data_func(column, renderer, on_render_filename, NULL, NULL);
424
425 /* line */
426 renderer = gtk_cell_renderer_text_new ();
427 column = gtk_tree_view_column_new_with_attributes (_("Line"), renderer, NULL);
428 gtk_tree_view_column_set_cell_data_func(column, renderer, on_render_line, NULL, NULL);
429 gtk_tree_view_column_set_resizable (column, TRUE);
430 gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
431
432 /* Last invisible column */
433 column = gtk_tree_view_column_new ();
434 gtk_tree_view_append_column (GTK_TREE_VIEW (tree), column);
435
436 return tree;
437 }
438
439 /*
440 * add frames to the tree view
441 */
stree_add(GList * frames)442 void stree_add(GList *frames)
443 {
444 GtkTreeIter thread_iter;
445 GList *item;
446
447 g_object_ref (model);
448 gtk_tree_view_set_model (GTK_TREE_VIEW (tree), NULL);
449
450 find_thread_iter (active_thread_id, &thread_iter);
451 /* prepending is a *lot* faster than appending, so prepend with a reversed data set */
452 for (item = g_list_last (frames); item; item = item->prev)
453 {
454 gtk_tree_store_insert_with_values (store, NULL, &thread_iter, 0,
455 S_FRAME, item->data, -1);
456 }
457
458 gtk_tree_view_set_model (GTK_TREE_VIEW (tree), model);
459 g_object_unref (model);
460 }
461
462 /*
463 * clear tree view completely
464 */
stree_clear(void)465 void stree_clear(void)
466 {
467 gtk_tree_store_clear(store);
468 }
469
470 /*
471 * select first frame in the stack
472 */
stree_select_first_frame(gboolean make_active)473 void stree_select_first_frame(gboolean make_active)
474 {
475 GtkTreeIter thread_iter, frame_iter;
476
477 gtk_tree_view_expand_all(GTK_TREE_VIEW(tree));
478
479 if (find_thread_iter (active_thread_id, &thread_iter) &&
480 gtk_tree_model_iter_children(model, &frame_iter, &thread_iter))
481 {
482 GtkTreePath* path;
483
484 if (make_active)
485 {
486 gtk_tree_store_set (store, &frame_iter, S_ACTIVE, TRUE, -1);
487 active_frame_index = 0;
488 }
489
490 path = gtk_tree_model_get_path(model, &frame_iter);
491
492 gtk_tree_view_set_cursor(GTK_TREE_VIEW(tree), path, NULL, FALSE);
493
494 gtk_tree_path_free(path);
495 }
496 }
497
498 /*
499 * called on plugin exit to free module data
500 */
stree_destroy(void)501 void stree_destroy(void)
502 {
503 }
504
505 /*
506 * add new thread to the tree view
507 */
stree_add_thread(int thread_id)508 void stree_add_thread(int thread_id)
509 {
510 GtkTreeIter thread_iter, new_thread_iter;
511
512 if (gtk_tree_model_get_iter_first(model, &thread_iter))
513 {
514 GtkTreeIter *consecutive = NULL;
515 do
516 {
517 int existing_thread_id;
518 gtk_tree_model_get(model, &thread_iter, S_THREAD_ID, &existing_thread_id, -1);
519 if (existing_thread_id > thread_id)
520 {
521 consecutive = &thread_iter;
522 break;
523 }
524 }
525 while(gtk_tree_model_iter_next(model, &thread_iter));
526
527 if(consecutive)
528 {
529 gtk_tree_store_prepend(store, &new_thread_iter, consecutive);
530 }
531 else
532 {
533 gtk_tree_store_append(store, &new_thread_iter, NULL);
534 }
535 }
536 else
537 {
538 gtk_tree_store_append (store, &new_thread_iter, NULL);
539 }
540
541 gtk_tree_store_set (store, &new_thread_iter,
542 S_FRAME, NULL,
543 S_THREAD_ID, thread_id,
544 -1);
545 }
546
547 /*
548 * remove a thread from the tree view
549 */
stree_remove_thread(int thread_id)550 void stree_remove_thread(int thread_id)
551 {
552 GtkTreeIter iter;
553
554 if (find_thread_iter (thread_id, &iter))
555 gtk_tree_store_remove(store, &iter);
556 }
557
558 /*
559 * remove all frames
560 */
stree_remove_frames(void)561 void stree_remove_frames(void)
562 {
563 GtkTreeIter child;
564 GtkTreeIter thread_iter;
565
566 if (find_thread_iter (active_thread_id, &thread_iter) &&
567 gtk_tree_model_iter_children(model, &child, &thread_iter))
568 {
569 while(gtk_tree_store_remove(GTK_TREE_STORE(model), &child))
570 ;
571 }
572 }
573
574 /*
575 * set current thread id
576 */
stree_set_active_thread_id(int thread_id)577 void stree_set_active_thread_id(int thread_id)
578 {
579 active_thread_id = thread_id;
580 }
581