1 /*
2  * py-command-line.c: Simple wrapper around GtkEntry with history support
3  *
4  * Author: Zbigniew Chyla (cyba@gnome.pl)
5  */
6 
7 #include <gnumeric-config.h>
8 #include "py-command-line.h"
9 #include "gnm-python.h"
10 #include <gnumeric.h>
11 #include <gutils.h>
12 #include <goffice/goffice.h>
13 #include <goffice/app/module-plugin-defs.h>
14 #include <gsf/gsf-impl-utils.h>
15 #include <glib-object.h>
16 #include <gdk/gdkkeysyms.h>
17 #include <string.h>
18 
19 #define MAX_HISTORY_SIZE  100
20 
21 struct _GnmPyCommandLine {
22 	GtkEntry parent;
23 
24 	GList *history, *history_tail, *history_cur;
25 	gboolean editing;
26 	int history_size;
27 };
28 
29 typedef struct {
30 	GtkEntryClass parent_class;
31 
32 	void (*entered) (GnmPyCommandLine *cline);
33 } GnmPyCommandLineClass;
34 
35 enum {
36 	ENTERED_SIGNAL,
37 	LAST_SIGNAL
38 };
39 
40 static guint signals[LAST_SIGNAL] = { 0 };
41 static GObjectClass *parent_class = NULL;
42 
43 static void
gnm_py_command_line_changed(GnmPyCommandLine * cline)44 gnm_py_command_line_changed (GnmPyCommandLine *cline)
45 {
46 	cline->editing = TRUE;
47 }
48 
49 static gint
gnm_py_command_line_keypress(GnmPyCommandLine * cline,GdkEventKey * event,gpointer user_data)50 gnm_py_command_line_keypress (GnmPyCommandLine *cline, GdkEventKey *event, gpointer user_data)
51 {
52 	switch (event->keyval) {
53 	case GDK_KEY_Return: {
54 		const char *text;
55 
56 		text = gtk_entry_get_text (GTK_ENTRY (cline));
57 		if (cline->history_tail == NULL) {
58 			cline->history = g_list_append (NULL, g_strdup (text));
59 			cline->history_tail = cline->history;
60 		} else if (text[0] != '\0' && strcmp (text, cline->history_tail->data) != 0) {
61 			cline->history_tail =
62 				g_list_append (cline->history_tail,
63 					       g_strdup (text))->next;
64 		}
65 		if (cline->history_size == MAX_HISTORY_SIZE) {
66 			g_free (cline->history->data);
67 			cline->history = g_list_delete_link (cline->history, cline->history);
68 		} else {
69 			cline->history_size++;
70 		}
71 		g_signal_emit (cline, signals[ENTERED_SIGNAL], 0);
72 		gtk_entry_set_text (GTK_ENTRY (cline), "");
73 		cline->editing = TRUE;
74 		break;
75 	}
76 	case GDK_KEY_Up:
77 		if (cline->editing) {
78 			if (cline->history_tail != NULL) {
79 				cline->history_cur = cline->history_tail;
80 				gtk_entry_set_text (GTK_ENTRY (cline), cline->history_cur->data);
81 				gtk_editable_set_position (
82 					GTK_EDITABLE (cline), strlen (cline->history_cur->data));
83 				cline->editing = FALSE;
84 			}
85 		} else {
86 			if (cline->history_cur->prev != NULL) {
87 				cline->history_cur = cline->history_cur->prev;
88 				gtk_entry_set_text (GTK_ENTRY (cline), cline->history_cur->data);
89 				gtk_editable_set_position (
90 					GTK_EDITABLE (cline), strlen (cline->history_cur->data));
91 				cline->editing = FALSE;
92 			}
93 		}
94 		break;
95 	case GDK_KEY_Down:
96 		if (!cline->editing) {
97 			if (cline->history_cur->next != NULL) {
98 				cline->history_cur = cline->history_cur->next;
99 				gtk_entry_set_text (GTK_ENTRY (cline), cline->history_cur->data);
100 				gtk_editable_set_position (
101 					GTK_EDITABLE (cline), strlen (cline->history_cur->data));
102 				cline->editing = FALSE;
103 			} else {
104 				gtk_entry_set_text (GTK_ENTRY (cline), "");
105 				cline->editing = TRUE;
106 			}
107 		}
108 		break;
109 	default:
110 		return FALSE;
111 	}
112 
113 	g_signal_stop_emission_by_name (cline, "key_press_event");
114 	return TRUE;
115 }
116 
117 static void
gnm_py_command_line_init(GnmPyCommandLine * cline)118 gnm_py_command_line_init (GnmPyCommandLine *cline)
119 {
120 	g_signal_connect (
121 		cline, "key_press_event",
122 		G_CALLBACK (gnm_py_command_line_keypress), NULL);
123 	g_signal_connect (
124 		cline, "changed",
125 		G_CALLBACK (gnm_py_command_line_changed), NULL);
126 	cline->history = cline->history_tail = NULL;
127 	cline->history_cur = NULL;
128 	cline->editing = TRUE;
129 	cline->history_size = 0;
130 }
131 
132 static void
gnm_py_command_line_finalize(GObject * obj)133 gnm_py_command_line_finalize (GObject *obj)
134 {
135 	GnmPyCommandLine *cline = GNM_PY_COMMAND_LINE (obj);
136 
137 	g_list_free_full (cline->history, g_free);
138 	cline->history = NULL;
139 
140 	parent_class->finalize (obj);
141 }
142 
143 static void
gnm_py_command_line_class_init(GObjectClass * gobject_class)144 gnm_py_command_line_class_init (GObjectClass *gobject_class)
145 {
146 	parent_class = g_type_class_peek_parent (gobject_class);
147 
148 	gobject_class->finalize = gnm_py_command_line_finalize;
149 
150 	signals[ENTERED_SIGNAL] =
151 	g_signal_new (
152 		"entered",
153 		G_TYPE_FROM_CLASS (gobject_class),
154 		G_SIGNAL_RUN_FIRST,
155 		G_STRUCT_OFFSET (GnmPyCommandLineClass, entered),
156 		NULL, NULL,
157 		g_cclosure_marshal_VOID__VOID,
158 		G_TYPE_NONE, 0);
159 }
160 
161 GtkWidget *
gnm_py_command_line_new(void)162 gnm_py_command_line_new (void)
163 {
164 	return g_object_new (GNM_PY_COMMAND_LINE_TYPE, NULL);
165 }
166 
167 GSF_DYNAMIC_CLASS (GnmPyCommandLine, gnm_py_command_line,
168 	gnm_py_command_line_class_init, gnm_py_command_line_init,
169 	GTK_TYPE_ENTRY)
170 
171