Mercurial > geeqie.yaz
annotate src/ui_tabcomp.c @ 709:f4fc939d327b
Build path using g_build_filename() then calculate its length.
author | zas_ |
---|---|
date | Tue, 20 May 2008 23:41:13 +0000 |
parents | fbebf5cf4a55 |
children | d8a88f279aca |
rev | line source |
---|---|
9 | 1 /* |
2 * (SLIK) SimpLIstic sKin functions | |
69
31759d770628
Fri Oct 13 10:27:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
12
diff
changeset
|
3 * (C) 2006 John Ellis |
475 | 4 * Copyright (C) 2008 The Geeqie Team |
9 | 5 * |
6 * Author: John Ellis | |
7 * | |
8 * This software is released under the GNU General Public License (GNU GPL). | |
9 * Please read the included file COPYING for more information. | |
10 * This software comes with no warranty of any kind, use at your own risk! | |
11 */ | |
12 | |
13 #ifdef HAVE_CONFIG_H | |
14 # include "config.h" | |
15 #endif | |
16 #include "intl.h" | |
17 | |
18 #include <stdio.h> | |
19 #include <stdlib.h> | |
20 #include <string.h> | |
21 #include <unistd.h> | |
22 #include <sys/types.h> | |
23 #include <dirent.h> | |
24 | |
25 #include <gdk/gdk.h> | |
26 #include <gtk/gtk.h> | |
27 #include <gdk-pixbuf/gdk-pixbuf.h> | |
28 | |
281 | 29 #include "main.h" |
9 | 30 #include "ui_tabcomp.h" |
31 | |
32 #include "ui_bookmark.h" | |
33 #include "ui_fileops.h" | |
34 #include "ui_spinner.h" | |
35 #include "ui_utildlg.h" | |
36 | |
37 #include <gdk/gdkkeysyms.h> /* for key values */ | |
38 | |
39 | |
40 /* define this to enable a pop-up menu that shows possible matches | |
41 * #define TAB_COMPLETION_ENABLE_POPUP_MENU | |
42 */ | |
43 #define TAB_COMPLETION_ENABLE_POPUP_MENU 1 | |
44 #define TAB_COMP_POPUP_MAX 500 | |
45 | |
46 #ifdef TAB_COMPLETION_ENABLE_POPUP_MENU | |
47 #include "ui_menu.h" | |
48 #endif | |
49 | |
50 | |
51 /* ---------------------------------------------------------------- | |
52 Tab completion routines, can be connected to any gtkentry widget | |
53 using the tab_completion_add_to_entry() function. | |
54 Use remove_trailing_slash() to strip the trailing '/'. | |
55 ----------------------------------------------------------------*/ | |
56 | |
57 typedef struct _TabCompData TabCompData; | |
58 struct _TabCompData | |
59 { | |
60 GtkWidget *entry; | |
61 gchar *dir_path; | |
62 GList *file_list; | |
63 void (*enter_func)(const gchar *, gpointer); | |
64 void (*tab_func)(const gchar *, gpointer); | |
65 gpointer enter_data; | |
66 gpointer tab_data; | |
67 | |
68 GtkWidget *combo; | |
69 gint has_history; | |
70 gchar *history_key; | |
71 gint history_levels; | |
72 | |
73 FileDialog *fd; | |
74 gchar *fd_title; | |
75 gint fd_folders_only; | |
76 GtkWidget *fd_button; | |
77 }; | |
78 | |
79 | |
80 static void tab_completion_select_show(TabCompData *td); | |
81 | |
82 static void tab_completion_free_list(TabCompData *td) | |
83 { | |
84 GList *list; | |
85 | |
86 g_free(td->dir_path); | |
87 td->dir_path = NULL; | |
88 | |
89 list = td->file_list; | |
90 | |
516 | 91 while (list) |
9 | 92 { |
93 g_free(list->data); | |
94 list = list->next; | |
95 } | |
96 | |
97 g_list_free(td->file_list); | |
98 td->file_list = NULL; | |
99 } | |
100 | |
101 static void tab_completion_read_dir(TabCompData *td, const gchar *path) | |
102 { | |
442 | 103 DIR *dp; |
104 struct dirent *dir; | |
105 GList *list = NULL; | |
9 | 106 gchar *pathl; |
107 | |
108 tab_completion_free_list(td); | |
109 | |
110 pathl = path_from_utf8(path); | |
442 | 111 dp = opendir(pathl); |
9 | 112 g_free(pathl); |
113 if (!dp) | |
442 | 114 { |
115 /* dir not found */ | |
116 return; | |
117 } | |
118 while ((dir = readdir(dp)) != NULL) | |
119 { | |
69
31759d770628
Fri Oct 13 10:27:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
12
diff
changeset
|
120 gchar *name = dir->d_name; |
31759d770628
Fri Oct 13 10:27:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
12
diff
changeset
|
121 if (strcmp(name, ".") != 0 && strcmp(name, "..") != 0) |
31759d770628
Fri Oct 13 10:27:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
12
diff
changeset
|
122 { |
31759d770628
Fri Oct 13 10:27:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
12
diff
changeset
|
123 list = g_list_prepend(list, path_to_utf8(name)); |
31759d770628
Fri Oct 13 10:27:22 2006 John Ellis <johne@verizon.net>
gqview
parents:
12
diff
changeset
|
124 } |
9 | 125 } |
442 | 126 closedir(dp); |
9 | 127 |
128 td->dir_path = g_strdup(path); | |
129 td->file_list = list; | |
130 } | |
131 | |
132 static void tab_completion_destroy(GtkWidget *widget, gpointer data) | |
133 { | |
134 TabCompData *td = data; | |
135 | |
136 tab_completion_free_list(td); | |
137 g_free(td->history_key); | |
138 | |
139 if (td->fd) file_dialog_close(td->fd); | |
140 g_free(td->fd_title); | |
141 | |
142 g_free(td); | |
143 } | |
144 | |
145 static gint tab_completion_emit_enter_signal(TabCompData *td) | |
146 { | |
147 gchar *text; | |
148 if (!td->enter_func) return FALSE; | |
149 | |
150 text = g_strdup(gtk_entry_get_text(GTK_ENTRY(td->entry))); | |
151 | |
152 if (text[0] == '~') | |
153 { | |
154 gchar *t = text; | |
155 text = g_strconcat(homedir(), t + 1, NULL); | |
156 g_free(t); | |
157 } | |
158 | |
159 td->enter_func(text, td->enter_data); | |
160 g_free(text); | |
161 | |
162 return TRUE; | |
163 } | |
164 | |
165 static void tab_completion_emit_tab_signal(TabCompData *td) | |
166 { | |
167 gchar *text; | |
168 if (!td->tab_func) return; | |
169 | |
170 text = g_strdup(gtk_entry_get_text(GTK_ENTRY(td->entry))); | |
171 | |
172 if (text[0] == '~') | |
173 { | |
174 gchar *t = text; | |
175 text = g_strconcat(homedir(), t + 1, NULL); | |
176 g_free(t); | |
177 } | |
178 | |
179 td->tab_func(text, td->tab_data); | |
180 g_free(text); | |
181 } | |
182 | |
183 #ifdef TAB_COMPLETION_ENABLE_POPUP_MENU | |
184 | |
185 static gint tab_completion_popup_key_press(GtkWidget *widget, GdkEventKey *event, gpointer data) | |
186 { | |
187 TabCompData *td = data; | |
188 | |
189 if (event->keyval == GDK_Tab || | |
190 event->keyval == GDK_BackSpace || | |
191 (event->keyval >= 0x20 && event->keyval <= 0xFF) ) | |
192 { | |
193 if (event->keyval >= 0x20 && event->keyval <= 0xFF) | |
194 { | |
195 gchar buf[2]; | |
196 gint p = -1; | |
197 | |
198 buf[0] = event->keyval; | |
199 buf[1] = '\0'; | |
200 gtk_editable_insert_text(GTK_EDITABLE(td->entry), buf, 1, &p); | |
201 gtk_editable_set_position(GTK_EDITABLE(td->entry), -1); | |
202 } | |
203 | |
204 /*close the menu */ | |
205 gtk_menu_popdown(GTK_MENU(widget)); | |
206 /* doing this does not emit the "selection done" signal, unref it ourselves */ | |
207 gtk_widget_unref(widget); | |
442 | 208 |
9 | 209 return TRUE; |
210 } | |
211 | |
212 return FALSE; | |
213 } | |
214 | |
215 static void tab_completion_popup_cb(GtkWidget *widget, gpointer data) | |
216 { | |
217 gchar *name = data; | |
218 TabCompData *td; | |
219 gchar *buf; | |
220 gchar *ptr; | |
221 | |
222 td = g_object_get_data(G_OBJECT(widget), "tab_completion_data"); | |
223 if (!td) return; | |
224 | |
225 ptr = td->dir_path + strlen(td->dir_path) - 1; | |
226 buf = g_strconcat(td->dir_path, (ptr[0] == '/') ? "" : "/", name, NULL); | |
227 gtk_entry_set_text(GTK_ENTRY(td->entry), buf); | |
228 gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf)); | |
229 g_free(buf); | |
230 | |
231 tab_completion_emit_tab_signal(td); | |
232 } | |
233 | |
234 static void tab_completion_popup_pos_cb(GtkMenu *menu, gint *x, gint *y, gboolean *push_in, gpointer data) | |
235 { | |
236 TabCompData *td = data; | |
237 gint height; | |
238 PangoLayout *layout; | |
239 PangoRectangle strong_pos, weak_pos; | |
240 gint length; | |
241 gint xoffset, yoffset; | |
12
147f4c4b9025
##### Note: GQview CVS on sourceforge is not always up to date, please use #####
gqview
parents:
9
diff
changeset
|
242 GtkRequisition req; |
147f4c4b9025
##### Note: GQview CVS on sourceforge is not always up to date, please use #####
gqview
parents:
9
diff
changeset
|
243 GdkScreen *screen; |
147f4c4b9025
##### Note: GQview CVS on sourceforge is not always up to date, please use #####
gqview
parents:
9
diff
changeset
|
244 gint monitor_num; |
147f4c4b9025
##### Note: GQview CVS on sourceforge is not always up to date, please use #####
gqview
parents:
9
diff
changeset
|
245 GdkRectangle monitor; |
9 | 246 |
247 gdk_window_get_origin(td->entry->window, x, y); | |
248 | |
12
147f4c4b9025
##### Note: GQview CVS on sourceforge is not always up to date, please use #####
gqview
parents:
9
diff
changeset
|
249 screen = gtk_widget_get_screen(GTK_WIDGET(menu)); |
147f4c4b9025
##### Note: GQview CVS on sourceforge is not always up to date, please use #####
gqview
parents:
9
diff
changeset
|
250 monitor_num = gdk_screen_get_monitor_at_window(screen, td->entry->window); |
147f4c4b9025
##### Note: GQview CVS on sourceforge is not always up to date, please use #####
gqview
parents:
9
diff
changeset
|
251 gdk_screen_get_monitor_geometry(screen, monitor_num, &monitor); |
147f4c4b9025
##### Note: GQview CVS on sourceforge is not always up to date, please use #####
gqview
parents:
9
diff
changeset
|
252 |
147f4c4b9025
##### Note: GQview CVS on sourceforge is not always up to date, please use #####
gqview
parents:
9
diff
changeset
|
253 gtk_widget_size_request(GTK_WIDGET(menu), &req); |
9 | 254 |
255 length = strlen(gtk_entry_get_text(GTK_ENTRY(td->entry))); | |
256 gtk_entry_get_layout_offsets(GTK_ENTRY(td->entry), &xoffset, &yoffset); | |
257 | |
258 layout = gtk_entry_get_layout(GTK_ENTRY(td->entry)); | |
259 pango_layout_get_cursor_pos(layout, length, &strong_pos, &weak_pos); | |
12
147f4c4b9025
##### Note: GQview CVS on sourceforge is not always up to date, please use #####
gqview
parents:
9
diff
changeset
|
260 |
9 | 261 *x += strong_pos.x / PANGO_SCALE + xoffset; |
12
147f4c4b9025
##### Note: GQview CVS on sourceforge is not always up to date, please use #####
gqview
parents:
9
diff
changeset
|
262 |
147f4c4b9025
##### Note: GQview CVS on sourceforge is not always up to date, please use #####
gqview
parents:
9
diff
changeset
|
263 height = MIN(td->entry->requisition.height, td->entry->allocation.height); |
147f4c4b9025
##### Note: GQview CVS on sourceforge is not always up to date, please use #####
gqview
parents:
9
diff
changeset
|
264 |
147f4c4b9025
##### Note: GQview CVS on sourceforge is not always up to date, please use #####
gqview
parents:
9
diff
changeset
|
265 if (req.height > monitor.y + monitor.height - *y - height && |
147f4c4b9025
##### Note: GQview CVS on sourceforge is not always up to date, please use #####
gqview
parents:
9
diff
changeset
|
266 *y - monitor.y > monitor.y + monitor.height - *y) |
147f4c4b9025
##### Note: GQview CVS on sourceforge is not always up to date, please use #####
gqview
parents:
9
diff
changeset
|
267 { |
147f4c4b9025
##### Note: GQview CVS on sourceforge is not always up to date, please use #####
gqview
parents:
9
diff
changeset
|
268 height = MIN(*y - monitor.y, req.height); |
147f4c4b9025
##### Note: GQview CVS on sourceforge is not always up to date, please use #####
gqview
parents:
9
diff
changeset
|
269 gtk_widget_set_size_request(GTK_WIDGET(menu), -1, height); |
147f4c4b9025
##### Note: GQview CVS on sourceforge is not always up to date, please use #####
gqview
parents:
9
diff
changeset
|
270 *y -= height; |
147f4c4b9025
##### Note: GQview CVS on sourceforge is not always up to date, please use #####
gqview
parents:
9
diff
changeset
|
271 } |
147f4c4b9025
##### Note: GQview CVS on sourceforge is not always up to date, please use #####
gqview
parents:
9
diff
changeset
|
272 else |
147f4c4b9025
##### Note: GQview CVS on sourceforge is not always up to date, please use #####
gqview
parents:
9
diff
changeset
|
273 { |
147f4c4b9025
##### Note: GQview CVS on sourceforge is not always up to date, please use #####
gqview
parents:
9
diff
changeset
|
274 *y += height; |
147f4c4b9025
##### Note: GQview CVS on sourceforge is not always up to date, please use #####
gqview
parents:
9
diff
changeset
|
275 } |
9 | 276 } |
277 | |
278 static void tab_completion_popup_list(TabCompData *td, GList *list) | |
279 { | |
280 GtkWidget *menu; | |
281 GList *work; | |
282 GdkEvent *event; | |
283 guint32 etime; | |
284 gint ebutton; | |
285 gint count = 0; | |
286 | |
287 if (!list) return; | |
288 | |
289 #if 0 | |
290 /* | |
291 * well, the menu would be too long anyway... | |
292 * (listing /dev causes gtk+ window allocation errors, -> too big a window) | |
293 * this is why menu popups are disabled, this really should be a popup scrollable listview. | |
294 */ | |
295 if (g_list_length(list) > 200) return; | |
296 #endif | |
297 | |
298 menu = popup_menu_short_lived(); | |
299 | |
300 work = list; | |
301 while (work && count < TAB_COMP_POPUP_MAX) | |
302 { | |
303 gchar *name = work->data; | |
304 GtkWidget *item; | |
305 | |
306 item = menu_item_add_simple(menu, name, G_CALLBACK(tab_completion_popup_cb), name); | |
307 g_object_set_data(G_OBJECT(item), "tab_completion_data", td); | |
308 | |
309 work = work->next; | |
310 count++; | |
311 } | |
312 | |
313 g_signal_connect(G_OBJECT(menu), "key_press_event", | |
314 G_CALLBACK(tab_completion_popup_key_press), td); | |
315 | |
316 /* peek at the current event to get the time, etc. */ | |
317 event = gtk_get_current_event(); | |
318 | |
319 if (event && event->type == GDK_BUTTON_RELEASE) | |
320 { | |
321 ebutton = event->button.button; | |
322 } | |
323 else | |
324 { | |
325 ebutton = 0; | |
326 } | |
327 | |
328 if (event) | |
329 { | |
330 etime = gdk_event_get_time(event); | |
331 gdk_event_free(event); | |
332 } | |
333 else | |
334 { | |
335 etime = 0; | |
336 } | |
337 | |
338 gtk_menu_popup(GTK_MENU(menu), NULL, NULL, | |
339 tab_completion_popup_pos_cb, td, ebutton, etime); | |
340 } | |
341 | |
342 #ifndef CASE_SORT | |
343 #define CASE_SORT strcmp | |
344 #endif | |
345 | |
346 static gint simple_sort(gconstpointer a, gconstpointer b) | |
347 { | |
442 | 348 return CASE_SORT((gchar *)a, (gchar *)b); |
9 | 349 } |
350 | |
351 #endif | |
352 | |
353 static gint tab_completion_do(TabCompData *td) | |
354 { | |
355 const gchar *entry_text = gtk_entry_get_text(GTK_ENTRY(td->entry)); | |
356 const gchar *entry_file; | |
357 gchar *entry_dir; | |
358 gchar *ptr; | |
359 gint home_exp = FALSE; | |
360 | |
361 /* home dir expansion */ | |
362 if (entry_text[0] == '~') | |
363 { | |
364 entry_dir = g_strconcat(homedir(), entry_text + 1, NULL); | |
365 home_exp = TRUE; | |
366 } | |
367 else | |
368 { | |
369 entry_dir = g_strdup(entry_text); | |
370 } | |
371 | |
372 entry_file = filename_from_path(entry_text); | |
373 | |
374 if (isfile(entry_dir)) | |
375 { | |
376 if (home_exp) | |
377 { | |
378 gtk_entry_set_text(GTK_ENTRY(td->entry), entry_dir); | |
379 gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(entry_dir)); | |
380 } | |
381 g_free(entry_dir); | |
382 return home_exp; | |
383 } | |
384 if (isdir(entry_dir) && strcmp(entry_file, ".") != 0 && strcmp(entry_file, "..") != 0) | |
385 { | |
386 ptr = entry_dir + strlen(entry_dir) - 1; | |
387 if (ptr[0] == '/') | |
388 { | |
389 if (home_exp) | |
390 { | |
391 gtk_entry_set_text(GTK_ENTRY(td->entry), entry_dir); | |
392 gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(entry_dir)); | |
393 } | |
394 | |
395 tab_completion_read_dir(td, entry_dir); | |
396 td->file_list = g_list_sort(td->file_list, simple_sort); | |
397 if (td->file_list && !td->file_list->next) | |
398 { | |
399 gchar *buf; | |
400 const gchar *file; | |
401 | |
402 file = td->file_list->data; | |
403 buf = g_strconcat(entry_dir, file, NULL); | |
404 if (isdir(buf)) | |
405 { | |
406 g_free(buf); | |
407 buf = g_strconcat(entry_dir, file, "/", NULL); | |
408 } | |
409 gtk_entry_set_text(GTK_ENTRY(td->entry), buf); | |
410 gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf)); | |
411 g_free(buf); | |
412 } | |
413 | |
414 #ifdef TAB_COMPLETION_ENABLE_POPUP_MENU | |
415 | |
416 else | |
417 { | |
418 tab_completion_popup_list(td, td->file_list); | |
419 } | |
420 #endif | |
421 | |
422 g_free(entry_dir); | |
423 return home_exp; | |
424 } | |
425 else | |
426 { | |
427 gchar *buf = g_strconcat(entry_dir, "/", NULL); | |
428 gtk_entry_set_text(GTK_ENTRY(td->entry), buf); | |
429 gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf)); | |
430 g_free(buf); | |
431 g_free(entry_dir); | |
432 return TRUE; | |
433 } | |
434 } | |
435 | |
436 ptr = (gchar *)filename_from_path(entry_dir); | |
437 if (ptr > entry_dir) ptr--; | |
438 ptr[0] = '\0'; | |
439 | |
440 if (strlen(entry_dir) == 0) | |
441 { | |
442 g_free(entry_dir); | |
443 entry_dir = g_strdup("/"); | |
444 } | |
445 | |
446 if (isdir(entry_dir)) | |
447 { | |
448 GList *list; | |
449 GList *poss = NULL; | |
450 gint l = strlen(entry_file); | |
451 | |
452 if (!td->dir_path || !td->file_list || strcmp(td->dir_path, entry_dir) != 0) | |
453 { | |
454 tab_completion_read_dir(td, entry_dir); | |
455 } | |
456 | |
457 if (strcmp(entry_dir, "/") == 0) entry_dir[0] = '\0'; | |
458 | |
459 list = td->file_list; | |
516 | 460 while (list) |
9 | 461 { |
462 gchar *file = list->data; | |
463 if (strncmp(entry_file, file, l) == 0) | |
464 { | |
465 poss = g_list_prepend(poss, file); | |
466 } | |
467 list = list->next; | |
468 } | |
469 | |
470 if (poss) | |
471 { | |
472 if (!poss->next) | |
473 { | |
474 gchar *file = poss->data; | |
475 gchar *buf; | |
476 | |
477 buf = g_strconcat(entry_dir, "/", file, NULL); | |
478 | |
479 if (isdir(buf)) | |
480 { | |
481 g_free(buf); | |
482 buf = g_strconcat(entry_dir, "/", file, "/", NULL); | |
483 } | |
484 gtk_entry_set_text(GTK_ENTRY(td->entry), buf); | |
485 gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf)); | |
486 g_free(buf); | |
487 g_list_free(poss); | |
488 g_free(entry_dir); | |
489 return TRUE; | |
490 } | |
491 else | |
492 { | |
493 gint c = strlen(entry_file); | |
494 gint done = FALSE; | |
495 gchar *test_file = poss->data; | |
496 | |
497 while (!done) | |
498 { | |
499 list = poss; | |
500 if (!list) done = TRUE; | |
516 | 501 while (list && !done) |
9 | 502 { |
503 gchar *file = list->data; | |
504 if (strlen(file) < c || strncmp(test_file, file, c) != 0) | |
505 { | |
506 done = TRUE; | |
507 } | |
508 list = list->next; | |
509 } | |
510 c++; | |
511 } | |
512 c -= 2; | |
513 if (c > 0) | |
514 { | |
515 gchar *file; | |
516 gchar *buf; | |
517 file = g_strdup(test_file); | |
518 file[c] = '\0'; | |
519 buf = g_strconcat(entry_dir, "/", file, NULL); | |
520 gtk_entry_set_text(GTK_ENTRY(td->entry), buf); | |
521 gtk_editable_set_position(GTK_EDITABLE(td->entry), strlen(buf)); | |
522 | |
523 #ifdef TAB_COMPLETION_ENABLE_POPUP_MENU | |
524 | |
525 poss = g_list_sort(poss, simple_sort); | |
526 tab_completion_popup_list(td, poss); | |
527 | |
528 #endif | |
529 | |
530 g_free(file); | |
531 g_free(buf); | |
532 g_list_free(poss); | |
533 g_free(entry_dir); | |
534 return TRUE; | |
535 } | |
536 } | |
537 g_list_free(poss); | |
538 } | |
539 } | |
540 | |
541 g_free(entry_dir); | |
542 | |
543 return FALSE; | |
544 } | |
545 | |
546 static gint tab_completion_key_pressed(GtkWidget *widget, GdkEventKey *event, gpointer data) | |
547 { | |
548 TabCompData *td = data; | |
549 gint stop_signal = FALSE; | |
550 | |
551 switch (event->keyval) | |
552 { | |
442 | 553 case GDK_Tab: |
9 | 554 if (!(event->state & GDK_CONTROL_MASK)) |
555 { | |
556 if (tab_completion_do(td)) | |
557 { | |
558 tab_completion_emit_tab_signal(td); | |
559 } | |
560 stop_signal = TRUE; | |
561 } | |
562 break; | |
563 case GDK_Return: case GDK_KP_Enter: | |
564 if (td->fd_button && | |
565 (event->state & GDK_CONTROL_MASK)) | |
566 { | |
567 tab_completion_select_show(td); | |
568 stop_signal = TRUE; | |
569 } | |
570 else if (tab_completion_emit_enter_signal(td)) | |
571 { | |
572 stop_signal = TRUE; | |
573 } | |
574 break; | |
575 default: | |
576 break; | |
577 } | |
578 | |
579 if (stop_signal) g_signal_stop_emission_by_name(G_OBJECT(widget), "key_press_event"); | |
580 | |
581 return (stop_signal); | |
582 } | |
583 | |
584 static void tab_completion_button_pressed(GtkWidget *widget, gpointer data) | |
585 { | |
586 TabCompData *td; | |
587 GtkWidget *entry = data; | |
588 | |
589 td = g_object_get_data(G_OBJECT(entry), "tab_completion_data"); | |
590 | |
591 if (!td) return; | |
592 | |
593 if (!GTK_WIDGET_HAS_FOCUS(entry)) | |
594 { | |
595 gtk_widget_grab_focus(entry); | |
596 } | |
597 | |
598 if (tab_completion_do(td)) | |
599 { | |
600 tab_completion_emit_tab_signal(td); | |
601 } | |
602 } | |
603 | |
604 static void tab_completion_button_size_allocate(GtkWidget *button, GtkAllocation *allocation, gpointer data) | |
605 { | |
606 GtkWidget *parent = data; | |
607 | |
608 if (allocation->height > parent->allocation.height) | |
609 { | |
610 GtkAllocation button_allocation; | |
611 | |
612 button_allocation = button->allocation; | |
613 button_allocation.height = parent->allocation.height; | |
614 button_allocation.y = parent->allocation.y + | |
615 (parent->allocation.height - parent->allocation.height) / 2; | |
616 gtk_widget_size_allocate(button, &button_allocation); | |
617 } | |
618 } | |
619 | |
620 static GtkWidget *tab_completion_create_complete_button(GtkWidget *entry, GtkWidget *parent) | |
621 { | |
622 GtkWidget *button; | |
623 GtkWidget *icon; | |
624 GdkPixbuf *pixbuf; | |
625 | |
626 button = gtk_button_new(); | |
627 GTK_WIDGET_UNSET_FLAGS(button, GTK_CAN_FOCUS); | |
628 g_signal_connect(G_OBJECT(button), "size_allocate", | |
629 G_CALLBACK(tab_completion_button_size_allocate), parent); | |
630 g_signal_connect(G_OBJECT(button), "clicked", | |
631 G_CALLBACK(tab_completion_button_pressed), entry); | |
632 | |
633 pixbuf = gdk_pixbuf_new_from_inline(-1, icon_tabcomp, FALSE, NULL); | |
634 icon = gtk_image_new_from_pixbuf(pixbuf); | |
635 gdk_pixbuf_unref(pixbuf); | |
636 gtk_container_add(GTK_CONTAINER(button), icon); | |
637 gtk_widget_show(icon); | |
638 | |
639 return button; | |
640 } | |
641 | |
642 /* | |
643 *---------------------------------------------------------------------------- | |
644 * public interface | |
645 *---------------------------------------------------------------------------- | |
646 */ | |
647 | |
648 GtkWidget *tab_completion_new_with_history(GtkWidget **entry, const gchar *text, | |
649 const gchar *history_key, gint max_levels, | |
650 void (*enter_func)(const gchar *, gpointer), gpointer data) | |
651 { | |
652 GtkWidget *box; | |
653 GtkWidget *combo; | |
654 GtkWidget *combo_entry; | |
655 GtkWidget *button; | |
656 GList *work; | |
657 TabCompData *td; | |
658 gint n = 0; | |
659 | |
660 box = gtk_hbox_new(FALSE, 0); | |
661 | |
662 combo = gtk_combo_box_entry_new_text(); | |
663 gtk_box_pack_start(GTK_BOX(box), combo, TRUE, TRUE, 0); | |
664 gtk_widget_show(combo); | |
665 | |
666 combo_entry = GTK_BIN(combo)->child; | |
667 #if 0 | |
668 gtk_combo_set_case_sensitive(GTK_COMBO(combo), TRUE); | |
669 gtk_combo_set_use_arrows(GTK_COMBO(combo), FALSE); | |
670 #endif | |
671 | |
672 button = tab_completion_create_complete_button(combo_entry, combo); | |
673 gtk_box_pack_start(GTK_BOX(box), button, FALSE, FALSE, 0); | |
674 gtk_widget_show(button); | |
675 | |
676 tab_completion_add_to_entry(combo_entry, enter_func, data); | |
677 | |
678 td = g_object_get_data(G_OBJECT(combo_entry), "tab_completion_data"); | |
679 if (!td) return NULL; /* this should never happen! */ | |
680 | |
681 td->combo = combo; | |
682 td->has_history = TRUE; | |
683 td->history_key = g_strdup(history_key); | |
684 td->history_levels = max_levels; | |
685 | |
686 work = history_list_get_by_key(td->history_key); | |
687 | |
688 work = history_list_get_by_key(history_key); | |
689 while (work) | |
690 { | |
691 gtk_combo_box_append_text(GTK_COMBO_BOX(combo), (gchar *)work->data); | |
692 work = work->next; | |
693 n++; | |
694 } | |
695 | |
696 if (text) | |
697 { | |
698 gtk_entry_set_text(GTK_ENTRY(combo_entry), text); | |
699 } | |
700 else if (n > 0) | |
701 { | |
702 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0); | |
703 } | |
704 | |
705 if (entry) *entry = combo_entry; | |
706 return box; | |
707 } | |
708 | |
709 const gchar *tab_completion_set_to_last_history(GtkWidget *entry) | |
710 { | |
711 TabCompData *td = g_object_get_data(G_OBJECT(entry), "tab_completion_data"); | |
712 const gchar *buf; | |
713 | |
714 if (!td || !td->has_history) return NULL; | |
715 | |
716 buf = history_list_find_last_path_by_key(td->history_key); | |
717 if (buf) | |
718 { | |
719 gtk_entry_set_text(GTK_ENTRY(td->entry), buf); | |
720 } | |
721 | |
722 return buf; | |
723 } | |
724 | |
725 void tab_completion_append_to_history(GtkWidget *entry, const gchar *path) | |
726 { | |
727 TabCompData *td; | |
728 GtkTreeModel *store; | |
729 GList *work; | |
730 | |
731 td = g_object_get_data(G_OBJECT(entry), "tab_completion_data"); | |
732 | |
733 if (!path) return; | |
734 | |
735 if (!td || !td->has_history) return; | |
736 | |
737 history_list_add_to_key(td->history_key, path, td->history_levels); | |
738 | |
739 gtk_combo_box_set_active(GTK_COMBO_BOX(td->combo), -1); | |
740 | |
741 store = gtk_combo_box_get_model(GTK_COMBO_BOX(td->combo)); | |
742 gtk_list_store_clear(GTK_LIST_STORE(store)); | |
743 | |
744 work = history_list_get_by_key(td->history_key); | |
745 while (work) | |
746 { | |
747 gtk_combo_box_append_text(GTK_COMBO_BOX(td->combo), (gchar *)work->data); | |
748 work = work->next; | |
749 } | |
750 } | |
751 | |
752 GtkWidget *tab_completion_new(GtkWidget **entry, const gchar *text, | |
753 void (*enter_func)(const gchar *, gpointer), gpointer data) | |
754 { | |
755 GtkWidget *hbox; | |
756 GtkWidget *button; | |
757 GtkWidget *newentry; | |
758 | |
759 hbox = gtk_hbox_new(FALSE, 0); | |
760 | |
761 newentry = gtk_entry_new(); | |
762 if (text) gtk_entry_set_text(GTK_ENTRY(newentry), text); | |
763 gtk_box_pack_start(GTK_BOX(hbox), newentry, TRUE, TRUE, 0); | |
764 gtk_widget_show(newentry); | |
765 | |
766 button = tab_completion_create_complete_button(newentry, newentry); | |
767 gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 0); | |
768 gtk_widget_show(button); | |
769 | |
770 tab_completion_add_to_entry(newentry, enter_func, data); | |
771 | |
772 if (entry) *entry = newentry; | |
773 return hbox; | |
774 } | |
775 | |
776 void tab_completion_add_to_entry(GtkWidget *entry, void (*enter_func)(const gchar *, gpointer), gpointer data) | |
777 { | |
778 TabCompData *td; | |
779 if (!entry) | |
780 { | |
673
fbebf5cf4a55
Do not use printf() directly but use new wrapper function log_printf() instead.
zas_
parents:
516
diff
changeset
|
781 log_printf("Tab completion error: entry != NULL\n"); |
9 | 782 return; |
783 } | |
784 | |
785 td = g_new0(TabCompData, 1); | |
786 td->entry = entry; | |
787 td->dir_path = NULL; | |
788 td->file_list = NULL; | |
789 td->enter_func = enter_func; | |
790 td->enter_data = data; | |
791 td->tab_func = NULL; | |
792 td->tab_data = NULL; | |
793 | |
794 td->has_history = FALSE; | |
795 td->history_key = NULL; | |
796 td->history_levels = 0; | |
797 | |
798 g_object_set_data(G_OBJECT(td->entry), "tab_completion_data", td); | |
799 | |
800 g_signal_connect(G_OBJECT(entry), "key_press_event", | |
801 G_CALLBACK(tab_completion_key_pressed), td); | |
802 g_signal_connect(G_OBJECT(entry), "destroy", | |
803 G_CALLBACK(tab_completion_destroy), td); | |
804 } | |
805 | |
806 void tab_completion_add_tab_func(GtkWidget *entry, void (*tab_func)(const gchar *, gpointer), gpointer data) | |
807 { | |
808 TabCompData *td = g_object_get_data(G_OBJECT(entry), "tab_completion_data"); | |
809 | |
810 if (!td) return; | |
811 | |
812 td->tab_func = tab_func; | |
813 td->tab_data = data; | |
814 } | |
815 | |
816 gchar *remove_trailing_slash(const gchar *path) | |
817 { | |
818 gint l; | |
260
249a9a6cd27f
Improve remove_trailing_slash() so it allocates no more than
zas_
parents:
254
diff
changeset
|
819 |
9 | 820 if (!path) return NULL; |
821 | |
260
249a9a6cd27f
Improve remove_trailing_slash() so it allocates no more than
zas_
parents:
254
diff
changeset
|
822 l = strlen(path); |
249a9a6cd27f
Improve remove_trailing_slash() so it allocates no more than
zas_
parents:
254
diff
changeset
|
823 while (l > 1 && path[l - 1] == '/') l--; |
9 | 824 |
260
249a9a6cd27f
Improve remove_trailing_slash() so it allocates no more than
zas_
parents:
254
diff
changeset
|
825 return g_strndup(path, l); |
9 | 826 } |
827 | |
828 static void tab_completion_select_cancel_cb(FileDialog *fd, gpointer data) | |
829 { | |
830 TabCompData *td = data; | |
831 | |
832 td->fd = NULL; | |
833 file_dialog_close(fd); | |
834 } | |
835 | |
836 static void tab_completion_select_ok_cb(FileDialog *fd, gpointer data) | |
837 { | |
838 TabCompData *td = data; | |
839 | |
840 gtk_entry_set_text(GTK_ENTRY(td->entry), gtk_entry_get_text(GTK_ENTRY(fd->entry))); | |
841 | |
842 tab_completion_select_cancel_cb(fd, data); | |
843 | |
844 tab_completion_emit_enter_signal(td); | |
845 } | |
846 | |
847 static void tab_completion_select_show(TabCompData *td) | |
848 { | |
849 const gchar *title; | |
850 const gchar *path; | |
851 | |
852 if (td->fd) | |
853 { | |
854 gtk_window_present(GTK_WINDOW(GENERIC_DIALOG(td->fd)->dialog)); | |
855 return; | |
856 } | |
857 | |
858 title = (td->fd_title) ? td->fd_title : _("Select path"); | |
254
9faf34f047b1
Make the wmclass value unique among the code by defining
zas_
parents:
138
diff
changeset
|
859 td->fd = file_dialog_new(title, GQ_WMCLASS, "select_path", td->entry, |
9 | 860 tab_completion_select_cancel_cb, td); |
861 file_dialog_add_button(td->fd, GTK_STOCK_OK, NULL, | |
862 tab_completion_select_ok_cb, TRUE); | |
863 | |
864 generic_dialog_add_message(GENERIC_DIALOG(td->fd), NULL, title, NULL); | |
865 | |
866 path = gtk_entry_get_text(GTK_ENTRY(td->entry)); | |
867 if (strlen(path) == 0) path = NULL; | |
868 if (td->fd_folders_only) | |
869 { | |
870 file_dialog_add_path_widgets(td->fd, NULL, path, td->history_key, NULL, NULL); | |
871 } | |
872 else | |
873 { | |
874 file_dialog_add_path_widgets(td->fd, NULL, path, td->history_key, "*", _("All files")); | |
875 } | |
876 | |
877 gtk_widget_show(GENERIC_DIALOG(td->fd)->dialog); | |
878 } | |
879 | |
880 static void tab_completion_select_pressed(GtkWidget *widget, gpointer data) | |
881 { | |
882 TabCompData *td = data; | |
883 | |
884 tab_completion_select_show(td); | |
885 } | |
886 | |
887 void tab_completion_add_select_button(GtkWidget *entry, const gchar *title, gint folders_only) | |
888 { | |
889 TabCompData *td; | |
890 GtkWidget *parent; | |
891 GtkWidget *hbox; | |
892 | |
893 td = g_object_get_data(G_OBJECT(entry), "tab_completion_data"); | |
894 | |
895 if (!td) return; | |
896 | |
897 g_free(td->fd_title); | |
898 td->fd_title = g_strdup(title); | |
899 td->fd_folders_only = folders_only; | |
900 | |
901 if (td->fd_button) return; | |
902 | |
903 parent = (td->combo) ? td->combo : td->entry; | |
904 | |
905 hbox = gtk_widget_get_parent(parent); | |
906 if (!GTK_IS_BOX(hbox)) return; | |
907 | |
908 td->fd_button = gtk_button_new_with_label("..."); | |
909 g_signal_connect(G_OBJECT(td->fd_button), "size_allocate", | |
910 G_CALLBACK(tab_completion_button_size_allocate), parent); | |
911 g_signal_connect(G_OBJECT(td->fd_button), "clicked", | |
912 G_CALLBACK(tab_completion_select_pressed), td); | |
913 | |
914 gtk_box_pack_start(GTK_BOX(hbox), td->fd_button, FALSE, FALSE, 0); | |
915 | |
916 gtk_widget_show(td->fd_button); | |
917 } |