/*
* This file is part of gedit
*
* Copyright (C) 2006 - Paolo Borelli
*
* 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, see .
*/
#include "config.h"
#include "gedit-history-entry.h"
#include
#include
#define MIN_ITEM_LEN 3
#define HISTORY_LENGTH_DEFAULT 10
struct _GeditHistoryEntry
{
GtkComboBoxText parent_instance;
gchar *history_id;
guint history_length;
GtkEntryCompletion *completion;
GSettings *settings;
};
enum
{
PROP_0,
PROP_HISTORY_ID,
PROP_HISTORY_LENGTH,
PROP_ENABLE_COMPLETION,
N_PROPERTIES
};
static GParamSpec *properties[N_PROPERTIES];
G_DEFINE_TYPE (GeditHistoryEntry, gedit_history_entry, GTK_TYPE_COMBO_BOX_TEXT)
static void
gedit_history_entry_set_property (GObject *object,
guint prop_id,
const GValue *value,
GParamSpec *spec)
{
GeditHistoryEntry *entry = GEDIT_HISTORY_ENTRY (object);
switch (prop_id)
{
case PROP_HISTORY_ID:
entry->history_id = g_value_dup_string (value);
break;
case PROP_HISTORY_LENGTH:
gedit_history_entry_set_history_length (entry, g_value_get_uint (value));
break;
case PROP_ENABLE_COMPLETION:
gedit_history_entry_set_enable_completion (entry, g_value_get_boolean (value));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, spec);
}
}
static void
gedit_history_entry_get_property (GObject *object,
guint prop_id,
GValue *value,
GParamSpec *spec)
{
GeditHistoryEntry *entry = GEDIT_HISTORY_ENTRY (object);
switch (prop_id)
{
case PROP_HISTORY_ID:
g_value_set_string (value, entry->history_id);
break;
case PROP_HISTORY_LENGTH:
g_value_set_uint (value, gedit_history_entry_get_history_length (entry));
break;
case PROP_ENABLE_COMPLETION:
g_value_set_boolean (value, gedit_history_entry_get_enable_completion (entry));
break;
default:
G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, spec);
}
}
static void
gedit_history_entry_dispose (GObject *object)
{
GeditHistoryEntry *entry = GEDIT_HISTORY_ENTRY (object);
gedit_history_entry_set_enable_completion (entry, FALSE);
g_clear_object (&entry->settings);
G_OBJECT_CLASS (gedit_history_entry_parent_class)->dispose (object);
}
static void
gedit_history_entry_finalize (GObject *object)
{
GeditHistoryEntry *entry = GEDIT_HISTORY_ENTRY (object);
g_free (entry->history_id);
G_OBJECT_CLASS (gedit_history_entry_parent_class)->finalize (object);
}
static void
gedit_history_entry_load_history (GeditHistoryEntry *entry)
{
gchar **items;
gsize i;
items = g_settings_get_strv (entry->settings, entry->history_id);
i = 0;
gtk_combo_box_text_remove_all (GTK_COMBO_BOX_TEXT (entry));
/* Now the default value is an empty string so we have to take care
of it to not add the empty string in the search list */
while (items[i] != NULL && *items[i] != '\0' &&
i < entry->history_length)
{
gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (entry), items[i]);
i++;
}
g_strfreev (items);
}
static void
gedit_history_entry_class_init (GeditHistoryEntryClass *klass)
{
GObjectClass *object_class = G_OBJECT_CLASS (klass);
object_class->set_property = gedit_history_entry_set_property;
object_class->get_property = gedit_history_entry_get_property;
object_class->dispose = gedit_history_entry_dispose;
object_class->finalize = gedit_history_entry_finalize;
properties[PROP_HISTORY_ID] =
g_param_spec_string ("history-id",
"history-id",
"",
NULL,
G_PARAM_READWRITE |
G_PARAM_CONSTRUCT_ONLY |
G_PARAM_STATIC_STRINGS);
properties[PROP_HISTORY_LENGTH] =
g_param_spec_uint ("history-length",
"history-length",
"",
0,
G_MAXUINT,
HISTORY_LENGTH_DEFAULT,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS);
properties[PROP_ENABLE_COMPLETION] =
g_param_spec_boolean ("enable-completion",
"enable-completion",
"",
TRUE,
G_PARAM_READWRITE |
G_PARAM_STATIC_STRINGS);
g_object_class_install_properties (object_class, N_PROPERTIES, properties);
}
static GtkListStore *
get_history_store (GeditHistoryEntry *entry)
{
GtkTreeModel *store;
store = gtk_combo_box_get_model (GTK_COMBO_BOX (entry));
g_return_val_if_fail (GTK_IS_LIST_STORE (store), NULL);
return (GtkListStore *) store;
}
static gchar **
get_history_items (GeditHistoryEntry *entry)
{
GtkListStore *store;
GtkTreeIter iter;
GPtrArray *array;
gboolean valid;
gint n_children;
gint text_column;
store = get_history_store (entry);
text_column = gtk_combo_box_get_entry_text_column (GTK_COMBO_BOX (entry));
valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store),
&iter);
n_children = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (store),
NULL);
array = g_ptr_array_sized_new (n_children + 1);
while (valid)
{
gchar *str;
gtk_tree_model_get (GTK_TREE_MODEL (store),
&iter,
text_column, &str,
-1);
g_ptr_array_add (array, str);
valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store),
&iter);
}
g_ptr_array_add (array, NULL);
return (gchar **)g_ptr_array_free (array, FALSE);
}
static void
gedit_history_entry_save_history (GeditHistoryEntry *entry)
{
gchar **items;
g_return_if_fail (GEDIT_IS_HISTORY_ENTRY (entry));
items = get_history_items (entry);
g_settings_set_strv (entry->settings,
entry->history_id,
(const gchar * const *)items);
g_strfreev (items);
}
static gboolean
remove_item (GeditHistoryEntry *entry,
const gchar *text)
{
GtkListStore *store;
GtkTreeIter iter;
gint text_column;
g_return_val_if_fail (text != NULL, FALSE);
store = get_history_store (entry);
text_column = gtk_combo_box_get_entry_text_column (GTK_COMBO_BOX (entry));
if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store), &iter))
return FALSE;
do
{
gchar *item_text;
gtk_tree_model_get (GTK_TREE_MODEL (store),
&iter,
text_column,
&item_text,
-1);
if (item_text != NULL &&
strcmp (item_text, text) == 0)
{
gtk_list_store_remove (store, &iter);
g_free (item_text);
return TRUE;
}
g_free (item_text);
} while (gtk_tree_model_iter_next (GTK_TREE_MODEL (store), &iter));
return FALSE;
}
static void
clamp_list_store (GtkListStore *store,
guint max)
{
GtkTreePath *path;
GtkTreeIter iter;
/* -1 because TreePath counts from 0 */
path = gtk_tree_path_new_from_indices (max - 1, -1);
if (gtk_tree_model_get_iter (GTK_TREE_MODEL (store), &iter, path))
{
while (TRUE)
{
if (!gtk_list_store_remove (store, &iter))
break;
}
}
gtk_tree_path_free (path);
}
void
gedit_history_entry_prepend_text (GeditHistoryEntry *entry,
const gchar *text)
{
GtkListStore *store;
g_return_if_fail (GEDIT_IS_HISTORY_ENTRY (entry));
g_return_if_fail (text != NULL);
if (g_utf8_strlen (text, -1) <= MIN_ITEM_LEN)
{
return;
}
store = get_history_store (entry);
if (!remove_item (entry, text))
{
clamp_list_store (store, entry->history_length - 1);
}
gtk_combo_box_text_prepend_text (GTK_COMBO_BOX_TEXT (entry), text);
gedit_history_entry_save_history (entry);
}
static void
gedit_history_entry_init (GeditHistoryEntry *entry)
{
entry->history_id = NULL;
entry->history_length = HISTORY_LENGTH_DEFAULT;
entry->completion = NULL;
entry->settings = g_settings_new ("org.gnome.gedit.state.history-entry");
}
void
gedit_history_entry_set_history_length (GeditHistoryEntry *entry,
guint history_length)
{
g_return_if_fail (GEDIT_IS_HISTORY_ENTRY (entry));
g_return_if_fail (history_length > 0);
entry->history_length = history_length;
/* TODO: update if we currently have more items than max */
}
guint
gedit_history_entry_get_history_length (GeditHistoryEntry *entry)
{
g_return_val_if_fail (GEDIT_IS_HISTORY_ENTRY (entry), 0);
return entry->history_length;
}
void
gedit_history_entry_set_enable_completion (GeditHistoryEntry *entry,
gboolean enable)
{
g_return_if_fail (GEDIT_IS_HISTORY_ENTRY (entry));
if (enable)
{
if (entry->completion != NULL)
{
return;
}
entry->completion = gtk_entry_completion_new ();
gtk_entry_completion_set_model (entry->completion,
GTK_TREE_MODEL (get_history_store (entry)));
/* Use model column 0 as the text column */
gtk_entry_completion_set_text_column (entry->completion, 0);
gtk_entry_completion_set_minimum_key_length (entry->completion,
MIN_ITEM_LEN);
gtk_entry_completion_set_popup_completion (entry->completion, FALSE);
gtk_entry_completion_set_inline_completion (entry->completion, TRUE);
/* Assign the completion to the entry */
gtk_entry_set_completion (GTK_ENTRY (gedit_history_entry_get_entry (entry)),
entry->completion);
}
else
{
if (entry->completion == NULL)
{
return;
}
gtk_entry_set_completion (GTK_ENTRY (gedit_history_entry_get_entry (entry)), NULL);
g_clear_object (&entry->completion);
}
}
gboolean
gedit_history_entry_get_enable_completion (GeditHistoryEntry *entry)
{
g_return_val_if_fail (GEDIT_IS_HISTORY_ENTRY (entry), FALSE);
return entry->completion != NULL;
}
GtkWidget *
gedit_history_entry_new (const gchar *history_id,
gboolean enable_completion)
{
GeditHistoryEntry *entry;
g_return_val_if_fail (history_id != NULL, NULL);
enable_completion = (enable_completion != FALSE);
entry = g_object_new (GEDIT_TYPE_HISTORY_ENTRY,
"has-entry", TRUE,
"entry-text-column", 0,
"id-column", 1,
"history-id", history_id,
"enable-completion", enable_completion,
NULL);
/* We must load the history after the object has been constructed,
* to ensure that the model is set properly.
*/
gedit_history_entry_load_history (entry);
return GTK_WIDGET (entry);
}
/*
* Utility function to get the editable text entry internal widget.
* I would prefer to not expose this implementation detail and
* simply make the GeditHistoryEntry widget implement the
* GtkEditable interface. Unfortunately both GtkEditable and
* GtkComboBox have a "changed" signal and I am not sure how to
* handle the conflict.
*/
GtkWidget *
gedit_history_entry_get_entry (GeditHistoryEntry *entry)
{
g_return_val_if_fail (GEDIT_IS_HISTORY_ENTRY (entry), NULL);
return gtk_bin_get_child (GTK_BIN (entry));
}
/* ex:set ts=8 noet: */