b60b7033464ac6878a85f0e7f435dc5818b1972a
[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 #ident "$Id$"
20 #include "../config.h"
21 #include <sys/types.h>
22 #include <stdio.h>
23 #include <stdlib.h>
24 #include <string.h>
25 #include <unistd.h>
26 #include <gtk/gtk.h>
27 #include <atk/atk.h>
28 #ifdef USE_VTE
29 #include "vte.h"
30 #endif
31 #ifdef USE_ZVT
32 #ifdef HAVE_ZVT
33 #include <libzvt/libzvt.h>
34 #endif
35 #endif
36
37 static GArray *contents = NULL;
38
39 #ifdef USE_TEXT_VIEW
40 /*
41  * Implementation for a TextView widget.
42  */
43 static void
44 terminal_init_text_view(GtkWidget **widget)
45 {
46         *widget = gtk_text_view_new();
47         gtk_text_view_set_editable(GTK_TEXT_VIEW(*widget), TRUE);
48 }
49 static void
50 terminal_shell_text_view(GtkWidget *widget)
51 {
52         /* no-op */
53 }
54 static GtkAdjustment *
55 terminal_adjustment_text_view(GtkWidget *terminal)
56 {
57         return (GTK_TEXT_VIEW(terminal))->vadjustment;
58 }
59 #endif
60 #ifdef USE_VTE
61 /*
62  * Implementation for a VteTerminal widget.
63  */
64 static void
65 terminal_init_vte(GtkWidget **terminal)
66 {
67         *terminal = vte_terminal_new();
68         g_signal_connect(G_OBJECT(*terminal), "eof",
69                          G_CALLBACK(gtk_main_quit), NULL);
70         g_signal_connect(G_OBJECT(*terminal), "child-exited",
71                          G_CALLBACK(gtk_main_quit), NULL);
72 }
73 static void
74 terminal_shell_vte(GtkWidget *terminal)
75 {
76         vte_terminal_fork_command(VTE_TERMINAL(terminal),
77                                   getenv("SHELL") ? getenv("SHELL") : "/bin/sh",
78                                   NULL,
79                                   NULL,
80                                   g_get_home_dir() ? g_get_home_dir() : NULL,
81                                   FALSE,
82                                   FALSE,
83                                   FALSE);
84 }
85 static GtkAdjustment *
86 terminal_adjustment_vte(GtkWidget *terminal)
87 {
88         return (VTE_TERMINAL(terminal))->adjustment;
89 }
90 #endif
91 #ifdef USE_ZVT
92 #ifdef HAVE_ZVT
93 /*
94  * Implementation for a ZvtTerm widget.
95  */
96 static void
97 terminal_hint_zvt(GtkWidget *widget, gpointer data)
98 {
99         ZvtTerm *terminal;
100         GtkStyle *style;
101         GdkGeometry hints;
102         GtkWidget *toplevel;
103
104         terminal = ZVT_TERM(widget);
105
106         toplevel = gtk_widget_get_toplevel(widget);
107         g_assert(toplevel != NULL);
108
109         gtk_widget_ensure_style(widget);
110         style = widget->style;
111         hints.base_width = style->xthickness * 2 + 2;
112         hints.base_height = style->ythickness * 2;
113
114         hints.width_inc = terminal->charwidth;
115         hints.height_inc = terminal->charheight;
116         hints.min_width = hints.base_width + hints.width_inc;
117         hints.min_height = hints.base_height + hints.height_inc;
118
119         gtk_window_set_geometry_hints(GTK_WINDOW(toplevel),
120                                       widget,
121                                       &hints,
122                                       GDK_HINT_RESIZE_INC |
123                                       GDK_HINT_MIN_SIZE |
124                                       GDK_HINT_BASE_SIZE);
125         gtk_widget_queue_resize(widget);
126 }
127 static void
128 terminal_init_zvt(GtkWidget **terminal)
129 {
130         *terminal = zvt_term_new();
131         g_signal_connect_after(G_OBJECT(*terminal), "realize",
132                                G_CALLBACK(terminal_hint_zvt), NULL);
133 }
134 static void
135 terminal_shell_zvt(GtkWidget *terminal)
136 {
137         const char *shell;
138         shell = getenv("SHELL") ? getenv("SHELL") : "/bin/sh";
139         g_signal_connect(G_OBJECT(terminal), "child-died",
140                          G_CALLBACK(gtk_main_quit), NULL);
141         if (zvt_term_forkpty(ZVT_TERM(terminal), 0) == 0) {
142                 execlp(shell, shell, NULL);
143                 g_assert_not_reached();
144         }
145 }
146 static GtkAdjustment *
147 terminal_adjustment_zvt(GtkWidget *terminal)
148 {
149         return (ZVT_TERM(terminal))->adjustment;
150 }
151 #else
152 /*
153  * Implementation for broken setups.
154  */
155 static void
156 terminal_init_broken(GtkWidget **terminal)
157 {
158         g_error("libzvt not found at compile-time");
159         _exit(1);
160 }
161 static void
162 terminal_shell_broken(GtkWidget *terminal)
163 {
164         g_error("libzvt not found at compile-time");
165         _exit(1);
166 }
167 static GtkAdjustment *
168 terminal_adjustment_broken(GtkWidget *terminal)
169 {
170         g_error("libzvt not found at compile-time");
171         _exit(1);
172 }
173 #endif
174 #endif
175
176 /*
177  * Update the contents of the widget with the data from our contents array.
178  */
179 static void
180 update_contents(AtkObject *obj, GtkWidget *widget)
181 {
182         int caret, i;
183         GString *s;
184
185         caret = atk_text_get_caret_offset(ATK_TEXT(obj));
186         s = g_string_new("");
187         for (i = 0; i < contents->len; i++) {
188                 if (i == caret) {
189                         s = g_string_append(s, "[CARET]");
190                 }
191                 s = g_string_append_unichar(s,
192                                             g_array_index(contents,
193                                                           gunichar,
194                                                           i));
195         }
196         if (i == caret) {
197                 s = g_string_append(s, "[CARET]");
198         }
199         if (GTK_IS_LABEL(widget)) {
200                 gtk_label_set_text(GTK_LABEL(widget), s->str);
201         }
202         g_string_free(s, TRUE);
203 }
204
205 /* Handle inserted text by inserting the text into our gunichar array. */
206 static void
207 text_changed_insert(AtkObject *obj, gint offset, gint length, gpointer data)
208 {
209         char *inserted, *p;
210         gunichar c;
211         int i;
212
213         inserted = atk_text_get_text(ATK_TEXT(obj), offset, offset + length);
214
215         if (!g_utf8_validate(inserted, -1, NULL)) {
216                 g_free(inserted);
217                 g_error("UTF-8 validation error");
218                 return;
219         }
220
221         p = inserted;
222         i = 0;
223         while (i < length) {
224                 c = g_utf8_get_char(p);
225                 if (offset + i >= contents->len) {
226                         g_array_append_val(contents, c);
227                 } else {
228                         g_array_insert_val(contents, offset + i, c);
229                 }
230                 i++;
231                 p = g_utf8_next_char(p);
232         }
233
234 #ifdef VTE_DEBUG
235         if ((getenv("REFLECT_VERBOSE") != NULL) &&
236             (atol(getenv("REFLECT_VERBOSE")) != 0)) {
237                 fprintf(stderr, "Inserted %d chars ('%.*s') at %d,",
238                         length, (int)(p - inserted), inserted, offset);
239                 fprintf(stderr, " buffer contains %d characters.\n",
240                         contents->len);
241         }
242 #endif
243
244         g_free(inserted);
245
246         update_contents(obj, GTK_WIDGET(data));
247 }
248
249 /* Handle deleted text by removing the text from our gunichar array. */
250 static void
251 text_changed_delete(AtkObject *obj, gint offset, gint length, gpointer data)
252 {
253         int i;
254         for (i = offset + length - 1; i >= offset; i--) {
255                 if (i > contents->len - 1) {
256                         g_warning("Invalid character %d was deleted.\n", i);
257                 }
258                 g_array_remove_index(contents, i);
259         }
260 #ifdef VTE_DEBUG
261         if ((getenv("REFLECT_VERBOSE") != NULL) &&
262             (atol(getenv("REFLECT_VERBOSE")) != 0)) {
263                 fprintf(stderr, "Deleted %d chars at %d.\n", length, offset);
264         }
265 #endif
266         update_contents(obj, GTK_WIDGET(data));
267 }
268
269 static void
270 text_caret_moved(AtkObject *obj, gint offset, gpointer data)
271 {
272         update_contents(obj, GTK_WIDGET(data));
273 }
274
275 /* Wrapper versions. */
276 static void
277 terminal_init(GtkWidget **terminal)
278 {
279         *terminal = NULL;
280 #ifdef USE_ZVT
281 #ifdef HAVE_ZVT
282         terminal_init_zvt(terminal);
283         return;
284 #else
285         terminal_init_broken(terminal);
286         return;
287 #endif
288 #endif
289 #ifdef USE_TEXT_VIEW
290         terminal_init_text_view(terminal);
291         return;
292 #endif
293 #ifdef USE_VTE
294         terminal_init_vte(terminal);
295         return;
296 #endif
297         g_assert_not_reached();
298 }
299 static void
300 terminal_shell(GtkWidget *terminal)
301 {
302 #ifdef USE_ZVT
303 #ifdef HAVE_ZVT
304         terminal_shell_zvt(terminal);
305         return;
306 #else
307         terminal_shell_broken(terminal);
308         return;
309 #endif
310 #endif
311 #ifdef USE_TEXT_VIEW
312         terminal_shell_text_view(terminal);
313         return;
314 #endif
315 #ifdef USE_VTE
316         terminal_shell_vte(terminal);
317         return;
318 #endif
319         g_assert_not_reached();
320 }
321 static GtkAdjustment *
322 terminal_adjustment(GtkWidget *terminal)
323 {
324 #ifdef USE_ZVT
325 #ifdef HAVE_ZVT
326         return terminal_adjustment_zvt(terminal);
327 #else
328         return terminal_adjustment_broken(terminal);
329 #endif
330 #endif
331 #ifdef USE_TEXT_VIEW
332         return terminal_adjustment_text_view(terminal);
333 #endif
334 #ifdef USE_VTE
335         return terminal_adjustment_vte(terminal);
336 #endif
337         g_assert_not_reached();
338 }
339
340 int
341 main(int argc, char **argv)
342 {
343         GtkWidget *label, *terminal, *tophalf, *pane, *window, *scrollbar, *sw;
344         AtkObject *obj;
345         char *text, *p;
346         gunichar c;
347         gint count;
348
349         gtk_init(&argc, &argv);
350
351         contents = g_array_new(TRUE, FALSE, sizeof(gunichar));
352
353         terminal_init(&terminal);
354
355 #ifdef USE_TEXT_VIEW
356         tophalf = gtk_scrolled_window_new(NULL, terminal_adjustment(terminal));
357         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(tophalf),
358                                        GTK_POLICY_AUTOMATIC,
359                                        GTK_POLICY_AUTOMATIC);
360         scrollbar = NULL;
361         gtk_container_add(GTK_CONTAINER(tophalf), terminal);
362 #else
363         tophalf = gtk_hbox_new(FALSE, 0);
364
365         gtk_box_pack_start(GTK_BOX(tophalf), terminal, TRUE, TRUE, 0);
366         gtk_widget_show(terminal);
367
368         scrollbar = gtk_vscrollbar_new(terminal_adjustment(terminal));
369         gtk_box_pack_start(GTK_BOX(tophalf), scrollbar, FALSE, TRUE, 0);
370         gtk_widget_show(scrollbar);
371 #endif
372         gtk_widget_show(terminal);
373
374         label = gtk_label_new("");
375         gtk_label_set_justify(GTK_LABEL(label), GTK_JUSTIFY_LEFT);
376         gtk_misc_set_alignment(GTK_MISC(label), 0, 0);
377
378         sw = gtk_scrolled_window_new(NULL, NULL);
379         gtk_scrolled_window_add_with_viewport(GTK_SCROLLED_WINDOW(sw), label);
380         gtk_widget_show(label);
381
382         pane = gtk_vpaned_new();
383         gtk_paned_pack1(GTK_PANED(pane), tophalf, TRUE, FALSE);
384         gtk_paned_pack2(GTK_PANED(pane), sw, TRUE, FALSE);
385         gtk_widget_show(tophalf);
386         gtk_widget_show(sw);
387
388         window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
389         g_signal_connect(G_OBJECT(window), "delete_event",
390                          G_CALLBACK(gtk_main_quit), NULL);
391         gtk_container_add(GTK_CONTAINER(window), pane);
392         gtk_widget_show(pane);
393
394         obj = gtk_widget_get_accessible(terminal);
395         g_assert(obj != NULL);
396         g_signal_connect(G_OBJECT(obj), "text-changed::insert",
397                          G_CALLBACK(text_changed_insert), label);
398         g_signal_connect(G_OBJECT(obj), "text-changed::delete",
399                          G_CALLBACK(text_changed_delete), label);
400         g_signal_connect(G_OBJECT(obj), "text-caret-moved",
401                          G_CALLBACK(text_caret_moved), label);
402
403         count = atk_text_get_character_count(ATK_TEXT(obj));
404         if (count > 0) {
405                 text = atk_text_get_text(ATK_TEXT(obj), 0, count);
406                 if (text != NULL) {
407                         for (p = text;
408                              contents->len < count;
409                              p = g_utf8_next_char(p)) {
410                                 c = g_utf8_get_char(p);
411                                 g_array_append_val(contents, c);
412                         }
413                         g_free(text);
414                 }
415         }
416         terminal_shell(terminal);
417
418         gtk_window_set_default_size(GTK_WINDOW(window), 600, 450);
419         gtk_widget_show(window);
420
421         update_contents(obj, terminal);
422
423         gtk_main();
424
425         g_array_free(contents, TRUE);
426         contents = NULL;
427
428         return 0;
429 }