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