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