1 /*
2  * glspi_dlg.c - This file is part of the Lua scripting plugin for the Geany IDE
3  * See the file "geanylua.c" for copyright information.
4  */
5 
6 
7 #include <gdk/gdkkeysyms.h>
8 
9 #define NEED_FAIL_ARG_TYPE
10 #define NEED_FAIL_ELEM_TYPE
11 
12 #include "glspi.h"
13 
14 /* Use newer "generic" dialogs if available */
15 #if ! GTK_CHECK_VERSION(2, 10, 0)
16 #define GTK_MESSAGE_OTHER GTK_MESSAGE_INFO
17 #endif
18 
19 #define DIALOG_FLAGS GTK_DIALOG_DESTROY_WITH_PARENT|GTK_DIALOG_MODAL
20 
21 
22 static GsDlgRunHook glspi_pause_timer = NULL;
23 
24 
do_glspi_dialog_run(lua_State * L,GtkDialog * dialog)25 static gint do_glspi_dialog_run(lua_State *L, GtkDialog *dialog)
26 {
27 	gint rv;
28 	glspi_pause_timer(TRUE, L);
29 	rv=gtk_dialog_run(dialog);
30 	glspi_pause_timer(FALSE, L);
31 	return rv;
32 }
33 
34 #define glspi_dialog_run(d) do_glspi_dialog_run(L,d)
35 
set_dialog_title(lua_State * L,GtkWidget * dialog)36 static void  set_dialog_title(lua_State *L, GtkWidget*dialog) {
37 	const gchar *banner=DEFAULT_BANNER;
38 	lua_getglobal(L, LUA_MODULE_NAME);
39 	if ( lua_istable(L, -1) ) {
40 		lua_pushstring(L,tokenBanner);
41 		lua_gettable(L, -2);
42 		if (lua_isstring(L, -1)) {
43 			banner=lua_tostring(L, -1);
44 		} else {
45 			banner=DEFAULT_BANNER;
46 			lua_getglobal(L, LUA_MODULE_NAME);
47 			lua_pushstring(L,tokenBanner);
48 			lua_pushstring(L,banner);
49 			lua_settable(L, -3);
50 		}
51 	}
52 	gtk_window_set_title(GTK_WINDOW(dialog), banner);
53 }
54 
55 
56 /*
57 	The GtkMessageDialog wants format strings, but we want literals.
58 	So we need to replace all '%' with "%%"
59 */
pct_esc(const gchar * s)60 static gchar*pct_esc(const gchar*s)
61 {
62 	gchar*rv=NULL;
63 	if (s && strchr(s, '%')) {
64 		gchar**a=g_strsplit(s,"%",-1);
65 		rv=g_strjoinv("%%",a);
66 		g_strfreev(a);
67 	}
68 	return rv;
69 }
70 
new_dlg(gint msg_type,gint buttons,const gchar * msg1,const gchar * msg2)71 static GtkWidget*new_dlg(gint msg_type, gint buttons, const gchar*msg1, const gchar*msg2)
72 {
73 	gchar *tmp=pct_esc(msg1);
74 	GtkWidget*rv=gtk_message_dialog_new(GTK_WINDOW(main_widgets->window),
75 										DIALOG_FLAGS, msg_type, buttons, "%s", tmp?tmp:msg1);
76 
77 	if (tmp) {
78 		g_free(tmp);
79 		tmp=NULL;
80 	}
81 	if (msg2) {
82 		tmp=pct_esc(msg2);
83 		gtk_message_dialog_format_secondary_text(GTK_MESSAGE_DIALOG(rv), "%s", tmp?tmp:msg2);
84 		if (tmp) {g_free(tmp);}
85 	}
86 	return rv;
87 }
88 
89 
90 /* Close the list dialog with an OK response if user double-clicks an item */
91 /* FIXME: Make sure the click was really on an item! */
on_tree_clicked(GtkWidget * w,GdkEventButton * e,gpointer p)92 static gboolean on_tree_clicked(GtkWidget *w, GdkEventButton *e, gpointer p)
93 {
94 	if (w && p && e && (GDK_2BUTTON_PRESS==e->type) && (1==e->button)) {
95 		gtk_dialog_response(GTK_DIALOG(p), GTK_RESPONSE_OK);
96 	}
97 	return FALSE;
98 }
99 
100 
101 
102 /* Close the list dialog with an OK response if user presses [Enter] in list */
on_tree_key_release(GtkWidget * w,GdkEventKey * e,gpointer p)103 static gboolean on_tree_key_release(GtkWidget *w, GdkEventKey *e, gpointer p)
104 {
105 	if (w && p && e && (GDK_Return==e->keyval) ) {
106 		gtk_dialog_response(GTK_DIALOG(p), GTK_RESPONSE_OK);
107 	}
108 	return FALSE;
109 }
110 
111 
glspi_choose(lua_State * L)112 static gint glspi_choose(lua_State* L)
113 {
114 	const gchar *arg1=NULL;
115 	gint i, n;
116 	GtkResponseType rv;
117 	GtkWidget*dialog, *ok_btn, *tree, *scroll;
118 	GtkListStore *store;
119 	GtkTreeIter iter;
120 	GtkTreeSelection *select;
121 
122 	if ( (lua_gettop(L)!=2) || (!lua_istable(L,2)) ) {
123 		return FAIL_TABLE_ARG(2);
124 	}
125 
126 	if (!lua_isnil(L, 1)) {
127 		if (!lua_isstring(L, 1))	{ return FAIL_STRING_ARG(1); }
128 			arg1=lua_tostring(L, 1);
129 	}
130 
131 	n=lua_objlen(L,2);
132 	for (i=1;i<=n; i++) {
133 		lua_rawgeti(L,2,i);
134 		if (!lua_isstring(L, -1)) {
135 			return glspi_fail_elem_type(L, __FUNCTION__, 2, i, "string");
136 		}
137 		lua_pop(L, 1);
138 	}
139 	store=gtk_list_store_new(1, G_TYPE_STRING);
140 	for (i=1;i<=n; i++) {
141 		lua_rawgeti(L,2,i);
142 		gtk_list_store_append(store, &iter);
143 		gtk_list_store_set(store, &iter, 0, lua_tostring(L, -1), -1);
144 		lua_pop(L, 1);
145 	}
146 	dialog = new_dlg(GTK_MESSAGE_OTHER, GTK_BUTTONS_NONE, arg1, NULL);
147 	ok_btn=gtk_dialog_add_button(GTK_DIALOG(dialog),
148 			GTK_STOCK_OK, GTK_RESPONSE_OK);
149 	gtk_dialog_add_button(GTK_DIALOG(dialog),
150 			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
151 	gtk_widget_grab_default(ok_btn);
152 	set_dialog_title(L,dialog);
153 
154 	tree = gtk_tree_view_new_with_model(GTK_TREE_MODEL(store));
155 	gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(tree), TRUE);
156 	gtk_tree_view_set_headers_visible(GTK_TREE_VIEW(tree), FALSE);
157 	gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(tree),
158 				-1, NULL, gtk_cell_renderer_text_new(), "text", 0, NULL);
159 
160 	select = gtk_tree_view_get_selection (GTK_TREE_VIEW (tree));
161 	gtk_tree_selection_set_mode(select, GTK_SELECTION_SINGLE);
162 
163 	scroll=gtk_scrolled_window_new(NULL, NULL);
164 	gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll),
165 		GTK_POLICY_AUTOMATIC,  GTK_POLICY_AUTOMATIC);
166 	gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))),scroll);
167 	gtk_container_add(GTK_CONTAINER(scroll),tree);
168 
169 	gtk_widget_set_size_request(tree, 320, 240);
170 	gtk_widget_show_all(dialog);
171 	gtk_window_set_resizable(GTK_WINDOW(dialog), TRUE);
172 
173 	g_signal_connect(G_OBJECT(tree), "button-press-event",
174 		G_CALLBACK(on_tree_clicked), dialog);
175 	g_signal_connect(G_OBJECT(tree), "key-release-event",
176 		G_CALLBACK(on_tree_key_release), dialog);
177 
178 	rv=glspi_dialog_run(GTK_DIALOG(dialog));
179 
180 	if (GTK_RESPONSE_OK == rv ) {
181 		gchar *txt=NULL;
182 		GtkTreeModel *model;
183 		if (gtk_tree_selection_get_selected(select, &model, &iter)) {
184 			gtk_tree_model_get(model, &iter, 0, &txt, -1);
185 		}
186 		if (txt) {
187 			lua_pushstring(L, txt);
188 			g_free(txt);
189 		} else {
190 			lua_pushnil(L);
191 		}
192 	} else {
193 		lua_pushnil(L);
194 	}
195 	gtk_widget_destroy(dialog);
196 	return 1;
197 }
198 
199 
200 
201 /* Display a simple message dialog */
glspi_message(lua_State * L)202 static gint glspi_message(lua_State* L)
203 {
204 	const gchar *arg1=NULL;
205 	const gchar *arg2=NULL;
206 	GtkWidget *dialog;
207 	switch (lua_gettop(L)) {
208 	case 0: break;
209 	case 2:
210 		if (!lua_isstring(L, 2))	{ return FAIL_STRING_ARG(2); }
211 		arg2=lua_tostring(L, 2);
212 	default:
213 		if (!lua_isstring(L, 1))	{ return FAIL_STRING_ARG(1); }
214 		arg1=lua_tostring(L, 1);
215 	}
216 	dialog = new_dlg(GTK_MESSAGE_OTHER, GTK_BUTTONS_OK, arg1, arg2);
217 	set_dialog_title(L,dialog);
218 	glspi_dialog_run(GTK_DIALOG(dialog));
219 	gtk_widget_destroy(dialog);
220 	return 0;
221 }
222 
223 
224 
225 
226 /* Display a yes-or-no dialog */
glspi_confirm(lua_State * L)227 static gint glspi_confirm(lua_State* L)
228 {
229 	const gchar *arg1=NULL;
230 	const gchar *arg2=NULL;
231 	GtkWidget *dialog, *yes_btn, *no_btn;
232 	GtkResponseType dv, rv;
233 
234 	if (lua_gettop(L)<3) {	return FAIL_BOOL_ARG(3);	}
235 	if (!lua_isboolean(L,3)) { return FAIL_BOOL_ARG(3); }
236 	dv=lua_toboolean(L,3)?GTK_RESPONSE_YES:GTK_RESPONSE_NO;
237 	if (!lua_isnil(L, 2)) {
238 		if (!lua_isstring(L, 2))	{ return FAIL_STRING_ARG(2); }
239 		arg2=lua_tostring(L, 2);
240 	}
241 	if (!lua_isnil(L, 1)) {
242 		if (!lua_isstring(L, 1))	{ return FAIL_STRING_ARG(1); }
243 		arg1=lua_tostring(L, 1);
244 	}
245 	dialog = new_dlg(GTK_MESSAGE_OTHER, GTK_BUTTONS_NONE, arg1,arg2);
246 	yes_btn=gtk_dialog_add_button(GTK_DIALOG(dialog),
247 		GTK_STOCK_YES, GTK_RESPONSE_YES);
248 	no_btn=gtk_dialog_add_button(GTK_DIALOG(dialog),
249 		GTK_STOCK_NO, GTK_RESPONSE_NO);
250 	gtk_widget_grab_default(dv==GTK_RESPONSE_YES?yes_btn:no_btn);
251 	/* Where I come from, we ask "yes-or-no?"
252 	 *  who the hell ever asks "no-or-yes?" ??? */
253 	/* It's probably better to use descriptive names for button text -ntrel */
254 	gtk_dialog_set_alternative_button_order(GTK_DIALOG(dialog),
255 		GTK_RESPONSE_YES,GTK_RESPONSE_NO );
256 	set_dialog_title(L,dialog);
257 	rv=glspi_dialog_run(GTK_DIALOG(dialog));
258 	gtk_widget_destroy(dialog);
259 	if ((rv!=GTK_RESPONSE_YES)&&(rv!=GTK_RESPONSE_NO)) {rv=dv;}
260 	lua_pushboolean(L, (GTK_RESPONSE_YES==rv));
261 	return 1;
262 }
263 
264 
265 
266 /* Display a dialog to prompt user for some text input */
glspi_input(lua_State * L)267 static gint glspi_input(lua_State* L)
268 {
269 	const gchar *arg1=NULL;
270 	const gchar *arg2=NULL;
271 	GtkResponseType rv;
272 	GtkWidget*dialog, *entry, *ok_btn;
273 
274 	switch (lua_gettop(L)) {
275 		case 0: break;
276 		case 2:
277 			if (!lua_isnil(L, 2)) {
278 				if (!lua_isstring(L, 2))	{ return FAIL_STRING_ARG(2); }
279 				arg2=lua_tostring(L, 2);
280 			}
281 		default:
282 			if (!lua_isnil(L, 1)) {
283 				if (!lua_isstring(L, 1))	{ return FAIL_STRING_ARG(1); }
284 				arg1=lua_tostring(L, 1);
285 			}
286 	}
287 
288 
289 	dialog = new_dlg(GTK_MESSAGE_OTHER, GTK_BUTTONS_NONE, arg1, NULL);
290 	ok_btn=gtk_dialog_add_button(GTK_DIALOG(dialog),
291 			GTK_STOCK_OK, GTK_RESPONSE_OK);
292 	gtk_dialog_add_button(GTK_DIALOG(dialog),
293 			GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL);
294 	gtk_widget_grab_default(ok_btn);
295 	entry=gtk_entry_new();
296 	if (arg2) { gtk_entry_set_text(GTK_ENTRY(entry), arg2); }
297 	gtk_container_add(GTK_CONTAINER(gtk_dialog_get_content_area(GTK_DIALOG(dialog))), entry);
298 	gtk_entry_set_activates_default(GTK_ENTRY(entry), TRUE);
299 	set_dialog_title(L,dialog);
300 	gtk_widget_set_size_request(entry, 320, -1);
301 	gtk_widget_show_all(dialog);
302 	gtk_window_set_resizable(GTK_WINDOW(dialog), TRUE);
303 
304 	rv=glspi_dialog_run(GTK_DIALOG(dialog));
305 
306 	if (GTK_RESPONSE_OK == rv ) {
307 		const gchar *s=gtk_entry_get_text(GTK_ENTRY(entry));
308 		if (s) {
309 			lua_pushstring(L, s);
310 		} else {
311 			lua_pushnil(L);
312 		}
313 	} else {
314 		lua_pushnil(L);
315 	}
316 	gtk_widget_destroy(dialog);
317 	return 1;
318 }
319 
320 
321 
322 #define NEED_OVERWRITE_PROMPT !GTK_CHECK_VERSION(2, 8, 0)
323 
324 
325 #if NEED_OVERWRITE_PROMPT
on_file_dlg_response(GtkWidget * w,gint resp,gpointer ud)326 static void on_file_dlg_response(GtkWidget*w, gint resp, gpointer ud)
327 {
328 	gboolean *accepted=ud;
329 	*accepted=TRUE;
330 	if ( resp == GTK_RESPONSE_ACCEPT) {
331 		gchar*fn=gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(w));
332 		if (fn) {
333 			if (g_file_test(fn, G_FILE_TEST_EXISTS)){
334 				GtkWidget*dlg;
335 				dlg=new_dlg(GTK_MESSAGE_QUESTION,GTK_BUTTONS_YES_NO,
336 										_("File exists"),_("Do you want to overwrite it?"));
337 				gtk_window_set_title(GTK_WINDOW(dlg), _("confirm"));
338 				if (gtk_dialog_run(GTK_DIALOG(dlg)) != GTK_RESPONSE_YES) {
339 					*accepted=FALSE;
340 				}
341 				gtk_widget_destroy(dlg);
342 			}
343 			g_free(fn);
344 		}
345 	}
346 }
347 #endif
348 
349 
create_file_filter(lua_State * L,GtkFileChooser * dlg,const gchar * mask)350 static gboolean create_file_filter(lua_State* L, GtkFileChooser*dlg, const gchar*mask)
351 {
352 	gchar **patterns=NULL;
353 	if (mask && *mask) {
354 		patterns=g_strsplit(mask, "|", 64);
355 		if (patterns) {
356 			gchar**pattern=patterns;
357 			if (g_strv_length(patterns)%2) {
358 				g_strfreev(patterns);
359 				return FALSE;
360 			}
361 			while (*pattern) {
362 				if (!**pattern) {
363 					g_strfreev(patterns);
364 					return FALSE;
365 				}
366 				pattern++;
367 			}
368 			pattern=patterns;
369 			while (*pattern) {
370 				gchar*maskname,*wildcard;
371 				maskname=*pattern;
372 				pattern++;
373 				wildcard=*pattern;
374 				if (maskname && wildcard) {
375 					GtkFileFilter*filter=gtk_file_filter_new();
376 					gchar*wc=wildcard;
377 					gtk_file_filter_set_name(filter, maskname);
378 					do {
379 						gchar*sem=strchr(wc,';');
380 						if (sem) {
381 							*sem='\0';
382 							gtk_file_filter_add_pattern(filter,wc);
383 							wc++;
384 						} else {
385 							gtk_file_filter_add_pattern(filter,wc);
386 							break;
387 						}
388 					} while (1);
389 					gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(dlg),filter);
390 				}
391 				pattern++;
392 			}
393 		}
394 	}
395 	if (patterns) { g_strfreev(patterns); }
396 	return TRUE;
397 }
398 
399 
file_dlg(lua_State * L,gboolean save,const gchar * path,const gchar * mask,const gchar * name)400 static gchar *file_dlg(lua_State* L, gboolean save, const gchar *path,	const gchar *mask, const gchar *name)
401 {
402 	gchar *rv=NULL;
403 	gchar *fullname = NULL;
404 	GtkWidget*dlg=NULL;
405 #if NEED_OVERWRITE_PROMPT
406 	gboolean accepted=FALSE;
407 #endif
408 	gint resp=GTK_RESPONSE_CANCEL;
409 	if (save) {
410 		dlg=gtk_file_chooser_dialog_new(_("Save file"),
411 					GTK_WINDOW(main_widgets->window), GTK_FILE_CHOOSER_ACTION_SAVE,
412 					GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
413 					GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,	NULL);
414 #if NEED_OVERWRITE_PROMPT
415 		g_signal_connect(G_OBJECT(dlg),"response",G_CALLBACK(on_file_dlg_response),&accepted);
416 #else
417 		gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dlg), TRUE);
418 #endif
419 	} else {
420 		dlg=gtk_file_chooser_dialog_new(_("Open file"),
421 					GTK_WINDOW(main_widgets->window),	GTK_FILE_CHOOSER_ACTION_OPEN,
422 					GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
423 					GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,	NULL);
424 	}
425 	if (name && *name) {
426 		if (g_path_is_absolute(name)) {
427 			fullname=g_strdup(name);
428 		} else if (path) {
429 			fullname=g_build_filename(path,name,NULL);
430 		}
431 		gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(dlg), fullname);
432 		if (save) { gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dlg), name); }
433 	}
434 	if (path && *path) {
435 		gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(dlg), path);
436 	}
437 	if (!create_file_filter(L, GTK_FILE_CHOOSER(dlg), mask)) {
438 		lua_pushfstring(L, _("Error in module \"%s\" at function pickfile():\n"
439 			"failed to parse filter string at argument #3.\n"),
440 			LUA_MODULE_NAME);
441 		lua_error(L);
442 		g_free(fullname);
443 		return NULL;
444 	}
445 
446 #if NEED_OVERWRITE_PROMPT
447 	glspi_pause_timer(TRUE,L);
448 	while (!accepted) {
449 		resp=gtk_dialog_run(GTK_DIALOG(dlg));
450 		if (!save) { accepted=TRUE; }
451 	}
452 	glspi_pause_timer(FALSE,L);
453 #else
454 		resp=glspi_dialog_run(GTK_DIALOG(dlg));
455 #endif
456 	if (resp == GTK_RESPONSE_ACCEPT) {
457 		rv=gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dlg));
458 	}
459 	gtk_widget_destroy(dlg);
460 	g_free(fullname);
461 	return rv;
462 }
463 
464 
465 
466 /*
467 mode (open or save?)
468 path
469 filter
470 
471 */
472 
glspi_pickfile(lua_State * L)473 static gint glspi_pickfile(lua_State* L)
474 {
475 	gboolean save = FALSE;
476 	gchar *path = NULL;
477 	const gchar *mask = NULL;
478 	gchar *name = NULL;
479 	gchar*result;
480 	gint argc=lua_gettop(L);
481 
482 	if (argc >= 1) {
483 		if  (lua_isstring(L,1))	{
484 			const gchar*tmp=lua_tostring(L,1);
485 			if (g_ascii_strcasecmp(tmp,"save")==0) {
486 				save=TRUE;
487 			} else
488 			if ( (*tmp != '\0') && (g_ascii_strcasecmp(tmp,"open")!=0) ) {
489 				lua_pushfstring(L, _("Error in module \"%s\" at function %s():\n"
490 							"expected string \"open\" or \"save\" for argument #1.\n"),
491 							LUA_MODULE_NAME, &__FUNCTION__[6]);
492 				lua_error(L);
493 				return 0;
494 			}
495 		} else if (!lua_isnil(L,1)) {
496 			FAIL_STRING_ARG(1);
497 			return 0;
498 		}
499 	}
500 	if (argc >= 2) {
501 		if (lua_isstring(L,2)) {
502 			path=g_strdup(lua_tostring(L,2));
503 		} else if (!lua_isnil(L,2)) {
504 			FAIL_STRING_ARG(2);
505 			return 0;
506 		}
507 	}
508 	if (argc >= 3 ) {
509 		if (lua_isstring(L,3)) {
510 			mask=lua_tostring(L,3);
511 		} else if (!lua_isnil(L,3)) {
512 			FAIL_STRING_ARG(3);
513 			return 0;
514 		}
515 	}
516 
517 	if (path && *path && !g_file_test(path,G_FILE_TEST_IS_DIR) ){
518 		gchar*sep=strrchr(path, G_DIR_SEPARATOR);
519 		if (sep) {
520 			name=sep+1;
521 			*sep='\0';
522 		} else {
523 			name=path;
524 			path=NULL;
525 		}
526 	}
527 
528 	result=file_dlg(L,save,path,mask,name);
529 	if (path) { g_free(path); } else if ( name ) { g_free(name); };
530 	if (result) {
531 		lua_pushstring(L,result);
532 		g_free(result);
533 	} else {
534 		lua_pushnil(L);
535 	}
536 	return 1;
537 }
538 
539 
540 
541 
542 
543 static const struct luaL_reg glspi_dlg_funcs[] = {
544 	{"choose",   glspi_choose},
545 	{"confirm",  glspi_confirm},
546 	{"input",    glspi_input},
547 	{"message",  glspi_message},
548 	{"pickfile", glspi_pickfile},
549 	{NULL,NULL}
550 };
551 
552 
553 
554 
glspi_init_dlg_funcs(lua_State * L,GsDlgRunHook hook)555 void glspi_init_dlg_funcs(lua_State *L, GsDlgRunHook hook) {
556 	glspi_pause_timer = hook;
557 	luaL_register(L, NULL,glspi_dlg_funcs);
558 }
559