chiark / gitweb /
more help widget fixes
[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
21 #include "disobedience.h"
22 #include "table.h"
23 #include "html.h"
24 #include "manual.h"
25
26 VECTOR_TYPE(markstack, GtkTextMark *, xrealloc);
27
28 /** @brief Known tag type */
29 struct tag {
30   /** @brief HTML tag name */
31   const char *name;
32
33   /** @brief Called to set up the tag */
34   void (*init)(GtkTextTag *tag);
35   
36   /** @brief GTK+ tag object */
37   GtkTextTag *tag;
38 };
39
40 static void init_bold(GtkTextTag *tag) {
41   g_object_set(G_OBJECT(tag), "weight", PANGO_WEIGHT_BOLD, (char *)0);
42 }
43
44 static void init_italic(GtkTextTag *tag) {
45   g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_ITALIC, (char *)0);
46 }
47
48 static void init_pre(GtkTextTag *tag) {
49   g_object_set(G_OBJECT(tag), "family", "monospace", (char *)0);
50 }
51 /** @brief Table of known tags
52  *
53  * Keep in alphabetical order
54  */
55 static  struct tag tags[] = {
56   { "b", init_bold, 0 },
57   { "i", init_italic, 0 },
58   { "pre", init_pre, 0 }
59 };
60
61 /** @brief Number of known tags */
62 #define NTAGS (sizeof tags / sizeof *tags)
63
64 /** @brief State structure for insert_html() */
65 struct state {
66   /** @brief The buffer to insert into */
67   GtkTextBuffer *buffer;
68
69   /** @brief True if we are inside <body> */
70   int body;
71
72   /** @brief True if inside <pre> */
73   int pre;
74
75   /** @brief True if a space is required before any non-space */
76   int space;
77
78   /** @brief Stack of marks corresponding to tags */
79   struct markstack marks[1];
80
81 };
82
83 /** @brief Called for an open tag */
84 static void html_open(const char *tag,
85                       hash attribute((unused)) *attrs,
86                       void *u) {
87   struct state *const s = u;
88   GtkTextIter iter[1];
89
90   if(!strcmp(tag, "body"))
91     ++s->body;
92   else if(!strcmp(tag, "pre"))
93     ++s->pre;
94   if(!s->body)
95     return;
96   /* push a mark for the start of the region */
97   gtk_text_buffer_get_iter_at_mark(s->buffer, iter,
98                                    gtk_text_buffer_get_insert(s->buffer));
99   markstack_append(s->marks,
100                    gtk_text_buffer_create_mark(s->buffer,
101                                                0/* mark_name */,
102                                                iter,
103                                                TRUE/*left_gravity*/));
104 }
105
106 /** @brief Called for a close tag */
107 static void html_close(const char *tag,
108                        void *u) {
109   struct state *const s = u;
110   GtkTextIter start[1], end[1];
111   int n;
112
113   if(!strcmp(tag, "body"))
114     --s->body;
115   else if(!strcmp(tag, "pre")) {
116     --s->pre;
117     s->space = 0;
118   }
119   if(!s->body)
120     return;
121   /* see if this is a known tag */
122   if((n = TABLE_FIND(tags, struct tag, name, tag)) < 0)
123     return;
124   /* pop the mark at the start of the region */
125   assert(s->marks->nvec > 0);
126   gtk_text_buffer_get_iter_at_mark(s->buffer, start,
127                                    s->marks->vec[--s->marks->nvec]);
128   gtk_text_buffer_get_iter_at_mark(s->buffer, end,
129                                    gtk_text_buffer_get_insert(s->buffer));
130   /* apply a tag */
131   gtk_text_buffer_apply_tag(s->buffer, tags[n].tag, start, end);
132   /* don't need the start mark any more */
133   gtk_text_buffer_delete_mark(s->buffer, s->marks->vec[s->marks->nvec]);
134 }
135
136 /** @brief Called for text */
137 static void html_text(const char *text,
138                       void *u) {
139   struct state *const s = u;
140
141   /* ignore header */
142   if(!s->body)
143     return;
144   if(!s->pre) {
145     char *formatted = xmalloc(strlen(text) + 1), *t = formatted;
146     /* normalize spacing */
147     while(*text) {
148       if(isspace((unsigned char)*text)) {
149         s->space = 1;
150         ++text;
151       } else {
152         if(s->space) {
153           *t++ = ' ';
154           s->space = 0;
155         }
156         *t++ = *text++;
157       }
158     }
159     *t = 0;
160     text = formatted;
161   }
162   gtk_text_buffer_insert_at_cursor(s->buffer, text, strlen(text));
163 }
164
165 /** @brief Callbacks for insert_html() */
166 static const struct html_parser_callbacks insert_html_callbacks = {
167   html_open,
168   html_close,
169   html_text
170 };
171
172 /** @brief Insert @p html into @p buffer at cursor */
173 static void insert_html(GtkTextBuffer *buffer,
174                         const char *html) {
175   struct state s[1];
176   size_t n;
177   GtkTextTagTable *tagtable;
178
179   memset(s, 0, sizeof *s);
180   s->buffer = buffer;
181   markstack_init(s->marks);
182   /* initialize tags */
183   if(!tags[0].tag)
184     for(n = 0; n < NTAGS; ++n)
185       tags[n].init(tags[n].tag = gtk_text_tag_new(0));
186   /* add tags to this buffer */
187   tagtable = gtk_text_buffer_get_tag_table(s->buffer);
188   for(n = 0; n < NTAGS; ++n)
189     gtk_text_tag_table_add(tagtable, tags[n].tag);
190   /* convert the input */
191   html_parse(&insert_html_callbacks, html, s);
192 }
193
194 static GtkTextBuffer *html_buffer(const char *html) {
195   GtkTextBuffer *buffer = gtk_text_buffer_new(NULL);
196
197   insert_html(buffer, html);
198   return buffer;
199 }
200
201 static GtkWidget *help_window;
202
203 void popup_help(void) {
204   GtkWidget *view;
205   
206   if(help_window) {
207     gtk_window_present(GTK_WINDOW(help_window));
208     return;
209   }
210   help_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
211   g_signal_connect(help_window, "destroy",
212                    G_CALLBACK(gtk_widget_destroyed), &help_window);
213   gtk_window_set_title(GTK_WINDOW(help_window), "Disobedience Manual");
214   view = gtk_text_view_new_with_buffer(html_buffer(manual));
215   gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE);
216   gtk_container_add(GTK_CONTAINER(help_window),
217                     scroll_widget(view,
218                                   "help"));
219   gtk_window_set_default_size(GTK_WINDOW(help_window), 512, 512);
220   gtk_widget_show_all(help_window);
221 }
222
223 /*
224 Local Variables:
225 c-basic-offset:2
226 comment-column:40
227 fill-column:79
228 indent-tabs-mode:nil
229 End:
230 */