7faa4920eea68720117451bff8189d4137a403a1
[vte.git] / src / reflect.c
1 /*
2  * Copyright (C) 2003 Red Hat, Inc.
3  *
4  * This is free software; you can redistribute it and/or modify it under
5  * the terms of the GNU Library General Public License as published by
6  * the Free Software Foundation; either version 2 of the License, or
7  * (at your option) any later version.
8  *
9  * This program is distributed in the hope that it will be useful, but
10  * WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * General Public License for more details.
13  *
14  * You should have received a copy of the GNU Library General Public
15  * License along with this program; if not, write to the Free Software
16  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
17  */
18
19 #include <config.h>
20 #include <sys/types.h>
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 #include <unistd.h>
25 #include <gtk/gtk.h>
26 #include <atk/atk.h>
27 #ifdef USE_VTE
28 #include "vte.h"
29 #endif
30
31 static GArray *contents = NULL;
32
33 #ifdef USE_TEXT_VIEW
34 /*
35  * Implementation for a TextView widget.
36  */
37 static void
38 terminal_init_text_view(GtkWidget **widget)
39 {
40         *widget = gtk_text_view_new();
41         gtk_text_view_set_editable(GTK_TEXT_VIEW(*widget), TRUE);
42 }
43 static void
44 terminal_shell_text_view(GtkWidget *widget)
45 {
46         /* no-op */
47 }
48 static GtkAdjustment *
49 terminal_adjustment_text_view(GtkWidget *terminal)
50 {
51 #if GTK_CHECK_VERSION (2, 21, 6)
52         return gtk_text_view_get_vadjustment(GTK_TEXT_VIEW(terminal));
53 #else
54         return GTK_TEXT_VIEW(terminal)->vadjustment;
55 #endif
56 }
57 #endif
58 #ifdef USE_VTE
59 /*
60  * Implementation for a VteTerminal widget.
61  */
62 static void
63 terminal_init_vte(GtkWidget **terminal)
64 {
65         *terminal = vte_terminal_new();
66         g_signal_connect(G_OBJECT(*terminal), "eof",
67                          G_CALLBACK(gtk_main_quit), NULL);
68         g_signal_connect(G_OBJECT(*terminal), "child-exited",
69                          G_CALLBACK(gtk_main_quit), NULL);
70 }
71 static void
72 terminal_shell_vte(GtkWidget *terminal)
73 {
74         vte_terminal_fork_command(VTE_TERMINAL(terminal),
75                                   getenv("SHELL") ? getenv("SHELL") : "/bin/sh",
76                                   NULL,
77                                   NULL,
78                                   g_get_home_dir() ? g_get_home_dir() : NULL,
79                                   FALSE,
80                                   FALSE,
81                                   FALSE);
82 }
83 static GtkAdjustment *
84 terminal_adjustment_vte(GtkWidget *terminal)
85 {
86         return (VTE_TERMINAL(terminal))->adjustment;
87 }
88 #endif
89
90 /*
91  * Update the contents of the widget with the data from our contents array.
92  */
93 static void
94 update_contents(AtkObject *obj, GtkWidget *widget)
95 {
96         int caret, i;
97         GString *s;
98
99         caret = atk_text_get_caret_offset(ATK_TEXT(obj));
100         s = g_string_new(NULL);
101         for (i = 0; i < contents->len; i++) {
102                 if (i == caret) {
103                         s = g_string_append(s, "[CARET]");
104                 }
105                 s = g_string_append_unichar(s,
106                                             g_array_index(contents,
107                                                           gunichar,
108                                                           i));
109         }
110         if (i == caret) {
111                 s = g_string_append(s, "[CARET]");
112         }
113         if (GTK_IS_LABEL(widget)) {
114                 gtk_label_set_text(GTK_LABEL(widget), s->str);
115                 gtk_label_set_selectable(GTK_LABEL(widget),
116                                          atk_text_get_n_selections(ATK_TEXT(obj)) > 0);
117                 if (gtk_label_get_selectable(GTK_LABEL(widget))) {
118                         int selection_start, selection_end;
119                         atk_text_get_selection(ATK_TEXT(obj), 0,
120                                                &selection_start,
121                                                &selection_end);
122                         gtk_label_select_region(GTK_LABEL(widget),
123                                                 selection_start, selection_end);
124                 }
125         }
126         g_string_free(s, TRUE);
127 }
128
129 /* Handle inserted text by inserting the text into our gunichar array. */
130 static void
131 text_changed_insert(AtkObject *obj, gint offset, gint length, gpointer data)
132 {
133         char *inserted, *p;
134         gunichar c;
135         int i;
136
137         inserted = atk_text_get_text(ATK_TEXT(obj), offset, offset + length);
138
139         if (!g_utf8_validate(inserted, -1, NULL)) {
140                 g_free(inserted);
141                 g_error("UTF-8 validation error");
142                 return;
143         }
144
145         p = inserted;
146         i = 0;
147         while (i < length) {
148                 c = g_utf8_get_char(p);
149                 if (offset + i >= contents->len) {
150                         g_array_append_val(contents, c);
151                 } else {
152                         g_array_insert_val(contents, offset + i, c);
153                 }
154                 i++;
155                 p = g_utf8_next_char(p);
156         }
157
158 #ifdef VTE_DEBUG
159         if ((getenv("REFLECT_VERBOSE") != NULL) &&
160             (atol(getenv("REFLECT_VERBOSE")) != 0)) {
161                 g_printerr("Inserted %d chars ('%.*s') at %d,",
162                         length, (int)(p - inserted), inserted, offset);
163                 g_printerr(" buffer contains %d characters.\n",
164                         contents->len);
165         }
166 #endif
167
168         g_free(inserted);
169
170         update_contents(obj, GTK_WIDGET(data));
171 }
172
173 /* Handle deleted text by removing the text from our gunichar array. */
174 static void
175 text_changed_delete(AtkObject *obj, gint offset, gint length, gpointer data)
176 {
177         int i;
178         for (i = offset + length - 1; i >= offset; i--) {
179                 if (i > contents->len - 1) {
180                         g_warning("Invalid character %d was deleted.\n", i);
181                 }
182                 g_array_remove_index(contents, i);
183         }
184 #ifdef VTE_DEBUG
185         if ((getenv("REFLECT_VERBOSE") != NULL) &&
186             (atol(getenv("REFLECT_VERBOSE")) != 0)) {
187                 g_printerr("Deleted %d chars at %d.\n", length, offset);
188         }
189 #endif
190         update_contents(obj, GTK_WIDGET(data));
191 }
192
193 static void
194 text_caret_moved(AtkObject *obj, gint offset, gpointer data)
195 {
196         update_contents(obj, GTK_WIDGET(data));
197 }
198
199 static void
200 text_selection_changed(AtkObject *obj, gpointer data)
201 {
202         update_contents(obj, GTK_WIDGET(data));
203 }
204
205 /* Wrapper versions. */
206 static void
207 terminal_init(GtkWidget **terminal)
208 {
209         *terminal = NULL;
210 #ifdef USE_TEXT_VIEW
211         terminal_init_text_view(terminal);
212         return;
213 #endif
214 #ifdef USE_VTE
215         terminal_init_vte(terminal);
216         return;
217 #endif
218         g_assert_not_reached();
219 }
220 static void
221 terminal_shell(GtkWidget *terminal)
222 {
223 #ifdef USE_TEXT_VIEW
224         terminal_shell_text_view(terminal);
225         return;
226 #endif
227 #ifdef USE_VTE
228         terminal_shell_vte(terminal);
229         return;
230 #endif
231         g_assert_not_reached();
232 }
233 static GtkAdjustment *
234 terminal_adjustment(GtkWidget *terminal)
235 {
236 #ifdef USE_TEXT_VIEW
237         return terminal_adjustment_text_view(terminal);
238 #endif
239 #ifdef USE_VTE
240         return terminal_adjustment_vte(terminal);
241 #endif
242         g_assert_not_reached();
243 }
244
245 int
246 main(int argc, char **argv)
247 {
248         GtkWidget *label, *terminal, *tophalf, *pane, *window, *scrollbar, *sw;
249         AtkObject *obj;
250         char *text, *p;
251         gunichar c;
252         gint count;
253
254         gtk_init(&argc, &argv);
255
256         contents = g_array_new(TRUE, FALSE, sizeof(gunichar));
257
258         terminal_init(&terminal);
259
260 #ifdef USE_TEXT_VIEW
261         tophalf = gtk_scrolled_window_new(NULL, terminal_adjustment(terminal));
262         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(tophalf),
263                                        GTK_POLICY_AUTOMATIC,
264                                        GTK_POLICY_AUTOMATIC);
265         scrollbar = NULL;
266         gtk_container_add(GTK_CONTAINER(tophalf), terminal);
267 #else
268         tophalf = gtk_hbox_new(FALSE, 0);
269
270         gtk_box_pack_start(GTK_BOX(tophalf), terminal, TRUE, TRUE, 0);
271         gtk_widget_show(terminal);
272
273         scrollbar = gtk_vscrollbar_new(terminal_adjustment(terminal));
274         gtk_box_pack_start(GTK_BOX(tophalf), scrollbar, FALSE, TRUE, 0);
275         gtk_widget_show(scrollbar);
276 #endif
277         gtk_widget_show(terminal);
278
279         label = gtk_label_new("");
280         gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
281         gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
282
283         sw = gtk_scrolled_window_new(NULL, NULL);
284         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sw), label);
285         gtk_widget_show(label);
286
287         pane = gtk_vpaned_new();
288         gtk_paned_pack1(GTK_PANED(pane), tophalf, TRUE, FALSE);
289         gtk_paned_pack2(GTK_PANED(pane), sw, TRUE, FALSE);
290         gtk_widget_show(tophalf);
291         gtk_widget_show(sw);
292
293         window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
294         g_signal_connect(G_OBJECT(window), "delete_event",
295                          G_CALLBACK(gtk_main_quit), NULL);
296         gtk_container_add(GTK_CONTAINER(window), pane);
297         gtk_widget_show(pane);
298
299         obj = gtk_widget_get_accessible(terminal);
300         g_assert(obj != NULL);
301         g_signal_connect(G_OBJECT(obj), "text-changed::insert",
302                          G_CALLBACK(text_changed_insert), label);
303         g_signal_connect(G_OBJECT(obj), "text-changed::delete",
304                          G_CALLBACK(text_changed_delete), label);
305         g_signal_connect(G_OBJECT(obj), "text-caret-moved",
306                          G_CALLBACK(text_caret_moved), label);
307         g_signal_connect(G_OBJECT(obj), "text-selection-changed",
308                          G_CALLBACK(text_selection_changed), label);
309
310         count = atk_text_get_character_count(ATK_TEXT(obj));
311         if (count > 0) {
312                 text = atk_text_get_text(ATK_TEXT(obj), 0, count);
313                 if (text != NULL) {
314                         for (p = text;
315                              contents->len < count;
316                              p = g_utf8_next_char(p)) {
317                                 c = g_utf8_get_char(p);
318                                 g_array_append_val(contents, c);
319                         }
320                         g_free(text);
321                 }
322         }
323         terminal_shell(terminal);
324
325         gtk_window_set_default_size(GTK_WINDOW(window), 600, 450);
326         gtk_widget_show(window);
327
328         update_contents(obj, terminal);
329
330         gtk_main();
331
332         g_array_free(contents, TRUE);
333         contents = NULL;
334
335         return 0;
336 }