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