1 /* logjam - a GTK client for LiveJournal.
2 * Copyright (C) 2000-2003 Evan Martin <evan@livejournal.com>
4 * vim: tabstop=4 shiftwidth=4 noexpandtab :
10 #include <gtkhtml/gtkhtml.h>
16 #include <sys/types.h>
24 #define RESPONSE_UPDATE (1)
26 #define HTMLPREVIEW(obj) (G_TYPE_CHECK_INSTANCE_CAST((obj), html_preview_get_type(), HTMLPreview))
28 static GType
html_preview_get_type (void);
30 static void url_requested (GtkHTML
*html
, const char *url
, GtkHTMLStream
*handle
);
31 static void link_clicked (GtkHTML
*html
, const char *url
, gpointer data
);
34 static void html_preview_init (HTMLPreview
*hp
) {
35 gtk_html_construct((gpointer
) hp
);
36 g_signal_connect(G_OBJECT(hp
), "url_requested", G_CALLBACK(url_requested
), NULL
);
37 g_signal_connect(G_OBJECT(hp
), "link_clicked", G_CALLBACK(link_clicked
), NULL
);
41 GtkWidget
*html_preview_new (GetEntryFunc get_entry_f
, gpointer get_entry_data
) {
42 HTMLPreview
*hp
= HTMLPREVIEW(g_object_new(html_preview_get_type(), NULL
));
43 hp
->get_entry
= get_entry_f
;
44 hp
->get_entry_data
= get_entry_data
;
45 return GTK_WIDGET(hp
);
49 GType
html_preview_get_type (void) {
50 static GType hp_type
= 0;
52 const GTypeInfo hp_info
= {
61 (GInstanceInitFunc
) html_preview_init
,
63 hp_type
= g_type_register_static(GTK_TYPE_HTML
, "HTMLPreview", &hp_info
, 0);
69 static void link_clicked (GtkHTML
*html
, const char *url
, gpointer data
) {
70 spawn_url(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(html
))), url
);
74 #define PROVIDE_IMAGE(name) if (g_ascii_strcasecmp(url, "logjam:" #name) == 0) { gtk_html_write(html, handle, (const char *)logjam_##name##_png, sizeof(logjam_##name##_png)); } else
76 static void url_requested (GtkHTML
*html
, const char *url
, GtkHTMLStream
*handle
) {
79 PROVIDE_IMAGE(protected)
80 PROVIDE_IMAGE(private)
82 gtk_html_end(html
, handle
, GTK_HTML_STREAM_ERROR
);
85 gtk_html_end(html
, handle
, GTK_HTML_STREAM_OK
);
91 /* XXX not utf8 safe, but i think we're ok because we depend on ASCII characters in HTML and usernames. */
92 static const char *parse_ljtag (const char *src
, GString
*dst
) {
94 const char *start
, *end
;
95 const char *p
= src
+ 3;
96 while (*p
&& isspace(*p
)) ++p
;
98 if (*p
== 'u') isuser
= TRUE
;
99 else if (*p
== 'c') isuser
= FALSE
;
102 /* skip forward to equals sign. */
104 if (*p
== 0) return src
;
108 while (*p
&& isspace(*p
)) ++p
; /* skip spaces. */
109 if (*p
== '\'' || *p
== '"') ++p
; /* optional quote. */
112 while (*p
!= '\'' && *p
!= '"' && *p
!= '>' && *p
!= ' ' && *p
!= '/') {
113 if (*p
== 0) return src
;
118 if (*p
== '\'' || *p
== '"') ++p
;
119 while (*p
&& isspace(*p
)) ++p
;
121 while (*p
&& isspace(*p
)) ++p
;
122 if (*p
!= '>') return src
;
125 g_string_append_printf(dst
, "<a href='nowhere'>" "<img src='logjam:%s' align='bottom' border='0'/>" "<b>", (isuser
? "ljuser" : "ljcomm"));
126 g_string_append_len(dst
, start
, end
- start
);
127 g_string_append(dst
, "</b></a>");
133 static GString
*entry_prepare_preview (LJEntry
*entry
) {
134 GString
*str
= g_string_new(NULL
);
136 gboolean has_time
, has_security
;
138 if (!entry
) return str
;
140 has_time
= (entry
->time
.tm_year
> 0);
141 has_security
= (entry
->security
.type
!= LJ_SECURITY_PUBLIC
);
143 if (has_security
|| entry
->subject
|| has_time
) {
144 g_string_append(str
, "<table width='100%'><tr>");
145 if (has_security
|| entry
->subject
) {
146 g_string_append(str
, "<td align='left'>");
148 char *img
= (entry
->security
.type
== LJ_SECURITY_PRIVATE
? "private" : "protected");
149 g_string_append_printf(str
, "<img src='logjam:%s' align='bottom'/>", img
);
151 if (entry
->subject
) g_string_append_printf(str
, "<b>%s</b>", entry
->subject
);
152 g_string_append(str
, "</td>");
154 if (has_time
) g_string_append_printf(str
, "<td align='right'>%s</td>", asctime(&entry
->time
));
155 g_string_append(str
, "</tr></table><hr/><br/>");
158 if (entry
->mood
|| entry
->music
|| entry
->location
|| entry
->taglist
) {
159 if (entry
->mood
) g_string_append_printf(str
, "<i>%s</i>: %s<br/>", _("Current Mood"), entry
->mood
);
160 if (entry
->music
) g_string_append_printf(str
, "<i>%s</i>: %s<br/>", _("Current Music"), entry
->music
);
161 if (entry
->location
) g_string_append_printf(str
, "<i>%s</i>: %s<br/>", _("Current Location"), entry
->location
);
162 if (entry
->taglist
) g_string_append_printf(str
, "<i>%s</i>: %s<br/>", _("Tags"), entry
->taglist
);
163 g_string_append(str
, "<br/>");
166 /* insert <br/> tags (if preformmated) and fixup <lj user=foo> tags. */
167 for (event
= entry
->event
; *event
; event
++) {
168 if (*event
== '\n' && !entry
->preformatted
) {
169 g_string_append_len(str
, "<br/>", 5);
170 } else if (event
[0] == '<' &&
171 /* check for lj user / lj comm. */
172 /* this is not good html parsing, but it should be enough.
173 * besides, the whole <lj user='foo'> tag is yucky html. :P */
174 event
[1] == 'l' && event
[2] == 'j' && (event
[3] == ' ' || (event
[3] == 'r' && event
[4] == ' '))) {
175 const char *p
= parse_ljtag(event
, str
);
181 g_string_append_c(str
, *event
);
189 void preview_update (HTMLPreview
*hp
) {
191 LJEntry
*entry
= hp
->get_entry(hp
->get_entry_data
);
193 str
= entry_prepare_preview(entry
);
194 /* gtkhtml whines if you give it an empty string. */
195 if (str
->str
[0] == '\0') g_string_append_c(str
, ' ');
196 gtk_html_load_from_string(GTK_HTML(hp
), str
->str
, -1);
197 g_string_free(str
, TRUE
);
198 lj_entry_free(entry
);
202 static LJEntry
*get_entry_from_jw (JamWin
*jw
) {
203 return jam_doc_get_entry(jw
->doc
);
207 static void response_cb (GtkWidget
*dlg
, gint response
, PreviewUI
*pui
) {
208 if (response
== RESPONSE_UPDATE
) preview_update(pui
->html
); else gtk_widget_destroy(dlg
);
212 static void make_window (PreviewUI
*pui
) {
213 pui
->win
= gtk_dialog_new_with_buttons(_("HTML Preview"),
215 GTK_DIALOG_DESTROY_WITH_PARENT
| GTK_DIALOG_NO_SEPARATOR
,
216 _("_Update"), RESPONSE_UPDATE
, GTK_STOCK_CLOSE
, GTK_RESPONSE_OK
, NULL
);
217 gtk_window_set_default_size(GTK_WINDOW(pui
->win
), 500, 400);
218 geometry_tie(GTK_WIDGET(pui
->win
), GEOM_PREVIEW
);
219 /* this reportedly breaks some wms.
220 * gtk_window_set_type_hint(GTK_WINDOW(pui->win),
221 GDK_WINDOW_TYPE_HINT_UTILITY);*/
222 g_signal_connect(G_OBJECT(pui
->win
), "response", G_CALLBACK(response_cb
), pui
);
224 pui
->html
= HTMLPREVIEW(html_preview_new((GetEntryFunc
) get_entry_from_jw
, pui
->jw
));
225 preview_update(pui
->html
);
227 jam_dialog_set_contents(GTK_DIALOG(pui
->win
), scroll_wrap(GTK_WIDGET(pui
->html
)));
231 void preview_ui_show (JamWin
*jw
) {
236 preview_update(pui
->html
);
237 gtk_window_present(GTK_WINDOW(pui
->win
));
241 jw
->preview
= pui
= g_new0(PreviewUI
, 1);
244 g_signal_connect_swapped(G_OBJECT(pui
->win
), "destroy", G_CALLBACK(g_free
), pui
);
245 gtk_widget_show_all(pui
->win
);
247 g_object_add_weak_pointer(G_OBJECT(pui
->win
), &jw
->preview
);