From 13affe66e42887bf256d91a719d8e25f51600136 Mon Sep 17 00:00:00 2001 Message-Id: <13affe66e42887bf256d91a719d8e25f51600136.1715657743.git.mdw@distorted.org.uk> From: Mark Wooding Date: Sun, 21 Oct 2007 20:13:18 +0100 Subject: [PATCH] help menu can now pop up the man page Organization: Straylight/Edgeware From: Richard Kettlewell --- .bzrignore | 2 + disobedience/Makefile.am | 16 ++- disobedience/disobedience.h | 4 + disobedience/help.c | 194 ++++++++++++++++++++++++++++++++++ disobedience/menu.c | 16 +++ doc/Makefile.am | 2 +- lib/Makefile.am | 1 + lib/charset.c | 45 +++++--- lib/charset.h | 4 + lib/html.c | 205 ++++++++++++++++++++++++++++++++++++ lib/html.h | 66 ++++++++++++ scripts/htmlman | 53 +++++++--- 12 files changed, 571 insertions(+), 37 deletions(-) create mode 100644 disobedience/help.c create mode 100644 lib/html.c create mode 100644 lib/html.h diff --git a/.bzrignore b/.bzrignore index 103b31d..2625610 100644 --- a/.bzrignore +++ b/.bzrignore @@ -109,3 +109,5 @@ doc/disorder-normalize.8.html doc/disorder-decode.8.html doc/disorder-decode.8 doc/plumbing.png +disobedience/manual.h +disobedience/manual.html diff --git a/disobedience/Makefile.am b/disobedience/Makefile.am index 86d1102..561f8bb 100644 --- a/disobedience/Makefile.am +++ b/disobedience/Makefile.am @@ -25,7 +25,7 @@ AM_CFLAGS=$(GLIB_CFLAGS) $(GTK_CFLAGS) disobedience_SOURCES=disobedience.h disobedience.c client.c queue.c \ choose.c misc.c style.h control.c properties.c menu.c \ - log.c progress.c login.c rtp.c \ + log.c progress.c login.c rtp.c help.c \ ../lib/memgc.c disobedience_LDADD=../lib/libdisorder.a $(LIBPCRE) $(LIBGC) $(LIBGCRYPT) disobedience_LDFLAGS=$(GTK_LIBS) @@ -38,8 +38,18 @@ check: check-help disobedience.o: style.h style.h: ${srcdir}/disobedience.rc ${top_srcdir}/scripts/text2c - ${top_srcdir}/scripts/text2c style ${srcdir}/disobedience.rc > style.h.tmp - mv style.h.tmp style.h + ${top_srcdir}/scripts/text2c style ${srcdir}/disobedience.rc > $@.tmp + mv $@.tmp $@ + +manual.html: ../doc/disobedience.1 $(top_srcdir)/scripts/htmlman + rm -f $@.new + $(top_srcdir)/scripts/htmlman $< >$@.new + chmod 444 $@.new + mv -f $@.new $@ + +manual.h: manual.html ${top_srcdir}/scripts/text2c + ${top_srcdir}/scripts/text2c manual manual.html > $@.tmp + mv $@.tmp $@ EXTRA_DIST=disobedience.rc diff --git a/disobedience/disobedience.h b/disobedience/disobedience.h index 884ebfe..293a773 100644 --- a/disobedience/disobedience.h +++ b/disobedience/disobedience.h @@ -235,6 +235,10 @@ void choose_update(void); void login_box(void); +/* Help */ + +void popup_help(void); + /* RTP */ int rtp_running(void); diff --git a/disobedience/help.c b/disobedience/help.c new file mode 100644 index 0000000..09de980 --- /dev/null +++ b/disobedience/help.c @@ -0,0 +1,194 @@ +/* + * This file is part of DisOrder + * Copyright (C) 2007 Richard Kettlewell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ + +#include "disobedience.h" +#include "table.h" +#include "html.h" +#include "manual.h" + +VECTOR_TYPE(markstack, GtkTextMark *, xrealloc); + +/** @brief Known tag type */ +struct tag { + /** @brief HTML tag name */ + const char *name; + + /** @brief Called to set up the tag */ + void (*init)(GtkTextTag *tag); + + /** @brief GTK+ tag object */ + GtkTextTag *tag; +}; + +static void init_bold(GtkTextTag *tag) { + g_object_set(G_OBJECT(tag), "weight", PANGO_WEIGHT_BOLD, (char *)0); +} + +static void init_italic(GtkTextTag *tag) { + g_object_set(G_OBJECT(tag), "style", PANGO_STYLE_ITALIC, (char *)0); +} + +/** @brief Table of known tags + * + * Keep in alphabetical order + */ +static struct tag tags[] = { + { "b", init_bold, 0 }, + { "i", init_italic, 0 } +}; + +/** @brief Number of known tags */ +#define NTAGS (sizeof tags / sizeof *tags) + +/** @brief State structure for insert_html() */ +struct state { + /** @brief The buffer to insert into */ + GtkTextBuffer *buffer; + + /** @brief True if we are inside */ + int body; + + /** @brief Stack of marks corresponding to tags */ + struct markstack marks[1]; + +}; + +/** @brief Called for an open tag */ +static void html_open(const char *tag, + hash attribute((unused)) *attrs, + void *u) { + struct state *const s = u; + GtkTextIter iter[1]; + + if(!strcmp(tag, "body")) + s->body = 1; + if(!s->body) + return; + /* push a mark for the start of the region */ + gtk_text_buffer_get_iter_at_mark(s->buffer, iter, + gtk_text_buffer_get_insert(s->buffer)); + markstack_append(s->marks, + gtk_text_buffer_create_mark(s->buffer, + 0/* mark_name */, + iter, + TRUE/*left_gravity*/)); +} + +/** @brief Called for a close tag */ +static void html_close(const char *tag, + void *u) { + struct state *const s = u; + GtkTextIter start[1], end[1]; + int n; + + if(!strcmp(tag, "body")) + s->body = 0; + if(!s->body) + return; + /* see if this is a known tag */ + if((n = TABLE_FIND(tags, struct tag, name, tag)) < 0) + return; + /* pop the mark at the start of the region */ + assert(s->marks->nvec > 0); + gtk_text_buffer_get_iter_at_mark(s->buffer, start, + s->marks->vec[--s->marks->nvec]); + gtk_text_buffer_get_iter_at_mark(s->buffer, end, + gtk_text_buffer_get_insert(s->buffer)); + /* apply a tag */ + gtk_text_buffer_apply_tag(s->buffer, tags[n].tag, start, end); + /* don't need the start mark any more */ + gtk_text_buffer_delete_mark(s->buffer, s->marks->vec[s->marks->nvec]); +} + +/** @brief Called for text */ +static void html_text(const char *text, + void *u) { + struct state *const s = u; + + /* ignore header */ + if(!s->body) + return; + gtk_text_buffer_insert_at_cursor(s->buffer, text, strlen(text)); +} + +/** @brief Callbacks for insert_html() */ +static const struct html_parser_callbacks insert_html_callbacks = { + html_open, + html_close, + html_text +}; + +/** @brief Insert @p html into @p buffer at cursor */ +static void insert_html(GtkTextBuffer *buffer, + const char *html) { + struct state s[1]; + size_t n; + GtkTextTagTable *tagtable; + + memset(s, 0, sizeof *s); + s->buffer = buffer; + markstack_init(s->marks); + /* initialize tags */ + if(!tags[0].tag) + for(n = 0; n < NTAGS; ++n) + tags[n].init(tags[n].tag = gtk_text_tag_new(0)); + /* add tags to this buffer */ + tagtable = gtk_text_buffer_get_tag_table(s->buffer); + for(n = 0; n < NTAGS; ++n) + gtk_text_tag_table_add(tagtable, tags[n].tag); + /* convert the input */ + html_parse(&insert_html_callbacks, html, s); +} + +static GtkTextBuffer *html_buffer(const char *html) { + GtkTextBuffer *buffer = gtk_text_buffer_new(NULL); + + insert_html(buffer, html); + return buffer; +} + +static GtkWidget *help_window; + +void popup_help(void) { + GtkWidget *view; + + if(help_window) { + gtk_window_present(GTK_WINDOW(help_window)); + return; + } + help_window = gtk_window_new(GTK_WINDOW_TOPLEVEL); + g_signal_connect(help_window, "destroy", + G_CALLBACK(gtk_widget_destroyed), &help_window); + gtk_window_set_title(GTK_WINDOW(help_window), "Disobedience Manual"); + view = gtk_text_view_new_with_buffer(html_buffer(manual)); + gtk_container_add(GTK_CONTAINER(help_window), + scroll_widget(view, + "help")); + gtk_widget_show_all(help_window); +} + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +fill-column:79 +indent-tabs-mode:nil +End: +*/ diff --git a/disobedience/menu.c b/disobedience/menu.c index 843f451..c5ace02 100644 --- a/disobedience/menu.c +++ b/disobedience/menu.c @@ -111,6 +111,14 @@ static void about_popup(gpointer attribute((unused)) callback_data, 0); } +static void manual_popup(gpointer attribute((unused)) callback_data, + guint attribute((unused)) callback_action, + GtkWidget attribute((unused)) *menu_item) { + D(("manual_popup")); + + popup_help(); +} + /** @brief Callde when version arrives, displays about... popup */ static void about_popup_got_version(void attribute((unused)) *v, const char *value) { @@ -238,6 +246,14 @@ GtkWidget *menubar(GtkWidget *w) { (char *)"", /* item_type */ 0 /* extra_data */ }, + { + (char *)"/Help/Manual page", /* path */ + 0, /* accelerator */ + manual_popup, /* callback */ + 0, /* callback_action */ + 0, /* item_type */ + 0 /* extra_data */ + }, { (char *)"/Help/About DisOrder", /* path */ 0, /* accelerator */ diff --git a/doc/Makefile.am b/doc/Makefile.am index 420d4ee..915d55a 100644 --- a/doc/Makefile.am +++ b/doc/Makefile.am @@ -36,7 +36,7 @@ HTMLMAN=$(foreach man,$(man_MANS),$(man).html) $(HTMLMAN) : %.html : % $(top_srcdir)/scripts/htmlman rm -f $@.new - $(top_srcdir)/scripts/htmlman $< >$@.new + $(top_srcdir)/scripts/htmlman -stdhead $< >$@.new chmod 444 $@.new mv -f $@.new $@ diff --git a/lib/Makefile.am b/lib/Makefile.am index 0216f05..e9b41f8 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -38,6 +38,7 @@ libdisorder_a_SOURCES=charset.c charset.h \ hash.c hash.h \ heap.h \ hex.c hex.h \ + html.c html.h \ ifreq.c ifreq.h \ inputline.c inputline.h \ kvp.c kvp.h \ diff --git a/lib/charset.c b/lib/charset.c index 2a38fbf..205084f 100644 --- a/lib/charset.c +++ b/lib/charset.c @@ -91,6 +91,33 @@ uint32_t *utf82ucs4(const char *mb) { return d.vec; } +/** @brief Convert one UCS-4 character to UTF-8 + * @param c Character to convert + * @param d Dynamic string to append UTF-8 sequence to + * @return 0 on success, -1 on error + */ +int one_ucs42utf8(uint32_t c, struct dynstr *d) { + if(c < 0x80) + dynstr_append(d, c); + else if(c < 0x800) { + dynstr_append(d, 0xC0 | (c >> 6)); + dynstr_append(d, 0x80 | (c & 0x3F)); + } else if(c < 0x10000) { + dynstr_append(d, 0xE0 | (c >> 12)); + dynstr_append(d, 0x80 | ((c >> 6) & 0x3F)); + dynstr_append(d, 0x80 | (c & 0x3F)); + } else if(c < 0x110000) { + dynstr_append(d, 0xF0 | (c >> 18)); + dynstr_append(d, 0x80 | ((c >> 12) & 0x3F)); + dynstr_append(d, 0x80 | ((c >> 6) & 0x3F)); + dynstr_append(d, 0x80 | (c & 0x3F)); + } else { + error(0, "invalid UCS-4 character %#"PRIx32, c); + return -1; + } + return 0; +} + /** @brief Convert UCS-4 to UTF-8 * @param u Pointer to 0-terminated UCS-4 string * @return Pointer to 0-terminated UTF-8 string @@ -103,24 +130,8 @@ char *ucs42utf8(const uint32_t *u) { dynstr_init(&d); while((c = *u++)) { - if(c < 0x80) - dynstr_append(&d, c); - else if(c < 0x800) { - dynstr_append(&d, 0xC0 | (c >> 6)); - dynstr_append(&d, 0x80 | (c & 0x3F)); - } else if(c < 0x10000) { - dynstr_append(&d, 0xE0 | (c >> 12)); - dynstr_append(&d, 0x80 | ((c >> 6) & 0x3F)); - dynstr_append(&d, 0x80 | (c & 0x3F)); - } else if(c < 0x110000) { - dynstr_append(&d, 0xF0 | (c >> 18)); - dynstr_append(&d, 0x80 | ((c >> 12) & 0x3F)); - dynstr_append(&d, 0x80 | ((c >> 6) & 0x3F)); - dynstr_append(&d, 0x80 | (c & 0x3F)); - } else { - error(0, "invalid UCS-4 character"); + if(one_ucs42utf8(c, &d)) return 0; - } } dynstr_terminate(&d); return d.vec; diff --git a/lib/charset.h b/lib/charset.h index f77c583..b172b76 100644 --- a/lib/charset.h +++ b/lib/charset.h @@ -20,8 +20,12 @@ #ifndef CHARSET_H #define CHARSET_H +struct dynstr; + /* Character encoding conversion routines */ +int one_ucs42utf8(uint32_t c, struct dynstr *d); + uint32_t *utf82ucs4(const char *mb); char *ucs42utf8(const uint32_t *u); char *mb2utf8(const char *mb); diff --git a/lib/html.c b/lib/html.c new file mode 100644 index 0000000..7ce16d9 --- /dev/null +++ b/lib/html.c @@ -0,0 +1,205 @@ +/* + * This file is part of DisOrder + * Copyright (C) 2007 Richard Kettlewell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ +/** @file lib/html.c + * @brief Noddy HTML parser + */ + +#include +#include "types.h" + +#include +#include +#include + +#include "hash.h" +#include "html.h" +#include "mem.h" +#include "log.h" +#include "vector.h" +#include "charset.h" +#include "table.h" + +/** @brief Entity table type */ +struct entity { + const char *name; + uint32_t value; +}; + +/** @brief Known entities + * + * We only support the entities that turn up in the HTML files we + * actually care about. + * + * Keep in alphabetical order. + */ +static const struct entity entities[] = { + { "amp", '&' }, + { "gt", '>' }, + { "lt", '<' } +}; + +/** @brief Skip whitespace */ +static const char *skipwhite(const char *input) { + while(*input && isspace((unsigned char)*input)) + ++input; + return input; +} + +/** @brief Parse an entity */ +static const char *parse_entity(const char *input, + uint32_t *entityp) { + input = skipwhite(input); + if(*input == '#') { + input = skipwhite(input + 1); + if(*input == 'x') + *entityp = strtoul(skipwhite(input + 1), (char **)&input, 16); + else + *entityp = strtoul(input, (char **)&input, 10); + } else { + struct dynstr name[1]; + int n; + + dynstr_init(name); + while(isalnum((unsigned char)*input)) + dynstr_append(name, tolower((unsigned char)*input++)); + dynstr_terminate(name); + if((n = TABLE_FIND(entities, struct entity, name, name->vec)) < 0) { + error(0, "unknown entity '%s'", name->vec); + *entityp = '?'; + } else + *entityp = entities[n].value; + } + input = skipwhite(input); + if(*input == ';') + ++input; + return input; +} + +/** @brief Parse one character or entity and append it to a @ref dynstr */ +static const char *parse_one(const char *input, struct dynstr *d) { + if(*input == '&') { + uint32_t c; + input = parse_entity(input + 1, &c); + if(one_ucs42utf8(c, d)) + dynstr_append(d, '?'); /* U+FFFD might be a better choice */ + } else + dynstr_append(d, *input++); + return input; +} + +/** @brief Too-stupid-to-live HTML parser + * @param callbacks Parser callbacks + * @param input HTML document + * @param u User data pointer + * @return 0 on success, -1 on error + */ +int html_parse(const struct html_parser_callbacks *callbacks, + const char *input, + void *u) { + struct dynstr text[1]; + + dynstr_init(text); + while(*input) { + if(*input == '<') { + struct dynstr tag[1]; + hash *attrs; + + /* flush collected text */ + if(text->nvec) { + dynstr_terminate(text); + callbacks->text(text->vec, u); + text->nvec = 0; + } + dynstr_init(tag); + input = skipwhite(input + 1); + /* see if it's an open or close tag */ + if(*input == '/') { + input = skipwhite(input + 1); + attrs = 0; + } else + attrs = hash_new(sizeof(char *)); + /* gather tag */ + while(isalnum((unsigned char)*input)) + dynstr_append(tag, tolower((unsigned char)*input++)); + dynstr_terminate(tag); + input = skipwhite(input); + if(attrs) { + /* gather attributes */ + while(*input && *input != '>') { + struct dynstr name[1], value[1]; + + dynstr_init(name); + dynstr_init(value); + /* attribute name */ + while(isalnum((unsigned char)*input)) + dynstr_append(name, tolower((unsigned char)*input++)); + dynstr_terminate(name); + input = skipwhite(input); + if(*input == '=') { + /* attribute value */ + input = skipwhite(input + 1); + if(*input == '"' || *input == '\'') { + /* quoted value */ + const int q = *input++; + while(*input && *input != q) + input = parse_one(input, value); + if(*input == q) + ++input; + } else { + /* unquoted value */ + while(*input && *input != '>' && !isspace((unsigned char)*input)) + input = parse_one(input, value); + } + dynstr_terminate(value); + } + /* stash the value */ + hash_add(attrs, name->vec, value->vec, HASH_INSERT_OR_REPLACE); + input = skipwhite(input); + } + } + if(*input != '>') { + error(0, "unterminated tag %s", tag->vec); + return -1; + } + ++input; + if(attrs) + callbacks->open(tag->vec, attrs, u); + else + callbacks->close(tag->vec, u); + } else + input = parse_one(input, text); + } + /* flush any trailing text */ + if(text->nvec) { + dynstr_terminate(text); + callbacks->text(text->vec, u); + text->nvec = 0; + } + return 0; +} + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +fill-column:79 +indent-tabs-mode:nil +End: +*/ diff --git a/lib/html.h b/lib/html.h new file mode 100644 index 0000000..fd81d1d --- /dev/null +++ b/lib/html.h @@ -0,0 +1,66 @@ +/* + * This file is part of DisOrder + * Copyright (C) 2007 Richard Kettlewell + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 + * USA + */ +/** @file lib/html.c + * @brief Noddy HTML parser + */ + +#ifndef HTML_H +#define HTML_H + +/** @brief HTML parser callbacks */ +struct html_parser_callbacks { + /** @brief Called for an open tag + * @param tag Name of tag, normalized to lower case + * @param attrs Hash containing attributes + * @param u User data pointer + */ + void (*open)(const char *tag, + hash *attrs, + void *u); + + /** @brief Called for a close tag + * @param tag Name of tag, normalized to lower case + * @param u User data pointer + */ + void (*close)(const char *tag, + void *u); + + /** @brief Called for text + * @param text Pointer to text + * @param u User data pointer + */ + void (*text)(const char *text, + void *u); +}; + +int html_parse(const struct html_parser_callbacks *callbacks, + const char *input, + void *u); + +#endif /* HTML_H */ + +/* +Local Variables: +c-basic-offset:2 +comment-column:40 +fill-column:79 +indent-tabs-mode:nil +End: +*/ diff --git a/scripts/htmlman b/scripts/htmlman index c579151..82a33ca 100755 --- a/scripts/htmlman +++ b/scripts/htmlman @@ -21,20 +21,41 @@ set -e +stdhead=false + +while test $# -gt 0; do + case "$1" in + -stdhead ) + stdhead=true + ;; + -* ) + echo >&2 "ERROR: unknown option $1" + exit 1 + ;; + * ) + break + esac + shift +done + title=$(basename $1) -cat < - -@include{stdhead}@ - $title - - -@include{@label{menu}@}@ -EOF +echo "" +echo " " +if $stdhead; then + echo "@include{stdhead}@" +fi +echo " $title" +echo " " +echo " " +if $stdhead; then + echo "@include{@label{menu}@}@" +fi printf "
"
 # this is kind of painful using only BREs
-nroff -man "$1" | sed 's/&/\&/g;
+nroff -man "$1" | sed \
+                      '1d;$d;
+                       s/&/\&/g;
                        s//\>/g;
                        s/@/\@/g;
@@ -43,9 +64,9 @@ nroff -man "$1" | sed 's/&/\&/g;
                        s!_\(.\)!\1!g;
                        s!_\(&[#0-9a-z][0-9a-z]*;\)!\1!g;
                        s!<\1>!!g'
-cat <
-@include{@label{menu}@end}@
- 
-
-EOF
+echo "
" +if $stdhead; then + echo "@include{@label{menu}@end}@" +fi +echo " " +echo "" -- [mdw]