gedit-history-entry.c 12.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*
 * gedit-history-entry.c
 * 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
18
 * along with this program; if not, see <http://www.gnu.org/licenses/>.
19
 */
20

21 22 23 24
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

25 26
#include "gedit-history-entry.h"

27 28 29 30
#include <string.h>
#include <glib/gi18n.h>
#include <gtk/gtk.h>

31 32
#define MIN_ITEM_LEN 3

33 34
#define GEDIT_HISTORY_ENTRY_HISTORY_LENGTH_DEFAULT 10

35
struct _GeditHistoryEntry
36
{
37 38
	GtkComboBoxText     parent_instance;

39 40
	gchar              *history_id;
	guint               history_length;
41

42
	GtkEntryCompletion *completion;
43

44
	GSettings          *settings;
45 46
};

47 48 49 50 51 52 53 54 55 56
enum {
	PROP_0,
	PROP_HISTORY_ID,
	PROP_HISTORY_LENGTH,
	PROP_ENABLE_COMPLETION,
	LAST_PROP
};

static GParamSpec *properties[LAST_PROP];

57
G_DEFINE_TYPE (GeditHistoryEntry, gedit_history_entry, GTK_TYPE_COMBO_BOX_TEXT)
58 59 60 61 62 63 64 65 66 67 68 69 70

static void
gedit_history_entry_set_property (GObject      *object,
				  guint         prop_id,
				  const GValue *value,
				  GParamSpec   *spec)
{
	GeditHistoryEntry *entry;

	g_return_if_fail (GEDIT_IS_HISTORY_ENTRY (object));

	entry = GEDIT_HISTORY_ENTRY (object);

Garrett Regier's avatar
Garrett Regier committed
71 72 73
	switch (prop_id)
	{
		case PROP_HISTORY_ID:
74
			entry->history_id = g_value_dup_string (value);
Garrett Regier's avatar
Garrett Regier committed
75 76 77 78 79
			break;
		case PROP_HISTORY_LENGTH:
			gedit_history_entry_set_history_length (entry,
								g_value_get_uint (value));
			break;
80 81 82 83
		case PROP_ENABLE_COMPLETION:
			gedit_history_entry_set_enable_completion (entry,
			                                           g_value_get_boolean (value));
			break;
Garrett Regier's avatar
Garrett Regier committed
84 85
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, spec);
86 87 88 89 90 91 92 93 94
	}
}

static void
gedit_history_entry_get_property (GObject    *object,
				  guint       prop_id,
				  GValue     *value,
				  GParamSpec *spec)
{
95
	GeditHistoryEntry *entry;
96 97 98

	g_return_if_fail (GEDIT_IS_HISTORY_ENTRY (object));

99
	entry = GEDIT_HISTORY_ENTRY (object);
100

Garrett Regier's avatar
Garrett Regier committed
101 102 103
	switch (prop_id)
	{
		case PROP_HISTORY_ID:
104
			g_value_set_string (value, entry->history_id);
Garrett Regier's avatar
Garrett Regier committed
105 106
			break;
		case PROP_HISTORY_LENGTH:
107
			g_value_set_uint (value, entry->history_length);
Garrett Regier's avatar
Garrett Regier committed
108
			break;
109 110 111
		case PROP_ENABLE_COMPLETION:
			g_value_set_boolean (value, gedit_history_entry_get_enable_completion (GEDIT_HISTORY_ENTRY (object)));
			break;
Garrett Regier's avatar
Garrett Regier committed
112 113
		default:
			G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, spec);
114 115 116
	}
}

117
static void
118
gedit_history_entry_dispose (GObject *object)
119
{
120
	GeditHistoryEntry *entry = GEDIT_HISTORY_ENTRY (object);
121

122
	gedit_history_entry_set_enable_completion (entry, FALSE);
123

124
	g_clear_object (&entry->settings);
125

126
	G_OBJECT_CLASS (gedit_history_entry_parent_class)->dispose (object);
127 128
}

129 130 131
static void
gedit_history_entry_finalize (GObject *object)
{
132
	GeditHistoryEntry *entry = GEDIT_HISTORY_ENTRY (object);
133

134
	g_free (entry->history_id);
135

136
	G_OBJECT_CLASS (gedit_history_entry_parent_class)->finalize (object);
137 138
}

139 140 141 142 143 144
static void
gedit_history_entry_load_history (GeditHistoryEntry *entry)
{
	gchar **items;
	gsize i;

145
	items = g_settings_get_strv (entry->settings, entry->history_id);
146 147 148 149 150 151 152
	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' &&
153
	       i < entry->history_length)
154 155 156 157 158 159 160 161
	{
		gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (entry), items[i]);
		i++;
	}

	g_strfreev (items);
}

162
static void
163 164
gedit_history_entry_class_init (GeditHistoryEntryClass *klass)
{
165
	GObjectClass *object_class = G_OBJECT_CLASS (klass);
166

167 168
	object_class->set_property = gedit_history_entry_set_property;
	object_class->get_property = gedit_history_entry_get_property;
169
	object_class->dispose = gedit_history_entry_dispose;
170
	object_class->finalize = gedit_history_entry_finalize;
171

172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195
	properties[PROP_HISTORY_ID] =
		g_param_spec_string ("history-id",
		                     "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",
		                   "Max History Length",
		                   "Max History Length",
		                   0,
		                   G_MAXUINT,
		                   GEDIT_HISTORY_ENTRY_HISTORY_LENGTH_DEFAULT,
		                   G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

	properties[PROP_ENABLE_COMPLETION] =
		g_param_spec_boolean ("enable-completion",
		                      "Enable Completion",
		                      "Wether the completion is enabled",
		                      TRUE,
		                      G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS);

	g_object_class_install_properties (object_class, LAST_PROP, properties);
196 197 198 199 200 201 202 203 204 205 206 207 208
}

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;
}

209
static gchar **
210
get_history_items (GeditHistoryEntry *entry)
211 212 213
{
	GtkListStore *store;
	GtkTreeIter iter;
214
	GPtrArray *array;
215
	gboolean valid;
216
	gint n_children;
217
	gint text_column;
218 219

	store = get_history_store (entry);
220
	text_column = gtk_combo_box_get_entry_text_column (GTK_COMBO_BOX (entry));
221 222 223

	valid = gtk_tree_model_get_iter_first (GTK_TREE_MODEL (store),
					       &iter);
224
	n_children = gtk_tree_model_iter_n_children (GTK_TREE_MODEL (store),
225
						     NULL);
226

227
	array = g_ptr_array_sized_new (n_children + 1);
228 229 230 231 232 233 234

	while (valid)
	{
		gchar *str;

		gtk_tree_model_get (GTK_TREE_MODEL (store),
				    &iter,
235
				    text_column, &str,
236 237
				    -1);

238
		g_ptr_array_add (array, str);
239 240 241 242

		valid = gtk_tree_model_iter_next (GTK_TREE_MODEL (store),
						  &iter);
	}
243

244
	g_ptr_array_add (array, NULL);
245

246
	return (gchar **)g_ptr_array_free (array, FALSE);
247 248 249 250 251
}

static void
gedit_history_entry_save_history (GeditHistoryEntry *entry)
{
252
	gchar **items;
253 254 255

	g_return_if_fail (GEDIT_IS_HISTORY_ENTRY (entry));

256
	items = get_history_items (entry);
257

258 259
	g_settings_set_strv (entry->settings,
			     entry->history_id,
260
			     (const gchar * const *)items);
261

262
	g_strfreev (items);
263 264 265
}

static gboolean
266 267
remove_item (GeditHistoryEntry *entry,
	     const gchar       *text)
268
{
269
	GtkListStore *store;
270
	GtkTreeIter iter;
271
	gint text_column;
272 273 274

	g_return_val_if_fail (text != NULL, FALSE);

275 276 277
	store = get_history_store (entry);
	text_column = gtk_combo_box_get_entry_text_column (GTK_COMBO_BOX (entry));

278 279 280 281 282 283 284 285 286
	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,
287
				    text_column,
288 289 290 291 292 293 294
				    &item_text,
				    -1);

		if (item_text != NULL &&
		    strcmp (item_text, text) == 0)
		{
			gtk_list_store_remove (store, &iter);
Paolo Borelli's avatar
Paolo Borelli committed
295
			g_free (item_text);
296 297 298
			return TRUE;
		}

Paolo Borelli's avatar
Paolo Borelli committed
299 300
		g_free (item_text);

301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317
	} 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))
	{
Garrett Regier's avatar
Garrett Regier committed
318
		while (TRUE)
319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334
		{
			if (!gtk_list_store_remove (store, &iter))
				break;
		}
	}

	gtk_tree_path_free (path);
}

static void
insert_history_item (GeditHistoryEntry *entry,
		     const gchar       *text,
		     gboolean           prepend)
{
	GtkListStore *store;

335 336
	if (g_utf8_strlen (text, -1) <= MIN_ITEM_LEN)
		return;
337

338 339 340 341 342 343 344
	store = get_history_store (entry);

	/* remove the text from the store if it was already
	 * present. If it wasn't, clamp to max history - 1
	 * before inserting the new row, otherwise appending
	 * would not work */

345
	if (!remove_item (entry, text))
346 347 348
	{
		clamp_list_store (store, entry->history_length - 1);
	}
349 350

	if (prepend)
351
	{
352
		gtk_combo_box_text_prepend_text (GTK_COMBO_BOX_TEXT (entry), text);
353
	}
354
	else
355
	{
356
		gtk_combo_box_text_append_text (GTK_COMBO_BOX_TEXT (entry), text);
357
	}
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386

	gedit_history_entry_save_history (entry);
}

void
gedit_history_entry_prepend_text (GeditHistoryEntry *entry,
				  const gchar       *text)
{
	g_return_if_fail (GEDIT_IS_HISTORY_ENTRY (entry));
	g_return_if_fail (text != NULL);

	insert_history_item (entry, text, TRUE);
}

void
gedit_history_entry_append_text (GeditHistoryEntry *entry,
				 const gchar       *text)
{
	g_return_if_fail (GEDIT_IS_HISTORY_ENTRY (entry));
	g_return_if_fail (text != NULL);

	insert_history_item (entry, text, FALSE);
}

void
gedit_history_entry_clear (GeditHistoryEntry *entry)
{
	g_return_if_fail (GEDIT_IS_HISTORY_ENTRY (entry));

387
	gtk_combo_box_text_remove_all (GTK_COMBO_BOX_TEXT (entry));
388 389 390 391 392 393 394

	gedit_history_entry_save_history (entry);
}

static void
gedit_history_entry_init (GeditHistoryEntry *entry)
{
395 396
	entry->history_id = NULL;
	entry->history_length = GEDIT_HISTORY_ENTRY_HISTORY_LENGTH_DEFAULT;
397

398
	entry->completion = NULL;
399

400
	entry->settings = g_settings_new ("org.gnome.gedit.state.history-entry");
401 402 403 404 405 406 407 408 409
}

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);

410
	entry->history_length = history_length;
411 412 413 414 415 416 417 418 419

	/* 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);

420
	return entry->history_length;
421 422
}

423 424 425 426 427
void
gedit_history_entry_set_enable_completion (GeditHistoryEntry *entry,
					   gboolean           enable)
{
	g_return_if_fail (GEDIT_IS_HISTORY_ENTRY (entry));
428

429 430
	if (enable)
	{
431 432
		if (entry->completion != NULL)
		{
433
			return;
434
		}
435

436 437
		entry->completion = gtk_entry_completion_new ();
		gtk_entry_completion_set_model (entry->completion,
438
						GTK_TREE_MODEL (get_history_store (entry)));
439

440
		/* Use model column 0 as the text column */
441
		gtk_entry_completion_set_text_column (entry->completion, 0);
442

443
		gtk_entry_completion_set_minimum_key_length (entry->completion,
444 445
							     MIN_ITEM_LEN);

446 447
		gtk_entry_completion_set_popup_completion (entry->completion, FALSE);
		gtk_entry_completion_set_inline_completion (entry->completion, TRUE);
448

449
		/* Assign the completion to the entry */
450
		gtk_entry_set_completion (GTK_ENTRY (gedit_history_entry_get_entry (entry)),
451
					  entry->completion);
452 453 454
	}
	else
	{
455 456
		if (entry->completion == NULL)
		{
457
			return;
458
		}
459

460 461
		gtk_entry_set_completion (GTK_ENTRY (gedit_history_entry_get_entry (entry)), NULL);
		g_clear_object (&entry->completion);
462 463
	}
}
464

465 466 467 468
gboolean
gedit_history_entry_get_enable_completion (GeditHistoryEntry *entry)
{
	g_return_val_if_fail (GEDIT_IS_HISTORY_ENTRY (entry), FALSE);
469

470
	return entry->completion != NULL;
471 472
}

473
GtkWidget *
474 475
gedit_history_entry_new (const gchar *history_id,
			 gboolean     enable_completion)
476
{
477 478
	GeditHistoryEntry *entry;

479 480
	g_return_val_if_fail (history_id != NULL, NULL);

481
	enable_completion = (enable_completion != FALSE);
Garrett Regier's avatar
Garrett Regier committed
482

483 484 485 486 487 488 489 490 491 492 493 494 495 496
	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);
497 498 499 500
}

/*
 * Utility function to get the editable text entry internal widget.
501 502
 * I would prefer to not expose this implementation detail and
 * simply make the GeditHistoryEntry widget implement the
503 504 505 506 507 508 509 510 511 512 513 514
 * 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));
}

515
/* ex:set ts=8 noet: */