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