1 /* -*- Mode: C; indent-tabs-mode: t; c-basic-offset: 8; tab-width: 8 -*- */
2 
3 /* NemoEntry: one-line text editing widget. This consists of bug fixes
4  * and other improvements to GtkEntry, and all the changes could be rolled
5  * into GtkEntry some day.
6  *
7  * Copyright (C) 2000 Eazel, Inc.
8  *
9  * Author: John Sullivan <sullivan@eazel.com>
10  *
11  * This library is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU Library General Public
13  * License as published by the Free Software Foundation; either
14  * version 2 of the License, or (at your option) any later version.
15  *
16  * This library is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19  * Library General Public License for more details.
20  *
21  * You should have received a copy of the GNU Library General Public
22  * License along with this library; if not, write to the
23  * Free Software Foundation, Inc., 51 Franklin Street - Suite 500,
24  * Boston, MA 02110-1335, USA.
25  */
26 
27 #include <config.h>
28 #include "nemo-entry.h"
29 
30 #include <string.h>
31 #include "nemo-global-preferences.h"
32 #include "nemo-undo-signal-handlers.h"
33 #include <gdk/gdkkeysyms.h>
34 #include <gtk/gtk.h>
35 #include <glib/gi18n.h>
36 
37 struct NemoEntryDetails {
38 	gboolean user_edit;
39 	gboolean special_tab_handling;
40 
41 	guint select_idle_id;
42 };
43 
44 enum {
45 	USER_CHANGED,
46 	SELECTION_CHANGED,
47 	LAST_SIGNAL
48 };
49 static guint signals[LAST_SIGNAL] = { 0 };
50 
51 static void nemo_entry_editable_init (GtkEditableInterface *iface);
52 
53 G_DEFINE_TYPE_WITH_CODE (NemoEntry, nemo_entry, GTK_TYPE_ENTRY,
54 			 G_IMPLEMENT_INTERFACE (GTK_TYPE_EDITABLE,
55 						nemo_entry_editable_init));
56 
57 static GtkEditableInterface *parent_editable_interface = NULL;
58 
59 static void
nemo_entry_init(NemoEntry * entry)60 nemo_entry_init (NemoEntry *entry)
61 {
62 	entry->details = g_new0 (NemoEntryDetails, 1);
63 
64 	entry->details->user_edit = TRUE;
65 
66 	nemo_undo_set_up_nemo_entry_for_undo (entry);
67 }
68 
69 GtkWidget *
nemo_entry_new(void)70 nemo_entry_new (void)
71 {
72 	return gtk_widget_new (NEMO_TYPE_ENTRY, NULL);
73 }
74 
75 GtkWidget *
nemo_entry_new_with_max_length(guint16 max)76 nemo_entry_new_with_max_length (guint16 max)
77 {
78 	GtkWidget *widget;
79 
80 	widget = gtk_widget_new (NEMO_TYPE_ENTRY, NULL);
81 	gtk_entry_set_max_length (GTK_ENTRY (widget), max);
82 
83 	return widget;
84 }
85 
86 static void
nemo_entry_finalize(GObject * object)87 nemo_entry_finalize (GObject *object)
88 {
89 	NemoEntry *entry;
90 
91 	entry = NEMO_ENTRY (object);
92 
93 	if (entry->details->select_idle_id != 0) {
94 		g_source_remove (entry->details->select_idle_id);
95 	}
96 
97 	g_free (entry->details);
98 
99 	G_OBJECT_CLASS (nemo_entry_parent_class)->finalize (object);
100 }
101 
102 static gboolean
nemo_entry_key_press(GtkWidget * widget,GdkEventKey * event)103 nemo_entry_key_press (GtkWidget *widget, GdkEventKey *event)
104 {
105 	NemoEntry *entry;
106 	GtkEditable *editable;
107 	int position;
108 	gboolean old_has, new_has;
109 	gboolean result;
110 
111 	entry = NEMO_ENTRY (widget);
112 	editable = GTK_EDITABLE (widget);
113 
114 	if (!gtk_editable_get_editable (editable)) {
115 		return FALSE;
116 	}
117 
118 	switch (event->keyval) {
119 	case GDK_KEY_Tab:
120 		/* The location bar entry wants TAB to work kind of
121 		 * like it does in the shell for command completion,
122 		 * so if we get a tab and there's a selection, we
123 		 * should position the insertion point at the end of
124 		 * the selection.
125 		 */
126 		if (entry->details->special_tab_handling && gtk_editable_get_selection_bounds (editable, NULL, NULL)) {
127 			position = strlen (gtk_entry_get_text (GTK_ENTRY (editable)));
128 			gtk_editable_select_region (editable, position, position);
129 			return TRUE;
130 		}
131 		break;
132 
133 	default:
134 		break;
135 	}
136 
137 	old_has = gtk_editable_get_selection_bounds (editable, NULL, NULL);
138 
139 	result = GTK_WIDGET_CLASS (nemo_entry_parent_class)->key_press_event (widget, event);
140 
141 	/* Pressing a key usually changes the selection if there is a selection.
142 	 * If there is not selection, we can save work by not emitting a signal.
143 	 */
144 	if (result) {
145 		new_has = gtk_editable_get_selection_bounds (editable, NULL, NULL);
146 		if (old_has || new_has) {
147 			g_signal_emit (widget, signals[SELECTION_CHANGED], 0);
148 		}
149 	}
150 
151 	return result;
152 
153 }
154 
155 static gboolean
nemo_entry_motion_notify(GtkWidget * widget,GdkEventMotion * event)156 nemo_entry_motion_notify (GtkWidget *widget, GdkEventMotion *event)
157 {
158 	int result;
159 	gboolean old_had, new_had;
160 	int old_start, old_end, new_start, new_end;
161 	GtkEditable *editable;
162 
163 	editable = GTK_EDITABLE (widget);
164 
165 	old_had = gtk_editable_get_selection_bounds (editable, &old_start, &old_end);
166 
167 	result = GTK_WIDGET_CLASS (nemo_entry_parent_class)->motion_notify_event (widget, event);
168 
169 	/* Send a signal if dragging the mouse caused the selection to change. */
170 	if (result) {
171 		new_had = gtk_editable_get_selection_bounds (editable, &new_start, &new_end);
172 		if (old_had != new_had || (old_had && (old_start != new_start || old_end != new_end))) {
173 			g_signal_emit (widget, signals[SELECTION_CHANGED], 0);
174 		}
175 	}
176 
177 	return result;
178 }
179 
180 /**
181  * nemo_entry_select_all
182  *
183  * Select all text, leaving the text cursor position at the end.
184  *
185  * @entry: A NemoEntry
186  **/
187 void
nemo_entry_select_all(NemoEntry * entry)188 nemo_entry_select_all (NemoEntry *entry)
189 {
190 	g_return_if_fail (NEMO_IS_ENTRY (entry));
191 
192 	gtk_editable_set_position (GTK_EDITABLE (entry), -1);
193 	gtk_editable_select_region (GTK_EDITABLE (entry), 0, -1);
194 }
195 
196 static gboolean
select_all_at_idle(gpointer callback_data)197 select_all_at_idle (gpointer callback_data)
198 {
199 	NemoEntry *entry;
200 
201 	entry = NEMO_ENTRY (callback_data);
202 
203 	nemo_entry_select_all (entry);
204 
205 	entry->details->select_idle_id = 0;
206 
207 	return FALSE;
208 }
209 
210 /**
211  * nemo_entry_select_all_at_idle
212  *
213  * Select all text at the next idle, not immediately.
214  * This is useful when reacting to a key press, because
215  * changing the selection and the text cursor position doesn't
216  * work in a key_press signal handler.
217  *
218  * @entry: A NemoEntry
219  **/
220 void
nemo_entry_select_all_at_idle(NemoEntry * entry)221 nemo_entry_select_all_at_idle (NemoEntry *entry)
222 {
223 	g_return_if_fail (NEMO_IS_ENTRY (entry));
224 
225 	/* If the text cursor position changes in this routine
226 	 * then gtk_entry_key_press will unselect (and we want
227 	 * to move the text cursor position to the end).
228 	 */
229 
230 	if (entry->details->select_idle_id == 0) {
231 		entry->details->select_idle_id = g_idle_add (select_all_at_idle, entry);
232 	}
233 }
234 
235 /**
236  * nemo_entry_set_text
237  *
238  * This function wraps gtk_entry_set_text.  It sets undo_registered
239  * to TRUE and preserves the old value for a later restore.  This is
240  * done so the programmatic changes to the entry do not register
241  * with the undo manager.
242  *
243  * @entry: A NemoEntry
244  * @test: The text to set
245  **/
246 
247 void
nemo_entry_set_text(NemoEntry * entry,const gchar * text)248 nemo_entry_set_text (NemoEntry *entry, const gchar *text)
249 {
250 	g_return_if_fail (NEMO_IS_ENTRY (entry));
251 
252 	entry->details->user_edit = FALSE;
253 	gtk_entry_set_text (GTK_ENTRY (entry), text);
254 	entry->details->user_edit = TRUE;
255 
256 	g_signal_emit (entry, signals[SELECTION_CHANGED], 0);
257 }
258 
259 static void
nemo_entry_set_selection_bounds(GtkEditable * editable,int start_pos,int end_pos)260 nemo_entry_set_selection_bounds (GtkEditable *editable,
261 				     int start_pos,
262 				     int end_pos)
263 {
264 	parent_editable_interface->set_selection_bounds (editable, start_pos, end_pos);
265 
266 	g_signal_emit (editable, signals[SELECTION_CHANGED], 0);
267 }
268 
269 static gboolean
nemo_entry_button_press(GtkWidget * widget,GdkEventButton * event)270 nemo_entry_button_press (GtkWidget *widget,
271 			     GdkEventButton *event)
272 {
273 	gboolean result;
274 
275 	result = GTK_WIDGET_CLASS (nemo_entry_parent_class)->button_press_event (widget, event);
276 
277 	if (result) {
278 		g_signal_emit (widget, signals[SELECTION_CHANGED], 0);
279 	}
280 
281 	return result;
282 }
283 
284 static gboolean
nemo_entry_button_release(GtkWidget * widget,GdkEventButton * event)285 nemo_entry_button_release (GtkWidget *widget,
286 			       GdkEventButton *event)
287 {
288 	gboolean result;
289 
290 	result = GTK_WIDGET_CLASS (nemo_entry_parent_class)->button_release_event (widget, event);
291 
292 	if (result) {
293 		g_signal_emit (widget, signals[SELECTION_CHANGED], 0);
294 	}
295 
296 	return result;
297 }
298 
299 static void
nemo_entry_insert_text(GtkEditable * editable,const gchar * text,int length,int * position)300 nemo_entry_insert_text (GtkEditable *editable, const gchar *text,
301 			    int length, int *position)
302 {
303 	NemoEntry *entry;
304 
305 	entry = NEMO_ENTRY(editable);
306 
307 	/* Fire off user changed signals */
308 	if (entry->details->user_edit) {
309 		g_signal_emit (editable, signals[USER_CHANGED], 0);
310 	}
311 
312 	parent_editable_interface->insert_text (editable, text, length, position);
313 
314 	g_signal_emit (editable, signals[SELECTION_CHANGED], 0);
315 }
316 
317 static void
nemo_entry_delete_text(GtkEditable * editable,int start_pos,int end_pos)318 nemo_entry_delete_text (GtkEditable *editable, int start_pos, int end_pos)
319 {
320 	NemoEntry *entry;
321 
322 	entry = NEMO_ENTRY (editable);
323 
324 	/* Fire off user changed signals */
325 	if (entry->details->user_edit) {
326 		g_signal_emit (editable, signals[USER_CHANGED], 0);
327 	}
328 
329 	parent_editable_interface->delete_text (editable, start_pos, end_pos);
330 
331 	g_signal_emit (editable, signals[SELECTION_CHANGED], 0);
332 }
333 
334 /* Overridden to work around GTK bug. The selection_clear_event is queued
335  * when the selection changes. Changing the selection to NULL and then
336  * back to the original selection owner still sends the event, so the
337  * selection owner then gets the selection ripped away from it. We ran into
338  * this with type-completion behavior in NemoLocationBar (see bug 5313).
339  * There's a FIXME comment that seems to be about this same issue in
340  * gtk+/gtkselection.c, gtk_selection_clear.
341  */
342 static gboolean
nemo_entry_selection_clear(GtkWidget * widget,GdkEventSelection * event)343 nemo_entry_selection_clear (GtkWidget *widget,
344 			        GdkEventSelection *event)
345 {
346 	g_assert (NEMO_IS_ENTRY (widget));
347 
348 	if (gdk_selection_owner_get (event->selection) == gtk_widget_get_window (widget)) {
349 		return FALSE;
350 	}
351 
352 	return GTK_WIDGET_CLASS (nemo_entry_parent_class)->selection_clear_event (widget, event);
353 }
354 
355 static void
nemo_entry_editable_init(GtkEditableInterface * iface)356 nemo_entry_editable_init (GtkEditableInterface *iface)
357 {
358 	parent_editable_interface = g_type_interface_peek_parent (iface);
359 
360 	iface->insert_text = nemo_entry_insert_text;
361 	iface->delete_text = nemo_entry_delete_text;
362 	iface->set_selection_bounds = nemo_entry_set_selection_bounds;
363 
364 	/* Otherwise we might need some memcpy loving */
365 	g_assert (iface->do_insert_text != NULL);
366 	g_assert (iface->get_position != NULL);
367 	g_assert (iface->get_chars != NULL);
368 }
369 
370 static void
nemo_entry_class_init(NemoEntryClass * class)371 nemo_entry_class_init (NemoEntryClass *class)
372 {
373 	GtkWidgetClass *widget_class;
374 	GObjectClass *gobject_class;
375 
376 	widget_class = GTK_WIDGET_CLASS (class);
377 	gobject_class = G_OBJECT_CLASS (class);
378 
379 	widget_class->button_press_event = nemo_entry_button_press;
380 	widget_class->button_release_event = nemo_entry_button_release;
381 	widget_class->key_press_event = nemo_entry_key_press;
382 	widget_class->motion_notify_event = nemo_entry_motion_notify;
383 	widget_class->selection_clear_event = nemo_entry_selection_clear;
384 
385 	gobject_class->finalize = nemo_entry_finalize;
386 
387 	/* Set up signals */
388 	signals[USER_CHANGED] = g_signal_new
389 		("user_changed",
390 		 G_TYPE_FROM_CLASS (class),
391 		 G_SIGNAL_RUN_LAST,
392 		 G_STRUCT_OFFSET (NemoEntryClass, user_changed),
393 		 NULL, NULL,
394 		 g_cclosure_marshal_VOID__VOID,
395 		 G_TYPE_NONE, 0);
396 	signals[SELECTION_CHANGED] = g_signal_new
397 		("selection_changed",
398 		 G_TYPE_FROM_CLASS (class),
399 		 G_SIGNAL_RUN_LAST,
400 		 G_STRUCT_OFFSET (NemoEntryClass, selection_changed),
401 		 NULL, NULL,
402 		 g_cclosure_marshal_VOID__VOID,
403 		 G_TYPE_NONE, 0);
404 }
405 
406 void
nemo_entry_set_special_tab_handling(NemoEntry * entry,gboolean special_tab_handling)407 nemo_entry_set_special_tab_handling (NemoEntry *entry,
408 					 gboolean special_tab_handling)
409 {
410 	g_return_if_fail (NEMO_IS_ENTRY (entry));
411 
412 	entry->details->special_tab_handling = special_tab_handling;
413 }
414 
415 
416