chiark / gitweb /
misc improvements to man page popup
[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 */
20
21#include "disobedience.h"
22#include "table.h"
23#include "html.h"
24#include "manual.h"
25
26VECTOR_TYPE(markstack, GtkTextMark *, xrealloc);
27
28/** @brief Known tag type */
29struct 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
40static void init_bold(GtkTextTag *tag) {
41 g_object_set(G_OBJECT(tag), "weight", PANGO_WEIGHT_BOLD, (char *)0);
42}
43
44static void init_italic(GtkTextTag *tag) {
45 g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_ITALIC, (char *)0);
46}
47
74aefe58
RK
48static void init_pre(GtkTextTag *tag) {
49 g_object_set(G_OBJECT(tag), "family", "monospace", (char *)0);
50}
13affe66
RK
51/** @brief Table of known tags
52 *
53 * Keep in alphabetical order
54 */
55static struct tag tags[] = {
56 { "b", init_bold, 0 },
74aefe58
RK
57 { "i", init_italic, 0 },
58 { "pre", init_pre, 0 }
13affe66
RK
59};
60
61/** @brief Number of known tags */
62#define NTAGS (sizeof tags / sizeof *tags)
63
64/** @brief State structure for insert_html() */
65struct state {
66 /** @brief The buffer to insert into */
67 GtkTextBuffer *buffer;
68
69 /** @brief True if we are inside <body> */
70 int body;
71
74aefe58
RK
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
13affe66
RK
78 /** @brief Stack of marks corresponding to tags */
79 struct markstack marks[1];
80
81};
82
83/** @brief Called for an open tag */
84static 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"))
74aefe58
RK
91 ++s->body;
92 else if(!strcmp(tag, "pre"))
93 ++s->pre;
13affe66
RK
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 */
107static 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"))
74aefe58
RK
114 --s->body;
115 else if(!strcmp(tag, "pre")) {
116 --s->pre;
117 s->space = 0;
118 }
13affe66
RK
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 */
137static 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;
74aefe58
RK
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 }
13affe66
RK
162 gtk_text_buffer_insert_at_cursor(s->buffer, text, strlen(text));
163}
164
165/** @brief Callbacks for insert_html() */
166static 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 */
173static 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
194static 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
201static GtkWidget *help_window;
202
203void 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));
74aefe58 215 gtk_text_view_set_editable(GTK_TEXT_VIEW(view), FALSE);
13affe66
RK
216 gtk_container_add(GTK_CONTAINER(help_window),
217 scroll_widget(view,
218 "help"));
74aefe58 219 gtk_window_set_default_size(GTK_WINDOW(help_window), 480, 512);
13affe66
RK
220 gtk_widget_show_all(help_window);
221}
222
223/*
224Local Variables:
225c-basic-offset:2
226comment-column:40
227fill-column:79
228indent-tabs-mode:nil
229End:
230*/