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