1 /*
2 * menu.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 <stdlib.h>
25 #include <string.h>
26 #include <gdk/gdkkeysyms.h>
27
28 #include "common.h"
29
menu_item_find(const MenuItem * menu_items,const char * name)30 const MenuItem *menu_item_find(const MenuItem *menu_items, const char *name)
31 {
32 const MenuItem *menu_item;
33
34 for (menu_item = menu_items; menu_item->name; menu_item++)
35 if (!strcmp(menu_item->name, name))
36 break;
37
38 g_assert(menu_item->name);
39 return menu_item;
40 }
41
menu_item_matches_state(const MenuItem * menu_item,guint state)42 gboolean menu_item_matches_state(const MenuItem *menu_item, guint state)
43 {
44 return (menu_item->state & DS_BASICS & state) &&
45 (menu_item->state & DS_EXTRAS) == (menu_item->state & DS_EXTRAS & state);
46 }
47
menu_item_execute(const MenuInfo * menu_info,const MenuItem * menu_item,gboolean beep)48 void menu_item_execute(const MenuInfo *menu_info, const MenuItem *menu_item, gboolean beep)
49 {
50 guint state = debug_state() | menu_info->extra_state();
51
52 if (!menu_item->state || menu_item_matches_state(menu_item, state))
53 menu_item->callback(menu_item);
54 else if (beep)
55 plugin_beep();
56 }
57
58 static gboolean block_execute = FALSE;
59
menu_item_set_active(const MenuItem * menu_item,gboolean active)60 void menu_item_set_active(const MenuItem *menu_item, gboolean active)
61 {
62 block_execute = TRUE;
63 gtk_check_menu_item_set_active(GTK_CHECK_MENU_ITEM(menu_item->widget), active);
64 block_execute = FALSE;
65 }
66
menu_insert_delete(const GdkEventKey * event,const MenuInfo * menu_info,const char * insert_name,const char * delete_name)67 gboolean menu_insert_delete(const GdkEventKey *event, const MenuInfo *menu_info,
68 const char *insert_name, const char *delete_name)
69 {
70 const char *name;
71
72 if (event->keyval == GDK_Insert || event->keyval == GDK_KP_Insert)
73 name = insert_name;
74 else if (event->keyval == GDK_Delete || event->keyval == GDK_KP_Delete)
75 name = delete_name;
76 else
77 return FALSE;
78
79 menu_item_execute(menu_info, menu_item_find(menu_info->items, name), FALSE);
80 return TRUE;
81 }
82
menu_shift_button_release(GtkWidget * widget,GdkEventButton * event,GtkWidget * menu,void (* action)(const MenuItem * menu_item))83 void menu_shift_button_release(GtkWidget *widget, GdkEventButton *event, GtkWidget *menu,
84 void (*action)(const MenuItem *menu_item))
85 {
86 if (event->state & GDK_SHIFT_MASK)
87 {
88 gtk_menu_popdown(GTK_MENU(menu));
89 action(NULL);
90 }
91 else
92 utils_handle_button_release(widget, event);
93 }
94
on_menu_item_activate(GtkMenuItem * menuitem,MenuInfo * menu_info)95 static void on_menu_item_activate(GtkMenuItem *menuitem, MenuInfo *menu_info)
96 {
97 if (!block_execute)
98 {
99 const MenuItem *menu_item;
100 GtkWidget *widget = GTK_WIDGET(menuitem);
101
102 for (menu_item = menu_info->items; menu_item->widget; menu_item++)
103 if (menu_item->widget == widget)
104 break;
105
106 g_assert(menu_item->widget);
107
108 if (!GTK_IS_RADIO_MENU_ITEM(menuitem) ||
109 gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(menuitem)))
110 {
111 menu_item_execute(menu_info, menu_item, TRUE);
112 }
113 }
114 }
115
116 static MenuInfo *active_menu = NULL;
117
update_active_menu(guint state)118 static void update_active_menu(guint state)
119 {
120 state |= active_menu->extra_state();
121
122 if (state != active_menu->last_state)
123 {
124 const MenuItem *menu_item;
125
126 for (menu_item = active_menu->items; menu_item->name; menu_item++)
127 {
128 if (menu_item->state)
129 {
130 gtk_widget_set_sensitive(menu_item->widget,
131 menu_item_matches_state(menu_item, state));
132 }
133 }
134
135 active_menu->last_state = state;
136 }
137 }
138
on_menu_show(G_GNUC_UNUSED GtkWidget * widget,MenuInfo * menu_info)139 static void on_menu_show(G_GNUC_UNUSED GtkWidget *widget, MenuInfo *menu_info)
140 {
141 active_menu = menu_info;
142 update_active_menu(debug_state());
143 }
144
on_menu_hide(G_GNUC_UNUSED GtkWidget * widget,G_GNUC_UNUSED gpointer gdata)145 static void on_menu_hide(G_GNUC_UNUSED GtkWidget *widget, G_GNUC_UNUSED gpointer gdata)
146 {
147 active_menu = NULL;
148 }
149
on_button_3_press(GtkWidget * widget,GdkEventButton * event,GtkMenu * menu)150 static gboolean on_button_3_press(GtkWidget *widget, GdkEventButton *event, GtkMenu *menu)
151 {
152 if (event->button == 3)
153 {
154 utils_handle_button_press(widget, event);
155 gtk_menu_popup(menu, NULL, NULL, NULL, NULL, event->button, event->time);
156 return TRUE;
157 }
158
159 return FALSE;
160 }
161
menu_connect(const char * name,MenuInfo * menu_info,GtkWidget * widget)162 GtkWidget *menu_connect(const char *name, MenuInfo *menu_info, GtkWidget *widget)
163 {
164 MenuItem *menu_item;
165 GtkWidget *menu = get_widget(name);
166
167 g_signal_connect(menu, "show", G_CALLBACK(on_menu_show), menu_info);
168 g_signal_connect(menu, "hide", G_CALLBACK(on_menu_hide), NULL);
169
170 for (menu_item = menu_info->items; menu_item->name; menu_item++)
171 {
172 menu_item->widget = get_widget(menu_item->name);
173
174 g_signal_connect(menu_item->widget,
175 GTK_IS_CHECK_MENU_ITEM(menu_item->widget) ? "toggled" : "activate",
176 G_CALLBACK(on_menu_item_activate), menu_info);
177 }
178
179 if (widget)
180 g_signal_connect(widget, "button-press-event", G_CALLBACK(on_button_3_press), menu);
181
182 return menu;
183 }
184
on_selection_changed(G_GNUC_UNUSED GtkTreeSelection * selection,GtkWidget * menu)185 static void on_selection_changed(G_GNUC_UNUSED GtkTreeSelection *selection, GtkWidget *menu)
186 {
187 if (gtk_widget_get_visible(menu))
188 gtk_menu_popdown(GTK_MENU(menu));
189 }
190
menu_select(const char * name,MenuInfo * menu_info,GtkTreeSelection * selection)191 GtkWidget *menu_select(const char *name, MenuInfo *menu_info, GtkTreeSelection *selection)
192 {
193 GtkTreeView *tree = gtk_tree_selection_get_tree_view(selection);
194 GtkWidget *menu = menu_connect(name, menu_info, GTK_WIDGET(tree));
195
196 g_signal_connect(selection, "changed", G_CALLBACK(on_selection_changed), menu);
197 return menu;
198 }
199
menu_mode_display(GtkTreeSelection * selection,const MenuItem * menu_item,gint column)200 void menu_mode_display(GtkTreeSelection *selection, const MenuItem *menu_item, gint column)
201 {
202 GtkTreeModel *model;
203 GtkTreeIter iter;
204 gint mode;
205
206 if (gtk_tree_selection_get_selected(selection, &model, &iter))
207 {
208 gtk_tree_model_get(model, &iter, column, &mode, -1);
209 menu_item_set_active(menu_item + mode + 1, TRUE);
210 }
211 }
212
menu_mode_update_iter(ScpTreeStore * store,GtkTreeIter * iter,gint new_mode,gboolean hbit)213 static void menu_mode_update_iter(ScpTreeStore *store, GtkTreeIter *iter, gint new_mode,
214 gboolean hbit)
215 {
216 gint hb_mode, mr_mode;
217 const char *value;
218 gchar *display;
219
220 scp_tree_store_get(store, iter, COLUMN_VALUE, &value, COLUMN_HB_MODE, &hb_mode,
221 COLUMN_MR_MODE, &mr_mode, -1);
222
223 if (hbit)
224 hb_mode = new_mode;
225 else
226 mr_mode = new_mode;
227
228 display = parse_get_display_from_7bit(value, hb_mode, mr_mode);
229 scp_tree_store_set(store, iter, COLUMN_HB_MODE, hb_mode, COLUMN_MR_MODE, mr_mode,
230 value ? COLUMN_DISPLAY : -1, display, -1);
231 g_free(display);
232 }
233
menu_mode_update(GtkTreeSelection * selection,gint new_mode,gboolean hbit)234 void menu_mode_update(GtkTreeSelection *selection, gint new_mode, gboolean hbit)
235 {
236 ScpTreeStore *store;
237 GtkTreeIter iter;
238 const char *name;
239
240 if (scp_tree_selection_get_selected(selection, &store, &iter))
241 {
242 scp_tree_store_get(store, &iter, COLUMN_NAME, &name, -1);
243 menu_mode_update_iter(store, &iter, new_mode, hbit);
244 parse_mode_update(name, hbit ? MODE_HBIT : MODE_MEMBER, new_mode);
245
246 if (hbit)
247 {
248 char *reverse = parse_mode_reentry(name);
249
250 if (store_find(store, &iter, COLUMN_NAME, reverse))
251 menu_mode_update_iter(store, &iter, new_mode, TRUE);
252 g_free(reverse);
253 }
254 }
255 }
256
menu_mber_display(GtkTreeSelection * selection,const MenuItem * menu_item)257 void menu_mber_display(GtkTreeSelection *selection, const MenuItem *menu_item)
258 {
259 GtkTreeModel *model;
260 GtkTreeIter iter;
261
262 if (gtk_tree_selection_get_selected(selection, &model, &iter))
263 {
264 GtkCheckMenuItem *item = GTK_CHECK_MENU_ITEM(menu_item->widget);
265 gint mr_mode;
266
267 gtk_tree_model_get(model, &iter, COLUMN_MR_MODE, &mr_mode, -1);
268
269 if (mr_mode == MR_DEFAULT)
270 gtk_check_menu_item_set_inconsistent(item, TRUE);
271 else
272 {
273 gtk_check_menu_item_set_inconsistent(item, FALSE);
274 menu_item_set_active(menu_item, mr_mode);
275 }
276 }
277 }
278
menu_mber_update(GtkTreeSelection * selection,const MenuItem * menu_item)279 void menu_mber_update(GtkTreeSelection *selection, const MenuItem *menu_item)
280 {
281 GtkCheckMenuItem *item = GTK_CHECK_MENU_ITEM(menu_item->widget);
282
283 if (gtk_check_menu_item_get_inconsistent(item))
284 {
285 gtk_check_menu_item_set_inconsistent(item, FALSE);
286 menu_item_set_active(menu_item, !option_member_names);
287 }
288
289 menu_mode_update(selection, gtk_check_menu_item_get_active(item), FALSE);
290 }
291
menu_mber_button_release(GtkTreeSelection * selection,GtkWidget * item,GdkEventButton * event,GtkWidget * menu)292 void menu_mber_button_release(GtkTreeSelection *selection, GtkWidget *item,
293 GdkEventButton *event, GtkWidget *menu)
294 {
295 if (event->state & GDK_SHIFT_MASK)
296 {
297 gtk_check_menu_item_set_inconsistent(GTK_CHECK_MENU_ITEM(item), TRUE);
298 menu_mode_update(selection, MR_DEFAULT, FALSE);
299 gtk_menu_popdown(GTK_MENU(menu));
300 }
301 else
302 utils_handle_button_release(item, event);
303 }
304
menu_copy(GtkTreeSelection * selection,const MenuItem * menu_item)305 void menu_copy(GtkTreeSelection *selection, const MenuItem *menu_item)
306 {
307 ScpTreeStore *store;
308 GtkTreeIter iter;
309 const gchar *name, *display;
310 const char *value;
311 GString *string;
312
313 if (scp_tree_selection_get_selected(selection, &store, &iter))
314 {
315 scp_tree_store_get(store, &iter, COLUMN_NAME, &name, COLUMN_DISPLAY, &display,
316 COLUMN_VALUE, &value, -1);
317 string = g_string_new(name);
318
319 if (value)
320 g_string_append_printf(string, " = %s", display);
321
322 gtk_clipboard_set_text(gtk_widget_get_clipboard(menu_item->widget,
323 GDK_SELECTION_CLIPBOARD), string->str, string->len);
324
325 g_string_free(string, TRUE);
326 }
327 }
328
329 static GtkWidget *modify_dialog;
330 static GtkLabel *modify_value_label;
331 static GtkWidget *modify_value;
332 static GtkTextBuffer *modify_text;
333 static GtkWidget *modify_ok;
334
modify_dialog_update_state(DebugState state)335 static void modify_dialog_update_state(DebugState state)
336 {
337 if (state == DS_INACTIVE)
338 gtk_widget_hide(modify_dialog);
339 else
340 gtk_widget_set_sensitive(modify_ok, (state & DS_SENDABLE) != 0);
341 }
342
menu_evaluate_modify(const gchar * expr,const char * value,const gchar * title,gint hb_mode,gint mr_mode,const char * prefix)343 static void menu_evaluate_modify(const gchar *expr, const char *value, const gchar *title,
344 gint hb_mode, gint mr_mode, const char *prefix)
345 {
346 gchar *display = parse_get_display_from_7bit(value, hb_mode, mr_mode);
347 gchar *text = g_strdup_printf("%s = %s", expr, display ? display : "");
348 GtkTextIter iter;
349
350 g_free(display);
351 gtk_window_set_title(GTK_WINDOW(modify_dialog), title);
352 gtk_widget_grab_focus(modify_value);
353 gtk_text_buffer_set_text(modify_text, text, -1);
354 g_free(text);
355 gtk_text_buffer_get_iter_at_offset(modify_text, &iter, g_utf8_strlen(expr, -1) + 3);
356 gtk_text_buffer_place_cursor(modify_text, &iter);
357 modify_dialog_update_state(debug_state());
358
359 if (gtk_dialog_run(GTK_DIALOG(modify_dialog)) == GTK_RESPONSE_ACCEPT)
360 {
361 text = utils_text_buffer_get_text(modify_text, -1);
362 utils_strchrepl(text, '\n', ' ');
363
364 if (validate_column(text, TRUE))
365 {
366 char *locale = utils_get_locale_from_display(text, hb_mode);
367 debug_send_format(F, "%s-gdb-set var %s", prefix ? prefix : "", locale);
368 g_free(locale);
369 }
370 g_free(text);
371 }
372 }
373
menu_modify(GtkTreeSelection * selection,const MenuItem * menu_item)374 void menu_modify(GtkTreeSelection *selection, const MenuItem *menu_item)
375 {
376 ScpTreeStore *store;
377 GtkTreeIter iter;
378 const gchar *name;
379 const char *value;
380 gint hb_mode;
381
382 if (scp_tree_selection_get_selected(selection, &store, &iter))
383 {
384 scp_tree_store_get(store, &iter, COLUMN_NAME, &name, COLUMN_VALUE, &value, COLUMN_HB_MODE,
385 &hb_mode, -1);
386 menu_evaluate_modify(name, value, _("Modify"), hb_mode, menu_item ? MR_MODIFY : MR_MODSTR,
387 "07");
388 }
389 }
390
menu_inspect(GtkTreeSelection * selection)391 void menu_inspect(GtkTreeSelection *selection)
392 {
393 ScpTreeStore *store;
394 GtkTreeIter iter;
395 const char *name;
396
397 if (scp_tree_selection_get_selected(selection, &store, &iter))
398 {
399 scp_tree_store_get(store, &iter, COLUMN_NAME, &name, -1);
400 inspect_add(name);
401 }
402 }
403
on_menu_display_booleans(const MenuItem * menu_item)404 void on_menu_display_booleans(const MenuItem *menu_item)
405 {
406 gint i, count = GPOINTER_TO_INT(menu_item->gdata);
407
408 for (i = 0; i < count; i++)
409 {
410 menu_item++;
411 menu_item_set_active(menu_item, *(gboolean *) menu_item->gdata);
412 }
413 }
414
on_menu_update_boolean(const MenuItem * menu_item)415 void on_menu_update_boolean(const MenuItem *menu_item)
416 {
417 GtkCheckMenuItem *item = GTK_CHECK_MENU_ITEM(menu_item->widget);
418 *(gboolean *) menu_item->gdata = gtk_check_menu_item_get_active(item);
419 }
420
421 static char *input = NULL;
422 static gint eval_mr_mode;
423 static gint scid_gen = 0;
424
on_menu_evaluate_value(GArray * nodes)425 void on_menu_evaluate_value(GArray *nodes)
426 {
427 if (atoi(parse_grab_token(nodes)) == scid_gen && !gtk_widget_get_visible(modify_dialog))
428 {
429 gchar *expr = utils_get_utf8_from_locale(input);
430
431 menu_evaluate_modify(expr, parse_lead_value(nodes), "Evaluate/Modify",
432 parse_mode_get(input, MODE_HBIT), eval_mr_mode, NULL);
433 g_free(expr);
434 }
435 }
436
on_popup_evaluate(const MenuItem * menu_item)437 static void on_popup_evaluate(const MenuItem *menu_item)
438 {
439 gchar *expr = utils_get_default_selection();
440
441 g_free(input);
442 eval_mr_mode = menu_item ? MR_MODIFY : MR_MODSTR;
443 input = debug_send_evaluate('8', ++scid_gen, expr);
444 g_free(expr);
445 }
446
on_popup_watch(G_GNUC_UNUSED const MenuItem * menu_item)447 static void on_popup_watch(G_GNUC_UNUSED const MenuItem *menu_item)
448 {
449 gchar *expr = utils_get_default_selection();
450 watch_add(expr);
451 g_free(expr);
452 }
453
on_popup_inspect(G_GNUC_UNUSED const MenuItem * menu_item)454 static void on_popup_inspect(G_GNUC_UNUSED const MenuItem *menu_item)
455 {
456 gchar *expr = utils_get_default_selection();
457 inspect_add(expr);
458 g_free(expr);
459 }
460
461 #define DS_EVALUATE (DS_SENDABLE | DS_EXTRA_1)
462
463 static MenuItem popup_menu_items[] =
464 {
465 { "popup_evaluate", on_popup_evaluate, DS_EVALUATE, NULL, NULL },
466 { "popup_watch", on_popup_watch, 0, NULL, NULL },
467 { "popup_inspect", on_popup_inspect, DS_NOT_BUSY, NULL, NULL },
468 { NULL, NULL, 0, NULL, NULL }
469 };
470
popup_menu_extra_state(void)471 static guint popup_menu_extra_state(void)
472 {
473 gchar *expr = utils_get_default_selection();
474 g_free(expr);
475 return (expr != NULL) << DS_INDEX_1;
476 }
477
478 static MenuInfo popup_menu_info = { popup_menu_items, popup_menu_extra_state, 0 };
479
480 static guint popup_start;
481
on_popup_key(guint key_id)482 static void on_popup_key(guint key_id)
483 {
484 menu_item_execute(&popup_menu_info, popup_menu_items + key_id - popup_start, FALSE);
485 }
486
on_popup_evaluate_button_release(GtkWidget * widget,GdkEventButton * event,GtkWidget * menu)487 static void on_popup_evaluate_button_release(GtkWidget *widget, GdkEventButton *event,
488 GtkWidget *menu)
489 {
490 menu_shift_button_release(widget, event, menu, on_popup_evaluate);
491 }
492
493 static MenuKey popup_menu_keys[] =
494 {
495 { "evaluate", N_("Evaluate/modify") },
496 { "watch", N_("Watch expression") },
497 { "inspect", N_("Inspect variable") }
498 };
499
menu_set_popup_keybindings(GeanyKeyGroup * scope_key_group,guint item)500 void menu_set_popup_keybindings(GeanyKeyGroup *scope_key_group, guint item)
501 {
502 const MenuKey *menu_key = popup_menu_keys;
503 const MenuItem *menu_item;
504
505 popup_start = item;
506
507 for (menu_item = popup_menu_items; menu_item->name; menu_item++, menu_key++, item++)
508 {
509 keybindings_set_item(scope_key_group, item, on_popup_key, 0, 0, menu_key->name,
510 _(menu_key->label), popup_menu_items[item].widget);
511 }
512 }
513
menu_clear(void)514 void menu_clear(void)
515 {
516 scid_gen = 0;
517 }
518
menu_update_state(DebugState state)519 void menu_update_state(DebugState state)
520 {
521 if (active_menu)
522 update_active_menu(state);
523
524 if (gtk_widget_get_visible(modify_dialog))
525 modify_dialog_update_state(state);
526 }
527
528 static GtkWidget *popup_item;
529
menu_init(void)530 void menu_init(void)
531 {
532 GtkMenuShell *shell = GTK_MENU_SHELL(geany->main_widgets->editor_menu);
533 GList *children = gtk_container_get_children(GTK_CONTAINER(shell));
534 GtkWidget *search2 = ui_lookup_widget(GTK_WIDGET(shell), "search2");
535
536 popup_item = get_widget("popup_item");
537 menu_connect("popup_menu", &popup_menu_info, NULL);
538 g_signal_connect(get_widget("popup_evaluate"), "button-release-event",
539 G_CALLBACK(on_popup_evaluate_button_release), geany->main_widgets->editor_menu);
540
541 if (search2)
542 gtk_menu_shell_insert(shell, popup_item, g_list_index(children, search2) + 1);
543 else
544 gtk_menu_shell_append(shell, popup_item);
545
546 modify_dialog = dialog_connect("modify_dialog");
547 modify_value_label = GTK_LABEL(get_widget("modify_value_label"));
548 modify_value = get_widget("modify_value");
549 modify_text = gtk_text_view_get_buffer(GTK_TEXT_VIEW(modify_value));
550 modify_ok = get_widget("modify_ok");
551 utils_enter_to_clicked(modify_value, modify_ok);
552 }
553
menu_finalize(void)554 void menu_finalize(void)
555 {
556 gtk_widget_destroy(modify_dialog);
557 gtk_widget_destroy(popup_item);
558 g_free(input);
559 }
560