chiark / gitweb /
server shouldn't crash on client disconnect!
[disorder] / disobedience / help.c
1 /*
2  * This file is part of DisOrder
3  * Copyright (C) 2007 Richard Kettlewell
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
18  * USA
19  */
20 /** @file disobedience/help.c
21  * @brief Help support
22  */
23
24 #include "disobedience.h"
25 #include "table.h"
26 #include "html.h"
27 #include "manual.h"
28
29 VECTOR_TYPE(markstack, GtkTextMark *, xrealloc);
30
31 /** @brief Known tag type */
32 struct tag {
33   /** @brief HTML tag name */
34   const char *name;
35
36   /** @brief Called to set up the tag */
37   void (*init)(GtkTextTag *tag);
38   
39   /** @brief GTK+ tag object */
40   GtkTextTag *tag;
41 };
42
43 /** @brief Initialize the bold tag
44  *
45  * This doesn't seem to work on OS X though the italic and monospace tags are
46  * fine, and bold is OK on Linux, even connecting to the Apple X swerver.
47  */
48 static void init_bold(GtkTextTag *tag) {
49   g_object_set(G_OBJECT(tag), "weight", PANGO_WEIGHT_BOLD, (char *)0);
50 }
51
52 /** @brief Initialize the italic tag */
53 static void init_italic(GtkTextTag *tag) {
54   g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_ITALIC, (char *)0);
55 }
56
57 /** @brief Initialize the pre tag */
58 static void init_pre(GtkTextTag *tag) {
59   g_object_set(G_OBJECT(tag), "family", "monospace", (char *)0);
60 }
61
62 /** @brief Table of known tags
63  *
64  * Keep in alphabetical order
65  */
66 static  struct tag tags[] = {
67   { "b", init_bold, 0 },
68   { "i", init_italic, 0 },
69   { "pre", init_pre, 0 }
70 };
71
72 /** @brief Number of known tags */
73 #define NTAGS (sizeof tags / sizeof *tags)
74
75 /** @brief State structure for insert_html() */
76 struct state {
77   /** @brief The buffer to insert into */
78   GtkTextBuffer *buffer;
79
80   /** @brief True if we are inside <body> */
81   int body;
82
83   /** @brief True if inside <pre> */
84   int pre;
85
86   /** @brief True if a space is required before any non-space */
87   int space;
88
89   /** @brief Stack of marks corresponding to tags */
90   struct markstack marks[1];
91
92 };
93
94 /** @brief Called for an open tag */
95 static void html_open(const char *tag,
96                       hash attribute((unused)) *attrs,
97                       void *u) {
98   struct state *const s = u;
99   GtkTextIter iter[1];
100
101   if(!strcmp(tag, "body"))
102     ++s->body;
103   else if(!strcmp(tag, "pre"))
104     ++s->pre;
105   if(!s->body)
106     return;
107   /* push a mark for the start of the region */
108   gtk_text_buffer_get_iter_at_mark(s->buffer, iter,
109                                    gtk_text_buffer_get_insert(s->buffer));
110   markstack_append(s->marks,
111                    gtk_text_buffer_create_mark(s->buffer,
112                                                0/* mark_name */,
113                                                iter,
114                                                TRUE/*left_gravity*/));
115 }
116
117 /** @brief Called for a close tag */
118 static void html_close(const char *tag,
119                        void *u) {
120   struct state *const s = u;
121   GtkTextIter start[1], end[1];
122   int n;
123
124   if(!strcmp(tag, "body"))
125     --s->body;
126   else if(!strcmp(tag, "pre")) {
127     --s->pre;
128     s->space = 0;
129   }
130   if(!s->body)
131     return;
132   /* see if this is a known tag */
133   if((n = TABLE_FIND(tags, struct tag, name, tag)) < 0)
134     return;
135   /* pop the mark at the start of the region */
136   assert(s->marks->nvec > 0);
137   gtk_text_buffer_get_iter_at_mark(s->buffer, start,
138                                    s->marks->vec[--s->marks->nvec]);
139   gtk_text_buffer_get_iter_at_mark(s->buffer, end,
140                                    gtk_text_buffer_get_insert(s->buffer));
141   /* apply a tag */
142   gtk_text_buffer_apply_tag(s->buffer, tags[n].tag, start, end);
143   /* don't need the start mark any more */
144   gtk_text_buffer_delete_mark(s->buffer, s->marks->vec[s->marks->nvec]);
145 }
146
147 /** @brief Called for text */
148 static void html_text(const char *text,
149                       void *u) {
150   struct state *const s = u;
151
152   /* ignore header */
153   if(!s->body)
154     return;
155   if(!s->pre) {
156     char *formatted = xmalloc(strlen(text) + 1), *t = formatted;
157     /* normalize spacing */
158     while(*text) {
159       if(isspace((unsigned char)*text)) {
160         s->space = 1;
161         ++text;
162       } else {
163         if(s->space) {
164           *t++ = ' ';
165           s->space = 0;
166         }
167         *t++ = *text++;
168       }
169     }
170     *t = 0;
171     text = formatted;
172   }
173   gtk_text_buffer_insert_at_cursor(s->buffer, text, strlen(text));
174 }
175
176 /** @brief Callbacks for insert_html() */
177 static const struct html_parser_callbacks insert_html_callbacks = {
178   html_open,
179   html_close,
180   html_text
181 };
182
183 /** @brief Insert @p html into @p buffer at cursor */
184 static void insert_html(GtkTextBuffer *buffer,
185                         const char *html) {
186   struct state s[1];
187   size_t n;
188   GtkTextTagTable *tagtable;
189
190   memset(s, 0, sizeof *s);
191   s->buffer = buffer;
192   markstack_init(s->marks);
193   /* initialize tags */
194   if(!tags[0].tag)
195     for(n = 0; n < NTAGS; ++n)
196       tags[n].init(tags[n].tag = gtk_text_tag_new(0));
197   /* add tags to this buffer */
198   tagtable = gtk_text_buffer_get_tag_table(s->buffer);
199   for(n = 0; n < NTAGS; ++n)
200     gtk_text_tag_table_add(tagtable, tags[n].tag);
201   /* convert the input */
202   html_parse(&insert_html_callbacks, html, s);
203 }
204
205 /** @brief Create a GtkTextBuffer with @p html rendered into it */
206 static GtkTextBuffer *html_buffer(const char *html) {
207   GtkTextBuffer *buffer = gtk_text_buffer_new(NULL);
208
209   insert_html(buffer, html);
210   return buffer;
211 }
212
213 /** @brief The manual page window */
214 static GtkWidget *help_window;
215
216 /** @brief Pop up the manual page in a window */
217 void popup_help(void) {
218   GtkWidget *view;
219   
220   if(help_window) {
221     gtk_window_present(GTK_WINDOW(help_window));
222     return;
223   }
224   help_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
225   g_signal_connect(help_window, "destroy",
226                    G_CALLBACK(gtk_widget_destroyed), &help_window);
227   gtk_window_set_title(GTK_WINDOW(help_window), "Disobedience Manual Page");
228   view = gtk_text_view_new_with_buffer(html_buffer(manual));
229   gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE);
230   gtk_container_add(GTK_CONTAINER(help_window), scroll_widget(view));
231   gtk_window_set_default_size(GTK_WINDOW(help_window), 512, 512);
232   gtk_widget_show_all(help_window);
233 }
234
235 /*
236 Local Variables:
237 c-basic-offset:2
238 comment-column:40
239 fill-column:79
240 indent-tabs-mode:nil
241 End:
242 */