1 /* -*- Mode: C; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 3 -*- */
2
3 /*
4 * GImageView
5 * Copyright (C) 2001 Takuro Ashie
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20 *
21 * $Id: auto_completion.c,v 1.3 2003/06/13 09:43:23 makeinu Exp $
22 */
23
24 /*
25 * These codes are mostly taken from gThumb.
26 * gThumb code Copyright (C) 2001 The Free Software Foundation, Inc.
27 * gThumb author: Paolo Bacchilega
28 */
29
30 #ifdef HAVE_CONFIG_H
31 # include "config.h"
32 #endif
33
34 #include <string.h>
35 #include <gdk/gdk.h>
36 #include <gdk/gdkkeysyms.h>
37 #include <gtk/gtkentry.h>
38 #include <gtk/gtkframe.h>
39 #include <gtk/gtkclist.h>
40 #include <gtk/gtkmain.h>
41 #include <gtk/gtkwindow.h>
42 #include <gtk/gtkscrolledwindow.h>
43
44 #include "auto_completion.h"
45 #include "charset.h"
46 #include "fileutil.h"
47 #include "gfileutil.h"
48 #include "gtk2-compat.h"
49 #include "prefs.h"
50
51 #define MAX_VISIBLE_ROWS 8
52 #define CLIST_ROW_PAD 5
53
54 static gchar *ac_dir = NULL;
55 static gchar *ac_path = NULL;
56 static gchar ac_show_dot = FALSE;
57 static GList *ac_subdirs = NULL;
58 static GList *ac_alternatives = NULL;
59
60 static GtkWidget *ac_window = NULL;
61 static GtkWidget *ac_clist = NULL;
62 static GtkWidget *ac_entry = NULL;
63 #ifdef ENABLE_TREEVIEW
64 static GtkListStore *ac_list_store = NULL;
65 #endif /* ENABLE_TREEVIEW */
66
67 static void
ac_dir_free(void)68 ac_dir_free (void)
69 {
70 if (!ac_dir) return;
71
72 g_free (ac_dir);
73 ac_dir = NULL;
74 }
75
76
77 static void
ac_path_free(void)78 ac_path_free (void)
79 {
80 if (!ac_path) return;
81
82 g_free (ac_path);
83 ac_path = NULL;
84 }
85
86
87 static void
ac_subdirs_free(void)88 ac_subdirs_free (void)
89 {
90 if (!ac_subdirs) return;
91
92 g_list_foreach (ac_subdirs, (GFunc) g_free, NULL);
93 g_list_free (ac_subdirs);
94 ac_subdirs = NULL;
95 }
96
97
98 static void
ac_alternatives_free(void)99 ac_alternatives_free (void)
100 {
101 if (!ac_alternatives) return;
102
103 g_list_foreach (ac_alternatives, (GFunc) g_free, NULL);
104 g_list_free (ac_alternatives);
105 ac_alternatives = NULL;
106 }
107
108
109 void
auto_compl_reset(void)110 auto_compl_reset (void)
111 {
112 ac_dir_free ();
113 ac_path_free ();
114 ac_subdirs_free ();
115 ac_alternatives_free ();
116 }
117
118
119 gint
auto_compl_get_n_alternatives(const gchar * path)120 auto_compl_get_n_alternatives (const gchar *path)
121 {
122 gchar *dir;
123 const gchar *filename;
124 gint path_len;
125 GList *scan;
126 gint n;
127 gint flags = GETDIR_FOLLOW_SYMLINK;
128 gboolean show_dot;
129
130 if (path == NULL) return 0;
131
132 filename = g_basename (path);
133 if (filename && filename[0] == '.') {
134 show_dot = TRUE;
135 flags = flags | GETDIR_READ_DOT;
136 } else {
137 show_dot = FALSE;
138 }
139
140 if (strcmp (path, "/") == 0)
141 dir = g_strdup ("/");
142 else
143 dir = g_dirname (path);
144
145 if (!isdir (dir)) {
146 g_free (dir);
147 return 0;
148 }
149
150 if ((ac_dir == NULL) || strcmp (dir, ac_dir) || ac_show_dot != show_dot) {
151 ac_dir_free ();
152 ac_subdirs_free ();
153
154 ac_dir = charset_to_internal (dir,
155 conf.charset_filename,
156 conf.charset_auto_detect_fn,
157 conf.charset_filename_mode);
158 if (!ac_dir && dir)
159 ac_dir = g_strdup (dir);
160
161 get_dir (dir, flags, NULL, &ac_subdirs);
162 if (ac_show_dot != show_dot)
163 ac_show_dot = show_dot;
164 }
165
166 ac_path_free ();
167 ac_alternatives_free ();
168
169 ac_path = g_strdup (path);
170 path_len = strlen (ac_path);
171 n = 0;
172
173 for (scan = ac_subdirs; scan; scan = scan->next) {
174 const gchar *subdir = (gchar*) scan->data;
175 gchar *subdir_internal;
176
177 subdir_internal = charset_to_internal (subdir,
178 conf.charset_filename,
179 conf.charset_auto_detect_fn,
180 conf.charset_filename_mode);
181
182 if (!subdir_internal && subdir)
183 subdir_internal = g_strdup (subdir);
184
185 if (strncmp (path, subdir_internal, path_len) != 0) {
186 g_free (subdir_internal);
187 continue;
188 }
189
190 ac_alternatives = g_list_prepend (ac_alternatives,
191 subdir_internal);
192
193 n++;
194 }
195
196 g_free (dir);
197 ac_alternatives = g_list_reverse (ac_alternatives);
198
199 return n;
200 }
201
202
203 static gint
get_common_prefix_length(void)204 get_common_prefix_length (void)
205 {
206 gint n;
207 GList *scan;
208 gchar c1, c2;
209
210 g_return_val_if_fail (ac_path != NULL, 0);
211 g_return_val_if_fail (ac_alternatives != NULL, 0);
212
213 /* if the number of alternatives is 1 return its length. */
214 if (ac_alternatives->next == NULL)
215 return strlen ((gchar*) ac_alternatives->data);
216
217 n = strlen (ac_path);
218 while (TRUE) {
219 scan = ac_alternatives;
220
221 c1 = ((gchar*) scan->data) [n];
222
223 if (c1 == 0)
224 return n;
225
226 /* check that all other alternatives have the same
227 * character at position n. */
228
229 scan = scan->next;
230
231 for (; scan; scan = scan->next) {
232 c2 = ((gchar*) scan->data) [n];
233 if (c1 != c2)
234 return n;
235 }
236
237 n++;
238 }
239
240 return -1;
241 }
242
243
244 gchar *
auto_compl_get_common_prefix(void)245 auto_compl_get_common_prefix (void)
246 {
247 gchar *alternative;
248 gint n;
249
250 if (ac_path == NULL)
251 return NULL;
252
253 if (ac_alternatives == NULL)
254 return NULL;
255
256 n = get_common_prefix_length ();
257 alternative = (gchar*) ac_alternatives->data;
258
259 return g_strndup (alternative, n);
260 }
261
262
263 GList *
auto_compl_get_alternatives(void)264 auto_compl_get_alternatives (void)
265 {
266 return ac_alternatives;
267 }
268
269
270 static gboolean
ac_window_button_press_cb(GtkWidget * widget,GdkEventButton * event,gpointer * data)271 ac_window_button_press_cb (GtkWidget *widget,
272 GdkEventButton *event,
273 gpointer *data)
274 {
275 GtkWidget *event_widget;
276 gint x, y, w, h;
277
278 event_widget = gtk_get_event_widget ((GdkEvent *)event);
279
280 gdk_window_get_origin (ac_window->window, &x, &y);
281 gdk_window_get_size (ac_window->window, &w, &h);
282
283 /* Checks if the button press happened inside the window,
284 * if not closes the window. */
285 if ((event->x >= 0) && (event->x <= w)
286 && (event->y >= 0) && (event->y <= h)) {
287 /* In window. */
288 return FALSE;
289 }
290
291 auto_compl_hide_alternatives ();
292
293 return TRUE;
294 }
295
296
297 static gboolean
ac_window_key_press_cb(GtkWidget * widget,GdkEventKey * event,gpointer * data)298 ac_window_key_press_cb (GtkWidget *widget,
299 GdkEventKey *event,
300 gpointer *data)
301 {
302 if (event->keyval == GDK_Escape) {
303 auto_compl_hide_alternatives ();
304 return TRUE;
305 }
306
307 /* allow keyboard navigation in the alternatives clist */
308 if (event->keyval == GDK_Up
309 || event->keyval == GDK_Down
310 || event->keyval == GDK_Page_Up
311 || event->keyval == GDK_Page_Down
312 || event->keyval == GDK_space)
313 return FALSE;
314
315 if (event->keyval == GDK_Return) {
316 event->keyval = GDK_space;
317 return FALSE;
318 }
319
320 auto_compl_hide_alternatives ();
321 gtk_widget_event (ac_entry, (GdkEvent*) event);
322 return TRUE;
323 }
324
325
326 #ifdef ENABLE_TREEVIEW
327 static void
cb_tree_cursor_changed(GtkTreeView * treeview,gpointer data)328 cb_tree_cursor_changed (GtkTreeView *treeview, gpointer data)
329 {
330 GtkTreeSelection *selection;
331 GtkTreeModel *model;
332 GtkTreeIter iter;
333 gchar *text, *full_path;
334
335 g_return_if_fail (GTK_IS_TREE_VIEW (treeview));
336
337 selection = gtk_tree_view_get_selection (treeview);
338 gtk_tree_selection_get_selected (selection, &model, &iter);
339 gtk_tree_model_get (model, &iter,
340 0, &text,
341 -1);
342 if (!text) return;
343
344 full_path = g_strconcat (ac_dir, "/", text, NULL);
345 gtk_entry_set_text (GTK_ENTRY (ac_entry), full_path);
346
347 g_free (text);
348 g_free (full_path);
349
350 gtk_editable_set_position (GTK_EDITABLE (ac_entry), -1);
351 }
352 #else /* ENABLE_TREEVIEW */
353 static void
ac_clist_select_row_cb(GtkCList * clist,gint row,gint column,GdkEventButton * event,gpointer * data)354 ac_clist_select_row_cb (GtkCList *clist,
355 gint row,
356 gint column,
357 GdkEventButton *event,
358 gpointer *data)
359 {
360 gchar *text;
361 gchar *full_path;
362
363 auto_compl_hide_alternatives ();
364
365 gtk_clist_get_text (GTK_CLIST (ac_clist), row, column, &text);
366 full_path = g_strconcat (ac_dir, "/", text, NULL);
367 gtk_entry_set_text (GTK_ENTRY (ac_entry), full_path);
368 g_free (full_path);
369
370 #ifdef USE_GTK2
371 gtk_editable_set_position (GTK_EDITABLE (ac_entry), -1);
372 #endif
373 }
374 #endif /* ENABLE_TREEVIEW */
375
376
377 /* displays a list of alternatives under the entry widget. */
378 void
auto_compl_show_alternatives(GtkWidget * entry)379 auto_compl_show_alternatives (GtkWidget *entry)
380 {
381 gint x, y, w, h;
382 GList *scan;
383 gint n, width;
384
385 if (ac_window == NULL) {
386 GtkWidget *scroll;
387 GtkWidget *frame;
388
389 ac_window = gtk_window_new (GTK_WINDOW_POPUP);
390
391 #ifdef ENABLE_TREEVIEW
392 {
393 GtkTreeViewColumn *col;
394 GtkCellRenderer *render;
395
396 ac_list_store = gtk_list_store_new (1, G_TYPE_STRING);
397 ac_clist = gtk_tree_view_new_with_model (GTK_TREE_MODEL (ac_list_store));
398 gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (ac_clist), TRUE);
399 gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (ac_clist), FALSE);
400
401 col = gtk_tree_view_column_new();
402 render = gtk_cell_renderer_text_new ();
403 gtk_tree_view_column_pack_start (col, render, FALSE);
404 gtk_tree_view_column_add_attribute (col, render, "text", 0);
405
406 gtk_tree_view_append_column (GTK_TREE_VIEW (ac_clist), col);
407 }
408 #else /* ENABLE_TREEVIEW */
409 {
410 GdkFont *font;
411 gint row_height;
412 ac_clist = gtk_clist_new (1);
413 font = gtk_style_get_font (GTK_WIDGET (ac_clist)->style);
414 row_height = (font->ascent + font->descent + CLIST_ROW_PAD);
415 gtk_clist_set_row_height (GTK_CLIST (ac_clist), row_height);
416 }
417 #endif /* ENABLE_TREEVIEW */
418
419 scroll = gtk_scrolled_window_new (NULL, NULL);
420 gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll),
421 GTK_POLICY_AUTOMATIC,
422 GTK_POLICY_AUTOMATIC);
423
424 frame = gtk_frame_new (NULL);
425 gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
426
427 gtk_container_add (GTK_CONTAINER (ac_window), frame);
428 gtk_container_add (GTK_CONTAINER (frame), scroll);
429 gtk_container_add (GTK_CONTAINER (scroll), ac_clist);
430
431 gtk_signal_connect (GTK_OBJECT (ac_window),
432 "button-press-event",
433 GTK_SIGNAL_FUNC(ac_window_button_press_cb),
434 NULL);
435 gtk_signal_connect (GTK_OBJECT (ac_window),
436 "key-press-event",
437 GTK_SIGNAL_FUNC(ac_window_key_press_cb),
438 NULL);
439
440 #ifdef ENABLE_TREEVIEW
441 g_signal_connect (G_OBJECT (ac_clist),
442 "cursor_changed",
443 G_CALLBACK (cb_tree_cursor_changed),
444 NULL);
445 #else /* ENABLE_TREEVIEW */
446 gtk_signal_connect (GTK_OBJECT (ac_clist),
447 "select_row",
448 GTK_SIGNAL_FUNC(ac_clist_select_row_cb),
449 NULL);
450 #endif /* ENABLE_TREEVIEW */
451 }
452
453 ac_entry = entry;
454 width = 0;
455 n = 0;
456
457 #ifdef ENABLE_TREEVIEW
458 {
459 GtkTreeIter iter;
460
461 gtk_list_store_clear (ac_list_store);
462
463 for (scan = ac_alternatives; scan; scan = scan->next) {
464 gtk_list_store_append (ac_list_store, &iter);
465 gtk_list_store_set (ac_list_store, &iter,
466 0, g_basename (scan->data),
467 -1);
468
469 if (n == 0) {
470 GtkTreeSelection *selection;
471 GtkTreePath *treepath;
472 selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ac_clist));
473 treepath = gtk_tree_model_get_path (GTK_TREE_MODEL (ac_list_store),
474 &iter);
475 gtk_tree_selection_select_path (selection, treepath);
476 gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (ac_clist),
477 treepath, NULL,
478 TRUE, 0.0, 0.0);
479 gtk_tree_path_free (treepath);
480 }
481
482 n++;
483 }
484 }
485 #else /* ENABLE_TREEVIEW */
486 gtk_clist_freeze (GTK_CLIST (ac_clist));
487 gtk_clist_clear (GTK_CLIST (ac_clist));
488
489 for (scan = ac_alternatives; scan; scan = scan->next) {
490 gchar *text[] = {NULL, NULL};
491
492 text[0] = (gchar *) g_basename (scan->data);
493
494 gtk_clist_append (GTK_CLIST (ac_clist), text);
495 width = MAX (width,
496 gdk_string_width (
497 gtk_style_get_font (GTK_WIDGET (ac_clist)->style),
498 text[0]));
499
500 n++;
501 }
502
503 gtk_clist_set_column_width (GTK_CLIST (ac_clist), 0, width);
504 gtk_clist_thaw (GTK_CLIST (ac_clist));
505 #endif /* ENABLE_TREEVIEW */
506
507 gdk_window_get_geometry (entry->window, &x, &y, &w, &h, NULL);
508 gdk_window_get_deskrelative_origin (entry->window, &x, &y);
509 gtk_widget_set_uposition (ac_window, x, y + h);
510 gtk_widget_set_usize (ac_window, w, 200);
511
512 gtk_widget_show_all (ac_window);
513 gdk_pointer_grab (ac_window->window,
514 TRUE,
515 (GDK_POINTER_MOTION_MASK
516 | GDK_BUTTON_PRESS_MASK
517 | GDK_BUTTON_RELEASE_MASK),
518 NULL,
519 NULL,
520 GDK_CURRENT_TIME);
521 gdk_keyboard_grab (ac_window->window,
522 FALSE,
523 GDK_CURRENT_TIME);
524 gtk_grab_add (ac_window);
525 }
526
527
528 void
auto_compl_hide_alternatives(void)529 auto_compl_hide_alternatives (void)
530 {
531 if (ac_window && GTK_WIDGET_VISIBLE (ac_window)) {
532 gdk_pointer_ungrab (GDK_CURRENT_TIME);
533 gdk_keyboard_ungrab (GDK_CURRENT_TIME);
534 gtk_grab_remove (ac_window);
535 gtk_widget_hide (ac_window);
536 }
537 }
538