diff options
Diffstat (limited to 'hardinfo2')
| -rw-r--r-- | hardinfo2/Makefile.in | 8 | ||||
| -rw-r--r-- | hardinfo2/callbacks.c | 28 | ||||
| -rw-r--r-- | hardinfo2/egg-markdown.c | 1588 | ||||
| -rw-r--r-- | hardinfo2/egg-markdown.h | 84 | ||||
| -rw-r--r-- | hardinfo2/help-viewer.c | 465 | ||||
| -rw-r--r-- | hardinfo2/help-viewer.h | 42 | ||||
| -rw-r--r-- | hardinfo2/markdown-text-view.c | 649 | ||||
| -rw-r--r-- | hardinfo2/markdown-text-view.h | 70 | ||||
| -rw-r--r-- | hardinfo2/menu.c | 2 | ||||
| -rw-r--r-- | hardinfo2/shell.c | 16 | ||||
| -rw-r--r-- | hardinfo2/shell.h | 5 | 
11 files changed, 2938 insertions, 19 deletions
| diff --git a/hardinfo2/Makefile.in b/hardinfo2/Makefile.in index 93cca0ca..abc7cda7 100644 --- a/hardinfo2/Makefile.in +++ b/hardinfo2/Makefile.in @@ -9,7 +9,8 @@ CCSLOW = gcc -O0 -g  OBJECTS = hardinfo.o shell.o util.o iconcache.o loadgraph.o  \  		menu.o stock.o callbacks.o expr.o report.o binreloc.o \  		vendor.o socket.o syncmanager.o remote.o xmlrpc-server.o \ -		xmlrpc-client.o ssh-conn.o +		xmlrpc-client.o ssh-conn.o egg-markdown.o markdown-text-view.o \ +		help-viewer.o  BENCHMARK_OBJECTS = fbench.o sha1.o blowfish.o md5.o nqueens.o fftbench.o  MODULES = computer.so devices.so benchmark.so network.so @@ -71,12 +72,17 @@ install:	all  	install -d ${DESTDIR}/usr/share/applications  	install -d ${DESTDIR}${LIBDIR}/hardinfo/modules  	install -d ${DESTDIR}/usr/share/hardinfo/pixmaps +	install -d ${DESTDIR}/usr/share/hardinfo/doc  	install -m 644 hardinfo.desktop ${DESTDIR}/usr/share/applications  	install -m 755 hardinfo ${DESTDIR}/usr/bin/hardinfo  	install -m 755 ${MODULES} ${DESTDIR}${LIBDIR}/hardinfo/modules  	install -m 644 pixmaps/* ${DESTDIR}/usr/share/hardinfo/pixmaps +	[ -e doc/*.hlp ] && install -m 644 doc/*.hlp \ +			${DESTDIR}/usr/share/hardinfo/doc || true +	[ -e doc/*.png ] && install -m 644 doc/*.png \ +			${DESTDIR}/usr/share/hardinfo/doc || true  	install -m 644 benchmark.conf ${DESTDIR}/usr/share/hardinfo  	install -m 644 benchmark.data ${DESTDIR}/usr/share/hardinfo diff --git a/hardinfo2/callbacks.c b/hardinfo2/callbacks.c index f1f6e335..1d27b5f7 100644 --- a/hardinfo2/callbacks.c +++ b/hardinfo2/callbacks.c @@ -19,15 +19,16 @@  #include <stdlib.h>  #include <gtk/gtk.h> -#include <hardinfo.h> -#include <callbacks.h> -#include <iconcache.h> +#include "hardinfo.h" +#include "callbacks.h" +#include "iconcache.h" -#include <shell.h> -#include <report.h> -#include <syncmanager.h> +#include "shell.h" +#include "report.h" +#include "syncmanager.h" +#include "help-viewer.h" -#include <config.h> +#include "config.h"  void cb_sync_manager()  { @@ -135,7 +136,18 @@ void cb_open_web_page()  void cb_open_online_docs()  { -    open_url("http://wiki.hardinfo.org/Documentation"); +    Shell *shell; +     +    shell = shell_get_main_shell(); +    if (shell->help_viewer) { +        help_viewer_open_page(shell->help_viewer, "index.hlp"); +    } else { +        gchar *help_dir; +         +        help_dir = g_build_filename(params.path_data, "doc", NULL); +        shell->help_viewer = help_viewer_new(help_dir, "index.hlp"); +        g_free(help_dir); +    }  }  void cb_report_bug() diff --git a/hardinfo2/egg-markdown.c b/hardinfo2/egg-markdown.c new file mode 100644 index 00000000..1d39b366 --- /dev/null +++ b/hardinfo2/egg-markdown.c @@ -0,0 +1,1588 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Richard Hughes <richard@hughsie.com> + * Copyright (C) 2009 Leandro Pereira <leandro@hardinfo.org> + * + * Licensed under the GNU General Public License Version 2 + * + * 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 "config.h" + +#define _GNU_SOURCE +#include <stdio.h> +#include <string.h> +#include <glib.h> + +#include "egg-markdown.h" + +/******************************************************************************* + * + * This is a simple Markdown parser. + * It can output to Pango, HTML or plain text. The following limitations are + * already known, and properly deliberate: + * + * - No code section support + * - No ordered list support + * - No blockquote section support + * - No image support + * - No links or email support + * - No backslash escapes support + * - No HTML escaping support + * - Auto-escapes certain word patterns, like http:// + * + * It does support the rest of the standard pretty well, although it's not + * been run against any conformance tests. The parsing is single pass, with + * a simple enumerated intepretor mode and a single line back-memory. + * + ******************************************************************************/ + +static void     egg_markdown_finalize	(GObject		*object); + +#define EGG_MARKDOWN_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), EGG_TYPE_MARKDOWN, EggMarkdownPrivate)) + +typedef gchar *(EggMarkdownLinkBuilder)(gchar *title, gchar *uri, gint link_id); +typedef gchar *(EggMarkdownImageBuilder)(gchar *alt_text, gchar *path, gint link_id); + +typedef enum { +	EGG_MARKDOWN_MODE_BLANK, +	EGG_MARKDOWN_MODE_RULE, +	EGG_MARKDOWN_MODE_BULLETT, +	EGG_MARKDOWN_MODE_PARA, +	EGG_MARKDOWN_MODE_H1, +	EGG_MARKDOWN_MODE_H2, +	EGG_MARKDOWN_MODE_UNKNOWN +} EggMarkdownMode; + +typedef struct { +	const gchar *em_start; +	const gchar *em_end; +	const gchar *strong_start; +	const gchar *strong_end; +	const gchar *code_start; +	const gchar *code_end; +	const gchar *h1_start; +	const gchar *h1_end; +	const gchar *h2_start; +	const gchar *h2_end; +	const gchar *bullett_start; +	const gchar *bullett_end; +	const gchar *rule; +	 +	EggMarkdownLinkBuilder *link_builder; +	EggMarkdownImageBuilder *image_builder; +} EggMarkdownTags; + +struct EggMarkdownPrivate +{ +	EggMarkdownMode		 mode; +	EggMarkdownTags		 tags; +	EggMarkdownOutput	 output; +	gint			 max_lines; +	guint			 line_count; +	gboolean		 smart_quoting; +	gboolean		 escape; +	gboolean		 autocode; +	GString			*pending; +	GString			*processed; +	GArray			*link_table; +}; + +G_DEFINE_TYPE (EggMarkdown, egg_markdown, G_TYPE_OBJECT) + +/** + * egg_markdown_to_text_line_is_rule: + * + * Horizontal rules are created by placing three or more hyphens, asterisks, + * or underscores on a line by themselves. + * You may use spaces between the hyphens or asterisks. + **/ +static gboolean +egg_markdown_to_text_line_is_rule (const gchar *line) +{ +	guint i; +	guint len; +	guint count = 0; +	gchar *copy = NULL; +	gboolean ret = FALSE; + +	len = strnlen (line, EGG_MARKDOWN_MAX_LINE_LENGTH); +	if (len == 0) +		goto out; + +	/* replace non-rule chars with ~ */ +	copy = g_strdup (line); +	g_strcanon (copy, "-*_ ", '~'); +	for (i=0; i<len; i++) { +		if (copy[i] == '~') +			goto out; +		if (copy[i] != ' ') +			count++; +	} + +	/* if we matched, return true */ +	if (count >= 3) +		ret = TRUE; +out: +	g_free (copy); +	return ret; +} + +/** + * egg_markdown_to_text_line_is_bullett: + **/ +static gboolean +egg_markdown_to_text_line_is_bullett (const gchar *line) +{ +	return (g_str_has_prefix (line, "- ") || +		g_str_has_prefix (line, "* ") || +		g_str_has_prefix (line, "+ ") || +		g_str_has_prefix (line, " - ") || +		g_str_has_prefix (line, " * ") || +		g_str_has_prefix (line, " + ")); +} + +/** + * egg_markdown_to_text_line_is_header1: + **/ +static gboolean +egg_markdown_to_text_line_is_header1 (const gchar *line) +{ +	return g_str_has_prefix (line, "# "); +} + +/** + * egg_markdown_to_text_line_is_header2: + **/ +static gboolean +egg_markdown_to_text_line_is_header2 (const gchar *line) +{ +	return g_str_has_prefix (line, "## "); +} + +/** + * egg_markdown_to_text_line_is_header1_type2: + **/ +static gboolean +egg_markdown_to_text_line_is_header1_type2 (const gchar *line) +{ +	return g_str_has_prefix (line, "==="); +} + +/** + * egg_markdown_to_text_line_is_header2_type2: + **/ +static gboolean +egg_markdown_to_text_line_is_header2_type2 (const gchar *line) +{ +	return g_str_has_prefix (line, "---"); +} + +#if 0 +/** + * egg_markdown_to_text_line_is_code: + **/ +static gboolean +egg_markdown_to_text_line_is_code (const gchar *line) +{ +	return (g_str_has_prefix (line, "    ") || g_str_has_prefix (line, "\t")); +} + +/** + * egg_markdown_to_text_line_is_blockquote: + **/ +static gboolean +egg_markdown_to_text_line_is_blockquote (const gchar *line) +{ +	return (g_str_has_prefix (line, "> ")); +} +#endif + +/** + * egg_markdown_to_text_line_is_blank: + **/ +static gboolean +egg_markdown_to_text_line_is_blank (const gchar *line) +{ +	guint i; +	guint len; +	gboolean ret = FALSE; + +	len = strnlen (line, EGG_MARKDOWN_MAX_LINE_LENGTH); + +	/* a line with no characters is blank by definition */ +	if (len == 0) { +		ret = TRUE; +		goto out; +	} + +	/* find if there are only space chars */ +	for (i=0; i<len; i++) { +		if (line[i] != ' ' && line[i] != '\t') +			goto out; +	} + +	/* if we matched, return true */ +	ret = TRUE; +out: +	return ret; +} + +/** + * egg_markdown_replace: + **/ +static gchar * +egg_markdown_replace (const gchar *haystack, const gchar *needle, const gchar *replace) +{ +	gchar *new; +	gchar **split; + +	split = g_strsplit (haystack, needle, -1); +	new = g_strjoinv (replace, split); +	g_strfreev (split); + +	return new; +} + +/** + * egg_markdown_strstr_spaces: + **/ +static gchar * +egg_markdown_strstr_spaces (const gchar *haystack, const gchar *needle) +{ +	gchar *found; +	const gchar *haystack_new = haystack; + +retry: +	/* don't find if surrounded by spaces */ +	found = strstr (haystack_new, needle); +	if (found == NULL) +		return NULL; + +	/* start of the string, always valid */ +	if (found == haystack) +		return found; + +	/* end of the string, always valid */ +	if (*(found-1) == ' ' && *(found+1) == ' ') { +		haystack_new = found+1; +		goto retry; +	} +	return found; +} + + +/** + * egg_markdown_to_text_line_formatter: + **/ +static gchar * +egg_markdown_to_text_line_formatter (const gchar *line, const gchar *formatter, const gchar *left, const gchar *right) +{ +	guint len; +	gchar *str1; +	gchar *str2; +	gchar *start = NULL; +	gchar *middle = NULL; +	gchar *end = NULL; +	gchar *copy = NULL; +	gchar *data = NULL; +	gchar *temp; + +	/* needed to know for shifts */ +	len = strnlen (formatter, EGG_MARKDOWN_MAX_LINE_LENGTH); +	if (len == 0) +		goto out; + +	/* find sections */ +	copy = g_strdup (line); +	str1 = egg_markdown_strstr_spaces (copy, formatter); +	if (str1 != NULL) { +		*str1 = '\0'; +		str2 = egg_markdown_strstr_spaces (str1+len, formatter); +		if (str2 != NULL) { +			*str2 = '\0'; +			middle = str1 + len; +			start = copy; +			end = str2 + len; +		} +	} +	 +	/* if we found, replace and keep looking for the same string */ +	if (start != NULL && middle != NULL && end != NULL) { +                temp = g_strdup_printf ("%s%s%s%s%s", start, left, middle, right, end); +		/* recursive */ +		data = egg_markdown_to_text_line_formatter (temp, formatter, left, right); +		g_free (temp); +	} else { +		/* not found, keep return as-is */ +		data = g_strdup (line); +	} +out: +	g_free (copy); +	return data; +} + +static gchar * +egg_markdown_to_text_line_formatter_image (EggMarkdown *self, const gchar *line) +{ +	const guint len = 2;	/* needed to know for shifts */ +	gchar *str1; +	gchar *str2; +	gchar *start = NULL; +	gchar *path = NULL; +	gchar *alt_text = NULL; +	gchar *end = NULL; +	gchar *copy = NULL; +	gchar *data = NULL; +	gchar *temp; + +	/* find sections */ +	copy = g_strdup (line); +	str1 = egg_markdown_strstr_spaces (copy, "!["); +	if (str1 != NULL) { +		*str1 = '\0'; +		str2 = egg_markdown_strstr_spaces (str1+len, "]"); +		if (str2 != NULL) { +			*str2 = '\0'; +			start = copy; +			alt_text = str1 + len; +			 +                        str2 = strstr (str2 + 1, "("); +			if (str2 != NULL) { +                           *str2 = '\0'; +                            +                           str1 = strstr (str2 + 1, ")"); +                           if (str1 != NULL) { +                             *str1 = '\0'; +                             path = str2 + 1; +                             end = str1 + 1; +                           } +			} +		} +	} +	 +	/* if we found, replace and keep looking for the same string */ +	if (start && (path && *path) && alt_text && end) { +	        gchar *formatted_img; +	        gchar *path_copy = g_strdup(path); +	         +	        g_array_append_val(self->priv->link_table, path_copy); +	         +	        formatted_img = self->priv->tags.image_builder(alt_text, +                                                               path, +                                                               self->priv->link_table->len - 1); +	         +                data = g_strdup_printf ("%s%s%s", +                                        start, formatted_img, end); +                 +                g_free(formatted_img); +	} else { +		/* not found, keep return as-is */ +		data = g_strdup (line); +	} +out: +	g_free (copy); +	return data; +} + + +/** + * egg_markdown_to_text_line_formatter_link: + **/ +static gchar * +egg_markdown_to_text_line_formatter_link (EggMarkdown *self, const gchar *line) +{ +	const guint len = 1;	/* needed to know for shifts */ +	gchar *str1; +	gchar *str2; +	gchar *start = NULL; +	gchar *link = NULL; +	gchar *link_title = NULL; +	gchar *end = NULL; +	gchar *copy = NULL; +	gchar *data = NULL; +	gchar *temp; + +	/* find sections */ +	copy = g_strdup (line); +	str1 = egg_markdown_strstr_spaces (copy, "["); +	if (str1 != NULL) { +		*str1 = '\0'; +		str2 = egg_markdown_strstr_spaces (str1+len, "]"); +		if (str2 != NULL) { +			*str2 = '\0'; +			start = copy; +			link = str1 + len; +			end = str2 + len; +			 +                        str2 = strstr (link, " "); +			if (str2 != NULL) { +                           *str2 = '\0'; +                           link_title = str2 + len; +			} +		} +	} +	 +	/* if we found, replace and keep looking for the same string */ +	if (start && (link && *link) && link_title && end) { +	        gchar *formatted_link; +	        gchar *link_copy = g_strdup(link); +	         +	        g_array_append_val(self->priv->link_table, link_copy); +	         +	        formatted_link = self->priv->tags.link_builder(link_title, +	                                                       link, +	                                                       self->priv->link_table->len - 1); +                      +                data = g_strdup_printf ("%s%s%s", +                                        start, formatted_link, end); +                 +                g_free(formatted_link); +	} else { +		/* not found, keep return as-is */ +		data = g_strdup (line); +	} +out: +	g_free (copy); +	return data; +} + +void +egg_markdown_clear(EggMarkdown *self) +{ +        int i; +         +        for (i = 0; i < self->priv->link_table->len; i++) { +             g_free(g_array_index(self->priv->link_table, gchar *, i)); +        } + +        g_array_free(self->priv->link_table, TRUE); +	self->priv->link_table = g_array_new(FALSE, FALSE, sizeof(gchar *)); +} + +gchar * +egg_markdown_get_link_uri(EggMarkdown *self, const gint link_id) +{ +        g_return_val_if_fail(link_id < self->priv->link_table->len, NULL); + +        return g_strdup(g_array_index(self->priv->link_table, gchar *, link_id)); +} + +/** + * egg_markdown_to_text_line_format_sections: + **/ +static gchar * +egg_markdown_to_text_line_format_sections (EggMarkdown *self, const gchar *line) +{ +	gchar *data = g_strdup (line); +	gchar *temp; + +	/* smart quoting */ +	if (self->priv->smart_quoting) { +	        if (self->priv->escape) { +                     temp = data; +                     data = egg_markdown_to_text_line_formatter (temp, """, "“", "”"); +                     g_free (temp); + +                     temp = data; +                     data = egg_markdown_to_text_line_formatter (temp, "'", "‘", "’"); +                     g_free (temp); +                } else { +                     temp = data; +                     data = egg_markdown_to_text_line_formatter (temp, "\"", "“", "”"); +                     g_free (temp); + +                     temp = data; +                     data = egg_markdown_to_text_line_formatter (temp, "'", "‘", "’"); +                     g_free (temp); +                } +	} + +	/* image */ +	temp = data; +	data = egg_markdown_to_text_line_formatter_image (self, temp); +	g_free(temp); + +	/* link */ +	temp = data; +	data = egg_markdown_to_text_line_formatter_link (self, temp); +	g_free(temp); + +	/* bold1 */ +	temp = data; +	data = egg_markdown_to_text_line_formatter (temp, "**", self->priv->tags.strong_start, self->priv->tags.strong_end); +	g_free (temp); + +	/* bold2 */ +	temp = data; +	data = egg_markdown_to_text_line_formatter (temp, "__", self->priv->tags.strong_start, self->priv->tags.strong_end); +	g_free (temp); + +	/* italic1 */ +	temp = data; +	data = egg_markdown_to_text_line_formatter (temp, "*", self->priv->tags.em_start, self->priv->tags.em_end); +	g_free (temp); + +	/* italic2 */ +	temp = data; +	data = egg_markdown_to_text_line_formatter (temp, "_", self->priv->tags.em_start, self->priv->tags.em_end); +	g_free (temp); +	 +	/* em-dash */ +	temp = data; +	data = egg_markdown_replace (temp, " -- ", " — "); +	g_free (temp); + +	return data; +} + +/** + * egg_markdown_to_text_line_format: + **/ +static gchar * +egg_markdown_to_text_line_format (EggMarkdown *self, const gchar *line) +{ +	guint i; +	gchar *text; +	gboolean mode = FALSE; +	gchar **codes; +	GString *string; + +	/* optimise the trivial case where we don't have any code tags */ +	text = strstr (line, "`"); +	if (text == NULL) { +		text = egg_markdown_to_text_line_format_sections (self, line); +		goto out; +	} + +	/* we want to parse the code sections without formatting */ +	codes = g_strsplit (line, "`", -1); +	string = g_string_new (""); +	for (i=0; codes[i] != NULL; i++) { +		if (!mode) { +			text = egg_markdown_to_text_line_format_sections (self, codes[i]); +			g_string_append (string, text); +			g_free (text); +			mode = TRUE; +		} else { +			/* just append without formatting */ +			g_string_append (string, self->priv->tags.code_start); +			g_string_append (string, codes[i]); +			g_string_append (string, self->priv->tags.code_end); +			mode = FALSE; +		} +	} +	text = g_string_free (string, FALSE); +out: +	return text; +} + +/** + * egg_markdown_add_pending: + **/ +static gboolean +egg_markdown_add_pending (EggMarkdown *self, const gchar *line) +{ +	gchar *copy; + +	/* would put us over the limit */ +	if (self->priv->line_count >= self->priv->max_lines) +		return FALSE; + +	copy = g_strdup (line); + +	/* strip leading and trailing spaces */ +	g_strstrip (copy); + +	/* append */ +	g_string_append_printf (self->priv->pending, "%s ", copy); + +	g_free (copy); +	return TRUE; +} + +/** + * egg_markdown_add_pending_header: + **/ +static gboolean +egg_markdown_add_pending_header (EggMarkdown *self, const gchar *line) +{ +	gchar *copy; +	gboolean ret; + +	/* strip trailing # */ +	copy = g_strdup (line); +	g_strdelimit (copy, "#", ' '); +	ret = egg_markdown_add_pending (self, copy); +	g_free (copy); +	return ret; +} + +/** + * egg_markdown_count_chars_in_word: + **/ +static guint +egg_markdown_count_chars_in_word (const gchar *text, gchar find) +{ +	guint i; +	guint len; +	guint count = 0; + +	/* get length */ +	len = strnlen (text, EGG_MARKDOWN_MAX_LINE_LENGTH); +	if (len == 0) +		goto out; + +	/* find matching chars */ +	for (i=0; i<len; i++) { +		if (text[i] == find) +			count++; +	} +out: +	return count; +} + +/** + * egg_markdown_word_is_code: + **/ +static gboolean +egg_markdown_word_is_code (const gchar *text) +{ +	/* already code */ +	if (g_str_has_prefix (text, "`")) +		return FALSE; +	if (g_str_has_suffix (text, "`")) +		return FALSE; + +	/* paths */ +	if (g_str_has_prefix (text, "/")) +		return TRUE; + +	/* bugzillas */ +	if (g_str_has_prefix (text, "#")) +		return TRUE; + +	/* uri's */ +	if (g_str_has_prefix (text, "http://")) +		return TRUE; +	if (g_str_has_prefix (text, "https://")) +		return TRUE; +	if (g_str_has_prefix (text, "ftp://")) +		return TRUE; + +	/* patch files */ +	if (g_strrstr (text, ".patch") != NULL) +		return TRUE; +	if (g_strrstr (text, ".diff") != NULL) +		return TRUE; + +	/* function names */ +	if (g_strrstr (text, "()") != NULL) +		return TRUE; + +	/* email addresses */ +	if (g_strrstr (text, "@") != NULL) +		return TRUE; + +	/* compiler defines */ +	if (text[0] != '_' && +	    egg_markdown_count_chars_in_word (text, '_') > 1) +		return TRUE; + +	/* nothing special */ +	return FALSE; +} + +/** + * egg_markdown_word_auto_format_code: + **/ +static gchar * +egg_markdown_word_auto_format_code (const gchar *text) +{ +	guint i; +	gchar *temp; +	gchar **words; +	gboolean ret = FALSE; + +	/* split sentence up with space */ +	words = g_strsplit (text, " ", -1); + +	/* search each word */ +	for (i=0; words[i] != NULL; i++) { +		if (egg_markdown_word_is_code (words[i])) { +			temp = g_strdup_printf ("`%s`", words[i]); +			g_free (words[i]); +			words[i] = temp; +			ret = TRUE; +		} +	} + +	/* no replacements, so just return a copy */ +	if (!ret) { +		temp = g_strdup (text); +		goto out; +	} + +	/* join the array back into a string */ +	temp = g_strjoinv (" ", words); +out: +	g_strfreev (words); +	return temp; +} + +/** + * egg_markdown_flush_pending: + **/ +static void +egg_markdown_flush_pending (EggMarkdown *self) +{ +	gchar *copy; +	gchar *temp; + +	/* no data yet */ +	if (self->priv->mode == EGG_MARKDOWN_MODE_UNKNOWN) +		return; + +	/* remove trailing spaces */ +	while (g_str_has_suffix (self->priv->pending->str, " ")) +		g_string_set_size (self->priv->pending, self->priv->pending->len - 1); + +	/* pango requires escaping */ +	copy = g_strdup (self->priv->pending->str); +	if (!self->priv->escape && self->priv->output == EGG_MARKDOWN_OUTPUT_PANGO) { +		g_strdelimit (copy, "<", '('); +		g_strdelimit (copy, ">", ')'); +	} + +	/* check words for code */ +	if (self->priv->autocode && +	    (self->priv->mode == EGG_MARKDOWN_MODE_PARA || +	     self->priv->mode == EGG_MARKDOWN_MODE_BULLETT)) { +		temp = egg_markdown_word_auto_format_code (copy); +		g_free (copy); +		copy = temp; +	} + +	/* escape */ +	if (self->priv->escape) { +		temp = g_markup_escape_text (copy, -1); +		g_free (copy); +		copy = temp; +	} + +	/* do formatting */ +	temp = egg_markdown_to_text_line_format (self, copy); +	if (self->priv->mode == EGG_MARKDOWN_MODE_BULLETT) { +		g_string_append_printf (self->priv->processed, "%s%s%s\n", self->priv->tags.bullett_start, temp, self->priv->tags.bullett_end); +		self->priv->line_count++; +	} else if (self->priv->mode == EGG_MARKDOWN_MODE_H1) { +		g_string_append_printf (self->priv->processed, "%s%s%s\n", self->priv->tags.h1_start, temp, self->priv->tags.h1_end); +	} else if (self->priv->mode == EGG_MARKDOWN_MODE_H2) { +		g_string_append_printf (self->priv->processed, "%s%s%s\n", self->priv->tags.h2_start, temp, self->priv->tags.h2_end); +	} else if (self->priv->mode == EGG_MARKDOWN_MODE_PARA || +		   self->priv->mode == EGG_MARKDOWN_MODE_RULE) { +		g_string_append_printf (self->priv->processed, "%s\n", temp); +		self->priv->line_count++; +	} + +	DEBUG ("adding '%s'", temp); + +	/* clear */ +	g_string_truncate (self->priv->pending, 0); +	g_free (copy); +	g_free (temp); +} + +/** + * egg_markdown_to_text_line_process: + **/ +static gboolean +egg_markdown_to_text_line_process (EggMarkdown *self, const gchar *line) +{ +	gboolean ret; + +	/* blank */ +	ret = egg_markdown_to_text_line_is_blank (line); +	if (ret) { +		DEBUG ("blank: '%s'", line); +		egg_markdown_flush_pending (self); +		/* a new line after a list is the end of list, not a gap */ +		if (self->priv->mode != EGG_MARKDOWN_MODE_BULLETT) +			ret = egg_markdown_add_pending (self, "\n"); +		self->priv->mode = EGG_MARKDOWN_MODE_BLANK; +		goto out; +	} + +	/* header1_type2 */ +	ret = egg_markdown_to_text_line_is_header1_type2 (line); +	if (ret) { +		DEBUG ("header1_type2: '%s'", line); +		if (self->priv->mode == EGG_MARKDOWN_MODE_PARA) +			self->priv->mode = EGG_MARKDOWN_MODE_H1; +		goto out; +	} + +	/* header2_type2 */ +	ret = egg_markdown_to_text_line_is_header2_type2 (line); +	if (ret) { +		DEBUG ("header2_type2: '%s'", line); +		if (self->priv->mode == EGG_MARKDOWN_MODE_PARA) +			self->priv->mode = EGG_MARKDOWN_MODE_H2; +		goto out; +	} + +	/* rule */ +	ret = egg_markdown_to_text_line_is_rule (line); +	if (ret) { +		DEBUG ("rule: '%s'", line); +		egg_markdown_flush_pending (self); +		self->priv->mode = EGG_MARKDOWN_MODE_RULE; +		ret = egg_markdown_add_pending (self, self->priv->tags.rule); +		goto out; +	} + +	/* bullett */ +	ret = egg_markdown_to_text_line_is_bullett (line); +	if (ret) { +		DEBUG ("bullett: '%s'", line); +		egg_markdown_flush_pending (self); +		self->priv->mode = EGG_MARKDOWN_MODE_BULLETT; +		ret = egg_markdown_add_pending (self, &line[2]); +		goto out; +	} + +	/* header1 */ +	ret = egg_markdown_to_text_line_is_header1 (line); +	if (ret) { +		DEBUG ("header1: '%s'", line); +		egg_markdown_flush_pending (self); +		self->priv->mode = EGG_MARKDOWN_MODE_H1; +		ret = egg_markdown_add_pending_header (self, &line[2]); +		goto out; +	} + +	/* header2 */ +	ret = egg_markdown_to_text_line_is_header2 (line); +	if (ret) { +		DEBUG ("header2: '%s'", line); +		egg_markdown_flush_pending (self); +		self->priv->mode = EGG_MARKDOWN_MODE_H2; +		ret = egg_markdown_add_pending_header (self, &line[3]); +		goto out; +	} + +	/* paragraph */ +	if (self->priv->mode == EGG_MARKDOWN_MODE_BLANK || self->priv->mode == EGG_MARKDOWN_MODE_UNKNOWN) { +		egg_markdown_flush_pending (self); +		self->priv->mode = EGG_MARKDOWN_MODE_PARA; +	} + +	/* add to pending */ +	DEBUG ("continue: '%s'", line); +	ret = egg_markdown_add_pending (self, line); +out: +	/* if we failed to add, we don't know the mode */ +	if (!ret) +		self->priv->mode = EGG_MARKDOWN_MODE_UNKNOWN; +	return ret; +} + +/** + * egg_markdown_linkbuilder_pango: + **/ +static gchar * +egg_markdown_linkbuilder_pango (gchar *title, gchar *uri, gint link_id) +{ +       /* FIXME: This is a nasty hack, since extending Pango markup to allow new tags +        *        is too complicated. We use the language code as a link index +        *        since it won't allow anything besides letters or numbers. +        *        To obtain the link URI, use egg_markdown_get_link_uri(). */ +       return g_strdup_printf("<span lang=\"%d\" foreground=\"blue\"><u>%s</u></span>", +                              link_id, title); +} + +/** + * egg_markdown_linkbuilder_html + **/ +static gchar * +egg_markdown_linkbuilder_html (gchar *title, gchar *uri, gint link_id) +{ +        return g_strdup_printf("<a href=\"%s\">%s</a>", uri, title); +} + +/** + * egg_markdown_linkbuilder_text + **/ +static gchar * +egg_markdown_linkbuilder_text (gchar *title, gchar *uri, gint link_id) +{ +        return g_strdup_printf("%s (%s)", title, uri); +} + +/** + * egg_markdown_imagebuilder_pango: + **/ +static gchar * +egg_markdown_imagebuilder_pango (gchar *alt_text, gchar *uri, gint link_id) +{ +       /* FIXME See egg_markdown_linkbuilder_pango() */ +       return g_strdup_printf("<span lang=\"%d\" underline=\"double\">%s</span>", +                              link_id, alt_text); +} + +/** + * egg_markdown_imagebuilder_html + **/ +static gchar * +egg_markdown_imagebuilder_html (gchar *alt_text, gchar *uri, gint link_id) +{ +        return g_strdup_printf("<img src=\"%s\" alt=\"%s\">", uri, alt_text); +} + +/** + * egg_markdown_imagebuilder_text + **/ +static gchar * +egg_markdown_imagebuilder_text (gchar *alt_text, gchar *uri, gint link_id) +{ +        return g_strdup(alt_text); +} + +/** + * egg_markdown_set_output: + **/ +gboolean +egg_markdown_set_output (EggMarkdown *self, EggMarkdownOutput output) +{ +	gboolean ret = TRUE; +	g_return_val_if_fail (EGG_IS_MARKDOWN (self), FALSE); + +	/* PangoMarkup */ +	if (output == EGG_MARKDOWN_OUTPUT_PANGO) { +		self->priv->tags.em_start = "<i>"; +		self->priv->tags.em_end = "</i>"; +		self->priv->tags.strong_start = "<b>"; +		self->priv->tags.strong_end = "</b>"; +		self->priv->tags.code_start = "<tt><span bgcolor=\"#eee\">"; +		self->priv->tags.code_end = "</span></tt>"; +		self->priv->tags.h1_start = "<span color=\"#444\" size=\"xx-large\"><b>"; +		self->priv->tags.h1_end = "</b></span>"; +		self->priv->tags.h2_start = "<big><b>"; +		self->priv->tags.h2_end = "</b></big>"; +		self->priv->tags.bullett_start = "  • "; +		self->priv->tags.bullett_end = ""; +		self->priv->tags.rule = "⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯\n"; +		self->priv->tags.link_builder = egg_markdown_linkbuilder_pango; +		self->priv->tags.image_builder = egg_markdown_imagebuilder_pango; + +	/* XHTML */ +	} else if (output == EGG_MARKDOWN_OUTPUT_HTML) { +		self->priv->tags.em_start = "<em>"; +		self->priv->tags.em_end = "<em>"; +		self->priv->tags.strong_start = "<strong>"; +		self->priv->tags.strong_end = "</strong>"; +		self->priv->tags.code_start = "<code>"; +		self->priv->tags.code_end = "</code>"; +		self->priv->tags.h1_start = "<h1>"; +		self->priv->tags.h1_end = "</h1>"; +		self->priv->tags.h2_start = "<h2>"; +		self->priv->tags.h2_end = "</h2>"; +		self->priv->tags.bullett_start = "<li>"; +		self->priv->tags.bullett_end = "</li>"; +		self->priv->tags.rule = "<hr>"; +		self->priv->tags.link_builder = egg_markdown_linkbuilder_html; +		self->priv->tags.image_builder = egg_markdown_imagebuilder_html; + +	/* plain text */ +	} else if (output == EGG_MARKDOWN_OUTPUT_TEXT) { +		self->priv->tags.em_start = ""; +		self->priv->tags.em_end = ""; +		self->priv->tags.strong_start = ""; +		self->priv->tags.strong_end = ""; +		self->priv->tags.code_start = ""; +		self->priv->tags.code_end = ""; +		self->priv->tags.h1_start = "["; +		self->priv->tags.h1_end = "]"; +		self->priv->tags.h2_start = "-"; +		self->priv->tags.h2_end = "-"; +		self->priv->tags.bullett_start = "* "; +		self->priv->tags.bullett_end = ""; +		self->priv->tags.rule = " ----- \n"; +		self->priv->tags.link_builder = egg_markdown_linkbuilder_text; +		self->priv->tags.image_builder = egg_markdown_imagebuilder_text; + +	/* unknown */ +	} else { +		g_warning ("unknown output enum"); +		ret = FALSE; +	} + +	/* save if valid */ +	if (ret) +		self->priv->output = output; +	return ret; +} + +/** + * egg_markdown_set_max_lines: + **/ +gboolean +egg_markdown_set_max_lines (EggMarkdown *self, gint max_lines) +{ +	g_return_val_if_fail (EGG_IS_MARKDOWN (self), FALSE); +	self->priv->max_lines = max_lines; +	return TRUE; +} + +/** + * egg_markdown_set_smart_quoting: + **/ +gboolean +egg_markdown_set_smart_quoting (EggMarkdown *self, gboolean smart_quoting) +{ +	g_return_val_if_fail (EGG_IS_MARKDOWN (self), FALSE); +	self->priv->smart_quoting = smart_quoting; +	return TRUE; +} + +/** + * egg_markdown_set_escape: + **/ +gboolean +egg_markdown_set_escape (EggMarkdown *self, gboolean escape) +{ +	g_return_val_if_fail (EGG_IS_MARKDOWN (self), FALSE); +	self->priv->escape = escape; +	return TRUE; +} + +/** + * egg_markdown_set_autocode: + **/ +gboolean +egg_markdown_set_autocode (EggMarkdown *self, gboolean autocode) +{ +	g_return_val_if_fail (EGG_IS_MARKDOWN (self), FALSE); +	self->priv->autocode = autocode; +	return TRUE; +} + +/** + * egg_markdown_parse: + **/ +gchar * +egg_markdown_parse (EggMarkdown *self, const gchar *markdown) +{ +	gchar **lines; +	guint i; +	guint len; +	gchar *temp; +	gboolean ret; + +	g_return_val_if_fail (EGG_IS_MARKDOWN (self), NULL); +	g_return_val_if_fail (self->priv->output != EGG_MARKDOWN_OUTPUT_UNKNOWN, NULL); + +	DEBUG ("input='%s'", markdown); + +	/* process */ +	self->priv->mode = EGG_MARKDOWN_MODE_UNKNOWN; +	self->priv->line_count = 0; +	g_string_truncate (self->priv->pending, 0); +	g_string_truncate (self->priv->processed, 0); +	lines = g_strsplit (markdown, "\n", -1); +	len = g_strv_length (lines); + +	/* process each line */ +	for (i=0; i<len; i++) { +		ret = egg_markdown_to_text_line_process (self, lines[i]); +		if (!ret) +			break; +	} +	g_strfreev (lines); +	egg_markdown_flush_pending (self); + +	/* remove trailing \n */ +	while (g_str_has_suffix (self->priv->processed->str, "\n")) +		g_string_set_size (self->priv->processed, self->priv->processed->len - 1); + +	/* get a copy */ +	temp = g_strdup (self->priv->processed->str); +	g_string_truncate (self->priv->pending, 0); +	g_string_truncate (self->priv->processed, 0); + +	DEBUG ("output='%s'", temp); + +	return temp; +} + +/** + * egg_markdown_class_init: + * @klass: The EggMarkdownClass + **/ +static void +egg_markdown_class_init (EggMarkdownClass *klass) +{ +	GObjectClass *object_class = G_OBJECT_CLASS (klass); +	object_class->finalize = egg_markdown_finalize; +	g_type_class_add_private (klass, sizeof (EggMarkdownPrivate)); +} + +/** + * egg_markdown_init: + **/ +static void +egg_markdown_init (EggMarkdown *self) +{ +	self->priv = EGG_MARKDOWN_GET_PRIVATE (self); + +	self->priv->mode = EGG_MARKDOWN_MODE_UNKNOWN; +	self->priv->output = EGG_MARKDOWN_OUTPUT_UNKNOWN; +	self->priv->pending = g_string_new (""); +	self->priv->processed = g_string_new (""); +	self->priv->link_table = g_array_new(FALSE, FALSE, sizeof(gchar *)); +	self->priv->max_lines = -1; +	self->priv->smart_quoting = FALSE; +	self->priv->escape = FALSE; +	self->priv->autocode = FALSE; +} + +/** + * egg_markdown_finalize: + * @object: The object to finalize + **/ +static void +egg_markdown_finalize (GObject *object) +{ +	EggMarkdown *self; +        int i; + +	g_return_if_fail (EGG_IS_MARKDOWN (object)); + +	self = EGG_MARKDOWN (object); + +	g_return_if_fail (self->priv != NULL); +	g_string_free (self->priv->pending, TRUE); +	g_string_free (self->priv->processed, TRUE); +         +        for (i = 0; i < self->priv->link_table->len; i++) { +             g_free(g_array_index(self->priv->link_table, gchar *, i)); +        } +	g_array_free (self->priv->link_table, TRUE); + +	G_OBJECT_CLASS (egg_markdown_parent_class)->finalize (object); +} + +/** + * egg_markdown_new: + * + * Return value: a new EggMarkdown object. + **/ +EggMarkdown * +egg_markdown_new (void) +{ +	EggMarkdown *self; +	self = g_object_new (EGG_TYPE_MARKDOWN, NULL); +	return EGG_MARKDOWN (self); +} + +/*************************************************************************** + ***                          MAKE CHECK TESTS                           *** + ***************************************************************************/ +#ifdef EGG_TEST +#include "egg-test.h" + +void +egg_markdown_test (EggTest *test) +{ +	EggMarkdown *self; +	gchar *text; +	gboolean ret; +	const gchar *markdown; +	const gchar *markdown_expected; + +	if (!egg_test_start (test, "EggMarkdown")) +		return; + +	/************************************************************ +	 ****************        line_is_rule          ************** +	 ************************************************************/ +	ret = egg_markdown_to_text_line_is_rule ("* * *"); +	egg_test_title_assert (test, "is rule (1)", ret); + +	/************************************************************/ +	ret = egg_markdown_to_text_line_is_rule ("***"); +	egg_test_title_assert (test, "is rule (2)", ret); + +	/************************************************************/ +	ret = egg_markdown_to_text_line_is_rule ("*****"); +	egg_test_title_assert (test, "is rule (3)", ret); + +	/************************************************************/ +	ret = egg_markdown_to_text_line_is_rule ("- - -"); +	egg_test_title_assert (test, "is rule (4)", ret); + +	/************************************************************/ +	ret = egg_markdown_to_text_line_is_rule ("---------------------------------------"); +	egg_test_title_assert (test, "is rule (5)", ret); + +	/************************************************************/ +	ret = egg_markdown_to_text_line_is_rule (""); +	egg_test_title_assert (test, "is rule (blank)", !ret); + +	/************************************************************/ +	ret = egg_markdown_to_text_line_is_rule ("richard hughes"); +	egg_test_title_assert (test, "is rule (text)", !ret); + +	/************************************************************/ +	ret = egg_markdown_to_text_line_is_rule ("- richard-hughes"); +	egg_test_title_assert (test, "is rule (bullet)", !ret); + +	/************************************************************/ +	ret = egg_markdown_to_text_line_is_blank (""); +	egg_test_title_assert (test, "is blank (blank)", ret); + +	/************************************************************/ +	ret = egg_markdown_to_text_line_is_blank (" \t "); +	egg_test_title_assert (test, "is blank (mix)", ret); + +	/************************************************************/ +	ret = egg_markdown_to_text_line_is_blank ("richard hughes"); +	egg_test_title_assert (test, "is blank (name)", !ret); + +	/************************************************************/ +	ret = egg_markdown_to_text_line_is_blank ("ccccccccc"); +	egg_test_title_assert (test, "is blank (full)", !ret); + + +	/************************************************************ +	 ****************           replace            ************** +	 ************************************************************/ +	text = egg_markdown_replace ("summary -- really -- sure!", " -- ", " – "); +	egg_test_title (test, "replace (multiple)"); +	if (g_str_equal (text, "summary – really – sure!")) +		egg_test_success (test, NULL); +	else +		egg_test_failed (test, "failed, got %s", text); +	g_free (text); + +	/************************************************************ +	 ****************          formatter           ************** +	 ************************************************************/ +	text = egg_markdown_to_text_line_formatter ("**is important** text", "**", "<b>", "</b>"); +	egg_test_title (test, "formatter (left)"); +	if (g_str_equal (text, "<b>is important</b> text")) +		egg_test_success (test, NULL); +	else +		egg_test_failed (test, "failed, got %s", text); +	g_free (text); + +	/************************************************************/ +	text = egg_markdown_to_text_line_formatter ("this is **important**", "**", "<b>", "</b>"); +	egg_test_title (test, "formatter (right)"); +	if (g_str_equal (text, "this is <b>important</b>")) +		egg_test_success (test, NULL); +	else +		egg_test_failed (test, "failed, got %s", text); +	g_free (text); + +	/************************************************************/ +	text = egg_markdown_to_text_line_formatter ("**important**", "**", "<b>", "</b>"); +	egg_test_title (test, "formatter (only)"); +	if (g_str_equal (text, "<b>important</b>")) +		egg_test_success (test, NULL); +	else +		egg_test_failed (test, "failed, got %s", text); +	g_free (text); + +	/************************************************************/ +	text = egg_markdown_to_text_line_formatter ("***important***", "**", "<b>", "</b>"); +	egg_test_title (test, "formatter (only)"); +	if (g_str_equal (text, "<b>*important</b>*")) +		egg_test_success (test, NULL); +	else +		egg_test_failed (test, "failed, got %s", text); +	g_free (text); + +	/************************************************************/ +	text = egg_markdown_to_text_line_formatter ("I guess * this is * not bold", "*", "<i>", "</i>"); +	egg_test_title (test, "formatter (with spaces)"); +	if (g_str_equal (text, "I guess * this is * not bold")) +		egg_test_success (test, NULL); +	else +		egg_test_failed (test, "failed, got %s", text); +	g_free (text); + +	/************************************************************/ +	text = egg_markdown_to_text_line_formatter ("this **is important** text in **several** places", "**", "<b>", "</b>"); +	egg_test_title (test, "formatter (middle, multiple)"); +	if (g_str_equal (text, "this <b>is important</b> text in <b>several</b> places")) +		egg_test_success (test, NULL); +	else +		egg_test_failed (test, "failed, got %s", text); +	g_free (text); + +	/************************************************************/ +	text = egg_markdown_word_auto_format_code ("this is http://www.hughsie.com/with_spaces_in_url inline link"); +	egg_test_title (test, "auto formatter (url)"); +	if (g_str_equal (text, "this is `http://www.hughsie.com/with_spaces_in_url` inline link")) +		egg_test_success (test, NULL); +	else +		egg_test_failed (test, "failed, got %s", text); +	g_free (text); + +	/************************************************************/ +	text = egg_markdown_to_text_line_formatter ("this was \"triffic\" it was", "\"", "“", "”"); +	egg_test_title (test, "formatter (quotes)"); +	if (g_str_equal (text, "this was “triffic” it was")) +		egg_test_success (test, NULL); +	else +		egg_test_failed (test, "failed, got %s", text); +	g_free (text); + +	/************************************************************/ +	text = egg_markdown_to_text_line_formatter ("This isn't a present", "'", "‘", "’"); +	egg_test_title (test, "formatter (one quote)"); +	if (g_str_equal (text, "This isn't a present")) +		egg_test_success (test, NULL); +	else +		egg_test_failed (test, "failed, got %s", text); +	g_free (text); + +	/************************************************************ +	 ****************          markdown            ************** +	 ************************************************************/ +	egg_test_title (test, "get EggMarkdown object"); +	self = egg_markdown_new (); +	egg_test_assert (test, self != NULL); + +	/************************************************************/ +	ret = egg_markdown_set_output (self, EGG_MARKDOWN_OUTPUT_PANGO); +	egg_test_title_assert (test, "set pango output", ret); + +	/************************************************************/ +	markdown = "OEMs\n" +		   "====\n" +		   " - Bullett\n"; +	markdown_expected = +		   "<big>OEMs</big>\n" +		   "• Bullett"; +	egg_test_title (test, "markdown (type2 header)"); +	text = egg_markdown_parse (self, markdown); +	if (g_str_equal (text, markdown_expected)) +		egg_test_success (test, NULL); +	else +		egg_test_failed (test, "failed, got '%s', expected '%s'", text, markdown_expected); +	g_free (text); + + +	/************************************************************/ +	markdown = "this is http://www.hughsie.com/with_spaces_in_url inline link\n"; +	markdown_expected = "this is <tt>http://www.hughsie.com/with_spaces_in_url</tt> inline link"; +	egg_test_title (test, "markdown (autocode)"); +	egg_markdown_set_autocode (self, TRUE); +	text = egg_markdown_parse (self, markdown); +	if (g_str_equal (text, markdown_expected)) +		egg_test_success (test, NULL); +	else +		egg_test_failed (test, "failed, got '%s', expected '%s'", text, markdown_expected); +	g_free (text); + +	/************************************************************/ +	markdown = "*** This software is currently in alpha state ***\n"; +	markdown_expected = "<b><i> This software is currently in alpha state </b></i>"; +	egg_test_title (test, "markdown some invalid header"); +	text = egg_markdown_parse (self, markdown); +	if (g_str_equal (text, markdown_expected)) +		egg_test_success (test, NULL); +	else +		egg_test_failed (test, "failed, got '%s', expected '%s'", text, markdown_expected); +	g_free (text); + +	/************************************************************/ +	markdown = " - This is a *very*\n" +		   "   short paragraph\n" +		   "   that is not usual.\n" +		   " - Another"; +	markdown_expected = +		   "• This is a <i>very</i> short paragraph that is not usual.\n" +		   "• Another"; +	egg_test_title (test, "markdown (complex1)"); +	text = egg_markdown_parse (self, markdown); +	if (g_str_equal (text, markdown_expected)) +		egg_test_success (test, NULL); +	else +		egg_test_failed (test, "failed, got '%s', expected '%s'", text, markdown_expected); +	g_free (text); + +	/************************************************************/ +	markdown = "*  This is a *very*\n" +		   "   short paragraph\n" +		   "   that is not usual.\n" +		   "*  This is the second\n" +		   "   bullett point.\n" +		   "*  And the third.\n" +		   " \n" +		   "* * *\n" +		   " \n" +		   "Paragraph one\n" +		   "isn't __very__ long at all.\n" +		   "\n" +		   "Paragraph two\n" +		   "isn't much better."; +	markdown_expected = +		   "• This is a <i>very</i> short paragraph that is not usual.\n" +		   "• This is the second bullett point.\n" +		   "• And the third.\n" +		   "⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯\n" +		   "Paragraph one isn't <b>very</b> long at all.\n" +		   "Paragraph two isn't much better."; +	egg_test_title (test, "markdown (complex1)"); +	text = egg_markdown_parse (self, markdown); +	if (g_str_equal (text, markdown_expected)) +		egg_test_success (test, NULL); +	else +		egg_test_failed (test, "failed, got '%s', expected '%s'", text, markdown_expected); +	g_free (text); + +	/************************************************************/ +	markdown = "This is a spec file description or\n" +		   "an **update** description in bohdi.\n" +		   "\n" +		   "* * *\n" +		   "# Big title #\n" +		   "\n" +		   "The *following* things 'were' fixed:\n" +		   "- Fix `dave`\n" +		   "* Fubar update because of \"security\"\n"; +	markdown_expected = +		   "This is a spec file description or an <b>update</b> description in bohdi.\n" +		   "⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯⎯\n" +		   "<big>Big title</big>\n" +		   "The <i>following</i> things 'were' fixed:\n" +		   "• Fix <tt>dave</tt>\n" +		   "• Fubar update because of \"security\""; +	egg_test_title (test, "markdown (complex2)"); +	text = egg_markdown_parse (self, markdown); +	if (g_str_equal (text, markdown_expected)) +		egg_test_success (test, NULL); +	else +		egg_test_failed (test, "failed, got '%s', expected '%s'", text, markdown_expected); +	g_free (text); + +	/************************************************************/ +	markdown = "* list seporated with spaces -\n" +		   "  first item\n" +		   "\n" +		   "* second item\n" +		   "\n" +		   "* third item\n"; +	markdown_expected = +		   "• list seporated with spaces - first item\n" +		   "• second item\n" +		   "• third item"; +	egg_test_title (test, "markdown (list with spaces)"); +	text = egg_markdown_parse (self, markdown); +	if (g_str_equal (text, markdown_expected)) +		egg_test_success (test, NULL); +	else +		egg_test_failed (test, "failed, got '%s', expected '%s'", text, markdown_expected); +	g_free (text); + +	/************************************************************/ +	ret = egg_markdown_set_max_lines (self, 1); +	egg_test_title_assert (test, "set max_lines", ret); + +	/************************************************************/ +	markdown = "* list seporated with spaces -\n" +		   "  first item\n" +		   "* second item\n"; +	markdown_expected = +		   "• list seporated with spaces - first item"; +	egg_test_title (test, "markdown (one line limit)"); +	text = egg_markdown_parse (self, markdown); +	if (g_str_equal (text, markdown_expected)) +		egg_test_success (test, NULL); +	else +		egg_test_failed (test, "failed, got '%s', expected '%s'", text, markdown_expected); +	g_free (text); + +	/************************************************************/ +	ret = egg_markdown_set_max_lines (self, 1); +	egg_test_title_assert (test, "set max_lines", ret); + +	/************************************************************/ +	markdown = "* list & spaces"; +	markdown_expected = +		   "• list & spaces"; +	egg_test_title (test, "markdown (escaping)"); +	text = egg_markdown_parse (self, markdown); +	if (g_str_equal (text, markdown_expected)) +		egg_test_success (test, NULL); +	else +		egg_test_failed (test, "failed, got '%s', expected '%s'", text, markdown_expected); +	g_free (text); + +	/************************************************************/ +	egg_test_title (test, "markdown (free text)"); +	text = egg_markdown_parse (self, "This isn't a present"); +	if (g_str_equal (text, "This isn't a present")) +		egg_test_success (test, NULL); +	else +		egg_test_failed (test, "failed, got '%s'", text); +	g_free (text); + +	/************************************************************/ +	egg_test_title (test, "markdown (autotext underscore)"); +	text = egg_markdown_parse (self, "This isn't CONFIG_UEVENT_HELPER_PATH present"); +	if (g_str_equal (text, "This isn't <tt>CONFIG_UEVENT_HELPER_PATH</tt> present")) +		egg_test_success (test, NULL); +	else +		egg_test_failed (test, "failed, got '%s'", text); +	g_free (text); + +	/************************************************************/ +	markdown = "*Thu Mar 12 12:00:00 2009* Dan Walsh <dwalsh@redhat.com> - 2.0.79-1\n" +		   "- Update to upstream \n" +		   " * Netlink socket handoff patch from Adam Jackson.\n" +		   " * AVC caching of compute_create results by Eric Paris.\n" +		   "\n" +		   "*Tue Mar 10 12:00:00 2009* Dan Walsh <dwalsh@redhat.com> - 2.0.78-5\n" +		   "- Add patch from ajax to accellerate X SELinux \n" +		   "- Update eparis patch\n"; +	markdown_expected = +		   "<i>Thu Mar 12 12:00:00 2009</i> Dan Walsh <tt><dwalsh@redhat.com></tt> - 2.0.79-1\n" +		   "• Update to upstream\n" +		   "• Netlink socket handoff patch from Adam Jackson.\n" +		   "• AVC caching of compute_create results by Eric Paris.\n" +		   "<i>Tue Mar 10 12:00:00 2009</i> Dan Walsh <tt><dwalsh@redhat.com></tt> - 2.0.78-5\n" +		   "• Add patch from ajax to accellerate X SELinux\n" +		   "• Update eparis patch"; +	egg_test_title (test, "markdown (end of bullett)"); +	egg_markdown_set_escape (self, TRUE); +	ret = egg_markdown_set_max_lines (self, 1024); +	text = egg_markdown_parse (self, markdown); +	if (g_str_equal (text, markdown_expected)) +		egg_test_success (test, NULL); +	else +		egg_test_failed (test, "failed, got '%s', expected '%s'", text, markdown_expected); +	g_free (text); + +	g_object_unref (self); + +	egg_test_end (test); +} +#endif + diff --git a/hardinfo2/egg-markdown.h b/hardinfo2/egg-markdown.h new file mode 100644 index 00000000..4475b9f0 --- /dev/null +++ b/hardinfo2/egg-markdown.h @@ -0,0 +1,84 @@ +/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- + * + * Copyright (C) 2008 Richard Hughes <richard@hughsie.com> + * Copyright (C) 2009 Leandro Pereira <leandro@hardinfo.org> + * + * Licensed under the GNU General Public License Version 2 + * + * 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. + */ + +#ifndef __EGG_MARKDOWN_H +#define __EGG_MARKDOWN_H + +#include <glib-object.h> + +G_BEGIN_DECLS + +#define EGG_TYPE_MARKDOWN		(egg_markdown_get_type ()) +#define EGG_MARKDOWN(o)		(G_TYPE_CHECK_INSTANCE_CAST ((o), EGG_TYPE_MARKDOWN, EggMarkdown)) +#define EGG_MARKDOWN_CLASS(k)	(G_TYPE_CHECK_CLASS_CAST((k), EGG_TYPE_MARKDOWN, EggMarkdownClass)) +#define EGG_IS_MARKDOWN(o)		(G_TYPE_CHECK_INSTANCE_TYPE ((o), EGG_TYPE_MARKDOWN)) +#define EGG_IS_MARKDOWN_CLASS(k)	(G_TYPE_CHECK_CLASS_TYPE ((k), EGG_TYPE_MARKDOWN)) +#define EGG_MARKDOWN_GET_CLASS(o)	(G_TYPE_INSTANCE_GET_CLASS ((o), EGG_TYPE_MARKDOWN, EggMarkdownClass)) +#define EGG_MARKDOWN_ERROR		(egg_markdown_error_quark ()) +#define EGG_MARKDOWN_TYPE_ERROR	(egg_markdown_error_get_type ()) + +#define EGG_MARKDOWN_MAX_LINE_LENGTH	2048 + +typedef struct EggMarkdownPrivate EggMarkdownPrivate; + +typedef struct +{ +	 GObject		 parent; +	 EggMarkdownPrivate	*priv; +} EggMarkdown; + +typedef struct +{ +	GObjectClass	parent_class; +	void		(* active_changed)		(EggMarkdown	*self, +							 gboolean	 active); +} EggMarkdownClass; + +typedef enum { +	EGG_MARKDOWN_OUTPUT_TEXT, +	EGG_MARKDOWN_OUTPUT_PANGO, +	EGG_MARKDOWN_OUTPUT_HTML, +	EGG_MARKDOWN_OUTPUT_UNKNOWN +} EggMarkdownOutput; + +GType		 egg_markdown_get_type	  		(void); +EggMarkdown	*egg_markdown_new			(void); +gboolean	 egg_markdown_set_output		(EggMarkdown		*self, +							 EggMarkdownOutput	 output); +gboolean	 egg_markdown_set_max_lines		(EggMarkdown		*self, +							 gint			 max_lines); +gboolean	 egg_markdown_set_smart_quoting		(EggMarkdown		*self, +							 gboolean		 smart_quoting); +gboolean	 egg_markdown_set_escape		(EggMarkdown		*self, +							 gboolean		 escape); +gboolean	 egg_markdown_set_autocode		(EggMarkdown		*self, +							 gboolean		 autocode); +gchar		*egg_markdown_parse			(EggMarkdown		*self, +							 const gchar		*text); +void		 egg_markdown_clear			(EggMarkdown		*self); +gchar 		*egg_markdown_get_link_uri		(EggMarkdown		*self, +                                                         const gint		link_id); + +G_END_DECLS + +#endif /* __EGG_MARKDOWN_H */ + diff --git a/hardinfo2/help-viewer.c b/hardinfo2/help-viewer.c new file mode 100644 index 00000000..b0a6c581 --- /dev/null +++ b/hardinfo2/help-viewer.c @@ -0,0 +1,465 @@ +/* + *    HelpViewer - Simple Help file browser + *    Copyright (C) 2009 Leandro A. F. Pereira <leandro@hardinfo.org> + * + *    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, version 2. + * + *    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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA + */ + +#define _GNU_SOURCE		/* for strcasestr() */ +#include <string.h> +#include <stdlib.h> +#include <gtk/gtk.h> + +#include "config.h" +#include "markdown-text-view.h" +#include "help-viewer.h" + +static void do_search(HelpViewer *hv, gchar *text); + +static void forward_clicked(GtkWidget *widget, gpointer data) +{ +    HelpViewer *hv = (HelpViewer *)data; +    GSList *temp; +     +    /* puts the current file on the back stack */ +    hv->back_stack = g_slist_prepend(hv->back_stack, g_strdup(hv->current_file)); +     +    /* enables the back button */ +    gtk_widget_set_sensitive(hv->btn_back, TRUE); + +    /* loads the new current file */ +    if (g_str_has_prefix(hv->forward_stack->data, "search://")) { +        do_search(hv, hv->forward_stack->data + sizeof("search://") - 1); +    } else { +        markdown_textview_load_file(MARKDOWN_TEXTVIEW(hv->text_view), hv->forward_stack->data); +    } +     +    /* pops the stack */ +    temp = hv->forward_stack->next; +    g_free(hv->forward_stack->data); +    g_slist_free1(hv->forward_stack); +    hv->forward_stack = temp; +     +    /* if there aren't items on forward stack anymore, disables the button */ +    if (!hv->forward_stack) { +        gtk_widget_set_sensitive(hv->btn_forward, FALSE); +    } +} + +static void back_clicked(GtkWidget *widget, gpointer data) +{ +    HelpViewer *hv = (HelpViewer *)data; +    GSList *temp; +     +    /* puts the current file on the forward stack */ +    hv->forward_stack = g_slist_prepend(hv->forward_stack, g_strdup(hv->current_file)); +     +    /* enables the forward button */ +    gtk_widget_set_sensitive(hv->btn_forward, TRUE); + +    /* loads the new current file */ +    if (g_str_has_prefix(hv->back_stack->data, "search://")) { +        do_search(hv, hv->back_stack->data + sizeof("search://") - 1); +    } else { +        markdown_textview_load_file(MARKDOWN_TEXTVIEW(hv->text_view), hv->back_stack->data); +    } +     +    /* pops the stack */ +    temp = hv->back_stack->next; +    g_free(hv->back_stack->data); +    g_slist_free1(hv->back_stack); +    hv->back_stack = temp; +     +    /* if there aren't items on back stack anymore, disables the button */ +    if (!hv->back_stack) { +        gtk_widget_set_sensitive(hv->btn_back, FALSE); +    } +} + +static void link_clicked(MarkdownTextView *text_view, gchar *link, gpointer data) +{ +    HelpViewer *hv = (HelpViewer *)data; +     +    /* adds the current file to the back stack (before loading the new file */ +    hv->back_stack = g_slist_prepend(hv->back_stack, g_strdup(hv->current_file)); +    gtk_widget_set_sensitive(hv->btn_back, TRUE); + +    gtk_statusbar_pop(GTK_STATUSBAR(hv->status_bar), 1); +    markdown_textview_load_file(text_view, link); +} + +static void file_load_complete(MarkdownTextView *text_view, gchar *file, gpointer data) +{ +    HelpViewer *hv = (HelpViewer *)data; +     +    /* sets the currently-loaded file */ +    g_free(hv->current_file); +    hv->current_file = g_strdup(file); +     +    gtk_statusbar_push(GTK_STATUSBAR(hv->status_bar), 1, "Done."); +} + +static void hovering_over_link(MarkdownTextView *text_view, gchar *link, gpointer data) +{ +    HelpViewer *hv = (HelpViewer *)data; +    gchar *temp; +     +    temp = g_strconcat("Link to ", link, NULL); +     +    gtk_statusbar_push(GTK_STATUSBAR(hv->status_bar), 1, temp); +     +    g_free(temp); +} + +static void hovering_over_text(MarkdownTextView *text_view, gpointer data) +{ +    HelpViewer *hv = (HelpViewer *)data; +     +    gtk_statusbar_pop(GTK_STATUSBAR(hv->status_bar), 1); +} + +static void do_search(HelpViewer *hv, gchar *text) +{ +    GString *markdown; +    GDir *dir; +    gchar **terms; +    gint no_results = 0; +    int term; +     +    /* +     * FIXME: This search is currently pretty slow; think on a better way to do search.  +     * Ideas: +     * - Build a index the first time the help file is opened +     * - Search only titles and subtitles +     */ +     +    terms = g_strsplit(text, " ", 0); +    markdown = g_string_new("# Search Results\n"); +    g_string_append_printf(markdown, "Search terms: *%s*\n****\n", text); +     +    gtk_widget_set_sensitive(hv->window, FALSE); +     +    if ((dir = g_dir_open(hv->help_directory, 0, NULL))) { +        const gchar *name; +         +        while ((name = g_dir_read_name(dir))) { +#if GTK_CHECK_VERSION(2,16,0) +            gtk_entry_progress_pulse(GTK_ENTRY(hv->text_search)); +#endif	/* GTK_CHECK_VERSION(2,16,0) */ +             +            if (g_str_has_suffix(name, ".hlp")) { +                FILE *file; +                gchar *path; +                gchar buffer[256]; +                 +                path = g_build_filename(hv->help_directory, name, NULL); +                if ((file = fopen(path, "rb"))) { +                    gboolean found = FALSE; +                    gchar *title = NULL; +                     +                    while (!found && fgets(buffer, sizeof buffer, file)) { +                        if (!title && (g_str_has_prefix(buffer, "# ") || g_str_has_prefix(buffer, " # "))) { +                            title = g_strstrip(strchr(buffer, '#') + 1); +                            title = g_strdup(title); +                        } +                         +                        for (term = 0; !found && terms[term]; term++) { +#ifdef strcasestr +                            found = strcasestr(buffer, terms[term]) != NULL; +#else +                            gchar *upper1, *upper2; +                             +                            upper1 = g_utf8_strup(buffer, -1); +                            upper2 = g_utf8_strup(terms[term], -1); +                             +                            found = strstr(upper1, upper2) != NULL; +                             +                            g_free(upper1); +                            g_free(upper2); +#endif +                        } +                    } +                     +                    if (found) { +                        no_results++; +                         +                        if (title) { +                              g_string_append_printf(markdown, +                                                     "* [%s %s]\n", name, title); +                        } else { +                              g_string_append_printf(markdown, +                                                     "* [%s %s]\n", name, name); +                        } +                    } +                     +                    g_free(title); +                    fclose(file); +                } +                 +                g_free(path); +            } +        } +         +        g_dir_close(dir); +    } +     +     +    if (no_results == 0) { +        g_string_append_printf(markdown, +                               "Search returned no results."); +    } else { +        g_string_append_printf(markdown, +                               "****\n%d results found.", no_results); +    } +     +    /* shows the results inside the textview */ +    markdown_textview_set_text(MARKDOWN_TEXTVIEW(hv->text_view), markdown->str); + +    g_free(hv->current_file); +    hv->current_file = g_strdup_printf("search://%s", text); + +#if GTK_CHECK_VERSION(2,16,0) +    gtk_entry_set_progress_fraction(GTK_ENTRY(hv->text_search), 0.0f); +#endif	/* GTK_CHECK_VERSION(2,16,0) */ +    gtk_widget_set_sensitive(hv->window, TRUE); + +    g_string_free(markdown, TRUE); +    g_strfreev(terms); +} + +static void activate(GtkEntry *entry, gpointer data) +{ +    HelpViewer *hv = (HelpViewer *)data; +     +    /* adds the current file to the back stack (before loading the new file */ +    hv->back_stack = g_slist_prepend(hv->back_stack, g_strdup(hv->current_file)); +    gtk_widget_set_sensitive(hv->btn_back, TRUE); + +    do_search((HelpViewer *)data, (gchar *)gtk_entry_get_text(entry)); +} + +static void home_clicked(GtkWidget *button, gpointer data) +{ +    HelpViewer *hv = (HelpViewer *)data; +     +    help_viewer_open_page(hv, "index.hlp"); +} + +void help_viewer_open_page(HelpViewer *hv, const gchar *page) +{ +    /* adds the current file to the back stack (before loading the new file */ +    hv->back_stack = g_slist_prepend(hv->back_stack, g_strdup(hv->current_file)); +    gtk_widget_set_sensitive(hv->btn_back, TRUE); + +    markdown_textview_load_file(MARKDOWN_TEXTVIEW(hv->text_view), page); +     +    gtk_window_present(GTK_WINDOW(hv->window)); +} + +void help_viewer_destroy(HelpViewer *hv) +{ +    GSList *item; + +    for (item = hv->back_stack; item; item = item->next) { +        g_free(item->data); +    } + +    for (item = hv->forward_stack; item; item = item->next) { +        g_free(item->data); +    } +     +    g_slist_free(hv->back_stack); +    g_slist_free(hv->forward_stack); + +    g_free(hv->current_file); +    g_free(hv->help_directory); +} + +static gboolean destroy_me(GtkWidget *widget, gpointer data) +{ +    HelpViewer *hv = (HelpViewer *)data; +     +    help_viewer_destroy(hv); + +    return FALSE; +} + +HelpViewer * +help_viewer_new (const gchar *help_dir, const gchar *help_file) +{ +    HelpViewer *hv; +    GtkWidget *help_viewer; +    GtkWidget *vbox; +    GtkWidget *hbox; +    GtkWidget *toolbar1; +    GtkIconSize tmp_toolbar_icon_size; +    GtkWidget *btn_back; +    GtkWidget *btn_forward; +    GtkWidget *separatortoolitem1; +    GtkWidget *toolbar2; +    GtkWidget *toolitem3; +#if !GTK_CHECK_VERSION(2,16,0) +    GtkWidget *label1; +#endif	/* GTK_CHECK_VERSION(2,16,0) */ +    GtkWidget *toolitem4; +    GtkWidget *txt_search; +    GtkWidget *scrolledhelp_viewer; +    GtkWidget *markdown_textview; +    GtkWidget *status_bar; +    GtkWidget *separatortoolitem; +    GtkWidget *btn_home; +     +    GdkPixbuf *icon; + +    help_viewer = gtk_window_new (GTK_WINDOW_TOPLEVEL); +    gtk_widget_set_size_request(help_viewer, 300, 200); +    gtk_window_set_default_size(GTK_WINDOW(help_viewer), 640, 480); +    gtk_window_set_title (GTK_WINDOW (help_viewer), "Help Viewer"); +     +    icon = gtk_widget_render_icon(help_viewer, GTK_STOCK_HELP,  +                                  GTK_ICON_SIZE_DIALOG, +                                  NULL); +    gtk_window_set_icon(GTK_WINDOW(help_viewer), icon); +    g_object_unref(icon); +     +    vbox = gtk_vbox_new (FALSE, 0); +    gtk_widget_show (vbox); +    gtk_container_add (GTK_CONTAINER (help_viewer), vbox); + +    hbox = gtk_hbox_new (FALSE, 0); +    gtk_widget_show (hbox); +    gtk_box_pack_start (GTK_BOX (vbox), hbox, FALSE, FALSE, 0); + +    toolbar1 = gtk_toolbar_new (); +    gtk_widget_show (toolbar1); +    gtk_box_pack_start (GTK_BOX (hbox), toolbar1, TRUE, TRUE, 0); +    gtk_toolbar_set_style (GTK_TOOLBAR (toolbar1), GTK_TOOLBAR_BOTH_HORIZ); +    tmp_toolbar_icon_size = gtk_toolbar_get_icon_size (GTK_TOOLBAR (toolbar1)); + +    btn_back = (GtkWidget*) gtk_tool_button_new_from_stock ("gtk-go-back"); +    gtk_widget_show (btn_back); +    gtk_container_add (GTK_CONTAINER (toolbar1), btn_back); +    gtk_tool_item_set_is_important (GTK_TOOL_ITEM (btn_back), TRUE); +    gtk_widget_set_sensitive(btn_back, FALSE); + +    btn_forward = (GtkWidget*) gtk_tool_button_new_from_stock ("gtk-go-forward"); +    gtk_widget_show (btn_forward); +    gtk_container_add (GTK_CONTAINER (toolbar1), btn_forward); +    gtk_widget_set_sensitive(btn_forward, FALSE); + +    separatortoolitem1 = (GtkWidget*) gtk_separator_tool_item_new (); +    gtk_widget_show (separatortoolitem1); +    gtk_container_add (GTK_CONTAINER (toolbar1), separatortoolitem1); + +    btn_home = (GtkWidget*) gtk_tool_button_new_from_stock ("gtk-home"); +    gtk_widget_show (btn_home); +    gtk_container_add (GTK_CONTAINER (toolbar1), btn_home); + +    toolbar2 = gtk_toolbar_new (); +    gtk_widget_show (toolbar2); +    gtk_box_pack_end (GTK_BOX (hbox), toolbar2, FALSE, TRUE, 0); +    gtk_toolbar_set_style (GTK_TOOLBAR (toolbar2), GTK_TOOLBAR_BOTH_HORIZ); +    gtk_toolbar_set_show_arrow (GTK_TOOLBAR (toolbar2), FALSE); +    tmp_toolbar_icon_size = gtk_toolbar_get_icon_size (GTK_TOOLBAR (toolbar2)); + +    toolitem3 = (GtkWidget*) gtk_tool_item_new (); +    gtk_widget_show (toolitem3); +    gtk_container_add (GTK_CONTAINER (toolbar2), toolitem3); + +#if !GTK_CHECK_VERSION(2,16,0) +    label1 = gtk_label_new_with_mnemonic ("_Search:"); +    gtk_widget_show (label1); +    gtk_container_add (GTK_CONTAINER (toolitem3), label1); +#endif	/* GTK_CHECK_VERSION(2,16,0) */ + +    toolitem4 = (GtkWidget*) gtk_tool_item_new (); +    gtk_widget_show (toolitem4); +    gtk_container_add (GTK_CONTAINER (toolbar2), toolitem4); + +    txt_search = gtk_entry_new (); +    gtk_widget_show (txt_search); +    gtk_container_add (GTK_CONTAINER (toolitem4), txt_search); +    gtk_entry_set_invisible_char (GTK_ENTRY (txt_search), 9679); +#if GTK_CHECK_VERSION(2,16,0) +    gtk_entry_set_icon_from_stock(GTK_ENTRY(txt_search), +                                  GTK_ENTRY_ICON_SECONDARY, +                                  GTK_STOCK_FIND); +#endif	/* GTK_CHECK_VERSION(2,16,0) */ + +    scrolledhelp_viewer = gtk_scrolled_window_new (NULL, NULL); +    gtk_widget_show (scrolledhelp_viewer); +    gtk_box_pack_start (GTK_BOX (vbox), scrolledhelp_viewer, TRUE, TRUE, 0); +    gtk_scrolled_window_set_policy (GTK_SCROLLED_WINDOW (scrolledhelp_viewer), GTK_POLICY_NEVER, GTK_POLICY_AUTOMATIC); + +    markdown_textview = markdown_textview_new(); +    markdown_textview_set_image_directory(MARKDOWN_TEXTVIEW(markdown_textview), help_dir); +    gtk_widget_show (markdown_textview); +    gtk_container_add (GTK_CONTAINER (scrolledhelp_viewer), markdown_textview); + +    status_bar = gtk_statusbar_new (); +    gtk_widget_show (status_bar); +    gtk_box_pack_start (GTK_BOX (vbox), status_bar, FALSE, FALSE, 0); + +    hv = g_new0(HelpViewer, 1); +    hv->window = help_viewer; +    hv->status_bar = status_bar; +    hv->btn_back = btn_back; +    hv->btn_forward = btn_forward; +    hv->text_view = markdown_textview; +    hv->text_search = txt_search; +    hv->help_directory = g_strdup(help_dir); +    hv->back_stack = NULL; +    hv->forward_stack = NULL; + +    g_signal_connect(markdown_textview, "link-clicked", G_CALLBACK(link_clicked), hv); +    g_signal_connect(markdown_textview, "hovering-over-link", G_CALLBACK(hovering_over_link), hv); +    g_signal_connect(markdown_textview, "hovering-over-text", G_CALLBACK(hovering_over_text), hv); +    g_signal_connect(markdown_textview, "file-load-complete", G_CALLBACK(file_load_complete), hv); + +    g_signal_connect(btn_back, "clicked", G_CALLBACK(back_clicked), hv); +    g_signal_connect(btn_forward, "clicked", G_CALLBACK(forward_clicked), hv); +    g_signal_connect(btn_home, "clicked", G_CALLBACK(home_clicked), hv); + +    g_signal_connect(help_viewer, "delete-event", G_CALLBACK(destroy_me), hv); +    g_signal_connect(txt_search, "activate", G_CALLBACK(activate), hv); +                             +    if (!markdown_textview_load_file(MARKDOWN_TEXTVIEW(markdown_textview), help_file ? help_file : "index.hlp")) { +        GtkWidget	*dialog; + +        dialog = gtk_message_dialog_new(NULL, +                                        GTK_DIALOG_DESTROY_WITH_PARENT, +                                        GTK_MESSAGE_ERROR, +                                        GTK_BUTTONS_CLOSE, +                                        "Cannot open help index file."); +        gtk_dialog_run(GTK_DIALOG(dialog)); +        gtk_widget_destroy(dialog); +    } +     +    gtk_widget_show_all(hv->window); + +    return hv; +} + +#ifdef HELPVIEWER_TEST +int main(int argc, char **argv) +{ +    HelpViewer *hv; +     +    gtk_init(&argc, &argv); +     +    hv = help_viewer_new("documentation", NULL); +     +    gtk_main(); +} +#endif	/* HELPVIEWER_TEST */ diff --git a/hardinfo2/help-viewer.h b/hardinfo2/help-viewer.h new file mode 100644 index 00000000..fc036cf5 --- /dev/null +++ b/hardinfo2/help-viewer.h @@ -0,0 +1,42 @@ +/* + *    HelpViewer - Simple Help file browser + *    Copyright (C) 2009 Leandro A. F. Pereira <leandro@hardinfo.org> + * + *    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, version 2. + * + *    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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA + */ + +#ifndef __HELP_VIEWER_H__ +#define __HELP_VIEWER_H__ + +typedef struct _HelpViewer	HelpViewer; + +struct _HelpViewer { +    GtkWidget *window; +    GtkWidget *status_bar; +     +    GtkWidget *btn_back, *btn_forward; +    GtkWidget *text_view; +    GtkWidget *text_search; +     +    gchar *current_file; +    gchar *help_directory; + +    GSList *back_stack, *forward_stack; +}; + +HelpViewer *help_viewer_new(const gchar *help_dir, const gchar *help_file); +void help_viewer_open_page(HelpViewer *help_viewer, const gchar *page); +void help_viewer_destroy(HelpViewer *help_viewer); + +#endif	/* __HELP_VIEWER_H__ */
\ No newline at end of file diff --git a/hardinfo2/markdown-text-view.c b/hardinfo2/markdown-text-view.c new file mode 100644 index 00000000..220be6c7 --- /dev/null +++ b/hardinfo2/markdown-text-view.c @@ -0,0 +1,649 @@ +/* + * Markdown Text View + * GtkTextView subclass that supports Markdown syntax + * + * Copyright (C) 2009 Leandro Pereira <leandro@hardinfo.org> + * Portions Copyright (C) 2007-2008 Richard Hughes <richard@hughsie.com> + * Portions Copyright (C) GTK+ Team (based on hypertext textview demo) + * + * Licensed under the GNU General Public License Version 2 + * + * 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 <gdk/gdkkeysyms.h> + +#include "markdown-text-view.h" +#include "config.h" + +static GdkCursor *hand_cursor = NULL; + +static void markdown_textview_finalize(GObject * object); + +G_DEFINE_TYPE(MarkdownTextView, markdown_textview, GTK_TYPE_TEXT_VIEW); + +enum { +     LINK_CLICKED, +     HOVERING_OVER_LINK, +     HOVERING_OVER_TEXT, +     FILE_LOAD_COMPLETE, +     LAST_SIGNAL +}; + +static guint markdown_textview_signals[LAST_SIGNAL] = { 0 }; + +GtkWidget *markdown_textview_new() +{ +    return g_object_new(TYPE_MARKDOWN_TEXTVIEW, NULL); +} + +static void markdown_textview_class_init(MarkdownTextViewClass * klass) +{ +    GObjectClass *object_class; + +    if (!hand_cursor) { +	hand_cursor = gdk_cursor_new(GDK_HAND2); +    } + +    object_class = G_OBJECT_CLASS(klass); + +    markdown_textview_signals[LINK_CLICKED] = g_signal_new( +             "link-clicked", +             G_OBJECT_CLASS_TYPE(object_class), +             G_SIGNAL_RUN_FIRST, +             G_STRUCT_OFFSET(MarkdownTextViewClass, link_clicked), +             NULL, NULL, +             g_cclosure_marshal_VOID__STRING, +             G_TYPE_NONE, +             1, +             G_TYPE_STRING); + +    markdown_textview_signals[HOVERING_OVER_LINK] = g_signal_new( +             "hovering-over-link", +             G_OBJECT_CLASS_TYPE(object_class), +             G_SIGNAL_RUN_FIRST, +             G_STRUCT_OFFSET(MarkdownTextViewClass, hovering_over_link), +             NULL, NULL, +             g_cclosure_marshal_VOID__STRING, +             G_TYPE_NONE, +             1, +             G_TYPE_STRING); + +    markdown_textview_signals[HOVERING_OVER_TEXT] = g_signal_new( +             "hovering-over-text", +             G_OBJECT_CLASS_TYPE(object_class), +             G_SIGNAL_RUN_FIRST, +             G_STRUCT_OFFSET(MarkdownTextViewClass, hovering_over_text), +             NULL, NULL, +             g_cclosure_marshal_VOID__VOID, +             G_TYPE_NONE, +             0); + +    markdown_textview_signals[FILE_LOAD_COMPLETE] = g_signal_new( +             "file-load-complete", +             G_OBJECT_CLASS_TYPE(object_class), +             G_SIGNAL_RUN_FIRST, +             G_STRUCT_OFFSET(MarkdownTextViewClass, file_load_complete), +             NULL, NULL, +             g_cclosure_marshal_VOID__STRING, +             G_TYPE_NONE, +             1, +             G_TYPE_STRING); +} + +static void +gtk_text_buffer_insert_markup(GtkTextBuffer * buffer, GtkTextIter * iter, +			      const gchar * markup) +{ +    PangoAttrIterator *paiter; +    PangoAttrList *attrlist; +    GtkTextMark *mark; +    GError *error = NULL; +    gchar *text; + +    g_return_if_fail(GTK_IS_TEXT_BUFFER(buffer)); +    g_return_if_fail(markup != NULL); + +    if (*markup == '\000') +	return; + +    /* invalid */ +    if (!pango_parse_markup(markup, -1, 0, &attrlist, &text, NULL, &error)) { +	g_warning("Invalid markup string: %s", error->message); +	g_error_free(error); +	return; +    } + +    /* trivial, no markup */ +    if (attrlist == NULL) { +	gtk_text_buffer_insert(buffer, iter, text, -1); +	g_free(text); +	return; +    } + +    /* create mark with right gravity */ +    mark = gtk_text_buffer_create_mark(buffer, NULL, iter, FALSE); +    paiter = pango_attr_list_get_iterator(attrlist); + +    do { +	PangoAttribute *attr; +	GtkTextTag *tag; +	GtkTextTag *tag_para; +	gint start, end; + +	pango_attr_iterator_range(paiter, &start, &end); + +	if (end == G_MAXINT)	/* last chunk */ +	    end = start - 1;	/* resulting in -1 to be passed to _insert */ + +	tag = gtk_text_tag_new(NULL); + +	if ((attr = pango_attr_iterator_get(paiter, PANGO_ATTR_LANGUAGE))) +	    g_object_set(tag, "language", +			 pango_language_to_string(((PangoAttrLanguage *) +						   attr)->value), NULL); + +	if ((attr = pango_attr_iterator_get(paiter, PANGO_ATTR_FAMILY))) +	    g_object_set(tag, "family", ((PangoAttrString *) attr)->value, +			 NULL); + +	if ((attr = pango_attr_iterator_get(paiter, PANGO_ATTR_STYLE))) +	    g_object_set(tag, "style", ((PangoAttrInt *) attr)->value, +			 NULL); + +	if ((attr = pango_attr_iterator_get(paiter, PANGO_ATTR_WEIGHT))) +	    g_object_set(tag, "weight", ((PangoAttrInt *) attr)->value, +			 NULL); + +	if ((attr = pango_attr_iterator_get(paiter, PANGO_ATTR_VARIANT))) +	    g_object_set(tag, "variant", ((PangoAttrInt *) attr)->value, +			 NULL); + +	if ((attr = pango_attr_iterator_get(paiter, PANGO_ATTR_STRETCH))) +	    g_object_set(tag, "stretch", ((PangoAttrInt *) attr)->value, +			 NULL); + +	if ((attr = pango_attr_iterator_get(paiter, PANGO_ATTR_SIZE))) +	    g_object_set(tag, "size", ((PangoAttrInt *) attr)->value, +			 NULL); + +	if ((attr = pango_attr_iterator_get(paiter, PANGO_ATTR_FONT_DESC))) +	    g_object_set(tag, "font-desc", +			 ((PangoAttrFontDesc *) attr)->desc, NULL); + +	if ((attr = +	     pango_attr_iterator_get(paiter, PANGO_ATTR_FOREGROUND))) { +	    GdkColor col = { 0, +		((PangoAttrColor *) attr)->color.red, +		((PangoAttrColor *) attr)->color.green, +		((PangoAttrColor *) attr)->color.blue +	    }; + +	    g_object_set(tag, "foreground-gdk", &col, NULL); +	} + +	if ((attr = +	     pango_attr_iterator_get(paiter, PANGO_ATTR_BACKGROUND))) { +	    GdkColor col = { 0, +		((PangoAttrColor *) attr)->color.red, +		((PangoAttrColor *) attr)->color.green, +		((PangoAttrColor *) attr)->color.blue +	    }; + +	    g_object_set(tag, "background-gdk", &col, NULL); +	} + +	if ((attr = pango_attr_iterator_get(paiter, PANGO_ATTR_UNDERLINE))) +	    g_object_set(tag, "underline", ((PangoAttrInt *) attr)->value, +			 NULL); + +	if ((attr = +	     pango_attr_iterator_get(paiter, PANGO_ATTR_STRIKETHROUGH))) +	    g_object_set(tag, "strikethrough", +			 (gboolean) (((PangoAttrInt *) attr)->value != 0), +			 NULL); + +	if ((attr = pango_attr_iterator_get(paiter, PANGO_ATTR_RISE))) +	    g_object_set(tag, "rise", ((PangoAttrInt *) attr)->value, +			 NULL); + +	if ((attr = pango_attr_iterator_get(paiter, PANGO_ATTR_SCALE))) +	    g_object_set(tag, "scale", ((PangoAttrFloat *) attr)->value, +			 NULL); + +	gtk_text_tag_table_add(gtk_text_buffer_get_tag_table(buffer), tag); + +	tag_para = +	    gtk_text_tag_table_lookup(gtk_text_buffer_get_tag_table +				      (buffer), "para"); +	gtk_text_buffer_insert_with_tags(buffer, iter, text + start, +					 end - start, tag, tag_para, NULL); + +	/* mark had right gravity, so it should be +	 *      at the end of the inserted text now */ +	gtk_text_buffer_get_iter_at_mark(buffer, iter, mark); +    } while (pango_attr_iterator_next(paiter)); + +    gtk_text_buffer_delete_mark(buffer, mark); +    pango_attr_iterator_destroy(paiter); +    pango_attr_list_unref(attrlist); +    g_free(text); +} + + +static void +set_cursor_if_appropriate(MarkdownTextView * self, gint x, gint y) +{ +    GSList *tags = NULL, *tagp = NULL; +    GtkTextIter iter; +    gboolean hovering = FALSE; +    gchar *link_uri = NULL; + +    gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(self), &iter, x, +				       y); + +    tags = gtk_text_iter_get_tags(&iter); +    for (tagp = tags; tagp != NULL; tagp = tagp->next) { +	GtkTextTag *tag = tagp->data; +	gint is_underline = 0; +	gchar *lang = NULL; + +	g_object_get(G_OBJECT(tag), +		     "underline", &is_underline, +		     "language", &lang, +		     NULL); + +	if (is_underline == 1 && lang) { +	    link_uri = egg_markdown_get_link_uri(self->markdown, atoi(lang)); +	    g_free(lang); +	    hovering = TRUE; +	    break; +	} + +	g_free(lang); +    } + +    if (hovering != self->hovering_over_link) { +	self->hovering_over_link = hovering; + +	if (self->hovering_over_link) { +            g_signal_emit(self, markdown_textview_signals[HOVERING_OVER_LINK], +                          0, link_uri); +	    gdk_window_set_cursor(gtk_text_view_get_window +				  (GTK_TEXT_VIEW(self), +				   GTK_TEXT_WINDOW_TEXT), hand_cursor); +	} else { +            g_signal_emit(self, markdown_textview_signals[HOVERING_OVER_TEXT], 0); +	    gdk_window_set_cursor(gtk_text_view_get_window +				  (GTK_TEXT_VIEW(self), +				   GTK_TEXT_WINDOW_TEXT), NULL); +        } +    } +     +    if (link_uri) +        g_free(link_uri); + +    if (tags) +	g_slist_free(tags); +} + +/* Update the cursor image if the pointer moved.  + */ +static gboolean +motion_notify_event(GtkWidget * self, GdkEventMotion * event) +{ +    gint x, y; + +    gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(self), +					  GTK_TEXT_WINDOW_WIDGET, +					  event->x, event->y, &x, &y); + +    set_cursor_if_appropriate(MARKDOWN_TEXTVIEW(self), x, y); + +    gdk_window_get_pointer(self->window, NULL, NULL, NULL); +    return FALSE; +} + +/* Also update the cursor image if the window becomes visible + * (e.g. when a window covering it got iconified). + */ +static gboolean +visibility_notify_event(GtkWidget * self, GdkEventVisibility * event) +{ +    gint wx, wy, bx, by; + +    gdk_window_get_pointer(self->window, &wx, &wy, NULL); + +    gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(self), +					  GTK_TEXT_WINDOW_WIDGET, +					  wx, wy, &bx, &by); + +    set_cursor_if_appropriate(MARKDOWN_TEXTVIEW(self), bx, by); + +    return FALSE; +} + +static void +follow_if_link(GtkWidget * widget, GtkTextIter * iter) +{ +    GSList *tags = NULL, *tagp = NULL; +    MarkdownTextView *self = MARKDOWN_TEXTVIEW(widget); +     +    tags = gtk_text_iter_get_tags(iter); +    for (tagp = tags; tagp != NULL; tagp = tagp->next) { +	GtkTextTag *tag = tagp->data; +	gint is_underline = 0; +	gchar *lang = NULL; + +	g_object_get(G_OBJECT(tag), +		     "underline", &is_underline, +		     "language", &lang, +		     NULL); + +	if (is_underline == 1 && lang) { +	    gchar *link = egg_markdown_get_link_uri(self->markdown, atoi(lang)); +	    if (link) { +	        g_signal_emit(self, markdown_textview_signals[LINK_CLICKED], +                              0, link); +		g_free(link); +	    } +	    g_free(lang); +	    break; +	} + +	g_free(lang); +    } + +    if (tags) +	g_slist_free(tags); +} + +static gboolean +key_press_event(GtkWidget * self, +		GdkEventKey * event) +{ +    GtkTextIter iter; +    GtkTextBuffer *buffer; + +    switch (event->keyval) { +    case GDK_Return: +    case GDK_KP_Enter: +	buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(self)); +	gtk_text_buffer_get_iter_at_mark(buffer, &iter, +					 gtk_text_buffer_get_insert +					 (buffer)); +	follow_if_link(self, &iter); +	break; + +    default: +	break; +    } + +    return FALSE; +} + +static gboolean +event_after(GtkWidget * self, GdkEvent * ev) +{ +    GtkTextIter start, end, iter; +    GtkTextBuffer *buffer; +    GdkEventButton *event; +    gint x, y; + +    if (ev->type != GDK_BUTTON_RELEASE) +	return FALSE; + +    event = (GdkEventButton *) ev; + +    if (event->button != 1) +	return FALSE; + +    buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(self)); + +    /* we shouldn't follow a link if the user has selected something */ +    gtk_text_buffer_get_selection_bounds(buffer, &start, &end); +    if (gtk_text_iter_get_offset(&start) != gtk_text_iter_get_offset(&end)) +	return FALSE; + +    gtk_text_view_window_to_buffer_coords(GTK_TEXT_VIEW(self), +					  GTK_TEXT_WINDOW_WIDGET, +					  event->x, event->y, &x, &y); + +    gtk_text_view_get_iter_at_location(GTK_TEXT_VIEW(self), &iter, x, +				       y); + +    follow_if_link(self, &iter); + +    return FALSE; +} + +void +markdown_textview_clear(MarkdownTextView * self) +{ +    GtkTextBuffer *text_buffer; +     +    g_return_if_fail(IS_MARKDOWN_TEXTVIEW(self)); + +    egg_markdown_clear(self->markdown); + +    text_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(self)); +    gtk_text_buffer_set_text(text_buffer, "\n", 1); +} + +static void +load_images(MarkdownTextView * self) +{ +    GtkTextBuffer *buffer; +    GtkTextIter iter; +    GSList *tags, *tagp; +    gchar *image_path; +     +    buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(self)); +    gtk_text_buffer_get_start_iter(buffer, &iter); +     +    do { +       tags = gtk_text_iter_get_tags(&iter); +       for (tagp = tags; tagp != NULL; tagp = tagp->next) { +           GtkTextTag *tag = tagp->data; +           gint is_underline = 0; +           gchar *lang = NULL; + +           g_object_get(G_OBJECT(tag), +                        "underline", &is_underline, +                        "language", &lang, +                        NULL); + +           if (is_underline == 2 && lang) { +               GdkPixbuf *pixbuf; +               gchar *path; +                +               image_path = egg_markdown_get_link_uri(self->markdown, atoi(lang)); +               path = g_build_filename(self->image_directory, image_path, NULL); +               pixbuf = gdk_pixbuf_new_from_file(path, NULL); +               if (pixbuf) { +                   GtkTextMark *mark; +                   GtkTextIter start; + +                   mark = gtk_text_buffer_create_mark(buffer, NULL, &iter, FALSE); +                    +                   gtk_text_buffer_get_iter_at_mark(buffer, &start, mark); +                   gtk_text_iter_forward_to_tag_toggle(&iter, tag); +                   gtk_text_buffer_delete(buffer, &start, &iter); + +                   gtk_text_buffer_insert_pixbuf(buffer, &iter, pixbuf); +   +                   g_object_unref(pixbuf); +                   gtk_text_buffer_delete_mark(buffer, mark); +               } +                +               g_free(image_path); +               g_free(lang); +               g_free(path); +               break; +           } + +           g_free(lang); +       } +        +       if (tags) +           g_slist_free(tags); +   } while (gtk_text_iter_forward_to_tag_toggle(&iter, NULL)); +} + +static gboolean +append_text(MarkdownTextView * self, +            const gchar * text) +{ +    GtkTextIter iter; +    GtkTextBuffer *text_buffer; +    gchar *line; +     +    g_return_val_if_fail(IS_MARKDOWN_TEXTVIEW(self), FALSE); +     +    text_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(self)); +    gtk_text_buffer_get_end_iter(text_buffer, &iter); + +    line = egg_markdown_parse(self->markdown, text); +    if (line && *line) { +        gtk_text_buffer_insert_markup(text_buffer, &iter, line); +        gtk_text_buffer_insert(text_buffer, &iter, "\n", 1); +        g_free(line); +         +        return TRUE; +    } +     +    return FALSE; +} + +gboolean +markdown_textview_set_text(MarkdownTextView * self, +                           const gchar * text) +{ +    gboolean result = TRUE; +    gchar **lines; +    gint line; +     +    g_return_val_if_fail(IS_MARKDOWN_TEXTVIEW(self), FALSE); + +    markdown_textview_clear(self); +     +    lines = g_strsplit(text, "\n", 0); +    for (line = 0; result && lines[line]; line++) { +         result = append_text(self, (const gchar *)lines[line]); +    } +    g_strfreev(lines); + +    load_images(self); +     +    return result; +}                            + +gboolean +markdown_textview_load_file(MarkdownTextView * self, +			    const gchar * file_name) +{ +    FILE *text_file; +    gchar *path; + +    g_return_val_if_fail(IS_MARKDOWN_TEXTVIEW(self), FALSE); +     +    path = g_build_filename(self->image_directory, file_name, NULL); + +    /* we do assume UTF-8 encoding */ +    if ((text_file = fopen(path, "rb"))) { +	GtkTextBuffer *text_buffer; +	GtkTextIter iter; +	gchar *line; +	gchar buffer[EGG_MARKDOWN_MAX_LINE_LENGTH]; + +	text_buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(self)); + +	gtk_text_buffer_set_text(text_buffer, "\n", 1); +	gtk_text_buffer_get_start_iter(text_buffer, &iter); + +	egg_markdown_clear(self->markdown); + +	while (fgets(buffer, EGG_MARKDOWN_MAX_LINE_LENGTH, text_file)) { +	    line = egg_markdown_parse(self->markdown, buffer); + +	    if (line && *line) { +		gtk_text_buffer_insert_markup(text_buffer, &iter, line); +		gtk_text_buffer_insert(text_buffer, &iter, "\n", 1); +	    } + +	    g_free(line); +	} +	fclose(text_file); +	 +	load_images(self); +	 +        g_signal_emit(self, markdown_textview_signals[FILE_LOAD_COMPLETE], 0, file_name); +         +        g_free(path); + +	return TRUE; +    } +     +    g_free(path); + +    return FALSE; +} + +void +markdown_textview_set_image_directory(MarkdownTextView * self, const gchar *directory) +{ +    g_return_if_fail(IS_MARKDOWN_TEXTVIEW(self)); + +    g_free(self->image_directory); +    self->image_directory = g_strdup(directory); +} + +static void markdown_textview_init(MarkdownTextView * self) +{ +    self->markdown = egg_markdown_new(); +    self->image_directory = g_strdup("."); + +    egg_markdown_set_output(self->markdown, EGG_MARKDOWN_OUTPUT_PANGO); +    egg_markdown_set_escape(self->markdown, TRUE); +    egg_markdown_set_autocode(self->markdown, TRUE); +    egg_markdown_set_smart_quoting(self->markdown, TRUE); + +    gtk_text_view_set_wrap_mode(GTK_TEXT_VIEW(self), GTK_WRAP_WORD); +    gtk_text_view_set_justification(GTK_TEXT_VIEW(self), +				    GTK_JUSTIFY_FILL); +    gtk_text_view_set_editable(GTK_TEXT_VIEW(self), FALSE); +    gtk_text_view_set_left_margin(GTK_TEXT_VIEW(self), 10); +    gtk_text_view_set_right_margin(GTK_TEXT_VIEW(self), 10); +    gtk_text_view_set_pixels_above_lines(GTK_TEXT_VIEW(self), 3); +    gtk_text_view_set_pixels_below_lines(GTK_TEXT_VIEW(self), 3); +     +    g_signal_connect(self, "event-after", +		     G_CALLBACK(event_after), NULL); +    g_signal_connect(self, "key-press-event", +		     G_CALLBACK(key_press_event), NULL); +    g_signal_connect(self, "motion-notify-event", +		     G_CALLBACK(motion_notify_event), NULL); +    g_signal_connect(self, "visibility-notify-event", +		     G_CALLBACK(visibility_notify_event), NULL); +} + +static void markdown_textview_finalize(GObject * object) +{ +    MarkdownTextView *self; + +    g_return_if_fail(IS_MARKDOWN_TEXTVIEW(object)); + +    self = MARKDOWN_TEXTVIEW(object); +    g_object_unref(self->markdown); +} diff --git a/hardinfo2/markdown-text-view.h b/hardinfo2/markdown-text-view.h new file mode 100644 index 00000000..36a81fb1 --- /dev/null +++ b/hardinfo2/markdown-text-view.h @@ -0,0 +1,70 @@ +/* + * Markdown Text View + * GtkTextView subclass that supports Markdown syntax + * + * Copyright (C) 2009 Leandro Pereira <leandro@hardinfo.org> + * Portions Copyright (C) 2007-2008 Richard Hughes <richard@hughsie.com> + * Portions Copyright (C) GTK+ Team (based on hypertext textview demo) + * + * Licensed under the GNU General Public License Version 2 + * + * 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. + */ +#ifndef __MARKDOWN_TEXTVIEW_H__ +#define __MARKDOWN_TEXTVIEW_H__ + +#include <gtk/gtk.h> +#include "egg-markdown.h" + +G_BEGIN_DECLS +#define TYPE_MARKDOWN_TEXTVIEW			(markdown_textview_get_type()) +#define MARKDOWN_TEXTVIEW(obj)			(G_TYPE_CHECK_INSTANCE_CAST((obj), TYPE_MARKDOWN_TEXTVIEW, MarkdownTextView)) +#define MARKDOWN_TEXTVIEW_CLASS(obj)		(G_TYPE_CHECK_CLASS_CAST((obj), MARKDOWN_TEXTVIEW, MarkdownTextViewClass)) +#define IS_MARKDOWN_TEXTVIEW(obj)		(G_TYPE_CHECK_INSTANCE_TYPE((obj), TYPE_MARKDOWN_TEXTVIEW)) +#define IS_MARKDOWN_TEXTVIEW_CLASS(obj)		(G_TYPE_CHECK_CLASS_TYPE((obj), TYPE_MARKDOWN_TEXTVIEW)) +#define MARKDOWN_TEXTVIEW_GET_CLASS(obj)	(G_TYPE_INSTANCE_GET_CLASS((obj), TYPE_MARKDOWN_TEXTVIEW, MarkdownTextViewClass)) + +typedef struct _MarkdownTextView MarkdownTextView; +typedef struct _MarkdownTextViewClass MarkdownTextViewClass; + +struct _MarkdownTextView { +    GtkTextView parent; + +    EggMarkdown *markdown; +    gboolean hovering_over_link; +    gchar *image_directory; +}; + +struct _MarkdownTextViewClass { +    GtkTextViewClass parent_class; +     +    void	(*link_clicked)		(MarkdownTextView *text_view, gchar *uri); +    void	(*hovering_over_link)	(MarkdownTextView *text_view, gchar *uri); +    void	(*hovering_over_text)	(MarkdownTextView *text_view); +    void	(*file_load_complete)	(MarkdownTextView *text_view, gchar *file); +}; + +GtkWidget	*markdown_textview_new(); +gboolean	 markdown_textview_load_file(MarkdownTextView * textview, +		  	                     const gchar * file_name); +gboolean 	 markdown_textview_set_text(MarkdownTextView * textview, +                                            const gchar * text); +void		 markdown_textview_clear(MarkdownTextView * textview); +void		 markdown_textview_set_image_directory(MarkdownTextView * self, +                                                       const gchar * directory); + +G_END_DECLS + +#endif				/* __MARKDOWN_TEXTVIEW_H__ */ diff --git a/hardinfo2/menu.c b/hardinfo2/menu.c index 2a8b6922..dc197129 100644 --- a/hardinfo2/menu.c +++ b/hardinfo2/menu.c @@ -84,7 +84,7 @@ static GtkActionEntry entries[] = {       G_CALLBACK(cb_refresh)},      {"OnlineDocsAction", GTK_STOCK_HELP, -     "Contents (online)", "F1", +     "Contents", "F1",       NULL,       G_CALLBACK(cb_open_online_docs)}, diff --git a/hardinfo2/shell.c b/hardinfo2/shell.c index 9e2ecbe6..50dc0232 100644 --- a/hardinfo2/shell.c +++ b/hardinfo2/shell.c @@ -19,17 +19,17 @@  #include <string.h>  #include <gtk/gtk.h> -#include <config.h> +#include "config.h" -#include <hardinfo.h> +#include "hardinfo.h" -#include <shell.h> -#include <syncmanager.h> -#include <iconcache.h> -#include <menu.h> -#include <stock.h> +#include "shell.h" +#include "syncmanager.h" +#include "iconcache.h" +#include "menu.h" +#include "stock.h" -#include <callbacks.h> +#include "callbacks.h"  /*   * Internal Prototypes ******************************************************** diff --git a/hardinfo2/shell.h b/hardinfo2/shell.h index a959c23c..3608c7a0 100644 --- a/hardinfo2/shell.h +++ b/hardinfo2/shell.h @@ -19,7 +19,9 @@  #define __SHELL_H__  #include <gtk/gtk.h> -#include <loadgraph.h> + +#include "loadgraph.h" +#include "help-viewer.h"  typedef struct _Shell			Shell;  typedef struct _ShellTree		ShellTree; @@ -97,6 +99,7 @@ struct _Shell {      gchar		*selected_module_name;      GKeyFile		*hosts; +    HelpViewer		*help_viewer;  };  struct _ShellTree { | 
