1 /*
2  *  break.c
3  *
4  *  Copyright 2012 Dimitar Toshkov Zhekov <dimitar.zhekov@gmail.com>
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, see <http://www.gnu.org/licenses/>.
18  */
19 
20 #ifdef HAVE_CONFIG_H
21 # include "config.h"
22 #endif
23 
24 #include <ctype.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <gdk/gdkkeysyms.h>
28 
29 #include "common.h"
30 
31 enum
32 {
33 	BREAK_ID,
34 	BREAK_FILE,
35 	BREAK_LINE,
36 	BREAK_SCID,
37 	BREAK_TYPE,
38 	BREAK_ENABLED,
39 	BREAK_DISPLAY,
40 	BREAK_FUNC,
41 	BREAK_ADDR,
42 	BREAK_TIMES,
43 	BREAK_IGNORE,
44 	BREAK_COND,
45 	BREAK_SCRIPT,
46 	BREAK_IGNNOW,
47 	BREAK_PENDING,
48 	BREAK_LOCATION,
49 	BREAK_RUN_APPLY,
50 	BREAK_TEMPORARY,
51 	BREAK_DISCARD,
52 	BREAK_MISSING
53 };
54 
break_id_compare(ScpTreeStore * store,GtkTreeIter * a,GtkTreeIter * b,G_GNUC_UNUSED gpointer gdata)55 static gint break_id_compare(ScpTreeStore *store, GtkTreeIter *a, GtkTreeIter *b,
56 	G_GNUC_UNUSED gpointer gdata)
57 {
58 	const char *s1, *s2;
59 	gint result;
60 
61 	scp_tree_store_get(store, a, BREAK_ID, &s1, -1);
62 	scp_tree_store_get(store, b, BREAK_ID, &s2, -1);
63 	result = utils_atoi0(s1) - utils_atoi0(s2);
64 
65 	if (result || !s1 || !s2)
66 		return result;
67 
68 	while (isdigit(*s1)) s1++;
69 	while (isdigit(*s2)) s2++;
70 	return atoi(s1 + (*s1 == '.')) - atoi(s2 + (*s2 == '.'));
71 }
72 
break_location_compare(ScpTreeStore * store,GtkTreeIter * a,GtkTreeIter * b,G_GNUC_UNUSED gpointer gdata)73 static gint break_location_compare(ScpTreeStore *store, GtkTreeIter *a, GtkTreeIter *b,
74 	G_GNUC_UNUSED gpointer gdata)
75 {
76 	gint result = store_seek_compare(store, a, b, NULL);
77 	return result ? result : scp_tree_store_compare_func(store, a, b,
78 		GINT_TO_POINTER(BREAK_LOCATION));
79 }
80 
81 static const char
82 	*const BP_TYPES   = "bhtfwwwaarrc?",
83 	*const BP_BREAKS  = "bh",
84 	*const BP_TRACES  = "tf",
85 	*const BP_HARDWS  = "hf",
86 	*const BP_BORTS   = "bhtf",
87 	*const BP_WHATS   = "warc",
88 	*const BP_KNOWNS  = "btfwar",
89 	*const BP_WATCHES = "war",
90 	*const BP_WATOPTS = "ar";
91 
92 typedef struct _BreakType
93 {
94 	const char *text;
95 	const char *type;
96 	const gchar *desc;
97 } BreakType;
98 
99 static const BreakType break_types[] =
100 {
101 	{ "breakpoint",      "break",  N_("breakpoint") },
102 	{ "hw breakpoint",   "hbreak", N_("hardware breakpoint")},
103 	{ "tracepoint",      "trace",  N_("tracepoint") },
104 	{ "fast tracepoint", "ftrace", N_("fast tracepoint") },
105 	{ "wpt",             "watch",  N_("write watchpoint") },
106 	{ "watchpoint",      "watch",  NULL },
107 	{ "hw watchpoint",   "watch",  NULL },
108 	{ "hw-awpt",         "access", N_("access watchpoint") },
109 	{ "acc watchpoint",  "access", NULL },
110 	{ "hw-rwpt",         "read",   N_("read watchpoint") },
111 	{ "read watchpoint", "read",   NULL },
112 	{ "catchpoint",      "catch",  N_("catchpoint") },
113 	{ NULL,              "??",     NULL }
114 };
115 
116 typedef enum _BreakStage
117 {
118 	BG_PERSIST,
119 	BG_DISCARD,
120 	BG_UNKNOWN,
121 	BG_PARTLOC,
122 	BG_APPLIED,
123 	BG_FOLLOW,
124 	BG_IGNORE,
125 	BG_ONLOAD,
126 	BG_RUNTO,
127 	BG_COUNT
128 } BreakStage;
129 
130 typedef struct _BreakInfo
131 {
132 	char info;
133 	const char *text;
134 } BreakInfo;
135 
136 static const BreakInfo break_infos[BG_COUNT] =
137 {
138 	{ '\0', NULL },
139 	{ 'c',  N_("CLI") },
140 	{ 'u',  N_("unsupported MI") },
141 	{ '\0', NULL },
142 	{ '\0', NULL },
143 	{ 'c',  N_("CLI") },
144 	{ '\0', NULL },
145 	{ 'l',  N_("on load") },
146 	{ 'r',  N_("Run to Cursor") }
147 };
148 
break_type_set_data_func(G_GNUC_UNUSED GtkTreeViewColumn * column,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,G_GNUC_UNUSED gpointer gdata)149 static void break_type_set_data_func(G_GNUC_UNUSED GtkTreeViewColumn *column,
150 	GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter,
151 	G_GNUC_UNUSED gpointer gdata)
152 {
153 	char type, info;
154 	gint discard;
155 	gboolean temporary;
156 	GString *string = g_string_sized_new(0x0F);
157 
158 	gtk_tree_model_get(model, iter, BREAK_TYPE, &type, BREAK_TEMPORARY, &temporary,
159 		BREAK_DISCARD, &discard, -1);
160 	g_string_append(string, break_types[strchr(BP_TYPES, type) - BP_TYPES].type);
161 	info = break_infos[discard].info;
162 
163 	if (info || temporary)
164 	{
165 		g_string_append_c(string, ',');
166 		if (info)
167 			g_string_append_c(string, info);
168 		if (temporary)
169 			g_string_append_c(string, 't');
170 	}
171 
172 	g_object_set(cell, "text", string->str, NULL);
173 	g_string_free(string, TRUE);
174 }
175 
break_ignore_set_data_func(G_GNUC_UNUSED GtkTreeViewColumn * column,GtkCellRenderer * cell,GtkTreeModel * model,GtkTreeIter * iter,G_GNUC_UNUSED gpointer gdata)176 static void break_ignore_set_data_func(G_GNUC_UNUSED GtkTreeViewColumn *column,
177 	GtkCellRenderer *cell, GtkTreeModel *model, GtkTreeIter *iter,
178 	G_GNUC_UNUSED gpointer gdata)
179 {
180 	const gchar *ignore, *ignnow;
181 
182 	gtk_tree_model_get(model, iter, BREAK_IGNORE, &ignore, BREAK_IGNNOW, &ignnow, -1);
183 
184 	if (ignnow)
185 	{
186 		char *text = g_strdup_printf("%s [%s]", ignnow, ignore);
187 		g_object_set(cell, "text", text, NULL);
188 		g_free(text);
189 	}
190 	else
191 		g_object_set(cell, "text", ignore, NULL);
192 }
193 
194 static ScpTreeStore *store;
195 static GtkTreeSelection *selection;
196 static gint scid_gen = 0;
197 
break_mark(GtkTreeIter * iter,gboolean mark)198 static void break_mark(GtkTreeIter *iter, gboolean mark)
199 {
200 	const char *file;
201 	gint line;
202 	gboolean enabled;
203 
204 	scp_tree_store_get(store, iter, BREAK_FILE, &file, BREAK_LINE, &line, BREAK_ENABLED,
205 		&enabled, -1);
206 	utils_mark(file, line, mark, MARKER_BREAKPT + enabled);
207 }
208 
break_enable(GtkTreeIter * iter,gboolean enable)209 static void break_enable(GtkTreeIter *iter, gboolean enable)
210 {
211 	break_mark(iter, FALSE);
212 	scp_tree_store_set(store, iter, BREAK_ENABLED, enable, -1);
213 	break_mark(iter, TRUE);
214 }
215 
on_break_enabled_toggled(G_GNUC_UNUSED GtkCellRendererToggle * renderer,gchar * path_str,G_GNUC_UNUSED gpointer gdata)216 static void on_break_enabled_toggled(G_GNUC_UNUSED GtkCellRendererToggle *renderer,
217 	gchar *path_str, G_GNUC_UNUSED gpointer gdata)
218 {
219 	GtkTreeIter iter;
220 	DebugState state = debug_state();
221 	const char *id;
222 	gint scid;
223 	gboolean enabled;
224 
225 	scp_tree_store_get_iter_from_string(store, &iter, path_str);
226 	scp_tree_store_get(store, &iter, BREAK_ID, &id, BREAK_SCID, &scid, BREAK_ENABLED,
227 		&enabled, -1);
228 	enabled ^= TRUE;
229 
230 	if (state == DS_INACTIVE || !id)
231 	{
232 		break_enable(&iter, enabled);
233 	}
234 	else if (state & DS_SENDABLE)
235 	{
236 		debug_send_format(N, "02%d%d-break-%sable %s", enabled, scid, enabled ? "en" :
237 			"dis", id);
238 	}
239 	else
240 		plugin_beep();
241 }
242 
243 #define EDITCOLS 3
244 
break_command(gint index,char type)245 static const char *break_command(gint index, char type)
246 {
247 	static const char *const break_commands[EDITCOLS] = { "after", "condition", "commands" };
248 	return !index && strchr(BP_TRACES, type) ? "passcount" : break_commands[index];
249 }
250 
on_break_column_edited(G_GNUC_UNUSED GtkCellRendererText * renderer,gchar * path_str,gchar * new_text,gpointer gdata)251 static void on_break_column_edited(G_GNUC_UNUSED GtkCellRendererText *renderer,
252 	gchar *path_str, gchar *new_text, gpointer gdata)
253 {
254 	gint index = GPOINTER_TO_INT(gdata) - 1;
255 	const gchar *set_text = validate_column(new_text, index > 0);
256 	GtkTreeIter iter;
257 	const char *id;
258 	char type;
259 
260 	scp_tree_store_get_iter_from_string(store, &iter, path_str);
261 	scp_tree_store_get(store, &iter, BREAK_ID, &id, BREAK_TYPE, &type, -1);
262 
263 	if (id && (debug_state() & DS_SENDABLE))
264 	{
265 		char *locale = utils_get_locale_from_display(new_text, HB_DEFAULT);
266 
267 		if (index)
268 		{
269 			debug_send_format(F, "023%s-break-%s %s %s", id, break_command(index, type),
270 				id, locale ? locale : "");
271 		}
272 		else
273 		{
274 			debug_send_format(N, "022%s-break-%s %s %s", id, break_command(0, type), id,
275 				locale ? locale : "0");
276 		}
277 		g_free(locale);
278 	}
279 	else if (!id)
280 	{
281 		scp_tree_store_set(store, &iter, index + BREAK_IGNORE, set_text,
282 			index ? -1 : BREAK_IGNNOW, NULL, -1);
283 	}
284 	else
285 		plugin_beep();
286 }
287 
on_break_ignore_editing_started(G_GNUC_UNUSED GtkCellRenderer * cell,GtkCellEditable * editable,const gchar * path_str,G_GNUC_UNUSED gpointer gdata)288 static void on_break_ignore_editing_started(G_GNUC_UNUSED GtkCellRenderer *cell,
289 	GtkCellEditable *editable, const gchar *path_str, G_GNUC_UNUSED gpointer gdata)
290 {
291 	GtkTreeIter iter;
292 	const gchar *ignore;
293 
294 	if (GTK_IS_EDITABLE(editable))
295 		validator_attach(GTK_EDITABLE(editable), VALIDATOR_NUMERIC);
296 
297 	if (GTK_IS_ENTRY(editable))
298 		gtk_entry_set_max_length(GTK_ENTRY(editable), 10);
299 
300 	scp_tree_store_get_iter_from_string(store, &iter, path_str);
301 	scp_tree_store_get(store, &iter, BREAK_IGNORE, &ignore, -1);
302 	g_signal_connect(editable, "map", G_CALLBACK(on_view_editable_map), g_strdup(ignore));
303 }
304 
305 static const TreeCell break_cells[] =
306 {
307 	{ "break_enabled", G_CALLBACK(on_break_enabled_toggled) },
308 	{ "break_ignore",  G_CALLBACK(on_break_column_edited)   },
309 	{ "break_cond",    G_CALLBACK(on_break_column_edited)   },
310 	{ "break_script",  G_CALLBACK(on_break_column_edited)   },
311 	{ NULL, NULL }
312 };
313 
append_script_command(const ParseNode * node,GString * string)314 static void append_script_command(const ParseNode *node, GString *string)
315 {
316 	iff (node->type == PT_VALUE, "script: contains array")
317 	{
318 		gchar *display = utils_get_display_from_7bit((char *) node->value, HB_DEFAULT);
319 		const gchar *s;
320 
321 		if (string->len)
322 			g_string_append_c(string, ' ');
323 		g_string_append_c(string, '"');
324 
325 		for (s = display; *s; s++)
326 		{
327 			if (*s == '"' || *s == '\\')
328 				g_string_append_c(string, '\\');
329 			g_string_append_c(string, *s);
330 		}
331 
332 		g_string_append_c(string, '"');
333 		g_free(display);
334 	}
335 }
336 
337 typedef struct _BreakData
338 {
339 	GtkTreeIter iter;
340 	char type;
341 	BreakStage stage;
342 } BreakData;
343 
break_iter_applied(GtkTreeIter * iter,const char * id)344 static void break_iter_applied(GtkTreeIter *iter, const char *id)
345 {
346 	const gchar *columns[EDITCOLS];
347 	gboolean enabled;
348 	char type;
349 	gint index;
350 
351 	scp_tree_store_get(store, iter, BREAK_ENABLED, &enabled, BREAK_IGNORE, &columns[0],
352 		BREAK_COND, &columns[1], BREAK_SCRIPT, &columns[2], BREAK_TYPE, &type, -1);
353 
354 	if (strchr(BP_BORTS, type))
355 	{
356 		if (strchr(BP_BREAKS, type))
357 			columns[0] = NULL;
358 
359 		columns[1] = NULL;
360 	}
361 	else if (!enabled)
362 		debug_send_format(N, "-break-disable %s", id);
363 
364 	for (index = 0; index < EDITCOLS; index++)
365 	{
366 		char *locale = utils_get_locale_from_display(columns[index], HB_DEFAULT);
367 
368 		if (locale)
369 		{
370 			debug_send_format(F, "-break-%s %s %s", break_command(index, type), id,
371 				locale);
372 			g_free(locale);
373 		}
374 	}
375 }
376 
break_node_parse(const ParseNode * node,BreakData * bd)377 static void break_node_parse(const ParseNode *node, BreakData *bd)
378 {
379 	GArray *nodes = (GArray *) node->value;
380 	const char *id;
381 
382 	if (node->type == PT_VALUE)
383 	{
384 		dc_error("breaks: contains value");
385 		bd->stage = BG_DISCARD;
386 	}
387 	else if ((id = parse_find_value(nodes, "number")) == NULL)
388 	{
389 		dc_error("no number");
390 		bd->stage = BG_DISCARD;
391 	}
392 	else  /* enough data to parse */
393 	{
394 		const char *text_type = parse_find_value(nodes, "type");
395 		const BreakType *bt;
396 		char type;
397 		gboolean leading = !strchr(id, '.');
398 		gboolean borts;
399 		ParseLocation loc;
400 		gboolean enabled = g_strcmp0(parse_find_value(nodes, "enabled"), "n");
401 		GtkTreeIter *const iter = &bd->iter;
402 		const char *times = parse_find_value(nodes, "times");
403 		gboolean temporary = !g_strcmp0(parse_find_value(nodes, "disp"), "del");
404 
405 		if (!text_type)
406 			text_type = node->name;
407 
408 		for (bt = break_types; bt->text; bt++)
409 			if (!strcmp(text_type, bt->text))
410 				break;
411 
412 		type = BP_TYPES[bt - break_types];
413 
414 		if (leading || bd->stage != BG_FOLLOW || type != '?')
415 			bd->type = type;
416 		else
417 			type = bd->type;
418 
419 		borts = strchr(BP_BORTS, type) != NULL;
420 		parse_location(nodes, &loc);
421 
422 		if (bd->stage != BG_APPLIED)
423 		{
424 			const ParseNode *script = parse_find_node(nodes, "script");
425 			GtkTreeIter iter1;
426 			gchar *cond = utils_get_display_from_7bit(parse_find_value(nodes, "cond"),
427 				HB_DEFAULT);
428 			gchar *commands;
429 
430 			if (store_find(store, &iter1, BREAK_ID, id))
431 			{
432 				bd->iter = iter1;
433 				break_mark(iter, FALSE);
434 			}
435 			else  /* new breakpoint */
436 			{
437 				const char *location = parse_find_locale(nodes, "original-location");
438 				char *original = g_strdup(location);
439 				gchar *display;
440 				gboolean pending = parse_find_locale(nodes, "pending") != NULL;
441 
442 				if (original)
443 				{
444 					char *split = strchr(original, ':');
445 
446 					if (g_path_is_absolute(original) && split > original &&
447 						split[1] != ':')
448 					{
449 						*split++ = '\0';
450 
451 						if (!loc.file)
452 							loc.file = original;
453 
454 						if (isdigit(*split) && !loc.line)
455 							loc.line = atoi(split);
456 					}
457 				}
458 				else if (strchr(BP_WHATS, type))
459 				{
460 					if ((location = parse_find_locale(nodes, "exp")) == NULL)
461 						location = parse_find_locale(nodes, "what");
462 				}
463 
464 				if (!location || !strchr(BP_KNOWNS, type))
465 				{
466 					if (bd->stage == BG_PERSIST)
467 						bd->stage = BG_UNKNOWN;  /* can't create apply command */
468 
469 					if (!location)
470 						location = loc.func;
471 				}
472 
473 				display = borts ? utils_get_utf8_basename(location) :
474 					utils_get_display_from_locale(location, HB_DEFAULT);
475 
476 				if (leading)
477 					scp_tree_store_append(store, iter, NULL);
478 				else
479 				{
480 					scp_tree_store_insert(store, &iter1, NULL,
481 						scp_tree_store_iter_tell(store, iter) + 1);
482 					bd->iter = iter1;
483 				}
484 
485 				scp_tree_store_set(store, iter, BREAK_SCID, ++scid_gen, BREAK_TYPE,
486 					type, BREAK_DISPLAY, display, BREAK_PENDING, pending,
487 					BREAK_LOCATION, location, BREAK_RUN_APPLY, leading && borts,
488 					BREAK_DISCARD, leading ? bd->stage : BG_PARTLOC, -1);
489 
490 				if (leading && bd->stage == BG_PERSIST)
491 					utils_tree_set_cursor(selection, iter, 0.5);
492 
493 				g_free(original);
494 				g_free(display);
495 			}
496 
497 			utils_mark(loc.file, loc.line, TRUE, MARKER_BREAKPT + enabled);
498 
499 			if (script)
500 			{
501 				GString *string = g_string_sized_new(0x7F);
502 
503 				if (script->type == PT_VALUE)
504 					append_script_command(script, string);
505 				else
506 				{
507 					parse_foreach((GArray *) script->value,
508 						(GFunc) append_script_command, string);
509 				}
510 
511 				commands = g_string_free(string, FALSE);
512 			}
513 			else
514 				commands = NULL;
515 
516 			scp_tree_store_set(store, iter, BREAK_ENABLED, enabled, BREAK_COND, cond,
517 				BREAK_SCRIPT, commands, -1);
518 			g_free(cond);
519 			g_free(commands);
520 		}
521 
522 		if (strchr(BP_BREAKS, type) || bd->stage != BG_APPLIED)
523 		{
524 			const char *ignnow = parse_find_value(nodes, "ignore");
525 			const char *ignore;
526 
527 			if (!ignnow)
528 				ignnow = parse_find_value(nodes, "pass");
529 
530 			scp_tree_store_get(store, iter, BREAK_IGNORE, &ignore, -1);
531 			scp_tree_store_set(store, iter, BREAK_IGNNOW, ignnow,
532 				!ignore || utils_atoi0(ignnow) > atoi(ignore) ||
533 				bd->stage == BG_IGNORE ? BREAK_IGNORE : -1, ignnow, -1);
534 		}
535 
536 		scp_tree_store_set(store, iter, BREAK_ID, id, BREAK_FILE, loc.file, BREAK_LINE,
537 			loc.line, BREAK_FUNC, loc.func, BREAK_ADDR, loc.addr, BREAK_TIMES,
538 			utils_atoi0(times), BREAK_MISSING, FALSE, BREAK_TEMPORARY, temporary, -1);
539 
540 		parse_location_free(&loc);
541 
542 		if (bd->stage == BG_APPLIED)
543 			break_iter_applied(iter, id);
544 		else if (bd->stage == BG_RUNTO)
545 			debug_send_thread("-exec-continue");
546 
547 		bd->stage = BG_FOLLOW;
548 	}
549 }
550 
on_break_inserted(GArray * nodes)551 void on_break_inserted(GArray *nodes)
552 {
553 	const char *token = parse_grab_token(nodes);
554 	BreakData bd;
555 
556 	bd.stage = BG_PERSIST;
557 
558 	if (token)
559 	{
560 		if (*token == '0')
561 			bd.stage = BG_RUNTO;
562 		else if (*token)
563 		{
564 			iff (store_find(store, &bd.iter, BREAK_SCID, token), "%s: b_scid not found",
565 				token)
566 			{
567 				bd.stage = BG_APPLIED;
568 			}
569 		}
570 		else
571 			bd.stage = BG_ONLOAD;
572 	}
573 
574 	parse_foreach(nodes, (GFunc) break_node_parse, &bd);
575 }
576 
break_apply(GtkTreeIter * iter,gboolean thread)577 static void break_apply(GtkTreeIter *iter, gboolean thread)
578 {
579 	GString *command = g_string_sized_new(0x1FF);
580 	gint scid;
581 	char type;
582 	const char *ignore, *location, *s;
583 	gboolean enabled, pending, temporary;
584 	const gchar *cond;
585 	gboolean borts;
586 
587 	scp_tree_store_get(store, iter, BREAK_SCID, &scid, BREAK_TYPE, &type, BREAK_ENABLED,
588 		&enabled, BREAK_IGNORE, &ignore, BREAK_COND, &cond, BREAK_LOCATION, &location,
589 		BREAK_PENDING, &pending, BREAK_TEMPORARY, &temporary, -1);
590 
591 	borts = strchr(BP_BORTS, type) != NULL;
592 	g_string_append_printf(command, "02%d-break-%s", scid, borts ? "insert" : "watch");
593 
594 	if (borts)
595 	{
596 		if (temporary)
597 			g_string_append(command, " -t");
598 
599 		if (strchr(BP_HARDWS, type))
600 			g_string_append(command, " -h");
601 
602 		if (strchr(BP_BREAKS, type))
603 		{
604 			if (ignore)
605 				g_string_append_printf(command, " -i %s", ignore);
606 		}
607 		else
608 			g_string_append(command, " -a");
609 
610 		if (!enabled)
611 			g_string_append(command, " -d");
612 
613 		if (cond)
614 		{
615 			char *locale = utils_get_locale_from_display(cond, HB_DEFAULT);
616 			g_string_append_printf(command, " -c \"%s\"", locale);
617 			g_free(locale);
618 		}
619 
620 		if (pending)
621 			g_string_append(command, " -f");
622 
623 		if (thread && thread_id)
624 			g_string_append_printf(command, " -p %s", thread_id);
625 	}
626 	else if (strchr(BP_WATOPTS, type))
627 		g_string_append_printf(command, " -%c", type);
628 
629 	for (s = location; *s; s++)
630 	{
631 		if (isspace(*s))
632 		{
633 			s = "\"";
634 			break;
635 		}
636 	}
637 
638 	g_string_append_printf(command, " %s%s%s", s, location, s);
639 	debug_send_command(F, command->str);
640 	g_string_free(command, TRUE);
641 }
642 
break_clear(GtkTreeIter * iter)643 static void break_clear(GtkTreeIter *iter)
644 {
645 	char type;
646 
647 	scp_tree_store_get(store, iter, BREAK_TYPE, &type, -1);
648 	scp_tree_store_set(store, iter, BREAK_ID, NULL, BREAK_ADDR, NULL, BREAK_IGNNOW, NULL,
649 		strchr(BP_BORTS, type) ? -1 : BREAK_TEMPORARY, FALSE, -1);
650 }
651 
break_remove(GtkTreeIter * iter)652 static gboolean break_remove(GtkTreeIter *iter)
653 {
654 	break_mark(iter, FALSE);
655 	return scp_tree_store_remove(store, iter);
656 }
657 
break_remove_all(const char * pref,gboolean force)658 static gboolean break_remove_all(const char *pref, gboolean force)
659 {
660 	GtkTreeIter iter;
661 	int len = strlen(pref);
662 	gboolean valid = scp_tree_store_get_iter_first(store, &iter);
663 	gboolean found = FALSE;
664 
665 	while (valid)
666 	{
667 		const char *id;
668 		gint discard;
669 
670 		scp_tree_store_get(store, &iter, BREAK_ID, &id, BREAK_DISCARD, &discard, -1);
671 
672 		if (id && !strncmp(id, pref, len) && strchr(".", id[len]))
673 		{
674 			found = TRUE;
675 
676 			if (discard % BG_ONLOAD || force)
677 			{
678 				valid = break_remove(&iter);
679 				continue;
680 			}
681 
682 			break_clear(&iter);
683 		}
684 
685 		valid = scp_tree_store_iter_next(store, &iter);
686 	}
687 
688 	return found;
689 }
690 
on_break_done(GArray * nodes)691 void on_break_done(GArray *nodes)
692 {
693 	const char *token = parse_grab_token(nodes);
694 	const char oper = *token++;
695 	const char *prefix = "";
696 
697 	switch (oper)
698 	{
699 		case '0' :
700 		case '1' :
701 		{
702 			GtkTreeIter iter;
703 
704 			iff (store_find(store, &iter, BREAK_SCID, token), "%s: b_scid not found",
705 				token)
706 			{
707 				break_enable(&iter, oper == '1');
708 			}
709 			break;
710 		}
711 		case '2' : prefix = "022";  /* and continue */
712 		case '3' :
713 		{
714 			debug_send_format(N, "%s-break-info %s", prefix, token);
715 			break;
716 		}
717 		case '4' :
718 		{
719 			if (!break_remove_all(token, TRUE))
720 				dc_error("%s: bid not found", token);
721 			break;
722 		}
723 		default : dc_error("%c%s: invalid b_oper", oper, token);
724 	}
725 }
726 
break_iter_missing(GtkTreeIter * iter,G_GNUC_UNUSED gpointer gdata)727 static void break_iter_missing(GtkTreeIter *iter, G_GNUC_UNUSED gpointer gdata)
728 {
729 	scp_tree_store_set(store, iter, BREAK_MISSING, TRUE, -1);
730 }
731 
breaks_missing(void)732 static void breaks_missing(void)
733 {
734 	GtkTreeIter iter;
735 	gboolean valid = scp_tree_store_get_iter_first(store, &iter);
736 
737 	while (valid)
738 	{
739 		const char *id;
740 		gint discard;
741 		gboolean missing;
742 
743 		scp_tree_store_get(store, &iter, BREAK_ID, &id, BREAK_DISCARD, &discard,
744 			BREAK_MISSING, &missing, -1);
745 
746 		if (id && missing)
747 		{
748 			if (discard % BG_ONLOAD)
749 			{
750 				valid = break_remove(&iter);
751 				continue;
752 			}
753 
754 			break_clear(&iter);
755 		}
756 
757 		valid = scp_tree_store_iter_next(store, &iter);
758 	}
759 }
760 
on_break_list(GArray * nodes)761 void on_break_list(GArray *nodes)
762 {
763 	iff ((nodes = parse_find_array(parse_lead_array(nodes), "body")) != NULL, "no body")
764 	{
765 		const char *token = parse_grab_token(nodes);
766 		gboolean refresh = !g_strcmp0(token, "");
767 		BreakData bd;
768 
769 		if (refresh)
770 			store_foreach(store, (GFunc) break_iter_missing, NULL);
771 
772 		bd.stage = !g_strcmp0(token, "2") ? BG_IGNORE : BG_DISCARD;
773 		parse_foreach(nodes, (GFunc) break_node_parse, &bd);
774 
775 		if (refresh)
776 			breaks_missing();
777 	}
778 }
779 
780 gint break_async = -1;
781 
on_break_stopped(GArray * nodes)782 void on_break_stopped(GArray *nodes)
783 {
784 	if (break_async < TRUE)
785 	{
786 		const char *id = parse_find_value(nodes, "bkptno");
787 
788 		if (id && !g_strcmp0(parse_find_value(nodes, "disp"), "del"))
789 			break_remove_all(id, FALSE);
790 	}
791 
792 	on_thread_stopped(nodes);
793 }
794 
on_break_created(GArray * nodes)795 void on_break_created(GArray *nodes)
796 {
797 #ifndef G_OS_UNIX
798 	if (!pref_async_break_bugs)
799 #endif
800 	{
801 		BreakData bd;
802 		bd.stage = BG_DISCARD;
803 		parse_foreach(nodes, (GFunc) break_node_parse, &bd);
804 	}
805 
806 	break_async = TRUE;
807 }
808 
on_break_deleted(GArray * nodes)809 void on_break_deleted(GArray *nodes)
810 {
811 	break_remove_all(parse_lead_value(nodes), FALSE);
812 	break_async = TRUE;
813 }
814 
break_feature_node_check(const ParseNode * node,G_GNUC_UNUSED gpointer gdata)815 static void break_feature_node_check(const ParseNode *node, G_GNUC_UNUSED gpointer gdata)
816 {
817 	if (!strcmp((const char *) node->value, "breakpoint-notifications"))
818 		break_async = TRUE;
819 }
820 
on_break_features(GArray * nodes)821 void on_break_features(GArray *nodes)
822 {
823 	parse_foreach(parse_lead_array(nodes), (GFunc) break_feature_node_check, NULL);
824 }
825 
break_delete(GtkTreeIter * iter)826 static void break_delete(GtkTreeIter *iter)
827 {
828 	const char *id;
829 
830 	scp_tree_store_get(store, iter, BREAK_ID, &id, -1);
831 
832 	if (debug_state() == DS_INACTIVE || !id)
833 		break_remove(iter);
834 	else
835 		debug_send_format(N, "024%s-break-delete %s", id, id);
836 }
837 
break_iter_mark(GtkTreeIter * iter,GeanyDocument * doc)838 static void break_iter_mark(GtkTreeIter *iter, GeanyDocument *doc)
839 {
840 	const char *file;
841 	gint line;
842 	gboolean enabled;
843 
844 	scp_tree_store_get(store, iter, BREAK_FILE, &file, BREAK_LINE, &line,
845 		BREAK_ENABLED, &enabled, -1);
846 
847 	if (line && !utils_filenamecmp(file, doc->real_path))
848 		sci_set_marker_at_line(doc->editor->sci, line - 1, MARKER_BREAKPT + enabled);
849 }
850 
breaks_mark(GeanyDocument * doc)851 void breaks_mark(GeanyDocument *doc)
852 {
853 	if (doc->real_path)
854 		store_foreach(store, (GFunc) break_iter_mark, doc);
855 }
856 
breaks_clear(void)857 void breaks_clear(void)
858 {
859 	GtkTreeIter iter;
860 	gboolean valid = scp_tree_store_get_iter_first(store, &iter);
861 
862 	while (valid)
863 	{
864 		gint discard;
865 
866 		scp_tree_store_get(store, &iter, BREAK_DISCARD, &discard, -1);
867 
868 		if (discard)
869 			valid = break_remove(&iter);
870 		else
871 		{
872 			break_clear(&iter);
873 			valid = scp_tree_store_iter_next(store, &iter);
874 		}
875 	}
876 }
877 
break_iter_reset(GtkTreeIter * iter,G_GNUC_UNUSED gpointer gdata)878 static void break_iter_reset(GtkTreeIter *iter, G_GNUC_UNUSED gpointer gdata)
879 {
880 	scp_tree_store_set(store, iter, BREAK_TIMES, 0, -1);
881 }
882 
breaks_reset(void)883 void breaks_reset(void)
884 {
885 	store_foreach(store, (GFunc) break_iter_reset, NULL);
886 }
887 
break_iter_apply(GtkTreeIter * iter,G_GNUC_UNUSED gpointer gdata)888 static void break_iter_apply(GtkTreeIter *iter, G_GNUC_UNUSED gpointer gdata)
889 {
890 	const char *id, *ignore, *ignnow;
891 	char type;
892 	gboolean run_apply;
893 
894 	scp_tree_store_get(store, iter, BREAK_ID, &id, BREAK_TYPE, &type, BREAK_IGNORE, &ignore,
895 		BREAK_IGNNOW, &ignnow, BREAK_RUN_APPLY, &run_apply, -1);
896 
897 	if (id)
898 	{
899 		if (g_strcmp0(ignore, ignnow))
900 		{
901 			debug_send_format(F, "023-break-%s %s %s", break_command(0, type), id,
902 				ignore);
903 		}
904 	}
905 	else if (run_apply)
906 		break_apply(iter, FALSE);
907 }
908 
breaks_apply(void)909 void breaks_apply(void)
910 {
911 	store_foreach(store, (GFunc) break_iter_apply, NULL);
912 }
913 
breaks_query_async(GString * commands)914 void breaks_query_async(GString *commands)
915 {
916 	if (break_async == -1)
917 	{
918 		break_async = FALSE;
919 		g_string_append(commands, "05-list-features\n");
920 	}
921 }
922 
break_relocate(GtkTreeIter * iter,const char * real_path,gint line)923 static void break_relocate(GtkTreeIter *iter, const char *real_path, gint line)
924 {
925 	char *location = g_strdup_printf("%s:%d", real_path, line);
926 	gchar *display = utils_get_utf8_basename(location);
927 
928 	scp_tree_store_set(store, iter, BREAK_FILE, real_path, BREAK_LINE, line, BREAK_DISPLAY,
929 		display, BREAK_LOCATION, location, -1);
930 
931 	g_free(display);
932 	g_free(location);
933 }
934 
breaks_delta(ScintillaObject * sci,const char * real_path,gint start,gint delta,gboolean active)935 void breaks_delta(ScintillaObject *sci, const char *real_path, gint start, gint delta,
936 	gboolean active)
937 {
938 	GtkTreeIter iter;
939 	gboolean valid = scp_tree_store_get_iter_first(store, &iter);
940 
941 	while (valid)
942 	{
943 		const char *file;
944 		gint line;
945 		gboolean enabled;
946 		const char *location;
947 
948 		scp_tree_store_get(store, &iter, BREAK_FILE, &file, BREAK_LINE, &line,
949 			BREAK_ENABLED, &enabled, BREAK_LOCATION, &location, -1);
950 
951 		if (--line >= 0 && start <= line && !utils_filenamecmp(file, real_path))
952 		{
953 			if (active)
954 			{
955 				utils_move_mark(sci, line, start, delta, MARKER_BREAKPT + enabled);
956 			}
957 			else if (delta > 0 || start - delta <= line)
958 			{
959 				char *split = strchr(location, ':');
960 
961 				line += delta + 1;
962 
963 				if (split && isdigit(split[1]))
964 					break_relocate(&iter, real_path, line);
965 				else
966 					scp_tree_store_set(store, &iter, BREAK_LINE, line, -1);
967 			}
968 			else
969 			{
970 				sci_delete_marker_at_line(sci, start, MARKER_BREAKPT + enabled);
971 				valid = scp_tree_store_remove(store, &iter);
972 				continue;
973 			}
974 		}
975 
976 		valid = scp_tree_store_iter_next(store, &iter);
977 	}
978 }
979 
break_iter_check(GtkTreeIter * iter,guint * active)980 static void break_iter_check(GtkTreeIter *iter, guint *active)
981 {
982 	const char *id;
983 	gboolean enabled;
984 
985 	scp_tree_store_get(store, iter, BREAK_ID, &id, BREAK_ENABLED, &enabled, -1);
986 	*active += enabled && id;
987 }
988 
breaks_active(void)989 guint breaks_active(void)
990 {
991 	guint active = 0;
992 	store_foreach(store, (GFunc) break_iter_check, &active);
993 	return active;
994 }
995 
on_break_toggle(G_GNUC_UNUSED const MenuItem * menu_item)996 void on_break_toggle(G_GNUC_UNUSED const MenuItem *menu_item)
997 {
998 	GeanyDocument *doc = document_get_current();
999 	gint doc_line = utils_current_line(doc);
1000 	GtkTreeIter iter, iter1;
1001 	gboolean valid = scp_tree_store_get_iter_first(store, &iter);
1002 	gint found = 0;
1003 
1004 	while (valid)
1005 	{
1006 		const char *id, *file;
1007 		gint line;
1008 
1009 		scp_tree_store_get(store, &iter, BREAK_ID, &id, BREAK_FILE, &file, BREAK_LINE,
1010 			&line, -1);
1011 
1012 		if (line == doc_line && !utils_filenamecmp(file, doc->real_path))
1013 		{
1014 			if (found && found != utils_atoi0(id))
1015 			{
1016 				dialogs_show_msgbox(GTK_MESSAGE_INFO,
1017 					_("There are two or more breakpoints at %s:%d.\n\n"
1018 					"Use the breakpoint list to remove the exact one."),
1019 					doc->file_name, doc_line);
1020 				return;
1021 			}
1022 
1023 			found = id ? atoi(id) : -1;
1024 			iter1 = iter;
1025 		}
1026 
1027 		valid = scp_tree_store_iter_next(store, &iter);
1028 	}
1029 
1030 	if (found)
1031 		break_delete(&iter1);
1032 	else if (debug_state() != DS_INACTIVE)
1033 		debug_send_format(N, "-break-insert %s:%d", doc->real_path, doc_line);
1034 	else
1035 	{
1036 		scp_tree_store_append_with_values(store, &iter, NULL, BREAK_SCID, ++scid_gen,
1037 			BREAK_TYPE, 'b', BREAK_ENABLED, TRUE, BREAK_RUN_APPLY, TRUE, -1);
1038 		break_relocate(&iter, doc->real_path, doc_line);
1039 		utils_tree_set_cursor(selection, &iter, 0.5);
1040 		sci_set_marker_at_line(doc->editor->sci, doc_line - 1, MARKER_BREAKPT + TRUE);
1041 	}
1042 }
1043 
breaks_update(void)1044 gboolean breaks_update(void)
1045 {
1046 	debug_send_command(N, "04-break-list");
1047 	return TRUE;
1048 }
1049 
break_iter_unmark(GtkTreeIter * iter,G_GNUC_UNUSED gpointer gdata)1050 static void break_iter_unmark(GtkTreeIter *iter, G_GNUC_UNUSED gpointer gdata)
1051 {
1052 	break_mark(iter, FALSE);
1053 }
1054 
breaks_delete_all(void)1055 void breaks_delete_all(void)
1056 {
1057 	store_foreach(store, (GFunc) break_iter_unmark, NULL);
1058 	store_clear(store);
1059 	scid_gen = 0;
1060 }
1061 
1062 enum
1063 {
1064 	STRING_FILE,
1065 	STRING_DISPLAY,
1066 	STRING_FUNC,
1067 	STRING_IGNORE,
1068 	STRING_COND,
1069 	STRING_SCRIPT,
1070 	STRING_LOCATION,
1071 	STRING_COUNT
1072 };
1073 
1074 static const char *string_names[STRING_COUNT] = { "file", "display", "func", "ignore", "cond",
1075 	"script", "location" };
1076 
break_load(GKeyFile * config,const char * section)1077 static gboolean break_load(GKeyFile *config, const char *section)
1078 {
1079 	guint i;
1080 	gint line, type;
1081 	gboolean enabled, pending, run_apply, temporary;
1082 	char *strings[STRING_COUNT];
1083 	gboolean valid = FALSE;
1084 
1085 	line = utils_get_setting_integer(config, section, "line", 0);
1086 	type = utils_get_setting_integer(config, section, "type", 0);
1087 	enabled = utils_get_setting_boolean(config, section, "enabled", TRUE);
1088 	pending = utils_get_setting_boolean(config, section, "pending", FALSE);
1089 	run_apply = utils_get_setting_boolean(config, section, "run_apply",
1090 		strchr(BP_BORTS, type) != NULL);
1091 	temporary = utils_get_setting_boolean(config, section, "temporary", FALSE);
1092 	for (i = 0; i < STRING_COUNT; i++)
1093 		strings[i] = utils_key_file_get_string(config, section, string_names[i]);
1094 
1095 	if (type && strchr(BP_KNOWNS, type) && strings[STRING_LOCATION] && line >= 0)
1096 	{
1097 		GtkTreeIter iter;
1098 		char *ignore = validate_column(strings[STRING_IGNORE], FALSE);
1099 
1100 		if (!strings[STRING_FILE])
1101 			line = 0;
1102 
1103 		scp_tree_store_append_with_values(store, &iter, NULL, BREAK_FILE,
1104 			strings[STRING_FILE], BREAK_LINE, line, BREAK_SCID, ++scid_gen, BREAK_TYPE,
1105 			type, BREAK_ENABLED, enabled, BREAK_DISPLAY, strings[STRING_DISPLAY],
1106 			BREAK_FUNC, strings[STRING_FUNC], BREAK_IGNORE, ignore, BREAK_COND,
1107 			strings[STRING_COND], BREAK_SCRIPT, strings[STRING_SCRIPT], BREAK_PENDING,
1108 			pending, BREAK_LOCATION, strings[STRING_LOCATION], BREAK_RUN_APPLY,
1109 			run_apply, BREAK_TEMPORARY, temporary, -1);
1110 
1111 		break_mark(&iter, TRUE);
1112 		valid = TRUE;
1113 	}
1114 
1115 	for (i = 0; i < STRING_COUNT; i++)
1116 		g_free(strings[i]);
1117 
1118 	return valid;
1119 }
1120 
breaks_load(GKeyFile * config)1121 void breaks_load(GKeyFile *config)
1122 {
1123 	breaks_delete_all();
1124 	utils_load(config, "break", break_load);
1125 }
1126 
break_save(GKeyFile * config,const char * section,GtkTreeIter * iter)1127 static gboolean break_save(GKeyFile *config, const char *section, GtkTreeIter *iter)
1128 {
1129 	gint discard;
1130 
1131 	scp_tree_store_get(store, iter, BREAK_DISCARD, &discard, -1);
1132 
1133 	if (!discard)
1134 	{
1135 		guint i;
1136 		gint line;
1137 		char type;
1138 		gboolean enabled, pending, run_apply, temporary;
1139 		const char *strings[STRING_COUNT];
1140 
1141 		scp_tree_store_get(store, iter, BREAK_FILE, &strings[STRING_FILE], BREAK_LINE,
1142 			&line, BREAK_TYPE, &type, BREAK_ENABLED, &enabled, BREAK_DISPLAY,
1143 			&strings[STRING_DISPLAY], BREAK_FUNC, &strings[STRING_FUNC], BREAK_IGNORE,
1144 			&strings[STRING_IGNORE], BREAK_COND, &strings[STRING_COND], BREAK_SCRIPT,
1145 			&strings[STRING_SCRIPT], BREAK_PENDING, &pending, BREAK_LOCATION,
1146 			&strings[STRING_LOCATION], BREAK_RUN_APPLY, &run_apply, BREAK_TEMPORARY,
1147 			&temporary, -1);
1148 
1149 		if (line)
1150 			g_key_file_set_integer(config, section, "line", line);
1151 		else
1152 			g_key_file_remove_key(config, section, "line", NULL);
1153 
1154 		g_key_file_set_integer(config, section, "type", type);
1155 		g_key_file_set_boolean(config, section, "enabled", enabled);
1156 		g_key_file_set_boolean(config, section, "pending", pending);
1157 		g_key_file_set_boolean(config, section, "run_apply", run_apply);
1158 
1159 		for (i = 0; i < STRING_COUNT; i++)
1160 		{
1161 			if (strings[i])
1162 				g_key_file_set_string(config, section, string_names[i], strings[i]);
1163 			else
1164 				g_key_file_remove_key(config, section, string_names[i], NULL);
1165 		}
1166 
1167 		if (strchr(BP_BORTS, type))
1168 			g_key_file_set_boolean(config, section, "temporary", temporary);
1169 		else
1170 			g_key_file_remove_key(config, section, "temporary", NULL);
1171 
1172 		return TRUE;
1173 	}
1174 
1175 	return FALSE;
1176 }
1177 
breaks_save(GKeyFile * config)1178 void breaks_save(GKeyFile *config)
1179 {
1180 	store_save(store, config, "break", break_save);
1181 }
1182 
1183 static GObject *block_cells[EDITCOLS];
1184 
on_break_selection_changed(GtkTreeSelection * selection,G_GNUC_UNUSED gpointer gdata)1185 static void on_break_selection_changed(GtkTreeSelection *selection,
1186 	G_GNUC_UNUSED gpointer gdata)
1187 {
1188 	GtkTreeIter iter;
1189 
1190 	if (gtk_tree_selection_get_selected(selection, NULL, &iter))
1191 	{
1192 		const char *id;
1193 		gboolean editable;
1194 		gint index;
1195 
1196 		scp_tree_store_get(store, &iter, BREAK_ID, &id, -1);
1197 		editable = !id || !strchr(id, '.');
1198 
1199 		for (index = 0; index < EDITCOLS; index++)
1200 			g_object_set(block_cells[index], "editable", editable, NULL);
1201 	}
1202 }
1203 
1204 static GtkTreeView *tree;
1205 static GtkTreeViewColumn *break_type_column;
1206 static GtkTreeViewColumn *break_display_column;
1207 
1208 #define gdk_rectangle_point_in(rect, x, y) ((guint) (x - rect.x) < (guint) rect.width && \
1209 	(guint) (y - rect.y) < (guint) rect.height)
1210 
on_break_query_tooltip(G_GNUC_UNUSED GtkWidget * widget,gint x,gint y,gboolean keyboard_tip,GtkTooltip * tooltip,G_GNUC_UNUSED gpointer gdata)1211 static gboolean on_break_query_tooltip(G_GNUC_UNUSED GtkWidget *widget, gint x, gint y,
1212 	gboolean keyboard_tip, GtkTooltip *tooltip, G_GNUC_UNUSED gpointer gdata)
1213 {
1214 	GtkTreePath *path;
1215 	GtkTreeIter iter;
1216 	gboolean has_tip = FALSE;
1217 
1218 	if (gtk_tree_view_get_tooltip_context(tree, &x, &y, keyboard_tip, NULL, &path, &iter))
1219 	{
1220 		GString *text = g_string_sized_new(0xFF);
1221 		GtkTreeViewColumn *tip_column = NULL;
1222 
1223 		if (keyboard_tip)
1224 			gtk_tree_view_get_cursor(tree, NULL, &tip_column);
1225 		else
1226 		{
1227 			GdkRectangle rect;
1228 
1229 			gtk_tree_view_get_background_area(tree, path, break_type_column, &rect);
1230 			tip_column = gdk_rectangle_point_in(rect, x, y) ? break_type_column : NULL;
1231 
1232 			if (!tip_column)
1233 			{
1234 				gtk_tree_view_get_background_area(tree, path, break_display_column,
1235 					&rect);
1236 				if (gdk_rectangle_point_in(rect, x, y))
1237 					tip_column = break_display_column;
1238 			}
1239 		}
1240 
1241 		if (tip_column == break_type_column)
1242 		{
1243 			char type;
1244 			gint discard;
1245 			gboolean temporary;
1246 
1247 			gtk_tree_view_set_tooltip_cell(tree, tooltip, NULL, tip_column, NULL);
1248 			scp_tree_store_get(store, &iter, BREAK_TYPE, &type, BREAK_TEMPORARY,
1249 				&temporary, BREAK_DISCARD, &discard, -1);
1250 			g_string_append(text, break_types[strchr(BP_TYPES, type) - BP_TYPES].desc);
1251 			has_tip = TRUE;
1252 
1253 			if (break_infos[discard].text)
1254 				g_string_append_printf(text, _(", %s"), break_infos[discard].text);
1255 
1256 			if (temporary)
1257 				g_string_append(text, ", temporary");
1258 		}
1259 		else if (tip_column == break_display_column)
1260 		{
1261 			const char *file, *func;
1262 			gint line;
1263 
1264 			gtk_tree_view_set_tooltip_cell(tree, tooltip, NULL, tip_column, NULL);
1265 			scp_tree_store_get(store, &iter, BREAK_FILE, &file, BREAK_LINE, &line,
1266 				BREAK_FUNC, &func, -1);
1267 
1268 			if (file)
1269 			{
1270 				g_string_append(text, file);
1271 				if (line)
1272 					g_string_append_printf(text, ":%d", line);
1273 				has_tip = TRUE;
1274 			}
1275 
1276 			if (func)
1277 			{
1278 				if (has_tip)
1279 					g_string_append(text, ", ");
1280 				g_string_append_printf(text, _("func %s"), func);
1281 				has_tip = TRUE;
1282 			}
1283 		}
1284 
1285 		gtk_tooltip_set_text(tooltip, text->str);
1286 		g_string_free(text, TRUE);
1287 		gtk_tree_path_free(path);
1288 	}
1289 
1290 	return has_tip;
1291 }
1292 
on_break_refresh(G_GNUC_UNUSED const MenuItem * menu_item)1293 static void on_break_refresh(G_GNUC_UNUSED const MenuItem *menu_item)
1294 {
1295 	debug_send_command(N, "02-break-list");
1296 }
1297 
on_break_unsorted(G_GNUC_UNUSED const MenuItem * menu_item)1298 static void on_break_unsorted(G_GNUC_UNUSED const MenuItem *menu_item)
1299 {
1300 	scp_tree_store_set_sort_column_id(store, BREAK_SCID, GTK_SORT_ASCENDING);
1301 }
1302 
on_break_insert(G_GNUC_UNUSED const MenuItem * menu_item)1303 static void on_break_insert(G_GNUC_UNUSED const MenuItem *menu_item)
1304 {
1305 	GeanyDocument *doc = document_get_current();
1306 	GString *command = g_string_new("-break-insert ");
1307 
1308 	if (doc && utils_source_document(doc))
1309 		g_string_append_printf(command, "%s:%d", doc->file_name, utils_current_line(doc));
1310 
1311 	view_command_line(command->str, _("Add Breakpoint"), " ", TRUE);
1312 	g_string_free(command, TRUE);
1313 }
1314 
on_break_watch(G_GNUC_UNUSED const MenuItem * menu_item)1315 static void on_break_watch(G_GNUC_UNUSED const MenuItem *menu_item)
1316 {
1317 	gchar *expr = utils_get_default_selection();
1318 	GString *command = g_string_new("-break-watch ");
1319 
1320 	if (expr)
1321 	{
1322 		g_string_append(command, expr);
1323 		g_free(expr);
1324 	}
1325 
1326 	view_command_line(command->str, _("Add Watchpoint"), " ", TRUE);
1327 	g_string_free(command, TRUE);
1328 }
1329 
on_break_apply(const MenuItem * menu_item)1330 static void on_break_apply(const MenuItem *menu_item)
1331 {
1332 	if (menu_item || thread_id)
1333 	{
1334 		GtkTreeIter iter;
1335 		if (gtk_tree_selection_get_selected(selection, NULL, &iter))
1336 		{
1337 			break_apply(&iter, !menu_item);
1338 		}
1339 	}
1340 	else
1341 		plugin_beep();
1342 }
1343 
on_break_run_apply(const MenuItem * menu_item)1344 static void on_break_run_apply(const MenuItem *menu_item)
1345 {
1346 	GtkTreeIter iter;
1347 
1348 	if (gtk_tree_selection_get_selected(selection, NULL, &iter))
1349 	{
1350 		scp_tree_store_set(store, &iter, BREAK_RUN_APPLY,
1351 			gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menu_item->widget)), -1);
1352 	}
1353 }
1354 
on_break_delete(G_GNUC_UNUSED const MenuItem * menu_item)1355 static void on_break_delete(G_GNUC_UNUSED const MenuItem *menu_item)
1356 {
1357 	GtkTreeIter iter;
1358 	if (gtk_tree_selection_get_selected(selection, NULL, &iter))
1359 	{
1360 		break_delete(&iter);
1361 	}
1362 }
1363 
break_seek_selected(gboolean focus)1364 static void break_seek_selected(gboolean focus)
1365 {
1366 	GtkTreeViewColumn *column;
1367 
1368 	gtk_tree_view_get_cursor(tree, NULL, &column);
1369 
1370 	if (column)
1371 	{
1372 		static const char *unseeks[] = { "break_enabled_column", "break_ignore_column",
1373 			"break_cond_column", "break_script_column", NULL };
1374 		const char *name = gtk_buildable_get_name(GTK_BUILDABLE(column));
1375 		const char **unseek;
1376 
1377 		for (unseek = unseeks; *unseek; unseek++)
1378 			if (!strcmp(*unseek, name))
1379 				return;
1380 	}
1381 
1382 	view_seek_selected(selection, focus, SK_DEFAULT);
1383 }
1384 
on_break_view_source(G_GNUC_UNUSED const MenuItem * menu_item)1385 static void on_break_view_source(G_GNUC_UNUSED const MenuItem *menu_item)
1386 {
1387 	view_seek_selected(selection, FALSE, SK_DEFAULT);
1388 }
1389 
1390 #define DS_VIEWABLE (DS_BASICS | DS_EXTRA_2)
1391 #define DS_APPLIABLE (DS_SENDABLE | DS_EXTRA_1)
1392 #define DS_RUN_APPLY (DS_BASICS | DS_EXTRA_3)
1393 #define DS_DELETABLE (DS_NOT_BUSY | DS_EXTRA_3)
1394 
1395 static MenuItem break_menu_items[] =
1396 {
1397 	{ "break_refresh",     on_break_refresh,     DS_SENDABLE,  NULL, NULL },
1398 	{ "break_unsorted",    on_break_unsorted,    0,            NULL, NULL },
1399 	{ "break_view_source", on_break_view_source, DS_VIEWABLE,  NULL, NULL },
1400 	{ "break_insert",      on_break_insert,      DS_SENDABLE,  NULL, NULL },
1401 	{ "break_watch",       on_break_watch,       DS_SENDABLE,  NULL, NULL },
1402 	{ "break_apply",       on_break_apply,       DS_APPLIABLE, NULL, NULL },
1403 	{ "break_run_apply",   on_break_run_apply,   DS_RUN_APPLY, NULL, NULL },
1404 	{ "break_delete",      on_break_delete,      DS_DELETABLE, NULL, NULL },
1405 	{ NULL, NULL, 0, NULL, NULL }
1406 };
1407 
break_menu_extra_state(void)1408 static guint break_menu_extra_state(void)
1409 {
1410 	GtkTreeIter iter;
1411 
1412 	if (gtk_tree_selection_get_selected(selection, NULL, &iter))
1413 	{
1414 		const char *id, *file;
1415 
1416 		scp_tree_store_get(store, &iter, BREAK_ID, &id, BREAK_FILE, &file, -1);
1417 
1418 		return (!id << DS_INDEX_1) | ((file != NULL) << DS_INDEX_2) |
1419 			((!id || !strchr(id, '.')) << DS_INDEX_3);
1420 	}
1421 
1422 	return 0;
1423 }
1424 
1425 static MenuInfo break_menu_info = { break_menu_items, break_menu_extra_state, 0 };
1426 
on_break_key_press(GtkWidget * widget,GdkEventKey * event,G_GNUC_UNUSED gpointer gdata)1427 static gboolean on_break_key_press(GtkWidget *widget, GdkEventKey *event,
1428 	G_GNUC_UNUSED gpointer gdata)
1429 {
1430 	return menu_insert_delete(event, &break_menu_info, "break_insert", "break_delete") ||
1431 		on_view_key_press(widget, event, break_seek_selected);
1432 }
1433 
on_break_menu_show(G_GNUC_UNUSED GtkWidget * widget,const MenuItem * menu_item)1434 static void on_break_menu_show(G_GNUC_UNUSED GtkWidget *widget, const MenuItem *menu_item)
1435 {
1436 	GtkTreeIter iter;
1437 
1438 	if (gtk_tree_selection_get_selected(selection, NULL, &iter))
1439 	{
1440 		gboolean run_apply;
1441 		scp_tree_store_get(store, &iter, BREAK_RUN_APPLY, &run_apply, -1);
1442 		menu_item_set_active(menu_item, run_apply);
1443 	}
1444 }
1445 
on_break_apply_button_release(GtkWidget * widget,GdkEventButton * event,GtkWidget * menu)1446 static void on_break_apply_button_release(GtkWidget *widget, GdkEventButton *event,
1447 	GtkWidget *menu)
1448 {
1449 	menu_shift_button_release(widget, event, menu, on_break_apply);
1450 }
1451 
break_init(void)1452 void break_init(void)
1453 {
1454 	GtkWidget *menu;
1455 	guint i;
1456 	GtkCellRenderer *break_ignore = GTK_CELL_RENDERER(get_object("break_ignore"));
1457 
1458 	break_type_column = get_column("break_type_column");
1459 	break_display_column = get_column("break_display_column");
1460 	tree = view_connect("break_view", &store, &selection, break_cells, "break_window", NULL);
1461 	gtk_tree_view_column_set_cell_data_func(break_type_column,
1462 		GTK_CELL_RENDERER(get_object("break_type")), break_type_set_data_func, NULL, NULL);
1463 	gtk_tree_view_column_set_cell_data_func(get_column("break_ignore_column"), break_ignore,
1464 		break_ignore_set_data_func, NULL, NULL);
1465 	g_signal_connect(break_ignore, "editing-started",
1466 		G_CALLBACK(on_break_ignore_editing_started), NULL);
1467 	view_set_sort_func(store, BREAK_ID, break_id_compare);
1468 	view_set_sort_func(store, BREAK_IGNORE, store_gint_compare);
1469 	view_set_sort_func(store, BREAK_LOCATION, break_location_compare);
1470 
1471 	for (i = 0; i < EDITCOLS; i++)
1472 		block_cells[i] = get_object(break_cells[i + 1].name);
1473 	g_signal_connect(selection, "changed", G_CALLBACK(on_break_selection_changed), NULL);
1474 	gtk_widget_set_has_tooltip(GTK_WIDGET(tree), TRUE);
1475 	g_signal_connect(tree, "query-tooltip", G_CALLBACK(on_break_query_tooltip), NULL);
1476 
1477 	menu = menu_select("break_menu", &break_menu_info, selection);
1478 	g_signal_connect(tree, "key-press-event", G_CALLBACK(on_break_key_press), NULL);
1479 	g_signal_connect(tree, "button-press-event", G_CALLBACK(on_view_button_1_press),
1480 		break_seek_selected);
1481 	g_signal_connect(menu, "show", G_CALLBACK(on_break_menu_show),
1482 		(gpointer) menu_item_find(break_menu_items, "break_run_apply"));
1483 	g_signal_connect(get_widget("break_apply"), "button-release-event",
1484 		G_CALLBACK(on_break_apply_button_release), menu);
1485 }
1486 
break_finalize(void)1487 void break_finalize(void)
1488 {
1489 	store_foreach(store, (GFunc) break_iter_unmark, NULL);
1490 }
1491