chiark / gitweb /
use gtk native mechanisms to look up text tags in help
[disorder] / disobedience / help.c
CommitLineData
13affe66
RK
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 */
8439fcfd
RK
20/** @file disobedience/help.c
21 * @brief Help support
22 */
13affe66
RK
23
24#include "disobedience.h"
13affe66
RK
25#include "html.h"
26#include "manual.h"
27
28VECTOR_TYPE(markstack, GtkTextMark *, xrealloc);
29
30/** @brief Known tag type */
31struct tag {
32 /** @brief HTML tag name */
33 const char *name;
34
35 /** @brief Called to set up the tag */
36 void (*init)(GtkTextTag *tag);
37
13affe66
RK
38};
39
8439fcfd
RK
40/** @brief Initialize the bold tag
41 *
42 * This doesn't seem to work on OS X though the italic and monospace tags are
43 * fine, and bold is OK on Linux, even connecting to the Apple X swerver.
44 */
13affe66
RK
45static void init_bold(GtkTextTag *tag) {
46 g_object_set(G_OBJECT(tag), "weight", PANGO_WEIGHT_BOLD, (char *)0);
47}
48
8439fcfd 49/** @brief Initialize the italic tag */
13affe66
RK
50static void init_italic(GtkTextTag *tag) {
51 g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_ITALIC, (char *)0);
52}
53
8439fcfd 54/** @brief Initialize the pre tag */
74aefe58
RK
55static void init_pre(GtkTextTag *tag) {
56 g_object_set(G_OBJECT(tag), "family", "monospace", (char *)0);
57}
8439fcfd 58
13affe66
RK
59/** @brief Table of known tags
60 *
61 * Keep in alphabetical order
62 */
f3d18e00
RK
63static const struct tag tags[] = {
64 { "b", init_bold },
65 { "i", init_italic },
66 { "pre", init_pre }
13affe66
RK
67};
68
69/** @brief Number of known tags */
70#define NTAGS (sizeof tags / sizeof *tags)
71
72/** @brief State structure for insert_html() */
73struct state {
74 /** @brief The buffer to insert into */
75 GtkTextBuffer *buffer;
76
f3d18e00
RK
77 /** @brief The buffer's tag table */
78 GtkTextTagTable *tagtable;
79
13affe66
RK
80 /** @brief True if we are inside <body> */
81 int body;
82
74aefe58
RK
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
13affe66
RK
89 /** @brief Stack of marks corresponding to tags */
90 struct markstack marks[1];
91
92};
93
94/** @brief Called for an open tag */
95static 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"))
74aefe58
RK
102 ++s->body;
103 else if(!strcmp(tag, "pre"))
104 ++s->pre;
13affe66
RK
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 */
118static void html_close(const char *tag,
119 void *u) {
120 struct state *const s = u;
121 GtkTextIter start[1], end[1];
f3d18e00 122 GtkTextTag *texttag;
13affe66
RK
123
124 if(!strcmp(tag, "body"))
74aefe58
RK
125 --s->body;
126 else if(!strcmp(tag, "pre")) {
127 --s->pre;
128 s->space = 0;
129 }
13affe66
RK
130 if(!s->body)
131 return;
132 /* see if this is a known tag */
f3d18e00
RK
133 texttag = gtk_text_tag_table_lookup(s->tagtable, tag);
134 if(!texttag)
13affe66
RK
135 return;
136 /* pop the mark at the start of the region */
137 assert(s->marks->nvec > 0);
138 gtk_text_buffer_get_iter_at_mark(s->buffer, start,
139 s->marks->vec[--s->marks->nvec]);
140 gtk_text_buffer_get_iter_at_mark(s->buffer, end,
141 gtk_text_buffer_get_insert(s->buffer));
f3d18e00
RK
142 /* apply the tag */
143 gtk_text_buffer_apply_tag(s->buffer, texttag, start, end);
13affe66
RK
144 /* don't need the start mark any more */
145 gtk_text_buffer_delete_mark(s->buffer, s->marks->vec[s->marks->nvec]);
146}
147
148/** @brief Called for text */
149static void html_text(const char *text,
150 void *u) {
151 struct state *const s = u;
152
153 /* ignore header */
154 if(!s->body)
155 return;
74aefe58
RK
156 if(!s->pre) {
157 char *formatted = xmalloc(strlen(text) + 1), *t = formatted;
158 /* normalize spacing */
159 while(*text) {
160 if(isspace((unsigned char)*text)) {
161 s->space = 1;
162 ++text;
163 } else {
164 if(s->space) {
165 *t++ = ' ';
166 s->space = 0;
167 }
168 *t++ = *text++;
169 }
170 }
171 *t = 0;
172 text = formatted;
173 }
13affe66
RK
174 gtk_text_buffer_insert_at_cursor(s->buffer, text, strlen(text));
175}
176
177/** @brief Callbacks for insert_html() */
178static const struct html_parser_callbacks insert_html_callbacks = {
179 html_open,
180 html_close,
181 html_text
182};
183
184/** @brief Insert @p html into @p buffer at cursor */
185static void insert_html(GtkTextBuffer *buffer,
186 const char *html) {
187 struct state s[1];
188 size_t n;
13affe66
RK
189
190 memset(s, 0, sizeof *s);
191 s->buffer = buffer;
192 markstack_init(s->marks);
193 /* initialize tags */
f3d18e00
RK
194 s->tagtable = gtk_text_buffer_get_tag_table(s->buffer);
195 for(n = 0; n < NTAGS; ++n) {
196 GtkTextTag *const tag = gtk_text_tag_new(tags[n].name);
197 tags[n].init(tag);
198 gtk_text_tag_table_add(s->tagtable, tag);
199 }
13affe66
RK
200 /* convert the input */
201 html_parse(&insert_html_callbacks, html, s);
202}
203
288dd12f 204/** @brief Create a GtkTextBuffer with @p html rendered into it */
13affe66
RK
205static GtkTextBuffer *html_buffer(const char *html) {
206 GtkTextBuffer *buffer = gtk_text_buffer_new(NULL);
207
208 insert_html(buffer, html);
209 return buffer;
210}
211
288dd12f 212/** @brief The manual page window */
13affe66
RK
213static GtkWidget *help_window;
214
288dd12f 215/** @brief Pop up the manual page in a window */
13affe66
RK
216void popup_help(void) {
217 GtkWidget *view;
218
219 if(help_window) {
220 gtk_window_present(GTK_WINDOW(help_window));
221 return;
222 }
223 help_window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
224 g_signal_connect(help_window, "destroy",
225 G_CALLBACK(gtk_widget_destroyed), &help_window);
288dd12f 226 gtk_window_set_title(GTK_WINDOW(help_window), "Disobedience Manual Page");
13affe66 227 view = gtk_text_view_new_with_buffer(html_buffer(manual));
74aefe58 228 gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE);
d4ef4132 229 gtk_container_add(GTK_CONTAINER(help_window), scroll_widget(view));
3c46e7df 230 gtk_window_set_default_size(GTK_WINDOW(help_window), 512, 512);
13affe66
RK
231 gtk_widget_show_all(help_window);
232}
233
234/*
235Local Variables:
236c-basic-offset:2
237comment-column:40
238fill-column:79
239indent-tabs-mode:nil
240End:
241*/