medit/moo/moofileview/moofileentry.c

1605 lines
50 KiB
C

/*
* moofileentry.c
*
* Copyright (C) 2004-2006 by Yevgen Muntyan <muntyan@math.tamu.edu>
*
* 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.
*
* See COPYING file that comes with this distribution.
*/
/* XXX use MooCombo */
#define MOO_FILE_SYSTEM_COMPILATION
#include "moofileentry.h"
#include "moofilesystem.h"
#include "moofoldermodel.h"
#include "mooutils/moomarshals.h"
#include "mooutils/mooutils-gobject.h"
#include "moofileview-private.h"
#include <gdk/gdkkeysyms.h>
#include <gtk/gtk.h>
#define COMPLETION_POPUP_LEN 15
#define COLUMN_FILE MOO_FOLDER_MODEL_COLUMN_FILE
#ifndef __WIN32__
#define CASE_SENSITIVE_DEFAULT TRUE
#else /* __WIN32__ */
#define CASE_SENSITIVE_DEFAULT FALSE
#endif /* __WIN32__ */
struct _MooFileEntryCompletionPrivate {
MooFileSystem *fs;
char *current_dir;
GtkEntry *entry;
gboolean managed; /* do not connect to entry signals */
gboolean enabled;
gboolean do_complete;
MooFileVisibleFunc visible_func;
gpointer visible_func_data;
gboolean dirs_only;
gboolean show_hidden;
GtkWidget *popup;
GtkTreeView *treeview;
GtkTreeViewColumn *column;
GtkTreeModel *model;
TextFuncs text_funcs;
gboolean case_sensitive;
gboolean recheck_prefix;
char *dirname;
char *display_dirname;
char *display_basename;
guint display_basename_len;
char *real_text;
gboolean walking_list;
};
static void moo_file_entry_completion_finalize (GObject *object);
static void moo_file_entry_completion_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec);
static void moo_file_entry_completion_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec);
static void completion_create_popup (MooFileEntryCompletion *cmpl);
static void completion_connect_folder (MooFileEntryCompletion *cmpl,
MooFolder *folder);
static void completion_disconnect_folder (MooFileEntryCompletion *cmpl);
static void completion_popup (MooFileEntryCompletion *cmpl);
static void completion_popdown (MooFileEntryCompletion *cmpl);
static gboolean completion_resize_popup (MooFileEntryCompletion *cmpl);
static void completion_cell_data_func (GtkTreeViewColumn *column,
GtkCellRenderer *cell,
GtkTreeModel *model,
GtkTreeIter *iter,
MooFileEntryCompletion *cmpl);
static gboolean completion_visible_func (GtkTreeModel *model,
GtkTreeIter *iter,
MooFileEntryCompletion *cmpl);
static gboolean completion_entry_focus_out (GtkEntry *entry,
MooFileEntryCompletion *cmpl);
static gboolean completion_popup_button_press (GtkWidget *popup_window,
GdkEventButton *event,
MooFileEntryCompletion *cmpl);
static gboolean completion_popup_key_press (GtkWidget *popup_window,
GdkEventKey *event,
MooFileEntryCompletion *cmpl);
static gboolean completion_list_button_press (MooFileEntryCompletion *cmpl,
GdkEventButton *event);
static void completion_entry_changed (GtkEntry *entry,
MooFileEntryCompletion *cmpl);
static gboolean completion_entry_key_press (GtkEntry *entry,
GdkEventKey *event,
MooFileEntryCompletion *cmpl);
static gboolean completion_parse_text (MooFileEntryCompletion *cmpl,
gboolean refilter);
static char *completion_make_text (MooFileEntryCompletion *cmpl,
MooFile *file);
static int completion_get_selected (MooFileEntryCompletion *cmpl);
static void completion_set_case_sensitive (MooFileEntryCompletion *cmpl,
gboolean case_sensitive);
static void completion_set_file_system (MooFileEntryCompletion *cmpl,
MooFileSystem *file_system);
static void completion_finished (MooFileEntryCompletion *cmpl);
static gboolean completion_default_visible_func (MooFile *file);
static void completion_entry_destroyed (MooFileEntryCompletion *cmpl);
static void completion_finish (MooFileEntryCompletion *cmpl,
const char *text);
/* MOO_TYPE_FILE_ENTRY_COMPLETION */
G_DEFINE_TYPE (MooFileEntryCompletion, _moo_file_entry_completion, G_TYPE_OBJECT)
enum {
PROP_0,
PROP_ENABLE_COMPLETION,
PROP_DIRECTORIES_ONLY,
PROP_SHOW_HIDDEN,
PROP_FILE_SYSTEM,
PROP_CURRENT_DIR,
PROP_CASE_SENSITIVE,
PROP_ENTRY
};
enum {
FINISHED,
LAST_SIGNAL
};
static guint signals[LAST_SIGNAL];
static void
_moo_file_entry_completion_class_init (MooFileEntryCompletionClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
gobject_class->finalize = moo_file_entry_completion_finalize;
gobject_class->set_property = moo_file_entry_completion_set_property;
gobject_class->get_property = moo_file_entry_completion_get_property;
g_object_class_install_property (gobject_class,
PROP_ENABLE_COMPLETION,
g_param_spec_boolean ("enable-completion",
"enable-completion",
"enable-completion",
TRUE,
G_PARAM_CONSTRUCT | G_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_DIRECTORIES_ONLY,
g_param_spec_boolean ("directories-only",
"directories-only",
"directories-only",
FALSE,
G_PARAM_CONSTRUCT | G_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_SHOW_HIDDEN,
g_param_spec_boolean ("show-hidden",
"show-hidden",
"show-hidden",
TRUE,
G_PARAM_CONSTRUCT | G_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_ENTRY,
g_param_spec_object ("entry",
"entry",
"entry",
GTK_TYPE_ENTRY,
G_PARAM_CONSTRUCT | G_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_CASE_SENSITIVE,
g_param_spec_boolean ("case-sensitive",
"case-sensitive",
"case-sensitive",
CASE_SENSITIVE_DEFAULT,
G_PARAM_CONSTRUCT | G_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_CURRENT_DIR,
g_param_spec_string ("current-dir",
"current-dir",
"current-dir",
NULL,
G_PARAM_CONSTRUCT | G_PARAM_READWRITE));
g_object_class_install_property (gobject_class,
PROP_FILE_SYSTEM,
g_param_spec_object ("file-system",
"file-system",
"file-system",
MOO_TYPE_FILE_SYSTEM,
G_PARAM_CONSTRUCT | G_PARAM_READWRITE));
signals[FINISHED] =
moo_signal_new_cb ("finished",
G_OBJECT_CLASS_TYPE (klass),
G_SIGNAL_RUN_LAST | G_SIGNAL_ACTION,
G_CALLBACK (completion_finished),
NULL, NULL,
_moo_marshal_VOID__VOID,
G_TYPE_NONE, 0);
}
static void
_moo_file_entry_completion_init (MooFileEntryCompletion *cmpl)
{
GtkTreeModel *model;
cmpl->priv = g_new0 (MooFileEntryCompletionPrivate, 1);
/* XXX */
cmpl->priv->managed = FALSE;
cmpl->priv->enabled = TRUE;
cmpl->priv->do_complete = FALSE;
cmpl->priv->case_sensitive = CASE_SENSITIVE_DEFAULT;
cmpl->priv->visible_func = (MooFileVisibleFunc) completion_default_visible_func;
if (cmpl->priv->case_sensitive)
{
cmpl->priv->text_funcs.strcmp_func = strcmp_func;
cmpl->priv->text_funcs.strncmp_func = strncmp_func;
cmpl->priv->text_funcs.normalize_func = normalize_func;
}
else
{
cmpl->priv->text_funcs.strcmp_func = case_strcmp_func;
cmpl->priv->text_funcs.strncmp_func = case_strncmp_func;
cmpl->priv->text_funcs.normalize_func = case_normalize_func;
}
model = _moo_folder_model_new (NULL);
cmpl->priv->model = _moo_folder_filter_new (MOO_FOLDER_MODEL (model));
gtk_tree_model_filter_set_visible_func (GTK_TREE_MODEL_FILTER (cmpl->priv->model),
(GtkTreeModelFilterVisibleFunc) completion_visible_func,
cmpl, NULL);
completion_create_popup (cmpl);
g_object_unref (model);
}
static void
moo_file_entry_completion_finalize (GObject *object)
{
MooFileEntryCompletion *cmpl = MOO_FILE_ENTRY_COMPLETION (object);
if (cmpl->priv->entry)
{
if (!cmpl->priv->managed)
{
g_signal_handlers_disconnect_by_func (cmpl->priv->entry,
(gpointer) completion_entry_changed,
cmpl);
g_signal_handlers_disconnect_by_func (cmpl->priv->entry,
(gpointer) completion_entry_key_press,
cmpl);
}
g_object_weak_unref (G_OBJECT (cmpl->priv->entry),
(GWeakNotify) completion_entry_destroyed,
cmpl);
}
if (cmpl->priv->fs)
g_object_unref (cmpl->priv->fs);
g_free (cmpl->priv->current_dir);
completion_disconnect_folder (cmpl);
g_free (cmpl->priv->dirname);
gtk_widget_destroy (cmpl->priv->popup);
g_object_unref (cmpl->priv->model);
g_free (cmpl->priv);
cmpl->priv = NULL;
G_OBJECT_CLASS (_moo_file_entry_completion_parent_class)->finalize (object);
}
static void
moo_file_entry_completion_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec)
{
MooFileEntryCompletion *cmpl = MOO_FILE_ENTRY_COMPLETION (object);
switch (prop_id)
{
case PROP_ENTRY:
_moo_file_entry_completion_set_entry (cmpl, g_value_get_object (value));
break;
case PROP_ENABLE_COMPLETION:
cmpl->priv->enabled = g_value_get_boolean (value);
g_object_notify (object, "enable-completion");
break;
case PROP_DIRECTORIES_ONLY:
cmpl->priv->dirs_only = g_value_get_boolean (value);
g_object_notify (object, "directories-only");
break;
case PROP_SHOW_HIDDEN:
cmpl->priv->show_hidden = g_value_get_boolean (value);
g_object_notify (object, "show-hidden");
break;
case PROP_CURRENT_DIR:
g_free (cmpl->priv->current_dir);
cmpl->priv->current_dir = g_strdup (g_value_get_string (value));
g_object_notify (object, "current-dir");
break;
case PROP_FILE_SYSTEM:
completion_set_file_system (cmpl, g_value_get_object (value));
break;
case PROP_CASE_SENSITIVE:
completion_set_case_sensitive (cmpl, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
moo_file_entry_completion_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
MooFileEntryCompletion *cmpl = MOO_FILE_ENTRY_COMPLETION (object);
switch (prop_id)
{
case PROP_ENTRY:
g_value_set_object (value, cmpl->priv->entry);
break;
case PROP_ENABLE_COMPLETION:
g_value_set_boolean (value, cmpl->priv->enabled);
break;
case PROP_DIRECTORIES_ONLY:
g_value_set_boolean (value, cmpl->priv->dirs_only);
break;
case PROP_SHOW_HIDDEN:
g_value_set_boolean (value, cmpl->priv->show_hidden);
break;
case PROP_CURRENT_DIR:
g_value_set_string (value, cmpl->priv->current_dir);
break;
case PROP_FILE_SYSTEM:
g_value_set_object (value, cmpl->priv->fs);
break;
case PROP_CASE_SENSITIVE:
g_value_set_boolean (value, cmpl->priv->case_sensitive);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
#define TAKE_STRING(p,s) \
G_STMT_START { \
g_free (p); \
p = s; \
s = NULL; \
} G_STMT_END
#define DELETE_MEM(p) \
G_STMT_START { \
g_free (p); \
p = NULL; \
} G_STMT_END
static void
completion_finished (MooFileEntryCompletion *cmpl)
{
cmpl->priv->do_complete = FALSE;
}
static void
completion_finish (MooFileEntryCompletion *cmpl,
const char *text)
{
completion_popdown (cmpl);
if (text)
{
gtk_entry_set_text (GTK_ENTRY (cmpl->priv->entry), text);
gtk_editable_set_position (GTK_EDITABLE (cmpl->priv->entry), -1);
}
g_signal_emit (cmpl, signals[FINISHED], 0);
}
static void
completion_entry_changed (G_GNUC_UNUSED GtkEntry *entry,
MooFileEntryCompletion *cmpl)
{
int n_items, selected;
GtkTreePath *path;
if (!cmpl->priv->enabled)
return;
if (cmpl->priv->walking_list)
{
cmpl->priv->walking_list = FALSE;
DELETE_MEM (cmpl->priv->real_text);
}
if (completion_get_selected (cmpl) >= 0)
{
GtkTreeView *treeview = cmpl->priv->treeview;
GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
gtk_tree_selection_unselect_all (selection);
}
if (!cmpl->priv->do_complete)
return;
if (!completion_parse_text (cmpl, FALSE))
goto finish;
gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (cmpl->priv->model));
n_items = gtk_tree_model_iter_n_children (cmpl->priv->model, NULL);
if (!n_items)
goto finish;
completion_resize_popup (cmpl);
selected = completion_get_selected (cmpl);
if (selected < 0)
selected = 0;
path = gtk_tree_path_new_from_indices (selected, -1);
gtk_tree_view_scroll_to_cell (cmpl->priv->treeview, path, NULL, FALSE, 0, 0);
gtk_tree_path_free (path);
cmpl->priv->recheck_prefix = TRUE;
return;
finish:
DELETE_MEM (cmpl->priv->display_dirname);
DELETE_MEM (cmpl->priv->display_basename);
DELETE_MEM (cmpl->priv->dirname);
completion_finish (cmpl, NULL);
}
static char*
completion_make_text (MooFileEntryCompletion *cmpl,
MooFile *file)
{
g_return_val_if_fail (file != NULL, NULL);
g_return_val_if_fail (cmpl->priv->display_dirname != NULL, NULL);
if (MOO_FILE_IS_DIR (file))
return g_strdup_printf ("%s%s%c", cmpl->priv->display_dirname,
_moo_file_display_name (file),
G_DIR_SEPARATOR);
else
return g_strdup_printf ("%s%s", cmpl->priv->display_dirname,
_moo_file_display_name (file));
}
static gboolean
completion_parse_text (MooFileEntryCompletion *cmpl,
gboolean refilter)
{
const char *text;
GError *error = NULL;
MooFolder *folder;
gboolean result = FALSE;
guint text_len;
char *path = NULL, *dirname = NULL;
char *display_dirname = NULL, *display_basename = NULL;
text = gtk_entry_get_text (GTK_ENTRY (cmpl->priv->entry));
if (!text || !text[0])
return FALSE;
path = _moo_file_system_get_absolute_path (cmpl->priv->fs, text,
cmpl->priv->current_dir);
if (!path)
return FALSE;
if (!_moo_file_system_parse_path (cmpl->priv->fs,
path, &dirname, &display_dirname,
&display_basename, &error))
{
g_message ("%s: could not parse path '%s'", G_STRLOC, path);
g_message ("%s: %s", G_STRLOC, error->message);
goto out;
}
TAKE_STRING (cmpl->priv->display_basename, display_basename);
cmpl->priv->display_basename_len = strlen (cmpl->priv->display_basename);
text_len = strlen (text);
#if 0
/* this was needed in the widget */
if (text_len <= cmpl->priv->display_basename_len)
{
g_warning ("%s: something wrong", G_STRLOC);
goto out;
}
#endif
if (!cmpl->priv->dirname || strcmp (cmpl->priv->dirname, dirname))
{
completion_disconnect_folder (cmpl);
DELETE_MEM (cmpl->priv->dirname);
folder = _moo_file_system_get_folder (cmpl->priv->fs,
dirname, MOO_FILE_HAS_STAT,
&error);
if (!folder)
{
g_message ("%s: could not get folder '%s'", G_STRLOC, dirname);
g_message ("%s: %s", G_STRLOC, error->message);
g_error_free (error);
goto out;
}
g_free (cmpl->priv->display_dirname);
cmpl->priv->display_dirname = g_strndup (text, text_len - cmpl->priv->display_basename_len);
TAKE_STRING (cmpl->priv->dirname, dirname);
completion_connect_folder (cmpl, folder);
}
else if (refilter)
{
gtk_tree_model_filter_refilter (GTK_TREE_MODEL_FILTER (cmpl->priv->model));
}
result = TRUE;
out:
g_free (path);
g_free (dirname);
g_free (display_dirname);
g_free (display_basename);
return result;
}
static void
completion_tab_key (MooFileEntryCompletion *cmpl)
{
int n_items;
GtkTreePath *path;
GtkTreeIter iter;
MooFile *file = NULL;
char *text;
GString *prefix;
gboolean unique;
if (!completion_parse_text (cmpl, TRUE))
return;
n_items = gtk_tree_model_iter_n_children (cmpl->priv->model, NULL);
if (!n_items)
return;
cmpl->priv->do_complete = TRUE;
if (n_items == 1)
{
path = gtk_tree_path_new_from_indices (0, -1);
gtk_tree_model_get_iter (cmpl->priv->model, &iter, path);
gtk_tree_path_free (path);
gtk_tree_model_get (cmpl->priv->model, &iter, COLUMN_FILE, &file, -1);
g_return_if_fail (file != NULL);
text = completion_make_text (cmpl, file);
completion_finish (cmpl, text);
g_free (text);
return;
}
if (cmpl->priv->display_basename && cmpl->priv->display_basename[0])
{
prefix = model_find_max_prefix (cmpl->priv->model,
cmpl->priv->display_basename,
&cmpl->priv->text_funcs, &unique, NULL);
if (!prefix)
{
g_critical ("%s: oops", G_STRLOC);
}
else if (prefix->len > cmpl->priv->display_basename_len)
{
text = g_strdup_printf ("%s%s", cmpl->priv->display_dirname,
prefix->str);
gtk_entry_set_text (GTK_ENTRY (cmpl->priv->entry), text);
gtk_editable_set_position (GTK_EDITABLE (cmpl->priv->entry), -1);
g_free (text);
}
g_string_free (prefix, TRUE);
}
/* XXX scroll to selected */
completion_popup (cmpl);
}
static gboolean
completion_popup_tab_key_press (MooFileEntryCompletion *cmpl)
{
int n_items;
GtkTreePath *path;
GtkTreeIter iter;
MooFile *file = NULL;
char *text;
GString *prefix;
gboolean unique;
cmpl->priv->recheck_prefix = FALSE;
n_items = gtk_tree_model_iter_n_children (cmpl->priv->model, NULL);
if (!n_items)
{
completion_popdown (cmpl);
g_return_val_if_reached (TRUE);
}
if (n_items == 1)
{
path = gtk_tree_path_new_from_indices (0, -1);
gtk_tree_model_get_iter (cmpl->priv->model, &iter, path);
gtk_tree_path_free (path);
gtk_tree_model_get (cmpl->priv->model, &iter, COLUMN_FILE, &file, -1);
g_return_val_if_fail (file != NULL, FALSE);
text = completion_make_text (cmpl, file);
completion_finish (cmpl, text);
_moo_file_unref (file);
g_free (text);
return TRUE;
}
if (cmpl->priv->display_basename && cmpl->priv->display_basename[0])
{
prefix = model_find_max_prefix (cmpl->priv->model, cmpl->priv->display_basename,
&cmpl->priv->text_funcs, &unique, NULL);
if (!prefix)
{
g_critical ("%s: oops", G_STRLOC);
return FALSE;
}
if (prefix->len > cmpl->priv->display_basename_len)
{
text = g_strdup_printf ("%s%s", cmpl->priv->display_dirname,
prefix->str);
completion_finish (cmpl, text);
g_free (text);
g_string_free (prefix, TRUE);
return TRUE;
}
g_string_free (prefix, TRUE);
}
return FALSE;
}
static void
completion_set_case_sensitive (MooFileEntryCompletion *cmpl,
gboolean case_sensitive)
{
case_sensitive = case_sensitive ? TRUE : FALSE;
if (case_sensitive != cmpl->priv->case_sensitive)
{
cmpl->priv->case_sensitive = case_sensitive;
if (case_sensitive)
{
cmpl->priv->text_funcs.strcmp_func = strcmp_func;
cmpl->priv->text_funcs.strncmp_func = strncmp_func;
cmpl->priv->text_funcs.normalize_func = normalize_func;
}
else
{
cmpl->priv->text_funcs.strcmp_func = case_strcmp_func;
cmpl->priv->text_funcs.strncmp_func = case_strncmp_func;
cmpl->priv->text_funcs.normalize_func = case_normalize_func;
}
g_object_notify (G_OBJECT (cmpl), "case-sensitive");
}
}
static void
completion_popup (MooFileEntryCompletion *cmpl)
{
GtkWidget *window;
GtkTreeSelection *selection;
if (GTK_WIDGET_MAPPED (cmpl->priv->popup))
return;
window = gtk_widget_get_toplevel (GTK_WIDGET (cmpl->priv->entry));
g_return_if_fail (GTK_IS_WINDOW (window));
gtk_widget_realize (cmpl->priv->popup);
GTK_WIDGET_SET_FLAGS (GTK_WIDGET (cmpl->priv->treeview), GTK_CAN_FOCUS);
if (GTK_WINDOW (window)->group)
gtk_window_group_add_window (GTK_WINDOW (window)->group,
GTK_WINDOW (cmpl->priv->popup));
gtk_window_set_modal (GTK_WINDOW (cmpl->priv->popup), TRUE);
completion_resize_popup (cmpl);
gtk_widget_show (cmpl->priv->popup);
gtk_widget_ensure_style (GTK_WIDGET (cmpl->priv->treeview));
gtk_widget_modify_bg (GTK_WIDGET (cmpl->priv->treeview), GTK_STATE_ACTIVE,
&GTK_WIDGET(cmpl->priv->treeview)->style->base[GTK_STATE_SELECTED]);
gtk_widget_modify_base (GTK_WIDGET (cmpl->priv->treeview), GTK_STATE_ACTIVE,
&GTK_WIDGET(cmpl->priv->treeview)->style->base[GTK_STATE_SELECTED]);
gtk_grab_add (cmpl->priv->popup);
gdk_pointer_grab (cmpl->priv->popup->window, TRUE,
GDK_BUTTON_PRESS_MASK |
GDK_BUTTON_RELEASE_MASK |
GDK_POINTER_MOTION_MASK,
NULL, NULL, GDK_CURRENT_TIME);
g_signal_connect (cmpl->priv->entry, "focus-out-event",
G_CALLBACK (completion_entry_focus_out), cmpl);
g_signal_connect (cmpl->priv->popup, "button-press-event",
G_CALLBACK (completion_popup_button_press), cmpl);
g_signal_connect (cmpl->priv->popup, "key-press-event",
G_CALLBACK (completion_popup_key_press), cmpl);
g_signal_connect_swapped (cmpl->priv->treeview, "button-press-event",
G_CALLBACK (completion_list_button_press),
cmpl);
/* treeview selects something on focus */
selection = gtk_tree_view_get_selection (cmpl->priv->treeview);
gtk_tree_selection_unselect_all (selection);
}
static gboolean
completion_entry_focus_out (G_GNUC_UNUSED GtkEntry *entry,
MooFileEntryCompletion *cmpl)
{
completion_popdown (cmpl);
return FALSE;
}
static void
completion_popdown (MooFileEntryCompletion *cmpl)
{
if (!GTK_WIDGET_MAPPED (cmpl->priv->popup))
return;
DELETE_MEM (cmpl->priv->real_text);
cmpl->priv->walking_list = FALSE;
g_signal_handlers_disconnect_by_func (cmpl->priv->entry,
(gpointer) completion_entry_focus_out,
cmpl);
g_signal_handlers_disconnect_by_func (cmpl->priv->popup,
(gpointer) completion_popup_button_press,
cmpl);
g_signal_handlers_disconnect_by_func (cmpl->priv->popup,
(gpointer) completion_popup_key_press,
cmpl);
g_signal_handlers_disconnect_by_func (cmpl->priv->treeview,
(gpointer) completion_list_button_press,
cmpl);
gdk_pointer_ungrab (GDK_CURRENT_TIME);
gtk_grab_remove (cmpl->priv->popup);
gtk_widget_hide (cmpl->priv->popup);
gtk_widget_unrealize (cmpl->priv->popup);
}
static gboolean
completion_popup_button_press (G_GNUC_UNUSED GtkWidget *popup_window,
GdkEventButton *event,
MooFileEntryCompletion *cmpl)
{
if (event->window == cmpl->priv->popup->window)
{
gint width, height;
gdk_drawable_get_size (GDK_DRAWABLE (event->window),
&width, &height);
if (event->x < 0 || event->x >= width ||
event->y < 0 || event->y >= height)
{
completion_popdown (cmpl);
}
}
else
{
g_print ("click\n");
completion_popdown (cmpl);
return FALSE;
}
return TRUE;
}
static void
completion_select_iter (MooFileEntryCompletion *cmpl,
GtkTreeIter *iter)
{
MooFile *file = NULL;
char *text;
gtk_tree_model_get (cmpl->priv->model, iter, COLUMN_FILE, &file, -1);
g_return_if_fail (file != NULL);
text = completion_make_text (cmpl, file);
completion_finish (cmpl, text);
g_free (text);
_moo_file_unref (file);
}
static gboolean
completion_list_button_press (MooFileEntryCompletion *cmpl,
GdkEventButton *event)
{
GtkTreePath *path;
GtkTreeIter iter;
if (gtk_tree_view_get_path_at_pos (cmpl->priv->treeview,
event->x, event->y,
&path, NULL, NULL, NULL))
{
gtk_tree_model_get_iter (cmpl->priv->model, &iter, path);
completion_select_iter (cmpl, &iter);
gtk_tree_path_free (path);
return TRUE;
}
g_print ("click click\n");
return FALSE;
}
static gboolean
completion_return_key (MooFileEntryCompletion *cmpl)
{
GtkTreeIter iter;
GtkTreeView *treeview = cmpl->priv->treeview;
GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
if (gtk_tree_selection_get_selected (selection, NULL, &iter))
completion_select_iter (cmpl, &iter);
else
completion_popdown (cmpl);
return TRUE;
}
static int
completion_get_selected (MooFileEntryCompletion *cmpl)
{
int ind;
GtkTreeIter iter;
GtkTreePath *path;
GtkTreeView *treeview = cmpl->priv->treeview;
GtkTreeSelection *selection = gtk_tree_view_get_selection (treeview);
if (gtk_tree_selection_get_selected (selection, NULL, &iter))
{
path = gtk_tree_model_get_path (cmpl->priv->model, &iter);
g_return_val_if_fail (path != NULL, -1);
ind = gtk_tree_path_get_indices (path)[0];
gtk_tree_path_free (path);
return ind;
}
else
{
return -1;
}
}
static void
completion_set_text_silent (MooFileEntryCompletion *cmpl,
const char *text)
{
g_signal_handlers_block_by_func (cmpl->priv->entry,
(gpointer) completion_entry_changed,
cmpl);
gtk_entry_set_text (cmpl->priv->entry, text);
gtk_editable_set_position (GTK_EDITABLE (cmpl->priv->entry), -1);
g_signal_handlers_unblock_by_func (cmpl->priv->entry,
(gpointer) completion_entry_changed,
cmpl);
}
static void
completion_move_selection (MooFileEntryCompletion *cmpl,
GdkEventKey *event)
{
int n_items, current_item, new_item = -1;
GtkTreePath *path;
GtkTreeSelection *selection;
n_items = gtk_tree_model_iter_n_children (cmpl->priv->model, NULL);
g_return_if_fail (n_items != 0);
current_item = completion_get_selected (cmpl);
switch (event->keyval)
{
case GDK_Down:
case GDK_KP_Down:
if (current_item < n_items - 1)
new_item = current_item + 1;
else
new_item = -1;
break;
case GDK_Up:
case GDK_KP_Up:
if (current_item < 0)
new_item = n_items - 1;
else
new_item = current_item - 1;
break;
case GDK_Page_Down:
case GDK_KP_Page_Down:
new_item = current_item + COMPLETION_POPUP_LEN - 1;
if (new_item >= n_items)
new_item = n_items - 1;
break;
case GDK_Page_Up:
case GDK_KP_Page_Up:
new_item = current_item - COMPLETION_POPUP_LEN + 1;
if (new_item < 0)
new_item = 0;
break;
case GDK_Tab:
case GDK_KP_Tab:
if (current_item < n_items - 1)
new_item = current_item + 1;
else
new_item = 0;
break;
case GDK_ISO_Left_Tab:
if (current_item <= 0)
new_item = n_items - 1;
else
new_item = current_item - 1;
break;
default:
g_return_if_reached ();
}
selection = gtk_tree_view_get_selection (cmpl->priv->treeview);
gtk_tree_selection_unselect_all (selection);
if (new_item >= 0)
{
GtkTreeIter iter;
MooFile *file = NULL;
char *new_text;
path = gtk_tree_path_new_from_indices (new_item, -1);
gtk_tree_selection_select_path (selection, path);
gtk_tree_view_scroll_to_cell (cmpl->priv->treeview, path, NULL, FALSE, 0, 0);
gtk_tree_model_get_iter (cmpl->priv->model, &iter, path);
gtk_tree_model_get (cmpl->priv->model, &iter, COLUMN_FILE, &file, -1);
g_return_if_fail (file != NULL);
new_text = completion_make_text (cmpl, file);
if (!cmpl->priv->walking_list)
{
cmpl->priv->real_text = g_strdup (gtk_entry_get_text (cmpl->priv->entry));
cmpl->priv->walking_list = TRUE;
}
completion_set_text_silent (cmpl, new_text);
gtk_tree_path_free (path);
g_free (new_text);
}
else if (cmpl->priv->walking_list)
{
completion_set_text_silent (cmpl, cmpl->priv->real_text);
DELETE_MEM (cmpl->priv->real_text);
cmpl->priv->walking_list = FALSE;
}
}
static gboolean
completion_popup_key_press (G_GNUC_UNUSED GtkWidget *popup,
GdkEventKey *event,
MooFileEntryCompletion *cmpl)
{
switch (event->keyval)
{
case GDK_Down:
case GDK_Up:
case GDK_KP_Down:
case GDK_KP_Up:
case GDK_Page_Down:
case GDK_Page_Up:
completion_move_selection (cmpl, event);
return TRUE;
case GDK_Tab:
case GDK_KP_Tab:
if (cmpl->priv->recheck_prefix && completion_popup_tab_key_press (cmpl))
return TRUE;
/* fallthrough */
case GDK_ISO_Left_Tab:
completion_move_selection (cmpl, event);
return TRUE;
case GDK_Escape:
completion_popdown (cmpl);
return TRUE;
case GDK_Return:
case GDK_ISO_Enter:
case GDK_KP_Enter:
return completion_return_key (cmpl);
default:
if (gtk_widget_event (GTK_WIDGET (cmpl->priv->entry), (GdkEvent*) event))
{
completion_entry_changed (cmpl->priv->entry, cmpl);
return TRUE;
}
else
{
return FALSE;
}
}
}
static void
completion_connect_folder (MooFileEntryCompletion *cmpl,
MooFolder *folder)
{
_moo_folder_filter_set_folder (MOO_FOLDER_FILTER (cmpl->priv->model), folder);
}
static void
completion_disconnect_folder (MooFileEntryCompletion *cmpl)
{
_moo_folder_filter_set_folder (MOO_FOLDER_FILTER (cmpl->priv->model), NULL);
}
static void
completion_create_popup (MooFileEntryCompletion *cmpl)
{
GtkCellRenderer *cell;
GtkWidget *scrolled_window, *frame;
GtkTreeSelection *selection;
cmpl->priv->popup = gtk_window_new (GTK_WINDOW_POPUP);
gtk_widget_set_size_request (cmpl->priv->popup, -1, -1);
gtk_window_set_default_size (GTK_WINDOW (cmpl->priv->popup), 1, 1);
gtk_window_set_resizable (GTK_WINDOW (cmpl->priv->popup), FALSE);
gtk_widget_add_events (cmpl->priv->popup, GDK_KEY_PRESS_MASK | GDK_BUTTON_PRESS_MASK);
cell = gtk_cell_renderer_text_new ();
cmpl->priv->column = gtk_tree_view_column_new ();
gtk_tree_view_column_pack_start (cmpl->priv->column, cell, TRUE);
gtk_tree_view_column_set_cell_data_func (cmpl->priv->column, cell,
(GtkTreeCellDataFunc) completion_cell_data_func,
cmpl, NULL);
cmpl->priv->treeview = GTK_TREE_VIEW (gtk_tree_view_new_with_model (cmpl->priv->model));
gtk_widget_set_size_request (GTK_WIDGET (cmpl->priv->treeview), -1, -1);
gtk_tree_view_append_column (cmpl->priv->treeview, cmpl->priv->column);
gtk_tree_view_set_headers_visible (cmpl->priv->treeview, FALSE);
// gtk_tree_view_set_hover_selection (cmpl->priv->treeview, TRUE);
selection = gtk_tree_view_get_selection (cmpl->priv->treeview);
gtk_tree_selection_set_mode (selection, GTK_SELECTION_SINGLE);
scrolled_window = gtk_scrolled_window_new (NULL, NULL);
gtk_widget_set_size_request (scrolled_window, -1, -1);
gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolled_window),
GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
// gtk_scrolled_window_set_shadow_type (GTK_SCROLLED_WINDOW (scrolled_window),
// GTK_SHADOW_ETCHED_IN);
gtk_container_add (GTK_CONTAINER (scrolled_window), GTK_WIDGET (cmpl->priv->treeview));
frame = gtk_frame_new (NULL);
gtk_frame_set_shadow_type (GTK_FRAME (frame), GTK_SHADOW_ETCHED_IN);
gtk_container_add (GTK_CONTAINER (frame), scrolled_window);
gtk_widget_show_all (frame);
gtk_container_add (GTK_CONTAINER (cmpl->priv->popup), frame);
}
static void
completion_cell_data_func (G_GNUC_UNUSED GtkTreeViewColumn *column,
GtkCellRenderer *cell,
GtkTreeModel *model,
GtkTreeIter *iter,
MooFileEntryCompletion *cmpl)
{
MooFile *file = NULL;
char *text;
if (!cmpl->priv->display_dirname)
return;
gtk_tree_model_get (model, iter, COLUMN_FILE, &file, -1);
g_return_if_fail (file != NULL);
text = completion_make_text (cmpl, file);
g_object_set (cell, "text", text, NULL);
g_free (text);
_moo_file_unref (file);
}
static gboolean
completion_visible_func (GtkTreeModel *model,
GtkTreeIter *iter,
MooFileEntryCompletion *cmpl)
{
MooFile *file = NULL;
gboolean visible = FALSE;
if (!cmpl->priv->display_basename)
return FALSE;
g_return_val_if_fail (cmpl->priv->visible_func != NULL, FALSE);
gtk_tree_model_get (model, iter, COLUMN_FILE, &file, -1);
g_return_val_if_fail (file != NULL, FALSE);
if (cmpl->priv->dirs_only && !MOO_FILE_IS_DIR (file))
{
visible = FALSE;
}
else if (!cmpl->priv->show_hidden &&
MOO_FILE_IS_HIDDEN (file) &&
!cmpl->priv->display_basename[0])
{
visible = FALSE;
}
else if (cmpl->priv->visible_func (file, cmpl->priv->visible_func_data))
{
visible = !cmpl->priv->text_funcs.strncmp_func (cmpl->priv->display_basename, file,
cmpl->priv->display_basename_len);
}
_moo_file_unref (file);
return visible ? TRUE : FALSE; /* #314335 */
}
/* XXX _gtk_entry_get_borders from gtkentry.c */
static void
entry_get_borders (GtkEntry *entry,
gint *xborder,
gint *yborder)
{
GtkWidget *widget = GTK_WIDGET (entry);
gint focus_width;
gboolean interior_focus;
gtk_widget_style_get (widget,
"interior-focus", &interior_focus,
"focus-line-width", &focus_width,
NULL);
if (entry->has_frame)
{
*xborder = widget->style->xthickness;
*yborder = widget->style->ythickness;
}
else
{
*xborder = 0;
*yborder = 0;
}
if (!interior_focus)
{
*xborder += focus_width;
*yborder += focus_width;
}
}
/* XXX gtk_entry_resize_popup from gtkentrycompletion.c */
static gboolean
completion_resize_popup (MooFileEntryCompletion *cmpl)
{
GtkWidget *widget = GTK_WIDGET (cmpl->priv->entry);
gint x, y;
gint matches, items, height, x_border, y_border;
GdkScreen *screen;
gint monitor_num;
GdkRectangle monitor;
GtkRequisition popup_req;
GtkRequisition entry_req;
gboolean above;
gint width, vert_separator = 0;
g_return_val_if_fail (GTK_WIDGET_REALIZED (cmpl->priv->entry), FALSE);
gdk_window_get_origin (widget->window, &x, &y);
/* XXX */
entry_get_borders (GTK_ENTRY (cmpl->priv->entry), &x_border, &y_border);
matches = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (cmpl->priv->model), NULL);
items = MIN (matches, COMPLETION_POPUP_LEN);
gtk_tree_view_column_cell_get_size (cmpl->priv->column, NULL,
NULL, NULL, NULL, &height);
screen = gtk_widget_get_screen (widget);
monitor_num = gdk_screen_get_monitor_at_window (screen, widget->window);
gdk_screen_get_monitor_geometry (screen, monitor_num, &monitor);
width = MIN (widget->allocation.width, monitor.width) - 2 * x_border;
gtk_widget_style_get (GTK_WIDGET (cmpl->priv->treeview), "vertical-separator",
&vert_separator, NULL);
gtk_widget_set_size_request (GTK_WIDGET (cmpl->priv->treeview), width,
items * (height + vert_separator));
gtk_widget_set_size_request (cmpl->priv->popup, -1, -1);
gtk_widget_size_request (cmpl->priv->popup, &popup_req);
gtk_widget_size_request (widget, &entry_req);
if (x < monitor.x)
x = monitor.x;
else if (x + popup_req.width > monitor.x + monitor.width)
x = monitor.x + monitor.width - popup_req.width;
if (y + entry_req.height + popup_req.height <= monitor.y + monitor.height)
{
y += entry_req.height;
above = FALSE;
}
else
{
y -= popup_req.height;
above = TRUE;
}
gtk_window_move (GTK_WINDOW (cmpl->priv->popup), x, y);
return above;
}
static void
completion_set_file_system (MooFileEntryCompletion *cmpl,
MooFileSystem *fs)
{
g_return_if_fail (!fs || MOO_IS_FILE_SYSTEM (fs));
if (fs && fs == cmpl->priv->fs)
return;
if (cmpl->priv->fs)
g_object_unref (cmpl->priv->fs);
if (fs)
cmpl->priv->fs = g_object_ref (fs);
else
cmpl->priv->fs = _moo_file_system_create ();
g_object_notify (G_OBJECT (cmpl), "file-system");
}
static void
completion_entry_destroyed (MooFileEntryCompletion *cmpl)
{
cmpl->priv->entry = NULL;
g_object_notify (G_OBJECT (cmpl), "entry");
}
void
_moo_file_entry_completion_set_entry (MooFileEntryCompletion *cmpl,
GtkEntry *entry)
{
g_return_if_fail (MOO_IS_FILE_ENTRY_COMPLETION (cmpl));
g_return_if_fail (!entry || GTK_IS_ENTRY (entry));
if (entry == cmpl->priv->entry)
return;
if (cmpl->priv->entry)
{
if (!cmpl->priv->managed)
{
g_signal_handlers_disconnect_by_func (cmpl->priv->entry,
(gpointer) completion_entry_changed,
cmpl);
g_signal_handlers_disconnect_by_func (cmpl->priv->entry,
(gpointer) completion_entry_key_press,
cmpl);
}
g_object_weak_unref (G_OBJECT (cmpl->priv->entry),
(GWeakNotify) completion_entry_destroyed,
cmpl);
}
cmpl->priv->entry = entry;
if (cmpl->priv->entry)
{
g_object_weak_ref (G_OBJECT (cmpl->priv->entry),
(GWeakNotify) completion_entry_destroyed,
cmpl);
if (!cmpl->priv->managed)
{
g_signal_connect (cmpl->priv->entry, "changed",
G_CALLBACK (completion_entry_changed), cmpl);
g_signal_connect (cmpl->priv->entry, "key-press-event",
G_CALLBACK (completion_entry_key_press), cmpl);
}
}
g_object_notify (G_OBJECT (cmpl), "entry");
}
static gboolean
completion_entry_key_press (GtkEntry *entry,
GdkEventKey *event,
MooFileEntryCompletion *cmpl)
{
g_return_val_if_fail (entry == cmpl->priv->entry, FALSE);
if (cmpl->priv->enabled &&
!(event->state & (GDK_CONTROL_MASK | GDK_SHIFT_MASK | GDK_MOD1_MASK)) &&
event->keyval == GDK_Tab)
{
completion_tab_key (cmpl);
return TRUE;
}
return FALSE;
}
static gboolean
completion_default_visible_func (MooFile *file)
{
return file && strcmp (_moo_file_name (file), "..");
}
void
_moo_file_entry_completion_set_visible_func (MooFileEntryCompletion *cmpl,
MooFileVisibleFunc func,
gpointer data)
{
g_return_if_fail (MOO_IS_FILE_ENTRY_COMPLETION (cmpl));
if (!func)
func = (MooFileVisibleFunc) completion_default_visible_func;
cmpl->priv->visible_func = func;
cmpl->priv->visible_func_data = data;
}
void
_moo_file_entry_completion_set_path (MooFileEntryCompletion *cmpl,
const char *path)
{
char *path_utf8;
g_return_if_fail (MOO_IS_FILE_ENTRY_COMPLETION (cmpl));
g_return_if_fail (path != NULL);
g_return_if_fail (cmpl->priv->entry != NULL);
path_utf8 = g_filename_display_name (path);
g_return_if_fail (path_utf8 != NULL);
gtk_entry_set_text (GTK_ENTRY (cmpl->priv->entry), path_utf8);
g_free (path_utf8);
}
/*****************************************************************************/
/* MooFileEntry
*/
struct _MooFileEntryPrivate {
MooFileEntryCompletion *completion;
};
static void moo_file_entry_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *pspec);
static void moo_file_entry_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec);
static void moo_file_entry_destroy (GtkObject *object);
static gboolean moo_file_entry_key_press (GtkWidget *widget,
GdkEventKey *event);
/* MOO_TYPE_FILE_ENTRY */
G_DEFINE_TYPE (MooFileEntry, _moo_file_entry, MOO_TYPE_ENTRY)
enum {
ENTRY_PROP_0,
ENTRY_PROP_COMPLETION
};
static void
_moo_file_entry_class_init (MooFileEntryClass *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
GtkObjectClass *gtkobject_class = GTK_OBJECT_CLASS (klass);
GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass);
gobject_class->set_property = moo_file_entry_set_property;
gobject_class->get_property = moo_file_entry_get_property;
gtkobject_class->destroy = moo_file_entry_destroy;
widget_class->key_press_event = moo_file_entry_key_press;
g_object_class_install_property (gobject_class,
ENTRY_PROP_COMPLETION,
g_param_spec_object ("completion",
"completion",
"completion",
MOO_TYPE_FILE_ENTRY_COMPLETION,
G_PARAM_READABLE));
}
static void
_moo_file_entry_init (G_GNUC_UNUSED MooFileEntry *entry)
{
entry->completion = g_object_new (MOO_TYPE_FILE_ENTRY_COMPLETION, NULL);
entry->completion->priv->managed = TRUE;
_moo_file_entry_completion_set_entry (entry->completion, GTK_ENTRY (entry));
}
static void
moo_file_entry_destroy (GtkObject *object)
{
MooFileEntry *entry = MOO_FILE_ENTRY (object);
if (entry->completion)
{
_moo_file_entry_completion_set_entry (entry->completion, NULL);
g_object_unref (entry->completion);
entry->completion = NULL;
}
GTK_OBJECT_CLASS(_moo_file_entry_parent_class)->destroy (object);
}
static void
moo_file_entry_set_property (GObject *object,
guint prop_id,
G_GNUC_UNUSED const GValue *value,
GParamSpec *pspec)
{
switch (prop_id)
{
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
static void
moo_file_entry_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *pspec)
{
MooFileEntry *entry = MOO_FILE_ENTRY (object);
switch (prop_id)
{
case ENTRY_PROP_COMPLETION:
g_value_set_object (value, entry->completion);
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
break;
}
}
GtkWidget*
_moo_file_entry_new (void)
{
return g_object_new (MOO_TYPE_FILE_ENTRY, NULL);
}
static gboolean
moo_file_entry_key_press (GtkWidget *widget,
GdkEventKey *event)
{
return completion_entry_key_press (GTK_ENTRY (widget), event,
MOO_FILE_ENTRY(widget)->completion) ||
GTK_WIDGET_CLASS(_moo_file_entry_parent_class)->key_press_event (widget, event);
}