/* -*- Mode: C; tab-width: 3; indent-tabs-mode: nil; c-basic-offset: 3 -*- */ /* * GImageView * Copyright (C) 2001 Takuro Ashie * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. * * $Id: auto_completion.c,v 1.3 2003/06/13 09:43:23 makeinu Exp $ */ /* * These codes are mostly taken from gThumb. * gThumb code Copyright (C) 2001 The Free Software Foundation, Inc. * gThumb author: Paolo Bacchilega */ #ifdef HAVE_CONFIG_H # include "config.h" #endif #include #include #include #include #include #include #include #include #include #include "auto_completion.h" #include "charset.h" #include "fileutil.h" #include "gfileutil.h" #include "gtk2-compat.h" #include "prefs.h" #define MAX_VISIBLE_ROWS 8 #define CLIST_ROW_PAD 5 static gchar *ac_dir = NULL; static gchar *ac_path = NULL; static gchar ac_show_dot = FALSE; static GList *ac_subdirs = NULL; static GList *ac_alternatives = NULL; static GtkWidget *ac_window = NULL; static GtkWidget *ac_clist = NULL; static GtkWidget *ac_entry = NULL; #ifdef ENABLE_TREEVIEW static GtkListStore *ac_list_store = NULL; #endif /* ENABLE_TREEVIEW */ static void ac_dir_free (void) { if (!ac_dir) return; g_free (ac_dir); ac_dir = NULL; } static void ac_path_free (void) { if (!ac_path) return; g_free (ac_path); ac_path = NULL; } static void ac_subdirs_free (void) { if (!ac_subdirs) return; g_list_foreach (ac_subdirs, (GFunc) g_free, NULL); g_list_free (ac_subdirs); ac_subdirs = NULL; } static void ac_alternatives_free (void) { if (!ac_alternatives) return; g_list_foreach (ac_alternatives, (GFunc) g_free, NULL); g_list_free (ac_alternatives); ac_alternatives = NULL; } void auto_compl_reset (void) { ac_dir_free (); ac_path_free (); ac_subdirs_free (); ac_alternatives_free (); } gint auto_compl_get_n_alternatives (const gchar *path) { gchar *dir; const gchar *filename; gint path_len; GList *scan; gint n; gint flags = GETDIR_FOLLOW_SYMLINK; gboolean show_dot; if (path == NULL) return 0; filename = g_basename (path); if (filename && filename[0] == '.') { show_dot = TRUE; flags = flags | GETDIR_READ_DOT; } else { show_dot = FALSE; } if (strcmp (path, "/") == 0) dir = g_strdup ("/"); else dir = g_dirname (path); if (!isdir (dir)) { g_free (dir); return 0; } if ((ac_dir == NULL) || strcmp (dir, ac_dir) || ac_show_dot != show_dot) { ac_dir_free (); ac_subdirs_free (); ac_dir = charset_to_internal (dir, conf.charset_filename, conf.charset_auto_detect_fn, conf.charset_filename_mode); if (!ac_dir && dir) ac_dir = g_strdup (dir); get_dir (dir, flags, NULL, &ac_subdirs); if (ac_show_dot != show_dot) ac_show_dot = show_dot; } ac_path_free (); ac_alternatives_free (); ac_path = g_strdup (path); path_len = strlen (ac_path); n = 0; for (scan = ac_subdirs; scan; scan = scan->next) { const gchar *subdir = (gchar*) scan->data; gchar *subdir_internal; subdir_internal = charset_to_internal (subdir, conf.charset_filename, conf.charset_auto_detect_fn, conf.charset_filename_mode); if (!subdir_internal && subdir) subdir_internal = g_strdup (subdir); if (strncmp (path, subdir_internal, path_len) != 0) { g_free (subdir_internal); continue; } ac_alternatives = g_list_prepend (ac_alternatives, subdir_internal); n++; } g_free (dir); ac_alternatives = g_list_reverse (ac_alternatives); return n; } static gint get_common_prefix_length (void) { gint n; GList *scan; gchar c1, c2; g_return_val_if_fail (ac_path != NULL, 0); g_return_val_if_fail (ac_alternatives != NULL, 0); /* if the number of alternatives is 1 return its length. */ if (ac_alternatives->next == NULL) return strlen ((gchar*) ac_alternatives->data); n = strlen (ac_path); while (TRUE) { scan = ac_alternatives; c1 = ((gchar*) scan->data) [n]; if (c1 == 0) return n; /* check that all other alternatives have the same * character at position n. */ scan = scan->next; for (; scan; scan = scan->next) { c2 = ((gchar*) scan->data) [n]; if (c1 != c2) return n; } n++; } return -1; } gchar * auto_compl_get_common_prefix (void) { gchar *alternative; gint n; if (ac_path == NULL) return NULL; if (ac_alternatives == NULL) return NULL; n = get_common_prefix_length (); alternative = (gchar*) ac_alternatives->data; return g_strndup (alternative, n); } GList * auto_compl_get_alternatives (void) { return ac_alternatives; } static gboolean ac_window_button_press_cb (GtkWidget *widget, GdkEventButton *event, gpointer *data) { GtkWidget *event_widget; gint x, y, w, h; event_widget = gtk_get_event_widget ((GdkEvent *)event); gdk_window_get_origin (ac_window->window, &x, &y); gdk_window_get_size (ac_window->window, &w, &h); /* Checks if the button press happened inside the window, * if not closes the window. */ if ((event->x >= 0) && (event->x <= w) && (event->y >= 0) && (event->y <= h)) { /* In window. */ return FALSE; } auto_compl_hide_alternatives (); return TRUE; } static gboolean ac_window_key_press_cb (GtkWidget *widget, GdkEventKey *event, gpointer *data) { if (event->keyval == GDK_Escape) { auto_compl_hide_alternatives (); return TRUE; } /* allow keyboard navigation in the alternatives clist */ if (event->keyval == GDK_Up || event->keyval == GDK_Down || event->keyval == GDK_Page_Up || event->keyval == GDK_Page_Down || event->keyval == GDK_space) return FALSE; if (event->keyval == GDK_Return) { event->keyval = GDK_space; return FALSE; } auto_compl_hide_alternatives (); gtk_widget_event (ac_entry, (GdkEvent*) event); return TRUE; } #ifdef ENABLE_TREEVIEW static void cb_tree_cursor_changed (GtkTreeView *treeview, gpointer data) { GtkTreeSelection *selection; GtkTreeModel *model; GtkTreeIter iter; gchar *text, *full_path; g_return_if_fail (GTK_IS_TREE_VIEW (treeview)); selection = gtk_tree_view_get_selection (treeview); gtk_tree_selection_get_selected (selection, &model, &iter); gtk_tree_model_get (model, &iter, 0, &text, -1); if (!text) return; full_path = g_strconcat (ac_dir, "/", text, NULL); gtk_entry_set_text (GTK_ENTRY (ac_entry), full_path); g_free (text); g_free (full_path); gtk_editable_set_position (GTK_EDITABLE (ac_entry), -1); } #else /* ENABLE_TREEVIEW */ static void ac_clist_select_row_cb (GtkCList *clist, gint row, gint column, GdkEventButton *event, gpointer *data) { gchar *text; gchar *full_path; auto_compl_hide_alternatives (); gtk_clist_get_text (GTK_CLIST (ac_clist), row, column, &text); full_path = g_strconcat (ac_dir, "/", text, NULL); gtk_entry_set_text (GTK_ENTRY (ac_entry), full_path); g_free (full_path); #ifdef USE_GTK2 gtk_editable_set_position (GTK_EDITABLE (ac_entry), -1); #endif } #endif /* ENABLE_TREEVIEW */ /* displays a list of alternatives under the entry widget. */ void auto_compl_show_alternatives (GtkWidget *entry) { gint x, y, w, h; GList *scan; gint n, width; if (ac_window == NULL) { GtkWidget *scroll; GtkWidget *frame; ac_window = gtk_window_new (GTK_WINDOW_POPUP); #ifdef ENABLE_TREEVIEW { GtkTreeViewColumn *col; GtkCellRenderer *render; ac_list_store = gtk_list_store_new (1, G_TYPE_STRING); ac_clist = gtk_tree_view_new_with_model (GTK_TREE_MODEL (ac_list_store)); gtk_tree_view_set_rules_hint (GTK_TREE_VIEW (ac_clist), TRUE); gtk_tree_view_set_headers_visible (GTK_TREE_VIEW (ac_clist), FALSE); col = gtk_tree_view_column_new(); render = gtk_cell_renderer_text_new (); gtk_tree_view_column_pack_start (col, render, FALSE); gtk_tree_view_column_add_attribute (col, render, "text", 0); gtk_tree_view_append_column (GTK_TREE_VIEW (ac_clist), col); } #else /* ENABLE_TREEVIEW */ { GdkFont *font; gint row_height; ac_clist = gtk_clist_new (1); font = gtk_style_get_font (GTK_WIDGET (ac_clist)->style); row_height = (font->ascent + font->descent + CLIST_ROW_PAD); gtk_clist_set_row_height (GTK_CLIST (ac_clist), row_height); } #endif /* ENABLE_TREEVIEW */ scroll = gtk_scrolled_window_new (NULL, NULL); gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scroll), GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC); frame = gtk_frame_new (NULL); gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN); gtk_container_add (GTK_CONTAINER (ac_window), frame); gtk_container_add (GTK_CONTAINER (frame), scroll); gtk_container_add (GTK_CONTAINER (scroll), ac_clist); gtk_signal_connect (GTK_OBJECT (ac_window), "button-press-event", GTK_SIGNAL_FUNC(ac_window_button_press_cb), NULL); gtk_signal_connect (GTK_OBJECT (ac_window), "key-press-event", GTK_SIGNAL_FUNC(ac_window_key_press_cb), NULL); #ifdef ENABLE_TREEVIEW g_signal_connect (G_OBJECT (ac_clist), "cursor_changed", G_CALLBACK (cb_tree_cursor_changed), NULL); #else /* ENABLE_TREEVIEW */ gtk_signal_connect (GTK_OBJECT (ac_clist), "select_row", GTK_SIGNAL_FUNC(ac_clist_select_row_cb), NULL); #endif /* ENABLE_TREEVIEW */ } ac_entry = entry; width = 0; n = 0; #ifdef ENABLE_TREEVIEW { GtkTreeIter iter; gtk_list_store_clear (ac_list_store); for (scan = ac_alternatives; scan; scan = scan->next) { gtk_list_store_append (ac_list_store, &iter); gtk_list_store_set (ac_list_store, &iter, 0, g_basename (scan->data), -1); if (n == 0) { GtkTreeSelection *selection; GtkTreePath *treepath; selection = gtk_tree_view_get_selection (GTK_TREE_VIEW (ac_clist)); treepath = gtk_tree_model_get_path (GTK_TREE_MODEL (ac_list_store), &iter); gtk_tree_selection_select_path (selection, treepath); gtk_tree_view_scroll_to_cell (GTK_TREE_VIEW (ac_clist), treepath, NULL, TRUE, 0.0, 0.0); gtk_tree_path_free (treepath); } n++; } } #else /* ENABLE_TREEVIEW */ gtk_clist_freeze (GTK_CLIST (ac_clist)); gtk_clist_clear (GTK_CLIST (ac_clist)); for (scan = ac_alternatives; scan; scan = scan->next) { gchar *text[] = {NULL, NULL}; text[0] = (gchar *) g_basename (scan->data); gtk_clist_append (GTK_CLIST (ac_clist), text); width = MAX (width, gdk_string_width ( gtk_style_get_font (GTK_WIDGET (ac_clist)->style), text[0])); n++; } gtk_clist_set_column_width (GTK_CLIST (ac_clist), 0, width); gtk_clist_thaw (GTK_CLIST (ac_clist)); #endif /* ENABLE_TREEVIEW */ gdk_window_get_geometry (entry->window, &x, &y, &w, &h, NULL); gdk_window_get_deskrelative_origin (entry->window, &x, &y); gtk_widget_set_uposition (ac_window, x, y + h); gtk_widget_set_usize (ac_window, w, 200); gtk_widget_show_all (ac_window); gdk_pointer_grab (ac_window->window, TRUE, (GDK_POINTER_MOTION_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK), NULL, NULL, GDK_CURRENT_TIME); gdk_keyboard_grab (ac_window->window, FALSE, GDK_CURRENT_TIME); gtk_grab_add (ac_window); } void auto_compl_hide_alternatives (void) { if (ac_window && GTK_WIDGET_VISIBLE (ac_window)) { gdk_pointer_ungrab (GDK_CURRENT_TIME); gdk_keyboard_ungrab (GDK_CURRENT_TIME); gtk_grab_remove (ac_window); gtk_widget_hide (ac_window); } }